diff --git a/.gitignore b/.gitignore index ce4b6840a..065ad2311 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ **/.classpath **/.settings **/.project -**/out +**/out/ /.gradle/ /.nb-gradle/ /.idea/ diff --git a/.travis.yml b/.travis.yml index c24f8941b..00aed0854 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ branches: only: - master - v3.1 + - /^v3.2.0-.*$/ matrix: include: diff --git a/gradle.properties b/gradle.properties index 168dffd7a..000bf6930 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ jmeVersionTag = SNAPSHOT jmeVersionTagID = 0 # specify if JavaDoc should be built -buildJavaDoc = false +buildJavaDoc = true # specify if SDK and Native libraries get built buildNativeProjects = false diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java b/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java index ae278c871..addea7a5e 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java @@ -67,7 +67,7 @@ public class BulletAppState implements AppState, PhysicsTickListener { /** * Creates a new BulletAppState running a PhysicsSpace for physics - * simulation, use getStateManager().addState(bulletAppState) to enable + * simulation, use getStateManager().attach(bulletAppState) to enable * physics for an Application. */ public BulletAppState() { @@ -75,7 +75,7 @@ public class BulletAppState implements AppState, PhysicsTickListener { /** * Creates a new BulletAppState running a PhysicsSpace for physics - * simulation, use getStateManager().addState(bulletAppState) to enable + * simulation, use getStateManager().attach(bulletAppState) to enable * physics for an Application. * * @param broadphaseType The type of broadphase collision detection, @@ -87,7 +87,7 @@ public class BulletAppState implements AppState, PhysicsTickListener { /** * Creates a new BulletAppState running a PhysicsSpace for physics - * simulation, use getStateManager().addState(bulletAppState) to enable + * simulation, use getStateManager().attach(bulletAppState) to enable * physics for an Application. An AxisSweep broadphase is used. * * @param worldMin The minimum world extent diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java index c1a79c946..49c0e0f34 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java @@ -31,36 +31,23 @@ */ package com.jme3.bullet.control; -import com.jme3.animation.AnimControl; -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.animation.SkeletonControl; +import com.jme3.animation.*; import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.PhysicsCollisionEvent; -import com.jme3.bullet.collision.PhysicsCollisionListener; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.RagdollCollisionListener; +import com.jme3.bullet.collision.*; import com.jme3.bullet.collision.shapes.BoxCollisionShape; import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.control.ragdoll.HumanoidRagdollPreset; -import com.jme3.bullet.control.ragdoll.RagdollPreset; -import com.jme3.bullet.control.ragdoll.RagdollUtils; +import com.jme3.bullet.control.ragdoll.*; import com.jme3.bullet.joints.SixDofJoint; import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; +import com.jme3.export.*; +import com.jme3.math.*; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.util.TempVars; import com.jme3.util.clone.JmeCloneable; + import java.io.IOException; import java.util.*; import java.util.logging.Level; @@ -91,7 +78,10 @@ import java.util.logging.Logger; *

* * @author Normen Hansen and Rémy Bouquet (Nehon) + * + * TODO this needs to be redone with the new animation system */ +@Deprecated public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener, JmeCloneable { protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName()); diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimClip.java b/jme3-core/src/main/java/com/jme3/anim/AnimClip.java new file mode 100644 index 000000000..9b86858a3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/AnimClip.java @@ -0,0 +1,99 @@ +package com.jme3.anim; + +import com.jme3.anim.tween.Tween; +import com.jme3.export.*; +import com.jme3.util.SafeArrayList; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; + +import java.io.IOException; + +/** + * Created by Nehon on 20/12/2017. + */ +public class AnimClip implements JmeCloneable, Savable { + + private String name; + private double length; + + private AnimTrack[] tracks; + + public AnimClip() { + } + + public AnimClip(String name) { + this.name = name; + } + + public void setTracks(AnimTrack[] tracks) { + this.tracks = tracks; + for (AnimTrack track : tracks) { + if (track.getLength() > length) { + length = track.getLength(); + } + } + } + + public String getName() { + return name; + } + + + public double getLength() { + return length; + } + + + public AnimTrack[] getTracks() { + return tracks; + } + + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("Error cloning", e); + } + } + + @Override + public void cloneFields(Cloner cloner, Object original) { + AnimTrack[] newTracks = new AnimTrack[tracks.length]; + for (int i = 0; i < tracks.length; i++) { + newTracks[i] = (cloner.clone(tracks[i])); + } + this.tracks = newTracks; + } + + @Override + public String toString() { + return "Clip " + name + ", " + length + 's'; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(name, "name", null); + oc.write(tracks, "tracks", null); + + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + name = ic.readString("name", null); + Savable[] arr = ic.readSavableArray("tracks", null); + if (arr != null) { + tracks = new AnimTrack[arr.length]; + for (int i = 0; i < arr.length; i++) { + AnimTrack t = (AnimTrack) arr[i]; + tracks[i] = t; + if (t.getLength() > length) { + length = t.getLength(); + } + } + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java new file mode 100644 index 000000000..646289367 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java @@ -0,0 +1,250 @@ +package com.jme3.anim; + +import com.jme3.anim.tween.Tween; +import com.jme3.anim.tween.Tweens; +import com.jme3.anim.tween.action.*; +import com.jme3.export.*; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.control.AbstractControl; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; + +import java.io.IOException; +import java.util.*; + +/** + * Created by Nehon on 20/12/2017. + */ +public class AnimComposer extends AbstractControl { + + public static final String DEFAULT_LAYER = "Default"; + private Map animClipMap = new HashMap<>(); + + private Map actions = new HashMap<>(); + private float globalSpeed = 1f; + private Map layers = new LinkedHashMap<>(); + + public AnimComposer() { + layers.put(DEFAULT_LAYER, new Layer()); + } + + /** + * Retrieve an animation from the list of animations. + * + * @param name The name of the animation to retrieve. + * @return The animation corresponding to the given name, or null, if no + * such named animation exists. + */ + public AnimClip getAnimClip(String name) { + return animClipMap.get(name); + } + + /** + * Adds an animation to be available for playing to this + * AnimControl. + * + * @param anim The animation to add. + */ + public void addAnimClip(AnimClip anim) { + animClipMap.put(anim.getName(), anim); + } + + /** + * Remove an animation so that it is no longer available for playing. + * + * @param anim The animation to remove. + */ + public void removeAnimClip(AnimClip anim) { + if (!animClipMap.containsKey(anim.getName())) { + throw new IllegalArgumentException("Given animation does not exist " + + "in this AnimControl"); + } + + animClipMap.remove(anim.getName()); + } + + public Action setCurrentAction(String name) { + return setCurrentAction(name, DEFAULT_LAYER); + } + + public Action setCurrentAction(String actionName, String layerName) { + Layer l = layers.get(layerName); + if (l == null) { + throw new IllegalArgumentException("Unknown layer " + layerName); + } + Action currentAction = action(actionName); + l.time = 0; + l.currentAction = currentAction; + return currentAction; + } + + public Action action(String name) { + Action action = actions.get(name); + if (action == null) { + action = makeAction(name); + actions.put(name, action); + } + return action; + } + + public Action makeAction(String name) { + Action action; + AnimClip clip = animClipMap.get(name); + if (clip == null) { + throw new IllegalArgumentException("Cannot find clip named " + name); + } + action = new ClipAction(clip); + return action; + } + + public void makeLayer(String name, AnimationMask mask){ + Layer l = new Layer(); + l.mask = mask; + layers.put(name, l); + } + + + public BaseAction actionSequence(String name, Tween... tweens) { + BaseAction action = new BaseAction(Tweens.sequence(tweens)); + actions.put(name, action); + return action; + } + + public BlendAction actionBlended(String name, BlendSpace blendSpace, String... clips) { + BlendableAction[] acts = new BlendableAction[clips.length]; + for (int i = 0; i < acts.length; i++) { + BlendableAction ba = (BlendableAction) makeAction(clips[i]); + acts[i] = ba; + } + BlendAction action = new BlendAction(blendSpace, acts); + actions.put(name, action); + return action; + } + + public void reset() { + for (Layer layer : layers.values()) { + layer.currentAction = null; + layer.time = 0; + } + } + + public Collection getAnimClips() { + return Collections.unmodifiableCollection(animClipMap.values()); + } + + public Collection getAnimClipsNames() { + return Collections.unmodifiableCollection(animClipMap.keySet()); + } + + @Override + protected void controlUpdate(float tpf) { + for (Layer layer : layers.values()) { + Action currentAction = layer.currentAction; + if (currentAction == null) { + continue; + } + layer.advance(tpf); + + currentAction.setMask(layer.mask); + boolean running = currentAction.interpolate(layer.time); + currentAction.setMask(null); + + if (!running) { + layer.time = 0; + } + } + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + + } + + public float getGlobalSpeed() { + return globalSpeed; + } + + public void setGlobalSpeed(float globalSpeed) { + this.globalSpeed = globalSpeed; + } + + @Override + public Object jmeClone() { + try { + AnimComposer clone = (AnimComposer) super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + Map clips = new HashMap<>(); + for (String key : animClipMap.keySet()) { + clips.put(key, cloner.clone(animClipMap.get(key))); + } + Map act = new HashMap<>(); + for (String key : actions.keySet()) { + act.put(key, cloner.clone(actions.get(key))); + } + actions = act; + animClipMap = clips; + + Map newLayers = new LinkedHashMap<>(); + for (String key : layers.keySet()) { + newLayers.put(key, cloner.clone(layers.get(key))); + } + + layers = newLayers; + + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + animClipMap = (Map) ic.readStringSavableMap("animClipMap", new HashMap()); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap()); + } + + private class Layer implements JmeCloneable { + private Action currentAction; + private AnimationMask mask; + private float weight; + private double time; + + public void advance(float tpf) { + time += tpf * currentAction.getSpeed() * globalSpeed; + // make sure negative time is in [0, length] range + if (time < 0) { + double length = currentAction.getLength(); + time = (time % length + length) % length; + } + + } + + @Override + public Object jmeClone() { + try { + Layer clone = (Layer) super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields(Cloner cloner, Object original) { + currentAction = null; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimTrack.java b/jme3-core/src/main/java/com/jme3/anim/AnimTrack.java new file mode 100644 index 000000000..45b54cf7f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/AnimTrack.java @@ -0,0 +1,12 @@ +package com.jme3.anim; + +import com.jme3.export.Savable; +import com.jme3.util.clone.JmeCloneable; + +public interface AnimTrack extends Savable, JmeCloneable { + + public void getDataAtTime(double time, T store); + public double getLength(); + + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java b/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java new file mode 100644 index 000000000..2aa8328b6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java @@ -0,0 +1,12 @@ +package com.jme3.anim; + +/** + * Created by Nehon + * An AnimationMask is defining a subset of elements on which an animation will be applied. + * Most used implementation is the ArmatureMask that defines a subset of joints in an Armature. + */ +public interface AnimationMask { + + boolean contains(Object target); + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/anim/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java new file mode 100644 index 000000000..ad7f5b0ac --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java @@ -0,0 +1,300 @@ +package com.jme3.anim; + +import com.jme3.anim.util.JointModelTransform; +import com.jme3.asset.AssetLoadException; +import com.jme3.export.*; +import com.jme3.math.Matrix4f; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; + +import java.io.IOException; +import java.util.*; + +/** + * Created by Nehon on 15/12/2017. + */ +public class Armature implements JmeCloneable, Savable { + + private Joint[] rootJoints; + private Joint[] jointList; + + /** + * Contains the skinning matrices, multiplying it by a vertex effected by a bone + * will cause it to go to the animated position. + */ + private transient Matrix4f[] skinningMatrixes; + private Class modelTransformClass = SeparateJointModelTransform.class; + + /** + * Serialization only + */ + public Armature() { + } + + /** + * Creates an armature from a joint list. + * The root joints are found automatically. + *

+ * Note that using this constructor will cause the joints in the list + * to have their bind pose recomputed based on their local transforms. + * + * @param jointList The list of joints to manage by this Armature + */ + public Armature(Joint[] jointList) { + this.jointList = jointList; + + List rootJointList = new ArrayList<>(); + for (int i = jointList.length - 1; i >= 0; i--) { + Joint joint = jointList[i]; + joint.setId(i); + instanciateJointModelTransform(joint); + if (joint.getParent() == null) { + rootJointList.add(joint); + } + } + rootJoints = rootJointList.toArray(new Joint[rootJointList.size()]); + + createSkinningMatrices(); + + for (int i = rootJoints.length - 1; i >= 0; i--) { + Joint rootJoint = rootJoints[i]; + rootJoint.update(); + } + } + + /** + * Update all joints sin this Amature. + */ + public void update() { + for (Joint rootJoint : rootJoints) { + rootJoint.update(); + } + } + + private void createSkinningMatrices() { + skinningMatrixes = new Matrix4f[jointList.length]; + for (int i = 0; i < skinningMatrixes.length; i++) { + skinningMatrixes[i] = new Matrix4f(); + } + } + + /** + * Sets the JointModelTransform implementation + * Default is {@link MatrixJointModelTransform} + * + * @param modelTransformClass + * @see {@link JointModelTransform},{@link MatrixJointModelTransform},{@link SeparateJointModelTransform}, + */ + public void setModelTransformClass(Class modelTransformClass) { + this.modelTransformClass = modelTransformClass; + if (jointList == null) { + return; + } + for (Joint joint : jointList) { + instanciateJointModelTransform(joint); + } + } + + private void instanciateJointModelTransform(Joint joint) { + try { + joint.setJointModelTransform(modelTransformClass.newInstance()); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * returns the array of all root joints of this Armature + * + * @return + */ + public Joint[] getRoots() { + return rootJoints; + } + + public List getJointList() { + return Arrays.asList(jointList); + } + + /** + * return a joint for the given index + * + * @param index + * @return + */ + public Joint getJoint(int index) { + return jointList[index]; + } + + /** + * returns the joint with the given name + * + * @param name + * @return + */ + public Joint getJoint(String name) { + for (int i = 0; i < jointList.length; i++) { + if (jointList[i].getName().equals(name)) { + return jointList[i]; + } + } + return null; + } + + /** + * returns the bone index of the given bone + * + * @param joint + * @return + */ + public int getJointIndex(Joint joint) { + for (int i = 0; i < jointList.length; i++) { + if (jointList[i] == joint) { + return i; + } + } + + return -1; + } + + /** + * returns the joint index of the joint that has the given name + * + * @param name + * @return + */ + public int getJointIndex(String name) { + for (int i = 0; i < jointList.length; i++) { + if (jointList[i].getName().equals(name)) { + return i; + } + } + + return -1; + } + + /** + * Saves the current Armature state as its bind pose. + * Note that the bind pose is supposed to be the one where the armature is aligned with the mesh to deform. + * Saving this pose will affect how skinning works. + */ + public void saveBindPose() { + //make sure all bones are updated + update(); + //Save the current pose as bind pose + for (Joint joint : jointList) { + joint.saveBindPose(); + } + } + + /** + * This methods sets this armature in its bind pose (aligned with the mesh to deform) + * Note that this is only useful for debugging purpose. + */ + public void applyBindPose() { + for (Joint joint : rootJoints) { + joint.applyBindPose(); + } + } + + /** + * Saves the current local transform as the initial transform. + * Initial transform is the one applied to the armature when loaded. + */ + public void saveInitialPose() { + for (Joint joint : jointList) { + joint.saveInitialPose(); + } + } + + /** + * Applies the initial pose to this armature + */ + public void applyInitialPose() { + for (Joint rootJoint : rootJoints) { + rootJoint.applyInitialPose(); + } + } + + /** + * Compute the skinning matrices for each bone of the armature that would be used to transform vertices of associated meshes + * + * @return + */ + public Matrix4f[] computeSkinningMatrices() { + for (int i = 0; i < jointList.length; i++) { + jointList[i].getOffsetTransform(skinningMatrixes[i]); + } + return skinningMatrixes; + } + + /** + * returns the number of joints of this armature + * + * @return + */ + public int getJointCount() { + return jointList.length; + } + + @Override + public Object jmeClone() { + try { + Armature clone = (Armature) super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields(Cloner cloner, Object original) { + this.rootJoints = cloner.clone(rootJoints); + this.jointList = cloner.clone(jointList); + this.skinningMatrixes = cloner.clone(skinningMatrixes); + for (Joint joint : jointList) { + instanciateJointModelTransform(joint); + } + } + + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule input = im.getCapsule(this); + + Savable[] jointRootsAsSavable = input.readSavableArray("rootJoints", null); + rootJoints = new Joint[jointRootsAsSavable.length]; + System.arraycopy(jointRootsAsSavable, 0, rootJoints, 0, jointRootsAsSavable.length); + + Savable[] jointListAsSavable = input.readSavableArray("jointList", null); + jointList = new Joint[jointListAsSavable.length]; + System.arraycopy(jointListAsSavable, 0, jointList, 0, jointListAsSavable.length); + + String className = input.readString("modelTransformClass", MatrixJointModelTransform.class.getCanonicalName()); + try { + modelTransformClass = (Class) Class.forName(className); + } catch (ClassNotFoundException e) { + throw new AssetLoadException("Cannnot find class for name " + className); + } + + int i = 0; + for (Joint joint : jointList) { + joint.setId(i++); + instanciateJointModelTransform(joint); + } + createSkinningMatrices(); + + for (Joint rootJoint : rootJoints) { + rootJoint.update(); + } + applyInitialPose(); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule output = ex.getCapsule(this); + output.write(rootJoints, "rootJoints", null); + output.write(jointList, "jointList", null); + output.write(modelTransformClass.getCanonicalName(), "modelTransformClass", MatrixJointModelTransform.class.getCanonicalName()); + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java b/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java new file mode 100644 index 000000000..b492eb4ed --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java @@ -0,0 +1,62 @@ +package com.jme3.anim; + +import java.util.BitSet; + +public class ArmatureMask implements AnimationMask { + + private BitSet affectedJoints = new BitSet(); + + @Override + public boolean contains(Object target) { + return affectedJoints.get(((Joint) target).getId()); + } + + public static ArmatureMask createMask(Armature armature, String fromJoint) { + ArmatureMask mask = new ArmatureMask(); + mask.addFromJoint(armature, fromJoint); + return mask; + } + + public static ArmatureMask createMask(Armature armature, String... joints) { + ArmatureMask mask = new ArmatureMask(); + mask.addBones(armature, joints); + for (String joint : joints) { + mask.affectedJoints.set(armature.getJoint(joint).getId()); + } + return mask; + } + + /** + * Add joints to be influenced by this animation mask. + */ + public void addBones(Armature armature, String... jointNames) { + for (String jointName : jointNames) { + Joint joint = findJoint(armature, jointName); + affectedJoints.set(joint.getId()); + } + } + + private Joint findJoint(Armature armature, String jointName) { + Joint joint = armature.getJoint(jointName); + if (joint == null) { + throw new IllegalArgumentException("Cannot find joint " + jointName); + } + return joint; + } + + /** + * Add a joint and all its sub armature joints to be influenced by this animation mask. + */ + public void addFromJoint(Armature armature, String jointName) { + Joint joint = findJoint(armature, jointName); + recurseAddJoint(joint); + } + + private void recurseAddJoint(Joint joint) { + affectedJoints.set(joint.getId()); + for (Joint j : joint.getChildren()) { + recurseAddJoint(j); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/Joint.java b/jme3-core/src/main/java/com/jme3/anim/Joint.java new file mode 100644 index 000000000..bf672fa29 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/Joint.java @@ -0,0 +1,342 @@ +package com.jme3.anim; + +import com.jme3.anim.util.HasLocalTransform; +import com.jme3.anim.util.JointModelTransform; +import com.jme3.export.*; +import com.jme3.material.MatParamOverride; +import com.jme3.math.*; +import com.jme3.scene.*; +import com.jme3.shader.VarType; +import com.jme3.util.SafeArrayList; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * A Joint is the basic component of an armature designed to perform skeletal animation + * Created by Nehon on 15/12/2017. + */ +public class Joint implements Savable, JmeCloneable, HasLocalTransform { + + private String name; + private int id; + private Joint parent; + private SafeArrayList children = new SafeArrayList<>(Joint.class); + private Geometry targetGeometry; + + /** + * The attachment node. + */ + private Node attachedNode; + + /** + * The transform of the joint in local space. Relative to its parent. + * Or relative to the model's origin for the root joint. + */ + private Transform localTransform = new Transform(); + + /** + * The initial transform of the joint in local space. Relative to its parent. + * Or relative to the model's origin for the root joint. + * this transform is the transform applied when the armature is loaded. + */ + private Transform initialTransform = new Transform(); + + /** + * The transform of the joint in model space. Relative to the origin of the model. + * this is either a MatrixJointModelTransform or a SeparateJointModelTransform + */ + private JointModelTransform jointModelTransform; + + /** + * The matrix used to transform affected vertices position into the joint model space. + * Used for skinning. + */ + private Matrix4f inverseModelBindMatrix = new Matrix4f(); + + + public Joint() { + } + + public Joint(String name) { + this.name = name; + } + + /** + * Updates world transforms for this bone and it's children. + */ + public final void update() { + this.updateModelTransforms(); + + for (Joint child : children.getArray()) { + child.update(); + } + } + + /** + * Updates the model transforms for this bone, and, possibly the attach node + * if not null. + *

+ * The model transform of this bone is computed by combining the parent's + * model transform with this bones' local transform. + */ + public final void updateModelTransforms() { + jointModelTransform.updateModelTransform(localTransform, parent); + updateAttachNode(); + } + + /** + * Update the local transform of the attachments node. + */ + private void updateAttachNode() { + if (attachedNode == null) { + return; + } + Node attachParent = attachedNode.getParent(); + if (attachParent == null || targetGeometry == null + || targetGeometry.getParent() == attachParent + && targetGeometry.getLocalTransform().isIdentity()) { + /* + * The animated meshes are in the same coordinate system as the + * attachments node: no further transforms are needed. + */ + attachedNode.setLocalTransform(getModelTransform()); + + } else { + Spatial loopSpatial = targetGeometry; + Transform combined = getModelTransform().clone(); + /* + * Climb the scene graph applying local transforms until the + * attachments node's parent is reached. + */ + while (loopSpatial != attachParent && loopSpatial != null) { + Transform localTransform = loopSpatial.getLocalTransform(); + combined.combineWithParent(localTransform); + loopSpatial = loopSpatial.getParent(); + } + attachedNode.setLocalTransform(combined); + } + } + + /** + * Stores the skinning transform in the specified Matrix4f. + * The skinning transform applies the animation of the bone to a vertex. + *

+ * This assumes that the world transforms for the entire bone hierarchy + * have already been computed, otherwise this method will return undefined + * results. + * + * @param outTransform + */ + void getOffsetTransform(Matrix4f outTransform) { + jointModelTransform.getOffsetTransform(outTransform, inverseModelBindMatrix); + } + + /** + * Sets the current localTransform as the Bind transform. + */ + protected void saveBindPose() { + //Note that the whole Armature must be updated before calling this method. + getModelTransform().toTransformMatrix(inverseModelBindMatrix); + inverseModelBindMatrix.invertLocal(); + } + + /** + * Sets the current local transforms as the initial transform. + */ + protected void saveInitialPose() { + initialTransform.set(localTransform); + } + + /** + * Sets the local transform with the bind transforms + */ + protected void applyBindPose() { + jointModelTransform.applyBindPose(localTransform, inverseModelBindMatrix, parent); + updateModelTransforms(); + + for (Joint child : children.getArray()) { + child.applyBindPose(); + } + } + + /** + * Sets the local transform with the initial transform + */ + protected void applyInitialPose() { + setLocalTransform(initialTransform); + updateModelTransforms(); + + for (Joint child : children.getArray()) { + child.applyInitialPose(); + } + } + + protected JointModelTransform getJointModelTransform() { + return jointModelTransform; + } + + protected void setJointModelTransform(JointModelTransform jointModelTransform) { + this.jointModelTransform = jointModelTransform; + } + + public Vector3f getLocalTranslation() { + return localTransform.getTranslation(); + } + + public Quaternion getLocalRotation() { + return localTransform.getRotation(); + } + + public Vector3f getLocalScale() { + return localTransform.getScale(); + } + + public void setLocalTranslation(Vector3f translation) { + localTransform.setTranslation(translation); + } + + public void setLocalRotation(Quaternion rotation) { + localTransform.setRotation(rotation); + } + + public void setLocalScale(Vector3f scale) { + localTransform.setScale(scale); + } + + public void addChild(Joint child) { + children.add(child); + child.parent = this; + } + + public void setName(String name) { + this.name = name; + } + + public void setLocalTransform(Transform localTransform) { + this.localTransform.set(localTransform); + } + + public void setInverseModelBindMatrix(Matrix4f inverseModelBindMatrix) { + this.inverseModelBindMatrix = inverseModelBindMatrix; + } + + public String getName() { + return name; + } + + public Joint getParent() { + return parent; + } + + public List getChildren() { + return children; + } + + /** + * Access the attachments node of this joint. If this joint doesn't already + * have an attachments node, create one. Models and effects attached to the + * attachments node will follow this bone's motions. + * + * @param jointIndex this bone's index in its armature (≥0) + * @param targets a list of geometries animated by this bone's skeleton (not + * null, unaffected) + */ + Node getAttachmentsNode(int jointIndex, SafeArrayList targets) { + targetGeometry = null; + /* + * Search for a geometry animated by this particular bone. + */ + for (Geometry geometry : targets) { + Mesh mesh = geometry.getMesh(); + if (mesh != null && mesh.isAnimatedByJoint(jointIndex)) { + targetGeometry = geometry; + break; + } + } + + if (attachedNode == null) { + attachedNode = new Node(name + "_attachnode"); + attachedNode.setUserData("AttachedBone", this); + //We don't want the node to have a numBone set by a parent node so we force it to null + attachedNode.addMatParamOverride(new MatParamOverride(VarType.Int, "NumberOfBones", null)); + } + + return attachedNode; + } + + + public Transform getLocalTransform() { + return localTransform; + } + + public Transform getModelTransform() { + return jointModelTransform.getModelTransform(); + } + + public Matrix4f getInverseModelBindMatrix() { + return inverseModelBindMatrix; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Override + public Object jmeClone() { + try { + Joint clone = (Joint) super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields(Cloner cloner, Object original) { + this.children = cloner.clone(children); + this.parent = cloner.clone(parent); + this.attachedNode = cloner.clone(attachedNode); + this.targetGeometry = cloner.clone(targetGeometry); + this.localTransform = cloner.clone(localTransform); + this.inverseModelBindMatrix = cloner.clone(inverseModelBindMatrix); + } + + + @Override + @SuppressWarnings("unchecked") + public void read(JmeImporter im) throws IOException { + InputCapsule input = im.getCapsule(this); + + name = input.readString("name", null); + attachedNode = (Node) input.readSavable("attachedNode", null); + targetGeometry = (Geometry) input.readSavable("targetGeometry", null); + initialTransform = (Transform) input.readSavable("initialTransform", new Transform()); + inverseModelBindMatrix = (Matrix4f) input.readSavable("inverseModelBindMatrix", inverseModelBindMatrix); + + ArrayList childList = input.readSavableArrayList("children", null); + for (int i = childList.size() - 1; i >= 0; i--) { + this.addChild(childList.get(i)); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule output = ex.getCapsule(this); + + output.write(name, "name", null); + output.write(attachedNode, "attachedNode", null); + output.write(targetGeometry, "targetGeometry", null); + output.write(initialTransform, "initialTransform", new Transform()); + output.write(inverseModelBindMatrix, "inverseModelBindMatrix", new Matrix4f()); + output.writeSavableArrayList(new ArrayList(children), "children", null); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java new file mode 100644 index 000000000..5f1aadad7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java @@ -0,0 +1,47 @@ +package com.jme3.anim; + +import com.jme3.anim.util.JointModelTransform; +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; + +/** + * This JointModelTransform implementation accumulate joints transforms in a Matrix4f to properly + * support non uniform scaling in an armature hierarchy + */ +public class MatrixJointModelTransform implements JointModelTransform { + + private Matrix4f modelTransformMatrix = new Matrix4f(); + private Transform modelTransform = new Transform(); + + @Override + public void updateModelTransform(Transform localTransform, Joint parent) { + localTransform.toTransformMatrix(modelTransformMatrix); + if (parent != null) { + ((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().mult(modelTransformMatrix, modelTransformMatrix); + } + + } + + public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) { + modelTransformMatrix.mult(inverseModelBindMatrix, outTransform); + } + + @Override + public void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent) { + modelTransformMatrix.set(inverseModelBindMatrix).invertLocal(); // model transform = model bind + if (parent != null) { + ((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().invert().mult(modelTransformMatrix, modelTransformMatrix); + } + localTransform.fromTransformMatrix(modelTransformMatrix); + } + + public Matrix4f getModelTransformMatrix() { + return modelTransformMatrix; + } + + @Override + public Transform getModelTransform() { + modelTransform.fromTransformMatrix(modelTransformMatrix); + return modelTransform; + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/MorphControl.java b/jme3-core/src/main/java/com/jme3/anim/MorphControl.java new file mode 100644 index 000000000..1aa055555 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/MorphControl.java @@ -0,0 +1,353 @@ +package com.jme3.anim; + +import com.jme3.export.Savable; +import com.jme3.material.*; +import com.jme3.renderer.*; +import com.jme3.scene.*; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.mesh.MorphTarget; +import com.jme3.shader.VarType; +import com.jme3.util.BufferUtils; +import com.jme3.util.SafeArrayList; + +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A control that handle morph animation for Position, Normal and Tangent buffers. + * All stock shaders only support morphing these 3 buffers, but note that MorphTargets can have any type of buffers. + * If you want to use other types of buffers you will need a custom MorphControl and a custom shader. + * + * @author Rémy Bouquet + */ +public class MorphControl extends AbstractControl implements Savable { + + private static final Logger logger = Logger.getLogger(MorphControl.class.getName()); + + private static final int MAX_MORPH_BUFFERS = 14; + private final static float MIN_WEIGHT = 0.005f; + + private SafeArrayList targets = new SafeArrayList<>(Geometry.class); + private TargetLocator targetLocator = new TargetLocator(); + + private boolean approximateTangents = true; + private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null); + + private float[] tmpPosArray; + private float[] tmpNormArray; + private float[] tmpTanArray; + + private static final VertexBuffer.Type bufferTypes[] = VertexBuffer.Type.values(); + + @Override + protected void controlUpdate(float tpf) { + if (!enabled) { + return; + } + // gathering geometries in the sub graph. + // This must be done in the update phase as the gathering might add a matparam override + targets.clear(); + this.spatial.depthFirstTraversal(targetLocator); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + if (!enabled) { + return; + } + for (Geometry geom : targets) { + Mesh mesh = geom.getMesh(); + if (!geom.isDirtyMorph()) { + continue; + } + + Material m = geom.getMaterial(); + float weights[] = geom.getMorphState(); + MorphTarget morphTargets[] = mesh.getMorphTargets(); + float matWeights[]; + //Number of buffer to handle for each morph target + int targetNumBuffers = getTargetNumBuffers(morphTargets[0]); + + int maxGPUTargets = getMaxGPUTargets(rm, geom, m, targetNumBuffers); + + MatParam param2 = m.getParam("MorphWeights"); + matWeights = (float[]) param2.getValue(); + + int nbGPUTargets = 0; + int lastGpuTargetIndex = 0; + int boundBufferIdx = 0; + float cpuWeightSum = 0; + // binding the morphTargets buffer to the mesh morph buffers + for (int i = 0; i < morphTargets.length; i++) { + // discard weights below the threshold + if (weights[i] < MIN_WEIGHT) { + continue; + } + if (nbGPUTargets >= maxGPUTargets) { + // we already bound all the available gpu slots we need to merge the remaining morph targets. + cpuWeightSum += weights[i]; + continue; + } + lastGpuTargetIndex = i; + // binding the morph target's buffers to the mesh morph buffers. + MorphTarget t = morphTargets[i]; + boundBufferIdx = bindMorphtargetBuffer(mesh, targetNumBuffers, boundBufferIdx, t); + // setting the weight in the mat param array + matWeights[nbGPUTargets] = weights[i]; + nbGPUTargets++; + } + + if (nbGPUTargets < matWeights.length) { + // if we have less simultaneous GPU targets than the length of the weight array, the array is padded with 0 + for (int i = nbGPUTargets; i < matWeights.length; i++) { + matWeights[i] = 0; + } + } else if (cpuWeightSum > 0) { + // we have more simultaneous morph targets than available gpu slots, + // we merge the additional morph targets and bind them to the last gpu slot + MorphTarget mt = geom.getFallbackMorphTarget(); + if (mt == null) { + mt = initCpuMorphTarget(geom); + geom.setFallbackMorphTarget(mt); + } + // adding the last Gpu target weight + cpuWeightSum += matWeights[nbGPUTargets - 1]; + ensureTmpArraysCapacity(geom.getVertexCount() * 3, targetNumBuffers); + + // merging all remaining targets in tmp arrays + for (int i = lastGpuTargetIndex; i < morphTargets.length; i++) { + if (weights[i] < MIN_WEIGHT) { + continue; + } + float weight = weights[i] / cpuWeightSum; + MorphTarget t = geom.getMesh().getMorphTargets()[i]; + mergeMorphTargets(targetNumBuffers, weight, t, i == lastGpuTargetIndex); + } + + // writing the tmp arrays to the float buffer + writeCpuBuffer(targetNumBuffers, mt); + + // binding the merged morph target + bindMorphtargetBuffer(mesh, targetNumBuffers, (nbGPUTargets - 1) * targetNumBuffers, mt); + + // setting the eight of the merged targets + matWeights[nbGPUTargets - 1] = cpuWeightSum; + } + geom.setDirtyMorph(false); + } + } + + private int getMaxGPUTargets(RenderManager rm, Geometry geom, Material mat, int targetNumBuffers) { + if (geom.getNbSimultaneousGPUMorph() > -1) { + return geom.getNbSimultaneousGPUMorph(); + } + + // Evaluate the number of CPU slots remaining for morph buffers. + int nbMaxBuffers = getRemainingBuffers(geom.getMesh(), rm.getRenderer()); + + int realNumTargetsBuffers = geom.getMesh().getMorphTargets().length * targetNumBuffers; + + // compute the max number of targets to send to the GPU + int maxGPUTargets = Math.min(realNumTargetsBuffers, Math.min(nbMaxBuffers, MAX_MORPH_BUFFERS)) / targetNumBuffers; + + MatParam param = mat.getParam("MorphWeights"); + if (param == null) { + // init the mat param if it doesn't exists. + float[] wts = new float[maxGPUTargets]; + mat.setParam("MorphWeights", VarType.FloatArray, wts); + } + + mat.setInt("NumberOfTargetsBuffers", targetNumBuffers); + + // test compile the shader to find the accurate number of remaining attributes slots + boolean compilationOk = false; + // Note that if ever the shader has an unrelated issue we want to break at some point, hence the maxGPUTargets > 0 + while (!compilationOk && maxGPUTargets > 0) { + // setting the maximum number as the real number may change every frame and trigger a shader recompilation since it's bound to a define. + mat.setInt("NumberOfMorphTargets", maxGPUTargets); + try { + // preload the spatial. this will trigger a shader compilation that will fail if the number of attributes is over the limit. + rm.preloadScene(spatial); + compilationOk = true; + } catch (RendererException e) { + logger.log(Level.FINE, geom.getName() + ": failed at " + maxGPUTargets); + // the compilation failed let's decrement the number of targets an try again. + maxGPUTargets--; + } + } + logger.log(Level.FINE, geom.getName() + ": " + maxGPUTargets); + // set the number of GPU morph on the geom to not have to recompute it next frame. + geom.setNbSimultaneousGPUMorph(maxGPUTargets); + return maxGPUTargets; + } + + private int bindMorphtargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) { + int start = VertexBuffer.Type.MorphTarget0.ordinal(); + if (targetNumBuffers >= 1) { + activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position)); + boundBufferIdx++; + } + if (targetNumBuffers >= 2) { + activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Normal)); + boundBufferIdx++; + } + if (!approximateTangents && targetNumBuffers == 3) { + activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Tangent)); + boundBufferIdx++; + } + return boundBufferIdx; + } + + private void writeCpuBuffer(int targetNumBuffers, MorphTarget mt) { + if (targetNumBuffers >= 1) { + FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Position); + dest.rewind(); + dest.put(tmpPosArray, 0, dest.capacity()); + } + if (targetNumBuffers >= 2) { + FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Normal); + dest.rewind(); + dest.put(tmpNormArray, 0, dest.capacity()); + } + if (!approximateTangents && targetNumBuffers == 3) { + FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Tangent); + dest.rewind(); + dest.put(tmpTanArray, 0, dest.capacity()); + } + } + + private void mergeMorphTargets(int targetNumBuffers, float weight, MorphTarget t, boolean init) { + if (targetNumBuffers >= 1) { + mergeTargetBuffer(tmpPosArray, weight, t.getBuffer(VertexBuffer.Type.Position), init); + } + if (targetNumBuffers >= 2) { + mergeTargetBuffer(tmpNormArray, weight, t.getBuffer(VertexBuffer.Type.Normal), init); + } + if (!approximateTangents && targetNumBuffers == 3) { + mergeTargetBuffer(tmpTanArray, weight, t.getBuffer(VertexBuffer.Type.Tangent), init); + } + } + + private void ensureTmpArraysCapacity(int capacity, int targetNumBuffers) { + if (targetNumBuffers >= 1) { + tmpPosArray = ensureCapacity(tmpPosArray, capacity); + } + if (targetNumBuffers >= 2) { + tmpNormArray = ensureCapacity(tmpNormArray, capacity); + } + if (!approximateTangents && targetNumBuffers == 3) { + tmpTanArray = ensureCapacity(tmpTanArray, capacity); + } + } + + private void mergeTargetBuffer(float[] array, float weight, FloatBuffer src, boolean init) { + src.rewind(); + for (int j = 0; j < src.capacity(); j++) { + if (init) { + array[j] = 0; + } + array[j] += weight * src.get(); + } + } + + private void activateBuffer(Mesh mesh, int idx, int start, FloatBuffer b) { + VertexBuffer.Type t = bufferTypes[start + idx]; + VertexBuffer vb = mesh.getBuffer(t); + // only set the buffer if it's different + if (vb == null || vb.getData() != b) { + mesh.setBuffer(t, 3, b); + } + } + + private float[] ensureCapacity(float[] tmpArray, int size) { + if (tmpArray == null || tmpArray.length < size) { + return new float[size]; + } + return tmpArray; + } + + private MorphTarget initCpuMorphTarget(Geometry geom) { + MorphTarget res = new MorphTarget(); + MorphTarget mt = geom.getMesh().getMorphTargets()[0]; + FloatBuffer b = mt.getBuffer(VertexBuffer.Type.Position); + if (b != null) { + res.setBuffer(VertexBuffer.Type.Position, BufferUtils.createFloatBuffer(b.capacity())); + } + b = mt.getBuffer(VertexBuffer.Type.Normal); + if (b != null) { + res.setBuffer(VertexBuffer.Type.Normal, BufferUtils.createFloatBuffer(b.capacity())); + } + if (!approximateTangents) { + b = mt.getBuffer(VertexBuffer.Type.Tangent); + if (b != null) { + res.setBuffer(VertexBuffer.Type.Tangent, BufferUtils.createFloatBuffer(b.capacity())); + } + } + return res; + } + + private int getTargetNumBuffers(MorphTarget morphTarget) { + int num = 0; + if (morphTarget.getBuffer(VertexBuffer.Type.Position) != null) num++; + if (morphTarget.getBuffer(VertexBuffer.Type.Normal) != null) num++; + + // if tangents are not needed we don't count the tangent buffer + if (!approximateTangents && morphTarget.getBuffer(VertexBuffer.Type.Tangent) != null) { + num++; + } + return num; + } + + /** + * Computes the number of remaining buffers on this mesh. + * This is supposed to give a hint on how many attributes will be used in the material and computes the remaining available slots for the morph attributes. + * However, the shader can declare attributes that are not used and not bound to a real buffer. + * That's why we attempt to compile the shader later on to avoid any compilation crash. + * This method is here to avoid too much render test iteration. + * + * @param mesh + * @param renderer + * @return + */ + private int getRemainingBuffers(Mesh mesh, Renderer renderer) { + int nbUsedBuffers = 0; + for (VertexBuffer vb : mesh.getBufferList().getArray()) { + boolean isMorphBuffer = vb.getBufferType().ordinal() >= VertexBuffer.Type.MorphTarget0.ordinal() && vb.getBufferType().ordinal() <= VertexBuffer.Type.MorphTarget9.ordinal(); + if (vb.getBufferType() == VertexBuffer.Type.Index || isMorphBuffer) continue; + if (vb.getUsage() != VertexBuffer.Usage.CpuOnly) { + nbUsedBuffers++; + } + } + return renderer.getLimits().get(Limits.VertexAttributes) - nbUsedBuffers; + } + + public void setApproximateTangents(boolean approximateTangents) { + this.approximateTangents = approximateTangents; + } + + public boolean isApproximateTangents() { + return approximateTangents; + } + + private class TargetLocator extends SceneGraphVisitorAdapter { + @Override + public void visit(Geometry geom) { + MatParam p = geom.getMaterial().getMaterialDef().getMaterialParam("MorphWeights"); + if (p == null) { + return; + } + Mesh mesh = geom.getMesh(); + if (mesh != null && mesh.hasMorphTargets()) { + targets.add(geom); + // If the mesh is in a subgraph of a node with a SkinningControl it might have hardware skinning activated through mat param override even if it's not skinned. + // this code makes sure that if the mesh has no hardware skinning buffers hardware skinning won't be activated. + // this is important, because if HW skinning is activated the shader will declare 2 additional useless attributes, + // and we desperately need all the attributes we can find for Morph animation. + if (mesh.getBuffer(VertexBuffer.Type.HWBoneIndex) == null && !geom.getLocalMatParamOverrides().contains(nullNumberOfBones)) { + geom.addMatParamOverride(nullNumberOfBones); + } + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java b/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java new file mode 100644 index 000000000..cd662c11e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.anim; + +import com.jme3.anim.interpolator.FrameInterpolator; +import com.jme3.animation.*; +import com.jme3.export.*; +import com.jme3.scene.Geometry; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; + +import java.io.IOException; + +/** + * Contains a list of weights and times for each keyframe. + * + * @author Rémy Bouquet + */ +public class MorphTrack implements AnimTrack { + + private double length; + private Geometry target; + + /** + * Weights and times for track. + */ + private float[] weights; + private FrameInterpolator interpolator = FrameInterpolator.DEFAULT; + private float[] times; + private int nbMorphTargets; + + /** + * Serialization-only. Do not use. + */ + public MorphTrack() { + } + + /** + * Creates a morph track with the given Geometry as a target + * + * @param times a float array with the time of each frame + * @param weights the morphs for each frames + */ + public MorphTrack(Geometry target, float[] times, float[] weights, int nbMorphTargets) { + this.target = target; + this.nbMorphTargets = nbMorphTargets; + this.setKeyframes(times, weights); + } + + /** + * return the array of weights of this track + * + * @return + */ + public float[] getWeights() { + return weights; + } + + /** + * returns the arrays of time for this track + * + * @return + */ + public float[] getTimes() { + return times; + } + + /** + * Sets the keyframes times for this Joint track + * + * @param times the keyframes times + */ + public void setTimes(float[] times) { + if (times.length == 0) { + throw new RuntimeException("TransformTrack with no keyframes!"); + } + this.times = times; + length = times[times.length - 1] - times[0]; + } + + + /** + * Set the weight for this morph track + * + * @param times a float array with the time of each frame + * @param weights the weights of the morphs for each frame + + */ + public void setKeyframes(float[] times, float[] weights) { + setTimes(times); + if (weights != null) { + if (times == null) { + throw new RuntimeException("MorphTrack doesn't have any time for key frames, please call setTimes first"); + } + + this.weights = weights; + + assert times != null && times.length == weights.length; + } + } + + @Override + public double getLength() { + return length; + } + + @Override + public void getDataAtTime(double t, float[] store) { + float time = (float) t; + + int lastFrame = times.length - 1; + if (time < 0 || lastFrame == 0) { + if (weights != null) { + System.arraycopy(weights,0,store,0, nbMorphTargets); + } + return; + } + + int startFrame = 0; + int endFrame = 1; + float blend = 0; + if (time >= times[lastFrame]) { + startFrame = lastFrame; + + time = time - times[startFrame] + times[startFrame - 1]; + blend = (time - times[startFrame - 1]) + / (times[startFrame] - times[startFrame - 1]); + + } else { + // use lastFrame so we never overflow the array + int i; + for (i = 0; i < lastFrame && times[i] < time; i++) { + startFrame = i; + endFrame = i + 1; + } + blend = (time - times[startFrame]) + / (times[endFrame] - times[startFrame]); + } + + interpolator.interpolateWeights(blend, startFrame, weights, nbMorphTargets, store); + } + + public void setFrameInterpolator(FrameInterpolator interpolator) { + this.interpolator = interpolator; + } + + public Geometry getTarget() { + return target; + } + + public void setTarget(Geometry target) { + this.target = target; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(weights, "weights", null); + oc.write(times, "times", null); + oc.write(target, "target", null); + oc.write(nbMorphTargets, "nbMorphTargets", 0); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + weights = ic.readFloatArray("weights", null); + times = ic.readFloatArray("times", null); + target = (Geometry) ic.readSavable("target", null); + nbMorphTargets = ic.readInt("nbMorphTargets", 0); + setTimes(times); + } + + @Override + public Object jmeClone() { + try { + MorphTrack clone = (MorphTrack) super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields(Cloner cloner, Object original) { + this.target = cloner.clone(target); + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java new file mode 100644 index 000000000..c06b97ba4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java @@ -0,0 +1,43 @@ +package com.jme3.anim; + +import com.jme3.anim.util.JointModelTransform; +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; + +/** + * This JointModelTransform implementation accumulates model transform in a Transform class + * This does NOT support proper non uniform scale in the armature hierarchy. + * But the effect might be useful in some circumstances. + * Note that this is how the old animation system was working, so you might want to use this + * if your model has non uniform scale and was migrated from old j3o model. + */ +public class SeparateJointModelTransform implements JointModelTransform { + + private Transform modelTransform = new Transform(); + + @Override + public void updateModelTransform(Transform localTransform, Joint parent) { + modelTransform.set(localTransform); + if (parent != null) { + modelTransform.combineWithParent(parent.getModelTransform()); + } + } + + public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) { + modelTransform.toTransformMatrix(outTransform).mult(inverseModelBindMatrix, outTransform); + } + + @Override + public void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent) { + localTransform.fromTransformMatrix(inverseModelBindMatrix.invert()); + if (parent != null) { + localTransform.combineWithParent(parent.getModelTransform().invert()); + } + } + + @Override + public Transform getModelTransform() { + return modelTransform; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java b/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java new file mode 100644 index 000000000..097840e81 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java @@ -0,0 +1,744 @@ +/* + * Copyright (c) 2009-2017 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.anim; + +import com.jme3.export.*; +import com.jme3.material.MatParamOverride; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.*; +import com.jme3.scene.*; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.shader.VarType; +import com.jme3.util.SafeArrayList; +import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; + +import java.io.IOException; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The Skinning control deforms a model according to an armature, It handles the + * computation of the deformation matrices and performs the transformations on + * the mesh + *

+ * It can perform software skinning or Hardware skinning + * + * @author Rémy Bouquet Based on SkeletonControl by Kirill Vainer + */ +public class SkinningControl extends AbstractControl implements Cloneable, JmeCloneable { + + private static final Logger logger = Logger.getLogger(SkinningControl.class.getName()); + + /** + * The armature of the model. + */ + private Armature armature; + + /** + * List of geometries affected by this control. + */ + private SafeArrayList targets = new SafeArrayList<>(Geometry.class); + + /** + * Used to track when a mesh was updated. Meshes are only updated if they + * are visible in at least one camera. + */ + private boolean wasMeshUpdated = false; + + /** + * User wishes to use hardware skinning if available. + */ + private transient boolean hwSkinningDesired = true; + + /** + * Hardware skinning is currently being used. + */ + private transient boolean hwSkinningEnabled = false; + + /** + * Hardware skinning was tested on this GPU, results + * are stored in {@link #hwSkinningSupported} variable. + */ + private transient boolean hwSkinningTested = false; + + /** + * If hardware skinning was {@link #hwSkinningTested tested}, then + * this variable will be set to true if supported, and false if otherwise. + */ + private transient boolean hwSkinningSupported = false; + + /** + * Bone offset matrices, recreated each frame + */ + private transient Matrix4f[] offsetMatrices; + + + private MatParamOverride numberOfJointsParam; + private MatParamOverride jointMatricesParam; + + /** + * Serialization only. Do not use. + */ + public SkinningControl() { + } + + /** + * Creates a armature control. The list of targets will be acquired + * automatically when the control is attached to a node. + * + * @param armature the armature + */ + public SkinningControl(Armature armature) { + if (armature == null) { + throw new IllegalArgumentException("armature cannot be null"); + } + this.armature = armature; + this.numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null); + this.jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null); + } + + + private void switchToHardware() { + numberOfJointsParam.setEnabled(true); + jointMatricesParam.setEnabled(true); + + // Next full 10 bones (e.g. 30 on 24 bones) + int numBones = ((armature.getJointCount() / 10) + 1) * 10; + numberOfJointsParam.setValue(numBones); + + for (Geometry geometry : targets) { + Mesh mesh = geometry.getMesh(); + if (mesh != null && mesh.isAnimated()) { + mesh.prepareForAnim(false); + } + } + } + + private void switchToSoftware() { + numberOfJointsParam.setEnabled(false); + jointMatricesParam.setEnabled(false); + + for (Geometry geometry : targets) { + Mesh mesh = geometry.getMesh(); + if (mesh != null && mesh.isAnimated()) { + mesh.prepareForAnim(true); + } + } + } + + private boolean testHardwareSupported(RenderManager rm) { + + //Only 255 bones max supported with hardware skinning + if (armature.getJointCount() > 255) { + return false; + } + + switchToHardware(); + + try { + rm.preloadScene(spatial); + return true; + } catch (RendererException e) { + logger.log(Level.WARNING, "Could not enable HW skinning due to shader compile error:", e); + return false; + } + } + + /** + * Specifies if hardware skinning is preferred. If it is preferred and + * supported by GPU, it shall be enabled, if its not preferred, or not + * supported by GPU, then it shall be disabled. + * + * @param preferred + * @see #isHardwareSkinningUsed() + */ + public void setHardwareSkinningPreferred(boolean preferred) { + hwSkinningDesired = preferred; + } + + /** + * @return True if hardware skinning is preferable to software skinning. + * Set to false by default. + * @see #setHardwareSkinningPreferred(boolean) + */ + public boolean isHardwareSkinningPreferred() { + return hwSkinningDesired; + } + + /** + * @return True is hardware skinning is activated and is currently used, false otherwise. + */ + public boolean isHardwareSkinningUsed() { + return hwSkinningEnabled; + } + + + /** + * If specified the geometry has an animated mesh, add its mesh and material + * to the lists of animation targets. + */ + private void findTargets(Geometry geometry) { + Mesh mesh = geometry.getMesh(); + if (mesh != null && mesh.isAnimated()) { + targets.add(geometry); + } + + } + + private void findTargets(Node node) { + for (Spatial child : node.getChildren()) { + if (child instanceof Geometry) { + findTargets((Geometry) child); + } else if (child instanceof Node) { + findTargets((Node) child); + } + } + } + + @Override + public void setSpatial(Spatial spatial) { + Spatial oldSpatial = this.spatial; + super.setSpatial(spatial); + updateTargetsAndMaterials(spatial); + + if (oldSpatial != null) { + oldSpatial.removeMatParamOverride(numberOfJointsParam); + oldSpatial.removeMatParamOverride(jointMatricesParam); + } + + if (spatial != null) { + spatial.removeMatParamOverride(numberOfJointsParam); + spatial.removeMatParamOverride(jointMatricesParam); + spatial.addMatParamOverride(numberOfJointsParam); + spatial.addMatParamOverride(jointMatricesParam); + } + } + + private void controlRenderSoftware() { + resetToBind(); // reset morph meshes to bind pose + + offsetMatrices = armature.computeSkinningMatrices(); + + for (Geometry geometry : targets) { + Mesh mesh = geometry.getMesh(); + // NOTE: This assumes code higher up has + // already ensured this mesh is animated. + // Otherwise a crash will happen in skin update. + softwareSkinUpdate(mesh, offsetMatrices); + } + } + + private void controlRenderHardware() { + offsetMatrices = armature.computeSkinningMatrices(); + jointMatricesParam.setValue(offsetMatrices); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + if (!wasMeshUpdated) { + updateTargetsAndMaterials(spatial); + + // Prevent illegal cases. These should never happen. + assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled); + assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported); + + if (hwSkinningDesired && !hwSkinningTested) { + hwSkinningTested = true; + hwSkinningSupported = testHardwareSupported(rm); + + if (hwSkinningSupported) { + hwSkinningEnabled = true; + + Logger.getLogger(SkinningControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for {0}", spatial); + } else { + switchToSoftware(); + } + } else if (hwSkinningDesired && hwSkinningSupported && !hwSkinningEnabled) { + switchToHardware(); + hwSkinningEnabled = true; + } else if (!hwSkinningDesired && hwSkinningEnabled) { + switchToSoftware(); + hwSkinningEnabled = false; + } + + if (hwSkinningEnabled) { + controlRenderHardware(); + } else { + controlRenderSoftware(); + } + + wasMeshUpdated = true; + } + } + + @Override + protected void controlUpdate(float tpf) { + wasMeshUpdated = false; + armature.update(); + } + + //only do this for software updates + void resetToBind() { + for (Geometry geometry : targets) { + Mesh mesh = geometry.getMesh(); + if (mesh != null && mesh.isAnimated()) { + Buffer bwBuff = mesh.getBuffer(Type.BoneWeight).getData(); + Buffer biBuff = mesh.getBuffer(Type.BoneIndex).getData(); + if (!biBuff.hasArray() || !bwBuff.hasArray()) { + mesh.prepareForAnim(true); // prepare for software animation + } + VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition); + VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal); + VertexBuffer pos = mesh.getBuffer(Type.Position); + VertexBuffer norm = mesh.getBuffer(Type.Normal); + FloatBuffer pb = (FloatBuffer) pos.getData(); + FloatBuffer nb = (FloatBuffer) norm.getData(); + FloatBuffer bpb = (FloatBuffer) bindPos.getData(); + FloatBuffer bnb = (FloatBuffer) bindNorm.getData(); + pb.clear(); + nb.clear(); + bpb.clear(); + bnb.clear(); + + //reseting bind tangents if there is a bind tangent buffer + VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent); + if (bindTangents != null) { + VertexBuffer tangents = mesh.getBuffer(Type.Tangent); + FloatBuffer tb = (FloatBuffer) tangents.getData(); + FloatBuffer btb = (FloatBuffer) bindTangents.getData(); + tb.clear(); + btb.clear(); + tb.put(btb).clear(); + } + + + pb.put(bpb).clear(); + nb.put(bnb).clear(); + } + } + } + + @Override + public Object jmeClone() { + return super.jmeClone(); + } + + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + + this.armature = cloner.clone(armature); + + // If the targets were cloned then this will clone them. If the targets + // were shared then this will share them. + this.targets = cloner.clone(targets); + + this.numberOfJointsParam = cloner.clone(numberOfJointsParam); + this.jointMatricesParam = cloner.clone(jointMatricesParam); + } + + /** + * Access the attachments node of the named bone. If the bone doesn't + * already have an attachments node, create one and attach it to the scene + * graph. Models and effects attached to the attachments node will follow + * the bone's motions. + * + * @param jointName the name of the joint + * @return the attachments node of the joint + */ + public Node getAttachmentsNode(String jointName) { + Joint b = armature.getJoint(jointName); + if (b == null) { + throw new IllegalArgumentException("Given bone name does not exist " + + "in the armature."); + } + + updateTargetsAndMaterials(spatial); + int boneIndex = armature.getJointIndex(b); + Node n = b.getAttachmentsNode(boneIndex, targets); + /* + * Select a node to parent the attachments node. + */ + Node parent; + if (spatial instanceof Node) { + parent = (Node) spatial; // the usual case + } else { + parent = spatial.getParent(); + } + parent.attachChild(n); + + return n; + } + + /** + * returns the armature of this control + * + * @return + */ + public Armature getArmature() { + return armature; + } + + /** + * Enumerate the target meshes of this control. + * + * @return a new array + */ + public Mesh[] getTargets() { + Mesh[] result = new Mesh[targets.size()]; + int i = 0; + for (Geometry geometry : targets) { + Mesh mesh = geometry.getMesh(); + result[i] = mesh; + i++; + } + + return result; + } + + /** + * Update the mesh according to the given transformation matrices + * + * @param mesh then mesh + * @param offsetMatrices the transformation matrices to apply + */ + private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) { + + VertexBuffer tb = mesh.getBuffer(Type.Tangent); + if (tb == null) { + //if there are no tangents use the classic skinning + applySkinning(mesh, offsetMatrices); + } else { + //if there are tangents use the skinning with tangents + applySkinningTangents(mesh, offsetMatrices, tb); + } + + + } + + /** + * Method to apply skinning transforms to a mesh's buffers + * + * @param mesh the mesh + * @param offsetMatrices the offset matices to apply + */ + private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) { + int maxWeightsPerVert = mesh.getMaxNumWeights(); + if (maxWeightsPerVert <= 0) { + throw new IllegalStateException("Max weights per vert is incorrectly set!"); + } + int fourMinusMaxWeights = 4 - maxWeightsPerVert; + + // NOTE: This code assumes the vertex buffer is in bind pose + // resetToBind() has been called this frame + VertexBuffer vb = mesh.getBuffer(Type.Position); + FloatBuffer fvb = (FloatBuffer) vb.getData(); + fvb.rewind(); + + VertexBuffer nb = mesh.getBuffer(Type.Normal); + FloatBuffer fnb = (FloatBuffer) nb.getData(); + fnb.rewind(); + + // get boneIndexes and weights for mesh + IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData()); + FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); + + wb.rewind(); + + float[] weights = wb.array(); + int idxWeights = 0; + + TempVars vars = TempVars.get(); + + float[] posBuf = vars.skinPositions; + float[] normBuf = vars.skinNormals; + + int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length)); + int bufLength = posBuf.length; + for (int i = iterations - 1; i >= 0; i--) { + // read next set of positions and normals from native buffer + bufLength = Math.min(posBuf.length, fvb.remaining()); + fvb.get(posBuf, 0, bufLength); + fnb.get(normBuf, 0, bufLength); + int verts = bufLength / 3; + int idxPositions = 0; + + // iterate vertices and apply skinning transform for each effecting bone + for (int vert = verts - 1; vert >= 0; vert--) { + // Skip this vertex if the first weight is zero. + if (weights[idxWeights] == 0) { + idxPositions += 3; + idxWeights += 4; + continue; + } + + float nmx = normBuf[idxPositions]; + float vtx = posBuf[idxPositions++]; + float nmy = normBuf[idxPositions]; + float vty = posBuf[idxPositions++]; + float nmz = normBuf[idxPositions]; + float vtz = posBuf[idxPositions++]; + + float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0; + + for (int w = maxWeightsPerVert - 1; w >= 0; w--) { + float weight = weights[idxWeights]; + Matrix4f mat = offsetMatrices[ib.get(idxWeights++)]; + + rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; + ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; + rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight; + + rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight; + rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight; + rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight; + } + + idxWeights += fourMinusMaxWeights; + + idxPositions -= 3; + normBuf[idxPositions] = rnx; + posBuf[idxPositions++] = rx; + normBuf[idxPositions] = rny; + posBuf[idxPositions++] = ry; + normBuf[idxPositions] = rnz; + posBuf[idxPositions++] = rz; + } + + fvb.position(fvb.position() - bufLength); + fvb.put(posBuf, 0, bufLength); + fnb.position(fnb.position() - bufLength); + fnb.put(normBuf, 0, bufLength); + } + + vars.release(); + + vb.updateData(fvb); + nb.updateData(fnb); + + } + + /** + * Specific method for skinning with tangents to avoid cluttering the + * classic skinning calculation with null checks that would slow down the + * process even if tangents don't have to be computed. Also the iteration + * has additional indexes since tangent has 4 components instead of 3 for + * pos and norm + * + * @param mesh the mesh + * @param offsetMatrices the offsetMatrices to apply + * @param tb the tangent vertexBuffer + */ + private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) { + int maxWeightsPerVert = mesh.getMaxNumWeights(); + + if (maxWeightsPerVert <= 0) { + throw new IllegalStateException("Max weights per vert is incorrectly set!"); + } + + int fourMinusMaxWeights = 4 - maxWeightsPerVert; + + // NOTE: This code assumes the vertex buffer is in bind pose + // resetToBind() has been called this frame + VertexBuffer vb = mesh.getBuffer(Type.Position); + FloatBuffer fvb = (FloatBuffer) vb.getData(); + fvb.rewind(); + + VertexBuffer nb = mesh.getBuffer(Type.Normal); + + FloatBuffer fnb = (FloatBuffer) nb.getData(); + fnb.rewind(); + + + FloatBuffer ftb = (FloatBuffer) tb.getData(); + ftb.rewind(); + + + // get boneIndexes and weights for mesh + IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData()); + FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); + + wb.rewind(); + + float[] weights = wb.array(); + int idxWeights = 0; + + TempVars vars = TempVars.get(); + + + float[] posBuf = vars.skinPositions; + float[] normBuf = vars.skinNormals; + float[] tanBuf = vars.skinTangents; + + int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length)); + int bufLength = 0; + int tanLength = 0; + for (int i = iterations - 1; i >= 0; i--) { + // read next set of positions and normals from native buffer + bufLength = Math.min(posBuf.length, fvb.remaining()); + tanLength = Math.min(tanBuf.length, ftb.remaining()); + fvb.get(posBuf, 0, bufLength); + fnb.get(normBuf, 0, bufLength); + ftb.get(tanBuf, 0, tanLength); + int verts = bufLength / 3; + int idxPositions = 0; + //tangents has their own index because of the 4 components + int idxTangents = 0; + + // iterate vertices and apply skinning transform for each effecting bone + for (int vert = verts - 1; vert >= 0; vert--) { + // Skip this vertex if the first weight is zero. + if (weights[idxWeights] == 0) { + idxTangents += 4; + idxPositions += 3; + idxWeights += 4; + continue; + } + + float nmx = normBuf[idxPositions]; + float vtx = posBuf[idxPositions++]; + float nmy = normBuf[idxPositions]; + float vty = posBuf[idxPositions++]; + float nmz = normBuf[idxPositions]; + float vtz = posBuf[idxPositions++]; + + float tnx = tanBuf[idxTangents++]; + float tny = tanBuf[idxTangents++]; + float tnz = tanBuf[idxTangents++]; + + // skipping the 4th component of the tangent since it doesn't have to be transformed + idxTangents++; + + float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0; + + for (int w = maxWeightsPerVert - 1; w >= 0; w--) { + float weight = weights[idxWeights]; + Matrix4f mat = offsetMatrices[ib.get(idxWeights++)]; + + rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; + ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; + rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight; + + rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight; + rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight; + rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight; + + rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight; + rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight; + rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight; + } + + idxWeights += fourMinusMaxWeights; + + idxPositions -= 3; + + normBuf[idxPositions] = rnx; + posBuf[idxPositions++] = rx; + normBuf[idxPositions] = rny; + posBuf[idxPositions++] = ry; + normBuf[idxPositions] = rnz; + posBuf[idxPositions++] = rz; + + idxTangents -= 4; + + tanBuf[idxTangents++] = rtx; + tanBuf[idxTangents++] = rty; + tanBuf[idxTangents++] = rtz; + + //once again skipping the 4th component of the tangent + idxTangents++; + } + + fvb.position(fvb.position() - bufLength); + fvb.put(posBuf, 0, bufLength); + fnb.position(fnb.position() - bufLength); + fnb.put(normBuf, 0, bufLength); + ftb.position(ftb.position() - tanLength); + ftb.put(tanBuf, 0, tanLength); + } + + vars.release(); + + vb.updateData(fvb); + nb.updateData(fnb); + tb.updateData(ftb); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(armature, "armature", null); + + oc.write(numberOfJointsParam, "numberOfBonesParam", null); + oc.write(jointMatricesParam, "boneMatricesParam", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + armature = (Armature) in.readSavable("armature", null); + + numberOfJointsParam = (MatParamOverride) in.readSavable("numberOfBonesParam", null); + jointMatricesParam = (MatParamOverride) in.readSavable("boneMatricesParam", null); + + if (numberOfJointsParam == null) { + numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null); + jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null); + getSpatial().addMatParamOverride(numberOfJointsParam); + getSpatial().addMatParamOverride(jointMatricesParam); + } + } + + /** + * Update the lists of animation targets. + * + * @param spatial the controlled spatial + */ + private void updateTargetsAndMaterials(Spatial spatial) { + targets.clear(); + + if (spatial instanceof Node) { + findTargets((Node) spatial); + } else if (spatial instanceof Geometry) { + findTargets((Geometry) spatial); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java new file mode 100644 index 000000000..dee2aefb9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.anim; + +import com.jme3.anim.interpolator.FrameInterpolator; +import com.jme3.anim.tween.Tween; +import com.jme3.anim.util.HasLocalTransform; +import com.jme3.animation.CompactQuaternionArray; +import com.jme3.animation.CompactVector3Array; +import com.jme3.export.*; +import com.jme3.math.*; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; + +import java.io.IOException; + +/** + * Contains a list of transforms and times for each keyframe. + * + * @author Rémy Bouquet + */ +public class TransformTrack implements AnimTrack { + + private double length; + private HasLocalTransform target; + + /** + * Transforms and times for track. + */ + private CompactVector3Array translations; + private CompactQuaternionArray rotations; + private CompactVector3Array scales; + private FrameInterpolator interpolator = FrameInterpolator.DEFAULT; + private float[] times; + + /** + * Serialization-only. Do not use. + */ + public TransformTrack() { + } + + /** + * Creates a transform track for the given bone index + * + * @param times a float array with the time of each frame + * @param translations the translation of the bone for each frame + * @param rotations the rotation of the bone for each frame + * @param scales the scale of the bone for each frame + */ + public TransformTrack(HasLocalTransform target, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { + this.target = target; + this.setKeyframes(times, translations, rotations, scales); + } + + /** + * return the array of rotations of this track + * + * @return + */ + public Quaternion[] getRotations() { + return rotations.toObjectArray(); + } + + /** + * returns the array of scales for this track + * + * @return + */ + public Vector3f[] getScales() { + return scales == null ? null : scales.toObjectArray(); + } + + /** + * returns the arrays of time for this track + * + * @return + */ + public float[] getTimes() { + return times; + } + + /** + * returns the array of translations of this track + * + * @return + */ + public Vector3f[] getTranslations() { + return translations.toObjectArray(); + } + + + /** + * Sets the keyframes times for this Joint track + * + * @param times the keyframes times + */ + public void setTimes(float[] times) { + if (times.length == 0) { + throw new RuntimeException("TransformTrack with no keyframes!"); + } + this.times = times; + length = times[times.length - 1] - times[0]; + } + + /** + * Set the translations for this joint track + * + * @param translations the translation of the bone for each frame + */ + public void setKeyframesTranslation(Vector3f[] translations) { + if (times == null) { + throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first"); + } + if (translations.length == 0) { + throw new RuntimeException("TransformTrack with no translation keyframes!"); + } + this.translations = new CompactVector3Array(); + this.translations.add(translations); + this.translations.freeze(); + + assert times != null && times.length == translations.length; + } + + /** + * Set the scales for this joint track + * + * @param scales the scales of the bone for each frame + */ + public void setKeyframesScale(Vector3f[] scales) { + if (times == null) { + throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first"); + } + if (scales.length == 0) { + throw new RuntimeException("TransformTrack with no scale keyframes!"); + } + this.scales = new CompactVector3Array(); + this.scales.add(scales); + this.scales.freeze(); + + assert times != null && times.length == scales.length; + } + + /** + * Set the rotations for this joint track + * + * @param rotations the rotations of the bone for each frame + */ + public void setKeyframesRotation(Quaternion[] rotations) { + if (times == null) { + throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first"); + } + if (rotations.length == 0) { + throw new RuntimeException("TransformTrack with no rotation keyframes!"); + } + this.rotations = new CompactQuaternionArray(); + this.rotations.add(rotations); + this.rotations.freeze(); + + assert times != null && times.length == rotations.length; + } + + + /** + * Set the translations, rotations and scales for this bone track + * + * @param times a float array with the time of each frame + * @param translations the translation of the bone for each frame + * @param rotations the rotation of the bone for each frame + * @param scales the scale of the bone for each frame + */ + public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { + setTimes(times); + if (translations != null) { + setKeyframesTranslation(translations); + } + if (rotations != null) { + setKeyframesRotation(rotations); + } + if (scales != null) { + setKeyframesScale(scales); + } + } + + public double getLength() { + return length; + } + + public void getDataAtTime(double t, Transform transform) { + float time = (float) t; + + int lastFrame = times.length - 1; + if (time < 0 || lastFrame == 0) { + if (translations != null) { + translations.get(0, transform.getTranslation()); + } + if (rotations != null) { + rotations.get(0, transform.getRotation()); + } + if (scales != null) { + scales.get(0, transform.getScale()); + } + return; + } + + int startFrame = 0; + int endFrame = 1; + float blend = 0; + if (time >= times[lastFrame]) { + startFrame = lastFrame; + + time = time - times[startFrame] + times[startFrame - 1]; + blend = (time - times[startFrame - 1]) + / (times[startFrame] - times[startFrame - 1]); + + } else { + // use lastFrame so we never overflow the array + int i; + for (i = 0; i < lastFrame && times[i] < time; i++) { + startFrame = i; + endFrame = i + 1; + } + blend = (time - times[startFrame]) + / (times[endFrame] - times[startFrame]); + } + + Transform interpolated = interpolator.interpolate(blend, startFrame, translations, rotations, scales, times); + + if (translations != null) { + transform.setTranslation(interpolated.getTranslation()); + } + if (rotations != null) { + transform.setRotation(interpolated.getRotation()); + } + if (scales != null) { + transform.setScale(interpolated.getScale()); + } + } + + public void setFrameInterpolator(FrameInterpolator interpolator) { + this.interpolator = interpolator; + } + + public HasLocalTransform getTarget() { + return target; + } + + public void setTarget(HasLocalTransform target) { + this.target = target; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(translations, "translations", null); + oc.write(rotations, "rotations", null); + oc.write(times, "times", null); + oc.write(scales, "scales", null); + oc.write(target, "target", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + translations = (CompactVector3Array) ic.readSavable("translations", null); + rotations = (CompactQuaternionArray) ic.readSavable("rotations", null); + times = ic.readFloatArray("times", null); + scales = (CompactVector3Array) ic.readSavable("scales", null); + target = (HasLocalTransform) ic.readSavable("target", null); + setTimes(times); + } + + @Override + public Object jmeClone() { + try { + TransformTrack clone = (TransformTrack) super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields(Cloner cloner, Object original) { + this.target = cloner.clone(target); + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/Weights.java b/jme3-core/src/main/java/com/jme3/anim/Weights.java new file mode 100644 index 000000000..8ac9b5b4d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/Weights.java @@ -0,0 +1,95 @@ +package com.jme3.anim; + +import java.util.ArrayList; + +public class Weights {//} extends Savable, JmeCloneable{ + + + private final static float MIN_WEIGHT = 0.005f; + + private int[] indices; + private float[] data; + private int size; + + public Weights(float[] array, int start, int length) { + ArrayList list = new ArrayList<>(); + ArrayList idx = new ArrayList<>(); + + for (int i = start; i < length; i++) { + float val = array[i]; + if (val > MIN_WEIGHT) { + list.add(val); + idx.add(i); + } + } + size = list.size(); + data = new float[size]; + indices = new int[size]; + for (int i = 0; i < size; i++) { + data[i] = list.get(i); + indices[i] = idx.get(i); + } + } + + public int getSize() { + return size; + } + + // public Weights(float[] array, int start, int length) { +// LinkedList list = new LinkedList<>(); +// LinkedList idx = new LinkedList<>(); +// for (int i = start; i < length; i++) { +// float val = array[i]; +// if (val > MIN_WEIGHT) { +// int index = insert(list, val); +// if (idx.size() < index) { +// idx.add(i); +// } else { +// idx.add(index, i); +// } +// } +// } +// data = new float[list.size()]; +// for (int i = 0; i < data.length; i++) { +// data[i] = list.get(i); +// } +// +// indices = new int[idx.size()]; +// for (int i = 0; i < indices.length; i++) { +// indices[i] = idx.get(i); +// } +// } +// +// private int insert(LinkedList list, float value) { +// for (int i = 0; i < list.size(); i++) { +// float w = list.get(i); +// if (value > w) { +// list.add(i, value); +// return i; +// } +// } +// +// list.add(value); +// return list.size(); +// } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < indices.length; i++) { + b.append(indices[i]).append(","); + } + b.append("\n"); + for (int i = 0; i < data.length; i++) { + b.append(data[i]).append(","); + } + return b.toString(); + } + + public static void main(String... args) { + // 6 7 4 8 + float values[] = {0, 0, 0, 0, 0.5f, 0.001f, 0.7f, 0.6f, 0.2f, 0, 0, 0}; + Weights w = new Weights(values, 0, values.length); + System.err.println(w); + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolator.java b/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolator.java new file mode 100644 index 000000000..a924a9d28 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolator.java @@ -0,0 +1,13 @@ +package com.jme3.anim.interpolator; + +import static com.jme3.anim.interpolator.FrameInterpolator.TrackDataReader; +import static com.jme3.anim.interpolator.FrameInterpolator.TrackTimeReader; + +/** + * Created by nehon on 15/04/17. + */ +public abstract class AnimInterpolator { + + public abstract T interpolate(float t, int currentIndex, TrackDataReader data, TrackTimeReader times, T store); + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java b/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java new file mode 100644 index 000000000..5f8b5f304 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java @@ -0,0 +1,149 @@ +package com.jme3.anim.interpolator; + +import com.jme3.math.*; + +import static com.jme3.anim.interpolator.FrameInterpolator.TrackDataReader; +import static com.jme3.anim.interpolator.FrameInterpolator.TrackTimeReader; + +/** + * Created by nehon on 15/04/17. + */ +public class AnimInterpolators { + + //Rotation interpolators + + public static final AnimInterpolator NLerp = new AnimInterpolator() { + private Quaternion next = new Quaternion(); + + @Override + public Quaternion interpolate(float t, int currentIndex, TrackDataReader data, TrackTimeReader times, Quaternion store) { + data.getEntryClamp(currentIndex, store); + data.getEntryClamp(currentIndex + 1, next); + store.nlerp(next, t); + return store; + } + }; + + public static final AnimInterpolator SLerp = new AnimInterpolator() { + private Quaternion next = new Quaternion(); + + @Override + public Quaternion interpolate(float t, int currentIndex, TrackDataReader data, TrackTimeReader times, Quaternion store) { + data.getEntryClamp(currentIndex, store); + data.getEntryClamp(currentIndex + 1, next); + //MathUtils.slerpNoInvert(store, next, t, store); + MathUtils.slerp(store, next, t, store); + return store; + } + }; + + public static final AnimInterpolator SQuad = new AnimInterpolator() { + private Quaternion a = new Quaternion(); + private Quaternion b = new Quaternion(); + + private Quaternion q0 = new Quaternion(); + private Quaternion q1 = new Quaternion(); + private Quaternion q2 = new Quaternion(); + private Quaternion q3 = new Quaternion(); + + @Override + public Quaternion interpolate(float t, int currentIndex, TrackDataReader data, TrackTimeReader times, Quaternion store) { + data.getEntryModSkip(currentIndex - 1, q0); + data.getEntryModSkip(currentIndex, q1); + data.getEntryModSkip(currentIndex + 1, q2); + data.getEntryModSkip(currentIndex + 2, q3); + MathUtils.squad(q0, q1, q2, q3, a, b, t, store); + return store; + } + }; + + //Position / Scale interpolators + public static final AnimInterpolator LinearVec3f = new AnimInterpolator() { + private Vector3f next = new Vector3f(); + + @Override + public Vector3f interpolate(float t, int currentIndex, TrackDataReader data, TrackTimeReader times, Vector3f store) { + data.getEntryClamp(currentIndex, store); + data.getEntryClamp(currentIndex + 1, next); + store.interpolateLocal(next, t); + return store; + } + }; + /** + * CatmullRom interpolation + */ + public static final CatmullRomInterpolator CatmullRom = new CatmullRomInterpolator(); + + public static class CatmullRomInterpolator extends AnimInterpolator { + private Vector3f p0 = new Vector3f(); + private Vector3f p1 = new Vector3f(); + private Vector3f p2 = new Vector3f(); + private Vector3f p3 = new Vector3f(); + private float tension = 0.7f; + + public CatmullRomInterpolator(float tension) { + this.tension = tension; + } + + public CatmullRomInterpolator() { + } + + @Override + public Vector3f interpolate(float t, int currentIndex, TrackDataReader data, TrackTimeReader times, Vector3f store) { + data.getEntryModSkip(currentIndex - 1, p0); + data.getEntryModSkip(currentIndex, p1); + data.getEntryModSkip(currentIndex + 1, p2); + data.getEntryModSkip(currentIndex + 2, p3); + + FastMath.interpolateCatmullRom(t, tension, p0, p1, p2, p3, store); + return store; + } + } + + //Time Interpolators + + public static class TimeInterpolator extends AnimInterpolator { + private EaseFunction ease; + + public TimeInterpolator(EaseFunction ease) { + this.ease = ease; + } + + @Override + public Float interpolate(float t, int currentIndex, TrackDataReader data, TrackTimeReader times, Float store) { + return ease.apply(t); + } + } + + //in + public static final TimeInterpolator easeInQuad = new TimeInterpolator(Easing.inQuad); + public static final TimeInterpolator easeInCubic = new TimeInterpolator(Easing.inCubic); + public static final TimeInterpolator easeInQuart = new TimeInterpolator(Easing.inQuart); + public static final TimeInterpolator easeInQuint = new TimeInterpolator(Easing.inQuint); + public static final TimeInterpolator easeInBounce = new TimeInterpolator(Easing.inBounce); + public static final TimeInterpolator easeInElastic = new TimeInterpolator(Easing.inElastic); + + //out + public static final TimeInterpolator easeOutQuad = new TimeInterpolator(Easing.outQuad); + public static final TimeInterpolator easeOutCubic = new TimeInterpolator(Easing.outCubic); + public static final TimeInterpolator easeOutQuart = new TimeInterpolator(Easing.outQuart); + public static final TimeInterpolator easeOutQuint = new TimeInterpolator(Easing.outQuint); + public static final TimeInterpolator easeOutBounce = new TimeInterpolator(Easing.outBounce); + public static final TimeInterpolator easeOutElastic = new TimeInterpolator(Easing.outElastic); + + //inout + public static final TimeInterpolator easeInOutQuad = new TimeInterpolator(Easing.inOutQuad); + public static final TimeInterpolator easeInOutCubic = new TimeInterpolator(Easing.inOutCubic); + public static final TimeInterpolator easeInOutQuart = new TimeInterpolator(Easing.inOutQuart); + public static final TimeInterpolator easeInOutQuint = new TimeInterpolator(Easing.inOutQuint); + public static final TimeInterpolator easeInOutBounce = new TimeInterpolator(Easing.inOutBounce); + public static final TimeInterpolator easeInOutElastic = new TimeInterpolator(Easing.inOutElastic); + + //extra + public static final TimeInterpolator smoothStep = new TimeInterpolator(Easing.smoothStep); + public static final TimeInterpolator smootherStep = new TimeInterpolator(Easing.smootherStep); + + public static final TimeInterpolator constant = new TimeInterpolator(Easing.constant); + + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java b/jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java new file mode 100644 index 000000000..9577107a8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java @@ -0,0 +1,136 @@ +package com.jme3.anim.interpolator; + +import com.jme3.animation.*; +import com.jme3.math.*; + +/** + * Created by nehon on 15/04/17. + */ +public class FrameInterpolator { + + public static final FrameInterpolator DEFAULT = new FrameInterpolator(); + + private AnimInterpolator timeInterpolator; + private AnimInterpolator translationInterpolator = AnimInterpolators.LinearVec3f; + private AnimInterpolator rotationInterpolator = AnimInterpolators.NLerp; + private AnimInterpolator scaleInterpolator = AnimInterpolators.LinearVec3f; + + private TrackDataReader translationReader = new TrackDataReader<>(); + private TrackDataReader rotationReader = new TrackDataReader<>(); + private TrackDataReader scaleReader = new TrackDataReader<>(); + private TrackTimeReader timesReader = new TrackTimeReader(); + + + private Transform transforms = new Transform(); + + public Transform interpolate(float t, int currentIndex, CompactVector3Array translations, CompactQuaternionArray rotations, CompactVector3Array scales, float[] times){ + timesReader.setData(times); + if( timeInterpolator != null){ + t = timeInterpolator.interpolate(t,currentIndex, null, timesReader, null ); + } + if(translations != null) { + translationReader.setData(translations); + translationInterpolator.interpolate(t, currentIndex, translationReader, timesReader, transforms.getTranslation()); + } + if(rotations != null) { + rotationReader.setData(rotations); + rotationInterpolator.interpolate(t, currentIndex, rotationReader, timesReader, transforms.getRotation()); + } + if(scales != null){ + scaleReader.setData(scales); + scaleInterpolator.interpolate(t, currentIndex, scaleReader, timesReader, transforms.getScale()); + } + return transforms; + } + + public void interpolateWeights(float t, int currentIndex, float[] weights, int nbMorphTargets, float[] store) { + int start = currentIndex * nbMorphTargets; + for (int i = 0; i < nbMorphTargets; i++) { + int current = start + i; + int next = current + nbMorphTargets; + if (next >= weights.length) { + next = current; + } + + float val = FastMath.interpolateLinear(t, weights[current], weights[next]); + store[i] = val; + } + } + + public void setTimeInterpolator(AnimInterpolator timeInterpolator) { + this.timeInterpolator = timeInterpolator; + } + + public void setTranslationInterpolator(AnimInterpolator translationInterpolator) { + this.translationInterpolator = translationInterpolator; + } + + public void setRotationInterpolator(AnimInterpolator rotationInterpolator) { + this.rotationInterpolator = rotationInterpolator; + } + + public void setScaleInterpolator(AnimInterpolator scaleInterpolator) { + this.scaleInterpolator = scaleInterpolator; + } + + + public static class TrackTimeReader { + private float[] data; + + protected void setData(float[] data) { + this.data = data; + } + + public float getEntry(int index) { + return data[mod(index, data.length)]; + } + + public int getLength() { + return data.length; + } + } + + public static class TrackDataReader { + + private CompactArray data; + + protected void setData(CompactArray data) { + this.data = data; + } + + public T getEntryMod(int index, T store) { + return data.get(mod(index, data.getTotalObjectSize()), store); + } + + public T getEntryClamp(int index, T store) { + index = (int) FastMath.clamp(index, 0, data.getTotalObjectSize() - 1); + return data.get(index, store); + } + + public T getEntryModSkip(int index, T store) { + int total = data.getTotalObjectSize(); + if (index == -1) { + index--; + } else if (index >= total) { + index++; + } + + index = mod(index, total); + + + return data.get(index, store); + } + } + + /** + * Euclidean modulo (cycle on 0,n instead of -n,0; 0,n) + * + * @param val + * @param n + * @return + */ + private static int mod(int val, int n) { + return ((val % n) + n) % n; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java b/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java new file mode 100644 index 000000000..272ec8dae --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java @@ -0,0 +1,97 @@ +/* + * $Id$ + * + * Copyright (c) 2015, Simsilica, LLC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.anim.tween; + + +import com.jme3.export.*; + +import java.io.IOException; + +/** + * Base implementation of the Tween interface that provides + * default implementations of the getLength() and interopolate() + * methods that provide common tween clamping and bounds checking. + * Subclasses need only override the doInterpolate() method and + * the rest is handled for them. + * + * @author Paul Speed + */ +public abstract class AbstractTween implements Tween { + + private double length; + + protected AbstractTween(double length) { + this.length = length; + } + + @Override + public double getLength() { + return length; + } + + public void setLength(double length) { + this.length = length; + } + + /** + * Default implementation clamps the time value, converts + * it to 0 to 1.0 based on getLength(), and calls doInterpolate(). + */ + @Override + public boolean interpolate(double t) { + if (t < 0) { + return true; + } + + // Scale t to be between 0 and 1 for our length + if (length == 0) { + t = 1; + } else { + t = t / length; + } + + boolean done = false; + if (t >= 1.0) { + t = 1.0; + done = true; + } + doInterpolate(t); + return !done; + } + + protected abstract void doInterpolate(double t); +} diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/ContainsTweens.java b/jme3-core/src/main/java/com/jme3/anim/tween/ContainsTweens.java new file mode 100644 index 000000000..b7fe69bfc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/ContainsTweens.java @@ -0,0 +1,6 @@ +package com.jme3.anim.tween; + +public interface ContainsTweens { + + public Tween[] getTweens(); +} diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java b/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java new file mode 100644 index 000000000..9ed4752c8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java @@ -0,0 +1,71 @@ +/* + * $Id$ + * + * Copyright (c) 2015, Simsilica, LLC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.anim.tween; + + +import com.jme3.export.Savable; + +/** + * Represents some action that interpolates across input between 0 + * and some length value. (For example, movement, rotation, fading.) + * It's also possible to have zero length 'instant' tweens. + * + * @author Paul Speed + */ +public interface Tween extends Cloneable { + + /** + * Returns the length of the tween. If 't' represents time in + * seconds then this is the notional time in seconds that the tween + * will run. Note: all of the caveats are because tweens may be + * externally scaled in such a way that 't' no longer represents + * actual time. + */ + public double getLength(); + + /** + * Sets the implementation specific interpolation to the + * specified 'tween' value as a value in the range from 0 to + * getLength(). If the value is greater or equal to getLength() + * then it is internally clamped and the method returns false. + * If 't' is still in the tween's range then this method returns + * true. + */ + public boolean interpolate(double t); + +} + diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java b/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java new file mode 100644 index 000000000..54ff3986b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java @@ -0,0 +1,619 @@ +/* + * $Id$ + * + * Copyright (c) 2015, Simsilica, LLC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.anim.tween; + +import com.jme3.anim.util.Primitives; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Static utility methods for creating common generic Tween objects. + * + * @author Paul Speed + */ +public class Tweens { + + static Logger log = Logger.getLogger(Tweens.class.getName()); + + private static final CurveFunction SMOOTH = new SmoothStep(); + private static final CurveFunction SINE = new Sine(); + + /** + * Creates a tween that will interpolate over an entire sequence + * of tweens in order. + */ + public static Tween sequence(Tween... delegates) { + return new Sequence(delegates); + } + + /** + * Creates a tween that will interpolate over an entire list + * of tweens in parallel, ie: all tweens will be run at the same + * time. + */ + public static Tween parallel(Tween... delegates) { + return new Parallel(delegates); + } + + /** + * Creates a tween that will perform a no-op until the length + * has expired. + */ + public static Tween delay(double length) { + return new Delay(length); + } + + /** + * Creates a tween that scales the specified delegate tween or tweens + * to the desired length. If more than one tween is specified then they + * are wrapped in a sequence using the sequence() method. + */ + public static Tween stretch(double desiredLength, Tween... delegates) { + if (delegates.length == 1) { + return new Stretch(delegates[0], desiredLength); + } + return new Stretch(sequence(delegates), desiredLength); + } + + /** + * Creates a tween that uses a sine function to smooth step the time value + * for the specified delegate tween or tweens. These 'curved' wrappers + * can be used to smooth the interpolation of another tween. + */ + public static Tween sineStep(Tween... delegates) { + if (delegates.length == 1) { + return new Curve(delegates[0], SINE); + } + return new Curve(sequence(delegates), SINE); + } + + /** + * Creates a tween that uses a hermite function to smooth step the time value + * for the specified delegate tween or tweens. This is similar to GLSL's + * smoothstep(). These 'curved' wrappers can be used to smooth the interpolation + * of another tween. + */ + public static Tween smoothStep(Tween... delegates) { + if (delegates.length == 1) { + return new Curve(delegates[0], SMOOTH); + } + return new Curve(sequence(delegates), SMOOTH); + } + + /** + * Creates a Tween that will call the specified method and optional arguments + * whenever supplied a time value greater than or equal to 0. This creates + * an "instant" tween of length 0. + */ + public static Tween callMethod(Object target, String method, Object... args) { + return new CallMethod(target, method, args); + } + + /** + * Creates a Tween that will call the specified method and optional arguments, + * including the time value scaled between 0 and 1. The method must take + * a float or double value as its first or last argument, in addition to whatever + * optional arguments are specified. + *

+ *

For example:

+ *
Tweens.callTweenMethod(1, myObject, "foo", "bar")
+ *

Would work for any of the following method signatures:

+ *
+     *    void foo( float t, String arg )
+     *    void foo( double t, String arg )
+     *    void foo( String arg, float t )
+     *    void foo( String arg, double t )
+     *  
+ */ + public static Tween callTweenMethod(double length, Object target, String method, Object... args) { + return new CallTweenMethod(length, target, method, args); + } + + private static interface CurveFunction { + public double curve(double input); + } + + /** + * Curve function for Hermite interpolation ala GLSL smoothstep(). + */ + private static class SmoothStep implements CurveFunction { + + @Override + public double curve(double t) { + if (t < 0) { + return 0; + } else if (t > 1) { + return 1; + } + return t * t * (3 - 2 * t); + } + } + + private static class Sine implements CurveFunction { + + @Override + public double curve(double t) { + if (t < 0) { + return 0; + } else if (t > 1) { + return 1; + } + // Sine starting at -90 will go from -1 to 1 through 0 + double result = Math.sin(t * Math.PI - Math.PI * 0.5); + return (result + 1) * 0.5; + } + } + + private static class Curve implements Tween { + private final Tween delegate; + private final CurveFunction func; + private final double length; + + public Curve(Tween delegate, CurveFunction func) { + this.delegate = delegate; + this.func = func; + this.length = delegate.getLength(); + } + + @Override + public double getLength() { + return length; + } + + @Override + public boolean interpolate(double t) { + // Sanity check the inputs + if (t < 0) { + return true; + } + + if (length == 0) { + // Caller did something strange but we'll allow it + return delegate.interpolate(t); + } + + t = func.curve(t / length); + return delegate.interpolate(t * length); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[delegate=" + delegate + ", func=" + func + "]"; + } + } + + private static class Sequence implements Tween, ContainsTweens { + private final Tween[] delegates; + private int current = 0; + private double baseTime; + private double length; + + public Sequence(Tween... delegates) { + this.delegates = delegates; + for (Tween t : delegates) { + length += t.getLength(); + } + } + + @Override + public double getLength() { + return length; + } + + @Override + public boolean interpolate(double t) { + + // Sanity check the inputs + if (t < 0) { + return true; + } + + if (t < baseTime) { + // We've rolled back before the current sequence step + // which means we need to reset and start forward + // again. We have no idea how to 'roll back' and + // this is the only way to maintain consistency. + // The only 'normal' case where this happens is when looping + // in which case a full rollback is appropriate. + current = 0; + baseTime = 0; + } + + if (current >= delegates.length) { + return false; + } + + // Skip any that are done + while (!delegates[current].interpolate(t - baseTime)) { + // Time to go to the next one + baseTime += delegates[current].getLength(); + current++; + if (current >= delegates.length) { + return false; + } + } + + return true; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[delegates=" + Arrays.asList(delegates) + "]"; + } + + @Override + public Tween[] getTweens() { + return delegates; + } + } + + private static class Parallel implements Tween, ContainsTweens { + private final Tween[] delegates; + private final boolean[] done; + private double length; + private double lastTime; + + public Parallel(Tween... delegates) { + this.delegates = delegates; + done = new boolean[delegates.length]; + + for (Tween t : delegates) { + if (t.getLength() > length) { + length = t.getLength(); + } + } + } + + @Override + public double getLength() { + return length; + } + + protected void reset() { + for (int i = 0; i < done.length; i++) { + done[i] = false; + } + } + + @Override + public boolean interpolate(double t) { + // Sanity check the inputs + if (t < 0) { + return true; + } + + if (t < lastTime) { + // We've rolled back before the last time we were given. + // This means we may have 'done'ed a few tasks that now + // need to be run again. Better to just reset and start + // over. As mentioned in the Sequence task, the only 'normal' + // use-case for time rolling backwards is when looping. And + // in that case, we want to start from the beginning anyway. + reset(); + } + lastTime = t; + + int runningCount = delegates.length; + for (int i = 0; i < delegates.length; i++) { + if (!done[i]) { + done[i] = !delegates[i].interpolate(t); + } + if (done[i]) { + runningCount--; + } + } + return runningCount > 0; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[delegates=" + Arrays.asList(delegates) + "]"; + } + + @Override + public Tween[] getTweens() { + return delegates; + } + } + + private static class Delay extends AbstractTween { + + public Delay(double length) { + super(length); + } + + @Override + protected void doInterpolate(double t) { + } + } + + private static class Stretch implements Tween, ContainsTweens { + + private final Tween[] delegate = new Tween[1]; + private final double length; + private final double scale; + + public Stretch(Tween delegate, double length) { + this.delegate[0] = delegate; + + this.length = length; + + // Caller desires delegate to be 'length' instead of + // it's actual length so we will calculate a time scale + // If the desired length is longer than delegate's then + // we need to feed time in slower, ie: scale < 1 + if (length != 0) { + this.scale = delegate.getLength() / length; + } else { + this.scale = 0; + } + } + + @Override + public double getLength() { + return length; + } + + @Override + public Tween[] getTweens() { + return delegate; + } + + @Override + public boolean interpolate(double t) { + if (t < 0) { + return true; + } + if (length > 0) { + t *= scale; + } else { + t = length; + } + return delegate[0].interpolate(t); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[delegate=" + delegate[0] + ", length=" + length + "]"; + } + } + + private static class CallMethod extends AbstractTween { + + private Object target; + private Method method; + private Object[] args; + + public CallMethod(Object target, String methodName, Object... args) { + super(0); + if (target == null) { + throw new IllegalArgumentException("Target cannot be null."); + } + this.target = target; + this.args = args; + + // Lookup the method + if (args == null) { + this.method = findMethod(target.getClass(), methodName); + } else { + this.method = findMethod(target.getClass(), methodName, args); + } + if (this.method == null) { + throw new IllegalArgumentException("Method not found for:" + methodName + " on type:" + target.getClass()); + } + this.method.setAccessible(true); + } + + private static Method findMethod(Class type, String name, Object... args) { + for (Method m : type.getDeclaredMethods()) { + if (!Objects.equals(m.getName(), name)) { + continue; + } + Class[] paramTypes = m.getParameterTypes(); + if (paramTypes.length != args.length) { + continue; + } + int matches = 0; + for (int i = 0; i < args.length; i++) { + if (paramTypes[i].isInstance(args[i]) + || Primitives.wrap(paramTypes[i]).isInstance(args[i])) { + matches++; + } + } + if (matches == args.length) { + return m; + } + } + if (type.getSuperclass() != null) { + return findMethod(type.getSuperclass(), name, args); + } + return null; + } + + @Override + protected void doInterpolate(double t) { + try { + method.invoke(target, args); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error running method:" + method + " for object:" + target, e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error running method:" + method + " for object:" + target, e); + } + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[method=" + method + ", parms=" + Arrays.asList(args) + "]"; + } + } + + private static class CallTweenMethod extends AbstractTween { + + private Object target; + private Method method; + private Object[] args; + private int tIndex = -1; + private boolean isFloat = false; + + public CallTweenMethod(double length, Object target, String methodName, Object... args) { + super(length); + if (target == null) { + throw new IllegalArgumentException("Target cannot be null."); + } + this.target = target; + + // Lookup the method + this.method = findMethod(target.getClass(), methodName, args); + if (this.method == null) { + throw new IllegalArgumentException("Method not found for:" + methodName + " on type:" + target.getClass()); + } + this.method.setAccessible(true); + + // So now setup the real args list + this.args = new Object[args.length + 1]; + if (tIndex == 0) { + for (int i = 0; i < args.length; i++) { + this.args[i + 1] = args[i]; + } + } else { + for (int i = 0; i < args.length; i++) { + this.args[i] = args[i]; + } + } + } + + private static boolean isFloatType(Class type) { + return type == Float.TYPE || type == Float.class; + } + + private static boolean isDoubleType(Class type) { + return type == Double.TYPE || type == Double.class; + } + + private Method findMethod(Class type, String name, Object... args) { + for (Method m : type.getDeclaredMethods()) { + if (!Objects.equals(m.getName(), name)) { + continue; + } + Class[] paramTypes = m.getParameterTypes(); + if (paramTypes.length != args.length + 1) { + if (log.isLoggable(Level.FINE)) { + log.log(Level.FINE, "Param lengths of [" + m + "] differ. method arg count:" + paramTypes.length + " lookging for:" + (args.length + 1)); + } + continue; + } + + // We accept the 't' parameter as either first or last + // so we'll see which one matches. + if (isFloatType(paramTypes[0]) || isDoubleType(paramTypes[0])) { + // Try it as the first parameter + int matches = 0; + + for (int i = 1; i < paramTypes.length; i++) { + if (paramTypes[i].isInstance(args[i - 1])) { + matches++; + } + } + if (matches == args.length) { + // Then this is our method and this is how we are configured + tIndex = 0; + isFloat = isFloatType(paramTypes[0]); + } else { + if (log.isLoggable(Level.FINE)) { + log.log(Level.FINE, m + " Leading float check failed because of type mismatches, for:" + m); + } + } + } + if (tIndex >= 0) { + return m; + } + + // Else try it at the end + int last = paramTypes.length - 1; + if (isFloatType(paramTypes[last]) || isDoubleType(paramTypes[last])) { + int matches = 0; + + for (int i = 0; i < last; i++) { + if (paramTypes[i].isInstance(args[i])) { + matches++; + } + } + if (matches == args.length) { + // Then this is our method and this is how we are configured + tIndex = last; + isFloat = isFloatType(paramTypes[last]); + return m; + } else { + if (log.isLoggable(Level.FINE)) { + log.log(Level.FINE, "Trailing float check failed because of type mismatches, for:" + m); + } + } + } + } + if (type.getSuperclass() != null) { + return findMethod(type.getSuperclass(), name, args); + } + return null; + } + + @Override + protected void doInterpolate(double t) { + try { + if (isFloat) { + args[tIndex] = (float) t; + } else { + args[tIndex] = t; + } + method.invoke(target, args); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error running method:" + method + " for object:" + target, e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error running method:" + method + " for object:" + target, e); + } + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[method=" + method + ", parms=" + Arrays.asList(args) + "]"; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java new file mode 100644 index 000000000..e4038caff --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java @@ -0,0 +1,70 @@ +package com.jme3.anim.tween.action; + +import com.jme3.anim.AnimationMask; +import com.jme3.anim.tween.Tween; + +public abstract class Action implements Tween { + + protected Action[] actions; + private double length; + private double speed = 1; + private AnimationMask mask; + private boolean forward = true; + + protected Action(Tween... tweens) { + this.actions = new Action[tweens.length]; + for (int i = 0; i < tweens.length; i++) { + Tween tween = tweens[i]; + if (tween instanceof Action) { + this.actions[i] = (Action) tween; + } else { + this.actions[i] = new BaseAction(tween); + } + } + } + + @Override + public double getLength() { + return length; + } + + protected void setLength(double length) { + this.length = length; + } + + public double getSpeed() { + return speed; + } + + public void setSpeed(double speed) { + this.speed = speed; + if( speed < 0){ + setForward(false); + } else { + setForward(true); + } + } + + public AnimationMask getMask() { + return mask; + } + + public void setMask(AnimationMask mask) { + this.mask = mask; + } + + protected boolean isForward() { + return forward; + } + + protected void setForward(boolean forward) { + if(this.forward == forward){ + return; + } + this.forward = forward; + for (Action action : actions) { + action.setForward(forward); + } + + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java new file mode 100644 index 000000000..d2e891343 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java @@ -0,0 +1,38 @@ +package com.jme3.anim.tween.action; + +import com.jme3.anim.tween.ContainsTweens; +import com.jme3.anim.tween.Tween; +import com.jme3.util.SafeArrayList; + +import java.util.Collections; +import java.util.List; + +public class BaseAction extends Action { + + private Tween tween; + + public BaseAction(Tween tween) { + this.tween = tween; + setLength(tween.getLength()); + List subActions = new SafeArrayList<>(Action.class); + gatherActions(tween, subActions); + actions = new Action[subActions.size()]; + subActions.toArray(actions); + } + + private void gatherActions(Tween tween, List subActions) { + if (tween instanceof Action) { + subActions.add((Action) tween); + } else if (tween instanceof ContainsTweens) { + Tween[] tweens = ((ContainsTweens) tween).getTweens(); + for (Tween t : tweens) { + gatherActions(t, subActions); + } + } + } + + @Override + public boolean interpolate(double t) { + return tween.interpolate(t); + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendAction.java new file mode 100644 index 000000000..9c5848e4d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendAction.java @@ -0,0 +1,129 @@ +package com.jme3.anim.tween.action; + +import com.jme3.anim.util.HasLocalTransform; +import com.jme3.math.Transform; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class BlendAction extends BlendableAction { + + private int firstActiveIndex; + private int secondActiveIndex; + private BlendSpace blendSpace; + private float blendWeight; + private double[] timeFactor; + private Map targetMap = new HashMap<>(); + + public BlendAction(BlendSpace blendSpace, BlendableAction... actions) { + super(actions); + timeFactor = new double[actions.length]; + this.blendSpace = blendSpace; + blendSpace.setBlendAction(this); + + for (BlendableAction action : actions) { + if (action.getLength() > getLength()) { + setLength(action.getLength()); + } + Collection targets = action.getTargets(); + for (HasLocalTransform target : targets) { + Transform t = targetMap.get(target); + if (t == null) { + t = new Transform(); + targetMap.put(target, t); + } + } + } + + //Blending effect maybe unexpected when blended animation don't have the same length + //Stretching any action that doesn't have the same length. + for (int i = 0; i < this.actions.length; i++) { + this.timeFactor[i] = 1; + if (this.actions[i].getLength() != getLength()) { + double actionLength = this.actions[i].getLength(); + if (actionLength > 0 && getLength() > 0) { + this.timeFactor[i] = this.actions[i].getLength() / getLength(); + } + } + } + } + + public void doInterpolate(double t) { + blendWeight = blendSpace.getWeight(); + BlendableAction firstActiveAction = (BlendableAction) actions[firstActiveIndex]; + BlendableAction secondActiveAction = (BlendableAction) actions[secondActiveIndex]; + firstActiveAction.setCollectTransformDelegate(this); + secondActiveAction.setCollectTransformDelegate(this); + + //only interpolate the first action if the weight if below 1. + if (blendWeight < 1f) { + firstActiveAction.setWeight(1f); + firstActiveAction.interpolate(t * timeFactor[firstActiveIndex]); + if (blendWeight == 0) { + for (HasLocalTransform target : targetMap.keySet()) { + collect(target, targetMap.get(target)); + } + } + } + + //Second action should be interpolated + secondActiveAction.setWeight(blendWeight); + secondActiveAction.interpolate(t * timeFactor[secondActiveIndex]); + + firstActiveAction.setCollectTransformDelegate(null); + secondActiveAction.setCollectTransformDelegate(null); + + } + + protected Action[] getActions() { + return actions; + } + + public BlendSpace getBlendSpace() { + return blendSpace; + } + + protected void setFirstActiveIndex(int index) { + this.firstActiveIndex = index; + } + + protected void setSecondActiveIndex(int index) { + this.secondActiveIndex = index; + } + + @Override + public Collection getTargets() { + return targetMap.keySet(); + } + + @Override + public void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source) { + + Transform tr = targetMap.get(target); + if (weight == 1) { + tr.set(t); + } else if (weight > 0) { + tr.interpolateTransforms(tr, t, weight); + } + + if (source == actions[secondActiveIndex]) { + collect(target, tr); + } + } + + private void collect(HasLocalTransform target, Transform tr) { + if (collectTransformDelegate != null) { + collectTransformDelegate.collectTransform(target, tr, this.getWeight(), this); + } else { + if (getTransitionWeight() == 1) { + target.setLocalTransform(tr); + } else { + Transform trans = target.getLocalTransform(); + trans.interpolateTransforms(trans, tr, getTransitionWeight()); + target.setLocalTransform(trans); + } + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java new file mode 100644 index 000000000..a88be7529 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java @@ -0,0 +1,10 @@ +package com.jme3.anim.tween.action; + +public interface BlendSpace { + + public void setBlendAction(BlendAction action); + + public float getWeight(); + + public void setValue(float value); +} diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java new file mode 100644 index 000000000..0ba79d841 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java @@ -0,0 +1,97 @@ +package com.jme3.anim.tween.action; + +import com.jme3.anim.tween.AbstractTween; +import com.jme3.anim.tween.Tween; +import com.jme3.anim.util.HasLocalTransform; +import com.jme3.math.FastMath; +import com.jme3.math.Transform; + +import java.util.Collection; + +public abstract class BlendableAction extends Action { + + protected BlendableAction collectTransformDelegate; + private float transitionWeight = 1.0f; + private double transitionLength = 0.4f; + private float weight = 1f; + private TransitionTween transition = new TransitionTween(transitionLength); + + public BlendableAction(Tween... tweens) { + super(tweens); + } + + public void setCollectTransformDelegate(BlendableAction delegate) { + this.collectTransformDelegate = delegate; + } + + @Override + public boolean interpolate(double t) { + // Sanity check the inputs + if (t < 0) { + return true; + } + + if (collectTransformDelegate == null) { + if (transition.getLength() > getLength()) { + transition.setLength(getLength()); + } + if(isForward()) { + transition.interpolate(t); + } else { + float v = Math.max((float)(getLength() - t), 0f); + transition.interpolate(v); + } + } else { + transitionWeight = 1f; + } + + if (weight == 0) { + //weight is 0 let's not interpolate + return t < getLength(); + } + + doInterpolate(t); + + return t < getLength(); + } + + public float getWeight() { + return weight; + } + + public void setWeight(float weight) { + this.weight = weight; + } + + protected abstract void doInterpolate(double t); + + public abstract Collection getTargets(); + + public abstract void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source); + + public double getTransitionLength() { + return transitionLength; + } + + public void setTransitionLength(double transitionLength) { + this.transitionLength = transitionLength; + } + + protected float getTransitionWeight() { + return transitionWeight; + } + + private class TransitionTween extends AbstractTween { + + + public TransitionTween(double length) { + super(length); + } + + @Override + protected void doInterpolate(double t) { + transitionWeight = (float) t; + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java new file mode 100644 index 000000000..9f0d49f20 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java @@ -0,0 +1,94 @@ +package com.jme3.anim.tween.action; + +import com.jme3.anim.*; +import com.jme3.anim.tween.AbstractTween; +import com.jme3.anim.util.HasLocalTransform; +import com.jme3.math.Transform; +import com.jme3.scene.Geometry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class ClipAction extends BlendableAction { + + private AnimClip clip; + private Transform transform = new Transform(); + + public ClipAction(AnimClip clip) { + this.clip = clip; + setLength(clip.getLength()); + } + + @Override + public void doInterpolate(double t) { + AnimTrack[] tracks = clip.getTracks(); + for (AnimTrack track : tracks) { + if (track instanceof TransformTrack) { + TransformTrack tt = (TransformTrack) track; + if(getMask() != null && !getMask().contains(tt.getTarget())){ + continue; + } + interpolateTransformTrack(t, tt); + } else if (track instanceof MorphTrack) { + interpolateMorphTrack(t, (MorphTrack) track); + } + } + } + + private void interpolateTransformTrack(double t, TransformTrack track) { + HasLocalTransform target = track.getTarget(); + transform.set(target.getLocalTransform()); + track.getDataAtTime(t, transform); + + if (collectTransformDelegate != null) { + collectTransformDelegate.collectTransform(target, transform, getWeight(), this); + } else { + this.collectTransform(target, transform, getTransitionWeight(), this); + } + } + private void interpolateMorphTrack(double t, MorphTrack track) { + Geometry target = track.getTarget(); + float[] weights = target.getMorphState(); + track.getDataAtTime(t, weights); + target.setMorphState(weights); + +// if (collectTransformDelegate != null) { +// collectTransformDelegate.collectTransform(target, transform, getWeight(), this); +// } else { +// this.collectTransform(target, transform, getTransitionWeight(), this); +// } + } + + public void reset() { + + } + + public String toString() { + return clip.toString(); + } + + @Override + public Collection getTargets() { + List targets = new ArrayList<>(clip.getTracks().length); + for (AnimTrack track : clip.getTracks()) { + if (track instanceof TransformTrack) { + targets.add(((TransformTrack) track).getTarget()); + } + } + return targets; + } + + @Override + public void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source) { + if (weight == 1f) { + target.setLocalTransform(t); + } else { + Transform tr = target.getLocalTransform(); + tr.interpolateTransforms(tr, t, weight); + target.setLocalTransform(tr); + } + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/LinearBlendSpace.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/LinearBlendSpace.java new file mode 100644 index 000000000..31d2931fe --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/LinearBlendSpace.java @@ -0,0 +1,49 @@ +package com.jme3.anim.tween.action; + +public class LinearBlendSpace implements BlendSpace { + + private BlendAction action; + private float value; + private float maxValue; + private float minValue; + private float step; + + public LinearBlendSpace(float minValue, float maxValue) { + this.maxValue = maxValue; + this.minValue = minValue; + } + + @Override + public void setBlendAction(BlendAction action) { + this.action = action; + Action[] actions = action.getActions(); + step = (maxValue - minValue) / (float) (actions.length - 1); + } + + @Override + public float getWeight() { + Action[] actions = action.getActions(); + float lowStep = minValue, highStep = minValue; + int lowIndex = 0, highIndex = 0; + for (int i = 0; i < actions.length && highStep < value; i++) { + lowStep = highStep; + lowIndex = i; + highStep += step; + } + highIndex = lowIndex + 1; + + action.setFirstActiveIndex(lowIndex); + action.setSecondActiveIndex(highIndex); + + if (highStep == lowStep) { + return 0; + } + + return (value - lowStep) / (highStep - lowStep); + } + + @Override + public void setValue(float value) { + this.value = value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java b/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java new file mode 100644 index 000000000..d50bf90b8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java @@ -0,0 +1,200 @@ +package com.jme3.anim.util; + +import com.jme3.anim.*; +import com.jme3.animation.*; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.*; + +import java.util.*; + +public class AnimMigrationUtils { + + private static AnimControlVisitor animControlVisitor = new AnimControlVisitor(); + private static SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor(); + + + public static Spatial migrate(Spatial source) { + Map skeletonArmatureMap = new HashMap<>(); + animControlVisitor.setMappings(skeletonArmatureMap); + source.depthFirstTraversal(animControlVisitor); + skeletonControlVisitor.setMappings(skeletonArmatureMap); + source.depthFirstTraversal(skeletonControlVisitor); + return source; + } + + private static class AnimControlVisitor implements SceneGraphVisitor { + + Map skeletonArmatureMap; + + @Override + public void visit(Spatial spatial) { + AnimControl control = spatial.getControl(AnimControl.class); + if (control != null) { + AnimComposer composer = new AnimComposer(); + Skeleton skeleton = control.getSkeleton(); + if (skeleton == null) { + //only bone anim for now + return; + } + + Joint[] joints = new Joint[skeleton.getBoneCount()]; + for (int i = 0; i < skeleton.getBoneCount(); i++) { + Bone b = skeleton.getBone(i); + Joint j = joints[i]; + if (j == null) { + j = fromBone(b); + joints[i] = j; + } + for (Bone bone : b.getChildren()) { + int index = skeleton.getBoneIndex(bone); + Joint joint = joints[index]; + if (joint == null) { + joint = fromBone(bone); + } + j.addChild(joint); + joints[index] = joint; + } + } + + Armature armature = new Armature(joints); + armature.saveBindPose(); + skeletonArmatureMap.put(skeleton, armature); + + List tracks = new ArrayList<>(); + + for (String animName : control.getAnimationNames()) { + tracks.clear(); + Animation anim = control.getAnim(animName); + AnimClip clip = new AnimClip(animName); + Joint[] staticJoints = new Joint[joints.length]; + + System.arraycopy(joints, 0, staticJoints, 0, joints.length); + for (Track track : anim.getTracks()) { + if (track instanceof BoneTrack) { + BoneTrack boneTrack = (BoneTrack) track; + int index = boneTrack.getTargetBoneIndex(); + Bone bone = skeleton.getBone(index); + Joint joint = joints[index]; + TransformTrack jointTrack = fromBoneTrack(boneTrack, bone, joint); + tracks.add(jointTrack); + //this joint is animated let's remove it from the static joints + staticJoints[index] = null; + } + //TODO spatial tracks , Effect tracks, Audio tracks + } + + for (int i = 0; i < staticJoints.length; i++) { + padJointTracks(tracks, staticJoints[i]); + } + + clip.setTracks(tracks.toArray(new TransformTrack[tracks.size()])); + + composer.addAnimClip(clip); + } + spatial.removeControl(control); + spatial.addControl(composer); + } + } + + public void setMappings(Map skeletonArmatureMap) { + this.skeletonArmatureMap = skeletonArmatureMap; + } + } + + public static void padJointTracks(List tracks, Joint staticJoint) { + Joint j = staticJoint; + if (j != null) { + // joint has no track , we create one with the default pose + float[] times = new float[]{0}; + Vector3f[] translations = new Vector3f[]{j.getLocalTranslation()}; + Quaternion[] rotations = new Quaternion[]{j.getLocalRotation()}; + Vector3f[] scales = new Vector3f[]{j.getLocalScale()}; + TransformTrack track = new TransformTrack(j, times, translations, rotations, scales); + tracks.add(track); + } + } + + private static class SkeletonControlVisitor implements SceneGraphVisitor { + + Map skeletonArmatureMap; + + @Override + public void visit(Spatial spatial) { + SkeletonControl control = spatial.getControl(SkeletonControl.class); + if (control != null) { + Armature armature = skeletonArmatureMap.get(control.getSkeleton()); + SkinningControl skinningControl = new SkinningControl(armature); + Map> attachedSpatials = new HashMap<>(); + for (int i = 0; i < control.getSkeleton().getBoneCount(); i++) { + Bone b = control.getSkeleton().getBone(i); + Node n = control.getAttachmentsNode(b.getName()); + n.removeFromParent(); + if (!n.getChildren().isEmpty()) { + attachedSpatials.put(b.getName(), n.getChildren()); + } + } + spatial.removeControl(control); + spatial.addControl(skinningControl); + for (String name : attachedSpatials.keySet()) { + List spatials = attachedSpatials.get(name); + for (Spatial child : spatials) { + skinningControl.getAttachmentsNode(name).attachChild(child); + } + } + + } + } + + public void setMappings(Map skeletonArmatureMap) { + this.skeletonArmatureMap = skeletonArmatureMap; + } + } + + public static TransformTrack fromBoneTrack(BoneTrack boneTrack, Bone bone, Joint joint) { + float[] times = new float[boneTrack.getTimes().length]; + int length = times.length; + System.arraycopy(boneTrack.getTimes(), 0, times, 0, length); + //translation + Vector3f[] translations = new Vector3f[length]; + if (boneTrack.getTranslations() != null) { + for (int i = 0; i < boneTrack.getTranslations().length; i++) { + Vector3f oldTrans = boneTrack.getTranslations()[i]; + Vector3f newTrans = new Vector3f(); + newTrans.set(bone.getBindPosition()).addLocal(oldTrans); + translations[i] = newTrans; + } + } + //rotation + Quaternion[] rotations = new Quaternion[length]; + if (boneTrack.getRotations() != null) { + for (int i = 0; i < boneTrack.getRotations().length; i++) { + Quaternion oldRot = boneTrack.getRotations()[i]; + Quaternion newRot = new Quaternion(); + newRot.set(bone.getBindRotation()).multLocal(oldRot); + rotations[i] = newRot; + } + } + //scale + Vector3f[] scales = new Vector3f[length]; + if (boneTrack.getScales() != null) { + for (int i = 0; i < boneTrack.getScales().length; i++) { + Vector3f oldScale = boneTrack.getScales()[i]; + Vector3f newScale = new Vector3f(); + newScale.set(bone.getBindScale()).multLocal(oldScale); + scales[i] = newScale; + } + } + TransformTrack t = new TransformTrack(joint, times, translations, rotations, scales); + return t; + } + + private static Joint fromBone(Bone b) { + Joint j = new Joint(b.getName()); + j.setLocalTranslation(b.getBindPosition()); + j.setLocalRotation(b.getBindRotation()); + j.setLocalScale(b.getBindScale()); + return j; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java b/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java new file mode 100644 index 000000000..28f560dfc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java @@ -0,0 +1,10 @@ +package com.jme3.anim.util; + +import com.jme3.export.Savable; +import com.jme3.math.Transform; + +public interface HasLocalTransform extends Savable { + public void setLocalTransform(Transform transform); + + public Transform getLocalTransform(); +} diff --git a/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java new file mode 100644 index 000000000..c73c03316 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java @@ -0,0 +1,20 @@ +package com.jme3.anim.util; + +import com.jme3.anim.Joint; +import com.jme3.math.Matrix4f; +import com.jme3.math.Transform; + +/** + * Implementations of this interface holds accumulated model transform of a Joint. + * Implementation might choose different accumulation strategy. + */ +public interface JointModelTransform { + + void updateModelTransform(Transform localTransform, Joint parent); + + void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix); + + void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent); + + Transform getModelTransform(); +} diff --git a/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java b/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java new file mode 100644 index 000000000..f61a996f8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java @@ -0,0 +1,56 @@ +package com.jme3.anim.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +/** + * This is a guava method used in {@link com.jme3.anim.tween.Tweens} class. + * Maybe we should just add guava as a dependency in the engine... + * //TODO do something about this. + */ +public class Primitives { + + /** + * A map from primitive types to their corresponding wrapper types. + */ + private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; + + static { + Map, Class> primToWrap = new HashMap<>(16); + + primToWrap.put(boolean.class, Boolean.class); + primToWrap.put(byte.class, Byte.class); + primToWrap.put(char.class, Character.class); + primToWrap.put(double.class, Double.class); + primToWrap.put(float.class, Float.class); + primToWrap.put(int.class, Integer.class); + primToWrap.put(long.class, Long.class); + primToWrap.put(short.class, Short.class); + primToWrap.put(void.class, Void.class); + + PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); + } + + /** + * Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise + * returns {@code type} itself. Idempotent. + *

+ *

+     *     wrap(int.class) == Integer.class
+     *     wrap(Integer.class) == Integer.class
+     *     wrap(String.class) == String.class
+     * 
+ */ + public static Class wrap(Class type) { + if (type == null) { + throw new IllegalArgumentException("type is null"); + } + + // cast is safe: long.class and Long.class are both of type Class + @SuppressWarnings("unchecked") + Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE.get(type); + return (wrapped == null) ? type : wrapped; + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java b/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java new file mode 100644 index 000000000..8fb6d3255 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java @@ -0,0 +1,11 @@ +package com.jme3.anim.util; + +import com.jme3.anim.tween.action.Action; +import com.jme3.math.Transform; + +public interface Weighted { + + // public void setWeight(float weight); + + public void setParentAction(Action action); +} diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java b/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java index 0cd24cab8..04c8c2f49 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java @@ -33,6 +33,7 @@ package com.jme3.animation; import com.jme3.math.FastMath; import com.jme3.util.TempVars; + import java.util.BitSet; /** @@ -46,6 +47,7 @@ import java.util.BitSet; * * @author Kirill Vainer */ +@Deprecated public final class AnimChannel { private static final float DEFAULT_BLEND_TIME = 0.15f; diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimControl.java b/jme3-core/src/main/java/com/jme3/animation/AnimControl.java index 8718cde26..7386e1084 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimControl.java @@ -64,7 +64,9 @@ import java.util.Map; * 1) Morph/Pose animation * * @author Kirill Vainer + * @deprecated use {@link com.jme3.anim.AnimComposer} */ +@Deprecated public final class AnimControl extends AbstractControl implements Cloneable, JmeCloneable { /** diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java b/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java index d9b48459a..c20f150da 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java @@ -37,6 +37,7 @@ package com.jme3.animation; * * @author Kirill Vainer */ +@Deprecated public interface AnimEventListener { /** diff --git a/jme3-core/src/main/java/com/jme3/animation/Animation.java b/jme3-core/src/main/java/com/jme3/animation/Animation.java index 92a5a38fc..834595238 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Animation.java +++ b/jme3-core/src/main/java/com/jme3/animation/Animation.java @@ -37,13 +37,16 @@ import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; + import java.io.IOException; /** * The animation class updates the animation target with the tracks of a given type. * * @author Kirill Vainer, Marcin Roguski (Kaelthas) + * @deprecated use {@link com.jme3.anim.AnimClip} */ +@Deprecated public class Animation implements Savable, Cloneable, JmeCloneable { /** diff --git a/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java b/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java index f4c53e4f1..300287c1e 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java @@ -32,15 +32,12 @@ package com.jme3.animation; import com.jme3.audio.AudioNode; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; +import com.jme3.export.*; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; -import com.jme3.util.clone.JmeCloneable; + import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -62,6 +59,7 @@ import java.util.logging.Logger; * * @author Nehon */ +@Deprecated public class AudioTrack implements ClonableTrack { private static final Logger logger = Logger.getLogger(AudioTrack.class.getName()); diff --git a/jme3-core/src/main/java/com/jme3/animation/Bone.java b/jme3-core/src/main/java/com/jme3/animation/Bone.java index e055f2e46..edc1b8894 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Bone.java +++ b/jme3-core/src/main/java/com/jme3/animation/Bone.java @@ -67,7 +67,9 @@ import java.util.ArrayList; * * @author Kirill Vainer * @author Rémy Bouquet + * @deprecated use {@link com.jme3.anim.Joint} */ +@Deprecated public final class Bone implements Savable, JmeCloneable { // Version #2: Changed naming of transforms as they were misleading @@ -533,11 +535,21 @@ public final class Bone implements Savable, JmeCloneable { attachNode.setLocalRotation(modelRot); attachNode.setLocalScale(modelScale); + } else if (targetGeometry.isIgnoreTransform()) { + /* + * The animated meshes ignore transforms: match the world transform + * of the attachments node to the bone's transform. + */ + attachNode.setLocalTranslation(modelPos); + attachNode.setLocalRotation(modelRot); + attachNode.setLocalScale(modelScale); + attachNode.getLocalTransform().combineWithParent(attachNode.getParent().getWorldTransform().invert()); + } else { Spatial loopSpatial = targetGeometry; Transform combined = new Transform(modelPos, modelRot, modelScale); /* - * Climb the scene graph applying local transforms until the + * Climb the scene graph applying local transforms until the * attachments node's parent is reached. */ while (loopSpatial != attachParent && loopSpatial != null) { diff --git a/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java b/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java index 1eca2e9dc..a39108c5a 100644 --- a/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java @@ -44,7 +44,9 @@ import java.util.BitSet; * Contains a list of transforms and times for each keyframe. * * @author Kirill Vainer + * @deprecated use {@link com.jme3.anim.JointTrack} */ +@Deprecated public final class BoneTrack implements JmeCloneable, Track { /** diff --git a/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java b/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java index 6dd94c806..b777203a5 100644 --- a/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java @@ -44,6 +44,7 @@ import com.jme3.util.clone.JmeCloneable; * * @author Nehon */ +@Deprecated public interface ClonableTrack extends Track, JmeCloneable { /** diff --git a/jme3-core/src/main/java/com/jme3/animation/CompactArray.java b/jme3-core/src/main/java/com/jme3/animation/CompactArray.java index f251b44a2..b64e0785c 100644 --- a/jme3-core/src/main/java/com/jme3/animation/CompactArray.java +++ b/jme3-core/src/main/java/com/jme3/animation/CompactArray.java @@ -44,7 +44,7 @@ import java.util.Map; */ public abstract class CompactArray implements JmeCloneable { - private Map indexPool = new HashMap(); + protected Map indexPool = new HashMap(); protected int[] index; protected float[] array; private boolean invalid; @@ -114,6 +114,10 @@ public abstract class CompactArray implements JmeCloneable { indexPool.clear(); } + protected void setInvalid(boolean invalid) { + this.invalid = invalid; + } + /** * @param index * @param value diff --git a/jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java b/jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java new file mode 100644 index 000000000..a879ef1cb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.export.*; +import com.jme3.math.Vector3f; + +import java.io.IOException; + +/** + * Serialize and compress Float by indexing similar values + * @author Lim, YongHoon + */ +public class CompactFloatArray extends CompactArray implements Savable { + + /** + * Creates a compact vector array + */ + public CompactFloatArray() { + } + + /** + * creates a compact vector array + * @param dataArray the data array + * @param index the indices + */ + public CompactFloatArray(float[] dataArray, int[] index) { + super(dataArray, index); + } + + @Override + protected final int getTupleSize() { + return 1; + } + + @Override + protected final Class getElementClass() { + return Float.class; + } + + @Override + public void write(JmeExporter ex) throws IOException { + serialize(); + OutputCapsule out = ex.getCapsule(this); + out.write(array, "array", null); + out.write(index, "index", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + array = in.readFloatArray("array", null); + index = in.readIntArray("index", null); + } + + public void fill(int startIndex, float[] store ){ + for (int i = 0; i < store.length; i++) { + store[i] = get(startIndex + i, null); + } + } + + @Override + protected void serialize(int i, Float data) { + array[i] = data; + } + + @Override + protected Float deserialize(int i, Float store) { + return array[i]; + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java index efc5b9fbb..a05b481c4 100644 --- a/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java @@ -32,10 +32,7 @@ package com.jme3.animation; import com.jme3.effect.ParticleEmitter; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; +import com.jme3.export.*; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Node; @@ -67,6 +64,7 @@ import java.util.logging.Logger; * * @author Nehon */ +@Deprecated public class EffectTrack implements ClonableTrack { private static final Logger logger = Logger.getLogger(EffectTrack.class.getName()); @@ -130,15 +128,17 @@ public class EffectTrack implements ClonableTrack { @Override protected void controlRender(RenderManager rm, ViewPort vp) { } - }; + } //Anim listener that stops the Emmitter when the animation is finished or changed. private class OnEndListener implements AnimEventListener { + @Override public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { stop(); } + @Override public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { } } @@ -188,6 +188,7 @@ public class EffectTrack implements ClonableTrack { * @see Track#setTime(float, float, com.jme3.animation.AnimControl, * com.jme3.animation.AnimChannel, com.jme3.util.TempVars) */ + @Override public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { if (time >= length) { @@ -233,6 +234,7 @@ public class EffectTrack implements ClonableTrack { * * @return length of the track */ + @Override public float getLength() { return length; } @@ -325,6 +327,7 @@ public class EffectTrack implements ClonableTrack { return null; } + @Override public void cleanUp() { TrackInfo t = (TrackInfo) emitter.getUserData("TrackInfo"); t.getTracks().remove(this); @@ -413,6 +416,7 @@ public class EffectTrack implements ClonableTrack { * @param ex exporter * @throws IOException exception */ + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule out = ex.getCapsule(this); //reset the particle emission rate on the emitter before saving. @@ -431,6 +435,7 @@ public class EffectTrack implements ClonableTrack { * @param im importer * @throws IOException Exception */ + @Override public void read(JmeImporter im) throws IOException { InputCapsule in = im.getCapsule(this); this.particlesPerSeconds = in.readFloat("particlesPerSeconds", 0); diff --git a/jme3-core/src/main/java/com/jme3/animation/LoopMode.java b/jme3-core/src/main/java/com/jme3/animation/LoopMode.java index ca7b1eb1c..16ca1deb0 100644 --- a/jme3-core/src/main/java/com/jme3/animation/LoopMode.java +++ b/jme3-core/src/main/java/com/jme3/animation/LoopMode.java @@ -35,6 +35,7 @@ package com.jme3.animation; * LoopMode determines how animations repeat, or if they * do not repeat. */ +@Deprecated public enum LoopMode { /** * The animation will play repeatedly, when it reaches the end diff --git a/jme3-core/src/main/java/com/jme3/animation/Pose.java b/jme3-core/src/main/java/com/jme3/animation/Pose.java index fc06d0d07..f5d0e305a 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Pose.java +++ b/jme3-core/src/main/java/com/jme3/animation/Pose.java @@ -34,12 +34,14 @@ package com.jme3.animation; import com.jme3.export.*; import com.jme3.math.Vector3f; import com.jme3.util.BufferUtils; + import java.io.IOException; import java.nio.FloatBuffer; /** * A pose is a list of offsets that say where a mesh vertices should be for this pose. */ +@Deprecated public final class Pose implements Savable, Cloneable { private String name; diff --git a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java index bff876790..651bf06a3 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java +++ b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java @@ -31,11 +31,13 @@ */ package com.jme3.animation; +import com.jme3.anim.Armature; import com.jme3.export.*; import com.jme3.math.Matrix4f; import com.jme3.util.TempVars; -import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -46,7 +48,9 @@ import java.util.List; * animated matrixes. * * @author Kirill Vainer + * @deprecated use {@link Armature} */ +@Deprecated public final class Skeleton implements Savable, JmeCloneable { private Bone[] rootBones; diff --git a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index 1e9abea5b..06f6927ab 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -31,6 +31,7 @@ */ package com.jme3.animation; +import com.jme3.anim.SkinningControl; import com.jme3.export.*; import com.jme3.material.MatParamOverride; import com.jme3.math.FastMath; @@ -57,7 +58,9 @@ import java.util.logging.Logger; * the mesh * * @author Rémy Bouquet Based on AnimControl by Kirill Vainer + * @deprecated use {@link SkinningControl} */ +@Deprecated public class SkeletonControl extends AbstractControl implements Cloneable, JmeCloneable { /** diff --git a/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java b/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java index 5afd84350..fc992d2b0 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java @@ -31,10 +31,7 @@ */ package com.jme3.animation; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; +import com.jme3.export.*; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.scene.Spatial; @@ -48,8 +45,9 @@ import java.io.IOException; * * @author Marcin Roguski (Kaelthas) */ +@Deprecated public class SpatialTrack implements JmeCloneable, Track { - + /** * Translations of the track. */ diff --git a/jme3-core/src/main/java/com/jme3/animation/Track.java b/jme3-core/src/main/java/com/jme3/animation/Track.java index 4a6dcdef9..7777e8894 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Track.java +++ b/jme3-core/src/main/java/com/jme3/animation/Track.java @@ -34,6 +34,7 @@ package com.jme3.animation; import com.jme3.export.Savable; import com.jme3.util.TempVars; +@Deprecated public interface Track extends Savable, Cloneable { /** diff --git a/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java b/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java index 7ee927cd3..93fd6a88e 100644 --- a/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java +++ b/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java @@ -31,13 +31,10 @@ */ package com.jme3.animation; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; +import com.jme3.export.*; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; + import java.io.IOException; import java.util.ArrayList; @@ -50,6 +47,7 @@ import java.util.ArrayList; * * @author Nehon */ +@Deprecated public class TrackInfo implements Savable, JmeCloneable { ArrayList tracks = new ArrayList(); diff --git a/jme3-core/src/main/java/com/jme3/bounding/Intersection.java b/jme3-core/src/main/java/com/jme3/bounding/Intersection.java index c627e23e5..37beede01 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/Intersection.java +++ b/jme3-core/src/main/java/com/jme3/bounding/Intersection.java @@ -34,6 +34,7 @@ package com.jme3.bounding; import com.jme3.math.FastMath; import com.jme3.math.Plane; import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; import com.jme3.util.TempVars; import static java.lang.Math.max; import static java.lang.Math.min; @@ -107,6 +108,15 @@ public final class Intersection { } } + public static boolean intersect(Camera camera, Vector3f center,float radius){ + for (int i = 5; i >= 0; i--) { + if (camera.getWorldPlane(i).pseudoDistance(center) <= -radius) { + return false; + } + } + return true; + } + // private boolean axisTest(float a, float b, float fa, float fb, Vector3f v0, Vector3f v1, ) // private boolean axisTestX01(float a, float b, float fa, float fb, // Vector3f center, Vector3f ext, diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java index 6dfc2a260..9616ebca5 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java @@ -31,24 +31,16 @@ */ package com.jme3.cinematic.events; -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.animation.LoopMode; +import com.jme3.animation.*; import com.jme3.app.Application; import com.jme3.cinematic.Cinematic; import com.jme3.cinematic.PlayState; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; +import com.jme3.export.*; import com.jme3.scene.Node; import com.jme3.scene.Spatial; -import com.jme3.util.clone.Cloner; -import com.jme3.util.clone.JmeCloneable; + import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import java.util.logging.Logger; /** @@ -60,6 +52,7 @@ import java.util.logging.Logger; * * @author Nehon */ +@Deprecated public class AnimationEvent extends AbstractCinematicEvent { // Version #2: directly keeping track on the model instead of trying to retrieve diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java index aff4dd0b9..a8fa7de37 100644 --- a/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java +++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java @@ -38,14 +38,9 @@ import com.jme3.environment.util.EnvMapUtils; import com.jme3.light.LightProbe; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.renderer.RenderManager; -import com.jme3.renderer.ViewPort; +import com.jme3.renderer.*; import com.jme3.scene.Spatial; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Image; -import com.jme3.texture.Texture2D; -import com.jme3.texture.TextureCubeMap; +import com.jme3.texture.*; import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; import com.jme3.util.MipMapGenerator; @@ -119,7 +114,7 @@ public class EnvironmentCamera extends BaseAppState { private final List jobs = new ArrayList(); /** - * Creates an EnvironmentCamera with a size of 128 + * Creates an EnvironmentCamera with a size of 256 */ public EnvironmentCamera() { } @@ -322,7 +317,7 @@ public class EnvironmentCamera extends BaseAppState { final Camera offCamera = new Camera(mapSize, mapSize); offCamera.setLocation(worldPos); offCamera.setAxes(axisX, axisY, axisZ); - offCamera.setFrustumPerspective(90f, 1f, 1, 1000); + offCamera.setFrustumPerspective(90f, 1f, 0.1f, 1000); offCamera.setLocation(position); return offCamera; } diff --git a/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java b/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java index 9a4259804..859472fe1 100644 --- a/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java +++ b/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java @@ -32,6 +32,7 @@ package com.jme3.environment; import com.jme3.app.Application; +import com.jme3.asset.AssetManager; import com.jme3.environment.generation.*; import com.jme3.environment.util.EnvMapUtils; import com.jme3.light.LightProbe; @@ -204,6 +205,26 @@ public class LightProbeFactory { } } + /** + * For debuging porpose only + * Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps. + * + * @param manager the asset manager + * @return a debug node + */ + public static Node getDebugGui(AssetManager manager, LightProbe probe) { + if (!probe.isReady()) { + throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()"); + } + + Node debugNode = new Node("debug gui probe"); + Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(probe.getPrefilteredEnvMap(), manager); + debugNode.attachChild(debugPfemCm); + debugPfemCm.setLocalTranslation(520, 0, 0); + + return debugNode; + } + /** * An inner class to keep the state of a generation process */ diff --git a/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java b/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java index 3d41148c4..a9d2a9e46 100644 --- a/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java +++ b/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java @@ -34,9 +34,8 @@ package com.jme3.environment.util; import com.jme3.app.Application; import com.jme3.app.state.BaseAppState; import com.jme3.bounding.BoundingSphere; +import com.jme3.light.*; import com.jme3.material.Material; -import com.jme3.light.LightProbe; -import com.jme3.light.Light; import com.jme3.renderer.RenderManager; import com.jme3.scene.Geometry; import com.jme3.scene.Node; @@ -68,7 +67,7 @@ public class LightsDebugState extends BaseAppState { @Override protected void initialize(Application app) { debugNode = new Node("Environment debug Node"); - Sphere s = new Sphere(16, 16, 1); + Sphere s = new Sphere(16, 16, 0.15f); debugGeom = new Geometry("debugEnvProbe", s); debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md"); debugGeom.setMaterial(debugMaterial); @@ -80,6 +79,16 @@ public class LightsDebugState extends BaseAppState { @Override public void update(float tpf) { + if(!isEnabled()){ + return; + } + updateLights(scene); + debugNode.updateLogicalState(tpf); + debugNode.updateGeometricState(); + cleanProbes(); + } + + public void updateLights(Spatial scene) { for (Light light : scene.getWorldLightList()) { switch (light.getType()) { @@ -101,16 +110,18 @@ public class LightsDebugState extends BaseAppState { m.setTexture("CubeMap", probe.getPrefilteredEnvMap()); } n.setLocalTranslation(probe.getPosition()); - n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius()); + n.getChild(1).setLocalScale(probe.getArea().getRadius()); break; default: break; } } - debugNode.updateLogicalState(tpf); - debugNode.updateGeometricState(); - cleanProbes(); - + if( scene instanceof Node){ + Node n = (Node)scene; + for (Spatial spatial : n.getChildren()) { + updateLights(spatial); + } + } } /** @@ -138,6 +149,9 @@ public class LightsDebugState extends BaseAppState { @Override public void render(RenderManager rm) { + if(!isEnabled()){ + return; + } rm.renderScene(debugNode, getApplication().getViewPort()); } diff --git a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java index 2710e6a29..a9023f551 100644 --- a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java +++ b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java @@ -42,11 +42,11 @@ import java.util.HashSet; public final class DefaultLightFilter implements LightFilter { private Camera camera; - private final HashSet processedLights = new HashSet<>(); - private final LightProbeBlendingStrategy probeBlendStrat; + private final HashSet processedLights = new HashSet(); + private LightProbeBlendingStrategy probeBlendStrat; public DefaultLightFilter() { - probeBlendStrat = new BasicProbeBlendingStrategy(); + probeBlendStrat = new WeightedProbeBlendingStrategy(); } public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrat) { @@ -114,5 +114,9 @@ public final class DefaultLightFilter implements LightFilter { vars.release(); } } - + + public void setLightProbeBlendingStrategy(LightProbeBlendingStrategy strategy){ + probeBlendStrat = strategy; + } + } diff --git a/jme3-core/src/main/java/com/jme3/light/LightProbe.java b/jme3-core/src/main/java/com/jme3/light/LightProbe.java index 2b962fc97..d6d716877 100644 --- a/jme3-core/src/main/java/com/jme3/light/LightProbe.java +++ b/jme3-core/src/main/java/com/jme3/light/LightProbe.java @@ -31,24 +31,16 @@ */ package com.jme3.light; -import com.jme3.asset.AssetManager; -import com.jme3.bounding.BoundingBox; -import com.jme3.bounding.BoundingSphere; -import com.jme3.bounding.BoundingVolume; +import com.jme3.bounding.*; import com.jme3.environment.EnvironmentCamera; import com.jme3.environment.LightProbeFactory; -import com.jme3.environment.util.EnvMapUtils; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; -import com.jme3.math.Vector3f; +import com.jme3.export.*; +import com.jme3.math.*; import com.jme3.renderer.Camera; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.texture.TextureCubeMap; import com.jme3.util.TempVars; + import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -56,7 +48,7 @@ import java.util.logging.Logger; /** * A LightProbe is not exactly a light. It holds environment map information used for Image Based Lighting. * This is used for indirect lighting in the Physically Based Rendering pipeline. - * + * * A light probe has a position in world space. This is the position from where the Environment Map are rendered. * There are two environment data structure held by the LightProbe : * - The irradiance spherical harmonics factors (used for indirect diffuse lighting in the PBR pipeline). @@ -64,10 +56,10 @@ import java.util.logging.Logger; * Note that when instantiating the LightProbe, both of those structures are null. * To compute them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} * and {@link EnvironmentCamera}. - * - * The light probe has an area of effect that is a bounding volume centered on its position. (for now only Bounding spheres are supported). - * - * A LightProbe will only be taken into account when it's marked as ready. + * + * The light probe has an area of effect centered on its position. It can have a Spherical area or an Oriented Box area + * + * A LightProbe will only be taken into account when it's marked as ready and enabled. * A light probe is ready when it has valid environment map data set. * Note that you should never call setReady yourself. * @@ -78,20 +70,25 @@ import java.util.logging.Logger; public class LightProbe extends Light implements Savable { private static final Logger logger = Logger.getLogger(LightProbe.class.getName()); + public static final Matrix4f FALLBACK_MATRIX = new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1); private Vector3f[] shCoeffs; private TextureCubeMap prefilteredEnvMap; - private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO); + private ProbeArea area = new SphereProbeArea(Vector3f.ZERO, 1.0f); private boolean ready = false; private Vector3f position = new Vector3f(); - private Node debugNode; private int nbMipMaps; + public enum AreaType{ + Spherical, + OrientedBox + } + /** - * Empty constructor used for serialization. + * Empty constructor used for serialization. * You should never call it, use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} instead */ - public LightProbe() { + public LightProbe() { } /** @@ -104,13 +101,59 @@ public class LightProbe extends Light implements Savable { } /** - * Sets the prefiltered environment map - * @param prefileteredEnvMap the prefiltered environment map + * Sets the prefiltered environment map + * @param prefileteredEnvMap the prefiltered environment map */ public void setPrefilteredMap(TextureCubeMap prefileteredEnvMap) { this.prefilteredEnvMap = prefileteredEnvMap; } + /** + * Returns the data to send to the shader. + * This is a column major matrix that is not a classic transform matrix, it's laid out in a particular way + // 3x3 rot mat| + // 0 1 2 | 3 + // 0 | ax bx cx | px | ) + // 1 | ay by cy | py | probe position + // 2 | az bz cz | pz | ) + // --|----------| + // 3 | sx sy sz sp | -> 1/probe radius + nbMipMaps + // --scale-- + *

+ * (ax, ay, az) is the pitch rotation axis + * (bx, by, bz) is the yaw rotation axis + * (cx, cy, cz) is the roll rotation axis + * Like in a standard 3x3 rotation matrix. + * It's also the valid rotation matrix of the probe in world space. + * Note that for the Spherical Probe area this part is a 3x3 identity matrix. + *

+ * (px, py, pz) is the position of the center of the probe in world space + * Like in a valid 4x4 transform matrix. + *

+ * (sx, sy, sy) is the extent of the probe ( the scale ) + * In a standard transform matrix the scale is applied to the rotation matrix part. + * In the shader we need the rotation and the scale to be separated, doing this avoid to extract + * the scale from a classic transform matrix in the shader + *

+ * (sp) is a special entry, it contains the packed number of mip maps of the probe and the inverse radius for the probe. + * since the inverse radius in lower than 1, it's packed in the decimal part of the float. + * The number of mip maps is packed in the integer part of the float. + * (ie: for 6 mip maps and a radius of 3, sp= 6.3333333) + *

+ * The radius is obvious for a SphereProbeArea, + * but in the case of a OrientedBoxProbeArea it's the max of the extent vector's components. + */ + public Matrix4f getUniformMatrix(){ + + Matrix4f mat = area.getUniformMatrix(); + + // setting the (sp) entry of the matrix + mat.m33 = nbMipMaps + 1f / area.getRadius(); + + return mat; + } + + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); @@ -118,7 +161,7 @@ public class LightProbe extends Light implements Savable { oc.write(shCoeffs, "shCoeffs", null); oc.write(prefilteredEnvMap, "prefilteredEnvMap", null); oc.write(position, "position", null); - oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO)); + oc.write(area, "area", new SphereProbeArea(Vector3f.ZERO, 1.0f)); oc.write(ready, "ready", false); oc.write(nbMipMaps, "nbMipMaps", 0); } @@ -127,10 +170,16 @@ public class LightProbe extends Light implements Savable { public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); - + prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null); position = (Vector3f) ic.readSavable("position", null); - bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO)); + area = (ProbeArea)ic.readSavable("area", null); + if(area == null) { + // retro compat + BoundingSphere bounds = (BoundingSphere) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO)); + area = new SphereProbeArea(bounds.getCenter(), bounds.getRadius()); + } + area.setCenter(position); nbMipMaps = ic.readInt("nbMipMaps", 0); ready = ic.readBoolean("ready", false); @@ -146,25 +195,49 @@ public class LightProbe extends Light implements Savable { } } + /** * returns the bounding volume of this LightProbe * @return a bounding volume. + * @deprecated use {@link LightProbe#getArea()} */ + @Deprecated public BoundingVolume getBounds() { - return bounds; + return new BoundingSphere(((SphereProbeArea)area).getRadius(), ((SphereProbeArea)area).getCenter()); } - + /** * Sets the bounds of this LightProbe - * Note that for now only BoundingSphere is supported and this method will + * Note that for now only BoundingSphere is supported and this method will * throw an UnsupportedOperationException with any other BoundingVolume type * @param bounds the bounds of the LightProbe + * @deprecated */ + @Deprecated public void setBounds(BoundingVolume bounds) { - if( bounds.getType()!= BoundingVolume.Type.Sphere){ - throw new UnsupportedOperationException("For not only BoundingSphere are suported for LightProbe"); + } + + public ProbeArea getArea() { + return area; + } + + public void setAreaType(AreaType type){ + switch (type){ + case Spherical: + area = new SphereProbeArea(Vector3f.ZERO, 1.0f); + break; + case OrientedBox: + area = new OrientedBoxProbeArea(new Transform()); + area.setCenter(position); + break; + } + } + + public AreaType getAreaType(){ + if(area instanceof SphereProbeArea){ + return AreaType.Spherical; } - this.bounds = bounds; + return AreaType.OrientedBox; } /** @@ -186,27 +259,6 @@ public class LightProbe extends Light implements Savable { this.ready = ready; } - /** - * For debuging porpose only - * Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps. - * - * @param manager the asset manager - * @return a debug node - */ - public Node getDebugGui(AssetManager manager) { - if (!ready) { - throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()"); - } - if (debugNode == null) { - debugNode = new Node("debug gui probe"); - Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager); - debugNode.attachChild(debugPfemCm); - debugPfemCm.setLocalTranslation(520, 0, 0); - } - - return debugNode; - } - public Vector3f[] getShCoeffs() { return shCoeffs; } @@ -229,7 +281,7 @@ public class LightProbe extends Light implements Savable { */ public void setPosition(Vector3f position) { this.position.set(position); - getBounds().setCenter(position); + area.setCenter(position); } public int getNbMipMaps() { @@ -242,12 +294,17 @@ public class LightProbe extends Light implements Savable { @Override public boolean intersectsBox(BoundingBox box, TempVars vars) { - return getBounds().intersectsBoundingBox(box); + return area.intersectsBox(box, vars); } @Override public boolean intersectsFrustum(Camera camera, TempVars vars) { - return camera.contains(bounds) != Camera.FrustumIntersect.Outside; + return area.intersectsFrustum(camera, vars); + } + + @Override + public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) { + return area.intersectsSphere(sphere, vars); } @Override @@ -267,14 +324,8 @@ public class LightProbe extends Light implements Savable { @Override public String toString() { - return "Light Probe : " + name + " at " + position + " / " + bounds; + return "Light Probe : " + name + " at " + position + " / " + area; } - @Override - public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) { - return getBounds().intersectsSphere(sphere); - } - - } diff --git a/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java b/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java deleted file mode 100644 index 572fd5c18..000000000 --- a/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java +++ /dev/null @@ -1,214 +0,0 @@ - /* - * Copyright (c) 2009-2015 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.light; - -import com.jme3.bounding.BoundingSphere; -import com.jme3.post.SceneProcessor; -import com.jme3.profile.AppProfiler; -import com.jme3.renderer.RenderManager; -import com.jme3.renderer.ViewPort; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Spatial; -import com.jme3.texture.FrameBuffer; -import com.jme3.util.TempVars; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * this processor allows to blend several light probes maps together according to a Point of Interest. - * This is all based on this article by Sebastien lagarde - * https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ - * @author Nehon - */ -public class LightProbeBlendingProcessor implements SceneProcessor { - - private ViewPort viewPort; - private LightFilter prevFilter; - private RenderManager renderManager; - private LightProbe probe = new LightProbe(); - private Spatial poi; - private AppProfiler prof; - - public LightProbeBlendingProcessor(Spatial poi) { - this.poi = poi; - } - - @Override - public void initialize(RenderManager rm, ViewPort vp) { - viewPort = vp; - renderManager = rm; - prevFilter = rm.getLightFilter(); - rm.setLightFilter(new PoiLightProbeLightFilter(this)); - } - - @Override - public void reshape(ViewPort vp, int w, int h) { - - } - - @Override - public boolean isInitialized() { - return viewPort != null; - } - - @Override - public void preFrame(float tpf) { - - } - - /** 1. For POI take a spatial in the constructor and make all calculation against its world pos - * - Alternatively compute an arbitrary POI by casting rays from the camera - * (one in the center and one for each corner and take the median point) - * 2. Take the 4 most weighted probes for default. Maybe allow the user to change this - * 3. For the inner influence radius take half of the radius for a start we'll see then how to change this. - * - */ - @Override - public void postQueue(RenderQueue rq) { - List blendFactors = new ArrayList(); - float sumBlendFactors = computeBlendFactors(blendFactors); - - //Sort blend factors according to their weight - Collections.sort(blendFactors); - - //normalize blend factors; - float normalizer = 1f / sumBlendFactors; - for (BlendFactor blendFactor : blendFactors) { - blendFactor.ndf *= normalizer; - // System.err.println(blendFactor); - } - - - //for now just pick the first probe. - if(!blendFactors.isEmpty()){ - probe = blendFactors.get(0).lightProbe; - }else{ - probe = null; - } - } - - private float computeBlendFactors(List blendFactors) { - float sumBlendFactors = 0; - for (Spatial scene : viewPort.getScenes()) { - for (Light light : scene.getWorldLightList()) { - if(light.getType() == Light.Type.Probe){ - LightProbe p = (LightProbe)light; - TempVars vars = TempVars.get(); - boolean intersect = p.intersectsFrustum(viewPort.getCamera(), vars); - vars.release(); - //check if the probe is inside the camera frustum - if(intersect){ - - //is the poi inside the bounds of this probe - if(poi.getWorldBound().intersects(p.getBounds())){ - - //computing the distance as we need it to check if th epoi in in the inner radius and later to compute the weight - float outerRadius = ((BoundingSphere)p.getBounds()).getRadius(); - float innerRadius = outerRadius * 0.5f; - float distance = p.getBounds().getCenter().distance(poi.getWorldTranslation()); - - // if the poi in inside the inner range of this probe, then this probe is the only one that matters. - if( distance < innerRadius ){ - blendFactors.clear(); - blendFactors.add(new BlendFactor(p, 1.0f)); - return 1.0f; - } - //else we need to compute the weight of this probe and collect it for blending - float ndf = (distance - innerRadius) / (outerRadius - innerRadius); - sumBlendFactors += ndf; - blendFactors.add(new BlendFactor(p, ndf)); - } - } - } - } - } - return sumBlendFactors; - } - - @Override - public void postFrame(FrameBuffer out) { - - } - - @Override - public void cleanup() { - viewPort = null; - renderManager.setLightFilter(prevFilter); - } - - public void populateProbe(LightList lightList){ - if(probe != null && probe.isReady()){ - lightList.add(probe); - } - } - - public Spatial getPoi() { - return poi; - } - - public void setPoi(Spatial poi) { - this.poi = poi; - } - - @Override - public void setProfiler(AppProfiler profiler) { - this.prof = profiler; - } - - private class BlendFactor implements Comparable{ - - LightProbe lightProbe; - float ndf; - - public BlendFactor(LightProbe lightProbe, float ndf) { - this.lightProbe = lightProbe; - this.ndf = ndf; - } - - @Override - public String toString() { - return "BlendFactor{" + "lightProbe=" + lightProbe + ", ndf=" + ndf + '}'; - } - - @Override - public int compareTo(BlendFactor o) { - if(o.ndf > ndf){ - return -1; - }else if(o.ndf < ndf){ - return 1; - } - return 0; - } - - } -} diff --git a/jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java b/jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java new file mode 100644 index 000000000..84bef1b3f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java @@ -0,0 +1,254 @@ +package com.jme3.light; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; +import com.jme3.export.*; +import com.jme3.math.*; +import com.jme3.renderer.Camera; +import com.jme3.util.TempVars; + +import java.io.IOException; + +public class OrientedBoxProbeArea implements ProbeArea { + private Transform transform = new Transform(); + + /** + * @see LightProbe#getUniformMatrix() + * for this Area type, the matrix is updated when the probe is transformed, + * and its data is used for bound checks in the light culling process. + */ + private Matrix4f uniformMatrix = new Matrix4f(); + + public OrientedBoxProbeArea() { + } + + public OrientedBoxProbeArea(Transform transform) { + transform.set(transform); + updateMatrix(); + } + + @Override + public boolean intersectsBox(BoundingBox box, TempVars vars) { + + Vector3f axis1 = getScaledAxis(0, vars.vect1); + Vector3f axis2 = getScaledAxis(1, vars.vect2); + Vector3f axis3 = getScaledAxis(2, vars.vect3); + + Vector3f tn = vars.vect4; + Plane p = vars.plane; + Vector3f c = box.getCenter(); + + p.setNormal(0, 0, -1); + p.setConstant(-(c.z + box.getZExtent())); + if (!insidePlane(p, axis1, axis2, axis3, tn)) return false; + + p.setNormal(0, 0, 1); + p.setConstant(c.z - box.getZExtent()); + if (!insidePlane(p, axis1, axis2, axis3, tn)) return false; + + + p.setNormal(0, -1, 0); + p.setConstant(-(c.y + box.getYExtent())); + if (!insidePlane(p, axis1, axis2, axis3, tn)) return false; + + p.setNormal(0, 1, 0); + p.setConstant(c.y - box.getYExtent()); + if (!insidePlane(p, axis1, axis2, axis3, tn)) return false; + + p.setNormal(-1, 0, 0); + p.setConstant(-(c.x + box.getXExtent())); + if (!insidePlane(p, axis1, axis2, axis3, tn)) return false; + + p.setNormal(1, 0, 0); + p.setConstant(c.x - box.getXExtent()); + if (!insidePlane(p, axis1, axis2, axis3, tn)) return false; + + return true; + + } + + @Override + public float getRadius() { + return Math.max(Math.max(transform.getScale().x, transform.getScale().y), transform.getScale().z); + } + + @Override + public void setRadius(float radius) { + transform.setScale(radius, radius, radius); + } + + @Override + public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) { + + Vector3f closestPoint = getClosestPoint(vars, sphere.getCenter()); + // check if the point intersects with the sphere bound + if (sphere.intersects(closestPoint)) { + return true; + } + return false; + } + + @Override + public boolean intersectsFrustum(Camera camera, TempVars vars) { + + // extract the scaled axis + // this allows a small optimization. + Vector3f axis1 = getScaledAxis(0, vars.vect1); + Vector3f axis2 = getScaledAxis(1, vars.vect2); + Vector3f axis3 = getScaledAxis(2, vars.vect3); + + Vector3f tn = vars.vect4; + + for (int i = 5; i >= 0; i--) { + Plane p = camera.getWorldPlane(i); + if (!insidePlane(p, axis1, axis2, axis3, tn)) return false; + } + return true; + } + + private Vector3f getScaledAxis(int index, Vector3f store) { + Matrix4f u = uniformMatrix; + float x = 0, y = 0, z = 0, s = 1; + switch (index) { + case 0: + x = u.m00; + y = u.m10; + z = u.m20; + s = u.m30; + break; + case 1: + x = u.m01; + y = u.m11; + z = u.m21; + s = u.m31; + break; + case 2: + x = u.m02; + y = u.m12; + z = u.m22; + s = u.m32; + } + return store.set(x, y, z).multLocal(s); + } + + private boolean insidePlane(Plane p, Vector3f axis1, Vector3f axis2, Vector3f axis3, Vector3f tn) { + // transform the plane normal in the box local space. + tn.set(axis1.dot(p.getNormal()), axis2.dot(p.getNormal()), axis3.dot(p.getNormal())); + + // distance check + float radius = FastMath.abs(tn.x) + + FastMath.abs(tn.y) + + FastMath.abs(tn.z); + + float distance = p.pseudoDistance(transform.getTranslation()); + + if (distance < -radius) { + return false; + } + return true; + } + + private Vector3f getClosestPoint(TempVars vars, Vector3f point) { + // non normalized direction + Vector3f dir = vars.vect2.set(point).subtractLocal(transform.getTranslation()); + // initialize the closest point with box center + Vector3f closestPoint = vars.vect3.set(transform.getTranslation()); + + //store extent in an array + float[] r = vars.fWdU; + r[0] = transform.getScale().x; + r[1] = transform.getScale().y; + r[2] = transform.getScale().z; + + // computing closest point to sphere center + for (int i = 0; i < 3; i++) { + // extract the axis from the 3x3 matrix + Vector3f axis = getScaledAxis(i, vars.vect1); + // nomalize (here we just divide by the extent + axis.divideLocal(r[i]); + // distance to the closest point on this axis. + float d = FastMath.clamp(dir.dot(axis), -r[i], r[i]); + closestPoint.addLocal(vars.vect4.set(axis).multLocal(d)); + } + return closestPoint; + } + + private void updateMatrix() { + TempVars vars = TempVars.get(); + Matrix3f r = vars.tempMat3; + Matrix4f u = uniformMatrix; + transform.getRotation().toRotationMatrix(r); + + u.m00 = r.get(0,0); + u.m10 = r.get(1,0); + u.m20 = r.get(2,0); + u.m01 = r.get(0,1); + u.m11 = r.get(1,1); + u.m21 = r.get(2,1); + u.m02 = r.get(0,2); + u.m12 = r.get(1,2); + u.m22 = r.get(2,2); + + //scale + u.m30 = transform.getScale().x; + u.m31 = transform.getScale().y; + u.m32 = transform.getScale().z; + + //position + u.m03 = transform.getTranslation().x; + u.m13 = transform.getTranslation().y; + u.m23 = transform.getTranslation().z; + + vars.release(); + } + + public Matrix4f getUniformMatrix() { + return uniformMatrix; + } + + public Vector3f getExtent() { + return transform.getScale(); + } + + public void setExtent(Vector3f extent) { + transform.setScale(extent); + updateMatrix(); + } + + public Vector3f getCenter() { + return transform.getTranslation(); + } + + public void setCenter(Vector3f center) { + transform.setTranslation(center); + updateMatrix(); + } + + public Quaternion getRotation() { + return transform.getRotation(); + } + + public void setRotation(Quaternion rotation) { + transform.setRotation(rotation); + updateMatrix(); + } + + @Override + protected OrientedBoxProbeArea clone() throws CloneNotSupportedException { + return new OrientedBoxProbeArea(transform); + } + + @Override + public void write(JmeExporter e) throws IOException { + OutputCapsule oc = e.getCapsule(this); + oc.write(transform, "transform", new Transform()); + } + + @Override + public void read(JmeImporter i) throws IOException { + InputCapsule ic = i.getCapsule(this); + transform = (Transform) ic.readSavable("transform", new Transform()); + updateMatrix(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java b/jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java deleted file mode 100644 index b991036ae..000000000 --- a/jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2009-2015 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.light; - -import com.jme3.bounding.BoundingBox; -import com.jme3.bounding.BoundingSphere; -import com.jme3.bounding.BoundingVolume; -import com.jme3.renderer.Camera; -import com.jme3.scene.Geometry; -import com.jme3.util.TempVars; -import java.util.HashSet; - -public final class PoiLightProbeLightFilter implements LightFilter { - - private Camera camera; - private final HashSet processedLights = new HashSet(); - private final LightProbeBlendingProcessor processor; - - public PoiLightProbeLightFilter(LightProbeBlendingProcessor processor) { - this.processor = processor; - } - - @Override - public void setCamera(Camera camera) { - this.camera = camera; - for (Light light : processedLights) { - light.frustumCheckNeeded = true; - } - } - - @Override - public void filterLights(Geometry geometry, LightList filteredLightList) { - TempVars vars = TempVars.get(); - try { - LightList worldLights = geometry.getWorldLightList(); - - for (int i = 0; i < worldLights.size(); i++) { - Light light = worldLights.get(i); - - if (light.getType() == Light.Type.Probe) { - continue; - } - - if (light.frustumCheckNeeded) { - processedLights.add(light); - light.frustumCheckNeeded = false; - light.intersectsFrustum = light.intersectsFrustum(camera, vars); - } - - if (!light.intersectsFrustum) { - continue; - } - - BoundingVolume bv = geometry.getWorldBound(); - - if (bv instanceof BoundingBox) { - if (!light.intersectsBox((BoundingBox) bv, vars)) { - continue; - } - } else if (bv instanceof BoundingSphere) { - if (!Float.isInfinite(((BoundingSphere) bv).getRadius())) { - if (!light.intersectsSphere((BoundingSphere) bv, vars)) { - continue; - } - } - } - - filteredLightList.add(light); - } - - processor.populateProbe(filteredLightList); - - } finally { - vars.release(); - } - } - -} diff --git a/jme3-core/src/main/java/com/jme3/light/PointLight.java b/jme3-core/src/main/java/com/jme3/light/PointLight.java index 69c096790..dddf06287 100644 --- a/jme3-core/src/main/java/com/jme3/light/PointLight.java +++ b/jme3-core/src/main/java/com/jme3/light/PointLight.java @@ -212,12 +212,7 @@ public class PointLight extends Light { if (this.radius == 0) { return true; } else { - for (int i = 5; i >= 0; i--) { - if (camera.getWorldPlane(i).pseudoDistance(position) <= -radius) { - return false; - } - } - return true; + return Intersection.intersect(camera, position, radius); } } diff --git a/jme3-core/src/main/java/com/jme3/light/ProbeArea.java b/jme3-core/src/main/java/com/jme3/light/ProbeArea.java new file mode 100644 index 000000000..28ca0c4e7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/ProbeArea.java @@ -0,0 +1,35 @@ +package com.jme3.light; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; +import com.jme3.export.Savable; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.util.TempVars; + +public interface ProbeArea extends Savable, Cloneable{ + + public void setCenter(Vector3f center); + + public float getRadius(); + + public void setRadius(float radius); + + public Matrix4f getUniformMatrix(); + + /** + * @see Light#intersectsBox(BoundingBox, TempVars) + */ + public boolean intersectsBox(BoundingBox box, TempVars vars); + + /** + * @see Light#intersectsSphere(BoundingSphere, TempVars) + */ + public boolean intersectsSphere(BoundingSphere sphere, TempVars vars); + + /** + * @see Light#intersectsFrustum(Camera, TempVars) + */ + public abstract boolean intersectsFrustum(Camera camera, TempVars vars); +} diff --git a/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java b/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java new file mode 100644 index 000000000..9c2d3fbe8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java @@ -0,0 +1,103 @@ +package com.jme3.light; + +import com.jme3.bounding.*; +import com.jme3.export.*; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.util.TempVars; + +import java.io.IOException; +import java.util.logging.Level; + +public class SphereProbeArea implements ProbeArea { + + private Vector3f center = new Vector3f(); + private float radius = 1; + private Matrix4f uniformMatrix = new Matrix4f(); + + public SphereProbeArea() { + } + + public SphereProbeArea(Vector3f center, float radius) { + this.center.set(center); + this.radius = radius; + updateMatrix(); + } + + public Vector3f getCenter() { + return center; + } + + public void setCenter(Vector3f center) { + this.center.set(center); + updateMatrix(); + } + + public float getRadius() { + return radius; + } + + @Override + public void setRadius(float radius) { + this.radius = radius; + updateMatrix(); + } + + @Override + public Matrix4f getUniformMatrix() { + return uniformMatrix; + } + + private void updateMatrix(){ + //position + uniformMatrix.m03 = center.x; + uniformMatrix.m13 = center.y; + uniformMatrix.m23 = center.z; + + } + + @Override + public boolean intersectsBox(BoundingBox box, TempVars vars) { + return Intersection.intersect(box, center, radius); + } + + @Override + public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) { + return Intersection.intersect(sphere, center, radius); + } + + @Override + public boolean intersectsFrustum(Camera camera, TempVars vars) { + return Intersection.intersect(camera, center, radius); + } + + @Override + public String toString() { + return "SphereProbeArea{" + + "center=" + center + + ", radius=" + radius + + '}'; + } + + @Override + protected SphereProbeArea clone() throws CloneNotSupportedException { + return new SphereProbeArea(center, radius); + } + + @Override + public void write(JmeExporter e) throws IOException { + OutputCapsule oc = e.getCapsule(this); + oc.write(center, "center", new Vector3f()); + oc.write(radius, "radius", 1); + } + + @Override + public void read(JmeImporter i) throws IOException { + InputCapsule ic = i.getCapsule(this); + center = (Vector3f) ic.readSavable("center", new Vector3f()); + radius = ic.readFloat("radius", 1); + updateMatrix(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java b/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java new file mode 100644 index 000000000..7e7f2f92f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.light; + +import com.jme3.scene.Geometry; + +import java.util.ArrayList; +import java.util.List; + +/** + * This strategy returns the 3 closest probe from the rendered object. + *

+ * Image based lighting will be blended between those probes in the shader according to their distance and range. + * + * @author Nehon + */ +public class WeightedProbeBlendingStrategy implements LightProbeBlendingStrategy { + + private final static int MAX_PROBES = 3; + List lightProbes = new ArrayList(); + + @Override + public void registerProbe(LightProbe probe) { + lightProbes.add(probe); + } + + @Override + public void populateProbes(Geometry g, LightList lightList) { + if (!lightProbes.isEmpty()) { + //The 3 first probes are the closest to the geometry since the + //light list is sorted according to the distance to the geom. + int addedProbes = 0; + for (LightProbe p : lightProbes) { + if (p.isReady() && p.isEnabled()) { + lightList.add(p); + addedProbes ++; + } + if (addedProbes == MAX_PROBES) { + break; + } + } + + //clearing the list for next pass. + lightProbes.clear(); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/material/MatParamOverride.java b/jme3-core/src/main/java/com/jme3/material/MatParamOverride.java index 8a7355b87..1b4aad480 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParamOverride.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParamOverride.java @@ -140,6 +140,9 @@ public final class MatParamOverride extends MatParam { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(enabled, "enabled", true); + if (value == null) { + oc.write(true, "isNull", false); + } } @Override @@ -147,5 +150,9 @@ public final class MatParamOverride extends MatParam { super.read(im); InputCapsule ic = im.getCapsule(this); enabled = ic.readBoolean("enabled", true); + boolean isNull = ic.readBoolean("isNull", false); + if (isNull) { + setValue(null); + } } } diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 719da4748..4d9a9cf39 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -46,10 +46,7 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Geometry; -import com.jme3.shader.Shader; -import com.jme3.shader.Uniform; -import com.jme3.shader.UniformBindingManager; -import com.jme3.shader.VarType; +import com.jme3.shader.*; import com.jme3.texture.Image; import com.jme3.texture.Texture; import com.jme3.texture.image.ColorSpace; @@ -414,6 +411,17 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { return paramValues.get(name); } + /** + * Returns the current parameter's value. + * + * @param name the parameter name to look up. + * @return current value or null if the parameter wasn't set. + */ + public T getParamValue(final String name) { + final MatParam param = paramValues.get(name); + return param == null ? null : (T) param.getValue(); + } + /** * Returns the texture parameter set on this material with the given name, * returns null if the parameter is not set. @@ -662,6 +670,28 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { setParam(name, VarType.Vector4, value); } + /** + * Pass an uniform buffer object to the material shader. + * + * @param name the name of the buffer object defined in the material definition (j3md). + * @param value the buffer object. + */ + public void setUniformBufferObject(final String name, final BufferObject value) { + value.setBufferType(BufferObject.BufferType.UniformBufferObject); + setParam(name, VarType.BufferObject, value); + } + + /** + * Pass a shader storage buffer object to the material shader. + * + * @param name the name of the buffer object defined in the material definition (j3md). + * @param value the buffer object. + */ + public void setShaderStorageBufferObject(final String name, final BufferObject value) { + value.setBufferType(BufferObject.BufferType.ShaderStorageBufferObject); + setParam(name, VarType.BufferObject, value); + } + /** * Pass a Vector2f to the material shader. * @@ -796,20 +826,29 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { } for (int i = 0; i < paramValues.size(); i++) { + MatParam param = paramValues.getValue(i); VarType type = param.getVarType(); - Uniform uniform = shader.getUniform(param.getPrefixedName()); - if (uniform.isSetByCurrentMaterial()) { - continue; - } + if (isBO(type)) { + + final ShaderBufferBlock bufferBlock = shader.getBufferBlock(param.getPrefixedName()); + bufferBlock.setBufferObject((BufferObject) param.getValue()); - if (type.isTextureType()) { - renderer.setTexture(unit, (Texture) param.getValue()); - uniform.setValue(VarType.Int, unit); - unit++; } else { - uniform.setValue(type, param.getValue()); + + Uniform uniform = shader.getUniform(param.getPrefixedName()); + if (uniform.isSetByCurrentMaterial()) { + continue; + } + + if (type.isTextureType()) { + renderer.setTexture(unit, (Texture) param.getValue()); + uniform.setValue(VarType.Int, unit); + unit++; + } else { + uniform.setValue(type, param.getValue()); + } } } @@ -817,6 +856,16 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { return unit; } + /** + * Returns true if the type is Buffer Object's type. + * + * @param type the material parameter type. + * @return true if the type is Buffer Object's type. + */ + private boolean isBO(final VarType type) { + return type == VarType.BufferObject; + } + private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) { if (renderManager.getForcedRenderState() != null) { if (techniqueDef.getForcedRenderState() != null) { @@ -845,7 +894,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { * * @param renderManager The render manager to preload for */ - public void preload(RenderManager renderManager) { + public void preload(RenderManager renderManager, Geometry geometry) { if (technique == null) { selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager); } @@ -856,9 +905,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { if (techniqueDef.isNoRender()) { return; } + // Get world overrides + SafeArrayList overrides = geometry.getWorldMatParamOverrides(); - Shader shader = technique.makeCurrent(renderManager, null, null, null, rendererCaps); - updateShaderMaterialParameters(renderer, shader, null, null); + Shader shader = technique.makeCurrent(renderManager, geometry, overrides, renderManager.getForcedMatParams(), rendererCaps); + updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams()); renderManager.getRenderer().setShader(shader); } diff --git a/jme3-core/src/main/java/com/jme3/material/RenderState.java b/jme3-core/src/main/java/com/jme3/material/RenderState.java index b58ea32db..fc5819a21 100644 --- a/jme3-core/src/main/java/com/jme3/material/RenderState.java +++ b/jme3-core/src/main/java/com/jme3/material/RenderState.java @@ -331,9 +331,17 @@ public class RenderState implements Cloneable, Savable { */ Exclusion, /** - * Allows for custom blending by using glBlendFuncSeparate. + * Uses the blend equations and blend factors defined by the render state. *

- * + * These attributes can be set by using the following methods: + *

    + *
  • {@link RenderState#setBlendEquation(BlendEquation)}
    + *
  • {@link RenderState#setBlendEquationAlpha(BlendEquationAlpha)}
    + *
  • {@link RenderState#setCustomBlendFactors(BlendFunc, BlendFunc, BlendFunc, BlendFunc)}
    + *
+ *

+ * Result.RGB = BlendEquation( sfactorRGB * Source.RGB , dfactorRGB * Destination.RGB )
+ * Result.A = BlendEquationAlpha( sfactorAlpha * Source.A , dfactorAlpha * Destination.A ) */ Custom } @@ -425,8 +433,6 @@ public class RenderState implements Cloneable, Savable { ADDITIONAL.applyDepthWrite = false; ADDITIONAL.applyDepthTest = false; ADDITIONAL.applyColorWrite = false; - ADDITIONAL.applyBlendEquation = false; - ADDITIONAL.applyBlendEquationAlpha = false; ADDITIONAL.applyBlendMode = false; ADDITIONAL.applyPolyOffset = false; ADDITIONAL.applyStencilTest = false; @@ -444,9 +450,7 @@ public class RenderState implements Cloneable, Savable { boolean colorWrite = true; boolean applyColorWrite = true; BlendEquation blendEquation = BlendEquation.Add; - boolean applyBlendEquation = true; BlendEquationAlpha blendEquationAlpha = BlendEquationAlpha.InheritColor; - boolean applyBlendEquationAlpha = true; BlendMode blendMode = BlendMode.Off; boolean applyBlendMode = true; float offsetFactor = 0; @@ -469,10 +473,10 @@ public class RenderState implements Cloneable, Savable { TestFunction frontStencilFunction = TestFunction.Always; TestFunction backStencilFunction = TestFunction.Always; int cachedHashCode = -1; - BlendFunc sfactorRGB=BlendFunc.One; - BlendFunc dfactorRGB=BlendFunc.Zero; - BlendFunc sfactorAlpha=BlendFunc.One; - BlendFunc dfactorAlpha=BlendFunc.Zero; + BlendFunc sfactorRGB = BlendFunc.One; + BlendFunc dfactorRGB = BlendFunc.One; + BlendFunc sfactorAlpha = BlendFunc.One; + BlendFunc dfactorAlpha = BlendFunc.One; public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); @@ -510,8 +514,6 @@ public class RenderState implements Cloneable, Savable { oc.write(applyDepthWrite, "applyDepthWrite", true); oc.write(applyDepthTest, "applyDepthTest", true); oc.write(applyColorWrite, "applyColorWrite", true); - oc.write(applyBlendEquation, "applyBlendEquation", true); - oc.write(applyBlendEquationAlpha, "applyBlendEquationAlpha", true); oc.write(applyBlendMode, "applyBlendMode", true); oc.write(applyPolyOffset, "applyPolyOffset", true); oc.write(applyDepthFunc, "applyDepthFunc", true); @@ -544,9 +546,9 @@ public class RenderState implements Cloneable, Savable { depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual); lineWidth = ic.readFloat("lineWidth", 1); sfactorRGB = ic.readEnum("sfactorRGB", BlendFunc.class, BlendFunc.One); - dfactorAlpha = ic.readEnum("dfactorRGB", BlendFunc.class, BlendFunc.Zero); + dfactorAlpha = ic.readEnum("dfactorRGB", BlendFunc.class, BlendFunc.One); sfactorRGB = ic.readEnum("sfactorAlpha", BlendFunc.class, BlendFunc.One); - dfactorAlpha = ic.readEnum("dfactorAlpha", BlendFunc.class, BlendFunc.Zero); + dfactorAlpha = ic.readEnum("dfactorAlpha", BlendFunc.class, BlendFunc.One); applyWireFrame = ic.readBoolean("applyWireFrame", true); @@ -554,14 +556,11 @@ public class RenderState implements Cloneable, Savable { applyDepthWrite = ic.readBoolean("applyDepthWrite", true); applyDepthTest = ic.readBoolean("applyDepthTest", true); applyColorWrite = ic.readBoolean("applyColorWrite", true); - applyBlendEquation = ic.readBoolean("applyBlendEquation", true); - applyBlendEquationAlpha = ic.readBoolean("applyBlendEquationAlpha", true); applyBlendMode = ic.readBoolean("applyBlendMode", true); applyPolyOffset = ic.readBoolean("applyPolyOffset", true); applyDepthFunc = ic.readBoolean("applyDepthFunc", true); applyLineWidth = ic.readBoolean("applyLineWidth", true); - } /** @@ -618,19 +617,32 @@ public class RenderState implements Cloneable, Savable { return false; } - if (blendEquation != rs.blendEquation) { + if (blendMode != rs.blendMode) { return false; } - if (blendEquationAlpha != rs.blendEquationAlpha) { - return false; - } + if (blendMode == BlendMode.Custom) { + if (blendEquation != rs.blendEquation) { + return false; + } + if (blendEquationAlpha != rs.blendEquationAlpha) { + return false; + } - if (blendMode != rs.blendMode) { - return false; + if (sfactorRGB != rs.sfactorRGB) { + return false; + } + if (dfactorRGB != rs.dfactorRGB) { + return false; + } + if (sfactorAlpha != rs.sfactorAlpha) { + return false; + } + if (dfactorAlpha != rs.dfactorAlpha) { + return false; + } } - if (offsetEnabled != rs.offsetEnabled) { return false; } @@ -678,14 +690,6 @@ public class RenderState implements Cloneable, Savable { if(lineWidth != rs.lineWidth){ return false; } - - if (blendMode.equals(BlendMode.Custom)) { - return sfactorRGB==rs.getCustomSfactorRGB() - && dfactorRGB==rs.getCustomDfactorRGB() - && sfactorAlpha==rs.getCustomSfactorAlpha() - && dfactorAlpha==rs.getCustomDfactorAlpha(); - - } return true; } @@ -771,80 +775,68 @@ public class RenderState implements Cloneable, Savable { } /** - * Set the blending equation. + * Set the blending equation for the color component (RGB). *

- * When blending is enabled, (blendMode is not - * {@link BlendMode#Off}) the input pixel will be blended with the pixel - * already in the color buffer. The blending equation is determined by the - * {@link BlendEquation}. For example, the mode {@link BlendMode#Additive} - * and {@link BlendEquation#Add} will add the input pixel's color to the - * color already in the color buffer: + * The blending equation determines, how the RGB values of the input pixel + * will be blended with the RGB values of the pixel already in the color buffer.
+ * For example, {@link BlendEquation#Add} will add the input pixel's color + * to the color already in the color buffer: *
* Result = Source Color + Destination Color - *
- * However, the mode {@link BlendMode#Additive} - * and {@link BlendEquation#Subtract} will subtract the input pixel's color to the - * color already in the color buffer: - *
- * Result = Source Color - Destination Color + *

+ * Note: This gets only used in {@link BlendMode#Custom} mode. + * All other blend modes will ignore this setting. * - * @param blendEquation The blend equation to use. + * @param blendEquation The {@link BlendEquation} to use. */ public void setBlendEquation(BlendEquation blendEquation) { - applyBlendEquation = true; this.blendEquation = blendEquation; cachedHashCode = -1; } - + /** * Set the blending equation for the alpha component. *

- * When blending is enabled, (blendMode is not - * {@link BlendMode#Off}) the input pixel will be blended with the pixel - * already in the color buffer. The blending equation is determined by the - * {@link BlendEquation} and can be overrode for the alpha component using - * the {@link BlendEquationAlpha} . For example, the mode - * {@link BlendMode#Additive} and {@link BlendEquationAlpha#Add} will add - * the input pixel's alpha to the alpha component already in the color - * buffer: + * The alpha blending equation determines, how the alpha values of the input pixel + * will be blended with the alpha values of the pixel already in the color buffer.
+ * For example, {@link BlendEquationAlpha#Add} will add the input pixel's color + * to the color already in the color buffer: *
- * Result = Source Alpha + Destination Alpha - *
- * However, the mode {@link BlendMode#Additive} and - * {@link BlendEquationAlpha#Subtract} will subtract the input pixel's alpha - * to the alpha component already in the color buffer: - *
- * Result = Source Alpha - Destination Alpha + * Result = Source Color + Destination Color + *

+ * Note: This gets only used in {@link BlendMode#Custom} mode. + * All other blend modes will ignore this setting. * - * @param blendEquationAlpha The blend equation to use for the alpha - * component. + * @param blendEquationAlpha The {@link BlendEquationAlpha} to use. */ public void setBlendEquationAlpha(BlendEquationAlpha blendEquationAlpha) { - applyBlendEquationAlpha = true; this.blendEquationAlpha = blendEquationAlpha; cachedHashCode = -1; } - /** - * Sets the custom blend factors for BlendMode.Custom as - * defined by the appropriate BlendFunc. - * + * Sets the blend factors used for the source and destination color. + *

+ * These factors will be multiplied with the color values of the input pixel + * and the pixel already in the color buffer, before both colors gets combined by the {@link BlendEquation}. + *

+ * Note: This gets only used in {@link BlendMode#Custom} mode. + * All other blend modes will ignore this setting. + * * @param sfactorRGB The source blend factor for RGB components. * @param dfactorRGB The destination blend factor for RGB components. * @param sfactorAlpha The source blend factor for the alpha component. * @param dfactorAlpha The destination blend factor for the alpha component. */ - public void setCustomBlendFactors(BlendFunc sfactorRGB, BlendFunc dfactorRGB, BlendFunc sfactorAlpha, BlendFunc dfactorAlpha) - { + public void setCustomBlendFactors(BlendFunc sfactorRGB, BlendFunc dfactorRGB, BlendFunc sfactorAlpha, BlendFunc dfactorAlpha) { this.sfactorRGB = sfactorRGB; this.dfactorRGB = dfactorRGB; this.sfactorAlpha = sfactorAlpha; this.dfactorAlpha = dfactorAlpha; cachedHashCode = -1; } - - + + /** * Enable depth testing. * @@ -1377,14 +1369,6 @@ public class RenderState implements Cloneable, Savable { return applyBlendMode; } - public boolean isApplyBlendEquation() { - return applyBlendEquation; - } - - public boolean isApplyBlendEquationAlpha() { - return applyBlendEquationAlpha; - } - public boolean isApplyColorWrite() { return applyColorWrite; } @@ -1514,27 +1498,26 @@ public class RenderState implements Cloneable, Savable { } else { state.colorWrite = colorWrite; } - if (additionalState.applyBlendEquation) { - state.blendEquation = additionalState.blendEquation; - } else { - state.blendEquation = blendEquation; - } - if (additionalState.applyBlendEquationAlpha) { - state.blendEquationAlpha = additionalState.blendEquationAlpha; - } else { - state.blendEquationAlpha = blendEquationAlpha; - } if (additionalState.applyBlendMode) { state.blendMode = additionalState.blendMode; - if (additionalState.getBlendMode().equals(BlendMode.Custom)) { - state.setCustomBlendFactors( - additionalState.getCustomSfactorRGB(), - additionalState.getCustomDfactorRGB(), - additionalState.getCustomSfactorAlpha(), - additionalState.getCustomDfactorAlpha()); + if (additionalState.blendMode == BlendMode.Custom) { + state.blendEquation = additionalState.blendEquation; + state.blendEquationAlpha = additionalState.blendEquationAlpha; + state.sfactorRGB = additionalState.sfactorRGB; + state.dfactorRGB = additionalState.dfactorRGB; + state.sfactorAlpha = additionalState.sfactorAlpha; + state.dfactorAlpha = additionalState.dfactorAlpha; } } else { state.blendMode = blendMode; + if (blendMode == BlendMode.Custom) { + state.blendEquation = blendEquation; + state.blendEquationAlpha = blendEquationAlpha; + state.sfactorRGB = sfactorRGB; + state.dfactorRGB = dfactorRGB; + state.sfactorAlpha = sfactorAlpha; + state.dfactorAlpha = dfactorAlpha; + } } if (additionalState.applyPolyOffset) { @@ -1611,8 +1594,6 @@ public class RenderState implements Cloneable, Savable { applyDepthWrite = true; applyDepthTest = true; applyColorWrite = true; - applyBlendEquation = true; - applyBlendEquationAlpha = true; applyBlendMode = true; applyPolyOffset = true; applyDepthFunc = true; @@ -1639,8 +1620,6 @@ public class RenderState implements Cloneable, Savable { + "\ncolorWrite=" + colorWrite + "\napplyColorWrite=" + applyColorWrite + "\nblendEquation=" + blendEquation - + "\napplyBlendEquation=" + applyBlendEquation - + "\napplyBlendEquationAlpha=" + applyBlendEquationAlpha + "\nblendMode=" + blendMode + "\napplyBlendMode=" + applyBlendMode + "\noffsetEnabled=" + offsetEnabled diff --git a/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java index 25a22c315..6d4e7a812 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java @@ -82,7 +82,7 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic { for (int i = 0; i < lights.size(); i++) { Light l = lights.get(i); - if (l instanceof AmbientLight) { + if (l.getType() == Light.Type.Ambient || l.getType() == Light.Type.Probe) { continue; } @@ -152,8 +152,6 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic { lightDir.setValue(VarType.Vector4, tmpLightDirection); - break; - case Probe: break; default: throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java index 23970f95f..5ce4dca15 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java @@ -48,7 +48,7 @@ import com.jme3.shadow.next.array.DirectionalArrayShadowMap; import com.jme3.texture.TextureArray; import java.util.Comparator; -import java.util.EnumSet; +import java.util.*; public final class SinglePassAndImageBasedLightingLogic extends DefaultTechniqueDefLogic { @@ -60,10 +60,11 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique private static final RenderState ADDITIVE_LIGHT = new RenderState(); private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); - private LightProbe lightProbe; private TextureArray shadowMapArray; private Vector3f pssmSplitsPositions; private int numPssmSplits; + private static final String DEFINE_NB_PROBES = "NB_PROBES"; + private List lightProbes = new ArrayList<>(3); static { ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive); @@ -73,16 +74,16 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique private final int singlePassLightingDefineId; private final int inPassShadowsDefineId; private final int nbLightsDefineId; - private final int indirectLightingDefineId; private final int numPssmSplitsDefineId; + private final int nbProbesDefineId; public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) { super(techniqueDef); numPssmSplitsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_PSSM_SPLITS, VarType.Int); singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean); nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int); - indirectLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_INDIRECT_LIGHTING, VarType.Boolean); inPassShadowsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_IN_PASS_SHADOWS, VarType.Boolean); + nbProbesDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_PROBES, VarType.Int); } @Override @@ -91,7 +92,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique defines.set(singlePassLightingDefineId, true); - // TODO: here we have a problem, this is called once before render, + // TODO: here we have a problem, this is called once before render, // so the define will be set for all passes (in case we have more than NB_LIGHTS lights) // Though the second pass should not render IBL as it is taken care of on // first pass like ambient light in phong lighting. @@ -100,7 +101,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique getFilteredLightList(renderManager, geometry); ambientLightColor.set(0, 0, 0, 1); - lightProbe = null; + lightProbes.clear(); pssmSplitsPositions = null; numPssmSplits = 0; @@ -110,7 +111,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique ambientLightColor.addLocal(light.getColor()); filteredLightList.remove(i--); } else if (light instanceof LightProbe) { - lightProbe = (LightProbe) light; + lightProbes.add((LightProbe) light); filteredLightList.remove(i--); } else if (light.getShadowMap() != null) { ArrayShadowMap shadowMap = (ArrayShadowMap) light.getShadowMap(); @@ -121,6 +122,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique } } } + defines.set(nbProbesDefineId, lightProbes.size()); ambientLightColor.a = 1.0f; filteredLightList.sort(new Comparator() { @@ -139,7 +141,6 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique }); defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3); - defines.set(indirectLightingDefineId, lightProbe != null); defines.set(inPassShadowsDefineId, shadowMapArray != null); defines.set(numPssmSplitsDefineId, numPssmSplits); @@ -167,12 +168,18 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique Uniform lightData = shader.getUniform("g_LightData"); lightData.setVector4Length(numLights * 3);//8 lights * max 3 Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); + + // Matrix4f Uniform lightProbeData = shader.getUniform("g_LightProbeData"); - lightProbeData.setVector4Length(1); + Uniform lightProbeData2 = shader.getUniform("g_LightProbeData2"); + Uniform lightProbeData3 = shader.getUniform("g_LightProbeData3"); - //TODO These 2 uniforms should be packed in an array, to be able to have several probes and blend between them. Uniform shCoeffs = shader.getUniform("g_ShCoeffs"); Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap"); + Uniform shCoeffs2 = shader.getUniform("g_ShCoeffs2"); + Uniform lightProbePemMap2 = shader.getUniform("g_PrefEnvMap2"); + Uniform shCoeffs3 = shader.getUniform("g_ShCoeffs3"); + Uniform lightProbePemMap3 = shader.getUniform("g_PrefEnvMap3"); if (startIndex != 0) { // apply additive blending for 2nd and future passes @@ -183,17 +190,20 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique } //If there is a lightProbe in the list we force its render on the first pass - if(lightProbe != null){ - BoundingSphere s = (BoundingSphere)lightProbe.getBounds(); - lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / s.getRadius() + lightProbe.getNbMipMaps(), 0); - shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs()); - //assigning new texture indexes - int pemUnit = lastTexUnit++; - rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap()); - lightProbePemMap.setValue(VarType.Int, pemUnit); + if (!lightProbes.isEmpty()) { + LightProbe lightProbe = lightProbes.get(0); + lastTexUnit = setProbeData(rm, lastTexUnit, lightProbeData, shCoeffs, lightProbePemMap, lightProbe); + if (lightProbes.size() > 1) { + lightProbe = lightProbes.get(1); + lastTexUnit = setProbeData(rm, lastTexUnit, lightProbeData2, shCoeffs2, lightProbePemMap2, lightProbe); + } + if (lightProbes.size() > 2) { + lightProbe = lightProbes.get(2); + setProbeData(rm, lastTexUnit, lightProbeData3, shCoeffs3, lightProbePemMap3, lightProbe); + } } else { //Disable IBL for this pass - lightProbeData.setVector4InArray(0,0,0,-1, 0); + lightProbeData.setValue(VarType.Matrix4, LightProbe.FALLBACK_MATRIX); } Uniform shadowMatricesUniform = shader.getUniform("g_ShadowMatrices"); @@ -290,6 +300,17 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique return curIndex; } + private int setProbeData(RenderManager rm, int lastTexUnit, Uniform lightProbeData, Uniform shCoeffs, Uniform lightProbePemMap, LightProbe lightProbe) { + + lightProbeData.setValue(VarType.Matrix4, lightProbe.getUniformMatrix()); + shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs()); + //assigning new texture indexes + int pemUnit = lastTexUnit++; + rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap()); + lightProbePemMap.setValue(VarType.Int, pemUnit); + return lastTexUnit; + } + @Override public void render(RenderManager renderManager, Shader shader, Geometry geometry, int lastTexUnit) { int nbRenderedLights = 0; diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java index c901c8f81..495013b5a 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java @@ -112,7 +112,6 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic { lightData.setVector4Length(numLights * 3);//8 lights * max 3 Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); - if (startIndex != 0) { // apply additive blending for 2nd and future passes rm.getRenderer().applyRenderState(ADDITIVE_LIGHT); @@ -129,7 +128,7 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic { for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) { Light l = lightList.get(curIndex); - if (l.getType() == Light.Type.Ambient) { + if (l.getType() == Light.Type.Ambient || l.getType() == Light.Type.Probe) { endIndex++; continue; } @@ -191,8 +190,6 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic { lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex); lightDataIndex++; break; - case Probe: - break; default: throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); } diff --git a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java index 9180c69a6..f403bbaa6 100644 --- a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java +++ b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java @@ -558,6 +558,19 @@ public final class ColorRGBA implements Savable, Cloneable, java.io.Serializable a = ((byte) (color) & 0xFF) / 255f; return this; } + /** + * Sets the RGBA values of this ColorRGBA with the given combined ABGR value + * Bits 24-31 are alpha, bits 16-23 are blue, bits 8-15 are green, bits 0-7 are red. + * @param color The integer ABGR value used to set this object. + * @return this + */ + public ColorRGBA fromIntABGR(int color) { + a = ((byte) (color >> 24) & 0xFF) / 255f; + b = ((byte) (color >> 16) & 0xFF) / 255f; + g = ((byte) (color >> 8) & 0xFF) / 255f; + r = ((byte) (color) & 0xFF) / 255f; + return this; + } /** * Transform this ColorRGBA to a Vector3f using diff --git a/jme3-core/src/main/java/com/jme3/math/EaseFunction.java b/jme3-core/src/main/java/com/jme3/math/EaseFunction.java new file mode 100644 index 000000000..c2f5383f2 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/EaseFunction.java @@ -0,0 +1,13 @@ +package com.jme3.math; + +/** + * Created by Nehon on 26/03/2017. + */ +public interface EaseFunction { + + /** + * @param value a value from 0 to 1. Passing a value out of this range will have unexpected behavior. + * @return + */ + float apply(float value); +} diff --git a/jme3-core/src/main/java/com/jme3/math/Easing.java b/jme3-core/src/main/java/com/jme3/math/Easing.java new file mode 100644 index 000000000..e9969c91a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/Easing.java @@ -0,0 +1,163 @@ +package com.jme3.math; + +/** + * Expose several Easing function from Robert Penner + * Created by Nehon on 26/03/2017. + */ +public class Easing { + + + public static EaseFunction constant = new EaseFunction() { + @Override + public float apply(float value) { + return 0; + } + }; + /** + * In + */ + public static EaseFunction linear = new EaseFunction() { + @Override + public float apply(float value) { + return value; + } + }; + + public static EaseFunction inQuad = new EaseFunction() { + @Override + public float apply(float value) { + return value * value; + } + }; + + public static EaseFunction inCubic = new EaseFunction() { + @Override + public float apply(float value) { + return value * value * value; + } + }; + + public static EaseFunction inQuart = new EaseFunction() { + @Override + public float apply(float value) { + return value * value * value * value; + } + }; + + public static EaseFunction inQuint = new EaseFunction() { + @Override + public float apply(float value) { + return value * value * value * value * value; + } + }; + + + /** + * Out Elastic and bounce + */ + public static EaseFunction outElastic = new EaseFunction() { + @Override + public float apply(float value) { + return FastMath.pow(2f, -10f * value) * FastMath.sin((value - 0.3f / 4f) * (2f * FastMath.PI) / 0.3f) + 1f; + } + }; + + public static EaseFunction outBounce = new EaseFunction() { + @Override + public float apply(float value) { + if (value < (1f / 2.75f)) { + return (7.5625f * value * value); + } else if (value < (2f / 2.75f)) { + return (7.5625f * (value -= (1.5f / 2.75f)) * value + 0.75f); + } else if (value < (2.5 / 2.75)) { + return (7.5625f * (value -= (2.25f / 2.75f)) * value + 0.9375f); + } else { + return (7.5625f * (value -= (2.625f / 2.75f)) * value + 0.984375f); + } + } + }; + + /** + * In Elastic and bounce + */ + public static EaseFunction inElastic = new Invert(outElastic); + public static EaseFunction inBounce = new Invert(outBounce); + + /** + * Out + */ + public static EaseFunction outQuad = new Invert(inQuad); + public static EaseFunction outCubic = new Invert(inCubic); + public static EaseFunction outQuart = new Invert(inQuart); + public static EaseFunction outQuint = new Invert(inQuint); + + /** + * inOut + */ + public static EaseFunction inOutQuad = new InOut(inQuad, outQuad); + public static EaseFunction inOutCubic = new InOut(inCubic, outCubic); + public static EaseFunction inOutQuart = new InOut(inQuart, outQuart); + public static EaseFunction inOutQuint = new InOut(inQuint, outQuint); + public static EaseFunction inOutElastic = new InOut(inElastic, outElastic); + public static EaseFunction inOutBounce = new InOut(inBounce, outBounce); + + + /** + * Extra functions + */ + + public static EaseFunction smoothStep = new EaseFunction() { + @Override + public float apply(float t) { + return t * t * (3f - 2f * t); + } + }; + + public static EaseFunction smootherStep = new EaseFunction() { + @Override + public float apply(float t) { + return t * t * t * (t * (t * 6f - 15f) + 10f); + } + }; + + /** + * An Ease function composed of 2 sb function for custom in and out easing + */ + public static class InOut implements EaseFunction { + + private EaseFunction in; + private EaseFunction out; + + public InOut(EaseFunction in, EaseFunction out) { + this.in = in; + this.out = out; + } + + @Override + public float apply(float value) { + if (value < 0.5) { + value = value * 2; + return inQuad.apply(value) / 2; + } else { + value = (value - 0.5f) * 2; + return outQuad.apply(value) / 2 + 0.5f; + } + } + } + + private static class Invert implements EaseFunction { + + private EaseFunction func; + + public Invert(EaseFunction func) { + this.func = func; + } + + @Override + public float apply(float value) { + return 1f - func.apply(1f - value); + } + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/math/MathUtils.java b/jme3-core/src/main/java/com/jme3/math/MathUtils.java new file mode 100644 index 000000000..8c22700b1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/math/MathUtils.java @@ -0,0 +1,247 @@ +package com.jme3.math; + +import com.jme3.renderer.Camera; +import com.jme3.util.TempVars; + +/** + * Created by Nehon on 23/04/2017. + */ +public class MathUtils { + + public static Quaternion log(Quaternion q, Quaternion store) { + float a = FastMath.acos(q.w); + float sina = FastMath.sin(a); + + store.w = 0; + if (sina > 0) { + store.x = a * q.x / sina; + store.y = a * q.y / sina; + store.z = a * q.z / sina; + } else { + store.x = 0; + store.y = 0; + store.z = 0; + } + return store; + } + + public static Quaternion exp(Quaternion q, Quaternion store) { + + float len = FastMath.sqrt(q.x * q.x + q.y * q.y + q.z * q.z); + float sinLen = FastMath.sin(len); + float cosLen = FastMath.cos(len); + + store.w = cosLen; + if (len > 0) { + store.x = sinLen * q.x / len; + store.y = sinLen * q.y / len; + store.z = sinLen * q.z / len; + } else { + store.x = 0; + store.y = 0; + store.z = 0; + } + return store; + } + + //! This version of slerp, used by squad, does not check for theta > 90. + public static Quaternion slerpNoInvert(Quaternion q1, Quaternion q2, float t, Quaternion store) { + float dot = q1.dot(q2); + + if (dot > -0.95f && dot < 0.95f) { + float angle = FastMath.acos(dot); + float sin1 = FastMath.sin(angle * (1 - t)); + float sin2 = FastMath.sin(angle * t); + float sin3 = FastMath.sin(angle); + store.x = (q1.x * sin1 + q2.x * sin2) / sin3; + store.y = (q1.y * sin1 + q2.y * sin2) / sin3; + store.z = (q1.z * sin1 + q2.z * sin2) / sin3; + store.w = (q1.w * sin1 + q2.w * sin2) / sin3; + System.err.println("real slerp"); + } else { + // if the angle is small, use linear interpolation + store.set(q1).nlerp(q2, t); + System.err.println("nlerp"); + } + return store; + } + + public static Quaternion slerp(Quaternion q1, Quaternion q2, float t, Quaternion store) { + + float dot = (q1.x * q2.x) + (q1.y * q2.y) + (q1.z * q2.z) + + (q1.w * q2.w); + + if (dot < 0.0f) { + // Negate the second quaternion and the result of the dot product + q2.x = -q2.x; + q2.y = -q2.y; + q2.z = -q2.z; + q2.w = -q2.w; + dot = -dot; + } + + // Set the first and second scale for the interpolation + float scale0 = 1 - t; + float scale1 = t; + + // Check if the angle between the 2 quaternions was big enough to + // warrant such calculations + if (dot < 0.9f) {// Get the angle between the 2 quaternions, + // and then store the sin() of that angle + float theta = FastMath.acos(dot); + float invSinTheta = 1f / FastMath.sin(theta); + + // Calculate the scale for q1 and q2, according to the angle and + // it's sine value + scale0 = FastMath.sin((1 - t) * theta) * invSinTheta; + scale1 = FastMath.sin((t * theta)) * invSinTheta; + + // Calculate the x, y, z and w values for the quaternion by using a + // special + // form of linear interpolation for quaternions. + store.x = (scale0 * q1.x) + (scale1 * q2.x); + store.y = (scale0 * q1.y) + (scale1 * q2.y); + store.z = (scale0 * q1.z) + (scale1 * q2.z); + store.w = (scale0 * q1.w) + (scale1 * q2.w); + } else { + store.x = (scale0 * q1.x) + (scale1 * q2.x); + store.y = (scale0 * q1.y) + (scale1 * q2.y); + store.z = (scale0 * q1.z) + (scale1 * q2.z); + store.w = (scale0 * q1.w) + (scale1 * q2.w); + store.normalizeLocal(); + } + // Return the interpolated quaternion + return store; + } + +// //! Given 3 quaternions, qn-1,qn and qn+1, calculate a control point to be used in spline interpolation +// private static Quaternion spline(Quaternion qnm1, Quaternion qn, Quaternion qnp1, Quaternion store, Quaternion tmp) { +// store.set(-qn.x, -qn.y, -qn.z, qn.w); +// //store.set(qn).inverseLocal(); +// tmp.set(store); +// +// log(store.multLocal(qnm1), store); +// log(tmp.multLocal(qnp1), tmp); +// store.addLocal(tmp).multLocal(1f / -4f); +// exp(store, tmp); +// store.set(tmp).multLocal(qn); +// +// return store.normalizeLocal(); +// //return qn * (((qni * qnm1).log() + (qni * qnp1).log()) / -4).exp(); +// } + + //! Given 3 quaternions, qn-1,qn and qn+1, calculate a control point to be used in spline interpolation + private static Quaternion spline(Quaternion qnm1, Quaternion qn, Quaternion qnp1, Quaternion store, Quaternion tmp) { + Quaternion invQn = new Quaternion(-qn.x, -qn.y, -qn.z, qn.w); + + + log(invQn.mult(qnp1), tmp); + log(invQn.mult(qnm1), store); + store.addLocal(tmp).multLocal(-1f / 4f); + exp(store, tmp); + store.set(qn).multLocal(tmp); + + return store.normalizeLocal(); + //return qn * (((qni * qnm1).log() + (qni * qnp1).log()) / -4).exp(); + } + + + //! spherical cubic interpolation + public static Quaternion squad(Quaternion q0, Quaternion q1, Quaternion q2, Quaternion q3, Quaternion a, Quaternion b, float t, Quaternion store) { + + spline(q0, q1, q2, a, store); + spline(q1, q2, q3, b, store); + + slerp(a, b, t, store); + slerp(q1, q2, t, a); + return slerp(a, store, 2 * t * (1 - t), b); + //slerpNoInvert(a, b, t, store); + //slerpNoInvert(q1, q2, t, a); + //return slerpNoInvert(a, store, 2 * t * (1 - t), b); + +// quaternion c = slerpNoInvert(q1, q2, t), +// d = slerpNoInvert(a, b, t); +// return slerpNoInvert(c, d, 2 * t * (1 - t)); + } + + + /** + * Returns the shortest distance between a Ray and a segment. + * The segment is defined by a start position and an end position in world space + * The distance returned will be in world space (world units). + * If the camera parameter is not null the distance will be returned in screen space (pixels) + * + * @param ray The ray + * @param segStart The start position of the segment in world space + * @param segEnd The end position of the segment in world space + * @param camera The renderer camera if the distance is required in screen space. Null if the distance is required in world space + * @return the shortest distance between the ray and the segment or -1 if no solution is found. + */ + public static float raySegmentShortestDistance(Ray ray, Vector3f segStart, Vector3f segEnd, Camera camera) { + // Algorithm is ported from the C algorithm of + // Paul Bourke at http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/ + TempVars vars = TempVars.get(); + Vector3f resultSegmentPoint1 = vars.vect1; + Vector3f resultSegmentPoint2 = vars.vect2; + + Vector3f p1 = segStart; + Vector3f p2 = segEnd; + Vector3f p3 = ray.origin; + Vector3f p4 = vars.vect3.set(ray.getDirection()).multLocal(Math.min(ray.getLimit(), 1000)).addLocal(ray.getOrigin()); + Vector3f p13 = vars.vect4.set(p1).subtractLocal(p3); + Vector3f p43 = vars.vect5.set(p4).subtractLocal(p3); + + if (p43.lengthSquared() < 0.0001) { + vars.release(); + return -1; + } + Vector3f p21 = vars.vect6.set(p2).subtractLocal(p1); + if (p21.lengthSquared() < 0.0001) { + vars.release(); + return -1; + } + + double d1343 = p13.x * (double) p43.x + (double) p13.y * p43.y + (double) p13.z * p43.z; + double d4321 = p43.x * (double) p21.x + (double) p43.y * p21.y + (double) p43.z * p21.z; + double d1321 = p13.x * (double) p21.x + (double) p13.y * p21.y + (double) p13.z * p21.z; + double d4343 = p43.x * (double) p43.x + (double) p43.y * p43.y + (double) p43.z * p43.z; + double d2121 = p21.x * (double) p21.x + (double) p21.y * p21.y + (double) p21.z * p21.z; + + double denom = d2121 * d4343 - d4321 * d4321; + if (Math.abs(denom) < 0.0001) { + vars.release(); + return -1; + } + double numer = d1343 * d4321 - d1321 * d4343; + + double mua = numer / denom; + double mub = (d1343 + d4321 * (mua)) / d4343; + + resultSegmentPoint1.x = (float) (p1.x + mua * p21.x); + resultSegmentPoint1.y = (float) (p1.y + mua * p21.y); + resultSegmentPoint1.z = (float) (p1.z + mua * p21.z); + resultSegmentPoint2.x = (float) (p3.x + mub * p43.x); + resultSegmentPoint2.y = (float) (p3.y + mub * p43.y); + resultSegmentPoint2.z = (float) (p3.z + mub * p43.z); + + //check if result 1 is in the segment section. + float startToPoint = vars.vect3.set(resultSegmentPoint1).subtractLocal(segStart).lengthSquared(); + float endToPoint = vars.vect3.set(resultSegmentPoint1).subtractLocal(segEnd).lengthSquared(); + float segLength = vars.vect3.set(segEnd).subtractLocal(segStart).lengthSquared(); + if (startToPoint > segLength || endToPoint > segLength) { + vars.release(); + return -1; + } + + if (camera != null) { + //camera is not null let's convert the points in screen space + camera.getScreenCoordinates(resultSegmentPoint1, resultSegmentPoint1); + camera.getScreenCoordinates(resultSegmentPoint2, resultSegmentPoint2); + } + + float length = resultSegmentPoint1.subtractLocal(resultSegmentPoint2).length(); + vars.release(); + return length; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java index 92de09332..53bdb56bf 100644 --- a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java +++ b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java @@ -34,6 +34,7 @@ package com.jme3.math; import com.jme3.export.*; import com.jme3.util.BufferUtils; import com.jme3.util.TempVars; + import java.io.IOException; import java.nio.FloatBuffer; import java.util.logging.Logger; @@ -1022,96 +1023,95 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable store = new Matrix4f(); } - float temp00, temp01, temp02, temp03; - float temp10, temp11, temp12, temp13; - float temp20, temp21, temp22, temp23; - float temp30, temp31, temp32, temp33; + TempVars v = TempVars.get(); + float[] m = v.matrixWrite; - temp00 = m00 * in2.m00 + m[0] = m00 * in2.m00 + m01 * in2.m10 + m02 * in2.m20 + m03 * in2.m30; - temp01 = m00 * in2.m01 + m[1] = m00 * in2.m01 + m01 * in2.m11 + m02 * in2.m21 + m03 * in2.m31; - temp02 = m00 * in2.m02 + m[2] = m00 * in2.m02 + m01 * in2.m12 + m02 * in2.m22 + m03 * in2.m32; - temp03 = m00 * in2.m03 + m[3] = m00 * in2.m03 + m01 * in2.m13 + m02 * in2.m23 + m03 * in2.m33; - temp10 = m10 * in2.m00 + m[4] = m10 * in2.m00 + m11 * in2.m10 + m12 * in2.m20 + m13 * in2.m30; - temp11 = m10 * in2.m01 + m[5] = m10 * in2.m01 + m11 * in2.m11 + m12 * in2.m21 + m13 * in2.m31; - temp12 = m10 * in2.m02 + m[6] = m10 * in2.m02 + m11 * in2.m12 + m12 * in2.m22 + m13 * in2.m32; - temp13 = m10 * in2.m03 + m[7] = m10 * in2.m03 + m11 * in2.m13 + m12 * in2.m23 + m13 * in2.m33; - temp20 = m20 * in2.m00 + m[8] = m20 * in2.m00 + m21 * in2.m10 + m22 * in2.m20 + m23 * in2.m30; - temp21 = m20 * in2.m01 + m[9] = m20 * in2.m01 + m21 * in2.m11 + m22 * in2.m21 + m23 * in2.m31; - temp22 = m20 * in2.m02 + m[10] = m20 * in2.m02 + m21 * in2.m12 + m22 * in2.m22 + m23 * in2.m32; - temp23 = m20 * in2.m03 + m[11] = m20 * in2.m03 + m21 * in2.m13 + m22 * in2.m23 + m23 * in2.m33; - temp30 = m30 * in2.m00 + m[12] = m30 * in2.m00 + m31 * in2.m10 + m32 * in2.m20 + m33 * in2.m30; - temp31 = m30 * in2.m01 + m[13] = m30 * in2.m01 + m31 * in2.m11 + m32 * in2.m21 + m33 * in2.m31; - temp32 = m30 * in2.m02 + m[14] = m30 * in2.m02 + m31 * in2.m12 + m32 * in2.m22 + m33 * in2.m32; - temp33 = m30 * in2.m03 + m[15] = m30 * in2.m03 + m31 * in2.m13 + m32 * in2.m23 + m33 * in2.m33; - store.m00 = temp00; - store.m01 = temp01; - store.m02 = temp02; - store.m03 = temp03; - store.m10 = temp10; - store.m11 = temp11; - store.m12 = temp12; - store.m13 = temp13; - store.m20 = temp20; - store.m21 = temp21; - store.m22 = temp22; - store.m23 = temp23; - store.m30 = temp30; - store.m31 = temp31; - store.m32 = temp32; - store.m33 = temp33; + store.m00 = m[0]; + store.m01 = m[1]; + store.m02 = m[2]; + store.m03 = m[3]; + store.m10 = m[4]; + store.m11 = m[5]; + store.m12 = m[6]; + store.m13 = m[7]; + store.m20 = m[8]; + store.m21 = m[9]; + store.m22 = m[10]; + store.m23 = m[11]; + store.m30 = m[12]; + store.m31 = m[13]; + store.m32 = m[14]; + store.m33 = m[15]; + v.release(); return store; } @@ -1708,8 +1708,8 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable return new Vector3f(m03, m13, m23); } - public void toTranslationVector(Vector3f vector) { - vector.set(m03, m13, m23); + public Vector3f toTranslationVector(Vector3f vector) { + return vector.set(m03, m13, m23); } public Quaternion toRotationQuat() { @@ -1718,8 +1718,9 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable return quat; } - public void toRotationQuat(Quaternion q) { - q.fromRotationMatrix(toRotationMatrix()); + public Quaternion toRotationQuat(Quaternion q) { + return q.fromRotationMatrix(m00, m01, m02, m10, + m11, m12, m20, m21, m22); } public Matrix3f toRotationMatrix() { @@ -1752,15 +1753,16 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable /** * Retreives the scale vector from the matrix and stores it into a given * vector. - * - * @param the - * vector where the scale will be stored + * + * @param store the vector where the scale will be stored + * @return the store vector */ - public void toScaleVector(Vector3f vector) { + public Vector3f toScaleVector(Vector3f store) { float scaleX = (float) Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20); float scaleY = (float) Math.sqrt(m01 * m01 + m11 * m11 + m21 * m21); float scaleZ = (float) Math.sqrt(m02 * m02 + m12 * m12 + m22 * m22); - vector.set(scaleX, scaleY, scaleZ); + store.set(scaleX, scaleY, scaleZ); + return store; } /** @@ -1774,25 +1776,30 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable * the Z scale */ public void setScale(float x, float y, float z) { - TempVars vars = TempVars.get(); - vars.vect1.set(m00, m10, m20); - vars.vect1.normalizeLocal().multLocal(x); - m00 = vars.vect1.x; - m10 = vars.vect1.y; - m20 = vars.vect1.z; - - vars.vect1.set(m01, m11, m21); - vars.vect1.normalizeLocal().multLocal(y); - m01 = vars.vect1.x; - m11 = vars.vect1.y; - m21 = vars.vect1.z; - - vars.vect1.set(m02, m12, m22); - vars.vect1.normalizeLocal().multLocal(z); - m02 = vars.vect1.x; - m12 = vars.vect1.y; - m22 = vars.vect1.z; - vars.release(); + + float length = m00 * m00 + m10 * m10 + m20 * m20; + if (length != 0f) { + length = length == 1 ? x : (x / FastMath.sqrt(length)); + m00 *= length; + m10 *= length; + m20 *= length; + } + + length = m01 * m01 + m11 * m11 + m21 * m21; + if (length != 0f) { + length = length == 1 ? y : (y / FastMath.sqrt(length)); + m01 *= length; + m11 *= length; + m21 *= length; + } + + length = m02 * m02 + m12 * m12 + m22 * m22; + if (length != 0f) { + length = length == 1 ? z : (z / FastMath.sqrt(length)); + m02 *= length; + m12 *= length; + m22 *= length; + } } /** diff --git a/jme3-core/src/main/java/com/jme3/math/Quaternion.java b/jme3-core/src/main/java/com/jme3/math/Quaternion.java index 89c7d8647..d592640e1 100644 --- a/jme3-core/src/main/java/com/jme3/math/Quaternion.java +++ b/jme3-core/src/main/java/com/jme3/math/Quaternion.java @@ -33,10 +33,8 @@ package com.jme3.math; import com.jme3.export.*; import com.jme3.util.TempVars; -import java.io.Externalizable; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; + +import java.io.*; import java.util.logging.Logger; /** @@ -452,11 +450,56 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl return result; } + /** + * toTransformMatrix converts this quaternion to a transform + * matrix. The result is stored in result. + * Note this method won't preserve the scale of the given matrix. + * + * @param store The Matrix3f to store the result in. + * @return the transform matrix with the rotation representation of this quaternion. + */ + public Matrix4f toTransformMatrix(Matrix4f store) { + + float norm = norm(); + // we explicitly test norm against one here, saving a division + // at the cost of a test and branch. Is it worth it? + float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0; + + // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs + // will be used 2-4 times each. + float xs = x * s; + float ys = y * s; + float zs = z * s; + float xx = x * xs; + float xy = x * ys; + float xz = x * zs; + float xw = w * xs; + float yy = y * ys; + float yz = y * zs; + float yw = w * ys; + float zz = z * zs; + float zw = w * zs; + + // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here + store.m00 = 1 - (yy + zz); + store.m01 = (xy - zw); + store.m02 = (xz + yw); + store.m10 = (xy + zw); + store.m11 = 1 - (xx + zz); + store.m12 = (yz - xw); + store.m20 = (xz - yw); + store.m21 = (yz + xw); + store.m22 = 1 - (xx + yy); + + return store; + } + /** * toRotationMatrix converts this quaternion to a rotational * matrix. The result is stored in result. 4th row and 4th column values are * untouched. Note: the result is created from a normalized version of this quat. - * + * Note that this method will preserve the scale of the given matrix + * * @param result * The Matrix4f to store the result in. * @return the rotation matrix representation of this quaternion. @@ -464,7 +507,7 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl public Matrix4f toRotationMatrix(Matrix4f result) { TempVars tempv = TempVars.get(); Vector3f originalScale = tempv.vect1; - + result.toScaleVector(originalScale); result.setScale(1, 1, 1); float norm = norm(); @@ -499,9 +542,9 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl result.m22 = 1 - (xx + yy); result.setScale(originalScale); - + tempv.release(); - + return result; } diff --git a/jme3-core/src/main/java/com/jme3/math/Transform.java b/jme3-core/src/main/java/com/jme3/math/Transform.java index 6fb29c99b..d9ac33ef0 100644 --- a/jme3-core/src/main/java/com/jme3/math/Transform.java +++ b/jme3-core/src/main/java/com/jme3/math/Transform.java @@ -32,6 +32,8 @@ package com.jme3.math; import com.jme3.export.*; +import com.jme3.util.TempVars; + import java.io.IOException; /** @@ -174,13 +176,14 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable } /** - * Sets this matrix to the interpolation between the first matrix and the second by delta amount. + * Sets this transform to the interpolation between the first transform and the second by delta amount. * @param t1 The beginning transform. * @param t2 The ending transform. * @param delta An amount between 0 and 1 representing how far to interpolate from t1 to t2. */ public void interpolateTransforms(Transform t1, Transform t2, float delta) { - this.rot.slerp(t1.rot,t2.rot,delta); + t1.rot.nlerp(t2.rot, delta); + this.rot.set(t1.rot); this.translation.interpolateLocal(t1.translation,t2.translation,delta); this.scale.interpolateLocal(t1.scale,t2.scale,delta); } @@ -257,17 +260,25 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable } public Matrix4f toTransformMatrix() { - Matrix4f trans = new Matrix4f(); - trans.setTranslation(translation); - trans.setRotationQuaternion(rot); - trans.setScale(scale); - return trans; + return toTransformMatrix(null); + } + + public Matrix4f toTransformMatrix(Matrix4f store) { + if (store == null) { + store = new Matrix4f(); + } + store.setTranslation(translation); + rot.toTransformMatrix(store); + store.setScale(scale); + return store; } public void fromTransformMatrix(Matrix4f mat) { - translation.set(mat.toTranslationVector()); - rot.set(mat.toRotationQuat()); - scale.set(mat.toScaleVector()); + TempVars vars = TempVars.get(); + translation.set(mat.toTranslationVector(vars.vect1)); + rot.set(mat.toRotationQuat(vars.quat1)); + scale.set(mat.toScaleVector(vars.vect2)); + vars.release(); } public Transform invert() { diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index d8e73a36e..0369517e2 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -394,7 +394,15 @@ public enum Caps { /** * GPU can provide and accept binary shaders. */ - BinaryShader; + BinaryShader, + /** + * Supporting working with UniformBufferObject. + */ + UniformBufferObject, + /** + * Supporting working with ShaderStorageBufferObjects. + */ + ShaderStorageBufferObject; /** * Returns true if given the renderer capabilities, the texture diff --git a/jme3-core/src/main/java/com/jme3/renderer/Limits.java b/jme3-core/src/main/java/com/jme3/renderer/Limits.java index a7e737092..cd1bfec35 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Limits.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Limits.java @@ -62,4 +62,20 @@ public enum Limits { ColorTextureSamples, DepthTextureSamples, TextureAnisotropy, + + // UBO + UniformBufferObjectMaxVertexBlocks, + UniformBufferObjectMaxFragmentBlocks, + UniformBufferObjectMaxGeometryBlocks, + UniformBufferObjectMaxBlockSize, + + // SSBO + ShaderStorageBufferObjectMaxBlockSize, + ShaderStorageBufferObjectMaxVertexBlocks, + ShaderStorageBufferObjectMaxFragmentBlocks, + ShaderStorageBufferObjectMaxGeometryBlocks, + ShaderStorageBufferObjectMaxTessControlBlocks, + ShaderStorageBufferObjectMaxTessEvaluationBlocks, + ShaderStorageBufferObjectMaxComputeBlocks, + ShaderStorageBufferObjectMaxCombineBlocks, } diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java index 49f25240a..4599fdbb3 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java @@ -32,6 +32,7 @@ package com.jme3.renderer; import com.jme3.material.RenderState; +import com.jme3.material.RenderState.BlendFunc; import com.jme3.math.ColorRGBA; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; @@ -110,6 +111,30 @@ public class RenderContext { */ public RenderState.BlendEquationAlpha blendEquationAlpha = RenderState.BlendEquationAlpha.InheritColor; + /** + * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc, + * com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc) + */ + public RenderState.BlendFunc sfactorRGB = RenderState.BlendFunc.One; + + /** + * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc, + * com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc) + */ + public RenderState.BlendFunc dfactorRGB = RenderState.BlendFunc.One; + + /** + * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc, + * com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc) + */ + public RenderState.BlendFunc sfactorAlpha = RenderState.BlendFunc.One; + + /** + * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc, + * com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc) + */ + public RenderState.BlendFunc dfactorAlpha = RenderState.BlendFunc.One; + /** * @see RenderState#setWireframe(boolean) */ @@ -266,6 +291,10 @@ public class RenderContext { blendMode = RenderState.BlendMode.Off; blendEquation = RenderState.BlendEquation.Add; blendEquationAlpha = RenderState.BlendEquationAlpha.InheritColor; + sfactorRGB = BlendFunc.One; + dfactorRGB = BlendFunc.One; + sfactorAlpha = BlendFunc.One; + dfactorAlpha = BlendFunc.One; wireframe = false; boundShaderProgram = 0; boundShader = null; diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index 12c0a1a85..fe67ee2a8 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -642,7 +642,7 @@ public class RenderManager { throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); } - gm.getMaterial().preload(this); + gm.getMaterial().preload(this, gm); Mesh mesh = gm.getMesh(); if (mesh != null && mesh.getVertexCount() != 0 diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java index 9f562ea61..201729da8 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java @@ -35,6 +35,7 @@ import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; +import com.jme3.shader.BufferObject; import com.jme3.shader.Shader; import com.jme3.shader.Shader.ShaderSource; import com.jme3.system.AppSettings; @@ -267,12 +268,26 @@ public interface Renderer { */ public void updateBufferData(VertexBuffer vb); + /** + * Uploads data of the buffer object on the GPU. + * + * @param bo the buffer object to upload. + */ + public void updateBufferData(BufferObject bo); + /** * Deletes a vertex buffer from the GPU. * @param vb The vertex buffer to delete */ public void deleteBuffer(VertexBuffer vb); + /** + * Deletes the buffer object from the GPU. + * + * @param bo the buffer object to delete. + */ + public void deleteBuffer(BufferObject bo); + /** * Renders count meshes, with the geometry data supplied and * per-instance data supplied. diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java index e0a1975c1..b2a57736f 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java @@ -45,156 +45,157 @@ import java.nio.ShortBuffer; */ public interface GL { - static final int GL_ALPHA = 0x1906; - static final int GL_ALWAYS = 0x207; - static final int GL_ARRAY_BUFFER = 0x8892; - static final int GL_BACK = 0x405; - static final int GL_BLEND = 0xBE2; - static final int GL_BLUE = 0x1905; - static final int GL_BYTE = 0x1400; - static final int GL_CLAMP_TO_EDGE = 0x812F; - static final int GL_COLOR_BUFFER_BIT = 0x4000; - static final int GL_COMPILE_STATUS = 0x8B81; - static final int GL_CULL_FACE = 0xB44; - static final int GL_DECR = 0x1E03; - static final int GL_DECR_WRAP = 0x8508; - static final int GL_DEPTH_BUFFER_BIT = 0x100; - static final int GL_DEPTH_COMPONENT = 0x1902; - static final int GL_DEPTH_COMPONENT16 = 0x81A5; - static final int GL_DEPTH_TEST = 0xB71; - static final int GL_DOUBLE = 0x140A; - static final int GL_DST_ALPHA = 0x0304; - static final int GL_DST_COLOR = 0x306; - static final int GL_DYNAMIC_DRAW = 0x88E8; - static final int GL_ELEMENT_ARRAY_BUFFER = 0x8893; - static final int GL_EQUAL = 0x202; - static final int GL_EXTENSIONS = 0x1F03; - static final int GL_FALSE = 0x0; - static final int GL_FLOAT = 0x1406; - static final int GL_FRAGMENT_SHADER = 0x8B30; - static final int GL_FRONT = 0x404; - static final int GL_FUNC_ADD = 0x8006; - static final int GL_FUNC_SUBTRACT = 0x800A; - static final int GL_FUNC_REVERSE_SUBTRACT = 0x800B; - static final int GL_FRONT_AND_BACK = 0x408; - static final int GL_GEQUAL = 0x206; - static final int GL_GREATER = 0x204; - static final int GL_GREEN = 0x1904; - static final int GL_INCR = 0x1E02; - static final int GL_INCR_WRAP = 0x8507; - static final int GL_INFO_LOG_LENGTH = 0x8B84; - static final int GL_INT = 0x1404; - static final int GL_INVALID_ENUM = 0x500; - static final int GL_INVALID_VALUE = 0x501; - static final int GL_INVALID_OPERATION = 0x502; - static final int GL_INVERT = 0x150A; - static final int GL_KEEP = 0x1E00; - static final int GL_LEQUAL = 0x203; - static final int GL_LESS = 0x201; - static final int GL_LINEAR = 0x2601; - static final int GL_LINEAR_MIPMAP_LINEAR = 0x2703; - static final int GL_LINEAR_MIPMAP_NEAREST = 0x2701; - static final int GL_LINES = 0x1; - static final int GL_LINE_LOOP = 0x2; - static final int GL_LINE_STRIP = 0x3; - static final int GL_LINK_STATUS = 0x8B82; - static final int GL_LUMINANCE = 0x1909; - static final int GL_LUMINANCE_ALPHA = 0x190A; - static final int GL_MAX = 0x8008; - static final int GL_MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C; - static final int GL_MAX_FRAGMENT_UNIFORM_COMPONENTS = 0x8B49; - static final int GL_MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD; - static final int GL_MAX_TEXTURE_IMAGE_UNITS = 0x8872; - static final int GL_MAX_TEXTURE_SIZE = 0xD33; - static final int GL_MAX_VERTEX_ATTRIBS = 0x8869; - static final int GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C; - static final int GL_MAX_VERTEX_UNIFORM_COMPONENTS = 0x8B4A; - static final int GL_MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB; - static final int GL_MIRRORED_REPEAT = 0x8370; - static final int GL_MIN = 0x8007; - static final int GL_NEAREST = 0x2600; - static final int GL_NEAREST_MIPMAP_LINEAR = 0x2702; - static final int GL_NEAREST_MIPMAP_NEAREST = 0x2700; - static final int GL_NEVER = 0x200; - static final int GL_NO_ERROR = 0x0; - static final int GL_NONE = 0x0; - static final int GL_NOTEQUAL = 0x205; - static final int GL_ONE = 0x1; - static final int GL_ONE_MINUS_DST_ALPHA = 0x0305; - static final int GL_ONE_MINUS_DST_COLOR = 0x307; - static final int GL_ONE_MINUS_SRC_ALPHA = 0x303; - static final int GL_ONE_MINUS_SRC_COLOR = 0x301; - static final int GL_OUT_OF_MEMORY = 0x505; - static final int GL_POINTS = 0x0; - static final int GL_POLYGON_OFFSET_FILL = 0x8037; - static final int GL_QUERY_RESULT = 0x8866; - static final int GL_QUERY_RESULT_AVAILABLE = 0x8867; - static final int GL_RED = 0x1903; - static final int GL_RENDERER = 0x1F01; - static final int GL_REPEAT = 0x2901; - static final int GL_REPLACE = 0x1E01; - static final int GL_RGB = 0x1907; - static final int GL_RGB565 = 0x8D62; - static final int GL_RGB5_A1 = 0x8057; - static final int GL_RGBA = 0x1908; - static final int GL_RGBA4 = 0x8056; - static final int GL_SCISSOR_TEST = 0xC11; - static final int GL_SHADING_LANGUAGE_VERSION = 0x8B8C; - static final int GL_SHORT = 0x1402; - static final int GL_SRC_ALPHA = 0x302; - static final int GL_SRC_ALPHA_SATURATE = 0x0308; - static final int GL_SRC_COLOR = 0x300; - static final int GL_STATIC_DRAW = 0x88E4; - static final int GL_STENCIL_BUFFER_BIT = 0x400; - static final int GL_STENCIL_TEST = 0xB90; - static final int GL_STREAM_DRAW = 0x88E0; - static final int GL_STREAM_READ = 0x88E1; - static final int GL_TEXTURE = 0x1702; - static final int GL_TEXTURE0 = 0x84C0; - static final int GL_TEXTURE1 = 0x84C1; - static final int GL_TEXTURE2 = 0x84C2; - static final int GL_TEXTURE3 = 0x84C3; - static final int GL_TEXTURE4 = 0x84C4; - static final int GL_TEXTURE5 = 0x84C5; - static final int GL_TEXTURE6 = 0x84C6; - static final int GL_TEXTURE7 = 0x84C7; - static final int GL_TEXTURE8 = 0x84C8; - static final int GL_TEXTURE9 = 0x84C9; - static final int GL_TEXTURE10 = 0x84CA; - static final int GL_TEXTURE11 = 0x84CB; - static final int GL_TEXTURE12 = 0x84CC; - static final int GL_TEXTURE13 = 0x84CD; - static final int GL_TEXTURE14 = 0x84CE; - static final int GL_TEXTURE15 = 0x84CF; - static final int GL_TEXTURE_2D = 0xDE1; - static final int GL_TEXTURE_CUBE_MAP = 0x8513; - static final int GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; - static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; - static final int GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; - static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; - static final int GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; - static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; - static final int GL_TEXTURE_MAG_FILTER = 0x2800; - static final int GL_TEXTURE_MIN_FILTER = 0x2801; - static final int GL_TEXTURE_WRAP_S = 0x2802; - static final int GL_TEXTURE_WRAP_T = 0x2803; - static final int GL_TIME_ELAPSED = 0x88BF; - static final int GL_TRIANGLES = 0x4; - static final int GL_TRIANGLE_FAN = 0x6; - static final int GL_TRIANGLE_STRIP = 0x5; - static final int GL_TRUE = 0x1; - static final int GL_UNPACK_ALIGNMENT = 0xCF5; - static final int GL_UNSIGNED_BYTE = 0x1401; - static final int GL_UNSIGNED_INT = 0x1405; - static final int GL_UNSIGNED_SHORT = 0x1403; - static final int GL_UNSIGNED_SHORT_5_6_5 = 0x8363; - static final int GL_UNSIGNED_SHORT_5_5_5_1 = 0x8034; - static final int GL_VENDOR = 0x1F00; - static final int GL_VERSION = 0x1F02; - static final int GL_VERTEX_SHADER = 0x8B31; - static final int GL_ZERO = 0x0; - - void resetStats(); + public static final int GL_ALPHA = 0x1906; + public static final int GL_ALWAYS = 0x207; + public static final int GL_ARRAY_BUFFER = 0x8892; + public static final int GL_BACK = 0x405; + public static final int GL_BLEND = 0xBE2; + public static final int GL_BLUE = 0x1905; + public static final int GL_BYTE = 0x1400; + public static final int GL_CLAMP_TO_EDGE = 0x812F; + public static final int GL_COLOR_BUFFER_BIT = 0x4000; + public static final int GL_COMPILE_STATUS = 0x8B81; + public static final int GL_CULL_FACE = 0xB44; + public static final int GL_DECR = 0x1E03; + public static final int GL_DECR_WRAP = 0x8508; + public static final int GL_DEPTH_BUFFER_BIT = 0x100; + public static final int GL_DEPTH_COMPONENT = 0x1902; + public static final int GL_DEPTH_COMPONENT16 = 0x81A5; + public static final int GL_DEPTH_TEST = 0xB71; + public static final int GL_DOUBLE = 0x140A; + public static final int GL_DST_ALPHA = 0x0304; + public static final int GL_DST_COLOR = 0x306; + public static final int GL_DYNAMIC_DRAW = 0x88E8; + public static final int GL_DYNAMIC_COPY = 0x88EA; + public static final int GL_ELEMENT_ARRAY_BUFFER = 0x8893; + public static final int GL_EQUAL = 0x202; + public static final int GL_EXTENSIONS = 0x1F03; + public static final int GL_FALSE = 0x0; + public static final int GL_FLOAT = 0x1406; + public static final int GL_FRAGMENT_SHADER = 0x8B30; + public static final int GL_FRONT = 0x404; + public static final int GL_FUNC_ADD = 0x8006; + public static final int GL_FUNC_SUBTRACT = 0x800A; + public static final int GL_FUNC_REVERSE_SUBTRACT = 0x800B; + public static final int GL_FRONT_AND_BACK = 0x408; + public static final int GL_GEQUAL = 0x206; + public static final int GL_GREATER = 0x204; + public static final int GL_GREEN = 0x1904; + public static final int GL_INCR = 0x1E02; + public static final int GL_INCR_WRAP = 0x8507; + public static final int GL_INFO_LOG_LENGTH = 0x8B84; + public static final int GL_INT = 0x1404; + public static final int GL_INVALID_ENUM = 0x500; + public static final int GL_INVALID_VALUE = 0x501; + public static final int GL_INVALID_OPERATION = 0x502; + public static final int GL_INVERT = 0x150A; + public static final int GL_KEEP = 0x1E00; + public static final int GL_LEQUAL = 0x203; + public static final int GL_LESS = 0x201; + public static final int GL_LINEAR = 0x2601; + public static final int GL_LINEAR_MIPMAP_LINEAR = 0x2703; + public static final int GL_LINEAR_MIPMAP_NEAREST = 0x2701; + public static final int GL_LINES = 0x1; + public static final int GL_LINE_LOOP = 0x2; + public static final int GL_LINE_STRIP = 0x3; + public static final int GL_LINK_STATUS = 0x8B82; + public static final int GL_LUMINANCE = 0x1909; + public static final int GL_LUMINANCE_ALPHA = 0x190A; + public static final int GL_MAX = 0x8008; + public static final int GL_MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C; + public static final int GL_MAX_FRAGMENT_UNIFORM_COMPONENTS = 0x8B49; + public static final int GL_MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD; + public static final int GL_MAX_TEXTURE_IMAGE_UNITS = 0x8872; + public static final int GL_MAX_TEXTURE_SIZE = 0xD33; + public static final int GL_MAX_VERTEX_ATTRIBS = 0x8869; + public static final int GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C; + public static final int GL_MAX_VERTEX_UNIFORM_COMPONENTS = 0x8B4A; + public static final int GL_MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB; + public static final int GL_MIRRORED_REPEAT = 0x8370; + public static final int GL_MIN = 0x8007; + public static final int GL_NEAREST = 0x2600; + public static final int GL_NEAREST_MIPMAP_LINEAR = 0x2702; + public static final int GL_NEAREST_MIPMAP_NEAREST = 0x2700; + public static final int GL_NEVER = 0x200; + public static final int GL_NO_ERROR = 0x0; + public static final int GL_NONE = 0x0; + public static final int GL_NOTEQUAL = 0x205; + public static final int GL_ONE = 0x1; + public static final int GL_ONE_MINUS_DST_ALPHA = 0x0305; + public static final int GL_ONE_MINUS_DST_COLOR = 0x307; + public static final int GL_ONE_MINUS_SRC_ALPHA = 0x303; + public static final int GL_ONE_MINUS_SRC_COLOR = 0x301; + public static final int GL_OUT_OF_MEMORY = 0x505; + public static final int GL_POINTS = 0x0; + public static final int GL_POLYGON_OFFSET_FILL = 0x8037; + public static final int GL_QUERY_RESULT = 0x8866; + public static final int GL_QUERY_RESULT_AVAILABLE = 0x8867; + public static final int GL_RED = 0x1903; + public static final int GL_RENDERER = 0x1F01; + public static final int GL_REPEAT = 0x2901; + public static final int GL_REPLACE = 0x1E01; + public static final int GL_RGB = 0x1907; + public static final int GL_RGB565 = 0x8D62; + public static final int GL_RGB5_A1 = 0x8057; + public static final int GL_RGBA = 0x1908; + public static final int GL_RGBA4 = 0x8056; + public static final int GL_SCISSOR_TEST = 0xC11; + public static final int GL_SHADING_LANGUAGE_VERSION = 0x8B8C; + public static final int GL_SHORT = 0x1402; + public static final int GL_SRC_ALPHA = 0x302; + public static final int GL_SRC_ALPHA_SATURATE = 0x0308; + public static final int GL_SRC_COLOR = 0x300; + public static final int GL_STATIC_DRAW = 0x88E4; + public static final int GL_STENCIL_BUFFER_BIT = 0x400; + public static final int GL_STENCIL_TEST = 0xB90; + public static final int GL_STREAM_DRAW = 0x88E0; + public static final int GL_STREAM_READ = 0x88E1; + public static final int GL_TEXTURE = 0x1702; + public static final int GL_TEXTURE0 = 0x84C0; + public static final int GL_TEXTURE1 = 0x84C1; + public static final int GL_TEXTURE2 = 0x84C2; + public static final int GL_TEXTURE3 = 0x84C3; + public static final int GL_TEXTURE4 = 0x84C4; + public static final int GL_TEXTURE5 = 0x84C5; + public static final int GL_TEXTURE6 = 0x84C6; + public static final int GL_TEXTURE7 = 0x84C7; + public static final int GL_TEXTURE8 = 0x84C8; + public static final int GL_TEXTURE9 = 0x84C9; + public static final int GL_TEXTURE10 = 0x84CA; + public static final int GL_TEXTURE11 = 0x84CB; + public static final int GL_TEXTURE12 = 0x84CC; + public static final int GL_TEXTURE13 = 0x84CD; + public static final int GL_TEXTURE14 = 0x84CE; + public static final int GL_TEXTURE15 = 0x84CF; + public static final int GL_TEXTURE_2D = 0xDE1; + public static final int GL_TEXTURE_CUBE_MAP = 0x8513; + public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; + public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; + public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; + public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; + public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; + public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; + public static final int GL_TEXTURE_MAG_FILTER = 0x2800; + public static final int GL_TEXTURE_MIN_FILTER = 0x2801; + public static final int GL_TEXTURE_WRAP_S = 0x2802; + public static final int GL_TEXTURE_WRAP_T = 0x2803; + public static final int GL_TIME_ELAPSED = 0x88BF; + public static final int GL_TRIANGLES = 0x4; + public static final int GL_TRIANGLE_FAN = 0x6; + public static final int GL_TRIANGLE_STRIP = 0x5; + public static final int GL_TRUE = 0x1; + public static final int GL_UNPACK_ALIGNMENT = 0xCF5; + public static final int GL_UNSIGNED_BYTE = 0x1401; + public static final int GL_UNSIGNED_INT = 0x1405; + public static final int GL_UNSIGNED_SHORT = 0x1403; + public static final int GL_UNSIGNED_SHORT_5_6_5 = 0x8363; + public static final int GL_UNSIGNED_SHORT_5_5_5_1 = 0x8034; + public static final int GL_VENDOR = 0x1F00; + public static final int GL_VERSION = 0x1F02; + public static final int GL_VERTEX_SHADER = 0x8B31; + public static final int GL_ZERO = 0x0; + + public void resetStats(); /** *

Reference Page

@@ -204,7 +205,7 @@ public interface GL { * * @param texture which texture unit to make active. One of:
{@link #GL_TEXTURE0 TEXTURE0}GL_TEXTURE[1-31]
*/ - void glActiveTexture(int texture); + public void glActiveTexture(int texture); /** *

Reference Page

@@ -225,7 +226,7 @@ public interface GL { * @param program the program object to which a shader object will be attached. * @param shader the shader object that is to be attached. */ - void glAttachShader(int program, int shader); + public void glAttachShader(int program, int shader); /** *

Reference Page

@@ -235,7 +236,7 @@ public interface GL { * @param target the target type of query object established. * @param query the name of a query object. */ - void glBeginQuery(int target, int query); + public void glBeginQuery(int target, int query); /** *

Reference Page

@@ -245,7 +246,7 @@ public interface GL { * @param target the target to which the buffer object is bound. * @param buffer the name of a buffer object. */ - void glBindBuffer(int target, int buffer); + public void glBindBuffer(int target, int buffer); /** *

Reference Page

@@ -259,7 +260,7 @@ public interface GL { * @param target the texture target. * @param texture the texture object to bind. */ - void glBindTexture(int target, int texture); + public void glBindTexture(int target, int texture); /** *

Reference Page

@@ -269,7 +270,7 @@ public interface GL { * @param colorMode the RGB blend equation, how the red, green, and blue components of the source and destination colors are combined. * @param alphaMode the alpha blend equation, how the alpha component of the source and destination colors are combined */ - void glBlendEquationSeparate(int colorMode, int alphaMode); + public void glBlendEquationSeparate(int colorMode, int alphaMode); /** *

Reference Page

@@ -279,7 +280,7 @@ public interface GL { * @param sfactor the source weighting factor. * @param dfactor the destination weighting factor. */ - void glBlendFunc(int sfactor, int dfactor); + public void glBlendFunc(int sfactor, int dfactor); /** *

Reference Page

@@ -291,7 +292,7 @@ public interface GL { * @param sfactorAlpha how the alpha source blending factor is computed. The initial value is GL_ONE. * @param dfactorAlpha how the alpha destination blending factor is computed. The initial value is GL_ZERO. */ - void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha); + public void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha); /** *

Reference Page

@@ -321,7 +322,7 @@ public interface GL { * @param dataSize the size in bytes of the buffer object's new data store * @param usage the expected usage pattern of the data store. */ - void glBufferData(int target, long dataSize, int usage); + public void glBufferData(int target, long dataSize, int usage); /** *

Reference Page

@@ -351,7 +352,7 @@ public interface GL { * @param data a pointer to data that will be copied into the data store for initialization, or {@code NULL} if no data is to be copied. * @param usage the expected usage pattern of the data store. */ - void glBufferData(int target, FloatBuffer data, int usage); + public void glBufferData(int target, FloatBuffer data, int usage); /** *

Reference Page

@@ -381,7 +382,7 @@ public interface GL { * @param data a pointer to data that will be copied into the data store for initialization, or {@code NULL} if no data is to be copied * @param usage the expected usage pattern of the data store. */ - void glBufferData(int target, ShortBuffer data, int usage); + public void glBufferData(int target, ShortBuffer data, int usage); /** *

Reference Page

@@ -411,7 +412,7 @@ public interface GL { * @param data a pointer to data that will be copied into the data store for initialization, or {@code NULL} if no data is to be copied. * @param usage the expected usage pattern of the data store. */ - void glBufferData(int target, ByteBuffer data, int usage); + public void glBufferData(int target, ByteBuffer data, int usage); /** *

Reference Page

@@ -422,7 +423,7 @@ public interface GL { * @param offset the offset into the buffer object's data store where data replacement will begin, measured in bytes. * @param data a pointer to the new data that will be copied into the data store. */ - void glBufferSubData(int target, long offset, FloatBuffer data); + public void glBufferSubData(int target, long offset, FloatBuffer data); /** *

Reference Page

@@ -433,7 +434,7 @@ public interface GL { * @param offset the offset into the buffer object's data store where data replacement will begin, measured in bytes. * @param data a pointer to the new data that will be copied into the data store. */ - void glBufferSubData(int target, long offset, ShortBuffer data); + public void glBufferSubData(int target, long offset, ShortBuffer data); /** *

Reference Page

@@ -444,7 +445,7 @@ public interface GL { * @param offset the offset into the buffer object's data store where data replacement will begin, measured in bytes. * @param data a pointer to the new data that will be copied into the data store. */ - void glBufferSubData(int target, long offset, ByteBuffer data); + public void glBufferSubData(int target, long offset, ByteBuffer data); /** *

Reference Page

@@ -454,7 +455,7 @@ public interface GL { * * @param mask Zero or the bitwise OR of one or more values indicating which buffers are to be cleared. */ - void glClear(int mask); + public void glClear(int mask); /** *

Reference Page

@@ -466,7 +467,7 @@ public interface GL { * @param blue the value to which to clear the B channel of the color buffer. * @param alpha the value to which to clear the A channel of the color buffer. */ - void glClearColor(float red, float green, float blue, float alpha); + public void glClearColor(float red, float green, float blue, float alpha); /** *

Reference Page

@@ -478,7 +479,7 @@ public interface GL { * @param blue whether B values are written or not. * @param alpha whether A values are written or not. */ - void glColorMask(boolean red, boolean green, boolean blue, boolean alpha); + public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha); /** *

Reference Page

@@ -487,7 +488,7 @@ public interface GL { * * @param shader the shader object to be compiled. */ - void glCompileShader(int shader); + public void glCompileShader(int shader); /** *

Reference Page

@@ -502,7 +503,7 @@ public interface GL { * @param border must be 0 * @param data a pointer to the compressed image data */ - void glCompressedTexImage2D(int target, int level, int internalFormat, int width, int height, int border, + public void glCompressedTexImage2D(int target, int level, int internalFormat, int width, int height, int border, ByteBuffer data); /** @@ -519,7 +520,7 @@ public interface GL { * @param format the format of the compressed image data stored at address {@code data}. * @param data a pointer to the compressed image data. */ - void glCompressedTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, + public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, ByteBuffer data); /** @@ -527,7 +528,7 @@ public interface GL { *

* Creates a program object. */ - int glCreateProgram(); + public int glCreateProgram(); /** *

Reference Page

@@ -536,7 +537,7 @@ public interface GL { * * @param shaderType the type of shader to be created. One of:
{@link #GL_VERTEX_SHADER VERTEX_SHADER}{@link #GL_FRAGMENT_SHADER FRAGMENT_SHADER}{@link GL3#GL_GEOMETRY_SHADER GEOMETRY_SHADER}{@link GL4#GL_TESS_CONTROL_SHADER TESS_CONTROL_SHADER}
{@link GL4#GL_TESS_EVALUATION_SHADER TESS_EVALUATION_SHADER}
*/ - int glCreateShader(int shaderType); + public int glCreateShader(int shaderType); /** *

Reference Page

@@ -547,7 +548,7 @@ public interface GL { * * @param mode the CullFace mode. One of:
{@link #GL_FRONT FRONT}{@link #GL_BACK BACK}{@link #GL_FRONT_AND_BACK FRONT_AND_BACK}
*/ - void glCullFace(int mode); + public void glCullFace(int mode); /** *

Reference Page

@@ -556,7 +557,7 @@ public interface GL { * * @param buffers an array of buffer objects to be deleted. */ - void glDeleteBuffers(IntBuffer buffers); + public void glDeleteBuffers(IntBuffer buffers); /** *

Reference Page

@@ -565,7 +566,7 @@ public interface GL { * * @param program the program object to be deleted. */ - void glDeleteProgram(int program); + public void glDeleteProgram(int program); /** *

Reference Page

@@ -574,7 +575,7 @@ public interface GL { * * @param shader the shader object to be deleted. */ - void glDeleteShader(int shader); + public void glDeleteShader(int shader); /** *

Reference Page

@@ -589,7 +590,7 @@ public interface GL { * * @param textures contains {@code n} names of texture objects to be deleted. */ - void glDeleteTextures(IntBuffer textures); + public void glDeleteTextures(IntBuffer textures); /** *

Reference Page

@@ -598,7 +599,7 @@ public interface GL { * * @param func the depth test comparison. One of:
{@link #GL_NEVER NEVER}{@link #GL_ALWAYS ALWAYS}{@link #GL_LESS LESS}{@link #GL_LEQUAL LEQUAL}{@link #GL_EQUAL EQUAL}{@link #GL_GREATER GREATER}{@link #GL_GEQUAL GEQUAL}{@link #GL_NOTEQUAL NOTEQUAL}
*/ - void glDepthFunc(int func); + public void glDepthFunc(int func); /** *

Reference Page

@@ -607,7 +608,7 @@ public interface GL { * * @param flag whether depth values are written or not. */ - void glDepthMask(boolean flag); + public void glDepthMask(boolean flag); /** *

Reference Page

@@ -617,7 +618,7 @@ public interface GL { * @param nearVal the near depth range. * @param farVal the far depth range. */ - void glDepthRange(double nearVal, double farVal); + public void glDepthRange(double nearVal, double farVal); /** *

Reference Page

@@ -627,7 +628,7 @@ public interface GL { * @param program the program object from which to detach the shader object. * @param shader the shader object to be detached. */ - void glDetachShader(int program, int shader); + public void glDetachShader(int program, int shader); /** *

Reference Page

@@ -636,7 +637,7 @@ public interface GL { * * @param cap the OpenGL state to disable. */ - void glDisable(int cap); + public void glDisable(int cap); /** *

Reference Page

@@ -645,7 +646,7 @@ public interface GL { * * @param index the index of the generic vertex attribute to be disabled. */ - void glDisableVertexAttribArray(int index); + public void glDisableVertexAttribArray(int index); /** *

Reference Page

@@ -660,7 +661,7 @@ public interface GL { * @param first the first vertex to transfer to the GL. * @param count the number of vertices after {@code first} to transfer to the GL. */ - void glDrawArrays(int mode, int first, int count); + public void glDrawArrays(int mode, int first, int count); /** *

Reference Page

@@ -700,7 +701,7 @@ public interface GL { * @param type the type of the values in {@code indices}. * @param indices a pointer to the location where the indices are stored. */ - void glDrawRangeElements(int mode, int start, int end, int count, int type, long indices); /// GL2+ + public void glDrawRangeElements(int mode, int start, int end, int count, int type, long indices); /// GL2+ /** *

Reference Page

@@ -709,7 +710,7 @@ public interface GL { * * @param cap the OpenGL state to enable. */ - void glEnable(int cap); + public void glEnable(int cap); /** *

Reference Page

@@ -718,7 +719,7 @@ public interface GL { * * @param index the index of the generic vertex attribute to be enabled. */ - void glEnableVertexAttribArray(int index); + public void glEnableVertexAttribArray(int index); /** *

Reference Page

@@ -727,7 +728,7 @@ public interface GL { * * @param target the query object target. */ - void glEndQuery(int target); + public void glEndQuery(int target); /** *

Reference Page

@@ -736,7 +737,7 @@ public interface GL { * * @param buffers a buffer in which the generated buffer object names are stored. */ - void glGenBuffers(IntBuffer buffers); + public void glGenBuffers(IntBuffer buffers); /** *

Reference Page

@@ -746,7 +747,7 @@ public interface GL { * * @param textures a scalar or buffer in which to place the returned texture names. */ - void glGenTextures(IntBuffer textures); + public void glGenTextures(IntBuffer textures); /** *

Reference Page

@@ -755,7 +756,7 @@ public interface GL { * * @param ids a buffer in which the generated query object names are stored. */ - void glGenQueries(int number, IntBuffer ids); + public void glGenQueries(int number, IntBuffer ids); /** *

Reference Page

@@ -765,7 +766,7 @@ public interface GL { * @param program the program object to be queried. * @param name a null terminated string containing the name of the attribute variable whose location is to be queried. */ - int glGetAttribLocation(int program, String name); + public int glGetAttribLocation(int program, String name); /** *

Reference Page

@@ -779,7 +780,7 @@ public interface GL { * @param pname the state variable. * @param params a scalar or buffer in which to place the returned data. */ - void glGetBoolean(int pname, ByteBuffer params); + public void glGetBoolean(int pname, ByteBuffer params); /** *

Reference Page

@@ -790,7 +791,7 @@ public interface GL { * @param offset the offset into the buffer object's data store from which data will be returned, measured in bytes. * @param data a pointer to the location where buffer object data is returned. */ - void glGetBufferSubData(int target, long offset, ByteBuffer data); + public void glGetBufferSubData(int target, long offset, ByteBuffer data); /** *

Reference Page

@@ -800,7 +801,7 @@ public interface GL { * further error will again record its code. If a call to {@code GetError} returns {@link #GL_NO_ERROR NO_ERROR}, then there has been no detectable error since * the last call to {@code GetError} (or since the GL was initialized). */ - int glGetError(); + public int glGetError(); /** *

Reference Page

@@ -814,7 +815,7 @@ public interface GL { * @param pname the state variable. * @param params a scalar or buffer in which to place the returned data. */ - void glGetInteger(int pname, IntBuffer params); + public void glGetInteger(int pname, IntBuffer params); /** *

Reference Page

@@ -825,7 +826,7 @@ public interface GL { * @param pname the object parameter. * @param params the requested object parameter. */ - void glGetProgram(int program, int pname, IntBuffer params); + public void glGetProgram(int program, int pname, IntBuffer params); /** *

Reference Page

@@ -835,7 +836,7 @@ public interface GL { * @param program the program object whose information log is to be queried. * @param maxSize the size of the character buffer for storing the returned information log. */ - String glGetProgramInfoLog(int program, int maxSize); + public String glGetProgramInfoLog(int program, int maxSize); /** * Unsigned version. @@ -843,7 +844,7 @@ public interface GL { * @param query the name of a query object * @param pname the symbolic name of a query object parameter */ - long glGetQueryObjectui64(int query, int pname); + public long glGetQueryObjectui64(int query, int pname); /** *

Reference Page

@@ -853,7 +854,7 @@ public interface GL { * @param query the name of a query object * @param pname the symbolic name of a query object parameter. One of:
{@link #GL_QUERY_RESULT QUERY_RESULT}{@link #GL_QUERY_RESULT_AVAILABLE QUERY_RESULT_AVAILABLE}
*/ - int glGetQueryObjectiv(int query, int pname); + public int glGetQueryObjectiv(int query, int pname); /** *

Reference Page

@@ -864,7 +865,7 @@ public interface GL { * @param pname the object parameter. * @param params the requested object parameter. */ - void glGetShader(int shader, int pname, IntBuffer params); + public void glGetShader(int shader, int pname, IntBuffer params); /** *

Reference Page

@@ -874,7 +875,7 @@ public interface GL { * @param shader the shader object whose information log is to be queried. * @param maxSize the size of the character buffer for storing the returned information log. */ - String glGetShaderInfoLog(int shader, int maxSize); + public String glGetShaderInfoLog(int shader, int maxSize); /** *

Reference Page

@@ -883,7 +884,7 @@ public interface GL { * * @param name the property to query. One of:
{@link #GL_RENDERER RENDERER}{@link #GL_VENDOR VENDOR}{@link #GL_EXTENSIONS EXTENSIONS}{@link #GL_VERSION VERSION}{@link GL2#GL_SHADING_LANGUAGE_VERSION SHADING_LANGUAGE_VERSION}
*/ - String glGetString(int name); + public String glGetString(int name); /** *

Reference Page

@@ -893,7 +894,7 @@ public interface GL { * @param program the program object to be queried. * @param name a null terminated string containing the name of the uniform variable whose location is to be queried. */ - int glGetUniformLocation(int program, String name); + public int glGetUniformLocation(int program, String name); /** *

Reference Page

@@ -902,7 +903,7 @@ public interface GL { * * @param cap the enable state to query. */ - boolean glIsEnabled(int cap); + public boolean glIsEnabled(int cap); /** *

Reference Page

@@ -911,7 +912,7 @@ public interface GL { * * @param width the line width. */ - void glLineWidth(float width); + public void glLineWidth(float width); /** *

Reference Page

@@ -920,7 +921,7 @@ public interface GL { * * @param program the program object to be linked. */ - void glLinkProgram(int program); + public void glLinkProgram(int program); /** *

Reference Page

@@ -930,7 +931,7 @@ public interface GL { * @param pname the pixel store parameter to set. * @param param the parameter value */ - void glPixelStorei(int pname, int param); + public void glPixelStorei(int pname, int param); /** *

Reference Page

@@ -944,7 +945,7 @@ public interface GL { * @param factor the maximum depth slope factor. * @param units the constant scale. */ - void glPolygonOffset(float factor, float units); + public void glPolygonOffset(float factor, float units); /** *

Reference Page

@@ -963,7 +964,7 @@ public interface GL { * @param type the pixel type. * @param data a buffer in which to place the returned pixel data. */ - void glReadPixels(int x, int y, int width, int height, int format, int type, ByteBuffer data); + public void glReadPixels(int x, int y, int width, int height, int format, int type, ByteBuffer data); /** @@ -983,7 +984,7 @@ public interface GL { * @param type the pixel type. * @param offset a buffer in which to place the returned pixel data/ */ - void glReadPixels(int x, int y, int width, int height, int format, int type, long offset); + public void glReadPixels(int x, int y, int width, int height, int format, int type, long offset); /** *

Reference Page

@@ -998,7 +999,7 @@ public interface GL { * @param width the scissor rectangle width. * @param height the scissor rectangle height. */ - void glScissor(int x, int y, int width, int height); + public void glScissor(int x, int y, int width, int height); /** *

Reference Page

@@ -1013,7 +1014,7 @@ public interface GL { * @param shader the shader object whose source code is to be replaced, * @param strings an array of pointers to strings containing the source code to be loaded into the shader */ - void glShaderSource(int shader, String[] strings, IntBuffer length); + public void glShaderSource(int shader, String[] strings, IntBuffer length); /** *

Reference Page

@@ -1026,7 +1027,7 @@ public interface GL { * buffer. The initial value is 0. * @param mask a mask that is ANDed with both the reference value and the stored stencil value when the test is done. The initial value is all 1's. */ - void glStencilFuncSeparate(int face, int func, int ref, int mask); + public void glStencilFuncSeparate(int face, int func, int ref, int mask); /** *

Reference Page

@@ -1039,7 +1040,7 @@ public interface GL { * @param dppass the stencil action when both the stencil test and the depth test pass, or when the stencil test passes and either there is no depth buffer or depth * testing is not enabled. The initial value is GL_KEEP. */ - void glStencilOpSeparate(int face, int sfail, int dpfail, int dppass); + public void glStencilOpSeparate(int face, int sfail, int dpfail, int dppass); /** *

Reference Page

@@ -1056,7 +1057,7 @@ public interface GL { * @param type the texel data type. * @param data the texel data. */ - void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, + public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer data); /** @@ -1068,7 +1069,7 @@ public interface GL { * @param pname the parameter to set. * @param param the parameter value. */ - void glTexParameterf(int target, int pname, float param); + public void glTexParameterf(int target, int pname, float param); /** *

Reference Page

@@ -1079,7 +1080,7 @@ public interface GL { * @param pname the parameter to set. * @param param the parameter value. */ - void glTexParameteri(int target, int pname, int param); + public void glTexParameteri(int target, int pname, int param); /** *

Reference Page

@@ -1097,7 +1098,7 @@ public interface GL { * @param type the pixel data type. * @param data the pixel data. */ - void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, + public void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer data); /** @@ -1108,7 +1109,7 @@ public interface GL { * @param location the location of the uniform variable to be modified. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. */ - void glUniform1(int location, FloatBuffer value); + public void glUniform1(int location, FloatBuffer value); /** *

Reference Page

@@ -1118,7 +1119,7 @@ public interface GL { * @param location the location of the uniform variable to be modified. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. */ - void glUniform1(int location, IntBuffer value); + public void glUniform1(int location, IntBuffer value); /** *

Reference Page

@@ -1128,7 +1129,7 @@ public interface GL { * @param location the location of the uniform variable to be modified. * @param v0 the uniform value. */ - void glUniform1f(int location, float v0); + public void glUniform1f(int location, float v0); /** *

Reference Page

@@ -1138,7 +1139,7 @@ public interface GL { * @param location the location of the uniform variable to be modified. * @param v0 the uniform value. */ - void glUniform1i(int location, int v0); + public void glUniform1i(int location, int v0); /** *

Reference Page

@@ -1148,7 +1149,7 @@ public interface GL { * @param location the location of the uniform variable to be modified. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. */ - void glUniform2(int location, IntBuffer value); + public void glUniform2(int location, IntBuffer value); /** *

Reference Page

@@ -1158,7 +1159,7 @@ public interface GL { * @param location the location of the uniform variable to be modified. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. */ - void glUniform2(int location, FloatBuffer value); + public void glUniform2(int location, FloatBuffer value); /** *

Reference Page

@@ -1169,7 +1170,7 @@ public interface GL { * @param v0 the uniform x value. * @param v1 the uniform y value. */ - void glUniform2f(int location, float v0, float v1); + public void glUniform2f(int location, float v0, float v1); /** *

Reference Page

@@ -1179,7 +1180,7 @@ public interface GL { * @param location the location of the uniform variable to be modified. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. */ - void glUniform3(int location, IntBuffer value); + public void glUniform3(int location, IntBuffer value); /** *

Reference Page

@@ -1189,7 +1190,7 @@ public interface GL { * @param location the location of the uniform variable to be modified. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. */ - void glUniform3(int location, FloatBuffer value); + public void glUniform3(int location, FloatBuffer value); /** *

Reference Page

@@ -1201,7 +1202,7 @@ public interface GL { * @param v1 the uniform y value. * @param v2 the uniform z value. */ - void glUniform3f(int location, float v0, float v1, float v2); + public void glUniform3f(int location, float v0, float v1, float v2); /** *

Reference Page

@@ -1211,7 +1212,7 @@ public interface GL { * @param location the location of the uniform variable to be modified. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. */ - void glUniform4(int location, FloatBuffer value); + public void glUniform4(int location, FloatBuffer value); /** *

Reference Page

@@ -1221,7 +1222,7 @@ public interface GL { * @param location the location of the uniform variable to be modified. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. */ - void glUniform4(int location, IntBuffer value); + public void glUniform4(int location, IntBuffer value); /** *

Reference Page

@@ -1234,7 +1235,7 @@ public interface GL { * @param v2 the uniform z value. * @param v3 the uniform w value. */ - void glUniform4f(int location, float v0, float v1, float v2, float v3); + public void glUniform4f(int location, float v0, float v1, float v2, float v3); /** *

Reference Page

@@ -1245,7 +1246,7 @@ public interface GL { * @param transpose whether to transpose the matrix as the values are loaded into the uniform variable. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. */ - void glUniformMatrix3(int location, boolean transpose, FloatBuffer value); + public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value); /** *

Reference Page

@@ -1256,7 +1257,7 @@ public interface GL { * @param transpose whether to transpose the matrix as the values are loaded into the uniform variable. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. */ - void glUniformMatrix4(int location, boolean transpose, FloatBuffer value); + public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value); /** *

Reference Page

@@ -1265,7 +1266,7 @@ public interface GL { * * @param program the program object whose executables are to be used as part of current rendering state. */ - void glUseProgram(int program); + public void glUseProgram(int program); /** *

Reference Page

@@ -1281,7 +1282,7 @@ public interface GL { * @param pointer the vertex attribute data or the offset of the first component of the first generic vertex attribute in the array in the data store of the buffer * currently bound to the {@link GL#GL_ARRAY_BUFFER ARRAY_BUFFER} target. The initial value is 0. */ - void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer); + public void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer); /** *

Reference Page

@@ -1297,5 +1298,5 @@ public interface GL { * @param width the viewport width. * @param height the viewport height. */ - void glViewport(int x, int y, int width, int height); -} + public void glViewport(int x, int y, int width, int height); +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java index 595f5ff85..dcf1d91eb 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java @@ -83,6 +83,46 @@ public interface GL3 extends GL2 { public static final int GL_RGB_INTEGER = 36248; public static final int GL_RGBA_INTEGER = 36249; + public static final int GL_UNIFORM_OFFSET = 0x8A3B; + + /** + * Accepted by the {@code target} parameters of BindBuffer, BufferData, BufferSubData, MapBuffer, UnmapBuffer, GetBufferSubData, and GetBufferPointerv. + */ + public static final int GL_UNIFORM_BUFFER = 0x8A11; + + /** + * Accepted by the {@code pname} parameter of GetActiveUniformBlockiv. + */ + public static final int GL_UNIFORM_BLOCK_BINDING = 0x8A3F; + public static final int GL_UNIFORM_BLOCK_DATA_SIZE = 0x8A40; + public static final int GL_UNIFORM_BLOCK_NAME_LENGTH = 0x8A41; + public static final int GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS = 0x8A42; + public static final int GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES = 0x8A43; + public static final int GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER = 0x8A44; + public static final int GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER = 0x8A45; + public static final int GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER = 0x8A46; + + /** + * Accepted by the <pname> parameter of GetBooleanv, GetIntegerv, + * GetFloatv, and GetDoublev: + */ + public static final int GL_MAX_VERTEX_UNIFORM_BLOCKS = 0x8A2B; + public static final int GL_MAX_GEOMETRY_UNIFORM_BLOCKS = 0x8A2C; + public static final int GL_MAX_FRAGMENT_UNIFORM_BLOCKS = 0x8A2D; + public static final int GL_MAX_COMBINED_UNIFORM_BLOCKS = 0x8A2E; + public static final int GL_MAX_UNIFORM_BUFFER_BINDINGS = 0x8A2F; + public static final int GL_MAX_UNIFORM_BLOCK_SIZE = 0x8A30; + public static final int GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS = 0x8A31; + public static final int GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS = 0x8A32; + public static final int GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS = 0x8A33; + public static final int GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 0x8A34; + + /** + * Accepted by the {@code target} parameters of BindBuffer, BufferData, BufferSubData, MapBuffer, UnmapBuffer, GetBufferSubData, GetBufferPointerv, + * BindBufferRange, BindBufferOffset and BindBufferBase. + */ + public static final int GL_TRANSFORM_FEEDBACK_BUFFER = 0x8C8E; + /** *

Reference Page

*

@@ -128,4 +168,47 @@ public interface GL3 extends GL2 { * @param index the index of the particular element being queried. */ public String glGetString(int name, int index); /// GL3+ + + + /** + *

Reference Page

+ * + * Retrieves the index of a named uniform block. + * + * @param program the name of a program containing the uniform block. + * @param uniformBlockName an array of characters to containing the name of the uniform block whose index to retrieve. + * @return the block index. + */ + public int glGetUniformBlockIndex(int program, String uniformBlockName); + + /** + *

Reference Page

+ * + * Binds a buffer object to an indexed buffer target. + * + * @param target the target of the bind operation. One of:
{@link #GL_TRANSFORM_FEEDBACK_BUFFER TRANSFORM_FEEDBACK_BUFFER}{@link #GL_UNIFORM_BUFFER UNIFORM_BUFFER}{@link GL4#GL_ATOMIC_COUNTER_BUFFER ATOMIC_COUNTER_BUFFER}{@link GL4#GL_SHADER_STORAGE_BUFFER SHADER_STORAGE_BUFFER}
+ * @param index the index of the binding point within the array specified by {@code target} + * @param buffer a buffer object to bind to the specified binding point + */ + public void glBindBufferBase(int target, int index, int buffer); + + /** + * Binding points for active uniform blocks are assigned using glUniformBlockBinding. Each of a program's active + * uniform blocks has a corresponding uniform buffer binding point. program is the name of a program object for + * which the command glLinkProgram has been issued in the past. + *

+ * If successful, glUniformBlockBinding specifies that program will use the data store of the buffer object bound + * to the binding point uniformBlockBinding to extract the values of the uniforms in the uniform block identified + * by uniformBlockIndex. + *

+ * When a program object is linked or re-linked, the uniform buffer object binding point assigned to each of its + * active uniform blocks is reset to zero. + * + * @param program The name of a program object containing the active uniform block whose binding to + * assign. + * @param uniformBlockIndex The index of the active uniform block within program whose binding to assign. + * @param uniformBlockBinding Specifies the binding point to which to bind the uniform block with index + * uniformBlockIndex within program. + */ + public void glUniformBlockBinding(int program, int uniformBlockIndex, int uniformBlockBinding); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java index 9afe4755b..821959aee 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java @@ -42,6 +42,32 @@ public interface GL4 extends GL3 { public static final int GL_TESS_EVALUATION_SHADER = 0x8E87; public static final int GL_PATCHES = 0xE; + /** + * Accepted by the {@code target} parameter of BindBufferBase and BindBufferRange. + */ + public static final int GL_ATOMIC_COUNTER_BUFFER = 0x92C0; + + /** + * Accepted by the {@code target} parameters of BindBuffer, BufferData, BufferSubData, MapBuffer, UnmapBuffer, GetBufferSubData, and GetBufferPointerv. + */ + public static final int GL_SHADER_STORAGE_BUFFER = 0x90D2; + public static final int GL_SHADER_STORAGE_BLOCK = 0x92E6; + + /** + * Accepted by the <pname> parameter of GetIntegerv, GetBooleanv, + * GetInteger64v, GetFloatv, and GetDoublev: + */ + public static final int GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS = 0x90D6; + public static final int GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS = 0x90D7; + public static final int GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS = 0x90D8; + public static final int GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS = 0x90D9; + public static final int GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS = 0x90DA; + public static final int GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS = 0x90DB; + public static final int GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS = 0x90DC; + public static final int GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS = 0x90DD; + public static final int GL_MAX_SHADER_STORAGE_BLOCK_SIZE = 0x90DE; + public static final int GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT = 0x90DF; + /** *

Reference Page

*

@@ -50,4 +76,28 @@ public interface GL4 extends GL3 { * @param count the new value for the parameter given by {@code pname} */ public void glPatchParameter(int count); + + /** + * Returns the unsigned integer index assigned to a resource named name in the interface type programInterface of + * program object program. + * + * @param program the name of a program object whose resources to query. + * @param programInterface a token identifying the interface within program containing the resource named name. + * @param name the name of the resource to query the index of. + * @return the index of a named resource within a program. + */ + public int glGetProgramResourceIndex(int program, int programInterface, String name); + + /** + * Cchanges the active shader storage block with an assigned index of storageBlockIndex in program object program. + * storageBlockIndex must be an active shader storage block index in program. storageBlockBinding must be less + * than the value of {@code #GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS}. If successful, glShaderStorageBlockBinding specifies + * that program will use the data store of the buffer object bound to the binding point storageBlockBinding to + * read and write the values of the buffer variables in the shader storage block identified by storageBlockIndex. + * + * @param program the name of a program object whose resources to query. + * @param storageBlockIndex The index storage block within the program. + * @param storageBlockBinding The index storage block binding to associate with the specified storage block. + */ + public void glShaderStorageBlockBinding(int program, int storageBlockIndex, int storageBlockBinding); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java index a945a1d85..3dfd2a8ca 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java @@ -83,6 +83,19 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 { return result; } + @Override + public int glGetUniformBlockIndex(final int program, final String uniformBlockName) { + final int result = gl3.glGetUniformBlockIndex(program, uniformBlockName); + checkError(); + return result; + } + + @Override + public void glBindBufferBase(final int target, final int index, final int buffer) { + gl3.glBindBufferBase(target, index, buffer); + checkError(); + } + @Override public void glDeleteVertexArrays(IntBuffer arrays) { gl3.glDeleteVertexArrays(arrays); @@ -95,8 +108,27 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 { checkError(); } + @Override + public int glGetProgramResourceIndex(int program, int programInterface, String name) { + final int result = gl4.glGetProgramResourceIndex(program, programInterface, name); + checkError(); + return result; + } + + @Override + public void glShaderStorageBlockBinding(int program, int storageBlockIndex, int storageBlockBinding) { + gl4.glShaderStorageBlockBinding(program, storageBlockIndex, storageBlockBinding); + checkError(); + } + public void glBlendEquationSeparate(int colorMode, int alphaMode) { gl.glBlendEquationSeparate(colorMode, alphaMode); checkError(); } + + @Override + public void glUniformBlockBinding(final int program, final int uniformBlockIndex, final int uniformBlockBinding) { + gl3.glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding); + checkError(); + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java index 21070227b..ca8546a67 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java @@ -196,6 +196,10 @@ public final class GLImageFormats { format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT); format(formatToGL, Format.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, halfFloatFormat); } + format(formatToGL, Format.R16F, GL3.GL_R16F, GL3.GL_RED, halfFloatFormat); + format(formatToGL, Format.R32F, GL3.GL_R32F, GL3.GL_RED, GL.GL_FLOAT); + format(formatToGL, Format.RG16F, GL3.GL_RG16F, GL3.GL_RG, halfFloatFormat); + format(formatToGL, Format.RG32F, GL3.GL_RG32F, GL3.GL_RG, GL.GL_FLOAT); format(formatToGL, Format.RGB16F, GLExt.GL_RGB16F_ARB, GL.GL_RGB, halfFloatFormat); format(formatToGL, Format.RGB32F, GLExt.GL_RGB32F_ARB, GL.GL_RGB, GL.GL_FLOAT); format(formatToGL, Format.RGBA16F, GLExt.GL_RGBA16F_ARB, GL.GL_RGBA, halfFloatFormat); @@ -220,7 +224,7 @@ public final class GLImageFormats { // NOTE: OpenGL ES 2.0 does not support DEPTH_COMPONENT as internal format -- fallback to 16-bit depth. if (caps.contains(Caps.OpenGLES20)) { - format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT16, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE); + format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT16, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT); } else { format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 9851f3418..372233dd6 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -33,6 +33,7 @@ package com.jme3.renderer.opengl; import com.jme3.material.RenderState; import com.jme3.material.RenderState.BlendFunc; +import com.jme3.material.RenderState.BlendMode; import com.jme3.material.RenderState.StencilOperation; import com.jme3.material.RenderState.TestFunction; import com.jme3.math.*; @@ -44,11 +45,9 @@ import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; -import com.jme3.shader.Attribute; -import com.jme3.shader.Shader; +import com.jme3.shader.*; import com.jme3.shader.Shader.ShaderSource; import com.jme3.shader.Shader.ShaderType; -import com.jme3.shader.Uniform; import com.jme3.texture.FrameBuffer; import com.jme3.texture.FrameBuffer.RenderBuffer; import com.jme3.texture.Image; @@ -60,17 +59,17 @@ import com.jme3.util.BufferUtils; import com.jme3.util.ListMap; import com.jme3.util.MipMapGenerator; import com.jme3.util.NativeObjectManager; -import java.nio.*; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.List; +import jme3tools.shader.ShaderDebug; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import jme3tools.shader.ShaderDebug; public final class GLRenderer implements Renderer { @@ -479,6 +478,26 @@ public final class GLRenderer implements Renderer { } } + if (hasExtension("GL_ARB_shader_storage_buffer_object")) { + caps.add(Caps.ShaderStorageBufferObject); + limits.put(Limits.ShaderStorageBufferObjectMaxBlockSize, getInteger(GL4.GL_MAX_SHADER_STORAGE_BLOCK_SIZE)); + limits.put(Limits.ShaderStorageBufferObjectMaxComputeBlocks, getInteger(GL4.GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS)); + limits.put(Limits.ShaderStorageBufferObjectMaxGeometryBlocks, getInteger(GL4.GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS)); + limits.put(Limits.ShaderStorageBufferObjectMaxFragmentBlocks, getInteger(GL4.GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS)); + limits.put(Limits.ShaderStorageBufferObjectMaxVertexBlocks, getInteger(GL4.GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS)); + limits.put(Limits.ShaderStorageBufferObjectMaxTessControlBlocks, getInteger(GL4.GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS)); + limits.put(Limits.ShaderStorageBufferObjectMaxTessEvaluationBlocks, getInteger(GL4.GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS)); + limits.put(Limits.ShaderStorageBufferObjectMaxCombineBlocks, getInteger(GL4.GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS)); + } + + if (hasExtension("GL_ARB_uniform_buffer_object")) { + caps.add(Caps.UniformBufferObject); + limits.put(Limits.UniformBufferObjectMaxBlockSize, getInteger(GL3.GL_MAX_UNIFORM_BLOCK_SIZE)); + limits.put(Limits.UniformBufferObjectMaxGeometryBlocks, getInteger(GL3.GL_MAX_GEOMETRY_UNIFORM_BLOCKS)); + limits.put(Limits.UniformBufferObjectMaxFragmentBlocks, getInteger(GL3.GL_MAX_FRAGMENT_UNIFORM_BLOCKS)); + limits.put(Limits.UniformBufferObjectMaxVertexBlocks, getInteger(GL3.GL_MAX_VERTEX_UNIFORM_BLOCKS)); + } + // Print context information logger.log(Level.INFO, "OpenGL Renderer Information\n" + " * Vendor: {0}\n" + @@ -743,68 +762,57 @@ public final class GLRenderer implements Renderer { context.cullMode = state.getFaceCullMode(); } - if (state.getBlendMode() != context.blendMode) { - if (state.getBlendMode() == RenderState.BlendMode.Off) { - gl.glDisable(GL.GL_BLEND); - } else { - if (context.blendMode == RenderState.BlendMode.Off) { - gl.glEnable(GL.GL_BLEND); - } - switch (state.getBlendMode()) { - case Off: - break; - case Additive: - gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE); - break; - case AlphaAdditive: - gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE); - break; - case Alpha: - gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); - break; - case PremultAlpha: - gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA); - break; - case Modulate: - gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_ZERO); - break; - case ModulateX2: - gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR); - break; - case Color: - case Screen: - gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR); - break; - case Exclusion: - gl.glBlendFunc(GL.GL_ONE_MINUS_DST_COLOR, GL.GL_ONE_MINUS_SRC_COLOR); - break; - case Custom: - gl.glBlendFuncSeparate( - convertBlendFunc(state.getCustomSfactorRGB()), - convertBlendFunc(state.getCustomDfactorRGB()), - convertBlendFunc(state.getCustomSfactorAlpha()), - convertBlendFunc(state.getCustomDfactorAlpha())); - break; - default: - throw new UnsupportedOperationException("Unrecognized blend mode: " - + state.getBlendMode()); - } - - if (state.getBlendEquation() != context.blendEquation || state.getBlendEquationAlpha() != context.blendEquationAlpha) { - int colorMode = convertBlendEquation(state.getBlendEquation()); - int alphaMode; - if (state.getBlendEquationAlpha() == RenderState.BlendEquationAlpha.InheritColor) { - alphaMode = colorMode; - } else { - alphaMode = convertBlendEquationAlpha(state.getBlendEquationAlpha()); - } - gl.glBlendEquationSeparate(colorMode, alphaMode); - context.blendEquation = state.getBlendEquation(); - context.blendEquationAlpha = state.getBlendEquationAlpha(); - } + // Always update the blend equations and factors when using custom blend mode. + if (state.getBlendMode() == BlendMode.Custom) { + changeBlendMode(BlendMode.Custom); + + blendFuncSeparate( + state.getCustomSfactorRGB(), + state.getCustomDfactorRGB(), + state.getCustomSfactorAlpha(), + state.getCustomDfactorAlpha()); + blendEquationSeparate(state.getBlendEquation(), state.getBlendEquationAlpha()); + + // Update the blend equations and factors only on a mode change for all the other (common) blend modes. + } else if (state.getBlendMode() != context.blendMode) { + changeBlendMode(state.getBlendMode()); + + switch (state.getBlendMode()) { + case Off: + break; + case Additive: + blendFunc(RenderState.BlendFunc.One, RenderState.BlendFunc.One); + break; + case AlphaAdditive: + blendFunc(RenderState.BlendFunc.Src_Alpha, RenderState.BlendFunc.One); + break; + case Alpha: + blendFunc(RenderState.BlendFunc.Src_Alpha, RenderState.BlendFunc.One_Minus_Src_Alpha); + break; + case PremultAlpha: + blendFunc(RenderState.BlendFunc.One, RenderState.BlendFunc.One_Minus_Src_Alpha); + break; + case Modulate: + blendFunc(RenderState.BlendFunc.Dst_Color, RenderState.BlendFunc.Zero); + break; + case ModulateX2: + blendFunc(RenderState.BlendFunc.Dst_Color, RenderState.BlendFunc.Src_Color); + break; + case Color: + case Screen: + blendFunc(RenderState.BlendFunc.One, RenderState.BlendFunc.One_Minus_Src_Color); + break; + case Exclusion: + blendFunc(RenderState.BlendFunc.One_Minus_Dst_Color, RenderState.BlendFunc.One_Minus_Src_Color); + break; + default: + throw new UnsupportedOperationException("Unrecognized blend mode: " + + state.getBlendMode()); } - context.blendMode = state.getBlendMode(); + // All of the common modes requires the ADD equation. + // (This might change in the future?) + blendEquationSeparate(RenderState.BlendEquation.Add, RenderState.BlendEquationAlpha.InheritColor); } if (context.stencilTest != state.isStencilTest() @@ -852,6 +860,65 @@ public final class GLRenderer implements Renderer { } } + private void changeBlendMode(RenderState.BlendMode blendMode) { + if (blendMode != context.blendMode) { + if (blendMode == RenderState.BlendMode.Off) { + gl.glDisable(GL.GL_BLEND); + } else if (context.blendMode == RenderState.BlendMode.Off) { + gl.glEnable(GL.GL_BLEND); + } + + context.blendMode = blendMode; + } + } + + private void blendEquationSeparate(RenderState.BlendEquation blendEquation, RenderState.BlendEquationAlpha blendEquationAlpha) { + if (blendEquation != context.blendEquation || blendEquationAlpha != context.blendEquationAlpha) { + int glBlendEquation = convertBlendEquation(blendEquation); + int glBlendEquationAlpha = blendEquationAlpha == RenderState.BlendEquationAlpha.InheritColor + ? glBlendEquation + : convertBlendEquationAlpha(blendEquationAlpha); + gl.glBlendEquationSeparate(glBlendEquation, glBlendEquationAlpha); + context.blendEquation = blendEquation; + context.blendEquationAlpha = blendEquationAlpha; + } + } + + private void blendFunc(RenderState.BlendFunc sfactor, RenderState.BlendFunc dfactor) { + if (sfactor != context.sfactorRGB + || dfactor != context.dfactorRGB + || sfactor != context.sfactorAlpha + || dfactor != context.dfactorAlpha) { + + gl.glBlendFunc( + convertBlendFunc(sfactor), + convertBlendFunc(dfactor)); + context.sfactorRGB = sfactor; + context.dfactorRGB = dfactor; + context.sfactorAlpha = sfactor; + context.dfactorAlpha = dfactor; + } + } + + private void blendFuncSeparate(RenderState.BlendFunc sfactorRGB, RenderState.BlendFunc dfactorRGB, + RenderState.BlendFunc sfactorAlpha, RenderState.BlendFunc dfactorAlpha) { + if (sfactorRGB != context.sfactorRGB + || dfactorRGB != context.dfactorRGB + || sfactorAlpha != context.sfactorAlpha + || dfactorAlpha != context.dfactorAlpha) { + + gl.glBlendFuncSeparate( + convertBlendFunc(sfactorRGB), + convertBlendFunc(dfactorRGB), + convertBlendFunc(sfactorAlpha), + convertBlendFunc(dfactorAlpha)); + context.sfactorRGB = sfactorRGB; + context.dfactorRGB = dfactorRGB; + context.sfactorAlpha = sfactorAlpha; + context.dfactorAlpha = dfactorAlpha; + } + } + private int convertBlendEquation(RenderState.BlendEquation blendEquation) { switch (blendEquation) { case Add: @@ -1001,12 +1068,25 @@ public final class GLRenderer implements Renderer { } } + @Override public void postFrame() { objManager.deleteUnused(this); OpenCLObjectManager.getInstance().deleteUnusedObjects(); gl.resetStats(); } + protected void bindProgram(Shader shader) { + int shaderId = shader.getId(); + if (context.boundShaderProgram != shaderId) { + gl.glUseProgram(shaderId); + statistics.onShaderUse(shader, true); + context.boundShader = shader; + context.boundShaderProgram = shaderId; + } else { + statistics.onShaderUse(shader, false); + } + } + /*********************************************************************\ |* Shaders *| \*********************************************************************/ @@ -1021,18 +1101,6 @@ public final class GLRenderer implements Renderer { } } - protected void bindProgram(Shader shader) { - int shaderId = shader.getId(); - if (context.boundShaderProgram != shaderId) { - gl.glUseProgram(shaderId); - statistics.onShaderUse(shader, true); - context.boundShader = shader; - context.boundShaderProgram = shaderId; - } else { - statistics.onShaderUse(shader, false); - } - } - protected void updateUniform(Shader shader, Uniform uniform) { int shaderId = shader.getId(); @@ -1138,6 +1206,58 @@ public final class GLRenderer implements Renderer { } } + /** + * Updates the buffer block for the shader. + * + * @param shader the shader. + * @param bufferBlock the storage block. + */ + protected void updateShaderBufferBlock(final Shader shader, final ShaderBufferBlock bufferBlock) { + + assert bufferBlock.getName() != null; + assert shader.getId() > 0; + + final BufferObject bufferObject = bufferBlock.getBufferObject(); + if (bufferObject.getUniqueId() == -1 || bufferObject.isUpdateNeeded()) { + updateBufferData(bufferObject); + } + + if (!bufferBlock.isUpdateNeeded()) { + return; + } + + bindProgram(shader); + + final int shaderId = shader.getId(); + final BufferObject.BufferType bufferType = bufferObject.getBufferType(); + + bindBuffer(bufferBlock, bufferObject, shaderId, bufferType); + + bufferBlock.clearUpdateNeeded(); + } + + private void bindBuffer(final ShaderBufferBlock bufferBlock, final BufferObject bufferObject, final int shaderId, + final BufferObject.BufferType bufferType) { + + switch (bufferType) { + case UniformBufferObject: { + final int blockIndex = gl3.glGetUniformBlockIndex(shaderId, bufferBlock.getName()); + gl3.glBindBufferBase(GL3.GL_UNIFORM_BUFFER, bufferObject.getBinding(), bufferObject.getId()); + gl3.glUniformBlockBinding(GL3.GL_UNIFORM_BUFFER, blockIndex, bufferObject.getBinding()); + break; + } + case ShaderStorageBufferObject: { + final int blockIndex = gl4.glGetProgramResourceIndex(shaderId, GL4.GL_SHADER_STORAGE_BLOCK, bufferBlock.getName()); + gl4.glShaderStorageBlockBinding(shaderId, blockIndex, bufferObject.getBinding()); + gl4.glBindBufferBase(GL4.GL_SHADER_STORAGE_BUFFER, bufferObject.getBinding(), bufferObject.getId()); + break; + } + default: { + throw new IllegalArgumentException("Doesn't support binding of " + bufferType); + } + } + } + protected void updateShaderUniforms(Shader shader) { ListMap uniforms = shader.getUniformMap(); for (int i = 0; i < uniforms.size(); i++) { @@ -1148,6 +1268,18 @@ public final class GLRenderer implements Renderer { } } + /** + * Updates all shader's buffer blocks. + * + * @param shader the shader. + */ + protected void updateShaderBufferBlocks(final Shader shader) { + final ListMap bufferBlocks = shader.getBufferBlockMap(); + for (int i = 0; i < bufferBlocks.size(); i++) { + updateShaderBufferBlock(shader, bufferBlocks.getValue(i)); + } + } + protected void resetUniformLocations(Shader shader) { ListMap uniforms = shader.getUniformMap(); for (int i = 0; i < uniforms.size(); i++) { @@ -1195,6 +1327,7 @@ public final class GLRenderer implements Renderer { + "Only GLSL 1.00 shaders are supported."); } + boolean insertPrecision = false; // Upload shader source. // Merge the defines and source code. stringBuf.setLength(0); @@ -1214,7 +1347,7 @@ public final class GLRenderer implements Renderer { if (source.getType() == ShaderType.Fragment) { // GLES2 requires precision qualifier. - stringBuf.append("precision mediump float;\n"); + insertPrecision = true; } } else { // version 100 does not exist in desktop GLSL. @@ -1233,6 +1366,14 @@ public final class GLRenderer implements Renderer { stringBuf.append(source.getDefines()); stringBuf.append(source.getSource()); + if(insertPrecision){ + // precision token is not a preprocessor dirrective therefore it must be placed after #extension tokens to avoid + // Error P0001: Extension directive must occur before any non-preprocessor tokens + int idx = stringBuf.lastIndexOf("#extension"); + idx = stringBuf.indexOf("\n", idx); + stringBuf.insert(idx + 1, "precision mediump float;\n"); + } + intBuf1.clear(); intBuf1.put(0, stringBuf.length()); gl.glShaderSource(id, new String[]{ stringBuf.toString() }, intBuf1); @@ -1366,6 +1507,7 @@ public final class GLRenderer implements Renderer { assert shader.getId() > 0; updateShaderUniforms(shader); + updateShaderBufferBlocks(shader); bindProgram(shader); } } @@ -2454,6 +2596,58 @@ public final class GLRenderer implements Renderer { vb.clearUpdateNeeded(); } + @Override + public void updateBufferData(final BufferObject bo) { + + int maxSize = Integer.MAX_VALUE; + + final BufferObject.BufferType bufferType = bo.getBufferType(); + + if (!caps.contains(bufferType.getRequiredCaps())) { + throw new IllegalArgumentException("The current video hardware doesn't support " + bufferType); + } + + final ByteBuffer data = bo.computeData(maxSize); + if (data == null) { + throw new IllegalArgumentException("Can't upload BO without data."); + } + + int bufferId = bo.getId(); + if (bufferId == -1) { + + // create buffer + intBuf1.clear(); + gl.glGenBuffers(intBuf1); + bufferId = intBuf1.get(0); + + bo.setId(bufferId); + + objManager.registerObject(bo); + } + + data.rewind(); + + switch (bufferType) { + case UniformBufferObject: { + gl3.glBindBuffer(GL3.GL_UNIFORM_BUFFER, bufferId); + gl3.glBufferData(GL4.GL_UNIFORM_BUFFER, data, GL3.GL_DYNAMIC_DRAW); + gl3.glBindBuffer(GL4.GL_UNIFORM_BUFFER, 0); + break; + } + case ShaderStorageBufferObject: { + gl4.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, bufferId); + gl4.glBufferData(GL4.GL_SHADER_STORAGE_BUFFER, data, GL4.GL_DYNAMIC_COPY); + gl4.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, 0); + break; + } + default: { + throw new IllegalArgumentException("Doesn't support binding of " + bufferType); + } + } + + bo.clearUpdateNeeded(); + } + public void deleteBuffer(VertexBuffer vb) { int bufId = vb.getId(); if (bufId != -1) { @@ -2467,6 +2661,23 @@ public final class GLRenderer implements Renderer { } } + @Override + public void deleteBuffer(final BufferObject bo) { + + int bufferId = bo.getId(); + if (bufferId == -1) { + return; + } + + intBuf1.clear(); + intBuf1.put(bufferId); + intBuf1.flip(); + + gl.glDeleteBuffers(intBuf1); + + bo.resetObject(); + } + public void clearVertexAttribs() { IDList attribList = context.attribIndexList; for (int i = 0; i < attribList.oldLen; i++) { 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 78ea0c349..437036222 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -43,6 +43,7 @@ import com.jme3.material.Material; import com.jme3.math.Matrix4f; import com.jme3.renderer.Camera; import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.MorphTarget; import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.IdentityCloneFunction; @@ -86,6 +87,16 @@ public class Geometry extends Spatial { */ protected int startIndex = -1; + /** + * Morph state variable for morph animation + */ + private float[] morphState; + private boolean dirtyMorph = true; + // a Morph target that will be used to merge all targets that + // can't be handled on the cpu on each frame. + private MorphTarget fallbackMorphTarget; + private int nbSimultaneousGPUMorph = -1; + /** * Serialization only. Do not use. */ @@ -248,7 +259,7 @@ public class Geometry extends Spatial { @Override public void setMaterial(Material material) { this.material = material; - + nbSimultaneousGPUMorph = -1; if (isGrouped()) { groupNode.onMaterialChange(this); } @@ -576,6 +587,80 @@ public class Geometry extends Spatial { this.material = cloner.clone(material); } + public void setMorphState(float[] state) { + if (mesh == null || mesh.getMorphTargets().length == 0){ + return; + } + + int nbMorphTargets = mesh.getMorphTargets().length; + + if (morphState == null) { + morphState = new float[nbMorphTargets]; + } + System.arraycopy(state, 0, morphState, 0, morphState.length); + this.dirtyMorph = true; + } + + /** + * returns true if the morph state has changed on the last frame. + * @return + */ + public boolean isDirtyMorph() { + return dirtyMorph; + } + + /** + * Seting this to true will stop this geometry morph buffer to be updated, + * unless the morph state changes + * @param dirtyMorph + */ + public void setDirtyMorph(boolean dirtyMorph) { + this.dirtyMorph = dirtyMorph; + } + + /** + * returns the morph state of this Geometry. + * Used internally by the MorphControl. + * @return + */ + public float[] getMorphState() { + if (morphState == null) { + morphState = new float[mesh.getMorphTargets().length]; + } + return morphState; + } + + /** + * Return the number of morph targets that can be handled on the GPU simultaneously for this geometry. + * Note that it depends on the material set on this geometry. + * This number is computed and set by the MorphControl, so it might be available only after the first frame. + * Else it's set to -1. + * @return the number of simultaneous morph targets handled on the GPU + */ + public int getNbSimultaneousGPUMorph() { + return nbSimultaneousGPUMorph; + } + + /** + * Sets the number of morph targets that can be handled on the GPU simultaneously for this geometry. + * Note that it depends on the material set on this geometry. + * This number is computed and set by the MorphControl, so it might be available only after the first frame. + * Else it's set to -1. + * WARNING: setting this manually might crash the shader compilation if set too high. Do it at your own risk. + * @param nbSimultaneousGPUMorph the number of simultaneous morph targets to be handled on the GPU. + */ + public void setNbSimultaneousGPUMorph(int nbSimultaneousGPUMorph) { + this.nbSimultaneousGPUMorph = nbSimultaneousGPUMorph; + } + + public MorphTarget getFallbackMorphTarget() { + return fallbackMorphTarget; + } + + public void setFallbackMorphTarget(MorphTarget fallbackMorphTarget) { + this.fallbackMorphTarget = fallbackMorphTarget; + } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java index e07c2cfa2..8dececf26 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -39,24 +39,18 @@ import com.jme3.collision.bih.BIHTree; import com.jme3.export.*; import com.jme3.material.Material; import com.jme3.material.RenderState; -import com.jme3.math.Matrix4f; -import com.jme3.math.Triangle; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.VertexBuffer.Format; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.math.*; +import com.jme3.scene.VertexBuffer.*; import com.jme3.scene.mesh.*; -import com.jme3.util.BufferUtils; -import com.jme3.util.IntMap; +import com.jme3.util.*; import com.jme3.util.IntMap.Entry; -import com.jme3.util.SafeArrayList; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.nio.*; import java.util.ArrayList; +import java.util.Arrays; /** * Mesh is used to store rendering data. @@ -171,8 +165,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { private CollisionData collisionTree = null; - private SafeArrayList buffersList = new SafeArrayList(VertexBuffer.class); - private IntMap buffers = new IntMap(); + private SafeArrayList buffersList = new SafeArrayList<>(VertexBuffer.class); + private IntMap buffers = new IntMap<>(); private VertexBuffer[] lodLevels; private float pointSize = 1; private float lineWidth = 1; @@ -190,6 +184,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { private Mode mode = Mode.Triangles; + private SafeArrayList morphTargets; + /** * Creates a new mesh with no {@link VertexBuffer vertex buffers}. */ @@ -210,7 +206,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { clone.meshBound = meshBound.clone(); clone.collisionTree = collisionTree != null ? collisionTree : null; clone.buffers = buffers.clone(); - clone.buffersList = new SafeArrayList(VertexBuffer.class,buffersList); + clone.buffersList = new SafeArrayList<>(VertexBuffer.class, buffersList); clone.vertexArrayID = -1; if (elementLengths != null) { clone.elementLengths = elementLengths.clone(); @@ -240,8 +236,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { //clone.collisionTree = collisionTree != null ? collisionTree : null; clone.collisionTree = null; // it will get re-generated in any case - clone.buffers = new IntMap(); - clone.buffersList = new SafeArrayList(VertexBuffer.class); + clone.buffers = new IntMap<>(); + clone.buffersList = new SafeArrayList<>(VertexBuffer.class); for (VertexBuffer vb : buffersList.getArray()){ VertexBuffer bufClone = vb.clone(); clone.buffers.put(vb.getBufferType().ordinal(), bufClone); @@ -704,7 +700,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { */ @Deprecated public void setInterleaved(){ - ArrayList vbs = new ArrayList(); + ArrayList vbs = new ArrayList<>(); vbs.addAll(buffersList); // ArrayList vbs = new ArrayList(buffers.values()); @@ -827,8 +823,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { * {@link #setInterleaved() interleaved} format. */ public void updateCounts(){ - if (getBuffer(Type.InterleavedData) != null) + if (getBuffer(Type.InterleavedData) != null) { throw new IllegalStateException("Should update counts before interleave"); + } VertexBuffer pb = getBuffer(Type.Position); VertexBuffer ib = getBuffer(Type.Index); @@ -851,11 +848,13 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { */ public int getTriangleCount(int lod){ if (lodLevels != null){ - if (lod < 0) + if (lod < 0) { throw new IllegalArgumentException("LOD level cannot be < 0"); + } - if (lod >= lodLevels.length) - throw new IllegalArgumentException("LOD level "+lod+" does not exist!"); + if (lod >= lodLevels.length) { + throw new IllegalArgumentException("LOD level " + lod + " does not exist!"); + } return computeNumElements(lodLevels[lod].getData().limit()); }else if (lod == 0){ @@ -975,8 +974,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { * Sets the mesh's VAO ID. Internal use only. */ public void setId(int id){ - if (vertexArrayID != -1) + if (vertexArrayID != -1) { throw new IllegalStateException("ID has already been set."); + } vertexArrayID = id; } @@ -1044,8 +1044,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { * @throws IllegalArgumentException If the buffer type is already set */ public void setBuffer(VertexBuffer vb){ - if (buffers.containsKey(vb.getBufferType().ordinal())) - throw new IllegalArgumentException("Buffer type already set: "+vb.getBufferType()); + if (buffers.containsKey(vb.getBufferType().ordinal())) { + throw new IllegalArgumentException("Buffer type already set: " + vb.getBufferType()); + } buffers.put(vb.getBufferType().ordinal(), vb); buffersList.add(vb); @@ -1158,8 +1159,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { */ public FloatBuffer getFloatBuffer(Type type) { VertexBuffer vb = getBuffer(type); - if (vb == null) + if (vb == null) { return null; + } return (FloatBuffer) vb.getData(); } @@ -1173,8 +1175,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { */ public ShortBuffer getShortBuffer(Type type) { VertexBuffer vb = getBuffer(type); - if (vb == null) + if (vb == null) { return null; + } return (ShortBuffer) vb.getData(); } @@ -1186,8 +1189,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { * @return A virtual or wrapped index buffer to read the data as a list */ public IndexBuffer getIndicesAsList(){ - if (mode == Mode.Hybrid) + if (mode == Mode.Hybrid) { throw new UnsupportedOperationException("Hybrid mode not supported"); + } IndexBuffer ib = getIndexBuffer(); if (ib != null){ @@ -1216,8 +1220,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { */ public IndexBuffer getIndexBuffer() { VertexBuffer vb = getBuffer(Type.Index); - if (vb == null) + if (vb == null) { return null; + } return IndexBuffer.wrapIndexBuffer(vb.getData()); } @@ -1240,8 +1245,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { IndexBuffer indexBuf = getIndexBuffer(); int numIndices = indexBuf.size(); - IntMap oldIndicesToNewIndices = new IntMap(numIndices); - ArrayList newIndicesToOldIndices = new ArrayList(); + IntMap oldIndicesToNewIndices = new IntMap<>(numIndices); + ArrayList newIndicesToOldIndices = new ArrayList<>(); int newIndex = 0; for (int i = 0; i < numIndices; i++) { @@ -1352,14 +1357,17 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { */ public void scaleTextureCoordinates(Vector2f scaleFactor){ VertexBuffer tc = getBuffer(Type.TexCoord); - if (tc == null) + if (tc == null) { throw new IllegalStateException("The mesh has no texture coordinates"); + } - if (tc.getFormat() != VertexBuffer.Format.Float) + if (tc.getFormat() != VertexBuffer.Format.Float) { throw new UnsupportedOperationException("Only float texture coord format is supported"); + } - if (tc.getNumComponents() != 2) + if (tc.getNumComponents() != 2) { throw new UnsupportedOperationException("Only 2D texture coords are supported"); + } FloatBuffer fb = (FloatBuffer) tc.getData(); fb.clear(); @@ -1446,13 +1454,23 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { getBuffer(Type.HWBoneIndex) != null; } + /** + * @deprecated use isAnimatedByJoint + * @param boneIndex + * @return + */ + @Deprecated + public boolean isAnimatedByBone(int boneIndex) { + return isAnimatedByJoint(boneIndex); + } + /** * Test whether the specified bone animates this mesh. * - * @param boneIndex the bone's index in its skeleton + * @param jointIndex the bone's index in its skeleton * @return true if the specified bone animates this mesh, otherwise false */ - public boolean isAnimatedByBone(int boneIndex) { + public boolean isAnimatedByJoint(int jointIndex) { VertexBuffer biBuf = getBuffer(VertexBuffer.Type.BoneIndex); VertexBuffer wBuf = getBuffer(VertexBuffer.Type.BoneWeight); if (biBuf == null || wBuf == null) { @@ -1472,7 +1490,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { /* * Test each vertex to determine whether the bone affects it. */ - int biByte = boneIndex; + int biByte = jointIndex; for (int vIndex = 0; vIndex < numVertices; vIndex++) { for (int wIndex = 0; wIndex < 4; wIndex++) { int bIndex = boneIndexBuffer.get(); @@ -1501,16 +1519,26 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { return patchVertexCount; } + + public void addMorphTarget(MorphTarget target) { + if (morphTargets == null) { + morphTargets = new SafeArrayList<>(MorphTarget.class); + } + morphTargets.add(target); + } + + public MorphTarget[] getMorphTargets() { + return morphTargets.getArray(); + } + + public boolean hasMorphTargets() { + return morphTargets != null && !morphTargets.isEmpty(); + } + + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule out = ex.getCapsule(this); -// HashMap map = new HashMap(); -// for (Entry buf : buffers){ -// if (buf.getValue() != null) -// map.put(buf.getKey()+"a", buf.getValue()); -// } -// out.writeStringSavableMap(map, "buffers", null); - out.write(meshBound, "modelBound", null); out.write(vertCount, "vertCount", -1); out.write(elementCount, "elementCount", -1); @@ -1545,8 +1573,12 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { } out.write(lodLevels, "lodLevels", null); + if (morphTargets != null) { + out.writeSavableArrayList(new ArrayList(morphTargets), "morphTargets", null); + } } + @Override public void read(JmeImporter im) throws IOException { InputCapsule in = im.getCapsule(this); meshBound = (BoundingVolume) in.readSavable("modelBound", null); @@ -1583,6 +1615,11 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { lodLevels = new VertexBuffer[lodLevelsSavable.length]; System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length); } + + ArrayList l = in.readSavableArrayList("morphTargets", null); + if (l != null) { + morphTargets = new SafeArrayList(MorphTarget.class, l); + } } } diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index c36b5f347..c44db734b 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -31,6 +31,7 @@ */ package com.jme3.scene; +import com.jme3.anim.util.HasLocalTransform; import com.jme3.asset.AssetKey; import com.jme3.asset.CloneableSmartAsset; import com.jme3.bounding.BoundingVolume; @@ -67,7 +68,7 @@ import java.util.logging.Logger; * @author Joshua Slack * @version $Revision: 4075 $, $Data$ */ -public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable { +public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable, HasLocalTransform { private static final Logger logger = Logger.getLogger(Spatial.class.getName()); @@ -1792,4 +1793,4 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab } protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue); -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java index 70d40a64c..bb5d70afb 100644 --- a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java @@ -212,7 +212,42 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { * Format should be {@link Format#Float} and number of components * should be 16. */ - InstanceData + InstanceData, + + /** + * Morph animations targets. + * Supports up tp 14 morph target buffers at the same time + * Limited due to the limited number of attributes you can bind to a vertex shader usually 16 + *

+ * MorphTarget buffers are either POSITION, NORMAL or TANGENT buffers. + * So we can support up to + * 14 simultaneous POSITION targets + * 7 simultaneous POSITION and NORMAL targets + * 4 simultaneous POSTION, NORMAL and TANGENT targets. + *

+ * Note that the MorphControl will find how many buffers can be supported for each mesh/material combination. + * Note that all buffers have 3 components (Vector3f) even the Tangent buffer that + * does not contain the w (handedness) component that will not be interpolated for morph animation. + *

+ * Note that those buffers contain the difference between the base buffer (POSITION, NORMAL or TANGENT) and the target value + * So that you can interpolate with a MADD operation in the vertex shader + * position = weight * diffPosition + basePosition; + */ + MorphTarget0, + MorphTarget1, + MorphTarget2, + MorphTarget3, + MorphTarget4, + MorphTarget5, + MorphTarget6, + MorphTarget7, + MorphTarget8, + MorphTarget9, + MorphTarget10, + MorphTarget11, + MorphTarget12, + MorphTarget13, + } /** @@ -241,7 +276,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { * Mesh data is not sent to GPU at all. It is only * used by the CPU. */ - CpuOnly; + CpuOnly } /** @@ -606,9 +641,13 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { * @return The total number of data elements in the data buffer. */ public int getNumElements(){ + if( data == null ) { + return 0; + } int elements = data.limit() / components; - if (format == Format.Half) + if (format == Format.Half) { elements /= 2; + } return elements; } @@ -639,14 +678,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { * argument. */ public void setupData(Usage usage, int components, Format format, Buffer data){ - if (id != -1) + if (id != -1) { throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again."); + } - if (usage == null || format == null || data == null) + if (usage == null || format == null || data == null) { throw new IllegalArgumentException("None of the arguments can be null"); - - if (data.isReadOnly()) - throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." ); + } + + if (data.isReadOnly()) { + throw new IllegalArgumentException("VertexBuffer data cannot be read-only."); + } if (bufType != Type.InstanceData) { if (components < 1 || components > 4) { @@ -717,11 +759,13 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { * Converts single floating-point data to {@link Format#Half half} floating-point data. */ public void convertToHalf(){ - if (id != -1) + if (id != -1) { throw new UnsupportedOperationException("Data has already been sent."); + } - if (format != Format.Float) + if (format != Format.Float) { throw new IllegalStateException("Format must be float!"); + } int numElements = data.limit() / components; format = Format.Half; @@ -910,8 +954,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { * match. */ public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){ - if (outVb.format != format || outVb.components != components) + if (outVb.format != format || outVb.components != components) { throw new IllegalArgumentException("Buffer format mismatch. Cannot copy"); + } int inPos = inIndex * components; int outPos = outIndex * components; @@ -978,8 +1023,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { * of elements with the given number of components in each element. */ public static Buffer createBuffer(Format format, int components, int numElements){ - if (components < 1 || components > 4) + if (components < 1 || components > 4) { throw new IllegalArgumentException("Num components must be between 1 and 4"); + } int total = numElements * components; diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java b/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java index 4f5364753..7fabbc503 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java @@ -42,29 +42,39 @@ import java.nio.FloatBuffer; public class WireFrustum extends Mesh { public WireFrustum(Vector3f[] points){ + initGeom(this, points); + } + + public static Mesh makeFrustum(Vector3f[] points){ + Mesh m = new Mesh(); + initGeom(m, points); + return m; + } + + private static void initGeom(Mesh m, Vector3f[] points) { if (points != null) - setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points)); + m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points)); - setBuffer(Type.Index, 2, + m.setBuffer(Type.Index, 2, new short[]{ - 0, 1, - 1, 2, - 2, 3, - 3, 0, + 0, 1, + 1, 2, + 2, 3, + 3, 0, - 4, 5, - 5, 6, - 6, 7, - 7, 4, + 4, 5, + 5, 6, + 6, 7, + 7, 4, - 0, 4, - 1, 5, - 2, 6, - 3, 7, + 0, 4, + 1, 5, + 2, 6, + 3, 7, } ); - getBuffer(Type.Index).setUsage(Usage.Static); - setMode(Mode.Lines); + m.getBuffer(Type.Index).setUsage(Usage.Static); + m.setMode(Mode.Lines); } public void update(Vector3f[] points){ diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java new file mode 100644 index 000000000..a3618b444 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java @@ -0,0 +1,208 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.scene.debug.custom; + +import com.jme3.anim.*; +import com.jme3.app.Application; +import com.jme3.app.state.BaseAppState; +import com.jme3.collision.CollisionResults; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.*; +import com.jme3.light.DirectionalLight; +import com.jme3.math.*; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.*; + +import java.util.*; + +/** + * @author Nehon + */ +public class ArmatureDebugAppState extends BaseAppState { + + public static final float CLICK_MAX_DELAY = 0.2f; + private Node debugNode = new Node("debugNode"); + private Map armatures = new HashMap<>(); + private Map selectedBones = new HashMap<>(); + private Application app; + private boolean displayAllJoints = false; + private float clickDelay = -1; + Vector3f tmp = new Vector3f(); + Vector3f tmp2 = new Vector3f(); + ViewPort vp; + + @Override + protected void initialize(Application app) { + vp = app.getRenderManager().createMainView("debug", app.getCamera()); + vp.attachScene(debugNode); + vp.setClearDepth(true); + this.app = app; + for (ArmatureDebugger armatureDebugger : armatures.values()) { + armatureDebugger.initialize(app.getAssetManager(), app.getCamera()); + } + app.getInputManager().addListener(actionListener, "shoot", "toggleJoints"); + app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); + app.getInputManager().addMapping("toggleJoints", new KeyTrigger(KeyInput.KEY_F10)); + + debugNode.addLight(new DirectionalLight(new Vector3f(-1f, -1f, -1f).normalizeLocal())); + + debugNode.addLight(new DirectionalLight(new Vector3f(1f, 1f, 1f).normalizeLocal(), new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f))); + vp.setEnabled(false); + } + + @Override + protected void cleanup(Application app) { + + } + + @Override + protected void onEnable() { + vp.setEnabled(true); + } + + @Override + protected void onDisable() { + vp.setEnabled(false); + } + + @Override + public void update(float tpf) { + if (clickDelay > -1) { + clickDelay += tpf; + } + debugNode.updateLogicalState(tpf); + debugNode.updateGeometricState(); + + } + + public ArmatureDebugger addArmatureFrom(SkinningControl skinningControl) { + Armature armature = skinningControl.getArmature(); + Spatial forSpatial = skinningControl.getSpatial(); + return addArmatureFrom(armature, forSpatial); + } + + public ArmatureDebugger addArmatureFrom(Armature armature, Spatial forSpatial) { + + ArmatureDebugger ad = armatures.get(armature); + if(ad != null){ + return ad; + } + + JointInfoVisitor visitor = new JointInfoVisitor(armature); + forSpatial.depthFirstTraversal(visitor); + + ad = new ArmatureDebugger(forSpatial.getName() + "_Armature", armature, visitor.deformingJoints); + ad.setLocalTransform(forSpatial.getWorldTransform()); + if (forSpatial instanceof Node) { + List geoms = new ArrayList<>(); + findGeoms((Node) forSpatial, geoms); + if (geoms.size() == 1) { + ad.setLocalTransform(geoms.get(0).getWorldTransform()); + } + } + armatures.put(armature, ad); + debugNode.attachChild(ad); + if (isInitialized()) { + ad.initialize(app.getAssetManager(), app.getCamera()); + } + return ad; + } + + private void findGeoms(Node node, List geoms) { + for (Spatial spatial : node.getChildren()) { + if (spatial instanceof Geometry) { + geoms.add((Geometry) spatial); + } else if (spatial instanceof Node) { + findGeoms((Node) spatial, geoms); + } + } + } + + private ActionListener actionListener = new ActionListener() { + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("shoot") && isPressed) { + clickDelay = 0; + } + if (name.equals("shoot") && !isPressed && clickDelay < CLICK_MAX_DELAY) { + Vector2f click2d = app.getInputManager().getCursorPosition(); + CollisionResults results = new CollisionResults(); + + Vector3f click3d = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f, tmp); + Vector3f dir = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f, tmp2).subtractLocal(click3d); + Ray ray = new Ray(click3d, dir); + debugNode.collideWith(ray, results); + + if (results.size() == 0) { + for (ArmatureDebugger ad : armatures.values()) { + ad.select(null); + } + return; + } + + // The closest result is the target that the player picked: + Geometry target = results.getClosestCollision().getGeometry(); + for (ArmatureDebugger ad : armatures.values()) { + Joint selectedjoint = ad.select(target); + if (selectedjoint != null) { + selectedBones.put(ad.getArmature(), selectedjoint); + System.err.println("-----------------------"); + System.err.println("Selected Joint : " + selectedjoint.getName() + " in armature " + ad.getName()); + System.err.println("Root Bone : " + (selectedjoint.getParent() == null)); + System.err.println("-----------------------"); + System.err.println("Local translation: " + selectedjoint.getLocalTranslation()); + System.err.println("Local rotation: " + selectedjoint.getLocalRotation()); + System.err.println("Local scale: " + selectedjoint.getLocalScale()); + System.err.println("---"); + System.err.println("Model translation: " + selectedjoint.getModelTransform().getTranslation()); + System.err.println("Model rotation: " + selectedjoint.getModelTransform().getRotation()); + System.err.println("Model scale: " + selectedjoint.getModelTransform().getScale()); + System.err.println("---"); + System.err.println("Bind inverse Transform: "); + System.err.println(selectedjoint.getInverseModelBindMatrix()); + return; + } + } + } + if (name.equals("toggleJoints") && isPressed) { + displayAllJoints = !displayAllJoints; + for (ArmatureDebugger ad : armatures.values()) { + ad.displayNonDeformingJoint(displayAllJoints); + } + } + } + }; + +// public Map getSelectedBones() { +// return selectedBones; +// } + + public Node getDebugNode() { + return debugNode; + } + + public void setDebugNode(Node debugNode) { + this.debugNode = debugNode; + } + + private class JointInfoVisitor extends SceneGraphVisitorAdapter { + + List deformingJoints = new ArrayList<>(); + Armature armature; + + public JointInfoVisitor(Armature armature) { + this.armature = armature; + } + + @Override + public void visit(Geometry g) { + for (Joint joint : armature.getJointList()) { + if (g.getMesh().isAnimatedByJoint(armature.getJointIndex(joint))) { + deformingJoints.add(joint); + } + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java new file mode 100644 index 000000000..31c292186 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java @@ -0,0 +1,188 @@ +package com.jme3.scene.debug.custom; + +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.asset.AssetManager; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.Vector2f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.texture.Texture; + +import java.util.List; + +/** + * The class that creates a mesh to display how bones behave. If it is supplied + * with the bones' lengths it will show exactly how the bones look like on the + * scene. If not then only connections between each bone heads will be shown. + */ +public class ArmatureDebugger extends Node { + + /** + * The lines of the bones or the wires between their heads. + */ + private ArmatureNode armatureNode; + + private Armature armature; + + private Node joints; + private Node outlines; + private Node wires; + /** + * The dotted lines between a bone's tail and the had of its children. Not + * available if the length data was not provided. + */ + private ArmatureInterJointsWire interJointWires; + + public ArmatureDebugger() { + } + + /** + * Creates a debugger with no length data. The wires will be a connection + * between the bones' heads only. The points will show the bones' heads only + * and no dotted line of inter bones connection will be visible. + * + * @param name the name of the debugger's node + * @param armature the armature that will be shown + */ + public ArmatureDebugger(String name, Armature armature, List deformingJoints) { + super(name); + this.armature = armature; + armature.update(); + + joints = new Node("joints"); + outlines = new Node("outlines"); + wires = new Node("bones"); + this.attachChild(joints); + this.attachChild(outlines); + this.attachChild(wires); + Node ndJoints = new Node("non deforming Joints"); + Node ndOutlines = new Node("non deforming Joints outlines"); + Node ndWires = new Node("non deforming Joints wires"); + joints.attachChild(ndJoints); + outlines.attachChild(ndOutlines); + wires.attachChild(ndWires); + Node outlineDashed = new Node("Outlines Dashed"); + Node wiresDashed = new Node("Wires Dashed"); + wiresDashed.attachChild(new Node("dashed non defrom")); + outlineDashed.attachChild(new Node("dashed non defrom")); + outlines.attachChild(outlineDashed); + wires.attachChild(wiresDashed); + + armatureNode = new ArmatureNode(armature, joints, wires, outlines, deformingJoints); + + this.attachChild(armatureNode); + + displayNonDeformingJoint(false); + } + + public void displayNonDeformingJoint(boolean display) { + joints.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always); + outlines.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always); + wires.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always); + ((Node) outlines.getChild(1)).getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always); + ((Node) wires.getChild(1)).getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always); + } + + public void initialize(AssetManager assetManager, Camera camera) { + + armatureNode.setCamera(camera); + + Material matJoints = new Material(assetManager, "Common/MatDefs/Misc/Billboard.j3md"); + Texture t = assetManager.loadTexture("Common/Textures/dot.png"); + matJoints.setTexture("Texture", t); + matJoints.getAdditionalRenderState().setDepthTest(false); + matJoints.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + joints.setQueueBucket(RenderQueue.Bucket.Translucent); + joints.setMaterial(matJoints); + + Material matWires = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matWires.setBoolean("VertexColor", true); + matWires.getAdditionalRenderState().setLineWidth(3); + wires.setMaterial(matWires); + + Material matOutline = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matOutline.setBoolean("VertexColor", true); + matOutline.getAdditionalRenderState().setLineWidth(5); + outlines.setMaterial(matOutline); + + Material matOutline2 = new Material(assetManager, "Common/MatDefs/Misc/DashedLine.j3md"); + matOutline2.getAdditionalRenderState().setLineWidth(1); + outlines.getChild(1).setMaterial(matOutline2); + + Material matWires2 = new Material(assetManager, "Common/MatDefs/Misc/DashedLine.j3md"); + matWires2.getAdditionalRenderState().setLineWidth(1); + wires.getChild(1).setMaterial(matWires2); + + } + + public Armature getArmature() { + return armature; + } + + @Override + public void updateLogicalState(float tpf) { + super.updateLogicalState(tpf); + armatureNode.updateGeometry(); + } + + @Override + public int collideWith(Collidable other, CollisionResults results) { + return armatureNode.collideWith(other, results); + } + + protected Joint select(Geometry g) { + return armatureNode.select(g); + } + + /** + * @return the armature wires + */ + public ArmatureNode getBoneShapes() { + return armatureNode; + } + + /** + * @return the dotted line between bones (can be null) + */ + public ArmatureInterJointsWire getInterJointWires() { + return interJointWires; + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java new file mode 100644 index 000000000..b1c62a55b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java @@ -0,0 +1,124 @@ +package com.jme3.scene.debug.custom; + +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; + +import java.nio.FloatBuffer; + +/** + * A class that displays a dotted line between a bone tail and its childrens' heads. + * + * @author Marcin Roguski (Kaelthas) + */ +public class ArmatureInterJointsWire extends Mesh { + private Vector3f tmp = new Vector3f(); + + + public ArmatureInterJointsWire(Vector3f start, Vector3f[] ends) { + setMode(Mode.Lines); + updateGeometry(start, ends); + } + + protected void updateGeometry(Vector3f start, Vector3f[] ends) { + float[] pos = new float[ends.length * 3 + 3]; + pos[0] = start.x; + pos[1] = start.y; + pos[2] = start.z; + int index; + for (int i = 0; i < ends.length; i++) { + index = i * 3 + 3; + pos[index] = ends[i].x; + pos[index + 1] = ends[i].y; + pos[index + 2] = ends[i].z; + } + setBuffer(Type.Position, 3, pos); + + float[] texCoord = new float[ends.length * 2 + 2]; + texCoord[0] = 0; + texCoord[1] = 0; + for (int i = 0; i < ends.length * 2; i++) { + texCoord[i + 2] = tmp.set(start).subtractLocal(ends[i / 2]).length(); + } + setBuffer(Type.TexCoord, 2, texCoord); + + float[] normal = new float[ends.length * 3 + 3]; + for (int i = 0; i < ends.length * 3 + 3; i += 3) { + normal[i] = start.x; + normal[i + 1] = start.y; + normal[i + 2] = start.z; + } + setBuffer(Type.Normal, 3, normal); + + short[] id = new short[ends.length * 2]; + index = 1; + for (int i = 0; i < ends.length * 2; i += 2) { + id[i] = 0; + id[i + 1] = (short) (index); + index++; + } + setBuffer(Type.Index, 2, id); + updateBound(); + } + + /** + * Update the start and end points of the line. + */ + public void updatePoints(Vector3f start, Vector3f[] ends) { + VertexBuffer posBuf = getBuffer(Type.Position); + FloatBuffer fb = (FloatBuffer) posBuf.getData(); + fb.rewind(); + fb.put(start.x).put(start.y).put(start.z); + for (int i = 0; i < ends.length; i++) { + fb.put(ends[i].x); + fb.put(ends[i].y); + fb.put(ends[i].z); + } + posBuf.updateData(fb); + + VertexBuffer normBuf = getBuffer(Type.Normal); + fb = (FloatBuffer) normBuf.getData(); + fb.rewind(); + for (int i = 0; i < ends.length * 3 + 3; i += 3) { + fb.put(start.x); + fb.put(start.y); + fb.put(start.z); + } + normBuf.updateData(fb); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java new file mode 100644 index 000000000..038936049 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java @@ -0,0 +1,332 @@ +package com.jme3.scene.debug.custom; + +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.collision.*; +import com.jme3.math.*; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.*; +import com.jme3.scene.shape.Line; + +import java.nio.FloatBuffer; +import java.util.*; + +/** + * The class that displays either wires between the bones' heads if no length + * data is supplied and full bones' shapes otherwise. + */ +public class ArmatureNode extends Node { + + public static final float PIXEL_BOX = 10f; + /** + * The armature to be displayed. + */ + private Armature armature; + /** + * The map between the bone index and its length. + */ + private Map jointToGeoms = new HashMap<>(); + private Map geomToJoint = new HashMap<>(); + private Joint selectedJoint = null; + private Vector3f tmp = new Vector3f(); + private Vector2f tmpv2 = new Vector2f(); + private final static ColorRGBA selectedColor = ColorRGBA.Orange; + private final static ColorRGBA selectedColorJ = ColorRGBA.Yellow; + private final static ColorRGBA outlineColor = ColorRGBA.LightGray; + private final static ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); + + private Camera camera; + + + /** + * Creates a wire with bone lengths data. If the data is supplied then the + * wires will show each full bone (from head to tail). + * + * @param armature the armature that will be shown + */ + public ArmatureNode(Armature armature, Node joints, Node wires, Node outlines, List deformingJoints) { + this.armature = armature; + + Geometry origin = new Geometry("Armature Origin", new JointShape()); + setColor(origin, ColorRGBA.Green); + attach(joints, true, origin); + + for (Joint joint : armature.getRoots()) { + createSkeletonGeoms(joint, joints, wires, outlines, deformingJoints); + } + this.updateModelBound(); + + } + + protected final void createSkeletonGeoms(Joint joint, Node joints, Node wires, Node outlines, List deformingJoints) { + Vector3f start = joint.getModelTransform().getTranslation().clone(); + + Vector3f[] ends = null; + if (!joint.getChildren().isEmpty()) { + ends = new Vector3f[joint.getChildren().size()]; + } + + for (int i = 0; i < joint.getChildren().size(); i++) { + ends[i] = joint.getChildren().get(i).getModelTransform().getTranslation().clone(); + } + + boolean deforms = deformingJoints.contains(joint); + + Geometry jGeom = new Geometry(joint.getName() + "Joint", new JointShape()); + jGeom.setLocalTranslation(start); + attach(joints, deforms, jGeom); + Geometry bGeom = null; + Geometry bGeomO = null; + if (ends == null) { + geomToJoint.put(jGeom, joint); + } else { + Mesh m = null; + Mesh mO = null; + Node wireAttach = wires; + Node outlinesAttach = outlines; + if (ends.length == 1) { + m = new Line(start, ends[0]); + mO = new Line(start, ends[0]); + } else { + m = new ArmatureInterJointsWire(start, ends); + mO = new ArmatureInterJointsWire(start, ends); + wireAttach = (Node) wires.getChild(1); + outlinesAttach = null; + } + bGeom = new Geometry(joint.getName() + "Bone", m); + setColor(bGeom, outlinesAttach == null ? outlineColor : baseColor); + geomToJoint.put(bGeom, joint); + bGeom.setUserData("start", getWorldTransform().transformVector(start, start)); + for (int i = 0; i < ends.length; i++) { + getWorldTransform().transformVector(ends[i], ends[i]); + } + bGeom.setUserData("end", ends); + bGeom.setQueueBucket(RenderQueue.Bucket.Transparent); + attach(wireAttach, deforms, bGeom); + if (outlinesAttach != null) { + bGeomO = new Geometry(joint.getName() + "BoneOutline", mO); + setColor(bGeomO, outlineColor); + attach(outlinesAttach, deforms, bGeomO); + } + } + jointToGeoms.put(joint, new Geometry[]{jGeom, bGeom, bGeomO}); + + for (Joint child : joint.getChildren()) { + createSkeletonGeoms(child, joints, wires, outlines, deformingJoints); + } + } + + public void setCamera(Camera camera) { + this.camera = camera; + } + + private void attach(Node parent, boolean deforms, Geometry geom) { + if (deforms) { + parent.attachChild(geom); + } else { + ((Node) parent.getChild(0)).attachChild(geom); + } + } + + protected Joint select(Geometry g) { + if (g == null) { + resetSelection(); + return null; + } + Joint j = geomToJoint.get(g); + if (j != null) { + if (selectedJoint == j) { + return null; + } + resetSelection(); + selectedJoint = j; + Geometry[] geomArray = jointToGeoms.get(selectedJoint); + setColor(geomArray[0], selectedColorJ); + + if (geomArray[1] != null) { + setColor(geomArray[1], selectedColor); + } + + if (geomArray[2] != null) { + setColor(geomArray[2], baseColor); + } + return j; + } + return null; + } + + private void resetSelection() { + if (selectedJoint == null) { + return; + } + Geometry[] geoms = jointToGeoms.get(selectedJoint); + setColor(geoms[0], ColorRGBA.White); + if (geoms[1] != null) { + setColor(geoms[1], geoms[2] == null ? outlineColor : baseColor); + } + if (geoms[2] != null) { + setColor(geoms[2], outlineColor); + } + selectedJoint = null; + } + + protected Joint getSelectedJoint() { + return selectedJoint; + } + + + protected final void updateSkeletonGeoms(Joint joint) { + Geometry[] geoms = jointToGeoms.get(joint); + if (geoms != null) { + Geometry jGeom = geoms[0]; + jGeom.setLocalTranslation(joint.getModelTransform().getTranslation()); + Geometry bGeom = geoms[1]; + if (bGeom != null) { + Vector3f start = bGeom.getUserData("start"); + Vector3f[] ends = bGeom.getUserData("end"); + start.set(joint.getModelTransform().getTranslation()); + if (ends != null) { + for (int i = 0; i < joint.getChildren().size(); i++) { + ends[i].set(joint.getChildren().get(i).getModelTransform().getTranslation()); + } + updateBoneMesh(bGeom, start, ends); + Geometry bGeomO = geoms[2]; + if (bGeomO != null) { + updateBoneMesh(bGeomO, start, ends); + } + bGeom.setUserData("start", getWorldTransform().transformVector(start, start)); + for (int i = 0; i < ends.length; i++) { + getWorldTransform().transformVector(ends[i], ends[i]); + } + bGeom.setUserData("end", ends); + + } + } + } + + for (Joint child : joint.getChildren()) { + updateSkeletonGeoms(child); + } + } + + public int pick(Vector2f cursor, CollisionResults results) { + + for (Geometry g : geomToJoint.keySet()) { + if (g.getMesh() instanceof JointShape) { + camera.getScreenCoordinates(g.getWorldTranslation(), tmp); + if (cursor.x <= tmp.x + PIXEL_BOX && cursor.x >= tmp.x - PIXEL_BOX + && cursor.y <= tmp.y + PIXEL_BOX && cursor.y >= tmp.y - PIXEL_BOX) { + CollisionResult res = new CollisionResult(); + res.setGeometry(g); + results.addCollision(res); + } + } + } + return 0; + } + + @Override + public int collideWith(Collidable other, CollisionResults results) { + if (!(other instanceof Ray)) { + return 0; + } + + // first try a 2D pick; + camera.getScreenCoordinates(((Ray)other).getOrigin(),tmp); + tmpv2.x = tmp.x; + tmpv2.y = tmp.y; + int nbHit = pick(tmpv2, results); + if (nbHit > 0) { + return nbHit; + } + + for (Geometry g : geomToJoint.keySet()) { + if (g.getMesh() instanceof JointShape) { + continue; + } + Vector3f start = g.getUserData("start"); + Vector3f[] ends = g.getUserData("end"); + for (int i = 0; i < ends.length; i++) { + float len = MathUtils.raySegmentShortestDistance((Ray) other, start, ends[i], camera); + if (len > 0 && len < PIXEL_BOX) { + CollisionResult res = new CollisionResult(); + res.setGeometry(g); + results.addCollision(res); + nbHit++; + } + } + } + return nbHit; + } + + private void updateBoneMesh(Geometry geom, Vector3f start, Vector3f[] ends) { + if (geom.getMesh() instanceof ArmatureInterJointsWire) { + ((ArmatureInterJointsWire) geom.getMesh()).updatePoints(start, ends); + } else if (geom.getMesh() instanceof Line) { + ((Line) geom.getMesh()).updatePoints(start, ends[0]); + } + geom.updateModelBound(); + } + + private void setColor(Geometry g, ColorRGBA color) { + float[] colors = new float[g.getMesh().getVertexCount() * 4]; + for (int i = 0; i < g.getMesh().getVertexCount() * 4; i += 4) { + colors[i] = color.r; + colors[i + 1] = color.g; + colors[i + 2] = color.b; + colors[i + 3] = color.a; + } + VertexBuffer colorBuff = g.getMesh().getBuffer(VertexBuffer.Type.Color); + if (colorBuff == null) { + g.getMesh().setBuffer(VertexBuffer.Type.Color, 4, colors); + } else { + FloatBuffer cBuff = (FloatBuffer) colorBuff.getData(); + cBuff.rewind(); + cBuff.put(colors); + colorBuff.updateData(cBuff); + } + } + + /** + * The method updates the geometry according to the positions of the bones. + */ + public void updateGeometry() { + armature.update(); + for (Joint joint : armature.getRoots()) { + updateSkeletonGeoms(joint); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/BoneShape.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/BoneShape.java deleted file mode 100644 index 1b6208619..000000000 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/BoneShape.java +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (c) 2009-2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -// $Id: Cylinder.java 4131 2009-03-19 20:15:28Z blaine.dev $ -package com.jme3.scene.debug.custom; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.mesh.IndexBuffer; -import com.jme3.util.BufferUtils; - -import static com.jme3.util.BufferUtils.*; - -import java.io.IOException; -import java.nio.FloatBuffer; - -/** - * A simple cylinder, defined by its height and radius. - * (Ported to jME3) - * - * @author Mark Powell - * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $ - */ -public class BoneShape extends Mesh { - - private int axisSamples; - - private int radialSamples; - - private float radius; - private float radius2; - - private float height; - private boolean closed; - private boolean inverted; - - /** - * Default constructor for serialization only. Do not use. - */ - public BoneShape() { - } - - /** - * Creates a new Cylinder. By default its center is the origin. Usually, a - * higher sample number creates a better looking cylinder, but at the cost - * of more vertex information. - * - * @param axisSamples Number of triangle samples along the axis. - * @param radialSamples Number of triangle samples along the radial. - * @param radius The radius of the cylinder. - * @param height The cylinder's height. - */ - public BoneShape(int axisSamples, int radialSamples, - float radius, float height) { - this(axisSamples, radialSamples, radius, height, false); - } - - /** - * Creates a new Cylinder. By default its center is the origin. Usually, a - * higher sample number creates a better looking cylinder, but at the cost - * of more vertex information.
- * If the cylinder is closed the texture is split into axisSamples parts: - * top most and bottom most part is used for top and bottom of the cylinder, - * rest of the texture for the cylinder wall. The middle of the top is - * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need - * a suited distorted texture. - * - * @param axisSamples Number of triangle samples along the axis. - * @param radialSamples Number of triangle samples along the radial. - * @param radius The radius of the cylinder. - * @param height The cylinder's height. - * @param closed true to create a cylinder with top and bottom surface - */ - public BoneShape(int axisSamples, int radialSamples, - float radius, float height, boolean closed) { - this(axisSamples, radialSamples, radius, height, closed, false); - } - - /** - * Creates a new Cylinder. By default its center is the origin. Usually, a - * higher sample number creates a better looking cylinder, but at the cost - * of more vertex information.
- * If the cylinder is closed the texture is split into axisSamples parts: - * top most and bottom most part is used for top and bottom of the cylinder, - * rest of the texture for the cylinder wall. The middle of the top is - * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need - * a suited distorted texture. - * - * @param axisSamples Number of triangle samples along the axis. - * @param radialSamples Number of triangle samples along the radial. - * @param radius The radius of the cylinder. - * @param height The cylinder's height. - * @param closed true to create a cylinder with top and bottom surface - * @param inverted true to create a cylinder that is meant to be viewed from the - * interior. - */ - public BoneShape(int axisSamples, int radialSamples, - float radius, float height, boolean closed, boolean inverted) { - this(axisSamples, radialSamples, radius, radius, height, closed, inverted); - } - - public BoneShape(int axisSamples, int radialSamples, - float radius, float radius2, float height, boolean closed, boolean inverted) { - super(); - updateGeometry(axisSamples, radialSamples, radius, radius2, height, closed, inverted); - } - - /** - * @return the number of samples along the cylinder axis - */ - public int getAxisSamples() { - return axisSamples; - } - - /** - * @return Returns the height. - */ - public float getHeight() { - return height; - } - - /** - * @return number of samples around cylinder - */ - public int getRadialSamples() { - return radialSamples; - } - - /** - * @return Returns the radius. - */ - public float getRadius() { - return radius; - } - - public float getRadius2() { - return radius2; - } - - /** - * @return true if end caps are used. - */ - public boolean isClosed() { - return closed; - } - - /** - * @return true if normals and uvs are created for interior use - */ - public boolean isInverted() { - return inverted; - } - - /** - * Rebuilds the cylinder based on a new set of parameters. - * - * @param axisSamples the number of samples along the axis. - * @param radialSamples the number of samples around the radial. - * @param radius the radius of the bottom of the cylinder. - * @param radius2 the radius of the top of the cylinder. - * @param height the cylinder's height. - * @param closed should the cylinder have top and bottom surfaces. - * @param inverted is the cylinder is meant to be viewed from the inside. - */ - public void updateGeometry(int axisSamples, int radialSamples, - float radius, float radius2, float height, boolean closed, boolean inverted) { - this.axisSamples = axisSamples + (closed ? 2 : 0); - this.radialSamples = radialSamples; - this.radius = radius; - this.radius2 = radius2; - this.height = height; - this.closed = closed; - this.inverted = inverted; - -// VertexBuffer pvb = getBuffer(Type.Position); -// VertexBuffer nvb = getBuffer(Type.Normal); -// VertexBuffer tvb = getBuffer(Type.TexCoord); - - // Vertices - int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0); - - setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount)); - - // Normals - setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount)); - - // Texture co-ordinates - setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount)); - - int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples; - - setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount)); - - //Color - setBuffer(Type.Color, 4, createFloatBuffer(vertCount * 4)); - - // generate geometry - float inverseRadial = 1.0f / radialSamples; - float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1); - float inverseAxisLessTexture = 1.0f / (axisSamples - 1); - float halfHeight = 0.5f * height; - - // Generate points on the unit circle to be used in computing the mesh - // points on a cylinder slice. - float[] sin = new float[radialSamples + 1]; - float[] cos = new float[radialSamples + 1]; - - for (int radialCount = 0; radialCount < radialSamples; radialCount++) { - float angle = FastMath.TWO_PI * inverseRadial * radialCount; - cos[radialCount] = FastMath.cos(angle); - sin[radialCount] = FastMath.sin(angle); - } - sin[radialSamples] = sin[0]; - cos[radialSamples] = cos[0]; - - // calculate normals - Vector3f[] vNormals = null; - Vector3f vNormal = Vector3f.UNIT_Z; - - if ((height != 0.0f) && (radius != radius2)) { - vNormals = new Vector3f[radialSamples]; - Vector3f vHeight = Vector3f.UNIT_Z.mult(height); - Vector3f vRadial = new Vector3f(); - - for (int radialCount = 0; radialCount < radialSamples; radialCount++) { - vRadial.set(cos[radialCount], sin[radialCount], 0.0f); - Vector3f vRadius = vRadial.mult(radius); - Vector3f vRadius2 = vRadial.mult(radius2); - Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius)); - Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z); - vNormals[radialCount] = vMantle.cross(vTangent).normalize(); - } - } - - FloatBuffer nb = getFloatBuffer(Type.Normal); - FloatBuffer pb = getFloatBuffer(Type.Position); - FloatBuffer tb = getFloatBuffer(Type.TexCoord); - FloatBuffer cb = getFloatBuffer(Type.Color); - - cb.rewind(); - for (int i = 0; i < vertCount; i++) { - cb.put(0.05f).put(0.05f).put(0.05f).put(1f); - } - - // generate the cylinder itself - Vector3f tempNormal = new Vector3f(); - for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) { - float axisFraction; - float axisFractionTexture; - int topBottom = 0; - if (!closed) { - axisFraction = axisCount * inverseAxisLess; // in [0,1] - axisFractionTexture = axisFraction; - } else { - if (axisCount == 0) { - topBottom = -1; // bottom - axisFraction = 0; - axisFractionTexture = inverseAxisLessTexture; - } else if (axisCount == axisSamples - 1) { - topBottom = 1; // top - axisFraction = 1; - axisFractionTexture = 1 - inverseAxisLessTexture; - } else { - axisFraction = (axisCount - 1) * inverseAxisLess; - axisFractionTexture = axisCount * inverseAxisLessTexture; - } - } - - // compute center of slice - float z = height * axisFraction; - Vector3f sliceCenter = new Vector3f(0, 0, z); - - // compute slice vertices with duplication at end point - int save = i; - for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) { - float radialFraction = radialCount * inverseRadial; // in [0,1) - tempNormal.set(cos[radialCount], sin[radialCount], 0.0f); - - if (vNormals != null) { - vNormal = vNormals[radialCount]; - } else if (radius == radius2) { - vNormal = tempNormal; - } - - if (topBottom == 0) { - if (!inverted) - nb.put(vNormal.x).put(vNormal.y).put(vNormal.z); - else - nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z); - } else { - nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1)); - } - - tempNormal.multLocal((radius - radius2) * axisFraction + radius2) - .addLocal(sliceCenter); - pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z); - - tb.put((inverted ? 1 - radialFraction : radialFraction)) - .put(axisFractionTexture); - } - - BufferUtils.copyInternalVector3(pb, save, i); - BufferUtils.copyInternalVector3(nb, save, i); - - tb.put((inverted ? 0.0f : 1.0f)) - .put(axisFractionTexture); - } - - if (closed) { - pb.put(0).put(0).put(-halfHeight); // bottom center - nb.put(0).put(0).put(-1 * (inverted ? -1 : 1)); - tb.put(0.5f).put(0); - pb.put(0).put(0).put(halfHeight); // top center - nb.put(0).put(0).put(1 * (inverted ? -1 : 1)); - tb.put(0.5f).put(1); - } - - IndexBuffer ib = getIndexBuffer(); - int index = 0; - // Connectivity - for (int axisCount = 0, axisStart = 0; axisCount < axisSamples - 1; axisCount++) { - int i0 = axisStart; - int i1 = i0 + 1; - axisStart += radialSamples + 1; - int i2 = axisStart; - int i3 = i2 + 1; - for (int i = 0; i < radialSamples; i++) { - if (closed && axisCount == 0) { - if (!inverted) { - ib.put(index++, i0++); - ib.put(index++, vertCount - 2); - ib.put(index++, i1++); - } else { - ib.put(index++, i0++); - ib.put(index++, i1++); - ib.put(index++, vertCount - 2); - } - } else if (closed && axisCount == axisSamples - 2) { - ib.put(index++, i2++); - ib.put(index++, inverted ? vertCount - 1 : i3++); - ib.put(index++, inverted ? i3++ : vertCount - 1); - } else { - ib.put(index++, i0++); - ib.put(index++, inverted ? i2 : i1); - ib.put(index++, inverted ? i1 : i2); - ib.put(index++, i1++); - ib.put(index++, inverted ? i2++ : i3++); - ib.put(index++, inverted ? i3++ : i2++); - } - } - } - - updateBound(); - } - - @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); - axisSamples = capsule.readInt("axisSamples", 0); - radialSamples = capsule.readInt("radialSamples", 0); - radius = capsule.readFloat("radius", 0); - radius2 = capsule.readFloat("radius2", 0); - height = capsule.readFloat("height", 0); - closed = capsule.readBoolean("closed", false); - inverted = capsule.readBoolean("inverted", false); - } - - @Override - public void write(JmeExporter e) throws IOException { - super.write(e); - OutputCapsule capsule = e.getCapsule(this); - capsule.write(axisSamples, "axisSamples", 0); - capsule.write(radialSamples, "radialSamples", 0); - capsule.write(radius, "radius", 0); - capsule.write(radius2, "radius2", 0); - capsule.write(height, "height", 0); - capsule.write(closed, "closed", false); - capsule.write(inverted, "inverted", false); - } - - -} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java new file mode 100644 index 000000000..b854095b9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.scene.debug.custom; + +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; + + +public class JointShape extends Mesh { + + + /** + * Serialization only. Do not use. + */ + public JointShape() { + float width = 1; + float height = 1; + setBuffer(Type.Position, 3, new float[]{-width * 0.5f, -width * 0.5f, 0, + width * 0.5f, -width * 0.5f, 0, + width * 0.5f, height * 0.5f, 0, + -width * 0.5f, height * 0.5f, 0 + }); + + + setBuffer(Type.TexCoord, 2, new float[]{0, 0, + 1, 0, + 1, 1, + 0, 1}); + + setBuffer(Type.Normal, 3, new float[]{0, 0, 1, + 0, 0, 1, + 0, 0, 1, + 0, 0, 1}); + + setBuffer(Type.Color, 4, new float[]{1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1}); + + setBuffer(Type.Index, 3, new short[]{0, 1, 2, + 0, 2, 3}); + + + updateBound(); + setStatic(); + } + + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonBone.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonBone.java deleted file mode 100644 index b4f21ec54..000000000 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonBone.java +++ /dev/null @@ -1,238 +0,0 @@ -package com.jme3.scene.debug.custom; - -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import java.util.Map; - -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.bounding.*; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.shape.Sphere; - -import static com.jme3.util.BufferUtils.createFloatBuffer; - -import java.nio.FloatBuffer; -import java.util.HashMap; - -/** - * The class that displays either wires between the bones' heads if no length - * data is supplied and full bones' shapes otherwise. - */ -public class SkeletonBone extends Node { - - /** - * The skeleton to be displayed. - */ - private Skeleton skeleton; - /** - * The map between the bone index and its length. - */ - private Map boneNodes = new HashMap(); - private Map nodeBones = new HashMap(); - private Node selectedNode = null; - private boolean guessBonesOrientation = false; - - /** - * Creates a wire with bone lengths data. If the data is supplied then the - * wires will show each full bone (from head to tail). - * - * @param skeleton the skeleton that will be shown - * @param boneLengths a map between the bone's index and the bone's length - */ - public SkeletonBone(Skeleton skeleton, Map boneLengths, boolean guessBonesOrientation) { - this.skeleton = skeleton; - this.skeleton.reset(); - this.skeleton.updateWorldVectors(); - this.guessBonesOrientation = guessBonesOrientation; - - BoneShape boneShape = new BoneShape(5, 12, 0.02f, 0.07f, 1f, false, false); - Sphere jointShape = new Sphere(10, 10, 0.1f); - jointShape.setBuffer(VertexBuffer.Type.Color, 4, createFloatBuffer(jointShape.getVertexCount() * 4)); - FloatBuffer cb = jointShape.getFloatBuffer(VertexBuffer.Type.Color); - - cb.rewind(); - for (int i = 0; i < jointShape.getVertexCount(); i++) { - cb.put(0.05f).put(0.05f).put(0.05f).put(1f); - } - - for (Bone bone : skeleton.getRoots()) { - createSkeletonGeoms(bone, boneShape, jointShape, boneLengths, skeleton, this, guessBonesOrientation); - } - this.updateModelBound(); - - - Sphere originShape = new Sphere(10, 10, 0.02f); - originShape.setBuffer(VertexBuffer.Type.Color, 4, createFloatBuffer(originShape.getVertexCount() * 4)); - cb = originShape.getFloatBuffer(VertexBuffer.Type.Color); - cb.rewind(); - for (int i = 0; i < jointShape.getVertexCount(); i++) { - cb.put(0.4f).put(0.4f).put(0.05f).put(1f); - } - - Geometry origin = new Geometry("origin", originShape); - BoundingVolume bv = this.getWorldBound(); - float scale = 1; - if (bv.getType() == BoundingVolume.Type.AABB) { - BoundingBox bb = (BoundingBox) bv; - scale = (bb.getXExtent() + bb.getYExtent() + bb.getZExtent()) / 3f; - } else if (bv.getType() == BoundingVolume.Type.Sphere) { - BoundingSphere bs = (BoundingSphere) bv; - scale = bs.getRadius(); - } - origin.scale(scale); - attachChild(origin); - - - - } - - protected final void createSkeletonGeoms(Bone bone, Mesh boneShape, Mesh jointShape, Map boneLengths, Skeleton skeleton, Node parent, boolean guessBonesOrientation) { - - if (guessBonesOrientation && bone.getName().equalsIgnoreCase("Site")) { - //BVH skeleton have a useless end point bone named Site - return; - } - Node n = new Node(bone.getName() + "Node"); - Geometry bGeom = new Geometry(bone.getName(), boneShape); - Geometry jGeom = new Geometry(bone.getName() + "Joint", jointShape); - n.setLocalTranslation(bone.getLocalPosition()); - n.setLocalRotation(bone.getLocalRotation()); - - float boneLength = boneLengths.get(skeleton.getBoneIndex(bone)); - n.setLocalScale(bone.getLocalScale()); - - bGeom.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X).normalizeLocal()); - - if (guessBonesOrientation) { - //One child only, the bone direction is from the parent joint to the child joint. - if (bone.getChildren().size() == 1) { - Vector3f v = bone.getChildren().get(0).getLocalPosition(); - Quaternion q = new Quaternion(); - q.lookAt(v, Vector3f.UNIT_Z); - bGeom.setLocalRotation(q); - boneLength = v.length(); - } - //no child, the bone has the same direction as the parent bone. - if (bone.getChildren().isEmpty()) { - if (parent.getChildren().size() > 0) { - bGeom.setLocalRotation(parent.getChild(0).getLocalRotation()); - } else { - //no parent, let's use the bind orientation of the bone - bGeom.setLocalRotation(bone.getBindRotation()); - } - } - } - bGeom.setLocalScale(boneLength); - jGeom.setLocalScale(boneLength); - - n.attachChild(bGeom); - n.attachChild(jGeom); - - //tip - if (bone.getChildren().size() != 1) { - Geometry gt = jGeom.clone(); - gt.scale(0.8f); - Vector3f v = new Vector3f(0, boneLength, 0); - if (guessBonesOrientation) { - if (bone.getChildren().isEmpty()) { - if (parent.getChildren().size() > 0) { - gt.setLocalTranslation(bGeom.getLocalRotation().mult(parent.getChild(0).getLocalRotation()).mult(v, v)); - } else { - gt.setLocalTranslation(bGeom.getLocalRotation().mult(bone.getBindRotation()).mult(v, v)); - } - } - } else { - gt.setLocalTranslation(v); - } - - n.attachChild(gt); - } - - - boneNodes.put(bone, n); - nodeBones.put(n, bone); - parent.attachChild(n); - for (Bone childBone : bone.getChildren()) { - createSkeletonGeoms(childBone, boneShape, jointShape, boneLengths, skeleton, n, guessBonesOrientation); - } - } - - protected Bone select(Geometry g) { - Node parentNode = g.getParent(); - - if (parent != null) { - Bone b = nodeBones.get(parentNode); - if (b != null) { - selectedNode = parentNode; - } - return b; - } - return null; - } - - protected Node getSelectedNode() { - return selectedNode; - } - - - protected final void updateSkeletonGeoms(Bone bone) { - if (guessBonesOrientation && bone.getName().equalsIgnoreCase("Site")) { - return; - } - Node n = boneNodes.get(bone); - n.setLocalTranslation(bone.getLocalPosition()); - n.setLocalRotation(bone.getLocalRotation()); - n.setLocalScale(bone.getLocalScale()); - - for (Bone childBone : bone.getChildren()) { - updateSkeletonGeoms(childBone); - } - } - - /** - * The method updates the geometry according to the positions of the bones. - */ - public void updateGeometry() { - - for (Bone bone : skeleton.getRoots()) { - updateSkeletonGeoms(bone); - } - } -} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugAppState.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugAppState.java deleted file mode 100644 index 43778f5ce..000000000 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugAppState.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.scene.debug.custom; - -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.animation.SkeletonControl; -import com.jme3.app.Application; -import com.jme3.app.state.AbstractAppState; -import com.jme3.app.state.AppStateManager; -import com.jme3.collision.CollisionResults; -import com.jme3.input.MouseInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.MouseButtonTrigger; -import com.jme3.math.Ray; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.ViewPort; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author Nehon - */ -public class SkeletonDebugAppState extends AbstractAppState { - - private Node debugNode = new Node("debugNode"); - private Map skeletons = new HashMap(); - private Map selectedBones = new HashMap(); - private Application app; - - @Override - public void initialize(AppStateManager stateManager, Application app) { - ViewPort vp = app.getRenderManager().createMainView("debug", app.getCamera()); - vp.attachScene(debugNode); - vp.setClearDepth(true); - this.app = app; - for (SkeletonDebugger skeletonDebugger : skeletons.values()) { - skeletonDebugger.initialize(app.getAssetManager()); - } - app.getInputManager().addListener(actionListener, "shoot"); - app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); - super.initialize(stateManager, app); - } - - @Override - public void update(float tpf) { - debugNode.updateLogicalState(tpf); - debugNode.updateGeometricState(); - } - - public SkeletonDebugger addSkeleton(SkeletonControl skeletonControl, boolean guessBonesOrientation) { - Skeleton skeleton = skeletonControl.getSkeleton(); - Spatial forSpatial = skeletonControl.getSpatial(); - return addSkeleton(skeleton, forSpatial, guessBonesOrientation); - } - - public SkeletonDebugger addSkeleton(Skeleton skeleton, Spatial forSpatial, boolean guessBonesOrientation) { - - SkeletonDebugger sd = new SkeletonDebugger(forSpatial.getName() + "_Skeleton", skeleton, guessBonesOrientation); - sd.setLocalTransform(forSpatial.getWorldTransform()); - if (forSpatial instanceof Node) { - List geoms = new ArrayList<>(); - findGeoms((Node) forSpatial, geoms); - if (geoms.size() == 1) { - sd.setLocalTransform(geoms.get(0).getWorldTransform()); - } - } - skeletons.put(skeleton, sd); - debugNode.attachChild(sd); - if (isInitialized()) { - sd.initialize(app.getAssetManager()); - } - return sd; - } - - private void findGeoms(Node node, List geoms) { - for (Spatial spatial : node.getChildren()) { - if (spatial instanceof Geometry) { - geoms.add((Geometry) spatial); - } else if (spatial instanceof Node) { - findGeoms((Node) spatial, geoms); - } - } - } - - /** - * Pick a Target Using the Mouse Pointer.

  1. Map "pick target" action - * to a MouseButtonTrigger.
  2. flyCam.setEnabled(false); - *
  3. inputManager.setCursorVisible(true);
  4. Implement action in - * AnalogListener (TODO).
- */ - private ActionListener actionListener = new ActionListener() { - public void onAction(String name, boolean isPressed, float tpf) { - if (name.equals("shoot") && isPressed) { - CollisionResults results = new CollisionResults(); - Vector2f click2d = app.getInputManager().getCursorPosition(); - Vector3f click3d = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone(); - Vector3f dir = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d); - Ray ray = new Ray(click3d, dir); - - debugNode.collideWith(ray, results); - - if (results.size() > 0) { - // The closest result is the target that the player picked: - Geometry target = results.getClosestCollision().getGeometry(); - for (SkeletonDebugger skeleton : skeletons.values()) { - Bone selectedBone = skeleton.select(target); - if (selectedBone != null) { - selectedBones.put(skeleton.getSkeleton(), selectedBone); - System.err.println("-----------------------"); - System.err.println("Selected Bone : " + selectedBone.getName() + " in skeleton " + skeleton.getName()); - System.err.println("Root Bone : " + (selectedBone.getParent() == null)); - System.err.println("-----------------------"); - System.err.println("Bind translation: " + selectedBone.getBindPosition()); - System.err.println("Bind rotation: " + selectedBone.getBindRotation()); - System.err.println("Bind scale: " + selectedBone.getBindScale()); - System.err.println("---"); - System.err.println("Local translation: " + selectedBone.getLocalPosition()); - System.err.println("Local rotation: " + selectedBone.getLocalRotation()); - System.err.println("Local scale: " + selectedBone.getLocalScale()); - System.err.println("---"); - System.err.println("Model translation: " + selectedBone.getModelSpacePosition()); - System.err.println("Model rotation: " + selectedBone.getModelSpaceRotation()); - System.err.println("Model scale: " + selectedBone.getModelSpaceScale()); - System.err.println("---"); - System.err.println("Bind inverse Transform: "); - System.err.println(selectedBone.getBindInverseTransform()); - return; - } - } - } - } - } - }; - - public Map getSelectedBones() { - return selectedBones; - } - - public Node getDebugNode() { - return debugNode; - } - - public void setDebugNode(Node debugNode) { - this.debugNode = debugNode; - } -} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugger.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugger.java deleted file mode 100644 index 1336b9d1b..000000000 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugger.java +++ /dev/null @@ -1,218 +0,0 @@ -package com.jme3.scene.debug.custom; - -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import com.jme3.animation.Bone; - -import java.util.Map; - -import com.jme3.animation.Skeleton; -import com.jme3.asset.AssetManager; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.scene.BatchNode; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.VertexBuffer; - -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * The class that creates a mesh to display how bones behave. If it is supplied - * with the bones' lengths it will show exactly how the bones look like on the - * scene. If not then only connections between each bone heads will be shown. - */ -public class SkeletonDebugger extends BatchNode { - - /** - * The lines of the bones or the wires between their heads. - */ - private SkeletonBone bones; - - private Skeleton skeleton; - /** - * The dotted lines between a bone's tail and the had of its children. Not - * available if the length data was not provided. - */ - private SkeletonInterBoneWire interBoneWires; - private List selectedBones = new ArrayList(); - - public SkeletonDebugger() { - } - - /** - * Creates a debugger with no length data. The wires will be a connection - * between the bones' heads only. The points will show the bones' heads only - * and no dotted line of inter bones connection will be visible. - * - * @param name the name of the debugger's node - * @param skeleton the skeleton that will be shown - */ - public SkeletonDebugger(String name, Skeleton skeleton, boolean guessBonesOrientation) { - super(name); - this.skeleton = skeleton; - skeleton.reset(); - skeleton.updateWorldVectors(); - Map boneLengths = new HashMap(); - - for (Bone bone : skeleton.getRoots()) { - computeLength(bone, boneLengths, skeleton); - } - - bones = new SkeletonBone(skeleton, boneLengths, guessBonesOrientation); - - this.attachChild(bones); - - interBoneWires = new SkeletonInterBoneWire(skeleton, boneLengths, guessBonesOrientation); - Geometry g = new Geometry(name + "_interwires", interBoneWires); - g.setBatchHint(BatchHint.Never); - this.attachChild(g); - } - - protected void initialize(AssetManager assetManager) { - Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mat.setColor("Color", new ColorRGBA(0.05f, 0.05f, 0.05f, 1.0f));//new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f) - setMaterial(mat); - Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mat2.setBoolean("VertexColor", true); - bones.setMaterial(mat2); - batch(); - - } - - @Override - public final void setMaterial(Material material) { - if (batches.isEmpty()) { - for (int i = 0; i < children.size(); i++) { - children.get(i).setMaterial(material); - } - } else { - super.setMaterial(material); - } - } - - public Skeleton getSkeleton() { - return skeleton; - } - - - private void computeLength(Bone b, Map boneLengths, Skeleton skeleton) { - if (b.getChildren().isEmpty()) { - if (b.getParent() != null) { - boneLengths.put(skeleton.getBoneIndex(b), boneLengths.get(skeleton.getBoneIndex(b.getParent())) * 0.75f); - } else { - boneLengths.put(skeleton.getBoneIndex(b), 0.1f); - } - } else { - float length = Float.MAX_VALUE; - for (Bone bone : b.getChildren()) { - float len = b.getModelSpacePosition().subtract(bone.getModelSpacePosition()).length(); - if (len < length) { - length = len; - } - } - boneLengths.put(skeleton.getBoneIndex(b), length); - for (Bone bone : b.getChildren()) { - computeLength(bone, boneLengths, skeleton); - } - } - } - - @Override - public void updateLogicalState(float tpf) { - super.updateLogicalState(tpf); - bones.updateGeometry(); - if (interBoneWires != null) { - interBoneWires.updateGeometry(); - } - } - - ColorRGBA selectedColor = ColorRGBA.Orange; - ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); - - protected Bone select(Geometry g) { - Node oldNode = bones.getSelectedNode(); - Bone b = bones.select(g); - if (b == null) { - return null; - } - if (oldNode != null) { - markSelected(oldNode, false); - } - markSelected(bones.getSelectedNode(), true); - return b; - } - - /** - * @return the skeleton wires - */ - public SkeletonBone getBoneShapes() { - return bones; - } - - /** - * @return the dotted line between bones (can be null) - */ - public SkeletonInterBoneWire getInterBoneWires() { - return interBoneWires; - } - - protected void markSelected(Node n, boolean selected) { - ColorRGBA c = baseColor; - if (selected) { - c = selectedColor; - } - for (Spatial spatial : n.getChildren()) { - if (spatial instanceof Geometry) { - Geometry geom = (Geometry) spatial; - - Geometry batch = (Geometry) getChild(getName() + "-batch0"); - VertexBuffer vb = batch.getMesh().getBuffer(VertexBuffer.Type.Color); - FloatBuffer color = (FloatBuffer) vb.getData(); - // System.err.println(getName() + "." + geom.getName() + " index " + getGeometryStartIndex(geom) * 4 + "/" + color.limit()); - - color.position(getGeometryStartIndex(geom) * 4); - - for (int i = 0; i < geom.getVertexCount(); i++) { - color.put(c.r).put(c.g).put(c.b).put(c.a); - } - color.rewind(); - vb.updateData(color); - } - } - } -} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonInterBoneWire.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonInterBoneWire.java deleted file mode 100644 index 504c81fe2..000000000 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonInterBoneWire.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.jme3.scene.debug.custom; - -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - -import java.nio.FloatBuffer; -import java.util.Map; - -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.VertexBuffer.Format; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.VertexBuffer.Usage; -import com.jme3.util.BufferUtils; - -/** - * A class that displays a dotted line between a bone tail and its childrens' heads. - * - * @author Marcin Roguski (Kaelthas) - */ -public class SkeletonInterBoneWire extends Mesh { - private static final int POINT_AMOUNT = 10; - /** - * The amount of connections between bones. - */ - private int connectionsAmount; - /** - * The skeleton that will be showed. - */ - private Skeleton skeleton; - /** - * The map between the bone index and its length. - */ - private Map boneLengths; - - private boolean guessBonesOrientation = false; - - /** - * Creates buffers for points. Each line has POINT_AMOUNT of points. - * - * @param skeleton the skeleton that will be showed - * @param boneLengths the lengths of the bones - */ - public SkeletonInterBoneWire(Skeleton skeleton, Map boneLengths, boolean guessBonesOrientation) { - this.skeleton = skeleton; - - for (Bone bone : skeleton.getRoots()) { - this.countConnections(bone); - } - - this.setMode(Mode.Points); - this.setPointSize(2); - this.boneLengths = boneLengths; - - VertexBuffer pb = new VertexBuffer(Type.Position); - FloatBuffer fpb = BufferUtils.createFloatBuffer(POINT_AMOUNT * connectionsAmount * 3); - pb.setupData(Usage.Stream, 3, Format.Float, fpb); - this.setBuffer(pb); - - this.guessBonesOrientation = guessBonesOrientation; - this.updateCounts(); - } - - /** - * The method updates the geometry according to the poitions of the bones. - */ - public void updateGeometry() { - VertexBuffer vb = this.getBuffer(Type.Position); - FloatBuffer posBuf = this.getFloatBuffer(Type.Position); - posBuf.clear(); - for (int i = 0; i < skeleton.getBoneCount(); ++i) { - Bone bone = skeleton.getBone(i); - Vector3f parentTail = bone.getModelSpacePosition().add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i)))); - - if (guessBonesOrientation) { - parentTail = bone.getModelSpacePosition(); - } - - for (Bone child : bone.getChildren()) { - Vector3f childHead = child.getModelSpacePosition(); - Vector3f v = childHead.subtract(parentTail); - float pointDelta = v.length() / POINT_AMOUNT; - v.normalizeLocal().multLocal(pointDelta); - Vector3f pointPosition = parentTail.clone(); - for (int j = 0; j < POINT_AMOUNT; ++j) { - posBuf.put(pointPosition.getX()).put(pointPosition.getY()).put(pointPosition.getZ()); - pointPosition.addLocal(v); - } - } - } - posBuf.flip(); - vb.updateData(posBuf); - - this.updateBound(); - } - - /** - * Th method couns the connections between bones. - * - * @param bone the bone where counting starts - */ - private void countConnections(Bone bone) { - for (Bone child : bone.getChildren()) { - ++connectionsAmount; - this.countConnections(child); - } - } -} diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java b/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java new file mode 100644 index 000000000..083d04fb7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java @@ -0,0 +1,51 @@ +package com.jme3.scene.mesh; + +import com.jme3.export.*; +import com.jme3.scene.VertexBuffer; + +import java.io.IOException; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.util.EnumMap; +import java.util.Map; + +public class MorphTarget implements Savable { + private EnumMap buffers = new EnumMap<>(VertexBuffer.Type.class); + + public void setBuffer(VertexBuffer.Type type, FloatBuffer buffer) { + buffers.put(type, buffer); + } + + public FloatBuffer getBuffer(VertexBuffer.Type type) { + return buffers.get(type); + } + + public EnumMap getBuffers() { + return buffers; + } + + public int getNumBuffers() { + return buffers.size(); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + for (Map.Entry entry : buffers.entrySet()) { + Buffer roData = entry.getValue().asReadOnlyBuffer(); + oc.write((FloatBuffer) roData, entry.getKey().name(),null); + } + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + for (VertexBuffer.Type type : VertexBuffer.Type.values()) { + FloatBuffer b = ic.readFloatBuffer(type.name(), null); + if(b!= null){ + setBuffer(type, b); + } + } + + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java b/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java index 7ffee1905..1571823a7 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java @@ -31,12 +31,10 @@ */ package com.jme3.scene.shape; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; +import com.jme3.export.*; import com.jme3.math.Vector3f; import com.jme3.scene.Mesh; + import java.io.IOException; /** @@ -88,12 +86,12 @@ public abstract class AbstractBox extends Mesh { /** * Convert the indices into the list of vertices that define the box's geometry. */ - protected abstract void duUpdateGeometryIndices(); + protected abstract void doUpdateGeometryIndices(); /** * Update the normals of each of the box's planes. */ - protected abstract void duUpdateGeometryNormals(); + protected abstract void doUpdateGeometryNormals(); /** * Update the points that define the texture of the box. @@ -101,14 +99,14 @@ public abstract class AbstractBox extends Mesh { * It's a one-to-one ratio, where each plane of the box has its own copy * of the texture. That is, the texture is repeated one time for each face. */ - protected abstract void duUpdateGeometryTextures(); + protected abstract void doUpdateGeometryTextures(); /** * Update the position of the vertices that define the box. *

* These eight points are determined from the minimum and maximum point. */ - protected abstract void duUpdateGeometryVertices(); + protected abstract void doUpdateGeometryVertices(); /** * Get the center point of this box. @@ -145,10 +143,10 @@ public abstract class AbstractBox extends Mesh { * need to call this method afterwards in order to update the box. */ public final void updateGeometry() { - duUpdateGeometryVertices(); - duUpdateGeometryNormals(); - duUpdateGeometryTextures(); - duUpdateGeometryIndices(); + doUpdateGeometryVertices(); + doUpdateGeometryNormals(); + doUpdateGeometryTextures(); + doUpdateGeometryIndices(); setStatic(); } diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Box.java b/jme3-core/src/main/java/com/jme3/scene/shape/Box.java index f94611de3..15b25d24b 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Box.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Box.java @@ -35,6 +35,7 @@ package com.jme3.scene.shape; import com.jme3.math.Vector3f; import com.jme3.scene.VertexBuffer.Type; import com.jme3.util.BufferUtils; + import java.nio.FloatBuffer; /** @@ -144,25 +145,25 @@ public class Box extends AbstractBox { return new Box(center.clone(), xExtent, yExtent, zExtent); } - protected void duUpdateGeometryIndices() { + protected void doUpdateGeometryIndices() { if (getBuffer(Type.Index) == null){ setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA)); } } - protected void duUpdateGeometryNormals() { + protected void doUpdateGeometryNormals() { if (getBuffer(Type.Normal) == null){ setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(GEOMETRY_NORMALS_DATA)); } } - protected void duUpdateGeometryTextures() { + protected void doUpdateGeometryTextures() { if (getBuffer(Type.TexCoord) == null){ setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA)); } } - protected void duUpdateGeometryVertices() { + protected void doUpdateGeometryVertices() { FloatBuffer fpb = BufferUtils.createVector3Buffer(24); Vector3f[] v = computeVertices(); fpb.put(new float[] { diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java b/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java index 312650caa..5ac256e93 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java @@ -35,6 +35,7 @@ package com.jme3.scene.shape; import com.jme3.math.Vector3f; import com.jme3.scene.VertexBuffer.Type; import com.jme3.util.BufferUtils; + import java.nio.FloatBuffer; /** @@ -138,13 +139,13 @@ public class StripBox extends AbstractBox { return new StripBox(center.clone(), xExtent, yExtent, zExtent); } - protected void duUpdateGeometryIndices() { + protected void doUpdateGeometryIndices() { if (getBuffer(Type.Index) == null){ setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA)); } } - protected void duUpdateGeometryNormals() { + protected void doUpdateGeometryNormals() { if (getBuffer(Type.Normal) == null){ float[] normals = new float[8 * 3]; @@ -163,13 +164,13 @@ public class StripBox extends AbstractBox { } } - protected void duUpdateGeometryTextures() { + protected void doUpdateGeometryTextures() { if (getBuffer(Type.TexCoord) == null){ setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA)); } } - protected void duUpdateGeometryVertices() { + protected void doUpdateGeometryVertices() { FloatBuffer fpb = BufferUtils.createVector3Buffer(8 * 3); Vector3f[] v = computeVertices(); fpb.put(new float[] { diff --git a/jme3-core/src/main/java/com/jme3/shader/BufferObject.java b/jme3-core/src/main/java/com/jme3/shader/BufferObject.java new file mode 100644 index 000000000..886f4a1ab --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/BufferObject.java @@ -0,0 +1,828 @@ +package com.jme3.shader; + +import com.jme3.math.*; +import com.jme3.renderer.Caps; +import com.jme3.renderer.Renderer; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObject; +import com.jme3.util.SafeArrayList; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The base implementation of BO. + * + * @author JavaSaBr + */ +public class BufferObject extends NativeObject { + + private static final Map, VarType> CLASS_TO_VAR_TYPE = new HashMap<>(); + + static { + CLASS_TO_VAR_TYPE.put(Float.class, VarType.Float); + CLASS_TO_VAR_TYPE.put(Integer.class, VarType.Int); + CLASS_TO_VAR_TYPE.put(Boolean.class, VarType.Boolean); + CLASS_TO_VAR_TYPE.put(Vector2f.class, VarType.Vector2); + CLASS_TO_VAR_TYPE.put(Vector3f.class, VarType.Vector3); + CLASS_TO_VAR_TYPE.put(ColorRGBA.class, VarType.Vector4); + CLASS_TO_VAR_TYPE.put(Quaternion.class, VarType.Vector4); + CLASS_TO_VAR_TYPE.put(Vector4f.class, VarType.Vector4); + + CLASS_TO_VAR_TYPE.put(Vector2f[].class, VarType.Vector2Array); + CLASS_TO_VAR_TYPE.put(Vector3f[].class, VarType.Vector3Array); + CLASS_TO_VAR_TYPE.put(Vector4f[].class, VarType.Vector4Array); + CLASS_TO_VAR_TYPE.put(ColorRGBA[].class, VarType.Vector4Array); + CLASS_TO_VAR_TYPE.put(Quaternion[].class, VarType.Vector4Array); + + CLASS_TO_VAR_TYPE.put(Matrix3f.class, VarType.Matrix3); + CLASS_TO_VAR_TYPE.put(Matrix4f.class, VarType.Matrix4); + CLASS_TO_VAR_TYPE.put(Matrix3f[].class, VarType.Matrix3Array); + CLASS_TO_VAR_TYPE.put(Matrix4f[].class, VarType.Matrix4Array); + } + + protected static VarType getVarTypeByValue(final Object value) { + + final VarType varType = CLASS_TO_VAR_TYPE.get(value.getClass()); + if (varType != null) { + return varType; + } else if (value instanceof Collection && ((Collection) value).isEmpty()) { + throw new IllegalArgumentException("Can't calculate a var type for the empty collection value[" + value + "]."); + } else if (value instanceof List) { + return getVarTypeByValue(((List) value).get(0)); + } else if (value instanceof Collection) { + return getVarTypeByValue(((Collection) value).iterator().next()); + } + + throw new IllegalArgumentException("Can't calculate a var type for the value " + value); + } + + public enum Layout { + std140, + /** unsupported yet */ + @Deprecated + std430, + } + + public enum BufferType { + ShaderStorageBufferObject(Caps.ShaderStorageBufferObject), + UniformBufferObject(Caps.UniformBufferObject), + ; + + private final Caps requiredCaps; + + BufferType(final Caps requiredCaps) { + this.requiredCaps = requiredCaps; + } + + /** + * Get the required caps. + * + * @return the required caps. + */ + public Caps getRequiredCaps() { + return requiredCaps; + } + } + + /** + * The fields of this BO. + */ + private final Map fields; + + /** + * The field's array. + */ + private final SafeArrayList fieldArray; + + /** + * The buffer's data layout. + */ + private final Layout layout; + + /** + * The binding number. + */ + private final int binding; + + /** + * The buffer's type. + */ + private BufferType bufferType; + + /** + * The previous data buffer. + */ + private ByteBuffer previousData; + + public BufferObject(final int binding, final Layout layout, final BufferType bufferType) { + this.handleRef = new Object(); + this.bufferType = bufferType; + this.binding = binding; + this.layout = layout; + this.fields = new HashMap<>(); + this.fieldArray = new SafeArrayList<>(BufferObjectField.class); + } + + public BufferObject(final int binding, final Layout layout) { + this(binding, layout, BufferType.UniformBufferObject); + } + + public BufferObject(final int binding, final BufferType bufferType) { + this(binding, Layout.std140, bufferType); + } + + public BufferObject(final BufferType bufferType) { + this(1, Layout.std140, bufferType); + } + + public BufferObject(final Layout layout) { + this(1, layout, BufferType.UniformBufferObject); + } + + public BufferObject(final int binding) { + this(binding, Layout.std140, BufferType.UniformBufferObject); + } + + public BufferObject() { + this(1, Layout.std140, BufferType.UniformBufferObject); + } + + private BufferObject(final Void unused, final int id) { + super(id); + this.fieldArray = null; + this.fields = null; + this.layout = null; + this.binding = 0; + } + + /** + * Declares a filed in this BO. + * + * @param name the field's name. + * @param varType the field's type. + */ + public void declareField(final String name, final VarType varType) { + + if (fields.containsKey(name)) { + throw new IllegalArgumentException("The field " + name + " is already declared."); + } + + final BufferObjectField field = new BufferObjectField(name, varType); + + fields.put(name, field); + fieldArray.add(field); + } + + /** + * Gets the buffer's type. + * + * @return the buffer's type. + */ + public BufferType getBufferType() { + return bufferType; + } + + /** + * Sets the buffer's type. + * + * @param bufferType the buffer's type. + */ + public void setBufferType(final BufferType bufferType) { + + if (getId() != -1) { + throw new IllegalStateException("Can't change buffer's type when this buffer is already initialized."); + } + + this.bufferType = bufferType; + } + + /** + * Sets the value to the filed by the field's name. + * + * @param name the field's name. + * @param value the value. + */ + public void setFieldValue(final String name, final Object value) { + + BufferObjectField field = fields.get(name); + + if (field == null) { + declareField(name, getVarTypeByValue(value)); + field = fields.get(name); + } + + field.setValue(value); + setUpdateNeeded(); + } + + /** + * Gets the current value of the field by the name. + * + * @param name the field name. + * @param the value's type. + * @return the current value. + */ + public T getFieldValue(final String name) { + + final BufferObjectField field = fields.get(name); + if (field == null) { + throw new IllegalArgumentException("Unknown a field with the name " + name); + } + + return (T) field.getValue(); + } + + /** + * Get the binding number. + * + * @return the binding number. + */ + public int getBinding() { + return binding; + } + + @Override + public void resetObject() { + this.id = -1; + setUpdateNeeded(); + } + + /** + * Computes the current binary data of this BO. + * + * @param maxSize the max data size. + * @return the current binary data of this BO. + */ + public ByteBuffer computeData(final int maxSize) { + + int estimateSize = 0; + + for (final BufferObjectField field : fieldArray) { + estimateSize += estimateSize(field); + } + + if(maxSize < estimateSize) { + throw new IllegalStateException("The estimated size(" + estimateSize + ") of this BO is bigger than " + + "maximum available size " + maxSize); + } + + if (previousData != null) { + if (previousData.capacity() < estimateSize) { + BufferUtils.destroyDirectBuffer(previousData); + previousData = null; + } else { + previousData.clear(); + } + } + + final ByteBuffer data = previousData == null ? BufferUtils.createByteBuffer(estimateSize) : previousData; + + for (final BufferObjectField field : fieldArray) { + writeField(field, data); + } + + data.flip(); + + this.previousData = data; + + return data; + } + + /** + * Estimates size of the field. + * + * @param field the field. + * @return the estimated size. + */ + protected int estimateSize(final BufferObjectField field) { + + switch (field.getType()) { + case Float: + case Int: { + if (layout == Layout.std140) { + return 16; + } + return 4; + } + case Boolean: { + if (layout == Layout.std140) { + return 16; + } + return 1; + } + case Vector2: { + return 4 * 2; + } + case Vector3: { + final int multiplier = layout == Layout.std140 ? 4 : 3; + return 4 * multiplier; + } + case Vector4: + return 16; + case IntArray: { + return estimate((int[]) field.getValue()); + } + case FloatArray: { + return estimate((float[]) field.getValue()); + } + case Vector2Array: { + return estimateArray(field.getValue(), 8); + } + case Vector3Array: { + final int multiplier = layout == Layout.std140 ? 16 : 12; + return estimateArray(field.getValue(), multiplier); + } + case Vector4Array: { + return estimateArray(field.getValue(), 16); + } + case Matrix3: { + final int multiplier = layout == Layout.std140 ? 16 : 12; + return 3 * 3 * multiplier; + } + case Matrix4: { + return 4 * 4 * 4; + } + case Matrix3Array: { + int multiplier = layout == Layout.std140 ? 16 : 12; + multiplier = 3 * 3 * multiplier; + return estimateArray(field.getValue(), multiplier); + } + case Matrix4Array: { + final int multiplier = 4 * 4 * 16; + return estimateArray(field.getValue(), multiplier); + } + default: { + throw new IllegalArgumentException("The type of BO field " + field.getType() + " doesn't support."); + } + } + } + + /** + * Estimates bytes count to present the value on GPU. + * + * @param value the value. + * @param multiplier the multiplier. + * @return the estimated bytes cunt. + */ + protected int estimateArray(final Object value, final int multiplier) { + + if (value instanceof Object[]) { + return ((Object[]) value).length * multiplier; + } else if (value instanceof Collection) { + return ((Collection) value).size() * multiplier; + } + + throw new IllegalArgumentException("Unexpected value " + value); + } + + /** + * Estimates bytes count to present the values on GPU. + * + * @param values the values. + * @return the estimated bytes cunt. + */ + protected int estimate(final float[] values) { + return values.length * 4; + } + + /** + * Estimates bytes count to present the values on GPU. + * + * @param values the values. + * @return the estimated bytes cunt. + */ + protected int estimate(final int[] values) { + return values.length * 4; + } + + /** + * Writes the field to the data buffer. + * + * @param field the field. + * @param data the data buffer. + */ + protected void writeField(final BufferObjectField field, final ByteBuffer data) { + + final Object value = field.getValue(); + + switch (field.getType()) { + case Int: { + data.putInt(((Number) value).intValue()); + if (layout == Layout.std140) { + data.putInt(0); + data.putLong(0); + } + break; + } + case Float: { + data.putFloat(((Number) value).floatValue()); + if (layout == Layout.std140) { + data.putInt(0); + data.putLong(0); + } + break; + } + case Boolean: + data.put((byte) (((Boolean) value) ? 1 : 0)); + if (layout == Layout.std140) { + data.putInt(0); + data.putLong(0); + data.putShort((short) 0); + data.put((byte) 0); + } + break; + case Vector2: + write(data, (Vector2f) value); + break; + case Vector3: + write(data, (Vector3f) value); + break; + case Vector4: + writeVec4(data, value); + break; + case IntArray: { + write(data, (int[]) value); + break; + } + case FloatArray: { + write(data, (float[]) value); + break; + } + case Vector2Array: { + writeVec2Array(data, value); + break; + } + case Vector3Array: { + writeVec3Array(data, value); + break; + } + case Vector4Array: { + writeVec4Array(data, value); + break; + } + case Matrix3: { + write(data, (Matrix3f) value); + break; + } + case Matrix4: { + write(data, (Matrix4f) value); + break; + } + case Matrix3Array: { + writeMat3Array(data, value); + break; + } + case Matrix4Array: { + writeMat4Array(data, value); + break; + } + default: { + throw new IllegalArgumentException("The type of BO field " + field.getType() + " doesn't support."); + } + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void writeMat3Array(final ByteBuffer data, final Object value) { + + if (value instanceof Matrix3f[]) { + + final Matrix3f[] values = (Matrix3f[]) value; + for (final Matrix3f mat : values) { + write(data, mat); + } + + } else if(value instanceof SafeArrayList) { + + final SafeArrayList values = (SafeArrayList) value; + for (final Matrix3f mat : values.getArray()) { + write(data, mat); + } + + } else if(value instanceof Collection) { + + final Collection values = (Collection) value; + for (final Matrix3f mat : values) { + write(data, mat); + } + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void writeMat4Array(final ByteBuffer data, final Object value) { + + if (value instanceof Matrix4f[]) { + + final Matrix4f[] values = (Matrix4f[]) value; + for (final Matrix4f mat : values) { + write(data, mat); + } + + } else if(value instanceof SafeArrayList) { + + final SafeArrayList values = (SafeArrayList) value; + for (final Matrix4f mat : values.getArray()) { + write(data, mat); + } + + } else if(value instanceof Collection) { + + final Collection values = (Collection) value; + for (final Matrix4f mat : values) { + write(data, mat); + } + } + } + + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void writeVec4Array(final ByteBuffer data, final Object value) { + + if (value instanceof Object[]) { + + final Object[] values = (Object[]) value; + for (final Object vec : values) { + writeVec4(data, vec); + } + + } else if(value instanceof SafeArrayList) { + + final SafeArrayList values = (SafeArrayList) value; + for (final Object vec : values.getArray()) { + writeVec4(data, vec); + } + + } else if(value instanceof Collection) { + + final Collection values = (Collection) value; + for (final Object vec : values) { + writeVec4(data, vec); + } + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void writeVec3Array(final ByteBuffer data, final Object value) { + + if (value instanceof Vector3f[]) { + + final Vector3f[] values = (Vector3f[]) value; + for (final Vector3f vec : values) { + write(data, vec); + } + + } else if(value instanceof SafeArrayList) { + + final SafeArrayList values = (SafeArrayList) value; + for (final Vector3f vec : values.getArray()) { + write(data, vec); + } + + } else if(value instanceof Collection) { + + final Collection values = (Collection) value; + for (final Vector3f vec : values) { + write(data, vec); + } + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void writeVec2Array(final ByteBuffer data, final Object value) { + + if (value instanceof Vector2f[]) { + + final Vector2f[] values = (Vector2f[]) value; + for (final Vector2f vec : values) { + write(data, vec); + } + + } else if(value instanceof SafeArrayList) { + + final SafeArrayList values = (SafeArrayList) value; + for (final Vector2f vec : values.getArray()) { + write(data, vec); + } + + } else if(value instanceof Collection) { + + final Collection values = (Collection) value; + for (final Vector2f vec : values) { + write(data, vec); + } + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void write(final ByteBuffer data, final float[] value) { + for (float val : value) { + data.putFloat(val); + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void write(final ByteBuffer data, final int[] value) { + for (int val : value) { + data.putInt(val); + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void writeVec4(final ByteBuffer data, final Object value) { + + if (value == null) { + data.putLong(0).putLong(0); + } else if (value instanceof Vector4f) { + + final Vector4f vec4 = (Vector4f) value; + data.putFloat(vec4.getX()) + .putFloat(vec4.getY()) + .putFloat(vec4.getZ()) + .putFloat(vec4.getW()); + + } else if(value instanceof Quaternion) { + + final Quaternion vec4 = (Quaternion) value; + data.putFloat(vec4.getX()) + .putFloat(vec4.getY()) + .putFloat(vec4.getZ()) + .putFloat(vec4.getW()); + + } else if(value instanceof ColorRGBA) { + + final ColorRGBA vec4 = (ColorRGBA) value; + data.putFloat(vec4.getRed()) + .putFloat(vec4.getGreen()) + .putFloat(vec4.getBlue()) + .putFloat(vec4.getAlpha()); + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void write(final ByteBuffer data, final Vector3f value) { + + if (value == null) { + data.putLong(0).putInt(0); + } else { + data.putFloat(value.getX()) + .putFloat(value.getY()) + .putFloat(value.getZ()); + } + + if (layout == Layout.std140) { + data.putInt(0); + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param x the x value. + * @param y the y value. + * @param z the z value. + */ + protected void write(final ByteBuffer data, final float x, final float y, final float z) { + + data.putFloat(x) + .putFloat(y) + .putFloat(z); + + if (layout == Layout.std140) { + data.putInt(0); + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param x the x value. + * @param y the y value. + * @param z the z value. + * @param w the w value. + */ + protected void write(final ByteBuffer data, final float x, final float y, final float z, final float w) { + data.putFloat(x) + .putFloat(y) + .putFloat(z) + .putFloat(w); + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void write(final ByteBuffer data, final Vector2f value) { + if (value == null) { + data.putLong(0); + } else { + data.putFloat(value.getX()).putFloat(value.getY()); + } + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void write(final ByteBuffer data, final Matrix3f value) { + write(data, value.get(0, 0), value.get(1, 0), value.get(2, 0)); + write(data, value.get(0, 1), value.get(1, 1), value.get(2, 1)); + write(data, value.get(0, 2), value.get(1, 2), value.get(2, 2)); + } + + /** + * Writes the value to the data buffer. + * + * @param data the data buffer. + * @param value the value. + */ + protected void write(final ByteBuffer data, final Matrix4f value) { + write(data, value.get(0, 0), value.get(1, 0), value.get(2, 0), value.get(3, 0)); + write(data, value.get(0, 1), value.get(1, 1), value.get(2, 1), value.get(3, 1)); + write(data, value.get(0, 2), value.get(1, 2), value.get(2, 2), value.get(3, 2)); + write(data, value.get(0, 3), value.get(1, 3), value.get(2, 3), value.get(3, 3)); + } + + @Override + public void deleteObject(final Object rendererObject) { + + if (!(rendererObject instanceof Renderer)) { + throw new IllegalArgumentException("This bo can't be deleted from " + rendererObject); + } + + ((Renderer) rendererObject).deleteBuffer(this); + } + + @Override + public NativeObject createDestructableClone() { + return new BufferObject(null, getId()); + } + + @Override + protected void deleteNativeBuffers() { + super.deleteNativeBuffers(); + if (previousData != null) { + BufferUtils.destroyDirectBuffer(previousData); + previousData = null; + } + } + + @Override + public long getUniqueId() { + return ((long) OBJTYPE_BO << 32) | ((long) id); + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java b/jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java new file mode 100644 index 000000000..798b418fc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java @@ -0,0 +1,77 @@ +package com.jme3.shader; + +import static java.util.Objects.requireNonNull; + +/** + * The class to describe a filed in BO. + * + * @author JavaSaBr + */ +public class BufferObjectField { + + + /** + * The field name. + */ + private final String name; + + /** + * The field type. + */ + private final VarType type; + + /** + * The field value. + */ + private Object value; + + public BufferObjectField(final String name, final VarType type) { + this.name = name; + this.type = type; + } + + /** + * Get the field name. + * + * @return the field name. + */ + public String getName() { + return name; + } + + /** + * Gets the field type. + * + * @return the field type. + */ + public VarType getType() { + return type; + } + + /** + * Gets the field value. + * + * @return the field value. + */ + public Object getValue() { + return value; + } + + /** + * Sets the field value. + * + * @param value the field value. + */ + public void setValue(final Object value) { + this.value = requireNonNull(value, "The field's value can't be null."); + } + + @Override + public String toString() { + return "BufferObjectField{" + + "name='" + name + '\'' + + ", type=" + type + + ", value=" + value + + '}'; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/Shader.java b/jme3-core/src/main/java/com/jme3/shader/Shader.java index 72ecbca17..05264f8db 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Shader.java +++ b/jme3-core/src/main/java/com/jme3/shader/Shader.java @@ -51,6 +51,11 @@ public final class Shader extends NativeObject { * Maps uniform name to the uniform variable. */ private final ListMap uniforms; + + /** + * Maps storage block name to the buffer block variables. + */ + private final ListMap bufferBlocks; /** * Uniforms bound to {@link UniformBinding}s. @@ -220,10 +225,11 @@ public final class Shader extends NativeObject { */ public Shader(){ super(); - shaderSourceList = new ArrayList(); - uniforms = new ListMap(); - attribs = new IntMap(); - boundUniforms = new ArrayList(); + shaderSourceList = new ArrayList<>(); + uniforms = new ListMap<>(); + bufferBlocks = new ListMap<>(); + attribs = new IntMap<>(); + boundUniforms = new ArrayList<>(); } /** @@ -240,6 +246,7 @@ public final class Shader extends NativeObject { } uniforms = null; + bufferBlocks = null; boundUniforms = null; attribs = null; } @@ -288,10 +295,40 @@ public final class Shader extends NativeObject { return uniform; } + /** + * Gets or creates a buffer block by the name. + * + * @param name the buffer block's name. + * @return the buffer block. + */ + public ShaderBufferBlock getBufferBlock(final String name) { + + assert name.startsWith("m_"); + + ShaderBufferBlock block = bufferBlocks.get(name); + + if (block == null) { + block = new ShaderBufferBlock(); + block.name = name; + bufferBlocks.put(name, block); + } + + return block; + } + public void removeUniform(String name){ uniforms.remove(name); } + /** + * Removes a buffer block by the name. + * + * @param name the buffer block's name. + */ + public void removeBufferBlock(final String name){ + bufferBlocks.remove(name); + } + public Attribute getAttribute(VertexBuffer.Type attribType){ int ordinal = attribType.ordinal(); Attribute attrib = attribs.get(ordinal); @@ -306,7 +343,16 @@ public final class Shader extends NativeObject { public ListMap getUniformMap(){ return uniforms; } - + + /** + * Get the buffer blocks map. + * + * @return the buffer blocks map. + */ + public ListMap getBufferBlockMap() { + return bufferBlocks; + } + public ArrayList getBoundUniforms() { return boundUniforms; } @@ -320,6 +366,7 @@ public final class Shader extends NativeObject { return getClass().getSimpleName() + "[numSources=" + shaderSourceList.size() + ", numUniforms=" + uniforms.size() + + ", numBufferBlocks=" + bufferBlocks.size() + ", shaderSources=" + getSources() + "]"; } @@ -343,7 +390,7 @@ public final class Shader extends NativeObject { * Resets all uniforms that do not have the "set-by-current-material" flag * to their default value (usually all zeroes or false). * When a uniform is modified, that flag is set, to remove the flag, - * use {@link #clearUniformsSetByCurrent() }. + * use {@link #clearUniformsSetByCurrentFlag() }. */ public void resetUniformsNotSetByCurrent() { int size = uniforms.size(); @@ -366,6 +413,11 @@ public final class Shader extends NativeObject { uniform.reset(); // fixes issue with re-initialization } } + if (bufferBlocks != null) { + for (ShaderBufferBlock shaderBufferBlock : bufferBlocks.values()) { + shaderBufferBlock.reset(); + } + } if (attribs != null) { for (Entry entry : attribs) { entry.getValue().location = ShaderVariable.LOC_UNKNOWN; diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java b/jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java new file mode 100644 index 000000000..27b44d18b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader; + +/** + * Implementation of shader's buffer block. + * + * @author JavaSaBr + */ +public class ShaderBufferBlock extends ShaderVariable { + + /** + * Current used buffer object. + */ + protected BufferObject bufferObject; + + /** + * Set the new buffer object. + * + * @param bufferObject the new buffer object. + */ + public void setBufferObject(final BufferObject bufferObject) { + + if (bufferObject == null) { + throw new IllegalArgumentException("for storage block " + name + ": storageData cannot be null"); + } + + this.bufferObject = bufferObject; + + updateNeeded = true; + } + + /** + * Return true if need to update this storage block. + * + * @return true if need to update this storage block. + */ + public boolean isUpdateNeeded(){ + return updateNeeded; + } + + /** + * Clear the flag {@link #isUpdateNeeded()}. + */ + public void clearUpdateNeeded(){ + updateNeeded = false; + } + + /** + * Reset this storage block. + */ + public void reset(){ + updateNeeded = true; + } + + /** + * Get the current storage data. + * + * @return the current storage data. + */ + public BufferObject getBufferObject() { + return bufferObject; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/VarType.java b/jme3-core/src/main/java/com/jme3/shader/VarType.java index 7300294d4..2319e7903 100644 --- a/jme3-core/src/main/java/com/jme3/shader/VarType.java +++ b/jme3-core/src/main/java/com/jme3/shader/VarType.java @@ -57,7 +57,8 @@ public enum VarType { Texture3D(false,true,"sampler3D"), TextureArray(false,true,"sampler2DArray|sampler2DArrayShadow"), TextureCubeMap(false,true,"samplerCube"), - Int("int"); + Int("int"), + BufferObject(false, false, "custom"); private boolean usesMultiData = false; private boolean textureType = false; diff --git a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java index 5e89fa76a..f3d5c952a 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java @@ -57,7 +57,7 @@ import java.io.IOException; * are from the camera, the smaller they are to maximize the resolution used of * the shadow map.
This results in a better quality shadow than standard * shadow mapping.
for more informations on this read this http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
+ * href="https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html">https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html
*

* @author Rémy Bouquet aka Nehon */ @@ -83,7 +83,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer { /** * Create a DirectionalLightShadowRenderer More info on the technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html + * href="https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html">https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html * * @param assetManager the application asset manager * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, diff --git a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java index 1eb2b1533..3c289ecc3 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java +++ b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java @@ -31,9 +31,6 @@ */ package com.jme3.system; -import java.nio.ByteBuffer; -import java.util.EnumSet; - import com.jme3.light.LightList; import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; @@ -44,12 +41,16 @@ import com.jme3.renderer.Renderer; import com.jme3.renderer.Statistics; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; +import com.jme3.shader.BufferObject; import com.jme3.shader.Shader; import com.jme3.shader.Shader.ShaderSource; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; import com.jme3.texture.Texture; + +import java.nio.ByteBuffer; import java.util.EnumMap; +import java.util.EnumSet; public class NullRenderer implements Renderer { @@ -148,9 +149,17 @@ public class NullRenderer implements Renderer { public void updateBufferData(VertexBuffer vb) { } + @Override + public void updateBufferData(BufferObject bo) { + } public void deleteBuffer(VertexBuffer vb) { } + @Override + public void deleteBuffer(BufferObject bo) { + + } + public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { } diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java index 39c7725c0..fde8d4ecb 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -258,6 +258,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { * half-precision floating point red, green, and blue. * * Requires {@link Caps#FloatTexture}. + * May be supported for renderbuffers, but the OpenGL specification does not require it. */ RGB16F(48,true), @@ -272,6 +273,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { * single-precision floating point red, green, and blue. * * Requires {@link Caps#FloatTexture}. + * May be supported for renderbuffers, but the OpenGL specification does not require it. */ RGB32F(96,true), @@ -300,31 +302,190 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { * Requires {@link Caps#TextureCompressionETC1}. */ ETC1(4, false, true, false), - - R8I(8), - R8UI(8), - R16I(16), - R16UI(16), - R32I(32), - R32UI(32), - RG8I(16), - RG8UI(16), - RG16I(32), - RG16UI(32), - RG32I(64), - RG32UI(64), - RGB8I(24), - RGB8UI(24), - RGB16I(48), - RGB16UI(48), - RGB32I(96), - RGB32UI(96), + + /** + * 8 bit signed int red. + * + * Requires {@link Caps#IntegerTexture}. + */ + R8I(8), + /** + * 8 bit unsigned int red. + * + * Requires {@link Caps#IntegerTexture}. + */ + R8UI(8), + /** + * 16 bit signed int red. + * + * Requires {@link Caps#IntegerTexture}. + */ + R16I(16), + /** + * 16 bit unsigned int red. + * + * Requires {@link Caps#IntegerTexture}. + */ + R16UI(16), + /** + * 32 bit signed int red. + * + * Requires {@link Caps#IntegerTexture}. + */ + R32I(32), + /** + * 32 bit unsigned int red. + * + * Requires {@link Caps#IntegerTexture}. + */ + R32UI(32), + + + /** + * 8 bit signed int red and green. + * + * Requires {@link Caps#IntegerTexture}. + */ + RG8I(16), + /** + * 8 bit unsigned int red and green. + * + * Requires {@link Caps#IntegerTexture}. + */ + RG8UI(16), + /** + * 16 bit signed int red and green. + * + * Requires {@link Caps#IntegerTexture}. + */ + RG16I(32), + /** + * 16 bit unsigned int red and green. + * + * Requires {@link Caps#IntegerTexture}. + */ + RG16UI(32), + /** + * 32 bit signed int red and green. + * + * Requires {@link Caps#IntegerTexture}. + */ + RG32I(64), + /** + * 32 bit unsigned int red and green. + * + * Requires {@link Caps#IntegerTexture}. + */ + RG32UI(64), + + /** + * 8 bit signed int red, green and blue. + * + * Requires {@link Caps#IntegerTexture} to be supported for textures. + * May be supported for renderbuffers, but the OpenGL specification does not require it. + */ + RGB8I(24), + /** + * 8 bit unsigned int red, green and blue. + * + * Requires {@link Caps#IntegerTexture} to be supported for textures. + * May be supported for renderbuffers, but the OpenGL specification does not require it. + */ + RGB8UI(24), + /** + * 16 bit signed int red, green and blue. + * + * Requires {@link Caps#IntegerTexture} to be supported for textures. + * May be supported for renderbuffers, but the OpenGL specification does not require it. + */ + RGB16I(48), + /** + * 16 bit unsigned int red, green and blue. + * + * Requires {@link Caps#IntegerTexture} to be supported for textures. + * May be supported for renderbuffers, but the OpenGL specification does not require it. + */ + RGB16UI(48), + /** + * 32 bit signed int red, green and blue. + * + * Requires {@link Caps#IntegerTexture} to be supported for textures. + * May be supported for renderbuffers, but the OpenGL specification does not require it. + */ + RGB32I(96), + /** + * 32 bit unsigned int red, green and blue. + * + * Requires {@link Caps#IntegerTexture} to be supported for textures. + * May be supported for renderbuffers, but the OpenGL specification does not require it. + */ + RGB32UI(96), + + + /** + * 8 bit signed int red, green, blue and alpha. + * + * Requires {@link Caps#IntegerTexture}. + */ RGBA8I(32), - RGBA8UI(32), - RGBA16I(64), - RGBA16UI(64), - RGBA32I(128), - RGBA32UI(128) + /** + * 8 bit unsigned int red, green, blue and alpha. + * + * Requires {@link Caps#IntegerTexture}. + */ + RGBA8UI(32), + /** + * 16 bit signed int red, green, blue and alpha. + * + * Requires {@link Caps#IntegerTexture}. + */ + RGBA16I(64), + /** + * 16 bit unsigned int red, green, blue and alpha. + * + * Requires {@link Caps#IntegerTexture}. + */ + RGBA16UI(64), + /** + * 32 bit signed int red, green, blue and alpha. + * + * Requires {@link Caps#IntegerTexture}. + */ + RGBA32I(128), + /** + * 32 bit unsigned int red, green, blue and alpha. + * + * Requires {@link Caps#IntegerTexture}. + */ + RGBA32UI(128), + + /** + * half-precision floating point red. + * + * Requires {@link Caps#FloatTexture}. + */ + R16F(16,true), + + /** + * single-precision floating point red. + * + * Requires {@link Caps#FloatTexture}. + */ + R32F(32,true), + + /** + * half-precision floating point red and green. + * + * Requires {@link Caps#FloatTexture}. + */ + RG16F(32,true), + + /** + * single-precision floating point red and green. + * + * Requires {@link Caps#FloatTexture}. + */ + RG32F(64,true), ; private int bpp; @@ -574,14 +735,14 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { } /** - * @see {@link #Image(com.jme3.texture.Image.Format, int, int, int, java.util.ArrayList, int[], boolean)} + * @see {@link #Image(com.jme3.texture.Image.Format, int, int, int, java.util.ArrayList, int[], com.jme3.texture.image.ColorSpace)} * @param format * @param width * @param height * @param depth * @param data * @param mipMapSizes - * @deprecated use {@link #Image(com.jme3.texture.Image.Format, int, int, int, java.util.ArrayList, int[], boolean)} + * @deprecated use {@link #Image(com.jme3.texture.Image.Format, int, int, int, java.util.ArrayList, int[], com.jme3.texture.image.ColorSpace)} */ @Deprecated public Image(Format format, int width, int height, int depth, ArrayList data, @@ -630,13 +791,13 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { } /** - * @see {@link #Image(com.jme3.texture.Image.Format, int, int, java.nio.ByteBuffer, int[], boolean)} + * @see {@link #Image(com.jme3.texture.Image.Format, int, int, java.nio.ByteBuffer, int[], com.jme3.texture.image.ColorSpace)} * @param format * @param width * @param height * @param data * @param mipMapSizes - * @deprecated use {@link #Image(com.jme3.texture.Image.Format, int, int, java.nio.ByteBuffer, int[], boolean)} + * @deprecated use {@link #Image(com.jme3.texture.Image.Format, int, int, java.nio.ByteBuffer, int[], com.jme3.texture.image.ColorSpace)} */ @Deprecated public Image(Format format, int width, int height, ByteBuffer data, @@ -664,13 +825,13 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { } /** - * @see {@link #Image(com.jme3.texture.Image.Format, int, int, int, java.util.ArrayList, boolean)} + * @see {@link #Image(com.jme3.texture.Image.Format, int, int, int, java.util.ArrayList, com.jme3.texture.image.ColorSpace)} * @param format * @param width * @param height * @param depth * @param data - * @deprecated use {@link #Image(com.jme3.texture.Image.Format, int, int, int, java.util.ArrayList, boolean)} + * @deprecated use {@link #Image(com.jme3.texture.Image.Format, int, int, int, java.util.ArrayList, com.jme3.texture.image.ColorSpace)} */ @Deprecated public Image(Format format, int width, int height, int depth, ArrayList data) { @@ -698,12 +859,12 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { /** - * @see {@link #Image(com.jme3.texture.Image.Format, int, int, java.nio.ByteBuffer, boolean)} + * @see {@link #Image(com.jme3.texture.Image.Format, int, int, java.nio.ByteBuffer, com.jme3.texture.image.ColorSpace)} * @param format * @param width * @param height * @param data - * @deprecated use {@link #Image(com.jme3.texture.Image.Format, int, int, java.nio.ByteBuffer, boolean)} + * @deprecated use {@link #Image(com.jme3.texture.Image.Format, int, int, java.nio.ByteBuffer, com.jme3.texture.image.ColorSpace)} */ @Deprecated public Image(Format format, int width, int height, ByteBuffer data) { diff --git a/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java b/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java index 01464f67b..b0f3565e4 100644 --- a/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java +++ b/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java @@ -200,7 +200,8 @@ public class MaterialDebugAppState extends AbstractAppState { assetManager.clearCache(); //creating a dummy mat with the mat def of the mat to reload - Material dummy = new Material(mat.getMaterialDef()); + // Force the reloading of the asset, otherwise the new shader code will not be applied. + Material dummy = new Material(assetManager, mat.getMaterialDef().getAssetName()); for (MatParam matParam : mat.getParams()) { dummy.setParam(matParam.getName(), matParam.getVarType(), matParam.getValue()); diff --git a/jme3-core/src/main/java/com/jme3/util/NativeObject.java b/jme3-core/src/main/java/com/jme3/util/NativeObject.java index 508e6623d..426e4c9e0 100644 --- a/jme3-core/src/main/java/com/jme3/util/NativeObject.java +++ b/jme3-core/src/main/java/com/jme3/util/NativeObject.java @@ -52,7 +52,8 @@ public abstract class NativeObject implements Cloneable { OBJTYPE_SHADERSOURCE = 5, OBJTYPE_AUDIOBUFFER = 6, OBJTYPE_AUDIOSTREAM = 7, - OBJTYPE_FILTER = 8; + OBJTYPE_FILTER = 8, + OBJTYPE_BO = 9; /** * The object manager to which this NativeObject is registered to. diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md index 9aac24591..19dbd13bc 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -113,6 +113,11 @@ MaterialDef Phong Lighting { // For hardware skinning Int NumberOfBones Matrix4Array BoneMatrices + + // For Morph animation + FloatArray MorphWeights + Int NumberOfMorphTargets + Int NumberOfTargetsBuffers //For instancing Boolean UseInstancing @@ -179,6 +184,8 @@ MaterialDef Phong Lighting { SPHERE_MAP : EnvMapAsSphereMap NUM_BONES : NumberOfBones INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } } @@ -218,6 +225,8 @@ MaterialDef Phong Lighting { SPHERE_MAP : EnvMapAsSphereMap NUM_BONES : NumberOfBones INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } } @@ -241,6 +250,8 @@ MaterialDef Phong Lighting { NUM_BONES : NumberOfBones INSTANCING : UseInstancing POINT_LIGHT : IsPointLight + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } ForcedRenderState { @@ -278,6 +289,8 @@ MaterialDef Phong Lighting { NUM_BONES : NumberOfBones INSTANCING : UseInstancing BACKFACE_SHADOWS: BackfaceShadows + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } ForcedRenderState { @@ -305,6 +318,8 @@ MaterialDef Phong Lighting { DIFFUSEMAP_ALPHA : DiffuseMap NUM_BONES : NumberOfBones INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } } @@ -327,6 +342,8 @@ MaterialDef Phong Lighting { NUM_BONES : NumberOfBones INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert index f2d4a3a89..30342c524 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert @@ -2,6 +2,8 @@ #import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Skinning.glsllib" #import "Common/ShaderLib/Lighting.glsllib" +#import "Common/ShaderLib/MorphAnim.glsllib" + #ifdef VERTEX_LIGHTING #import "Common/ShaderLib/BlinnPhongLighting.glsllib" #endif @@ -90,6 +92,14 @@ void main(){ vec3 modelSpaceTan = inTangent.xyz; #endif + #ifdef NUM_MORPH_TARGETS + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); + #else + Morph_Compute(modelSpacePos, modelSpaceNorm); + #endif + #endif + #ifdef NUM_BONES #ifndef VERTEX_LIGHTING Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag index a38cbc138..45364c3bb 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag @@ -12,7 +12,6 @@ varying vec2 texCoord; varying vec4 Color; uniform vec4 g_LightData[NB_LIGHTS]; - uniform vec3 g_CameraPosition; uniform float m_Roughness; @@ -21,11 +20,20 @@ uniform float m_Metallic; varying vec3 wPosition; -#ifdef INDIRECT_LIGHTING -// uniform sampler2D m_IntegrateBRDF; +#if NB_PROBES >= 1 uniform samplerCube g_PrefEnvMap; uniform vec3 g_ShCoeffs[9]; - uniform vec4 g_LightProbeData; + uniform mat4 g_LightProbeData; +#endif +#if NB_PROBES >= 2 + uniform samplerCube g_PrefEnvMap2; + uniform vec3 g_ShCoeffs2[9]; + uniform mat4 g_LightProbeData2; +#endif +#if NB_PROBES == 3 + uniform samplerCube g_PrefEnvMap3; + uniform vec3 g_ShCoeffs3[9]; + uniform mat4 g_LightProbeData3; #endif uniform vec4 g_AmbientLightColor; @@ -182,7 +190,7 @@ void main() { vec4 specularColor = vec4(1.0); #endif #ifdef GLOSSINESSMAP - float glossiness = texture2D(m_GlossinesMap, newTexCoord).r * m_Glossiness; + float glossiness = texture2D(m_GlossinessMap, newTexCoord).r * m_Glossiness; #else float glossiness = m_Glossiness; #endif @@ -264,34 +272,48 @@ void main() { gl_FragColor.rgb += directLighting * fallOff; } + gl_FragColor.rgb += g_AmbientLightColor.rgb * diffuseColor.rgb; + #if NB_PROBES >= 1 + vec3 color1 = vec3(0.0); + vec3 color2 = vec3(0.0); + vec3 color3 = vec3(0.0); + float weight1 = 1.0; + float weight2 = 0.0; + float weight3 = 0.0; + + float ndf = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData, g_ShCoeffs, g_PrefEnvMap, color1); + #if NB_PROBES >= 2 + float ndf2 = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData2, g_ShCoeffs2, g_PrefEnvMap2, color2); + #endif + #if NB_PROBES == 3 + float ndf3 = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData3, g_ShCoeffs3, g_PrefEnvMap3, color3); + #endif - #ifdef INDIRECT_LIGHTING - vec3 rv = reflect(-viewDir.xyz, normal.xyz); - //prallax fix for spherical bounds from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ - // g_LightProbeData.w is 1/probe radius + nbMipMaps, g_LightProbeData.xyz is the position of the lightProbe. - float invRadius = fract( g_LightProbeData.w); - float nbMipMaps = g_LightProbeData.w - invRadius; - rv = invRadius * (wPosition - g_LightProbeData.xyz) +rv; - - //horizon fade from http://marmosetco.tumblr.com/post/81245981087 - float horiz = dot(rv, norm); - float horizFadePower = 1.0 - Roughness; - horiz = clamp( 1.0 + horizFadePower * horiz, 0.0, 1.0 ); - horiz *= horiz; - - vec3 indirectDiffuse = vec3(0.0); - vec3 indirectSpecular = vec3(0.0); - indirectDiffuse = sphericalHarmonics(normal.xyz, g_ShCoeffs) * diffuseColor.rgb; - vec3 dominantR = getSpecularDominantDir( normal, rv.xyz, Roughness*Roughness ); - indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, dominantR, nbMipMaps); - indirectSpecular *= vec3(horiz); - - vec3 indirectLighting = (indirectDiffuse + indirectSpecular) * ao; - - gl_FragColor.rgb = gl_FragColor.rgb + indirectLighting * step( 0.0, g_LightProbeData.w); + #if NB_PROBES >= 2 + float invNdf = max(1.0 - ndf,0.0); + float invNdf2 = max(1.0 - ndf2,0.0); + float sumNdf = ndf + ndf2; + float sumInvNdf = invNdf + invNdf2; + #if NB_PROBES == 3 + float invNdf3 = max(1.0 - ndf3,0.0); + sumNdf += ndf3; + sumInvNdf += invNdf3; + weight3 = ((1.0 - (ndf3 / sumNdf)) / (NB_PROBES - 1)) * (invNdf3 / sumInvNdf); + #endif + + weight1 = ((1.0 - (ndf / sumNdf)) / (NB_PROBES - 1)) * (invNdf / sumInvNdf); + weight2 = ((1.0 - (ndf2 / sumNdf)) / (NB_PROBES - 1)) * (invNdf2 / sumInvNdf); + + float weightSum = weight1 + weight2 + weight3; + + weight1 /= weightSum; + weight2 /= weightSum; + weight3 /= weightSum; + #endif + gl_FragColor.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0); #endif - + #if defined(EMISSIVE) || defined (EMISSIVEMAP) #ifdef EMISSIVEMAP vec4 emissive = texture2D(m_EmissiveMap, newTexCoord); diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md index fbe8b2814..adea98778 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md @@ -28,6 +28,9 @@ MaterialDef PBR Lighting { Texture2D RoughnessMap -LINEAR //Metallic and Roughness are packed respectively in the b and g channel of a single map + // r: unspecified + // g: Roughness + // b: Metallic Texture2D MetallicRoughnessMap -LINEAR // Texture of the emissive parts of the material @@ -47,17 +50,6 @@ MaterialDef PBR Lighting { Color Specular : 1.0 1.0 1.0 1.0 Float Glossiness : 1.0 - Vector4 ProbeData - - // Prefiltered Env Map for indirect specular lighting - TextureCubeMap PrefEnvMap -LINEAR - - // Irradiance map for indirect diffuse lighting - TextureCubeMap IrradianceMap -LINEAR - - //integrate BRDF map for indirect Lighting - Texture2D IntegrateBRDF -LINEAR - // Parallax/height map Texture2D ParallaxMap -LINEAR @@ -70,6 +62,9 @@ MaterialDef PBR Lighting { //Set to true to activate Steep Parallax mapping Boolean SteepParallax + //Horizon fade + Boolean HorizonFade + // Set to Use Lightmap Texture2D LightMap @@ -110,6 +105,11 @@ MaterialDef PBR Lighting { // For hardware skinning Int NumberOfBones Matrix4Array BoneMatrices + + // For Morph animation + FloatArray MorphWeights + Int NumberOfMorphTargets + Int NumberOfTargetsBuffers //For instancing Boolean UseInstancing @@ -161,6 +161,9 @@ MaterialDef PBR Lighting { NORMAL_TYPE: NormalType VERTEX_COLOR : UseVertexColor AO_MAP: LightMapAsAOMap + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + HORIZON_FADE: HorizonFade } } @@ -185,6 +188,8 @@ MaterialDef PBR Lighting { NUM_BONES : NumberOfBones INSTANCING : UseInstancing POINT_LIGHT : IsPointLight + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } ForcedRenderState { @@ -222,6 +227,8 @@ MaterialDef PBR Lighting { NUM_BONES : NumberOfBones INSTANCING : UseInstancing BACKFACE_SHADOWS: BackfaceShadows + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } ForcedRenderState { @@ -254,6 +261,8 @@ MaterialDef PBR Lighting { NUM_BONES : NumberOfBones INSTANCING : UseInstancing BACKFACE_SHADOWS: BackfaceShadows + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } ForcedRenderState { @@ -279,6 +288,8 @@ MaterialDef PBR Lighting { Defines { NUM_BONES : NumberOfBones INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } } @@ -298,6 +309,8 @@ MaterialDef PBR Lighting { NEED_TEXCOORD1 NUM_BONES : NumberOfBones INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert index 45d484181..37ead2b7c 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert @@ -2,10 +2,9 @@ #import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Skinning.glsllib" #import "Common/ShaderLib/InPassShadows.glsllib" - +#import "Common/ShaderLib/MorphAnim.glsllib" uniform vec4 m_BaseColor; - uniform vec4 g_AmbientLightColor; varying vec2 texCoord; @@ -39,11 +38,19 @@ void main(){ vec3 modelSpaceTan = inTangent.xyz; #endif + #ifdef NUM_MORPH_TARGETS + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); + #else + Morph_Compute(modelSpacePos, modelSpaceNorm); + #endif + #endif + #ifdef NUM_BONES #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) - Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); + Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); #else - Skinning_Compute(modelSpacePos, modelSpaceNorm); + Skinning_Compute(modelSpacePos, modelSpaceNorm); #endif #endif @@ -69,4 +76,4 @@ void main(){ #ifdef VERTEX_COLOR Color *= inColor; #endif -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert index c24ee7f19..1da0c76c4 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert @@ -2,6 +2,8 @@ #import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Skinning.glsllib" #import "Common/ShaderLib/Lighting.glsllib" +#import "Common/ShaderLib/MorphAnim.glsllib" + #ifdef VERTEX_LIGHTING #import "Common/ShaderLib/BlinnPhongLighting.glsllib" #endif @@ -84,6 +86,14 @@ void main(){ vec3 modelSpaceTan = inTangent.xyz; #endif + #ifdef NUM_MORPH_TARGETS + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); + #else + Morph_Compute(modelSpacePos, modelSpaceNorm); + #endif + #endif + #ifdef NUM_BONES #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Billboard.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Billboard.j3md new file mode 100644 index 000000000..f97b3c736 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Billboard.j3md @@ -0,0 +1,78 @@ +MaterialDef Billboard { + MaterialParameters { + Float SpriteHeight : 10 + Texture2D Texture + } + + Technique { + WorldParameters { + WorldViewMatrix + ProjectionMatrix + WorldMatrix + CameraDirection + ViewPort + CameraPosition + } + + VertexShaderNodes { + ShaderNode TexCoord { + Definition: AttributeToVarying: Common/MatDefs/ShaderNodes/Basic/AttributeToVarying.j3sn + InputMappings { + vec2Variable = Attr.inTexCoord + vec4Variable = Attr.inColor + } + OutputMappings { + } + } + ShaderNode FixedScale { + Definition: FixedScale: Common/MatDefs/ShaderNodes/Common/FixedScale.j3sn + InputMappings { + projectionMatrix = WorldParam.ProjectionMatrix + worldMatrix = WorldParam.WorldMatrix + cameraDir = WorldParam.CameraDirection + viewport = WorldParam.ViewPort + modelPosition = Attr.inPosition + cameraPos = WorldParam.CameraPosition + spriteHeight = MatParam.SpriteHeight + } + OutputMappings { + } + } + ShaderNode Billboard { + Definition: Billboard: Common/MatDefs/ShaderNodes/Common/Billboard.j3sn + InputMappings { + worldViewMatrix = WorldParam.WorldViewMatrix + projectionMatrix = WorldParam.ProjectionMatrix + modelPosition = Attr.inPosition + scale = FixedScale.scale + } + OutputMappings { + Global.position = projPosition + } + } + } + + FragmentShaderNodes { + ShaderNode TextureFetch { + Definition: TextureFetch: Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn + InputMappings { + textureMap = MatParam.Texture + texCoord = TexCoord.vec2Variable + } + OutputMappings { + } + } + ShaderNode ColorMult { + Definition: ColorMult: Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn + InputMappings { + color1 = TextureFetch.outColor + color2 = TexCoord.vec4Variable + } + OutputMappings { + Global.color = outColor + } + } + } + + } +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/DashedLine.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/DashedLine.j3md new file mode 100644 index 000000000..097b44c71 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/DashedLine.j3md @@ -0,0 +1,59 @@ +MaterialDef DashedLine { + MaterialParameters { + } + + Technique { + WorldParameters { + WorldViewProjectionMatrix + Resolution + } + + VertexShaderNodes { + ShaderNode TransformPosition { + Definition: TransformPosition: Common/MatDefs/ShaderNodes/Basic/TransformPosition.j3sn + InputMappings { + transformsMatrix = WorldParam.WorldViewProjectionMatrix + inputPosition = Attr.inNormal + } + OutputMappings { + } + } + ShaderNode PerspectiveDivide { + Definition: PerspectiveDivide: Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide.j3sn + InputMappings { + inVec = TransformPosition.outPosition + } + OutputMappings { + } + } + ShaderNode CommonVert { + Definition: CommonVert: Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn + InputMappings { + worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix + modelPosition = Attr.inPosition + vertColor = Attr.inColor + texCoord1 = Attr.inTexCoord + } + OutputMappings { + Global.position = projPosition + } + } + } + + FragmentShaderNodes { + ShaderNode Dashed { + Definition: Dashed: Common/MatDefs/ShaderNodes/Misc/Dashed.j3sn + InputMappings { + texCoord = CommonVert.texCoord1 + inColor = CommonVert.vertColor + resolution = WorldParam.Resolution + startPos = PerspectiveDivide.outVec + } + OutputMappings { + Global.color = outColor + } + } + } + + } +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md index a194c15d7..fec38f503 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md @@ -20,6 +20,11 @@ MaterialDef Unshaded { Int NumberOfBones Matrix4Array BoneMatrices + // For Morph animation + FloatArray MorphWeights + Int NumberOfMorphTargets + Int NumberOfTargetsBuffers + // Alpha threshold for fragment discarding Float AlphaDiscardThreshold (AlphaTestFallOff) @@ -79,26 +84,30 @@ MaterialDef Unshaded { HAS_COLOR : Color NUM_BONES : NumberOfBones DISCARD_ALPHA : AlphaDiscardThreshold + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } } Technique PreNormalPass { - VertexShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert - FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag - - WorldParameters { - WorldViewProjectionMatrix - WorldViewMatrix - NormalMatrix - ViewProjectionMatrix - ViewMatrix - } - - Defines { - NUM_BONES : NumberOfBones - INSTANCING : UseInstancing - } + VertexShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + NormalMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + } } Technique PreShadow { @@ -122,6 +131,8 @@ MaterialDef Unshaded { NUM_BONES : NumberOfBones INSTANCING : UseInstancing POINT_LIGHT : IsPointLight + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } ForcedRenderState { @@ -157,8 +168,10 @@ MaterialDef Unshaded { PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones - INSTANCING : UseInstancing + INSTANCING : UseInstancing BACKFACE_SHADOWS: BackfaceShadows + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } ForcedRenderState { @@ -184,8 +197,10 @@ MaterialDef Unshaded { HAS_GLOWMAP : GlowMap HAS_GLOWCOLOR : GlowColor NUM_BONES : NumberOfBones - INSTANCING : UseInstancing + INSTANCING : UseInstancing HAS_POINTSIZE : PointSize + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } } } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert index 6cf9d9484..2c694d45d 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert @@ -1,6 +1,7 @@ #import "Common/ShaderLib/GLSLCompat.glsllib" #import "Common/ShaderLib/Skinning.glsllib" #import "Common/ShaderLib/Instancing.glsllib" +#import "Common/ShaderLib/MorphAnim.glsllib" attribute vec3 inPosition; @@ -38,6 +39,11 @@ void main(){ #endif vec4 modelSpacePos = vec4(inPosition, 1.0); + + #ifdef NUM_MORPH_TARGETS + Morph_Compute(modelSpacePos); + #endif + #ifdef NUM_BONES Skinning_Compute(modelSpacePos); #endif diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/fakeLighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/fakeLighting.j3md new file mode 100644 index 000000000..58797c112 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/fakeLighting.j3md @@ -0,0 +1,49 @@ +MaterialDef FakeLighting { + MaterialParameters { + Vector4 Color + } + + Technique { + WorldParameters { + WorldViewProjectionMatrix + NormalMatrix + } + + VertexShaderNodes { + ShaderNode Mat3Vec3Mult { + Definition: Mat3Vec3Mult: Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult.j3sn + InputMappings { + matrix3 = WorldParam.NormalMatrix + vector3 = Attr.inNormal + } + OutputMappings { + } + } + ShaderNode CommonVert { + Definition: CommonVert: Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn + InputMappings { + worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix + modelPosition = Attr.inPosition + } + OutputMappings { + Global.position = projPosition + } + } + } + + + FragmentShaderNodes { + ShaderNode FakeLighting { + Definition: FakeLighting: Common/MatDefs/ShaderNodes/Misc/fakeLighting.j3sn + InputMappings { + inColor = MatParam.Color + normal = Mat3Vec3Mult.outVector3.xyz + } + OutputMappings { + Global.color = outColor + } + } + } + + } +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult.j3sn new file mode 100644 index 000000000..019edb839 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult.j3sn @@ -0,0 +1,31 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition Mat3Vec3Mult { + //Vertex/Fragment + Type: Vertex + + //Shader GLSL: + Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult100.frag + + Documentation{ + //type documentation here. This is optional but recommended + + //@input + @input mat3 matrix3 the mat3 + @input vec3 vector3 the vec3 + + //@output + @output vec3 outVector3 the output vector + } + Input { + //all the node inputs + // + mat3 matrix3 + vec3 vector3 + } + Output { + //all the node outputs + // + vec3 outVector3 + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult100.frag new file mode 100644 index 000000000..f1a29fd2c --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult100.frag @@ -0,0 +1,3 @@ +void main(){ + outVector3 = matrix3 * vector3; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard.j3sn new file mode 100644 index 000000000..a9421de4c --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard.j3sn @@ -0,0 +1,35 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition Billboard { + //Vertex/Fragment + Type: Vertex + + //Shader GLSL: + Shader GLSL100: Common/MatDefs/ShaderNodes/Common/Billboard100.frag + + Documentation{ + //type documentation here. This is optional but recommended + + //@input + @input mat4 worldViewMatrix The worldView matrix + @input mat4 projectionMatrix The projection matrix + @input vec3 modelPosition the vertex position + @input float scale the scale of the billboard (defautl 1) + + //@output + @output vec4 projPosition The position in projection space + } + Input { + //all the node inputs + // + mat4 worldViewMatrix + mat4 projectionMatrix + vec3 modelPosition + float scale 1 + } + Output { + //all the node outputs + // + vec4 projPosition + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard100.frag new file mode 100644 index 000000000..d85880760 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard100.frag @@ -0,0 +1,19 @@ +void main(){ + // First colunm. + worldViewMatrix[0][0] = scale; + worldViewMatrix[0][1] = 0.0; + worldViewMatrix[0][2] = 0.0; + + // Second colunm. + worldViewMatrix[1][0] = 0.0; + worldViewMatrix[1][1] = scale; + worldViewMatrix[1][2] = 0.0; + + // Thrid colunm. + worldViewMatrix[2][0] = 0.0; + worldViewMatrix[2][1] = 0.0; + worldViewMatrix[2][2] = scale; + + vec4 position = worldViewMatrix * vec4(modelPosition,1.0); + projPosition = projectionMatrix * position; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale.j3sn new file mode 100644 index 000000000..48a920474 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale.j3sn @@ -0,0 +1,41 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition FixedScale { + //Vertex/Fragment + Type: Vertex + + //Shader GLSL: + Shader GLSL100: Common/MatDefs/ShaderNodes/Common/FixedScale100.frag + + Documentation{ + //type documentation here. This is optional but recommended + + //@input + @input vec4 viewport The viewport information (right, top, left, bottom) + @input vec3 cameraDir The direction of the camera + @input vec3 cameraPos The position of the camera + @input mat4 worldMatrix The world matrix + @input mat4 projectionMatrix The projection matrix + @input vec3 modelPosition the vertex position + @input float spriteHeight the desired image height in pixel + + //@output + @output float scale The constant scale + } + Input { + //all the node inputs + // + vec4 viewport + vec3 cameraDir + vec3 cameraPos + mat4 worldMatrix + mat4 projectionMatrix + vec3 modelPosition + float spriteHeight 10.0 + } + Output { + //all the node outputs + // + float scale + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag new file mode 100644 index 000000000..98a05d116 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag @@ -0,0 +1,8 @@ +void main(){ + vec4 worldPos = worldMatrix * vec4(0.0, 0.0, 0.0, 1.0); + vec3 dir = worldPos.xyz - cameraPos; + float distance = dot(cameraDir, dir); + float m11 = projectionMatrix[1][1]; + float halfHeight = (viewport.w - viewport.y) * 0.5; + scale = ((distance/halfHeight) * spriteHeight)/m11; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/texCoord.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/texCoord.j3sn new file mode 100644 index 000000000..fcf1bddb9 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/texCoord.j3sn @@ -0,0 +1,57 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition TexCoord { + //Vertex/Fragment + Type: Vertex + + //Shader GLSL: + Shader GLSL100: Common/MatDefs/ShaderNodes/Common/texCoord100.frag + + Documentation{ + //type documentation here. This is optional but recommended + + //@input + @input vec2 texCoord The input texture Coord + @input vec2 texCoord2 The input texture Coord + @input vec2 texCoord3 The input texture Coord + @input vec2 texCoord4 The input texture Coord + @input vec2 texCoord5 The input texture Coord + @input vec2 texCoord6 The input texture Coord + @input vec2 texCoord7 The input texture Coord + @input vec2 texCoord8 The input texture Coord + + //@output + @output vec2 texCoord The input texture Coord + @output vec2 texCoord2 The input texture Coord + @output vec2 texCoord3 The input texture Coord + @output vec2 texCoord4 The input texture Coord + @output vec2 texCoord5 The input texture Coord + @output vec2 texCoord6 The input texture Coord + @output vec2 texCoord7 The input texture Coord + @output vec2 texCoord8 The input texture Coord + } + Input { + //all the node inputs + // + vec2 texCoord + vec2 texCoord2 + vec2 texCoord3 + vec2 texCoord4 + vec2 texCoord5 + vec2 texCoord6 + vec2 texCoord7 + vec2 texCoord8 + } + Output { + //all the node outputs + // + vec2 texCoord + vec2 texCoord2 + vec2 texCoord3 + vec2 texCoord4 + vec2 texCoord5 + vec2 texCoord6 + vec2 texCoord7 + vec2 texCoord8 + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/texCoord100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/texCoord100.frag new file mode 100644 index 000000000..61a3f2e67 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/texCoord100.frag @@ -0,0 +1,2 @@ +void main(){ +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed.j3sn new file mode 100644 index 000000000..87f235121 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed.j3sn @@ -0,0 +1,37 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition Dashed { + //Vertex/Fragment + Type: Fragment + + //Shader GLSL: + Shader GLSL100: Common/MatDefs/ShaderNodes/Misc/Dashed100.frag + + Documentation{ + //type documentation here. This is optional but recommended + Output a dashed line (better have a LINE mode mesh) + + //@input + @input vec2 texCoord the texture coordinates + @input vec4 inColor The input color + + + //@output + @output vec4 outColor The modified output color + } + Input { + //all the node inputs + // + vec2 texCoord + vec4 inColor + vec4 startPos + vec2 resolution + + + } + Output { + //all the node outputs + // + vec4 outColor + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed100.frag new file mode 100644 index 000000000..4fa7babec --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed100.frag @@ -0,0 +1,10 @@ +void main(){ + startPos.xy = (startPos * 0.5 + 0.5).xy * resolution; + float len = distance(gl_FragCoord.xy,startPos.xy); + outColor = inColor; + float factor = float(int(len * 0.25)); + if(mod(factor, 2.0) > 0.0){ + discard; + } + +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide.j3sn new file mode 100644 index 000000000..61ada6c2c --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide.j3sn @@ -0,0 +1,30 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition PerspectiveDivide { + //Vertex/Fragment + Type: Vertex + + //Shader GLSL: + Shader GLSL100: Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide100.frag + + Documentation{ + //type documentation here. This is optional but recommended + performs inVec.xyz / inVec.w + + //@input + @input vec4 inVec The input vector + + //@output + @output vec4 outVec The modified output vector + } + Input { + //all the node inputs + // + vec4 inVec + } + Output { + //all the node outputs + // + vec4 outVec + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide100.frag new file mode 100644 index 000000000..0e4f1d5be --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide100.frag @@ -0,0 +1,3 @@ +void main(){ + outVec = vec4(inVec.xyz / inVec.w,1.0); +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/fakeLighting.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/fakeLighting.j3sn new file mode 100644 index 000000000..c47e1bf61 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/fakeLighting.j3sn @@ -0,0 +1,33 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition FakeLighting { + //Vertex/Fragment + Type: Fragment + + //Shader GLSL: + Shader GLSL100: Common/MatDefs/ShaderNodes/Misc/fakeLighting100.frag + + Documentation{ + //type documentation here. This is optional but recommended + + //@input + @input vec4 inColor The input color + @input vec3 normal The normal in view space + + + //@output + @output vec4 outColor The modified output color + } + Input { + //all the node inputs + // + vec4 inColor + vec3 normal + + } + Output { + //all the node outputs + // + vec4 outColor + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/fakeLighting100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/fakeLighting100.frag new file mode 100644 index 000000000..9b6b98b09 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/fakeLighting100.frag @@ -0,0 +1,9 @@ +void main(){ + + vec4 dark = inColor * 0.3; + vec4 bright = min(inColor * 4.0, 1.0); + normal = normalize(normal); + vec3 dir = vec3(0,0,1); + float factor = dot(dir, normal); + outColor = mix(dark, bright, factor); +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert index 417427155..15be15f8d 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert @@ -49,10 +49,13 @@ const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0); - void main(){ vec4 modelSpacePos = vec4(inPosition, 1.0); - + + #ifdef NUM_MORPH_TARGETS + Morph_Compute(modelSpacePos); + #endif + #ifdef NUM_BONES Skinning_Compute(modelSpacePos); #endif diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert index c62d50488..78bb2297f 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert @@ -1,6 +1,8 @@ #import "Common/ShaderLib/GLSLCompat.glsllib" #import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Skinning.glsllib" +#import "Common/ShaderLib/MorphAnim.glsllib" + attribute vec3 inPosition; #ifdef DISCARD_ALPHA attribute vec2 inTexCoord; @@ -14,7 +16,11 @@ uniform vec2 g_FrustumNearFar; void main() { vec4 modelSpacePos = vec4(inPosition, 1.0); - + + #ifdef NUM_MORPH_TARGETS + Morph_Compute(modelSpacePos, modelSpaceNorm); + #endif + #ifdef NUM_BONES Skinning_Compute(modelSpacePos); #endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/MorphAnim.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/MorphAnim.glsllib new file mode 100644 index 000000000..68c26f4ae --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/MorphAnim.glsllib @@ -0,0 +1,212 @@ +/** +A glsllib that perform morph animation. +Note that it only handles morphing position, normals and tangents. +*/ +#ifdef NUM_MORPH_TARGETS + vec3 dummy_norm = vec3(0.0); + vec3 dummy_tan = vec3(0.0); + #define NUM_BUFFERS NUM_MORPH_TARGETS * NUM_TARGETS_BUFFERS + #if (NUM_BUFFERS > 0) + uniform float m_MorphWeights[NUM_MORPH_TARGETS]; + attribute vec3 inMorphTarget0; + #endif + #if (NUM_BUFFERS > 1) + attribute vec3 inMorphTarget1; + #endif + #if (NUM_BUFFERS > 2) + attribute vec3 inMorphTarget2; + #endif + #if (NUM_BUFFERS > 3) + attribute vec3 inMorphTarget3; + #endif + #if (NUM_BUFFERS > 4) + attribute vec3 inMorphTarget4; + #endif + #if (NUM_BUFFERS > 5) + attribute vec3 inMorphTarget5; + #endif + #if (NUM_BUFFERS > 6) + attribute vec3 inMorphTarget6; + #endif + #if (NUM_BUFFERS > 7) + attribute vec3 inMorphTarget7; + #endif + #if (NUM_BUFFERS > 8) + attribute vec3 inMorphTarget8; + #endif + #if (NUM_BUFFERS > 9) + attribute vec3 inMorphTarget9; + #endif + #if (NUM_BUFFERS > 10) + attribute vec3 inMorphTarget10; + #endif + #if (NUM_BUFFERS > 11) + attribute vec3 inMorphTarget11; + #endif + #if (NUM_BUFFERS > 12) + attribute vec3 inMorphTarget12; + #endif + #if (NUM_BUFFERS > 13) + attribute vec3 inMorphTarget13; + #endif + + void Morph_Compute_Pos(inout vec4 pos){ + #if (NUM_TARGETS_BUFFERS == 1) + #if (NUM_MORPH_TARGETS > 0) + pos.xyz += m_MorphWeights[0] * inMorphTarget0; + #endif + #if (NUM_MORPH_TARGETS > 1) + pos.xyz += m_MorphWeights[1] * inMorphTarget1; + #endif + #if (NUM_MORPH_TARGETS > 2) + pos.xyz += m_MorphWeights[2] * inMorphTarget2; + #endif + #if (NUM_MORPH_TARGETS > 3) + pos.xyz += m_MorphWeights[3] * inMorphTarget3; + #endif + #if (NUM_MORPH_TARGETS > 4) + pos.xyz += m_MorphWeights[4] * inMorphTarget4; + #endif + #if (NUM_MORPH_TARGETS > 5) + pos.xyz += m_MorphWeights[5] * inMorphTarget5; + #endif + #if (NUM_MORPH_TARGETS > 6) + pos.xyz += m_MorphWeights[6] * inMorphTarget6; + #endif + #if (NUM_MORPH_TARGETS > 7) + pos.xyz += m_MorphWeights[7] * inMorphTarget7; + #endif + #if (NUM_MORPH_TARGETS > 8) + pos.xyz += m_MorphWeights[8] * inMorphTarget8; + #endif + #if (NUM_MORPH_TARGETS > 9) + pos.xyz += m_MorphWeights[9] * inMorphTarget9; + #endif + #if (NUM_MORPH_TARGETS > 10) + pos.xyz += m_MorphWeights[10] * inMorphTarget10; + #endif + #if (NUM_MORPH_TARGETS > 11) + pos.xyz += m_MorphWeights[11] * inMorphTarget11; + #endif + #if (NUM_MORPH_TARGETS > 12) + pos.xyz += m_MorphWeights[12] * inMorphTarget12; + #endif + #if (NUM_MORPH_TARGETS > 13) + pos.xyz += m_MorphWeights[13] * inMorphTarget13; + #endif + #endif + } + + float Get_Inverse_Weights_Sum(){ + float sum = 0; + for( int i = 0;i < NUM_MORPH_TARGETS; i++){ + sum += m_MorphWeights[i]; + } + return 1.0 / max(1.0, sum); + } + + void Morph_Compute_Pos_Norm(inout vec4 pos, inout vec3 norm){ + #if (NUM_TARGETS_BUFFERS == 2) + // weight sum may be over 1.0. It's totallyvalid for position + // but for normals. the weights needs to be normalized. + float invWeightsSum = Get_Inverse_Weights_Sum(); + #if (NUM_BUFFERS > 1) + pos.xyz += m_MorphWeights[0] * inMorphTarget0; + norm += m_MorphWeights[0] * invWeightsSum * inMorphTarget1; + #endif + #if (NUM_BUFFERS > 3) + pos.xyz += m_MorphWeights[1] * inMorphTarget2; + norm.xyz += m_MorphWeights[1] * invWeightsSum * inMorphTarget3; + #endif + #if (NUM_BUFFERS > 5) + pos.xyz += m_MorphWeights[2] * inMorphTarget4; + norm += m_MorphWeights[2] * invWeightsSum * inMorphTarget5; + #endif + #if (NUM_BUFFERS > 7) + pos.xyz += m_MorphWeights[3] * inMorphTarget6; + norm += m_MorphWeights[3] * invWeightsSum * inMorphTarget7; + #endif + #if (NUM_BUFFERS > 9) + pos.xyz += m_MorphWeights[4] * inMorphTarget8; + norm += m_MorphWeights[4] * invWeightsSum * inMorphTarget9; + #endif + #if (NUM_BUFFERS > 11) + pos.xyz += m_MorphWeights[5] * inMorphTarget10; + norm += m_MorphWeights[5] * invWeightsSum * inMorphTarget11; + #endif + #if (NUM_BUFFERS > 13) + pos.xyz += m_MorphWeights[6] * inMorphTarget12; + norm += m_MorphWeights[6] * invWeightsSum * inMorphTarget13; + #endif + #endif + } + + void Morph_Compute_Pos_Norm_Tan(inout vec4 pos, inout vec3 norm, inout vec3 tan){ + #if (NUM_TARGETS_BUFFERS == 3) + // weight sum may be over 1.0. It's totallyvalid for position + // but for normals. the weights needs to be normalized. + float invWeightsSum = Get_Inverse_Weights_Sum(); + #if (NUM_BUFFERS > 2) + float normWeight = m_MorphWeights[0] * invWeightsSum; + pos.xyz += m_MorphWeights[0] * inMorphTarget0; + norm += normWeight * inMorphTarget1; + tan += normWeight * inMorphTarget2; + #endif + #if (NUM_BUFFERS > 5) + float normWeight = m_MorphWeights[1] * invWeightsSum; + pos.xyz += m_MorphWeights[1] * inMorphTarget3; + norm += normWeight * inMorphTarget4; + tan += normWeight * inMorphTarget5; + #endif + #if (NUM_BUFFERS > 8) + float normWeight = m_MorphWeights[2] * invWeightsSum; + pos.xyz += m_MorphWeights[2] * inMorphTarget6; + norm += normWeight * inMorphTarget7; + tan += normWeight * inMorphTarget8; + #endif + #if (NUM_BUFFERS > 11) + float normWeight = m_MorphWeights[3] * invWeightsSum; + pos.xyz += m_MorphWeights[3] * inMorphTarget9; + norm += normWeight * inMorphTarget10; + tan += normWeight * inMorphTarget11; + #endif + #endif + } + + void Morph_Compute(inout vec4 pos){ + #if (NUM_TARGETS_BUFFERS == 2) + Morph_Compute_Pos_Norm(pos,dummy_norm); + return; + #elif (NUM_TARGETS_BUFFERS == 3) + Morph_Compute_Pos_Norm_Tan(pos, dummy_norm, dummy_tan); + return; + #endif + Morph_Compute_Pos(pos); + } + + void Morph_Compute(inout vec4 pos, inout vec3 norm){ + #if (NUM_TARGETS_BUFFERS == 1) + Morph_Compute_Pos(pos); + return; + #elif (NUM_TARGETS_BUFFERS == 3) + Morph_Compute_Pos_Norm_Tan(pos, dummy_norm, dummy_tan); + return; + #elif (NUM_TARGETS_BUFFERS == 2) + Morph_Compute_Pos_Norm(pos, norm); + #endif + } + + void Morph_Compute(inout vec4 pos, inout vec3 norm, inout vec3 tan){ + #if (NUM_TARGETS_BUFFERS == 1) + Morph_Compute_Pos(pos); + return; + #elif (NUM_TARGETS_BUFFERS == 2) + Morph_Compute_Pos_Norm(pos, norm); + tan = normalize(tan - dot(tan, norm) * norm); + return; + #elif (NUM_TARGETS_BUFFERS == 3) + Morph_Compute_Pos_Norm_Tan(pos, norm, tan); + #endif + } + +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib index 81d56dc95..ee4edac8e 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib @@ -121,6 +121,92 @@ vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , f } +float renderProbe(vec3 viewDir, vec3 worldPos, vec3 normal, vec3 norm, float Roughness, vec4 diffuseColor, vec4 specularColor, float ndotv, vec3 ao, mat4 lightProbeData,vec3 shCoeffs[9],samplerCube prefEnvMap, inout vec3 color ){ + + // lightProbeData is a mat4 with this layout + // 3x3 rot mat| + // 0 1 2 | 3 + // 0 | ax bx cx | px | ) + // 1 | ay by cy | py | probe position + // 2 | az bz cz | pz | ) + // --|----------| + // 3 | sx sy sz sp | -> 1/probe radius + nbMipMaps + // --scale-- + // parallax fix for spherical / obb bounds and probe blending from + // from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ + vec3 rv = reflect(-viewDir, normal); + vec4 probePos = lightProbeData[3]; + float invRadius = fract( probePos.w); + float nbMipMaps = probePos.w - invRadius; + vec3 direction = worldPos - probePos.xyz; + float ndf = 0.0; + + if(lightProbeData[0][3] != 0.0){ + // oriented box probe + mat3 wToLocalRot = mat3(lightProbeData); + wToLocalRot = inverse(wToLocalRot); + vec3 scale = vec3(lightProbeData[0][3], lightProbeData[1][3], lightProbeData[2][3]); + #if NB_PROBES >= 2 + // probe blending + // compute fragment position in probe local space + vec3 localPos = wToLocalRot * worldPos; + localPos -= probePos.xyz; + // compute normalized distance field + vec3 localDir = abs(localPos); + localDir /= scale; + ndf = max(max(localDir.x, localDir.y), localDir.z); + #endif + // parallax fix + vec3 rayLs = wToLocalRot * rv; + rayLs /= scale; + + vec3 positionLs = worldPos - probePos.xyz; + positionLs = wToLocalRot * positionLs; + positionLs /= scale; + + vec3 unit = vec3(1.0); + vec3 firstPlaneIntersect = (unit - positionLs) / rayLs; + vec3 secondPlaneIntersect = (-unit - positionLs) / rayLs; + vec3 furthestPlane = max(firstPlaneIntersect, secondPlaneIntersect); + float distance = min(min(furthestPlane.x, furthestPlane.y), furthestPlane.z); + + vec3 intersectPositionWs = worldPos + rv * distance; + rv = intersectPositionWs - probePos.xyz; + + } else { + // spherical probe + // paralax fix + rv = invRadius * direction + rv; + + #if NB_PROBES >= 2 + // probe blending + float dist = sqrt(dot(direction, direction)); + ndf = dist * invRadius; + #endif + } + + vec3 indirectDiffuse = vec3(0.0); + vec3 indirectSpecular = vec3(0.0); + indirectDiffuse = sphericalHarmonics(normal.xyz, shCoeffs) * diffuseColor.rgb; + vec3 dominantR = getSpecularDominantDir( normal, rv.xyz, Roughness * Roughness ); + indirectSpecular = ApproximateSpecularIBLPolynomial(prefEnvMap, specularColor.rgb, Roughness, ndotv, dominantR, nbMipMaps); + + #ifdef HORIZON_FADE + //horizon fade from http://marmosetco.tumblr.com/post/81245981087 + float horiz = dot(rv, norm); + float horizFadePower = 1.0 - Roughness; + horiz = clamp( 1.0 + horizFadePower * horiz, 0.0, 1.0 ); + horiz *= horiz; + indirectSpecular *= vec3(horiz); + #endif + + vec3 indirectLighting = (indirectDiffuse + indirectSpecular) * ao; + + color = indirectLighting * step( 0.0, probePos.w); + return ndf; +} + + diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib index baf003f88..c7c6b8851 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib @@ -3,7 +3,10 @@ // gather functions are declared to work on shadowmaps #extension GL_ARB_gpu_shader5 : enable #define IVEC2 ivec2 - #ifdef HARDWARE_SHADOWS + #if defined GL_ES + #define SHADOWMAP sampler2D + #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r) + #elif defined HARDWARE_SHADOWS #define SHADOWMAP sampler2DShadow #define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset) #define SHADOWCOMPARE(tex,coord) textureProj(tex, coord) diff --git a/jme3-core/src/main/resources/Common/Textures/dot.png b/jme3-core/src/main/resources/Common/Textures/dot.png new file mode 100644 index 000000000..59605f195 Binary files /dev/null and b/jme3-core/src/main/resources/Common/Textures/dot.png differ diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java index 9eecd12b7..c8c674046 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java @@ -43,10 +43,7 @@ import com.jme3.shader.*; import com.jme3.util.blockparser.Statement; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * This class is here to be able to load shaderNodeDefinition from both the @@ -75,7 +72,7 @@ public class ShaderNodeLoaderDelegate { protected MaterialDef materialDef; protected String shaderLanguage; protected String shaderName; - protected String varNames = ""; + protected Set varNames = new HashSet<>(); protected AssetManager assetManager; protected ConditionParser conditionParser = new ConditionParser(); protected List nulledConditions = new ArrayList(); @@ -177,7 +174,7 @@ public class ShaderNodeLoaderDelegate { shaderNodeDefinition.setDocumentation(doc); } } else if (line.startsWith("Input")) { - varNames = ""; + varNames.clear(); for (Statement statement1 : statement.getContents()) { try { shaderNodeDefinition.getInputs().add(readVariable(statement1)); @@ -186,7 +183,7 @@ public class ShaderNodeLoaderDelegate { } } } else if (line.startsWith("Output")) { - varNames = ""; + varNames.clear(); for (Statement statement1 : statement.getContents()) { try { if (statement1.getLine().trim().equals("None")) { @@ -235,11 +232,11 @@ public class ShaderNodeLoaderDelegate { multiplicity = arr[1].replaceAll("\\]", "").trim(); } - if (varNames.contains(varName + ";")) { + if (varNames.contains(varName)) { throw new MatParseException("Duplicate variable name " + varName, statement); } - varNames += varName + ";"; + varNames.add(varName); final ShaderNodeVariable variable = new ShaderNodeVariable(varType, "", varName, multiplicity); variable.setDefaultValue(defaultValue); @@ -1139,7 +1136,7 @@ public class ShaderNodeLoaderDelegate { materialDef = null; shaderLanguage = ""; shaderName = ""; - varNames = ""; + varNames.clear(); assetManager = null; nulledConditions.clear(); } diff --git a/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java b/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java index 93ad16f7f..8332ffafe 100644 --- a/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java +++ b/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java @@ -31,13 +31,9 @@ */ package jme3test.animation; -import com.jme3.animation.AnimControl; -import com.jme3.animation.AnimationFactory; -import com.jme3.animation.LoopMode; +import com.jme3.animation.*; import com.jme3.app.SimpleApplication; -import com.jme3.cinematic.Cinematic; -import com.jme3.cinematic.MotionPath; -import com.jme3.cinematic.PlayState; +import com.jme3.cinematic.*; import com.jme3.cinematic.events.*; import com.jme3.font.BitmapText; import com.jme3.input.ChaseCamera; @@ -45,21 +41,18 @@ import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; +import com.jme3.math.*; import com.jme3.niftygui.NiftyJmeDisplay; import com.jme3.post.FilterPostProcessor; import com.jme3.post.filters.FadeFilter; import com.jme3.renderer.Caps; import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.CameraNode; -import com.jme3.scene.Geometry; -import com.jme3.scene.Spatial; +import com.jme3.scene.*; import com.jme3.scene.shape.Box; import com.jme3.shadow.DirectionalLightShadowRenderer; import de.lessvoid.nifty.Nifty; +//TODO rework this Test when the new animation system is done. public class TestCinematic extends SimpleApplication { private Spatial model; @@ -77,7 +70,6 @@ public class TestCinematic extends SimpleApplication { app.start(); - } @Override @@ -202,7 +194,7 @@ public class TestCinematic extends SimpleApplication { private void createScene() { - model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + model = assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); model.center(); model.setShadowMode(ShadowMode.CastAndReceive); rootNode.attachChild(model); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java b/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java index bc806b018..8b86f1b0c 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java @@ -36,24 +36,17 @@ import com.jme3.app.SimpleApplication; import com.jme3.asset.TextureKey; import com.jme3.bullet.BulletAppState; import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.PhysicsCollisionEvent; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.RagdollCollisionListener; +import com.jme3.bullet.collision.*; import com.jme3.bullet.collision.shapes.SphereCollisionShape; import com.jme3.bullet.control.KinematicRagdollControl; import com.jme3.bullet.control.RigidBodyControl; import com.jme3.font.BitmapText; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.input.controls.*; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; +import com.jme3.math.*; import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.debug.SkeletonDebugger; @@ -65,6 +58,7 @@ import com.jme3.texture.Texture; * PHYSICS RAGDOLLS ARE NOT WORKING PROPERLY YET! * @author normenhansen */ +//TODO rework this Test when the new animation system is done. public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisionListener, AnimEventListener { private BulletAppState bulletAppState; @@ -101,7 +95,7 @@ public class TestBoneRagdoll extends SimpleApplication implements RagdollCollisi PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); setupLight(); - model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + model = (Node) assetManager.loadModel("Models/Sinbad/SinbadOldAnim.j3o"); // model.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X)); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java b/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java index 5b9e948c8..f94307d6e 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java @@ -31,10 +31,7 @@ */ package jme3test.bullet; -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.animation.AnimEventListener; -import com.jme3.animation.LoopMode; +import com.jme3.animation.*; import com.jme3.app.SimpleApplication; import com.jme3.asset.TextureKey; import com.jme3.bullet.BulletAppState; @@ -46,9 +43,7 @@ import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; +import com.jme3.math.*; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Node; @@ -58,6 +53,7 @@ import com.jme3.texture.Texture; /** * @author normenhansen */ +//TODO rework this Test when the new animation system is done. public class TestRagdollCharacter extends SimpleApplication implements AnimEventListener, ActionListener { BulletAppState bulletAppState; @@ -89,7 +85,7 @@ public class TestRagdollCharacter extends SimpleApplication implements AnimEvent cam.setLocation(new Vector3f(-8,0,-4)); cam.lookAt(new Vector3f(4,0,-7), Vector3f.UNIT_Y); - model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + model = (Node) assetManager.loadModel("Models/Sinbad/SinbadOldAnim.j3o"); model.lookAt(new Vector3f(0,0,-1), Vector3f.UNIT_Y); model.setLocalTranslation(4, 0, -7f); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java b/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java index d5b42d01b..4359154d1 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java @@ -31,10 +31,7 @@ */ package jme3test.bullet; -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.animation.AnimEventListener; -import com.jme3.animation.LoopMode; +import com.jme3.animation.*; import com.jme3.app.SimpleApplication; import com.jme3.bullet.BulletAppState; import com.jme3.bullet.PhysicsSpace; @@ -54,16 +51,12 @@ import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; +import com.jme3.math.*; import com.jme3.post.FilterPostProcessor; import com.jme3.post.filters.BloomFilter; import com.jme3.renderer.Camera; import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; +import com.jme3.scene.*; import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Sphere; import com.jme3.scene.shape.Sphere.TextureMode; @@ -74,6 +67,7 @@ import com.jme3.terrain.heightmap.ImageBasedHeightMap; import com.jme3.texture.Texture; import com.jme3.texture.Texture.WrapMode; import com.jme3.util.SkyFactory; + import java.util.ArrayList; import java.util.List; @@ -297,7 +291,7 @@ public class TestWalkingChar extends SimpleApplication implements ActionListener private void createCharacter() { CapsuleCollisionShape capsule = new CapsuleCollisionShape(3f, 4f); character = new CharacterControl(capsule, 0.01f); - model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + model = (Node) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); //model.setLocalScale(0.5f); model.addControl(character); character.setPhysicsLocation(new Vector3f(-140, 40, -10)); diff --git a/jme3-examples/src/main/java/jme3test/collision/Main.java b/jme3-examples/src/main/java/jme3test/collision/Main.java new file mode 100644 index 000000000..e69de29bb diff --git a/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java b/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java index bfc03c0c5..57dba2fb5 100644 --- a/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java +++ b/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java @@ -32,8 +32,7 @@ package jme3test.export; -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; +import com.jme3.anim.AnimComposer; import com.jme3.app.SimpleApplication; import com.jme3.export.binary.BinaryExporter; import com.jme3.export.binary.BinaryImporter; @@ -42,9 +41,8 @@ import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; import com.jme3.scene.Node; import com.jme3.scene.Spatial; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; + +import java.io.*; public class TestOgreConvert extends SimpleApplication { @@ -71,10 +69,9 @@ public class TestOgreConvert extends SimpleApplication { BinaryImporter imp = new BinaryImporter(); imp.setAssetManager(assetManager); Node ogreModelReloaded = (Node) imp.load(bais, null, null); - - AnimControl control = ogreModelReloaded.getControl(AnimControl.class); - AnimChannel chan = control.createChannel(); - chan.setAnim("Walk"); + + AnimComposer composer = ogreModelReloaded.getControl(AnimComposer.class); + composer.setCurrentAction("Walk"); rootNode.attachChild(ogreModelReloaded); } catch (IOException ex){ diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java index bb1587b3e..db9c700c8 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java @@ -32,10 +32,7 @@ package jme3test.helloworld; -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.animation.AnimEventListener; -import com.jme3.animation.LoopMode; +import com.jme3.animation.*; import com.jme3.app.SimpleApplication; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; @@ -70,7 +67,7 @@ public class HelloAnimation extends SimpleApplication rootNode.addLight(dl); /** Load a model that contains animation */ - player = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + player = (Node) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); player.setLocalScale(0.5f); rootNode.attachChild(player); diff --git a/jme3-examples/src/main/java/jme3test/light/DlsfError.java b/jme3-examples/src/main/java/jme3test/light/DlsfError.java new file mode 100644 index 000000000..e69de29bb diff --git a/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java b/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java index 24729948d..d96edf706 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java +++ b/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java @@ -102,7 +102,7 @@ public class TestConeVSFrustum extends SimpleApplication { float radius = FastMath.tan(spotLight.getSpotOuterAngle()) * spotLight.getSpotRange(); - Cylinder cylinder = new Cylinder(5, 16, 0, radius, spotLight.getSpotRange(), true, false); + Cylinder cylinder = new Cylinder(5, 16, 0.01f, radius, spotLight.getSpotRange(), true, false); geom = new Geometry("light", cylinder); geom.setMaterial(new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md")); geom.getMaterial().setColor("Diffuse", ColorRGBA.White); diff --git a/jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java b/jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java new file mode 100644 index 000000000..c32773070 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.ChaseCameraAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.*; +import com.jme3.light.*; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.renderer.Camera; +import com.jme3.scene.*; +import com.jme3.scene.debug.Grid; +import com.jme3.scene.debug.WireFrustum; +import com.jme3.scene.shape.*; +import com.jme3.shadow.ShadowUtil; +import com.jme3.util.TempVars; + +import java.io.File; +import java.io.IOException; + +public class TestObbVsBounds extends SimpleApplication { + + private Node ln; + private BoundingBox aabb = new BoundingBox(); + private BoundingSphere sphere = new BoundingSphere(10, new Vector3f(-30, 0, -60)); + + private final static float MOVE_SPEED = 60; + private Vector3f tmp = new Vector3f(); + private Quaternion tmpQuat = new Quaternion(); + private boolean moving, shift; + private boolean panning; + + private OrientedBoxProbeArea area = new OrientedBoxProbeArea(); + private Camera frustumCam; + + private Geometry areaGeom; + private Geometry frustumGeom; + private Geometry aabbGeom; + private Geometry sphereGeom; + + public static void main(String[] args) { + TestObbVsBounds app = new TestObbVsBounds(); + app.start(); + } + + @Override + public void simpleInitApp() { + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + frustumCam = cam.clone(); + frustumCam.setFrustumFar(25); + makeCamFrustum(); + aabb.setCenter(20, 10, -60); + aabb.setXExtent(10); + aabb.setYExtent(5); + aabb.setZExtent(3); + makeBoxWire(aabb); + makeSphereWire(sphere); + + rootNode.addLight(new DirectionalLight()); + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(0.2f)); + rootNode.addLight(al); + + Grid grid = new Grid(50, 50, 5); + Geometry gridGeom = new Geometry("grid", grid); + gridGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); + gridGeom.getMaterial().setColor("Color", ColorRGBA.Gray); + rootNode.attachChild(gridGeom); + gridGeom.setLocalTranslation(-125, -25, -125); + + area.setCenter(Vector3f.ZERO); + area.setExtent(new Vector3f(4, 8, 5)); + makeAreaGeom(); + + ln = new Node("lb"); + ln.setLocalRotation(new Quaternion(-0.18826798f, -0.38304946f, -0.12780227f, 0.895261f)); + ln.attachChild(areaGeom); + ln.setLocalScale(4,8,5); + rootNode.attachChild(ln); + + inputManager.addMapping("click", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); + inputManager.addMapping("shift", new KeyTrigger(KeyInput.KEY_LSHIFT), new KeyTrigger(KeyInput.KEY_RSHIFT)); + inputManager.addMapping("middleClick", new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE)); + inputManager.addMapping("up", new MouseAxisTrigger(MouseInput.AXIS_Y, false)); + inputManager.addMapping("down", new MouseAxisTrigger(MouseInput.AXIS_Y, true)); + inputManager.addMapping("left", new MouseAxisTrigger(MouseInput.AXIS_X, true)); + inputManager.addMapping("right", new MouseAxisTrigger(MouseInput.AXIS_X, false)); + + + final Node camTarget = new Node("CamTarget"); + rootNode.attachChild(camTarget); + + ChaseCameraAppState chaser = new ChaseCameraAppState(); + chaser.setTarget(camTarget); + chaser.setMaxDistance(150); + chaser.setDefaultDistance(70); + chaser.setDefaultHorizontalRotation(FastMath.HALF_PI); + chaser.setMinVerticalRotation(-FastMath.PI); + chaser.setMaxVerticalRotation(FastMath.PI * 2); + chaser.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + stateManager.attach(chaser); + flyCam.setEnabled(false); + + inputManager.addListener(new AnalogListener() { + public void onAnalog(String name, float value, float tpf) { + Spatial s = null; + float mult = 1; + if (moving) { + s = ln; + } + if (panning) { + s = camTarget; + mult = -1; + } + if ((moving || panning) && s != null) { + if (shift) { + if (name.equals("left")) { + tmp.set(cam.getDirection()); + s.rotate(tmpQuat.fromAngleAxis(value, tmp)); + } + if (name.equals("right")) { + tmp.set(cam.getDirection()); + s.rotate(tmpQuat.fromAngleAxis(-value, tmp)); + } + } else { + value *= MOVE_SPEED * mult; + if (name.equals("up")) { + tmp.set(cam.getUp()).multLocal(value); + s.move(tmp); + } + if (name.equals("down")) { + tmp.set(cam.getUp()).multLocal(-value); + s.move(tmp); + } + if (name.equals("left")) { + tmp.set(cam.getLeft()).multLocal(value); + s.move(tmp); + } + if (name.equals("right")) { + tmp.set(cam.getLeft()).multLocal(-value); + s.move(tmp); + } + } + } + } + }, "up", "down", "left", "right"); + + inputManager.addListener(new ActionListener() { + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("click")) { + if (isPressed) { + moving = true; + } else { + moving = false; + } + } + if (name.equals("middleClick")) { + if (isPressed) { + panning = true; + } else { + panning = false; + } + } + if (name.equals("shift")) { + if (isPressed) { + shift = true; + } else { + shift = false; + } + } + } + }, "click", "middleClick", "shift"); + + } + + public void makeAreaGeom() { + + Vector3f[] points = new Vector3f[8]; + + for (int i = 0; i < points.length; i++) { + points[i] = new Vector3f(); + } + + points[0].set(-1, -1, 1); + points[1].set(-1, 1, 1); + points[2].set(1, 1, 1); + points[3].set(1, -1, 1); + + points[4].set(-1, -1, -1); + points[5].set(-1, 1, -1); + points[6].set(1, 1, -1); + points[7].set(1, -1, -1); + + Mesh box = WireFrustum.makeFrustum(points); + areaGeom = new Geometry("light", (Mesh)box); + areaGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); + areaGeom.getMaterial().setColor("Color", ColorRGBA.White); + } + + public void makeCamFrustum() { + Vector3f[] points = new Vector3f[8]; + for (int i = 0; i < 8; i++) { + points[i] = new Vector3f(); + } + ShadowUtil.updateFrustumPoints2(frustumCam, points); + WireFrustum frustumShape = new WireFrustum(points); + frustumGeom = new Geometry("frustum", frustumShape); + frustumGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); + rootNode.attachChild(frustumGeom); + } + + public void makeBoxWire(BoundingBox box) { + Vector3f[] points = new Vector3f[8]; + for (int i = 0; i < 8; i++) { + points[i] = new Vector3f(); + } + points[0].set(-1, -1, 1); + points[1].set(-1, 1, 1); + points[2].set(1, 1, 1); + points[3].set(1, -1, 1); + + points[4].set(-1, -1, -1); + points[5].set(-1, 1, -1); + points[6].set(1, 1, -1); + points[7].set(1, -1, -1); + + WireFrustum frustumShape = new WireFrustum(points); + aabbGeom = new Geometry("box", frustumShape); + aabbGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); + aabbGeom.getMaterial().getAdditionalRenderState().setWireframe(true); + aabbGeom.setLocalTranslation(box.getCenter()); + aabbGeom.setLocalScale(box.getXExtent(), box.getYExtent(), box.getZExtent()); + rootNode.attachChild(aabbGeom); + } + + public void makeSphereWire(BoundingSphere sphere) { + + sphereGeom = new Geometry("box", new Sphere(16, 16, 10)); + sphereGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); + sphereGeom.getMaterial().getAdditionalRenderState().setWireframe(true); + sphereGeom.setLocalTranslation(sphere.getCenter()); + rootNode.attachChild(sphereGeom); + } + + + @Override + public void simpleUpdate(float tpf) { + + area.setCenter(ln.getLocalTranslation()); + area.setRotation(ln.getLocalRotation()); + + TempVars vars = TempVars.get(); + boolean intersectBox = area.intersectsBox(aabb, vars); + boolean intersectFrustum = area.intersectsFrustum(frustumCam, vars); + boolean intersectSphere = area.intersectsSphere(sphere, vars); + vars.release(); + + boolean intersect = intersectBox || intersectFrustum || intersectSphere; + + areaGeom.getMaterial().setColor("Color", intersect ? ColorRGBA.Green : ColorRGBA.White); + sphereGeom.getMaterial().setColor("Color", intersectSphere ? ColorRGBA.Cyan : ColorRGBA.White); + frustumGeom.getMaterial().setColor("Color", intersectFrustum ? ColorRGBA.Cyan : ColorRGBA.White); + aabbGeom.getMaterial().setColor("Color", intersectBox ? ColorRGBA.Cyan : ColorRGBA.White); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java b/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java index f267b1d41..97629117e 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java +++ b/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java @@ -69,7 +69,6 @@ public class TestShadowsPerf extends SimpleApplication { @Override public void simpleInitApp() { - Logger.getLogger("com.jme3").setLevel(Level.SEVERE); flyCam.setMoveSpeed(50); flyCam.setEnabled(false); viewPort.setBackgroundColor(ColorRGBA.DarkGray); diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java b/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java index a88e12bb0..051202cda 100644 --- a/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java +++ b/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java @@ -1,7 +1,6 @@ package jme3test.light.pbr; import com.jme3.app.SimpleApplication; -import com.jme3.bounding.BoundingSphere; import com.jme3.environment.EnvironmentCamera; import com.jme3.environment.LightProbeFactory; import com.jme3.environment.generation.JobProgressAdapter; @@ -10,6 +9,7 @@ import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.LightProbe; +import com.jme3.light.SphereProbeArea; import com.jme3.material.Material; import com.jme3.math.*; import com.jme3.scene.*; @@ -127,7 +127,7 @@ public class RefEnv extends SimpleApplication { rootNode.getChild(0).setCullHint(Spatial.CullHint.Dynamic); } }); - ((BoundingSphere) probe.getBounds()).setRadius(100); + ((SphereProbeArea) probe.getArea()).setRadius(100); rootNode.addLight(probe); } diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java index 5cb276a07..4cc36b123 100644 --- a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java @@ -42,8 +42,7 @@ import com.jme3.input.ChaseCamera; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.DirectionalLight; -import com.jme3.light.LightProbe; +import com.jme3.light.*; import com.jme3.material.Material; import com.jme3.math.*; import com.jme3.post.FilterPostProcessor; @@ -205,7 +204,7 @@ public class TestPBRLighting extends SimpleApplication { tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager); } }); - ((BoundingSphere) probe.getBounds()).setRadius(100); + ((SphereProbeArea) probe.getArea()).setRadius(100); rootNode.addLight(probe); //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe); diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java deleted file mode 100644 index eb30e2d2f..000000000 --- a/jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright (c) 2009-2015 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.light.pbr; - -import com.jme3.app.SimpleApplication; -import com.jme3.bounding.BoundingSphere; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.input.controls.MouseAxisTrigger; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.Geometry; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Box; -import com.jme3.scene.shape.Sphere; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; - -import com.jme3.environment.LightProbeFactory; -import com.jme3.environment.EnvironmentCamera; -import com.jme3.environment.util.LightsDebugState; -import com.jme3.light.LightProbe; -import com.jme3.material.TechniqueDef; -import com.jme3.post.FilterPostProcessor; -import com.jme3.post.filters.BloomFilter; -import com.jme3.post.filters.FXAAFilter; -import com.jme3.post.filters.ToneMapFilter; -import com.jme3.post.ssao.SSAOFilter; -import com.jme3.scene.Node; -import com.jme3.texture.plugins.ktx.KTXLoader; -import com.jme3.util.SkyFactory; -import com.jme3.util.TangentBinormalGenerator; - -public class TestPbrEnv extends SimpleApplication implements ActionListener { - - public static final int SHADOWMAP_SIZE = 1024; - private Spatial[] obj; - private Material[] mat; - private DirectionalLightShadowRenderer dlsr; - private LightsDebugState debugState; - - private EnvironmentCamera envCam; - - private Geometry ground; - private Material matGroundU; - private Material matGroundL; - - private Geometry camGeom; - - public static void main(String[] args) { - TestPbrEnv app = new TestPbrEnv(); - app.start(); - } - - - public void loadScene() { - - renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass); - renderManager.setSinglePassLightBatchSize(3); - obj = new Spatial[2]; - // Setup first view - - mat = new Material[2]; - mat[0] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat.j3m"); - //mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); - mat[1] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat2.j3m"); -// mat[1].setBoolean("UseMaterialColors", true); -// mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f)); -// mat[1].setColor("Diffuse", ColorRGBA.White.clone()); - - obj[0] = new Geometry("sphere", new Sphere(30, 30, 2)); - obj[0].setShadowMode(ShadowMode.CastAndReceive); - obj[1] = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f)); - obj[1].setShadowMode(ShadowMode.CastAndReceive); - TangentBinormalGenerator.generate(obj[1]); - TangentBinormalGenerator.generate(obj[0]); - -// for (int i = 0; i < 60; i++) { -// Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false); -// t.setName("Cube" + i); -// t.setLocalScale(FastMath.nextRandomFloat() * 10f); -// t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]); -// rootNode.attachChild(t); -// t.setLocalTranslation(FastMath.nextRandomFloat() * 200f, FastMath.nextRandomFloat() * 30f + 20, 30f * (i + 2f)); -// } - - for (int i = 0; i < 2; i++) { - Spatial t = obj[0].clone(false); - t.setName("Cube" + i); - t.setLocalScale( 10f); - t.setMaterial(mat[1].clone()); - rootNode.attachChild(t); - t.setLocalTranslation(i * 200f+ 100f, 50, 800f * (i)); - } - - Box b = new Box(1000, 2, 1000); - b.scaleTextureCoordinates(new Vector2f(20, 20)); - ground = new Geometry("soil", b); - TangentBinormalGenerator.generate(ground); - ground.setLocalTranslation(0, 10, 550); - matGroundU = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - matGroundU.setColor("Color", ColorRGBA.Green); - -// matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); -// Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); -// grass.setWrap(WrapMode.Repeat); -// matGroundL.setTexture("DiffuseMap", grass); - - matGroundL = assetManager.loadMaterial("jme3test/light/pbr/pbrMat4.j3m"); - - ground.setMaterial(matGroundL); - - //ground.setShadowMode(ShadowMode.CastAndReceive); - rootNode.attachChild(ground); - - l = new DirectionalLight(); - l.setColor(ColorRGBA.White); - //l.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal()); - l.setDirection(new Vector3f(-0.2823181f, -0.41889593f, 0.863031f).normalizeLocal()); - - rootNode.addLight(l); - - AmbientLight al = new AmbientLight(); - al.setColor(ColorRGBA.White.mult(0.5f)); - // rootNode.addLight(al); - - //Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap); - Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap); - sky.setLocalScale(350); - - rootNode.attachChild(sky); - } - DirectionalLight l; - - @Override - public void simpleInitApp() { - assetManager.registerLoader(KTXLoader.class, "ktx"); - - - // put the camera in a bad position - cam.setLocation(new Vector3f(-52.433647f, 68.69636f, -118.60924f)); - cam.setRotation(new Quaternion(0.10294232f, 0.25269797f, -0.027049713f, 0.96167296f)); - - flyCam.setMoveSpeed(100); - - loadScene(); - - dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4); - dlsr.setLight(l); - //dlsr.setLambda(0.55f); - dlsr.setShadowIntensity(0.5f); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - //dlsr.displayDebug(); - // viewPort.addProcessor(dlsr); - - FilterPostProcessor fpp = new FilterPostProcessor(assetManager); - - fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(6.0f))); - SSAOFilter ssao = new SSAOFilter(); - ssao.setIntensity(5); - - fpp.addFilter(ssao); - - BloomFilter bloomFilter = new BloomFilter(); - fpp.addFilter(bloomFilter); - fpp.addFilter(new FXAAFilter()); - //viewPort.addProcessor(fpp); - - initInputs(); - -// envManager = new EnvironmentManager(); -// getStateManager().attach(envManager); -// - envCam = new EnvironmentCamera(); - getStateManager().attach(envCam); - - debugState = new LightsDebugState(); - debugState.setProbeScale(5); - getStateManager().attach(debugState); - - camGeom = new Geometry("camGeom", new Sphere(16, 16, 2)); -// Material m = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md"); -// m.setColor("Color", ColorRGBA.Green); - Material m = assetManager.loadMaterial("jme3test/light/pbr/pbrMat3.j3m"); - camGeom.setMaterial(m); - camGeom.setLocalTranslation(0, 20, 0); - camGeom.setLocalScale(5); - rootNode.attachChild(camGeom); - - // envManager.setScene(rootNode); - -// MaterialDebugAppState debug = new MaterialDebugAppState(); -// debug.registerBinding("MatDefs/PBRLighting.frag", rootNode); -// getStateManager().attach(debug); - - flyCam.setDragToRotate(true); - setPauseOnLostFocus(false); - - // cam.lookAt(camGeom.getWorldTranslation(), Vector3f.UNIT_Y); - - } - - private void fixFLyCamInputs() { - inputManager.deleteMapping(CameraInput.FLYCAM_LEFT); - inputManager.deleteMapping(CameraInput.FLYCAM_RIGHT); - inputManager.deleteMapping(CameraInput.FLYCAM_UP); - inputManager.deleteMapping(CameraInput.FLYCAM_DOWN); - - inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true)); - - inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false)); - - inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false)); - - inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true)); - - inputManager.addListener(flyCam, CameraInput.FLYCAM_LEFT, CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_UP, CameraInput.FLYCAM_DOWN); - } - - private void initInputs() { - inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M)); - inputManager.addMapping("snapshot", new KeyTrigger(KeyInput.KEY_SPACE)); - inputManager.addMapping("fc", new KeyTrigger(KeyInput.KEY_F)); - inputManager.addMapping("debugProbe", new KeyTrigger(KeyInput.KEY_RETURN)); - inputManager.addMapping("debugTex", new KeyTrigger(KeyInput.KEY_T)); - inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_UP)); - inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN)); - inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT)); - inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT)); - inputManager.addMapping("delete", new KeyTrigger(KeyInput.KEY_DELETE)); - - inputManager.addListener(this, "delete","switchGroundMat", "snapshot", "debugTex", "debugProbe", "fc", "up", "down", "left", "right"); - } - - private LightProbe lastProbe; - private Node debugGui ; - - @Override - public void onAction(String name, boolean keyPressed, float tpf) { - - if (name.equals("switchGroundMat") && keyPressed) { - if (ground.getMaterial() == matGroundL) { - ground.setMaterial(matGroundU); - } else { - - ground.setMaterial(matGroundL); - } - } - - if (name.equals("snapshot") && keyPressed) { - envCam.setPosition(camGeom.getWorldTranslation()); - lastProbe = LightProbeFactory.makeProbe(envCam, rootNode, new ConsoleProgressReporter()); - ((BoundingSphere)lastProbe.getBounds()).setRadius(200); - rootNode.addLight(lastProbe); - - } - - if (name.equals("delete") && keyPressed) { - System.err.println(rootNode.getWorldLightList().size()); - rootNode.removeLight(lastProbe); - System.err.println("deleted"); - System.err.println(rootNode.getWorldLightList().size()); - } - - if (name.equals("fc") && keyPressed) { - - flyCam.setEnabled(true); - } - - if (name.equals("debugProbe") && keyPressed) { - debugState.setEnabled(!debugState.isEnabled()); - } - - if (name.equals("debugTex") && keyPressed) { - if(debugGui == null || debugGui.getParent() == null){ - debugGui = lastProbe.getDebugGui(assetManager); - debugGui.setLocalTranslation(10, 200, 0); - guiNode.attachChild(debugGui); - } else if(debugGui != null){ - debugGui.removeFromParent(); - } - } - - if (name.equals("up")) { - up = keyPressed; - } - if (name.equals("down")) { - down = keyPressed; - } - if (name.equals("right")) { - right = keyPressed; - } - if (name.equals("left")) { - left = keyPressed; - } - if (name.equals("fwd")) { - fwd = keyPressed; - } - if (name.equals("back")) { - back = keyPressed; - } - - } - boolean up = false; - boolean down = false; - boolean left = false; - boolean right = false; - boolean fwd = false; - boolean back = false; - float time = 0; - float s = 50f; - boolean initialized = false; - - @Override - public void simpleUpdate(float tpf) { - - if (!initialized) { - fixFLyCamInputs(); - initialized = true; - } - float val = tpf * s; - if (up) { - camGeom.move(0, 0, val); - } - if (down) { - camGeom.move(0, 0, -val); - - } - if (right) { - camGeom.move(-val, 0, 0); - - } - if (left) { - camGeom.move(val, 0, 0); - - } - - } - -} diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java index d3d0da624..8aa044d55 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java @@ -31,22 +31,22 @@ */ package jme3test.model; -import com.jme3.animation.*; -import com.jme3.app.ChaseCameraAppState; -import com.jme3.app.SimpleApplication; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; +import com.jme3.app.*; import com.jme3.asset.plugins.FileLocator; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.math.*; import com.jme3.renderer.Limits; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; +import com.jme3.scene.*; import com.jme3.scene.control.Control; -import com.jme3.scene.debug.custom.SkeletonDebugAppState; +import com.jme3.scene.debug.custom.ArmatureDebugAppState; +import com.jme3.scene.plugins.gltf.GltfModelKey; +import jme3test.model.anim.EraseTimer; -import java.util.ArrayList; -import java.util.List; +import java.util.*; public class TestGltfLoading extends SimpleApplication { @@ -57,9 +57,13 @@ public class TestGltfLoading extends SimpleApplication { int assetIndex = 0; boolean useAutoRotate = false; private final static String indentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; - int duration = 2; + int duration = 1; boolean playAnim = true; + Geometry g; + int morphIndex = 0; + + public static void main(String[] args) { TestGltfLoading app = new TestGltfLoading(); app.start(); @@ -72,10 +76,12 @@ public class TestGltfLoading extends SimpleApplication { https://sketchfab.com/features/gltf You have to copy them in Model/gltf folder in the test-data project. */ + @Override public void simpleInitApp() { - SkeletonDebugAppState skeletonDebugAppState = new SkeletonDebugAppState(); - getStateManager().attach(skeletonDebugAppState); + ArmatureDebugAppState armatureDebugappState = new ArmatureDebugAppState(); + getStateManager().attach(armatureDebugappState); + setTimer(new EraseTimer()); String folder = System.getProperty("user.home"); assetManager.registerLocator(folder, FileLocator.class); @@ -109,31 +115,54 @@ public class TestGltfLoading extends SimpleApplication { // PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50); // rootNode.addLight(pl1); + //loadModel("Models/gltf/polly/project_polly.gltf", new Vector3f(0, 0, 0), 0.5f); + //loadModel("Models/gltf/zophrac/scene.gltf", new Vector3f(0, 0, 0), 0.1f); + // loadModel("Models/gltf/scifigirl/scene.gltf", new Vector3f(0, -1, 0), 0.1f); + //loadModel("Models/gltf/man/scene.gltf", new Vector3f(0, -1, 0), 0.1f); + //loadModel("Models/gltf/torus/scene.gltf", new Vector3f(0, -1, 0), 0.1f); + //loadModel("Models/gltf/morph/scene.gltf", new Vector3f(0, 0, 0), 0.2f); + //loadModel("Models/gltf/morphCube/AnimatedMorphCube.gltf", new Vector3f(0, 0, 0), 1f); + // loadModel("Models/gltf/morph/SimpleMorph.gltf", new Vector3f(0, 0, 0), 0.1f); + //loadModel("Models/gltf/nier/scene.gltf", new Vector3f(0, -1.5f, 0), 0.01f); + loadModel("Models/gltf/izzy/scene.gltf", new Vector3f(0, -1, 0), 0.01f); + //loadModel("Models/gltf/darth/scene.gltf", new Vector3f(0, -1, 0), 0.01f); + //loadModel("Models/gltf/mech/scene.gltf", new Vector3f(0, -1, 0), 0.01f); + //loadModel("Models/gltf/elephant/scene.gltf", new Vector3f(0, -1, 0), 0.01f); + //loadModel("Models/gltf/buffalo/scene.gltf", new Vector3f(0, -1, 0), 0.1f); + //loadModel("Models/gltf/war/scene.gltf", new Vector3f(0, -1, 0), 0.1f); + //loadModel("Models/gltf/ganjaarl/scene.gltf", new Vector3f(0, -1, 0), 0.01f); + //loadModel("Models/gltf/hero/scene.gltf", new Vector3f(0, -1, 0), 0.1f); + //loadModel("Models/gltf/mercy/scene.gltf", new Vector3f(0, -1, 0), 0.01f); + //loadModel("Models/gltf/crab/scene.gltf", Vector3f.ZERO, 1); + //loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f); + //loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f); // loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1); // loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 1); // loadModel("Models/gltf/damagedHelmet/damagedHelmet.gltf", Vector3f.ZERO, 1); // loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f); //// loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f); - // loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f); + //loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f); // loadModel("Models/gltf/animatedCube/AnimatedCube.gltf", Vector3f.ZERO, 0.5f); // -// //loadModel("Models/gltf/BoxAnimated/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f); + //loadModel("Models/gltf/BoxAnimated/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f); // -// loadModel("Models/gltf/RiggedFigure/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f); + //loadModel("Models/gltf/RiggedFigure/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f); //loadModel("Models/gltf/RiggedFigure/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f); //loadModel("Models/gltf/CesiumMan/CesiumMan.gltf", new Vector3f(0, -1, 0), 1f); //loadModel("Models/gltf/BrainStem/BrainStem.gltf", new Vector3f(0, -1, 0), 1f); //loadModel("Models/gltf/Jaime/Jaime.gltf", new Vector3f(0, -1, 0), 1f); -// loadModel("Models/gltf/GiantWorm/GiantWorm.gltf", new Vector3f(0, -1, 0), 1f); -// //loadModel("Models/gltf/RiggedFigure/WalkingLady.gltf", new Vector3f(0, -0.f, 0), 1f); -// loadModel("Models/gltf/Monster/Monster.gltf", Vector3f.ZERO, 0.03f); + // loadModel("Models/gltf/GiantWorm/GiantWorm.gltf", new Vector3f(0, -1, 0), 1f); + //loadModel("Models/gltf/RiggedFigure/WalkingLady.gltf", new Vector3f(0, -0.f, 0), 1f); + //loadModel("Models/gltf/Monster/Monster.gltf", Vector3f.ZERO, 0.03f); // loadModel("Models/gltf/corset/Corset.gltf", new Vector3f(0, -1, 0), 20f); - loadModel("Models/gltf/boxInter/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f); + // loadModel("Models/gltf/boxInter/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f); probeNode.attachChild(assets.get(0)); + // setMorphTarget(morphIndex); + ChaseCameraAppState chaseCam = new ChaseCameraAppState(); chaseCam.setTarget(probeNode); getStateManager().attach(chaseCam); @@ -156,6 +185,7 @@ public class TestGltfLoading extends SimpleApplication { }, "autorotate"); inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addListener(new ActionListener() { @Override public void onAction(String name, boolean isPressed, float tpf) { @@ -169,8 +199,21 @@ public class TestGltfLoading extends SimpleApplication { } } }, "toggleAnim"); + inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed && composer != null) { + String anim = anims.poll(); + anims.add(anim); + composer.setCurrentAction(anim); + } + } + }, "nextAnim"); dumpScene(rootNode, 0); + + // stateManager.attach(new DetailedProfilerState()); } private T findControl(Spatial s, Class controlClass) { @@ -191,7 +234,9 @@ public class TestGltfLoading extends SimpleApplication { } private void loadModel(String path, Vector3f offset, float scale) { - Spatial s = assetManager.loadModel(path); + GltfModelKey k = new GltfModelKey(path); + //k.setKeepSkeletonPose(true); + Spatial s = assetManager.loadModel(k); s.scale(scale); s.move(offset); assets.add(s); @@ -199,36 +244,44 @@ public class TestGltfLoading extends SimpleApplication { playFirstAnim(s); } - SkeletonControl ctrl = findControl(s, SkeletonControl.class); + SkinningControl ctrl = findControl(s, SkinningControl.class); -// //ctrl.getSpatial().removeControl(ctrl); + // ctrl.getSpatial().removeControl(ctrl); if (ctrl == null) { return; } - ctrl.setHardwareSkinningPreferred(false); - getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true); + //System.err.println(ctrl.getArmature().toString()); + //ctrl.setHardwareSkinningPreferred(false); + // getStateManager().getState(ArmatureDebugAppState.class).addArmatureFrom(ctrl); // AnimControl aCtrl = findControl(s, AnimControl.class); // //ctrl.getSpatial().removeControl(ctrl); // if (aCtrl == null) { // return; // } -// if (aCtrl.getSkeleton() != null) { -// getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(aCtrl.getSkeleton(), aCtrl.getSpatial(), true); +// if (aCtrl.getArmature() != null) { +// getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(aCtrl.getArmature(), aCtrl.getSpatial(), true); // } } + Queue anims = new LinkedList<>(); + AnimComposer composer; + private void playFirstAnim(Spatial s) { - AnimControl control = s.getControl(AnimControl.class); + AnimComposer control = s.getControl(AnimComposer.class); if (control != null) { -// if (control.getAnimationNames().size() > 0) { -// control.createChannel().setAnim(control.getAnimationNames().iterator().next()); -// } - for (String name : control.getAnimationNames()) { - control.createChannel().setAnim(name); + anims.clear(); + for (String name : control.getAnimClipsNames()) { + anims.add(name); } - + if (anims.isEmpty()) { + return; + } + String anim = anims.poll(); + anims.add(anim); + control.setCurrentAction(anim); + composer = control; } if (s instanceof Node) { Node n = (Node) s; @@ -240,17 +293,9 @@ public class TestGltfLoading extends SimpleApplication { private void stopAnim(Spatial s) { - AnimControl control = s.getControl(AnimControl.class); + AnimComposer control = s.getControl(AnimComposer.class); if (control != null) { - for (int i = 0; i < control.getNumChannels(); i++) { - AnimChannel ch = control.getChannel(i); - ch.reset(true); - } - control.clearChannels(); - if (control.getSkeleton() != null) { - control.getSkeleton().reset(); - } - + control.reset(); } if (s instanceof Node) { Node n = (Node) s; @@ -262,18 +307,19 @@ public class TestGltfLoading extends SimpleApplication { @Override public void simpleUpdate(float tpf) { - if (!useAutoRotate) { return; } time += tpf; - autoRotate.rotate(0, tpf * 0.5f, 0); + // autoRotate.rotate(0, tpf * 0.5f, 0); if (time > duration) { + // morphIndex++; + // setMorphTarget(morphIndex); assets.get(assetIndex).removeFromParent(); assetIndex = (assetIndex + 1) % assets.size(); - if (assetIndex == 0) { - duration = 10; - } +// if (assetIndex == 0) { +// duration = 10; +// } probeNode.attachChild(assets.get(assetIndex)); time = 0; } diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading2.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading2.java deleted file mode 100644 index 7988da4fa..000000000 --- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading2.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.model; - -import com.jme3.animation.*; -import com.jme3.app.ChaseCameraAppState; -import com.jme3.app.SimpleApplication; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.math.*; -import com.jme3.renderer.Limits; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.control.Control; -import com.jme3.scene.debug.custom.SkeletonDebugAppState; -import com.jme3.scene.plugins.gltf.GltfModelKey; - -import java.util.*; - -public class TestGltfLoading2 extends SimpleApplication { - - Node autoRotate = new Node("autoRotate"); - List assets = new ArrayList<>(); - Node probeNode; - float time = 0; - int assetIndex = 0; - boolean useAutoRotate = false; - private final static String indentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; - int duration = 2; - boolean playAnim = true; - - public static void main(String[] args) { - TestGltfLoading2 app = new TestGltfLoading2(); - app.start(); - } - - /* - WARNING this test case can't wok without the assets, and considering their size, they are not pushed into the repo - you can find them here : - https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0 - https://sketchfab.com/features/gltf - You have to copy them in Model/gltf folder in the test-data project. - */ - public void simpleInitApp() { - - SkeletonDebugAppState skeletonDebugAppState = new SkeletonDebugAppState(); - getStateManager().attach(skeletonDebugAppState); - - // cam.setLocation(new Vector3f(4.0339394f, 2.645184f, 6.4627485f)); - // cam.setRotation(new Quaternion(-0.013950467f, 0.98604023f, -0.119502485f, -0.11510504f)); - cam.setFrustumPerspective(45f, (float) cam.getWidth() / cam.getHeight(), 0.1f, 100f); - renderer.setDefaultAnisotropicFilter(Math.min(renderer.getLimits().get(Limits.TextureAnisotropy), 8)); - setPauseOnLostFocus(false); - - flyCam.setMoveSpeed(5); - flyCam.setDragToRotate(true); - flyCam.setEnabled(false); - viewPort.setBackgroundColor(new ColorRGBA().setAsSrgb(0.2f, 0.2f, 0.2f, 1.0f)); - rootNode.attachChild(autoRotate); - probeNode = (Node) assetManager.loadModel("Scenes/defaultProbe.j3o"); - autoRotate.attachChild(probeNode); - -// DirectionalLight dl = new DirectionalLight(); -// dl.setDirection(new Vector3f(-1f, -1.0f, -1f).normalizeLocal()); -// dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); -// rootNode.addLight(dl); - -// DirectionalLight dl2 = new DirectionalLight(); -// dl2.setDirection(new Vector3f(1f, 1.0f, 1f).normalizeLocal()); -// dl2.setColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)); -// rootNode.addLight(dl2); - -// PointLight pl = new PointLight(new Vector3f(5.0f, 5.0f, 5.0f), ColorRGBA.White, 30); -// rootNode.addLight(pl); -// PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50); -// rootNode.addLight(pl1); - //loadModel("Models/gltf/buffalo/scene.gltf", new Vector3f(0, -1, 0), 0.1f); - //loadModel("Models/gltf/war/scene.gltf", new Vector3f(0, -1, 0), 0.1f); - loadModel("Models/gltf/ganjaarl/scene.gltf", new Vector3f(0, -1, 0), 0.01f); - //loadModel("Models/gltf/hero/scene.gltf", new Vector3f(0, -1, 0), 0.1f); - //loadModel("Models/gltf/mercy/scene.gltf", new Vector3f(0, -1, 0), 0.01f); - //loadModel("Models/gltf/crab/scene.gltf", Vector3f.ZERO, 1); - //loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f); -// loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f); -// loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1); -// loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 1); -// loadModel("Models/gltf/damagedHelmet/damagedHelmet.gltf", Vector3f.ZERO, 1); -// loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f); -//// loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f); - //loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f); -// loadModel("Models/gltf/animatedCube/AnimatedCube.gltf", Vector3f.ZERO, 0.5f); -// -// //loadModel("Models/gltf/BoxAnimated/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f); -// - //loadModel("Models/gltf/RiggedFigure/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f); - //loadModel("Models/gltf/RiggedFigure/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f); - //loadModel("Models/gltf/CesiumMan/CesiumMan.gltf", new Vector3f(0, -1, 0), 1f); - //loadModel("Models/gltf/BrainStem/BrainStem.gltf", new Vector3f(0, -1, 0), 1f); - //loadModel("Models/gltf/Jaime/Jaime.gltf", new Vector3f(0, -1, 0), 1f); - //loadModel("Models/gltf/GiantWorm/GiantWorm.gltf", new Vector3f(0, -1, 0), 1f); - //loadModel("Models/gltf/RiggedFigure/WalkingLady.gltf", new Vector3f(0, -0.f, 0), 1f); - //loadModel("Models/gltf/Monster/Monster.gltf", Vector3f.ZERO, 0.03f); - -// loadModel("Models/gltf/corset/Corset.gltf", new Vector3f(0, -1, 0), 20f); - // loadModel("Models/gltf/boxInter/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f); - - - probeNode.attachChild(assets.get(0)); - - ChaseCameraAppState chaseCam = new ChaseCameraAppState(); - chaseCam.setTarget(probeNode); - getStateManager().attach(chaseCam); - chaseCam.setInvertHorizontalAxis(true); - chaseCam.setInvertVerticalAxis(true); - chaseCam.setZoomSpeed(0.5f); - chaseCam.setMinVerticalRotation(-FastMath.HALF_PI); - chaseCam.setRotationSpeed(3); - chaseCam.setDefaultDistance(3); - chaseCam.setDefaultVerticalRotation(0.3f); - - inputManager.addMapping("autorotate", new KeyTrigger(KeyInput.KEY_SPACE)); - inputManager.addListener(new ActionListener() { - @Override - public void onAction(String name, boolean isPressed, float tpf) { - if (isPressed) { - useAutoRotate = !useAutoRotate; - } - } - }, "autorotate"); - - inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN)); - - inputManager.addListener(new ActionListener() { - @Override - public void onAction(String name, boolean isPressed, float tpf) { - if (isPressed) { - playAnim = !playAnim; - if (playAnim) { - playFirstAnim(rootNode); - } else { - stopAnim(rootNode); - } - } - } - }, "toggleAnim"); - inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT)); - inputManager.addListener(new ActionListener() { - @Override - public void onAction(String name, boolean isPressed, float tpf) { - if (isPressed && animControl != null) { - AnimChannel c = animControl.getChannel(0); - if (c == null) { - c = animControl.createChannel(); - } - String anim = anims.poll(); - anims.add(anim); - c.setAnim(anim); - } - } - }, "nextAnim"); - - dumpScene(rootNode, 0); - } - - private T findControl(Spatial s, Class controlClass) { - T ctrl = s.getControl(controlClass); - if (ctrl != null) { - return ctrl; - } - if (s instanceof Node) { - Node n = (Node) s; - for (Spatial spatial : n.getChildren()) { - ctrl = findControl(spatial, controlClass); - if (ctrl != null) { - return ctrl; - } - } - } - return null; - } - - private void loadModel(String path, Vector3f offset, float scale) { - GltfModelKey k = new GltfModelKey(path); - //k.setKeepSkeletonPose(true); - Spatial s = assetManager.loadModel(k); - s.scale(scale); - s.move(offset); - assets.add(s); - if (playAnim) { - playFirstAnim(s); - } - - SkeletonControl ctrl = findControl(s, SkeletonControl.class); - - // ctrl.getSpatial().removeControl(ctrl); - if (ctrl == null) { - return; - } - //System.err.println(ctrl.getSkeleton().toString()); - //ctrl.setHardwareSkinningPreferred(false); - // getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true); -// AnimControl aCtrl = findControl(s, AnimControl.class); -// //ctrl.getSpatial().removeControl(ctrl); -// if (aCtrl == null) { -// return; -// } -// if (aCtrl.getSkeleton() != null) { -// getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(aCtrl.getSkeleton(), aCtrl.getSpatial(), true); -// } - - } - - Queue anims = new LinkedList<>(); - AnimControl animControl; - - private void playFirstAnim(Spatial s) { - - AnimControl control = s.getControl(AnimControl.class); - if (control != null) { - anims.clear(); - for (String name : control.getAnimationNames()) { - anims.add(name); - } - if (anims.isEmpty()) { - return; - } - String anim = anims.poll(); - anims.add(anim); - control.createChannel().setAnim(anim); - animControl = control; - } - if (s instanceof Node) { - Node n = (Node) s; - for (Spatial spatial : n.getChildren()) { - playFirstAnim(spatial); - } - } - } - - private void stopAnim(Spatial s) { - - AnimControl control = s.getControl(AnimControl.class); - if (control != null) { - for (int i = 0; i < control.getNumChannels(); i++) { - AnimChannel ch = control.getChannel(i); - ch.reset(true); - } - control.clearChannels(); - } - if (s instanceof Node) { - Node n = (Node) s; - for (Spatial spatial : n.getChildren()) { - stopAnim(spatial); - } - } - } - - @Override - public void simpleUpdate(float tpf) { - - if (!useAutoRotate) { - return; - } - time += tpf; - autoRotate.rotate(0, tpf * 0.5f, 0); - if (time > duration) { - assets.get(assetIndex).removeFromParent(); - assetIndex = (assetIndex + 1) % assets.size(); - if (assetIndex == 0) { - duration = 10; - } - probeNode.attachChild(assets.get(assetIndex)); - time = 0; - } - } - - private void dumpScene(Spatial s, int indent) { - System.err.println(indentString.substring(0, indent) + s.getName() + " (" + s.getClass().getSimpleName() + ") / " + - s.getLocalTransform().getTranslation().toString() + ", " + - s.getLocalTransform().getRotation().toString() + ", " + - s.getLocalTransform().getScale().toString()); - if (s instanceof Node) { - Node n = (Node) s; - for (Spatial spatial : n.getChildren()) { - dumpScene(spatial, indent + 1); - } - } - } -} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/EraseTimer.java b/jme3-examples/src/main/java/jme3test/model/anim/EraseTimer.java new file mode 100644 index 000000000..ba6189195 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/EraseTimer.java @@ -0,0 +1,76 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package jme3test.model.anim; + +import com.jme3.system.Timer; + +/** + * @author Nehon + */ +public class EraseTimer extends Timer { + + + //private static final long TIMER_RESOLUTION = 1000L; + //private static final float INVERSE_TIMER_RESOLUTION = 1f/1000L; + private static final long TIMER_RESOLUTION = 1000000000L; + private static final float INVERSE_TIMER_RESOLUTION = 1f / 1000000000L; + + private long startTime; + private long previousTime; + private float tpf; + private float fps; + + public EraseTimer() { + //startTime = System.currentTimeMillis(); + startTime = System.nanoTime(); + } + + /** + * Returns the time in seconds. The timer starts + * at 0.0 seconds. + * + * @return the current time in seconds + */ + @Override + public float getTimeInSeconds() { + return getTime() * INVERSE_TIMER_RESOLUTION; + } + + public long getTime() { + //return System.currentTimeMillis() - startTime; + return System.nanoTime() - startTime; + } + + public long getResolution() { + return TIMER_RESOLUTION; + } + + public float getFrameRate() { + return fps; + } + + public float getTimePerFrame() { + return tpf; + } + + public void update() { + tpf = (getTime() - previousTime) * (1.0f / TIMER_RESOLUTION); + if (tpf >= 0.2) { + //the frame lasted more than 200ms we erase its time to 16ms. + tpf = 0.016666f; + } else { + fps = 1.0f / tpf; + } + previousTime = getTime(); + } + + public void reset() { + //startTime = System.currentTimeMillis(); + startTime = System.nanoTime(); + previousTime = getTime(); + } + + +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimBlendBug.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimBlendBug.java deleted file mode 100644 index b57fc83cb..000000000 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimBlendBug.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.model.anim; - -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.app.SimpleApplication; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.debug.SkeletonDebugger; - -public class TestAnimBlendBug extends SimpleApplication implements ActionListener { - -// private AnimControl control; - private AnimChannel channel1, channel2; - private String[] animNames; - - private float blendTime = 0.5f; - private float lockAfterBlending = blendTime + 0.25f; - private float blendingAnimationLock; - - public static void main(String[] args) { - TestAnimBlendBug app = new TestAnimBlendBug(); - app.start(); - } - - public void onAction(String name, boolean value, float tpf) { - if (name.equals("One") && value){ - channel1.setAnim(animNames[4], blendTime); - channel2.setAnim(animNames[4], 0); - channel1.setSpeed(0.25f); - channel2.setSpeed(0.25f); - blendingAnimationLock = lockAfterBlending; - } - } - - public void onPreUpdate(float tpf) { - } - - public void onPostUpdate(float tpf) { - } - - @Override - public void simpleUpdate(float tpf) { - // Is there currently a blending underway? - if (blendingAnimationLock > 0f) { - blendingAnimationLock -= tpf; - } - } - - @Override - public void simpleInitApp() { - inputManager.addMapping("One", new KeyTrigger(KeyInput.KEY_1)); - inputManager.addListener(this, "One"); - - flyCam.setMoveSpeed(100f); - cam.setLocation( new Vector3f( 0f, 150f, -325f ) ); - cam.lookAt( new Vector3f( 0f, 100f, 0f ), Vector3f.UNIT_Y ); - - DirectionalLight dl = new DirectionalLight(); - dl.setDirection(new Vector3f(-0.1f, -0.7f, 1).normalizeLocal()); - dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); - rootNode.addLight(dl); - - Node model1 = (Node) assetManager.loadModel("Models/Ninja/Ninja.mesh.xml"); - Node model2 = (Node) assetManager.loadModel("Models/Ninja/Ninja.mesh.xml"); -// Node model2 = model1.clone(); - - model1.setLocalTranslation(-60, 0, 0); - model2.setLocalTranslation(60, 0, 0); - - AnimControl control1 = model1.getControl(AnimControl.class); - animNames = control1.getAnimationNames().toArray(new String[0]); - channel1 = control1.createChannel(); - - AnimControl control2 = model2.getControl(AnimControl.class); - channel2 = control2.createChannel(); - - SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton1", control1.getSkeleton()); - Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mat.getAdditionalRenderState().setWireframe(true); - mat.setColor("Color", ColorRGBA.Red); - mat.setFloat("PointSize", 7f); - mat.getAdditionalRenderState().setDepthTest(false); - skeletonDebug.setMaterial(mat); - model1.attachChild(skeletonDebug); - - skeletonDebug = new SkeletonDebugger("skeleton2", control2.getSkeleton()); - skeletonDebug.setMaterial(mat); - model2.attachChild(skeletonDebug); - - rootNode.attachChild(model1); - rootNode.attachChild(model2); - } - -} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java new file mode 100644 index 000000000..18cbb0275 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java @@ -0,0 +1,218 @@ +package jme3test.model.anim; + +import com.jme3.anim.*; +import com.jme3.anim.tween.action.Action; +import com.jme3.anim.tween.action.BlendAction; +import com.jme3.anim.tween.action.BlendableAction; +import com.jme3.anim.tween.action.LinearBlendSpace; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.app.ChaseCameraAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.*; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.custom.ArmatureDebugAppState; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * Created by Nehon on 18/12/2017. + */ +public class TestAnimMigration extends SimpleApplication { + + ArmatureDebugAppState debugAppState; + AnimComposer composer; + LinkedList anims = new LinkedList<>(); + boolean playAnim = false; + BlendAction action; + float blendValue = 1f; + + public static void main(String... argv) { + TestAnimMigration app = new TestAnimMigration(); + app.start(); + } + + @Override + public void simpleInitApp() { + setTimer(new EraseTimer()); + cam.setFrustumPerspective(45f, (float) cam.getWidth() / cam.getHeight(), 0.1f, 100f); + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal())); + rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray)); + + Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o"); + // Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml").scale(0.2f).move(0, 1, 0); + //Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + //Spatial model = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml").scale(0.02f); + + AnimMigrationUtils.migrate(model); + + rootNode.attachChild(model); + + + debugAppState = new ArmatureDebugAppState(); + stateManager.attach(debugAppState); + + setupModel(model); + + flyCam.setEnabled(false); + + Node target = new Node("CamTarget"); + //target.setLocalTransform(model.getLocalTransform()); + target.move(0, 1, 0); + ChaseCameraAppState chaseCam = new ChaseCameraAppState(); + chaseCam.setTarget(target); + getStateManager().attach(chaseCam); + chaseCam.setInvertHorizontalAxis(true); + chaseCam.setInvertVerticalAxis(true); + chaseCam.setZoomSpeed(0.5f); + chaseCam.setMinVerticalRotation(-FastMath.HALF_PI); + chaseCam.setRotationSpeed(3); + chaseCam.setDefaultDistance(3); + chaseCam.setMinDistance(0.01f); + chaseCam.setZoomSpeed(0.01f); + chaseCam.setDefaultVerticalRotation(0.3f); + + initInputs(); + } + + public void initInputs() { + inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN)); + + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + playAnim = !playAnim; + if (playAnim) { + String anim = anims.poll(); + anims.add(anim); + composer.setCurrentAction(anim); + System.err.println(anim); + } else { + composer.reset(); + } + } + } + }, "toggleAnim"); + inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed && composer != null) { + String anim = anims.poll(); + anims.add(anim); + composer.setCurrentAction(anim); + System.err.println(anim); + } + } + }, "nextAnim"); + inputManager.addMapping("toggleArmature", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + debugAppState.setEnabled(!debugAppState.isEnabled()); + } + } + }, "toggleArmature"); + + inputManager.addMapping("mask", new KeyTrigger(KeyInput.KEY_M)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + composer.setCurrentAction("Wave", "LeftArm"); + } + } + }, "mask"); + + inputManager.addMapping("blendUp", new KeyTrigger(KeyInput.KEY_UP)); + inputManager.addMapping("blendDown", new KeyTrigger(KeyInput.KEY_DOWN)); + + inputManager.addListener(new AnalogListener() { + + @Override + public void onAnalog(String name, float value, float tpf) { + if (name.equals("blendUp")) { + blendValue += value; + blendValue = FastMath.clamp(blendValue, 1, 4); + action.getBlendSpace().setValue(blendValue); + action.setSpeed(blendValue); + } + if (name.equals("blendDown")) { + blendValue -= value; + blendValue = FastMath.clamp(blendValue, 1, 4); + action.getBlendSpace().setValue(blendValue); + action.setSpeed(blendValue); + } + //System.err.println(blendValue); + } + }, "blendUp", "blendDown"); + } + + private void setupModel(Spatial model) { + if (composer != null) { + return; + } + composer = model.getControl(AnimComposer.class); + if (composer != null) { + + SkinningControl sc = model.getControl(SkinningControl.class); + debugAppState.addArmatureFrom(sc); + + anims.clear(); + for (String name : composer.getAnimClipsNames()) { + anims.add(name); + } + composer.actionSequence("Sequence1", + composer.makeAction("Walk"), + composer.makeAction("Run"), + composer.makeAction("Jumping")).setSpeed(1); + + composer.actionSequence("Sequence2", + composer.makeAction("Walk"), + composer.makeAction("Run"), + composer.makeAction("Jumping")).setSpeed(-1); + + action = composer.actionBlended("Blend", new LinearBlendSpace(1, 4), + "Walk", "Run"); + + action.getBlendSpace().setValue(1); + + composer.action("Walk").setSpeed(-1); + + composer.makeLayer("LeftArm", ArmatureMask.createMask(sc.getArmature(), "shoulder.L")); + + anims.addFirst("Blend"); + anims.addFirst("Sequence2"); + anims.addFirst("Sequence1"); + + if (anims.isEmpty()) { + return; + } + if (playAnim) { + String anim = anims.poll(); + anims.add(anim); + composer.setCurrentAction(anim); + System.err.println(anim); + } + + } else { + if (model instanceof Node) { + Node n = (Node) model; + for (Spatial child : n.getChildren()) { + setupModel(child); + } + } + } + + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java new file mode 100644 index 000000000..2e122a35e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java @@ -0,0 +1,170 @@ +package jme3test.model.anim; + +import com.jme3.anim.*; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.app.ChaseCameraAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.FileLocator; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.*; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.custom.ArmatureDebugAppState; +import com.jme3.system.JmeSystem; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.Queue; + +/** + * Created by Nehon on 18/12/2017. + */ +public class TestAnimMorphSerialization extends SimpleApplication { + + ArmatureDebugAppState debugAppState; + AnimComposer composer; + Queue anims = new LinkedList<>(); + boolean playAnim = true; + File file; + + public static void main(String... argv) { + TestAnimMorphSerialization app = new TestAnimMorphSerialization(); + app.start(); + } + + @Override + public void simpleInitApp() { + setTimer(new EraseTimer()); + //cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f); + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + //rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal())); + //rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray)); + Node probeNode = (Node) assetManager.loadModel("Scenes/defaultProbe.j3o"); + rootNode.attachChild(probeNode); + Spatial model = assetManager.loadModel("Models/gltf/zophrac/scene.gltf"); + + File storageFolder = JmeSystem.getStorageFolder(); + file = new File(storageFolder.getPath() + File.separator + "zophrac.j3o"); + BinaryExporter be = new BinaryExporter(); + try { + be.save(model, file); + } catch (IOException e) { + e.printStackTrace(); + } + + assetManager.registerLocator(storageFolder.getPath(), FileLocator.class); + Spatial model2 = assetManager.loadModel("zophrac.j3o"); + model2.setLocalScale(0.1f); + probeNode.attachChild(model2); + + debugAppState = new ArmatureDebugAppState(); + stateManager.attach(debugAppState); + + setupModel(model2); + + flyCam.setEnabled(false); + + Node target = new Node("CamTarget"); + //target.setLocalTransform(model.getLocalTransform()); + target.move(0, 0, 0); + ChaseCameraAppState chaseCam = new ChaseCameraAppState(); + chaseCam.setTarget(target); + getStateManager().attach(chaseCam); + chaseCam.setInvertHorizontalAxis(true); + chaseCam.setInvertVerticalAxis(true); + chaseCam.setZoomSpeed(0.5f); + chaseCam.setMinVerticalRotation(-FastMath.HALF_PI); + chaseCam.setRotationSpeed(3); + chaseCam.setDefaultDistance(3); + chaseCam.setMinDistance(0.01f); + chaseCam.setZoomSpeed(0.01f); + chaseCam.setDefaultVerticalRotation(0.3f); + + initInputs(); + } + + public void initInputs() { + inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN)); + + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + playAnim = !playAnim; + if (playAnim) { + String anim = anims.poll(); + anims.add(anim); + composer.setCurrentAction(anim); + System.err.println(anim); + } else { + composer.reset(); + } + } + } + }, "toggleAnim"); + inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed && composer != null) { + String anim = anims.poll(); + anims.add(anim); + composer.setCurrentAction(anim); + System.err.println(anim); + } + } + }, "nextAnim"); + } + + private void setupModel(Spatial model) { + if (composer != null) { + return; + } + composer = model.getControl(AnimComposer.class); + if (composer != null) { +// model.getControl(SkinningControl.class).setEnabled(false); +// model.getControl(MorphControl.class).setEnabled(false); +// composer.setEnabled(false); + + + SkinningControl sc = model.getControl(SkinningControl.class); + debugAppState.addArmatureFrom(sc); + + anims.clear(); + for (String name : composer.getAnimClipsNames()) { + anims.add(name); + } + if (anims.isEmpty()) { + return; + } + if (playAnim) { + String anim = anims.poll(); + anims.add(anim); + composer.setCurrentAction(anim); + System.err.println(anim); + } + + } else { + if (model instanceof Node) { + Node n = (Node) model; + for (Spatial child : n.getChildren()) { + setupModel(child); + } + } + } + + } + + + @Override + public void destroy() { + super.destroy(); + file.delete(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java new file mode 100644 index 000000000..1d3551df2 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java @@ -0,0 +1,168 @@ +package jme3test.model.anim; + +import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.app.ChaseCameraAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.FileLocator; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.*; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.custom.ArmatureDebugAppState; +import com.jme3.system.JmeSystem; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.Queue; + +/** + * Created by Nehon on 18/12/2017. + */ +public class TestAnimSerialization extends SimpleApplication { + + ArmatureDebugAppState debugAppState; + AnimComposer composer; + Queue anims = new LinkedList<>(); + boolean playAnim = true; + File file; + + public static void main(String... argv) { + TestAnimSerialization app = new TestAnimSerialization(); + app.start(); + } + + @Override + public void simpleInitApp() { + setTimer(new EraseTimer()); + //cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f); + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal())); + rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray)); + + Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o"); + + AnimMigrationUtils.migrate(model); + + File storageFolder = JmeSystem.getStorageFolder(); + file = new File(storageFolder.getPath() + File.separator + "newJaime.j3o"); + BinaryExporter be = new BinaryExporter(); + try { + be.save(model, file); + } catch (IOException e) { + e.printStackTrace(); + } + + assetManager.registerLocator(storageFolder.getPath(), FileLocator.class); + model = assetManager.loadModel("newJaime.j3o"); + + rootNode.attachChild(model); + + debugAppState = new ArmatureDebugAppState(); + stateManager.attach(debugAppState); + + setupModel(model); + + flyCam.setEnabled(false); + + Node target = new Node("CamTarget"); + //target.setLocalTransform(model.getLocalTransform()); + target.move(0, 1, 0); + ChaseCameraAppState chaseCam = new ChaseCameraAppState(); + chaseCam.setTarget(target); + getStateManager().attach(chaseCam); + chaseCam.setInvertHorizontalAxis(true); + chaseCam.setInvertVerticalAxis(true); + chaseCam.setZoomSpeed(0.5f); + chaseCam.setMinVerticalRotation(-FastMath.HALF_PI); + chaseCam.setRotationSpeed(3); + chaseCam.setDefaultDistance(3); + chaseCam.setMinDistance(0.01f); + chaseCam.setZoomSpeed(0.01f); + chaseCam.setDefaultVerticalRotation(0.3f); + + initInputs(); + } + + public void initInputs() { + inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN)); + + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + playAnim = !playAnim; + if (playAnim) { + String anim = anims.poll(); + anims.add(anim); + composer.setCurrentAction(anim); + System.err.println(anim); + } else { + composer.reset(); + } + } + } + }, "toggleAnim"); + inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed && composer != null) { + String anim = anims.poll(); + anims.add(anim); + composer.setCurrentAction(anim); + System.err.println(anim); + } + } + }, "nextAnim"); + } + + private void setupModel(Spatial model) { + if (composer != null) { + return; + } + composer = model.getControl(AnimComposer.class); + if (composer != null) { + + SkinningControl sc = model.getControl(SkinningControl.class); + + debugAppState.addArmatureFrom(sc); + anims.clear(); + for (String name : composer.getAnimClipsNames()) { + anims.add(name); + } + if (anims.isEmpty()) { + return; + } + if (playAnim) { + String anim = anims.poll(); + anims.add(anim); + composer.setCurrentAction(anim); + System.err.println(anim); + } + + } else { + if (model instanceof Node) { + Node n = (Node) model; + for (Spatial child : n.getChildren()) { + setupModel(child); + } + } + } + + } + + + @Override + public void destroy() { + super.destroy(); + file.delete(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java b/jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java new file mode 100644 index 000000000..f7a839327 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java @@ -0,0 +1,217 @@ +package jme3test.model.anim; + +import com.jme3.anim.*; +import com.jme3.app.ChaseCameraAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.scene.*; +import com.jme3.scene.debug.custom.ArmatureDebugAppState; +import com.jme3.scene.shape.Cylinder; +import com.jme3.util.TangentBinormalGenerator; + +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * Created by Nehon on 18/12/2017. + */ +public class TestArmature extends SimpleApplication { + + Joint j1; + Joint j2; + + public static void main(String... argv) { + TestArmature app = new TestArmature(); + app.start(); + } + + @Override + public void simpleInitApp() { + setTimer(new EraseTimer()); + renderManager.setSinglePassLightBatchSize(2); + //cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f); + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + + //create armature + Joint root = new Joint("Root_Joint"); + j1 = new Joint("Joint_1"); + j2 = new Joint("Joint_2"); + Joint j3 = new Joint("Joint_3"); + root.addChild(j1); + j1.addChild(j2); + j2.addChild(j3); + root.setLocalTranslation(new Vector3f(0, 0, 0.5f)); + j1.setLocalTranslation(new Vector3f(0, 0.0f, -0.5f)); + j2.setLocalTranslation(new Vector3f(0, 0.0f, -0.3f)); + j3.setLocalTranslation(new Vector3f(0, 0, -0.2f)); + Joint[] joints = new Joint[]{root, j1, j2, j3}; + + final Armature armature = new Armature(joints); + //armature.setModelTransformClass(SeparateJointModelTransform.class); + armature.saveBindPose(); + + //create animations + AnimClip clip = new AnimClip("anim"); + float[] times = new float[]{0, 2, 4}; + Quaternion[] rotations = new Quaternion[]{ + new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X), + new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X), + new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X) + }; + Vector3f[] translations = new Vector3f[]{ + new Vector3f(0, 0.2f, 0), + new Vector3f(0, 1.0f, 0), + new Vector3f(0, 0.2f, 0), + }; + Vector3f[] scales = new Vector3f[]{ + new Vector3f(1, 1, 1), + new Vector3f(1, 1, 2), + new Vector3f(1, 1, 1), + }; + Vector3f[] scales2 = new Vector3f[]{ + new Vector3f(1, 1, 1), + new Vector3f(1, 1, 0.5f), + new Vector3f(1, 1, 1), + }; + + TransformTrack track1 = new TransformTrack(j1, times, null, rotations, scales); + TransformTrack track2 = new TransformTrack(j2, times, null, rotations, null); + + clip.setTracks(new TransformTrack[]{track1, track2}); + + //create the animComposer control + final AnimComposer composer = new AnimComposer(); + composer.addAnimClip(clip); + + //create the SkinningControl + SkinningControl ac = new SkinningControl(armature); + ac.setHardwareSkinningPreferred(false); + Node node = new Node("Test Armature"); + + rootNode.attachChild(node); + + //Create the mesh to deform. + Geometry cylinder = new Geometry("cylinder", createMesh()); + Material m = new Material(assetManager, "Common/MatDefs/Misc/fakeLighting.j3md"); + m.setColor("Color", ColorRGBA.randomColor()); + cylinder.setMaterial(m); + node.attachChild(cylinder); + node.addControl(composer); + node.addControl(ac); + + composer.setCurrentAction("anim"); + + ArmatureDebugAppState debugAppState = new ArmatureDebugAppState(); + debugAppState.addArmatureFrom(ac); + stateManager.attach(debugAppState); + + flyCam.setEnabled(false); + + ChaseCameraAppState chaseCam = new ChaseCameraAppState(); + chaseCam.setTarget(node); + getStateManager().attach(chaseCam); + chaseCam.setInvertHorizontalAxis(true); + chaseCam.setInvertVerticalAxis(true); + chaseCam.setZoomSpeed(0.5f); + chaseCam.setMinVerticalRotation(-FastMath.HALF_PI); + chaseCam.setRotationSpeed(3); + chaseCam.setDefaultDistance(3); + chaseCam.setMinDistance(0.01f); + chaseCam.setZoomSpeed(0.01f); + chaseCam.setDefaultVerticalRotation(0.3f); + + + inputManager.addMapping("bind", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + composer.reset(); + armature.applyBindPose(); + + } else { + composer.setCurrentAction("anim"); + } + } + }, "bind"); + } + + + private void displayNormals(Spatial s) { + final Node debugTangents = new Node("debug tangents"); + debugTangents.setCullHint(Spatial.CullHint.Never); + + rootNode.attachChild(debugTangents); + + final Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); + debugMat.getAdditionalRenderState().setLineWidth(2); + + s.depthFirstTraversal(new SceneGraphVisitorAdapter() { + @Override + public void visit(Geometry g) { + Mesh m = g.getMesh(); + Geometry debug = new Geometry( + "debug tangents geom", + TangentBinormalGenerator.genNormalLines(m, 0.1f) + ); + debug.setMaterial(debugMat); + debug.setCullHint(Spatial.CullHint.Never); + debug.setLocalTransform(g.getWorldTransform()); + debugTangents.attachChild(debug); + } + }); + } + + private Mesh createMesh() { + Cylinder c = new Cylinder(30, 16, 0.1f, 1, true); + + ShortBuffer jointIndex = (ShortBuffer) VertexBuffer.createBuffer(VertexBuffer.Format.UnsignedShort, 4, c.getVertexCount()); + jointIndex.rewind(); + c.setMaxNumWeights(1); + FloatBuffer jointWeight = (FloatBuffer) VertexBuffer.createBuffer(VertexBuffer.Format.Float, 4, c.getVertexCount()); + jointWeight.rewind(); + VertexBuffer vb = c.getBuffer(VertexBuffer.Type.Position); + FloatBuffer fvb = (FloatBuffer) vb.getData(); + fvb.rewind(); + for (int i = 0; i < c.getVertexCount(); i++) { + fvb.get(); + fvb.get(); + float z = fvb.get(); + int index = 0; + if (z > 0) { + index = 0; + } else if (z > -0.2) { + index = 1; + } else { + index = 2; + } + jointIndex.put((short) index).put((short) 0).put((short) 0).put((short) 0); + jointWeight.put(1f).put(0f).put(0f).put(0f); + + } + c.setBuffer(VertexBuffer.Type.BoneIndex, 4, jointIndex); + c.setBuffer(VertexBuffer.Type.BoneWeight, 4, jointWeight); + + c.updateCounts(); + c.updateBound(); + + VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight); + VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex); + + indicesHW.setUsage(VertexBuffer.Usage.CpuOnly); + weightsHW.setUsage(VertexBuffer.Usage.CpuOnly); + c.setBuffer(weightsHW); + c.setBuffer(indicesHW); + c.generateBindPose(); + + c.prepareForAnim(false); + + return c; + } + + +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java b/jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java new file mode 100644 index 000000000..24d8b21e5 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java @@ -0,0 +1,217 @@ +package jme3test.model.anim; + +import com.jme3.anim.*; +import com.jme3.app.ChaseCameraAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.FileLocator; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.scene.*; +import com.jme3.scene.debug.custom.ArmatureDebugAppState; +import com.jme3.scene.shape.Cylinder; +import com.jme3.system.JmeSystem; + +import java.io.File; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + * Created by Nehon on 18/12/2017. + */ +public class TestBaseAnimSerialization extends SimpleApplication { + + Joint j1; + Joint j2; + AnimComposer composer; + Armature armature; + File file; + + public static void main(String... argv) { + TestBaseAnimSerialization app = new TestBaseAnimSerialization(); + app.start(); + } + + @Override + public void simpleInitApp() { + setTimer(new EraseTimer()); + renderManager.setSinglePassLightBatchSize(2); + //cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f); + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + + //create armature + Joint root = new Joint("Root_Joint"); + j1 = new Joint("Joint_1"); + j2 = new Joint("Joint_2"); + Joint j3 = new Joint("Joint_3"); + root.addChild(j1); + j1.addChild(j2); + j2.addChild(j3); + root.setLocalTranslation(new Vector3f(0, 0, 0.5f)); + j1.setLocalTranslation(new Vector3f(0, 0.0f, -0.5f)); + j2.setLocalTranslation(new Vector3f(0, 0.0f, -0.3f)); + j3.setLocalTranslation(new Vector3f(0, 0, -0.2f)); + Joint[] joints = new Joint[]{root, j1, j2, j3}; + + armature = new Armature(joints); + //armature.setModelTransformClass(SeparateJointModelTransform.class); + armature.saveBindPose(); + + //create animations + AnimClip clip = new AnimClip("anim"); + float[] times = new float[]{0, 2, 4}; + Quaternion[] rotations = new Quaternion[]{ + new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X), + new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X), + new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X) + }; + Vector3f[] translations = new Vector3f[]{ + new Vector3f(0, 0.2f, 0), + new Vector3f(0, 1.0f, 0), + new Vector3f(0, 0.2f, 0), + }; + Vector3f[] scales = new Vector3f[]{ + new Vector3f(1, 1, 1), + new Vector3f(1, 1, 2), + new Vector3f(1, 1, 1), + }; + Vector3f[] scales2 = new Vector3f[]{ + new Vector3f(1, 1, 1), + new Vector3f(1, 1, 0.5f), + new Vector3f(1, 1, 1), + }; + + TransformTrack track1 = new TransformTrack(j1, times, null, rotations, scales); + TransformTrack track2 = new TransformTrack(j2, times, null, rotations, null); + + clip.setTracks(new TransformTrack[]{track1, track2}); + + //create the animComposer control + composer = new AnimComposer(); + composer.addAnimClip(clip); + + //create the SkinningControl + SkinningControl ac = new SkinningControl(armature); + Node node = new Node("Test Armature"); + + //Create the mesh to deform. + Geometry cylinder = new Geometry("cylinder", createMesh()); + Material m = new Material(assetManager, "Common/MatDefs/Misc/fakeLighting.j3md"); + m.setColor("Color", ColorRGBA.randomColor()); + cylinder.setMaterial(m); + node.attachChild(cylinder); + node.addControl(composer); + node.addControl(ac); + + File storageFolder = JmeSystem.getStorageFolder(); + file = new File(storageFolder.getPath() + File.separator + "test.j3o"); + BinaryExporter be = new BinaryExporter(); + try { + be.save(node, file); + } catch (IOException e) { + e.printStackTrace(); + } + + assetManager.registerLocator(storageFolder.getPath(), FileLocator.class); + Node newNode = (Node) assetManager.loadModel("test.j3o"); + + rootNode.attachChild(newNode); + + composer = newNode.getControl(AnimComposer.class); + ac = newNode.getControl(SkinningControl.class); + ac.setHardwareSkinningPreferred(false); + armature = ac.getArmature(); + composer.setCurrentAction("anim"); + + ArmatureDebugAppState debugAppState = new ArmatureDebugAppState(); + debugAppState.addArmatureFrom(ac); + stateManager.attach(debugAppState); + + flyCam.setEnabled(false); + + ChaseCameraAppState chaseCam = new ChaseCameraAppState(); + chaseCam.setTarget(node); + getStateManager().attach(chaseCam); + chaseCam.setInvertHorizontalAxis(true); + chaseCam.setInvertVerticalAxis(true); + chaseCam.setZoomSpeed(0.5f); + chaseCam.setMinVerticalRotation(-FastMath.HALF_PI); + chaseCam.setRotationSpeed(3); + chaseCam.setDefaultDistance(3); + chaseCam.setMinDistance(0.01f); + chaseCam.setZoomSpeed(0.01f); + chaseCam.setDefaultVerticalRotation(0.3f); + + + inputManager.addMapping("bind", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + composer.reset(); + armature.applyBindPose(); + + } else { + composer.setCurrentAction("anim"); + } + } + }, "bind"); + } + + private Mesh createMesh() { + Cylinder c = new Cylinder(30, 16, 0.1f, 1, true); + + ShortBuffer jointIndex = (ShortBuffer) VertexBuffer.createBuffer(VertexBuffer.Format.UnsignedShort, 4, c.getVertexCount()); + jointIndex.rewind(); + c.setMaxNumWeights(1); + FloatBuffer jointWeight = (FloatBuffer) VertexBuffer.createBuffer(VertexBuffer.Format.Float, 4, c.getVertexCount()); + jointWeight.rewind(); + VertexBuffer vb = c.getBuffer(VertexBuffer.Type.Position); + FloatBuffer fvb = (FloatBuffer) vb.getData(); + fvb.rewind(); + for (int i = 0; i < c.getVertexCount(); i++) { + fvb.get(); + fvb.get(); + float z = fvb.get(); + int index = 0; + if (z > 0) { + index = 0; + } else if (z > -0.2) { + index = 1; + } else { + index = 2; + } + jointIndex.put((short) index).put((short) 0).put((short) 0).put((short) 0); + jointWeight.put(1f).put(0f).put(0f).put(0f); + + } + c.setBuffer(VertexBuffer.Type.BoneIndex, 4, jointIndex); + c.setBuffer(VertexBuffer.Type.BoneWeight, 4, jointWeight); + + c.updateCounts(); + c.updateBound(); + + VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight); + VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex); + + indicesHW.setUsage(VertexBuffer.Usage.CpuOnly); + weightsHW.setUsage(VertexBuffer.Usage.CpuOnly); + c.setBuffer(weightsHW); + c.setBuffer(indicesHW); + c.generateBindPose(); + + c.prepareForAnim(false); + + return c; + } + + @Override + public void destroy() { + super.destroy(); + file.delete(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java b/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java index 9a2e78804..475075267 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java @@ -31,28 +31,30 @@ */ package jme3test.model.anim; -import com.jme3.animation.*; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; +import com.jme3.app.DetailedProfilerState; import com.jme3.app.SimpleApplication; import com.jme3.font.BitmapText; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; +import com.jme3.math.*; +import com.jme3.scene.Node; import com.jme3.scene.Spatial; + import java.util.ArrayList; import java.util.List; public class TestHWSkinning extends SimpleApplication implements ActionListener{ - private AnimChannel channel; - private AnimControl control; + + // private AnimComposer composer; private String[] animNames = {"Dodge", "Walk", "pull", "push"}; - private final static int SIZE = 10; + private final static int SIZE = 40; private boolean hwSkinningEnable = true; - private List skControls = new ArrayList(); + private List skControls = new ArrayList<>(); private BitmapText hwsText; public static void main(String[] args) { @@ -63,8 +65,11 @@ public class TestHWSkinning extends SimpleApplication implements ActionListener{ @Override public void simpleInitApp() { flyCam.setMoveSpeed(10f); - cam.setLocation(new Vector3f(3.8664846f, 6.2704787f, 9.664585f)); - cam.setRotation(new Quaternion(-0.054774776f, 0.94064945f, -0.27974048f, -0.18418397f)); + flyCam.setDragToRotate(true); + setPauseOnLostFocus(false); + cam.setLocation(new Vector3f(38.76639f, 14.744472f, 45.097454f)); + cam.setRotation(new Quaternion(-0.06086266f, 0.92303723f, -0.1639443f, -0.34266636f)); + makeHudText(); DirectionalLight dl = new DirectionalLight(); @@ -72,31 +77,45 @@ public class TestHWSkinning extends SimpleApplication implements ActionListener{ dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); rootNode.addLight(dl); + Spatial models[] = new Spatial[4]; + for (int i = 0; i < 4; i++) { + models[i] =loadModel(i); + } + for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { - Spatial model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); - model.setLocalScale(0.1f); - model.setLocalTranslation(i - SIZE / 2, 0, j - SIZE / 2); - control = model.getControl(AnimControl.class); - - channel = control.createChannel(); - channel.setAnim(animNames[(i + j) % 4]); - SkeletonControl skeletonControl = model.getControl(SkeletonControl.class); - skeletonControl.setHardwareSkinningPreferred(hwSkinningEnable); - skControls.add(skeletonControl); - rootNode.attachChild(model); + Node model = (Node)models[(i + j) % 4]; + Spatial s = model.getChild(0).clone(); + model.attachChild(s); + float x = (float)(i - SIZE / 2) / 0.1f; + float z = (float)(j - SIZE / 2) / 0.1f; + s.setLocalTranslation(x, 0, z); } } inputManager.addListener(this, "toggleHWS"); inputManager.addMapping("toggleHWS", new KeyTrigger(KeyInput.KEY_SPACE)); + + } + + private Spatial loadModel(int i) { + Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + model.setLocalScale(0.1f); + AnimComposer composer = model.getControl(AnimComposer.class); + + composer.setCurrentAction(animNames[i]); + SkinningControl skinningControl = model.getControl(SkinningControl.class); + skinningControl.setHardwareSkinningPreferred(hwSkinningEnable); + skControls.add(skinningControl); + rootNode.attachChild(model); + return model; } @Override public void onAction(String name, boolean isPressed, float tpf) { if(isPressed && name.equals("toggleHWS")){ hwSkinningEnable = !hwSkinningEnable; - for (SkeletonControl control : skControls) { + for (SkinningControl control : skControls) { control.setHardwareSkinningPreferred(hwSkinningEnable); hwsText.setText("HWS : "+ hwSkinningEnable); } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinningOld.java b/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinningOld.java new file mode 100644 index 000000000..f0fa27d8f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinningOld.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.model.anim; + +import com.jme3.animation.*; +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.math.*; +import com.jme3.scene.Spatial; + +import java.util.ArrayList; +import java.util.List; + +public class TestHWSkinningOld extends SimpleApplication implements ActionListener { + + private AnimChannel channel; + private AnimControl control; + private String[] animNames = {"Dodge", "Walk", "pull", "push"}; + private final static int SIZE = 50; + private boolean hwSkinningEnable = true; + private List skControls = new ArrayList(); + private BitmapText hwsText; + + public static void main(String[] args) { + TestHWSkinningOld app = new TestHWSkinningOld(); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(10f); + flyCam.setDragToRotate(true); + setPauseOnLostFocus(false); + cam.setLocation(new Vector3f(24.746134f, 13.081396f, 32.72753f)); + cam.setRotation(new Quaternion(-0.06867662f, 0.92435044f, -0.19981281f, -0.31770203f)); + makeHudText(); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); + rootNode.addLight(dl); + + for (int i = 0; i < SIZE; i++) { + for (int j = 0; j < SIZE; j++) { + Spatial model = (Spatial) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); + model.setLocalScale(0.1f); + model.setLocalTranslation(i - SIZE / 2, 0, j - SIZE / 2); + control = model.getControl(AnimControl.class); + + channel = control.createChannel(); + channel.setAnim(animNames[(i + j) % 4]); + SkeletonControl skeletonControl = model.getControl(SkeletonControl.class); + skeletonControl.setHardwareSkinningPreferred(hwSkinningEnable); + skControls.add(skeletonControl); + rootNode.attachChild(model); + } + } + + inputManager.addListener(this, "toggleHWS"); + inputManager.addMapping("toggleHWS", new KeyTrigger(KeyInput.KEY_SPACE)); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed && name.equals("toggleHWS")) { + hwSkinningEnable = !hwSkinningEnable; + for (SkeletonControl control : skControls) { + control.setHardwareSkinningPreferred(hwSkinningEnable); + hwsText.setText("HWS : " + hwSkinningEnable); + } + } + } + + private void makeHudText() { + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + hwsText = new BitmapText(guiFont, false); + hwsText.setSize(guiFont.getCharSet().getRenderedSize()); + hwsText.setText("HWS : " + hwSkinningEnable); + hwsText.setLocalTranslation(0, cam.getHeight(), 0); + guiNode.attachChild(hwsText); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java b/jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java index de162907d..63b5023d6 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestModelExportingCloning.java @@ -31,8 +31,7 @@ */ package jme3test.model.anim; -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; +import com.jme3.anim.AnimComposer; import com.jme3.app.SimpleApplication; import com.jme3.export.binary.BinaryExporter; import com.jme3.light.DirectionalLight; @@ -57,27 +56,23 @@ public class TestModelExportingCloning extends SimpleApplication { dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); rootNode.addLight(dl); - AnimControl control; - AnimChannel channel; - + AnimComposer composer; + Spatial originalModel = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); - control = originalModel.getControl(AnimControl.class); - channel = control.createChannel(); - channel.setAnim("Walk"); + composer = originalModel.getControl(AnimComposer.class); + composer.setCurrentAction("Walk"); rootNode.attachChild(originalModel); Spatial clonedModel = originalModel.clone(); clonedModel.move(10, 0, 0); - control = clonedModel.getControl(AnimControl.class); - channel = control.createChannel(); - channel.setAnim("push"); + composer = clonedModel.getControl(AnimComposer.class); + composer.setCurrentAction("push"); rootNode.attachChild(clonedModel); Spatial exportedModel = BinaryExporter.saveAndLoad(assetManager, originalModel); exportedModel.move(20, 0, 0); - control = exportedModel.getControl(AnimControl.class); - channel = control.createChannel(); - channel.setAnim("pull"); + composer = exportedModel.getControl(AnimComposer.class); + composer.setCurrentAction("pull"); rootNode.attachChild(exportedModel); } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java b/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java new file mode 100644 index 000000000..629046685 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java @@ -0,0 +1,122 @@ +package jme3test.model.anim; + +import com.jme3.anim.MorphControl; +import com.jme3.app.ChaseCameraAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.mesh.MorphTarget; +import com.jme3.scene.shape.Box; +import com.jme3.shader.VarType; +import com.jme3.util.BufferUtils; + +import java.nio.FloatBuffer; + +public class TestMorph extends SimpleApplication { + + float[] weights = new float[2]; + + public static void main(String... args) { + TestMorph app = new TestMorph(); + app.start(); + } + + @Override + public void simpleInitApp() { + final Box box = new Box(1, 1, 1); + FloatBuffer buffer = BufferUtils.createVector3Buffer(box.getVertexCount()); + + float[] d = new float[box.getVertexCount() * 3]; + for (int i = 0; i < d.length; i++) { + d[i] = 0; + } + + d[12] = 1f; + d[15] = 1f; + d[18] = 1f; + d[21] = 1f; + + buffer.put(d); + buffer.rewind(); + + MorphTarget target = new MorphTarget(); + target.setBuffer(VertexBuffer.Type.Position, buffer); + box.addMorphTarget(target); + + + buffer = BufferUtils.createVector3Buffer(box.getVertexCount()); + + for (int i = 0; i < d.length; i++) { + d[i] = 0; + } + + d[13] = 1f; + d[16] = 1f; + d[19] = 1f; + d[22] = 1f; + + buffer.put(d); + buffer.rewind(); + + final MorphTarget target2 = new MorphTarget(); + target2.setBuffer(VertexBuffer.Type.Position, buffer); + box.addMorphTarget(target2); + + final Geometry g = new Geometry("box", box); + Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + g.setMaterial(m); + m.setColor("Color", ColorRGBA.Red); + m.setInt("NumberOfMorphTargets", 2); + + rootNode.attachChild(g); + + g.setMorphState(weights); + g.addControl(new MorphControl()); + + ChaseCameraAppState chase = new ChaseCameraAppState(); + chase.setTarget(rootNode); + getStateManager().attach(chase); + flyCam.setEnabled(false); + + inputManager.addMapping("morphright", new KeyTrigger(KeyInput.KEY_I)); + inputManager.addMapping("morphleft", new KeyTrigger(KeyInput.KEY_Y)); + inputManager.addMapping("morphup", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("morphdown", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addMapping("change", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(new AnalogListener() { + @Override + public void onAnalog(String name, float value, float tpf) { + if (name.equals("morphleft")) { + weights[0] -= tpf; + } + if (name.equals("morphright")) { + weights[0] += tpf; + } + if (name.equals("morphup")) { + weights[1] += tpf; + } + if (name.equals("morphdown")) { + weights[1] -= tpf; + } + g.setMorphState(weights); + + } + }, "morphup", "morphdown", "morphleft", "morphright"); + + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("change") && isPressed) { + box.setBuffer(VertexBuffer.Type.MorphTarget0, 3, target2.getBuffer(VertexBuffer.Type.Position)); + } + } + }, "change"); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java index b6bd4cd63..a7616f7b5 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java @@ -38,15 +38,12 @@ import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; +import com.jme3.math.*; +import com.jme3.scene.*; import com.jme3.scene.shape.Box; -public class TestOgreAnim extends SimpleApplication +//TODO rework this Test when the new animation system is done. +public class TestOgreAnim extends SimpleApplication implements AnimEventListener, ActionListener { private AnimChannel channel; @@ -69,7 +66,7 @@ public class TestOgreAnim extends SimpleApplication dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); rootNode.addLight(dl); - Spatial model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + Spatial model = (Spatial) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); model.center(); control = model.getControl(AnimControl.class); @@ -102,7 +99,7 @@ public class TestOgreAnim extends SimpleApplication // geom.getMesh().createCollisionData(); } - + public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { if (animName.equals("Dodge")){ diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java index fa1283540..e83fa7a6b 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java @@ -32,20 +32,15 @@ package jme3test.model.anim; -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.animation.Bone; -import com.jme3.animation.LoopMode; +import com.jme3.animation.*; import com.jme3.app.SimpleApplication; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; +import com.jme3.math.*; import com.jme3.scene.Node; import com.jme3.scene.debug.SkeletonDebugger; +//TODO rework this Test when the new animation system is done. public class TestOgreComplexAnim extends SimpleApplication { private AnimControl control; @@ -69,7 +64,7 @@ public class TestOgreComplexAnim extends SimpleApplication { dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); rootNode.addLight(dl); - Node model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + Node model = (Node) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); control = model.getControl(AnimControl.class); @@ -118,8 +113,8 @@ public class TestOgreComplexAnim extends SimpleApplication { public void simpleUpdate(float tpf){ Bone b = control.getSkeleton().getBone("spinehigh"); Bone b2 = control.getSkeleton().getBone("uparm.left"); - - angle += tpf * rate; + + angle += tpf * rate; if (angle > FastMath.HALF_PI / 2f){ angle = FastMath.HALF_PI / 2f; rate = -1; @@ -133,11 +128,11 @@ public class TestOgreComplexAnim extends SimpleApplication { b.setUserControl(true); b.setUserTransforms(Vector3f.ZERO, q, Vector3f.UNIT_XYZ); - + b2.setUserControl(true); b2.setUserTransforms(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(1+angle,1+ angle, 1+angle)); - - + + } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java b/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java index d594f197f..bbc727af3 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java @@ -37,7 +37,6 @@ package jme3test.model.anim; */ - import com.jme3.animation.*; import com.jme3.app.SimpleApplication; import com.jme3.asset.TextureKey; @@ -47,11 +46,7 @@ import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; +import com.jme3.math.*; import com.jme3.post.FilterPostProcessor; import com.jme3.post.ssao.SSAOFilter; import com.jme3.renderer.queue.RenderQueue; @@ -59,11 +54,11 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; import com.jme3.scene.shape.Quad; import com.jme3.shadow.DirectionalLightShadowFilter; -import com.jme3.shadow.DirectionalLightShadowRenderer; + import java.util.ArrayList; import java.util.List; -import jme3test.post.SSAOUI; - + +//TODO rework this Test when the new animation system is done. public class TestSkeletonControlRefresh extends SimpleApplication implements ActionListener{ private AnimChannel channel; @@ -97,7 +92,7 @@ public class TestSkeletonControlRefresh extends SimpleApplication implements Act for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { - Spatial model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + Spatial model = (Spatial) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); //setting a different material model.setMaterial(m.clone()); model.setLocalScale(0.1f); diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestBlendEquations.java b/jme3-examples/src/main/java/jme3test/renderer/TestBlendEquations.java index 9335912b6..435d57db3 100644 --- a/jme3-examples/src/main/java/jme3test/renderer/TestBlendEquations.java +++ b/jme3-examples/src/main/java/jme3test/renderer/TestBlendEquations.java @@ -36,75 +36,106 @@ import com.jme3.light.DirectionalLight; import com.jme3.material.Material; import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; import com.jme3.math.Vector3f; import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; import com.jme3.scene.shape.Quad; +/** + * This test demonstrates the usage of customized blend equations and factors on a material.
+ * Customized blend equations and factors always requires {@link RenderState.BlendMode#Custom}. + * + * @author the_Minka + */ public class TestBlendEquations extends SimpleApplication { + private Geometry leftQuad; + private Geometry rightQuad; + + private float timer; + public static void main(String[] args) { TestBlendEquations app = new TestBlendEquations(); app.start(); } public void simpleInitApp() { - Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); - teaGeom.scale(6); - teaGeom.getMaterial().getAdditionalRenderState().setBlendEquation(RenderState.BlendEquation.Add); - teaGeom.move(0, -2f, 0); - - DirectionalLight dl = new DirectionalLight(); - dl.setColor(ColorRGBA.Red); - dl.setDirection(Vector3f.UNIT_XYZ.negate()); - - rootNode.addLight(dl); - rootNode.attachChild(teaGeom); - - Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mat.setColor("Color", new ColorRGBA(0.5f, 0f, 1f, 0.3f)); - mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Color); - mat.getAdditionalRenderState().setBlendEquation(RenderState.BlendEquation.Subtract); - - Geometry geo = new Geometry("BottomLeft", new Quad(guiViewPort.getCamera().getWidth() / 2, guiViewPort.getCamera().getHeight() / 2)); - geo.setMaterial(mat); - geo.setQueueBucket(RenderQueue.Bucket.Gui); - geo.setLocalTranslation(0, 0, 1); - - guiNode.attachChild(geo); - - Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - m.getAdditionalRenderState().setBlendEquation(RenderState.BlendEquation.ReverseSubtract); - m.setColor("Color", new ColorRGBA(0.0f, 1f, 1.f, 1f)); - m.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.AlphaAdditive); - - geo = new Geometry("BottomRight", new Quad(guiViewPort.getCamera().getWidth() / 2, guiViewPort.getCamera().getHeight() / 2)); - geo.setMaterial(m); - geo.setQueueBucket(RenderQueue.Bucket.Gui); - geo.setLocalTranslation(guiViewPort.getCamera().getWidth() / 2, 0, 1); - - guiNode.attachChild(geo); - - m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - m.getAdditionalRenderState().setBlendEquation(RenderState.BlendEquation.Min); - m.setColor("Color", new ColorRGBA(0.3f, 0f, 0.1f, 0.3f)); - m.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Additive); - - geo = new Geometry("TopRight", new Quad(guiViewPort.getCamera().getWidth() / 2, guiViewPort.getCamera().getHeight() / 2)); - geo.setMaterial(m); - geo.setQueueBucket(RenderQueue.Bucket.Gui); - geo.setLocalTranslation(guiViewPort.getCamera().getWidth() / 2, guiViewPort.getCamera().getHeight() / 2, 1); - - guiNode.attachChild(geo); - - geo = new Geometry("OverTeaPot", new Quad(guiViewPort.getCamera().getWidth() / 2, guiViewPort.getCamera().getHeight() / 2)); - geo.setMaterial(mat); - geo.setQueueBucket(RenderQueue.Bucket.Transparent); - geo.setLocalTranslation(0, -100, 5); - - rootNode.attachChild(geo); + cam.setLocation(new Vector3f(0f, 0.5f, 3f)); + viewPort.setBackgroundColor(ColorRGBA.LightGray); + + // Add a light source to the scene. + DirectionalLight directionalLight = new DirectionalLight(); + directionalLight.setColor(ColorRGBA.Magenta); + directionalLight.setDirection(Vector3f.UNIT_XYZ.negate()); + rootNode.addLight(directionalLight); + + + // Create and add a teapot to the scene graph. + Spatial teapotModel = assetManager.loadModel("Models/Teapot/Teapot.obj"); + rootNode.attachChild(teapotModel); + // Create the two moving quads with custom blend modes. + createLeftQuad(); + createRightQuad(); } + /** + * Adds a "transparent" quad to the scene, that shows an inverse blue value sight of the scene behind. + */ + private void createLeftQuad() { + Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + + // This color creates a blue value image. The effect will have a strength of 80% (set by the alpha value). + material.setColor("Color", new ColorRGBA(0f, 0f, 1f, 0.8f)); + + // Result.RGB = Source.A * Source.RGB - Source.A * Destination.RGB + // Result.A = Destination.A + material.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Custom); + material.getAdditionalRenderState().setBlendEquation(RenderState.BlendEquation.Subtract); + material.getAdditionalRenderState().setBlendEquationAlpha(RenderState.BlendEquationAlpha.Add); + material.getAdditionalRenderState().setCustomBlendFactors( + RenderState.BlendFunc.Src_Alpha, RenderState.BlendFunc.Src_Alpha, + RenderState.BlendFunc.Zero, RenderState.BlendFunc.One); + + leftQuad = new Geometry("LeftQuad", new Quad(1f, 1f)); + leftQuad.setMaterial(material); + leftQuad.setQueueBucket(RenderQueue.Bucket.Transparent); + rootNode.attachChild(leftQuad); + } + + /** + * Adds a "transparent" quad to the scene, that limits the color values of the scene behind the object.
+ * This effect can be good seen on bright areas of the scene (e.g. areas with specular lighting effects). + */ + private void createRightQuad() { + Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + material.setColor("Color", new ColorRGBA(0.4f, 0.4f, 0.4f, 1f)); + + // Min( Source , Destination) + material.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Custom); + material.getAdditionalRenderState().setBlendEquation(RenderState.BlendEquation.Min); + material.getAdditionalRenderState().setBlendEquationAlpha(RenderState.BlendEquationAlpha.Min); + + // In OpenGL no blend factors are used, when using the blend equations Min or Max! + //material.getAdditionalRenderState().setCustomBlendFactors( + // RenderState.BlendFunc.One, RenderState.BlendFunc.One, + // RenderState.BlendFunc.One, RenderState.BlendFunc.One); + + rightQuad = new Geometry("RightQuad", new Quad(1f, 1f)); + rightQuad.setMaterial(material); + rightQuad.setQueueBucket(RenderQueue.Bucket.Transparent); + rootNode.attachChild(rightQuad); + } + + @Override + public void simpleUpdate(float tpf) { + timer += tpf; + + float xOffset = FastMath.sin(timer * 0.5f) * 2f; + leftQuad.setLocalTranslation(xOffset - 2f, 0f, 0.5f); + rightQuad.setLocalTranslation(xOffset + 1f, 0f, 0.5f); + } } diff --git a/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java b/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java index cf8dbb64b..5400e98d8 100644 --- a/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java +++ b/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java @@ -31,9 +31,8 @@ */ package jme3test.stress; +import com.jme3.anim.SkinningControl; import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.animation.SkeletonControl; import com.jme3.app.SimpleApplication; import com.jme3.bounding.BoundingBox; import com.jme3.font.BitmapText; @@ -43,18 +42,14 @@ import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.AmbientLight; import com.jme3.light.DirectionalLight; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.VertexBuffer; +import com.jme3.math.*; +import com.jme3.scene.*; +import jme3tools.optimize.LodGenerator; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ScheduledThreadPoolExecutor; -import jme3tools.optimize.LodGenerator; public class TestLodGeneration extends SimpleApplication { @@ -101,7 +96,7 @@ public class TestLodGeneration extends SimpleApplication { // ch = model.getControl(AnimControl.class).createChannel(); // ch.setAnim("Wave"); - SkeletonControl c = model.getControl(SkeletonControl.class); + SkinningControl c = model.getControl(SkinningControl.class); if (c != null) { c.setEnabled(false); } diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java index e6435e5c5..545bd9c64 100644 --- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java +++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java @@ -4,12 +4,11 @@ import com.jme3.renderer.RendererException; import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GL2; import com.jme3.renderer.opengl.GL3; - -import java.nio.*; - import com.jme3.renderer.opengl.GL4; import com.jogamp.opengl.GLContext; +import java.nio.*; + public class JoglGL implements GL, GL2, GL3, GL4 { private static int getLimitBytes(ByteBuffer buffer) { @@ -628,10 +627,36 @@ public class JoglGL implements GL, GL2, GL3, GL4 { public void glPatchParameter(int count) { GLContext.getCurrentGL().getGL3().glPatchParameteri(com.jogamp.opengl.GL3.GL_PATCH_VERTICES, count); } - + @Override public void glDeleteVertexArrays(IntBuffer arrays) { checkLimit(arrays); GLContext.getCurrentGL().getGL2ES3().glDeleteVertexArrays(arrays.limit(), arrays); } + + @Override + public int glGetUniformBlockIndex(final int program, final String uniformBlockName) { + return GLContext.getCurrentGL().getGL3bc().glGetUniformBlockIndex(program, uniformBlockName); + } + + @Override + public void glBindBufferBase(final int target, final int index, final int buffer) { + GLContext.getCurrentGL().getGL3bc().glBindBufferBase(target, index, buffer); + } + + @Override + public int glGetProgramResourceIndex(final int program, final int programInterface, final String name) { + throw new UnsupportedOperationException(); + //return GLContext.getCurrentGL().getGL4bc().glGetProgramResourceIndex(program, programInterface, name); + } + + @Override + public void glShaderStorageBlockBinding(final int program, final int storageBlockIndex, final int storageBlockBinding) { + GLContext.getCurrentGL().getGL4bc().glShaderStorageBlockBinding(program, storageBlockIndex, storageBlockBinding); + } + + @Override + public void glUniformBlockBinding(final int program, final int uniformBlockIndex, final int uniformBlockBinding) { + GLContext.getCurrentGL().getGL3bc().glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding); + } } diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index 5a74e06f0..bca6d0dbb 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java @@ -4,16 +4,12 @@ import com.jme3.renderer.RendererException; import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GL2; import com.jme3.renderer.opengl.GL3; -import java.nio.Buffer; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.nio.ShortBuffer; - import com.jme3.renderer.opengl.GL4; import com.jme3.util.BufferUtils; import org.lwjgl.opengl.*; +import java.nio.*; + public final class LwjglGL implements GL, GL2, GL3, GL4 { IntBuffer tmpBuff = BufferUtils.createIntBuffer(1); @@ -487,10 +483,35 @@ public final class LwjglGL implements GL, GL2, GL3, GL4 { public void glPatchParameter(int count) { GL40.glPatchParameteri(GL40.GL_PATCH_VERTICES,count); } - + + @Override + public int glGetProgramResourceIndex(final int program, final int programInterface, final String name) { + return GL43.glGetProgramResourceIndex(program, programInterface, name); + } + + @Override + public void glShaderStorageBlockBinding(final int program, final int storageBlockIndex, final int storageBlockBinding) { + GL43.glShaderStorageBlockBinding(program, storageBlockIndex, storageBlockBinding); + } + @Override public void glDeleteVertexArrays(IntBuffer arrays) { checkLimit(arrays); ARBVertexArrayObject.glDeleteVertexArrays(arrays); } + + @Override + public int glGetUniformBlockIndex(final int program, final String uniformBlockName) { + return GL31.glGetUniformBlockIndex(program, uniformBlockName); + } + + @Override + public void glBindBufferBase(final int target, final int index, final int buffer) { + GL30.glBindBufferBase(target, index, buffer); + } + + @Override + public void glUniformBlockBinding(final int program, final int uniformBlockIndex, final int uniformBlockBinding) { + GL31.glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding); + } } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index e87bdf20a..0d8c32a8d 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java @@ -616,9 +616,34 @@ public class LwjglGL extends LwjglRender implements GL, GL2, GL3, GL4 { GL40.glPatchParameteri(GL40.GL_PATCH_VERTICES, count); } + @Override + public int glGetProgramResourceIndex(final int program, final int programInterface, final String name) { + return GL43.glGetProgramResourceIndex(program, programInterface, name); + } + + @Override + public void glShaderStorageBlockBinding(final int program, final int storageBlockIndex, final int storageBlockBinding) { + GL43.glShaderStorageBlockBinding(program, storageBlockIndex, storageBlockBinding); + } + @Override public void glDeleteVertexArrays(final IntBuffer arrays) { checkLimit(arrays); ARBVertexArrayObject.glDeleteVertexArrays(arrays); } + + @Override + public int glGetUniformBlockIndex(final int program, final String uniformBlockName) { + return GL31.glGetUniformBlockIndex(program, uniformBlockName); + } + + @Override + public void glBindBufferBase(final int target, final int index, final int buffer) { + GL30.glBindBufferBase(target, index, buffer); + } + + @Override + public void glUniformBlockBinding(final int program, final int uniformBlockIndex, final int uniformBlockBinding) { + GL31.glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding); + } } 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 0b1350845..260e018e6 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 @@ -2,7 +2,7 @@ package com.jme3.scene.plugins.gltf; import com.google.gson.*; import com.google.gson.stream.JsonReader; -import com.jme3.animation.*; +import com.jme3.anim.*; import com.jme3.asset.*; import com.jme3.material.Material; import com.jme3.material.RenderState; @@ -11,6 +11,7 @@ import com.jme3.renderer.Camera; import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.*; import com.jme3.scene.control.CameraControl; +import com.jme3.scene.mesh.MorphTarget; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; import com.jme3.util.IntMap; @@ -19,6 +20,8 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator; import javax.xml.bind.DatatypeConverter; import java.io.*; import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.rmi.ServerError; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -128,8 +131,6 @@ public class GltfLoader implements AssetLoader { rootNode = customContentManager.readExtensionAndExtras("root", docRoot, rootNode); - setupControls(); - //Loading animations if (animations != null) { for (int i = 0; i < animations.size(); i++) { @@ -137,6 +138,8 @@ public class GltfLoader implements AssetLoader { } } + setupControls(); + //only one scene let's not return the root. if (rootNode.getChildren().size() == 1) { rootNode = (Node) rootNode.getChild(0); @@ -196,7 +199,7 @@ public class GltfLoader implements AssetLoader { public Object readNode(int nodeIndex) throws IOException { Object obj = fetchFromCache("nodes", nodeIndex, Object.class); if (obj != null) { - if (obj instanceof BoneWrapper) { + if (obj instanceof JointWrapper) { //the node can be a previously loaded bone let's return it return obj; } else { @@ -245,9 +248,11 @@ public class GltfLoader implements AssetLoader { Integer skinIndex = getAsInteger(nodeData, "skin"); if (skinIndex != null) { SkinData skinData = fetchFromCache("skins", skinIndex, SkinData.class); - List spatials = skinnedSpatials.get(skinData); - spatials.add(spatial); - skinData.used = true; + if (skinData != null) { + List spatials = skinnedSpatials.get(skinData); + spatials.add(spatial); + skinData.used = true; + } } spatial.setLocalTransform(readTransforms(nodeData)); @@ -274,9 +279,9 @@ public class GltfLoader implements AssetLoader { readChild(spatial, child); } } - } else if (loaded instanceof BoneWrapper) { + } else if (loaded instanceof JointWrapper) { //parent is the Armature Node, we have to apply its transforms to the root bone's animation data - BoneWrapper bw = (BoneWrapper) loaded; + JointWrapper bw = (JointWrapper) loaded; bw.isRoot = true; SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class); if (skinData == null) { @@ -387,7 +392,7 @@ public class GltfLoader implements AssetLoader { //the buffers will be setup if ever used. VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight); VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex); - //setting usage to cpuOnly so that the buffer is not send empty to the GPU + //setting usage to cpuOnly so that the buffer is not sent empty to the GPU indicesHW.setUsage(VertexBuffer.Usage.CpuOnly); weightsHW.setUsage(VertexBuffer.Usage.CpuOnly); mesh.setBuffer(weightsHW); @@ -395,6 +400,22 @@ public class GltfLoader implements AssetLoader { mesh.generateBindPose(); } + JsonArray targets = meshObject.getAsJsonArray("targets"); + if(targets != null){ + for (JsonElement target : targets) { + MorphTarget morphTarget = new MorphTarget(); + for (Map.Entry entry : target.getAsJsonObject().entrySet()) { + String bufferType = entry.getKey(); + VertexBuffer.Type type = getVertexBufferType(bufferType); + VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(type)); + if (vb != null) { + morphTarget.setBuffer(type, (FloatBuffer)vb.getData()); + } + } + mesh.addMorphTarget(morphTarget); + } + } + mesh = customContentManager.readExtensionAndExtras("primitive", meshObject, mesh); Geometry geom = new Geometry(null, mesh); @@ -423,7 +444,6 @@ public class GltfLoader implements AssetLoader { geomArray[index] = geom; index++; - //TODO targets(morph anim...) } geomArray = customContentManager.readExtensionAndExtras("mesh", meshData, geomArray); @@ -719,6 +739,7 @@ public class GltfLoader implements AssetLoader { //temp data storage of track data TrackData[] tracks = new TrackData[nodes.size()]; + boolean hasMorphTrack = false; for (JsonElement channel : channels) { @@ -731,12 +752,12 @@ public class GltfLoader implements AssetLoader { continue; } assertNotNull(targetPath, "No target path for channel"); - - if (targetPath.equals("weight")) { - //Morph animation, not implemented in JME, let's warn the user and skip the channel - logger.log(Level.WARNING, "Morph animation is not supported by JME yet, skipping animation"); - continue; - } +// +// if (targetPath.equals("weights")) { +// //Morph animation, not implemented in JME, let's warn the user and skip the channel +// logger.log(Level.WARNING, "Morph animation is not supported by JME yet, skipping animation track"); +// continue; +// } TrackData trackData = tracks[targetNode]; if (trackData == null) { @@ -780,9 +801,10 @@ public class GltfLoader implements AssetLoader { Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator); trackData.rotations = rotations; } else { - //TODO support weights - logger.log(Level.WARNING, "Morph animation is not supported"); - continue; + trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Morph)); + float[] weights = readAccessorData(dataIndex, floatArrayPopulator); + trackData.weights = weights; + hasMorphTrack = true; } tracks[targetNode] = customContentManager.readExtensionAndExtras("channel", channel, trackData); } @@ -792,46 +814,53 @@ public class GltfLoader implements AssetLoader { } List spatials = new ArrayList<>(); - Animation anim = new Animation(); - anim.setName(name); + AnimClip anim = new AnimClip(name); + List aTracks = new ArrayList<>(); int skinIndex = -1; - List usedBones = new ArrayList<>(); + List usedJoints = new ArrayList<>(); for (int i = 0; i < tracks.length; i++) { TrackData trackData = tracks[i]; if (trackData == null || trackData.timeArrays.isEmpty()) { continue; } trackData.update(); - if (trackData.length > anim.getLength()) { - anim.setLength(trackData.length); - } Object node = fetchFromCache("nodes", i, Object.class); if (node instanceof Spatial) { Spatial s = (Spatial) node; spatials.add(s); - SpatialTrack track = new SpatialTrack(trackData.times, trackData.translations, trackData.rotations, trackData.scales); - track.setTrackSpatial(s); - anim.addTrack(track); - } else if (node instanceof BoneWrapper) { - BoneWrapper b = (BoneWrapper) node; - //apply the inverseBindMatrix to animation data. - b.update(trackData); - usedBones.add(b.bone); + if (trackData.rotations != null || trackData.translations != null || trackData.scales != null) { + TransformTrack track = new TransformTrack(s, trackData.times, trackData.translations, trackData.rotations, trackData.scales); + aTracks.add(track); + } + if( trackData.weights != null && s instanceof Geometry){ + Geometry g = (Geometry)s; + int nbMorph = g.getMesh().getMorphTargets().length; +// for (int k = 0; k < trackData.weights.length; k++) { +// System.err.print(trackData.weights[k] + ","); +// if(k % nbMorph == 0 && k!=0){ +// System.err.println(" "); +// } +// } + MorphTrack track = new MorphTrack(g, trackData.times, trackData.weights, nbMorph); + aTracks.add(track); + } + } else if (node instanceof JointWrapper) { + JointWrapper jw = (JointWrapper) node; + usedJoints.add(jw.joint); if (skinIndex == -1) { - skinIndex = b.skinIndex; + skinIndex = jw.skinIndex; } else { - //Check if all bones affected by this animation are from the same skin, the track will be skipped. - if (skinIndex != b.skinIndex) { - logger.log(Level.WARNING, "Animation " + animationIndex + " (" + name + ") applies to bones that are not from the same skin: skin " + skinIndex + ", bone " + b.bone.getName() + " from skin " + b.skinIndex); + //Check if all joints affected by this animation are from the same skin, the track will be skipped. + if (skinIndex != jw.skinIndex) { + logger.log(Level.WARNING, "Animation " + animationIndex + " (" + name + ") applies to joints that are not from the same skin: skin " + skinIndex + ", joint " + jw.joint.getName() + " from skin " + jw.skinIndex); continue; } } - BoneTrack track = new BoneTrack(b.boneIndex, trackData.times, trackData.translations, trackData.rotations, trackData.scales); - anim.addTrack(track); - + TransformTrack track = new TransformTrack(jw.joint, trackData.times, trackData.translations, trackData.rotations, trackData.scales); + aTracks.add(track); } } @@ -840,42 +869,40 @@ public class GltfLoader implements AssetLoader { // instead of the local pose that is supposed to be the default if (skinIndex != -1) { SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class); - Skeleton skeleton = skin.skeletonControl.getSkeleton(); - for (Bone bone : skin.bones) { - if (!usedBones.contains(bone) && !equalBindAndLocalTransforms(bone)) { + for (Joint joint : skin.joints) { + if (!usedJoints.contains(joint)) { //create a track - float[] times = new float[]{0, anim.getLength()}; - - Vector3f t = bone.getLocalPosition().subtract(bone.getBindPosition()); - Quaternion r = tmpQuat.set(bone.getBindRotation()).inverse().multLocal(bone.getLocalRotation()); - Vector3f s = bone.getLocalScale().divide(bone.getBindScale()); + float[] times = new float[]{0}; - Vector3f[] translations = new Vector3f[]{t, t}; - Quaternion[] rotations = new Quaternion[]{r, r}; - Vector3f[] scales = new Vector3f[]{s, s}; - - int boneIndex = skeleton.getBoneIndex(bone); - BoneTrack track = new BoneTrack(boneIndex, times, translations, rotations, scales); - anim.addTrack(track); + Vector3f[] translations = new Vector3f[]{joint.getLocalTranslation()}; + Quaternion[] rotations = new Quaternion[]{joint.getLocalRotation()}; + Vector3f[] scales = new Vector3f[]{joint.getLocalScale()}; + TransformTrack track = new TransformTrack(joint, times, translations, rotations, scales); + aTracks.add(track); } } } + anim.setTracks(aTracks.toArray(new AnimTrack[aTracks.size()])); + anim = customContentManager.readExtensionAndExtras("animations", animation, anim); if (skinIndex != -1) { - //we have a bone animation. + //we have a armature animation. SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class); - skin.animControl.addAnim(anim); + skin.animComposer.addAnimClip(anim); } if (!spatials.isEmpty()) { if (skinIndex != -1) { - //there are some spatial tracks in this bone animation... or the other way around. Let's add the spatials in the skinnedSpatials. + //there are some spatial or moph tracks in this bone animation... or the other way around. Let's add the spatials in the skinnedSpatials. SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class); List spat = skinnedSpatials.get(skin); spat.addAll(spatials); //the animControl will be added in the setupControls(); + if (hasMorphTrack && skin.morphControl == null) { + skin.morphControl = new MorphControl(); + } } else { //Spatial animation Spatial spatial = null; @@ -885,12 +912,15 @@ public class GltfLoader implements AssetLoader { spatial = findCommonAncestor(spatials); } - AnimControl control = spatial.getControl(AnimControl.class); - if (control == null) { - control = new AnimControl(); - spatial.addControl(control); + AnimComposer composer = spatial.getControl(AnimComposer.class); + if (composer == null) { + composer = new AnimComposer(); + spatial.addControl(composer); + } + composer.addAnimClip(anim); + if (hasMorphTrack && spatial.getControl(MorphControl.class) == null) { + spatial.addControl(new MorphControl()); } - control.addAnim(anim); } } } @@ -932,16 +962,16 @@ public class GltfLoader implements AssetLoader { //It's not mandatory and exporters tends to mix up how it should be used because the specs are not clear. //Anyway we have other means to detect both armature structures and root bones. - JsonArray joints = skin.getAsJsonArray("joints"); - assertNotNull(joints, "No joints defined for skin"); - int idx = allJoints.indexOf(joints); + JsonArray jsonJoints = skin.getAsJsonArray("joints"); + assertNotNull(jsonJoints, "No joints defined for skin"); + int idx = allJoints.indexOf(jsonJoints); if (idx >= 0) { //skin already exists let's just set it in the cache SkinData sd = fetchFromCache("skins", idx, SkinData.class); addToCache("skins", index, sd, nodes.size()); continue; } else { - allJoints.add(joints); + allJoints.add(jsonJoints); } //These inverse bind matrices, once inverted again, will give us the real bind pose of the bones (in model space), @@ -951,136 +981,76 @@ public class GltfLoader implements AssetLoader { if (matricesIndex != null) { inverseBindMatrices = readAccessorData(matricesIndex, matrix4fArrayPopulator); } else { - inverseBindMatrices = new Matrix4f[joints.size()]; + inverseBindMatrices = new Matrix4f[jsonJoints.size()]; for (int i = 0; i < inverseBindMatrices.length; i++) { inverseBindMatrices[i] = new Matrix4f(); } } - Bone[] bones = new Bone[joints.size()]; - for (int i = 0; i < joints.size(); i++) { - int boneIndex = joints.get(i).getAsInt(); - //we don't need the inverse bind matrix, we need the bind matrix so let's invert it. - Matrix4f modelBindMatrix = inverseBindMatrices[i].invertLocal(); - bones[i] = readNodeAsBone(boneIndex, i, index, modelBindMatrix); + Joint[] joints = new Joint[jsonJoints.size()]; + for (int i = 0; i < jsonJoints.size(); i++) { + int boneIndex = jsonJoints.get(i).getAsInt(); + Matrix4f inverseModelBindMatrix = inverseBindMatrices[i]; + joints[i] = readNodeAsBone(boneIndex, i, index, inverseModelBindMatrix); } - for (int i = 0; i < joints.size(); i++) { - findChildren(joints.get(i).getAsInt()); + for (int i = 0; i < jsonJoints.size(); i++) { + findChildren(jsonJoints.get(i).getAsInt()); } - Skeleton skeleton = new Skeleton(bones); + Armature armature = new Armature(joints); - //Compute bind transforms. We need to do it from root bone to leaves bone. - for (Bone bone : skeleton.getRoots()) { - BoneWrapper bw = findBoneWrapper(bone); - computeBindTransforms(bw, skeleton); - } - - skeleton = customContentManager.readExtensionAndExtras("skin", skin, skeleton); + armature = customContentManager.readExtensionAndExtras("skin", skin, armature); SkinData skinData = new SkinData(); - skinData.bones = bones; - skinData.skeletonControl = new SkeletonControl(skeleton); - skinData.animControl = new AnimControl(skinData.skeletonControl.getSkeleton()); + skinData.joints = joints; + skinData.skinningControl = new SkinningControl(armature); + skinData.animComposer = new AnimComposer(); addToCache("skins", index, skinData, nodes.size()); skinnedSpatials.put(skinData, new ArrayList()); - // Set local transforms. - // The skeleton may come in a given pose, that is not the rest pose, so let 's apply it. - // We will need it later for animation - for (int i = 0; i < joints.size(); i++) { - applyPose(joints.get(i).getAsInt()); - } - skeleton.updateWorldVectors(); - - //If the user didn't ask to keep the pose we reset the skeleton user control - if (!isKeepSkeletonPose(info)) { - for (Bone bone : bones) { - bone.setUserControl(false); - } - } - } - } - - private void applyPose(int index) { - BoneWrapper bw = fetchFromCache("nodes", index, BoneWrapper.class); - bw.bone.setUserControl(true); - bw.bone.setLocalTranslation(bw.localTransform.getTranslation()); - bw.bone.setLocalRotation(bw.localTransform.getRotation()); - bw.bone.setLocalScale(bw.localTransform.getScale()); - } - - private void computeBindTransforms(BoneWrapper boneWrapper, Skeleton skeleton) { - Bone bone = boneWrapper.bone; - tmpTransforms.fromTransformMatrix(boneWrapper.modelBindMatrix); - if (bone.getParent() != null) { - //root bone, model transforms are the same as the local transforms - //but for child bones we need to combine it with the parents inverse model transforms. - tmpMat.setTranslation(bone.getParent().getModelSpacePosition()); - tmpMat.setRotationQuaternion(bone.getParent().getModelSpaceRotation()); - tmpMat.setScale(bone.getParent().getModelSpaceScale()); - tmpMat.invertLocal(); - tmpTransforms2.fromTransformMatrix(tmpMat); - tmpTransforms.combineWithParent(tmpTransforms2); - } - bone.setBindTransforms(tmpTransforms.getTranslation(), tmpTransforms.getRotation(), tmpTransforms.getScale()); - - //resets the local transforms to bind transforms for all bones. - //then computes the model transforms from local transforms for each bone. - skeleton.resetAndUpdate(); - skeleton.setBindingPose(); - for (Integer childIndex : boneWrapper.children) { - BoneWrapper child = fetchFromCache("nodes", childIndex, BoneWrapper.class); - computeBindTransforms(child, skeleton); - } - } - - private BoneWrapper findBoneWrapper(Bone bone) { - for (int i = 0; i < nodes.size(); i++) { - BoneWrapper bw = fetchFromCache("nodes", i, BoneWrapper.class); - if (bw != null && bw.bone == bone) { - return bw; - } + armature.update(); + armature.saveInitialPose(); } - return null; } - public Bone readNodeAsBone(int nodeIndex, int boneIndex, int skinIndex, Matrix4f modelBindMatrix) throws IOException { + public Joint readNodeAsBone(int nodeIndex, int jointIndex, int skinIndex, Matrix4f inverseModelBindMatrix) throws IOException { - BoneWrapper boneWrapper = fetchFromCache("nodes", nodeIndex, BoneWrapper.class); - if (boneWrapper != null) { - return boneWrapper.bone; + JointWrapper jointWrapper = fetchFromCache("nodes", nodeIndex, JointWrapper.class); + if (jointWrapper != null) { + return jointWrapper.joint; } JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject(); String name = getAsString(nodeData, "name"); if (name == null) { - name = "Bone_" + nodeIndex; + name = "Joint_" + nodeIndex; } - Bone bone = new Bone(name); + Joint joint = new Joint(name); Transform boneTransforms = null; boneTransforms = readTransforms(nodeData); + joint.setLocalTransform(boneTransforms); + joint.setInverseModelBindMatrix(inverseModelBindMatrix); - addToCache("nodes", nodeIndex, new BoneWrapper(bone, boneIndex, skinIndex, modelBindMatrix, boneTransforms), nodes.size()); + addToCache("nodes", nodeIndex, new JointWrapper(joint, jointIndex, skinIndex), nodes.size()); - return bone; + return joint; } private void findChildren(int nodeIndex) throws IOException { - BoneWrapper bw = fetchFromCache("nodes", nodeIndex, BoneWrapper.class); + JointWrapper jw = fetchFromCache("nodes", nodeIndex, JointWrapper.class); JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject(); JsonArray children = nodeData.getAsJsonArray("children"); if (children != null) { for (JsonElement child : children) { int childIndex = child.getAsInt(); - if (bw.children.contains(childIndex)) { + if (jw.children.contains(childIndex)) { //bone already has the child in its children continue; } - BoneWrapper cbw = fetchFromCache("nodes", childIndex, BoneWrapper.class); - if (cbw != null) { - bw.bone.addChild(cbw.bone); - bw.children.add(childIndex); + JointWrapper cjw = fetchFromCache("nodes", childIndex, JointWrapper.class); + if (cjw != null) { + jw.joint.addChild(cjw.joint); + jw.children.add(childIndex); } else { //The child might be a Node //Creating a dummy node to read the subgraph @@ -1089,7 +1059,7 @@ public class GltfLoader implements AssetLoader { Spatial s = n.getChild(0); //removing the spatial from the dummy node, it will be attached to the attachment node of the bone s.removeFromParent(); - bw.attachedSpatial = s; + jw.attachedSpatial = s; } } @@ -1099,33 +1069,38 @@ public class GltfLoader implements AssetLoader { private void setupControls() { for (SkinData skinData : skinnedSpatials.keySet()) { List spatials = skinnedSpatials.get(skinData); - Spatial spatial = skinData.parent; if (spatials.isEmpty()) { continue; } + Spatial spatial = skinData.parent; if (spatials.size() >= 1) { spatial = findCommonAncestor(spatials); } - if (skinData.parent != null && spatial != skinData.parent) { - skinData.rootBoneTransformOffset = spatial.getWorldTransform().invert(); - skinData.rootBoneTransformOffset.combineWithParent(skinData.parent.getWorldTransform()); +// if (spatial != skinData.parent) { +// skinData.rootBoneTransformOffset = spatial.getWorldTransform().invert(); +// if (skinData.parent != null) { +// skinData.rootBoneTransformOffset.combineWithParent(skinData.parent.getWorldTransform()); +// } +// } + + if (skinData.animComposer != null && skinData.animComposer.getSpatial() == null) { + spatial.addControl(skinData.animComposer); } - - if (skinData.animControl != null && skinData.animControl.getSpatial() == null) { - spatial.addControl(skinData.animControl); + spatial.addControl(skinData.skinningControl); + if (skinData.morphControl != null) { + spatial.addControl(skinData.morphControl); } - spatial.addControl(skinData.skeletonControl); } for (int i = 0; i < nodes.size(); i++) { - BoneWrapper bw = fetchFromCache("nodes", i, BoneWrapper.class); + JointWrapper bw = fetchFromCache("nodes", i, JointWrapper.class); if (bw == null || bw.attachedSpatial == null) { continue; } SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class); - skinData.skeletonControl.getAttachmentsNode(bw.bone.getName()).attachChild(bw.attachedSpatial); + skinData.skinningControl.getAttachmentsNode(bw.joint.getName()).attachChild(bw.attachedSpatial); } } @@ -1182,118 +1157,29 @@ public class GltfLoader implements AssetLoader { } - private class BoneWrapper { - Bone bone; - int boneIndex; + private class JointWrapper { + Joint joint; + int jointIndex; int skinIndex; - Transform localTransform; - Transform localTransformOffset; - Matrix4f modelBindMatrix; boolean isRoot = false; - boolean localUpdated = false; Spatial attachedSpatial; List children = new ArrayList<>(); - public BoneWrapper(Bone bone, int boneIndex, int skinIndex, Matrix4f modelBindMatrix, Transform localTransform) { - this.bone = bone; - this.boneIndex = boneIndex; + public JointWrapper(Joint joint, int jointIndex, int skinIndex) { + this.joint = joint; + this.jointIndex = jointIndex; this.skinIndex = skinIndex; - this.modelBindMatrix = modelBindMatrix; - this.localTransform = localTransform; - this.localTransformOffset = localTransform.clone(); - } - - /** - * Applies the inverse Bind transforms to anim data. and the armature transforms if relevant. - */ - public void update(TrackData data) { - Transform bindTransforms = new Transform(bone.getBindPosition(), bone.getBindRotation(), bone.getBindScale()); - SkinData skinData = fetchFromCache("skins", skinIndex, SkinData.class); - - if (!localUpdated) { - //LocalTransform of the bone are default position to use for animations when there is no track. - //We need to transform them so that JME can us them in blendAnimTransform. - reverseBlendAnimTransforms(localTransformOffset, bindTransforms); - localUpdated = true; - } - - for (int i = 0; i < data.getNbKeyFrames(); i++) { - - Vector3f translation = getTranslation(data, i); - Quaternion rotation = getRotation(data, i); - Vector3f scale = getScale(data, i); - - Transform t = new Transform(translation, rotation, scale); - if (isRoot && skinData.rootBoneTransformOffset != null) { - //Apply the armature transforms to the root bone anim track. - t.combineWithParent(skinData.rootBoneTransformOffset); - } - - reverseBlendAnimTransforms(t, bindTransforms); - - if (data.translations != null) { - data.translations[i] = t.getTranslation(); - } - if (data.rotations != null) { - data.rotations[i] = t.getRotation(); - } - if (data.scales != null) { - data.scales[i] = t.getScale(); - } - } - - data.ensureTranslationRotations(localTransformOffset); - } - - private void reverseBlendAnimTransforms(Transform t, Transform bindTransforms) { - //This is wrong - //You'd normally combine those transforms with transform.combineWithParent() - //Here we actually do in reverse what JME does to combine anim transforms with bind transfoms (add trans/mult rot/ mult scale) - //The code to fix is in Bone.blendAnimTransforms - //TODO fix blendAnimTransforms - t.getTranslation().subtractLocal(bindTransforms.getTranslation()); - t.getScale().divideLocal(bindTransforms.getScale()); - tmpQuat.set(bindTransforms.getRotation()).inverseLocal().multLocal(t.getRotation()); - t.setRotation(tmpQuat); - } - - private Vector3f getTranslation(TrackData data, int i) { - Vector3f translation; - if (data.translations == null) { - translation = bone.getLocalPosition(); - } else { - translation = data.translations[i]; - } - return translation; - } - - private Quaternion getRotation(TrackData data, int i) { - Quaternion rotation; - if (data.rotations == null) { - rotation = bone.getLocalRotation(); - } else { - rotation = data.rotations[i]; - } - return rotation; - } - - private Vector3f getScale(TrackData data, int i) { - Vector3f scale; - if (data.scales == null) { - scale = bone.getLocalScale(); - } else { - scale = data.scales[i]; - } - return scale; } } private class SkinData { - SkeletonControl skeletonControl; - AnimControl animControl; + SkinningControl skinningControl; + MorphControl morphControl; + AnimComposer animComposer; + Spatial spatial; Spatial parent; Transform rootBoneTransformOffset; - Bone[] bones; + Joint[] joints; boolean used = false; } @@ -1380,6 +1266,27 @@ public class GltfLoader implements AssetLoader { } } +// +// private class FloaGridPopulator implements Populator { +// +// @Override +// public float[][] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException { +// +// int numComponents = getNumberOfComponents(type); +// int dataSize = numComponents * count; +// float[] data = new float[dataSize]; +// +// if (bufferViewIndex == null) { +// //no referenced buffer, specs says to pad the data with zeros. +// padBuffer(data, dataSize); +// } else { +// readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, getVertexBufferFormat(componentType)); +// } +// +// return data; +// } +// +// } private class Vector3fArrayPopulator implements Populator { diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java index 68675e680..1e6ebfcd6 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java @@ -1,7 +1,6 @@ package com.jme3.scene.plugins.gltf; import com.google.gson.*; -import com.jme3.animation.Bone; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoadException; import com.jme3.math.*; @@ -487,6 +486,8 @@ public class GltfUtils { mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, BufferUtils.createShortBuffer(jointsArray)); } mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, BufferUtils.createFloatBuffer(weightsArray)); + mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(VertexBuffer.Usage.CpuOnly); + mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(VertexBuffer.Usage.CpuOnly); } private static void populateFloatArray(float[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { @@ -686,11 +687,11 @@ public class GltfUtils { } } - public static boolean equalBindAndLocalTransforms(Bone b) { - return equalsEpsilon(b.getBindPosition(), b.getLocalPosition()) - && equalsEpsilon(b.getBindRotation(), b.getLocalRotation()) - && equalsEpsilon(b.getBindScale(), b.getLocalScale()); - } +// public static boolean equalBindAndLocalTransforms(Joint b) { +// return equalsEpsilon(b.getBindPosition(), b.getLocalPosition()) +// && equalsEpsilon(b.getBindRotation(), b.getLocalRotation()) +// && equalsEpsilon(b.getBindScale(), b.getLocalScale()); +// } private static float epsilon = 0.0001f; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TrackData.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TrackData.java index b9122f072..f5e0154f3 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TrackData.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TrackData.java @@ -10,7 +10,8 @@ public class TrackData { public enum Type { Translation, Rotation, - Scale + Scale, + Morph } Float length; @@ -21,7 +22,6 @@ public class TrackData { Vector3f[] translations; Quaternion[] rotations; Vector3f[] scales; - //not used for now float[] weights; public void update() { @@ -125,6 +125,13 @@ public class TrackData { System.arraycopy(scales, 0, newScales, 1, scales.length); scales = newScales; } + if (weights != null) { + int nbMorph = weights.length / (times.length - 1); + float[] newWeights = new float[weights.length + nbMorph]; + System.arraycopy(weights, 0, newWeights, 0, nbMorph); + System.arraycopy(weights, 0, newWeights, nbMorph, weights.length); + weights = newWeights; + } } checkTimesConsistantcy(); @@ -336,4 +343,4 @@ public class TrackData { Vector3f scale; } -} \ No newline at end of file +} diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/AnimData.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/AnimData.java index 1b1eb2a9b..281926a46 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/AnimData.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/AnimData.java @@ -31,17 +31,18 @@ */ package com.jme3.scene.plugins.ogre; -import com.jme3.animation.Animation; -import com.jme3.animation.Skeleton; +import com.jme3.anim.AnimClip; +import com.jme3.anim.Armature; + import java.util.ArrayList; public class AnimData { - public final Skeleton skeleton; - public final ArrayList anims; + public final Armature armature; + public final ArrayList anims; - public AnimData(Skeleton skeleton, ArrayList anims) { - this.skeleton = skeleton; + public AnimData(Armature armature, ArrayList anims) { + this.armature = armature; this.anims = anims; } } diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java index baa30a0d1..01474b5c4 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java @@ -31,9 +31,7 @@ */ package com.jme3.scene.plugins.ogre; -import com.jme3.animation.AnimControl; -import com.jme3.animation.Animation; -import com.jme3.animation.SkeletonControl; +import com.jme3.anim.*; import com.jme3.asset.*; import com.jme3.material.Material; import com.jme3.material.MaterialList; @@ -41,30 +39,23 @@ import com.jme3.math.ColorRGBA; import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.*; -import com.jme3.scene.VertexBuffer.Format; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.VertexBuffer.*; import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey; -import com.jme3.util.BufferUtils; -import com.jme3.util.IntMap; +import com.jme3.util.*; import com.jme3.util.IntMap.Entry; -import com.jme3.util.PlaceholderAssets; -import static com.jme3.util.xml.SAXUtil.*; +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; import java.io.IOException; import java.io.InputStreamReader; import java.nio.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParserFactory; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.helpers.DefaultHandler; + +import static com.jme3.util.xml.SAXUtil.*; /** * Loads Ogre3D mesh.xml files. @@ -799,35 +790,28 @@ public class MeshLoader extends DefaultHandler implements AssetLoader { for (int i = 0; i < geoms.size(); i++) { Geometry g = geoms.get(i); Mesh m = geoms.get(i).getMesh(); - - //FIXME the parameter is now useless. - //It was !HADWARE_SKINNING before, but since toggleing - //HW skinning does not happen at load time it was always true. - //We should use something similar as for the HWBoneIndex and - //HWBoneWeight : create the vertex buffers empty so that they - //are put in the cache, and really populate them the first time - //software skinning is used on the mesh. - m.generateBindPose(true); - + m.generateBindPose(); } // Put the animations in the AnimControl - HashMap anims = new HashMap(); - ArrayList animList = animData.anims; + HashMap anims = new HashMap<>(); + ArrayList animList = animData.anims; for (int i = 0; i < animList.size(); i++) { - Animation anim = animList.get(i); + AnimClip anim = animList.get(i); anims.put(anim.getName(), anim); } - AnimControl ctrl = new AnimControl(animData.skeleton); - ctrl.setAnimations(anims); - model.addControl(ctrl); + AnimComposer composer = new AnimComposer(); + for (AnimClip clip : anims.values()) { + composer.addAnimClip(clip); + } + model.addControl(composer); // Put the skeleton in the skeleton control - SkeletonControl skeletonControl = new SkeletonControl(animData.skeleton); + SkinningControl skinningControl = new SkinningControl(animData.armature); // This will acquire the targets from the node - model.addControl(skeletonControl); + model.addControl(skinningControl); } return model; diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java index 56dc95bb6..d9977d3a9 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java @@ -31,55 +31,46 @@ */ package com.jme3.scene.plugins.ogre; -import com.jme3.animation.Animation; -import com.jme3.animation.Bone; -import com.jme3.animation.BoneTrack; -import com.jme3.animation.Skeleton; +import com.jme3.anim.*; +import com.jme3.anim.util.AnimMigrationUtils; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoader; -import com.jme3.asset.AssetManager; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.util.xml.SAXUtil; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.Stack; -import java.util.logging.Logger; +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.helpers.DefaultHandler; +import java.io.*; +import java.util.*; +import java.util.logging.Logger; public class SkeletonLoader extends DefaultHandler implements AssetLoader { private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); - private AssetManager assetManager; + //private AssetManager assetManager; private Stack elementStack = new Stack(); - private HashMap indexToBone = new HashMap(); - private HashMap nameToBone = new HashMap(); - private BoneTrack track; - private ArrayList tracks = new ArrayList(); - private Animation animation; - private ArrayList animations; - private Bone bone; - private Skeleton skeleton; - private ArrayList times = new ArrayList(); - private ArrayList translations = new ArrayList(); - private ArrayList rotations = new ArrayList(); - private ArrayList scales = new ArrayList(); + private HashMap indexToJoint = new HashMap<>(); + private HashMap nameToJoint = new HashMap<>(); + private TransformTrack track; + private ArrayList tracks = new ArrayList<>(); + private AnimClip animClip; + private ArrayList animClips; + private Joint joint; + private Armature armature; + private ArrayList times = new ArrayList<>(); + private ArrayList translations = new ArrayList<>(); + private ArrayList rotations = new ArrayList<>(); + private ArrayList scales = new ArrayList<>(); private float time = -1; private Vector3f position; private Quaternion rotation; private Vector3f scale; private float angle; private Vector3f axis; + private List unusedJoints = new ArrayList<>(); public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException { if (qName.equals("position") || qName.equals("translate")) { @@ -99,38 +90,40 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader { assert elementStack.peek().equals("track"); } else if (qName.equals("track")) { assert elementStack.peek().equals("tracks"); - String boneName = SAXUtil.parseString(attribs.getValue("bone")); - Bone bone = nameToBone.get(boneName); - int index = skeleton.getBoneIndex(bone); - track = new BoneTrack(index); + String jointName = SAXUtil.parseString(attribs.getValue("bone")); + joint = nameToJoint.get(jointName); + track = new TransformTrack(); + track.setTarget(joint); } else if (qName.equals("boneparent")) { assert elementStack.peek().equals("bonehierarchy"); - String boneName = attribs.getValue("bone"); + String jointName = attribs.getValue("bone"); String parentName = attribs.getValue("parent"); - Bone bone = nameToBone.get(boneName); - Bone parent = nameToBone.get(parentName); - parent.addChild(bone); + Joint joint = nameToJoint.get(jointName); + Joint parent = nameToJoint.get(parentName); + parent.addChild(joint); } else if (qName.equals("bone")) { assert elementStack.peek().equals("bones"); // insert bone into indexed map - bone = new Bone(attribs.getValue("name")); + joint = new Joint(attribs.getValue("name")); int id = SAXUtil.parseInt(attribs.getValue("id")); - indexToBone.put(id, bone); - nameToBone.put(bone.getName(), bone); + indexToJoint.put(id, joint); + nameToJoint.put(joint.getName(), joint); } else if (qName.equals("tracks")) { assert elementStack.peek().equals("animation"); tracks.clear(); + unusedJoints.clear(); + unusedJoints.addAll(nameToJoint.values()); } else if (qName.equals("animation")) { assert elementStack.peek().equals("animations"); String name = SAXUtil.parseString(attribs.getValue("name")); - float length = SAXUtil.parseFloat(attribs.getValue("length")); - animation = new Animation(name, length); + //float length = SAXUtil.parseFloat(attribs.getValue("length")); + animClip = new AnimClip(name); } else if (qName.equals("bonehierarchy")) { assert elementStack.peek().equals("skeleton"); } else if (qName.equals("animations")) { assert elementStack.peek().equals("skeleton"); - animations = new ArrayList(); + animClips = new ArrayList<>(); } else if (qName.equals("bones")) { assert elementStack.peek().equals("skeleton"); } else if (qName.equals("skeleton")) { @@ -149,32 +142,42 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader { angle = 0; axis = null; } else if (qName.equals("bone")) { - bone.setBindTransforms(position, rotation, scale); - bone = null; + joint.getLocalTransform().setTranslation(position); + joint.getLocalTransform().setRotation(rotation); + if (scale != null) { + joint.getLocalTransform().setScale(scale); + } + joint = null; position = null; rotation = null; scale = null; } else if (qName.equals("bonehierarchy")) { - Bone[] bones = new Bone[indexToBone.size()]; - // find bones without a parent and attach them to the skeleton - // also assign the bones to the bonelist - for (Map.Entry entry : indexToBone.entrySet()) { - Bone bone = entry.getValue(); - bones[entry.getKey()] = bone; + Joint[] joints = new Joint[indexToJoint.size()]; + // find joints without a parent and attach them to the armature + // also assign the joints to the jointList + for (Map.Entry entry : indexToJoint.entrySet()) { + Joint joint = entry.getValue(); + joints[entry.getKey()] = joint; } - indexToBone.clear(); - skeleton = new Skeleton(bones); + indexToJoint.clear(); + armature = new Armature(joints); + armature.saveBindPose(); } else if (qName.equals("animation")) { - animations.add(animation); - animation = null; + animClips.add(animClip); + animClip = null; } else if (qName.equals("track")) { if (track != null) { // if track has keyframes tracks.add(track); + unusedJoints.remove(joint); track = null; } } else if (qName.equals("tracks")) { - BoneTrack[] trackList = tracks.toArray(new BoneTrack[tracks.size()]); - animation.setTracks(trackList); + //nameToJoint contains the joints with no track + for (Joint j : unusedJoints) { + AnimMigrationUtils.padJointTracks(tracks, j); + } + TransformTrack[] trackList = tracks.toArray(new TransformTrack[tracks.size()]); + animClip.setTracks(trackList); tracks.clear(); } else if (qName.equals("keyframe")) { assert time >= 0; @@ -182,14 +185,13 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader { assert rotation != null; times.add(time); - translations.add(position); - rotations.add(rotation); + translations.add(position.addLocal(joint.getLocalTranslation())); + rotations.add(joint.getLocalRotation().mult(rotation, rotation)); if (scale != null) { - scales.add(scale); + scales.add(scale.multLocal(joint.getLocalScale())); }else{ scales.add(new Vector3f(1,1,1)); } - time = -1; position = null; rotation = null; @@ -206,7 +208,6 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader { Vector3f[] scalesArray = scales.toArray(new Vector3f[scales.size()]); track.setKeyframes(timesArray, transArray, rotArray, scalesArray); - //track.setKeyframes(timesArray, transArray, rotArray); } else { track = null; } @@ -216,7 +217,7 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader { rotations.clear(); scales.clear(); } else if (qName.equals("skeleton")) { - nameToBone.clear(); + nameToJoint.clear(); } assert elementStack.peek().equals(qName); elementStack.pop(); @@ -228,17 +229,17 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader { */ private void fullReset() { elementStack.clear(); - indexToBone.clear(); - nameToBone.clear(); + indexToJoint.clear(); + nameToJoint.clear(); track = null; tracks.clear(); - animation = null; - if (animations != null) { - animations.clear(); + animClip = null; + if (animClips != null) { + animClips.clear(); } - bone = null; - skeleton = null; + joint = null; + armature = null; times.clear(); rotations.clear(); translations.clear(); @@ -266,12 +267,12 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader { xr.setErrorHandler(this); InputStreamReader r = new InputStreamReader(in); xr.parse(new InputSource(r)); - if (animations == null) { - animations = new ArrayList(); + if (animClips == null) { + animClips = new ArrayList(); } - AnimData data = new AnimData(skeleton, animations); - skeleton = null; - animations = null; + AnimData data = new AnimData(armature, animClips); + armature = null; + animClips = null; return data; } catch (SAXException ex) { IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); @@ -288,7 +289,7 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader { } public Object load(AssetInfo info) throws IOException { - assetManager = info.getManager(); + //AssetManager assetManager = info.getManager(); InputStream in = null; try { in = info.openStream(); diff --git a/jme3-testdata/src/main/resources/Models/Oto/OtoOldAnim.j3o b/jme3-testdata/src/main/resources/Models/Oto/OtoOldAnim.j3o new file mode 100644 index 000000000..101dd8405 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Oto/OtoOldAnim.j3o differ diff --git a/jme3-testdata/src/main/resources/Models/Sinbad/SinbadOldAnim.j3o b/jme3-testdata/src/main/resources/Models/Sinbad/SinbadOldAnim.j3o new file mode 100644 index 000000000..4df92c80a Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Sinbad/SinbadOldAnim.j3o differ