Home | History | Annotate | Download | only in objects
      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  */
     32 package com.jme3.scene.plugins.blender.objects;
     33 
     34 import java.util.Collection;
     35 import java.util.List;
     36 import java.util.logging.Level;
     37 import java.util.logging.Logger;
     38 
     39 import com.jme3.asset.BlenderKey.FeaturesToLoad;
     40 import com.jme3.light.DirectionalLight;
     41 import com.jme3.light.Light;
     42 import com.jme3.light.PointLight;
     43 import com.jme3.light.SpotLight;
     44 import com.jme3.math.Matrix4f;
     45 import com.jme3.math.Quaternion;
     46 import com.jme3.math.Transform;
     47 import com.jme3.math.Vector3f;
     48 import com.jme3.renderer.Camera;
     49 import com.jme3.scene.Geometry;
     50 import com.jme3.scene.Node;
     51 import com.jme3.scene.Spatial;
     52 import com.jme3.scene.Spatial.CullHint;
     53 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
     54 import com.jme3.scene.plugins.blender.BlenderContext;
     55 import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
     56 import com.jme3.scene.plugins.blender.cameras.CameraHelper;
     57 import com.jme3.scene.plugins.blender.constraints.Constraint;
     58 import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
     59 import com.jme3.scene.plugins.blender.curves.CurvesHelper;
     60 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
     61 import com.jme3.scene.plugins.blender.file.DynamicArray;
     62 import com.jme3.scene.plugins.blender.file.Pointer;
     63 import com.jme3.scene.plugins.blender.file.Structure;
     64 import com.jme3.scene.plugins.blender.lights.LightHelper;
     65 import com.jme3.scene.plugins.blender.meshes.MeshHelper;
     66 import com.jme3.scene.plugins.blender.modifiers.Modifier;
     67 import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
     68 
     69 /**
     70  * A class that is used in object calculations.
     71  * @author Marcin Roguski (Kaelthas)
     72  */
     73 public 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