package complexion.client; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.lwjgl.LWJGLException; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.DisplayMode; import org.lwjgl.opengl.GL11; import complexion.common.Config; /** * The renderer is strictly the low-level mechanism responsible for drawing * objects, text, widgets and so on. It should be created and managed by a * higher-level Client manager. */ public class Renderer { /** * Initialize the LWJGL/OpenGL context. * * @param scaleFactor By how much the rendered sprites should be scaled. 1 for no scaling. * * @throws LWJGLException */ public Renderer(double scaleFactor) throws LWJGLException { // Create the client window Display.setDisplayMode(new DisplayMode(800, 600)); Display.create(); // Initialize OpenGL GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); GL11.glOrtho(0, 800, 0, 600, 1, -1); GL11.glScaled(scaleFactor, scaleFactor, 1); GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); } /** * Function responsible for drawing the current frame. This will draw all * objects currently assigned to the renderer. */ public void draw() { // Iterate over the atoms and draw them one by one // TODO: sort them by layer for (Atom a : atoms) { // Try to get a texture for our atom from its sprite BufferedImage buf = a.getCurrentImage(); // Check if the image exists if(buf == null) continue; // Do we have the texture cached? if (!this.textures.containsKey(buf)) { // Texture isn't cached yet, need to generate it textures.put(buf, TextureLoader.loadTexture(buf)); } // Load cached texture int t = this.textures.get(a.getCurrentImage()); // Bind cached texture GL11.glBindTexture(GL11.GL_TEXTURE_2D, t); // Get the sprite dimensions int width = buf.getWidth(); int height = buf.getHeight(); // Check where to render the sprite on the buffer int offset_x = a.tile_x * Config.tileWidth + a.pixel_x; int offset_y = a.tile_y * Config.tileWidth + a.pixel_y; // Objects with onMap set are displayed relative to the // current viewport position(scrolling) if(a.onMap) { offset_x -= this.eye_x; offset_y -= this.eye_y; } // Draw the sprite as quad // TODO: Check if drawing two triangles is faster GL11.glBegin(GL11.GL_QUADS); GL11.glTexCoord2f(0, 0); GL11.glVertex2f(offset_x, offset_y); GL11.glTexCoord2f(1, 0); GL11.glVertex2f(offset_x + width, offset_y); GL11.glTexCoord2f(1, 1); GL11.glVertex2f(offset_x + width, offset_y + height); GL11.glTexCoord2f(0, 1); GL11.glVertex2f(offset_x, offset_y + height); GL11.glEnd(); } // TODO: uncache unused textures if the cache is too crowded } /** * Add a visible atom to the Renderer for rendering. */ public void addAtom(Atom a) { atoms.add(a); Collections.sort(atoms, new Renderer.LayerComparator()); } /** * Sets the current position of the client viewport on the map. * This will affect how Atoms with onMap = true are displaced * on the rendering window. */ public void setEyePos(int pos_x, int pos_y) { this.eye_x = pos_x; this.eye_y = pos_y; } /** * Destroy the renderer and rendering window. */ public void destroy() { Display.destroy(); } /** * Private class used for sorting collections by layer. */ private static class LayerComparator implements Comparator<Atom> { public int compare(Atom a1, Atom a2) { // TODO: something like return Integer.compare(a1.layer,a2.layer) // would be more efficient if (a1.layer < a2.layer) return -1; else if(a1.layer > a2.layer) return 1; else return 0; } } // All the atoms we're currently rendering. List<Atom> atoms = new ArrayList<Atom>(); private Map<BufferedImage, Integer> textures = new HashMap<BufferedImage, Integer>(); // Current position of the viewport on the map private int eye_x, eye_y; /** * Sorts the render list from bottom to top layerwise. */ public void sortLayers() { Collections.sort(atoms, new Renderer.LayerComparator()); } }