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