From 8a5da0eb0e1ae03796928bfd18f99f32626c25e8 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sat, 23 Sep 2017 14:36:01 -0700 Subject: [PATCH 1/8] Geometry: set worldTransform to identity if ignoreTranform is true --- .../src/main/java/com/jme3/scene/Geometry.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/Geometry.java b/jme3-core/src/main/java/com/jme3/scene/Geometry.java index 9bd7ace97..6ba209bc3 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -137,6 +137,20 @@ public class Geometry extends Spatial { return super.checkCulling(cam); } + /** + * Update the world transform of this Geometry and clear the + * TRANSFORM refresh flag. + */ + @Override + void checkDoTransformUpdate() { + if (ignoreTransform) { + worldTransform.loadIdentity(); + refreshFlags &= ~RF_TRANSFORM; + } else { + super.checkDoTransformUpdate(); + } + } + /** * @return If ignoreTransform mode is set. * @@ -151,6 +165,7 @@ public class Geometry extends Spatial { */ public void setIgnoreTransform(boolean ignoreTransform) { this.ignoreTransform = ignoreTransform; + setTransformRefresh(); } /** @@ -398,9 +413,6 @@ public class Geometry extends Spatial { // Compute the cached world matrix cachedWorldMat.loadIdentity(); - if (ignoreTransform) { - return; - } cachedWorldMat.setRotationQuaternion(worldTransform.getRotation()); cachedWorldMat.setTranslation(worldTransform.getTranslation()); From 7b2f1094b14b481f39a375039f5bfe0808dbe829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=CC=81my=20Bouquet?= Date: Sun, 24 Sep 2017 15:55:46 +0200 Subject: [PATCH 2/8] Gltf fix data reading --- .../src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index a97d40017..ca9d03726 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -367,6 +367,7 @@ public class GltfLoader implements AssetLoader { buffs.weights = readAccessorData(entry.getValue().getAsInt(), new FloatArrayPopulator()); } else { VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(getVertexBufferType(bufferType))); + System.err.println(bufferType); if (vb != null) { mesh.setBuffer(vb); } @@ -527,7 +528,8 @@ public class GltfLoader implements AssetLoader { BinDataKey key = new BinDataKey(info.getKey().getFolder() + uri); InputStream input = (InputStream) info.getManager().loadAsset(key); data = new byte[bufferLength]; - input.read(data); + DataInputStream dataStream = new DataInputStream(input); + dataStream.readFully(data); } } else { //no URI this should not happen in a gltf file, only in glb files. From c17c3e9605eb6e84605629c9298f2c8cb5283ec6 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sun, 24 Sep 2017 16:02:45 +0200 Subject: [PATCH 3/8] Remove leftover sys err --- .../src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index ca9d03726..01371bee9 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -367,7 +367,6 @@ public class GltfLoader implements AssetLoader { buffs.weights = readAccessorData(entry.getValue().getAsInt(), new FloatArrayPopulator()); } else { VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(getVertexBufferType(bufferType))); - System.err.println(bufferType); if (vb != null) { mesh.setBuffer(vb); } From 3f99c801090b2dc822444b7133ab8a0b52b3f308 Mon Sep 17 00:00:00 2001 From: Nehon Date: Wed, 27 Sep 2017 10:00:49 +0200 Subject: [PATCH 4/8] glTF: properly close opened stream and avoid caching them. Fixed an issue when an animation track didn't have any translation or scale entries --- .../jme3/scene/plugins/gltf/BinDataKey.java | 6 +++++ .../jme3/scene/plugins/gltf/GltfLoader.java | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinDataKey.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinDataKey.java index c84678751..505ca29c3 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinDataKey.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinDataKey.java @@ -1,6 +1,7 @@ package com.jme3.scene.plugins.gltf; import com.jme3.asset.AssetKey; +import com.jme3.asset.cache.AssetCache; /** * Created by Nehon on 09/09/2017. @@ -9,4 +10,9 @@ class BinDataKey extends AssetKey { public BinDataKey(String name) { super(name); } + + @Override + public Class getCacheType() { + return null; + } } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index 01371bee9..0c6e98f72 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -140,6 +140,8 @@ public class GltfLoader implements AssetLoader { return rootNode; } catch (Exception e) { throw new AssetLoadException("An error occurred loading " + assetInfo.getKey().getName(), e); + } finally { + stream.close(); } } @@ -529,6 +531,7 @@ public class GltfLoader implements AssetLoader { data = new byte[bufferLength]; DataInputStream dataStream = new DataInputStream(input); dataStream.readFully(data); + dataStream.close(); } } else { //no URI this should not happen in a gltf file, only in glb files. @@ -1147,6 +1150,25 @@ public class GltfLoader implements AssetLoader { float[] weights; public void update() { + if (translations == null) { + translations = new Vector3f[times.length]; + for (int i = 0; i < translations.length; i++) { + translations[i] = new Vector3f(); + } + } + if (rotations == null) { + rotations = new Quaternion[times.length]; + for (int i = 0; i < rotations.length; i++) { + rotations[i] = new Quaternion(); + } + } + if (scales == null) { + scales = new Vector3f[times.length]; + for (int i = 0; i < scales.length; i++) { + scales[i] = new Vector3f().set(Vector3f.UNIT_XYZ); + } + } + if (times[0] > 0) { //Anim doesn't start at 0, JME can't handle that and will interpolate transforms linearly from 0 to the first frame of the anim. //we need to add a frame at 0 that copies the first real frame From 083f21d6a22f499a781a642607c6d5cc39b6ba4b Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Wed, 27 Sep 2017 21:15:46 -0700 Subject: [PATCH 5/8] FlyByCamera: comments, annotations, & imports; address GitHub issue #697 --- .../main/java/com/jme3/input/FlyByCamera.java | 256 ++++++++++++------ 1 file changed, 167 insertions(+), 89 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java index dd9f00dc1..30d7a3b78 100644 --- a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2017 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,7 +32,11 @@ package com.jme3.input; import com.jme3.collision.MotionAllowedListener; -import com.jme3.input.controls.*; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseAxisTrigger; +import com.jme3.input.controls.MouseButtonTrigger; import com.jme3.math.FastMath; import com.jme3.math.Matrix3f; import com.jme3.math.Quaternion; @@ -40,12 +44,13 @@ import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; /** - * A first person view camera controller. - * After creation, you must register the camera controller with the - * dispatcher using #registerWithDispatcher(). + * A first-person camera controller. + * + * After creation, you (or FlyCamAppState) must register the controller using + * {@link #registerWithInput(com.jme3.input.InputManager)}. * * Controls: - * - Move the mouse to rotate the camera + * - Move (or, in drag-to-rotate mode, drag) the mouse to rotate the camera * - Mouse wheel for zooming in or out * - WASD keys for moving forward/backward and strafing * - QZ keys raise or lower the camera @@ -53,41 +58,62 @@ import com.jme3.renderer.Camera; public class FlyByCamera implements AnalogListener, ActionListener { private static String[] mappings = new String[]{ - CameraInput.FLYCAM_LEFT, - CameraInput.FLYCAM_RIGHT, - CameraInput.FLYCAM_UP, - CameraInput.FLYCAM_DOWN, - - CameraInput.FLYCAM_STRAFELEFT, - CameraInput.FLYCAM_STRAFERIGHT, - CameraInput.FLYCAM_FORWARD, - CameraInput.FLYCAM_BACKWARD, - - CameraInput.FLYCAM_ZOOMIN, - CameraInput.FLYCAM_ZOOMOUT, - CameraInput.FLYCAM_ROTATEDRAG, - - CameraInput.FLYCAM_RISE, - CameraInput.FLYCAM_LOWER, - - CameraInput.FLYCAM_INVERTY - }; + CameraInput.FLYCAM_LEFT, + CameraInput.FLYCAM_RIGHT, + CameraInput.FLYCAM_UP, + CameraInput.FLYCAM_DOWN, + + CameraInput.FLYCAM_STRAFELEFT, + CameraInput.FLYCAM_STRAFERIGHT, + CameraInput.FLYCAM_FORWARD, + CameraInput.FLYCAM_BACKWARD, + + CameraInput.FLYCAM_ZOOMIN, + CameraInput.FLYCAM_ZOOMOUT, + CameraInput.FLYCAM_ROTATEDRAG, + CameraInput.FLYCAM_RISE, + CameraInput.FLYCAM_LOWER, + + CameraInput.FLYCAM_INVERTY + }; + /** + * camera controlled by this controller (not null) + */ protected Camera cam; + /** + * normalized "up" direction (a unit vector) + */ protected Vector3f initialUpVec; + /** + * rotation-rate multiplier (1=default) + */ protected float rotationSpeed = 1f; + /** + * translation speed (in world units per second) + */ protected float moveSpeed = 3f; + /** + * zoom-rate multiplier (1=default) + */ protected float zoomSpeed = 1f; protected MotionAllowedListener motionAllowed = null; + /** + * enable flag for controller (false→ignoring input) + */ protected boolean enabled = true; + /** + * drag-to-rotate mode flag + */ protected boolean dragToRotate = false; protected boolean canRotate = false; protected boolean invertY = false; protected InputManager inputManager; - + /** - * Creates a new FlyByCamera to control the given Camera object. - * @param cam + * Creates a new FlyByCamera to control the specified camera. + * + * @param cam camera to be controlled (not null) */ public FlyByCamera(Camera cam){ this.cam = cam; @@ -96,10 +122,11 @@ public class FlyByCamera implements AnalogListener, ActionListener { /** * Sets the up vector that should be used for the camera. + * * @param upVec */ public void setUpVector(Vector3f upVec) { - initialUpVec.set(upVec); + initialUpVec.set(upVec); } public void setMotionAllowedListener(MotionAllowedListener listener){ @@ -107,56 +134,68 @@ public class FlyByCamera implements AnalogListener, ActionListener { } /** - * Sets the move speed. The speed is given in world units per second. - * @param moveSpeed + * Set the translation speed. + * + * @param moveSpeed new speed (in world units per second) */ public void setMoveSpeed(float moveSpeed){ this.moveSpeed = moveSpeed; } - + /** - * Gets the move speed. The speed is given in world units per second. - * @return moveSpeed + * Read the translation speed. + * + * @return current speed (in world units per second) */ public float getMoveSpeed(){ return moveSpeed; } /** - * Sets the rotation speed. - * @param rotationSpeed + * Set the rotation-rate multiplier. The bigger the multiplier, the more + * rotation for a given movement of the mouse. + * + * @param rotationSpeed new rate multiplier (1=default) */ public void setRotationSpeed(float rotationSpeed){ this.rotationSpeed = rotationSpeed; } - + /** - * Gets the move speed. The speed is given in world units per second. - * @return rotationSpeed + * Read the rotation-rate multiplier. The bigger the multiplier, the more + * rotation for a given movement of the mouse. + * + * @return current rate multiplier (1=default) */ public float getRotationSpeed(){ return rotationSpeed; } - + /** - * Sets the zoom speed. - * @param zoomSpeed + * Set the zoom-rate multiplier. The bigger the multiplier, the more zoom + * for a given movement of the mouse wheel. + * + * @param zoomSpeed new rate multiplier (1=default) */ public void setZoomSpeed(float zoomSpeed) { this.zoomSpeed = zoomSpeed; } - + /** - * Gets the zoom speed. The speed is a multiplier to increase/decrease - * the zoom rate. - * @return zoomSpeed + * Read the zoom-rate multiplier. The bigger the multiplier, the more zoom + * for a given movement of the mouse wheel. + * + * @return current rate multiplier (1=default) */ public float getZoomSpeed() { return zoomSpeed; } /** - * @param enable If false, the camera will ignore input. + * Enable or disable this controller. When disabled, the controller ignored + * input. + * + * @param enable true to enable, false to disable */ public void setEnabled(boolean enable){ if (enabled && !enable){ @@ -168,32 +207,36 @@ public class FlyByCamera implements AnalogListener, ActionListener { } /** - * @return If enabled - * @see FlyByCamera#setEnabled(boolean) + * Test whether this controller is enabled. + * + * @return true if enabled, otherwise false + * @see #setEnabled(boolean) */ public boolean isEnabled(){ return enabled; } /** + * Test whether drag-to-rotate mode is enabled. + * * @return If drag to rotate feature is enabled. * - * @see FlyByCamera#setDragToRotate(boolean) + * @see #setDragToRotate(boolean) */ public boolean isDragToRotate() { return dragToRotate; } /** - * Set if drag to rotate mode is enabled. - * - * When true, the user must hold the mouse button - * and drag over the screen to rotate the camera, and the cursor is - * visible until dragged. Otherwise, the cursor is invisible at all times - * and holding the mouse button is not needed to rotate the camera. - * This feature is disabled by default. - * - * @param dragToRotate True if drag to rotate mode is enabled. + * Enable or disable drag-to-rotate mode. + * + * When drag-to-rotate mode is enabled, the user must hold the mouse button + * and drag over the screen to rotate the camera, and the cursor is visible + * until dragged. When drag-to-rotate mode is disabled, the cursor is + * invisible at all times and holding the mouse button is not needed to + * rotate the camera. This mode is disabled by default. + * + * @param dragToRotate true to enable, false to disable */ public void setDragToRotate(boolean dragToRotate) { this.dragToRotate = dragToRotate; @@ -203,25 +246,26 @@ public class FlyByCamera implements AnalogListener, ActionListener { } /** - * Registers the FlyByCamera to receive input events from the provided - * Dispatcher. + * Register this controller to receive input events from the specified input + * manager. + * * @param inputManager */ public void registerWithInput(InputManager inputManager){ this.inputManager = inputManager; - + // both mouse and button - rotation of cam inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true), - new KeyTrigger(KeyInput.KEY_LEFT)); + new KeyTrigger(KeyInput.KEY_LEFT)); inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false), - new KeyTrigger(KeyInput.KEY_RIGHT)); + new KeyTrigger(KeyInput.KEY_RIGHT)); inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false), - new KeyTrigger(KeyInput.KEY_UP)); + new KeyTrigger(KeyInput.KEY_UP)); inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true), - new KeyTrigger(KeyInput.KEY_DOWN)); + new KeyTrigger(KeyInput.KEY_DOWN)); // mouse only - zoom in/out with wheel, and rotate drag inputManager.addMapping(CameraInput.FLYCAM_ZOOMIN, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); @@ -248,43 +292,42 @@ public class FlyByCamera implements AnalogListener, ActionListener { } protected void mapJoystick( Joystick joystick ) { - + // Map it differently if there are Z axis if( joystick.getAxis( JoystickAxis.Z_ROTATION ) != null && joystick.getAxis( JoystickAxis.Z_AXIS ) != null ) { - + // Make the left stick move joystick.getXAxis().assignAxis( CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT ); joystick.getYAxis().assignAxis( CameraInput.FLYCAM_BACKWARD, CameraInput.FLYCAM_FORWARD ); - - // And the right stick control the camera + + // And the right stick control the camera joystick.getAxis( JoystickAxis.Z_ROTATION ).assignAxis( CameraInput.FLYCAM_DOWN, CameraInput.FLYCAM_UP ); joystick.getAxis( JoystickAxis.Z_AXIS ).assignAxis( CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_LEFT ); - - // And let the dpad be up and down + + // And let the dpad be up and down joystick.getPovYAxis().assignAxis(CameraInput.FLYCAM_RISE, CameraInput.FLYCAM_LOWER); - - if( joystick.getButton( "Button 8" ) != null ) { + + if( joystick.getButton( "Button 8" ) != null ) { // Let the stanard select button be the y invert toggle joystick.getButton( "Button 8" ).assignButton( CameraInput.FLYCAM_INVERTY ); - } - - } else { + } + + } else { joystick.getPovXAxis().assignAxis(CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT); joystick.getPovYAxis().assignAxis(CameraInput.FLYCAM_FORWARD, CameraInput.FLYCAM_BACKWARD); joystick.getXAxis().assignAxis(CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_LEFT); joystick.getYAxis().assignAxis(CameraInput.FLYCAM_DOWN, CameraInput.FLYCAM_UP); - } + } } /** - * Unregisters the FlyByCamera from the event Dispatcher. + * Unregister this controller from its input manager. */ public void unregisterInput(){ - if (inputManager == null) { return; } - + for (String s : mappings) { if (inputManager.hasMapping(s)) { inputManager.deleteMapping( s ); @@ -296,12 +339,16 @@ public class FlyByCamera implements AnalogListener, ActionListener { Joystick[] joysticks = inputManager.getJoysticks(); if (joysticks != null && joysticks.length > 0){ - Joystick joystick = joysticks[0]; - - // No way to unassing axis + // No way to unassign axis } } + /** + * Rotate the camera by the specified amount around the specified axis. + * + * @param value rotation amount + * @param axis direction of rotation (a unit vector) + */ protected void rotateCamera(float value, Vector3f axis){ if (dragToRotate){ if (canRotate){ @@ -329,6 +376,11 @@ public class FlyByCamera implements AnalogListener, ActionListener { cam.setAxes(q); } + /** + * Zoom the camera by the specified amount. + * + * @param value zoom amount + */ protected void zoomCamera(float value){ // derive fovY value float h = cam.getFrustumTop(); @@ -338,7 +390,7 @@ public class FlyByCamera implements AnalogListener, ActionListener { float near = cam.getFrustumNear(); float fovY = FastMath.atan(h / near) - / (FastMath.DEG_TO_RAD * .5f); + / (FastMath.DEG_TO_RAD * .5f); float newFovY = fovY + value * 0.1f * zoomSpeed; if (newFovY > 0f) { // Don't let the FOV go zero or negative. @@ -354,6 +406,11 @@ public class FlyByCamera implements AnalogListener, ActionListener { cam.setFrustumRight(w); } + /** + * Translate the camera upward by the specified amount. + * + * @param value translation amount + */ protected void riseCamera(float value){ Vector3f vel = new Vector3f(0, value * moveSpeed, 0); Vector3f pos = cam.getLocation().clone(); @@ -366,6 +423,12 @@ public class FlyByCamera implements AnalogListener, ActionListener { cam.setLocation(pos); } + /** + * Translate the camera left or forward by the specified amount. + * + * @param value translation amount + * @param sideways true→left, false→forward + */ protected void moveCamera(float value, boolean sideways){ Vector3f vel = new Vector3f(); Vector3f pos = cam.getLocation().clone(); @@ -385,6 +448,14 @@ public class FlyByCamera implements AnalogListener, ActionListener { cam.setLocation(pos); } + /** + * Callback to notify this controller of an analog input event. + * + * @param name name of the input event + * @param value value of the axis (from 0 to 1) + * @param tpf time per frame (in seconds) + */ + @Override public void onAnalog(String name, float value, float tpf) { if (!enabled) return; @@ -416,6 +487,14 @@ public class FlyByCamera implements AnalogListener, ActionListener { } } + /** + * Callback to notify this controller of an action input event. + * + * @param name name of the input event + * @param value true if the action is "pressed", false otherwise + * @param tpf time per frame (in seconds) + */ + @Override public void onAction(String name, boolean value, float tpf) { if (!enabled) return; @@ -424,11 +503,10 @@ public class FlyByCamera implements AnalogListener, ActionListener { canRotate = value; inputManager.setCursorVisible(!value); } else if (name.equals(CameraInput.FLYCAM_INVERTY)) { - // Toggle on the up. - if( !value ) { + // Invert the "up" direction. + if( !value ) { invertY = !invertY; } - } + } } - } From db23985f92fd52a4a6223b382bf9a701f34ffa14 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sat, 30 Sep 2017 15:34:29 +0200 Subject: [PATCH 6/8] glTf: proper animation data padding when transforms are given as sparse arrays --- .../com/jme3/scene/plugins/gltf/AnimData.java | 296 ++++++++++++++++++ .../jme3/scene/plugins/gltf/GltfLoader.java | 90 +----- 2 files changed, 312 insertions(+), 74 deletions(-) create mode 100644 jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/AnimData.java diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/AnimData.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/AnimData.java new file mode 100644 index 000000000..890b74239 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/AnimData.java @@ -0,0 +1,296 @@ +package com.jme3.scene.plugins.gltf; + +import com.jme3.math.*; + +import java.util.*; + +public class AnimData { + + public enum Type { + Translation, + Rotation, + Scale + } + + Float length; + float[] times; + List timeArrays = new ArrayList<>(); + + + Vector3f[] translations; + Quaternion[] rotations; + Vector3f[] scales; + //not used for now + float[] weights; + + public void update() { + + if (equalTimes(timeArrays)) { + times = timeArrays.get(0).times; + ensureArraysInit(); + } else { + //Times array are different and contains different sampling times. + //We have to merge them because JME needs the 3 types of transforms for each keyFrame. + + //extracting keyframes information + List keyFrames = new ArrayList<>(); + TimeData timeData = timeArrays.get(0); + Type type = timeData.type; + for (int i = 0; i < timeData.times.length; i++) { + float time = timeData.times[i]; + KeyFrame keyFrame = new KeyFrame(); + keyFrame.time = time; + setKeyFrameTransforms(type, keyFrame, timeData.times); + keyFrames.add(keyFrame); + } + + for (int i = 1; i < timeArrays.size(); i++) { + timeData = timeArrays.get(i); + type = timeData.type; + for (float time : timeData.times) { + for (int j = 0; j < keyFrames.size(); j++) { + KeyFrame kf = keyFrames.get(j); + if (Float.floatToIntBits(kf.time) != Float.floatToIntBits(time)) { + if (time > kf.time) { + continue; + } else { + kf = new KeyFrame(); + kf.time = time; + keyFrames.add(j, kf); + //we inserted a keyframe let's shift the counter. + j++; + } + } + setKeyFrameTransforms(type, kf, timeData.times); + break; + } + } + } + // populating transforms array from the keyframes, interpolating + times = new float[keyFrames.size()]; + + ensureArraysInit(); + + TransformIndices translationIndices = new TransformIndices(); + TransformIndices rotationIndices = new TransformIndices(); + TransformIndices scaleIndices = new TransformIndices(); + + for (int i = 0; i < keyFrames.size(); i++) { + KeyFrame kf = keyFrames.get(i); + //we need Interpolate between keyframes when transforms are sparse. + times[i] = kf.time; + populateTransform(Type.Translation, i, keyFrames, kf, translationIndices); + populateTransform(Type.Rotation, i, keyFrames, kf, rotationIndices); + populateTransform(Type.Scale, i, keyFrames, kf, scaleIndices); + } + } + + ensureArraysInit(); + + if (times[0] > 0) { + //Anim doesn't start at 0, JME can't handle that and will interpolate transforms linearly from 0 to the first frame of the anim. + //we need to add a frame at 0 that copies the first real frame + + float[] newTimes = new float[times.length + 1]; + newTimes[0] = 0f; + System.arraycopy(times, 0, newTimes, 1, times.length); + times = newTimes; + + if (translations != null) { + Vector3f[] newTranslations = new Vector3f[translations.length + 1]; + newTranslations[0] = translations[0]; + System.arraycopy(translations, 0, newTranslations, 1, translations.length); + translations = newTranslations; + } + if (rotations != null) { + Quaternion[] newRotations = new Quaternion[rotations.length + 1]; + newRotations[0] = rotations[0]; + System.arraycopy(rotations, 0, newRotations, 1, rotations.length); + rotations = newRotations; + } + if (scales != null) { + Vector3f[] newScales = new Vector3f[scales.length + 1]; + newScales[0] = scales[0]; + System.arraycopy(scales, 0, newScales, 1, scales.length); + scales = newScales; + } + } + + length = times[times.length - 1]; + } + + private void populateTransform(Type type, int index, List keyFrames, KeyFrame currentKeyFrame, TransformIndices transformIndices) { + Object transform = getTransform(type, currentKeyFrame); + if (transform != null) { + getArray(type)[index] = transform; + transformIndices.last = index; + } else { + transformIndices.next = findNext(keyFrames, type, index); + if (transformIndices.next == -1) { + //no next let's use prev value. + if (transformIndices.last == -1) { + //last Transform Index = -1 it means there are no transforms. nothing more to do + return; + } + KeyFrame lastKeyFrame = keyFrames.get(transformIndices.last); + getArray(type)[index] = getTransform(type, lastKeyFrame); + return; + } + KeyFrame nextKeyFrame = keyFrames.get(transformIndices.next); + if (transformIndices.last == -1) { + //no previous transforms let's use the new one. + translations[index] = nextKeyFrame.translation; + } else { + //interpolation between the previous transform and the next one. + KeyFrame lastKeyFrame = keyFrames.get(transformIndices.last); + float ratio = currentKeyFrame.time / (nextKeyFrame.time - lastKeyFrame.time); + interpolate(type, ratio, lastKeyFrame, nextKeyFrame, index); + } + + } + } + + private int findNext(List keyFrames, Type type, int fromIndex) { + for (int i = fromIndex + 1; i < keyFrames.size(); i++) { + KeyFrame kf = keyFrames.get(i); + switch (type) { + case Translation: + if (kf.translation != null) { + return i; + } + break; + case Rotation: + if (kf.rotation != null) { + return i; + } + break; + case Scale: + if (kf.scale != null) { + return i; + } + break; + } + } + return -1; + } + + private void interpolate(Type type, float ratio, KeyFrame lastKeyFrame, KeyFrame nextKeyFrame, int currentIndex) { + //TODO here we should interpolate differently according to the interpolation given in the gltf file. + switch (type) { + case Translation: + translations[currentIndex] = FastMath.interpolateLinear(ratio, lastKeyFrame.translation, nextKeyFrame.translation); + break; + case Rotation: + Quaternion rot = new Quaternion().set(lastKeyFrame.rotation); + rot.nlerp(nextKeyFrame.rotation, ratio); + rotations[currentIndex] = rot; + break; + case Scale: + scales[currentIndex] = FastMath.interpolateLinear(ratio, lastKeyFrame.scale, nextKeyFrame.scale); + break; + } + } + + private Object[] getArray(Type type) { + switch (type) { + case Translation: + return translations; + case Rotation: + return rotations; + case Scale: + return scales; + default: + return translations; + } + } + + private Object getTransform(Type type, KeyFrame kf) { + switch (type) { + case Translation: + return kf.translation; + case Rotation: + return kf.rotation; + case Scale: + return kf.scale; + default: + return kf.translation; + } + } + + private void ensureArraysInit() { + if (translations == null || translations.length < times.length) { + translations = new Vector3f[times.length]; + for (int i = 0; i < translations.length; i++) { + translations[i] = new Vector3f(); + } + } + if (rotations == null || rotations.length < times.length) { + rotations = new Quaternion[times.length]; + for (int i = 0; i < rotations.length; i++) { + rotations[i] = new Quaternion(); + } + } + if (scales == null || scales.length < times.length) { + scales = new Vector3f[times.length]; + for (int i = 0; i < scales.length; i++) { + scales[i] = new Vector3f().set(Vector3f.UNIT_XYZ); + } + } + } + + private void setKeyFrameTransforms(Type type, KeyFrame keyFrame, float[] transformTimes) { + int index = 0; + while (Float.floatToIntBits(transformTimes[index]) != Float.floatToIntBits(keyFrame.time)) { + index++; + } + switch (type) { + case Translation: + keyFrame.translation = translations[index]; + break; + case Rotation: + keyFrame.rotation = rotations[index]; + break; + case Scale: + keyFrame.scale = scales[index]; + break; + } + } + + private boolean equalTimes(List timeData) { + if (timeData.size() == 1) { + return true; + } + float[] times0 = timeData.get(0).times; + for (int i = 1; i < timeData.size(); i++) { + float[] timesI = timeData.get(i).times; + if (!Arrays.equals(times0, timesI)) { + return false; + } + } + return true; + } + + static class TimeData { + + float[] times; + Type type; + + public TimeData(float[] times, Type type) { + this.times = times; + this.type = type; + } + } + + private class TransformIndices { + int last = -1; + int next = -1; + } + + private class KeyFrame { + float time; + Vector3f translation; + Quaternion rotation; + Vector3f scale; + } + +} \ No newline at end of file diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index 0c6e98f72..a1353d33f 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java @@ -19,6 +19,7 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator; import javax.xml.bind.DatatypeConverter; import java.io.*; import java.nio.Buffer; +import java.sql.Time; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -760,27 +761,27 @@ public class GltfLoader implements AssetLoader { times = readAccessorData(timeIndex, floatArrayPopulator); addToCache("accessors", timeIndex, times, accessors.size()); } - if (animData.times == null) { - animData.times = times; - } else { - //check if we are loading the same time array - if (animData.times != times) { - //TODO there might be work to do here... if the inputs are different we might want to merge the different times array... - //easier said than done. - logger.log(Level.WARNING, "Channel has different input accessors for samplers"); - } - } - if (animData.length == null) { - //animation length is the last timestamp - animData.length = times[times.length - 1]; - } +// if (animData.times == null) { +// animData.times = times; +// } else { +// //check if we are loading the same time array +// if (animData.times != times) { +// //TODO there might be work to do here... if the inputs are different we might want to merge the different times array... +// //easier said than done. +// logger.log(Level.WARNING, "Channel has different input accessors for samplers"); +// } +// } + if (targetPath.equals("translation")) { + animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Translation)); Vector3f[] translations = readAccessorData(dataIndex, vector3fArrayPopulator); animData.translations = translations; } else if (targetPath.equals("scale")) { + animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Scale)); Vector3f[] scales = readAccessorData(dataIndex, vector3fArrayPopulator); animData.scales = scales; } else if (targetPath.equals("rotation")) { + animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Rotation)); Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator); animData.rotations = rotations; } @@ -801,10 +802,10 @@ public class GltfLoader implements AssetLoader { if (animData == null) { continue; } + animData.update(); if (animData.length > anim.getLength()) { anim.setLength(animData.length); } - animData.update(); Object node = fetchFromCache("nodes", i, Object.class); if (node instanceof Spatial) { Spatial s = (Spatial) node; @@ -1140,65 +1141,6 @@ public class GltfLoader implements AssetLoader { } } - private class AnimData { - Float length; - float[] times; - Vector3f[] translations; - Quaternion[] rotations; - Vector3f[] scales; - //not used for now - float[] weights; - - public void update() { - if (translations == null) { - translations = new Vector3f[times.length]; - for (int i = 0; i < translations.length; i++) { - translations[i] = new Vector3f(); - } - } - if (rotations == null) { - rotations = new Quaternion[times.length]; - for (int i = 0; i < rotations.length; i++) { - rotations[i] = new Quaternion(); - } - } - if (scales == null) { - scales = new Vector3f[times.length]; - for (int i = 0; i < scales.length; i++) { - scales[i] = new Vector3f().set(Vector3f.UNIT_XYZ); - } - } - - if (times[0] > 0) { - //Anim doesn't start at 0, JME can't handle that and will interpolate transforms linearly from 0 to the first frame of the anim. - //we need to add a frame at 0 that copies the first real frame - - float[] newTimes = new float[times.length + 1]; - newTimes[0] = 0f; - System.arraycopy(times, 0, newTimes, 1, times.length); - times = newTimes; - - if (translations != null) { - Vector3f[] newTranslations = new Vector3f[translations.length + 1]; - newTranslations[0] = translations[0]; - System.arraycopy(translations, 0, newTranslations, 1, translations.length); - translations = newTranslations; - } - if (rotations != null) { - Quaternion[] newRotations = new Quaternion[rotations.length + 1]; - newRotations[0] = rotations[0]; - System.arraycopy(rotations, 0, newRotations, 1, rotations.length); - rotations = newRotations; - } - if (scales != null) { - Vector3f[] newScales = new Vector3f[scales.length + 1]; - newScales[0] = scales[0]; - System.arraycopy(scales, 0, newScales, 1, scales.length); - scales = newScales; - } - } - } - } private class BoneWrapper { Bone bone; From 2632c3227c8188d4b6937c677e46729ce01b8074 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Sun, 1 Oct 2017 00:12:43 -0700 Subject: [PATCH 7/8] fix issue #749: NPE in CollideIgnoreTransformTest --- .../java/com/jme3/collision/CollideIgnoreTransformTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jme3-core/src/test/java/com/jme3/collision/CollideIgnoreTransformTest.java b/jme3-core/src/test/java/com/jme3/collision/CollideIgnoreTransformTest.java index f2eb63ef8..e098c22ba 100644 --- a/jme3-core/src/test/java/com/jme3/collision/CollideIgnoreTransformTest.java +++ b/jme3-core/src/test/java/com/jme3/collision/CollideIgnoreTransformTest.java @@ -42,6 +42,8 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.Node; import com.jme3.scene.shape.Quad; +import com.jme3.system.JmeSystem; +import com.jme3.system.MockJmeSystemDelegate; import org.junit.Test; /** @@ -87,6 +89,7 @@ public class CollideIgnoreTransformTest { @Test public void testPhantomTriangles() { + JmeSystem.setSystemDelegate(new MockJmeSystemDelegate()); assetManager = new DesktopAssetManager(); assetManager.registerLocator(null, ClasspathLocator.class); assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); From 556e3de1c07f51958ee452f05bab26a4d3b23ebb Mon Sep 17 00:00:00 2001 From: Alexandr Brui Date: Mon, 2 Oct 2017 03:48:38 +0300 Subject: [PATCH 8/8] fixed asset linked node to reuse shared data between loaded models. (#739) * fixed asset linked node to reuse shared data between loaded models. * updated implementation of binary loader. --- .../java/com/jme3/scene/AssetLinkNode.java | 30 +++++++------- .../main/resources/com/jme3/asset/General.cfg | 4 +- .../com/jme3/export/binary/BinaryLoader.java | 41 +++++++++++++++++++ 3 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 jme3-core/src/plugins/java/com/jme3/export/binary/BinaryLoader.java diff --git a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java index d31997f5b..82711ac6b 100644 --- a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java @@ -31,16 +31,15 @@ */ package com.jme3.scene; -import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetManager; import com.jme3.asset.ModelKey; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; -import com.jme3.export.binary.BinaryImporter; -import com.jme3.util.clone.Cloner; import com.jme3.util.SafeArrayList; +import com.jme3.util.clone.Cloner; + import java.io.IOException; import java.util.*; import java.util.Map.Entry; @@ -164,25 +163,24 @@ public class AssetLinkNode extends Node { @Override public void read(JmeImporter e) throws IOException { super.read(e); - InputCapsule capsule = e.getCapsule(this); - BinaryImporter importer = BinaryImporter.getInstance(); - AssetManager loaderManager = e.getAssetManager(); + + final InputCapsule capsule = e.getCapsule(this); + final AssetManager assetManager = e.getAssetManager(); assetLoaderKeys = (ArrayList) capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList()); - for (Iterator it = assetLoaderKeys.iterator(); it.hasNext();) { - ModelKey modelKey = it.next(); - AssetInfo info = loaderManager.locateAsset(modelKey); - Spatial child = null; - if (info != null) { - child = (Spatial) importer.load(info); - } + + for (final Iterator iterator = assetLoaderKeys.iterator(); iterator.hasNext(); ) { + + final ModelKey modelKey = iterator.next(); + final Spatial child = assetManager.loadAsset(modelKey); + if (child != null) { child.parent = this; children.add(child); assetChildren.put(modelKey, child); } else { - Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}", - new Object[]{ modelKey, key }); + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, + "Cannot locate {0} for asset link node {1}", new Object[]{modelKey, key}); } } } @@ -190,7 +188,7 @@ public class AssetLinkNode extends Node { @Override public void write(JmeExporter e) throws IOException { SafeArrayList childs = children; - children = new SafeArrayList(Spatial.class); + children = new SafeArrayList<>(Spatial.class); super.write(e); OutputCapsule capsule = e.getCapsule(this); capsule.writeSavableArrayList(assetLoaderKeys, "assetLoaderKeyList", null); diff --git a/jme3-core/src/main/resources/com/jme3/asset/General.cfg b/jme3-core/src/main/resources/com/jme3/asset/General.cfg index 016dcccf2..80b038ba6 100644 --- a/jme3-core/src/main/resources/com/jme3/asset/General.cfg +++ b/jme3-core/src/main/resources/com/jme3/asset/General.cfg @@ -12,8 +12,8 @@ LOADER com.jme3.texture.plugins.DDSLoader : dds LOADER com.jme3.texture.plugins.PFMLoader : pfm LOADER com.jme3.texture.plugins.HDRLoader : hdr LOADER com.jme3.texture.plugins.TGALoader : tga -LOADER com.jme3.export.binary.BinaryImporter : j3o -LOADER com.jme3.export.binary.BinaryImporter : j3f +LOADER com.jme3.export.binary.BinaryLoader : j3o +LOADER com.jme3.export.binary.BinaryLoader : j3f LOADER com.jme3.scene.plugins.OBJLoader : obj LOADER com.jme3.scene.plugins.MTLLoader : mtl LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryLoader.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryLoader.java new file mode 100644 index 000000000..4f8d7e09a --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryLoader.java @@ -0,0 +1,41 @@ +package com.jme3.export.binary; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; + +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * The default loader to load binaries files. + * + * @author JavaSaBr + */ +public class BinaryLoader implements AssetLoader { + + /** + * The importers queue. + */ + private final Deque importers; + + public BinaryLoader() { + importers = new ArrayDeque<>(); + } + + @Override + public Object load(final AssetInfo assetInfo) throws IOException { + + BinaryImporter importer = importers.pollLast(); + + if (importer == null) { + importer = new BinaryImporter(); + } + + try { + return importer.load(assetInfo); + } finally { + importers.addLast(importer); + } + } +}