2
0

ObjectHelper.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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. import java.util.Collection;
  34. import java.util.List;
  35. import java.util.logging.Level;
  36. import java.util.logging.Logger;
  37. import com.jme3.asset.BlenderKey.FeaturesToLoad;
  38. import com.jme3.math.Matrix4f;
  39. import com.jme3.math.Quaternion;
  40. import com.jme3.math.Transform;
  41. import com.jme3.math.Vector3f;
  42. import com.jme3.scene.CameraNode;
  43. import com.jme3.scene.Geometry;
  44. import com.jme3.scene.LightNode;
  45. import com.jme3.scene.Node;
  46. import com.jme3.scene.Spatial;
  47. import com.jme3.scene.Spatial.CullHint;
  48. import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
  49. import com.jme3.scene.plugins.blender.BlenderContext;
  50. import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
  51. import com.jme3.scene.plugins.blender.cameras.CameraHelper;
  52. import com.jme3.scene.plugins.blender.constraints.Constraint;
  53. import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
  54. import com.jme3.scene.plugins.blender.curves.CurvesHelper;
  55. import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
  56. import com.jme3.scene.plugins.blender.file.DynamicArray;
  57. import com.jme3.scene.plugins.blender.file.Pointer;
  58. import com.jme3.scene.plugins.blender.file.Structure;
  59. import com.jme3.scene.plugins.blender.lights.LightHelper;
  60. import com.jme3.scene.plugins.blender.meshes.MeshHelper;
  61. import com.jme3.scene.plugins.blender.modifiers.Modifier;
  62. import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
  63. /**
  64. * A class that is used in object calculations.
  65. * @author Marcin Roguski (Kaelthas)
  66. */
  67. public class ObjectHelper extends AbstractBlenderHelper {
  68. private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName());
  69. protected static final int OBJECT_TYPE_EMPTY = 0;
  70. protected static final int OBJECT_TYPE_MESH = 1;
  71. protected static final int OBJECT_TYPE_CURVE = 2;
  72. protected static final int OBJECT_TYPE_SURF = 3;
  73. protected static final int OBJECT_TYPE_TEXT = 4;
  74. protected static final int OBJECT_TYPE_METABALL = 5;
  75. protected static final int OBJECT_TYPE_LAMP = 10;
  76. protected static final int OBJECT_TYPE_CAMERA = 11;
  77. protected static final int OBJECT_TYPE_WAVE = 21;
  78. protected static final int OBJECT_TYPE_LATTICE = 22;
  79. protected static final int OBJECT_TYPE_ARMATURE = 25;
  80. /**
  81. * This constructor parses the given blender version and stores the result. Some functionalities may differ in
  82. * different blender versions.
  83. * @param blenderVersion
  84. * the version read from the blend file
  85. * @param fixUpAxis
  86. * a variable that indicates if the Y asxis is the UP axis or not
  87. */
  88. public ObjectHelper(String blenderVersion, boolean fixUpAxis) {
  89. super(blenderVersion, fixUpAxis);
  90. }
  91. /**
  92. * This method reads the given structure and createn an object that represents the data.
  93. * @param objectStructure
  94. * the object's structure
  95. * @param blenderContext
  96. * the blender context
  97. * @return blener's object representation
  98. * @throws BlenderFileException
  99. * an exception is thrown when the given data is inapropriate
  100. */
  101. public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
  102. Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
  103. if(loadedResult != null) {
  104. return loadedResult;
  105. }
  106. blenderContext.pushParent(objectStructure);
  107. //get object data
  108. int type = ((Number)objectStructure.getFieldValue("type")).intValue();
  109. String name = objectStructure.getName();
  110. LOGGER.log(Level.INFO, "Loading obejct: {0}", name);
  111. int restrictflag = ((Number)objectStructure.getFieldValue("restrictflag")).intValue();
  112. boolean visible = (restrictflag & 0x01) != 0;
  113. Object result = null;
  114. Pointer pParent = (Pointer)objectStructure.getFieldValue("parent");
  115. Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
  116. if(parent == null && pParent.isNotNull()) {
  117. Structure parentStructure = pParent.fetchData(blenderContext.getInputStream()).get(0);
  118. parent = this.toObject(parentStructure, blenderContext);
  119. }
  120. Transform t = this.getTransformation(objectStructure, blenderContext);
  121. try {
  122. switch(type) {
  123. case OBJECT_TYPE_EMPTY:
  124. LOGGER.log(Level.INFO, "Importing empty.");
  125. Node empty = new Node(name);
  126. empty.setLocalTransform(t);
  127. if(parent instanceof Node) {
  128. ((Node) parent).attachChild(empty);
  129. }
  130. empty.updateModelBound();
  131. result = empty;
  132. break;
  133. case OBJECT_TYPE_MESH:
  134. LOGGER.log(Level.INFO, "Importing mesh.");
  135. Node node = new Node(name);
  136. node.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
  137. //reading mesh
  138. MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
  139. Pointer pMesh = (Pointer)objectStructure.getFieldValue("data");
  140. List<Structure> meshesArray = pMesh.fetchData(blenderContext.getInputStream());
  141. List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext);
  142. if (geometries != null){
  143. for(Geometry geometry : geometries) {
  144. node.attachChild(geometry);
  145. }
  146. }
  147. node.setLocalTransform(t);
  148. //reading and applying all modifiers
  149. ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);
  150. Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);
  151. for(Modifier modifier : modifiers) {
  152. modifier.apply(node, blenderContext);
  153. }
  154. //setting the parent
  155. if(parent instanceof Node) {
  156. ((Node)parent).attachChild(node);
  157. }
  158. node.updateModelBound();//I prefer do calculate bounding box here than read it from the file
  159. result = node;
  160. break;
  161. case OBJECT_TYPE_SURF:
  162. case OBJECT_TYPE_CURVE:
  163. LOGGER.log(Level.INFO, "Importing curve/nurb.");
  164. Pointer pCurve = (Pointer)objectStructure.getFieldValue("data");
  165. if(pCurve.isNotNull()) {
  166. CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class);
  167. Structure curveData = pCurve.fetchData(blenderContext.getInputStream()).get(0);
  168. List<Geometry> curves = curvesHelper.toCurve(curveData, blenderContext);
  169. result = new Node(name);
  170. for(Geometry curve : curves) {
  171. ((Node)result).attachChild(curve);
  172. }
  173. ((Node)result).setLocalTransform(t);
  174. }
  175. break;
  176. case OBJECT_TYPE_LAMP:
  177. LOGGER.log(Level.INFO, "Importing lamp.");
  178. Pointer pLamp = (Pointer)objectStructure.getFieldValue("data");
  179. if(pLamp.isNotNull()) {
  180. LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
  181. List<Structure> lampsArray = pLamp.fetchData(blenderContext.getInputStream());
  182. LightNode light = lightHelper.toLight(lampsArray.get(0), blenderContext);
  183. if(light!=null) {
  184. light.setName(name);
  185. light.setLocalTransform(t);
  186. }
  187. result = light;
  188. }
  189. break;
  190. case OBJECT_TYPE_CAMERA:
  191. Pointer pCamera = (Pointer)objectStructure.getFieldValue("data");
  192. if(pCamera.isNotNull()) {
  193. CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
  194. List<Structure> camerasArray = pCamera.fetchData(blenderContext.getInputStream());
  195. CameraNode camera = cameraHelper.toCamera(camerasArray.get(0));
  196. camera.setName(name);
  197. camera.setLocalTransform(t);
  198. result = camera;
  199. }
  200. break;
  201. case OBJECT_TYPE_ARMATURE:
  202. //need to create an empty node to properly create parent-children relationships between nodes
  203. Node armature = new Node(name);
  204. armature.setLocalTransform(t);
  205. //TODO: modifiers for armature ????
  206. if(parent instanceof Node) {
  207. ((Node)parent).attachChild(armature);
  208. }
  209. armature.updateModelBound();//I prefer do calculate bounding box here than read it from the file
  210. result = armature;
  211. break;
  212. default:
  213. LOGGER.log(Level.WARNING, "Unknown object type: {0}", type);
  214. }
  215. } finally {
  216. blenderContext.popParent();
  217. }
  218. if(result != null) {
  219. blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
  220. //loading constraints connected with this object
  221. ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
  222. constraintHelper.loadConstraints(objectStructure, blenderContext);
  223. //baking constraints
  224. List<Constraint> objectConstraints = blenderContext.getConstraints(objectStructure.getOldMemoryAddress());
  225. if(objectConstraints!=null) {
  226. for(Constraint objectConstraint : objectConstraints) {
  227. objectConstraint.bake();
  228. }
  229. }
  230. //reading custom properties
  231. if(blenderContext.getBlenderKey().isLoadObjectProperties()) {
  232. Properties properties = this.loadProperties(objectStructure, blenderContext);
  233. //the loaded property is a group property, so we need to get each value and set it to Spatial
  234. if(result instanceof Spatial && properties != null && properties.getValue() != null) {
  235. this.applyProperties((Spatial) result, properties);
  236. }
  237. }
  238. }
  239. return result;
  240. }
  241. /**
  242. * This method calculates local transformation for the object. Parentage is taken under consideration.
  243. * @param objectStructure
  244. * the object's structure
  245. * @return objects transformation relative to its parent
  246. */
  247. @SuppressWarnings("unchecked")
  248. public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) {
  249. //these are transformations in global space
  250. DynamicArray<Number> loc = (DynamicArray<Number>)objectStructure.getFieldValue("loc");
  251. DynamicArray<Number> size = (DynamicArray<Number>)objectStructure.getFieldValue("size");
  252. DynamicArray<Number> rot = (DynamicArray<Number>)objectStructure.getFieldValue("rot");
  253. //load parent inverse matrix
  254. Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
  255. Matrix4f parentInv = pParent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv");
  256. //create the global matrix (without the scale)
  257. Matrix4f globalMatrix = new Matrix4f();
  258. globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue());
  259. globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue()));
  260. //compute local matrix
  261. Matrix4f localMatrix = parentInv.mult(globalMatrix);
  262. Vector3f translation = localMatrix.toTranslationVector();
  263. Quaternion rotation = localMatrix.toRotationQuat();
  264. Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
  265. if(fixUpAxis) {
  266. float y = translation.y;
  267. translation.y = translation.z;
  268. translation.z = -y;
  269. y = rotation.getY();
  270. float z = rotation.getZ();
  271. rotation.set(rotation.getX(), z, -y, rotation.getW());
  272. y=scale.y;
  273. scale.y = scale.z;
  274. scale.z = y;
  275. }
  276. //create the result
  277. Transform t = new Transform(translation, rotation);
  278. t.setScale(scale);
  279. return t;
  280. }
  281. /**
  282. * This method returns the matrix of a given name for the given structure.
  283. * The matrix is NOT transformed if Y axis is up - the raw data is loaded from the blender file.
  284. * @param structure
  285. * the structure with matrix data
  286. * @param matrixName
  287. * the name of the matrix
  288. * @return the required matrix
  289. */
  290. public Matrix4f getMatrix(Structure structure, String matrixName) {
  291. return this.getMatrix(structure, matrixName, false);
  292. }
  293. /**
  294. * This method returns the matrix of a given name for the given structure.
  295. * It takes up axis into consideration.
  296. * @param structure
  297. * the structure with matrix data
  298. * @param matrixName
  299. * the name of the matrix
  300. * @return the required matrix
  301. */
  302. @SuppressWarnings("unchecked")
  303. public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) {
  304. Matrix4f result = new Matrix4f();
  305. DynamicArray<Number> obmat = (DynamicArray<Number>)structure.getFieldValue(matrixName);
  306. int rowAndColumnSize = Math.abs((int)Math.sqrt(obmat.getTotalSize()));//the matrix must be square
  307. for(int i = 0; i < rowAndColumnSize; ++i) {
  308. for(int j = 0; j < rowAndColumnSize; ++j) {
  309. result.set(i, j, obmat.get(j, i).floatValue());
  310. }
  311. }
  312. if(applyFixUpAxis && fixUpAxis) {
  313. Vector3f translation = result.toTranslationVector();
  314. Quaternion rotation = result.toRotationQuat();
  315. Vector3f scale = this.getScale(result);
  316. float y = translation.y;
  317. translation.y = translation.z;
  318. translation.z = -y;
  319. y = rotation.getY();
  320. float z = rotation.getZ();
  321. rotation.set(rotation.getX(), z, -y, rotation.getW());
  322. y=scale.y;
  323. scale.y = scale.z;
  324. scale.z = y;
  325. result.loadIdentity();
  326. result.setTranslation(translation);
  327. result.setRotationQuaternion(rotation);
  328. result.setScale(scale);
  329. }
  330. return result;
  331. }
  332. /**
  333. * This method returns the scale from the given matrix.
  334. *
  335. * @param matrix
  336. * the transformation matrix
  337. * @return the scale from the given matrix
  338. */
  339. public Vector3f getScale(Matrix4f matrix) {
  340. float scaleX = (float) Math.sqrt(matrix.m00 * matrix.m00 + matrix.m10 * matrix.m10 + matrix.m20 * matrix.m20);
  341. float scaleY = (float) Math.sqrt(matrix.m01 * matrix.m01 + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21);
  342. float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22);
  343. return new Vector3f(scaleX, scaleY, scaleZ);
  344. }
  345. @Override
  346. public void clearState() {
  347. fixUpAxis = false;
  348. }
  349. @Override
  350. public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
  351. int lay = ((Number) structure.getFieldValue("lay")).intValue();
  352. return (lay & blenderContext.getBlenderKey().getLayersToLoad()) != 0
  353. && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0;
  354. }
  355. }