summaryrefslogtreecommitdiff
path: root/rust/crates/mask-defs/src/geometry.rs
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-07-28 20:14:51 +0200
committercashmere <cashmere@autistici.org>2026-07-28 20:14:51 +0200
commit32438ce25f69de99d2523b98237f62cfa90890d7 (patch)
tree3a712c347dec0889bdea3328b4c817c2d9392b27 /rust/crates/mask-defs/src/geometry.rs
parentf5a82152394fde4d8f740f664ab7737b82861618 (diff)
downloadopencut-32438ce25f69de99d2523b98237f62cfa90890d7.tar.gz
feat: port domain crates to Rust (session 1 complete)
All green: cargo test --workspace (~300 tests, 0 failures). - timeline (45): placement, resize/ripple, split, snap, retime, audio separation, scene helpers - animation (73): bezier solver, channel sampling, keyframe ops, split/clamp/clone, param definitions - commands (9): Command trait, BatchCommand, history, 19 concrete commands, freeze-frame port 1:1 - audio (22): audible collection, range mixing, mastering limiter, rms/peak buckets - text (19): cosmic-text layout/measure/raster, backgrounds, decorations - graphics (52): full CSS gradient parser, tiny-skia raster, 4 shape definitions - effect-defs (16): blur definition, registry, pass resolution - mask-defs (19): all 9 mask renderers via tiny-skia - renderer (6): node model + scene builder port - desktop: GPUI projects screen wired to ProjectStore - scene: factory (web defaults: v32, Main Track, 1920x1080, 30fps)
Diffstat (limited to 'rust/crates/mask-defs/src/geometry.rs')
-rw-r--r--rust/crates/mask-defs/src/geometry.rs109
1 files changed, 109 insertions, 0 deletions
diff --git a/rust/crates/mask-defs/src/geometry.rs b/rust/crates/mask-defs/src/geometry.rs
new file mode 100644
index 00000000..c9ae6b66
--- /dev/null
+++ b/rust/crates/mask-defs/src/geometry.rs
@@ -0,0 +1,109 @@
+use scene::{RectangleMaskParams, StrokeAlign};
+
+pub const MIN_MASK_DIMENSION: f64 = 0.01;
+
+pub const NORMAL_SNAP_EPSILON: f64 = 1e-10;
+pub const MIN_POLYGON_AREA_PX: f64 = 0.5;
+pub const INTERSECTION_EPSILON: f64 = 1e-6;
+pub const LINE_PARALLEL_EPSILON: f64 = 1e-10;
+
+#[derive(Clone, Copy, Debug)]
+pub struct BoxGeometry {
+ pub center_x: f64,
+ pub center_y: f64,
+ pub mask_width: f64,
+ pub mask_height: f64,
+ pub rotation_rad: f64,
+}
+
+pub fn box_like_geometry(params: &RectangleMaskParams, width: f64, height: f64) -> BoxGeometry {
+ BoxGeometry {
+ center_x: width / 2.0 + params.center_x * width,
+ center_y: height / 2.0 + params.center_y * height,
+ mask_width: params.width.max(MIN_MASK_DIMENSION) * width,
+ mask_height: params.height.max(MIN_MASK_DIMENSION) * height,
+ rotation_rad: params.rotation.to_radians(),
+ }
+}
+
+pub fn stroke_offset(stroke_align: StrokeAlign, stroke_width: f64) -> f64 {
+ match stroke_align {
+ StrokeAlign::Inside => -(stroke_width / 2.0),
+ StrokeAlign::Outside => stroke_width / 2.0,
+ StrokeAlign::Center => 0.0,
+ }
+}
+
+pub fn rotate_point(
+ x: f64,
+ y: f64,
+ center_x: f64,
+ center_y: f64,
+ rotation_rad: f64,
+) -> (f64, f64) {
+ let dx = x - center_x;
+ let dy = y - center_y;
+ let cos = rotation_rad.cos();
+ let sin = rotation_rad.sin();
+ (center_x + dx * cos - dy * sin, center_y + dx * sin + dy * cos)
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct SplitLine {
+ pub normal_x: f64,
+ pub normal_y: f64,
+ pub line_x: f64,
+ pub line_y: f64,
+}
+
+pub fn split_line_geometry(
+ center_x: f64,
+ center_y: f64,
+ rotation: f64,
+ width: f64,
+ height: f64,
+) -> SplitLine {
+ let angle_rad = rotation.to_radians();
+ let cos = angle_rad.cos();
+ let sin = angle_rad.sin();
+ SplitLine {
+ normal_x: if cos.abs() < NORMAL_SNAP_EPSILON { 0.0 } else { cos },
+ normal_y: if sin.abs() < NORMAL_SNAP_EPSILON { 0.0 } else { sin },
+ line_x: width / 2.0 + center_x * width,
+ line_y: height / 2.0 + center_y * height,
+ }
+}
+
+pub fn half_plane_sign(line: SplitLine, x: f64, y: f64) -> f64 {
+ (x - line.line_x) * line.normal_x + (y - line.line_y) * line.normal_y
+}
+
+pub fn line_edge_intersection(
+ line: SplitLine,
+ x1: f64,
+ y1: f64,
+ x2: f64,
+ y2: f64,
+) -> Option<(f64, f64)> {
+ let distance1 = half_plane_sign(line, x1, y1);
+ let distance2 = half_plane_sign(line, x2, y2);
+ let denom = distance1 - distance2;
+ if denom.abs() < LINE_PARALLEL_EPSILON {
+ return None;
+ }
+ let t = distance1 / denom;
+ if !(0.0..=1.0).contains(&t) {
+ return None;
+ }
+ Some((x1 + (x2 - x1) * t, y1 + (y2 - y1) * t))
+}
+
+pub fn polygon_area(vertices: &[(f64, f64)]) -> f64 {
+ let mut area = 0.0;
+ for i in 0..vertices.len() {
+ let (x1, y1) = vertices[i];
+ let (x2, y2) = vertices[(i + 1) % vertices.len()];
+ area += x1 * y2 - x2 * y1;
+ }
+ area.abs() * 0.5
+}