www.jpct.net

jPCT - a 3d engine for Java => Support => Topic started by: AGP on October 22, 2009, 07:28:17 pm

Title: Lightsaber Test
Post by: AGP on October 22, 2009, 07:28:17 pm
I was going to put this little example in the wiki anyway, but something strange is happening. The very simple premise is that at 0-degree x rotation, the lightsaber pointed up, the lightsaber's blade (a transparent plane on top of a 3d cylinder) is at its full height. At 90 degrees, it's invisible. So for every degree you rotate it, it shrinks height/90. That was going to work for the first 90 degrees, and I would apply it appropriately for each of the other three sets of 90, and then apply it on the second axis (it never needs to rotate on all 3). To prove that everything works I'm drawing squares around the edges of the blade and I wrote a shrinkOrGrow() method which successfully fully shrinks the blade plane and extends it again to its full height. Yet when I rotate the lightsaber by 10 degrees and shrink the plane by unitPerDegree*10, the plane doesn't shrink far enough. The lightsaber is Obi-wan's off of scifi3d.com.

Code: [Select]
/*
AGP, 2009
*/
import java.awt.*;
import java.awt.event.*;
import com.threed.jpct.*;
import com.threed.jpct.util.*;

public class LightsaberTest extends Frame implements WindowListener, KeyListener {
     private World theWorld;
     private FrameBuffer buffer;
     private Camera theCamera;
     private boolean keepGoing;
     private Graphics g;
     private Light light;
     private Canvas canvasGL;
     private Lightsaber lightsaber;
     protected float xRotation;

     public LightsaberTest() {
this.setTitle("BR's");
xRotation = 0.0f;
keepGoing = true;
Config.maxPolysVisible *= 16;
theWorld = new World();
lightsaber = new Lightsaber(theWorld);
theCamera = theWorld.getCamera();
buffer = new FrameBuffer(1024, 768, FrameBuffer.SAMPLINGMODE_NORMAL);
canvasGL = new Canvas();
// canvasGL = buffer.enableGLCanvasRenderer();
// buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
this.add(canvasGL, BorderLayout.CENTER);
theWorld.setAmbientLight(145, 145, 145);
theCamera.setPosition(lightsaber.getTransformedCenter());
light = new Light(theWorld);
light.setIntensity(35, 35, 35);
light.setAttenuation(8f);
theCamera.moveCamera(Camera.CAMERA_MOVEOUT, 25f);
theCamera.lookAt(lightsaber.getTransformedCenter());
light.setPosition(theCamera.getPosition());

canvasGL.addKeyListener(this);
this.addKeyListener(this);
this.addWindowListener(this);
this.setSize(1024, 768);
this.setVisible(true);
drawLoop();
     }

     private void drawLoop() {
g = this.getGraphics();
while (keepGoing) {

     try {
Thread.sleep(50);
     }
     catch (InterruptedException e) {}
if (canvasGL.getGraphics() == null)
continue;
     buffer.clear();
     lightsaber.rotateDegreesX(xRotation);
     theWorld.renderScene(buffer);
     theWorld.draw(buffer);
//      buffer.display(g);
buffer.display(canvasGL.getGraphics());
lightsaber.drawEdges(theCamera, buffer, (Graphics2D)canvasGL.getGraphics());


// buffer.displayGLOnly();
// canvasGL.paint(canvasGL.getGraphics());//NEEDED ONLY BECAUSE displayGLOnly() DOESN'T GO AS FAR AS display()
}
     }

     public void windowClosing(WindowEvent e) {
if (e.getWindow() == this) {
     keepGoing = false;
     this.dispose();
     System.exit(0);
}
     }
     public void windowClosed(WindowEvent e) {}
     public void windowOpened(WindowEvent e) {}
     public void windowActivated(WindowEvent e) {}
     public void windowDeactivated(WindowEvent e) {}
     public void windowIconified(WindowEvent e) {}
     public void windowDeiconified(WindowEvent e) {}

     public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (e.isShiftDown() && keyCode == KeyEvent.VK_UP)
     theCamera.moveCamera(Camera.CAMERA_MOVEIN, 5f);
else if (e.isShiftDown() && keyCode == KeyEvent.VK_DOWN)
     theCamera.moveCamera(Camera.CAMERA_MOVEOUT, 5f);
else if (keyCode == KeyEvent.VK_UP)
     xRotation += 10f;
else if (keyCode == KeyEvent.VK_DOWN)
     xRotation -= 10f;

else if (keyCode == KeyEvent.VK_G)
     lightsaber.bladeController.shrinkOrGrow();
     }
     public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_DOWN)
     xRotation = 0f;
     }
     public void keyTyped(KeyEvent e) {}

     public static void main(String[] args) {
new LightsaberTest();
     }
}
class Lightsaber {
     private Object3D lightsaber, bladePlane;
     protected VertexController bladeController;
     private float totalX;

