• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
33 
34 import com.jme3.asset.AssetNotFoundException;
35 import com.jme3.bounding.BoundingVolume;
36 import com.jme3.collision.Collidable;
37 import com.jme3.collision.CollisionResults;
38 import com.jme3.export.InputCapsule;
39 import com.jme3.export.JmeExporter;
40 import com.jme3.export.JmeImporter;
41 import com.jme3.export.OutputCapsule;
42 import com.jme3.material.Material;
43 import com.jme3.math.Matrix4f;
44 import com.jme3.math.Transform;
45 import com.jme3.scene.VertexBuffer.Type;
46 import com.jme3.util.TempVars;
47 import java.io.IOException;
48 import java.util.Queue;
49 import java.util.logging.Level;
50 import java.util.logging.Logger;
51 
52 /**
53  * <code>Geometry</code> defines a leaf node of the scene graph. The leaf node
54  * contains the geometric data for rendering objects. It manages all rendering
55  * information such as a {@link Material} object to define how the surface
56  * should be shaded and the {@link Mesh} data to contain the actual geometry.
57  *
58  * @author Kirill Vainer
59  */
60 public class Geometry extends Spatial {
61 
62     // Version #1: removed shared meshes.
63     // models loaded with shared mesh will be automatically fixed.
64     public static final int SAVABLE_VERSION = 1;
65 
66     private static final Logger logger = Logger.getLogger(Geometry.class.getName());
67     protected Mesh mesh;
68     protected transient int lodLevel = 0;
69     protected Material material;
70     /**
71      * When true, the geometry's transform will not be applied.
72      */
73     protected boolean ignoreTransform = false;
74     protected transient Matrix4f cachedWorldMat = new Matrix4f();
75     /**
76      * used when geometry is batched
77      */
78     protected BatchNode batchNode = null;
79     /**
80      * the start index of this geom's mesh in the batchNode mesh
81      */
82     protected int startIndex;
83     /**
84      * the previous transforms of the geometry used to compute world transforms
85      */
86     protected Transform prevBatchTransforms = null;
87     /**
88      * the cached offset matrix used when the geometry is batched
89      */
90     protected Matrix4f cachedOffsetMat = null;
91 
92     /**
93      * Serialization only. Do not use.
94      */
Geometry()95     public Geometry() {
96     }
97 
98     /**
99      * Create a geometry node without any mesh data.
100      * Both the mesh and the material are null, the geometry
101      * cannot be rendered until those are set.
102      *
103      * @param name The name of this geometry
104      */
Geometry(String name)105     public Geometry(String name) {
106         super(name);
107     }
108 
109     /**
110      * Create a geometry node with mesh data.
111      * The material of the geometry is null, it cannot
112      * be rendered until it is set.
113      *
114      * @param name The name of this geometry
115      * @param mesh The mesh data for this geometry
116      */
Geometry(String name, Mesh mesh)117     public Geometry(String name, Mesh mesh) {
118         this(name);
119         if (mesh == null) {
120             throw new NullPointerException();
121         }
122 
123         this.mesh = mesh;
124     }
125 
126     /**
127      * @return If ignoreTransform mode is set.
128      *
129      * @see Geometry#setIgnoreTransform(boolean)
130      */
isIgnoreTransform()131     public boolean isIgnoreTransform() {
132         return ignoreTransform;
133     }
134 
135     /**
136      * @param ignoreTransform If true, the geometry's transform will not be applied.
137      */
setIgnoreTransform(boolean ignoreTransform)138     public void setIgnoreTransform(boolean ignoreTransform) {
139         this.ignoreTransform = ignoreTransform;
140     }
141 
142     /**
143      * Sets the LOD level to use when rendering the mesh of this geometry.
144      * Level 0 indicates that the default index buffer should be used,
145      * levels [1, LodLevels + 1] represent the levels set on the mesh
146      * with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
147      *
148      * @param lod The lod level to set
149      */
150     @Override
setLodLevel(int lod)151     public void setLodLevel(int lod) {
152         if (mesh.getNumLodLevels() == 0) {
153             throw new IllegalStateException("LOD levels are not set on this mesh");
154         }
155 
156         if (lod < 0 || lod >= mesh.getNumLodLevels()) {
157             throw new IllegalArgumentException("LOD level is out of range: " + lod);
158         }
159 
160         lodLevel = lod;
161     }
162 
163     /**
164      * Returns the LOD level set with {@link #setLodLevel(int) }.
165      *
166      * @return the LOD level set
167      */
getLodLevel()168     public int getLodLevel() {
169         return lodLevel;
170     }
171 
172     /**
173      * Returns this geometry's mesh vertex count.
174      *
175      * @return this geometry's mesh vertex count.
176      *
177      * @see Mesh#getVertexCount()
178      */
getVertexCount()179     public int getVertexCount() {
180         return mesh.getVertexCount();
181     }
182 
183     /**
184      * Returns this geometry's mesh triangle count.
185      *
186      * @return this geometry's mesh triangle count.
187      *
188      * @see Mesh#getTriangleCount()
189      */
getTriangleCount()190     public int getTriangleCount() {
191         return mesh.getTriangleCount();
192     }
193 
194     /**
195      * Sets the mesh to use for this geometry when rendering.
196      *
197      * @param mesh the mesh to use for this geometry
198      *
199      * @throws IllegalArgumentException If mesh is null
200      */
setMesh(Mesh mesh)201     public void setMesh(Mesh mesh) {
202         if (mesh == null) {
203             throw new IllegalArgumentException();
204         }
205         if (isBatched()) {
206             throw new UnsupportedOperationException("Cannot set the mesh of a batched geometry");
207         }
208 
209         this.mesh = mesh;
210         setBoundRefresh();
211     }
212 
213     /**
214      * Returns the mseh to use for this geometry
215      *
216      * @return the mseh to use for this geometry
217      *
218      * @see #setMesh(com.jme3.scene.Mesh)
219      */
getMesh()220     public Mesh getMesh() {
221         return mesh;
222     }
223 
224     /**
225      * Sets the material to use for this geometry.
226      *
227      * @param material the material to use for this geometry
228      */
229     @Override
setMaterial(Material material)230     public void setMaterial(Material material) {
231         if (isBatched()) {
232             throw new UnsupportedOperationException("Cannot set the material of a batched geometry, change the material of the parent BatchNode.");
233         }
234         this.material = material;
235     }
236 
237     /**
238      * Returns the material that is used for this geometry.
239      *
240      * @return the material that is used for this geometry
241      *
242      * @see #setMaterial(com.jme3.material.Material)
243      */
getMaterial()244     public Material getMaterial() {
245         return material;
246     }
247 
248     /**
249      * @return The bounding volume of the mesh, in model space.
250      */
getModelBound()251     public BoundingVolume getModelBound() {
252         return mesh.getBound();
253     }
254 
255     /**
256      * Updates the bounding volume of the mesh. Should be called when the
257      * mesh has been modified.
258      */
updateModelBound()259     public void updateModelBound() {
260         mesh.updateBound();
261         setBoundRefresh();
262     }
263 
264     /**
265      * <code>updateWorldBound</code> updates the bounding volume that contains
266      * this geometry. The location of the geometry is based on the location of
267      * all this node's parents.
268      *
269      * @see Spatial#updateWorldBound()
270      */
271     @Override
updateWorldBound()272     protected void updateWorldBound() {
273         super.updateWorldBound();
274         if (mesh == null) {
275             throw new NullPointerException("Geometry: " + getName() + " has null mesh");
276         }
277 
278         if (mesh.getBound() != null) {
279             if (ignoreTransform) {
280                 // we do not transform the model bound by the world transform,
281                 // just use the model bound as-is
282                 worldBound = mesh.getBound().clone(worldBound);
283             } else {
284                 worldBound = mesh.getBound().transform(worldTransform, worldBound);
285             }
286         }
287     }
288 
289     @Override
updateWorldTransforms()290     protected void updateWorldTransforms() {
291 
292         super.updateWorldTransforms();
293         computeWorldMatrix();
294 
295         if (isBatched()) {
296             computeOffsetTransform();
297             batchNode.updateSubBatch(this);
298             prevBatchTransforms.set(batchNode.getTransforms(this));
299 
300         }
301         // geometry requires lights to be sorted
302         worldLights.sort(true);
303     }
304 
305     /**
306      * Batch this geometry, should only be called by the BatchNode.
307      * @param node the batchNode
308      * @param startIndex the starting index of this geometry in the batched mesh
309      */
batch(BatchNode node, int startIndex)310     protected void batch(BatchNode node, int startIndex) {
311         this.batchNode = node;
312         this.startIndex = startIndex;
313         prevBatchTransforms = new Transform();
314         cachedOffsetMat = new Matrix4f();
315         setCullHint(CullHint.Always);
316     }
317 
318     /**
319      * unBatch this geometry.
320      */
unBatch()321     protected void unBatch() {
322         this.startIndex = 0;
323         prevBatchTransforms = null;
324         cachedOffsetMat = null;
325         //once the geometry is removed from the screnegraph the batchNode needs to be rebatched.
326         this.batchNode.setNeedsFullRebatch(true);
327         this.batchNode = null;
328         setCullHint(CullHint.Dynamic);
329     }
330 
331     @Override
removeFromParent()332     public boolean removeFromParent() {
333         boolean removed = super.removeFromParent();
334         //if the geometry is batched we also have to unbatch it
335         if (isBatched()) {
336             unBatch();
337         }
338         return removed;
339     }
340 
341     /**
342      * Recomputes the cached offset matrix used when the geometry is batched     *
343      */
computeOffsetTransform()344     public void computeOffsetTransform() {
345         TempVars vars = TempVars.get();
346         Matrix4f tmpMat = vars.tempMat42;
347 
348         // Compute the cached world matrix
349         cachedOffsetMat.loadIdentity();
350         cachedOffsetMat.setRotationQuaternion(prevBatchTransforms.getRotation());
351         cachedOffsetMat.setTranslation(prevBatchTransforms.getTranslation());
352 
353 
354         Matrix4f scaleMat = vars.tempMat4;
355         scaleMat.loadIdentity();
356         scaleMat.scale(prevBatchTransforms.getScale());
357         cachedOffsetMat.multLocal(scaleMat);
358         cachedOffsetMat.invertLocal();
359 
360         tmpMat.loadIdentity();
361         tmpMat.setRotationQuaternion(batchNode.getTransforms(this).getRotation());
362         tmpMat.setTranslation(batchNode.getTransforms(this).getTranslation());
363         scaleMat.loadIdentity();
364         scaleMat.scale(batchNode.getTransforms(this).getScale());
365         tmpMat.multLocal(scaleMat);
366 
367         tmpMat.mult(cachedOffsetMat, cachedOffsetMat);
368 
369         vars.release();
370     }
371 
372     /**
373      * Indicate that the transform of this spatial has changed and that
374      * a refresh is required.
375      */
376     @Override
setTransformRefresh()377     protected void setTransformRefresh() {
378         refreshFlags |= RF_TRANSFORM;
379         setBoundRefresh();
380     }
381 
382     /**
383      * Recomputes the matrix returned by {@link Geometry#getWorldMatrix() }.
384      * This will require a localized transform update for this geometry.
385      */
computeWorldMatrix()386     public void computeWorldMatrix() {
387         // Force a local update of the geometry's transform
388         checkDoTransformUpdate();
389 
390         // Compute the cached world matrix
391         cachedWorldMat.loadIdentity();
392         cachedWorldMat.setRotationQuaternion(worldTransform.getRotation());
393         cachedWorldMat.setTranslation(worldTransform.getTranslation());
394 
395         TempVars vars = TempVars.get();
396         Matrix4f scaleMat = vars.tempMat4;
397         scaleMat.loadIdentity();
398         scaleMat.scale(worldTransform.getScale());
399         cachedWorldMat.multLocal(scaleMat);
400         vars.release();
401     }
402 
403     /**
404      * A {@link Matrix4f matrix} that transforms the {@link Geometry#getMesh() mesh}
405      * from model space to world space. This matrix is computed based on the
406      * {@link Geometry#getWorldTransform() world transform} of this geometry.
407      * In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() }
408      * before using this method.
409      *
410      * @return Matrix to transform from local space to world space
411      */
getWorldMatrix()412     public Matrix4f getWorldMatrix() {
413         return cachedWorldMat;
414     }
415 
416     /**
417      * Sets the model bound to use for this geometry.
418      * This alters the bound used on the mesh as well via
419      * {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and
420      * forces the world bounding volume to be recomputed.
421      *
422      * @param modelBound The model bound to set
423      */
424     @Override
setModelBound(BoundingVolume modelBound)425     public void setModelBound(BoundingVolume modelBound) {
426         this.worldBound = null;
427         mesh.setBound(modelBound);
428         setBoundRefresh();
429 
430         // NOTE: Calling updateModelBound() would cause the mesh
431         // to recompute the bound based on the geometry thus making
432         // this call useless!
433         //updateModelBound();
434     }
435 
collideWith(Collidable other, CollisionResults results)436     public int collideWith(Collidable other, CollisionResults results) {
437         // Force bound to update
438         checkDoBoundUpdate();
439         // Update transform, and compute cached world matrix
440         computeWorldMatrix();
441 
442         assert (refreshFlags & (RF_BOUND | RF_TRANSFORM)) == 0;
443 
444         if (mesh != null) {
445             // NOTE: BIHTree in mesh already checks collision with the
446             // mesh's bound
447             int prevSize = results.size();
448             int added = mesh.collideWith(other, cachedWorldMat, worldBound, results);
449             int newSize = results.size();
450             for (int i = prevSize; i < newSize; i++) {
451                 results.getCollisionDirect(i).setGeometry(this);
452             }
453             return added;
454         }
455         return 0;
456     }
457 
458     @Override
depthFirstTraversal(SceneGraphVisitor visitor)459     public void depthFirstTraversal(SceneGraphVisitor visitor) {
460         visitor.visit(this);
461     }
462 
463     @Override
breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue)464     protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
465     }
466 
isBatched()467     public boolean isBatched() {
468         return batchNode != null;
469     }
470 
471     /**
472      * This version of clone is a shallow clone, in other words, the
473      * same mesh is referenced as the original geometry.
474      * Exception: if the mesh is marked as being a software
475      * animated mesh, (bind pose is set) then the positions
476      * and normals are deep copied.
477      */
478     @Override
clone(boolean cloneMaterial)479     public Geometry clone(boolean cloneMaterial) {
480         Geometry geomClone = (Geometry) super.clone(cloneMaterial);
481         geomClone.cachedWorldMat = cachedWorldMat.clone();
482         if (material != null) {
483             if (cloneMaterial) {
484                 geomClone.material = material.clone();
485             } else {
486                 geomClone.material = material;
487             }
488         }
489 
490         if (mesh != null && mesh.getBuffer(Type.BindPosePosition) != null) {
491             geomClone.mesh = mesh.cloneForAnim();
492         }
493 
494         return geomClone;
495     }
496 
497     /**
498      * This version of clone is a shallow clone, in other words, the
499      * same mesh is referenced as the original geometry.
500      * Exception: if the mesh is marked as being a software
501      * animated mesh, (bind pose is set) then the positions
502      * and normals are deep copied.
503      */
504     @Override
clone()505     public Geometry clone() {
506         return clone(true);
507     }
508 
509     /**
510      * Creates a deep clone of the geometry,
511      * this creates an identical copy of the mesh
512      * with the vertexbuffer data duplicated.
513      */
514     @Override
deepClone()515     public Spatial deepClone() {
516         Geometry geomClone = clone(true);
517         geomClone.mesh = mesh.deepClone();
518         return geomClone;
519     }
520 
521     @Override
write(JmeExporter ex)522     public void write(JmeExporter ex) throws IOException {
523         super.write(ex);
524         OutputCapsule oc = ex.getCapsule(this);
525         oc.write(mesh, "mesh", null);
526         if (material != null) {
527             oc.write(material.getAssetName(), "materialName", null);
528         }
529         oc.write(material, "material", null);
530         oc.write(ignoreTransform, "ignoreTransform", false);
531     }
532 
533     @Override
read(JmeImporter im)534     public void read(JmeImporter im) throws IOException {
535         super.read(im);
536         InputCapsule ic = im.getCapsule(this);
537         mesh = (Mesh) ic.readSavable("mesh", null);
538 
539         material = null;
540         String matName = ic.readString("materialName", null);
541         if (matName != null) {
542             // Material name is set,
543             // Attempt to load material via J3M
544             try {
545                 material = im.getAssetManager().loadMaterial(matName);
546             } catch (AssetNotFoundException ex) {
547                 // Cannot find J3M file.
548                 logger.log(Level.FINE, "Cannot locate {0} for geometry {1}", new Object[]{matName, key});
549             }
550         }
551         // If material is NULL, try to load it from the geometry
552         if (material == null) {
553             material = (Material) ic.readSavable("material", null);
554         }
555         ignoreTransform = ic.readBoolean("ignoreTransform", false);
556 
557         if (ic.getSavableVersion(Geometry.class) == 0){
558             // Fix shared mesh (if set)
559             Mesh sharedMesh = getUserData(UserData.JME_SHAREDMESH);
560             if (sharedMesh != null){
561                 getMesh().extractVertexData(sharedMesh);
562             }
563         }
564     }
565 }
566