diff --git a/src/java/com/threerings/jme/model/Model.java b/src/java/com/threerings/jme/model/Model.java index 98a3346cc..4ad7fb71b 100644 --- a/src/java/com/threerings/jme/model/Model.java +++ b/src/java/com/threerings/jme/model/Model.java @@ -43,6 +43,7 @@ import com.samskivert.util.ObserverList; import com.jme.math.Quaternion; import com.jme.math.Vector3f; +import com.jme.renderer.CloneCreator; import com.jme.scene.Controller; import com.jme.scene.Spatial; @@ -89,6 +90,25 @@ public class Model extends ModelNode /** The animation transforms (one transform per target per frame). */ public transient Transform[][] transforms; + /** + * Rebinds this animation for a prototype instance. + * + * @param pnodes a mapping from prototype nodes to instance nodes + */ + public Animation rebind (HashMap pnodes) + { + Animation anim = new Animation(); + anim.frameRate = frameRate; + anim.repeatType = repeatType; + anim.transforms = transforms; + anim.transformTargets = new Spatial[transformTargets.length]; + for (int ii = 0; ii < transformTargets.length; ii++) { + anim.transformTargets[ii] = + (Spatial)pnodes.get(transformTargets[ii]); + } + return anim; + } + private void writeObject (ObjectOutputStream out) throws IOException { @@ -234,7 +254,8 @@ public class Model extends ModelNode } /** - * Adds an animation to the model's library. + * Adds an animation to the model's library. This should only be called by + * the model compiler. */ public void addAnimation (String name, Animation anim) { @@ -249,6 +270,9 @@ public class Model extends ModelNode */ public String[] getAnimations () { + if (_prototype != null) { + return _prototype.getAnimations(); + } return (_anims == null) ? new String[0] : _anims.keySet().toArray(new String[_anims.size()]); } @@ -261,17 +285,21 @@ public class Model extends ModelNode if (_anim != null) { stopAnimation(); } - _anim = _anims.get(name); - if (_anim == null) { - Log.warning("Requested unknown animation [name=" + - name + "]."); + Animation anim = _anims.get(name); + if (anim != null) { + startAnimation(name, anim); return; } - _animName = name; - _fidx = 0; - _nidx = 1; - _fdir = +1; - _elapsed = 0f; + if (_prototype != null) { + Animation panim = _prototype._anims.get(name); + if (panim != null) { + _anims.put(name, anim = panim.rebind(_pnodes)); + startAnimation(name, anim); + return; + } + } + Log.warning("Requested unknown animation [name=" + + name + "]."); } /** @@ -353,6 +381,55 @@ public class Model extends ModelNode _props = (Properties)in.readObject(); _anims = (HashMap)in.readObject(); } + + /** + * Creates and returns a new instance of this model. + */ + public Model createInstance () + { + if (_prototype != null) { + return _prototype.createInstance(); + } + if (_ccreator == null) { + // allow adding and removing properties at any time + _ccreator = new CloneCreator(this) { + public void addProperty (String name) { + props.put(name, Boolean.TRUE); + } + public void removeProperty (String name) { + props.remove(name); + } + }; + _ccreator.addProperty("vertices"); + _ccreator.addProperty("colors"); + _ccreator.addProperty("normals"); + _ccreator.addProperty("texcoords"); + _ccreator.addProperty("vboinfo"); + _ccreator.addProperty("indices"); + _ccreator.addProperty("obbtree"); + _ccreator.addProperty("displaylistid"); + _ccreator.addProperty("bound"); + } + return (Model)_ccreator.createCopy(); + } + + @Override // documentation inherited + public Spatial putClone (Spatial store, CloneCreator properties) + { + Model mstore; + if (store == null) { + mstore = new Model(getName(), _props); + } else { + mstore = (Model)store; + } + super.putClone(mstore, properties); + mstore._prototype = this; + if (_anims != null) { + mstore._anims = new HashMap(); + } + mstore._pnodes = properties.originalToCopy; + return mstore; + } @Override // documentation inherited public void updateWorldData (float time) @@ -364,6 +441,19 @@ public class Model extends ModelNode // update children super.updateWorldData(time); } + + /** + * Starts the supplied animation. + */ + protected void startAnimation (String name, Animation anim) + { + _anim = anim; + _animName = name; + _fidx = 0; + _nidx = 1; + _fdir = +1; + _elapsed = 0f; + } /** * Updates the model's state according to the current animation. @@ -415,6 +505,18 @@ public class Model extends ModelNode } } + /** A reference to the prototype, or null if this is a + * prototype. */ + protected Model _prototype; + + /** For prototype models, a customized clone creator used to generate + * instances. */ + protected CloneCreator _ccreator; + + /** For instances, maps prototype nodes to their corresponding instance + * nodes. */ + protected HashMap _pnodes; + /** The model properties. */ protected Properties _props; diff --git a/src/java/com/threerings/jme/model/ModelMesh.java b/src/java/com/threerings/jme/model/ModelMesh.java index 7d9a5f081..bf529ba7a 100644 --- a/src/java/com/threerings/jme/model/ModelMesh.java +++ b/src/java/com/threerings/jme/model/ModelMesh.java @@ -39,8 +39,12 @@ import com.jme.bounding.BoundingBox; import com.jme.bounding.BoundingSphere; import com.jme.math.Quaternion; import com.jme.math.Vector3f; +import com.jme.renderer.CloneCreator; import com.jme.renderer.Renderer; +import com.jme.scene.SharedMesh; +import com.jme.scene.Spatial; import com.jme.scene.TriMesh; +import com.jme.scene.VBOInfo; import com.jme.scene.state.AlphaState; import com.jme.scene.state.CullState; import com.jme.scene.state.TextureState; @@ -155,6 +159,26 @@ public class ModelMesh extends TriMesh _textureBufferSize = (textures == null) ? 0 : textures.capacity(); } + @Override // documentation inherited + public Spatial putClone (Spatial store, CloneCreator properties) + { + ModelMesh mstore; + if (store == null) { + mstore = new ModelMesh(getName()); + } else { + mstore = (ModelMesh)store; + } + super.putClone(mstore, properties); + if (properties.isSet("displaylistid")) { + mstore.batch.setDisplayListID(getDisplayListID()); + } + if (properties.isSet("bound")) { + mstore.setModelBound(getModelBound()); + } + mstore._texture = _texture; + return mstore; + } + // documentation inherited from interface Externalizable public void writeExternal (ObjectOutput out) throws IOException @@ -199,6 +223,20 @@ public class ModelMesh extends TriMesh // no-op } + // documentation inherited from interface ModelSpatial + public void lockStaticMeshes ( + Renderer renderer, boolean useVBOs, boolean useDisplayLists) + { + if (useVBOs && renderer.supportsVBO()) { + VBOInfo vboinfo = new VBOInfo(true); + vboinfo.setVBOIndexEnabled(true); + setVBOInfo(vboinfo); + + } else if (useDisplayLists) { + lockMeshes(renderer); + } + } + // documentation inherited from interface ModelSpatial public void resolveTextures (TextureProvider tprov) { diff --git a/src/java/com/threerings/jme/model/ModelNode.java b/src/java/com/threerings/jme/model/ModelNode.java index 52f26ddd7..7aed912ed 100644 --- a/src/java/com/threerings/jme/model/ModelNode.java +++ b/src/java/com/threerings/jme/model/ModelNode.java @@ -35,6 +35,8 @@ import java.util.ArrayList; import com.jme.math.Matrix4f; import com.jme.math.Quaternion; import com.jme.math.Vector3f; +import com.jme.renderer.CloneCreator; +import com.jme.renderer.Renderer; import com.jme.scene.Node; import com.jme.scene.Spatial; @@ -70,17 +72,6 @@ public class ModelNode extends Node return _modelTransform; } - // documentation inherited from interface ModelSpatial - public void setReferenceTransforms () - { - updateWorldVectors(); - for (Object child : getChildren()) { - if (child instanceof ModelSpatial) { - ((ModelSpatial)child).setReferenceTransforms(); - } - } - } - @Override // documentation inherited public void updateWorldVectors () { @@ -96,6 +87,19 @@ public class ModelNode extends Node } } + @Override // documentation inherited + public Spatial putClone (Spatial store, CloneCreator properties) + { + ModelNode mstore; + if (store == null) { + mstore = new ModelNode(getName()); + } else { + mstore = (ModelNode)store; + } + super.putClone(mstore, properties); + return mstore; + } + // documentation inherited from interface Externalizable public void writeExternal (ObjectOutput out) throws IOException @@ -121,6 +125,29 @@ public class ModelNode extends Node } } + // documentation inherited from interface ModelSpatial + public void setReferenceTransforms () + { + updateWorldVectors(); + for (Object child : getChildren()) { + if (child instanceof ModelSpatial) { + ((ModelSpatial)child).setReferenceTransforms(); + } + } + } + + // documentation inherited from interface ModelSpatial + public void lockStaticMeshes ( + Renderer renderer, boolean useVBOs, boolean useDisplayLists) + { + for (Object child : getChildren()) { + if (child instanceof ModelSpatial) { + ((ModelSpatial)child).lockStaticMeshes(renderer, useVBOs, + useDisplayLists); + } + } + } + // documentation inherited from interface ModelSpatial public void resolveTextures (TextureProvider tprov) { diff --git a/src/java/com/threerings/jme/model/ModelSpatial.java b/src/java/com/threerings/jme/model/ModelSpatial.java index 84c0365d3..d93c18357 100644 --- a/src/java/com/threerings/jme/model/ModelSpatial.java +++ b/src/java/com/threerings/jme/model/ModelSpatial.java @@ -27,7 +27,11 @@ import java.io.IOException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; +import java.util.HashMap; + import com.jme.math.Matrix4f; +import com.jme.renderer.Renderer; +import com.jme.scene.Spatial; /** * Contains method common to both {@link ModelNode}s and {@link ModelMesh}es. @@ -39,6 +43,17 @@ public interface ModelSpatial */ public void setReferenceTransforms (); + /** + * Recursively compiles any static meshes to vertex buffer objects (VBOs) + * or display lists. + * + * @param useVBOs if true, use VBOs if the graphics card supports them + * @param useDisplayLists if true and not using VBOs, compile static + * objects to display lists + */ + public void lockStaticMeshes ( + Renderer renderer, boolean useVBOs, boolean useDisplayLists); + /** * Recursively resolves texture references using the given provider. */ diff --git a/src/java/com/threerings/jme/model/SkinMesh.java b/src/java/com/threerings/jme/model/SkinMesh.java index 12619390e..8f648fa09 100644 --- a/src/java/com/threerings/jme/model/SkinMesh.java +++ b/src/java/com/threerings/jme/model/SkinMesh.java @@ -30,8 +30,14 @@ import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; +import java.util.HashMap; + import com.jme.math.Matrix4f; import com.jme.math.Vector3f; +import com.jme.renderer.CloneCreator; +import com.jme.renderer.Renderer; +import com.jme.scene.Spatial; +import com.jme.scene.VBOInfo; import com.jme.util.geom.BufferUtils; import com.threerings.jme.Log; @@ -60,6 +66,24 @@ public class SkinMesh extends ModelMesh /** The inverses of the bones' mesh space reference transforms. */ public transient Matrix4f[] invRefTransforms; + /** + * Rebinds this weight group for a prototype instance. + * + * @param pnodes a mapping from prototype nodes to instance nodes + */ + public WeightGroup rebind (HashMap pnodes) + { + WeightGroup group = new WeightGroup(); + group.indices = indices; + group.weights = weights; + group.invRefTransforms = invRefTransforms; + group.bones = new ModelNode[bones.length]; + for (int ii = 0; ii < bones.length; ii++) { + group.bones[ii] = (ModelNode)pnodes.get(bones[ii]); + } + return group; + } + private static final long serialVersionUID = 1; } @@ -107,6 +131,32 @@ public class SkinMesh extends ModelMesh setNormalBuffer(BufferUtils.clone(_onbuf = getNormalBuffer())); } + @Override // documentation inherited + public Spatial putClone (Spatial store, CloneCreator properties) + { + SkinMesh mstore; + if (store == null) { + mstore = new SkinMesh(getName()); + } else { + mstore = (SkinMesh)store; + } + properties.removeProperty("vertices"); + properties.removeProperty("normals"); + properties.removeProperty("displaylistid"); + super.putClone(mstore, properties); + properties.addProperty("vertices"); + properties.addProperty("normals"); + properties.addProperty("displaylistid"); + mstore._weightGroups = new WeightGroup[_weightGroups.length]; + for (int ii = 0; ii < _weightGroups.length; ii++) { + mstore._weightGroups[ii] = + _weightGroups[ii].rebind(properties.originalToCopy); + } + mstore._ovbuf = _ovbuf; + mstore._onbuf = _onbuf; + return mstore; + } + @Override // documentation inherited public void writeExternal (ObjectOutput out) throws IOException @@ -138,6 +188,20 @@ public class SkinMesh extends ModelMesh } } + @Override // documentation inherited + public void lockStaticMeshes ( + Renderer renderer, boolean useVBOs, boolean useDisplayLists) + { + // we can use VBOs for color, texture, and indices + if (useVBOs && renderer.supportsVBO()) { + VBOInfo vboinfo = new VBOInfo(false); + vboinfo.setVBOColorEnabled(true); + vboinfo.setVBOTextureEnabled(true); + vboinfo.setVBOIndexEnabled(true); + setVBOInfo(vboinfo); + } + } + @Override // documentation inherited public void updateWorldVectors () { @@ -197,9 +261,9 @@ public class SkinMesh extends ModelMesh xform = _transforms[kk]; weight = weights[ww++]; _vertex.addLocal( - xform.mult(_overtex, _tmp).mult(weight)); + xform.mult(_overtex, _tmp).multLocal(weight)); _normal.addLocal( - xform.multAcross(_onormal, _tmp).mult(weight)); + xform.multAcross(_onormal, _tmp).multLocal(weight)); } BufferUtils.setInBuffer(_vertex, vbuf, idx); BufferUtils.setInBuffer(_normal, nbuf, idx); diff --git a/src/java/com/threerings/jme/tools/ModelDef.java b/src/java/com/threerings/jme/tools/ModelDef.java index bfd6ce504..5d7867879 100644 --- a/src/java/com/threerings/jme/tools/ModelDef.java +++ b/src/java/com/threerings/jme/tools/ModelDef.java @@ -258,13 +258,20 @@ public class ModelDef /** A vertex influenced by a number of bones. */ public static class SkinVertex extends Vertex { - /** The bones influencing the vertex. */ - public ArrayList boneWeights = new ArrayList(); + /** The bones influencing the vertex, mapped by name. */ + public HashMap boneWeights = + new HashMap(); public void addBoneWeight (BoneWeight weight) { - if (weight.weight > 0f) { - boneWeights.add(weight); + if (weight.weight == 0f) { + return; + } + BoneWeight bweight = boneWeights.get(weight.bone); + if (bweight != null) { + bweight.weight += weight.weight; + } else { + boneWeights.put(weight.bone, weight); } } @@ -272,10 +279,13 @@ public class ModelDef public HashSet getBones (HashMap nodes) { HashSet bones = new HashSet(); - for (BoneWeight bweight : boneWeights) { - Spatial node = nodes.get(bweight.bone); + for (String bone : boneWeights.keySet()) { + Spatial node = nodes.get(bone); if (node instanceof ModelNode) { bones.add((ModelNode)node); + } else { + Log.warning("Missing or invalid bone for bone weight " + + "[bone=" + bone + "]."); } } return bones; @@ -284,13 +294,8 @@ public class ModelDef /** Returns the weight of the given bone. */ public float getWeight (ModelNode bone) { - String name = bone.getName(); - for (BoneWeight bweight : boneWeights) { - if (bweight.bone.equals(name)) { - return bweight.weight; - } - } - return 0f; + BoneWeight bweight = boneWeights.get(bone.getName()); + return (bweight == null) ? 0f : bweight.weight; } } diff --git a/src/java/com/threerings/jme/tools/ModelViewer.java b/src/java/com/threerings/jme/tools/ModelViewer.java index 7838c4bff..951045e29 100644 --- a/src/java/com/threerings/jme/tools/ModelViewer.java +++ b/src/java/com/threerings/jme/tools/ModelViewer.java @@ -360,6 +360,7 @@ public class ModelViewer extends JmeCanvasApp _ctx.getGeometry().detachChild(_model); } _ctx.getGeometry().attachChild(_model = model); + _model.lockStaticMeshes(_ctx.getRenderer(), true, true); // resolve the textures from the file's directory final File dir = file.getParentFile();