Parcourir la source

main for unit tests, unit test wrapper, unsorted array list and hash map
including unit tests, moved from chunk / world to ChunkRenderer,
interfaces for chunk related stuff, frustum culling

Kajetan Johannes Hammerle il y a 5 ans
Parent
commit
57a29fe073

+ 0 - 1
MainServer.cpp

@@ -4,7 +4,6 @@ using namespace std;
 
 int main(int argc, char** argv)
 {
-    cout << "server" << endl;
     return 0;
 }
 

+ 393 - 0
MainTest.cpp

@@ -0,0 +1,393 @@
+#include <iostream>
+#include "data/UnsortedArrayList.h"
+#include "data/HashMap.h"
+
+using namespace std;
+
+// -----------------------------------------------------------------------------
+// test feedback
+// -----------------------------------------------------------------------------
+
+const char* RED = "\033[0;31m";
+const char* GREEN = "\033[0;32m";
+
+int tests = 0;
+int successTests = 0;
+
+void notifySuccess(string text)
+{
+    tests++;
+    successTests++;
+    cout << GREEN << tests << ". " << text << endl; 
+}
+
+void notifyFail(string text)
+{
+    tests++;
+    cout << RED << tests << ". " << text << endl;
+}
+
+void finalizeTest()
+{
+    cout << successTests << " / " << tests << " succeeded" << endl;
+    tests = 0;
+    successTests = 0;
+}
+
+// -----------------------------------------------------------------------------
+// checks
+// -----------------------------------------------------------------------------
+
+void checkEqual(int a, int b, string text)
+{
+    if(a == b)
+    {
+        notifySuccess(text);
+    }
+    else
+    {
+        notifyFail(text + " - expected " + std::to_string(a) + " got " + std::to_string(b));
+    }
+}
+
+void checkBool(bool a, bool b, string text)
+{
+    if(a == b)
+    {
+        notifySuccess(text);
+    }
+    else
+    {
+        notifyFail(text + " - expected " + std::to_string(a) + " got " + std::to_string(b));
+    }
+}
+
+void checkGreaterOrEqual(int a, int b, string text)
+{
+    if(a >= b)
+    {
+        notifySuccess(text);
+    }
+    else
+    {
+        notifyFail(text + " - expected " + std::to_string(a) + " >= " + std::to_string(b));
+    }
+}
+
+// -----------------------------------------------------------------------------
+// tests of UnsortedArrayList
+// -----------------------------------------------------------------------------
+
+void testUnsortedArrayList1(string text)
+{
+    UnsortedArrayList<int> list;
+    
+    list.add(1);
+    list.add(5);
+    list.add(10);
+    
+    checkEqual(3, list.getSize(), text);
+    checkEqual(1, list.get(0, -1), text);
+    checkEqual(5, list.get(1, -1), text);
+    checkEqual(10, list.get(2, -1), text);
+}
+
+void testUnsortedArrayList2(string text)
+{
+    UnsortedArrayList<int> list;
+    
+    list.add(1);
+    list.add(5);
+    list.add(10);
+    list.removeIndex(1);
+    
+    checkEqual(2, list.getSize(), text);
+    checkEqual(1, list.get(0, -1), text);
+    checkEqual(10, list.get(1, -1), text);
+}
+
+void testUnsortedArrayList3(string text)
+{
+    UnsortedArrayList<int> list;
+    
+    list.add(1);
+    list.add(5);
+    list.add(10);
+    list.removeIndex(0);
+    list.add(20);
+    
+    checkEqual(3, list.getSize(), text);
+    checkEqual(10, list.get(0, -1), text);
+    checkEqual(5, list.get(1, -1), text);
+    checkEqual(20, list.get(2, -1), text);
+}
+
+void testUnsortedArrayList4(string text)
+{
+    UnsortedArrayList<int> list;
+    
+    list.add(1);
+    list.add(5);
+    list.add(10);
+    list.removeIndex(-5);
+    list.removeIndex(5);
+    
+    checkEqual(3, list.getSize(), text);
+    checkEqual(1, list.get(0, -1), text);
+    checkEqual(5, list.get(1, -1), text);
+    checkEqual(10, list.get(2, -1), text);
+}
+
+void testUnsortedArrayList5(string text)
+{
+    UnsortedArrayList<int> list;
+    
+    list.add(1);
+    list.add(5);
+    list.add(1);
+    list.add(20);
+    list.remove(2);
+    
+    checkEqual(4, list.getSize(), text);
+    checkEqual(1, list.get(0, -1), text);
+    checkEqual(5, list.get(1, -1), text);
+    checkEqual(1, list.get(2, -1), text);
+    checkEqual(20, list.get(3, -1), text);
+}
+
+void testUnsortedArrayList6(string text)
+{
+    UnsortedArrayList<int> list;
+    
+    int r = rand();
+    checkEqual(r, list.get(-100, r), text);
+    r = rand();
+    checkEqual(r, list.get(100, r), text);
+}
+
+void testUnsortedArrayList7(string text)
+{
+    UnsortedArrayList<int> list;
+    
+    list.add(1);
+    list.add(1);
+    list.add(5);
+    list.add(1);
+    list.add(20);
+    list.remove(1);
+    
+    checkEqual(2, list.getSize(), text);
+    checkEqual(20, list.get(0, -1), text);
+    checkEqual(5, list.get(1, -1), text);
+}
+
+void testUnsortedArrayList()
+{
+    testUnsortedArrayList1("add, size and get");
+    testUnsortedArrayList2("remove index");
+    testUnsortedArrayList3("add after remove index, move last to first");
+    testUnsortedArrayList4("remove of non existent element");
+    testUnsortedArrayList5("remove index of non existent index");
+    testUnsortedArrayList6("get returns error value");
+    testUnsortedArrayList7("remove deletes all occurencies");
+    finalizeTest();
+}
+
+// -----------------------------------------------------------------------------
+// tests of HashMap
+// -----------------------------------------------------------------------------
+
+unsigned int hashInt(int i)
+{
+    return i;
+}
+
+void testHashMap1(string text)
+{
+    HashMap<int, int> map(16, hashInt);
+    int cap = map.getCapacity();
+    checkGreaterOrEqual(cap, 16, text);
+    HashMap<int, int> map2(cap, hashInt);
+    HashMap<int, int> map3(cap + 1, hashInt);
+    checkEqual(cap, map2.getCapacity(), text);
+    checkGreaterOrEqual(map3.getCapacity(), cap + 1, text);
+}
+
+void testHashMap2(string text)
+{
+    HashMap<int, int> map(16, hashInt);
+    
+    for(int i = 0; i < 5; i++)
+    {
+        map.put(i, i);
+    }
+    
+    checkEqual(5, map.getSize(), text);
+}
+
+void testHashMap3(string text)
+{
+    HashMap<int, int> map(16, hashInt);
+    int cap = map.getCapacity();
+    if(cap < 0)
+    {
+        cap = 0;
+    }
+    cap += 3;
+    
+    for(int i = 0; i < cap; i++)
+    {
+        map.put(i, i);
+    }
+    
+    checkEqual(cap, map.getSize(), text);
+}
+
+void testHashMap4(string text)
+{
+    HashMap<int, int> map(16, hashInt);
+    for(int i = 0; i < 3; i++)
+    {
+        map.put(i, i + 20);
+    }
+    for(int i = 0; i < 3; i++)
+    {
+        checkEqual(i + 20, map.get(i, -1), text);
+    }
+}
+
+void testHashMap5(string text)
+{
+    HashMap<int, int> map(16, hashInt);
+    int cap = map.getCapacity();
+    if(cap < 0)
+    {
+        cap = 0;
+    }
+    cap += 3;
+    for(int i = 0; i < cap; i++)
+    {
+        map.put(i, i + 20);
+    }
+    
+    checkBool(true, map.remove(cap / 2), text);
+    checkEqual(-1, map.get(cap / 2, -1), text);
+    checkEqual(cap - 1, map.getSize(), text);
+}
+
+void testHashMap6(string text)
+{
+    HashMap<int, int> map(16, hashInt);
+    int cap = map.getCapacity();
+    if(cap < 0)
+    {
+        cap = 0;
+    }
+    cap += 3;
+    for(int i = 0; i < cap; i++)
+    {
+        map.put(i, i + 20);
+    }
+    checkBool(false, map.remove(cap + 5), text);
+    checkBool(false, map.remove(cap + 20), text);
+    checkBool(false, map.remove(cap + 25), text);
+    checkEqual(cap, map.getSize(), text);
+}
+
+void testHashMap7(string text)
+{
+    HashMap<int, int> map(16, hashInt);
+    for(int i = 0; i < 3; i++)
+    {
+        map.put(i, i + 20);
+    }
+    checkEqual(3, map.getSize(), text);
+    map.clear();
+    checkEqual(0, map.getSize(), text);
+    checkEqual(-1, map.get(0, -1), text);
+    checkEqual(-1, map.get(1, -1), text);
+    checkEqual(-1, map.get(2, -1), text);
+}
+
+void testHashMap8(string text)
+{
+    HashMap<int, int> map(16, hashInt);
+    for(int i = 0; i < 3; i++)
+    {
+        map.put(i, i + 20);
+    }
+    for(int i = 0; i < 3; i++)
+    {
+        map.put(i, i + 25);
+    }
+    for(int i = 0; i < 3; i++)
+    {
+        checkEqual(i + 25, map.get(i, -1), text);
+    }
+}
+
+void testHashMap9(string text)
+{
+    HashMap<int, int> map(16, hashInt);
+    int cap = map.getCapacity();
+    if(cap < 0)
+    {
+        cap = 0;
+    }
+    cap += 3;
+    for(int i = 0; i < cap; i++)
+    {
+        map.put(i, i + 20);
+    }
+    
+    checkEqual(cap / 2 + 20, map.remove(cap / 2, -1), text);
+    checkEqual(cap - 1, map.getSize(), text);
+}
+
+void testHashMap10(string text)
+{
+    HashMap<int, int> map(16, hashInt);
+    int cap = map.getCapacity();
+    if(cap < 0)
+    {
+        cap = 0;
+    }
+    cap += 3;
+    for(int i = 0; i < cap; i++)
+    {
+        map.put(i, i + 20);
+    }
+    checkEqual(-1, map.remove(cap + 5, -1), text);
+    checkEqual(-1, map.remove(cap + 20, -1), text);
+    checkEqual(-1, map.remove(cap + 25, -1), text);
+    checkEqual(cap, map.getSize(), text);
+}
+
+void testHashMap()
+{
+    testHashMap1("capacity greater or equal than inital value");
+    testHashMap2("put increases size");
+    testHashMap3("put over capacity");
+    testHashMap4("get");
+    testHashMap5("remove of existent value");
+    testHashMap6("remove of non existent value");
+    testHashMap7("clear");
+    testHashMap8("put overwrites old value");
+    testHashMap9("remove with return value of existent value");
+    testHashMap10("remove with return value of non existent value");
+    finalizeTest();
+}
+
+// -----------------------------------------------------------------------------
+// main
+// -----------------------------------------------------------------------------
+
+int main(int argc, char** argv)
+{
+    testUnsortedArrayList();
+    testHashMap();
+    return 0;
+}
+
+
+

