Files
Zivro/src/render/cpu/ellipse.zig

114 lines
4.8 KiB
Zig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const std = @import("std");
const Document = @import("../../models/Document.zig");
const pipeline = @import("pipeline.zig");
const DrawContext = pipeline.DrawContext;
const Color = @import("dvui").Color;
const basic_models = @import("../../models/basic_models.zig");
const Point2_f = basic_models.Point2_f;
const Object = Document.Object;
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
const default_thickness: f32 = 2.0;
const std_math = std.math;
/// Эллипс с центром (0,0) и полуосями radii. Обводка — полоса расстояния до контура (чёткая линия, не круги).
/// arc_percent: 100 — полный эллипс, иначе одна дуга; обход в коде от (0,ry) по квадрантам (визуально может казаться от низа против часовой из‑за экранной Y).
/// Отрисовка в отдельный буфер и один composite, чтобы при alpha<255 пиксели не накладывались несколько раз.
pub fn draw(ctx: *DrawContext, obj: *const Object, allocator: std.mem.Allocator) !void {
const r_prop = obj.getProperty(.radii) orelse return;
const rx = r_prop.radii.x;
const ry = r_prop.radii.y;
if (rx <= 0 or ry <= 0) return;
const stroke = if (obj.getProperty(.stroke_rgba)) |s| pipeline.rgbaToPma(s.stroke_rgba) else default_stroke;
const thickness = if (obj.getProperty(.thickness)) |t| t.thickness else default_thickness;
const arc_percent = blk: {
const p = obj.getProperty(.arc_percent);
break :blk std.math.clamp(if (p) |v| v.arc_percent else 100.0, 0.0, 100.0);
};
const t = &ctx.transform;
const min_r = @min(rx, ry);
const half_norm = thickness / (2.0 * min_r);
const inner = @max(0.0, 1.0 - half_norm);
const outer = 1.0 + half_norm;
const d_inner_sq = inner * inner;
const d_outer_sq = outer * outer;
const margin = 1.0 + half_norm;
const corners = [_]Point2_f{
.{ .x = -rx * margin, .y = -ry * margin },
.{ .x = rx * margin, .y = -ry * margin },
.{ .x = rx * margin, .y = ry * margin },
.{ .x = -rx * margin, .y = ry * margin },
};
var min_bx: f32 = undefined;
var min_by: f32 = undefined;
var max_bx: f32 = undefined;
var max_by: f32 = undefined;
{
const w0 = ctx.localToWorld(corners[0].x, corners[0].y);
const b0 = ctx.worldToBufferF(w0.x, w0.y);
min_bx = b0.x;
min_by = b0.y;
max_bx = b0.x;
max_by = b0.y;
}
for (corners[1..]) |c| {
const w = ctx.localToWorld(c.x, c.y);
const b = ctx.worldToBufferF(w.x, w.y);
min_bx = @min(min_bx, b.x);
min_by = @min(min_by, b.y);
max_bx = @max(max_bx, b.x);
max_by = @max(max_by, b.y);
}
const buf_w: i32 = @intCast(ctx.buf_width);
const buf_h: i32 = @intCast(ctx.buf_height);
const x0: i32 = @max(0, @as(i32, @intFromFloat(std_math.floor(min_bx))));
const y0: i32 = @max(0, @as(i32, @intFromFloat(std_math.floor(min_by))));
const x1: i32 = @min(buf_w, @as(i32, @intFromFloat(std_math.ceil(max_bx))) + 1);
const y1: i32 = @min(buf_h, @as(i32, @intFromFloat(std_math.ceil(max_by))) + 1);
const buffer = try allocator.alloc(Color.PMA, ctx.buf_width * ctx.buf_height);
@memset(buffer, .{ .r = 0, .g = 0, .b = 0, .a = 0 });
defer allocator.free(buffer);
var stroke_ctx = ctx.*;
stroke_ctx.pixels = buffer;
stroke_ctx.replace_mode = true;
const inv_rx = 1.0 / rx;
const inv_ry = 1.0 / ry;
const arc_full = arc_percent >= 100.0;
const t_start = std_math.pi / 2.0;
const t_end_raw = t_start - 2.0 * std_math.pi * arc_percent / 100.0;
const t_end = if (t_end_raw <= -std_math.pi) t_end_raw + 2.0 * std_math.pi else t_end_raw;
var by: i32 = y0;
while (by < y1) : (by += 1) {
const buf_y = @as(f32, @floatFromInt(by)) + 0.5;
var bx: i32 = x0;
while (bx < x1) : (bx += 1) {
const buf_x = @as(f32, @floatFromInt(bx)) + 0.5;
const w = stroke_ctx.bufferToWorld(buf_x, buf_y);
const loc = stroke_ctx.worldToLocal(w.x, w.y);
const nx = loc.x * inv_rx;
const ny = loc.y * inv_ry;
const d = nx * nx + ny * ny;
if (d < d_inner_sq or d > d_outer_sq) continue;
if (!arc_full) {
const t_pt = std_math.atan2(ny, nx);
const in_arc = if (t_end <= t_start)
(t_pt >= t_end and t_pt <= t_start)
else
(t_pt >= t_end or t_pt <= t_start);
if (!in_arc) continue;
}
stroke_ctx.blendPixelAtBuffer(bx, by, stroke);
}
}
ctx.compositeDrawerContext(&stroke_ctx, t.opacity);
}