package net.kennux.cubicworld;
import java.io.IOException;
import net.kennux.cubicworld.entity.EntityManager;
import net.kennux.cubicworld.entity.PlayerController;
import net.kennux.cubicworld.environment.DayNightCycle;
import net.kennux.cubicworld.environment.Skybox;
import net.kennux.cubicworld.gui.GuiManager;
import net.kennux.cubicworld.input.GameInputProcessor;
import net.kennux.cubicworld.input.InputManager;
import net.kennux.cubicworld.networking.CubicWorldClient;
import net.kennux.cubicworld.networking.packet.ClientChunkRequest;
import net.kennux.cubicworld.profiler.Profiler;
import net.kennux.cubicworld.profiler.ProfilerResult;
import net.kennux.cubicworld.util.ConsoleHelper;
import net.kennux.cubicworld.util.DebugHelper;
import net.kennux.cubicworld.util.ShaderLoader;
import net.kennux.cubicworld.voxel.RaycastHit;
import net.kennux.cubicworld.voxel.VoxelWorld;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.decals.CameraGroupStrategy;
import com.badlogic.gdx.graphics.g3d.decals.DecalBatch;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.graphics.profiling.GLProfiler;
import com.badlogic.gdx.math.Quaternion;
/**
* The game's main class.
*
* @author kennux
*
*/
public class CubicWorldGame implements ApplicationListener
{
/**
* The perspective camera used for rendering.
*/
public PerspectiveCamera cam;
/**
* The input processor
*/
private InputMultiplexer inputMultiplexer;
/**
* The input processor
*/
private GameInputProcessor gameInputProcessor;
/**
* The voxel world instance.
*/
public VoxelWorld voxelWorld;
/**
* The entity manager
*/
public EntityManager entityManager;
/**
* Contains the last rendered final frame buffer.
*/
public FrameBuffer finalFrameTexture;
/**
* The framebuffer which will get used in the main rendering pass.
*/
private FrameBuffer frameBuffer;
/**
* Gets used to draw the framebuffer after the main rendering (before the gui rendering pass) pass.
*/
private SpriteBatch frameBufferBatch;
/**
* The shader which gets used to blit textures on the screen.
*/
private ShaderProgram blitShader;
/**
* The client socket.
*/
public CubicWorldClient client;
/**
* The player controller.
*/
public PlayerController playerController;
/**
* The skybox.
*/
private Skybox skybox;
/**
* The day night cycle instance.
*/
public DayNightCycle dayNightCycle;
/**
* The gui manager
*/
public GuiManager guiManager;
/**
* When this is set to true a current test (post processing AA) will be
* active.
*/
public boolean postProcessing = false;
/**
* The decal batch for entity rendering.
*/
private DecalBatch decalBatch;
/**
* The model batch used to draw the entities
*/
private ModelBatch entityBatch;
/**
* The sprite batch used for entity rendering
*/
private SpriteBatch entitySpriteBatch;
/**
* Font used to draw strings in entity rendering.
*/
private BitmapFont entityFont;
/**
* Contains the current block hit by raycasting from screen's center towards
* forward axis of the camera.
* Raycast will be performed in the update block in the render() method.
*/
public RaycastHit currentBlockHit;
/**
* If this is set to true DebugHelper.renderDebugInformation() will get called.
*/
public boolean debugActive = true;
/**
* The profiler used by this instance.
*/
public Profiler profiler;
/**
* The plugin manager of the game.
*/
public PluginManager pluginManager;
/**
* The input manager for this instance.
*/
public InputManager inputManager;
public CubicWorldGame()
{
if (CubicWorld.getClient() != null)
System.exit(-1);
CubicWorld.setClient(this);
}
/**
* <pre>
* Creates this instance.
* Will initialize things in the following order:
*
* - Profiler
* - Gui manager
* - Bootstrap initialization
* - Day night cycle
* - voxel world
* - player controller
* - client socket
* - input processor
* - post processing
* - rendering batches
*
* This function gets profiled. It will be in the frame -1 on the client's
* profiler log.
* </pre>
*/
@Override
public void create()
{
// Init Profiler
GLProfiler.enable();
this.profiler = new Profiler();
try
{
this.profiler.openProfilingFile("client_profiling.txt", Profiler.FileFormat.PLAINTEXT);
}
catch (IOException e)
{
ConsoleHelper.writeLog("error", "Profiler init failed!", "CubicWorld Main");
ConsoleHelper.logError(e);
}
this.profiler.startProfiling("Init()", "None");
// Init gui
this.guiManager = new GuiManager(null);
// Bootstrap
this.pluginManager = new PluginManager();
this.inputManager = new InputManager();
Bootstrap.bootstrap(this, this.pluginManager);
// Create cam
this.setupCamera();
// Create day night cycle instance
this.dayNightCycle = new DayNightCycle();
// Create voxel world
this.voxelWorld = new VoxelWorld(ShaderLoader.loadShader("world"), this.cam);
// Init player controller, initial position will get set in the
// ServerPlayerSpawn packet
this.playerController = new PlayerController(this.voxelWorld, this.cam);
// Connect socket
try
{
this.client = new CubicWorldClient(this, "127.0.0.1", (short) 13371);
this.client.update(this.playerController.getPosition());
}
catch (Exception e)
{
// TODO Auto-generated catch block
ConsoleHelper.logError(e);
System.exit(-1);
}
// Wait till all chunk requests are processed
while(ClientChunkRequest.areRequestsPending())
{
this.client.waitForChunkPackets();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Generate all chunk meshes
int generationPerFrameLimit = CubicWorldConfiguration.meshGenerationsPerFrameLimit;
int creationPerFrameLimit = CubicWorldConfiguration.meshCreationsPerFrameLimit;
CubicWorldConfiguration.meshGenerationsPerFrameLimit = -1;
CubicWorldConfiguration.meshCreationsPerFrameLimit = -1;
// Wait till all chunks are ready
while(!this.voxelWorld.allChunksReady())
{
this.voxelWorld.update();
this.voxelWorld.render(this.cam);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
CubicWorldConfiguration.meshGenerationsPerFrameLimit = generationPerFrameLimit;
CubicWorldConfiguration.meshGenerationsPerFrameLimit = creationPerFrameLimit;
// System.exit(1);
// Start update thread AFTER the client requested all chunks
this.voxelWorld.initUpdateThread();
// Setup input multiplexer
this.inputMultiplexer = new InputMultiplexer();
Gdx.input.setInputProcessor(this.inputMultiplexer);
// Add input processors
this.gameInputProcessor = new GameInputProcessor(this);
this.inputMultiplexer.addProcessor(this.gameInputProcessor);
this.inputMultiplexer.addProcessor(this.guiManager.getInputProcessor());
// Create framebuffer
this.frameBuffer = new FrameBuffer(Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);
this.frameBuffer.getColorBufferTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
this.finalFrameTexture = new FrameBuffer(Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);
this.finalFrameTexture.getColorBufferTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
this.frameBufferBatch = new SpriteBatch();
this.entityManager = new EntityManager(this.voxelWorld);
// Init skybox
this.skybox = new Skybox(Gdx.files.internal("textures/skybox/left.png"), Gdx.files.internal("textures/skybox/right.png"), Gdx.files.internal("textures/skybox/top.png"), Gdx.files.internal("textures/skybox/bottom.png"), Gdx.files.internal("textures/skybox/front.png"), Gdx.files.internal("textures/skybox/back.png"));
// Init batches
this.entityBatch = new ModelBatch();
this.entitySpriteBatch = new SpriteBatch();
this.decalBatch = new DecalBatch(new CameraGroupStrategy(this.cam));
this.entityFont = new BitmapFont();
// Init shader
this.blitShader = ShaderLoader.loadShader("blit");
this.profiler.stopProfiling("Init()");
this.profiler.reset();
}
//private float lastTime;
/**
* <pre>
* This is the cubic world main routine.
* First it will update everything in this order:
*
* - update this.currentBlockHit by raycasting
* - Cleanup the entity manager
* - Call update() on all entities in range
* - Update the gui manager
* - Update the client socket (mainly interprets read packets and send the ones who are enquened).
* - World cleanup (Removes all chunks too far away from the player).
* - Player update (Handles the player movement)
*
* Then it will render the following to the frambuffer used for
* post-processing:
* - Skybox with camera's rotation
* - World render
* - Entity render
* - Postprocessing if activated
* - Gui rendering pass
* - Profiler reset / update
*
* Every section of this function gets profiled with the Profiler class.
* </pre>
*/
@Override
public void render()
{
long nanos = System.nanoTime();
this.profiler.startProfiling("Update", "The whole update part of the render() routine");
// Raycast
this.profiler.startProfiling("CurrentLookAtRaycast", this.cam.position.toString() + ", dir: " + this.cam.direction.toString() + ", Distance: " + 5);
this.currentBlockHit = this.voxelWorld.raycast(this.cam.position, this.cam.direction, 5);
this.profiler.stopProfiling("CurrentLookAtRaycast");
// Update
this.gameInputProcessor.update();
this.profiler.startProfiling("Client Socket Update", "");
this.client.update(this.playerController.getPosition());
this.profiler.stopProfiling("Client Socket Update");
this.profiler.startProfiling("Entity Update", "Cleanup and Update call");
this.entityManager.cleanupUpdateTimeout(CubicWorldConfiguration.entityUpdateTimeout);
this.entityManager.update(this.playerController.getPosition());
this.profiler.stopProfiling("Entity Update");
this.profiler.startProfiling("Gui Update", "");
this.guiManager.update();
this.profiler.stopProfiling("Gui Update");
this.profiler.startProfiling("World cleanup", "");
// Chunk cleanup
this.voxelWorld.cleanup(this.cam.position, CubicWorldConfiguration.chunkLoadDistance);
this.profiler.stopProfiling("World cleanup");
this.profiler.startProfiling("Player update", "");
// Player controller
this.playerController.update();
this.profiler.stopProfiling("Player update");
this.profiler.startProfiling("Plugin updates", "");
this.pluginManager.fireEvent("update", false);
this.profiler.stopProfiling("Plugin updates");
this.profiler.stopProfiling("Update");
this.profiler.startProfiling("Render", "The whole rendering part of the render() routine");
Gdx.gl20.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(1, 1, 0, 1);
// Render to framebuffer
this.frameBuffer.begin();
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
// Skybox pass
Gdx.gl.glDisable(GL20.GL_DEPTH_TEST);
Quaternion rotation = new Quaternion();
this.cam.view.inv().getRotation(rotation);
this.profiler.startProfiling("Skybox render", "");
this.skybox.render(rotation);
this.profiler.stopProfiling("Skybox render");
// World rendering pass
Gdx.gl.glEnable(GL20.GL_DEPTH_TEST);
// Debug rendering
DebugHelper.renderDebug(this.cam, this);
// Render world
this.profiler.startProfiling("World render", "");
this.voxelWorld.render(this.cam);
this.profiler.stopProfiling("World render");
// Render entities
this.profiler.startProfiling("Entity render", "");
this.entityBatch.begin(this.cam);
this.entitySpriteBatch.begin();
this.entityManager.render(this.cam, this.entityBatch, this.decalBatch, this.entitySpriteBatch, this.entityFont);
this.entitySpriteBatch.end();
this.entityBatch.end();
this.decalBatch.flush();
this.profiler.stopProfiling("Entity render");
// Render world pass done
// Stop rendering to the framebuffer
this.frameBuffer.end();
// INSERT POST-PROCESSING HERE
// Render to final texture
this.finalFrameTexture.begin();
// Draw framebuffer
this.frameBufferBatch.setShader(this.blitShader);
this.frameBufferBatch.begin();
this.frameBufferBatch.draw(this.frameBuffer.getColorBufferTexture(), 0, 0);
this.frameBufferBatch.end();
// World rendering done
Gdx.gl.glDisable(GL20.GL_DEPTH_TEST);
this.profiler.startProfiling("Gui render", "");
// GUI Rendering pass
Gdx.gl.glEnable(GL20.GL_BLEND);
this.guiManager.render();
this.profiler.stopProfiling("Gui render");
this.profiler.stopProfiling("Render");
this.profiler.startProfiling("Other", "");
// Render debug info
if (this.debugActive)
{
this.profiler.startProfiling("Debug Render", "");
DebugHelper.renderDebugInformation(this.cam, this.client);
this.profiler.stopProfiling("Debug Render");
}
Gdx.gl.glDisable(GL20.GL_BLEND);
// Rendering done
this.finalFrameTexture.end();
// Draw to screen
this.frameBufferBatch.begin();
this.frameBufferBatch.draw(this.finalFrameTexture.getColorBufferTexture(), 0, 0);
this.frameBufferBatch.end();
this.profiler.stopProfiling("Other");
/*float frameResult = ((System.nanoTime() - nanos) / 1000000.0f);
if (frameResult > 50 || Gdx.graphics.getDeltaTime() > 1)
{
ProfilerResult[] results = this.profiler.getResults();
System.out.println("Delta time: " + Gdx.graphics.getDeltaTime());
System.out.println("frame time: " + frameResult);
System.out.println("last frame time: " + this.lastTime);
System.out.println("Profiler trace: ");
for (ProfilerResult result : results)
{
if (result.getMilliseconds() > 20)
System.out.println(result.getName() + " - " + result.getMilliseconds() + " ms");
}
System.out.println("");
}
this.lastTime = frameResult;*/
// Reset profiler
this.profiler.reset();
GLProfiler.reset();
}
/**
* Initializes the perspective camera used for rendering.
*/
private void setupCamera()
{
this.cam = new PerspectiveCamera(70, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
this.cam.near = 0.05f;
this.cam.far = 600.0f;
this.cam.fieldOfView = 100.0f;
// this.cam.translate(new Vector3(0, 130, 10));
this.cam.update(true);
}
@Override
public void resize(int width, int height)
{
// TODO Auto-generated method stub
}
@Override
public void pause()
{
}
@Override
public void resume()
{
// TODO Auto-generated method stub
}
@Override
public void dispose()
{
// TODO Auto-generated method stub
}
}