feat: Добавил толщину линий, унифицировал геометрию

Переименовал основные геометрические модели (Point, Size, Rect, Scale, Radii), явно разделив их на типы с плавающей точкой (_f) и целочисленные (_i). Обновил использование этих типов во всем проекте для улучшения типобезопасности и ясности.

Ввел новое свойство thickness для объектов и реализовал его применение при отрисовке линий и ломаных. Добавил Point2_i для целочисленных координат буфера в конвейере отрисовки.
This commit is contained in:
2026-02-25 00:57:55 +03:00
parent 1d995995e7
commit 0114db1f48
19 changed files with 124 additions and 106 deletions

View File

@@ -1,16 +1,16 @@
const std = @import("std");
const basic_models = @import("basic_models.zig");
const Size = basic_models.Size;
const Size_f = basic_models.Size_f;
const Document = @This();
pub const Object = @import("Object.zig");
const shape = @import("shape/shape.zig");
size: Size,
size: Size_f,
allocator: std.mem.Allocator,
objects: std.ArrayList(Object),
pub fn init(allocator: std.mem.Allocator, size: Size) Document {
pub fn init(allocator: std.mem.Allocator, size: Size_f) Document {
return .{
.size = size,
.allocator = allocator,

View File

@@ -18,6 +18,7 @@ const default_common_data = [_]PropertyData{
.{ .opacity = 1.0 },
.{ .locked = false },
.{ .stroke_rgba = 0x000000FF },
.{ .thickness = 2.0 },
};
pub const defaultCommonProperties: [default_common_data.len]Property = blk: {
@@ -47,7 +48,6 @@ pub fn setProperty(self: *Object, allocator: std.mem.Allocator, prop: Property)
return;
}
}
std.debug.print("Property not found: {s}\n", .{@tagName(prop.data)});
return error.PropertyNotFound;
}

View File

@@ -1,23 +1,23 @@
const std = @import("std");
const basic_models = @import("basic_models.zig");
const Point2 = basic_models.Point2;
const Scale2 = basic_models.Scale2;
const Size = basic_models.Size;
const Radii = basic_models.Radii;
const Point2_f = basic_models.Point2_f;
const Scale2_f = basic_models.Scale2_f;
const Size_f = basic_models.Size_f;
const Radii_f = basic_models.Radii_f;
pub const Data = union(enum) {
position: Point2,
position: Point2_f,
angle: f32,
scale: Scale2,
scale: Scale2_f,
visible: bool,
opacity: f32,
locked: bool,
size: Size,
radii: Radii,
end_point: Point2,
size: Size_f,
radii: Radii_f,
end_point: Point2_f,
points: std.ArrayList(Point2),
points: std.ArrayList(Point2_f),
fill_rgba: u32,
stroke_rgba: u32,

View File

@@ -1,37 +1,43 @@
pub const ImageRect = struct {
pub const Rect_i = struct {
x: u32,
y: u32,
w: u32,
h: u32,
};
pub const ImageSize = struct {
pub const Size_i = struct {
w: u32,
h: u32,
};
pub const Size = struct {
width: f32,
height: f32,
pub const Size_f = struct {
w: f32,
h: f32,
};
pub const Point2 = struct {
pub const Point2_f = struct {
x: f32 = 0,
y: f32 = 0,
};
pub const Radii = struct {
/// Целочисленная точка (например, координаты в буфере пикселей).
pub const Point2_i = struct {
x: i32 = 0,
y: i32 = 0,
};
pub const Radii_f = struct {
x: f32,
y: f32,
};
pub const Scale2 = struct {
pub const Scale2_f = struct {
scale_x: f32 = 1,
scale_y: f32 = 1,
};
/// Прямоугольник в координатах документа (f32), например локальные границы объекта.
pub const Rect = struct {
pub const Rect_f = struct {
x: f32 = 0,
y: f32 = 0,
w: f32 = 0,

View File

@@ -3,10 +3,10 @@ const Document = @import("Document.zig");
const Object = Document.Object;
const shape = @import("shape/shape.zig");
const basic_models = @import("basic_models.zig");
const Size = basic_models.Size;
const Point2 = basic_models.Point2;
const Scale2 = basic_models.Scale2;
const Radii = basic_models.Radii;
const Size_f = basic_models.Size_f;
const Point2_f = basic_models.Point2_f;
const Scale2_f = basic_models.Scale2_f;
const Radii_f = basic_models.Radii_f;
fn randFloat(rng: std.Random, min: f32, max: f32) f32 {
return min + (max - min) * rng.float(f32);
@@ -26,10 +26,10 @@ fn randomShapeKind(rng: std.Random) Object.ShapeKind {
}
/// Случайно заполняет все доступные свойства объекта; позиция — в пределах документа.
fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size, obj: *Object, rng: std.Random) !void {
fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size_f, obj: *Object, rng: std.Random) !void {
const margin: f32 = 8;
const max_x = @max(0, doc_size.width - margin);
const max_y = @max(0, doc_size.height - margin);
const max_x = @max(0, doc_size.w - margin);
const max_y = @max(0, doc_size.h - margin);
try obj.setProperty(allocator, .{ .data = .{
.position = .{
@@ -44,17 +44,19 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size
.scale_y = randFloat(rng, 0.25, 2.0),
},
} });
try obj.setProperty(allocator, .{ .data = .{ .visible = rng.boolean() } });
try obj.setProperty(allocator, .{ .data = .{ .visible = true } });
try obj.setProperty(allocator, .{ .data = .{ .opacity = randFloat(rng, 0.3, 1.0) } });
try obj.setProperty(allocator, .{ .data = .{ .locked = rng.boolean() } });
const stroke = randRgba(rng);
try obj.setProperty(allocator, .{ .data = .{ .stroke_rgba = stroke } });
obj.setProperty(allocator, .{ .data = .{ .fill_rgba = randRgba(rng) } }) catch {};
const thickness = randFloat(rng, max_x * 0.01, max_x * 0.1);
try obj.setProperty(allocator, .{ .data = .{ .thickness = thickness } });
switch (obj.shape) {
.line => {
const len = randFloat(rng, 20, @min(doc_size.width, doc_size.height) * 0.5);
const len = randFloat(rng, 20, @min(doc_size.w, doc_size.h) * 0.5);
const angle = randFloat(rng, 0, 2 * std.math.pi);
try obj.setProperty(allocator, .{ .data = .{
.end_point = .{
@@ -64,7 +66,7 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size
} });
},
.ellipse => {
const max_r = @min(120, @min(doc_size.width / 4, doc_size.height / 4));
const max_r = @min(120, @min(doc_size.w / 4, doc_size.h / 4));
try obj.setProperty(allocator, .{ .data = .{
.radii = .{
.x = randFloat(rng, 8, @max(8, max_r)),
@@ -73,7 +75,7 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size
} });
},
.broken => {
var points = std.ArrayList(Point2).empty;
var points = std.ArrayList(Point2_f).empty;
const n = rng.intRangeLessThan(usize, 2, 9);
var x: f32 = 0;
var y: f32 = 0;
@@ -96,7 +98,7 @@ pub fn addRandomShapes(doc: *Document, rng: std.Random) !void {
var total_count: usize = 0;
const allocator = doc.allocator;
const n_root = rng.intRangeLessThan(usize, 1, 5);
const n_root = rng.intRangeLessThan(usize, 6, 15);
for (0..n_root) |_| {
if (total_count >= max_total) break;
var obj = try shape.createObject(allocator, randomShapeKind(rng));

View File

@@ -1,7 +1,7 @@
const std = @import("std");
const Object = @import("../Object.zig");
const PropertyData = @import("../Property.zig").Data;
const Rect = @import("../basic_models.zig").Rect;
const Rect_f = @import("../basic_models.zig").Rect_f;
const shape_mod = @import("shape.zig");
/// Теги обязательных свойств (заглушка: arc пока не реализован).
@@ -17,7 +17,7 @@ pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object)
}
/// Локальные границы дуги (заглушка: пока не реализовано).
pub fn getLocalBounds(obj: *const Object) !Rect {
pub fn getLocalBounds(obj: *const Object) !Rect_f {
try shape_mod.ensure(obj, .arc);
return error.ArcNotImplemented;
}

View File

@@ -2,12 +2,12 @@ const std = @import("std");
const Object = @import("../Object.zig");
const Property = @import("../Property.zig").Property;
const PropertyData = @import("../Property.zig").Data;
const Point2 = @import("../basic_models.zig").Point2;
const Rect = @import("../basic_models.zig").Rect;
const Point2_f = @import("../basic_models.zig").Point2_f;
const Rect_f = @import("../basic_models.zig").Rect_f;
const shape_mod = @import("shape.zig");
/// Точки ломаной по умолчанию (для создания).
pub const default_points = [_]Point2{
pub const default_points = [_]Point2_f{
.{ .x = 0, .y = 0 },
.{ .x = 80, .y = 0 },
.{ .x = 80, .y = 60 },
@@ -22,13 +22,13 @@ pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
/// Добавляет к объекту свойства по умолчанию для ломаной (points из default_points).
pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void {
var points = std.ArrayList(Point2).empty;
var points = std.ArrayList(Point2_f).empty;
try points.appendSlice(allocator, &default_points);
try obj.properties.append(allocator, .{ .data = .{ .points = points } });
}
/// Локальные границы ломаной: AABB по всем точкам.
pub fn getLocalBounds(obj: *const Object) !Rect {
pub fn getLocalBounds(obj: *const Object) !Rect_f {
try shape_mod.ensure(obj, .broken);
const p = obj.getProperty(.points).?;
if (p.points.items.len == 0) return error.EmptyPoints;

View File

@@ -2,7 +2,7 @@ const std = @import("std");
const Object = @import("../Object.zig");
const Property = @import("../Property.zig").Property;
const PropertyData = @import("../Property.zig").Data;
const Rect = @import("../basic_models.zig").Rect;
const Rect_f = @import("../basic_models.zig").Rect_f;
const shape_mod = @import("shape.zig");
/// Свойства фигуры по умолчанию (для создания и проверки типа). Теги для ensure выводятся отсюда.
@@ -21,7 +21,7 @@ pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object)
}
/// Локальные границы эллипса: [-radii.x, -radii.y] .. [radii.x, radii.y].
pub fn getLocalBounds(obj: *const Object) !Rect {
pub fn getLocalBounds(obj: *const Object) !Rect_f {
try shape_mod.ensure(obj, .ellipse);
const r = obj.getProperty(.radii).?;
return .{

View File

@@ -2,7 +2,7 @@ const std = @import("std");
const Object = @import("../Object.zig");
const Property = @import("../Property.zig").Property;
const PropertyData = @import("../Property.zig").Data;
const Rect = @import("../basic_models.zig").Rect;
const Rect_f = @import("../basic_models.zig").Rect_f;
const shape_mod = @import("shape.zig");
/// Свойства фигуры по умолчанию (для создания и проверки типа). Теги для ensure выводятся отсюда.
@@ -21,7 +21,7 @@ pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object)
}
/// Локальные границы линии: от (0,0) до end_point.
pub fn getLocalBounds(obj: *const Object) !Rect {
pub fn getLocalBounds(obj: *const Object) !Rect_f {
try shape_mod.ensure(obj, .line);
const ep = obj.getProperty(.end_point).?;
const min_x = @min(0, ep.end_point.x);

View File

@@ -9,7 +9,7 @@ const ellipse = @import("ellipse.zig");
const broken = @import("broken.zig");
const arc = @import("arc.zig");
pub const Rect = basic_models.Rect;
pub const Rect = basic_models.Rectf;
/// Создаёт объект с общими свойствами по умолчанию и специфичными для типа фигуры.
pub fn createObject(allocator: std.mem.Allocator, shape_kind: Object.ShapeKind) !Object {