Kajetan Johannes Hammerle 6 years ago
commit
5f5cf95a6d

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+/build
+/dist
+/nbproject
+/test
+build.xml

+ 3 - 0
manifest.mf

@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+

+ 289 - 0
src/me/hammerle/supersnuvi/entity/Entity.java

@@ -0,0 +1,289 @@
+package me.hammerle.supersnuvi.entity;
+
+import java.util.LinkedList;
+import javafx.scene.image.Image;
+import javafx.scene.image.PixelWriter;
+import javafx.scene.image.WritableImage;
+import javafx.scene.paint.Color;
+import me.hammerle.supersnuvi.input.IKeyHandler;
+import me.hammerle.supersnuvi.rendering.ITick;
+import me.hammerle.supersnuvi.rendering.MapRenderer;
+import me.hammerle.supersnuvi.tiles.Tile;
+import me.hammerle.supersnuvi.util.CollisionBox;
+import me.hammerle.supersnuvi.util.Utils;
+
+public class Entity implements ITick
+{
+    // 1 Tile = 1 m
+    // Pixel    m       s      Pixel
+    // ----- * --- * ------ = -------
+    //   m      s     Tick     Tick
+    // added correction factor, real life gravity seems to strong
+    public static double GRAVITY = Tile.TILE_SIZE * 9.81 / 60 * 0.1;
+    
+    private double prevX;
+    private double prevY;
+    private double posX;
+    private double posY;
+    
+    protected double motionX;
+    protected double motionY;
+    private boolean onGround;
+    
+    private CollisionBox collisionBox;
+    
+    private Image image;
+    
+    private MapRenderer map;
+    
+    public Entity(MapRenderer map, double x, double y, double width, double height)
+    {
+        this.map = map;
+        this.posX = x;
+        this.posY = y;
+        this.prevX = x;
+        this.prevY = y;
+        this.motionX = 0;
+        this.motionY = 0;
+        this.onGround = false;
+        this.collisionBox = new CollisionBox(-width / 2, -height / 2, width / 2, height / 2);
+        
+        image = new WritableImage((int) width, (int) height);
+        PixelWriter writer = ((WritableImage) image).getPixelWriter();
+        Color black = Color.BLACK;
+        for(int fx = 0; fx < width; fx++)
+        {
+            for(int fy = 1; fy < height; fy++)
+            {
+                writer.setColor(fx, fy, black);
+            }
+        }
+        for(int fx = 0; fx < width; fx++)
+        {
+            writer.setColor(fx, 0, Color.WHITE);
+        }
+    }
+
+    public void controlTick(IKeyHandler keyHandler) 
+    {
+    }
+    
+    @Override
+    public final void tick(long delta) 
+    {
+        if(isAffectedByGravity())
+        {
+            motionY += GRAVITY;
+        }
+        
+        prevX = posX;
+        prevY = posY;
+        setAllowedMotion();
+        posX += motionX;
+        posY += motionY;      
+        
+        motionX = Utils.round(motionX);
+        motionY = Utils.round(motionY);
+        
+        if(motionY != 0)
+        {
+            onGround = false;
+        }
+        
+        posX = Utils.round(posX);
+        posY = Utils.round(posY);
+    }
+    
+    public final void jump()
+    {
+        if(onGround)
+        {
+            onGround = false;
+            motionY -= getJumpPower();
+        }
+    }
+    
+    public double getJumpPower()
+    {
+        return 0;
+    }
+    
+    public final CollisionBox getBox() 
+    {
+        return collisionBox.reset().offset(posX, posY);
+    }
+    
+    private void setAllowedMotion()
+    {
+        if(motionX == 0 && motionY == 0)
+        {
+            return;
+        }
+        double dirX = motionX;
+        double dirY = motionY;
+        double startX = posX;
+        double startY = posY;
+        double endX = startX + dirX;
+        double endY = startY + dirY;
+        
+        
+        CollisionBox eBox = getBox();
+        double fixedStartX = eBox.getFixedStartX();
+        double fixedStartY = eBox.getFixedStartY();
+        eBox = eBox.copy();
+        
+        // expanding area by the size of the entity
+        CollisionBox expandedBox = eBox.copy().expand(motionX, motionY).grow(fixedStartX, fixedStartY);
+        
+        LinkedList<CollisionBox> allBoxes = map.getCollisionBoxesAt(this, expandedBox);
+        if(allBoxes.isEmpty())
+        {
+            return;
+        }         
+        
+        double x;
+        double y;
+                
+        if(dirX >= 0)
+        {
+            if(dirY <= 0) // Right - Up
+            {
+                for(CollisionBox box : allBoxes)
+                {
+                    box.grow(fixedStartX, fixedStartY);
+                    if(endX <= box.getMinX() || endY >= box.getMaxY() ||
+                        startX >= box.getMaxX() || startY <= box.getMinY())
+                    {
+                        continue;
+                    }
+                    x = Utils.interpolateX(startX, startY, endX, endY, box.getMaxY());
+                    y = Utils.interpolateY(startX, startY, endX, endY, box.getMinX());
+                    if(x >= box.getMinX() && x <= box.getMaxX())
+                    {
+                        //endX = x;
+                        endY = box.getMaxY();
+                    }
+                    else if(y >= box.getMinY() && y <= box.getMaxY())
+                    {
+                        endX = box.getMinX();
+                        //endY = y;
+                    }
+                }
+            }
+            else // Right - Down
+            {
+                for(CollisionBox box : allBoxes)
+                {
+                    box.grow(fixedStartX, fixedStartY);
+                    if(endX <= box.getMinX() || endY <= box.getMinY() ||
+                        startX >= box.getMaxX() || startY >= box.getMaxY())
+                    {
+                        continue;
+                    }
+                    x = Utils.interpolateX(startX, startY, endX, endY, box.getMinY());
+                    y = Utils.interpolateY(startX, startY, endX, endY, box.getMinX());
+                    if(x >= box.getMinX() && x <= box.getMaxX())
+                    {
+                        //endX = x;
+                        endY = box.getMinY();
+                    }
+                    else if(y >= box.getMinY() && y <= box.getMaxY())
+                    {
+                        endX = box.getMinX();
+                        //endY = y;
+                    }
+                }
+            }
+        }
+        else
+        {
+            if(dirY <= 0) // Left - Up
+            {
+                for(CollisionBox box : allBoxes)
+                {
+                    box.grow(fixedStartX, fixedStartY);
+                    if(endX >= box.getMaxX() || endY >= box.getMaxY() ||
+                        startX <= box.getMinX() || startY <= box.getMinY())
+                    {
+                        continue;
+                    }
+                    x = Utils.interpolateX(startX, startY, endX, endY, box.getMaxY());
+                    y = Utils.interpolateY(startX, startY, endX, endY, box.getMaxX());
+                    if(x >= box.getMinX() && x <= box.getMaxX())
+                    {
+                        endY = box.getMaxY();
+                    }
+                    else if(y >= box.getMinY() && y <= box.getMaxY())
+                    {
+                        endX = box.getMaxX();
+                    }
+                }
+            }
+            else  // Left - Down
+            {
+                for(CollisionBox box : allBoxes)
+                {
+                    box.grow(fixedStartX, fixedStartY);
+                    if(endX >= box.getMaxX() || endY <= box.getMinY() ||
+                        startX <= box.getMinX() || startY >= box.getMaxY())
+                    {
+                        continue;
+                    }
+                    x = Utils.interpolateX(startX, startY, endX, endY, box.getMinY());
+                    y = Utils.interpolateY(startX, startY, endX, endY, box.getMaxX());
+                    if(x >= box.getMinX() && x <= box.getMaxX())
+                    {
+                        endY = box.getMinY();
+                    }
+                    else if(y >= box.getMinY() && y <= box.getMaxY())
+                    {
+                        endX = box.getMaxX();
+                    }
+                }
+            }
+        }
+        
+        motionX = endX - startX;
+        motionY = endY - startY;
+        if(dirY > 0 && motionY == 0)
+        {
+            onGround = true;
+        }
+        
+        if(Math.abs(motionY) >= 1000 || Math.abs(motionX) >= 1000)
+        {
+            System.err.println("illegal movement " + motionX + " " + motionY);
+            System.exit(0);
+        }
+    }
+    
+    public final Image getImage()
+    {
+        return image;
+    }
+    
+    public final double getRenderX()
+    {
+        return getX() + collisionBox.getFixedStartX();
+    }
+    
+    public final double getRenderY()
+    {
+        return getY() + collisionBox.getFixedStartY();
+    }
+    
+    public boolean isAffectedByGravity()
+    {
+        return true;
+    }
+    
+    public final double getX()
+    {
+        return posX;
+    }
+    
+    public final double getY()
+    {
+        return posY;
+    }
+}

