|
@@ -1,1512 +1,1515 @@
|
|
|
-/*
|
|
|
- * Copyright (c) 2009-2012 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.AssetKey;
|
|
|
-import com.jme3.asset.CloneableSmartAsset;
|
|
|
-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, CloneableSmartAsset {
|
|
|
-
|
|
|
- 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;
|
|
|
-
|
|
|
- Spatial p = parent;
|
|
|
- while (p != null) {
|
|
|
- if ((p.refreshFlags & RF_BOUND) != 0) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- p.refreshFlags |= RF_BOUND;
|
|
|
- p = p.parent;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * (Internal use only) Forces a refresh of the given types of data.
|
|
|
- *
|
|
|
- * @param transforms Refresh world transform based on parents'
|
|
|
- * @param bounds Refresh bounding volume data based on child nodes
|
|
|
- * @param lights Refresh light list based on parents'
|
|
|
- */
|
|
|
- public void forceRefresh(boolean transforms, boolean bounds, boolean lights) {
|
|
|
- if (transforms) {
|
|
|
- setTransformRefresh();
|
|
|
- }
|
|
|
- if (bounds) {
|
|
|
- setBoundRefresh();
|
|
|
- }
|
|
|
- if (lights) {
|
|
|
- setLightListRefresh();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * <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 in world space 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.
|
|
|
- *
|
|
|
- * Note : 28/01/2013 this method has been fixed as it was not taking into account the parent rotation.
|
|
|
- * This was resulting in improper rotation when the spatial had rotated parent nodes.
|
|
|
- * This method is intended to work in world space, so no matter what parent graph the
|
|
|
- * spatial has, it will look at the given position in world space.
|
|
|
- *
|
|
|
- * @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;
|
|
|
-
|
|
|
- compVecA.set(position).subtractLocal(worldTranslation);
|
|
|
- getLocalRotation().lookAt(compVecA, upVector);
|
|
|
-
|
|
|
- if ( getParent() != null ) {
|
|
|
- Quaternion rot=vars.quat1;
|
|
|
- rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation());
|
|
|
- rot.normalizeLocal();
|
|
|
- setLocalRotation(rot);
|
|
|
- }
|
|
|
- vars.release();
|
|
|
- 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 xAngle, yAngle and zAngle angles (in radians),
|
|
|
- * (aka pitch, yaw, roll) in the local coordinate space.
|
|
|
- *
|
|
|
- * @return The spatial on which this method is called, e.g <code>this</code>.
|
|
|
- */
|
|
|
- public Spatial rotate(float xAngle, float yAngle, float zAngle) {
|
|
|
- TempVars vars = TempVars.get();
|
|
|
- Quaternion q = vars.quat1;
|
|
|
- q.fromAngles(xAngle, yAngle, zAngle);
|
|
|
- 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++) {
|
|
|
- Control newControl = controls.get(i).cloneForSpatial(clone);
|
|
|
- newControl.setSpatial(clone);
|
|
|
- clone.controls.add(newControl);
|
|
|
- }
|
|
|
-
|
|
|
- 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 == null){
|
|
|
- userData.remove(key);
|
|
|
- }else 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);
|
|
|
-}
|
|
|
+/*
|
|
|
+ * Copyright (c) 2009-2013 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.AssetKey;
|
|
|
+import com.jme3.asset.CloneableSmartAsset;
|
|
|
+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, CloneableSmartAsset {
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ Spatial p = parent;
|
|
|
+ while (p != null) {
|
|
|
+ if ((p.refreshFlags & RF_BOUND) != 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ p.refreshFlags |= RF_BOUND;
|
|
|
+ p = p.parent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * (Internal use only) Forces a refresh of the given types of data.
|
|
|
+ *
|
|
|
+ * @param transforms Refresh world transform based on parents'
|
|
|
+ * @param bounds Refresh bounding volume data based on child nodes
|
|
|
+ * @param lights Refresh light list based on parents'
|
|
|
+ */
|
|
|
+ public void forceRefresh(boolean transforms, boolean bounds, boolean lights) {
|
|
|
+ if (transforms) {
|
|
|
+ setTransformRefresh();
|
|
|
+ }
|
|
|
+ if (bounds) {
|
|
|
+ setBoundRefresh();
|
|
|
+ }
|
|
|
+ if (lights) {
|
|
|
+ setLightListRefresh();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <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 in world space 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.
|
|
|
+ *
|
|
|
+ * Note : 28/01/2013 this method has been fixed as it was not taking into account the parent rotation.
|
|
|
+ * This was resulting in improper rotation when the spatial had rotated parent nodes.
|
|
|
+ * This method is intended to work in world space, so no matter what parent graph the
|
|
|
+ * spatial has, it will look at the given position in world space.
|
|
|
+ *
|
|
|
+ * @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;
|
|
|
+
|
|
|
+ compVecA.set(position).subtractLocal(worldTranslation);
|
|
|
+ getLocalRotation().lookAt(compVecA, upVector);
|
|
|
+
|
|
|
+ if ( getParent() != null ) {
|
|
|
+ Quaternion rot=vars.quat1;
|
|
|
+ rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation());
|
|
|
+ rot.normalizeLocal();
|
|
|
+ setLocalRotation(rot);
|
|
|
+ }
|
|
|
+ vars.release();
|
|
|
+ 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 xAngle, yAngle and zAngle angles (in radians),
|
|
|
+ * (aka pitch, yaw, roll) in the local coordinate space.
|
|
|
+ *
|
|
|
+ * @return The spatial on which this method is called, e.g <code>this</code>.
|
|
|
+ */
|
|
|
+ public Spatial rotate(float xAngle, float yAngle, float zAngle) {
|
|
|
+ TempVars vars = TempVars.get();
|
|
|
+ Quaternion q = vars.quat1;
|
|
|
+ q.fromAngles(xAngle, yAngle, zAngle);
|
|
|
+ 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++) {
|
|
|
+ Control newControl = controls.get(i).cloneForSpatial(clone);
|
|
|
+ newControl.setSpatial(clone);
|
|
|
+ clone.controls.add(newControl);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 == null){
|
|
|
+ userData.remove(key);
|
|
|
+ }else 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> alters how view frustum culling will treat this
|
|
|
+ * spatial.
|
|
|
+ *
|
|
|
+ * @param hint one of: <code>CullHint.Dynamic</code>,
|
|
|
+ * <code>CullHint.Always</code>, <code>CullHint.Inherit</code>, or
|
|
|
+ * <code>CullHint.Never</code>
|
|
|
+ * <p>
|
|
|
+ * The effect of the default value (CullHint.Inherit) may change if the
|
|
|
+ * spatial gets re-parented.
|
|
|
+ */
|
|
|
+ public void setCullHint(CullHint hint) {
|
|
|
+ cullHint = hint;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <code>setBatchHint</code> alters how batching will treat this spatial.
|
|
|
+ *
|
|
|
+ * @param hint one of: <code>BatchHint.Never</code>,
|
|
|
+ * <code>BatchHint.Always</code>, or <code>BatchHint.Inherit</code>
|
|
|
+ * <p>
|
|
|
+ * The effect of the default value (BatchHint.Inherit) may change if the
|
|
|
+ * spatial gets re-parented.
|
|
|
+ */
|
|
|
+ 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);
|
|
|
+}
|