package net.hvidtfeldts.fragapi; import java.nio.FloatBuffer; import javax.media.opengl.GL2ES2; import javax.media.opengl.GLException; import javax.media.opengl.GLUniformData; import net.hvidtfeldts.utils.Logger; import com.jogamp.opengl.util.PMVMatrix; import com.jogamp.opengl.util.glsl.ShaderState; public class Camera { private static Camera instance; private final float[] right = new float[] { 0.7f, 0, -0.7f }; private float[] up = new float[] { 0.4f, -0.8f, 0.4f }; private float[] forward = new float[] { 0.57f, 0.57f, 0.57f }; private final float[] pos = new float[] { 0, 0, -6 }; private PMVMatrix raytracerMatrixStack; private GLUniformData raytracerPmvMatrixUniform; private final FloatBuffer farNear = FloatBuffer.allocate(2); private GLUniformData fovYScaleUniform; private float fovY = 45.0f; private GLUniformData aspectUniform; private GLUniformData farNearUniform; private float aspect; private float zNear; private float zFar; public Camera() { if (instance != null) { throw new IllegalStateException("Only one camera allowed"); } instance = this; } void setupRaytracerMatrixStack(final GL2ES2 gl) { raytracerMatrixStack = new PMVMatrix(); raytracerMatrixStack.glMatrixMode(PMVMatrix.GL_PROJECTION); raytracerMatrixStack.glLoadIdentity(); raytracerMatrixStack.glMatrixMode(PMVMatrix.GL_MODELVIEW); raytracerMatrixStack.glLoadIdentity(); raytracerPmvMatrixUniform = new GLUniformData("pmvMatrix", 4, 4, raytracerMatrixStack.glGetPMvMvitMatrixf()); // P, Mv fovYScaleUniform = new GLUniformData("fov_y_scale", 1); aspectUniform = new GLUniformData("aspect", 1); float fovYScale = (float) Math.tan(Math.PI * fovY / 360.0); fovYScaleUniform.setData(fovYScale); aspectUniform.setData(1.0f); farNearUniform = new GLUniformData("farNear", 2, farNear); // raytracerShaderState.ownUniform(raytracerPmvMatrixUniform); // raytracerShaderState.uniform(gl, raytracerPmvMatrixUniform); } public float[] getRight() { return right; } public float[] getUp() { return up; } public float[] getPosInCameraCoords() { return pos; } public float[] getPosInWorldCoords() { // [ r.x r.y r.z q.x ] // [ u.x u.y u.z q.y ] // [ -f.x -f.y -f.z q.z ] // [ 0 0 0 1 ] // eye = -(modelView[3].xyz)*mat3(modelView); return new float[] { -pos[0] * right[0] - pos[1] * up[0] + pos[2] * forward[0], -pos[0] * right[1] - pos[1] * up[1] + pos[2] * forward[1], -pos[0] * right[2] - pos[1] * up[2] + pos[2] * forward[2], }; } public void setUpDir(float[] up) { this.up = up; } public void setForward(float[] forward) { this.forward = forward; } public void rotateAboutUp(float angle) { float[] rot = rotation(angle, up); forward = multiply(rot, forward); ortogonalize(); } public void rotateAboutForward(float angle) { float[] rot = rotation(angle, forward); up = multiply(rot, up); ortogonalize(); } public void rotateAboutRight(float angle) { float[] rot = rotation(angle, right); up = multiply(rot, up); forward = multiply(rot, forward); ortogonalize(); } public static float[] multiply(float[] m, float[] v) { return new float[] { m[0] * v[0] + m[1 + 0] * v[1] + m[2 + 0] * v[2] + m[3], m[0 + 4] * v[0] + m[1 + 4] * v[1] + m[2 + 4] * v[2] + m[3 + 4], m[0 + 8] * v[0] + m[1 + 8] * v[1] + m[2 + 8] * v[2] + m[3 + 8], m[0 + 12] * v[0] + m[1 + 12] * v[1] + m[2 + 12] * v[2] + m[3 + 12], }; } public static float[] multiply2(float[] m, float[] v) { return new float[] { m[0] * v[0] + m[0 + 4] * v[1] + m[0 + 8] * v[2] + m[0 + 12], m[1] * v[0] + m[1 + 4] * v[1] + m[1 + 8] * v[2] + m[1 + 12], m[2] * v[0] + m[2 + 4] * v[1] + m[2 + 8] * v[2] + m[2 + 12], m[3] * v[0] + m[3 + 4] * v[1] + m[3 + 8] * v[2] + m[3 + 12], }; } public static float[] rotation(double angle, float[] v) { float[] m = new float[16]; float c = (float) Math.cos(angle); float s = (float) Math.sin(angle); float omc = 1 - c; float xs = v[0] * s; float ys = v[1] * s; float zs = v[2] * s; float xyomc = v[0] * v[1] * omc; float xzomc = v[0] * v[2] * omc; float yzomc = v[1] * v[2] * omc; m[0] = v[0] * v[0] * omc + c; m[1] = xyomc + zs; m[2] = xzomc - ys; m[3] = 0; m[4] = xyomc - zs; m[5] = v[1] * v[1] * omc + c; m[6] = yzomc + xs; m[7] = 0; m[8] = xzomc + ys; m[9] = yzomc - xs; m[10] = v[2] * v[2] * omc + c; m[11] = 0; m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1; return m; } public void ortogonalize() { // LxU = F; // UxF = L normalize(forward); normalize(up); up = subtract(up, mul(dot(forward, up), forward)); normalize(up); cross(up, forward, right); negate(right); // Logger.log("Up " + toString(up) + " Forward: " + toString(forward) + // " Left: " + toString(left)); } public void negate(float[] v) { v[0] = -v[0]; v[1] = -v[1]; v[2] = -v[2]; } public float dot(float[] a, float[] b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } public float[] mul(float a, float[] b) { return new float[] { a * b[0], a * b[1], a * b[2] }; } public float[] subtract(float[] a, float[] b) { return new float[] { a[0] - b[0], a[1] - b[1], a[2] - b[2] }; } private void normalize(float[] a) { float l = (float) Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]); a[0] /= l; a[1] /= l; a[2] /= l; } private void cross(float[] a, float[] b, float[] out) { out[0] = a[1] * b[2] - a[2] * b[1]; out[1] = -a[0] * b[2] + a[2] * b[0]; out[2] = a[0] * b[1] - a[1] * b[0]; } public void updateMatrix() { PMVMatrix m = raytracerMatrixStack; m.glMatrixMode(PMVMatrix.GL_MODELVIEW); m.glLoadIdentity(); ortogonalize(); // For an idea of these, see: http://3dengine.org/Right-up-back_from_modelview // OpenGL is column-major based. // So these values: float[] values = new float[] { right[0], up[0], -forward[0], 0, right[1], up[1], -forward[1], 0, right[2], up[2], -forward[2], 0, pos[0], pos[1], pos[2], 1, }; // Correspond to this matrix: // [ r.x r.y r.z p.x ] // [ u.x u.y u.z p.y ] // [ -f.x -f.y -f.z p.z ] // [ 0 0 0 1 ] m.glLoadMatrixf(values, 0); } public void move(float x, float y, float z) { pos[0] += x; pos[1] += y; pos[2] += z; } public float[] getForward() { return forward; } public void setUniforms(GL2ES2 gl, ShaderState ss) { updateMatrix(); raytracerMatrixStack.update(); ss.uniform(gl, raytracerPmvMatrixUniform); ss.uniform(gl, fovYScaleUniform); ss.uniform(gl, aspectUniform); ss.uniform(gl, farNearUniform); } public void zoom(float f) { this.fovY *= 1.0 + f; if (fovY > 90) { fovY = 90; } if (fovY < 4) { fovY = 4; } updateFOV(); Logger.log(fovY); } private void updateFOV() { // compute projection parameters 'normal' perspective aspectUniform.setData(aspect); farNear.put(0, zFar); farNear.put(1, zNear); float fovYScale = (float) Math.tan(Math.PI * fovY / 360.0); fovYScaleUniform.setData(fovYScale); Logger.log("FOV: " + fovY + " aspect:" + aspect); } public void reshape(GL2ES2 gl, int x, int y, int width, int height) { try { gl.setSwapInterval(1); aspect = (float) width / (float) height; zNear = 0.5f; zFar = 100f; updateFOV(); } catch (GLException e) { Logger.warn(e); } } public static Camera getInstance() { return instance; } }