package tess import "core:math" import draw ".." SMOOTH_CIRCLE_ERROR_RATE :: 0.1 auto_segments :: proc(radius: f32, arc_degrees: f32) -> int { if radius <= 0 do return 4 phys_radius := radius * draw.GLOB.dpi_scaling acos_arg := clamp(2 * math.pow(1 - SMOOTH_CIRCLE_ERROR_RATE / phys_radius, 2) - 1, -1, 1) theta := math.acos(acos_arg) if theta <= 0 do return 4 full_circle_segments := int(math.ceil(2 * math.PI / theta)) segments := int(f32(full_circle_segments) * arc_degrees / 360.0) min_segments := max(int(math.ceil(f64(arc_degrees / 90.0))), 4) return max(segments, min_segments) } // ----- Internal helpers ----- // Color is premultiplied: the tessellated fragment shader passes it through directly // and the blend state is ONE, ONE_MINUS_SRC_ALPHA. solid_vertex :: proc(position: draw.Vec2, color: draw.Color) -> draw.Vertex { return draw.Vertex{position = position, color = draw.premultiply_color(color)} } emit_rectangle :: proc(x, y, width, height: f32, color: draw.Color, vertices: []draw.Vertex, offset: int) { vertices[offset + 0] = solid_vertex({x, y}, color) vertices[offset + 1] = solid_vertex({x + width, y}, color) vertices[offset + 2] = solid_vertex({x + width, y + height}, color) vertices[offset + 3] = solid_vertex({x, y}, color) vertices[offset + 4] = solid_vertex({x + width, y + height}, color) vertices[offset + 5] = solid_vertex({x, y + height}, color) } extrude_line :: proc( start, end_pos: draw.Vec2, thickness: f32, color: draw.Color, vertices: []draw.Vertex, offset: int, ) -> int { direction := end_pos - start delta_x := direction[0] delta_y := direction[1] length := math.sqrt(delta_x * delta_x + delta_y * delta_y) if length < 0.0001 do return 0 scale := thickness / (2 * length) perpendicular := draw.Vec2{-delta_y * scale, delta_x * scale} p0 := start + perpendicular p1 := start - perpendicular p2 := end_pos - perpendicular p3 := end_pos + perpendicular vertices[offset + 0] = solid_vertex(p0, color) vertices[offset + 1] = solid_vertex(p1, color) vertices[offset + 2] = solid_vertex(p2, color) vertices[offset + 3] = solid_vertex(p0, color) vertices[offset + 4] = solid_vertex(p2, color) vertices[offset + 5] = solid_vertex(p3, color) return 6 } // ----- Public draw ----- pixel :: proc(layer: ^draw.Layer, pos: draw.Vec2, color: draw.Color) { vertices: [6]draw.Vertex emit_rectangle(pos[0], pos[1], 1, 1, color, vertices[:], 0) draw.prepare_shape(layer, vertices[:]) } triangle :: proc( layer: ^draw.Layer, v1, v2, v3: draw.Vec2, color: draw.Color, origin: draw.Vec2 = {}, rotation: f32 = 0, ) { if !draw.needs_transform(origin, rotation) { vertices := [3]draw.Vertex{solid_vertex(v1, color), solid_vertex(v2, color), solid_vertex(v3, color)} draw.prepare_shape(layer, vertices[:]) return } bounds_min := draw.Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} transform := draw.build_pivot_rotation(bounds_min, origin, rotation) local_v1 := v1 - bounds_min local_v2 := v2 - bounds_min local_v3 := v3 - bounds_min vertices := [3]draw.Vertex { solid_vertex(draw.apply_transform(transform, local_v1), color), solid_vertex(draw.apply_transform(transform, local_v2), color), solid_vertex(draw.apply_transform(transform, local_v3), color), } draw.prepare_shape(layer, vertices[:]) } // Draw an anti-aliased triangle via extruded edge quads. // Interior vertices get the full premultiplied color; outer fringe vertices get BLANK (0,0,0,0). // The rasterizer linearly interpolates between them, producing a smooth 1-pixel AA band. // `aa_px` controls the extrusion width in logical pixels (default 1.0). // This proc emits 21 vertices (3 interior + 6 edge quads × 3 verts each). triangle_aa :: proc( layer: ^draw.Layer, v1, v2, v3: draw.Vec2, color: draw.Color, aa_px: f32 = draw.DFT_FEATHER_PX, origin: draw.Vec2 = {}, rotation: f32 = 0, ) { // Apply rotation if needed, then work in world space. p0, p1, p2: draw.Vec2 if !draw.needs_transform(origin, rotation) { p0 = v1 p1 = v2 p2 = v3 } else { bounds_min := draw.Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} transform := draw.build_pivot_rotation(bounds_min, origin, rotation) p0 = draw.apply_transform(transform, v1 - bounds_min) p1 = draw.apply_transform(transform, v2 - bounds_min) p2 = draw.apply_transform(transform, v3 - bounds_min) } // Compute outward edge normals (unit length, pointing away from triangle interior). // Winding-independent: we check against the centroid to ensure normals point outward. centroid_x := (p0.x + p1.x + p2.x) / 3.0 centroid_y := (p0.y + p1.y + p2.y) / 3.0 edge_normal :: proc(edge_start, edge_end: draw.Vec2, centroid_x, centroid_y: f32) -> draw.Vec2 { delta_x := edge_end.x - edge_start.x delta_y := edge_end.y - edge_start.y length := math.sqrt(delta_x * delta_x + delta_y * delta_y) if length < 0.0001 do return {0, 0} inverse_length := 1.0 / length // Perpendicular: (-delta_y, delta_x) normalized normal_x := -delta_y * inverse_length normal_y := delta_x * inverse_length // Midpoint of the edge midpoint_x := (edge_start.x + edge_end.x) * 0.5 midpoint_y := (edge_start.y + edge_end.y) * 0.5 // If normal points toward centroid, flip it if normal_x * (centroid_x - midpoint_x) + normal_y * (centroid_y - midpoint_y) > 0 { normal_x = -normal_x normal_y = -normal_y } return {normal_x, normal_y} } normal_01 := edge_normal(p0, p1, centroid_x, centroid_y) normal_12 := edge_normal(p1, p2, centroid_x, centroid_y) normal_20 := edge_normal(p2, p0, centroid_x, centroid_y) extrude_distance := aa_px * draw.GLOB.dpi_scaling // Outer fringe vertices: each edge vertex extruded outward outer_0_01 := p0 + normal_01 * extrude_distance outer_1_01 := p1 + normal_01 * extrude_distance outer_1_12 := p1 + normal_12 * extrude_distance outer_2_12 := p2 + normal_12 * extrude_distance outer_2_20 := p2 + normal_20 * extrude_distance outer_0_20 := p0 + normal_20 * extrude_distance // Premultiplied interior color (solid_vertex does premul internally). // Outer fringe is BLANK = {0,0,0,0} which is already premul. transparent := draw.BLANK // 3 interior + 6 × 3 edge-quad = 21 vertices vertices: [21]draw.Vertex // Interior triangle vertices[0] = solid_vertex(p0, color) vertices[1] = solid_vertex(p1, color) vertices[2] = solid_vertex(p2, color) // Edge quad: p0→p1 (2 triangles) vertices[3] = solid_vertex(p0, color) vertices[4] = solid_vertex(p1, color) vertices[5] = solid_vertex(outer_1_01, transparent) vertices[6] = solid_vertex(p0, color) vertices[7] = solid_vertex(outer_1_01, transparent) vertices[8] = solid_vertex(outer_0_01, transparent) // Edge quad: p1→p2 (2 triangles) vertices[9] = solid_vertex(p1, color) vertices[10] = solid_vertex(p2, color) vertices[11] = solid_vertex(outer_2_12, transparent) vertices[12] = solid_vertex(p1, color) vertices[13] = solid_vertex(outer_2_12, transparent) vertices[14] = solid_vertex(outer_1_12, transparent) // Edge quad: p2→p0 (2 triangles) vertices[15] = solid_vertex(p2, color) vertices[16] = solid_vertex(p0, color) vertices[17] = solid_vertex(outer_0_20, transparent) vertices[18] = solid_vertex(p2, color) vertices[19] = solid_vertex(outer_0_20, transparent) vertices[20] = solid_vertex(outer_2_20, transparent) draw.prepare_shape(layer, vertices[:]) } triangle_lines :: proc( layer: ^draw.Layer, v1, v2, v3: draw.Vec2, color: draw.Color, thickness: f32 = draw.DFT_STROKE_THICKNESS, origin: draw.Vec2 = {}, rotation: f32 = 0, temp_allocator := context.temp_allocator, ) { vertices := make([]draw.Vertex, 18, temp_allocator) defer delete(vertices, temp_allocator) write_offset := 0 if !draw.needs_transform(origin, rotation) { write_offset += extrude_line(v1, v2, thickness, color, vertices, write_offset) write_offset += extrude_line(v2, v3, thickness, color, vertices, write_offset) write_offset += extrude_line(v3, v1, thickness, color, vertices, write_offset) } else { bounds_min := draw.Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} transform := draw.build_pivot_rotation(bounds_min, origin, rotation) transformed_v1 := draw.apply_transform(transform, v1 - bounds_min) transformed_v2 := draw.apply_transform(transform, v2 - bounds_min) transformed_v3 := draw.apply_transform(transform, v3 - bounds_min) write_offset += extrude_line(transformed_v1, transformed_v2, thickness, color, vertices, write_offset) write_offset += extrude_line(transformed_v2, transformed_v3, thickness, color, vertices, write_offset) write_offset += extrude_line(transformed_v3, transformed_v1, thickness, color, vertices, write_offset) } if write_offset > 0 { draw.prepare_shape(layer, vertices[:write_offset]) } } triangle_fan :: proc( layer: ^draw.Layer, points: []draw.Vec2, color: draw.Color, origin: draw.Vec2 = {}, 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([]draw.Vertex, vertex_count, temp_allocator) defer delete(vertices, temp_allocator) if !draw.needs_transform(origin, rotation) { for i in 1 ..< len(points) - 1 { idx := (i - 1) * 3 vertices[idx + 0] = solid_vertex(points[0], color) vertices[idx + 1] = solid_vertex(points[i], color) vertices[idx + 2] = solid_vertex(points[i + 1], color) } } else { bounds_min := draw.Vec2{max(f32), max(f32)} for point in points { bounds_min.x = min(bounds_min.x, point.x) bounds_min.y = min(bounds_min.y, point.y) } transform := draw.build_pivot_rotation(bounds_min, origin, rotation) for i in 1 ..< len(points) - 1 { idx := (i - 1) * 3 vertices[idx + 0] = solid_vertex(draw.apply_transform(transform, points[0] - bounds_min), color) vertices[idx + 1] = solid_vertex(draw.apply_transform(transform, points[i] - bounds_min), color) vertices[idx + 2] = solid_vertex(draw.apply_transform(transform, points[i + 1] - bounds_min), color) } } draw.prepare_shape(layer, vertices) } triangle_strip :: proc( layer: ^draw.Layer, points: []draw.Vec2, color: draw.Color, origin: draw.Vec2 = {}, 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([]draw.Vertex, vertex_count, temp_allocator) defer delete(vertices, temp_allocator) if !draw.needs_transform(origin, rotation) { for i in 0 ..< triangle_count { idx := i * 3 if i % 2 == 0 { vertices[idx + 0] = solid_vertex(points[i], color) vertices[idx + 1] = solid_vertex(points[i + 1], color) vertices[idx + 2] = solid_vertex(points[i + 2], color) } else { vertices[idx + 0] = solid_vertex(points[i + 1], color) vertices[idx + 1] = solid_vertex(points[i], color) vertices[idx + 2] = solid_vertex(points[i + 2], color) } } } else { bounds_min := draw.Vec2{max(f32), max(f32)} for point in points { bounds_min.x = min(bounds_min.x, point.x) bounds_min.y = min(bounds_min.y, point.y) } transform := draw.build_pivot_rotation(bounds_min, origin, rotation) for i in 0 ..< triangle_count { idx := i * 3 if i % 2 == 0 { vertices[idx + 0] = solid_vertex(draw.apply_transform(transform, points[i] - bounds_min), color) vertices[idx + 1] = solid_vertex(draw.apply_transform(transform, points[i + 1] - bounds_min), color) vertices[idx + 2] = solid_vertex(draw.apply_transform(transform, points[i + 2] - bounds_min), color) } else { vertices[idx + 0] = solid_vertex(draw.apply_transform(transform, points[i + 1] - bounds_min), color) vertices[idx + 1] = solid_vertex(draw.apply_transform(transform, points[i] - bounds_min), color) vertices[idx + 2] = solid_vertex(draw.apply_transform(transform, points[i + 2] - bounds_min), color) } } } draw.prepare_shape(layer, vertices) }