1package com.jme3.scene.plugins.blender.animations;
2
3import java.util.ArrayList;
4import java.util.List;
5import java.util.Map;
6
7import com.jme3.animation.Bone;
8import com.jme3.math.Matrix4f;
9import com.jme3.math.Quaternion;
10import com.jme3.math.Transform;
11import com.jme3.math.Vector3f;
12import com.jme3.scene.plugins.blender.BlenderContext;
13import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
14import com.jme3.scene.plugins.blender.file.DynamicArray;
15import com.jme3.scene.plugins.blender.file.Structure;
16import com.jme3.scene.plugins.blender.objects.ObjectHelper;
17
18/**
19 * This class holds the basic data that describes a bone.
20 *
21 * @author Marcin Roguski (Kaelthas)
22 */
23public class BoneContext {
24	/** The structure of the bone. */
25	private Structure			boneStructure;
26	/** Bone's pose channel structure. */
27	private Structure			poseChannel;
28	/** Bone's name. */
29	private String				boneName;
30	/** This variable indicates if the Y axis should be the UP axis. */
31	private boolean				fixUpAxis;
32	/** The bone's armature matrix. */
33	private Matrix4f			armatureMatrix;
34	/** The parent context. */
35	private BoneContext			parent;
36	/** The children of this context. */
37	private List<BoneContext>	children		= new ArrayList<BoneContext>();
38	/** Created bone (available after calling 'buildBone' method). */
39	private Bone				bone;
40	/** Bone's pose transform (available after calling 'buildBone' method). */
41	private Transform			poseTransform	= new Transform();
42	/** The bone's rest matrix. */
43	private Matrix4f			restMatrix;
44	/** Bone's total inverse transformation. */
45	private Matrix4f			inverseTotalTransformation;
46	/** Bone's parent inverse matrix. */
47	private Matrix4f			inverseParentMatrix;
48
49	/**
50	 * Constructor. Creates the basic set of bone's data.
51	 *
52	 * @param boneStructure
53	 *            the bone's structure
54	 * @param objectToArmatureMatrix
55	 *            object-to-armature transformation matrix
56	 * @param bonesPoseChannels
57	 *            a map of pose channels for each bone OMA
58	 * @param blenderContext
59	 *            the blender context
60	 * @throws BlenderFileException
61	 *             an exception is thrown when problem with blender data reading
62	 *             occurs
63	 */
64	public BoneContext(Structure boneStructure, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
65		this(boneStructure, null, objectToArmatureMatrix, bonesPoseChannels, blenderContext);
66	}
67
68	/**
69	 * Constructor. Creates the basic set of bone's data.
70	 *
71	 * @param boneStructure
72	 *            the bone's structure
73	 * @param parent
74	 *            bone's parent (null if the bone is the root bone)
75	 * @param objectToArmatureMatrix
76	 *            object-to-armature transformation matrix
77	 * @param bonesPoseChannels
78	 *            a map of pose channels for each bone OMA
79	 * @param blenderContext
80	 *            the blender context
81	 * @throws BlenderFileException
82	 *             an exception is thrown when problem with blender data reading
83	 *             occurs
84	 */
85	private BoneContext(Structure boneStructure, BoneContext parent, Matrix4f objectToArmatureMatrix, final Map<Long, Structure> bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException {
86		this.parent = parent;
87		this.boneStructure = boneStructure;
88		boneName = boneStructure.getFieldValue("name").toString();
89		ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
90		armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", true);
91
92		fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();
93		this.computeRestMatrix(objectToArmatureMatrix);
94		List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext);
95		for (Structure child : childbase) {
96			this.children.add(new BoneContext(child, this, objectToArmatureMatrix, bonesPoseChannels, blenderContext));
97		}
98
99		poseChannel = bonesPoseChannels.get(boneStructure.getOldMemoryAddress());
100
101		blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this);
102	}
103
104	/**
105	 * This method computes the rest matrix for the bone.
106	 *
107	 * @param objectToArmatureMatrix
108	 *            object-to-armature transformation matrix
109	 */
110	private void computeRestMatrix(Matrix4f objectToArmatureMatrix) {
111		if (parent != null) {
112			inverseParentMatrix = parent.inverseTotalTransformation.clone();
113		} else if (fixUpAxis) {
114			inverseParentMatrix = objectToArmatureMatrix.clone();
115		} else {
116			inverseParentMatrix = Matrix4f.IDENTITY.clone();
117		}
118
119		restMatrix = armatureMatrix.clone();
120		inverseTotalTransformation = restMatrix.invert();
121
122		restMatrix = inverseParentMatrix.mult(restMatrix);
123
124		for (BoneContext child : this.children) {
125			child.computeRestMatrix(objectToArmatureMatrix);
126		}
127	}
128
129	/**
130	 * This method computes the pose transform for the bone.
131	 */
132	@SuppressWarnings("unchecked")
133	private void computePoseTransform() {
134		DynamicArray<Number> loc = (DynamicArray<Number>) poseChannel.getFieldValue("loc");
135		DynamicArray<Number> size = (DynamicArray<Number>) poseChannel.getFieldValue("size");
136		DynamicArray<Number> quat = (DynamicArray<Number>) poseChannel.getFieldValue("quat");
137		if (fixUpAxis) {
138			poseTransform.setTranslation(loc.get(0).floatValue(), -loc.get(2).floatValue(), loc.get(1).floatValue());
139			poseTransform.setRotation(new Quaternion(quat.get(1).floatValue(), quat.get(3).floatValue(), -quat.get(2).floatValue(), quat.get(0).floatValue()));
140			poseTransform.setScale(size.get(0).floatValue(), size.get(2).floatValue(), size.get(1).floatValue());
141		} else {
142			poseTransform.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue());
143			poseTransform.setRotation(new Quaternion(quat.get(0).floatValue(), quat.get(1).floatValue(), quat.get(2).floatValue(), quat.get(3).floatValue()));
144			poseTransform.setScale(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
145		}
146
147		Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
148		localTransform.setScale(bone.getLocalScale());
149		localTransform.getTranslation().addLocal(poseTransform.getTranslation());
150		localTransform.getRotation().multLocal(poseTransform.getRotation());
151		localTransform.getScale().multLocal(poseTransform.getScale());
152
153		poseTransform.set(localTransform);
154	}
155
156	/**
157	 * This method builds the bone. It recursively builds the bone's children.
158	 *
159	 * @param bones
160	 *            a list of bones where the newly created bone will be added
161	 * @param boneOMAs
162	 *            the map between bone and its old memory address
163	 * @param blenderContext
164	 *            the blender context
165	 * @return newly created bone
166	 */
167	public Bone buildBone(List<Bone> bones, Map<Bone, Long> boneOMAs, BlenderContext blenderContext) {
168		Long boneOMA = boneStructure.getOldMemoryAddress();
169		bone = new Bone(boneName);
170		bones.add(bone);
171		boneOMAs.put(bone, boneOMA);
172		blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone);
173
174		Matrix4f pose = this.restMatrix.clone();
175		ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
176
177		Vector3f poseLocation = pose.toTranslationVector();
178		Quaternion rotation = pose.toRotationQuat();
179		Vector3f scale = objectHelper.getScale(pose);
180
181		bone.setBindTransforms(poseLocation, rotation, scale);
182		for (BoneContext child : children) {
183			bone.addChild(child.buildBone(bones, boneOMAs, blenderContext));
184		}
185
186		this.computePoseTransform();
187
188		return bone;
189	}
190
191	/**
192	 * @return bone's pose transformation
193	 */
194	public Transform getPoseTransform() {
195		return poseTransform;
196	}
197
198	/**
199	 * @return built bone (available after calling 'buildBone' method)
200	 */
201	public Bone getBone() {
202		return bone;
203	}
204}
205