parent
6b69da3480
commit
3eb890da38
@ -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