+ 24 - 5
Makefile

@@ -9,11 +9,11 @@ run_client: game_client
 	
 game_client: MainClient.cpp\
 	Clock.o DirectRenderer.o KeyManager.o Mesh.o MouseManager.o Shader.o Texture.o Utils.o Wrapper.o\
-	Matrix3D.o Matrix3DStack.o StackOverflow.o StackUnderflow.o Vector3D.o\
+	Matrix3D.o Matrix3DStack.o StackOverflow.o StackUnderflow.o Vector3D.o Plane3D.o\
 	Client.o\
-	Chunk.o World.o
+	Chunk.o World.o ChunkRenderer.o ClientChunkProvider.o
 	g++ $(VERSION) -o $@ MainClient.cpp *.o $(LDFLAGS)
-	
+
 # ------------------------------------------------------------------------------	
 # Engine
 # ------------------------------------------------------------------------------
@@ -52,6 +52,12 @@ Wrapper.o: engine/Wrapper.cpp engine/Wrapper.h
 Client.o: client/Client.h client/Client.cpp
 	g++ $(VERSION) -c client/Client.cpp -o $@
 	
+ChunkRenderer.o: client/rendering/ChunkRenderer.h client/rendering/ChunkRenderer.cpp world/IChunkListener.h
+	g++ $(VERSION) -c client/rendering/ChunkRenderer.cpp -o $@
+	
+ClientChunkProvider.o: client/rendering/ClientChunkProvider.h client/rendering/ClientChunkProvider.cpp world/IChunkProvider.h
+	g++ $(VERSION) -c client/rendering/ClientChunkProvider.cpp -o $@
+	
 # ------------------------------------------------------------------------------	
 # World
 # ------------------------------------------------------------------------------
@@ -59,7 +65,7 @@ Client.o: client/Client.h client/Client.cpp
 Chunk.o: world/Chunk.h world/Chunk.cpp
 	g++ $(VERSION) -c world/Chunk.cpp -o $@
 	
-World.o: world/World.h world/World.cpp
+World.o: world/World.h world/World.cpp data/UnsortedArrayList.h
 	g++ $(VERSION) -c world/World.cpp -o $@
 
 # ------------------------------------------------------------------------------	
@@ -81,6 +87,9 @@ StackUnderflow.o: math/StackUnderflow.h math/StackUnderflow.cpp
 Vector3D.o: math/Vector3D.h math/Vector3D.cpp
 	g++ $(VERSION) -c math/Vector3D.cpp -o $@
 	
+Plane3D.o: math/Plane3D.h math/Plane3D.cpp
+	g++ $(VERSION) -c math/Plane3D.cpp -o $@
+	
 # ------------------------------------------------------------------------------	
 # server
 # ------------------------------------------------------------------------------
@@ -91,9 +100,19 @@ run_server: game_server
 game_server: MainServer.cpp
 	g++ $(VERSION) -o $@ MainServer.cpp
 	
+# ------------------------------------------------------------------------------	
+# tests
+# ------------------------------------------------------------------------------
+	
+run_tests: test
+	./test
+	
+test: MainTest.cpp data/UnsortedArrayList.h data/HashMap.h
+	g++ $(VERSION) -o $@ MainTest.cpp
+	
 # ------------------------------------------------------------------------------	
 # clean
 # ------------------------------------------------------------------------------
 	
 clean:
-	rm -f game_server game_client *.o
+	rm -f game_server game_client *.o test

+ 6 - 14
client/Client.cpp

@@ -3,7 +3,7 @@
 
 using namespace std;
 
-Client::Client()
+Client::Client() : world(&chunkProvider)
 {    
     position.set(0, 0, -2);
     shader.setCamera(position.getX(), position.getY(), position.getZ(), 0, 0);
@@ -23,26 +23,18 @@ Client::Client()
     keyManager.map(KEY_CAM_TEST, GLFW_KEY_T);
     
     mouseManager.map(MOUSE_LEFT, GLFW_MOUSE_BUTTON_1);
+    
+    world.registerChunkListener(&chunkRenderer);
+    world.updateDirtyChunks();
 }
 
 Client::~Client()
 {
 }
 