     public Lightsaber(World theWorld) {
totalX = 0.00f;
TextureManager.getInstance().addTexture("PLATEOX2.JPG", new Texture("PLATEOX2.JPG"));
TextureManager.getInstance().addTexture("scratch.jpg", new Texture("scratch.jpg"));
lightsaber = Object3D.mergeAll(Loader.load3DS("saber.3ds", .1f));

TextureManager.getInstance().addTexture("Blade2.jpg", new Texture("Blade2.jpg"));
bladePlane = Primitives.getPlane(1, 8);
bladePlane.setTransparency(0);
bladePlane.setTransparencyMode(Object3D.TRANSPARENCY_MODE_ADD);
bladePlane.setTexture("Blade2.jpg");
bladePlane.setBillboarding(true);
theWorld.addObject(lightsaber);
theWorld.addObject(bladePlane);
theWorld.buildAllObjects();
bladePlane.translate(-1.4f, -19f, 0);
bladeController = new VertexController(bladePlane);
bladeController.scaleYandShift(8f);
     }
     public SimpleVector getTransformedCenter() {
return lightsaber.getTransformedCenter();//CHANGE LATER TO AVERAGE GRIP'S AND BLADE'S CENTERS
     }

     private void toWorldSpace(SimpleVector objectSpace) {
SimpleVector translation = bladePlane.getTranslation();
objectSpace.x += translation.x;
objectSpace.y += translation.y;
objectSpace.z += translation.z;
     }
     public void drawEdges(Camera cam, FrameBuffer buffer, Graphics2D g) {
bladeController.refreshMeshData();
VectorAndIndex[] topMost = bladeController.getTopMost();


toWorldSpace(topMost[0].vector=new SimpleVector(topMost[0].vector));
toWorldSpace(topMost[1].vector=new SimpleVector(topMost[1].vector));
SimpleVector[] top2D = new SimpleVector[]{Interact2D.project3D2D(cam, buffer, topMost[0].vector), Interact2D.project3D2D(cam, buffer, topMost[1].vector)};




VectorAndIndex[] bottomMost = bladeController.getBottomMost();
toWorldSpace(bottomMost[0].vector=new SimpleVector(bottomMost[0].vector));
toWorldSpace(bottomMost[1].vector=new SimpleVector(bottomMost[1].vector));
SimpleVector[] bottom2D = new SimpleVector[]{Interact2D.project3D2D(cam, buffer, bottomMost[0].vector), Interact2D.project3D2D(cam, buffer, bottomMost[1].vector)};
if (bottom2D[0].y > 0) {
     g.setColor(Color.red);
     g.drawRect((int)top2D[0].x-10, (int)top2D[0].y-10, 20, 20);
     g.drawRect((int)top2D[1].x-10, (int)top2D[1].y-10, 20, 20);
     g.setColor(new Color(255, 0, 128));
     g.drawRect((int)bottom2D[0].x-10, (int)bottom2D[0].y-10, 20, 20);
     g.drawRect((int)bottom2D[1].x-10, (int)bottom2D[1].y-10, 20, 20);
}
     }

     public void rotateDegreesX(float xRotation) {//.05 RADIANS==2.8647º, 90 DEGREES==1.5707 RADIANS
float rotationRadians = (float) Math.toRadians(xRotation);
lightsaber.rotateAxis(lightsaber.getXAxis(), rotationRadians);
if (xRotation != 0.0f) {
     totalX += xRotation;
     bladeController.shrinkByDegrees(xRotation);
}
     }
}
class VertexController extends GenericVertexController {
     protected Mesh controlled;
     private float originalWidth = 0f;
     private float unitPerDegree;//THE AMOUNT BY WHICH THE PLACE SHOULD SHRINK PER DEGREE's ROTATION
     private boolean activated;
     private float originalHeight;

     public VertexController(Object3D toControl) {
controlled = toControl.getMesh();
this.init(controlled, true);
unitPerDegree = getHeight()/90.0f;



activated = true;
System.out.println("Height: "+getHeight() +", unitPerDegree: "+unitPerDegree);
     }

