Need suggestions to fix my lighting system

Hi

For my current project, I am working on a “smart” lighting system. This system would have lights across the map, where the light would go out except where there are blocked tiles (like wall tiles) in the way. However, I have tried many solutions to no avail. Here are the options I have tried out:

  • GLSL - It was too hard to make the lighting “smart” in the sense of reacting to walls
  • OpenGL lighting - It wasn’t working well for me, and it had the same flaws along with other limitations
  • Drawing layers of circles - Too slow and still not “smart”
  • Drawing each pixel of the screen - Dropped the FPS from 60 (capped) to 3-5
  • Drawing all the lights to a buffered image and the drawing the blocked tiles on top - System is “smart” but really slow

Ideally I would like to do the last one, as it is “smart”. However, I would like to make it work faster because it seems promising.


package warlord.effects;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.IOException;
import java.util.ArrayList;

import javax.imageio.ImageIO;

import warlord.ingame.world.Tile;
import warlord.opengl.Game;
import warlord.opengl.Texture;
import warlord.screens.GameScreen;
import warlord.screens.Screen;

import static org.lwjgl.opengl.GL11.*;

public class Lights {
	
	public static Texture lightoverlay;
	public static BufferedImage result;
	private static ArrayList<Light> lights;
	private static BufferedImage lightImage;
	
	private static Thread updating;
	
	public static void render(ArrayList<Light> lights){
		if(lights == null){
			lights = new ArrayList<Light>();
		}
		if(lightImage == null){
			load();
		}
	/*	for(Light light : lights){
			if(light.isPlayer){
				GameScreen screen = (GameScreen) Screen.getScreen();
				light.setPosition(screen.getPosition().getX(), screen.getPosition().getY());
				System.out.println("Refreshing the light");
				updateLights(lights);
			}
		}	*/
		if(result == null){
			updateLights(lights);
			return;
		}
		if(lightoverlay == null){
			lightoverlay = new Texture(result);
		}
		int width = lightoverlay.getWidth();
		int height = lightoverlay.getHeight(); 
		glPushMatrix();
		glEnable(GL_TEXTURE_2D);
		lightoverlay.bind();
		glBegin(GL_QUADS);
		glTexCoord2f(0, 0);
		glVertex2f(0, 0);
		glTexCoord2f(1, 0);
		glVertex2f(width, 0);
		glTexCoord2f(1, 1);
		glVertex2f(width, height);
		glTexCoord2f(0, 1);
		glVertex2f(0, height);
		glEnd();
		glPopMatrix();
	}
	
	public static void load(){
		try {
			lightImage = ImageIO.read(Game.getFile("light2.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		updating = new Thread(new Runnable(){
			public void run(){
				result = createFromLights(lights);
			}
		});
	}
	
	public static void updateLights(final ArrayList<Light> lights){
		if(lightImage == null){
			load();
		}
		Lights.lights = lights;
		if(updating.isAlive()){
			return;
		}
		try {
			updating.start();
		} catch(Exception e) {
			e.printStackTrace();
			// Don't start it
		}
	}
	
	public static BufferedImage createFromLights(ArrayList<Light> lights){
		long beforeMake = System.currentTimeMillis();
		int width = Math.round(Tile.WIDTH) * Game.getWorld().getCurrentBlock().getWidthInTiles();
		int height = Math.round(Tile.HEIGHT) * Game.getWorld().getCurrentBlock().getHeightInTiles(); 
		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		Graphics graphics = image.createGraphics();
		// Draw the lights
		graphics.setColor(Color.WHITE);
		graphics.drawRect(0, 0, width, height);
		for(Light light : lights){
			if(light.isPlayer){
				GameScreen screen = (GameScreen) Screen.getScreen();
				light.setPosition(screen.getPosition().getX(), screen.getPosition().getY());
				System.out.println("Refreshing the light");
			}
			if(lightImage == null){
				continue;
			}
		//	int dimensionWidth = lightImage.getWidth(), dimensionHeight = lightImage.getHeight();
			float x = light.getPosition().getX() - (/*dimensionWidth*/ 300 / 2), y = light.getPosition().getY() - (/*dimensionHeight*/ 300 / 2);
			graphics.drawImage(lightImage, Math.round(x), Math.round(y), /*dimensionWidth*/ 300, /*dimensionHeight*/ 300, null);
		}
		int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
		int raw = new Color(0, 0, 0, 200).getRGB();
		for(int x1 = 0; x1 < width; x1++){
			for(int y1 = 0; y1 < height; y1++){
				int index = y1 * width + x1;
				Tile tile = Game.getWorld().getCurrentBlock().getTile(x1, y1);
				pixels[index] = tile.getProperties().isBlocked() ? raw : pack(getR(pixels[index]), getG(pixels[index]), getB(pixels[index]), Math.min(200, 255 - getA(pixels[index])));
			}
		}
		graphics.setColor(new Color(0, 0, 0, 200));
		long afterMake = System.currentTimeMillis();
		System.out.println(afterMake - beforeMake);
		return image;
	}
	
	// Color packing
	public static int pack(int r, int g, int b, int a) {
	   //clamp to range [0, 255]
	   if (r < 0)
	      r = 0;
	   else if (r > 255) r = 255;
	   if (g < 0)
	      g = 0;
	   else if (g > 255) g = 255;
	   if (b < 0)
	      b = 0;
	   else if (b > 255) b = 255;
	   
	   //pack it together
	   return (a << 24) | (r << 16) | (g << 8) | (b);
	}
	public static int getR(int pixel){
		return (pixel >> 16) & 0xFF;
	}
	public static int getG(int pixel){
		return (pixel >> 8) & 0xFF;
	}
	public static int getB(int pixel){
		return pixel & 0xFF;
	}
	public static int getA(int pixel){
		return (pixel >> 24) & 0xFF;
	}

}

This code is updated

This code certainly has flaws, but I would like to have it work out. I hope someone can help me fix this code up :slight_smile:

CopyableCougar4

Hi

So I updated the source above, because I almost fixed the issue :slight_smile: Now the only issue left is that the image takes almost 700ms to make. I can’t just make it before hand because my plan is to have a light around the player. Does anybody have any ideas to speed up the image creation?

CopyableCougar4

Don’t use get/setRGB(), for one.

Much faster: http://www.java-gaming.org/topics/pixel-manipulation-with-bufferstrategy/30362/msg/280922/view.html#msg280922

Hi

Much thanks BurntPizza :slight_smile: Load time went from 700ms to 200ms on a big map and 80ms on a small map :slight_smile:

CopyableCougar4

Hi

Now I just need an efficient way to update the ‘light’ behind the player :slight_smile:

CopyableCougar4

I’m pretty sure that that loop over every pixel (probably the slowest part) could also be done in a fragment shader, and since that’s really the only part you needed the BufferedImage for, you could probably skip that whole ordeal all together.

Any time you think "draw every pixel of the _____ " you should then immediately think “fragment shader” and then figure out if that is possible.