1package com.jme3.scene.plugins.blender.modifiers;
2
3import java.nio.ByteBuffer;
4import java.nio.FloatBuffer;
5import java.util.ArrayList;
6import java.util.HashMap;
7import java.util.List;
8import java.util.Map;
9import java.util.logging.Level;
10import java.util.logging.Logger;
11
12import com.jme3.animation.AnimControl;
13import com.jme3.animation.Animation;
14import com.jme3.animation.Bone;
15import com.jme3.animation.BoneTrack;
16import com.jme3.animation.Skeleton;
17import com.jme3.animation.SkeletonControl;
18import com.jme3.math.Matrix4f;
19import com.jme3.scene.Geometry;
20import com.jme3.scene.Mesh;
21import com.jme3.scene.Node;
22import com.jme3.scene.VertexBuffer;
23import com.jme3.scene.VertexBuffer.Format;
24import com.jme3.scene.VertexBuffer.Type;
25import com.jme3.scene.VertexBuffer.Usage;
26import com.jme3.scene.plugins.blender.BlenderContext;
27import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
28import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
29import com.jme3.scene.plugins.blender.constraints.Constraint;
30import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
31import com.jme3.scene.plugins.blender.file.FileBlockHeader;
32import com.jme3.scene.plugins.blender.file.Pointer;
33import com.jme3.scene.plugins.blender.file.Structure;
34import com.jme3.scene.plugins.blender.meshes.MeshContext;
35import com.jme3.scene.plugins.blender.objects.ObjectHelper;
36import com.jme3.scene.plugins.ogre.AnimData;
37import com.jme3.util.BufferUtils;
38
39/**
40 * This modifier allows to add bone animation to the object.
41 *
42 * @author Marcin Roguski (Kaelthas)
43 */
44/* package */class ArmatureModifier extends Modifier {
45	private static final Logger	LOGGER						= Logger.getLogger(ArmatureModifier.class.getName());
46	private static final int	MAXIMUM_WEIGHTS_PER_VERTEX	= 4;
47	// @Marcin it was an Ogre limitation, but as long as we use a MaxNumWeight
48	// variable in mesh,
49	// i guess this limitation has no sense for the blender loader...so i guess
50	// it's up to you. You'll have to deternine the max weight according to the
51	// provided blend file
52	// I added a check to avoid crash when loading a model that has more than 4
53	// weight per vertex on line 258
54	// If you decide to remove this limitation, remove this code.
55	// Rémy
56
57	/** Loaded animation data. */
58	private AnimData			animData;
59	/** Old memory address of the mesh that will have the skeleton applied. */
60	private Long				meshOMA;
61	/**
62	 * The maxiumum amount of bone groups applied to a single vertex (max =
63	 * MAXIMUM_WEIGHTS_PER_VERTEX).
64	 */
65	private int					boneGroups;
66	/** The weights of vertices. */
67	private VertexBuffer		verticesWeights;
68	/** The indexes of bones applied to vertices. */
69	private VertexBuffer		verticesWeightsIndices;
70
71	/**
72	 * This constructor reads animation data from the object structore. The
73	 * stored data is the AnimData and additional data is armature's OMA.
74	 *
75	 * @param objectStructure
76	 *            the structure of the object
77	 * @param modifierStructure
78	 *            the structure of the modifier
79	 * @param blenderContext
80	 *            the blender context
81	 * @throws BlenderFileException
82	 *             this exception is thrown when the blender file is somehow
83	 *             corrupted
84	 */
85	public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
86		Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);
87		Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert
88																		// =
89																		// DeformVERTices
90
91		// if pDvert==null then there are not vertex groups and no need to load
92		// skeleton (untill bone envelopes are supported)
93		if (this.validate(modifierStructure, blenderContext) && pDvert.isNotNull()) {
94			Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
95			if (pArmatureObject.isNotNull()) {
96				ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);
97
98				Structure armatureObject = pArmatureObject.fetchData(blenderContext.getInputStream()).get(0);
99
100				// load skeleton
101				Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);
102
103				Structure pose = ((Pointer) armatureObject.getFieldValue("pose")).fetchData(blenderContext.getInputStream()).get(0);
104				List<Structure> chanbase = ((Structure) pose.getFieldValue("chanbase")).evaluateListBase(blenderContext);
105
106				Map<Long, Structure> bonesPoseChannels = new HashMap<Long, Structure>(chanbase.size());
107				for (Structure poseChannel : chanbase) {
108					Pointer pBone = (Pointer) poseChannel.getFieldValue("bone");
109					bonesPoseChannels.put(pBone.getOldMemoryAddress(), poseChannel);
110				}
111
112				ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
113				Matrix4f armatureObjectMatrix = objectHelper.getMatrix(armatureObject, "obmat", true);
114				Matrix4f inverseMeshObjectMatrix = objectHelper.getMatrix(objectStructure, "obmat", true).invertLocal();
115				Matrix4f objectToArmatureTransformation = armatureObjectMatrix.multLocal(inverseMeshObjectMatrix);
116
117				List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(blenderContext);
118				List<Bone> bonesList = new ArrayList<Bone>();
119				for (int i = 0; i < bonebase.size(); ++i) {
120					armatureHelper.buildBones(bonebase.get(i), null, bonesList, objectToArmatureTransformation, bonesPoseChannels, blenderContext);
121				}
122				bonesList.add(0, new Bone(""));
123				Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
124				Skeleton skeleton = new Skeleton(bones);
125
126				// read mesh indexes
127				this.meshOMA = meshStructure.getOldMemoryAddress();
128				this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, blenderContext);
129
130				// read animations
131				ArrayList<Animation> animations = new ArrayList<Animation>();
132				List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
133				if (actionHeaders != null) {// it may happen that the model has
134											// armature with no actions
135					for (FileBlockHeader header : actionHeaders) {
136						Structure actionStructure = header.getStructure(blenderContext);
137						String actionName = actionStructure.getName();
138
139						BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext);
140						if(tracks != null && tracks.length > 0) {
141							// determining the animation time
142							float maximumTrackLength = 0;
143							for (BoneTrack track : tracks) {
144								float length = track.getLength();
145								if (length > maximumTrackLength) {
146									maximumTrackLength = length;
147								}
148							}
149
150							Animation boneAnimation = new Animation(actionName, maximumTrackLength);
151							boneAnimation.setTracks(tracks);
152							animations.add(boneAnimation);
153						}
154					}
155				}
156				animData = new AnimData(skeleton, animations);
157
158				// store the animation data for each bone
159				for (Bone bone : bones) {
160					Long boneOma = armatureHelper.getBoneOMA(bone);
161					if (boneOma != null) {
162						blenderContext.setAnimData(boneOma, animData);
163					}
164				}
165			}
166		}
167	}
168
169	@Override
170	@SuppressWarnings("unchecked")
171	public Node apply(Node node, BlenderContext blenderContext) {
172		if (invalid) {
173			LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
174		}// if invalid, animData will be null
175		if (animData == null) {
176			return node;
177		}
178
179		// setting weights for bones
180		List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(this.meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
181		for (Geometry geom : geomList) {
182			Mesh mesh = geom.getMesh();
183			if (this.verticesWeights != null) {
184				mesh.setMaxNumWeights(this.boneGroups);
185				mesh.setBuffer(this.verticesWeights);
186				mesh.setBuffer(this.verticesWeightsIndices);
187			}
188		}
189
190		// applying constraints to Bones
191		ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);
192		for (int i = 0; i < animData.skeleton.getBoneCount(); ++i) {
193			Long boneOMA = armatureHelper.getBoneOMA(animData.skeleton.getBone(i));
194			List<Constraint> constraints = blenderContext.getConstraints(boneOMA);
195			if (constraints != null && constraints.size() > 0) {
196				for (Constraint constraint : constraints) {
197					constraint.bake();
198				}
199			}
200		}
201
202		// applying animations
203		AnimControl control = new AnimControl(animData.skeleton);
204		ArrayList<Animation> animList = animData.anims;
205		if (animList != null && animList.size() > 0) {
206			HashMap<String, Animation> anims = new HashMap<String, Animation>(animList.size());
207			for (int i = 0; i < animList.size(); ++i) {
208				Animation animation = animList.get(i);
209				anims.put(animation.getName(), animation);
210			}
211			control.setAnimations(anims);
212		}
213		node.addControl(control);
214		node.addControl(new SkeletonControl(animData.skeleton));
215
216		return node;
217	}
218
219	/**
220	 * This method reads mesh indexes
221	 *
222	 * @param objectStructure
223	 *            structure of the object that has the armature modifier applied
224	 * @param meshStructure
225	 *            the structure of the object's mesh
226	 * @param blenderContext
227	 *            the blender context
228	 * @throws BlenderFileException
229	 *             this exception is thrown when the blend file structure is
230	 *             somehow invalid or corrupted
231	 */
232	private void readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException {
233		ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);
234		Structure defBase = (Structure) objectStructure.getFieldValue("defbase");
235		Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton, blenderContext);
236
237		int[] bonesGroups = new int[] { 0 };
238		MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress());
239
240		VertexBuffer[] boneWeightsAndIndex = this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexList().size(), bonesGroups, meshContext.getVertexReferenceMap(), groupToBoneIndexMap, blenderContext);
241		this.verticesWeights = boneWeightsAndIndex[0];
242		this.verticesWeightsIndices = boneWeightsAndIndex[1];
243		this.boneGroups = bonesGroups[0];
244	}
245
246	/**
247	 * This method returns an array of size 2. The first element is a vertex
248	 * buffer holding bone weights for every vertex in the model. The second
249	 * element is a vertex buffer holding bone indices for vertices (the indices
250	 * of bones the vertices are assigned to).
251	 *
252	 * @param meshStructure
253	 *            the mesh structure object
254	 * @param vertexListSize
255	 *            a number of vertices in the model
256	 * @param bonesGroups
257	 *            this is an output parameter, it should be a one-sized array;
258	 *            the maximum amount of weights per vertex (up to
259	 *            MAXIMUM_WEIGHTS_PER_VERTEX) is stored there
260	 * @param vertexReferenceMap
261	 *            this reference map allows to map the original vertices read
262	 *            from blender to vertices that are really in the model; one
263	 *            vertex may appear several times in the result model
264	 * @param groupToBoneIndexMap
265	 *            this object maps the group index (to which a vertices in
266	 *            blender belong) to bone index of the model
267	 * @param blenderContext
268	 *            the blender context
269	 * @return arrays of vertices weights and their bone indices and (as an
270	 *         output parameter) the maximum amount of weights for a vertex
271	 * @throws BlenderFileException
272	 *             this exception is thrown when the blend file structure is
273	 *             somehow invalid or corrupted
274	 */
275	private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext)
276			throws BlenderFileException {
277		Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
278		FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
279		ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
280		if (pDvert.isNotNull()) {// assigning weights and bone indices
281			List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per
282																						// vertex in blender)
283			int vertexIndex = 0;
284			for (Structure dvert : dverts) {
285				int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex
286																						// (max. 4 in JME)
287				Pointer pDW = (Pointer) dvert.getFieldValue("dw");
288				List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here
289				if (totweight > 0 && pDW.isNotNull() && groupToBoneIndexMap!=null) {// pDW should never be null here, but I check it just in case :)
290					int weightIndex = 0;
291					List<Structure> dw = pDW.fetchData(blenderContext.getInputStream());
292					for (Structure deformWeight : dw) {
293						Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());
294
295						// Remove this code if 4 weights limitation is removed
296						if (weightIndex == 4) {
297							LOGGER.log(Level.WARNING, "{0} has more than 4 weight on bone index {1}", new Object[] { meshStructure.getName(), boneIndex });
298							break;
299						}
300
301						// null here means that we came accross group that has no bone attached to
302						if (boneIndex != null) {
303							float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
304							if (weight == 0.0f) {
305								weight = 1;
306								boneIndex = Integer.valueOf(0);
307							}
308							// we apply the weight to all referenced vertices
309							for (Integer index : vertexIndices) {
310								weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);
311								indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());
312							}
313						}
314						++weightIndex;
315					}
316				} else {
317					for (Integer index : vertexIndices) {
318						weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
319						indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
320					}
321				}
322				++vertexIndex;
323			}
324		} else {
325			// always bind all vertices to 0-indexed bone
326			// this bone makes the model look normally if vertices have no bone
327			// assigned
328			// and it is used in object animation, so if we come accross object
329			// animation
330			// we can use the 0-indexed bone for this
331			for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {
332				// we apply the weight to all referenced vertices
333				for (Integer index : vertexIndexList) {
334					weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
335					indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
336				}
337			}
338		}
339
340		bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData);
341		VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
342		verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);
343
344		VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
345		verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData);
346		return new VertexBuffer[] { verticesWeights, verticesWeightsIndices };
347	}
348
349	/**
350	 * Normalizes weights if needed and finds largest amount of weights used for
351	 * all vertices in the buffer.
352	 *
353	 * @param vertCount
354	 *            amount of vertices
355	 * @param weightsFloatData
356	 *            weights for vertices
357	 */
358	private int endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {
359		int maxWeightsPerVert = 0;
360		weightsFloatData.rewind();
361		for (int v = 0; v < vertCount; ++v) {
362			float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get();
363
364			if (w3 != 0) {
365				maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
366			} else if (w2 != 0) {
367				maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
368			} else if (w1 != 0) {
369				maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
370			} else if (w0 != 0) {
371				maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
372			}
373
374			float sum = w0 + w1 + w2 + w3;
375			if (sum != 1f && sum != 0.0f) {
376				weightsFloatData.position(weightsFloatData.position() - 4);
377				// compute new vals based on sum
378				float sumToB = 1f / sum;
379				weightsFloatData.put(w0 * sumToB);
380				weightsFloatData.put(w1 * sumToB);
381				weightsFloatData.put(w2 * sumToB);
382				weightsFloatData.put(w3 * sumToB);
383			}
384		}
385		weightsFloatData.rewind();
386		return maxWeightsPerVert;
387	}
388
389	@Override
390	public String getType() {
391		return Modifier.ARMATURE_MODIFIER_DATA;
392	}
393}
394