From 66547b0f68a63dfe464f0c4bf7254b4afcf37c0c Mon Sep 17 00:00:00 2001 From: Daniel Ledda Date: Mon, 11 May 2026 23:30:35 +0200 Subject: [PATCH] render 3d to tex --- assets/shaders/2d-solid.fragment.glsl | 6 +- assets/shaders/2d-solid.vertex.glsl | 9 + assets/shaders/phong-solid.fragment.glsl | 4 +- assets/shaders/text.fragment.glsl | 2 +- src/gfx/Texture.c | 1 + src/main.c | 237 ++++++++++++++++++----- src/render.c | 27 +-- src/render.h | 2 + src/ui.c | 67 +++++-- src/ui.h | 16 +- 10 files changed, 279 insertions(+), 92 deletions(-) diff --git a/assets/shaders/2d-solid.fragment.glsl b/assets/shaders/2d-solid.fragment.glsl index 41dc79b..8b92f6b 100644 --- a/assets/shaders/2d-solid.fragment.glsl +++ b/assets/shaders/2d-solid.fragment.glsl @@ -9,6 +9,9 @@ in float frag_softness; in float frag_border_radius; in float frag_border_thickness; in vec4 frag_border_color; +in vec2 uv; + +uniform sampler2D rect_tex; float roundedRectSDF(vec2 sample_pos, vec2 rect_center, vec2 rect_half_size, float r) { vec2 d2 = (abs(rect_center - sample_pos) - rect_half_size + vec2(r, r)); @@ -52,6 +55,7 @@ void main() { float sdf_factor = 1 - smoothstep(0, 2*frag_softness, dist); + vec4 out_color = frag_color * texture(rect_tex, uv); pixel_color = frag_border_color * sample * sdf_factor * border_factor - + frag_color * sample * sdf_factor; + + out_color * sample * sdf_factor; }; diff --git a/assets/shaders/2d-solid.vertex.glsl b/assets/shaders/2d-solid.vertex.glsl index 7facc67..7b81b7a 100644 --- a/assets/shaders/2d-solid.vertex.glsl +++ b/assets/shaders/2d-solid.vertex.glsl @@ -17,6 +17,14 @@ out float frag_softness; out float frag_border_radius; out float frag_border_thickness; out vec4 frag_border_color; +out vec2 uv; + +const vec2 rectangle_uv[4] = vec2[]( + vec2(0, 1), + vec2(0, 0), + vec2(1, 1), + vec2(1, 0) +); const vec2 rectangle_vertices[4] = vec2[]( vec2(-1, -1), @@ -44,4 +52,5 @@ void main() { frag_border_thickness = border_thickness; frag_border_color = border_color; frag_softness = edge_softness; + uv = rectangle_uv[gl_VertexID]; } diff --git a/assets/shaders/phong-solid.fragment.glsl b/assets/shaders/phong-solid.fragment.glsl index 4d45485..a84bdb6 100644 --- a/assets/shaders/phong-solid.fragment.glsl +++ b/assets/shaders/phong-solid.fragment.glsl @@ -1,5 +1,5 @@ #version 330 core -out vec4 frag_color; +layout(location = 0) out vec4 frag_color; uniform vec3 light_pos; uniform vec4 solid_color; @@ -7,7 +7,7 @@ uniform vec3 camera; in vec3 normal; in vec3 frag_position; - + void main() { vec4 light_color = vec4(1, 1, 1, 1); vec3 normal_norm = normalize(normal); diff --git a/assets/shaders/text.fragment.glsl b/assets/shaders/text.fragment.glsl index 7bb77e9..9ab950f 100644 --- a/assets/shaders/text.fragment.glsl +++ b/assets/shaders/text.fragment.glsl @@ -7,5 +7,5 @@ in vec2 frag_uv_position; uniform sampler2D glyph_atlas; void main() { - pixel_color = vec4(1,1,1,texture(glyph_atlas, frag_uv_position).r); + pixel_color = vec4(frag_color.xyz,texture(glyph_atlas, frag_uv_position).r); }; diff --git a/src/gfx/Texture.c b/src/gfx/Texture.c index 78f266b..7d861ac 100644 --- a/src/gfx/Texture.c +++ b/src/gfx/Texture.c @@ -99,6 +99,7 @@ Font createFont(Arena *arena, string ttfLocation, real32 lineHeight) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glGenerateMipmap(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); glGenBuffers(1, &result.glyphTableBufId); glBindBuffer(GL_TEXTURE_BUFFER, result.glyphTableBufId); diff --git a/src/main.c b/src/main.c index ceea7d6..dc49b11 100644 --- a/src/main.c +++ b/src/main.c @@ -78,6 +78,7 @@ struct Soma { struct { GLFWcursor *pointer; GLFWcursor *arrow; + GLFWcursor *forbidden; } cursors; } window; @@ -110,6 +111,7 @@ void tryScheduleSolve(Soma *soma) { for (EachEl(soma->polycubeInput, PolycubeInput, polycubeInput)) { ListAppend(mappedInputs, polycubeInput->repr.space); } + ClearList(soma->solveTaskCtx.solutions); soma->solveTaskCtx.input = mappedInputs; soma->solveTaskCtx.taskStatus = SolveTaskStatus_Solving; soma->solveTaskCtx.dims = PushFullList(soma->solveTaskCtx.arena, IntList, 3); @@ -399,56 +401,102 @@ static void ui_Soma(UI_Context *ui, Soma *soma) { UI_Rect btnHoverRectBlue = btnRectBlue; btnHoverRectBlue.color = lightblueHighlight; - UI(.flags=UI_Flag_HeightGrow | UI_Flag_WidthGrow) { - UI_SetTxtAttr(.lineHeight=20, .fontSize=20); - UI(.padding={.left=20, .right=20}, .color=darkgrey, .flags=UI_Flag_HeightGrow | UI_Flag_Vertical) { - UI(.padding=UI_PadUniform(10)) UI_Txt(s("Somaesque"), .fontSize=26); - ui_Interaction(ui, soma); - } - UI(.flags=UI_Flag_Vertical | UI_Flag_HeightGrow | UI_Flag_WidthGrow) { - UI(.padding=UI_PadUniform(10), .color=darkgrey, .flags=UI_Flag_WidthGrow) { - UI(.childGap=10) { - if (ui_ButtonWithHover(ui, btnRect, btnHoverRect, UI_TxtAttr(s("Previous")))) { - advanceDisplayReverse(soma); - } + int32 solveBtnId = 0; - if (ui_ButtonWithHover(ui, btnRect, btnHoverRect, UI_TxtAttr(s("Next")))) { - advanceDisplay(soma); + int32 totalUnitsPlaced = 0; + for (EachIn(soma->polycubeInput, i)) { + totalUnitsPlaced += size(soma->polycubeInput.data[i].repr.space); + } + int32 requiredUnits = soma->puzzleDims[0] * soma->puzzleDims[1] * soma->puzzleDims[2]; + + bool canSolve = requiredUnits == totalUnitsPlaced; + + UI(.flags=UI_Flag_WidthGrow | UI_Flag_HeightGrow) { + UI_SetTxtAttr(.lineHeight=20, .fontSize=20, .color={1,1,1,1}); + UI(.flags=UI_Flag_HeightGrow | UI_Flag_WidthGrow) { + UI(.padding={.left=20, .right=20}, .color=darkgrey, .flags=UI_Flag_HeightGrow | UI_Flag_Vertical) { + UI(.padding=UI_PadUniform(10)) UI_Txt(s("Somaesque"), .fontSize=26); + ui_Interaction(ui, soma); + } + UI(.flags=UI_Flag_Vertical | UI_Flag_HeightGrow | UI_Flag_WidthGrow) { + UI(.padding=UI_PadUniform(10), .color=darkgrey, .flags=UI_Flag_WidthGrow) { + UI(.childGap=10) { + if (ui_ButtonWithHover(ui, btnRect, btnHoverRect, UI_TxtAttr(s("Previous")))) { + advanceDisplayReverse(soma); + } + + if (ui_ButtonWithHover(ui, btnRect, btnHoverRect, UI_TxtAttr(s("Next")))) { + advanceDisplay(soma); + } + } + UI(.flags=UI_Flag_WidthGrow); + UI(.childGap=10) { + UI_Rect *rect = soma->state.displayingSolutions ? &btnRect : &btnRectBlue; + UI_Rect *hoverRect = soma->state.displayingSolutions ? &btnHoverRect : &btnHoverRectBlue; + if (ui_ButtonWithHover(ui, *rect, *hoverRect, UI_TxtAttr(s("Design")))) { + soma->state.displayingSolutions = false; + } + + solveBtnId = UI_NextID(); + + rect = soma->state.displayingSolutions ? &btnRectBlue : &btnRect; + hoverRect = soma->state.displayingSolutions ? &btnHoverRectBlue : &btnHoverRect; + RLVector4 txtColor = {1,1,1,1}; + + if (!canSolve) { + hoverRect = rect; + txtColor = lightgrey; + } + if (ui_ButtonWithHover(ui, *rect, *hoverRect, UI_TxtAttr(s("Solve"), .color=txtColor))) { + if (canSolve) { + if (!soma->state.displayingSolutions) { + tryScheduleSolve(soma); + } else { + soma->state.displayingSolutions = false; + } + } + } + if (ui->hoveredNode == solveBtnId && !canSolve) { + ui->cursorType = UI_Cursor_Forbidden; + } } } - UI(.flags=UI_Flag_WidthGrow); - UI(.childGap=10) { - UI_Rect *rect = soma->state.displayingSolutions ? &btnRect : &btnRectBlue; - UI_Rect *hoverRect = soma->state.displayingSolutions ? &btnHoverRect : &btnHoverRectBlue; - if (ui_ButtonWithHover(ui, *rect, *hoverRect, UI_TxtAttr(s("Design")))) { - soma->state.displayingSolutions = false; - } - rect = soma->state.displayingSolutions ? &btnRectBlue : &btnRect; - hoverRect = soma->state.displayingSolutions ? &btnHoverRectBlue : &btnHoverRect; - if (ui_ButtonWithHover(ui, *rect, *hoverRect, UI_TxtAttr(s("Solve")))) { - if (!soma->state.displayingSolutions) { - tryScheduleSolve(soma); - } else { - soma->state.displayingSolutions = false; + UI(.flags=UI_Flag_Vertical | UI_Flag_WidthGrow | UI_Flag_HeightGrow | UI_Flag_3DScene) { + UI_SetTxtAttr(.fontSize=26, .alignment=UI_TxtAlign_Right); + if (soma->state.displayingSolutions && soma->solutions.length == 0) { + UI(.flags=UI_Flag_WidthGrow | UI_Flag_HeightGrow | UI_Flag_Vertical) { + UI(.flags=UI_Flag_HeightGrow); + UI(.flags=UI_Flag_WidthGrow) { + UI_Txt(s("No solutions!"), .alignment=UI_TxtAlign_Center, .fontSize=50); + } + UI(.flags=UI_Flag_HeightGrow); + } + } else { + UI(.flags=UI_Flag_HeightGrow); + UI(.padding=UI_PadUniform(10), .flags=UI_Flag_WidthGrow) { + if (soma->solveTaskCtx.taskStatus == SolveTaskStatus_Solving) { + UI_Txt(s("Solving...")); + } else { + bool soln = soma->state.displayingSolutions; + UI_Txt( + strPrintf(ui->arena, "%S #%d (%d total)", + soln ? s("Solution") : s("Polycube"), + (soln ? soma->state.displayedSolution : soma->state.displayedPolycube) + 1, + soln ? soma->solutions.length : soma->polycubeInput.length), + ); + } } } } } - UI(.flags=UI_Flag_Vertical | UI_Flag_WidthGrow | UI_Flag_HeightGrow | UI_Flag_3DScene) { - UI_SetTxtAttr(.fontSize=26, .alignment=UI_TxtAlign_Right); - UI(.flags=UI_Flag_HeightGrow); - UI(.padding=UI_PadUniform(10), .flags=UI_Flag_WidthGrow) { - if (soma->solveTaskCtx.taskStatus == SolveTaskStatus_Solving) { - UI_Txt(s("Solving...")); - } else { - bool soln = soma->state.displayingSolutions; - UI_Txt( - strPrintf(ui->arena, "%S #%d (%d total)", - soln ? s("Solution") : s("Polycube"), - (soln ? soma->state.displayedSolution : soma->state.displayedPolycube) + 1, - soln ? soma->solutions.length : soma->polycubeInput.length), - ); - } + } + + // Tooltip + if (ui->hoveredNode == solveBtnId && requiredUnits != totalUnitsPlaced) { + string tooltipStr = strPrintf(ui->arena, "Required units: %d - Placed: %d", requiredUnits, totalUnitsPlaced); + UI(.flags=UI_Flag_Pos_Absolute, .color={0,0,0,1}, .xOffset=ui->input->mouse.x - tooltipStr.length*10, .yOffset=ui->input->mouse.y, .width=tooltipStr.length * 10, .height=22, .padding=UI_PadUniform(1)) { + UI(.flags=UI_Flag_HeightGrow | UI_Flag_WidthGrow, .color={1,1,1,1}) { + UI_Txt(tooltipStr, .alignment=UI_TxtAlign_Center, .color={0,0,0,1}); } } } @@ -458,7 +506,7 @@ static void ui_Soma(UI_Context *ui, Soma *soma) { static void updatePolycubeDisplay(Soma *soma) { Scene *s = soma->scene; - if (soma->state.displayingSolutions && soma->state.displayedSolution != soma->prevState.displayedSolution) { + if (soma->state.displayingSolutions && soma->solutions.length > 0 && soma->state.displayedSolution != soma->prevState.displayedSolution) { removeSceneGraphNode(s, soma->solutionNode); if (soma->state.displayedSolution >= 0 && soma->state.displayedSolution < soma->solutions.length) { SomaSolution soln = soma->solutions.data[soma->state.displayedSolution]; @@ -476,7 +524,9 @@ static void updatePolycubeDisplay(Soma *soma) { } if (soma->state.displayingSolutions) { - show(s, soma->solutionNode); + if (soma->solutions.length > 0) { + show(s, soma->solutionNode); + } if (soma->prevState.displayedPolycube >= 0 && soma->prevState.displayedPolycube < soma->polycubes.length) { hide(s, soma->polycubes.data[soma->prevState.displayedPolycube]); } @@ -541,9 +591,9 @@ int mainGfx() { return 1; } -glEnable(GL_DEBUG_OUTPUT); -glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); -glDebugMessageCallback(glDebugCallback, NULL); + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(glDebugCallback, NULL); Scene mainScene = createScene(arena); Camera cam = createCamera(winWidth, winHeight); @@ -574,6 +624,7 @@ glDebugMessageCallback(glDebugCallback, NULL); .cursors = { .pointer = glfwCreateStandardCursor(GLFW_HAND_CURSOR), .arrow = glfwCreateStandardCursor(GLFW_ARROW_CURSOR), + .forbidden = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR), }, }, .scene = &mainScene, @@ -636,6 +687,76 @@ glDebugMessageCallback(glDebugCallback, NULL); Shader textShader = createShader(s("./assets/shaders/text.vertex.glsl"), s("./assets/shaders/text.fragment.glsl")); renderer.textShader = &textShader; + // --- Offscreen framebuffer setup + // GL + int32 offscreenWidth = 256; + int32 offscreenHeight = 256; + uint32 offscreenFrameBufHandle; + glGenFramebuffers(1, &offscreenFrameBufHandle); + glBindFramebuffer(GL_FRAMEBUFFER, offscreenFrameBufHandle); + uint32 offscreenTexHandle; + glGenTextures(1, &offscreenTexHandle); + glBindTexture(GL_TEXTURE_2D, offscreenTexHandle); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, offscreenWidth, offscreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + uint32 depthRenderBufferHandle; + glGenRenderbuffers(1, &depthRenderBufferHandle); + glBindRenderbuffer(GL_RENDERBUFFER, depthRenderBufferHandle); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, offscreenWidth, offscreenHeight); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderBufferHandle); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, offscreenTexHandle, 0); + uint32 DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}; + glDrawBuffers(1, DrawBuffers); + // Camera and scene + Scene offscreenScene = createScene(arena); + Camera offscreenCam = createCamera(offscreenWidth, offscreenHeight); + int32 offscreenLight = createSceneGraphNode(&offscreenScene); + offscreenCam.pos = (RLVector3){0.0f, 0.0f, 8.0f}; + cameraLookAt(&offscreenCam, 0.0f, 0.0f, 0.0f); + getSceneGraphNode(&offscreenScene, offscreenLight)->translation = (RLVector3){4.0f, 6.0f, 24.0f}; + VoxelSpace offscreenPolycube = (VoxelSpace){ + .space=stdSoma.data[0], + .dim_x=soma.puzzleDims[0], + .dim_y=soma.puzzleDims[1], + .dim_z=soma.puzzleDims[2], + }; + cullEmptySpace(&offscreenPolycube); + uint32 offscreenPolycubeHandle = createPolycubeFromRepr(&offscreenScene, &offscreenPolycube, (RLVector4){1,1,1,1}); + sceneNodeAddNode(&offscreenScene, offscreenScene.sceneRoot, offscreenPolycubeHandle); + renderer.testTexId = offscreenTexHandle; + show(&offscreenScene, offscreenPolycubeHandle); + recalcScene(&offscreenScene); + // 3D overlay + { + glBindFramebuffer(GL_FRAMEBUFFER, offscreenFrameBufHandle); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + glViewport(0, 0, offscreenWidth, offscreenHeight); + cameraSetAspect(&offscreenCam, offscreenWidth, offscreenHeight); + + glUseProgram(phongShader.progId); + setUniformMat4fv(&phongShader, "projection", &offscreenCam.proj); + setUniformMat4fv(&phongShader, "view", &offscreenCam.view); + + SceneGraphNode *lightGraphNode = getSceneGraphNode(&offscreenScene, offscreenLight); + setUniform3fv(&phongShader, "light_pos", &lightGraphNode->translation); + setUniform3fv(&phongShader, "camera", &offscreenCam.pos); + + glBindVertexArray(cubeMesh.vao); + + int model_uniform = getUniformLocation(&phongShader, "model"); + int solid_color_uniform = getUniformLocation(&phongShader, "solid_color"); + for (EachEl(offscreenScene.entities, Entity, entity)) { + if (entity->flags & EntityFlags_Render && entity->flags & EntityFlags_Visible) { + setUniform4fvByLoc(solid_color_uniform, &entity->color); + setUniformMat4fvByLoc(model_uniform, &getSceneGraphNode(&offscreenScene, entity->graphNodeHandle)->world); + glBindTexture(GL_TEXTURE_2D, entity->tex->tex_id); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)entity->mesh->num_indices); + } + } + }; + // Render loop real64 lastFrame = glfwGetTime(); real64 timeDelta = 1.0f / TARGET_FPS; @@ -657,7 +778,8 @@ glDebugMessageCallback(glDebugCallback, NULL); processInput(&soma); if (soma.solveTaskCtx.taskStatus == SolveTaskStatus_Complete) { - soma.solutions = PushList(solutionsArena, SomaSolutionList, soma.solveTaskCtx.solutions.length); + solutionsArena->head = 0; + soma.solutions = PushListCopy(solutionsArena, SomaSolutionList, soma.solveTaskCtx.solutions); for (EachIn(soma.solveTaskCtx.solutions, i)) { ListAppend(soma.solutions, PushListCopy(solutionsArena, SomaSolution, soma.solveTaskCtx.solutions.data[i])); } @@ -672,10 +794,19 @@ glDebugMessageCallback(glDebugCallback, NULL); ui_Soma(&ui, &soma); } - if (ui.cursorIsPointer) { - glfwSetCursor(soma.window.handle, soma.window.cursors.pointer); - } else { - glfwSetCursor(soma.window.handle, soma.window.cursors.arrow); + switch (ui.cursorType) { + case UI_Cursor_Arrow: + glfwSetCursor(soma.window.handle, soma.window.cursors.arrow); + break; + case UI_Cursor_Pointer: + glfwSetCursor(soma.window.handle, soma.window.cursors.pointer); + break; + case UI_Cursor_Forbidden: + glfwSetCursor(soma.window.handle, soma.window.cursors.forbidden); + break; + default: + glfwSetCursor(soma.window.handle, soma.window.cursors.arrow); + break; } } diff --git a/src/render.c b/src/render.c index 2bce095..93963b0 100644 --- a/src/render.c +++ b/src/render.c @@ -182,11 +182,13 @@ void renderBegin(Renderer *r) { #define CHECK() do{ GLenum e=glGetError(); if(e) printf("GL err 0x%x at %s:%d\n",e,__FILE__,__LINE__); }while(0) void renderEnd(Renderer *r) { - // 3D Scene + glBindFramebuffer(GL_FRAMEBUFFER, 0); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glEnable(GL_DEPTH_TEST); + // --- 3D Scene --- + glViewport(r->sceneX, r->height - r->sceneY - r->sceneHeight, r->sceneWidth, r->sceneHeight); cameraSetAspect(r->camera, r->sceneWidth, r->sceneHeight); @@ -213,9 +215,11 @@ void renderEnd(Renderer *r) { } } - // 2D overlay + // --- UI overlay --- glViewport(0, 0, r->width, r->height); glUseProgram(r->solidShader->progId); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, r->testTexId); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); @@ -223,28 +227,29 @@ void renderEnd(Renderer *r) { Matrix ortho = MatrixOrtho(0.0, r->width, r->height, 0.0, -1.0, 1.0); - // 1. Rects + // - 1. Rects updateRectangleObjectBuffers(r); setUniformMat4fv(r->solidShader, "projection", &ortho); glBindVertexArray(r->rects.vao); glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, r->rects.p0.buf.length); - // 2. Text + // - 2. Text updateCharObjectBuffers(r); glUseProgram(r->textShader->progId); setUniformMat4fv(r->textShader, "projection", &ortho); - glActiveTexture(GL_TEXTURE0); CHECK(); - glBindTexture(GL_TEXTURE_2D, r->activeFont->texId); CHECK(); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, r->activeFont->texId); - glActiveTexture(GL_TEXTURE1); CHECK(); - glBindTexture(GL_TEXTURE_BUFFER, r->activeFont->glyphTableTexId); CHECK(); - setUniform1i(r->textShader, "glyph_table", 1); CHECK(); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_BUFFER, r->activeFont->glyphTableTexId); + setUniform1i(r->textShader, "glyph_table", 1); - glBindVertexArray(r->chars.vao); CHECK(); - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, r->chars.begin.buf.length); CHECK(); + glBindVertexArray(r->chars.vao); + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, r->chars.begin.buf.length); glDisable(GL_BLEND); + glActiveTexture(GL_TEXTURE0); } void rendererPlaceRectangle(Renderer *r, real32 x, real32 y, real32 width, real32 height, RLVector4 color, real32 borderRadius, real32 borderThickness, RLVector4 borderColor) { diff --git a/src/render.h b/src/render.h index 6707341..4723fa7 100644 --- a/src/render.h +++ b/src/render.h @@ -87,6 +87,8 @@ struct Renderer { /** SceneGraphNode handle */ int32 light; Mesh *cubeMesh; + + int32 testTexId; }; Renderer createRenderer(Arena *arena, Scene *scene, Camera *cam, int32 light); diff --git a/src/ui.c b/src/ui.c index 9a290ab..79655f6 100644 --- a/src/ui.c +++ b/src/ui.c @@ -54,7 +54,7 @@ UI_Context ui_initContext(Arena *arena, Renderer *renderer) { .prevRects=prevList, .hotNode=0, .prevHotNode=0, - .cursorIsPointer=false, + .cursorType=UI_Cursor_Arrow, .scene3DHandle=0, .input=NULL, .prevInput=NULL, @@ -72,6 +72,9 @@ void ui_placeText(UI_Context *ui, UI_RectStr strData) { strDataCopy->fontSize = ui->renderer->activeFont->lineHeight; } } + if (strDataCopy->color.x == -1) { + strDataCopy->color = currRect->inheritedTextAttr->color; + } if (strDataCopy->lineHeight == -1) { strDataCopy->lineHeight = currRect->inheritedTextAttr->lineHeight; if (strDataCopy->lineHeight == -1) { @@ -115,11 +118,13 @@ void ui_sizingPass(UI_Context *ui, bool isXAxis, int32 rectHandle) { if (isVertical != isXAxis) { while (childHandle) { child = &ui->rects.data[childHandle]; - if (child->flags & UI_Flag_HeightGrow && !isXAxis || child->flags & UI_Flag_WidthGrow && isXAxis) { - growableChildrenCount++; + if (!(child->flags & UI_Flag_Pos_Absolute)) { + if (child->flags & UI_Flag_HeightGrow && !isXAxis || child->flags & UI_Flag_WidthGrow && isXAxis) { + growableChildrenCount++; + } + real32 childBreadth = isXAxis ? child->resolvedWidth : child->resolvedHeight; + remainingSpace -= childBreadth + rect->childGap; } - real32 childBreadth = isXAxis ? child->resolvedWidth : child->resolvedHeight; - remainingSpace -= childBreadth + rect->childGap; childHandle = child->nextSibling; } } @@ -161,27 +166,43 @@ void ui_calcLayout(UI_Context *ui, bool isXAxis, int32 rectHandle) { bool isVertical = (rect->flags & UI_Flag_Vertical); real32 coord = isXAxis - ? rect->x + rect->xOffset + rect->padding.left - : rect->y + rect->yOffset + rect->padding.top; + ? rect->x + rect->padding.left + : rect->y + rect->padding.top; + + real32 parentCoord = coord; int32 childHandle = rect->firstChild; UI_Rect *child; while (childHandle) { child = &ui->rects.data[childHandle]; - if (isXAxis) { - child->x = child->flags & UI_Flag_Center && isVertical - ? coord + (rect->resolvedWidth - rect->padding.left - rect->padding.right)/2 - child->resolvedWidth/2 - : coord; + if (child->flags & UI_Flag_Pos_Absolute) { + if (isXAxis) { + child->x = parentCoord; + } else { + child->y = parentCoord; + } } else { - child->y = child->flags & UI_Flag_Center && !isVertical - ? coord + (rect->resolvedHeight - rect->padding.top - rect->padding.bottom)/2 - child->resolvedHeight/2 - : coord; + if (isXAxis) { + child->x = child->flags & UI_Flag_Center && isVertical + ? coord + (rect->resolvedWidth - rect->padding.left - rect->padding.right)/2 - child->resolvedWidth/2 + : coord; + } else { + child->y = child->flags & UI_Flag_Center && !isVertical + ? coord + (rect->resolvedHeight - rect->padding.top - rect->padding.bottom)/2 - child->resolvedHeight/2 + : coord; + } + } + + if (isXAxis) { + child->x += child->xOffset; + } else { + child->y += child->yOffset; } ui_calcLayout(ui, isXAxis, childHandle); - if (!isVertical == isXAxis) { + if (!(child->flags & UI_Flag_Pos_Absolute) && !isVertical == isXAxis) { coord += isXAxis ? child->resolvedWidth : child->resolvedHeight; coord += rect->childGap; } @@ -195,9 +216,11 @@ void ui_begin(UI_Context *ui) { arenaFreeFrom(ui->arena, 0); ClearList(ui->prevRects); ListAppendList(ui->prevRects, ui->rects); - ui->cursorIsPointer = false; + ui->cursorType = UI_Cursor_Arrow; ui->prevHotNode = ui->hotNode; + ui->prevHoveredNode = ui->hoveredNode; ui->hotNode = 0; + ui->hoveredNode = 0; ui->scene3DHandle = 0; ui->rootRect = 0; ui->currRect = 0; @@ -205,6 +228,7 @@ void ui_begin(UI_Context *ui) { ListAppend(ui->rects, (UI_Rect){0}); ui_openElement(ui, (UI_Rect){ .width=ui->renderer->width, .height=ui->renderer->height, .color=(RLVector4){0,0,0,0}}); + ui_placeText(ui, (UI_RectStr){ .s={.str=NULL, .length=0}, .fontSize=10, .color={1,1,1,1}, .alignment=UI_TxtAlign_Left, .lineHeight=10}); } void ui_end(UI_Context *ui) { @@ -298,9 +322,9 @@ void ui_openElement(UI_Context *ui, UI_Rect rect) { if (currRect->stringData) { UI_RectStr *inheritedTextAttr = PushStructZero(ui->arena, UI_RectStr); *inheritedTextAttr = *currRect->stringData; - inheritedTextAttr->s = s(""); - newRect->inheritedTextAttr = inheritedTextAttr; - } else { + *newRect->inheritedTextAttr = *currRect->stringData; + newRect->inheritedTextAttr->s = (string){ .str=NULL, .length=0 }; + } else if (currRect) { newRect->inheritedTextAttr = currRect->inheritedTextAttr; } } @@ -373,12 +397,13 @@ bool ui_ButtonWithHover(UI_Context *ui, UI_Rect rect, UI_Rect hovered, UI_RectSt bool inRect = pointInRect(ui->input->mouse.point.x, ui->input->mouse.point.y, ui->prevRects.data[id]); if (inRect) { - ui->cursorIsPointer = true; + ui->cursorType = UI_Cursor_Pointer; if (ui->prevHotNode == id && !ui->input->mouse.btnLeft) { clicked = true; } else if (ui->input->mouse.btnLeft && (!ui->prevInput->mouse.btnLeft || ui->prevHotNode == id)) { ui->hotNode = id; } + ui->hoveredNode = id; } bool useHover = inRect && !(hovered.flags & UI_Flag_Ignore); @@ -407,7 +432,7 @@ bool ui_CheckboxRect(UI_Context *ui, bool *value, UI_Rect rect) { bool clicked = false; bool pointerInRect = pointInRect(ui->input->mouse.point.x, ui->input->mouse.point.y, ui->prevRects.data[id]); if (pointerInRect) { - ui->cursorIsPointer = true; + ui->cursorType = UI_Cursor_Pointer; if (ui->prevHotNode == id && !ui->input->mouse.btnLeft) { *value = !*value; clicked = true; diff --git a/src/ui.h b/src/ui.h index 2e6ac19..a4c4eaa 100644 --- a/src/ui.h +++ b/src/ui.h @@ -43,6 +43,7 @@ enum UI_Flag { UI_Flag_3DScene=1<<3, UI_Flag_Center=1<<4, UI_Flag_Ignore=1<<7, // For optional parameters + UI_Flag_Pos_Absolute=1<<8, // Default is relative // .. UI_Flag_COUNT, }; @@ -55,6 +56,13 @@ struct UI_Padding { real32 left; }; +enum UI_Cursor { + UI_Cursor_Arrow=1<<0, + UI_Cursor_Pointer=1<<1, + UI_Cursor_Forbidden=1<<2, + UI_Cursor_Count=1<<3, +}; + enum UI_TxtAlign { UI_TxtAlign_Left=1<<0, // Default UI_TxtAlign_Center=1<<1, @@ -110,7 +118,7 @@ struct UI_Rect { /** * Inherited from parent to propagate attributes. Actual string is ignored, only used for filling out defaults in * the next stringData pointer that is set. - * This pointer is always set, as the global UI context will set it on the root not if it does not present its own + * This pointer is always set, as the global UI context will set it on the root node if it does not present its own * text data. */ UI_RectStr *inheritedTextAttr; @@ -123,13 +131,15 @@ struct UI_Context { UI_RectList prevRects; UI_RectList rects; int32 hotNode; + int32 hoveredNode; + int32 prevHoveredNode; int32 scene3DHandle; int32 prevHotNode; Renderer *renderer; Input *prevInput; Input *input; - bool cursorIsPointer; + enum UI_Cursor cursorType; int32 rootRect; int32 currRect; @@ -176,7 +186,7 @@ extern UI_Context *__UI_current_ctx__; #define UI_TxtAttr(str, ...)\ /** Generate UI text to be used as a value */\ - ((UI_RectStr){ .alignment=-1, .xOffset=0, .yOffset=0, .fontSize=-1, .lineHeight=-1, .s=(str), __VA_ARGS__ }) + ((UI_RectStr){ .color={-1,-1,-1,-1}, .alignment=-1, .xOffset=0, .yOffset=0, .fontSize=-1, .lineHeight=-1, .s=(str), __VA_ARGS__ }) #define UI_Txt(str, ...)\ /** Place UI text in the current position in the UI tree. Attaches to the current UI_Rect */\