// // $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved // http://www.threerings.net/code/narya/ // // 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.DataOutput; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.HashSet; import com.jme.math.Matrix4f; import com.jme.math.Quaternion; import com.jme.math.Vector3f; import com.jme.renderer.Renderer; import com.jme.scene.Controller; import com.jme.scene.Node; import com.jme.scene.Spatial; import com.jme.scene.state.RenderState; import com.threerings.jme.Log; /** * A {@link Node} with a serialization mechanism tailored to stored models. */ public class ModelNode extends Node implements Externalizable, ModelSpatial { /** * No-arg constructor for deserialization. */ public ModelNode () { super("node"); } /** * Standard constructor. */ public ModelNode (String name) { super(name); } /** * 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 () { // if the node is culled, there are no mesh descendants and thus no // bounds if (cullMode != CULL_ALWAYS) { super.updateWorldBound(); } } @Override // documentation inherited public void updateWorldVectors () { super.updateWorldVectors(); if (parent instanceof ModelNode) { 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)); } } return mstore; } // documentation inherited from interface Externalizable public void writeExternal (ObjectOutput out) throws IOException { out.writeUTF(getName()); out.writeObject(getLocalTranslation()); out.writeObject(getLocalRotation()); out.writeObject(getLocalScale()); out.writeObject(getChildren()); } // documentation inherited from interface Externalizable public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException { setName(in.readUTF()); setLocalTranslation((Vector3f)in.readObject()); setLocalRotation((Quaternion)in.readObject()); setLocalScale((Vector3f)in.readObject()); ArrayList children = (ArrayList)in.readObject(); if (children != null) { for (Spatial child : children) { attachChild(child); } } } // 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 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); } } } // documentation inherited from interface ModelSpatial public void writeBuffers (FileChannel out) throws IOException { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).writeBuffers(out); } } } // documentation inherited from interface ModelSpatial public void readBuffers (FileChannel in) throws IOException { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).readBuffers(in); } } } // documentation inherited from interface ModelSpatial public void sliceBuffers (MappedByteBuffer map) { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (child instanceof ModelSpatial) { ((ModelSpatial)child).sliceBuffers(map); } } } /** * 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 () { boolean hasVisibleDescendants = false; for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { Spatial child = getChild(ii); if (!(child instanceof ModelNode) || ((ModelNode)child).cullInvisibleNodes()) { hasVisibleDescendants = true; } } setCullMode(hasVisibleDescendants ? CULL_INHERIT : CULL_ALWAYS); 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; } } /** * Sets a matrix to the transform defined by the given translation, * rotation, and scale values. */ protected static Matrix4f setTransform ( Vector3f translation, Quaternion rotation, Vector3f scale, Matrix4f result) { result.set(rotation); result.setTranslation(translation); result.m00 *= scale.x; result.m01 *= scale.y; result.m02 *= scale.z; result.m10 *= scale.x; result.m11 *= scale.y; result.m12 *= scale.z; result.m20 *= scale.x; result.m21 *= scale.y; result.m22 *= scale.z; return result; } /** The node's transform in local and model space. */ protected Matrix4f _localTransform = new Matrix4f(), _modelTransform = new Matrix4f(); private static final long serialVersionUID = 1; }