/** * Entry point to our test * * @param argv The arguments passed to the test */ public static void main(String[] argv) { try { Renderer.setLineStripRenderer(Renderer.QUAD_BASED_LINE_STRIP_RENDERER); Renderer.getLineStripRenderer().setLineCaps(true); AppGameContainer container = new AppGameContainer(new LineRenderTest()); container.setDisplayMode(800, 600, false); container.start(); } catch (SlickException e) { e.printStackTrace(); } }
/** * Uploads the pixel data to the given texture at the specified position. This only needs to be * called once, after the pixel data has changed. * * @param texture the texture to modify * @param x the x position to place the pixel data on the texture * @param y the y position to place the pixel data on the texture */ public void apply(Texture texture, int x, int y) { if (x + width > texture.getTextureWidth() || y + height > texture.getTextureHeight()) throw new IndexOutOfBoundsException("pixel data won't fit in given texture"); position(length()); pixels.flip(); int glFmt = format.getOGLType(); final SGL GL = Renderer.get(); texture.bind(); GL.glTexSubImage2D( SGL.GL_TEXTURE_2D, 0, x, y, width, height, glFmt, SGL.GL_UNSIGNED_BYTE, pixels); }
/** * Entry point to our test * * @param argv The arguments to pass into the test */ public static void main(String[] argv) { try { Renderer.setRenderer(Renderer.VERTEX_ARRAY_RENDERER); AppGameContainer container = new AppGameContainer(new GradientTest()); container.setDisplayMode(800, 600, false); container.start(); } catch (SlickException e) { e.printStackTrace(); } }
/** * A particle syste responsible for maintaining a set of data about individual particles which are * created and controlled by assigned emitters. This pseudo chaotic nature hopes to give more * organic looking effects * * @author kevin */ public class ParticleSystem { /** The renderer to use for all GL operations */ protected static SGL GL = Renderer.get(); /** The blending mode for the glowy style */ public static final int BLEND_ADDITIVE = 1; /** The blending mode for the normal style */ public static final int BLEND_COMBINE = 2; /** The default number of particles in the system */ private static final int DEFAULT_PARTICLES = 100; /** * Set the path from which images should be loaded * * @param path The path from which images should be loaded */ public static void setRelativePath(String path) { ConfigurableEmitter.setRelativePath(path); } /** * A pool of particles being used by a specific emitter * * @author void */ private class ParticlePool { /** The particles being rendered and maintained */ public Particle[] particles; /** * The list of particles left to be used, if this size() == 0 then the particle engine was too * small for the effect */ public ArrayList available; /** * Create a new particle pool contiaining a set of particles * * @param system The system that owns the particles over all * @param maxParticles The maximum number of particles in the pool */ public ParticlePool(ParticleSystem system, int maxParticles) { particles = new Particle[maxParticles]; available = new ArrayList(); for (int i = 0; i < particles.length; i++) { particles[i] = createParticle(system); } reset(system); } /** * Rest the list of particles * * @param system The system in which the particle belong */ public void reset(ParticleSystem system) { available.clear(); for (int i = 0; i < particles.length; i++) { available.add(particles[i]); } } } /** * A map from emitter to a the particle pool holding the particles it uses void: this is now * sorted by emitters to allow emitter specfic state to be set for each emitter. actually this is * used to allow setting an individual blend mode for each emitter */ protected HashMap particlesByEmitter = new HashMap(); /** The maximum number of particles allows per emitter */ protected int maxParticlesPerEmitter; /** The list of emittered producing and controlling particles */ protected ArrayList emitters = new ArrayList(); /** The dummy particle to return should no more particles be available */ protected Particle dummy; /** The blending mode */ private int blendingMode = BLEND_COMBINE; /** The number of particles in use */ private int pCount; /** True if we're going to use points to render the particles */ private boolean usePoints; /** The x coordinate at which this system should be rendered */ private float x; /** The x coordinate at which this system should be rendered */ private float y; /** True if we should remove completed emitters */ private boolean removeCompletedEmitters = true; /** The default image for the particles */ private Image sprite; /** True if the particle system is visible */ private boolean visible = true; /** The name of the default image */ private String defaultImageName; /** The mask used to make the particle image background transparent if any */ private Color mask; /** * Create a new particle system * * @param defaultSprite The sprite to render for each particle */ public ParticleSystem(Image defaultSprite) { this(defaultSprite, DEFAULT_PARTICLES); } /** * Create a new particle system * * @param defaultSpriteRef The sprite to render for each particle */ public ParticleSystem(String defaultSpriteRef) { this(defaultSpriteRef, DEFAULT_PARTICLES); } /** Reset the state of the system */ public void reset() { Iterator pools = particlesByEmitter.values().iterator(); while (pools.hasNext()) { ParticlePool pool = (ParticlePool) pools.next(); pool.reset(this); } for (int i = 0; i < emitters.size(); i++) { ParticleEmitter emitter = (ParticleEmitter) emitters.get(i); emitter.resetState(); } } /** * Check if this system is currently visible, i.e. it's actually rendered * * @return True if the particle system is rendered */ public boolean isVisible() { return visible; } /** * Indicate whether the particle system should be visible, i.e. whether it'll actually render * * @param visible True if the particle system should render */ public void setVisible(boolean visible) { this.visible = visible; } /** * Indicate if completed emitters should be removed * * @param remove True if completed emitters should be removed */ public void setRemoveCompletedEmitters(boolean remove) { removeCompletedEmitters = remove; } /** * Indicate if this engine should use points to render the particles * * @param usePoints True if points should be used to render the particles */ public void setUsePoints(boolean usePoints) { this.usePoints = usePoints; } /** * Check if this engine should use points to render the particles * * @return True if the engine should use points to render the particles */ public boolean usePoints() { return usePoints; } /** * Create a new particle system * * @param defaultSpriteRef The sprite to render for each particle * @param maxParticles The number of particles available in the system */ public ParticleSystem(String defaultSpriteRef, int maxParticles) { this(defaultSpriteRef, maxParticles, null); } /** * Create a new particle system * * @param defaultSpriteRef The sprite to render for each particle * @param maxParticles The number of particles available in the system * @param mask The mask used to make the sprite image transparent */ public ParticleSystem(String defaultSpriteRef, int maxParticles, Color mask) { this.maxParticlesPerEmitter = maxParticles; this.mask = mask; setDefaultImageName(defaultSpriteRef); dummy = createParticle(this); } /** * Create a new particle system * * @param defaultSprite The sprite to render for each particle * @param maxParticles The number of particles available in the system */ public ParticleSystem(Image defaultSprite, int maxParticles) { this.maxParticlesPerEmitter = maxParticles; sprite = defaultSprite; dummy = createParticle(this); } /** * Set the default image name * * @param ref The default image name */ public void setDefaultImageName(String ref) { defaultImageName = ref; sprite = null; } /** * Get the blending mode in use * * @see #BLEND_COMBINE * @see #BLEND_ADDITIVE * @return The blending mode in use */ public int getBlendingMode() { return blendingMode; } /** * Create a particle specific to this system, override for your own implementations. These * particles will be cached and reused within this system. * * @param system The system owning this particle * @return The newly created particle. */ protected Particle createParticle(ParticleSystem system) { return new Particle(system); } /** * Set the blending mode for the particles * * @param mode The mode for blending particles together */ public void setBlendingMode(int mode) { this.blendingMode = mode; } /** * Get the number of emitters applied to the system * * @return The number of emitters applied to the system */ public int getEmitterCount() { return emitters.size(); } /** * Get an emitter a specified index int he list contained within this system * * @param index The index of the emitter to retrieve * @return The particle emitter */ public ParticleEmitter getEmitter(int index) { return (ParticleEmitter) emitters.get(index); } /** * Add a particle emitter to be used on this system * * @param emitter The emitter to be added */ public void addEmitter(ParticleEmitter emitter) { emitters.add(emitter); ParticlePool pool = new ParticlePool(this, maxParticlesPerEmitter); particlesByEmitter.put(emitter, pool); } /** * Remove a particle emitter that is currently used in the system * * @param emitter The emitter to be removed */ public void removeEmitter(ParticleEmitter emitter) { emitters.remove(emitter); particlesByEmitter.remove(emitter); } /** Remove all the emitters from the system */ public void removeAllEmitters() { for (int i = 0; i < emitters.size(); i++) { removeEmitter((ParticleEmitter) emitters.get(i)); i--; } } /** * Get the x coordiante of the position of the system * * @return The x coordinate of the position of the system */ public float getPositionX() { return x; } /** * Get the y coordiante of the position of the system * * @return The y coordinate of the position of the system */ public float getPositionY() { return y; } /** * Set the position at which this system should render relative to the current graphics context * setup * * @param x The x coordinate at which this system should be centered * @param y The y coordinate at which this system should be centered */ public void setPosition(float x, float y) { this.x = x; this.y = y; } /** Render the particles in the system */ public void render() { render(x, y); } /** * Render the particles in the system * * @param x The x coordinate to render the particle system at (in the current coordinate space) * @param y The y coordinate to render the particle system at (in the current coordiante space) */ public void render(float x, float y) { if ((sprite == null) && (defaultImageName != null)) { loadSystemParticleImage(); } if (!visible) { return; } GL.glTranslatef(x, y, 0); if (blendingMode == BLEND_ADDITIVE) { GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE); } if (usePoints()) { GL.glEnable(SGL.GL_POINT_SMOOTH); TextureImpl.bindNone(); } // iterate over all emitters for (int emitterIdx = 0; emitterIdx < emitters.size(); emitterIdx++) { // get emitter ParticleEmitter emitter = (ParticleEmitter) emitters.get(emitterIdx); // check for additive override and enable when set if (emitter.useAdditive()) { GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE); } // now get the particle pool for this emitter and render all particles that are in use ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter); Image image = emitter.getImage(); if (image == null) { image = this.sprite; } if (!emitter.isOriented() && !emitter.usePoints(this)) { image.startUse(); } for (int i = 0; i < pool.particles.length; i++) { if (pool.particles[i].inUse()) pool.particles[i].render(); } if (!emitter.isOriented() && !emitter.usePoints(this)) { image.endUse(); } // reset additive blend mode if (emitter.useAdditive()) { GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA); } } if (usePoints()) { GL.glDisable(SGL.GL_POINT_SMOOTH); } if (blendingMode == BLEND_ADDITIVE) { GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA); } Color.white.bind(); GL.glTranslatef(-x, -y, 0); } /** Load the system particle image as the extension permissions */ private void loadSystemParticleImage() { AccessController.doPrivileged( new PrivilegedAction() { public Object run() { try { if (mask != null) { sprite = new Image(defaultImageName, mask); } else { sprite = new Image(defaultImageName); } } catch (SlickException e) { Log.error(e); defaultImageName = null; } return null; // nothing to return } }); } /** * Update the system, request the assigned emitters update the particles * * @param delta The amount of time thats passed since last update in milliseconds */ public void update(int delta) { if ((sprite == null) && (defaultImageName != null)) { loadSystemParticleImage(); } ArrayList removeMe = new ArrayList(); for (int i = 0; i < emitters.size(); i++) { ParticleEmitter emitter = (ParticleEmitter) emitters.get(i); if (emitter.isEnabled()) { emitter.update(this, delta); if (removeCompletedEmitters) { if (emitter.completed()) { removeMe.add(emitter); particlesByEmitter.remove(emitter); } } } } emitters.removeAll(removeMe); pCount = 0; if (!particlesByEmitter.isEmpty()) { Iterator it = particlesByEmitter.values().iterator(); while (it.hasNext()) { ParticlePool pool = (ParticlePool) it.next(); for (int i = 0; i < pool.particles.length; i++) { if (pool.particles[i].life > 0) { pool.particles[i].update(delta); pCount++; } } } } } /** * Get the number of particles in use in this system * * @return The number of particles in use in this system */ public int getParticleCount() { return pCount; } /** * Get a new particle from the system. This should be used by emitters to request particles * * @param emitter The emitter requesting the particle * @param life The time the new particle should live for * @return A particle from the system */ public Particle getNewParticle(ParticleEmitter emitter, float life) { ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter); ArrayList available = pool.available; if (available.size() > 0) { Particle p = (Particle) available.remove(available.size() - 1); p.init(emitter, life); p.setImage(sprite); return p; } Log.warn("Ran out of particles (increase the limit)!"); return dummy; } /** * Release a particle back to the system once it has expired * * @param particle The particle to be released */ public void release(Particle particle) { if (particle != dummy) { ParticlePool pool = (ParticlePool) particlesByEmitter.get(particle.getEmitter()); pool.available.add(particle); } } /** * Release all the particles owned by the specified emitter * * @param emitter The emitter owning the particles that should be released */ public void releaseAll(ParticleEmitter emitter) { if (!particlesByEmitter.isEmpty()) { Iterator it = particlesByEmitter.values().iterator(); while (it.hasNext()) { ParticlePool pool = (ParticlePool) it.next(); for (int i = 0; i < pool.particles.length; i++) { if (pool.particles[i].inUse()) { if (pool.particles[i].getEmitter() == emitter) { pool.particles[i].setLife(-1); release(pool.particles[i]); } } } } } } /** * Move all the particles owned by the specified emitter * * @param emitter The emitter owning the particles that should be released * @param x The amount on the x axis to move the particles * @param y The amount on the y axis to move the particles */ public void moveAll(ParticleEmitter emitter, int x, int y) { if (!particlesByEmitter.isEmpty()) { Iterator it = particlesByEmitter.values().iterator(); while (it.hasNext()) { ParticlePool pool = (ParticlePool) it.next(); for (int i = 0; i < pool.particles.length; i++) { if (pool.particles[i].inUse()) { pool.particles[i].move(x, y); } } } } } /** * Create a duplicate of this system. This would have been nicer as a different interface but may * cause to much API change headache. Maybe next full version release it should be rethought. * * <p>TODO: Consider refactor at next point release * * @return A copy of this particle system * @throws SlickException Indicates a failure during copy or a invalid particle system to be * duplicated */ public ParticleSystem duplicate() throws SlickException { for (int i = 0; i < emitters.size(); i++) { if (!(emitters.get(i) instanceof ConfigurableEmitter)) { throw new SlickException("Only systems contianing configurable emitters can be duplicated"); } } ParticleSystem theCopy = null; try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ParticleIO.saveConfiguredSystem(bout, this); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); theCopy = ParticleIO.loadConfiguredSystem(bin); } catch (IOException e) { Log.error("Failed to duplicate particle system"); throw new SlickException("Unable to duplicated particle system", e); } return theCopy; } }
/** * A font implementation that will parse BMFont format font files. The font files can be output by * Hiero, which is included with Slick, and also the AngelCode font tool available at: * * <p><a * href="http://www.angelcode.com/products/bmfont/">http://www.angelcode.com/products/bmfont/</a> * * <p>This implementation copes with both the font display and kerning information allowing nicer * looking paragraphs of text. Note that this utility only supports the text BMFont format * definition file. * * @author kevin * @author Nathan Sweet <*****@*****.**> */ public class AngelCodeFont implements Font, java.io.Serializable { /** The renderer to use for all GL operations */ private static SGL GL = Renderer.get(); /** * The line cache size, this is how many lines we can render before starting to regenerate lists */ private static final int DISPLAY_LIST_CACHE_SIZE = 200; /** The highest character that AngelCodeFont will support. */ private static final int MAX_CHAR = 255; /** True if this font should use display list caching */ private boolean displayListCaching = true; /** The image containing the bitmap font */ private Image fontImage; /** The characters building up the font */ private CharDef[] chars; /** The height of a line */ private int lineHeight; /** The first display list ID */ private int baseDisplayListID = -1; /** The eldest display list ID */ private int eldestDisplayListID; /** The eldest display list */ private DisplayList eldestDisplayList; /** The display list cache for rendered lines */ private final LinkedHashMap displayLists = new LinkedHashMap(DISPLAY_LIST_CACHE_SIZE, 1, true) { protected boolean removeEldestEntry(Entry eldest) { eldestDisplayList = (DisplayList) eldest.getValue(); eldestDisplayListID = eldestDisplayList.id; return false; } }; /** * Create a new font based on a font definition from AngelCode's tool and the font image generated * from the tool. * * @param fntFile The location of the font defnition file * @param image The image to use for the font * @throws SlickException Indicates a failure to load either file */ public AngelCodeFont(String fntFile, Image image) throws SlickException { fontImage = image; parseFnt(ResourceLoader.getResourceAsStream(fntFile)); } /** * Create a new font based on a font definition from AngelCode's tool and the font image generated * from the tool. * * @param fntFile The location of the font defnition file * @param imgFile The location of the font image * @throws SlickException Indicates a failure to load either file */ public AngelCodeFont(String fntFile, String imgFile) throws SlickException { fontImage = new Image(imgFile); parseFnt(ResourceLoader.getResourceAsStream(fntFile)); } /** * Create a new font based on a font definition from AngelCode's tool and the font image generated * from the tool. * * @param fntFile The location of the font defnition file * @param image The image to use for the font * @param caching True if this font should use display list caching * @throws SlickException Indicates a failure to load either file */ public AngelCodeFont(String fntFile, Image image, boolean caching) throws SlickException { fontImage = image; displayListCaching = caching; parseFnt(ResourceLoader.getResourceAsStream(fntFile)); } /** * Create a new font based on a font definition from AngelCode's tool and the font image generated * from the tool. * * @param fntFile The location of the font defnition file * @param imgFile The location of the font image * @param caching True if this font should use display list caching * @throws SlickException Indicates a failure to load either file */ public AngelCodeFont(String fntFile, String imgFile, boolean caching) throws SlickException { fontImage = new Image(imgFile); displayListCaching = caching; parseFnt(ResourceLoader.getResourceAsStream(fntFile)); } /** * Create a new font based on a font definition from AngelCode's tool and the font image generated * from the tool. * * @param name The name to assign to the font image in the image store * @param fntFile The stream of the font defnition file * @param imgFile The stream of the font image * @throws SlickException Indicates a failure to load either file */ public AngelCodeFont(String name, InputStream fntFile, InputStream imgFile) throws SlickException { fontImage = new Image(imgFile, name, false); parseFnt(fntFile); } /** * Create a new font based on a font definition from AngelCode's tool and the font image generated * from the tool. * * @param name The name to assign to the font image in the image store * @param fntFile The stream of the font defnition file * @param imgFile The stream of the font image * @param caching True if this font should use display list caching * @throws SlickException Indicates a failure to load either file */ public AngelCodeFont(String name, InputStream fntFile, InputStream imgFile, boolean caching) throws SlickException { fontImage = new Image(imgFile, name, false); displayListCaching = caching; parseFnt(fntFile); } /** * Parse the font definition file * * @param fntFile The stream from which the font file can be read * @throws SlickException */ private void parseFnt(InputStream fntFile) throws SlickException { if (displayListCaching) { baseDisplayListID = GL.glGenLists(DISPLAY_LIST_CACHE_SIZE); if (baseDisplayListID == 0) displayListCaching = false; } try { // now parse the font file BufferedReader in = new BufferedReader(new InputStreamReader(fntFile)); String info = in.readLine(); String common = in.readLine(); String page = in.readLine(); Map kerning = new HashMap(64); List charDefs = new ArrayList(MAX_CHAR); int maxChar = 0; boolean done = false; while (!done) { String line = in.readLine(); if (line == null) { done = true; } else { if (line.startsWith("chars c")) { // ignore } else if (line.startsWith("char")) { CharDef def = parseChar(line); if (def != null) { maxChar = Math.max(maxChar, def.id); charDefs.add(def); } } if (line.startsWith("kernings c")) { // ignore } else if (line.startsWith("kerning")) { StringTokenizer tokens = new StringTokenizer(line, " ="); tokens.nextToken(); // kerning tokens.nextToken(); // first short first = Short.parseShort(tokens.nextToken()); // first value tokens.nextToken(); // second int second = Integer.parseInt(tokens.nextToken()); // second value tokens.nextToken(); // offset int offset = Integer.parseInt(tokens.nextToken()); // offset value List values = (List) kerning.get(new Short(first)); if (values == null) { values = new ArrayList(); kerning.put(new Short(first), values); } // Pack the character and kerning offset into a short. values.add(new Short((short) ((offset << 8) | second))); } } } chars = new CharDef[maxChar + 1]; for (Iterator iter = charDefs.iterator(); iter.hasNext(); ) { CharDef def = (CharDef) iter.next(); chars[def.id] = def; } // Turn each list of kerning values into a short[] and set on the chardef. for (Iterator iter = kerning.entrySet().iterator(); iter.hasNext(); ) { Entry entry = (Entry) iter.next(); short first = ((Short) entry.getKey()).shortValue(); List valueList = (List) entry.getValue(); short[] valueArray = new short[valueList.size()]; int i = 0; for (Iterator valueIter = valueList.iterator(); valueIter.hasNext(); i++) valueArray[i] = ((Short) valueIter.next()).shortValue(); chars[first].kerning = valueArray; } } catch (IOException e) { Log.error(e); throw new SlickException("Failed to parse font file: " + fntFile); } } /** * Parse a single character line from the definition * * @param line The line to be parsed * @return The character definition from the line * @throws SlickException Indicates a given character is not valid in an angel code font */ private CharDef parseChar(String line) throws SlickException { CharDef def = new CharDef(); StringTokenizer tokens = new StringTokenizer(line, " ="); tokens.nextToken(); // char tokens.nextToken(); // id def.id = Short.parseShort(tokens.nextToken()); // id value if (def.id < 0) { return null; } if (def.id > MAX_CHAR) { throw new SlickException( "Invalid character '" + def.id + "': AngelCodeFont does not support characters above " + MAX_CHAR); } tokens.nextToken(); // x def.x = Short.parseShort(tokens.nextToken()); // x value tokens.nextToken(); // y def.y = Short.parseShort(tokens.nextToken()); // y value tokens.nextToken(); // width def.width = Short.parseShort(tokens.nextToken()); // width value tokens.nextToken(); // height def.height = Short.parseShort(tokens.nextToken()); // height value tokens.nextToken(); // x offset def.xoffset = Short.parseShort(tokens.nextToken()); // xoffset value tokens.nextToken(); // y offset def.yoffset = Short.parseShort(tokens.nextToken()); // yoffset value tokens.nextToken(); // xadvance def.xadvance = Short.parseShort(tokens.nextToken()); // xadvance def.init(); if (def.id != ' ') { lineHeight = Math.max(def.height + def.yoffset, lineHeight); } return def; } /** @see org.newdawn.slick.Font#drawString(float, float, java.lang.String) */ public void drawString(float x, float y, String text) { drawString(x, y, text, Color.white); } /** * @see org.newdawn.slick.Font#drawString(float, float, java.lang.String, org.newdawn.slick.Color) */ public void drawString(float x, float y, String text, Color col) { drawString(x, y, text, col, 0, text.length() - 1); } /** @see Font#drawString(float, float, String, Color, int, int) */ public void drawString(float x, float y, String text, Color col, int startIndex, int endIndex) { fontImage.bind(); col.bind(); GL.glTranslatef(x, y, 0); if (displayListCaching && startIndex == 0 && endIndex == text.length() - 1) { DisplayList displayList = (DisplayList) displayLists.get(text + "" + startIndex + "" + endIndex); if (displayList != null) { GL.glCallList(displayList.id); } else { // Compile a new display list. displayList = new DisplayList(); displayList.text = text; int displayListCount = displayLists.size(); if (displayListCount < DISPLAY_LIST_CACHE_SIZE) { displayList.id = baseDisplayListID + displayListCount; } else { displayList.id = eldestDisplayListID; displayLists.remove(eldestDisplayList.text); } displayLists.put(text + "" + startIndex + "" + endIndex, displayList); GL.glNewList(displayList.id, SGL.GL_COMPILE_AND_EXECUTE); render(text, startIndex, endIndex); GL.glEndList(); } } else { render(text, startIndex, endIndex); } GL.glTranslatef(-x, -y, 0); } /** * Render based on immediate rendering * * @param text The text to be rendered * @param start The index of the first character in the string to render * @param end The index of the last character in the string to render */ private void render(String text, int start, int end) { GL.glBegin(SGL.GL_QUADS); int x = 0, y = 0; CharDef lastCharDef = null; char[] data = text.toCharArray(); for (int i = 0; i < data.length; i++) { int id = data[i]; if (id == '\n') { x = 0; y += getLineHeight(); continue; } if (id >= chars.length) { continue; } CharDef charDef = chars[id]; if (charDef == null) { continue; } if (lastCharDef != null) x += lastCharDef.getKerning(id); lastCharDef = charDef; if ((i >= start) && (i <= end)) { charDef.draw(x, y); } x += charDef.xadvance; } GL.glEnd(); } /** * Returns the distance from the y drawing location to the top most pixel of the specified text. * * @param text The text that is to be tested * @return The yoffset from the y draw location at which text will start */ public int getYOffset(String text) { DisplayList displayList = null; if (displayListCaching) { displayList = (DisplayList) displayLists.get(text); if (displayList != null && displayList.yOffset != null) return displayList.yOffset.intValue(); } int stopIndex = text.indexOf('\n'); if (stopIndex == -1) stopIndex = text.length(); int minYOffset = 10000; for (int i = 0; i < stopIndex; i++) { int id = text.charAt(i); CharDef charDef = chars[id]; if (charDef == null) { continue; } minYOffset = Math.min(charDef.yoffset, minYOffset); } if (displayList != null) displayList.yOffset = new Short((short) minYOffset); return minYOffset; } /** @see org.newdawn.slick.Font#getHeight(java.lang.String) */ public int getHeight(String text) { DisplayList displayList = null; if (displayListCaching) { displayList = (DisplayList) displayLists.get(text); if (displayList != null && displayList.height != null) return displayList.height.intValue(); } int lines = 0; int maxHeight = 0; for (int i = 0; i < text.length(); i++) { int id = text.charAt(i); if (id == '\n') { lines++; maxHeight = 0; continue; } // ignore space, it doesn't contribute to height if (id == ' ') { continue; } CharDef charDef = chars[id]; if (charDef == null) { continue; } maxHeight = Math.max(charDef.height + charDef.yoffset, maxHeight); } maxHeight += lines * getLineHeight(); if (displayList != null) displayList.height = new Short((short) maxHeight); return maxHeight; } /** @see org.newdawn.slick.Font#getWidth(java.lang.String) */ public int getWidth(String text) { DisplayList displayList = null; if (displayListCaching) { displayList = (DisplayList) displayLists.get(text); if (displayList != null && displayList.width != null) return displayList.width.intValue(); } int maxWidth = 0; int width = 0; CharDef lastCharDef = null; for (int i = 0, n = text.length(); i < n; i++) { int id = text.charAt(i); if (id == '\n') { width = 0; continue; } if (id >= chars.length) { continue; } CharDef charDef = chars[id]; if (charDef == null) { continue; } if (lastCharDef != null) width += lastCharDef.getKerning(id); lastCharDef = charDef; if (i < n - 1) { width += charDef.xadvance; } else { width += charDef.width; } maxWidth = Math.max(maxWidth, width); } if (displayList != null) displayList.width = new Short((short) maxWidth); return maxWidth; } /** * The definition of a single character as defined in the AngelCode file format * * @author kevin */ private class CharDef implements java.io.Serializable { /** The id of the character */ public short id; /** The x location on the sprite sheet */ public short x; /** The y location on the sprite sheet */ public short y; /** The width of the character image */ public short width; /** The height of the character image */ public short height; /** The amount the x position should be offset when drawing the image */ public short xoffset; /** The amount the y position should be offset when drawing the image */ public short yoffset; /** The amount to move the current position after drawing the character */ public short xadvance; /** The image containing the character */ public Image image; /** The display list index for this character */ public short dlIndex; /** The kerning info for this character */ public short[] kerning; /** * Initialise the image by cutting the right section from the map produced by the AngelCode * tool. */ public void init() { image = fontImage.getSubImage(x, y, width, height); } /** @see java.lang.Object#toString() */ public String toString() { return "[CharDef id=" + id + " x=" + x + " y=" + y + "]"; } /** * Draw this character embedded in a image draw * * @param x The x position at which to draw the text * @param y The y position at which to draw the text */ public void draw(float x, float y) { image.drawEmbedded(x + xoffset, y + yoffset, width, height); } /** * Get the kerning offset between this character and the specified character. * * @param otherCodePoint The other code point * @return the kerning offset */ public int getKerning(int otherCodePoint) { if (kerning == null) return 0; int low = 0; int high = kerning.length - 1; while (low <= high) { int midIndex = (low + high) >>> 1; int value = kerning[midIndex]; int foundCodePoint = value & 0xff; if (foundCodePoint < otherCodePoint) low = midIndex + 1; else if (foundCodePoint > otherCodePoint) high = midIndex - 1; else return value >> 8; } return 0; } } /** @see org.newdawn.slick.Font#getLineHeight() */ public int getLineHeight() { return lineHeight; } /** * A descriptor for a single display list * * @author Nathan Sweet <*****@*****.**> */ private static class DisplayList { /** The if of the distance list */ int id; /** The offset of the line rendered */ Short yOffset; /** The width of the line rendered */ Short width; /** The height of the line rendered */ Short height; /** The text that the display list holds */ String text; } }
/** * A generic game container that handles the game loop, fps recording and managing the input system * * @author kevin */ public abstract class GameContainer implements GUIContext { /** The renderer to use for all GL operations */ protected static SGL GL = Renderer.get(); /** The shared drawable if any */ protected static Drawable SHARED_DRAWABLE; /** The time the last frame was rendered */ protected long lastFrame; /** The last time the FPS recorded */ protected long lastFPS; /** The last recorded FPS */ protected int recordedFPS; /** The current count of FPS */ protected int fps; /** True if we're currently running the game loop */ protected boolean running = true; /** The width of the display */ protected int width; /** The height of the display */ protected int height; /** The game being managed */ protected Game game; /** The default font to use in the graphics context */ private Font defaultFont; /** The graphics context to be passed to the game */ private Graphics graphics; /** The input system to pass to the game */ protected Input input; /** The FPS we want to lock to */ protected int targetFPS = -1; /** True if we should show the fps */ private boolean showFPS = true; /** The minimum logic update interval */ protected long minimumLogicInterval = 1; /** The stored delta */ protected long storedDelta; /** The maximum logic update interval */ protected long maximumLogicInterval = 0; /** The last game started */ protected Game lastGame; /** True if we should clear the screen each frame */ protected boolean clearEachFrame = true; /** True if the game is paused */ protected boolean paused; /** True if we should force exit */ protected boolean forceExit = true; /** True if vsync has been requested */ protected boolean vsync; /** Smoothed deltas requested */ protected boolean smoothDeltas; /** The number of samples we'll attempt through hardware */ protected int samples; /** True if this context supports multisample */ protected boolean supportsMultiSample; /** True if we should render when not focused */ protected boolean alwaysRender; /** True if we require stencil bits */ protected static boolean stencil; /** * Create a new game container wrapping a given game * * @param game The game to be wrapped */ protected GameContainer(Game game) { this.game = game; lastFrame = getTime(); getBuildVersion(); Log.checkVerboseLogSetting(); } public static void enableStencil() { stencil = true; } /** * Set the default font that will be intialised in the graphics held in this container * * @param font The font to use as default */ public void setDefaultFont(Font font) { if (font != null) { this.defaultFont = font; } else { Log.warn("Please provide a non null font"); } } /** * Indicate whether we want to try to use fullscreen multisampling. This will give antialiasing * across the whole scene using a hardware feature. * * @param samples The number of samples to attempt (2 is safe) */ public void setMultiSample(int samples) { this.samples = samples; } /** * Check if this hardware can support multi-sampling * * @return True if the hardware supports multi-sampling */ public boolean supportsMultiSample() { return supportsMultiSample; } /** * The number of samples we're attempting to performing using hardware multisampling * * @return The number of samples requested */ public int getSamples() { return samples; } /** * Indicate if we should force exitting the VM at the end of the game (default = true) * * @param forceExit True if we should force the VM exit */ public void setForceExit(boolean forceExit) { this.forceExit = forceExit; } /** * Indicate if we want to smooth deltas. This feature will report a delta based on the FPS not the * time passed. This works well with vsync. * * @param smoothDeltas True if we should report smooth deltas */ public void setSmoothDeltas(boolean smoothDeltas) { this.smoothDeltas = smoothDeltas; } /** * Check if the display is in fullscreen mode * * @return True if the display is in fullscreen mode */ public boolean isFullscreen() { return false; } /** * Get the aspect ratio of the screen * * @return The aspect ratio of the display */ public float getAspectRatio() { return getWidth() / getHeight(); } /** * Indicate whether we want to be in fullscreen mode. Note that the current display mode must be * valid as a fullscreen mode for this to work * * @param fullscreen True if we want to be in fullscreen mode * @throws SlickException Indicates we failed to change the display mode */ public void setFullscreen(boolean fullscreen) throws SlickException {} /** * Enable shared OpenGL context. After calling this all containers created will shared a single * parent context * * @throws SlickException Indicates a failure to create the shared drawable */ public static void enableSharedContext() throws SlickException { try { SHARED_DRAWABLE = new Pbuffer(64, 64, new PixelFormat(8, 0, 0), null); } catch (LWJGLException e) { throw new SlickException( "Unable to create the pbuffer used for shard context, buffers not supported", e); } } /** * Get the context shared by all containers * * @return The context shared by all the containers or null if shared context isn't enabled */ public static Drawable getSharedContext() { return SHARED_DRAWABLE; } /** * Indicate if we should clear the screen at the beginning of each frame. If you're rendering to * the whole screen each frame then setting this to false can give some performance improvements * * @param clear True if the the screen should be cleared each frame */ public void setClearEachFrame(boolean clear) { this.clearEachFrame = clear; } /** * Renitialise the game and the context in which it's being rendered * * @throws SlickException Indicates a failure rerun initialisation routines */ public void reinit() throws SlickException {} /** Pause the game - i.e. suspend updates */ public void pause() { setPaused(true); } /** Resumt the game - i.e. continue updates */ public void resume() { setPaused(false); } /** * Check if the container is currently paused. * * @return True if the container is paused */ public boolean isPaused() { return paused; } /** * Indicates if the game should be paused, i.e. if updates should be propogated through to the * game. * * @param paused True if the game should be paused */ public void setPaused(boolean paused) { this.paused = paused; } /** * True if this container should render when it has focus * * @return True if this container should render when it has focus */ public boolean getAlwaysRender() { return alwaysRender; } /** * Indicate whether we want this container to render when it has focus * * @param alwaysRender True if this container should render when it has focus */ public void setAlwaysRender(boolean alwaysRender) { this.alwaysRender = alwaysRender; } /** * Get the build number of slick * * @return The build number of slick */ public static int getBuildVersion() { try { Properties props = new Properties(); props.load(ResourceLoader.getResourceAsStream("version")); int build = Integer.parseInt(props.getProperty("build")); Log.info("Slick Build #" + build); return build; } catch (Exception e) { Log.info("Unable to determine Slick build number"); return -1; } } /** * Get the default system font * * @return The default system font */ @Override public Font getDefaultFont() { return defaultFont; } /** * Check if sound effects are enabled * * @return True if sound effects are enabled */ public boolean isSoundOn() { return SoundStore.get().soundsOn(); } /** * Check if music is enabled * * @return True if music is enabled */ public boolean isMusicOn() { return SoundStore.get().musicOn(); } /** * Indicate whether music should be enabled * * @param on True if music should be enabled */ public void setMusicOn(boolean on) { SoundStore.get().setMusicOn(on); } /** * Indicate whether sound effects should be enabled * * @param on True if sound effects should be enabled */ public void setSoundOn(boolean on) { SoundStore.get().setSoundsOn(on); } /** * Retrieve the current default volume for music * * @return the current default volume for music */ public float getMusicVolume() { return SoundStore.get().getMusicVolume(); } /** * Retrieve the current default volume for sound fx * * @return the current default volume for sound fx */ public float getSoundVolume() { return SoundStore.get().getSoundVolume(); } /** * Set the default volume for sound fx * * @param volume the new default value for sound fx volume */ public void setSoundVolume(float volume) { SoundStore.get().setSoundVolume(volume); } /** * Set the default volume for music * * @param volume the new default value for music volume */ public void setMusicVolume(float volume) { SoundStore.get().setMusicVolume(volume); } /** * Get the width of the standard screen resolution * * @return The screen width */ @Override public abstract int getScreenWidth(); /** * Get the height of the standard screen resolution * * @return The screen height */ @Override public abstract int getScreenHeight(); /** * Get the width of the game canvas * * @return The width of the game canvas */ @Override public int getWidth() { return width; } /** * Get the height of the game canvas * * @return The height of the game canvas */ @Override public int getHeight() { return height; } /** * Set the icon to be displayed if possible in this type of container * * @param ref The reference to the icon to be displayed * @throws SlickException Indicates a failure to load the icon */ public abstract void setIcon(String ref) throws SlickException; /** * Set the icons to be used for this application. Note that the size of the icon defines how it * will be used. Important ones to note * * <p>Windows window icon must be 16x16 Windows alt-tab icon must be 24x24 or 32x32 depending on * Windows version (XP=32) * * @param refs The reference to the icon to be displayed * @throws SlickException Indicates a failure to load the icon */ public abstract void setIcons(String[] refs) throws SlickException; /** * Get the accurate system time * * @return The system time in milliseconds */ @Override public long getTime() { return (Sys.getTime() * 1000) / Sys.getTimerResolution(); } /** * Sleep for a given period * * @param milliseconds The period to sleep for in milliseconds */ public void sleep(int milliseconds) { long target = getTime() + milliseconds; while (getTime() < target) { try { Thread.sleep(1); } catch (Exception e) { } } } /** * Set the mouse cursor to be displayed - this is a hardware cursor and hence shouldn't have any * impact on FPS. * * @param ref The location of the image to be loaded for the cursor * @param hotSpotX The x coordinate of the hotspot within the cursor image * @param hotSpotY The y coordinate of the hotspot within the cursor image * @throws SlickException Indicates a failure to load the cursor image or create the hardware * cursor */ @Override public abstract void setMouseCursor(String ref, int hotSpotX, int hotSpotY) throws SlickException; /** * Set the mouse cursor to be displayed - this is a hardware cursor and hence shouldn't have any * impact on FPS. * * @param data The image data from which the cursor can be construted * @param hotSpotX The x coordinate of the hotspot within the cursor image * @param hotSpotY The y coordinate of the hotspot within the cursor image * @throws SlickException Indicates a failure to load the cursor image or create the hardware * cursor */ @Override public abstract void setMouseCursor(ImageData data, int hotSpotX, int hotSpotY) throws SlickException; /** * Set the mouse cursor based on the contents of the image. Note that this will not take account * of render state type changes to images (rotation and such). If these effects are required it is * recommended that an offscreen buffer be used to produce an appropriate image. An offscreen * buffer will always be used to produce the new cursor and as such this operation an be very * expensive * * @param image The image to use as the cursor * @param hotSpotX The x coordinate of the hotspot within the cursor image * @param hotSpotY The y coordinate of the hotspot within the cursor image * @throws SlickException Indicates a failure to load the cursor image or create the hardware * cursor */ public abstract void setMouseCursor(Image image, int hotSpotX, int hotSpotY) throws SlickException; /** * Set the mouse cursor to be displayed - this is a hardware cursor and hence shouldn't have any * impact on FPS. * * @param cursor The cursor to use * @param hotSpotX The x coordinate of the hotspot within the cursor image * @param hotSpotY The y coordinate of the hotspot within the cursor image * @throws SlickException Indicates a failure to load the cursor image or create the hardware * cursor */ @Override public abstract void setMouseCursor(Cursor cursor, int hotSpotX, int hotSpotY) throws SlickException; /** * Get a cursor based on a image reference on the classpath. The image is assumed to be a * set/strip of cursor animation frames running from top to bottom. * * @param ref The reference to the image to be loaded * @param x The x-coordinate of the cursor hotspot (left {@literal ->} right) * @param y The y-coordinate of the cursor hotspot (bottom {@literal ->} top) * @param width The x width of the cursor * @param height The y height of the cursor * @param cursorDelays image delays between changing frames in animation * @throws SlickException Indicates a failure to load the image or a failure to create the * hardware cursor */ public void setAnimatedMouseCursor( String ref, int x, int y, int width, int height, int[] cursorDelays) throws SlickException { try { Cursor cursor; cursor = CursorLoader.get().getAnimatedCursor(ref, x, y, width, height, cursorDelays); setMouseCursor(cursor, x, y); } catch (IOException e) { throw new SlickException("Failed to set mouse cursor", e); } catch (LWJGLException e) { throw new SlickException("Failed to set mouse cursor", e); } } /** Set the default mouse cursor - i.e. the original cursor before any native cursor was set */ @Override public abstract void setDefaultMouseCursor(); /** * Get the input system * * @return The input system available to this game container */ @Override public Input getInput() { return input; } /** * Get the current recorded FPS (frames per second) * * @return The current FPS */ public int getFPS() { return recordedFPS; } /** * Indicate whether mouse cursor should be grabbed or not * * @param grabbed True if mouse cursor should be grabbed */ public abstract void setMouseGrabbed(boolean grabbed); /** * Check if the mouse cursor is current grabbed. This will cause it not to be seen. * * @return True if the mouse is currently grabbed */ public abstract boolean isMouseGrabbed(); /** * Retrieve the time taken to render the last frame, i.e. the change in time - delta. * * @return The time taken to render the last frame */ protected int getDelta() { long time = getTime(); int delta = (int) (time - lastFrame); lastFrame = time; return delta; } /** Updated the FPS counter */ protected void updateFPS() { if (getTime() - lastFPS > 1000) { lastFPS = getTime(); recordedFPS = fps; fps = 0; } fps++; } /** * Set the minimum amount of time in milliseonds that has to pass before update() is called on the * container game. This gives a way to limit logic updates compared to renders. * * @param interval The minimum interval between logic updates */ public void setMinimumLogicUpdateInterval(int interval) { minimumLogicInterval = interval; } /** * Set the maximum amount of time in milliseconds that can passed into the update method. Useful * for collision detection without sweeping. * * @param interval The maximum interval between logic updates */ public void setMaximumLogicUpdateInterval(int interval) { maximumLogicInterval = interval; } /** * Update and render the game * * @param delta The change in time since last update and render * @throws SlickException Indicates an internal fault to the game. */ protected void updateAndRender(int delta) throws SlickException { if (smoothDeltas) { if (getFPS() != 0) { delta = 1000 / getFPS(); } } input.poll(width, height); Music.poll(delta); if (!paused) { storedDelta += delta; if (storedDelta >= minimumLogicInterval) { try { if (maximumLogicInterval != 0) { long cycles = storedDelta / maximumLogicInterval; for (int i = 0; i < cycles; i++) { game.update(this, (int) maximumLogicInterval); } int remainder = (int) (storedDelta % maximumLogicInterval); if (remainder > minimumLogicInterval) { game.update(this, (int) (remainder % maximumLogicInterval)); storedDelta = 0; } else { storedDelta = remainder; } } else { game.update(this, (int) storedDelta); storedDelta = 0; } } catch (Throwable e) { // Log.error(e); throw new SlickException("Game.update() failure.", e); } } } else { game.update(this, 0); } if (hasFocus() || getAlwaysRender()) { if (clearEachFrame) { GL.glClear(SGL.GL_COLOR_BUFFER_BIT | SGL.GL_DEPTH_BUFFER_BIT); } GL.glLoadIdentity(); graphics.resetTransform(); graphics.resetFont(); graphics.resetLineWidth(); graphics.setAntiAlias(false); try { game.render(this, graphics); } catch (Throwable e) { // Log.error(e); throw new SlickException("Game.render() failure.", e); } graphics.resetTransform(); if (showFPS) { defaultFont.drawString(10, 10, "FPS: " + recordedFPS); } GL.flush(); } if (targetFPS != -1) { Display.sync(targetFPS); } } /** * Indicate if the display should update only when the game is visible (the default is true) * * @param updateOnlyWhenVisible True if we should updated only when the display is visible */ public void setUpdateOnlyWhenVisible(boolean updateOnlyWhenVisible) {} /** * Check if this game is only updating when visible to the user (default = true) * * @return True if the game is only updated when the display is visible */ public boolean isUpdatingOnlyWhenVisible() { return true; } /** Initialise the GL context */ protected void initGL() { Log.info("Starting display " + width + "x" + height); GL.initDisplay(width, height); if (input == null) { input = new Input(height); } input.init(height); // no need to remove listeners? // input.removeAllListeners(); if (game instanceof InputListener) { input.removeListener((InputListener) game); input.addListener((InputListener) game); } if (graphics != null) { graphics.setDimensions(getWidth(), getHeight()); } lastGame = game; } /** * Initialise the system components, OpenGL and OpenAL. * * @throws SlickException Indicates a failure to create a native handler */ protected void initSystem() throws SlickException { initGL(); setMusicVolume(1.0f); setSoundVolume(1.0f); graphics = new Graphics(width, height); defaultFont = graphics.getFont(); } /** Enter the orthographic mode */ protected void enterOrtho() { enterOrtho(width, height); } /** * Indicate whether the container should show the FPS * * @param show True if the container should show the FPS */ public void setShowFPS(boolean show) { showFPS = show; } /** * Check if the FPS is currently showing * * @return True if the FPS is showing */ public boolean isShowingFPS() { return showFPS; } /** * Set the target fps we're hoping to get * * @param fps The target fps we're hoping to get */ public void setTargetFrameRate(int fps) { targetFPS = fps; } /** * Indicate whether the display should be synced to the vertical refresh (stops tearing) * * @param vsync True if we want to sync to vertical refresh */ public void setVSync(boolean vsync) { this.vsync = vsync; Display.setVSyncEnabled(vsync); } /** * True if vsync is requested * * @return True if vsync is requested */ public boolean isVSyncRequested() { return vsync; } /** * True if the game is running * * @return True if the game is running */ protected boolean running() { return running; } /** * Inidcate we want verbose logging * * @param verbose True if we want verbose logging (INFO and DEBUG) */ public void setVerbose(boolean verbose) { Log.setVerbose(verbose); } /** Cause the game to exit and shutdown cleanly */ public void exit() { running = false; } /** * Check if the game currently has focus * * @return True if the game currently has focus */ public abstract boolean hasFocus(); /** * Get the graphics context used by this container. Note that this value may vary over the life * time of the game. * * @return The graphics context used by this container */ public Graphics getGraphics() { return graphics; } /** * Enter the orthographic mode * * @param xsize The size of the panel being used * @param ysize The size of the panel being used */ protected void enterOrtho(int xsize, int ysize) { GL.enterOrtho(xsize, ysize); } }
/** * @author Mark Bernard * <p>Use this class to render shpaes directly to OpenGL. Allows you to bypass the Graphics * class. */ public final class ShapeRenderer { /** The renderer to use for all GL operations */ private static SGL GL = Renderer.get(); /** The renderer to use line strips */ private static LineStripRenderer LSR = Renderer.getLineStripRenderer(); /** * Draw the outline of the given shape. Only the vertices are set. The colour has to be set * independently of this method. * * @param shape The shape to draw. */ public static final void draw(Shape shape) { Texture t = TextureImpl.getLastBind(); TextureImpl.bindNone(); float points[] = shape.getPoints(); LSR.start(); for (int i = 0; i < points.length; i += 2) { LSR.vertex(points[i], points[i + 1]); } if (shape.closed()) { LSR.vertex(points[0], points[1]); } LSR.end(); if (t == null) { TextureImpl.bindNone(); } else { t.bind(); } } /** * Draw the outline of the given shape. Only the vertices are set. The colour has to be set * independently of this method. * * @param shape The shape to draw. * @param fill The fill to apply */ public static final void draw(Shape shape, ShapeFill fill) { float points[] = shape.getPoints(); Texture t = TextureImpl.getLastBind(); TextureImpl.bindNone(); float center[] = shape.getCenter(); GL.glBegin(SGL.GL_LINE_STRIP); for (int i = 0; i < points.length; i += 2) { fill.colorAt(shape, points[i], points[i + 1]).bind(); Vector2f offset = fill.getOffsetAt(shape, points[i], points[i + 1]); GL.glVertex2f(points[i] + offset.x, points[i + 1] + offset.y); } if (shape.closed()) { fill.colorAt(shape, points[0], points[1]).bind(); Vector2f offset = fill.getOffsetAt(shape, points[0], points[1]); GL.glVertex2f(points[0] + offset.x, points[1] + offset.y); } GL.glEnd(); if (t == null) { TextureImpl.bindNone(); } else { t.bind(); } } /** * Check there are enough points to fill * * @param shape THe shape we're drawing * @return True if the fill is valid */ public static boolean validFill(Shape shape) { if (shape.getTriangles() == null) { return false; } return shape.getTriangles().getTriangleCount() != 0; } /** * Draw the the given shape filled in. Only the vertices are set. The colour has to be set * independently of this method. * * @param shape The shape to fill. */ public static final void fill(Shape shape) { if (!validFill(shape)) { return; } Texture t = TextureImpl.getLastBind(); TextureImpl.bindNone(); fill( shape, new PointCallback() { public float[] preRenderPoint(Shape shape, float x, float y) { // do nothing, we're just filling the shape this time return null; } }); if (t == null) { TextureImpl.bindNone(); } else { t.bind(); } } /** * Draw the the given shape filled in. Only the vertices are set. The colour has to be set * independently of this method. * * @param shape The shape to fill. * @param callback The callback that will be invoked for each shape point */ private static final void fill(Shape shape, PointCallback callback) { Triangulator tris = shape.getTriangles(); GL.glBegin(SGL.GL_TRIANGLES); for (int i = 0; i < tris.getTriangleCount(); i++) { for (int p = 0; p < 3; p++) { float[] pt = tris.getTrianglePoint(i, p); float[] np = callback.preRenderPoint(shape, pt[0], pt[1]); if (np == null) { GL.glVertex2f(pt[0], pt[1]); } else { GL.glVertex2f(np[0], np[1]); } } } GL.glEnd(); } /** * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to * be set independently of this method. * * @param shape The shape to texture. * @param image The image to tile across the shape */ public static final void texture(Shape shape, Image image) { texture(shape, image, 0.01f, 0.01f); } /** * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to * be set independently of this method. This method is required to fit the texture once across the * shape. * * @param shape The shape to texture. * @param image The image to tile across the shape */ public static final void textureFit(Shape shape, Image image) { textureFit(shape, image, 1f, 1f); } /** * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to * be set independently of this method. * * @param shape The shape to texture. * @param image The image to tile across the shape * @param scaleX The scale to apply on the x axis for texturing * @param scaleY The scale to apply on the y axis for texturing */ public static final void texture( Shape shape, final Image image, final float scaleX, final float scaleY) { if (!validFill(shape)) { return; } final Texture t = TextureImpl.getLastBind(); image.getTexture().bind(); fill( shape, new PointCallback() { public float[] preRenderPoint(Shape shape, float x, float y) { float tx = x * scaleX; float ty = y * scaleY; tx = image.getTextureOffsetX() + (image.getTextureWidth() * tx); ty = image.getTextureOffsetY() + (image.getTextureHeight() * ty); GL.glTexCoord2f(tx, ty); return null; } }); float points[] = shape.getPoints(); if (t == null) { TextureImpl.bindNone(); } else { t.bind(); } } /** * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to * be set independently of this method. This method is required to fit the texture scaleX times * across the shape and scaleY times down the shape. * * @param shape The shape to texture. * @param image The image to tile across the shape * @param scaleX The scale to apply on the x axis for texturing * @param scaleY The scale to apply on the y axis for texturing */ public static final void textureFit( Shape shape, final Image image, final float scaleX, final float scaleY) { if (!validFill(shape)) { return; } float points[] = shape.getPoints(); Texture t = TextureImpl.getLastBind(); image.getTexture().bind(); final float minX = shape.getX(); final float minY = shape.getY(); final float maxX = shape.getMaxX() - minX; final float maxY = shape.getMaxY() - minY; fill( shape, new PointCallback() { public float[] preRenderPoint(Shape shape, float x, float y) { x -= shape.getMinX(); y -= shape.getMinY(); x /= (shape.getMaxX() - shape.getMinX()); y /= (shape.getMaxY() - shape.getMinY()); float tx = x * scaleX; float ty = y * scaleY; tx = image.getTextureOffsetX() + (image.getTextureWidth() * tx); ty = image.getTextureOffsetY() + (image.getTextureHeight() * ty); GL.glTexCoord2f(tx, ty); return null; } }); if (t == null) { TextureImpl.bindNone(); } else { t.bind(); } } /** * Draw the the given shape filled in. Only the vertices are set. The colour has to be set * independently of this method. * * @param shape The shape to fill. * @param fill The fill to apply */ public static final void fill(final Shape shape, final ShapeFill fill) { if (!validFill(shape)) { return; } Texture t = TextureImpl.getLastBind(); TextureImpl.bindNone(); final float center[] = shape.getCenter(); fill( shape, new PointCallback() { public float[] preRenderPoint(Shape shape, float x, float y) { fill.colorAt(shape, x, y).bind(); Vector2f offset = fill.getOffsetAt(shape, x, y); return new float[] {offset.x + x, offset.y + y}; } }); if (t == null) { TextureImpl.bindNone(); } else { t.bind(); } } /** * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to * be set independently of this method. * * @param shape The shape to texture. * @param image The image to tile across the shape * @param scaleX The scale to apply on the x axis for texturing * @param scaleY The scale to apply on the y axis for texturing * @param fill The fill to apply */ public static final void texture( final Shape shape, final Image image, final float scaleX, final float scaleY, final ShapeFill fill) { if (!validFill(shape)) { return; } Texture t = TextureImpl.getLastBind(); image.getTexture().bind(); final float center[] = shape.getCenter(); fill( shape, new PointCallback() { public float[] preRenderPoint(Shape shape, float x, float y) { fill.colorAt(shape, x - center[0], y - center[1]).bind(); Vector2f offset = fill.getOffsetAt(shape, x, y); x += offset.x; y += offset.y; float tx = x * scaleX; float ty = y * scaleY; tx = image.getTextureOffsetX() + (image.getTextureWidth() * tx); ty = image.getTextureOffsetY() + (image.getTextureHeight() * ty); GL.glTexCoord2f(tx, ty); return new float[] {offset.x + x, offset.y + y}; } }); if (t == null) { TextureImpl.bindNone(); } else { t.bind(); } } /** * Draw the the given shape filled in with a texture. Only the vertices are set. The colour has to * be set independently of this method. * * @param shape The shape to texture. * @param image The image to tile across the shape * @param gen The texture coordinate generator to create coordiantes for the shape */ public static final void texture(final Shape shape, Image image, final TexCoordGenerator gen) { Texture t = TextureImpl.getLastBind(); image.getTexture().bind(); final float center[] = shape.getCenter(); fill( shape, new PointCallback() { public float[] preRenderPoint(Shape shape, float x, float y) { Vector2f tex = gen.getCoordFor(x, y); GL.glTexCoord2f(tex.x, tex.y); return new float[] {x, y}; } }); if (t == null) { TextureImpl.bindNone(); } else { t.bind(); } } /** * Description of some feature that will be applied to each point render * * @author kevin */ private static interface PointCallback { /** * Apply feature before the call to glVertex * * @param shape The shape the point belongs to * @param x The x poisiton the vertex will be at * @param y The y position the vertex will be at * @return The new coordinates of null */ float[] preRenderPoint(Shape shape, float x, float y); } }
/** * Load a texture into OpenGL from a BufferedImage * * @param resourceName The location of the resource to load * @param resourceimage The BufferedImage we are converting * @param target The GL target to load the texture against * @param dstPixelFormat The pixel format of the screen * @param minFilter The minimising filter * @param magFilter The magnification filter * @return The loaded texture * @throws IOException Indicates a failure to access the resource */ public static Texture getTexture( String resourceName, BufferedImage resourceimage, int target, int dstPixelFormat, int minFilter, int magFilter) throws IOException { ImageIOImageData data = new ImageIOImageData(); int srcPixelFormat = 0; // create the texture ID for this texture int textureID = InternalTextureLoader.createTextureID(); TextureImpl texture = new TextureImpl(resourceName, target, textureID); // Enable texturing Renderer.get().glEnable(SGL.GL_TEXTURE_2D); // bind this texture Renderer.get().glBindTexture(target, textureID); BufferedImage bufferedImage = resourceimage; texture.setWidth(bufferedImage.getWidth()); texture.setHeight(bufferedImage.getHeight()); if (bufferedImage.getColorModel().hasAlpha()) { srcPixelFormat = SGL.GL_RGBA; } else { srcPixelFormat = SGL.GL_RGB; } // convert that image into a byte buffer of texture data ByteBuffer textureBuffer = data.imageToByteBuffer(bufferedImage, false, false, null); texture.setTextureHeight(data.getTexHeight()); texture.setTextureWidth(data.getTexWidth()); texture.setAlpha(data.getDepth() == 32); if (target == SGL.GL_TEXTURE_2D) { Renderer.get().glTexParameteri(target, SGL.GL_TEXTURE_MIN_FILTER, minFilter); Renderer.get().glTexParameteri(target, SGL.GL_TEXTURE_MAG_FILTER, magFilter); if (Renderer.get().canTextureMirrorClamp()) { Renderer.get() .glTexParameteri( SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT); Renderer.get() .glTexParameteri( SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_MIRROR_CLAMP_TO_EDGE_EXT); } else { Renderer.get().glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_S, SGL.GL_CLAMP); Renderer.get().glTexParameteri(SGL.GL_TEXTURE_2D, SGL.GL_TEXTURE_WRAP_T, SGL.GL_CLAMP); } } Renderer.get() .glTexImage2D( target, 0, dstPixelFormat, texture.getTextureWidth(), texture.getTextureHeight(), 0, srcPixelFormat, SGL.GL_UNSIGNED_BYTE, textureBuffer); return texture; }
/** * A wrapper to allow any game to be scalable. This relies on knowing the normal width/height of the * game - i.e. the dimensions that the game is expecting to be run at. The wrapper then takes the * size of the container and scales rendering and input based on the ratio. * * <p>Note: Using OpenGL directly within a ScalableGame can break it * * @author kevin */ public class ScalableGame implements Game { /** The renderer to use for all GL operations */ private static SGL GL = Renderer.get(); /** The normal or native width of the game */ private float normalWidth; /** The normal or native height of the game */ private float normalHeight; /** The game that is being wrapped */ private Game held; /** True if we should maintain the aspect ratio */ private boolean maintainAspect; /** The target width */ private int targetWidth; /** The target height */ private int targetHeight; /** The game container wrapped */ private GameContainer container; /** * Create a new scalable game wrapper * * @param held The game to be wrapper and displayed at a different resolution * @param normalWidth The normal width of the game * @param normalHeight The noral height of the game */ public ScalableGame(Game held, int normalWidth, int normalHeight) { this(held, normalWidth, normalHeight, false); } /** * Create a new scalable game wrapper * * @param held The game to be wrapper and displayed at a different resolution * @param normalWidth The normal width of the game * @param normalHeight The noral height of the game * @param maintainAspect True if we should maintain the aspect ratio */ public ScalableGame(Game held, int normalWidth, int normalHeight, boolean maintainAspect) { this.held = held; this.normalWidth = normalWidth; this.normalHeight = normalHeight; this.maintainAspect = maintainAspect; } /** @see org.newdawn.slick.BasicGame#init(org.newdawn.slick.GameContainer) */ public void init(GameContainer container) throws SlickException { this.container = container; recalculateScale(); held.init(container); } /** * Returns the horizontal scale factor; e.g. target width divided by native width. * * @return the x-axis scaling */ public float getScaleX() { return targetWidth / normalWidth; } /** * Returns the vertical scale factor; e.g. target height divided by native height. * * @return the y-axis scaling */ public float getScaleY() { return targetHeight / normalHeight; } /** * Recalculate the scale of the game * * @throws SlickException Indicates a failure to reinit the game */ public void recalculateScale() throws SlickException { targetWidth = container.getWidth(); targetHeight = container.getHeight(); if (maintainAspect) { boolean normalIsWide = (normalWidth / normalHeight > 1.6 ? true : false); boolean containerIsWide = ((float) targetWidth / (float) targetHeight > 1.6 ? true : false); float wScale = targetWidth / normalWidth; float hScale = targetHeight / normalHeight; if (normalIsWide & containerIsWide) { float scale = (wScale < hScale ? wScale : hScale); targetWidth = (int) (normalWidth * scale); targetHeight = (int) (normalHeight * scale); } else if (normalIsWide & !containerIsWide) { targetWidth = (int) (normalWidth * wScale); targetHeight = (int) (normalHeight * wScale); } else if (!normalIsWide & containerIsWide) { targetWidth = (int) (normalWidth * hScale); targetHeight = (int) (normalHeight * hScale); } else { float scale = (wScale < hScale ? wScale : hScale); targetWidth = (int) (normalWidth * scale); targetHeight = (int) (normalHeight * scale); } } if (held instanceof InputListener) { container.getInput().addListener((InputListener) held); } container.getInput().setScale(normalWidth / targetWidth, normalHeight / targetHeight); int yoffset = 0; int xoffset = 0; if (targetHeight < container.getHeight()) { yoffset = (container.getHeight() - targetHeight) / 2; } if (targetWidth < container.getWidth()) { xoffset = (container.getWidth() - targetWidth) / 2; } container .getInput() .setOffset( -xoffset / (targetWidth / normalWidth), -yoffset / (targetHeight / normalHeight)); } /** @see org.newdawn.slick.BasicGame#update(org.newdawn.slick.GameContainer, int) */ public void update(GameContainer container, int delta) throws SlickException { if ((targetHeight != container.getHeight()) || (targetWidth != container.getWidth())) { recalculateScale(); } held.update(container, delta); } /** * @see org.newdawn.slick.Game#render(org.newdawn.slick.GameContainer, org.newdawn.slick.Graphics) */ public final void render(GameContainer container, Graphics g) throws SlickException { int yoffset = 0; int xoffset = 0; if (targetHeight < container.getHeight()) { yoffset = (container.getHeight() - targetHeight) / 2; } if (targetWidth < container.getWidth()) { xoffset = (container.getWidth() - targetWidth) / 2; } SlickCallable.enterSafeBlock(); g.setClip(xoffset, yoffset, targetWidth, targetHeight); GL.glTranslatef(xoffset, yoffset, 0); g.scale(targetWidth / normalWidth, targetHeight / normalHeight); GL.glPushMatrix(); held.render(container, g); GL.glPopMatrix(); g.clearClip(); SlickCallable.leaveSafeBlock(); renderOverlay(container, g); } /** * Render the overlay that will sit over the scaled screen * * @param container The container holding the game being render * @param g Graphics context on which to render */ protected void renderOverlay(GameContainer container, Graphics g) {} /** @see org.newdawn.slick.Game#closeRequested() */ public boolean closeRequested() { return held.closeRequested(); } /** @see org.newdawn.slick.Game#getTitle() */ public String getTitle() { return held.getTitle(); } }