Package org.terasology.rendering.world

Source Code of org.terasology.rendering.world.WorldRendererLwjgl$ChunkBackToFrontComparator

/*
* 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;
        }
    }
}
TOP

Related Classes of org.terasology.rendering.world.WorldRendererLwjgl$ChunkBackToFrontComparator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.