@ -1,16 +1,17 @@
package com.jme3.scene.plugins.blender.modifiers ;
import java.nio.ByteBuffer ;
import java.nio.FloatBuffer ;
import java.util.ArrayList ;
import java.util.HashMap ;
import java.util.List ;
import java.util.Map.Entry ;
import java.util.Map ;
import java.util.Set ;
import com.jme3.animation.AnimControl ;
import com.jme3.animation.Animation ;
import com.jme3.animation.Bone ;
import com.jme3.animation.BoneAnimation ;
import com.jme3.animation.BoneTrack ;
import com.jme3.animation.Skeleton ;
import com.jme3.animation.SkeletonControl ;
import com.jme3.math.Matrix4f ;
@ -18,16 +19,23 @@ import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh ;
import com.jme3.scene.Node ;
import com.jme3.scene.Spatial ;
import com.jme3.scene.VertexBuffer ;
import com.jme3.scene.VertexBuffer.Format ;
import com.jme3.scene.VertexBuffer.Type ;
import com.jme3.scene.VertexBuffer.Usage ;
import com.jme3.scene.plugins.blender.DataRepository ;
import com.jme3.scene.plugins.blender.DataRepository.LoadedFeatureDataType ;
import com.jme3.scene.plugins.blender.animations.ArmatureHelper ;
import com.jme3.scene.plugins.blender.animations.ArmatureHelper.BoneTransformationData ;
import com.jme3.scene.plugins.blender.constraints.Constraint ;
import com.jme3.scene.plugins.blender.exceptions.BlenderFileException ;
import com.jme3.scene.plugins.blender.file.FileBlockHeader ;
import com.jme3.scene.plugins.blender.file.Pointer ;
import com.jme3.scene.plugins.blender.file.Structure ;
import com.jme3.scene.plugins.blender.meshes.MeshHelper.VertexData ;
import com.jme3.scene.plugins.blender.objects.ObjectHelper ;
import com.jme3.scene.plugins.ogre.AnimData ;
import com.jme3.util.BufferUtils ;
/ * *
* This modifier allows to add bone animation to the object .
@ -35,7 +43,19 @@ import com.jme3.scene.plugins.ogre.AnimData;
* @author Marcin Roguski ( Kaelthas )
* /
/* package */ class ArmatureModifier extends Modifier {
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4 ; // have no idea why 4, could someone please explain ?
/** Old memory address of the armature's object. */
private Long armatureObjectOMA ;
/** Old memory address of the mesh that will have the skeleton applied. */
private Long meshOMA ;
/** The maxiumum amount of bone groups applied to a single vertex (max = MAXIMUM_WEIGHTS_PER_VERTEX). */
private int boneGroups ;
/** The weights of vertices. */
private VertexBuffer verticesWeights ;
/** The indexes of bones applied to vertices. */
private VertexBuffer verticesWeightsIndices ;
/ * *
* This constructor is only temporary . It will be removed when object
* animation is implemented in jme . TODO ! ! ! ! ! ! !
@ -61,29 +81,31 @@ import com.jme3.scene.plugins.ogre.AnimData;
Pointer pArmatureObject = ( Pointer ) modifierStructure . getFieldValue ( "object" ) ;
if ( pArmatureObject . isNotNull ( ) ) {
ObjectHelper objectHelper = dataRepository . getHelper ( ObjectHelper . class ) ;
Structure armatureObject = ( Structure ) dataRepository . getLoadedFeature ( pArmatureObject . getOldMemoryAddress ( ) ,
LoadedFeatureDataType . LOADED_STRUCTURE ) ;
if ( armatureObject = = null ) { // we check this first not to fetch the
// structure unnecessary
armatureObject = pArmatureObject . fetchData ( dataRepository . getInputStream ( ) ) . get ( 0 ) ;
objectHelper . toObject ( armatureObject , dataRepository ) ;
}
additionalData = armatureObject . getOldMemoryAddress ( ) ;
ArmatureHelper armatureHelper = dataRepository
. getHelper ( ArmatureHelper . class ) ;
ArmatureHelper armatureHelper = dataRepository . getHelper ( ArmatureHelper . class ) ;
Structure armatureObject = pArmatureObject . fetchData ( dataRepository . getInputStream ( ) ) . get ( 0 ) ;
this . armatureObjectOMA = armatureObject . getOldMemoryAddress ( ) ;
// changing bones matrices so that they fit the current object (that
// is why we need a copy of a skeleton)
//read skeleton
// changing bones matrices so that they fit the current object
Structure armatureStructure = ( ( Pointer ) armatureObject . getFieldValue ( "data" ) ) . fetchData ( dataRepository . getInputStream ( ) ) . get ( 0 ) ;
Structure bonebase = ( Structure ) armatureStructure . getFieldValue ( "bonebase" ) ;
List < Structure > bonesStructures = bonebase . evaluateListBase ( dataRepository ) ;
for ( Structure boneStructure : bonesStructures ) {
BoneTransformationData rootBoneTransformationData = armatureHelper . readBoneAndItsChildren ( boneStructure , null , dataRepository ) ;
armatureHelper . addBoneDataRoot ( rootBoneTransformationData ) ;
}
Matrix4f armatureObjectMatrix = objectHelper . getTransformationMatrix ( armatureObject ) ;
Matrix4f inverseMeshObjectMatrix = objectHelper . getTransformationMatrix ( objectStructure ) . invert ( ) ;
Matrix4f additionalRootBoneTransformation = inverseMeshObjectMatrix . multLocal ( armatureObjectMatrix ) ;
Bone [ ] bones = armatureHelper . buildBonesStructure ( Long . valueOf ( 0L ) , additionalRootBoneTransformation ) ;
// setting the bones structure inside the skeleton (thus completing
// its loading)
Skeleton skeleton = new Skeleton ( bones ) ;
dataRepository . addLoadedFeatures ( armatureObject . getOldMemoryAddress ( ) , armatureObject . getName ( ) , armatureObject , skeleton ) ;
//read mesh indexes
Structure meshStructure = ( ( Pointer ) objectStructure . getFieldValue ( "data" ) ) . fetchData ( dataRepository . getInputStream ( ) ) . get ( 0 ) ;
this . meshOMA = meshStructure . getOldMemoryAddress ( ) ;
this . readVerticesWeightsData ( objectStructure , meshStructure , dataRepository ) ;
//read animations
String objectName = objectStructure . getName ( ) ;
Set < String > animationNames = dataRepository . getBlenderKey ( ) . getAnimationNames ( objectName ) ;
if ( animationNames ! = null & & animationNames . size ( ) > 0 ) {
@ -106,17 +128,29 @@ import com.jme3.scene.plugins.ogre.AnimData;
}
}
}
@Override
@SuppressWarnings ( "unchecked" )
public Node apply ( Node node , DataRepository dataRepository ) {
if ( jmeModifierRepresentation = = null ) {
return node ;
}
// setting weights for bones
List < Geometry > geomList = ( List < Geometry > ) dataRepository . getLoadedFeature ( this . meshOMA , LoadedFeatureDataType . LOADED_FEATURE ) ;
for ( Geometry geom : geomList ) {
Mesh mesh = geom . getMesh ( ) ;
if ( this . verticesWeights ! = null ) {
mesh . setMaxNumWeights ( this . boneGroups ) ;
mesh . setBuffer ( this . verticesWeights ) ;
mesh . setBuffer ( this . verticesWeightsIndices ) ;
}
}
AnimData ad = ( AnimData ) jmeModifierRepresentation ;
ArrayList < Animation > animList = ad . anims ;
Long modifierArmatureObject = ( Long ) additionalData ;
if ( animList ! = null & & animList . size ( ) > 0 ) {
List < Constraint > constraints = dataRepository . getConstraints ( modifierArmatureObject ) ;
List < Constraint > constraints = dataRepository . getConstraints ( this . armatureObjectOMA ) ;
HashMap < String , Animation > anims = new HashMap < String , Animation > ( ) ;
for ( int i = 0 ; i < animList . size ( ) ; + + i ) {
BoneAnimation boneAnimation = ( BoneAnimation ) animList . get ( i ) . clone ( ) ;
@ -146,72 +180,166 @@ import com.jme3.scene.plugins.ogre.AnimData;
// applying the control to the node
SkeletonControl skeletonControl = new SkeletonControl ( meshes , ad . skeleton ) ;
AnimControl control = node . getControl ( AnimControl . class ) ;
if ( control = = null ) {
control = new AnimControl ( ad . skeleton ) ;
} else {
// merging skeletons
Skeleton controlSkeleton = control . getSkeleton ( ) ;
int boneIndexIncrease = controlSkeleton . getBoneCount ( ) ;
Skeleton skeleton = this . merge ( controlSkeleton , ad . skeleton ) ;
// merging animations
HashMap < String , Animation > animations = new HashMap < String , Animation > ( ) ;
for ( String animationName : control . getAnimationNames ( ) ) {
animations . put ( animationName ,
control . getAnim ( animationName ) ) ;
}
for ( Entry < String , Animation > animEntry : anims . entrySet ( ) ) {
BoneAnimation ba = ( BoneAnimation ) animEntry . getValue ( ) ;
for ( int i = 0 ; i < ba . getTracks ( ) . length ; + + i ) {
BoneTrack bt = ba . getTracks ( ) [ i ] ;
int newBoneIndex = bt . getTargetBoneIndex ( )
+ boneIndexIncrease ;
ba . getTracks ( ) [ i ] = new BoneTrack ( newBoneIndex ,
bt . getTimes ( ) , bt . getTranslations ( ) ,
bt . getRotations ( ) , bt . getScales ( ) ) ;
}
animations . put ( animEntry . getKey ( ) , animEntry . getValue ( ) ) ;
}
AnimControl control = new AnimControl ( ad . skeleton ) ;
// replacing the control
node . removeControl ( control ) ;
control = new AnimControl ( skeleton ) ;
}
control . setAnimations ( anims ) ;
node . addControl ( control ) ;
node . addControl ( skeletonControl ) ;
}
return node ;
}
/ * *
* This method reads mesh indexes
* @param objectStructure structure of the object that has the armature modifier applied
* @param meshStructure the structure of the object ' s mesh
* @param dataRepository the data repository
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
* /
private void readVerticesWeightsData ( Structure objectStructure , Structure meshStructure , DataRepository dataRepository ) throws BlenderFileException {
ArmatureHelper armatureHelper = dataRepository . getHelper ( ArmatureHelper . class ) ;
Structure defBase = ( Structure ) objectStructure . getFieldValue ( "defbase" ) ;
Map < Integer , Integer > groupToBoneIndexMap = armatureHelper . getGroupToBoneIndexMap ( defBase , dataRepository ) ;
@Override
public String getType ( ) {
return Modifier . ARMATURE_MODIFIER_DATA ;
int [ ] bonesGroups = new int [ ] { 0 } ;
VertexData vertexData = dataRepository . getVertexData ( meshStructure . getOldMemoryAddress ( ) ) ;
VertexBuffer [ ] boneWeightsAndIndex = this . getBoneWeightAndIndexBuffer ( meshStructure , vertexData . getVertexList ( ) . size ( ) , bonesGroups ,
vertexData . getVertexReferenceMap ( ) , groupToBoneIndexMap , dataRepository ) ;
this . verticesWeights = boneWeightsAndIndex [ 0 ] ;
this . verticesWeightsIndices = boneWeightsAndIndex [ 1 ] ;
this . boneGroups = bonesGroups [ 0 ] ;
}
/ * *
* This method merges two skeletons into one . I assume that each skeleton ' s
* 0 - indexed bone is objectAnimationBone so only one such bone should be
* placed in the result
* This method returns an array of size 2 . The first element is a vertex buffer holding bone weights for every vertex in the model . The
* second element is a vertex buffer holding bone indices for vertices ( the indices of bones the vertices are assigned to ) .
*
* @param s1
* first skeleton
* @param s2
* second skeleton
* @return merged skeleton
* @param meshStructure
* the mesh structure object
* @param vertexListSize
* a number of vertices in the model
* @param bonesGroups
* this is an output parameter , it should be a one - sized array ; the maximum amount of weights per vertex ( up to
* MAXIMUM_WEIGHTS_PER_VERTEX ) is stored there
* @param vertexReferenceMap
* this reference map allows to map the original vertices read from blender to vertices that are really in the model ; one
* vertex may appear several times in the result model
* @param groupToBoneIndexMap
* this object maps the group index ( to which a vertices in blender belong ) to bone index of the model
* @param dataRepository
* the data repository
* @return arrays of vertices weights and their bone indices and ( as an output parameter ) the maximum amount of weights for a vertex
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
* /
protected Skeleton merge ( Skeleton s1 , Skeleton s2 ) {
List < Bone > bones = new ArrayList < Bone > ( s1 . getBoneCount ( )
+ s2 . getBoneCount ( ) ) ;
for ( int i = 0 ; i < s1 . getBoneCount ( ) ; + + i ) {
bones . add ( s1 . getBone ( i ) ) ;
private VertexBuffer [ ] getBoneWeightAndIndexBuffer ( Structure meshStructure , int vertexListSize , int [ ] bonesGroups ,
Map < Integer , List < Integer > > vertexReferenceMap , Map < Integer , Integer > groupToBoneIndexMap , DataRepository dataRepository )
throws BlenderFileException {
Pointer pDvert = ( Pointer ) meshStructure . getFieldValue ( "dvert" ) ; // dvert = DeformVERTices
FloatBuffer weightsFloatData = BufferUtils . createFloatBuffer ( vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX ) ;
ByteBuffer indicesData = BufferUtils . createByteBuffer ( vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX ) ;
if ( pDvert . isNotNull ( ) ) { // assigning weights and bone indices
List < Structure > dverts = pDvert . fetchData ( dataRepository . getInputStream ( ) ) ; // dverts.size() == verticesAmount (one dvert per
// vertex in blender)
int vertexIndex = 0 ;
for ( Structure dvert : dverts ) {
int totweight = ( ( Number ) dvert . getFieldValue ( "totweight" ) ) . intValue ( ) ; // total amount of weights assignet to the vertex
// (max. 4 in JME)
Pointer pDW = ( Pointer ) dvert . getFieldValue ( "dw" ) ;
List < Integer > vertexIndices = vertexReferenceMap . get ( Integer . valueOf ( vertexIndex ) ) ; // we fetch the referenced vertices here
if ( totweight > 0 & & pDW . isNotNull ( ) & & groupToBoneIndexMap ! = null ) { // pDW should never be null here, but I check it just in case :)
int weightIndex = 0 ;
List < Structure > dw = pDW . fetchData ( dataRepository . getInputStream ( ) ) ;
for ( Structure deformWeight : dw ) {
Integer boneIndex = groupToBoneIndexMap . get ( ( ( Number ) deformWeight . getFieldValue ( "def_nr" ) ) . intValue ( ) ) ;
if ( boneIndex ! = null ) { // null here means that we came accross group that has no bone attached to
float weight = ( ( Number ) deformWeight . getFieldValue ( "weight" ) ) . floatValue ( ) ;
if ( weight = = 0 . 0f ) {
weight = 1 ;
boneIndex = Integer . valueOf ( 0 ) ;
}
// we apply the weight to all referenced vertices
for ( Integer index : vertexIndices ) {
weightsFloatData . put ( index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex , weight ) ;
indicesData . put ( index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex , boneIndex . byteValue ( ) ) ;
}
}
+ + weightIndex ;
}
} else {
for ( Integer index : vertexIndices ) {
weightsFloatData . put ( index * MAXIMUM_WEIGHTS_PER_VERTEX , 1 . 0f ) ;
indicesData . put ( index * MAXIMUM_WEIGHTS_PER_VERTEX , ( byte ) 0 ) ;
}
}
+ + vertexIndex ;
}
} else {
// always bind all vertices to 0-indexed bone
// this bone makes the model look normally if vertices have no bone assigned
// and it is used in object animation, so if we come accross object animation
// we can use the 0-indexed bone for this
for ( List < Integer > vertexIndexList : vertexReferenceMap . values ( ) ) {
// we apply the weight to all referenced vertices
for ( Integer index : vertexIndexList ) {
weightsFloatData . put ( index * MAXIMUM_WEIGHTS_PER_VERTEX , 1 . 0f ) ;
indicesData . put ( index * MAXIMUM_WEIGHTS_PER_VERTEX , ( byte ) 0 ) ;
}
}
}
for ( int i = 1 ; i < s2 . getBoneCount ( ) ; + + i ) { // ommit
// objectAnimationBone
bones . add ( s2 . getBone ( i ) ) ;
bonesGroups [ 0 ] = this . endBoneAssigns ( vertexListSize , weightsFloatData ) ;
VertexBuffer verticesWeights = new VertexBuffer ( Type . BoneWeight ) ;
verticesWeights . setupData ( Usage . CpuOnly , bonesGroups [ 0 ] , Format . Float , weightsFloatData ) ;
VertexBuffer verticesWeightsIndices = new VertexBuffer ( Type . BoneIndex ) ;
verticesWeightsIndices . setupData ( Usage . CpuOnly , bonesGroups [ 0 ] , Format . UnsignedByte , indicesData ) ;
return new VertexBuffer [ ] { verticesWeights , verticesWeightsIndices } ;
}
/ * *
* Normalizes weights if needed and finds largest amount of weights used for all vertices in the buffer .
* @param vertCount amount of vertices
* @param weightsFloatData weights for vertices
* /
private int endBoneAssigns ( int vertCount , FloatBuffer weightsFloatData ) {
int maxWeightsPerVert = 0 ;
weightsFloatData . rewind ( ) ;
for ( int v = 0 ; v < vertCount ; + + v ) {
float w0 = weightsFloatData . get ( ) , w1 = weightsFloatData . get ( ) , w2 = weightsFloatData . get ( ) , w3 = weightsFloatData . get ( ) ;
if ( w3 ! = 0 ) {
maxWeightsPerVert = Math . max ( maxWeightsPerVert , 4 ) ;
} else if ( w2 ! = 0 ) {
maxWeightsPerVert = Math . max ( maxWeightsPerVert , 3 ) ;
} else if ( w1 ! = 0 ) {
maxWeightsPerVert = Math . max ( maxWeightsPerVert , 2 ) ;
} else if ( w0 ! = 0 ) {
maxWeightsPerVert = Math . max ( maxWeightsPerVert , 1 ) ;
}
float sum = w0 + w1 + w2 + w3 ;
if ( sum ! = 1f & & sum ! = 0 . 0f ) {
weightsFloatData . position ( weightsFloatData . position ( ) - 4 ) ;
// compute new vals based on sum
float sumToB = 1f / sum ;
weightsFloatData . put ( w0 * sumToB ) ;
weightsFloatData . put ( w1 * sumToB ) ;
weightsFloatData . put ( w2 * sumToB ) ;
weightsFloatData . put ( w3 * sumToB ) ;
}
}
return new Skeleton ( bones . toArray ( new Bone [ bones . size ( ) ] ) ) ;
weightsFloatData . rewind ( ) ;
// mesh.setMaxNumWeights(maxWeightsPerVert);
return maxWeightsPerVert ;
}
@Override
public String getType ( ) {
return Modifier . ARMATURE_MODIFIER_DATA ;
}
}