Java tutorial
/* * Copyright 2016 Nathan Howard * * This file is part of OpenGrave * * OpenGrave 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 3 of the License, or * (at your option) any later version. * * OpenGrave 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 OpenGrave. If not, see <http://www.gnu.org/licenses/>. */ package com.opengrave.og.engine; import java.util.ArrayList; import java.util.Collections; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL20; import com.opengrave.og.MainThread; import com.opengrave.og.Util; import com.opengrave.og.input.MouseRenderableHoverEvent; import com.opengrave.og.light.Shadow; import com.opengrave.og.light.Shadow2D; import com.opengrave.og.light.ShadowCube; import com.opengrave.og.util.Matrix3f; import com.opengrave.og.util.Matrix4f; import com.opengrave.og.util.Vector3f; import com.opengrave.og.util.Vector4f; public class RenderView { private RootNode sceneNode; private Camera cam; private Matrix4f projectionMatrix, viewMatrix, shadowMatrix; public int totalx; public int totaly; public int width; public int height; private boolean shadows = false, hasshadows = false; Matrix4f ident = new Matrix4f(); private Vector4f clearCol = new Vector4f(0f, 0f, 0f, 1f); private boolean clear = false; private ArrayList<ShadowCube> lightShadows = new ArrayList<ShadowCube>(); public RenderView(RootNode node, Camera camera) { this.sceneNode = node; this.cam = camera; } public Node getSceneNode() { return sceneNode; } public void setSceneNode(RootNode sceneNode) { this.sceneNode = sceneNode; } public Camera getCam() { return cam; } public void setCam(FlyByCamera cam) { this.cam = cam; } public void clearAreaBeforeDraw(boolean b) { clear = b; } public void renderForPicking(int totalx, int totaly, int width, int height) { if (totalx < 0 || totaly < 0 || width < 1 || height < 1) { return; } this.totalx = totalx; this.totaly = totaly; this.width = width; this.height = height; if (sceneNode == null) { System.out.println("No scene node to render"); return; } if (cam == null) { System.out.println("No camera to render from"); return; } Util.checkErr(); GL11.glViewport(totalx, totaly, width, height); Util.checkErr(); prepare3DOpaque(); Util.checkErr(); sceneNode.renderForPicking(ident); Util.checkErr(); revertSettings(); } public void render(int totalx, int totaly, int width, int height) { this.totalx = totalx; this.totaly = totaly; this.width = width; this.height = height; if (sceneNode == null) { System.out.println("No scene node to render"); return; } if (cam == null) { System.out.println("No camera to render from"); return; } Shadow2D skyLight = sceneNode.getSkyLight(); if (shadows && hasshadows) { // For each light source we map out a texture using a quick // render. prepare3DShadow(); skyLight.getFramebuffer().bindDraw(); // GL11.glEnable(GL32.GL_DEPTH_CLAMP); // TODO Simu;ate depth clamp in shadow shader when z < 0. This way we force distanced objects to still cast a shadow. sceneNode.renderShadows(ident, skyLight); skyLight.getFramebuffer().unbindDraw(); int count = 0; ArrayList<PointLightNode> lightList = new ArrayList<PointLightNode>(); sceneNode.getAllLights(lightList, ident, cam.getLocation().toVector4()); Collections.sort(lightList, new PointLightSorter()); for (PointLightNode light : lightList) { if (light.getColour().x > 0 || light.getColour().y > 0 || light.getColour().z > 0) { // Render each face if (lightShadows.size() == count) { ShadowCube shadow = new ShadowCube(MainThread.config.getInteger("shadowSize", 1024)); lightShadows.add(shadow); } ShadowCube shadow = lightShadows.get(count); shadow.setLight(light); for (int i = 0; i < 6; i++) { shadow.getFramebuffer().bindDraw(i); Util.checkErr(); shadow.setFace(i); Util.checkErr(); sceneNode.renderShadows(ident, shadow); Util.checkErr(); shadow.getFramebuffer().unbindDraw(); Util.checkErr(); } count++; } if (count == 16) { break; } } GL11.glDisable(GL11.GL_POLYGON_OFFSET_FILL); } GL11.glViewport(totalx, totaly, width, height); GL11.glScissor(totalx, totaly, width, height); GL11.glEnable(GL11.GL_SCISSOR_TEST); GL11.glClearColor(clearCol.x, clearCol.y, clearCol.z, clearCol.w); GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT | (clear ? GL11.GL_COLOR_BUFFER_BIT : 0)); Util.checkErr(); prepare3DOpaque(); sceneNode.render(ident); Util.checkErr(); prepare3DTransparent(); sceneNode.renderSemiTransparent(ident); MouseRenderableHoverEvent lF = MainThread.main.input.getLastHovered(); if (lF != null) { if (lF.getRenderable() instanceof BaseObject) { /* * GL11.glDepthFunc(GL11.GL_LESS); * GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); * GL11.glPolygonMode(GL11.GL_BACK, GL11.GL_LINE); * * GL11.glLineWidth(10f); * GL11.glCullFace(GL11.GL_FRONT); * GL11.glEnable(GL11.GL_CULL_FACE); * GL11.glDisable(GL11.GL_BLEND); * BaseObject bo = (BaseObject) lF.getRenderable(); * RenderStyle rs = bo.getRenderStyle(); * bo.setRenderStyle(RenderStyle.HALO); * sceneNode.renderOne(ident, bo, ident); * * GL11.glDisable(GL11.GL_BLEND); * * bo.setRenderStyle(rs); * GL11.glCullFace(GL11.GL_BACK); * GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); * GL11.glPolygonMode(GL11.GL_BACK, GL11.GL_FILL); * * GL11.glLineWidth(1f); * sceneNode.renderOne(ident, bo, ident); */ } } revertSettings(); GL11.glDisable(GL11.GL_SCISSOR_TEST); } private void prepare3DShadow() { GL11.glDisable(GL11.GL_BLEND); GL11.glDepthFunc(GL11.GL_LESS); // GL11.glEnable(GL11.GL_POLYGON_OFFSET_FILL); // GL11.glPolygonOffset(1.1f, 10f); GL11.glCullFace(GL11.GL_FRONT); GL11.glEnable(GL11.GL_CULL_FACE); } private void revertSettings() { GL11.glViewport(0, 0, MainThread.lastW, MainThread.lastH); GL11.glDisable(GL11.GL_CULL_FACE); GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); GL11.glDepthFunc(GL11.GL_LEQUAL); GL11.glDisable(GL11.GL_POLYGON_OFFSET_FILL); } public void prepare3DTransparent() { Util.checkErr(); GL11.glDisable(GL11.GL_POLYGON_OFFSET_FILL); Util.checkErr(); GL11.glEnable(GL11.GL_BLEND); Util.checkErr(); GL11.glDepthFunc(GL11.GL_LEQUAL); Util.checkErr(); GL11.glDisable(GL11.GL_CULL_FACE); } private void prepare3DOpaque() { GL11.glDisable(GL11.GL_POLYGON_OFFSET_FILL); GL11.glDisable(GL11.GL_BLEND); GL11.glDepthFunc(GL11.GL_LESS); GL11.glCullFace(GL11.GL_BACK); GL11.glEnable(GL11.GL_CULL_FACE); } public void update(float delta) { cam.update(delta); projectionMatrix = cam.getProjectionMatrix(width, height); viewMatrix = cam.getViewMatrix(); Shadow2D skyLight = sceneNode.getSkyLight(); hasshadows = skyLight != null; shadows = MainThread.config.getBoolean("shadows", true); if (hasshadows) { skyLight.update(delta); } sceneNode.update(this, delta); } public Matrix4f getProjectionMatrix() { return projectionMatrix; } public void setProjectionMatrix(Matrix4f projectionMatrix) { this.projectionMatrix = projectionMatrix; } public Matrix4f getViewMatrix() { return viewMatrix; } public void setViewMatrix(Matrix4f viewMatrix) { this.viewMatrix = viewMatrix; } public Matrix4f getShadowMatrix() { return shadowMatrix; } public void setShadowMatrix(Matrix4f shadowMatrix) { this.shadowMatrix = shadowMatrix; } public void setMatrices(int pID, Matrix4f modelView) { GL20.glUseProgram(pID); Util.setUniformMat44(pID, "M", modelView); Matrix4f MV = viewMatrix.mult(modelView, null); Util.setUniformMat44(pID, "V", viewMatrix); Util.setUniformMat44(pID, "MV", MV); Matrix4f MVP = projectionMatrix.mult(MV, null); Util.setUniformMat44(pID, "MVP", MVP); Matrix3f N = makeNormalMatrix(modelView); Util.setUniformMat33(pID, "N", N); // FIXME Was M 3x3, watch out for the bugs this'll cause if (shadows && hasshadows) { Matrix4f shadowBMVP = Util.shadowBiasMatrix.mult(sceneNode.getSkyLight().getMVP(modelView, this), null); Util.setUniformMat44(pID, "shadowBMVP", shadowBMVP); } } public Matrix3f makeNormalMatrix(Matrix4f in) { Matrix4f out = new Matrix4f(in); out.inverse(out); out.transpose(out); // out.m00 = in.m00; // out.m01 = in.m01; // out.m02 = in.m02; // out.m10 = in.m10; // out.m11 = in.m11; // out.m12 = in.m12; // out.m20 = in.m20; // out.m21 = in.m21; // out.m22 = in.m22; // out = (Matrix3f) out.transpose(); // out = (Matrix3f) out.invert(); return new Matrix3f(out); } public void setShadowMatrices(int pID, Matrix4f modelView, Shadow shadow) { GL20.glUseProgram(pID); Util.setUniformMat44(pID, "M", modelView); Matrix4f MVP = shadow.getMVP(modelView, this); Util.setUniformMat44(pID, "MVP", MVP); } public Matrix4f createMatrixFor(Location location, Matrix4f parent, Matrix4f inside) { Vector3f loc = location.toVector3(); if (cam != null) { loc = location.minus(cam.getLocation()); } Matrix4f modelView = new Matrix4f(); // Translate to world co-ords first modelView.translate(loc, modelView); // Then scale/rotate modelView.scale(location.getScale(), modelView); modelView.rotate(Util.degreesToRadians(location.getRotate().z), new Vector3f(0, 0, 1), modelView); modelView.rotate(Util.degreesToRadians(location.getRotate().y), new Vector3f(0, 1, 0), modelView); modelView.rotate(Util.degreesToRadians(location.getRotate().x), new Vector3f(1, 0, 0), modelView); if (parent != null) { // Matrix4f.mul(modelView, parent, modelView); parent.mult(modelView, modelView); } if (inside != null) { inside.mult(inside, modelView); } return modelView; } public void bindLights(int pID, int texture) { GL20.glUseProgram(pID); int lights = MainThread.config.getInteger("lightCount", 16); int showShadows = GL20.glGetUniformLocation(pID, "showShadows"); GL20.glUniform1i(showShadows, hasshadows ? 1 : 0); Util.checkErr(); if (cam instanceof FlyByCamera) { int eyepos = GL20.glGetUniformLocation(pID, "eyepos"); Util.checkErr(); Location camL = ((FlyByCamera) cam).getCameraLocation(); Util.checkErr(); GL20.glUniform3f(eyepos, camL.getFullXAsFloat(), camL.getFullYAsFloat(), camL.getZ()); Util.checkErr(); } if (hasshadows) { int lightDir = GL20.glGetUniformLocation(pID, "sunDir"); Vector3f dir = sceneNode.getSkyLight().getDirection().normalise(null); GL20.glUniform3f(lightDir, dir.x, dir.y, dir.z); int sunStr = GL20.glGetUniformLocation(pID, "sunStr"); GL20.glUniform1f(sunStr, sceneNode.getSkyLight().getIntensity()); } if (!shadows) { return; } int lightCount = GL20.glGetUniformLocation(pID, "lightCount"); GL20.glUniform1i(lightCount, lights); if (lights < 4) { lights = 4; } for (int i = 0; i < lights; i++) { ShadowCube ls; if (i >= lightShadows.size()) { ls = new ShadowCube(MainThread.config.getInteger("shadowSize", 1024)); lightShadows.add(ls); } ls = lightShadows.get(i); if (ls == null) { continue; } PointLightNode light = ls.getLight(); if (light == null) { continue; } int cube = GL20.glGetUniformLocation(pID, "cube" + i); int lightcolour = GL20.glGetUniformLocation(pID, "lights[" + i + "].colour"); int lightposition = GL20.glGetUniformLocation(pID, "lights[" + i + "].position"); int lightpower = GL20.glGetUniformLocation(pID, "lights[" + i + "].power"); Vector3f loc = light.getPosition().toVector3(); if (cam != null) { // loc = light.getPosition().minus(cam.getLocation()); // loc = cam.minus(light.getPosition()); } Util.checkErr(); // Only upload a shadow texture location if we're having shadows for // the light. Basic has lights but no shadows if (i < showShadows) { GL20.glUniform1i(cube, texture + i - GL13.GL_TEXTURE0); ls.getFramebuffer().bindDepthTexture(texture + i); } Util.checkErr(); GL20.glUniform4f(lightcolour, light.getColour().x, light.getColour().y, light.getColour().z, light.getColour().w); Util.checkErr(); GL20.glUniform4f(lightposition, loc.x, loc.y, loc.z, 1f); Util.checkErr(); GL20.glUniform1f(lightpower, light.getPower()); Util.checkErr(); Util.checkErr(); } } public void unbindLights() { int lights = MainThread.config.getInteger("lightCount", 16); if (!shadows) { return; } int lightShadowCount = lights; if (lights < 4) { lights = 4; } for (int i = 0; i < lightShadowCount; i++) { ShadowCube ls = lightShadows.get(i); if (ls == null) { continue; } Util.checkErr(); ls.getFramebuffer().unbindDepthTexture(); Util.checkErr(); } } public void bindShadowData(int glTexture) { if (!hasshadows || !shadows) { return; } Util.checkErr(); sceneNode.getSkyLight().getFramebuffer().bindDepthTexture(glTexture); Util.checkErr(); } public void unbindShadowData() { if (!shadows || !hasshadows) { return; } Util.checkErr(); sceneNode.getSkyLight().getFramebuffer().unbindDepthTexture(); Util.checkErr(); } }