Various performance tweaks: cull nodes with no mesh children, allow
locking the transforms and bounds of models whose positions will not change, skip/postpone updates for models that aren't in view. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4078 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -38,13 +38,19 @@ import java.nio.channels.FileChannel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
|
||||
import com.jme.bounding.BoundingVolume;
|
||||
import com.jme.math.FastMath;
|
||||
import com.jme.math.Matrix4f;
|
||||
import com.jme.math.Quaternion;
|
||||
import com.jme.math.Vector3f;
|
||||
import com.jme.renderer.Camera;
|
||||
import com.jme.renderer.CloneCreator;
|
||||
import com.jme.renderer.Renderer;
|
||||
import com.jme.scene.Controller;
|
||||
import com.jme.scene.Node;
|
||||
import com.jme.scene.Spatial;
|
||||
@@ -277,6 +283,7 @@ public class Model extends ModelNode
|
||||
public void initPrototype ()
|
||||
{
|
||||
setReferenceTransforms();
|
||||
cullInvisibleNodes();
|
||||
initInstance();
|
||||
}
|
||||
|
||||
@@ -318,13 +325,14 @@ public class Model extends ModelNode
|
||||
/**
|
||||
* Starts the named animation.
|
||||
*
|
||||
* @return a reference to the started animation
|
||||
* @return the duration of the started animation (for looping animations,
|
||||
* the duration of one cycle), or -1 if the animation was not found
|
||||
*/
|
||||
public Animation startAnimation (String name)
|
||||
public float startAnimation (String name)
|
||||
{
|
||||
Animation anim = getAnimation(name);
|
||||
if (anim == null) {
|
||||
return null;
|
||||
return -1f;
|
||||
}
|
||||
_anim = anim;
|
||||
_animName = name;
|
||||
@@ -333,7 +341,7 @@ public class Model extends ModelNode
|
||||
_fdir = +1;
|
||||
_elapsed = 0f;
|
||||
_animObservers.apply(new AnimStartedOp(_animName));
|
||||
return anim;
|
||||
return anim.getDuration() / _animSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,11 +493,29 @@ public class Model extends ModelNode
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks the transforms and bounds of this model in the expectation that it
|
||||
* will never be moved from its current position.
|
||||
*/
|
||||
public void lockInstance ()
|
||||
{
|
||||
// collect the controller targets and lock recursively
|
||||
HashSet<Spatial> targets = new HashSet<Spatial>();
|
||||
for (Object ctrl : getControllers()) {
|
||||
if (ctrl instanceof ModelController) {
|
||||
targets.add(((ModelController)ctrl).getTarget());
|
||||
}
|
||||
}
|
||||
lockInstance(targets);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public Spatial putClone (Spatial store, CloneCreator properties)
|
||||
{
|
||||
Model mstore;
|
||||
if (store == null) {
|
||||
Model mstore = (Model)properties.originalToCopy.get(this);
|
||||
if (mstore != null) {
|
||||
return mstore;
|
||||
} else if (store == null) {
|
||||
mstore = new Model(getName(), _props);
|
||||
} else {
|
||||
mstore = (Model)store;
|
||||
@@ -499,21 +525,64 @@ public class Model extends ModelNode
|
||||
if (_anims != null) {
|
||||
mstore._anims = new HashMap<String, Animation>();
|
||||
}
|
||||
mstore._pnodes = properties.originalToCopy;
|
||||
mstore._pnodes = (HashMap)properties.originalToCopy.clone();
|
||||
return mstore;
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void updateWorldData (float time)
|
||||
public void updateGeometricState (float time, boolean initiator)
|
||||
{
|
||||
// if we were not visible the last time we were rendered, don't do a
|
||||
// full update; just update the world bound and wait until we come
|
||||
// into view
|
||||
boolean wasOutside = _outside;
|
||||
_outside = isOutsideFrustum() && worldBound != null;
|
||||
|
||||
// slow evvvverything down by the animation speed
|
||||
time *= _animSpeed;
|
||||
if (_anim != null) {
|
||||
updateAnimation(time);
|
||||
}
|
||||
|
||||
// update children
|
||||
super.updateWorldData(time);
|
||||
// update controllers and children with accumulated time
|
||||
_accum += time;
|
||||
if (_outside) {
|
||||
if (!wasOutside) {
|
||||
updateModelBound();
|
||||
}
|
||||
updateWorldVectors();
|
||||
worldBound = _modelBound.transform(getWorldRotation(),
|
||||
getWorldTranslation(), getWorldScale(), worldBound);
|
||||
|
||||
} else {
|
||||
super.updateGeometricState(_accum, initiator);
|
||||
_accum = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void onDraw (Renderer r)
|
||||
{
|
||||
// if we switch from invisible to visible, we have to do a last-minute
|
||||
// full update (which only works if our meshes are enqueued)
|
||||
super.onDraw(r);
|
||||
if (_outside && !isOutsideFrustum()) {
|
||||
updateWorldData(0f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this node was determined to be entirely outside the
|
||||
* view frustum.
|
||||
*/
|
||||
protected boolean isOutsideFrustum ()
|
||||
{
|
||||
for (Node node = this; node != null; node = node.getParent()) {
|
||||
if (node.getLastFrustumIntersection() == Camera.OUTSIDE_FRUSTUM) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -540,12 +609,14 @@ public class Model extends ModelNode
|
||||
_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]);
|
||||
// update the target transforms if not outside the view frustum
|
||||
if (!_outside) {
|
||||
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
|
||||
@@ -579,6 +650,42 @@ public class Model extends ModelNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the model bound based on the current world bound.
|
||||
*/
|
||||
protected void updateModelBound ()
|
||||
{
|
||||
if (worldBound == null) {
|
||||
return;
|
||||
}
|
||||
setTransform(getWorldTranslation(), getWorldRotation(),
|
||||
getWorldScale(), _xform);
|
||||
_xform.invertLocal();
|
||||
_xform.toTranslationVector(_trans);
|
||||
extractScale(_xform, _scale);
|
||||
_xform.toRotationQuat(_rot);
|
||||
_modelBound = worldBound.transform(_rot, _trans, _scale, _modelBound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the scale factor from the given transform and normalizes it.
|
||||
*/
|
||||
protected static void extractScale (Matrix4f m, Vector3f scale)
|
||||
{
|
||||
scale.x = FastMath.sqrt(m.m00*m.m00 + m.m01*m.m01 + m.m02*m.m02);
|
||||
m.m00 /= scale.x;
|
||||
m.m01 /= scale.x;
|
||||
m.m02 /= scale.x;
|
||||
scale.y = FastMath.sqrt(m.m10*m.m10 + m.m11*m.m11 + m.m12*m.m12);
|
||||
m.m10 /= scale.y;
|
||||
m.m11 /= scale.y;
|
||||
m.m12 /= scale.y;
|
||||
scale.z = FastMath.sqrt(m.m20*m.m20 + m.m21*m.m21 + m.m22*m.m22);
|
||||
m.m20 /= scale.z;
|
||||
m.m21 /= scale.z;
|
||||
m.m22 /= scale.z;
|
||||
}
|
||||
|
||||
/** A reference to the prototype, or <code>null</code> if this is a
|
||||
* prototype. */
|
||||
protected Model _prototype;
|
||||
@@ -615,9 +722,23 @@ public class Model extends ModelNode
|
||||
/** The frame portion elapsed since the start of the current frame. */
|
||||
protected float _elapsed;
|
||||
|
||||
/** The amount of update time accumulated while outside of view frustum. */
|
||||
protected float _accum;
|
||||
|
||||
/** The child node that contains the model's emissions in world space. */
|
||||
protected Node _emissionNode;
|
||||
|
||||
/** The model space bounding volume. */
|
||||
protected BoundingVolume _modelBound;
|
||||
|
||||
/** Whether or not we were outside the frustum at the last update. */
|
||||
protected boolean _outside;
|
||||
|
||||
/** Temporary transform variables. */
|
||||
protected Matrix4f _xform = new Matrix4f();
|
||||
protected Vector3f _trans = new Vector3f(), _scale = new Vector3f();
|
||||
protected Quaternion _rot = new Quaternion();
|
||||
|
||||
/** Animation completion listeners. */
|
||||
protected ObserverList<AnimationObserver> _animObservers =
|
||||
new ObserverList<AnimationObserver>(ObserverList.FAST_UNSAFE_NOTIFY);
|
||||
|
||||
@@ -59,6 +59,14 @@ public abstract class ModelController extends Controller
|
||||
Collections.addAll(_animations, anims);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the controller's target.
|
||||
*/
|
||||
public Spatial getTarget ()
|
||||
{
|
||||
return _target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves any textures required by the controller.
|
||||
*/
|
||||
|
||||
@@ -186,8 +186,10 @@ public class ModelMesh extends TriMesh
|
||||
@Override // documentation inherited
|
||||
public Spatial putClone (Spatial store, CloneCreator properties)
|
||||
{
|
||||
ModelMesh mstore;
|
||||
if (store == null) {
|
||||
ModelMesh mstore = (ModelMesh)properties.originalToCopy.get(this);
|
||||
if (mstore != null) {
|
||||
return mstore;
|
||||
} else if (store == null) {
|
||||
mstore = new ModelMesh(getName());
|
||||
} else {
|
||||
mstore = (ModelMesh)store;
|
||||
@@ -213,6 +215,14 @@ public class ModelMesh extends TriMesh
|
||||
return mstore;
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void updateWorldVectors ()
|
||||
{
|
||||
if (!_transformLocked) {
|
||||
super.updateWorldVectors();
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface Externalizable
|
||||
public void writeExternal (ObjectOutput out)
|
||||
throws IOException
|
||||
@@ -401,6 +411,16 @@ public class ModelMesh extends TriMesh
|
||||
reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks the transform and bounds of this mesh on the assumption that its
|
||||
* position will not change.
|
||||
*/
|
||||
protected void lockInstance ()
|
||||
{
|
||||
lockBounds();
|
||||
_transformLocked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imposes the specified order on the given buffer of 32 bit values.
|
||||
*/
|
||||
@@ -455,6 +475,11 @@ public class ModelMesh extends TriMesh
|
||||
/** For prototype meshes, the resolved texture states. */
|
||||
protected TextureState[] _tstates;
|
||||
|
||||
/** Whether or not the transform has been locked. This operates in a
|
||||
* slightly different way than JME's locking, in that it allows applying
|
||||
* transformations to display lists. */
|
||||
protected boolean _transformLocked;
|
||||
|
||||
/** The shared state for back face culling. */
|
||||
protected static CullState _backCull;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
import com.jme.math.Matrix4f;
|
||||
import com.jme.math.Quaternion;
|
||||
@@ -92,6 +93,16 @@ public class ModelNode extends Node
|
||||
return _modelTransform;
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void updateWorldData (float time)
|
||||
{
|
||||
// we use locked bounds as an indication that we can skip the update
|
||||
// altogether
|
||||
if ((lockedMode & LOCKED_BOUNDS) == 0) {
|
||||
super.updateWorldData(time);
|
||||
}
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void updateWorldVectors ()
|
||||
{
|
||||
@@ -110,13 +121,16 @@ public class ModelNode extends Node
|
||||
@Override // documentation inherited
|
||||
public Spatial putClone (Spatial store, CloneCreator properties)
|
||||
{
|
||||
ModelNode mstore;
|
||||
if (store == null) {
|
||||
ModelNode mstore = (ModelNode)properties.originalToCopy.get(this);
|
||||
if (mstore != null) {
|
||||
return mstore;
|
||||
} else if (store == null) {
|
||||
mstore = new ModelNode(getName());
|
||||
} else {
|
||||
mstore = (ModelNode)store;
|
||||
}
|
||||
super.putClone(mstore, properties);
|
||||
mstore.cullMode = cullMode;
|
||||
return mstore;
|
||||
}
|
||||
|
||||
@@ -210,6 +224,62 @@ public class ModelNode extends Node
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cull state of any nodes that do not contain geometric
|
||||
* descendants to {@link CULL_ALWAYS} so that they don't waste
|
||||
* rendering time.
|
||||
*
|
||||
* @return true if this node should be drawn, false if it contains
|
||||
* no mesh descendants
|
||||
*/
|
||||
protected boolean cullInvisibleNodes ()
|
||||
{
|
||||
boolean hasVisibleDescendants = false;
|
||||
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
|
||||
Spatial child = getChild(ii);
|
||||
if (!(child instanceof ModelNode) ||
|
||||
((ModelNode)child).cullInvisibleNodes()) {
|
||||
hasVisibleDescendants = true;
|
||||
}
|
||||
}
|
||||
setCullMode(hasVisibleDescendants ? CULL_INHERIT : CULL_ALWAYS);
|
||||
return hasVisibleDescendants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks the transforms and bounds of this instance with the assumption
|
||||
* that the position will never change.
|
||||
*
|
||||
* @param targets the targets of the model's controllers, which determine
|
||||
* the subset of nodes that can be locked
|
||||
* @return true if this node is a target or contains any targets, otherwise
|
||||
* false
|
||||
*/
|
||||
protected boolean lockInstance (HashSet<Spatial> targets)
|
||||
{
|
||||
updateWorldVectors();
|
||||
lockedMode |= LOCKED_TRANSFORMS;
|
||||
|
||||
boolean containsTargets = false;
|
||||
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
|
||||
Spatial child = getChild(ii);
|
||||
if (targets.contains(child) || (child instanceof ModelNode &&
|
||||
((ModelNode)child).lockInstance(targets))) {
|
||||
containsTargets = true;
|
||||
|
||||
} else if (child instanceof ModelMesh) {
|
||||
((ModelMesh)child).lockInstance();
|
||||
}
|
||||
}
|
||||
if (containsTargets) {
|
||||
return true;
|
||||
} else {
|
||||
updateWorldBound();
|
||||
lockedMode |= LOCKED_BOUNDS;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a matrix to the transform defined by the given translation,
|
||||
* rotation, and scale values.
|
||||
|
||||
@@ -27,8 +27,6 @@ import java.io.IOException;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.jme.math.Matrix4f;
|
||||
import com.jme.renderer.Renderer;
|
||||
import com.jme.scene.Spatial;
|
||||
|
||||
@@ -143,8 +143,10 @@ public class SkinMesh extends ModelMesh
|
||||
@Override // documentation inherited
|
||||
public Spatial putClone (Spatial store, CloneCreator properties)
|
||||
{
|
||||
SkinMesh mstore;
|
||||
if (store == null) {
|
||||
SkinMesh mstore = (SkinMesh)properties.originalToCopy.get(this);
|
||||
if (mstore != null) {
|
||||
return mstore;
|
||||
} else if (store == null) {
|
||||
mstore = new SkinMesh(getName());
|
||||
} else {
|
||||
mstore = (SkinMesh)store;
|
||||
|
||||
@@ -64,7 +64,7 @@ public class AnimationDef
|
||||
if (target != null) {
|
||||
targets.add(target);
|
||||
} else {
|
||||
Log.warning("Missing animation target [name=" + name +
|
||||
Log.debug("Missing animation target [name=" + name +
|
||||
"].");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +436,7 @@ public class ModelDef
|
||||
}
|
||||
|
||||
// get rid of any nodes that serve no purpose
|
||||
pruneUnusedNodes(model, referenced);
|
||||
pruneUnusedNodes(model, nodes, referenced);
|
||||
|
||||
return model;
|
||||
}
|
||||
@@ -461,16 +461,18 @@ public class ModelDef
|
||||
|
||||
/** Recursively removes any unused nodes. */
|
||||
protected boolean pruneUnusedNodes (
|
||||
ModelNode node, HashSet<Spatial> referenced)
|
||||
ModelNode node, HashMap<String, Spatial> nodes,
|
||||
HashSet<Spatial> referenced)
|
||||
{
|
||||
boolean hasValidChildren = false;
|
||||
for (Iterator it = node.getChildren().iterator(); it.hasNext(); ) {
|
||||
Spatial child = (Spatial)it.next();
|
||||
if (!(child instanceof ModelNode) ||
|
||||
pruneUnusedNodes((ModelNode)child, referenced)) {
|
||||
pruneUnusedNodes((ModelNode)child, nodes, referenced)) {
|
||||
hasValidChildren = true;
|
||||
} else {
|
||||
it.remove();
|
||||
nodes.remove(child.getName());
|
||||
}
|
||||
}
|
||||
return referenced.contains(node) || hasValidChildren;
|
||||
|
||||
@@ -62,6 +62,7 @@ import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
|
||||
import com.jme.bounding.BoundingBox;
|
||||
import com.jme.image.Texture;
|
||||
import com.jme.light.DirectionalLight;
|
||||
import com.jme.math.FastMath;
|
||||
@@ -294,6 +295,8 @@ public class ModelViewer extends JmeCanvasApp
|
||||
Line grid = new Line("grid", points, null, null, null);
|
||||
grid.getDefaultColor().set(0.25f, 0.25f, 0.25f, 1f);
|
||||
grid.setLightCombineMode(LightState.OFF);
|
||||
grid.setModelBound(new BoundingBox());
|
||||
grid.updateModelBound();
|
||||
_ctx.getGeometry().attachChild(grid);
|
||||
grid.updateRenderState();
|
||||
|
||||
@@ -453,6 +456,7 @@ public class ModelViewer extends JmeCanvasApp
|
||||
}
|
||||
_ctx.getGeometry().attachChild(_model = model);
|
||||
_model.lockStaticMeshes(_ctx.getRenderer(), true, true);
|
||||
_model.getLocalTranslation().set(-20f, 0f, 0f);
|
||||
|
||||
// resolve the textures from the file's directory
|
||||
final File dir = file.getParentFile();
|
||||
|
||||
Reference in New Issue
Block a user