summaryrefslogtreecommitdiff
path: root/rust/crates/mask-defs/src/geometry.rs
blob: c9ae6b66f165919d52d027fc079ec1b5cabc3123 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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
}