Merge meshes in animated models by finding ones that maintain the same
relative position throughout all animations. Also, don't include transforms in the animations for nodes that never move. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@218 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
@@ -69,11 +69,11 @@ import com.threerings.jme.util.SpatialVisitor;
|
||||
*/
|
||||
public class Model extends ModelNode
|
||||
{
|
||||
/** The supported types of animation in decreasing order of complexity. */
|
||||
/** The supported types of animation in decreasing order of complexity. */
|
||||
public enum AnimationMode {
|
||||
SKIN, MORPH, FLIPBOOK
|
||||
};
|
||||
|
||||
|
||||
/** Lets listeners know when animations are completed (which only happens
|
||||
* for non-repeating animations) or cancelled. */
|
||||
public interface AnimationObserver
|
||||
@@ -84,14 +84,14 @@ public class Model extends ModelNode
|
||||
* @return true to remain on the observer list, false to remove self
|
||||
*/
|
||||
public boolean animationStarted (Model model, String anim);
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -99,30 +99,33 @@ public class Model extends ModelNode
|
||||
*/
|
||||
public boolean animationCancelled (Model model, String anim);
|
||||
}
|
||||
|
||||
|
||||
/** An animation for the model. */
|
||||
public static class Animation
|
||||
implements Savable
|
||||
{
|
||||
/** 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;
|
||||
|
||||
|
||||
/** Any nodes visible that never move within the model. */
|
||||
public Spatial[] staticTargets;
|
||||
|
||||
/** The transformation targets of the animation. */
|
||||
public Spatial[] transformTargets;
|
||||
|
||||
|
||||
/** The animation transforms (one transform per target per frame). */
|
||||
public transient Transform[][] transforms;
|
||||
|
||||
|
||||
/** Uniquely identifies this animation within the model. */
|
||||
public transient int animId;
|
||||
|
||||
|
||||
/** For each frame, whether the frame has been stored in meshes. */
|
||||
public transient boolean[] stored;
|
||||
|
||||
|
||||
/**
|
||||
* Returns this animation's duration in seconds.
|
||||
*/
|
||||
@@ -130,7 +133,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
return (float)transforms.length / frameRate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rebinds this animation for a prototype instance.
|
||||
*
|
||||
@@ -141,17 +144,14 @@ public class Model extends ModelNode
|
||||
Animation anim = new Animation();
|
||||
anim.frameRate = frameRate;
|
||||
anim.repeatType = repeatType;
|
||||
anim.staticTargets = rebind(staticTargets, pnodes);
|
||||
anim.transformTargets = rebind(transformTargets, pnodes);
|
||||
anim.transforms = transforms;
|
||||
anim.transformTargets = new Spatial[transformTargets.length];
|
||||
for (int ii = 0; ii < transformTargets.length; ii++) {
|
||||
anim.transformTargets[ii] =
|
||||
(Spatial)pnodes.get(transformTargets[ii]);
|
||||
}
|
||||
anim.animId = animId;
|
||||
anim.stored = stored;
|
||||
return anim;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies the transforms for a frame of this animation.
|
||||
*/
|
||||
@@ -162,7 +162,7 @@ public class Model extends ModelNode
|
||||
xforms[ii].apply(transformTargets[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Blends the transforms between two frames of this animation.
|
||||
*/
|
||||
@@ -173,13 +173,13 @@ public class Model extends ModelNode
|
||||
xforms[ii].blend(nxforms[ii], alpha, transformTargets[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited
|
||||
public Class getClassTag ()
|
||||
{
|
||||
return getClass();
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited
|
||||
public void read (JMEImporter im)
|
||||
throws IOException
|
||||
@@ -187,6 +187,8 @@ public class Model extends ModelNode
|
||||
InputCapsule capsule = im.getCapsule(this);
|
||||
frameRate = capsule.readInt("frameRate", 0);
|
||||
repeatType = capsule.readInt("repeatType", Controller.RT_CLAMP);
|
||||
staticTargets = ArrayUtil.copy(capsule.readSavableArray(
|
||||
"staticTargets", null), new Spatial[0]);
|
||||
transformTargets = ArrayUtil.copy(capsule.readSavableArray(
|
||||
"transformTargets", null), new Spatial[0]);
|
||||
FloatBuffer pxforms = capsule.readFloatBuffer("transforms", null);
|
||||
@@ -208,9 +210,10 @@ public class Model extends ModelNode
|
||||
OutputCapsule capsule = ex.getCapsule(this);
|
||||
capsule.write(frameRate, "frameRate", 0);
|
||||
capsule.write(repeatType, "repeatType", Controller.RT_CLAMP);
|
||||
capsule.write(staticTargets, "staticTargets", null);
|
||||
capsule.write(transformTargets, "transformTargets", null);
|
||||
FloatBuffer pxforms = FloatBuffer.allocate(transforms.length *
|
||||
transformTargets.length * Transform.PACKED_SIZE);
|
||||
FloatBuffer pxforms = FloatBuffer.allocate(
|
||||
transforms.length * transformTargets.length * Transform.PACKED_SIZE);
|
||||
for (Transform[] frame : transforms) {
|
||||
for (Transform xform : frame) {
|
||||
xform.writeToBuffer(pxforms);
|
||||
@@ -219,16 +222,25 @@ public class Model extends ModelNode
|
||||
pxforms.rewind();
|
||||
capsule.write(pxforms, "transforms", null);
|
||||
}
|
||||
|
||||
|
||||
protected Spatial[] rebind (Spatial[] targets, HashMap pnodes)
|
||||
{
|
||||
Spatial[] ntargets = new Spatial[targets.length];
|
||||
for (int ii = 0; ii < targets.length; ii++) {
|
||||
ntargets[ii] = (Spatial)pnodes.get(targets[ii]);
|
||||
}
|
||||
return ntargets;
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1;
|
||||
}
|
||||
|
||||
|
||||
/** A frame element that manipulates the target's transform. */
|
||||
public static final class Transform
|
||||
{
|
||||
/** The number of floats required to store a packed transform. */
|
||||
public static final int PACKED_SIZE = 3 + 4 + 3;
|
||||
|
||||
|
||||
public Transform (
|
||||
Vector3f translation, Quaternion rotation, Vector3f scale)
|
||||
{
|
||||
@@ -236,7 +248,7 @@ public class Model extends ModelNode
|
||||
_rotation = rotation;
|
||||
_scale = scale;
|
||||
}
|
||||
|
||||
|
||||
public Transform (FloatBuffer buf)
|
||||
{
|
||||
_translation = new Vector3f(buf.get(), buf.get(), buf.get());
|
||||
@@ -244,14 +256,14 @@ public class Model extends ModelNode
|
||||
buf.get());
|
||||
_scale = new Vector3f(buf.get(), buf.get(), buf.get());
|
||||
}
|
||||
|
||||
|
||||
public void apply (Spatial target)
|
||||
{
|
||||
target.getLocalTranslation().set(_translation);
|
||||
target.getLocalRotation().set(_rotation);
|
||||
target.getLocalScale().set(_scale);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Blends between this transform and the next, applying the result to
|
||||
* the given target.
|
||||
@@ -266,7 +278,7 @@ public class Model extends ModelNode
|
||||
target.getLocalRotation().slerp(_rotation, next._rotation, alpha);
|
||||
target.getLocalScale().interpolate(_scale, next._scale, alpha);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes this transform to the current position in the supplied
|
||||
* buffer.
|
||||
@@ -276,31 +288,31 @@ public class Model extends ModelNode
|
||||
buf.put(_translation.x);
|
||||
buf.put(_translation.y);
|
||||
buf.put(_translation.z);
|
||||
|
||||
|
||||
buf.put(_rotation.x);
|
||||
buf.put(_rotation.y);
|
||||
buf.put(_rotation.z);
|
||||
buf.put(_rotation.w);
|
||||
|
||||
|
||||
buf.put(_scale.x);
|
||||
buf.put(_scale.y);
|
||||
buf.put(_scale.z);
|
||||
}
|
||||
|
||||
|
||||
/** The transform at this frame. */
|
||||
protected Vector3f _translation, _scale;
|
||||
protected Quaternion _rotation;
|
||||
}
|
||||
|
||||
|
||||
/** Customized clone creator for models. */
|
||||
public static class CloneCreator
|
||||
{
|
||||
/** A shared seed used to select textures consistently. */
|
||||
public int random;
|
||||
|
||||
|
||||
/** Maps original objects to their copies. */
|
||||
public HashMap originalToCopy = new HashMap();
|
||||
|
||||
|
||||
public CloneCreator (Model toCopy)
|
||||
{
|
||||
_toCopy = toCopy;
|
||||
@@ -314,7 +326,7 @@ public class Model extends ModelNode
|
||||
addProperty("displaylistid");
|
||||
addProperty("bound");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the named property.
|
||||
*/
|
||||
@@ -322,7 +334,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
_properties.add(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears the named property.
|
||||
*/
|
||||
@@ -330,7 +342,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
_properties.remove(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the named property has been set.
|
||||
*/
|
||||
@@ -338,7 +350,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
return _properties.contains(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new copy of the target model.
|
||||
*/
|
||||
@@ -349,14 +361,14 @@ public class Model extends ModelNode
|
||||
originalToCopy.clear(); // make sure no references remain
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
/** The model to copy. */
|
||||
protected Model _toCopy;
|
||||
|
||||
|
||||
/** The set of added properties. */
|
||||
protected HashSet<String> _properties = new HashSet<String>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to read a model from the specified file.
|
||||
*/
|
||||
@@ -367,20 +379,20 @@ public class Model extends ModelNode
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
Model model = (Model)BinaryImporter.getInstance().load(fis);
|
||||
fis.close();
|
||||
|
||||
|
||||
// initialize the model as a prototype
|
||||
model.initPrototype();
|
||||
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* No-arg constructor for deserialization.
|
||||
*/
|
||||
public Model ()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Standard constructor.
|
||||
*/
|
||||
@@ -389,7 +401,7 @@ public class Model extends ModelNode
|
||||
super(name);
|
||||
_props = props;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reference to the properties of the model.
|
||||
*/
|
||||
@@ -397,7 +409,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
return _props;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes this model as prototype. Only necessary when the prototype
|
||||
* was not loaded through {@link #readFromFile}.
|
||||
@@ -416,7 +428,7 @@ public class Model extends ModelNode
|
||||
cullInvisibleNodes();
|
||||
initInstance();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the names of the model's variant configurations.
|
||||
*/
|
||||
@@ -424,7 +436,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
return StringUtil.parseStringArray(_props.getProperty("variants", ""));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an animation to the model's library. This should only be called by
|
||||
* the model compiler.
|
||||
@@ -435,7 +447,7 @@ public class Model extends ModelNode
|
||||
_anims = new HashMap<String, Animation>();
|
||||
}
|
||||
_anims.put(name, anim);
|
||||
|
||||
|
||||
// store the original transforms
|
||||
Transform[] oxforms = new Transform[anim.transformTargets.length];
|
||||
for (int ii = 0; ii < anim.transformTargets.length; ii++) {
|
||||
@@ -445,7 +457,7 @@ public class Model extends ModelNode
|
||||
new Quaternion(target.getLocalRotation()),
|
||||
new Vector3f(target.getLocalScale()));
|
||||
}
|
||||
|
||||
|
||||
// run through every frame of the animation, expanding the bounding
|
||||
// volumes of any deformable meshes
|
||||
for (int ii = 0; ii < anim.transforms.length; ii++) {
|
||||
@@ -455,14 +467,14 @@ public class Model extends ModelNode
|
||||
updateWorldData(0f);
|
||||
expandModelBounds();
|
||||
}
|
||||
|
||||
|
||||
// restore the original transforms
|
||||
for (int ii = 0; ii < anim.transformTargets.length; ii++) {
|
||||
oxforms[ii].apply(anim.transformTargets[ii]);
|
||||
}
|
||||
updateWorldData(0f);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the animation mode to use for this model. This should be set
|
||||
* on the prototype before any animations are started or any instances
|
||||
@@ -472,7 +484,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
_animMode = mode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the animation mode configured for this model.
|
||||
*/
|
||||
@@ -480,7 +492,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
return _animMode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the names of the model's animations.
|
||||
*/
|
||||
@@ -492,7 +504,7 @@ public class Model extends ModelNode
|
||||
return (_anims == null) ? new String[0] :
|
||||
_anims.keySet().toArray(new String[_anims.size()]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the unit has an animation with the given name.
|
||||
*/
|
||||
@@ -503,7 +515,7 @@ public class Model extends ModelNode
|
||||
}
|
||||
return (_anims == null) ? false : _anims.containsKey(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the named animation.
|
||||
*
|
||||
@@ -533,14 +545,16 @@ public class Model extends ModelNode
|
||||
if (_anim != null) {
|
||||
_animObservers.apply(new AnimCancelledOp(_animName));
|
||||
}
|
||||
|
||||
// first cull all model nodes, then re-activate the ones in the
|
||||
// target list
|
||||
|
||||
// first cull all model nodes, then re-activate the ones in the target lists
|
||||
cullModelNodes();
|
||||
for (Spatial target : anim.staticTargets) {
|
||||
((ModelNode)target).updateCullMode();
|
||||
}
|
||||
for (Spatial target : anim.transformTargets) {
|
||||
((ModelNode)target).updateCullMode();
|
||||
}
|
||||
|
||||
|
||||
_paused = false;
|
||||
_anim = anim;
|
||||
_animName = name;
|
||||
@@ -552,7 +566,7 @@ public class Model extends ModelNode
|
||||
_animObservers.apply(new AnimStartedOp(_animName));
|
||||
return anim.getDuration() / _animSpeed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fast-forwards the current animation by the given number of seconds.
|
||||
*/
|
||||
@@ -560,7 +574,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
updateAnimation(time);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a reference to the animation with the given name.
|
||||
*/
|
||||
@@ -583,7 +597,7 @@ public class Model extends ModelNode
|
||||
Log.warning("Requested unknown animation [name=" + name + "].");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reference to the currently running animation, or
|
||||
* <code>null</code> if no animation is running.
|
||||
@@ -592,7 +606,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
return _anim;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops the currently running animation.
|
||||
*/
|
||||
@@ -633,7 +647,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
_fdir *= -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the animation speed, which acts as a multiplier for the frame rate
|
||||
* of each animation.
|
||||
@@ -642,7 +656,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
_animSpeed = speed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the currently configured animation speed.
|
||||
*/
|
||||
@@ -650,7 +664,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
return _animSpeed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an animation observer.
|
||||
*/
|
||||
@@ -658,7 +672,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
_animObservers.add(obs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes an animation observer.
|
||||
*/
|
||||
@@ -666,7 +680,7 @@ public class Model extends ModelNode
|
||||
{
|
||||
_animObservers.remove(obs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reference to the node that contains this model's emissions
|
||||
* (in world space, so the emissions do not move with the model). This
|
||||
@@ -685,7 +699,7 @@ public class Model extends ModelNode
|
||||
}
|
||||
return _emissionNode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes this model out to a file.
|
||||
*/
|
||||
@@ -697,7 +711,7 @@ public class Model extends ModelNode
|
||||
BinaryExporter.getInstance().save(this, fos);
|
||||
fos.close();
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void read (JMEImporter im)
|
||||
throws IOException
|
||||
@@ -729,7 +743,7 @@ public class Model extends ModelNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void write (JMEExporter ex)
|
||||
throws IOException
|
||||
@@ -756,7 +770,7 @@ public class Model extends ModelNode
|
||||
}
|
||||
capsule.writeSavableArrayList(getControllers(), "controllers", null);
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void resolveTextures (TextureProvider tprov)
|
||||
{
|
||||
@@ -767,7 +781,7 @@ public class Model extends ModelNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new prototype using the given variant configuration.
|
||||
* Use {@link #createInstance} on the returned prototype to create
|
||||
@@ -788,7 +802,7 @@ public class Model extends ModelNode
|
||||
}
|
||||
prototype._prototype = null;
|
||||
prototype._pnodes = null;
|
||||
|
||||
|
||||
// reconfigure meshes with new variant type
|
||||
if (variant != null) {
|
||||
prototype._props = PropertiesUtil.getFilteredProperties(
|
||||
@@ -802,10 +816,10 @@ public class Model extends ModelNode
|
||||
}
|
||||
return prototype;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates and returns a new instance of this model.
|
||||
*/
|
||||
*/
|
||||
public Model createInstance ()
|
||||
{
|
||||
if (_prototype != null) {
|
||||
@@ -838,7 +852,7 @@ public class Model extends ModelNode
|
||||
}
|
||||
lockInstance(targets);
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public Spatial putClone (Spatial store, CloneCreator properties)
|
||||
{
|
||||
@@ -866,7 +880,7 @@ public class Model extends ModelNode
|
||||
mstore._animMode = _animMode;
|
||||
return mstore;
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void updateGeometricState (float time, boolean initiator)
|
||||
{
|
||||
@@ -875,13 +889,13 @@ public class Model extends ModelNode
|
||||
// into view
|
||||
boolean wasOutside = _outside;
|
||||
_outside = isOutsideFrustum() && worldBound != null;
|
||||
|
||||
|
||||
// slow evvvverything down by the animation speed
|
||||
time *= _animSpeed;
|
||||
if (_anim != null) {
|
||||
updateAnimation(time);
|
||||
}
|
||||
|
||||
|
||||
// update controllers and children with accumulated time
|
||||
_accum += time;
|
||||
if (_outside) {
|
||||
@@ -894,14 +908,14 @@ public class Model extends ModelNode
|
||||
updateWorldVectors();
|
||||
worldBound = _storedBound.transform(getWorldRotation(),
|
||||
getWorldTranslation(), getWorldScale(), worldBound);
|
||||
|
||||
|
||||
} else {
|
||||
super.updateGeometricState(_shouldAccumulate ? _accum : time,
|
||||
initiator);
|
||||
_accum = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void onDraw (Renderer r)
|
||||
{
|
||||
@@ -912,7 +926,7 @@ public class Model extends ModelNode
|
||||
updateWorldData(0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transforms the current world bound into model space and stores it for
|
||||
* when the model is offscreen.
|
||||
@@ -928,10 +942,10 @@ public class Model extends ModelNode
|
||||
getChild(ii).updateGeometricState(0f, false);
|
||||
}
|
||||
updateWorldBound();
|
||||
|
||||
|
||||
_storedBound = worldBound.clone(_storedBound);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether this node was determined to be entirely outside the
|
||||
* view frustum.
|
||||
@@ -945,7 +959,7 @@ public class Model extends ModelNode
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the per-instance state of this model.
|
||||
*/
|
||||
@@ -960,7 +974,7 @@ public class Model extends ModelNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the model's state according to the current animation.
|
||||
*/
|
||||
@@ -972,7 +986,7 @@ public class Model extends ModelNode
|
||||
_elapsed += (time * _anim.frameRate);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// advance the frame counter if necessary
|
||||
while (_elapsed > 1f) {
|
||||
if (!_paused) {
|
||||
@@ -982,13 +996,13 @@ public class Model extends ModelNode
|
||||
_elapsed = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update the target transforms and animation frame if not outside the
|
||||
// view frustum
|
||||
if (!_outside) {
|
||||
updateMeshes();
|
||||
updateMeshes();
|
||||
}
|
||||
|
||||
|
||||
// if the next index is the same as this one, we are finished
|
||||
if (_fidx == _nidx && !_paused) {
|
||||
_anim = null;
|
||||
@@ -997,7 +1011,7 @@ public class Model extends ModelNode
|
||||
}
|
||||
_elapsed += (time * _anim.frameRate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Advances the frame counter by one frame.
|
||||
*/
|
||||
@@ -1007,14 +1021,14 @@ public class Model extends ModelNode
|
||||
int nframes = _anim.transforms.length;
|
||||
if (_anim.repeatType == Controller.RT_CLAMP) {
|
||||
_nidx = Math.max(0, Math.min(_nidx + _fdir, nframes - 1));
|
||||
|
||||
|
||||
} else if (_anim.repeatType == Controller.RT_WRAP) {
|
||||
// % is not a modulo operator, so is not guaranteed to be positive
|
||||
_nidx = (_nidx + _fdir) % nframes;
|
||||
if (_nidx < 0) {
|
||||
_nidx += nframes;
|
||||
}
|
||||
|
||||
|
||||
} else { // _anim.repeatType == Controller.RT_CYCLE
|
||||
if ((_nidx + _fdir) < 0 || (_nidx + _fdir) >= nframes) {
|
||||
_fdir *= -1; // reverse direction
|
||||
@@ -1022,7 +1036,7 @@ public class Model extends ModelNode
|
||||
_nidx += _fdir;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the states of the model's meshes according to the animation state.
|
||||
*/
|
||||
@@ -1037,7 +1051,7 @@ public class Model extends ModelNode
|
||||
_anim.stored[_fidx] = true;
|
||||
}
|
||||
setMeshFrame(frameId);
|
||||
|
||||
|
||||
} else if (_animMode == AnimationMode.MORPH) {
|
||||
int frameId1 = (_anim.animId << 16) | _fidx,
|
||||
frameId2 = (_anim.animId << 16) | _nidx;
|
||||
@@ -1055,74 +1069,74 @@ public class Model extends ModelNode
|
||||
}
|
||||
_anim.blendFrames(_fidx, _nidx, _elapsed);
|
||||
blendMeshFrames(frameId1, frameId2, _elapsed);
|
||||
|
||||
|
||||
} else { // _animMode == AnimationMode.SKIN
|
||||
_anim.blendFrames(_fidx, _nidx, _elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** A reference to the prototype, or <code>null</code> if this is a
|
||||
* prototype. */
|
||||
protected Model _prototype;
|
||||
|
||||
|
||||
/** For prototype models, a customized clone creator used to generate
|
||||
* instances. */
|
||||
protected CloneCreator _ccreator;
|
||||
|
||||
|
||||
/** The animation mode to use for this model. */
|
||||
protected AnimationMode _animMode;
|
||||
|
||||
|
||||
/** For instances, maps prototype nodes to their corresponding instance
|
||||
* nodes. */
|
||||
protected HashMap _pnodes;
|
||||
|
||||
|
||||
/** The model properties. */
|
||||
protected Properties _props;
|
||||
|
||||
|
||||
/** The model animations. */
|
||||
protected HashMap<String, Animation> _anims;
|
||||
|
||||
|
||||
/** The currently running animation, or <code>null</code> for none. */
|
||||
protected Animation _anim;
|
||||
|
||||
|
||||
/** The name of the currently running animation, if any. */
|
||||
protected String _animName;
|
||||
|
||||
|
||||
/** The current animation speed multiplier. */
|
||||
protected float _animSpeed = 1f;
|
||||
|
||||
|
||||
/** The index of the current and next frames. */
|
||||
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;
|
||||
|
||||
/** The pause status of this model. */
|
||||
protected boolean _paused;
|
||||
|
||||
|
||||
/** The amount of update time accumulated while outside of view frustum. */
|
||||
protected float _accum;
|
||||
|
||||
|
||||
/** Whether or not we should accumulate update time while out of view. */
|
||||
protected boolean _shouldAccumulate;
|
||||
|
||||
|
||||
/** The child node that contains the model's emissions in world space. */
|
||||
protected Node _emissionNode;
|
||||
|
||||
|
||||
/** The model space bounding volume stored for use when the model is
|
||||
* offscreen. */
|
||||
protected BoundingVolume _storedBound;
|
||||
|
||||
|
||||
/** Whether or not we were outside the frustum at the last update. */
|
||||
protected boolean _outside;
|
||||
|
||||
|
||||
/** Animation completion listeners. */
|
||||
protected ObserverList<AnimationObserver> _animObservers =
|
||||
new ObserverList<AnimationObserver>(ObserverList.FAST_UNSAFE_NOTIFY);
|
||||
|
||||
|
||||
/** Used to notify observers of animation initiation. */
|
||||
protected class AnimStartedOp
|
||||
implements ObserverList.ObserverOp<AnimationObserver>
|
||||
@@ -1131,17 +1145,17 @@ public class Model extends ModelNode
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ObserverOp
|
||||
public boolean apply (AnimationObserver obs)
|
||||
{
|
||||
return obs.animationStarted(Model.this, _name);
|
||||
}
|
||||
|
||||
|
||||
/** The name of the animation started. */
|
||||
protected String _name;
|
||||
}
|
||||
|
||||
|
||||
/** Used to notify observers of animation completion. */
|
||||
protected class AnimCompletedOp
|
||||
implements ObserverList.ObserverOp<AnimationObserver>
|
||||
@@ -1150,17 +1164,17 @@ public class Model extends ModelNode
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ObserverOp
|
||||
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>
|
||||
@@ -1169,13 +1183,13 @@ public class Model extends ModelNode
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ObserverOp
|
||||
public boolean apply (AnimationObserver obs)
|
||||
{
|
||||
return obs.animationCancelled(Model.this, _name);
|
||||
}
|
||||
|
||||
|
||||
/** The name of the animation cancelled. */
|
||||
protected String _name;
|
||||
}
|
||||
|
||||
@@ -144,51 +144,6 @@ public class ModelMesh extends TriMesh
|
||||
Boolean.parseBoolean(tprops.getProperty("translucent"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an opaque object representing this mesh's attributes. The
|
||||
* object will implement {@link Object#hashCode} and {@link Object#equals}
|
||||
* so that meshes with the same attributes will have identical keys.
|
||||
*/
|
||||
public Object getAttributeKey ()
|
||||
{
|
||||
return new AttributeKey(_textureKey, _textures, _sphereMapped,
|
||||
_filterMode, _mipMapMode, _compress, _emissiveMap, _emissive, _additive,
|
||||
_solid, _transparent, _alphaThreshold, _translucent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges another mesh into this one. The mesh is assumed to be in the same coordinate system
|
||||
* and have the same attributes.
|
||||
*/
|
||||
public void merge (ModelMesh mesh)
|
||||
{
|
||||
TriangleBatch batch = (TriangleBatch)getBatch(0);
|
||||
TriangleBatch mbatch = (TriangleBatch)mesh.getBatch(0);
|
||||
|
||||
int vcount = batch.getVertexCount();
|
||||
batch.setVertexBuffer(concatenate(batch.getVertexBuffer(), mbatch.getVertexBuffer()));
|
||||
batch.setNormalBuffer(concatenate(batch.getNormalBuffer(), mbatch.getNormalBuffer()));
|
||||
if (batch.getColorBuffer() != null && mbatch.getColorBuffer() != null) {
|
||||
batch.setColorBuffer(concatenate(batch.getColorBuffer(), mbatch.getColorBuffer()));
|
||||
}
|
||||
batch.setTextureBuffer(concatenate(
|
||||
batch.getTextureBuffer(0), mbatch.getTextureBuffer(0)), 0);
|
||||
for (int ii = 1, nn = getTextureCount(); ii < nn; ii++) {
|
||||
batch.setTextureBuffer(batch.getTextureBuffer(0), ii);
|
||||
}
|
||||
|
||||
IntBuffer ibuf = batch.getIndexBuffer(), mibuf = mbatch.getIndexBuffer();
|
||||
IntBuffer nibuf = BufferUtils.createIntBuffer(ibuf.capacity() + mibuf.capacity());
|
||||
ibuf.clear();
|
||||
nibuf.put(ibuf);
|
||||
mibuf.clear();
|
||||
while (mibuf.hasRemaining()) {
|
||||
nibuf.put(mibuf.get() + vcount);
|
||||
}
|
||||
nibuf.clear();
|
||||
batch.setIndexBuffer(nibuf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the vertices and the transform of the mesh so that the mesh's
|
||||
* position lies at the center of its bounding volume.
|
||||
@@ -819,30 +774,6 @@ public class ModelMesh extends TriMesh
|
||||
new RenderState[RenderState.RS_MAX_STATE];
|
||||
}
|
||||
|
||||
/** A wrapper around an array of attributes that uses {@link Arrays#deepHashCode} and
|
||||
* {@link Arrays#deepEquals} for hashing/comparison. */
|
||||
protected static class AttributeKey
|
||||
{
|
||||
public AttributeKey (Object... attrs)
|
||||
{
|
||||
_attrs = attrs;
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public int hashCode ()
|
||||
{
|
||||
return Arrays.deepHashCode(_attrs);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public boolean equals (Object other)
|
||||
{
|
||||
return Arrays.deepEquals(_attrs, ((AttributeKey)other)._attrs);
|
||||
}
|
||||
|
||||
protected Object[] _attrs;
|
||||
}
|
||||
|
||||
/** The name of the texture specified in the model file, which acts as a
|
||||
* property key and a default value. */
|
||||
protected String _textureKey;
|
||||
|
||||
@@ -41,6 +41,7 @@ import com.jme.util.export.InputCapsule;
|
||||
import com.jme.util.export.OutputCapsule;
|
||||
|
||||
import com.threerings.jme.Log;
|
||||
import com.threerings.jme.util.JmeUtil;
|
||||
|
||||
/**
|
||||
* A {@link Node} with a serialization mechanism tailored to stored models.
|
||||
@@ -55,7 +56,7 @@ public class ModelNode extends Node
|
||||
{
|
||||
super("node");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Standard constructor.
|
||||
*/
|
||||
@@ -63,14 +64,14 @@ public class ModelNode extends Node
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public int hashCode ()
|
||||
{
|
||||
// hash on the name rather than the identity for consistent ordering
|
||||
return getName().hashCode();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recursively searches the scene graph rooted at this node for a
|
||||
* node with the provided name.
|
||||
@@ -90,7 +91,7 @@ public class ModelNode extends Node
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reference to the model space transform of the node.
|
||||
*/
|
||||
@@ -98,7 +99,7 @@ public class ModelNode extends Node
|
||||
{
|
||||
return _modelTransform;
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void updateWorldData (float time)
|
||||
{
|
||||
@@ -108,7 +109,7 @@ public class ModelNode extends Node
|
||||
super.updateWorldData(time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void updateWorldBound ()
|
||||
{
|
||||
@@ -117,22 +118,22 @@ public class ModelNode extends Node
|
||||
super.updateWorldBound();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void updateWorldVectors ()
|
||||
{
|
||||
super.updateWorldVectors();
|
||||
if (parent instanceof ModelNode) {
|
||||
setTransform(getLocalTranslation(), getLocalRotation(),
|
||||
JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
|
||||
getLocalScale(), _localTransform);
|
||||
((ModelNode)parent).getModelTransform().mult(_localTransform,
|
||||
_modelTransform);
|
||||
|
||||
|
||||
} else {
|
||||
_modelTransform.loadIdentity();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ModelSpatial
|
||||
public Spatial putClone (Spatial store, Model.CloneCreator properties)
|
||||
{
|
||||
@@ -178,7 +179,7 @@ public class ModelNode extends Node
|
||||
mstore._hasVisibleDescendants = _hasVisibleDescendants;
|
||||
return mstore;
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void read (JMEImporter im)
|
||||
throws IOException
|
||||
@@ -198,7 +199,7 @@ public class ModelNode extends Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void write (JMEExporter ex)
|
||||
throws IOException
|
||||
@@ -210,7 +211,7 @@ public class ModelNode extends Node
|
||||
capsule.write(getLocalScale(), "localScale", null);
|
||||
capsule.writeSavableArrayList(getChildren(), "children", null);
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ModelSpatial
|
||||
public void expandModelBounds ()
|
||||
{
|
||||
@@ -221,7 +222,7 @@ public class ModelNode extends Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ModelSpatial
|
||||
public void setReferenceTransforms ()
|
||||
{
|
||||
@@ -233,7 +234,7 @@ public class ModelNode extends Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ModelSpatial
|
||||
public void lockStaticMeshes (
|
||||
Renderer renderer, boolean useVBOs, boolean useDisplayLists)
|
||||
@@ -246,7 +247,7 @@ public class ModelNode extends Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ModelSpatial
|
||||
public void resolveTextures (TextureProvider tprov)
|
||||
{
|
||||
@@ -257,7 +258,7 @@ public class ModelNode extends Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ModelSpatial
|
||||
public void storeMeshFrame (int frameId, boolean blend)
|
||||
{
|
||||
@@ -268,7 +269,7 @@ public class ModelNode extends Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ModelSpatial
|
||||
public void setMeshFrame (int frameId)
|
||||
{
|
||||
@@ -279,7 +280,7 @@ public class ModelNode extends Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited from interface ModelSpatial
|
||||
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
|
||||
{
|
||||
@@ -291,7 +292,7 @@ public class ModelNode extends Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether this node should be culled even if it has mesh
|
||||
* descendants.
|
||||
@@ -300,7 +301,7 @@ public class ModelNode extends Node
|
||||
{
|
||||
_forceCull = force;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether this node should be culled even if it has mesh
|
||||
* descendants.
|
||||
@@ -309,7 +310,7 @@ public class ModelNode extends Node
|
||||
{
|
||||
return _forceCull;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the cull state of any nodes that do not contain geometric
|
||||
* descendants to {@link CULL_ALWAYS} so that they don't waste
|
||||
@@ -330,7 +331,7 @@ public class ModelNode extends Node
|
||||
updateCullMode();
|
||||
return _hasVisibleDescendants;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Locks the transforms and bounds of this instance with the assumption
|
||||
* that the position will never change.
|
||||
@@ -344,14 +345,14 @@ public class ModelNode extends Node
|
||||
{
|
||||
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();
|
||||
}
|
||||
@@ -364,7 +365,7 @@ public class ModelNode extends Node
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recursively culls all model nodes under this one in preparation for
|
||||
* activating the ones listed in an animation.
|
||||
@@ -379,7 +380,7 @@ public class ModelNode extends Node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes this node visible if and only if it has visible descendants and
|
||||
* has not been specifically culled.
|
||||
@@ -389,42 +390,16 @@ public class ModelNode extends Node
|
||||
setCullMode((_hasVisibleDescendants && !_forceCull) ?
|
||||
CULL_INHERIT : CULL_ALWAYS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.setRotationQuaternion(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();
|
||||
|
||||
/** Whether or not this node has mesh descendants. */
|
||||
protected boolean _hasVisibleDescendants;
|
||||
|
||||
|
||||
/** If true, cull the node even if it has mesh descendants. */
|
||||
protected boolean _forceCull;
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
|
||||
import com.threerings.jme.Log;
|
||||
import com.threerings.jme.util.JmeUtil;
|
||||
|
||||
/**
|
||||
* A triangle mesh that deforms according to a bone hierarchy.
|
||||
@@ -67,10 +68,10 @@ public class SkinMesh extends ModelMesh
|
||||
{
|
||||
/** 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. */
|
||||
@@ -92,13 +93,13 @@ public class SkinMesh extends ModelMesh
|
||||
wgroup.weights = weights;
|
||||
return wgroup;
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited
|
||||
public Class getClassTag ()
|
||||
{
|
||||
return getClass();
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited
|
||||
public void read (JMEImporter im)
|
||||
throws IOException
|
||||
@@ -119,7 +120,7 @@ public class SkinMesh extends ModelMesh
|
||||
capsule.write(bones, "bones", null);
|
||||
capsule.write(weights, "weights", null);
|
||||
}
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1;
|
||||
}
|
||||
|
||||
@@ -129,24 +130,24 @@ public class SkinMesh extends ModelMesh
|
||||
{
|
||||
/** 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();
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
|
||||
public Bone ()
|
||||
{
|
||||
transform = new Matrix4f();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rebinds this bone for a prototype instance.
|
||||
*
|
||||
@@ -159,13 +160,13 @@ public class SkinMesh extends ModelMesh
|
||||
bone.transform = new Matrix4f();
|
||||
return bone;
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited
|
||||
public Class getClassTag ()
|
||||
{
|
||||
return getClass();
|
||||
}
|
||||
|
||||
|
||||
// documentation inherited
|
||||
public void read (JMEImporter im)
|
||||
throws IOException
|
||||
@@ -181,17 +182,17 @@ public class SkinMesh extends ModelMesh
|
||||
OutputCapsule capsule = ex.getCapsule(this);
|
||||
capsule.write(node, "node", null);
|
||||
}
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* No-arg constructor for deserialization.
|
||||
*/
|
||||
public SkinMesh ()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an empty mesh.
|
||||
*/
|
||||
@@ -199,7 +200,7 @@ public class SkinMesh extends ModelMesh
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the array of weight groups that determine how bones affect
|
||||
* each vertex.
|
||||
@@ -207,7 +208,7 @@ public class SkinMesh extends ModelMesh
|
||||
public void setWeightGroups (WeightGroup[] weightGroups)
|
||||
{
|
||||
_weightGroups = weightGroups;
|
||||
|
||||
|
||||
// compile a list of all referenced bones
|
||||
HashSet<Bone> bones = new HashSet<Bone>();
|
||||
for (WeightGroup group : weightGroups) {
|
||||
@@ -215,18 +216,18 @@ public class SkinMesh extends ModelMesh
|
||||
}
|
||||
_bones = bones.toArray(new Bone[bones.size()]);
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void reconstruct (
|
||||
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
|
||||
FloatBuffer textures, IntBuffer indices)
|
||||
{
|
||||
super.reconstruct(vertices, normals, colors, textures, indices);
|
||||
|
||||
|
||||
// initialize the quantized frame table
|
||||
_frames = new HashIntMap<Object>();
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public Spatial putClone (Spatial store, Model.CloneCreator properties)
|
||||
{
|
||||
@@ -264,7 +265,7 @@ public class SkinMesh extends ModelMesh
|
||||
mstore._nbuf = new float[_nbuf.length];
|
||||
return mstore;
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void read (JMEImporter im)
|
||||
throws IOException
|
||||
@@ -274,7 +275,7 @@ public class SkinMesh extends ModelMesh
|
||||
setWeightGroups(ArrayUtil.copy(capsule.readSavableArray(
|
||||
"weightGroups", null), new WeightGroup[0]));
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void write (JMEExporter ex)
|
||||
throws IOException
|
||||
@@ -283,7 +284,7 @@ public class SkinMesh extends ModelMesh
|
||||
OutputCapsule capsule = ex.getCapsule(this);
|
||||
capsule.write(_weightGroups, "weightGroups", null);
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void expandModelBounds ()
|
||||
{
|
||||
@@ -292,14 +293,14 @@ public class SkinMesh extends ModelMesh
|
||||
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(),
|
||||
JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
|
||||
getLocalScale(), transform);
|
||||
((ModelNode)parent).getModelTransform().mult(transform,
|
||||
_invRefTransform);
|
||||
@@ -310,7 +311,7 @@ public class SkinMesh extends ModelMesh
|
||||
_invRefTransform.mult(bone.node.getModelTransform()).invert();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void lockStaticMeshes (
|
||||
Renderer renderer, boolean useVBOs, boolean useDisplayLists)
|
||||
@@ -325,14 +326,14 @@ public class SkinMesh extends ModelMesh
|
||||
}
|
||||
_useDisplayLists = useDisplayLists && !_translucent;
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void storeMeshFrame (int frameId, boolean blend)
|
||||
{
|
||||
_storeFrameId = frameId;
|
||||
_storeBlend = blend;
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void setMeshFrame (int frameId)
|
||||
{
|
||||
@@ -346,7 +347,7 @@ public class SkinMesh extends ModelMesh
|
||||
getBatch(0).updateRenderState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
|
||||
{
|
||||
@@ -359,7 +360,7 @@ public class SkinMesh extends ModelMesh
|
||||
nbuf.rewind();
|
||||
nbuf.put(_nbuf);
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
public void updateWorldData (float time)
|
||||
{
|
||||
@@ -373,7 +374,7 @@ public class SkinMesh extends ModelMesh
|
||||
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;
|
||||
@@ -397,11 +398,11 @@ public class SkinMesh extends ModelMesh
|
||||
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;
|
||||
@@ -414,7 +415,7 @@ public class SkinMesh extends ModelMesh
|
||||
_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) {
|
||||
@@ -428,7 +429,7 @@ public class SkinMesh extends ModelMesh
|
||||
_storeFrameId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stores the current frame data for later use.
|
||||
*/
|
||||
@@ -465,12 +466,12 @@ public class SkinMesh extends ModelMesh
|
||||
_frames.put(_storeFrameId, tbatch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override // documentation inherited
|
||||
protected void storeOriginalBuffers ()
|
||||
{
|
||||
super.storeOriginalBuffers();
|
||||
|
||||
|
||||
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
|
||||
vbuf.rewind();
|
||||
nbuf.rewind();
|
||||
@@ -479,19 +480,19 @@ public class SkinMesh extends ModelMesh
|
||||
_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)
|
||||
{
|
||||
@@ -502,26 +503,26 @@ public class SkinMesh extends ModelMesh
|
||||
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[] _onbuf, _ovbuf, _nbuf;
|
||||
@@ -530,12 +531,12 @@ public class SkinMesh extends ModelMesh
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -26,15 +26,19 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.jme.math.Matrix4f;
|
||||
import com.jme.math.Quaternion;
|
||||
import com.jme.math.Vector3f;
|
||||
import com.jme.scene.Controller;
|
||||
import com.jme.scene.Node;
|
||||
import com.jme.scene.Spatial;
|
||||
|
||||
import com.threerings.jme.Log;
|
||||
import com.threerings.jme.model.Model;
|
||||
import com.threerings.jme.util.JmeUtil;
|
||||
|
||||
import com.threerings.jme.tools.ModelDef.TransformNode;
|
||||
|
||||
/**
|
||||
* A basic representation for keyframe animations.
|
||||
*/
|
||||
@@ -42,35 +46,39 @@ public class AnimationDef
|
||||
{
|
||||
/** The rate of the animation in frames per second. */
|
||||
public int frameRate;
|
||||
|
||||
|
||||
/** A single frame of the animation. */
|
||||
public static class FrameDef
|
||||
{
|
||||
/** Transform for affected nodes. */
|
||||
public ArrayList<TransformDef> transforms =
|
||||
new ArrayList<TransformDef>();
|
||||
|
||||
|
||||
public void addTransform (TransformDef transform)
|
||||
{
|
||||
transforms.add(transform);
|
||||
}
|
||||
|
||||
/** Adds all transform targets in this frame to the supplied set. */
|
||||
|
||||
/** Adds all transform targets in this frame to the supplied sets. */
|
||||
public void addTransformTargets (
|
||||
HashMap<String, Spatial> nodes, HashSet<Spatial> targets)
|
||||
HashMap<String, Spatial> nodes, HashMap<String, TransformNode> tnodes,
|
||||
HashSet<Spatial> staticTargets, HashSet<Spatial> transformTargets)
|
||||
{
|
||||
for (int ii = 0, nn = transforms.size(); ii < nn; ii++) {
|
||||
String name = transforms.get(ii).name;
|
||||
Spatial target = nodes.get(name);
|
||||
if (target != null) {
|
||||
targets.add(target);
|
||||
if (target == null) {
|
||||
continue;
|
||||
}
|
||||
TransformNode tnode = tnodes.get(name);
|
||||
if (tnode.baseLocalTransform != null) {
|
||||
staticTargets.add(target);
|
||||
} else {
|
||||
Log.debug("Missing animation target [name=" + name +
|
||||
"].");
|
||||
transformTargets.add(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Returns the array of transforms for this frame. */
|
||||
public Model.Transform[] getTransforms (Spatial[] targets)
|
||||
{
|
||||
@@ -81,7 +89,7 @@ public class AnimationDef
|
||||
}
|
||||
return mtransforms;
|
||||
}
|
||||
|
||||
|
||||
/** Returns the transform for the supplied target. */
|
||||
protected Model.Transform getTransform (Spatial target)
|
||||
{
|
||||
@@ -95,18 +103,18 @@ public class AnimationDef
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** A transform for a single node. */
|
||||
public static class TransformDef
|
||||
{
|
||||
/** The name of the affected node. */
|
||||
public String name;
|
||||
|
||||
|
||||
/** The transformation parameters. */
|
||||
public float[] translation;
|
||||
public float[] rotation;
|
||||
public float[] scale;
|
||||
|
||||
|
||||
/** Returns the live transform object. */
|
||||
public Model.Transform getTransform ()
|
||||
{
|
||||
@@ -117,15 +125,36 @@ public class AnimationDef
|
||||
new Vector3f(scale[0], scale[1], scale[2]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** The individual frames of the animation. */
|
||||
public ArrayList<FrameDef> frames = new ArrayList<FrameDef>();
|
||||
|
||||
|
||||
public void addFrame (FrameDef frame)
|
||||
{
|
||||
frames.add(frame);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs through each frame of the animation, updating the transforms of the preprocessing
|
||||
* node to keep track of which transforms diverge from their original states.
|
||||
*/
|
||||
public void filterTransforms (Node root, HashMap<String, TransformNode> nodes)
|
||||
{
|
||||
for (FrameDef frame : frames) {
|
||||
for (TransformDef transform : frame.transforms) {
|
||||
TransformNode node = nodes.get(transform.name);
|
||||
if (node != null) {
|
||||
node.setLocalTransform(
|
||||
transform.translation, transform.rotation, transform.scale);
|
||||
}
|
||||
}
|
||||
root.updateGeometricState(0f, true);
|
||||
for (TransformNode node : nodes.values()) {
|
||||
node.cullDivergentTransforms();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the "live" animation object that will be serialized with the
|
||||
* object.
|
||||
@@ -134,27 +163,30 @@ public class AnimationDef
|
||||
* @param nodes the nodes in the model, mapped by name
|
||||
*/
|
||||
public Model.Animation createAnimation (
|
||||
Properties props, HashMap<String, Spatial> nodes)
|
||||
Properties props, HashMap<String, Spatial> nodes, HashMap<String, TransformNode> tnodes)
|
||||
{
|
||||
// find all affected nodes
|
||||
HashSet<Spatial> targets = new HashSet<Spatial>();
|
||||
HashSet<Spatial> staticTargets = new HashSet<Spatial>(),
|
||||
transformTargets = new HashSet<Spatial>();
|
||||
for (int ii = 0, nn = frames.size(); ii < nn; ii++) {
|
||||
frames.get(ii).addTransformTargets(nodes, targets);
|
||||
frames.get(ii).addTransformTargets(nodes, tnodes, staticTargets, transformTargets);
|
||||
}
|
||||
|
||||
|
||||
// create and configure the animation
|
||||
Model.Animation anim = new Model.Animation();
|
||||
anim.frameRate = frameRate;
|
||||
anim.repeatType = JmeUtil.parseRepeatType(props.getProperty("repeat_type"),
|
||||
Controller.RT_CLAMP);
|
||||
|
||||
|
||||
// collect all transforms
|
||||
anim.transformTargets = targets.toArray(new Spatial[targets.size()]);
|
||||
anim.transforms = new Model.Transform[frames.size()][targets.size()];
|
||||
anim.staticTargets = staticTargets.toArray(new Spatial[staticTargets.size()]);
|
||||
anim.transformTargets = transformTargets.toArray(new Spatial[transformTargets.size()]);
|
||||
anim.transforms = new Model.Transform[frames.size()][transformTargets.size()];
|
||||
for (int ii = 0; ii < anim.transforms.length; ii++) {
|
||||
anim.transforms[ii] =
|
||||
frames.get(ii).getTransforms(anim.transformTargets);
|
||||
}
|
||||
|
||||
return anim;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.jme.scene.Node;
|
||||
import com.jme.scene.Spatial;
|
||||
import com.jme.util.LoggingSystem;
|
||||
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
|
||||
@@ -43,6 +44,7 @@ import com.threerings.jme.model.Model;
|
||||
import com.threerings.jme.model.ModelMesh;
|
||||
import com.threerings.jme.model.ModelNode;
|
||||
import com.threerings.jme.model.SkinMesh;
|
||||
import com.threerings.jme.tools.ModelDef.TransformNode;
|
||||
import com.threerings.jme.tools.xml.AnimationParser;
|
||||
import com.threerings.jme.tools.xml.ModelParser;
|
||||
|
||||
@@ -106,18 +108,32 @@ public class CompileModel
|
||||
|
||||
System.out.println("Compiling to " + target + "...");
|
||||
|
||||
// load the model content
|
||||
// parse the model and animations
|
||||
ModelDef mdef = _mparser.parseModel(content.toString());
|
||||
AnimationDef[] adefs = new AnimationDef[anims.length];
|
||||
for (int ii = 0; ii < adefs.length; ii++) {
|
||||
System.out.println(" Adding " + afiles[ii] + "...");
|
||||
adefs[ii] = _aparser.parseAnimation(afiles[ii].toString());
|
||||
}
|
||||
|
||||
// preprocess the model and animations to determine which nodes never move, which never
|
||||
// move within an animation, and which never move with respect to others
|
||||
HashMap<String, TransformNode> tnodes = new HashMap<String, TransformNode>();
|
||||
Node troot = mdef.createTransformTree(props, tnodes);
|
||||
for (AnimationDef adef : adefs) {
|
||||
adef.filterTransforms(troot, tnodes);
|
||||
}
|
||||
mdef.mergeSpatials(tnodes);
|
||||
|
||||
// load the model content
|
||||
HashMap<String, Spatial> nodes = new HashMap<String, Spatial>();
|
||||
Model model = mdef.createModel(props, nodes);
|
||||
model.initPrototype();
|
||||
|
||||
// load the animations, if any
|
||||
for (int ii = 0; ii < anims.length; ii++) {
|
||||
System.out.println(" Adding " + afiles[ii] + "...");
|
||||
AnimationDef adef = _aparser.parseAnimation(afiles[ii].toString());
|
||||
model.addAnimation(anims[ii], adef.createAnimation(
|
||||
PropertiesUtil.getSubProperties(props, anims[ii]), nodes));
|
||||
model.addAnimation(anims[ii], adefs[ii].createAnimation(
|
||||
PropertiesUtil.getSubProperties(props, anims[ii]), nodes, tnodes));
|
||||
}
|
||||
|
||||
// write and return the model
|
||||
|
||||
@@ -39,13 +39,18 @@ import java.util.Set;
|
||||
import com.jme.bounding.BoundingBox;
|
||||
import com.jme.bounding.BoundingSphere;
|
||||
import com.jme.math.FastMath;
|
||||
import com.jme.math.Matrix4f;
|
||||
import com.jme.math.Quaternion;
|
||||
import com.jme.math.Vector3f;
|
||||
import com.jme.scene.Node;
|
||||
import com.jme.scene.Spatial;
|
||||
import com.jme.scene.batch.GeomBatch;
|
||||
import com.jme.util.geom.BufferUtils;
|
||||
|
||||
import com.samskivert.util.ObjectUtil;
|
||||
import com.samskivert.util.PropertiesUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import com.threerings.jme.Log;
|
||||
import com.threerings.jme.model.Model;
|
||||
@@ -53,6 +58,7 @@ import com.threerings.jme.model.ModelController;
|
||||
import com.threerings.jme.model.ModelMesh;
|
||||
import com.threerings.jme.model.ModelNode;
|
||||
import com.threerings.jme.model.SkinMesh;
|
||||
import com.threerings.jme.util.JmeUtil;
|
||||
import com.threerings.jme.util.SpatialVisitor;
|
||||
|
||||
/**
|
||||
@@ -75,6 +81,21 @@ public class ModelDef
|
||||
public float[] rotation;
|
||||
public float[] scale;
|
||||
|
||||
/**
|
||||
* Stores the names of all bones referenced by this spatial in the supplied set.
|
||||
*/
|
||||
public void getBoneNames (HashSet<String> bones)
|
||||
{
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
/** Checks whether it's possible (disregarding issues of transformation) to merge
|
||||
* the specified spatial into this one. */
|
||||
public abstract boolean canMerge (Properties props, SpatialDef other);
|
||||
|
||||
/** Merges another spatial into this one. */
|
||||
public abstract void merge (SpatialDef other, Matrix4f xform);
|
||||
|
||||
/** Returns a JME node for this definition. */
|
||||
public Spatial getSpatial (Properties props)
|
||||
{
|
||||
@@ -155,6 +176,42 @@ public class ModelDef
|
||||
tcoords = tcoords || vertex.tcoords != null;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public boolean canMerge (Properties props, SpatialDef other)
|
||||
{
|
||||
if (getClass() != other.getClass()) {
|
||||
return false; // require exact class match
|
||||
}
|
||||
TriMeshDef omesh = (TriMeshDef)other;
|
||||
return solid == omesh.solid && transparent == omesh.transparent &&
|
||||
ObjectUtil.equals(texture, omesh.texture) &&
|
||||
PropertiesUtil.getSubProperties(props, name).equals(
|
||||
PropertiesUtil.getSubProperties(props, omesh.name));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void merge (SpatialDef other, Matrix4f xform)
|
||||
{
|
||||
TriMeshDef omesh = (TriMeshDef)other;
|
||||
|
||||
// prepend the inverse of the offset transformation
|
||||
xform = getOffsetTransform().invertLocal().multLocal(xform);
|
||||
|
||||
// and append the other's offset
|
||||
xform.multLocal(omesh.getOffsetTransform());
|
||||
|
||||
// extract the rotation to transform normals
|
||||
Quaternion xrot = xform.toRotationQuat();
|
||||
|
||||
// transform the vertices and add them in
|
||||
for (Vertex vertex : omesh.vertices) {
|
||||
vertex.transform(xform, xrot);
|
||||
}
|
||||
for (int idx : omesh.indices) {
|
||||
addVertex(omesh.vertices.get(idx));
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Spatial createSpatial (Properties props)
|
||||
{
|
||||
@@ -167,6 +224,24 @@ public class ModelDef
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Gets the matrix representing the offset transform. */
|
||||
protected Matrix4f getOffsetTransform ()
|
||||
{
|
||||
Vector3f otrans = new Vector3f(), oscale = new Vector3f(1f, 1f, 1f);
|
||||
Quaternion orot = new Quaternion();
|
||||
if (offsetTranslation != null) {
|
||||
otrans.set(offsetTranslation[0], offsetTranslation[1], offsetTranslation[2]);
|
||||
}
|
||||
if (offsetRotation != null) {
|
||||
orot.set(offsetRotation[0], offsetRotation[1], offsetRotation[2],
|
||||
offsetRotation[3]);
|
||||
}
|
||||
if (offsetScale != null) {
|
||||
oscale.set(offsetScale[0], offsetScale[1], offsetScale[2]);
|
||||
}
|
||||
return JmeUtil.setTransform(otrans, orot, oscale, new Matrix4f());
|
||||
}
|
||||
|
||||
/** Creates the mesh to attach to the node. */
|
||||
protected ModelMesh createMesh ()
|
||||
{
|
||||
@@ -229,6 +304,14 @@ public class ModelDef
|
||||
/** A triangle mesh that deforms according to bone positions. */
|
||||
public static class SkinMeshDef extends TriMeshDef
|
||||
{
|
||||
@Override // documentation inherited
|
||||
public void getBoneNames (HashSet<String> bones)
|
||||
{
|
||||
for (Vertex vertex : vertices) {
|
||||
bones.addAll(((SkinVertex)vertex).boneWeights.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
protected ModelMesh createMesh ()
|
||||
{
|
||||
@@ -250,6 +333,7 @@ public class ModelDef
|
||||
HashMap<String, SkinMesh.Bone> bones =
|
||||
new HashMap<String, SkinMesh.Bone>();
|
||||
int ii = 0;
|
||||
int mweights = 0, tweights = 0;
|
||||
for (Map.Entry<Set<String>, WeightGroupDef> entry :
|
||||
_groups.entrySet()) {
|
||||
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
|
||||
@@ -267,6 +351,8 @@ public class ModelDef
|
||||
wgroup.bones[jj++] = bone;
|
||||
}
|
||||
wgroup.weights = toArray(entry.getValue().weights);
|
||||
tweights += wgroup.bones.length;
|
||||
mweights = Math.max(wgroup.bones.length, mweights);
|
||||
wgroups[ii++] = wgroup;
|
||||
}
|
||||
((SkinMesh)_mesh).setWeightGroups(wgroups);
|
||||
@@ -315,6 +401,18 @@ public class ModelDef
|
||||
/** A generic node. */
|
||||
public static class NodeDef extends SpatialDef
|
||||
{
|
||||
// documentation inherited
|
||||
public boolean canMerge (Properties props, SpatialDef other)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void merge (SpatialDef other, Matrix4f xform)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Spatial createSpatial (Properties props)
|
||||
{
|
||||
@@ -329,6 +427,21 @@ public class ModelDef
|
||||
public float[] normal;
|
||||
public float[] tcoords;
|
||||
|
||||
public void transform (Matrix4f xform, Quaternion xrot)
|
||||
{
|
||||
Vector3f xvec = new Vector3f(location[0], location[1], location[2]);
|
||||
xform.mult(xvec, xvec);
|
||||
location[0] = xvec.x;
|
||||
location[1] = xvec.y;
|
||||
location[2] = xvec.z;
|
||||
|
||||
xvec.set(normal[0], normal[1], normal[2]);
|
||||
xrot.mult(xvec, xvec);
|
||||
normal[0] = xvec.x;
|
||||
normal[1] = xvec.y;
|
||||
normal[2] = xvec.z;
|
||||
}
|
||||
|
||||
public void setInBuffers (
|
||||
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
|
||||
{
|
||||
@@ -418,6 +531,99 @@ public class ModelDef
|
||||
public ArrayList<Float> weights = new ArrayList<Float>();
|
||||
}
|
||||
|
||||
/** Contains the transform of a node for preprocessing. */
|
||||
public static class TransformNode extends Node
|
||||
{
|
||||
/** The source definition. */
|
||||
public SpatialDef spatial;
|
||||
|
||||
/** If true, this node is referenced by name (as a bone or parent) and cannot be merged
|
||||
* into another. */
|
||||
public boolean referenced;
|
||||
|
||||
/** If true, this node is a controller target; nodes beneath it can only be merged with
|
||||
* other descendants. */
|
||||
public boolean controlled;
|
||||
|
||||
/** The node's current local transform. */
|
||||
public Matrix4f localTransform = new Matrix4f();
|
||||
|
||||
/** The node's current world space transform. */
|
||||
public Matrix4f worldTransform = new Matrix4f();
|
||||
|
||||
/** The node's local transform in the original model, or <code>null</code> if the node's
|
||||
* transform has diverged from the original. */
|
||||
public Matrix4f baseLocalTransform;
|
||||
|
||||
/** The relative transforms between this and all other loosely compatible nodes not yet
|
||||
* eliminated. As soon as the relative transform diverges in the course of preprocessing
|
||||
* an animation, the node/transform pair is removed from the list. */
|
||||
public ArrayList<Tuple<TransformNode, Matrix4f>> relativeTransforms;
|
||||
|
||||
public TransformNode (SpatialDef spatial)
|
||||
{
|
||||
super(spatial.name);
|
||||
this.spatial = spatial;
|
||||
setLocalTransform(spatial.translation, spatial.rotation, spatial.scale);
|
||||
}
|
||||
|
||||
public void setLocalTransform (float[] translation, float[] rotation, float[] scale)
|
||||
{
|
||||
getLocalTranslation().set(translation[0], translation[1], translation[2]);
|
||||
getLocalRotation().set(rotation[0], rotation[1], rotation[2], rotation[3]);
|
||||
getLocalScale().set(scale[0], scale[1], scale[2]);
|
||||
JmeUtil.setTransform(
|
||||
getLocalTranslation(), getLocalRotation(), getLocalScale(), localTransform);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void updateWorldVectors ()
|
||||
{
|
||||
super.updateWorldVectors();
|
||||
JmeUtil.setTransform(
|
||||
getWorldTranslation(), getWorldRotation(), getWorldScale(), worldTransform);
|
||||
}
|
||||
|
||||
public boolean canMerge (Properties props, TransformNode onode)
|
||||
{
|
||||
// nodes must have same controlled ancestor
|
||||
return !onode.referenced && spatial.canMerge(props, onode.spatial) &&
|
||||
getControlledAncestor() == onode.getControlledAncestor();
|
||||
}
|
||||
|
||||
protected Node getControlledAncestor ()
|
||||
{
|
||||
Node ref = this;
|
||||
while (ref instanceof TransformNode && !((TransformNode)ref).controlled) {
|
||||
ref = ref.getParent();
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
public void cullDivergentTransforms ()
|
||||
{
|
||||
if (baseLocalTransform != null && !epsilonEquals(localTransform, baseLocalTransform)) {
|
||||
baseLocalTransform = null;
|
||||
}
|
||||
for (Iterator<Tuple<TransformNode, Matrix4f>> it = relativeTransforms.iterator();
|
||||
it.hasNext(); ) {
|
||||
Tuple<TransformNode, Matrix4f> tuple = it.next();
|
||||
if (!epsilonEquals(getRelativeTransform(tuple.left), tuple.right)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Matrix4f getRelativeTransform (TransformNode other)
|
||||
{
|
||||
// return the matrix that takes vertices from the space of the other node
|
||||
// into the space of this one
|
||||
Matrix4f inv = new Matrix4f();
|
||||
worldTransform.invert(inv);
|
||||
return inv.mult(other.worldTransform);
|
||||
}
|
||||
}
|
||||
|
||||
/** The meshes and bones comprising the model. */
|
||||
public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>();
|
||||
|
||||
@@ -428,6 +634,85 @@ public class ModelDef
|
||||
spatial);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a transform tree representing the model for preprocessing.
|
||||
*/
|
||||
public Node createTransformTree (Properties props, HashMap<String, TransformNode> nodes)
|
||||
{
|
||||
// create the nodes and map them by name
|
||||
for (SpatialDef spatial : spatials) {
|
||||
nodes.put(spatial.name, new TransformNode(spatial));
|
||||
}
|
||||
|
||||
// resolve the parents and collect the names of the bones
|
||||
Node root = new Node("root");
|
||||
HashSet<String> bones = new HashSet<String>();
|
||||
for (TransformNode node : nodes.values()) {
|
||||
if (node.spatial.parent == null) {
|
||||
root.attachChild(node);
|
||||
} else {
|
||||
TransformNode pnode = nodes.get(node.spatial.parent);
|
||||
if (pnode != null) {
|
||||
pnode.attachChild(node);
|
||||
pnode.referenced = true;
|
||||
}
|
||||
}
|
||||
node.spatial.getBoneNames(bones);
|
||||
}
|
||||
|
||||
// mark the bones as referenced
|
||||
for (String name : bones) {
|
||||
TransformNode node = nodes.get(name);
|
||||
if (node != null) {
|
||||
node.referenced = true;
|
||||
}
|
||||
}
|
||||
|
||||
// mark the controlled nodes
|
||||
String[] controllers = StringUtil.parseStringArray(props.getProperty("controllers", ""));
|
||||
for (String controller : controllers) {
|
||||
Properties subProps = PropertiesUtil.getSubProperties(props, controller);
|
||||
TransformNode node = nodes.get(subProps.getProperty("node", controller));
|
||||
if (node != null) {
|
||||
node.referenced = node.controlled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// store the base transforms and relative transforms for merge candidates
|
||||
root.updateGeometricState(0f, true);
|
||||
for (TransformNode node : nodes.values()) {
|
||||
node.baseLocalTransform = new Matrix4f(node.localTransform);
|
||||
node.relativeTransforms = new ArrayList<Tuple<TransformNode, Matrix4f>>();
|
||||
for (TransformNode onode : nodes.values()) {
|
||||
if (node == onode || !node.canMerge(props, onode)) {
|
||||
continue;
|
||||
}
|
||||
node.relativeTransforms.add(new Tuple<TransformNode, Matrix4f>(
|
||||
onode, node.getRelativeTransform(onode)));
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges compatible meshes that retain the same relative transform throughout all animations.
|
||||
*/
|
||||
public void mergeSpatials (HashMap<String, TransformNode> nodes)
|
||||
{
|
||||
for (TransformNode node : nodes.values()) {
|
||||
if (!spatials.contains(node.spatial)) {
|
||||
continue;
|
||||
}
|
||||
for (Tuple<TransformNode, Matrix4f> tuple : node.relativeTransforms) {
|
||||
if (spatials.contains(tuple.left.spatial)) {
|
||||
node.spatial.merge(tuple.left.spatial, tuple.right);
|
||||
spatials.remove(tuple.left.spatial);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the model node defined herein.
|
||||
*
|
||||
@@ -475,11 +760,6 @@ public class ModelDef
|
||||
}
|
||||
}
|
||||
|
||||
// in non-animated models, merge meshes with the same attributes
|
||||
if (StringUtil.isBlank(props.getProperty("animations"))) {
|
||||
mergeEquivalentMeshes(model, nodes, referenced);
|
||||
}
|
||||
|
||||
// get rid of any nodes that serve no purpose
|
||||
pruneUnusedNodes(model, nodes, referenced);
|
||||
|
||||
@@ -526,63 +806,17 @@ public class ModelDef
|
||||
return referenced.contains(node) || hasValidChildren;
|
||||
}
|
||||
|
||||
/** Merges meshes with the same attributes. */
|
||||
protected void mergeEquivalentMeshes (
|
||||
Model model, HashMap<String, Spatial> nodes, final HashSet<Spatial> referenced)
|
||||
/** Determines whether a pair of matrices are "close enough" to equal. */
|
||||
public static boolean epsilonEquals (Matrix4f m1, Matrix4f m2)
|
||||
{
|
||||
final HashMap<Object, ArrayList<ModelMesh>> meshes =
|
||||
new HashMap<Object, ArrayList<ModelMesh>>();
|
||||
new SpatialVisitor<ModelMesh>(ModelMesh.class) {
|
||||
public void traverse (Spatial spatial) {
|
||||
// cut out when we encounter referenced nodes
|
||||
if (!referenced.contains(spatial)) {
|
||||
spatial.updateWorldVectors();
|
||||
super.traverse(spatial);
|
||||
for (int ii = 0; ii < 4; ii++) {
|
||||
for (int jj = 0; jj < 4; jj++) {
|
||||
if (FastMath.abs(m1.get(ii, jj) - m2.get(ii, jj)) > 0.0001f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
protected void visit (ModelMesh mesh) {
|
||||
// make sure we don't try this with skinned meshes
|
||||
if (mesh instanceof SkinMesh) {
|
||||
return;
|
||||
}
|
||||
Object key = mesh.getAttributeKey();
|
||||
ArrayList<ModelMesh> list = meshes.get(key);
|
||||
if (list == null) {
|
||||
meshes.put(key, list = new ArrayList<ModelMesh>());
|
||||
}
|
||||
list.add(mesh);
|
||||
}
|
||||
}.traverse(model);
|
||||
|
||||
for (ArrayList<ModelMesh> list : meshes.values()) {
|
||||
// the first in the list becomes the base
|
||||
ModelMesh base = list.get(0);
|
||||
flattenTransforms(base);
|
||||
model.attachChild(base);
|
||||
|
||||
// the rest are merged with it
|
||||
for (int ii = 1, nn = list.size(); ii < nn; ii++) {
|
||||
ModelMesh mesh = list.get(ii);
|
||||
flattenTransforms(mesh);
|
||||
mesh.getParent().detachChild(mesh);
|
||||
base.merge(mesh);
|
||||
}
|
||||
|
||||
// update the model bound and recenter
|
||||
base.updateModelBound();
|
||||
base.centerVertices();
|
||||
}
|
||||
}
|
||||
|
||||
/** Transforms the mesh's vertices and normals into model coordinates. */
|
||||
protected static void flattenTransforms (ModelMesh mesh)
|
||||
{
|
||||
GeomBatch batch = mesh.getBatch(0);
|
||||
batch.getWorldCoords(batch.getVertexBuffer());
|
||||
batch.getWorldNormals(batch.getNormalBuffer());
|
||||
mesh.getLocalTranslation().zero();
|
||||
mesh.getLocalRotation().loadIdentity();
|
||||
mesh.getLocalScale().set(Vector3f.UNIT_XYZ);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Converts a boxed Integer list to an unboxed int array. */
|
||||
|
||||
@@ -30,6 +30,8 @@ import java.nio.IntBuffer;
|
||||
|
||||
import org.lwjgl.opengl.ARBShaderObjects;
|
||||
|
||||
import com.jme.math.Matrix4f;
|
||||
import com.jme.math.Quaternion;
|
||||
import com.jme.math.Vector3f;
|
||||
import com.jme.scene.Controller;
|
||||
import com.jme.scene.state.GLSLShaderObjectsState;
|
||||
@@ -111,6 +113,31 @@ public class JmeUtil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a matrix to the transform defined by the given translation,
|
||||
* rotation, and scale values.
|
||||
*/
|
||||
public static Matrix4f setTransform (
|
||||
Vector3f translation, Quaternion rotation, Vector3f scale, Matrix4f result)
|
||||
{
|
||||
result.setRotationQuaternion(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse a string containing an axis: either "x", "y", or "z", or three
|
||||
* comma-delimited values representing an axis vector. The value returned may be one of
|
||||
|
||||
Reference in New Issue
Block a user