-float oldTicker = 0;
-float ticker = 0;
-
 void Client::tick()
 {
     tps.update();
-        
-    oldTicker = ticker;
-    ticker += 1;
-    if(ticker >= 360)
-    {
-        oldTicker = 0;
-        ticker = 0;
-    }
     
     if(keyManager.isDown(KEY_CAM_TEST))
     {
@@ -55,7 +47,7 @@ void Client::tick()
     
     shader.storeCamera();
     
-    float factor = 2.0f;
+    float factor = 10.0f;
     if(keyManager.isDown(KEY_LEFT))
     {
         position.addMul(shader.getLeft(), factor);
@@ -115,7 +107,7 @@ void Client::renderTick(float lag)
     shader.setTextureEnabled(false);
     shader.setUseBlending(false);
     shader.setNormalsEnabled(true);
-    world.renderTick(shader, directRenderer, lag);
+    chunkRenderer.renderTick(shader, directRenderer, lag);
     
     shader.set2DMode();
     shader.setToIdentity();

+ 5 - 0
client/Client.h

@@ -8,6 +8,9 @@
 #include "../engine/Shader.h"
 #include "../engine/DirectRenderer.h"
 #include "../world/World.h"
+#include "../world/IChunkProvider.h"
+#include "rendering/ClientChunkProvider.h"
+#include "rendering/ChunkRenderer.h"
 
 using namespace std;
 
@@ -44,6 +47,8 @@ private:
     KeyManager keyManager;
     MouseManager mouseManager;
     
+    ChunkRenderer chunkRenderer;
+    ClientChunkProvider chunkProvider;
     World world;
     
     Shader shader;

+ 260 - 0
client/rendering/ChunkRenderer.cpp

@@ -0,0 +1,260 @@
+#include "ChunkRenderer.h"
+
+ChunkRenderer::ChunkRenderer()
+{
+    mesh = new ChunkMesh[chunkX * chunkZ * Chunk::HEIGHT_PARTIONS];
+}
+
+ChunkRenderer::~ChunkRenderer()
+{
+    delete[] mesh;
+}
+
+void ChunkRenderer::renderTick(Shader& shader, DirectRenderer& dr, float lag)
+{
+    //int frustumCull1 = 0;
+    //int frustumCull2 = 0;
+    for(int x = 0; x < chunkX; x++)
+    {
+        for(int z = 0; z < chunkZ; z++)
+        {
+            int sx = x * Chunk::WIDTH;
+            int sz = z * Chunk::DEPTH;
+            int ex = sx + Chunk::WIDTH;
+            int ez = sz + Chunk::DEPTH;
+            
+            if(shader.isInFrustum(sx, 0, sz, ex, Chunk::HEIGHT, ez))
+            {
+                shader.translateTo(x * Chunk::WIDTH, 0, z * Chunk::DEPTH);
+                shader.updateModelMatrix();
+
+                for(int l = 0; l < Chunk::HEIGHT_PARTIONS; l++)
+                {
+                    if(shader.isInFrustum(sx, l * Chunk::PARTION_HEIGHT, sz, ex, (l + 1) * Chunk::PARTION_HEIGHT, ez))
+                    {
+                        mesh[l + z * Chunk::HEIGHT_PARTIONS + x * chunkZ * Chunk::HEIGHT_PARTIONS].draw();
+                    }
+                    //else
+                    //{
+                    //    frustumCull2++;
+                    //}
+                }
+            }
+            //else
+            //{
+            //    frustumCull1 += 16;
+            //}
+        }
+    }
+    //cout << "CULL :" << frustumCull1 << " " << frustumCull2 << endl;
+}
+
+void ChunkRenderer::updateChunk(Chunk& c, Chunk* north, Chunk* east, Chunk* south, Chunk* west)
+{
+    int x = c.getChunkX();
+    int z = c.getChunkZ();
+    if(x < 0 || x >= chunkX || z < 0 || z >= chunkZ)
+    {
+        return;
+    }
+    cout << "UPDATE: " << x << " " << z << endl;
+    for(int l = 0; l < Chunk::HEIGHT_PARTIONS; l++)
+    {
+        if(c.isDirty(l))
+        {
+            buildChunk(l, c, north, east, south, west);
+        }
+    }
+}
+
+void ChunkRenderer::buildChunk(int partionY, Chunk& c, Chunk* north, Chunk* east, Chunk* south, Chunk* west)
+{    
+    Mesh& m = mesh[partionY + c.getChunkZ() * Chunk::HEIGHT_PARTIONS + c.getChunkX() * chunkZ * Chunk::HEIGHT_PARTIONS];
+    
+    int max = (partionY + 1) * Chunk::PARTION_HEIGHT;
+    for(int y = partionY * Chunk::PARTION_HEIGHT; y < max; y++)
+    {
+        for(int x = 0; x < Chunk::WIDTH; x++)
+        {
+            for(int z = 0; z < Chunk::DEPTH; z++)
+            {
+                if(c.getBlock(x, y, z) == 1)
+                {
+                    // bottom
+                    if(y <= 0 || c.getBlock(x, y - 1, z) != 1)
+                    {
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addColor(1, 0, 0, 1);
+                        }
+
+                        //m.addTexture(0.125f, 0.0f);
+                        //m.addTexture(0.1875f, 0.0f);
+                        //m.addTexture(0.125f, 0.0625f);
+                        //m.addTexture(0.1875f, 0.0f);
+                        //m.addTexture(0.1875f, 0.0625f);
+                        //m.addTexture(0.125f, 0.0625f);
+
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addNormal(0.0f, -1.0f, 0.0f);
+                        }
+
+                        m.addPosition(x + 0.0f, y + 0.0f, z + 0.0f);
+                        m.addPosition(x + 1.0f, y + 0.0f, z + 0.0f);
+                        m.addPosition(x + 0.0f, y + 0.0f, z + 1.0f);
+                        m.addPosition(x + 1.0f, y + 0.0f, z + 0.0f);
+                        m.addPosition(x + 1.0f, y + 0.0f, z + 1.0f);
+                        m.addPosition(x + 0.0f, y + 0.0f, z + 1.0f);
+                    }
+
+                    // top
+                    if(y + 1 >= Chunk::HEIGHT || c.getBlock(x, y + 1, z) != 1)
+                    {
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addColor(1, 0, 0, 1);
+                        }
+                        
+                        //m.addTexture(0.25f, 0.0f);
+                        //m.addTexture(0.25f, 0.0625f);
+                        //m.addTexture(0.3125f, 0.0f);
+                        //m.addTexture(0.3125f, 0.0f);
+                        //m.addTexture(0.25f, 0.0625f);
+                        //m.addTexture(0.3125f, 0.0625f);
+
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addNormal(0.0f, 1.0f, 0.0f);
+                        }
+
+                        m.addPosition(x + 0.0f, y + 1.0f, z + 0.0f);
+                        m.addPosition(x + 0.0f, y + 1.0f, z + 1.0f);
+                        m.addPosition(x + 1.0f, y + 1.0f, z + 0.0f);
+                        m.addPosition(x + 1.0f, y + 1.0f, z + 0.0f);
+                        m.addPosition(x + 0.0f, y + 1.0f, z + 1.0f);
+                        m.addPosition(x + 1.0f, y + 1.0f, z + 1.0f);
+                    }
+
+                    // right
+                    if((x + 1 < Chunk::WIDTH && c.getBlock(x + 1, y, z) != 1) ||
+                        (x + 1 >= Chunk::WIDTH && (north == nullptr || north->getBlock(x + 1 - Chunk::WIDTH, y, z) != 1)))
+                    {
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addColor(1, 0, 0, 1);
+                        }
+                        
+                        //m.addTexture(0.1875f, 0.0625f);
+                        //m.addTexture(0.25f, 0.0f);
+                        //m.addTexture(0.25f, 0.0625f);
+                        //m.addTexture(0.1875f, 0.0625f);
+                        //m.addTexture(0.1875f, 0.0f);
+                        //m.addTexture(0.25f, 0.0f);
+
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addNormal(1.0f, 0.0f, 0.0f);
+                        }
+
+                        m.addPosition(x + 1.0f, y + 0.0f, z + 0.0f);
+                        m.addPosition(x + 1.0f, y + 1.0f, z + 1.0f);
+                        m.addPosition(x + 1.0f, y + 0.0f, z + 1.0f);
+                        m.addPosition(x + 1.0f, y + 0.0f, z + 0.0f);
+                        m.addPosition(x + 1.0f, y + 1.0f, z + 0.0f);
+                        m.addPosition(x + 1.0f, y + 1.0f, z + 1.0f);
+                    }
+
+                    // left
+                    if((x - 1 >= 0 && c.getBlock(x - 1, y, z) != 1) ||
+                        (x - 1 < 0 && (south == nullptr || south->getBlock(x - 1 + Chunk::WIDTH, y, z) != 1)))
+                    {
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addColor(1, 0, 0, 1);
+                        }
+                        
+                        //m.addTexture(0.1875f, 0.0625f);
+                        //m.addTexture(0.25f, 0.0625f);
+                        //m.addTexture(0.25f, 0.0f);
+                        //m.addTexture(0.1875f, 0.0625f);
+                        //m.addTexture(0.25f, 0.0f);
+                        //m.addTexture(0.1875f, 0.0f);
+
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addNormal(-1.0f, 0.0f, 0.0f);
+                        }
+
+                        m.addPosition(x + 0.0f, y + 0.0f, z + 0.0f);
+                        m.addPosition(x + 0.0f, y + 0.0f, z + 1.0f);
+                        m.addPosition(x + 0.0f, y + 1.0f, z + 1.0f);
+                        m.addPosition(x + 0.0f, y + 0.0f, z + 0.0f);
+                        m.addPosition(x + 0.0f, y + 1.0f, z + 1.0f);
+                        m.addPosition(x + 0.0f, y + 1.0f, z + 0.0f);
+                    }
+
+                    // back
+                    if((z + 1 < Chunk::DEPTH && c.getBlock(x, y, z + 1) != 1) ||
+                        (z + 1 >= Chunk::DEPTH && (east == nullptr || east->getBlock(x, y, z + 1 - Chunk::DEPTH) != 1)))
+                    {
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addColor(1, 0, 0, 1);
+                        }
+                        
+                        //m.addTexture(0.1875f, 0.0625f);
+                        //m.addTexture(0.25f, 0.0625f);
+                        //m.addTexture(0.25f, 0.0f);
+                        //m.addTexture(0.1875f, 0.0625f);
+                        //m.addTexture(0.25f, 0.0f);
+                        //m.addTexture(0.1875f, 0.0f);
+
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addNormal(0.0f, 0.0f, -1.0f);
+                        }
+
+                        m.addPosition(x + 0.0f, y + 0.0f, z + 1.0f);
+                        m.addPosition(x + 1.0f, y + 0.0f, z + 1.0f);
+                        m.addPosition(x + 1.0f, y + 1.0f, z + 1.0f);
+                        m.addPosition(x + 0.0f, y + 0.0f, z + 1.0f);
+                        m.addPosition(x + 1.0f, y + 1.0f, z + 1.0f);
+                        m.addPosition(x + 0.0f, y + 1.0f, z + 1.0f);
+                    }
+
+                    // front
+                    if((z - 1 >= 0 && c.getBlock(x, y, z - 1) != 1) ||
+                        (z - 1 < 0 && (west == nullptr || west->getBlock(x, y, z - 1 + Chunk::DEPTH) != 1)))
+                    {
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addColor(1, 0, 0, 1);
+                        }
+                        
+                        //m.addTexture(0.1875f, 0.0625f);
+                        //m.addTexture(0.25f, 0.0f);
+                        //m.addTexture(0.25f, 0.0625f);
+                        //m.addTexture(0.1875f, 0.0625f);
+                        //m.addTexture(0.1875f, 0.0f);
+                        //m.addTexture(0.25f, 0.0f);
+
+                        for(int i = 0; i < 6; i++)
+                        {
+                            m.addNormal(0.0f, 0.0f, 1.0f);
+                        }
+
+                        m.addPosition(x + 0.0f, y + 0.0f, z + 0.0f);
+                        m.addPosition(x + 1.0f, y + 1.0f, z + 0.0f);
+                        m.addPosition(x + 1.0f, y + 0.0f, z + 0.0f);
+                        m.addPosition(x + 0.0f, y + 0.0f, z + 0.0f);
+                        m.addPosition(x + 0.0f, y + 1.0f, z + 0.0f);
+                        m.addPosition(x + 1.0f, y + 1.0f, z + 0.0f);
+                    }
+                }
+            }
+        }
+    }
+    
+    m.build();
+}

