Home | History | Annotate | Download | only in scene
      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      */
     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      */
    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      */
    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      */
    131     public boolean isIgnoreTransform() {
    132         return ignoreTransform;
    133     }
    134 
    135     /**
    136      * @param ignoreTransform If true, the geometry's transform will not be applied.
    137      */
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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      */
    244     public Material getMaterial() {
    245         return material;
    246     }
    247 
    248     /**
    249      * @return The bounding volume of the mesh, in model space.
    250      */
    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      */
    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
    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
    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      */
    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      */
    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
    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      */
    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
    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      */
    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      */
    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
    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 
    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
    459     public void depthFirstTraversal(SceneGraphVisitor visitor) {
    460         visitor.visit(this);
    461     }
    462 
    463     @Override
    464     protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
    465     }
    466 
    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
    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
    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
    515     public Spatial deepClone() {
    516         Geometry geomClone = clone(true);
    517         geomClone.mesh = mesh.deepClone();
    518         return geomClone;
    519     }
    520 
    521     @Override
    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
    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