+ 43 - 0
src/me/hammerle/supersnuvi/entity/Hero.java

@@ -0,0 +1,43 @@
+package me.hammerle.supersnuvi.entity;
+
+import javafx.scene.input.KeyCode;
+import me.hammerle.supersnuvi.input.IKeyHandler;
+import me.hammerle.supersnuvi.rendering.MapRenderer;
+
+public class Hero extends Entity
+{
+    public Hero(MapRenderer map, double x, double y, double width, double height) 
+    {
+        super(map, x, y, width, height);
+    }
+
+    @Override
+    public double getJumpPower() 
+    {
+        return 10;
+    }
+    
+    @Override
+    public void controlTick(IKeyHandler keyHandler) 
+    {
+        super.controlTick(keyHandler);
+        
+        if(keyHandler.isKeyDown(KeyCode.SPACE))
+        {
+            jump();
+        }
+        
+        if(keyHandler.isKeyDown(KeyCode.LEFT))
+        {
+            motionX = -2;
+        }
+        else if(keyHandler.isKeyDown(KeyCode.RIGHT))
+        {
+            motionX = 2;
+        }
+        else
+        {
+            motionX = 0;
+        }
+    }
+}

+ 10 - 0
src/me/hammerle/supersnuvi/input/IKeyHandler.java

