/**
* Copyright 2012 Jason Sorensen (sorensenj@smert.net)
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package net.smert.frameworkgl.opengl.camera;
import net.smert.frameworkgl.Fw;
import net.smert.frameworkgl.math.MathHelper;
import net.smert.frameworkgl.math.Matrix3f;
import net.smert.frameworkgl.math.Matrix4f;
import net.smert.frameworkgl.math.Ray;
import net.smert.frameworkgl.math.Vector3f;
/**
*
* @author Jason Sorensen <sorensenj@smert.net>
*/
public class Camera {
public final static float MAX_SAFE_PITCH = 90f;
public final static float MAX_SAFE_ROLL = 30f;
private boolean invert;
private float aspectRatio;
private float fieldOfView;
private float totalHeading;
private float totalPitch;
private float totalRoll;
private float zFar;
private float zNear;
private AbstractFrustumCulling frustumCulling;
private final Matrix3f movementMatrix;
private final Matrix3f rotationMatrix;
private final Matrix3f tempMatrix;
private final Matrix4f inverseProjectionViewMatrix;
private final Matrix4f projectionMatrix;
private final Matrix4f projectionViewMatrix;
private final Matrix4f viewMatrix;
private final Vector3f position;
private final Vector3f viewDirection;
public Camera() {
invert = false;
aspectRatio = 0f;
fieldOfView = 0f;
totalHeading = 0f;
totalPitch = 0f;
totalRoll = 0f;
zFar = 0f;
zNear = 0f;
movementMatrix = new Matrix3f();
rotationMatrix = new Matrix3f();
tempMatrix = new Matrix3f();
inverseProjectionViewMatrix = new Matrix4f();
projectionMatrix = new Matrix4f();
projectionViewMatrix = new Matrix4f();
viewMatrix = new Matrix4f();
position = new Vector3f();
viewDirection = new Vector3f();
}
private void updateRotationMatrix() {
rotationMatrix.orthonormalize();
viewDirection.setInvert(rotationMatrix.getZAxis());
totalHeading = rotationMatrix.getHeading();
totalPitch = rotationMatrix.getPitch();
totalRoll = rotationMatrix.getRoll();
}
public float getAspectRatio() {
return aspectRatio;
}
public float getFieldOfView() {
return fieldOfView;
}
public float getHeading() {
return totalHeading;
}
public float getPitch() {
return totalPitch;
}
public float getRoll() {
return totalRoll;
}
public float getZFar() {
return zFar;
}
public float getZNear() {
return zNear;
}
public AbstractFrustumCulling getFrustumCulling() {
return frustumCulling;
}
public void setFrustumCulling(AbstractFrustumCulling frustumCulling) {
this.frustumCulling = frustumCulling;
}
public Matrix3f getMovementMatrix() {
return movementMatrix;
}
public Matrix3f getRotationMatrix() {
return rotationMatrix;
}
public Matrix4f getInverseProjectionViewMatrix() {
return inverseProjectionViewMatrix;
}
public Matrix4f getProjectionMatrix() {
return projectionMatrix;
}
public Matrix4f getProjectionViewMatrix() {
return projectionViewMatrix;
}
public Matrix4f getViewMatrix() {
return viewMatrix;
}
public Vector3f getPosition() {
return position;
}
public Ray getPickRay(Ray ray, float screenX, float screenY, float viewportX, float viewportY, float viewportWidth,
float viewportHeight) {
ray.setOrigin(screenX, screenY, 0f);
ray.setDirection(screenX, screenY, 1f);
unproject(ray.getOrigin(), viewportX, viewportY, viewportWidth, viewportHeight);
unproject(ray.getDirection(), viewportX, viewportY, viewportWidth, viewportHeight);
ray.getDirection().subtract(ray.getOrigin()).normalize();
return ray;
}
public Vector3f getViewDirection() {
return viewDirection;
}
public boolean isInvert() {
return invert;
}
public void setInvert(boolean invert) {
this.invert = invert;
}
public void lookAt(Vector3f position, Vector3f target, Vector3f up) {
this.position.set(position);
rotationMatrix.orthonormalize(position, target, up);
viewDirection.setInvert(rotationMatrix.getZAxis());
totalHeading = rotationMatrix.getHeading();
totalPitch = rotationMatrix.getPitch();
totalRoll = rotationMatrix.getRoll();
}
public void move(float dx, float dy, float dz) {
position.add(dx, dy, dz);
}
public void moveForward(float dx, float dy, float dz) {
float zDotUp = viewDirection.dot(Vector3f.WORLD_Y_AXIS);
if ((zDotUp < -MathHelper.TOLERANCE_DOT_PRODUCT_PARALLEL) || (zDotUp > MathHelper.TOLERANCE_DOT_PRODUCT_PARALLEL)) {
movementMatrix.getZAxis().set(Vector3f.WORLD_Y_AXIS).cross(rotationMatrix.getXAxis()).normalize();
movementMatrix.getXAxis().set(movementMatrix.getZAxis()).cross(Vector3f.WORLD_Y_AXIS).normalize();
movementMatrix.getYAxis().set(movementMatrix.getXAxis()).cross(movementMatrix.getZAxis()).normalize();
} else {
movementMatrix.getZAxis().set(viewDirection).setY(0).normalize();
movementMatrix.getXAxis().set(movementMatrix.getZAxis()).cross(Vector3f.WORLD_Y_AXIS).normalize();
movementMatrix.getYAxis().set(movementMatrix.getXAxis()).cross(movementMatrix.getZAxis()).normalize();
}
position.addScaled(movementMatrix.getXAxis(), dx);
position.addScaled(movementMatrix.getYAxis(), dy);
position.addScaled(movementMatrix.getZAxis(), dz);
}
public void rotate(float pitch, float heading, float roll) {
if (invert) {
pitch = -pitch;
}
totalPitch += pitch;
if (totalPitch > MAX_SAFE_PITCH) {
pitch = MAX_SAFE_PITCH - (totalPitch - pitch);
}
if (totalPitch < -MAX_SAFE_PITCH) {
pitch = -MAX_SAFE_PITCH - (totalPitch - pitch);
}
totalRoll += roll;
if (totalRoll > MAX_SAFE_ROLL) {
roll = MAX_SAFE_ROLL - (totalRoll - roll);
}
if (totalRoll < -MAX_SAFE_ROLL) {
roll = -MAX_SAFE_ROLL - (totalRoll - roll);
}
if (heading != 0f) {
tempMatrix.fromAxisAngle(Vector3f.WORLD_Y_AXIS, heading);
tempMatrix.multiplyOut(rotationMatrix.getXAxis(), rotationMatrix.getXAxis());
tempMatrix.multiplyOut(rotationMatrix.getZAxis(), rotationMatrix.getZAxis());
}
if (roll != 0f) {
tempMatrix.fromAxisAngle(rotationMatrix.getZAxis(), roll);
tempMatrix.multiplyOut(rotationMatrix.getYAxis(), rotationMatrix.getYAxis());
tempMatrix.multiplyOut(rotationMatrix.getXAxis(), rotationMatrix.getXAxis());
}
if (pitch != 0f) {
tempMatrix.fromAxisAngle(rotationMatrix.getXAxis(), pitch);
tempMatrix.multiplyOut(rotationMatrix.getYAxis(), rotationMatrix.getYAxis());
tempMatrix.multiplyOut(rotationMatrix.getZAxis(), rotationMatrix.getZAxis());
}
updateRotationMatrix();
}
public void resetRotation() {
rotationMatrix.setAxes(Vector3f.WORLD_X_AXIS, Vector3f.WORLD_Y_AXIS, Vector3f.WORLD_Z_AXIS);
updateRotationMatrix();
}
public void setFrustum(float left, float right, float bottom, float top, float zNear, float zFar) {
float invDeltaY = 1f / (top - bottom);
float h = 2f * zNear * invDeltaY;
aspectRatio = (right - left) / (top - bottom);
fieldOfView = MathHelper.ArcTan(1f / h) / MathHelper.PI_OVER_360;
this.zFar = zFar;
this.zNear = zNear;
projectionMatrix.setFrustum(left, right, bottom, top, zNear, zFar);
}
public void setOrthogonalProjection(float left, float right, float bottom, float top, float zNear, float zFar) {
float invDeltaY = 1f / (top - bottom);
float h = 2f * invDeltaY;
aspectRatio = 1f;
fieldOfView = MathHelper.ArcTan(1f / h) / MathHelper.PI_OVER_360;
this.zFar = zFar;
this.zNear = zNear;
projectionMatrix.setOrthogonal(left, right, bottom, top, zNear, zFar);
}
public void setPerspectiveProjection(float fieldOfViewY, float aspectRatio, float zNear, float zFar) {
this.aspectRatio = aspectRatio;
fieldOfView = fieldOfViewY;
this.zFar = zFar;
this.zNear = zNear;
projectionMatrix.setPerspective(fieldOfViewY, aspectRatio, zNear, zFar);
}
public Vector3f unproject(Vector3f windowCoords, float viewportX, float viewportY, float viewportWidth, float viewportHeight) {
// Map x and y from window coordinates
float x = (windowCoords.getX() - viewportX) / viewportWidth;
float y = ((Fw.config.getCurrentHeight() - windowCoords.getY()) - viewportY) / viewportHeight;
float z = windowCoords.getZ();
// Map to range -1 to 1
x = x * 2f - 1f;
y = y * 2f - 1f;
z = z * 2f - 1f;
windowCoords.set(x, y, z);
// Translate back into world coordinates
inverseProjectionViewMatrix.multiplyProjectionOut(windowCoords, windowCoords);
return windowCoords;
}
public void update() {
viewMatrix.setInverse(rotationMatrix, position);
projectionMatrix.projectionMultiplyViewOut(viewMatrix, projectionViewMatrix);
inverseProjectionViewMatrix.setInverse(projectionViewMatrix);
}
public void updatePlanes() {
frustumCulling.updatePlanes(projectionMatrix, viewMatrix);
}
}