/*
* Copyright 2017 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.engine.subsystem.headless.renderer;
import com.google.common.collect.Lists;
import org.terasology.config.Config;
import org.terasology.context.Context;
import org.terasology.logic.players.LocalPlayerSystem;
import org.terasology.math.Region3i;
import org.terasology.math.geom.Vector3f;
import org.terasology.math.geom.Vector3i;
import org.terasology.monitoring.PerformanceMonitor;
import org.terasology.rendering.assets.material.Material;
import org.terasology.rendering.cameras.Camera;
import org.terasology.rendering.cameras.SubmersibleCamera;
import org.terasology.rendering.world.viewDistance.ViewDistance;
import org.terasology.rendering.world.WorldRenderer;
import org.terasology.world.WorldProvider;
import org.terasology.world.chunks.ChunkConstants;
import org.terasology.world.chunks.ChunkProvider;
import org.terasology.world.chunks.RenderableChunk;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
public class HeadlessWorldRenderer implements WorldRenderer {
private static final int MAX_CHUNKS = ViewDistance.MEGA.getChunkDistance().x * ViewDistance.MEGA.getChunkDistance().y * ViewDistance.MEGA.getChunkDistance().z;
private WorldProvider worldProvider;
private ChunkProvider chunkProvider;
private Camera noCamera = new NullCamera(null, null);
/* CHUNKS */
private boolean pendingChunks;
private final List<RenderableChunk> chunksInProximity = Lists.newArrayListWithCapacity(MAX_CHUNKS);
private Vector3i chunkPos = new Vector3i();
private Config config;
public HeadlessWorldRenderer(Context context) {
this.worldProvider = context.get(WorldProvider.class);
this.chunkProvider = context.get(ChunkProvider.class);
LocalPlayerSystem localPlayerSystem = context.get(LocalPlayerSystem.class);
localPlayerSystem.setPlayerCamera(noCamera);
config = context.get(Config.class);
}
@Override
public float getSecondsSinceLastFrame() {
return 0;
}
@Override
public Material getMaterial(String assetId) {
return null;
}
@Override
public boolean isFirstRenderingStageForCurrentFrame() {
return false;
}
@Override
public void onChunkLoaded(Vector3i pos) {
}
@Override
public void onChunkUnloaded(Vector3i pos) {
}
@Override
public SubmersibleCamera getActiveCamera() {
return (SubmersibleCamera) noCamera;
}
@Override
public Camera getLightCamera() {
return noCamera;
}
@Override
public void update(float delta) {
worldProvider.processPropagation();
// Free unused space
PerformanceMonitor.startActivity("Update Chunk Cache");
chunkProvider.completeUpdate();
chunkProvider.beginUpdate();
PerformanceMonitor.endActivity();
PerformanceMonitor.startActivity("Update Close Chunks");
updateChunksInProximity(false);
PerformanceMonitor.endActivity();
}
@Override
public void increaseTrianglesCount(int increase) {
// we are not going to count triangles in headless
}
@Override
public void increaseNotReadyChunkCount(int increase) {
// we are not going to count not ready chunks in headless
}
@Override
public void render(RenderingStage mono) {
// TODO Auto-generated method stub
}
@Override
public void requestTaskListRefresh() {
}
@Override
public void dispose() {
worldProvider.dispose();
}
@Override
public boolean pregenerateChunks() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setViewDistance(ViewDistance viewDistance) {
// TODO Auto-generated method stub
}
@Override
public float getRenderingLightIntensityAt(Vector3f vector3f) {
// TODO Auto-generated method stub
return 0;
}
@Override
public float getMainLightIntensityAt(Vector3f worldPos) {
// TODO Auto-generated method stub
return 0;
}
@Override
public float getBlockLightIntensityAt(Vector3f worldPos) {
// TODO Auto-generated method stub
return 0;
}
@Override
public float getTimeSmoothedMainLightIntensity() {
// TODO Auto-generated method stub
return 0;
}
@Override
public float getMillisecondsSinceRenderingStart() {
// TODO Auto-generated method stub
return 0;
}
@Override
public RenderingStage getCurrentRenderStage() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getMetrics() {
return "";
}
/**
* Updates the list of chunks around the player.
*
* @param force Forces the update
* @return True if the list was changed
*/
public boolean updateChunksInProximity(boolean force) {
Vector3i newChunkPos = calcCamChunkOffset();
// TODO: This should actually be done based on events from the ChunkProvider on new chunk availability/old chunk removal
boolean chunksCurrentlyPending = false;
if (!newChunkPos.equals(chunkPos) || force || pendingChunks) {
Vector3i viewingDistance = config.getRendering().getViewDistance().getChunkDistance();
Region3i viewRegion = Region3i.createFromCenterExtents(newChunkPos, new Vector3i(viewingDistance.x / 2, viewingDistance.y / 2, viewingDistance.z / 2));
if (chunksInProximity.size() == 0 || force || pendingChunks) {
// just add all visible chunks
chunksInProximity.clear();
for (Vector3i chunkPosition : viewRegion) {
RenderableChunk c = chunkProvider.getChunk(chunkPosition);
if (c != null && worldProvider.getLocalView(c.getPosition()) != null) {
chunksInProximity.add(c);
} else {
chunksCurrentlyPending = true;
}
}
} else {
Region3i oldRegion = Region3i.createFromCenterExtents(chunkPos, new Vector3i(viewingDistance.x / 2, viewingDistance.y / 2, viewingDistance.z / 2));
Iterator<Vector3i> chunksForRemove = oldRegion.subtract(viewRegion);
// remove
while (chunksForRemove.hasNext()) {
Vector3i r = chunksForRemove.next();
RenderableChunk c = chunkProvider.getChunk(r);
if (c != null) {
chunksInProximity.remove(c);
c.disposeMesh();
}
}
// add
for (Vector3i chunkPosition : viewRegion) {
RenderableChunk c = chunkProvider.getChunk(chunkPosition);
if (c != null && worldProvider.getLocalView(c.getPosition()) != null) {
chunksInProximity.add(c);
} else {
chunksCurrentlyPending = true;
}
}
}
chunkPos.set(newChunkPos);
pendingChunks = chunksCurrentlyPending;
Collections.sort(chunksInProximity, new ChunkFrontToBackComparator());
return true;
}
return false;
}
/**
* Chunk position of the player.
*
* @return The player offset on the x-axis
*/
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));
}
private float distanceToCamera(RenderableChunk chunk) {
Vector3f result = new Vector3f((chunk.getPosition().x + 0.5f) * ChunkConstants.SIZE_X, 0, (chunk.getPosition().z + 0.5f) * ChunkConstants.SIZE_Z);
Vector3f cameraPos = getActiveCamera().getPosition();
result.x -= cameraPos.x;
result.z -= cameraPos.z;
return result.length();
}
private 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;
}
}
}