New Armature system

shader-nodes-enhancement
Nehon 7 years ago committed by Rémy Bouquet
parent c2b6e8f407
commit 75f90fb70c
  1. 251
      jme3-core/src/main/java/com/jme3/animation/Armature.java
  2. 742
      jme3-core/src/main/java/com/jme3/animation/ArmatureControl.java
  3. 292
      jme3-core/src/main/java/com/jme3/animation/Joint.java
  4. 17
      jme3-core/src/main/java/com/jme3/math/Transform.java
  5. 29
      jme3-core/src/main/java/com/jme3/scene/Mesh.java

@ -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 (&ge;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);
}
}

@ -32,6 +32,7 @@
package com.jme3.math;
import com.jme3.export.*;
import java.io.IOException;
/**
@ -257,11 +258,17 @@ 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);
store.setRotationQuaternion(rot);
store.setScale(scale);
return store;
}
public void fromTransformMatrix(Matrix4f mat) {

@ -39,18 +39,11 @@ 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;
@ -1446,13 +1439,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 +1475,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();

Loading…
Cancel
Save