/* * Copyright 2012 Benjamin Glatzel <benjamin.glatzel@me.com> * * 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 static org.lwjgl.opengl.GL11.GL_BLEND; import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; 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_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.glClear; import static org.lwjgl.opengl.GL11.glColorMask; import static org.lwjgl.opengl.GL11.glCullFace; 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; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.vecmath.Vector3d; import javax.vecmath.Vector3f; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.GL11; import org.spout.api.generator.biome.Biome; import org.spout.api.geo.World; import org.spout.api.geo.cuboid.Chunk; import org.spout.engine.world.SpoutChunk; import org.spout.engine.world.SpoutWorld; import org.terasology.componentSystem.RenderSystem; import org.terasology.componentSystem.controllers.LocalPlayerSystem; import org.terasology.components.PlayerComponent; import org.terasology.entitySystem.EntityManager; import org.terasology.game.ComponentSystemManager; import org.terasology.game.CoreRegistry; import org.terasology.game.TerasologyEngine; import org.terasology.logic.LocalPlayer; import org.terasology.logic.manager.AudioManager; import org.terasology.logic.manager.Config; import org.terasology.logic.manager.PathManager; import org.terasology.logic.manager.PostProcessingRenderer; import org.terasology.logic.manager.ShaderManager; import org.terasology.logic.world.ChunkStore; import org.terasology.logic.world.WorldProvider; import org.terasology.math.Rect2i; import org.terasology.math.TeraMath; import org.terasology.model.structures.AABB; import org.terasology.performanceMonitor.PerformanceMonitor; import org.terasology.rendering.cameras.Camera; import org.terasology.rendering.cameras.DefaultCamera; import org.terasology.rendering.interfaces.IGameObject; import org.terasology.rendering.physics.BulletPhysicsRenderer; import org.terasology.rendering.primitives.ChunkMesh; import org.terasology.rendering.primitives.ChunkTessellator; import org.terasology.rendering.shader.ShaderProgram; import org.terasology.teraspout.TeraBlock; import org.terasology.teraspout.TeraChunk; import com.google.common.collect.Lists; /** * 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 WorldRenderer implements IGameObject { public static final int MAX_ANIMATED_CHUNKS = 64; public static final int MAX_BILLBOARD_CHUNKS = 64; public static final int VERTICAL_SEGMENTS = Config.getInstance().getVerticalChunkMeshSegments(); private final TerasologyEngine engine; /* WORLD PROVIDER */ private final SpoutWorld world; private ChunkStore chunkStore; private Logger _logger = Logger.getLogger(getClass().getName()); /* PLAYER */ private LocalPlayer _player; /* CAMERA */ public enum CAMERA_MODE { PLAYER, SPAWN } private CAMERA_MODE _cameraMode = CAMERA_MODE.PLAYER; private Camera _spawnCamera = new DefaultCamera(); private DefaultCamera _defaultCamera = new DefaultCamera(); private Camera _activeCamera = _defaultCamera; /* CHUNKS */ private ChunkTessellator _chunkTesselator; private boolean _pendingChunks = false; private final List<SpoutChunk> _chunksInProximity = Lists.newArrayList(); private int _chunkPosX, _chunkPosZ; /* RENDERING */ private final LinkedList<IGameObject> _renderQueueTransparent = Lists.newLinkedList(); private final LinkedList<SpoutChunk> _renderQueueChunksOpaque = Lists.newLinkedList(); private final PriorityQueue<SpoutChunk> _renderQueueChunksSortedWater = new PriorityQueue<SpoutChunk>(16 * 16, new ChunkProximityComparator()); private final PriorityQueue<SpoutChunk> _renderQueueChunksSortedBillboards = new PriorityQueue<SpoutChunk>(16 * 16, new ChunkProximityComparator()); /* HORIZON */ private final Skysphere _skysphere; /* TICKING */ private float _tick = 0; private int _tickTock = 0; private long _lastTick; /* UPDATING */ private final ChunkUpdateManager _chunkUpdateManager; /* PHYSICS */ private final BulletPhysicsRenderer _bulletRenderer; /* BLOCK GRID */ private final BlockGrid _blockGrid; /* STATISTICS */ private int _statDirtyChunks = 0, _statVisibleChunks = 0, _statIgnoredPhases = 0; private int _statChunkMeshEmpty, _statChunkNotReady, _statRenderedTriangles; /* OTHER SETTINGS */ private boolean _wireframe; private ComponentSystemManager _systemManager; /** * Initializes a new (local) world for the single player mode. * * @param title The title/description of the world * @param seed The seed string used to generate the terrain */ public WorldRenderer(TerasologyEngine engine, SpoutWorld world, EntityManager manager, LocalPlayerSystem localPlayerSystem) { this.engine = engine; this.world = world; _chunkTesselator = new ChunkTessellator(world); _skysphere = new Skysphere(this); _chunkUpdateManager = new ChunkUpdateManager(_chunkTesselator, world); _blockGrid = new BlockGrid(); _bulletRenderer = new BulletPhysicsRenderer(this); // TODO: won't need localPlayerSystem here once camera is in the ES proper localPlayerSystem.setPlayerCamera(_defaultCamera); _systemManager = CoreRegistry.get(ComponentSystemManager.class); } public TerasologyEngine getEngine() { return engine; } /** * 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) { int newChunkPosX = calcCamChunkOffsetX(); int newChunkPosZ = calcCamChunkOffsetZ(); // TODO: This should actually be done based on events from the ChunkProvider on new chunk availability/old chunk removal int viewingDistance = Config.getInstance().getActiveViewingDistance(); if (_chunkPosX != newChunkPosX || _chunkPosZ != newChunkPosZ || force || _pendingChunks) { // just add all visible chunks if (_chunksInProximity.size() == 0 || force || _pendingChunks) { _chunksInProximity.clear(); for (int x = -(viewingDistance / 2); x < viewingDistance / 2; x++) { for (int z = -(viewingDistance / 2); z < viewingDistance / 2; z++) { SpoutChunk c = world.getChunk(newChunkPosX + x, 0, newChunkPosZ + z); if (c != null) { // TODO make chunks wait for loading? _chunksInProximity.add(c); } else { _pendingChunks = true; } } } } // adjust proximity chunk list else { int vd2 = viewingDistance / 2; Rect2i oldView = new Rect2i(_chunkPosX - vd2, _chunkPosZ - vd2, viewingDistance, viewingDistance); Rect2i newView = new Rect2i(newChunkPosX - vd2, newChunkPosZ - vd2, viewingDistance, viewingDistance); // remove List<Rect2i> removeRects = Rect2i.subtractEqualsSized(oldView, newView); for (Rect2i r : removeRects) { for (int x = r.minX(); x < r.maxX(); ++x) { for (int y = r.minY(); y < r.maxY(); ++y) { SpoutChunk c = world.getChunk(x, 0, y); _chunksInProximity.remove(c); } } } // add List<Rect2i> addRects = Rect2i.subtractEqualsSized(newView, oldView); for (Rect2i r : addRects) { for (int x = r.minX(); x < r.maxX(); ++x) { for (int y = r.minY(); y < r.maxY(); ++y) { SpoutChunk c = world.getChunk(x, 0, y); if (c != null) { _chunksInProximity.add(c); } else { _pendingChunks = true; } } } } } _chunkPosX = newChunkPosX; _chunkPosZ = newChunkPosZ; Collections.sort(_chunksInProximity, new ChunkProximityComparator()); return true; } return false; } private static class ChunkProximityComparator implements Comparator<SpoutChunk> { @Override public int compare(SpoutChunk o1, SpoutChunk 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 float distanceToCamera(SpoutChunk chunk) { Vector3f result = new Vector3f((chunk.getX() + 0.5f) * Chunk.BLOCKS.SIZE, 0, (chunk.getZ() + 0.5f) * Chunk.BLOCKS.SIZE); Vector3d cameraPos = CoreRegistry.get(WorldRenderer.class).getActiveCamera().getPosition(); result.x -= cameraPos.x; result.z -= cameraPos.z; return result.length(); } } private Vector3f getPlayerPosition() { if (_player != null) { return _player.getPosition(); } return new Vector3f(); } /** * Updates the currently visible chunks (in sight of the player). */ public void updateAndQueueVisibleChunks() { _statDirtyChunks = 0; _statVisibleChunks = 0; _statIgnoredPhases = 0; for (int i = 0; i < _chunksInProximity.size(); i++) { SpoutChunk chunk = _chunksInProximity.get(i); TeraChunk c = engine.getTeraSpout().getChunk(chunk); ChunkMesh[] mesh = c.getMesh(); if (isChunkVisible(c) && isChunkValidForRender(c)) { if (triangleCount(mesh, ChunkMesh.RENDER_PHASE.OPAQUE) > 0) _renderQueueChunksOpaque.add(chunk); else _statIgnoredPhases++; if (triangleCount(mesh, ChunkMesh.RENDER_PHASE.WATER_AND_ICE) > 0) _renderQueueChunksSortedWater.add(chunk); else _statIgnoredPhases++; if (triangleCount(mesh, ChunkMesh.RENDER_PHASE.BILLBOARD_AND_TRANSLUCENT) > 0 && i < MAX_BILLBOARD_CHUNKS) _renderQueueChunksSortedBillboards.add(chunk); else _statIgnoredPhases++; if (i < MAX_ANIMATED_CHUNKS) c.setAnimated(true); else c.setAnimated(false); if (c.getPendingMesh() != null) { for (int j = 0; j < c.getPendingMesh().length; j++) { c.getPendingMesh()[j].generateVBOs(); } if (c.getMesh() != null) { for (int j = 0; j < c.getMesh().length; j++) { c.getMesh()[j].dispose(); } } c.setMesh(c.getPendingMesh()); c.setPendingMesh(null); } if ((c.isDirty() || mesh == null) && isChunkValidForRender(c)) { _statDirtyChunks++; _chunkUpdateManager.queueChunkUpdate(c, ChunkUpdateManager.UPDATE_TYPE.DEFAULT); } _statVisibleChunks++; } else if (i > Config.getInstance().getMaxChunkVBOs()) { if (mesh != null) { // Make sure not too many chunk VBOs are available in the video memory at the same time // Otherwise VBOs are moved into system memory which is REALLY slow and causes lag for (ChunkMesh m : mesh) { m.dispose(); } c.setMesh(null); } } } } private int triangleCount(ChunkMesh[] mesh, ChunkMesh.RENDER_PHASE type) { int count = 0; if (mesh != null) for (int i = 0; i < mesh.length; i++) count += mesh[i].triangleCount(type); return count; } private void resetStats() { _statChunkMeshEmpty = 0; _statChunkNotReady = 0; _statRenderedTriangles = 0; } /** * Renders the world. */ @Override public void render() { _renderQueueTransparent.add(_bulletRenderer); resetStats(); updateAndQueueVisibleChunks(); if (Config.getInstance().isComplexWater()) { PostProcessingRenderer.getInstance().beginRenderReflectedScene(); glCullFace(GL11.GL_FRONT); getActiveCamera().setReflected(true); renderWorldReflection(getActiveCamera()); getActiveCamera().setReflected(false); glCullFace(GL11.GL_BACK); PostProcessingRenderer.getInstance().endRenderReflectedScene(); } PostProcessingRenderer.getInstance().beginRenderScene(); renderWorld(getActiveCamera()); PostProcessingRenderer.getInstance().endRenderScene(); /* RENDER THE FINAL POST-PROCESSED SCENE */ PerformanceMonitor.startActivity("Render Post-Processing"); PostProcessingRenderer.getInstance().renderScene(); PerformanceMonitor.endActivity(); if (_cameraMode == CAMERA_MODE.PLAYER) { glClear(GL_DEPTH_BUFFER_BIT); glPushMatrix(); glLoadIdentity(); _activeCamera.loadProjectionMatrix(80f); PerformanceMonitor.startActivity("Render First Person"); for (RenderSystem renderer : _systemManager.iterateRenderSubscribers()) { renderer.renderFirstPerson(); } PerformanceMonitor.endActivity(); glPopMatrix(); } } public void renderWorld(Camera camera) { /* SKYSPHERE */ PerformanceMonitor.startActivity("Render Sky"); camera.lookThroughNormalized(); _skysphere.render(); PerformanceMonitor.endActivity(); /* WORLD RENDERING */ PerformanceMonitor.startActivity("Render World"); camera.lookThrough(); glEnable(GL_LIGHT0); boolean headUnderWater; headUnderWater = _cameraMode == CAMERA_MODE.PLAYER && isUnderwater(); if (_wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); PerformanceMonitor.startActivity("RenderOpaque"); for (RenderSystem renderer : _systemManager.iterateRenderSubscribers()) { renderer.renderOpaque(); } PerformanceMonitor.endActivity(); PerformanceMonitor.startActivity("Render ChunkOpaque"); /* * FIRST RENDER PASS: OPAQUE ELEMENTS */ while (_renderQueueChunksOpaque.size() > 0) renderChunk(_renderQueueChunksOpaque.poll(), ChunkMesh.RENDER_PHASE.OPAQUE, camera); PerformanceMonitor.endActivity(); PerformanceMonitor.startActivity("Render ChunkTransparent"); /* * SECOND RENDER PASS: BILLBOARDS */ glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); while (_renderQueueChunksSortedBillboards.size() > 0) renderChunk(_renderQueueChunksSortedBillboards.poll(), ChunkMesh.RENDER_PHASE.BILLBOARD_AND_TRANSLUCENT, camera); PerformanceMonitor.endActivity(); PerformanceMonitor.startActivity("Render Transparent"); while (_renderQueueTransparent.size() > 0) _renderQueueTransparent.poll().render(); for (RenderSystem renderer : _systemManager.iterateRenderSubscribers()) { renderer.renderTransparent(); } PerformanceMonitor.endActivity(); PerformanceMonitor.startActivity("Render ChunkWaterIce"); // Make sure the water surface is rendered if the player is swimming if (headUnderWater) { glDisable(GL11.GL_CULL_FACE); } /* * THIRD (AND FOURTH) RENDER PASS: WATER AND ICE */ while (_renderQueueChunksSortedWater.size() > 0) { SpoutChunk c = _renderQueueChunksSortedWater.poll(); for (int j = 0; j < 2; j++) { if (j == 0) { glColorMask(false, false, false, false); renderChunk(c, ChunkMesh.RENDER_PHASE.WATER_AND_ICE, camera); } else { glColorMask(true, true, true, true); renderChunk(c, ChunkMesh.RENDER_PHASE.WATER_AND_ICE, camera); } } } for (RenderSystem renderer : _systemManager.iterateRenderSubscribers()) { renderer.renderOverlay(); } glDisable(GL_BLEND); if (headUnderWater) glEnable(GL11.GL_CULL_FACE); if (_wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glDisable(GL_LIGHT0); PerformanceMonitor.endActivity(); } public void renderWorldReflection(Camera camera) { PerformanceMonitor.startActivity("Render Sky"); camera.lookThroughNormalized(); _skysphere.render(); camera.lookThrough(); glEnable(GL_LIGHT0); for (SpoutChunk c : _renderQueueChunksOpaque) renderChunk(c, ChunkMesh.RENDER_PHASE.OPAQUE, camera); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); for (SpoutChunk c : _renderQueueChunksSortedBillboards) renderChunk(c, ChunkMesh.RENDER_PHASE.BILLBOARD_AND_TRANSLUCENT, camera); for (IGameObject g : _renderQueueTransparent) g.render(); glDisable(GL_BLEND); glDisable(GL_LIGHT0); } private void renderChunk(SpoutChunk sc, ChunkMesh.RENDER_PHASE phase, Camera camera) { TeraChunk chunk = engine.getTeraSpout().getChunk(sc); if (chunk.getChunkState() == TeraChunk.State.COMPLETE && chunk.getMesh() != null) { ShaderProgram shader = ShaderManager.getInstance().getShaderProgram("chunk"); // Transfer the world offset of the chunk to the shader for various effects shader.setFloat3("chunkOffset", (float) (chunk.getPos().x * Chunk.BLOCKS.SIZE), (float) (chunk.getPos().y * Chunk.BLOCKS.SIZE), (float) (chunk.getPos().z * Chunk.BLOCKS.SIZE)); shader.setFloat("animated", chunk.getAnimated() ? 1.0f: 0.0f); shader.setFloat("clipHeight", camera.getClipHeight()); GL11.glPushMatrix(); Vector3d cameraPosition = camera.getPosition(); GL11.glTranslated(chunk.getPos().x * Chunk.BLOCKS.SIZE - cameraPosition.x, chunk.getPos().y * Chunk.BLOCKS.SIZE - cameraPosition.y, chunk.getPos().z * Chunk.BLOCKS.SIZE - cameraPosition.z); for (int i = 0; i < VERTICAL_SEGMENTS; i++) { if (!chunk.getMesh()[i].isEmpty()) { if (Config.getInstance().isRenderChunkBoundingBoxes()) { chunk.getSubMeshAABB(i).renderLocally(1f); _statRenderedTriangles += 12; } shader.enable(); chunk.getMesh()[i].render(phase); _statRenderedTriangles += chunk.getMesh()[i].triangleCount(); } } GL11.glPopMatrix(); } else { _statChunkNotReady++; } } public float getRenderingLightValue() { return getRenderingLightValueAt(new Vector3f(getActiveCamera().getPosition())); } public float getRenderingLightValueAt(Vector3f pos) { float lightValueSun = world.getBlockSkyLight((int) pos.x,(int) pos.y, (int) pos.z) / 255f; lightValueSun /= 15.0f; lightValueSun *= getDaylight(); float lightValueBlock = world.getBlockLight((int) pos.x, (int) pos.y, (int) pos.z) / 255f; lightValueBlock /= 15f; return (float) TeraMath.clamp(lightValueSun + lightValueBlock * (1.0 - lightValueSun)); } @Override public void update(float delta) { PerformanceMonitor.startActivity("Cameras"); animateSpawnCamera(delta); _spawnCamera.update(delta); PerformanceMonitor.endActivity(); PerformanceMonitor.startActivity("Update Close Chunks"); updateChunksInProximity(false); PerformanceMonitor.endActivity(); PerformanceMonitor.startActivity("Skysphere"); _skysphere.update(delta); PerformanceMonitor.endActivity(); if (_activeCamera != null) { _activeCamera.update(delta); } PerformanceMonitor.startActivity("Physics Renderer"); _bulletRenderer.update(delta); PerformanceMonitor.endActivity(); } private boolean isUnderwater() { Vector3d cameraPos = CoreRegistry.get(WorldRenderer.class).getActiveCamera().getPosition(); TeraBlock block = CoreRegistry.get(WorldProvider.class).getBlock(new Vector3f(cameraPos)); return block.isLiquid(); } private void animateSpawnCamera(double delta) { if (_player == null || !_player.isValid()) return; PlayerComponent player = _player.getEntity().getComponent(PlayerComponent.class); Vector3f cameraPosition = new Vector3f(player.spawnPosition); cameraPosition.y += 32; cameraPosition.x += Math.sin(getTick() * 0.0005f) * 32f; cameraPosition.z += Math.cos(getTick() * 0.0005f) * 32f; Vector3f playerToCamera = new Vector3f(); playerToCamera.sub(getPlayerPosition(), cameraPosition); double distanceToPlayer = playerToCamera.length(); Vector3f cameraDirection = new Vector3f(); if (distanceToPlayer > 64.0) { cameraDirection.sub(player.spawnPosition, cameraPosition); } else { cameraDirection.set(playerToCamera); } cameraDirection.normalize(); _spawnCamera.getPosition().set(cameraPosition); _spawnCamera.getViewingDirection().set(cameraDirection); } /** * Chunk position of the player. * * @return The player offset on the x-axis */ private int calcCamChunkOffsetX() { return (int) (getActiveCamera().getPosition().x / Chunk.BLOCKS.SIZE); } /** * Chunk position of the player. * * @return The player offset on the z-axis */ private int calcCamChunkOffsetZ() { return (int) (getActiveCamera().getPosition().z / Chunk.BLOCKS.SIZE); } /** * Sets a new player and spawns him at the spawning point. * * @param p The player */ public void setPlayer(LocalPlayer p) { // _player = p; // _chunkProvider.addRegionEntity(p.getEntity(), Config.getInstance().getActiveViewingDistance()); // updateChunksInProximity(true); // TODO } public void changeViewDistance(int viewingDistance) { // _logger.log(Level.INFO, "New Viewing Distance: " + viewingDistance); // if (_player != null) { // _chunkProvider.addRegionEntity(_player.getEntity(), viewingDistance); // } // updateChunksInProximity(true); // TODO } /** * Disposes this world. */ public void dispose() { AudioManager.getInstance().stopAllSounds(); // TODO save world if it isn't saved already } public void printScreen() { GL11.glReadBuffer(GL11.GL_FRONT); final int width = Display.getWidth(); final int height = Display.getHeight(); //int bpp = Display.getDisplayMode().getBitsPerPixel(); does return 0 - why? final int bpp = 4; final ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * bpp); // hardcoded until i know how to get bpp GL11.glReadPixels(0, 0, width, height, (bpp == 3) ? GL11.GL_RGB : GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer); Runnable r = new Runnable() { @Override public void run() { Calendar cal = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmssSSS"); File file = new File(PathManager.getInstance().getScreensPath(), sdf.format(cal.getTime()) + ".png"); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) { int i = (x + width * y) * bpp; int r = buffer.get(i) & 0xFF; int g = buffer.get(i + 1) & 0xFF; int b = buffer.get(i + 2) & 0xFF; image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b); } try { ImageIO.write(image, "png", file); } catch (IOException e) { _logger.log(Level.WARNING, "Could not save image!", e); } } }; CoreRegistry.get(TerasologyEngine.class).submitTask("Write screenshot", r); } @Override public String toString() { return String.format("world (biome: %s, time: %.2f, exposure: %.2f, sun: %.2f, cache: %fMb, dirty: %d, ign: %d, vis: %d, tri: %d, empty: %d, !ready: %d, seed: \"%s\", title: \"%s\")", getPlayerBiome(), world.getAge(), PostProcessingRenderer.getInstance().getExposure(), _skysphere.getSunPosAngle(), 10, _statDirtyChunks, _statIgnoredPhases, _statVisibleChunks, _statRenderedTriangles, _statChunkMeshEmpty, _statChunkNotReady, world.getSeed(), world.getName()); } public LocalPlayer getPlayer() { return _player; } public boolean isAABBVisible(AABB aabb) { return getActiveCamera().getViewFrustum().intersects(aabb); } public boolean isChunkValidForRender(TeraChunk c) { return true; // TODO wait for loading } public boolean isChunkVisible(TeraChunk c) { return getActiveCamera().getViewFrustum().intersects(c.getAABB()); } public double getDaylight() { return _skysphere.getDaylight(); } public Biome getPlayerBiome() { Vector3f pos = getPlayerPosition(); return world.getBiomeType((int) pos.x, (int) pos.y, (int) pos.z); } public World getWorldProvider() { return world; } public BlockGrid getBlockGrid() { return _blockGrid; } public Skysphere getSkysphere() { return _skysphere; } public double getTick() { return _tick; } public List<SpoutChunk> getChunksInProximity() { return _chunksInProximity; } public boolean isWireframe() { return _wireframe; } public void setWireframe(boolean _wireframe) { this._wireframe = _wireframe; } public BulletPhysicsRenderer getBulletRenderer() { return _bulletRenderer; } public Camera getActiveCamera() { return _activeCamera; } //TODO: Review public void setCameraMode(CAMERA_MODE mode) { _cameraMode = mode; switch (mode) { case PLAYER: _activeCamera = _defaultCamera; break; default: _activeCamera = _spawnCamera; break; } } public ChunkTessellator getChunkTesselator() { return _chunkTesselator; } }