parent
7024fb4402
commit
ec64ad21fc
@ -0,0 +1,34 @@ |
||||
package mightypork.gamecore.backend; |
||||
|
||||
|
||||
import mightypork.gamecore.render.RenderModule; |
||||
import mightypork.utils.eventbus.BusAccess; |
||||
import mightypork.utils.eventbus.clients.RootBusNode; |
||||
|
||||
|
||||
/** |
||||
* Application backend interface (set of core modules) |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public abstract class Backend extends RootBusNode { |
||||
|
||||
public Backend(BusAccess busAccess) { |
||||
super(busAccess); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Initialize backend modules, add them to event bus.<br> |
||||
* Event bus must already be available in the BusAccess. |
||||
*/ |
||||
public abstract void initialize(); |
||||
|
||||
|
||||
/** |
||||
* Get graphics module (renderer) |
||||
* |
||||
* @return graphics module |
||||
*/ |
||||
public abstract RenderModule getRenderer(); |
||||
} |
@ -0,0 +1,32 @@ |
||||
package mightypork.gamecore.backend; |
||||
|
||||
|
||||
import mightypork.utils.annotations.DefaultImpl; |
||||
import mightypork.utils.eventbus.BusAccess; |
||||
import mightypork.utils.eventbus.clients.BusNode; |
||||
import mightypork.utils.interfaces.Destroyable; |
||||
|
||||
|
||||
/** |
||||
* Abstract application backend module. |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public abstract class BackendModule extends BusNode implements Destroyable { |
||||
|
||||
/** |
||||
* Create a module with bus access |
||||
* |
||||
* @param busAccess |
||||
*/ |
||||
public BackendModule(BusAccess busAccess) { |
||||
super(busAccess); |
||||
} |
||||
|
||||
@Override |
||||
@DefaultImpl |
||||
public void destroy() |
||||
{ |
||||
} |
||||
|
||||
} |
@ -0,0 +1,85 @@ |
||||
package mightypork.gamecore.backend.lwjgl; |
||||
|
||||
|
||||
import java.awt.image.BufferedImage; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
import javax.imageio.ImageIO; |
||||
|
||||
import mightypork.gamecore.render.Screenshot; |
||||
|
||||
|
||||
/** |
||||
* Screenshot object, can be used to extract image or write to file.<br> |
||||
* Screenshot, once taken, can be safely processed in separate thread.<br> |
||||
* Based on {@link BufferedImage} and {@link ImageIO}. |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class AwtScreenshot implements Screenshot { |
||||
|
||||
private final int width; |
||||
private final int height; |
||||
private final int bpp; |
||||
private final ByteBuffer bytes; |
||||
private BufferedImage image; |
||||
|
||||
|
||||
/** |
||||
* @param width image width |
||||
* @param height image height |
||||
* @param bpp bits per pixel (typically 4) |
||||
* @param buffer |
||||
*/ |
||||
public AwtScreenshot(int width, int height, int bpp, ByteBuffer buffer) |
||||
{ |
||||
this.width = width; |
||||
this.height = height; |
||||
this.bpp = bpp; |
||||
this.bytes = buffer; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Extract to an image.<br> |
||||
* Subsequent calls will use a cached value. |
||||
* |
||||
* @return image |
||||
*/ |
||||
public BufferedImage getImage() |
||||
{ |
||||
if (image != null) return image; |
||||
|
||||
image = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB); |
||||
|
||||
// convert to a buffered image
|
||||
for (int x = 0; x < this.width; x++) { |
||||
for (int y = 0; y < this.height; y++) { |
||||
final int i = (x + (this.width * y)) * this.bpp; |
||||
final int r = this.bytes.get(i) & 0xFF; |
||||
final int g = this.bytes.get(i + 1) & 0xFF; |
||||
final int b = this.bytes.get(i + 2) & 0xFF; |
||||
image.setRGB(x, this.height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b); |
||||
} |
||||
} |
||||
|
||||
return image; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Save to a file.<br> |
||||
* Cached value is used if any. |
||||
* |
||||
* @param file target file |
||||
* @throws IOException on error writing to file |
||||
*/ |
||||
@Override |
||||
public void save(File file) throws IOException |
||||
{ |
||||
file.getParentFile().mkdirs(); |
||||
ImageIO.write(getImage(), "PNG", file); |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
package mightypork.gamecore.backend.lwjgl; |
||||
|
||||
|
||||
import mightypork.gamecore.backend.Backend; |
||||
import mightypork.gamecore.render.RenderModule; |
||||
import mightypork.utils.eventbus.BusAccess; |
||||
|
||||
|
||||
/** |
||||
* Game backend using LWJGL |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public class LwjglBackend extends Backend { |
||||
|
||||
public LwjglBackend(BusAccess busAccess) { |
||||
super(busAccess); |
||||
} |
||||
|
||||
|
||||
private LwjglRenderModule renderer; |
||||
|
||||
|
||||
@Override |
||||
public void initialize() |
||||
{ |
||||
addChildClient(renderer = new LwjglRenderModule(this)); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public RenderModule getRenderer() |
||||
{ |
||||
return renderer; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,641 @@ |
||||
package mightypork.gamecore.backend.lwjgl; |
||||
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.util.Stack; |
||||
|
||||
import org.lwjgl.BufferUtils; |
||||
import org.lwjgl.LWJGLException; |
||||
import org.lwjgl.opengl.Display; |
||||
import org.lwjgl.opengl.DisplayMode; |
||||
import org.lwjgl.opengl.GL11; |
||||
|
||||
import mightypork.gamecore.render.Grad; |
||||
import mightypork.gamecore.render.RenderModule; |
||||
import mightypork.gamecore.render.Screenshot; |
||||
import mightypork.gamecore.render.events.DisplayReadyEvent; |
||||
import mightypork.gamecore.render.events.ViewportChangeEvent; |
||||
import mightypork.gamecore.resources.textures.ITexture; |
||||
import mightypork.gamecore.resources.textures.LazyTexture; |
||||
import mightypork.gamecore.resources.textures.TxQuad; |
||||
import mightypork.utils.eventbus.BusAccess; |
||||
import mightypork.utils.logging.Log; |
||||
import mightypork.utils.math.color.Color; |
||||
import mightypork.utils.math.color.pal.RGB; |
||||
import mightypork.utils.math.constraints.rect.Rect; |
||||
import mightypork.utils.math.constraints.rect.caching.RectDigest; |
||||
import mightypork.utils.math.constraints.vect.Vect; |
||||
import mightypork.utils.math.timing.FpsMeter; |
||||
|
||||
|
||||
/** |
||||
* LWJGL rendering module |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public class LwjglRenderModule extends RenderModule { |
||||
|
||||
public LwjglRenderModule(BusAccess busAccess) { |
||||
super(busAccess); |
||||
} |
||||
|
||||
/** Currently binded color */ |
||||
private Color activeColor = null; |
||||
/** Currently binded color's alpha multiplier */ |
||||
private double activeColorAlpha = 1; |
||||
/** Stack of pushed colors */ |
||||
private Stack<Color> colorPushStack = new Stack<>(); |
||||
/** Currently binded texture */ |
||||
private ITexture activeTexture; |
||||
|
||||
/** Display mode used currently for the window */ |
||||
private DisplayMode windowDisplayMode; |
||||
/** FPS the user wants */ |
||||
private int targetFps; |
||||
/** FPS meter used for measuring actual FPS */ |
||||
private FpsMeter fpsMeter; |
||||
|
||||
/** Flag that at the end of frame, fullscreen should be toggled. */ |
||||
private boolean fullscreenToggleRequested; |
||||
/** Flag that at the end of frame, fullscreen should be set. */ |
||||
private boolean fullscreenSetRequested; |
||||
/** State to which fullscreen should be set. */ |
||||
private boolean fullscreenSetState; |
||||
|
||||
/** Current screen size */ |
||||
private static final Vect screenSize = new Vect() { |
||||
|
||||
@Override |
||||
public double y() |
||||
{ |
||||
return Display.getHeight(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public double x() |
||||
{ |
||||
return Display.getWidth(); |
||||
} |
||||
}; |
||||
|
||||
/** Current screen rectangle */ |
||||
private static final Rect rect = Rect.make(screenSize); |
||||
|
||||
|
||||
@Override |
||||
public void setColor(Color color) |
||||
{ |
||||
setColor(color, 1); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setColor(Color color, double alpha) |
||||
{ |
||||
if (color == null) color = RGB.WHITE; |
||||
|
||||
// color components can change over time - must use equals()
|
||||
if (activeColorAlpha == alpha && color.equals(activeColor)) return; |
||||
|
||||
activeColor = color; |
||||
activeColorAlpha = alpha; |
||||
GL11.glColor4d(color.r(), color.g(), color.b(), alpha * color.a()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void translate(double x, double y) |
||||
{ |
||||
glTranslated(x, y, 0); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void translate(double x, double y, double z) |
||||
{ |
||||
glTranslated(x, y, z); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void translate(Vect offset) |
||||
{ |
||||
glTranslated(offset.x(), offset.y(), offset.z()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void translateXY(Vect offset) |
||||
{ |
||||
glTranslated(offset.x(), offset.y(), 0); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void scale(double x, double y) |
||||
{ |
||||
glScaled(x, y, 0); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void scale(double x, double y, double z) |
||||
{ |
||||
glScaled(x, y, z); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void scale(Vect scale) |
||||
{ |
||||
glScaled(scale.x(), scale.y(), scale.z()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void scaleXY(double scale) |
||||
{ |
||||
glScaled(scale, scale, 1); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void scaleX(double scale) |
||||
{ |
||||
glScaled(scale, 1, 1); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void scaleY(double scale) |
||||
{ |
||||
glScaled(1, scale, 1); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void scaleZ(double scale) |
||||
{ |
||||
glScaled(1, 1, scale); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void rotateX(double angle) |
||||
{ |
||||
rotate(angle, AXIS_X); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void rotateY(double angle) |
||||
{ |
||||
rotate(angle, AXIS_Y); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void rotateZ(double angle) |
||||
{ |
||||
rotate(angle, AXIS_Z); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void rotate(double angle, Vect axis) |
||||
{ |
||||
final Vect vec = axis.norm(1); |
||||
glRotated(angle, vec.x(), vec.y(), vec.z()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void pushState() |
||||
{ |
||||
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); |
||||
GL11.glPushClientAttrib(GL11.GL_ALL_CLIENT_ATTRIB_BITS); |
||||
GL11.glPushMatrix(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void popState() |
||||
{ |
||||
GL11.glPopMatrix(); |
||||
GL11.glPopClientAttrib(); |
||||
GL11.glPopAttrib(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void pushGeometry() |
||||
{ |
||||
GL11.glPushMatrix(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void popGeometry() |
||||
{ |
||||
GL11.glPopMatrix(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void pushColor() |
||||
{ |
||||
colorPushStack.push(activeColor); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void popColor() |
||||
{ |
||||
setColor(colorPushStack.pop()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void quad(Rect rect) |
||||
{ |
||||
final RectDigest q = rect.digest(); |
||||
|
||||
// disable texture
|
||||
if (activeTexture != null) { |
||||
activeTexture = null; |
||||
glDisable(GL_TEXTURE_2D); |
||||
} |
||||
|
||||
// quad
|
||||
glBegin(GL_QUADS); |
||||
glVertex2d(q.left, q.bottom); |
||||
glVertex2d(q.right, q.bottom); |
||||
glVertex2d(q.right, q.top); |
||||
glVertex2d(q.left, q.top); |
||||
glEnd(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void quad(Rect rect, Color color) |
||||
{ |
||||
setColor(color); |
||||
quad(rect); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void quad(Rect rect, Grad grad) |
||||
{ |
||||
final RectDigest r = rect.digest(); |
||||
|
||||
// disable texture
|
||||
if (activeTexture != null) { |
||||
activeTexture = null; |
||||
glDisable(GL_TEXTURE_2D); |
||||
} |
||||
|
||||
// quad
|
||||
glBegin(GL_QUADS); |
||||
setColor(grad.leftBottom); |
||||
glVertex2d(r.left, r.bottom); |
||||
|
||||
setColor(grad.rightBottom); |
||||
glVertex2d(r.right, r.bottom); |
||||
|
||||
setColor(grad.rightTop); |
||||
glVertex2d(r.right, r.top); |
||||
|
||||
setColor(grad.leftTop); |
||||
glVertex2d(r.left, r.top); |
||||
glEnd(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void quad(Rect rect, TxQuad txquad) |
||||
{ |
||||
quad(rect, txquad, RGB.WHITE); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void quad(Rect rect, TxQuad txquad, Color color) |
||||
{ |
||||
// texture is loaded uniquely -> can compare with ==
|
||||
if (activeTexture != txquad.tx) { |
||||
glEnable(GL_TEXTURE_2D); |
||||
activeTexture = txquad.tx; |
||||
activeTexture.bind(); |
||||
} |
||||
|
||||
glBegin(GL_QUADS); |
||||
setColor(color); |
||||
|
||||
final RectDigest q = rect.digest(); |
||||
final RectDigest u = txquad.uvs.digest(); |
||||
|
||||
final double offs = 0.0001;// hack to avoid white stitching
|
||||
|
||||
double tL = u.left + offs, tR = u.right - offs, tT = u.top + offs, tB = u.bottom - offs; |
||||
|
||||
// handle flip
|
||||
if (txquad.isFlippedY()) { |
||||
final double swap = tT; |
||||
tT = tB; |
||||
tB = swap; |
||||
} |
||||
|
||||
if (txquad.isFlippedX()) { |
||||
final double swap = tL; |
||||
tL = tR; |
||||
tR = swap; |
||||
} |
||||
|
||||
final double w = txquad.tx.getWidth01(); |
||||
final double h = txquad.tx.getHeight01(); |
||||
|
||||
// quad with texture
|
||||
glTexCoord2d(tL * w, tB * h); |
||||
glVertex2d(q.left, q.bottom); |
||||
|
||||
glTexCoord2d(tR * w, tB * h); |
||||
glVertex2d(q.right, q.bottom); |
||||
|
||||
glTexCoord2d(tR * w, tT * h); |
||||
glVertex2d(q.right, q.top); |
||||
|
||||
glTexCoord2d(tL * w, tT * h); |
||||
glVertex2d(q.left, q.top); |
||||
|
||||
glEnd(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setupProjection(Vect screenSize) |
||||
{ |
||||
// fix projection for changed size
|
||||
glMatrixMode(GL_PROJECTION); |
||||
glLoadIdentity(); |
||||
glViewport(0, 0, screenSize.xi(), screenSize.yi()); |
||||
glOrtho(0, screenSize.xi(), screenSize.yi(), 0, -1000, 1000); |
||||
|
||||
// back to modelview
|
||||
glMatrixMode(GL_MODELVIEW); |
||||
|
||||
glLoadIdentity(); |
||||
|
||||
glDisable(GL_LIGHTING); |
||||
|
||||
glClearDepth(1f); |
||||
glEnable(GL_DEPTH_TEST); |
||||
glDepthFunc(GL_LEQUAL); |
||||
|
||||
glEnable(GL_NORMALIZE); |
||||
|
||||
glShadeModel(GL_SMOOTH); |
||||
|
||||
glEnable(GL_BLEND); |
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public LazyTexture getLazyTexture(String path) |
||||
{ |
||||
return new SlickLazyTexture(path); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void destroy() |
||||
{ |
||||
Display.destroy(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setTargetFps(int fps) |
||||
{ |
||||
this.targetFps = fps; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void createDisplay() |
||||
{ |
||||
if (Display.isCreated()) throw new IllegalStateException("Display already created."); |
||||
try { |
||||
Display.create(); |
||||
|
||||
fpsMeter = new FpsMeter(); |
||||
|
||||
if (fullscreenSetRequested && fullscreenSetState) { |
||||
doToggleFullscreen(); |
||||
Display.update(); |
||||
fullscreenSetRequested = false; |
||||
} |
||||
|
||||
getEventBus().send(new DisplayReadyEvent()); |
||||
|
||||
} catch (final Exception e) { |
||||
throw new RuntimeException("Could not initialize display.", e); |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setFullscreen(boolean fs) |
||||
{ |
||||
fullscreenSetRequested = true; |
||||
fullscreenSetState = fs; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void switchFullscreen() |
||||
{ |
||||
fullscreenToggleRequested = true; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean isFullscreen() |
||||
{ |
||||
return Display.isFullscreen(); |
||||
} |
||||
|
||||
|
||||
private void doToggleFullscreen() |
||||
{ |
||||
doSetFullscreen(!Display.isFullscreen()); |
||||
} |
||||
|
||||
|
||||
private void doSetFullscreen(boolean fs) |
||||
{ |
||||
try { |
||||
|
||||
if (Display.isFullscreen() == fs) return; // no work
|
||||
|
||||
if (fs) { |
||||
Log.f3("Entering fullscreen."); |
||||
// save window resize
|
||||
windowDisplayMode = new DisplayMode(Display.getWidth(), Display.getHeight()); |
||||
|
||||
Display.setDisplayMode(Display.getDesktopDisplayMode()); |
||||
Display.setFullscreen(true); |
||||
Display.update(); |
||||
} else { |
||||
Log.f3("Leaving fullscreen."); |
||||
Display.setDisplayMode(windowDisplayMode); |
||||
Display.update(); |
||||
} |
||||
|
||||
getEventBus().send(new ViewportChangeEvent(getSize())); |
||||
|
||||
} catch (final Throwable t) { |
||||
Log.e("Failed to change fullscreen mode.", t); |
||||
try { |
||||
Display.setDisplayMode(windowDisplayMode); |
||||
Display.update(); |
||||
} catch (final Throwable t1) { |
||||
throw new RuntimeException("Failed to revert failed fullscreen toggle.", t1); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Screenshot takeScreenshot() |
||||
{ |
||||
glReadBuffer(GL_FRONT); |
||||
final int width = Display.getWidth(); |
||||
final int height = Display.getHeight(); |
||||
final int bpp = 4; |
||||
final ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * bpp); |
||||
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); |
||||
|
||||
final AwtScreenshot sc = new AwtScreenshot(width, height, bpp, buffer); |
||||
|
||||
return sc; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean isCloseRequested() |
||||
{ |
||||
return Display.isCloseRequested(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void beginFrame() |
||||
{ |
||||
// handle resize
|
||||
if (Display.wasResized()) { |
||||
getEventBus().send(new ViewportChangeEvent(getSize())); |
||||
} |
||||
|
||||
if (fullscreenToggleRequested) { |
||||
fullscreenToggleRequested = false; |
||||
doToggleFullscreen(); |
||||
} |
||||
|
||||
if (fullscreenSetRequested) { |
||||
fullscreenSetRequested = false; |
||||
doSetFullscreen(fullscreenSetState); |
||||
} |
||||
|
||||
glLoadIdentity(); |
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
||||
fpsMeter.frame(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void endFrame() |
||||
{ |
||||
Display.update(false); // don't poll input devices
|
||||
Display.sync(targetFps); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setSize(int width, int height) |
||||
{ |
||||
try { |
||||
Display.setDisplayMode(windowDisplayMode = new DisplayMode(width, height)); |
||||
} catch (LWJGLException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setTitle(String title) |
||||
{ |
||||
Display.setTitle(title); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setVSync(boolean vsync) |
||||
{ |
||||
Display.setVSyncEnabled(vsync); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setResizable(boolean resizable) |
||||
{ |
||||
Display.setResizable(resizable); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Rect getRect() |
||||
{ |
||||
return rect; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public long getFps() |
||||
{ |
||||
return fpsMeter.getFPS(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Vect getCenter() |
||||
{ |
||||
return rect.center(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public int getWidth() |
||||
{ |
||||
return Display.getWidth(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public int getHeight() |
||||
{ |
||||
return Display.getHeight(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public Vect getSize() |
||||
{ |
||||
return screenSize; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,142 @@ |
||||
package mightypork.gamecore.backend.lwjgl; |
||||
|
||||
|
||||
import java.io.IOException; |
||||
|
||||
import mightypork.gamecore.resources.TextureBasedResource; |
||||
import mightypork.gamecore.resources.textures.*; |
||||
import mightypork.utils.annotations.Alias; |
||||
import mightypork.utils.files.FileUtils; |
||||
import mightypork.utils.logging.Log; |
||||
import org.lwjgl.opengl.GL11; |
||||
import org.newdawn.slick.opengl.Texture; |
||||
import org.newdawn.slick.opengl.TextureLoader; |
||||
|
||||
|
||||
/** |
||||
* Deferred texture |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
@Alias(name = "Texture") |
||||
@TextureBasedResource |
||||
public class SlickLazyTexture extends LazyTexture { |
||||
|
||||
private org.newdawn.slick.opengl.Texture backingTexture; |
||||
private boolean alpha; |
||||
private boolean alphal; |
||||
|
||||
/** |
||||
* @param resourcePath resource path |
||||
*/ |
||||
public SlickLazyTexture(String resourcePath) { |
||||
super(resourcePath); |
||||
} |
||||
|
||||
@Override |
||||
protected synchronized void loadResource(String path) |
||||
{ |
||||
try { |
||||
final String ext = FileUtils.getExtension(path).toUpperCase(); |
||||
|
||||
final Texture texture = TextureLoader.getTexture(ext, FileUtils.getResource(path), false, filter.num); |
||||
|
||||
if (texture == null) { |
||||
Log.w("Texture " + path + " could not be loaded."); |
||||
} |
||||
|
||||
backingTexture = texture; |
||||
|
||||
} catch (final IOException e) { |
||||
Log.e("Loading of texture " + path + " failed.", e); |
||||
throw new RuntimeException("Could not load texture " + path + ".", e); |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean hasAlpha() |
||||
{ |
||||
if (!ensureLoaded()) return false; |
||||
|
||||
if (!alphal) { |
||||
alphal = true; |
||||
alpha = backingTexture.hasAlpha(); |
||||
} |
||||
|
||||
return alpha; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void bind() |
||||
{ |
||||
if (!ensureLoaded()) return; |
||||
|
||||
//GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, getTextureID()); |
||||
|
||||
GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); |
||||
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrap.num); |
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrap.num); |
||||
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter.num); |
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter.num); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public int getImageHeight() |
||||
{ |
||||
if (!ensureLoaded()) return 0; |
||||
|
||||
return backingTexture.getImageHeight(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public int getImageWidth() |
||||
{ |
||||
if (!ensureLoaded()) return 0; |
||||
|
||||
return backingTexture.getImageWidth(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public float getHeight01() |
||||
{ |
||||
if (!ensureLoaded()) return 0; |
||||
|
||||
return backingTexture.getHeight(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public float getWidth01() |
||||
{ |
||||
if (!ensureLoaded()) return 0; |
||||
|
||||
return backingTexture.getWidth(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void destroy() |
||||
{ |
||||
if (!isLoaded()) return; |
||||
|
||||
backingTexture.release(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public int getTextureID() |
||||
{ |
||||
if (!ensureLoaded()) return -1; |
||||
|
||||
return backingTexture.getTextureID(); |
||||
} |
||||
} |
@ -0,0 +1,85 @@ |
||||
package mightypork.gamecore.core.modules; |
||||
|
||||
|
||||
import mightypork.gamecore.backend.Backend; |
||||
import mightypork.gamecore.render.RenderModule; |
||||
|
||||
|
||||
/** |
||||
* Game base class & static subsystem access |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public class App { |
||||
|
||||
private static App runningInstance; |
||||
private static Backend backend; |
||||
|
||||
|
||||
public App() { |
||||
if (App.isInitialized()) throw new IllegalStateException("App already initialized"); |
||||
|
||||
// store current instance in static field
|
||||
App.runningInstance = this; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create app with given backend. |
||||
* |
||||
* @param backend backend to use |
||||
*/ |
||||
public void setBackend(Backend backend) |
||||
{ |
||||
// store used backend in static field
|
||||
App.backend = backend; |
||||
|
||||
// initialize the backend
|
||||
App.backend.initialize(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Throw error if app is not initialized |
||||
*/ |
||||
protected static void assertInitialized() |
||||
{ |
||||
if (!App.isInitialized()) throw new IllegalStateException("App is not initialized."); |
||||
if (backend == null) throw new IllegalStateException("No backend set!"); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Check whether the app is initialized (backend assigned). |
||||
* |
||||
* @return is initialized |
||||
*/ |
||||
public static boolean isInitialized() |
||||
{ |
||||
return runningInstance != null; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get current backend |
||||
* |
||||
* @return the backend |
||||
*/ |
||||
public static Backend getBackend() |
||||
{ |
||||
assertInitialized(); |
||||
return backend; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get renderer instance from the backend |
||||
* |
||||
* @return backend |
||||
*/ |
||||
public static RenderModule gfx() |
||||
{ |
||||
assertInitialized(); |
||||
return backend.getRenderer(); |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
package mightypork.gamecore.render; |
||||
|
||||
|
||||
import mightypork.utils.math.color.Color; |
||||
|
||||
|
||||
/** |
||||
* Linear gradient (each vertex can have different color) |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public class Grad { |
||||
|
||||
public final Color leftTop, rightTop, rightBottom, leftBottom; |
||||
|
||||
|
||||
/** |
||||
* Create a gradient |
||||
* |
||||
* @param leftTop left top color |
||||
* @param rightTop right top color |
||||
* @param rightBottom right bottom color |
||||
* @param leftBottom left bottom color |
||||
*/ |
||||
public Grad(Color leftTop, Color rightTop, Color rightBottom, Color leftBottom) { |
||||
this.leftTop = leftTop; |
||||
this.rightTop = rightTop; |
||||
this.rightBottom = rightBottom; |
||||
this.leftBottom = leftBottom; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
package mightypork.gamecore.render; |
||||
|
||||
|
||||
import mightypork.utils.math.color.Color; |
||||
|
||||
|
||||
/** |
||||
* Linear horizontal gradient |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public class GradH extends Grad { |
||||
|
||||
public GradH(Color left, Color right) { |
||||
super(left, right, right, left); |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
package mightypork.gamecore.render; |
||||
|
||||
|
||||
import mightypork.utils.math.color.Color; |
||||
|
||||
|
||||
/** |
||||
* Linear vertical gradient |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public class GradV extends Grad { |
||||
|
||||
public GradV(Color top, Color bottom) { |
||||
super(top, top, bottom, bottom); |
||||
} |
||||
} |
@ -0,0 +1,420 @@ |
||||
package mightypork.gamecore.render; |
||||
|
||||
|
||||
import mightypork.gamecore.backend.BackendModule; |
||||
import mightypork.gamecore.resources.textures.LazyTexture; |
||||
import mightypork.gamecore.resources.textures.TxQuad; |
||||
import mightypork.utils.eventbus.BusAccess; |
||||
import mightypork.utils.math.color.Color; |
||||
import mightypork.utils.math.constraints.rect.Rect; |
||||
import mightypork.utils.math.constraints.vect.Vect; |
||||
import mightypork.utils.math.constraints.vect.VectConst; |
||||
import mightypork.utils.math.timing.FpsMeter; |
||||
|
||||
|
||||
/** |
||||
* Render and display backend module.<br> |
||||
* This module takes care of setting and getting screen size and parameters, |
||||
* drawing on screen and timing render frames. |
||||
* |
||||
* @author MightyPork |
||||
*/ |
||||
public abstract class RenderModule extends BackendModule { |
||||
|
||||
public RenderModule(BusAccess busAccess) { |
||||
super(busAccess); |
||||
} |
||||
|
||||
protected static final VectConst AXIS_X = Vect.make(1, 0, 0); |
||||
protected static final VectConst AXIS_Y = Vect.make(0, 1, 0); |
||||
protected static final VectConst AXIS_Z = Vect.make(0, 0, 1); |
||||
|
||||
|
||||
/** |
||||
* Set drawing color |
||||
* |
||||
* @param color color |
||||
*/ |
||||
public abstract void setColor(Color color); |
||||
|
||||
|
||||
/** |
||||
* Set drawing color, adjust alpha |
||||
* |
||||
* @param color color |
||||
* @param alpha alpha multiplier |
||||
*/ |
||||
public abstract void setColor(Color color, double alpha); |
||||
|
||||
|
||||
/** |
||||
* Translate by x, y |
||||
* |
||||
* @param x x offset |
||||
* @param y y offset |
||||
*/ |
||||
public abstract void translate(double x, double y); |
||||
|
||||
|
||||
/** |
||||
* Translate by x, y, z |
||||
* |
||||
* @param x x offset |
||||
* @param y y offset |
||||
* @param z z offset |
||||
*/ |
||||
public abstract void translate(double x, double y, double z); |
||||
|
||||
|
||||
/** |
||||
* Translate by offset vector |
||||
* |
||||
* @param offset offset coordinate |
||||
*/ |
||||
public abstract void translate(Vect offset); |
||||
|
||||
|
||||
/** |
||||
* Translate by offset vector, ignore Z |
||||
* |
||||
* @param offset offset coordinate |
||||
*/ |
||||
public abstract void translateXY(Vect offset); |
||||
|
||||
|
||||
/** |
||||
* Set scale for translations and coordinates |
||||
* |
||||
* @param x x scale |
||||
* @param y y scale |
||||
*/ |
||||
public abstract void scale(double x, double y); |
||||
|
||||
|
||||
/** |
||||
* Set scale for translations and coordinates |
||||
* |
||||
* @param x x scale |
||||
* @param y y scale |
||||
* @param z z scale |
||||
*/ |
||||
public abstract void scale(double x, double y, double z); |
||||
|
||||
|
||||
/** |
||||
* Set scale for translations and coordinates |
||||
* |
||||
* @param scale vector |
||||
*/ |
||||
public abstract void scale(Vect scale); |
||||
|
||||
|
||||
/** |
||||
* Set scale for translations and coordinates (same value for X and Y scale) |
||||
* |
||||
* @param scale scaling factor |
||||
*/ |
||||
public abstract void scaleXY(double scale); |
||||
|
||||
|
||||
/** |
||||
* Set X scale for translations and coordinates |
||||
* |
||||
* @param scale scaling factor |
||||
*/ |
||||
public abstract void scaleX(double scale); |
||||
|
||||
|
||||
/** |
||||
* Set Y scale for translations and coordinates |
||||
* |
||||
* @param scale scaling factor |
||||
*/ |
||||
public abstract void scaleY(double scale); |
||||
|
||||
|
||||
/** |
||||
* Set Z scale for translations and coordinates |
||||
* |
||||
* @param scale scaling factor |
||||
*/ |
||||
public abstract void scaleZ(double scale); |
||||
|
||||
|
||||
/** |
||||
* Rotate coordinate system around X axis |
||||
* |
||||
* @param angle rotation (in degrees) |
||||
*/ |
||||
public abstract void rotateX(double angle); |
||||
|
||||
|
||||
/** |
||||
* Rotate coordinate system around Y axis |
||||
* |
||||
* @param angle rotation (in degrees) |
||||
*/ |
||||
public abstract void rotateY(double angle); |
||||
|
||||
|
||||
/** |
||||
* Rotate coordinate system around Z axis |
||||
* |
||||
* @param angle rotation (in degrees) |
||||
*/ |
||||
public abstract void rotateZ(double angle); |
||||
|
||||
|
||||
/** |
||||
* Rotate coordinate system around given axis |
||||
* |
||||
* @param angle rotation angle |
||||
* @param axis rotation axis (unit vector) |
||||
*/ |
||||
public abstract void rotate(double angle, Vect axis); |
||||
|
||||
|
||||
/** |
||||
* Store render state on stack<br> |
||||
* This includes pushGeometry and pushColor. |
||||
*/ |
||||
public abstract void pushState(); |
||||
|
||||
|
||||
/** |
||||
* Restore state from stack (must be pushed first)<br> |
||||
* This includes popColor and popGeometry. |
||||
*/ |
||||
public abstract void popState(); |
||||
|
||||
|
||||
/** |
||||
* Store current rotation and translation on stack |
||||
*/ |
||||
public abstract void pushGeometry(); |
||||
|
||||
|
||||
/** |
||||
* Restore rotation and translation from stack |
||||
*/ |
||||
public abstract void popGeometry(); |
||||
|
||||
|
||||
/** |
||||
* Store color on stack (so it can later be restored) |
||||
*/ |
||||
public abstract void pushColor(); |
||||
|
||||
|
||||
/** |
||||
* Restore color from stack (must be pushed first) |
||||
*/ |
||||
public abstract void popColor(); |
||||
|
||||
|
||||
/** |
||||
* Render 2D quad with currently set color |
||||
* |
||||
* @param rect drawn rect |
||||
*/ |
||||
public abstract void quad(Rect rect); |
||||
|
||||
|
||||
/** |
||||
* Render 2D quad with given color.<br> |
||||
* This may change current drawing color. |
||||
* |
||||
* @param rect rectangle |
||||
* @param color draw color |
||||
*/ |
||||
public abstract void quad(Rect rect, Color color); |
||||
|
||||
|
||||
/** |
||||
* Render 2D quad with gradient.<br> |
||||
* This may change current drawing color. |
||||
* |
||||
* @param rect rectangle |
||||
* @param grad gradient |
||||
*/ |
||||
public abstract void quad(Rect rect, Grad grad); |
||||
|
||||
|
||||
/** |
||||
* Render textured quad with current color |
||||
* |
||||
* @param rect rectangle to draw |
||||
* @param txquad texture quad |
||||
*/ |
||||
public abstract void quad(Rect rect, TxQuad txquad); |
||||
|
||||
|
||||
/** |
||||
* Render textured quad with given color |
||||
* |
||||
* @param rect rectangle to draw |
||||
* @param txquad texture instance |
||||
* @param color color tint |
||||
*/ |
||||
public abstract void quad(Rect rect, TxQuad txquad, Color color); |
||||
|
||||
|
||||
/** |
||||
* Setup projection for 2D graphics |
||||
* |
||||
* @param screenSize current viewport size |
||||
*/ |
||||
public abstract void setupProjection(Vect screenSize); |
||||
|
||||
|
||||
/** |
||||
* Get backend-flavoured lazy texture |
||||
* |
||||
* @param path path to texture |
||||
* @return the lazy texture |
||||
*/ |
||||
public abstract LazyTexture getLazyTexture(String path); |
||||
|
||||
|
||||
/** |
||||
* Set target fps (for syncing in endFrame() call).<br> |
||||
* With vsync enabled, the target fps may not be met. |
||||
* |
||||
* @param fps requested fps |
||||
*/ |
||||
public abstract void setTargetFps(int fps); |
||||
|
||||
|
||||
/** |
||||
* Create a main window |
||||
*/ |
||||
public abstract void createDisplay(); |
||||
|
||||
|
||||
/** |
||||
* Set fullscreen |
||||
* |
||||
* @param fs true for fullscreen |
||||
*/ |
||||
public abstract void setFullscreen(boolean fs); |
||||
|
||||
|
||||
/** |
||||
* Request fullscreen toggle (eg. at the end of render frame) |
||||
*/ |
||||
public abstract void switchFullscreen(); |
||||
|
||||
|
||||
/** |
||||
* Get fullscreen state |
||||
* |
||||
* @return is fullscreen |
||||
*/ |
||||
public abstract boolean isFullscreen(); |
||||
|
||||
|
||||
/** |
||||
* Take screenshot (expensive processing should be done in separate thread |
||||
* when screenshot is saved). |
||||
* |
||||
* @return screenshot object |
||||
*/ |
||||
public abstract Screenshot takeScreenshot(); |
||||
|
||||
|
||||
/** |
||||
* FIXME This should be moved to inout module |
||||
* |
||||
* @return true if close was requested recently (i.e. click on cross) |
||||
*/ |
||||
public abstract boolean isCloseRequested(); |
||||
|
||||
|
||||
/** |
||||
* Start a render frame - clear buffers, prepare rendering context etc. |
||||
*/ |
||||
public abstract void beginFrame(); |
||||
|
||||
|
||||
/** |
||||
* End a render frame: flip buffers, sync to fps... |
||||
*/ |
||||
public abstract void endFrame(); |
||||
|
||||
|
||||
/** |
||||
* Set display dimensions |
||||
* |
||||
* @param width display width (pixels) |
||||
* @param height display height (pixels) |
||||
*/ |
||||
public abstract void setSize(int width, int height); |
||||
|
||||
|
||||
/** |
||||
* Set titlebar text |
||||
* |
||||
* @param title titlebar text |
||||
*/ |
||||
public abstract void setTitle(String title); |
||||
|
||||
|
||||
/** |
||||
* Enable or disable VSync |
||||
* |
||||
* @param vsync true for vsync enabled |
||||
*/ |
||||
public abstract void setVSync(boolean vsync); |
||||
|
||||
|
||||
/** |
||||
* Set window resizable / fixed |
||||
* |
||||
* @param resizable true for resizable |
||||
*/ |
||||
public abstract void setResizable(boolean resizable); |
||||
|
||||
|
||||
/** |
||||
* Get screen rect. Should always return the same Vect instance. |
||||
* |
||||
* @return the rect |
||||
*/ |
||||
public abstract Rect getRect(); |
||||
|
||||
|
||||
/** |
||||
* Get current FPS (eg. measured by a {@link FpsMeter}) |
||||
* |
||||
* @return current FPS |
||||
*/ |
||||
public abstract long getFps(); |
||||
|
||||
|
||||
/** |
||||
* Get screen center. Should always return the same Vect instance. |
||||
* |
||||
* @return screen center. |
||||
*/ |
||||
public abstract Vect getCenter(); |
||||
|
||||
|
||||
/** |
||||
* @return screen width |
||||
*/ |
||||
public abstract int getWidth(); |
||||
|
||||
|
||||
/** |
||||
* @return screen height |
||||
*/ |
||||
public abstract int getHeight(); |
||||
|
||||
|
||||
/** |
||||
* Get screen size. Should always return the same Vect instance. |
||||
* |
||||
* @return size |
||||
*/ |
||||
public abstract Vect getSize(); |
||||
} |
@ -1,81 +1,33 @@ |
||||
package mightypork.gamecore.render; |
||||
|
||||
|
||||
import java.awt.image.BufferedImage; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
import javax.imageio.ImageIO; |
||||
|
||||
|
||||
/** |
||||
* Screenshot object, can be used to extract image or write to file.<br> |
||||
* Screenshot, once taken, can be safely processed in separate thread. |
||||
* <p> |
||||
* Screenshot object, can be used to save image to file. |
||||
* </p> |
||||
* <p> |
||||
* Screenshot typically takes a byte buffer and converts it to image before |
||||
* saving to file. This image can be cached to speed up repeated saving. |
||||
* </p> |
||||
* <p> |
||||
* Once created (passing byte buffer in constructor), the Screenshot should be |
||||
* safe to process (call the save() method) in separate thread. |
||||
* </p> |
||||
* |
||||
* @author Ondřej Hruška (MightyPork) |
||||
*/ |
||||
public class Screenshot { |
||||
|
||||
private final int width; |
||||
private final int height; |
||||
private final int bpp; |
||||
private final ByteBuffer bytes; |
||||
private BufferedImage image; |
||||
|
||||
|
||||
/** |
||||
* @param width image width |
||||
* @param height image height |
||||
* @param bpp bits per pixel (typically 4) |
||||
* @param buffer |
||||
* @author MightyPork |
||||
*/ |
||||
public Screenshot(int width, int height, int bpp, ByteBuffer buffer) |
||||
{ |
||||
this.width = width; |
||||
this.height = height; |
||||
this.bpp = bpp; |
||||
this.bytes = buffer; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Extract to an image.<br> |
||||
* Subsequent calls will use a cached value. |
||||
* |
||||
* @return image |
||||
*/ |
||||
public BufferedImage getImage() |
||||
{ |
||||
if (image != null) return image; |
||||
|
||||
image = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB); |
||||
|
||||
// convert to a buffered image
|
||||
for (int x = 0; x < this.width; x++) { |
||||
for (int y = 0; y < this.height; y++) { |
||||
final int i = (x + (this.width * y)) * this.bpp; |
||||
final int r = this.bytes.get(i) & 0xFF; |
||||
final int g = this.bytes.get(i + 1) & 0xFF; |
||||
final int b = this.bytes.get(i + 2) & 0xFF; |
||||
image.setRGB(x, this.height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b); |
||||
} |
||||
} |
||||
|
||||
return image; |
||||
} |
||||
|
||||
public interface Screenshot { |
||||
|
||||
/** |
||||
* Save to a file.<br> |
||||
* Cached value is used if any. |
||||
* Process byte buffer and write image to a file.<br> |
||||
* Image can be cached for future save. |
||||
* |
||||
* @param file target file |
||||
* @throws IOException on error writing to file |
||||
*/ |
||||
public void save(File file) throws IOException |
||||
{ |
||||
file.getParentFile().mkdirs(); |
||||
ImageIO.write(getImage(), "PNG", file); |
||||
} |
||||
void save(File file) throws IOException; |
||||
} |
||||
|
Loading…
Reference in new issue