+ 26 - 0
client/rendering/ChunkRenderer.h

@@ -0,0 +1,26 @@
+#ifndef CHUNKRENDERER_H
+#define CHUNKRENDERER_H
+
+#include "../../engine/Mesh.h"
+#include "../../engine/Shader.h"
+#include "../../engine/DirectRenderer.h"
+#include "../../world/IChunkListener.h"
+
+class ChunkRenderer : public IChunkListener
+{
+public:
+    ChunkRenderer();
+    virtual ~ChunkRenderer();
+    
+    void renderTick(Shader& shader, DirectRenderer& dr, float lag);
+    void updateChunk(Chunk& c, Chunk* north, Chunk* east, Chunk* south, Chunk* west) override;
+private:
+    void buildChunk(int partionY, Chunk& c, Chunk* north, Chunk* east, Chunk* south, Chunk* west);
+    
+    const int chunkX = 16;
+    const int chunkZ = 16;
+    ChunkMesh* mesh;
+};
+
+#endif
+

+ 50 - 0
client/rendering/ClientChunkProvider.cpp

@@ -0,0 +1,50 @@
+#include "ClientChunkProvider.h"
+
+ClientChunkProvider::ClientChunkProvider()
+{
+    chunks = new Chunk*[chunkX * chunkZ];
+    for(int x = 0; x < chunkX; x++)
+    {
+        for(int z = 0; z < chunkZ; z++)
+        {
+            chunks[x + z * chunkX] = new Chunk(x, z);
+        }
+    }
+}
+
+ClientChunkProvider::~ClientChunkProvider()
+{
+    for(int i = 0; i < chunkX * chunkZ; i++)
+    {
+        delete chunks[i];
+    }
+    delete[] chunks;
+}
+
+Chunk* ClientChunkProvider::getChunk(int x, int z)
+{
+    if(isChunkLoaded(x, z))
+    {
+        return chunks[x + z * chunkX];
+    }
+    return nullptr;
+}
+        
+bool ClientChunkProvider::isChunkLoaded(int x, int z) const
+{
+    return x >= 0 && x < chunkX && z >= 0 && z < chunkZ;
+}
+
+void ClientChunkProvider::forEachLoadedChunk(void* data, void (*fun) (Chunk&, void*)) const
+{
+    for(int x = 0; x < chunkX; x++)
+    {
+        for(int z = 0; z < chunkZ; z++)
+        {
+            fun(*chunks[x + z * chunkX], data);
+        }
+    }
+}
+
+        
+

+ 23 - 0
client/rendering/ClientChunkProvider.h

@@ -0,0 +1,23 @@
+#ifndef CLIENTCHUNKPROVIDER_H
+#define CLIENTCHUNKPROVIDER_H
+
+#include "../../world/IChunkProvider.h"
+
+class ClientChunkProvider : public IChunkProvider
+{
+public:
+    ClientChunkProvider();
+    virtual ~ClientChunkProvider();
+    
+    Chunk* getChunk(int x, int z) override;
+    bool isChunkLoaded(int x, int z) const override;
+    void forEachLoadedChunk(void* data, void (*fun) (Chunk&, void*)) const override;
+    
+private:
+    const int chunkX = 16;
+    const int chunkZ = 16;
+    Chunk** chunks;
+};
+
+#endif
+

+ 348 - 0
data/HashMap.h

