Skip to main content

pfx_geom/
strip.rs

1use crate::mesh::{Builder, Mesh};
2use std::f32::consts::{PI, TAU};
3
4pub const ROLL_WGSL: &str = r#"
5struct Rolled {
6    position: vec3<f32>,
7    tangent: vec3<f32>,
8    normal: vec3<f32>,
9}
10
11fn roll_outer(core: f32, thickness: f32, wound: f32) -> f32 {
12    return sqrt(core * core + max(wound, 0.0) * thickness / 3.14159265);
13}
14
15fn roll_place(rest: vec3<f32>, at: f32, outer: f32, thickness: f32) -> Rolled {
16    var out: Rolled;
17    let s = rest.x - at;
18    if (s <= 0.0) {
19        out.position = vec3<f32>(rest.x, 0.0, rest.z);
20        out.tangent = vec3<f32>(1.0, 0.0, 0.0);
21        out.normal = vec3<f32>(0.0, 1.0, 0.0);
22        return out;
23    }
24    var theta = s / outer;
25    if (thickness > 1e-7) {
26        let inside = max(outer * outer - thickness * s / 3.14159265, 0.0);
27        theta = (outer - sqrt(inside)) * 6.28318531 / thickness;
28    }
29    let shrink = thickness / 6.28318531;
30    let r = outer - shrink * theta;
31    let around = vec2<f32>(sin(theta), -cos(theta));
32    let along = vec2<f32>(cos(theta), sin(theta));
33    let flat = -shrink * around + r * along;
34    let t = normalize(flat);
35    out.position = vec3<f32>(at + r * around.x, outer + r * around.y, rest.z);
36    out.tangent = vec3<f32>(t.x, t.y, 0.0);
37    out.normal = vec3<f32>(-t.y, t.x, 0.0);
38    return out;
39}
40"#;
41
42#[derive(Clone, Copy, Debug, PartialEq)]
43pub struct Strip {
44    pub length: f32,
45    pub width: f32,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq)]
49pub struct Roller {
50    pub at: f32,
51    pub core: f32,
52    pub thickness: f32,
53}
54
55#[derive(Clone, Copy, Debug, PartialEq)]
56pub struct Rolled {
57    pub position: [f32; 3],
58    pub tangent: [f32; 3],
59    pub normal: [f32; 3],
60}
61
62impl Roller {
63    pub fn outer(&self, strip: &Strip) -> f32 {
64        let wound = (strip.length - self.at).max(0.0);
65        (self.core * self.core + wound * self.thickness / PI).sqrt()
66    }
67
68    pub fn place(&self, rest: [f32; 3], outer: f32) -> Rolled {
69        let s = rest[0] - self.at;
70        if s <= 0.0 {
71            return Rolled {
72                position: [rest[0], 0.0, rest[2]],
73                tangent: [1.0, 0.0, 0.0],
74                normal: [0.0, 1.0, 0.0],
75            };
76        }
77        let theta = if self.thickness > 1e-7 {
78            let inside = (outer * outer - self.thickness * s / PI).max(0.0);
79            (outer - inside.sqrt()) * TAU / self.thickness
80        } else {
81            s / outer
82        };
83        let shrink = self.thickness / TAU;
84        let r = outer - shrink * theta;
85        let around = [theta.sin(), -theta.cos()];
86        let along = [theta.cos(), theta.sin()];
87        let flat = [
88            -shrink * around[0] + r * along[0],
89            -shrink * around[1] + r * along[1],
90        ];
91        let len = (flat[0] * flat[0] + flat[1] * flat[1]).sqrt().max(1e-12);
92        let t = [flat[0] / len, flat[1] / len];
93        Rolled {
94            position: [self.at + r * around[0], outer + r * around[1], rest[2]],
95            tangent: [t[0], t[1], 0.0],
96            normal: [-t[1], t[0], 0.0],
97        }
98    }
99}
100
101impl Strip {
102    pub fn columns(&self, core: f32, error: f32, band: Option<(f32, f32)>) -> Vec<f32> {
103        let step = column_step(core, error);
104        let (from, to) = band
105            .map(|(a, b)| (a.clamp(0.0, self.length), b.clamp(0.0, self.length)))
106            .unwrap_or((0.0, self.length));
107        let (from, to) = (from.min(to), from.max(to));
108        let mut out = vec![0.0];
109        if from > 0.0 {
110            out.push(from);
111        }
112        let count = ((to - from) / step).ceil().max(1.0) as usize;
113        for i in 1..=count {
114            out.push(from + (to - from) * i as f32 / count as f32);
115        }
116        if to < self.length {
117            out.push(self.length);
118        }
119        out.dedup_by(|a, b| (*a - *b).abs() <= 1e-7);
120        out
121    }
122
123    pub fn mesh(&self, core: f32, error: f32, band: Option<(f32, f32)>, across: usize) -> Mesh {
124        let xs = self.columns(core, error, band);
125        let across = across.max(1);
126        let mut builder = Builder::new();
127        let mut grid = Vec::with_capacity(xs.len());
128        for x in &xs {
129            let mut column = Vec::with_capacity(across + 1);
130            for j in 0..=across {
131                let z = self.width * j as f32 / across as f32;
132                let uv = [x / self.length.max(1e-6), z / self.width.max(1e-6)];
133                column.push(builder.vertex([*x, 0.0, z], [0.0, 1.0, 0.0], uv));
134            }
135            grid.push(column);
136        }
137        for i in 0..xs.len() - 1 {
138            for j in 0..across {
139                let (a, b, c, d) = (
140                    grid[i][j],
141                    grid[i + 1][j],
142                    grid[i + 1][j + 1],
143                    grid[i][j + 1],
144                );
145                builder.tri(a, b, c);
146                builder.tri(a, c, d);
147            }
148        }
149        builder.build()
150    }
151}
152
153pub fn column_step(core: f32, error: f32) -> f32 {
154    let core = core.max(1e-6);
155    let error = error.clamp(core * 1e-5, core);
156    let alpha = 2.0 * (1.0 - error / core).acos();
157    (core * alpha).max(1e-6)
158}