From 31b2196976252bf56c59177485135044b5d0b452 Mon Sep 17 00:00:00 2001 From: Froxwin <56168224+Froxwin@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:31:03 +0000 Subject: [PATCH] fix: add bevel edge case to miter join --- src/application/applicationstate.c | 80 ++++++++++++++++++------------ src/core/modifier.c | 10 ++-- src/core/modifier.h | 13 ++--- src/core/raster.c | 23 +++++++-- src/core/raster.h | 2 +- src/ui/vektorcanvas.c | 6 +-- 6 files changed, 84 insertions(+), 50 deletions(-) diff --git a/src/application/applicationstate.c b/src/application/applicationstate.c index 314ebc5..d906f72 100644 --- a/src/application/applicationstate.c +++ b/src/application/applicationstate.c @@ -118,13 +118,15 @@ begin_click_dispatch: VektorStyle style = (VektorStyle){ .stroke_color = state->currentColor, .stroke_width = 0.01}; - vektor_shapenodebuf_add( - state->shapeBuffer, vektor_shapenode_new(vektor_shape_new(linePrimitive, style, 0))); + vektor_shapenodebuf_add(state->shapeBuffer, + vektor_shapenode_new(vektor_shape_new( + linePrimitive, style, 0))); state->selectedShape = &(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]); - } else if (state->selectedShape->base.primitive.kind != VEKTOR_POLYLINE) { + } else if (state->selectedShape->base.primitive.kind != + VEKTOR_POLYLINE) { // selecting a tool resets the selection, so this condition // should not happen g_warning("Invalid selected primitive; polyline expected"); @@ -134,7 +136,8 @@ begin_click_dispatch: vektor_polyline_add_point(state->selectedShape->base.primitive.polyline, pos); - state->selectedShape->base.bbox = vektor_primitive_get_bbox(state->selectedShape->base.primitive); + state->selectedShape->base.bbox = + vektor_primitive_get_bbox(state->selectedShape->base.primitive); // polyline's handle count is not fixed, so we have to add them manually vektor_shape_add_handle(&state->selectedShape->base, pos); @@ -148,20 +151,24 @@ begin_click_dispatch: (VektorPrimitive){.kind = VEKTOR_POLYGON, .polygon = polygon}; VektorStyle style = (VektorStyle){ .stroke_color = state->currentColor, .stroke_width = 0.01}; - vektor_shapenodebuf_add( - state->shapeBuffer, vektor_shapenode_new(vektor_shape_new(polygonPrimitive, style, 0))); + vektor_shapenodebuf_add(state->shapeBuffer, + vektor_shapenode_new(vektor_shape_new( + polygonPrimitive, style, 0))); state->selectedShape = &(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]); - } else if (state->selectedShape->base.primitive.kind != VEKTOR_POLYGON) { + } else if (state->selectedShape->base.primitive.kind != + VEKTOR_POLYGON) { g_warning("Invalid selected primitive; polygon expected"); vektor_appstate_deselect_shape(state); goto begin_click_dispatch; // retry } - vektor_polygon_add_point(state->selectedShape->base.primitive.polygon, pos); - state->selectedShape->base.bbox = vektor_primitive_get_bbox(state->selectedShape->base.primitive); + vektor_polygon_add_point(state->selectedShape->base.primitive.polygon, + pos); + state->selectedShape->base.bbox = + vektor_primitive_get_bbox(state->selectedShape->base.primitive); // polygon's handle count is not fixed, so we have to add them manually vektor_shape_add_handle(&state->selectedShape->base, pos); @@ -174,21 +181,26 @@ begin_click_dispatch: VektorStyle style = (VektorStyle){.stroke_color = state->currentColor, .stroke_width = 0.01}; vektor_shapenodebuf_add( - state->shapeBuffer, vektor_shapenode_new(vektor_shape_new(circlePrimitive, style, 0))); + state->shapeBuffer, + vektor_shapenode_new(vektor_shape_new(circlePrimitive, style, 0))); state->selectedShape = &(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]); vektor_circle_free(circle); - vektor_circle_set_center(&state->selectedShape->base.primitive.circle, pos); - vektor_circle_set_radius(&state->selectedShape->base.primitive.circle, 0.1f); + vektor_circle_set_center(&state->selectedShape->base.primitive.circle, + pos); + vektor_circle_set_radius(&state->selectedShape->base.primitive.circle, + 0.1f); - state->selectedShape->base.bbox = vektor_primitive_get_bbox(state->selectedShape->base.primitive); + state->selectedShape->base.bbox = + vektor_primitive_get_bbox(state->selectedShape->base.primitive); - vektor_circle_create_handles(&state->selectedShape->base.primitive.circle, - &state->selectedShape->base.handles, - &state->selectedShape->base.handleCount); + vektor_circle_create_handles( + &state->selectedShape->base.primitive.circle, + &state->selectedShape->base.handles, + &state->selectedShape->base.handleCount); } else if (state->selectedTool == VektorRectangleTool) { VektorRectangle* rect = vektor_rectangle_new(); @@ -197,28 +209,32 @@ begin_click_dispatch: VektorStyle style = (VektorStyle){.stroke_color = state->currentColor, .stroke_width = 0.01}; vektor_shapenodebuf_add( - state->shapeBuffer, vektor_shapenode_new(vektor_shape_new(rectPrimitive, style, 0))); + state->shapeBuffer, + vektor_shapenode_new(vektor_shape_new(rectPrimitive, style, 0))); state->selectedShape = &(state->shapeBuffer->nodes[state->shapeBuffer->count - 1]); vektor_rectangle_free(rect); - vektor_rectangle_set_start(&state->selectedShape->base.primitive.rectangle, - pos); - vektor_rectangle_set_end(&state->selectedShape->base.primitive.rectangle, - vec2_add(pos, (V2){0.1f, 0.1f})); + vektor_rectangle_set_start( + &state->selectedShape->base.primitive.rectangle, pos); + vektor_rectangle_set_end( + &state->selectedShape->base.primitive.rectangle, + vec2_add(pos, (V2){0.1f, 0.1f})); vektor_rectangle_create_handles( &state->selectedShape->base.primitive.rectangle, - &state->selectedShape->base.handles, &state->selectedShape->base.handleCount); + &state->selectedShape->base.handles, + &state->selectedShape->base.handleCount); - state->selectedShape->base.bbox = vektor_primitive_get_bbox(state->selectedShape->base.primitive); + state->selectedShape->base.bbox = + vektor_primitive_get_bbox(state->selectedShape->base.primitive); } else if (state->selectedTool == VektorSelectionTool) { for (size_t i = 0; i < state->shapeBuffer->count; i++) { VektorBBox bbox = vektor_primitive_get_bbox( state->shapeBuffer->nodes[i].base.primitive); - + // expand the bbox a little so its not painful to // try to grab handles located on the border of said bbox bbox = vektor_bbox_expand(bbox, 0.02); @@ -247,15 +263,16 @@ void vektor_appstate_canvas_drag_begin(GtkGestureDrag* gesture, gdouble x, position = m33_transform(m33_inverse(state->renderInfo->canvasMat), (V2){position.x, position.y}); - if(state->selectedShape != NULL) { + if (state->selectedShape != NULL) { VektorShapeNode* selectedShape = state->selectedShape; // get selected shape's handles and check // if we click any of them - for(size_t i = 0; i < selectedShape->base.handleCount; i++) { - VektorBBox bbox = vektor_shape_get_handle_bbox(selectedShape->base.handles[i]); - if(vektor_bbox_isinside(bbox, position)) { - // clicked inside handle + for (size_t i = 0; i < selectedShape->base.handleCount; i++) { + VektorBBox bbox = + vektor_shape_get_handle_bbox(selectedShape->base.handles[i]); + if (vektor_bbox_isinside(bbox, position)) { + // clicked inside handle state->heldHandleIndex = i; vektor_canvas_geometry_changed(state->renderInfo); break; @@ -285,9 +302,10 @@ void vektor_appstate_canvas_drag_update(GtkGestureDrag* gesture, gdouble x, (V2){position.x, position.y}); // drag handle if selected - if(state->selectedShape != NULL && state->heldHandleIndex != -1) { + if (state->selectedShape != NULL && state->heldHandleIndex != -1) { state->selectedShape->base.handles[state->heldHandleIndex] = position; - vektor_shape_handles_updated(&state->selectedShape->base, &state->heldHandleIndex); + vektor_shape_handles_updated(&state->selectedShape->base, + &state->heldHandleIndex); vektor_canvas_geometry_changed(state->renderInfo); } } diff --git a/src/core/modifier.c b/src/core/modifier.c index 299c821..fe984ff 100644 --- a/src/core/modifier.c +++ b/src/core/modifier.c @@ -1,11 +1,8 @@ #include "modifier.h" VektorShapeNode vektor_shapenode_new(VektorShape shape) { - VektorShapeNode node = (VektorShapeNode) { - .base = shape, - .modifier_count = 0, - .evaluated = shape - }; + VektorShapeNode node = (VektorShapeNode){ + .base = shape, .modifier_count = 0, .evaluated = shape}; return node; } @@ -14,7 +11,8 @@ VektorShape* vektor_shapenode_get_evaluated(VektorShapeNode* shapeNode) { return &shapeNode->evaluated; } -void vektor_shapenodebuf_add(VektorShapeNodeBuffer* buffer, VektorShapeNode node) { +void vektor_shapenodebuf_add(VektorShapeNodeBuffer* buffer, + VektorShapeNode node) { if (buffer->count >= buffer->capacity) { buffer->capacity = buffer->capacity ? buffer->capacity * 2 : 4; buffer->nodes = diff --git a/src/core/modifier.h b/src/core/modifier.h index 024ed55..0f17f8e 100644 --- a/src/core/modifier.h +++ b/src/core/modifier.h @@ -3,9 +3,7 @@ #include "src/core/primitives.h" -typedef enum { - VEKTOR_MODIFIER_BEVEL -} VektorModifierType; +typedef enum { VEKTOR_MODIFIER_BEVEL } VektorModifierType; typedef struct VektorModifier { VektorModifierType type; @@ -35,10 +33,13 @@ typedef struct VektorShapeNodeBuffer { VektorShapeNode vektor_shapenode_new(VektorShape shape); VektorShape* vektor_shapenode_get_evaluated(VektorShapeNode* shapeNode); -void vektor_shapenode_modifier_add(VektorShapeNode* shapeNode, VektorModifier* mod); -void vektor_shapenode_modifier_remove(VektorShapeNode* shapeNode, VektorModifier* mod); +void vektor_shapenode_modifier_add(VektorShapeNode* shapeNode, + VektorModifier* mod); +void vektor_shapenode_modifier_remove(VektorShapeNode* shapeNode, + VektorModifier* mod); void vektor_shapenode_free(VektorShapeNode* shapeNode); -void vektor_shapenodebuf_add(VektorShapeNodeBuffer* buffer, VektorShapeNode node); +void vektor_shapenodebuf_add(VektorShapeNodeBuffer* buffer, + VektorShapeNode node); #endif \ No newline at end of file diff --git a/src/core/raster.c b/src/core/raster.c index 9ee4152..72f6c0d 100644 --- a/src/core/raster.c +++ b/src/core/raster.c @@ -41,7 +41,7 @@ void vektor_polygon_tessellate(EdgeBuffer* buffer, VektorPolygon* polygon, void vektor_circle_tessellate(EdgeBuffer* buffer, VektorCircle* circle, size_t j, double scale) { double err = 0.0025; - size_t res = MIN(PI * sqrt((scale * circle->radius) / (2 * err)), 20); + size_t res = PI * sqrt((scale * circle->radius) / (2 * err)); for (size_t i = 0; i < res; i++) { double theta1 = (2 * PI * i) / res; double theta2 = (2 * PI * (i + 1)) / res; @@ -71,7 +71,7 @@ void vektor_rectangle_tessellate(EdgeBuffer* buffer, VektorRectangle* rct, } void vektor_vb_rasterize(VertexBuffer* vb, VektorShapeNodeBuffer* nodebuf, - double scale) { + double scale) { for (size_t i = 0; i < nodebuf->count; i++) { EdgeBuffer edges = {0}; VektorPrimitive* p = &nodebuf->nodes[i].base.primitive; @@ -104,7 +104,6 @@ void vektor_vb_rasterize(VertexBuffer* vb, VektorShapeNodeBuffer* nodebuf, break; } } - } void vektor_vb_add_triangle(VertexBuffer* vb, V2 v0, V2 v1, V2 v2, @@ -202,6 +201,24 @@ void vektor_edges_to_triangles(VertexBuffer* vb, EdgeBuffer* edges, V2 v22 = vec2_add(e2.p2, off2); V2 v23 = vec2_sub(e2.p2, off2); + V2 outer1 = vec2_add(corner, off1); + V2 outer2 = vec2_add(corner, off2); + + V2 inner1 = vec2_sub(corner, off1); + V2 inner2 = vec2_sub(corner, off2); + + float cos_theta = vec2_dot(d1, d2); + float sin_half = sqrtf((1.0f - cos_theta) * 0.5f); + float miter_len = hw / sin_half; + + if (miter_len > 4.0 * hw || miter_len < 1.05 * hw) { + vektor_vb_add_triangle(vb, outer1, corner, outer2, + style.stroke_color); + vektor_vb_add_triangle(vb, inner1, corner, inner2, + style.stroke_color); + continue; + } + V2 outer_miter = line_intersection(vec2_add(corner, off1), d1, vec2_add(corner, off2), d2); diff --git a/src/core/raster.h b/src/core/raster.h index 04e09e9..f64eca4 100644 --- a/src/core/raster.h +++ b/src/core/raster.h @@ -53,6 +53,6 @@ void vektor_edge_to_triangles(VertexBuffer* vb, Edge e, void vektor_edges_to_triangles(VertexBuffer* vb, EdgeBuffer* edges, M33* transform, VektorStyle style, bool closed); void vektor_vb_rasterize(VertexBuffer* vb, VektorShapeNodeBuffer* shapes, - double scale); + double scale); #endif // RASTER_H_ diff --git a/src/ui/vektorcanvas.c b/src/ui/vektorcanvas.c index b66a998..27ff794 100644 --- a/src/ui/vektorcanvas.c +++ b/src/ui/vektorcanvas.c @@ -160,9 +160,9 @@ void vektor_canvas_geometry_changed(VektorCanvasRenderInfo* renderInfo) { shape_vertex_count = vb.count; - // create selection quad if a shape is selected - VektorBBox bbox = vektor_primitive_get_bbox(selectedShape->base.primitive); + VektorBBox bbox = + vektor_primitive_get_bbox(selectedShape->base.primitive); // expand it a little so it is not inset bbox = vektor_bbox_expand(bbox, 0.03f); @@ -196,7 +196,7 @@ static gboolean render(GtkGLArea* a, GdkGLContext* ctx, // PASS 2 - draw selection quads if (vb.count > shape_vertex_count) { - //g_print("vdelta: %zu\n", vb.count - shape_vertex_count); + // g_print("vdelta: %zu\n", vb.count - shape_vertex_count); float time = (g_get_monotonic_time() - renderInfo->startupTime) / 10000000.0f;