Major rendering updates

This commit is contained in:
Zachary Levy
2026-04-22 14:49:37 -07:00
parent 37da2ea068
commit e1938f1c42
15 changed files with 1770 additions and 1736 deletions

330
draw/tess/tess.odin Normal file
View File

@@ -0,0 +1,330 @@
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)
}