@@ -0,0 +1,10 @@
+package me.hammerle.supersnuvi.input;
+
+import javafx.scene.input.KeyCode;
+
+public interface IKeyHandler 
+{
+    public int getKeyDownTime(KeyCode key);
+    public int isKeyJustReleased(KeyCode key);
+    public boolean isKeyDown(KeyCode key);
+}

+ 67 - 0
src/me/hammerle/supersnuvi/input/KeyHandler.java

@@ -0,0 +1,67 @@
+package me.hammerle.supersnuvi.input;
+
+import java.util.EnumMap;
+import java.util.LinkedList;
+import javafx.scene.input.KeyCode;
+import me.hammerle.supersnuvi.rendering.ITick;
+
+public class KeyHandler implements ITick, IKeyHandler
+{
+    private LinkedList<KeyCode> removeQueue;
+    private EnumMap<KeyCode, Integer> keys;
+    private EnumMap<KeyCode, Integer> justReleased;
+    
+    public KeyHandler()
+    {
+        keys = new EnumMap(KeyCode.class);
+        justReleased = new EnumMap(KeyCode.class);
+        removeQueue = new LinkedList<>();
+    }
+    
+    public void pressKey(KeyCode key)
+    {
+        keys.putIfAbsent(key, 0);
+    }
+    
+    public void releaseKey(KeyCode key)
+    {
+        removeQueue.add(key);
+    }
+    
+    @Override
+    public int getKeyDownTime(KeyCode key)
+    {
+        Integer i = keys.get(key);
+        return i != null ? i : 0;
+    }
+    
+    @Override
+    public int isKeyJustReleased(KeyCode key)
+    {
+        Integer i = justReleased.get(key);
+        return i != null ? i : -1;
+    }
+    
+    @Override
+    public boolean isKeyDown(KeyCode key)
+    {
+        return keys.containsKey(key);
+    }
+
+    @Override
+    public void tick(long delta) 
+    {
+        keys.replaceAll((k, v) -> v + 1);
+        justReleased.clear();
+        
+        removeQueue.forEach(key -> 
+        {
+            Integer i = keys.remove(key);
+            if(i != null)
+            {
+                justReleased.put(key, i);
+            }
+        });
+        removeQueue.clear();
+    }
+}

