LaserTest

Hello JME community. I am new here as I have only been learning JME for a short while.



I am trying to create a simple application that fires a laser. I read in some of the past forum posts and learned that TrailMesh was a good way to go. From there, I borrowed form the HelloIntersection class and came up with the following code:


import jmetest.renderer.TestBoxColor;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingSphere;
import com.jme.image.Texture;
import com.jme.input.KeyInput;
import com.jme.input.action.InputActionEvent;
import com.jme.input.action.KeyInputAction;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Controller;
import com.jme.scene.TriMesh;
import com.jme.scene.Spatial.CullHint;
import com.jme.scene.Spatial.LightCombineMode;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.jmex.effects.TrailMesh;

public class TestLasers extends SimpleGame
{
   private TrailMesh theTrailMesh;
   private Vector3f tangent = new Vector3f();
   
    protected void simpleInitGame()
    {
        display.setTitle("LaserTest");

        cam.getLocation().set(new Vector3f(140, 140, 140));
        cam.lookAt(new Vector3f(), Vector3f.UNIT_Y);

        // Create the trail
        theTrailMesh = new TrailMesh("TrailMesh", 100);
        theTrailMesh.setUpdateSpeed(60.0f);
        theTrailMesh.setFacingMode(TrailMesh.FacingMode.Billboard);
        theTrailMesh.setUpdateMode(TrailMesh.UpdateMode.Step);

        // Try out some additive blending etc
        theTrailMesh.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
        theTrailMesh.setCullHint(CullHint.Never);

        TextureState ts = display.getRenderer().createTextureState();
        ts.setEnabled(true);
       
        Texture t1 = TextureManager.loadTexture(
                TestBoxColor.class.getClassLoader().getResource(
                        "jmetest/data/texture/trail.png"),
                Texture.MinificationFilter.Trilinear,
                Texture.MagnificationFilter.Bilinear);
       
        ts.setTexture(t1);
        theTrailMesh.setRenderState(ts);

        BlendState bs = display.getRenderer().createBlendState();
        bs.setBlendEnabled(true);
        bs.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
        bs.setDestinationFunction(BlendState.DestinationFunction.One);
        bs.setTestEnabled(true);
        theTrailMesh.setRenderState(bs);

        ZBufferState zs = display.getRenderer().createZBufferState();
        zs.setWritable(false);
        theTrailMesh.setRenderState(zs);

        CullState cs = display.getRenderer().createCullState();
        cs.setCullFace(CullState.Face.None);
        cs.setEnabled(true);
        theTrailMesh.setRenderState(cs);

        rootNode.attachChild(theTrailMesh);
       
        input.addAction(new FireLaser(), "firelaser", KeyInput.KEY_F, false);

        // No lighting for clarity
        rootNode.setLightCombineMode(LightCombineMode.Off);
        rootNode.setRenderQueueMode(com.jme.renderer.Renderer.QUEUE_OPAQUE);
    }

    public static void main(String[] args) {
       TestLasers app = new TestLasers();
        app.setConfigShowMode(ConfigShowMode.AlwaysShow);
        app.start();
    }
   
    class FireLaser extends KeyInputAction {
        int numBullets;

        public void performAction(InputActionEvent evt) {
           
           TrailMesh newLaser = new TrailMesh("newLaser" + numBullets++, 5);
            newLaser.setUpdateSpeed(60.0f);
            newLaser.setFacingMode(TrailMesh.FacingMode.Billboard);
            newLaser.setUpdateMode(TrailMesh.UpdateMode.Step);

            // Try out some additive blending etc
            newLaser.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
            newLaser.setCullHint(CullHint.Never);

            /**
             * The question is, why isn't the commented code below needed to
             * keep the lasers colored? (i.e. Not white)
             */
//            TextureState ts = display.getRenderer().createTextureState();
//            ts.setEnabled(true);
//            Texture t1 = TextureManager.loadTexture(
//                    TestBoxColor.class.getClassLoader().getResource(
//                            "jmetest/data/texture/trail.png"),
//                    Texture.MinificationFilter.Trilinear,
//                    Texture.MagnificationFilter.Bilinear);
//            ts.setTexture(t1);
//            trailMesh.setRenderState(ts);
//
//            BlendState bs = display.getRenderer().createBlendState();
//            bs.setBlendEnabled(true);
//            bs.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
//            bs.setDestinationFunction(BlendState.DestinationFunction.One);
//            bs.setTestEnabled(true);
//            trailMesh.setRenderState(bs);
//
//            ZBufferState zs = display.getRenderer().createZBufferState();
//            zs.setWritable(false);
//            trailMesh.setRenderState(zs);
//
//            CullState cs = display.getRenderer().createCullState();
//            cs.setCullFace(CullState.Face.None);
//            cs.setEnabled(true);
//            trailMesh.setRenderState(cs);

            rootNode.attachChild(newLaser);

            Sphere bullet = new Sphere("bullet" + numBullets++, 8, 8, 0.001f);
            bullet.setModelBound(new BoundingSphere());
            bullet.updateModelBound();
            /** Move bullet to the camera location */
            bullet.setLocalTranslation(new Vector3f(15,1,1));

            /**
             * Update the new world location for the bullet before I add a
             * controller
             */
            bullet.updateGeometricState(0, true);
            /**
             * Add a movement controller to the bullet going in the camera's
             * direction
             */
            bullet.addController(new LaserMover(bullet, newLaser, new Vector3f(new Vector3f(1,1,1))));
            rootNode.attachChild(bullet);
            bullet.updateRenderState();
        }
    }

