Base of spritesheet system

v5stable
Ondřej Hruška 10 years ago
parent d18ff99fa8
commit 7777a61e32
  1. 9
      src/mightypork/gamecore/render/Render.java
  2. 18
      src/mightypork/gamecore/render/textures/DeferredTexture.java
  3. 26
      src/mightypork/gamecore/render/textures/FilteredTexture.java
  4. 53
      src/mightypork/gamecore/render/textures/GLTexture.java
  5. 92
      src/mightypork/gamecore/render/textures/QuadGrid.java
  6. 115
      src/mightypork/gamecore/render/textures/TextureBank.java
  7. 32
      src/mightypork/gamecore/render/textures/TxQuad.java
  8. 90
      src/mightypork/gamecore/render/textures/TxSheet.java
  9. 2
      src/mightypork/rogue/App.java
  10. 28
      src/mightypork/rogue/Res.java
  11. 9
      src/mightypork/rogue/screens/CrossfadeOverlay.java
  12. 35
      src/mightypork/test/TestRandomSeed.java
  13. 22
      src/mightypork/util/error/KeyAlreadyExistsException.java

@ -6,6 +6,7 @@ import static org.lwjgl.opengl.GL11.*;
import java.io.IOException;
import mightypork.gamecore.render.textures.FilterMode;
import mightypork.gamecore.render.textures.GLTexture;
import mightypork.gamecore.render.textures.TxQuad;
import mightypork.util.constraints.rect.Rect;
import mightypork.util.constraints.rect.caching.RectDigest;
@ -327,7 +328,7 @@ public class Render {
* @param texture the texture
* @throws RuntimeException if not loaded yet
*/
private static void bindTexture(Texture texture) throws RuntimeException
private static void bindTexture(GLTexture texture) throws RuntimeException
{
texture.bind();
}
@ -486,7 +487,7 @@ public class Render {
* @param texture texture instance
* @param tint color tint
*/
public static void quadTextured(Rect quad, Rect uvs, Texture texture, Color tint)
public static void quadTextured(Rect quad, Rect uvs, GLTexture texture, Color tint)
{
bindTexture(texture);
setColor(tint);
@ -502,7 +503,7 @@ public class Render {
* @param uvs texture coords rectangle (px)
* @param texture texture instance
*/
public static void quadTextured(Rect quad, Rect uvs, Texture texture)
public static void quadTextured(Rect quad, Rect uvs, GLTexture texture)
{
quadTextured(quad, uvs, texture, Color.WHITE);
}
@ -514,7 +515,7 @@ public class Render {
* @param quad rectangle (px)
* @param texture texture instance
*/
public static void quadTextured(Rect quad, Texture texture)
public static void quadTextured(Rect quad, GLTexture texture)
{
quadTextured(quad, Rect.ONE, texture, Color.WHITE);
}

@ -8,7 +8,6 @@ import mightypork.util.constraints.rect.Rect;
import mightypork.util.logging.LogAlias;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.opengl.Texture;
/**
@ -18,9 +17,9 @@ import org.newdawn.slick.opengl.Texture;
*/
@MustLoadInMainThread
@LogAlias(name = "Texture")
public class DeferredTexture extends DeferredResource implements FilteredTexture {
public class DeferredTexture extends DeferredResource implements GLTexture {
private Texture backingTexture;
private org.newdawn.slick.opengl.Texture backingTexture;
private FilterMode filter = FilterMode.NEAREST;
private WrapMode wrap = WrapMode.CLAMP;
@ -36,12 +35,13 @@ public class DeferredTexture extends DeferredResource implements FilteredTexture
/**
* Get a quad from this texture of given position/size
*
* @param rect quad rect
* @param uvs quad rect
* @return the quad
*/
public TxQuad getQuad(Rect rect)
@Override
public TxQuad makeQuad(Rect uvs)
{
return new TxQuad(this, rect);
return new TxQuad(this, uvs);
}
@ -64,6 +64,7 @@ public class DeferredTexture extends DeferredResource implements FilteredTexture
/**
* Bind without adjusting parameters
*/
@Override
public void bindRaw()
{
if (!ensureLoaded()) return;
@ -211,4 +212,9 @@ public class DeferredTexture extends DeferredResource implements FilteredTexture
this.wrap = wrapping;
}
@Override
public QuadGrid grid(int x, int y)
{
return new QuadGrid(this, x, y);
}
}

@ -1,26 +0,0 @@
package mightypork.gamecore.render.textures;
import org.newdawn.slick.opengl.Texture;
/**
* Texture with filter and wrap mode
*
* @author MightyPork
*/
public interface FilteredTexture extends Texture {
/**
* Set filter for scaling
*
* @param filter filter
*/
void setFilter(FilterMode filter);
/**
* @param wrapping wrap mode
*/
void setWrap(WrapMode wrapping);
}

@ -0,0 +1,53 @@
package mightypork.gamecore.render.textures;
import org.newdawn.slick.opengl.Texture;
import mightypork.util.constraints.rect.Rect;
/**
* Texture with filter and wrap mode
*
* @author MightyPork
*/
public interface GLTexture extends Texture {
/**
* Set filter for scaling
*
* @param filter filter
*/
void setFilter(FilterMode filter);
/**
* @param wrapping wrap mode
*/
void setWrap(WrapMode wrapping);
/**
* Get a quad from this texture of given position/size
*
* @param uvs quad rect
* @return the quad
*/
TxQuad makeQuad(Rect uvs);
/**
* Bind without adjusting parameters
*/
void bindRaw();
/**
* Get a grid for given number of tiles
*
* @param x horizontal tile count
* @param y vertical tile count
* @return grid
*/
QuadGrid grid(int x, int y);
}

@ -0,0 +1,92 @@
package mightypork.gamecore.render.textures;
import mightypork.util.constraints.rect.Rect;
/**
* {@link TxQuad} and {@link TxSheet} building utility
*
* @author MightyPork
*/
public class QuadGrid {
private final GLTexture tx;
private int txHeight;
private int txWidth;
private double tileW;
private double tileH;
public QuadGrid(GLTexture tx, int tilesX, int tilesY) {
this.tx = tx;
this.txWidth = tilesX;
this.txHeight = tilesY;
this.tileW = 1D / tilesX;
this.tileH = 1D / tilesY;
}
/**
* Make square quad at given coords (one grid cell)
*
* @param x x coordinate (cells)
* @param y y coordinate (cells)
* @return the quad
*/
public TxQuad makeQuad(int x, int y)
{
if (x < 0 || x >= txWidth || y < 0 || y >= txHeight) {
throw new IndexOutOfBoundsException("Requested invalid txquad coordinates.");
}
return makeQuad(x, y, 1, 1);
}
/**
* Make square quad at given coords, with arbitrary size. Coordinates are
* multiples of cell size.
*
* @param x x coordinate (cells)
* @param y y coordinate (cells)
* @param width width (cells)
* @param height height (cells)
* @return the quad
*/
public TxQuad makeQuad(double x, double y, double width, double height)
{
if (x < 0 || x >= txWidth || y < 0 || y >= txHeight) {
throw new IndexOutOfBoundsException("Requested invalid txquad coordinates.");
}
if (x + width > txWidth || y + height > txHeight) {
throw new IndexOutOfBoundsException("Requested invalid txquad size (would go beyond texture size).");
}
return tx.makeQuad(Rect.make(tileW * x, tileH * y, tileW * width, tileH * height));
}
/**
* Make a sheet of given cells.
*
* @param x x origin coordinate (cells)
* @param y y origin coordinate (cells)
* @param width width (cells)
* @param height height (cells)
* @return the sheet
*/
public TxSheet makeSheet(int x, int y, int width, int height)
{
if (x < 0 || x >= txWidth || y < 0 || y >= txHeight) {
throw new IndexOutOfBoundsException("Requested invalid txquad coordinates.");
}
if (x + width > txWidth || y + height > txHeight) {
throw new IndexOutOfBoundsException("Requested invalid txsheet size (would go beyond texture size).");
}
return makeQuad(x, y).makeSheet(width, height);
}
}

@ -2,13 +2,13 @@ package mightypork.gamecore.render.textures;
import java.util.HashMap;
import java.util.Map;
import mightypork.gamecore.control.AppAccess;
import mightypork.gamecore.control.AppAdapter;
import mightypork.gamecore.control.events.ResourceLoadRequest;
import mightypork.util.constraints.rect.Rect;
import org.newdawn.slick.opengl.Texture;
import mightypork.util.error.KeyAlreadyExistsException;
/**
@ -25,80 +25,113 @@ public class TextureBank extends AppAdapter {
super(app);
}
private final HashMap<String, DeferredTexture> textures = new HashMap<>();
private final Map<String, GLTexture> textures = new HashMap<>();
private final HashMap<String, TxQuad> quads = new HashMap<>();
private final Map<String, TxQuad> quads = new HashMap<>();
private DeferredTexture lastTx;
private final Map<String, TxSheet> sheets = new HashMap<>();
/**
* Load a {@link Texture}
* Load a texture from resource. A full-sized quad with the same key will be
* automatically added.
*
* @param key texture key
* @param texture texture to load
* @param resourcePath texture resource path
* @param filter filter
* @param wrap texture wrapping
* @return the loaded texture.
*/
public GLTexture loadTexture(String key, String resourcePath, FilterMode filter, WrapMode wrap)
{
if (textures.containsKey(key)) throw new KeyAlreadyExistsException();
final DeferredTexture texture = new DeferredTexture(resourcePath);
texture.setFilter(filter);
texture.setWrap(wrap);
loadTexture(key, texture);
return texture;
}
/**
* Add an already initialized deferred texture to textures registry
*
* @param key
* @param texture
*/
public void loadTexture(String key, DeferredTexture texture)
{
getEventBus().send(new ResourceLoadRequest(texture));
textures.put(key, texture);
lastTx = texture;
makeQuad(key, Rect.ONE);
addQuad(key, texture.makeQuad(Rect.ONE));
}
/**
* Load a {@link Texture} from resource
* Make a quad from texture, and add it to quads registry.
*
* @param key texture key
* @param resourcePath texture resource path
* @param filter filter
* @param wrap texture wrapping
* @param quadKey key
* @param texture source texture
* @param uvs rect
* @return the created quad
*/
public void loadTexture(String key, String resourcePath, FilterMode filter, WrapMode wrap)
public TxQuad makeQuad(String quadKey, GLTexture texture, Rect uvs)
{
final DeferredTexture texture = new DeferredTexture(resourcePath);
texture.setFilter(filter);
texture.setWrap(wrap);
TxQuad quad = texture.makeQuad(uvs);
addQuad(quadKey, quad);
return quad;
}
/**
* Add already created quad to the quad registry
*
* @param quadKey key
* @param quad quad to add
*/
public void addQuad(String quadKey, TxQuad quad)
{
if (quads.containsKey(quadKey)) throw new KeyAlreadyExistsException();
loadTexture(key, texture);
quads.put(quadKey, quad);
}
/**
* Create a {@link TxQuad} in a texture
* make a sprite sheet originating at given quad, spanning right and down.
*
* @param quadKey quad key
* @param textureKey texture key
* @param quad quad rectangle (absolute pixel coordinates) *
* @param sheetKey key
* @param origin starting quad
* @param width sheet width (multiplies of origin width)
* @param height sheet height (multiplies of origin height)
* @return the created sheet
*/
public void makeQuad(String quadKey, String textureKey, Rect quad)
public TxSheet makeSheet(String sheetKey, TxQuad origin, int width, int height)
{
final DeferredTexture tx = textures.get(textureKey);
if (tx == null) throw new RuntimeException("Texture with key " + textureKey + " not defined!");
TxSheet sheet = origin.makeSheet(width, height);
final TxQuad txquad = tx.getQuad(quad);
addSheet(sheetKey, sheet);
quads.put(quadKey, txquad);
return sheet;
}
/**
* Create a {@link TxQuad} in the last loaded texture
* Add an already created sheet
*
* @param quadKey quad key
* @param quad quad rectangle (0-1)
* @param sheetKey key
* @param sheet sheet to add
*/
public void makeQuad(String quadKey, Rect quad)
public void addSheet(String sheetKey, TxSheet sheet)
{
final DeferredTexture tx = lastTx;
if (tx == null) throw new RuntimeException("There's no texture loaded yet, can't define quads!");
final TxQuad txquad = tx.getQuad(quad);
if (sheets.containsKey(sheetKey)) throw new KeyAlreadyExistsException();
quads.put(quadKey, txquad);
sheets.put(sheetKey, sheet);
}
@ -108,7 +141,7 @@ public class TextureBank extends AppAdapter {
* @param key quad key
* @return the quad
*/
public TxQuad getTxQuad(String key)
public TxQuad getQuad(String key)
{
final TxQuad q = quads.get(key);
@ -119,14 +152,14 @@ public class TextureBank extends AppAdapter {
/**
* Get a loaded {@link Texture}
* Get a loaded {@link GLTexture}
*
* @param key texture key
* @return the texture
*/
public Texture getTexture(String key)
public GLTexture getTexture(String key)
{
final Texture t = textures.get(key);
final GLTexture t = textures.get(key);
if (t == null) throw new RuntimeException("There's no texture called " + key + "!");

@ -2,8 +2,7 @@ package mightypork.gamecore.render.textures;
import mightypork.util.constraints.rect.Rect;
import org.newdawn.slick.opengl.Texture;
import mightypork.util.constraints.rect.RectConst;
/**
@ -14,9 +13,9 @@ import org.newdawn.slick.opengl.Texture;
public class TxQuad {
/** The texture */
public final Texture tx;
public final GLTexture tx;
/** Coords in texture (0-1) */
public final Rect uvs;
public final RectConst uvs;
/**
@ -29,7 +28,7 @@ public class TxQuad {
* @param heightPx area height (0-1)
* @return new TxQuad
*/
public static TxQuad fromSizePx(Texture tx, double xPx, double yPx, double widthPx, double heightPx)
public static TxQuad fromSizePx(GLTexture tx, double xPx, double yPx, double widthPx, double heightPx)
{
final double w = tx.getImageWidth();
final double h = tx.getImageHeight();
@ -48,7 +47,7 @@ public class TxQuad {
* @param height area height (0-1)
* @return new TxQuad
*/
public static TxQuad fromSize(Texture tx, double x1, double y1, double width, double height)
public static TxQuad fromSize(GLTexture tx, double x1, double y1, double width, double height)
{
return new TxQuad(tx, x1, y1, x1 + width, y1 + height);
}
@ -63,18 +62,18 @@ public class TxQuad {
* @param x2 right bottom X (0-1)
* @param y2 right bottom Y (0-1)
*/
public TxQuad(Texture tx, double x1, double y1, double x2, double y2) {
public TxQuad(GLTexture tx, double x1, double y1, double x2, double y2) {
this(tx, Rect.make(x1, y1, x2, y2));
}
/**
* @param tx Texture
* @param uvs Rect of texture UVs (0-1); will be stored as is.
* @param uvs Rect of texture UVs (0-1); will be frozen.
*/
public TxQuad(Texture tx, Rect uvs) {
public TxQuad(GLTexture tx, Rect uvs) {
this.tx = tx;
this.uvs = uvs;
this.uvs = uvs.freeze();
}
@ -98,4 +97,17 @@ public class TxQuad {
{
return new TxQuad(this);
}
/**
* Make a sheet starting with this quad, spannign to right and down.
*
* @param width sheet width
* @param height sheet height
* @return sheet
*/
public TxSheet makeSheet(int width, int height)
{
return new TxSheet(this, width, height);
}
}

@ -0,0 +1,90 @@
package mightypork.gamecore.render.textures;
import java.util.Random;
/**
* Basic sprite sheet
*
* @author MightyPork
*/
public class TxSheet {
private final TxQuad original;
private final TxQuad[] sprites;
private final int width;
private final Random rand = new Random();
private final Random randForSeed = new Random();
private int count;
public TxSheet(TxQuad tx, int width, int height) {
this.original = tx;
this.width = width;
this.count = width * height;
this.sprites = new TxQuad[count];
}
/**
* @return number of quads
*/
public int getQuadCount()
{
return count;
}
/**
* Get quad of index
*
* @param index index
* @return the quad
*/
public TxQuad getQuad(int index)
{
if (index < 0 || index > count) {
throw new IndexOutOfBoundsException("Index out of bounds: " + index + ", allowed: 0.." + count);
}
// lazy - init only when needed
if (sprites[index] == null) {
final int x = index % width;
final int y = index / width;
final double origW = original.uvs.width().value();
final double origH = original.uvs.height().value();
sprites[index] = new TxQuad(original.tx, original.uvs.move(x * origW, y * origH));
}
return sprites[index];
}
/**
* Get entirely random TxQuad from this sheet
*
* @return the picked quad
*/
public TxQuad getRandomQuad()
{
return getQuad(rand.nextInt(count));
}
/**
* Get random TxQuad from this sheet
*
* @param seed random number generator seed
* @return the picked quad
*/
public TxQuad getRandomQuad(long seed)
{
randForSeed.setSeed(seed);
return getQuad(randForSeed.nextInt(count));
}
}

@ -132,7 +132,7 @@ public final class App extends BaseApp {
// this will work only with reusable events (such as requests)
bindToKey(new ActionRequest(RequestType.FULLSCREEN), Keys.F11);
bindToKey(new ActionRequest(RequestType.SCREENSHOT), Keys.F2);
bindToKey(new ActionRequest(RequestType.SHUTDOWN), Keys.L_CONTROL, Keys.Q);
bindToKey(new CrossfadeRequest(null), Keys.L_CONTROL, Keys.Q);
bindToKey(new CrossfadeRequest("main_menu"), Keys.L_CONTROL, Keys.M);
}

@ -10,11 +10,7 @@ import mightypork.gamecore.render.fonts.FontBank;
import mightypork.gamecore.render.fonts.GLFont;
import mightypork.gamecore.render.fonts.Glyphs;
import mightypork.gamecore.render.fonts.impl.DeferredFont;
import mightypork.gamecore.render.textures.DeferredTexture;
import mightypork.gamecore.render.textures.FilterMode;
import mightypork.gamecore.render.textures.TextureBank;
import mightypork.gamecore.render.textures.TxQuad;
import mightypork.gamecore.render.textures.WrapMode;
import mightypork.gamecore.render.textures.*;
import mightypork.util.constraints.rect.Rect;
import org.newdawn.slick.opengl.Texture;
@ -83,22 +79,20 @@ public final class Res {
texture.setFilter(FilterMode.NEAREST);
texture.setWrap(WrapMode.CLAMP);
textures.loadTexture("gui1", texture);
final double p16 = 0.25D;
final double p8 = 0.125D;
QuadGrid guiGrid = texture.grid(4, 4);
//@formatter:off
textures.makeQuad("item_frame", Rect.make(0, 0, p16, p16));
textures.makeQuad("sword", Rect.make(p16, 0, p16, p16));
textures.makeQuad("meat", Rect.make(p16*2, 0, p16, p16));
textures.addQuad("item_frame", guiGrid.makeQuad(0, 0));
textures.addQuad("sword", guiGrid.makeQuad(1, 0));
textures.addQuad("meat", guiGrid.makeQuad(2, 0));
textures.makeQuad("heart_on", Rect.make(0, p16, p8, p8));
textures.makeQuad("heart_off", Rect.make(p8, p16, p8, p8));
textures.addQuad("heart_on", guiGrid.makeQuad(.0, 1, .5, .5));
textures.addQuad("heart_off", guiGrid.makeQuad(.5, 1, .5, .5));
textures.makeQuad("xp_on", Rect.make(0, p16+p8, p8, p8));
textures.makeQuad("xp_off", Rect.make(p8, p16+p8, p8, p8));
textures.addQuad("xp_on", guiGrid.makeQuad(0, 1.5, .5, .5));
textures.addQuad("xp_off", guiGrid.makeQuad(.5, 1.5, .5, .5));
textures.makeQuad("panel", Rect.make(0, p16*4-p8/2, p16*4, p8/2));
textures.addQuad("panel", guiGrid.makeQuad(0, 3.75, 4, .25));
//@formatter:off
}
@ -113,7 +107,7 @@ public final class Res {
public static TxQuad getTxQuad(String key)
{
return textures.getTxQuad(key);
return textures.getQuad(key);
}

@ -5,6 +5,8 @@ import mightypork.gamecore.control.AppAccess;
import mightypork.gamecore.control.events.ScreenRequestEvent;
import mightypork.gamecore.gui.components.painters.QuadPainter;
import mightypork.gamecore.gui.screens.Overlay;
import mightypork.rogue.events.ActionRequest;
import mightypork.rogue.events.ActionRequest.RequestType;
import mightypork.util.constraints.num.mutable.NumAnimated;
import mightypork.util.control.timing.TimedTask;
import mightypork.util.math.Easing;
@ -26,8 +28,11 @@ public class CrossfadeOverlay extends Overlay implements CrossfadeRequest.Listen
@Override
public void run()
{
if (requestedScreenName == null) shutdown();
getEventBus().send(new ScreenRequestEvent(requestedScreenName));
if (requestedScreenName == null) {
getEventBus().send(new ActionRequest(RequestType.SHUTDOWN));
} else {
getEventBus().send(new ScreenRequestEvent(requestedScreenName));
}
}
};

@ -0,0 +1,35 @@
package mightypork.test;
import java.util.Random;
public class TestRandomSeed {
public static void main(String[] args)
{
{
Random rand = new Random();
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
rand.setSeed(1000);
}
System.out.println((System.currentTimeMillis() - begin) / 1000D);
}
{
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
Random rand = new Random();
rand.setSeed(1000);
}
System.out.println((System.currentTimeMillis() - begin) / 1000D);
}
}
}

@ -0,0 +1,22 @@
package mightypork.util.error;
public class KeyAlreadyExistsException extends RuntimeException {
public KeyAlreadyExistsException() {
super();
}
public KeyAlreadyExistsException(String message, Throwable cause) {
super(message, cause);
}
public KeyAlreadyExistsException(String message) {
super(message);
}
public KeyAlreadyExistsException(Throwable cause) {
super(cause);
}
}
Loading…
Cancel
Save