// stdlib // TODO(djledda): get rid of this #include // Graphics bindings and libs #include "lib/glad/glad.c" #include #define GLM_ENABLE_EXPERIMENTAL #include #include #include #include #include // Project #include "SomaSolve.cpp" // errors from iostream also defining the keyword `global` #include "gfx/gfx.cpp" #include "world/world.cpp" #include "VoxelSpace.cpp" #include "./tests.cpp" #include "lib/djstdlib/core.cpp" // Library initialisation #define STB_IMAGE_IMPLEMENTATION #include "lib/loaders/stb_image.h" #define TINYOBJ_LOADER_C_IMPLEMENTATION #include "lib/loaders/tinyobj.h" #define PI (real32)3.14159265358979323846264338327950288 void print(glm::vec3* vector) { glm::vec3 vec = *vector; print( "┌ ┐\n" "│%7.2f%, %7.2f, %7.2f │\n" "└ ┘\n", vec[0], vec[1], vec[2]); } void print(glm::mat4* matrix) { glm::mat4 mat = *matrix; print( "┌ ┐\n" "│%7.2f%, %7.2f, %7.2f, %7.2f │\n" "│%7.2f%, %7.2f, %7.2f, %7.2f │\n" "│%7.2f%, %7.2f, %7.2f, %7.2f │\n" "│%7.2f%, %7.2f, %7.2f, %7.2f │\n" "└ ┘\n", mat[0][0], mat[0][1], mat[0][2], mat[0][3], mat[1][0], mat[1][1], mat[1][2], mat[1][3], mat[2][0], mat[2][1], mat[2][2], mat[2][3], mat[3][0], mat[3][1], mat[3][2], mat[3][3]); } struct Camera { glm::mat4 view; glm::mat4 proj; glm::vec3 pos; glm::vec3 up; glm::vec3 target; }; Camera *createCamera(Arena *arena, real32 aspect_ratio = 800.0f / 600.0f) { Camera *result = PushStruct(arena, Camera); result->view = glm::mat4(); result->proj = glm::perspective(glm::radians(45.0f), aspect_ratio, 0.1f, 100.0f); result->pos = glm::vec3(0.0f); result->up = glm::vec3(0.0f, 1.0f, 0.0f); return result; } void cameraLookAt(Camera *c, float x, float y, float z) { c->target = glm::vec3(x, y, z); c->view = glm::lookAt(c->pos, c->target, c->up); } void camera_set_up(Camera *c, real32 up_x, real32 up_y, real32 up_z) { c->up = glm::vec3(up_x, up_y, up_z); } struct Frame { uint32 width; uint32 height; int32 x; int32 y; Camera* cam; }; struct Polycube { uint32 entityHandle; Space repr; Vector4 color; }; struct RenderObjects_Rectangle { uint32 vao; list> p0; uint32 p0BufferId; list> p1; uint32 p1BufferId; list> color; uint32 colorBufferId; uint64 count; }; struct Input { struct { bool escape; bool enter; bool space; bool lshift; bool x; bool y; bool z; } keyboard; struct { union { struct { real32 x; real32 y; }; Vector2 point; }; bool btnLeft; bool btnRight; bool btnMiddle; } mouse; }; struct Renderer { Scene *scene; RenderObjects_Rectangle rects; }; struct PolycubeInput { Space repr; Vector4 color; }; struct SomaState { bool wireframe; bool polycubeDirty; uint32 currentPolycube; uint32 lastPolycubeVisible; uint32 light; list polycubes; Camera* camera; glm::vec3 rotAxisX; glm::vec3 rotAxisY; list polycubeInput; }; struct Soma { Scene *scene; Renderer *renderer; SomaState state; Input currInput; Input prevInput; struct { GLFWwindow *handle; uint32 width; uint32 height; } window; }; void showEntity(Scene *scene, uint32 entityHandle) { SceneGraphNode *node = getSceneGraphNodeForEntity(scene, entityHandle); for (uint32 &child : node->children) { SceneGraphNode *subNode = getSceneGraphNode(scene, child); if (subNode->entityHandle) { getEntity(scene, subNode->entityHandle)->flags |= EntityFlags_Visible; } } } void hideEntity(Scene *scene, uint32 entityHandle) { SceneGraphNode *node = getSceneGraphNodeForEntity(scene, entityHandle); for (uint32 &child : node->children) { SceneGraphNode *subNode = getSceneGraphNode(scene, child); if (subNode->entityHandle) { getEntity(scene, subNode->entityHandle)->flags &= ~EntityFlags_Visible; } } } glm::vec3 centreFromPolycube(Scene *scene, Polycube *p) { glm::vec3 centre = glm::vec3(0.0f); for (uint32 &child : getSceneGraphNode(scene, p->entityHandle)->children) { centre += getSceneGraphNode(scene, child)->translation; } centre /= getSceneGraphNodeForEntity(scene, p->entityHandle)->children.size(); return centre; } Frame createFrame(Arena *arena, uint32 width, uint32 height, uint32 x, uint32 y) { Frame result = {0}; result.width = width; result.height = height; result.x = x; result.y = y; result.cam = createCamera(arena, (real32)result.width / (real32)result.height); return result; } void framebufferSizeCallback(GLFWwindow *window, int width, int height) { glViewport(0, 0, width, height); } GLFWwindow *initWindowAndGL(uint32 windowWidth, uint32 windowHeight) { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); GLFWwindow *window = glfwCreateWindow(windowWidth, windowHeight, "Somaesque", NULL, NULL); if (window == NULL) { print("Failed to create GLFW window\n"); glfwTerminate(); return NULL; } glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { print("Failed to initilaize GLAD\n"); return NULL; } glViewport(0, 0, 800, 600); glfwSetFramebufferSizeCallback(window, framebufferSizeCallback); glfwSetInputMode(window, GLFW_CURSOR | GLFW_RAW_MOUSE_MOTION, GLFW_CURSOR_NORMAL); glEnable(GL_DEPTH_TEST); return window; } void updateViewportFromFrame(uint32 windowWidth, uint32 windowHeight, Frame* frame) { glViewport(frame->x, windowHeight - frame->y - frame->height, frame->width, frame->height); } struct UI_Context { Renderer *renderer; Input *prevInput; Input *input; bool cursorIsPointer; }; struct UI_Rect { real32 x; real32 y; real32 width; real32 height; real32 borderRadius; Vector4 color; }; Mesh cubeMesh = {0}; Texture wallTex = {0}; Shader solidColorShader; Shader phongShader; inline bool glfwMouse(GLFWwindow *window, int mouseBtnCode) { switch (glfwGetMouseButton(window, mouseBtnCode)) { case GLFW_RELEASE: return false; case GLFW_PRESS: return true; default: return false; } } inline bool glfwKey(GLFWwindow *window, int keyCode) { switch (glfwGetKey(window, keyCode)) { case GLFW_RELEASE: return false; case GLFW_PRESS: return true; default: return false; } } Input getCurrentInput(GLFWwindow *window) { Input input = {0}; input.keyboard.escape = glfwKey(window, GLFW_KEY_ESCAPE); input.keyboard.enter = glfwKey(window, GLFW_KEY_ENTER); input.keyboard.space = glfwKey(window, GLFW_KEY_SPACE); input.keyboard.lshift = glfwKey(window, GLFW_KEY_LEFT_SHIFT); input.keyboard.x = glfwKey(window, GLFW_KEY_X); input.keyboard.y = glfwKey(window, GLFW_KEY_Y); input.keyboard.z = glfwKey(window, GLFW_KEY_Z); input.mouse.btnLeft = glfwMouse(window, GLFW_MOUSE_BUTTON_LEFT); input.mouse.btnRight = glfwMouse(window, GLFW_MOUSE_BUTTON_RIGHT); input.mouse.btnMiddle = glfwMouse(window, GLFW_MOUSE_BUTTON_MIDDLE); real64 mouseX; real64 mouseY; glfwGetCursorPos(window, &mouseX, &mouseY); input.mouse.point = vec2((real32)mouseX, (real32)mouseY); return input; } void processInput(Soma *soma) { Input *input = &soma->currInput; Input *prevInput = &soma->prevInput; if (input->keyboard.escape) { glfwSetWindowShouldClose(soma->window.handle, true); } if (input->keyboard.space && !prevInput->keyboard.space) { glPolygonMode(GL_FRONT_AND_BACK, !soma->state.wireframe ? GL_LINE : GL_FILL); soma->state.wireframe = !soma->state.wireframe; } SceneGraphNode *node = getSceneGraphNode(soma->scene, getEntity(soma->scene, soma->state.light)->graphNodeHandle); int shiftMultiplier = input->keyboard.lshift ? -1 : 1; node->translation.x += 1.0 * input->keyboard.x * shiftMultiplier; node->translation.y += 1.0 * input->keyboard.y * shiftMultiplier; node->translation.z += 1.0 * input->keyboard.z * shiftMultiplier; if (input->keyboard.enter && !prevInput->keyboard.enter) { if (soma->state.currentPolycube == 6) { soma->state.currentPolycube = 0; } else { soma->state.currentPolycube += 1; } } bool dragScene = false; if (input->mouse.btnLeft) { Polycube *current_polycube = &soma->state.polycubes.data[soma->state.currentPolycube]; SceneGraphNode *polycubeGraphNode = getSceneGraphNodeForEntity(soma->scene, current_polycube->entityHandle); real64 deltaX = (input->mouse.x - prevInput->mouse.x) * 0.005; if (deltaX > 0.00000001 || deltaX < -0.00000001) { polycubeGraphNode->rotation *= glm::angleAxis((real32)deltaX, soma->state.rotAxisY); } real64 deltaY = (input->mouse.y - prevInput->mouse.y) * 0.005; if (deltaY > 0.00000001 || deltaY < -0.00000001) { polycubeGraphNode->rotation = glm::angleAxis(-(real32)deltaY, soma->state.rotAxisX) * polycubeGraphNode->rotation; } } } Polycube createPolycubeFromRepr(Soma *soma, Space *repr, Vector4 color) { uint32 polycubeMainEntityHandle = createEntity(soma->scene); Entity *polycubeMainEntity = getEntity(soma->scene, polycubeMainEntityHandle); for (int x = 0; x < repr->dim_x; x++) { for (int y = 0; y < repr->dim_y; y++) { for (int z = 0; z < repr->dim_z; z++) { if (filledAt(repr, x, y, z)) { uint32 segmentEntityHandle = createEntity(soma->scene); Entity *polycubeSegment = getEntity(soma->scene, segmentEntityHandle); polycubeSegment->mesh = &cubeMesh; polycubeSegment->tex = &wallTex; SceneGraphNode *graphNode = getSceneGraphNode(soma->scene, polycubeSegment->graphNodeHandle); graphNode->translation = glm::vec3( -((repr->dim_z - 1)/2.0f) + z, ((repr->dim_x - 1)/2.0f) - x, -((repr->dim_y - 1)/2.0f) + y ); sceneNodeAddEntity(soma->scene, polycubeMainEntity->graphNodeHandle, segmentEntityHandle); } } } } Polycube result = {}; result.entityHandle = polycubeMainEntityHandle; result.color = color; result.repr = *repr; return result; } RenderObjects_Rectangle createRectangleObjects(Arena *arena, size_t count) { RenderObjects_Rectangle result = {0}; result.count = count; result.p0 = PushFullList(arena, Vector2, count); result.p1 = PushFullList(arena, Vector2, count); result.color = PushFullList(arena, Vector4, count); glGenVertexArrays(1, &result.vao); uint32 p0Buffer; uint32 p1Buffer; uint32 colorBuffer; glGenBuffers(1, &result.p0BufferId); glGenBuffers(1, &result.p1BufferId); glGenBuffers(1, &result.colorBufferId); glBindVertexArray(result.vao); glBindBuffer(GL_ARRAY_BUFFER, result.p0BufferId); glBufferData(GL_ARRAY_BUFFER, result.p0.length * sizeof(Vector2), 0, GL_DYNAMIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), (void*)0); glVertexAttribDivisor(0, 1); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, result.p1BufferId); glBufferData(GL_ARRAY_BUFFER, result.p1.length * sizeof(Vector2), 0, GL_DYNAMIC_DRAW); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), (void*)0); glVertexAttribDivisor(1, 1); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, result.colorBufferId); glBufferData(GL_ARRAY_BUFFER, result.color.length * sizeof(Vector4), 0, GL_DYNAMIC_DRAW); glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vector4), (void*)0); glVertexAttribDivisor(2, 1); glEnableVertexAttribArray(2); return result; } void reinitRectangleObjectBuffers(Renderer *r) { glBindVertexArray(r->rects.vao); glBindBuffer(GL_ARRAY_BUFFER, r->rects.p0BufferId); glBufferSubData(GL_ARRAY_BUFFER, 0, r->rects.p0.head * sizeof(Vector2), r->rects.p0.data); glBindBuffer(GL_ARRAY_BUFFER, r->rects.p1BufferId); glBufferSubData(GL_ARRAY_BUFFER, 0, r->rects.p1.head * sizeof(Vector2), r->rects.p1.data); glBindBuffer(GL_ARRAY_BUFFER, r->rects.colorBufferId); glBufferSubData(GL_ARRAY_BUFFER, 0, r->rects.color.head * sizeof(Vector4), r->rects.color.data); } Renderer createRenderer(Arena *arena, Scene *scene) { Renderer result = {0}; result.scene = scene; result.scene->sceneRoot = createSceneGraphNode(scene); result.rects = createRectangleObjects(arena, 100); initGraphNode(getSceneGraphNode(scene, scene->sceneRoot)); return result; } void renderBegin(Renderer *r) { r->rects.p0.head = 0; r->rects.p1.head = 0; r->rects.color.head = 0; } void renderEnd(Soma *soma, Renderer *renderer) { // 3D Entities glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glUseProgram(phongShader.progId); setUniformMat4fv(&phongShader, "projection", &soma->state.camera->proj); setUniformMat4fv(&phongShader, "view", &soma->state.camera->view); SceneGraphNode *lightGraphNode = getSceneGraphNode(soma->scene, getEntity(soma->scene, soma->state.light)->graphNodeHandle); setUniform3fv(&phongShader, "light_pos", &lightGraphNode->translation); setUniform3fv(&phongShader, "camera", &soma->state.camera->pos); Polycube *currentPolycube = &soma->state.polycubes.data[soma->state.currentPolycube]; glBindVertexArray(cubeMesh.vao); setUniform4fv(&phongShader, "solid_color", ¤tPolycube->color); int model_uniform = getUniformLocation(&phongShader, "model"); for (Entity &entity : renderer->scene->entities) { if (entity.flags & EntityFlags_Render && entity.flags & EntityFlags_Visible) { setUniformMat4fv(model_uniform, &getSceneGraphNode(renderer->scene, entity.graphNodeHandle)->world); glBindTexture(GL_TEXTURE_2D, entity.tex->tex_id); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)entity.mesh->num_indices); entity.flags &= ~EntityFlags_Render; } } // 2D overlay glUseProgram(solidColorShader.progId); reinitRectangleObjectBuffers(renderer); glm::mat4 ortho = glm::ortho(0.0, 800.0, 600.0, 0.0, -1.0, 1.0); setUniformMat4fv(&solidColorShader, "projection", &ortho); glBindVertexArray(renderer->rects.vao); glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, renderer->rects.p0.head); } void rendererPlaceRectangle(Renderer *r, real32 x, real32 y, real32 width, real32 height, Vector4 color) { appendList(&r->rects.p0, vec2(x, y)); appendList(&r->rects.p1, vec2(x + width, y + height)); appendList(&r->rects.color, color); } inline bool pointInRect(Vector2 point, UI_Rect rect) { return point.x > rect.x && point.y > rect.y && point.x < (rect.x + rect.width) && point.y < (rect.y + rect.height); } bool ui_checkboxRect(UI_Context *ui, bool *value, UI_Rect rect) { bool clicked = false; if (pointInRect(ui->input->mouse.point, rect)) { ui->cursorIsPointer = true; if (ui->prevInput->mouse.btnLeft && !ui->input->mouse.btnLeft) { *value = !*value; clicked = true; } } rendererPlaceRectangle(ui->renderer, rect.x, rect.y, rect.width, rect.height, *value ? rect.color : vec4(1, 1, 1, 1)); return clicked; } void uiPass(Soma *soma, UI_Context *ui, Renderer *renderer) { PolycubeInput *currentPolycube = &soma->state.polycubeInput.data[soma->state.currentPolycube]; real32 boxSize = 30; real32 padding = 20; real32 paddingBetween = 5; real32 currY = padding; for (int x = 0; x < currentPolycube->repr.dim_x; x++) { for (int y = 0; y < currentPolycube->repr.dim_y; y++) { real32 currX = padding; for (int z = 0; z < currentPolycube->repr.dim_z; z++) { bool cellActive = filledAt(¤tPolycube->repr, x, y, z); UI_Rect rect = { currX, currY, boxSize, boxSize, 0, currentPolycube->color, }; if (ui_checkboxRect(ui, &cellActive, rect)) { soma->state.polycubeDirty = true; spaceSet(¤tPolycube->repr, cellActive, x, y, z); } currX += paddingBetween + boxSize; } currY += paddingBetween + boxSize; } currY += padding; } } int main_cmd() { interactive_cmd_line_solve_soma(); return 0; } int mainGfx() { Arena *arena = arenaAlloc(Megabytes(128)); Scene mainScene = createScene(); Soma soma = {}; soma.window.width = 800; soma.window.height = 600; soma.window.handle = initWindowAndGL(soma.window.width, soma.window.height); soma.scene = &mainScene; Renderer renderer = createRenderer(arena, &mainScene); soma.renderer = &renderer; if (!soma.window.handle) { return -1; } Frame mainFrame = createFrame(arena, soma.window.width, soma.window.height, 0, 0); UI_Context ui = {}; GLFWcursor *pointerCursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR); GLFWcursor *arrowCursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); soma.state.currentPolycube = 0; soma.state.lastPolycubeVisible = 6; soma.state.polycubeInput = PushListZero(arena, PolycubeInput, 64); soma.state.polycubes = PushListZero(arena, Polycube, 64); soma.state.light = createEntity(&mainScene); soma.state.camera = mainFrame.cam; SceneGraphNode *light = getSceneGraphNode(&mainScene, getEntity(&mainScene, soma.state.light)->graphNodeHandle); light->translation = glm::vec3(4.0f, 6.0f, 24.0f); /* Shader solid_texture_shader = createShader( "./assets/shaders/2d.vertex.glsl"_s, "./assets/shaders/2d-tex.fragment.glsl"_s); */ solidColorShader = createShader( "./assets/shaders/2d-solid.vertex.glsl"_s, "./assets/shaders/2d-solid.fragment.glsl"_s); phongShader = createShader( "./assets/shaders/phong-solid.vertex.glsl"_s, "./assets/shaders/phong-solid.fragment.glsl"_s); cubeMesh = createMesh("./assets/models/cube.obj"); wallTex = createTexture("./assets/textures/brick-wall.jpg"); for (int i = 0; i < ArrayCount(STD_SOMA); i++) { Space voxelSpace = { STD_SOMA[i], 3, 3, 3 }; Vector4 color = colorFromIndex(i); appendList(&soma.state.polycubeInput, PolycubeInput{ voxelSpace, color }); cullEmptySpace(&voxelSpace); Polycube polycube = createPolycubeFromRepr(&soma, &voxelSpace, color); polycube.color = color; appendList(&soma.state.polycubes, polycube); sceneNodeAddEntity(renderer.scene, renderer.scene->sceneRoot, polycube.entityHandle); } soma.state.camera->pos = glm::vec3(0.0f, 0.0f, 8.0f); cameraLookAt(soma.state.camera, 0.0f, 0.0f, 0.0f); SceneGraphNode *reference_polycube_gn = getSceneGraphNodeForEntity(soma.scene, soma.state.polycubes.data[0].entityHandle); soma.state.rotAxisY = glm::normalize(glm::vec4(0, 1, 0, 0) * glm::inverse(reference_polycube_gn->world)); glm::vec3 eyes = glm::normalize(soma.state.camera->pos - reference_polycube_gn->translation); soma.state.rotAxisX = glm::normalize(glm::cross(eyes, soma.state.rotAxisY)); for (int i = 0; i < ArrayCount(STD_SOMA); i++) { auto gn = getSceneGraphNodeForEntity(soma.scene, soma.state.polycubes.data[i].entityHandle); gn->rotation *= glm::angleAxis(PI / 4, glm::vec3(1, 0, 0)); gn->rotation *= glm::angleAxis(PI / 4, glm::vec3(0, 1, 0)); } real64 lastFrame = glfwGetTime(); real64 timeDelta = 1.0f/60.0f; while (!glfwWindowShouldClose(soma.window.handle)) { real64 currTime = glfwGetTime(); timeDelta = currTime - lastFrame; lastFrame = currTime; //print("%.7f\n", timeDelta); glfwPollEvents(); soma.currInput = getCurrentInput(soma.window.handle); processInput(&soma); if (soma.state.lastPolycubeVisible != soma.state.currentPolycube) { hideEntity(soma.scene, soma.state.polycubes.data[soma.state.lastPolycubeVisible].entityHandle); showEntity(soma.scene, soma.state.polycubes.data[soma.state.currentPolycube].entityHandle); soma.state.lastPolycubeVisible = soma.state.currentPolycube; } if (soma.state.polycubeDirty) { PolycubeInput *pinput = &soma.state.polycubeInput.data[soma.state.currentPolycube]; removeEntity(soma.scene, soma.state.polycubes.data[soma.state.currentPolycube].entityHandle); Space culledRepr = pinput->repr; cullEmptySpace(&culledRepr); Polycube polycube = createPolycubeFromRepr(&soma, &culledRepr, pinput->color); soma.state.polycubes.data[soma.state.currentPolycube] = polycube; sceneNodeAddEntity(soma.scene, soma.scene->sceneRoot, polycube.entityHandle); soma.state.polycubeDirty = false; } updateViewportFromFrame(soma.window.width, soma.window.height, &mainFrame); recalcScene(soma.scene); renderBegin(&renderer); ui.cursorIsPointer = false; ui.prevInput = &soma.prevInput; ui.input = &soma.currInput; ui.renderer = &renderer; uiPass(&soma, &ui, &renderer); if (ui.cursorIsPointer) { glfwSetCursor(soma.window.handle, pointerCursor); } else { glfwSetCursor(soma.window.handle, arrowCursor); } renderEnd(&soma, &renderer); glfwSwapBuffers(soma.window.handle); soma.prevInput = soma.currInput; } glfwTerminate(); return 0; } int main() { initialiseCore(); return mainGfx(); }