    class LaserMover extends Controller
    {
        private static final long serialVersionUID = 1L;
        /** Bullet that's moving */
        TriMesh bullet;
       
        TrailMesh laser;

        /** Direction of bullet **/
        Vector3f direction;

        /** speed of bullet */
        float speed = 1000;

        /** Seconds it will last before going away */
        float lifeTime = 10;

        LaserMover(TriMesh bullet, TrailMesh trailMesh, Vector3f direction) {
            this.bullet = bullet;
            this.laser = trailMesh;
            this.direction = direction;
            this.direction.normalizeLocal();
        }

        public void update(float time)
        {
            lifeTime -= time;
       
            /** If life is gone, remove it **/
            if (lifeTime < 0)
            {
               rootNode.detachChild(laser);
                rootNode.detachChild(bullet);
                bullet.removeController(this);
                laser.removeController(this);
                return;
            }
           
            /** Move bullet */
            Vector3f bulletPos = bullet.getLocalTranslation();
            bulletPos.addLocal(direction.mult(time * speed));
            bullet.setLocalTranslation(bulletPos);

            laser.setLocalTranslation(direction.mult(time * speed));
            laser.updateGeometricState(0.0f, true);
    
            Vector3f tg = new Vector3f();
           
            // Create a spin for tangent mode
            tg.set(direction.mult(time * speed));
            tg.normalizeLocal();

            // Setup width
            float width = 1.0f;
           
            laser.setTrailFront(bullet.getWorldTranslation(), tangent,
                    width, Timer.getTimer().getTimePerFrame());

            laser.update(cam.getLocation());
        }
    }
}



NOTE: when you run this code, press 'f' to fire the laser.

Notice the block of commented code in FireLaser.performAction(). Originally, I had this code running and I didn't have any similar code in simpleInitGame(). Unfortunately, when this was so, nothing worked. When I comment this code out and leave the code in simpleInitGame(), I get a yellow laser.

I don't understand why this is the case. Can someone explain why running this code only once <in simpleInitGame()> forces all lasers to show up properly? At first I thought I had to apply this TextureState to all laser objects but from my code I don't see how I'm applying it at all right now...

Any help is greatly appreciated.

OldMonk

Your code works, you just need to update renderstates of the newly created TrailMesh after attaching the mesh to the root node.


            rootNode.attachChild(newLaser);
            newLaser.updateRenderState();

Thanks, got it now.



By the way, why did having the code in simpleInitGame force the laser yellow when I didn't have the


newLaser.updateRenderState();



I don't see how the renderstate for newLaser was getting set...

Thats because BaseSimpleGame calls a rootNode.updateRenderState() at the end of simpleInitGame() for you

forgetting a updateRenderState() a very common error, i wonder if we just can set a flag after setting a Renderstate, which automaticly forces a updateRenderState() at the next update / render cycle.


That would probably be a good idea–I think it was originally avoided because you may want to set multiple render states at once and, as updating the render state takes a long time, it would be really annoying if it was getting updated before you had made all of the changes you wanted to. I'm pretty sure that Ardor3D, while coming from a core similar to that of jME, has removed the need for render state updates, so perhaps that will be a feature of jME 3.0?

Core-Dump said:

forgetting a updateRenderState() a very common error, i wonder if we just can set a flag after setting a Renderstate, which automaticly forces a updateRenderState() at the next update / render cycle.


Yes, I've forgot it many times too. It's easy to spot but annoying. So an automatic approach would be good.

Yes this issue is removed in Ardor3D, but i didn't check to see how its done yet.


This issue is also removed in "jME3". You simply set a flag "need refresh" on the node and any effected nodes, and on the next update you just go through the scene graph and make the needed updates depending on the flags.

This can be a bit tricky at times, e.g if you set "need world bound refresh" on a child node, then all its parent nodes will also need to have that flag set because the parent's bounding volume is generated from it's children's bounding volume. This is almost the exact opposite for world transform updates, because a node's world transform depends on it's parent's world transform so if you set "need world transform refresh" all the node's children must have it set as well. For render state updates you may need specific handling per state, e.g for LightState all the lights in the parent's path must be contained in the child node's LightState. Though I can't help much with this, because "jME3" doesn't have render states.