package org.jrenner.fps;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
import com.badlogic.gdx.graphics.g3d.environment.PointLight;
import com.badlogic.gdx.graphics.g3d.utils.DefaultShaderProvider;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.TimeUtils;
import org.jrenner.fps.entity.Entity;
import org.jrenner.fps.graphics.EntityModel;
import org.jrenner.fps.graphics.ModelManager;
import org.jrenner.fps.net.packages.ChatMessage;
import org.jrenner.fps.particles.Particles;
public class View implements Disposable {
public static View inst;
private GL20 gl;
public static int width, height;
private PerspectiveCamera camera;
private Environment environ, basicEnviron;
private PointLight camLight;
private DirectionalLight dirLight;
public HUD hud;
private ModelBatch modelBatch;
private Color fogColor = new Color(0.3f, 0.4f, 0.3f, 1f);
private DefaultShaderProvider shaderProvider;
private void initShaders() {
FileHandle vertexFile = Gdx.files.internal("shaders/vertex.glsl");
FileHandle fragFile = Gdx.files.internal("shaders/frag.glsl");
shaderProvider = new DefaultShaderProvider(vertexFile, fragFile);
shaderProvider.config.numPointLights = 10;
shaderProvider.config.numDirectionalLights = 2;
// check shader compile logs
/*shader = new ShaderProgram(vertexFile, fragFile);
ShaderProgram.pedantic = false;
String log = shader.getLog();
if (!shader.isCompiled())
throw new GdxRuntimeException(log);
if (log!=null && log.length()!=0)
System.out.println("Shader Log: "+log);*/
}
public View() {
ModelManager modelManager = new ModelManager();
modelManager.init();
storeSize();
inst = this;
gl = Gdx.graphics.getGL20();
float fov = 67f;
camera = new PerspectiveCamera(fov, width(), height());
// camera.far affects frustrum culling, so a shorter distance can boost performance
camera.far = 60f;
camera.near = 0.01f;
resetCamera();
initShaders();
modelBatch = new ModelBatch(shaderProvider);
environ = new Environment();
basicEnviron = new Environment();
camLight = new PointLight();
float intensity = 100f;
camLight.set(new Color(0.2f, 0.2f, 0.2f, 1f), 0f, 0f, 0f, intensity);
ColorAttribute ambientLight = ColorAttribute.createAmbient(new Color(0.1f, 0.1f, 0.1f, 1f));
environ.set(ambientLight);
ColorAttribute fog = new ColorAttribute(ColorAttribute.Fog);
fog.color.set(fogColor);
environ.set(fog);
environ.add(camLight);
dirLight = new DirectionalLight();
dirLight.set(new Color(0.3f, 0.3f, 0.35f, 1f), -0.25f, -0.75f, 0.25f);
environ.add(dirLight);
basicEnviron.set(ColorAttribute.createAmbient(0.3f, 0.3f, 0.3f, 1f));
if (Toggleable.profileGL()) {
Profiler.enable();
}
hud = new HUD();
Sky.createSkyBox(
Assets.manager.get("textures/skybox/xpos.png", Texture.class),
Assets.manager.get("textures/skybox/xneg.png", Texture.class),
Assets.manager.get("textures/skybox/ypos.png", Texture.class),
Assets.manager.get("textures/skybox/yneg.png", Texture.class),
Assets.manager.get("textures/skybox/zpos.png", Texture.class),
Assets.manager.get("textures/skybox/zneg.png", Texture.class)
);
}
private void updateCamera() {
//System.out.println("update camera");
if (!Toggleable.freeCamera() && Main.inst.client.player != null) {
Entity playerEntity = Main.inst.client.player.entity;
//System.out.println("cam move to pos: " + DynamicEntity.player.getPosition());
camera.position.set(playerEntity.getPosition());
setCameraRotation(playerEntity.getRotation());
}
camera.update();
}
long lastSwitch = -1;
RollingArray renderTimes = new RollingArray();
RollingArray hudTimes = new RollingArray();
RollingArray entityTimes = new RollingArray();
RollingArray staticTimes = new RollingArray();
RollingArray boxTimes = new RollingArray();
RollingArray skyTimes = new RollingArray();
RollingArray groundTimes = new RollingArray();
private void debugRenderTimes() {
// tied in with the profileGL toggleable for now
long now = TimeUtils.millis();
long passed = now - lastSwitch;
if (passed >= 1000 || lastSwitch < 0) {
float totalTime = renderTimes.getAverage();
float hudTime = hudTimes.getAverage();
float entityTime = entityTimes.getAverage();
float staticTime = staticTimes.getAverage();
float boxTime = boxTimes.getAverage();
float skyTime = skyTimes.getAverage();
float groundTime = groundTimes.getAverage();
renderTimes.clear();
hudTimes.clear();
entityTimes.clear();
staticTimes.clear();
boxTimes.clear();
skyTimes.clear();
groundTimes.clear();
lastSwitch = now;
String text = String.format("render-times(ms), total: %.1f, hud: %.1f, entity: %.1f, static: %.1f\n" +
" boxes: %.1f, sky: %.1f, ground: %.1f",
totalTime, hudTime, entityTime, staticTime, boxTime, skyTime, groundTime);
Main.inst.client.sendChatMessage(new ChatMessage(text));
Log.debug(text);
}
}
boolean debugRenderPerformance = false;
public void render() {
if (debugRenderPerformance) {
debugRenderTimes();
}
long start = TimeUtils.millis();
boolean profileGL = Toggleable.profileGL();
updateLights();
if (profileGL) {
Profiler.reset();
}
gl.glViewport(0, 0, width(), height());
gl.glClearColor(fogColor.r, fogColor.g, fogColor.b, fogColor.a);
gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
camera.up.set(Vector3.Y);
updateCamera();
long skyStart = TimeUtils.millis();
updateSky();
modelBatch.begin(camera);
if (Sky.isEnabled()) {
modelBatch.render(Sky.modelInstance);
}
skyTimes.add((int) TimeUtils.timeSinceMillis(skyStart));
// includes 3d models and billboards
long entityStart = TimeUtils.millis();
visibleEntities = 0;
for (EntityModel entityModel : EntityModel.list) {
entityModel.update();
// don't draw self when in FPS mode
if (!entityModel.isClientEntity() || Toggleable.freeCamera()) {
if (entityVisibilityCheck(entityModel)) {
visibleEntities++;
modelBatch.render(entityModel.modelInstance, environ);
}
}
}
entityTimes.add((int) TimeUtils.timeSinceMillis(entityStart));
long boxStart = TimeUtils.millis();
if (Box.instance != null) {
modelBatch.render(Box.instance, environ);
}
boxTimes.add((int) TimeUtils.timeSinceMillis(boxStart));
long staticStart = TimeUtils.millis();
if (LevelBuilder.staticGeometry != null) {
for (ModelInstance staticGeo : LevelBuilder.staticGeometry) {
modelBatch.render(staticGeo, environ);
}
}
staticTimes.add((int) TimeUtils.timeSinceMillis(staticStart));
long groundStart = TimeUtils.millis();
visibleGroundPieces = 0;
for (ModelInstance groundPiece : LevelBuilder.groundPieces) {
if (groundPieceVisibilityCheck(groundPiece)) {
visibleGroundPieces++;
modelBatch.render(groundPiece, environ);
}
}
groundTimes.add((int) TimeUtils.timeSinceMillis(groundStart));
modelBatch.end();
// draw particle effects in a separate batch to make depth testing work better
modelBatch.begin(camera);
for (Shadow shadow : Shadow.list) {
modelBatch.render(shadow.modelInstance, environ);
}
drawParticleEffects();
modelBatch.end();
if (profileGL) {
Profiler.tick();
}
if (Toggleable.debugDraw() && Main.isClient()) {
Physics.inst.debugDraw();
}
long hudStart = TimeUtils.millis();
hud.draw();
long hudTime = TimeUtils.timeSinceMillis(hudStart);
hudTimes.add((int) hudTime);
long time = TimeUtils.timeSinceMillis(start);
renderTimes.add((int) time);
}
private void drawParticleEffects() {
Particles.inst.system.update();
Particles.inst.system.begin();
Particles.inst.system.draw();
Particles.inst.system.end();
modelBatch.render(Particles.inst.system);
}
private void updateSky() {
if (Sky.isEnabled()) {
Sky.update(camera.position);
}
}
private void updateLights() {
camLight.position.set(camera.position);
}
public static int width() { return Gdx.graphics.getWidth(); }
public static int height() { return Gdx.graphics.getHeight(); }
public static float screenSizeRatio() { return width() / 1920f; }
public void resetCamera() {
camera.position.set(0f, 3f, -3f);
camera.lookAt(0f, 0f, 0f);
Log.debug("reset camera");
}
public PerspectiveCamera getCamera() {
return camera;
}
private Vector3 tmp = new Vector3();
private Quaternion q = new Quaternion();
private Matrix4 mtx = new Matrix4();
public void transCamera(Direction.Translation dir) {
float transStep = 0.1f;
switch (dir) {
case Forward:
tmp.set(camera.direction);
camera.position.add(tmp.nor().scl(transStep));
break;
case Back:
tmp.set(camera.direction);
camera.position.add(tmp.nor().scl(-transStep));
break;
case Right:
tmp.set(camera.direction);
camera.position.add(tmp.crs(camera.up).nor().scl(transStep));
break;
case Left:
tmp.set(camera.direction);
camera.position.add(tmp.crs(camera.up).nor().scl(-transStep));
break;
case Up:
tmp.set(camera.up).nor();
tmp.scl(transStep);
camera.position.add(tmp);
break;
case Down:
tmp.set(camera.up).nor().scl(-1f);
tmp.scl(transStep);
camera.position.add(tmp);
break;
}
}
public void rotateCamera(Direction.Rotation dir, float step) {
switch (dir) {
case PitchUp:
tmp.set(camera.direction);
camera.direction.rotate(tmp.crs(camera.up), step);
break;
case PitchDown:
tmp.set(camera.direction);
camera.direction.rotate(tmp.crs(camera.up), -step);
break;
case YawRight:
camera.view.getRotation(q);
q.setEulerAngles(-step, 0f, 0f);
mtx.set(q);
camera.direction.prj(mtx);
break;
case YawLeft:
camera.view.getRotation(q);
q.setEulerAngles(step, 0f, 0f);
mtx.set(q);
camera.direction.prj(mtx);
break;
}
}
public void setCameraRotation(Quaternion q) {
mtx.set(q);
camera.direction.set(Vector3.Z);
camera.direction.prj(mtx);
}
public void storeSize() {
width = Gdx.graphics.getWidth();
height = Gdx.graphics.getHeight();
}
public static int visibleGroundPieces;
private boolean groundPieceVisibilityCheck(ModelInstance modelInst) {
float halfWidth = LevelBuilder.groundPieceSize / 2f;
modelInst.transform.getTranslation(tmp);
// we want the center of the piece
tmp.add(halfWidth, 0, halfWidth);
float radius = LevelBuilder.groundPieceSize;
return camera.frustum.sphereInFrustum(tmp, LevelBuilder.groundPieceSize);
// this naive method is useful for debugging to see pop-in/pop-out
//return camera.frustum.pointInFrustum(tmp);
}
public static int visibleEntities;
private boolean entityVisibilityCheck(EntityModel entModel) {
entModel.modelInstance.transform.getTranslation(tmp);
float radius = entModel.entity.getRadius();
return camera.frustum.sphereInFrustum(tmp, radius);
}
@Override
public void dispose() {
Tools.dispose(modelBatch);
}
}