Texture Rendering (#9)
Co-authored-by: Zachary Levy <zachary@sunforge.is> Reviewed-on: #9
This commit was merged in pull request #9.
This commit is contained in:
164
draw/shapes.odin
164
draw/shapes.odin
@@ -68,6 +68,19 @@ emit_rectangle :: proc(x, y, width, height: f32, color: Color, vertices: []Verte
|
||||
vertices[offset + 5] = solid_vertex({x, y + height}, color)
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
prepare_sdf_primitive_textured :: proc(
|
||||
layer: ^Layer,
|
||||
prim: Primitive,
|
||||
texture_id: Texture_Id,
|
||||
sampler: Sampler_Preset,
|
||||
) {
|
||||
offset := u32(len(GLOB.tmp_primitives))
|
||||
append(&GLOB.tmp_primitives, prim)
|
||||
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
|
||||
append_or_extend_sub_batch(scissor, layer, .SDF, offset, 1, texture_id, sampler)
|
||||
}
|
||||
|
||||
// ----- Drawing functions ----
|
||||
|
||||
pixel :: proc(layer: ^Layer, pos: [2]f32, color: Color) {
|
||||
@@ -83,6 +96,7 @@ rectangle_gradient :: proc(
|
||||
temp_allocator := context.temp_allocator,
|
||||
) {
|
||||
vertices := make([]Vertex, 6, temp_allocator)
|
||||
defer delete(vertices, temp_allocator)
|
||||
|
||||
corner_top_left := [2]f32{rect.x, rect.y}
|
||||
corner_top_right := [2]f32{rect.x + rect.width, rect.y}
|
||||
@@ -115,6 +129,7 @@ circle_sector :: proc(
|
||||
|
||||
vertex_count := segment_count * 3
|
||||
vertices := make([]Vertex, vertex_count, temp_allocator)
|
||||
defer delete(vertices, temp_allocator)
|
||||
|
||||
start_radians := math.to_radians(start_angle)
|
||||
end_radians := math.to_radians(end_angle)
|
||||
@@ -167,6 +182,7 @@ circle_gradient :: proc(
|
||||
|
||||
vertex_count := segment_count * 3
|
||||
vertices := make([]Vertex, vertex_count, temp_allocator)
|
||||
defer delete(vertices, temp_allocator)
|
||||
|
||||
step_angle := math.TAU / f32(segment_count)
|
||||
|
||||
@@ -238,6 +254,7 @@ triangle_lines :: proc(
|
||||
temp_allocator := context.temp_allocator,
|
||||
) {
|
||||
vertices := make([]Vertex, 18, temp_allocator)
|
||||
defer delete(vertices, temp_allocator)
|
||||
write_offset := 0
|
||||
|
||||
if !needs_transform(origin, rotation) {
|
||||
@@ -273,6 +290,7 @@ triangle_fan :: proc(
|
||||
triangle_count := len(points) - 2
|
||||
vertex_count := triangle_count * 3
|
||||
vertices := make([]Vertex, vertex_count, temp_allocator)
|
||||
defer delete(vertices, temp_allocator)
|
||||
|
||||
if !needs_transform(origin, rotation) {
|
||||
for i in 1 ..< len(points) - 1 {
|
||||
@@ -312,6 +330,7 @@ triangle_strip :: proc(
|
||||
triangle_count := len(points) - 2
|
||||
vertex_count := triangle_count * 3
|
||||
vertices := make([]Vertex, vertex_count, temp_allocator)
|
||||
defer delete(vertices, temp_allocator)
|
||||
|
||||
if !needs_transform(origin, rotation) {
|
||||
for i in 0 ..< triangle_count {
|
||||
@@ -352,17 +371,20 @@ triangle_strip :: proc(
|
||||
|
||||
// ----- 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.
|
||||
// Compute the visual center of a center-parametrized shape after applying
|
||||
// Convention B origin semantics: `center` is where the origin-point lands in
|
||||
// world space; the visual center is offset by -origin and then rotated around
|
||||
// the landing point.
|
||||
// visual_center = center + R(θ) · (-origin)
|
||||
// When θ=0: visual_center = center - origin (pure positioning shift).
|
||||
// When origin={0,0}: visual_center = center (no change).
|
||||
@(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)
|
||||
cos_angle, sin_angle := math.cos(theta), math.sin(theta)
|
||||
// pivot = center + origin; new_center = pivot + R(θ) * (center - pivot)
|
||||
return(
|
||||
center +
|
||||
origin +
|
||||
{cos_angle * (-origin.x) - sin_angle * (-origin.y), sin_angle * (-origin.x) + cos_angle * (-origin.y)} \
|
||||
)
|
||||
}
|
||||
@@ -378,6 +400,13 @@ rotated_aabb_half_extents :: proc(half_width, half_height, rotation_radians: f32
|
||||
// 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.
|
||||
//
|
||||
// Origin semantics:
|
||||
// `origin` is a local offset from the rect's top-left corner that selects both the positioning
|
||||
// anchor and the rotation pivot. `rect.x, rect.y` specifies where that anchor point lands in
|
||||
// world space. When `origin = {0, 0}` (default), `rect.x, rect.y` is the top-left corner.
|
||||
// When `origin = center_of_rectangle(rect)`, `rect.x, rect.y` is the visual center.
|
||||
// Rotation always occurs around the anchor point.
|
||||
rectangle :: proc(
|
||||
layer: ^Layer,
|
||||
rect: Rectangle,
|
||||
@@ -394,6 +423,7 @@ rectangle :: proc(
|
||||
// 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.
|
||||
// Origin semantics: see `rectangle`.
|
||||
rectangle_lines :: proc(
|
||||
layer: ^Layer,
|
||||
rect: Rectangle,
|
||||
@@ -409,6 +439,7 @@ rectangle_lines :: proc(
|
||||
}
|
||||
|
||||
// Draw a rectangle with per-corner rounding radii via SDF.
|
||||
// Origin semantics: see `rectangle`.
|
||||
rectangle_corners :: proc(
|
||||
layer: ^Layer,
|
||||
rect: Rectangle,
|
||||
@@ -430,12 +461,12 @@ rectangle_corners :: proc(
|
||||
half_width := rect.width * 0.5
|
||||
half_height := rect.height * 0.5
|
||||
rotation_radians: f32 = 0
|
||||
center_x := rect.x + half_width
|
||||
center_y := rect.y + half_height
|
||||
center_x := rect.x + half_width - origin.x
|
||||
center_y := rect.y + half_height - origin.y
|
||||
|
||||
if needs_transform(origin, rotation) {
|
||||
rotation_radians = math.to_radians(rotation)
|
||||
transform := build_pivot_rotation({rect.x, rect.y}, origin, rotation)
|
||||
transform := build_pivot_rotation({rect.x + origin.x, rect.y + origin.y}, origin, rotation)
|
||||
new_center := apply_transform(transform, {half_width, half_height})
|
||||
center_x = new_center.x
|
||||
center_y = new_center.y
|
||||
@@ -474,6 +505,7 @@ rectangle_corners :: proc(
|
||||
}
|
||||
|
||||
// Draw a stroked rectangle with per-corner rounding radii via SDF.
|
||||
// Origin semantics: see `rectangle`.
|
||||
rectangle_corners_lines :: proc(
|
||||
layer: ^Layer,
|
||||
rect: Rectangle,
|
||||
@@ -496,12 +528,12 @@ rectangle_corners_lines :: proc(
|
||||
half_width := rect.width * 0.5
|
||||
half_height := rect.height * 0.5
|
||||
rotation_radians: f32 = 0
|
||||
center_x := rect.x + half_width
|
||||
center_y := rect.y + half_height
|
||||
center_x := rect.x + half_width - origin.x
|
||||
center_y := rect.y + half_height - origin.y
|
||||
|
||||
if needs_transform(origin, rotation) {
|
||||
rotation_radians = math.to_radians(rotation)
|
||||
transform := build_pivot_rotation({rect.x, rect.y}, origin, rotation)
|
||||
transform := build_pivot_rotation({rect.x + origin.x, rect.y + origin.y}, origin, rotation)
|
||||
new_center := apply_transform(transform, {half_width, half_height})
|
||||
center_x = new_center.x
|
||||
center_y = new_center.y
|
||||
@@ -539,7 +571,114 @@ rectangle_corners_lines :: proc(
|
||||
prepare_sdf_primitive(layer, prim)
|
||||
}
|
||||
|
||||
// Draw a rectangle with a texture fill via SDF. Supports rounded corners via `roundness`,
|
||||
// rotation, and analytical anti-aliasing on the shape silhouette.
|
||||
// Origin semantics: see `rectangle`.
|
||||
rectangle_texture :: proc(
|
||||
layer: ^Layer,
|
||||
rect: Rectangle,
|
||||
id: Texture_Id,
|
||||
tint: Color = WHITE,
|
||||
uv_rect: Rectangle = {0, 0, 1, 1},
|
||||
sampler: Sampler_Preset = .Linear_Clamp,
|
||||
roundness: f32 = 0,
|
||||
origin: [2]f32 = {0, 0},
|
||||
rotation: f32 = 0,
|
||||
soft_px: f32 = 1.0,
|
||||
) {
|
||||
cr := min(rect.width, rect.height) * clamp(roundness, 0, 1) * 0.5
|
||||
rectangle_texture_corners(
|
||||
layer,
|
||||
rect,
|
||||
{cr, cr, cr, cr},
|
||||
id,
|
||||
tint,
|
||||
uv_rect,
|
||||
sampler,
|
||||
origin,
|
||||
rotation,
|
||||
soft_px,
|
||||
)
|
||||
}
|
||||
|
||||
// Draw a rectangle with a texture fill and per-corner rounding radii via SDF.
|
||||
// Origin semantics: see `rectangle`.
|
||||
rectangle_texture_corners :: proc(
|
||||
layer: ^Layer,
|
||||
rect: Rectangle,
|
||||
radii: [4]f32,
|
||||
id: Texture_Id,
|
||||
tint: Color = WHITE,
|
||||
uv_rect: Rectangle = {0, 0, 1, 1},
|
||||
sampler: Sampler_Preset = .Linear_Clamp,
|
||||
origin: [2]f32 = {0, 0},
|
||||
rotation: f32 = 0,
|
||||
soft_px: f32 = 1.0,
|
||||
) {
|
||||
max_radius := min(rect.width, rect.height) * 0.5
|
||||
top_left := clamp(radii[0], 0, max_radius)
|
||||
top_right := clamp(radii[1], 0, max_radius)
|
||||
bottom_right := clamp(radii[2], 0, max_radius)
|
||||
bottom_left := clamp(radii[3], 0, max_radius)
|
||||
|
||||
padding := soft_px / GLOB.dpi_scaling
|
||||
dpi_scale := GLOB.dpi_scaling
|
||||
|
||||
half_width := rect.width * 0.5
|
||||
half_height := rect.height * 0.5
|
||||
rotation_radians: f32 = 0
|
||||
center_x := rect.x + half_width - origin.x
|
||||
center_y := rect.y + half_height - origin.y
|
||||
|
||||
if needs_transform(origin, rotation) {
|
||||
rotation_radians = math.to_radians(rotation)
|
||||
transform := build_pivot_rotation({rect.x + origin.x, rect.y + origin.y}, origin, rotation)
|
||||
new_center := apply_transform(transform, {half_width, half_height})
|
||||
center_x = new_center.x
|
||||
center_y = new_center.y
|
||||
}
|
||||
|
||||
bounds_half_width, bounds_half_height := half_width, half_height
|
||||
if rotation_radians != 0 {
|
||||
expanded := rotated_aabb_half_extents(half_width, half_height, rotation_radians)
|
||||
bounds_half_width = expanded.x
|
||||
bounds_half_height = expanded.y
|
||||
}
|
||||
|
||||
prim := Primitive {
|
||||
bounds = {
|
||||
center_x - bounds_half_width - padding,
|
||||
center_y - bounds_half_height - padding,
|
||||
center_x + bounds_half_width + padding,
|
||||
center_y + bounds_half_height + padding,
|
||||
},
|
||||
color = tint,
|
||||
kind_flags = pack_kind_flags(.RRect, {.Textured}),
|
||||
rotation = rotation_radians,
|
||||
uv_rect = {uv_rect.x, uv_rect.y, uv_rect.width, uv_rect.height},
|
||||
}
|
||||
prim.params.rrect = RRect_Params {
|
||||
half_size = {half_width * dpi_scale, half_height * dpi_scale},
|
||||
radii = {
|
||||
top_right * dpi_scale,
|
||||
bottom_right * dpi_scale,
|
||||
top_left * dpi_scale,
|
||||
bottom_left * dpi_scale,
|
||||
},
|
||||
soft_px = soft_px,
|
||||
stroke_px = 0,
|
||||
}
|
||||
prepare_sdf_primitive_textured(layer, prim, id, sampler)
|
||||
}
|
||||
|
||||
// Draw a filled circle via SDF.
|
||||
//
|
||||
// Origin semantics (Convention B):
|
||||
// `origin` is a local offset from the shape's center that selects both the positioning anchor
|
||||
// and the rotation pivot. The `center` parameter specifies where that anchor point lands in
|
||||
// world space. When `origin = {0, 0}` (default), `center` is the visual center.
|
||||
// When `origin = {r, 0}`, the point `r` pixels to the right of the shape center lands at
|
||||
// `center`, shifting the shape left by `r`.
|
||||
circle :: proc(
|
||||
layer: ^Layer,
|
||||
center: [2]f32,
|
||||
@@ -576,6 +715,7 @@ circle :: proc(
|
||||
}
|
||||
|
||||
// Draw a stroked circle via SDF.
|
||||
// Origin semantics: see `circle`.
|
||||
circle_lines :: proc(
|
||||
layer: ^Layer,
|
||||
center: [2]f32,
|
||||
@@ -613,6 +753,7 @@ circle_lines :: proc(
|
||||
}
|
||||
|
||||
// Draw a filled ellipse via SDF.
|
||||
// Origin semantics: see `circle`.
|
||||
ellipse :: proc(
|
||||
layer: ^Layer,
|
||||
center: [2]f32,
|
||||
@@ -659,6 +800,7 @@ ellipse :: proc(
|
||||
}
|
||||
|
||||
// Draw a stroked ellipse via SDF.
|
||||
// Origin semantics: see `circle`.
|
||||
ellipse_lines :: proc(
|
||||
layer: ^Layer,
|
||||
center: [2]f32,
|
||||
@@ -709,6 +851,7 @@ ellipse_lines :: proc(
|
||||
}
|
||||
|
||||
// Draw a filled ring arc via SDF.
|
||||
// Origin semantics: see `circle`.
|
||||
ring :: proc(
|
||||
layer: ^Layer,
|
||||
center: [2]f32,
|
||||
@@ -751,6 +894,7 @@ ring :: proc(
|
||||
}
|
||||
|
||||
// Draw stroked ring arc outlines via SDF.
|
||||
// Origin semantics: see `circle`.
|
||||
ring_lines :: proc(
|
||||
layer: ^Layer,
|
||||
center: [2]f32,
|
||||
|
||||
Reference in New Issue
Block a user