diff --git a/jme3-core/src/main/java/com/jme3/animation/Bone.java b/jme3-core/src/main/java/com/jme3/animation/Bone.java index 2e2ff55a4..15ac6cc6f 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Bone.java +++ b/jme3-core/src/main/java/com/jme3/animation/Bone.java @@ -34,8 +34,10 @@ package com.jme3.animation; import com.jme3.export.*; import com.jme3.math.*; import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; import com.jme3.scene.Node; import com.jme3.scene.Spatial; +import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.Cloner; @@ -701,16 +703,27 @@ public final class Bone implements Savable, JmeCloneable { * have an attachments node, create one. Models and effects attached to the * attachments node will follow this bone's motions. * - * @param target a geometry animated by this bone, or null to indicate that - * all geometries affected by this bone have the same global transform as - * the attachment node's parent - */ - Node getAttachmentsNode(Geometry target) { + * @param boneIndex this bone's index in its skeleton (≥0) + * @param targets a list of geometries animated by this bone's skeleton (not + * null, unaffected) + */ + Node getAttachmentsNode(int boneIndex, SafeArrayList targets) { + targetGeometry = null; + /* + * Search for a geometry animated by this particular bone. + */ + for (Geometry geometry : targets) { + Mesh mesh = geometry.getMesh(); + if (mesh != null && mesh.isAnimatedByBone(boneIndex)) { + targetGeometry = geometry; + break; + } + } + if (attachNode == null) { attachNode = new Node(name + "_attachnode"); attachNode.setUserData("AttachedBone", this); } - targetGeometry = target; return attachNode; } diff --git a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index 94948808f..82a4c6e22 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -70,15 +70,12 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl * The skeleton of the model. */ private Skeleton skeleton; + /** - * List of targets which this controller effects. - */ - private SafeArrayList targets = new SafeArrayList(Mesh.class); - /** - * Geometry with an animated mesh, for calculating attachments node - * transforms. A null means no geometry is subject to this control. + * List of geometries affected by this control. */ - private Geometry targetGeometry = null; + private SafeArrayList targets = new SafeArrayList(Geometry.class); + /** * Used to track when a mesh was updated. Meshes are only updated if they * are visible in at least one camera. @@ -128,8 +125,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl for (Material m : materials) { m.setInt("NumberOfBones", numBones); } - for (Mesh mesh : targets) { - if (mesh.isAnimated()) { + for (Geometry geometry : targets) { + Mesh mesh = geometry.getMesh(); + if (mesh != null && mesh.isAnimated()) { mesh.prepareForAnim(false); } } @@ -141,8 +139,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl m.clearParam("NumberOfBones"); } } - for (Mesh mesh : targets) { - if (mesh.isAnimated()) { + for (Geometry geometry : targets) { + Mesh mesh = geometry.getMesh(); + if (mesh != null && mesh.isAnimated()) { mesh.prepareForAnim(true); } } @@ -220,9 +219,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl */ private void findTargets(Geometry geometry) { Mesh mesh = geometry.getMesh(); - if (mesh.isAnimated()) { - targets.add(mesh); - targetGeometry = geometry; + if (mesh != null && mesh.isAnimated()) { + targets.add(geometry); materials.add(geometry.getMaterial()); } } @@ -248,10 +246,11 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl offsetMatrices = skeleton.computeSkinningMatrices(); - for (Mesh mesh : targets) { - // NOTE: This assumes that code higher up - // Already ensured those targets are animated - // otherwise a crash will happen in skin update + 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); } } @@ -325,8 +324,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl //only do this for software updates void resetToBind() { - for (Mesh mesh : targets) { - if (mesh.isAnimated()) { + 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()) { @@ -460,7 +460,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl } updateTargetsAndMaterials(spatial); - Node n = b.getAttachmentsNode(targetGeometry); + int boneIndex = skeleton.getBoneIndex(b); + Node n = b.getAttachmentsNode(boneIndex, targets); /* * Select a node to parent the attachments node. */ @@ -485,12 +486,20 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl } /** - * returns a copy of array of the targets meshes of this control + * Enumerate the target meshes of this control. * - * @return + * @return a new array */ - public Mesh[] getTargets() { - return targets.toArray(new Mesh[targets.size()]); + 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; } /** @@ -791,7 +800,6 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl */ private void updateTargetsAndMaterials(Spatial spatial) { targets.clear(); - targetGeometry = null; materials.clear(); if (spatial instanceof Node) { diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java index 84c279536..68b153633 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2017 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -1408,6 +1408,45 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { getBuffer(Type.HWBoneIndex) != null; } + /** + * Test whether the specified bone animates this mesh. + * + * @param boneIndex the bone's index in its skeleton + * @return true if the specified bone animates this mesh, otherwise false + */ + public boolean isAnimatedByBone(int boneIndex) { + VertexBuffer biBuf = getBuffer(VertexBuffer.Type.BoneIndex); + VertexBuffer wBuf = getBuffer(VertexBuffer.Type.BoneWeight); + if (biBuf == null || wBuf == null) { + return false; // no bone animation data + } + + ByteBuffer boneIndexBuffer = (ByteBuffer) biBuf.getData(); + boneIndexBuffer.rewind(); + int numBoneIndices = boneIndexBuffer.remaining(); + assert numBoneIndices % 4 == 0 : numBoneIndices; + int numVertices = boneIndexBuffer.remaining() / 4; + + FloatBuffer weightBuffer = (FloatBuffer) wBuf.getData(); + weightBuffer.rewind(); + int numWeights = weightBuffer.remaining(); + assert numWeights == numVertices * 4 : numWeights; + /* + * Test each vertex to determine whether the bone affects it. + */ + byte biByte = (byte) boneIndex; // bone indices wrap after 127 + for (int vIndex = 0; vIndex < numVertices; vIndex++) { + for (int wIndex = 0; wIndex < 4; wIndex++) { + byte bIndex = boneIndexBuffer.get(); + float weight = weightBuffer.get(); + if (wIndex < maxNumWeights && bIndex == biByte && weight != 0f) { + return true; + } + } + } + return false; + } + /** * Sets the count of vertices used for each tessellation patch * @param patchVertexCount