+ 66 - 0
src/me/hammerle/supersnuvi/rendering/GameWindow.java

@@ -0,0 +1,66 @@
+package me.hammerle.supersnuvi.rendering;
+
+import me.hammerle.supersnuvi.input.KeyHandler;
+import javafx.application.Application;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.input.KeyEvent;
+import javafx.stage.Stage;
+
+public class GameWindow extends Application
+{
+    private KeyHandler keys;
+    
+    public static void main(String[] args) 
+    {
+        Application.launch(args);
+    }
+
+    @Override
+    public void init() throws Exception 
+    {
+        super.init();
+        keys = new KeyHandler();
+    }
+    
+    @Override
+    public void start(Stage stage) throws Exception 
+    {
+        stage.setTitle("Super Snuvi");
+         
+        Group root = new Group();
+        Scene scene = new Scene(root);
+
+        stage.setScene(scene);
+        
+        Canvas canvas = new Canvas(800, 400);
+        root.getChildren().add(canvas);
+         
+        RenderCanvas timer = new RenderCanvas(this, canvas.getGraphicsContext2D(), canvas.getWidth(), canvas.getHeight());
+        timer.start();
+        
+        scene.setOnKeyPressed((KeyEvent e) -> 
+        {
+            keys.pressKey(e.getCode());
+        });
+        
+        scene.setOnKeyReleased((KeyEvent e) -> 
+        {
+            keys.releaseKey(e.getCode());
+        });
+        
+        stage.show();
+    }
+
+    @Override
+    public void stop() throws Exception 
+    {
+        super.stop();
+    }
+    
+    public final KeyHandler getKeyHandler()
+    {
+        return keys;
+    }
+}

+ 6 - 0
src/me/hammerle/supersnuvi/rendering/ITick.java

@@ -0,0 +1,6 @@
+package me.hammerle.supersnuvi.rendering;
+
+public interface ITick 
+{
+    public void tick(long delta);
+}

+ 237 - 0
src/me/hammerle/supersnuvi/rendering/MapRenderer.java

