/*******************************************************************************
* Copyright 2015 See AUTHORS file.
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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 com.mygdx.game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.Renderable;
import com.badlogic.gdx.graphics.g3d.Shader;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.environment.BaseLight;
import com.badlogic.gdx.graphics.g3d.environment.DirectionalShadowLight;
import com.badlogic.gdx.graphics.g3d.particles.ParticleSystem;
import com.badlogic.gdx.graphics.g3d.utils.DefaultShaderProvider;
import com.badlogic.gdx.graphics.g3d.utils.DepthShaderProvider;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Bits;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.mygdx.game.debugdrawers.ArmatureDebugDrawer;
import com.mygdx.game.debugdrawers.NavMeshDebugDrawer;
import com.mygdx.game.objects.Billboard;
import com.mygdx.game.objects.GameCharacter;
import com.mygdx.game.objects.GameModel;
import com.mygdx.game.settings.DebugViewSettings;
import com.mygdx.game.settings.GameSettings;
import com.mygdx.game.shaders.UberShader;
import com.mygdx.game.utilities.MyShapeRenderer;
import com.mygdx.game.utilities.Observer;
/**
* @author jsjolund
*/
public class GameRenderer implements Disposable, Observer {
/**
* Temporary vector that steerers can use to draw
*/
public final Vector3 vTmpDraw1 = new Vector3();
/**
* Temporary vector that steerers can use to draw
*/
public final Vector3 vTmpDraw2 = new Vector3();
private final ModelBatch modelBatch;
private final ModelBatch shadowBatch;
public final MyShapeRenderer shapeRenderer;
private final SpriteBatch spriteBatch;
private final ParticleSystem particleSystem = ParticleSystem.get();;
private final Vector3 tmp = new Vector3();
private final Vector3 cursorWorldPosition = new Vector3();
public final Viewport viewport;
private GameCharacter selectedCharacter;
private BitmapFont font;
private Camera camera;
private Environment environment;
private DirectionalShadowLight shadowLight;
private GameEngine engine;
private Billboard markerBillboard;
private ArmatureDebugDrawer armatureDebugDrawer = new ArmatureDebugDrawer();
private NavMeshDebugDrawer navMeshDebugDrawer = new NavMeshDebugDrawer();
public GameRenderer(Viewport viewport, Camera camera, GameEngine engine) {
this.viewport = viewport;
this.camera = camera;
this.engine = engine;
shapeRenderer = new MyShapeRenderer();
shapeRenderer.setAutoShapeType(true);
spriteBatch = new SpriteBatch();
font = new BitmapFont();
font.setColor(Color.WHITE);
font.setUseIntegerPositions(false);
font.getData().setScale(0.01f);
shadowBatch = new ModelBatch(new DepthShaderProvider());
ShaderProgram.pedantic = false;
final String vertUber = Gdx.files.internal("shaders/uber.vert").readString();
final String fragUber = Gdx.files.internal("shaders/uber.frag").readString();
modelBatch = new ModelBatch(new DefaultShaderProvider(vertUber, fragUber) {
@Override
protected Shader createShader(final Renderable renderable) {
return new UberShader(renderable, config);
}
});
}
@Override
public void notifyEntitySelected(GameCharacter entity) {
selectedCharacter = entity;
markerBillboard.setFollowTransform(entity.motionState.transform, entity.selectionMarkerOffset);
markerBillboard.visibleOnLayers.clear();
markerBillboard.visibleOnLayers.or(entity.visibleOnLayers);
}
@Override
public void notifyLayerChanged(Bits layer) {
}
@Override
public void notifyCursorWorldPosition(float x, float y, float z) {
this.cursorWorldPosition.set(x, y, z);
}
@Override
public void dispose() {
modelBatch.dispose();
shadowBatch.dispose();
shapeRenderer.dispose();
spriteBatch.dispose();
font.dispose();
shadowLight.dispose();
}
public void setEnvironmentLights(Array<BaseLight<?>> lights, Vector3 sunDirection) {
environment = new Environment();
environment.add((shadowLight = new DirectionalShadowLight(
GameSettings.SHADOW_MAP_WIDTH,
GameSettings.SHADOW_MAP_HEIGHT,
GameSettings.SHADOW_VIEWPORT_WIDTH,
GameSettings.SHADOW_VIEWPORT_HEIGHT,
GameSettings.SHADOW_NEAR,
GameSettings.SHADOW_FAR))
.set(GameSettings.SHADOW_INTENSITY,
GameSettings.SHADOW_INTENSITY,
GameSettings.SHADOW_INTENSITY,
sunDirection.nor()));
environment.shadowMap = shadowLight;
float ambientLight = GameSettings.SCENE_AMBIENT_LIGHT;
environment.set(new ColorAttribute(ColorAttribute.AmbientLight, ambientLight, ambientLight, ambientLight, 1));
for (BaseLight<?> light : lights) {
environment.add(light);
}
}
/**
* Checks if a model is visible using camera frustum culling and model layer visibility.
*
* @param camera
* @param gameModel
* @return
*/
private boolean isVisible(final Camera camera, final GameModel gameModel) {
if (!gameModel.visibleOnLayers.intersects(engine.getVisibleLayers())) {
return false;
}
gameModel.modelInstance.transform.getTranslation(tmp);
tmp.add(gameModel.center);
return camera.frustum.sphereInFrustum(tmp, gameModel.boundingBoxRadius);
}
private void drawShadowBatch() {
shadowLight.begin(Vector3.Zero, camera.direction);
shadowBatch.begin(shadowLight.getCamera());
shadowBatch.render(engine.getModelCache());
for (GameModel mdl : engine.getDynamicModels()) {
if (isVisible(camera, mdl)) {
shadowBatch.render(mdl.modelInstance);
}
}
shadowBatch.end();
shadowLight.end();
}
public void update(float deltaTime) {
camera.update();
if (DebugViewSettings.drawModels) {
drawShadowBatch();
viewport.apply();
modelBatch.begin(camera);
modelBatch.render(engine.getModelCache(), environment);
for (GameModel mdl : engine.getDynamicModels()) {
if (isVisible(camera, mdl)) {
modelBatch.render(mdl.modelInstance, environment);
}
}
if (markerBillboard != null && isVisible(camera, markerBillboard)) {
modelBatch.render(markerBillboard.modelInstance, environment);
}
particleSystem.update(); // technically not necessary for rendering
particleSystem.begin();
particleSystem.draw();
particleSystem.end();
modelBatch.render(particleSystem);
modelBatch.end();
}
if (DebugViewSettings.drawArmature) {
shapeRenderer.setProjectionMatrix(viewport.getCamera().combined);
armatureDebugDrawer.drawArmature(shapeRenderer, selectedCharacter, "armature");
}
if (DebugViewSettings.drawSteering) {
drawSteering();
}
if (DebugViewSettings.drawNavmesh) {
shapeRenderer.setProjectionMatrix(viewport.getCamera().combined);
navMeshDebugDrawer.drawNavMesh(shapeRenderer, spriteBatch,
engine.getScene().navMesh, selectedCharacter,
engine.getVisibleLayers(), viewport.getCamera(), font);
}
if (DebugViewSettings.drawMouseNavMeshPos) {
shapeRenderer.setProjectionMatrix(viewport.getCamera().combined);
drawMouseWorldAxis();
}
}
private void drawModels() {
}
private void renderParticleEffects() {
}
/**
* Draws a kind of compass, showing the world axis
*/
private void drawMouseWorldAxis() {
Vector3 v = cursorWorldPosition;
if (!Float.isNaN(v.x + v.y + v.z)) { // No vector component is NaN
shapeRenderer.begin();
shapeRenderer.line(v.x, v.y, v.z, v.x + 1, v.y, v.z, Color.RED, Color.RED);
shapeRenderer.line(v.x, v.y, v.z, v.x, v.y + 1, v.z, Color.GREEN, Color.GREEN);
shapeRenderer.line(v.x, v.y, v.z, v.x, v.y, v.z + 1, Color.BLUE, Color.BLUE);
shapeRenderer.end();
}
}
/**
* Draws the steering behavior of selected character
*/
private void drawSteering() {
if (selectedCharacter != null && selectedCharacter.steerer != null) {
selectedCharacter.steerer.draw(this);
}
}
public void setSelectionMarker(Billboard markerBillboard) {
this.markerBillboard = markerBillboard;
}
}