Author Topic: Lightsaber Test  (Read 8367 times)

Offline paulscode

  • double
  • *****
  • Posts: 863
    • View Profile
    • PaulsCode.Com
Re: Lightsaber Test
« Reply #15 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

Offline AGP

  • quad
  • ******
  • Posts: 1726
    • View Profile
Re: Lightsaber Test
« Reply #16 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?

Offline paulscode

  • double
  • *****
  • Posts: 863
    • View Profile
    • PaulsCode.Com
Re: Lightsaber Test
« Reply #17 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).  The way I did it for this example was with quads at opposite sides of an immaginary cube.

I also talked about this method in the context of a vehicle's headlights, in this thread.


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).