@@ -0,0 +1,237 @@
+package me.hammerle.supersnuvi.rendering;
+
+import me.hammerle.supersnuvi.input.KeyHandler;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.stream.Collectors;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.image.Image;
+import javafx.scene.image.WritableImage;
+import javafx.scene.paint.Color;
+import me.hammerle.supersnuvi.entity.Entity;
+import me.hammerle.supersnuvi.entity.Hero;
+import me.hammerle.supersnuvi.tiles.Tile;
+import me.hammerle.supersnuvi.util.CollisionBox;
+
+public class MapRenderer implements ITick
+{
+    private static final Image FALLBACK_IMAGE = new WritableImage(1, 1);
+    
+    private GraphicsContext gc;
+    
+    private int[][] background;
+    private int[][] background2;
+    private int[][] foreground;
+    
+    private HashMap<Integer, Entity> entities;
+    
+    private int mWidth;
+    private int mHeight;
+    private HashMap<Integer, Tile> registeredTiles;
+    
+    private double cameraOffsetX;
+    private double cameraWidth;
+    private double cameraHeight;
+    
+    private double offsetY;
+   
+    private GameWindow w;
+    private KeyHandler keyHandler;
+    
+    public MapRenderer(GameWindow w, GraphicsContext gc, double width, double height)
+    {
+        this.w = w;
+        this.keyHandler = w.getKeyHandler();
+        this.gc = gc;
+        this.background = null;
+        this.background2 = null;
+        this.foreground = null;
+        this.mWidth = 0;
+        this.mHeight = 0;
+        this.cameraWidth = width;
+        this.cameraHeight = height;
+        this.cameraOffsetX = 0;
+        this.registeredTiles = new HashMap<>();
+        this.entities = new HashMap<>();
+        this.offsetY = 0;
+        
+        Entity ent = new Entity(this, 48, 272, 32, 60);
+        Hero hero = new Hero(this, 48, 0, 32, 60);
+        
+        entities.put(1, ent);
+        entities.put(2, hero);
+        
+        registerTiles();
+    }    
+
+    private void registerTile(int id, int r, int g, int b, int opacity)
+    {
+        registeredTiles.put(id, new Tile(new Color(r / 255d, g / 255d, b / 255d, opacity / 255d)));
+    }
+    
+    private void registerTiles()
+    {
+        registeredTiles.put(1, new Tile("air"));
+        registeredTiles.put(2, new Tile("dirt").setDefaultCollisionBox());
+        registeredTiles.put(3, new Tile("grass").setDefaultCollisionBox());
+        registerTile(4, 255, 0, 0, 255);
+        registeredTiles.get(4).setCollisionBox(CollisionBox.DEFAULT_BOX);
+        registerTile(5, 255, 0, 255, 255);
+        registerTile(6, 255, 255, 0, 255);
+        registerTile(7, 255, 255, 255, 255);
+    }
+    
+    private Image getTile(int id)
+    {
+        Tile tile = registeredTiles.get(id);
+        if(tile == null)
+        {
+            return FALLBACK_IMAGE;
+        }
+        return tile.getImage();
+    }
+
+    @Override
+    public void tick(long delta)
+    {
+        keyHandler.tick(delta);
+        if(background != null)
+        {
+            // doing entity logic first
+            entities.values().forEach(entity -> 
+            {
+                entity.controlTick(keyHandler);
+                entity.tick(delta);
+            });
+
+            int startX = Math.max((int) (cameraOffsetX / Tile.TILE_SIZE), 0);
+            
+            int id;
+            for(int x = startX; x < mWidth; x++)
+            {
+                for(int y = 0; y < mHeight; y++)
+                {
+                    id = background[x][y];
+                    if(id != -1)
+                    {
+                        gc.drawImage(getTile(id), x * Tile.TILE_SIZE - cameraOffsetX, y * Tile.TILE_SIZE + offsetY);
+                    }
+                    id = background2[x][y];
+                    if(id != -1)
+                    {
+                        gc.drawImage(getTile(id), x * Tile.TILE_SIZE - cameraOffsetX, y * Tile.TILE_SIZE + offsetY);
+                    }
+                }
+            }
+            
+            // render entities here
+            entities.values().forEach(en -> gc.drawImage(en.getImage(), en.getRenderX(), en.getRenderY()));
+            // end entity rendering
+            
+            for(int x = startX; x < mWidth; x++)
+            {
+                for(int y = 0; y < mHeight; y++)
+                {
+                    id = foreground[x][y];
+                    if(id != -1)
+                    {
+                        gc.drawImage(getTile(id), x * Tile.TILE_SIZE  - cameraOffsetX, y * Tile.TILE_SIZE  + offsetY);
+                    }
+                }
+            }
+        }
+    }
+    
+    public Tile getInteractionTile(int x, int y)
+    {
+        if(x < 0 || y < 0 || x >= mWidth || y >= mHeight)
+        {
+            return Tile.FALLBACK_TILE;
+        }
+        return registeredTiles.get(background2[x][y]);
+    }
+    
+    public void loadMap(int[][] background, int[][] background2, int[][] foreground)
+    {
+        this.background = background;
+        this.background2 = background2;
+        this.foreground = foreground;
+        mWidth = background2.length;
+        if(mWidth > 0)
+        {
+            mHeight = background2[0].length;
+        }
+        else
+        {
+            mHeight = 0;
+        }
+        offsetY = cameraHeight - mHeight * Tile.TILE_SIZE;
+    }
+    
+    // -------------------------------------------------------------------------
+    // coord utils
+    // -------------------------------------------------------------------------
+    
+    public int getBlockX(double d)
+    {
+        return (int) (d / Tile.TILE_SIZE);
+    }
+    
+    public int getBlockY(double d)
+    {
+        return (int) ((d - offsetY) / Tile.TILE_SIZE);
+    }
+    
+    public double getCordX(int d)
+    {
+        return d * Tile.TILE_SIZE;
+    }
+    
+    public double getCordY(int d)
+    {
+        return d * Tile.TILE_SIZE + offsetY;
+    }
+    
+    // -------------------------------------------------------------------------
+    // collision box
+    // -------------------------------------------------------------------------
+    
+    public CollisionBox getTileCollisionBox(int x, int y)
+    {
+        if(x < 0 || y < 0 || x >= mWidth || y >= mHeight)
+        {
+            return CollisionBox.NULL_BOX;
+        }
+        Tile tile = registeredTiles.get(background2[x][y]);
+        return tile.getCollisionBox().reset().offset(getCordX(x), getCordY(y));
+    }
+    
+    public LinkedList<CollisionBox> getCollisionBoxesAt(Entity not, CollisionBox cb)
+    {
+        LinkedList<CollisionBox> boxes = new LinkedList<>(
+                entities.values().stream()
+                        .filter(ent -> ent != not && ent.getBox().intersects(cb))
+                        .map(ent -> ent.getBox())
+                        .collect(Collectors.toList()));
+        
+        int startX = getBlockX(cb.getMinX());
+        int endX = getBlockX(cb.getMaxX());
+        int startY = getBlockY(cb.getMinY());
+        int endY = getBlockY(cb.getMaxY());
+        //System.out.println("x = " + startX + " - " + endX + "  y = " + startY + " - " + endY);
+        
+        CollisionBox box;
+        for(int x = startX; x <= endX; x++)
+        {
+            for(int y = startY; y <= endY; y++)
+            {
+                box = getTileCollisionBox(x, y);
+                if(box.intersects(cb))
+                {
+                    boxes.add(box.copy());
+                }
+            }
+        }
+        return boxes;
+    }
+}

