package com.bitwaffle.spaceguts.entities;
import javax.vecmath.Quat4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Quaternion;
import org.lwjgl.util.vector.Vector3f;
import com.bitwaffle.spaceguts.graphics.render.Render3D;
import com.bitwaffle.spaceguts.input.KeyBindings;
import com.bitwaffle.spaceguts.input.MouseManager;
import com.bitwaffle.spaceguts.physics.Builder;
import com.bitwaffle.spaceguts.physics.CollisionTypes;
import com.bitwaffle.spaceguts.physics.Physics;
import com.bitwaffle.spaceguts.util.DisplayHelper;
import com.bitwaffle.spaceguts.util.QuaternionHelper;
import com.bitwaffle.spaceguts.util.console.Console;
import com.bitwaffle.spaceout.resources.Textures;
import com.bulletphysics.collision.dispatch.CollisionObject;
import com.bulletphysics.collision.dispatch.CollisionWorld.ClosestRayResultCallback;
import com.bulletphysics.collision.shapes.SphereShape;
import com.bulletphysics.linearmath.Transform;
/**
* A camera that describes how the scene is being looked at and can move around inside of the physics world
* There should only be one camera at a time, Entities.camera
*
* @author TranquilMarmot
*/
public class Camera extends DynamicEntity {
/** initial values for when the camera is created */
private static final float INIT_ZOOM = 12.0f;
private static final float INIT_XOFFSET = 0.0f;
private static final float INIT_YOFFSET = -2.7F;
/** the entity that the camera is following */
public DynamicEntity following;
/** handles special interactions with the game world */
public Builder builder;
/** how fast the camera moves in free mode */
public float speed = 350.0f;
private float maxSpeed = 10000.0f, minSpeed = 0.01f;
/** Offset along Y axis */
public float yOffset;
/** Offset along X axis */
public float xOffset;
/** used to preserve offset when switching modes */
private float oldYOffset, oldXOffset, oldMinZoom;
/** zoom level */
public float zoom;
/** maximum and minimum zoom level */
private float maxZoom = 3000.0f, minZoom = 10.0f;
/** changes how fast the zoom is based on the current zoom distance */
float zoomSensitivity = 10;
private float oldZoom;
/** for switching into/out of build mode */
private Vector3f oldLocation = new Vector3f(0.0f, 0.0f, 0.0f);
/**
* The camera has three main modes:
* Normal Mode - vanityMode = false, freeMode = false
* Camera simply follows behind whatever it's attached to
* Vanity Mode - vanityMode = true, freeMode = false
* Camera spins around whatever it's following
* Free Mode - vanityMode = false, freeMode = true
* Camera can move about freely on its own and zoom out real far
*/
// FIXME there's a more clever way to do this- it could easily be a short with each bit representing a mode- 00 would be normal, 01 would be vanity, 10 would be free, and 11 would be build
public boolean vanityMode = false;
public boolean freeMode = false;
public boolean buildMode = false;
/** how fast the camera rolls */
float rollSpeed = 100.0f;
// FIXME should the crosshair be its own class?
public int crosshairWidth = 8, crosshairHeight = 8;
public int handWidth = 15, handHeight = 17;
private int currentCrosshairWidth = crosshairWidth, currentCrosshairHeight = crosshairHeight;
private Textures currentCrosshair = Textures.CROSSHAIR;
public Vector3f defaultCrosshairColor = new Vector3f(1.0f, 1.0f, 1.0f);
/**
* Camera constructor
*/
public Camera() {
super(new Vector3f(0.0f, 0.0f, 0.0f), new Quaternion(0.0f, 0.0f, 0.0f, 1.0f), new SphereShape(10.0f), 0.0001f, 0.01f, CollisionTypes.NOTHING, CollisionTypes.EVERYTHING);
this.zoom = INIT_ZOOM;
this.yOffset = INIT_YOFFSET;
this.xOffset = INIT_XOFFSET;
rotation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
this.type = "camera";
builder = new Builder(this);
this.rigidBody.setActivationState(CollisionObject.DISABLE_DEACTIVATION);
}
/**
* Update the camera. This handles following other things, mode switches etc.
*/
public void update(float timeStep) {
this.rigidBody.applyDamping(100.0f);
// get the rigid body's tranform
Transform trans = new Transform();
this.rigidBody.getWorldTransform(trans);
// set location to be with rigid body
this.location.set(trans.origin.x, trans.origin.y, trans.origin.z);
// set rotation to be with rigid body
Quat4f rot = new Quat4f();
trans.getRotation(rot);
this.rotation.set(rot.x, rot.y, rot.z, rot.w);
if(buildMode)
builder.update(timeStep);
// if we're not following anything, we're in free mode
if (following == null) {
vanityMode = false;
freeMode = true;
}
// perform zoom logic
mousewheelLogic();
// check for any key presses
checkForModeSwitch();
// only look around if the builder isn't rotating something
if(!builder.rightGrabbed)
lookLogic(timeStep, trans);
// if we're not in free mode, move the camera to be behind whatever it's
// following
if (!freeMode) {
this.location.set(following.location);
trans.origin.set(location.x, location.y, location.z);
} else if (freeMode && !vanityMode) {
// else do logic for moving around in free mode
freeMode(timeStep, trans);
}
this.rigidBody.setWorldTransform(trans);
}
/**
* Changes where the camera is looking based on mouse movement/keyboard input
* @param timeStep Time since last update
* @param trans Transform to modify
*/
private void lookLogic(float timeStep, Transform trans){
// roll left/right
float dz = 0.0f;
boolean rollRight = KeyBindings.CONTROL_ROLL_RIGHT.isPressed();
boolean rollLeft = KeyBindings.CONTROL_ROLL_LEFT.isPressed();
if (rollRight)
dz = timeStep * -rollSpeed;
if (rollLeft)
dz = timeStep * rollSpeed;
// apply any rotation changes
this.rotation = QuaternionHelper.rotate(this.rotation, new Vector3f(MouseManager.dy, MouseManager.dx, dz));
// update rigid body transform
trans.setRotation(new Quat4f(rotation.x, rotation.y, rotation.z, rotation.w));
}
/**
* Checks if the mode change button has been pressed and changes states
* accordingly
*/
private void checkForModeSwitch() {
// check for mode key
if(!Console.consoleOn){
if(!buildMode){
// swap the camera mode
if (KeyBindings.SYS_CAMERA_MODE.pressedOnce()) {
// three states: normal (vanity false free false), vanity (vanity true
// free false), free (vanity false free true)
if (!vanityMode && !freeMode) {
vanityMode = true;
oldYOffset = yOffset;
oldXOffset = xOffset;
//oldMinZoom = minZoom;
yOffset = 0;
xOffset = 0;
//minZoom = 0;
} else if (vanityMode && !freeMode) {
vanityMode = false;
freeMode = true;
oldMinZoom = minZoom;
minZoom = 0;
} else if (freeMode && !vanityMode) {
freeMode = false;
rotation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
yOffset = oldYOffset;
xOffset = oldXOffset;
minZoom = oldMinZoom;
}
}
}
// go into/out of build mode
if(KeyBindings.SYS_BUILD_MODE.pressedOnce()){
buildMode = !buildMode;
// going in to build mode
if(buildMode){
// save the zoom and set it to 0
oldZoom = zoom;
zoom = 0.0f;
// go into free mode
vanityMode = false;
freeMode = true;
// only save variables if they aren't 0 (otherwise they don't need to be changed)
if(yOffset != 0){
oldYOffset = yOffset;
yOffset = 0;
}
if(xOffset != 0){
oldXOffset = xOffset;
xOffset = 0;
}
if(minZoom > 0){
oldMinZoom = minZoom;
minZoom = 0;
}
// save the camera's location and move it back a bit
oldLocation.set(location);
Vector3f.add(this.location, QuaternionHelper.rotateVectorByQuaternion(new Vector3f(0.0f, 0.0f, -50.0f), this.rotation), this.location);
Transform trans = new Transform();
this.rigidBody.getWorldTransform(trans);
trans.origin.set(location.x, location.y, location.z);
this.rigidBody.setWorldTransform(trans);
} else{
// re-enter normal mode
vanityMode = false;
freeMode = false;
// reset variables to what they once were
yOffset = oldYOffset;
xOffset = oldXOffset;
zoom = oldZoom;
minZoom = oldMinZoom;
// check for zoom bounds
if(zoom < minZoom)
zoom = minZoom;
// re-set the location
location.set(oldLocation);
Transform trans = new Transform();
this.rigidBody.getWorldTransform(trans);
trans.origin.set(location.x, location.y, location.z);
this.rigidBody.setWorldTransform(trans);
}
}
} else{
// so that they don't get pressed if they're being typed to the console
KeyBindings.SYS_CAMERA_MODE.pressedOnce();
KeyBindings.SYS_BUILD_MODE.pressedOnce();
}
}
/**
* Zooms in and out or changes speed based on the current camera mode and how much the mouse wheel has been moved
*/
private void mousewheelLogic() {
// only zoom when the console isn't on (otherwise the mouse wheel
// controls console scrolling)
if (!Console.consoleOn) {
if (MouseManager.wheel != 0) {
if(buildMode && !builder.leftGrabbed && !builder.rightGrabbed){
speed += (speed * zoomSensitivity / MouseManager.wheel);
// keep zoom in bounds
if (speed < minSpeed)
speed = minSpeed;
else if (speed > maxSpeed)
speed = maxSpeed;
}
else{
zoom -= (zoom * zoomSensitivity / MouseManager.wheel);
// keep zoom in bounds
if (zoom < minZoom)
zoom = minZoom;
else if (zoom > maxZoom)
zoom = maxZoom;
}
}
}
}
/**
* Performs any free mode movement
*
* @param timeStep
* Amount of time passed since last update
* @param trans Transform to modify
*/
private void freeMode(float timeStep, Transform trans) {
timeStep *= 100;
// check for forward and backward movement
boolean forward = KeyBindings.CONTROL_FORWARD.isPressed();
boolean backward = KeyBindings.CONTROL_BACKWARD.isPressed();
float dx = 0, dy = 0, dz = 0;
// control forward and backward movement
if(!(forward && backward)){
if (forward)
dz = speed * timeStep;
else if (backward)
dz = -speed * timeStep;
}
// check for left and right movement
boolean left = KeyBindings.CONTROL_LEFT.isPressed();
boolean right = KeyBindings.CONTROL_RIGHT.isPressed();
// control strafing left and right
if(!(left && right)){
if (left)
dx = speed * timeStep;
else if (right)
dx = -speed * timeStep;
}
// handle going up/down
boolean up = KeyBindings.CONTROL_ASCEND.isPressed();
boolean down = KeyBindings.CONTROL_DESCEND.isPressed();
if(!(up && down)){
if (up)
dy = -speed * timeStep;
else if (down)
dy = speed * timeStep;
}
Vector3f veloc = QuaternionHelper.rotateVectorByQuaternion(new Vector3f(dx, dy, dz), this.rotation);
this.rigidBody.setLinearVelocity(new javax.vecmath.Vector3f(veloc.x, veloc.y, veloc.z));
trans.setRotation(new Quat4f(rotation.x, rotation.y, rotation.z, rotation.w));
}
/**
* Performs a ray test at the center of the camera into the depths of space.
* @return A RayResultCallback that says whether or not something was hit
*/
public ClosestRayResultCallback rayTestAtCenter(){
// rotate the camera's offsets by its current rotation to get the offsets on the right plane
Vector3f offsets = new Vector3f(xOffset, yOffset, -zoom);
Vector3f actualLocation = QuaternionHelper.rotateVectorByQuaternion(offsets, rotation);
// add location to rotated offsets to get actual camera position
Vector3f.add(location, actualLocation, actualLocation);
// create a vector far at as out we can see, then rotate it by the camera's current rotation to get it on the right plane
Vector3f endOfTheGalaxy = QuaternionHelper.rotateVectorByQuaternion(new Vector3f(0.0f, 0.0f, Render3D.drawDistance), rotation);
Vector3f endAdd = new Vector3f();
// add the vector at the end to the camera's location to get a straight line going to the end
Vector3f.add(actualLocation, endOfTheGalaxy, endAdd);
javax.vecmath.Vector3f start = new javax.vecmath.Vector3f(actualLocation.x, actualLocation.y, actualLocation.z);
javax.vecmath.Vector3f end = new javax.vecmath.Vector3f(endAdd.x, endAdd.y, endAdd.z);
// perform test
ClosestRayResultCallback callback = new ClosestRayResultCallback(start, end);
Physics.dynamicsWorld.rayTest(start, end, callback);
return callback;
}
/**
* @return The camera's position with its xOffset, yOffset and zoom taken into consideration
*/
public Vector3f getLocationWithOffset(){
float x = location.x + xOffset;
float y = location.y + yOffset;
float z = location.z - zoom;
return new Vector3f(x, y, z);
}
public void draw2D(){
GL11.glColor3f(defaultCrosshairColor.x, defaultCrosshairColor.y, defaultCrosshairColor.z);
// if we're not in build mode, use the crosshair
if(!buildMode){
currentCrosshairWidth = crosshairWidth;
currentCrosshairHeight = crosshairHeight;
currentCrosshair = Textures.CROSSHAIR;
}else{
// else if in build mode and grabbed, use grabbed image
if(builder.leftGrabbed || builder.rightGrabbed){
currentCrosshairWidth = handWidth;
currentCrosshairHeight = handHeight;
currentCrosshair = Textures.BUILDER_GRABBED;
// else if not grabbed and we're looking at something, use the open image
}else if(builder.lookingAt != null){
currentCrosshairWidth = handWidth;
currentCrosshairHeight = handHeight;
currentCrosshair = Textures.BUILDER_OPEN;
// else just the crosshair
}else{
currentCrosshairWidth = crosshairWidth;
currentCrosshairHeight = crosshairHeight;
currentCrosshair = Textures.CROSSHAIR;
}
}
currentCrosshair.texture().bind();
// draw the crosshair
GL11.glBegin(GL11.GL_QUADS);
GL11.glTexCoord2f(0, 0);
GL11.glVertex2f((DisplayHelper.windowWidth / 2.0f) - currentCrosshairWidth, (DisplayHelper.windowHeight / 2.0f) + currentCrosshairHeight);
GL11.glTexCoord2f(currentCrosshair.texture().getWidth(), 0);
GL11.glVertex2f((DisplayHelper.windowWidth / 2.0f) + currentCrosshairWidth, (DisplayHelper.windowHeight / 2.0f) + currentCrosshairHeight);
GL11.glTexCoord2f(currentCrosshair.texture().getWidth(), currentCrosshair.texture().getHeight());
GL11.glVertex2f((DisplayHelper.windowWidth / 2.0f) + currentCrosshairWidth, (DisplayHelper.windowHeight / 2.0f) - currentCrosshairHeight);
GL11.glTexCoord2f(0, currentCrosshair.texture().getHeight());
GL11.glVertex2f((DisplayHelper.windowWidth / 2.0f) - currentCrosshairWidth, (DisplayHelper.windowHeight / 2.0f) - currentCrosshairHeight);
GL11.glEnd();
GL11.glColor3f(1.0f, 1.0f, 1.0f);
}
@Override
public void draw() {
// camera dont need no drawin camera better than that
}
@Override
public void cleanup() {
// camera is clean, man
}
}