Skinned animation now works, although there are still some optimizations
to be made. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4027 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -9,12 +9,14 @@ m.file_menu = File
|
|||||||
m.file_load = Load Model...
|
m.file_load = Load Model...
|
||||||
m.file_quit = Quit
|
m.file_quit = Quit
|
||||||
|
|
||||||
m.anim_menu = Animations
|
|
||||||
m.anim_stop = Stop
|
|
||||||
|
|
||||||
m.load_title = Select Model to Load
|
m.load_title = Select Model to Load
|
||||||
m.load_filter = Model Files (*.properties, *.dat)
|
m.load_filter = Model Files (*.properties, *.dat)
|
||||||
|
|
||||||
|
m.anim_select = Animations:
|
||||||
|
m.anim_start = Start
|
||||||
|
m.anim_stop = Stop
|
||||||
|
m.anim_speed = Speed:
|
||||||
|
|
||||||
m.compiling_model = Compiling {0}...
|
m.compiling_model = Compiling {0}...
|
||||||
m.loading_model = Loading {0}...
|
m.loading_model = Loading {0}...
|
||||||
m.loaded_model = Loaded {0}
|
m.loaded_model = Loaded {0}
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
//
|
|
||||||
// $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 com.jme.scene.Node;
|
|
||||||
|
|
||||||
import com.jme.math.Quaternion;
|
|
||||||
import com.jme.math.TransformMatrix;
|
|
||||||
import com.jme.math.Vector3f;
|
|
||||||
|
|
||||||
import com.threerings.jme.Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a bone in a scene. Bones can have simple meshes as children,
|
|
||||||
* or they can be used to deform {@link SkinMesh}es.
|
|
||||||
*/
|
|
||||||
public class BoneNode extends ModelNode
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* No-arg constructor for deserialization.
|
|
||||||
*/
|
|
||||||
public BoneNode ()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default constructor.
|
|
||||||
*
|
|
||||||
* @param name the name of the node
|
|
||||||
*/
|
|
||||||
public BoneNode (String name)
|
|
||||||
{
|
|
||||||
super(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // documentation inherited
|
|
||||||
public void setReferenceTransforms ()
|
|
||||||
{
|
|
||||||
super.setReferenceTransforms();
|
|
||||||
|
|
||||||
// store the inverse of the reference transform
|
|
||||||
_invRefTransform.set(worldRotation, worldTranslation);
|
|
||||||
_invRefTransform.setScale(worldScale);
|
|
||||||
_invRefTransform.inverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // documentation inherited
|
|
||||||
public void updateWorldVectors ()
|
|
||||||
{
|
|
||||||
super.updateWorldVectors();
|
|
||||||
_skinTransform.set(worldRotation, worldTranslation);
|
|
||||||
_skinTransform.setScale(worldScale);
|
|
||||||
_skinTransform.multLocal(_invRefTransform, _tempStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a reference to the matrix that transforms vertices in the
|
|
||||||
* reference position to vertices in the current position.
|
|
||||||
*/
|
|
||||||
public TransformMatrix getSkinTransform ()
|
|
||||||
{
|
|
||||||
return _skinTransform;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The inverse of the bone's world space reference transform. */
|
|
||||||
protected TransformMatrix _invRefTransform = new TransformMatrix();
|
|
||||||
|
|
||||||
/** The bone's current skin transform. */
|
|
||||||
protected TransformMatrix _skinTransform = new TransformMatrix();
|
|
||||||
|
|
||||||
/** A temporary vector for transformations. */
|
|
||||||
protected Vector3f _tempStore = new Vector3f();
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1;
|
|
||||||
}
|
|
||||||
@@ -39,8 +39,11 @@ import java.nio.channels.FileChannel;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import com.samskivert.util.ObserverList;
|
||||||
|
|
||||||
import com.jme.math.Quaternion;
|
import com.jme.math.Quaternion;
|
||||||
import com.jme.math.Vector3f;
|
import com.jme.math.Vector3f;
|
||||||
|
import com.jme.scene.Controller;
|
||||||
import com.jme.scene.Spatial;
|
import com.jme.scene.Spatial;
|
||||||
|
|
||||||
import com.threerings.jme.Log;
|
import com.threerings.jme.Log;
|
||||||
@@ -50,10 +53,36 @@ import com.threerings.jme.Log;
|
|||||||
*/
|
*/
|
||||||
public class Model extends ModelNode
|
public class Model extends ModelNode
|
||||||
{
|
{
|
||||||
|
/** Lets listeners know when animations are completed (which only happens
|
||||||
|
* for non-repeating animations) or cancelled. */
|
||||||
|
public interface AnimationObserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called when a non-repeating animation has finished.
|
||||||
|
*
|
||||||
|
* @return true to remain on the observer list, false to remove self
|
||||||
|
*/
|
||||||
|
public boolean animationCompleted (Model model, String anim);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an animation has been cancelled.
|
||||||
|
*
|
||||||
|
* @return true to remain on the observer list, false to remove self
|
||||||
|
*/
|
||||||
|
public boolean animationCancelled (Model model, String anim);
|
||||||
|
}
|
||||||
|
|
||||||
/** An animation for the model. */
|
/** An animation for the model. */
|
||||||
public static class Animation
|
public static class Animation
|
||||||
implements Serializable
|
implements Serializable
|
||||||
{
|
{
|
||||||
|
/** The rate of the animation in frames per second. */
|
||||||
|
public int frameRate;
|
||||||
|
|
||||||
|
/** The animation repeat type ({@link Controller#RT_CLAMP},
|
||||||
|
* {@link Controller#RT_CYCLE}, or {@link Controller#RT_WRAP}). */
|
||||||
|
public int repeatType;
|
||||||
|
|
||||||
/** The transformation targets of the animation. */
|
/** The transformation targets of the animation. */
|
||||||
public Spatial[] transformTargets;
|
public Spatial[] transformTargets;
|
||||||
|
|
||||||
@@ -173,6 +202,10 @@ public class Model extends ModelNode
|
|||||||
model.readBuffers(fc);
|
model.readBuffers(fc);
|
||||||
}
|
}
|
||||||
ois.close();
|
ois.close();
|
||||||
|
|
||||||
|
// set the reference transforms before any animations are applied
|
||||||
|
model.setReferenceTransforms();
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,13 +258,20 @@ public class Model extends ModelNode
|
|||||||
*/
|
*/
|
||||||
public void startAnimation (String name)
|
public void startAnimation (String name)
|
||||||
{
|
{
|
||||||
|
if (_anim != null) {
|
||||||
|
stopAnimation();
|
||||||
|
}
|
||||||
_anim = _anims.get(name);
|
_anim = _anims.get(name);
|
||||||
if (_anim == null) {
|
if (_anim == null) {
|
||||||
Log.warning("Requested unknown animation [name=" +
|
Log.warning("Requested unknown animation [name=" +
|
||||||
name + "].");
|
name + "].");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_fidx = -1;
|
_animName = name;
|
||||||
|
_fidx = 0;
|
||||||
|
_nidx = 1;
|
||||||
|
_fdir = +1;
|
||||||
|
_elapsed = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -239,7 +279,44 @@ public class Model extends ModelNode
|
|||||||
*/
|
*/
|
||||||
public void stopAnimation ()
|
public void stopAnimation ()
|
||||||
{
|
{
|
||||||
|
if (_anim == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_anim = null;
|
_anim = null;
|
||||||
|
_animObservers.apply(new AnimCancelledOp(_animName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the animation speed, which acts as a multiplier for the frame rate
|
||||||
|
* of each animation.
|
||||||
|
*/
|
||||||
|
public void setAnimationSpeed (float speed)
|
||||||
|
{
|
||||||
|
_animSpeed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the currently configured animation speed.
|
||||||
|
*/
|
||||||
|
public float getAnimationSpeed ()
|
||||||
|
{
|
||||||
|
return _animSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an animation observer.
|
||||||
|
*/
|
||||||
|
public void addAnimationObserver (AnimationObserver obs)
|
||||||
|
{
|
||||||
|
_animObservers.add(obs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an animation observer.
|
||||||
|
*/
|
||||||
|
public void removeAnimationObserver (AnimationObserver obs)
|
||||||
|
{
|
||||||
|
_animObservers.remove(obs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -293,7 +370,49 @@ public class Model extends ModelNode
|
|||||||
*/
|
*/
|
||||||
protected void updateAnimation (float time)
|
protected void updateAnimation (float time)
|
||||||
{
|
{
|
||||||
|
// advance the frame counter if necessary
|
||||||
|
while (_elapsed > 1f) {
|
||||||
|
advanceFrameCounter();
|
||||||
|
_elapsed -= 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the target transforms
|
||||||
|
Spatial[] targets = _anim.transformTargets;
|
||||||
|
Transform[] xforms = _anim.transforms[_fidx],
|
||||||
|
nxforms = _anim.transforms[_nidx];
|
||||||
|
for (int ii = 0; ii < targets.length; ii++) {
|
||||||
|
xforms[ii].blend(nxforms[ii], _elapsed, targets[ii]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the next index is the same as this one, we are finished
|
||||||
|
if (_fidx == _nidx) {
|
||||||
|
_anim = null;
|
||||||
|
_animObservers.apply(new AnimCompletedOp(_animName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_elapsed += (time * _anim.frameRate * _animSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advances the frame counter by one frame.
|
||||||
|
*/
|
||||||
|
protected void advanceFrameCounter ()
|
||||||
|
{
|
||||||
|
_fidx = _nidx;
|
||||||
|
int nframes = _anim.transforms.length;
|
||||||
|
if (_anim.repeatType == Controller.RT_CLAMP) {
|
||||||
|
_nidx = Math.min(_nidx + 1, nframes - 1);
|
||||||
|
|
||||||
|
} else if (_anim.repeatType == Controller.RT_CYCLE) {
|
||||||
|
_nidx = (_nidx + 1) % nframes;
|
||||||
|
|
||||||
|
} else { // _anim.repeatType == Controller.RT_WRAP
|
||||||
|
if ((_nidx + _fdir) < 0 || (_nidx + _fdir) >= nframes) {
|
||||||
|
_fdir *= -1; // reverse direction
|
||||||
|
}
|
||||||
|
_nidx += _fdir;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The model properties. */
|
/** The model properties. */
|
||||||
@@ -305,14 +424,60 @@ public class Model extends ModelNode
|
|||||||
/** The currently running animation, or <code>null</code> for none. */
|
/** The currently running animation, or <code>null</code> for none. */
|
||||||
protected Animation _anim;
|
protected Animation _anim;
|
||||||
|
|
||||||
/** The last frame index. */
|
/** The name of the currently running animation, if any. */
|
||||||
protected int _fidx;
|
protected String _animName;
|
||||||
|
|
||||||
/** The time corresponding to the last frame. */
|
/** The current animation speed multiplier. */
|
||||||
protected float _ftime;
|
protected float _animSpeed = 1f;
|
||||||
|
|
||||||
/** Identifies a transform frame element. */
|
/** The index of the current and next frames. */
|
||||||
protected static final byte TRANSFORM_ELEMENT = 0;
|
protected int _fidx, _nidx;
|
||||||
|
|
||||||
|
/** The direction for wrapping animations (+1 forward, -1 backward). */
|
||||||
|
protected int _fdir;
|
||||||
|
|
||||||
|
/** The frame portion elapsed since the start of the current frame. */
|
||||||
|
protected float _elapsed;
|
||||||
|
|
||||||
|
/** Animation completion listeners. */
|
||||||
|
protected ObserverList<AnimationObserver> _animObservers =
|
||||||
|
new ObserverList<AnimationObserver>(ObserverList.FAST_UNSAFE_NOTIFY);
|
||||||
|
|
||||||
|
/** Used to notify observers of animation completion. */
|
||||||
|
protected class AnimCompletedOp
|
||||||
|
implements ObserverList.ObserverOp<AnimationObserver>
|
||||||
|
{
|
||||||
|
public AnimCompletedOp (String name)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean apply (AnimationObserver obs)
|
||||||
|
{
|
||||||
|
return obs.animationCompleted(Model.this, _name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The name of the animation completed. */
|
||||||
|
protected String _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Used to notify observers of animation cancellation. */
|
||||||
|
protected class AnimCancelledOp
|
||||||
|
implements ObserverList.ObserverOp<AnimationObserver>
|
||||||
|
{
|
||||||
|
public AnimCancelledOp (String name)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean apply (AnimationObserver obs)
|
||||||
|
{
|
||||||
|
return obs.animationCancelled(Model.this, _name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The name of the animation cancelled. */
|
||||||
|
protected String _name;
|
||||||
|
}
|
||||||
|
|
||||||
private static final long serialVersionUID = 1;
|
private static final long serialVersionUID = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,16 +71,6 @@ public class ModelMesh extends TriMesh
|
|||||||
super(name);
|
super(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and populates a mesh.
|
|
||||||
*/
|
|
||||||
public ModelMesh (
|
|
||||||
String name, FloatBuffer vertices, FloatBuffer normals,
|
|
||||||
FloatBuffer colors, FloatBuffer tcoords, IntBuffer indices)
|
|
||||||
{
|
|
||||||
super(name, vertices, normals, colors, tcoords, indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures this mesh based on the given (sub-)properties.
|
* Configures this mesh based on the given (sub-)properties.
|
||||||
*/
|
*/
|
||||||
@@ -138,7 +128,7 @@ public class ModelMesh extends TriMesh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited
|
@Override // documentation inherited
|
||||||
public void reconstruct (
|
public void reconstruct (
|
||||||
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
|
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
|
||||||
FloatBuffer textures, IntBuffer indices)
|
FloatBuffer textures, IntBuffer indices)
|
||||||
@@ -152,7 +142,7 @@ public class ModelMesh extends TriMesh
|
|||||||
_indexBufferSize = (indices == null) ? 0 : indices.capacity();
|
_indexBufferSize = (indices == null) ? 0 : indices.capacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited
|
@Override // documentation inherited
|
||||||
public void reconstruct (
|
public void reconstruct (
|
||||||
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
|
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
|
||||||
FloatBuffer textures)
|
FloatBuffer textures)
|
||||||
@@ -203,6 +193,12 @@ public class ModelMesh extends TriMesh
|
|||||||
_transparent = in.readBoolean();
|
_transparent = in.readBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface ModelSpatial
|
||||||
|
public void setReferenceTransforms ()
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
// documentation inherited from interface ModelSpatial
|
// documentation inherited from interface ModelSpatial
|
||||||
public void resolveTextures (TextureProvider tprov)
|
public void resolveTextures (TextureProvider tprov)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import java.nio.channels.FileChannel;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import com.jme.math.Matrix4f;
|
||||||
import com.jme.math.Quaternion;
|
import com.jme.math.Quaternion;
|
||||||
import com.jme.math.Vector3f;
|
import com.jme.math.Vector3f;
|
||||||
import com.jme.scene.Node;
|
import com.jme.scene.Node;
|
||||||
@@ -62,18 +63,39 @@ public class ModelNode extends Node
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively sets the reference transforms for any {@link BoneNode}s in
|
* Returns a reference to the model space transform of the node.
|
||||||
* the model.
|
|
||||||
*/
|
*/
|
||||||
|
public Matrix4f getModelTransform ()
|
||||||
|
{
|
||||||
|
return _modelTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface ModelSpatial
|
||||||
public void setReferenceTransforms ()
|
public void setReferenceTransforms ()
|
||||||
{
|
{
|
||||||
|
updateWorldVectors();
|
||||||
for (Object child : getChildren()) {
|
for (Object child : getChildren()) {
|
||||||
if (child instanceof ModelNode) {
|
if (child instanceof ModelSpatial) {
|
||||||
((ModelNode)child).setReferenceTransforms();
|
((ModelSpatial)child).setReferenceTransforms();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 Externalizable
|
// documentation inherited from interface Externalizable
|
||||||
public void writeExternal (ObjectOutput out)
|
public void writeExternal (ObjectOutput out)
|
||||||
throws IOException
|
throws IOException
|
||||||
@@ -141,5 +163,35 @@ public class ModelNode extends Node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
private static final long serialVersionUID = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,18 @@ import java.io.IOException;
|
|||||||
import java.nio.MappedByteBuffer;
|
import java.nio.MappedByteBuffer;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
|
|
||||||
|
import com.jme.math.Matrix4f;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains method common to both {@link ModelNode}s and {@link ModelMesh}es.
|
* Contains method common to both {@link ModelNode}s and {@link ModelMesh}es.
|
||||||
*/
|
*/
|
||||||
public interface ModelSpatial
|
public interface ModelSpatial
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Recursively sets the reference transforms for any bones in the model.
|
||||||
|
*/
|
||||||
|
public void setReferenceTransforms ();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively resolves texture references using the given provider.
|
* Recursively resolves texture references using the given provider.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -26,9 +26,14 @@ import java.io.ObjectInput;
|
|||||||
import java.io.ObjectOutput;
|
import java.io.ObjectOutput;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
import java.nio.IntBuffer;
|
import java.nio.IntBuffer;
|
||||||
|
|
||||||
|
import com.jme.math.Matrix4f;
|
||||||
|
import com.jme.math.Vector3f;
|
||||||
|
import com.jme.util.geom.BufferUtils;
|
||||||
|
|
||||||
import com.threerings.jme.Log;
|
import com.threerings.jme.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,13 +50,16 @@ public class SkinMesh extends ModelMesh
|
|||||||
public int[] indices;
|
public int[] indices;
|
||||||
|
|
||||||
/** The bones influencing this group. */
|
/** The bones influencing this group. */
|
||||||
public BoneNode[] bones;
|
public ModelNode[] bones;
|
||||||
|
|
||||||
/** The array of interleaved weights (of length <code>indices.length *
|
/** The array of interleaved weights (of length <code>indices.length *
|
||||||
* bones.length</code>): weights for first vertex, weights for second,
|
* bones.length</code>): weights for first vertex, weights for second,
|
||||||
* etc. */
|
* etc. */
|
||||||
public float[] weights;
|
public float[] weights;
|
||||||
|
|
||||||
|
/** The inverses of the bones' mesh space reference transforms. */
|
||||||
|
public transient Matrix4f[] invRefTransforms;
|
||||||
|
|
||||||
private static final long serialVersionUID = 1;
|
private static final long serialVersionUID = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,16 +78,6 @@ public class SkinMesh extends ModelMesh
|
|||||||
super(name);
|
super(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and populates a mesh.
|
|
||||||
*/
|
|
||||||
public SkinMesh (
|
|
||||||
String name, FloatBuffer vertices, FloatBuffer normals,
|
|
||||||
FloatBuffer color, FloatBuffer texture, IntBuffer indices)
|
|
||||||
{
|
|
||||||
super(name, vertices, normals, color, texture, indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the weight groups that determine how vertices are affected by
|
* Sets the weight groups that determine how vertices are affected by
|
||||||
* bones.
|
* bones.
|
||||||
@@ -97,6 +95,18 @@ public class SkinMesh extends ModelMesh
|
|||||||
return _weightGroups;
|
return _weightGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override // documentation inherited
|
||||||
|
public void reconstruct (
|
||||||
|
ByteBuffer vertices, ByteBuffer normals, ByteBuffer colors,
|
||||||
|
ByteBuffer textures, ByteBuffer indices)
|
||||||
|
{
|
||||||
|
super.reconstruct(vertices, normals, colors, textures, indices);
|
||||||
|
|
||||||
|
// replace the vertex and normal buffers with working buffers
|
||||||
|
setVertexBuffer(BufferUtils.clone(_ovbuf = getVertexBuffer()));
|
||||||
|
setNormalBuffer(BufferUtils.clone(_onbuf = getNormalBuffer()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override // documentation inherited
|
@Override // documentation inherited
|
||||||
public void writeExternal (ObjectOutput out)
|
public void writeExternal (ObjectOutput out)
|
||||||
throws IOException
|
throws IOException
|
||||||
@@ -113,6 +123,36 @@ public class SkinMesh extends ModelMesh
|
|||||||
_weightGroups = (WeightGroup[])in.readObject();
|
_weightGroups = (WeightGroup[])in.readObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override // documentation inherited
|
||||||
|
public void setReferenceTransforms ()
|
||||||
|
{
|
||||||
|
updateWorldVectors();
|
||||||
|
_modelTransform.invert(_transform);
|
||||||
|
for (int ii = 0; ii < _weightGroups.length; ii++) {
|
||||||
|
WeightGroup group = _weightGroups[ii];
|
||||||
|
group.invRefTransforms = new Matrix4f[group.bones.length];
|
||||||
|
for (int jj = 0; jj < group.bones.length; jj++) {
|
||||||
|
group.invRefTransforms[jj] = _transform.mult(
|
||||||
|
group.bones[jj].getModelTransform()).invertLocal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // documentation inherited
|
||||||
|
public void updateWorldVectors ()
|
||||||
|
{
|
||||||
|
super.updateWorldVectors();
|
||||||
|
if (parent instanceof ModelNode) {
|
||||||
|
ModelNode.setTransform(getLocalTranslation(), getLocalRotation(),
|
||||||
|
getLocalScale(), _transform);
|
||||||
|
((ModelNode)parent).getModelTransform().mult(_transform,
|
||||||
|
_modelTransform);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_modelTransform.loadIdentity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override // documentation inherited
|
@Override // documentation inherited
|
||||||
public void updateWorldData (float time)
|
public void updateWorldData (float time)
|
||||||
{
|
{
|
||||||
@@ -120,15 +160,72 @@ public class SkinMesh extends ModelMesh
|
|||||||
if (_weightGroups == null) {
|
if (_weightGroups == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_modelTransform.invert(_transform);
|
||||||
|
|
||||||
// deform the mesh according to the positions of the bones
|
// deform the mesh according to the positions of the bones
|
||||||
|
ModelNode[] bones;
|
||||||
|
int idx;
|
||||||
|
int[] indices;
|
||||||
|
float[] weights;
|
||||||
|
Matrix4f xform;
|
||||||
|
Matrix4f[] invRefXforms;
|
||||||
|
float weight;
|
||||||
|
FloatBuffer vbuf = getVertexBuffer(), nbuf = getNormalBuffer();
|
||||||
for (int ii = 0; ii < _weightGroups.length; ii++) {
|
for (int ii = 0; ii < _weightGroups.length; ii++) {
|
||||||
|
bones = _weightGroups[ii].bones;
|
||||||
|
invRefXforms = _weightGroups[ii].invRefTransforms;
|
||||||
|
if (_transforms == null || _transforms.length < bones.length) {
|
||||||
|
_transforms = new Matrix4f[bones.length];
|
||||||
|
}
|
||||||
|
for (int jj = 0; jj < bones.length; jj++) {
|
||||||
|
if (_transforms[jj] == null) {
|
||||||
|
_transforms[jj] = new Matrix4f();
|
||||||
|
}
|
||||||
|
_transform.mult(bones[jj].getModelTransform(),
|
||||||
|
_transforms[jj]);
|
||||||
|
_transforms[jj].multLocal(invRefXforms[jj]);
|
||||||
|
}
|
||||||
|
indices = _weightGroups[ii].indices;
|
||||||
|
weights = _weightGroups[ii].weights;
|
||||||
|
for (int jj = 0, ww = 0; jj < indices.length; jj++) {
|
||||||
|
idx = indices[jj];
|
||||||
|
BufferUtils.populateFromBuffer(_overtex, _ovbuf, idx);
|
||||||
|
BufferUtils.populateFromBuffer(_onormal, _onbuf, idx);
|
||||||
|
_vertex.zero();
|
||||||
|
_normal.zero();
|
||||||
|
for (int kk = 0; kk < bones.length; kk++) {
|
||||||
|
xform = _transforms[kk];
|
||||||
|
weight = weights[ww++];
|
||||||
|
_vertex.addLocal(
|
||||||
|
xform.mult(_overtex, _tmp).mult(weight));
|
||||||
|
_normal.addLocal(
|
||||||
|
xform.multAcross(_onormal, _tmp).mult(weight));
|
||||||
|
}
|
||||||
|
BufferUtils.setInBuffer(_vertex, vbuf, idx);
|
||||||
|
BufferUtils.setInBuffer(_normal, nbuf, idx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The groups of vertices influenced by different sets of bones. */
|
/** The groups of vertices influenced by different sets of bones. */
|
||||||
protected WeightGroup[] _weightGroups;
|
protected WeightGroup[] _weightGroups;
|
||||||
|
|
||||||
|
/** The original (undeformed) vertex and normal buffers. */
|
||||||
|
protected FloatBuffer _ovbuf, _onbuf;
|
||||||
|
|
||||||
|
/** The node's transform in model space. */
|
||||||
|
protected Matrix4f _modelTransform = new Matrix4f();
|
||||||
|
|
||||||
|
/** A working array for transforms. */
|
||||||
|
protected Matrix4f[] _transforms;
|
||||||
|
|
||||||
|
/** Working vectors. */
|
||||||
|
protected Vector3f _overtex = new Vector3f(), _vertex = new Vector3f(),
|
||||||
|
_onormal = new Vector3f(), _normal = new Vector3f(),
|
||||||
|
_tmp = new Vector3f();
|
||||||
|
|
||||||
|
/** Working transform. */
|
||||||
|
protected Matrix4f _transform = new Matrix4f();
|
||||||
|
|
||||||
private static final long serialVersionUID = 1;
|
private static final long serialVersionUID = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.util.Properties;
|
|||||||
|
|
||||||
import com.jme.math.Quaternion;
|
import com.jme.math.Quaternion;
|
||||||
import com.jme.math.Vector3f;
|
import com.jme.math.Vector3f;
|
||||||
|
import com.jme.scene.Controller;
|
||||||
import com.jme.scene.Spatial;
|
import com.jme.scene.Spatial;
|
||||||
|
|
||||||
import com.threerings.jme.Log;
|
import com.threerings.jme.Log;
|
||||||
@@ -38,6 +39,9 @@ import com.threerings.jme.model.Model;
|
|||||||
*/
|
*/
|
||||||
public class AnimationDef
|
public class AnimationDef
|
||||||
{
|
{
|
||||||
|
/** The rate of the animation in frames per second. */
|
||||||
|
public int frameRate;
|
||||||
|
|
||||||
/** A single frame of the animation. */
|
/** A single frame of the animation. */
|
||||||
public static class FrameDef
|
public static class FrameDef
|
||||||
{
|
{
|
||||||
@@ -125,7 +129,7 @@ public class AnimationDef
|
|||||||
* Creates the "live" animation object that will be serialized with the
|
* Creates the "live" animation object that will be serialized with the
|
||||||
* object.
|
* object.
|
||||||
*
|
*
|
||||||
* @param props the model properties
|
* @param props the animation properties
|
||||||
* @param nodes the nodes in the model, mapped by name
|
* @param nodes the nodes in the model, mapped by name
|
||||||
*/
|
*/
|
||||||
public Model.Animation createAnimation (
|
public Model.Animation createAnimation (
|
||||||
@@ -137,8 +141,19 @@ public class AnimationDef
|
|||||||
frames.get(ii).addTransformTargets(nodes, targets);
|
frames.get(ii).addTransformTargets(nodes, targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect all transforms
|
// create and configure the animation
|
||||||
Model.Animation anim = new Model.Animation();
|
Model.Animation anim = new Model.Animation();
|
||||||
|
anim.frameRate = frameRate;
|
||||||
|
String rtype = props.getProperty("repeat_type", "clamp");
|
||||||
|
if (rtype.equals("cycle")) {
|
||||||
|
anim.repeatType = Controller.RT_CYCLE;
|
||||||
|
} else if (rtype.equals("wrap")) {
|
||||||
|
anim.repeatType = Controller.RT_WRAP;
|
||||||
|
} else {
|
||||||
|
anim.repeatType = Controller.RT_CLAMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect all transforms
|
||||||
anim.transformTargets = targets.toArray(new Spatial[targets.size()]);
|
anim.transformTargets = targets.toArray(new Spatial[targets.size()]);
|
||||||
anim.transforms = new Model.Transform[frames.size()][targets.size()];
|
anim.transforms = new Model.Transform[frames.size()][targets.size()];
|
||||||
for (int ii = 0; ii < anim.transforms.length; ii++) {
|
for (int ii = 0; ii < anim.transforms.length; ii++) {
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ import org.apache.tools.ant.types.FileSet;
|
|||||||
|
|
||||||
import com.jme.scene.Spatial;
|
import com.jme.scene.Spatial;
|
||||||
|
|
||||||
|
import com.samskivert.util.PropertiesUtil;
|
||||||
import com.samskivert.util.StringUtil;
|
import com.samskivert.util.StringUtil;
|
||||||
|
|
||||||
import com.threerings.jme.model.BoneNode;
|
|
||||||
import com.threerings.jme.model.Model;
|
import com.threerings.jme.model.Model;
|
||||||
import com.threerings.jme.model.ModelMesh;
|
import com.threerings.jme.model.ModelMesh;
|
||||||
import com.threerings.jme.model.ModelNode;
|
import com.threerings.jme.model.ModelNode;
|
||||||
@@ -82,12 +82,12 @@ public class CompileModelTask extends Task
|
|||||||
in.close();
|
in.close();
|
||||||
|
|
||||||
// locate the animations, if any
|
// locate the animations, if any
|
||||||
String[] actions =
|
String[] anims =
|
||||||
StringUtil.parseStringArray(props.getProperty("actions", ""));
|
StringUtil.parseStringArray(props.getProperty("animations", ""));
|
||||||
File[] afiles = new File[actions.length];
|
File[] afiles = new File[anims.length];
|
||||||
File dir = source.getParentFile();
|
File dir = source.getParentFile();
|
||||||
for (int ii = 0; ii < actions.length; ii++) {
|
for (int ii = 0; ii < anims.length; ii++) {
|
||||||
afiles[ii] = new File(dir, actions[ii] + ".xml");
|
afiles[ii] = new File(dir, anims[ii] + ".xml");
|
||||||
if (afiles[ii].lastModified() >= target.lastModified()) {
|
if (afiles[ii].lastModified() >= target.lastModified()) {
|
||||||
needsUpdate = true;
|
needsUpdate = true;
|
||||||
}
|
}
|
||||||
@@ -102,11 +102,12 @@ public class CompileModelTask extends Task
|
|||||||
HashMap<String, Spatial> nodes = new HashMap<String, Spatial>();
|
HashMap<String, Spatial> nodes = new HashMap<String, Spatial>();
|
||||||
Model model = mdef.createModel(props, nodes);
|
Model model = mdef.createModel(props, nodes);
|
||||||
|
|
||||||
// load the actions, if any
|
// load the animations, if any
|
||||||
for (int ii = 0; ii < actions.length; ii++) {
|
for (int ii = 0; ii < anims.length; ii++) {
|
||||||
System.out.println(" Adding " + afiles[ii] + "...");
|
System.out.println(" Adding " + afiles[ii] + "...");
|
||||||
AnimationDef adef = _aparser.parseAnimation(afiles[ii].toString());
|
AnimationDef adef = _aparser.parseAnimation(afiles[ii].toString());
|
||||||
model.addAnimation(actions[ii], adef.createAnimation(props, nodes));
|
model.addAnimation(anims[ii], adef.createAnimation(
|
||||||
|
PropertiesUtil.getSubProperties(props, anims[ii]), nodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
// write and return the model
|
// write and return the model
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import com.jme.util.geom.BufferUtils;
|
|||||||
import com.samskivert.util.PropertiesUtil;
|
import com.samskivert.util.PropertiesUtil;
|
||||||
|
|
||||||
import com.threerings.jme.Log;
|
import com.threerings.jme.Log;
|
||||||
import com.threerings.jme.model.BoneNode;
|
|
||||||
import com.threerings.jme.model.Model;
|
import com.threerings.jme.model.Model;
|
||||||
import com.threerings.jme.model.ModelMesh;
|
import com.threerings.jme.model.ModelMesh;
|
||||||
import com.threerings.jme.model.ModelNode;
|
import com.threerings.jme.model.ModelNode;
|
||||||
@@ -178,17 +177,17 @@ public class ModelDef
|
|||||||
super.resolveReferences(nodes);
|
super.resolveReferences(nodes);
|
||||||
|
|
||||||
// divide the vertices up by weight groups
|
// divide the vertices up by weight groups
|
||||||
HashMap<HashSet<BoneNode>, WeightGroupDef> groups =
|
HashMap<HashSet<ModelNode>, WeightGroupDef> groups =
|
||||||
new HashMap<HashSet<BoneNode>, WeightGroupDef>();
|
new HashMap<HashSet<ModelNode>, WeightGroupDef>();
|
||||||
for (int ii = 0, nn = vertices.size(); ii < nn; ii++) {
|
for (int ii = 0, nn = vertices.size(); ii < nn; ii++) {
|
||||||
SkinVertex svertex = (SkinVertex)vertices.get(ii);
|
SkinVertex svertex = (SkinVertex)vertices.get(ii);
|
||||||
HashSet<BoneNode> bones = svertex.getBonesAndNormalize(nodes);
|
HashSet<ModelNode> bones = svertex.getBones(nodes);
|
||||||
WeightGroupDef group = groups.get(bones);
|
WeightGroupDef group = groups.get(bones);
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
groups.put(bones, group = new WeightGroupDef());
|
groups.put(bones, group = new WeightGroupDef());
|
||||||
}
|
}
|
||||||
group.indices.add(ii);
|
group.indices.add(ii);
|
||||||
for (BoneNode bone : bones) {
|
for (ModelNode bone : bones) {
|
||||||
group.weights.add(svertex.getWeight(bone));
|
group.weights.add(svertex.getWeight(bone));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,12 +196,12 @@ public class ModelDef
|
|||||||
SkinMesh.WeightGroup[] wgroups =
|
SkinMesh.WeightGroup[] wgroups =
|
||||||
new SkinMesh.WeightGroup[groups.size()];
|
new SkinMesh.WeightGroup[groups.size()];
|
||||||
int ii = 0;
|
int ii = 0;
|
||||||
for (Map.Entry<HashSet<BoneNode>, WeightGroupDef> entry :
|
for (Map.Entry<HashSet<ModelNode>, WeightGroupDef> entry :
|
||||||
groups.entrySet()) {
|
groups.entrySet()) {
|
||||||
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
|
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
|
||||||
wgroup.indices = toArray(entry.getValue().indices);
|
wgroup.indices = toArray(entry.getValue().indices);
|
||||||
HashSet<BoneNode> bones = entry.getKey();
|
HashSet<ModelNode> bones = entry.getKey();
|
||||||
wgroup.bones = bones.toArray(new BoneNode[bones.size()]);
|
wgroup.bones = bones.toArray(new ModelNode[bones.size()]);
|
||||||
wgroup.weights = toArray(entry.getValue().weights);
|
wgroup.weights = toArray(entry.getValue().weights);
|
||||||
wgroups[ii++] = wgroup;
|
wgroups[ii++] = wgroup;
|
||||||
}
|
}
|
||||||
@@ -220,16 +219,6 @@ public class ModelDef
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A bone that influences skinned meshes. */
|
|
||||||
public static class BoneNodeDef extends NodeDef
|
|
||||||
{
|
|
||||||
// documentation inherited
|
|
||||||
public Spatial createSpatial (Properties props)
|
|
||||||
{
|
|
||||||
return new BoneNode(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A basic vertex. */
|
/** A basic vertex. */
|
||||||
public static class Vertex
|
public static class Vertex
|
||||||
{
|
{
|
||||||
@@ -279,30 +268,21 @@ public class ModelDef
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Finds the bone nodes influencing this vertex and normalizes the
|
/** Finds the bone nodes influencing this vertex. */
|
||||||
* weights. */
|
public HashSet<ModelNode> getBones (HashMap<String, Spatial> nodes)
|
||||||
public HashSet<BoneNode> getBonesAndNormalize (
|
|
||||||
HashMap<String, Spatial> nodes)
|
|
||||||
{
|
{
|
||||||
HashSet<BoneNode> bones = new HashSet<BoneNode>();
|
HashSet<ModelNode> bones = new HashSet<ModelNode>();
|
||||||
float totalWeight = 0f;
|
|
||||||
for (BoneWeight bweight : boneWeights) {
|
for (BoneWeight bweight : boneWeights) {
|
||||||
Spatial node = nodes.get(bweight.bone);
|
Spatial node = nodes.get(bweight.bone);
|
||||||
if (node instanceof BoneNode) {
|
if (node instanceof ModelNode) {
|
||||||
bones.add((BoneNode)node);
|
bones.add((ModelNode)node);
|
||||||
totalWeight += bweight.weight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (totalWeight > 0f && totalWeight < 1f) {
|
|
||||||
for (BoneWeight bweight : boneWeights) {
|
|
||||||
bweight.weight /= totalWeight;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bones;
|
return bones;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the weight of the given bone. */
|
/** Returns the weight of the given bone. */
|
||||||
public float getWeight (BoneNode bone)
|
public float getWeight (ModelNode bone)
|
||||||
{
|
{
|
||||||
String name = bone.getName();
|
String name = bone.getName();
|
||||||
for (BoneWeight bweight : boneWeights) {
|
for (BoneWeight bweight : boneWeights) {
|
||||||
@@ -339,7 +319,9 @@ public class ModelDef
|
|||||||
|
|
||||||
public void addSpatial (SpatialDef spatial)
|
public void addSpatial (SpatialDef spatial)
|
||||||
{
|
{
|
||||||
spatials.add(spatial);
|
// put nodes before meshes so that bones are updated before skin
|
||||||
|
spatials.add(spatial instanceof NodeDef ? 0 : spatials.size(),
|
||||||
|
spatial);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -352,6 +334,9 @@ public class ModelDef
|
|||||||
{
|
{
|
||||||
Model model = new Model(props.getProperty("name", "model"), props);
|
Model model = new Model(props.getProperty("name", "model"), props);
|
||||||
|
|
||||||
|
// set the overall scale
|
||||||
|
model.setLocalScale(Float.parseFloat(props.getProperty("scale", "1")));
|
||||||
|
|
||||||
// start by creating the spatials and mapping them to their names
|
// start by creating the spatials and mapping them to their names
|
||||||
for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
|
for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
|
||||||
Spatial spatial = spatials.get(ii).getSpatial(props);
|
Spatial spatial = spatials.get(ii).getSpatial(props);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import java.awt.BorderLayout;
|
|||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
@@ -42,7 +43,10 @@ import java.util.logging.Level;
|
|||||||
|
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.DefaultComboBoxModel;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JComboBox;
|
||||||
import javax.swing.JFileChooser;
|
import javax.swing.JFileChooser;
|
||||||
import javax.swing.JFrame;
|
import javax.swing.JFrame;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
@@ -51,7 +55,10 @@ import javax.swing.JMenuBar;
|
|||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
|
import javax.swing.JSlider;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
import javax.swing.event.ChangeEvent;
|
||||||
|
import javax.swing.event.ChangeListener;
|
||||||
import javax.swing.filechooser.FileFilter;
|
import javax.swing.filechooser.FileFilter;
|
||||||
|
|
||||||
import com.jme.image.Texture;
|
import com.jme.image.Texture;
|
||||||
@@ -67,6 +74,7 @@ import com.jme.util.LoggingSystem;
|
|||||||
import com.jme.util.TextureManager;
|
import com.jme.util.TextureManager;
|
||||||
|
|
||||||
import com.samskivert.swing.GroupLayout;
|
import com.samskivert.swing.GroupLayout;
|
||||||
|
import com.samskivert.swing.Spacer;
|
||||||
import com.samskivert.util.Config;
|
import com.samskivert.util.Config;
|
||||||
|
|
||||||
import com.threerings.util.MessageBundle;
|
import com.threerings.util.MessageBundle;
|
||||||
@@ -134,24 +142,44 @@ public class ModelViewer extends JmeCanvasApp
|
|||||||
KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK));
|
KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK));
|
||||||
file.add(quit);
|
file.add(quit);
|
||||||
|
|
||||||
_amenu = new JMenu(_msg.get("m.anim_menu"));
|
|
||||||
_amenu.setMnemonic(KeyEvent.VK_A);
|
|
||||||
menu.add(_amenu);
|
|
||||||
_amenu.setVisible(false);
|
|
||||||
_stop = new AbstractAction(_msg.get("m.anim_stop")) {
|
|
||||||
public void actionPerformed (ActionEvent e) {
|
|
||||||
_model.stopAnimation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
_stop.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S);
|
|
||||||
_stop.putValue(Action.ACCELERATOR_KEY,
|
|
||||||
KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK));
|
|
||||||
|
|
||||||
_frame.getContentPane().add(getCanvas(), BorderLayout.CENTER);
|
_frame.getContentPane().add(getCanvas(), BorderLayout.CENTER);
|
||||||
|
|
||||||
|
JPanel bpanel = new JPanel(new BorderLayout());
|
||||||
|
_frame.getContentPane().add(bpanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
_animctrls = new JPanel();
|
||||||
|
_animctrls.setBorder(BorderFactory.createEtchedBorder());
|
||||||
|
bpanel.add(_animctrls, BorderLayout.NORTH);
|
||||||
|
_animctrls.add(new JLabel(_msg.get("m.anim_select")));
|
||||||
|
_animctrls.add(_animbox = new JComboBox());
|
||||||
|
_animctrls.add(new JButton(
|
||||||
|
new AbstractAction(_msg.get("m.anim_start")) {
|
||||||
|
public void actionPerformed (ActionEvent e) {
|
||||||
|
_model.startAnimation((String)_animbox.getSelectedItem());
|
||||||
|
_animstop.setEnabled(true);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
_animctrls.add(_animstop = new JButton(
|
||||||
|
new AbstractAction(_msg.get("m.anim_stop")) {
|
||||||
|
public void actionPerformed (ActionEvent e) {
|
||||||
|
_model.stopAnimation();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
_animstop.setEnabled(false);
|
||||||
|
_animctrls.add(new Spacer(50, 1));
|
||||||
|
_animctrls.add(new JLabel(_msg.get("m.anim_speed")));
|
||||||
|
_animctrls.add(_animspeed = new JSlider(-100, +100, 0));
|
||||||
|
_animspeed.addChangeListener(new ChangeListener() {
|
||||||
|
public void stateChanged (ChangeEvent e) {
|
||||||
|
updateAnimationSpeed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_animctrls.setVisible(false);
|
||||||
|
|
||||||
_status = new JLabel(" ");
|
_status = new JLabel(" ");
|
||||||
_status.setHorizontalAlignment(JLabel.LEFT);
|
_status.setHorizontalAlignment(JLabel.LEFT);
|
||||||
_frame.getContentPane().add(_status, BorderLayout.SOUTH);
|
_status.setBorder(BorderFactory.createEtchedBorder());
|
||||||
|
bpanel.add(_status, BorderLayout.SOUTH);
|
||||||
|
|
||||||
_frame.pack();
|
_frame.pack();
|
||||||
_frame.setVisible(true);
|
_frame.setVisible(true);
|
||||||
@@ -300,6 +328,7 @@ public class ModelViewer extends JmeCanvasApp
|
|||||||
_status.setText(_msg.get("m.compiling_model", file));
|
_status.setText(_msg.get("m.compiling_model", file));
|
||||||
Model model = CompileModelTask.compileModel(file);
|
Model model = CompileModelTask.compileModel(file);
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
|
model.setReferenceTransforms();
|
||||||
setModel(model, file);
|
setModel(model, file);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -331,7 +360,6 @@ public class ModelViewer extends JmeCanvasApp
|
|||||||
_ctx.getGeometry().detachChild(_model);
|
_ctx.getGeometry().detachChild(_model);
|
||||||
}
|
}
|
||||||
_ctx.getGeometry().attachChild(_model = model);
|
_ctx.getGeometry().attachChild(_model = model);
|
||||||
_model.setLocalScale(0.04f);
|
|
||||||
|
|
||||||
// resolve the textures from the file's directory
|
// resolve the textures from the file's directory
|
||||||
final File dir = file.getParentFile();
|
final File dir = file.getParentFile();
|
||||||
@@ -358,24 +386,26 @@ public class ModelViewer extends JmeCanvasApp
|
|||||||
});
|
});
|
||||||
_model.updateRenderState();
|
_model.updateRenderState();
|
||||||
|
|
||||||
// create buttons for the model's animations
|
// configure the animation panel
|
||||||
String[] anims = _model.getAnimations();
|
String[] anims = _model.getAnimations();
|
||||||
_amenu.removeAll();
|
|
||||||
if (anims.length == 0) {
|
if (anims.length == 0) {
|
||||||
_amenu.setVisible(false);
|
_animctrls.setVisible(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_amenu.setVisible(true);
|
_model.addAnimationObserver(_animobs);
|
||||||
for (int ii = 0; ii < anims.length; ii++) {
|
_animctrls.setVisible(true);
|
||||||
final String anim = anims[ii];
|
_animbox.setModel(new DefaultComboBoxModel(anims));
|
||||||
_amenu.add(new AbstractAction(anim) {
|
updateAnimationSpeed();
|
||||||
public void actionPerformed (ActionEvent e) {
|
}
|
||||||
_model.startAnimation(anim);
|
|
||||||
}
|
/**
|
||||||
});
|
* Updates the model's animation speed based on the position of the
|
||||||
}
|
* animation speed slider.
|
||||||
_amenu.addSeparator();
|
*/
|
||||||
_amenu.add(_stop);
|
protected void updateAnimationSpeed ()
|
||||||
|
{
|
||||||
|
_model.setAnimationSpeed(
|
||||||
|
FastMath.pow(2f, _animspeed.getValue() / 50f));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The translation bundle. */
|
/** The translation bundle. */
|
||||||
@@ -387,11 +417,17 @@ public class ModelViewer extends JmeCanvasApp
|
|||||||
/** The viewer frame. */
|
/** The viewer frame. */
|
||||||
protected JFrame _frame;
|
protected JFrame _frame;
|
||||||
|
|
||||||
/** The animation menu. */
|
/** The animation controls. */
|
||||||
protected JMenu _amenu;
|
protected JPanel _animctrls;
|
||||||
|
|
||||||
/** The stop animation action. */
|
/** The animation selector. */
|
||||||
protected Action _stop;
|
protected JComboBox _animbox;
|
||||||
|
|
||||||
|
/** The "stop animation" button. */
|
||||||
|
protected JButton _animstop;
|
||||||
|
|
||||||
|
/** The animation speed slider. */
|
||||||
|
protected JSlider _animspeed;
|
||||||
|
|
||||||
/** The status bar. */
|
/** The status bar. */
|
||||||
protected JLabel _status;
|
protected JLabel _status;
|
||||||
@@ -402,6 +438,19 @@ public class ModelViewer extends JmeCanvasApp
|
|||||||
/** The currently loaded model. */
|
/** The currently loaded model. */
|
||||||
protected Model _model;
|
protected Model _model;
|
||||||
|
|
||||||
|
/** Disables the stop button when animations stop. */
|
||||||
|
protected Model.AnimationObserver _animobs =
|
||||||
|
new Model.AnimationObserver() {
|
||||||
|
public boolean animationCompleted (Model model, String name) {
|
||||||
|
_animstop.setEnabled(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public boolean animationCancelled (Model model, String name) {
|
||||||
|
_animstop.setEnabled(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** Moves the camera using mouse input. */
|
/** Moves the camera using mouse input. */
|
||||||
protected class MouseOrbiter extends MouseAdapter
|
protected class MouseOrbiter extends MouseAdapter
|
||||||
implements MouseMotionListener, MouseWheelListener
|
implements MouseMotionListener, MouseWheelListener
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public class AnimationParser
|
|||||||
// add the rules
|
// add the rules
|
||||||
String anim = "animation";
|
String anim = "animation";
|
||||||
_digester.addObjectCreate(anim, AnimationDef.class.getName());
|
_digester.addObjectCreate(anim, AnimationDef.class.getName());
|
||||||
|
_digester.addRule(anim, new SetPropertyFieldsRule());
|
||||||
_digester.addSetNext(anim, "setAnimation",
|
_digester.addSetNext(anim, "setAnimation",
|
||||||
AnimationDef.class.getName());
|
AnimationDef.class.getName());
|
||||||
|
|
||||||
|
|||||||
@@ -65,12 +65,6 @@ public class ModelParser
|
|||||||
_digester.addSetNext(node, "addSpatial",
|
_digester.addSetNext(node, "addSpatial",
|
||||||
ModelDef.SpatialDef.class.getName());
|
ModelDef.SpatialDef.class.getName());
|
||||||
|
|
||||||
String bnode = model + "/boneNode";
|
|
||||||
_digester.addObjectCreate(bnode, ModelDef.BoneNodeDef.class.getName());
|
|
||||||
_digester.addRule(bnode, new SetPropertyFieldsRule());
|
|
||||||
_digester.addSetNext(bnode, "addSpatial",
|
|
||||||
ModelDef.SpatialDef.class.getName());
|
|
||||||
|
|
||||||
String vertex = tmesh + "/vertex", svertex = smesh + "/vertex";
|
String vertex = tmesh + "/vertex", svertex = smesh + "/vertex";
|
||||||
_digester.addObjectCreate(vertex, ModelDef.Vertex.class.getName());
|
_digester.addObjectCreate(vertex, ModelDef.Vertex.class.getName());
|
||||||
_digester.addObjectCreate(svertex,
|
_digester.addObjectCreate(svertex,
|
||||||
|
|||||||
@@ -37,18 +37,20 @@ macroScript TRAnimationExporter category:"File" \
|
|||||||
-- Writes a single node transform
|
-- Writes a single node transform
|
||||||
fn writeTransform node outFile = (
|
fn writeTransform node outFile = (
|
||||||
format " <transform name=\"%\"" node.name to:outFile
|
format " <transform name=\"%\"" node.name to:outFile
|
||||||
writePoint3Attr " translation" node.transform.translationPart outFile
|
xform = node.transform
|
||||||
writeQuatAttr " rotation" (inverse node.transform.rotationPart) outFile
|
if node.parent != undefined do (
|
||||||
writePoint3Attr " scale" node.transform.scalePart outFile
|
xform = xform * (inverse node.parent.transform)
|
||||||
|
)
|
||||||
|
writePoint3Attr " translation" xform.translationPart outFile
|
||||||
|
writeQuatAttr " rotation" (inverse xform.rotationPart) outFile
|
||||||
|
writePoint3Attr " scale" xform.scalePart outFile
|
||||||
format "/>\n" to:outFile
|
format "/>\n" to:outFile
|
||||||
)
|
)
|
||||||
|
|
||||||
-- Writes a single animation frame
|
-- Writes a single animation frame
|
||||||
fn writeFrame nodes outFile = (
|
fn writeFrame nodes outFile = (
|
||||||
format " <frame>\n" to:outFile
|
format " <frame>\n" to:outFile
|
||||||
for node in nodes do in coordsys parent (
|
writeTransform node outFile
|
||||||
writeTransform node outFile
|
|
||||||
)
|
|
||||||
format " </frame>\n\n" to:outFile
|
format " </frame>\n\n" to:outFile
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@ macroScript TRAnimationExporter category:"File" \
|
|||||||
(
|
(
|
||||||
outFile = createfile fileName
|
outFile = createfile fileName
|
||||||
format "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" to:outFile
|
format "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" to:outFile
|
||||||
format "<animation>\n\n" to:outFile
|
format "<animation frameRate=\"%\">\n\n" frameRate to:outFile
|
||||||
local nodes
|
local nodes
|
||||||
if selection.count > 0 then (
|
if selection.count > 0 then (
|
||||||
nodes = selection
|
nodes = selection
|
||||||
|
|||||||
@@ -103,22 +103,20 @@ macroScript TRModelExporter category:"File" \
|
|||||||
) else (
|
) else (
|
||||||
kind = "triMesh"
|
kind = "triMesh"
|
||||||
)
|
)
|
||||||
) else if node.boneEnable then (
|
|
||||||
kind = "boneNode"
|
|
||||||
) else (
|
) else (
|
||||||
kind = "node"
|
kind = "node"
|
||||||
)
|
)
|
||||||
format " <% name=\"%\"" kind node.name to:outFile
|
format " <% name=\"%\"" kind node.name to:outFile
|
||||||
|
xform = node.transform
|
||||||
if node.parent != undefined do (
|
if node.parent != undefined do (
|
||||||
|
xform = xform * (inverse node.parent.transform)
|
||||||
format " parent=\"%\"" node.parent.name to:outFile
|
format " parent=\"%\"" node.parent.name to:outFile
|
||||||
)
|
)
|
||||||
in coordsys parent (
|
writePoint3Attr " translation" xform.translationPart \
|
||||||
writePoint3Attr " translation" node.transform.translationPart \
|
outFile
|
||||||
outFile
|
writeQuatAttr " rotation" (inverse xform.rotationPart) \
|
||||||
writeQuatAttr " rotation" (inverse node.transform.rotationPart) \
|
outFile
|
||||||
outFile
|
writePoint3Attr " scale" xform.scalePart outFile
|
||||||
writePoint3Attr " scale" node.transform.scalePart outFile
|
|
||||||
)
|
|
||||||
if isMesh then (
|
if isMesh then (
|
||||||
format ">\n" to:outFile
|
format ">\n" to:outFile
|
||||||
if isProperty node #skin do (
|
if isProperty node #skin do (
|
||||||
@@ -140,6 +138,7 @@ macroScript TRModelExporter category:"File" \
|
|||||||
format "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" to:outFile
|
format "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" to:outFile
|
||||||
format "<model>\n\n" to:outFile
|
format "<model>\n\n" to:outFile
|
||||||
local nodes
|
local nodes
|
||||||
|
oldsel = selection as array
|
||||||
if selection.count > 0 then (
|
if selection.count > 0 then (
|
||||||
nodes = selection
|
nodes = selection
|
||||||
) else (
|
) else (
|
||||||
@@ -148,6 +147,7 @@ macroScript TRModelExporter category:"File" \
|
|||||||
for node in nodes do (
|
for node in nodes do (
|
||||||
writeNode node outFile
|
writeNode node outFile
|
||||||
)
|
)
|
||||||
|
select oldsel
|
||||||
format "</model>\n" to:outFile
|
format "</model>\n" to:outFile
|
||||||
close outFile
|
close outFile
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user