• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.jme3.scene.plugins.blender.modifiers;
2 
3 import java.nio.ByteBuffer;
4 import java.nio.FloatBuffer;
5 import java.util.ArrayList;
6 import java.util.HashMap;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.logging.Level;
10 import java.util.logging.Logger;
11 
12 import com.jme3.animation.AnimControl;
13 import com.jme3.animation.Animation;
14 import com.jme3.animation.Bone;
15 import com.jme3.animation.BoneTrack;
16 import com.jme3.animation.Skeleton;
17 import com.jme3.animation.SkeletonControl;
18 import com.jme3.math.Matrix4f;
19 import com.jme3.scene.Geometry;
20 import com.jme3.scene.Mesh;
21 import com.jme3.scene.Node;
22 import com.jme3.scene.VertexBuffer;
23 import com.jme3.scene.VertexBuffer.Format;
24 import com.jme3.scene.VertexBuffer.Type;
25 import com.jme3.scene.VertexBuffer.Usage;
26 import com.jme3.scene.plugins.blender.BlenderContext;
27 import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
28 import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
29 import com.jme3.scene.plugins.blender.constraints.Constraint;
30 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
31 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
32 import com.jme3.scene.plugins.blender.file.Pointer;
33 import com.jme3.scene.plugins.blender.file.Structure;
34 import com.jme3.scene.plugins.blender.meshes.MeshContext;
35 import com.jme3.scene.plugins.blender.objects.ObjectHelper;
36 import com.jme3.scene.plugins.ogre.AnimData;
37 import 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 	 */
ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext)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")
apply(Node node, BlenderContext blenderContext)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 	 */
readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, BlenderContext blenderContext)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 	 */
getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext)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 	 */
endBoneAssigns(int vertCount, FloatBuffer weightsFloatData)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
getType()390 	public String getType() {
391 		return Modifier.ARMATURE_MODIFIER_DATA;
392 	}
393 }
394