aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/scene/Geometry.java
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/scene/Geometry.java')
-rw-r--r--engine/src/core/com/jme3/scene/Geometry.java565
1 files changed, 565 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/scene/Geometry.java b/engine/src/core/com/jme3/scene/Geometry.java
new file mode 100644
index 0000000..b02196d
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Geometry.java
@@ -0,0 +1,565 @@
+/*
+ * 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;
+
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.Queue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Geometry</code> defines a leaf node of the scene graph. The leaf node
+ * contains the geometric data for rendering objects. It manages all rendering
+ * information such as a {@link Material} object to define how the surface
+ * should be shaded and the {@link Mesh} data to contain the actual geometry.
+ *
+ * @author Kirill Vainer
+ */
+public class Geometry extends Spatial {
+
+ // Version #1: removed shared meshes.
+ // models loaded with shared mesh will be automatically fixed.
+ public static final int SAVABLE_VERSION = 1;
+
+ private static final Logger logger = Logger.getLogger(Geometry.class.getName());
+ protected Mesh mesh;
+ protected transient int lodLevel = 0;
+ protected Material material;
+ /**
+ * When true, the geometry's transform will not be applied.
+ */
+ protected boolean ignoreTransform = false;
+ protected transient Matrix4f cachedWorldMat = new Matrix4f();
+ /**
+ * used when geometry is batched
+ */
+ protected BatchNode batchNode = null;
+ /**
+ * the start index of this geom's mesh in the batchNode mesh
+ */
+ protected int startIndex;
+ /**
+ * the previous transforms of the geometry used to compute world transforms
+ */
+ protected Transform prevBatchTransforms = null;
+ /**
+ * the cached offset matrix used when the geometry is batched
+ */
+ protected Matrix4f cachedOffsetMat = null;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Geometry() {
+ }
+
+ /**
+ * Create a geometry node without any mesh data.
+ * Both the mesh and the material are null, the geometry
+ * cannot be rendered until those are set.
+ *
+ * @param name The name of this geometry
+ */
+ public Geometry(String name) {
+ super(name);
+ }
+
+ /**
+ * Create a geometry node with mesh data.
+ * The material of the geometry is null, it cannot
+ * be rendered until it is set.
+ *
+ * @param name The name of this geometry
+ * @param mesh The mesh data for this geometry
+ */
+ public Geometry(String name, Mesh mesh) {
+ this(name);
+ if (mesh == null) {
+ throw new NullPointerException();
+ }
+
+ this.mesh = mesh;
+ }
+
+ /**
+ * @return If ignoreTransform mode is set.
+ *
+ * @see Geometry#setIgnoreTransform(boolean)
+ */
+ public boolean isIgnoreTransform() {
+ return ignoreTransform;
+ }
+
+ /**
+ * @param ignoreTransform If true, the geometry's transform will not be applied.
+ */
+ public void setIgnoreTransform(boolean ignoreTransform) {
+ this.ignoreTransform = ignoreTransform;
+ }
+
+ /**
+ * Sets the LOD level to use when rendering the mesh of this geometry.
+ * Level 0 indicates that the default index buffer should be used,
+ * levels [1, LodLevels + 1] represent the levels set on the mesh
+ * with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
+ *
+ * @param lod The lod level to set
+ */
+ @Override
+ public void setLodLevel(int lod) {
+ if (mesh.getNumLodLevels() == 0) {
+ throw new IllegalStateException("LOD levels are not set on this mesh");
+ }
+
+ if (lod < 0 || lod >= mesh.getNumLodLevels()) {
+ throw new IllegalArgumentException("LOD level is out of range: " + lod);
+ }
+
+ lodLevel = lod;
+ }
+
+ /**
+ * Returns the LOD level set with {@link #setLodLevel(int) }.
+ *
+ * @return the LOD level set
+ */
+ public int getLodLevel() {
+ return lodLevel;
+ }
+
+ /**
+ * Returns this geometry's mesh vertex count.
+ *
+ * @return this geometry's mesh vertex count.
+ *
+ * @see Mesh#getVertexCount()
+ */
+ public int getVertexCount() {
+ return mesh.getVertexCount();
+ }
+
+ /**
+ * Returns this geometry's mesh triangle count.
+ *
+ * @return this geometry's mesh triangle count.
+ *
+ * @see Mesh#getTriangleCount()
+ */
+ public int getTriangleCount() {
+ return mesh.getTriangleCount();
+ }
+
+ /**
+ * Sets the mesh to use for this geometry when rendering.
+ *
+ * @param mesh the mesh to use for this geometry
+ *
+ * @throws IllegalArgumentException If mesh is null
+ */
+ public void setMesh(Mesh mesh) {
+ if (mesh == null) {
+ throw new IllegalArgumentException();
+ }
+ if (isBatched()) {
+ throw new UnsupportedOperationException("Cannot set the mesh of a batched geometry");
+ }
+
+ this.mesh = mesh;
+ setBoundRefresh();
+ }
+
+ /**
+ * Returns the mseh to use for this geometry
+ *
+ * @return the mseh to use for this geometry
+ *
+ * @see #setMesh(com.jme3.scene.Mesh)
+ */
+ public Mesh getMesh() {
+ return mesh;
+ }
+
+ /**
+ * Sets the material to use for this geometry.
+ *
+ * @param material the material to use for this geometry
+ */
+ @Override
+ public void setMaterial(Material material) {
+ if (isBatched()) {
+ throw new UnsupportedOperationException("Cannot set the material of a batched geometry, change the material of the parent BatchNode.");
+ }
+ this.material = material;
+ }
+
+ /**
+ * Returns the material that is used for this geometry.
+ *
+ * @return the material that is used for this geometry
+ *
+ * @see #setMaterial(com.jme3.material.Material)
+ */
+ public Material getMaterial() {
+ return material;
+ }
+
+ /**
+ * @return The bounding volume of the mesh, in model space.
+ */
+ public BoundingVolume getModelBound() {
+ return mesh.getBound();
+ }
+
+ /**
+ * Updates the bounding volume of the mesh. Should be called when the
+ * mesh has been modified.
+ */
+ public void updateModelBound() {
+ mesh.updateBound();
+ setBoundRefresh();
+ }
+
+ /**
+ * <code>updateWorldBound</code> updates the bounding volume that contains
+ * this geometry. The location of the geometry is based on the location of
+ * all this node's parents.
+ *
+ * @see Spatial#updateWorldBound()
+ */
+ @Override
+ protected void updateWorldBound() {
+ super.updateWorldBound();
+ if (mesh == null) {
+ throw new NullPointerException("Geometry: " + getName() + " has null mesh");
+ }
+
+ if (mesh.getBound() != null) {
+ if (ignoreTransform) {
+ // we do not transform the model bound by the world transform,
+ // just use the model bound as-is
+ worldBound = mesh.getBound().clone(worldBound);
+ } else {
+ worldBound = mesh.getBound().transform(worldTransform, worldBound);
+ }
+ }
+ }
+
+ @Override
+ protected void updateWorldTransforms() {
+
+ super.updateWorldTransforms();
+ computeWorldMatrix();
+
+ if (isBatched()) {
+ computeOffsetTransform();
+ batchNode.updateSubBatch(this);
+ prevBatchTransforms.set(batchNode.getTransforms(this));
+
+ }
+ // geometry requires lights to be sorted
+ worldLights.sort(true);
+ }
+
+ /**
+ * Batch this geometry, should only be called by the BatchNode.
+ * @param node the batchNode
+ * @param startIndex the starting index of this geometry in the batched mesh
+ */
+ protected void batch(BatchNode node, int startIndex) {
+ this.batchNode = node;
+ this.startIndex = startIndex;
+ prevBatchTransforms = new Transform();
+ cachedOffsetMat = new Matrix4f();
+ setCullHint(CullHint.Always);
+ }
+
+ /**
+ * unBatch this geometry.
+ */
+ protected void unBatch() {
+ this.startIndex = 0;
+ prevBatchTransforms = null;
+ cachedOffsetMat = null;
+ //once the geometry is removed from the screnegraph the batchNode needs to be rebatched.
+ this.batchNode.setNeedsFullRebatch(true);
+ this.batchNode = null;
+ setCullHint(CullHint.Dynamic);
+ }
+
+ @Override
+ public boolean removeFromParent() {
+ boolean removed = super.removeFromParent();
+ //if the geometry is batched we also have to unbatch it
+ if (isBatched()) {
+ unBatch();
+ }
+ return removed;
+ }
+
+ /**
+ * Recomputes the cached offset matrix used when the geometry is batched *
+ */
+ public void computeOffsetTransform() {
+ TempVars vars = TempVars.get();
+ Matrix4f tmpMat = vars.tempMat42;
+
+ // Compute the cached world matrix
+ cachedOffsetMat.loadIdentity();
+ cachedOffsetMat.setRotationQuaternion(prevBatchTransforms.getRotation());
+ cachedOffsetMat.setTranslation(prevBatchTransforms.getTranslation());
+
+
+ Matrix4f scaleMat = vars.tempMat4;
+ scaleMat.loadIdentity();
+ scaleMat.scale(prevBatchTransforms.getScale());
+ cachedOffsetMat.multLocal(scaleMat);
+ cachedOffsetMat.invertLocal();
+
+ tmpMat.loadIdentity();
+ tmpMat.setRotationQuaternion(batchNode.getTransforms(this).getRotation());
+ tmpMat.setTranslation(batchNode.getTransforms(this).getTranslation());
+ scaleMat.loadIdentity();
+ scaleMat.scale(batchNode.getTransforms(this).getScale());
+ tmpMat.multLocal(scaleMat);
+
+ tmpMat.mult(cachedOffsetMat, cachedOffsetMat);
+
+ vars.release();
+ }
+
+ /**
+ * Indicate that the transform of this spatial has changed and that
+ * a refresh is required.
+ */
+ @Override
+ protected void setTransformRefresh() {
+ refreshFlags |= RF_TRANSFORM;
+ setBoundRefresh();
+ }
+
+ /**
+ * Recomputes the matrix returned by {@link Geometry#getWorldMatrix() }.
+ * This will require a localized transform update for this geometry.
+ */
+ public void computeWorldMatrix() {
+ // Force a local update of the geometry's transform
+ checkDoTransformUpdate();
+
+ // Compute the cached world matrix
+ cachedWorldMat.loadIdentity();
+ cachedWorldMat.setRotationQuaternion(worldTransform.getRotation());
+ cachedWorldMat.setTranslation(worldTransform.getTranslation());
+
+ TempVars vars = TempVars.get();
+ Matrix4f scaleMat = vars.tempMat4;
+ scaleMat.loadIdentity();
+ scaleMat.scale(worldTransform.getScale());
+ cachedWorldMat.multLocal(scaleMat);
+ vars.release();
+ }
+
+ /**
+ * A {@link Matrix4f matrix} that transforms the {@link Geometry#getMesh() mesh}
+ * from model space to world space. This matrix is computed based on the
+ * {@link Geometry#getWorldTransform() world transform} of this geometry.
+ * In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() }
+ * before using this method.
+ *
+ * @return Matrix to transform from local space to world space
+ */
+ public Matrix4f getWorldMatrix() {
+ return cachedWorldMat;
+ }
+
+ /**
+ * Sets the model bound to use for this geometry.
+ * This alters the bound used on the mesh as well via
+ * {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and
+ * forces the world bounding volume to be recomputed.
+ *
+ * @param modelBound The model bound to set
+ */
+ @Override
+ public void setModelBound(BoundingVolume modelBound) {
+ this.worldBound = null;
+ mesh.setBound(modelBound);
+ setBoundRefresh();
+
+ // NOTE: Calling updateModelBound() would cause the mesh
+ // to recompute the bound based on the geometry thus making
+ // this call useless!
+ //updateModelBound();
+ }
+
+ public int collideWith(Collidable other, CollisionResults results) {
+ // Force bound to update
+ checkDoBoundUpdate();
+ // Update transform, and compute cached world matrix
+ computeWorldMatrix();
+
+ assert (refreshFlags & (RF_BOUND | RF_TRANSFORM)) == 0;
+
+ if (mesh != null) {
+ // NOTE: BIHTree in mesh already checks collision with the
+ // mesh's bound
+ int prevSize = results.size();
+ int added = mesh.collideWith(other, cachedWorldMat, worldBound, results);
+ int newSize = results.size();
+ for (int i = prevSize; i < newSize; i++) {
+ results.getCollisionDirect(i).setGeometry(this);
+ }
+ return added;
+ }
+ return 0;
+ }
+
+ @Override
+ public void depthFirstTraversal(SceneGraphVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
+ }
+
+ public boolean isBatched() {
+ return batchNode != null;
+ }
+
+ /**
+ * This version of clone is a shallow clone, in other words, the
+ * same mesh is referenced as the original geometry.
+ * Exception: if the mesh is marked as being a software
+ * animated mesh, (bind pose is set) then the positions
+ * and normals are deep copied.
+ */
+ @Override
+ public Geometry clone(boolean cloneMaterial) {
+ Geometry geomClone = (Geometry) super.clone(cloneMaterial);
+ geomClone.cachedWorldMat = cachedWorldMat.clone();
+ if (material != null) {
+ if (cloneMaterial) {
+ geomClone.material = material.clone();
+ } else {
+ geomClone.material = material;
+ }
+ }
+
+ if (mesh != null && mesh.getBuffer(Type.BindPosePosition) != null) {
+ geomClone.mesh = mesh.cloneForAnim();
+ }
+
+ return geomClone;
+ }
+
+ /**
+ * This version of clone is a shallow clone, in other words, the
+ * same mesh is referenced as the original geometry.
+ * Exception: if the mesh is marked as being a software
+ * animated mesh, (bind pose is set) then the positions
+ * and normals are deep copied.
+ */
+ @Override
+ public Geometry clone() {
+ return clone(true);
+ }
+
+ /**
+ * Creates a deep clone of the geometry,
+ * this creates an identical copy of the mesh
+ * with the vertexbuffer data duplicated.
+ */
+ @Override
+ public Spatial deepClone() {
+ Geometry geomClone = clone(true);
+ geomClone.mesh = mesh.deepClone();
+ return geomClone;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(mesh, "mesh", null);
+ if (material != null) {
+ oc.write(material.getAssetName(), "materialName", null);
+ }
+ oc.write(material, "material", null);
+ oc.write(ignoreTransform, "ignoreTransform", false);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ mesh = (Mesh) ic.readSavable("mesh", null);
+
+ material = null;
+ String matName = ic.readString("materialName", null);
+ if (matName != null) {
+ // Material name is set,
+ // Attempt to load material via J3M
+ try {
+ material = im.getAssetManager().loadMaterial(matName);
+ } catch (AssetNotFoundException ex) {
+ // Cannot find J3M file.
+ logger.log(Level.FINE, "Cannot locate {0} for geometry {1}", new Object[]{matName, key});
+ }
+ }
+ // If material is NULL, try to load it from the geometry
+ if (material == null) {
+ material = (Material) ic.readSavable("material", null);
+ }
+ ignoreTransform = ic.readBoolean("ignoreTransform", false);
+
+ if (ic.getSavableVersion(Geometry.class) == 0){
+ // Fix shared mesh (if set)
+ Mesh sharedMesh = getUserData(UserData.JME_SHAREDMESH);
+ if (sharedMesh != null){
+ getMesh().extractVertexData(sharedMesh);
+ }
+ }
+ }
+}