From a37ea807e93886e6a6ebc22a878a5649e97f015a Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Tue, 3 Jan 2023 16:40:57 -0500 Subject: shape layer work --- src/bezier.cpp | 76 ++-- src/createcalls.cpp | 36 ++ src/gl_calls.cpp | 121 ++++++- src/imgui_ui.cpp | 664 ++--------------------------------- src/imgui_ui_stable_diffusion.cpp | 2 +- src/imgui_ui_viewport.cpp | 715 ++++++++++++++++++++++++++++++++++++++ src/include/functions.h | 5 + src/include/gl_calls.h | 1 + src/include/keybinds.h | 1 + src/include/main.h | 31 +- src/layer.cpp | 32 +- src/main.cpp | 129 +++++-- src/nanovg.cpp | 247 +++++++++++++ 13 files changed, 1332 insertions(+), 728 deletions(-) create mode 100644 src/imgui_ui_viewport.cpp create mode 100644 src/nanovg.cpp diff --git a/src/bezier.cpp b/src/bezier.cpp index 6fca5cc..ac89ed3 100644 --- a/src/bezier.cpp +++ b/src/bezier.cpp @@ -102,6 +102,46 @@ Bezier_Add(memory *Memory, memory_table_list TableName, property_channel *Proper } } +// return all points +static void Bezier_CubicCalcPointsCasteljauStep(void *Data, uint32 Size, uint32 *Increment, real32 x1, real32 y1, real32 x2, real32 y2, real32 x3, real32 y3, real32 x4, real32 y4, real32 tess_tol, int level) +{ + real32 dx = x4 - x1; + real32 dy = y4 - y1; + real32 d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + real32 d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) + { + real32 *Address = (real32 *)((uint8 *)Data + *Increment*Size); + *Address = x4; + *(Address + 1) = y4; + *(Address + 2) = 0; + *Increment += 1; + } + else if (level < 10) + { + real32 x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; + real32 x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; + real32 x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; + real32 x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; + real32 x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; + real32 x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; + Bezier_CubicCalcPointsCasteljauStep(Data, Size, Increment, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); + Bezier_CubicCalcPointsCasteljauStep(Data, Size, Increment, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); + } +} + +uint32 Bezier_CubicCalcPoints(v2 p1, v2 p2, v2 p3, v2 p4, void *Data, uint32 Size) +{ + uint32 Increment = 0; + real32 tess_tol = TESS_TOL; + void *Pointer = Data; + Bezier_CubicCalcPointsCasteljauStep(Pointer, Size, &Increment, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); + return Increment; +} + + #if 0 // A modified version of the bezier code in ImGui with extra features for bitmap and path interaction. @@ -214,35 +254,6 @@ static void Bezier_CubicMinMaxCasteljauStep(v2 *p_min, v2 *p_max, real32 x1, rea } } -// return all points -static void Bezier_CubicCalcPointsCasteljauStep(void *Data, uint32 *Increment, real32 x1, real32 y1, real32 x2, real32 y2, real32 x3, real32 y3, real32 x4, real32 y4, real32 tess_tol, int level) -{ - real32 dx = x4 - x1; - real32 dy = y4 - y1; - real32 d2 = ((x2 - x4) * dy - (y2 - y4) * dx); - real32 d3 = ((x3 - x4) * dy - (y3 - y4) * dx); - d2 = (d2 >= 0) ? d2 : -d2; - d3 = (d3 >= 0) ? d3 : -d3; - if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) - { - *((real32 *)Data + *Increment*3) = x4; - *((real32 *)Data + *Increment*3 + 1) = y4; - *((real32 *)Data + *Increment*3 + 2) = 0; - *Increment += 1; - } - else if (level < 10) - { - real32 x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f; - real32 x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f; - real32 x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f; - real32 x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; - real32 x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; - real32 x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; - Bezier_CubicCalcPointsCasteljauStep(Data, Increment, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); - Bezier_CubicCalcPointsCasteljauStep(Data, Increment, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); - } -} - real32 Bezier_CubicRatioOfPoint(v2 p1, v2 p2, v2 p3, v2 p4, v2 p) { real32 tess_tol = TESS_TOL; @@ -254,13 +265,6 @@ real32 Bezier_CubicRatioOfPoint(v2 p1, v2 p2, v2 p3, v2 p4, v2 p) return ratio; } -void Bezier_CubicCalcPoints(v2 p1, v2 p2, v2 p3, v2 p4, void *Data, uint32 *Increment) -{ - real32 tess_tol = TESS_TOL; - void *Pointer = Data; - Bezier_CubicCalcPointsCasteljauStep(Pointer, Increment, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); -} - // These functions will become more generalized as shapes are added. static void diff --git a/src/createcalls.cpp b/src/createcalls.cpp index 8b0ebf7..4bdd6d7 100644 --- a/src/createcalls.cpp +++ b/src/createcalls.cpp @@ -609,6 +609,42 @@ Property_IsGraphSelected(memory *Memory, property_channel *Property, uint16 *Arr return 0; } +static void +Project_ShapeLayer_New(project_data *File, project_state *State, memory *Memory, v2 Point, v2 Vector) +{ + int TopOffset = Layer_GetTopOffset(File, Memory); + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + Layer_DeselectAll(File, State, Memory); + History_Entry_Commit(Memory, "New shape layer"); + block_layer *Layer = Layer_Init(File, Memory); + Layer->IsShapeLayer = true; + Layer->x.CurrentValue = MainComp->Width / 2; + Layer->y.CurrentValue = MainComp->Height / 2; + if (File->Layer_Count == 1) { + Layer->Vertical_Offset = 11 - 1; + } else { + Layer->Vertical_Offset = TopOffset - 1; + } + Layer->Frame_Start = MainComp->Frame_Start; + Layer->Frame_End = MainComp->Frame_End; + Layer_Select(Memory, State, Memory_Block_LazyIndexAtAddress(Memory, F_Layers, Layer)); + + Layer->Shape.Block_Bezier_Index[0] = Memory_Block_AllocateNew(Memory, F_Bezier); + block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Layer->Shape.Block_Bezier_Index[0], 0); + Bezier->Occupied = true; + v2 BezierPoint[3] = { Point, V2(000, 000), V2(000, 000) }; + interpolation_type Type = interpolation_type_linear; + if (Vector.x) { + BezierPoint[1] = Vector; + BezierPoint[2] = Vector * -1; + Type = interpolation_type_bezier; + } + Bezier->Point[0] = { 1, { BezierPoint[0], BezierPoint[1], BezierPoint[2] }, Type, 0 }; + + History_Entry_End(Memory); + State->UpdateFrame = true; +} + static void Project_PaintLayer_New(project_data *File, project_state *State, memory *Memory) { diff --git a/src/gl_calls.cpp b/src/gl_calls.cpp index 1cb408a..5007148 100644 --- a/src/gl_calls.cpp +++ b/src/gl_calls.cpp @@ -1,17 +1,27 @@ #include "gl_calls.h" const char *DefaultVertexShaderSource = "#version 330 core\n" -"layout (location = 0) in vec3 aPos;\n" +"layout (location = 0) in vec3 Point;\n" "layout (location = 1) in vec2 aTexCoord;\n" "out vec2 TexCoord;\n" "uniform int VertexMode;\n" "uniform vec3 CompDimensions;\n" +"uniform vec2 LayerDimensions;\n" +"uniform vec2 Pos;\n" +"uniform vec2 Anchor;\n" +"uniform float Rad;\n" +"uniform float Scale;\n" "void main()\n" "{\n" " if (VertexMode == 0) {\n" -" gl_Position = vec4(aPos, 1.0);\n" +" gl_Position = vec4(Point, 1.0);\n" "} else {\n" -" gl_Position = vec4(vec2(aPos.x / CompDimensions.x, aPos.y / CompDimensions.y) * 2 - 1.0f, 0.0f, 1.0);\n" +" vec2 XRotation = vec2(cos(Rad), sin(Rad));\n" +" vec2 YRotation = vec2(sin(Rad), -cos(Rad));\n" +" vec2 XAxis = (Point.x - (Anchor.x * LayerDimensions.x)) * Scale * XRotation;\n" +" vec2 YAxis = (Point.y - (Anchor.y * LayerDimensions.y)) * -Scale * YRotation;\n" +" vec2 LocalPoint = Pos + vec2(XAxis + YAxis);\n" +" gl_Position = vec4(vec2(LocalPoint.x / CompDimensions.x, LocalPoint.y / CompDimensions.y) * 2 - 1.0f, 0.0f, 1.0);\n" "}\n" " TexCoord = aTexCoord;\n" "}\0"; @@ -83,25 +93,24 @@ static void GL_InitDefaultVerts() { 1, 2, 3 }; - // Indices! glGenVertexArrays(1, &DefaultVerts.VertexArrayObject); - glGenBuffers(1, &DefaultVerts.ElementBufferObject); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, DefaultVerts.ElementBufferObject); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLIndices), GLIndices, GL_STATIC_DRAW); glGenBuffers(1, &DefaultVerts.VertexBufferObject); - // Our vertices need to be stored in this buffer. glBindBuffer(GL_ARRAY_BUFFER, DefaultVerts.VertexBufferObject); glBufferData(GL_ARRAY_BUFFER, sizeof(GL_DefaultVertices), GL_DefaultVertices, GL_STATIC_DRAW); - // position attribute glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); - // texture coordinate (note the last parameter's offset) glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); + + glGenVertexArrays(1, &ShapeVerts.VertexArrayObject); + glGenBuffers(1, &ShapeVerts.VertexBufferObject); } void @@ -155,6 +164,102 @@ GL_DeleteHWBuffer(gl_effect_layer *Test) Test->Initialized = true; } +void +GL_RasterizeShape(gl_effect_layer *TestL, gl_effect_layer *TestM, void *PointData, uint32 GL_PointCount, + layer_transforms T, int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, int L_Width, int L_Height) +{ + glBindTexture(GL_TEXTURE_2D, TestL->Texture); + int ByteFlag = (BytesPerPixel == 4) ? GL_RGBA : GL_RGBA16; + int ByteFlag2 = (BytesPerPixel == 4) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT; + glTexImage2D(GL_TEXTURE_2D, 0, ByteFlag, Width, Height, 0, GL_RGBA, + ByteFlag2, EffectBitmapAddress); + + GL_UpdateTexture(TestL, EffectBitmapAddress, Width, Height, BytesPerPixel, 0); + GL_UpdateTexture(TestM, EffectBitmapAddress, Width, Height, BytesPerPixel, 1); + + glBindFramebuffer(GL_FRAMEBUFFER, TestM->FramebufferObject); + + // stencil buffer + glEnable(GL_STENCIL_TEST); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + glStencilFunc(GL_ALWAYS, 0, 0xFF); + glStencilMask(0xff); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + glUseProgram(DefaultShaderProgram); + + int Uniform = glGetUniformLocation(DefaultShaderProgram, "VertexMode"); + glUniform1i(Uniform, 1); + Uniform = glGetUniformLocation(DefaultShaderProgram, "FragmentMode"); + glUniform1i(Uniform, 1); + Uniform = glGetUniformLocation(DefaultShaderProgram, "CompDimensions"); + glUniform3f(Uniform, Width, Height, 0); + Uniform = glGetUniformLocation(DefaultShaderProgram, "LayerDimensions"); + glUniform2f(Uniform, L_Width, L_Height); + Uniform = glGetUniformLocation(DefaultShaderProgram, "Pos"); + glUniform2f(Uniform, T.x, T.y); + Uniform = glGetUniformLocation(DefaultShaderProgram, "Anchor"); + glUniform2f(Uniform, T.ax, T.ay); + real32 Rad = (T.rotation * (PI / 180)); + Uniform = glGetUniformLocation(DefaultShaderProgram, "Rad"); + glUniform1f(Uniform, Rad); + Uniform = glGetUniformLocation(DefaultShaderProgram, "Scale"); + glUniform1f(Uniform, T.scale); + + glBindBuffer(GL_ARRAY_BUFFER, ShapeVerts.VertexBufferObject); + glBufferData(GL_ARRAY_BUFFER, sizeof(real32) * 4 * GL_PointCount, PointData, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); + glEnableVertexAttribArray(1); + + // stencil buffer + glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); + glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); + glDisable(GL_CULL_FACE); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, GL_PointCount); + + // stencil buffer + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + // default verts + glBindVertexArray(0); + Uniform = glGetUniformLocation(DefaultShaderProgram, "VertexMode"); + glUniform1i(Uniform, 0); + glBindBuffer(GL_ARRAY_BUFFER, DefaultVerts.VertexBufferObject); + glBufferData(GL_ARRAY_BUFFER, sizeof(GL_DefaultVertices), GL_DefaultVertices, GL_STATIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + + // stencil buffer + glStencilFunc(GL_NOTEQUAL, 0, 0xFF); + glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); + + glDrawElements(GL_TRIANGLE_STRIP, 6, GL_UNSIGNED_INT, 0); + + // stencil buffer + glDisable(GL_STENCIL_TEST); + glStencilMask(0xFF); + glStencilFunc(GL_ALWAYS, 0, 0xFF); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, TestM->FramebufferObject); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, TestL->FramebufferObject); + glBlitFramebuffer(0, 0, Width, Height, 0, 0, Width, Height, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_FRAMEBUFFER, TestL->FramebufferObject); + + glReadPixels(0, 0, Width, Height, GL_RGBA, GL_UNSIGNED_BYTE, EffectBitmapAddress); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + // GL_DeleteHWBuffer(TestL); + // GL_DeleteHWBuffer(TestM); +} + void GL_UpdateTexture(gl_effect_layer *Test, void *Data, uint16 Width, uint16 Height, uint16 BytesPerPixel, bool32 Multisample) { diff --git a/src/imgui_ui.cpp b/src/imgui_ui.cpp index 77a6712..1feb1e5 100644 --- a/src/imgui_ui.cpp +++ b/src/imgui_ui.cpp @@ -7,6 +7,7 @@ #include "imgui_ui_properties.cpp" #include "imgui_ui_timeline.cpp" +#include "imgui_ui_viewport.cpp" #if DEBUG #include "imgui_ui_debug.cpp" @@ -92,7 +93,7 @@ ImGui_File(project_data *File, project_state *State, memory *Memory, ImGuiIO io, if (ImGui::BeginPopup("sourcecontext")) { if (ImGui::MenuItem("Create layer from source")) { - State->HotkeyInput = hotkey_newlayerfromsource; + State->HotkeyInput = hotkey_newlayer_source; } ImGui::EndPopup(); } @@ -115,6 +116,27 @@ ImGui_File(project_data *File, project_state *State, memory *Memory, ImGuiIO io, ImGui::End(); } + +static void +ImGui_Opt_Shape(shape_options *Opt) +{ + // TODO(fox): Combine with RGBA function? + char *Names[3] = { "Fill only", "Stroke only", "Fill and stroke" }; + if (ImGui::BeginListBox("Shape render type")) { + for (int i = 0; i < 3; i++) { + if (ImGui::Selectable(Names[i], (Opt->Visibility == i))) { + Opt->Visibility = i; + } + } + ImGui::EndListBox(); + } + ImGui::ColorEdit4("Fill color", (real32 *)&Opt->FillCol, ImGuiColorEditFlags_Float); + ImGui::ColorEdit4("Stroke color", (real32 *)&Opt->StrokeCol, ImGuiColorEditFlags_Float); + real32 StrokeWidthMin = 0; + real32 StrokeWidthMax = 1024; + ImGui::DragScalar("Stroke width", ImGuiDataType_Float, &Opt->StrokeWidth, 1, &StrokeWidthMin, &StrokeWidthMax, "%.3f"); +} + static void ImGui_ColorPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io) { @@ -173,646 +195,13 @@ ImGui_ColorPanel(project_data *File, project_state *State, ui *UI, memory *Memor Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); State_BindBrushTexture(Memory, &State->Brush, 4); } + } else if (State->Tool == tool_pen && State->MostRecentlySelectedLayer == -1) { + ImGui_Opt_Shape(&State->Pen.Opt); } ImGui::End(); } -static void -ImGui_Viewport_Toolbar(project_state *State, ImDrawList *draw_list) -{ - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - // ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(50, 50, 50, 0)); - - real32 IconSize = ImGui::GetFontSize() * 4; - int ToolCount = (int)tool_count; - ImVec2 ButtonSize(IconSize, IconSize); - ImVec2 WindowSize(IconSize, IconSize * ToolCount); - ImGui::BeginChild("Toolbar", WindowSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); - for (int i = 0; i < ToolCount; i++) { - ImGui::PushID(i); - // draw_list->AddImage((void *)(intptr_t)State->ToolIconTex[i], Min, Max); - if ((int)State->Tool == i) { - ImVec2 Min = ImGui::GetCursorScreenPos(); - ImVec2 Max = Min + ButtonSize; - draw_list->AddRectFilled(Min, Max, IM_COL32(255, 255, 255, 128)); - } - ImGui::Button(ToolName[i], ButtonSize); - if (ImGui::IsItemActivated()) { - State->Tool = (tool)i; - } - ImGui::PopID(); - } - ImGui::EndChild(); - - // ImGui::PopStyleColor(); - ImGui::PopStyleVar(2); -} - -static void -ImGui_Viewport_BrushUI(project_state *State, memory *Memory, ImVec2 ViewportMin, ImVec2 ViewportMax, ImVec2 CompZoom, ImGuiIO io, uint16 Width, uint16 Height) -{ - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - if (State->Tool == tool_brush) { - - if (ImGui::IsKeyPressed(ImGuiKey_ModAlt, false)) { - State->Brush.UIPos = io.MousePos; - } - - ImVec2 CompScale = CompZoom / ImVec2(Width, Height); - ImVec2 BrushSize = CompScale * State->Brush.Size; - ImVec2 MinBounds = State->Brush.UIPos - BrushSize/2; - ImVec2 MaxBounds = MinBounds + BrushSize; - - // if (io.KeyCtrl) { - // ImGui::SetCursorScreenPos(State->Brush.UIPos); - // char buf[256]; - // sprintf(buf, "RGBA: %.1f, %.1f, %.1f, %.1f", State->Brush.Size, State->Brush.Hardness); - // } - - if (io.KeyAlt) { - draw_list->PushClipRect(ViewportMin, ViewportMax, true); - draw_list->AddImage((void *)(intptr_t)State->Brush.GLTexture, MinBounds, MaxBounds, ImVec2(0, 0), ImVec2(1, 1), 1); - draw_list->PopClipRect(); - ImGui::SetCursorScreenPos(State->Brush.UIPos); - char buf[256]; - sprintf(buf, "Size: %.1f, Hardness: %.1f", State->Brush.Size, State->Brush.Hardness); - ImGui::Text(buf); - if (io.MouseDelta.x || io.MouseDelta.y) { - ImVec2 Delta = io.MouseDelta; - State->Brush.Size += Delta.x; - State->Brush.Hardness += Delta.y*State->Brush.Hardness/100; - if (State->Brush.Size < 0) - State->Brush.Size = 0; - if (State->Brush.Size > 1024) - State->Brush.Size = 1024; - if (State->Brush.Hardness < 0.5) - State->Brush.Hardness = 0.5; - if (State->Brush.Hardness > 100) - State->Brush.Hardness = 100; - Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); - State_BindBrushTexture(Memory, &State->Brush, 4); - } - } - } -} - -static void -ImGui_Viewport_TransformUI(project_data *File, project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list, ImGuiIO &io, - interact_transform *Interact, ImVec2 ViewportMin, uint32 CompWidth, uint32 CompHeight, uint16 *SortedKeyframeArray) -{ - v2 InteractMin = Interact->Min + Interact->Position; - v2 InteractMax = Interact->Max + Interact->Position; - - v2 BoxLength = InteractMax - InteractMin; - v2 Center = InteractMax - (BoxLength/2); - - real32 Point0X = Center.x - InteractMin.x; - real32 Point0Y = Center.y - InteractMin.y; - - real32 Rad = Interact->Radians; - - v2 XAxis = (Point0X * Interact->Scale)*V2(cos(Rad), sin(Rad)); - v2 YAxis = (Point0Y * -Interact->Scale)*V2(sin(Rad), -cos(Rad)); - - // Points are clockwise starting from the top left. - real32 X0 = -XAxis.x - YAxis.x + Center.x; - real32 Y0 = -XAxis.y - YAxis.y + Center.y; - real32 X1 = X0 + XAxis.x*2; - real32 Y1 = Y0 + XAxis.y*2; - real32 X2 = X1 + YAxis.x*2; - real32 Y2 = Y1 + YAxis.y*2; - real32 X3 = X2 - XAxis.x*2; - real32 Y3 = Y2 - XAxis.y*2; - - // Midway points. - real32 Mid_X0 = X0 + XAxis.x; - real32 Mid_Y0 = Y0 + XAxis.y; - real32 Mid_X1 = X1 + YAxis.x; - real32 Mid_Y1 = Y1 + YAxis.y; - real32 Mid_X2 = X2 - XAxis.x; - real32 Mid_Y2 = Y2 - XAxis.y; - real32 Mid_X3 = X3 - YAxis.x; - real32 Mid_Y3 = Y3 - YAxis.y; - - ImVec2 CompScale = UI->CompZoom / ImVec2(CompWidth, CompHeight); - - ImVec2 P[4]; - P[0] = ImVec2(X0, Y0)*CompScale + UI->CompPos; - P[1] = ImVec2(X1, Y1)*CompScale + UI->CompPos; - P[2] = ImVec2(X2, Y2)*CompScale + UI->CompPos; - P[3] = ImVec2(X3, Y3)*CompScale + UI->CompPos; - - ImVec2 Mid_P[4]; - Mid_P[0] = ImVec2(Mid_X0, Mid_Y0)*CompScale + UI->CompPos; - Mid_P[1] = ImVec2(Mid_X1, Mid_Y1)*CompScale + UI->CompPos; - Mid_P[2] = ImVec2(Mid_X2, Mid_Y2)*CompScale + UI->CompPos; - Mid_P[3] = ImVec2(Mid_X3, Mid_Y3)*CompScale + UI->CompPos; - - ImU32 wcol = ImGui::GetColorU32(ImGuiCol_Text); - draw_list->AddLine(P[0], P[1], wcol, 2.0f); - draw_list->AddLine(P[1], P[2], wcol, 2.0f); - draw_list->AddLine(P[2], P[3], wcol, 2.0f); - draw_list->AddLine(P[3], P[0], wcol, 2.0f); - - v2 XAxis2 = (BoxLength*CompScale.x)*V2(cos(Rad), sin(Rad)); - v2 YAxis2 = (BoxLength*CompScale.y)*V2(sin(Rad), -cos(Rad)); - - v2 XAxisPerp = (1.0f / LengthSq(XAxis))*XAxis; - v2 YAxisPerp = (1.0f / LengthSq(YAxis))*YAxis; - - // real32 LocalX = ((io.MousePos.x - UI->CompPos.x) - Center.x) ; - // real32 LocalY = ((io.MousePos.y - UI->CompPos.y) - Center.y) ; - layer_transforms BoxTransforms = { Center.x, Center.y, 0.5, 0.5, (real32)(Interact->Radians / (PI / 180)), Interact->Scale }; - v2 LayerPoint = Transform_ScreenSpaceToLocal(BoxTransforms, CompWidth, CompHeight, BoxLength.x, BoxLength.y, UI->CompPos, UI->CompZoom, ViewportMin, io.MousePos); - - real32 U = LayerPoint.x / BoxLength.x; - real32 V = LayerPoint.y / BoxLength.y; - - ImVec2 ScaleHandleSize(50, 50); - - bool32 OtherActions = ImGui::IsKeyDown(ImGuiKey_Z); - - // First do the halfway scale points, since they don't need UVs considered: - for (int i = 0; i < 4; i++) { - ImGui::SetCursorScreenPos(Mid_P[i] - ScaleHandleSize/2); - ImGui::PushID(i); - - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertFloat4ToU32(ImVec4(0.6f, 0.0f, 0.3f, 1.0f))); - ImGui::Button("##ScaleMids", ScaleHandleSize); - ImGui::PopStyleColor(); - - if (ImGui::IsItemActivated() && !OtherActions) { - State->InteractTransformMode = 1; - } - - if (State->InteractTransformMode == 1 && ImGui::IsItemActive()) - { - uint32 side = i; - if (side == 0) { - Interact->Scale -= io.MouseDelta.y / BoxLength.y; - Interact->Position.y += io.MouseDelta.y / 2; - } else if (side == 1) { - Interact->Scale += io.MouseDelta.x / BoxLength.x; - Interact->Position.x += io.MouseDelta.x / 2; - } else if (side == 2) { - Interact->Scale += io.MouseDelta.y / BoxLength.y; - Interact->Position.y += io.MouseDelta.y / 2; - } else if (side == 3) { - Interact->Scale -= io.MouseDelta.x / BoxLength.x; - Interact->Position.x += io.MouseDelta.x / 2; - } - } - ImGui::PopID(); - } - - bool32 InBounds = false; - // Scale if cursor is on button within the UV, rotate if outside UV, and position if a non-button is dragged. - if (U >= 0.0f && U <= 1.0f && V >= 0.0f && V <= 1.0f) - { - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertFloat4ToU32(ImVec4(0.6f, 0.0f, 0.3f, 1.0f))); - InBounds = true; - } - - for (int i = 0; i < 4; i++) { - ImGui::SetCursorScreenPos(P[i] - ScaleHandleSize/2); - ImGui::PushID(i); - ImGui::Button("##ScaleRotateCorners", ScaleHandleSize); - - if (ImGui::IsItemActivated() && !OtherActions) { - if (InBounds) - State->InteractTransformMode = 1; - else - State->InteractTransformMode = 2; - } - - // Scale part - if (State->InteractTransformMode == 1 && ImGui::IsItemActive()) - { - // TODO(fox): Corner dragging scale only works in the X - // axis. Mostly feels right when dragged how you expect, - // but I'll fix it if someone complains. - uint32 side = i; - if (side == 0) { - Interact->Scale -= io.MouseDelta.x / BoxLength.x; - Interact->Position.x += io.MouseDelta.x / 2; - Interact->Position.y += io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; - } else if (side == 1) { - Interact->Scale += io.MouseDelta.x / BoxLength.x; - Interact->Position.x += io.MouseDelta.x / 2; - Interact->Position.y -= io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; - } else if (side == 2) { - Interact->Scale += io.MouseDelta.x / BoxLength.x; - Interact->Position.x += io.MouseDelta.x / 2; - Interact->Position.y += io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; - } else if (side == 3) { - Interact->Scale -= io.MouseDelta.x / BoxLength.x; - Interact->Position.x += io.MouseDelta.x / 2; - Interact->Position.y -= io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; - } - } - - // Rotation part - if (State->InteractTransformMode == 2 && ImGui::IsItemActive()) - { - real32 LocalX = (io.MousePos.x - UI->CompPos.x)/CompScale.x - InteractMin.x - (BoxLength.x/2); - real32 LocalY = (io.MousePos.y - UI->CompPos.y)/CompScale.y - InteractMin.y - (BoxLength.y/2); - - real32 Slope_Mouse = LocalY/LocalX; - real32 Slope_Corner = 0; - real32 Slope_Flipped = 0; - real32 Dot = 0; - - // TODO(fox) learn basic geometry to do this properly - - // We find the angle between the direction of whichever corner the - // mouse is grabbing (Slope_Corner) and the mouse's current - // position (Slope_Mouse) to get ExtraRadians. The calculation only - // works between -90 and 90, so I take the dot product of the - // opposite edge of the corner and add the extra degrees when it's negative. - - v2 SlopeDot = V2(BoxLength.x, BoxLength.y); - // top left clockwise - uint32 side = i; - if (side == 0) { - Slope_Corner = BoxLength.y / BoxLength.x; - Slope_Flipped = -BoxLength.x / BoxLength.y; - Dot = LocalX * -SlopeDot.x + LocalY * -SlopeDot.y; - } else if (side == 1) { - Slope_Corner = -BoxLength.y / BoxLength.x; - Slope_Flipped = BoxLength.x / BoxLength.y; - Dot = LocalX * SlopeDot.x + LocalY * -SlopeDot.y; - } else if (side == 2) { - Slope_Corner = BoxLength.y / BoxLength.x; - Slope_Flipped = -BoxLength.x / BoxLength.y; - Dot = LocalX * SlopeDot.x + LocalY * SlopeDot.y; - } else if (side == 3) { - Slope_Corner = -BoxLength.y / BoxLength.x; - Slope_Flipped = BoxLength.x / BoxLength.y; - Dot = LocalX * -SlopeDot.x + LocalY * SlopeDot.y; - } - - Interact->Radians = atan((Slope_Mouse - Slope_Corner) / (1 + Slope_Mouse * Slope_Corner)); - real32 ExtraRadians2 = atan((Slope_Mouse - Slope_Flipped) / (1 + Slope_Mouse * Slope_Flipped)); - - if (Dot < 0) { - if (Interact->Radians < 0) { - Interact->Radians = (90 * (PI / 180)) + ExtraRadians2; - } else { - Interact->Radians = (-90 * (PI / 180)) + ExtraRadians2; - } - } - } - - ImGui::PopID(); - } - - if (!State->InteractTransformMode && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && InBounds && !OtherActions) - State->InteractTransformMode = 3; - - if (State->InteractTransformMode == 3) { - Interact->Position.x += (real32)io.MouseDelta.x/CompScale.x; - Interact->Position.y += (real32)io.MouseDelta.y/CompScale.y; - } - - if (State->InteractTransformMode) - { - if (io.MouseDelta.x || io.MouseDelta.y) - State->UpdateFrame = true; - if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) - State->InteractTransformMode = 0; - } - - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { - State->Interact_Active = interact_type_none; - State->Interact_Modifier = 0; - State->UpdateFrame = true; - Memory->PurgeCache = true; - } - - // Second condition so you don't have to reach for Enter. - if (ImGui::IsKeyPressed(ImGuiKey_Enter) || (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && io.KeyCtrl)) { - int h = 0, c = 0, i = 0; - if (!io.KeyCtrl) - History_Entry_Commit(Memory, "Transform layers"); - while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { - block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); - if (Layer->IsSelected == 1) { - if (io.KeyCtrl) { - layer_transforms T = Layer_GetTransforms(Layer); - Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &T.x, &T.y, &T.rotation, &T.scale); - property_channel *Property[4] = { &Layer->x, &Layer->y, &Layer->rotation, &Layer->scale }; - real32 Val[4] = { T.x, T.y, T.rotation, T.scale }; - for (int a = 0; a < 4; a++) { - if (Property[a]->CurrentValue != Val[a]) { - History_Entry_Commit(Memory, "Add keyframe"); - bezier_point Point = { 1, {(real32)State->Frame_Current, Val[a], -1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 }; - uint16 *ArrayLocation = Property_GetSortedArray(SortedKeyframeArray, State->MostRecentlySelectedLayer, h); - Bezier_Add(Memory, F_Layers, Property[a], Point, ArrayLocation); - History_Entry_End(Memory); - } - } - } else { - History_Action_Swap(Memory, F_File, sizeof(Layer->x.CurrentValue), &Layer->x.CurrentValue); - History_Action_Swap(Memory, F_File, sizeof(Layer->y.CurrentValue), &Layer->y.CurrentValue); - History_Action_Swap(Memory, F_File, sizeof(Layer->scale.CurrentValue), &Layer->scale.CurrentValue); - History_Action_Swap(Memory, F_File, sizeof(Layer->rotation.CurrentValue), &Layer->rotation.CurrentValue); - Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &Layer->x.CurrentValue, &Layer->y.CurrentValue, &Layer->rotation.CurrentValue, &Layer->scale.CurrentValue); - } - } - } - if (!io.KeyCtrl) - History_Entry_End(Memory); - State->Interact_Active = interact_type_none; - State->Interact_Modifier = 0; - State->UncommitedKeyframe = 1; - State->UpdateFrame = true; - } - - if (InBounds == true) { - ImGui::PopStyleColor(); - } - -} - -static void -ImGui_Viewport_SelectedLayerUI(project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list, block_composition *MainComp, uint32 CompIndex, block_layer *ParentLayer[4], uint32 Recursions, - sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray) -{ - sorted_comp_array *SortedCompStart = &SortedCompArray[CompIndex]; - sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, CompIndex); - ImU32 wcol = ImGui::GetColorU32(ImGuiCol_Text); - for (int i = 0; i < SortedCompStart->LayerCount; i++) - { - sorted_layer_array SortEntry = SortedLayerStart[i]; - uint32 Index_Physical = SortEntry.Block_Layer_Index; - block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); - if (Layer->IsPrecomp) { - ParentLayer[Recursions] = Layer; - ImGui_Viewport_SelectedLayerUI(State, Memory, UI, draw_list, MainComp, Layer->Block_Source_Index, ParentLayer, Recursions + 1, SortedCompArray, SortedLayerArray); - } - if (Layer->IsSelected) { - uint32 Width = 0, Height = 0; - if (!Layer->IsPrecomp) { - block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); - Width = Source->Width; - Height = Source->Height; - } else { - block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); - Width = Comp->Width; - Height = Comp->Height; - } - - v2 Point[5] = { V2(Width*Layer->ax.CurrentValue, Height*Layer->ay.CurrentValue), V2(0, 0), V2(Width, 0), V2(0, Height), V2(Width, Height) }; - - layer_transforms T = Layer_GetTransforms(Layer); - - if (State->Interact_Active == interact_type_viewport_transform && Layer->IsSelected == 1) { - Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &T.x, &T.y, &T.rotation, &T.scale); - } - - v2 NewPos[5]; - for (int i = 0; i < 5; i++) { - NewPos[i] = TransformPoint(T, Width, Height, Point[i]); - } - - int i = 0; - while (i < Recursions) { - T = Layer_GetTransforms(ParentLayer[i]); - block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, ParentLayer[i]->Block_Source_Index); - Width = Comp->Width; - Height = Comp->Height; - for (int i = 0; i < 5; i++) { - NewPos[i] = TransformPoint(T, Width, Height, NewPos[i]); - } - i++; - } - - ImVec2 ScreenPoint[5]; - for (int i = 0; i < 5; i++) { - v2 CompUV = NewPos[i] / V2(MainComp->Width, MainComp->Height); - - ScreenPoint[i] = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, - UI->CompPos.y + CompUV.y * UI->CompZoom.y); - - } - if (State->Tool != tool_brush) { - ImU32 wcol2 = IM_COL32(10, 10, 10, 255); - draw_list->AddNgon(ScreenPoint[0], 10, wcol2, 8, 9.0f); - draw_list->AddNgon(ScreenPoint[0], 10, wcol, 8, 5.0f); - } - draw_list->AddLine(ScreenPoint[1], ScreenPoint[2], wcol, 2.0f); - draw_list->AddLine(ScreenPoint[2], ScreenPoint[4], wcol, 2.0f); - draw_list->AddLine(ScreenPoint[1], ScreenPoint[3], wcol, 2.0f); - draw_list->AddLine(ScreenPoint[3], ScreenPoint[4], wcol, 2.0f); - } - } -} - -static void -ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, GLuint textureID, - sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 *SortedKeyframeArray) -{ - bool open = true; - ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - - - if (ImGui::IsWindowHovered(ImGuiFocusedFlags_ChildWindows)) { - State->FocusedWindow = focus_viewport; - } - - block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); - - ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); - ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); - ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); - - if (ViewportScale.x < 50 || ViewportScale.y < 50) { - ImGui::End(); - return; - } - - if (State->Initializing) { - UI->CompZoom = ImVec2(MainComp->Width, MainComp->Height); - UI->CompPos = ImVec2(ViewportMin.x + ((ViewportMax.x - ViewportMin.x)/2 - UI->CompZoom.x/2), - ViewportMin.y + ((ViewportMax.y - ViewportMin.y)/2 - UI->CompZoom.y/2)); - } - - ImVec2 CompPosMin = ImVec2(UI->CompPos.x, UI->CompPos.y); - ImVec2 CompPosMax = ImVec2(UI->CompPos.x + UI->CompZoom.x, UI->CompPos.y + UI->CompZoom.y); - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); - draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255, 255, 255, 255)); - draw_list->AddRect(CompPosMin, CompPosMax, IM_COL32(255, 255, 255, 55)); - - real32 FontSize = ImGui::GetFontSize(); - ImGui::SetCursorScreenPos(ImVec2(ViewportMax.x - FontSize*2, ViewportMin.y + ViewportScale.y - FontSize*3.0)); - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 80)); - if (ImGui::Button("?")) - State->ImGuiPopups = popup_keybinds; - ImGui::PopStyleColor(); - - - // Actual composition texture - draw_list->PushClipRect(ViewportMin, ViewportMax, true); - draw_list->AddImage((void *)(intptr_t)textureID, CompPosMin, CompPosMax); - draw_list->PopClipRect(); - - // UI+interaction for layer - if (State->MostRecentlySelectedLayer > -1) - { - block_layer *ParentLayer[4]; - ImGui_Viewport_SelectedLayerUI(State, Memory, UI, draw_list, MainComp, File->PrincipalCompIndex, ParentLayer, 0, SortedCompArray, SortedLayerArray); - if (State->Interact_Active == interact_type_viewport_transform) { - ImGui_Viewport_TransformUI(File, State, Memory, UI, draw_list, io, (interact_transform *)&State->Interact_Offset[0], ViewportMin, MainComp->Width, MainComp->Height, SortedKeyframeArray); - } - } - - - - // Interactions for dragging and zooming - ImGui::SetCursorScreenPos(ViewportMin); - - ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); - bool32 IsHovered = ImGui::IsItemHovered(); -#if 1 - bool32 IsActive = ImGui::IsItemActive(); - bool32 IsActivated = ImGui::IsItemActivated(); - bool32 IsDeactivated = ImGui::IsItemDeactivated(); -#else - bool32 IsActive = ImGui::IsKeyDown(ImGuiKey_3); - bool32 IsActivated = ImGui::IsKeyPressed(ImGuiKey_3); - bool32 IsDeactivated = ImGui::IsKeyReleased(ImGuiKey_3); -#endif - - if (IsHovered && IsActivated && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) - { - // Point to zoom in on if Z is held - State->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); - - if (State->Tool == tool_brush && State->Interact_Active != interact_type_brush && !ImGui::IsKeyDown(ImGuiKey_Z)) { - if (!io.KeyCtrl) { - int h = 0, c = 0, i = 0; - while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) - { - block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); - if (!Layer->IsPrecomp) { - block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); - if (Layer->IsSelected && Source->Type == source_type_principal) { - Assert(Source->BytesPerPixel == 4); - Arbitrary_Zero((uint8 *)State->Brush.TransientBitmap, 2048*2048*4); - State->Interact_Active = interact_type_brush; - State->Brush.LayerToPaint_Index = i; - break; - } - } - } - } - if (State->Brush.LayerToPaint_Index == -1) { - State->HotkeyInput = hotkey_newpaintlayer; - } - } - - // Layer selection - if (!ImGui::IsKeyDown(ImGuiKey_Z) && State->Tool == tool_default && State->Interact_Active == interact_type_none) { - int32 Selection = Layer_TestSelection(Memory, State, UI, SortedCompArray, SortedLayerArray, File->PrincipalCompIndex); - if (!io.KeyShift && State->Interact_Active == interact_type_none) - Layer_DeselectAll(File, State, Memory); - if (Selection != -1) - Layer_Select(Memory, State, Selection); - } - } - - /* - if (State->Interact_Active == interact_type_viewport_transform) { - interact_transform *Interact = (interact_transform *)&State->Interact_Offset[0]; - ImVec2 DragDelta = io.MousePos - Interact->OGPos; - Interact->Position = V2(DragDelta.x, DragDelta.y); - if (io.MouseDelta.x || io.MouseDelta.y) - State->UpdateFrame = true; - } - */ - - if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) - { - UI->CompPos.x += io.MouseDelta.x; - UI->CompPos.y += io.MouseDelta.y; - } - - - bool32 OtherActions = ImGui::IsKeyDown(ImGuiKey_Z) || ImGui::IsMouseDown(ImGuiMouseButton_Right); - if (State->Tool == tool_brush && State->Interact_Active == interact_type_brush) { - Assert(State->Brush.LayerToPaint_Index != -1); - if (IsActive && !OtherActions) { - block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->Brush.LayerToPaint_Index); - layer_transforms T_Layer = Layer_GetTransforms(Layer); - block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); - ImVec2 MouseDelta = io.MouseDelta; - real32 Delta = MouseDelta.x + MouseDelta.y; - v2 PrincipalCompUV = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); - v2 LayerPos = Layer_TraverseForPoint(File, State, Memory, PrincipalCompUV, SortedCompArray, SortedLayerArray); - if (IsActivated) { - RenderQueue_AddBrush(State, LayerPos); - } else if (Delta != 0.0f) { - v2 PrevPos = State->Brush.PrevPos; - v2 Delta = PrevPos - LayerPos; - real32 Dist = sqrt(LengthSq(Delta)); - if (Dist > State->Brush.Spacing) { - RenderQueue_AddBrush(State, LayerPos); - } - } - State->UpdateFrame = true; - } - - if (IsDeactivated) { - RenderQueue_AddBlit(State); - } - } - - if (ImGui::IsKeyDown(ImGuiKey_Z) && ImGui::IsWindowHovered()) { - if (IsActive) - ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); - else - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - } - - real32 Distance = 0; - if (IsActive) { - if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1.0f)) - Distance = io.MouseDelta.x + io.MouseDelta.y; - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - Distance = 200; - } - if (Distance && ImGui::IsKeyDown(ImGuiKey_Z)) - { - if (io.KeyShift) - Distance *= -1; - UI->CompZoom.x += (Distance)*(real32)MainComp->Width/MainComp->Height; - UI->CompZoom.y += (Distance); - UI->CompPos.x -= ((Distance)*(real32)MainComp->Width/MainComp->Height)*State->TempZoomRatio.x; - UI->CompPos.y -= Distance*State->TempZoomRatio.y; - } - - ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMin.y + ViewportScale.y - FontSize*1.5)); - - ImGui::Text("%.1f", 100.0f * (UI->CompZoom.x / MainComp->Width)); - if (State->MsgTime > 0) { - ImGui::SameLine(); - ImGui::SetCursorPosX((ViewportScale.x / 5)*4); - ImGui::Text(State->Msg); - State->MsgTime--; - } - - ImGui::SetCursorScreenPos(ViewportMin); - ImGui_Viewport_Toolbar(State, draw_list); - ImGui_Viewport_BrushUI(State, Memory, ViewportMin, ViewportMax, UI->CompPos, io, MainComp->Width, MainComp->Height); - - ImGui::End(); -} - #include "keybinds.h" static void @@ -1051,6 +440,9 @@ ImGui_ProcessInputs(project_data *File, project_state *State, ui *UI, memory *Me if (ImGui::IsKeyPressed(ImGuiKey_B)) { State->Tool = tool_brush; } + if (ImGui::IsKeyPressed(ImGuiKey_D)) { + State->Tool = tool_pen; + } // NOTE(fox): File data not tracked on undo tree! if (ImGui::IsKeyPressed(ImGuiKey_N)) { if (io.KeyShift) { diff --git a/src/imgui_ui_stable_diffusion.cpp b/src/imgui_ui_stable_diffusion.cpp index a5c93c6..e6ef8cc 100644 --- a/src/imgui_ui_stable_diffusion.cpp +++ b/src/imgui_ui_stable_diffusion.cpp @@ -68,7 +68,7 @@ ImGui_SD_Thumbnail(project_data *File, project_state *State, ui *UI, memory *Mem } if (ImGui::BeginPopup("temptosource")) { if (ImGui::MenuItem("Create layer from source")) { - State->HotkeyInput = hotkey_newlayerfromsource; + State->HotkeyInput = hotkey_newlayer_source; } ImGui::EndPopup(); } diff --git a/src/imgui_ui_viewport.cpp b/src/imgui_ui_viewport.cpp new file mode 100644 index 0000000..e21c1a6 --- /dev/null +++ b/src/imgui_ui_viewport.cpp @@ -0,0 +1,715 @@ + +static void +ImGui_Viewport_Toolbar(project_state *State, ImDrawList *draw_list) +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + // ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(50, 50, 50, 0)); + + real32 IconSize = ImGui::GetFontSize() * 4; + int ToolCount = (int)tool_count; + ImVec2 ButtonSize(IconSize, IconSize); + ImVec2 WindowSize(IconSize, IconSize * ToolCount); + ImGui::BeginChild("Toolbar", WindowSize, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar); + for (int i = 0; i < ToolCount; i++) { + ImGui::PushID(i); + // draw_list->AddImage((void *)(intptr_t)State->ToolIconTex[i], Min, Max); + if ((int)State->Tool == i) { + ImVec2 Min = ImGui::GetCursorScreenPos(); + ImVec2 Max = Min + ButtonSize; + draw_list->AddRectFilled(Min, Max, IM_COL32(255, 255, 255, 128)); + } + ImGui::Button(ToolName[i], ButtonSize); + if (ImGui::IsItemActivated()) { + State->Tool = (tool)i; + } + ImGui::PopID(); + } + ImGui::EndChild(); + + // ImGui::PopStyleColor(); + ImGui::PopStyleVar(2); +} + +static void +ImGui_Viewport_BrushUI(project_state *State, memory *Memory, ImVec2 ViewportMin, ImVec2 ViewportMax, ImVec2 CompZoom, ImGuiIO io, uint16 Width, uint16 Height) +{ + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + if (State->Tool == tool_brush) { + + if (ImGui::IsKeyPressed(ImGuiKey_ModAlt, false)) { + State->Brush.UIPos = io.MousePos; + } + + ImVec2 CompScale = CompZoom / ImVec2(Width, Height); + ImVec2 BrushSize = CompScale * State->Brush.Size; + ImVec2 MinBounds = State->Brush.UIPos - BrushSize/2; + ImVec2 MaxBounds = MinBounds + BrushSize; + + // if (io.KeyCtrl) { + // ImGui::SetCursorScreenPos(State->Brush.UIPos); + // char buf[256]; + // sprintf(buf, "RGBA: %.1f, %.1f, %.1f, %.1f", State->Brush.Size, State->Brush.Hardness); + // } + + if (io.KeyAlt) { + draw_list->PushClipRect(ViewportMin, ViewportMax, true); + draw_list->AddImage((void *)(intptr_t)State->Brush.GLTexture, MinBounds, MaxBounds, ImVec2(0, 0), ImVec2(1, 1), 1); + draw_list->PopClipRect(); + ImGui::SetCursorScreenPos(State->Brush.UIPos); + char buf[256]; + sprintf(buf, "Size: %.1f, Hardness: %.1f", State->Brush.Size, State->Brush.Hardness); + ImGui::Text(buf); + if (io.MouseDelta.x || io.MouseDelta.y) { + ImVec2 Delta = io.MouseDelta; + State->Brush.Size += Delta.x; + State->Brush.Hardness += Delta.y*State->Brush.Hardness/100; + if (State->Brush.Size < 0) + State->Brush.Size = 0; + if (State->Brush.Size > 1024) + State->Brush.Size = 1024; + if (State->Brush.Hardness < 0.5) + State->Brush.Hardness = 0.5; + if (State->Brush.Hardness > 100) + State->Brush.Hardness = 100; + Brush_CalcBitmapAlphaFromSize(Memory, &State->Brush, 4); + State_BindBrushTexture(Memory, &State->Brush, 4); + } + } + } +} + +static void +ImGui_Viewport_TransformUI(project_data *File, project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list, ImGuiIO &io, + interact_transform *Interact, ImVec2 ViewportMin, uint32 CompWidth, uint32 CompHeight, uint16 *SortedKeyframeArray) +{ + v2 InteractMin = Interact->Min + Interact->Position; + v2 InteractMax = Interact->Max + Interact->Position; + + v2 BoxLength = InteractMax - InteractMin; + v2 Center = InteractMax - (BoxLength/2); + + real32 Point0X = Center.x - InteractMin.x; + real32 Point0Y = Center.y - InteractMin.y; + + real32 Rad = Interact->Radians; + + v2 XAxis = (Point0X * Interact->Scale)*V2(cos(Rad), sin(Rad)); + v2 YAxis = (Point0Y * -Interact->Scale)*V2(sin(Rad), -cos(Rad)); + + // Points are clockwise starting from the top left. + real32 X0 = -XAxis.x - YAxis.x + Center.x; + real32 Y0 = -XAxis.y - YAxis.y + Center.y; + real32 X1 = X0 + XAxis.x*2; + real32 Y1 = Y0 + XAxis.y*2; + real32 X2 = X1 + YAxis.x*2; + real32 Y2 = Y1 + YAxis.y*2; + real32 X3 = X2 - XAxis.x*2; + real32 Y3 = Y2 - XAxis.y*2; + + // Midway points. + real32 Mid_X0 = X0 + XAxis.x; + real32 Mid_Y0 = Y0 + XAxis.y; + real32 Mid_X1 = X1 + YAxis.x; + real32 Mid_Y1 = Y1 + YAxis.y; + real32 Mid_X2 = X2 - XAxis.x; + real32 Mid_Y2 = Y2 - XAxis.y; + real32 Mid_X3 = X3 - YAxis.x; + real32 Mid_Y3 = Y3 - YAxis.y; + + ImVec2 CompScale = UI->CompZoom / ImVec2(CompWidth, CompHeight); + + ImVec2 P[4]; + P[0] = ImVec2(X0, Y0)*CompScale + UI->CompPos; + P[1] = ImVec2(X1, Y1)*CompScale + UI->CompPos; + P[2] = ImVec2(X2, Y2)*CompScale + UI->CompPos; + P[3] = ImVec2(X3, Y3)*CompScale + UI->CompPos; + + ImVec2 Mid_P[4]; + Mid_P[0] = ImVec2(Mid_X0, Mid_Y0)*CompScale + UI->CompPos; + Mid_P[1] = ImVec2(Mid_X1, Mid_Y1)*CompScale + UI->CompPos; + Mid_P[2] = ImVec2(Mid_X2, Mid_Y2)*CompScale + UI->CompPos; + Mid_P[3] = ImVec2(Mid_X3, Mid_Y3)*CompScale + UI->CompPos; + + ImU32 wcol = ImGui::GetColorU32(ImGuiCol_Text); + draw_list->AddLine(P[0], P[1], wcol, 2.0f); + draw_list->AddLine(P[1], P[2], wcol, 2.0f); + draw_list->AddLine(P[2], P[3], wcol, 2.0f); + draw_list->AddLine(P[3], P[0], wcol, 2.0f); + + v2 XAxis2 = (BoxLength*CompScale.x)*V2(cos(Rad), sin(Rad)); + v2 YAxis2 = (BoxLength*CompScale.y)*V2(sin(Rad), -cos(Rad)); + + v2 XAxisPerp = (1.0f / LengthSq(XAxis))*XAxis; + v2 YAxisPerp = (1.0f / LengthSq(YAxis))*YAxis; + + // real32 LocalX = ((io.MousePos.x - UI->CompPos.x) - Center.x) ; + // real32 LocalY = ((io.MousePos.y - UI->CompPos.y) - Center.y) ; + layer_transforms BoxTransforms = { Center.x, Center.y, 0.5, 0.5, (real32)(Interact->Radians / (PI / 180)), Interact->Scale }; + v2 LayerPoint = Transform_ScreenSpaceToLocal(BoxTransforms, CompWidth, CompHeight, BoxLength.x, BoxLength.y, UI->CompPos, UI->CompZoom, ViewportMin, io.MousePos); + + real32 U = LayerPoint.x / BoxLength.x; + real32 V = LayerPoint.y / BoxLength.y; + + ImVec2 ScaleHandleSize(50, 50); + + bool32 OtherActions = ImGui::IsKeyDown(ImGuiKey_Z); + + // First do the halfway scale points, since they don't need UVs considered: + for (int i = 0; i < 4; i++) { + ImGui::SetCursorScreenPos(Mid_P[i] - ScaleHandleSize/2); + ImGui::PushID(i); + + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertFloat4ToU32(ImVec4(0.6f, 0.0f, 0.3f, 1.0f))); + ImGui::Button("##ScaleMids", ScaleHandleSize); + ImGui::PopStyleColor(); + + if (ImGui::IsItemActivated() && !OtherActions) { + State->InteractTransformMode = 1; + } + + if (State->InteractTransformMode == 1 && ImGui::IsItemActive()) + { + uint32 side = i; + if (side == 0) { + Interact->Scale -= io.MouseDelta.y / BoxLength.y; + Interact->Position.y += io.MouseDelta.y / 2; + } else if (side == 1) { + Interact->Scale += io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + } else if (side == 2) { + Interact->Scale += io.MouseDelta.y / BoxLength.y; + Interact->Position.y += io.MouseDelta.y / 2; + } else if (side == 3) { + Interact->Scale -= io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + } + } + ImGui::PopID(); + } + + bool32 InBounds = false; + // Scale if cursor is on button within the UV, rotate if outside UV, and position if a non-button is dragged. + if (U >= 0.0f && U <= 1.0f && V >= 0.0f && V <= 1.0f) + { + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertFloat4ToU32(ImVec4(0.6f, 0.0f, 0.3f, 1.0f))); + InBounds = true; + } + + for (int i = 0; i < 4; i++) { + ImGui::SetCursorScreenPos(P[i] - ScaleHandleSize/2); + ImGui::PushID(i); + ImGui::Button("##ScaleRotateCorners", ScaleHandleSize); + + if (ImGui::IsItemActivated() && !OtherActions) { + if (InBounds) + State->InteractTransformMode = 1; + else + State->InteractTransformMode = 2; + } + + // Scale part + if (State->InteractTransformMode == 1 && ImGui::IsItemActive()) + { + // TODO(fox): Corner dragging scale only works in the X + // axis. Mostly feels right when dragged how you expect, + // but I'll fix it if someone complains. + uint32 side = i; + if (side == 0) { + Interact->Scale -= io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + Interact->Position.y += io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; + } else if (side == 1) { + Interact->Scale += io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + Interact->Position.y -= io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; + } else if (side == 2) { + Interact->Scale += io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + Interact->Position.y += io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; + } else if (side == 3) { + Interact->Scale -= io.MouseDelta.x / BoxLength.x; + Interact->Position.x += io.MouseDelta.x / 2; + Interact->Position.y -= io.MouseDelta.x*(BoxLength.y/BoxLength.x) / 2; + } + } + + // Rotation part + if (State->InteractTransformMode == 2 && ImGui::IsItemActive()) + { + real32 LocalX = (io.MousePos.x - UI->CompPos.x)/CompScale.x - InteractMin.x - (BoxLength.x/2); + real32 LocalY = (io.MousePos.y - UI->CompPos.y)/CompScale.y - InteractMin.y - (BoxLength.y/2); + + real32 Slope_Mouse = LocalY/LocalX; + real32 Slope_Corner = 0; + real32 Slope_Flipped = 0; + real32 Dot = 0; + + // TODO(fox) learn basic geometry to do this properly + + // We find the angle between the direction of whichever corner the + // mouse is grabbing (Slope_Corner) and the mouse's current + // position (Slope_Mouse) to get ExtraRadians. The calculation only + // works between -90 and 90, so I take the dot product of the + // opposite edge of the corner and add the extra degrees when it's negative. + + v2 SlopeDot = V2(BoxLength.x, BoxLength.y); + // top left clockwise + uint32 side = i; + if (side == 0) { + Slope_Corner = BoxLength.y / BoxLength.x; + Slope_Flipped = -BoxLength.x / BoxLength.y; + Dot = LocalX * -SlopeDot.x + LocalY * -SlopeDot.y; + } else if (side == 1) { + Slope_Corner = -BoxLength.y / BoxLength.x; + Slope_Flipped = BoxLength.x / BoxLength.y; + Dot = LocalX * SlopeDot.x + LocalY * -SlopeDot.y; + } else if (side == 2) { + Slope_Corner = BoxLength.y / BoxLength.x; + Slope_Flipped = -BoxLength.x / BoxLength.y; + Dot = LocalX * SlopeDot.x + LocalY * SlopeDot.y; + } else if (side == 3) { + Slope_Corner = -BoxLength.y / BoxLength.x; + Slope_Flipped = BoxLength.x / BoxLength.y; + Dot = LocalX * -SlopeDot.x + LocalY * SlopeDot.y; + } + + Interact->Radians = atan((Slope_Mouse - Slope_Corner) / (1 + Slope_Mouse * Slope_Corner)); + real32 ExtraRadians2 = atan((Slope_Mouse - Slope_Flipped) / (1 + Slope_Mouse * Slope_Flipped)); + + if (Dot < 0) { + if (Interact->Radians < 0) { + Interact->Radians = (90 * (PI / 180)) + ExtraRadians2; + } else { + Interact->Radians = (-90 * (PI / 180)) + ExtraRadians2; + } + } + } + + ImGui::PopID(); + } + + if (!State->InteractTransformMode && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && InBounds && !OtherActions) + State->InteractTransformMode = 3; + + if (State->InteractTransformMode == 3) { + Interact->Position.x += (real32)io.MouseDelta.x/CompScale.x; + Interact->Position.y += (real32)io.MouseDelta.y/CompScale.y; + } + + if (State->InteractTransformMode) + { + if (io.MouseDelta.x || io.MouseDelta.y) + State->UpdateFrame = true; + if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) + State->InteractTransformMode = 0; + } + + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + State->Interact_Active = interact_type_none; + State->Interact_Modifier = 0; + State->UpdateFrame = true; + Memory->PurgeCache = true; + } + + // Second condition so you don't have to reach for Enter. + if (ImGui::IsKeyPressed(ImGuiKey_Enter) || (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && io.KeyCtrl)) { + int h = 0, c = 0, i = 0; + if (!io.KeyCtrl) + History_Entry_Commit(Memory, "Transform layers"); + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (Layer->IsSelected == 1) { + if (io.KeyCtrl) { + layer_transforms T = Layer_GetTransforms(Layer); + Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &T.x, &T.y, &T.rotation, &T.scale); + property_channel *Property[4] = { &Layer->x, &Layer->y, &Layer->rotation, &Layer->scale }; + real32 Val[4] = { T.x, T.y, T.rotation, T.scale }; + for (int a = 0; a < 4; a++) { + if (Property[a]->CurrentValue != Val[a]) { + History_Entry_Commit(Memory, "Add keyframe"); + bezier_point Point = { 1, {(real32)State->Frame_Current, Val[a], -1, 0, 1, 0}, interpolation_type_linear, 0, {0, 0, 0}, 0 }; + uint16 *ArrayLocation = Property_GetSortedArray(SortedKeyframeArray, State->MostRecentlySelectedLayer, h); + Bezier_Add(Memory, F_Layers, Property[a], Point, ArrayLocation); + History_Entry_End(Memory); + } + } + } else { + History_Action_Swap(Memory, F_File, sizeof(Layer->x.CurrentValue), &Layer->x.CurrentValue); + History_Action_Swap(Memory, F_File, sizeof(Layer->y.CurrentValue), &Layer->y.CurrentValue); + History_Action_Swap(Memory, F_File, sizeof(Layer->scale.CurrentValue), &Layer->scale.CurrentValue); + History_Action_Swap(Memory, F_File, sizeof(Layer->rotation.CurrentValue), &Layer->rotation.CurrentValue); + Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &Layer->x.CurrentValue, &Layer->y.CurrentValue, &Layer->rotation.CurrentValue, &Layer->scale.CurrentValue); + } + } + } + if (!io.KeyCtrl) + History_Entry_End(Memory); + State->Interact_Active = interact_type_none; + State->Interact_Modifier = 0; + State->UncommitedKeyframe = 1; + State->UpdateFrame = true; + } + + if (InBounds == true) { + ImGui::PopStyleColor(); + } + +} + +static void +ImGui_Viewport_SelectedLayerUI(project_state *State, memory *Memory, ui *UI, ImDrawList *draw_list, block_composition *MainComp, uint32 CompIndex, block_layer *ParentLayer[4], uint32 Recursions, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray) +{ + sorted_comp_array *SortedCompStart = &SortedCompArray[CompIndex]; + sorted_layer_array *SortedLayerStart = Sorted_GetLayerStart(SortedLayerArray, SortedCompArray, CompIndex); + ImU32 wcol = ImGui::GetColorU32(ImGuiCol_Text); + for (int i = 0; i < SortedCompStart->LayerCount; i++) + { + sorted_layer_array SortEntry = SortedLayerStart[i]; + uint32 Index_Physical = SortEntry.Block_Layer_Index; + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, Index_Physical); + if (Layer->IsPrecomp) { + ParentLayer[Recursions] = Layer; + ImGui_Viewport_SelectedLayerUI(State, Memory, UI, draw_list, MainComp, Layer->Block_Source_Index, ParentLayer, Recursions + 1, SortedCompArray, SortedLayerArray); + } + if (Layer->IsSelected) { + uint32 Width = 0, Height = 0; + void *Data; + uint32 NumberOfVerts; + if (Layer->IsPrecomp) { + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + Width = Comp->Width; + Height = Comp->Height; + } else if (Layer->IsShapeLayer) { + block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Layer->Shape.Block_Bezier_Index[0]); + Data = Memory_PushScratch(Memory, sizeof(nvg_point) * 128); + int L_Width = 0, L_Height = 0; + NumberOfVerts = NVG_FlattenPath(Memory, Bezier, 3, (nvg_point *)Data, &L_Width, &L_Height); + Width = L_Width; + Height = L_Height; + } else { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + Width = Source->Width; + Height = Source->Height; + } + + v2 Point[5] = { V2(Width*Layer->ax.CurrentValue, Height*Layer->ay.CurrentValue), V2(0, 0), V2(Width, 0), V2(0, Height), V2(Width, Height) }; + + layer_transforms T = Layer_GetTransforms(Layer); + + if (State->Interact_Active == interact_type_viewport_transform && Layer->IsSelected == 1) { + Transform_ApplyInteractive(*(interact_transform *)&State->Interact_Offset[0], &T.x, &T.y, &T.rotation, &T.scale); + } + + if (Layer->IsShapeLayer) { + block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Layer->Shape.Block_Bezier_Index[0]); + // for (int i = 0; i < Layer->Shape.Point_Count; i++) { + // v2 ThisPoint = Bezier->Point[i].Pos[0]; + // v2 Pos = TransformPoint(T, Width, Height, ThisPoint); + // v2 CompUV = Pos / V2(MainComp->Width, MainComp->Height); + // ImVec2 ScreenPoint = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, + // UI->CompPos.y + CompUV.y * UI->CompZoom.y); + // draw_list->AddNgon(ScreenPoint, 10, IM_COL32(10, 10, 10, 255), 8, 9.0f); + // } + + // imgui code + + void *Data2 = Memory_PushScratch(Memory, sizeof(real32) * 3 * 256); + uint32 GL_PointCount = NVG_ExpandStroke(Memory, Bezier, NumberOfVerts, (nvg_point *)Data, (real32 *)Data2); + + v2 L_Pos[4] = { Bezier->Point[0].Pos[0], Bezier->Point[0].Pos[2], Bezier->Point[1].Pos[1], Bezier->Point[1].Pos[0] }; + L_Pos[1] = L_Pos[1] + L_Pos[0]; + L_Pos[2] = L_Pos[2] + L_Pos[3]; + ImVec2 ScreenPoint[4]; + for (int i = 0; i < 4; i++) { + v2 Pos = TransformPoint(T, Width, Height, L_Pos[i]); + v2 CompUV = Pos / V2(MainComp->Width, MainComp->Height); + ScreenPoint[i] = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, + UI->CompPos.y + CompUV.y * UI->CompZoom.y); + } + draw_list->AddNgon(ScreenPoint[0], 10, IM_COL32(10, 10, 10, 255), 8, 2.0f); + draw_list->AddNgon(ScreenPoint[3], 10, IM_COL32(10, 10, 10, 255), 8, 2.0f); + draw_list->AddBezierCubic(ScreenPoint[0],ScreenPoint[1],ScreenPoint[2],ScreenPoint[3], IM_COL32(10, 10, 10, 255), 1.0f, 0); + + // test code + + for (int i = 0; i < NumberOfVerts; i++) { + nvg_point Point = *((nvg_point *)Data + i); + v2 PointPos = V2(Point.x, Point.y); + v2 Pos = TransformPoint(T, Width, Height, PointPos); + v2 CompUV = Pos / V2(MainComp->Width, MainComp->Height); + ImVec2 ScreenPoint = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, + UI->CompPos.y + CompUV.y * UI->CompZoom.y); + draw_list->AddNgon(ScreenPoint, 2, IM_COL32(00, 00, 80, 255), 8, 2.0f); + } + for (int i = 0; i < GL_PointCount; i++) { + v2 PointPos = *((v2 *)Data2 + i*2); + v2 Pos = TransformPoint(T, Width, Height, PointPos); + v2 CompUV = Pos / V2(MainComp->Width, MainComp->Height); + ImVec2 ScreenPoint = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, + UI->CompPos.y + CompUV.y * UI->CompZoom.y); + draw_list->AddNgon(ScreenPoint, 2, IM_COL32(80, 80, 10, 255), 8, 2.0f); + } + Memory_PopScratch(Memory, sizeof(real32) * 3 * 256); + Memory_PopScratch(Memory, sizeof(nvg_point) * 128); + } + + v2 NewPos[5]; + for (int i = 0; i < 5; i++) { + NewPos[i] = TransformPoint(T, Width, Height, Point[i]); + } + + int i = 0; + while (i < Recursions) { + T = Layer_GetTransforms(ParentLayer[i]); + block_composition *Comp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, ParentLayer[i]->Block_Source_Index); + Width = Comp->Width; + Height = Comp->Height; + for (int i = 0; i < 5; i++) { + NewPos[i] = TransformPoint(T, Width, Height, NewPos[i]); + } + i++; + } + + ImVec2 ScreenPoint[5]; + for (int i = 0; i < 5; i++) { + v2 CompUV = NewPos[i] / V2(MainComp->Width, MainComp->Height); + + ScreenPoint[i] = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, + UI->CompPos.y + CompUV.y * UI->CompZoom.y); + + } + if (State->Tool != tool_brush) { + ImU32 wcol2 = IM_COL32(10, 10, 10, 128); + draw_list->AddNgon(ScreenPoint[0], 10, wcol2, 8, 9.0f); + draw_list->AddNgon(ScreenPoint[0], 10, wcol, 8, 5.0f); + } + draw_list->AddLine(ScreenPoint[1], ScreenPoint[2], wcol, 2.0f); + draw_list->AddLine(ScreenPoint[2], ScreenPoint[4], wcol, 2.0f); + draw_list->AddLine(ScreenPoint[1], ScreenPoint[3], wcol, 2.0f); + draw_list->AddLine(ScreenPoint[3], ScreenPoint[4], wcol, 2.0f); + } + } +} + +static void +ImGui_Viewport(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, GLuint textureID, + sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, uint16 *SortedKeyframeArray) +{ + bool open = true; + ImGui::Begin("Viewport", &open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + + if (ImGui::IsWindowHovered(ImGuiFocusedFlags_ChildWindows)) { + State->FocusedWindow = focus_viewport; + } + + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, File->PrincipalCompIndex); + + ImVec2 ViewportMin = ImGui::GetCursorScreenPos(); + ImVec2 ViewportScale = ImGui::GetContentRegionAvail(); + ImVec2 ViewportMax = ImVec2(ViewportMin.x + ViewportScale.x, ViewportMin.y + ViewportScale.y); + + if (ViewportScale.x < 50 || ViewportScale.y < 50) { + ImGui::End(); + return; + } + + if (State->Initializing) { + UI->CompZoom = ImVec2(MainComp->Width, MainComp->Height); + UI->CompPos = ImVec2(ViewportMin.x + ((ViewportMax.x - ViewportMin.x)/2 - UI->CompZoom.x/2), + ViewportMin.y + ((ViewportMax.y - ViewportMin.y)/2 - UI->CompZoom.y/2)); + } + + ImVec2 CompPosMin = ImVec2(UI->CompPos.x, UI->CompPos.y); + ImVec2 CompPosMax = ImVec2(UI->CompPos.x + UI->CompZoom.x, UI->CompPos.y + UI->CompZoom.y); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(ViewportMin, ViewportMax, IM_COL32(50, 50, 50, 255)); + draw_list->AddRect(ViewportMin, ViewportMax, IM_COL32(255, 255, 255, 255)); + draw_list->AddRect(CompPosMin, CompPosMax, IM_COL32(255, 255, 255, 55)); + + real32 FontSize = ImGui::GetFontSize(); + ImGui::SetCursorScreenPos(ImVec2(ViewportMax.x - FontSize*2, ViewportMin.y + ViewportScale.y - FontSize*3.0)); + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 80)); + if (ImGui::Button("?")) + State->ImGuiPopups = popup_keybinds; + ImGui::PopStyleColor(); + + + // Actual composition texture + draw_list->PushClipRect(ViewportMin, ViewportMax, true); + draw_list->AddImage((void *)(intptr_t)textureID, CompPosMin, CompPosMax); + draw_list->PopClipRect(); + + // UI+interaction for layer + if (State->MostRecentlySelectedLayer > -1) + { + block_layer *ParentLayer[4]; + ImGui_Viewport_SelectedLayerUI(State, Memory, UI, draw_list, MainComp, File->PrincipalCompIndex, ParentLayer, 0, SortedCompArray, SortedLayerArray); + if (State->Interact_Active == interact_type_viewport_transform) { + ImGui_Viewport_TransformUI(File, State, Memory, UI, draw_list, io, (interact_transform *)&State->Interact_Offset[0], ViewportMin, MainComp->Width, MainComp->Height, SortedKeyframeArray); + } + } + + + + // Interactions for dragging and zooming + ImGui::SetCursorScreenPos(ViewportMin); + + ImGui::InvisibleButton("canvas", ViewportScale, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); + bool32 IsHovered = ImGui::IsItemHovered(); +#if 1 + bool32 IsActive = ImGui::IsItemActive(); + bool32 IsActivated = ImGui::IsItemActivated(); + bool32 IsDeactivated = ImGui::IsItemDeactivated(); +#else + bool32 IsActive = ImGui::IsKeyDown(ImGuiKey_3); + bool32 IsActivated = ImGui::IsKeyPressed(ImGuiKey_3); + bool32 IsDeactivated = ImGui::IsKeyReleased(ImGuiKey_3); +#endif + + if (IsHovered && IsActivated && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) + { + State->TempZoomRatio = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); + + if (!ImGui::IsKeyDown(ImGuiKey_Z)) { + if (State->Tool == tool_brush && State->Interact_Active != interact_type_brush) { + if (!io.KeyCtrl) { + int h = 0, c = 0, i = 0; + while (Block_Loop(Memory, F_Layers, File->Layer_Count, &h, &c, &i)) + { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, i); + if (!Layer->IsPrecomp) { + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + if (Layer->IsSelected && Source->Type == source_type_principal) { + Assert(Source->BytesPerPixel == 4); + Arbitrary_Zero((uint8 *)State->Brush.TransientBitmap, 2048*2048*4); + State->Interact_Active = interact_type_brush; + State->Brush.LayerToPaint_Index = i; + break; + } + } + } + } + if (State->Brush.LayerToPaint_Index == -1) { + State->HotkeyInput = hotkey_newlayer_paint; + } + } + // Layer selection + if (State->Tool == tool_default && State->Interact_Active == interact_type_none) { + int32 Selection = Layer_TestSelection(Memory, State, UI, SortedCompArray, SortedLayerArray, File->PrincipalCompIndex); + if (!io.KeyShift && State->Interact_Active == interact_type_none) + Layer_DeselectAll(File, State, Memory); + if (Selection != -1) + Layer_Select(Memory, State, Selection); + } + } + } + + /* + if (State->Interact_Active == interact_type_viewport_transform) { + interact_transform *Interact = (interact_transform *)&State->Interact_Offset[0]; + ImVec2 DragDelta = io.MousePos - Interact->OGPos; + Interact->Position = V2(DragDelta.x, DragDelta.y); + if (io.MouseDelta.x || io.MouseDelta.y) + State->UpdateFrame = true; + } + */ + + if (IsActive && ImGui::IsMouseDragging(ImGuiMouseButton_Right, -1.0f)) + { + UI->CompPos.x += io.MouseDelta.x; + UI->CompPos.y += io.MouseDelta.y; + } + + + bool32 OtherActions = ImGui::IsKeyDown(ImGuiKey_Z) || ImGui::IsMouseDown(ImGuiMouseButton_Right); + if (State->Tool == tool_brush && State->Interact_Active == interact_type_brush) { + Assert(State->Brush.LayerToPaint_Index != -1); + if (IsActive && !OtherActions) { + block_layer *Layer = (block_layer *)Memory_Block_AddressAtIndex(Memory, F_Layers, State->Brush.LayerToPaint_Index); + layer_transforms T_Layer = Layer_GetTransforms(Layer); + block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); + ImVec2 MouseDelta = io.MouseDelta; + real32 Delta = MouseDelta.x + MouseDelta.y; + v2 PrincipalCompUV = ImGui_ScreenPointToCompUV(ViewportMin, UI->CompPos, UI->CompZoom, io.MousePos); + v2 LayerPos = Layer_TraverseForPoint(File, State, Memory, PrincipalCompUV, SortedCompArray, SortedLayerArray); + if (IsActivated) { + RenderQueue_AddBrush(State, LayerPos); + } else if (Delta != 0.0f) { + v2 PrevPos = State->Brush.PrevPos; + v2 Delta = PrevPos - LayerPos; + real32 Dist = sqrt(LengthSq(Delta)); + if (Dist > State->Brush.Spacing) { + RenderQueue_AddBrush(State, LayerPos); + } + } + State->UpdateFrame = true; + } + + if (IsDeactivated) { + RenderQueue_AddBlit(State); + } + } + if (State->Tool == tool_pen && State->Interact_Active == interact_type_none && State->MostRecentlySelectedLayer == -1) { + v2 CompUV = State->TempZoomRatio; + ImVec2 ScreenPoint = ImVec2(UI->CompPos.x + CompUV.x * UI->CompZoom.x, + UI->CompPos.y + CompUV.y * UI->CompZoom.y); + ImVec2 Vector = io.MousePos - ScreenPoint; + if (IsActive && !OtherActions) { + uint32 wcol = IM_COL32(00, 00, 80, 255); + draw_list->AddLine(ScreenPoint - Vector, io.MousePos, wcol, 2.0f); + draw_list->AddNgon(ScreenPoint, 2, wcol, 8, 2.0f); + } + if (IsDeactivated) { + State->HotkeyInput = hotkey_newlayer_shape; + if (fabs(Vector.x) > 5 && fabs(Vector.y) > 5) { + State->HotkeyExtra[0] = Vector.x; + State->HotkeyExtra[1] = Vector.y; + } + } + } + + if (ImGui::IsKeyDown(ImGuiKey_Z) && ImGui::IsWindowHovered()) { + if (IsActive) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); + else + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + + real32 Distance = 0; + if (IsActive) { + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, -1.0f)) + Distance = io.MouseDelta.x + io.MouseDelta.y; + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + Distance = 200; + } + if (Distance && ImGui::IsKeyDown(ImGuiKey_Z)) + { + if (io.KeyShift) + Distance *= -1; + UI->CompZoom.x += (Distance)*(real32)MainComp->Width/MainComp->Height; + UI->CompZoom.y += (Distance); + UI->CompPos.x -= ((Distance)*(real32)MainComp->Width/MainComp->Height)*State->TempZoomRatio.x; + UI->CompPos.y -= Distance*State->TempZoomRatio.y; + } + + ImGui::SetCursorScreenPos(ImVec2(ViewportMin.x, ViewportMin.y + ViewportScale.y - FontSize*1.5)); + + ImGui::Text("%.1f", 100.0f * (UI->CompZoom.x / MainComp->Width)); + if (State->MsgTime > 0) { + ImGui::SameLine(); + ImGui::SetCursorPosX((ViewportScale.x / 5)*4); + ImGui::Text(State->Msg); + State->MsgTime--; + } + + ImGui::SetCursorScreenPos(ViewportMin); + ImGui_Viewport_Toolbar(State, draw_list); + ImGui_Viewport_BrushUI(State, Memory, ViewportMin, ViewportMax, UI->CompPos, io, MainComp->Width, MainComp->Height); + + ImGui::End(); +} diff --git a/src/include/functions.h b/src/include/functions.h index 23d741e..53920cb 100644 --- a/src/include/functions.h +++ b/src/include/functions.h @@ -16,6 +16,11 @@ static void Bezier_Interact_Evaluate(project_state *State, bezier_poin // NOTE(fox): GraphZoomHeight and Y_Increment don't have to be specified if the Y value isn't needed, i.e. in Property_SortAll(). static void Bezier_Add(memory *Memory, memory_table_list TableName, property_channel *Property, bezier_point PointData, uint16 *ArrayLocation); +uint32 Bezier_CubicCalcPoints(v2 p1, v2 p2, v2 p3, v2 p4, void *Data, uint32 Size); + + +void GL_RasterizeShape(gl_effect_layer *TestL, gl_effect_layer *TestM, void *PointData, uint32 GL_PointCount, + layer_transforms T, int Width, int Height, int BytesPerPixel, void *EffectBitmapAddress, int L_Width, int L_Height); static void ImGui_ProcessInputs(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_file Sorted); static void ImGui_PropertiesPanel(project_data *File, project_state *State, ui *UI, memory *Memory, ImGuiIO io, sorted_comp_array *SortedCompArray, sorted_layer_array *SortedLayerArray, sorted_property_array *SortedPropertyStart, uint16 *SortedKeyframeArray); diff --git a/src/include/gl_calls.h b/src/include/gl_calls.h index fa0a00c..3c2bef0 100644 --- a/src/include/gl_calls.h +++ b/src/include/gl_calls.h @@ -10,6 +10,7 @@ struct gl_vertex_shader { }; static default_gl_vertex_object DefaultVerts; +static default_gl_vertex_object ShapeVerts; static gl_vertex_shader GL_DefaultVertexObjects; static uint32 DefaultVertexShader; static uint32 DefaultShaderProgram; diff --git a/src/include/keybinds.h b/src/include/keybinds.h index 3fe5f64..7ea0499 100644 --- a/src/include/keybinds.h +++ b/src/include/keybinds.h @@ -30,6 +30,7 @@ static shortcut_entry ShortcutArray[] { { ImGuiKey_E, Mod_None, key_mode_all, "Step forward one frame" }, { ImGuiKey_V, Mod_None, key_mode_all, "Move tool" }, { ImGuiKey_B, Mod_None, key_mode_all, "Brush tool" }, + { ImGuiKey_D, Mod_None, key_mode_all, "Pen tool" }, { ImGuiKey_Space, Mod_None, key_mode_all, "Play scene" }, { ImGuiKey_Delete, Mod_None, key_mode_all, "Delete selection (WIP)" }, { ImGuiKey_S, Mod_Ctrl, key_mode_all, "Save" }, diff --git a/src/include/main.h b/src/include/main.h index fd295cf..8590904 100644 --- a/src/include/main.h +++ b/src/include/main.h @@ -183,6 +183,23 @@ struct sorted_file uint64 Source_SortSize; }; +struct shape_options { + int Visibility; + v4 FillCol = {1, 1, 1, 1}; + v4 StrokeCol = {1, 1, 1, 1}; + float StrokeWidth; +}; + +struct shape_layer { + uint16 Block_Bezier_Index[MAX_KEYFRAME_BLOCKS]; + uint16 Block_Bezier_Count; + uint16 Point_Count; + bool32 IsClosed; + int Width; + int Height; + shape_options Opt; +}; + enum timeline_mode { timeline_mode_default, @@ -190,7 +207,7 @@ enum timeline_mode }; struct pen_state { - bool32 IsActive; + shape_options Opt; }; struct brush_state @@ -251,8 +268,9 @@ struct interact_transform enum hotkey_input { hotkey_none, - hotkey_newpaintlayer, - hotkey_newlayerfromsource, + hotkey_newlayer_source, + hotkey_newlayer_paint, + hotkey_newlayer_shape, hotkey_deletelayer, hotkey_undo, hotkey_redo, @@ -360,6 +378,7 @@ struct project_state int32 PreviewSource = -1; hotkey_input HotkeyInput; + real32 HotkeyExtra[4]; // bloat? void *Dump1; void *Dump2; @@ -564,6 +583,7 @@ struct block_layer { uint8 Occupied; bool32 IsPrecomp; + bool32 IsShapeLayer; bool32 Precomp_Toggled; // NOTE(fox): References a precomp index if IsPrecomp is true, not a source index. uint16 Block_Source_Index; @@ -571,12 +591,11 @@ struct block_layer { // References the precomp that the layer belongs to. uint16 Block_Composition_Index; - uint16 Block_Mask_Index[MAX_MASKS]; - uint16 Block_Mask_Count; - uint16 Block_Effect_Index[MAX_EFFECTS]; uint16 Block_Effect_Count; + shape_layer Shape; + blend_mode BlendMode; union diff --git a/src/layer.cpp b/src/layer.cpp index 41b52cc..c3d3d16 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -94,7 +94,7 @@ Layer_GetTopOffset(project_data *File, memory *Memory) static void Layer_UpdateMasksEffects(project_state *State, block_layer *Layer, memory *Memory, void *EffectBitmapAddress, - int Width, int Height, int BytesPerPixel) + int Width, int Height, int BytesPerPixel, int *L_Width, int *L_Height) { uint64 Size = Width*Height*BytesPerPixel; @@ -106,6 +106,22 @@ Layer_UpdateMasksEffects(project_state *State, block_layer *Layer, memory *Memor GL_UpdateTexture(&TestL, EffectBitmapAddress, Width, Height, BytesPerPixel, 0); GL_UpdateTexture(&TestM, EffectBitmapAddress, Width, Height, BytesPerPixel, 1); + if (Layer->IsShapeLayer) { + block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(Memory, F_Bezier, Layer->Shape.Block_Bezier_Index[0]); + void *Data = Memory_PushScratch(Memory, sizeof(nvg_point) * 128); + uint32 NumberOfVerts = NVG_FlattenPath(Memory, Bezier, 3, (nvg_point *)Data, L_Width, L_Height); + void *Data2 = Memory_PushScratch(Memory, sizeof(real32) * 3 * 256); + uint32 GL_PointCount = NVG_ExpandStroke(Memory, Bezier, NumberOfVerts, (nvg_point *)Data, (real32 *)Data2); + + layer_transforms T = Layer_GetTransforms(Layer); + GL_RasterizeShape(&TestL, &TestM, Data2, GL_PointCount, T, Width, Height, BytesPerPixel, EffectBitmapAddress, *L_Width, *L_Height); + + Memory_PopScratch(Memory, sizeof(real32) * 3 * 256); + Memory_PopScratch(Memory, sizeof(nvg_point) * 128); + + // Bitmap_StencilAlpha(SourceBitmapAddress, EffectBitmapAddress, Source->BytesPerPixel, Size); + } + for (int i = 0; i < Layer->Block_Effect_Count; i++) { block_effect Effect = *(block_effect *)Memory_Block_AddressAtIndex(Memory, F_Effects, Layer->Block_Effect_Index[i]); @@ -140,20 +156,6 @@ Layer_UpdateMasksEffects(project_state *State, block_layer *Layer, memory *Memor Memory_PopScratch(Memory, Size); } } - /* - if (Layer->NumberOfMasks) { - for (int i = 0; i < Layer->NumberOfMasks; i++) { - file_mask_header *MaskHeader = (file_mask_header *)((uint8 *)Layer + sizeof(file_layer) + MaskOffset); - if (MaskHeader->IsClosed && MaskHeader->IsToggled) { - mask_point *Point = (mask_point *)((uint8 *)MaskHeader + sizeof(file_mask_header)); - Mask_TriangulateAndRasterize(TestM, TestL, Memory, MaskHeader, Point, Source->Width, Source->Height, Source->BytesPerPixel, EffectBitmapAddress); - } - } - Bitmap_StencilAlpha(SourceBitmapAddress, EffectBitmapAddress, Source->BytesPerPixel, Size); - } - - Layer->OutputBitmapLocation = EffectBitmapAddress; - */ GL_DeleteHWBuffer(&TestL); GL_DeleteHWBuffer(&TestM); diff --git a/src/main.cpp b/src/main.cpp index 88bc3ef..9856143 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -85,6 +85,7 @@ uint32 BitmapFill = 0x00000001; #include "undo.cpp" #include "io.cpp" #include "sorted.cpp" +#include "nanovg.cpp" #include "layer.cpp" #include "strings.cpp" #include "threading.cpp" @@ -352,7 +353,16 @@ Render_Comp(project_data *File, project_state *State, memory *Memory, sorted_fil void *RenderAddress = NULL; // result of masking, effects, and intermediate paint stroke int Width = 0, Height = 0, BytesPerPixel = 0; - if (!Layer->IsPrecomp) { + if (Layer->IsPrecomp) { + block_composition *Precomp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); + BitmapAddress = Render_Comp(File, State, Memory, Sorted, UI, window, textureID, io, SortedCompArray, SortedLayerArray, + SortedPropertyStart, SortedKeyframeArray, Layer->Block_Source_Index, (int32)Layer->time.CurrentValue); + Width = Precomp->Width; + Height = Precomp->Height; + BytesPerPixel = Precomp->BytesPerPixel; + } else if (Layer->IsShapeLayer) { + BytesPerPixel = 4; + } else { block_source *Source = (block_source *)Memory_Block_AddressAtIndex(Memory, F_Sources, Layer->Block_Source_Index); av_info *AV = NULL; if (Source->Type == source_type_file) { @@ -413,20 +423,13 @@ Render_Comp(project_data *File, project_state *State, memory *Memory, sorted_fil Width = Source->Width; Height = Source->Height; BytesPerPixel = Source->BytesPerPixel; - } else { - block_composition *Precomp = (block_composition *)Memory_Block_AddressAtIndex(Memory, F_Precomps, Layer->Block_Source_Index); - BitmapAddress = Render_Comp(File, State, Memory, Sorted, UI, window, textureID, io, SortedCompArray, SortedLayerArray, - SortedPropertyStart, SortedKeyframeArray, Layer->Block_Source_Index, (int32)Layer->time.CurrentValue); - Width = Precomp->Width; - Height = Precomp->Height; - BytesPerPixel = Precomp->BytesPerPixel; } - Assert(BitmapAddress); - uint64 ScratchSize = Width * Height * BytesPerPixel; RenderAddress = Memory_PushScratch(Memory, ScratchSize); - Memory_Copy((uint8 *)RenderAddress, (uint8 *)BitmapAddress, ScratchSize); + if (BitmapAddress) { + Memory_Copy((uint8 *)RenderAddress, (uint8 *)BitmapAddress, ScratchSize); + } if (State->Interact_Active == interact_type_brush && State->Brush.LayerToPaint_Index == Index_Physical) { // TODO(fox): Do all these extra precomputes really make a difference in the renderer? @@ -436,18 +439,28 @@ Render_Comp(project_data *File, project_state *State, memory *Memory, sorted_fil RenderRegion, State->Brush.TransientBitmap, 0, 0}; Render_Main(File, State, Memory, Sorted, UI, window, textureID, (void *)&Info, RenderAddress, render_type_notransform, State->Brush.CacheBounds); } - if (Layer->Block_Effect_Count || Layer->Block_Mask_Count) { - Layer_UpdateMasksEffects(State, Layer, Memory, RenderAddress, Width, Height, BytesPerPixel); - } - Assert(Width && Width <= 2048); - Assert(Height && Height <= 2048); - transform_info T = Transform_Calculate(State, Memory, File, Layer, Comp, Width, Height, BytesPerPixel); - T.SourceBuffer = RenderAddress; - rectangle RenderRegion = {0, 0, Comp->Width, Comp->Height}; + if (Layer->IsShapeLayer) { + Layer_UpdateMasksEffects(State, Layer, Memory, RenderAddress, Comp->Width, Comp->Height, Comp->BytesPerPixel, &Width, &Height); + rectangle RenderRegion = { 0, 0, Comp->Width, Comp->Height }; + direct_info Info = { (real32)Comp->Width, (real32)Comp->Height, (real32)Comp->BytesPerPixel, (real32)Comp->Width * Comp->BytesPerPixel, + Bitmap_ByteInfo(Comp->BytesPerPixel), UI->Color.a, blend_normal, + RenderRegion, RenderAddress, 0, 0}; + Render_Main(File, State, Memory, Sorted, UI, window, textureID, (void *)&Info, CompBuffer, render_type_notransform, RenderRegion); + } else { + Assert(Width && Width <= 2048); + Assert(Height && Height <= 2048); + transform_info T = Transform_Calculate(State, Memory, File, Layer, Comp, Width, Height, BytesPerPixel); + T.SourceBuffer = RenderAddress; + rectangle RenderRegion = {0, 0, Comp->Width, Comp->Height}; - Render_Main(File, State, Memory, Sorted, UI, window, textureID, (void *)&T, CompBuffer, render_type_main, RenderRegion); + Render_Main(File, State, Memory, Sorted, UI, window, textureID, (void *)&T, CompBuffer, render_type_main, RenderRegion); + } Memory_PopScratch(Memory, ScratchSize); + // if (Layer->Block_Effect_Count) { // || Layer->Block_Bezier_Count) { + // Layer_UpdateMasksEffects(State, Layer, Memory, RenderAddress, Width, Height, BytesPerPixel); + // } + } } @@ -750,8 +763,13 @@ int main(int argc, char *argv[]) { GLuint textureID; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_2D, textureID); +#if DEBUG glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +#endif ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); @@ -771,10 +789,55 @@ int main(int argc, char *argv[]) { State->UpdateFrame = true; // State->MostRecentlySelectedLayer = 0; #else - uint16 SourceIndex = Source_Generate(File, State, &Memory, (void *)"../asset/yu.webm"); - block_source *Source = (block_source *)Memory_Block_AddressAtIndex(&Memory, F_Sources, SourceIndex); - Source->IsSelected = true; - Source_UICreateButton(File, State, &Memory); + // uint16 SourceIndex = Source_Generate(File, State, &Memory, (void *)"../asset/yu.webm"); + // block_source *Source = (block_source *)Memory_Block_AddressAtIndex(&Memory, F_Sources, SourceIndex); + // Source->IsSelected = true; + // Source_UICreateButton(File, State, &Memory); + + /* + { + block_layer *Layer = Layer_Init(File, &Memory); + Layer->IsShapeLayer = true; + Layer->Shape.Point_Count = 2; + Layer->Shape.Block_Bezier_Index[0] = Memory_Block_AllocateNew(&Memory, F_Bezier); + block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(&Memory, F_Bezier, Layer->Shape.Block_Bezier_Index[0], 0); + Bezier->Occupied = true; + Bezier->Point[0] = { 1, { V2(001, 001), V2(000, 000), V2(000, 000) }, interpolation_type_linear, 0 }; + Bezier->Point[1] = { 1, { V2(300, 300), V2(-150, 000), V2(150, 000) }, interpolation_type_bezier, 0 }; + // Bezier->Point[0] = { 1, { V2(200, 250), V2(000, 000), V2(000, 000) }, interpolation_type_linear, 0 }; + // Bezier->Point[1] = { 1, { V2(300, 300), V2(-150, 000), V2(150, 000) }, interpolation_type_linear, 0 }; + // Bezier->Point[2] = { 1, { V2(400, 250), V2(-150, 000), V2(150, 000) }, interpolation_type_linear, 0 }; + Layer->x.CurrentValue = MainComp->Width/2; + Layer->y.CurrentValue = MainComp->Height/2; + // Layer->ax.CurrentValue = 0; + // Layer->ay.CurrentValue = 0; + Layer->Vertical_Offset = 10; + Layer->Frame_Start = MainComp->Frame_Start; + Layer->Frame_End = MainComp->Frame_End; + } + { + block_layer *Layer = Layer_Init(File, &Memory); + Layer->IsShapeLayer = true; + Layer->Shape.Point_Count = 2; + Layer->Shape.Block_Bezier_Index[0] = Memory_Block_AllocateNew(&Memory, F_Bezier); + block_bezier *Bezier = (block_bezier *)Memory_Block_AddressAtIndex(&Memory, F_Bezier, Layer->Shape.Block_Bezier_Index[0], 0); + Bezier->Occupied = true; + Bezier->Point[0] = { 1, { V2(00, 000), V2(000, 000), V2(000, 000) }, interpolation_type_linear, 0 }; + Bezier->Point[1] = { 1, { V2(500, 300), V2(-150, 000), V2(150, 000) }, interpolation_type_bezier, 0 }; + // Bezier->Point[0] = { 1, { V2(200, 250), V2(000, 000), V2(000, 000) }, interpolation_type_linear, 0 }; + // Bezier->Point[1] = { 1, { V2(300, 300), V2(-150, 000), V2(150, 000) }, interpolation_type_linear, 0 }; + // Bezier->Point[2] = { 1, { V2(400, 250), V2(-150, 000), V2(150, 000) }, interpolation_type_linear, 0 }; + Layer->x.CurrentValue = MainComp->Width/2; + Layer->y.CurrentValue = MainComp->Height/2; + // Layer->ax.CurrentValue = 0; + // Layer->ay.CurrentValue = 0; + Layer->Vertical_Offset = 11; + Layer->IsSelected = true; + State->MostRecentlySelectedLayer = 1; + Layer->Frame_Start = MainComp->Frame_Start; + Layer->Frame_End = MainComp->Frame_End; + } + */ #endif #endif @@ -809,8 +872,22 @@ int main(int argc, char *argv[]) { // NextEffect->Index -= 1; // History_Entry_End(Memory); // } break; - case hotkey_newpaintlayer: { Project_PaintLayer_New(File, State, &Memory); } break; - case hotkey_newlayerfromsource: + case hotkey_newlayer_paint: { + Project_PaintLayer_New(File, State, &Memory); + } break; + case hotkey_newlayer_shape: + { + block_composition *MainComp = (block_composition *)Memory_Block_AddressAtIndex(&Memory, F_Precomps, File->PrincipalCompIndex); + v2 Point = State->TempZoomRatio * V2(MainComp->Width, MainComp->Height); + v2 Vector = V2(0, 0); + if (State->HotkeyExtra[0] != 0) { + Vector = V2(State->HotkeyExtra[0], State->HotkeyExtra[1]); + State->HotkeyExtra[0] = 0; + State->HotkeyExtra[1] = 0; + } + Project_ShapeLayer_New(File, State, &Memory, Point, Vector); + } break; + case hotkey_newlayer_source: { Source_UICreateButton(File, State, &Memory); State->UpdateKeyframes = true; diff --git a/src/nanovg.cpp b/src/nanovg.cpp new file mode 100644 index 0000000..06b2910 --- /dev/null +++ b/src/nanovg.cpp @@ -0,0 +1,247 @@ + +enum nvg_point_flags +{ + NVG_PT_CORNER = 0x01, + NVG_PT_LEFT = 0x02, + NVG_PT_BEVEL = 0x04, + NVG_PR_INNERBEVEL = 0x08, +}; + +struct nvg_point +{ + real32 x; + real32 y; + real32 dx; + real32 dy; + real32 Length; + real32 dmx; + real32 dmy; + uint8 Flags; +}; + +static real32 +NVG_Normalize(real32 *x, float* y) +{ + real32 d = sqrtf((*x)*(*x) + (*y)*(*y)); + if (d > 1e-6f) { + real32 id = 1.0f / d; + *x *= id; + *y *= id; + } + return d; +} + +static real32 * +NVG_Point(real32 *StrokeData, real32 x, real32 y, real32 u, real32 v) +{ + *(v4 *)StrokeData = V4(x, y, u, v); + return StrokeData + 4; +} + +static void NVG_ChooseBevel(int bevel, nvg_point *p0, nvg_point *p1, float w, + float* x0, float* y0, float* x1, float* y1) +{ + if (bevel) { + *x0 = p1->x + p0->dy * w; + *y0 = p1->y - p0->dx * w; + *x1 = p1->x + p1->dy * w; + *y1 = p1->y - p1->dx * w; + } else { + *x0 = p1->x + p1->dmx * w; + *y0 = p1->y + p1->dmy * w; + *x1 = p1->x + p1->dmx * w; + *y1 = p1->y + p1->dmy * w; + } +} + +static int NVG_Clampi(int a, int mn, int mx) { return a < mn ? mn : (a > mx ? mx : a); } + +static real32 * NVG_RoundJoin(nvg_point *Point, nvg_point *NextPoint, real32 *StrokeData, + float lw, float rw, float lu, float ru, int ncap) +{ + int i, n; + float dlx0 = Point->dy; + float dly0 = -Point->dx; + float dlx1 = NextPoint->dy; + float dly1 = -NextPoint->dx; + + if (NextPoint->Flags & NVG_PT_LEFT) { + float lx0,ly0,lx1,ly1,a0,a1; + NVG_ChooseBevel(NextPoint->Flags & NVG_PR_INNERBEVEL, Point, NextPoint, lw, &lx0,&ly0, &lx1,&ly1); + a0 = atan2f(-dly0, -dlx0); + a1 = atan2f(-dly1, -dlx1); + if (a1 > a0) a1 -= PI*2; + + StrokeData = NVG_Point(StrokeData, lx0, ly0, 0, 0); + StrokeData = NVG_Point(StrokeData, NextPoint->x - dlx0*rw, NextPoint->y - dly0*rw, 0, 0); + + n = NVG_Clampi((int)ceilf(((a0 - a1) / PI) * ncap), 2, ncap); + for (i = 0; i < n; i++) { + float u = i/(float)(n-1); + float a = a0 + u*(a1-a0); + float rx = NextPoint->x + cosf(a) * rw; + float ry = NextPoint->y + sinf(a) * rw; + StrokeData = NVG_Point(StrokeData, NextPoint->x, NextPoint->y, 0, 0); + StrokeData = NVG_Point(StrokeData, rx, ry, 0, 0); + } + + StrokeData = NVG_Point(StrokeData, lx1, ly1, 0, 0); + StrokeData = NVG_Point(StrokeData, NextPoint->x - dlx1*rw, NextPoint->y - dly1*rw, 0, 0); + } + return StrokeData; +} + +static real32 * NVG_RoundCap(nvg_point * Point, real32 *StrokeData, + float dx, float dy, float w, int ncap, + float u0, float u1, int Mode) +{ + int i; + float px = Point->x; + float py = Point->y; + float dlx = dy; + float dly = -dx; + float Flip = (Mode == 0) ? 1 : -1; + if (Mode != 0) { + StrokeData = NVG_Point(StrokeData, px + dlx*w, py + dly*w, 0, 0); + StrokeData = NVG_Point(StrokeData, px - dlx*w, py - dly*w, 0, 0); + } + for (i = 0; i < ncap; i++) { + float a = i/(float)(ncap-1)*PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + v2 OuterPoint = V2(px - dlx*ax - dx*ay*Flip, py - dly*ax - dy*ay*Flip); + v2 InnerPoint = V2(px, py); + if (Mode == 0) { + StrokeData = NVG_Point(StrokeData, OuterPoint.x, OuterPoint.y, 0, 0); + StrokeData = NVG_Point(StrokeData, InnerPoint.x, InnerPoint.y, 0, 0); + } else { + StrokeData = NVG_Point(StrokeData, InnerPoint.x, InnerPoint.y, 0, 0); + StrokeData = NVG_Point(StrokeData, OuterPoint.x, OuterPoint.y, 0, 0); + } + } + if (Mode == 0) { + StrokeData = NVG_Point(StrokeData, px + dlx*w, py + dly*w, 0, 0); + StrokeData = NVG_Point(StrokeData, px - dlx*w, py - dly*w, 0, 0); + } + return StrokeData; +} + +// NOTE(fox): We only have to care about winding if we want to do HW accelerated +// shape subtraction with the stencil buffer (I think). +static uint32 +NVG_FlattenPath(void *Memory, block_bezier *Bezier, int PointCount, nvg_point *PointData, int *Width, int *Height) +{ + uint32 NumberOfVerts = 0; + nvg_point *PointPlayhead = PointData; + for (int i = 0; i < PointCount; i++) { + if (i == 0 || Bezier->Point[i].Type == interpolation_type_linear) { + *(v2 *)PointPlayhead = Bezier->Point[i].Pos[0]; + if (i != 0 && i != (PointCount - 1)) { + PointPlayhead->Flags |= NVG_PT_CORNER; + } + PointPlayhead++; + NumberOfVerts++; + } else if (Bezier->Point[i].Type == interpolation_type_bezier) { + v2 Pos[4] = { Bezier->Point[i].Pos[0], Bezier->Point[i].Pos[1], Bezier->Point[i+1].Pos[2], Bezier->Point[i+1].Pos[0] }; + Pos[1] = Pos[1] + Pos[0]; + Pos[2] = Pos[2] + Pos[3]; + NumberOfVerts += Bezier_CubicCalcPoints(Pos[3], Pos[2], Pos[1], Pos[0], PointPlayhead, sizeof(nvg_point)); + // The point at the end is also returned, so we remove it. + NumberOfVerts--; + PointPlayhead--; + } else { + Assert(0); + } + } + nvg_point *Point = &PointData[NumberOfVerts - 1]; + nvg_point *NextPoint = PointData; + v2 Min = V2(10000, 10000); + v2 Max = V2(-10000, -10000); + for (int i = 0; i < NumberOfVerts; i++) { + Point->dx = NextPoint->x - Point->x; + Point->dy = NextPoint->y - Point->y; + Point->Length = NVG_Normalize(&Point->dx, &Point->dy); + if (Point->x > Max.x) + Max.x = Point->x; + if (Point->x < Min.x) + Min.x = Point->x; + if (Point->y > Max.y) + Max.y = Point->y; + if (Point->y < Min.y) + Min.y = Point->y; + Point = NextPoint++; + } + *Width = Max.x - Min.x; + *Height = Max.y - Min.y; + return NumberOfVerts; +} + +real32 MiterLimit = 2.4f; + +static uint32 +NVG_ExpandStroke(void *Memory, block_bezier *Bezier, int NumberOfVerts, nvg_point *PointData, real32 *StrokeData) +{ + real32 Width = 50 * 0.5; + nvg_point *Point = PointData; + nvg_point *NextPoint = &PointData[1]; + int ncap = 12; + real32 *StartingStrokeData = StrokeData; + + StrokeData = NVG_RoundCap(Point, StrokeData, Point->dx, Point->dy, Width, ncap, 0.5, 0.5, 0); + + for (int i = 1; i < (NumberOfVerts - 1); i++) { + + real32 dlx0, dly0, dlx1, dly1, dmr2, cross, limit; + dlx0 = Point->dy; + dly0 = -Point->dx; + dlx1 = NextPoint->dy; + dly1 = -NextPoint->dx; + + // Calculate extrusions + NextPoint->dmx = (dlx0 + dlx1) * 0.5f; + NextPoint->dmy = (dly0 + dly1) * 0.5f; + dmr2 = NextPoint->dmx*NextPoint->dmx + NextPoint->dmy*NextPoint->dmy; + if (dmr2 > 0.000001f) { + float scale = 1.0f / dmr2; + if (scale > 600.0f) { + scale = 600.0f; + } + NextPoint->dmx *= scale; + NextPoint->dmy *= scale; + } + + // Keep track of left turns. + cross = NextPoint->dx * Point->dy - Point->dx * NextPoint->dy; + if (cross > 0.0f) { + NextPoint->Flags |= NVG_PT_LEFT; + } + + // Calculate if we should use bevel or miter for inner join. + // limit = nvg__maxf(1.01f, nvg__minf(p0->len, p1->len) * iw); + // if ((dmr2 * limit*limit) < 1.0f) + // p1->flags |= NVG_PR_INNERBEVEL; + + // Check to see if the corner needs to be beveled. + if (NextPoint->Flags & NVG_PT_CORNER) { + // if ((dmr2 * MiterLimit*MiterLimit) < 1.0f) r // || lineJoin == NVG_BEVEL || lineJoin == NVG_ROUND) { + NextPoint->Flags |= NVG_PT_BEVEL; + // } + } + + if ((NextPoint->Flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) { + // if (lineJoin == NVG_ROUND) { + StrokeData = NVG_RoundJoin(Point, NextPoint, StrokeData, Width, Width, 0.5, 0.5, ncap); + // } + } else { + StrokeData = NVG_Point(StrokeData, NextPoint->x + (NextPoint->dmx * Width), NextPoint->y + (NextPoint->dmy * Width), 0, 0); + StrokeData = NVG_Point(StrokeData, NextPoint->x - (NextPoint->dmx * Width), NextPoint->y - (NextPoint->dmy * Width), 0, 0); + } + + Point = NextPoint++; + } + + StrokeData = NVG_RoundCap(NextPoint, StrokeData, Point->dx, Point->dy, Width, ncap, 0.5, 0.5, 1); + + int GL_PointCount = (StrokeData - StartingStrokeData) / 4; + + return GL_PointCount; +} -- cgit v1.2.3