// // $Id$ // // Nenya library - tools for developing networked games // Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved // http://www.threerings.net/code/nenya/ // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package com.threerings.jme.model; import java.io.IOException; import java.util.HashSet; import java.util.List; import com.jme.math.Matrix4f; import com.jme.math.Quaternion; import com.jme.math.Vector3f; import com.jme.renderer.Renderer; import com.jme.scene.Node; import com.jme.scene.Spatial; import com.jme.scene.state.RenderState; import com.jme.util.export.JMEExporter; import com.jme.util.export.JMEImporter; import com.jme.util.export.InputCapsule; import com.jme.util.export.OutputCapsule; import com.threerings.jme.util.JmeUtil; import com.threerings.jme.util.ShaderCache; /** * A {@link Node} with a serialization mechanism tailored to stored models. */ public class ModelNode extends Node implements ModelSpatial { /** * No-arg constructor for deserialization. */ public ModelNode () { super("node"); } /** * Standard constructor. */ public ModelNode (String name) { super(name); } @Override // documentation inherited public int hashCode () { // hash on the name rather than the identity for consistent ordering return getName().hashCode(); } /** * Recursively searches the scene graph rooted at this node for a * node with the provided name. */ public Spatial getDescendant (String name) { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child.getName().equals(name)) { return child; } else if (child instanceof ModelNode) { child = ((ModelNode)child).getDescendant(name); if (child != null) { return child; } } } return null; } /** * Returns a reference to the model space transform of the node. */ public Matrix4f getModelTransform () { return _modelTransform; } @Override // documentation inherited public void updateWorldData (float time) { // we use locked bounds as an indication that we can skip the update // altogether if ((lockedMode & LOCKED_BOUNDS) == 0) { super.updateWorldData(time); } } @Override // documentation inherited public void updateWorldBound () { // don't bother updating if we know there are no visible descendants if (_hasVisibleDescendants) { super.updateWorldBound(); } } @Override // documentation inherited public void updateWorldVectors () { super.updateWorldVectors(); if (parent instanceof ModelNode) { JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(), getLocalScale(), _localTransform); ((ModelNode)parent).getModelTransform().mult(_localTransform, _modelTransform); } else { _modelTransform.loadIdentity(); } } // documentation inherited from interface ModelSpatial public Spatial putClone (Spatial store, Model.CloneCreator properties) { ModelNode mstore = (ModelNode)properties.originalToCopy.get(this); if (mstore != null) { return mstore; } else if (store == null) { mstore = new ModelNode(getName()); } else { mstore = (ModelNode)store; } properties.originalToCopy.put(this, mstore); mstore.normalsMode = normalsMode; mstore.cullMode = cullMode; for (int ii = 0; ii < RenderState.RS_MAX_STATE; ii++) { RenderState rstate = getRenderState(ii); if (rstate != null) { mstore.setRenderState(rstate); } } mstore.renderQueueMode = renderQueueMode; mstore.lockedMode = lockedMode; mstore.lightCombineMode = lightCombineMode; mstore.textureCombineMode = textureCombineMode; mstore.name = name; mstore.isCollidable = isCollidable; mstore.localRotation.set(localRotation); mstore.localTranslation.set(localTranslation); mstore.localScale.set(localScale); for (Object controller : getControllers()) { if (controller instanceof ModelController) { mstore.addController( ((ModelController)controller).putClone(null, properties)); } } for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { mstore.attachChild( ((ModelSpatial)child).putClone(null, properties)); } } mstore._hasVisibleDescendants = _hasVisibleDescendants; return mstore; } @Override // documentation inherited public void read (JMEImporter im) throws IOException { InputCapsule capsule = im.getCapsule(this); setName(capsule.readString("name", null)); setLocalTranslation((Vector3f)capsule.readSavable( "localTranslation", null)); setLocalRotation((Quaternion)capsule.readSavable( "localRotation", null)); setLocalScale((Vector3f)capsule.readSavable( "localScale", null)); List children = capsule.readSavableArrayList("children", null); if (children != null) { for (Object child : children) { attachChild((Spatial)child); } } } @Override // documentation inherited public void write (JMEExporter ex) throws IOException { OutputCapsule capsule = ex.getCapsule(this); capsule.write(getName(), "name", null); capsule.write(getLocalTranslation(), "localTranslation", null); capsule.write(getLocalRotation(), "localRotation", null); capsule.write(getLocalScale(), "localScale", null); capsule.writeSavableArrayList(getChildren(), "children", null); } // documentation inherited from interface ModelSpatial public void expandModelBounds () { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).expandModelBounds(); } } } // documentation inherited from interface ModelSpatial public void setReferenceTransforms () { updateWorldVectors(); for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).setReferenceTransforms(); } } } // documentation inherited from interface ModelSpatial public void lockStaticMeshes ( Renderer renderer, boolean useVBOs, boolean useDisplayLists) { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).lockStaticMeshes(renderer, useVBOs, useDisplayLists); } } } // documentation inherited from interface ModelSpatial public void resolveTextures (TextureProvider tprov) { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).resolveTextures(tprov); } } } // documentation inherited from interface ModelSpatial public void configureShaders (ShaderCache scache) { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).configureShaders(scache); } } } // documentation inherited from interface ModelSpatial public void storeMeshFrame (int frameId, boolean blend) { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).storeMeshFrame(frameId, blend); } } } // documentation inherited from interface ModelSpatial public void setMeshFrame (int frameId) { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).setMeshFrame(frameId); } } } // documentation inherited from interface ModelSpatial public void blendMeshFrames (int frameId1, int frameId2, float alpha) { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).blendMeshFrames( frameId1, frameId2, alpha); } } } /** * Sets whether this node should be culled even if it has mesh * descendants. */ public void setForceCull (boolean force) { _forceCull = force; } /** * Checks whether this node should be culled even if it has mesh * descendants. */ public boolean getForceCull () { return _forceCull; } /** * Sets the cull state of any nodes that do not contain geometric * descendants to {@link CULL_ALWAYS} so that they don't waste * rendering time. * * @return true if this node should be drawn, false if it contains * no mesh descendants */ protected boolean cullInvisibleNodes () { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (!(child instanceof ModelNode) || ((ModelNode)child).cullInvisibleNodes()) { _hasVisibleDescendants = true; } } updateCullMode(); return _hasVisibleDescendants; } /** * Locks the transforms and bounds of this instance with the assumption * that the position will never change. * * @param targets the targets of the model's controllers, which determine * the subset of nodes that can be locked * @return true if this node is a target or contains any targets, otherwise * false */ protected boolean lockInstance (HashSet targets) { updateWorldVectors(); lockedMode |= LOCKED_TRANSFORMS; boolean containsTargets = false; for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (targets.contains(child) || (child instanceof ModelNode && ((ModelNode)child).lockInstance(targets))) { containsTargets = true; } else if (child instanceof ModelMesh) { ((ModelMesh)child).lockInstance(); } } if (containsTargets) { return true; } else { updateWorldBound(); lockedMode |= LOCKED_BOUNDS; return false; } } /** * Recursively culls all model nodes under this one in preparation for * activating the ones listed in an animation. */ protected void cullModelNodes () { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelNode) { child.setCullMode(CULL_ALWAYS); ((ModelNode)child).cullModelNodes(); } } } /** * Makes this node visible if and only if it has visible descendants and * has not been specifically culled. */ protected void updateCullMode () { setCullMode((_hasVisibleDescendants && !_forceCull) ? CULL_INHERIT : CULL_ALWAYS); } /** The node's transform in local and model space. */ protected Matrix4f _localTransform = new Matrix4f(), _modelTransform = new Matrix4f(); /** Whether or not this node has mesh descendants. */ protected boolean _hasVisibleDescendants; /** If true, cull the node even if it has mesh descendants. */ protected boolean _forceCull; private static final long serialVersionUID = 1; }