feat: Añadir filledTriangle (rasterización scanline)
Nueva primitiva de dibujo para gráficos 3D: - FilledTriangleCommand en command.zig - Algoritmo scanline en software.zig - Ordena vértices por Y, interpola bordes - Soporta clipping correctamente Base para logos 3D sólidos y widgets avanzados. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ae600f4341
commit
de56496803
2 changed files with 135 additions and 0 deletions
|
|
@ -33,6 +33,9 @@ pub const DrawCommand = union(enum) {
|
|||
/// Draw a gradient-filled rectangle (fancy mode)
|
||||
gradient: GradientCommand,
|
||||
|
||||
/// Draw a filled triangle (for 3D graphics)
|
||||
filled_triangle: FilledTriangleCommand,
|
||||
|
||||
/// Begin clipping to a rectangle
|
||||
clip: ClipCommand,
|
||||
|
||||
|
|
@ -154,6 +157,21 @@ pub const GradientCommand = struct {
|
|||
radius: u8 = 0,
|
||||
};
|
||||
|
||||
/// Draw a filled triangle (for 3D graphics, logos, etc.)
|
||||
pub const FilledTriangleCommand = struct {
|
||||
/// First vertex
|
||||
x1: i32,
|
||||
y1: i32,
|
||||
/// Second vertex
|
||||
x2: i32,
|
||||
y2: i32,
|
||||
/// Third vertex
|
||||
x3: i32,
|
||||
y3: i32,
|
||||
/// Fill color
|
||||
color: Style.Color,
|
||||
};
|
||||
|
||||
/// Begin clipping to a rectangle
|
||||
pub const ClipCommand = struct {
|
||||
x: i32,
|
||||
|
|
@ -224,6 +242,19 @@ pub fn clipEnd() DrawCommand {
|
|||
return .clip_end;
|
||||
}
|
||||
|
||||
/// Create a filled triangle command
|
||||
pub fn filledTriangle(x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32, color: Style.Color) DrawCommand {
|
||||
return .{ .filled_triangle = .{
|
||||
.x1 = x1,
|
||||
.y1 = y1,
|
||||
.x2 = x2,
|
||||
.y2 = y2,
|
||||
.x3 = x3,
|
||||
.y3 = y3,
|
||||
.color = color,
|
||||
} };
|
||||
}
|
||||
|
||||
/// Create a rounded rect command (fancy mode)
|
||||
pub fn roundedRect(x: i32, y: i32, w: u32, h: u32, color: Style.Color, radius: u8) DrawCommand {
|
||||
return .{ .rounded_rect = .{
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ pub const SoftwareRenderer = struct {
|
|||
.rounded_rect_outline => |r| self.drawRoundedRectOutline(r),
|
||||
.shadow => |s| self.drawShadow(s),
|
||||
.gradient => |g| self.drawGradient(g),
|
||||
.filled_triangle => |tri| self.drawFilledTriangle(tri),
|
||||
.clip => |c| self.pushClip(c),
|
||||
.clip_end => self.popClip(),
|
||||
.nop => {},
|
||||
|
|
@ -477,6 +478,97 @@ pub const SoftwareRenderer = struct {
|
|||
}
|
||||
}
|
||||
|
||||
/// Draw a filled triangle using scanline algorithm
|
||||
fn drawFilledTriangle(self: *Self, tri: Command.FilledTriangleCommand) void {
|
||||
const clip = self.getClip();
|
||||
|
||||
// Sort vertices by Y coordinate (p0.y <= p1.y <= p2.y)
|
||||
var p0 = [2]i32{ tri.x1, tri.y1 };
|
||||
var p1 = [2]i32{ tri.x2, tri.y2 };
|
||||
var p2 = [2]i32{ tri.x3, tri.y3 };
|
||||
|
||||
// Bubble sort by Y
|
||||
if (p0[1] > p1[1]) {
|
||||
const tmp = p0;
|
||||
p0 = p1;
|
||||
p1 = tmp;
|
||||
}
|
||||
if (p1[1] > p2[1]) {
|
||||
const tmp = p1;
|
||||
p1 = p2;
|
||||
p2 = tmp;
|
||||
}
|
||||
if (p0[1] > p1[1]) {
|
||||
const tmp = p0;
|
||||
p0 = p1;
|
||||
p1 = tmp;
|
||||
}
|
||||
|
||||
// Early exit if triangle is degenerate (all same Y)
|
||||
if (p0[1] == p2[1]) return;
|
||||
|
||||
// Calculate inverse slopes for edge interpolation
|
||||
const total_height = p2[1] - p0[1];
|
||||
const top_height = p1[1] - p0[1];
|
||||
const bottom_height = p2[1] - p1[1];
|
||||
|
||||
// Draw scanlines from top to bottom
|
||||
var y = p0[1];
|
||||
while (y <= p2[1]) : (y += 1) {
|
||||
// Skip if outside clip region
|
||||
if (y < clip.y or y >= clip.y + @as(i32, @intCast(clip.h))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate X coordinates for this scanline
|
||||
var x_left: i32 = undefined;
|
||||
var x_right: i32 = undefined;
|
||||
|
||||
// Progress along the long edge (p0 to p2)
|
||||
const t_long: f32 = @as(f32, @floatFromInt(y - p0[1])) / @as(f32, @floatFromInt(total_height));
|
||||
const x_long = p0[0] + @as(i32, @intFromFloat(@as(f32, @floatFromInt(p2[0] - p0[0])) * t_long));
|
||||
|
||||
// Progress along the short edges
|
||||
var x_short: i32 = undefined;
|
||||
if (y < p1[1]) {
|
||||
// Upper half: interpolate p0 to p1
|
||||
if (top_height == 0) {
|
||||
x_short = p0[0];
|
||||
} else {
|
||||
const t_short: f32 = @as(f32, @floatFromInt(y - p0[1])) / @as(f32, @floatFromInt(top_height));
|
||||
x_short = p0[0] + @as(i32, @intFromFloat(@as(f32, @floatFromInt(p1[0] - p0[0])) * t_short));
|
||||
}
|
||||
} else {
|
||||
// Lower half: interpolate p1 to p2
|
||||
if (bottom_height == 0) {
|
||||
x_short = p1[0];
|
||||
} else {
|
||||
const t_short: f32 = @as(f32, @floatFromInt(y - p1[1])) / @as(f32, @floatFromInt(bottom_height));
|
||||
x_short = p1[0] + @as(i32, @intFromFloat(@as(f32, @floatFromInt(p2[0] - p1[0])) * t_short));
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure left < right
|
||||
if (x_long < x_short) {
|
||||
x_left = x_long;
|
||||
x_right = x_short;
|
||||
} else {
|
||||
x_left = x_short;
|
||||
x_right = x_long;
|
||||
}
|
||||
|
||||
// Clip X to clip region
|
||||
if (x_left < clip.x) x_left = clip.x;
|
||||
if (x_right >= clip.x + @as(i32, @intCast(clip.w))) x_right = clip.x + @as(i32, @intCast(clip.w)) - 1;
|
||||
|
||||
// Draw horizontal line
|
||||
if (x_left <= x_right) {
|
||||
const w: u32 = @intCast(x_right - x_left + 1);
|
||||
self.framebuffer.fillRect(x_left, y, w, 1, tri.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pushClip(self: *Self, c: Command.ClipCommand) void {
|
||||
if (self.clip_depth >= self.clip_stack.len) return;
|
||||
|
||||
|
|
@ -543,6 +635,18 @@ fn commandBounds(cmd: DrawCommand) ?Rect {
|
|||
);
|
||||
},
|
||||
.gradient => |g| Rect.init(g.x, g.y, g.w, g.h),
|
||||
.filled_triangle => |tri| blk: {
|
||||
const min_x = @min(@min(tri.x1, tri.x2), tri.x3);
|
||||
const min_y = @min(@min(tri.y1, tri.y2), tri.y3);
|
||||
const max_x = @max(@max(tri.x1, tri.x2), tri.x3);
|
||||
const max_y = @max(@max(tri.y1, tri.y2), tri.y3);
|
||||
break :blk Rect.init(
|
||||
min_x,
|
||||
min_y,
|
||||
@intCast(@max(1, max_x - min_x + 1)),
|
||||
@intCast(@max(1, max_y - min_y + 1)),
|
||||
);
|
||||
},
|
||||
.clip, .clip_end, .nop => null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue