diff options
Diffstat (limited to 'engine/src/core/com/jme3/scene/Spatial.java')
-rw-r--r-- | engine/src/core/com/jme3/scene/Spatial.java | 1478 |
1 files changed, 1478 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/scene/Spatial.java b/engine/src/core/com/jme3/scene/Spatial.java new file mode 100644 index 0000000..3785d6e --- /dev/null +++ b/engine/src/core/com/jme3/scene/Spatial.java @@ -0,0 +1,1478 @@ +/* + * 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.Asset; +import com.jme3.asset.AssetKey; +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.export.*; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.control.Control; +import com.jme3.util.SafeArrayList; +import com.jme3.util.TempVars; +import java.io.IOException; +import java.util.*; +import java.util.logging.Logger; + +/** + * <code>Spatial</code> defines the base class for scene graph nodes. It + * maintains a link to a parent, it's local transforms and the world's + * transforms. All other scene graph elements, such as {@link Node} and + * {@link Geometry} are subclasses of <code>Spatial</code>. + * + * @author Mark Powell + * @author Joshua Slack + * @version $Revision: 4075 $, $Data$ + */ +public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { + + private static final Logger logger = Logger.getLogger(Spatial.class.getName()); + + /** + * Specifies how frustum culling should be handled by + * this spatial. + */ + public enum CullHint { + + /** + * Do whatever our parent does. If no parent, default to {@link #Dynamic}. + */ + Inherit, + /** + * Do not draw if we are not at least partially within the view frustum + * of the camera. This is determined via the defined + * Camera planes whether or not this Spatial should be culled. + */ + Dynamic, + /** + * Always cull this from the view, throwing away this object + * and any children from rendering commands. + */ + Always, + /** + * Never cull this from view, always draw it. + * Note that we will still get culled if our parent is culled. + */ + Never; + } + + /** + * Specifies if this spatial should be batched + */ + public enum BatchHint { + + /** + * Do whatever our parent does. If no parent, default to {@link #Always}. + */ + Inherit, + /** + * This spatial will always be batched when attached to a BatchNode. + */ + Always, + /** + * This spatial will never be batched when attached to a BatchNode. + */ + Never; + } + /** + * Refresh flag types + */ + protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms + RF_BOUND = 0x02, + RF_LIGHTLIST = 0x04; // changes in light lists + protected CullHint cullHint = CullHint.Inherit; + protected BatchHint batchHint = BatchHint.Inherit; + /** + * Spatial's bounding volume relative to the world. + */ + protected BoundingVolume worldBound; + /** + * LightList + */ + protected LightList localLights; + protected transient LightList worldLights; + /** + * This spatial's name. + */ + protected String name; + // scale values + protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects; + protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit; + protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit; + public transient float queueDistance = Float.NEGATIVE_INFINITY; + protected Transform localTransform; + protected Transform worldTransform; + protected SafeArrayList<Control> controls = new SafeArrayList<Control>(Control.class); + protected HashMap<String, Savable> userData = null; + /** + * Used for smart asset caching + * + * @see AssetKey#useSmartCache() + */ + protected AssetKey key; + /** + * Spatial's parent, or null if it has none. + */ + protected transient Node parent; + /** + * Refresh flags. Indicate what data of the spatial need to be + * updated to reflect the correct state. + */ + protected transient int refreshFlags = 0; + + /** + * Serialization only. Do not use. + */ + public Spatial() { + localTransform = new Transform(); + worldTransform = new Transform(); + + localLights = new LightList(this); + worldLights = new LightList(this); + + refreshFlags |= RF_BOUND; + } + + /** + * Constructor instantiates a new <code>Spatial</code> object setting the + * rotation, translation and scale value to defaults. + * + * @param name + * the name of the scene element. This is required for + * identification and comparison purposes. + */ + public Spatial(String name) { + this(); + this.name = name; + } + + public void setKey(AssetKey key) { + this.key = key; + } + + public AssetKey getKey() { + return key; + } + + /** + * Indicate that the transform of this spatial has changed and that + * a refresh is required. + */ + protected void setTransformRefresh() { + refreshFlags |= RF_TRANSFORM; + setBoundRefresh(); + } + + protected void setLightListRefresh() { + refreshFlags |= RF_LIGHTLIST; + } + + /** + * Indicate that the bounding of this spatial has changed and that + * a refresh is required. + */ + protected void setBoundRefresh() { + refreshFlags |= RF_BOUND; + + // XXX: Replace with a recursive call? + Spatial p = parent; + while (p != null) { + if ((p.refreshFlags & RF_BOUND) != 0) { + return; + } + + p.refreshFlags |= RF_BOUND; + p = p.parent; + } + } + + /** + * <code>checkCulling</code> checks the spatial with the camera to see if it + * should be culled. + * <p> + * This method is called by the renderer. Usually it should not be called + * directly. + * + * @param cam The camera to check against. + * @return true if inside or intersecting camera frustum + * (should be rendered), false if outside. + */ + public boolean checkCulling(Camera cam) { + if (refreshFlags != 0) { + throw new IllegalStateException("Scene graph is not properly updated for rendering.\n" + + "State was changed after rootNode.updateGeometricState() call. \n" + + "Make sure you do not modify the scene from another thread!\n" + + "Problem spatial name: " + getName()); + } + + CullHint cm = getCullHint(); + assert cm != CullHint.Inherit; + if (cm == Spatial.CullHint.Always) { + setLastFrustumIntersection(Camera.FrustumIntersect.Outside); + return false; + } else if (cm == Spatial.CullHint.Never) { + setLastFrustumIntersection(Camera.FrustumIntersect.Intersects); + return true; + } + + // check to see if we can cull this node + frustrumIntersects = (parent != null ? parent.frustrumIntersects + : Camera.FrustumIntersect.Intersects); + + if (frustrumIntersects == Camera.FrustumIntersect.Intersects) { + if (getQueueBucket() == Bucket.Gui) { + return cam.containsGui(getWorldBound()); + } else { + frustrumIntersects = cam.contains(getWorldBound()); + } + } + + return frustrumIntersects != Camera.FrustumIntersect.Outside; + } + + /** + * Sets the name of this spatial. + * + * @param name + * The spatial's new name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the name of this spatial. + * + * @return This spatial's name. + */ + public String getName() { + return name; + } + + /** + * Returns the local {@link LightList}, which are the lights + * that were directly attached to this <code>Spatial</code> through the + * {@link #addLight(com.jme3.light.Light) } and + * {@link #removeLight(com.jme3.light.Light) } methods. + * + * @return The local light list + */ + public LightList getLocalLightList() { + return localLights; + } + + /** + * Returns the world {@link LightList}, containing the lights + * combined from all this <code>Spatial's</code> parents up to and including + * this <code>Spatial</code>'s lights. + * + * @return The combined world light list + */ + public LightList getWorldLightList() { + return worldLights; + } + + /** + * <code>getWorldRotation</code> retrieves the absolute rotation of the + * Spatial. + * + * @return the Spatial's world rotation quaternion. + */ + public Quaternion getWorldRotation() { + checkDoTransformUpdate(); + return worldTransform.getRotation(); + } + + /** + * <code>getWorldTranslation</code> retrieves the absolute translation of + * the spatial. + * + * @return the Spatial's world tranlsation vector. + */ + public Vector3f getWorldTranslation() { + checkDoTransformUpdate(); + return worldTransform.getTranslation(); + } + + /** + * <code>getWorldScale</code> retrieves the absolute scale factor of the + * spatial. + * + * @return the Spatial's world scale factor. + */ + public Vector3f getWorldScale() { + checkDoTransformUpdate(); + return worldTransform.getScale(); + } + + /** + * <code>getWorldTransform</code> retrieves the world transformation + * of the spatial. + * + * @return the world transform. + */ + public Transform getWorldTransform() { + checkDoTransformUpdate(); + return worldTransform; + } + + /** + * <code>rotateUpTo</code> is a utility function that alters the + * local rotation to point the Y axis in the direction given by newUp. + * + * @param newUp + * the up vector to use - assumed to be a unit vector. + */ + public void rotateUpTo(Vector3f newUp) { + TempVars vars = TempVars.get(); + + Vector3f compVecA = vars.vect1; + Quaternion q = vars.quat1; + + // First figure out the current up vector. + Vector3f upY = compVecA.set(Vector3f.UNIT_Y); + Quaternion rot = localTransform.getRotation(); + rot.multLocal(upY); + + // get angle between vectors + float angle = upY.angleBetween(newUp); + + // figure out rotation axis by taking cross product + Vector3f rotAxis = upY.crossLocal(newUp).normalizeLocal(); + + // Build a rotation quat and apply current local rotation. + q.fromAngleNormalAxis(angle, rotAxis); + q.mult(rot, rot); + + vars.release(); + + setTransformRefresh(); + } + + /** + * <code>lookAt</code> is a convenience method for auto-setting the local + * rotation based on a position and an up vector. It computes the rotation + * to transform the z-axis to point onto 'position' and the y-axis to 'up'. + * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } + * this method takes a world position to look at and not a relative direction. + * + * @param position + * where to look at in terms of world coordinates + * @param upVector + * a vector indicating the (local) up direction. (typically {0, + * 1, 0} in jME.) + */ + public void lookAt(Vector3f position, Vector3f upVector) { + Vector3f worldTranslation = getWorldTranslation(); + + TempVars vars = TempVars.get(); + + Vector3f compVecA = vars.vect4; + vars.release(); + + compVecA.set(position).subtractLocal(worldTranslation); + getLocalRotation().lookAt(compVecA, upVector); + + setTransformRefresh(); + } + + /** + * Should be overridden by Node and Geometry. + */ + protected void updateWorldBound() { + // the world bound of a leaf is the same as it's model bound + // for a node, the world bound is a combination of all it's children + // bounds + // -> handled by subclass + refreshFlags &= ~RF_BOUND; + } + + protected void updateWorldLightList() { + if (parent == null) { + worldLights.update(localLights, null); + refreshFlags &= ~RF_LIGHTLIST; + } else { + if ((parent.refreshFlags & RF_LIGHTLIST) == 0) { + worldLights.update(localLights, parent.worldLights); + refreshFlags &= ~RF_LIGHTLIST; + } else { + assert false; + } + } + } + + /** + * Should only be called from updateGeometricState(). + * In most cases should not be subclassed. + */ + protected void updateWorldTransforms() { + if (parent == null) { + worldTransform.set(localTransform); + refreshFlags &= ~RF_TRANSFORM; + } else { + // check if transform for parent is updated + assert ((parent.refreshFlags & RF_TRANSFORM) == 0); + worldTransform.set(localTransform); + worldTransform.combineWithParent(parent.worldTransform); + refreshFlags &= ~RF_TRANSFORM; + } + } + + /** + * Computes the world transform of this Spatial in the most + * efficient manner possible. + */ + void checkDoTransformUpdate() { + if ((refreshFlags & RF_TRANSFORM) == 0) { + return; + } + + if (parent == null) { + worldTransform.set(localTransform); + refreshFlags &= ~RF_TRANSFORM; + } else { + TempVars vars = TempVars.get(); + + Spatial[] stack = vars.spatialStack; + Spatial rootNode = this; + int i = 0; + while (true) { + Spatial hisParent = rootNode.parent; + if (hisParent == null) { + rootNode.worldTransform.set(rootNode.localTransform); + rootNode.refreshFlags &= ~RF_TRANSFORM; + i--; + break; + } + + stack[i] = rootNode; + + if ((hisParent.refreshFlags & RF_TRANSFORM) == 0) { + break; + } + + rootNode = hisParent; + i++; + } + + vars.release(); + + for (int j = i; j >= 0; j--) { + rootNode = stack[j]; + //rootNode.worldTransform.set(rootNode.localTransform); + //rootNode.worldTransform.combineWithParent(rootNode.parent.worldTransform); + //rootNode.refreshFlags &= ~RF_TRANSFORM; + rootNode.updateWorldTransforms(); + } + } + } + + /** + * Computes this Spatial's world bounding volume in the most efficient + * manner possible. + */ + void checkDoBoundUpdate() { + if ((refreshFlags & RF_BOUND) == 0) { + return; + } + + checkDoTransformUpdate(); + + // Go to children recursively and update their bound + if (this instanceof Node) { + Node node = (Node) this; + int len = node.getQuantity(); + for (int i = 0; i < len; i++) { + Spatial child = node.getChild(i); + child.checkDoBoundUpdate(); + } + } + + // All children's bounds have been updated. Update my own now. + updateWorldBound(); + } + + private void runControlUpdate(float tpf) { + if (controls.isEmpty()) { + return; + } + + for (Control c : controls.getArray()) { + c.update(tpf); + } + } + + /** + * Called when the Spatial is about to be rendered, to notify + * controls attached to this Spatial using the Control.render() method. + * + * @param rm The RenderManager rendering the Spatial. + * @param vp The ViewPort to which the Spatial is being rendered to. + * + * @see Spatial#addControl(com.jme3.scene.control.Control) + * @see Spatial#getControl(java.lang.Class) + */ + public void runControlRender(RenderManager rm, ViewPort vp) { + if (controls.isEmpty()) { + return; + } + + for (Control c : controls.getArray()) { + c.render(rm, vp); + } + } + + /** + * Add a control to the list of controls. + * @param control The control to add. + * + * @see Spatial#removeControl(java.lang.Class) + */ + public void addControl(Control control) { + controls.add(control); + control.setSpatial(this); + } + + /** + * Removes the first control that is an instance of the given class. + * + * @see Spatial#addControl(com.jme3.scene.control.Control) + */ + public void removeControl(Class<? extends Control> controlType) { + for (int i = 0; i < controls.size(); i++) { + if (controlType.isAssignableFrom(controls.get(i).getClass())) { + Control control = controls.remove(i); + control.setSpatial(null); + } + } + } + + /** + * Removes the given control from this spatial's controls. + * + * @param control The control to remove + * @return True if the control was successfuly removed. False if + * the control is not assigned to this spatial. + * + * @see Spatial#addControl(com.jme3.scene.control.Control) + */ + public boolean removeControl(Control control) { + boolean result = controls.remove(control); + if (result) { + control.setSpatial(null); + } + + return result; + } + + /** + * Returns the first control that is an instance of the given class, + * or null if no such control exists. + * + * @param controlType The superclass of the control to look for. + * @return The first instance in the list of the controlType class, or null. + * + * @see Spatial#addControl(com.jme3.scene.control.Control) + */ + public <T extends Control> T getControl(Class<T> controlType) { + for (Control c : controls.getArray()) { + if (controlType.isAssignableFrom(c.getClass())) { + return (T) c; + } + } + return null; + } + + /** + * Returns the control at the given index in the list. + * + * @param index The index of the control in the list to find. + * @return The control at the given index. + * + * @throws IndexOutOfBoundsException + * If the index is outside the range [0, getNumControls()-1] + * + * @see Spatial#addControl(com.jme3.scene.control.Control) + */ + public Control getControl(int index) { + return controls.get(index); + } + + /** + * @return The number of controls attached to this Spatial. + * @see Spatial#addControl(com.jme3.scene.control.Control) + * @see Spatial#removeControl(java.lang.Class) + */ + public int getNumControls() { + return controls.size(); + } + + /** + * <code>updateLogicalState</code> calls the <code>update()</code> method + * for all controls attached to this Spatial. + * + * @param tpf Time per frame. + * + * @see Spatial#addControl(com.jme3.scene.control.Control) + */ + public void updateLogicalState(float tpf) { + runControlUpdate(tpf); + } + + /** + * <code>updateGeometricState</code> updates the lightlist, + * computes the world transforms, and computes the world bounds + * for this Spatial. + * Calling this when the Spatial is attached to a node + * will cause undefined results. User code should only call this + * method on Spatials having no parent. + * + * @see Spatial#getWorldLightList() + * @see Spatial#getWorldTransform() + * @see Spatial#getWorldBound() + */ + public void updateGeometricState() { + // assume that this Spatial is a leaf, a proper implementation + // for this method should be provided by Node. + + // NOTE: Update world transforms first because + // bound transform depends on them. + if ((refreshFlags & RF_LIGHTLIST) != 0) { + updateWorldLightList(); + } + if ((refreshFlags & RF_TRANSFORM) != 0) { + updateWorldTransforms(); + } + if ((refreshFlags & RF_BOUND) != 0) { + updateWorldBound(); + } + + assert refreshFlags == 0; + } + + /** + * Convert a vector (in) from this spatials' local coordinate space to world + * coordinate space. + * + * @param in + * vector to read from + * @param store + * where to write the result (null to create a new vector, may be + * same as in) + * @return the result (store) + */ + public Vector3f localToWorld(final Vector3f in, Vector3f store) { + checkDoTransformUpdate(); + return worldTransform.transformVector(in, store); + } + + /** + * Convert a vector (in) from world coordinate space to this spatials' local + * coordinate space. + * + * @param in + * vector to read from + * @param store + * where to write the result + * @return the result (store) + */ + public Vector3f worldToLocal(final Vector3f in, final Vector3f store) { + checkDoTransformUpdate(); + return worldTransform.transformInverseVector(in, store); + } + + /** + * <code>getParent</code> retrieves this node's parent. If the parent is + * null this is the root node. + * + * @return the parent of this node. + */ + public Node getParent() { + return parent; + } + + /** + * Called by {@link Node#attachChild(Spatial)} and + * {@link Node#detachChild(Spatial)} - don't call directly. + * <code>setParent</code> sets the parent of this node. + * + * @param parent + * the parent of this node. + */ + protected void setParent(Node parent) { + this.parent = parent; + } + + /** + * <code>removeFromParent</code> removes this Spatial from it's parent. + * + * @return true if it has a parent and performed the remove. + */ + public boolean removeFromParent() { + if (parent != null) { + parent.detachChild(this); + return true; + } + return false; + } + + /** + * determines if the provided Node is the parent, or parent's parent, etc. of this Spatial. + * + * @param ancestor + * the ancestor object to look for. + * @return true if the ancestor is found, false otherwise. + */ + public boolean hasAncestor(Node ancestor) { + if (parent == null) { + return false; + } else if (parent.equals(ancestor)) { + return true; + } else { + return parent.hasAncestor(ancestor); + } + } + + /** + * <code>getLocalRotation</code> retrieves the local rotation of this + * node. + * + * @return the local rotation of this node. + */ + public Quaternion getLocalRotation() { + return localTransform.getRotation(); + } + + /** + * <code>setLocalRotation</code> sets the local rotation of this node + * by using a {@link Matrix3f}. + * + * @param rotation + * the new local rotation. + */ + public void setLocalRotation(Matrix3f rotation) { + localTransform.getRotation().fromRotationMatrix(rotation); + setTransformRefresh(); + } + + /** + * <code>setLocalRotation</code> sets the local rotation of this node. + * + * @param quaternion + * the new local rotation. + */ + public void setLocalRotation(Quaternion quaternion) { + localTransform.setRotation(quaternion); + setTransformRefresh(); + } + + /** + * <code>getLocalScale</code> retrieves the local scale of this node. + * + * @return the local scale of this node. + */ + public Vector3f getLocalScale() { + return localTransform.getScale(); + } + + /** + * <code>setLocalScale</code> sets the local scale of this node. + * + * @param localScale + * the new local scale, applied to x, y and z + */ + public void setLocalScale(float localScale) { + localTransform.setScale(localScale); + setTransformRefresh(); + } + + /** + * <code>setLocalScale</code> sets the local scale of this node. + */ + public void setLocalScale(float x, float y, float z) { + localTransform.setScale(x, y, z); + setTransformRefresh(); + } + + /** + * <code>setLocalScale</code> sets the local scale of this node. + * + * @param localScale + * the new local scale. + */ + public void setLocalScale(Vector3f localScale) { + localTransform.setScale(localScale); + setTransformRefresh(); + } + + /** + * <code>getLocalTranslation</code> retrieves the local translation of + * this node. + * + * @return the local translation of this node. + */ + public Vector3f getLocalTranslation() { + return localTransform.getTranslation(); + } + + /** + * <code>setLocalTranslation</code> sets the local translation of this + * spatial. + * + * @param localTranslation + * the local translation of this spatial. + */ + public void setLocalTranslation(Vector3f localTranslation) { + this.localTransform.setTranslation(localTranslation); + setTransformRefresh(); + } + + /** + * <code>setLocalTranslation</code> sets the local translation of this + * spatial. + */ + public void setLocalTranslation(float x, float y, float z) { + this.localTransform.setTranslation(x, y, z); + setTransformRefresh(); + } + + /** + * <code>setLocalTransform</code> sets the local transform of this + * spatial. + */ + public void setLocalTransform(Transform t) { + this.localTransform.set(t); + setTransformRefresh(); + } + + /** + * <code>getLocalTransform</code> retrieves the local transform of + * this spatial. + * + * @return the local transform of this spatial. + */ + public Transform getLocalTransform() { + return localTransform; + } + + /** + * Applies the given material to the Spatial, this will propagate the + * material down to the geometries in the scene graph. + * + * @param material The material to set. + */ + public void setMaterial(Material material) { + } + + /** + * <code>addLight</code> adds the given light to the Spatial; causing + * all child Spatials to be effected by it. + * + * @param light The light to add. + */ + public void addLight(Light light) { + localLights.add(light); + setLightListRefresh(); + } + + /** + * <code>removeLight</code> removes the given light from the Spatial. + * + * @param light The light to remove. + * @see Spatial#addLight(com.jme3.light.Light) + */ + public void removeLight(Light light) { + localLights.remove(light); + setLightListRefresh(); + } + + /** + * Translates the spatial by the given translation vector. + * + * @return The spatial on which this method is called, e.g <code>this</code>. + */ + public Spatial move(float x, float y, float z) { + this.localTransform.getTranslation().addLocal(x, y, z); + setTransformRefresh(); + + return this; + } + + /** + * Translates the spatial by the given translation vector. + * + * @return The spatial on which this method is called, e.g <code>this</code>. + */ + public Spatial move(Vector3f offset) { + this.localTransform.getTranslation().addLocal(offset); + setTransformRefresh(); + + return this; + } + + /** + * Scales the spatial by the given value + * + * @return The spatial on which this method is called, e.g <code>this</code>. + */ + public Spatial scale(float s) { + return scale(s, s, s); + } + + /** + * Scales the spatial by the given scale vector. + * + * @return The spatial on which this method is called, e.g <code>this</code>. + */ + public Spatial scale(float x, float y, float z) { + this.localTransform.getScale().multLocal(x, y, z); + setTransformRefresh(); + + return this; + } + + /** + * Rotates the spatial by the given rotation. + * + * @return The spatial on which this method is called, e.g <code>this</code>. + */ + public Spatial rotate(Quaternion rot) { + this.localTransform.getRotation().multLocal(rot); + setTransformRefresh(); + + return this; + } + + /** + * Rotates the spatial by the yaw, roll and pitch angles (in radians), + * in the local coordinate space. + * + * @return The spatial on which this method is called, e.g <code>this</code>. + */ + public Spatial rotate(float yaw, float roll, float pitch) { + TempVars vars = TempVars.get(); + Quaternion q = vars.quat1; + q.fromAngles(yaw, roll, pitch); + rotate(q); + vars.release(); + + return this; + } + + /** + * Centers the spatial in the origin of the world bound. + * @return The spatial on which this method is called, e.g <code>this</code>. + */ + public Spatial center() { + Vector3f worldTrans = getWorldTranslation(); + Vector3f worldCenter = getWorldBound().getCenter(); + + Vector3f absTrans = worldTrans.subtract(worldCenter); + setLocalTranslation(absTrans); + + return this; + } + + /** + * @see #setCullHint(CullHint) + * @return the cull mode of this spatial, or if set to CullHint.Inherit, + * the cullmode of it's parent. + */ + public CullHint getCullHint() { + if (cullHint != CullHint.Inherit) { + return cullHint; + } else if (parent != null) { + return parent.getCullHint(); + } else { + return CullHint.Dynamic; + } + } + + public BatchHint getBatchHint() { + if (batchHint != BatchHint.Inherit) { + return batchHint; + } else if (parent != null) { + return parent.getBatchHint(); + } else { + return BatchHint.Always; + } + } + + /** + * Returns this spatial's renderqueue bucket. If the mode is set to inherit, + * then the spatial gets its renderqueue bucket from its parent. + * + * @return The spatial's current renderqueue mode. + */ + public RenderQueue.Bucket getQueueBucket() { + if (queueBucket != RenderQueue.Bucket.Inherit) { + return queueBucket; + } else if (parent != null) { + return parent.getQueueBucket(); + } else { + return RenderQueue.Bucket.Opaque; + } + } + + /** + * @return The shadow mode of this spatial, if the local shadow + * mode is set to inherit, then the parent's shadow mode is returned. + * + * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) + * @see ShadowMode + */ + public RenderQueue.ShadowMode getShadowMode() { + if (shadowMode != RenderQueue.ShadowMode.Inherit) { + return shadowMode; + } else if (parent != null) { + return parent.getShadowMode(); + } else { + return ShadowMode.Off; + } + } + + /** + * Sets the level of detail to use when rendering this Spatial, + * this call propagates to all geometries under this Spatial. + * + * @param lod The lod level to set. + */ + public void setLodLevel(int lod) { + } + + /** + * <code>updateModelBound</code> recalculates the bounding object for this + * Spatial. + */ + public abstract void updateModelBound(); + + /** + * <code>setModelBound</code> sets the bounding object for this Spatial. + * + * @param modelBound + * the bounding object for this spatial. + */ + public abstract void setModelBound(BoundingVolume modelBound); + + /** + * @return The sum of all verticies under this Spatial. + */ + public abstract int getVertexCount(); + + /** + * @return The sum of all triangles under this Spatial. + */ + public abstract int getTriangleCount(); + + /** + * @return A clone of this Spatial, the scene graph in its entirety + * is cloned and can be altered independently of the original scene graph. + * + * Note that meshes of geometries are not cloned explicitly, they + * are shared if static, or specially cloned if animated. + * + * All controls will be cloned using the Control.cloneForSpatial method + * on the clone. + * + * @see Mesh#cloneForAnim() + */ + public Spatial clone(boolean cloneMaterial) { + try { + Spatial clone = (Spatial) super.clone(); + if (worldBound != null) { + clone.worldBound = worldBound.clone(); + } + clone.worldLights = worldLights.clone(); + clone.localLights = localLights.clone(); + + // Set the new owner of the light lists + clone.localLights.setOwner(clone); + clone.worldLights.setOwner(clone); + + // No need to force cloned to update. + // This node already has the refresh flags + // set below so it will have to update anyway. + clone.worldTransform = worldTransform.clone(); + clone.localTransform = localTransform.clone(); + + if (clone instanceof Node) { + Node node = (Node) this; + Node nodeClone = (Node) clone; + nodeClone.children = new SafeArrayList<Spatial>(Spatial.class); + for (Spatial child : node.children) { + Spatial childClone = child.clone(cloneMaterial); + childClone.parent = nodeClone; + nodeClone.children.add(childClone); + } + } + + clone.parent = null; + clone.setBoundRefresh(); + clone.setTransformRefresh(); + clone.setLightListRefresh(); + + clone.controls = new SafeArrayList<Control>(Control.class); + for (int i = 0; i < controls.size(); i++) { + clone.controls.add(controls.get(i).cloneForSpatial(clone)); + } + + if (userData != null) { + clone.userData = (HashMap<String, Savable>) userData.clone(); + } + + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * @return A clone of this Spatial, the scene graph in its entirety + * is cloned and can be altered independently of the original scene graph. + * + * Note that meshes of geometries are not cloned explicitly, they + * are shared if static, or specially cloned if animated. + * + * All controls will be cloned using the Control.cloneForSpatial method + * on the clone. + * + * @see Mesh#cloneForAnim() + */ + @Override + public Spatial clone() { + return clone(true); + } + + /** + * @return Similar to Spatial.clone() except will create a deep clone + * of all geometry's meshes, normally this method shouldn't be used + * instead use Spatial.clone() + * + * @see Spatial#clone() + */ + public abstract Spatial deepClone(); + + public void setUserData(String key, Object data) { + if (userData == null) { + userData = new HashMap<String, Savable>(); + } + + if (data instanceof Savable) { + userData.put(key, (Savable) data); + } else { + userData.put(key, new UserData(UserData.getObjectType(data), data)); + } + } + + @SuppressWarnings("unchecked") + public <T> T getUserData(String key) { + if (userData == null) { + return null; + } + + Savable s = userData.get(key); + if (s instanceof UserData) { + return (T) ((UserData) s).getValue(); + } else { + return (T) s; + } + } + + public Collection<String> getUserDataKeys() { + if (userData != null) { + return userData.keySet(); + } + + return Collections.EMPTY_SET; + } + + /** + * Note that we are <i>matching</i> the pattern, therefore the pattern + * must match the entire pattern (i.e. it behaves as if it is sandwiched + * between "^" and "$"). + * You can set regex modes, like case insensitivity, by using the (?X) + * or (?X:Y) constructs. + * + * @param spatialSubclass Subclass which this must implement. + * Null causes all Spatials to qualify. + * @param nameRegex Regular expression to match this name against. + * Null causes all Names to qualify. + * @return true if this implements the specified class and this's name + * matches the specified pattern. + * + * @see java.util.regex.Pattern + */ + public boolean matches(Class<? extends Spatial> spatialSubclass, + String nameRegex) { + if (spatialSubclass != null && !spatialSubclass.isInstance(this)) { + return false; + } + + if (nameRegex != null && (name == null || !name.matches(nameRegex))) { + return false; + } + + return true; + } + + public void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(name, "name", null); + capsule.write(worldBound, "world_bound", null); + capsule.write(cullHint, "cull_mode", CullHint.Inherit); + capsule.write(batchHint, "batch_hint", BatchHint.Inherit); + capsule.write(queueBucket, "queue", RenderQueue.Bucket.Inherit); + capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit); + capsule.write(localTransform, "transform", Transform.IDENTITY); + capsule.write(localLights, "lights", null); + + // Shallow clone the controls array to convert its type. + capsule.writeSavableArrayList(new ArrayList(controls), "controlsList", null); + capsule.writeStringSavableMap(userData, "user_data", null); + } + + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + + name = ic.readString("name", null); + worldBound = (BoundingVolume) ic.readSavable("world_bound", null); + cullHint = ic.readEnum("cull_mode", CullHint.class, CullHint.Inherit); + batchHint = ic.readEnum("batch_hint", BatchHint.class, BatchHint.Inherit); + queueBucket = ic.readEnum("queue", RenderQueue.Bucket.class, + RenderQueue.Bucket.Inherit); + shadowMode = ic.readEnum("shadow_mode", ShadowMode.class, + ShadowMode.Inherit); + + localTransform = (Transform) ic.readSavable("transform", Transform.IDENTITY); + + localLights = (LightList) ic.readSavable("lights", null); + localLights.setOwner(this); + + //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split + //the AnimControl creates the SkeletonControl for old files and add it to the spatial. + //The SkeletonControl must be the last in the stack so we add the list of all other control before it. + //When backward compatibility won't be needed anymore this can be replaced by : + //controls = ic.readSavableArrayList("controlsList", null)); + controls.addAll(0, ic.readSavableArrayList("controlsList", null)); + + userData = (HashMap<String, Savable>) ic.readStringSavableMap("user_data", null); + } + + /** + * <code>getWorldBound</code> retrieves the world bound at this node + * level. + * + * @return the world bound at this level. + */ + public BoundingVolume getWorldBound() { + checkDoBoundUpdate(); + return worldBound; + } + + /** + * <code>setCullHint</code> sets how scene culling should work on this + * spatial during drawing. NOTE: You must set this AFTER attaching to a + * parent or it will be reset with the parent's cullMode value. + * + * @param hint + * one of CullHint.Dynamic, CullHint.Always, CullHint.Inherit or + * CullHint.Never + */ + public void setCullHint(CullHint hint) { + cullHint = hint; + } + + /** + * <code>setBatchHint</code> sets how batching should work on this + * spatial. NOTE: You must set this AFTER attaching to a + * parent or it will be reset with the parent's cullMode value. + * + * @param hint + * one of BatchHint.Never, BatchHint.Always, BatchHint.Inherit + */ + public void setBatchHint(BatchHint hint) { + batchHint = hint; + } + + /** + * @return the cullmode set on this Spatial + */ + public CullHint getLocalCullHint() { + return cullHint; + } + + /** + * @return the batchHint set on this Spatial + */ + public BatchHint getLocalBatchHint() { + return batchHint; + } + + /** + * <code>setQueueBucket</code> determines at what phase of the + * rendering process this Spatial will rendered. See the + * {@link Bucket} enum for an explanation of the various + * render queue buckets. + * + * @param queueBucket + * The bucket to use for this Spatial. + */ + public void setQueueBucket(RenderQueue.Bucket queueBucket) { + this.queueBucket = queueBucket; + } + + /** + * Sets the shadow mode of the spatial + * The shadow mode determines how the spatial should be shadowed, + * when a shadowing technique is used. See the + * documentation for the class {@link ShadowMode} for more information. + * + * @see ShadowMode + * + * @param shadowMode The local shadow mode to set. + */ + public void setShadowMode(RenderQueue.ShadowMode shadowMode) { + this.shadowMode = shadowMode; + } + + /** + * @return The locally set queue bucket mode + * + * @see Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket) + */ + public RenderQueue.Bucket getLocalQueueBucket() { + return queueBucket; + } + + /** + * @return The locally set shadow mode + * + * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) + */ + public RenderQueue.ShadowMode getLocalShadowMode() { + return shadowMode; + } + + /** + * Returns this spatial's last frustum intersection result. This int is set + * when a check is made to determine if the bounds of the object fall inside + * a camera's frustum. If a parent is found to fall outside the frustum, the + * value for this spatial will not be updated. + * + * @return The spatial's last frustum intersection result. + */ + public Camera.FrustumIntersect getLastFrustumIntersection() { + return frustrumIntersects; + } + + /** + * Overrides the last intersection result. This is useful for operations + * that want to start rendering at the middle of a scene tree and don't want + * the parent of that node to influence culling. + * + * @param intersects + * the new value + */ + public void setLastFrustumIntersection(Camera.FrustumIntersect intersects) { + frustrumIntersects = intersects; + } + + /** + * Returns the Spatial's name followed by the class of the spatial <br> + * Example: "MyNode (com.jme3.scene.Spatial) + * + * @return Spatial's name followed by the class of the Spatial + */ + @Override + public String toString() { + return name + " (" + this.getClass().getSimpleName() + ')'; + } + + /** + * Creates a transform matrix that will convert from this spatials' + * local coordinate space to the world coordinate space + * based on the world transform. + * + * @param store Matrix where to store the result, if null, a new one + * will be created and returned. + * + * @return store if not null, otherwise, a new matrix containing the result. + * + * @see Spatial#getWorldTransform() + */ + public Matrix4f getLocalToWorldMatrix(Matrix4f store) { + if (store == null) { + store = new Matrix4f(); + } else { + store.loadIdentity(); + } + // multiply with scale first, then rotate, finally translate (cf. + // Eberly) + store.scale(getWorldScale()); + store.multLocal(getWorldRotation()); + store.setTranslation(getWorldTranslation()); + return store; + } + + /** + * Visit each scene graph element ordered by DFS + * @param visitor + */ + public abstract void depthFirstTraversal(SceneGraphVisitor visitor); + + /** + * Visit each scene graph element ordered by BFS + * @param visitor + */ + public void breadthFirstTraversal(SceneGraphVisitor visitor) { + Queue<Spatial> queue = new LinkedList<Spatial>(); + queue.add(this); + + while (!queue.isEmpty()) { + Spatial s = queue.poll(); + visitor.visit(s); + s.breadthFirstTraversal(visitor, queue); + } + } + + protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue); +} |