Textured line

It is not really that I have no idea how to create a textured line (say for a laser beam), but I am wondering if there are any good convenience methods already made for this… Basically it is just a quad… right? With texture applied. Only long and narrow. But then I come to the problem of creating it with exactly the correct length. And placing it so that it is between points A and B.



Currently I would do this by calculating the distance between A and B and making a quad with that height and some default width (texture width). Placing it would be quite a bit harder though - Sure I can find the spot halfway between points A and B for placing the quad there - but then I'd also have to rotate it so that it would start at one point and end at the second one. Anyways it is all doable but seems so clumsy.



Is there a better and easier way of achieving what I am describing? And how does the use of textured quads lend to reusing these lines later again? The new lines would have to be of a different length, after all?



All advise would be welcome

You could use line particles for a spectacular look.



Worry about performance when it is an issue

Heh…hey Mind, didn't you comment on that very question I brought up?



http://www.jmonkeyengine.com/jmeforum/index.php?topic=8080.0



I think we should ask for a "laser" feature in jME 2.0.



:slight_smile:



I've moved on to some UI stuff right now, so I'd be interested in what you come up with here as I'll eventually need to attack this again.

Yeah… I did :)  I guess i still COULD use a line and bloom but I would really like to animate the laser beam… could make it look like an electrical charge or whatever. The biggest problem with this is the dynamic quad creation. Well actually I'd want * shaped line (looking from one end) , but I could tackle this once I get a flat one easily enough I just find it hard to believe that everyone who has implemented something like this uses the method I described above. I am certain there must be a better way. O well perhaps I will think of it once I start actually writing the code :slight_smile:

You could get around the positioning and rotation issues though by just moving the centre of rotation to the end of the laser.



  • Make a narrow quad of length 1 - easier to scale

  • Centre of rotation is at the end rather than the middle - easier to rotate

  • Length along the z axis instead of y - easier to point

  • Now you ca just attach it to a node and it will point out from there (or even just use lookAt on the quad)

  • To scale it you can just use setLocalScale() to scale it to the distance from it's target - no need to reposition it as scaling has no effect on the starting point (it's at 0).



Example
LaserLine.java

import com.jme.scene.batch.TriangleBatch;
import com.jme.scene.shape.Quad;

public class LaserLine extends Quad {
   private static final long serialVersionUID = 1L;

   public LaserLine(String name, float width, float height) {
      super(name,width,height);
        TriangleBatch batch = getBatch(0);
      batch.getVertexBuffer().clear();
      batch.getVertexBuffer().put(-width / 2f).put(0).put(height);
      batch.getVertexBuffer().put(-width / 2f).put(0).put(0);
      batch.getVertexBuffer().put(width / 2f).put(0).put(0);
      batch.getVertexBuffer().put(width / 2f).put(0).put(height);
   }
}



Laser.java


import com.jme.bounding.BoundingBox;
import com.jme.math.Vector3f;
import com.jme.scene.Node;

public class Laser {
   private LaserLine laserQuad = null;
   private Node source = null;
   private Node target = null;
   private boolean isOn = false;
   
   public Laser(Node s, Node t) {
      source = s;
      target = t;
      
      laserQuad = new LaserLine("Line", 2.0f, 1.0f);
      laserQuad.setModelBound(new BoundingBox());
      laserQuad.updateModelBound();
      
      isOn = true;
      source.attachChild(laserQuad);
   }
   
   public void updateLaser() {
      laserQuad.setLocalScale( new Vector3f(1.0f, 1.0f,
            source.getLocalTranslation().distance(target.getLocalTranslation())) );
   }
   
   public void setOn() {
      isOn = true;
      source.attachChild(laserQuad);
   }
   
   public void setOff() {
      isOn = false;
      source.detachChild(laserQuad);
   }
   
   public boolean isOn() {
      return isOn;
   }
}



LaserTest.java


import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;

public class LaserTest extends SimpleGame {
   final Vector3f SPEED = new Vector3f(20f, 33f, 25f);
   
   Vector3f vel = new Vector3f(SPEED.x, SPEED.y, SPEED.z);
   Sphere s = null;
   Box b = null;
   Node laserGun,laserTarget = null;
   Laser l;

   static float ROF = 2.0f;
   static float PULSELENGTH = 1.0f;
   float cooldown = ROF;
   
   public static void main(String[] args) {
      LaserTest app = new LaserTest();
      app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
      app.start();
   }
   @Override
   protected void simpleInitGame() {
      
      s = new Sphere("Target", 10, 10, 5);
      s.setModelBound(new BoundingBox());
      s.updateModelBound();
      
      b = new Box("Source", new Vector3f(-2.0f, -2.0f, -5.0f), new Vector3f(2.0f, 2.0f, 5.0f));
      b.setModelBound(new BoundingBox());
      b.updateModelBound();
      
      laserGun = new Node();
      laserGun.setLocalTranslation(new Vector3f(0, 0, -40));
      laserTarget = new Node();
      laserTarget.setLocalTranslation(new Vector3f(30.0f, 10.0f, -40.0f));
      
      laserGun.attachChild(b);
      laserTarget.attachChild(s);
      
      l = new Laser(laserGun,laserTarget);
      
      rootNode.attachChild(laserGun);
      rootNode.attachChild(laserTarget);
   }

