package com.shade.lighting;
import java.util.Arrays;
import java.util.LinkedList;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.state.StateBasedGame;
import com.shade.controls.DayPhaseTimer;
import com.shade.entities.Roles;
/**
* A view which renders a set of entities, lights, and background images in such
* a way as to generate dynamic lighting.
*
* It's safe to draw things to the screen after calling LightMask.render if you
* want them to appear above the gameplay, for instance user controls.
*
* <em>Note that calling render will update your entities' luminosity. Please
* direct any hate mail to JJ Jou.</em>
*
* @author JJ Jou <j.j@duke.edu>
* @author Alexander Schearer <aschearer@gmail.com>
*/
public class LightMask {
/* Set to remove white borders from player, mushrooms, etc. */
private static final float MAGIC_ALPHA_VALUE = .65f;
private static final float MAGIC_ARROW_VALUE = .1f;
protected final static Color SHADE = new Color(0, 0, 0, .3f);
public static final float MAX_DARKNESS = 0.4f;
private DayPhaseTimer timer;
/**======================END CONSTANTS=======================*/
private int threshold;
private LinkedList<LightSource> lights;
public LightMask(int threshold, DayPhaseTimer time) {
this.threshold = threshold;
lights = new LinkedList<LightSource>();
timer = time;
}
public void add(LightSource light) {
lights.add(light);
}
public void render(StateBasedGame game, Graphics g,
LuminousEntity[] entities, Image... backgrounds) {
renderLights(game, g, entities);
renderBackgrounds(game, g, backgrounds);
renderEntities(game, g, entities);
//RENDER NIGHT! WHEEE
renderTimeOfDay(game, g);
}
public void renderTimeOfDay(StateBasedGame game, Graphics g){
Color c = g.getColor();
if(timer.getDaylightStatus()==DayPhaseTimer.DayLightStatus.DUSK){
g.setColor(new Color(1-timer.timeLeft(),1-timer.timeLeft(),0f,MAX_DARKNESS*timer.timeLeft()));
g.fillRect(0, 0, game.getContainer().getWidth(), game.getContainer().getHeight());
}
else if(timer.getDaylightStatus()==DayPhaseTimer.DayLightStatus.NIGHT){
g.setColor(new Color(0,0,0,MAX_DARKNESS));
g.fillRect(0, 0, game.getContainer().getWidth(), game.getContainer().getHeight());
}
else if(timer.getDaylightStatus()==DayPhaseTimer.DayLightStatus.DAWN){
g.setColor(new Color(timer.timeLeft(),timer.timeLeft(),0,MAX_DARKNESS*(1-timer.timeLeft())));
g.fillRect(0, 0, game.getContainer().getWidth(), game.getContainer().getHeight());
}
g.setColor(c);
}
private void renderLights(StateBasedGame game, Graphics g,
LuminousEntity... entities) {
enableStencil();
for (LightSource light : lights) {
light.render(game, g, entities);
}
disableStencil();
GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
g.setColor(SHADE);
GameContainer c = game.getContainer();
g.fillRect(0, 0, c.getWidth(), c.getHeight());
g.setColor(Color.white);
}
private void renderBackgrounds(StateBasedGame game, Graphics g,
Image... backgrounds) {
GL11.glBlendFunc(GL11.GL_DST_ALPHA, GL11.GL_ONE);
for (Image background : backgrounds) {
background.draw();
}
}
private void renderEntities(StateBasedGame game, Graphics g,
LuminousEntity... entities) {
Arrays.sort(entities);
int i = 0;
GL11.glEnable(GL11.GL_ALPHA_TEST);
GL11.glAlphaFunc(GL11.GL_GREATER, 0.2f);
GL11.glBlendFunc(GL11.GL_DST_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
while (i < entities.length && entities[i].getZIndex() < threshold) {
if (entityException(entities[i])) {
GL11.glAlphaFunc(GL11.GL_GREATER, MAGIC_ARROW_VALUE);
} else {
GL11.glAlphaFunc(GL11.GL_GREATER, 0.95f);
}
entities[i].setLuminosity(getLuminosityFor(entities[i], g));
entities[i].render(game, g);
i++;
}
GL11.glAlphaFunc(GL11.GL_GREATER, 0f);
GL11.glDisable(GL11.GL_ALPHA_TEST);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
while (i < entities.length) {
entities[i].setLuminosity(getLuminosityFor(entities[i], g));
entities[i].render(game, g);
i++;
}
//GL11.glDisable(GL11.GL_ALPHA_TEST);
}
private boolean entityException(LuminousEntity e) {
return (e.getRole() == Roles.DUMMY.ordinal());
}
private float getLuminosityFor(LuminousEntity entity, Graphics g) {
return g.getPixel((int) entity.getXCenter(), (int) entity.getYCenter()).a;
}
/**
* Called before drawing the shadows cast by a light.
*/
protected static void enableStencil() {
// write only to the stencil buffer
GL11.glEnable(GL11.GL_STENCIL_TEST);
GL11.glColorMask(false, false, false, false);
GL11.glDepthMask(false);
}
protected static void resetStencil(){
GL11.glClearStencil(0);
// write a one to the stencil buffer everywhere we are about to draw
GL11.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
// this is to always pass a one to the stencil buffer where we draw
GL11.glStencilOp(GL11.GL_REPLACE, GL11.GL_REPLACE, GL11.GL_REPLACE);
}
/**
* Called after drawing the shadows cast by a light.
*/
protected static void keepStencil() {
// resume drawing to everything
GL11.glDepthMask(true);
GL11.glColorMask(true, true, true, true);
GL11.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
// don't modify the contents of the stencil buffer
GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
}
protected static void disableStencil(){
GL11.glDisable(GL11.GL_STENCIL_TEST);
GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
}
}