Major rendering updates
This commit is contained in:
327
draw/draw.odin
327
draw/draw.odin
@@ -4,6 +4,7 @@ import "base:runtime"
|
||||
import "core:c"
|
||||
import "core:log"
|
||||
import "core:math"
|
||||
|
||||
import "core:strings"
|
||||
import sdl "vendor:sdl3"
|
||||
import sdl_ttf "vendor:sdl3/ttf"
|
||||
@@ -27,72 +28,22 @@ BUFFER_INIT_SIZE :: 256
|
||||
INITIAL_LAYER_SIZE :: 5
|
||||
INITIAL_SCISSOR_SIZE :: 10
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Color -------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// Sentinel value: when passed as msaa_samples, `init` will use the maximum MSAA sample count
|
||||
// supported by the GPU for the swapchain format.
|
||||
MSAA_MAX :: sdl.GPUSampleCount(0xFF)
|
||||
|
||||
Color :: distinct [4]u8
|
||||
|
||||
BLACK :: Color{0, 0, 0, 255}
|
||||
WHITE :: Color{255, 255, 255, 255}
|
||||
RED :: Color{255, 0, 0, 255}
|
||||
GREEN :: Color{0, 255, 0, 255}
|
||||
BLUE :: Color{0, 0, 255, 255}
|
||||
BLANK :: Color{0, 0, 0, 0}
|
||||
|
||||
// Convert clay.Color ([4]c.float in 0–255 range) to Color.
|
||||
color_from_clay :: proc(clay_color: clay.Color) -> Color {
|
||||
return Color{u8(clay_color[0]), u8(clay_color[1]), u8(clay_color[2]), u8(clay_color[3])}
|
||||
}
|
||||
|
||||
// Convert Color to [4]f32 in 0.0–1.0 range. Useful for SDL interop (e.g. clear color).
|
||||
color_to_f32 :: proc(color: Color) -> [4]f32 {
|
||||
INV :: 1.0 / 255.0
|
||||
return {f32(color[0]) * INV, f32(color[1]) * INV, f32(color[2]) * INV, f32(color[3]) * INV}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Core types --------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
Rectangle :: struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
}
|
||||
|
||||
Sub_Batch_Kind :: enum u8 {
|
||||
Shapes, // non-indexed, white texture or user texture, mode 0
|
||||
Text, // indexed, atlas texture, mode 0
|
||||
SDF, // instanced unit quad, white texture or user texture, mode 1
|
||||
}
|
||||
|
||||
Sub_Batch :: struct {
|
||||
kind: Sub_Batch_Kind,
|
||||
offset: u32, // Shapes: vertex offset; Text: text_batch index; SDF: primitive index
|
||||
count: u32, // Shapes: vertex count; Text: always 1; SDF: primitive count
|
||||
texture_id: Texture_Id,
|
||||
sampler: Sampler_Preset,
|
||||
}
|
||||
|
||||
Layer :: struct {
|
||||
bounds: Rectangle,
|
||||
sub_batch_start: u32,
|
||||
sub_batch_len: u32,
|
||||
scissor_start: u32,
|
||||
scissor_len: u32,
|
||||
}
|
||||
|
||||
Scissor :: struct {
|
||||
bounds: sdl.Rect,
|
||||
sub_batch_start: u32,
|
||||
sub_batch_len: u32,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Global state ------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Default parameter values -----
|
||||
// Named constants for non-zero default procedure parameters. Centralizes magic numbers
|
||||
// so they can be tuned in one place and referenced by name in proc signatures.
|
||||
DFT_FEATHER_PX :: 1 // Total AA feather width in physical pixels (half on each side of boundary).
|
||||
DFT_STROKE_THICKNESS :: 1 // Default line/stroke thickness in logical pixels.
|
||||
DFT_FONT_SIZE :: 44 // Default font size in points for text rendering.
|
||||
DFT_CIRC_END_ANGLE :: 360 // Full-circle end angle in degrees (ring/arc).
|
||||
DFT_UV_RECT :: Rectangle{0, 0, 1, 1} // Full-texture UV rect (rectangle_texture).
|
||||
DFT_TINT :: WHITE // Default texture tint (rectangle_texture, clay_image).
|
||||
DFT_TEXT_COLOR :: BLACK // Default text color.
|
||||
DFT_CLEAR_COLOR :: BLACK // Default clear color for end().
|
||||
DFT_SAMPLER :: Sampler_Preset.Linear_Clamp // Default texture sampler preset.
|
||||
|
||||
GLOB: Global
|
||||
|
||||
@@ -153,6 +104,136 @@ Global :: struct {
|
||||
odin_context: runtime.Context, // Odin context captured at init for use in callbacks.
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Core types --------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// A 2D position in world space. Non-distinct alias for [2]f32 — bare literals like {100, 200}
|
||||
// work at non-ambiguous call sites.
|
||||
//
|
||||
// Coordinate system: origin is the top-left corner of the window/layer. X increases rightward,
|
||||
// Y increases downward. This matches SDL, HTML Canvas, and most 2D UI coordinate conventions.
|
||||
// All position parameters in the draw API (center, origin, start_position, end_position, etc.)
|
||||
// use this coordinate system.
|
||||
//
|
||||
// Units are logical pixels (pre-DPI-scaling). The renderer multiplies by dpi_scaling internally
|
||||
// before uploading to the GPU. A Vec2{100, 50} refers to the same visual location regardless of
|
||||
// display DPI.
|
||||
Vec2 :: [2]f32
|
||||
|
||||
// An RGBA color with 8 bits per channel. Distinct type over [4]u8 so that proc-group
|
||||
// overloads can disambiguate Color from other 4-byte structs.
|
||||
//
|
||||
// Channel order: R, G, B, A (indices 0, 1, 2, 3). Alpha 255 is fully opaque, 0 is fully
|
||||
// transparent. This matches the GPU-side layout: the shader unpacks via unpackUnorm4x8 which
|
||||
// reads the bytes in memory order as R, G, B, A and normalizes each to [0, 1].
|
||||
//
|
||||
// When used in the Primitive struct (Primitive.color), the 4 bytes are stored as a u32 in
|
||||
// native byte order and unpacked by the shader.
|
||||
Color :: [4]u8
|
||||
|
||||
BLACK :: Color{0, 0, 0, 255}
|
||||
WHITE :: Color{255, 255, 255, 255}
|
||||
RED :: Color{255, 0, 0, 255}
|
||||
GREEN :: Color{0, 255, 0, 255}
|
||||
BLUE :: Color{0, 0, 255, 255}
|
||||
BLANK :: Color{0, 0, 0, 0}
|
||||
|
||||
// Per-corner rounding radii for rectangles, specified clockwise from top-left.
|
||||
// All values are in logical pixels (pre-DPI-scaling).
|
||||
Rectangle_Radii :: struct {
|
||||
top_left: f32,
|
||||
top_right: f32,
|
||||
bottom_right: f32,
|
||||
bottom_left: f32,
|
||||
}
|
||||
|
||||
// A linear gradient between two colors along an arbitrary angle.
|
||||
// The `end_color` is the color at the end of the gradient direction; the shape's fill `color`
|
||||
// parameter acts as the start color. `angle` is in degrees: 0 = left-to-right, 90 = top-to-bottom.
|
||||
Linear_Gradient :: struct {
|
||||
end_color: Color,
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
// A radial gradient between two colors from center to edge.
|
||||
// The `outer_color` is the color at the shape's edge; the shape's fill `color` parameter
|
||||
// acts as the inner (center) color.
|
||||
Radial_Gradient :: struct {
|
||||
outer_color: Color,
|
||||
}
|
||||
|
||||
// Tagged union for specifying a gradient on any shape. Defaults to `nil` (no gradient).
|
||||
// When a gradient is active, the shape's `color` parameter becomes the start/inner color,
|
||||
// and the gradient struct carries the end/outer color plus any type-specific parameters.
|
||||
//
|
||||
// Gradient and Textured are mutually exclusive on the same primitive. If a shape uses
|
||||
// `rectangle_texture`, gradients are not applicable — use the tint color instead.
|
||||
Gradient :: union {
|
||||
Linear_Gradient,
|
||||
Radial_Gradient,
|
||||
}
|
||||
|
||||
// Convert clay.Color ([4]c.float in 0–255 range) to Color.
|
||||
color_from_clay :: #force_inline proc(clay_color: clay.Color) -> Color {
|
||||
return Color{u8(clay_color[0]), u8(clay_color[1]), u8(clay_color[2]), u8(clay_color[3])}
|
||||
}
|
||||
|
||||
// Convert Color to [4]f32 in 0.0–1.0 range. Useful for SDL interop (e.g. clear color).
|
||||
color_to_f32 :: proc(color: Color) -> [4]f32 {
|
||||
INV :: 1.0 / 255.0
|
||||
return {f32(color[0]) * INV, f32(color[1]) * INV, f32(color[2]) * INV, f32(color[3]) * INV}
|
||||
}
|
||||
|
||||
// Pre-multiply RGB channels by alpha. The tessellated vertex path and text path require
|
||||
// premultiplied colors because the blend state is ONE, ONE_MINUS_SRC_ALPHA and the
|
||||
// tessellated fragment shader passes vertex color through without further modification.
|
||||
// Users who construct Vertex structs manually for prepare_shape must premultiply their colors.
|
||||
premultiply_color :: #force_inline proc(color: Color) -> Color {
|
||||
a := u32(color[3])
|
||||
return Color {
|
||||
u8((u32(color[0]) * a + 127) / 255),
|
||||
u8((u32(color[1]) * a + 127) / 255),
|
||||
u8((u32(color[2]) * a + 127) / 255),
|
||||
color[3],
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle :: struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
}
|
||||
|
||||
Sub_Batch_Kind :: enum u8 {
|
||||
Tessellated, // non-indexed, white texture or user texture, mode 0
|
||||
Text, // indexed, atlas texture, mode 0
|
||||
SDF, // instanced unit quad, white texture or user texture, mode 1
|
||||
}
|
||||
|
||||
Sub_Batch :: struct {
|
||||
kind: Sub_Batch_Kind,
|
||||
offset: u32, // Tessellated: vertex offset; Text: text_batch index; SDF: primitive index
|
||||
count: u32, // Tessellated: vertex count; Text: always 1; SDF: primitive count
|
||||
texture_id: Texture_Id,
|
||||
sampler: Sampler_Preset,
|
||||
}
|
||||
|
||||
Layer :: struct {
|
||||
bounds: Rectangle,
|
||||
sub_batch_start: u32,
|
||||
sub_batch_len: u32,
|
||||
scissor_start: u32,
|
||||
scissor_len: u32,
|
||||
}
|
||||
|
||||
Scissor :: struct {
|
||||
bounds: sdl.Rect,
|
||||
sub_batch_start: u32,
|
||||
sub_batch_len: u32,
|
||||
}
|
||||
|
||||
Init_Options :: struct {
|
||||
// MSAA sample count. Default is ._1 (no MSAA). SDF rendering does not benefit from MSAA
|
||||
// because SDF fragments compute coverage analytically via `smoothstep`. MSAA helps for
|
||||
@@ -162,10 +243,6 @@ Init_Options :: struct {
|
||||
msaa_samples: sdl.GPUSampleCount,
|
||||
}
|
||||
|
||||
// Sentinel value: when passed as msaa_samples, `init` will use the maximum MSAA sample count
|
||||
// supported by the GPU for the swapchain format.
|
||||
MSAA_MAX :: sdl.GPUSampleCount(0xFF)
|
||||
|
||||
// Initialize the renderer. Returns false if GPU pipeline or text engine creation fails.
|
||||
@(require_results)
|
||||
init :: proc(
|
||||
@@ -378,12 +455,13 @@ new_layer :: proc(prev_layer: ^Layer, bounds: Rectangle) -> ^Layer {
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Submit shape vertices (colored triangles) to the given layer for rendering.
|
||||
// TODO: Should probably be renamed to better match tesselated naming conventions in the library.
|
||||
prepare_shape :: proc(layer: ^Layer, vertices: []Vertex) {
|
||||
if len(vertices) == 0 do return
|
||||
offset := u32(len(GLOB.tmp_shape_verts))
|
||||
append(&GLOB.tmp_shape_verts, ..vertices)
|
||||
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
|
||||
append_or_extend_sub_batch(scissor, layer, .Shapes, offset, u32(len(vertices)))
|
||||
append_or_extend_sub_batch(scissor, layer, .Tessellated, offset, u32(len(vertices)))
|
||||
}
|
||||
|
||||
// Submit an SDF primitive to the given layer for rendering.
|
||||
@@ -409,6 +487,9 @@ prepare_text :: proc(layer: ^Layer, text: Text) {
|
||||
base_x := math.round(text.position[0] * GLOB.dpi_scaling)
|
||||
base_y := math.round(text.position[1] * GLOB.dpi_scaling)
|
||||
|
||||
// Premultiply text color once — reused across all glyph vertices.
|
||||
pm_color := premultiply_color(text.color)
|
||||
|
||||
for data != nil {
|
||||
vertex_start := u32(len(GLOB.tmp_text_verts))
|
||||
index_start := u32(len(GLOB.tmp_text_indices))
|
||||
@@ -419,7 +500,7 @@ prepare_text :: proc(layer: ^Layer, text: Text) {
|
||||
uv := data.uv[i]
|
||||
append(
|
||||
&GLOB.tmp_text_verts,
|
||||
Vertex{position = {pos.x + base_x, -pos.y + base_y}, uv = {uv.x, uv.y}, color = text.color},
|
||||
Vertex{position = {pos.x + base_x, -pos.y + base_y}, uv = {uv.x, uv.y}, color = pm_color},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -457,6 +538,9 @@ prepare_text_transformed :: proc(layer: ^Layer, text: Text, transform: Transform
|
||||
|
||||
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
|
||||
|
||||
// Premultiply text color once — reused across all glyph vertices.
|
||||
pm_color := premultiply_color(text.color)
|
||||
|
||||
for data != nil {
|
||||
vertex_start := u32(len(GLOB.tmp_text_verts))
|
||||
index_start := u32(len(GLOB.tmp_text_indices))
|
||||
@@ -469,7 +553,7 @@ prepare_text_transformed :: proc(layer: ^Layer, text: Text, transform: Transform
|
||||
// so we apply directly — no per-vertex DPI divide/multiply.
|
||||
append(
|
||||
&GLOB.tmp_text_verts,
|
||||
Vertex{position = apply_transform(transform, {pos.x, -pos.y}), uv = {uv.x, uv.y}, color = text.color},
|
||||
Vertex{position = apply_transform(transform, {pos.x, -pos.y}), uv = {uv.x, uv.y}, color = pm_color},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -502,7 +586,7 @@ append_or_extend_sub_batch :: proc(
|
||||
offset: u32,
|
||||
count: u32,
|
||||
texture_id: Texture_Id = INVALID_TEXTURE,
|
||||
sampler: Sampler_Preset = .Linear_Clamp,
|
||||
sampler: Sampler_Preset = DFT_SAMPLER,
|
||||
) {
|
||||
if scissor.sub_batch_len > 0 {
|
||||
last := &GLOB.tmp_sub_batches[scissor.sub_batch_start + scissor.sub_batch_len - 1]
|
||||
@@ -595,6 +679,9 @@ prepare_clay_batch :: proc(
|
||||
|
||||
switch (render_command.commandType) {
|
||||
case clay.RenderCommandType.None:
|
||||
log.errorf(
|
||||
"Received render command with type None. This generally means we're in some kind of fucked up state.",
|
||||
)
|
||||
case clay.RenderCommandType.Text:
|
||||
render_data := render_command.renderData.text
|
||||
txt := string(render_data.stringContents.chars[:render_data.stringContents.length])
|
||||
@@ -609,46 +696,29 @@ prepare_clay_batch :: proc(
|
||||
)
|
||||
prepare_text(layer, Text{sdl_text, {bounds.x, bounds.y}, color_from_clay(render_data.textColor)})
|
||||
case clay.RenderCommandType.Image:
|
||||
// Any texture
|
||||
render_data := render_command.renderData.image
|
||||
if render_data.imageData == nil do continue
|
||||
img_data := (^Clay_Image_Data)(render_data.imageData)^
|
||||
cr := render_data.cornerRadius
|
||||
radii := [4]f32{cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft}
|
||||
radii := Rectangle_Radii {
|
||||
top_left = cr.topLeft,
|
||||
top_right = cr.topRight,
|
||||
bottom_right = cr.bottomRight,
|
||||
bottom_left = cr.bottomLeft,
|
||||
}
|
||||
|
||||
// Background color behind the image (Clay allows it)
|
||||
bg := color_from_clay(render_data.backgroundColor)
|
||||
if bg[3] > 0 {
|
||||
if radii == {0, 0, 0, 0} {
|
||||
rectangle(layer, bounds, bg)
|
||||
} else {
|
||||
rectangle_corners(layer, bounds, radii, bg)
|
||||
}
|
||||
rectangle(layer, bounds, bg, radii = radii)
|
||||
}
|
||||
|
||||
// Compute fit UVs
|
||||
uv, sampler, inner := fit_params(img_data.fit, bounds, img_data.texture_id)
|
||||
|
||||
// Draw the image — route by cornerRadius
|
||||
if radii == {0, 0, 0, 0} {
|
||||
rectangle_texture(
|
||||
layer,
|
||||
inner,
|
||||
img_data.texture_id,
|
||||
tint = img_data.tint,
|
||||
uv_rect = uv,
|
||||
sampler = sampler,
|
||||
)
|
||||
} else {
|
||||
rectangle_texture_corners(
|
||||
layer,
|
||||
inner,
|
||||
radii,
|
||||
img_data.texture_id,
|
||||
tint = img_data.tint,
|
||||
uv_rect = uv,
|
||||
sampler = sampler,
|
||||
)
|
||||
}
|
||||
// Draw the image
|
||||
rectangle_texture(layer, inner, img_data.texture_id, img_data.tint, uv, sampler, radii)
|
||||
case clay.RenderCommandType.ScissorStart:
|
||||
if bounds.width == 0 || bounds.height == 0 do continue
|
||||
|
||||
@@ -680,34 +750,38 @@ prepare_clay_batch :: proc(
|
||||
render_data := render_command.renderData.rectangle
|
||||
cr := render_data.cornerRadius
|
||||
color := color_from_clay(render_data.backgroundColor)
|
||||
radii := [4]f32{cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft}
|
||||
|
||||
if radii == {0, 0, 0, 0} {
|
||||
rectangle(layer, bounds, color)
|
||||
} else {
|
||||
rectangle_corners(layer, bounds, radii, color)
|
||||
radii := Rectangle_Radii {
|
||||
top_left = cr.topLeft,
|
||||
top_right = cr.topRight,
|
||||
bottom_right = cr.bottomRight,
|
||||
bottom_left = cr.bottomLeft,
|
||||
}
|
||||
|
||||
rectangle(layer, bounds, color, radii = radii)
|
||||
case clay.RenderCommandType.Border:
|
||||
render_data := render_command.renderData.border
|
||||
cr := render_data.cornerRadius
|
||||
color := color_from_clay(render_data.color)
|
||||
thickness := f32(render_data.width.top)
|
||||
radii := [4]f32{cr.topLeft, cr.topRight, cr.bottomRight, cr.bottomLeft}
|
||||
|
||||
if radii == {0, 0, 0, 0} {
|
||||
rectangle_lines(layer, bounds, color, thickness)
|
||||
} else {
|
||||
rectangle_corners_lines(layer, bounds, radii, color, thickness)
|
||||
radii := Rectangle_Radii {
|
||||
top_left = cr.topLeft,
|
||||
top_right = cr.topRight,
|
||||
bottom_right = cr.bottomRight,
|
||||
bottom_left = cr.bottomLeft,
|
||||
}
|
||||
|
||||
rectangle(layer, bounds, BLANK, outline_color = color, outline_width = thickness, radii = radii)
|
||||
case clay.RenderCommandType.Custom: if custom_draw != nil {
|
||||
custom_draw(layer, bounds, render_command.renderData.custom)
|
||||
} else {
|
||||
log.error("Received clay render command of type custom but no custom_draw proc provided.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render primitives. clear_color is the background fill before any layers are drawn.
|
||||
end :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, clear_color: Color = BLACK) {
|
||||
end :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, clear_color: Color = DFT_CLEAR_COLOR) {
|
||||
cmd_buffer := sdl.AcquireGPUCommandBuffer(device)
|
||||
if cmd_buffer == nil {
|
||||
log.panicf("Failed to acquire GPU command buffer: %s", sdl.GetError())
|
||||
@@ -740,7 +814,16 @@ end :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, clear_color: Color = BL
|
||||
render_texture = GLOB.msaa_texture
|
||||
}
|
||||
|
||||
clear_color_f32 := color_to_f32(clear_color)
|
||||
// Premultiply clear color: the blend state is ONE, ONE_MINUS_SRC_ALPHA (premultiplied),
|
||||
// so the clear color must also be premultiplied for correct background compositing.
|
||||
clear_color_straight := color_to_f32(clear_color)
|
||||
clear_alpha := clear_color_straight[3]
|
||||
clear_color_f32 := [4]f32 {
|
||||
clear_color_straight[0] * clear_alpha,
|
||||
clear_color_straight[1] * clear_alpha,
|
||||
clear_color_straight[2] * clear_alpha,
|
||||
clear_alpha,
|
||||
}
|
||||
|
||||
// Draw layers. One render pass per layer; sub-batches draw in submission order within each scissor.
|
||||
for &layer, index in GLOB.layers {
|
||||
@@ -948,10 +1031,20 @@ Transform_2D :: struct {
|
||||
// origin – pivot point in local space (measured from the shape's natural reference point).
|
||||
// rotation_deg – rotation in degrees, counter-clockwise.
|
||||
//
|
||||
build_pivot_rotation :: proc(position: [2]f32, origin: [2]f32, rotation_deg: f32) -> Transform_2D {
|
||||
build_pivot_rotation :: proc(position: Vec2, origin: Vec2, rotation_deg: f32) -> Transform_2D {
|
||||
radians := math.to_radians(rotation_deg)
|
||||
cos_angle := math.cos(radians)
|
||||
sin_angle := math.sin(radians)
|
||||
return build_pivot_rotation_sc(position, origin, cos_angle, sin_angle)
|
||||
}
|
||||
|
||||
// Variant of build_pivot_rotation that accepts pre-computed cos/sin values,
|
||||
// avoiding redundant trigonometry when the caller has already computed them.
|
||||
build_pivot_rotation_sc :: #force_inline proc(
|
||||
position: Vec2,
|
||||
origin: Vec2,
|
||||
cos_angle, sin_angle: f32,
|
||||
) -> Transform_2D {
|
||||
return Transform_2D {
|
||||
m00 = cos_angle,
|
||||
m01 = -sin_angle,
|
||||
@@ -963,7 +1056,7 @@ build_pivot_rotation :: proc(position: [2]f32, origin: [2]f32, rotation_deg: f32
|
||||
}
|
||||
|
||||
// Apply the transform to a local-space point, producing a world-space point.
|
||||
apply_transform :: #force_inline proc(transform: Transform_2D, point: [2]f32) -> [2]f32 {
|
||||
apply_transform :: #force_inline proc(transform: Transform_2D, point: Vec2) -> Vec2 {
|
||||
return {
|
||||
transform.m00 * point.x + transform.m01 * point.y + transform.tx,
|
||||
transform.m10 * point.x + transform.m11 * point.y + transform.ty,
|
||||
@@ -973,7 +1066,7 @@ apply_transform :: #force_inline proc(transform: Transform_2D, point: [2]f32) ->
|
||||
// Fast-path check callers use BEFORE building a transform.
|
||||
// Returns true if either the origin is non-zero or rotation is non-zero,
|
||||
// meaning a transform actually needs to be computed.
|
||||
needs_transform :: #force_inline proc(origin: [2]f32, rotation: f32) -> bool {
|
||||
needs_transform :: #force_inline proc(origin: Vec2, rotation: f32) -> bool {
|
||||
return origin != {0, 0} || rotation != 0
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user