+ 67 - 0
src/me/hammerle/supersnuvi/rendering/RenderCanvas.java

@@ -0,0 +1,67 @@
+package me.hammerle.supersnuvi.rendering;
+
+import javafx.animation.AnimationTimer;
+import javafx.scene.canvas.GraphicsContext;
+
+public class RenderCanvas extends AnimationTimer
+{
+    private GraphicsContext gc;
+    
+    private final long startNanoTime;
+    private long lastHandle;
+    private long delta;
+    
+    private MapRenderer map;
+    
+    public RenderCanvas(GameWindow w, GraphicsContext gc, double width, double height)
+    {
+        this.gc = gc;
+        this.startNanoTime = System.nanoTime();
+        this.lastHandle = startNanoTime;
+        this.map = new MapRenderer(w, gc, width, height);
+        
+        map.loadMap(new int[][]
+        {
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
+        }, new int[][]
+        {
+            {1, 1, 1, 3, 2, 1, 1, 1, 1, 3, 2},
+            {1, 1, 1, 3, 2, 1, 1, 1, 1, 3, 2},
+            {1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 2},
+            {1, 1, 1, 1, 3, 1, 1, 1, 3, 2, 2},
+            {1, 1, 1, 1, 3, 1, 1, 1, 3, 2, 2},
+            {1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 2},
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2},
+            {1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 2},
+            {1, 1, 1, 1, 1, 3, 2, 1, 1, 3, 2}
+        }, new int[][]
+        {
+            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+            {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
+        });
+    }
+
+    @Override
+    public void handle(long now) 
+    {
+        delta = (now - lastHandle) / 1000;
+        lastHandle = now;
+        
+        map.tick(delta);
+    }
+}

BIN
src/me/hammerle/supersnuvi/resources/air.png


BIN
src/me/hammerle/supersnuvi/resources/dirt.png


BIN
src/me/hammerle/supersnuvi/resources/grass.png


+ 66 - 0
src/me/hammerle/supersnuvi/tiles/Tile.java

