1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32package com.jme3.scene.plugins.blender.objects;
33
34import java.util.Collection;
35import java.util.List;
36import java.util.logging.Level;
37import java.util.logging.Logger;
38
39import com.jme3.asset.BlenderKey.FeaturesToLoad;
40import com.jme3.light.DirectionalLight;
41import com.jme3.light.Light;
42import com.jme3.light.PointLight;
43import com.jme3.light.SpotLight;
44import com.jme3.math.Matrix4f;
45import com.jme3.math.Quaternion;
46import com.jme3.math.Transform;
47import com.jme3.math.Vector3f;
48import com.jme3.renderer.Camera;
49import com.jme3.scene.Geometry;
50import com.jme3.scene.Node;
51import com.jme3.scene.Spatial;
52import com.jme3.scene.Spatial.CullHint;
53import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
54import com.jme3.scene.plugins.blender.BlenderContext;
55import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
56import com.jme3.scene.plugins.blender.cameras.CameraHelper;
57import com.jme3.scene.plugins.blender.constraints.Constraint;
58import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
59import com.jme3.scene.plugins.blender.curves.CurvesHelper;
60import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
61import com.jme3.scene.plugins.blender.file.DynamicArray;
62import com.jme3.scene.plugins.blender.file.Pointer;
63import com.jme3.scene.plugins.blender.file.Structure;
64import com.jme3.scene.plugins.blender.lights.LightHelper;
65import com.jme3.scene.plugins.blender.meshes.MeshHelper;
66import com.jme3.scene.plugins.blender.modifiers.Modifier;
67import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
68
69/**
70 * A class that is used in object calculations.
71 * @author Marcin Roguski (Kaelthas)
72 */
73public class ObjectHelper extends AbstractBlenderHelper {
74	private static final Logger			LOGGER		= Logger.getLogger(ObjectHelper.class.getName());
75
76	protected static final int		OBJECT_TYPE_EMPTY			= 0;
77	protected static final int		OBJECT_TYPE_MESH			= 1;
78	protected static final int		OBJECT_TYPE_CURVE			= 2;
79	protected static final int		OBJECT_TYPE_SURF			= 3;
80	protected static final int		OBJECT_TYPE_TEXT			= 4;
81	protected static final int		OBJECT_TYPE_METABALL		= 5;
82	protected static final int		OBJECT_TYPE_LAMP			= 10;
83	protected static final int		OBJECT_TYPE_CAMERA			= 11;
84	protected static final int		OBJECT_TYPE_WAVE			= 21;
85	protected static final int		OBJECT_TYPE_LATTICE			= 22;
86	protected static final int		OBJECT_TYPE_ARMATURE		= 25;
87
88	/**
89	 * This constructor parses the given blender version and stores the result. Some functionalities may differ in
90	 * different blender versions.
91	 * @param blenderVersion
92	 *        the version read from the blend file
93	 * @param fixUpAxis
94     *        a variable that indicates if the Y asxis is the UP axis or not
95	 */
96	public ObjectHelper(String blenderVersion, boolean fixUpAxis) {
97		super(blenderVersion, fixUpAxis);
98	}
99
100	/**
101	 * This method reads the given structure and createn an object that represents the data.
102	 * @param objectStructure
103	 *            the object's structure
104	 * @param blenderContext
105	 *            the blender context
106	 * @return blener's object representation
107	 * @throws BlenderFileException
108	 *             an exception is thrown when the given data is inapropriate
109	 */
110	public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
111		Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
112		if(loadedResult != null) {
113			return loadedResult;
114		}
115
116		blenderContext.pushParent(objectStructure);
117
118		//get object data
119		int type = ((Number)objectStructure.getFieldValue("type")).intValue();
120		String name = objectStructure.getName();
121		LOGGER.log(Level.INFO, "Loading obejct: {0}", name);
122
123		int restrictflag = ((Number)objectStructure.getFieldValue("restrictflag")).intValue();
124		boolean visible = (restrictflag & 0x01) != 0;
125		Object result = null;
126
127		Pointer pParent = (Pointer)objectStructure.getFieldValue("parent");
128		Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
129		if(parent == null && pParent.isNotNull()) {
130			Structure parentStructure = pParent.fetchData(blenderContext.getInputStream()).get(0);
131			parent = this.toObject(parentStructure, blenderContext);
132		}
133
134		Transform t = this.getTransformation(objectStructure, blenderContext);
135
136		try {
137			switch(type) {
138				case OBJECT_TYPE_EMPTY:
139					LOGGER.log(Level.INFO, "Importing empty.");
140					Node empty = new Node(name);
141					empty.setLocalTransform(t);
142					if(parent instanceof Node) {
143						((Node) parent).attachChild(empty);
144					}
145					empty.updateModelBound();
146					result = empty;
147					break;
148				case OBJECT_TYPE_MESH:
149					LOGGER.log(Level.INFO, "Importing mesh.");
150					Node node = new Node(name);
151					node.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
152
153					//reading mesh
154					MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
155					Pointer pMesh = (Pointer)objectStructure.getFieldValue("data");
156					List<Structure> meshesArray = pMesh.fetchData(blenderContext.getInputStream());
157					List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext);
158					if (geometries != null){
159                        for(Geometry geometry : geometries) {
160                            node.attachChild(geometry);
161                        }
162                    }
163					node.setLocalTransform(t);
164
165					//reading and applying all modifiers
166					ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);
167					Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);
168					for(Modifier modifier : modifiers) {
169						modifier.apply(node, blenderContext);
170					}
171
172					//setting the parent
173					if(parent instanceof Node) {
174						((Node)parent).attachChild(node);
175					}
176					node.updateModelBound();//I prefer do calculate bounding box here than read it from the file
177					result = node;
178					break;
179				case OBJECT_TYPE_SURF:
180				case OBJECT_TYPE_CURVE:
181					LOGGER.log(Level.INFO, "Importing curve/nurb.");
182					Pointer pCurve = (Pointer)objectStructure.getFieldValue("data");
183					if(pCurve.isNotNull()) {
184						CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class);
185						Structure curveData = pCurve.fetchData(blenderContext.getInputStream()).get(0);
186						List<Geometry> curves = curvesHelper.toCurve(curveData, blenderContext);
187						result = new Node(name);
188						for(Geometry curve : curves) {
189							((Node)result).attachChild(curve);
190						}
191						((Node)result).setLocalTransform(t);
192					}
193					break;
194				case OBJECT_TYPE_LAMP:
195					LOGGER.log(Level.INFO, "Importing lamp.");
196					Pointer pLamp = (Pointer)objectStructure.getFieldValue("data");
197					if(pLamp.isNotNull()) {
198						LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
199						List<Structure> lampsArray = pLamp.fetchData(blenderContext.getInputStream());
200						Light light = lightHelper.toLight(lampsArray.get(0), blenderContext);
201						if(light!=null) {
202							light.setName(name);
203						}
204						if(light instanceof PointLight) {
205							((PointLight)light).setPosition(t.getTranslation());
206						} else if(light instanceof DirectionalLight) {
207							Quaternion quaternion = t.getRotation();
208							Vector3f[] axes = new Vector3f[3];
209							quaternion.toAxes(axes);
210							if(fixUpAxis) {
211								((DirectionalLight)light).setDirection(axes[1].negate());//-Z is the direction axis of area lamp in blender
212							} else {
213								((DirectionalLight)light).setDirection(axes[2].negate());
214							}
215						} else if(light instanceof SpotLight) {
216							((SpotLight)light).setPosition(t.getTranslation());
217
218							Quaternion quaternion = t.getRotation();
219							Vector3f[] axes = new Vector3f[3];
220							quaternion.toAxes(axes);
221							if(fixUpAxis) {
222								((SpotLight)light).setDirection(axes[1].negate());//-Z is the direction axis of area lamp in blender
223							} else {
224								((SpotLight)light).setDirection(axes[2].negate());
225							}
226						} else {
227							LOGGER.log(Level.WARNING, "Unknown type of light: {0}", light);
228						}
229						result = light;
230					}
231					break;
232				case OBJECT_TYPE_CAMERA:
233					Pointer pCamera = (Pointer)objectStructure.getFieldValue("data");
234					if(pCamera.isNotNull()) {
235						CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
236						List<Structure> camerasArray = pCamera.fetchData(blenderContext.getInputStream());
237						Camera camera = cameraHelper.toCamera(camerasArray.get(0));
238						camera.setLocation(t.getTranslation());
239						camera.setRotation(t.getRotation());
240						result = camera;
241					}
242					break;
243				case OBJECT_TYPE_ARMATURE:
244					//need to create an empty node to properly create parent-children relationships between nodes
245					Node armature = new Node(name);
246					armature.setLocalTransform(t);
247					//TODO: modifiers for armature ????
248					if(parent instanceof Node) {
249						((Node)parent).attachChild(armature);
250					}
251					armature.updateModelBound();//I prefer do calculate bounding box here than read it from the file
252					result = armature;
253					break;
254				default:
255					LOGGER.log(Level.WARNING, "Unknown object type: {0}", type);
256			}
257		} finally {
258			blenderContext.popParent();
259		}
260
261		if(result != null) {
262			blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
263
264			//loading constraints connected with this object
265			ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
266			constraintHelper.loadConstraints(objectStructure, blenderContext);
267
268			//baking constraints
269			List<Constraint> objectConstraints = blenderContext.getConstraints(objectStructure.getOldMemoryAddress());
270			if(objectConstraints!=null) {
271				for(Constraint objectConstraint : objectConstraints) {
272					objectConstraint.bake();
273				}
274			}
275
276			//reading custom properties
277			Properties properties = this.loadProperties(objectStructure, blenderContext);
278			if(result instanceof Spatial && properties != null && properties.getValue() != null) {
279				((Spatial)result).setUserData("properties", properties);
280			}
281		}
282		return result;
283	}
284
285	/**
286	 * This method calculates local transformation for the object. Parentage is taken under consideration.
287	 * @param objectStructure
288	 *        the object's structure
289	 * @return objects transformation relative to its parent
290	 */
291	@SuppressWarnings("unchecked")
292	public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) {
293		//these are transformations in global space
294		DynamicArray<Number> loc = (DynamicArray<Number>)objectStructure.getFieldValue("loc");
295		DynamicArray<Number> size = (DynamicArray<Number>)objectStructure.getFieldValue("size");
296		DynamicArray<Number> rot = (DynamicArray<Number>)objectStructure.getFieldValue("rot");
297
298		//load parent inverse matrix
299		Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
300		Matrix4f parentInv = pParent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv");
301
302		//create the global matrix (without the scale)
303		Matrix4f globalMatrix = new Matrix4f();
304		globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue());
305		globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue()));
306		//compute local matrix
307		Matrix4f localMatrix = parentInv.mult(globalMatrix);
308
309		Vector3f translation = localMatrix.toTranslationVector();
310		Quaternion rotation = localMatrix.toRotationQuat();
311		Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
312
313		if(fixUpAxis) {
314			float y = translation.y;
315			translation.y = translation.z;
316			translation.z = -y;
317
318			y = rotation.getY();
319			float z = rotation.getZ();
320			rotation.set(rotation.getX(), z, -y, rotation.getW());
321
322			y=scale.y;
323			scale.y = scale.z;
324			scale.z = y;
325		}
326
327		//create the result
328		Transform t = new Transform(translation, rotation);
329		t.setScale(scale);
330		return t;
331	}
332
333	/**
334	 * This method returns the matrix of a given name for the given structure.
335	 * The matrix is NOT transformed if Y axis is up - the raw data is loaded from the blender file.
336	 * @param structure
337	 *        the structure with matrix data
338	 * @param matrixName
339	 * 		  the name of the matrix
340	 * @return the required matrix
341	 */
342	public Matrix4f getMatrix(Structure structure, String matrixName) {
343		return this.getMatrix(structure, matrixName, false);
344	}
345
346	/**
347	 * This method returns the matrix of a given name for the given structure.
348	 * It takes up axis into consideration.
349	 * @param structure
350	 *        the structure with matrix data
351	 * @param matrixName
352	 * 		  the name of the matrix
353	 * @return the required matrix
354	 */
355	@SuppressWarnings("unchecked")
356	public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) {
357		Matrix4f result = new Matrix4f();
358		DynamicArray<Number> obmat = (DynamicArray<Number>)structure.getFieldValue(matrixName);
359		int rowAndColumnSize = Math.abs((int)Math.sqrt(obmat.getTotalSize()));//the matrix must be square
360		for(int i = 0; i < rowAndColumnSize; ++i) {
361			for(int j = 0; j < rowAndColumnSize; ++j) {
362				result.set(i, j, obmat.get(j, i).floatValue());
363			}
364		}
365		if(applyFixUpAxis && fixUpAxis) {
366        	Vector3f translation = result.toTranslationVector();
367            Quaternion rotation = result.toRotationQuat();
368            Vector3f scale = this.getScale(result);
369
370			float y = translation.y;
371			translation.y = translation.z;
372			translation.z = -y;
373
374			y = rotation.getY();
375			float z = rotation.getZ();
376			rotation.set(rotation.getX(), z, -y, rotation.getW());
377
378			y=scale.y;
379			scale.y = scale.z;
380			scale.z = y;
381
382			result.loadIdentity();
383			result.setTranslation(translation);
384			result.setRotationQuaternion(rotation);
385			result.setScale(scale);
386        }
387		return result;
388	}
389
390	/**
391	 * This method returns the scale from the given matrix.
392	 *
393	 * @param matrix
394	 *            the transformation matrix
395	 * @return the scale from the given matrix
396	 */
397	public Vector3f getScale(Matrix4f matrix) {
398		float scaleX = (float) Math.sqrt(matrix.m00 * matrix.m00 + matrix.m10 * matrix.m10 + matrix.m20 * matrix.m20);
399		float scaleY = (float) Math.sqrt(matrix.m01 * matrix.m01 + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21);
400		float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22);
401		return new Vector3f(scaleX, scaleY, scaleZ);
402	}
403
404	@Override
405	public void clearState() {
406		fixUpAxis = false;
407	}
408
409	@Override
410	public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
411		int lay = ((Number) structure.getFieldValue("lay")).intValue();
412        return ((lay & blenderContext.getBlenderKey().getLayersToLoad()) != 0
413                && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0);
414	}
415}
416