     public void scaleYandShift(float scale) {
SimpleVector[] vertices = this.getSourceMesh();
SimpleVector[] destination = this.getDestinationMesh();
VectorAndIndex[] topMost = getTopMost();

float height = getHeight();
topMost[0].vector.y -= height*scale;
topMost[1].vector.y -= height*scale;
destination[topMost[0].index] = topMost[0].vector;
destination[topMost[1].index] = topMost[1].vector;
this.updateMesh();
originalHeight = getHeight();
     }

     public void shrinkOrGrow() {

VectorAndIndex[] topMost = getTopMost();
SimpleVector[] destination = this.getDestinationMesh();
if (activated) {


    float height = getHeight();
    destination[topMost[0].index].y += height-.05;
    destination[topMost[1].index].y += height-.05;
}
else {

    destination[topMost[0].index].y -= originalHeight+.05;
    destination[topMost[1].index].y -= originalHeight+.05;    
}
activated = !activated;

this.updateMesh();
     }

     public void shrinkByDegrees(float degrees) {
VectorAndIndex[] topMost = getTopMost();
SimpleVector[] destination = this.getDestinationMesh();
System.out.println("Shrink factor: "+(unitPerDegree*degrees));
destination[topMost[0].index].y += (unitPerDegree*degrees);
destination[topMost[1].index].y += (unitPerDegree*degrees);
this.updateMesh();
     }

     protected VectorAndIndex[] getTopMost() {//POSITIVE y GOES *DOWN*
VectorAndIndex[] topMost = new VectorAndIndex[2];
SimpleVector[] vertices = this.getSourceMesh();

topMost[0] = new VectorAndIndex(vertices[0], 0);
for (int i = 1; i < vertices.length; i++)
     if (topMost[0].vector.y > vertices[i].y)
topMost[0] = new VectorAndIndex(vertices[i], i);
topMost[1] = new VectorAndIndex(vertices[0], 0);
for (int i = 1; i < vertices.length; i++)
     if (topMost[1].vector.y >= vertices[i].y && topMost[0].index != i)
topMost[1] = new VectorAndIndex(vertices[i], i);
return topMost;
     }
     public float getHeight() {
VectorAndIndex[] topMost = getTopMost();
VectorAndIndex[] bottomMost = getBottomMost();

float height = bottomMost[0].vector.y-topMost[0].vector.y;
return height;
     }

     public void apply() {}

     protected VectorAndIndex[] getBottomMost() {//POSITIVE y GOES *DOWN*
VectorAndIndex[] bottomMost = new VectorAndIndex[2];
SimpleVector[] vertices = this.getSourceMesh();

bottomMost[0] = new VectorAndIndex(vertices[0], 0);
for (int i = 1; i < vertices.length; i++)
     if (bottomMost[0].vector.y < vertices[i].y)
bottomMost[0] = new VectorAndIndex(vertices[i], i);
bottomMost[1] = new VectorAndIndex(vertices[0], 0);
for (int i = 1; i < vertices.length; i++)
     if (bottomMost[1].vector.y <= vertices[i].y && bottomMost[1].index != i)
bottomMost[1] = new VectorAndIndex(vertices[i], i);
return bottomMost;
     }
}
class VectorAndIndex {
     protected SimpleVector vector;
     protected int index;
     public VectorAndIndex(SimpleVector vector, int index) {
this.vector = vector;
this.index = index;
     }
}
Title: Re: Lightsaber Test
Post by: AGP on October 22, 2009, 07:38:32 pm
By the way, I know that the pivot isn't perfectly set so that the lower end of the blade, after several rotations, needs to be adjusted as well. I was going to do that last.
Title: Re: Lightsaber Test
Post by: paulscode on October 22, 2009, 09:40:12 pm
This isn't an answer to your specific problem, but I think I wrote a single-axis billboarding method a while back to use for cylindrical special effects (should work for a lightsaber glow effect).  I can dig up that method for you if you are interested.  It might be a viable alternative to achieve the same effect you are trying to simulate here.
Title: Re: Lightsaber Test
Post by: AGP on October 22, 2009, 09:55:59 pm
Sure, thanks a lot, show me what you've got.