@@ -0,0 +1,66 @@
+package me.hammerle.supersnuvi.tiles;
+
+import javafx.scene.image.Image;
+import javafx.scene.image.PixelWriter;
+import javafx.scene.image.WritableImage;
+import javafx.scene.paint.Color;
+import me.hammerle.supersnuvi.util.CollisionBox;
+
+public class Tile 
+{
+    public final static int TILE_SIZE = 32;
+    public final static Tile FALLBACK_TILE = new Tile(new Color(1, 1, 1, 1));
+    
+    private Image image;
+    private CollisionBox cb;
+    
+    public Tile(String path)
+    {
+        image = new Image("me/hammerle/supersnuvi/resources/" + path + ".png", TILE_SIZE, TILE_SIZE, true, false);
+        cb = null;
+    }
+    
+    public Tile(Color c)
+    {
+        image = new WritableImage(TILE_SIZE, TILE_SIZE);
+        PixelWriter writer = ((WritableImage) image).getPixelWriter();
+        for(int x = 0; x < TILE_SIZE; x++)
+        {
+            for(int y = 0; y < TILE_SIZE; y++)
+            {
+                writer.setColor(x, y, c);
+            }
+        }
+    }
+    
+    public Image getImage()
+    {
+        return image;
+    }
+    
+    public boolean hasHitBox()
+    {
+        return cb != null;
+    }
+    
+    public Tile setCollisionBox(CollisionBox cb)
+    {
+        this.cb = cb;
+        return this;
+    }
+    
+    public Tile setDefaultCollisionBox()
+    {
+        this.cb = CollisionBox.DEFAULT_BOX.copy();
+        return this;
+    }
+    
+    public CollisionBox getCollisionBox()
+    {
+        if(hasHitBox())
+        {
+            return cb;
+        }
+        return CollisionBox.NULL_BOX.reset();
+    }
+}

+ 200 - 0
src/me/hammerle/supersnuvi/util/CollisionBox.java

