parent
c2b6e8f407
commit
75f90fb70c
@ -0,0 +1,251 @@ |
|||||||
|
package com.jme3.animation; |
||||||
|
|
||||||
|
import com.jme3.export.*; |
||||||
|
import com.jme3.math.Matrix4f; |
||||||
|
import com.jme3.util.TempVars; |
||||||
|
import com.jme3.util.clone.Cloner; |
||||||
|
import com.jme3.util.clone.JmeCloneable; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* 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; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Serialization only |
||||||
|
*/ |
||||||
|
public Armature() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an armature from a joint list. |
||||||
|
* The root joints are found automatically. |
||||||
|
* <p> |
||||||
|
* 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<Joint> rootJointList = new ArrayList<>(); |
||||||
|
for (int i = jointList.length - 1; i >= 0; i--) { |
||||||
|
Joint b = jointList[i]; |
||||||
|
if (b.getParent() == null) { |
||||||
|
rootJointList.add(b); |
||||||
|
} |
||||||
|
} |
||||||
|
rootJoints = rootJointList.toArray(new Joint[rootJointList.size()]); |
||||||
|
|
||||||
|
createSkinningMatrices(); |
||||||
|
|
||||||
|
for (int i = rootJoints.length - 1; i >= 0; i--) { |
||||||
|
Joint rootJoint = rootJoints[i]; |
||||||
|
rootJoint.update(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Special-purpose copy constructor.
|
||||||
|
// * <p>
|
||||||
|
// * Shallow copies bind pose data from the source skeleton, does not
|
||||||
|
// * copy any other data.
|
||||||
|
// *
|
||||||
|
// * @param source The source Skeleton to copy from
|
||||||
|
// */
|
||||||
|
// public Armature(Armature source) {
|
||||||
|
// Joint[] sourceList = source.jointList;
|
||||||
|
// jointList = new Joint[sourceList.length];
|
||||||
|
// for (int i = 0; i < sourceList.length; i++) {
|
||||||
|
// jointList[i] = new Joint(sourceList[i]);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rootJoints = new Bone[source.rootJoints.length];
|
||||||
|
// for (int i = 0; i < rootJoints.length; i++) {
|
||||||
|
// rootJoints[i] = recreateBoneStructure(source.rootJoints[i]);
|
||||||
|
// }
|
||||||
|
// createSkinningMatrices();
|
||||||
|
//
|
||||||
|
// for (int i = rootJoints.length - 1; i >= 0; i--) {
|
||||||
|
// rootJoints[i].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(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* returns the array of all root joints of this Armatire |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public Joint[] getRoots() { |
||||||
|
return rootJoints; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 it's bind pose. |
||||||
|
*/ |
||||||
|
public void setBindPose() { |
||||||
|
//make sure all bones are updated
|
||||||
|
update(); |
||||||
|
//Save the current pose as bind pose
|
||||||
|
for (Joint rootJoint : rootJoints) { |
||||||
|
rootJoint.setBindPose(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Compute the skining matrices for each bone of the armature that would be used to transform vertices of associated meshes |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
public Matrix4f[] computeSkinningMatrices() { |
||||||
|
TempVars vars = TempVars.get(); |
||||||
|
for (int i = 0; i < jointList.length; i++) { |
||||||
|
jointList[i].getOffsetTransform(skinningMatrixes[i], vars.quat1, vars.vect1, vars.vect2, vars.tempMat3); |
||||||
|
} |
||||||
|
vars.release(); |
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@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); |
||||||
|
|
||||||
|
createSkinningMatrices(); |
||||||
|
|
||||||
|
for (Joint rootJoint : rootJoints) { |
||||||
|
rootJoint.update(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void write(JmeExporter ex) throws IOException { |
||||||
|
OutputCapsule output = ex.getCapsule(this); |
||||||
|
output.write(rootJoints, "rootJoints", null); |
||||||
|
output.write(jointList, "jointList", null); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,742 @@ |
|||||||
|
/* |
||||||
|
* 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.animation; |
||||||
|
|
||||||
|
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 Skeleton control deforms a model according to a armature, It handles the |
||||||
|
* computation of the deformation matrices and performs the transformations on |
||||||
|
* the mesh |
||||||
|
* |
||||||
|
* @author Rémy Bouquet Based on AnimControl by Kirill Vainer |
||||||
|
*/ |
||||||
|
public class ArmatureControl extends AbstractControl implements Cloneable, JmeCloneable { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(ArmatureControl.class.getName()); |
||||||
|
|
||||||
|
/** |
||||||
|
* The armature of the model. |
||||||
|
*/ |
||||||
|
private Armature armature; |
||||||
|
|
||||||
|
/** |
||||||
|
* List of geometries affected by this control. |
||||||
|
*/ |
||||||
|
private SafeArrayList<Geometry> targets = new SafeArrayList<Geometry>(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 ArmatureControl() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a armature control. The list of targets will be acquired |
||||||
|
* automatically when the control is attached to a node. |
||||||
|
* |
||||||
|
* @param skeleton the armature |
||||||
|
*/ |
||||||
|
public ArmatureControl(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(ArmatureControl.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; |
||||||
|
} |
||||||
|
|
||||||
|
//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 maxWeightsPerVert maximum number of weights per vertex |
||||||
|
* @param mesh the mesh |
||||||
|
* @param offsetMatrices the offsetMaytrices 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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,292 @@ |
|||||||
|
package com.jme3.animation; |
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 { |
||||||
|
|
||||||
|
private String name; |
||||||
|
private Joint parent; |
||||||
|
private ArrayList<Joint> children = new ArrayList<>(); |
||||||
|
private Geometry targetGeometry; |
||||||
|
|
||||||
|
public Joint() { |
||||||
|
} |
||||||
|
|
||||||
|
public Joint(String name) { |
||||||
|
this.name = name; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 base transform of the joint in local space. |
||||||
|
* Those transform are the bone's initial value. |
||||||
|
*/ |
||||||
|
private Transform baseLocalTransform = new Transform(); |
||||||
|
|
||||||
|
/** |
||||||
|
* The transform of the bone in model space. Relative to the origin of the model. |
||||||
|
*/ |
||||||
|
private Transform modelTransform = new Transform(); |
||||||
|
|
||||||
|
/** |
||||||
|
* The matrix used to transform affected vertices position into the bone model space. |
||||||
|
* Used for skinning. |
||||||
|
*/ |
||||||
|
private Matrix4f inverseModelBindMatrix = new Matrix4f(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates world transforms for this bone and it's children. |
||||||
|
*/ |
||||||
|
public final void update() { |
||||||
|
this.updateModelTransforms(); |
||||||
|
|
||||||
|
for (int i = children.size() - 1; i >= 0; i--) { |
||||||
|
children.get(i).update(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates the model transforms for this bone, and, possibly the attach node |
||||||
|
* if not null. |
||||||
|
* <p> |
||||||
|
* The model transform of this bone is computed by combining the parent's |
||||||
|
* model transform with this bones' local transform. |
||||||
|
*/ |
||||||
|
public final void updateModelTransforms() { |
||||||
|
modelTransform.set(localTransform); |
||||||
|
if (parent != null) { |
||||||
|
modelTransform.combineWithParent(parent.getModelTransform()); |
||||||
|
} |
||||||
|
|
||||||
|
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(modelTransform); |
||||||
|
|
||||||
|
} else { |
||||||
|
Spatial loopSpatial = targetGeometry; |
||||||
|
Transform combined = modelTransform.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. |
||||||
|
* <p> |
||||||
|
* 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, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3, Matrix3f tmp4) { |
||||||
|
modelTransform.toTransformMatrix(outTransform).mult(inverseModelBindMatrix, outTransform); |
||||||
|
} |
||||||
|
|
||||||
|
protected void setBindPose() { |
||||||
|
//Note that the whole Armature must be updated before calling this method.
|
||||||
|
modelTransform.toTransformMatrix(inverseModelBindMatrix); |
||||||
|
inverseModelBindMatrix.invertLocal(); |
||||||
|
} |
||||||
|
|
||||||
|
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 ArrayList<Joint> 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<Geometry> 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 modelTransform; |
||||||
|
} |
||||||
|
|
||||||
|
public Matrix4f getInverseModelBindMatrix() { |
||||||
|
return inverseModelBindMatrix; |
||||||
|
} |
||||||
|
|
||||||
|
@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.attachedNode = cloner.clone(attachedNode); |
||||||
|
this.targetGeometry = cloner.clone(targetGeometry); |
||||||
|
|
||||||
|
this.baseLocalTransform = cloner.clone(baseLocalTransform); |
||||||
|
this.localTransform = cloner.clone(baseLocalTransform); |
||||||
|
this.modelTransform = cloner.clone(baseLocalTransform); |
||||||
|
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); |
||||||
|
baseLocalTransform = (Transform) input.readSavable("baseLocalTransforms", baseLocalTransform); |
||||||
|
localTransform.set(baseLocalTransform); |
||||||
|
inverseModelBindMatrix = (Matrix4f) input.readSavable("inverseModelBindMatrix", inverseModelBindMatrix); |
||||||
|
|
||||||
|
ArrayList<Joint> 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(baseLocalTransform, "baseLocalTransform", new Transform()); |
||||||
|
output.write(inverseModelBindMatrix, "inverseModelBindMatrix", new Matrix4f()); |
||||||
|
output.writeSavableArrayList(children, "children", null); |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue