diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..d43212b
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.project b/.project
new file mode 100644
index 0000000..568be6b
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ GameCore-LWJGL
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..7341ab1
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/bin/mightypork/gamecore/backends/lwjgl/BufferHelper.class b/bin/mightypork/gamecore/backends/lwjgl/BufferHelper.class
new file mode 100644
index 0000000..18a29e2
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/BufferHelper.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/InitTaskRedirectSlickLog.class b/bin/mightypork/gamecore/backends/lwjgl/InitTaskRedirectSlickLog.class
new file mode 100644
index 0000000..a831b8c
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/InitTaskRedirectSlickLog.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/LwjglBackend.class b/bin/mightypork/gamecore/backends/lwjgl/LwjglBackend.class
new file mode 100644
index 0000000..f020ed7
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/LwjglBackend.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/LwjglInputModule$1.class b/bin/mightypork/gamecore/backends/lwjgl/LwjglInputModule$1.class
new file mode 100644
index 0000000..f24e14a
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/LwjglInputModule$1.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/LwjglInputModule.class b/bin/mightypork/gamecore/backends/lwjgl/LwjglInputModule.class
new file mode 100644
index 0000000..66698c9
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/LwjglInputModule.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/SlickLogRedirector.class b/bin/mightypork/gamecore/backends/lwjgl/SlickLogRedirector.class
new file mode 100644
index 0000000..ad14f4d
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/SlickLogRedirector.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/audio/SlickAudio.class b/bin/mightypork/gamecore/backends/lwjgl/audio/SlickAudio.class
new file mode 100644
index 0000000..fd27244
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/audio/SlickAudio.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/audio/SlickAudioModule.class b/bin/mightypork/gamecore/backends/lwjgl/audio/SlickAudioModule.class
new file mode 100644
index 0000000..7d1a397
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/audio/SlickAudioModule.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/graphics/AwtScreenshot.class b/bin/mightypork/gamecore/backends/lwjgl/graphics/AwtScreenshot.class
new file mode 100644
index 0000000..cacf9cf
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/graphics/AwtScreenshot.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/graphics/LwjglGraphicsModule$1.class b/bin/mightypork/gamecore/backends/lwjgl/graphics/LwjglGraphicsModule$1.class
new file mode 100644
index 0000000..7a2effe
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/graphics/LwjglGraphicsModule$1.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/graphics/LwjglGraphicsModule.class b/bin/mightypork/gamecore/backends/lwjgl/graphics/LwjglGraphicsModule.class
new file mode 100644
index 0000000..aa6b4a4
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/graphics/LwjglGraphicsModule.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/graphics/SlickTexture.class b/bin/mightypork/gamecore/backends/lwjgl/graphics/SlickTexture.class
new file mode 100644
index 0000000..855a97e
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/graphics/SlickTexture.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFont.class b/bin/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFont.class
new file mode 100644
index 0000000..18f3720
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFont.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFontFromSystem.class b/bin/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFontFromSystem.class
new file mode 100644
index 0000000..fbca3d7
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFontFromSystem.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont$1LoadedGlyph.class b/bin/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont$1LoadedGlyph.class
new file mode 100644
index 0000000..01f0fa5
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont$1LoadedGlyph.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont$CharTile.class b/bin/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont$CharTile.class
new file mode 100644
index 0000000..84ecbd9
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont$CharTile.class differ
diff --git a/bin/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont.class b/bin/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont.class
new file mode 100644
index 0000000..a91e3ea
Binary files /dev/null and b/bin/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont.class differ
diff --git a/lib/OpenAL32.dll b/lib/OpenAL32.dll
new file mode 100644
index 0000000..6dd2600
Binary files /dev/null and b/lib/OpenAL32.dll differ
diff --git a/lib/OpenAL64.dll b/lib/OpenAL64.dll
new file mode 100644
index 0000000..00c98c0
Binary files /dev/null and b/lib/OpenAL64.dll differ
diff --git a/lib/jinput-dx8.dll b/lib/jinput-dx8.dll
new file mode 100644
index 0000000..6d27ad5
Binary files /dev/null and b/lib/jinput-dx8.dll differ
diff --git a/lib/jinput-dx8_64.dll b/lib/jinput-dx8_64.dll
new file mode 100644
index 0000000..6730589
Binary files /dev/null and b/lib/jinput-dx8_64.dll differ
diff --git a/lib/jinput-raw.dll b/lib/jinput-raw.dll
new file mode 100644
index 0000000..ce1d162
Binary files /dev/null and b/lib/jinput-raw.dll differ
diff --git a/lib/jinput-raw_64.dll b/lib/jinput-raw_64.dll
new file mode 100644
index 0000000..3d2b3ad
Binary files /dev/null and b/lib/jinput-raw_64.dll differ
diff --git a/lib/jinput.jar b/lib/jinput.jar
new file mode 100644
index 0000000..7c2b6b0
Binary files /dev/null and b/lib/jinput.jar differ
diff --git a/lib/jogg-0.0.7.jar b/lib/jogg-0.0.7.jar
new file mode 100644
index 0000000..ecb0260
Binary files /dev/null and b/lib/jogg-0.0.7.jar differ
diff --git a/lib/jorbis-0.0.15.jar b/lib/jorbis-0.0.15.jar
new file mode 100644
index 0000000..4cf51f9
Binary files /dev/null and b/lib/jorbis-0.0.15.jar differ
diff --git a/lib/libjinput-linux.so b/lib/libjinput-linux.so
new file mode 100644
index 0000000..3cdc439
Binary files /dev/null and b/lib/libjinput-linux.so differ
diff --git a/lib/libjinput-linux64.so b/lib/libjinput-linux64.so
new file mode 100644
index 0000000..de1ee5f
Binary files /dev/null and b/lib/libjinput-linux64.so differ
diff --git a/lib/libjinput-osx.jnilib b/lib/libjinput-osx.jnilib
new file mode 100644
index 0000000..59a3eab
Binary files /dev/null and b/lib/libjinput-osx.jnilib differ
diff --git a/lib/liblwjgl.jnilib b/lib/liblwjgl.jnilib
new file mode 100644
index 0000000..cbb5b4c
Binary files /dev/null and b/lib/liblwjgl.jnilib differ
diff --git a/lib/liblwjgl.so b/lib/liblwjgl.so
new file mode 100644
index 0000000..5a02874
Binary files /dev/null and b/lib/liblwjgl.so differ
diff --git a/lib/liblwjgl64.so b/lib/liblwjgl64.so
new file mode 100644
index 0000000..4572589
Binary files /dev/null and b/lib/liblwjgl64.so differ
diff --git a/lib/libopenal.so b/lib/libopenal.so
new file mode 100644
index 0000000..7742faf
Binary files /dev/null and b/lib/libopenal.so differ
diff --git a/lib/libopenal64.so b/lib/libopenal64.so
new file mode 100644
index 0000000..d1e45e5
Binary files /dev/null and b/lib/libopenal64.so differ
diff --git a/lib/lwjgl-source-2.8.4.zip b/lib/lwjgl-source-2.8.4.zip
new file mode 100644
index 0000000..f48fd31
Binary files /dev/null and b/lib/lwjgl-source-2.8.4.zip differ
diff --git a/lib/lwjgl.dll b/lib/lwjgl.dll
new file mode 100644
index 0000000..6819404
Binary files /dev/null and b/lib/lwjgl.dll differ
diff --git a/lib/lwjgl.jar b/lib/lwjgl.jar
new file mode 100644
index 0000000..a0fb56d
Binary files /dev/null and b/lib/lwjgl.jar differ
diff --git a/lib/lwjgl64.dll b/lib/lwjgl64.dll
new file mode 100644
index 0000000..e66ab2a
Binary files /dev/null and b/lib/lwjgl64.dll differ
diff --git a/lib/lwjgl_util.jar b/lib/lwjgl_util.jar
new file mode 100644
index 0000000..9973b24
Binary files /dev/null and b/lib/lwjgl_util.jar differ
diff --git a/lib/slick-util-src.zip b/lib/slick-util-src.zip
new file mode 100644
index 0000000..7ca6790
Binary files /dev/null and b/lib/slick-util-src.zip differ
diff --git a/lib/slick-util.jar b/lib/slick-util.jar
new file mode 100644
index 0000000..86a64a7
Binary files /dev/null and b/lib/slick-util.jar differ
diff --git a/src/mightypork/gamecore/backends/lwjgl/BufferHelper.java b/src/mightypork/gamecore/backends/lwjgl/BufferHelper.java
new file mode 100644
index 0000000..3e38b3b
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/BufferHelper.java
@@ -0,0 +1,52 @@
+package mightypork.gamecore.backends.lwjgl;
+
+
+import java.nio.FloatBuffer;
+
+import org.lwjgl.BufferUtils;
+
+
+/**
+ * Calc subclass with buffer utils.
+ *
+ * @author Ondřej Hruška (MightyPork)
+ */
+public class BufferHelper {
+
+ /**
+ * Create java.nio.FloatBuffer of given floats, and flip it.
+ *
+ * @param obj floats or float array
+ * @return float buffer
+ */
+ public static FloatBuffer mkFillBuff(float... obj)
+ {
+ return (FloatBuffer) BufferUtils.createFloatBuffer(obj.length).put(obj).flip();
+ }
+
+
+ /**
+ * Fill java.nio.FloatBuffer with floats or float array
+ *
+ * @param buff
+ * @param obj
+ */
+ public static void fill(FloatBuffer buff, float... obj)
+ {
+ buff.put(obj);
+ buff.flip();
+ }
+
+
+ /**
+ * Create new java.nio.FloatBuffer of given length
+ *
+ * @param count elements
+ * @return the new java.nio.FloatBuffer
+ */
+ public static FloatBuffer alloc(int count)
+ {
+ return BufferUtils.createFloatBuffer(count);
+ }
+
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/InitTaskRedirectSlickLog.java b/src/mightypork/gamecore/backends/lwjgl/InitTaskRedirectSlickLog.java
new file mode 100644
index 0000000..a951141
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/InitTaskRedirectSlickLog.java
@@ -0,0 +1,38 @@
+package mightypork.gamecore.backends.lwjgl;
+
+
+import mightypork.gamecore.core.InitTask;
+import mightypork.gamecore.core.OptionalInitTask;
+import mightypork.utils.logging.writers.LogWriter;
+
+
+/**
+ * Initializer that redirects slick logging to main logger.
+ *
+ * @author Ondřej Hruška (MightyPork)
+ */
+@OptionalInitTask
+public class InitTaskRedirectSlickLog extends InitTask {
+
+ @Override
+ public void run()
+ {
+ LogWriter ml = mightypork.utils.logging.Log.getMainLogger();
+ SlickLogRedirector slr = new SlickLogRedirector(ml);
+ org.newdawn.slick.util.Log.setLogSystem(slr);
+ }
+
+
+ @Override
+ public String getName()
+ {
+ return "slick_log";
+ }
+
+
+ @Override
+ public String[] getDependencies()
+ {
+ return new String[] { "log" };
+ }
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/LwjglBackend.java b/src/mightypork/gamecore/backends/lwjgl/LwjglBackend.java
new file mode 100644
index 0000000..d11b58b
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/LwjglBackend.java
@@ -0,0 +1,58 @@
+package mightypork.gamecore.backends.lwjgl;
+
+
+import mightypork.gamecore.audio.AudioModule;
+import mightypork.gamecore.backends.lwjgl.audio.SlickAudioModule;
+import mightypork.gamecore.backends.lwjgl.graphics.LwjglGraphicsModule;
+import mightypork.gamecore.core.AppBackend;
+import mightypork.gamecore.graphics.GraphicsModule;
+import mightypork.gamecore.input.InputModule;
+
+
+/**
+ * Game backend using LWJGL and SlickUtil
+ *
+ * @author MightyPork
+ */
+public class LwjglBackend extends AppBackend {
+
+ private LwjglGraphicsModule graphics;
+ private SlickAudioModule audio;
+ private LwjglInputModule input;
+
+
+ @Override
+ public void initialize()
+ {
+ addChildClient(graphics = new LwjglGraphicsModule());
+ addChildClient(audio = new SlickAudioModule());
+ addChildClient(input = new LwjglInputModule());
+
+ graphics.init();
+ audio.init();
+ input.init();
+
+ app.addInitTask(new InitTaskRedirectSlickLog());
+ }
+
+
+ @Override
+ public GraphicsModule getGraphics()
+ {
+ return graphics;
+ }
+
+
+ @Override
+ public AudioModule getAudio()
+ {
+ return audio;
+ }
+
+
+ @Override
+ public InputModule getInput()
+ {
+ return input;
+ }
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/LwjglInputModule.java b/src/mightypork/gamecore/backends/lwjgl/LwjglInputModule.java
new file mode 100644
index 0000000..698a676
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/LwjglInputModule.java
@@ -0,0 +1,302 @@
+package mightypork.gamecore.backends.lwjgl;
+
+
+import mightypork.gamecore.core.App;
+import mightypork.gamecore.input.InputModule;
+import mightypork.gamecore.input.Key;
+import mightypork.gamecore.input.Keys;
+import mightypork.gamecore.input.events.KeyEvent;
+import mightypork.gamecore.input.events.MouseButtonEvent;
+import mightypork.gamecore.input.events.MouseMotionEvent;
+import mightypork.utils.interfaces.Updateable;
+import mightypork.utils.math.constraints.vect.Vect;
+import mightypork.utils.math.constraints.vect.var.VectVar;
+
+import org.lwjgl.LWJGLException;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.Display;
+
+
+/**
+ * Lwjgl Input Module.
+ *
+ * @author Ondřej Hruška (MightyPork)
+ */
+public class LwjglInputModule extends InputModule implements Updateable {
+
+ /** Current mouse position */
+ private static final Vect mousePos = new Vect() {
+
+ @Override
+ public double x()
+ {
+ if (!Mouse.isInsideWindow()) return Integer.MIN_VALUE;
+
+ return Mouse.getX();
+ }
+
+
+ @Override
+ public double y()
+ {
+ if (!Mouse.isInsideWindow()) return Integer.MIN_VALUE;
+ // flip Y axis
+ return Display.getHeight() - Mouse.getY();
+ }
+ };
+
+
+ @Override
+ protected void initDevices()
+ {
+ try {
+ Mouse.create();
+ Keyboard.create();
+ Keyboard.enableRepeatEvents(false);
+ } catch (final LWJGLException e) {
+ throw new RuntimeException("Failed to initialize input devices.", e);
+ }
+ }
+
+
+ @Override
+ protected void initKeyCodes()
+ {
+ Keys.NONE.setCode(Keyboard.KEY_NONE);
+
+ Keys.NUM_1.setCode(Keyboard.KEY_1);
+ Keys.NUM_2.setCode(Keyboard.KEY_2);
+ Keys.NUM_3.setCode(Keyboard.KEY_3);
+ Keys.NUM_4.setCode(Keyboard.KEY_4);
+ Keys.NUM_5.setCode(Keyboard.KEY_5);
+ Keys.NUM_6.setCode(Keyboard.KEY_6);
+ Keys.NUM_7.setCode(Keyboard.KEY_7);
+ Keys.NUM_8.setCode(Keyboard.KEY_8);
+ Keys.NUM_9.setCode(Keyboard.KEY_9);
+ Keys.NUM_0.setCode(Keyboard.KEY_0);
+
+ Keys.Q.setCode(Keyboard.KEY_Q);
+ Keys.W.setCode(Keyboard.KEY_W);
+ Keys.E.setCode(Keyboard.KEY_E);
+ Keys.R.setCode(Keyboard.KEY_R);
+ Keys.T.setCode(Keyboard.KEY_T);
+ Keys.Y.setCode(Keyboard.KEY_Y);
+ Keys.U.setCode(Keyboard.KEY_U);
+ Keys.I.setCode(Keyboard.KEY_I);
+ Keys.O.setCode(Keyboard.KEY_O);
+ Keys.P.setCode(Keyboard.KEY_P);
+ Keys.A.setCode(Keyboard.KEY_A);
+ Keys.S.setCode(Keyboard.KEY_S);
+ Keys.D.setCode(Keyboard.KEY_D);
+ Keys.F.setCode(Keyboard.KEY_F);
+ Keys.G.setCode(Keyboard.KEY_G);
+ Keys.H.setCode(Keyboard.KEY_H);
+ Keys.J.setCode(Keyboard.KEY_J);
+ Keys.K.setCode(Keyboard.KEY_K);
+ Keys.L.setCode(Keyboard.KEY_L);
+ Keys.Z.setCode(Keyboard.KEY_Z);
+ Keys.X.setCode(Keyboard.KEY_X);
+ Keys.C.setCode(Keyboard.KEY_C);
+ Keys.V.setCode(Keyboard.KEY_V);
+ Keys.B.setCode(Keyboard.KEY_B);
+ Keys.N.setCode(Keyboard.KEY_N);
+ Keys.M.setCode(Keyboard.KEY_M);
+
+ Keys.MINUS.setCode(Keyboard.KEY_MINUS);
+ Keys.EQUALS.setCode(Keyboard.KEY_EQUALS);
+ Keys.SLASH.setCode(Keyboard.KEY_SLASH);
+ Keys.BACKSLASH.setCode(Keyboard.KEY_BACKSLASH);
+ Keys.BRACKET_LEFT.setCode(Keyboard.KEY_LBRACKET);
+ Keys.BRACKET_RIGHT.setCode(Keyboard.KEY_RBRACKET);
+ Keys.SEMICOLON.setCode(Keyboard.KEY_SEMICOLON);
+ Keys.APOSTROPHE.setCode(Keyboard.KEY_APOSTROPHE);
+ Keys.GRAVE.setCode(Keyboard.KEY_GRAVE);
+ Keys.COMMA.setCode(Keyboard.KEY_COMMA);
+ Keys.PERIOD.setCode(Keyboard.KEY_PERIOD);
+
+ Keys.SPACE.setCode(Keyboard.KEY_SPACE);
+ Keys.BACKSPACE.setCode(Keyboard.KEY_BACK);
+ Keys.TAB.setCode(Keyboard.KEY_TAB);
+ Keys.ESCAPE.setCode(Keyboard.KEY_ESCAPE);
+
+ Keys.APPS.setCode(Keyboard.KEY_APPS);
+ Keys.POWER.setCode(Keyboard.KEY_POWER);
+ Keys.SLEEP.setCode(Keyboard.KEY_SLEEP);
+ //Keys.MENU.setCode(Keyboard.KEY_MENU); // not defined
+
+ Keys.F1.setCode(Keyboard.KEY_F1);
+ Keys.F2.setCode(Keyboard.KEY_F2);
+ Keys.F3.setCode(Keyboard.KEY_F3);
+ Keys.F4.setCode(Keyboard.KEY_F4);
+ Keys.F5.setCode(Keyboard.KEY_F5);
+ Keys.F6.setCode(Keyboard.KEY_F6);
+ Keys.F7.setCode(Keyboard.KEY_F7);
+ Keys.F8.setCode(Keyboard.KEY_F8);
+ Keys.F9.setCode(Keyboard.KEY_F9);
+ Keys.F10.setCode(Keyboard.KEY_F10);
+ Keys.F11.setCode(Keyboard.KEY_F11);
+ Keys.F12.setCode(Keyboard.KEY_F12);
+ Keys.F13.setCode(Keyboard.KEY_F13);
+ Keys.F14.setCode(Keyboard.KEY_F14);
+ Keys.F15.setCode(Keyboard.KEY_F15);
+
+ Keys.CAPS_LOCK.setCode(Keyboard.KEY_CAPITAL);
+ Keys.SCROLL_LOCK.setCode(Keyboard.KEY_SCROLL);
+ Keys.NUM_LOCK.setCode(Keyboard.KEY_NUMLOCK);
+
+ Keys.NUMPAD_MINUS.setCode(Keyboard.KEY_SUBTRACT);
+ Keys.NUMPAD_PLUSS.setCode(Keyboard.KEY_ADD);
+ Keys.NUMPAD_0.setCode(Keyboard.KEY_NUMPAD0);
+ Keys.NUMPAD_1.setCode(Keyboard.KEY_NUMPAD1);
+ Keys.NUMPAD_2.setCode(Keyboard.KEY_NUMPAD2);
+ Keys.NUMPAD_3.setCode(Keyboard.KEY_NUMPAD3);
+ Keys.NUMPAD_4.setCode(Keyboard.KEY_NUMPAD4);
+ Keys.NUMPAD_5.setCode(Keyboard.KEY_NUMPAD5);
+ Keys.NUMPAD_6.setCode(Keyboard.KEY_NUMPAD6);
+ Keys.NUMPAD_7.setCode(Keyboard.KEY_NUMPAD7);
+ Keys.NUMPAD_8.setCode(Keyboard.KEY_NUMPAD8);
+ Keys.NUMPAD_9.setCode(Keyboard.KEY_NUMPAD9);
+ Keys.NUMPAD_DECIMAL.setCode(Keyboard.KEY_DECIMAL);
+ Keys.NUMPAD_ENTER.setCode(Keyboard.KEY_NUMPADENTER);
+ Keys.NUMPAD_DIVIDE.setCode(Keyboard.KEY_DIVIDE);
+ Keys.NUMPAD_MULTIPLY.setCode(Keyboard.KEY_MULTIPLY);
+
+ Keys.CONTROL_LEFT.setCode(Keyboard.KEY_LCONTROL);
+ Keys.CONTROL_RIGHT.setCode(Keyboard.KEY_RCONTROL);
+ Keys.ALT_LEFT.setCode(Keyboard.KEY_LMENU);
+ Keys.ALT_RIGHT.setCode(Keyboard.KEY_RMENU);
+ Keys.SHIFT_LEFT.setCode(Keyboard.KEY_LSHIFT);
+ Keys.SHIFT_RIGHT.setCode(Keyboard.KEY_RSHIFT);
+ Keys.META_LEFT.setCode(Keyboard.KEY_LMETA);
+ Keys.META_RIGHT.setCode(Keyboard.KEY_RMETA);
+
+ Keys.UP.setCode(Keyboard.KEY_UP);
+ Keys.DOWN.setCode(Keyboard.KEY_DOWN);
+ Keys.LEFT.setCode(Keyboard.KEY_LEFT);
+ Keys.RIGHT.setCode(Keyboard.KEY_RIGHT);
+
+ Keys.HOME.setCode(Keyboard.KEY_HOME);
+ Keys.END.setCode(Keyboard.KEY_END);
+
+ Keys.PAGE_UP.setCode(Keyboard.KEY_PRIOR);
+ Keys.PAGE_DOWN.setCode(Keyboard.KEY_NEXT);
+
+ Keys.RETURN.setCode(Keyboard.KEY_RETURN);
+ Keys.PAUSE.setCode(Keyboard.KEY_PAUSE);
+ Keys.INSERT.setCode(Keyboard.KEY_INSERT);
+ Keys.DELETE.setCode(Keyboard.KEY_DELETE);
+ Keys.SYSRQ.setCode(Keyboard.KEY_SYSRQ);
+ }
+
+
+ @Override
+ public void destroy()
+ {
+ Mouse.destroy();
+ Keyboard.destroy();
+ }
+
+ private final VectVar mouseMove = Vect.makeVar();
+ private final VectVar mouseLastPos = Vect.makeVar();
+
+
+ @Override
+ public synchronized void update(double delta)
+ {
+ // was destroyed or not initialized
+ if (!Display.isCreated()) return;
+ if (!Mouse.isCreated()) return;
+ if (!Keyboard.isCreated()) return;
+
+ Display.processMessages();
+
+ // sum the moves
+ mouseMove.reset();
+ mouseLastPos.reset();
+ boolean wasMouse = false;
+ while (Mouse.next()) {
+ onMouseEvent(mouseMove, mouseLastPos);
+ wasMouse = true;
+ }
+
+ if (wasMouse && !mouseMove.isZero()) {
+ App.bus().send(new MouseMotionEvent(mouseLastPos, mouseMove));
+ }
+
+ while (Keyboard.next()) {
+ onKeyEvent();
+ }
+
+ if (Display.isCloseRequested()) {
+ App.shutdown();
+ }
+ }
+
+
+ private void onMouseEvent(VectVar moveSum, VectVar lastPos)
+ {
+ final int button = Mouse.getEventButton();
+ final boolean down = Mouse.getEventButtonState();
+
+ final VectVar pos = Vect.makeVar(Mouse.getEventX(), Mouse.getEventY());
+ final VectVar move = Vect.makeVar(Mouse.getEventDX(), Mouse.getEventDY());
+
+ final int wheeld = Mouse.getEventDWheel();
+
+ // flip Y axis
+ pos.setY(Display.getHeight() - pos.y());
+
+ if (button != -1 || wheeld != 0) {
+ App.bus().send(new MouseButtonEvent(pos.freeze(), button, down, wheeld));
+ }
+
+ moveSum.setTo(moveSum.add(move));
+ lastPos.setTo(pos);
+ }
+
+
+ private void onKeyEvent()
+ {
+ final int key = Keyboard.getEventKey();
+ final boolean down = Keyboard.getEventKeyState();
+ final char c = Keyboard.getEventCharacter();
+
+ App.bus().send(new KeyEvent(key, c, down));
+ }
+
+
+ @Override
+ public Vect getMousePos()
+ {
+ return mousePos;
+ }
+
+
+ @Override
+ public boolean isMouseInside()
+ {
+ return Mouse.isInsideWindow();
+ }
+
+
+ @Override
+ public void grabMouse(boolean grab)
+ {
+ Mouse.setGrabbed(grab);
+ }
+
+
+ @Override
+ public boolean isKeyDown(Key key)
+ {
+ return key.isDefined() && Keyboard.isKeyDown(key.getCode());
+ }
+
+
+ @Override
+ public boolean isMouseButtonDown(int button)
+ {
+ return Mouse.isButtonDown(button);
+ }
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/SlickLogRedirector.java b/src/mightypork/gamecore/backends/lwjgl/SlickLogRedirector.java
new file mode 100644
index 0000000..3aec98f
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/SlickLogRedirector.java
@@ -0,0 +1,75 @@
+package mightypork.gamecore.backends.lwjgl;
+
+
+import java.util.logging.Level;
+
+import mightypork.utils.logging.writers.LogWriter;
+
+
+/**
+ * Used to redirect slick log into main logger.
+ *
+ * @author Ondřej Hruška (MightyPork)
+ */
+class SlickLogRedirector implements org.newdawn.slick.util.LogSystem {
+
+ LogWriter writer;
+
+
+ /**
+ * @param log log to redirect into
+ */
+ public SlickLogRedirector(LogWriter log) {
+ this.writer = log;
+ }
+
+
+ @Override
+ public void error(String msg, Throwable e)
+ {
+ writer.log(Level.SEVERE, msg, e);
+ }
+
+
+ @Override
+ public void error(Throwable e)
+ {
+ writer.log(Level.SEVERE, null, e);
+ }
+
+
+ @Override
+ public void error(String msg)
+ {
+ writer.log(Level.SEVERE, msg);
+ }
+
+
+ @Override
+ public void warn(String msg)
+ {
+ writer.log(Level.WARNING, msg);
+ }
+
+
+ @Override
+ public void warn(String msg, Throwable e)
+ {
+ writer.log(Level.WARNING, msg, e);
+ }
+
+
+ @Override
+ public void info(String msg)
+ {
+ writer.log(Level.INFO, msg);
+ }
+
+
+ @Override
+ public void debug(String msg)
+ {
+ writer.log(Level.FINEST, msg);
+ }
+
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/audio/SlickAudio.java b/src/mightypork/gamecore/backends/lwjgl/audio/SlickAudio.java
new file mode 100644
index 0000000..2928712
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/audio/SlickAudio.java
@@ -0,0 +1,146 @@
+package mightypork.gamecore.backends.lwjgl.audio;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import mightypork.gamecore.audio.DeferredAudio;
+import mightypork.utils.files.FileUtil;
+
+import org.lwjgl.openal.AL10;
+import org.newdawn.slick.openal.Audio;
+import org.newdawn.slick.openal.SoundStore;
+
+
+/**
+ * SlickUtil-based deferred audio resource.
+ *
+ * @author Ondřej Hruška (MightyPork)
+ */
+public class SlickAudio extends DeferredAudio {
+
+ private double pauseLoopPosition = 0;
+ private boolean looping = false;
+ private boolean paused = false;
+ private double lastPlayPitch = 1;
+ private double lastPlayGain = 1;
+
+ /** Audio resource */
+ private Audio backingAudio = null;
+ private int sourceID;
+
+
+ public SlickAudio(String resourceName) {
+ super(resourceName);
+ }
+
+
+ @Override
+ protected void loadResource(String resource) throws IOException
+ {
+ final String ext = FileUtil.getExtension(resource);
+
+ try (final InputStream stream = FileUtil.getResource(resource)) {
+
+ if (ext.equalsIgnoreCase("ogg")) {
+ backingAudio = SoundStore.get().getOgg(resource, stream);
+
+ } else if (ext.equalsIgnoreCase("wav")) {
+ backingAudio = SoundStore.get().getWAV(resource, stream);
+
+ } else if (ext.equalsIgnoreCase("aif")) {
+ backingAudio = SoundStore.get().getAIF(resource, stream);
+
+ } else if (ext.equalsIgnoreCase("mod")) {
+ backingAudio = SoundStore.get().getMOD(resource, stream);
+
+ } else {
+ throw new RuntimeException("Invalid audio file extension.");
+ }
+ }
+ }
+
+
+ @Override
+ public void pauseLoop()
+ {
+ if (!ensureLoaded()) return;
+
+ if (isPlaying() && looping) {
+ pauseLoopPosition = backingAudio.getPosition();
+ stop();
+ paused = true;
+ }
+ }
+
+
+ @Override
+ public void resumeLoop()
+ {
+ if (!ensureLoaded()) return;
+
+ if (looping && paused) {
+ sourceID = backingAudio.playAsSoundEffect((float) lastPlayPitch, (float) lastPlayGain, true);
+ backingAudio.setPosition((float) pauseLoopPosition);
+ paused = false;
+ }
+ }
+
+
+ @Override
+ public void adjustGain(double gain)
+ {
+ AL10.alSourcef(sourceID, AL10.AL_GAIN, (float) gain);
+ }
+
+
+ @Override
+ public void stop()
+ {
+ if (!isLoaded()) return;
+
+ backingAudio.stop();
+ paused = false;
+ }
+
+
+ @Override
+ public boolean isPlaying()
+ {
+ if (!isLoaded()) return false;
+
+ return backingAudio.isPlaying();
+ }
+
+
+ @Override
+ public boolean isPaused()
+ {
+ if (!isLoaded()) return false;
+
+ return backingAudio.isPaused();
+ }
+
+
+ @Override
+ public void play(double pitch, double gain, boolean loop, double x, double y, double z)
+ {
+ if (!ensureLoaded()) return;
+
+ this.lastPlayPitch = pitch;
+ this.lastPlayGain = gain;
+ looping = loop;
+
+ sourceID = backingAudio.playAsSoundEffect((float) pitch, (float) gain, loop, (float) x, (float) y, (float) z);
+ }
+
+
+ @Override
+ public void destroy()
+ {
+ if (!isLoaded() || backingAudio == null) return;
+
+ backingAudio.release();
+ backingAudio = null;
+ }
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/audio/SlickAudioModule.java b/src/mightypork/gamecore/backends/lwjgl/audio/SlickAudioModule.java
new file mode 100644
index 0000000..87384ca
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/audio/SlickAudioModule.java
@@ -0,0 +1,75 @@
+package mightypork.gamecore.backends.lwjgl.audio;
+
+
+import java.nio.FloatBuffer;
+
+import mightypork.gamecore.audio.AudioModule;
+import mightypork.gamecore.audio.DeferredAudio;
+import mightypork.gamecore.backends.lwjgl.BufferHelper;
+import mightypork.utils.math.constraints.vect.Vect;
+import mightypork.utils.math.constraints.vect.var.VectVar;
+
+import org.lwjgl.openal.AL;
+import org.lwjgl.openal.AL10;
+import org.newdawn.slick.openal.SoundStore;
+
+
+/**
+ * SlickUtil-based audio module
+ *
+ * @author Ondřej Hruška (MightyPork)
+ */
+public class SlickAudioModule extends AudioModule {
+
+ private final VectVar listenerPos = Vect.makeVar();
+
+
+ @Override
+ public void init()
+ {
+ SoundStore.get().setMaxSources(256);
+ SoundStore.get().init();
+ setListenerPos(Vect.ZERO);
+ }
+
+
+ @Override
+ public void setListenerPos(Vect pos)
+ {
+ listenerPos.setTo(pos);
+ final FloatBuffer buf3 = BufferHelper.alloc(3);
+ final FloatBuffer buf6 = BufferHelper.alloc(6);
+ buf3.clear();
+ BufferHelper.fill(buf3, (float) pos.x(), (float) pos.y(), (float) pos.z());
+ AL10.alListener(AL10.AL_POSITION, buf3);
+ buf3.clear();
+ BufferHelper.fill(buf3, 0, 0, 0);
+ AL10.alListener(AL10.AL_VELOCITY, buf3);
+ buf6.clear();
+ BufferHelper.fill(buf6, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f);
+ AL10.alListener(AL10.AL_ORIENTATION, buf6);
+ }
+
+
+ @Override
+ public Vect getListenerPos()
+ {
+ return listenerPos;
+ }
+
+
+ @Override
+ protected void deinitSoundSystem()
+ {
+ SoundStore.get().clear();
+ AL.destroy();
+ }
+
+
+ @Override
+ protected DeferredAudio doCreateResource(String res)
+ {
+ return new SlickAudio(res);
+ }
+
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/graphics/AwtScreenshot.java b/src/mightypork/gamecore/backends/lwjgl/graphics/AwtScreenshot.java
new file mode 100644
index 0000000..fa11ce7
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/graphics/AwtScreenshot.java
@@ -0,0 +1,84 @@
+package mightypork.gamecore.backends.lwjgl.graphics;
+
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.imageio.ImageIO;
+
+import mightypork.gamecore.graphics.Screenshot;
+
+
+/**
+ * Screenshot object, can be used to extract image or write to file.
+ * Screenshot, once taken, can be safely processed in separate thread.
+ * 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.
+ * 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.
+ * 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);
+ }
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/graphics/LwjglGraphicsModule.java b/src/mightypork/gamecore/backends/lwjgl/graphics/LwjglGraphicsModule.java
new file mode 100644
index 0000000..59ff484
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/graphics/LwjglGraphicsModule.java
@@ -0,0 +1,620 @@
+package mightypork.gamecore.backends.lwjgl.graphics;
+
+
+import static org.lwjgl.opengl.GL11.*;
+
+import java.nio.ByteBuffer;
+import java.util.Stack;
+
+import mightypork.gamecore.core.App;
+import mightypork.gamecore.graphics.GraphicsModule;
+import mightypork.gamecore.graphics.Screenshot;
+import mightypork.gamecore.graphics.textures.DeferredTexture;
+import mightypork.gamecore.graphics.textures.TxQuad;
+import mightypork.gamecore.gui.events.ViewportChangeEvent;
+import mightypork.utils.logging.Log;
+import mightypork.utils.math.color.Color;
+import mightypork.utils.math.color.Grad;
+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;
+
+import org.lwjgl.BufferUtils;
+import org.lwjgl.LWJGLException;
+import org.lwjgl.opengl.Display;
+import org.lwjgl.opengl.DisplayMode;
+import org.lwjgl.opengl.GL11;
+
+
+/**
+ * LWJGL rendering module
+ *
+ * @author MightyPork
+ */
+public class LwjglGraphicsModule extends GraphicsModule {
+
+ /** Currently binded color */
+ private Color activeColor = null;
+ /** Currently binded color's alpha multiplier */
+ private double activeColorAlpha = 1;
+ /** Stack of pushed colors */
+ private Stack colorPushStack = new Stack<>();
+ /** Currently binded texture */
+ private SlickTexture 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 = new 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 init()
+ {
+ try {
+ Display.create();
+ } catch (final Exception e) {
+ throw new RuntimeException("Could not initialize display.", e);
+ }
+ }
+
+
+ @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 = (SlickTexture) 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 = activeTexture.getWidth01();
+ final double h = activeTexture.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()
+ {
+ // fix projection for changed size
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ int w = Display.getWidth();
+ int h = Display.getHeight();
+
+ glViewport(0, 0, w, h);
+ glOrtho(0, w, h, 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 DeferredTexture getLazyTexture(String path)
+ {
+ return new SlickTexture(path);
+ }
+
+
+ @Override
+ public void destroy()
+ {
+ Display.destroy();
+ }
+
+
+ @Override
+ public void setTargetFps(int fps)
+ {
+ this.targetFps = fps;
+ }
+
+
+ @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();
+ }
+
+ App.bus().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 void beginFrame()
+ {
+ // handle resize
+ if (Display.wasResized()) {
+ App.bus().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;
+ }
+
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/graphics/SlickTexture.java b/src/mightypork/gamecore/backends/lwjgl/graphics/SlickTexture.java
new file mode 100644
index 0000000..2498c4b
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/graphics/SlickTexture.java
@@ -0,0 +1,194 @@
+package mightypork.gamecore.backends.lwjgl.graphics;
+
+
+import java.io.IOException;
+
+import mightypork.gamecore.graphics.textures.DeferredTexture;
+import mightypork.gamecore.resources.loading.MustLoadInRenderingContext;
+import mightypork.utils.annotations.Alias;
+import mightypork.utils.exceptions.IllegalValueException;
+import mightypork.utils.files.FileUtil;
+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")
+@MustLoadInRenderingContext
+public class SlickTexture extends DeferredTexture {
+
+ private org.newdawn.slick.opengl.Texture backingTexture;
+ private boolean alpha;
+ private boolean alphal;
+
+
+ /**
+ * @param resourcePath resource path
+ */
+ public SlickTexture(String resourcePath) {
+ super(resourcePath);
+ }
+
+
+ @Override
+ protected synchronized void loadResource(String path)
+ {
+ try {
+ final String ext = FileUtil.getExtension(path).toUpperCase();
+
+ final int filtering;
+ switch (filter) {
+ case NEAREST:
+ filtering = GL11.GL_NEAREST;
+ break;
+ case LINEAR:
+ filtering = GL11.GL_LINEAR;
+ break;
+ default:
+ throw new IllegalValueException("Unsupported filtering mode.");
+ }
+
+ final Texture texture = TextureLoader.getTexture(ext, FileUtil.getResource(path), false, filtering);
+
+ 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;
+ }
+
+
+ /**
+ * Bind to GL context, applying the filters prescribed.
+ */
+ 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);
+
+ final int wrapping;
+ switch (wrap) {
+ case CLAMP:
+ wrapping = GL11.GL_CLAMP;
+ break;
+ case REPEAT:
+ wrapping = GL11.GL_REPEAT;
+ break;
+ default:
+ throw new IllegalValueException("Unsupported wrapping mode.");
+ }
+
+ GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrapping);
+ GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrapping);
+
+ final int filtering;
+ switch (filter) {
+ case NEAREST:
+ filtering = GL11.GL_NEAREST;
+ break;
+ case LINEAR:
+ filtering = GL11.GL_LINEAR;
+ break;
+ default:
+ throw new IllegalValueException("Unsupported filtering mode.");
+ }
+
+ GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filtering);
+ GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filtering);
+ }
+
+
+ @Override
+ public int getImageHeight()
+ {
+ if (!ensureLoaded()) return 0;
+
+ return backingTexture.getImageHeight();
+ }
+
+
+ @Override
+ public int getImageWidth()
+ {
+ if (!ensureLoaded()) return 0;
+
+ return backingTexture.getImageWidth();
+ }
+
+
+ @Override
+ public void destroy()
+ {
+ if (!isLoaded()) return;
+
+ backingTexture.release();
+ }
+
+
+ /**
+ * Get the height of the texture, 0..1.
+ *
+ * @return height 0..1
+ */
+ public float getHeight01()
+ {
+ if (!ensureLoaded()) return 0;
+
+ return backingTexture.getHeight();
+ }
+
+
+ /**
+ * Get the width of the texture, 0..1.
+ *
+ * @return width 0..1
+ */
+ public float getWidth01()
+ {
+ if (!ensureLoaded()) return 0;
+
+ return backingTexture.getWidth();
+ }
+
+
+ /**
+ * @return OpenGL texture ID
+ */
+ public int getTextureID()
+ {
+ if (!ensureLoaded()) return -1;
+
+ return backingTexture.getTextureID();
+ }
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFont.java b/src/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFont.java
new file mode 100644
index 0000000..127b203
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFont.java
@@ -0,0 +1,143 @@
+package mightypork.gamecore.backends.lwjgl.graphics.font;
+
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import mightypork.gamecore.graphics.fonts.DeferredFont;
+import mightypork.gamecore.graphics.fonts.IFont;
+import mightypork.gamecore.resources.loading.MustLoadInRenderingContext;
+import mightypork.utils.annotations.Alias;
+import mightypork.utils.files.FileUtil;
+import mightypork.utils.math.color.Color;
+import mightypork.utils.math.constraints.vect.Vect;
+
+
+/**
+ * Font obtained from a resource file (TTF).
+ *
+ * @author Ondřej Hruška (MightyPork)
+ */
+@MustLoadInRenderingContext
+@Alias(name = "Font")
+public class DeferredLwjglFont extends DeferredFont {
+
+ private IFont font = null;
+
+
+ /**
+ * A font from resource
+ *
+ * @param resourcePath resource to load
+ * @param chars chars to load; null to load basic chars only
+ * @param size size (px)
+ */
+ public DeferredLwjglFont(String resourcePath, String chars, double size) {
+ super(resourcePath);
+ this.size = size;
+ this.chars = chars;
+ }
+
+
+ @Override
+ protected synchronized final void loadResource(String path) throws IOException
+ {
+ final Font awtFont = getAwtFont(path, (float) size, style.numval);
+
+ font = new LwjglTextureBackedFont(awtFont, antialias, filter, chars);
+ font.setDiscardRatio(discardTop, discardBottom);
+ }
+
+
+ /**
+ * Get a font for a resource path / name
+ *
+ * @param resource resource to load
+ * @param size font size (pt)
+ * @param style font style
+ * @return the {@link Font}
+ * @throws IOException
+ */
+ protected Font getAwtFont(String resource, float size, int style) throws IOException
+ {
+ try (InputStream in = FileUtil.getResource(resource)) {
+
+ Font awtFont = Font.createFont(Font.TRUETYPE_FONT, in);
+
+ awtFont = awtFont.deriveFont(size);
+ awtFont = awtFont.deriveFont(style);
+
+ return awtFont;
+ } catch (final FontFormatException e) {
+ throw new IOException("Could not load font, bad format.", e);
+ }
+ }
+
+
+ /**
+ * Draw string
+ *
+ * @param str string to draw
+ * @param color draw color
+ */
+ @Override
+ public void draw(String str, Color color)
+ {
+ if (!ensureLoaded()) return;
+
+ font.draw(str, color);
+ }
+
+
+ /**
+ * Get size needed to render give string
+ *
+ * @param text string to check
+ * @return coord (width, height)
+ */
+ @Override
+ public Vect getNeededSpace(String text)
+ {
+ if (!ensureLoaded()) return Vect.ZERO;
+
+ return font.getNeededSpace(text);
+ }
+
+
+ /**
+ * @return font height
+ */
+ @Override
+ public int getLineHeight()
+ {
+ if (!ensureLoaded()) return 0;
+
+ return font.getLineHeight();
+ }
+
+
+ @Override
+ public int getFontSize()
+ {
+ if (!ensureLoaded()) return 0;
+
+ return font.getFontSize();
+ }
+
+
+ @Override
+ public int getWidth(String text)
+ {
+ return font.getWidth(text);
+ }
+
+
+ @Override
+ public void destroy()
+ {
+ // this will have to suffice
+ font = null;
+ }
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFontFromSystem.java b/src/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFontFromSystem.java
new file mode 100644
index 0000000..841b15b
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/graphics/font/DeferredLwjglFontFromSystem.java
@@ -0,0 +1,36 @@
+package mightypork.gamecore.backends.lwjgl.graphics.font;
+
+
+import java.awt.Font;
+import java.io.IOException;
+
+import mightypork.utils.annotations.Alias;
+
+
+/**
+ * Font obtained from the OS
+ *
+ * @author Ondřej Hruška (MightyPork)
+ */
+@Alias(name = "FontNative")
+public class DeferredLwjglFontFromSystem extends DeferredLwjglFont {
+
+ /**
+ * A font from OS, found by name
+ *
+ * @param fontName font family name
+ * @param chars chars to load; null to load basic chars only
+ * @param size size (pt)
+ */
+ public DeferredLwjglFontFromSystem(String fontName, String chars, double size) {
+ super(fontName, chars, size);
+ }
+
+
+ @Override
+ protected Font getAwtFont(String resource, float size, int style) throws IOException
+ {
+ return new Font(resource, style, (int) size);
+ }
+
+}
diff --git a/src/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont.java b/src/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont.java
new file mode 100644
index 0000000..6042349
--- /dev/null
+++ b/src/mightypork/gamecore/backends/lwjgl/graphics/font/LwjglTextureBackedFont.java
@@ -0,0 +1,494 @@
+package mightypork.gamecore.backends.lwjgl.graphics.font;
+
+
+import static org.lwjgl.opengl.GL11.*;
+
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import mightypork.gamecore.graphics.fonts.IFont;
+import mightypork.gamecore.graphics.textures.FilterMode;
+import mightypork.utils.exceptions.IllegalValueException;
+import mightypork.utils.logging.Log;
+import mightypork.utils.math.color.Color;
+import mightypork.utils.math.constraints.vect.Vect;
+import mightypork.utils.math.constraints.vect.VectConst;
+
+import org.lwjgl.BufferUtils;
+import org.lwjgl.util.glu.GLU;
+import org.newdawn.slick.opengl.GLUtils;
+
+
+/**
+ * A TrueType font renderer with backing texture.
+ *
+ * @author James Chambers (Jimmy)
+ * @author Jeremy Adams (elias4444)
+ * @author Kevin Glass (kevglass)
+ * @author Peter Korzuszek (genail)
+ * @author David Aaron Muhar (bobjob)
+ * @author Ondřej Hruška (MightyPork)
+ */
+public class LwjglTextureBackedFont implements IFont {
+
+ private class CharTile {
+
+ public int width;
+ public int height;
+ public int texPosX;
+ public int texPosY;
+ }
+
+ /* char bank */
+ private final Map chars = new HashMap<>(255);
+
+ /* use antialiasing for rendering */
+ private final boolean antiAlias;
+
+ /* loaded font size (requested) */
+ private final int fontSize;
+
+ /* actual height of drawn glyphs */
+ private int fontHeight;
+
+ /* texture id */
+ private int textureID;
+
+ /* texture width */
+ private int textureWidth;
+
+ /* texture height */
+ private int textureHeight;
+
+ /* AWT font source */
+ private final java.awt.Font font;
+
+ private final FilterMode filter;
+
+ private double discardTop;
+
+ private double discardBottom;
+
+
+ /**
+ * Make a font
+ *
+ * @param font original awt font to load
+ * @param antialias use antialiasing when rendering to cache texture
+ * @param filter used Gl filter
+ * @param chars chars to load
+ */
+ public LwjglTextureBackedFont(java.awt.Font font, boolean antialias, FilterMode filter, String chars) {
+ this(font, antialias, filter, (" " + chars).toCharArray());
+ }
+
+
+ /**
+ * Make a font
+ *
+ * @param font original awt font to load
+ * @param antialias use antialiasing when rendering to cache texture
+ * @param filter used Gl filter
+ * @param chars chars to load
+ */
+ public LwjglTextureBackedFont(java.awt.Font font, boolean antialias, FilterMode filter, char[] chars) {
+ GLUtils.checkGLContext();
+
+ this.font = font;
+ this.filter = filter;
+ this.fontSize = font.getSize();
+ this.antiAlias = antialias;
+
+ createSet(chars);
+ }
+
+
+ /**
+ * Create a BufferedImage of the given character
+ *
+ * @param ch the character
+ * @return BufferedImage containing the drawn character
+ */
+ private BufferedImage getFontImage(char ch)
+ {
+ FontMetrics metrics;
+ BufferedImage img;
+ Graphics2D g;
+
+ // Create a temporary image to extract the character's size
+ img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+
+ g = (Graphics2D) img.getGraphics();
+ if (antiAlias == true) g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setFont(font);
+
+ metrics = g.getFontMetrics();
+
+ final int charwidth = Math.max(1, metrics.charWidth(ch));
+ final int charheight = Math.max(fontSize, metrics.getHeight());
+
+ // Create another image holding the character we are creating
+ final BufferedImage fontImage = new BufferedImage(charwidth, charheight, BufferedImage.TYPE_INT_ARGB);
+
+ g = (Graphics2D) fontImage.getGraphics();
+ if (antiAlias == true) g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setFont(font);
+ g.setColor(java.awt.Color.WHITE);
+ g.drawString(String.valueOf(ch), 0, metrics.getAscent());
+
+ return fontImage;
+ }
+
+
+ private void createSet(char[] charsToLoad)
+ {
+ try {
+ class LoadedGlyph {
+
+ public char c;
+ public BufferedImage image;
+ public int width;
+ public int height;
+
+
+ public LoadedGlyph(char c, BufferedImage image) {
+ this.image = image;
+ this.c = c;
+ this.width = image.getWidth();
+ this.height = image.getHeight();
+ }
+ }
+
+ final List glyphs = new ArrayList<>();
+ final List loaded = new ArrayList<>();
+ for (final char ch : charsToLoad) {
+ if (!loaded.contains(ch)) {
+ glyphs.add(new LoadedGlyph(ch, getFontImage(ch)));
+ loaded.add(ch);
+ }
+ }
+
+ int lineHeight = 0;
+
+ int beginX = 0, beginY = 0;
+ int canvasW = 128, canvasH = 128;
+
+ boolean needsLarger = false;
+
+ // find smallest 2^x size for texture
+ while (true) {
+ needsLarger = false;
+
+ for (final LoadedGlyph glyph : glyphs) {
+ if (beginX + glyph.width > canvasW) {
+ beginY += lineHeight;
+ lineHeight = 0;
+ beginX = 0;
+ }
+
+ if (lineHeight < glyph.height) {
+ lineHeight = glyph.height;
+ }
+
+ if (beginY + lineHeight > canvasH) {
+ needsLarger = true;
+ break;
+ }
+
+ // draw.
+ beginX += glyph.width;
+ }
+
+ if (needsLarger) {
+ canvasW *= 2;
+ canvasH *= 2;
+ beginX = 0;
+ beginY = 0;
+ lineHeight = 0;
+ } else {
+ Log.f3(String.format("Generating font texture: %d×%d", canvasW, canvasH));
+ break;
+ }
+ }
+
+ textureWidth = canvasW;
+ textureHeight = canvasH;
+
+ BufferedImage imag = new BufferedImage(textureWidth, textureHeight, BufferedImage.TYPE_INT_ARGB);
+ final Graphics2D g = (Graphics2D) imag.getGraphics();
+
+ g.setColor(new java.awt.Color(0, 0, 0, 1));
+ g.fillRect(0, 0, textureWidth, textureHeight);
+
+ int rowHeight = 0, posX = 0, posY = 0;
+
+ for (final LoadedGlyph glyph : glyphs) {
+ final CharTile cht = new CharTile();
+
+ cht.width = glyph.width;
+ cht.height = glyph.height;
+
+ if (posX + cht.width >= textureWidth) {
+ posX = 0;
+ posY += rowHeight;
+ rowHeight = 0;
+ }
+
+ cht.texPosX = posX;
+ cht.texPosY = posY;
+
+ if (cht.height > fontHeight) {
+ fontHeight = cht.height;
+ }
+
+ if (cht.height > rowHeight) {
+ rowHeight = cht.height;
+ }
+
+ // Draw it here
+ g.drawImage(glyph.image, posX, posY, null);
+
+ posX += cht.width;
+
+ chars.put(glyph.c, cht);
+ }
+
+ textureID = loadImage(imag);
+
+ imag = null;
+
+ } catch (final Throwable t) {
+ Log.e("Failed to load font.", t);
+ }
+ }
+
+
+ private int loadImage(BufferedImage bufferedImage)
+ {
+ try {
+ final short width = (short) bufferedImage.getWidth();
+ final short height = (short) bufferedImage.getHeight();
+ final int bpp = (byte) bufferedImage.getColorModel().getPixelSize();
+
+ ByteBuffer byteBuffer;
+ final DataBuffer db = bufferedImage.getData().getDataBuffer();
+ if (db instanceof DataBufferInt) {
+ final int intI[] = ((DataBufferInt) (bufferedImage.getData().getDataBuffer())).getData();
+ final byte newI[] = new byte[intI.length * 4];
+ for (int i = 0; i < intI.length; i++) {
+ final byte b[] = intToByteArray(intI[i]);
+ final int newIndex = i * 4;
+
+ newI[newIndex] = b[1];
+ newI[newIndex + 1] = b[2];
+ newI[newIndex + 2] = b[3];
+ newI[newIndex + 3] = b[0];
+ }
+
+ byteBuffer = ByteBuffer.allocateDirect(width * height * (bpp / 8)).order(ByteOrder.nativeOrder()).put(newI);
+ } else {
+ byteBuffer = ByteBuffer.allocateDirect(width * height * (bpp / 8)).order(ByteOrder.nativeOrder()).put(((DataBufferByte) (bufferedImage.getData().getDataBuffer())).getData());
+ }
+
+ byteBuffer.flip();
+
+ final int internalFormat = GL_RGBA8, format = GL_RGBA;
+ final IntBuffer textureId = BufferUtils.createIntBuffer(1);
+
+ glGenTextures(textureId);
+ glBindTexture(GL_TEXTURE_2D, textureId.get(0));
+
+ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+
+ final int filtering;
+ switch (filter) {
+ case NEAREST:
+ filtering = GL_NEAREST;
+ break;
+ case LINEAR:
+ filtering = GL_LINEAR;
+ break;
+ default:
+ throw new IllegalValueException("Unsupported filtering mode.");
+ }
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
+
+ GLU.gluBuild2DMipmaps(GL_TEXTURE_2D, internalFormat, width, height, format, GL_UNSIGNED_BYTE, byteBuffer);
+ return textureId.get(0);
+
+ } catch (final Throwable t) {
+ Log.e("Failed to load font.", t);
+ }
+
+ return -1;
+ }
+
+
+ private static byte[] intToByteArray(int value)
+ {
+ return new byte[] { (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value };
+ }
+
+
+ /**
+ * Get size needed to draw given string
+ *
+ * @param text drawn text
+ * @return needed width
+ */
+ @Override
+ public int getWidth(String text)
+ {
+ int totalwidth = 0;
+ CharTile ch = null;
+ for (int i = 0; i < text.length(); i++) {
+
+ ch = chars.get(text.charAt(i));
+
+ if (ch != null) totalwidth += ch.width;
+ }
+ return totalwidth;
+ }
+
+
+ @Override
+ public int getLineHeight()
+ {
+ return (int) Math.round(fontHeight * ((1 - discardTop) - discardBottom));
+ }
+
+
+ @Override
+ public int getFontSize()
+ {
+ return fontSize;
+ }
+
+
+ @Override
+ public void draw(String text, Color color)
+ {
+ GLUtils.checkGLContext();
+
+ // PUSH
+ glPushAttrib(GL_ENABLE_BIT);
+
+ glEnable(GL_TEXTURE_2D);
+
+ glBindTexture(GL_TEXTURE_2D, textureID);
+
+ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+
+ final int filtering;
+ switch (filter) {
+ case NEAREST:
+ filtering = GL_NEAREST;
+ break;
+ case LINEAR:
+ filtering = GL_LINEAR;
+ break;
+ default:
+ throw new IllegalValueException("Unsupported filtering mode.");
+ }
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
+
+ glColor4d(color.r(), color.g(), color.b(), color.a());
+
+ glBegin(GL_QUADS);
+
+ CharTile chtx = null;
+ char charCurrent;
+ int minx = 0;
+
+ for (int i = 0; i < text.length(); i++) {
+ charCurrent = text.charAt(i);
+
+ chtx = chars.get(charCurrent);
+
+ if (chtx != null) {
+
+ // draw quad
+
+ final float txmin = chtx.texPosX;
+ final float tymin = chtx.texPosY;
+ final float draw_width = minx + chtx.width - minx;
+ final float draw_height = chtx.height;
+ final float drawy0 = (float) (0f - draw_height * discardTop);
+
+ final float txmin01 = txmin / textureWidth;
+ final float tymin01 = tymin / textureHeight;
+ final float twidth01 = ((chtx.texPosX + chtx.width - txmin) / textureWidth);
+ final float theight01 = ((chtx.texPosY + chtx.height - tymin) / textureHeight);
+
+ glTexCoord2f(txmin01, tymin01);
+ glVertex2f(minx, drawy0);
+
+ glTexCoord2f(txmin01, tymin01 + theight01);
+ glVertex2f(minx, drawy0 + draw_height);
+
+ glTexCoord2f(txmin01 + twidth01, tymin01 + theight01);
+ glVertex2f(minx + draw_width, drawy0 + draw_height);
+
+ glTexCoord2f(txmin01 + twidth01, tymin01);
+ glVertex2f(minx + draw_width, drawy0);
+ minx += chtx.width;
+ }
+ }
+
+ glEnd();
+
+ // POP
+ glPopAttrib();
+ }
+
+
+ @Override
+ public VectConst getNeededSpace(String text)
+ {
+ return Vect.make(getWidth(text), getLineHeight());
+ }
+
+
+ @Override
+ public void setDiscardRatio(double top, double bottom)
+ {
+ discardTop = top;
+ discardBottom = bottom;
+ }
+
+
+ @Override
+ public double getTopDiscardRatio()
+ {
+ return discardTop;
+ }
+
+
+ @Override
+ public double getBottomDiscardRatio()
+ {
+ return discardBottom;
+ }
+}