Home | History | Annotate | Download | only in modifiers
      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 	// Rmy
     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