Убраны лишние комментарии
This commit is contained in:
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
@@ -16,6 +16,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/zig-out/bin/Zivro",
|
"program": "${workspaceFolder}/zig-out/bin/Zivro",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
|
"MIMode": "gdb",
|
||||||
"preLaunchTask": "zig: build"
|
"preLaunchTask": "zig: build"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -123,8 +123,7 @@ pub fn getZoomedImageSize(self: Canvas) Rect_i {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Возвращает координаты точки контента в координатах документа. Всегда возвращает точку,
|
/// Точка контента -> координаты документа.
|
||||||
/// даже если она за пределами документа
|
|
||||||
pub fn contentPointToDocument(self: Canvas, content_point: dvui.Point, natural_scale: f32) Point2_f {
|
pub fn contentPointToDocument(self: Canvas, content_point: dvui.Point, natural_scale: f32) Point2_f {
|
||||||
const img = self.getZoomedImageSize();
|
const img = self.getZoomedImageSize();
|
||||||
const px_x = content_point.x * natural_scale - @as(f32, @floatFromInt(img.x));
|
const px_x = content_point.x * natural_scale - @as(f32, @floatFromInt(img.x));
|
||||||
@@ -135,7 +134,7 @@ pub fn contentPointToDocument(self: Canvas, content_point: dvui.Point, natural_s
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Возвращает true, если точка контента лежит внутри холста (документа).
|
/// Точка контента внутри холста.
|
||||||
pub fn isContentPointOnDocument(self: Canvas, content_point: dvui.Point, natural_scale: f32) bool {
|
pub fn isContentPointOnDocument(self: Canvas, content_point: dvui.Point, natural_scale: f32) bool {
|
||||||
const img = self.getZoomedImageSize();
|
const img = self.getZoomedImageSize();
|
||||||
const left_n = @as(f32, @floatFromInt(img.x)) / natural_scale;
|
const left_n = @as(f32, @floatFromInt(img.x)) / natural_scale;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const default_common_data = [_]PropertyData{
|
|||||||
.{ .visible = true },
|
.{ .visible = true },
|
||||||
.{ .opacity = 1.0 },
|
.{ .opacity = 1.0 },
|
||||||
.{ .locked = false },
|
.{ .locked = false },
|
||||||
.{ .stroke_rgba = 0x000000FF }, // 0xRRGGBBAA: чёрный, полная непрозрачность
|
.{ .stroke_rgba = 0x000000FF }, // чёрный, полная непрозрачность
|
||||||
.{ .thickness = 2.0 },
|
.{ .thickness = 2.0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ pub const Data = union(enum) {
|
|||||||
|
|
||||||
points: std.ArrayList(Point2_f),
|
points: std.ArrayList(Point2_f),
|
||||||
|
|
||||||
/// Цвет заливки: u32 в формате 0xRRGGBBAA (R старший байт, A младший).
|
/// Цвет заливки, 0xRRGGBBAA.
|
||||||
fill_rgba: u32,
|
fill_rgba: u32,
|
||||||
/// Цвет обводки: u32 в формате 0xRRGGBBAA (R старший байт, A младший).
|
/// Цвет обводки, 0xRRGGBBAA.
|
||||||
stroke_rgba: u32,
|
stroke_rgba: u32,
|
||||||
|
|
||||||
thickness: f32,
|
thickness: f32,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ pub const Point2_f = struct {
|
|||||||
y: f32 = 0,
|
y: f32 = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Целочисленная точка (например, координаты в буфере пикселей).
|
|
||||||
pub const Point2_i = struct {
|
pub const Point2_i = struct {
|
||||||
x: i32 = 0,
|
x: i32 = 0,
|
||||||
y: i32 = 0,
|
y: i32 = 0,
|
||||||
@@ -36,7 +35,6 @@ pub const Scale2_f = struct {
|
|||||||
scale_y: f32 = 1,
|
scale_y: f32 = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Прямоугольник в координатах документа (f32), например локальные границы объекта.
|
|
||||||
pub const Rect_f = struct {
|
pub const Rect_f = struct {
|
||||||
x: f32 = 0,
|
x: f32 = 0,
|
||||||
y: f32 = 0,
|
y: f32 = 0,
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ const PropertyData = @import("../Property.zig").Data;
|
|||||||
const Rect_f = @import("../basic_models.zig").Rect_f;
|
const Rect_f = @import("../basic_models.zig").Rect_f;
|
||||||
const shape_mod = @import("shape.zig");
|
const shape_mod = @import("shape.zig");
|
||||||
|
|
||||||
/// Теги обязательных свойств (заглушка: arc пока не реализован).
|
/// Теги обязательных свойств (arc не реализован).
|
||||||
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
||||||
return &[_]std.meta.Tag(PropertyData){};
|
return &[_]std.meta.Tag(PropertyData){};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Добавляет свойства по умолчанию для дуги (заглушка).
|
/// Добавляет свойства по умолчанию для дуги.
|
||||||
pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void {
|
pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void {
|
||||||
_ = allocator;
|
_ = allocator;
|
||||||
_ = obj;
|
_ = obj;
|
||||||
return error.ArcNotImplemented;
|
return error.ArcNotImplemented;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Локальные границы дуги (заглушка: пока не реализовано).
|
/// Локальные границы дуги (не реализовано).
|
||||||
pub fn getLocalBounds(obj: *const Object) !Rect_f {
|
pub fn getLocalBounds(obj: *const Object) !Rect_f {
|
||||||
try shape_mod.ensure(obj, .arc);
|
try shape_mod.ensure(obj, .arc);
|
||||||
return error.ArcNotImplemented;
|
return error.ArcNotImplemented;
|
||||||
|
|||||||
@@ -6,28 +6,28 @@ const Point2_f = @import("../basic_models.zig").Point2_f;
|
|||||||
const Rect_f = @import("../basic_models.zig").Rect_f;
|
const Rect_f = @import("../basic_models.zig").Rect_f;
|
||||||
const shape_mod = @import("shape.zig");
|
const shape_mod = @import("shape.zig");
|
||||||
|
|
||||||
/// Точки ломаной по умолчанию (для создания).
|
/// Точки ломаной по умолчанию.
|
||||||
pub const default_points = [_]Point2_f{
|
pub const default_points = [_]Point2_f{
|
||||||
.{ .x = 0, .y = 0 },
|
.{ .x = 0, .y = 0 },
|
||||||
.{ .x = 80, .y = 0 },
|
.{ .x = 80, .y = 0 },
|
||||||
.{ .x = 80, .y = 60 },
|
.{ .x = 80, .y = 60 },
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Теги обязательных свойств (у ломаной нет const default_shape_properties, только default_points).
|
/// Теги обязательных свойств.
|
||||||
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
||||||
return &[_]std.meta.Tag(PropertyData){
|
return &[_]std.meta.Tag(PropertyData){
|
||||||
.points,
|
.points,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Добавляет к объекту свойства по умолчанию для ломаной (points из default_points).
|
/// Добавляет к объекту свойства по умолчанию для ломаной.
|
||||||
pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void {
|
pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object) !void {
|
||||||
var points = std.ArrayList(Point2_f).empty;
|
var points = std.ArrayList(Point2_f).empty;
|
||||||
try points.appendSlice(allocator, &default_points);
|
try points.appendSlice(allocator, &default_points);
|
||||||
try obj.properties.append(allocator, .{ .data = .{ .points = points } });
|
try obj.properties.append(allocator, .{ .data = .{ .points = points } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Локальные границы ломаной: AABB по всем точкам.
|
/// Локальные границы: AABB по точкам.
|
||||||
pub fn getLocalBounds(obj: *const Object) !Rect_f {
|
pub fn getLocalBounds(obj: *const Object) !Rect_f {
|
||||||
try shape_mod.ensure(obj, .broken);
|
try shape_mod.ensure(obj, .broken);
|
||||||
const p = obj.getProperty(.points).?;
|
const p = obj.getProperty(.points).?;
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ const PropertyData = @import("../Property.zig").Data;
|
|||||||
const Rect_f = @import("../basic_models.zig").Rect_f;
|
const Rect_f = @import("../basic_models.zig").Rect_f;
|
||||||
const shape_mod = @import("shape.zig");
|
const shape_mod = @import("shape.zig");
|
||||||
|
|
||||||
/// Свойства фигуры по умолчанию (для создания и проверки типа). Теги для ensure выводятся отсюда.
|
/// Свойства фигуры по умолчанию.
|
||||||
pub const default_shape_properties = [_]Property{
|
pub const default_shape_properties = [_]Property{
|
||||||
.{ .data = .{ .radii = .{ .x = 50, .y = 50 } } },
|
.{ .data = .{ .radii = .{ .x = 50, .y = 50 } } },
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Теги обязательных свойств (выводятся из default_shape_properties).
|
/// Теги обязательных свойств.
|
||||||
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
||||||
return &([_]std.meta.Tag(PropertyData){std.meta.activeTag(default_shape_properties[0].data)});
|
return &([_]std.meta.Tag(PropertyData){std.meta.activeTag(default_shape_properties[0].data)});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ const PropertyData = @import("../Property.zig").Data;
|
|||||||
const Rect_f = @import("../basic_models.zig").Rect_f;
|
const Rect_f = @import("../basic_models.zig").Rect_f;
|
||||||
const shape_mod = @import("shape.zig");
|
const shape_mod = @import("shape.zig");
|
||||||
|
|
||||||
/// Свойства фигуры по умолчанию (для создания и проверки типа). Теги для ensure выводятся отсюда.
|
/// Свойства фигуры по умолчанию.
|
||||||
pub const default_shape_properties = [_]Property{
|
pub const default_shape_properties = [_]Property{
|
||||||
.{ .data = .{ .end_point = .{ .x = 100, .y = 0 } } },
|
.{ .data = .{ .end_point = .{ .x = 100, .y = 0 } } },
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Теги обязательных свойств (выводятся из default_shape_properties).
|
/// Теги обязательных свойств.
|
||||||
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
pub fn getRequiredTags() []const std.meta.Tag(PropertyData) {
|
||||||
return &([_]std.meta.Tag(PropertyData){std.meta.activeTag(default_shape_properties[0].data)});
|
return &([_]std.meta.Tag(PropertyData){std.meta.activeTag(default_shape_properties[0].data)});
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ pub fn appendDefaultShapeProperties(allocator: std.mem.Allocator, obj: *Object)
|
|||||||
for (default_shape_properties) |prop| try obj.properties.append(allocator, prop);
|
for (default_shape_properties) |prop| try obj.properties.append(allocator, prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Локальные границы линии: от (0,0) до end_point.
|
/// Локальные границы: от (0,0) до end_point.
|
||||||
pub fn getLocalBounds(obj: *const Object) !Rect_f {
|
pub fn getLocalBounds(obj: *const Object) !Rect_f {
|
||||||
try shape_mod.ensure(obj, .line);
|
try shape_mod.ensure(obj, .line);
|
||||||
const ep = obj.getProperty(.end_point).?;
|
const ep = obj.getProperty(.end_point).?;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const arc = @import("arc.zig");
|
|||||||
|
|
||||||
pub const Rect = basic_models.Rectf;
|
pub const Rect = basic_models.Rectf;
|
||||||
|
|
||||||
/// Создаёт объект с общими свойствами по умолчанию и специфичными для типа фигуры.
|
/// Создаёт объект с дефолтными общими и фигурными свойствами.
|
||||||
pub fn createObject(allocator: std.mem.Allocator, shape_kind: Object.ShapeKind) !Object {
|
pub fn createObject(allocator: std.mem.Allocator, shape_kind: Object.ShapeKind) !Object {
|
||||||
var obj = try createWithCommonProperties(allocator, shape_kind);
|
var obj = try createWithCommonProperties(allocator, shape_kind);
|
||||||
errdefer obj.deinit(allocator);
|
errdefer obj.deinit(allocator);
|
||||||
@@ -35,7 +35,7 @@ fn createWithCommonProperties(allocator: std.mem.Allocator, shape_kind: Object.S
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Проверяет, что объект имеет ожидаемый тип и все требуемые для этого типа свойства.
|
/// Проверяет тип объекта и наличие обязательных свойств.
|
||||||
pub fn ensure(obj: *const Object, expected_kind: Object.ShapeKind) !void {
|
pub fn ensure(obj: *const Object, expected_kind: Object.ShapeKind) !void {
|
||||||
if (obj.shape != expected_kind) return error.WrongShapeKind;
|
if (obj.shape != expected_kind) return error.WrongShapeKind;
|
||||||
const tags = requiredTagsFor(expected_kind);
|
const tags = requiredTagsFor(expected_kind);
|
||||||
@@ -53,7 +53,7 @@ fn requiredTagsFor(kind: Object.ShapeKind) []const std.meta.Tag(PropertyData) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Локальные границы объекта (AABB в своих координатах).
|
/// Локальные границы (AABB).
|
||||||
pub fn getLocalBounds(obj: *const Object) !Rect {
|
pub fn getLocalBounds(obj: *const Object) !Rect {
|
||||||
return switch (obj.shape) {
|
return switch (obj.shape) {
|
||||||
.line => line.getLocalBounds(obj),
|
.line => line.getLocalBounds(obj),
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ fn randFloat(rng: std.Random, min: f32, max: f32) f32 {
|
|||||||
return min + (max - min) * rng.float(f32);
|
return min + (max - min) * rng.float(f32);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Цвет в u32, порядок 0xRRGGBBAA (R старший, A младший; чёрный = 0x000000FF).
|
|
||||||
fn randRgba(rng: std.Random) u32 {
|
fn randRgba(rng: std.Random) u32 {
|
||||||
const r = rng.int(u8);
|
const r = rng.int(u8);
|
||||||
const g = rng.int(u8);
|
const g = rng.int(u8);
|
||||||
@@ -26,7 +25,6 @@ fn randomShapeKind(rng: std.Random) Object.ShapeKind {
|
|||||||
return shapes_implemented[rng.intRangeLessThan(usize, 0, shapes_implemented.len)];
|
return shapes_implemented[rng.intRangeLessThan(usize, 0, shapes_implemented.len)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Случайно заполняет все доступные свойства объекта; позиция — в пределах документа.
|
|
||||||
fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size_f, 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 margin: f32 = 8;
|
||||||
const max_x = @max(0, doc_size.w - margin);
|
const max_x = @max(0, doc_size.w - margin);
|
||||||
@@ -91,9 +89,7 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Создаёт в документе случайное количество фигур (в т.ч. вложенных).
|
/// Создаёт в документе случайные фигуры (line, ellipse, broken).
|
||||||
/// У каждой фигуры все доступные свойства задаются случайно; позиция — в пределах документа.
|
|
||||||
/// Реализованные типы: line, ellipse, broken.
|
|
||||||
pub fn addRandomShapes(doc: *Document, rng: std.Random) !void {
|
pub fn addRandomShapes(doc: *Document, rng: std.Random) !void {
|
||||||
const max_total: usize = 80;
|
const max_total: usize = 80;
|
||||||
var total_count: usize = 0;
|
var total_count: usize = 0;
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ pub fn renderEngine(self: *CpuRenderEngine) RenderEngine {
|
|||||||
return .{ .cpu = self };
|
return .{ .cpu = self };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Растеризует документ: фон + рекурсивная отрисовка фигур через конвейер (трансформ, прозрачность, наложение).
|
/// Растеризует документ в текстуру (фон + фигуры через конвейер).
|
||||||
pub fn renderDocument(self: *CpuRenderEngine, document: *const Document, canvas_size: Size_i, visible_rect: Rect_i) !?dvui.Texture {
|
pub fn renderDocument(self: *CpuRenderEngine, document: *const Document, canvas_size: Size_i, visible_rect: Rect_i) !?dvui.Texture {
|
||||||
const width = visible_rect.w;
|
const width = visible_rect.w;
|
||||||
const height = visible_rect.h;
|
const height = visible_rect.h;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub const RenderEngine = union(enum) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Растеризует документ в текстуру (размер и видимая область в пикселях холста).
|
/// Растеризует документ в текстуру.
|
||||||
pub fn render(self: RenderEngine, document: *const Document, canvas_size: basic_models.Size_i, visible_rect: basic_models.Rect_i) !?dvui.Texture {
|
pub fn render(self: RenderEngine, document: *const Document, canvas_size: basic_models.Size_i, visible_rect: basic_models.Rect_i) !?dvui.Texture {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.cpu => |cpu_r| cpu_r.renderDocument(document, canvas_size, visible_rect),
|
.cpu => |cpu_r| cpu_r.renderDocument(document, canvas_size, visible_rect),
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ const DrawContext = pipeline.DrawContext;
|
|||||||
|
|
||||||
const Object = Document.Object;
|
const Object = Document.Object;
|
||||||
|
|
||||||
/// Рисует дугу (заглушка: пока не реализовано).
|
/// Дуга (не реализовано).
|
||||||
pub fn draw(_: *DrawContext, _: *const Object) void {}
|
pub fn draw(_: *DrawContext, _: *const Object) void {}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const Object = Document.Object;
|
|||||||
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
||||||
const default_thickness: f32 = 2.0;
|
const default_thickness: f32 = 2.0;
|
||||||
|
|
||||||
/// Рисует ломаную по точкам в локальных координатах. Обводка по stroke_rgba.
|
/// Ломаная по точкам, обводка stroke_rgba.
|
||||||
pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
||||||
const p_prop = obj.getProperty(.points) orelse return;
|
const p_prop = obj.getProperty(.points) orelse return;
|
||||||
const pts = p_prop.points.items;
|
const pts = p_prop.points.items;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ fn drawObject(ctx: *DrawContext, obj: *const Object, parent_transform: Transform
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Рекурсивно рисует документ в буфер: сначала корневые объекты по порядку, затем их потомков (каждый следующий поверх предыдущего).
|
/// Рекурсивно рисует документ в буфер (объекты и потомки по порядку).
|
||||||
pub fn drawDocument(
|
pub fn drawDocument(
|
||||||
pixels: []@import("dvui").Color.PMA,
|
pixels: []@import("dvui").Color.PMA,
|
||||||
buf_width: u32,
|
buf_width: u32,
|
||||||
@@ -68,8 +68,6 @@ pub fn drawDocument(
|
|||||||
.scale_x = scale_x,
|
.scale_x = scale_x,
|
||||||
.scale_y = scale_y,
|
.scale_y = scale_y,
|
||||||
};
|
};
|
||||||
// вывести visible_rect
|
|
||||||
std.debug.print("visible_rect: {{ x: {}, y: {}, w: {}, h: {} }}\n", .{ visible_rect.x, visible_rect.y, visible_rect.w, visible_rect.h });
|
|
||||||
const identity = Transform{};
|
const identity = Transform{};
|
||||||
for (document.objects.items) |*obj| {
|
for (document.objects.items) |*obj| {
|
||||||
drawObject(&ctx, obj, identity);
|
drawObject(&ctx, obj, identity);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const Color = @import("dvui").Color;
|
|||||||
const Object = Document.Object;
|
const Object = Document.Object;
|
||||||
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
||||||
|
|
||||||
/// Рисует эллипс: центр (0,0), полуоси radii. Растеризация в координатах буфера (обводка кольцом).
|
/// Эллипс с центром (0,0) и полуосями radii (обводка).
|
||||||
pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
||||||
const r_prop = obj.getProperty(.radii) orelse return;
|
const r_prop = obj.getProperty(.radii) orelse return;
|
||||||
const rx = r_prop.radii.x;
|
const rx = r_prop.radii.x;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const Object = Document.Object;
|
|||||||
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
||||||
const default_thickness: f32 = 2.0;
|
const default_thickness: f32 = 2.0;
|
||||||
|
|
||||||
/// Рисует линию в локальных координатах: от (0,0) до end_point. Растеризация в координатах буфера (без пробелов при зуме).
|
/// Линия от (0,0) до end_point.
|
||||||
pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
||||||
const ep_prop = obj.getProperty(.end_point) orelse return;
|
const ep_prop = obj.getProperty(.end_point) orelse return;
|
||||||
const end_x = ep_prop.end_point.x;
|
const end_x = ep_prop.end_point.x;
|
||||||
@@ -17,7 +17,7 @@ pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
|||||||
drawLine(ctx, 0, 0, end_x, end_y, stroke, thickness);
|
drawLine(ctx, 0, 0, end_x, end_y, stroke, thickness);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Линия по локальным координатам фигуры: переводит концы в буфер и рисует в пикселях буфера.
|
/// Рисует отрезок по локальным концам (перевод в буфер внутри).
|
||||||
pub fn drawLine(ctx: *DrawContext, x0: f32, y0: f32, x1: f32, y1: f32, color: Color.PMA, thickness: f32) void {
|
pub fn drawLine(ctx: *DrawContext, x0: f32, y0: f32, x1: f32, y1: f32, color: Color.PMA, thickness: f32) void {
|
||||||
const w0 = ctx.localToWorld(x0, y0);
|
const w0 = ctx.localToWorld(x0, y0);
|
||||||
const w1 = ctx.localToWorld(x1, y1);
|
const w1 = ctx.localToWorld(x1, y1);
|
||||||
@@ -26,7 +26,6 @@ pub fn drawLine(ctx: *DrawContext, x0: f32, y0: f32, x1: f32, y1: f32, color: Co
|
|||||||
drawLineInBuffer(ctx, b0.x, b0.y, b1.x, b1.y, color, thickness);
|
drawLineInBuffer(ctx, b0.x, b0.y, b1.x, b1.y, color, thickness);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Брезенхем в координатах буфера; пиксели вне [0, buf_width) x [0, buf_height) пропускаются.
|
|
||||||
fn drawLineInBuffer(ctx: *DrawContext, bx0: i32, by0: i32, bx1: i32, by1: i32, color: Color.PMA, thickness: f32) void {
|
fn drawLineInBuffer(ctx: *DrawContext, bx0: i32, by0: i32, bx1: i32, by1: i32, color: Color.PMA, thickness: f32) void {
|
||||||
const bw = ctx.buf_width;
|
const bw = ctx.buf_width;
|
||||||
const bh = ctx.buf_height;
|
const bh = ctx.buf_height;
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ const Scale2_f = basic_models.Scale2_f;
|
|||||||
const Rect_i = basic_models.Rect_i;
|
const Rect_i = basic_models.Rect_i;
|
||||||
const Color = dvui.Color;
|
const Color = dvui.Color;
|
||||||
|
|
||||||
/// Трансформ объекта в мировых координатах документа (позиция, угол, масштаб, непрозрачность).
|
/// Трансформ объекта: позиция, угол, масштаб, непрозрачность.
|
||||||
pub const Transform = struct {
|
pub const Transform = struct {
|
||||||
position: Point2_f = .{},
|
position: Point2_f = .{},
|
||||||
angle: f32 = 0,
|
angle: f32 = 0,
|
||||||
scale: Scale2_f = .{},
|
scale: Scale2_f = .{},
|
||||||
opacity: f32 = 1.0,
|
opacity: f32 = 1.0,
|
||||||
|
|
||||||
/// Композиция: мировой трансформ = parent * local (local в пространстве родителя).
|
/// Композиция: world = parent * local.
|
||||||
pub fn compose(parent: Transform, local: Transform) Transform {
|
pub fn compose(parent: Transform, local: Transform) Transform {
|
||||||
const cos_a = std.math.cos(parent.angle);
|
const cos_a = std.math.cos(parent.angle);
|
||||||
const sin_a = std.math.sin(parent.angle);
|
const sin_a = std.math.sin(parent.angle);
|
||||||
@@ -36,8 +36,7 @@ pub const Transform = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Единый конвейер: принимает позицию в локальных координатах фигуры и цвет пикселя,
|
/// Конвейер отрисовки: локальные координаты -> трансформ -> буфер.
|
||||||
/// применяет трансформ (вращение, масштаб, перенос) и непрозрачность, накладывает на буфер.
|
|
||||||
pub const DrawContext = struct {
|
pub const DrawContext = struct {
|
||||||
pixels: []Color.PMA,
|
pixels: []Color.PMA,
|
||||||
buf_width: u32,
|
buf_width: u32,
|
||||||
@@ -51,7 +50,7 @@ pub const DrawContext = struct {
|
|||||||
self.transform = t;
|
self.transform = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Локальные координаты фигуры -> мировые (документ).
|
/// Локальные -> мировые.
|
||||||
pub fn localToWorld(self: *const DrawContext, local_x: f32, local_y: f32) Point2_f {
|
pub fn localToWorld(self: *const DrawContext, local_x: f32, local_y: f32) Point2_f {
|
||||||
const t = &self.transform;
|
const t = &self.transform;
|
||||||
const cos_a = std.math.cos(t.angle);
|
const cos_a = std.math.cos(t.angle);
|
||||||
@@ -62,7 +61,7 @@ pub const DrawContext = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Мировые координаты документа -> координаты в буфере (float; могут быть вне [0, buf_w] x [0, buf_h]).
|
/// Мировые -> буфер (float).
|
||||||
pub fn worldToBufferF(self: *const DrawContext, wx: f32, wy: f32) Point2_f {
|
pub fn worldToBufferF(self: *const DrawContext, wx: f32, wy: f32) Point2_f {
|
||||||
const canvas_x = wx * self.scale_x;
|
const canvas_x = wx * self.scale_x;
|
||||||
const canvas_y = wy * self.scale_y;
|
const canvas_y = wy * self.scale_y;
|
||||||
@@ -74,7 +73,7 @@ pub const DrawContext = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Мировые координаты документа -> целочисленные координаты в буфере (округление до ближайшего пикселя).
|
/// Мировые -> буфер (целые).
|
||||||
pub fn worldToBuffer(self: *const DrawContext, wx: f32, wy: f32) Point2_i {
|
pub fn worldToBuffer(self: *const DrawContext, wx: f32, wy: f32) Point2_i {
|
||||||
const b = self.worldToBufferF(wx, wy);
|
const b = self.worldToBufferF(wx, wy);
|
||||||
return .{
|
return .{
|
||||||
@@ -83,7 +82,7 @@ pub const DrawContext = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Координаты буфера -> мировые (документ). scale_x/scale_y не должны быть 0.
|
/// Буфер -> мировые.
|
||||||
pub fn bufferToWorld(self: *const DrawContext, buf_x: f32, buf_y: f32) Point2_f {
|
pub fn bufferToWorld(self: *const DrawContext, buf_x: f32, buf_y: f32) Point2_f {
|
||||||
const vx = @as(f32, @floatFromInt(self.visible_rect.x));
|
const vx = @as(f32, @floatFromInt(self.visible_rect.x));
|
||||||
const vy = @as(f32, @floatFromInt(self.visible_rect.y));
|
const vy = @as(f32, @floatFromInt(self.visible_rect.y));
|
||||||
@@ -97,7 +96,7 @@ pub const DrawContext = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Мировые координаты -> локальные фигуры (обратное к localToWorld).
|
/// Мировые -> локальные.
|
||||||
pub fn worldToLocal(self: *const DrawContext, wx: f32, wy: f32) Point2_f {
|
pub fn worldToLocal(self: *const DrawContext, wx: f32, wy: f32) Point2_f {
|
||||||
const t = &self.transform;
|
const t = &self.transform;
|
||||||
const dx = wx - t.position.x;
|
const dx = wx - t.position.x;
|
||||||
@@ -112,14 +111,13 @@ pub const DrawContext = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Смешивает цвет в пикселе буфера (bx, by). color уже в PMA; opacity трансформа применяется к альфе и к RGB.
|
/// Смешивает цвет в пикселе буфера с учётом opacity трансформа.
|
||||||
pub fn blendPixelAtBuffer(self: *DrawContext, bx: u32, by: u32, color: Color.PMA) void {
|
pub fn blendPixelAtBuffer(self: *DrawContext, bx: u32, by: u32, color: Color.PMA) void {
|
||||||
if (bx >= self.buf_width or by >= self.buf_height) return;
|
if (bx >= self.buf_width or by >= self.buf_height) return;
|
||||||
const t = &self.transform;
|
const t = &self.transform;
|
||||||
const idx = by * self.buf_width + bx;
|
const idx = by * self.buf_width + bx;
|
||||||
const dst = &self.pixels[idx];
|
const dst = &self.pixels[idx];
|
||||||
const a = @as(f32, @floatFromInt(color.a)) / 255.0 * t.opacity;
|
const a = @as(f32, @floatFromInt(color.a)) / 255.0 * t.opacity;
|
||||||
// PMA: color.r/g/b уже помножены на color.a; при opacity только масштабируем их на t.opacity
|
|
||||||
const src_r = @as(f32, @floatFromInt(color.r)) * t.opacity;
|
const src_r = @as(f32, @floatFromInt(color.r)) * t.opacity;
|
||||||
const src_g = @as(f32, @floatFromInt(color.g)) * t.opacity;
|
const src_g = @as(f32, @floatFromInt(color.g)) * t.opacity;
|
||||||
const src_b = @as(f32, @floatFromInt(color.b)) * t.opacity;
|
const src_b = @as(f32, @floatFromInt(color.b)) * t.opacity;
|
||||||
@@ -130,7 +128,7 @@ pub const DrawContext = struct {
|
|||||||
dst.a = @intFromFloat(std.math.clamp(a * 255 + inv_a * @as(f32, @floatFromInt(dst.a)), 0, 255));
|
dst.a = @intFromFloat(std.math.clamp(a * 255 + inv_a * @as(f32, @floatFromInt(dst.a)), 0, 255));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Записывает пиксель в локальных координатах фигуры с учётом трансформа и прозрачности (PMA blend).
|
/// Пиксель в локальных координатах (трансформ + PMA).
|
||||||
pub fn blendPixelLocal(self: *DrawContext, local_x: f32, local_y: f32, color: Color.PMA) void {
|
pub fn blendPixelLocal(self: *DrawContext, local_x: f32, local_y: f32, color: Color.PMA) void {
|
||||||
const w = self.localToWorld(local_x, local_y);
|
const w = self.localToWorld(local_x, local_y);
|
||||||
const b = self.worldToBufferF(w.x, w.y);
|
const b = self.worldToBufferF(w.x, w.y);
|
||||||
@@ -143,7 +141,7 @@ pub const DrawContext = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// u32 в порядке 0xRRGGBBAA: старший байт R, младший A (чёрный = 0x000000FF).
|
/// Конвертирует u32 0xRRGGBBAA в Color.PMA.
|
||||||
pub fn rgbaToPma(rgba: u32) Color.PMA {
|
pub fn rgbaToPma(rgba: u32) Color.PMA {
|
||||||
const r: u8 = @intCast((rgba >> 24) & 0xFF);
|
const r: u8 = @intCast((rgba >> 24) & 0xFF);
|
||||||
const g: u8 = @intCast((rgba >> 16) & 0xFF);
|
const g: u8 = @intCast((rgba >> 16) & 0xFF);
|
||||||
|
|||||||
Reference in New Issue
Block a user