But I'd still like to know what's wrong with mine (nothing, as far as I can tell!).
Title: Re: Lightsaber Test
Post by: paulscode on October 23, 2009, 02:43:04 am
Ok, I tried to clean the method up a little bit:
Code: [Select]
    private void singleAxisBillboard( Object3D parentObject,
                                      Object3D childBillboard,
                                      SimpleVector targetPoint )
    {
        // Get parent object's world rotation matrix:
        Matrix m = new Matrix( parentObject.getWorldTransformation() );
        float[] dm = m.getDump();
        for( int i = 12; i < 15; i++ )
        {
            dm[i] = 0;
        }
        dm[15] = 1;
        m.setDump( dm );
        // Get the inverse:
        Matrix i = new Matrix( m ).invert3x3();

        // Convert target point into parent's Object Space:
        SimpleVector convertedTarget = targetPoint.calcSub(
                                          parentObject.getTransformedCenter() );
        convertedTarget.matMul( i );

        // Cast target onto root object's x/z plane (Object Space):
        convertedTarget.y = 0;

        // Calculate the "up" and "look" directions (Object Space):
        SimpleVector up = new SimpleVector( 0, -1, 0 );
        SimpleVector look = convertedTarget.normalize();

        // ** NOTE: Use this if childBillboard is not a child of parentObject **
        // Convert to World Space directions:
        //  up.matMul( m );
        //  up = up.normalize();
        //  look.matMul( m );
        //  look = look.normalize();
        // **

        // Calculate the "right" direction:
        SimpleVector right = up.calcCross( look ).normalize();

        // Create the destination rotation matrix:
        Matrix destMatrix = new Matrix();
        destMatrix.set( 0, 0, right.x );
        destMatrix.set( 1, 0, up.x );
        destMatrix.set( 2, 0, look.x );
        destMatrix.set( 3, 0, 0.0f );

        destMatrix.set( 0, 1, right.y );
        destMatrix.set( 1, 1, up.y );
        destMatrix.set( 2, 1, look.y );
        destMatrix.set( 3, 1, 0.0f );

        destMatrix.set( 0, 2, right.z );
        destMatrix.set( 1, 2, up.z );
        destMatrix.set( 2, 2, look.z );
        destMatrix.set( 3, 2, 0.0f );

        destMatrix.set( 0, 3, 0.0f );
        destMatrix.set( 1, 3, 0.0f );
        destMatrix.set( 2, 3, 0.0f );
        destMatrix.set( 3, 3, 1.0f );

        // ** NOTE: Use this if childBillboard is not a child of parentObject **
        //  childBillboard.translate( rootObject.getTransformedCenter().calcSub(
        //      myBillboard.getTransformedCenter() ) );
        // **

        childBillboard.setRotationMatrix( destMatrix );
    }

The method assumes that childBillboard is a child of parentObject.  If that is not the case in your example, see the comments in the code for how to change it.  It should at least be enough to get you started.

I created a simple demo applet:
http://www.paulscode.com/source/Glowing/TubeLightEffect.html (http://www.paulscode.com/source/Glowing/TubeLightEffect.html)

As you can see in the above demo, this effect works well for cylindars as long as you aren't looking straight down one of the ends.  If you need to fix this, you could try placing something on the ends to hide the edge or maybe adding a "cap" of some kind to the billboard itself.
Title: Re: Lightsaber Test
Post by: AGP on October 23, 2009, 04:25:25 am
I know I said I was going to read a book on matrices, but I ended up reading (still am) one on game AI and finishing The New Jedi Order.  I did get one and it's my next read. I understand your general idea, but I'd really like to make mine work.

I would cap it with an intersecting plane if I were you. Thanks for digging this up, it's always good looking at different approaches.
Title: Re: Lightsaber Test
Post by: EgonOlsen on October 23, 2009, 09:30:15 am
Could post some screen shots that illustrate the problem?
Title: Re: Lightsaber Test
Post by: AGP on October 23, 2009, 06:42:42 pm
Here you go:

