Behold, Nenya, Ring of Water and repository for our media and animation related

goodies, both Java 2D and LWJGL/JME 3D.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@1 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Michael Bayne
2006-06-23 18:07:28 +00:00
commit c2117ee86d
570 changed files with 61913 additions and 0 deletions
@@ -0,0 +1,79 @@
//
// $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.util.Properties;
import com.jme.math.Vector3f;
import com.jme.scene.Geometry;
import com.jme.scene.Spatial;
import com.jme.util.geom.BufferUtils;
/**
* A model controller whose target represents an emitter.
*/
public abstract class EmissionController extends ModelController
{
@Override // documentation inherited
public void configure (Properties props, Spatial target)
{
// substitute underlying mesh for geometry targets
if (target instanceof ModelNode) {
Spatial mesh = ((ModelNode)target).getChild("mesh");
if (mesh != null) {
target = mesh;
}
}
super.configure(props, target);
}
@Override // documentation inherited
public void init (Model model)
{
super.init(model);
_target.setCullMode(Spatial.CULL_ALWAYS);
}
/**
* Determines the current location of the emitter in world coordinates.
*/
protected void getEmitterLocation (Vector3f result)
{
result.set(_target.getWorldTranslation());
}
/**
* Determines the current direction of the emitter in world coordinates.
*/
protected void getEmitterDirection (Vector3f result)
{
if (_target instanceof Geometry) {
BufferUtils.populateFromBuffer(result,
((Geometry)_target).getNormalBuffer(0), 0);
} else {
result.set(0f, 0f, -1f);
}
_target.getWorldRotation().multLocal(result);
}
private static final long serialVersionUID = 1;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,167 @@
//
// $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.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.samskivert.util.StringUtil;
/**
* The superclass of procedural animation controllers for models.
*/
public abstract class ModelController extends Controller
implements Externalizable
{
/**
* Configures this controller based on the supplied (sub-)properties and
* controller target.
*/
public void configure (Properties props, Spatial target)
{
_target = target;
String[] anims = StringUtil.parseStringArray(
props.getProperty("animations", ""));
if (anims.length == 0) {
return;
}
_animations = new HashSet<String>();
Collections.addAll(_animations, anims);
}
/**
* Returns a reference to the controller's target.
*/
public Spatial getTarget ()
{
return _target;
}
/**
* Resolves any textures required by the controller.
*/
public void resolveTextures (TextureProvider tprov)
{
}
/**
* Initializes this controller.
*/
public void init (Model model)
{
model.addAnimationObserver(_animobs);
if (_animations != null) {
setActive(false);
}
}
/**
* Creates or populates and returns a clone of this object using the given
* clone properties.
*
* @param store an instance of this class to populate, or <code>null</code>
* to create a new instance
*/
public Controller putClone (
Controller store, Model.CloneCreator properties)
{
if (store == null) {
return null;
}
ModelController mstore = (ModelController)store;
mstore._target = ((ModelSpatial)_target).putClone(null, properties);
mstore._animations = _animations;
return mstore;
}
// documentation inherited from interface Externalizable
public void writeExternal (ObjectOutput out)
throws IOException
{
out.writeObject(_target);
out.writeObject(_animations);
}
// documentation inherited from interface Externalizable
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException
{
_target = (Spatial)in.readObject();
_animations = (HashSet<String>)in.readObject();
}
/**
* Called when an animation is started on the model.
*/
protected void animationStarted (String anim)
{
if (_animations != null && _animations.contains(anim)) {
setActive(true);
}
}
/**
* Called when an animation is stopped on the model.
*/
protected void animationStopped (String anim)
{
if (_animations != null) {
setActive(false);
}
}
/** The target to control. */
protected Spatial _target;
/** The animations for which this controller should be active, or
* <code>null</code> for all of them. */
protected HashSet<String> _animations;
/** Listens to the model's animation state. */
protected Model.AnimationObserver _animobs =
new Model.AnimationObserver() {
public boolean animationStarted (Model model, String anim) {
ModelController.this.animationStarted(anim);
return true;
}
public boolean animationCompleted (Model model, String anim) {
animationStopped(anim);
return true;
}
public boolean animationCancelled (Model model, String anim) {
animationStopped(anim);
return true;
}
};
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,557 @@
//
// $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.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Properties;
import com.jme.bounding.BoundingVolume;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Controller;
import com.jme.scene.SharedMesh;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.VBOInfo;
import com.jme.scene.batch.TriangleBatch;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.geom.BufferUtils;
import com.samskivert.util.StringUtil;
import com.threerings.jme.Log;
/**
* A {@link TriMesh} with a serialization mechanism tailored to stored models.
*/
public class ModelMesh extends TriMesh
implements Externalizable, ModelSpatial
{
/**
* No-arg constructor for deserialization.
*/
public ModelMesh ()
{
super("mesh");
}
/**
* Creates a mesh with no vertex data.
*/
public ModelMesh (String name)
{
super(name);
}
/**
* Configures this mesh based on the given parameters and (sub-)properties.
*
* @param texture the texture specified in the model export, if any (can be
* overridden by textures specified in the properties)
* @param solid whether or not the mesh allows back face culling
* @param transparent whether or not the mesh is (partially) transparent
*/
public void configure (
boolean solid, String texture, boolean transparent, Properties props)
{
_textures = (texture == null) ? null : StringUtil.parseStringArray(
props.getProperty(texture, texture));
_solid = solid;
_transparent = transparent;
}
/**
* Sets the buffers as {@link ByteBuffer}s, because we can't create byte
* views of non-byte buffers. This method is where the model is
* initialized after loading.
*/
public void reconstruct (
ByteBuffer vertices, ByteBuffer normals, ByteBuffer colors,
ByteBuffer textures, ByteBuffer indices)
{
reconstruct(
vertices == null ? null : vertices.asFloatBuffer(),
normals == null ? null : normals.asFloatBuffer(),
colors == null ? null : colors.asFloatBuffer(),
textures == null ? null : textures.asFloatBuffer(),
indices == null ? null : indices.asIntBuffer());
_vertexByteBuffer = vertices;
_normalByteBuffer = normals;
_colorByteBuffer = colors;
_textureByteBuffer = textures;
_indexByteBuffer = indices;
// initialize the model if we're displaying
if (DisplaySystem.getDisplaySystem() == null) {
return;
}
if (_backCull == null) {
initSharedStates();
}
if (_solid) {
setRenderState(_backCull);
}
if (_transparent) {
setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
setRenderState(_blendAlpha);
setRenderState(_overlayZBuffer);
}
}
/**
* Adjusts the vertices and the transform of the mesh so that the mesh's
* position lies at the center of its bounding volume.
*/
public void centerVertices ()
{
Vector3f offset = getBatch(0).getModelBound().getCenter().negate();
if (!offset.equals(Vector3f.ZERO)) {
getLocalTranslation().subtractLocal(offset);
getBatch(0).getModelBound().getCenter().set(Vector3f.ZERO);
getBatch(0).translatePoints(offset);
}
}
@Override // documentation inherited
public void reconstruct (
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
FloatBuffer textures, IntBuffer indices)
{
super.reconstruct(vertices, normals, colors, textures, indices);
_vertexBufferSize = (vertices == null) ? 0 : vertices.capacity();
_normalBufferSize = (normals == null) ? 0 : normals.capacity();
_colorBufferSize = (colors == null) ? 0 : colors.capacity();
_textureBufferSize = (textures == null) ? 0 : textures.capacity();
_indexBufferSize = (indices == null) ? 0 : indices.capacity();
}
@Override // documentation inherited
public void reconstruct (
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
FloatBuffer textures)
{
super.reconstruct(vertices, normals, colors, textures);
_vertexBufferSize = (vertices == null) ? 0 : vertices.capacity();
_normalBufferSize = (normals == null) ? 0 : normals.capacity();
_colorBufferSize = (colors == null) ? 0 : colors.capacity();
_textureBufferSize = (textures == null) ? 0 : textures.capacity();
}
// documentation inherited from interface ModelSpatial
public Spatial putClone (Spatial store, Model.CloneCreator properties)
{
ModelMesh mstore = (ModelMesh)properties.originalToCopy.get(this);
if (mstore != null) {
return mstore;
} else if (store == null) {
mstore = new ModelMesh(getName());
} else {
mstore = (ModelMesh)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));
}
}
TriangleBatch batch = (TriangleBatch)getBatch(0),
mbatch = (TriangleBatch)mstore.getBatch(0);
mbatch.setVertexBuffer(properties.isSet("vertices") ?
batch.getVertexBuffer() :
BufferUtils.clone(batch.getVertexBuffer()));
mbatch.setColorBuffer(properties.isSet("colors") ?
batch.getColorBuffer() :
BufferUtils.clone(batch.getColorBuffer()));
mbatch.setNormalBuffer(properties.isSet("normals") ?
batch.getNormalBuffer() :
BufferUtils.clone(batch.getNormalBuffer()));
FloatBuffer texcoords;
for (int ii = 0; (texcoords = batch.getTextureBuffer(ii)) != null;
ii++) {
mbatch.setTextureBuffer(properties.isSet("texcoords") ?
texcoords : BufferUtils.clone(texcoords), ii);
}
mbatch.setIndexBuffer(properties.isSet("indices") ?
batch.getIndexBuffer() :
BufferUtils.clone(batch.getIndexBuffer()));
if (properties.isSet("vboinfo")) {
mbatch.setVBOInfo(batch.getVBOInfo());
}
if (properties.isSet("obbtree")) {
mbatch.setCollisionTree(batch.getCollisionTree());
}
if (properties.isSet("displaylistid")) {
mbatch.setDisplayListID(batch.getDisplayListID());
}
if (batch.getModelBound() != null) {
mbatch.setModelBound(properties.isSet("bound") ?
batch.getModelBound() : batch.getModelBound().clone(null));
}
if (_textures != null && _textures.length > 1) {
int tidx = properties.random % _textures.length;
mstore._textures = new String[] { _textures[tidx] };
mstore._tstates = new TextureState[] { _tstates[tidx] };
mstore.setRenderState(_tstates[tidx]);
} else {
mstore._textures = _textures;
mstore._tstates = _tstates;
}
return mstore;
}
@Override // documentation inherited
public void updateWorldVectors ()
{
if (!_transformLocked) {
super.updateWorldVectors();
}
}
// 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(getBatch(0).getModelBound());
out.writeInt(_vertexBufferSize);
out.writeInt(_normalBufferSize);
out.writeInt(_colorBufferSize);
out.writeInt(_textureBufferSize);
out.writeInt(_indexBufferSize);
out.writeObject(_textures);
out.writeBoolean(_solid);
out.writeBoolean(_transparent);
}
// 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());
setModelBound((BoundingVolume)in.readObject());
_vertexBufferSize = in.readInt();
_normalBufferSize = in.readInt();
_colorBufferSize = in.readInt();
_textureBufferSize = in.readInt();
_indexBufferSize = in.readInt();
_textures = (String[])in.readObject();
_solid = in.readBoolean();
_transparent = in.readBoolean();
}
// documentation inherited from interface ModelSpatial
public void expandModelBounds ()
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void setReferenceTransforms ()
{
// 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)
{
if (_textures == null) {
return;
}
_tstates = new TextureState[_textures.length];
for (int ii = 0; ii < _textures.length; ii++) {
_tstates[ii] = tprov.getTexture(_textures[ii]);
}
if (_tstates[0] != null) {
setRenderState(_tstates[0]);
}
}
// documentation inherited from interface ModelSpatial
public void storeMeshFrame (int frameId, boolean blend)
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void setMeshFrame (int frameId)
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void writeBuffers (FileChannel out)
throws IOException
{
if (_vertexBufferSize > 0) {
_vertexByteBuffer.rewind();
convertOrder(_vertexByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_vertexByteBuffer);
convertOrder(_vertexByteBuffer, ByteOrder.nativeOrder());
}
if (_normalBufferSize > 0) {
_normalByteBuffer.rewind();
convertOrder(_normalByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_normalByteBuffer);
convertOrder(_normalByteBuffer, ByteOrder.nativeOrder());
}
if (_colorBufferSize > 0) {
_colorByteBuffer.rewind();
convertOrder(_colorByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_colorByteBuffer);
convertOrder(_colorByteBuffer, ByteOrder.nativeOrder());
}
if (_textureBufferSize > 0) {
_textureByteBuffer.rewind();
convertOrder(_textureByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_textureByteBuffer);
convertOrder(_textureByteBuffer, ByteOrder.nativeOrder());
}
if (_indexBufferSize > 0) {
_indexByteBuffer.rewind();
convertOrder(_indexByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_indexByteBuffer);
convertOrder(_indexByteBuffer, ByteOrder.nativeOrder());
}
}
// documentation inherited from interface ModelSpatial
public void readBuffers (FileChannel in)
throws IOException
{
ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
ibbuf = null;
ByteOrder le = ByteOrder.LITTLE_ENDIAN;
if (_vertexBufferSize > 0) {
vbbuf = ByteBuffer.allocateDirect(_vertexBufferSize*4).order(le);
in.read(vbbuf);
vbbuf.rewind();
convertOrder(vbbuf, ByteOrder.nativeOrder());
}
if (_normalBufferSize > 0) {
nbbuf = ByteBuffer.allocateDirect(_normalBufferSize*4).order(le);
in.read(nbbuf);
nbbuf.rewind();
convertOrder(nbbuf, ByteOrder.nativeOrder());
}
if (_colorBufferSize > 0) {
cbbuf = ByteBuffer.allocateDirect(_colorBufferSize*4).order(le);
in.read(cbbuf);
cbbuf.rewind();
convertOrder(cbbuf, ByteOrder.nativeOrder());
}
if (_textureBufferSize > 0) {
tbbuf = ByteBuffer.allocateDirect(_textureBufferSize*4).order(le);
in.read(tbbuf);
tbbuf.rewind();
convertOrder(tbbuf, ByteOrder.nativeOrder());
}
if (_indexBufferSize > 0) {
ibbuf = ByteBuffer.allocateDirect(_indexBufferSize*4).order(le);
in.read(ibbuf);
ibbuf.rewind();
convertOrder(ibbuf, ByteOrder.nativeOrder());
}
reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
}
// documentation inherited from interface ModelSpatial
public void sliceBuffers (MappedByteBuffer map)
{
ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
ibbuf = null;
int total = 0;
if (_vertexBufferSize > 0) {
int npos = map.position() + _vertexBufferSize*4;
map.limit(npos);
vbbuf = map.slice().order(ByteOrder.LITTLE_ENDIAN);
map.position(npos);
}
if (_normalBufferSize > 0) {
int npos = map.position() + _normalBufferSize*4;
map.limit(npos);
nbbuf = map.slice().order(ByteOrder.LITTLE_ENDIAN);
map.position(npos);
}
if (_colorBufferSize > 0) {
int npos = map.position() + _colorBufferSize*4;
map.limit(npos);
cbbuf = map.slice().order(ByteOrder.LITTLE_ENDIAN);
map.position(npos);
}
if (_textureBufferSize > 0) {
int npos = map.position() + _textureBufferSize*4;
map.limit(npos);
tbbuf = map.slice().order(ByteOrder.LITTLE_ENDIAN);
map.position(npos);
}
if (_indexBufferSize > 0) {
int npos = map.position() + _indexBufferSize*4;
map.limit(npos);
ibbuf = map.slice().order(ByteOrder.LITTLE_ENDIAN);
map.position(npos);
}
reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
}
/**
* Locks the transform and bounds of this mesh on the assumption that its
* position will not change.
*/
protected void lockInstance ()
{
lockBounds();
_transformLocked = true;
}
/**
* Imposes the specified order on the given buffer of 32 bit values.
*/
protected static void convertOrder (ByteBuffer buf, ByteOrder order)
{
if (buf.order() == order) {
return;
}
IntBuffer obuf = buf.asIntBuffer(),
nbuf = buf.order(order).asIntBuffer();
while (obuf.hasRemaining()) {
nbuf.put(obuf.get());
}
}
/**
* Initializes the states shared between all models. Requires an active
* display.
*/
protected static void initSharedStates ()
{
Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer();
_backCull = renderer.createCullState();
_backCull.setCullMode(CullState.CS_BACK);
_blendAlpha = renderer.createAlphaState();
_blendAlpha.setBlendEnabled(true);
_overlayZBuffer = renderer.createZBufferState();
_overlayZBuffer.setFunction(ZBufferState.CF_LEQUAL);
_overlayZBuffer.setWritable(false);
}
/** The sizes of the various buffers (zero for <code>null</code>). */
protected int _vertexBufferSize, _normalBufferSize, _colorBufferSize,
_textureBufferSize, _indexBufferSize;
/** The backing byte buffers for the various buffers. */
protected ByteBuffer _vertexByteBuffer, _normalByteBuffer,
_colorByteBuffer, _textureByteBuffer, _indexByteBuffer;
/** The type of bounding volume that this mesh should use. */
protected int _boundingType;
/** The name of this model's textures, or <code>null</code> for none. */
protected String[] _textures;
/** Whether or not this mesh can enable back-face culling. */
protected boolean _solid;
/** Whether or not this mesh must be rendered as transparent. */
protected boolean _transparent;
/** For prototype meshes, the resolved texture states. */
protected TextureState[] _tstates;
/** Whether or not the transform has been locked. This operates in a
* slightly different way than JME's locking, in that it allows applying
* transformations to display lists. */
protected boolean _transformLocked;
/** The shared state for back face culling. */
protected static CullState _backCull;
/** The shared state for alpha blending. */
protected static AlphaState _blendAlpha;
/** The shared state for checking, but not writing to, the z buffer. */
protected static ZBufferState _overlayZBuffer;
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,407 @@
//
// $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<Spatial> children = (ArrayList<Spatial>)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<Spatial> 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;
}
@@ -0,0 +1,115 @@
//
// $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.Externalizable;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
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.
*/
public interface ModelSpatial
{
/**
* Recursively expands the model bounds of any deformable meshes so that
* they include the current vertex positions.
*/
public void expandModelBounds ();
/**
* Recursively sets the reference transforms for any bones in the model.
*/
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.
*/
public void resolveTextures (TextureProvider tprov);
/**
* Recursively requests that the current state of all skinned meshes be
* stored as an animation frame on the next update.
*
* @param frameId the frame id, which uniquely identifies one frame of
* one animation
* @param blend whether or not the stored frames will be retrieved by
* calls to {@link #blendAnimationFrames} as opposed to
* {@link #setAnimationFrames}
*/
public void storeMeshFrame (int frameId, boolean blend);
/**
* Recursively switches all skinned meshes to a stored animation frame for
* the next update.
*/
public void setMeshFrame (int frameId);
/**
* Recursively blends all skinned meshes between two stored animation
* frames for the next update.
*/
public void blendMeshFrames (int frameId1, int frameId2, float alpha);
/**
* Creates or populates and returns a clone of this object using the given
* clone properties.
*
* @param store an instance of this class to populate, or <code>null</code>
* to create a new instance
*/
public Spatial putClone (Spatial store, Model.CloneCreator properties);
/**
* Recursively writes any data buffers to the output channel.
*/
public void writeBuffers (FileChannel out)
throws IOException;
/**
* Recursively reads any data buffers from the input channel.
*/
public void readBuffers (FileChannel in)
throws IOException;
/**
* Recursively slices any data buffers from the buffer map.
*/
public void sliceBuffers (MappedByteBuffer map);
}
@@ -0,0 +1,128 @@
//
// $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.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Properties;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Controller;
import com.jme.scene.Spatial;
import com.samskivert.util.StringUtil;
import com.threerings.jme.Log;
/**
* A procedural animation that rotates a node around at a constant angular
* velocity.
*/
public class Rotator extends ModelController
{
@Override // documentation inherited
public void configure (Properties props, Spatial target)
{
super.configure(props, target);
String axisstr = props.getProperty("axis", "x"),
rpsstr = props.getProperty("radpersec", "3.14");
if (axisstr.equalsIgnoreCase("x")) {
_axis = Vector3f.UNIT_X;
} else if (axisstr.equalsIgnoreCase("y")) {
_axis = Vector3f.UNIT_Y;
} else if (axisstr.equalsIgnoreCase("z")) {
_axis = Vector3f.UNIT_Z;
} else {
float[] axis = StringUtil.parseFloatArray(axisstr);
if (axis != null && axis.length == 3) {
_axis = new Vector3f(axis[0], axis[1],
axis[2]).normalizeLocal();
} else {
Log.warning("Invalid rotation axis [axis=" + axisstr + "].");
}
}
try {
_radpersec = Float.parseFloat(rpsstr);
} catch (NumberFormatException e) {
Log.warning("Invalid rotation rate [radpersec=" + rpsstr + "].");
}
}
// documentation inherited
public void update (float time)
{
if (!isActive()) {
return;
}
_rot.fromAngleNormalAxis(time * _radpersec, _axis);
_target.getLocalRotation().multLocal(_rot);
}
@Override // documentation inherited
public Controller putClone (
Controller store, Model.CloneCreator properties)
{
Rotator rstore;
if (store == null) {
rstore = new Rotator();
} else {
rstore = (Rotator)store;
}
super.putClone(rstore, properties);
rstore._axis = _axis;
rstore._radpersec = _radpersec;
return rstore;
}
// documentation inherited from interface Externalizable
public void writeExternal (ObjectOutput out)
throws IOException
{
super.writeExternal(out);
out.writeObject(_axis);
out.writeFloat(_radpersec);
}
// documentation inherited from interface Externalizable
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException
{
super.readExternal(in);
_axis = (Vector3f)in.readObject();
_radpersec = in.readFloat();
}
/** The axis about which to rotate. */
protected Vector3f _axis;
/** The velocity at which to rotate in radians per second. */
protected float _radpersec;
/** A temporary quaternion. */
protected Quaternion _rot = new Quaternion();
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,495 @@
//
// $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.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import com.jme.bounding.BoundingVolume;
import com.jme.math.Matrix4f;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.VBOInfo;
import com.jme.scene.batch.SharedBatch;
import com.jme.scene.batch.TriangleBatch;
import com.jme.system.DisplaySystem;
import com.jme.util.geom.BufferUtils;
import com.samskivert.util.HashIntMap;
import com.threerings.jme.Log;
/**
* A triangle mesh that deforms according to a bone hierarchy.
*/
public class SkinMesh extends ModelMesh
{
/** Represents the vertex weights of a group of vertices influenced by the
* same set of bones. */
public static class WeightGroup
implements Serializable
{
/** The number of vertices in this weight group. */
public int vertexCount;
/** The bones influencing this group. */
public Bone[] bones;
/** The array of interleaved weights (of length <code>vertexCount *
* boneIndices.length</code>): weights for first vertex, weights for
* second, etc. */
public float[] weights;
/**
* Rebinds this weight group for a prototype instance.
*
* @param bmap the mapping from prototype to instance bones
*/
public WeightGroup rebind (HashMap<Bone, Bone> bmap)
{
WeightGroup wgroup = new WeightGroup();
wgroup.vertexCount = vertexCount;
wgroup.bones = new Bone[bones.length];
for (int ii = 0; ii < bones.length; ii++) {
wgroup.bones[ii] = bmap.get(bones[ii]);
}
wgroup.weights = weights;
return wgroup;
}
private static final long serialVersionUID = 1;
}
/** Represents a bone that influences the mesh. */
public static class Bone
implements Serializable
{
/** The node that defines the bone's position. */
public ModelNode node;
/** The inverse of the bone's model space reference transform. */
public transient Matrix4f invRefTransform;
/** The bone's current transform in model space. */
public transient Matrix4f transform;
public Bone (ModelNode node)
{
this.node = node;
transform = new Matrix4f();
}
/**
* Rebinds this bone for a prototype instance.
*
* @param pnodes a mapping from prototype nodes to instance nodes
*/
public Bone rebind (HashMap pnodes)
{
Bone bone = new Bone((ModelNode)pnodes.get(node));
bone.invRefTransform = invRefTransform;
bone.transform = new Matrix4f();
return bone;
}
/**
* Initializes the bone's transient state.
*/
private void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
transform = new Matrix4f();
}
private static final long serialVersionUID = 1;
}
/**
* No-arg constructor for deserialization.
*/
public SkinMesh ()
{
}
/**
* Creates an empty mesh.
*/
public SkinMesh (String name)
{
super(name);
}
/**
* Sets the array of weight groups that determine how bones affect
* each vertex.
*/
public void setWeightGroups (WeightGroup[] weightGroups)
{
_weightGroups = weightGroups;
// compile a list of all referenced bones
HashSet<Bone> bones = new HashSet<Bone>();
for (WeightGroup group : weightGroups) {
Collections.addAll(bones, group.bones);
}
_bones = bones.toArray(new Bone[bones.size()]);
}
@Override // documentation inherited
public void reconstruct (
ByteBuffer vertices, ByteBuffer normals, ByteBuffer colors,
ByteBuffer textures, ByteBuffer indices)
{
super.reconstruct(vertices, normals, colors, textures, indices);
// store the current buffers as the originals
storeOriginalBuffers();
// initialize the quantized frame table
_frames = new HashIntMap<Object>();
}
@Override // documentation inherited
public void centerVertices ()
{
super.centerVertices();
storeOriginalBuffers();
}
@Override // documentation inherited
public Spatial putClone (Spatial store, Model.CloneCreator properties)
{
SkinMesh mstore = (SkinMesh)properties.originalToCopy.get(this);
if (mstore != null) {
return mstore;
} else 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._frames = _frames;
mstore._useDisplayLists = _useDisplayLists;
mstore._invRefTransform = _invRefTransform;
mstore._bones = new Bone[_bones.length];
HashMap<Bone, Bone> bmap = new HashMap<Bone, Bone>();
for (int ii = 0; ii < _bones.length; ii++) {
bmap.put(_bones[ii], mstore._bones[ii] =
_bones[ii].rebind(properties.originalToCopy));
}
mstore._weightGroups = new WeightGroup[_weightGroups.length];
for (int ii = 0; ii < _weightGroups.length; ii++) {
mstore._weightGroups[ii] = _weightGroups[ii].rebind(bmap);
}
mstore._ovbuf = _ovbuf;
mstore._onbuf = _onbuf;
mstore._vbuf = new float[_vbuf.length];
mstore._nbuf = new float[_nbuf.length];
return mstore;
}
@Override // documentation inherited
public void writeExternal (ObjectOutput out)
throws IOException
{
super.writeExternal(out);
out.writeObject(_weightGroups);
}
@Override // documentation inherited
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException
{
super.readExternal(in);
setWeightGroups((WeightGroup[])in.readObject());
}
@Override // documentation inherited
public void expandModelBounds ()
{
BoundingVolume obound =
(BoundingVolume)getBatch(0).getModelBound().clone(null);
updateModelBound();
getBatch(0).getModelBound().mergeLocal(obound);
}
@Override // documentation inherited
public void setReferenceTransforms ()
{
_invRefTransform = new Matrix4f();
if (parent instanceof ModelNode) {
Matrix4f transform = new Matrix4f();
ModelNode.setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), transform);
((ModelNode)parent).getModelTransform().mult(transform,
_invRefTransform);
_invRefTransform.invertLocal();
}
for (Bone bone : _bones) {
bone.invRefTransform =
_invRefTransform.mult(bone.node.getModelTransform()).invert();
}
}
@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);
}
_useDisplayLists = useDisplayLists;
}
@Override // documentation inherited
public void storeMeshFrame (int frameId, boolean blend)
{
_storeFrameId = frameId;
_storeBlend = blend;
}
@Override // documentation inherited
public void setMeshFrame (int frameId)
{
TriangleBatch batch = getBatch(0),
tbatch = (TriangleBatch)_frames.get(frameId);
if (batch instanceof SharedBatch) {
((SharedBatch)batch).setTarget(tbatch);
} else {
clearBatches();
addBatch(new SharedBatch(tbatch));
getBatch(0).updateRenderState();
}
}
@Override // documentation inherited
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{
BlendFrame frame1 = (BlendFrame)_frames.get(frameId1),
frame2 = (BlendFrame)_frames.get(frameId2);
frame1.blend(frame2, alpha, _vbuf, _nbuf);
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind();
vbuf.put(_vbuf);
nbuf.rewind();
nbuf.put(_nbuf);
}
@Override // documentation inherited
public void updateWorldData (float time)
{
super.updateWorldData(time);
if (_weightGroups == null || _storeFrameId == -1) {
return;
}
// update the bone transforms
for (Bone bone : _bones) {
_invRefTransform.mult(bone.node.getModelTransform(),
bone.transform);
bone.transform.multLocal(bone.invRefTransform);
}
// deform the mesh according to the positions of the bones (this code
// is ugly as sin because it's optimized at a low level)
Bone[] bones;
int vertexCount, jj, kk, ww;
float[] weights;
Matrix4f m;
float weight, ovx, ovy, ovz, onx, ony, onz, vx, vy, vz, nx, ny, nz;
for (int ii = 0, bidx = 0; ii < _weightGroups.length; ii++) {
vertexCount = _weightGroups[ii].vertexCount;
bones = _weightGroups[ii].bones;
weights = _weightGroups[ii].weights;
for (jj = 0, ww = 0; jj < vertexCount; jj++) {
ovx = _ovbuf[bidx];
ovy = _ovbuf[bidx + 1];
ovz = _ovbuf[bidx + 2];
onx = _onbuf[bidx];
ony = _onbuf[bidx + 1];
onz = _onbuf[bidx + 2];
vx = vy = vz = 0f;
nx = ny = nz = 0f;
for (kk = 0; kk < bones.length; kk++) {
m = bones[kk].transform;
weight = weights[ww++];
vx += (ovx*m.m00 + ovy*m.m01 + ovz*m.m02 + m.m03) * weight;
vy += (ovx*m.m10 + ovy*m.m11 + ovz*m.m12 + m.m13) * weight;
vz += (ovx*m.m20 + ovy*m.m21 + ovz*m.m22 + m.m23) * weight;
nx += (onx*m.m00 + ony*m.m01 + onz*m.m02) * weight;
ny += (onx*m.m10 + ony*m.m11 + onz*m.m12) * weight;
nz += (onx*m.m20 + ony*m.m21 + onz*m.m22) * weight;
}
_vbuf[bidx] = vx;
_vbuf[bidx + 1] = vy;
_vbuf[bidx + 2] = vz;
_nbuf[bidx++] = nx;
_nbuf[bidx++] = ny;
_nbuf[bidx++] = nz;
}
}
// if skinning in real time, copy the data from arrays to buffers;
// otherwise, store the mesh as an animation frame
if (_storeFrameId == 0) {
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind();
vbuf.put(_vbuf);
nbuf.rewind();
nbuf.put(_nbuf);
} else {
storeFrame();
_storeFrameId = -1;
}
}
/**
* Stores the current frame data for later use.
*/
protected void storeFrame ()
{
if (_storeBlend) {
_frames.put(_storeFrameId, new BlendFrame(
(float[])_vbuf.clone(), (float[])_nbuf.clone()));
} else {
TriangleBatch batch = getBatch(0), tbatch = new TriangleBatch();
tbatch.setParentGeom(DUMMY_MESH);
tbatch.setColorBuffer(batch.getColorBuffer());
tbatch.setTextureBuffer(batch.getTextureBuffer(0), 0);
tbatch.setIndexBuffer(batch.getIndexBuffer());
tbatch.setVertexBuffer(BufferUtils.createFloatBuffer(_vbuf));
tbatch.setNormalBuffer(BufferUtils.createFloatBuffer(_nbuf));
VBOInfo ovboinfo = batch.getVBOInfo();
if (ovboinfo != null) {
VBOInfo vboinfo = new VBOInfo(true);
vboinfo.setVBOIndexEnabled(true);
vboinfo.setVBOColorID(ovboinfo.getVBOColorID());
vboinfo.setVBOTextureID(0, ovboinfo.getVBOTextureID(0));
vboinfo.setVBOIndexID(ovboinfo.getVBOIndexID());
tbatch.setVBOInfo(vboinfo);
} else if (_useDisplayLists) {
tbatch.lockMeshes(
DisplaySystem.getDisplaySystem().getRenderer());
}
_frames.put(_storeFrameId, tbatch);
}
}
/**
* Stores the current vertex and normal buffers for later deformation.
*/
protected void storeOriginalBuffers ()
{
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind();
nbuf.rewind();
FloatBuffer.wrap(_ovbuf = new float[vbuf.capacity()]).put(vbuf);
FloatBuffer.wrap(_onbuf = new float[nbuf.capacity()]).put(nbuf);
_vbuf = new float[_ovbuf.length];
_nbuf = new float[_onbuf.length];
}
/** A stored frame used for linear blending. */
protected static class BlendFrame
{
/** The skinned vertex and normal values. */
public float[] vbuf, nbuf;
public BlendFrame (float[] vbuf, float[] nbuf)
{
this.vbuf = vbuf;
this.nbuf = nbuf;
}
public void blend (
BlendFrame next, float alpha, float[] rvbuf, float[] rnbuf)
{
float[] nvbuf = next.vbuf, nnbuf = next.nbuf;
float ialpha = 1f - alpha;
for (int ii = 0, nn = vbuf.length; ii < nn; ii++) {
rvbuf[ii] = vbuf[ii] * ialpha + nvbuf[ii] * alpha;
rnbuf[ii] = nbuf[ii] * ialpha + nnbuf[ii] * alpha;
}
}
}
/** Pre-skinned {@link TriangleBatch}es or {@link BlendFrame}s shared
* between all instances corresponding to frame ids from
* {@link #storeAnimationFrame}. */
protected HashIntMap<Object> _frames;
/** Whether or to use display lists if VBOs are unavailable for quantized
* meshes. */
protected boolean _useDisplayLists;
/** The inverse of the model space reference transform. */
protected Matrix4f _invRefTransform;
/** The groups of vertices influenced by different sets of bones. */
protected WeightGroup[] _weightGroups;
/** The bones referenced by the weight groups. */
protected Bone[] _bones;
/** The original (undeformed) vertex and normal buffers and the deformed
* versions. */
protected float[] _ovbuf, _onbuf, _vbuf, _nbuf;
/** The frame id to store on the next update. If 0, don't store any frame
* and skin the mesh as normal. If -1, a frame has been stored and thus
* skinning should only take place when further frames are requested. */
protected int _storeFrameId;
/** Whether or not the stored frame id will be used for blending. */
protected boolean _storeBlend;
/** A dummy mesh that simply hold transformation values. */
protected static final TriMesh DUMMY_MESH = new TriMesh();
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,39 @@
//
// $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 com.jme.scene.state.TextureState;
/**
* Provides a means for models to resolve their texture references.
*/
public interface TextureProvider
{
/**
* Returns a texture state containing the named texture.
*
* @param name the name of the texture, which will be interpreted as an
* absolute resource path if it starts with a forward slash; otherwise,
* as a relative resource path
*/
public TextureState getTexture (String name);
}