added sources for Slick

Former-commit-id: 1647fa32ef6894bd7db44f741f07c2f4dcdf9054
Former-commit-id: 0e5810dcfbe1fd59b13e7cabe9f1e93c5542da2d
This commit is contained in:
Song Minjae
2016-12-30 23:29:12 +09:00
parent d24b31e15d
commit 059abff814
329 changed files with 58400 additions and 7 deletions

View File

@@ -0,0 +1,635 @@
package org.newdawn.slick;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Map.Entry;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* 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:
*
* <a
* href="http://www.angelcode.com/products/bmfont/">http://www.angelcode.com/products/bmfont/</a>
*
* 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 <misc@n4te.com>
*/
public class AngelCodeFont implements Font {
/** 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);
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, 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 {
/** 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 <misc@n4te.com>
*/
static private 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;
}
}

View File

@@ -0,0 +1,724 @@
package org.newdawn.slick;
import java.util.ArrayList;
import org.lwjgl.Sys;
import org.newdawn.slick.util.Log;
/**
* A utility to hold and render animations
*
* @author kevin
* @author DeX (speed updates)
*/
public class Animation implements Renderable {
/** The list of frames to render in this animation */
private ArrayList frames = new ArrayList();
/** The frame currently being displayed */
private int currentFrame = -1;
/** The time the next frame change should take place */
private long nextChange = 0;
/** True if the animation is stopped */
private boolean stopped = false;
/** The time left til the next frame */
private long timeLeft;
/** The current speed of the animation */
private float speed = 1.0f;
/** The frame to stop at */
private int stopAt = -2;
/** The last time the frame was automagically updated */
private long lastUpdate;
/** True if this is the first update */
private boolean firstUpdate = true;
/** True if we should auto update the animation - default true */
private boolean autoUpdate = true;
/** The direction the animation is running */
private int direction = 1;
/** True if the animation in ping ponging back and forth */
private boolean pingPong;
/** True if the animation should loop (default) */
private boolean loop = true;
/** The spriteSheet backing this animation */
private SpriteSheet spriteSheet = null;
/**
* Create an empty animation
*/
public Animation() {
this(true);
}
/**
* Create a new animation from a set of images
*
* @param frames The images for the animation frames
* @param duration The duration to show each frame
*/
public Animation(Image[] frames, int duration) {
this(frames, duration, true);
}
/**
* Create a new animation from a set of images
*
* @param frames The images for the animation frames
* @param durations The duration to show each frame
*/
public Animation(Image[] frames, int[] durations) {
this(frames, durations, true);
}
/**
* Create an empty animation
*
* @param autoUpdate True if this animation should automatically update. This means that the
* current frame will be caculated based on the time between renders
*/
public Animation(boolean autoUpdate) {
currentFrame = 0;
this.autoUpdate = autoUpdate;
}
/**
* Create a new animation from a set of images
*
* @param frames The images for the animation frames
* @param duration The duration to show each frame
* @param autoUpdate True if this animation should automatically update. This means that the
* current frame will be caculated based on the time between renders
*/
public Animation(Image[] frames, int duration, boolean autoUpdate) {
for (int i=0;i<frames.length;i++) {
addFrame(frames[i], duration);
}
currentFrame = 0;
this.autoUpdate = autoUpdate;
}
/**
* Create a new animation from a set of images
*
* @param frames The images for the animation frames
* @param durations The duration to show each frame
* @param autoUpdate True if this animation should automatically update. This means that the
* current frame will be caculated based on the time between renders
*/
public Animation(Image[] frames, int[] durations, boolean autoUpdate) {
this.autoUpdate = autoUpdate;
if (frames.length != durations.length) {
throw new RuntimeException("There must be one duration per frame");
}
for (int i=0;i<frames.length;i++) {
addFrame(frames[i], durations[i]);
}
currentFrame = 0;
}
/**
* Create a new animation based on the sprite from a sheet. It assumed that
* the sprites are organised on horizontal scan lines and that every sprite
* in the sheet should be used.
*
* @param frames The sprite sheet containing the frames
* @param duration The duration each frame should be displayed for
*/
public Animation(SpriteSheet frames, int duration) {
this(frames, 0,0,frames.getHorizontalCount()-1,frames.getVerticalCount()-1,true,duration,true);
}
/**
* Create a new animation based on a selection of sprites from a sheet
*
* @param frames The sprite sheet containing the frames
* @param x1 The x coordinate of the first sprite from the sheet to appear in the animation
* @param y1 The y coordinate of the first sprite from the sheet to appear in the animation
* @param x2 The x coordinate of the last sprite from the sheet to appear in the animation
* @param y2 The y coordinate of the last sprite from the sheet to appear in the animation
* @param horizontalScan True if the sprites are arranged in hoizontal scan lines. Otherwise
* vertical is assumed
* @param duration The duration each frame should be displayed for
* @param autoUpdate True if this animation should automatically update based on the render times
*/
public Animation(SpriteSheet frames, int x1, int y1, int x2, int y2, boolean horizontalScan, int duration, boolean autoUpdate) {
this.autoUpdate = autoUpdate;
if (!horizontalScan) {
for (int x=x1;x<=x2;x++) {
for (int y=y1;y<=y2;y++) {
addFrame(frames.getSprite(x, y), duration);
}
}
} else {
for (int y=y1;y<=y2;y++) {
for (int x=x1;x<=x2;x++) {
addFrame(frames.getSprite(x, y), duration);
}
}
}
}
/**
* Creates a new Animation where each frame is a sub-image of <tt>SpriteSheet</tt> ss.
* @param ss The <tt>SpriteSheet</tt> backing this animation
* @param frames An array of coordinates of sub-image locations for each frame
* @param duration The duration each frame should be displayed for
*/
public Animation(SpriteSheet ss, int[] frames, int[] duration){
spriteSheet = ss;
int x = -1;
int y = -1;
for(int i = 0; i < frames.length/2; i++){
x = frames[i*2];
y = frames[i*2 + 1];
addFrame(duration[i], x, y);
}
}
/**
* Add animation frame to the animation.
* @param duration The duration to display the frame for
* @param x The x location of the frame on the <tt>SpriteSheet</tt>
* @param y The y location of the frame on the <tt>spriteSheet</tt>
*/
public void addFrame(int duration, int x, int y){
if (duration == 0) {
Log.error("Invalid duration: "+duration);
throw new RuntimeException("Invalid duration: "+duration);
}
if (frames.isEmpty()) {
nextChange = (int) (duration / speed);
}
frames.add(new Frame(duration, x, y));
currentFrame = 0;
}
/**
* Indicate if this animation should automatically update based on the
* time between renders or if it should need updating via the update()
* method.
*
* @param auto True if this animation should automatically update
*/
public void setAutoUpdate(boolean auto) {
this.autoUpdate = auto;
}
/**
* Indicate if this animation should ping pong back and forth
*
* @param pingPong True if the animation should ping pong
*/
public void setPingPong(boolean pingPong) {
this.pingPong = pingPong;
}
/**
* Check if this animation has stopped (either explictly or because it's reached its target frame)
*
* @see #stopAt
* @return True if the animation has stopped
*/
public boolean isStopped() {
return stopped;
}
/**
* Adjust the overall speed of the animation.
*
* @param spd The speed to run the animation. Default: 1.0
*/
public void setSpeed(float spd) {
if (spd > 0) {
// Adjust nextChange
nextChange = (long) (nextChange * speed / spd);
speed = spd;
}
}
/**
* Returns the current speed of the animation.
*
* @return The speed this animation is being played back at
*/
public float getSpeed() {
return speed;
}
/**
* Stop the animation
*/
public void stop() {
if (frames.size() == 0) {
return;
}
timeLeft = nextChange;
stopped = true;
}
/**
* Start the animation playing again
*/
public void start() {
if (!stopped) {
return;
}
if (frames.size() == 0) {
return;
}
stopped = false;
nextChange = timeLeft;
}
/**
* Restart the animation from the beginning
*/
public void restart() {
if (frames.size() == 0) {
return;
}
stopped = false;
currentFrame = 0;
nextChange = (int) (((Frame) frames.get(0)).duration / speed);
firstUpdate = true;
lastUpdate = 0;
}
/**
* Add animation frame to the animation
*
* @param frame The image to display for the frame
* @param duration The duration to display the frame for
*/
public void addFrame(Image frame, int duration) {
if (duration == 0) {
Log.error("Invalid duration: "+duration);
throw new RuntimeException("Invalid duration: "+duration);
}
if (frames.isEmpty()) {
nextChange = (int) (duration / speed);
}
frames.add(new Frame(frame, duration));
currentFrame = 0;
}
/**
* Draw the animation to the screen
*/
public void draw() {
draw(0,0);
}
/**
* Draw the animation at a specific location
*
* @param x The x position to draw the animation at
* @param y The y position to draw the animation at
*/
public void draw(float x,float y) {
draw(x,y,getWidth(),getHeight());
}
/**
* Draw the animation at a specific location
*
* @param x The x position to draw the animation at
* @param y The y position to draw the animation at
* @param filter The filter to apply
*/
public void draw(float x,float y, Color filter) {
draw(x,y,getWidth(),getHeight(), filter);
}
/**
* Draw the animation
*
* @param x The x position to draw the animation at
* @param y The y position to draw the animation at
* @param width The width to draw the animation at
* @param height The height to draw the animation at
*/
public void draw(float x,float y,float width,float height) {
draw(x,y,width,height,Color.white);
}
/**
* Draw the animation
*
* @param x The x position to draw the animation at
* @param y The y position to draw the animation at
* @param width The width to draw the animation at
* @param height The height to draw the animation at
* @param col The colour filter to use
*/
public void draw(float x,float y,float width,float height, Color col) {
if (frames.size() == 0) {
return;
}
if (autoUpdate) {
long now = getTime();
long delta = now - lastUpdate;
if (firstUpdate) {
delta = 0;
firstUpdate = false;
}
lastUpdate = now;
nextFrame(delta);
}
Frame frame = (Frame) frames.get(currentFrame);
frame.image.draw(x,y,width,height, col);
}
/**
* Render the appropriate frame when the spriteSheet backing this Animation is in use.
* @param x The x position to draw the animation at
* @param y The y position to draw the animation at
*/
public void renderInUse(int x, int y){
if (frames.size() == 0) {
return;
}
if (autoUpdate) {
long now = getTime();
long delta = now - lastUpdate;
if (firstUpdate) {
delta = 0;
firstUpdate = false;
}
lastUpdate = now;
nextFrame(delta);
}
Frame frame = (Frame) frames.get(currentFrame);
spriteSheet.renderInUse(x, y, frame.x, frame.y);
}
/**
* Get the width of the current frame
*
* @return The width of the current frame
*/
public int getWidth() {
return ((Frame) frames.get(currentFrame)).image.getWidth();
}
/**
* Get the height of the current frame
*
* @return The height of the current frame
*/
public int getHeight() {
return ((Frame) frames.get(currentFrame)).image.getHeight();
}
/**
* Draw the animation
*
* @param x The x position to draw the animation at
* @param y The y position to draw the animation at
* @param width The width to draw the animation at
* @param height The height to draw the animation at
*/
public void drawFlash(float x,float y,float width,float height) {
drawFlash(x,y,width,height, Color.white);
}
/**
* Draw the animation
*
* @param x The x position to draw the animation at
* @param y The y position to draw the animation at
* @param width The width to draw the animation at
* @param height The height to draw the animation at
* @param col The colour for the flash
*/
public void drawFlash(float x,float y,float width,float height, Color col) {
if (frames.size() == 0) {
return;
}
if (autoUpdate) {
long now = getTime();
long delta = now - lastUpdate;
if (firstUpdate) {
delta = 0;
firstUpdate = false;
}
lastUpdate = now;
nextFrame(delta);
}
Frame frame = (Frame) frames.get(currentFrame);
frame.image.drawFlash(x,y,width,height,col);
}
/**
* Update the animation cycle without draw the image, useful
* for keeping two animations in sync
*
* @deprecated
*/
public void updateNoDraw() {
if (autoUpdate) {
long now = getTime();
long delta = now - lastUpdate;
if (firstUpdate) {
delta = 0;
firstUpdate = false;
}
lastUpdate = now;
nextFrame(delta);
}
}
/**
* Update the animation, note that this will have odd effects if auto update
* is also turned on
*
* @see #autoUpdate
* @param delta The amount of time thats passed since last update
*/
public void update(long delta) {
nextFrame(delta);
}
/**
* Get the index of the current frame
*
* @return The index of the current frame
*/
public int getFrame() {
return currentFrame;
}
/**
* Set the current frame to be rendered
*
* @param index The index of the frame to rendered
*/
public void setCurrentFrame(int index) {
currentFrame = index;
}
/**
* Get the image assocaited with a given frame index
*
* @param index The index of the frame image to retrieve
* @return The image of the specified animation frame
*/
public Image getImage(int index) {
Frame frame = (Frame) frames.get(index);
return frame.image;
}
/**
* Get the number of frames that are in the animation
*
* @return The number of frames that are in the animation
*/
public int getFrameCount() {
return frames.size();
}
/**
* Get the image associated with the current animation frame
*
* @return The image associated with the current animation frame
*/
public Image getCurrentFrame() {
Frame frame = (Frame) frames.get(currentFrame);
return frame.image;
}
/**
* Check if we need to move to the next frame
*
* @param delta The amount of time thats passed since last update
*/
private void nextFrame(long delta) {
if (stopped) {
return;
}
if (frames.size() == 0) {
return;
}
nextChange -= delta;
while (nextChange < 0 && (!stopped)) {
if (currentFrame == stopAt) {
stopped = true;
break;
}
if ((currentFrame == frames.size() - 1) && (!loop) && (!pingPong)) {
stopped = true;
break;
}
currentFrame = (currentFrame + direction) % frames.size();
if (pingPong) {
if (currentFrame <= 0) {
currentFrame = 0;
direction = 1;
if (!loop) {
stopped = true;
break;
}
}
else if (currentFrame >= frames.size()-1) {
currentFrame = frames.size()-1;
direction = -1;
}
}
int realDuration = (int) (((Frame) frames.get(currentFrame)).duration / speed);
nextChange = nextChange + realDuration;
}
}
/**
* Indicate if this animation should loop or stop at the last frame
*
* @param loop True if this animation should loop (true = default)
*/
public void setLooping(boolean loop) {
this.loop = loop;
}
/**
* Get the accurate system time
*
* @return The system time in milliseconds
*/
private long getTime() {
return (Sys.getTime() * 1000) / Sys.getTimerResolution();
}
/**
* Indicate the animation should stop when it reaches the specified
* frame index (note, not frame number but index in the animation
*
* @param frameIndex The index of the frame to stop at
*/
public void stopAt(int frameIndex) {
stopAt = frameIndex;
}
/**
* Get the duration of a particular frame
*
* @param index The index of the given frame
* @return The duration in (ms) of the given frame
*/
public int getDuration(int index) {
return ((Frame) frames.get(index)).duration;
}
/**
* Set the duration of the given frame
*
* @param index The index of the given frame
* @param duration The duration in (ms) for the given frame
*/
public void setDuration(int index, int duration) {
((Frame) frames.get(index)).duration = duration;
}
/**
* Get the durations of all the frames in this animation
*
* @return The durations of all the frames in this animation
*/
public int[] getDurations() {
int[] durations = new int[frames.size()];
for (int i=0;i<frames.size();i++) {
durations[i] = getDuration(i);
}
return durations;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
String res = "[Animation ("+frames.size()+") ";
for (int i=0;i<frames.size();i++) {
Frame frame = (Frame) frames.get(i);
res += frame.duration+",";
}
res += "]";
return res;
}
/**
* Create a copy of this animation. Note that the frames
* are not duplicated but shared with the original
*
* @return A copy of this animation
*/
public Animation copy() {
Animation copy = new Animation();
copy.spriteSheet = spriteSheet;
copy.frames = frames;
copy.autoUpdate = autoUpdate;
copy.direction = direction;
copy.loop = loop;
copy.pingPong = pingPong;
copy.speed = speed;
return copy;
}
/**
* A single frame within the animation
*
* @author kevin
*/
private class Frame {
/** The image to display for this frame */
public Image image;
/** The duration to display the image fro */
public int duration;
/** The x location of this frame on a SpriteSheet*/
public int x = -1;
/** The y location of this frame on a SpriteSheet*/
public int y = -1;
/**
* Create a new animation frame
*
* @param image The image to display for the frame
* @param duration The duration in millisecond to display the image for
*/
public Frame(Image image, int duration) {
this.image = image;
this.duration = duration;
}
/**
* Creates a new animation frame with the frames image location on a sprite sheet
* @param duration The duration in millisecond to display the image for
* @param x the x location of the frame on the <tt>SpriteSheet</tt>
* @param y the y location of the frame on the <tt>SpriteSheet</tt>
*/
public Frame(int duration, int x, int y) {
this.image = spriteSheet.getSubImage(x, y);
this.duration = duration;
this.x = x;
this.y = y;
}
}
}

View File

@@ -0,0 +1,547 @@
package org.newdawn.slick;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Cursor;
import org.lwjgl.input.Mouse;
import org.lwjgl.openal.AL;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.PixelFormat;
import org.newdawn.slick.openal.SoundStore;
import org.newdawn.slick.opengl.CursorLoader;
import org.newdawn.slick.opengl.ImageData;
import org.newdawn.slick.opengl.ImageIOImageData;
import org.newdawn.slick.opengl.InternalTextureLoader;
import org.newdawn.slick.opengl.LoadableImageData;
import org.newdawn.slick.opengl.TGAImageData;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* A game container that will display the game as an stand alone
* application.
*
* @author kevin
*/
public class AppGameContainer extends GameContainer {
static {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
Display.getDisplayMode();
} catch (Exception e) {
Log.error(e);
}
return null;
}});
}
/** The original display mode before we tampered with things */
protected DisplayMode originalDisplayMode;
/** The display mode we're going to try and use */
protected DisplayMode targetDisplayMode;
/** True if we should update the game only when the display is visible */
protected boolean updateOnlyOnVisible = true;
/** Alpha background supported */
protected boolean alphaSupport = false;
/**
* Create a new container wrapping a game
*
* @param game The game to be wrapped
* @throws SlickException Indicates a failure to initialise the display
*/
public AppGameContainer(Game game) throws SlickException {
this(game,640,480,false);
}
/**
* Create a new container wrapping a game
*
* @param game The game to be wrapped
* @param width The width of the display required
* @param height The height of the display required
* @param fullscreen True if we want fullscreen mode
* @throws SlickException Indicates a failure to initialise the display
*/
public AppGameContainer(Game game,int width,int height,boolean fullscreen) throws SlickException {
super(game);
originalDisplayMode = Display.getDisplayMode();
setDisplayMode(width,height,fullscreen);
}
/**
* Check if the display created supported alpha in the back buffer
*
* @return True if the back buffer supported alpha
*/
public boolean supportsAlphaInBackBuffer() {
return alphaSupport;
}
/**
* Set the title of the window
*
* @param title The title to set on the window
*/
public void setTitle(String title) {
Display.setTitle(title);
}
/**
* Set the display mode to be used
*
* @param width The width of the display required
* @param height The height of the display required
* @param fullscreen True if we want fullscreen mode
* @throws SlickException Indicates a failure to initialise the display
*/
public void setDisplayMode(int width, int height, boolean fullscreen) throws SlickException {
if ((this.width == width) && (this.height == height) && (isFullscreen() == fullscreen)) {
return;
}
try {
targetDisplayMode = null;
if (fullscreen) {
DisplayMode[] modes = Display.getAvailableDisplayModes();
int freq = 0;
for (int i=0;i<modes.length;i++) {
DisplayMode current = modes[i];
if ((current.getWidth() == width) && (current.getHeight() == height)) {
if ((targetDisplayMode == null) || (current.getFrequency() >= freq)) {
if ((targetDisplayMode == null) || (current.getBitsPerPixel() > targetDisplayMode.getBitsPerPixel())) {
targetDisplayMode = current;
freq = targetDisplayMode.getFrequency();
}
}
// if we've found a match for bpp and frequence against the
// original display mode then it's probably best to go for this one
// since it's most likely compatible with the monitor
if ((current.getBitsPerPixel() == originalDisplayMode.getBitsPerPixel()) &&
(current.getFrequency() == originalDisplayMode.getFrequency())) {
targetDisplayMode = current;
break;
}
}
}
} else {
targetDisplayMode = new DisplayMode(width,height);
}
if (targetDisplayMode == null) {
throw new SlickException("Failed to find value mode: "+width+"x"+height+" fs="+fullscreen);
}
this.width = width;
this.height = height;
Display.setDisplayMode(targetDisplayMode);
Display.setFullscreen(fullscreen);
if (Display.isCreated()) {
initGL();
enterOrtho();
}
if (targetDisplayMode.getBitsPerPixel() == 16) {
InternalTextureLoader.get().set16BitMode();
}
} catch (LWJGLException e) {
throw new SlickException("Unable to setup mode "+width+"x"+height+" fullscreen="+fullscreen, e);
}
getDelta();
}
/**
* Check if the display is in fullscreen mode
*
* @return True if the display is in fullscreen mode
*/
public boolean isFullscreen() {
return Display.isFullscreen();
}
/**
* 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 {
if (isFullscreen() == fullscreen) {
return;
}
if (!fullscreen) {
try {
Display.setFullscreen(fullscreen);
} catch (LWJGLException e) {
throw new SlickException("Unable to set fullscreen="+fullscreen, e);
}
} else {
setDisplayMode(width, height, fullscreen);
}
getDelta();
}
/**
* @see org.newdawn.slick.GameContainer#setMouseCursor(java.lang.String, int, int)
*/
public void setMouseCursor(String ref, int hotSpotX, int hotSpotY) throws SlickException {
try {
Cursor cursor = CursorLoader.get().getCursor(ref, hotSpotX, hotSpotY);
Mouse.setNativeCursor(cursor);
} catch (Throwable e) {
Log.error("Failed to load and apply cursor.", e);
throw new SlickException("Failed to set mouse cursor", e);
}
}
/**
* @see org.newdawn.slick.GameContainer#setMouseCursor(org.newdawn.slick.opengl.ImageData, int, int)
*/
public void setMouseCursor(ImageData data, int hotSpotX, int hotSpotY) throws SlickException {
try {
Cursor cursor = CursorLoader.get().getCursor(data, hotSpotX, hotSpotY);
Mouse.setNativeCursor(cursor);
} catch (Throwable e) {
Log.error("Failed to load and apply cursor.", e);
throw new SlickException("Failed to set mouse cursor", e);
}
}
/**
* @see org.newdawn.slick.GameContainer#setMouseCursor(org.lwjgl.input.Cursor, int, int)
*/
public void setMouseCursor(Cursor cursor, int hotSpotX, int hotSpotY) throws SlickException {
try {
Mouse.setNativeCursor(cursor);
} catch (Throwable e) {
Log.error("Failed to load and apply cursor.", e);
throw new SlickException("Failed to set mouse cursor", e);
}
}
/**
* Get the closest greater power of 2 to the fold number
*
* @param fold The target number
* @return The power of 2
*/
private int get2Fold(int fold) {
int ret = 2;
while (ret < fold) {
ret *= 2;
}
return ret;
}
/**
* @see org.newdawn.slick.GameContainer#setMouseCursor(org.newdawn.slick.Image, int, int)
*/
public void setMouseCursor(Image image, int hotSpotX, int hotSpotY) throws SlickException {
try {
Image temp = new Image(get2Fold(image.getWidth()), get2Fold(image.getHeight()));
Graphics g = temp.getGraphics();
ByteBuffer buffer = BufferUtils.createByteBuffer(temp.getWidth() * temp.getHeight() * 4);
g.drawImage(image.getFlippedCopy(false, true), 0, 0);
g.flush();
g.getArea(0,0,temp.getWidth(),temp.getHeight(),buffer);
Cursor cursor = CursorLoader.get().getCursor(buffer, hotSpotX, hotSpotY,temp.getWidth(),image.getHeight());
Mouse.setNativeCursor(cursor);
} catch (Throwable e) {
Log.error("Failed to load and apply cursor.", e);
throw new SlickException("Failed to set mouse cursor", e);
}
}
/**
* @see org.newdawn.slick.GameContainer#reinit()
*/
public void reinit() throws SlickException {
InternalTextureLoader.get().clear();
SoundStore.get().clear();
initSystem();
enterOrtho();
try {
game.init(this);
} catch (SlickException e) {
Log.error(e);
running = false;
}
}
/**
* Try creating a display with the given format
*
* @param format The format to attempt
* @throws LWJGLException Indicates a failure to support the given format
*/
private void tryCreateDisplay(PixelFormat format) throws LWJGLException {
if (SHARED_DRAWABLE == null)
{
Display.create(format);
}
else
{
Display.create(format, SHARED_DRAWABLE);
}
}
/**
* Start running the game
*
* @throws SlickException Indicates a failure to initialise the system
*/
public void start() throws SlickException {
try {
setup();
getDelta();
while (running()) {
gameLoop();
}
} finally {
destroy();
}
if (forceExit) {
System.exit(0);
}
}
/**
* Setup the environment
*
* @throws SlickException Indicates a failure
*/
protected void setup() throws SlickException {
if (targetDisplayMode == null) {
setDisplayMode(640,480,false);
}
Display.setTitle(game.getTitle());
Log.info("LWJGL Version: "+Sys.getVersion());
Log.info("OriginalDisplayMode: "+originalDisplayMode);
Log.info("TargetDisplayMode: "+targetDisplayMode);
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
PixelFormat format = new PixelFormat(8,8,stencil ? 8 : 0,samples);
tryCreateDisplay(format);
supportsMultiSample = true;
} catch (Exception e) {
Display.destroy();
try {
PixelFormat format = new PixelFormat(8,8,stencil ? 8 : 0);
tryCreateDisplay(format);
alphaSupport = false;
} catch (Exception e2) {
Display.destroy();
// if we couldn't get alpha, let us know
try {
tryCreateDisplay(new PixelFormat());
} catch (Exception e3) {
Log.error(e3);
}
}
}
return null;
}});
if (!Display.isCreated()) {
throw new SlickException("Failed to initialise the LWJGL display");
}
initSystem();
enterOrtho();
try {
getInput().initControllers();
} catch (SlickException e) {
Log.info("Controllers not available");
} catch (Throwable e) {
Log.info("Controllers not available");
}
try {
game.init(this);
} catch (SlickException e) {
Log.error(e);
running = false;
}
}
/**
* Strategy for overloading game loop context handling
*
* @throws SlickException Indicates a game failure
*/
protected void gameLoop() throws SlickException {
int delta = getDelta();
if (!Display.isVisible() && updateOnlyOnVisible) {
try { Thread.sleep(100); } catch (Exception e) {}
} else {
try {
updateAndRender(delta);
} catch (SlickException e) {
Log.error(e);
running = false;
return;
}
}
updateFPS();
Display.update();
if (Display.isCloseRequested()) {
if (game.closeRequested()) {
running = false;
}
}
}
/**
* @see org.newdawn.slick.GameContainer#setUpdateOnlyWhenVisible(boolean)
*/
public void setUpdateOnlyWhenVisible(boolean updateOnlyWhenVisible) {
updateOnlyOnVisible = updateOnlyWhenVisible;
}
/**
* @see org.newdawn.slick.GameContainer#isUpdatingOnlyWhenVisible()
*/
public boolean isUpdatingOnlyWhenVisible() {
return updateOnlyOnVisible;
}
/**
* @see org.newdawn.slick.GameContainer#setIcon(java.lang.String)
*/
public void setIcon(String ref) throws SlickException {
setIcons(new String[] {ref});
}
/**
* @see org.newdawn.slick.GameContainer#setMouseGrabbed(boolean)
*/
public void setMouseGrabbed(boolean grabbed) {
Mouse.setGrabbed(grabbed);
}
/**
* @see org.newdawn.slick.GameContainer#isMouseGrabbed()
*/
public boolean isMouseGrabbed() {
return Mouse.isGrabbed();
}
/**
* @see org.newdawn.slick.GameContainer#hasFocus()
*/
public boolean hasFocus() {
// hmm, not really the right thing, talk to the LWJGL guys
return Display.isActive();
}
/**
* @see org.newdawn.slick.GameContainer#getScreenHeight()
*/
public int getScreenHeight() {
return originalDisplayMode.getHeight();
}
/**
* @see org.newdawn.slick.GameContainer#getScreenWidth()
*/
public int getScreenWidth() {
return originalDisplayMode.getWidth();
}
/**
* Destroy the app game container
*/
public void destroy() {
Display.destroy();
AL.destroy();
}
/**
* A null stream to clear out those horrid errors
*
* @author kevin
*/
private class NullOutputStream extends OutputStream {
/**
* @see java.io.OutputStream#write(int)
*/
public void write(int b) throws IOException {
// null implemetnation
}
}
/**
* @see org.newdawn.slick.GameContainer#setIcons(java.lang.String[])
*/
public void setIcons(String[] refs) throws SlickException {
ByteBuffer[] bufs = new ByteBuffer[refs.length];
for (int i=0;i<refs.length;i++) {
LoadableImageData data;
boolean flip = true;
if (refs[i].endsWith(".tga")) {
data = new TGAImageData();
} else {
flip = false;
data = new ImageIOImageData();
}
try {
bufs[i] = data.loadImage(ResourceLoader.getResourceAsStream(refs[i]), flip, false, null);
} catch (Exception e) {
Log.error(e);
throw new SlickException("Failed to set the icon");
}
}
Display.setIcon(bufs);
}
/**
* @see org.newdawn.slick.GameContainer#setDefaultMouseCursor()
*/
public void setDefaultMouseCursor() {
try {
Mouse.setNativeCursor(null);
} catch (LWJGLException e) {
Log.error("Failed to reset mouse cursor", e);
}
}
}

View File

@@ -0,0 +1,576 @@
package org.newdawn.slick;
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.Panel;
import java.awt.TextArea;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Cursor;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.PixelFormat;
import org.newdawn.slick.openal.SoundStore;
import org.newdawn.slick.opengl.CursorLoader;
import org.newdawn.slick.opengl.ImageData;
import org.newdawn.slick.opengl.InternalTextureLoader;
import org.newdawn.slick.util.Log;
/**
* A game container that displays the game as an applet. Note however that the
* actual game container implementation is an internal class which can be
* obtained with the getContainer() method - this is due to the Applet being a
* class wrap than an interface.
*
* @author kevin
*/
public class AppletGameContainer extends Applet {
/** The GL Canvas used for this container */
protected ContainerPanel canvas;
/** The actual container implementation */
protected Container container;
/** The parent of the display */
protected Canvas displayParent;
/** The thread that is looping for the game */
protected Thread gameThread;
/** Alpha background supported */
protected boolean alphaSupport = true;
/**
* @see java.applet.Applet#destroy()
*/
public void destroy() {
if (displayParent != null) {
remove(displayParent);
}
super.destroy();
Log.info("Clear up");
}
/**
* Clean up the LWJGL resources
*/
private void destroyLWJGL() {
container.stopApplet();
try {
gameThread.join();
} catch (InterruptedException e) {
Log.error(e);
}
}
/**
* @see java.applet.Applet#start()
*/
public void start() {
}
/**
* Start a thread to run LWJGL in
*/
public void startLWJGL() {
if (gameThread != null) {
return;
}
gameThread = new Thread() {
public void run() {
try {
canvas.start();
}
catch (Exception e) {
e.printStackTrace();
if (Display.isCreated()) {
Display.destroy();
}
displayParent.setVisible(false);//removeAll();
add(new ConsolePanel(e));
validate();
}
}
};
gameThread.start();
}
/**
* @see java.applet.Applet#stop()
*/
public void stop() {
}
/**
* @see java.applet.Applet#init()
*/
public void init() {
removeAll();
setLayout(new BorderLayout());
setIgnoreRepaint(true);
try {
Game game = (Game) Class.forName(getParameter("game")).newInstance();
container = new Container(game);
canvas = new ContainerPanel(container);
displayParent = new Canvas() {
public final void addNotify() {
super.addNotify();
startLWJGL();
}
public final void removeNotify() {
destroyLWJGL();
super.removeNotify();
}
};
displayParent.setSize(getWidth(), getHeight());
add(displayParent);
displayParent.setFocusable(true);
displayParent.requestFocus();
displayParent.setIgnoreRepaint(true);
setVisible(true);
} catch (Exception e) {
Log.error(e);
throw new RuntimeException("Unable to create game container");
}
}
/**
* Get the GameContainer providing this applet
*
* @return The game container providing this applet
*/
public GameContainer getContainer() {
return container;
}
/**
* Create a new panel to display the GL context
*
* @author kevin
*/
public class ContainerPanel {
/** The container being displayed on this canvas */
private Container container;
/**
* Create a new panel
*
* @param container The container we're running
*/
public ContainerPanel(Container container) {
this.container = container;
}
/**
* Create the LWJGL display
*
* @throws Exception Failure to create display
*/
private void createDisplay() throws Exception {
try {
// create display with alpha
Display.create(new PixelFormat(8,8,GameContainer.stencil ? 8 : 0));
alphaSupport = true;
} catch (Exception e) {
// if we couldn't get alpha, let us know
alphaSupport = false;
Display.destroy();
// create display without alpha
Display.create();
}
}
/**
* Start the game container
*
* @throws Exception Failure to create display
*/
public void start() throws Exception {
Display.setParent(displayParent);
Display.setVSyncEnabled(true);
try {
createDisplay();
} catch (LWJGLException e) {
e.printStackTrace();
// failed to create Display, apply workaround (sleep for 1 second) and try again
Thread.sleep(1000);
createDisplay();
}
initGL();
displayParent.requestFocus();
container.runloop();
}
/**
* Initialise GL state
*/
protected void initGL() {
try {
InternalTextureLoader.get().clear();
SoundStore.get().clear();
container.initApplet();
} catch (Exception e) {
Log.error(e);
container.stopApplet();
}
}
}
/**
* A game container to provide the applet context
*
* @author kevin
*/
public class Container extends GameContainer {
/**
* Create a new container wrapped round the game
*
* @param game The game to be held in this container
*/
public Container(Game game) {
super(game);
width = AppletGameContainer.this.getWidth();
height = AppletGameContainer.this.getHeight();
}
/**
* Initiliase based on Applet init
*
* @throws SlickException Indicates a failure to inialise the basic framework
*/
public void initApplet() throws SlickException {
initSystem();
enterOrtho();
try {
getInput().initControllers();
} catch (SlickException e) {
Log.info("Controllers not available");
} catch (Throwable e) {
Log.info("Controllers not available");
}
game.init(this);
getDelta();
}
/**
* Check if the applet is currently running
*
* @return True if the applet is running
*/
public boolean isRunning() {
return running;
}
/**
* Stop the applet play back
*/
public void stopApplet() {
running = false;
}
/**
* @see org.newdawn.slick.GameContainer#getScreenHeight()
*/
public int getScreenHeight() {
return 0;
}
/**
* @see org.newdawn.slick.GameContainer#getScreenWidth()
*/
public int getScreenWidth() {
return 0;
}
/**
* Check if the display created supported alpha in the back buffer
*
* @return True if the back buffer supported alpha
*/
public boolean supportsAlphaInBackBuffer() {
return alphaSupport;
}
/**
* @see org.newdawn.slick.GameContainer#hasFocus()
*/
public boolean hasFocus() {
return true;
}
/**
* Returns the Applet Object
* @return Applet Object
*/
public Applet getApplet() {
return AppletGameContainer.this;
}
/**
* @see org.newdawn.slick.GameContainer#setIcon(java.lang.String)
*/
public void setIcon(String ref) throws SlickException {
// unsupported in an applet
}
/**
* @see org.newdawn.slick.GameContainer#setMouseGrabbed(boolean)
*/
public void setMouseGrabbed(boolean grabbed) {
Mouse.setGrabbed(grabbed);
}
/**
* @see org.newdawn.slick.GameContainer#isMouseGrabbed()
*/
public boolean isMouseGrabbed() {
return Mouse.isGrabbed();
}
/**
* @see org.newdawn.slick.GameContainer#setMouseCursor(java.lang.String,
* int, int)
*/
public void setMouseCursor(String ref, int hotSpotX, int hotSpotY) throws SlickException {
try {
Cursor cursor = CursorLoader.get().getCursor(ref, hotSpotX, hotSpotY);
Mouse.setNativeCursor(cursor);
} catch (Throwable e) {
Log.error("Failed to load and apply cursor.", e);
throw new SlickException("Failed to set mouse cursor", e);
}
}
/**
* Get the closest greater power of 2 to the fold number
*
* @param fold The target number
* @return The power of 2
*/
private int get2Fold(int fold) {
int ret = 2;
while (ret < fold) {
ret *= 2;
}
return ret;
}
/**
* {@inheritDoc}
*/
public void setMouseCursor(Image image, int hotSpotX, int hotSpotY) throws SlickException {
try {
Image temp = new Image(get2Fold(image.getWidth()), get2Fold(image.getHeight()));
Graphics g = temp.getGraphics();
ByteBuffer buffer = BufferUtils.createByteBuffer(temp.getWidth() * temp.getHeight() * 4);
g.drawImage(image.getFlippedCopy(false, true), 0, 0);
g.flush();
g.getArea(0,0,temp.getWidth(),temp.getHeight(),buffer);
Cursor cursor = CursorLoader.get().getCursor(buffer, hotSpotX, hotSpotY,temp.getWidth(),temp.getHeight());
Mouse.setNativeCursor(cursor);
} catch (Throwable e) {
Log.error("Failed to load and apply cursor.", e);
throw new SlickException("Failed to set mouse cursor", e);
}
}
/**
* @see org.newdawn.slick.GameContainer#setIcons(java.lang.String[])
*/
public void setIcons(String[] refs) throws SlickException {
// unsupported in an applet
}
/**
* @see org.newdawn.slick.GameContainer#setMouseCursor(org.newdawn.slick.opengl.ImageData, int, int)
*/
public void setMouseCursor(ImageData data, int hotSpotX, int hotSpotY) throws SlickException {
try {
Cursor cursor = CursorLoader.get().getCursor(data, hotSpotX, hotSpotY);
Mouse.setNativeCursor(cursor);
} catch (Throwable e) {
Log.error("Failed to load and apply cursor.", e);
throw new SlickException("Failed to set mouse cursor", e);
}
}
/**
* @see org.newdawn.slick.GameContainer#setMouseCursor(org.lwjgl.input.Cursor, int, int)
*/
public void setMouseCursor(Cursor cursor, int hotSpotX, int hotSpotY) throws SlickException {
try {
Mouse.setNativeCursor(cursor);
} catch (Throwable e) {
Log.error("Failed to load and apply cursor.", e);
throw new SlickException("Failed to set mouse cursor", e);
}
}
/**
* @see org.newdawn.slick.GameContainer#setDefaultMouseCursor()
*/
public void setDefaultMouseCursor() {
}
public boolean isFullscreen() {
return Display.isFullscreen();
}
public void setFullscreen(boolean fullscreen) throws SlickException {
if (fullscreen == isFullscreen()) {
return;
}
try {
if (fullscreen) {
// get current screen resolution
int screenWidth = Display.getDisplayMode().getWidth();
int screenHeight = Display.getDisplayMode().getHeight();
// calculate aspect ratio
float gameAspectRatio = (float) width / height;
float screenAspectRatio = (float) screenWidth
/ screenHeight;
int newWidth;
int newHeight;
// get new screen resolution to match aspect ratio
if (gameAspectRatio >= screenAspectRatio) {
newWidth = screenWidth;
newHeight = (int) (height / ((float) width / screenWidth));
} else {
newWidth = (int) (width / ((float) height / screenHeight));
newHeight = screenHeight;
}
// center new screen
int xoffset = (screenWidth - newWidth) / 2;
int yoffset = (screenHeight - newHeight) / 2;
// scale game to match new resolution
GL11.glViewport(xoffset, yoffset, newWidth, newHeight);
enterOrtho();
// fix input to match new resolution
this.getInput().setOffset(
-xoffset * (float) width / newWidth,
-yoffset * (float) height / newHeight);
this.getInput().setScale((float) width / newWidth,
(float) height / newHeight);
width = screenWidth;
height = screenHeight;
Display.setFullscreen(true);
} else {
// restore input
this.getInput().setOffset(0, 0);
this.getInput().setScale(1, 1);
width = AppletGameContainer.this.getWidth();
height = AppletGameContainer.this.getHeight();
GL11.glViewport(0, 0, width, height);
enterOrtho();
Display.setFullscreen(false);
}
} catch (LWJGLException e) {
Log.error(e);
}
}
/**
* The running game loop
*
* @throws Exception Indicates a failure within the game's loop rather than the framework
*/
public void runloop() throws Exception {
while (running) {
int delta = getDelta();
updateAndRender(delta);
updateFPS();
Display.update();
}
Display.destroy();
}
}
/**
* A basic console to display an error message if the applet crashes.
* This will prevent the applet from just freezing in the browser
* and give the end user an a nice gui where the error message can easily
* be viewed and copied.
*/
public class ConsolePanel extends Panel {
/** The area display the console output */
TextArea textArea = new TextArea();
/**
* Create a new panel to display the console output
*
* @param e The exception causing the console to be displayed
*/
public ConsolePanel(Exception e) {
setLayout(new BorderLayout());
setBackground(Color.black);
setForeground(Color.white);
Font consoleFont = new Font("Arial", Font.BOLD, 14);
Label slickLabel = new Label("SLICK CONSOLE", Label.CENTER);
slickLabel.setFont(consoleFont);
add(slickLabel, BorderLayout.PAGE_START);
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
textArea.setText(sw.toString());
textArea.setEditable(false);
add(textArea, BorderLayout.CENTER);
// add a border on both sides of the console
add(new Panel(), BorderLayout.LINE_START);
add(new Panel(), BorderLayout.LINE_END);
Panel bottomPanel = new Panel();
bottomPanel.setLayout(new GridLayout(0, 1));
Label infoLabel1 = new Label("An error occured while running the applet.", Label.CENTER);
Label infoLabel2 = new Label("Plese contact support to resolve this issue.", Label.CENTER);
infoLabel1.setFont(consoleFont);
infoLabel2.setFont(consoleFont);
bottomPanel.add(infoLabel1);
bottomPanel.add(infoLabel2);
add(bottomPanel, BorderLayout.PAGE_END);
}
}
}

View File

@@ -0,0 +1,205 @@
package org.newdawn.slick;
/**
* A basic implementation of a game to take out the boring bits
*
* @author kevin
*/
public abstract class BasicGame implements Game, InputListener {
/** The maximum number of controllers supported by the basic game */
private static final int MAX_CONTROLLERS = 20;
/** The maximum number of controller buttons supported by the basic game */
private static final int MAX_CONTROLLER_BUTTONS = 100;
/** The title of the game */
private String title;
/** The state of the left control */
protected boolean[] controllerLeft = new boolean[MAX_CONTROLLERS];
/** The state of the right control */
protected boolean[] controllerRight = new boolean[MAX_CONTROLLERS];
/** The state of the up control */
protected boolean[] controllerUp = new boolean[MAX_CONTROLLERS];
/** The state of the down control */
protected boolean[] controllerDown = new boolean[MAX_CONTROLLERS];
/** The state of the button controlls */
protected boolean[][] controllerButton = new boolean[MAX_CONTROLLERS][MAX_CONTROLLER_BUTTONS];
/**
* Create a new basic game
*
* @param title The title for the game
*/
public BasicGame(String title) {
this.title = title;
}
/**
* @see org.newdawn.slick.InputListener#setInput(org.newdawn.slick.Input)
*/
public void setInput(Input input) {
}
/**
* @see org.newdawn.slick.Game#closeRequested()
*/
public boolean closeRequested() {
return true;
}
/**
* @see org.newdawn.slick.Game#getTitle()
*/
public String getTitle() {
return title;
}
/**
* @see org.newdawn.slick.Game#init(org.newdawn.slick.GameContainer)
*/
public abstract void init(GameContainer container) throws SlickException;
/**
* @see org.newdawn.slick.InputListener#keyPressed(int, char)
*/
public void keyPressed(int key, char c) {
}
/**
* @see org.newdawn.slick.InputListener#keyReleased(int, char)
*/
public void keyReleased(int key, char c) {
}
/**
* @see org.newdawn.slick.InputListener#mouseMoved(int, int, int, int)
*/
public void mouseMoved(int oldx, int oldy, int newx, int newy) {
}
/**
* @see org.newdawn.slick.InputListener#mouseDragged(int, int, int, int)
*/
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
}
/**
* @see org.newdawn.slick.InputListener#mouseClicked(int, int, int, int)
*/
public void mouseClicked(int button, int x, int y, int clickCount) {
}
/**
* @see org.newdawn.slick.InputListener#mousePressed(int, int, int)
*/
public void mousePressed(int button, int x, int y) {
}
/**
* @see org.newdawn.slick.InputListener#controllerButtonPressed(int, int)
*/
public void controllerButtonPressed(int controller, int button) {
controllerButton[controller][button] = true;
}
/**
* @see org.newdawn.slick.InputListener#controllerButtonReleased(int, int)
*/
public void controllerButtonReleased(int controller, int button) {
controllerButton[controller][button] = false;
}
/**
* @see org.newdawn.slick.InputListener#controllerDownPressed(int)
*/
public void controllerDownPressed(int controller) {
controllerDown[controller] = true;
}
/**
* @see org.newdawn.slick.InputListener#controllerDownReleased(int)
*/
public void controllerDownReleased(int controller) {
controllerDown[controller] = false;
}
/**
* @see org.newdawn.slick.InputListener#controllerLeftPressed(int)
*/
public void controllerLeftPressed(int controller) {
controllerLeft[controller] = true;
}
/**
* @see org.newdawn.slick.InputListener#controllerLeftReleased(int)
*/
public void controllerLeftReleased(int controller) {
controllerLeft[controller] = false;
}
/**
* @see org.newdawn.slick.InputListener#controllerRightPressed(int)
*/
public void controllerRightPressed(int controller) {
controllerRight[controller] = true;
}
/**
* @see org.newdawn.slick.InputListener#controllerRightReleased(int)
*/
public void controllerRightReleased(int controller) {
controllerRight[controller] = false;
}
/**
* @see org.newdawn.slick.InputListener#controllerUpPressed(int)
*/
public void controllerUpPressed(int controller) {
controllerUp[controller] = true;
}
/**
* @see org.newdawn.slick.InputListener#controllerUpReleased(int)
*/
public void controllerUpReleased(int controller) {
controllerUp[controller] = false;
}
/**
* @see org.newdawn.slick.InputListener#mouseReleased(int, int, int)
*/
public void mouseReleased(int button, int x, int y) {
}
/**
* @see org.newdawn.slick.Game#update(org.newdawn.slick.GameContainer, int)
*/
public abstract void update(GameContainer container, int delta) throws SlickException;
/**
* @see org.newdawn.slick.InputListener#mouseWheelMoved(int)
*/
public void mouseWheelMoved(int change) {
}
/**
* @see org.newdawn.slick.InputListener#isAcceptingInput()
*/
public boolean isAcceptingInput() {
return true;
}
/**
* @see org.newdawn.slick.InputListener#inputEnded()
*/
public void inputEnded() {
}
/**
* @see org.newdawn.slick.ControlledInputReciever#inputStarted()
*/
public void inputStarted() {
}
}

View File

@@ -0,0 +1,768 @@
package org.newdawn.slick;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.newdawn.slick.opengl.ImageData;
import org.newdawn.slick.opengl.ImageDataFactory;
import org.newdawn.slick.opengl.LoadableImageData;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.renderer.SGL;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.util.OperationNotSupportedException;
import org.newdawn.slick.util.ResourceLoader;
/**
* An image implementation that handles loaded images that are larger than the
* maximum texture size supported by the card. In most cases it makes sense
* to make sure all of your image resources are less than 512x512 in size when
* using OpenGL. However, in the rare circumstances where this isn't possible
* this implementation can be used to draw a tiled version of the image built
* from several smaller textures.
*
* This implementation does come with limitations and some performance impact
* however - so use only when absolutely required.
*
* TODO: The code in here isn't pretty, really needs revisiting with a comment stick.
*
* @author kevin
*/
public class BigImage extends Image {
/** The renderer to use for all GL operations */
protected static SGL GL = Renderer.get();
/**
* Get the maximum size of an image supported by the underlying
* hardware.
*
* @return The maximum size of the textures supported by the underlying
* hardware.
*/
public static final int getMaxSingleImageSize() {
IntBuffer buffer = BufferUtils.createIntBuffer(16);
GL.glGetInteger(SGL.GL_MAX_TEXTURE_SIZE, buffer);
return buffer.get(0);
}
/** The last image that we put into "in use" mode */
private static Image lastBind;
/** The images building up this sub-image */
private Image[][] images;
/** The number of images on the xaxis */
private int xcount;
/** The number of images on the yaxis */
private int ycount;
/** The real width of the whole image - maintained even when scaled */
private int realWidth;
/** The real hieght of the whole image - maintained even when scaled */
private int realHeight;
/**
* Create a new big image. Empty contructor for cloning only
*/
private BigImage() {
inited = true;
}
/**
* Create a new big image by loading it from the specified reference
*
* @param ref The reference to the image to load
* @throws SlickException Indicates we were unable to locate the resource
*/
public BigImage(String ref) throws SlickException {
this(ref, Image.FILTER_NEAREST);
}
/**
* Create a new big image by loading it from the specified reference
*
* @param ref The reference to the image to load
* @param filter The image filter to apply (@see #Image.FILTER_NEAREST)
* @throws SlickException Indicates we were unable to locate the resource
*/
public BigImage(String ref,int filter) throws SlickException {
build(ref, filter, getMaxSingleImageSize());
}
/**
* Create a new big image by loading it from the specified reference
*
* @param ref The reference to the image to load
* @param filter The image filter to apply (@see #Image.FILTER_NEAREST)
* @param tileSize The maximum size of the tiles to use to build the bigger image
* @throws SlickException Indicates we were unable to locate the resource
*/
public BigImage(String ref, int filter, int tileSize) throws SlickException {
build(ref, filter, tileSize);
}
/**
* Create a new big image by loading it from the specified image data
*
* @param data The pixelData to use to create the image
* @param imageBuffer The buffer containing texture data
* @param filter The image filter to apply (@see #Image.FILTER_NEAREST)
*/
public BigImage(LoadableImageData data, ByteBuffer imageBuffer, int filter) {
build(data, imageBuffer, filter, getMaxSingleImageSize());
}
/**
* Create a new big image by loading it from the specified image data
*
* @param data The pixelData to use to create the image
* @param imageBuffer The buffer containing texture data
* @param filter The image filter to apply (@see #Image.FILTER_NEAREST)
* @param tileSize The maximum size of the tiles to use to build the bigger image
*/
public BigImage(LoadableImageData data, ByteBuffer imageBuffer, int filter, int tileSize) {
build(data, imageBuffer, filter, tileSize);
}
/**
* Get a sub tile of this big image. Useful for debugging
*
* @param x The x tile index
* @param y The y tile index
* @return The image used for this tile
*/
public Image getTile(int x, int y) {
return images[x][y];
}
/**
* Create a new big image by loading it from the specified reference
*
* @param ref The reference to the image to load
* @param filter The image filter to apply (@see #Image.FILTER_NEAREST)
* @param tileSize The maximum size of the tiles to use to build the bigger image
* @throws SlickException Indicates we were unable to locate the resource
*/
private void build(String ref, int filter, int tileSize) throws SlickException {
try {
final LoadableImageData data = ImageDataFactory.getImageDataFor(ref);
final ByteBuffer imageBuffer = data.loadImage(ResourceLoader.getResourceAsStream(ref), false, null);
build(data, imageBuffer, filter, tileSize);
} catch (IOException e) {
throw new SlickException("Failed to load: "+ref, e);
}
}
/**
* Create an big image from a image data source.
*
* @param data The pixelData to use to create the image
* @param imageBuffer The buffer containing texture data
* @param filter The filter to use when scaling this image
* @param tileSize The maximum size of the tiles to use to build the bigger image
*/
private void build(final LoadableImageData data, final ByteBuffer imageBuffer, int filter, int tileSize) {
final int dataWidth = data.getTexWidth();
final int dataHeight = data.getTexHeight();
realWidth = width = data.getWidth();
realHeight = height = data.getHeight();
if ((dataWidth <= tileSize) && (dataHeight <= tileSize)) {
images = new Image[1][1];
ImageData tempData = new ImageData() {
public int getDepth() {
return data.getDepth();
}
public int getHeight() {
return dataHeight;
}
public ByteBuffer getImageBufferData() {
return imageBuffer;
}
public int getTexHeight() {
return dataHeight;
}
public int getTexWidth() {
return dataWidth;
}
public int getWidth() {
return dataWidth;
}
};
images[0][0] = new Image(tempData, filter);
xcount = 1;
ycount = 1;
inited = true;
return;
}
xcount = ((realWidth-1) / tileSize) + 1;
ycount = ((realHeight-1) / tileSize) + 1;
images = new Image[xcount][ycount];
int components = data.getDepth() / 8;
for (int x=0;x<xcount;x++) {
for (int y=0;y<ycount;y++) {
int finalX = ((x+1) * tileSize);
int finalY = ((y+1) * tileSize);
final int imageWidth = Math.min((realWidth - (x*tileSize)), tileSize);
final int imageHeight = Math.min((realHeight - (y*tileSize)), tileSize);
final int xSize = tileSize;
final int ySize = tileSize;
final ByteBuffer subBuffer = BufferUtils.createByteBuffer(tileSize*tileSize*components);
int xo = x*tileSize*components;
byte[] byteData = new byte[xSize*components];
for (int i=0;i<ySize;i++) {
int yo = (((y * tileSize) + i) * dataWidth) * components;
imageBuffer.position(yo+xo);
imageBuffer.get(byteData, 0, xSize*components);
subBuffer.put(byteData);
}
subBuffer.flip();
ImageData imgData = new ImageData() {
public int getDepth() {
return data.getDepth();
}
public int getHeight() {
return imageHeight;
}
public int getWidth() {
return imageWidth;
}
public ByteBuffer getImageBufferData() {
return subBuffer;
}
public int getTexHeight() {
return ySize;
}
public int getTexWidth() {
return xSize;
}
};
images[x][y] = new Image(imgData, filter);
}
}
inited = true;
}
/**
* Not supported in BigImage
*
* @see org.newdawn.slick.Image#bind()
*/
public void bind() {
throw new OperationNotSupportedException("Can't bind big images yet");
}
/**
* Not supported in BigImage
*
* @see org.newdawn.slick.Image#copy()
*/
public Image copy() {
throw new OperationNotSupportedException("Can't copy big images yet");
}
/**
* @see org.newdawn.slick.Image#draw()
*/
public void draw() {
draw(0,0);
}
/**
* @see org.newdawn.slick.Image#draw(float, float, org.newdawn.slick.Color)
*/
public void draw(float x, float y, Color filter) {
draw(x,y,width,height,filter);
}
/**
* @see org.newdawn.slick.Image#draw(float, float, float, org.newdawn.slick.Color)
*/
public void draw(float x, float y, float scale, Color filter) {
draw(x,y,width*scale,height*scale,filter);
}
/**
* @see org.newdawn.slick.Image#draw(float, float, float, float, org.newdawn.slick.Color)
*/
public void draw(float x, float y, float width, float height, Color filter) {
float sx = width / realWidth;
float sy = height / realHeight;
GL.glTranslatef(x,y,0);
GL.glScalef(sx,sy,1);
float xp = 0;
float yp = 0;
for (int tx=0;tx<xcount;tx++) {
yp = 0;
for (int ty=0;ty<ycount;ty++) {
Image image = images[tx][ty];
image.draw(xp,yp,image.getWidth(), image.getHeight(), filter);
yp += image.getHeight();
if (ty == ycount - 1) {
xp += image.getWidth();
}
}
}
GL.glScalef(1.0f/sx,1.0f/sy,1);
GL.glTranslatef(-x,-y,0);
}
/**
* @see org.newdawn.slick.Image#draw(float, float, float, float, float, float, float, float)
*/
public void draw(float x, float y, float x2, float y2, float srcx, float srcy, float srcx2, float srcy2) {
int srcwidth = (int) (srcx2 - srcx);
int srcheight = (int) (srcy2 - srcy);
Image subImage = getSubImage((int) srcx,(int) srcy,srcwidth,srcheight);
int width = (int) (x2 - x);
int height = (int) (y2 - y);
subImage.draw(x,y,width,height);
}
/**
* @see org.newdawn.slick.Image#draw(float, float, float, float, float, float)
*/
public void draw(float x, float y, float srcx, float srcy, float srcx2, float srcy2) {
int srcwidth = (int) (srcx2 - srcx);
int srcheight = (int) (srcy2 - srcy);
draw(x,y,srcwidth,srcheight,srcx,srcy,srcx2,srcy2);
}
/**
* @see org.newdawn.slick.Image#draw(float, float, float, float)
*/
public void draw(float x, float y, float width, float height) {
draw(x,y,width,height,Color.white);
}
/**
* @see org.newdawn.slick.Image#draw(float, float, float)
*/
public void draw(float x, float y, float scale) {
draw(x,y,scale,Color.white);
}
/**
* @see org.newdawn.slick.Image#draw(float, float)
*/
public void draw(float x, float y) {
draw(x,y,Color.white);
}
/**
* @see org.newdawn.slick.Image#drawEmbedded(float, float, float, float)
*/
public void drawEmbedded(float x, float y, float width, float height) {
float sx = width / realWidth;
float sy = height / realHeight;
float xp = 0;
float yp = 0;
for (int tx=0;tx<xcount;tx++) {
yp = 0;
for (int ty=0;ty<ycount;ty++) {
Image image = images[tx][ty];
if ((lastBind == null) || (image.getTexture() != lastBind.getTexture())) {
if (lastBind != null) {
lastBind.endUse();
}
lastBind = image;
lastBind.startUse();
}
image.drawEmbedded(xp+x,yp+y,image.getWidth(), image.getHeight());
yp += image.getHeight();
if (ty == ycount - 1) {
xp += image.getWidth();
}
}
}
}
/**
* @see org.newdawn.slick.Image#drawFlash(float, float, float, float)
*/
public void drawFlash(float x, float y, float width, float height) {
float sx = width / realWidth;
float sy = height / realHeight;
GL.glTranslatef(x,y,0);
GL.glScalef(sx,sy,1);
float xp = 0;
float yp = 0;
for (int tx=0;tx<xcount;tx++) {
yp = 0;
for (int ty=0;ty<ycount;ty++) {
Image image = images[tx][ty];
image.drawFlash(xp,yp,image.getWidth(), image.getHeight());
yp += image.getHeight();
if (ty == ycount - 1) {
xp += image.getWidth();
}
}
}
GL.glScalef(1.0f/sx,1.0f/sy,1);
GL.glTranslatef(-x,-y,0);
}
/**
* @see org.newdawn.slick.Image#drawFlash(float, float)
*/
public void drawFlash(float x, float y) {
drawFlash(x,y,width,height);
}
/**
* Not supported in BigImage
*
* @see org.newdawn.slick.Image#endUse()
*/
public void endUse() {
if (lastBind != null) {
lastBind.endUse();
}
lastBind = null;
}
/**
* Not supported in BigImage
*
* @see org.newdawn.slick.Image#startUse()
*/
public void startUse() {
}
/**
* Not supported in BigImage
*
* @see org.newdawn.slick.Image#ensureInverted()
*/
public void ensureInverted() {
throw new OperationNotSupportedException("Doesn't make sense for tiled operations");
}
/**
* Not supported in BigImage
*
* @see org.newdawn.slick.Image#getColor(int, int)
*/
public Color getColor(int x, int y) {
throw new OperationNotSupportedException("Can't use big images as buffers");
}
/**
* @see org.newdawn.slick.Image#getFlippedCopy(boolean, boolean)
*/
public Image getFlippedCopy(boolean flipHorizontal, boolean flipVertical) {
BigImage image = new BigImage();
image.images = images;
image.xcount = xcount;
image.ycount = ycount;
image.width = width;
image.height = height;
image.realWidth = realWidth;
image.realHeight = realHeight;
if (flipHorizontal) {
Image[][] images = image.images;
image.images = new Image[xcount][ycount];
for (int x=0;x<xcount;x++) {
for (int y=0;y<ycount;y++) {
image.images[x][y] = images[xcount-1-x][y].getFlippedCopy(true, false);
}
}
}
if (flipVertical) {
Image[][] images = image.images;
image.images = new Image[xcount][ycount];
for (int x=0;x<xcount;x++) {
for (int y=0;y<ycount;y++) {
image.images[x][y] = images[x][ycount-1-y].getFlippedCopy(false, true);
}
}
}
return image;
}
/**
* Not supported in BigImage
*
* @see org.newdawn.slick.Image#getGraphics()
*/
public Graphics getGraphics() throws SlickException {
throw new OperationNotSupportedException("Can't use big images as offscreen buffers");
}
/**
* @see org.newdawn.slick.Image#getScaledCopy(float)
*/
public Image getScaledCopy(float scale) {
return getScaledCopy((int) (scale * width), (int) (scale * height));
}
/**
* @see org.newdawn.slick.Image#getScaledCopy(int, int)
*/
public Image getScaledCopy(int width, int height) {
BigImage image = new BigImage();
image.images = images;
image.xcount = xcount;
image.ycount = ycount;
image.width = width;
image.height = height;
image.realWidth = realWidth;
image.realHeight = realHeight;
return image;
}
/**
* @see org.newdawn.slick.Image#getSubImage(int, int, int, int)
*/
public Image getSubImage(int x, int y, int width, int height) {
BigImage image = new BigImage();
image.width = width;
image.height = height;
image.realWidth = width;
image.realHeight = height;
image.images = new Image[this.xcount][this.ycount];
float xp = 0;
float yp = 0;
int x2 = x+width;
int y2 = y+height;
int startx = 0;
int starty = 0;
boolean foundStart = false;
for (int xt=0;xt<xcount;xt++) {
yp = 0;
starty = 0;
foundStart = false;
for (int yt=0;yt<ycount;yt++) {
Image current = images[xt][yt];
int xp2 = (int) (xp + current.getWidth());
int yp2 = (int) (yp + current.getHeight());
// if the top corner of the subimage is inside the area
// we want or the bottom corrent of the image is, then consider using the
// image
// this image contributes to the sub image we're attempt to retrieve
int targetX1 = (int) Math.max(x, xp);
int targetY1 = (int) Math.max(y, yp);
int targetX2 = Math.min(x2, xp2);
int targetY2 = Math.min(y2, yp2);
int targetWidth = targetX2 - targetX1;
int targetHeight = targetY2 - targetY1;
if ((targetWidth > 0) && (targetHeight > 0)) {
Image subImage = current.getSubImage((int) (targetX1 - xp), (int) (targetY1 - yp),
(targetX2 - targetX1),
(targetY2 - targetY1));
foundStart = true;
image.images[startx][starty] = subImage;
starty++;
image.ycount = Math.max(image.ycount, starty);
}
yp += current.getHeight();
if (yt == ycount - 1) {
xp += current.getWidth();
}
}
if (foundStart) {
startx++;
image.xcount++;
}
}
return image;
}
/**
* Not supported in BigImage
*
* @see org.newdawn.slick.Image#getTexture()
*/
public Texture getTexture() {
throw new OperationNotSupportedException("Can't use big images as offscreen buffers");
}
/**
* @see org.newdawn.slick.Image#initImpl()
*/
protected void initImpl() {
throw new OperationNotSupportedException("Can't use big images as offscreen buffers");
}
/**
* @see org.newdawn.slick.Image#reinit()
*/
protected void reinit() {
throw new OperationNotSupportedException("Can't use big images as offscreen buffers");
}
/**
* Not supported in BigImage
*
* @see org.newdawn.slick.Image#setTexture(org.newdawn.slick.opengl.Texture)
*/
public void setTexture(Texture texture) {
throw new OperationNotSupportedException("Can't use big images as offscreen buffers");
}
/**
* Get a sub-image that builds up this image. Note that the offsets
* used will depend on the maximum texture size on the OpenGL hardware
*
* @param offsetX The x position of the image to return
* @param offsetY The y position of the image to return
* @return The image at the specified offset into the big image
*/
public Image getSubImage(int offsetX, int offsetY) {
return images[offsetX][offsetY];
}
/**
* Get a count of the number images that build this image up horizontally
*
* @return The number of sub-images across the big image
*/
public int getHorizontalImageCount() {
return xcount;
}
/**
* Get a count of the number images that build this image up vertically
*
* @return The number of sub-images down the big image
*/
public int getVerticalImageCount() {
return ycount;
}
/**
* @see org.newdawn.slick.Image#toString()
*/
public String toString() {
return "[BIG IMAGE]";
}
/**
* Destroy the image and release any native resources.
* Calls on a destroyed image have undefined results
*/
public void destroy() throws SlickException {
for (int tx=0;tx<xcount;tx++) {
for (int ty=0;ty<ycount;ty++) {
Image image = images[tx][ty];
image.destroy();
}
}
}
/**
* @see org.newdawn.slick.Image#draw(float, float, float, float, float, float, float, float, org.newdawn.slick.Color)
*/
public void draw(float x, float y, float x2, float y2, float srcx,
float srcy, float srcx2, float srcy2, Color filter) {
int srcwidth = (int) (srcx2 - srcx);
int srcheight = (int) (srcy2 - srcy);
Image subImage = getSubImage((int) srcx,(int) srcy,srcwidth,srcheight);
int width = (int) (x2 - x);
int height = (int) (y2 - y);
subImage.draw(x,y,width,height,filter);
}
/**
* @see org.newdawn.slick.Image#drawCentered(float, float)
*/
public void drawCentered(float x, float y) {
throw new UnsupportedOperationException();
}
/**
* @see org.newdawn.slick.Image#drawEmbedded(float, float, float, float, float, float, float, float, org.newdawn.slick.Color)
*/
public void drawEmbedded(float x, float y, float x2, float y2, float srcx,
float srcy, float srcx2, float srcy2, Color filter) {
throw new UnsupportedOperationException();
}
/**
* @see org.newdawn.slick.Image#drawEmbedded(float, float, float, float, float, float, float, float)
*/
public void drawEmbedded(float x, float y, float x2, float y2, float srcx,
float srcy, float srcx2, float srcy2) {
throw new UnsupportedOperationException();
}
/**
* @see org.newdawn.slick.Image#drawFlash(float, float, float, float, org.newdawn.slick.Color)
*/
public void drawFlash(float x, float y, float width, float height, Color col) {
throw new UnsupportedOperationException();
}
/**
* @see org.newdawn.slick.Image#drawSheared(float, float, float, float)
*/
public void drawSheared(float x, float y, float hshear, float vshear) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,76 @@
package org.newdawn.slick;
import org.newdawn.slick.opengl.SlickCallable;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
/**
* A set of rendering that is cached onto the graphics card and hopefully
* is quicker to render. Note that there are some things that can't be done
* in lists and that all dependent operations must be container. For instance,
* any colour configuration can not be assumed from outside the cache.
*
* Note: The destroy method needs to be used to tidy up. This is pretty important
* in this case since there are limited number of underlying resources.
*
* @author kevin
*/
public class CachedRender {
/** The renderer to use for all GL operations */
protected static SGL GL = Renderer.get();
/** The operations to cache */
private Runnable runnable;
/** The display list cached to */
private int list = -1;
/**
* Create a new cached render that will build the specified
* operations on to a video card resource
*
* @param runnable The operations to cache
*/
public CachedRender(Runnable runnable) {
this.runnable = runnable;
build();
}
/**
* Build the display list
*/
private void build() {
if (list == -1) {
list = GL.glGenLists(1);
SlickCallable.enterSafeBlock();
GL.glNewList(list, SGL.GL_COMPILE);
runnable.run();
GL.glEndList();
SlickCallable.leaveSafeBlock();
} else {
throw new RuntimeException("Attempt to build the display list more than once in CachedRender");
}
}
/**
* Render the cached operations. Note that this doesn't call the operations, but
* rather calls the cached version
*/
public void render() {
if (list == -1) {
throw new RuntimeException("Attempt to render cached operations that have been destroyed");
}
SlickCallable.enterSafeBlock();
GL.glCallList(list);
SlickCallable.leaveSafeBlock();
}
/**
* Destroy this cached render
*/
public void destroy() {
GL.glDeleteLists(list,1);
list = -1;
}
}

View File

@@ -0,0 +1,184 @@
package org.newdawn.slick;
import java.awt.Canvas;
import javax.swing.SwingUtilities;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.newdawn.slick.util.Log;
/**
* A game container that displays the game on an AWT Canvas.
*
* @author kevin
*/
public class CanvasGameContainer extends Canvas {
/** The actual container implementation */
protected Container container;
/** The game being held in this container */
protected Game game;
/**
* Create a new panel
*
* @param game The game being held
* @throws SlickException Indicates a failure during creation of the container
*/
public CanvasGameContainer(Game game) throws SlickException {
this(game, false);
}
/**
* Create a new panel
*
* @param game The game being held
* @param shared True if shared GL context should be enabled. This allows multiple panels
* to share textures and other GL resources.
* @throws SlickException Indicates a failure during creation of the container
*/
public CanvasGameContainer(Game game, boolean shared) throws SlickException {
super();
this.game = game;
setIgnoreRepaint(true);
requestFocus();
setSize(500,500);
container = new Container(game, shared);
container.setForceExit(false);
}
/**
* Start the game container rendering
*
* @throws SlickException Indicates a failure during game execution
*/
public void start() throws SlickException {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
Input.disableControllers();
try {
Display.setParent(CanvasGameContainer.this);
} catch (LWJGLException e) {
throw new SlickException("Failed to setParent of canvas", e);
}
container.setup();
scheduleUpdate();
} catch (SlickException e) {
e.printStackTrace();
System.exit(0);
}
}
});
}
/**
* Schedule an update on the EDT
*/
private void scheduleUpdate() {
if (!isVisible()) {
return;
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
container.gameLoop();
} catch (SlickException e) {
e.printStackTrace();
}
container.checkDimensions();
scheduleUpdate();
}
});
}
/**
* Dispose the container and any resources it holds
*/
public void dispose() {
}
/**
* Get the GameContainer providing this canvas
*
* @return The game container providing this canvas
*/
public GameContainer getContainer() {
return container;
}
/**
* A game container to provide the canvas context
*
* @author kevin
*/
private class Container extends AppGameContainer {
/**
* Create a new container wrapped round the game
*
* @param game
* The game to be held in this container
* @param shared True if shared GL context should be enabled. This allows multiple panels
* to share textures and other GL resources.
* @throws SlickException Indicates a failure to initialise
*/
public Container(Game game, boolean shared) throws SlickException {
super(game, CanvasGameContainer.this.getWidth(), CanvasGameContainer.this.getHeight(), false);
width = CanvasGameContainer.this.getWidth();
height = CanvasGameContainer.this.getHeight();
if (shared) {
enableSharedContext();
}
}
/**
* Updated the FPS counter
*/
protected void updateFPS() {
super.updateFPS();
}
/**
* @see org.newdawn.slick.GameContainer#running()
*/
protected boolean running() {
return super.running() && CanvasGameContainer.this.isDisplayable();
}
/**
* @see org.newdawn.slick.GameContainer#getHeight()
*/
public int getHeight() {
return CanvasGameContainer.this.getHeight();
}
/**
* @see org.newdawn.slick.GameContainer#getWidth()
*/
public int getWidth() {
return CanvasGameContainer.this.getWidth();
}
/**
* Check the dimensions of the canvas match the display
*/
public void checkDimensions() {
if ((width != CanvasGameContainer.this.getWidth()) ||
(height != CanvasGameContainer.this.getHeight())) {
try {
setDisplayMode(CanvasGameContainer.this.getWidth(),
CanvasGameContainer.this.getHeight(), false);
} catch (SlickException e) {
Log.error(e);
}
}
}
}
}

View File

@@ -0,0 +1,392 @@
package org.newdawn.slick;
import java.io.Serializable;
import java.nio.FloatBuffer;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
/**
* A simple wrapper round the values required for a colour
*
* @author Kevin Glass
*/
public class Color implements Serializable {
/** The version ID for this class */
private static final long serialVersionUID = 1393939L;
/** The renderer to use for all GL operations */
protected transient SGL GL = Renderer.get();
/** The fixed color transparent */
public static final Color transparent = new Color(0.0f,0.0f,0.0f,0.0f);
/** The fixed colour white */
public static final Color white = new Color(1.0f,1.0f,1.0f,1.0f);
/** The fixed colour yellow */
public static final Color yellow = new Color(1.0f,1.0f,0,1.0f);
/** The fixed colour red */
public static final Color red = new Color(1.0f,0,0,1.0f);
/** The fixed colour blue */
public static final Color blue = new Color(0,0,1.0f,1.0f);
/** The fixed colour green */
public static final Color green = new Color(0,1.0f,0,1.0f);
/** The fixed colour black */
public static final Color black = new Color(0,0,0,1.0f);
/** The fixed colour gray */
public static final Color gray = new Color(0.5f,0.5f,0.5f,1.0f);
/** The fixed colour cyan */
public static final Color cyan = new Color(0,1.0f,1.0f,1.0f);
/** The fixed colour dark gray */
public static final Color darkGray = new Color(0.3f,0.3f,0.3f,1.0f);
/** The fixed colour light gray */
public static final Color lightGray = new Color(0.7f,0.7f,0.7f,1.0f);
/** The fixed colour dark pink */
public final static Color pink = new Color(255, 175, 175, 255);
/** The fixed colour dark orange */
public final static Color orange = new Color(255, 200, 0, 255);
/** The fixed colour dark magenta */
public final static Color magenta = new Color(255, 0, 255, 255);
/** The red component of the colour */
public float r;
/** The green component of the colour */
public float g;
/** The blue component of the colour */
public float b;
/** The alpha component of the colour */
public float a = 1.0f;
/**
* Copy constructor
*
* @param color The color to copy into the new instance
*/
public Color(Color color) {
r = color.r;
g = color.g;
b = color.b;
a = color.a;
}
/**
* Create a component based on the first 4 elements of a float buffer
*
* @param buffer The buffer to read the color from
*/
public Color(FloatBuffer buffer) {
this.r = buffer.get();
this.g = buffer.get();
this.b = buffer.get();
this.a = buffer.get();
}
/**
* Create a 3 component colour
*
* @param r The red component of the colour (0.0 -> 1.0)
* @param g The green component of the colour (0.0 -> 1.0)
* @param b The blue component of the colour (0.0 -> 1.0)
*/
public Color(float r,float g,float b) {
this.r = r;
this.g = g;
this.b = b;
this.a = 1;
}
/**
* Create a 4 component colour
*
* @param r The red component of the colour (0.0 -> 1.0)
* @param g The green component of the colour (0.0 -> 1.0)
* @param b The blue component of the colour (0.0 -> 1.0)
* @param a The alpha component of the colour (0.0 -> 1.0)
*/
public Color(float r,float g,float b,float a) {
this.r = Math.min(r, 1);
this.g = Math.min(g, 1);
this.b = Math.min(b, 1);
this.a = Math.min(a, 1);
}
/**
* Create a 3 component colour
*
* @param r The red component of the colour (0 -> 255)
* @param g The green component of the colour (0 -> 255)
* @param b The blue component of the colour (0 -> 255)
*/
public Color(int r,int g,int b) {
this.r = r / 255.0f;
this.g = g / 255.0f;
this.b = b / 255.0f;
this.a = 1;
}
/**
* Create a 4 component colour
*
* @param r The red component of the colour (0 -> 255)
* @param g The green component of the colour (0 -> 255)
* @param b The blue component of the colour (0 -> 255)
* @param a The alpha component of the colour (0 -> 255)
*/
public Color(int r,int g,int b,int a) {
this.r = r / 255.0f;
this.g = g / 255.0f;
this.b = b / 255.0f;
this.a = a / 255.0f;
}
/**
* Create a colour from an evil integer packed 0xAARRGGBB. If AA
* is specified as zero then it will be interpreted as unspecified
* and hence a value of 255 will be recorded.
*
* @param value The value to interpret for the colour
*/
public Color(int value) {
int r = (value & 0x00FF0000) >> 16;
int g = (value & 0x0000FF00) >> 8;
int b = (value & 0x000000FF);
int a = (value & 0xFF000000) >> 24;
if (a < 0) {
a += 256;
}
if (a == 0) {
a = 255;
}
this.r = r / 255.0f;
this.g = g / 255.0f;
this.b = b / 255.0f;
this.a = a / 255.0f;
}
/**
* Decode a number in a string and process it as a colour
* reference.
*
* @param nm The number string to decode
* @return The color generated from the number read
*/
public static Color decode(String nm) {
return new Color(Integer.decode(nm).intValue());
}
/**
* Bind this colour to the GL context
*/
public void bind() {
GL.glColor4f(r,g,b,a);
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return ((int) (r+g+b+a)*255);
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object other) {
if (other instanceof Color) {
Color o = (Color) other;
return ((o.r == r) && (o.g == g) && (o.b == b) && (o.a == a));
}
return false;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "Color ("+r+","+g+","+b+","+a+")";
}
/**
* Make a darker instance of this colour
*
* @return The darker version of this colour
*/
public Color darker() {
return darker(0.5f);
}
/**
* Make a darker instance of this colour
*
* @param scale The scale down of RGB (i.e. if you supply 0.03 the colour will be darkened by 3%)
* @return The darker version of this colour
*/
public Color darker(float scale) {
scale = 1 - scale;
Color temp = new Color(r * scale,g * scale,b * scale,a);
return temp;
}
/**
* Make a brighter instance of this colour
*
* @return The brighter version of this colour
*/
public Color brighter() {
return brighter(0.2f);
}
/**
* Get the red byte component of this colour
*
* @return The red component (range 0-255)
*/
public int getRed() {
return (int) (r * 255);
}
/**
* Get the green byte component of this colour
*
* @return The green component (range 0-255)
*/
public int getGreen() {
return (int) (g * 255);
}
/**
* Get the blue byte component of this colour
*
* @return The blue component (range 0-255)
*/
public int getBlue() {
return (int) (b * 255);
}
/**
* Get the alpha byte component of this colour
*
* @return The alpha component (range 0-255)
*/
public int getAlpha() {
return (int) (a * 255);
}
/**
* Get the red byte component of this colour
*
* @return The red component (range 0-255)
*/
public int getRedByte() {
return (int) (r * 255);
}
/**
* Get the green byte component of this colour
*
* @return The green component (range 0-255)
*/
public int getGreenByte() {
return (int) (g * 255);
}
/**
* Get the blue byte component of this colour
*
* @return The blue component (range 0-255)
*/
public int getBlueByte() {
return (int) (b * 255);
}
/**
* Get the alpha byte component of this colour
*
* @return The alpha component (range 0-255)
*/
public int getAlphaByte() {
return (int) (a * 255);
}
/**
* Make a brighter instance of this colour
*
* @param scale The scale up of RGB (i.e. if you supply 0.03 the colour will be brightened by 3%)
* @return The brighter version of this colour
*/
public Color brighter(float scale) {
scale += 1;
Color temp = new Color(r * scale,g * scale,b * scale,a);
return temp;
}
/**
* Multiply this color by another
*
* @param c the other color
* @return product of the two colors
*/
public Color multiply(Color c) {
return new Color(r * c.r, g * c.g, b * c.b, a * c.a);
}
/**
* Add another colour to this one
*
* @param c The colour to add
*/
public void add(Color c) {
r += c.r;
g += c.g;
b += c.b;
a += c.a;
}
/**
* Scale the components of the colour by the given value
*
* @param value The value to scale by
*/
public void scale(float value) {
r *= value;
g *= value;
b *= value;
a *= value;
}
/**
* Add another colour to this one
*
* @param c The colour to add
* @return The copy which has had the color added to it
*/
public Color addToCopy(Color c) {
Color copy = new Color(r,g,b,a);
copy.r += c.r;
copy.g += c.g;
copy.b += c.b;
copy.a += c.a;
return copy;
}
/**
* Scale the components of the colour by the given value
*
* @param value The value to scale by
* @return The copy which has been scaled
*/
public Color scaleCopy(float value) {
Color copy = new Color(r,g,b,a);
copy.r *= value;
copy.g *= value;
copy.b *= value;
copy.a *= value;
return copy;
}
}

View File

@@ -0,0 +1,42 @@
package org.newdawn.slick;
/**
* Description of any class capable of recieving and controlling it's own
* reception of input
*
* You'll shouldn't really need to implement this one for your self, use one of the sub-interfaces:
*
* {@link InputListener}
* {@link MouseListener}
* {@link KeyListener}
* {@link ControllerListener}
*
* @author kevin
*/
public interface ControlledInputReciever {
/**
* Set the input that events are being sent from
*
* @param input The input instance sending events
*/
public abstract void setInput(Input input);
/**
* Check if this input listener is accepting input
*
* @return True if the input listener should recieve events
*/
public abstract boolean isAcceptingInput();
/**
* Notification that all input events have been sent for this frame
*/
public abstract void inputEnded();
/**
* Notification that input is about to be processed
*/
public abstract void inputStarted();
}

View File

@@ -0,0 +1,102 @@
package org.newdawn.slick;
/**
* Description of classes capable of responding to controller events
*
* @author kevin
*/
public interface ControllerListener extends ControlledInputReciever {
/**
* Notification that the left control has been pressed on
* the controller.
*
* @param controller The index of the controller on which the control
* was pressed.
*/
public abstract void controllerLeftPressed(int controller);
/**
* Notification that the left control has been released on
* the controller.
*
* @param controller The index of the controller on which the control
* was released.
*/
public abstract void controllerLeftReleased(int controller);
/**
* Notification that the right control has been pressed on
* the controller.
*
* @param controller The index of the controller on which the control
* was pressed.
*/
public abstract void controllerRightPressed(int controller);
/**
* Notification that the right control has been released on
* the controller.
*
* @param controller The index of the controller on which the control
* was released.
*/
public abstract void controllerRightReleased(int controller);
/**
* Notification that the up control has been pressed on
* the controller.
*
* @param controller The index of the controller on which the control
* was pressed.
*/
public abstract void controllerUpPressed(int controller);
/**
* Notification that the up control has been released on
* the controller.
*
* @param controller The index of the controller on which the control
* was released.
*/
public abstract void controllerUpReleased(int controller);
/**
* Notification that the down control has been pressed on
* the controller.
*
* @param controller The index of the controller on which the control
* was pressed.
*/
public abstract void controllerDownPressed(int controller);
/**
* Notification that the down control has been released on
* the controller.
*
* @param controller The index of the controller on which the control
* was released.
*/
public abstract void controllerDownReleased(int controller);
/**
* Notification that a button control has been pressed on
* the controller.
*
* @param controller The index of the controller on which the control
* was pressed.
* @param button The index of the button pressed (starting at 1)
*/
public abstract void controllerButtonPressed(int controller, int button);
/**
* Notification that a button control has been released on
* the controller.
*
* @param controller The index of the controller on which the control
* was released.
* @param button The index of the button released (starting at 1)
*/
public abstract void controllerButtonReleased(int controller, int button);
}

View File

@@ -0,0 +1,65 @@
package org.newdawn.slick;
/**
* The proprites of any font implementation
*
* @author Kevin Glass
*/
public interface Font {
/**
* Get the width of the given string
*
* @param str The string to obtain the rendered with of
* @return The width of the given string
*/
public abstract int getWidth(String str);
/**
* Get the height of the given string
*
* @param str The string to obtain the rendered with of
* @return The width of the given string
*/
public abstract int getHeight(String str);
/**
* Get the maximum height of any line drawn by this font
*
* @return The maxium height of any line drawn by this font
*/
public int getLineHeight();
/**
* Draw a string to the screen
*
* @param x The x location at which to draw the string
* @param y The y location at which to draw the string
* @param text The text to be displayed
*/
public abstract void drawString(float x, float y, String text);
/**
* Draw a string to the screen
*
* @param x The x location at which to draw the string
* @param y The y location at which to draw the string
* @param text The text to be displayed
* @param col The colour to draw with
*/
public abstract void drawString(float x, float y, String text, Color col);
/**
* Draw part of a string to the screen. Note that this will
* still position the text as though it's part of the bigger string.
*
* @param x The x location at which to draw the string
* @param y The y location at which to draw the string
* @param text The text to be displayed
* @param col The colour to draw with
* @param startIndex The index of the first character to draw
* @param endIndex The index of the last character from the string to draw
*/
public abstract void drawString(float x, float y, String text, Color col, int startIndex, int endIndex);
}

View File

@@ -0,0 +1,55 @@
package org.newdawn.slick;
/**
* The main game interface that should be implemented by any game being developed
* using the container system. There will be some utility type sub-classes as development
* continues.
*
* @see org.newdawn.slick.BasicGame
*
* @author kevin
*/
public interface Game {
/**
* Initialise the game. This can be used to load static resources. It's called
* before the game loop starts
*
* @param container The container holding the game
* @throws SlickException Throw to indicate an internal error
*/
public void init(GameContainer container) throws SlickException;
/**
* Update the game logic here. No rendering should take place in this method
* though it won't do any harm.
*
* @param container The container holing this game
* @param delta The amount of time thats passed since last update in milliseconds
* @throws SlickException Throw to indicate an internal error
*/
public void update(GameContainer container, int delta) throws SlickException;
/**
* Render the game's screen here.
*
* @param container The container holing this game
* @param g The graphics context that can be used to render. However, normal rendering
* routines can also be used.
* @throws SlickException Throw to indicate a internal error
*/
public void render(GameContainer container, Graphics g) throws SlickException;
/**
* Notification that a game close has been requested
*
* @return True if the game should close
*/
public boolean closeRequested();
/**
* Get the title of this game
*
* @return The title of the game
*/
public String getTitle();
}

View File

@@ -0,0 +1,868 @@
package org.newdawn.slick;
import java.io.IOException;
import java.util.Properties;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Cursor;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.Drawable;
import org.lwjgl.opengl.Pbuffer;
import org.lwjgl.opengl.PixelFormat;
import org.newdawn.slick.gui.GUIContext;
import org.newdawn.slick.openal.SoundStore;
import org.newdawn.slick.opengl.CursorLoader;
import org.newdawn.slick.opengl.ImageData;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* 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.error("Unable to determine Slick build number");
return -1;
}
}
/**
* Get the default system font
*
* @return The default system font
*/
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
*/
public abstract int getScreenWidth();
/**
* Get the height of the standard screen resolution
*
* @return The screen height
*/
public abstract int getScreenHeight();
/**
* Get the width of the game canvas
*
* @return The width of the game canvas
*/
public int getWidth() {
return width;
}
/**
* Get the height of the game canvas
*
* @return The height of the game canvas
*/
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
*
* 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
*/
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
*/
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
*/
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
*/
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 -> right)
* @param y The y-coordinate of the cursor hotspot (bottom -> 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
*/
public abstract void setDefaultMouseCursor();
/**
* Get the input system
*
* @return The input system available to this game container
*/
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 - check the game code.");
}
}
} 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 - check the game code.");
}
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);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,167 @@
package org.newdawn.slick;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.lwjgl.BufferUtils;
import org.newdawn.slick.opengl.ImageData;
/**
* A utility for creating images from pixel operations
*
* Expected usage is:
* <code>
* ImageBuffer buffer = new ImageBuffer(320,200);
* buffer.setRGBA(100,100,50,50,20,255);
* ..
* Image image = buffer.getImage();
* </code>
*
* @author kevin
*/
public class ImageBuffer implements ImageData {
/** The width of the image */
private int width;
/** The height of the image */
private int height;
/** The width of the texture */
private int texWidth;
/** The height of the texture */
private int texHeight;
/** The raw data generated for the image */
private byte[] rawData;
/**
*
* @param width
* @param height
*/
public ImageBuffer(int width, int height) {
this.width = width;
this.height = height;
texWidth = get2Fold(width);
texHeight = get2Fold(height);
rawData = new byte[texWidth * texHeight * 4];
}
/**
* Retrieve the raw data stored within the image buffer
*
* @return The raw data in RGBA packed format from within the image buffer
*/
public byte[] getRGBA() {
return rawData;
}
/**
* @see org.newdawn.slick.opengl.ImageData#getDepth()
*/
public int getDepth() {
return 32;
}
/**
* @see org.newdawn.slick.opengl.ImageData#getHeight()
*/
public int getHeight() {
return height;
}
/**
* @see org.newdawn.slick.opengl.ImageData#getTexHeight()
*/
public int getTexHeight() {
return texHeight;
}
/**
* @see org.newdawn.slick.opengl.ImageData#getTexWidth()
*/
public int getTexWidth() {
return texWidth;
}
/**
* @see org.newdawn.slick.opengl.ImageData#getWidth()
*/
public int getWidth() {
return width;
}
/**
* @see org.newdawn.slick.opengl.ImageData#getImageBufferData()
*/
public ByteBuffer getImageBufferData() {
ByteBuffer scratch = BufferUtils.createByteBuffer(rawData.length);
scratch.put(rawData);
scratch.flip();
return scratch;
}
/**
* Set a pixel in the image buffer
*
* @param x The x position of the pixel to set
* @param y The y position of the pixel to set
* @param r The red component to set (0->255)
* @param g The green component to set (0->255)
* @param b The blue component to set (0->255)
* @param a The alpha component to set (0->255)
*/
public void setRGBA(int x, int y, int r, int g, int b, int a) {
if ((x < 0) || (x >= width) || (y < 0) || (y >= height)) {
throw new RuntimeException("Specified location: "+x+","+y+" outside of image");
}
int ofs = ((x + (y * texWidth)) * 4);
if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {
rawData[ofs] = (byte) b;
rawData[ofs + 1] = (byte) g;
rawData[ofs + 2] = (byte) r;
rawData[ofs + 3] = (byte) a;
} else {
rawData[ofs] = (byte) r;
rawData[ofs + 1] = (byte) g;
rawData[ofs + 2] = (byte) b;
rawData[ofs + 3] = (byte) a;
}
}
/**
* Get an image generated based on this buffer
*
* @return The image generated from this buffer
*/
public Image getImage() {
return new Image(this);
}
/**
* Get an image generated based on this buffer
*
* @param filter The filtering method to use when scaling this image
* @return The image generated from this buffer
*/
public Image getImage(int filter) {
return new Image(this, filter);
}
/**
* Get the closest greater power of 2 to the fold number
*
* @param fold The target number
* @return The power of 2
*/
private int get2Fold(int fold) {
int ret = 2;
while (ret < fold) {
ret *= 2;
}
return ret;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
package org.newdawn.slick;
/**
* A listener that will be notified of keyboard, mouse and controller events
*
* @author kevin
*/
public interface InputListener extends MouseListener, KeyListener, ControllerListener {
}

View File

@@ -0,0 +1,25 @@
package org.newdawn.slick;
/**
* Describes classes capable of responding to key presses
*
* @author kevin
*/
public interface KeyListener extends ControlledInputReciever {
/**
* Notification that a key was pressed
*
* @param key The key code that was pressed (@see org.newdawn.slick.Input)
* @param c The character of the key that was pressed
*/
public abstract void keyPressed(int key, char c);
/**
* Notification that a key was released
*
* @param key The key code that was released (@see org.newdawn.slick.Input)
* @param c The character of the key that was released
*/
public abstract void keyReleased(int key, char c);
}

View File

@@ -0,0 +1,69 @@
package org.newdawn.slick;
/**
* Description of classes that respond to mouse related input events
*
* @author kevin
*/
public interface MouseListener extends ControlledInputReciever {
/**
* Notification that the mouse wheel position was updated
*
* @param change The amount of the wheel has moved
*/
public abstract void mouseWheelMoved(int change);
/**
* Notification that a mouse button was clicked. Due to double click
* handling the single click may be delayed slightly. For absolute notification
* of single clicks use mousePressed().
*
* To be absolute this method should only be used when considering double clicks
*
* @param button The index of the button (starting at 0)
* @param x The x position of the mouse when the button was pressed
* @param y The y position of the mouse when the button was pressed
* @param clickCount The number of times the button was clicked
*/
public abstract void mouseClicked(int button, int x, int y, int clickCount);
/**
* Notification that a mouse button was pressed
*
* @param button The index of the button (starting at 0)
* @param x The x position of the mouse when the button was pressed
* @param y The y position of the mouse when the button was pressed
*/
public abstract void mousePressed(int button, int x, int y);
/**
* Notification that a mouse button was released
*
* @param button The index of the button (starting at 0)
* @param x The x position of the mouse when the button was released
* @param y The y position of the mouse when the button was released
*/
public abstract void mouseReleased(int button, int x, int y);
/**
* Notification that mouse cursor was moved
*
* @param oldx The old x position of the mouse
* @param oldy The old y position of the mouse
* @param newx The new x position of the mouse
* @param newy The new y position of the mouse
*/
public abstract void mouseMoved(int oldx, int oldy, int newx, int newy);
/**
* Notification that mouse cursor was dragged
*
* @param oldx The old x position of the mouse
* @param oldy The old y position of the mouse
* @param newx The new x position of the mouse
* @param newy The new y position of the mouse
*/
public abstract void mouseDragged(int oldx, int oldy, int newx, int newy);
}

View File

@@ -0,0 +1,412 @@
package org.newdawn.slick;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import org.newdawn.slick.openal.Audio;
import org.newdawn.slick.openal.AudioImpl;
import org.newdawn.slick.openal.SoundStore;
import org.newdawn.slick.util.Log;
/**
* A piece of music loaded and playable within the game. Only one piece of music can
* play at any given time and a channel is reserved so music will always play.
*
* @author kevin
* @author Nathan Sweet <misc@n4te.com>
*/
public class Music {
/** The music currently being played or null if none */
private static Music currentMusic;
/**
* Poll the state of the current music. This causes streaming music
* to stream and checks listeners. Note that if you're using a game container
* this will be auto-magically called for you.
*
* @param delta The amount of time since last poll
*/
public static void poll(int delta) {
if (currentMusic != null) {
SoundStore.get().poll(delta);
if (!SoundStore.get().isMusicPlaying()) {
if (!currentMusic.positioning) {
Music oldMusic = currentMusic;
currentMusic = null;
oldMusic.fireMusicEnded();
}
} else {
currentMusic.update(delta);
}
}
}
/** The sound from FECK representing this music */
private Audio sound;
/** True if the music is playing */
private boolean playing;
/** The list of listeners waiting for notification that the music ended */
private ArrayList listeners = new ArrayList();
/** The volume of this music */
private float volume = 1.0f;
/** Start gain for fading in/out */
private float fadeStartGain;
/** End gain for fading in/out */
private float fadeEndGain;
/** Countdown for fading in/out */
private int fadeTime;
/** Duration for fading in/out */
private int fadeDuration;
/** True if music should be stopped after fading in/out */
private boolean stopAfterFade;
/** True if the music is being repositioned and it is therefore normal that it's not playing */
private boolean positioning;
/** The position that was requested */
private float requiredPosition = -1;
/**
* Create and load a piece of music (either OGG or MOD/XM)
*
* @param ref The location of the music
* @throws SlickException
*/
public Music(String ref) throws SlickException {
this(ref, false);
}
/**
* Create and load a piece of music (either OGG or MOD/XM)
*
* @param ref The location of the music
* @throws SlickException
*/
public Music(URL ref) throws SlickException {
this(ref, false);
}
/**
* Create and load a piece of music (either OGG or MOD/XM)
* @param in The stream to read the music from
* @param ref The symbolic name of this music
* @throws SlickException Indicates a failure to read the music from the stream
*/
public Music(InputStream in, String ref) throws SlickException {
SoundStore.get().init();
try {
if (ref.toLowerCase().endsWith(".ogg")) {
sound = SoundStore.get().getOgg(in);
} else if (ref.toLowerCase().endsWith(".wav")) {
sound = SoundStore.get().getWAV(in);
} else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) {
sound = SoundStore.get().getMOD(in);
} else if (ref.toLowerCase().endsWith(".aif") || ref.toLowerCase().endsWith(".aiff")) {
sound = SoundStore.get().getAIF(in);
} else {
throw new SlickException("Only .xm, .mod, .ogg, and .aif/f are currently supported.");
}
} catch (Exception e) {
Log.error(e);
throw new SlickException("Failed to load music: "+ref);
}
}
/**
* Create and load a piece of music (either OGG or MOD/XM)
*
* @param url The location of the music
* @param streamingHint A hint to indicate whether streaming should be used if possible
* @throws SlickException
*/
public Music(URL url, boolean streamingHint) throws SlickException {
SoundStore.get().init();
String ref = url.getFile();
try {
if (ref.toLowerCase().endsWith(".ogg")) {
if (streamingHint) {
sound = SoundStore.get().getOggStream(url);
} else {
sound = SoundStore.get().getOgg(url.openStream());
}
} else if (ref.toLowerCase().endsWith(".wav")) {
sound = SoundStore.get().getWAV(url.openStream());
} else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) {
sound = SoundStore.get().getMOD(url.openStream());
} else if (ref.toLowerCase().endsWith(".aif") || ref.toLowerCase().endsWith(".aiff")) {
sound = SoundStore.get().getAIF(url.openStream());
} else {
throw new SlickException("Only .xm, .mod, .ogg, and .aif/f are currently supported.");
}
} catch (Exception e) {
Log.error(e);
throw new SlickException("Failed to load sound: "+url);
}
}
/**
* Create and load a piece of music (either OGG or MOD/XM)
*
* @param ref The location of the music
* @param streamingHint A hint to indicate whether streaming should be used if possible
* @throws SlickException
*/
public Music(String ref, boolean streamingHint) throws SlickException {
SoundStore.get().init();
try {
if (ref.toLowerCase().endsWith(".ogg")) {
if (streamingHint) {
sound = SoundStore.get().getOggStream(ref);
} else {
sound = SoundStore.get().getOgg(ref);
}
} else if (ref.toLowerCase().endsWith(".wav")) {
sound = SoundStore.get().getWAV(ref);
} else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) {
sound = SoundStore.get().getMOD(ref);
} else if (ref.toLowerCase().endsWith(".aif") || ref.toLowerCase().endsWith(".aiff")) {
sound = SoundStore.get().getAIF(ref);
} else {
throw new SlickException("Only .xm, .mod, .ogg, and .aif/f are currently supported.");
}
} catch (Exception e) {
Log.error(e);
throw new SlickException("Failed to load sound: "+ref);
}
}
/**
* Add a listener to this music
*
* @param listener The listener to add
*/
public void addListener(MusicListener listener) {
listeners.add(listener);
}
/**
* Remove a listener from this music
*
* @param listener The listener to remove
*/
public void removeListener(MusicListener listener) {
listeners.remove(listener);
}
/**
* Fire notifications that this music ended
*/
private void fireMusicEnded() {
playing = false;
for (int i=0;i<listeners.size();i++) {
((MusicListener) listeners.get(i)).musicEnded(this);
}
}
/**
* Fire notifications that this music was swapped out
*
* @param newMusic The new music that will be played
*/
private void fireMusicSwapped(Music newMusic) {
playing = false;
for (int i=0;i<listeners.size();i++) {
((MusicListener) listeners.get(i)).musicSwapped(this, newMusic);
}
}
/**
* Loop the music
*/
public void loop() {
loop(1.0f, 1.0f);
}
/**
* Play the music
*/
public void play() {
play(1.0f, 1.0f);
}
/**
* Play the music at a given pitch and volume
*
* @param pitch The pitch to play the music at (1.0 = default)
* @param volume The volume to play the music at (1.0 = default)
*/
public void play(float pitch, float volume) {
startMusic(pitch, volume, false);
}
/**
* Loop the music at a given pitch and volume
*
* @param pitch The pitch to play the music at (1.0 = default)
* @param volume The volume to play the music at (1.0 = default)
*/
public void loop(float pitch, float volume) {
startMusic(pitch, volume, true);
}
/**
* play or loop the music at a given pitch and volume
* @param pitch The pitch to play the music at (1.0 = default)
* @param volume The volume to play the music at (1.0 = default)
* @param loop if false the music is played once, the music is looped otherwise
*/
private void startMusic(float pitch, float volume, boolean loop) {
if (currentMusic != null) {
currentMusic.stop();
currentMusic.fireMusicSwapped(this);
}
currentMusic = this;
if (volume < 0.0f)
volume = 0.0f;
if (volume > 1.0f)
volume = 1.0f;
sound.playAsMusic(pitch, volume, loop);
playing = true;
setVolume(volume);
if (requiredPosition != -1) {
setPosition(requiredPosition);
}
}
/**
* Pause the music playback
*/
public void pause() {
playing = false;
AudioImpl.pauseMusic();
}
/**
* Stop the music playing
*/
public void stop() {
sound.stop();
}
/**
* Resume the music playback
*/
public void resume() {
playing = true;
AudioImpl.restartMusic();
}
/**
* Check if the music is being played
*
* @return True if the music is being played
*/
public boolean playing() {
return (currentMusic == this) && (playing);
}
/**
* Set the volume of the music as a factor of the global volume setting
*
* @param volume The volume to play music at. 0 - 1, 1 is Max
*/
public void setVolume(float volume) {
// Bounds check
if(volume > 1) {
volume = 1;
} else if(volume < 0) {
volume = 0;
}
this.volume = volume;
// This sound is being played as music
if (currentMusic == this) {
SoundStore.get().setCurrentMusicVolume(volume);
}
}
/**
* Get the individual volume of the music
* @return The volume of this music, still effected by global SoundStore volume. 0 - 1, 1 is Max
*/
public float getVolume() {
return volume;
}
/**
* Fade this music to the volume specified
*
* @param duration Fade time in milliseconds.
* @param endVolume The target volume
* @param stopAfterFade True if music should be stopped after fading in/out
*/
public void fade (int duration, float endVolume, boolean stopAfterFade) {
this.stopAfterFade = stopAfterFade;
fadeStartGain = volume;
fadeEndGain = endVolume;
fadeDuration = duration;
fadeTime = duration;
}
/**
* Update the current music applying any effects that need to updated per
* tick.
*
* @param delta The amount of time in milliseconds thats passed since last update
*/
void update(int delta) {
if (!playing) {
return;
}
if (fadeTime > 0) {
fadeTime -= delta;
if (fadeTime < 0) {
fadeTime = 0;
if (stopAfterFade) {
stop();
return;
}
}
float offset = (fadeEndGain - fadeStartGain) * (1 - (fadeTime / (float)fadeDuration));
setVolume(fadeStartGain + offset);
}
}
/**
* Seeks to a position in the music. For streaming music, seeking before the current position causes
* the stream to be reloaded.
*
* @param position Position in seconds.
* @return True if the seek was successful
*/
public boolean setPosition(float position) {
if (playing) {
requiredPosition = -1;
positioning = true;
playing = false;
boolean result = sound.setPosition(position);
playing = true;
positioning = false;
return result;
} else {
requiredPosition = position;
return false;
}
}
/**
* The position into the sound thats being played
*
* @return The current position in seconds.
*/
public float getPosition () {
return sound.getPosition();
}
}

View File

@@ -0,0 +1,26 @@
package org.newdawn.slick;
/**
* The description of any class needing to recieve notification of changes
* to music state.
*
* @author kevin
*/
public interface MusicListener {
/**
* Notification that a piece of music finished playing
*
* @param music The music that finished playing
*/
public void musicEnded(Music music);
/**
* Notification that a piece of music has been swapped
* for another.
*
* @param music The music that has been swapped out
* @param newMusic The new music we're playing
*/
public void musicSwapped(Music music, Music newMusic);
}

View File

@@ -0,0 +1,193 @@
package org.newdawn.slick;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
/**
* A sprite sheet packed and defined by the Pacific Software Image Packer available
* from:
*
* http://homepage.ntlworld.com/config/imagepacker/
*
* @author kevin
*/
public class PackedSpriteSheet {
/** The image loaded for the sheet */
private Image image;
/** The base path where the image is expected to be found based on the original definition file */
private String basePath;
/** The section definitions */
private HashMap sections = new HashMap();
/** The filter used when loading the image */
private int filter = Image.FILTER_NEAREST;
/**
* Create a new packed sprite sheet based on a ImagePacker definition file
*
* @param def The location of the definition file to read
* @throws SlickException Indicates a failure to read the definition file
*/
public PackedSpriteSheet(String def) throws SlickException {
this(def, null);
}
/**
* Create a new packed sprite sheet based on a ImagePacker definition file
*
* @param def The location of the definition file to read
* @param trans The color to be treated as transparent
* @throws SlickException Indicates a failure to read the definition file
*/
public PackedSpriteSheet(String def, Color trans) throws SlickException {
def = def.replace('\\', '/');
basePath = def.substring(0,def.lastIndexOf("/")+1);
loadDefinition(def, trans);
}
/**
* Create a new packed sprite sheet based on a ImagePacker definition file
*
* @param def The location of the definition file to read
* @param filter The image filter to use when loading the packed sprite image
* @throws SlickException Indicates a failure to read the definition file
*/
public PackedSpriteSheet(String def, int filter) throws SlickException {
this(def, filter, null);
}
/**
* Create a new packed sprite sheet based on a ImagePacker definition file
*
* @param def The location of the definition file to read
* @param filter The image filter to use when loading the packed sprite image
* @param trans The color to be treated as transparent
* @throws SlickException Indicates a failure to read the definition file
*/
public PackedSpriteSheet(String def, int filter, Color trans) throws SlickException {
this.filter = filter;
def = def.replace('\\', '/');
basePath = def.substring(0,def.lastIndexOf("/")+1);
loadDefinition(def, trans);
}
/**
* Get the full image contaning all the sprites/sections
*
* @return The full image containing all the sprites/sections
*/
public Image getFullImage() {
return image;
}
/**
* Get a single named sprite from the sheet
*
* @param name The name of the sprite to retrieve
* @return The sprite requested (image of)
*/
public Image getSprite(String name) {
Section section = (Section) sections.get(name);
if (section == null) {
throw new RuntimeException("Unknown sprite from packed sheet: "+name);
}
return image.getSubImage(section.x, section.y, section.width, section.height);
}
/**
* Get a sprite sheet that has been packed into the greater image
*
* @param name The name of the sprite sheet to retrieve
* @return The sprite sheet from the packed sheet
*/
public SpriteSheet getSpriteSheet(String name) {
Image image = getSprite(name);
Section section = (Section) sections.get(name);
return new SpriteSheet(image, section.width / section.tilesx, section.height / section.tilesy);
}
/**
* Load the definition file and parse each of the sections
*
* @param def The location of the definitions file
* @param trans The color to be treated as transparent
* @throws SlickException Indicates a failure to read or parse the definitions file
* or referenced image.
*/
private void loadDefinition(String def, Color trans) throws SlickException {
BufferedReader reader = new BufferedReader(new InputStreamReader(ResourceLoader.getResourceAsStream(def)));
try {
image = new Image(basePath+reader.readLine(), false, filter, trans);
while (reader.ready()) {
if (reader.readLine() == null) {
break;
}
Section sect = new Section(reader);
sections.put(sect.name, sect);
if (reader.readLine() == null) {
break;
}
}
} catch (Exception e) {
Log.error(e);
throw new SlickException("Failed to process definitions file - invalid format?", e);
}
}
/**
* A single section defined within the packed sheet
*
* @author kevin
*/
private class Section {
/** The x position of the section */
public int x;
/** The y position of the section */
public int y;
/** The width of the section */
public int width;
/** The height of the section */
public int height;
/** The number of sprites across this section */
public int tilesx;
/** The number of sprites down this section */
public int tilesy;
/** The name of this section */
public String name;
/**
* Create a new section by reading the stream provided
*
* @param reader The reader from which the definition can be read
* @throws IOException Indicates a failure toread the provided stream
*/
public Section(BufferedReader reader) throws IOException {
name = reader.readLine().trim();
x = Integer.parseInt(reader.readLine().trim());
y = Integer.parseInt(reader.readLine().trim());
width = Integer.parseInt(reader.readLine().trim());
height = Integer.parseInt(reader.readLine().trim());
tilesx = Integer.parseInt(reader.readLine().trim());
tilesy = Integer.parseInt(reader.readLine().trim());
reader.readLine().trim();
reader.readLine().trim();
tilesx = Math.max(1,tilesx);
tilesy = Math.max(1,tilesy);
}
}
}

View File

@@ -0,0 +1,18 @@
package org.newdawn.slick;
/**
* Description of anything that can be drawn
*
* @author kevin
*/
public interface Renderable {
/**
* Draw this artefact at the given location
*
* @param x The x coordinate to draw the artefact at
* @param y The y coordinate to draw the artefact at
*/
public void draw(float x, float y);
}

View File

@@ -0,0 +1,176 @@
package org.newdawn.slick;
import java.io.IOException;
import java.util.HashMap;
import javax.jnlp.ServiceManager;
import org.newdawn.slick.muffin.FileMuffin;
import org.newdawn.slick.muffin.Muffin;
import org.newdawn.slick.muffin.WebstartMuffin;
import org.newdawn.slick.util.Log;
/**
* A utility to allow game setup/state to be stored locally. This utility will adapt to the
* current enviornment (webstart or file based). Note that this will not currently
* work in an applet.
*
* @author kappaOne
*/
public class SavedState {
/** file name of where the scores will be saved */
private String fileName;
/** Type of Muffin to use */
private Muffin muffin;
/** hash map where int data will be stored */
private HashMap numericData = new HashMap();
/** hash map where string data will be stored */
private HashMap stringData = new HashMap();
/**
* Create and Test to see if the app is running
* as webstart or local app and select the appropriate
* muffin type
*
* @param fileName name of muffin where data will be saved
* @throws SlickException Indicates a failure to load the stored state
*/
public SavedState(String fileName) throws SlickException {
this.fileName = fileName;
if (isWebstartAvailable()) {
muffin = new WebstartMuffin();
}
else {
muffin = new FileMuffin();
}
try {
load();
} catch (IOException e) {
throw new SlickException("Failed to load state on startup",e);
}
}
/**
* Get number stored at given location
*
* @param nameOfField The name of the number to retrieve
* @return The number saved at this location
*/
public double getNumber(String nameOfField) {
return getNumber(nameOfField, 0);
}
/**
* Get number stored at given location
*
* @param nameOfField The name of the number to retrieve
* @param defaultValue The value to return if the specified value hasn't been set
* @return The number saved at this location
*/
public double getNumber(String nameOfField, double defaultValue) {
Double value = ((Double)numericData.get(nameOfField));
if (value == null) {
return defaultValue;
}
return value.doubleValue();
}
/**
* Save the given value at the given location
* will overwrite any previous value at this location
*
* @param nameOfField The name to store the value against
* @param value The value to store
*/
public void setNumber(String nameOfField, double value){
numericData.put(nameOfField, new Double(value));
}
/**
* Get the String at the given location
*
* @param nameOfField location of string
* @return String stored at the location given
*/
public String getString(String nameOfField) {
return getString(nameOfField, null);
}
/**
* Get the String at the given location
*
* @param nameOfField location of string
* @param defaultValue The value to return if the specified value hasn't been set
* @return String stored at the location given
*/
public String getString(String nameOfField, String defaultValue) {
String value = (String) stringData.get(nameOfField);
if (value == null) {
return defaultValue;
}
return value;
}
/**
* Save the given value at the given location
* will overwrite any previous value at this location
*
* @param nameOfField location to store int
* @param value The value to store
*/
public void setString(String nameOfField, String value){
stringData.put(nameOfField, value);
}
/**
* Save the stored data to file/muffin
*
* @throws IOException Indicates it wasn't possible to store the state
*/
public void save() throws IOException {
muffin.saveFile(numericData, fileName + "_Number");
muffin.saveFile(stringData, fileName + "_String");
}
/**
* Load the data from file/muffin
*
* @throws IOException Indicates it wasn't possible to load the state
*/
public void load() throws IOException {
numericData = muffin.loadFile(fileName + "_Number");
stringData = muffin.loadFile(fileName + "_String");
}
/**
* Will delete all current data held in Score
*/
public void clear() {
numericData.clear();
stringData.clear();
}
/**
* Quick test to see if running through Java webstart
*
* @return True if jws running
*/
private boolean isWebstartAvailable() {
try {
Class.forName("javax.jnlp.ServiceManager");
// this causes to go and see if the service is available
ServiceManager.lookup("javax.jnlp.PersistenceService");
Log.info("Webstart detected using Muffins");
} catch (Exception e) {
Log.info("Using Local File System");
return false;
}
return true;
}
}

View File

@@ -0,0 +1,188 @@
package org.newdawn.slick;
import org.newdawn.slick.opengl.SlickCallable;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
/**
* 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.
*
* 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);
}
/**
* 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();
}
}

View File

@@ -0,0 +1,33 @@
package org.newdawn.slick;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Vector2f;
/**
* A filling method for a shape. This allows changing colours at shape verticies and
* modify they're positions as required
*
* @author kevin
*/
public interface ShapeFill {
/**
* Get the colour that should be applied at the specified location
*
* @param shape The shape being filled
* @param x The x coordinate of the point being coloured
* @param y The y coordinate of the point being coloured
* @return The colour that should be applied based on the control points of this gradient
*/
public Color colorAt(Shape shape, float x, float y);
/**
* Get the offset for a vertex at a given location based on it's shape
*
* @param shape The shape being filled
* @param x The x coordinate of the point being drawn
* @param y The y coordinate of the point being drawn
* @return The offset to apply to this vertex
*/
public Vector2f getOffsetAt(Shape shape, float x, float y);
}

View File

@@ -0,0 +1,27 @@
package org.newdawn.slick;
/**
* A generic exception thrown by everything in the library
*
* @author kevin
*/
public class SlickException extends Exception {
/**
* Create a new exception with a detail message
*
* @param message The message describing the cause of this exception
*/
public SlickException(String message) {
super(message);
}
/**
* Create a new exception with a detail message
*
* @param message The message describing the cause of this exception
* @param e The exception causing this exception to be thrown
*/
public SlickException(String message, Throwable e) {
super(message, e);
}
}

View File

@@ -0,0 +1,175 @@
package org.newdawn.slick;
import java.io.InputStream;
import java.net.URL;
import org.newdawn.slick.openal.Audio;
import org.newdawn.slick.openal.SoundStore;
import org.newdawn.slick.util.Log;
/**
* A single sound effect loaded from either OGG or XM/MOD file. Sounds are allocated to
* channels dynamically - if not channel is available the sound will not play.
*
* @author kevin
*/
public class Sound {
/** The internal sound effect represent this sound */
private Audio sound;
/**
* Create a new Sound
*
* @param in The location of the OGG or MOD/XM to load
* @param ref The name to associate this stream
* @throws SlickException Indicates a failure to load the sound effect
*/
public Sound(InputStream in, String ref) throws SlickException {
SoundStore.get().init();
try {
if (ref.toLowerCase().endsWith(".ogg")) {
sound = SoundStore.get().getOgg(in);
} else if (ref.toLowerCase().endsWith(".wav")) {
sound = SoundStore.get().getWAV(in);
} else if (ref.toLowerCase().endsWith(".aif")) {
sound = SoundStore.get().getAIF(in);
} else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) {
sound = SoundStore.get().getMOD(in);
} else {
throw new SlickException("Only .xm, .mod, .aif, .wav and .ogg are currently supported.");
}
} catch (Exception e) {
Log.error(e);
throw new SlickException("Failed to load sound: "+ref);
}
}
/**
* Create a new Sound
*
* @param url The location of the OGG or MOD/XM to load
* @throws SlickException Indicates a failure to load the sound effect
*/
public Sound(URL url) throws SlickException {
SoundStore.get().init();
String ref = url.getFile();
try {
if (ref.toLowerCase().endsWith(".ogg")) {
sound = SoundStore.get().getOgg(url.openStream());
} else if (ref.toLowerCase().endsWith(".wav")) {
sound = SoundStore.get().getWAV(url.openStream());
} else if (ref.toLowerCase().endsWith(".aif")) {
sound = SoundStore.get().getAIF(url.openStream());
} else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) {
sound = SoundStore.get().getMOD(url.openStream());
} else {
throw new SlickException("Only .xm, .mod, .aif, .wav and .ogg are currently supported.");
}
} catch (Exception e) {
Log.error(e);
throw new SlickException("Failed to load sound: "+ref);
}
}
/**
* Create a new Sound
*
* @param ref The location of the OGG or MOD/XM to load
* @throws SlickException Indicates a failure to load the sound effect
*/
public Sound(String ref) throws SlickException {
SoundStore.get().init();
try {
if (ref.toLowerCase().endsWith(".ogg")) {
sound = SoundStore.get().getOgg(ref);
} else if (ref.toLowerCase().endsWith(".wav")) {
sound = SoundStore.get().getWAV(ref);
} else if (ref.toLowerCase().endsWith(".aif")) {
sound = SoundStore.get().getAIF(ref);
} else if (ref.toLowerCase().endsWith(".xm") || ref.toLowerCase().endsWith(".mod")) {
sound = SoundStore.get().getMOD(ref);
} else {
throw new SlickException("Only .xm, .mod, .aif, .wav and .ogg are currently supported.");
}
} catch (Exception e) {
Log.error(e);
throw new SlickException("Failed to load sound: "+ref);
}
}
/**
* Play this sound effect at default volume and pitch
*/
public void play() {
play(1.0f, 1.0f);
}
/**
* Play this sound effect at a given volume and pitch
*
* @param pitch The pitch to play the sound effect at
* @param volume The volumen to play the sound effect at
*/
public void play(float pitch, float volume) {
sound.playAsSoundEffect(pitch, volume * SoundStore.get().getSoundVolume(), false);
}
/**
* Play a sound effect from a particular location
*
* @param x The x position of the source of the effect
* @param y The y position of the source of the effect
* @param z The z position of the source of the effect
*/
public void playAt(float x, float y, float z) {
playAt(1.0f, 1.0f, x,y,z);
}
/**
* Play a sound effect from a particular location
*
* @param pitch The pitch to play the sound effect at
* @param volume The volumen to play the sound effect at
* @param x The x position of the source of the effect
* @param y The y position of the source of the effect
* @param z The z position of the source of the effect
*/
public void playAt(float pitch, float volume, float x, float y, float z) {
sound.playAsSoundEffect(pitch, volume * SoundStore.get().getSoundVolume(), false, x,y,z);
}
/**
* Loop this sound effect at default volume and pitch
*/
public void loop() {
loop(1.0f, 1.0f);
}
/**
* Loop this sound effect at a given volume and pitch
*
* @param pitch The pitch to play the sound effect at
* @param volume The volumen to play the sound effect at
*/
public void loop(float pitch, float volume) {
sound.playAsSoundEffect(pitch, volume * SoundStore.get().getSoundVolume(), true);
}
/**
* Check if the sound is currently playing
*
* @return True if the sound is playing
*/
public boolean playing() {
return sound.isPlaying();
}
/**
* Stop the sound being played
*/
public void stop() {
sound.stop();
}
}

View File

@@ -0,0 +1,303 @@
package org.newdawn.slick;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.newdawn.slick.opengl.Texture;
/**
* A sheet of sprites that can be drawn individually
*
* @author Kevin Glass
*/
public class SpriteSheet extends Image {
/** The width of a single element in pixels */
private int tw;
/** The height of a single element in pixels */
private int th;
/** The margin of the image */
private int margin = 0;
/** Subimages */
private Image[][] subImages;
/** The spacing between tiles */
private int spacing;
/** The target image for this sheet */
private Image target;
/**
* Create a new sprite sheet based on a image location
*
* @param ref The URL to the image to use
* @param tw The width of the tiles on the sheet
* @param th The height of the tiles on the sheet
* @throws SlickException Indicates a failure to read image data
* @throws IOException Indicates the URL could not be opened
*/
public SpriteSheet(URL ref,int tw,int th) throws SlickException, IOException {
this(new Image(ref.openStream(), ref.toString(), false), tw, th);
}
/**
* Create a new sprite sheet based on a image location
*
* @param image The image to based the sheet of
* @param tw The width of the tiles on the sheet
* @param th The height of the tiles on the sheet
*/
public SpriteSheet(Image image,int tw,int th) {
super(image);
this.target = image;
this.tw = tw;
this.th = th;
// call init manually since constructing from an image will have previously initialised
// from incorrect values
initImpl();
}
/**
* Create a new sprite sheet based on a image location
*
* @param image The image to based the sheet of
* @param tw The width of the tiles on the sheet
* @param th The height of the tiles on the sheet
* @param spacing The spacing between tiles
* @param margin The magrin around the tiles
*/
public SpriteSheet(Image image,int tw,int th,int spacing,int margin) {
super(image);
this.target = image;
this.tw = tw;
this.th = th;
this.spacing = spacing;
this.margin = margin;
// call init manually since constructing from an image will have previously initialised
// from incorrect values
initImpl();
}
/**
* Create a new sprite sheet based on a image location
*
* @param image The image to based the sheet of
* @param tw The width of the tiles on the sheet
* @param th The height of the tiles on the sheet
* @param spacing The spacing between tiles
*/
public SpriteSheet(Image image,int tw,int th,int spacing) {
this(image,tw,th,spacing,0);
}
/**
* Create a new sprite sheet based on a image location
*
* @param ref The location of the sprite sheet to load
* @param tw The width of the tiles on the sheet
* @param th The height of the tiles on the sheet
* @param spacing The spacing between tiles
* @throws SlickException Indicates a failure to load the image
*/
public SpriteSheet(String ref,int tw,int th, int spacing) throws SlickException {
this(ref,tw,th,null,spacing);
}
/**
* Create a new sprite sheet based on a image location
*
* @param ref The location of the sprite sheet to load
* @param tw The width of the tiles on the sheet
* @param th The height of the tiles on the sheet
* @throws SlickException Indicates a failure to load the image
*/
public SpriteSheet(String ref,int tw,int th) throws SlickException {
this(ref,tw,th,null);
}
/**
* Create a new sprite sheet based on a image location
*
* @param ref The location of the sprite sheet to load
* @param tw The width of the tiles on the sheet
* @param th The height of the tiles on the sheet
* @param col The colour to treat as transparent
* @throws SlickException Indicates a failure to load the image
*/
public SpriteSheet(String ref,int tw,int th, Color col) throws SlickException {
this(ref, tw, th, col, 0);
}
/**
* Create a new sprite sheet based on a image location
*
* @param ref The location of the sprite sheet to load
* @param tw The width of the tiles on the sheet
* @param th The height of the tiles on the sheet
* @param col The colour to treat as transparent
* @param spacing The spacing between tiles
* @throws SlickException Indicates a failure to load the image
*/
public SpriteSheet(String ref,int tw,int th, Color col, int spacing) throws SlickException {
super(ref, false, FILTER_NEAREST, col);
this.target = this;
this.tw = tw;
this.th = th;
this.spacing = spacing;
}
/**
* Create a new sprite sheet based on a image location
*
* @param name The name to give to the image in the image cache
* @param ref The stream from which we can load the image
* @param tw The width of the tiles on the sheet
* @param th The height of the tiles on the sheet
* @throws SlickException Indicates a failure to load the image
*/
public SpriteSheet(String name, InputStream ref,int tw,int th) throws SlickException {
super(ref,name,false);
this.target = this;
this.tw = tw;
this.th = th;
}
/**
* @see org.newdawn.slick.Image#initImpl()
*/
protected void initImpl() {
if (subImages != null) {
return;
}
int tilesAcross = ((getWidth()-(margin*2) - tw) / (tw + spacing)) + 1;
int tilesDown = ((getHeight()-(margin*2) - th) / (th + spacing)) + 1;
if ((getHeight() - th) % (th+spacing) != 0) {
tilesDown++;
}
subImages = new Image[tilesAcross][tilesDown];
for (int x=0;x<tilesAcross;x++) {
for (int y=0;y<tilesDown;y++) {
subImages[x][y] = getSprite(x,y);
}
}
}
/**
* Get the sub image cached in this sprite sheet
*
* @param x The x position in tiles of the image to get
* @param y The y position in tiles of the image to get
* @return The subimage at that location on the sheet
*/
public Image getSubImage(int x, int y) {
init();
if ((x < 0) || (x >= subImages.length)) {
throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y);
}
if ((y < 0) || (y >= subImages[0].length)) {
throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y);
}
return subImages[x][y];
}
/**
* Get a sprite at a particular cell on the sprite sheet
*
* @param x The x position of the cell on the sprite sheet
* @param y The y position of the cell on the sprite sheet
* @return The single image from the sprite sheet
*/
public Image getSprite(int x, int y) {
target.init();
initImpl();
if ((x < 0) || (x >= subImages.length)) {
throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y);
}
if ((y < 0) || (y >= subImages[0].length)) {
throw new RuntimeException("SubImage out of sheet bounds: "+x+","+y);
}
return target.getSubImage(x*(tw+spacing) + margin, y*(th+spacing) + margin,tw,th);
}
/**
* Get the number of sprites across the sheet
*
* @return The number of sprites across the sheet
*/
public int getHorizontalCount() {
target.init();
initImpl();
return subImages.length;
}
/**
* Get the number of sprites down the sheet
*
* @return The number of sprite down the sheet
*/
public int getVerticalCount() {
target.init();
initImpl();
return subImages[0].length;
}
/**
* Render a sprite when this sprite sheet is in use.
*
* @see #startUse()
* @see #endUse()
*
* @param x The x position to render the sprite at
* @param y The y position to render the sprite at
* @param sx The x location of the cell to render
* @param sy The y location of the cell to render
*/
public void renderInUse(int x,int y,int sx,int sy) {
subImages[sx][sy].drawEmbedded(x, y, tw, th);
}
/**
* @see org.newdawn.slick.Image#endUse()
*/
public void endUse() {
if (target == this) {
super.endUse();
return;
}
target.endUse();
}
/**
* @see org.newdawn.slick.Image#startUse()
*/
public void startUse() {
if (target == this) {
super.startUse();
return;
}
target.startUse();
}
/**
* @see org.newdawn.slick.Image#setTexture(org.newdawn.slick.opengl.Texture)
*/
public void setTexture(Texture texture) {
if (target == this) {
super.setTexture(texture);
return;
}
target.setTexture(texture);
}
}

View File

@@ -0,0 +1,111 @@
package org.newdawn.slick;
import java.io.UnsupportedEncodingException;
import org.newdawn.slick.util.Log;
/**
* A font implementation that will use the graphics inside a SpriteSheet for its data.
* This is useful when your font has a fixed width and height for each character as
* opposed to the more complex AngelCodeFont that allows different sizes and kerning
* for each character.
*
* @author Onno Scheffers
*/
public class SpriteSheetFont implements Font {
/** The SpriteSheet containing the bitmap font */
private SpriteSheet font;
/** First character in the SpriteSheet */
private char startingCharacter;
/** Width of each character in pixels */
private int charWidth;
/** Height of each character in pixels */
private int charHeight;
/** Number of characters in SpriteSheet horizontally */
private int horizontalCount;
/** Total number of characters in SpriteSheet */
private int numChars;
/**
* Create a new font based on a SpriteSheet. The SpriteSheet should hold your
* fixed-width character set in ASCII order. To only get upper-case characters
* working you would usually set up a SpriteSheet with characters for these values:
* <pre>
* !"#$%&'()*+,-./
* 0123456789:;<=>?
* &#0064;ABCDEFGHIJKLMNO
* PQRSTUVWXYZ[\]^_<pre>
* In this set, ' ' (SPACE) would be the startingCharacter of your characterSet.
*
* @param font The SpriteSheet holding the font data.
* @param startingCharacter The first character that is defined in the SpriteSheet.
*/
public SpriteSheetFont(SpriteSheet font, char startingCharacter) {
this.font = font;
this.startingCharacter = startingCharacter;
horizontalCount = font.getHorizontalCount();
int verticalCount = font.getVerticalCount();
charWidth = font.getWidth() / horizontalCount;
charHeight = font.getHeight() / verticalCount;
numChars = horizontalCount * verticalCount;
}
/**
* @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) {
try {
byte[] data = text.getBytes("US-ASCII");
for (int i = 0; i < data.length; i++) {
int index = data[i] - startingCharacter;
if (index < numChars) {
int xPos = (index % horizontalCount);
int yPos = (index / horizontalCount);
if ((i >= startIndex) || (i <= endIndex)) {
font.getSprite(xPos, yPos)
.draw(x + (i * charWidth), y, col);
}
}
}
} catch (UnsupportedEncodingException e) {
// Should never happen, ASCII is supported pretty much anywhere
Log.error(e);
}
}
/**
* @see org.newdawn.slick.Font#getHeight(java.lang.String)
*/
public int getHeight(String text) {
return charHeight;
}
/**
* @see org.newdawn.slick.Font#getWidth(java.lang.String)
*/
public int getWidth(String text) {
return charWidth * text.length();
}
/**
* @see org.newdawn.slick.Font#getLineHeight()
*/
public int getLineHeight() {
return charHeight;
}
}

View File

@@ -0,0 +1,409 @@
package org.newdawn.slick;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.newdawn.slick.opengl.GLUtils;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
import org.newdawn.slick.util.BufferedImageUtil;
/**
* A TrueType font implementation for Slick
*
* @author James Chambers (Jimmy)
* @author Jeremy Adams (elias4444)
* @author Kevin Glass (kevglass)
* @author Peter Korzuszek (genail)
*/
public class TrueTypeFont implements org.newdawn.slick.Font {
/** The renderer to use for all GL operations */
private static final SGL GL = Renderer.get();
/** Array that holds necessary information about the font characters */
private IntObject[] charArray = new IntObject[256];
/** Map of user defined font characters (Character <-> IntObject) */
private Map customChars = new HashMap();
/** Boolean flag on whether AntiAliasing is enabled or not */
private boolean antiAlias;
/** Font's size */
private int fontSize = 0;
/** Font's height */
private int fontHeight = 0;
/** Texture used to cache the font 0-255 characters */
private Texture fontTexture;
/** Default font texture width */
private int textureWidth = 512;
/** Default font texture height */
private int textureHeight = 512;
/** A reference to Java's AWT Font that we create our font texture from */
private java.awt.Font font;
/** The font metrics for our Java AWT font */
private FontMetrics fontMetrics;
/**
* This is a special internal class that holds our necessary information for
* the font characters. This includes width, height, and where the character
* is stored on the font texture.
*/
private class IntObject {
/** Character's width */
public int width;
/** Character's height */
public int height;
/** Character's stored x position */
public int storedX;
/** Character's stored y position */
public int storedY;
}
/**
* Constructor for the TrueTypeFont class Pass in the preloaded standard
* Java TrueType font, and whether you want it to be cached with
* AntiAliasing applied.
*
* @param font
* Standard Java AWT font
* @param antiAlias
* Whether or not to apply AntiAliasing to the cached font
* @param additionalChars
* Characters of font that will be used in addition of first 256 (by unicode).
*/
public TrueTypeFont(java.awt.Font font, boolean antiAlias, char[] additionalChars) {
GLUtils.checkGLContext();
this.font = font;
this.fontSize = font.getSize();
this.antiAlias = antiAlias;
createSet( additionalChars );
}
/**
* Constructor for the TrueTypeFont class Pass in the preloaded standard
* Java TrueType font, and whether you want it to be cached with
* AntiAliasing applied.
*
* @param font
* Standard Java AWT font
* @param antiAlias
* Whether or not to apply AntiAliasing to the cached font
*/
public TrueTypeFont(java.awt.Font font, boolean antiAlias) {
this( font, antiAlias, null );
}
/**
* Create a standard Java2D BufferedImage of the given character
*
* @param ch
* The character to create a BufferedImage for
*
* @return A BufferedImage containing the character
*/
private BufferedImage getFontImage(char ch) {
// Create a temporary image to extract the character's size
BufferedImage tempfontImage = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) tempfontImage.getGraphics();
if (antiAlias == true) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
g.setFont(font);
fontMetrics = g.getFontMetrics();
int charwidth = fontMetrics.charWidth(ch);
if (charwidth <= 0) {
charwidth = 1;
}
int charheight = fontMetrics.getHeight();
if (charheight <= 0) {
charheight = fontSize;
}
// Create another image holding the character we are creating
BufferedImage fontImage;
fontImage = new BufferedImage(charwidth, charheight,
BufferedImage.TYPE_INT_ARGB);
Graphics2D gt = (Graphics2D) fontImage.getGraphics();
if (antiAlias == true) {
gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
gt.setFont(font);
gt.setColor(Color.WHITE);
int charx = 0;
int chary = 0;
gt.drawString(String.valueOf(ch), (charx), (chary)
+ fontMetrics.getAscent());
return fontImage;
}
/**
* Create and store the font
*
* @param customCharsArray Characters that should be also added to the cache.
*/
private void createSet( char[] customCharsArray ) {
// If there are custom chars then I expand the font texture twice
if (customCharsArray != null && customCharsArray.length > 0) {
textureWidth *= 2;
}
// In any case this should be done in other way. Texture with size 512x512
// can maintain only 256 characters with resolution of 32x32. The texture
// size should be calculated dynamicaly by looking at character sizes.
try {
BufferedImage imgTemp = new BufferedImage(textureWidth, textureHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) imgTemp.getGraphics();
g.setColor(new Color(255,255,255,1));
g.fillRect(0,0,textureWidth,textureHeight);
int rowHeight = 0;
int positionX = 0;
int positionY = 0;
int customCharsLength = ( customCharsArray != null ) ? customCharsArray.length : 0;
for (int i = 0; i < 256 + customCharsLength; i++) {
// get 0-255 characters and then custom characters
char ch = ( i < 256 ) ? (char) i : customCharsArray[i-256];
BufferedImage fontImage = getFontImage(ch);
IntObject newIntObject = new IntObject();
newIntObject.width = fontImage.getWidth();
newIntObject.height = fontImage.getHeight();
if (positionX + newIntObject.width >= textureWidth) {
positionX = 0;
positionY += rowHeight;
rowHeight = 0;
}
newIntObject.storedX = positionX;
newIntObject.storedY = positionY;
if (newIntObject.height > fontHeight) {
fontHeight = newIntObject.height;
}
if (newIntObject.height > rowHeight) {
rowHeight = newIntObject.height;
}
// Draw it here
g.drawImage(fontImage, positionX, positionY, null);
positionX += newIntObject.width;
if( i < 256 ) { // standard characters
charArray[i] = newIntObject;
} else { // custom characters
customChars.put( new Character( ch ), newIntObject );
}
fontImage = null;
}
fontTexture = BufferedImageUtil
.getTexture(font.toString(), imgTemp);
} catch (IOException e) {
System.err.println("Failed to create font.");
e.printStackTrace();
}
}
/**
* Draw a textured quad
*
* @param drawX
* The left x position to draw to
* @param drawY
* The top y position to draw to
* @param drawX2
* The right x position to draw to
* @param drawY2
* The bottom y position to draw to
* @param srcX
* The left source x position to draw from
* @param srcY
* The top source y position to draw from
* @param srcX2
* The right source x position to draw from
* @param srcY2
* The bottom source y position to draw from
*/
private void drawQuad(float drawX, float drawY, float drawX2, float drawY2,
float srcX, float srcY, float srcX2, float srcY2) {
float DrawWidth = drawX2 - drawX;
float DrawHeight = drawY2 - drawY;
float TextureSrcX = srcX / textureWidth;
float TextureSrcY = srcY / textureHeight;
float SrcWidth = srcX2 - srcX;
float SrcHeight = srcY2 - srcY;
float RenderWidth = (SrcWidth / textureWidth);
float RenderHeight = (SrcHeight / textureHeight);
GL.glTexCoord2f(TextureSrcX, TextureSrcY);
GL.glVertex2f(drawX, drawY);
GL.glTexCoord2f(TextureSrcX, TextureSrcY + RenderHeight);
GL.glVertex2f(drawX, drawY + DrawHeight);
GL.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY + RenderHeight);
GL.glVertex2f(drawX + DrawWidth, drawY + DrawHeight);
GL.glTexCoord2f(TextureSrcX + RenderWidth, TextureSrcY);
GL.glVertex2f(drawX + DrawWidth, drawY);
}
/**
* Get the width of a given String
*
* @param whatchars
* The characters to get the width of
*
* @return The width of the characters
*/
public int getWidth(String whatchars) {
int totalwidth = 0;
IntObject intObject = null;
int currentChar = 0;
for (int i = 0; i < whatchars.length(); i++) {
currentChar = whatchars.charAt(i);
if (currentChar < 256) {
intObject = charArray[currentChar];
} else {
intObject = (IntObject)customChars.get( new Character( (char) currentChar ) );
}
if( intObject != null )
totalwidth += intObject.width;
}
return totalwidth;
}
/**
* Get the font's height
*
* @return The height of the font
*/
public int getHeight() {
return fontHeight;
}
/**
* Get the height of a String
*
* @return The height of a given string
*/
public int getHeight(String HeightString) {
return fontHeight;
}
/**
* Get the font's line height
*
* @return The line height of the font
*/
public int getLineHeight() {
return fontHeight;
}
/**
* Draw a string
*
* @param x
* The x position to draw the string
* @param y
* The y position to draw the string
* @param whatchars
* The string to draw
* @param color
* The color to draw the text
*/
public void drawString(float x, float y, String whatchars,
org.newdawn.slick.Color color) {
drawString(x,y,whatchars,color,0,whatchars.length()-1);
}
/**
* @see Font#drawString(float, float, String, org.newdawn.slick.Color, int, int)
*/
public void drawString(float x, float y, String whatchars,
org.newdawn.slick.Color color, int startIndex, int endIndex) {
color.bind();
fontTexture.bind();
IntObject intObject = null;
int charCurrent;
GL.glBegin(SGL.GL_QUADS);
int totalwidth = 0;
for (int i = 0; i < whatchars.length(); i++) {
charCurrent = whatchars.charAt(i);
if (charCurrent < 256) {
intObject = charArray[charCurrent];
} else {
intObject = (IntObject)customChars.get( new Character( (char) charCurrent ) );
}
if( intObject != null ) {
if ((i >= startIndex) || (i <= endIndex)) {
drawQuad((x + totalwidth), y,
(x + totalwidth + intObject.width),
(y + intObject.height), intObject.storedX,
intObject.storedY, intObject.storedX + intObject.width,
intObject.storedY + intObject.height);
}
totalwidth += intObject.width;
}
}
GL.glEnd();
}
/**
* Draw a string
*
* @param x
* The x position to draw the string
* @param y
* The y position to draw the string
* @param whatchars
* The string to draw
*/
public void drawString(float x, float y, String whatchars) {
drawString(x, y, whatchars, org.newdawn.slick.Color.white);
}
}

View File

@@ -0,0 +1,980 @@
package org.newdawn.slick;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Rectangle;
import java.awt.font.GlyphVector;
import java.awt.font.TextAttribute;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.newdawn.slick.font.Glyph;
import org.newdawn.slick.font.GlyphPage;
import org.newdawn.slick.font.HieroSettings;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureImpl;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
import org.newdawn.slick.util.ResourceLoader;
/**
* A Slick bitmap font that can display unicode glyphs from a TrueTypeFont.
*
* For efficiency, glyphs are packed on to textures. Glyphs can be loaded to the textures on the fly, when they are first needed
* for display. However, it is best to load the glyphs that are known to be needed at startup.
* @author Nathan Sweet <misc@n4te.com>
*/
public class UnicodeFont implements org.newdawn.slick.Font {
/** The number of display lists that will be cached for strings from this font */
private static final int DISPLAY_LIST_CACHE_SIZE = 200;
/** The highest glyph code allowed */
static private final int MAX_GLYPH_CODE = 0x10FFFF;
/** The number of glyphs on a page */
private static final int PAGE_SIZE = 512;
/** The number of pages */
private static final int PAGES = MAX_GLYPH_CODE / PAGE_SIZE;
/** Interface to OpenGL */
private static final SGL GL = Renderer.get();
/** A dummy display list used as a place holder */
private static final DisplayList EMPTY_DISPLAY_LIST = new DisplayList();
/**
* Utility to create a Java font for a TTF file reference
*
* @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
* @return The font created
* @throws SlickException Indicates a failure to locate or load the font into Java's font
* system.
*/
private static Font createFont (String ttfFileRef) throws SlickException {
try {
return Font.createFont(Font.TRUETYPE_FONT, ResourceLoader.getResourceAsStream(ttfFileRef));
} catch (FontFormatException ex) {
throw new SlickException("Invalid font: " + ttfFileRef, ex);
} catch (IOException ex) {
throw new SlickException("Error reading font: " + ttfFileRef, ex);
}
}
/**
* Sorts glyphs by height, tallest first.
*/
private static final Comparator heightComparator = new Comparator() {
public int compare (Object o1, Object o2) {
return ((Glyph)o1).getHeight() - ((Glyph)o2).getHeight();
}
};
/** The AWT font that is being rendered */
private Font font;
/** The reference to the True Type Font file that has kerning information */
private String ttfFileRef;
/** The ascent of the font */
private int ascent;
/** The decent of the font */
private int descent;
/** The leading edge of the font */
private int leading;
/** The width of a space for the font */
private int spaceWidth;
/** The glyphs that are available in this font */
private final Glyph[][] glyphs = new Glyph[PAGES][];
/** The pages that have been loaded for this font */
private final List glyphPages = new ArrayList();
/** The glyphs queued up to be rendered */
private final List queuedGlyphs = new ArrayList(256);
/** The effects that need to be applied to the font */
private final List effects = new ArrayList();
/** The padding applied in pixels to the top of the glyph rendered area */
private int paddingTop;
/** The padding applied in pixels to the left of the glyph rendered area */
private int paddingLeft;
/** The padding applied in pixels to the bottom of the glyph rendered area */
private int paddingBottom;
/** The padding applied in pixels to the right of the glyph rendered area */
private int paddingRight;
/** The padding applied in pixels to horizontal advance for each glyph */
private int paddingAdvanceX;
/** The padding applied in pixels to vertical advance for each glyph */
private int paddingAdvanceY;
/** The glyph to display for missing glyphs in code points */
private Glyph missingGlyph;
/** The width of the glyph page generated */
private int glyphPageWidth = 512;
/** The height of the glyph page generated */
private int glyphPageHeight = 512;
/** True if display list caching is turned on */
private boolean displayListCaching = true;
/** The based display list ID */
private int baseDisplayListID = -1;
/** The ID of the display list that has been around the longest time */
private int eldestDisplayListID;
/** The eldest display list */
private DisplayList eldestDisplayList;
/** The map fo the display list generated and cached - modified to allow removal of the oldest entry */
private final LinkedHashMap displayLists = new LinkedHashMap(DISPLAY_LIST_CACHE_SIZE, 1, true) {
protected boolean removeEldestEntry (Entry eldest) {
DisplayList displayList = (DisplayList)eldest.getValue();
if (displayList != null) eldestDisplayListID = displayList.id;
return size() > DISPLAY_LIST_CACHE_SIZE;
}
};
/**
* Create a new unicode font based on a TTF file
*
* @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
* @param hieroFileRef The file system or classpath location of the Hiero settings file.
* @throws SlickException if the UnicodeFont could not be initialized.
*/
public UnicodeFont (String ttfFileRef, String hieroFileRef) throws SlickException {
this(ttfFileRef, new HieroSettings(hieroFileRef));
}
/**
* Create a new unicode font based on a TTF file and a set of heiro configuration
*
* @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
* @param settings The settings configured via the Hiero tool
* @throws SlickException if the UnicodeFont could not be initialized.
*/
public UnicodeFont (String ttfFileRef, HieroSettings settings) throws SlickException {
this.ttfFileRef = ttfFileRef;
Font font = createFont(ttfFileRef);
initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic());
loadSettings(settings);
}
/**
* Create a new unicode font based on a TTF file alone
*
* @param ttfFileRef The file system or classpath location of the TrueTypeFont file.
* @param size The point size of the font to generated
* @param bold True if the font should be rendered in bold typeface
* @param italic True if the font should be rendered in bold typeface
* @throws SlickException if the UnicodeFont could not be initialized.
*/
public UnicodeFont (String ttfFileRef, int size, boolean bold, boolean italic) throws SlickException {
this.ttfFileRef = ttfFileRef;
initializeFont(createFont(ttfFileRef), size, bold, italic);
}
/**
* Creates a new UnicodeFont.
*
* @param font The AWT font to render
* @param hieroFileRef The file system or classpath location of the Hiero settings file.
* @throws SlickException if the UnicodeFont could not be initialized.
*/
public UnicodeFont (Font font, String hieroFileRef) throws SlickException {
this(font, new HieroSettings(hieroFileRef));
}
/**
* Creates a new UnicodeFont.
*
* @param font The AWT font to render
* @param settings The settings configured via the Hiero tool
*/
public UnicodeFont (Font font, HieroSettings settings) {
initializeFont(font, settings.getFontSize(), settings.isBold(), settings.isItalic());
loadSettings(settings);
}
/**
* Creates a new UnicodeFont.
*
* @param font The AWT font to render
*/
public UnicodeFont (Font font) {
initializeFont(font, font.getSize(), font.isBold(), font.isItalic());
}
/**
* Creates a new UnicodeFont.
*
* @param font The AWT font to render
* @param size The point size of the font to generated
* @param bold True if the font should be rendered in bold typeface
* @param italic True if the font should be rendered in bold typeface
*/
public UnicodeFont (Font font, int size, boolean bold, boolean italic) {
initializeFont(font, size, bold, italic);
}
/**
* Initialise the font to be used based on configuration
*
* @param baseFont The AWT font to render
* @param size The point size of the font to generated
* @param bold True if the font should be rendered in bold typeface
* @param italic True if the font should be rendered in bold typeface
*/
private void initializeFont(Font baseFont, int size, boolean bold, boolean italic) {
Map attributes = baseFont.getAttributes();
attributes.put(TextAttribute.SIZE, new Float(size));
attributes.put(TextAttribute.WEIGHT, bold ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR);
attributes.put(TextAttribute.POSTURE, italic ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR);
try {
attributes.put(TextAttribute.class.getDeclaredField("KERNING").get(null), TextAttribute.class.getDeclaredField(
"KERNING_ON").get(null));
} catch (Exception ignored) {
}
font = baseFont.deriveFont(attributes);
FontMetrics metrics = GlyphPage.getScratchGraphics().getFontMetrics(font);
ascent = metrics.getAscent();
descent = metrics.getDescent();
leading = metrics.getLeading();
// Determine width of space glyph (getGlyphPixelBounds gives a width of zero).
char[] chars = " ".toCharArray();
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
spaceWidth = vector.getGlyphLogicalBounds(0).getBounds().width;
}
/**
* Load the hiero setting and configure the unicode font's rendering
*
* @param settings The settings to be applied
*/
private void loadSettings(HieroSettings settings) {
paddingTop = settings.getPaddingTop();
paddingLeft = settings.getPaddingLeft();
paddingBottom = settings.getPaddingBottom();
paddingRight = settings.getPaddingRight();
paddingAdvanceX = settings.getPaddingAdvanceX();
paddingAdvanceY = settings.getPaddingAdvanceY();
glyphPageWidth = settings.getGlyphPageWidth();
glyphPageHeight = settings.getGlyphPageHeight();
effects.addAll(settings.getEffects());
}
/**
* Queues the glyphs in the specified codepoint range (inclusive) to be loaded. Note that the glyphs are not actually loaded
* until {@link #loadGlyphs()} is called.
*
* Some characters like combining marks and non-spacing marks can only be rendered with the context of other glyphs. In this
* case, use {@link #addGlyphs(String)}.
*
* @param startCodePoint The code point of the first glyph to add
* @param endCodePoint The code point of the last glyph to add
*/
public void addGlyphs(int startCodePoint, int endCodePoint) {
for (int codePoint = startCodePoint; codePoint <= endCodePoint; codePoint++)
addGlyphs(new String(Character.toChars(codePoint)));
}
/**
* Queues the glyphs in the specified text to be loaded. Note that the glyphs are not actually loaded until
* {@link #loadGlyphs()} is called.
*
* @param text The text containing the glyphs to be added
*/
public void addGlyphs(String text) {
if (text == null) throw new IllegalArgumentException("text cannot be null.");
char[] chars = text.toCharArray();
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {
int codePoint = text.codePointAt(vector.getGlyphCharIndex(i));
Rectangle bounds = getGlyphBounds(vector, i, codePoint);
getGlyph(vector.getGlyphCode(i), codePoint, bounds, vector, i);
}
}
/**
* Queues the glyphs in the ASCII character set (codepoints 32 through 255) to be loaded. Note that the glyphs are not actually
* loaded until {@link #loadGlyphs()} is called.
*/
public void addAsciiGlyphs () {
addGlyphs(32, 255);
}
/**
* Queues the glyphs in the NEHE character set (codepoints 32 through 128) to be loaded. Note that the glyphs are not actually
* loaded until {@link #loadGlyphs()} is called.
*/
public void addNeheGlyphs () {
addGlyphs(32, 32 + 96);
}
/**
* Loads all queued glyphs to the backing textures. Glyphs that are typically displayed together should be added and loaded at
* the same time so that they are stored on the same backing texture. This reduces the number of backing texture binds required
* to draw glyphs.
*
* @return True if the glyphs were loaded entirely
* @throws SlickException if the glyphs could not be loaded.
*/
public boolean loadGlyphs () throws SlickException {
return loadGlyphs(-1);
}
/**
* Loads up to the specified number of queued glyphs to the backing textures. This is typically called from the game loop to
* load glyphs on the fly that were requested for display but have not yet been loaded.
*
* @param maxGlyphsToLoad The maximum number of glyphs to be loaded this time
* @return True if the glyphs were loaded entirely
* @throws SlickException if the glyphs could not be loaded.
*/
public boolean loadGlyphs (int maxGlyphsToLoad) throws SlickException {
if (queuedGlyphs.isEmpty()) return false;
if (effects.isEmpty())
throw new IllegalStateException("The UnicodeFont must have at least one effect before any glyphs can be loaded.");
for (Iterator iter = queuedGlyphs.iterator(); iter.hasNext();) {
Glyph glyph = (Glyph)iter.next();
int codePoint = glyph.getCodePoint();
// Don't load an image for a glyph with nothing to display.
if (glyph.getWidth() == 0 || codePoint == ' ') {
iter.remove();
continue;
}
// Only load the first missing glyph.
if (glyph.isMissing()) {
if (missingGlyph != null) {
if (glyph != missingGlyph) iter.remove();
continue;
}
missingGlyph = glyph;
}
}
Collections.sort(queuedGlyphs, heightComparator);
// Add to existing pages.
for (Iterator iter = glyphPages.iterator(); iter.hasNext();) {
GlyphPage glyphPage = (GlyphPage)iter.next();
maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad);
if (maxGlyphsToLoad == 0 || queuedGlyphs.isEmpty())
return true;
}
// Add to new pages.
while (!queuedGlyphs.isEmpty()) {
GlyphPage glyphPage = new GlyphPage(this, glyphPageWidth, glyphPageHeight);
glyphPages.add(glyphPage);
maxGlyphsToLoad -= glyphPage.loadGlyphs(queuedGlyphs, maxGlyphsToLoad);
if (maxGlyphsToLoad == 0) return true;
}
return true;
}
/**
* Clears all loaded and queued glyphs.
*/
public void clearGlyphs () {
for (int i = 0; i < PAGES; i++)
glyphs[i] = null;
for (Iterator iter = glyphPages.iterator(); iter.hasNext();) {
GlyphPage page = (GlyphPage)iter.next();
try {
page.getImage().destroy();
} catch (SlickException ignored) {
}
}
glyphPages.clear();
if (baseDisplayListID != -1) {
GL.glDeleteLists(baseDisplayListID, displayLists.size());
baseDisplayListID = -1;
}
queuedGlyphs.clear();
missingGlyph = null;
}
/**
* Releases all resources used by this UnicodeFont. This method should be called when this UnicodeFont instance is no longer
* needed.
*/
public void destroy () {
// The destroy() method is just to provide a consistent API for releasing resources.
clearGlyphs();
}
/**
* Identical to {@link #drawString(float, float, String, Color, int, int)} but returns a
* DisplayList which provides access to the width and height of the text drawn.
*
* @param text The text to render
* @param x The horizontal location to render at
* @param y The vertical location to render at
* @param color The colour to apply as a filter on the text
* @param startIndex The start index into the string to start rendering at
* @param endIndex The end index into the string to render to
* @return The reference to the display list that was drawn and potentiall ygenerated
*/
public DisplayList drawDisplayList (float x, float y, String text, Color color, int startIndex, int endIndex) {
if (text == null) throw new IllegalArgumentException("text cannot be null.");
if (text.length() == 0) return EMPTY_DISPLAY_LIST;
if (color == null) throw new IllegalArgumentException("color cannot be null.");
x -= paddingLeft;
y -= paddingTop;
String displayListKey = text.substring(startIndex, endIndex);
color.bind();
TextureImpl.bindNone();
DisplayList displayList = null;
if (displayListCaching && queuedGlyphs.isEmpty()) {
if (baseDisplayListID == -1) {
baseDisplayListID = GL.glGenLists(DISPLAY_LIST_CACHE_SIZE);
if (baseDisplayListID == 0) {
baseDisplayListID = -1;
displayListCaching = false;
return new DisplayList();
}
}
// Try to use a display list compiled for this text.
displayList = (DisplayList)displayLists.get(displayListKey);
if (displayList != null) {
if (displayList.invalid)
displayList.invalid = false;
else {
GL.glTranslatef(x, y, 0);
GL.glCallList(displayList.id);
GL.glTranslatef(-x, -y, 0);
return displayList;
}
} else if (displayList == null) {
// Compile a new display list.
displayList = new DisplayList();
int displayListCount = displayLists.size();
displayLists.put(displayListKey, displayList);
if (displayListCount < DISPLAY_LIST_CACHE_SIZE)
displayList.id = baseDisplayListID + displayListCount;
else
displayList.id = eldestDisplayListID;
}
displayLists.put(displayListKey, displayList);
}
GL.glTranslatef(x, y, 0);
if (displayList != null) GL.glNewList(displayList.id, SGL.GL_COMPILE_AND_EXECUTE);
char[] chars = text.substring(0, endIndex).toCharArray();
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
int maxWidth = 0, totalHeight = 0, lines = 0;
int extraX = 0, extraY = ascent;
boolean startNewLine = false;
Texture lastBind = null;
for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {
int charIndex = vector.getGlyphCharIndex(glyphIndex);
if (charIndex < startIndex) continue;
if (charIndex > endIndex) break;
int codePoint = text.codePointAt(charIndex);
Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint);
Glyph glyph = getGlyph(vector.getGlyphCode(glyphIndex), codePoint, bounds, vector, glyphIndex);
if (startNewLine && codePoint != '\n') {
extraX = -bounds.x;
startNewLine = false;
}
Image image = glyph.getImage();
if (image == null && missingGlyph != null && glyph.isMissing()) image = missingGlyph.getImage();
if (image != null) {
// Draw glyph, only binding a new glyph page texture when necessary.
Texture texture = image.getTexture();
if (lastBind != null && lastBind != texture) {
GL.glEnd();
lastBind = null;
}
if (lastBind == null) {
texture.bind();
GL.glBegin(SGL.GL_QUADS);
lastBind = texture;
}
image.drawEmbedded(bounds.x + extraX, bounds.y + extraY, image.getWidth(), image.getHeight());
}
if (glyphIndex >= 0) extraX += paddingRight + paddingLeft + paddingAdvanceX;
maxWidth = Math.max(maxWidth, bounds.x + extraX + bounds.width);
totalHeight = Math.max(totalHeight, ascent + bounds.y + bounds.height);
if (codePoint == '\n') {
startNewLine = true; // Mac gives -1 for bounds.x of '\n', so use the bounds.x of the next glyph.
extraY += getLineHeight();
lines++;
totalHeight = 0;
}
}
if (lastBind != null) GL.glEnd();
if (displayList != null) {
GL.glEndList();
// Invalidate the display list if it had glyphs that need to be loaded.
if (!queuedGlyphs.isEmpty()) displayList.invalid = true;
}
GL.glTranslatef(-x, -y, 0);
if (displayList == null) displayList = new DisplayList();
displayList.width = (short)maxWidth;
displayList.height = (short)(lines * getLineHeight() + totalHeight);
return displayList;
}
public void drawString (float x, float y, String text, Color color, int startIndex, int endIndex) {
drawDisplayList(x, y, text, color, startIndex, endIndex);
}
public void drawString (float x, float y, String text) {
drawString(x, y, text, Color.white);
}
public void drawString (float x, float y, String text, Color col) {
drawString(x, y, text, col, 0, text.length());
}
/**
* Returns the glyph for the specified codePoint. If the glyph does not exist yet,
* it is created and queued to be loaded.
*
* @param glyphCode The code of the glyph to locate
* @param codePoint The code point associated with the glyph
* @param bounds The bounds of the glyph on the page
* @param vector The vector the glyph is part of
* @param index The index of the glyph within the vector
* @return The glyph requested
*/
private Glyph getGlyph (int glyphCode, int codePoint, Rectangle bounds, GlyphVector vector, int index) {
if (glyphCode < 0 || glyphCode >= MAX_GLYPH_CODE) {
// GlyphVector#getGlyphCode sometimes returns negative numbers on OS X.
return new Glyph(codePoint, bounds, vector, index, this) {
public boolean isMissing () {
return true;
}
};
}
int pageIndex = glyphCode / PAGE_SIZE;
int glyphIndex = glyphCode & (PAGE_SIZE - 1);
Glyph glyph = null;
Glyph[] page = glyphs[pageIndex];
if (page != null) {
glyph = page[glyphIndex];
if (glyph != null) return glyph;
} else
page = glyphs[pageIndex] = new Glyph[PAGE_SIZE];
// Add glyph so size information is available and queue it so its image can be loaded later.
glyph = page[glyphIndex] = new Glyph(codePoint, bounds, vector, index, this);
queuedGlyphs.add(glyph);
return glyph;
}
/**
* Returns the bounds of the specified glyph.\
*
* @param vector The vector the glyph is part of
* @param index The index of the glyph within the vector
* @param codePoint The code point associated with the glyph
*/
private Rectangle getGlyphBounds (GlyphVector vector, int index, int codePoint) {
Rectangle bounds = vector.getGlyphPixelBounds(index, GlyphPage.renderContext, 0, 0);
if (codePoint == ' ') bounds.width = spaceWidth;
return bounds;
}
/**
* Returns the width of the space character.
*/
public int getSpaceWidth () {
return spaceWidth;
}
/**
* @see org.newdawn.slick.Font#getWidth(java.lang.String)
*/
public int getWidth (String text) {
if (text == null) throw new IllegalArgumentException("text cannot be null.");
if (text.length() == 0) return 0;
if (displayListCaching) {
DisplayList displayList = (DisplayList)displayLists.get(text);
if (displayList != null) return displayList.width;
}
char[] chars = text.toCharArray();
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
int width = 0;
int extraX = 0;
boolean startNewLine = false;
for (int glyphIndex = 0, n = vector.getNumGlyphs(); glyphIndex < n; glyphIndex++) {
int charIndex = vector.getGlyphCharIndex(glyphIndex);
int codePoint = text.codePointAt(charIndex);
Rectangle bounds = getGlyphBounds(vector, glyphIndex, codePoint);
if (startNewLine && codePoint != '\n') extraX = -bounds.x;
if (glyphIndex > 0) extraX += paddingLeft + paddingRight + paddingAdvanceX;
width = Math.max(width, bounds.x + extraX + bounds.width);
if (codePoint == '\n') startNewLine = true;
}
return width;
}
/**
* @see org.newdawn.slick.Font#getHeight(java.lang.String)
*/
public int getHeight (String text) {
if (text == null) throw new IllegalArgumentException("text cannot be null.");
if (text.length() == 0) return 0;
if (displayListCaching) {
DisplayList displayList = (DisplayList)displayLists.get(text);
if (displayList != null) return displayList.height;
}
char[] chars = text.toCharArray();
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
int lines = 0, height = 0;
for (int i = 0, n = vector.getNumGlyphs(); i < n; i++) {
int charIndex = vector.getGlyphCharIndex(i);
int codePoint = text.codePointAt(charIndex);
if (codePoint == ' ') continue;
Rectangle bounds = getGlyphBounds(vector, i, codePoint);
height = Math.max(height, ascent + bounds.y + bounds.height);
if (codePoint == '\n') {
lines++;
height = 0;
}
}
return lines * getLineHeight() + height;
}
/**
* Returns the distance from the y drawing location to the top most pixel of the
* specified text.
*
* @param text The text to analyse
* @return The distance fro the y drawing location ot the top most pixel of the specified text
*/
public int getYOffset (String text) {
if (text == null) throw new IllegalArgumentException("text cannot be null.");
DisplayList displayList = null;
if (displayListCaching) {
displayList = (DisplayList)displayLists.get(text);
if (displayList != null && displayList.yOffset != null) return displayList.yOffset.intValue();
}
int index = text.indexOf('\n');
if (index != -1) text = text.substring(0, index);
char[] chars = text.toCharArray();
GlyphVector vector = font.layoutGlyphVector(GlyphPage.renderContext, chars, 0, chars.length, Font.LAYOUT_LEFT_TO_RIGHT);
int yOffset = ascent + vector.getPixelBounds(null, 0, 0).y;
if (displayList != null) displayList.yOffset = new Short((short)yOffset);
return yOffset;
}
/**
* Returns the TrueTypeFont for this UnicodeFont.
*
* @return The AWT Font being rendered
*/
public Font getFont() {
return font;
}
/**
* Returns the padding above a glyph on the GlyphPage to allow for effects to be drawn.
*
* @return The padding at the top of the glyphs when drawn
*/
public int getPaddingTop() {
return paddingTop;
}
/**
* Sets the padding above a glyph on the GlyphPage to allow for effects to be drawn.
*
* @param paddingTop The padding at the top of the glyphs when drawn
*/
public void setPaddingTop(int paddingTop) {
this.paddingTop = paddingTop;
}
/**
* Returns the padding to the left of a glyph on the GlyphPage to allow for effects to be drawn.
*
* @return The padding at the left of the glyphs when drawn
*/
public int getPaddingLeft() {
return paddingLeft;
}
/**
* Sets the padding to the left of a glyph on the GlyphPage to allow for effects to be drawn.
*
* @param paddingLeft The padding at the left of the glyphs when drawn
*/
public void setPaddingLeft(int paddingLeft) {
this.paddingLeft = paddingLeft;
}
/**
* Returns the padding below a glyph on the GlyphPage to allow for effects to be drawn.
*
* @return The padding at the bottom of the glyphs when drawn
*/
public int getPaddingBottom() {
return paddingBottom;
}
/**
* Sets the padding below a glyph on the GlyphPage to allow for effects to be drawn.
*
* @param paddingBottom The padding at the bottom of the glyphs when drawn
*/
public void setPaddingBottom(int paddingBottom) {
this.paddingBottom = paddingBottom;
}
/**
* Returns the padding to the right of a glyph on the GlyphPage to allow for effects to be drawn.
*
* @return The padding at the right of the glyphs when drawn
*/
public int getPaddingRight () {
return paddingRight;
}
/**
* Sets the padding to the right of a glyph on the GlyphPage to allow for effects to be drawn.
*
* @param paddingRight The padding at the right of the glyphs when drawn
*/
public void setPaddingRight (int paddingRight) {
this.paddingRight = paddingRight;
}
/**
* Gets the additional amount to offset glyphs on the x axis.
*
* @return The padding applied for each horizontal advance (i.e. when a glyph is rendered)
*/
public int getPaddingAdvanceX() {
return paddingAdvanceX;
}
/**
* Sets the additional amount to offset glyphs on the x axis. This is typically set to a negative number when left or right
* padding is used so that glyphs are not spaced too far apart.
*
* @param paddingAdvanceX The padding applied for each horizontal advance (i.e. when a glyph is rendered)
*/
public void setPaddingAdvanceX (int paddingAdvanceX) {
this.paddingAdvanceX = paddingAdvanceX;
}
/**
* Gets the additional amount to offset a line of text on the y axis.
*
* @return The padding applied for each vertical advance (i.e. when a glyph is rendered)
*/
public int getPaddingAdvanceY () {
return paddingAdvanceY;
}
/**
* Sets the additional amount to offset a line of text on the y axis. This is typically set to a negative number when top or
* bottom padding is used so that lines of text are not spaced too far apart.
*
* @param paddingAdvanceY The padding applied for each vertical advance (i.e. when a glyph is rendered)
*/
public void setPaddingAdvanceY (int paddingAdvanceY) {
this.paddingAdvanceY = paddingAdvanceY;
}
/**
* Returns the distance from one line of text to the next. This is the sum of the descent, ascent, leading, padding top,
* padding bottom, and padding advance y. To change the line height, use {@link #setPaddingAdvanceY(int)}.
*/
public int getLineHeight() {
return descent + ascent + leading + paddingTop + paddingBottom + paddingAdvanceY;
}
/**
* Gets the distance from the baseline to the y drawing location.
*
* @return The ascent of this font
*/
public int getAscent() {
return ascent;
}
/**
* Gets the distance from the baseline to the bottom of most alphanumeric characters
* with descenders.
*
* @return The distance from the baseline to the bottom of the font
*/
public int getDescent () {
return descent;
}
/**
* Gets the extra distance between the descent of one line of text to the ascent of the next.
*
* @return The leading edge of the font
*/
public int getLeading () {
return leading;
}
/**
* Returns the width of the backing textures.
*
* @return The width of the glyph pages in this font
*/
public int getGlyphPageWidth () {
return glyphPageWidth;
}
/**
* Sets the width of the backing textures. Default is 512.
*
* @param glyphPageWidth The width of the glyph pages in this font
*/
public void setGlyphPageWidth(int glyphPageWidth) {
this.glyphPageWidth = glyphPageWidth;
}
/**
* Returns the height of the backing textures.
*
* @return The height of the glyph pages in this font
*/
public int getGlyphPageHeight() {
return glyphPageHeight;
}
/**
* Sets the height of the backing textures. Default is 512.
*
* @param glyphPageHeight The width of the glyph pages in this font
*/
public void setGlyphPageHeight(int glyphPageHeight) {
this.glyphPageHeight = glyphPageHeight;
}
/**
* Returns the GlyphPages for this UnicodeFont.
*
* @return The glyph pages that have been loaded into this font
*/
public List getGlyphPages () {
return glyphPages;
}
/**
* Returns a list of {@link org.newdawn.slick.font.effects.Effect}s that will be applied
* to the glyphs.
*
* @return The list of effects to be applied to the font
*/
public List getEffects () {
return effects;
}
/**
* Returns true if this UnicodeFont caches the glyph drawing instructions to
* improve performance.
*
* @return True if caching is turned on
*/
public boolean isCaching () {
return displayListCaching;
}
/**
* Sets if this UnicodeFont caches the glyph drawing instructions to improve performance.
* Default is true. Text rendering is very slow without display list caching.
*
* @param displayListCaching True if caching should be turned on
*/
public void setDisplayListCaching (boolean displayListCaching) {
this.displayListCaching = displayListCaching;
}
/**
* Returns the path to the TTF file for this UnicodeFont, or null. If this UnicodeFont was created without specifying the TTF
* file, it will try to determine the path using Sun classes. If this fails, null is returned.
*
* @return The reference to the font file that the kerning was loaded from
*/
public String getFontFile () {
if (ttfFileRef == null) {
// Worst case if this UnicodeFont was loaded without a ttfFileRef, try to get the font file from Sun's classes.
try {
Object font2D = Class.forName("sun.font.FontManager").getDeclaredMethod("getFont2D", new Class[] {Font.class})
.invoke(null, new Object[] {font});
Field platNameField = Class.forName("sun.font.PhysicalFont").getDeclaredField("platName");
platNameField.setAccessible(true);
ttfFileRef = (String)platNameField.get(font2D);
} catch (Throwable ignored) {
}
if (ttfFileRef == null) ttfFileRef = "";
}
if (ttfFileRef.length() == 0) return null;
return ttfFileRef;
}
/**
* A simple descriptor for display lists cached within this font
*/
public static class DisplayList {
/** True if this display list has been invalidated */
boolean invalid;
/** The ID of the display list this descriptor represents */
int id;
/** The vertical offset to the top of this display list */
Short yOffset;
/** The width of rendered text in the list */
public short width;
/** The height of the rendered text in the list */
public short height;
/** Application data stored in the list */
public Object userData;
DisplayList () {
}
}
}

View File

@@ -0,0 +1,65 @@
package org.newdawn.slick;
import java.util.HashMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.newdawn.slick.util.ResourceLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* A sprite sheet based on an XML descriptor generated from the simple slick tool
*
* @author kevin
*/
public class XMLPackedSheet {
/** The full sheet image */
private Image image;
/** The sprites stored on the image */
private HashMap sprites = new HashMap();
/**
* Create a new XML packed sheet from the XML output by the slick tool
*
* @param imageRef The reference to the image
* @param xmlRef The reference to the XML
* @throws SlickException Indicates a failure to parse the XML or read the image
*/
public XMLPackedSheet(String imageRef, String xmlRef) throws SlickException
{
image = new Image(imageRef, false, Image.FILTER_NEAREST);
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse(ResourceLoader.getResourceAsStream(xmlRef));
NodeList list = doc.getElementsByTagName("sprite");
for (int i=0;i<list.getLength();i++) {
Element element = (Element) list.item(i);
String name = element.getAttribute("name");
int x = Integer.parseInt(element.getAttribute("x"));
int y = Integer.parseInt(element.getAttribute("y"));
int width = Integer.parseInt(element.getAttribute("width"));
int height = Integer.parseInt(element.getAttribute("height"));
sprites.put(name, image.getSubImage(x,y,width,height));
}
} catch (Exception e) {
throw new SlickException("Failed to parse sprite sheet XML", e);
}
}
/**
* Get a sprite by it's given name
*
* @param name The name of the sprite to retrieve
* @return The sprite from the sheet or null if the name isn't used in this sheet
*/
public Image getSprite(String name) {
return (Image) sprites.get(name);
}
}

View File

@@ -0,0 +1,54 @@
package org.newdawn.slick.command;
/**
* A simple named command
*
* @author kevin
*/
public class BasicCommand implements Command {
/** The name of the command */
private String name;
/**
* Create a new basic command
*
* @param name The name to give this command
*/
public BasicCommand(String name) {
this.name = name;
}
/**
* Get the name given for this basic command
*
* @return The name given for this basic command
*/
public String getName() {
return name;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return name.hashCode();
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object other) {
if (other instanceof BasicCommand) {
return ((BasicCommand) other).name.equals(name);
}
return false;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "[Command="+name+"]";
}
}

View File

@@ -0,0 +1,12 @@
package org.newdawn.slick.command;
/**
* The description of a action feedback from the abstract input system. This marker allows the
* creation of action objects that can contain useful state. If you don't need state and just
* a name use <code>BasicCommand</code.
*
* @author kevin
*/
public interface Command {
}

View File

@@ -0,0 +1,9 @@
package org.newdawn.slick.command;
/**
* Marker class for abstract input controls
*
* @author joverton
*/
public interface Control {
}

View File

@@ -0,0 +1,20 @@
package org.newdawn.slick.command;
/**
* A control indicating that a gamepad/joystick button must be pressed
* or released to invoke an command.
*
* @author kevin
*/
public class ControllerButtonControl extends ControllerControl {
/**
* Create a new control based on a controller input
*
* @param controllerIndex The index of the controller to listen to
* @param button The index of the button that causes the command
*/
public ControllerButtonControl(int controllerIndex, int button) {
super(controllerIndex, BUTTON_EVENT, button);
}
}

View File

@@ -0,0 +1,61 @@
package org.newdawn.slick.command;
/**
* A control describing input provided from a controller. This allows controls to be
* mapped to game pad inputs.
*
* @author joverton
*/
abstract class ControllerControl implements Control {
/** Indicates a button was pressed */
protected static final int BUTTON_EVENT = 0;
/** Indicates left was pressed */
protected static final int LEFT_EVENT = 1;
/** Indicates right was pressed */
protected static final int RIGHT_EVENT = 2;
/** Indicates up was pressed */
protected static final int UP_EVENT = 3;
/** Indicates down was pressed */
protected static final int DOWN_EVENT = 4;
/** The type of event we're looking for */
private int event;
/** The index of the button we're waiting for */
private int button;
/** The index of the controller we're waiting on */
private int controllerNumber;
/**
* Create a new controller control
*
* @param controllerNumber The index of the controller to react to
* @param event The event to react to
* @param button The button index to react to on a BUTTON event
*/
protected ControllerControl(int controllerNumber, int event, int button) {
this.event = event;
this.button = button;
this.controllerNumber = controllerNumber;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o) {
if(o == null)
return false;
if(!(o instanceof ControllerControl))
return false;
ControllerControl c = (ControllerControl)o;
return c.controllerNumber == controllerNumber && c.event == event && c.button == button;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return event + button + controllerNumber;
}
}

View File

@@ -0,0 +1,47 @@
package org.newdawn.slick.command;
/**
* A control indicating that a particular direction must be pressed or released
* on a controller to cause the command to fire
*
* @author kevin
*/
public class ControllerDirectionControl extends ControllerControl {
/** The direction indicating we're waiting for the user to press left */
public static final Direction LEFT = new Direction(LEFT_EVENT);
/** The direction indicating we're waiting for the user to press up */
public static final Direction UP = new Direction(UP_EVENT);
/** The direction indicating we're waiting for the user to press down */
public static final Direction DOWN = new Direction(DOWN_EVENT);
/** The direction indicating we're waiting for the user to press right */
public static final Direction RIGHT = new Direction(RIGHT_EVENT);
/**
* Create a new input that indicates a direcitonal control must be pressed
*
* @param controllerIndex The index of the controller to listen to
* @param dir The direction to wait for
*/
public ControllerDirectionControl(int controllerIndex, Direction dir) {
super(controllerIndex, dir.event, 0);
}
/**
* Enum pretender
*
* @author kevin
*/
private static class Direction {
/** The event to be fired for this direction */
private int event;
/**
* Create a new direction indicator/enum value
*
* @param event The event to fire when this direction is used
*/
public Direction(int event) {
this.event = event;
}
}
}

View File

@@ -0,0 +1,464 @@
package org.newdawn.slick.command;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.newdawn.slick.Input;
import org.newdawn.slick.util.InputAdapter;
/**
* The central provider that maps real device input into abstract commands
* defined by the developer. Registering a control against an command with this
* class will cause the provider to produce an event for the command when the
* input is pressed and released.
*
* @author joverton
*/
public class InputProvider {
/** The commands that have been defined */
private HashMap commands;
/** The list of listeners that may be listening */
private ArrayList listeners = new ArrayList();
/** The input context we're responding to */
private Input input;
/** The command input states */
private HashMap commandState = new HashMap();
/** True if this provider is actively sending events */
private boolean active = true;
/**
* Create a new input proider which will provide abstract input descriptions
* based on the input from the supplied context.
*
* @param input
* The input from which this provider will receive events
*/
public InputProvider(Input input) {
this.input = input;
input.addListener(new InputListenerImpl());
commands = new HashMap();
}
/**
* Get the list of commands that have been registered with the provider,
* i.e. the commands that can be issued to the listeners
*
* @return The list of commands (@see Command) that can be issued from this
* provider
*/
public List getUniqueCommands() {
List uniqueCommands = new ArrayList();
for (Iterator it = commands.values().iterator(); it.hasNext();) {
Command command = (Command) it.next();
if (!uniqueCommands.contains(command)) {
uniqueCommands.add(command);
}
}
return uniqueCommands;
}
/**
* Get a list of the registered controls (@see Control) that can cause a
* particular command to be invoked
*
* @param command
* The command to be invoked
* @return The list of controls that can cause the command (@see Control)
*/
public List getControlsFor(Command command) {
List controlsForCommand = new ArrayList();
for (Iterator it = commands.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
Control key = (Control) entry.getKey();
Command value = (Command) entry.getValue();
if (value == command) {
controlsForCommand.add(key);
}
}
return controlsForCommand;
}
/**
* Indicate whether this provider should be sending events
*
* @param active
* True if this provider should be sending events
*/
public void setActive(boolean active) {
this.active = active;
}
/**
* Check if this provider should be sending events
*
* @return True if this provider should be sending events
*/
public boolean isActive() {
return active;
}
/**
* Add a listener to the provider. This listener will be notified of
* commands detected from the input.
*
* @param listener
* The listener to be added
*/
public void addListener(InputProviderListener listener) {
listeners.add(listener);
}
/**
* Remove a listener from this provider. The listener will no longer be
* provided with notification of commands performe.
*
* @param listener
* The listener to be removed
*/
public void removeListener(InputProviderListener listener) {
listeners.remove(listener);
}
/**
* Bind an command to a control.
*
* @param command
* The command to bind to
* @param control
* The control that is pressed/released to represent the command
*/
public void bindCommand(Control control, Command command) {
commands.put(control, command);
if (commandState.get(command) == null) {
commandState.put(command, new CommandState());
}
}
/**
* Clear all the controls that have been configured for a given command
*
* @param command The command whose controls should be unbound
*/
public void clearCommand(Command command) {
List controls = getControlsFor(command);
for (int i=0;i<controls.size();i++) {
unbindCommand((Control) controls.get(i));
}
}
/**
* Unbinds the command associated with this control
*
* @param control
* The control to remove
*/
public void unbindCommand(Control control) {
Command command = (Command) commands.remove(control);
if (command != null) {
if (!commands.keySet().contains(command)) {
commandState.remove(command);
}
}
}
/**
* Get the recorded state for a given command
*
* @param command
* The command to get the state for
* @return The given command state
*/
private CommandState getState(Command command) {
return (CommandState) commandState.get(command);
}
/**
* Check if the last control event we recieved related to the given command
* indicated that a control was down
*
* @param command
* The command to check
* @return True if the last event indicated a button down
*/
public boolean isCommandControlDown(Command command) {
return getState(command).isDown();
}
/**
* Check if one of the controls related to the command specified has been
* pressed since we last called this method
*
* @param command
* The command to check
* @return True if one of the controls has been pressed
*/
public boolean isCommandControlPressed(Command command) {
return getState(command).isPressed();
}
/**
* Fire notification to any interested listeners that a control has been
* pressed indication an particular command
*
* @param command
* The command that has been pressed
*/
protected void firePressed(Command command) {
getState(command).down = true;
getState(command).pressed = true;
if (!isActive()) {
return;
}
for (int i = 0; i < listeners.size(); i++) {
((InputProviderListener) listeners.get(i)).controlPressed(command);
}
}
/**
* Fire notification to any interested listeners that a control has been
* released indication an particular command should be stopped
*
* @param command
* The command that has been pressed
*/
protected void fireReleased(Command command) {
getState(command).down = false;
if (!isActive()) {
return;
}
for (int i = 0; i < listeners.size(); i++) {
((InputProviderListener) listeners.get(i)).controlReleased(command);
}
}
/**
* A token representing the state of all the controls causing an command to
* be invoked
*
* @author kevin
*/
private class CommandState {
/** True if one of the controls for this command is down */
private boolean down;
/** True if one of the controls for this command is pressed */
private boolean pressed;
/**
* Check if a control for the command has been pressed since last call.
*
* @return True if the command has been pressed
*/
public boolean isPressed() {
if (pressed) {
pressed = false;
return true;
}
return false;
}
/**
* Check if the last event we had indicated the control was pressed
*
* @return True if the control was pressed
*/
public boolean isDown() {
return down;
}
}
/**
* A simple listener to respond to input and look up any required commands
*
* @author kevin
*/
private class InputListenerImpl extends InputAdapter {
/**
* @see org.newdawn.slick.util.InputAdapter#isAcceptingInput()
*/
public boolean isAcceptingInput() {
return true;
}
/**
* @see org.newdawn.slick.util.InputAdapter#keyPressed(int, char)
*/
public void keyPressed(int key, char c) {
Command command = (Command) commands.get(new KeyControl(key));
if (command != null) {
firePressed(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#keyReleased(int, char)
*/
public void keyReleased(int key, char c) {
Command command = (Command) commands.get(new KeyControl(key));
if (command != null) {
fireReleased(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#mousePressed(int, int, int)
*/
public void mousePressed(int button, int x, int y) {
Command command = (Command) commands.get(new MouseButtonControl(
button));
if (command != null) {
firePressed(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#mouseReleased(int, int, int)
*/
public void mouseReleased(int button, int x, int y) {
Command command = (Command) commands.get(new MouseButtonControl(
button));
if (command != null) {
fireReleased(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#controllerLeftPressed(int)
*/
public void controllerLeftPressed(int controller) {
Command command = (Command) commands
.get(new ControllerDirectionControl(controller,
ControllerDirectionControl.LEFT));
if (command != null) {
firePressed(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#controllerLeftReleased(int)
*/
public void controllerLeftReleased(int controller) {
Command command = (Command) commands
.get(new ControllerDirectionControl(controller,
ControllerDirectionControl.LEFT));
if (command != null) {
fireReleased(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#controllerRightPressed(int)
*/
public void controllerRightPressed(int controller) {
Command command = (Command) commands
.get(new ControllerDirectionControl(controller,
ControllerDirectionControl.RIGHT));
if (command != null) {
firePressed(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#controllerRightReleased(int)
*/
public void controllerRightReleased(int controller) {
Command command = (Command) commands
.get(new ControllerDirectionControl(controller,
ControllerDirectionControl.RIGHT));
if (command != null) {
fireReleased(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#controllerUpPressed(int)
*/
public void controllerUpPressed(int controller) {
Command command = (Command) commands
.get(new ControllerDirectionControl(controller,
ControllerDirectionControl.UP));
if (command != null)
firePressed(command);
}
/**
* @see org.newdawn.slick.util.InputAdapter#controllerUpReleased(int)
*/
public void controllerUpReleased(int controller) {
Command command = (Command) commands
.get(new ControllerDirectionControl(controller,
ControllerDirectionControl.UP));
if (command != null) {
fireReleased(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#controllerDownPressed(int)
*/
public void controllerDownPressed(int controller) {
Command command = (Command) commands
.get(new ControllerDirectionControl(controller,
ControllerDirectionControl.DOWN));
if (command != null) {
firePressed(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#controllerDownReleased(int)
*/
public void controllerDownReleased(int controller) {
Command command = (Command) commands
.get(new ControllerDirectionControl(controller,
ControllerDirectionControl.DOWN));
if (command != null) {
fireReleased(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#controllerButtonPressed(int,
* int)
*/
public void controllerButtonPressed(int controller, int button) {
Command command = (Command) commands
.get(new ControllerButtonControl(controller, button));
if (command != null) {
firePressed(command);
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#controllerButtonReleased(int,
* int)
*/
public void controllerButtonReleased(int controller, int button) {
Command command = (Command) commands
.get(new ControllerButtonControl(controller, button));
if (command != null) {
fireReleased(command);
}
}
};
}

View File

@@ -0,0 +1,24 @@
package org.newdawn.slick.command;
/**
* Description of any class wishing to recieve notifications of command invocations. Implementations
* should be added to an appropriate input provider to recieve input notification
*
* @author joverton
*/
public interface InputProviderListener {
/**
* A control representing an control was pressed relating to a given command.
*
* @param command The command that the control related to
*/
public void controlPressed(Command command);
/**
* A control representing an control was released relating to a given command.
*
* @param command The command that the control related to
*/
public void controlReleased(Command command);
}

View File

@@ -0,0 +1,39 @@
package org.newdawn.slick.command;
/**
* A control relating to a command indicate that it should be fired when a specific key is pressed
* or released.
*
* @author joverton
*/
public class KeyControl implements Control {
/** The key code that needs to be pressed */
private int keycode;
/**
* Create a new control that caused an command to be fired on a key pressed/released
*
* @param keycode The code of the key that causes the command
*/
public KeyControl(int keycode) {
this.keycode = keycode;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o) {
if (o instanceof KeyControl) {
return ((KeyControl)o).keycode == keycode;
}
return false;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return keycode;
}
}

View File

@@ -0,0 +1,39 @@
package org.newdawn.slick.command;
/**
* A control indicating that a mouse button must be pressed or released to cause an command
*
* @author joverton
*/
public class MouseButtonControl implements Control {
/** The button to be pressed */
private int button;
/**
* Create a new control that indicates a mouse button to be pressed or released
*
* @param button The button that should be pressed to cause the command
*/
public MouseButtonControl(int button) {
this.button = button;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o) {
if (o instanceof MouseButtonControl)
{
return ((MouseButtonControl)o).button == button;
}
return false;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return button;
}
}

View File

@@ -0,0 +1,4 @@
<BODY>
Provides abstract input by mapping physical device inputs (mouse, keyboard and controllers) to abstract
commands that are relevant to a particular game.
</BODY>

View File

@@ -0,0 +1 @@
*.{psd,tga,ogg} filter=lfs diff=lfs merge=lfs -text

View File

@@ -0,0 +1,195 @@
info face="Courier New Bold" size=16 bold=1 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1
common lineHeight=19 base=26 scaleW=256 scaleH=256 pages=1 packed=0
page id=0 file="defaultfont.png"
chars count=189
char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=14 xadvance=9 page=0 chnl=0
char id=253 x=0 y=0 width=12 height=16 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=254 x=12 y=0 width=11 height=15 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=221 x=23 y=0 width=12 height=15 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
char id=218 x=35 y=0 width=11 height=15 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
char id=217 x=46 y=0 width=11 height=15 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
char id=211 x=57 y=0 width=11 height=15 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
char id=210 x=68 y=0 width=11 height=15 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
char id=205 x=79 y=0 width=10 height=15 xoffset=1 yoffset=0 xadvance=9 page=0 chnl=0
char id=204 x=89 y=0 width=10 height=15 xoffset=1 yoffset=0 xadvance=9 page=0 chnl=0
char id=201 x=99 y=0 width=10 height=15 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
char id=200 x=109 y=0 width=10 height=15 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
char id=199 x=119 y=0 width=11 height=15 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=197 x=130 y=0 width=12 height=15 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
char id=193 x=142 y=0 width=12 height=15 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
char id=192 x=154 y=0 width=12 height=15 xoffset=0 yoffset=0 xadvance=9 page=0 chnl=0
char id=36 x=166 y=0 width=9 height=15 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=0
char id=106 x=175 y=0 width=8 height=15 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=255 x=183 y=0 width=12 height=14 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=219 x=195 y=0 width=11 height=14 xoffset=0 yoffset=1 xadvance=9 page=0 chnl=0
char id=213 x=206 y=0 width=11 height=14 xoffset=0 yoffset=1 xadvance=9 page=0 chnl=0
char id=212 x=217 y=0 width=11 height=14 xoffset=0 yoffset=1 xadvance=9 page=0 chnl=0
char id=209 x=228 y=0 width=11 height=14 xoffset=0 yoffset=1 xadvance=9 page=0 chnl=0
char id=206 x=239 y=0 width=10 height=14 xoffset=1 yoffset=1 xadvance=9 page=0 chnl=0
char id=202 x=0 y=16 width=10 height=14 xoffset=0 yoffset=1 xadvance=9 page=0 chnl=0
char id=195 x=10 y=16 width=12 height=14 xoffset=0 yoffset=1 xadvance=9 page=0 chnl=0
char id=194 x=22 y=16 width=12 height=14 xoffset=0 yoffset=1 xadvance=9 page=0 chnl=0
char id=162 x=34 y=16 width=10 height=14 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=0
char id=92 x=44 y=16 width=9 height=14 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=47 x=53 y=16 width=9 height=14 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=93 x=62 y=16 width=6 height=14 xoffset=2 yoffset=3 xadvance=9 page=0 chnl=0
char id=91 x=68 y=16 width=6 height=14 xoffset=4 yoffset=3 xadvance=9 page=0 chnl=0
char id=41 x=74 y=16 width=5 height=14 xoffset=2 yoffset=3 xadvance=9 page=0 chnl=0
char id=40 x=79 y=16 width=5 height=14 xoffset=3 yoffset=3 xadvance=9 page=0 chnl=0
char id=250 x=84 y=16 width=11 height=13 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=249 x=95 y=16 width=11 height=13 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=243 x=106 y=16 width=10 height=13 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=242 x=116 y=16 width=10 height=13 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=237 x=126 y=16 width=10 height=13 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=0
char id=236 x=136 y=16 width=10 height=13 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=0
char id=233 x=146 y=16 width=11 height=13 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=232 x=157 y=16 width=11 height=13 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=229 x=168 y=16 width=10 height=13 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=0
char id=225 x=178 y=16 width=10 height=13 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=0
char id=224 x=188 y=16 width=10 height=13 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=0
char id=220 x=198 y=16 width=11 height=13 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=216 x=209 y=16 width=11 height=13 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=214 x=220 y=16 width=11 height=13 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=207 x=231 y=16 width=10 height=13 xoffset=1 yoffset=2 xadvance=9 page=0 chnl=0
char id=203 x=241 y=16 width=10 height=13 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=196 x=0 y=30 width=12 height=13 xoffset=0 yoffset=2 xadvance=9 page=0 chnl=0
char id=182 x=12 y=30 width=10 height=13 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=167 x=22 y=30 width=10 height=13 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=166 x=32 y=30 width=4 height=13 xoffset=4 yoffset=4 xadvance=9 page=0 chnl=0
char id=124 x=36 y=30 width=4 height=13 xoffset=4 yoffset=4 xadvance=9 page=0 chnl=0
char id=125 x=40 y=30 width=6 height=13 xoffset=3 yoffset=4 xadvance=9 page=0 chnl=0
char id=123 x=46 y=30 width=6 height=13 xoffset=2 yoffset=4 xadvance=9 page=0 chnl=0
char id=81 x=52 y=30 width=11 height=13 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=251 x=63 y=30 width=11 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=245 x=74 y=30 width=10 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=244 x=84 y=30 width=10 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=241 x=94 y=30 width=11 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=240 x=105 y=30 width=10 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=238 x=115 y=30 width=10 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=234 x=125 y=30 width=11 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=231 x=136 y=30 width=10 height=12 xoffset=0 yoffset=7 xadvance=9 page=0 chnl=0
char id=227 x=146 y=30 width=10 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=226 x=156 y=30 width=10 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=223 x=166 y=30 width=9 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=181 x=175 y=30 width=11 height=12 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=127 x=186 y=30 width=11 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=35 x=197 y=30 width=10 height=12 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=64 x=207 y=30 width=8 height=12 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=48 x=215 y=30 width=9 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=57 x=224 y=30 width=9 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=56 x=233 y=30 width=9 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=55 x=242 y=30 width=9 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=54 x=0 y=43 width=9 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=53 x=9 y=43 width=9 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=52 x=18 y=43 width=9 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=51 x=27 y=43 width=9 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=50 x=36 y=43 width=8 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=121 x=44 y=43 width=12 height=12 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=113 x=56 y=43 width=11 height=12 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=112 x=67 y=43 width=11 height=12 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=108 x=78 y=43 width=10 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=107 x=88 y=43 width=11 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=105 x=99 y=43 width=10 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=104 x=109 y=43 width=11 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=103 x=120 y=43 width=11 height=12 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=102 x=131 y=43 width=10 height=12 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=100 x=141 y=43 width=11 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=98 x=152 y=43 width=11 height=12 xoffset=0 yoffset=3 xadvance=9 page=0 chnl=0
char id=252 x=163 y=43 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=248 x=174 y=43 width=10 height=11 xoffset=0 yoffset=5 xadvance=9 page=0 chnl=0
char id=246 x=184 y=43 width=10 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=239 x=194 y=43 width=10 height=11 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=235 x=204 y=43 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=228 x=215 y=43 width=10 height=11 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=222 x=225 y=43 width=10 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=208 x=235 y=43 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=198 x=0 y=55 width=12 height=11 xoffset=-1 yoffset=4 xadvance=9 page=0 chnl=0
char id=191 x=12 y=55 width=9 height=11 xoffset=1 yoffset=7 xadvance=9 page=0 chnl=0
char id=190 x=21 y=55 width=12 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=189 x=33 y=55 width=12 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=188 x=45 y=55 width=12 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=177 x=57 y=55 width=9 height=11 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=174 x=66 y=55 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=169 x=77 y=55 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=165 x=88 y=55 width=12 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=163 x=100 y=55 width=10 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=161 x=110 y=55 width=4 height=11 xoffset=3 yoffset=7 xadvance=9 page=0 chnl=0
char id=38 x=114 y=55 width=9 height=11 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=37 x=123 y=55 width=9 height=11 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=63 x=132 y=55 width=8 height=11 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=33 x=140 y=55 width=4 height=11 xoffset=3 yoffset=4 xadvance=9 page=0 chnl=0
char id=49 x=144 y=55 width=10 height=11 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=116 x=154 y=55 width=9 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=90 x=163 y=55 width=9 height=11 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=89 x=172 y=55 width=12 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=88 x=184 y=55 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=87 x=195 y=55 width=13 height=11 xoffset=-1 yoffset=4 xadvance=9 page=0 chnl=0
char id=86 x=208 y=55 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=85 x=219 y=55 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=84 x=230 y=55 width=10 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=83 x=240 y=55 width=9 height=11 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=82 x=0 y=66 width=12 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=80 x=12 y=66 width=10 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=79 x=22 y=66 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=78 x=33 y=66 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=77 x=44 y=66 width=13 height=11 xoffset=-1 yoffset=4 xadvance=9 page=0 chnl=0
char id=76 x=57 y=66 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=75 x=68 y=66 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=74 x=79 y=66 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=73 x=90 y=66 width=10 height=11 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=72 x=100 y=66 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=71 x=111 y=66 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=70 x=122 y=66 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=69 x=133 y=66 width=10 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=68 x=143 y=66 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=67 x=154 y=66 width=11 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=66 x=165 y=66 width=10 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=65 x=175 y=66 width=12 height=11 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=247 x=187 y=66 width=10 height=10 xoffset=0 yoffset=5 xadvance=9 page=0 chnl=0
char id=62 x=197 y=66 width=10 height=10 xoffset=0 yoffset=5 xadvance=9 page=0 chnl=0
char id=60 x=207 y=66 width=11 height=10 xoffset=0 yoffset=5 xadvance=9 page=0 chnl=0
char id=59 x=218 y=66 width=6 height=10 xoffset=2 yoffset=7 xadvance=9 page=0 chnl=0
char id=230 x=224 y=66 width=12 height=9 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=164 x=236 y=66 width=11 height=9 xoffset=0 yoffset=4 xadvance=9 page=0 chnl=0
char id=43 x=0 y=77 width=9 height=9 xoffset=1 yoffset=5 xadvance=9 page=0 chnl=0
char id=122 x=9 y=77 width=9 height=9 xoffset=1 yoffset=6 xadvance=9 page=0 chnl=0
char id=120 x=18 y=77 width=11 height=9 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=119 x=29 y=77 width=13 height=9 xoffset=-1 yoffset=6 xadvance=9 page=0 chnl=0
char id=118 x=42 y=77 width=11 height=9 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=117 x=53 y=77 width=11 height=9 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=115 x=64 y=77 width=9 height=9 xoffset=1 yoffset=6 xadvance=9 page=0 chnl=0
char id=114 x=73 y=77 width=10 height=9 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=111 x=83 y=77 width=10 height=9 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=110 x=93 y=77 width=11 height=9 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=109 x=104 y=77 width=12 height=9 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=101 x=116 y=77 width=11 height=9 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=0
char id=97 x=127 y=77 width=10 height=9 xoffset=1 yoffset=6 xadvance=9 page=0 chnl=0
char id=42 x=137 y=77 width=10 height=8 xoffset=1 yoffset=4 xadvance=9 page=0 chnl=0
char id=58 x=147 y=77 width=3 height=8 xoffset=3 yoffset=7 xadvance=9 page=0 chnl=0
char id=99 x=150 y=77 width=10 height=8 xoffset=0 yoffset=7 xadvance=9 page=0 chnl=0
char id=215 x=160 y=77 width=7 height=7 xoffset=1 yoffset=7 xadvance=9 page=0 chnl=0
char id=187 x=167 y=77 width=10 height=7 xoffset=0 yoffset=8 xadvance=9 page=0 chnl=0
char id=186 x=177 y=77 width=8 height=7 xoffset=2 yoffset=3 xadvance=9 page=0 chnl=0
char id=184 x=185 y=77 width=5 height=7 xoffset=3 yoffset=12 xadvance=9 page=0 chnl=0
char id=178 x=190 y=77 width=7 height=7 xoffset=2 yoffset=3 xadvance=9 page=0 chnl=0
char id=172 x=197 y=77 width=11 height=7 xoffset=0 yoffset=8 xadvance=9 page=0 chnl=0
char id=171 x=208 y=77 width=11 height=7 xoffset=0 yoffset=8 xadvance=9 page=0 chnl=0
char id=94 x=219 y=77 width=8 height=7 xoffset=1 yoffset=3 xadvance=9 page=0 chnl=0
char id=44 x=227 y=77 width=4 height=7 xoffset=3 yoffset=11 xadvance=9 page=0 chnl=0
char id=39 x=231 y=77 width=4 height=7 xoffset=4 yoffset=4 xadvance=9 page=0 chnl=0
char id=34 x=235 y=77 width=8 height=7 xoffset=2 yoffset=4 xadvance=9 page=0 chnl=0
char id=185 x=243 y=77 width=5 height=6 xoffset=3 yoffset=4 xadvance=9 page=0 chnl=0
char id=179 x=248 y=77 width=7 height=6 xoffset=3 yoffset=4 xadvance=9 page=0 chnl=0
char id=170 x=0 y=86 width=7 height=6 xoffset=2 yoffset=4 xadvance=9 page=0 chnl=0
char id=180 x=7 y=86 width=4 height=5 xoffset=3 yoffset=2 xadvance=9 page=0 chnl=0
char id=176 x=11 y=86 width=5 height=5 xoffset=2 yoffset=4 xadvance=9 page=0 chnl=0
char id=126 x=16 y=86 width=9 height=5 xoffset=1 yoffset=7 xadvance=9 page=0 chnl=0
char id=61 x=25 y=86 width=11 height=5 xoffset=0 yoffset=7 xadvance=9 page=0 chnl=0
char id=96 x=36 y=86 width=4 height=5 xoffset=3 yoffset=2 xadvance=9 page=0 chnl=0
char id=183 x=40 y=86 width=3 height=3 xoffset=3 yoffset=8 xadvance=9 page=0 chnl=0
char id=175 x=43 y=86 width=13 height=3 xoffset=-1 yoffset=1 xadvance=9 page=0 chnl=0
char id=168 x=56 y=86 width=6 height=3 xoffset=2 yoffset=4 xadvance=9 page=0 chnl=0
char id=95 x=62 y=86 width=13 height=3 xoffset=-1 yoffset=17 xadvance=9 page=0 chnl=0
char id=45 x=75 y=86 width=9 height=3 xoffset=1 yoffset=8 xadvance=9 page=0 chnl=0
char id=46 x=84 y=86 width=3 height=3 xoffset=3 yoffset=12 xadvance=9 page=0 chnl=0
kernings count=-1

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,131 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" >
<svg width="800" height="600">
<defs>
<font horiz-adv-x="904">
<font-face font-family="Helvetica" units-per-em="2048" ascent="1854" descent="-434" baseline="0"/>
<missing-glyph horiz-adv-x="569" />
<glyph unicode=" " horiz-adv-x="569" />
<glyph unicode="!" horiz-adv-x="569" d="M231 364L176 1141V1466H399V1141L347 364H231zM184 0V205H391V0H184z"/>
<glyph unicode="&quot;" horiz-adv-x="727" d="M144 947L94 1226V1466H299V1226L254 947H144zM475 947L426 1226V1466H631V1226L583 947H475z"/>
<glyph unicode="#" horiz-adv-x="1139" d="M103 -25L190 401H21V550H220L294 913H21V1062H324L411 1491H561L474 1062H789L876 1491H1027L940 1062H1113V913H910L835 550H1113V401H805L718 -25H568L654 401H340L253 -25H103zM370 550H684L759 913H444L370
550z"/>
<glyph unicode="$" horiz-adv-x="1139" d="M510 -211V-31Q375 -14 291 29T145 170T73 407L254 441Q275 296 328 228Q404 132 510 121V694Q399 715 283 780Q197 828 151 913T104 1106Q104 1298 240 1417Q331 1497 510 1515V1601H616V1515Q773 1500 865 1423Q983
1325 1007 1154L821 1126Q805 1232 755 1288T616 1363V844Q752 810 796 791Q880 754 933 701T1014 575T1043 417Q1043 230 924 105T616 -29V-211H510zM510 1365Q405 1349 345 1281T284 1120Q284 1028 335 966T510 867V1365zM616 121Q721 134 789 212T858 405Q858
503 810 562T616 669V121z"/>
<glyph unicode="%" horiz-adv-x="1821" d="M119 1114Q119 1271 198 1381T427 1491Q565 1491 655 1393T746 1103Q746 917 655 817T429 716Q296 716 208 815T119 1114zM432 1367Q365 1367 321 1309T276 1096Q276 955 321 898T432 840Q500 840 544 898T589
1110Q589 1252 544 1309T432 1367zM433 -54L1235 1491H1381L582 -54H433zM1067 344Q1067 502 1146 611T1376 721Q1514 721 1604 623T1695 333Q1695 147 1604 47T1377 -54Q1244 -54 1156 45T1067 344zM1381 597Q1313 597 1269 539T1224 326Q1224 186 1269 128T1380
70Q1449 70 1493 128T1538 340Q1538 482 1493 539T1381 597z"/>
<glyph unicode="&amp;" horiz-adv-x="1366" d="M973 173Q884 74 779 25T552 -25Q327 -25 195 127Q88 251 88 404Q88 540 175 649T437 842Q338 956 305 1027T272 1164Q272 1296 375 1393T636 1491Q786 1491 881 1399T977 1178Q977 969 700 821L963 486Q1008
574 1033 690L1220 650Q1172 458 1090 334Q1191 200 1319 109L1198 -34Q1089 36 973 173zM607 937Q724 1006 758 1058T793 1173Q793 1248 746 1295T627 1343Q554 1343 506 1296T457 1181Q457 1147 474 1110T527 1030L607 937zM860 315L530 724Q384 637 333 563T282
415Q282 326 353 230T554 134Q635 134 721 184T860 315z"/>
<glyph unicode="&apos;" horiz-adv-x="391" d="M136 947L90 1221V1466H295V1221L247 947H136z"/>
<glyph unicode="(" horiz-adv-x="682" d="M479 -431Q330 -243 227 9T124 531Q124 769 201 987Q291 1240 479 1491H608Q487 1283 448 1194Q387 1056 352 906Q309 719 309 530Q309 49 608 -431H479z"/>
<glyph unicode=")" horiz-adv-x="682" d="M253 -431H124Q423 49 423 530Q423 718 380 903Q346 1053 285 1191Q246 1281 124 1491H253Q441 1240 531 987Q608 769 608 531Q608 261 505 9T253 -431z"/>
<glyph unicode="*" horiz-adv-x="797" d="M64 1197L110 1339Q269 1283 341 1242Q322 1423 321 1491H466Q463 1392 443 1243Q546 1295 679 1339L725 1197Q598 1155 476 1141Q537 1088 648 952L528 867Q470 946 391 1082Q317 941 261 867L143 952Q259 1095
309 1141Q180 1166 64 1197z"/>
<glyph unicode="+" horiz-adv-x="1196" d="M513 237V639H114V807H513V1206H683V807H1082V639H683V237H513z"/>
<glyph unicode="," horiz-adv-x="569" d="M182 0V205H387V0Q387 -113 347 -182T220 -290L170 -213Q227 -188 254 -140T284 0H182z"/>
<glyph unicode="-" horiz-adv-x="682" d="M65 440V621H618V440H65z"/>
<glyph unicode="." horiz-adv-x="569" d="M186 0V205H391V0H186z"/>
<glyph unicode="/" horiz-adv-x="569" d="M0 -25L425 1491H569L145 -25H0z"/>
<glyph unicode="0" horiz-adv-x="1139" d="M85 723Q85 983 138 1141T297 1386T563 1472Q681 1472 770 1425T917 1288T1008 1070T1041 723Q1041 465 988 307T830 62T563 -25Q351 -25 230 127Q85 310 85 723zM270 723Q270 362 354 243T563 123Q687 123 771
243T856 723Q856 1085 772 1204T561 1323Q437 1323 363 1218Q270 1084 270 723z"/>
<glyph unicode="1" horiz-adv-x="1139" d="M763 0H583V1147Q518 1085 413 1023T223 930V1104Q374 1175 487 1276T647 1472H763V0z"/>
<glyph unicode="2" horiz-adv-x="1139" d="M1031 173V0H62Q60 65 83 125Q120 224 201 320T437 542Q676 738 760 852T844 1069Q844 1176 768 1249T568 1323Q438 1323 360 1245T281 1029L96 1048Q115 1255 239 1363T572 1472Q783 1472 906 1355T1029 1065Q1029
977 993 892T874 713T596 455Q434 319 388 271T312 173H1031z"/>
<glyph unicode="3" horiz-adv-x="1139" d="M86 387L266 411Q297 258 371 191T553 123Q680 123 767 211T855 429Q855 553 774 633T568 714Q517 714 441 694L461 852Q479 850 490 850Q605 850 697 910T789 1095Q789 1194 722 1259T549 1324Q444 1324 374 1258T284
1060L104 1092Q137 1273 254 1372T545 1472Q665 1472 766 1421T920 1280T974 1091Q974 996 923 918T772 794Q902 764 974 670T1046 433Q1046 241 906 108T552 -26Q359 -26 232 89T86 387z"/>
<glyph unicode="4" horiz-adv-x="1139" d="M662 0V351H26V516L695 1466H842V516H1040V351H842V0H662zM662 516V1177L203 516H662z"/>
<glyph unicode="5" horiz-adv-x="1139" d="M85 384L274 400Q295 262 371 193T556 123Q686 123 776 221T866 481Q866 635 780 724T553 813Q466 813 396 774T286 671L117 693L259 1446H988V1274H403L324 880Q456 972 601 972Q793 972 925 839T1057 497Q1057
298 941 153Q800 -25 556 -25Q356 -25 230 87T85 384z"/>
<glyph unicode="6" horiz-adv-x="1139" d="M1019 1107L840 1093Q816 1199 772 1247Q699 1324 592 1324Q506 1324 441 1276Q356 1214 307 1095T256 756Q321 855 415 903T612 951Q792 951 918 819T1045 476Q1045 338 986 220T822 38T586 -25Q361 -25 219 140T77
686Q77 1111 234 1304Q371 1472 603 1472Q776 1472 886 1375T1019 1107zM284 475Q284 382 323 297T434 168T583 123Q697 123 779 215T861 465Q861 617 780 704T576 792Q454 792 369 705T284 475z"/>
<glyph unicode="7" horiz-adv-x="1139" d="M97 1274V1447H1046V1307Q906 1158 769 911T556 403Q502 219 487 0H302Q305 173 370 418T556 890T815 1274H97z"/>
<glyph unicode="8" horiz-adv-x="1139" d="M362 795Q250 836 196 912T142 1094Q142 1254 257 1363T563 1472Q755 1472 872 1361T989 1089Q989 987 936 912T773 795Q908 751 978 653T1049 419Q1049 231 916 103T566 -25Q349 -25 216 103T83 424Q83 567 155
663T362 795zM326 1100Q326 996 393 930T567 864Q671 864 737 929T804 1090Q804 1189 736 1256T565 1324Q462 1324 394 1258T326 1100zM268 423Q268 346 304 274T413 163T568 123Q697 123 781 206T865 417Q865 547 779 632T562 717Q435 717 352 633T268 423z"/>
<glyph unicode="9" horiz-adv-x="1139" d="M112 339L285 355Q307 233 369 178T528 123Q611 123 673 161T776 262T843 434T870 654Q870 666 869 690Q815 604 722 551T519 497Q337 497 211 629T85 977Q85 1200 216 1336T546 1472Q689 1472 807 1395T987 1176T1049
763Q1049 482 988 316T807 62T524 -25Q352 -25 243 70T112 339zM849 986Q849 1141 767 1232T568 1323Q448 1323 359 1225T270 971Q270 831 354 744T563 656Q688 656 768 743T849 986z"/>
<glyph unicode=":" horiz-adv-x="569" d="M185 857V1062H390V857H185zM185 0V205H390V0H185z"/>
<glyph unicode=";" horiz-adv-x="569" d="M182 857V1062H387V857H182zM182 0V205H387V0Q387 -113 347 -182T220 -290L170 -213Q227 -188 254 -140T284 0H182z"/>
<glyph unicode="&lt;" horiz-adv-x="1196" d="M112 641V809L1083 1219V1040L313 724L1083 405V226L112 641z"/>
<glyph unicode="=" horiz-adv-x="1196" d="M1082 862H114V1030H1082V862zM1082 417H114V585H1082V417z"/>
<glyph unicode="&gt;" horiz-adv-x="1196" d="M1083 641L112 226V405L881 724L112 1040V1219L1083 809V641z"/>
<glyph unicode="?" horiz-adv-x="1139" d="M472 361Q471 397 471 415Q471 521 501 598Q523 656 572 715Q608 758 701 840T823 972T851 1079Q851 1184 769 1263T568 1343Q453 1343 376 1271T275 1046L90 1068Q115 1273 238 1382T565 1491Q780 1491 908 1374T1036
1091Q1036 995 991 914T815 717Q727 639 700 602T660 517T645 361H472zM461 0V205H666V0H461z"/>
<glyph unicode="@" horiz-adv-x="2079" d="M1161 163Q1096 88 1016 43T854 -3Q765 -3 681 49T545 209T492 446Q492 605 573 764T776 1004T1011 1084Q1098 1084 1177 1039T1313 900L1347 1055H1526L1382 384Q1352 244 1352 229Q1352 202 1372 183T1422 163Q1475
163 1561 224Q1675 304 1741 438T1808 716Q1808 883 1723 1028T1468 1260T1093 1347Q859 1347 666 1238T366 924T259 485Q259 240 365 63T673 -198T1120 -283Q1382 -283 1559 -195T1824 19H2005Q1954 -86 1830 -195T1535 -367T1123 -431Q901 -431 714 -374T394
-203T195 61Q111 250 111 469Q111 713 211 934Q333 1205 557 1349T1102 1493Q1350 1493 1547 1392T1859 1089Q1956 916 1956 713Q1956 423 1752 198Q1570 -4 1354 -4Q1285 -4 1243 17T1180 77Q1167 102 1161 163zM677 434Q677 297 742 221T891 145Q947 145 1009
178T1127 278T1220 445T1256 649Q1256 785 1189 860T1024 935Q960 935 904 903T794 798T709 623T677 434z"/>
<glyph unicode="A" horiz-adv-x="1366" d="M-3 0L560 1466H769L1369 0H1148L977 444H364L203 0H-3zM420 602H917L764 1008Q694 1193 660 1312Q632 1171 581 1032L420 602z"/>
<glyph unicode="B" horiz-adv-x="1366" d="M150 0V1466H700Q868 1466 969 1422T1128 1285T1186 1091Q1186 997 1135 914T981 780Q1114 741 1185 647T1257 425Q1257 322 1214 234T1106 97T946 25T709 0H150zM344 850H661Q790 850 846 867Q920 889 957 940T995 1068Q995 1141 960 1196T860 1272T637 1293H344V850zM344 173H709Q803 173 841 180Q908 192 953 220T1027 301T1056 425Q1056 507 1014 567T898 652T683 677H344V173z"/>
<glyph unicode="C" horiz-adv-x="1479" d="M1204 514L1398 465Q1337 226 1179 101T791 -25Q554 -25 406 71T180 351T102 744Q102 973 189 1143T438 1402T794 1491Q1014 1491 1164 1379T1373 1064L1182 1019Q1131 1179 1034 1252T790 1325Q621 1325 508 1244T348 1027T302 745Q302 558 356 419T526 210T775 141Q938 141 1051 235T1204 514z"/>
<glyph unicode="D" horiz-adv-x="1479" d="M158 0V1466H663Q834 1466 924 1445Q1050 1416 1139 1340Q1255 1242 1312 1090T1370 741Q1370 574 1331 445T1231 232T1098 99T923 25T687 0H158zM352 173H665Q810 173 892 200T1024 276Q1093 345 1131 461T1170 744Q1170 974 1095 1097T911 1263Q833 1293 660 1293H352V173z"/>
<glyph unicode="E" horiz-adv-x="1366" d="M162 0V1466H1222V1293H356V844H1167V672H356V173H1256V0H162z"/>
<glyph unicode="F" horiz-adv-x="1251" d="M168 0V1466H1157V1293H362V839H1050V666H362V0H168z"/>
<glyph unicode="G" horiz-adv-x="1593" d="M844 575V747L1465 748V204Q1322 90 1170 33T858 -25Q642 -25 466 67T199 335T109 726Q109 940 198 1125T456 1401T843 1491Q1002 1491 1130 1440T1332 1296T1443 1056L1268 1008Q1235 1120 1186 1184T1046 1286T844 1325Q711 1325 614 1285T458 1178T365 1033Q309 897 309 738Q309 542 376 410T573 214T847 150Q973 150 1093 198T1275 302V575H844z"/>
<glyph unicode="H" horiz-adv-x="1479" d="M164 0V1466H358V864H1120V1466H1314V0H1120V691H358V0H164z"/>
<glyph unicode="I" horiz-adv-x="569" d="M191 0V1466H385V0H191z"/>
<glyph unicode="J" horiz-adv-x="1024" d="M59 416L234 440Q241 272 297 210T452 148Q525 148 578 181T651 272T671 456V1466H865V467Q865 283 821 182T680 28T453 -25Q260 -25 158 86T59 416z"/>
<glyph unicode="K" horiz-adv-x="1366" d="M150 0V1466H344V739L1072 1466H1335L720 872L1362 0H1106L584 742L344 508V0H150z"/>
<glyph unicode="L" horiz-adv-x="1139" d="M150 0V1466H344V173H1066V0H150z"/>
<glyph unicode="M" horiz-adv-x="1706" d="M152 0V1466H444L791 428Q839 283 861 211Q886 291 939 446L1290 1466H1551V0H1364V1227L938 0H763L339 1248V0H152z"/>
<glyph unicode="N" horiz-adv-x="1479" d="M156 0V1466H355L1125 315V1466H1311V0H1112L342 1152V0H156z"/>
<glyph unicode="O" horiz-adv-x="1593" d="M99 714Q99 1079 295 1285T801 1492Q1004 1492 1167 1395T1415 1125T1501 731Q1501 508 1411 332T1156 66T800 -25Q593 -25 430 75T183 348T99 714zM299 711Q299 446 441 294T799 141Q1018 141 1159 295T1301 732Q1301 911 1241 1044T1064 1251T802 1325Q596 1325 448 1184T299 711z"/>
<glyph unicode="P" horiz-adv-x="1366" d="M158 0V1466H711Q857 1466 934 1452Q1042 1434 1115 1384T1232 1242T1277 1042Q1277 855 1158 726T728 596H352V0H158zM352 769H731Q919 769 998 839T1077 1036Q1077 1128 1031 1193T908 1280Q859 1293 727 1293H352V769z"/>
<glyph unicode="Q" horiz-adv-x="1593" d="M1269 157Q1404 64 1518 21L1461 -114Q1303 -57 1146 66Q983 -25 786 -25Q587 -25 425 71T176 341T88 733Q88 950 176 1128T426 1399T790 1492Q993 1492 1156 1396T1404 1126T1490 734Q1490 553 1435 409T1269 157zM842 405Q1010 358 1119 265Q1290 421 1290 734Q1290 912 1230 1045T1053 1251T791 1325Q574 1325 431 1177T288 733Q288 447 429 294T791 141Q895 141 987 180Q896 239 795 264L842 405z"/>
<glyph unicode="R" horiz-adv-x="1479" d="M161 0V1466H811Q1007 1466 1109 1427T1272 1287T1333 1066Q1333 910 1232 803T920 667Q997 630 1037 594Q1122 516 1198 399L1453 0H1209L1015 305Q930 437 875 507T777 605T688 644Q655 651 580 651H355V0H161zM355 819H772Q905 819 980 846T1094 934T1133 1066Q1133 1170 1058 1237T819 1304H355V819z"/>
<glyph unicode="S" horiz-adv-x="1366" d="M92 471L275 487Q288 377 335 307T483 193T708 149Q819 149 904 182T1030 272T1072 398Q1072 467 1032 518T900 605Q841 628 639 676T356 768Q251 823 200 904T148 1087Q148 1198 211 1294T395 1441T664 1491Q827 1491 951 1439T1143 1284T1215 1053L1029 1039Q1014 1178 928 1249T672 1320Q496 1320 416 1256T335 1100Q335 1021 392 970Q448 919 684 866T1009 772Q1137 713 1198 623T1259 414Q1259 297 1192 194T1000 33T717 -25Q518 -25 384 33T173 207T92 471z"/>
<glyph unicode="T" horiz-adv-x="1251" d="M531 0V1293H48V1466H1210V1293H725V0H531z"/>
<glyph unicode="U" horiz-adv-x="1479" d="M1120 1466H1314V619Q1314 398 1264 268T1084 57T741 -25Q535 -25 404 46T217 251T161 619V1466H355V620Q355 429 390 339T512 199T724 150Q938 150 1029 247T1120 620V1466z"/>
<glyph unicode="V" horiz-adv-x="1366" d="M577 0L9 1466H219L600 401Q646 273 677 161Q711 281 756 401L1152 1466H1350L776 0H577z"/>
<glyph unicode="W" horiz-adv-x="1933" d="M414 0L25 1466H224L447 505Q483 354 509 205Q565 440 575 476L854 1466H1088L1298 724Q1377 448 1412 205Q1440 344 1485 524L1715 1466H1910L1508 0H1321L1012 1117Q973 1257 966 1289Q943 1188 923 1117L612 0H414z"/>
<glyph unicode="X" horiz-adv-x="1366" d="M9 0L576 764L76 1466H307L573 1090Q656 973 691 910Q740 990 807 1077L1102 1466H1313L798 775L1353 0H1113L744 523Q713 568 680 621Q631 541 610 511L242 0H9z"/>
<glyph unicode="Y" horiz-adv-x="1366" d="M571 0V621L6 1466H242L531 1024Q611 900 680 776Q746 891 840 1035L1124 1466H1350L765 621V0H571z"/>
<glyph unicode="Z" horiz-adv-x="1251" d="M41 0V180L792 1119Q872 1219 944 1293H126V1466H1176V1293L353 276L264 173H1200V0H41z"/>
<glyph unicode="[" horiz-adv-x="569" d="M139 -407V1466H536V1317H319V-258H536V-407H139z"/>
<glyph unicode="\" horiz-adv-x="569" d="M425 -25L0 1491H145L569 -25H425z"/>
<glyph unicode="]" horiz-adv-x="569" d="M436 -407H39V-258H256V1317H39V1466H436V-407z"/>
<glyph unicode="^" horiz-adv-x="961" d="M239 690H54L407 1491H552L907 690H726L479 1287L239 690z"/>
<glyph unicode="_" horiz-adv-x="1139" d="M-31 -407V-277H1162V-407H-31z"/>
<glyph unicode="`" horiz-adv-x="682" d="M465 1194H320L89 1474H330L465 1194z"/>
<glyph unicode="a" horiz-adv-x="1139" d="M828 131Q728 46 636 11T437 -24Q262 -24 168 61T74 280Q74 358 109 422T202 526T332 585Q385 599 492 612Q710 638 813 674Q814 711 814 721Q814 831 763 876Q694 937 558 937Q431 937 371 893T281 735L105 759Q129 872 184 941T343 1048T584 1086Q720 1086 805 1054T930 974T986 851Q995 805 995 685V445Q995 194 1006 128T1052 0H864Q836 56 828 131zM813 533Q715 493 519 465Q408 449 362 429T291 371T266 285Q266 213 320 165T480 117Q584 117 665 162T784 287Q813 348 813 467V533z"/>
<glyph unicode="b" horiz-adv-x="1139" d="M301 0H134V1466H314V943Q428 1086 605 1086Q703 1086 790 1047T934 936T1023 763T1055 547Q1055 274 920 125T596 -24Q408 -24 301 133V0zM299 539Q299 348 351 263Q436 124 581 124Q699 124 785 226T871 532Q871 740 789 839T589 938Q471 938 385 836T299 539z"/>
<glyph unicode="c" horiz-adv-x="1024" d="M828 389L1005 366Q976 183 857 80T563 -24Q345 -24 213 118T80 527Q80 699 137 828T310 1021T564 1086Q737 1086 847 999T988 750L813 723Q788 830 725 884T571 938Q435 938 350 841T265 532Q265 318 347 221T561 124Q667 124 738 189T828 389z"/>
<glyph unicode="d" horiz-adv-x="1139" d="M824 0V134Q723 -24 527 -24Q400 -24 294 46T129 241T70 530Q70 689 123 818T282 1017T519 1086Q615 1086 690 1046T812 940V1466H991V0H824zM255 530Q255 326 341 225T544 124Q662 124 744 220T827 515Q827 733 743 835T536 937Q416 937 336 839T255 530z"/>
<glyph unicode="e" horiz-adv-x="1139" d="M862 342L1048 319Q1004 156 885 66T581 -24Q348 -24 212 119T75 522Q75 790 213 938T571 1086Q784 1086 919 941T1054 533Q1054 517 1053 485H261Q271 310 360 217T582 124Q681 124 751 176T862 342zM271 633H864Q852 767 796 834Q710 938 573 938Q449 938 365 855T271 633z"/>
<glyph unicode="f" horiz-adv-x="569" d="M178 0V922H19V1062H178V1175Q178 1282 197 1334Q223 1404 288 1447T472 1491Q548 1491 640 1473L613 1316Q557 1326 507 1326Q425 1326 391 1291T357 1160V1062H564V922H357V0H178z"/>
<glyph unicode="g" horiz-adv-x="1139" d="M102 -88L277 -114Q288 -195 338 -232Q405 -282 521 -282Q646 -282 714 -232T806 -92Q820 -37 819 139Q701 0 525 0Q306 0 186 158T66 537Q66 689 121 817T280 1016T526 1086Q714 1086 836 934V1062H1002V144Q1002 -104 952 -207T792 -371T522 -431Q332 -431 215 -346T102 -88zM251 550Q251 341 334 245T542 149Q666 149 750 244T834 544Q834 739 748 838T539 937Q419 937 335 840T251 550z"/>
<glyph unicode="h" horiz-adv-x="1139" d="M135 0V1466H315V940Q441 1086 633 1086Q751 1086 838 1040T962 911T1000 673V0H820V673Q820 808 762 869T596 931Q516 931 446 890T345 777T315 581V0H135z"/>
<glyph unicode="i" horiz-adv-x="455" d="M136 1259V1466H316V1259H136zM136 0V1062H316V0H136z"/>
<glyph unicode="j" horiz-adv-x="455" d="M134 1257V1466H314V1257H134zM-94 -412L-60 -259Q-6 -273 25 -273Q80 -273 107 -237T134 -54V1062H314V-58Q314 -254 263 -331Q198 -431 47 -431Q-26 -431 -94 -412z"/>
<glyph unicode="k" horiz-adv-x="1024" d="M136 0V1466H316V630L742 1062H975L569 668L1016 0H794L443 543L316 421V0H136z"/>
<glyph unicode="l" horiz-adv-x="455" d="M131 0V1466H311V0H131z"/>
<glyph unicode="m" horiz-adv-x="1706" d="M135 0V1062H296V913Q346 991 429 1038T618 1086Q736 1086 811 1037T918 900Q1044 1086 1246 1086Q1404 1086 1489 999T1574 729V0H1395V669Q1395 777 1378 824T1314 901T1206 930Q1094 930 1020 856T946 617V0H766V690Q766 810 722 870T578 930Q502 930 438 890T344 773T315 551V0H135z"/>
<glyph unicode="n" horiz-adv-x="1139" d="M135 0V1062H297V911Q414 1086 635 1086Q731 1086 811 1052T932 961T988 828Q998 778 998 653V0H818V646Q818 756 797 810T723 897T597 930Q482 930 399 857T315 580V0H135z"/>
<glyph unicode="o" horiz-adv-x="1139" d="M68 531Q68 826 232 968Q369 1086 566 1086Q785 1086 924 943T1063 546Q1063 341 1002 224T823 41T566 -24Q343 -24 206 119T68 531zM253 531Q253 327 342 226T566 124Q700 124 789 226T878 537Q878 734 789 835T566 937Q431 937 342 836T253 531z"/>
<glyph unicode="p" horiz-adv-x="1139" d="M135 -407V1062H299V924Q357 1005 430 1045T607 1086Q743 1086 847 1016T1004 819T1057 539Q1057 376 999 246T829 46T594 -24Q504 -24 433 14T315 110V-407H135zM298 525Q298 320 381 222T582 124Q702 124 787 225T873 540Q873 743 790 844T590 945Q475 945 387 838T298 525z"/>
<glyph unicode="q" horiz-adv-x="1139" d="M812 -407V113Q770 54 695 15T534 -24Q345 -24 209 127T72 541Q72 701 127 828T288 1020T520 1086Q717 1086 830 920V1062H992V-407H812zM257 534Q257 329 343 227T549 124Q664 124 747 221T830 518Q830 730 743 837T537 944Q420 944 339 845T257 534z"/>
<glyph unicode="r" horiz-adv-x="682" d="M133 0V1062H295V901Q357 1014 409 1050T525 1086Q616 1086 710 1028L648 861Q582 900 516 900Q457 900 410 865T343 766Q313 670 313 556V0H133z"/>
<glyph unicode="s" horiz-adv-x="1024" d="M63 317L241 345Q256 238 324 181T516 124Q640 124 700 174T760 293Q760 354 707 389Q670 413 523 450Q325 500 249 536T133 637T93 780Q93 851 125 911T214 1012Q256 1043 328 1064T484 1086Q609 1086 703 1050T843 953T905 788L729 764Q717 846 660 892T497 938Q373 938 320 897T267 801Q267 766 289 738Q311 709 358 690Q385 680 517 644Q708 593 783 561T902 466T945 312Q945 222 893 143T741 20T517 -24Q310 -24 202 62T63 317z"/>
<glyph unicode="t" horiz-adv-x="569" d="M528 161L554 2Q478 -14 418 -14Q320 -14 266 17T190 98T168 311V922H36V1062H168V1325L347 1433V1062H528V922H347V301Q347 224 356 202T387 167T449 154Q479 154 528 161z"/>
<glyph unicode="u" horiz-adv-x="1139" d="M831 0V156Q707 -24 494 -24Q400 -24 319 12T198 102T142 236Q131 289 131 404V1062H311V473Q311 332 322 283Q339 212 394 172T530 131Q611 131 682 172T782 285T812 493V1062H992V0H831z"/>
<glyph unicode="v" horiz-adv-x="1024" d="M430 0L26 1062H216L444 426Q481 323 512 212Q536 296 579 414L815 1062H1000L598 0H430z"/>
<glyph unicode="w" horiz-adv-x="1479" d="M331 0L6 1062H192L361 449L424 221Q428 238 479 440L648 1062H833L992 446L1045 243L1106 448L1288 1062H1463L1131 0H944L775 636L734 817L519 0H331z"/>
<glyph unicode="x" horiz-adv-x="1024" d="M15 0L403 552L44 1062H269L432 813Q478 742 506 694Q550 760 587 811L766 1062H981L614 562L1009 0H788L570 330L512 419L233 0H15z"/>
<glyph unicode="y" horiz-adv-x="1024" d="M127 -409L107 -240Q166 -256 210 -256Q270 -256 306 -236T365 -180Q382 -153 420 -46Q425 -31 436 -2L33 1062H227L448 447Q491 330 525 201Q556 325 599 443L826 1062H1006L602 -18Q537 -193 501 -259Q453 -348 391 -389T243 -431Q191 -431 127 -409z"/>
<glyph unicode="z" horiz-adv-x="1024" d="M40 0V146L716 922Q601 916 513 916H80V1062H948V943L373 269L262 146Q383 155 489 155H980V0H40z"/>
<glyph unicode="{" horiz-adv-x="684" d="M57 612Q134 614 182 653T247 762T264 998T270 1218Q279 1302 303 1353T364 1434T456 1481Q494 1491 580 1491H636V1334H605Q501 1334 467 1297T433 1129Q433 867 422 798Q404 691 361 633T224 530Q334 484 383
390T433 80Q433 -115 437 -152Q445 -220 477 -247T605 -274H636V-431H580Q482 -431 438 -415Q374 -392 332 -341T278 -210T264 49T247 298T183 407T57 449V612z"/>
<glyph unicode="|" horiz-adv-x="532" d="M188 -431V1491H345V-431H188z"/>
<glyph unicode="}" horiz-adv-x="684" d="M626 612V449Q549 447 501 407T436 299T419 63T413 -157Q404 -242 380 -292T319 -373T227 -420Q189 -431 103 -431H47V-274H78Q182 -274 216 -237T250 -68Q250 182 259 249Q275 360 323 426T459 530Q344 585 297
675T250 981Q250 1176 245 1214Q238 1281 206 1307T78 1334H47V1491H103Q201 1491 245 1475Q309 1453 351 1401T405 1270T419 1011T436 763T500 654T626 612z"/>
<glyph unicode="~" horiz-adv-x="1196" d="M87 557V762Q193 882 365 882Q425 882 491 865T679 795Q748 766 782 757T852 748Q917 748 986 787T1110 885V673Q1046 613 981 586T833 559Q773 559 719 573T546 640T348 693Q284 693 228 666T87 557z"/>
</font>
</defs>
<g font-family="Helvetica" font-size="18" fill="black">
<text x="20" y="60"> !&quot;#$%&amp;&apos;()*+,-./0123456789:;&lt;&gt;?</text>
<text x="20" y="120">@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_</text>
<text x="20" y="180">`abcdefghijklmnopqrstuvwxyz{|}~</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,4 @@
<BODY>
This package contains the default data required for the basic functions of YASL. Currently this includes a default
font to ensure text can always be displayed.
</BODY>

View File

@@ -0,0 +1,253 @@
package org.newdawn.slick.fills;
import org.newdawn.slick.Color;
import org.newdawn.slick.ShapeFill;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Vector2f;
/**
* A fill effect used to define gradients when filling and drawing shapes. A gradient is defined
* by two control points. Each point that is rendered is coloured based on it's proximity to the
* points. Note that the points are defined relative to the center of the shape being drawn. This
* is with the intention that the gradient fills can be used and do not need to be updated when
* the geometry is moved
*
* @author kevin
*/
public class GradientFill implements ShapeFill {
/** The contant offset */
private Vector2f none = new Vector2f(0,0);
/** The start position of the gradient */
private Vector2f start;
/** The end poisition of the gradient */
private Vector2f end;
/** The starting colour of the gradient */
private Color startCol;
/** The ending colour of the gradient */
private Color endCol;
/** True if the graident is defined in shape coordinates */
private boolean local = false;
/**
* Create a gradient fill
*
* @param sx The x coordinate of the starting control point
* @param sy The y coordinate of the starting control point
* @param startCol The colour to apply at the starting control point
* @param ex The x coordinate of the ending control point
* @param ey The y coordinate of the ending control point
* @param endCol The colour to apply at the ending control point
*/
public GradientFill(float sx, float sy, Color startCol, float ex, float ey, Color endCol)
{
this(sx,sy,startCol,ex,ey,endCol,false);
}
/**
* Create a gradient fill
*
* @param sx The x coordinate of the starting control point
* @param sy The y coordinate of the starting control point
* @param startCol The colour to apply at the starting control point
* @param ex The x coordinate of the ending control point
* @param ey The y coordinate of the ending control point
* @param endCol The colour to apply at the ending control point
* @param local True if the gradient is defined in local shape coordinates
*/
public GradientFill(float sx, float sy, Color startCol, float ex, float ey, Color endCol, boolean local)
{
this(new Vector2f(sx,sy), startCol, new Vector2f(ex,ey), endCol, local);
}
/**
* Create a gradient fill
*
* @param start The position of the starting control point
* @param startCol The colour to apply at the starting control point
* @param end The position of the ending control point
* @param endCol The colour to apply at the ending control point
* @param local True if the gradient is defined in local shape coordinates
*/
public GradientFill(Vector2f start, Color startCol, Vector2f end, Color endCol, boolean local) {
this.start = new Vector2f(start);
this.end = new Vector2f(end);
this.startCol = new Color(startCol);
this.endCol = new Color(endCol);
this.local = local;
}
/**
* Get an inverted copy of the gradient
*
* @return The copy with the colours inverted
*/
public GradientFill getInvertedCopy() {
return new GradientFill(start, endCol, end, startCol, local);
}
/**
* Indicate if the gradient is defined in shape local coordinates
*
* @param local True if the gradient is defined in shape local coordinates
*/
public void setLocal(boolean local) {
this.local = local;
}
/**
* Get the position of the start control point
*
* @return The position of the start control point
*/
public Vector2f getStart() {
return start;
}
/**
* Get the position of the end control point
*
* @return The position of the end control point
*/
public Vector2f getEnd() {
return end;
}
/**
* Get the colour at the start control point
*
* @return The color at the start control point
*/
public Color getStartColor() {
return startCol;
}
/**
* Get the colour at the end control point
*
* @return The color at the end control point
*/
public Color getEndColor() {
return endCol;
}
/**
* Set the start point's position
*
* @param x The x coordinate of the start control point
* @param y The y coordinate of the start control point
*/
public void setStart(float x, float y) {
setStart(new Vector2f(x,y));
}
/**
* Set the start control point's position
*
* @param start The new poisition for the start point
*/
public void setStart(Vector2f start) {
this.start = new Vector2f(start);
}
/**
* Set the end control point's position
*
* @param x The x coordinate of the end control point
* @param y The y coordinate of the end control point
*/
public void setEnd(float x, float y) {
setEnd(new Vector2f(x,y));
}
/**
* Set the end control point's position
*
* @param end The new position for the end point
*/
public void setEnd(Vector2f end) {
this.end = new Vector2f(end);
}
/**
* Set the colour to apply at the start control's position
*
* @param color The colour to apply at the start control point
*/
public void setStartColor(Color color) {
this.startCol = new Color(color);
}
/**
* Set the colour to apply at the end control's position
*
* @param color The colour to apply at the end control point
*/
public void setEndColor(Color color) {
this.endCol = new Color(color);
}
/**
* Get the colour that should be applied at the specified location
*
* @param shape The shape being filled
* @param x The x coordinate of the point being coloured
* @param y The y coordinate of the point being coloured
* @return The colour that should be applied based on the control points of this gradient
*/
public Color colorAt(Shape shape, float x, float y) {
if (local) {
return colorAt(x-shape.getCenterX(),y-shape.getCenterY());
} else {
return colorAt(x,y);
}
}
/**
* Get the colour that should be applied at the specified location
*
* @param x The x coordinate of the point being coloured
* @param y The y coordinate of the point being coloured
* @return The colour that should be applied based on the control points of this gradient
*/
public Color colorAt(float x, float y) {
float dx1 = end.getX() - start.getX();
float dy1 = end.getY() - start.getY();
float dx2 = -dy1;
float dy2 = dx1;
float denom = (dy2 * dx1) - (dx2 * dy1);
if (denom == 0) {
return Color.black;
}
float ua = (dx2 * (start.getY() - y)) - (dy2 * (start.getX() - x));
ua /= denom;
float ub = (dx1 * (start.getY() - y)) - (dy1 * (start.getX() - x));
ub /= denom;
float u = ua;
if (u < 0) {
u = 0;
}
if (u > 1) {
u = 1;
}
float v = 1 - u;
// u is the proportion down the line we are
Color col = new Color(1,1,1,1);
col.r = (u * endCol.r) + (v * startCol.r);
col.b = (u * endCol.b) + (v * startCol.b);
col.g = (u * endCol.g) + (v * startCol.g);
col.a = (u * endCol.a) + (v * startCol.a);
return col;
}
/**
* @see org.newdawn.slick.ShapeFill#getOffsetAt(org.newdawn.slick.geom.Shape, float, float)
*/
public Vector2f getOffsetAt(Shape shape, float x, float y) {
return none;
}
}

View File

@@ -0,0 +1,3 @@
<BODY>
Fill effects used to colour and mogrify shapes during rendering
</BODY>

View File

@@ -0,0 +1,152 @@
package org.newdawn.slick.font;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.GlyphMetrics;
import java.awt.font.GlyphVector;
import org.newdawn.slick.Image;
import org.newdawn.slick.UnicodeFont;
/**
* Represents the glyph in a font for a unicode codepoint.
*
* @author Nathan Sweet <misc@n4te.com>
*/
public class Glyph {
/** The code point in which this glyph is found */
private int codePoint;
/** The width of this glyph in pixels */
private short width;
/** The height of this glyph in pixels */
private short height;
/** The offset on the y axis to draw the glyph at */
private short yOffset;
/** True if the glyph isn't defined */
private boolean isMissing;
/** The shape drawn for this glyph */
private Shape shape;
/** The image generated for this glyph */
private Image image;
/**
* Create a new glyph
*
* @param codePoint The code point in which this glyph can be found
* @param bounds The bounds that this glrph can fill
* @param vector The vector this glyph is part of
* @param index The index of this glyph within the vector
* @param unicodeFont The font this glyph forms part of
*/
public Glyph(int codePoint, Rectangle bounds, GlyphVector vector, int index, UnicodeFont unicodeFont) {
this.codePoint = codePoint;
GlyphMetrics metrics = vector.getGlyphMetrics(index);
int lsb = (int)metrics.getLSB();
if (lsb > 0) lsb = 0;
int rsb = (int)metrics.getRSB();
if (rsb > 0) rsb = 0;
int glyphWidth = bounds.width - lsb - rsb;
int glyphHeight = bounds.height;
if (glyphWidth > 0 && glyphHeight > 0) {
int padTop = unicodeFont.getPaddingTop();
int padRight = unicodeFont.getPaddingRight();
int padBottom = unicodeFont.getPaddingBottom();
int padLeft = unicodeFont.getPaddingLeft();
int glyphSpacing = 1; // Needed to prevent filtering problems.
width = (short)(glyphWidth + padLeft + padRight + glyphSpacing);
height = (short)(glyphHeight + padTop + padBottom + glyphSpacing);
yOffset = (short)(unicodeFont.getAscent() + bounds.y - padTop);
}
shape = vector.getGlyphOutline(index, -bounds.x + unicodeFont.getPaddingLeft(), -bounds.y + unicodeFont.getPaddingTop());
isMissing = !unicodeFont.getFont().canDisplay((char)codePoint);
}
/**
* The unicode codepoint the glyph represents.
*
* @return The codepoint the glyph represents
*/
public int getCodePoint () {
return codePoint;
}
/**
* Returns true if the font does not have a glyph for this codepoint.
*
* @return True if this glyph is not defined in the given code point
*/
public boolean isMissing () {
return isMissing;
}
/**
* The width of the glyph's image.
*
* @return The width in pixels of the glyphs image
*/
public int getWidth () {
return width;
}
/**
* The height of the glyph's image.
*
* @return The height in pixels of the glyphs image
*/
public int getHeight () {
return height;
}
/**
* The shape to use to draw this glyph. This is set to null after the glyph is stored
* in a GlyphPage.
*
* @return The shape drawn for this glyph
*/
public Shape getShape () {
return shape;
}
/**
* Set the shape that should be drawn for this glyph
*
* @param shape The shape that should be drawn for this glyph
*/
public void setShape(Shape shape) {
this.shape = shape;
}
/**
* The image to use for this glyph. This is null until after the glyph is stored in a
* GlyphPage.
*
* @return The image that has been generated for this glyph
*/
public Image getImage () {
return image;
}
/**
* Set the image that has been generated for this glyph
*
* @param image The image that has been generated for this glyph
*/
public void setImage(Image image) {
this.image = image;
}
/**
* The distance from drawing y location to top of this glyph, causing the glyph to sit
* on the baseline.
*
* @return The offset on the y axis this glyph should be drawn at
*/
public int getYOffset() {
return yOffset;
}
}

View File

@@ -0,0 +1,260 @@
package org.newdawn.slick.font;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.newdawn.slick.Color;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.font.effects.Effect;
import org.newdawn.slick.opengl.TextureImpl;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
/**
* Stores a number of glyphs on a single texture.
*
* @author Nathan Sweet <misc@n4te.com>
*/
public class GlyphPage {
/** The interface to OpenGL */
private static final SGL GL = Renderer.get();
/** The maxium size of an individual glyph */
public static final int MAX_GLYPH_SIZE = 256;
/** A temporary working buffer */
private static ByteBuffer scratchByteBuffer = ByteBuffer.allocateDirect(MAX_GLYPH_SIZE * MAX_GLYPH_SIZE * 4);
static {
scratchByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
}
/** A temporary working buffer */
private static IntBuffer scratchIntBuffer = scratchByteBuffer.asIntBuffer();
/** A temporary image used to generate the glyph page */
private static BufferedImage scratchImage = new BufferedImage(MAX_GLYPH_SIZE, MAX_GLYPH_SIZE, BufferedImage.TYPE_INT_ARGB);
/** The graphics context form the temporary image */
private static Graphics2D scratchGraphics = (Graphics2D)scratchImage.getGraphics();
static {
scratchGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
scratchGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
scratchGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
}
/** The render context in which the glyphs will be generated */
public static FontRenderContext renderContext = scratchGraphics.getFontRenderContext();
/**
* Get the scratch graphics used to generate the page of glyphs
*
* @return The scratch graphics used to build the page
*/
public static Graphics2D getScratchGraphics() {
return scratchGraphics;
}
/** The font this page is part of */
private final UnicodeFont unicodeFont;
/** The width of this page's image */
private final int pageWidth;
/** The height of this page's image */
private final int pageHeight;
/** The image containing the glyphs */
private final Image pageImage;
/** The x position of the page */
private int pageX;
/** The y position of the page */
private int pageY;
/** The height of the last row on the page */
private int rowHeight;
/** True if the glyphs are ordered */
private boolean orderAscending;
/** The list of glyphs on this page */
private final List pageGlyphs = new ArrayList(32);
/**
* Create a new page of glyphs
*
* @param unicodeFont The font this page forms part of
* @param pageWidth The width of the backing texture.
* @param pageHeight The height of the backing texture.
* @throws SlickException if the backing texture could not be created.
*/
public GlyphPage(UnicodeFont unicodeFont, int pageWidth, int pageHeight) throws SlickException {
this.unicodeFont = unicodeFont;
this.pageWidth = pageWidth;
this.pageHeight = pageHeight;
pageImage = new Image(pageWidth, pageHeight);
}
/**
* Loads glyphs to the backing texture and sets the image on each loaded glyph. Loaded glyphs are removed from the list.
*
* If this page already has glyphs and maxGlyphsToLoad is -1, then this method will return 0 if all the new glyphs don't fit.
* This reduces texture binds when drawing since glyphs loaded at once are typically displayed together.
* @param glyphs The glyphs to load.
* @param maxGlyphsToLoad This is the maximum number of glyphs to load from the list. Set to -1 to attempt to load all the
* glyphs.
* @return The number of glyphs that were actually loaded.
* @throws SlickException if the glyph could not be rendered.
*/
public int loadGlyphs (List glyphs, int maxGlyphsToLoad) throws SlickException {
if (rowHeight != 0 && maxGlyphsToLoad == -1) {
// If this page has glyphs and we are not loading incrementally, return zero if any of the glyphs don't fit.
int testX = pageX;
int testY = pageY;
int testRowHeight = rowHeight;
for (Iterator iter = getIterator(glyphs); iter.hasNext();) {
Glyph glyph = (Glyph)iter.next();
int width = glyph.getWidth();
int height = glyph.getHeight();
if (testX + width >= pageWidth) {
testX = 0;
testY += testRowHeight;
testRowHeight = height;
} else if (height > testRowHeight) {
testRowHeight = height;
}
if (testY + testRowHeight >= pageWidth) return 0;
testX += width;
}
}
Color.white.bind();
pageImage.bind();
int i = 0;
for (Iterator iter = getIterator(glyphs); iter.hasNext();) {
Glyph glyph = (Glyph)iter.next();
int width = Math.min(MAX_GLYPH_SIZE, glyph.getWidth());
int height = Math.min(MAX_GLYPH_SIZE, glyph.getHeight());
if (rowHeight == 0) {
// The first glyph always fits.
rowHeight = height;
} else {
// Wrap to the next line if needed, or break if no more fit.
if (pageX + width >= pageWidth) {
if (pageY + rowHeight + height >= pageHeight) break;
pageX = 0;
pageY += rowHeight;
rowHeight = height;
} else if (height > rowHeight) {
if (pageY + height >= pageHeight) break;
rowHeight = height;
}
}
renderGlyph(glyph, width, height);
pageGlyphs.add(glyph);
pageX += width;
iter.remove();
i++;
if (i == maxGlyphsToLoad) {
// If loading incrementally, flip orderAscending so it won't change, since we'll probably load the rest next time.
orderAscending = !orderAscending;
break;
}
}
TextureImpl.bindNone();
// Every other batch of glyphs added to a page are sorted the opposite way to attempt to keep same size glyps together.
orderAscending = !orderAscending;
return i;
}
/**
* Loads a single glyph to the backing texture, if it fits.
*
* @param glyph The glyph to be rendered
* @param width The expected width of the glyph
* @param height The expected height of the glyph
* @throws SlickException if the glyph could not be rendered.
*/
private void renderGlyph(Glyph glyph, int width, int height) throws SlickException {
// Draw the glyph to the scratch image using Java2D.
scratchGraphics.setComposite(AlphaComposite.Clear);
scratchGraphics.fillRect(0, 0, MAX_GLYPH_SIZE, MAX_GLYPH_SIZE);
scratchGraphics.setComposite(AlphaComposite.SrcOver);
scratchGraphics.setColor(java.awt.Color.white);
for (Iterator iter = unicodeFont.getEffects().iterator(); iter.hasNext();)
((Effect)iter.next()).draw(scratchImage, scratchGraphics, unicodeFont, glyph);
glyph.setShape(null); // The shape will never be needed again.
WritableRaster raster = scratchImage.getRaster();
int[] row = new int[width];
for (int y = 0; y < height; y++) {
raster.getDataElements(0, y, width, 1, row);
scratchIntBuffer.put(row);
}
GL.glTexSubImage2D(SGL.GL_TEXTURE_2D, 0, pageX, pageY, width, height, SGL.GL_BGRA, SGL.GL_UNSIGNED_BYTE,
scratchByteBuffer);
scratchIntBuffer.clear();
glyph.setImage(pageImage.getSubImage(pageX, pageY, width, height));
}
/**
* Returns an iterator for the specified glyphs, sorted either ascending or descending.
*
* @param glyphs The glyphs to return if present
* @return An iterator of the sorted list of glyphs
*/
private Iterator getIterator(List glyphs) {
if (orderAscending) return glyphs.iterator();
final ListIterator iter = glyphs.listIterator(glyphs.size());
return new Iterator() {
public boolean hasNext () {
return iter.hasPrevious();
}
public Object next () {
return iter.previous();
}
public void remove () {
iter.remove();
}
};
}
/**
* Returns the glyphs stored on this page.
*
* @return A list of {@link Glyph} elements on this page
*/
public List getGlyphs () {
return pageGlyphs;
}
/**
* Returns the backing texture for this page.
*
* @return The image of this page of glyphs
*/
public Image getImage () {
return pageImage;
}
}

View File

@@ -0,0 +1,380 @@
package org.newdawn.slick.font;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.font.effects.ConfigurableEffect;
import org.newdawn.slick.font.effects.ConfigurableEffect.Value;
import org.newdawn.slick.util.ResourceLoader;
/**
* Holds the settings needed to configure a UnicodeFont.
*
* @author Nathan Sweet <misc@n4te.com>
*/
public class HieroSettings {
/** The size of the font to be generated */
private int fontSize = 12;
/** True if the font is rendered bold */
private boolean bold = false;
/** True fi the font if rendered italic */
private boolean italic = false;
/** The padding applied in pixels to the top of the glyph rendered area */
private int paddingTop;
/** The padding applied in pixels to the left of the glyph rendered area */
private int paddingLeft;
/** The padding applied in pixels to the bottom of the glyph rendered area */
private int paddingBottom;
/** The padding applied in pixels to the right of the glyph rendered area */
private int paddingRight;
/** The padding applied in pixels to horizontal advance for each glyph */
private int paddingAdvanceX;
/** The padding applied in pixels to vertical advance for each glyph */
private int paddingAdvanceY;
/** The width of the glyph page generated */
private int glyphPageWidth = 512;
/** The height of the glyph page generated */
private int glyphPageHeight = 512;
/** The list of effects applied */
private final List effects = new ArrayList();
/**
* Default constructor for injection
*/
public HieroSettings() {
}
/**
* Create a new set of configuration from a file
*
* @param hieroFileRef The file system or classpath location of the Hiero settings file.
* @throws SlickException if the file could not be read.
*/
public HieroSettings(String hieroFileRef) throws SlickException {
this(ResourceLoader.getResourceAsStream(hieroFileRef));
}
/**
* Create a new set of configuration from a file
*
* @param in The stream from which to read the settings from
* @throws SlickException if the file could not be read.
*/
public HieroSettings(InputStream in) throws SlickException {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
while (true) {
String line = reader.readLine();
if (line == null) break;
line = line.trim();
if (line.length() == 0) continue;
String[] pieces = line.split("=", 2);
String name = pieces[0].trim();
String value = pieces[1];
if (name.equals("font.size")) {
fontSize = Integer.parseInt(value);
} else if (name.equals("font.bold")) {
bold = Boolean.valueOf(value).booleanValue();
} else if (name.equals("font.italic")) {
italic = Boolean.valueOf(value).booleanValue();
} else if (name.equals("pad.top")) {
paddingTop = Integer.parseInt(value);
} else if (name.equals("pad.right")) {
paddingRight = Integer.parseInt(value);
} else if (name.equals("pad.bottom")) {
paddingBottom = Integer.parseInt(value);
} else if (name.equals("pad.left")) {
paddingLeft = Integer.parseInt(value);
} else if (name.equals("pad.advance.x")) {
paddingAdvanceX = Integer.parseInt(value);
} else if (name.equals("pad.advance.y")) {
paddingAdvanceY = Integer.parseInt(value);
} else if (name.equals("glyph.page.width")) {
glyphPageWidth = Integer.parseInt(value);
} else if (name.equals("glyph.page.height")) {
glyphPageHeight = Integer.parseInt(value);
} else if (name.equals("effect.class")) {
try {
effects.add(Class.forName(value).newInstance());
} catch (Exception ex) {
throw new SlickException("Unable to create effect instance: " + value, ex);
}
} else if (name.startsWith("effect.")) {
// Set an effect value on the last added effect.
name = name.substring(7);
ConfigurableEffect effect = (ConfigurableEffect)effects.get(effects.size() - 1);
List values = effect.getValues();
for (Iterator iter = values.iterator(); iter.hasNext();) {
Value effectValue = (Value)iter.next();
if (effectValue.getName().equals(name)) {
effectValue.setString(value);
break;
}
}
effect.setValues(values);
}
}
reader.close();
} catch (Exception ex) {
throw new SlickException("Unable to load Hiero font file", ex);
}
}
/**
* @see UnicodeFont#getPaddingTop()
*
* @return The padding for the top of the glyph area in pixels
*/
public int getPaddingTop () {
return paddingTop;
}
/**
* @see UnicodeFont#setPaddingTop(int)
*
* @param paddingTop The padding for the top of the glyph area in pixels
*/
public void setPaddingTop(int paddingTop) {
this.paddingTop = paddingTop;
}
/**
* @see UnicodeFont#getPaddingLeft()
*
* @return The padding for the left of the glyph area in pixels
*/
public int getPaddingLeft() {
return paddingLeft;
}
/**
* @see UnicodeFont#setPaddingLeft(int)
*
* @param paddingLeft The padding for the left of the glyph area in pixels
*/
public void setPaddingLeft(int paddingLeft) {
this.paddingLeft = paddingLeft;
}
/**
* @see UnicodeFont#getPaddingBottom()
*
* @return The padding for the bottom of the glyph area in pixels
*/
public int getPaddingBottom() {
return paddingBottom;
}
/**
* @see UnicodeFont#setPaddingBottom(int)
*
* @param paddingBottom The padding for the bottom of the glyph area in pixels
*/
public void setPaddingBottom(int paddingBottom) {
this.paddingBottom = paddingBottom;
}
/**
* @see UnicodeFont#getPaddingRight()
*
* @return The padding for the right of the glyph area in pixels
*/
public int getPaddingRight() {
return paddingRight;
}
/**
* @see UnicodeFont#setPaddingRight(int)
*
* @param paddingRight The padding for the right of the glyph area in pixels
*/
public void setPaddingRight(int paddingRight) {
this.paddingRight = paddingRight;
}
/**
* @see UnicodeFont#getPaddingAdvanceX()
*
* @return The padding for the horizontal advance of each glyph
*/
public int getPaddingAdvanceX() {
return paddingAdvanceX;
}
/**
* @see UnicodeFont#setPaddingAdvanceX(int)
*
* @param paddingAdvanceX The padding for the horizontal advance of each glyph
*/
public void setPaddingAdvanceX(int paddingAdvanceX) {
this.paddingAdvanceX = paddingAdvanceX;
}
/**
* @see UnicodeFont#getPaddingAdvanceY()
*
* @return The padding for the vertical advance of each glyph
*/
public int getPaddingAdvanceY() {
return paddingAdvanceY;
}
/**
* @see UnicodeFont#setPaddingAdvanceY(int)
*
* @param paddingAdvanceY The padding for the vertical advance of each glyph
*/
public void setPaddingAdvanceY(int paddingAdvanceY) {
this.paddingAdvanceY = paddingAdvanceY;
}
/**
* @see UnicodeFont#getGlyphPageWidth()
*
* @return The width of the generate glyph pages
*/
public int getGlyphPageWidth() {
return glyphPageWidth;
}
/**
* @see UnicodeFont#setGlyphPageWidth(int)
*
* @param glyphPageWidth The width of the generate glyph pages
*/
public void setGlyphPageWidth(int glyphPageWidth) {
this.glyphPageWidth = glyphPageWidth;
}
/**
* @see UnicodeFont#getGlyphPageHeight()
*
* @return The height of the generate glyph pages
*/
public int getGlyphPageHeight() {
return glyphPageHeight;
}
/**
* @see UnicodeFont#setGlyphPageHeight(int)
*
* @param glyphPageHeight The height of the generate glyph pages
*/
public void setGlyphPageHeight(int glyphPageHeight) {
this.glyphPageHeight = glyphPageHeight;
}
/**
* @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
* @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
*
* @return The point size of the font generated
*/
public int getFontSize() {
return fontSize;
}
/**
* @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
* @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
*
* @param fontSize The point size of the font generated
*/
public void setFontSize (int fontSize) {
this.fontSize = fontSize;
}
/**
* @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
* @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
*
* @return True if the font was generated in bold typeface
*/
public boolean isBold () {
return bold;
}
/**
* @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
* @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
*
* @param bold True if the font was generated in bold typeface
*/
public void setBold (boolean bold) {
this.bold = bold;
}
/**
* @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
* @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
*
* @return True if the font was generated in italic typeface
*/
public boolean isItalic () {
return italic;
}
/**
* @see UnicodeFont#UnicodeFont(String, int, boolean, boolean)
* @see UnicodeFont#UnicodeFont(java.awt.Font, int, boolean, boolean)
*
* @param italic True if the font was generated in italic typeface
*/
public void setItalic (boolean italic) {
this.italic = italic;
}
/**
* @see UnicodeFont#getEffects()
*
* @return The list of effects applied to the text
*/
public List getEffects() {
return effects;
}
/**
* Saves the settings to a file.
*
* @param file The file we're saving to
* @throws IOException if the file could not be saved.
*/
public void save(File file) throws IOException {
PrintStream out = new PrintStream(new FileOutputStream(file));
out.println("font.size=" + fontSize);
out.println("font.bold=" + bold);
out.println("font.italic=" + italic);
out.println();
out.println("pad.top=" + paddingTop);
out.println("pad.right=" + paddingRight);
out.println("pad.bottom=" + paddingBottom);
out.println("pad.left=" + paddingLeft);
out.println("pad.advance.x=" + paddingAdvanceX);
out.println("pad.advance.y=" + paddingAdvanceY);
out.println();
out.println("glyph.page.width=" + glyphPageWidth);
out.println("glyph.page.height=" + glyphPageHeight);
out.println();
for (Iterator iter = effects.iterator(); iter.hasNext();) {
ConfigurableEffect effect = (ConfigurableEffect)iter.next();
out.println("effect.class=" + effect.getClass().getName());
for (Iterator iter2 = effect.getValues().iterator(); iter2.hasNext();) {
Value value = (Value)iter2.next();
out.println("effect." + value.getName() + "=" + value.getString());
}
out.println();
}
out.close();
}
}

View File

@@ -0,0 +1,92 @@
package org.newdawn.slick.font.effects;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.font.Glyph;
/**
* Makes glyphs a solid color.
*
* @author Nathan Sweet <misc@n4te.com>
*/
public class ColorEffect implements ConfigurableEffect {
/** The colour that will be applied across the text */
private Color color = Color.white;
/**
* Default constructor for injection
*/
public ColorEffect() {
}
/**
* Create a new effect to colour the text
*
* @param color The colour to apply across the text
*/
public ColorEffect(Color color) {
this.color = color;
}
/**
* @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
*/
public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
g.setColor(color);
g.fill(glyph.getShape());
}
/**
* Get the colour being applied by this effect
*
* @return The colour being applied by this effect
*/
public Color getColor() {
return color;
}
/**
* Set the colour being applied by this effect
*
* @param color The colour being applied by this effect
*/
public void setColor(Color color) {
if (color == null) throw new IllegalArgumentException("color cannot be null.");
this.color = color;
}
/**
* @see java.lang.Object#toString()
*/
public String toString () {
return "Color";
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect#getValues()
*/
public List getValues() {
List values = new ArrayList();
values.add(EffectUtil.colorValue("Color", color));
return values;
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect#setValues(java.util.List)
*/
public void setValues(List values) {
for (Iterator iter = values.iterator(); iter.hasNext();) {
Value value = (Value)iter.next();
if (value.getName().equals("Color")) {
setColor((Color)value.getObject());
}
}
}
}

View File

@@ -0,0 +1,53 @@
package org.newdawn.slick.font.effects;
import java.util.List;
/**
* An effect that has a number of configuration values. This allows the effect to be configured in the Hiero GUI and to be saved
* and loaded to and from a file.
*
* @author Nathan Sweet <misc@n4te.com>
*/
public interface ConfigurableEffect extends Effect {
/**
* Returns the list of {@link Value}s for this effect. This list is not typically backed by the effect, so changes to the
* values will not take affect until {@link #setValues(List)} is called.
*/
public List getValues();
/**
* Sets the list of {@link Value}s for this effect.
*/
public void setValues(List values);
/**
* Represents a configurable value for an effect.
*/
static public interface Value {
/**
* Returns the name of the value.
*/
public String getName ();
/**
* Sets the string representation of the value.
*/
public void setString (String value);
/**
* Gets the string representation of the value.
*/
public String getString ();
/**
* Gets the object representation of the value.
*/
public Object getObject ();
/**
* Shows a dialog allowing a user to configure this value.
*/
public void showDialog ();
}
}

View File

@@ -0,0 +1,25 @@
package org.newdawn.slick.font.effects;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.font.Glyph;
/**
* A graphical effect that is applied to glyphs in a {@link UnicodeFont}.
*
* @author Nathan Sweet <misc@n4te.com>
*/
public interface Effect {
/**
* Called to draw the effect.
*
* @param image The image to draw into
* @param g The graphics context to use for applying the effect
* @param unicodeFont The font being rendered
* @param glyph The particular glyph being rendered
*/
public void draw (BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph);
}

View File

@@ -0,0 +1,368 @@
package org.newdawn.slick.font.effects;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.SpinnerNumberModel;
import org.newdawn.slick.font.GlyphPage;
import org.newdawn.slick.font.effects.ConfigurableEffect.Value;
/**
* Provides utility methods for effects.
*
* @author Nathan Sweet <misc@n4te.com>
*/
public class EffectUtil {
/** A graphics 2D temporary surface to be used when generating effects */
static private BufferedImage scratchImage = new BufferedImage(GlyphPage.MAX_GLYPH_SIZE, GlyphPage.MAX_GLYPH_SIZE,
BufferedImage.TYPE_INT_ARGB);
/**
* Returns an image that can be used by effects as a temp image.
*
* @return The scratch image used for temporary operations
*/
static public BufferedImage getScratchImage() {
Graphics2D g = (Graphics2D)scratchImage.getGraphics();
g.setComposite(AlphaComposite.Clear);
g.fillRect(0, 0, GlyphPage.MAX_GLYPH_SIZE, GlyphPage.MAX_GLYPH_SIZE);
g.setComposite(AlphaComposite.SrcOver);
g.setColor(java.awt.Color.white);
return scratchImage;
}
/**
* Prompts the user for a colour value
*
* @param name Thename of the value being configured
* @param currentValue The default value that should be selected
* @return The value selected
*/
static public Value colorValue(String name, Color currentValue) {
return new DefaultValue(name, EffectUtil.toString(currentValue)) {
public void showDialog () {
Color newColor = JColorChooser.showDialog(null, "Choose a color", EffectUtil.fromString(value));
if (newColor != null) value = EffectUtil.toString(newColor);
}
public Object getObject () {
return EffectUtil.fromString(value);
}
};
}
/**
* Prompts the user for int value
*
* @param name The name of the dialog to show
* @param currentValue The current value to be displayed
* @param description The help text to provide
* @return The value selected by the user
*/
static public Value intValue (String name, final int currentValue, final String description) {
return new DefaultValue(name, String.valueOf(currentValue)) {
public void showDialog () {
JSpinner spinner = new JSpinner(new SpinnerNumberModel(currentValue, Short.MIN_VALUE, Short.MAX_VALUE, 1));
if (showValueDialog(spinner, description)) value = String.valueOf(spinner.getValue());
}
public Object getObject () {
return Integer.valueOf(value);
}
};
}
/**
* Prompts the user for float value
*
* @param name The name of the dialog to show
* @param currentValue The current value to be displayed
* @param description The help text to provide
* @param min The minimum value to allow
* @param max The maximum value to allow
* @return The value selected by the user
*/
static public Value floatValue (String name, final float currentValue, final float min, final float max,
final String description) {
return new DefaultValue(name, String.valueOf(currentValue)) {
public void showDialog () {
JSpinner spinner = new JSpinner(new SpinnerNumberModel(currentValue, min, max, 0.1f));
if (showValueDialog(spinner, description)) value = String.valueOf(((Double)spinner.getValue()).floatValue());
}
public Object getObject () {
return Float.valueOf(value);
}
};
}
/**
* Prompts the user for boolean value
*
* @param name The name of the dialog to show
* @param currentValue The current value to be displayed
* @param description The help text to provide
* @return The value selected by the user
*/
static public Value booleanValue (String name, final boolean currentValue, final String description) {
return new DefaultValue(name, String.valueOf(currentValue)) {
public void showDialog () {
JCheckBox checkBox = new JCheckBox();
checkBox.setSelected(currentValue);
if (showValueDialog(checkBox, description)) value = String.valueOf(checkBox.isSelected());
}
public Object getObject () {
return Boolean.valueOf(value);
}
};
}
/**
* Prompts the user for a value that represents a fixed number of options.
* All options are strings.
*
* @param options The first array has an entry for each option. Each entry is either a String[1] that is both the display value
* and actual value, or a String[2] whose first element is the display value and second element is the actual value.
*
* @param name The name of the value being prompted for
* @param currentValue The current value to show as default
* @param description The description of the value
* @return The value selected by the user
*/
static public Value optionValue (String name, final String currentValue, final String[][] options, final String description) {
return new DefaultValue(name, currentValue.toString()) {
public void showDialog () {
int selectedIndex = -1;
DefaultComboBoxModel model = new DefaultComboBoxModel();
for (int i = 0; i < options.length; i++) {
model.addElement(options[i][0]);
if (getValue(i).equals(currentValue)) selectedIndex = i;
}
JComboBox comboBox = new JComboBox(model);
comboBox.setSelectedIndex(selectedIndex);
if (showValueDialog(comboBox, description)) value = getValue(comboBox.getSelectedIndex());
}
private String getValue (int i) {
if (options[i].length == 1) return options[i][0];
return options[i][1];
}
public String toString () {
for (int i = 0; i < options.length; i++)
if (getValue(i).equals(value)) return options[i][0].toString();
return "";
}
public Object getObject () {
return value;
}
};
}
/**
* Convers a color to a string.
*
* @param color The color to encode to a string
* @return The colour as a string
*/
static public String toString (Color color) {
if (color == null) throw new IllegalArgumentException("color cannot be null.");
String r = Integer.toHexString(color.getRed());
if (r.length() == 1) r = "0" + r;
String g = Integer.toHexString(color.getGreen());
if (g.length() == 1) g = "0" + g;
String b = Integer.toHexString(color.getBlue());
if (b.length() == 1) b = "0" + b;
return r + g + b;
}
/**
* Converts a string to a color.
*
* @param rgb The string encoding the colour
* @return The colour represented by the given encoded string
*/
static public Color fromString (String rgb) {
if (rgb == null || rgb.length() != 6) return Color.white;
return new Color(Integer.parseInt(rgb.substring(0, 2), 16), Integer.parseInt(rgb.substring(2, 4), 16), Integer.parseInt(rgb
.substring(4, 6), 16));
}
/**
* Provides generic functionality for an effect's configurable value.
*/
static private abstract class DefaultValue implements Value {
/** The value being held */
String value;
/** The key/name of the value */
String name;
/**
* Create a default value
*
* @param name The name of the value being configured
* @param value The value to use for the default
*/
public DefaultValue(String name, String value) {
this.value = value;
this.name = name;
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect.Value#setString(java.lang.String)
*/
public void setString(String value) {
this.value = value;
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect.Value#getString()
*/
public String getString() {
return value;
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect.Value#getName()
*/
public String getName() {
return name;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
if (value == null) {
return "";
}
return value.toString();
}
/**
* Prompt the user for a value
*
* @param component The component to use as parent for the prompting dialog
* @param description The description of the value being prompted for
* @return True if the value was configured
*/
public boolean showValueDialog(final JComponent component, String description) {
ValueDialog dialog = new ValueDialog(component, name, description);
dialog.setTitle(name);
dialog.setLocationRelativeTo(null);
EventQueue.invokeLater(new Runnable() {
public void run () {
JComponent focusComponent = component;
if (focusComponent instanceof JSpinner)
focusComponent = ((JSpinner.DefaultEditor)((JSpinner)component).getEditor()).getTextField();
focusComponent.requestFocusInWindow();
}
});
dialog.setVisible(true);
return dialog.okPressed;
}
};
/**
* Provides generic functionality for a dialog to configure a value.
*/
static private class ValueDialog extends JDialog {
/** True if OK was pressed */
public boolean okPressed = false;
/**
* Create a new dialog to configure a specific value
*
* @param component The component to use as the parent of the dialog prompting the user
* @param name The name of the value being configured
* @param description The description of the value being configured
*/
public ValueDialog(JComponent component, String name, String description) {
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setLayout(new GridBagLayout());
setModal(true);
if (component instanceof JSpinner)
((JSpinner.DefaultEditor)((JSpinner)component).getEditor()).getTextField().setColumns(4);
JPanel descriptionPanel = new JPanel();
descriptionPanel.setLayout(new GridBagLayout());
getContentPane().add(
descriptionPanel,
new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0,
0), 0, 0));
descriptionPanel.setBackground(Color.white);
descriptionPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.black));
{
JTextArea descriptionText = new JTextArea(description);
descriptionPanel.add(descriptionText, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,
GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));
descriptionText.setWrapStyleWord(true);
descriptionText.setLineWrap(true);
descriptionText.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
descriptionText.setEditable(false);
}
JPanel panel = new JPanel();
getContentPane().add(
panel,
new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 0,
5), 0, 0));
panel.add(new JLabel(name + ":"));
panel.add(component);
JPanel buttonPanel = new JPanel();
getContentPane().add(
buttonPanel,
new GridBagConstraints(0, 2, 2, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE,
new Insets(0, 0, 0, 0), 0, 0));
{
JButton okButton = new JButton("OK");
buttonPanel.add(okButton);
okButton.addActionListener(new ActionListener() {
public void actionPerformed (ActionEvent evt) {
okPressed = true;
setVisible(false);
}
});
}
{
JButton cancelButton = new JButton("Cancel");
buttonPanel.add(cancelButton);
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed (ActionEvent evt) {
setVisible(false);
}
});
}
setSize(new Dimension(320, 175));
}
}
}

View File

@@ -0,0 +1,62 @@
package org.newdawn.slick.font.effects;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.font.Glyph;
/**
* Applys a {@link BufferedImageOp} filter to glyphs. Many filters can be found
* here: http://www.jhlabs.com/ip/filters/index.html
*
* @author Nathan Sweet <misc@n4te.com>
*/
public class FilterEffect implements Effect {
/** The filter to be applied */
private BufferedImageOp filter;
/**
* Default constructor for injection
*/
public FilterEffect () {
}
/**
* Create a new filtering effect based on a convolution operation
*
* @param filter The filter to apply
*/
public FilterEffect (BufferedImageOp filter) {
this.filter = filter;
}
/**
* @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
*/
public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
BufferedImage scratchImage = EffectUtil.getScratchImage();
filter.filter(image, scratchImage);
image.getGraphics().drawImage(scratchImage, 0, 0, null);
}
/**
* Get the filter being applied by this effect
*
* @return The filter being applied by this effect
*/
public BufferedImageOp getFilter() {
return filter;
}
/**
* Set the filter being applied by this effect
*
* @param filter The filter being used by this effect
*/
public void setFilter(BufferedImageOp filter) {
this.filter = filter;
}
}

View File

@@ -0,0 +1,195 @@
package org.newdawn.slick.font.effects;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.font.Glyph;
/**
* Paints glyphs with a gradient fill.
*
* @author Nathan Sweet <misc@n4te.com>
*/
public class GradientEffect implements ConfigurableEffect {
/** The top of gradients colour */
private Color topColor = Color.cyan;
/** The bottom of the gradient's colour */
private Color bottomColor = Color.blue;
/** The offset the gradient starts at */
private int offset = 0;
/** The scaling of the graident */
private float scale = 1;
/** True if the graident should cycle back and forth across the surface */
private boolean cyclic;
/**
* Default constructor for injection
*/
public GradientEffect() {
}
/**
* Create a new effect to apply a graident
*
* @param topColor The colour at the top of the graident
* @param bottomColor The colour at the bottom of the gradient
* @param scale The scale of the graident
*/
public GradientEffect(Color topColor, Color bottomColor, float scale) {
this.topColor = topColor;
this.bottomColor = bottomColor;
this.scale = scale;
}
/**
* @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
*/
public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
int ascent = unicodeFont.getAscent();
float height = (ascent) * scale;
float top = -glyph.getYOffset() + unicodeFont.getDescent() + offset + ascent / 2 - height / 2;
g.setPaint(new GradientPaint(0, top, topColor, 0, top + height, bottomColor, cyclic));
g.fill(glyph.getShape());
}
/**
* Get the colour at the top of the graident
*
* @return The colour at the top of the gradient
*/
public Color getTopColor() {
return topColor;
}
/**
* Set the colour at the top of the graident
*
* @param topColor The colour at the top of the graident
*/
public void setTopColor(Color topColor) {
this.topColor = topColor;
}
/**
* Get the colour at the bottom of the graident
*
* @return The colour at the bottom of the gradient
*/
public Color getBottomColor () {
return bottomColor;
}
/**
* Set the colour at the bottom of the graident
*
* @param bottomColor The colour at the bottom of the graident
*/
public void setBottomColor(Color bottomColor) {
this.bottomColor = bottomColor;
}
/**
* Get the offset the gradients starts at
*
* @return The offset the gradient starts at
*/
public int getOffset() {
return offset;
}
/**
* Sets the pixel offset to move the gradient up or down.
* The gradient is normally centered on the glyph.
*
* @param offset The offset the gradient is moved by
*/
public void setOffset (int offset) {
this.offset = offset;
}
/**
* Get the percentage scaling being applied to the gradient across the surface
*
* @return The scale of the graident
*/
public float getScale() {
return scale;
}
/**
* Changes the height of the gradient by a percentage. The gradient is
* normally the height of most glyphs in the font.
*
* @param scale The scale to apply
*/
public void setScale (float scale) {
this.scale = scale;
}
/**
* Check if the graident is repeating
*
* @return True if the gradient is repeating
*/
public boolean isCyclic() {
return cyclic;
}
/**
* If set to true, the gradient will repeat.
*
* @param cyclic True if the graident repeats
*/
public void setCyclic(boolean cyclic) {
this.cyclic = cyclic;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "Gradient";
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect#getValues()
*/
public List getValues() {
List values = new ArrayList();
values.add(EffectUtil.colorValue("Top color", topColor));
values.add(EffectUtil.colorValue("Bottom color", bottomColor));
values.add(EffectUtil.intValue("Offset", offset,
"This setting allows you to move the gradient up or down. The gradient is normally centered on the glyph."));
values.add(EffectUtil.floatValue("Scale", scale, 0, 1, "This setting allows you to change the height of the gradient by a"
+ "percentage. The gradient is normally the height of most glyphs in the font."));
values.add(EffectUtil.booleanValue("Cyclic", cyclic, "If this setting is checked, the gradient will repeat."));
return values;
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect#setValues(java.util.List)
*/
public void setValues(List values) {
for (Iterator iter = values.iterator(); iter.hasNext();) {
Value value = (Value)iter.next();
if (value.getName().equals("Top color")) {
topColor = (Color)value.getObject();
} else if (value.getName().equals("Bottom color")) {
bottomColor = (Color)value.getObject();
} else if (value.getName().equals("Offset")) {
offset = ((Integer)value.getObject()).intValue();
} else if (value.getName().equals("Scale")) {
scale = ((Float)value.getObject()).floatValue();
} else if (value.getName().equals("Cyclic")) {
cyclic = ((Boolean)value.getObject()).booleanValue();
}
}
}
}

View File

@@ -0,0 +1,178 @@
package org.newdawn.slick.font.effects;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.font.Glyph;
/**
* Strokes glyphs with an outline.
*
* @author Nathan Sweet <misc@n4te.com>
*/
public class OutlineEffect implements ConfigurableEffect {
/** The width of the outline in pixels */
private float width = 2;
/** The colour of the outline */
private Color color = Color.black;
/** The type of join at the line joins of the out line */
private int join = BasicStroke.JOIN_BEVEL;
/** The stroke used to draw the outline */
private Stroke stroke;
/**
* Default constructor for injection
*/
public OutlineEffect() {
}
/**
* Create a new effect to draw the outline of the text
*
* @param width The width of the outline
* @param color The colour of the outline
*/
public OutlineEffect(int width, Color color) {
this.width = width;
this.color = color;
}
/**
* @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
*/
public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
g = (Graphics2D)g.create();
if (stroke != null)
g.setStroke(stroke);
else
g.setStroke(getStroke());
g.setColor(color);
g.draw(glyph.getShape());
g.dispose();
}
/**
* Get the width of the outline being drawn
*
* @return The width of the outline being drawn
*/
public float getWidth() {
return width;
}
/**
* Sets the width of the outline. The glyphs will need padding so the
* outline doesn't get clipped.
*
* @param width The width of the outline being drawn
*/
public void setWidth (int width) {
this.width = width;
}
/**
* Get the colour of the outline being drawn
*
* @return The colour of the outline being drawn
*/
public Color getColor() {
return color;
}
/**
* Set the colour of the outline being drawn
*
* @param color The colour of the outline to draw
*/
public void setColor(Color color) {
this.color = color;
}
/**
* Get the join type as indicated by @see BasicStroke
*
* @return The join type between segments in the outline
*/
public int getJoin() {
return join;
}
/**
* Get the stroke being used to draw the outline
*
* @return The stroke being used to draw the outline
*/
public Stroke getStroke() {
if (stroke == null) {
return new BasicStroke(width, BasicStroke.CAP_SQUARE, join);
}
return stroke;
}
/**
* Sets the stroke to use for the outline. If this is set,
* the other outline settings are ignored.
*
* @param stroke The stroke to be used to draw the outline
*/
public void setStroke (Stroke stroke) {
this.stroke = stroke;
}
/**
* Sets how the corners of the outline are drawn. This is usually only noticeable
* at large outline widths.
*
* @param join One of: {@link BasicStroke#JOIN_BEVEL}, {@link BasicStroke#JOIN_MITER}, {@link BasicStroke#JOIN_ROUND}
*/
public void setJoin (int join) {
this.join = join;
}
/**
* @see java.lang.Object#toString()
*/
public String toString () {
return "Outline";
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect#getValues()
*/
public List getValues () {
List values = new ArrayList();
values.add(EffectUtil.colorValue("Color", color));
values.add(EffectUtil.floatValue("Width", width, 0.1f, 999, "This setting controls the width of the outline. "
+ "The glyphs will need padding so the outline doesn't get clipped."));
values.add(EffectUtil.optionValue("Join", String.valueOf(join), new String[][] { {"Bevel", BasicStroke.JOIN_BEVEL + ""},
{"Miter", BasicStroke.JOIN_MITER + ""}, {"Round", BasicStroke.JOIN_ROUND + ""}},
"This setting defines how the corners of the outline are drawn. "
+ "This is usually only noticeable at large outline widths."));
return values;
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect#setValues(java.util.List)
*/
public void setValues (List values) {
for (Iterator iter = values.iterator(); iter.hasNext();) {
Value value = (Value)iter.next();
if (value.getName().equals("Color")) {
color = (Color)value.getObject();
} else if (value.getName().equals("Width")) {
width = ((Float)value.getObject()).floatValue();
} else if (value.getName().equals("Join")) {
join = Integer.parseInt((String)value.getObject());
}
}
}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2006 Jerry Huxtable
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.newdawn.slick.font.effects;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.util.Iterator;
import java.util.List;
/**
* An effect that genrates a wobbly line around the outline of the text
*
* @author Jerry Huxtable
* @author Nathan Sweet <misc@n4te.com>
*/
public class OutlineWobbleEffect extends OutlineEffect {
/** How often the line wobbles */
private float detail = 1;
/** The amount of the line wobbles */
private float amplitude = 1;
/**
* Default constructor for injection
*/
public OutlineWobbleEffect () {
setStroke(new WobbleStroke());
}
/**
* Gets the detail of the wobble effect.
*
* @return The detail of the wobble effect
*/
public float getDetail() {
return detail;
}
/**
* Sets the detail of the wobble effect.
*
* @param detail The detail of the wobble effect
*/
public void setDetail(float detail) {
this.detail = detail;
}
/**
* Gets the amplitude of the wobble effect.
*
* @return The amplitude of the wobble effect
*/
public float getAmplitude() {
return amplitude;
}
/**
* Sets the amplitude of the wobble effect.
*
* @param amplitude The detail of the wobble effect
*/
public void setAmplitude(float amplitude) {
this.amplitude = amplitude;
}
/**
* Create a new effect that generates a wobbly line around the text
*
* @param width The width of the line
* @param color The colour of the line
*/
public OutlineWobbleEffect (int width, Color color) {
super(width, color);
}
/**
* @see org.newdawn.slick.font.effects.OutlineEffect#toString()
*/
public String toString() {
return "Outline (Wobble)";
}
/**
* @see org.newdawn.slick.font.effects.OutlineEffect#getValues()
*/
public List getValues() {
List values = super.getValues();
values.remove(2); // Remove "Join".
values.add(EffectUtil.floatValue("Detail", detail, 1, 50, "This setting controls how detailed the outline will be. "
+ "Smaller numbers cause the outline to have more detail."));
values.add(EffectUtil.floatValue("Amplitude", amplitude, 0.5f, 50, "This setting controls the amplitude of the outline."));
return values;
}
/**
* @see org.newdawn.slick.font.effects.OutlineEffect#setValues(java.util.List)
*/
public void setValues(List values) {
super.setValues(values);
for (Iterator iter = values.iterator(); iter.hasNext();) {
Value value = (Value)iter.next();
if (value.getName().equals("Detail")) {
detail = ((Float)value.getObject()).floatValue();
} else if (value.getName().equals("Amplitude")) {
amplitude = ((Float)value.getObject()).floatValue();
}
}
}
/**
* A stroke that generate a wobbly line
*
* @author Jerry Huxtable
* @author Nathan Sweet <misc@n4te.com>
*/
private class WobbleStroke implements Stroke {
/** The flattening factor of the stroke */
private static final float FLATNESS = 1;
/**
* @see java.awt.Stroke#createStrokedShape(java.awt.Shape)
*/
public Shape createStrokedShape (Shape shape) {
GeneralPath result = new GeneralPath();
shape = new BasicStroke(getWidth(), BasicStroke.CAP_SQUARE, getJoin()).createStrokedShape(shape);
PathIterator it = new FlatteningPathIterator(shape.getPathIterator(null), FLATNESS);
float points[] = new float[6];
float moveX = 0, moveY = 0;
float lastX = 0, lastY = 0;
float thisX = 0, thisY = 0;
int type = 0;
float next = 0;
while (!it.isDone()) {
type = it.currentSegment(points);
switch (type) {
case PathIterator.SEG_MOVETO:
moveX = lastX = randomize(points[0]);
moveY = lastY = randomize(points[1]);
result.moveTo(moveX, moveY);
next = 0;
break;
case PathIterator.SEG_CLOSE:
points[0] = moveX;
points[1] = moveY;
// Fall into....
case PathIterator.SEG_LINETO:
thisX = randomize(points[0]);
thisY = randomize(points[1]);
float dx = thisX - lastX;
float dy = thisY - lastY;
float distance = (float)Math.sqrt(dx * dx + dy * dy);
if (distance >= next) {
float r = 1.0f / distance;
while (distance >= next) {
float x = lastX + next * dx * r;
float y = lastY + next * dy * r;
result.lineTo(randomize(x), randomize(y));
next += detail;
}
}
next -= distance;
lastX = thisX;
lastY = thisY;
break;
}
it.next();
}
return result;
}
/**
* Get a random wobble factor
*
* @param x The position on the line
* @return The wobble factor
*/
private float randomize(float x) {
return x + (float)Math.random() * amplitude * 2 - 1;
}
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright 2006 Jerry Huxtable
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.newdawn.slick.font.effects;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.util.Iterator;
import java.util.List;
/**
* An effect to generate a uniformly zigzaging line around text
*
* @author Jerry Huxtable
* @author Nathan Sweet <misc@n4te.com>
*/
public class OutlineZigzagEffect extends OutlineEffect {
/** The amount the line moves away from the text */
private float amplitude = 1;
/** How often the line zigs and zags */
private float wavelength = 3;
/**
* Default constructor for injection
*/
public OutlineZigzagEffect() {
setStroke(new ZigzagStroke());
}
/**
* Gets the wavelength of the wobble effect.
*
* @return The wavelength of the wobble effect
*/
public float getWavelength() {
return wavelength;
}
/**
* Sets the wavelength of the wobble effect.
*
* @param wavelength The wavelength of the wobble effect
*/
public void setWavelength(float wavelength) {
this.wavelength = wavelength;
}
/**
* Gets the amplitude of the wobble effect.
*
* @return The amplitude of the wobble effect
*/
public float getAmplitude() {
return amplitude;
}
/**
* Sets the amplitude of the wobble effect.
*
* @param amplitude The detail of the wobble effect
*/
public void setAmplitude(float amplitude) {
this.amplitude = amplitude;
}
/**
* Create a new effect to generate a zigzagging line around the text
*
* @param width The width of the line
* @param color The colour of the line
*/
public OutlineZigzagEffect(int width, Color color) {
super(width, color);
}
/**
* @see org.newdawn.slick.font.effects.OutlineEffect#toString()
*/
public String toString () {
return "Outline (Zigzag)";
}
/**
* @see org.newdawn.slick.font.effects.OutlineEffect#getValues()
*/
public List getValues() {
List values = super.getValues();
values.add(EffectUtil.floatValue("Wavelength", wavelength, 1, 100, "This setting controls the wavelength of the outline. "
+ "The smaller the value, the more segments will be used to draw the outline."));
values.add(EffectUtil.floatValue("Amplitude", amplitude, 0.5f, 50, "This setting controls the amplitude of the outline. "
+ "The bigger the value, the more the zigzags will vary."));
return values;
}
/**
* @see org.newdawn.slick.font.effects.OutlineEffect#setValues(java.util.List)
*/
public void setValues(List values) {
super.setValues(values);
for (Iterator iter = values.iterator(); iter.hasNext();) {
Value value = (Value)iter.next();
if (value.getName().equals("Wavelength")) {
wavelength = ((Float)value.getObject()).floatValue();
} else if (value.getName().equals("Amplitude")) {
amplitude = ((Float)value.getObject()).floatValue();
}
}
}
/**
* A stroke to generate zigzags
*
* @author Jerry Huxtable
* @author Nathan Sweet <misc@n4te.com>
*/
private class ZigzagStroke implements Stroke {
/** The flattening factor applied to the path iterator */
private static final float FLATNESS = 1;
/**
* @see java.awt.Stroke#createStrokedShape(java.awt.Shape)
*/
public Shape createStrokedShape (Shape shape) {
GeneralPath result = new GeneralPath();
PathIterator it = new FlatteningPathIterator(shape.getPathIterator(null), FLATNESS);
float points[] = new float[6];
float moveX = 0, moveY = 0;
float lastX = 0, lastY = 0;
float thisX = 0, thisY = 0;
int type = 0;
float next = 0;
int phase = 0;
while (!it.isDone()) {
type = it.currentSegment(points);
switch (type) {
case PathIterator.SEG_MOVETO:
moveX = lastX = points[0];
moveY = lastY = points[1];
result.moveTo(moveX, moveY);
next = wavelength / 2;
break;
case PathIterator.SEG_CLOSE:
points[0] = moveX;
points[1] = moveY;
// Fall into....
case PathIterator.SEG_LINETO:
thisX = points[0];
thisY = points[1];
float dx = thisX - lastX;
float dy = thisY - lastY;
float distance = (float)Math.sqrt(dx * dx + dy * dy);
if (distance >= next) {
float r = 1.0f / distance;
while (distance >= next) {
float x = lastX + next * dx * r;
float y = lastY + next * dy * r;
if ((phase & 1) == 0)
result.lineTo(x + amplitude * dy * r, y - amplitude * dx * r);
else
result.lineTo(x - amplitude * dy * r, y + amplitude * dx * r);
next += wavelength;
phase++;
}
}
next -= distance;
lastX = thisX;
lastY = thisY;
if (type == PathIterator.SEG_CLOSE) result.closePath();
break;
}
it.next();
}
return new BasicStroke(getWidth(), BasicStroke.CAP_SQUARE, getJoin()).createStrokedShape(result);
}
}
}

View File

@@ -0,0 +1,321 @@
package org.newdawn.slick.font.effects;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.font.Glyph;
/**
* An effect to generate soft shadows beneath text
*
* @author Nathan Sweet <misc@n4te.com>
*/
public class ShadowEffect implements ConfigurableEffect {
/** The number of kernels to apply */
public static final int NUM_KERNELS = 16;
/** The blur kernels applied across the effect */
public static final float[][] GAUSSIAN_BLUR_KERNELS = generateGaussianBlurKernels(NUM_KERNELS);
/** The colour of the shadow to render */
private Color color = Color.black;
/** The transparency factor of the shadow */
private float opacity = 0.6f;
/** The distance on the x axis of the shadow from the text */
private float xDistance = 2;
/** The distance on the y axis of the shadow from the text */
private float yDistance = 2;
/** The size of the kernel used to blur the shadow */
private int blurKernelSize = 0;
/** The number of passes applied to create the blur */
private int blurPasses = 1;
/**
* Default constructor for injection
*/
public ShadowEffect() {
}
/**
* Create a new effect to apply a drop shadow to text
*
* @param color The colour of the shadow to generate
* @param xDistance The distance from the text on the x axis the shadow should be rendered
* @param yDistance The distance from the text on the y axis the shadow should be rendered
* @param opacity The transparency factor of the shadow
*/
public ShadowEffect (Color color, int xDistance, int yDistance, float opacity) {
this.color = color;
this.xDistance = xDistance;
this.yDistance = yDistance;
this.opacity = opacity;
}
/**
* @see org.newdawn.slick.font.effects.Effect#draw(java.awt.image.BufferedImage, java.awt.Graphics2D, org.newdawn.slick.UnicodeFont, org.newdawn.slick.font.Glyph)
*/
public void draw(BufferedImage image, Graphics2D g, UnicodeFont unicodeFont, Glyph glyph) {
g = (Graphics2D)g.create();
g.translate(xDistance, yDistance);
g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), Math.round(opacity * 255)));
g.fill(glyph.getShape());
// Also shadow the outline, if one exists.
for (Iterator iter = unicodeFont.getEffects().iterator(); iter.hasNext();) {
Effect effect = (Effect)iter.next();
if (effect instanceof OutlineEffect) {
Composite composite = g.getComposite();
g.setComposite(AlphaComposite.Src); // Prevent shadow and outline shadow alpha from combining.
g.setStroke(((OutlineEffect)effect).getStroke());
g.draw(glyph.getShape());
g.setComposite(composite);
break;
}
}
g.dispose();
if (blurKernelSize > 1 && blurKernelSize < NUM_KERNELS && blurPasses > 0) blur(image);
}
/**
* Apply blurring to the generate image
*
* @param image The image to be blurred
*/
private void blur(BufferedImage image) {
float[] matrix = GAUSSIAN_BLUR_KERNELS[blurKernelSize - 1];
Kernel gaussianBlur1 = new Kernel(matrix.length, 1, matrix);
Kernel gaussianBlur2 = new Kernel(1, matrix.length, matrix);
RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
ConvolveOp gaussianOp1 = new ConvolveOp(gaussianBlur1, ConvolveOp.EDGE_NO_OP, hints);
ConvolveOp gaussianOp2 = new ConvolveOp(gaussianBlur2, ConvolveOp.EDGE_NO_OP, hints);
BufferedImage scratchImage = EffectUtil.getScratchImage();
for (int i = 0; i < blurPasses; i++) {
gaussianOp1.filter(image, scratchImage);
gaussianOp2.filter(scratchImage, image);
}
}
/**
* Get the colour of the shadow generated
*
* @return The colour of the shadow generated
*/
public Color getColor() {
return color;
}
/**
* Set the colour of the shadow to be generated
*
* @param color The colour ofthe shadow to be generated
*/
public void setColor(Color color) {
this.color = color;
}
/**
* Get the distance on the X axis from the text the shadow should
* be generated at
*
* @return The distance on the X axis the shadow will be from the text
*/
public float getXDistance() {
return xDistance;
}
/**
* Sets the pixels to offset the shadow on the x axis. The glyphs will need padding so the
* shadow doesn't get clipped.
*
* @param distance The offset on the x axis
*/
public void setXDistance(float distance) {
xDistance = distance;
}
/**
* Get the distance on the Y axis from the text the shadow should
* be generated at
*
* @return The distance on the Y axis the shadow will be from the text
*/
public float getYDistance() {
return yDistance;
}
/**
* Sets the pixels to offset the shadow on the y axis. The glyphs will need
* padding so the shadow doesn't get clipped.
*
* @param distance The offset on the y axis
*/
public void setYDistance (float distance) {
yDistance = distance;
}
/**
* Get the size of the kernel used to apply the blur
*
* @return The blur kernel size
*/
public int getBlurKernelSize() {
return blurKernelSize;
}
/**
* Sets how many neighboring pixels are used to blur the shadow. Set to 0 for no blur.
*
* @param blurKernelSize The size of the kernel to apply the blur with
*/
public void setBlurKernelSize (int blurKernelSize) {
this.blurKernelSize = blurKernelSize;
}
/**
* Get the number of passes to apply the kernel for blurring
*
* @return The number of passes
*/
public int getBlurPasses() {
return blurPasses;
}
/**
* Sets the number of times to apply a blur to the shadow. Set to 0 for no blur.
*
* @param blurPasses The number of passes to apply when blurring
*/
public void setBlurPasses (int blurPasses) {
this.blurPasses = blurPasses;
}
/**
* Get the opacity of the shadow, i.e. how transparent it is
*
* @return The opacity of the shadow
*/
public float getOpacity() {
return opacity;
}
/**
* Set the opacity of the shadow, i.e. how transparent it is
*
* @param opacity The opacity of the shadow
*/
public void setOpacity(float opacity) {
this.opacity = opacity;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "Shadow";
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect#getValues()
*/
public List getValues() {
List values = new ArrayList();
values.add(EffectUtil.colorValue("Color", color));
values.add(EffectUtil.floatValue("Opacity", opacity, 0, 1, "This setting sets the translucency of the shadow."));
values.add(EffectUtil.floatValue("X distance", xDistance, Float.MIN_VALUE, Float.MAX_VALUE, "This setting is the amount of pixels to offset the shadow on the"
+ " x axis. The glyphs will need padding so the shadow doesn't get clipped."));
values.add(EffectUtil.floatValue("Y distance", yDistance, Float.MIN_VALUE, Float.MAX_VALUE, "This setting is the amount of pixels to offset the shadow on the"
+ " y axis. The glyphs will need padding so the shadow doesn't get clipped."));
List options = new ArrayList();
options.add(new String[] {"None", "0"});
for (int i = 2; i < NUM_KERNELS; i++)
options.add(new String[] {String.valueOf(i)});
String[][] optionsArray = (String[][])options.toArray(new String[options.size()][]);
values.add(EffectUtil.optionValue("Blur kernel size", String.valueOf(blurKernelSize), optionsArray,
"This setting controls how many neighboring pixels are used to blur the shadow. Set to \"None\" for no blur."));
values.add(EffectUtil.intValue("Blur passes", blurPasses,
"The setting is the number of times to apply a blur to the shadow. Set to \"0\" for no blur."));
return values;
}
/**
* @see org.newdawn.slick.font.effects.ConfigurableEffect#setValues(java.util.List)
*/
public void setValues(List values) {
for (Iterator iter = values.iterator(); iter.hasNext();) {
Value value = (Value)iter.next();
if (value.getName().equals("Color")) {
color = (Color)value.getObject();
} else if (value.getName().equals("Opacity")) {
opacity = ((Float)value.getObject()).floatValue();
} else if (value.getName().equals("X distance")) {
xDistance = ((Float)value.getObject()).floatValue();
} else if (value.getName().equals("Y distance")) {
yDistance = ((Float)value.getObject()).floatValue();
} else if (value.getName().equals("Blur kernel size")) {
blurKernelSize = Integer.parseInt((String)value.getObject());
} else if (value.getName().equals("Blur passes")) {
blurPasses = ((Integer)value.getObject()).intValue();
}
}
}
/**
* Generate the blur kernels which will be repeatedly applied when blurring images
*
* @param level The number of kernels to generate
* @return The kernels generated
*/
private static float[][] generateGaussianBlurKernels(int level) {
float[][] pascalsTriangle = generatePascalsTriangle(level);
float[][] gaussianTriangle = new float[pascalsTriangle.length][];
for (int i = 0; i < gaussianTriangle.length; i++) {
float total = 0.0f;
gaussianTriangle[i] = new float[pascalsTriangle[i].length];
for (int j = 0; j < pascalsTriangle[i].length; j++)
total += pascalsTriangle[i][j];
float coefficient = 1 / total;
for (int j = 0; j < pascalsTriangle[i].length; j++)
gaussianTriangle[i][j] = coefficient * pascalsTriangle[i][j];
}
return gaussianTriangle;
}
/**
* Generate Pascal's triangle
*
* @param level The level of the triangle to generate
* @return The Pascal's triangle kernel
*/
private static float[][] generatePascalsTriangle(int level) {
if (level < 2) level = 2;
float[][] triangle = new float[level][];
triangle[0] = new float[1];
triangle[1] = new float[2];
triangle[0][0] = 1.0f;
triangle[1][0] = 1.0f;
triangle[1][1] = 1.0f;
for (int i = 2; i < level; i++) {
triangle[i] = new float[i + 1];
triangle[i][0] = 1.0f;
triangle[i][i] = 1.0f;
for (int j = 1; j < triangle[i].length - 1; j++)
triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
}
return triangle;
}
}

View File

@@ -0,0 +1,437 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
/**
* Triangulates a polygon into triangles - duh. Doesn't handle
* holes in polys
*
* @author Based on Public Source from FlipCode
*/
public class BasicTriangulator implements Triangulator {
/** The accepted error value */
private static final float EPSILON = 0.0000000001f;
/** The list of points to be triangulated */
private PointList poly = new PointList();
/** The list of points describing the triangles */
private PointList tris = new PointList();
/** True if we've tried to triangulate */
private boolean tried;
/**
* Create a new triangulator
*/
public BasicTriangulator() {
}
/**
* Add a point describing the polygon to be triangulated
*
* @param x The x coordinate of the point
* @param y the y coordinate of the point
*/
public void addPolyPoint(float x, float y) {
Point p = new Point(x,y);
if (!poly.contains(p)) {
poly.add(p);
}
}
/**
* Get the number of points in the polygon
*
* @return The number of points in the polygon
*/
public int getPolyPointCount() {
return poly.size();
}
/**
* Get the coordinates of the point at the specified index
*
* @param index The index of the point to retrieve
* @return The oordinates of the point at the specified index
*/
public float[] getPolyPoint(int index) {
return new float[] {poly.get(index).x,poly.get(index).y};
}
/**
* Cause the triangulator to split the polygon
*
* @return True if we managed the task
*/
public boolean triangulate() {
tried = true;
boolean worked = process(poly,tris);
return worked;
}
/**
* Get a count of the number of triangles produced
*
* @return The number of triangles produced
*/
public int getTriangleCount() {
if (!tried) {
throw new RuntimeException("Call triangulate() before accessing triangles");
}
return tris.size() / 3;
}
/**
* Get a point on a specified generated triangle
*
* @param tri The index of the triangle to interegate
* @param i The index of the point within the triangle to retrieve
* (0 - 2)
* @return The x,y coordinate pair for the point
*/
public float[] getTrianglePoint(int tri, int i) {
if (!tried) {
throw new RuntimeException("Call triangulate() before accessing triangles");
}
return tris.get((tri*3)+i).toArray();
}
/**
* Find the area of a polygon defined by the series of points
* in the list
*
* @param contour The list of points defined the contour of the polygon
* (Vector2f)
* @return The area of the polygon defined
*/
private float area(PointList contour) {
int n = contour.size();
float A = 0.0f;
for (int p = n - 1, q = 0; q < n; p = q++) {
Point contourP = contour.get(p);
Point contourQ = contour.get(q);
A += contourP.getX() * contourQ.getY() - contourQ.getX()
* contourP.getY();
}
return A * 0.5f;
}
/**
* Check if the point P is inside the triangle defined by
* the points A,B,C
*
* @param Ax Point A x-coordinate
* @param Ay Point A y-coordinate
* @param Bx Point B x-coordinate
* @param By Point B y-coordinate
* @param Cx Point C x-coordinate
* @param Cy Point C y-coordinate
* @param Px Point P x-coordinate
* @param Py Point P y-coordinate
* @return True if the point specified is within the triangle
*/
private boolean insideTriangle(float Ax, float Ay, float Bx,
float By, float Cx, float Cy, float Px, float Py) {
float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
float cCROSSap, bCROSScp, aCROSSbp;
ax = Cx - Bx;
ay = Cy - By;
bx = Ax - Cx;
by = Ay - Cy;
cx = Bx - Ax;
cy = By - Ay;
apx = Px - Ax;
apy = Py - Ay;
bpx = Px - Bx;
bpy = Py - By;
cpx = Px - Cx;
cpy = Py - Cy;
aCROSSbp = ax * bpy - ay * bpx;
cCROSSap = cx * apy - cy * apx;
bCROSScp = bx * cpy - by * cpx;
return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
}
/**
* Cut a the contour and add a triangle into V to describe the
* location of the cut
*
* @param contour The list of points defining the polygon
* @param u The index of the first point
* @param v The index of the second point
* @param w The index of the third point
* @param n ?
* @param V The array to populate with indicies of triangles
* @return True if a triangle was found
*/
private boolean snip(PointList contour, int u, int v, int w, int n,
int[] V) {
int p;
float Ax, Ay, Bx, By, Cx, Cy, Px, Py;
Ax = contour.get(V[u]).getX();
Ay = contour.get(V[u]).getY();
Bx = contour.get(V[v]).getX();
By = contour.get(V[v]).getY();
Cx = contour.get(V[w]).getX();
Cy = contour.get(V[w]).getY();
if (EPSILON > (((Bx - Ax) * (Cy - Ay)) - ((By - Ay) * (Cx - Ax)))) {
return false;
}
for (p = 0; p < n; p++) {
if ((p == u) || (p == v) || (p == w)) {
continue;
}
Px = contour.get(V[p]).getX();
Py = contour.get(V[p]).getY();
if (insideTriangle(Ax, Ay, Bx, By, Cx, Cy, Px, Py)) {
return false;
}
}
return true;
}
/**
* Process a list of points defining a polygon
* @param contour The list of points describing the polygon
* @param result The list of points describing the triangles. Groups
* of 3 describe each triangle
*
* @return True if we succeeded in completing triangulation
*/
private boolean process(PointList contour, PointList result) {
result.clear();
/* allocate and initialize list of Vertices in polygon */
int n = contour.size();
if (n < 3)
return false;
int[] V = new int[n];
/* we want a counter-clockwise polygon in V */
if (0.0f < area(contour)) {
for (int v = 0; v < n; v++)
V[v] = v;
} else {
for (int v = 0; v < n; v++)
V[v] = (n - 1) - v;
}
int nv = n;
/* remove nv-2 Vertices, creating 1 triangle every time */
int count = 2 * nv; /* error detection */
for (int m = 0, v = nv - 1; nv > 2;) {
/* if we loop, it is probably a non-simple polygon */
if (0 >= (count--)) {
//** Triangulator4: ERROR - probable bad polygon!
return false;
}
/* three consecutive vertices in current polygon, <u,v,w> */
int u = v;
if (nv <= u)
u = 0; /* previous */
v = u + 1;
if (nv <= v)
v = 0; /* new v */
int w = v + 1;
if (nv <= w)
w = 0; /* next */
if (snip(contour, u, v, w, nv, V)) {
int a, b, c, s, t;
/* true names of the vertices */
a = V[u];
b = V[v];
c = V[w];
/* output Triangle */
result.add(contour.get(a));
result.add(contour.get(b));
result.add(contour.get(c));
m++;
/* remove v from remaining polygon */
for (s = v, t = v + 1; t < nv; s++, t++) {
V[s] = V[t];
}
nv--;
/* resest error detection counter */
count = 2 * nv;
}
}
return true;
}
/**
* A single point handled by the triangulator
*
* @author Kevin Glass
*/
private class Point {
/** The x coorindate of this point */
private float x;
/** The y coorindate of this point */
private float y;
/** The points in an array */
private float[] array;
/**
* Create a new point
*
* @param x The x coordindate of the point
* @param y The y coordindate of the point
*/
public Point(float x, float y) {
this.x = x;
this.y = y;
array = new float[] {x,y};
}
/**
* Get the x coordinate of the point
*
* @return The x coordinate of the point
*/
public float getX() {
return x;
}
/**
* Get the y coordinate of the point
*
* @return The y coordinate of the point
*/
public float getY() {
return y;
}
/**
* Convert this point into a float array
*
* @return The contents of this point as a float array
*/
public float[] toArray() {
return array;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return (int) (x * y * 31);
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object other) {
if (other instanceof Point) {
Point p = (Point) other;
return (p.x == x) && (p.y == y);
}
return false;
}
}
/**
* A list of type <code>Point</code>
*
* @author Kevin Glass
*/
private class PointList {
/** The list of points */
private ArrayList points = new ArrayList();
/**
* Create a new empty list
*/
public PointList() {
}
/**
* Check if the list contains a point
*
* @param p The point to look for
* @return True if the point is in the list
*/
public boolean contains(Point p) {
return points.contains(p);
}
/**
* Add a point to the list
*
* @param point The point to add
*/
public void add(Point point) {
points.add(point);
}
/**
* Remove a point from the list
*
* @param point The point to remove
*/
public void remove(Point point) {
points.remove(point);
}
/**
* Get the size of the list
*
* @return The size of the list
*/
public int size() {
return points.size();
}
/**
* Get a point a specific index in the list
*
* @param i The index of the point to retrieve
* @return The point
*/
public Point get(int i) {
return (Point) points.get(i);
}
/**
* Clear the list
*/
public void clear() {
points.clear();
}
}
/**
* @see org.newdawn.slick.geom.Triangulator#startHole()
*/
public void startHole() {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,234 @@
package org.newdawn.slick.geom;
/**
* A simple Circle geometry
*
* @author Kevin Glass
*/
public strictfp class Circle extends Ellipse {
/** The radius of the circle */
public float radius;
/**
* Create a new circle based on its radius
*
* @param centerPointX The x location of the center of the circle
* @param centerPointY The y location of the center of the circle
* @param radius The radius of the circle
*/
public Circle(float centerPointX, float centerPointY, float radius) {
this(centerPointX, centerPointY, radius, DEFAULT_SEGMENT_COUNT);
}
/**
* Create a new circle based on its radius
*
* @param centerPointX The x location of the center of the circle
* @param centerPointY The y location of the center of the circle
* @param radius The radius of the circle
* @param segmentCount The number of segments to build the circle out of
*/
public Circle(float centerPointX, float centerPointY, float radius, int segmentCount) {
super(centerPointX, centerPointY, radius, radius, segmentCount);
this.x = centerPointX - radius;
this.y = centerPointY - radius;
this.radius = radius;
boundingCircleRadius = radius;
}
/**
* Get the x coordinate of the centre of the circle
*
* @return The x coordinate of the centre of the circle
*/
public float getCenterX() {
return getX() + radius;
}
/**
* Get the y coordinate of the centre of the circle
*
* @return The y coordinate of the centre of the circle
*/
public float getCenterY() {
return getY() + radius;
}
/**
* Get the coordinates of the center of the circle
*
* @return 2-element array with the center of the circle.
*/
@Override
public float[] getCenter() {
return new float[] { getCenterX(), getCenterY() };
}
/**
* Set the radius of this circle
*
* @param radius The radius of this circle
*/
public void setRadius(float radius) {
if (radius != this.radius) {
pointsDirty = true;
this.radius = radius;
setRadii(radius, radius);
}
}
/**
* Get the radius of the circle
*
* @return The radius of the circle
*/
public float getRadius() {
return radius;
}
/**
* Check if this circle touches another
*
* @param shape The other circle
* @return True if they touch
*/
public boolean intersects(Shape shape) {
if(shape instanceof Circle) {
Circle other = (Circle)shape;
float totalRad2 = getRadius() + other.getRadius();
if (Math.abs(other.getCenterX() - getCenterX()) > totalRad2) {
return false;
}
if (Math.abs(other.getCenterY() - getCenterY()) > totalRad2) {
return false;
}
totalRad2 *= totalRad2;
float dx = Math.abs(other.getCenterX() - getCenterX());
float dy = Math.abs(other.getCenterY() - getCenterY());
return totalRad2 >= ((dx*dx) + (dy*dy));
}
else if(shape instanceof Rectangle) {
return intersects((Rectangle)shape);
}
else {
return super.intersects(shape);
}
}
/**
* Check if a point is contained by this circle
*
* @param x The x coordinate of the point to check
* @param y The y coorindate of the point to check
* @return True if the point is contained by this circle
*/
public boolean contains(float x, float y)
{
float xDelta = x - getCenterX(), yDelta = y - getCenterY();
return xDelta * xDelta + yDelta * yDelta < getRadius() * getRadius();
}
/**
* Check if circle contains the line
* @param line Line to check against
* @return True if line inside circle
*/
private boolean contains(Line line) {
return contains(line.getX1(), line.getY1()) && contains(line.getX2(), line.getY2());
}
/**
* @see org.newdawn.slick.geom.Ellipse#findCenter()
*/
protected void findCenter() {
center = new float[2];
center[0] = x + radius;
center[1] = y + radius;
}
/**
* @see org.newdawn.slick.geom.Ellipse#calculateRadius()
*/
protected void calculateRadius() {
boundingCircleRadius = radius;
}
/**
* Check if this circle touches a rectangle
*
* @param other The rectangle to check against
* @return True if they touch
*/
private boolean intersects(Rectangle other) {
Rectangle box = other;
Circle circle = this;
if (box.contains(x+radius,y+radius)) {
return true;
}
float x1 = box.getX();
float y1 = box.getY();
float x2 = box.getX() + box.getWidth();
float y2 = box.getY() + box.getHeight();
Line[] lines = new Line[4];
lines[0] = new Line(x1,y1,x2,y1);
lines[1] = new Line(x2,y1,x2,y2);
lines[2] = new Line(x2,y2,x1,y2);
lines[3] = new Line(x1,y2,x1,y1);
float r2 = circle.getRadius() * circle.getRadius();
Vector2f pos = new Vector2f(circle.getCenterX(), circle.getCenterY());
for (int i=0;i<4;i++) {
float dis = lines[i].distanceSquared(pos);
if (dis < r2) {
return true;
}
}
return false;
}
/**
* Check if circle touches a line.
* @param other The line to check against
* @return True if they touch
*/
private boolean intersects(Line other) {
// put it nicely into vectors
Vector2f lineSegmentStart = new Vector2f(other.getX1(), other.getY1());
Vector2f lineSegmentEnd = new Vector2f(other.getX2(), other.getY2());
Vector2f circleCenter = new Vector2f(getCenterX(), getCenterY());
// calculate point on line closest to the circle center and then
// compare radius to distance to the point for intersection result
Vector2f closest;
Vector2f segv = lineSegmentEnd.copy().sub(lineSegmentStart);
Vector2f ptv = circleCenter.copy().sub(lineSegmentStart);
float segvLength = segv.length();
float projvl = ptv.dot(segv) / segvLength;
if (projvl < 0)
{
closest = lineSegmentStart;
}
else if (projvl > segvLength)
{
closest = lineSegmentEnd;
}
else
{
Vector2f projv = segv.copy().scale(projvl / segvLength);
closest = lineSegmentStart.copy().add(projv);
}
boolean intersects = circleCenter.copy().sub(closest).lengthSquared() <= getRadius()*getRadius();
return intersects;
}
}

View File

@@ -0,0 +1,113 @@
package org.newdawn.slick.geom;
/**
* A beizer curve implementation. The curve is defined by a start point, an end point
* and two control points that it will tend towards. This is implementation is fixed
* segmenting meaning it doesn't scale too well.
*
* @author kevin
*/
public class Curve extends Shape {
/** The start point of the curve */
private Vector2f p1;
/** The first control point */
private Vector2f c1;
/** The second control point */
private Vector2f c2;
/** The end point of the curve */
private Vector2f p2;
/** The number of lines segments the curve is built out of */
private int segments;
/**
* Create a new curve with the default segments (20)
*
* @param p1 The start of the curve
* @param c1 The first control point
* @param c2 The second control point
* @param p2 The end of the curve
*/
public Curve(Vector2f p1, Vector2f c1, Vector2f c2, Vector2f p2) {
this(p1,c1,c2,p2,20);
}
/**
* Create a new curve
*
* @param p1 The start of the curve
* @param c1 The first control point
* @param c2 The second control point
* @param p2 The end of the curve
* @param segments The number of segments to use
*/
public Curve(Vector2f p1, Vector2f c1, Vector2f c2, Vector2f p2, int segments) {
this.p1 = new Vector2f(p1);
this.c1 = new Vector2f(c1);
this.c2 = new Vector2f(c2);
this.p2 = new Vector2f(p2);
this.segments = segments;
pointsDirty = true;
}
/**
* Get the point at a particular location on the curve
*
* @param t A value between 0 and 1 defining the location of the curve the point is at
* @return The point on the curve
*/
public Vector2f pointAt(float t) {
float a = 1 - t;
float b = t;
float f1 = a * a * a;
float f2 = 3 * a * a * b;
float f3 = 3 * a * b * b;
float f4 = b * b * b;
float nx = (p1.x * f1) + (c1.x * f2) + (c2.x * f3) + (p2.x * f4);
float ny = (p1.y * f1) + (c1.y * f2) + (c2.y * f3) + (p2.y * f4);
return new Vector2f(nx,ny);
}
/**
* @see org.newdawn.slick.geom.Shape#createPoints()
*/
protected void createPoints() {
float step = 1.0f / segments;
points = new float[(segments+1) * 2];
for (int i=0;i<segments+1;i++) {
float t = i * step;
Vector2f p = pointAt(t);
points[i*2] = p.x;
points[(i*2)+1] = p.y;
}
}
/**
* @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
*/
public Shape transform(Transform transform) {
float[] pts = new float[8];
float[] dest = new float[8];
pts[0] = p1.x; pts[1] = p1.y;
pts[2] = c1.x; pts[3] = c1.y;
pts[4] = c2.x; pts[5] = c2.y;
pts[6] = p2.x; pts[7] = p2.y;
transform.transform(pts, 0, dest, 0, 4);
return new Curve(new Vector2f(dest[0],dest[1]), new Vector2f(dest[2],dest[3]),
new Vector2f(dest[4],dest[5]), new Vector2f(dest[6],dest[7]));
}
/**
* True if this is a closed shape
*
* @return True if this is a closed shape
*/
public boolean closed() {
return false;
}
}

View File

@@ -0,0 +1,196 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
import org.newdawn.slick.util.FastTrig;
/**
* An ellipse meeting the <code>Shape</code> contract. The ellipse is actually an approximation using
* a series of points generated around the contour of the ellipse.
*
* @author Mark
*/
public class Ellipse extends Shape {
/**
* Default number of segments to draw this ellipse with
*/
protected static final int DEFAULT_SEGMENT_COUNT = 50;
/**
* The number of segments for graphical representation.
*/
private int segmentCount;
/**
* horizontal radius
*/
private float radius1;
/**
* vertical radius
*/
private float radius2;
/**
* Creates a new Ellipse object.
*
* @param centerPointX x coordinate of the center of the ellipse
* @param centerPointY y coordinate of the center of the ellipse
* @param radius1 horizontal radius
* @param radius2 vertical radius
*/
public Ellipse(float centerPointX, float centerPointY, float radius1, float radius2) {
this(centerPointX, centerPointY, radius1, radius2, DEFAULT_SEGMENT_COUNT);
}
/**
* Creates a new Ellipse object.
*
* @param centerPointX x coordinate of the center of the ellipse
* @param centerPointY y coordinate of the center of the ellipse
* @param radius1 horizontal radius
* @param radius2 vertical radius
* @param segmentCount how fine to make the ellipse.
*/
public Ellipse(float centerPointX, float centerPointY, float radius1, float radius2, int segmentCount) {
this.x = centerPointX - radius1;
this.y = centerPointY - radius2;
this.radius1 = radius1;
this.radius2 = radius2;
this.segmentCount = segmentCount;
checkPoints();
}
/**
* Change the shape of this Ellipse
*
* @param radius1 horizontal radius
* @param radius2 vertical radius
*/
public void setRadii(float radius1, float radius2) {
setRadius1(radius1);
setRadius2(radius2);
}
/**
* Get the horizontal radius of the ellipse
*
* @return The horizontal radius of the ellipse
*/
public float getRadius1() {
return radius1;
}
/**
* Set the horizontal radius of the ellipse
*
* @param radius1 The horizontal radius to set
*/
public void setRadius1(float radius1) {
if (radius1 != this.radius1) {
this.radius1 = radius1;
pointsDirty = true;
}
}
/**
* Get the vertical radius of the ellipse
*
* @return The vertical radius of the ellipse
*/
public float getRadius2() {
return radius2;
}
/**
* Set the vertical radius of the ellipse
*
* @param radius2 The vertical radius to set
*/
public void setRadius2(float radius2) {
if (radius2 != this.radius2) {
this.radius2 = radius2;
pointsDirty = true;
}
}
/**
* Generate the points to outline this ellipse.
*
*/
protected void createPoints() {
ArrayList tempPoints = new ArrayList();
maxX = -Float.MIN_VALUE;
maxY = -Float.MIN_VALUE;
minX = Float.MAX_VALUE;
minY = Float.MAX_VALUE;
float start = 0;
float end = 359;
float cx = x + radius1;
float cy = y + radius2;
int step = 360 / segmentCount;
for (float a=start;a<=end+step;a+=step) {
float ang = a;
if (ang > end) {
ang = end;
}
float newX = (float) (cx + (FastTrig.cos(Math.toRadians(ang)) * radius1));
float newY = (float) (cy + (FastTrig.sin(Math.toRadians(ang)) * radius2));
if(newX > maxX) {
maxX = newX;
}
if(newY > maxY) {
maxY = newY;
}
if(newX < minX) {
minX = newX;
}
if(newY < minY) {
minY = newY;
}
tempPoints.add(new Float(newX));
tempPoints.add(new Float(newY));
}
points = new float[tempPoints.size()];
for(int i=0;i<points.length;i++) {
points[i] = ((Float)tempPoints.get(i)).floatValue();
}
}
/**
* @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
*/
public Shape transform(Transform transform) {
checkPoints();
Polygon resultPolygon = new Polygon();
float result[] = new float[points.length];
transform.transform(points, 0, result, 0, points.length / 2);
resultPolygon.points = result;
resultPolygon.checkPoints();
return resultPolygon;
}
/**
* @see org.newdawn.slick.geom.Shape#findCenter()
*/
protected void findCenter() {
center = new float[2];
center[0] = x + radius1;
center[1] = y + radius2;
}
/**
* @see org.newdawn.slick.geom.Shape#calculateRadius()
*/
protected void calculateRadius() {
boundingCircleRadius = (radius1 > radius2) ? radius1 : radius2;
}
}

View File

@@ -0,0 +1,449 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
/**
* A set of utilities to play with geometry
*
* @author kevin
*/
public class GeomUtil {
/** The tolerance for determining changes and steps */
public float EPSILON = 0.0001f;
/** The tolerance for determining direction change */
public float EDGE_SCALE = 1f;
/** The maximum number of points returned by an operation - prevents full lockups */
public int MAX_POINTS = 10000;
/** The listener to notify of operations */
public GeomUtilListener listener;
/**
* Subtract one shape from another - note this is experimental and doesn't
* currently handle islands
*
* @param target The target to be subtracted from
* @param missing The shape to subtract
* @return The newly created shapes
*/
public Shape[] subtract(Shape target, Shape missing) {
target = target.transform(new Transform());
missing = missing.transform(new Transform());
int count = 0;
for (int i=0;i<target.getPointCount();i++) {
if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
count++;
}
}
if (count == target.getPointCount()) {
return new Shape[0];
}
if (!target.intersects(missing)) {
return new Shape[] {target};
}
int found = 0;
for (int i=0;i<missing.getPointCount();i++) {
if (target.contains(missing.getPoint(i)[0], missing.getPoint(i)[1])) {
if (!onPath(target, missing.getPoint(i)[0], missing.getPoint(i)[1])) {
found++;
}
}
}
for (int i=0;i<target.getPointCount();i++) {
if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
if (!onPath(missing, target.getPoint(i)[0], target.getPoint(i)[1]))
{
found++;
}
}
}
if (found < 1) {
return new Shape[] {target};
}
return combine(target, missing, true);
}
/**
* Check if the given point is on the path
*
* @param path The path to check
* @param x The x coordinate of the point to check
* @param y The y coordiante of teh point to check
* @return True if the point is on the path
*/
private boolean onPath(Shape path, float x, float y) {
for (int i=0;i<path.getPointCount()+1;i++) {
int n = rationalPoint(path, i+1);
Line line = getLine(path, rationalPoint(path, i), n);
if (line.distance(new Vector2f(x,y)) < EPSILON*100) {
return true;
}
}
return false;
}
/**
* Set the listener to be notified of geometry based operations
*
* @param listener The listener to be notified of geometry based operations
*/
public void setListener(GeomUtilListener listener) {
this.listener = listener;
}
/**
* Join to shapes together. Note that the shapes must be touching
* for this method to work.
*
* @param target The target shape to union with
* @param other The additional shape to union
* @return The newly created shapes
*/
public Shape[] union(Shape target, Shape other) {
target = target.transform(new Transform());
other = other.transform(new Transform());
if (!target.intersects(other)) {
return new Shape[] {target, other};
}
// handle the case where intersects is true but really we're talking
// about edge points
boolean touches = false;
int buttCount = 0;
for (int i=0;i<target.getPointCount();i++) {
if (other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
touches = true;
break;
}
}
if (other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
buttCount++;
}
}
for (int i=0;i<other.getPointCount();i++) {
if (target.contains(other.getPoint(i)[0], other.getPoint(i)[1])) {
if (!target.hasVertex(other.getPoint(i)[0], other.getPoint(i)[1])) {
touches = true;
break;
}
}
}
if ((!touches) && (buttCount < 2)) {
return new Shape[] {target, other};
}
// so they are definitely touching, consider the union
return combine(target, other, false);
}
/**
* Perform the combination
*
* @param target The target shape we're updating
* @param other The other shape in the operation
* @param subtract True if it's a subtract operation, otherwise it's union
* @return The set of shapes produced
*/
private Shape[] combine(Shape target, Shape other, boolean subtract) {
if (subtract) {
ArrayList shapes = new ArrayList();
ArrayList used = new ArrayList();
// remove any points that are contianed in the shape we're removing, these
// are implicitly used
for (int i=0;i<target.getPointCount();i++) {
float[] point = target.getPoint(i);
if (other.contains(point[0], point[1])) {
used.add(new Vector2f(point[0], point[1]));
if (listener != null) {
listener.pointExcluded(point[0], point[1]);
}
}
}
for (int i=0;i<target.getPointCount();i++) {
float[] point = target.getPoint(i);
Vector2f pt = new Vector2f(point[0], point[1]);
if (!used.contains(pt)) {
Shape result = combineSingle(target, other, true, i);
shapes.add(result);
for (int j=0;j<result.getPointCount();j++) {
float[] kpoint = result.getPoint(j);
Vector2f kpt = new Vector2f(kpoint[0], kpoint[1]);
used.add(kpt);
}
}
}
return (Shape[]) shapes.toArray(new Shape[0]);
} else {
for (int i=0;i<target.getPointCount();i++) {
if (!other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
Shape shape = combineSingle(target, other, false, i);
return new Shape[] {shape};
}
}
}
return new Shape[] {other};
}
}
/**
* Combine two shapes
*
* @param target The target shape
* @param missing The second shape to apply
* @param subtract True if we should subtract missing from target, otherwise union
* @param start The point to start at
* @return The newly created shape
*/
private Shape combineSingle(Shape target, Shape missing, boolean subtract, int start) {
Shape current = target;
Shape other = missing;
int point = start;
int dir = 1;
Polygon poly = new Polygon();
boolean first = true;
int loop = 0;
// while we've not reached the same point
float px = current.getPoint(point)[0];
float py = current.getPoint(point)[1];
while (!poly.hasVertex(px, py) || (first) || (current != target)) {
first = false;
loop++;
if (loop > MAX_POINTS) {
break;
}
// add the current point to the result shape
poly.addPoint(px,py);
if (listener != null) {
listener.pointUsed(px,py);
}
// if the line between the current point and the next one intersect the
// other shape work out where on the other shape and start traversing it's
// path instead
Line line = getLine(current, px, py, rationalPoint(current, point+dir));
HitResult hit = intersect(other, line);
if (hit != null) {
Line hitLine = hit.line;
Vector2f pt = hit.pt;
px = pt.x;
py = pt.y;
if (listener != null) {
listener.pointIntersected(px,py);
}
if (other.hasVertex(px, py)) {
point = other.indexOf(pt.x,pt.y);
dir = 1;
px = pt.x;
py = pt.y;
Shape temp = current;
current = other;
other = temp;
continue;
}
float dx = hitLine.getDX() / hitLine.length();
float dy = hitLine.getDY() / hitLine.length();
dx *= EDGE_SCALE;
dy *= EDGE_SCALE;
if (current.contains(pt.x + dx, pt.y + dy)) {
// the point is the next one, we need to take the first and traverse
// the path backwards
if (subtract) {
if (current == missing) {
point = hit.p2;
dir = -1;
} else {
point = hit.p1;
dir = 1;
}
} else {
if (current == target) {
point = hit.p2;
dir = -1;
} else {
point = hit.p2;
dir = -1;
}
}
// swap the shapes over, we'll traverse the other one
Shape temp = current;
current = other;
other = temp;
} else if (current.contains(pt.x - dx, pt.y - dy)) {
if (subtract) {
if (current == target) {
point = hit.p2;
dir = -1;
} else {
point = hit.p1;
dir = 1;
}
} else {
if (current == missing) {
point = hit.p1;
dir = 1;
} else {
point = hit.p1;
dir = 1;
}
}
// swap the shapes over, we'll traverse the other one
Shape temp = current;
current = other;
other = temp;
} else {
// give up
if (subtract) {
break;
} else {
point = hit.p1;
dir = 1;
Shape temp = current;
current = other;
other = temp;
point = rationalPoint(current, point+dir);
px = current.getPoint(point)[0];
py = current.getPoint(point)[1];
}
}
} else {
// otherwise just move to the next point in the current shape
point = rationalPoint(current, point+dir);
px = current.getPoint(point)[0];
py = current.getPoint(point)[1];
}
}
poly.addPoint(px,py);
if (listener != null) {
listener.pointUsed(px,py);
}
return poly;
}
/**
* Intersect a line with a shape
*
* @param shape The shape to compare
* @param line The line to intersect against the shape
* @return The result describing the intersection or null if none
*/
public HitResult intersect(Shape shape, Line line) {
float distance = Float.MAX_VALUE;
HitResult hit = null;
for (int i=0;i<shape.getPointCount();i++) {
int next = rationalPoint(shape, i+1);
Line local = getLine(shape, i, next);
Vector2f pt = line.intersect(local, true);
if (pt != null) {
float newDis = pt.distance(line.getStart());
if ((newDis < distance) && (newDis > EPSILON)) {
hit = new HitResult();
hit.pt = pt;
hit.line = local;
hit.p1 = i;
hit.p2 = next;
distance = newDis;
}
}
}
return hit;
}
/**
* Rationalise a point in terms of a given shape
*
* @param shape The shape
* @param p The index of the point
* @return The index that is rational for the shape
*/
public static int rationalPoint(Shape shape, int p) {
while (p < 0) {
p += shape.getPointCount();
}
while (p >= shape.getPointCount()) {
p -= shape.getPointCount();
}
return p;
}
/**
* Get a line between two points in a shape
*
* @param shape The shape
* @param s The index of the start point
* @param e The index of the end point
* @return The line between the two points
*/
public Line getLine(Shape shape, int s, int e) {
float[] start = shape.getPoint(s);
float[] end = shape.getPoint(e);
Line line = new Line(start[0],start[1],end[0],end[1]);
return line;
}
/**
* Get a line between two points in a shape
*
* @param shape The shape
* @param sx The x coordinate of the start point
* @param sy The y coordinate of the start point
* @param e The index of the end point
* @return The line between the two points
*/
public Line getLine(Shape shape, float sx, float sy, int e) {
float[] end = shape.getPoint(e);
Line line = new Line(sx,sy,end[0],end[1]);
return line;
}
/**
* A lightweigtht description of a intersection between a shape and
* line.
*/
public class HitResult {
/** The line on the target shape that intersected */
public Line line;
/** The index of the first point on the target shape that forms the line */
public int p1;
/** The index of the second point on the target shape that forms the line */
public int p2;
/** The position of the intersection */
public Vector2f pt;
}
}

View File

@@ -0,0 +1,32 @@
package org.newdawn.slick.geom;
/**
* Debug listener for notifications assocaited with geometry utilities
*
* @author kevin
*/
public interface GeomUtilListener {
/**
* Notification that a point was excluded from geometry
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public void pointExcluded(float x, float y);
/**
* Notification that a point was intersected between two geometries
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public void pointIntersected(float x, float y);
/**
* Notification that a point was used to build a new geometry
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public void pointUsed(float x, float y);
}

View File

@@ -0,0 +1,482 @@
package org.newdawn.slick.geom;
/**
* Implemenation of a bunch of maths functions to do with lines. Note that lines
* can't be used as dynamic shapes right now - also collision with the end of a
* line is undefined.
*
* @author Kevin Glass
*/
public class Line extends Shape {
/** The start point of the line */
private Vector2f start;
/** The end point of the line */
private Vector2f end;
/** The vector between the two points */
private Vector2f vec;
/** The length of the line squared */
private float lenSquared;
/** Temporary storage - declared globally to reduce GC */
private Vector2f loc = new Vector2f(0, 0);
/** Temporary storage - declared globally to reduce GC */
private Vector2f v = new Vector2f(0, 0);
/** Temporary storage - declared globally to reduce GC */
private Vector2f v2 = new Vector2f(0, 0);
/** Temporary storage - declared globally to reduce GC */
private Vector2f proj = new Vector2f(0, 0);
/** Temporary storage - declared globally to reduce GC */
private Vector2f closest = new Vector2f(0, 0);
/** Temporary storage - declared globally to reduce GC */
private Vector2f other = new Vector2f(0, 0);
/** True if this line blocks on the outer edge */
private boolean outerEdge = true;
/** True if this line blocks on the inner edge */
private boolean innerEdge = true;
/**
* Create a new line based on the origin and a single point
*
* @param x
* The end point of the line
* @param y
* The end point of the line
* @param inner
* True if this line blocks on it's inner edge
* @param outer
* True if this line blocks on it's outer edge
*/
public Line(float x, float y, boolean inner, boolean outer) {
this(0, 0, x, y);
}
/**
* Create a new line based on the origin and a single point
*
* @param x
* The end point of the line
* @param y
* The end point of the line
*/
public Line(float x, float y) {
this(x, y, true, true);
}
/**
* Create a new line based on two points
*
* @param x1
* The x coordinate of the start point
* @param y1
* The y coordinate of the start point
* @param x2
* The x coordinate of the end point
* @param y2
* The y coordinate of the end point
*/
public Line(float x1, float y1, float x2, float y2) {
this(new Vector2f(x1, y1), new Vector2f(x2, y2));
}
/**
* Create a line with relative second point
*
* @param x1
* The x coordinate of the start point
* @param y1
* The y coordinate of the start point
* @param dx
* The x change to get to the second point
* @param dy
* The y change to get to the second point
* @param dummy
* A dummy value
*/
public Line(float x1, float y1, float dx, float dy, boolean dummy) {
this(new Vector2f(x1, y1), new Vector2f(x1 + dx, y1 + dy));
}
/**
* Create a new line based on two points
*
* @param start
* The start point
* @param end
* The end point
*/
public Line(float[] start, float[] end) {
super();
set(start, end);
}
/**
* Create a new line based on two points
*
* @param start
* The start point
* @param end
* The end point
*/
public Line(Vector2f start, Vector2f end) {
super();
set(start, end);
}
/**
* Configure the line
*
* @param start
* The start point of the line
* @param end
* The end point of the line
*/
public void set(float[] start, float[] end) {
set(start[0], start[1], end[0], end[1]);
}
/**
* Get the start point of the line
*
* @return The start point of the line
*/
public Vector2f getStart() {
return start;
}
/**
* Get the end point of the line
*
* @return The end point of the line
*/
public Vector2f getEnd() {
return end;
}
/**
* Find the length of the line
*
* @return The the length of the line
*/
public float length() {
return vec.length();
}
/**
* Find the length of the line squared (cheaper and good for comparisons)
*
* @return The length of the line squared
*/
public float lengthSquared() {
return vec.lengthSquared();
}
/**
* Configure the line
*
* @param start
* The start point of the line
* @param end
* The end point of the line
*/
public void set(Vector2f start, Vector2f end) {
super.pointsDirty = true;
if (this.start == null) {
this.start = new Vector2f();
}
this.start.set(start);
if (this.end == null) {
this.end = new Vector2f();
}
this.end.set(end);
vec = new Vector2f(end);
vec.sub(start);
lenSquared = vec.lengthSquared();
}
/**
* Configure the line without garbage
*
* @param sx
* The x coordinate of the start
* @param sy
* The y coordinate of the start
* @param ex
* The x coordiante of the end
* @param ey
* The y coordinate of the end
*/
public void set(float sx, float sy, float ex, float ey) {
super.pointsDirty = true;
start.set(sx, sy);
end.set(ex, ey);
float dx = (ex - sx);
float dy = (ey - sy);
vec.set(dx,dy);
lenSquared = (dx * dx) + (dy * dy);
}
/**
* Get the x direction of this line
*
* @return The x direction of this line
*/
public float getDX() {
return end.getX() - start.getX();
}
/**
* Get the y direction of this line
*
* @return The y direction of this line
*/
public float getDY() {
return end.getY() - start.getY();
}
/**
* @see org.newdawn.slick.geom.Shape#getX()
*/
public float getX() {
return getX1();
}
/**
* @see org.newdawn.slick.geom.Shape#getY()
*/
public float getY() {
return getY1();
}
/**
* Get the x coordinate of the start point
*
* @return The x coordinate of the start point
*/
public float getX1() {
return start.getX();
}
/**
* Get the y coordinate of the start point
*
* @return The y coordinate of the start point
*/
public float getY1() {
return start.getY();
}
/**
* Get the x coordinate of the end point
*
* @return The x coordinate of the end point
*/
public float getX2() {
return end.getX();
}
/**
* Get the y coordinate of the end point
*
* @return The y coordinate of the end point
*/
public float getY2() {
return end.getY();
}
/**
* Get the shortest distance from a point to this line
*
* @param point
* The point from which we want the distance
* @return The distance from the line to the point
*/
public float distance(Vector2f point) {
return (float) Math.sqrt(distanceSquared(point));
}
/**
* Check if the given point is on the line
*
* @param point
* The point to check
* @return True if the point is on this line
*/
public boolean on(Vector2f point) {
getClosestPoint(point, closest);
return point.equals(closest);
}
/**
* Get the shortest distance squared from a point to this line
*
* @param point
* The point from which we want the distance
* @return The distance squared from the line to the point
*/
public float distanceSquared(Vector2f point) {
getClosestPoint(point, closest);
closest.sub(point);
float result = closest.lengthSquared();
return result;
}
/**
* Get the closest point on the line to a given point
*
* @param point
* The point which we want to project
* @param result
* The point on the line closest to the given point
*/
public void getClosestPoint(Vector2f point, Vector2f result) {
loc.set(point);
loc.sub(start);
float projDistance = vec.dot(loc);
projDistance /= vec.lengthSquared();
if (projDistance < 0) {
result.set(start);
return;
}
if (projDistance > 1) {
result.set(end);
return;
}
result.x = start.getX() + projDistance * vec.getX();
result.y = start.getY() + projDistance * vec.getY();
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "[Line " + start + "," + end + "]";
}
/**
* Intersect this line with another
*
* @param other
* The other line we should intersect with
* @return The intersection point or null if the lines are parallel
*/
public Vector2f intersect(Line other) {
return intersect(other, false);
}
/**
* Intersect this line with another
*
* @param other
* The other line we should intersect with
* @param limit
* True if the collision is limited to the extent of the lines
* @return The intersection point or null if the lines don't intersect
*/
public Vector2f intersect(Line other, boolean limit) {
Vector2f temp = new Vector2f();
if (!intersect(other, limit, temp)) {
return null;
}
return temp;
}
/**
* Intersect this line with another
*
* @param other
* The other line we should intersect with
* @param limit
* True if the collision is limited to the extent of the lines
* @param result
* The resulting intersection point if any
* @return True if the lines intersect
*/
public boolean intersect(Line other, boolean limit, Vector2f result) {
float dx1 = end.getX() - start.getX();
float dx2 = other.end.getX() - other.start.getX();
float dy1 = end.getY() - start.getY();
float dy2 = other.end.getY() - other.start.getY();
float denom = (dy2 * dx1) - (dx2 * dy1);
if (denom == 0) {
return false;
}
float ua = (dx2 * (start.getY() - other.start.getY()))
- (dy2 * (start.getX() - other.start.getX()));
ua /= denom;
float ub = (dx1 * (start.getY() - other.start.getY()))
- (dy1 * (start.getX() - other.start.getX()));
ub /= denom;
if ((limit) && ((ua < 0) || (ua > 1) || (ub < 0) || (ub > 1))) {
return false;
}
float u = ua;
float ix = start.getX() + (u * (end.getX() - start.getX()));
float iy = start.getY() + (u * (end.getY() - start.getY()));
result.set(ix, iy);
return true;
}
/**
* @see org.newdawn.slick.geom.Shape#createPoints()
*/
protected void createPoints() {
points = new float[4];
points[0] = getX1();
points[1] = getY1();
points[2] = getX2();
points[3] = getY2();
}
/**
* @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
*/
public Shape transform(Transform transform) {
float[] temp = new float[4];
createPoints();
transform.transform(points, 0, temp, 0, 2);
return new Line(temp[0], temp[1], temp[2], temp[3]);
}
/**
* @see org.newdawn.slick.geom.Shape#closed()
*/
public boolean closed() {
return false;
}
/**
* @see org.newdawn.slick.geom.Shape#intersects(org.newdawn.slick.geom.Shape)
*/
public boolean intersects(Shape shape)
{
if (shape instanceof Circle)
{
return shape.intersects(this);
}
return super.intersects(shape);
}
}

View File

@@ -0,0 +1,617 @@
/*
* Triangulator0.java
*
* (BSD license)
*
* Copyright (c) 2005, Matthias Mann (www.matthiasmann.de)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* * Neither the name of the Matthias Mann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Created on 17. March 2005, 22:19
*/
package org.newdawn.slick.geom;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
/**
* A 2D Triangulator. Graciously made available by the man(n) himself.
*
* @author Matthias Mann
*/
public class MannTriangulator implements Triangulator {
/** The allowed error value */
private static final double EPSILON = 1e-5;
/** The outer countour of the shape */
protected PointBag contour;
/** The holes defined in the polygon */
protected PointBag holes;
/** The next available point bag */
private PointBag nextFreePointBag;
/** The next available point */
private Point nextFreePoint;
/** The list of triangles created (or rather points in triangles, 3xn) */
private List triangles = new ArrayList();
/** Creates a new instance of Triangulator0 */
public MannTriangulator() {
contour = getPointBag();
}
/**
* @see org.newdawn.slick.geom.Triangulator#addPolyPoint(float, float)
*/
public void addPolyPoint(float x, float y) {
addPoint(new Vector2f(x,y));
}
/**
* Reset the internal state of the triangulator
*/
public void reset() {
while (holes != null) {
holes = freePointBag(holes);
}
contour.clear();
holes = null;
}
/**
* Begin adding a hole to the polygon
*/
public void startHole() {
PointBag newHole = getPointBag();
newHole.next = holes;
holes = newHole;
}
/**
* Add a defined point to the current contour
*
* @param pt The point to add
*/
private void addPoint(Vector2f pt) {
if (holes == null) {
Point p = getPoint(pt);
contour.add(p);
} else {
Point p = getPoint(pt);
holes.add(p);
}
}
/**
* Triangulate the points given
*
* @param result The array to fill with the result or use to determine type
* @return The resultng triangles
*/
private Vector2f[] triangulate(Vector2f[] result) {
// Step 1: Compute all angles
contour.computeAngles();
for (PointBag hole = holes; hole != null; hole = hole.next) {
hole.computeAngles();
}
// Step 2: Connect the holes with the contour (build bridges)
while (holes != null) {
Point pHole = holes.first;
outer: do {
if (pHole.angle <= 0) {
Point pContour = contour.first;
do {
inner: if (pHole.isInfront(pContour)
&& pContour.isInfront(pHole)) {
if (!contour.doesIntersectSegment(pHole.pt,
pContour.pt)) {
PointBag hole = holes;
do {
if (hole.doesIntersectSegment(pHole.pt,
pContour.pt)) {
break inner;
}
} while ((hole = hole.next) != null);
Point newPtContour = getPoint(pContour.pt);
pContour.insertAfter(newPtContour);
Point newPtHole = getPoint(pHole.pt);
pHole.insertBefore(newPtHole);
pContour.next = pHole;
pHole.prev = pContour;
newPtHole.next = newPtContour;
newPtContour.prev = newPtHole;
pContour.computeAngle();
pHole.computeAngle();
newPtContour.computeAngle();
newPtHole.computeAngle();
// detach the points from the hole
holes.first = null;
break outer;
}
}
} while ((pContour = pContour.next) != contour.first);
}
} while ((pHole = pHole.next) != holes.first);
// free the hole
holes = freePointBag(holes);
}
// Step 3: Make sure we have enough space for the result
int numTriangles = contour.countPoints() - 2;
int neededSpace = numTriangles * 3 + 1; // for the null
if (result.length < neededSpace) {
result = (Vector2f[]) Array.newInstance(result.getClass()
.getComponentType(), neededSpace);
}
// Step 4: Extract the triangles
int idx = 0;
for (;;) {
Point pContour = contour.first;
if (pContour == null) {
break;
}
// Are there 2 or less points left ?
if (pContour.next == pContour.prev) {
break;
}
outer: do {
if (pContour.angle > 0) {
Point prev = pContour.prev;
Point next = pContour.next;
if (next.next == prev || prev.isInfront(next) && next.isInfront(prev)) {
if (!contour.doesIntersectSegment(prev.pt, next.pt)) {
result[idx++] = pContour.pt;
result[idx++] = next.pt;
result[idx++] = prev.pt;
break outer;
}
}
}
} while ((pContour = pContour.next) != contour.first);
// remove the point - we do it in every case to prevent endless loop
Point prev = pContour.prev;
Point next = pContour.next;
contour.first = prev;
pContour.unlink();
freePoint(pContour);
next.computeAngle();
prev.computeAngle();
}
// Step 5: Append a null (see Collection.toArray)
result[idx] = null;
// Step 6: Free memory
contour.clear();
// Finished !
return result;
}
/**
* Create a new point bag (or recycle an old one)
*
* @return The new point bag
*/
private PointBag getPointBag() {
PointBag pb = nextFreePointBag;
if (pb != null) {
nextFreePointBag = pb.next;
pb.next = null;
return pb;
}
return new PointBag();
}
/**
* Release a pooled bag
*
* @param pb The bag to release
* @return The next available bag
*/
private PointBag freePointBag(PointBag pb) {
PointBag next = pb.next;
pb.clear();
pb.next = nextFreePointBag;
nextFreePointBag = pb;
return next;
}
/**
* Create or reuse a point
*
* @param pt The point data to set
* @return The new point
*/
private Point getPoint(Vector2f pt) {
Point p = nextFreePoint;
if (p != null) {
nextFreePoint = p.next;
// initialize new point to safe values
p.next = null;
p.prev = null;
p.pt = pt;
return p;
}
return new Point(pt);
}
/**
* Release a point into the pool
*
* @param p The point to release
*/
private void freePoint(Point p) {
p.next = nextFreePoint;
nextFreePoint = p;
}
/**
* Release all points
*
* @param head The head of the points bag
*/
private void freePoints(Point head) {
head.prev.next = nextFreePoint;
head.prev = null;
nextFreePoint = head;
}
/**
* A single point being considered during triangulation
*
* @author Matthias Mann
*/
private static class Point implements Serializable {
/** The location of the point */
protected Vector2f pt;
/** The previous point in the contour */
protected Point prev;
/** The next point in the contour */
protected Point next;
/** The x component of the of the normal */
protected double nx;
/** The y component of the of the normal */
protected double ny;
/** The angle at this point in the path */
protected double angle;
/** The distance of this point from */
protected double dist;
/**
* Create a new point
*
* @param pt The points location
*/
public Point(Vector2f pt) {
this.pt = pt;
}
/**
* Remove this point from it's contour
*/
public void unlink() {
prev.next = next;
next.prev = prev;
next = null;
prev = null;
}
/**
* Insert a point before this one (see LinkedList)
*
* @param p The point to insert
*/
public void insertBefore(Point p) {
prev.next = p;
p.prev = prev;
p.next = this;
prev = p;
}
/**
* Insert a point after this one (see LinkedList)
*
* @param p The point to insert
*/
public void insertAfter(Point p) {
next.prev = p;
p.prev = this;
p.next = next;
next = p;
}
/**
* Java 5 hypot method
*
* @param x The x component
* @param y The y component
* @return The hypotenuse
*/
private double hypot(double x, double y) {
return Math.sqrt(x*x + y*y);
}
/**
* Compute the angle at this point
*/
public void computeAngle() {
if (prev.pt.equals(pt)) {
pt.x += 0.01f;
}
double dx1 = pt.x - prev.pt.x;
double dy1 = pt.y - prev.pt.y;
double len1 = hypot(dx1, dy1);
dx1 /= len1;
dy1 /= len1;
if (next.pt.equals(pt)) {
pt.y += 0.01f;
}
double dx2 = next.pt.x - pt.x;
double dy2 = next.pt.y - pt.y;
double len2 = hypot(dx2, dy2);
dx2 /= len2;
dy2 /= len2;
double nx1 = -dy1;
double ny1 = dx1;
nx = (nx1 - dy2) * 0.5;
ny = (ny1 + dx2) * 0.5;
if (nx * nx + ny * ny < EPSILON) {
nx = dx1;
ny = dy2;
angle = 1; // TODO: nx1,ny1 and nx2,ny2 facing ?
if (dx1 * dx2 + dy1 * dy2 > 0) {
nx = -dx1;
ny = -dy1;
}
} else {
angle = nx * dx2 + ny * dy2;
}
}
/**
* Get the angle of this point to another
*
* @param p The other point
* @return The angle between this point and another
*/
public double getAngle(Point p) {
double dx = p.pt.x - pt.x;
double dy = p.pt.y - pt.y;
double dlen = hypot(dx, dy);
return (nx * dx + ny * dy) / dlen;
}
/**
* Check if this point is convave
*
* @return True if this point remains concave
*/
public boolean isConcave() {
return angle < 0;
}
/**
* Check if this point is infront of another
*
* @param dx The other x
* @param dy The other y
* @return True if this point is infront (in the contour)
*/
public boolean isInfront(double dx, double dy) {
// no nead to normalize, amplitude does not metter for side
// detection
boolean sidePrev = ((prev.pt.y - pt.y) * dx + (pt.x - prev.pt.x)
* dy) >= 0;
boolean sideNext = ((pt.y - next.pt.y) * dx + (next.pt.x - pt.x)
* dy) >= 0;
return (angle < 0) ? (sidePrev | sideNext) : (sidePrev & sideNext);
}
/**
* Check if this point is infront of another
*
* @param p The other point
* @return True if this point is infront (in the contour)
*/
public boolean isInfront(Point p) {
return isInfront(p.pt.x - pt.x, p.pt.y - pt.y);
}
}
/**
* A bag/pool of point objects
*
* @author kevin
*/
protected class PointBag implements Serializable {
/** The first point in the bag - head of the list */
protected Point first;
/** The next bag in the list of bags */
protected PointBag next;
/**
* Clear all the points from this bag
*/
public void clear() {
if (first != null) {
freePoints(first);
first = null;
}
}
/**
* Add a point to the bag
*
* @param p The point to add
*/
public void add(Point p) {
if (first != null) {
first.insertBefore(p);
} else {
first = p;
p.next = p;
p.prev = p;
}
}
/**
* Compute the angles for the points in this bag
*/
public void computeAngles() {
if (first == null) {
return;
}
Point p = first;
do {
p.computeAngle();
} while ((p = p.next) != first);
}
/**
* Check if the points in this bag form a path intersecting
* with the specified path
*
* @param v1 The start point of the segment
* @param v2 The end point of the segment
* @return True if points in this contour intersect with the segment
*/
public boolean doesIntersectSegment(Vector2f v1, Vector2f v2) {
double dxA = v2.x - v1.x;
double dyA = v2.y - v1.y;
for (Point p = first;;) {
Point n = p.next;
if (p.pt != v1 && n.pt != v1 && p.pt != v2 && n.pt != v2) {
double dxB = n.pt.x - p.pt.x;
double dyB = n.pt.y - p.pt.y;
double d = (dxA * dyB) - (dyA * dxB);
if (Math.abs(d) > EPSILON) {
double tmp1 = p.pt.x - v1.x;
double tmp2 = p.pt.y - v1.y;
double tA = (dyB * tmp1 - dxB * tmp2) / d;
double tB = (dyA * tmp1 - dxA * tmp2) / d;
if (tA >= 0 && tA <= 1 && tB >= 0 && tB <= 1) {
return true;
}
}
}
if (n == first) {
return false;
}
p = n;
}
}
/**
* Get the number of points in the bag
*
* @return The number of points in the bag
*/
public int countPoints() {
if (first == null) {
return 0;
}
int count = 0;
Point p = first;
do {
++count;
} while ((p = p.next) != first);
return count;
}
/**
* Check if the point provided was contained
*
* @param point The point provided
* @return True if it's in the bag
*/
public boolean contains(Vector2f point) {
if (first == null) {
return false;
}
if (first.prev.pt.equals(point)) {
return true;
}
if (first.pt.equals(point)) {
return true;
}
return false;
}
}
public boolean triangulate() {
Vector2f[] temp = triangulate(new Vector2f[0]);
for (int i = 0; i < temp.length; i++) {
if (temp[i] == null) {
break;
} else {
triangles.add(temp[i]);
}
}
return true;
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTriangleCount()
*/
public int getTriangleCount() {
return triangles.size() / 3;
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTrianglePoint(int, int)
*/
public float[] getTrianglePoint(int tri, int i) {
Vector2f pt = (Vector2f) triangles.get((tri * 3) + i);
return new float[] { pt.x, pt.y };
}
}

View File

@@ -0,0 +1,193 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
/**
* A shape that morphs between a set of other shapes
*
* @author kevin
*/
public class MorphShape extends Shape {
/** The shapes to morph between */
private ArrayList shapes = new ArrayList();
/** The offset between the shapes */
private float offset;
/** The current shape */
private Shape current;
/** The next shape */
private Shape next;
/**
* Create a new mighty morphin shape
*
* @param base The base shape we're starting the morph from
*/
public MorphShape(Shape base) {
shapes.add(base);
float[] copy = base.points;
this.points = new float[copy.length];
current = base;
next = base;
}
/**
* Add a subsequent shape that we should morph too in order
*
* @param shape The new shape that forms part of the morphing shape
*/
public void addShape(Shape shape) {
if (shape.points.length != points.length) {
throw new RuntimeException("Attempt to morph between two shapes with different vertex counts");
}
Shape prev = (Shape) shapes.get(shapes.size()-1);
if (equalShapes(prev, shape)) {
shapes.add(prev);
} else {
shapes.add(shape);
}
if (shapes.size() == 2) {
next = (Shape) shapes.get(1);
}
}
/**
* Check if the shape's points are all equal
*
* @param a The first shape to compare
* @param b The second shape to compare
* @return True if the shapes are equal
*/
private boolean equalShapes(Shape a, Shape b) {
a.checkPoints();
b.checkPoints();
for (int i=0;i<a.points.length;i++) {
if (a.points[i] != b.points[i]) {
return false;
}
}
return true;
}
/**
* Set the "time" index for this morph. This is given in terms of shapes, so
* 0.5f would give you the position half way between the first and second shapes.
*
* @param time The time index to represent on this shape
*/
public void setMorphTime(float time) {
int p = (int) time;
int n = p + 1;
float offset = time - p;
p = rational(p);
n = rational(n);
setFrame(p, n, offset);
}
/**
* Update the morph time and hence the curent frame
*
* @param delta The amount to change the morph time by
*/
public void updateMorphTime(float delta) {
offset += delta;
if (offset < 0) {
int index = shapes.indexOf(current);
if (index < 0) {
index = shapes.size() - 1;
}
int nframe = rational(index+1);
setFrame(index, nframe, offset);
offset += 1;
} else if (offset > 1) {
int index = shapes.indexOf(next);
if (index < 1) {
index = 0;
}
int nframe = rational(index+1);
setFrame(index, nframe, offset);
offset -= 1;
} else {
pointsDirty = true;
}
}
/**
* Set the current frame
*
* @param current The current frame
*/
public void setExternalFrame(Shape current) {
this.current = current;
next = (Shape) shapes.get(0);
offset = 0;
}
/**
* Get an index that is rational, i.e. fits inside this set of shapes
*
* @param n The index to rationalize
* @return The index rationalized
*/
private int rational(int n) {
while (n >= shapes.size()) {
n -= shapes.size();
}
while (n < 0) {
n += shapes.size();
}
return n;
}
/**
* Set the frame to be represented
*
* @param a The index of the first shape
* @param b The index of the second shape
* @param offset The offset between the two shapes to represent
*/
private void setFrame(int a, int b, float offset) {
current = (Shape) shapes.get(a);
next = (Shape) shapes.get(b);
this.offset = offset;
pointsDirty = true;
}
/**
* @see MorphShape#createPoints()
*/
protected void createPoints() {
if (current == next) {
System.arraycopy(current.points,0,points,0,points.length);
return;
}
float[] apoints = current.points;
float[] bpoints = next.points;
for (int i=0;i<points.length;i++) {
points[i] = apoints[i] * (1 - offset);
points[i] += bpoints[i] * offset;
}
}
/**
* @see MorphShape#transform(Transform)
*/
public Shape transform(Transform transform) {
createPoints();
Polygon poly = new Polygon(points);
return poly;
}
}

View File

@@ -0,0 +1,617 @@
package org.newdawn.slick.geom;
/**
* A second triangulator that seems slightly more robust
*
* @author Online examples
*/
public class NeatTriangulator implements Triangulator
{
/** The error factor */
static final float EPSILON = 1E-006F;
/** The x coordinates */
private float pointsX[];
/** The y coordiantes */
private float pointsY[];
/** The number of points that have been added */
private int numPoints;
/** The edges defines by triangulation */
private Edge edges[];
/** Voroni */
private int V[];
/** The number of edges found */
private int numEdges;
/** The triangles that have been found */
private Triangle triangles[];
/** The number of triangles found */
private int numTriangles;
/** The current offset */
private float offset = EPSILON;
/**
* Create a new triangulator
*/
public NeatTriangulator()
{
pointsX = new float[100];
pointsY = new float[100];
numPoints = 0;
edges = new Edge[100];
numEdges = 0;
triangles = new Triangle[100];
numTriangles = 0;
}
/**
* Clear the triangulator status
*/
public void clear()
{
numPoints = 0;
numEdges = 0;
numTriangles = 0;
}
/**
* Find an edge between two verts
*
* @param i The index of the first vert
* @param j The index of the second vert
* @return The index of the dge
*/
private int findEdge(int i, int j)
{
int k;
int l;
if(i < j)
{
k = i;
l = j;
} else
{
k = j;
l = i;
}
for(int i1 = 0; i1 < numEdges; i1++)
if(edges[i1].v0 == k && edges[i1].v1 == l)
return i1;
return -1;
}
/**
* Add a discovered edge
*
* @param i The index of the first vert
* @param j The index of the second vert
* @param k The index of the spread vert
*/
private void addEdge(int i, int j, int k)
{
int l1 = findEdge(i, j);
int j1;
int k1;
Edge edge;
if(l1 < 0)
{
if(numEdges == edges.length)
{
Edge aedge[] = new Edge[edges.length * 2];
System.arraycopy(edges, 0, aedge, 0, numEdges);
edges = aedge;
}
j1 = -1;
k1 = -1;
l1 = numEdges++;
edge = edges[l1] = new Edge();
} else
{
edge = edges[l1];
j1 = edge.t0;
k1 = edge.t1;
}
int l;
int i1;
if(i < j)
{
l = i;
i1 = j;
j1 = k;
} else
{
l = j;
i1 = i;
k1 = k;
}
edge.v0 = l;
edge.v1 = i1;
edge.t0 = j1;
edge.t1 = k1;
edge.suspect = true;
}
/**
* Remove and edge identified by it's verts
*
* @param i The index of the first vert
* @param j The index of the second vert
* @throws InternalException Indicates the edge didn't exist
*/
private void deleteEdge(int i, int j) throws InternalException
{
int k;
if(0 > (k = findEdge(i, j)))
{
throw new InternalException("Attempt to delete unknown edge");
}
else
{
edges[k] = edges[--numEdges];
return;
}
}
/**
* Mark an edge as either a suspect or not
*
* @param i The index of the first vert
* @param j The index of the second vert
* @param flag True if the edge is a suspect
* @throws InternalException Indicates the edge didn't exist
*/
void markSuspect(int i, int j, boolean flag) throws InternalException
{
int k;
if(0 > (k = findEdge(i, j)))
{
throw new InternalException("Attempt to mark unknown edge");
} else
{
edges[k].suspect = flag;
return;
}
}
/**
* Choose the suspect to become part of the triangle
*
* @return The edge selected
*/
private Edge chooseSuspect()
{
for(int i = 0; i < numEdges; i++)
{
Edge edge = edges[i];
if(edge.suspect)
{
edge.suspect = false;
if(edge.t0 >= 0 && edge.t1 >= 0)
return edge;
}
}
return null;
}
/**
* Factor rho.
*
* @param f Factor 1
* @param f1 Factor 2
* @param f2 Factor 3
* @param f3 Factor 4
* @param f4 Factor 5
* @param f5 Factor 6
* @return The computation of rho
*/
private static float rho(float f, float f1, float f2, float f3, float f4, float f5)
{
float f6 = f4 - f2;
float f7 = f5 - f3;
float f8 = f - f4;
float f9 = f1 - f5;
float f18 = f6 * f9 - f7 * f8;
if(f18 > 0.0F)
{
if(f18 < 1E-006F)
f18 = 1E-006F;
float f12 = f6 * f6;
float f13 = f7 * f7;
float f14 = f8 * f8;
float f15 = f9 * f9;
float f10 = f2 - f;
float f11 = f3 - f1;
float f16 = f10 * f10;
float f17 = f11 * f11;
return ((f12 + f13) * (f14 + f15) * (f16 + f17)) / (f18 * f18);
} else
{
return -1F;
}
}
/**
* Check if the point P is inside the triangle defined by
* the points A,B,C
*
* @param f Point A x-coordinate
* @param f1 Point A y-coordinate
* @param f2 Point B x-coordinate
* @param f3 Point B y-coordinate
* @param f4 Point C x-coordinate
* @param f5 Point C y-coordinate
* @param f6 Point P x-coordinate
* @param f7 Point P y-coordinate
* @return True if the point specified is within the triangle
*/
private static boolean insideTriangle(float f, float f1, float f2, float f3, float f4, float f5, float f6, float f7)
{
float f8 = f4 - f2;
float f9 = f5 - f3;
float f10 = f - f4;
float f11 = f1 - f5;
float f12 = f2 - f;
float f13 = f3 - f1;
float f14 = f6 - f;
float f15 = f7 - f1;
float f16 = f6 - f2;
float f17 = f7 - f3;
float f18 = f6 - f4;
float f19 = f7 - f5;
float f22 = f8 * f17 - f9 * f16;
float f20 = f12 * f15 - f13 * f14;
float f21 = f10 * f19 - f11 * f18;
return f22 >= 0.0D && f21 >= 0.0D && f20 >= 0.0D;
}
/**
* Cut a the contour and add a triangle into V to describe the
* location of the cut
*
* @param i The index of the first point
* @param j The index of the second point
* @param k The index of the third point
* @param l ?
* @return True if a triangle was found
*/
private boolean snip(int i, int j, int k, int l)
{
float f = pointsX[V[i]];
float f1 = pointsY[V[i]];
float f2 = pointsX[V[j]];
float f3 = pointsY[V[j]];
float f4 = pointsX[V[k]];
float f5 = pointsY[V[k]];
if(1E-006F > (f2 - f) * (f5 - f1) - (f3 - f1) * (f4 - f))
return false;
for(int i1 = 0; i1 < l; i1++)
if(i1 != i && i1 != j && i1 != k)
{
float f6 = pointsX[V[i1]];
float f7 = pointsY[V[i1]];
if(insideTriangle(f, f1, f2, f3, f4, f5, f6, f7))
return false;
}
return true;
}
/**
* Get the area defined by the points
*
* @return The area defined by the points
*/
private float area()
{
float f = 0.0F;
int i = numPoints - 1;
for(int j = 0; j < numPoints;)
{
f += pointsX[i] * pointsY[j] - pointsY[i] * pointsX[j];
i = j++;
}
return f * 0.5F;
}
/**
* Perform simple triangulation
*
* @throws InternalException Indicates a polygon that can't be triangulated
*/
public void basicTriangulation() throws InternalException
{
int i = numPoints;
if(i < 3)
return;
numEdges = 0;
numTriangles = 0;
V = new int[i];
if(0.0D < area())
{
for(int k = 0; k < i; k++)
V[k] = k;
} else
{
for(int l = 0; l < i; l++)
V[l] = numPoints - 1 - l;
}
int k1 = 2 * i;
int i1 = i - 1;
while(i > 2)
{
if(0 >= k1--) {
throw new InternalException("Bad polygon");
}
int j = i1;
if(i <= j)
j = 0;
i1 = j + 1;
if(i <= i1)
i1 = 0;
int j1 = i1 + 1;
if(i <= j1)
j1 = 0;
if(snip(j, i1, j1, i))
{
int l1 = V[j];
int i2 = V[i1];
int j2 = V[j1];
if(numTriangles == triangles.length)
{
Triangle atriangle[] = new Triangle[triangles.length * 2];
System.arraycopy(triangles, 0, atriangle, 0, numTriangles);
triangles = atriangle;
}
triangles[numTriangles] = new Triangle(l1, i2, j2);
addEdge(l1, i2, numTriangles);
addEdge(i2, j2, numTriangles);
addEdge(j2, l1, numTriangles);
numTriangles++;
int k2 = i1;
for(int l2 = i1 + 1; l2 < i; l2++)
{
V[k2] = V[l2];
k2++;
}
i--;
k1 = 2 * i;
}
}
V = null;
}
/**
* Optimize the triangulation by applying delauney rules
*
* @throws InternalException Indicates an invalid polygon
*/
private void optimize() throws InternalException
{
do
{
Edge edge;
if ((edge = chooseSuspect()) == null) {
break;
}
int i1 = edge.v0;
int k1 = edge.v1;
int i = edge.t0;
int j = edge.t1;
int j1 = -1;
int l1 = -1;
for (int k = 0; k < 3; k++)
{
int i2 = triangles[i].v[k];
if(i1 == i2 || k1 == i2) {
continue;
}
l1 = i2;
break;
}
for (int l = 0; l < 3; l++)
{
int j2 = triangles[j].v[l];
if(i1 == j2 || k1 == j2) {
continue;
}
j1 = j2;
break;
}
if(-1 == j1 || -1 == l1) {
throw new InternalException("can't find quad");
}
float f = pointsX[i1];
float f1 = pointsY[i1];
float f2 = pointsX[j1];
float f3 = pointsY[j1];
float f4 = pointsX[k1];
float f5 = pointsY[k1];
float f6 = pointsX[l1];
float f7 = pointsY[l1];
float f8 = rho(f, f1, f2, f3, f4, f5);
float f9 = rho(f, f1, f4, f5, f6, f7);
float f10 = rho(f2, f3, f4, f5, f6, f7);
float f11 = rho(f2, f3, f6, f7, f, f1);
if(0.0F > f8 || 0.0F > f9) {
throw new InternalException("original triangles backwards");
}
if(0.0F <= f10 && 0.0F <= f11)
{
if(f8 > f9) {
f8 = f9;
}
if(f10 > f11) {
f10 = f11;
}
if(f8 > f10) {
deleteEdge(i1, k1);
triangles[i].v[0] = j1;
triangles[i].v[1] = k1;
triangles[i].v[2] = l1;
triangles[j].v[0] = j1;
triangles[j].v[1] = l1;
triangles[j].v[2] = i1;
addEdge(j1, k1, i);
addEdge(k1, l1, i);
addEdge(l1, j1, i);
addEdge(l1, i1, j);
addEdge(i1, j1, j);
addEdge(j1, l1, j);
markSuspect(j1, l1, false);
}
}
} while(true);
}
/**
* Upate the triangles
*/
public boolean triangulate()
{
try
{
basicTriangulation();
//optimize();
return true;
}
catch (InternalException e)
{
numEdges = 0;
}
return false;
}
/**
* Add a point to the polygon
*/
public void addPolyPoint(float x, float y)
{
for (int i=0;i<numPoints;i++) {
if ((pointsX[i] == x) && (pointsY[i] == y)) {
//return;
y += offset;
offset += EPSILON;
}
}
if(numPoints == pointsX.length)
{
float af[] = new float[numPoints * 2];
System.arraycopy(pointsX, 0, af, 0, numPoints);
pointsX = af;
af = new float[numPoints * 2];
System.arraycopy(pointsY, 0, af, 0, numPoints);
pointsY = af;
}
pointsX[numPoints] = x;
pointsY[numPoints] = y;
numPoints++;
}
/**
* A single triangle
*
* @author Online Source
*/
class Triangle
{
/** The verticies index */
int v[];
/**
* Create a new triangle
*
* @param i The index of vert 1
* @param j The index of vert 2
* @param k The index of vert 3
*/
Triangle(int i, int j, int k)
{
v = new int[3];
v[0] = i;
v[1] = j;
v[2] = k;
}
}
/**
* A single edge between two points
*
* @author Online Source
*/
class Edge
{
/** The start vert */
int v0;
/** The end vert */
int v1;
/** The start tangent vert */
int t0;
/** The end tangent vert */
int t1;
/** True if the edge is marked as a suspect */
boolean suspect;
/**
* Create a new empty edge
*/
Edge()
{
v0 = -1;
v1 = -1;
t0 = -1;
t1 = -1;
}
}
/**
* A failure to triangulate, hidden from outside and handled
*
* @author Online Source
*/
class InternalException extends Exception {
/**
* Create an internal exception
*
* @param msg The message describing the exception
*/
public InternalException(String msg) {
super(msg);
}
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTriangleCount()
*/
public int getTriangleCount() {
return numTriangles;
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTrianglePoint(int, int)
*/
public float[] getTrianglePoint(int tri, int i) {
float xp = pointsX[triangles[tri].v[i]];
float yp = pointsY[triangles[tri].v[i]];
return new float[] {xp,yp};
}
/**
* @see org.newdawn.slick.geom.Triangulator#startHole()
*/
public void startHole() {
}
}

View File

@@ -0,0 +1,113 @@
package org.newdawn.slick.geom;
/**
* A triangulator implementation that splits the triangules of another, subdividing
* to give a higher tesselation - and hence smoother transitions.
*
* @author kevin
*/
public class OverTriangulator implements Triangulator {
/** The triangles data */
private float[][] triangles;
/**
* Create a new triangulator
*
* @param tris The original set of triangles to be sub-dividied
*/
public OverTriangulator(Triangulator tris) {
triangles = new float[tris.getTriangleCount()*6*3][2];
int tcount = 0;
for (int i=0;i<tris.getTriangleCount();i++) {
float cx = 0;
float cy = 0;
for (int p = 0;p < 3;p++) {
float[] pt = tris.getTrianglePoint(i, p);
cx += pt[0];
cy += pt[1];
}
cx /= 3;
cy /= 3;
for (int p = 0;p < 3;p++) {
int n = p +1;
if (n > 2) {
n = 0;
}
float[] pt1 = tris.getTrianglePoint(i, p);
float[] pt2 = tris.getTrianglePoint(i, n);
pt1[0] = (pt1[0] + pt2[0]) / 2;
pt1[1] = (pt1[1] + pt2[1]) / 2;
triangles[(tcount *3) + 0][0] = cx;
triangles[(tcount *3) + 0][1] = cy;
triangles[(tcount *3) + 1][0] = pt1[0];
triangles[(tcount *3) + 1][1] = pt1[1];
triangles[(tcount *3) + 2][0] = pt2[0];
triangles[(tcount *3) + 2][1] = pt2[1];
tcount++;
}
for (int p = 0;p < 3;p++) {
int n = p +1;
if (n > 2) {
n = 0;
}
float[] pt1 = tris.getTrianglePoint(i, p);
float[] pt2 = tris.getTrianglePoint(i, n);
pt2[0] = (pt1[0] + pt2[0]) / 2;
pt2[1] = (pt1[1] + pt2[1]) / 2;
triangles[(tcount *3) + 0][0] = cx;
triangles[(tcount *3) + 0][1] = cy;
triangles[(tcount *3) + 1][0] = pt1[0];
triangles[(tcount *3) + 1][1] = pt1[1];
triangles[(tcount *3) + 2][0] = pt2[0];
triangles[(tcount *3) + 2][1] = pt2[1];
tcount++;
}
}
}
/**
* @see org.newdawn.slick.geom.Triangulator#addPolyPoint(float, float)
*/
public void addPolyPoint(float x, float y) {
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTriangleCount()
*/
public int getTriangleCount() {
return triangles.length / 3;
}
/**
* @see org.newdawn.slick.geom.Triangulator#getTrianglePoint(int, int)
*/
public float[] getTrianglePoint(int tri, int i) {
float[] pt = triangles[(tri * 3)+i];
return new float[] {pt[0],pt[1]};
}
/**
* @see org.newdawn.slick.geom.Triangulator#startHole()
*/
public void startHole() {
}
/**
* @see org.newdawn.slick.geom.Triangulator#triangulate()
*/
public boolean triangulate() {
return true;
}
}

View File

@@ -0,0 +1,241 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
/**
* A shape built from lines and curves. Hole support is present but
* restricted.
*
* @author kevin
*/
public class Path extends Shape {
/** The local list of points */
private ArrayList localPoints = new ArrayList();
/** The current x coordinate */
private float cx;
/** The current y coordiante */
private float cy;
/** True if the path has been closed */
private boolean closed;
/** The list of holes placed */
private ArrayList holes = new ArrayList();
/** The current hole being built */
private ArrayList hole;
/**
* Create a new path
*
* @param sx The start x coordinate of the path
* @param sy The start y coordiante of the path
*/
public Path(float sx, float sy) {
localPoints.add(new float[] {sx,sy});
cx = sx;
cy = sy;
pointsDirty = true;
}
/**
* Start building a hole in the previously defined contour
*
* @param sx The start point of the hole
* @param sy The start point of the hole
*/
public void startHole(float sx, float sy) {
hole = new ArrayList();
holes.add(hole);
}
/**
* Add a line to the contour or hole which ends at the specified
* location.
*
* @param x The x coordinate to draw the line to
* @param y The y coordiante to draw the line to
*/
public void lineTo(float x, float y) {
if (hole != null) {
hole.add(new float[] {x,y});
} else {
localPoints.add(new float[] {x,y});
}
cx = x;
cy = y;
pointsDirty = true;
}
/**
* Close the path to form a polygon
*/
public void close() {
closed = true;
}
/**
* Add a curve to the specified location (using the default segments 10)
*
* @param x The destination x coordinate
* @param y The destination y coordiante
* @param cx1 The x coordiante of the first control point
* @param cy1 The y coordiante of the first control point
* @param cx2 The x coordinate of the second control point
* @param cy2 The y coordinate of the second control point
*/
public void curveTo(float x, float y, float cx1, float cy1, float cx2, float cy2) {
curveTo(x,y,cx1,cy1,cx2,cy2,10);
}
/**
* Add a curve to the specified location (specifing the number of segments)
*
* @param x The destination x coordinate
* @param y The destination y coordiante
* @param cx1 The x coordiante of the first control point
* @param cy1 The y coordiante of the first control point
* @param cx2 The x coordinate of the second control point
* @param cy2 The y coordinate of the second control point
* @param segments The number of segments to use for the new curve
*/
public void curveTo(float x, float y, float cx1, float cy1, float cx2, float cy2, int segments) {
// special case for zero movement
if ((cx == x) && (cy == y)) {
return;
}
Curve curve = new Curve(new Vector2f(cx,cy),new Vector2f(cx1,cy1),new Vector2f(cx2,cy2),new Vector2f(x,y));
float step = 1.0f / segments;
for (int i=1;i<segments+1;i++) {
float t = i * step;
Vector2f p = curve.pointAt(t);
if (hole != null) {
hole.add(new float[] {p.x,p.y});
} else {
localPoints.add(new float[] {p.x,p.y});
}
cx = p.x;
cy = p.y;
}
pointsDirty = true;
}
/**
* @see org.newdawn.slick.geom.Shape#createPoints()
*/
protected void createPoints() {
points = new float[localPoints.size() * 2];
for (int i=0;i<localPoints.size();i++) {
float[] p = (float[]) localPoints.get(i);
points[(i*2)] = p[0];
points[(i*2)+1] = p[1];
}
}
/**
* @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
*/
public Shape transform(Transform transform) {
Path p = new Path(cx,cy);
p.localPoints = transform(localPoints, transform);
for (int i=0;i<holes.size();i++) {
p.holes.add(transform((ArrayList) holes.get(i), transform));
}
p.closed = this.closed;
return p;
}
/**
* Transform a list of points
*
* @param pts The pts to transform
* @param t The transform to apply
* @return The transformed points
*/
private ArrayList transform(ArrayList pts, Transform t) {
float[] in = new float[pts.size()*2];
float[] out = new float[pts.size()*2];
for (int i=0;i<pts.size();i++) {
in[i*2] = ((float[]) pts.get(i))[0];
in[(i*2)+1] = ((float[]) pts.get(i))[1];
}
t.transform(in, 0, out, 0, pts.size());
ArrayList outList = new ArrayList();
for (int i=0;i<pts.size();i++) {
outList.add(new float[] {out[(i*2)],out[(i*2)+1]});
}
return outList;
}
// /**
// * Calculate the triangles that can fill this shape
// */
// protected void calculateTriangles() {
// if (!trianglesDirty) {
// return;
// }
// if (points.length >= 6) {
// boolean clockwise = true;
// float area = 0;
// for (int i=0;i<(points.length/2)-1;i++) {
// float x1 = points[(i*2)];
// float y1 = points[(i*2)+1];
// float x2 = points[(i*2)+2];
// float y2 = points[(i*2)+3];
//
// area += (x1 * y2) - (y1 * x2);
// }
// area /= 2;
// clockwise = area > 0;
//
// if (clockwise) {
// tris = new MannTriangulator();
// for (int i=0;i<points.length;i+=2) {
// tris.addPolyPoint(points[i], points[i+1]);
// }
//
// for (int h=0;h<holes.size();h++) {
// ArrayList hole = (ArrayList) holes.get(h);
// tris.startHole();
// for (int i=0;i<hole.size();i++) {
// float[] pt = (float[]) hole.get(i);
// tris.addPolyPoint(pt[0],pt[1]);
// }
// }
// tris.triangulate();
// } else {
// tris = new MannTriangulator();
// for (int i=points.length-2;i>=0;i-=2) {
// tris.addPolyPoint(points[i], points[i+1]);
// }
//
// for (int h=0;h<holes.size();h++) {
// ArrayList hole = (ArrayList) holes.get(h);
// tris.startHole();
// for (int i=hole.size()-1;i>=0;i--) {
// float[] pt = (float[]) hole.get(i);
// tris.addPolyPoint(pt[0],pt[1]);
// }
// }
// tris.triangulate();
// }
//
// } else {
// tris.triangulate();
// }
//
// trianglesDirty = false;
// }
/**
* True if this is a closed shape
*
* @return True if this is a closed shape
*/
public boolean closed() {
return closed;
}
}

View File

@@ -0,0 +1,72 @@
package org.newdawn.slick.geom;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Transform;
/**
* A single point shape
*
* @author Kova
*/
public class Point extends Shape
{
/**
* Create a new point
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public Point(float x, float y)
{
this.x = x;
this.y = y;
checkPoints();
}
/**
* @see org.newdawn.slick.geom.Shape#transform(org.newdawn.slick.geom.Transform)
*/
public Shape transform(Transform transform)
{
float result[] = new float[points.length];
transform.transform(points, 0, result, 0, points.length / 2);
return new Point(points[0], points[1]);
}
/**
* @see org.newdawn.slick.geom.Shape#createPoints()
*/
protected void createPoints()
{
points = new float[2];
points[0] = getX();
points[1] = getY();
maxX = x;
maxY = y;
minX = x;
minY = y;
findCenter();
calculateRadius();
}
/**
* @see org.newdawn.slick.geom.Shape#findCenter()
*/
protected void findCenter()
{
center = new float[2];
center[0] = points[0];
center[1] = points[1];
}
/**
* @see org.newdawn.slick.geom.Shape#calculateRadius()
*/
protected void calculateRadius()
{
boundingCircleRadius = 0;
}
}

View File

@@ -0,0 +1,200 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
import java.util.Arrays;
/**
* A polygon implementation meeting the <code>Shape</code> contract.
*
* @author Mark
*/
public class Polygon extends Shape {
/** Allow duplicated points */
private boolean allowDups = false;
/** True if the polygon is closed */
private boolean closed = true;
/**
* Construct a new polygon with 3 or more points.
* This constructor will take the first set of points and copy them after
* the last set of points to create a closed shape.
*
* @param points An array of points in x, y order.
*/
public Polygon(float points[]) {
int length = points.length;
this.points = new float[length];
maxX = -Float.MIN_VALUE;
maxY = -Float.MIN_VALUE;
minX = Float.MAX_VALUE;
minY = Float.MAX_VALUE;
x = Float.MAX_VALUE;
y = Float.MAX_VALUE;
for(int i=0;i<length;i++) {
this.points[i] = points[i];
if(i % 2 == 0) {
if(points[i] > maxX) {
maxX = points[i];
}
if(points[i] < minX) {
minX = points[i];
}
if(points[i] < x) {
x = points[i];
}
}
else {
if(points[i] > maxY) {
maxY = points[i];
}
if(points[i] < minY) {
minY = points[i];
}
if(points[i] < y) {
y = points[i];
}
}
}
findCenter();
calculateRadius();
pointsDirty = true;
}
/**
* Create an empty polygon
*
*/
public Polygon(){
points = new float[0];
maxX = -Float.MIN_VALUE;
maxY = -Float.MIN_VALUE;
minX = Float.MAX_VALUE;
minY = Float.MAX_VALUE;
}
/**
* Indicate if duplicate points are allow
*
* @param allowDups True if duplicate points are allowed
*/
public void setAllowDuplicatePoints(boolean allowDups) {
this.allowDups = allowDups;
}
/**
* Add a point to the polygon
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public void addPoint(float x, float y) {
if (hasVertex(x,y) && (!allowDups)) {
return;
}
ArrayList tempPoints = new ArrayList();
for(int i=0;i<points.length;i++) {
tempPoints.add(new Float(points[i]));
}
tempPoints.add(new Float(x));
tempPoints.add(new Float(y));
int length = tempPoints.size();
points = new float[length];
for(int i=0;i<length;i++) {
points[i] = ((Float)tempPoints.get(i)).floatValue();
}
if(x > maxX) {
maxX = x;
}
if(y > maxY) {
maxY = y;
}
if(x < minX) {
minX = x;
}
if(y < minY) {
minY = y;
}
findCenter();
calculateRadius();
pointsDirty = true;
}
/**
* Apply a transformation and return a new shape. This will not alter the current shape but will
* return the transformed shape.
*
* @param transform The transform to be applied
* @return The transformed shape.
*/
public Shape transform(Transform transform) {
checkPoints();
Polygon resultPolygon = new Polygon();
float result[] = new float[points.length];
transform.transform(points, 0, result, 0, points.length / 2);
resultPolygon.points = result;
resultPolygon.findCenter();
resultPolygon.closed = closed;
return resultPolygon;
}
/**
* @see org.newdawn.slick.geom.Shape#setX(float)
*/
public void setX(float x) {
super.setX(x);
pointsDirty = false;
}
/**
* @see org.newdawn.slick.geom.Shape#setY(float)
*/
public void setY(float y) {
super.setY(y);
pointsDirty = false;
}
/**
* @see org.newdawn.slick.geom.Shape#createPoints()
*/
protected void createPoints() {
// This is empty since a polygon must have it's points all the time.
}
/**
* @see org.newdawn.slick.geom.Shape#closed()
*/
public boolean closed() {
return closed;
}
/**
* Indicate if the polygon should be closed
*
* @param closed True if the polygon should be closed
*/
public void setClosed(boolean closed) {
this.closed = closed;
}
/**
* Provide a copy of this polygon
*
* @return A copy of this polygon
*/
public Polygon copy() {
float[] copyPoints = new float[points.length];
System.arraycopy(points, 0, copyPoints, 0, copyPoints.length);
return new Polygon(copyPoints);
}
}

View File

@@ -0,0 +1,269 @@
package org.newdawn.slick.geom;
/**
* An axis oriented used for shape bounds
*
* @author Kevin Glass
*/
public class Rectangle extends Shape {
/** The width of the box */
protected float width;
/** The height of the box */
protected float height;
/**
* Create a new bounding box
*
* @param x The x position of the box
* @param y The y position of the box
* @param width The width of the box
* @param height The hieght of the box
*/
public Rectangle(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
maxX = x+width;
maxY = y+height;
checkPoints();
}
/**
* Check if this rectangle contains a point
*
* @param xp The x coordinate of the point to check
* @param yp The y coordinate of the point to check
* @return True if the point is within the rectangle
*/
public boolean contains(float xp, float yp) {
if (xp <= getX()) {
return false;
}
if (yp <= getY()) {
return false;
}
if (xp >= maxX) {
return false;
}
if (yp >= maxY) {
return false;
}
return true;
}
/**
* Set the bounds of this rectangle based on the given rectangle
*
* @param other The other rectangle whose bounds should be applied
*/
public void setBounds(Rectangle other) {
setBounds(other.getX(), other.getY(), other.getWidth(), other.getHeight());
}
/**
* Set the bounds of this rectangle
*
* @param x The x coordinate of this rectangle
* @param y The y coordinate of this rectangle
* @param width The width to set in this rectangle
* @param height The height to set in this rectangle
*/
public void setBounds(float x, float y, float width, float height) {
setX(x);
setY(y);
setSize(width, height);
}
/**
* Set the size (widtha and height) of this rectangle
*
* @param width The width to set in this rectangle
* @param height The height to set in this rectangle
*/
public void setSize(float width, float height) {
setWidth(width);
setHeight(height);
}
/**
* Get the width of the box
*
* @return The width of the box
*/
public float getWidth() {
return width;
}
/**
* Get the height of the box
*
* @return The height of the box
*/
public float getHeight() {
return height;
}
/**
* Grow the rectangle at all edges by the given amounts. This will result in the
* rectangle getting larger around it's centre.
*
* @param h The amount to adjust horizontally
* @param v The amount to ajust vertically
*/
public void grow(float h, float v) {
setX(getX() - h);
setY(getY() - v);
setWidth(getWidth() + (h*2));
setHeight(getHeight() + (v*2));
}
/**
* Grow the rectangle based on scaling it's size
*
* @param h The scale to apply to the horizontal
* @param v The scale to appy to the vertical
*/
public void scaleGrow(float h, float v) {
grow(getWidth() * (h-1), getHeight() * (v-1));
}
/**
* Set the width of this box
*
* @param width The new width of this box
*/
public void setWidth(float width) {
if (width != this.width) {
pointsDirty = true;
this.width = width;
maxX = x+width;
}
}
/**
* Set the heightof this box
*
* @param height The height of this box
*/
public void setHeight(float height) {
if (height != this.height) {
pointsDirty = true;
this.height = height;
maxY = y+height;
}
}
/**
* Check if this box touches another
*
* @param shape The other shape to check against
* @return True if the rectangles touch
*/
public boolean intersects(Shape shape) {
if(shape instanceof Rectangle) {
Rectangle other = (Rectangle)shape;
if ((x > (other.x + other.width)) || ((x + width) < other.x)) {
return false;
}
if ((y > (other.y + other.height)) || ((y + height) < other.y)) {
return false;
}
return true;
}
else if(shape instanceof Circle) {
return intersects((Circle)shape);
}
else {
return super.intersects(shape);
}
}
protected void createPoints() {
float useWidth = width ;
float useHeight = height;
points = new float[8];
points[0] = x;
points[1] = y;
points[2] = x + useWidth;
points[3] = y;
points[4] = x + useWidth;
points[5] = y + useHeight;
points[6] = x;
points[7] = y + useHeight;
maxX = points[2];
maxY = points[5];
minX = points[0];
minY = points[1];
findCenter();
calculateRadius();
}
/**
* Check if a circle touches this rectangle
*
* @param other The circle to check against
* @return True if they touch
*/
private boolean intersects(Circle other) {
return other.intersects(this);
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "[Rectangle "+width+"x"+height+"]";
}
/**
* Check if a rectangle contains a point (static to use it everywhere)
*
* @param xp
* The x coordinate of the point to check
* @param yp
* The y coordinate of the point to check
* @param xr
* The x coordinate of the rectangle
* @param yr
* The y coordinate of the rectangle
* @param widthr
* The width of the rectangle
* @param heightr The height of the rectangle
* @return True if the point is within the rectangle
*/
public static boolean contains(float xp, float yp, float xr, float yr,
float widthr, float heightr) {
return (xp >= xr) && (yp >= yr) && (xp <= xr + widthr)
&& (yp <= yr + heightr);
}
/**
* Apply a transformation and return a new shape. This will not alter the current shape but will
* return the transformed shape.
*
* @param transform The transform to be applied
* @return The transformed shape.
*/
public Shape transform(Transform transform) {
checkPoints();
Polygon resultPolygon = new Polygon();
float result[] = new float[points.length];
transform.transform(points, 0, result, 0, points.length / 2);
resultPolygon.points = result;
resultPolygon.findCenter();
resultPolygon.checkPoints();
return resultPolygon;
}
}

View File

@@ -0,0 +1,284 @@
package org.newdawn.slick.geom;
import java.util.ArrayList;
import java.util.List;
import org.newdawn.slick.util.FastTrig;
/**
* Class to create rounded rectangles with.
*
* @author Mark Bernard
*/
public class RoundedRectangle extends Rectangle {
/** Indicates the top left corner should be rounded */
public static final int TOP_LEFT = 1;
/** Indicates the top right corner should be rounded */
public static final int TOP_RIGHT = 2;
/** Indicates the bottom right corner should be rounded */
public static final int BOTTOM_RIGHT = 4;
/** Indicates the bottom left corner should be rounded */
public static final int BOTTOM_LEFT = 8;
/** Indicates the all cornders should be rounded */
public static final int ALL = TOP_LEFT | TOP_RIGHT | BOTTOM_RIGHT | BOTTOM_LEFT;
/** Default number of segments to draw the rounded corners with */
private static final int DEFAULT_SEGMENT_COUNT = 25;
/** radius of each corner */
private float cornerRadius;
/** number of segments for each corner */
private int segmentCount;
/** The flags indicating which corners should be rounded */
private int cornerFlags;
/**
* Construct a rectangle with rounded corners.
*
* @param x The x position of the rectangle.
* @param y The y position of the rectangle.
* @param width The width of the rectangle.
* @param height The hieght of the rectangle.
* @param cornerRadius The radius to use for the arc in each corner.
*/
public RoundedRectangle(float x, float y, float width, float height, float cornerRadius) {
this(x, y, width, height, cornerRadius, DEFAULT_SEGMENT_COUNT);
}
/**
* Construct a rectangle with rounded corners.
*
* @param x The x position of the rectangle.
* @param y The y position of the rectangle.
* @param width The width of the rectangle.
* @param height The hieght of the rectangle.
* @param cornerRadius The radius to use for the arc in each corner.
* @param segmentCount The number of segments to use to draw each corner arc.
*/
public RoundedRectangle(float x, float y, float width, float height, float cornerRadius, int segmentCount) {
this(x,y,width,height,cornerRadius,segmentCount,ALL);
}
/**
* Construct a rectangle with rounded corners.
*
* @param x The x position of the rectangle.
* @param y The y position of the rectangle.
* @param width The width of the rectangle.
* @param height The hieght of the rectangle.
* @param cornerRadius The radius to use for the arc in each corner.
* @param segmentCount The number of segments to use to draw each corner arc.
* @param cornerFlags Indicates which corners should be rounded
*/
public RoundedRectangle(float x, float y, float width, float height,
float cornerRadius, int segmentCount, int cornerFlags) {
super(x,y,width,height);
if(cornerRadius < 0) {
throw new IllegalArgumentException("corner radius must be >= 0");
}
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.cornerRadius = cornerRadius;
this.segmentCount = segmentCount;
this.pointsDirty = true;
this.cornerFlags = cornerFlags;
}
/**
* Get the radius for each corner.
*
* @return The radius for each corner.
*/
public float getCornerRadius() {
return cornerRadius;
}
/**
* Set the radius for each corner.
*
* @param cornerRadius The radius for each corner to set.
*/
public void setCornerRadius(float cornerRadius) {
if (cornerRadius >= 0) {
if (cornerRadius != this.cornerRadius) {
this.cornerRadius = cornerRadius;
pointsDirty = true;
}
}
}
/**
* Get the height of this rectangle.
*
* @return The height of this rectangle.
*/
public float getHeight() {
return height;
}
/**
* Set the height of this rectangle.
*
* @param height The height to set.
*/
public void setHeight(float height) {
if (this.height != height) {
this.height = height;
pointsDirty = true;
}
}
/**
* Get the width of this rectangle.
*
* @return The width of this rectangle.
*/
public float getWidth() {
return width;
}
/**
* Set the width of this rectangle.
*
* @param width The width to set.
*/
public void setWidth(float width) {
if (width != this.width) {
this.width = width;
pointsDirty = true;
}
}
protected void createPoints() {
maxX = x + width;
maxY = y + height;
minX = x;
minY = y;
float useWidth = width - 1;
float useHeight = height - 1;
if(cornerRadius == 0) {
points = new float[8];
points[0] = x;
points[1] = y;
points[2] = x + useWidth;
points[3] = y;
points[4] = x + useWidth;
points[5] = y + useHeight;
points[6] = x;
points[7] = y + useHeight;
}
else {
float doubleRadius = cornerRadius * 2;
if(doubleRadius > useWidth) {
doubleRadius = useWidth;
cornerRadius = doubleRadius / 2;
}
if(doubleRadius > useHeight) {
doubleRadius = useHeight;
cornerRadius = doubleRadius / 2;
}
ArrayList tempPoints = new ArrayList();
//the outer most set of points for each arc will also ac as the points that start the
//straight sides, so the straight sides do not have to be added.
//top left corner arc
if ((cornerFlags & TOP_LEFT) != 0) {
tempPoints.addAll(createPoints(segmentCount, cornerRadius, x + cornerRadius, y + cornerRadius, 180, 270));
} else {
tempPoints.add(new Float(x));
tempPoints.add(new Float(y));
}
//top right corner arc
if ((cornerFlags & TOP_RIGHT) != 0) {
tempPoints.addAll(createPoints(segmentCount, cornerRadius, x + useWidth - cornerRadius, y + cornerRadius, 270, 360));
} else {
tempPoints.add(new Float(x+useWidth));
tempPoints.add(new Float(y));
}
//bottom right corner arc
if ((cornerFlags & BOTTOM_RIGHT) != 0) {
tempPoints.addAll(createPoints(segmentCount, cornerRadius, x + useWidth - cornerRadius, y + useHeight - cornerRadius, 0, 90));
} else {
tempPoints.add(new Float(x+useWidth));
tempPoints.add(new Float(y+useHeight));
}
//bottom left corner arc
if ((cornerFlags & BOTTOM_LEFT) != 0) {
tempPoints.addAll(createPoints(segmentCount, cornerRadius, x + cornerRadius, y + useHeight - cornerRadius, 90, 180));
} else {
tempPoints.add(new Float(x));
tempPoints.add(new Float(y+useHeight));
}
points = new float[tempPoints.size()];
for(int i=0;i<tempPoints.size();i++) {
points[i] = ((Float)tempPoints.get(i)).floatValue();
}
}
findCenter();
calculateRadius();
}
/**
* Generate the points to fill a corner arc.
*
* @param numberOfSegments How fine to make the ellipse.
* @param radius The radius of the arc.
* @param cx The x center of the arc.
* @param cy The y center of the arc.
* @param start The start angle of the arc.
* @param end The end angle of the arc.
* @return The points created.
*/
private List createPoints(int numberOfSegments, float radius, float cx, float cy, float start, float end) {
ArrayList tempPoints = new ArrayList();
int step = 360 / numberOfSegments;
for (float a=start;a<=end+step;a+=step) {
float ang = a;
if (ang > end) {
ang = end;
}
float x = (float) (cx + (FastTrig.cos(Math.toRadians(ang)) * radius));
float y = (float) (cy + (FastTrig.sin(Math.toRadians(ang)) * radius));
tempPoints.add(new Float(x));
tempPoints.add(new Float(y));
}
return tempPoints;
}
/**
* Apply a transformation and return a new shape. This will not alter the current shape but will
* return the transformed shape.
*
* @param transform The transform to be applied
* @return The transformed shape.
*/
public Shape transform(Transform transform) {
checkPoints();
Polygon resultPolygon = new Polygon();
float result[] = new float[points.length];
transform.transform(points, 0, result, 0, points.length / 2);
resultPolygon.points = result;
resultPolygon.findCenter();
return resultPolygon;
}
}

View File

@@ -0,0 +1,767 @@
package org.newdawn.slick.geom;
import java.io.Serializable;
/**
* The description of any 2D shape that can be transformed. The points provided approximate the intent
* of the shape.
*
* @author Mark
*/
public abstract class Shape implements Serializable {
/** The points representing this polygon. */
protected float points[];
/** Center point of the polygon. */
protected float center[];
/** The left most point of this shape. */
protected float x;
/** The top most point of this shape. */
protected float y;
/** The right most point of this shape */
protected float maxX;
/** The bottom most point of this shape */
protected float maxY;
/** The left most point of this shape. */
protected float minX;
/** The top most point of this shape. */
protected float minY;
/** Radius of a circle that can completely enclose this shape. */
protected float boundingCircleRadius;
/** Flag to tell whether points need to be generated */
protected boolean pointsDirty;
/** The triangles that define the shape */
protected transient Triangulator tris;
/** True if the triangles need updating */
protected boolean trianglesDirty;
/**
* Shape constructor.
*
*/
public Shape() {
pointsDirty = true;
}
/**
* Set the top-left location of this shape
*
* @param x The x coordinate of the new location of the shape
* @param y The y coordinate of the new location of the shape
*/
public void setLocation(float x, float y) {
setX(x);
setY(y);
}
/**
* Apply a transformation and return a new shape. This will not alter the current shape but will
* return the transformed shape.
*
* @param transform The transform to be applied
* @return The transformed shape.
*/
public abstract Shape transform(Transform transform);
/**
* Subclasses implement this to create the points of the shape.
*
*/
protected abstract void createPoints();
/**
* Get the x location of the left side of this shape.
*
* @return The x location of the left side of this shape.
*/
public float getX() {
return x;
}
/**
* Set the x position of the left side this shape.
*
* @param x The new x position of the left side this shape.
*/
public void setX(float x) {
if (x != this.x) {
float dx = x - this.x;
this.x = x;
if ((points == null) || (center == null)) {
checkPoints();
}
// update the points in the special case
for (int i=0;i<points.length/2;i++) {
points[i*2] += dx;
}
center[0] += dx;
x += dx;
maxX += dx;
minX += dx;
trianglesDirty = true;
}
}
/**
* Set the y position of the top of this shape.
*
* @param y The new y position of the top of this shape.
*/
public void setY(float y) {
if (y != this.y) {
float dy = y - this.y;
this.y = y;
if ((points == null) || (center == null)) {
checkPoints();
}
// update the points in the special case
for (int i=0;i<points.length/2;i++) {
points[(i*2)+1] += dy;
}
center[1] += dy;
y += dy;
maxY += dy;
minY += dy;
trianglesDirty = true;
}
}
/**
* Get the y position of the top of this shape.
*
* @return The y position of the top of this shape.
*/
public float getY() {
return y;
}
/**
* Get the top-left location of this shape.
*
* @return The coordinate of the top-left of this shape
*/
public Vector2f getLocation() {
return new Vector2f(getX(), getY());
}
/**
* Set the top-left location of this shape
*
* @param loc The new coordinate of the top-left of this shape
*/
public void setLocation(Vector2f loc) {
setX(loc.x);
setY(loc.y);
}
/**
* Get the x center of this shape.
*
* @return The x center of this shape.
*/
public float getCenterX() {
checkPoints();
return center[0];
}
/**
* Set the x center of this shape.
*
* @param centerX The center point to set.
*/
public void setCenterX(float centerX) {
if ((points == null) || (center == null)) {
checkPoints();
}
float xDiff = centerX - getCenterX();
setX(x + xDiff);
}
/**
* Get the y center of this shape.
*
* @return The y center of this shape.
*/
public float getCenterY() {
checkPoints();
return center[1];
}
/**
* Set the y center of this shape.
*
* @param centerY The center point to set.
*/
public void setCenterY(float centerY) {
if ((points == null) || (center == null)) {
checkPoints();
}
float yDiff = centerY - getCenterY();
setY(y + yDiff);
}
/**
* Get the right most point of this shape.
*
* @return The right most point of this shape.
*/
public float getMaxX() {
checkPoints();
return maxX;
}
/**
* Get the bottom most point of this shape.
*
* @return The bottom most point of this shape.
*/
public float getMaxY() {
checkPoints();
return maxY;
}
/**
* Get the left most point of this shape.
*
* @return The left most point of this shape.
*/
public float getMinX() {
checkPoints();
return minX;
}
/**
* Get the top most point of this shape.
*
* @return The top most point of this shape.
*/
public float getMinY() {
checkPoints();
return minY;
}
/**
* Get the radius of a circle that can completely enclose this shape.
*
* @return The radius of the circle.
*/
public float getBoundingCircleRadius() {
checkPoints();
return boundingCircleRadius;
}
/**
* Get the point closet to the center of all the points in this Shape
*
* @return The x,y coordinates of the center.
*/
public float[] getCenter() {
checkPoints();
return center;
}
/**
* Get the points that outline this shape. Use CW winding rule
*
* @return an array of x,y points
*/
public float[] getPoints() {
checkPoints();
return points;
}
/**
* Get the number of points in this polygon
*
* @return The number of points in this polygon
*/
public int getPointCount() {
checkPoints();
return points.length / 2;
}
/**
* Get a single point in this polygon
*
* @param index The index of the point to retrieve
* @return The point's coordinates
*/
public float[] getPoint(int index) {
checkPoints();
float result[] = new float[2];
result[0] = points[index * 2];
result[1] = points[index * 2 + 1];
return result;
}
/**
* Get the combine normal of a given point
*
* @param index The index of the point whose normal should be retrieved
* @return The combined normal of a given point
*/
public float[] getNormal(int index) {
float[] current = getPoint(index);
float[] prev = getPoint(index - 1 < 0 ? getPointCount() - 1 : index - 1);
float[] next = getPoint(index + 1 >= getPointCount() ? 0 : index + 1);
float[] t1 = getNormal(prev, current);
float[] t2 = getNormal(current, next);
if ((index == 0) && (!closed())) {
return t2;
}
if ((index == getPointCount()-1) && (!closed())) {
return t1;
}
float tx = (t1[0]+t2[0])/2;
float ty = (t1[1]+t2[1])/2;
float len = (float) Math.sqrt((tx*tx)+(ty*ty));
return new float[] {tx/len,ty/len};
}
/**
* Check if the shape passed is entirely contained within
* this shape.
*
* @param other The other shape to test against this one
* @return True if the other shape supplied is entirely contained
* within this one.
*/
public boolean contains(Shape other) {
if (other.intersects(this)) {
return false;
}
for (int i=0;i<other.getPointCount();i++) {
float[] pt = other.getPoint(i);
if (!contains(pt[0], pt[1])) {
return false;
}
}
return true;
}
/**
* Get the normal of the line between two points
*
* @param start The start point
* @param end The end point
* @return The normal of the line between the two points
*/
private float[] getNormal(float[] start, float[] end) {
float dx = start[0] - end[0];
float dy = start[1] - end[1];
float len = (float) Math.sqrt((dx*dx)+(dy*dy));
dx /= len;
dy /= len;
return new float[] {-dy,dx};
}
/**
* Check if the given point is part of the path that
* forms this shape
*
* @param x The x position of the point to check
* @param y The y position of the point to check
* @return True if the point is includes in the path of the polygon
*/
public boolean includes(float x, float y) {
if (points.length == 0) {
return false;
}
checkPoints();
Line testLine = new Line(0,0,0,0);
Vector2f pt = new Vector2f(x,y);
for (int i=0;i<points.length;i+=2) {
int n = i+2;
if (n >= points.length) {
n = 0;
}
testLine.set(points[i], points[i+1], points[n], points[n+1]);
if (testLine.on(pt)) {
return true;
}
}
return false;
}
/**
* Get the index of a given point
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
* @return The index of the point or -1 if the point is not part of this shape path
*/
public int indexOf(float x, float y) {
for (int i=0;i<points.length;i+=2) {
if ((points[i] == x) && (points[i+1] == y)) {
return i / 2;
}
}
return -1;
}
/**
* Check if this polygon contains the given point
*
* @param x The x position of the point to check
* @param y The y position of the point to check
* @return True if the point is contained in the polygon
*/
public boolean contains(float x, float y) {
checkPoints();
if (points.length == 0) {
return false;
}
boolean result = false;
float xnew,ynew;
float xold,yold;
float x1,y1;
float x2,y2;
int npoints = points.length;
xold=points[npoints - 2];
yold=points[npoints - 1];
for (int i=0;i < npoints;i+=2) {
xnew = points[i];
ynew = points[i + 1];
if (xnew > xold) {
x1 = xold;
x2 = xnew;
y1 = yold;
y2 = ynew;
}
else {
x1 = xnew;
x2 = xold;
y1 = ynew;
y2 = yold;
}
if ((xnew < x) == (x <= xold) /* edge "open" at one end */
&& ((double)y - (double)y1) * (x2 - x1)
< ((double)y2 - (double)y1) * (x - x1)) {
result = !result;
}
xold = xnew;
yold = ynew;
}
return result;
}
/**
* Check if this shape intersects with the shape provided.
*
* @param shape The shape to check if it intersects with this one.
* @return True if the shapes do intersect, false otherwise.
*/
public boolean intersects(Shape shape) {
/*
* Intersection formula used:
* (x4 - x3)(y1 - y3) - (y4 - y3)(x1 - x3)
* UA = ---------------------------------------
* (y4 - y3)(x2 - x1) - (x4 - x3)(y2 - y1)
*
* (x2 - x1)(y1 - y3) - (y2 - y1)(x1 - x3)
* UB = ---------------------------------------
* (y4 - y3)(x2 - x1) - (x4 - x3)(y2 - y1)
*
* if UA and UB are both between 0 and 1 then the lines intersect.
*
* Source: http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/
*/
checkPoints();
boolean result = false;
float points[] = getPoints(); // (x3, y3) and (x4, y4)
float thatPoints[] = shape.getPoints(); // (x1, y1) and (x2, y2)
int length = points.length;
int thatLength = thatPoints.length;
double unknownA;
double unknownB;
if (!closed()) {
length -= 2;
}
if (!shape.closed()) {
thatLength -= 2;
}
// x1 = thatPoints[j]
// x2 = thatPoints[j + 2]
// y1 = thatPoints[j + 1]
// y2 = thatPoints[j + 3]
// x3 = points[i]
// x4 = points[i + 2]
// y3 = points[i + 1]
// y4 = points[i + 3]
for(int i=0;i<length;i+=2) {
int iNext = i+2;
if (iNext >= points.length) {
iNext = 0;
}
for(int j=0;j<thatLength;j+=2) {
int jNext = j+2;
if (jNext >= thatPoints.length) {
jNext = 0;
}
unknownA = (((points[iNext] - points[i]) * (double) (thatPoints[j + 1] - points[i + 1])) -
((points[iNext+1] - points[i + 1]) * (thatPoints[j] - points[i]))) /
(((points[iNext+1] - points[i + 1]) * (thatPoints[jNext] - thatPoints[j])) -
((points[iNext] - points[i]) * (thatPoints[jNext+1] - thatPoints[j + 1])));
unknownB = (((thatPoints[jNext] - thatPoints[j]) * (double) (thatPoints[j + 1] - points[i + 1])) -
((thatPoints[jNext+1] - thatPoints[j + 1]) * (thatPoints[j] - points[i]))) /
(((points[iNext+1] - points[i + 1]) * (thatPoints[jNext] - thatPoints[j])) -
((points[iNext] - points[i]) * (thatPoints[jNext+1] - thatPoints[j + 1])));
if(unknownA >= 0 && unknownA <= 1 && unknownB >= 0 && unknownB <= 1) {
result = true;
break;
}
}
if(result) {
break;
}
}
return result;
}
/**
* Check if a particular location is a vertex of this polygon
*
* @param x The x coordinate to check
* @param y The y coordinate to check
* @return True if the cordinates supplied are a vertex of this polygon
*/
public boolean hasVertex(float x, float y) {
if (points.length == 0) {
return false;
}
checkPoints();
for (int i=0;i<points.length;i+=2) {
if ((points[i] == x) && (points[i+1] == y)) {
return true;
}
}
return false;
}
/**
* Get the center of this polygon.
*
*/
protected void findCenter() {
center = new float[]{0, 0};
int length = points.length;
for(int i=0;i<length;i+=2) {
center[0] += points[i];
center[1] += points[i + 1];
}
center[0] /= (length / 2);
center[1] /= (length / 2);
}
/**
* Calculate the radius of a circle that can completely enclose this shape.
*
*/
protected void calculateRadius() {
boundingCircleRadius = 0;
for(int i=0;i<points.length;i+=2) {
float temp = ((points[i] - center[0]) * (points[i] - center[0])) +
((points[i + 1] - center[1]) * (points[i + 1] - center[1]));
boundingCircleRadius = (boundingCircleRadius > temp) ? boundingCircleRadius : temp;
}
boundingCircleRadius = (float)Math.sqrt(boundingCircleRadius);
}
/**
* Calculate the triangles that can fill this shape
*/
protected void calculateTriangles() {
if ((!trianglesDirty) && (tris != null)) {
return;
}
if (points.length >= 6) {
boolean clockwise = true;
float area = 0;
for (int i=0;i<(points.length/2)-1;i++) {
float x1 = points[(i*2)];
float y1 = points[(i*2)+1];
float x2 = points[(i*2)+2];
float y2 = points[(i*2)+3];
area += (x1 * y2) - (y1 * x2);
}
area /= 2;
clockwise = area > 0;
tris = new NeatTriangulator();
for (int i=0;i<points.length;i+=2) {
tris.addPolyPoint(points[i], points[i+1]);
}
tris.triangulate();
}
trianglesDirty = false;
}
/**
* Increase triangulation
*/
public void increaseTriangulation() {
checkPoints();
calculateTriangles();
tris = new OverTriangulator(tris);
}
/**
* The triangles that define the filled version of this shape
*
* @return The triangles that define the
*/
public Triangulator getTriangles() {
checkPoints();
calculateTriangles();
return tris;
}
/**
* Check the dirty flag and create points as necessary.
*/
protected final void checkPoints() {
if (pointsDirty) {
createPoints();
findCenter();
calculateRadius();
if (points.length > 0) {
maxX = points[0];
maxY = points[1];
minX = points[0];
minY = points[1];
for (int i=0;i<points.length/2;i++) {
maxX = Math.max(points[i*2],maxX);
maxY = Math.max(points[(i*2)+1],maxY);
minX = Math.min(points[i*2],minX);
minY = Math.min(points[(i*2)+1],minY);
}
}
pointsDirty = false;
trianglesDirty = true;
}
}
/**
* Cause all internal state to be generated and cached
*/
public void preCache() {
checkPoints();
getTriangles();
}
/**
* True if this is a closed shape
*
* @return True if this is a closed shape
*/
public boolean closed() {
return true;
}
/**
* Prune any required points in this shape
*
* @return The new shape with points pruned
*/
public Shape prune() {
Polygon result = new Polygon();
for (int i=0;i<getPointCount();i++) {
int next = i+1 >= getPointCount() ? 0 : i+1;
int prev = i-1 < 0 ? getPointCount() - 1 : i-1;
float dx1 = getPoint(i)[0] - getPoint(prev)[0];
float dy1 = getPoint(i)[1] - getPoint(prev)[1];
float dx2 = getPoint(next)[0] - getPoint(i)[0];
float dy2 = getPoint(next)[1] - getPoint(i)[1];
float len1 = (float) Math.sqrt((dx1*dx1) + (dy1*dy1));
float len2 = (float) Math.sqrt((dx2*dx2) + (dy2*dy2));
dx1 /= len1;
dy1 /= len1;
dx2 /= len2;
dy2 /= len2;
if ((dx1 != dx2) || (dy1 != dy2)) {
result.addPoint(getPoint(i)[0],getPoint(i)[1]);
}
}
return result;
}
/**
* Subtract the given shape from this one. Note that this method only deals
* with edges, it will not create holes in polygons.
*
* @param other The other shape to subtract from this one
* @return The newly created set of shapes resulting from the operation
*/
public Shape[] subtract(Shape other) {
return new GeomUtil().subtract(this, other);
}
/**
* Join this shape with another.
*
* @param other The other shape to join with this one
* @return The newly created set of shapes resulting from the operation
*/
public Shape[] union(Shape other) {
return new GeomUtil().union(this, other);
}
/**
* Get the width of the shape
*
* @return The width of the shape
*/
public float getWidth() {
return maxX - minX;
}
/**
* Get the height of the shape
*
* @return The height of the shape
*/
public float getHeight() {
return maxY - minY;
}
}

View File

@@ -0,0 +1,391 @@
package org.newdawn.slick.geom;
import org.newdawn.slick.Image;
import org.newdawn.slick.ShapeFill;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureImpl;
import org.newdawn.slick.opengl.renderer.LineStripRenderer;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
/**
* @author Mark Bernard
*
* 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);
}
}

View File

@@ -0,0 +1,19 @@
package org.newdawn.slick.geom;
/**
* A class capable of generating texture coordiantes based on
* rendering positions of verticies. This allows custom texturing
* of geometric shapes
*
* @author kevin
*/
public interface TexCoordGenerator {
/**
* Get the texture coordinate for a given render position
*
* @param x The x coordinate of the vertex being rendered
* @param y The y coordinate of the vertex being rendered
* @return The texture coordinate to apply
*/
public Vector2f getCoordFor(float x, float y);
}

View File

@@ -0,0 +1,230 @@
package org.newdawn.slick.geom;
import org.newdawn.slick.util.FastTrig;
/**
* A 2 dimensional transformation that can be applied to <code>Shape</code> implemenations.
*
* @author Mark
*/
public class Transform {
/**
* Value for each position in the matrix
*
* |0 1 2|
* |3 4 5|
* |6 7 8|
*/
private float matrixPosition[];
/**
* Create and identity transform
*
*/
public Transform() {
matrixPosition = new float[]{1, 0, 0, 0, 1, 0, 0, 0, 1};
}
/**
* Copy a transform
*
* @param other The other transform to copy
*/
public Transform(Transform other) {
matrixPosition = new float[9];
for (int i=0;i<9;i++) {
matrixPosition[i] = other.matrixPosition[i];
}
}
/**
* Concatanate to transform into one
*
* @param t1 The first transform to join
* @param t2 The second transform to join
*/
public Transform(Transform t1, Transform t2) {
this(t1);
concatenate(t2);
}
/**
* Create a transform for the given positions
*
* @param matrixPosition An array of float[6] to set up a transform
* @throws RuntimeException if the array is not of length 6
*/
public Transform(float matrixPosition[]) {
if(matrixPosition.length != 6) {
throw new RuntimeException("The parameter must be a float array of length 6.");
}
this.matrixPosition = new float[]{matrixPosition[0], matrixPosition[1], matrixPosition[2],
matrixPosition[3], matrixPosition[4], matrixPosition[5],
0, 0, 1};
}
/**
* Create a transform for the given positions
*
* @param point00 float for the first position
* @param point01 float for the second position
* @param point02 float for the third position
* @param point10 float for the fourth position
* @param point11 float for the fifth position
* @param point12 float for the sixth position
*/
public Transform(float point00, float point01, float point02, float point10, float point11, float point12) {
matrixPosition = new float[]{point00, point01, point02, point10, point11, point12, 0, 0, 1};
}
/**
* Transform the point pairs in the source array and store them in the destination array.
* All operations will be done before storing the results in the destination. This way the source
* and destination array can be the same without worry of overwriting information before it is transformed.
*
* @param source Array of floats containing the points to be transformed
* @param sourceOffset Where in the array to start processing
* @param destination Array of floats to store the results.
* @param destOffset Where in the array to start storing
* @param numberOfPoints Number of points to be transformed
* @throws ArrayIndexOutOfBoundsException if sourceOffset + numberOfPoints * 2 > source.length or the same operation on the destination array
*/
public void transform(float source[], int sourceOffset, float destination[], int destOffset, int numberOfPoints) {
//TODO performance can be improved by removing the safety to the destination array
float result[] = source == destination ? new float[numberOfPoints * 2] : destination;
for(int i=0;i<numberOfPoints * 2;i+=2) {
for(int j=0;j<6;j+=3) {
result[i + (j / 3)] = source[i + sourceOffset] * matrixPosition[j] + source[i + sourceOffset + 1] * matrixPosition[j + 1] + 1 * matrixPosition[j + 2];
}
}
if (source == destination) {
//for safety of the destination, the results are copied after the entire operation.
for(int i=0;i<numberOfPoints * 2;i+=2) {
destination[i + destOffset] = result[i];
destination[i + destOffset + 1] = result[i + 1];
}
}
}
/**
* Update this Transform by concatenating the given Transform to this one.
*
* @param tx The Transfrom to concatenate to this one.
* @return The resulting Transform
*/
public Transform concatenate(Transform tx) {
float[] mp = new float[9];
float n00 = matrixPosition[0] * tx.matrixPosition[0] + matrixPosition[1] * tx.matrixPosition[3];
float n01 = matrixPosition[0] * tx.matrixPosition[1] + matrixPosition[1] * tx.matrixPosition[4];
float n02 = matrixPosition[0] * tx.matrixPosition[2] + matrixPosition[1] * tx.matrixPosition[5] + matrixPosition[2];
float n10 = matrixPosition[3] * tx.matrixPosition[0] + matrixPosition[4] * tx.matrixPosition[3];
float n11 = matrixPosition[3] * tx.matrixPosition[1] + matrixPosition[4] * tx.matrixPosition[4];
float n12 = matrixPosition[3] * tx.matrixPosition[2] + matrixPosition[4] * tx.matrixPosition[5] + matrixPosition[5];
mp[0] = n00;
mp[1] = n01;
mp[2] = n02;
mp[3] = n10;
mp[4] = n11;
mp[5] = n12;
//
// mp[0] = matrixPosition[0] * transform.matrixPosition[0] + matrixPosition[0] * transform.matrixPosition[3] + matrixPosition[0] * transform.matrixPosition[6];
// mp[1] = matrixPosition[1] * transform.matrixPosition[1] + matrixPosition[1] * transform.matrixPosition[4] + matrixPosition[1] * transform.matrixPosition[7];
// mp[2] = matrixPosition[2] * transform.matrixPosition[2] + matrixPosition[2] * transform.matrixPosition[5] + matrixPosition[2] * transform.matrixPosition[8];
// mp[3] = matrixPosition[3] * transform.matrixPosition[0] + matrixPosition[3] * transform.matrixPosition[3] + matrixPosition[3] * transform.matrixPosition[6];
// mp[4] = matrixPosition[4] * transform.matrixPosition[1] + matrixPosition[4] * transform.matrixPosition[4] + matrixPosition[4] * transform.matrixPosition[7];
// mp[5] = matrixPosition[5] * transform.matrixPosition[2] + matrixPosition[5] * transform.matrixPosition[5] + matrixPosition[5] * transform.matrixPosition[8];
//
matrixPosition = mp;
return this;
}
/**
* Convert this Transform to a String.
*
* @return This Transform in human readable format.
*/
public String toString() {
String result = "Transform[[" + matrixPosition[0] + "," + matrixPosition[1] + "," + matrixPosition[2] +
"][" + matrixPosition[3] + "," + matrixPosition[4] + "," + matrixPosition[5] +
"][" + matrixPosition[6] + "," + matrixPosition[7] + "," + matrixPosition[8] + "]]";
return result.toString();
}
/**
* Get an array representing this Transform.
*
* @return an array representing this Transform.
*/
public float[] getMatrixPosition() {
return matrixPosition;
}
/**
* Create a new rotation Transform
*
* @param angle The angle in radians to set the transform.
* @return The resulting Transform
*/
public static Transform createRotateTransform(float angle) {
return new Transform((float)FastTrig.cos(angle), -(float)FastTrig.sin(angle), 0, (float)FastTrig.sin(angle), (float)FastTrig.cos(angle), 0);
}
/**
* Create a new rotation Transform around the specified point
*
* @param angle The angle in radians to set the transform.
* @param x The x coordinate around which to rotate.
* @param y The y coordinate around which to rotate.
* @return The resulting Transform
*/
public static Transform createRotateTransform(float angle, float x, float y) {
Transform temp = Transform.createRotateTransform(angle);
float sinAngle = temp.matrixPosition[3];
float oneMinusCosAngle = 1.0f - temp.matrixPosition[4];
temp.matrixPosition[2] = x * oneMinusCosAngle + y * sinAngle;
temp.matrixPosition[5] = y * oneMinusCosAngle - x * sinAngle;
return temp;
}
/**
* Create a new translation Transform
*
* @param xOffset The amount to move in the x direction
* @param yOffset The amount to move in the y direction
* @return The resulting Transform
*/
public static Transform createTranslateTransform(float xOffset, float yOffset) {
return new Transform(1, 0, xOffset, 0, 1, yOffset);
}
/**
* Create an new scaling Transform
*
* @param xScale The amount to scale in the x coordinate
* @param yScale The amount to scale in the x coordinate
* @return The resulting Transform
*/
public static Transform createScaleTransform(float xScale, float yScale) {
return new Transform(xScale, 0, 0, 0, yScale, 0);
}
/**
* Transform the vector2f based on the matrix defined in this transform
*
* @param pt The point to be transformed
* @return The resulting point transformed by this matrix
*/
public Vector2f transform(Vector2f pt) {
float[] in = new float[] {pt.x, pt.y};
float[] out = new float[2];
transform(in, 0, out, 0, 1);
return new Vector2f(out[0], out[1]);
}
}

View File

@@ -0,0 +1,48 @@
package org.newdawn.slick.geom;
import java.io.Serializable;
/**
* A collection of triangles
*
* @author kevin
*/
public interface Triangulator extends Serializable {
/**
* Get a count of the number of triangles produced
*
* @return The number of triangles produced
*/
public int getTriangleCount();
/**
* Get a point on a specified generated triangle
*
* @param tri The index of the triangle to interegate
* @param i The index of the point within the triangle to retrieve
* (0 - 2)
* @return The x,y coordinate pair for the point
*/
public float[] getTrianglePoint(int tri, int i);
/**
* Add a point that forms part of the outer polygon
*
* @param x The x coordinate of the point
* @param y The y coordiante of the point
*/
public void addPolyPoint(float x, float y);
/**
* Start a hole in the polygon
*/
public void startHole();
/**
* Run the triangulation
*
* @return True if successful
*/
public boolean triangulate();
}

View File

@@ -0,0 +1,396 @@
package org.newdawn.slick.geom;
import java.io.Serializable;
import org.newdawn.slick.util.FastTrig;
/**
* A two dimensional vector
*
* @author Kevin Glass
*/
public strictfp class Vector2f implements Serializable {
/** The version ID for this class */
private static final long serialVersionUID = 1339934L;
/** The x component of this vector */
public float x;
/** The y component of this vector */
public float y;
/**
* Create an empty vector
*/
public Vector2f() {
}
/**
* Create a vector based on the contents of a coordinate array
*
* @param coords The coordinates array, index 0 = x, index 1 = y
*/
public Vector2f(float[] coords) {
x = coords[0];
y = coords[1];
}
/**
* Create a new vector based on an angle
*
* @param theta The angle of the vector in degrees
*/
public Vector2f(double theta) {
x = 1;
y = 0;
setTheta(theta);
}
/**
* Calculate the components of the vectors based on a angle
*
* @param theta The angle to calculate the components from (in degrees)
*/
public void setTheta(double theta) {
// Next lines are to prevent numbers like -1.8369701E-16
// when working with negative numbers
if ((theta < -360) || (theta > 360)) {
theta = theta % 360;
}
if (theta < 0) {
theta = 360 + theta;
}
double oldTheta = getTheta();
if ((theta < -360) || (theta > 360)) {
oldTheta = oldTheta % 360;
}
if (theta < 0) {
oldTheta = 360 + oldTheta;
}
float len = length();
x = len * (float) FastTrig.cos(StrictMath.toRadians(theta));
y = len * (float) FastTrig.sin(StrictMath.toRadians(theta));
// x = x / (float) FastTrig.cos(StrictMath.toRadians(oldTheta))
// * (float) FastTrig.cos(StrictMath.toRadians(theta));
// y = x / (float) FastTrig.sin(StrictMath.toRadians(oldTheta))
// * (float) FastTrig.sin(StrictMath.toRadians(theta));
}
/**
* Adjust this vector by a given angle
*
* @param theta
* The angle to adjust the angle by (in degrees)
* @return This vector - useful for chaining operations
*
*/
public Vector2f add(double theta) {
setTheta(getTheta() + theta);
return this;
}
/**
* Adjust this vector by a given angle
*
* @param theta The angle to adjust the angle by (in degrees)
* @return This vector - useful for chaining operations
*/
public Vector2f sub(double theta) {
setTheta(getTheta() - theta);
return this;
}
/**
* Get the angle this vector is at
*
* @return The angle this vector is at (in degrees)
*/
public double getTheta() {
double theta = StrictMath.toDegrees(StrictMath.atan2(y, x));
if ((theta < -360) || (theta > 360)) {
theta = theta % 360;
}
if (theta < 0) {
theta = 360 + theta;
}
return theta;
}
/**
* Get the x component
*
* @return The x component
*/
public float getX() {
return x;
}
/**
* Get the y component
*
* @return The y component
*/
public float getY() {
return y;
}
/**
* Create a new vector based on another
*
* @param other The other vector to copy into this one
*/
public Vector2f(Vector2f other) {
this(other.getX(),other.getY());
}
/**
* Create a new vector
*
* @param x The x component to assign
* @param y The y component to assign
*/
public Vector2f(float x, float y) {
this.x = x;
this.y = y;
}
/**
* Set the value of this vector
*
* @param other The values to set into the vector
*/
public void set(Vector2f other) {
set(other.getX(),other.getY());
}
/**
* Dot this vector against another
*
* @param other The other vector dot agianst
* @return The dot product of the two vectors
*/
public float dot(Vector2f other) {
return (x * other.getX()) + (y * other.getY());
}
/**
* Set the values in this vector
*
* @param x The x component to set
* @param y The y component to set
* @return This vector - useful for chaining operations
*/
public Vector2f set(float x, float y) {
this.x = x;
this.y = y;
return this;
}
/**
* A vector perpendicular to this vector.
*
* @return a vector perpendicular to this vector
*/
public Vector2f getPerpendicular() {
return new Vector2f(-y, x);
}
/**
* Set the values in this vector
*
* @param pt The pair of values to set into the vector
* @return This vector - useful for chaining operations
*/
public Vector2f set(float[] pt) {
return set(pt[0], pt[1]);
}
/**
* Negate this vector
*
* @return A copy of this vector negated
*/
public Vector2f negate() {
return new Vector2f(-x, -y);
}
/**
* Negate this vector without creating a new copy
*
* @return This vector - useful for chaning operations
*/
public Vector2f negateLocal() {
x = -x;
y = -y;
return this;
}
/**
* Add a vector to this vector
*
* @param v The vector to add
* @return This vector - useful for chaning operations
*/
public Vector2f add(Vector2f v)
{
x += v.getX();
y += v.getY();
return this;
}
/**
* Subtract a vector from this vector
*
* @param v The vector subtract
* @return This vector - useful for chaining operations
*/
public Vector2f sub(Vector2f v)
{
x -= v.getX();
y -= v.getY();
return this;
}
/**
* Scale this vector by a value
*
* @param a The value to scale this vector by
* @return This vector - useful for chaining operations
*/
public Vector2f scale(float a)
{
x *= a;
y *= a;
return this;
}
/**
* Normalise the vector
*
* @return This vector - useful for chaning operations
*/
public Vector2f normalise() {
float l = length();
if (l == 0) {
return this;
}
x /= l;
y /= l;
return this;
}
/**
* The normal of the vector
*
* @return A unit vector with the same direction as the vector
*/
public Vector2f getNormal() {
Vector2f cp = copy();
cp.normalise();
return cp;
}
/**
* The length of the vector squared
*
* @return The length of the vector squared
*/
public float lengthSquared() {
return (x * x) + (y * y);
}
/**
* Get the length of this vector
*
* @return The length of this vector
*/
public float length()
{
return (float) Math.sqrt(lengthSquared());
}
/**
* Project this vector onto another
*
* @param b The vector to project onto
* @param result The projected vector
*/
public void projectOntoUnit(Vector2f b, Vector2f result) {
float dp = b.dot(this);
result.x = dp * b.getX();
result.y = dp * b.getY();
}
/**
* Return a copy of this vector
*
* @return The new instance that copies this vector
*/
public Vector2f copy() {
return new Vector2f(x,y);
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "[Vector2f "+x+","+y+" ("+length()+")]";
}
/**
* Get the distance from this point to another
*
* @param other The other point we're measuring to
* @return The distance to the other point
*/
public float distance(Vector2f other) {
return (float) Math.sqrt(distanceSquared(other));
}
/**
* Get the distance from this point to another, squared. This
* can sometimes be used in place of distance and avoids the
* additional sqrt.
*
* @param other The other point we're measuring to
* @return The distance to the other point squared
*/
public float distanceSquared(Vector2f other) {
float dx = other.getX() - getX();
float dy = other.getY() - getY();
return (float) (dx*dx)+(dy*dy);
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return 997 * ((int)x) ^ 991 * ((int)y); //large primes!
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object other) {
if (other instanceof Vector2f) {
Vector2f o = ((Vector2f) other);
return (o.x == x) && (o.y == y);
}
return false;
}
}

View File

@@ -0,0 +1,3 @@
<BODY>
Simple geometric wrappers that can be used for rendering and collision.
</BODY>

View File

@@ -0,0 +1,182 @@
package org.newdawn.slick.gui;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.util.InputAdapter;
/**
* The utility class to handle all the input related gubbins for basic GUI
* components
*
* @author kevin
*/
public abstract class AbstractComponent extends InputAdapter {
/** The component that currently has focus */
private static AbstractComponent currentFocus = null;
/** The game container */
protected GUIContext container;
/** Listeners for the component to notify */
protected Set listeners;
/** True if this component currently has focus */
private boolean focus = false;
/** The input we're responding to */
protected Input input;
/**
* Create a new component
*
* @param container
* The container displaying this component
*/
public AbstractComponent(GUIContext container) {
this.container = container;
listeners = new HashSet();
input = container.getInput();
input.addPrimaryListener(this);
setLocation(0, 0);
}
/**
* Add a component listener to be informed when the component sees fit.
*
* It will ignore listeners already added.
*
* @param listener
* listener
*/
public void addListener(ComponentListener listener) {
listeners.add(listener);
}
/**
* Remove a component listener.
*
* It will ignore if the listener wasn't added.
*
* @param listener
* listener
*/
public void removeListener(ComponentListener listener) {
listeners.remove(listener);
}
/**
* Notify all the listeners.
*/
protected void notifyListeners() {
Iterator it = listeners.iterator();
while (it.hasNext()) {
((ComponentListener) it.next()).componentActivated(this);
}
}
/**
* Render this component to the screen
*
* @param container
* The container displaying this component
* @param g
* The graphics context used to render to the display
* @throws SlickException
* If there has been an error rendering the component
*/
public abstract void render(GUIContext container, Graphics g)
throws SlickException;
/**
* Moves the component.
*
* @param x
* X coordinate
* @param y
* Y coordinate
*/
public abstract void setLocation(int x, int y);
/**
* Returns the position in the X coordinate
*
* @return x
*/
public abstract int getX();
/**
* Returns the position in the Y coordinate
*
* @return y
*/
public abstract int getY();
/**
* Get the width of the component
*
* @return The width of the component
*/
public abstract int getWidth();
/**
* Get the height of the component
*
* @return The height of the component
*/
public abstract int getHeight();
/**
* Indicate whether this component should be focused or not
*
* @param focus
* if the component should be focused
*/
public void setFocus(boolean focus) {
if (focus) {
if (currentFocus != null) {
currentFocus.setFocus(false);
}
currentFocus = this;
} else {
if (currentFocus == this) {
currentFocus = null;
}
}
this.focus = focus;
}
/**
* Check if this component currently has focus
*
* @return if this field currently has focus
*/
public boolean hasFocus() {
return focus;
}
/**
* Consume the event currently being processed
*/
protected void consumeEvent() {
input.consumeEvent();
}
/**
* Gives the focus to this component with a click of the mouse.
*
* @see org.newdawn.slick.gui.AbstractComponent#mouseReleased(int, int, int)
*/
public void mouseReleased(int button, int x, int y) {
setFocus(Rectangle.contains(x, y, getX(), getY(), getWidth(),
getHeight()));
}
}

View File

@@ -0,0 +1,83 @@
package org.newdawn.slick.gui;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
/**
* Renamed to provide backwards compatibility
*
* @author kevin
* @deprecated
*/
public abstract class BasicComponent extends AbstractComponent {
/** The x position of the component */
protected int x;
/** The y position of the component */
protected int y;
/** The width of the component */
protected int width;
/** The height of the component */
protected int height;
/**
* Create a new component
*
* @param container
* The container displaying this component
*/
public BasicComponent(GUIContext container) {
super(container);
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#getHeight()
*/
public int getHeight() {
return height;
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#getWidth()
*/
public int getWidth() {
return width;
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#getX()
*/
public int getX() {
return x;
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#getY()
*/
public int getY() {
return y;
}
/**
* Allow the sub-component to render
*
* @param container The container holding the GUI
* @param g The graphics context into which we should render
*/
public abstract void renderImpl(GUIContext container, Graphics g);
/**
* @see org.newdawn.slick.gui.AbstractComponent#render(org.newdawn.slick.gui.GUIContext, org.newdawn.slick.Graphics)
*/
public void render(GUIContext container, Graphics g) throws SlickException {
renderImpl(container,g);
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#setLocation(int, int)
*/
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
}
}

View File

@@ -0,0 +1,17 @@
package org.newdawn.slick.gui;
/**
* A descritpion of a class responding to events occuring on a GUI component
*
* @author kevin
*/
public interface ComponentListener {
/**
* Notification that a component has been activated (button clicked,
* text field entered, etc)
*
* @param source The source of the event
*/
public void componentActivated(AbstractComponent source);
}

View File

@@ -0,0 +1,103 @@
package org.newdawn.slick.gui;
import org.lwjgl.input.Cursor;
import org.newdawn.slick.Font;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.opengl.ImageData;
/**
* The context in which GUI components are created and rendered
*
* @author kevin
*/
public interface GUIContext {
/**
* Get the input system
*
* @return The input system available to this game container
*/
public Input getInput();
/**
* Get the accurate system time
*
* @return The system time in milliseconds
*/
public long getTime();
/**
* Get the width of the standard screen resolution
*
* @return The screen width
*/
public abstract int getScreenWidth();
/**
* Get the height of the standard screen resolution
*
* @return The screen height
*/
public abstract int getScreenHeight();
/**
* Get the width of the game canvas
*
* @return The width of the game canvas
*/
public int getWidth();
/**
* Get the height of the game canvas
*
* @return The height of the game canvas
*/
public int getHeight();
/**
* Get the default system font
*
* @return The default system font
*/
public Font getDefaultFont();
/**
* 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
*/
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
*/
public abstract void setMouseCursor(ImageData data, 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
*/
public abstract void setMouseCursor(Cursor cursor, int hotSpotX, int hotSpotY) throws SlickException;
/**
* Set the default mouse cursor - i.e. the original cursor before any native
* cursor was set
*/
public abstract void setDefaultMouseCursor();
}

View File

@@ -0,0 +1,433 @@
package org.newdawn.slick.gui;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.Sound;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Shape;
/**
* A mouse over area that can be used for menus or buttons
*
* @author kevin
*/
public class MouseOverArea extends AbstractComponent {
/** The default state */
private static final int NORMAL = 1;
/** The mouse down state */
private static final int MOUSE_DOWN = 2;
/** The mouse over state */
private static final int MOUSE_OVER = 3;
/** The normalImage being displayed in normal state */
private Image normalImage;
/** The normalImage being displayed in mouseOver state */
private Image mouseOverImage;
/** The normalImage being displayed in mouseDown state */
private Image mouseDownImage;
/** The colour used in normal state */
private Color normalColor = Color.white;
/** The colour used in mouseOver state */
private Color mouseOverColor = Color.white;
/** The colour used in mouseDown state */
private Color mouseDownColor = Color.white;
/** The sound for mouse over */
private Sound mouseOverSound;
/** The sound for mouse down */
private Sound mouseDownSound;
/** The shape defining the area */
private Shape area;
/** The current normalImage being displayed */
private Image currentImage;
/** The current color being used */
private Color currentColor;
/** True if the mouse is over the area */
private boolean over;
/** True if the mouse button is pressed */
private boolean mouseDown;
/** The state of the area */
private int state = NORMAL;
/** True if the mouse has been up since last press */
private boolean mouseUp;
/**
* Create a new mouse over area
*
* @param container
* The container displaying the mouse over area
* @param image
* The normalImage to display
* @param x
* The position of the area
* @param y
* the position of the area
* @param listener
* A listener to add to the area
*/
public MouseOverArea(GUIContext container, Image image, int x, int y, ComponentListener listener) {
this(container, image, x, y, image.getWidth(), image.getHeight());
addListener(listener);
}
/**
* Create a new mouse over area
*
* @param container
* The container displaying the mouse over area
* @param image
* The normalImage to display
* @param x
* The position of the area
* @param y
* the position of the area
*/
public MouseOverArea(GUIContext container, Image image, int x, int y) {
this(container, image, x, y, image.getWidth(), image.getHeight());
}
/**
* Create a new mouse over area
*
* @param container
* The container displaying the mouse over area
* @param image
* The normalImage to display
* @param x
* The position of the area
* @param y
* the position of the area
* @param width
* The width of the area
* @param height
* The height of the area
* @param listener
* A listener to add to the area
*/
public MouseOverArea(GUIContext container, Image image, int x, int y,
int width, int height, ComponentListener listener) {
this(container,image,x,y,width,height);
addListener(listener);
}
/**
* Create a new mouse over area
*
* @param container
* The container displaying the mouse over area
* @param image
* The normalImage to display
* @param x
* The position of the area
* @param y
* the position of the area
* @param width
* The width of the area
* @param height
* The height of the area
*/
public MouseOverArea(GUIContext container, Image image, int x, int y,
int width, int height) {
this(container,image,new Rectangle(x,y,width,height));
}
/**
* Create a new mouse over area
*
* @param container
* The container displaying the mouse over area
* @param image
* The normalImage to display
* @param shape
* The shape defining the area of the mouse sensitive zone
*/
public MouseOverArea(GUIContext container, Image image, Shape shape) {
super(container);
area = shape;
normalImage = image;
currentImage = image;
mouseOverImage = image;
mouseDownImage = image;
currentColor = normalColor;
state = NORMAL;
Input input = container.getInput();
over = area.contains(input.getMouseX(), input.getMouseY());
mouseDown = input.isMouseButtonDown(0);
updateImage();
}
/**
* Moves the component.
*
* @param x X coordinate
* @param y Y coordinate
*/
public void setLocation(float x, float y) {
if (area != null) {
area.setX(x);
area.setY(y);
}
}
/**
* Set the x coordinate of this area
*
* @param x The new x coordinate of this area
*/
public void setX(float x) {
area.setX(x);
}
/**
* Set the y coordinate of this area
*
* @param y The new y coordinate of this area
*/
public void setY(float y) {
area.setY(y);
}
/**
* Returns the position in the X coordinate
*
* @return x
*/
public int getX() {
return (int) area.getX();
}
/**
* Returns the position in the Y coordinate
*
* @return y
*/
public int getY() {
return (int) area.getY();
}
/**
* Set the normal color used on the image in the default state
*
* @param color
* The color to be used
*/
public void setNormalColor(Color color) {
normalColor = color;
}
/**
* Set the color to be used when the mouse is over the area
*
* @param color
* The color to be used when the mouse is over the area
*/
public void setMouseOverColor(Color color) {
mouseOverColor = color;
}
/**
* Set the color to be used when the mouse is down the area
*
* @param color
* The color to be used when the mouse is down the area
*/
public void setMouseDownColor(Color color) {
mouseDownColor = color;
}
/**
* Set the normal image used on the image in the default state
*
* @param image
* The image to be used
*/
public void setNormalImage(Image image) {
normalImage = image;
}
/**
* Set the image to be used when the mouse is over the area
*
* @param image
* The image to be used when the mouse is over the area
*/
public void setMouseOverImage(Image image) {
mouseOverImage = image;
}
/**
* Set the image to be used when the mouse is down the area
*
* @param image
* The image to be used when the mouse is down the area
*/
public void setMouseDownImage(Image image) {
mouseDownImage = image;
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#render(org.newdawn.slick.gui.GUIContext,
* org.newdawn.slick.Graphics)
*/
public void render(GUIContext container, Graphics g) {
if (currentImage != null) {
int xp = (int) (area.getX() + ((getWidth() - currentImage.getWidth()) / 2));
int yp = (int) (area.getY() + ((getHeight() - currentImage.getHeight()) / 2));
currentImage.draw(xp, yp, currentColor);
} else {
g.setColor(currentColor);
g.fill(area);
}
updateImage();
}
/**
* Update the current normalImage based on the mouse state
*/
private void updateImage() {
if (!over) {
currentImage = normalImage;
currentColor = normalColor;
state = NORMAL;
mouseUp = false;
} else {
if (mouseDown) {
if ((state != MOUSE_DOWN) && (mouseUp)) {
if (mouseDownSound != null) {
mouseDownSound.play();
}
currentImage = mouseDownImage;
currentColor = mouseDownColor;
state = MOUSE_DOWN;
notifyListeners();
mouseUp = false;
}
return;
} else {
mouseUp = true;
if (state != MOUSE_OVER) {
if (mouseOverSound != null) {
mouseOverSound.play();
}
currentImage = mouseOverImage;
currentColor = mouseOverColor;
state = MOUSE_OVER;
}
}
}
mouseDown = false;
state = NORMAL;
}
/**
* Set the mouse over sound effect
*
* @param sound
* The mouse over sound effect
*/
public void setMouseOverSound(Sound sound) {
mouseOverSound = sound;
}
/**
* Set the mouse down sound effect
*
* @param sound
* The mouse down sound effect
*/
public void setMouseDownSound(Sound sound) {
mouseDownSound = sound;
}
/**
* @see org.newdawn.slick.util.InputAdapter#mouseMoved(int, int, int, int)
*/
public void mouseMoved(int oldx, int oldy, int newx, int newy) {
over = area.contains(newx, newy);
}
/**
* @see org.newdawn.slick.util.InputAdapter#mouseDragged(int, int, int, int)
*/
public void mouseDragged(int oldx, int oldy, int newx, int newy) {
mouseMoved(oldx, oldy, newx, newy);
}
/**
* @see org.newdawn.slick.util.InputAdapter#mousePressed(int, int, int)
*/
public void mousePressed(int button, int mx, int my) {
over = area.contains(mx, my);
if (button == 0) {
mouseDown = true;
}
}
/**
* @see org.newdawn.slick.util.InputAdapter#mouseReleased(int, int, int)
*/
public void mouseReleased(int button, int mx, int my) {
over = area.contains(mx, my);
if (button == 0) {
mouseDown = false;
}
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#getHeight()
*/
public int getHeight() {
return (int) (area.getMaxY() - area.getY());
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#getWidth()
*/
public int getWidth() {
return (int) (area.getMaxX() - area.getX());
}
/**
* Check if the mouse is over this area
*
* @return True if the mouse is over this area
*/
public boolean isMouseOver() {
return over;
}
/**
* Set the location of this area
*
* @param x The x coordinate of this area
* @param y The y coordiante of this area
*/
public void setLocation(int x, int y) {
setLocation((float) x,(float) y);
}
}

View File

@@ -0,0 +1,476 @@
package org.newdawn.slick.gui;
import org.lwjgl.Sys;
import org.newdawn.slick.Color;
import org.newdawn.slick.Font;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.geom.Rectangle;
/**
* A single text field supporting text entry
*
* @author kevin
*/
public class TextField extends AbstractComponent {
/** The key repeat interval */
private static final int INITIAL_KEY_REPEAT_INTERVAL = 400;
/** The key repeat interval */
private static final int KEY_REPEAT_INTERVAL = 50;
/** The width of the field */
private int width;
/** The height of the field */
private int height;
/** The location in the X coordinate */
protected int x;
/** The location in the Y coordinate */
protected int y;
/** The maximum number of characters allowed to be input */
private int maxCharacter = 10000;
/** The value stored in the text field */
private String value = "";
/** The font used to render text in the field */
private Font font;
/** The border color - null if no border */
private Color border = Color.white;
/** The text color */
private Color text = Color.white;
/** The background color - null if no background */
private Color background = new Color(0, 0, 0, 0.5f);
/** The current cursor position */
private int cursorPos;
/** True if the cursor should be visible */
private boolean visibleCursor = true;
/** The last key pressed */
private int lastKey = -1;
/** The last character pressed */
private char lastChar = 0;
/** The time since last key repeat */
private long repeatTimer;
/** The text before the paste in */
private String oldText;
/** The cursor position before the paste */
private int oldCursorPos;
/** True if events should be consumed by the field */
private boolean consume = true;
/**
* Create a new text field
*
* @param container
* The container rendering this field
* @param font
* The font to use in the text field
* @param x
* The x coordinate of the top left corner of the text field
* @param y
* The y coordinate of the top left corner of the text field
* @param width
* The width of the text field
* @param height
* The height of the text field
* @param listener
* The listener to add to the text field
*/
public TextField(GUIContext container, Font font, int x, int y, int width,
int height, ComponentListener listener) {
this(container,font,x,y,width,height);
addListener(listener);
}
/**
* Create a new text field
*
* @param container
* The container rendering this field
* @param font
* The font to use in the text field
* @param x
* The x coordinate of the top left corner of the text field
* @param y
* The y coordinate of the top left corner of the text field
* @param width
* The width of the text field
* @param height
* The height of the text field
*/
public TextField(GUIContext container, Font font, int x, int y, int width,
int height) {
super(container);
this.font = font;
setLocation(x, y);
this.width = width;
this.height = height;
}
/**
* Indicate if the input events should be consumed by this field
*
* @param consume True if events should be consumed by this field
*/
public void setConsumeEvents(boolean consume) {
this.consume = consume;
}
/**
* Deactivate the key input handling for this field
*/
public void deactivate() {
setFocus(false);
}
/**
* Moves the component.
*
* @param x
* X coordinate
* @param y
* Y coordinate
*/
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
}
/**
* Returns the position in the X coordinate
*
* @return x
*/
public int getX() {
return x;
}
/**
* Returns the position in the Y coordinate
*
* @return y
*/
public int getY() {
return y;
}
/**
* Get the width of the component
*
* @return The width of the component
*/
public int getWidth() {
return width;
}
/**
* Get the height of the component
*
* @return The height of the component
*/
public int getHeight() {
return height;
}
/**
* Set the background color. Set to null to disable the background
*
* @param color
* The color to use for the background
*/
public void setBackgroundColor(Color color) {
background = color;
}
/**
* Set the border color. Set to null to disable the border
*
* @param color
* The color to use for the border
*/
public void setBorderColor(Color color) {
border = color;
}
/**
* Set the text color.
*
* @param color
* The color to use for the text
*/
public void setTextColor(Color color) {
text = color;
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#render(org.newdawn.slick.gui.GUIContext,
* org.newdawn.slick.Graphics)
*/
public void render(GUIContext container, Graphics g) {
if (lastKey != -1) {
if (input.isKeyDown(lastKey)) {
if (repeatTimer < System.currentTimeMillis()) {
repeatTimer = System.currentTimeMillis() + KEY_REPEAT_INTERVAL;
keyPressed(lastKey, lastChar);
}
} else {
lastKey = -1;
}
}
Rectangle oldClip = g.getClip();
g.setWorldClip(x,y,width, height);
// Someone could have set a color for me to blend...
Color clr = g.getColor();
if (background != null) {
g.setColor(background.multiply(clr));
g.fillRect(x, y, width, height);
}
g.setColor(text.multiply(clr));
Font temp = g.getFont();
int cpos = font.getWidth(value.substring(0, cursorPos));
int tx = 0;
if (cpos > width) {
tx = width - cpos - font.getWidth("_");
}
g.translate(tx + 2, 0);
g.setFont(font);
g.drawString(value, x + 1, y + 1);
if (hasFocus() && visibleCursor) {
g.drawString("_", x + 1 + cpos + 2, y + 1);
}
g.translate(-tx - 2, 0);
if (border != null) {
g.setColor(border.multiply(clr));
g.drawRect(x, y, width, height);
}
g.setColor(clr);
g.setFont(temp);
g.clearWorldClip();
g.setClip(oldClip);
}
/**
* Get the value in the text field
*
* @return The value in the text field
*/
public String getText() {
return value;
}
/**
* Set the value to be displayed in the text field
*
* @param value
* The value to be displayed in the text field
*/
public void setText(String value) {
this.value = value;
if (cursorPos > value.length()) {
cursorPos = value.length();
}
}
/**
* Set the position of the cursor
*
* @param pos
* The new position of the cursor
*/
public void setCursorPos(int pos) {
cursorPos = pos;
if (cursorPos > value.length()) {
cursorPos = value.length();
}
}
/**
* Indicate whether the mouse cursor should be visible or not
*
* @param visibleCursor
* True if the mouse cursor should be visible
*/
public void setCursorVisible(boolean visibleCursor) {
this.visibleCursor = visibleCursor;
}
/**
* Set the length of the allowed input
*
* @param length
* The length of the allowed input
*/
public void setMaxLength(int length) {
maxCharacter = length;
if (value.length() > maxCharacter) {
value = value.substring(0, maxCharacter);
}
}
/**
* Do the paste into the field, overrideable for custom behaviour
*
* @param text The text to be pasted in
*/
protected void doPaste(String text) {
recordOldPosition();
for (int i=0;i<text.length();i++) {
keyPressed(-1, text.charAt(i));
}
}
/**
* Record the old position and content
*/
protected void recordOldPosition() {
oldText = getText();
oldCursorPos = cursorPos;
}
/**
* Do the undo of the paste, overrideable for custom behaviour
*
* @param oldCursorPos before the paste
* @param oldText The text before the last paste
*/
protected void doUndo(int oldCursorPos, String oldText) {
if (oldText != null) {
setText(oldText);
setCursorPos(oldCursorPos);
}
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#keyPressed(int, char)
*/
public void keyPressed(int key, char c) {
if (hasFocus()) {
if (key != -1)
{
if ((key == Input.KEY_V) &&
((input.isKeyDown(Input.KEY_LCONTROL)) || (input.isKeyDown(Input.KEY_RCONTROL)))) {
String text = Sys.getClipboard();
if (text != null) {
doPaste(text);
}
return;
}
if ((key == Input.KEY_Z) &&
((input.isKeyDown(Input.KEY_LCONTROL)) || (input.isKeyDown(Input.KEY_RCONTROL)))) {
if (oldText != null) {
doUndo(oldCursorPos, oldText);
}
return;
}
// alt and control keys don't come through here
if (input.isKeyDown(Input.KEY_LCONTROL) || input.isKeyDown(Input.KEY_RCONTROL)) {
return;
}
if (input.isKeyDown(Input.KEY_LALT) || input.isKeyDown(Input.KEY_RALT)) {
return;
}
}
if (lastKey != key) {
lastKey = key;
repeatTimer = System.currentTimeMillis() + INITIAL_KEY_REPEAT_INTERVAL;
} else {
repeatTimer = System.currentTimeMillis() + KEY_REPEAT_INTERVAL;
}
lastChar = c;
if (key == Input.KEY_LEFT) {
if (cursorPos > 0) {
cursorPos--;
}
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
} else if (key == Input.KEY_RIGHT) {
if (cursorPos < value.length()) {
cursorPos++;
}
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
} else if (key == Input.KEY_BACK) {
if ((cursorPos > 0) && (value.length() > 0)) {
if (cursorPos < value.length()) {
value = value.substring(0, cursorPos - 1)
+ value.substring(cursorPos);
} else {
value = value.substring(0, cursorPos - 1);
}
cursorPos--;
}
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
} else if (key == Input.KEY_DELETE) {
if (value.length() > cursorPos) {
value = value.substring(0,cursorPos) + value.substring(cursorPos+1);
}
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
} else if ((c < 127) && (c > 31) && (value.length() < maxCharacter)) {
if (cursorPos < value.length()) {
value = value.substring(0, cursorPos) + c
+ value.substring(cursorPos);
} else {
value = value.substring(0, cursorPos) + c;
}
cursorPos++;
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
} else if (key == Input.KEY_RETURN) {
notifyListeners();
// Nobody more will be notified
if (consume) {
container.getInput().consumeEvent();
}
}
}
}
/**
* @see org.newdawn.slick.gui.AbstractComponent#setFocus(boolean)
*/
public void setFocus(boolean focus) {
lastKey = -1;
super.setFocus(focus);
}
}

View File

@@ -0,0 +1,3 @@
<BODY>
Some extremely simple GUI elements which should be used where a game does not require a full GUI
</BODY>

View File

@@ -0,0 +1,94 @@
package org.newdawn.slick.imageout;
import java.awt.Point;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import javax.imageio.ImageIO;
import org.newdawn.slick.Color;
import org.newdawn.slick.Image;
/**
* A utility to write a Slick image out using ImageIO
*
* @author Jon
*/
public class ImageIOWriter implements ImageWriter {
/**
* @see org.newdawn.slick.imageout.ImageWriter#saveImage(org.newdawn.slick.Image,
* java.lang.String, java.io.OutputStream, boolean)
*/
public void saveImage(Image image, String format, OutputStream output, boolean hasAlpha)
throws IOException {
// conver the image into a byte buffer by reading each pixel in turn
int len = 4 * image.getWidth() * image.getHeight();
if (!hasAlpha) {
len = 3 * image.getWidth() * image.getHeight();
}
ByteBuffer out = ByteBuffer.allocate(len);
Color c;
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
c = image.getColor(x, y);
out.put((byte) (c.r * 255.0f));
out.put((byte) (c.g * 255.0f));
out.put((byte) (c.b * 255.0f));
if (hasAlpha) {
out.put((byte) (c.a * 255.0f));
}
}
}
// create a raster of the correct format and fill it with our buffer
DataBufferByte dataBuffer = new DataBufferByte(out.array(), len);
PixelInterleavedSampleModel sampleModel;
ColorModel cm;
if (hasAlpha) {
int[] offsets = { 0, 1, 2, 3 };
sampleModel = new PixelInterleavedSampleModel(
DataBuffer.TYPE_BYTE, image.getWidth(), image.getHeight(), 4,
4 * image.getWidth(), offsets);
cm = new ComponentColorModel(ColorSpace
.getInstance(ColorSpace.CS_sRGB), new int[] { 8, 8, 8, 8 },
true, false, ComponentColorModel.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
} else {
int[] offsets = { 0, 1, 2};
sampleModel = new PixelInterleavedSampleModel(
DataBuffer.TYPE_BYTE, image.getWidth(), image.getHeight(), 3,
3 * image.getWidth(), offsets);
cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
new int[] {8,8,8,0},
false,
false,
ComponentColorModel.OPAQUE,
DataBuffer.TYPE_BYTE);
}
WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer, new Point(0, 0));
// finally create the buffered image based on the data from the texture
// and spit it through to ImageIO
BufferedImage img = new BufferedImage(cm, raster, false, null);
ImageIO.write(img, format, output);
}
}

View File

@@ -0,0 +1,131 @@
package org.newdawn.slick.imageout;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
/**
* A static hook to access all the Image output utilities. The list of format strings
* provided is not the limit of capability. These are provided for utility, use @see {@link #getSupportedFormats()}
* for a full list of supported formats.
*
* @author kevin
*/
public class ImageOut {
/** The default setting for writing out the alpha channel */
private static final boolean DEFAULT_ALPHA_WRITE = false;
/** The format string for TGA */
public static String TGA = "tga";
/** The format string for PNG */
public static String PNG = "png";
/** The format string for JPG */
public static String JPG = "jpg";
/**
* Get a list of supported formats
*
* @see ImageWriterFactory#getSupportedFormats()
* @return The list of supported format strings
*/
public static String[] getSupportedFormats() {
return ImageWriterFactory.getSupportedFormats();
}
/**
* Write an image out to a specified output stream
*
* @param image The image to write out to
* @param format The format to write the image out in
* @param out The output stream to which the image should be written
* @throws SlickException Indicates a failure to write the image in the specified format
*/
public static void write(Image image, String format, OutputStream out) throws SlickException {
write(image, format, out, DEFAULT_ALPHA_WRITE);
}
/**
* Write an image out to a specified output stream
*
* @param image The image to write out to
* @param format The format to write the image out in
* @param out The output stream to which the image should be written
* @param writeAlpha True if we should write the alpha channel out (some formats don't support this, like JPG)
* @throws SlickException Indicates a failure to write the image in the specified format
*/
public static void write(Image image, String format, OutputStream out, boolean writeAlpha) throws SlickException {
try {
ImageWriter writer = ImageWriterFactory.getWriterForFormat(format);
writer.saveImage(image, format, out, writeAlpha);
} catch (IOException e) {
throw new SlickException("Unable to write out the image in format: "+format, e);
}
}
/**
* Write an image out to a file on the local file system. The format of the output
* is determined based on the file name extension
*
* @param image The image to be written out
* @param dest The destination path to write to
* @throws SlickException Indicates a failure to write the image in the determined format
*/
public static void write(Image image, String dest) throws SlickException {
write(image, dest, DEFAULT_ALPHA_WRITE);
}
/**
* Write an image out to a file on the local file system. The format of the output
* is determined based on the file name extension
*
* @param image The image to be written out
* @param dest The destination path to write to
* @param writeAlpha True if we should write the alpha channel out (some formats don't support this, like JPG)
* @throws SlickException Indicates a failure to write the image in the determined format
*/
public static void write(Image image, String dest, boolean writeAlpha) throws SlickException {
try {
int ext = dest.lastIndexOf('.');
if (ext < 0) {
throw new SlickException("Unable to determine format from: "+dest);
}
String format = dest.substring(ext+1);
write(image, format, new FileOutputStream(dest), writeAlpha);
} catch (IOException e) {
throw new SlickException("Unable to write to the destination: "+dest, e);
}
}
/**
* Write an image out to a file on the local file system.
*
* @param image The image to be written out
* @param format The format to write the image out in
* @param dest The destination path to write to
* @throws SlickException Indicates a failure to write the image in the determined format
*/
public static void write(Image image, String format, String dest) throws SlickException {
write(image, format, dest, DEFAULT_ALPHA_WRITE);
}
/**
* Write an image out to a file on the local file system.
*
* @param image The image to be written out
* @param format The format to write the image out in
* @param dest The destination path to write to
* @param writeAlpha True if we should write the alpha channel out (some formats don't support this, like JPG)
* @throws SlickException Indicates a failure to write the image in the determined format
*/
public static void write(Image image, String format, String dest, boolean writeAlpha) throws SlickException {
try {
write(image, format, new FileOutputStream(dest), writeAlpha);
} catch (IOException e) {
throw new SlickException("Unable to write to the destination: "+dest, e);
}
}
}

View File

@@ -0,0 +1,25 @@
package org.newdawn.slick.imageout;
import java.io.IOException;
import java.io.OutputStream;
import org.newdawn.slick.Image;
/**
* The description of any class that can produce data to an output stream reprsenting
* some image in memory.
*
* @author Jon
*/
public interface ImageWriter {
/**
* Save an Image to an given location
*
* @param image The image to be written
* @param format The format that this writer is expected to be produced in
* @param out The output stream to which the image data should be written
* @param writeAlpha True if we should write alpha information to the file
* @throws IOException Indicates a failure to write out the image to the specified location
*/
void saveImage(Image image, String format, OutputStream out, boolean writeAlpha) throws IOException;
}

View File

@@ -0,0 +1,76 @@
package org.newdawn.slick.imageout;
import java.util.HashMap;
import javax.imageio.ImageIO;
import org.newdawn.slick.SlickException;
/**
* A factory to produce image writers based on format names
*
* @author kevin
*/
public class ImageWriterFactory {
/** The map from format names to image writer instances */
private static HashMap writers = new HashMap();
// Initialise the list of writers based on the classes we know about
static {
String[] formats = ImageIO.getWriterFormatNames();
ImageIOWriter writer = new ImageIOWriter();
for (int i=0;i<formats.length;i++) {
registerWriter(formats[i], writer);
}
TGAWriter tga = new TGAWriter();
registerWriter("tga", tga);
}
/**
* Register an image writer with the factory. This will allow users
* to use it to write out the explicit format
*
* @param format The format (usually extension) of the files that will be written out
* @param writer The writer to use for the given format
*/
public static void registerWriter(String format, ImageWriter writer) {
writers.put(format, writer);
}
/**
* Get the list of support format strings for this factory
*
* @return The list of support format strings for this factory
*/
public static String[] getSupportedFormats() {
return (String[]) writers.keySet().toArray(new String[0]);
}
/**
* Get a Slick image writer for the given format
*
* @param format The format of the image to write
* @return The image write to use to produce these images
* @throws SlickException
*/
public static ImageWriter getWriterForFormat(String format) throws SlickException
{
ImageWriter writer = (ImageWriter) writers.get(format);
if (writer != null) {
return writer;
}
writer = (ImageWriter) writers.get(format.toLowerCase());
if (writer != null) {
return writer;
}
writer = (ImageWriter) writers.get(format.toUpperCase());
if (writer != null) {
return writer;
}
throw new SlickException("No image writer available for: "+format);
}
}

Some files were not shown because too many files have changed in this diff Show More