I threw this little missile trail class together and it works just fine, but I need some advice on performance issues as it seems to drop frame rates if I have too many which seems odd because it’s fairly simple only consisting of 2 quads per segment and maybe 20-30 segments per trail depending on the speed of the object. I’ve tried several techniques such as SharedNodes and such but they make no difference. I’d be thrilled if anyone has any ideas.
Required texture (you’ll need to rename it):
package imp.ingame.missile;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
public class RibbonTrail extends Node{
protected Vector3f lastPos;
protected Node rootNode;
protected long segmentDuration;
protected float segmentLength;
protected MaterialState materialState;
protected ZBufferState zBufferState;
protected AlphaState alphaState;
protected TextureState textureState;
protected Quaternion quadRotation;
protected Quaternion segNodeRotation;
/**
*
* <code>RibbonTrail</code> Creates a new trail effect behind any node
* to which it is attached.
*
* @param rootNode
* the node to which the trail will attach individual segments,
* such as a root node of the scene.
* @param segmentDuration
* the duration from the instant that a segment is created
* to the time it is completely faded from the scene.
* @param segmentLength
* the length a segment will stretch before creating a new one.
*
* @author Scott Oehlerking
*/
public RibbonTrail(Node rootNode, long segmentDuration, float segmentLength){
super();
this.segmentDuration = segmentDuration;
this.segmentLength = segmentLength;
this.lastPos = new Vector3f(this.getWorldTranslation());
this.rootNode = rootNode;
zBufferState = DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
alphaState = DisplaySystem.getDisplaySystem().getRenderer().createAlphaState();
materialState = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
materialState.setEmissive(new ColorRGBA(150f, 150f, 150f, 0.3f));
textureState = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
textureState.setEnabled(true);
Texture texture = TextureManager.loadTexture(RibbonSegment.class
.getClassLoader().getResource(
"resources/texture/effects/ribbon.png"),
Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR);
textureState.setTexture(texture);
zBufferState.setWritable(false);
zBufferState.setEnabled(true);
zBufferState.setFunction(ZBufferState.CF_LEQUAL);
alphaState.setBlendEnabled(true);
alphaState.setSrcFunction(AlphaState.SB_SRC_ALPHA);
alphaState.setDstFunction(AlphaState.DB_ONE);
alphaState.setTestEnabled(true);
alphaState.setTestFunction(AlphaState.TF_GREATER);
quadRotation = new Quaternion();
quadRotation.fromAngleAxis(1.5707f, Vector3f.UNIT_X);
segNodeRotation = new Quaternion();
segNodeRotation.fromAngleAxis(1.5707f, Vector3f.UNIT_Y);
}
public void updateWorldData(float time) {
super.updateWorldData(time);
if (getWorldTranslation().distance(lastPos) > segmentLength){
//Add a new segment
rootNode.attachChild(new RibbonSegment(getWorldTranslation(), segmentDuration, segmentLength));
lastPos.set(getWorldTranslation());
}
}
private class RibbonSegment extends Node {
protected Vector3f startPos;
protected Quad quad1;
protected Quad quad2;
protected Vector3f parentPos;
protected float maxLength;
protected float maxLife;
protected float life;
protected Vector3f midpoint;
protected ColorRGBA color;
protected boolean maxed = false;
protected Node segnode;
protected RibbonSegment(Vector3f parentPos, long life, float maxLength){
super("RibNode");
this.life = life;
this.maxLife = life;
this.parentPos = parentPos;
this.startPos = new Vector3f(parentPos);
this.maxLength = maxLength;
this.midpoint = new Vector3f();
setupGeometry();
setLocalTranslation(startPos);
}
protected void setupGeometry(){
segnode = new Node("subnode");
quad1 = new Quad("RibSeg1", 0, 0);
quad2 = new Quad("RibSeg2", 0, 0);
color = new ColorRGBA(1, 1, 1, 1);
quad1.setRenderState(zBufferState);
quad1.setRenderState(alphaState);
quad1.setRenderState(materialState);
quad1.setRenderState(textureState);
quad1.setDefaultColor(color);
segnode.attachChild(quad1);
quad1.updateRenderState();
quad2.setRenderState(zBufferState);
quad2.setRenderState(alphaState);
quad2.setRenderState(materialState);
quad2.setRenderState(textureState);
quad2.setDefaultColor(color);
segnode.attachChild(quad2);
quad2.updateRenderState();
segnode.setLocalRotation(segNodeRotation);
quad2.setLocalRotation(quadRotation);
segnode.setModelBound(new BoundingBox());
attachChild(segnode);
}
public void updateWorldData(float time) {
super.updateWorldData( time );
if (!maxed){
midpoint.set((startPos.x + parentPos.x)/2, (startPos.y + parentPos.y)/2, (startPos.z + parentPos.z)/2);
setLocalTranslation(midpoint);
lookAt(parentPos, Vector3f.UNIT_Y);
quad1.resize(startPos.distance(parentPos), 6);
quad2.resize(startPos.distance(parentPos), 6);
if (startPos.distance(parentPos) > maxLength){
maxed = true;
lockTransforms();
lockBounds();
}
}
if (life <= 0){
getParent().detachChild(this);
} else {
life -= time;
color.a = life/maxLife;
}
}
}
}