/* * Copyright (c) 2009-2010 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.scene.plugins.blender.objects; import java.util.Collection; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.light.DirectionalLight; import com.jme3.light.Light; import com.jme3.light.PointLight; import com.jme3.light.SpotLight; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.Spatial.CullHint; import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.cameras.CameraHelper; import com.jme3.scene.plugins.blender.constraints.Constraint; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; import com.jme3.scene.plugins.blender.curves.CurvesHelper; import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.lights.LightHelper; import com.jme3.scene.plugins.blender.meshes.MeshHelper; import com.jme3.scene.plugins.blender.modifiers.Modifier; import com.jme3.scene.plugins.blender.modifiers.ModifierHelper; /** * A class that is used in object calculations. * @author Marcin Roguski (Kaelthas) */ public class ObjectHelper extends AbstractBlenderHelper { private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName()); protected static final int OBJECT_TYPE_EMPTY = 0; protected static final int OBJECT_TYPE_MESH = 1; protected static final int OBJECT_TYPE_CURVE = 2; protected static final int OBJECT_TYPE_SURF = 3; protected static final int OBJECT_TYPE_TEXT = 4; protected static final int OBJECT_TYPE_METABALL = 5; protected static final int OBJECT_TYPE_LAMP = 10; protected static final int OBJECT_TYPE_CAMERA = 11; protected static final int OBJECT_TYPE_WAVE = 21; protected static final int OBJECT_TYPE_LATTICE = 22; protected static final int OBJECT_TYPE_ARMATURE = 25; /** * This constructor parses the given blender version and stores the result. Some functionalities may differ in * different blender versions. * @param blenderVersion * the version read from the blend file * @param fixUpAxis * a variable that indicates if the Y asxis is the UP axis or not */ public ObjectHelper(String blenderVersion, boolean fixUpAxis) { super(blenderVersion, fixUpAxis); } /** * This method reads the given structure and createn an object that represents the data. * @param objectStructure * the object's structure * @param blenderContext * the blender context * @return blener's object representation * @throws BlenderFileException * an exception is thrown when the given data is inapropriate */ public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); if(loadedResult != null) { return loadedResult; } blenderContext.pushParent(objectStructure); //get object data int type = ((Number)objectStructure.getFieldValue("type")).intValue(); String name = objectStructure.getName(); LOGGER.log(Level.INFO, "Loading obejct: {0}", name); int restrictflag = ((Number)objectStructure.getFieldValue("restrictflag")).intValue(); boolean visible = (restrictflag & 0x01) != 0; Object result = null; Pointer pParent = (Pointer)objectStructure.getFieldValue("parent"); Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); if(parent == null && pParent.isNotNull()) { Structure parentStructure = pParent.fetchData(blenderContext.getInputStream()).get(0); parent = this.toObject(parentStructure, blenderContext); } Transform t = this.getTransformation(objectStructure, blenderContext); try { switch(type) { case OBJECT_TYPE_EMPTY: LOGGER.log(Level.INFO, "Importing empty."); Node empty = new Node(name); empty.setLocalTransform(t); if(parent instanceof Node) { ((Node) parent).attachChild(empty); } empty.updateModelBound(); result = empty; break; case OBJECT_TYPE_MESH: LOGGER.log(Level.INFO, "Importing mesh."); Node node = new Node(name); node.setCullHint(visible ? CullHint.Always : CullHint.Inherit); //reading mesh MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); Pointer pMesh = (Pointer)objectStructure.getFieldValue("data"); List meshesArray = pMesh.fetchData(blenderContext.getInputStream()); List geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext); if (geometries != null){ for(Geometry geometry : geometries) { node.attachChild(geometry); } } node.setLocalTransform(t); //reading and applying all modifiers ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class); Collection modifiers = modifierHelper.readModifiers(objectStructure, blenderContext); for(Modifier modifier : modifiers) { modifier.apply(node, blenderContext); } //setting the parent if(parent instanceof Node) { ((Node)parent).attachChild(node); } node.updateModelBound();//I prefer do calculate bounding box here than read it from the file result = node; break; case OBJECT_TYPE_SURF: case OBJECT_TYPE_CURVE: LOGGER.log(Level.INFO, "Importing curve/nurb."); Pointer pCurve = (Pointer)objectStructure.getFieldValue("data"); if(pCurve.isNotNull()) { CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class); Structure curveData = pCurve.fetchData(blenderContext.getInputStream()).get(0); List curves = curvesHelper.toCurve(curveData, blenderContext); result = new Node(name); for(Geometry curve : curves) { ((Node)result).attachChild(curve); } ((Node)result).setLocalTransform(t); } break; case OBJECT_TYPE_LAMP: LOGGER.log(Level.INFO, "Importing lamp."); Pointer pLamp = (Pointer)objectStructure.getFieldValue("data"); if(pLamp.isNotNull()) { LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); List lampsArray = pLamp.fetchData(blenderContext.getInputStream()); Light light = lightHelper.toLight(lampsArray.get(0), blenderContext); if(light!=null) { light.setName(name); } if(light instanceof PointLight) { ((PointLight)light).setPosition(t.getTranslation()); } else if(light instanceof DirectionalLight) { Quaternion quaternion = t.getRotation(); Vector3f[] axes = new Vector3f[3]; quaternion.toAxes(axes); if(fixUpAxis) { ((DirectionalLight)light).setDirection(axes[1].negate());//-Z is the direction axis of area lamp in blender } else { ((DirectionalLight)light).setDirection(axes[2].negate()); } } else if(light instanceof SpotLight) { ((SpotLight)light).setPosition(t.getTranslation()); Quaternion quaternion = t.getRotation(); Vector3f[] axes = new Vector3f[3]; quaternion.toAxes(axes); if(fixUpAxis) { ((SpotLight)light).setDirection(axes[1].negate());//-Z is the direction axis of area lamp in blender } else { ((SpotLight)light).setDirection(axes[2].negate()); } } else { LOGGER.log(Level.WARNING, "Unknown type of light: {0}", light); } result = light; } break; case OBJECT_TYPE_CAMERA: Pointer pCamera = (Pointer)objectStructure.getFieldValue("data"); if(pCamera.isNotNull()) { CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); List camerasArray = pCamera.fetchData(blenderContext.getInputStream()); Camera camera = cameraHelper.toCamera(camerasArray.get(0)); camera.setLocation(t.getTranslation()); camera.setRotation(t.getRotation()); result = camera; } break; case OBJECT_TYPE_ARMATURE: //need to create an empty node to properly create parent-children relationships between nodes Node armature = new Node(name); armature.setLocalTransform(t); //TODO: modifiers for armature ???? if(parent instanceof Node) { ((Node)parent).attachChild(armature); } armature.updateModelBound();//I prefer do calculate bounding box here than read it from the file result = armature; break; default: LOGGER.log(Level.WARNING, "Unknown object type: {0}", type); } } finally { blenderContext.popParent(); } if(result != null) { blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result); //loading constraints connected with this object ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); constraintHelper.loadConstraints(objectStructure, blenderContext); //baking constraints List objectConstraints = blenderContext.getConstraints(objectStructure.getOldMemoryAddress()); if(objectConstraints!=null) { for(Constraint objectConstraint : objectConstraints) { objectConstraint.bake(); } } //reading custom properties Properties properties = this.loadProperties(objectStructure, blenderContext); if(result instanceof Spatial && properties != null && properties.getValue() != null) { ((Spatial)result).setUserData("properties", properties); } } return result; } /** * This method calculates local transformation for the object. Parentage is taken under consideration. * @param objectStructure * the object's structure * @return objects transformation relative to its parent */ @SuppressWarnings("unchecked") public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) { //these are transformations in global space DynamicArray loc = (DynamicArray)objectStructure.getFieldValue("loc"); DynamicArray size = (DynamicArray)objectStructure.getFieldValue("size"); DynamicArray rot = (DynamicArray)objectStructure.getFieldValue("rot"); //load parent inverse matrix Pointer pParent = (Pointer) objectStructure.getFieldValue("parent"); Matrix4f parentInv = pParent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv"); //create the global matrix (without the scale) Matrix4f globalMatrix = new Matrix4f(); globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue()); globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue())); //compute local matrix Matrix4f localMatrix = parentInv.mult(globalMatrix); Vector3f translation = localMatrix.toTranslationVector(); Quaternion rotation = localMatrix.toRotationQuat(); Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue()); if(fixUpAxis) { float y = translation.y; translation.y = translation.z; translation.z = -y; y = rotation.getY(); float z = rotation.getZ(); rotation.set(rotation.getX(), z, -y, rotation.getW()); y=scale.y; scale.y = scale.z; scale.z = y; } //create the result Transform t = new Transform(translation, rotation); t.setScale(scale); return t; } /** * This method returns the matrix of a given name for the given structure. * The matrix is NOT transformed if Y axis is up - the raw data is loaded from the blender file. * @param structure * the structure with matrix data * @param matrixName * the name of the matrix * @return the required matrix */ public Matrix4f getMatrix(Structure structure, String matrixName) { return this.getMatrix(structure, matrixName, false); } /** * This method returns the matrix of a given name for the given structure. * It takes up axis into consideration. * @param structure * the structure with matrix data * @param matrixName * the name of the matrix * @return the required matrix */ @SuppressWarnings("unchecked") public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) { Matrix4f result = new Matrix4f(); DynamicArray obmat = (DynamicArray)structure.getFieldValue(matrixName); int rowAndColumnSize = Math.abs((int)Math.sqrt(obmat.getTotalSize()));//the matrix must be square for(int i = 0; i < rowAndColumnSize; ++i) { for(int j = 0; j < rowAndColumnSize; ++j) { result.set(i, j, obmat.get(j, i).floatValue()); } } if(applyFixUpAxis && fixUpAxis) { Vector3f translation = result.toTranslationVector(); Quaternion rotation = result.toRotationQuat(); Vector3f scale = this.getScale(result); float y = translation.y; translation.y = translation.z; translation.z = -y; y = rotation.getY(); float z = rotation.getZ(); rotation.set(rotation.getX(), z, -y, rotation.getW()); y=scale.y; scale.y = scale.z; scale.z = y; result.loadIdentity(); result.setTranslation(translation); result.setRotationQuaternion(rotation); result.setScale(scale); } return result; } /** * This method returns the scale from the given matrix. * * @param matrix * the transformation matrix * @return the scale from the given matrix */ public Vector3f getScale(Matrix4f matrix) { float scaleX = (float) Math.sqrt(matrix.m00 * matrix.m00 + matrix.m10 * matrix.m10 + matrix.m20 * matrix.m20); float scaleY = (float) Math.sqrt(matrix.m01 * matrix.m01 + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21); float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22); return new Vector3f(scaleX, scaleY, scaleZ); } @Override public void clearState() { fixUpAxis = false; } @Override public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { int lay = ((Number) structure.getFieldValue("lay")).intValue(); return ((lay & blenderContext.getBlenderKey().getLayersToLoad()) != 0 && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0); } }