package draw import "core:math" SMOOTH_CIRCLE_ERROR_RATE :: 0.1 // ----- Adaptive tessellation ---- auto_segments :: proc(radius: f32, arc_degrees: f32) -> int { if radius <= 0 do return 4 phys_radius := radius * GLOB.dpi_scaling acos_arg := clamp(2 * math.pow(1 - SMOOTH_CIRCLE_ERROR_RATE / phys_radius, 2) - 1, -1, 1) th := math.acos(acos_arg) if th <= 0 do return 4 full_circle_segs := int(math.ceil(2 * math.PI / th)) segs := int(f32(full_circle_segs) * arc_degrees / 360.0) min_segs := max(int(math.ceil(f64(arc_degrees / 90.0))), 4) return max(segs, min_segs) } // ----- Internal helpers ---- @(private = "file") extrude_line :: proc( start, end_pos: [2]f32, thick: f32, color: Color, vertices: []Vertex, offset: int, ) -> int { direction := end_pos - start dx := direction[0] dy := direction[1] length := math.sqrt(dx * dx + dy * dy) if length < 0.0001 do return 0 scale := thick / (2 * length) perpendicular := [2]f32{-dy * scale, dx * scale} p0 := start + perpendicular p1 := start - perpendicular p2 := end_pos - perpendicular p3 := end_pos + perpendicular vertices[offset + 0] = sv(p0, color) vertices[offset + 1] = sv(p1, color) vertices[offset + 2] = sv(p2, color) vertices[offset + 3] = sv(p0, color) vertices[offset + 4] = sv(p2, color) vertices[offset + 5] = sv(p3, color) return 6 } // Create a vertex for solid-color shape drawing (no texture, UV defaults to zero). @(private = "file") sv :: proc(pos: [2]f32, color: Color) -> Vertex { return Vertex{position = pos, color = color} } @(private = "file") emit_rect :: proc(x, y, w, h: f32, color: Color, vertices: []Vertex, offset: int) { vertices[offset + 0] = sv({x, y}, color) vertices[offset + 1] = sv({x + w, y}, color) vertices[offset + 2] = sv({x + w, y + h}, color) vertices[offset + 3] = sv({x, y}, color) vertices[offset + 4] = sv({x + w, y + h}, color) vertices[offset + 5] = sv({x, y + h}, color) } // ----- Drawing functions ---- pixel :: proc(layer: ^Layer, pos: [2]f32, color: Color) { vertices: [6]Vertex emit_rect(pos[0], pos[1], 1, 1, color, vertices[:], 0) prepare_shape(layer, vertices[:]) } rectangle_gradient :: proc( layer: ^Layer, rect: Rectangle, top_left, top_right, bottom_left, bottom_right: Color, temp_allocator := context.temp_allocator, ) { vertices := make([]Vertex, 6, temp_allocator) tl := [2]f32{rect.x, rect.y} tr := [2]f32{rect.x + rect.w, rect.y} br := [2]f32{rect.x + rect.w, rect.y + rect.h} bl := [2]f32{rect.x, rect.y + rect.h} vertices[0] = sv(tl, top_left) vertices[1] = sv(tr, top_right) vertices[2] = sv(br, bottom_right) vertices[3] = sv(tl, top_left) vertices[4] = sv(br, bottom_right) vertices[5] = sv(bl, bottom_left) prepare_shape(layer, vertices) } circle_sector :: proc( layer: ^Layer, center: [2]f32, radius: f32, start_angle, end_angle: f32, color: Color, origin: [2]f32 = {0, 0}, rotation: f32 = 0, segments: int = 0, temp_allocator := context.temp_allocator, ) { arc_length := abs(end_angle - start_angle) segs := segments > 0 ? segments : auto_segments(radius, arc_length) vertex_count := segs * 3 vertices := make([]Vertex, vertex_count, temp_allocator) start_rad := math.to_radians(start_angle) end_rad := math.to_radians(end_angle) step_angle := (end_rad - start_rad) / f32(segs) if !needs_transform(origin, rotation) { for i in 0 ..< segs { current_angle := start_rad + step_angle * f32(i) next_angle := start_rad + step_angle * f32(i + 1) edge_current := center + [2]f32{math.cos(current_angle) * radius, math.sin(current_angle) * radius} edge_next := center + [2]f32{math.cos(next_angle) * radius, math.sin(next_angle) * radius} idx := i * 3 vertices[idx + 0] = sv(center, color) vertices[idx + 1] = sv(edge_next, color) vertices[idx + 2] = sv(edge_current, color) } } else { xform := build_pivot_rot(center, origin, rotation) center_local := [2]f32{0, 0} for i in 0 ..< segs { current_angle := start_rad + step_angle * f32(i) next_angle := start_rad + step_angle * f32(i + 1) edge_current := [2]f32{math.cos(current_angle) * radius, math.sin(current_angle) * radius} edge_next := [2]f32{math.cos(next_angle) * radius, math.sin(next_angle) * radius} idx := i * 3 vertices[idx + 0] = sv(apply_transform(xform, center_local), color) vertices[idx + 1] = sv(apply_transform(xform, edge_next), color) vertices[idx + 2] = sv(apply_transform(xform, edge_current), color) } } prepare_shape(layer, vertices) } circle_gradient :: proc( layer: ^Layer, center: [2]f32, radius: f32, inner, outer: Color, origin: [2]f32 = {0, 0}, rotation: f32 = 0, segments: int = 0, temp_allocator := context.temp_allocator, ) { segs := segments > 0 ? segments : auto_segments(radius, 360) vertex_count := segs * 3 vertices := make([]Vertex, vertex_count, temp_allocator) step_angle := math.TAU / f32(segs) if !needs_transform(origin, rotation) { for i in 0 ..< segs { current_angle := step_angle * f32(i) next_angle := step_angle * f32(i + 1) edge_current := center + [2]f32{math.cos(current_angle) * radius, math.sin(current_angle) * radius} edge_next := center + [2]f32{math.cos(next_angle) * radius, math.sin(next_angle) * radius} idx := i * 3 vertices[idx + 0] = sv(center, inner) vertices[idx + 1] = sv(edge_next, outer) vertices[idx + 2] = sv(edge_current, outer) } } else { xform := build_pivot_rot(center, origin, rotation) center_local := [2]f32{0, 0} for i in 0 ..< segs { current_angle := step_angle * f32(i) next_angle := step_angle * f32(i + 1) edge_current := [2]f32{math.cos(current_angle) * radius, math.sin(current_angle) * radius} edge_next := [2]f32{math.cos(next_angle) * radius, math.sin(next_angle) * radius} idx := i * 3 vertices[idx + 0] = sv(apply_transform(xform, center_local), inner) vertices[idx + 1] = sv(apply_transform(xform, edge_next), outer) vertices[idx + 2] = sv(apply_transform(xform, edge_current), outer) } } prepare_shape(layer, vertices) } triangle :: proc( layer: ^Layer, v1, v2, v3: [2]f32, color: Color, origin: [2]f32 = {0, 0}, rotation: f32 = 0, ) { if !needs_transform(origin, rotation) { vertices := [3]Vertex{sv(v1, color), sv(v2, color), sv(v3, color)} prepare_shape(layer, vertices[:]) return } mn := [2]f32{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} xform := build_pivot_rot(mn, origin, rotation) local_v1 := v1 - mn local_v2 := v2 - mn local_v3 := v3 - mn vertices := [3]Vertex { sv(apply_transform(xform, local_v1), color), sv(apply_transform(xform, local_v2), color), sv(apply_transform(xform, local_v3), color), } prepare_shape(layer, vertices[:]) } triangle_lines :: proc( layer: ^Layer, v1, v2, v3: [2]f32, color: Color, thick: f32 = 1, origin: [2]f32 = {0, 0}, rotation: f32 = 0, temp_allocator := context.temp_allocator, ) { vertices := make([]Vertex, 18, temp_allocator) write_offset := 0 if !needs_transform(origin, rotation) { write_offset += extrude_line(v1, v2, thick, color, vertices, write_offset) write_offset += extrude_line(v2, v3, thick, color, vertices, write_offset) write_offset += extrude_line(v3, v1, thick, color, vertices, write_offset) } else { mn := [2]f32{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} xform := build_pivot_rot(mn, origin, rotation) tv1 := apply_transform(xform, v1 - mn) tv2 := apply_transform(xform, v2 - mn) tv3 := apply_transform(xform, v3 - mn) write_offset += extrude_line(tv1, tv2, thick, color, vertices, write_offset) write_offset += extrude_line(tv2, tv3, thick, color, vertices, write_offset) write_offset += extrude_line(tv3, tv1, thick, color, vertices, write_offset) } if write_offset > 0 { prepare_shape(layer, vertices[:write_offset]) } } triangle_fan :: proc( layer: ^Layer, points: [][2]f32, color: Color, origin: [2]f32 = {0, 0}, rotation: f32 = 0, temp_allocator := context.temp_allocator, ) { if len(points) < 3 do return triangle_count := len(points) - 2 vertex_count := triangle_count * 3 vertices := make([]Vertex, vertex_count, temp_allocator) if !needs_transform(origin, rotation) { for i in 1 ..< len(points) - 1 { idx := (i - 1) * 3 vertices[idx + 0] = sv(points[0], color) vertices[idx + 1] = sv(points[i], color) vertices[idx + 2] = sv(points[i + 1], color) } } else { mn := [2]f32{max(f32), max(f32)} for p in points { mn.x = min(mn.x, p.x) mn.y = min(mn.y, p.y) } xform := build_pivot_rot(mn, origin, rotation) for i in 1 ..< len(points) - 1 { idx := (i - 1) * 3 vertices[idx + 0] = sv(apply_transform(xform, points[0] - mn), color) vertices[idx + 1] = sv(apply_transform(xform, points[i] - mn), color) vertices[idx + 2] = sv(apply_transform(xform, points[i + 1] - mn), color) } } prepare_shape(layer, vertices) } triangle_strip :: proc( layer: ^Layer, points: [][2]f32, color: Color, origin: [2]f32 = {0, 0}, rotation: f32 = 0, temp_allocator := context.temp_allocator, ) { if len(points) < 3 do return triangle_count := len(points) - 2 vertex_count := triangle_count * 3 vertices := make([]Vertex, vertex_count, temp_allocator) if !needs_transform(origin, rotation) { for i in 0 ..< triangle_count { idx := i * 3 if i % 2 == 0 { vertices[idx + 0] = sv(points[i], color) vertices[idx + 1] = sv(points[i + 1], color) vertices[idx + 2] = sv(points[i + 2], color) } else { vertices[idx + 0] = sv(points[i + 1], color) vertices[idx + 1] = sv(points[i], color) vertices[idx + 2] = sv(points[i + 2], color) } } } else { mn := [2]f32{max(f32), max(f32)} for p in points { mn.x = min(mn.x, p.x) mn.y = min(mn.y, p.y) } xform := build_pivot_rot(mn, origin, rotation) for i in 0 ..< triangle_count { idx := i * 3 if i % 2 == 0 { vertices[idx + 0] = sv(apply_transform(xform, points[i] - mn), color) vertices[idx + 1] = sv(apply_transform(xform, points[i + 1] - mn), color) vertices[idx + 2] = sv(apply_transform(xform, points[i + 2] - mn), color) } else { vertices[idx + 0] = sv(apply_transform(xform, points[i + 1] - mn), color) vertices[idx + 1] = sv(apply_transform(xform, points[i] - mn), color) vertices[idx + 2] = sv(apply_transform(xform, points[i + 2] - mn), color) } } } prepare_shape(layer, vertices) } // ----- SDF drawing functions ---- // Compute new center position after rotating a center-parametrized shape // around a pivot point. The pivot is at (center + origin) in world space. @(private = "file") compute_pivot_center :: proc(center: [2]f32, origin: [2]f32, rotation_deg: f32) -> [2]f32 { if origin == {0, 0} do return center theta := math.to_radians(rotation_deg) c, s := math.cos(theta), math.sin(theta) // pivot = center + origin; new_center = pivot + R(θ) * (center - pivot) return center + origin + {c * (-origin.x) - s * (-origin.y), s * (-origin.x) + c * (-origin.y)} } // Compute the AABB half-extents of a rectangle with half-size (hx, hy) rotated by rot_rad. @(private = "file") rotated_aabb_half :: proc(hx, hy, rot_rad: f32) -> [2]f32 { c_r := abs(math.cos(rot_rad)) s_r := abs(math.sin(rot_rad)) return {hx * c_r + hy * s_r, hx * s_r + hy * c_r} } // Draw a filled rectangle via SDF (analytical anti-aliasing at all orientations). // `roundness` is a 0–1 fraction controlling uniform corner rounding — 0 is sharp, 1 is fully rounded. // For per-corner pixel-precise rounding, use `rectangle_corners` instead. rectangle :: proc( layer: ^Layer, rect: Rectangle, color: Color, roundness: f32 = 0, origin: [2]f32 = {0, 0}, rotation: f32 = 0, soft_px: f32 = 1.0, ) { cr := min(rect.w, rect.h) * clamp(roundness, 0, 1) * 0.5 rectangle_corners(layer, rect, {cr, cr, cr, cr}, color, origin, rotation, soft_px) } // Draw a stroked rectangle via SDF (analytical anti-aliasing at all orientations). // `roundness` is a 0–1 fraction controlling uniform corner rounding — 0 is sharp, 1 is fully rounded. // For per-corner pixel-precise rounding, use `rectangle_corners_lines` instead. rectangle_lines :: proc( layer: ^Layer, rect: Rectangle, color: Color, thick: f32 = 1, roundness: f32 = 0, origin: [2]f32 = {0, 0}, rotation: f32 = 0, soft_px: f32 = 1.0, ) { cr := min(rect.w, rect.h) * clamp(roundness, 0, 1) * 0.5 rectangle_corners_lines(layer, rect, {cr, cr, cr, cr}, color, thick, origin, rotation, soft_px) } // Draw a rectangle with per-corner rounding radii via SDF. rectangle_corners :: proc( layer: ^Layer, rect: Rectangle, radii: [4]f32, color: Color, origin: [2]f32 = {0, 0}, rotation: f32 = 0, soft_px: f32 = 1.0, ) { max_radius := min(rect.w, rect.h) * 0.5 tl := clamp(radii[0], 0, max_radius) tr := clamp(radii[1], 0, max_radius) br := clamp(radii[2], 0, max_radius) bl := clamp(radii[3], 0, max_radius) pad := soft_px / GLOB.dpi_scaling dpi := GLOB.dpi_scaling hx := rect.w * 0.5 hy := rect.h * 0.5 rot_rad: f32 = 0 center_x := rect.x + hx center_y := rect.y + hy if needs_transform(origin, rotation) { rot_rad = math.to_radians(rotation) xform := build_pivot_rot({rect.x, rect.y}, origin, rotation) new_center := apply_transform(xform, {hx, hy}) center_x = new_center.x center_y = new_center.y } bhx, bhy := hx, hy if rot_rad != 0 { expanded := rotated_aabb_half(hx, hy, rot_rad) bhx = expanded.x bhy = expanded.y } prim := Primitive { bounds = {center_x - bhx - pad, center_y - bhy - pad, center_x + bhx + pad, center_y + bhy + pad}, color = color, kind_flags = pack_kind_flags(.RRect, {}), rotation = rot_rad, } prim.params.rrect = RRect_Params { half_size = {hx * dpi, hy * dpi}, radii = {tr * dpi, br * dpi, tl * dpi, bl * dpi}, soft_px = soft_px, stroke_px = 0, } prepare_sdf_primitive(layer, prim) } // Draw a stroked rectangle with per-corner rounding radii via SDF. rectangle_corners_lines :: proc( layer: ^Layer, rect: Rectangle, radii: [4]f32, color: Color, thick: f32 = 1, origin: [2]f32 = {0, 0}, rotation: f32 = 0, soft_px: f32 = 1.0, ) { max_radius := min(rect.w, rect.h) * 0.5 tl := clamp(radii[0], 0, max_radius) tr := clamp(radii[1], 0, max_radius) br := clamp(radii[2], 0, max_radius) bl := clamp(radii[3], 0, max_radius) pad := (thick * 0.5 + soft_px) / GLOB.dpi_scaling dpi := GLOB.dpi_scaling hx := rect.w * 0.5 hy := rect.h * 0.5 rot_rad: f32 = 0 center_x := rect.x + hx center_y := rect.y + hy if needs_transform(origin, rotation) { rot_rad = math.to_radians(rotation) xform := build_pivot_rot({rect.x, rect.y}, origin, rotation) new_center := apply_transform(xform, {hx, hy}) center_x = new_center.x center_y = new_center.y } bhx, bhy := hx, hy if rot_rad != 0 { expanded := rotated_aabb_half(hx, hy, rot_rad) bhx = expanded.x bhy = expanded.y } prim := Primitive { bounds = {center_x - bhx - pad, center_y - bhy - pad, center_x + bhx + pad, center_y + bhy + pad}, color = color, kind_flags = pack_kind_flags(.RRect, {.Stroke}), rotation = rot_rad, } prim.params.rrect = RRect_Params { half_size = {hx * dpi, hy * dpi}, radii = {tr * dpi, br * dpi, tl * dpi, bl * dpi}, soft_px = soft_px, stroke_px = thick * dpi, } prepare_sdf_primitive(layer, prim) } // Draw a filled circle via SDF. circle :: proc( layer: ^Layer, center: [2]f32, radius: f32, color: Color, origin: [2]f32 = {0, 0}, rotation: f32 = 0, soft_px: f32 = 1.0, ) { pad := soft_px / GLOB.dpi_scaling dpi := GLOB.dpi_scaling actual_center := center if origin != {0, 0} { actual_center = compute_pivot_center(center, origin, rotation) } prim := Primitive { bounds = { actual_center.x - radius - pad, actual_center.y - radius - pad, actual_center.x + radius + pad, actual_center.y + radius + pad, }, color = color, kind_flags = pack_kind_flags(.Circle, {}), // rotation stays 0 — circle is rotationally symmetric } prim.params.circle = Circle_Params { radius = radius * dpi, soft_px = soft_px, } prepare_sdf_primitive(layer, prim) } // Draw a stroked circle via SDF. circle_lines :: proc( layer: ^Layer, center: [2]f32, radius: f32, color: Color, thick: f32 = 1, origin: [2]f32 = {0, 0}, rotation: f32 = 0, soft_px: f32 = 1.0, ) { pad := (thick * 0.5 + soft_px) / GLOB.dpi_scaling dpi := GLOB.dpi_scaling actual_center := center if origin != {0, 0} { actual_center = compute_pivot_center(center, origin, rotation) } prim := Primitive { bounds = { actual_center.x - radius - pad, actual_center.y - radius - pad, actual_center.x + radius + pad, actual_center.y + radius + pad, }, color = color, kind_flags = pack_kind_flags(.Circle, {.Stroke}), } prim.params.circle = Circle_Params { radius = radius * dpi, soft_px = soft_px, stroke_px = thick * dpi, } prepare_sdf_primitive(layer, prim) } // Draw a filled ellipse via SDF. ellipse :: proc( layer: ^Layer, center: [2]f32, radius_h, radius_v: f32, color: Color, origin: [2]f32 = {0, 0}, rotation: f32 = 0, soft_px: f32 = 1.0, ) { pad := soft_px / GLOB.dpi_scaling dpi := GLOB.dpi_scaling actual_center := center rot_rad: f32 = 0 if needs_transform(origin, rotation) { actual_center = compute_pivot_center(center, origin, rotation) rot_rad = math.to_radians(rotation) } // When rotated, expand the bounds AABB to enclose the rotated ellipse bound_h, bound_v := radius_h, radius_v if rot_rad != 0 { expanded := rotated_aabb_half(radius_h, radius_v, rot_rad) bound_h = expanded.x bound_v = expanded.y } prim := Primitive { bounds = { actual_center.x - bound_h - pad, actual_center.y - bound_v - pad, actual_center.x + bound_h + pad, actual_center.y + bound_v + pad, }, color = color, kind_flags = pack_kind_flags(.Ellipse, {}), rotation = rot_rad, } prim.params.ellipse = Ellipse_Params { radii = {radius_h * dpi, radius_v * dpi}, soft_px = soft_px, } prepare_sdf_primitive(layer, prim) } // Draw a stroked ellipse via SDF. ellipse_lines :: proc( layer: ^Layer, center: [2]f32, radius_h, radius_v: f32, color: Color, thick: f32 = 1, origin: [2]f32 = {0, 0}, rotation: f32 = 0, soft_px: f32 = 1.0, ) { // Extra 10% padding: iq's sdEllipse has precision degradation near the tips of highly // eccentric ellipses, so the quad needs additional breathing room beyond the stroke width. extra := max(radius_h, radius_v) * 0.1 + thick * 0.5 pad := (extra + soft_px) / GLOB.dpi_scaling dpi := GLOB.dpi_scaling actual_center := center rot_rad: f32 = 0 if needs_transform(origin, rotation) { actual_center = compute_pivot_center(center, origin, rotation) rot_rad = math.to_radians(rotation) } bound_h, bound_v := radius_h, radius_v if rot_rad != 0 { expanded := rotated_aabb_half(radius_h, radius_v, rot_rad) bound_h = expanded.x bound_v = expanded.y } prim := Primitive { bounds = { actual_center.x - bound_h - pad, actual_center.y - bound_v - pad, actual_center.x + bound_h + pad, actual_center.y + bound_v + pad, }, color = color, kind_flags = pack_kind_flags(.Ellipse, {.Stroke}), rotation = rot_rad, } prim.params.ellipse = Ellipse_Params { radii = {radius_h * dpi, radius_v * dpi}, soft_px = soft_px, stroke_px = thick * dpi, } prepare_sdf_primitive(layer, prim) } // Draw a filled ring arc via SDF. ring :: proc( layer: ^Layer, center: [2]f32, inner_radius, outer_radius: f32, start_angle, end_angle: f32, color: Color, origin: [2]f32 = {0, 0}, rotation: f32 = 0, soft_px: f32 = 1.0, ) { pad := soft_px / GLOB.dpi_scaling dpi := GLOB.dpi_scaling actual_center := center rotation_offset: f32 = 0 if needs_transform(origin, rotation) { actual_center = compute_pivot_center(center, origin, rotation) rotation_offset = math.to_radians(rotation) } prim := Primitive { bounds = { actual_center.x - outer_radius - pad, actual_center.y - outer_radius - pad, actual_center.x + outer_radius + pad, actual_center.y + outer_radius + pad, }, color = color, kind_flags = pack_kind_flags(.Ring_Arc, {}), // No shader rotation — arc rotation handled by offsetting start/end angles } prim.params.ring_arc = Ring_Arc_Params { inner_radius = inner_radius * dpi, outer_radius = outer_radius * dpi, start_rad = math.to_radians(start_angle) + rotation_offset, end_rad = math.to_radians(end_angle) + rotation_offset, soft_px = soft_px, } prepare_sdf_primitive(layer, prim) } // Draw stroked ring arc outlines via SDF. ring_lines :: proc( layer: ^Layer, center: [2]f32, inner_radius, outer_radius: f32, start_angle, end_angle: f32, color: Color, thick: f32 = 1, origin: [2]f32 = {0, 0}, rotation: f32 = 0, soft_px: f32 = 1.0, ) { // Compute effective angles and pivot-translated center up front eff_start := start_angle + rotation eff_end := end_angle + rotation actual_center := center if needs_transform(origin, rotation) { actual_center = compute_pivot_center(center, origin, rotation) } // Inner arc outline (pass already-transformed center; no further origin/rotation) ring( layer, actual_center, max(0, inner_radius - thick * 0.5), inner_radius + thick * 0.5, eff_start, eff_end, color, soft_px = soft_px, ) // Outer arc outline ring( layer, actual_center, max(0, outer_radius - thick * 0.5), outer_radius + thick * 0.5, eff_start, eff_end, color, soft_px = soft_px, ) // Start cap start_rad := math.to_radians(eff_start) end_rad := math.to_radians(eff_end) inner_start := actual_center + {math.cos(start_rad) * inner_radius, math.sin(start_rad) * inner_radius} outer_start := actual_center + {math.cos(start_rad) * outer_radius, math.sin(start_rad) * outer_radius} line(layer, inner_start, outer_start, color, thick, soft_px) // End cap inner_end := actual_center + {math.cos(end_rad) * inner_radius, math.sin(end_rad) * inner_radius} outer_end := actual_center + {math.cos(end_rad) * outer_radius, math.sin(end_rad) * outer_radius} line(layer, inner_end, outer_end, color, thick, soft_px) } // Draw a line segment via SDF. line :: proc(layer: ^Layer, start, end_pos: [2]f32, color: Color, thick: f32 = 1, soft_px: f32 = 1.0) { cap := thick * 0.5 + soft_px / GLOB.dpi_scaling min_x := min(start.x, end_pos.x) - cap max_x := max(start.x, end_pos.x) + cap min_y := min(start.y, end_pos.y) - cap max_y := max(start.y, end_pos.y) + cap dpi := GLOB.dpi_scaling center := [2]f32{(min_x + max_x) * 0.5, (min_y + max_y) * 0.5} local_a := (start - center) * dpi local_b := (end_pos - center) * dpi prim := Primitive { bounds = {min_x, min_y, max_x, max_y}, color = color, kind_flags = pack_kind_flags(.Segment, {}), } prim.params.segment = Segment_Params { a = local_a, b = local_b, width = thick * dpi, soft_px = soft_px, } prepare_sdf_primitive(layer, prim) } // Draw a line strip via decomposed SDF segments. line_strip :: proc(layer: ^Layer, points: [][2]f32, color: Color, thick: f32 = 1, soft_px: f32 = 1.0) { if len(points) < 2 do return for i in 0 ..< len(points) - 1 { line(layer, points[i], points[i + 1], color, thick, soft_px) } } // Draw a filled regular polygon via SDF. poly :: proc( layer: ^Layer, center: [2]f32, sides: int, radius: f32, color: Color, rotation: f32 = 0, origin: [2]f32 = {0, 0}, soft_px: f32 = 1.0, ) { if sides < 3 do return pad := soft_px / GLOB.dpi_scaling dpi := GLOB.dpi_scaling actual_center := center if origin != {0, 0} && rotation != 0 { actual_center = compute_pivot_center(center, origin, rotation) } prim := Primitive { bounds = { actual_center.x - radius - pad, actual_center.y - radius - pad, actual_center.x + radius + pad, actual_center.y + radius + pad, }, color = color, kind_flags = pack_kind_flags(.NGon, {}), } prim.params.ngon = NGon_Params { radius = radius * math.cos(math.PI / f32(sides)) * dpi, rotation = math.to_radians(rotation), sides = f32(sides), soft_px = soft_px, } prepare_sdf_primitive(layer, prim) } // Draw a stroked regular polygon via SDF. poly_lines :: proc( layer: ^Layer, center: [2]f32, sides: int, radius: f32, color: Color, rotation: f32 = 0, origin: [2]f32 = {0, 0}, thick: f32 = 1, soft_px: f32 = 1.0, ) { if sides < 3 do return pad := (thick * 0.5 + soft_px) / GLOB.dpi_scaling dpi := GLOB.dpi_scaling actual_center := center if origin != {0, 0} && rotation != 0 { actual_center = compute_pivot_center(center, origin, rotation) } prim := Primitive { bounds = { actual_center.x - radius - pad, actual_center.y - radius - pad, actual_center.x + radius + pad, actual_center.y + radius + pad, }, color = color, kind_flags = pack_kind_flags(.NGon, {.Stroke}), } prim.params.ngon = NGon_Params { radius = radius * math.cos(math.PI / f32(sides)) * dpi, rotation = math.to_radians(rotation), sides = f32(sides), soft_px = soft_px, stroke_px = thick * dpi, } prepare_sdf_primitive(layer, prim) } // --------------------------------------------------------------------------------------------------------------------- // ----- Anchor helpers ---------------- // --------------------------------------------------------------------------------------------------------------------- // // Return [2]f32 pixel offsets for use as the `origin` parameter of draw calls. // Composable with normal vector +/- arithmetic. // // Text anchor helpers are in text.odin (they depend on measure_text / SDL_ttf). // ----- Rectangle anchors (origin measured from rect's top-left) -------------------------------------------------- center_of_rect :: #force_inline proc(r: Rectangle) -> [2]f32 { return {r.w * 0.5, r.h * 0.5} } top_left_of_rect :: #force_inline proc(r: Rectangle) -> [2]f32 { return {0, 0} } top_of_rect :: #force_inline proc(r: Rectangle) -> [2]f32 { return {r.w * 0.5, 0} } top_right_of_rect :: #force_inline proc(r: Rectangle) -> [2]f32 { return {r.w, 0} } left_of_rect :: #force_inline proc(r: Rectangle) -> [2]f32 { return {0, r.h * 0.5} } right_of_rect :: #force_inline proc(r: Rectangle) -> [2]f32 { return {r.w, r.h * 0.5} } bottom_left_of_rect :: #force_inline proc(r: Rectangle) -> [2]f32 { return {0, r.h} } bottom_of_rect :: #force_inline proc(r: Rectangle) -> [2]f32 { return {r.w * 0.5, r.h} } bottom_right_of_rect :: #force_inline proc(r: Rectangle) -> [2]f32 { return {r.w, r.h} } // ----- Triangle anchors (origin measured from AABB top-left) ----------------------------------------------------- center_of_triangle :: #force_inline proc(v1, v2, v3: [2]f32) -> [2]f32 { mn := [2]f32{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} return (v1 + v2 + v3) / 3 - mn } top_left_of_triangle :: #force_inline proc(v1, v2, v3: [2]f32) -> [2]f32 { return {0, 0} } top_of_triangle :: #force_inline proc(v1, v2, v3: [2]f32) -> [2]f32 { mn_x := min(v1.x, v2.x, v3.x) mx_x := max(v1.x, v2.x, v3.x) return {(mx_x - mn_x) * 0.5, 0} } top_right_of_triangle :: #force_inline proc(v1, v2, v3: [2]f32) -> [2]f32 { mn_x := min(v1.x, v2.x, v3.x) mx_x := max(v1.x, v2.x, v3.x) return {mx_x - mn_x, 0} } left_of_triangle :: #force_inline proc(v1, v2, v3: [2]f32) -> [2]f32 { mn_y := min(v1.y, v2.y, v3.y) mx_y := max(v1.y, v2.y, v3.y) return {0, (mx_y - mn_y) * 0.5} } right_of_triangle :: #force_inline proc(v1, v2, v3: [2]f32) -> [2]f32 { mn := [2]f32{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} mx := [2]f32{max(v1.x, v2.x, v3.x), max(v1.y, v2.y, v3.y)} return {mx.x - mn.x, (mx.y - mn.y) * 0.5} } bottom_left_of_triangle :: #force_inline proc(v1, v2, v3: [2]f32) -> [2]f32 { mn_y := min(v1.y, v2.y, v3.y) mx_y := max(v1.y, v2.y, v3.y) return {0, mx_y - mn_y} } bottom_of_triangle :: #force_inline proc(v1, v2, v3: [2]f32) -> [2]f32 { mn := [2]f32{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} mx := [2]f32{max(v1.x, v2.x, v3.x), max(v1.y, v2.y, v3.y)} return {(mx.x - mn.x) * 0.5, mx.y - mn.y} } bottom_right_of_triangle :: #force_inline proc(v1, v2, v3: [2]f32) -> [2]f32 { mn := [2]f32{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} mx := [2]f32{max(v1.x, v2.x, v3.x), max(v1.y, v2.y, v3.y)} return mx - mn }