Selaa lähdekoodia

new formatting, reworked everything

Kajetan Johannes Hammerle 4 vuotta sitten
vanhempi
commit
2e8af64736
34 muutettua tiedostoa jossa 1328 lisäystä ja 1639 poistoa
  1. 0 34
      src/me/hammerle/snuviengine/Game.java
  2. 8 9
      src/me/hammerle/snuviengine/Main.java
  3. 62 0
      src/me/hammerle/snuviengine/TestGame.java
  4. 46 60
      src/me/hammerle/snuviengine/api/ColorRenderer.java
  5. 48 63
      src/me/hammerle/snuviengine/api/DirectTextureRenderer.java
  6. 0 177
      src/me/hammerle/snuviengine/api/Engine.java
  7. 176 220
      src/me/hammerle/snuviengine/api/FontRenderer.java
  8. 11 16
      src/me/hammerle/snuviengine/api/GLHelper.java
  9. 16 0
      src/me/hammerle/snuviengine/api/Game.java
  10. 116 0
      src/me/hammerle/snuviengine/api/Gamepad.java
  11. 0 66
      src/me/hammerle/snuviengine/api/GamepadBinding.java
  12. 0 69
      src/me/hammerle/snuviengine/api/GamepadHandler.java
  13. 0 8
      src/me/hammerle/snuviengine/api/IGame.java
  14. 72 0
      src/me/hammerle/snuviengine/api/Key.java
  15. 0 121
      src/me/hammerle/snuviengine/api/KeyBinding.java
  16. 0 71
      src/me/hammerle/snuviengine/api/KeyHandler.java
  17. 80 0
      src/me/hammerle/snuviengine/api/KeyNames.java
  18. 69 0
      src/me/hammerle/snuviengine/api/Keys.java
  19. 27 37
      src/me/hammerle/snuviengine/api/MatrixStack.java
  20. 127 308
      src/me/hammerle/snuviengine/api/Renderer.java
  21. 125 0
      src/me/hammerle/snuviengine/api/Shader.java
  22. 0 9
      src/me/hammerle/snuviengine/api/ShaderException.java
  23. 22 0
      src/me/hammerle/snuviengine/api/Stats.java
  24. 56 96
      src/me/hammerle/snuviengine/api/Texture.java
  25. 0 9
      src/me/hammerle/snuviengine/api/TextureException.java
  26. 43 72
      src/me/hammerle/snuviengine/api/TextureRenderer.java
  27. 7 132
      src/me/hammerle/snuviengine/api/Timer.java
  28. 25 0
      src/me/hammerle/snuviengine/api/UniformBoolean.java
  29. 154 0
      src/me/hammerle/snuviengine/api/Window.java
  30. 11 24
      src/me/hammerle/snuviengine/shader/fragment.fs
  31. 2 4
      src/me/hammerle/snuviengine/shader/vertex.vs
  32. 12 17
      src/me/hammerle/snuviengine/util/Color.java
  33. 13 17
      src/me/hammerle/snuviengine/util/Rectangle.java
  34. BIN
      tiles.png

+ 0 - 34
src/me/hammerle/snuviengine/Game.java

@@ -1,34 +0,0 @@
-package me.hammerle.snuviengine;
-
-import me.hammerle.snuviengine.api.Engine;
-import me.hammerle.snuviengine.api.GamepadBinding;
-import me.hammerle.snuviengine.api.GamepadHandler;
-import me.hammerle.snuviengine.api.IGame;
-import me.hammerle.snuviengine.api.Renderer;
-
-public class Game implements IGame
-{
-    @Override
-    public void tick()
-    {        
-    }
-    
-    @Override
-    public void renderTick(Renderer r, float lag)
-    { 
-        float y = 10;
-        r.setColorEnabled(true);
-        r.setTextureEnabled(true);
-        y = r.getFontRenderer().drawString(10, y, true, String.format("FPS: %.1f", Engine.getFramesPerSecond()));
-        y = r.getFontRenderer().drawString(10, y, true, String.format("TPS: %.1f", Engine.getTicksPerSecond()));
-        for(GamepadBinding binding : GamepadHandler.BINDINGS)
-        {
-            y = r.getFontRenderer().drawString(10, y, true, String.format("%s: %b, %d, %b", binding.getName(), binding.isDown(), binding.getTime(), binding.isReleased()));
-        }
-    }
-
-    @Override
-    public void onStop()
-    {
-    }
-}

+ 8 - 9
src/me/hammerle/snuviengine/Main.java

@@ -1,14 +1,13 @@
 package me.hammerle.snuviengine;
 
-import me.hammerle.snuviengine.api.Engine;
+import me.hammerle.snuviengine.api.Window;
 
