/*******************************************************************************
* Copyright (C) 2013 JMaNGOS <http://jmangos.org/>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package org.jmangos.tools.openGL;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static org.lwjgl.util.glu.GLU.gluLookAt;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Quaternion;
import org.lwjgl.util.vector.Vector3f;
import org.lwjgl.util.vector.Vector4f;
/**
* Camera class.
*/
public final class Camera {
/** A zero vector. **/
private final Vector3f vZero = new Vector3f(0.0f, 0.0f, 0.0f);
/** The view vector. **/
private final Vector3f vView = new Vector3f(0.0f, 1.0f, 0.5f);
/** Up vector (rarely changed). **/
private final Vector3f vUp = new Vector3f(0.0f, 0.0f, 1.0f);
/** Strafe vector. **/
private Vector3f m_vStrafe = new Vector3f();
/** Position vector. **/
private Vector3f m_vPosition = this.vZero;
/** View Vector. **/
private Vector3f m_vView = this.vView;
/** up vector. **/
private Vector3f m_vUpVector = this.vUp;
/** View by mouse ? Default = false. **/
private boolean viewByMouse = false;
/** Is mouse inverted ? Default = false. **/
private boolean mouseInverted = false;
/** Mouse sensibility. **/
private float mouseSensibility = 750.0f;
/** Current mouse rotation on X axis. **/
private float currentRotX = 0.0f;
/** Fixed on X axis ? **/
private boolean fixedX = false;
/** Fixed on Y axis ? **/
private boolean fixedY = false;
/** Fixed on Z axis ? **/
private boolean fixedZ = false;
private float radius;
private Vector3f pr; /*
* Point to rotate
* about
*/
private float focallength; /*
* Focal Length along
* vd
*/
private float aperture; /* Camera aperture */
private float eyesep; /* Eye separation */
/** **/
public final static int X_AXIS = 0;
/** **/
public final static int Y_AXIS = 1;
/** **/
public final static int Z_AXIS = 2;
/**
* Camera constructor.
*/
public Camera() {
}
/**
* Camera constructor.
*
* @param vbm
* View by mouse statement.
*/
public Camera(final boolean vbm) {
this.viewByMouse = vbm;
}
/**
* Change the view by mouse statement.
*
* @param vbm
* View by mouse statement.
*/
public final void setViewByMouse(final boolean vbm) {
this.viewByMouse = vbm;
}
public final boolean getViewByMouse() {
return this.viewByMouse;
}
/**
* Change mouse Y movement type.
*
* @param inverted
* Mouse Inverted ?
*/
public final void setMouseInverted(final boolean inverted) {
this.mouseInverted = inverted;
}
public Vector3f getVview() {
return this.m_vView;
}
/**
* Change mouse sensibility.
*
* @param s
* Mouse sensibility (high = less sensible).
*/
public final void setMouseSensibility(final float s) {
this.mouseSensibility = s;
}
public final void setVview(final float y) {
this.m_vPosition.y = 0;
}
/**
* Set camera fixed to an axis.
*
* @param axis
* <code>Camera.X_AXIS</code> or <code>Camera.Y_AXIS</code> or
* <code>Camera.Z_AXIS</code>.
*/
public final void setFixedAxis(final int axis) {
if (axis == X_AXIS) {
this.fixedX = true;
} else if (axis == Y_AXIS) {
this.fixedY = true;
} else if (axis == Z_AXIS) {
this.fixedZ = true;
}
}
/**
* Change the camera position.
*
* @param positionX
* Position X.
* @param positionY
* Position Y.
* @param positionZ
* Position Z.
* @param viewX
* View X.
* @param viewY
* View Y.
* @param viewZ
* View Z.
* @param upVectorX
* Up vector X.
* @param upVectorY
* Up vector Y.
* @param upVectorZ
* Up vector Z.
*/
public final void setPosition(final float positionX, final float positionY,
final float positionZ, final float viewX, final float viewY, final float viewZ,
final float upVectorX, final float upVectorY, final float upVectorZ) {
this.m_vPosition = new Vector3f(positionX, positionY, positionZ);
this.m_vView = new Vector3f(viewX, viewY, viewZ);
this.m_vUpVector = new Vector3f(upVectorX, upVectorY, upVectorZ);
}
public final Vector3f[] getPositionCamera() {
final Vector3f[] position = { this.m_vPosition, this.m_vView, this.m_vUpVector };
return position;
}
public void setPosition(final Vector3f m_vPosition) {
this.m_vPosition.set(m_vPosition);
}
public void setView(final Vector3f m_vView) {
this.m_vView.set(m_vView);
}
public void setUpVector(final Vector3f m_vUpVector) {
this.m_vUpVector.set(m_vUpVector);
}
public final Vector3f getPosition() {
return this.m_vPosition;
}
public final Vector3f getView() {
return this.m_vView;
}
public final Vector3f getUpVector() {
return this.m_vUpVector;
}
/**
* Move the camera (forward if speed is positive).
*
* @param speed
* The camera speed.
*/
public final void move(final float speed) {
Vector3f vVector = new Vector3f();
// Get our view vector (The direction we are facing).
vVector = Vector3f.sub(this.m_vView, this.m_vPosition, vVector);
// That way you don't move faster than you strafe, since the strafe
// vector
// is normalized too.
vVector.normalise();
// Fixed axis ?
if (!this.fixedX) {
this.m_vPosition.x += vVector.x * speed;
this.m_vView.x += vVector.x * speed;
}
if (!this.fixedY) {
this.m_vPosition.y += vVector.y * speed;
this.m_vView.y += vVector.y * speed;
}
if (!this.fixedZ) {
this.m_vPosition.z += vVector.z * speed;
this.m_vView.z += vVector.z * speed;
}
}
/**
* Set the view according to the mouse position.
*/
private final void mouse_view() {
float angleY = 0.0f;
float angle_product = 0.0f;
final Vector3f vAxis;
// Get the direction the mouse moved in, but bring the number down to a
// reasonable amount.
angleY = -(float) (Mouse.getDX()) / this.mouseSensibility;
angle_product = (Mouse.getDY()) / this.mouseSensibility;
// If mouse is inverted, invert rotation on angle_product axis.
if (this.mouseInverted) {
angle_product = -angle_product;
}
// Here we keep track of the current rotation (for up and down) so that
// we can restrict the camera from doing a full 360 loop.
this.currentRotX -= angle_product;
// If the current rotation (in radians) is greater than 1.0, we want to
// cap it.
if (this.currentRotX > 90.0f) {
this.currentRotX = 90.0f;
} else if (this.currentRotX < -90.0f) {
this.currentRotX = -90.0f;
} else {
vAxis =
Vector3f.cross(Vector3f.sub(this.m_vView, this.m_vPosition, null),
this.m_vUpVector, null);
vAxis.normalise();
// Rotate around our perpendicular axis and along the y-axis.
rotateWithQuaternion(angle_product, vAxis.x, vAxis.y, vAxis.z);
rotateWithQuaternion(angleY, 0, 1, 0);
}
}
/**
* This rotates the view around the position using an axis-angle rotation.
*
* @param angle
* Rotation angle.
* @param x
* On X axis.
* @param y
* On Y axis.
* @param z
* On Z axis.
*/
@SuppressWarnings("unused")
private final void rotate(final float angle, final float x, final float y, final float z) {
final Vector3f vNewView = new Vector3f();
final Vector3f vView = new Vector3f();
// Get our view vector (The direction we are facing).
vView.x = this.m_vView.x - this.m_vPosition.x; // This gets the
// direction of the
// X.
vView.y = this.m_vView.y - this.m_vPosition.y; // This gets the
// direction of the
// Y.
vView.z = this.m_vView.z - this.m_vPosition.z; // This gets the
// direction of the
// Z.
// Calculate the sine and cosine of the angle once.
final float cosTheta = (float) cos(angle);
final float sinTheta = (float) sin(angle);
// Find the new x position for the new rotated point.
vNewView.x = (cosTheta + ((1 - cosTheta) * x * x)) * vView.x;
vNewView.x += (((1 - cosTheta) * x * y) - (z * sinTheta)) * vView.y;
vNewView.x += (((1 - cosTheta) * x * z) + (y * sinTheta)) * vView.z;
// Find the new y position for the new rotated point.
vNewView.y = (((1 - cosTheta) * x * y) + (z * sinTheta)) * vView.x;
vNewView.y += (cosTheta + ((1 - cosTheta) * y * y)) * vView.y;
vNewView.y += (((1 - cosTheta) * y * z) - (x * sinTheta)) * vView.z;
// Find the new z position for the new rotated point.
vNewView.z = (((1 - cosTheta) * x * z) - (y * sinTheta)) * vView.x;
vNewView.z += (((1 - cosTheta) * y * z) + (x * sinTheta)) * vView.y;
vNewView.z += (cosTheta + ((1 - cosTheta) * z * z)) * vView.z;
// Now we just add the newly rotated vector to our position to set.
// our new rotated view of our camera.
this.m_vView.x = this.m_vPosition.x + vNewView.x;
this.m_vView.y = this.m_vPosition.y + vNewView.y;
this.m_vView.z = this.m_vPosition.z + vNewView.z;
}
private final void rotateWithQuaternion(final float angleDir, final float xSpeed,
final float ySpeed, final float zSpeed) {
final Quaternion qRotation = new Quaternion();
final Quaternion qView = new Quaternion();
final Quaternion qNewView = new Quaternion();
// Create the rotation quaternion based on the axis we are rotating on.
qRotation.setFromAxisAngle(new Vector4f(xSpeed, ySpeed, zSpeed, angleDir));
// Create the view quaternion. This will be the direction of the view
// and position.
qView.x = this.m_vView.x - this.m_vPosition.x; // This gets the
// direction of the
// X.
qView.y = this.m_vView.y - this.m_vPosition.y; // This gets the
// direction of the
// Y.
qView.z = this.m_vView.z - this.m_vPosition.z; // This gets the
// direction of the
// Z.
qView.w = 0;
// Create the resulting quaternion by multiplying the rotation quat by
// the view quat
// then multiplying that by the conjugate of the rotation quat.
final Quaternion dv = new Quaternion();
final Quaternion nqRotation = new Quaternion();
Quaternion.mul(qRotation, qView, dv);
qRotation.negate(nqRotation);
Quaternion.mul(dv, nqRotation, qNewView);
// Update the view information by adding the position to the resulting
// quaternion.
this.m_vView.x = this.m_vPosition.x + qNewView.x;
this.m_vView.y = this.m_vPosition.y + qNewView.y;
this.m_vView.z = this.m_vPosition.z + qNewView.z;
}
/**
* Strafe the camera (left or right, depending on the speed).
*
* @param speed
* The camera moving speed.
*/
public final void strafe(final float speed) {
// Add the strafe vector to our position.
this.m_vPosition.x += this.m_vStrafe.x * speed;
this.m_vPosition.z += this.m_vStrafe.z * speed;
// Add the strafe vector to our view.
this.m_vView.x += this.m_vStrafe.x * speed;
this.m_vView.z += this.m_vStrafe.z * speed;
}
/**
* Update the camera state.
*/
public final void update() {
// Normalize the strafe vector.
this.m_vStrafe =
Vector3f.cross(Vector3f.sub(this.m_vView, this.m_vPosition, null),
this.m_vUpVector, null);
this.m_vStrafe.normalise();
// View by mouse if enabled.
if (this.viewByMouse) {
mouse_view();
}
}
/**
* Update camera view.
*/
public final void look() {
gluLookAt(this.m_vPosition.x, this.m_vPosition.y, this.m_vPosition.z, this.m_vView.x,
this.m_vView.y, this.m_vView.z, this.m_vUpVector.x, this.m_vUpVector.y,
this.m_vUpVector.z);
}
/**
* @param radius
* the radius to set
*/
public void setRadius(final float radius) {
this.radius = radius;
}
/**
* @return the radius
*/
public float getRadius() {
return this.radius;
}
/**
* @param pr
* the pr to set
*/
public void setPr(final Vector3f pr) {
this.pr = pr;
}
/**
* @return the pr
*/
public Vector3f getPr() {
return this.pr;
}
/**
* @param focallength
* the focallength to set
*/
public void setFocallength(final float focallength) {
this.focallength = focallength;
}
/**
* @return the focallength
*/
public float getFocallength() {
return this.focallength;
}
/**
* @param aperture
* the aperture to set
*/
public void setAperture(final float aperture) {
this.aperture = aperture;
}
/**
* @return the aperture
*/
public float getAperture() {
return this.aperture;
}
/**
* @param eyesep
* the eyesep to set
*/
public void setEyesep(final float eyesep) {
this.eyesep = eyesep;
}
/**
* @return the eyesep
*/
public float getEyesep() {
return this.eyesep;
}
public Ray getRayFromMouse() {
int mousex = 0, mousey = 0;
mousex = Mouse.getX();
mousey = Mouse.getY();
final FloatBuffer projection = BufferUtils.createFloatBuffer(16);
final FloatBuffer modelview = BufferUtils.createFloatBuffer(16);
final IntBuffer viewport = BufferUtils.createIntBuffer(16);
final FloatBuffer position = BufferUtils.createFloatBuffer(3);
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, modelview);
GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, projection);
GL11.glGetInteger(GL11.GL_VIEWPORT, viewport);
GLU.gluUnProject(mousex, mousey, 1, modelview, projection, viewport, position);
final Vector3f near = new Vector3f(position.get(0), position.get(1), position.get(2));
// reuse buffers
// near = eyeOut;
projection.rewind();
modelview.rewind();
viewport.rewind();
position.rewind();
GLU.gluUnProject(mousex, mousey, 0, modelview, projection, viewport, position);
final Vector3f far = new Vector3f(position.get(0), position.get(1), position.get(2));
Vector3f.sub(near, far, this.vView);
this.vView.normalise();
return new Ray(this.m_vPosition, this.vView);
}
}