@@ -0,0 +1,200 @@
+package me.hammerle.supersnuvi.util;
+
+import me.hammerle.supersnuvi.tiles.Tile;
+
+public class CollisionBox 
+{
+    public final static CollisionBox NULL_BOX = new CollisionBox(0, 0, 0, 0);
+    public final static CollisionBox DEFAULT_BOX = new CollisionBox(0, 0, Tile.TILE_SIZE, Tile.TILE_SIZE);
+    
+    private final double minX;
+    private final double maxX;
+    private final double minY;
+    private final double maxY;
+    
+    private double cMinX;
+    private double cMaxX;
+    private double cMinY;
+    private double cMaxY;
+    
+    public CollisionBox(double x1, double y1, double x2, double y2)
+    {
+        minX = Math.min(x1, x2);
+        maxX = Math.max(x1, x2);
+        minY = Math.min(y1, y2);
+        maxY = Math.max(y1, y2);
+        cMinX = minX;
+        cMaxX = maxX;
+        cMinY = minY;
+        cMaxY = maxY;
+    }
+    
+    public CollisionBox copy()
+    {
+        return new CollisionBox(cMinX, cMinY, cMaxX, cMaxY);
+    }
+    
+    public CollisionBox reset()
+    {
+        cMinX = minX;
+        cMaxX = maxX;
+        cMinY = minY;
+        cMaxY = maxY;
+        return this;
+    }
+
+    public double getFixedStartX() 
+    {
+        return minX;
+    }
+    
+    public double getFixedStartY() 
+    {
+        return minY;
+    }
+    
+    public double getMinX() 
+    {
+        return cMinX;
+    }
+
+    public double getMaxX() 
+    {
+        return cMaxX;
+    }
+
+    public double getMinY() 
+    {
+        return cMinY;
+    }
+
+    public double getMaxY() 
+    {
+        return cMaxY;
+    }
+   
+    public CollisionBox expand(double x, double y)
+    {
+        if(x < 0.0d)
+        {
+            cMinX += x;
+        }
+        else
+        {
+            cMaxX += x;
+        }
+        if(y < 0.0d)
+        {
+            cMinY += y;
+        }
+        else
+        {
+            cMaxY += y;
+        }
+        return this;
+    }
+
+    public CollisionBox grow(double x, double y)
+    {
+        x = Math.abs(x);
+        y = Math.abs(y);
+        cMinX -= x;
+        cMaxX += x;
+        cMinY -= y;
+        cMaxY += y;
+        return this;
+    }
+
+    public CollisionBox grow(double value)
+    {
+        return this.grow(value, value);
+    }
+
+    public CollisionBox intersect(CollisionBox cb)
+    {
+        return new CollisionBox(Math.max(cMinX, cb.cMinX), Math.max(cMinY, cb.cMinY), 
+                Math.min(cMaxX, cb.cMaxX), Math.min(cMaxY, cb.cMaxY));
+    }
+    
+    public boolean intersects(double x1, double y1, double x2, double y2)
+    {
+        if(this == NULL_BOX)
+        {
+            return false;
+        }
+        if(x1 > x2)
+        {
+            double swap = x1;
+            x1 = x2;
+            x2 = swap;
+        }
+        if(y1 > y2)
+        {
+            double swap = y1;
+            y1 = y2;
+            y2 = swap;
+        }
+        return cMinX < x2 && cMaxX > x1 && cMinY < y2 && cMaxY > y1;
+    }
+    
+    public boolean intersects(CollisionBox cb)
+    {
+        if(cb == NULL_BOX)
+        {
+            return false;
+        }
+        return this.intersects(cb.cMinX, cb.cMinY, cb.cMaxX, cb.cMaxY);
+    }
+
+    public CollisionBox offset(double x, double y)
+    {
+        cMinX += x;
+        cMaxX += x;
+        cMinY += y;
+        cMaxY += y;
+        return this;
+    }
+
+    public double calculateXOffset(CollisionBox cb, double offsetX)
+    {
+        if(cb.cMaxY > cMinY && cb.cMinY < cMaxY)
+        {
+            double newOffset = Math.signum(offsetX) * (Math.max(cMinX, cb.cMinX) - Math.min(cMaxX, cb.cMaxX));
+            if(Math.abs(newOffset) < Math.abs(offsetX))
+            {
+                return newOffset;
+            }
+            return offsetX;
+        }
+        return offsetX;
+    }
+
+    public double calculateYOffset(CollisionBox cb, double offsetY)
+    {
+        if(cb.cMaxX > cMinX && cb.cMinX < cMaxX)
+        {
+            double newOffset = Math.signum(offsetY) * (Math.max(cMinY, cb.cMinY) - Math.min(cMaxY, cb.cMaxY));
+            if(Math.abs(newOffset) < Math.abs(offsetY))
+            {
+                return newOffset;
+            }
+            return offsetY;
+        }
+        return offsetY;
+    }
+
+    @Override
+    public String toString() 
+    {
+        StringBuilder sb = new StringBuilder("box[x1 = ");
+        sb.append(cMinX);
+        sb.append(", y1 = ");
+        sb.append(cMinY);
+        sb.append(", x2 = ");
+        sb.append(cMaxX);
+        sb.append(", y2 = ");
+        sb.append(cMaxY);
+        sb.append(']');
+        return sb.toString();
+    } 
+}

+ 21 - 0
src/me/hammerle/supersnuvi/util/Utils.java

@@ -0,0 +1,21 @@
+package me.hammerle.supersnuvi.util;
+
+public class Utils 
+{
+    public static double round(double d)
+    {
+        return Math.round(d * 10000) / 10000d;
+    }
+    
+    public static double interpolateY(double x1, double y1, double x2, double y2, double x3)
+    {
+        double k = (y2 - y1) / (x2 - x1);
+        return k * x3 + y1 - k * x1;
+    }
+    
+    public static double interpolateX(double x1, double y1, double x2, double y2, double y3)
+    {
+        double k = (x2 - x1) / (y2 - y1);
+        return (y3 - y1) * k + x1;
+    }
+}