Skip to content

Commit

Permalink
Add lighting example
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin committed Nov 2, 2010
1 parent c2f894d commit 42eda61
Show file tree
Hide file tree
Showing 2 changed files with 325 additions and 0 deletions.
81 changes: 81 additions & 0 deletions examples/org/newdawn/slick/examples/lights/Light.java
@@ -0,0 +1,81 @@
package org.newdawn.slick.examples.lights;

import org.newdawn.slick.Color;

/**
* A single light in the example. It's capable of determining how much effect
* it will have in any given point on the tile map. Note that all coordinates
* are given in tile coordinates rather than pixel coordinates.
*
* @author kevin
*/
class Light {
/** The x coordinate of the position the light has in the world */
private float xpos;
/** The y coordinate of the position the light has in the world */
private float ypos;
/** The strength of the light, this specifies in tiles how far the light will shine */
private float strength;
/** The colour the light should apply */
private Color col;

/**
* Create a new light in the world
*
* @param x The x coordinate of the position the light has in the world
* @param y The y coordinate of the position the light has in the world
* @param str The strength of the light, this specifies in tiles how far the light will shine
* @param col The colour the light should apply
*/
public Light(float x, float y, float str, Color col) {
xpos = x;
ypos = y;
strength = str;
this.col = col;
}

/**
* Set the location of the light in the world
*
* @param x The x coordinate of the position the light has in the world
* @param y The y coordinate of the position the light has in the world
*/
public void setLocation(float x, float y) {
xpos = x;
ypos = y;
}

/**
* Get the effect the light should apply to a given location
*
* @param x The x coordinate of the location being considered for lighting
* @param y The y coordinate of the location being considered for lighting
* @param colouredLights True if we're supporting coloured lights
*
* @return The effect on a given location of the light in terms of colour components (all
* the same if we don't support coloured lights)
*/
public float[] getEffectAt(float x, float y, boolean colouredLights) {
// first work out what propotion of the strength distance the light
// is from the point. This is a value from 0-1 where 1 is the centre of the
// light (i.e. full brightness) and 0 is the very edge (or outside) the lights
// range
float dx = (x - xpos);
float dy = (y - ypos);
float distance2 = (dx*dx)+(dy*dy);
float effect = 1 - (distance2 / (strength*strength));

if (effect < 0) {
effect = 0;
}

// if we doing coloured lights then multiple the colour of the light
// by the effect. Otherwise just use the effect for all components to
// give white light
if (colouredLights) {
return new float[] {col.r * effect, col.g * effect, col.b * effect};
} else {
return new float[] {effect,effect,effect};
}
}
}
244 changes: 244 additions & 0 deletions examples/org/newdawn/slick/examples/lights/LightTest.java
@@ -0,0 +1,244 @@
package org.newdawn.slick.examples.lights;

import java.util.ArrayList;

import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.util.Bootstrap;

