1package com.jme3.scene.plugins.blender.constraints;
2
3import com.jme3.animation.Bone;
4import com.jme3.math.Matrix4f;
5import com.jme3.math.Quaternion;
6import com.jme3.math.Transform;
7import com.jme3.math.Vector3f;
8import com.jme3.scene.Spatial;
9import com.jme3.scene.plugins.blender.BlenderContext;
10import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
11import com.jme3.scene.plugins.blender.constraints.Constraint.Space;
12import com.jme3.scene.plugins.blender.file.DynamicArray;
13import com.jme3.scene.plugins.blender.file.Structure;
14
15/**
16 * This class represents either owner or target of the constraint. It has the
17 * common methods that take the evalueation space of the feature.
18 *
19 * @author Marcin Roguski (Kaelthas)
20 */
21/* package */class Feature {
22	/** The evalueation space. */
23	protected Space				space;
24	/** Old memory address of the feature. */
25	protected Long				oma;
26	/** The spatial that is hold by the Feature. */
27	protected Spatial			spatial;
28	/** The bone that is hold by the Feature. */
29	protected Bone				bone;
30	/** The blender context. */
31	protected BlenderContext	blenderContext;
32
33	/**
34	 * Constructs the feature. This object should be loaded later
35	 * when it is read from the blender file.
36	 * The update method should be called before the feature is used.
37	 *
38	 * @param space
39	 *            the spatial's evaluation space
40	 * @param oma
41	 *            the spatial's old memory address
42	 * @param blenderContext
43	 *            the blender context
44	 */
45	public Feature(Space space, Long oma, BlenderContext blenderContext) {
46		this.space = space;
47		this.oma = oma;
48		this.blenderContext = blenderContext;
49	}
50
51	/**
52	 * Constructs the feature based on spatial.
53	 *
54	 * @param spatial
55	 *            the spatial
56	 * @param space
57	 *            the spatial's evaluation space
58	 * @param oma
59	 *            the spatial's old memory address
60	 * @param blenderContext
61	 *            the blender context
62	 */
63	public Feature(Spatial spatial, Space space, Long oma, BlenderContext blenderContext) {
64		this(space, oma, blenderContext);
65		this.blenderContext = blenderContext;
66	}
67
68	/**
69	 * Constructs the feature based on bone.
70	 *
71	 * @param bone
72	 *            the bone
73	 * @param space
74	 *            the bone evaluation space
75	 * @param oma
76	 *            the bone old memory address
77	 * @param blenderContext
78	 *            the blender context
79	 */
80	public Feature(Bone bone, Space space, Long oma, BlenderContext blenderContext) {
81		this(space, oma, blenderContext);
82		this.bone = bone;
83	}
84
85	/**
86	 * This method should be called before the feature is used.
87	 * It may happen that the object this feature refers to was not yet loaded from blend file
88	 * when the instance of this class was created.
89	 */
90	public void update() {
91		Object owner = blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
92		if(owner instanceof Spatial) {
93			this.spatial = (Spatial) owner;
94		} else if(owner instanceof Bone) {
95			this.bone = (Bone) owner;
96		} else {
97			throw new IllegalStateException("Unknown type of owner: " + owner.getClass());
98		}
99	}
100
101	/**
102	 * @return the feature's old memory address
103	 */
104	public Long getOma() {
105		return oma;
106	}
107
108	/**
109	 * @return the object held by the feature (either bone or spatial)
110	 */
111	public Object getObject() {
112		if (spatial != null) {
113			return spatial;
114		}
115		return bone;
116	}
117
118	/**
119	 * @return the feature's transform depending on the evaluation space
120	 */
121	@SuppressWarnings("unchecked")
122	public Transform getTransform() {
123		if (spatial != null) {
124			switch (space) {
125				case CONSTRAINT_SPACE_LOCAL:
126					Structure targetStructure = (Structure) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_STRUCTURE);
127
128					DynamicArray<Number> locArray = ((DynamicArray<Number>) targetStructure.getFieldValue("loc"));
129					Vector3f loc = new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), locArray.get(2).floatValue());
130					DynamicArray<Number> rotArray = ((DynamicArray<Number>) targetStructure.getFieldValue("rot"));
131					Quaternion rot = new Quaternion(new float[] { rotArray.get(0).floatValue(), rotArray.get(1).floatValue(), rotArray.get(2).floatValue() });
132					DynamicArray<Number> sizeArray = ((DynamicArray<Number>) targetStructure.getFieldValue("size"));
133					Vector3f size = new Vector3f(sizeArray.get(0).floatValue(), sizeArray.get(1).floatValue(), sizeArray.get(2).floatValue());
134
135					if (blenderContext.getBlenderKey().isFixUpAxis()) {
136						float y = loc.y;
137						loc.y = loc.z;
138						loc.z = -y;
139
140						y = rot.getY();
141						float z = rot.getZ();
142						rot.set(rot.getX(), z, -y, rot.getW());
143
144						y = size.y;
145						size.y = size.z;
146						size.z = y;
147					}
148
149					Transform result = new Transform(loc, rot);
150					result.setScale(size);
151					return result;
152				case CONSTRAINT_SPACE_WORLD:
153					return spatial.getWorldTransform();
154				default:
155					throw new IllegalStateException("Invalid space type for target object: " + space.toString());
156			}
157		}
158		// Bone
159		switch (space) {
160			case CONSTRAINT_SPACE_LOCAL:
161				Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
162				localTransform.setScale(bone.getLocalScale());
163				return localTransform;
164			case CONSTRAINT_SPACE_WORLD:
165				Transform worldTransform = new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation());
166				worldTransform.setScale(bone.getWorldBindScale());
167				return worldTransform;
168			case CONSTRAINT_SPACE_POSE:
169				Transform poseTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
170				poseTransform.setScale(bone.getLocalScale());
171				return poseTransform;
172			case CONSTRAINT_SPACE_PARLOCAL:
173				Transform parentLocalTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
174				parentLocalTransform.setScale(bone.getLocalScale());
175				return parentLocalTransform;
176			default:
177				throw new IllegalStateException("Invalid space type for target object: " + space.toString());
178		}
179	}
180
181	/**
182	 * This method applies the given transform to the feature in the proper
183	 * evaluation space.
184	 *
185	 * @param transform
186	 *            the transform to be applied
187	 */
188	public void applyTransform(Transform transform) {
189		if (spatial != null) {
190			switch (space) {
191				case CONSTRAINT_SPACE_LOCAL:
192					Transform ownerLocalTransform = spatial.getLocalTransform();
193					ownerLocalTransform.getTranslation().addLocal(transform.getTranslation());
194					ownerLocalTransform.getRotation().multLocal(transform.getRotation());
195					ownerLocalTransform.getScale().multLocal(transform.getScale());
196					break;
197				case CONSTRAINT_SPACE_WORLD:
198					Matrix4f m = this.getParentWorldTransformMatrix();
199					m.invertLocal();
200					Matrix4f matrix = this.toMatrix(transform);
201					m.multLocal(matrix);
202
203					float scaleX = (float) Math.sqrt(m.m00 * m.m00 + m.m10 * m.m10 + m.m20 * m.m20);
204					float scaleY = (float) Math.sqrt(m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21);
205					float scaleZ = (float) Math.sqrt(m.m02 * m.m02 + m.m12 * m.m12 + m.m22 * m.m22);
206
207					transform.setTranslation(m.toTranslationVector());
208					transform.setRotation(m.toRotationQuat());
209					transform.setScale(scaleX, scaleY, scaleZ);
210					spatial.setLocalTransform(transform);
211					break;
212				case CONSTRAINT_SPACE_PARLOCAL:
213				case CONSTRAINT_SPACE_POSE:
214					throw new IllegalStateException("Invalid space type (" + space.toString() + ") for owner object.");
215				default:
216					throw new IllegalStateException("Invalid space type for target object: " + space.toString());
217			}
218		} else {// Bone
219			switch (space) {
220				case CONSTRAINT_SPACE_LOCAL:
221					bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
222					break;
223				case CONSTRAINT_SPACE_WORLD:
224					Matrix4f m = this.getParentWorldTransformMatrix();
225//					m.invertLocal();
226					transform.setTranslation(m.mult(transform.getTranslation()));
227					transform.setRotation(m.mult(transform.getRotation(), null));
228					transform.setScale(transform.getScale());
229					bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
230//					float x = FastMath.HALF_PI/2;
231//					float y = -FastMath.HALF_PI;
232//					float z = -FastMath.HALF_PI/2;
233//					bone.setBindTransforms(new Vector3f(0,0,0), new Quaternion().fromAngles(x, y, z), new Vector3f(1,1,1));
234					break;
235				case CONSTRAINT_SPACE_PARLOCAL:
236					Vector3f parentLocalTranslation = bone.getLocalPosition().add(transform.getTranslation());
237					Quaternion parentLocalRotation = bone.getLocalRotation().mult(transform.getRotation());
238					bone.setBindTransforms(parentLocalTranslation, parentLocalRotation, transform.getScale());
239					break;
240				case CONSTRAINT_SPACE_POSE:
241					bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
242					break;
243				default:
244					throw new IllegalStateException("Invalid space type for target object: " + space.toString());
245			}
246		}
247	}
248
249	/**
250	 * @return world transform matrix of the feature
251	 */
252	public Matrix4f getWorldTransformMatrix() {
253		if (spatial != null) {
254			Matrix4f result = new Matrix4f();
255			Transform t = spatial.getWorldTransform();
256			result.setTransform(t.getTranslation(), t.getScale(), t.getRotation().toRotationMatrix());
257			return result;
258		}
259		// Bone
260		Matrix4f result = new Matrix4f();
261		result.setTransform(bone.getWorldBindPosition(), bone.getWorldBindScale(), bone.getWorldBindRotation().toRotationMatrix());
262		return result;
263	}
264
265	/**
266	 * @return world transform matrix of the feature's parent or identity matrix
267	 *         if the feature has no parent
268	 */
269	public Matrix4f getParentWorldTransformMatrix() {
270		Matrix4f result = new Matrix4f();
271		if (spatial != null) {
272			if (spatial.getParent() != null) {
273				Transform t = spatial.getParent().getWorldTransform();
274				result.setTransform(t.getTranslation(), t.getScale(), t.getRotation().toRotationMatrix());
275			}
276		} else {// Bone
277			Bone parent = bone.getParent();
278			if (parent != null) {
279				result.setTransform(parent.getWorldBindPosition(), parent.getWorldBindScale(), parent.getWorldBindRotation().toRotationMatrix());
280			}
281		}
282		return result;
283	}
284
285	/**
286	 * Converts given transform to the matrix.
287	 *
288	 * @param transform
289	 *            the transform to be converted
290	 * @return 4x4 matri that represents the given transform
291	 */
292	protected Matrix4f toMatrix(Transform transform) {
293		Matrix4f result = Matrix4f.IDENTITY;
294		if (transform != null) {
295			result = new Matrix4f();
296			result.setTranslation(transform.getTranslation());
297			result.setRotationQuaternion(transform.getRotation());
298			result.setScale(transform.getScale());
299		}
300		return result;
301	}
302}
303