(http://www.agpgames.com/LightsaberRotated.png)
Title: Re: Lightsaber Test
Post by: EgonOlsen on October 23, 2009, 11:13:05 pm
Well...ok...and the exact problem is? From that screen shot, it's hard to tell. I've looked at your code, but it's hard to find out what may be wrong if i'm not sure what exactly you want to achieve.... ???
Title: Re: Lightsaber Test
Post by: AGP on October 24, 2009, 12:37:39 am
I wrote it in the first post: for every x degree I turn I should need only translate top most vertices by height/90, right? Only as you can see in the screenshot, it doesn't shrink enough.
Title: Re: Lightsaber Test
Post by: paulscode on October 24, 2009, 01:03:05 am
I would put in a few more calls to println at strategic points in the code to display varriable values along the way.  Then you can check the console output and use a calculator to make sure those numbers jive with what you expect them to be.  It can at least give you an idea of where the problem is not at.
Title: Re: Lightsaber Test
Post by: AGP on October 24, 2009, 01:41:21 am
That's always good advice, but I already have everywhere, the code is really simple, and I already proved I can shrink and re-extent the glow at will.
Title: Re: Lightsaber Test
Post by: paulscode on October 24, 2009, 03:13:45 am
So having verified that the numbers add up the way they should, and having verified that the glow effect resizing works properly when given the proper angles, then the problem must be with the light sabre and not with the glow effect, correct?  In other words, the light sabre is shorter than it should be, not that the glow effect is longer than it should be.  That is strange.  One thing you might check is if the visible height of the blade after rotation is a linear function of the rotation angle.  In other words, you know that 0 degrees is full height, and 90 degrees is zero height, but is 45 degrees half height?  (I'd check this for the glow effect resizing function too if you haven't already).  It seems unlikely that this is the problem, but it might be worth while to actually measure it to be sure.  I know depth figures into the 3D equation to some extent, so since the tip is further away from the camera than the handle is, that might cause a noticable height difference than what you are expecting it to be.
Title: Re: Lightsaber Test
Post by: AGP on October 24, 2009, 05:55:06 am
That's actually quite brilliant, thank you. Now that you mentioned it, it does seem likely that it's not linear, given the distances. I'll check and report back.
Title: Re: Lightsaber Test
Post by: AGP on October 25, 2009, 08:12:18 am
OK, stupid question: what is targetPoint? When I first read your method I assumed it was just a bad name for the rotation, but clearly that's not it. Is it supposed to be a translation?
Title: Re: Lightsaber Test
Post by: paulscode on October 25, 2009, 12:03:41 pm
Oh, right - I probably should have explained how to use that method.

'targetPoint' is the point you want the 'childBillboard' object to point towards.  'childBillboard' will orbit around the 'parentObject' y-axis to try and points towards 'targetPoint'.  Normally you would use camera.getPosition() for this argument.

For reference, here is the source code for the 'Tube Light Effect' applet I posted earlier:
Code: [Select]
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JApplet;

import com.threed.jpct.Camera;
import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Lights;
import com.threed.jpct.Matrix;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.KeyMapper;
import com.threed.jpct.util.KeyState;

public class TubeLightEffect extends JApplet implements MouseListener,
                                                        MouseMotionListener,
                                                        Runnable
{
    private Object3D tube;
    private Object3D glowBar;

    private FrameBuffer buffer = null;
    private World world = null;
    private Camera camera = null;
    private int width = 640;
    private int height = 480;
    private int prevMouseX, prevMouseY;
    private boolean loop=true;

    private Object3D pivot = null;
    private Object3D satellite = null;
    private KeyMapper keyMapper;
    private boolean ctrlDown = false;
    private boolean shiftDown = false;
    private boolean upDown = false;
    private boolean downDown = false;
    private boolean leftDown = false;
    private boolean rightDown = false;

    private SimpleVector rotationXAxis, rotationYAxis;

    // Initialize all components of the applet
    @Override
    public void init()
    {
        Config.maxPolysVisible = 70000;
        world = new World();  // create a new world

        float scaler = 0.01f;

        World.setDefaultThread( Thread.currentThread() );

        // create a new buffer to draw on:
        buffer = new FrameBuffer( width, height, FrameBuffer.SAMPLINGMODE_NORMAL );
        TextureManager.getInstance().addTexture( "White Glow",
                            new Texture( getClass().getClassLoader().
                              getResourceAsStream( "Textures/WhiteGlow.png" ),
                              true ) );
        TextureManager.getInstance().addTexture( "Clear Texture",
                                             new Texture( 8, 8, Color.BLACK ) );
        tube = Primitives.getCylinder( 90, 25 * scaler, 25 );
        tube.setVisibility( true );
        tube.build();
        world.addObject( tube );

        glowBar = new Object3D( 4 );
        float offset = 200 * scaler;
        float zoffset = 90 * scaler;
        float heightScale = 3.0f;
        // Create polys for the glow effect:
        glowBar.addTriangle( new SimpleVector( -offset, -offset * heightScale,
                                -zoffset / 2 ), 0, 0,
                           new SimpleVector( -offset, offset * heightScale,
                                -zoffset / 2 ), 0, 1,
                           new SimpleVector( offset, offset * heightScale,
                                -zoffset / 2 ), 1, 1,
                           TextureManager.getInstance().getTextureID(
                                                               "White Glow" ) );
        glowBar.addTriangle( new SimpleVector( offset, offset * heightScale,
                                -zoffset / 2 ), 1, 1,
                           new SimpleVector( offset, -offset * heightScale,
                                -zoffset / 2 ), 1, 0,
                           new SimpleVector( -offset, -offset * heightScale,
                                -zoffset / 2 ), 0, 0,
                           TextureManager.getInstance().getTextureID(
                                                               "White Glow" ) );
        glowBar.addTriangle( new SimpleVector( offset, offset * heightScale,
                                zoffset / 2 ), 1, 1,
                           new SimpleVector( -offset, offset * heightScale,
                                zoffset / 2 ), 0, 1,
                           new SimpleVector( -offset, -offset * heightScale,
                                zoffset / 2 ), 0, 0,
                           TextureManager.getInstance().getTextureID(
                                                               "White Glow" ) );
        glowBar.addTriangle( new SimpleVector( -offset, -offset * heightScale,
                                zoffset / 2 ), 0, 0,
                           new SimpleVector( offset, -offset * heightScale,
                                zoffset / 2 ), 1, 0,
                           new SimpleVector( offset, offset * heightScale,
                                zoffset / 2 ), 1, 1,
                           TextureManager.getInstance().getTextureID(
                                                               "White Glow" ) );
        // Set up the transparency:
        glowBar.setTransparency( 0 );
        glowBar.setTransparencyMode( Object3D.TRANSPARENCY_MODE_ADD );

        glowBar.build();  // set up bounding box and normals
        // Make sure external lighting doesn't affect the glow object:
        glowBar.setLighting( Object3D.LIGHTING_NO_LIGHTS );
        glowBar.setAdditionalColor( intensityColor(
                                              new Color( 255, 255, 0, 75 ) ) );
        world.addObject( glowBar );  // add glow object to the world
        // Make glow a child of the tube:
        tube.addChild( glowBar );

        // set up the camera assembly:
        pivot = Object3D.createDummyObj();
        satellite = Object3D.createDummyObj();
        pivot.addChild( satellite );
        satellite.translate(  new SimpleVector( 0, 0, -20 ) );
        camera = world.getCamera();

        // Make sure everything is built:
        world.buildAllObjects();

        resetCameraPosition();

        letThereBeLight();  // create light sources for the scene

        // receive mouse input from the main applet:
        addMouseListener( this );
        addMouseMotionListener( this );

        keyMapper = new KeyMapper( this );

        addKeyListener( keyMapper );

        new Thread(this).start();
    }

    // Draw the scene
    @Override
    public void paint( Graphics g )
    {
        buffer.clear();   // erase the previous frame

        // render the world onto the buffer:
        world.renderScene( buffer );
        world.draw( buffer );
        buffer.update();

        buffer.display( g, 0, 0);  // Paint this frame onto the applet
    }

    @Override
    public void destroy()
    {
    loop=false;
    }

    @Override
    public void run()
    {
        while (loop)
        {
            pollKeyboard();
            tick();
            this.repaint();
            try
            {
                Thread.sleep(10);
            }
            catch(Exception e){}
        }
    }

    public void tick()
    {
        float theta = 0.02f;
        float step = 0.2f;
        SimpleVector trans;

        if( upDown )
        {
            if( ctrlDown )
            {
                trans = pivot.getTransformedCenter().calcSub(
                                 satellite.getTransformedCenter() ).normalize();
                trans.scalarMul( step );
                satellite.translate( trans );
            }
            else if( shiftDown )
            {
                trans = pivot.getYAxis();
                trans.scalarMul( -step );
                satellite.translate( trans );
            }
            else
            {
                pivot.rotateAxis( pivot.getXAxis(), theta );
            }
            resetCameraPosition();
        }
        else if( downDown )
        {
            if( ctrlDown )
            {
                trans = pivot.getTransformedCenter().calcSub(
                                 satellite.getTransformedCenter() ).normalize();
                trans.scalarMul( -step );
                satellite.translate( trans );
            }
            else if( shiftDown )
            {
                trans = pivot.getYAxis();
                trans.scalarMul( step );
                satellite.translate( trans );
            }
            else
            {
                pivot.rotateAxis( pivot.getXAxis(), -theta );
            }

            resetCameraPosition();
        }
        if( leftDown )
        {
            if( shiftDown )
            {
                trans = pivot.getXAxis();
                trans.scalarMul( -step );
                satellite.translate( trans );
            }
            else
            {
                pivot.rotateAxis( pivot.getYAxis(), -theta );
            }
            resetCameraPosition();
        }
        else if( rightDown )
        {
            if( shiftDown )
            {
                trans = pivot.getXAxis();
                trans.scalarMul( step );
                satellite.translate( trans );
            }
            else
            {
                pivot.rotateAxis( pivot.getYAxis(), theta );
            }
            resetCameraPosition();
        }
    }

    private void resetCameraPosition()
    {
        SimpleVector look = new SimpleVector( pivot.getZAxis() ).normalize();
        SimpleVector up = new SimpleVector( pivot.getYAxis() ).normalize();

        camera.setOrientation( look, up );

        camera.setPosition( satellite.getTransformedCenter() );

        // update the axis to rotate the firefly around:
        rotationYAxis = pivot.getYAxis();
        rotationXAxis = pivot.getXAxis();

        // Make the glow object billboard around its y-axis
        singleAxisBillboard( tube, glowBar, camera.getPosition() );
    }

    private void singleAxisBillboard( Object3D parentObject,
                                      Object3D childBillboard,
                                      SimpleVector targetPoint )
    {
        // Get parent object's world rotation matrix:
        Matrix m = new Matrix( parentObject.getWorldTransformation() );
        float[] dm = m.getDump();
        for( int i = 12; i < 15; i++ )
        {
            dm[i] = 0;
        }
        dm[15] = 1;
        m.setDump( dm );
        // Get the inverse:
        Matrix i = new Matrix( m ).invert3x3();

        // Convert target point into parent's Object Space:
        SimpleVector convertedTarget = targetPoint.calcSub(
                                          parentObject.getTransformedCenter() );
        convertedTarget.matMul( i );

        // Cast target onto root object's x/z plane (Object Space):
        convertedTarget.y = 0;

        // Calculate the "up" and "look" directions (Object Space):
        SimpleVector up = new SimpleVector( 0, -1, 0 );
        SimpleVector look = convertedTarget.normalize();

        // ** NOTE: Use this if childBillboard is not a child of parentObject **
        // Convert to World Space directions:
        //  up.matMul( m );
        //  up = up.normalize();
        //  look.matMul( m );
        //  look = look.normalize();
        // **

        // Calculate the "right" direction:
        SimpleVector right = up.calcCross( look ).normalize();

        // Create the destination rotation matrix:
        Matrix destMatrix = new Matrix();
        destMatrix.set( 0, 0, right.x );
        destMatrix.set( 1, 0, up.x );
        destMatrix.set( 2, 0, look.x );
        destMatrix.set( 3, 0, 0.0f );

        destMatrix.set( 0, 1, right.y );
        destMatrix.set( 1, 1, up.y );
        destMatrix.set( 2, 1, look.y );
        destMatrix.set( 3, 1, 0.0f );

        destMatrix.set( 0, 2, right.z );
        destMatrix.set( 1, 2, up.z );
        destMatrix.set( 2, 2, look.z );
        destMatrix.set( 3, 2, 0.0f );

        destMatrix.set( 0, 3, 0.0f );
        destMatrix.set( 1, 3, 0.0f );
        destMatrix.set( 2, 3, 0.0f );
        destMatrix.set( 3, 3, 1.0f );

        // ** NOTE: Use this if childBillboard is not a child of parentObject **
        //  childBillboard.translate( rootObject.getTransformedCenter().calcSub(
        //      myBillboard.getTransformedCenter() ) );
        // **

        childBillboard.setRotationMatrix( destMatrix );
    }

    // create light sources for the scene
    private void letThereBeLight()
    {
        world.getLights().setOverbrightLighting (
            Lights.OVERBRIGHT_LIGHTING_DISABLED );
        world.getLights().setRGBScale( Lights.RGB_SCALE_2X );

        // Set the overall brightness of the world:
        world.setAmbientLight( 50, 50, 50 );

        // Create a main light-source:
        world.addLight( new SimpleVector( 0, -50, -50 ), 20, 20, 20 );
    }

    private void pollKeyboard()
    {
        KeyState state = null;
        do
        {
            state = keyMapper.poll();
            if( state != KeyState.NONE )
            {
                keyAffected( state );
            }
        } while( state != KeyState.NONE );
    }

    private void keyAffected( KeyState state )
    {
        int code = state.getKeyCode();
        boolean event = state.getState();

        switch( code )
        {
            case( KeyEvent.VK_CONTROL ):
            {
                ctrlDown = event;
                break;
            }
            case( KeyEvent.VK_SHIFT ):
            {
                shiftDown = event;
                break;
            }
            case( KeyEvent.VK_UP ):
            {
                upDown = event;
                break;
            }
            case( KeyEvent.VK_DOWN ):
            {
                downDown = event;
                break;
            }
            case( KeyEvent.VK_LEFT ):
            {
                leftDown = event;
                break;
            }
            case( KeyEvent.VK_RIGHT ):
            {
                rightDown = event;
                break;
            }
            case( KeyEvent.VK_ESCAPE ):
            {
                loop = event;
                break;
            }
        }
    }

    public void mouseClicked( MouseEvent e ){}
    public void mouseEntered( MouseEvent e ) {}
    public void mouseExited( MouseEvent e ) {}
    public void mouseMoved( MouseEvent e ) {}
    public void mouseReleased( MouseEvent e ){}
    // Dragging the mouse should rotate the object
    public void mouseDragged( MouseEvent e )
    {
        // get the mouse's coordinates:
        int x = e.getX();
        int y = e.getY();
        Dimension size = e.getComponent().getSize();

        // Calculate the angles to rotate the object:
        float thetaY = (float)Math.PI * ( (float) ( x - prevMouseX )
                                                         / (float) size.width );
        float thetaX = (float)Math.PI * ( (float) ( prevMouseY - y )
                                                        / (float) size.height );

        // Apply the rotations to the object:
        tube.rotateAxis( rotationXAxis, thetaX );
        tube.rotateAxis( rotationYAxis, thetaY );

        resetCameraPosition();

        // Keep track of the mouse location:
        prevMouseX = x;
        prevMouseY = y;
    }
    public void mousePressed( MouseEvent e )
    {
        // Start keeping track of the mouse location now.
        // This prevent the gear assembly from jerking to the new angle
        // whenever mouseDragged first gets called:
        prevMouseX = e.getX();
        prevMouseY = e.getY();
    }
    // Takes the alpha channel of a color, and uses it to convert
    // the rgb intensities.  Returns the resulting color (with alpha=1)
    private Color intensityColor( Color color )
    {
        float r = color.getRed();
        float g = color.getGreen();
        float b = color.getBlue();

        float a = color.getAlpha();

        float intensity = a / 255.0f;

        r *= intensity;
        g *= intensity;
        b *= intensity;

        return new Color( (int) r, (int) g, (int) b );
    }
}

In this example, the parentObject is just a simple cylindar, billboardChild is a rectangular glow object, and targetPoint is the camera's position.  The method is called anytime the camera or the object itself is translated or rotated.  You might also notice that I placed a quad on both the front and the back of the billboardChild - that was just because I couldn't remember which direction is the used as the "front" of billboardChild
Title: Re: Lightsaber Test
Post by: AGP on October 25, 2009, 05:01:32 pm
I thought it could be the camera position, but apparently not enough to try it. :- ) And why not just set culling to false on billboardChild instead?
Title: Re: Lightsaber Test
Post by: paulscode on October 26, 2009, 12:48:38 am
why not just set culling to false on billboardChild instead?
That would work if the quad were in the center of the parent object.  However, I think for most glowing effects, you want the quad to be some distance outside the parent object, to create the illusion of a sphere (or cylindar in this case).  There are two ways to accomplish this (see my post at the end of this thread (http://www.jpct.net/forum2/index.php/topic,1382.msg9697.html#msg9697)).  The way I did it for this example was with quads at opposite sides of an immaginary cube.
(http://www.paulscode.com/images/PivotPoly.gif)
I also talked about this method in the context of a vehicle's headlights, in this thread (http://www.jpct.net/forum2/index.php/topic,1323.msg9153.html#msg9153).
(http://www.paulscode.com/images/BillboardGlow.gif)

Obviously in the case of the tube light or a light sabre, we are dealing with a rectangular prism instead of a cube, but the basic concept is the same.

At any rate, looking back at the singleAxisBillboard method, the "front" of the object should be its +z axis (I think that's opposite of a normal billboard, but I'm too lazy to check right now).