/**
* This example shows using vertex colours on a tile map to producing a lighting
* effect. The approach is very efficient and pretty flexible. It can be extended
* in many ways.
*
* The down side is that the resolution of your lighting map is the same as that of your
* tile map. So the small area you'll see a change in lighting across is 32x32 pixels in
* this case (the size of a single tile). This can be worked round by rendering black tiles
* over an existing map at a different resolution. For instance, you may use 16x16 black tiles
* over a 32x32 pixels tiled map to get a better resolution of lighting.
*
* This example essentially generates a random map of tiles, creates a set of lights and calculates
* their effect on each of vertexs in the tiled map. When rendering we apply those calculated values
* to the tile image vertex colours before rendering it. This gives us the effect of lighting
* the tiles.
*
* @author kevin
*/
public class LightTest extends BasicGame {
/** The width of the tile map in tiles */
private static final int WIDTH = 15;
/** The height of the tile map in tiles */
private static final int HEIGHT = 15;

/** True if we're going to render with lighting */
private boolean lightingOn = true;
/** True if we're going to render coloured lighting .... oooooh disco inferno! */
private boolean colouredLights = false;

/** The sprite sheet we're using for our tiles */
private SpriteSheet tiles;
/** The tile map we'll randomly generate and render */
private int[][] map = new int[WIDTH][HEIGHT];
/**
* The values calculated for each vertex of the tile map,
* note how it's one more to account for the bottom corner of the map.
* The 3 dimension is for colour components (red, green, blue) used for coloured lighting
*/
private float[][][] lightValue = new float[WIDTH+1][HEIGHT+1][3];
/** The lights we've defined */
private ArrayList lights = new ArrayList();
/** The main light that we'll move around with the mouse, held seperately so we can update it */
private Light mainLight;

/**
* Create the example game
*/
public LightTest() {
super("Light Test");
}

/**
* Initialise our resources for the example
*
* @param container The game container the game is running in
*/
public void init(GameContainer container) throws SlickException {
tiles = new SpriteSheet("testdata/tiles.png", 32,32);
generateMap();
}

/**
* Randomly generate a tile map
*/
private void generateMap() {
// cycle through the map placing a random tile in each location
for (int y=0;y<HEIGHT;y++) {
for (int x=0;x<WIDTH;x++) {
map[x][y] = 0;

// 20% of tiles have features
if (Math.random() > 0.8) {
map[x][y] = 1 + (int) (Math.random() * 7);
}
}
}

// create and add our lights
lights.clear();

mainLight = new Light(8f,7f,4f,Color.white);
lights.add(mainLight);
lights.add(new Light(2,2,2f,Color.red));
lights.add(new Light(2,11,1.5f,Color.yellow));
lights.add(new Light(12,2,3f,Color.green));

// finally update the lighting map for the first time
updateLightMap();
}

/**
* Update the vertex values for lighting based on the current
* light configuration.
*/
private void updateLightMap() {
// for every vertex on the map (notice the +1 again accounting for the trailing vertex)
for (int y=0;y<HEIGHT+1;y++) {
for (int x=0;x<WIDTH+1;x++) {
// first reset the lighting value for each component (red, green, blue)
for (int component=0;component<3;component++) {
lightValue[x][y][component] = 0;
}

// next cycle through all the lights. Ask each light how much effect
// it'll have on the current vertex. Combine this value with the currently
// existing value for the vertex. This lets us blend coloured lighting and
// brightness
for (int i=0;i<lights.size();i++) {
float[] effect = ((Light) lights.get(i)).getEffectAt(x, y, colouredLights);
for (int component=0;component<3;component++) {
lightValue[x][y][component] += effect[component];
}
}

// finally clamp the components to 1, since we don't want to
// blow up over the colour values
for (int component=0;component<3;component++) {
if (lightValue[x][y][component] > 1) {
lightValue[x][y][component] = 1;
}
}
}
}
}

/**
* Update the game
*
* @param container The container the game is running in
* @param delta The amount of time that passed since last update (in seconds)
*/
public void update(GameContainer container, int delta)
throws SlickException {
// toggle the lighting on/off
if (container.getInput().isKeyPressed(Input.KEY_L)){
lightingOn = !lightingOn;
}
// toggle the use of coloured lighting on/off
if (container.getInput().isKeyPressed(Input.KEY_C)){
colouredLights = !colouredLights;
// we need to recaculate the lighting values because
// colours may now be involved
updateLightMap();
}
}

/**
* Notification that the mouse was dragged
*
* @param oldx The old x coordinate of the mouse
* @param oldy The old y coordinate of the mouse
* @param newx The new x coordinate of the mouse
* @param newy The new y coordinate of the mouse
*/
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
mousePressed(0, newx, newy);
}

/**
* Notification that mouse was pressed
*
* @param button The button that was pressed
* @param x The x coordinate the mouse was pressed at
* @param y The y coordinate the mouse was pressed at
*/
public void mousePressed(int button, int x, int y) {
mainLight.setLocation((x-64)/32.0f,(y-50)/32.0f);
updateLightMap();
}

/**
* Render the tile map and lighting to the game window
*
* @param container The container the game is running in
* @param g The graphics context to which we can render
*/
public void render(GameContainer container, Graphics g)
throws SlickException {
// display some instructions on how to use the example
g.setColor(Color.white);
g.drawString("Lighting Example", 440, 5);
g.drawString("Press L to toggle light", 80, 560);
g.drawString("Press C to toggle coloured lights", 80, 575);
g.drawString("Click or Drag to move the main light", 80, 545);

// move the display to nicely position the tilemap
g.translate(64,50);

// cycle round every tile in the map
for (int y=0;y<HEIGHT;y++) {
for (int x=0;x<WIDTH;x++) {
// get the appropriate image to draw for the current tile
int tile = map[x][y];
Image image = tiles.getSubImage(tile % 4, tile / 4);

if (lightingOn) {
// if lighting is on apply the lighting values we've
// calculated for each vertex to the image. We can apply
// colour components here as well as just a single value.
image.setColor(Image.TOP_LEFT, lightValue[x][y][0], lightValue[x][y][1], lightValue[x][y][2], 1);
image.setColor(Image.TOP_RIGHT, lightValue[x+1][y][0], lightValue[x+1][y][1], lightValue[x+1][y][2], 1);
image.setColor(Image.BOTTOM_RIGHT, lightValue[x+1][y+1][0], lightValue[x+1][y+1][1], lightValue[x+1][y+1][2], 1);
image.setColor(Image.BOTTOM_LEFT, lightValue[x][y+1][0], lightValue[x][y+1][1], lightValue[x][y+1][2], 1);
} else {
// if lighting is turned off then use "1" for every value
// so we just have full colour everywhere.
float light = 1;
image.setColor(Image.TOP_LEFT, light, light, light, 1);
image.setColor(Image.TOP_RIGHT, light, light, light, 1);
image.setColor(Image.BOTTOM_RIGHT, light, light, light, 1);
image.setColor(Image.BOTTOM_LEFT, light, light, light, 1);
}

// draw the image with it's newly declared vertex colours
// to the display
image.draw(x*32,y*32);
}
}
}

/**
* Entry point to the example game
*
* @param argv The arguments provided at the command line
*/
public static void main(String[] argv) {
Bootstrap.runAsApplication(new LightTest(), 600, 600, false);
}
}

0 comments on commit 42eda61

Please sign in to comment.