   protected void simpleUpdate() {
      laserTarget.getLocalTranslation().addLocal(vel.mult(tpf));
      
      if (laserTarget.getLocalTranslation().x > 50.0) vel.x = -(SPEED.x);
      if (laserTarget.getLocalTranslation().y > 50.0) vel.y = -(SPEED.y);
      if (laserTarget.getLocalTranslation().z > -40.0) vel.z = -(SPEED.z);
      if (laserTarget.getLocalTranslation().x < -50.0) vel.x = (SPEED.x);
      if (laserTarget.getLocalTranslation().y < -50.0) vel.y = (SPEED.y);
      if (laserTarget.getLocalTranslation().z < -100.0) vel.z = (SPEED.z);
            
      laserGun.lookAt(laserTarget.getLocalTranslation(), new Vector3f(0,1f,0));
      
      cooldown -= tpf;
      if (cooldown <= 0) {
         if (l.isOn() ) {
            l.setOff();
            cooldown = ROF;
         }
         else
         {
            l.setOn();
            cooldown = PULSELENGTH;
         }
      }
      
      l.updateLaser();
      super.simpleUpdate();
   }

}



It's untextured but that should be trivial to fix - it's shamelessly hackedtogether which may not be!
But it does show tracking a moving object with just one line of code to do the pointing and scaling.

Cool… and it works :slight_smile: Very nice, thank you man :slight_smile:



I never knew you could just move the rotation point… All I have to do now is:

  • either make the quad always face the camera
  • or better yet add 2 more quads so that they are in * shape looking from one end…



    Thanks again

Alric,



Was just catching up on this.



You sir, are the man. Been trying to get this for some time.



Thanks,

D

Hey MindGamer,



Last night I was able to create this in a '*' pattern and get it working quite nicely, with one problem. The problem was texturing the laser lines. I made them transparent and then applied a texture, but for some reason, the texture is only applied to one side of the quad. I must be missing something. How do I apply a texture to both sides of the laser line?



Cheers,

D

Hi, I'm gald that was useful. It's been a while since I played with the laser code but my approach was the other one Mindgamer suggested, making the quad always face the camera. This is probably slower (at least with my code!), but I don't like having transparent things intersecting where I can help it.

pp

My Vector math is pretty awful so I am sure there is a much more efficeint way of doing it, but all I really wanted to do is spin the quad around it's z-axis so that you can always see it's thickness, while leaving the start and end points unchanged.



The code I used was (those good at maths look away now):



   protected void showFlatToCamera() {
   // This method rotates the laserQuad around it's z-axis to ensure that it is always shown flat  to the camera, not edge on.
      Vector3f lVisibleAxis = laserQuad.getWorldRotation().getRotationColumn(0).normalize();
      Vector3f laserViewVector = display.getRenderer().getCamera().getLocation().subtract(
                           laserQuad.getWorldTranslation()).normalize();
      float laserViewAngle = lVisibleAxis.angleBetween(laserViewVector);
      Quaternion correctionRot = new Quaternion();
      float cr = (FastMath.PI / 2.0f) - laserViewAngle;
      correctionRot.fromAngleNormalAxis(cr, new Vector3f(0,0,1.0f));
      laserQuad.getLocalRotation().multLocal(correctionRot);
}



pic!




That looks good Alric…and having the quad always face the camera may fix the txture issue that I've been having. Going to give that a try tonight.

you could also have a look at the TrailMesh, which basically does the same thing but for any number of points, and can be set to do the "billboarding" for making the ribbon face the camera.

(copy paste some code, or use it with only 2 points for same behaviour as a quad)



http://www.jmonkeyengine.com/jmeforum/index.php?topic=8288.msg64649#msg64649

there you go: http://www.jmonkeyengine.com/jmeforum/index.php?topic=8288.0



EDIT: damn this, too late again  :wink:

Thank you all for these ideas. I have not implemented my own * shape yet but probably will. I want to have a pretty much free camera in my game-world where the shooting takes place… and I am just afraid that if I look at a billboarded laser from an angle more or less ahead of it, it will just look lame and flat…



This is all happening in my mind only, though… maybe it would look ok…

Alric…how did you texture that laser in the pic above.



This is what I'm doing…but it's not as shiny as yours  :wink:




final AlphaState as1 = DisplaySystem.getDisplaySystem().getRenderer()
      .createAlphaState();
      as1.setBlendEnabled(true);
      as1.setSrcFunction(AlphaState.SB_SRC_ALPHA);
      as1.setDstFunction(AlphaState.DB_ONE);
      as1.setTestEnabled(true);
      as1.setTestFunction(AlphaState.TF_GREATER);
      as1.setEnabled(true);
      
      laserQuadA.setRenderState(as1);
      
      
      final TextureState ts = DisplaySystem.getDisplaySystem().getRenderer()
      .createTextureState();

      laserTexture = TextureManager.loadTexture(LaserTest.class
            .getClassLoader().getResource("laser_text2.jpg"),
            Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR,
            Image.GUESS_FORMAT_NO_S3TC, 1.0f, true);

      if (laserTexture == null) {
         throw new NullPointerException(
         "Darn it, I forgot to initialize the texture.");
         }
      
      MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
      ms.setEmissive(ColorRGBA.red.clone());

      laserTexture.setWrap(Texture.WM_WRAP_S_WRAP_T);
      laserTexture.setTranslation(new Vector3f());
      ts.setTexture(laserTexture);
      
      laserQuadA.setRenderState(ms);
      laserQuadA.setRenderState(ts);





laser_text2.jpg is just a red texture I created in Photoshop.

It's just a bit of white in the middle of the texture. I also turn lighting off with



laserQuad.setLightCombineMode(LightState.OFF);



With a pure red laser it looks like this: