/*
* Copyright 2013 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.world;
import com.google.common.collect.Lists;
import org.lwjgl.opengl.GL11;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.asset.Assets;
import org.terasology.audio.AudioManager;
import org.terasology.config.Config;
import org.terasology.engine.ComponentSystemManager;
import org.terasology.engine.Time;
import org.terasology.engine.subsystem.lwjgl.GLBufferPool;
import org.terasology.entitySystem.entity.EntityManager;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.entitySystem.systems.RenderSystem;
import org.terasology.logic.location.LocationComponent;
import org.terasology.logic.players.LocalPlayer;
import org.terasology.logic.players.LocalPlayerSystem;
import org.terasology.math.AABB;
import org.terasology.math.Region3i;
import org.terasology.math.TeraMath;
import org.terasology.math.Vector3i;
import org.terasology.monitoring.PerformanceMonitor;
import org.terasology.physics.bullet.BulletPhysics;
import org.terasology.registry.CoreRegistry;
import org.terasology.rendering.AABBRenderer;
import org.terasology.rendering.RenderHelper;
import org.terasology.rendering.ShaderManager;
import org.terasology.rendering.assets.material.Material;
import org.terasology.rendering.assets.shader.ShaderProgramFeature;
import org.terasology.rendering.cameras.Camera;
import org.terasology.rendering.cameras.OculusStereoCamera;
import org.terasology.rendering.cameras.OrthographicCamera;
import org.terasology.rendering.cameras.PerspectiveCamera;
import org.terasology.rendering.logic.LightComponent;
import org.terasology.rendering.logic.MeshRenderer;
import org.terasology.rendering.opengl.DefaultRenderingProcess;
import org.terasology.rendering.primitives.ChunkMesh;
import org.terasology.rendering.primitives.ChunkTessellator;
import org.terasology.rendering.primitives.LightGeometryHelper;
import org.terasology.world.ChunkView;
import org.terasology.world.WorldProvider;
import org.terasology.world.block.Block;
import org.terasology.world.chunks.ChunkConstants;
import org.terasology.world.chunks.ChunkProvider;
import org.terasology.world.chunks.RenderableChunk;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.GL_FILL;
import static org.lwjgl.opengl.GL11.GL_FRONT_AND_BACK;
import static org.lwjgl.opengl.GL11.GL_LEQUAL;
import static org.lwjgl.opengl.GL11.GL_LIGHT0;
import static org.lwjgl.opengl.GL11.GL_LINE;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.glBlendFunc;
import static org.lwjgl.opengl.GL11.glCullFace;
import static org.lwjgl.opengl.GL11.glDepthFunc;
import static org.lwjgl.opengl.GL11.glDepthMask;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glLoadIdentity;
import static org.lwjgl.opengl.GL11.glPolygonMode;
import static org.lwjgl.opengl.GL11.glPopMatrix;
import static org.lwjgl.opengl.GL11.glPushMatrix;
/**
* The world of Terasology. At its most basic the world contains chunks (consisting of a fixed amount of blocks)
* and the player.
* <p/>
* The world is randomly generated by using a bunch of Perlin noise generators initialized
* with a favored seed value.
*
* @author Benjamin Glatzel <benjamin.glatzel@me.com>
*/
public final class WorldRendererLwjgl implements WorldRenderer {
public static final int MAX_ANIMATED_CHUNKS = 64;
public static final int MAX_BILLBOARD_CHUNKS = 64;
public static final int VERTICAL_SEGMENTS = CoreRegistry.get(Config.class).getSystem().getVerticalChunkMeshSegments();
private static final int MAX_CHUNKS = ViewDistance.MEGA.getChunkDistance().x * ViewDistance.MEGA.getChunkDistance().y * ViewDistance.MEGA.getChunkDistance().z;
private static final Logger logger = LoggerFactory.getLogger(WorldRendererLwjgl.class);
private static final int SHADOW_FRUSTUM_BOUNDS = 500;
/* WORLD PROVIDER */
private final WorldProvider worldProvider;
private ChunkProvider chunkProvider;
/* PLAYER */
private LocalPlayer player;
/* CAMERA */
private Camera activeCamera;
/* SHADOW MAPPING */
private Camera lightCamera = new OrthographicCamera(-SHADOW_FRUSTUM_BOUNDS, SHADOW_FRUSTUM_BOUNDS, SHADOW_FRUSTUM_BOUNDS, -SHADOW_FRUSTUM_BOUNDS);
/* LIGHTING */
// TODO: Review this? (What are we doing with a component not attached to an entity?)
private LightComponent mainDirectionalLight = new LightComponent();
private float smoothedPlayerSunlightValue;
/* CHUNKS */
private ChunkTessellator chunkTessellator;
// TODO: Review usage of ChunkImpl throughout WorldRenderer
private final List<RenderableChunk> chunksInProximity = Lists.newArrayListWithCapacity(MAX_CHUNKS);
private Region3i renderRegion = Region3i.EMPTY;
/* RENDERING */
private final PriorityQueue<RenderableChunk> renderQueueChunksOpaque = new PriorityQueue<>(MAX_CHUNKS, new ChunkFrontToBackComparator());
private final PriorityQueue<RenderableChunk> renderQueueChunksOpaqueShadow = new PriorityQueue<>(MAX_CHUNKS, new ChunkFrontToBackComparator());
private final PriorityQueue<RenderableChunk> renderQueueChunksOpaqueReflection = new PriorityQueue<>(MAX_CHUNKS, new ChunkFrontToBackComparator());
private final PriorityQueue<RenderableChunk> renderQueueChunksAlphaReject = new PriorityQueue<>(MAX_CHUNKS, new ChunkFrontToBackComparator());
private final PriorityQueue<RenderableChunk> renderQueueChunksAlphaBlend = new PriorityQueue<>(MAX_CHUNKS, new ChunkBackToFrontComparator());
private WorldRenderingStage currentRenderingStage = WorldRenderingStage.DEFAULT;
/* HORIZON */
private final Skysphere skysphere;
/* TICKING */
private Time time = CoreRegistry.get(Time.class);
private float tick;
/* UPDATING */
private final ChunkMeshUpdateManager chunkMeshUpdateManager;
/* PHYSICS */
// TODO: Remove physics handling from world renderer
private final BulletPhysics bulletPhysics;
/* STATISTICS */
private int statDirtyChunks;
private int statVisibleChunks;
private int statIgnoredPhases;
private int statChunkMeshEmpty;
private int statChunkNotReady;
private int statRenderedTriangles;
/* ENUMS */
public enum ChunkRenderMode {
DEFAULT,
REFLECTION,
SHADOW_MAP,
Z_PRE_PASS
}
private ComponentSystemManager systemManager;
private Config config;
/**
* Initializes a new (local) world for the single player mode.
*/
public WorldRendererLwjgl(WorldProvider worldProvider, ChunkProvider chunkProvider, LocalPlayerSystem localPlayerSystem, GLBufferPool bufferPool) {
this.chunkProvider = chunkProvider;
this.worldProvider = worldProvider;
bulletPhysics = new BulletPhysics(worldProvider);
chunkTessellator = new ChunkTessellator(bufferPool);
skysphere = new Skysphere();
chunkMeshUpdateManager = new ChunkMeshUpdateManager(chunkTessellator, worldProvider);
// TODO: won't need localPlayerSystem here once camera is in the ES proper
systemManager = CoreRegistry.get(ComponentSystemManager.class);
Camera localPlayerCamera;
if (CoreRegistry.get(Config.class).getRendering().isOculusVrSupport()) {
localPlayerCamera = new OculusStereoCamera();
} else {
localPlayerCamera = new PerspectiveCamera(CoreRegistry.get(Config.class).getRendering().getCameraSettings());
}
activeCamera = localPlayerCamera;
mainDirectionalLight.lightType = LightComponent.LightType.DIRECTIONAL;
mainDirectionalLight.lightColorAmbient = new Vector3f(1.0f, 1.0f, 1.0f);
mainDirectionalLight.lightColorDiffuse = new Vector3f(1.0f, 1.0f, 1.0f);
mainDirectionalLight.lightAmbientIntensity = 1.0f;
mainDirectionalLight.lightDiffuseIntensity = 2.0f;
mainDirectionalLight.lightSpecularIntensity = 0.0f;
localPlayerSystem.setPlayerCamera(localPlayerCamera);
config = CoreRegistry.get(Config.class);
}
@Override
public void onChunkLoaded(Vector3i pos) {
if (renderRegion.encompasses(pos)) {
RenderableChunk chunk = chunkProvider.getChunk(pos);
chunksInProximity.add(chunk);
Collections.sort(chunksInProximity, new ChunkFrontToBackComparator());
}
}
@Override
public void onChunkUnloaded(Vector3i pos) {
if (renderRegion.encompasses(pos)) {
Iterator<RenderableChunk> iterator = chunksInProximity.iterator();
while (iterator.hasNext()) {
RenderableChunk chunk = iterator.next();
if (chunk.getPosition().equals(pos)) {
chunk.disposeMesh();
iterator.remove();
Collections.sort(chunksInProximity, new ChunkFrontToBackComparator());
break;
}
}
}
}
/**
* Updates the list of chunks around the player.
*
* @return True if the list was changed
*/
public boolean updateChunksInProximity(Region3i newRegion) {
if (!newRegion.equals(renderRegion)) {
Iterator<Vector3i> removeChunks = renderRegion.subtract(newRegion);
while (removeChunks.hasNext()) {
Vector3i pos = removeChunks.next();
Iterator<RenderableChunk> iterator = chunksInProximity.iterator();
while (iterator.hasNext()) {
RenderableChunk chunk = iterator.next();
if (chunk.getPosition().equals(pos)) {
chunk.disposeMesh();
iterator.remove();
break;
}
}
}
Iterator<Vector3i> addChunks = newRegion.subtract(renderRegion);
while (addChunks.hasNext()) {
Vector3i pos = addChunks.next();
RenderableChunk c = chunkProvider.getChunk(pos);
if (c != null) {
chunksInProximity.add(c);
}
}
renderRegion = newRegion;
Collections.sort(chunksInProximity, new ChunkFrontToBackComparator());
return true;
}
return false;
}
private static float distanceToCamera(RenderableChunk chunk) {
Vector3f result = new Vector3f((chunk.getPosition().x + 0.5f) * ChunkConstants.SIZE_X,
(chunk.getPosition().y + 0.5f) * ChunkConstants.SIZE_Y, (chunk.getPosition().z + 0.5f) * ChunkConstants.SIZE_Z);
Vector3f cameraPos = CoreRegistry.get(WorldRenderer.class).getActiveCamera().getPosition();
result.x -= cameraPos.x;
result.y -= cameraPos.y;
result.z -= cameraPos.z;
return result.lengthSquared();
}
/**
* Updates the currently visible chunks (in sight of the player).
*/
public void updateAndQueueVisibleChunks() {
updateAndQueueVisibleChunks(true, true);
}
public int updateAndQueueVisibleChunks(boolean fillShadowRenderQueue, boolean processChunkUpdates) {
statDirtyChunks = 0;
statVisibleChunks = 0;
statIgnoredPhases = 0;
if (processChunkUpdates) {
PerformanceMonitor.startActivity("Building Mesh VBOs");
for (RenderableChunk c : chunkMeshUpdateManager.availableChunksForUpdate()) {
if (chunksInProximity.contains(c) && c.getPendingMesh() != null) {
for (int i = 0; i < c.getPendingMesh().length; i++) {
c.getPendingMesh()[i].generateVBOs();
}
if (c.getMesh() != null) {
for (int i = 0; i < c.getMesh().length; i++) {
c.getMesh()[i].dispose();
}
}
c.setMesh(c.getPendingMesh());
c.setPendingMesh(null);
} else {
ChunkMesh[] pendingMesh = c.getPendingMesh();
c.setPendingMesh(null);
if (pendingMesh != null) {
for (ChunkMesh mesh : pendingMesh) {
mesh.dispose();
}
}
}
}
PerformanceMonitor.endActivity();
}
int processedChunks = 0;
for (int i = 0; i < chunksInProximity.size(); i++) {
RenderableChunk c = chunksInProximity.get(i);
ChunkMesh[] mesh = c.getMesh();
if (i < TeraMath.clamp(config.getRendering().getMaxChunksUsedForShadowMapping(), 64, 1024)
&& config.getRendering().isDynamicShadows() && fillShadowRenderQueue) {
if (isChunkVisibleLight(c) && isChunkValidForRender(c)) {
if (triangleCount(mesh, ChunkMesh.RenderPhase.OPAQUE) > 0) {
renderQueueChunksOpaqueShadow.add(c);
} else {
statIgnoredPhases++;
}
}
}
if (isChunkValidForRender(c)) {
if (isChunkVisible(c)) {
if (triangleCount(mesh, ChunkMesh.RenderPhase.OPAQUE) > 0) {
renderQueueChunksOpaque.add(c);
} else {
statIgnoredPhases++;
}
if (triangleCount(mesh, ChunkMesh.RenderPhase.REFRACTIVE) > 0) {
renderQueueChunksAlphaBlend.add(c);
} else {
statIgnoredPhases++;
}
if (triangleCount(mesh, ChunkMesh.RenderPhase.ALPHA_REJECT) > 0 && i < MAX_BILLBOARD_CHUNKS) {
renderQueueChunksAlphaReject.add(c);
} else {
statIgnoredPhases++;
}
statVisibleChunks++;
if (statVisibleChunks < MAX_ANIMATED_CHUNKS) {
c.setAnimated(true);
} else {
c.setAnimated(false);
}
}
if (isChunkVisibleReflection(c)) {
renderQueueChunksOpaqueReflection.add(c);
}
// Process all chunks in the area, not only the visible ones
if (processChunkUpdates && processChunkUpdate(c)) {
processedChunks++;
}
}
}
return processedChunks;
}
private boolean processChunkUpdate(RenderableChunk c) {
if ((c.isDirty() || c.getMesh() == null)) {
statDirtyChunks++;
chunkMeshUpdateManager.queueChunkUpdate(c);
return true;
}
return false;
}
private int triangleCount(ChunkMesh[] mesh, ChunkMesh.RenderPhase type) {
int count = 0;
if (mesh != null) {
for (ChunkMesh subMesh : mesh) {
count += subMesh.triangleCount(type);
}
}
return count;
}
private void resetStats() {
statChunkMeshEmpty = 0;
statChunkNotReady = 0;
statRenderedTriangles = 0;
}
/**
* Renders the world.
*/
public void render(DefaultRenderingProcess.StereoRenderState stereoRenderState) {
switch (stereoRenderState) {
case MONO:
currentRenderingStage = WorldRenderingStage.DEFAULT;
break;
case OCULUS_LEFT_EYE:
currentRenderingStage = WorldRenderingStage.OCULUS_LEFT_EYE;
activeCamera.updateFrustum();
break;
case OCULUS_RIGHT_EYE:
currentRenderingStage = WorldRenderingStage.OCULUS_RIGHT_EYE;
activeCamera.updateFrustum();
break;
}
resetStats();
if (stereoRenderState == DefaultRenderingProcess.StereoRenderState.MONO || stereoRenderState == DefaultRenderingProcess.StereoRenderState.OCULUS_LEFT_EYE) {
updateAndQueueVisibleChunks();
} else {
updateAndQueueVisibleChunks(false, false);
}
if (config.getRendering().isDynamicShadows()
&& (stereoRenderState == DefaultRenderingProcess.StereoRenderState.MONO || stereoRenderState == DefaultRenderingProcess.StereoRenderState.OCULUS_LEFT_EYE)) {
DefaultRenderingProcess.getInstance().beginRenderSceneShadowMap();
renderShadowMap(lightCamera);
DefaultRenderingProcess.getInstance().endRenderSceneShadowMap();
}
DefaultRenderingProcess.getInstance().beginRenderReflectedScene();
glCullFace(GL11.GL_FRONT);
getActiveCamera().setReflected(true);
renderWorldReflection(activeCamera);
getActiveCamera().setReflected(false);
glCullFace(GL11.GL_BACK);
DefaultRenderingProcess.getInstance().endRenderReflectedScene();
renderWorld(getActiveCamera());
/* COMBINE REFRACTIVE/REFLECTIVE WITH THE OPAQUE SCENE */
PerformanceMonitor.startActivity("Render Comined Scene");
DefaultRenderingProcess.getInstance().renderPreCombinedScene();
PerformanceMonitor.endActivity();
/* RENDER SIMPLE BLEND MATERIALS INTO THE COMBINED SCENE */
PerformanceMonitor.startActivity("Render Objects (Transparent)");
DefaultRenderingProcess.getInstance().beginRenderSceneOpaque();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(false);
for (RenderSystem renderer : systemManager.iterateRenderSubscribers()) {
renderer.renderAlphaBlend();
}
glDisable(GL_BLEND);
glDepthMask(true);
DefaultRenderingProcess.getInstance().endRenderSceneOpaque();
PerformanceMonitor.endActivity();
/* RENDER THE FINAL POST-PROCESSED SCENE */
PerformanceMonitor.startActivity("Render Post-Processing");
DefaultRenderingProcess.getInstance().renderPost(stereoRenderState);
PerformanceMonitor.endActivity();
if (activeCamera != null) {
activeCamera.updatePrevViewProjectionMatrix();
}
}
public void renderWorld(Camera camera) {
if (config.getRendering().getDebug().isWireframe()) {
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
DefaultRenderingProcess.getInstance().clear();
DefaultRenderingProcess.getInstance().beginRenderSceneOpaque();
/*
* SKYSPHERE
*/
camera.lookThroughNormalized();
PerformanceMonitor.startActivity("Render Sky");
DefaultRenderingProcess.getInstance().beginRenderSceneSky();
skysphere.render(camera);
DefaultRenderingProcess.getInstance().endRenderSceneSky();
PerformanceMonitor.endActivity();
/* WORLD RENDERING */
PerformanceMonitor.startActivity("Render World");
camera.lookThrough();
PerformanceMonitor.startActivity("Render Objects (Opaque)");
for (RenderSystem renderer : systemManager.iterateRenderSubscribers()) {
renderer.renderOpaque();
}
PerformanceMonitor.endActivity();
/*
* FIRST CHUNK PASS: OPAQUE
*/
PerformanceMonitor.startActivity("Render Chunks (Opaque)");
while (renderQueueChunksOpaque.size() > 0) {
renderChunk(renderQueueChunksOpaque.poll(), ChunkMesh.RenderPhase.OPAQUE, camera, ChunkRenderMode.DEFAULT);
}
PerformanceMonitor.endActivity();
/*
* SECOND RENDER PASS: ALPHA REJECT
*/
PerformanceMonitor.startActivity("Render Chunks (Alpha Reject)");
while (renderQueueChunksAlphaReject.size() > 0) {
renderChunk(renderQueueChunksAlphaReject.poll(), ChunkMesh.RenderPhase.ALPHA_REJECT, camera, ChunkRenderMode.DEFAULT);
}
PerformanceMonitor.endActivity();
/*
* OVERLAYS
*/
PerformanceMonitor.startActivity("Render Overlays");
for (RenderSystem renderer : systemManager.iterateRenderSubscribers()) {
renderer.renderOverlay();
}
PerformanceMonitor.endActivity();
/*
* FIRST PERSON VIEW
*/
if (activeCamera != null && !config.getRendering().getDebug().isFirstPersonElementsHidden()) {
PerformanceMonitor.startActivity("Render First Person");
glPushMatrix();
glLoadIdentity();
activeCamera.updateMatrices(90f);
activeCamera.loadProjectionMatrix();
glDepthFunc(GL11.GL_ALWAYS);
for (RenderSystem renderer : systemManager.iterateRenderSubscribers()) {
renderer.renderFirstPerson();
}
glDepthFunc(GL_LEQUAL);
activeCamera.updateMatrices();
activeCamera.loadProjectionMatrix();
glPopMatrix();
PerformanceMonitor.endActivity();
}
DefaultRenderingProcess.getInstance().endRenderSceneOpaque();
/*
* LIGHT GEOMETRY (STENCIL) PASS
*/
PerformanceMonitor.startActivity("Render Light Geometry");
DefaultRenderingProcess.getInstance().beginRenderLightGeometryStencilPass();
Material program = Assets.getMaterial("engine:prog.simple");
program.enable();
program.setCamera(camera);
EntityManager entityManager = CoreRegistry.get(EntityManager.class);
for (EntityRef entity : entityManager.getEntitiesWith(LightComponent.class, LocationComponent.class)) {
LocationComponent locationComponent = entity.getComponent(LocationComponent.class);
LightComponent lightComponent = entity.getComponent(LightComponent.class);
final Vector3f worldPosition = locationComponent.getWorldPosition();
renderLightComponent(lightComponent, worldPosition, program, camera, true);
}
DefaultRenderingProcess.getInstance().endRenderLightGeometryStencilPass();
/*
* LIGHT GEOMETRY PASS
*/
DefaultRenderingProcess.getInstance().beginRenderLightGeometry();
program = Assets.getMaterial("engine:prog.lightGeometryPass");
for (EntityRef entity : entityManager.getEntitiesWith(LightComponent.class, LocationComponent.class)) {
LocationComponent locationComponent = entity.getComponent(LocationComponent.class);
LightComponent lightComponent = entity.getComponent(LightComponent.class);
final Vector3f worldPosition = locationComponent.getWorldPosition();
renderLightComponent(lightComponent, worldPosition, program, camera, false);
}
DefaultRenderingProcess.getInstance().endRenderLightGeometry();
DefaultRenderingProcess.getInstance().beginRenderDirectionalLights();
// Sunlight
Vector3f sunlightWorldPosition = new Vector3f(skysphere.getSunDirection(true));
sunlightWorldPosition.scale(50000f);
if (activeCamera != null) {
sunlightWorldPosition.add(activeCamera.getPosition());
}
renderLightComponent(mainDirectionalLight, sunlightWorldPosition, program, camera, false);
DefaultRenderingProcess.getInstance().endRenderDirectionalLights();
PerformanceMonitor.endActivity();
/*
* THIRD CHUNK PASS: REFRACTIVE CHUNKS
*/
PerformanceMonitor.startActivity("Render Chunks (Refractive/Reflective)");
DefaultRenderingProcess.getInstance().beginRenderSceneReflectiveRefractive();
// Make sure the water surface is rendered if the player is swimming
boolean isHeadUnderWater = isHeadUnderWater();
if (isHeadUnderWater) {
glDisable(GL11.GL_CULL_FACE);
}
while (renderQueueChunksAlphaBlend.size() > 0) {
renderChunk(renderQueueChunksAlphaBlend.poll(), ChunkMesh.RenderPhase.REFRACTIVE, camera, ChunkRenderMode.DEFAULT);
}
PerformanceMonitor.endActivity();
if (isHeadUnderWater) {
glEnable(GL11.GL_CULL_FACE);
}
PerformanceMonitor.endActivity();
DefaultRenderingProcess.getInstance().endRenderSceneReflectiveRefractive();
if (config.getRendering().getDebug().isWireframe()) {
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
}
private boolean renderLightComponent(LightComponent lightComponent, Vector3f lightWorldPosition, Material program, Camera camera, boolean geometryOnly) {
Vector3f positionViewSpace = new Vector3f();
positionViewSpace.sub(lightWorldPosition, activeCamera.getPosition());
boolean doRenderLight = lightComponent.lightType == LightComponent.LightType.DIRECTIONAL
|| lightComponent.lightRenderingDistance == 0.0f
|| positionViewSpace.lengthSquared() < (lightComponent.lightRenderingDistance * lightComponent.lightRenderingDistance);
doRenderLight &= isLightVisible(positionViewSpace, lightComponent);
if (!doRenderLight) {
return false;
}
if (!geometryOnly) {
if (lightComponent.lightType == LightComponent.LightType.POINT) {
program.activateFeature(ShaderProgramFeature.FEATURE_LIGHT_POINT);
} else if (lightComponent.lightType == LightComponent.LightType.DIRECTIONAL) {
program.activateFeature(ShaderProgramFeature.FEATURE_LIGHT_DIRECTIONAL);
}
}
program.enable();
program.setCamera(camera);
Vector3f worldPosition = new Vector3f();
worldPosition.sub(lightWorldPosition, activeCamera.getPosition());
Vector3f lightViewPosition = new Vector3f();
camera.getViewMatrix().transform(worldPosition, lightViewPosition);
program.setFloat3("lightViewPos", lightViewPosition.x, lightViewPosition.y, lightViewPosition.z, true);
Matrix4f modelMatrix = new Matrix4f();
modelMatrix.setIdentity();
modelMatrix.setTranslation(worldPosition);
modelMatrix.setScale(lightComponent.lightAttenuationRange);
program.setMatrix4("modelMatrix", modelMatrix, true);
if (!geometryOnly) {
program.setFloat3("lightColorDiffuse", lightComponent.lightColorDiffuse.x, lightComponent.lightColorDiffuse.y, lightComponent.lightColorDiffuse.z, true);
program.setFloat3("lightColorAmbient", lightComponent.lightColorAmbient.x, lightComponent.lightColorAmbient.y, lightComponent.lightColorAmbient.z, true);
program.setFloat4("lightProperties", lightComponent.lightAmbientIntensity, lightComponent.lightDiffuseIntensity,
lightComponent.lightSpecularIntensity, lightComponent.lightSpecularPower, true);
}
if (lightComponent.lightType == LightComponent.LightType.POINT) {
if (!geometryOnly) {
program.setFloat4("lightExtendedProperties", lightComponent.lightAttenuationRange * 0.975f, lightComponent.lightAttenuationFalloff, 0.0f, 0.0f, true);
}
LightGeometryHelper.renderSphereGeometry();
} else if (lightComponent.lightType == LightComponent.LightType.DIRECTIONAL) {
// Directional lights cover all pixels on the screen
DefaultRenderingProcess.getInstance().renderFullscreenQuad();
}
if (!geometryOnly) {
if (lightComponent.lightType == LightComponent.LightType.POINT) {
program.deactivateFeature(ShaderProgramFeature.FEATURE_LIGHT_POINT);
} else if (lightComponent.lightType == LightComponent.LightType.DIRECTIONAL) {
program.deactivateFeature(ShaderProgramFeature.FEATURE_LIGHT_DIRECTIONAL);
}
}
return true;
}
public void renderWorldReflection(Camera camera) {
PerformanceMonitor.startActivity("Render World (Reflection)");
camera.lookThroughNormalized();
skysphere.render(camera);
Material chunkShader = Assets.getMaterial("engine:prog.chunk");
chunkShader.activateFeature(ShaderProgramFeature.FEATURE_USE_FORWARD_LIGHTING);
if (config.getRendering().isReflectiveWater()) {
camera.lookThrough();
glEnable(GL_LIGHT0);
while (renderQueueChunksOpaqueReflection.size() > 0) {
renderChunk(renderQueueChunksOpaqueReflection.poll(), ChunkMesh.RenderPhase.OPAQUE, camera, ChunkRenderMode.REFLECTION);
}
}
chunkShader.deactivateFeature(ShaderProgramFeature.FEATURE_USE_FORWARD_LIGHTING);
PerformanceMonitor.endActivity();
}
private void renderShadowMap(Camera camera) {
PerformanceMonitor.startActivity("Render World (Shadow Map)");
glDisable(GL_CULL_FACE);
camera.lookThrough();
while (renderQueueChunksOpaqueShadow.size() > 0) {
renderChunk(renderQueueChunksOpaqueShadow.poll(), ChunkMesh.RenderPhase.OPAQUE, camera, ChunkRenderMode.SHADOW_MAP);
}
for (RenderSystem renderer : systemManager.iterateRenderSubscribers()) {
renderer.renderShadows();
}
glEnable(GL_CULL_FACE);
PerformanceMonitor.endActivity();
}
private void renderChunk(RenderableChunk chunk, ChunkMesh.RenderPhase phase, Camera camera, ChunkRenderMode mode) {
if (chunk.getMesh() != null) {
Material shader = null;
final Vector3f cameraPosition = camera.getPosition();
final Vector3f chunkPositionRelToCamera =
new Vector3f(chunk.getPosition().x * ChunkConstants.SIZE_X - cameraPosition.x,
chunk.getPosition().y * ChunkConstants.SIZE_Y - cameraPosition.y,
chunk.getPosition().z * ChunkConstants.SIZE_Z - cameraPosition.z);
if (mode == ChunkRenderMode.DEFAULT || mode == ChunkRenderMode.REFLECTION) {
shader = Assets.getMaterial("engine:prog.chunk");
shader.enable();
if (phase == ChunkMesh.RenderPhase.REFRACTIVE) {
shader.activateFeature(ShaderProgramFeature.FEATURE_REFRACTIVE_PASS);
} else if (phase == ChunkMesh.RenderPhase.ALPHA_REJECT) {
shader.activateFeature(ShaderProgramFeature.FEATURE_ALPHA_REJECT);
}
shader.setFloat3("chunkPositionWorld", chunk.getPosition().x * ChunkConstants.SIZE_X,
chunk.getPosition().y * ChunkConstants.SIZE_Y, chunk.getPosition().z * ChunkConstants.SIZE_Z);
shader.setFloat("animated", chunk.isAnimated() ? 1.0f : 0.0f);
if (mode == ChunkRenderMode.REFLECTION) {
shader.setFloat("clip", camera.getClipHeight());
} else {
shader.setFloat("clip", 0.0f);
}
} else if (mode == ChunkRenderMode.SHADOW_MAP) {
shader = Assets.getMaterial("engine:prog.shadowMap");
shader.enable();
} else if (mode == ChunkRenderMode.Z_PRE_PASS) {
CoreRegistry.get(ShaderManager.class).disableShader();
}
GL11.glPushMatrix();
GL11.glTranslatef(chunkPositionRelToCamera.x, chunkPositionRelToCamera.y, chunkPositionRelToCamera.z);
for (int i = 0; i < VERTICAL_SEGMENTS; i++) {
if (!chunk.getMesh()[i].isEmpty()) {
if (config.getRendering().getDebug().isRenderChunkBoundingBoxes()) {
AABBRenderer aabbRenderer = new AABBRenderer(chunk.getSubMeshAABB(i));
aabbRenderer.renderLocally(1f);
statRenderedTriangles += 12;
}
if (shader != null) {
shader.enable();
}
chunk.getMesh()[i].render(phase);
statRenderedTriangles += chunk.getMesh()[i].triangleCount();
}
}
if (mode == ChunkRenderMode.DEFAULT || mode == ChunkRenderMode.REFLECTION) {
// eclipse is paranoid about this - it thinks that shader could be null here
if (shader != null) {
if (phase == ChunkMesh.RenderPhase.REFRACTIVE) {
shader.deactivateFeature(ShaderProgramFeature.FEATURE_REFRACTIVE_PASS);
} else if (phase == ChunkMesh.RenderPhase.ALPHA_REJECT) {
shader.deactivateFeature(ShaderProgramFeature.FEATURE_ALPHA_REJECT);
}
}
}
GL11.glPopMatrix();
} else {
statChunkNotReady++;
}
}
public float getSmoothedPlayerSunlightValue() {
return smoothedPlayerSunlightValue;
}
public float getSunlightValue() {
return getSunlightValueAt(new Vector3f(getActiveCamera().getPosition()));
}
public float getBlockLightValue() {
return getBlockLightValueAt(new Vector3f(getActiveCamera().getPosition()));
}
public float getRenderingLightValueAt(Vector3f pos) {
float rawLightValueSun = worldProvider.getSunlight(pos) / 15.0f;
float rawLightValueBlock = worldProvider.getLight(pos) / 15.0f;
float lightValueSun = (float) Math.pow(BLOCK_LIGHT_SUN_POW, (1.0f - rawLightValueSun) * 16.0f) * rawLightValueSun;
lightValueSun *= getDaylight();
// TODO: Hardcoded factor and value to compensate for daylight tint and night brightness
lightValueSun *= 0.9f;
lightValueSun += 0.05f;
float lightValueBlock = (float) Math.pow(BLOCK_LIGHT_POW, (1.0f - rawLightValueBlock) * 16.0f) * rawLightValueBlock * BLOCK_INTENSITY_FACTOR;
return Math.max(lightValueBlock, lightValueSun);
}
public float getSunlightValueAt(Vector3f pos) {
float sunlight = worldProvider.getSunlight(pos) / 15.0f;
sunlight *= getDaylight();
return sunlight;
}
public float getBlockLightValueAt(Vector3f pos) {
return worldProvider.getLight(pos) / 15.0f;
}
public void update(float delta) {
PerformanceMonitor.startActivity("Update Tick");
updateTick(delta);
PerformanceMonitor.endActivity();
PerformanceMonitor.startActivity("Complete chunk update");
chunkProvider.completeUpdate();
PerformanceMonitor.endActivity();
PerformanceMonitor.startActivity("Update Lighting");
worldProvider.processPropagation();
PerformanceMonitor.endActivity();
PerformanceMonitor.startActivity("Begin chunk update");
chunkProvider.beginUpdate();
PerformanceMonitor.endActivity();
PerformanceMonitor.startActivity("Update Close Chunks");
updateChunksInProximity(calculateViewRegion(config.getRendering().getViewDistance()));
PerformanceMonitor.endActivity();
if (activeCamera != null) {
activeCamera.update(delta);
}
if (lightCamera != null) {
positionLightCamera();
lightCamera.update(delta);
}
smoothedPlayerSunlightValue = TeraMath.lerp(smoothedPlayerSunlightValue, getSunlightValue(), delta);
}
public void positionLightCamera() {
// Shadows are rendered around the player so...
Vector3f lightPosition = new Vector3f(activeCamera.getPosition().x, 0.0f, activeCamera.getPosition().z);
// Project the camera position to light space and make sure it is only moved in texel steps (avoids flickering when moving the camera)
float texelSize = 1.0f / config.getRendering().getShadowMapResolution();
texelSize *= 2.0f;
lightCamera.getViewProjectionMatrix().transform(lightPosition);
lightPosition.set(TeraMath.fastFloor(lightPosition.x / texelSize) * texelSize, 0.0f, TeraMath.fastFloor(lightPosition.z / texelSize) * texelSize);
lightCamera.getInverseViewProjectionMatrix().transform(lightPosition);
// ... we position our new camera at the position of the player and move it
// quite a bit into the direction of the sun (our main light).
// Make sure the sun does not move too often since it causes massive shadow flickering (from hell to the max)!
float stepSize = 50f;
Vector3f sunDirection = skysphere.getQuantizedSunDirection(stepSize);
Vector3f sunPosition = new Vector3f(sunDirection);
sunPosition.scale(256.0f + 64.0f);
lightPosition.add(sunPosition);
lightCamera.getPosition().set(lightPosition);
// and adjust it to look from the sun direction into the direction of our player
Vector3f negSunDirection = new Vector3f(sunDirection);
negSunDirection.scale(-1.0f);
lightCamera.getViewingDirection().set(negSunDirection);
}
public boolean isHeadUnderWater() {
Vector3f cameraPos = new Vector3f(CoreRegistry.get(WorldRenderer.class).getActiveCamera().getPosition());
// Compensate for waves
if (config.getRendering().isAnimateWater()) {
cameraPos.y -= RenderHelper.evaluateOceanHeightAtPosition(cameraPos, worldProvider.getTime().getDays());
}
if (worldProvider.isBlockRelevant(new Vector3f(cameraPos))) {
Block block = worldProvider.getBlock(new Vector3f(cameraPos));
return block.isLiquid();
}
return false;
}
/**
* Updates the tick variable that animation is based on
*/
private void updateTick(float delta) {
tick += delta * 1000;
}
/**
* Chunk position of the player.
*
* @return The player offset chunk
*/
private Vector3i calcCamChunkOffset() {
return new Vector3i((int) (getActiveCamera().getPosition().x / ChunkConstants.SIZE_X),
(int) (getActiveCamera().getPosition().y / ChunkConstants.SIZE_Y),
(int) (getActiveCamera().getPosition().z / ChunkConstants.SIZE_Z));
}
/**
* Sets a new player and spawns him at the spawning point.
*
* @param p The player
*/
public void setPlayer(LocalPlayer p) {
player = p;
updateChunksInProximity(calculateViewRegion(config.getRendering().getViewDistance()));
}
public void changeViewDistance(ViewDistance viewingDistance) {
logger.info("New Viewing Distance: {}", viewingDistance);
updateChunksInProximity(calculateViewRegion(viewingDistance));
}
private Region3i calculateViewRegion(ViewDistance viewingDistance) {
Vector3i newChunkPos = calcCamChunkOffset();
Vector3i distance = viewingDistance.getChunkDistance();
return Region3i.createFromCenterExtents(newChunkPos, new Vector3i(distance.x / 2, distance.y / 2, distance.z / 2));
}
public ChunkProvider getChunkProvider() {
return chunkProvider;
}
/**
* Disposes this world.
*/
public void dispose() {
worldProvider.dispose();
CoreRegistry.get(AudioManager.class).stopAllSounds();
}
/**
* @return true if pregeneration is complete
*/
public boolean pregenerateChunks() {
boolean complete = true;
Vector3i newChunkPos = calcCamChunkOffset();
Vector3i viewingDistance = config.getRendering().getViewDistance().getChunkDistance();
chunkProvider.completeUpdate();
chunkProvider.beginUpdate();
for (Vector3i pos : Region3i.createFromCenterExtents(newChunkPos, new Vector3i(viewingDistance.x / 2, viewingDistance.y / 2, viewingDistance.z / 2))) {
RenderableChunk chunk = chunkProvider.getChunk(pos);
if (chunk == null) {
complete = false;
} else if (chunk.isDirty()) {
ChunkView view = worldProvider.getLocalView(chunk.getPosition());
if (view == null) {
continue;
}
chunk.setDirty(false);
ChunkMesh[] newMeshes = new ChunkMesh[VERTICAL_SEGMENTS];
for (int seg = 0; seg < VERTICAL_SEGMENTS; seg++) {
newMeshes[seg] = chunkTessellator.generateMesh(view,
ChunkConstants.SIZE_Y / VERTICAL_SEGMENTS, seg * (ChunkConstants.SIZE_Y / VERTICAL_SEGMENTS));
}
chunk.setPendingMesh(newMeshes);
if (chunk.getPendingMesh() != null) {
for (int j = 0; j < chunk.getPendingMesh().length; j++) {
chunk.getPendingMesh()[j].generateVBOs();
}
if (chunk.getMesh() != null) {
for (int j = 0; j < chunk.getMesh().length; j++) {
chunk.getMesh()[j].dispose();
}
}
chunk.setMesh(chunk.getPendingMesh());
chunk.setPendingMesh(null);
}
return false;
}
}
return complete;
}
@Override
public String getMetrics() {
StringBuilder builder = new StringBuilder();
builder.append("Dirty Chunks: ");
builder.append(statDirtyChunks);
builder.append("\n");
builder.append("Ignored Phases: ");
builder.append(statIgnoredPhases);
builder.append("\n");
builder.append("Visible Chunks: ");
builder.append(statVisibleChunks);
builder.append("\n");
builder.append("Empty Mesh Chunks: ");
builder.append(statChunkMeshEmpty);
builder.append("\n");
builder.append("Unready Chunks: ");
builder.append(statChunkNotReady);
builder.append("\n");
return builder.toString();
}
@Override
public String toString() {
float renderedTriangles = 0.0f;
String renderedTrianglesUnit = "";
if (statRenderedTriangles > 1000000.0f) {
renderedTriangles = statRenderedTriangles / 1000000.0f;
renderedTrianglesUnit = "mil";
} else if (statRenderedTriangles > 1000.0f) {
renderedTriangles = statRenderedTriangles / 1000.0f;
renderedTrianglesUnit = "k";
}
return String.format("world (db: %d, b: %s, t: %.1f, exposure: %.1f"
+ ", dirty: %d, ign: %d, vis: %d, tri: %.1f%s, empty: %d, !rdy: %d, seed: \"%s\", title: \"%s\")",
((MeshRenderer) CoreRegistry.get(ComponentSystemManager.class).get("engine:MeshRenderer")).getLastRendered(),
worldProvider.getTime().getDays(),
DefaultRenderingProcess.getInstance().getExposure(),
statDirtyChunks,
statIgnoredPhases,
statVisibleChunks,
renderedTriangles,
renderedTrianglesUnit,
statChunkMeshEmpty,
statChunkNotReady,
worldProvider.getSeed(),
worldProvider.getTitle()
);
}
public LocalPlayer getPlayer() {
return player;
}
public boolean isAABBVisible(Camera cam, AABB aabb) {
return cam.getViewFrustum().intersects(aabb);
}
public boolean isAABBVisible(AABB aabb) {
return isAABBVisible(activeCamera, aabb);
}
public boolean isChunkValidForRender(RenderableChunk c) {
return worldProvider.getLocalView(c.getPosition()) != null;
}
public boolean isChunkVisible(Camera cam, RenderableChunk c) {
return cam.getViewFrustum().intersects(c.getAABB());
}
public boolean isChunkVisibleLight(RenderableChunk c) {
return isChunkVisible(lightCamera, c);
}
public boolean isChunkVisible(RenderableChunk c) {
return isChunkVisible(activeCamera, c);
}
public boolean isChunkVisibleReflection(RenderableChunk c) {
return activeCamera.getViewFrustumReflected().intersects(c.getAABB());
}
public boolean isLightVisible(Vector3f positionViewSpace, LightComponent component) {
return component.lightType == LightComponent.LightType.DIRECTIONAL
|| activeCamera.getViewFrustum().intersects(positionViewSpace, component.lightAttenuationRange);
}
public float getDaylight() {
return skysphere.getDaylight();
}
public WorldProvider getWorldProvider() {
return worldProvider;
}
public Skysphere getSkysphere() {
return skysphere;
}
public Time getTime() {
return time;
}
public float getTick() {
return tick;
}
public BulletPhysics getBulletRenderer() {
return bulletPhysics;
}
public Camera getActiveCamera() {
return activeCamera;
}
public Camera getLightCamera() {
return lightCamera;
}
public WorldRenderingStage getCurrentRenderStage() {
return currentRenderingStage;
}
public Vector3f getTint() {
Vector3f cameraPos = getActiveCamera().getPosition();
Block block = worldProvider.getBlock(cameraPos);
return block.getTint();
}
private static class ChunkFrontToBackComparator implements Comparator<RenderableChunk> {
@Override
public int compare(RenderableChunk o1, RenderableChunk o2) {
double distance = distanceToCamera(o1);
double distance2 = distanceToCamera(o2);
if (o1 == null) {
return -1;
} else if (o2 == null) {
return 1;
}
if (distance == distance2) {
return 0;
}
return distance2 > distance ? -1 : 1;
}
}
private static class ChunkBackToFrontComparator implements Comparator<RenderableChunk> {
@Override
public int compare(RenderableChunk o1, RenderableChunk o2) {
double distance = distanceToCamera(o1);
double distance2 = distanceToCamera(o2);
if (o1 == null) {
return 1;
} else if (o2 == null) {
return -1;
}
if (distance == distance2) {
return 0;
}
return distance2 > distance ? 1 : -1;
}
}
}