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 extends JointModelTransform> 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 extends JointModelTransform> 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 extends JointModelTransform>) 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. Map "pick target" action
- * to a MouseButtonTrigger. flyCam.setEnabled(false);
- * inputManager.setCursorVisible(true); 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
+ //