#include "GLFW/glfw3.h" #include "common/Box.h" #include "common/Packets.h" #include "data/Array.h" #include "data/HashMap.h" #include "data/RingBuffer.h" #include "math/Frustum.h" #include "network/Client.h" #include "rendering/Shader.h" #include "rendering/Texture.h" #include "rendering/VertexBuffer.h" #include "rendering/Window.h" #include "utils/Buffer.h" static constexpr int WORLD_SIZE = 16; static Array world(true); static Shader shader; static Shader fontShader; static VertexBuffer vertexBuffer; static VertexBuffer markVertexBuffer; static VertexBuffer fontBuffer; static Texture fontTexture; static bool dirtyVertexBuffer = true; static int vertices = 0; static Frustum frustum(80.0f, 0.1f, 1000.0f); static Vector3 lastPosition(8.0f, 30.0f, 8.0f); static Vector3 position(lastPosition); static float lastWidthAngle = 0.0f; static float widthAngle = lastWidthAngle; static float lastLengthAngle = 0.0f; static float lengthAngle = lastLengthAngle; static Vector3 velocity; static Vector3 acceleration; static bool onGround = false; static Window::Controls::ButtonId leftKey; static Window::Controls::ButtonId rightKey; static Window::Controls::ButtonId upKey; static Window::Controls::ButtonId downKey; static Window::Controls::ButtonId jumpKey; static Window::Controls::ButtonId sneakKey; static Window::Controls::ButtonId escapeKey; static Window::Controls::ButtonId chatKey; static Window::Controls::ButtonId sendChatKey; static Window::Controls::ButtonId primaryClick; static bool trappedMoused = false; static Vector3 up; static Vector3 down; static Vector3 left; static Vector3 right; static Vector3 front; static Vector3 back; static Vector3 focus; static bool hasFocus = false; struct Player { Vector3 lastPosition; Vector3 position; }; static HashMap players; typedef StringBuffer<50> ChatMessage; static Array chat; static int chatIndex = 0; static bool renderInput = false; static void addToChat(const ChatMessage& msg) { chat[chatIndex] = msg; chatIndex = (chatIndex + 1) % chat.getLength(); } static bool isRunning() { return !Window::shouldClose(); // return !Window::shouldClose() && // (Client::isConnecting() || Client::isConnected()); } static void set(int x, int y, int z, bool b) { if(x < 0 || x >= WORLD_SIZE || y < 0 || y >= WORLD_SIZE || z < 0 || z >= WORLD_SIZE) { return; } world[x * WORLD_SIZE * WORLD_SIZE + y * WORLD_SIZE + z] = b; dirtyVertexBuffer = true; } static void onPacket(InPacket& in) { uint8 type = 0; if(in.readU8(type)) { puts("no data"); return; } switch(static_cast(type)) { case Packet::Type::WORLD: { for(bool& b : world) { uint8 data = 0; if(in.readU8(data)) { puts("too less data in world packet"); return; } b = data; } dirtyVertexBuffer = true; break; } case Packet::Type::SET_BLOCK: { Vector3 pos; in.readFloat(pos[0]); in.readFloat(pos[1]); in.readFloat(pos[2]); uint8 type; in.readU8(type); set(pos[0], pos[1], pos[2], type); break; } case Packet::Type::PLAYER: { Vector3 pos; in.readFloat(pos[0]); in.readFloat(pos[1]); in.readFloat(pos[2]); int client = -1; in.readS32(client); Player* p = players.search(client); if(p != nullptr) { p->position = pos; } else { players.add(client, {pos, pos}); } break; } case Packet::Type::CHAT: { ChatMessage msg; uint32 u; int i = 0; while(!in.readU32(u)) { i++; msg.appendUnicode(u); } printf("%d\n", i); addToChat(msg); break; } default: printf("invalid package type %d\n", static_cast(type)); } } static void onDisconnect() { puts("Disconnect"); } static void addTriangle(Buffer& buffer, const Vector3& a, const Vector3& b, const Vector3& c) { Vector3 normal = (b - a).cross(c - a); buffer.add(a).add(normal).add(b).add(normal).add(c).add(normal); } static bool init() { Error e = shader.compile("resources/shader/test.vs", "resources/shader/test.fs"); if(e.has()) { e.message.printLine(); return true; } e = fontShader.compile("resources/shader/fontTest.vs", "resources/shader/fontTest.fs"); if(e.has()) { e.message.printLine(); return true; } e = fontTexture.load("resources/font8x8.png", 0); if(e.has()) { e.message.printLine(); return true; } vertexBuffer.init(VertexBuffer::Attributes().addFloat(3).addFloat(3)); markVertexBuffer.init(VertexBuffer::Attributes().addFloat(3).addFloat(3)); fontBuffer.init(VertexBuffer::Attributes().addFloat(3).addFloat(2)); Vector3 v000(0, 0, 0); Vector3 v001(0, 0, 1); Vector3 v010(0, 1, 0); Vector3 v011(0, 1, 1); Vector3 v100(1, 0, 0); Vector3 v101(1, 0, 1); Vector3 v110(1, 1, 0); Vector3 v111(1, 1, 1); Buffer buffer(100); addTriangle(buffer, v000, v100, v001); addTriangle(buffer, v100, v101, v001); addTriangle(buffer, v010, v011, v110); addTriangle(buffer, v110, v011, v111); addTriangle(buffer, v000, v001, v010); addTriangle(buffer, v001, v011, v010); addTriangle(buffer, v100, v110, v101); addTriangle(buffer, v101, v110, v111); addTriangle(buffer, v001, v101, v011); addTriangle(buffer, v111, v011, v101); addTriangle(buffer, v000, v010, v100); addTriangle(buffer, v110, v100, v010); markVertexBuffer.setStaticData(buffer.getLength(), buffer); Client::setPacketHandler(onPacket); Client::setDisconnectHandler(onDisconnect); leftKey = Window::Controls::add("Left"); Window::Controls::bindKey(leftKey, GLFW_KEY_A); rightKey = Window::Controls::add("Right"); Window::Controls::bindKey(rightKey, GLFW_KEY_D); upKey = Window::Controls::add("Up"); Window::Controls::bindKey(upKey, GLFW_KEY_W); downKey = Window::Controls::add("Right"); Window::Controls::bindKey(downKey, GLFW_KEY_S); jumpKey = Window::Controls::add("Jump"); Window::Controls::bindKey(jumpKey, GLFW_KEY_SPACE); sneakKey = Window::Controls::add("Sneak"); Window::Controls::bindKey(sneakKey, GLFW_KEY_LEFT_SHIFT); escapeKey = Window::Controls::add("Escape"); Window::Controls::bindKey(escapeKey, GLFW_KEY_ESCAPE); chatKey = Window::Controls::add("Chat"); Window::Controls::bindKey(chatKey, GLFW_KEY_T); sendChatKey = Window::Controls::add("Send Chat"); Window::Controls::bindKey(sendChatKey, GLFW_KEY_ENTER); primaryClick = Window::Controls::add("Primary Click"); Window::Controls::bindMouse(primaryClick, GLFW_MOUSE_BUTTON_LEFT); return false; } static void updateDirections(float lag) { front.setAngles(Math::interpolate(lastLengthAngle, lengthAngle, lag), Math::interpolate(lastWidthAngle, widthAngle, lag)); back = -front; right = front.cross(Vector3(0.0f, 1.0f, 0.0f)); right.normalize(); left = -right; up = front.cross(left); down = -up; } static bool isAir(int x, int y, int z) { if(x < 0 || x >= WORLD_SIZE || y < 0 || y >= WORLD_SIZE || z < 0 || z >= WORLD_SIZE) { return true; } return !world[x * WORLD_SIZE * WORLD_SIZE + y * WORLD_SIZE + z]; } static List getBoxes(const Box& box) { int minX = floorf(box.getMin()[0]); int minY = floorf(box.getMin()[1]); int minZ = floorf(box.getMin()[2]); int maxX = floorf(box.getMax()[0]); int maxY = floorf(box.getMax()[1]); int maxZ = floorf(box.getMax()[2]); Box base(Vector3(1.0f, 1.0f, 1.0f)); List boxes; for(int x = minX; x <= maxX; x++) { for(int y = minY; y <= maxY; y++) { for(int z = minZ; z <= maxZ; z++) { if(!isAir(x, y, z)) { boxes.add(base.offset(Vector3(x, y, z))); } } } } return boxes; } static Vector3 limitMove() { Vector3 move = velocity; Box box(Vector3(0.8f, 0.8f, 0.8f)); box = box.offset(position - Vector3(0.4f, 0.0f, 0.4f)); List boxes = getBoxes(box.expand(move)); if(boxes.getLength() == 0) { return move; } Vector3 realMove; constexpr float step = 0.05f; while(move[0] != 0.0f || move[1] != 0.0f || move[2] != 0.0f) { for(int i = 0; i < 3; i++) { Vector3 old = realMove; if(move[i] > step) { realMove[i] += step; move[i] -= step; } else if(move[i] < -step) { realMove[i] -= step; move[i] += step; } else if(move[i] != 0.0f) { realMove[i] += move[i]; move[i] = 0.0f; } Box moved = box.offset(realMove); for(const Box& box : boxes) { if(box.collidesWith(moved)) { move[i] = 0.0f; realMove = old; break; } } } } return realMove; } static void tick() { if(renderInput) { if(Window::Controls::wasReleased(sendChatKey)) { OutPacket out = Packet::build(Packet::Type::CHAT); for(uint32 u : Window::Input::getUnicode()) { out.writeU32(u); } Client::send(out, PacketType::RELIABLE); Window::Input::reset(); } if(Window::Controls::wasReleased(escapeKey)) { Window::Input::disable(); Window::trapCursor(); trappedMoused = true; renderInput = false; } } else { if(Window::Controls::isDown(chatKey)) { Window::Input::enable(); Window::freeCursor(); trappedMoused = false; renderInput = true; } if(Window::Controls::wasReleased(primaryClick)) { Window::trapCursor(); trappedMoused = true; } if(Window::Controls::wasReleased(escapeKey)) { Window::freeCursor(); trappedMoused = false; } } lastPosition = position; lastWidthAngle = widthAngle; lastLengthAngle = lengthAngle; for(Player& p : players.values()) { p.lastPosition = p.position; } Client::tick(); updateDirections(0.0f); Vector3 b = back; b[1] = 0.0f; b.normalize(); Vector3 r = right; r[1] = 0.0f; r.normalize(); if(!renderInput) { Vector3 force; if(Window::Controls::isDown(downKey)) { force += b; } if(Window::Controls::isDown(upKey)) { force -= b; } if(Window::Controls::isDown(leftKey)) { force -= r; } if(Window::Controls::isDown(rightKey)) { force += r; } if(force.squareLength() > 0.0f) { force.normalize(); } acceleration += force * 0.1f; if(Window::Controls::isDown(jumpKey) && onGround) { acceleration[1] += (0.42f / 0.98f + 0.08f); } } if(trappedMoused) { Vector2 diff = (Window::Controls::getLastMousePosition() - Window::Controls::getMousePosition()) * 0.1f; widthAngle += diff[1]; lengthAngle += diff[0]; if(widthAngle > 89.0f) { widthAngle = 89.0f; } if(widthAngle < -89.0f) { widthAngle = -89.0f; } } hasFocus = false; Vector3 p = position + Vector3(0.0f, 0.8f, 0.0f); Vector3 step = front * 0.125f; for(int i = 0; i < 80; i++) { if(!isAir(p[0], p[1], p[2])) { hasFocus = true; focus = Vector3(static_cast(p[0]), static_cast(p[1]), static_cast(p[2])); break; } p += step; } if(hasFocus && Window::Controls::wasReleased(primaryClick) && !renderInput) { set(focus[0], focus[1], focus[2], false); dirtyVertexBuffer = true; OutPacket out = Packet::build(Packet::Type::SET_BLOCK); out.writeFloat(focus[0]); out.writeFloat(focus[1]); out.writeFloat(focus[2]); out.writeU8(0); Client::send(out, PacketType::RELIABLE); } OutPacket out = Packet::build(Packet::Type::PLAYER); out.writeFloat(position[0]); out.writeFloat(position[1]); out.writeFloat(position[2]); out.writeS32(-1); Client::send(out, PacketType::RELIABLE); velocity += acceleration; velocity *= Vector3(0.686f, 0.98f, 0.686f); acceleration = Vector3(0.0f, -0.08f, 0.0f); Vector3 move = limitMove(); if(move[1] + position[1] < 0.0f) { move[1] = -position[1]; } position += move; onGround = move[1] == 0.0f && velocity[1] < 0.0f; velocity = move; } static void addCube(Buffer& buffer, int x, int y, int z) { if(isAir(x, y, z)) { return; } Vector3 v000(x, y, z); Vector3 v001(x, y, z + 1); Vector3 v010(x, y + 1, z); Vector3 v011(x, y + 1, z + 1); Vector3 v100(x + 1, y, z); Vector3 v101(x + 1, y, z + 1); Vector3 v110(x + 1, y + 1, z); Vector3 v111(x + 1, y + 1, z + 1); if(isAir(x, y - 1, z)) { addTriangle(buffer, v000, v100, v001); addTriangle(buffer, v100, v101, v001); } if(isAir(x, y + 1, z)) { addTriangle(buffer, v010, v011, v110); addTriangle(buffer, v110, v011, v111); } if(isAir(x - 1, y, z)) { addTriangle(buffer, v000, v001, v010); addTriangle(buffer, v001, v011, v010); } if(isAir(x + 1, y, z)) { addTriangle(buffer, v100, v110, v101); addTriangle(buffer, v101, v110, v111); } if(isAir(x, y, z + 1)) { addTriangle(buffer, v001, v101, v011); addTriangle(buffer, v111, v011, v101); } if(isAir(x, y, z - 1)) { addTriangle(buffer, v000, v010, v100); addTriangle(buffer, v110, v100, v010); } } static void buildRenderingBuffer() { Buffer buffer(100); for(int x = 0; x < WORLD_SIZE; x++) { for(int y = 0; y < WORLD_SIZE; y++) { for(int z = 0; z < WORLD_SIZE; z++) { addCube(buffer, x, y, z); } } } vertices = buffer.getLength() / (2 * sizeof(Vector3)); vertexBuffer.setStaticData(buffer.getLength(), buffer); } static void renderString(const char* text) { static Buffer buffer(50); buffer.clear(); constexpr float fontSize = 8.0f; constexpr float fontStep = 8.0f / 128.0f; int index = 0; Vector3 pos; int vertices = 0; while(text[index] != '\0') { Vector3 right = pos + Vector3(fontSize, 0.0f, 0.0f); Vector3 down = pos + Vector3(0.0f, fontSize, 0.0f); Vector3 downRight = pos + Vector3(fontSize, fontSize, 0.0f); int i = text[index]; if(i < 0 && text[index + 1] != '\0') { index++; i = ((i & 0x1F) << 6) | (text[index] & 0x3F); } Vector2 tPos(fontStep * (i % 16), fontStep * (i / 16)); Vector2 tRight = tPos + Vector2(fontStep, 0.0f); Vector2 tDown = tPos + Vector2(0.0f, fontStep); Vector2 tDownRight = tPos + Vector2(fontStep, fontStep); buffer.add(pos).add(tPos).add(down).add(tDown).add(right).add(tRight); buffer.add(down).add(tDown).add(right).add(tRight).add(downRight).add( tDownRight); pos += Vector3(fontSize, 0.0f, 0.0f); vertices += 6; index++; } fontBuffer.setDynamicData(buffer.getLength(), buffer); fontBuffer.draw(vertices); } static void render(float lag) { GL::clear(); GL::enableDepthTesting(); if(dirtyVertexBuffer) { dirtyVertexBuffer = false; buildRenderingBuffer(); puts("rebuilt buffer"); } shader.use(); GL::setViewport(Window::getSize()[0], Window::getSize()[1]); const Matrix& proj = frustum.updateProjection(Window::getSize()); shader.setMatrix("proj", proj.getValues()); updateDirections(lag); Matrix view; Vector3 center = Math::interpolate(lastPosition, position, lag) + Vector3(0.0f, 0.8f, 0.0f); view.set(0, Vector4(right[0], right[1], right[2], right.dot(-center))); view.set(1, Vector4(up[0], up[1], up[2], up.dot(-center))); view.set(2, Vector4(back[0], back[1], back[2], back.dot(-center))); view.set(3, Vector4(0.0f, 0.0f, 0.0f, 1.0f)); shader.setMatrix("view", view.getValues()); shader.setVector("color", Vector3(1.0f, 1.0f, 1.0f)); Matrix model; shader.setMatrix("model", model.getValues()); vertexBuffer.draw(vertices); if(hasFocus) { shader.setVector("color", Vector3(0.8f, 0.6f, 0.6f)); model.translate(Vector3(-0.5f, -0.5f, -0.5f)); model.scale(1.01f); model.translate(Vector3(0.5f, 0.5f, 0.5f)); model.translate(focus); shader.setMatrix("model", model.getValues()); markVertexBuffer.draw(36); } shader.setVector("color", Vector3(1.0f, 0.0f, 0.0f)); for(const Player& p : players.values()) { model.translateTo(Vector3(-0.5f, -0.5f, -0.5f)); model.scale(0.8f); model.translate(Math::interpolate(p.lastPosition, p.position, lag) + Vector3(0.0f, 0.4f, 0.0f)); shader.setMatrix("model", model.getValues()); markVertexBuffer.draw(36); } shader.setMatrix("proj", Matrix().getValues()); shader.setMatrix("view", Matrix().getValues()); shader.setVector("color", Vector3(1.0f, 0.0f, 1.0f)); model.translateTo(Vector3(-0.5f, -0.5f, -0.5f)); model.scale(0.05f); shader.setMatrix("model", model.getValues()); markVertexBuffer.draw(36); GL::disableDepthTesting(); GL::enableBlending(); fontShader.use(); fontShader.setMatrix("proj", Matrix().getValues()); view.translateTo(Vector3(0.0f, 0.0f, 0.0f)); IntVector2 size = Window::getSize(); view.scale(Vector3(2.0f / size[0], -2.0f / size[1], 1.0f)); view.translate(Vector3(-1.0f, 1.0f, 0.0f)); fontShader.setMatrix("view", view.getValues()); fontTexture.bindTo(0); for(int i = 0; i < chat.getLength(); i++) { model.translateTo(Vector3(0.0f, i * 8.0f, 0.0f)).scale(3.0f); fontShader.setMatrix("model", model.getValues()); renderString(chat[(i + chatIndex) % chat.getLength()]); } if(renderInput) { model.translateTo(Vector3(0.0f, chat.getLength() * 8.0f, 0.0f)) .scale(3.0f); fontShader.setMatrix("model", model.getValues()); StringBuffer<256> s; Window::Input::toString(s); renderString(s); model .translateTo(Vector3(Window::Input::getCursor() * 8.0f, chat.getLength() * 8.0f + 2.0f, 0.0f)) .scale(3.0f); fontShader.setMatrix("model", model.getValues()); renderString("_"); } GL::disableBlending(); } int main(int argAmount, const char* const* args) { const char* server = "127.0.0.1"; if(argAmount >= 2) { server = args[1]; } Error e = Client::start(); if(e.has()) { e.message.printLine(); return 0; } e = Client::connect(server, 11196, 40); if(e.has()) { e.message.printLine(); return 0; } Window::Options options(4, 3, IntVector2(1024, 600), false, "test"); e = Window::open(options); if(e.has()) { e.message.printLine(); Client::stop(); return 0; } if(init()) { Client::stop(); return 0; } Window::show(); Window::run(50'000'000); Window::close(); Client::stop(); return 0; }