/* * Copyright 2017 MovingBlocks * * 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 org.terasology.rendering.cameras; import org.terasology.rendering.openvrprovider.OpenVRProvider; import org.lwjgl.opengl.GL11; import org.terasology.config.RenderingConfig; import org.terasology.math.MatrixUtils; import org.terasology.math.geom.Matrix4f; import org.terasology.math.geom.Vector3f; import org.terasology.registry.CoreRegistry; import org.terasology.rendering.world.WorldRenderer; import org.terasology.rendering.world.WorldRenderer.RenderingStage; import org.terasology.world.WorldProvider; import static org.lwjgl.opengl.GL11.GL_PROJECTION; import static org.lwjgl.opengl.GL11.glMatrixMode; /** * Camera which can be used to render stereoscopic images of the scene for VR. */ public class OpenVRStereoCamera extends SubmersibleCamera { private Matrix4f projectionMatrixLeftEye = new Matrix4f(); private Matrix4f projectionMatrixRightEye = new Matrix4f(); private Matrix4f inverseProjectionMatrixLeftEye = new Matrix4f(); private Matrix4f inverseProjectionMatrixRightEye = new Matrix4f(); private Matrix4f inverseViewProjectionMatrixLeftEye = new Matrix4f(); private Matrix4f inverseViewProjectionMatrixRightEye = new Matrix4f(); private Matrix4f viewMatrixLeftEye = new Matrix4f(); private Matrix4f viewMatrixRightEye = new Matrix4f(); private Matrix4f viewMatrixReflectedLeftEye = new Matrix4f(); private Matrix4f viewMatrixReflectedRightEye = new Matrix4f(); private ViewFrustum viewFrustumLeftEye = new ViewFrustum(); private ViewFrustum viewFrustumRightEye = new ViewFrustum(); private ViewFrustum viewFrustumReflectedLeftEye = new ViewFrustum(); private ViewFrustum viewFrustumReflectedRightEye = new ViewFrustum(); private Matrix4f viewProjectionMatrixLeftEye = new Matrix4f(); private Matrix4f viewProjectionMatrixRightEye = new Matrix4f(); private Matrix4f viewTranslationLeftEye = new Matrix4f(); private Matrix4f viewTranslationRightEye = new Matrix4f(); private OpenVRProvider vrProvider; public OpenVRStereoCamera(OpenVRProvider provider, WorldProvider worldProvider, RenderingConfig renderingConfig) { super(worldProvider, renderingConfig); vrProvider = provider; // OpenVR's projection matrix is such that this is approximately true. zFar = 400.0f; } @Override public void updateFrustum() { super.updateFrustum(); viewFrustumLeftEye.updateFrustum(MatrixUtils.matrixToFloatBuffer(viewMatrixLeftEye), MatrixUtils.matrixToFloatBuffer(projectionMatrixLeftEye)); viewFrustumRightEye.updateFrustum(MatrixUtils.matrixToFloatBuffer(viewMatrixRightEye), MatrixUtils.matrixToFloatBuffer(projectionMatrixRightEye)); viewFrustumReflectedLeftEye.updateFrustum(MatrixUtils.matrixToFloatBuffer(viewMatrixReflectedLeftEye), MatrixUtils.matrixToFloatBuffer(projectionMatrixLeftEye)); viewFrustumReflectedRightEye.updateFrustum(MatrixUtils.matrixToFloatBuffer(viewMatrixReflectedRightEye), MatrixUtils.matrixToFloatBuffer(projectionMatrixRightEye)); } @Override public boolean isBobbingAllowed() { return false; } @Override public ViewFrustum getViewFrustum() { RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); if (renderingStage == RenderingStage.LEFT_EYE) { return viewFrustumLeftEye; } else if (renderingStage == RenderingStage.RIGHT_EYE) { return viewFrustumRightEye; } return null; } @Override public ViewFrustum getViewFrustumReflected() { RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); if (renderingStage == RenderingStage.LEFT_EYE) { return viewFrustumReflectedLeftEye; } else if (renderingStage == RenderingStage.RIGHT_EYE) { return viewFrustumReflectedRightEye; } return null; } @Override public Matrix4f getViewProjectionMatrix() { RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); if (renderingStage == RenderingStage.LEFT_EYE) { return viewProjectionMatrixLeftEye; } else if (renderingStage == RenderingStage.RIGHT_EYE) { return viewProjectionMatrixRightEye; } return null; } @Override public Matrix4f getViewMatrix() { RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); if (renderingStage == RenderingStage.LEFT_EYE) { if (!isReflected()) { return viewMatrixLeftEye; } return viewMatrixReflectedLeftEye; } else if (renderingStage == RenderingStage.RIGHT_EYE) { if (!isReflected()) { return viewMatrixRightEye; } return viewMatrixReflectedRightEye; } return null; } @Override public Matrix4f getProjectionMatrix() { RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); if (renderingStage == RenderingStage.LEFT_EYE) { return projectionMatrixLeftEye; } else if (renderingStage == RenderingStage.RIGHT_EYE) { return projectionMatrixRightEye; } return null; } @Override public Matrix4f getInverseProjectionMatrix() { RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); if (renderingStage == RenderingStage.LEFT_EYE) { return inverseProjectionMatrixLeftEye; } else if (renderingStage == RenderingStage.RIGHT_EYE) { return inverseProjectionMatrixRightEye; } return null; } @Override public Matrix4f getInverseViewProjectionMatrix() { RenderingStage renderingStage = CoreRegistry.get(WorldRenderer.class).getCurrentRenderStage(); if (renderingStage == RenderingStage.LEFT_EYE) { return inverseViewProjectionMatrixLeftEye; } else if (renderingStage == RenderingStage.RIGHT_EYE) { return inverseViewProjectionMatrixRightEye; } return null; } @Override @Deprecated public void loadProjectionMatrix() { glMatrixMode(GL_PROJECTION); GL11.glLoadMatrix(MatrixUtils.matrixToFloatBuffer(getProjectionMatrix())); glMatrixMode(GL11.GL_MODELVIEW); } @Override @Deprecated public void loadModelViewMatrix() { glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadMatrix(MatrixUtils.matrixToFloatBuffer(getViewMatrix())); } @Override @Deprecated public void loadNormalizedModelViewMatrix() { glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadMatrix(MatrixUtils.matrixToFloatBuffer(normViewMatrix)); } @Override public void update(float deltaT) { super.update(deltaT); updateMatrices(); } @Override public void updateMatrices() { updateMatrices(activeFov); } private void jomlMatrix4f(org.joml.Matrix4f matrixInput, org.terasology.math.geom.Matrix4f matrixOut) { matrixOut.set(0, 0, matrixInput.m00()); matrixOut.set(0, 1, matrixInput.m10()); matrixOut.set(0, 2, matrixInput.m20()); matrixOut.set(0, 3, matrixInput.m30()); matrixOut.set(1, 0, matrixInput.m01()); matrixOut.set(1, 1, matrixInput.m11()); matrixOut.set(1, 2, matrixInput.m21()); matrixOut.set(1, 3, matrixInput.m31()); matrixOut.set(2, 0, matrixInput.m02()); matrixOut.set(2, 1, matrixInput.m12()); matrixOut.set(2, 2, matrixInput.m22()); matrixOut.set(2, 3, matrixInput.m32()); matrixOut.set(3, 0, matrixInput.m03()); matrixOut.set(3, 1, matrixInput.m13()); matrixOut.set(3, 2, matrixInput.m23()); matrixOut.set(3, 3, matrixInput.m33()); } @Override public void updateMatrices(float fov) { prevViewProjectionMatrix.set(viewProjectionMatrix); org.joml.Matrix4f leftEyeProjection = vrProvider.getState().getEyeProjectionMatrix(0); org.joml.Matrix4f rightEyeProjection = vrProvider.getState().getEyeProjectionMatrix(1); org.joml.Matrix4f leftEyePose = vrProvider.getState().getEyePose(0); org.joml.Matrix4f rightEyePose = vrProvider.getState().getEyePose(1); float halfIPD = (float) Math.sqrt(Math.pow(leftEyePose.m30() - rightEyePose.m30(), 2) + Math.pow(leftEyePose.m31() - rightEyePose.m31(), 2) + Math.pow(leftEyePose.m32() - rightEyePose.m32(), 2)) / 2.0f; leftEyePose = leftEyePose.invert(); // view matrix is inverse of pose matrix rightEyePose = rightEyePose.invert(); if (Math.sqrt(Math.pow(leftEyePose.m30(), 2) + Math.pow(leftEyePose.m31(), 2) + Math.pow(leftEyePose.m32(), 2)) < 0.25) { return; } jomlMatrix4f(leftEyeProjection, projectionMatrixLeftEye); jomlMatrix4f(rightEyeProjection, projectionMatrixRightEye); projectionMatrix = projectionMatrixLeftEye; jomlMatrix4f(leftEyePose, viewMatrixLeftEye); jomlMatrix4f(rightEyePose, viewMatrixRightEye); viewMatrix = viewMatrixLeftEye; normViewMatrix = viewMatrixLeftEye; reflectionMatrix.setRow(0, 1.0f, 0.0f, 0.0f, 0.0f); reflectionMatrix.setRow(1, 0.0f, -1.0f, 0.0f, 2f * (-position.y + 32f)); reflectionMatrix.setRow(2, 0.0f, 0.0f, 1.0f, 0.0f); reflectionMatrix.setRow(3, 0.0f, 0.0f, 0.0f, 1.0f); viewMatrixReflected.mul(viewMatrix, reflectionMatrix); reflectionMatrix.setRow(1, 0.0f, -1.0f, 0.0f, 0.0f); normViewMatrixReflected.mul(normViewMatrix, reflectionMatrix); viewTranslationLeftEye.setIdentity(); viewTranslationLeftEye.setTranslation(new Vector3f(halfIPD, 0.0f, 0.0f)); viewTranslationRightEye.setIdentity(); viewTranslationRightEye.setTranslation(new Vector3f(-halfIPD, 0.0f, 0.0f)); viewMatrixReflectedLeftEye.mul(viewMatrixReflected, viewTranslationLeftEye); viewMatrixReflectedRightEye.mul(viewMatrixReflected, viewTranslationRightEye); viewProjectionMatrixLeftEye = MatrixUtils.calcViewProjectionMatrix(viewMatrixLeftEye, projectionMatrixLeftEye); viewProjectionMatrixRightEye = MatrixUtils.calcViewProjectionMatrix(viewMatrixRightEye, projectionMatrixRightEye); inverseViewProjectionMatrixLeftEye.invert(viewProjectionMatrixLeftEye); inverseViewProjectionMatrixRightEye.invert(viewProjectionMatrixRightEye); inverseProjectionMatrixLeftEye.invert(projectionMatrixLeftEye); inverseProjectionMatrixRightEye.invert(projectionMatrixRightEye); updateFrustum(); } }