com.jme3.scene.Geometry Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jme3-core Show documentation
Show all versions of jme3-core Show documentation
jMonkeyEngine is a 3-D game engine for adventurous Java developers
/*
* Copyright (c) 2009-2021 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.renderer.Camera;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.MorphTarget;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.IdentityCloneFunction;
import java.io.IOException;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Geometry
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();
/**
* Specifies which {@link GeometryGroupNode} this Geometry
* is managed by.
*/
protected GeometryGroupNode groupNode;
/**
* The start index of this Geometry's
inside
* the {@link GeometryGroupNode}.
*/
protected int startIndex = -1;
/**
* Morph state variable for morph animation
*/
private float[] morphState;
private boolean dirtyMorph = true;
// a Morph target that will be used to merge all targets that
// can't be handled on the cpu on each frame.
private MorphTarget fallbackMorphTarget;
private int nbSimultaneousGPUMorph = -1;
/**
* Instantiate a Geometry
with no name, no mesh, and no
* material. The mesh and material must be set prior to rendering.
*/
public Geometry() {
this(null);
}
/**
* 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);
// For backwards compatibility, only clear the "requires
// update" flag if we are not a subclass of Geometry.
// This prevents subclass from silently failing to receive
// updates when they upgrade.
setRequiresUpdates(Geometry.class != getClass());
}
/**
* 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 IllegalArgumentException("mesh cannot be null");
}
this.mesh = mesh;
}
@Override
public boolean checkCulling(Camera cam) {
if (isGrouped()) {
setLastFrustumIntersection(Camera.FrustumIntersect.Outside);
return false;
}
return super.checkCulling(cam);
}
/**
* @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;
if (isGrouped()) {
groupNode.onMeshChange(this);
}
}
/**
* 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()
*/
@Override
public int getVertexCount() {
return mesh.getVertexCount();
}
/**
* Returns this geometry's mesh triangle count.
*
* @return this geometry's mesh triangle count.
*
* @see Mesh#getTriangleCount()
*/
@Override
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();
}
this.mesh = mesh;
setBoundRefresh();
if (isGrouped()) {
groupNode.onMeshChange(this);
}
}
/**
* Returns the mesh to use for this geometry
*
* @return the mesh 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) {
this.material = material;
nbSimultaneousGPUMorph = -1;
if (isGrouped()) {
groupNode.onMaterialChange(this);
}
}
/**
* 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.
*/
@Override
public void updateModelBound() {
mesh.updateBound();
setBoundRefresh();
}
/**
* updateWorldBound
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 IllegalStateException("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 (isGrouped()) {
groupNode.onTransformChange(this);
}
// geometry requires lights to be sorted
worldLights.sort(true);
}
@Override
protected void updateWorldLightList() {
super.updateWorldLightList();
// geometry requires lights to be sorted
worldLights.sort(true);
}
/**
* Associate this Geometry
with a {@link GeometryGroupNode}.
*
* Should only be called by the parent {@link GeometryGroupNode}.
*
* @param node Which {@link GeometryGroupNode} to associate with.
* @param startIndex The starting index of this geometry in the group.
*/
public void associateWithGroupNode(GeometryGroupNode node, int startIndex) {
if (isGrouped()) {
unassociateFromGroupNode();
}
this.groupNode = node;
this.startIndex = startIndex;
}
/**
* Removes the {@link GeometryGroupNode} association from this
* Geometry
.
*
* Should only be called by the parent {@link GeometryGroupNode}.
*/
public void unassociateFromGroupNode() {
if (groupNode != null) {
// Once the geometry is removed
// from the parent, the group node needs to be updated.
groupNode.onGeometryUnassociated(this);
groupNode = null;
// change the default to -1 to make error detection easier
startIndex = -1;
}
}
@Override
public boolean removeFromParent() {
return super.removeFromParent();
}
@Override
protected void setParent(Node parent) {
super.setParent(parent);
// If the geometry is managed by group node we need to unassociate.
if (parent == null && isGrouped()) {
unassociateFromGroupNode();
}
}
/*
* Indicate that the transform of this spatial has changed and that
* a refresh is required.
*/
// NOTE: Spatial has an identical implementation of this method,
// thus it was commented out.
// @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();
if (ignoreTransform) {
return;
}
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();
}
@Override
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, DFSMode mode) {
visitor.visit(this);
}
@Override
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue) {
}
/**
* Determine whether this Geometry
is managed by a
* {@link GeometryGroupNode} or not.
*
* @return True if managed by a {@link GeometryGroupNode}.
*/
public boolean isGrouped() {
return groupNode != null;
}
/**
* @deprecated Use {@link #isGrouped()} instead.
* @return true if managed by a {@link GeometryGroupNode}
*/
@Deprecated
public boolean isBatched() {
return isGrouped();
}
/**
* 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) {
return (Geometry) super.clone(cloneMaterial);
}
/**
* 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);
}
/**
* Create a deep clone of the geometry. This creates an identical copy of
* the mesh with the vertex buffer data duplicated.
*/
@Override
public Spatial deepClone() {
return super.deepClone();
}
public Spatial oldDeepClone() {
Geometry geomClone = clone(true);
geomClone.mesh = mesh.deepClone();
return geomClone;
}
/**
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
*/
@Override
public void cloneFields(Cloner cloner, Object original) {
super.cloneFields(cloner, original);
// If this is a grouped node and if our group node is
// also cloned then we'll grab its reference.
if (groupNode != null) {
if (cloner.isCloned(groupNode)) {
// Then resolve the reference
this.groupNode = cloner.clone(groupNode);
} else {
// We are on our own now
this.groupNode = null;
this.startIndex = -1;
}
// The above is based on the fact that if we were
// cloning the hierarchy that contained the parent
// group then it would have been shallow cloned before
// this child. Can't really be otherwise.
}
this.cachedWorldMat = cloner.clone(cachedWorldMat);
// See if we are doing a shallow clone or a deep mesh clone
boolean shallowClone = (cloner.getCloneFunction(Mesh.class) instanceof IdentityCloneFunction);
// See if we clone the mesh using the special animation
// semi-deep cloning
if (shallowClone && mesh != null && mesh.getBuffer(Type.BindPosePosition) != null) {
// Then we need to clone the mesh a little deeper
this.mesh = mesh.cloneForAnim();
} else {
// Do whatever the cloner wants to do about it
this.mesh = cloner.clone(mesh);
}
this.material = cloner.clone(material);
}
public void setMorphState(float[] state) {
if (mesh == null || mesh.getMorphTargets().length == 0) {
return;
}
int nbMorphTargets = mesh.getMorphTargets().length;
if (morphState == null) {
morphState = new float[nbMorphTargets];
}
System.arraycopy(state, 0, morphState, 0, morphState.length);
this.dirtyMorph = true;
}
/**
* Set the state of the morph with the given name.
*
* If the name of the morph is not found, no state will be set.
*
* @param morphTarget The name of the morph to set the state of
* @param state The state to set the morph to
*/
public void setMorphState(String morphTarget, float state) {
int index = mesh.getMorphIndex(morphTarget);
if (index >= 0) {
morphState[index] = state;
this.dirtyMorph = true;
}
}
/**
* returns true if the morph state has changed on the last frame.
*
* @return true if changed, otherwise false
*/
public boolean isDirtyMorph() {
return dirtyMorph;
}
/**
* Setting this to true will stop this geometry morph buffer to be updated,
* unless the morph state changes
*
* @param dirtyMorph true→prevent updating, false→allow updating
*/
public void setDirtyMorph(boolean dirtyMorph) {
this.dirtyMorph = dirtyMorph;
}
/**
* returns the morph state of this Geometry.
* Used internally by the MorphControl.
*
* @return an array
*/
public float[] getMorphState() {
if (morphState == null) {
morphState = new float[mesh.getMorphTargets().length];
}
return morphState;
}
/**
* Get the state of a morph
*
* @param morphTarget the name of the morph to get the state of
* @return the state of the morph, or -1 if the morph is not found
*/
public float getMorphState(String morphTarget) {
int index = mesh.getMorphIndex(morphTarget);
if (index < 0) {
return -1;
} else {
return morphState[index];
}
}
/**
* Return the number of morph targets that can be handled
* on the GPU simultaneously for this geometry.
* Note that it depends on the material set on this geometry.
* This number is computed and set by the MorphControl,
* so it might be available only after the first frame.
* Else it's set to -1.
*
* @return the number of simultaneous morph targets handled on the GPU
*/
public int getNbSimultaneousGPUMorph() {
return nbSimultaneousGPUMorph;
}
/**
* Sets the number of morph targets that can be handled
* on the GPU simultaneously for this geometry.
* Note that it depends on the material set on this geometry.
* This number is computed and set by the MorphControl,
* so it might be available only after the first frame.
* Else it's set to -1.
* WARNING: setting this manually might crash the shader compilation if set too high.
* Do it at your own risk.
*
* @param nbSimultaneousGPUMorph the number of simultaneous morph targets to be handled on the GPU.
*/
public void setNbSimultaneousGPUMorph(int nbSimultaneousGPUMorph) {
this.nbSimultaneousGPUMorph = nbSimultaneousGPUMorph;
}
public MorphTarget getFallbackMorphTarget() {
return fallbackMorphTarget;
}
public void setFallbackMorphTarget(MorphTarget fallbackMorphTarget) {
this.fallbackMorphTarget = fallbackMorphTarget;
}
@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.
if (logger.isLoggable(Level.FINE)) {
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);
setUserData(UserData.JME_SHAREDMESH, null);
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy