package draw import "core:math" // ----- Internal helpers ---- // Internal extrude_line :: proc( start, end_pos: Vec2, thickness: f32, color: Color, vertices: []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 := 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 } // Create a vertex for solid-color shape drawing (no texture, UV defaults to zero). // 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: Vec2, color: Color) -> Vertex { return Vertex{position = position, color = premultiply_color(color)} } emit_rectangle :: proc(x, y, width, height: f32, color: Color, vertices: []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) } // Internal 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) } //Internal // // 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). compute_pivot_center :: proc(center: Vec2, origin: Vec2, sin_angle, cos_angle: f32) -> Vec2 { if origin == {0, 0} do return center return( center + {cos_angle * (-origin.x) - sin_angle * (-origin.y), sin_angle * (-origin.x) + cos_angle * (-origin.y)} \ ) } // Compute the AABB half-extents of a rectangle with half-size (half_width, half_height) rotated by the given cos/sin. rotated_aabb_half_extents :: proc(half_width, half_height, cos_angle, sin_angle: f32) -> [2]f32 { cos_abs := abs(cos_angle) sin_abs := abs(sin_angle) return {half_width * cos_abs + half_height * sin_abs, half_width * sin_abs + half_height * cos_abs} } // Pack sin/cos into the Primitive.rotation_sc field as two f16 values. pack_rotation_sc :: #force_inline proc(sin_angle, cos_angle: f32) -> u32 { return pack_f16_pair(f16(sin_angle), f16(cos_angle)) } // Internal // // Build an RRect Primitive with bounds, params, and rotation computed from rectangle geometry. // The caller sets color, flags, and uv fields on the returned primitive before submitting. build_rrect_primitive :: proc( rect: Rectangle, radii: Rectangle_Radii, origin: Vec2, rotation: f32, feather_px: f32, ) -> Primitive { max_radius := min(rect.width, rect.height) * 0.5 clamped_top_left := clamp(radii.top_left, 0, max_radius) clamped_top_right := clamp(radii.top_right, 0, max_radius) clamped_bottom_right := clamp(radii.bottom_right, 0, max_radius) clamped_bottom_left := clamp(radii.bottom_left, 0, max_radius) half_feather := feather_px * 0.5 padding := half_feather / GLOB.dpi_scaling dpi_scale := GLOB.dpi_scaling half_width := rect.width * 0.5 half_height := rect.height * 0.5 center_x := rect.x + half_width - origin.x center_y := rect.y + half_height - origin.y sin_angle: f32 = 0 cos_angle: f32 = 1 has_rotation := false if needs_transform(origin, rotation) { rotation_radians := math.to_radians(rotation) sin_angle, cos_angle = math.sincos(rotation_radians) has_rotation = rotation != 0 transform := build_pivot_rotation_sc({rect.x + origin.x, rect.y + origin.y}, origin, cos_angle, sin_angle) 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 has_rotation { expanded := rotated_aabb_half_extents(half_width, half_height, cos_angle, sin_angle) 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, }, rotation_sc = has_rotation ? pack_rotation_sc(sin_angle, cos_angle) : 0, } prim.params.rrect = RRect_Params { half_size = {half_width * dpi_scale, half_height * dpi_scale}, radii = { clamped_bottom_right * dpi_scale, clamped_top_right * dpi_scale, clamped_bottom_left * dpi_scale, clamped_top_left * dpi_scale, }, half_feather = half_feather, } return prim } // Internal // // Build an RRect Primitive for a circle (fully-rounded square RRect). // The caller sets color, flags, and uv fields on the returned primitive before submitting. build_circle_primitive :: proc( center: Vec2, radius: f32, origin: Vec2, rotation: f32, feather_px: f32, ) -> Primitive { half_feather := feather_px * 0.5 padding := half_feather / GLOB.dpi_scaling dpi_scale := GLOB.dpi_scaling actual_center := center if origin != {0, 0} { sin_a, cos_a := math.sincos(math.to_radians(rotation)) actual_center = compute_pivot_center(center, origin, sin_a, cos_a) } prim := Primitive { bounds = { actual_center.x - radius - padding, actual_center.y - radius - padding, actual_center.x + radius + padding, actual_center.y + radius + padding, }, } scaled_radius := radius * dpi_scale prim.params.rrect = RRect_Params { half_size = {scaled_radius, scaled_radius}, radii = {scaled_radius, scaled_radius, scaled_radius, scaled_radius}, half_feather = half_feather, } return prim } // Internal // // Build an Ellipse Primitive with bounds, params, and rotation computed from ellipse geometry. // The caller sets color, flags, and uv fields on the returned primitive before submitting. build_ellipse_primitive :: proc( center: Vec2, radius_horizontal, radius_vertical: f32, origin: Vec2, rotation: f32, feather_px: f32, ) -> Primitive { half_feather := feather_px * 0.5 padding := half_feather / GLOB.dpi_scaling dpi_scale := GLOB.dpi_scaling actual_center := center sin_angle: f32 = 0 cos_angle: f32 = 1 has_rotation := false if needs_transform(origin, rotation) { rotation_radians := math.to_radians(rotation) sin_angle, cos_angle = math.sincos(rotation_radians) actual_center = compute_pivot_center(center, origin, sin_angle, cos_angle) has_rotation = rotation != 0 } bound_horizontal, bound_vertical := radius_horizontal, radius_vertical if has_rotation { expanded := rotated_aabb_half_extents(radius_horizontal, radius_vertical, cos_angle, sin_angle) bound_horizontal = expanded.x bound_vertical = expanded.y } prim := Primitive { bounds = { actual_center.x - bound_horizontal - padding, actual_center.y - bound_vertical - padding, actual_center.x + bound_horizontal + padding, actual_center.y + bound_vertical + padding, }, rotation_sc = has_rotation ? pack_rotation_sc(sin_angle, cos_angle) : 0, } prim.params.ellipse = Ellipse_Params { radii = {radius_horizontal * dpi_scale, radius_vertical * dpi_scale}, half_feather = half_feather, } return prim } // Internal // // Build an NGon Primitive with bounds, params, and rotation computed from polygon geometry. // The caller sets color, flags, and uv fields on the returned primitive before submitting. build_polygon_primitive :: proc( center: Vec2, sides: int, radius: f32, origin: Vec2, rotation: f32, feather_px: f32, ) -> Primitive { half_feather := feather_px * 0.5 padding := half_feather / GLOB.dpi_scaling dpi_scale := GLOB.dpi_scaling actual_center := center if origin != {0, 0} && rotation != 0 { sin_a, cos_a := math.sincos(math.to_radians(rotation)) actual_center = compute_pivot_center(center, origin, sin_a, cos_a) } rotation_radians := math.to_radians(rotation) sin_rot, cos_rot := math.sincos(rotation_radians) prim := Primitive { bounds = { actual_center.x - radius - padding, actual_center.y - radius - padding, actual_center.x + radius + padding, actual_center.y + radius + padding, }, rotation_sc = rotation != 0 ? pack_rotation_sc(sin_rot, cos_rot) : 0, } prim.params.ngon = NGon_Params { radius = radius * math.cos(math.PI / f32(sides)) * dpi_scale, sides = f32(sides), half_feather = half_feather, } return prim } // Internal // // Build a Ring_Arc Primitive with bounds and params computed from ring/arc geometry. // Pre-computes the angular boundary normals on the CPU so the fragment shader needs // no per-pixel sin/cos. The radial SDF uses max(inner-r, r-outer) which correctly // handles pie slices (inner_radius = 0) and full rings. // The caller sets color, flags, and uv fields on the returned primitive before submitting. build_ring_arc_primitive :: proc( center: Vec2, inner_radius, outer_radius: f32, start_angle: f32, end_angle: f32, origin: Vec2, rotation: f32, feather_px: f32, ) -> ( Primitive, Shape_Flags, ) { half_feather := feather_px * 0.5 padding := half_feather / GLOB.dpi_scaling dpi_scale := GLOB.dpi_scaling actual_center := center rotation_offset: f32 = 0 if needs_transform(origin, rotation) { sin_a, cos_a := math.sincos(math.to_radians(rotation)) actual_center = compute_pivot_center(center, origin, sin_a, cos_a) rotation_offset = math.to_radians(rotation) } start_rad := math.to_radians(start_angle) + rotation_offset end_rad := math.to_radians(end_angle) + rotation_offset // Normalize arc span to [0, 2π] arc_span := end_rad - start_rad if arc_span < 0 { arc_span += 2 * math.PI } // Pre-compute edge normals and arc flags on CPU — no per-pixel trig needed. // arc_flags: {} = full ring, {.Arc_Narrow} = span ≤ π (intersect), {.Arc_Wide} = span > π (union) arc_flags: Shape_Flags = {} normal_start: [2]f32 = {} normal_end: [2]f32 = {} if arc_span < 2 * math.PI - 0.001 { sin_start, cos_start := math.sincos(start_rad) sin_end, cos_end := math.sincos(end_rad) normal_start = {sin_start, -cos_start} normal_end = {-sin_end, cos_end} arc_flags = arc_span <= math.PI ? {.Arc_Narrow} : {.Arc_Wide} } prim := Primitive { bounds = { actual_center.x - outer_radius - padding, actual_center.y - outer_radius - padding, actual_center.x + outer_radius + padding, actual_center.y + outer_radius + padding, }, } prim.params.ring_arc = Ring_Arc_Params { inner_radius = inner_radius * dpi_scale, outer_radius = outer_radius * dpi_scale, normal_start = normal_start, normal_end = normal_end, half_feather = half_feather, } return prim, arc_flags } // Apply gradient and outline effects to a primitive. Sets flags, uv.effects, and expands bounds. // All parameters (outline_width) are in logical pixels, matching the rest of the public API. // The helper converts to physical pixels for GPU packing internally. @(private) apply_shape_effects :: proc( prim: ^Primitive, kind: Shape_Kind, gradient: Gradient, outline_color: Color, outline_width: f32, extra_flags: Shape_Flags = {}, ) { flags: Shape_Flags = extra_flags gradient_dir_sc: u32 = 0 switch g in gradient { case Linear_Gradient: flags += {.Gradient} prim.uv.effects.gradient_color = g.end_color rad := math.to_radians(g.angle) sin_a, cos_a := math.sincos(rad) gradient_dir_sc = pack_f16_pair(f16(cos_a), f16(sin_a)) case Radial_Gradient: flags += {.Gradient, .Gradient_Radial} prim.uv.effects.gradient_color = g.outer_color case: } outline_packed: u32 = 0 if outline_width > 0 { flags += {.Outline} prim.uv.effects.outline_color = outline_color outline_packed = pack_f16_pair(f16(outline_width * GLOB.dpi_scaling), 0) // Expand bounds to contain the outline (bounds are in logical pixels) prim.bounds[0] -= outline_width prim.bounds[1] -= outline_width prim.bounds[2] += outline_width prim.bounds[3] += outline_width } // Set .Rotated flag if rotation_sc was populated by the build proc if prim.rotation_sc != 0 { flags += {.Rotated} } prim.uv.effects.gradient_dir_sc = gradient_dir_sc prim.uv.effects.outline_packed = outline_packed prim.flags = pack_kind_flags(kind, flags) } // --------------------------------------------------------------------------------------------------------------------- // ----- SDF Rectangle procs ----------- // --------------------------------------------------------------------------------------------------------------------- // Draw a filled rectangle via SDF with optional per-corner rounding radii. // Use `uniform_radii(rect, roundness)` to compute uniform radii from a 0–1 fraction. // // 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. // Rotation always occurs around the anchor point. rectangle :: proc( layer: ^Layer, rect: Rectangle, color: Color, gradient: Gradient = nil, outline_color: Color = {}, outline_width: f32 = 0, radii: Rectangle_Radii = {}, origin: Vec2 = {}, rotation: f32 = 0, feather_px: f32 = DFT_FEATHER_PX, ) { prim := build_rrect_primitive(rect, radii, origin, rotation, feather_px) prim.color = color apply_shape_effects(&prim, .RRect, gradient, outline_color, outline_width) prepare_sdf_primitive(layer, prim) } // Draw a rectangle with a texture fill via SDF with optional per-corner rounding radii. // Texture and gradient/outline are mutually exclusive (they share the same storage in the // primitive). To outline a textured rect, draw the texture first, then a stroke-only rect on top. // Origin semantics: see `rectangle`. rectangle_texture :: proc( layer: ^Layer, rect: Rectangle, id: Texture_Id, tint: Color = DFT_TINT, uv_rect: Rectangle = DFT_UV_RECT, sampler: Sampler_Preset = DFT_SAMPLER, radii: Rectangle_Radii = {}, origin: Vec2 = {}, rotation: f32 = 0, feather_px: f32 = DFT_FEATHER_PX, ) { prim := build_rrect_primitive(rect, radii, origin, rotation, feather_px) prim.color = tint tex_flags: Shape_Flags = {.Textured} if prim.rotation_sc != 0 { tex_flags += {.Rotated} } prim.flags = pack_kind_flags(.RRect, tex_flags) prim.uv.uv_rect = {uv_rect.x, uv_rect.y, uv_rect.width, uv_rect.height} prepare_sdf_primitive_textured(layer, prim, id, sampler) } // --------------------------------------------------------------------------------------------------------------------- // ----- SDF Circle procs (emit RRect primitives) ------ // --------------------------------------------------------------------------------------------------------------------- // Draw a filled circle via SDF (emitted as a fully-rounded RRect). // // 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: Vec2, radius: f32, color: Color, gradient: Gradient = nil, outline_color: Color = {}, outline_width: f32 = 0, origin: Vec2 = {}, rotation: f32 = 0, feather_px: f32 = DFT_FEATHER_PX, ) { prim := build_circle_primitive(center, radius, origin, rotation, feather_px) prim.color = color apply_shape_effects(&prim, .RRect, gradient, outline_color, outline_width) prepare_sdf_primitive(layer, prim) } // --------------------------------------------------------------------------------------------------------------------- // ----- SDF Ellipse procs (emit Ellipse primitives) --- // --------------------------------------------------------------------------------------------------------------------- // Draw a filled ellipse via SDF. // Origin semantics: see `circle`. ellipse :: proc( layer: ^Layer, center: Vec2, radius_horizontal, radius_vertical: f32, color: Color, gradient: Gradient = nil, outline_color: Color = {}, outline_width: f32 = 0, origin: Vec2 = {}, rotation: f32 = 0, feather_px: f32 = DFT_FEATHER_PX, ) { prim := build_ellipse_primitive(center, radius_horizontal, radius_vertical, origin, rotation, feather_px) prim.color = color apply_shape_effects(&prim, .Ellipse, gradient, outline_color, outline_width) prepare_sdf_primitive(layer, prim) } // --------------------------------------------------------------------------------------------------------------------- // ----- SDF Polygon procs (emit NGon primitives) ------ // --------------------------------------------------------------------------------------------------------------------- // Draw a filled regular polygon via SDF. // `sides` must be >= 3. The polygon is inscribed in a circle of the given `radius`. // Origin semantics: see `circle`. polygon :: proc( layer: ^Layer, center: Vec2, sides: int, radius: f32, color: Color, gradient: Gradient = nil, outline_color: Color = {}, outline_width: f32 = 0, origin: Vec2 = {}, rotation: f32 = 0, feather_px: f32 = DFT_FEATHER_PX, ) { if sides < 3 do return prim := build_polygon_primitive(center, sides, radius, origin, rotation, feather_px) prim.color = color apply_shape_effects(&prim, .NGon, gradient, outline_color, outline_width) prepare_sdf_primitive(layer, prim) } // --------------------------------------------------------------------------------------------------------------------- // ----- SDF Ring / Arc procs (emit Ring_Arc primitives) ---- // --------------------------------------------------------------------------------------------------------------------- // Draw a ring, arc, or pie slice via SDF. // Full ring by default. Pass start_angle/end_angle (degrees) for partial arcs. // Use inner_radius = 0 for pie slices (sectors). // Origin semantics: see `circle`. ring :: proc( layer: ^Layer, center: Vec2, inner_radius, outer_radius: f32, color: Color, gradient: Gradient = nil, outline_color: Color = {}, outline_width: f32 = 0, start_angle: f32 = 0, end_angle: f32 = DFT_CIRC_END_ANGLE, origin: Vec2 = {}, rotation: f32 = 0, feather_px: f32 = DFT_FEATHER_PX, ) { prim, arc_flags := build_ring_arc_primitive( center, inner_radius, outer_radius, start_angle, end_angle, origin, rotation, feather_px, ) prim.color = color apply_shape_effects(&prim, .Ring_Arc, gradient, outline_color, outline_width, arc_flags) prepare_sdf_primitive(layer, prim) } // --------------------------------------------------------------------------------------------------------------------- // ----- SDF Line procs (emit rotated RRect primitives) ---- // --------------------------------------------------------------------------------------------------------------------- // Draw a line segment via SDF (emitted as a rotated capsule-shaped RRect). // Round caps are produced by setting corner radii equal to half the thickness. line :: proc( layer: ^Layer, start_position, end_position: Vec2, color: Color, thickness: f32 = DFT_STROKE_THICKNESS, outline_color: Color = {}, outline_width: f32 = 0, feather_px: f32 = DFT_FEATHER_PX, ) { delta_x := end_position.x - start_position.x delta_y := end_position.y - start_position.y seg_length := math.sqrt(delta_x * delta_x + delta_y * delta_y) if seg_length < 0.0001 do return rotation_radians := math.atan2(delta_y, delta_x) sin_angle, cos_angle := math.sincos(rotation_radians) center_x := (start_position.x + end_position.x) * 0.5 center_y := (start_position.y + end_position.y) * 0.5 half_length := seg_length * 0.5 half_thickness := thickness * 0.5 cap_radius := half_thickness half_feather := feather_px * 0.5 padding := half_feather / GLOB.dpi_scaling dpi_scale := GLOB.dpi_scaling // Expand bounds for rotation bounds_half := rotated_aabb_half_extents(half_length + cap_radius, half_thickness, cos_angle, sin_angle) prim := Primitive { bounds = { center_x - bounds_half.x - padding, center_y - bounds_half.y - padding, center_x + bounds_half.x + padding, center_y + bounds_half.y + padding, }, color = color, rotation_sc = pack_rotation_sc(sin_angle, cos_angle), } prim.params.rrect = RRect_Params { half_size = {(half_length + cap_radius) * dpi_scale, half_thickness * dpi_scale}, radii = { cap_radius * dpi_scale, cap_radius * dpi_scale, cap_radius * dpi_scale, cap_radius * dpi_scale, }, half_feather = half_feather, } apply_shape_effects(&prim, .RRect, nil, outline_color, outline_width) prepare_sdf_primitive(layer, prim) } // Draw a line strip via decomposed SDF line segments. line_strip :: proc( layer: ^Layer, points: []Vec2, color: Color, thickness: f32 = DFT_STROKE_THICKNESS, outline_color: Color = {}, outline_width: f32 = 0, feather_px: f32 = DFT_FEATHER_PX, ) { if len(points) < 2 do return for i in 0 ..< len(points) - 1 { line(layer, points[i], points[i + 1], color, thickness, outline_color, outline_width, feather_px) } } // --------------------------------------------------------------------------------------------------------------------- // ----- Helpers ---------------- // --------------------------------------------------------------------------------------------------------------------- // Returns uniform radii (all corners the same) as a fraction of the shorter side. // `roundness` is clamped to [0, 1]; 0 = sharp corners, 1 = fully rounded (stadium or circle). uniform_radii :: #force_inline proc(rect: Rectangle, roundness: f32) -> Rectangle_Radii { cr := min(rect.width, rect.height) * clamp(roundness, 0, 1) * 0.5 return {cr, cr, cr, cr} } // Return Vec2 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 rectangle's top-left) --------------------------------------------- center_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 { return {rectangle.width * 0.5, rectangle.height * 0.5} } top_left_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 { return {0, 0} } top_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 { return {rectangle.width * 0.5, 0} } top_right_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 { return {rectangle.width, 0} } left_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 { return {0, rectangle.height * 0.5} } right_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 { return {rectangle.width, rectangle.height * 0.5} } bottom_left_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 { return {0, rectangle.height} } bottom_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 { return {rectangle.width * 0.5, rectangle.height} } bottom_right_of_rectangle :: #force_inline proc(rectangle: Rectangle) -> Vec2 { return {rectangle.width, rectangle.height} } // ----- Triangle anchors (origin measured from AABB top-left) ----------------------------------------------------- center_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 { bounds_min := Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} return (v1 + v2 + v3) / 3 - bounds_min } top_left_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 { return {0, 0} } top_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 { min_x := min(v1.x, v2.x, v3.x) max_x := max(v1.x, v2.x, v3.x) return {(max_x - min_x) * 0.5, 0} } top_right_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 { min_x := min(v1.x, v2.x, v3.x) max_x := max(v1.x, v2.x, v3.x) return {max_x - min_x, 0} } left_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 { min_y := min(v1.y, v2.y, v3.y) max_y := max(v1.y, v2.y, v3.y) return {0, (max_y - min_y) * 0.5} } right_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 { bounds_min := Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} bounds_max := Vec2{max(v1.x, v2.x, v3.x), max(v1.y, v2.y, v3.y)} return {bounds_max.x - bounds_min.x, (bounds_max.y - bounds_min.y) * 0.5} } bottom_left_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 { min_y := min(v1.y, v2.y, v3.y) max_y := max(v1.y, v2.y, v3.y) return {0, max_y - min_y} } bottom_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 { bounds_min := Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} bounds_max := Vec2{max(v1.x, v2.x, v3.x), max(v1.y, v2.y, v3.y)} return {(bounds_max.x - bounds_min.x) * 0.5, bounds_max.y - bounds_min.y} } bottom_right_of_triangle :: #force_inline proc(v1, v2, v3: Vec2) -> Vec2 { bounds_min := Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)} bounds_max := Vec2{max(v1.x, v2.x, v3.x), max(v1.y, v2.y, v3.y)} return bounds_max - bounds_min }