@@ -0,0 +1,348 @@
+#ifndef HASHMAP_H
+#define HASHMAP_H
+
+#include <iostream>
+
+constexpr static int NUMBER_OF_PRIMES = 26;
+constexpr static int PRIMES[26] = 
+{
+    17, 37, 79, 163, 331, 673, 1361, 2729, 5471, 10949, 21911, 43853, 87719,
+    175447, 350899, 701819, 1403641, 2807303, 5614657, 11229331, 22458671,
+    44917381, 89834777, 179669557, 359339171, 718678369
+};
+
+template<class K, class V>
+class HashMap
+{
+public:
+    HashMap(int initialLoad, unsigned int (*hasher)(K)) : hasher(hasher)
+    {
+        primeIndex = getHigherPrimeIndex(initialLoad);
+        capacity = PRIMES[primeIndex];
+        resizeCap = (capacity >> 2) * 3;
+        
+        data = new Node*[capacity];
+        for(int i = 0; i < capacity; i++)
+        {
+            data[i] = nullptr;
+        }
+        entries = 0;
+    }
+    
+    ~HashMap()
+    {
+        for(int i = 0; i < capacity; i++)
+        {
+            Node* n = data[i];
+            while(n != nullptr)
+            {
+                Node* next = n->next;
+                delete n;
+                n = next;
+            }
+        }
+        delete[] data;
+    }
+    
+    int getCapacity() const
+    {
+        return capacity;
+    }
+    
+    int getSize() const
+    {
+        return entries;
+    }
+    
+    void put(K k, V v)
+    {
+        ensureCapacity();
+        unsigned int hash = hasher(k) % capacity;
+        if(data[hash] == nullptr)
+        {
+            data[hash] = new Node(k, v);
+            entries++;
+        }
+        else
+        {
+            Node* n = data[hash];
+            while(true)
+            {
+                if(n->k == k)
+                {
+                    n->v = v;
+                    break;
+                }
+                else if(n->next == nullptr)
+                {
+                    n->next = new Node(k, v);
+                    entries++;
+                    break;
+                }
+                n = n->next;
+            }
+        }
+    }
+    
+    V remove(K k, V notFound)
+    {
+        unsigned int hash = hasher(k) % capacity;
+        if(data[hash] == nullptr)
+        {
+            return notFound;
+        }
+        else
+        {
+            Node* n = data[hash];
+            if(n->k == k)
+            {
+                V v = n->v;
+                data[hash] = n->next;
+                delete n;
+                entries--;
+                return v;
+            }
+            
+            while(n->next != nullptr)
+            {
+                if(n->next->k == k)
+                {
+                    Node* n = n->next;
+                    n->next = n->next->next;
+                    V v = n->v;
+                    delete n;
+                    entries--;
+                    return v;
+                }
+            }
+            return notFound;
+        }
+    }
+    
+    bool remove(K k)
+    {
+        unsigned int hash = hasher(k) % capacity;
+        if(data[hash] == nullptr)
+        {
+            return false;
+        }
+        else
+        {
+            Node* n = data[hash];
+            if(n->k == k)
+            {
+                data[hash] = n->next;
+                delete n;
+                entries--;
+                return true;
+            }
+            
+            while(n->next != nullptr)
+            {
+                if(n->next->k == k)
+                {
+                    Node* n = n->next;
+                    n->next = n->next->next;
+                    delete n;
+                    entries--;
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+    
+    V get(K k, V error) const
+    {
+        unsigned int hash = hasher(k) % capacity;
+        if(data[hash] == nullptr)
+        {
+            return error;
+        }
+        else
+        {
+            for(Node* n = data[hash]; n != nullptr; n = n->next)
+            {
+                if(n->k == k)
+                {
+                    return n->v;
+                }
+            }
+            return error;
+        }
+    }
+    
+    void clear()
+    {
+        for(int i = 0; i < capacity; i++)
+        {
+            Node* n = data[i];
+            while(n != nullptr)
+            {
+                Node* next = n->next;
+                delete n;
+                n = next;
+            }
+            data[i] = nullptr;
+        }
+        entries = 0;
+    }
+    
+    void print() const
+    {
+        for(int i = 0; i < capacity; i++)
+        {
+            Node* n = data[i];
+            std::cout << i << ": ";
+            if(n != nullptr)
+            {
+                std::cout << "(" << n->k << " = " << n->v << ")";
+                while(n->next != nullptr)
+                {
+                    n = n->next;
+                    std::cout << " -> (" << n->k << " = " << n->v << ")";
+                }
+            }
+            std::cout << std::endl;
+        }
+    }
+    
+    void printStats() const
+    {
+        int fill = capacity;
+        int cells = capacity;
+        for(int i = 0; i < capacity; i++)
+        {
+            Node* n = data[i];
+            if(n != nullptr)
+            {
+                while(n->next != nullptr)
+                {
+                    fill++;
+                    n = n->next;
+                }
+            }
+        }
+        std::cout << (double) fill / cells << std::endl;
+    }
+    
+private:
+    class Node
+    {
+    public:
+        Node* next = nullptr;
+        K k;
+        V v;
+        
+        Node(K k, V v) : k(k), v(v) 
+        {
+        }
+    };  
+    
+    int getHigherPrimeIndex(int lower) const
+    {
+        int low = 0;
+        int high = NUMBER_OF_PRIMES - 1;
+        int mid;
+        while(true)
+        {
+            if(low == high)
+            {
+                return low;
+            }
+            mid = (high + low) >> 1;
+            if(PRIMES[mid] >= lower)
+            {
+                high = mid;
+            }
+            else
+            {
+                low = mid + 1;
+            }
+        }
+    }
+    
+    void ensureCapacity()
+    {
+        if(entries < resizeCap)
+        {
+            return;
+        }
+        
+        primeIndex++;
+        if(primeIndex >= NUMBER_OF_PRIMES)
+        {
+            return;
+        }
+        
+        int oldCapacity = capacity;
+        capacity = PRIMES[primeIndex];
+        
+        //std::cout << "resize from " << oldCapacity << " to " << capacity << std::endl; 
+        
+        resizeCap = (capacity >> 2) * 3;
+        
+        Node** newData = new Node*[capacity];
+        for(int i = 0; i < capacity; i++)
+        {
+            newData[i] = nullptr;
+        }
+        
+        for(int i = 0; i < oldCapacity; i++)
+        {
+            Node* old = data[i];
+            if(old != nullptr)
+            {
+                unsigned int hash = hasher(old->k) % capacity;
+                Node* n = newData[hash];
+                if(n == nullptr)
+                {
+                    newData[hash] = old;
+                }
+                else
+                {
+                    while(n->next != nullptr)
+                    {
+                        n = n->next;
+                    }
+                    n->next = old;
+                }
+                
+                while(old->next != nullptr)
+                {
+                    n = old->next;
+                    old->next = nullptr;
+                    hash = hasher(n->k) % capacity;
+                    Node* m = newData[hash];
+                    if(m == nullptr)
+                    {
+                        newData[hash] = n;
+                    }
+                    else
+                    {
+                        while(m->next != nullptr)
+                        {
+                            m = m->next;
+                        }
+                        m->next = n;
+                    }
+                    old = n;
+                }
+            }
+        }
+        
+        delete[] data;
+        data = newData;
+    }
+    
+    unsigned int (*hasher)(K); 
+    
+    int primeIndex;
+    int capacity;
+    int resizeCap;
+    
+    Node** data;
+    int entries;
+};
+
+#endif

+ 98 - 0
data/UnsortedArrayList.h

@@ -0,0 +1,98 @@
+#ifndef UNSORTEDARRAYLIST_H
+#define UNSORTEDARRAYLIST_H
+
+#include <cstring>
+#include <iostream>
+
+template<class E>
+class UnsortedArrayList
+{
+public:
+    UnsortedArrayList()
+    {
+        data = new E[capacity];
+    }
+    
+    virtual ~UnsortedArrayList()
+    {
+        delete[] data;
+    }
+    
+    int getSize() const
+    {
+        return size;
+    }
+    
+    void add(E e)
+    {
+        if(capacity <= size)
+        {
+            capacity *= 2;
+            E* newData = new E[capacity];
+            memcpy(newData, data, sizeof(E) * size);
+            delete[] data;
+            data = newData;
+        }
+        data[size] = e;
+        size++;
+    }
+    
+    void remove(E e)
+    {
+        int i = 0;
+        while(i < size)
+        {
+            if(e == data[i])
+            {
+                if(i == size - 1)
+                {
+                    memset(data + i, 0, sizeof(E));
+                }
+                else
+                {
+                    data[i] = data[size - 1];
+                    memset(data + size - 1, 0, sizeof(E));
+                }
+                size--;
+            }
+            else
+            {
+                i++;
+            }
+        }
+    }
+    
+    void removeIndex(int index)
+    {
+        if(index >= 0 && index < size)
+        {
+            if(size == 1 || index == size - 1)
+            {
+                memset(data + index, 0, sizeof(E));
+            }
+            else
+            {
+                data[index] = data[size - 1];
+                memset(data + size - 1, 0, sizeof(E));
+            }
+            size--;
+        }
+    }
+    
+    E get(int index, E error) const
+    {
+        if(index >= 0 && index < size)
+        {
+            return data[index];
+        }
+        return error;
+    }
+    
+private:
+    int size = 0;
+    int capacity = 1;
+    E* data;
+};
+
+#endif
+

+ 1 - 0
engine/Mesh.h

@@ -41,6 +41,7 @@ private:
     unsigned int vertexSize = 0;
     unsigned int vertices = 0;
     unsigned int dataSize = 3;
+    
     float* data = nullptr;
 };
 

+ 91 - 3
engine/Shader.cpp

@@ -26,9 +26,11 @@ Shader::~Shader()
 
 void Shader::set3DMode(float lag)
 {
-    float q = 1.0f / tanf((0.5f * fovY) * M_PI / 180.0f);
+    float tan = tanf((0.5f * fovY) * M_PI / 180.0f);
+    float q = 1.0f / tan;
+    float aspect = (float) Engine::getWidth() / Engine::getHeight();
 
-    proj.set(0, 0, (q * Engine::getHeight()) / Engine::getWidth());
+    proj.set(0, 0, q / aspect);
     proj.set(1, 1, q);
     proj.set(2, 2, (nearClip + farClip) / (nearClip - farClip));
     proj.set(3, 2, -1.0f);
@@ -60,6 +62,9 @@ void Shader::set3DMode(float lag)
     up.cross(left);
     up.normalize();
     
+    // down
+    down.setInverse(up);
+    
     Vector3D interCamera = oldCamera;
     interCamera.addMul(camera, lag);
     interCamera.addMul(oldCamera, -lag);
@@ -83,6 +88,55 @@ void Shader::set3DMode(float lag)
     view.set(3, 1, 0.0f);
     view.set(3, 0, 0.0f);
     view.set(3, 3, 1.0f);
+    
+    // -------------------------------------------------------------------------
+    // update frustum planes
+    // -------------------------------------------------------------------------
+    
+    // http://cgvr.informatik.uni-bremen.de/teaching/cg_literatur/lighthouse3d_view_frustum_culling/index.html
+    float nearHigh = tan * nearClip;
+    float nearWidth = nearHigh * aspect;
+
+    float farHigh = tan * farClip;
+    float farWidth = farHigh * aspect;
+
+    Vector3D fc = interCamera;
+    fc.addMul(front, farClip);
+
+    Vector3D ftl = fc;
+    ftl.addMul(left, farWidth);
+    ftl.addMul(up, farHigh);
+
+    Vector3D fbl = fc;
+    fbl.addMul(left, farWidth);
+    fbl.addMul(down, farHigh);
+
+    Vector3D fbr = fc;
+    fbr.addMul(right, farWidth);
+    fbr.addMul(down, farHigh);
+
+    Vector3D nc = interCamera;
+    nc.addMul(front, nearClip);
+
+    Vector3D ntl = nc;
+    ntl.addMul(left, nearWidth);
+    ntl.addMul(down, nearHigh);
+
+    Vector3D ntr = nc;
+    ntr.addMul(right, nearWidth);
+    ntr.addMul(up, nearHigh);
+
+    Vector3D nbr = nc;
+    nbr.addMul(right, nearWidth);
+    nbr.addMul(down, nearHigh);
+
+    // generating planes with counter clockwise vector order
+    frustumPlanes[0].set(fbl, ftl, fbr); // far
+    frustumPlanes[1].set(ntl, ftl, fbl); // left
+    frustumPlanes[2].set(fbr, ntr, nbr); // right
+    frustumPlanes[3].set(fbl, fbr, nbr); // bottom
+    frustumPlanes[4].set(ntr, ftl, ntl); // top
+    frustumPlanes[5].set(nbr, ntr, ntl); // near
    
     // -------------------------------------------------------------------------
     // calculate vectors for movement
@@ -155,6 +209,40 @@ void Shader::setCamera(float x, float y, float z, float length, float width)
     widthAngle = width;
 }
 
+bool Shader::isInFrustum(float x, float y, float z, float x2, float y2, float z2) const
+{
+    //return true;
+    // http://cgvr.informatik.uni-bremen.de/teaching/cg_literatur/lighthouse3d_view_frustum_culling/index.html
+    // for each plane do ...
+    for(int fp = 0; fp < 6; fp++) 
+    {
+        // reset counters for corners in and out
+        int out = 0;
+        int in = 0;
+        // for each corner of the box do ...
+        // get out of the cycle as soon as a box as corners
+        // both inside and out of the frustum
+        for(int i = 0; i < 8 && (in == 0 || out == 0); i++) 
+        {
+            // is the corner outside or inside
+            if(frustumPlanes[fp].getSignedDistance(((i >> 2) & 1) ? x : x2, ((i >> 1) & 1) ? y : y2, (i & 1) ? z : z2) > 0)
+            {
+                out++;
+            }
+            else
+            {
+                in++;
+            }
+        }
+        //if all corners are out
+        if(in == 0)
+        {
+            return false;
+        }
+    }
+    return true;
+}
+
 const Vector3D& Shader::getFront() const
 {
     return front;
@@ -301,4 +389,4 @@ void Shader::rotateZ(float degrees)
 void Shader::updateModelMatrix()
 {
     Engine::setMatrix(unifModelMatrix, model.get().getValues());
-}
+}

+ 5 - 0
engine/Shader.h

@@ -1,6 +1,7 @@
 #ifndef CAMERA_H
 #define CAMERA_H
 
+#include "../math/Plane3D.h"
 #include "../math/Vector3D.h"
 #include "../math/Matrix3D.h"
 #include "../math/Matrix3DStack.h"
@@ -17,6 +18,8 @@ public:
     
     void storeCamera();
     void setCamera(float x, float y, float z, float length, float width);
+    
+    bool isInFrustum(float x, float y, float z, float x2, float y2, float z2) const;
 
     const Vector3D& getFront() const;
     const Vector3D& getBack() const;
@@ -73,6 +76,8 @@ private:
     Vector3D up;
     Vector3D down;   
     
+    Plane3D frustumPlanes[6];
+    
     float fovY = 60;
     float nearClip = 0.1f;
     float farClip = 1000.0f;

+ 28 - 0
math/Plane3D.cpp

@@ -0,0 +1,28 @@
+#include "Plane3D.h"
+#include <iostream>
+
+using namespace std;
+
+Plane3D::Plane3D()
+{
+}
+
+void Plane3D::set(const Vector3D& va, const Vector3D& vb, const Vector3D& vc)
+{
+    Vector3D h1 = vb;
+    h1.sub(va);
+    Vector3D h2 = vc;
+    h2.sub(va);
+    h1.cross(h2);
+    h1.normalize();
+    
+    a = h1.getX();
+    b = h1.getY();
+    c = h1.getZ();
+    d = -h1.dot(va);
+}
+
+float Plane3D::getSignedDistance(float x, float y, float z) const
+{
+    return x * a + y * b + z * c + d;
+}

+ 21 - 0
math/Plane3D.h

@@ -0,0 +1,21 @@
+#ifndef PLANE3D_H
+#define PLANE3D_H
+
+#include "Vector3D.h"
+
+class Plane3D
+{
+public:
+    Plane3D();
+
+    void set(const Vector3D& va, const Vector3D& vb, const Vector3D& vc);
+    float getSignedDistance(float x, float y, float z) const;
+    
+private:
+    float a = 1.0f;
+    float b = 1.0f;
+    float c = 1.0f;
+    float d = 1.0f;
+};
+
+#endif

+ 26 - 168
world/Chunk.cpp

@@ -4,7 +4,6 @@
 
 Chunk::Chunk(int chunkX, int chunkZ) : chunkX(chunkX), chunkZ(chunkZ)
 {
-    mesh = new ChunkMesh[HEIGHT_PARTIONS];
     for(int i = 0; i < HEIGHT_PARTIONS; i++)
     {
         dirty[i] = true;
@@ -13,7 +12,7 @@ Chunk::Chunk(int chunkX, int chunkZ) : chunkX(chunkX), chunkZ(chunkZ)
     {
         for(int x = 0; x < WIDTH; x++)
         {
-            int maxY = (int) (sin((x + chunkX * WIDTH) * 0.3) * 20 + 22) + (sin((z + chunkZ * DEPTH) * 0.3) * 20 + 22);
+            int maxY = (int) (sinf((x + chunkX * WIDTH) * 0.3) * 20 + 22) + (sinf((z + chunkZ * DEPTH) * 0.3) * 20 + 22);
             if(maxY > HEIGHT)
             {
                 maxY = HEIGHT;
@@ -33,7 +32,6 @@ Chunk::Chunk(int chunkX, int chunkZ) : chunkX(chunkX), chunkZ(chunkZ)
 
 Chunk::~Chunk()
 {
-    delete[] mesh;
 }
 
 void Chunk::setBlock(int x, int y, int z, unsigned short block)
@@ -46,181 +44,41 @@ unsigned short Chunk::getBlock(int x, int y, int z)
     return blocks[y & BITMASK_HEIGHT][x & BITMASK_WIDTH][z & BITMASK_DEPTH];
 }
 
-void Chunk::renderTick(Shader& shader, DirectRenderer& dr, float lag)
+int Chunk::getChunkX() const
+{
+    return chunkX;
+}
+
+int Chunk::getChunkZ() const
+{
+    return chunkZ;
+}
+
+bool Chunk::isDirty() const
 {
-    shader.translateTo(chunkX * WIDTH, 0, chunkZ * DEPTH);
-    shader.updateModelMatrix();
     for(int i = 0; i < HEIGHT_PARTIONS; i++)
     {
         if(dirty[i])
         {
-            buildChunk(i);
-            dirty[i] = false;
+            return true;
         }
-        mesh[i].draw();
     }
+    return false;
 }
 
-void Chunk::buildChunk(int partionY)
-{    
-    Mesh& m = mesh[partionY];
-    
-    int max = (partionY + 1) * PARTION_HEIGHT;
-    for(int y = partionY * PARTION_HEIGHT; y < max; y++)
+bool Chunk::isDirty(int index) const
+{
+    if(index >= 0 && index < HEIGHT_PARTIONS)
     {
-        for(int x = 0; x < WIDTH; x++)
-        {
-            for(int z = 0; z < DEPTH; z++)
-            {
-                if(blocks[y][x][z] == 1)
-                {
-                    for(int i = 0; i < 36; i++)
-                    {
-                        m.addColor(1, 0, 0, 1);
-                    }
-                    
-                    // bottom
-                    if(y <= 0 || blocks[y - 1][x][z] != 1)
-                    {
-                        //m.addTexture(0.125f, 0.0f);
-                        //m.addTexture(0.1875f, 0.0f);
-                        //m.addTexture(0.125f, 0.0625f);
-                        //m.addTexture(0.1875f, 0.0f);
-                        //m.addTexture(0.1875f, 0.0625f);
-                        //m.addTexture(0.125f, 0.0625f);
-
-                        for(int i = 0; i < 6; i++)
-                        {
-                            m.addNormal(0.0f, -1.0f, 0.0f);
-                        }
-
-                        m.addPosition(x + 0.0f, y + 0.0f, z + 0.0f);
-                        m.addPosition(x + 1.0f, y + 0.0f, z + 0.0f);
-                        m.addPosition(x + 0.0f, y + 0.0f, z + 1.0f);
-                        m.addPosition(x + 1.0f, y + 0.0f, z + 0.0f);
-                        m.addPosition(x + 1.0f, y + 0.0f, z + 1.0f);
-                        m.addPosition(x + 0.0f, y + 0.0f, z + 1.0f);
-                    }
-
-                    // top
-                    if(y + 1 >= HEIGHT || blocks[y + 1][x][z] != 1)
-                    {
-                        //m.addTexture(0.25f, 0.0f);
-                        //m.addTexture(0.25f, 0.0625f);
-                        //m.addTexture(0.3125f, 0.0f);
-                        //m.addTexture(0.3125f, 0.0f);
-                        //m.addTexture(0.25f, 0.0625f);
-                        //m.addTexture(0.3125f, 0.0625f);
-
-                        for(int i = 0; i < 6; i++)
-                        {
-                            m.addNormal(0.0f, 1.0f, 0.0f);
-                        }
-
-                        m.addPosition(x + 0.0f, y + 1.0f, z + 0.0f);
-                        m.addPosition(x + 0.0f, y + 1.0f, z + 1.0f);
-                        m.addPosition(x + 1.0f, y + 1.0f, z + 0.0f);
-                        m.addPosition(x + 1.0f, y + 1.0f, z + 0.0f);
-                        m.addPosition(x + 0.0f, y + 1.0f, z + 1.0f);
-                        m.addPosition(x + 1.0f, y + 1.0f, z + 1.0f);
-                    }
-
-                    // right
-                    if(x + 1 >= WIDTH || blocks[y][x + 1][z] != 1)
-                    {
-                        //m.addTexture(0.1875f, 0.0625f);
-                        //m.addTexture(0.25f, 0.0f);
-                        //m.addTexture(0.25f, 0.0625f);
-                        //m.addTexture(0.1875f, 0.0625f);
-                        //m.addTexture(0.1875f, 0.0f);
-                        //m.addTexture(0.25f, 0.0f);
-
-                        for(int i = 0; i < 6; i++)
-                        {
-                            m.addNormal(1.0f, 0.0f, 0.0f);
-                        }
-
-                        m.addPosition(x + 1.0f, y + 0.0f, z + 0.0f);
-                        m.addPosition(x + 1.0f, y + 1.0f, z + 1.0f);
-                        m.addPosition(x + 1.0f, y + 0.0f, z + 1.0f);
-                        m.addPosition(x + 1.0f, y + 0.0f, z + 0.0f);
-                        m.addPosition(x + 1.0f, y + 1.0f, z + 0.0f);
-                        m.addPosition(x + 1.0f, y + 1.0f, z + 1.0f);
-                    }
-
-                    // left
-                    if(x <= 0 || blocks[y][x - 1][z] != 1)
-                    {
-                        //m.addTexture(0.1875f, 0.0625f);
-                        //m.addTexture(0.25f, 0.0625f);
-                        //m.addTexture(0.25f, 0.0f);
-                        //m.addTexture(0.1875f, 0.0625f);
-                        //m.addTexture(0.25f, 0.0f);
-                        //m.addTexture(0.1875f, 0.0f);
-
-                        for(int i = 0; i < 6; i++)
-                        {
-                            m.addNormal(-1.0f, 0.0f, 0.0f);
-                        }
-
-                        m.addPosition(x + 0.0f, y + 0.0f, z + 0.0f);
-                        m.addPosition(x + 0.0f, y + 0.0f, z + 1.0f);
-                        m.addPosition(x + 0.0f, y + 1.0f, z + 1.0f);
-                        m.addPosition(x + 0.0f, y + 0.0f, z + 0.0f);
-                        m.addPosition(x + 0.0f, y + 1.0f, z + 1.0f);
-                        m.addPosition(x + 0.0f, y + 1.0f, z + 0.0f);
-                    }
-
-                    // back
-                    if(z + 1 >= DEPTH || blocks[y][x][z + 1] != 1)
-                    {
-                        //m.addTexture(0.1875f, 0.0625f);
-                        //m.addTexture(0.25f, 0.0625f);
-                        //m.addTexture(0.25f, 0.0f);
-                        //m.addTexture(0.1875f, 0.0625f);
-                        //m.addTexture(0.25f, 0.0f);
-                        //m.addTexture(0.1875f, 0.0f);
-
-                        for(int i = 0; i < 6; i++)
-                        {
-                            m.addNormal(0.0f, 0.0f, -1.0f);
-                        }
-
-                        m.addPosition(x + 0.0f, y + 0.0f, z + 1.0f);
-                        m.addPosition(x + 1.0f, y + 0.0f, z + 1.0f);
-                        m.addPosition(x + 1.0f, y + 1.0f, z + 1.0f);
-                        m.addPosition(x + 0.0f, y + 0.0f, z + 1.0f);
-                        m.addPosition(x + 1.0f, y + 1.0f, z + 1.0f);
-                        m.addPosition(x + 0.0f, y + 1.0f, z + 1.0f);
-                    }
-
-                    // front
-                    if(z <= 0 || blocks[y][x][z - 1] != 1)
-                    {
-                        //m.addTexture(0.1875f, 0.0625f);
-                        //m.addTexture(0.25f, 0.0f);
-                        //m.addTexture(0.25f, 0.0625f);
-                        //m.addTexture(0.1875f, 0.0625f);
-                        //m.addTexture(0.1875f, 0.0f);
-                        //m.addTexture(0.25f, 0.0f);
-
-                        for(int i = 0; i < 6; i++)
-                        {
-                            m.addNormal(0.0f, 0.0f, 1.0f);
-                        }
-
-                        m.addPosition(x + 0.0f, y + 0.0f, z + 0.0f);
-                        m.addPosition(x + 1.0f, y + 1.0f, z + 0.0f);
-                        m.addPosition(x + 1.0f, y + 0.0f, z + 0.0f);
-                        m.addPosition(x + 0.0f, y + 0.0f, z + 0.0f);
-                        m.addPosition(x + 0.0f, y + 1.0f, z + 0.0f);
-                        m.addPosition(x + 1.0f, y + 1.0f, z + 0.0f);
-                    }
-                }
-            }
-        }
+        return dirty[index];
     }
-    
-    m.build();
+    return false;
 }
 
+void Chunk::clearDirtyFlag(int index)
+{
+    if(index >= 0 && index < HEIGHT_PARTIONS)
+    {
+        dirty[index] = false;
+    }
+}

+ 6 - 8
world/Chunk.h

@@ -1,10 +1,6 @@
 #ifndef CHUNK_H
 #define CHUNK_H
 
-#include "../engine/Mesh.h"
-#include "../engine/Shader.h"
-#include "../engine/DirectRenderer.h"
-
 class Chunk
 {
 public:
@@ -14,7 +10,12 @@ public:
     void setBlock(int x, int y, int z, unsigned short block);
     unsigned short getBlock(int x, int y, int z);
     
-    void renderTick(Shader& shader, DirectRenderer& dr, float lag);
+    int getChunkX() const;
+    int getChunkZ() const;
+    
+    bool isDirty() const;
+    bool isDirty(int index) const;
+    void clearDirtyFlag(int index);
     
     // chunk constants
     static const int PARTION_HEIGHT = 16;
@@ -28,14 +29,11 @@ public:
     static const int BITMASK_HEIGHT = HEIGHT - 1;
     static const int BITMASK_DEPTH = DEPTH - 1;
 private:
-    void buildChunk(int partionY);
-    
     int chunkX;
     int chunkZ;
     
     unsigned short blocks[HEIGHT][WIDTH][DEPTH];
     bool dirty[HEIGHT_PARTIONS];
-    ChunkMesh* mesh;
 };
 
 #endif

+ 13 - 0
world/IChunkListener.h

@@ -0,0 +1,13 @@
+#ifndef ICHUNKLISTENER_H
+#define ICHUNKLISTENER_H
+
+#include "Chunk.h"
+
+class IChunkListener
+{
+public:
+    virtual void updateChunk(Chunk& c, Chunk* north, Chunk* east, Chunk* south, Chunk* west) = 0;
+};
+
+#endif
+

+ 14 - 0
world/IChunkProvider.h

@@ -0,0 +1,14 @@
+#ifndef ICHUNKPROVIDER_H
+#define ICHUNKPROVIDER_H
+
+#include "Chunk.h"
+
+class IChunkProvider
+{
+public:
+    virtual Chunk* getChunk(int x, int z) = 0;
+    virtual bool isChunkLoaded(int x, int z) const = 0;
+    virtual void forEachLoadedChunk(void* data, void (*fun) (Chunk&, void*)) const = 0;
+};
+
+#endif

+ 31 - 22
world/World.cpp

@@ -1,39 +1,48 @@
 #include "World.h"
 
-World::World()
+World::World(IChunkProvider* chunks) : chunks(chunks)
 {
-    chunks = new Chunk**[chunkX];
-    for(int x = 0; x < chunkX; x++)
-    {
-        chunks[x] = new Chunk*[chunkZ];
-        for(int z = 0; z < chunkZ; z++)
-        {
-            chunks[x][z] = new Chunk(x, z);
-        }
-    }
 }
 
 World::~World()
 {
-    for(int x = 0; x < chunkX; x++)
+    
+}
+
+void World::registerChunkListener(IChunkListener* listener)
+{
+    chunkListener.add(listener);
+}
+
+void World::removeChunkListener(IChunkListener* listener)
+{
+    chunkListener.remove(listener);
+}
+
+void World::updateDirtyChunks()
+{
+    chunks->forEachLoadedChunk(this, [](Chunk& c, void* world)
     {
-        for(int z = 0; z < chunkZ; z++)
+        if(c.isDirty())
         {
-            delete chunks[x][z];
+            ((World*) world)->updateChunk(c);
         }
-        delete[] chunks[x];
-    }
-    delete[] chunks;
+    });
 }
 
-void World::renderTick(Shader& shader, DirectRenderer& dr, float lag)
+void World::updateChunk(Chunk& c)
 {
-    for(int x = 0; x < chunkX; x++)
+    int x = c.getChunkX();
+    int z = c.getChunkZ();
+    
+    Chunk* north = chunks->getChunk(x + 1, z);
+    Chunk* east = chunks->getChunk(x, z + 1);
+    Chunk* south = chunks->getChunk(x - 1, z);
+    Chunk* west = chunks->getChunk(x, z - 1);
+    
+    for(int i = 0; i < chunkListener.getSize(); i++)
     {
-        for(int z = 0; z < chunkZ; z++)
-        {
-            chunks[x][z]->renderTick(shader, dr, lag);
-        }
+        chunkListener.get(i, nullptr)->updateChunk(c, north, east, south, west);
     }
 }
 

+ 14 - 7
world/World.h

@@ -1,22 +1,29 @@
 #ifndef WORLD_H
 #define WORLD_H
 
-#include "../engine/Shader.h"
-#include "../engine/DirectRenderer.h"
+#include <iostream>
 #include "Chunk.h"
+#include "IChunkProvider.h"
+#include "IChunkListener.h"
+#include "../data/UnsortedArrayList.h"
+
+using namespace std;
 
 class World
 {
 public:
-    World();
+    World(IChunkProvider* chunks);
     virtual ~World();
     
-    void renderTick(Shader& shader, DirectRenderer& dr, float lag);
+    void registerChunkListener(IChunkListener* listener);
+    void removeChunkListener(IChunkListener* listener);
+    
+    void updateDirtyChunks();
 private:
-    const int chunkX = 16;
-    const int chunkZ = 16;
+    void updateChunk(Chunk& c);
     
-    Chunk*** chunks;
+    UnsortedArrayList<IChunkListener*> chunkListener;
+    IChunkProvider* chunks;
 };
 
 #endif