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:
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