-public class Main 
-{
-    public static void main(String[] args)
-    {
-        Engine.init("wusi", 1024, 620);      
-        Engine.setNanosPerTick(50_000_000);          
-        Game game = new Game();
-        Engine.start(game);
+public class Main {
+    public static void main(String[] args) {
+        Window window = new Window(50_000_000);
+        if(window.initialize("wusi", 1024, 620)) {
+            TestGame game = new TestGame();
+            window.open(game);
+        }
     }
 }

+ 62 - 0
src/me/hammerle/snuviengine/TestGame.java

@@ -0,0 +1,62 @@
+package me.hammerle.snuviengine;
+
+import me.hammerle.snuviengine.api.Gamepad;
+import me.hammerle.snuviengine.api.Renderer;
+import me.hammerle.snuviengine.api.Game;
+import me.hammerle.snuviengine.api.Keys;
+import me.hammerle.snuviengine.api.Stats;
+
+public class TestGame implements Game {
+    private float posX = 200.0f;
+    private float posY = 0.0f;
+    private float lastPosX = 0.0f;
+    private float lastPosY = 0.0f;
+    private float move = 16.5f;
+    private final float size = 20.0f;
+
+    private Stats stats = null;
+    private Gamepad gamepad = null;
+
+    @Override
+    public void onCompleteInitialization(Stats stats, Keys keys, Gamepad gamepad) {
+        this.stats = stats;
+        this.gamepad = gamepad;
+    }
+
+    @Override
+    public void tick() {
+        lastPosX = posX;
+        lastPosY = posY;
+        posX += move;
+        if(posX < 0.0f || posX > 250.0f) {
+            move = -move;
+        }
+    }
+
+    @Override
+    public void renderTick(Renderer renderer, float lag) {
+        renderer.translateTo(0.0f, 0.0f);
+        renderer.updateMatrix();
+        float y = 10;
+        renderer.setColorEnabled(true);
+        renderer.setTextureEnabled(true);
+        y = renderer.getFontRenderer().drawString(10, y, true, String.format("FPS: #00FF00%.1f", stats.getFramesPerSecond()));
+        y = renderer.getFontRenderer().drawString(10, y, true, String.format("TPS: %.1f", stats.getTicksPerSecond()));
+        for(Gamepad.GamepadButton binding : gamepad.bindings) {
+            y = renderer.getFontRenderer().drawString(10, y, true, String.format("%s: %b, %d, %b", binding.getName(), binding.isDown(), binding.getTime(), binding.isReleased()));
+        }
+        renderer.setColorEnabled(true);
+        renderer.setTextureEnabled(false);
+        float rx = lastPosX + (posX - lastPosX) * lag;
+        float ry = lastPosY + (posY - lastPosY) * lag;
+        rx = renderer.round(rx);
+        ry = renderer.round(ry);
+        renderer.translateTo(rx, ry);
+        renderer.updateMatrix();
+        renderer.getColorRenderer().drawRectangle(0, 0, size, size, 0xFF0000FF);
+    }
+
+    @Override
+    public void onStop() {
+    }
+}

+ 46 - 60
src/me/hammerle/snuviengine/api/ColorRenderer.java

@@ -1,87 +1,73 @@
 package me.hammerle.snuviengine.api;
 
 import java.nio.ByteBuffer;
-import org.lwjgl.BufferUtils;
 import static org.lwjgl.opengl.GL11.*;
 import static org.lwjgl.opengl.GL15.*;
 import static org.lwjgl.opengl.GL20.*;
 import static org.lwjgl.opengl.GL30.*;
 
-public class ColorRenderer
-{
-    private final static int BUFFER_BYTE_LENGTH = 1024 * 1024;
-    private final static int OBJECT_LENGTH = 64;
-    
-    private final int vao;
-    private final int vbo;
-    
-    private ByteBuffer buffer = BufferUtils.createByteBuffer(64);
-    private int offset = BUFFER_BYTE_LENGTH - OBJECT_LENGTH;
-    private float depth = 0.0f;
-    
-    protected ColorRenderer()
-    {
-        vao = glGenVertexArrays();
-        vbo = glGenBuffers();
+public final class ColorRenderer {
+    private final static int BUFFER_BYTE_LENGTH = 1024 * 1024; // 1 MiB
+    private final static int OBJECT_LENGTH = 64; // 16 Floats
 
-        GLHelper.glBindVertexArray(vao);
-        GLHelper.glBindBuffer(vbo);
+    private final int vertexArray;
+    private final int vertexBuffer;
+    private ByteBuffer buffer;
+    private int offset = BUFFER_BYTE_LENGTH;
+    private float depth = 0.0f;
 
+    protected ColorRenderer() {
+        vertexArray = glGenVertexArrays();
+        vertexBuffer = glGenBuffers();
+        GLHelper.glBindVertexArray(vertexArray);
+        GLHelper.glBindBuffer(vertexBuffer);
         glEnableVertexAttribArray(0);
         glEnableVertexAttribArray(2);
-
         glVertexAttribPointer(0, 3, GL_FLOAT, false, 16, 0);
         glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, true, 16, 12);
     }
-    
-    public void setDepth(float depth)
-    {
+
+    public void setDepth(float depth) {
         this.depth = depth;
     }
 
-    public void drawRectangle(float minX, float minY, float maxX, float maxY, int color)
-    {
-        GLHelper.glBindBuffer(vbo);
-        
+    public void drawRectangle(float minX, float minY, float maxX, float maxY, int color) {
+        buffer = getNextBuffer();
+        if(buffer == null) {
+            return;
+        }
+        addToBuffer(minX, maxY, color);
+        addToBuffer(minX, minY, color);
+        addToBuffer(maxX, maxY, color);
+        addToBuffer(maxX, minY, color);
+        finishBuffer();
+        drawBuffer();
+    }
+
+    private ByteBuffer getNextBuffer() {
+        GLHelper.glBindBuffer(vertexBuffer);
         offset += OBJECT_LENGTH;
-        if(offset + OBJECT_LENGTH >= BUFFER_BYTE_LENGTH)
-        {
+        if(offset + OBJECT_LENGTH >= BUFFER_BYTE_LENGTH) {
             offset = 0;
             glBufferData(GL_ARRAY_BUFFER, BUFFER_BYTE_LENGTH, GL_STREAM_DRAW);
         }
-        buffer = glMapBufferRange(GL_ARRAY_BUFFER, offset, OBJECT_LENGTH, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, buffer);
-        if(buffer == null)
-        {
-            return;
-        }
-        
-        float fcolor = Float.intBitsToFloat(color);
-        
-        buffer.putFloat(minX);
-        buffer.putFloat(maxY);   
-        buffer.putFloat(depth);  
-        buffer.putFloat(fcolor);
-        
-        buffer.putFloat(minX);
-        buffer.putFloat(minY);   
-        buffer.putFloat(depth);  
-        buffer.putFloat(fcolor);
-        
-        buffer.putFloat(maxX);
-        buffer.putFloat(maxY);  
-        buffer.putFloat(depth);  
-        buffer.putFloat(fcolor);
-        
-        buffer.putFloat(maxX);
-        buffer.putFloat(minY);  
-        buffer.putFloat(depth);  
-        buffer.putFloat(fcolor);
-        
-        buffer.flip();
+        return glMapBufferRange(GL_ARRAY_BUFFER, offset, OBJECT_LENGTH, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, buffer);
+    }
+
+    private void addToBuffer(float x, float y, int color) {
+        buffer.putFloat(x);
+        buffer.putFloat(y);
+        buffer.putFloat(depth);
+        buffer.putInt(color);
+    }
 
+    private void finishBuffer() {
+        buffer.flip();
         glUnmapBuffer(GL_ARRAY_BUFFER);
-        
-        GLHelper.glBindVertexArray(vao);
+    }
+
+    private void drawBuffer() {
+        GLHelper.glBindVertexArray(vertexArray);
         glDrawArrays(GL_TRIANGLE_STRIP, offset / 16, 4);
     }
 }

+ 48 - 63
src/me/hammerle/snuviengine/api/DirectTextureRenderer.java

@@ -1,89 +1,74 @@
 package me.hammerle.snuviengine.api;
 
 import java.nio.ByteBuffer;
-import org.lwjgl.BufferUtils;
 import static org.lwjgl.opengl.GL11.*;
 import static org.lwjgl.opengl.GL15.*;
 import static org.lwjgl.opengl.GL20.*;
 import static org.lwjgl.opengl.GL30.*;
 
-public class DirectTextureRenderer
-{
-    private final static int BUFFER_BYTE_LENGTH = 1024 * 1024;
-    private final static int OBJECT_LENGTH = 80;
-    
-    private final int vao;
-    private final int vbo;
-    
-    private ByteBuffer buffer = BufferUtils.createByteBuffer(OBJECT_LENGTH);
-    private int offset = BUFFER_BYTE_LENGTH - OBJECT_LENGTH;    
-    private float depth = 0.0f;
-    
-    protected DirectTextureRenderer()
-    {
-        vao = glGenVertexArrays();
-        vbo = glGenBuffers();
+public final class DirectTextureRenderer {
+    private final static int BUFFER_BYTE_LENGTH = 1024 * 1024; // 1 MiB
+    private final static int OBJECT_LENGTH = 80; // 20 Floats
 
-        GLHelper.glBindVertexArray(vao);
-        GLHelper.glBindBuffer(vbo);
+    private final int vertexArray;
+    private final int vertexBuffer;
+    private ByteBuffer buffer;
+    private int offset = BUFFER_BYTE_LENGTH;
+    private float depth = 0.0f;
 
+    protected DirectTextureRenderer() {
+        vertexArray = glGenVertexArrays();
+        vertexBuffer = glGenBuffers();
+        GLHelper.glBindVertexArray(vertexArray);
+        GLHelper.glBindBuffer(vertexBuffer);
         glEnableVertexAttribArray(0);
         glVertexAttribPointer(0, 3, GL_FLOAT, false, 20, 0);
-
-        glEnableVertexAttribArray(1);  
+        glEnableVertexAttribArray(1);
         glVertexAttribPointer(1, 2, GL_FLOAT, false, 20, 12);
     }
-    
-    public void setDepth(float depth)
-    {
+
+    public void setDepth(float depth) {
         this.depth = depth;
     }
 
-    public void drawRectangle(float minX, float minY, float maxX, float maxY, float tMinX, float tMinY, float tMaxX, float tMaxY)
-    {
-        GLHelper.glBindBuffer(vbo);
-        
+    public void drawRectangle(float minX, float minY, float maxX, float maxY, float tMinX, float tMinY, float tMaxX, float tMaxY) {
+        buffer = getNextBuffer();
+        if(buffer == null) {
+            return;
+        }
+        addToBuffer(minX, maxY, tMinX, tMaxY);
+        addToBuffer(minX, minY, tMinX, tMinY);
+        addToBuffer(maxX, maxY, tMaxX, tMaxY);
+        addToBuffer(maxX, minY, tMaxX, tMinY);
+        finishBuffer();
+        drawBuffer();
+    }
+
+    private ByteBuffer getNextBuffer() {
+        GLHelper.glBindBuffer(vertexBuffer);
         offset += OBJECT_LENGTH;
-        if(offset + OBJECT_LENGTH >= BUFFER_BYTE_LENGTH)
-        {
+        if(offset + OBJECT_LENGTH >= BUFFER_BYTE_LENGTH) {
             offset = 0;
             glBufferData(GL_ARRAY_BUFFER, BUFFER_BYTE_LENGTH, GL_STREAM_DRAW);
         }
-        buffer = glMapBufferRange(GL_ARRAY_BUFFER, offset, OBJECT_LENGTH, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, buffer);
-        if(buffer == null)
-        {
-            return;
-        }
-        
-        buffer.putFloat(minX);
-        buffer.putFloat(maxY);   
-        buffer.putFloat(depth);  
-        buffer.putFloat(tMinX);
-        buffer.putFloat(tMaxY);
-        
-        buffer.putFloat(minX);
-        buffer.putFloat(minY);   
-        buffer.putFloat(depth);  
-        buffer.putFloat(tMinX);
-        buffer.putFloat(tMinY);
-        
-        buffer.putFloat(maxX);
-        buffer.putFloat(maxY);  
-        buffer.putFloat(depth);  
-        buffer.putFloat(tMaxX);
-        buffer.putFloat(tMaxY);
-        
-        buffer.putFloat(maxX);
-        buffer.putFloat(minY);  
-        buffer.putFloat(depth);  
-        buffer.putFloat(tMaxX);
-        buffer.putFloat(tMinY);
-        
-        buffer.flip();
+        return glMapBufferRange(GL_ARRAY_BUFFER, offset, OBJECT_LENGTH, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, buffer);
+    }
+
+    private void addToBuffer(float x, float y, float tx, float ty) {
+        buffer.putFloat(x);
+        buffer.putFloat(y);
+        buffer.putFloat(depth);
+        buffer.putFloat(tx);
+        buffer.putFloat(ty);
+    }
 
+    private void finishBuffer() {
+        buffer.flip();
         glUnmapBuffer(GL_ARRAY_BUFFER);
-        
-        GLHelper.glBindVertexArray(vao);
+    }
+
+    private void drawBuffer() {
+        GLHelper.glBindVertexArray(vertexArray);
         glDrawArrays(GL_TRIANGLE_STRIP, offset / 20, 4);
     }
 }

+ 0 - 177
src/me/hammerle/snuviengine/api/Engine.java

@@ -1,177 +0,0 @@
-package me.hammerle.snuviengine.api;
-
-import org.lwjgl.glfw.*;
-import org.lwjgl.opengl.*;
-import static org.lwjgl.glfw.Callbacks.*;
-import static org.lwjgl.glfw.GLFW.*;
-import static org.lwjgl.opengl.GL11.*;
-import static org.lwjgl.system.MemoryUtil.*;
-
-public class Engine
-{
-    private static long window;
-    
-    private static long nanosPerTick = 10_000_000;
-    private static final int MAX_TICKS_PER_FRAME = 20;
-    
-    private static Timer fpsTimer;
-    private static Timer tpsTimer;
-    
-    private static Renderer renderer;
-    
-    private Engine()
-    {
-    }    
-
-    public static void start(IGame game)
-    {
-        loop(game);
-
-        glfwFreeCallbacks(window);
-        glfwDestroyWindow(window);
-
-        glfwTerminate();
-        GLFWErrorCallback error = glfwSetErrorCallback(null);
-        if(error != null)
-        {
-            error.free();
-        }
-    }
-    
-    public static void stop()
-    {
-        glfwSetWindowShouldClose(window, true);
-    }
-
-    public static void init(String name, int width, int height)
-    {
-        GLFWErrorCallback.createPrint(System.err).set();
-        
-        if(!glfwInit())
-        {
-            throw new IllegalStateException("Unable to initialize GLFW");
-        }
-
-        glfwDefaultWindowHints();
-        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
-        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
-        
-        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
-        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
-        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
-
-        window = glfwCreateWindow(width, height, name, NULL, NULL);
-        if(window == NULL)
-        {
-            throw new IllegalStateException("Failed to create the GLFW window");
-        }
-
-        glfwSetKeyCallback(window, (w, key, scancode, action, mods) -> 
-        {
-            if(action == GLFW_RELEASE)
-            {
-                KeyHandler.onKeyUpEvent(key);
-            }
-            else if(action == GLFW_PRESS)
-            {
-                KeyHandler.onKeyDownEvent(key);
-            }
-        });
-        
-        glfwMakeContextCurrent(window);
-        glfwSwapInterval(2);
-       
-        GL.createCapabilities();
-        
-        renderer = new Renderer(width, height);
-        fpsTimer = new Timer(60, 0.0f);
-        tpsTimer = new Timer(1_000_000_000l / nanosPerTick, height * 0.25f);
-        
-        glfwSetFramebufferSizeCallback(window, (w, fwidth, fheight) -> 
-        {
-            glViewport(0, 0, fwidth, fheight);
-            renderer.setViewPort(fwidth, fheight);
-        });
-        
-        glfwShowWindow(window);
-    }
-    
-    private static void loop(IGame game)
-    {
-        long oldTime = System.nanoTime();
-        long lag = 0;
-
-        while(!glfwWindowShouldClose(window))
-        {
-            long newTime = System.nanoTime();
-            lag += newTime - oldTime;
-            oldTime = newTime;
-
-            int ticksPerFrame = 0;
-            while(lag >= nanosPerTick)
-            {
-                lag -= nanosPerTick;
-
-                tpsTimer.update();
-                GamepadHandler.tick();
-                KeyHandler.tick();
-                game.tick();
-
-                ticksPerFrame++;  
-
-                if(ticksPerFrame >= MAX_TICKS_PER_FRAME)
-                {
-                    long skip = lag / nanosPerTick;
-                    lag -= skip * nanosPerTick;
-                    if(skip > 0)
-                    {
-                        System.out.println(String.format("skipped %d game ticks %d", skip, lag));
-                    }
-                    break;
-                }
-            }
-            
-            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-            game.renderTick(renderer, (float) lag / nanosPerTick);
-            tpsTimer.draw(renderer);
-            fpsTimer.draw(renderer);
-            fpsTimer.update();   
-            glfwSwapBuffers(window);
-            glfwPollEvents();
-        }
-        game.onStop();
-    }
-    
-    public static void setNanosPerTick(long nanos)
-    {
-        nanosPerTick = nanos;
-        tpsTimer.setExpectedValue(1_000_000_000l / nanos);
-    }
-    
-    public static long getNanosPerTick()
-    {
-        return nanosPerTick;
-    }
-    
-    public static double getTicksPerSecond()
-    {
-        return tpsTimer.getCallsPerSecond();
-    }
-    
-    public static void setRenderTicksPerSecond(boolean active)
-    {
-        tpsTimer.setActive(active);
-    }
-    
-    public static double getFramesPerSecond()
-    {
-        return fpsTimer.getCallsPerSecond();
-    }
-
-    public static void setRenderFramesPerSecond(boolean active)
-    {
-        fpsTimer.setActive(active);
-    }
-}
-
-

+ 176 - 220
src/me/hammerle/snuviengine/api/FontRenderer.java

@@ -1,268 +1,224 @@
 package me.hammerle.snuviengine.api;
 
 import java.nio.ByteBuffer;
-import me.hammerle.snuviengine.util.Rectangle;
 import me.hammerle.snuviengine.util.Color;
-import org.lwjgl.BufferUtils;
+import me.hammerle.snuviengine.util.Rectangle;
 import static org.lwjgl.opengl.GL11.*;
 import static org.lwjgl.opengl.GL15.*;
 import static org.lwjgl.opengl.GL20.*;
 import static org.lwjgl.opengl.GL30.*;
 
-public class FontRenderer
-{
-    public static final char COLOR_CHAR = '&';
-    private static final float[] COLORS = new float[128];
-    private static final float[] DARK_COLORS = new float[128];
+public final class FontRenderer {
+    private static final char COLOR_CHAR = '#';
     private static final int FONT_SIZE = 8;
     private static final int LINE_STEP = 1;
     private static final float SHADOW_STEP = 1.0f;
-    private final static int MAX_LENGTH = 256;
-    private final static int BUFFER_BYTE_LENGTH = 1024 * 1024;
-    private final static int OBJECT_LENGTH = 120 * MAX_LENGTH * 2;
-    
-    static
-    {
-        COLORS['0'] = Float.intBitsToFloat(Color.get(0, 0, 0));
-        COLORS['1'] = Float.intBitsToFloat(Color.get(0, 0, 170));
-        COLORS['2'] = Float.intBitsToFloat(Color.get(0, 170, 0));
-        COLORS['3'] = Float.intBitsToFloat(Color.get(0, 170, 170));
-        COLORS['4'] = Float.intBitsToFloat(Color.get(170, 0, 0));
-        COLORS['5'] = Float.intBitsToFloat(Color.get(170, 0, 170));
-        COLORS['6'] = Float.intBitsToFloat(Color.get(255, 170, 0));
-        COLORS['7'] = Float.intBitsToFloat(Color.get(170, 170, 170));
-        COLORS['8'] = Float.intBitsToFloat(Color.get(85, 85, 85));
-        COLORS['9'] = Float.intBitsToFloat(Color.get(85, 85, 255));
-        COLORS['a'] = Float.intBitsToFloat(Color.get(85, 255, 85));
-        COLORS['b'] = Float.intBitsToFloat(Color.get(85, 255, 255));
-        COLORS['c'] = Float.intBitsToFloat(Color.get(255, 85, 85));
-        COLORS['d'] = Float.intBitsToFloat(Color.get(255, 85, 255));
-        COLORS['e'] = Float.intBitsToFloat(Color.get(255, 255, 85));
-        COLORS['f'] = Float.intBitsToFloat(Color.get(255, 255, 255));
-        
-        float factor = 0.5f;
-        DARK_COLORS['0'] = Float.intBitsToFloat(Color.darken(Color.get(0, 0, 0), factor));
-        DARK_COLORS['1'] = Float.intBitsToFloat(Color.darken(Color.get(0, 0, 170), factor));
-        DARK_COLORS['2'] = Float.intBitsToFloat(Color.darken(Color.get(0, 170, 0), factor));
-        DARK_COLORS['3'] = Float.intBitsToFloat(Color.darken(Color.get(0, 170, 170), factor));
-        DARK_COLORS['4'] = Float.intBitsToFloat(Color.darken(Color.get(170, 0, 0), factor));
-        DARK_COLORS['5'] = Float.intBitsToFloat(Color.darken(Color.get(170, 0, 170), factor));
-        DARK_COLORS['6'] = Float.intBitsToFloat(Color.darken(Color.get(255, 170, 0), factor));
-        DARK_COLORS['7'] = Float.intBitsToFloat(Color.darken(Color.get(170, 170, 170), factor));
-        DARK_COLORS['8'] = Float.intBitsToFloat(Color.darken(Color.get(85, 85, 85), factor));
-        DARK_COLORS['9'] = Float.intBitsToFloat(Color.darken(Color.get(85, 85, 255), factor));
-        DARK_COLORS['a'] = Float.intBitsToFloat(Color.darken(Color.get(85, 255, 85), factor));
-        DARK_COLORS['b'] = Float.intBitsToFloat(Color.darken(Color.get(85, 255, 255), factor));
-        DARK_COLORS['c'] = Float.intBitsToFloat(Color.darken(Color.get(255, 85, 85), factor));
-        DARK_COLORS['d'] = Float.intBitsToFloat(Color.darken(Color.get(255, 85, 255), factor));
-        DARK_COLORS['e'] = Float.intBitsToFloat(Color.darken(Color.get(255, 255, 85), factor));
-        DARK_COLORS['f'] = Float.intBitsToFloat(Color.darken(Color.get(255, 255, 255), factor));
-    }
-    
-    private final Texture[] FONT_TEXTURE = new Texture[]
-    {
-        new Texture("font8x8.png", true), 
-        new Texture("font16x16.png", true), 
+    private final static int BUFFER_BYTE_LENGTH = 4 * 1024 * 1024; // 4 MiB
+
+    private final Texture[] FONT_TEXTURE = new Texture[]{
+        new Texture("font8x8.png", true),
+        new Texture("font16x16.png", true),
         new Texture("font24x24.png", true)
     };
-    
-    private final int vao;
-    private final int vbo;
-    
-    private float color = 0.0f;
-    private int scale = 1;
-    private ByteBuffer buffer = BufferUtils.createByteBuffer(OBJECT_LENGTH);
-    private int offset = BUFFER_BYTE_LENGTH - OBJECT_LENGTH;
-    
-    protected FontRenderer()
-    {
-        vao = glGenVertexArrays();
-        vbo = glGenBuffers();
 
-        GLHelper.glBindVertexArray(vao);
-        GLHelper.glBindBuffer(vbo);
+    private final int vertexArray;
+    private final int vertexBuffer;
+    private int color = 0;
+    private int scale = 1;
+    private ByteBuffer buffer;
+    private int offset = BUFFER_BYTE_LENGTH;
+    private int width = 0;
+    private int height = 0;
 
+    protected FontRenderer() {
+        vertexArray = glGenVertexArrays();
+        vertexBuffer = glGenBuffers();
+        GLHelper.glBindVertexArray(vertexArray);
+        GLHelper.glBindBuffer(vertexBuffer);
         glEnableVertexAttribArray(0);
-        glEnableVertexAttribArray(1); 
+        glEnableVertexAttribArray(1);
         glEnableVertexAttribArray(2);
-
         glVertexAttribPointer(0, 2, GL_FLOAT, false, 20, 0);
         glVertexAttribPointer(1, 2, GL_FLOAT, false, 20, 8);
         glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, true, 20, 16);
     }
-    
-    protected void setScale(int scale)
-    {
+
+    protected void setScale(int scale) {
         this.scale = scale;
     }
 
-    private void addRectangle(float minX, float minY, char c)
-    {
-        minY = Math.round(minY * scale) / scale;
-        minX = Math.round(minX * scale) / scale;
-        
-        float tMinX = (c & 0xF) / 16.0f;
-        float tMinY = (c >> 4) / 16.0f;
-        float tMaxX = tMinX + 0.0625f;
-        float tMaxY = tMinY + 0.0625f;
-        float maxX = minX + FONT_SIZE;
-        float maxY = minY + FONT_SIZE;
-        
-        buffer.putFloat(minX);
-        buffer.putFloat(maxY);        
-        buffer.putFloat(tMinX);
-        buffer.putFloat(tMaxY);
-        buffer.putFloat(color);
-        
-        buffer.putFloat(minX);
-        buffer.putFloat(minY);        
-        buffer.putFloat(tMinX);
-        buffer.putFloat(tMinY);
-        buffer.putFloat(color);
-        
-        buffer.putFloat(maxX);
-        buffer.putFloat(maxY);        
-        buffer.putFloat(tMaxX);
-        buffer.putFloat(tMaxY);
-        buffer.putFloat(color);
-        
-        buffer.putFloat(maxX);
-        buffer.putFloat(maxY);        
-        buffer.putFloat(tMaxX);
-        buffer.putFloat(tMaxY);
-        buffer.putFloat(color);
-        
-        buffer.putFloat(minX);
-        buffer.putFloat(minY);        
-        buffer.putFloat(tMinX);
-        buffer.putFloat(tMinY);
-        buffer.putFloat(color);
-        
-        buffer.putFloat(maxX);
-        buffer.putFloat(minY);        
-        buffer.putFloat(tMaxX);
-        buffer.putFloat(tMinY);
-        buffer.putFloat(color);
-    }
-    
-    public float drawString(float x, float y, boolean shadow, String s)
-    {
-        GLHelper.glBindBuffer(vbo);
-        
-        offset += OBJECT_LENGTH;
-        if(offset + OBJECT_LENGTH >= BUFFER_BYTE_LENGTH)
-        {
-            offset = 0;
-            glBufferData(GL_ARRAY_BUFFER, BUFFER_BYTE_LENGTH, GL_STREAM_DRAW);
-        }
-        buffer = glMapBufferRange(GL_ARRAY_BUFFER, offset, OBJECT_LENGTH, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, buffer);
-        if(buffer == null)
-        {
+    public float drawString(float x, float y, boolean shadow, String text) {
+        buffer = getNextBuffer(120 * 256 * 2);
+        if(buffer == null) {
             return y;
         }
-        
-        if(shadow)
-        {
-            addString(x + SHADOW_STEP, y + SHADOW_STEP, DARK_COLORS, s);
+        if(shadow) {
+            addStringToBuffer(x + SHADOW_STEP, y + SHADOW_STEP, true, text);
         }
-        y = addString(x, y, COLORS, s);
-        
-        buffer.flip();
-        glUnmapBuffer(GL_ARRAY_BUFFER);
-        
-        GLHelper.glBindVertexArray(vao);
-        FONT_TEXTURE[Math.min(scale - 1, FONT_TEXTURE.length - 1)].bind();
-        
-        glDrawArrays(GL_TRIANGLES, offset / 20, buffer.limit() / 20);
-
-        buffer.limit(buffer.capacity());
+        y = addStringToBuffer(x, y, false, text);
+        finishBuffer();
+        drawBuffer();
         return y;
     }
-    
-    public float drawString(float x, float y, String s)
-    {
-        return drawString(x, y, true, s);
+
+    public float drawString(float x, float y, String text) {
+        return drawString(x, y, true, text);
     }
 
-    private float addString(float x, float y, float[] colors, String s)
-    {
-        int l = Math.min(s.length(), MAX_LENGTH);
-        
-        float oldX = x;
-        color = colors['f'];
+    private ByteBuffer getNextBuffer(int byteLength) {
+        GLHelper.glBindBuffer(vertexBuffer);
+        offset += byteLength;
+        if(offset + byteLength >= BUFFER_BYTE_LENGTH) {
+            offset = 0;
+            glBufferData(GL_ARRAY_BUFFER, BUFFER_BYTE_LENGTH, GL_STREAM_DRAW);
+        }
+        return glMapBufferRange(GL_ARRAY_BUFFER, offset, byteLength, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, buffer);
+    }
 
-        for(int pos = 0; pos < l; pos++)
-        {
-            char c = s.charAt(pos);
-            if(c == COLOR_CHAR)
-            {
-                pos++;
-                if(pos < l)
-                {
-                    int index = s.charAt(pos);
-                    if(index >= 0 && index <= colors.length)
-                    {
-                        color = colors[s.charAt(pos)];
-                    }
-                }
-                continue;
+    private float addStringToBuffer(float x, float y, boolean shadow, String text) {
+        float oldX = x;
+        setColor(0xFFFFFF, shadow);
+        int index = 0;
+        while(true) {
+            index += consumeColor(text, index, shadow);
+            if(index >= text.length()) {
+                break;
             }
-            else if(c == '\n')
-            {
+            char c = text.charAt(index);
+            if(c == '\n') {
                 y += FONT_SIZE + LINE_STEP;
                 x = oldX;
-                continue;
+            } else {
+                addRectangle(x, y, c);
+                x += FONT_SIZE;
             }
-
-            addRectangle(x, y, c);
-            x += FONT_SIZE;
+            index++;
         }
         return y + FONT_SIZE + LINE_STEP;
     }
-    
-    public Rectangle getSize(String s)
-    {
-        int longestLine = 0;
-        int length = 0;
-        int counter = 1;
-        for(int i = 0; i < s.length(); i++)
-        {
-            switch(s.charAt(i))
-            {
-                case '\n':
-                    counter++;
-                    if(length > longestLine)
-                    {
-                        longestLine = length;
-                    }
-                    length = 0;
-                    break;
-                case COLOR_CHAR:
-                    i++;
-                    break;
-                default:
-                    length++;
-            }
+
+    private void setColor(int color, boolean shadow) {
+        if(shadow) {
+            this.color = Color.darken(color, 0.5f);
+        } else {
+            this.color = color;
         }
-        if(length > longestLine)
-        {
-            longestLine = length;
+    }
+
+    private int consumeColor(String text, int index, boolean shadow) {
+        char c = getChar(text, index);
+        if(c != COLOR_CHAR) {
+            return 0;
+        } else if(getChar(text, index + 1) == COLOR_CHAR) {
+            return 1;
+        } else if(text.length() < index + 7) {
+            return 0;
         }
-        return new Rectangle(FONT_SIZE * longestLine, (FONT_SIZE + LINE_STEP) * counter);
+        setColor(parseColor(text, index + 1), shadow);
+        return 7;
     }
-    
-    public Rectangle getSize(int w, int h)
-    {
-        return new Rectangle(FONT_SIZE * w, (FONT_SIZE + LINE_STEP) * h);
+
+    private char getChar(String text, int index) {
+        return index < text.length() ? text.charAt(index) : '\0';
     }
-    
-    public float getHeight()
-    {
-        return FONT_SIZE + LINE_STEP;
+
+    private int parseColor(String text, int index) {
+        int c = 0;
+        c += getCharValue(text.charAt(index)) << 4;
+        c += getCharValue(text.charAt(index + 1));
+        c += getCharValue(text.charAt(index + 2)) << 12;
+        c += getCharValue(text.charAt(index + 3)) << 8;
+        c += getCharValue(text.charAt(index + 4)) << 20;
+        c += getCharValue(text.charAt(index + 5)) << 16;
+        return c;
+    }
+
+    private int getCharValue(char c) {
+        if(c >= '0' && c <= '9') {
+            return c - '0';
+        } else if(c >= 'A' && c <= 'F') {
+            return c - 'A' + 10;
+        } else if(c >= 'a' && c <= 'f') {
+            return c - 'a' + 10;
+        }
+        return 0;
+    }
+
+    private void addRectangle(float minX, float minY, char c) {
+        float tMinX = (c & 0xF) / 16.0f;
+        float tMinY = (c >> 4) / 16.0f;
+        float tMaxX = tMinX + 0.0625f;
+        float tMaxY = tMinY + 0.0625f;
+        float maxX = minX + FONT_SIZE;
+        float maxY = minY + FONT_SIZE;
+        addToBuffer(minX, maxY, tMinX, tMaxY);
+        addToBuffer(minX, minY, tMinX, tMinY);
+        addToBuffer(maxX, maxY, tMaxX, tMaxY);
+        addToBuffer(maxX, maxY, tMaxX, tMaxY);
+        addToBuffer(minX, minY, tMinX, tMinY);
+        addToBuffer(maxX, minY, tMaxX, tMinY);
+    }
+
+    private void addToBuffer(float x, float y, float tx, float ty) {
+        buffer.putFloat(x);
+        buffer.putFloat(y);
+        buffer.putFloat(tx);
+        buffer.putFloat(ty);
+        buffer.putInt(color);
+    }
+
+    private void finishBuffer() {
+        buffer.flip();
+        glUnmapBuffer(GL_ARRAY_BUFFER);
+    }
+
+    private void drawBuffer() {
+        GLHelper.glBindVertexArray(vertexArray);
+        FONT_TEXTURE[Math.min(scale - 1, FONT_TEXTURE.length - 1)].bind();
+        glDrawArrays(GL_TRIANGLES, offset / 20, buffer.limit() / 20);
+    }
+
+    public FontRenderer getSize(String text) {
+        width = 0;
+        height = FONT_SIZE + LINE_STEP;
+        int currentWidth = 0;
+        int index = 0;
+        while(true) {
+            index += consumeColor(text, index, false);
+            if(index >= text.length()) {
+                break;
+            }
+            char c = text.charAt(index);
+            if(c == '\n') {
+                width = Math.max(currentWidth, width);
+                currentWidth = 0;
+                height += FONT_SIZE + LINE_STEP;
+            } else {
+                currentWidth++;
+            }
+            index++;
+        }
+        width = Math.max(currentWidth, width) * FONT_SIZE;
+        return this;
+    }
+
+    public int getStringWidth() {
+        return width;
     }
-    
-    public float getWidth()
-    {
+
+    public int getStringHeight() {
+        return height;
+    }
+
+    public Rectangle getSize(int w, int h) {
+        return new Rectangle(FONT_SIZE * w, (FONT_SIZE + LINE_STEP) * h);
+    }
+
+    public float getCharWidth() {
         return FONT_SIZE;
     }
+
+    public float getCharHeight() {
+        return FONT_SIZE + LINE_STEP;
+    }
 }

+ 11 - 16
src/me/hammerle/snuviengine/api/GLHelper.java

@@ -3,25 +3,20 @@ package me.hammerle.snuviengine.api;
 import org.lwjgl.opengl.GL15;
 import org.lwjgl.opengl.GL30;
 
-public class GLHelper
-{
-    private static int currentVao = -1;
-    private static int currentVbo = -1;
-        
-    protected static void glBindVertexArray(int vao)
-    {
-        if(vao != currentVao)
-        {
-            currentVao = vao;
+public final class GLHelper {
+    private static int currentVertextArray = -1;
+    private static int currentVertexBuffer = -1;
+
+    protected static void glBindVertexArray(int vao) {
+        if(vao != currentVertextArray) {
+            currentVertextArray = vao;
             GL30.glBindVertexArray(vao);
         }
     }
-    
-    protected static void glBindBuffer(int vbo)
-    {
-        if(vbo != currentVbo)
-        {
-            currentVbo = vbo;
+
+    protected static void glBindBuffer(int vbo) {
+        if(vbo != currentVertexBuffer) {
+            currentVertexBuffer = vbo;
             GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
         }
     }

+ 16 - 0
src/me/hammerle/snuviengine/api/Game.java

@@ -0,0 +1,16 @@
+package me.hammerle.snuviengine.api;
+
+public interface Game {
+    public default void onCompleteInitialization(Stats stats, Keys keys, Gamepad gamepad) {
+    }
+
+    public void tick();
+
+    public void renderTick(Renderer r, float lag);
+
+    public default boolean isRunning() {
+        return true;
+    }
+
+    public void onStop();
+}

+ 116 - 0
src/me/hammerle/snuviengine/api/Gamepad.java

@@ -0,0 +1,116 @@
+package me.hammerle.snuviengine.api;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import static org.lwjgl.glfw.GLFW.*;
+
+public final class Gamepad {
+    public static final class GamepadButton {
+        private final String name;
+        private final int mapping;
+
+        private boolean isDown = false;
+        private int time = 0;
+        private boolean isReleased = false;
+
+        protected GamepadButton(String name, int mapping) {
+            this.name = name;
+            this.mapping = mapping;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        protected int getMapping() {
+            return mapping;
+        }
+
+        protected void tick(boolean isDown) {
+            if(isReleased) {
+                isReleased = false;
+                time = 0;
+            }
+            if(this.isDown && !isDown) {
+                isReleased = true;
+                time++;
+            }
+            this.isDown = isDown;
+            if(isDown) {
+                time++;
+            }
+        }
+
+        public boolean isDown() {
+            return isDown;
+        }
+
+        public boolean isReleased() {
+            return isReleased;
+        }
+
+        public int getTime() {
+            return time;
+        }
+
+        public void setTime(int time) {
+            this.time = time;
+        }
+    }
+
+    public final GamepadButton a = new GamepadButton("A", 1);
+    public final GamepadButton b = new GamepadButton("B", 2);
+    public final GamepadButton x = new GamepadButton("X", 0);
+    public final GamepadButton y = new GamepadButton("Y", 3);
+    public final GamepadButton l = new GamepadButton("L", 4);
+    public final GamepadButton r = new GamepadButton("R", 5);
+    public final GamepadButton start = new GamepadButton("Start", 9);
+    public final GamepadButton select = new GamepadButton("Select", 8);
+    public final GamepadButton left = new GamepadButton("Left", 0);
+    public final GamepadButton right = new GamepadButton("Right", 0);
+    public final GamepadButton up = new GamepadButton("Up", 1);
+    public final GamepadButton down = new GamepadButton("Down", 1);
+
+    private final GamepadButton[] buttons = new GamepadButton[]{
+        a, b, x, y, l, r, start, select
+    };
+
+    public final GamepadButton[] bindings = new GamepadButton[]{
+        a, b, x, y, l, r, start, select, left, right, up, down
+    };
+
+    public final int joystickId;
+
+    protected Gamepad(int joystickId) {
+        this.joystickId = joystickId;
+    }
+
+    protected void tick() {
+        if(!glfwJoystickPresent(joystickId)) {
+            return;
+        }
+        updateButtons();
+        updateAxes();
+    }
+
+    private void updateButtons() {
+        ByteBuffer buttonBuffer = glfwGetJoystickButtons(joystickId);
+        if(buttonBuffer == null) {
+            return;
+        }
+        for(GamepadButton binding : buttons) {
+            binding.tick(buttonBuffer.get(binding.getMapping()) != 0);
+        }
+    }
+
+    private void updateAxes() {
+        FloatBuffer axesBuffer = glfwGetJoystickAxes(joystickId);
+        if(axesBuffer == null) {
+            return;
+        }
+        left.tick(axesBuffer.get(left.getMapping()) < -0.5f);
+        right.tick(axesBuffer.get(right.getMapping()) > 0.5f);
+        up.tick(axesBuffer.get(up.getMapping()) < -0.5f);
+        down.tick(axesBuffer.get(down.getMapping()) > 0.5f);
+    }
+}

+ 0 - 66
src/me/hammerle/snuviengine/api/GamepadBinding.java

@@ -1,66 +0,0 @@
-package me.hammerle.snuviengine.api;
-
-public final class GamepadBinding
-{
-    private final String name;
-    private final int mapping;
-    
-    private boolean isDown = false;
-    private int time = 0;
-    private boolean isReleased = false;
-    
-    protected GamepadBinding(String name, int mapping)
-    {
-        this.name = name;
-        this.mapping = mapping;
-    }
-    
-    public String getName()
-    {
-        return name;
-    }
-
-    protected int getMapping()
-    {
-        return mapping;
-    }
-    
-    protected void tick(boolean isDown)
-    {
-        if(isReleased)
-        {
-            isReleased = false;
-            time = 0;
-        }
-        if(this.isDown && !isDown)
-        {
-            isReleased = true;
-            time++;
-        }
-        this.isDown = isDown;
-        if(isDown)
-        {
-            time++;
-        }
-    }
-    
-    public boolean isDown()
-    {
-        return isDown;
-    }
-
-    public boolean isReleased()
-    {
-        return isReleased;
-    }
-
-    public int getTime()
-    {
-        return time;
-    }
-
-    public void setTime(int time)
-    {
-        this.time = time;
-    }
-}

+ 0 - 69
src/me/hammerle/snuviengine/api/GamepadHandler.java

@@ -1,69 +0,0 @@
-package me.hammerle.snuviengine.api;
-
-import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
-import java.util.function.Consumer;
-import static org.lwjgl.glfw.GLFW.*;
-
-public class GamepadHandler
-{
-    public final static GamepadBinding A = new GamepadBinding("A", 1);
-    public final static GamepadBinding B = new GamepadBinding("B", 2);
-    public final static GamepadBinding X = new GamepadBinding("X", 0);
-    public final static GamepadBinding Y = new GamepadBinding("Y", 3);
-    public final static GamepadBinding L = new GamepadBinding("L", 4);
-    public final static GamepadBinding R = new GamepadBinding("R", 5);
-    public final static GamepadBinding START = new GamepadBinding("Start", 9);
-    public final static GamepadBinding SELECT = new GamepadBinding("Select", 8);
-
-    public final static GamepadBinding LEFT = new GamepadBinding("Left", 0);
-    public final static GamepadBinding RIGHT = new GamepadBinding("Right", 0);
-    public final static GamepadBinding UP = new GamepadBinding("Up", 1);
-    public final static GamepadBinding DOWN = new GamepadBinding("Down", 1);
-
-    private final static GamepadBinding[] BUTTONS = new GamepadBinding[]
-    {
-        A, B, X, Y, L, R, START, SELECT
-    };
-    
-    public final static GamepadBinding[] BINDINGS = new GamepadBinding[]
-    {
-        A, B, X, Y, L, R, START, SELECT, LEFT, RIGHT, UP, DOWN
-    };
-
-    protected static void tick()
-    {
-        if(!glfwJoystickPresent(GLFW_JOYSTICK_1))
-        {
-            return;
-        }
-        updateButtons();
-        updateAxes();
-    }
-
-    private static void updateButtons()
-    {
-        ByteBuffer buttonBuffer = glfwGetJoystickButtons(GLFW_JOYSTICK_1);
-        if(buttonBuffer == null)
-        {
-            return;
-        }
-        for(GamepadBinding binding : BUTTONS)
-        {
-            binding.tick(buttonBuffer.get(binding.getMapping()) != 0);
-        }
-    }
-
-    private static void updateAxes()
-    {
-        FloatBuffer axesBuffer = glfwGetJoystickAxes(GLFW_JOYSTICK_1);
-        if(axesBuffer == null)
-        {
-            return;
-        }
-        LEFT.tick(axesBuffer.get(LEFT.getMapping()) < -0.5f);
-        RIGHT.tick(axesBuffer.get(RIGHT.getMapping()) > 0.5f);
-        UP.tick(axesBuffer.get(UP.getMapping()) < -0.5f);
-        DOWN.tick(axesBuffer.get(DOWN.getMapping()) > 0.5f);
-    }
-}

+ 0 - 8
src/me/hammerle/snuviengine/api/IGame.java

@@ -1,8 +0,0 @@
-package me.hammerle.snuviengine.api;
-
-public interface IGame
-{
-    public void tick();
-    public void renderTick(Renderer r, float lag);
-    public void onStop();
-}

+ 72 - 0
src/me/hammerle/snuviengine/api/Key.java

@@ -0,0 +1,72 @@
+package me.hammerle.snuviengine.api;
+
+public final class Key {
+    private boolean down = false;
+    private int time = 0;
+    private boolean released = false;
+    private int key;
+    private boolean rebinding = false;
+    private String name = null;
+
+    protected Key(int key) {
+        this.key = key;
+    }
+
+    public boolean isRebinding() {
+        return rebinding;
+    }
+
+    public String getName() {
+        if(name == null) {
+            name = KeyNames.getName(key);
+        }
+        return name;
+    }
+
+    protected void setRebinding(boolean b) {
+        rebinding = b;
+    }
+
+    protected void press() {
+        down = true;
+    }
+
+    protected void release() {
+        released = true;
+    }
+
+    protected void tick() {
+        if(down) {
+            time++;
+            down = !released;
+        } else if(released) {
+            released = false;
+            time = 0;
+        }
+    }
+
+    public boolean isReleased() {
+        return released;
+    }
+
+    public boolean isDown() {
+        return down;
+    }
+
+    public int getTime() {
+        return time;
+    }
+
+    public void setTime(int time) {
+        this.time = time;
+    }
+
+    protected void setKey(int key) {
+        this.key = key;
+        name = null;
+    }
+
+    public int getKey() {
+        return key;
+    }
+}

+ 0 - 121
src/me/hammerle/snuviengine/api/KeyBinding.java

@@ -1,121 +0,0 @@
-package me.hammerle.snuviengine.api;
-
-import static org.lwjgl.glfw.GLFW.*;
-
-public final class KeyBinding
-{
-    private boolean isDown = false;
-    private int time = 0;
-    private boolean isReleased = false;
-    private int key;
-    private boolean isRebinding = false;
-    private String name = null;
-    
-    protected KeyBinding(int key)
-    {
-        this.key = key;
-    }
-    
-    public boolean isRebinding()
-    {
-        return isRebinding;
-    }
-    
-    public String getName()
-    {
-        if(name == null)
-        {
-            switch(key)
-            {
-                case GLFW_KEY_UP: name = "Up"; break;
-                case GLFW_KEY_DOWN: name = "Down"; break;
-                case GLFW_KEY_LEFT: name = "Left"; break;
-                case GLFW_KEY_RIGHT: name = "Right"; break;
-                case GLFW_KEY_LEFT_SHIFT: name = "L-Shift"; break;
-                case GLFW_KEY_RIGHT_SHIFT: name = "R-Shift"; break;
-                case GLFW_KEY_ESCAPE: name = "Escape"; break;
-                case GLFW_KEY_ENTER: name = "Enter"; break;
-                case GLFW_KEY_SPACE: name = "Space"; break;
-                default:
-                    name = glfwGetKeyName(key, glfwGetKeyScancode(key));
-                    if(name != null)
-                    {
-                        name = name.substring(0, 1).toUpperCase() + name.substring(1);
-                    }
-            }
-            if(name == null)
-            {
-                name = "Unknown";
-            }
-        }
-        return name;
-    }
-    
-    protected void setIsRebinding(boolean b)
-    {
-        isRebinding = b;
-    }
-    
-    protected void onKeyDownEvent()
-    {
-        isDown = true;
-    }
-    
-    protected void onKeyUpEvent()
-    {
-        isReleased = true;
-    }
-    
-    protected void tick()
-    {   
-        if(isDown)
-        {
-            time++;
-            if(isReleased)
-            {
-                isDown = false;
-            }
-        }
-        else if(isReleased)
-        {
-            isReleased = false;
-            time = 0;
-        }
-    }
-    
-    public boolean isReleased()
-    {
-        return isReleased;
-    }
-    
-    public boolean isUp()
-    {
-        return !isDown;
-    }
-    
-    public boolean isDown()
-    {
-        return isDown;
-    }
-    
-    public int getTime()
-    {
-        return time;
-    }
-    
-    public void setTime(int time)
-    {
-        this.time = time;
-    }
-    
-    protected void setKey(int key)
-    {
-        this.key = key;
-        name = null;
-    }
-    
-    public int getKey()
-    {
-        return key;
-    }
-}

+ 0 - 71
src/me/hammerle/snuviengine/api/KeyHandler.java

@@ -1,71 +0,0 @@
-package me.hammerle.snuviengine.api;
-
-import java.util.HashMap;
-
-public final class KeyHandler
-{
-    private final static HashMap<Integer, KeyBinding> BINDINGS = new HashMap<>();
-    private static KeyBinding rebind = null;
-    
-    public static KeyBinding register(int key)
-    {
-        KeyBinding binding = new KeyBinding(key);
-        if(BINDINGS.putIfAbsent(key, binding) != null)
-        {
-            throw new IllegalArgumentException(String.format("the key '%s' has already been registered", key));
-        }
-        return binding;
-    }
-    
-    public static void rebind(KeyBinding binding)
-    {
-        rebind = binding;
-        if(binding != null)
-        {
-            binding.setIsRebinding(true);
-        }
-    }
-    
-    public static void rebind(KeyBinding binding, int key)
-    {
-        if(BINDINGS.get(binding.getKey()) == binding)
-        {
-            BINDINGS.remove(binding.getKey());
-        }
-        binding.setKey(key);
-        BINDINGS.put(key, binding);
-    }
-
-    protected static void onKeyDownEvent(int key) 
-    {
-        if(rebind != null)
-        {
-            rebind.setIsRebinding(false);
-            if(!BINDINGS.containsKey(key))
-            {
-                rebind(rebind, key);
-            }
-            rebind = null;
-            return;
-        }
-        KeyBinding binding = BINDINGS.get(key);
-        if(binding != null)
-        {
-            binding.onKeyDownEvent();
-        }
-    }
-
-    protected static void onKeyUpEvent(int key) 
-    {
-        KeyBinding binding = BINDINGS.get(key);
-        if(binding != null)
-        {
-            binding.onKeyUpEvent();
-        }
-    }
-    
-    protected static void tick()
-    {
-        BINDINGS.values().forEach(binding -> binding.tick());
-    }
-}

+ 80 - 0
src/me/hammerle/snuviengine/api/KeyNames.java

@@ -0,0 +1,80 @@
+package me.hammerle.snuviengine.api;
+
+import java.util.HashMap;
+import static org.lwjgl.glfw.GLFW.*;
+
+public final class KeyNames {
+    private final static HashMap<Integer, String> NAMES = new HashMap<>();
+
+    static {
+        NAMES.put(GLFW_KEY_UP, "Up");
+        NAMES.put(GLFW_KEY_DOWN, "Down");
+        NAMES.put(GLFW_KEY_LEFT, "Left");
+        NAMES.put(GLFW_KEY_RIGHT, "Right");
+        NAMES.put(GLFW_KEY_LEFT_SHIFT, "L-Shift");
+        NAMES.put(GLFW_KEY_RIGHT_SHIFT, "R-Shift");
+        NAMES.put(GLFW_KEY_ESCAPE, "Escape");
+        NAMES.put(GLFW_KEY_ENTER, "Enter");
+        NAMES.put(GLFW_KEY_SPACE, "Space");
+        NAMES.put(GLFW_KEY_WORLD_2, "World 2");
+        NAMES.put(GLFW_KEY_TAB, "Tab");
+        NAMES.put(GLFW_KEY_BACKSPACE, "Backspace");
+        NAMES.put(GLFW_KEY_INSERT, "Insert");
+        NAMES.put(GLFW_KEY_DELETE, "Delete");
+        NAMES.put(GLFW_KEY_PAGE_UP, "Page Up");
+        NAMES.put(GLFW_KEY_PAGE_DOWN, "Page Down");
+        NAMES.put(GLFW_KEY_HOME, "Home");
+        NAMES.put(GLFW_KEY_END, "End");
+        NAMES.put(GLFW_KEY_CAPS_LOCK, "Caps Lock");
+        NAMES.put(GLFW_KEY_SCROLL_LOCK, "Scroll Lock");
+        NAMES.put(GLFW_KEY_NUM_LOCK, "Num Lock");
+        NAMES.put(GLFW_KEY_PRINT_SCREEN, "Print Screen");
+        NAMES.put(GLFW_KEY_PAUSE, "Pause");
+        NAMES.put(GLFW_KEY_F1, "F1");
+        NAMES.put(GLFW_KEY_F2, "F2");
+        NAMES.put(GLFW_KEY_F3, "F3");
+        NAMES.put(GLFW_KEY_F4, "F4");
+        NAMES.put(GLFW_KEY_F5, "F5");
+        NAMES.put(GLFW_KEY_F6, "F6");
+        NAMES.put(GLFW_KEY_F7, "F7");
+        NAMES.put(GLFW_KEY_F8, "F8");
+        NAMES.put(GLFW_KEY_F9, "F9");
+        NAMES.put(GLFW_KEY_F10, "F10");
+        NAMES.put(GLFW_KEY_F11, "F11");
+        NAMES.put(GLFW_KEY_F12, "F12");
+        NAMES.put(GLFW_KEY_F13, "F13");
+        NAMES.put(GLFW_KEY_F14, "F14");
+        NAMES.put(GLFW_KEY_F15, "F15");
+        NAMES.put(GLFW_KEY_F16, "F16");
+        NAMES.put(GLFW_KEY_F17, "F17");
+        NAMES.put(GLFW_KEY_F18, "F18");
+        NAMES.put(GLFW_KEY_F19, "F19");
+        NAMES.put(GLFW_KEY_F20, "F20");
+        NAMES.put(GLFW_KEY_F21, "F21");
+        NAMES.put(GLFW_KEY_F22, "F22");
+        NAMES.put(GLFW_KEY_F23, "F23");
+        NAMES.put(GLFW_KEY_F24, "F24");
+        NAMES.put(GLFW_KEY_F25, "F25");
+        NAMES.put(GLFW_KEY_KP_ENTER, "Keypad Enter");
+        NAMES.put(GLFW_KEY_LEFT_CONTROL, "L-Control");
+        NAMES.put(GLFW_KEY_LEFT_ALT, "L-Alt");
+        NAMES.put(GLFW_KEY_LEFT_SUPER, "L-Super");
+        NAMES.put(GLFW_KEY_RIGHT_CONTROL, "R-Control");
+        NAMES.put(GLFW_KEY_RIGHT_ALT, "R-Alt");
+        NAMES.put(GLFW_KEY_RIGHT_SUPER, "R-Super");
+        NAMES.put(GLFW_KEY_MENU, "Menu");
+        NAMES.put(GLFW_KEY_LAST, "Last");
+    }
+
+    public static String getName(int key) {
+        String name = NAMES.get(key);
+        if(name != null) {
+            return name;
+        }
+        name = glfwGetKeyName(key, glfwGetKeyScancode(key));
+        if(name != null) {
+            return name.substring(0, 1).toUpperCase() + name.substring(1);
+        }
+        return "Unknown";
+    }
+}

+ 69 - 0
src/me/hammerle/snuviengine/api/Keys.java

@@ -0,0 +1,69 @@
+package me.hammerle.snuviengine.api;
+
+import java.util.HashMap;
+
+public final class Keys {
+    private final HashMap<Integer, Key> bindings = new HashMap<>();
+    private Key rebind = null;
+
+    protected Keys() {
+    }
+
+    public Key register(int key) {
+        if(bindings.containsKey(key)) {
+            return null;
+        }
+        Key binding = new Key(key);
+        bindings.put(key, binding);
+        return binding;
+    }
+
+    public void rebindOnNextKeyPress(Key binding) {
+        rebind = binding;
+        if(binding != null) {
+            binding.setRebinding(true);
+        }
+    }
+
+    public void rebind(Key binding, int key) {
+        if(bindings.get(binding.getKey()) == binding) {
+            bindings.remove(binding.getKey());
+        }
+        binding.setKey(key);
+        bindings.put(key, binding);
+    }
+
+    protected void press(int key) {
+        if(isRebinding()) {
+            rebindMarkedBinding(key);
+            return;
+        }
+        Key binding = bindings.get(key);
+        if(binding != null) {
+            binding.press();
+        }
+    }
+
+    private boolean isRebinding() {
+        return rebind != null;
+    }
+
+    private void rebindMarkedBinding(int key) {
+        rebind.setRebinding(false);
+        if(!bindings.containsKey(key)) {
+            rebind(rebind, key);
+        }
+        rebind = null;
+    }
+
+    protected void release(int key) {
+        Key binding = bindings.get(key);
+        if(binding != null) {
+            binding.release();
+        }
+    }
+
+    protected void tick() {
+        bindings.values().forEach(binding -> binding.tick());
+    }
+}

+ 27 - 37
src/me/hammerle/snuviengine/api/MatrixStack.java

@@ -1,81 +1,71 @@
 package me.hammerle.snuviengine.api;
 
-public class MatrixStack
-{
+public final class MatrixStack {
     private final int capacity;
     private final float[][] stack;
     private int index = 0;
-    
-    protected MatrixStack(int capacity)
-    {
+
+    protected MatrixStack(int capacity) {
         this.capacity = capacity;
         stack = new float[capacity][16];
-        
         stack[index][0] = 1.0f;
         stack[index][5] = 1.0f;
         stack[index][10] = 1.0f;
         stack[index][15] = 1.0f;
     }
-    
-    protected float[] getData()
-    {
+
+    protected float[] getData() {
         return stack[index];
     }
-    
-    protected void push()
-    {
-        index++;
-        if(index >= capacity)
-        {
-            throw new ShaderException("matrix stack overflow, max capacity is " + capacity);
+
+    protected boolean push() {
+        if(index + 1 >= capacity) {
+            return false;
         }
+        index++;
         System.arraycopy(stack[index - 1], 0, stack[index], 0, 16);
+        return true;
     }
-    
-    protected void pop()
-    {
-        if(index <= 0)
-        {
-            throw new ShaderException("matrix stack underflow");
+
+    protected boolean pop() {
+        if(index - 1 < 0) {
+            return false;
         }
         index--;
+        return true;
     }
-    
-    protected void scale(float sx, float sy) 
-    {
+
+    protected void scale(float sx, float sy) {
         stack[index][0] *= sx;
         stack[index][1] *= sx;
         stack[index][4] *= sy;
         stack[index][5] *= sy;
     }
-    
-    protected void translate(float tx, float ty) 
-    {
+
+    protected void translate(float tx, float ty) {
         stack[index][12] += stack[index][0] * tx + stack[index][4] * ty;
         stack[index][13] += stack[index][1] * tx + stack[index][5] * ty;
     }
-    
-    protected void translateTo(float tx, float ty) 
-    {
+
+    protected void translateTo(float tx, float ty) {
         stack[index][0] = 1.0f;
         stack[index][1] = 0.0f;
         stack[index][4] = 0.0f;
-        stack[index][5] = 1.0f; 
+        stack[index][5] = 1.0f;
         stack[index][12] = tx;
         stack[index][13] = ty;
     }
-    
-    protected void rotate(float angle) 
-    {
+
+    protected void rotate(float angle) {
         angle = (float) Math.toRadians(angle);
         float cos = (float) Math.cos(angle);
         float sin = (float) Math.sin(angle);
-        
+
         float a1 = stack[index][0];
         float a2 = stack[index][4];
         stack[index][0] = a1 * cos - a2 * sin;
         stack[index][4] = a1 * sin + a2 * cos;
-        
+
         a1 = stack[index][1];
         a2 = stack[index][5];
         stack[index][1] = a1 * cos - a2 * sin;

+ 127 - 308
src/me/hammerle/snuviengine/api/Renderer.java

@@ -1,59 +1,47 @@
 package me.hammerle.snuviengine.api;
 
-import java.io.IOException;
-import java.net.URL;
 import java.nio.FloatBuffer;
-import java.util.ArrayList;
-import java.util.Scanner;
 import org.lwjgl.BufferUtils;
 import static org.lwjgl.opengl.GL11.*;
 import static org.lwjgl.opengl.GL14.*;
 import static org.lwjgl.opengl.GL20.*;
 
-public final class Renderer
-{
-    private final int program;
-    
+public final class Renderer {
     private float width;
     private float height;
     private int scale;
-    
-    private final FontRenderer fontRenderer;
+    private final FontRenderer fontRenderer = new FontRenderer();
     private final ColorRenderer colorRenderer = new ColorRenderer();
     private final DirectTextureRenderer textureRenderer = new DirectTextureRenderer();
-    
+
     // uniform stuff
     private final int unifViewMatrix;
     private final int unifModelMatrix;
     private final MatrixStack modelMatrix = new MatrixStack(20);
-    
+
     private final int unifAmbientLight;
     private final int[][] unifLight = new int[32][3];
-    
-    private final int unifUseTexture;
-    private final int unifUseColor;
-    private final int unifUseLight;
-    private final int unifUseMixColor;
+
+    private final UniformBoolean useTexture;
+    private final UniformBoolean useColor;
+    private final UniformBoolean useLight;
+    private final UniformBoolean useMixColor;
     private final int unifMixColorLoc;
-    private final float[] unifMixColor = new float[] {0.0f, 0.0f, 0.0f, 0.0f};
- 
-    protected Renderer(int width, int height)
-    {
-        program = createShaderProgram("vertex.vs", "fragment.fs");
+    private final float[] unifMixColor = new float[]{0.0f, 0.0f, 0.0f, 0.0f};
+
+    protected Renderer(int program, int width, int height) {
         glUseProgram(program);
-        
+
         unifViewMatrix = glGetUniformLocation(program, "viewMatrix");
-        fontRenderer = new FontRenderer();
-        setViewPort(width, height);
-        
+        setSize(width, height);
+
         unifModelMatrix = glGetUniformLocation(program, "modelMatrix");
         updateMatrix();
-        
+
         unifAmbientLight = glGetUniformLocation(program, "ambientLight");
         setAmbientLight(1.0f, 1.0f, 1.0f);
-        
-        for(int index = 0; index < unifLight.length; index++)
-        {
+
+        for(int index = 0; index < unifLight.length; index++) {
             unifLight[index][0] = glGetUniformLocation(program, String.format("lights[%d].color", index));
             unifLight[index][1] = glGetUniformLocation(program, String.format("lights[%d].pos", index));
             unifLight[index][2] = glGetUniformLocation(program, String.format("lights[%d].strength", index));
@@ -61,337 +49,168 @@ public final class Renderer
             setLightLocation(index, 0.0f, 0.0f);
             setLightStrength(index, 0.0f);
         }
-        
-        unifUseTexture = glGetUniformLocation(program, "useTexture");
-        setTextureEnabled(false);
-        
-        unifUseColor = glGetUniformLocation(program, "useColor");
-        setColorEnabled(false);
-        
-        unifUseLight = glGetUniformLocation(program, "useLight");
-        setLightEnabled(false);
-        
-        unifUseMixColor = glGetUniformLocation(program, "useMixColor");
-        setMixColorEnabled(false);
+
+        useTexture = new UniformBoolean(program, "useTexture", false);
+        useColor = new UniformBoolean(program, "useColor", false);
+        useLight = new UniformBoolean(program, "useLight", false);
+        useMixColor = new UniformBoolean(program, "useMixColor", false);
+
         unifMixColorLoc = glGetUniformLocation(program, "mixColor");
         setMixColor(0.0f, 0.0f, 0.0f, 0.0f);
-        
-        setViewPort(width, height);
+
+        setSize(width, height);
     }
-    
-    public FontRenderer getFontRenderer()
-    {
+
+    public FontRenderer getFontRenderer() {
         return fontRenderer;
     }
-    
-    public ColorRenderer getColorRenderer()
-    {
+
+    public ColorRenderer getColorRenderer() {
         return colorRenderer;
     }
-    
-    public DirectTextureRenderer getTextureRenderer()
-    {
+
+    public DirectTextureRenderer getTextureRenderer() {
         return textureRenderer;
     }
-    
-    private void updateViewMatrix()
-    {
+
+    private void updateViewMatrix() {
         FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
-        
-        buffer.put(2.0f / width);
-        buffer.put(0.0f);
-        buffer.put(0.0f);
-        buffer.put(0.0f);
-        
-        buffer.put(0.0f);
-        buffer.put(-2.0f / height);
-        buffer.put(0.0f);
-        buffer.put(0.0f);
-        
-        buffer.put(0.0f);
-        buffer.put(0.0f);
-        buffer.put(-1.0f / height);
-        buffer.put(0.0f);
-        
-        buffer.put(-1.0f);
-        buffer.put(1.0f);
-        buffer.put(0.5f);
-        buffer.put(1.0f);
-        
+        addToBuffer(buffer, 2.0f / width, 0.0f, 0.0f, 0.0f);
+        addToBuffer(buffer, 0.0f, -2.0f / height, 0.0f, 0.0f);
+        addToBuffer(buffer, 0.0f, 0.0f, -1.0f / height, 0.0f);
+        addToBuffer(buffer, -1.0f, 1.0f, 0.5f, 1.0f);
         buffer.flip();
-        
         glUniformMatrix4fv(unifViewMatrix, false, buffer);
     }
-    
-    protected void setViewPort(float width, float height)
-    {
+
+    private void addToBuffer(FloatBuffer buffer, float a, float b, float c, float d) {
+        buffer.put(a);
+        buffer.put(b);
+        buffer.put(c);
+        buffer.put(d);
+    }
+
+    protected void setSize(int width, int height) {
         scale = 1;
-        while(width / (scale + 1) >= 400 && height / (scale + 1) >= 300)
-        {
+        while(width / (scale + 1) >= 400 && height / (scale + 1) >= 300) {
             scale++;
         }
-        
         fontRenderer.setScale(scale);
-
         this.width = width / scale;
         this.height = height / scale;
-        
         updateViewMatrix();
     }
-    
-    public int getViewScale()
-    {
+
+    public int getViewScale() {
         return scale;
     }
-    
-    public float getViewWidth()
-    {
+
+    public float getViewWidth() {
         return width;
     }
-    
-    public float getViewHeight()
-    {
+
+    public float getViewHeight() {
         return height;
     }
-    
-    public void updateMatrix()
-    {
+
+    public void updateMatrix() {
         glUniformMatrix4fv(unifModelMatrix, false, modelMatrix.getData());
     }
-    
-    public void pushMatrix()
-    {
+
+    public void pushMatrix() {
         modelMatrix.push();
     }
-    
-    public void popMatrix()
-    {
+
+    public void popMatrix() {
         modelMatrix.pop();
     }
-    
-    public void translate(float tx, float ty)
-    {
+
+    public void translate(float tx, float ty) {
         modelMatrix.translate(tx, ty);
     }
-    
-    public void translateTo(float tx, float ty)
-    {
+
+    public void translateTo(float tx, float ty) {
         modelMatrix.translateTo(tx, ty);
     }
-    
-    public void scale(float sx, float sy)
-    {
+
+    public void scale(float sx, float sy) {
         modelMatrix.scale(sx, sy);
     }
-    
-    public void rotate(float angle)
-    {
+
+    public void rotate(float angle) {
         modelMatrix.rotate(angle);
     }
-    
-    public void setAmbientLight(float r, float g, float b)
-    {
+
+    public void setAmbientLight(float r, float g, float b) {
         glUniform3f(unifAmbientLight, r, g, b);
     }
-    
-    private void checkLightIndex(int index)
-    {
-        if(index < 0 || index > unifLight.length)
-        {
-            throw new ShaderException("'%d' is not a valid light index", index);
+
+    private boolean isValidLightIndex(int index) {
+        return index >= 0 && index < unifLight.length;
+    }
+
+    public void setLightColor(int index, float r, float g, float b) {
+        if(isValidLightIndex(index)) {
+            glUniform3f(unifLight[index][0], r, g, b);
+        }
+    }
+
+    public void setLightLocation(int index, float x, float y) {
+        if(isValidLightIndex(index)) {
+            glUniform2f(unifLight[index][1], x, y);
+        }
+    }
+
+    public void setLightStrength(int index, float strength) {
+        if(isValidLightIndex(index)) {
+            glUniform1f(unifLight[index][2], strength);
         }
     }
-    
-    public void setLightColor(int index, float r, float g, float b)
-    {
-        checkLightIndex(index);
-        glUniform3f(unifLight[index][0], r, g, b);
-    }
-    
-    public void setLightLocation(int index, float x, float y)
-    {
-        checkLightIndex(index);
-        glUniform2f(unifLight[index][1], x, y);
-    }
-    
-    public void setLightStrength(int index, float strength)
-    {
-        checkLightIndex(index);
-        glUniform1f(unifLight[index][2], strength);
-    }
-    
-    public void setTextureEnabled(boolean use)
-    {
-        glUniform1i(unifUseTexture, use ? 1 : 0);
-    }
-    
-    public void setColorEnabled(boolean use)
-    {
-        glUniform1i(unifUseColor, use ? 1 : 0);
-    }
-    
-    public void setMixColorEnabled(boolean use)
-    {
-        glUniform1i(unifUseMixColor, use ? 1 : 0);
-    }
-    
-    public void setMixColor(float r, float g, float b, float a)
-    {
+
+    public void setTextureEnabled(boolean use) {
+        useTexture.set(use);
+    }
+
+    public void setColorEnabled(boolean use) {
+        useColor.set(use);
+    }
+
+    public void setMixColorEnabled(boolean use) {
+        useMixColor.set(use);
+    }
+
+    public void setMixColor(float r, float g, float b, float a) {
         unifMixColor[0] = r;
         unifMixColor[1] = g;
         unifMixColor[2] = b;
         unifMixColor[3] = a;
         glUniform4fv(unifMixColorLoc, unifMixColor);
     }
-    
-    public void setLightEnabled(boolean use)
-    {
-        glUniform1i(unifUseLight, use ? 1 : 0);
-    }
-    
-    public void setDepthTestEnabled(boolean use)
-    {
-        if(use)
-        {
-            glEnable(GL_DEPTH_TEST);
-            glDepthFunc(GL_LEQUAL);
-        }
-        else
-        {
-            glDisable(GL_DEPTH_TEST);
-        }
+
+    public void setLightEnabled(boolean use) {
+        useLight.set(use);
     }
-    
-    public void setBlendingEnabled(boolean use)
-    {
-        if(use)
-        {
-            glEnable(GL_BLEND);
-            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-            glBlendEquation(GL_FUNC_ADD);
-        }
-        else
-        {
-            glDisable(GL_BLEND);
-        }
+
+    public void enableDepthTest() {
+        glEnable(GL_DEPTH_TEST);
+        glDepthFunc(GL_LEQUAL);
     }
-    
-    public float round(float f)
-    {
-        return ((int) (f * scale)) / scale;
-    }
-    
-    // -------------------------------------------------------------------------
-    // general stuff
-    // -------------------------------------------------------------------------
-    
-    private String[] readFile(String name)
-    {
-        URL url = Renderer.class.getClassLoader().getResource("me/hammerle/snuviengine/shader/" + name);
-        if(url == null)
-        {
-            throw new ShaderException("failed reading shader '%s'", name);
-        }
-        ArrayList<String> strings = new ArrayList<>();
-        try(Scanner scanner = new Scanner(url.openStream()))
-        {
-            while(scanner.hasNext())
-            {
-                strings.add(scanner.nextLine() + "\n");
-            }
-            return strings.toArray(new String[strings.size()]);
-        }
-        catch(IOException ex)
-        {
-            throw new ShaderException("failed reading shader '%s'", name);
-        }
+
+    public void disableDepthTest() {
+        glDisable(GL_DEPTH_TEST);
+    }
+
+    public void enableBlending() {
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        glBlendEquation(GL_FUNC_ADD);
+    }
+
+    public void disableBlending() {
+        glDisable(GL_BLEND);
     }
-    
-    private String getError()
-    {
-        StringBuilder sb = new StringBuilder();
-        
-        int glErr = glGetError();
-        while(glErr != GL_NO_ERROR)
-        {
-            sb.append(glErr);
-            sb.append(" ");
-            glErr = glGetError();
-        }
-        
-        return sb.toString();
-    }
-    
-    private int createShaderProgram(String vertex, String fragment)
-    {
-        // ---------------------------------------------------------------------
-        // vertex shader
-        // ---------------------------------------------------------------------
-          
-        String vShaderSource[] = readFile(vertex);
-        
-        int vShader = glCreateShader(GL_VERTEX_SHADER);
-        glShaderSource(vShader, vShaderSource);
-        glCompileShader(vShader);
-
-        String error = getError();
-        if(!error.isEmpty())
-        {
-            throw new ShaderException("failed compiling vertex shader '%s' %s", vertex, error);
-        }
-        
-        int compiled = glGetShaderi(vShader, GL_COMPILE_STATUS);
-        if(compiled != 1)
-        {
-            throw new ShaderException("failed compiling vertex shader '%s' %d %s", vertex, compiled, glGetShaderInfoLog(vShader));
-        }
-        
-        // ---------------------------------------------------------------------
-        // fragment shader
-        // ---------------------------------------------------------------------
-
-        String fShaderSource[] = readFile(fragment);
-        
-        int fShader = glCreateShader(GL_FRAGMENT_SHADER);
-        glShaderSource(fShader, fShaderSource);
-        glCompileShader(fShader);
-
-        error = getError();
-        if(!error.isEmpty())
-        {
-            throw new ShaderException("failed compiling fragment shader '%s' %s", fragment, error);
-        }
-        
-        compiled = glGetShaderi(fShader, GL_COMPILE_STATUS);
-        if(compiled != 1)
-        {
-            throw new ShaderException("failed compiling fragment shader '%s' %d %s", fragment, compiled, glGetShaderInfoLog(fShader));
-        }
-        
-        // ---------------------------------------------------------------------
-        // linking
-        // ---------------------------------------------------------------------
-
-        int vfprogram = glCreateProgram();
-        glAttachShader(vfprogram, vShader);
-        glAttachShader(vfprogram, fShader);
-        glLinkProgram(vfprogram);
-
-        error = getError();
-        if(!error.isEmpty())
-        {
-            throw new ShaderException("failed linking shaders '%s' and '%s' %s", vertex, fragment, error);
-        }
-        
-        compiled = glGetProgrami(vfprogram, GL_LINK_STATUS);
-        if(compiled != 1)
-        {
-            throw new ShaderException("failed linking shaders '%s' and '%s' %d %s", vertex, fragment, compiled, glGetProgramInfoLog(vfprogram));
-        }
 
-        glDeleteShader(vShader);
-        glDeleteShader(fShader);
-        
-        return vfprogram;
-    }  
+    public float round(float f) {
+        return ((int) (f * scale)) / (float) scale;
+    }
 }

+ 125 - 0
src/me/hammerle/snuviengine/api/Shader.java

@@ -0,0 +1,125 @@
+package me.hammerle.snuviengine.api;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Scanner;
+import static org.lwjgl.opengl.GL11.GL_NO_ERROR;
+import static org.lwjgl.opengl.GL11.glGetError;
+import static org.lwjgl.opengl.GL20.GL_COMPILE_STATUS;
+import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER;
+import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;
+import static org.lwjgl.opengl.GL20.GL_VERTEX_SHADER;
+import static org.lwjgl.opengl.GL20.glAttachShader;
+import static org.lwjgl.opengl.GL20.glCompileShader;
+import static org.lwjgl.opengl.GL20.glCreateProgram;
+import static org.lwjgl.opengl.GL20.glCreateShader;
+import static org.lwjgl.opengl.GL20.glDeleteShader;
+import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
+import static org.lwjgl.opengl.GL20.glGetProgrami;
+import static org.lwjgl.opengl.GL20.glGetShaderInfoLog;
+import static org.lwjgl.opengl.GL20.glGetShaderi;
+import static org.lwjgl.opengl.GL20.glLinkProgram;
+import static org.lwjgl.opengl.GL20.glShaderSource;
+
+public class Shader {
+    public static int createShaderProgram(String vertex, String fragment) {
+        int vertexShader = readAndCompileShader(vertex, GL_VERTEX_SHADER);
+        if(vertexShader == -1) {
+            return -1;
+        }
+        int fragmentShader = readAndCompileShader(fragment, GL_FRAGMENT_SHADER);
+        if(fragmentShader == -1) {
+            return -1;
+        }
+        return createShaderProgram(vertexShader, fragmentShader);
+    }
+
+    private static int readAndCompileShader(String file, int shaderType) {
+        String[] shaderSource = readFile(file);
+        if(shaderSource == null) {
+            return -1;
+        }
+        return compileShader(file, shaderSource, shaderType);
+    }
+
+    private static String[] readFile(String name) {
+        URL url = Renderer.class.getClassLoader().getResource("me/hammerle/snuviengine/shader/" + name);
+        if(url == null) {
+            printError("failed reading shader '%s'", name);
+            return null;
+        }
+        return readUrl(url);
+    }
+
+    private static String[] readUrl(URL url) {
+        try(Scanner scanner = new Scanner(url.openStream())) {
+            return readScanner(scanner);
+        } catch(IOException ex) {
+            printError("failed reading shader '%s'", url);
+            return null;
+        }
+    }
+
+    private static String[] readScanner(Scanner scanner) {
+        ArrayList<String> strings = new ArrayList<>();
+        while(scanner.hasNext()) {
+            strings.add(scanner.nextLine() + "\n");
+        }
+        return strings.toArray(new String[strings.size()]);
+    }
+
+    private static int compileShader(String name, String[] shaderSource, int shaderType) {
+        int shader = glCreateShader(shaderType);
+        glShaderSource(shader, shaderSource);
+        glCompileShader(shader);
+        if(checkForShaderError(name, shader)) {
+            return -1;
+        }
+        return shader;
+    }
+
+    private static boolean checkForShaderError(String name, int shader) {
+        int error = glGetError();
+        if(error != GL_NO_ERROR) {
+            printError("failed compiling shader '%s' with error code: %d", name, error);
+            return true;
+        }
+        int compiled = glGetShaderi(shader, GL_COMPILE_STATUS);
+        if(compiled != 1) {
+            printError("failed compiling shader '%s': %s", name, glGetShaderInfoLog(shader));
+            return true;
+        }
+        return false;
+    }
+
+    private static void printError(String format, Object... o) {
+        System.out.println(String.format(format, o));
+    }
+
+    private static int createShaderProgram(int vertexShader, int fragmentShader) {
+        int program = glCreateProgram();
+        glAttachShader(program, vertexShader);
+        glAttachShader(program, fragmentShader);
+        glLinkProgram(program);
+        if(checkForLinkingError(program)) {
+            return -1;
+        }
+        return program;
+    }
+
+    private static boolean checkForLinkingError(int program) {
+        int error = glGetError();
+        if(error != GL_NO_ERROR) {
+            printError("failed linking shaders with error code: %d", error);
+            return true;
+        }
+        int compiled = glGetProgrami(program, GL_LINK_STATUS);
+        if(compiled != 1) {
+            printError("failed linking shaders: %s", glGetProgramInfoLog(program));
+            return true;
+        }
+        return false;
+    }
+
+}

+ 0 - 9
src/me/hammerle/snuviengine/api/ShaderException.java

@@ -1,9 +0,0 @@
-package me.hammerle.snuviengine.api;
-
-public class ShaderException extends RuntimeException
-{
-    public ShaderException(String format, Object... args)
-    {
-        super(String.format(format, args));
-    }
-}

+ 22 - 0
src/me/hammerle/snuviengine/api/Stats.java

@@ -0,0 +1,22 @@
+package me.hammerle.snuviengine.api;
+
+public class Stats {
+    private final Timer framesPerSecond = new Timer();
+    private final Timer ticksPerSecond = new Timer();
+
+    protected void updateFramesPerSecond() {
+        framesPerSecond.update();
+    }
+
+    public double getFramesPerSecond() {
+        return framesPerSecond.getCallPerSecond();
+    }
+
+    protected void updateTicksPerSecond() {
+        ticksPerSecond.update();
+    }
+
+    public double getTicksPerSecond() {
+        return ticksPerSecond.getCallPerSecond();
+    }
+}

+ 56 - 96
src/me/hammerle/snuviengine/api/Texture.java

@@ -10,106 +10,76 @@ import static org.lwjgl.opengl.GL11.*;
 import static org.lwjgl.opengl.GL12.*;
 import static org.lwjgl.opengl.GL13.*;
 
-public class Texture
-{
-    public class Animation
-    {
-        private IntBuffer[] data;
-        private int[] wi;
-        private int[] he;
+public class Texture {
+    public class Animation {
+        private final IntBuffer[] data;
+        private final int[] widths;
+        private final int[] heights;
         private final int offsetX;
         private final int offsetY;
         private int index = 0;
-        
-        private Animation(int offsetX, int offsetY, String... paths)
-        {
+
+        private Animation(int offsetX, int offsetY, String... paths) {
             this.offsetX = offsetX;
             this.offsetY = offsetY;
-            
             int l = paths.length;
             data = new IntBuffer[l];
-            wi = new int[l];
-            he = new int[l];
-            
-            for(int i = 0; i < l; i++)
-            {
-                BufferedImage image;
-                try
-                {
-                    image = ImageIO.read(new File(paths[i]));
-                }
-                catch(IOException | IllegalArgumentException ex)
-                {
-                    throw new TextureException("cannot read texture file '%s'", paths[i]);
-                }
+            widths = new int[l];
+            heights = new int[l];
 
-                if(offsetX + image.getWidth() > w || offsetY + image.getHeight() > h)
-                {
-                    System.out.println(offsetX + " " + image.getWidth() + " " +  w + " " + offsetY + " " + image.getHeight() + " " + h);
-                    throw new TextureException("animation image '%s'is out of range", paths[i]);
+            for(int i = 0; i < l; i++) {
+                BufferedImage image = readImage(paths[i], false);
+                if(offsetX + image.getWidth() > width || offsetY + image.getHeight() > height) {
+                    System.out.println(String.format("animation image '%s'is out of range", paths[i]));
+                    return;
                 }
-                
-                wi[i] = image.getWidth();
-                he[i] = image.getHeight();
-                IntBuffer buffer = BufferUtils.createIntBuffer(wi[i] * he[i]);
-                for(int y = 0; y < he[i]; y++)
-                {
-                    for(int x = 0; x < wi[i]; x++)
-                    {
-                        int c = image.getRGB(x, y);
-                        buffer.put((c << 8) | ((c >> 24) & 0xFF));
-                    }
-                }
-                buffer.flip();
-                data[i] = buffer;
+                widths[i] = image.getWidth();
+                heights[i] = image.getHeight();
+                data[i] = createBufferFromImage(image);
             }
         }
-        
-        public void nextFrame()
-        {
+
+        public void nextFrame() {
             glActiveTexture(GL_TEXTURE0);
-            glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, wi[index], he[index], GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, data[index]);
+            glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, widths[index], heights[index], GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, data[index]);
             index = (index + 1) % data.length;
         }
     }
-    
-    private static int boundTexture = -1;
-    private int texture = -1; 
-    private final int w;
-    private final int h;
-    
-    protected Texture(String path, boolean internal)
-    {
-        BufferedImage image;
-        try
-        {
-            if(internal)
-            {
-                image = ImageIO.read(Texture.class.getClassLoader().getResource("me/hammerle/snuviengine/resources/" + path));
-            }
-            else
-            {
-                image = ImageIO.read(new File(path));
+
+    private static BufferedImage readImage(String path, boolean internal) {
+        try {
+            if(internal) {
+                return ImageIO.read(Texture.class.getClassLoader().getResource("me/hammerle/snuviengine/resources/" + path));
             }
+            return ImageIO.read(new File(path));
+        } catch(IOException | IllegalArgumentException ex) {
+            System.out.println(String.format("cannot read texture file '%s': %s", path, ex.getMessage()));
+            return new BufferedImage(0, 0, BufferedImage.TYPE_INT_ARGB);
         }
-        catch(IOException | IllegalArgumentException ex)
-        {
-            throw new TextureException("cannot read texture file '%s'", path);
-        }
-        
-        w = image.getWidth();
-        h = image.getHeight();
-        IntBuffer buffer = BufferUtils.createIntBuffer(w * h);
-        for(int y = 0; y < h; y++)
-        {
-            for(int x = 0; x < w; x++)
-            {
+    }
+
+    private static IntBuffer createBufferFromImage(BufferedImage image) {
+        IntBuffer buffer = BufferUtils.createIntBuffer(image.getWidth() * image.getHeight());
+        for(int y = 0; y < image.getHeight(); y++) {
+            for(int x = 0; x < image.getWidth(); x++) {
                 int c = image.getRGB(x, y);
                 buffer.put((c << 8) | ((c >> 24) & 0xFF));
             }
         }
         buffer.flip();
+        return buffer;
+    }
 
+    private static int boundTexture = -1;
+    private final int texture;
+    private final int width;
+    private final int height;
+
+    protected Texture(String path, boolean internal) {
+        BufferedImage image = readImage(path, internal);
+        IntBuffer buffer = createBufferFromImage(image);
+        width = image.getWidth();
+        height = image.getHeight();
         glActiveTexture(GL_TEXTURE0);
         texture = glGenTextures();
         glBindTexture(GL_TEXTURE_2D, texture);
@@ -117,31 +87,21 @@ public class Texture
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buffer);
-        //glGenerateMipmap(GL_TEXTURE_2D);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buffer);
     }
-    
-    public Texture(String path)
-    {
+
+    public Texture(String path) {
         this(path, false);
     }
-    
-    public Animation addAnimation(int offsetX, int offsetY, String... paths)
-    {
+
+    public Animation addAnimation(int offsetX, int offsetY, String... paths) {
         return new Animation(offsetX, offsetY, paths);
-    } 
+    }
 
-    public void bind()
-    {
-        if(texture == -1)
-        {
-            throw new TextureException("cannot bind non loaded texture");
-        }
-        if(boundTexture != texture)
-        {
-            boundTexture = texture; 
+    public void bind() {
+        if(boundTexture != texture) {
+            boundTexture = texture;
             glBindTexture(GL_TEXTURE_2D, texture);
         }
     }
 }
-

+ 0 - 9
src/me/hammerle/snuviengine/api/TextureException.java

@@ -1,9 +0,0 @@
-package me.hammerle.snuviengine.api;
-
-public class TextureException extends RuntimeException
-{
-    public TextureException(String format, Object... args)
-    {
-        super(String.format(format, args));
-    }
-}

+ 43 - 72
src/me/hammerle/snuviengine/api/TextureRenderer.java

@@ -7,114 +7,85 @@ import static org.lwjgl.opengl.GL15.*;
 import static org.lwjgl.opengl.GL20.*;
 import static org.lwjgl.opengl.GL30.*;
 
-public class TextureRenderer
-{
-    private final int vao;
-    private final int vbo;
-    
+public class TextureRenderer {
+    private final int vertexArray;
+    private final int vertexBuffer;
     private int triangles = 0;
     private FloatBuffer buffer;
-    
     private boolean built = false;
-    
-    public TextureRenderer(int triangles)
-    {
-        buffer = BufferUtils.createFloatBuffer(triangles * 12);
-       
-        vao = glGenVertexArrays();
-        vbo = glGenBuffers();
-
-        GLHelper.glBindVertexArray(vao);
-        GLHelper.glBindBuffer(vbo);
 
+    public TextureRenderer(int triangles) {
+        buffer = BufferUtils.createFloatBuffer(triangles * 12);
+        vertexArray = glGenVertexArrays();
+        vertexBuffer = glGenBuffers();
+        GLHelper.glBindVertexArray(vertexArray);
+        GLHelper.glBindBuffer(vertexBuffer);
         glEnableVertexAttribArray(0);
         glVertexAttribPointer(0, 2, GL_FLOAT, false, 16, 0);
-
-        glEnableVertexAttribArray(1);  
+        glEnableVertexAttribArray(1);
         glVertexAttribPointer(1, 2, GL_FLOAT, false, 16, 8);
     }
-    
-    public void addTriangle(float x1, float y1, float x2, float y2, float x3, float y3, float tx1, float ty1, float tx2, float ty2, float tx3, float ty3)
-    {
-        if(buffer.position() + 12 > buffer.capacity())
-        {
+
+    public void addTriangle(float x1, float y1, float x2, float y2, float x3, float y3, float tx1, float ty1, float tx2, float ty2, float tx3, float ty3) {
+        if(buffer.position() + 12 > buffer.capacity()) {
             FloatBuffer newBuffer = BufferUtils.createFloatBuffer(buffer.capacity() * 2);
             buffer.flip();
             newBuffer.put(buffer);
             buffer = newBuffer;
         }
-        
-        buffer.put(x1);
-        buffer.put(y1);        
-        buffer.put(tx1);
-        buffer.put(ty1);
-        
-        buffer.put(x2);
-        buffer.put(y2);        
-        buffer.put(tx2);
-        buffer.put(ty2);
-        
-        buffer.put(x3);
-        buffer.put(y3);        
-        buffer.put(tx3);
-        buffer.put(ty3);
-        
+        addToBuffer(x1, y1, tx1, ty1);
+        addToBuffer(x2, y2, tx2, ty2);
+        addToBuffer(x3, y3, tx3, ty3);
         triangles++;
     }
-    
-    public void addRectangle(float minX, float minY, float maxX, float maxY, float tMinX, float tMinY, float tMaxX, float tMaxY)
-    {
+
+    private void addToBuffer(float x, float y, float tx, float ty) {
+        buffer.put(x);
+        buffer.put(y);
+        buffer.put(tx);
+        buffer.put(ty);
+    }
+
+    public void addRectangle(float minX, float minY, float maxX, float maxY, float tMinX, float tMinY, float tMaxX, float tMaxY) {
         addTriangle(minX, maxY, minX, minY, maxX, maxY, tMinX, tMaxY, tMinX, tMinY, tMaxX, tMaxY);
         addTriangle(maxX, maxY, minX, minY, maxX, minY, tMaxX, tMaxY, tMinX, tMinY, tMaxX, tMinY);
     }
-    
-    public boolean isBuilt()
-    {
+
+    public boolean isBuilt() {
         return built;
     }
-    
-    public void build()
-    {
-        if(triangles == 0 || built)
-        {
+
+    public void build() {
+        if(triangles == 0 || built) {
             return;
         }
         buffer.flip();
-        GLHelper.glBindVertexArray(vao);
-        GLHelper.glBindBuffer(vbo);
+        GLHelper.glBindVertexArray(vertexArray);
+        GLHelper.glBindBuffer(vertexBuffer);
         glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);
-        
         buffer.limit(buffer.capacity());
         built = true;
     }
-    
-    public void clear()
-    {
+
+    public void clear() {
         triangles = 0;
         buffer.clear();
         built = false;
     }
-    
-    public void draw()
-    {
-        if(triangles == 0)
-        {
+
+    public void draw() {
+        if(triangles == 0 || !built) {
             return;
         }
-        if(!built)
-        {
-            throw new ShaderException("build must be called before drawing");
-        }
-        GLHelper.glBindVertexArray(vao);
-        GLHelper.glBindBuffer(vbo);       
+        GLHelper.glBindVertexArray(vertexArray);
+        GLHelper.glBindBuffer(vertexBuffer);
         glDrawArrays(GL_TRIANGLES, 0, triangles * 3);
     }
-    
-    public void delete()
-    {
+
+    public void delete() {
         buffer = null;
-        glDeleteVertexArrays(vao);
-        glDeleteBuffers(vbo);
+        glDeleteVertexArrays(vertexArray);
+        glDeleteBuffers(vertexBuffer);
         built = false;
     }
 }

+ 7 - 132
src/me/hammerle/snuviengine/api/Timer.java

@@ -1,150 +1,25 @@
 package me.hammerle.snuviengine.api;
 
-import java.nio.FloatBuffer;
-import org.lwjgl.BufferUtils;
-import static org.lwjgl.opengl.GL11.*;
-import static org.lwjgl.opengl.GL15.*;
-import static org.lwjgl.opengl.GL20.*;
-import static org.lwjgl.opengl.GL30.*;
-
-public class Timer
-{
+public class Timer {
     private static final int SIZE = 32;
-    
+
     private int index = -1;
     private final long[] times = new long[SIZE];
     private long sum = 0;
     private double callsPerSecond = 0;
     private long lastTime = System.nanoTime();
-    
-    private final int vao;
-    private final int vbo;
-    
-    private final FloatBuffer buffer = BufferUtils.createFloatBuffer(18);
-    
-    private boolean active = false;
-    
-    private float expectedValue;
-    private final float offset;
-    
-    protected Timer(float expectedValue, float offset)
-    {
-        this.expectedValue = expectedValue;
-        this.offset = offset;
-
-        vao = glGenVertexArrays();
-        vbo = glGenBuffers();
-
-        GLHelper.glBindVertexArray(vao);
-        GLHelper.glBindBuffer(vbo);
-
-        glEnableVertexAttribArray(0);
-        glEnableVertexAttribArray(2);
-
-        glVertexAttribPointer(0, 2, GL_FLOAT, false, 12, 0);
-        glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, true, 12, 8);
 
-        glBufferData(GL_ARRAY_BUFFER, SIZE * buffer.limit() * 4, GL_STATIC_DRAW);
-    }
-    
-    protected void setActive(boolean active)
-    {
-        this.active = active;
-    }
-    
-    protected void update()
-    {
-        index = (index + 1) & (SIZE - 1);
-        
+    public void update() {
+        index = (index + 1) % SIZE;
         long time = System.nanoTime();
         sum -= times[index];
         times[index] = time - lastTime;
         sum += times[index];
         lastTime = time;
         callsPerSecond = (1_000_000_000.0 * SIZE) / sum;
-        
-        if(active)
-        {
-            float minX = index * 3;
-            float minY = 0.0f;
-            float maxX = minX + 2;
-            float maxY = 1_000_000_000.0f / times[index];
-
-            float diff = (maxY - expectedValue) / expectedValue;
-            int r = Math.min((int) (128 * (1 - Math.min(diff, 0.0f) * 16)), 255);
-            int g = Math.min((int) (128 * (1 + Math.max(diff, 0.0f) * 16)), 255);
-            int b = (int) (128 * (1 - Math.abs(diff)));
-            float color = Float.intBitsToFloat(r | (g << 8) | (b << 16) | 0xFF000000);
-            
-            minY += offset;
-            maxY += offset;
-
-            buffer.put(minX);
-            buffer.put(maxY);   
-            buffer.put(color);
-
-            buffer.put(minX);
-            buffer.put(minY);   
-            buffer.put(color);
-
-            buffer.put(maxX);
-            buffer.put(maxY);  
-            buffer.put(color);
-
-            buffer.put(minX);
-            buffer.put(minY);   
-            buffer.put(color);
-
-            buffer.put(maxX);
-            buffer.put(maxY);  
-            buffer.put(color);
-
-            buffer.put(maxX);
-            buffer.put(minY);  
-            buffer.put(color);
-
-            buffer.flip();
-
-            GLHelper.glBindBuffer(vbo);
-            glBufferSubData(GL_ARRAY_BUFFER, index * buffer.limit() * 4, buffer);
-        }
-    }
-    
-    protected void setExpectedValue(float value)
-    {
-        expectedValue = value;
     }
-    
-    protected long getAverageTime()
-    {
-        return sum / SIZE;
-    }
-    
-    protected long getCurrentTime()
-    {
-        return System.nanoTime() - lastTime;
-    }
-    
-    protected long getTime()
-    {
-        return times[index];
-    }
-    
-    protected double getCallsPerSecond()
-    {
-        return callsPerSecond;
-    }
-    
-    protected void draw(Renderer r)
-    {
-        if(active)
-        {
-            r.setTextureEnabled(false);
-            r.setColorEnabled(true);
 
-            GLHelper.glBindBuffer(vbo);
-            GLHelper.glBindVertexArray(vao);
-            glDrawArrays(GL_TRIANGLES, 0, SIZE * 6);
-        }
+    public double getCallPerSecond() {
+        return callsPerSecond;
     }
-}
+}

+ 25 - 0
src/me/hammerle/snuviengine/api/UniformBoolean.java

@@ -0,0 +1,25 @@
+package me.hammerle.snuviengine.api;
+
+import static org.lwjgl.opengl.GL20.glGetUniformLocation;
+import static org.lwjgl.opengl.GL20.glUniform1i;
+
+public class UniformBoolean {
+    private final int location;
+    private boolean value;
+
+    public UniformBoolean(int program, String name, boolean value) {
+        location = glGetUniformLocation(program, name);
+        setUnchecked(value);
+    }
+
+    private void setUnchecked(boolean value) {
+        this.value = value;
+        glUniform1i(location, value ? 1 : 0);
+    }
+
+    public void set(boolean value) {
+        if(this.value != value) {
+            setUnchecked(value);
+        }
+    }
+}

+ 154 - 0
src/me/hammerle/snuviengine/api/Window.java

@@ -0,0 +1,154 @@
+package me.hammerle.snuviengine.api;
+
+import org.lwjgl.glfw.*;
+import org.lwjgl.opengl.*;
+import static org.lwjgl.glfw.GLFW.*;
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.system.MemoryUtil.*;
+
+public final class Window {
+    private final double secondsPerTick;
+    private long window = NULL;
+    private Keys keys = null;
+    private Gamepad gamepad = null;
+    private Renderer renderer;
+    private final Stats stats = new Stats();
+
+    public Window(long nanosPerTick) {
+        secondsPerTick = nanosPerTick / 1_000_000_000.0;
+    }
+
+    public boolean initialize(String name, int width, int height) {
+        setErrorCallback();
+        if(initializeGLFW()) {
+            return false;
+        }
+        setWindowHints();
+        if(createWindow(name, width, height)) {
+            return false;
+        }
+        setKeyCallback();
+        setGamepad();
+        setResizeCallback();
+        createAndSetContext();
+        setBufferSwapInterval();
+        return !initializeRenderer(width, height);
+    }
+
+    private void setErrorCallback() {
+        GLFWErrorCallback.createPrint(System.out).set();
+    }
+
+    private boolean initializeGLFW() {
+        return !glfwInit();
+    }
+
+    private void setWindowHints() {
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+    }
+
+    private boolean createWindow(String name, int width, int height) {
+        window = glfwCreateWindow(width, height, name, NULL, NULL);
+        if(window == NULL) {
+            terminateGLFW();
+            return true;
+        }
+        return false;
+    }
+
+    private void terminateGLFW() {
+        glfwTerminate();
+    }
+
+    private void setKeyCallback() {
+        keys = new Keys();
+        glfwSetKeyCallback(window, (eventWindow, key, scancode, action, mods) -> {
+            if(action == GLFW_PRESS) {
+                keys.press(key);
+            } else if(action == GLFW_RELEASE) {
+                keys.release(key);
+            }
+        });
+    }
+
+    private void setGamepad() {
+        gamepad = new Gamepad(GLFW_JOYSTICK_1);
+    }
+
+    private void setResizeCallback() {
+        glfwSetFramebufferSizeCallback(window, (eventWindow, width, height) -> {
+            glViewport(0, 0, width, height);
+            renderer.setSize(width, height);
+        });
+    }
+
+    private void createAndSetContext() {
+        glfwMakeContextCurrent(window);
+        GL.createCapabilities();
+    }
+
+    private void setBufferSwapInterval() {
+        glfwSwapInterval(1);
+    }
+
+    private boolean initializeRenderer(int width, int height) {
+        int shaderProgram = Shader.createShaderProgram("vertex.vs", "fragment.fs");
+        if(shaderProgram == -1) {
+            return true;
+        }
+        renderer = new Renderer(shaderProgram, width, height);
+        return false;
+    }
+
+    public void open(Game game) {
+        game.onCompleteInitialization(stats, keys, gamepad);
+        runGameLoop(game);
+        game.onStop();
+        destroyWindow();
+        terminateGLFW();
+        freeErrorCallback();
+    }
+
+    private void runGameLoop(Game game) {
+        double lastSecondTime = glfwGetTime();
+        double lag = 0;
+        while(!glfwWindowShouldClose(window) && game.isRunning()) {
+            double newSecondTime = glfwGetTime();
+            lag += newSecondTime - lastSecondTime;
+            lastSecondTime = newSecondTime;
+            while(lag >= secondsPerTick) {
+                lag -= secondsPerTick;
+                tick(game);
+            }
+            renderTick(game, (float) (lag / secondsPerTick));
+        }
+    }
+
+    private void tick(Game game) {
+        stats.updateTicksPerSecond();
+        gamepad.tick();
+        keys.tick();
+        game.tick();
+    }
+
+    private void renderTick(Game game, float lag) {
+        stats.updateFramesPerSecond();
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+        game.renderTick(renderer, lag);
+        glfwSwapBuffers(window);
+        glfwPollEvents();
+    }
+
+    private void destroyWindow() {
+        glfwDestroyWindow(window);
+    }
+
+    private void freeErrorCallback() {
+        GLFWErrorCallback error = glfwSetErrorCallback(null);
+        if(error != null) {
+            error.free();
+        }
+    }
+}

+ 11 - 24
src/me/hammerle/snuviengine/shader/fragment.fs

@@ -6,8 +6,7 @@ uniform mat4 viewMatrix;
 uniform mat4 modelMatrix;
 uniform vec3 ambientLight;
 
-struct Light 
-{
+struct Light {
     vec2 pos;
     float strength;
     vec3 color;
@@ -26,44 +25,32 @@ in vec2 loc;
 in vec4 vColor;
 out vec4 color;
 
-vec3 getLight()
-{
+vec3 getLight() {
     vec3 light = vec3(0, 0, 0);
-    for(int i = 0; i < 32; i++)
-    {
+    for(int i = 0; i < 32; i++) {
         light += lights[i].color * max(1 - lights[i].strength * length(loc - lights[i].pos), 0);
     }
     return light;
 }
 
-void main(void)
-{
-    if(useTexture)
-    {
+void main(void) {
+    if(useTexture) {
         color = texture(samp, tc);
-        if(color.a == 0.0)
-        {
+        if(color.a == 0.0){
             discard;
         }
-        if(useColor)
-        {
-            if(useMixColor)
-            {
+        if(useColor) {
+            if(useMixColor) {
                 color = (color + mixColor) * 0.5;
-            }
-            else
-            {
+            } else{
                 color = vColor;
             }
         }
-    }
-    else
-    {
+    } else {
         color = vColor;
     }
 
-    if(useLight)
-    {
+    if(useLight) {
         color = vec4(pow(pow(color.xyz, vec3(1 / 2.2)) * (ambientLight + getLight()), vec3(2.2)), color.w);
     }
 }

+ 2 - 4
src/me/hammerle/snuviengine/shader/vertex.vs

@@ -10,8 +10,7 @@ uniform mat4 viewMatrix;
 uniform mat4 modelMatrix;
 uniform vec3 ambientLight;
 
-struct Light 
-{
+struct Light {
     vec2 pos;
     float strength;
     vec3 color;
@@ -29,8 +28,7 @@ out vec2 tc;
 out vec2 loc;
 out vec4 vColor;
 
-void main(void)
-{ 
+void main(void){ 
     loc = (modelMatrix * vec4(pos, 1.0)).xy;
 
     gl_Position = viewMatrix * modelMatrix * vec4(pos, 1.0);

+ 12 - 17
src/me/hammerle/snuviengine/util/Color.java

@@ -1,27 +1,22 @@
 package me.hammerle.snuviengine.util;
 
-public class Color
-{
-    public static int get(int r, int g, int b, int a)
-    {
+public class Color {
+    public static int get(int r, int g, int b, int a) {
         return (r & 0xFF) | ((g & 0xFF) << 8) | ((b & 0xFF) << 16) | ((a & 0xFF) << 24);
     }
-    
-    public static int get(float r, float g, float b, float a)
-    {
+
+    public static int get(float r, float g, float b, float a) {
         return get((int) r, (int) g, (int) b, (int) a);
     }
-    
-    public static int darken(int rgba, float factor)
-    {
-        return ((int) ((rgba & 0xFF) * factor) & 0xFF) | 
-                (((int) (((rgba >> 8) & 0xFF) * factor) & 0xFF) << 8) | 
-                (((int) (((rgba >> 16) & 0xFF) * factor) & 0xFF) << 16) | 
-                (rgba & 0xFF000000);
+
+    public static int darken(int rgba, float factor) {
+        return ((int) ((rgba & 0xFF) * factor) & 0xFF)
+                | (((int) (((rgba >> 8) & 0xFF) * factor) & 0xFF) << 8)
+                | (((int) (((rgba >> 16) & 0xFF) * factor) & 0xFF) << 16)
+                | (rgba & 0xFF000000);
     }
-    
-    public static int get(int r, int g, int b)
-    {
+
+    public static int get(int r, int g, int b) {
         return get(r, g, b, 255);
     }
 }

+ 13 - 17
src/me/hammerle/snuviengine/util/Rectangle.java

@@ -1,23 +1,19 @@
 package me.hammerle.snuviengine.util;
 
-public class Rectangle
-{
-    private final int w;
-    private final int h;
-    
-    public Rectangle(int w, int h)
-    {
-        this.w = w;
-        this.h = h;
+public class Rectangle {
+    private final int width;
+    private final int height;
+
+    public Rectangle(int width, int height) {
+        this.width = width;
+        this.height = height;
     }
-    
-    public int getWidth()
-    {
-        return w;
+
+    public int getWidth() {
+        return width;
     }
-    
-    public int getHeight()
-    {
-        return h;
+
+    public int getHeight() {
+        return height;
     }
 }

BIN
tiles.png