Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ADRs/shape - bounding box padding
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ No constant option will be easy to implement then.
Additionaly it siplifies point normalazing, because points should be normalzied between bounding box - padding area.

IMPORTANT:
to calculate paddign we use angles from current bounding point to neighbour points.
to calculate padding we use angles from current bounding point to neighbour points.
Because of that the box cannot have 0 widht or 0 height because neighbour angles has to be apart 90 degree.

That's why bounds have to be at least 1x1, never less.
Expand Down
1 change: 1 addition & 0 deletions integration-tests/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<select id="tools-select">
<option value="0">None</option>
<option value="1">Draw Shape</option>
<option value="2">Edit Shape</option>
</select>
</div>
<div>
Expand Down
Empty file.
8 changes: 5 additions & 3 deletions src/WebGPU/programs/drawShape/shader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ struct VSOutput {
@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {
let sdf = textureLoad(texture, vec2u(vsOut.uv));

let is_filled = select(0.0, 1.0, sdf.r > -u.stroke_width * 0.5);
var color = select(u.stroke_color, u.fill_color, sdf.r > u.stroke_width * 0.5);
// let stroke_factor = select(0.5, 0.0, sdf.g > 1.0);
let stroke_factor = 0.0;
let is_filled = select(0.0, 1.0, sdf.r > -u.stroke_width * stroke_factor);
var color = select(u.stroke_color, u.fill_color, sdf.r > u.stroke_width * stroke_factor);

// return vec4f(sdf.r / 100.0, sdf.g % 1, sdf.b / (2 * 3.1415926), 1.0);
color = vec4f(sdf.r / 100.0, sdf.g % 1, sdf.b / (2 * 3.1415926), 1.0);
// color = select(vec4f(0.5, 0, 0, 1), vec4f(0, 0, 0.5, 1), u32(sdf.r / 20.0) % 2 == 0);

return color * is_filled;
Expand Down
15 changes: 0 additions & 15 deletions src/__test__/index.test.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type SerializedOutputAsset = SerializedOutputImage | SerializedOutputShap
export enum CreatorTool {
None = 0,
DrawShape = 1,
EditShape = 2,
}

export interface CreatorAPI {
Expand Down
155 changes: 129 additions & 26 deletions src/logic/index.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const std = @import("std");
const types = @import("types.zig");
const images = @import("images.zig");
const Line = @import("line.zig");
const lines = @import("lines.zig");
const Triangle = @import("triangle.zig");
const TransformUI = @import("transform_ui.zig");
const zigar = @import("zigar");
Expand All @@ -12,6 +12,9 @@ const bounding_box = @import("shapes/bounding_box.zig");
const shared = @import("shared.zig");
const get_sdf_texture_size = @import("get_sdf_texture_size.zig").get_sdf_texture_size;
const Utils = @import("utils.zig");
const PackedId = @import("shapes/packed_id.zig");
const Matrix3x3 = @import("matrix.zig").Matrix3x3;
const PathUtils = @import("shapes/path_utils.zig");

const WebGpuPrograms = struct {
draw_texture: *const fn (images.DrawVertex, u32) void,
Expand Down Expand Up @@ -68,7 +71,7 @@ fn update_texture_cache(texture_id: u32, box: bounding_box.BoundingBox, width: f
end_cache_callback();
}

pub const ASSET_ID_TRESHOLD: u32 = 1000;
pub const ASSET_ID_MIN: u32 = 1000;
const NO_SELECTION = 0;
const MIN_NEW_CONTROL_POINT_DISTANCE = 10.0; // Minimum distance to consider a new control point

Expand All @@ -81,6 +84,7 @@ const ActionType = enum {
const Tool = enum {
None,
DrawShape,
EditShape,
};

const Asset = union(enum) {
Expand Down Expand Up @@ -143,10 +147,11 @@ pub fn updateRenderScale(scale: f32) !void {
}
}

var next_asset_id: u32 = ASSET_ID_TRESHOLD;
var next_asset_id: u32 = ASSET_ID_MIN;
fn generateId() u32 {
const id = next_asset_id;
next_asset_id +%= 1;
// TODO: once hits PackedId.ASSET_ID_MAX we should re-indentify all assets
return id;
}

Expand Down Expand Up @@ -273,12 +278,17 @@ fn getSelectedShape() ?*shapes.Shape {
fn updateSelectedAsset(id: u32) !void {
try commitChanges();

state.selected_asset_id = id;
if (id >= PackedId.MIN_PACKED_ID) {
const point_id = PackedId.decode(id);
shapes.selected_point_id = point_id;
state.selected_asset_id = point_id.shape;
} else {
state.selected_asset_id = id;
}
on_asset_select_cb(id);
}

pub fn onPointerDown(_allocator: std.mem.Allocator, x: f32, y: f32) !void {
_ = _allocator; // autofix
pub fn onPointerDown(x: f32, y: f32) !void {
if (state.tool == Tool.DrawShape) {
if (state.selected_asset_id == NO_SELECTION) {
const id = try addShape(
Expand All @@ -304,11 +314,18 @@ pub fn onPointerDown(_allocator: std.mem.Allocator, x: f32, y: f32) !void {
return;
}

if (state.tool == Tool.EditShape) {
// sould not be accessible on mobile, that's why selection happens with pointer down
if (state.hovered_asset_id != NO_SELECTION) {
try updateSelectedAsset(state.hovered_asset_id);
}
}

if (state.selected_asset_id == NO_SELECTION) {
// No active asset, do nothing
} else if (TransformUI.isTransformUi(state.hovered_asset_id)) {
state.action = .Transform;
} else if (state.selected_asset_id >= ASSET_ID_TRESHOLD and state.selected_asset_id == state.hovered_asset_id) {
} else if (state.selected_asset_id >= ASSET_ID_MIN and state.selected_asset_id == state.hovered_asset_id) {
state.action = .Move;
state.last_pointer_coords = types.Point{ .x = x, .y = y };
}
Expand All @@ -327,13 +344,77 @@ pub fn onPointerUp() !void {
if (getSelectedShape()) |shape| {
shape.onReleasePointer();
}
} else if (state.tool == Tool.EditShape) {
shapes.selected_point_id = null;
}
}

pub fn onPointerMove(x: f32, y: f32) !void {
pub fn onPointerMove(x: f32, y: f32) void {
if (state.tool == Tool.DrawShape) {
if (getSelectedShape()) |shape| {
try shape.updatePointPreview(types.Point{ .x = x, .y = y });
shape.updatePointPreview(types.Point{ .x = x, .y = y });
}
return;
}

if (state.tool == Tool.EditShape) {
if (shapes.selected_point_id) |selected_point_id| {
if (getSelectedShape()) |shape| {
const matrix = Matrix3x3.getMatrixFromRectangle(shape.bounds);
const pointer = matrix.inverse().get(types.Point{ .x = x, .y = y });

const path = shape.paths.items[selected_point_id.path];
const points = path.points.items;
const i = selected_point_id.point;

if (i % 3 == 0) { // it's control point
const diff = types.Point{
.x = pointer.x - points[i].x,
.y = pointer.y - points[i].y,
};

const option_index = if (i == 0 and path.closed) points.len - 1 else if (i > 0) i - 1 else null;
if (option_index) |index| {
if (!PathUtils.isStraightLineHandle(points[index])) {
points[index].x += diff.x;
points[index].y += diff.y;
}
}
if (i + 1 < points.len - 1) {
if (!PathUtils.isStraightLineHandle(points[i + 1])) {
points[i + 1].x += diff.x;
points[i + 1].y += diff.y;
}
}
} else {
const cp, const option_index = if (i % 3 == 1) blk: {
const index = if (i == 1 and path.closed) points.len - 1 else if (i > 1) i - 2 else null;
break :blk .{ points[i - 1], index };
} else blk: {
const index = if (i == points.len - 1 and path.closed) 1 else if (i + 2 < points.len) i + 2 else null;
const cp = if (i == points.len - 1 and path.closed) points[0] else points[i + 1];
break :blk .{ cp, index };
};

if (option_index) |index| {
const opposite_handler = PathUtils.getOppositeHandle(
cp,
points[i],
);

const dist = opposite_handler.distance(points[index]);
if (dist < 1.0) {
points[index] = PathUtils.getOppositeHandle(
cp,
pointer,
);
}
}
}

points[i] = pointer;
shape.outdated_sdf = true;
}
}
return;
}
Expand Down Expand Up @@ -414,7 +495,7 @@ fn getBorder(allocator: std.mem.Allocator) struct { []Triangle.DrawInstance, []M
const next_point = if (i == 3) points[0] else points[i + 1];
var buffer: [2]Triangle.DrawInstance = undefined;

Line.getDrawVertexData(
lines.getDrawVertexData(
buffer[0..2],
point,
next_point,
Expand All @@ -439,7 +520,7 @@ fn getBorder(allocator: std.mem.Allocator) struct { []Triangle.DrawInstance, []M
const next_point = if (i == 3) points[0] else points[i + 1];
var buffer: [2]Triangle.DrawInstance = undefined;

Line.getDrawVertexData(
lines.getDrawVertexData(
buffer[0..2],
point,
next_point,
Expand Down Expand Up @@ -500,7 +581,7 @@ fn drawProjectBoundary() void {
for (points, 0..) |point, i| {
const next_point = if (i == 3) points[0] else points[i + 1];

Line.getDrawVertexData(
lines.getDrawVertexData(
buffer[i * 2 ..][0..2],
point,
next_point,
Expand Down Expand Up @@ -537,7 +618,7 @@ pub fn calculateShapesSDF() !void {
point.x *= scale;
point.y *= scale;
}

std.debug.print("New SDF size: {}x{}\n", .{ shape.sdf_size.w, shape.sdf_size.h });
web_gpu_programs.compute_shape(
points,
shape.sdf_size.w,
Expand Down Expand Up @@ -584,9 +665,18 @@ pub fn renderDraw() !void {
}
}

if (state.tool == Tool.DrawShape) {
if (state.tool == Tool.DrawShape or state.tool == Tool.EditShape) {
const hover_point_id = PackedId.decode(state.hovered_asset_id);
const select_point_id = shapes.selected_point_id;
_ = select_point_id; // autofix

if (getSelectedShape()) |shape| {
const vertex_data = try shape.getSkeletonDrawVertexData(allocator);
const hover_id = if (shape.id == hover_point_id.shape) hover_point_id else null;
const vertex_data = try shape.getSkeletonDrawVertexData(
allocator,
hover_id,
state.tool == Tool.DrawShape,
);
web_gpu_programs.draw_triangle(vertex_data);
web_gpu_programs.draw_shape(&shape.getDrawBounds(), shapes.getSkeletonUniform(), shape.texture_id);
}
Expand Down Expand Up @@ -624,11 +714,11 @@ pub fn renderDraw() !void {
// web_gpu_programs.draw_msdf(&msdf_vertex_data, 0);
}

pub fn renderPick() void {
pub fn renderPick() !void {
if (state.tool == Tool.DrawShape) {
// TODO: draw selected shape path control points only
return;
}

var iterator = state.assets.iterator();
while (iterator.next()) |asset| {
switch (asset.value_ptr.*) {
Expand All @@ -644,14 +734,27 @@ pub fn renderPick() void {
}
}

if (state.assets.get(state.selected_asset_id)) |asset| {
const points = switch (asset) {
.img => |img| img.points,
.shape => |shape| shape.bounds,
};
var vertex_buffer: [TransformUI.PICK_TRIANGLE_INSTANCES]Triangle.PickInstance = undefined;
TransformUI.getPickVertexData(vertex_buffer[0..TransformUI.PICK_TRIANGLE_INSTANCES], points);
web_gpu_programs.pick_triangle(vertex_buffer[0..TransformUI.PICK_TRIANGLE_INSTANCES]);
if (state.tool == Tool.EditShape) {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();

if (getSelectedShape()) |shape| {
const vertex_data = try shape.getSkeletonPickVertexData(allocator);
web_gpu_programs.pick_triangle(vertex_data);
}
}

if (state.tool == Tool.None) {
if (state.assets.get(state.selected_asset_id)) |asset| {
const points = switch (asset) {
.img => |img| img.points,
.shape => |shape| shape.bounds,
};
var vertex_buffer: [TransformUI.PICK_TRIANGLE_INSTANCES]Triangle.PickInstance = undefined;
TransformUI.getPickVertexData(vertex_buffer[0..TransformUI.PICK_TRIANGLE_INSTANCES], points);
web_gpu_programs.pick_triangle(vertex_buffer[0..TransformUI.PICK_TRIANGLE_INSTANCES]);
}
}
}

Expand Down Expand Up @@ -689,7 +792,7 @@ pub fn destroyState() void {
last_assets_update = &.{};
Msdf.deinitIcons();
state.selected_asset_id = 0;
next_asset_id = ASSET_ID_TRESHOLD;
next_asset_id = ASSET_ID_MIN;
web_gpu_programs = undefined;
on_asset_update_cb = undefined;
// state itself is not destoyed as it will be reinitalized before usage
Expand Down
File renamed without changes.
7 changes: 3 additions & 4 deletions src/logic/shapes/bounding_box.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const Point = @import("../types.zig").Point;
const std = @import("std");
const Path = @import("paths.zig").Path;
const PathUtils = @import("path_utils.zig");

/// Represents a 2D bounding box with minimum and maximum coordinates
pub const BoundingBox = struct {
Expand Down Expand Up @@ -114,7 +114,6 @@ fn evaluateCubicBezierComponent(t: f32, p0: f32, p1: f32, p2: f32, p3: f32) f32
/// Returns an allocated slice of 6 points representing two triangles for the bounding rectangle
/// Caller owns the returned memory and must free it
pub fn getBoundingBox(curves: []const Point) BoundingBox {
// std.debug.print("curves len: {d}\n", .{curves.len});
if (curves.len == 0) {
return BoundingBox{};
}
Expand All @@ -135,11 +134,11 @@ pub fn getBoundingBox(curves: []const Point) BoundingBox {
const p3 = curves[i * 4 + 3];

// Do we need this? How did it worked before????? Wit treating stright handles as any point
if (Path.isStraightLineHandle(p1)) {
if (PathUtils.isStraightLineHandle(p1)) {
// If p1 is a straight line handle, we skip it
p1 = p0; // Use p0 as the first point
}
if (Path.isStraightLineHandle(p2)) {
if (PathUtils.isStraightLineHandle(p2)) {
// If p2 is a straight line handle, we skip it
p2 = p3; // Use p3 as the last point
}
Expand Down
Loading