Skip to main content

pfx_geom/
tree.rs

1use crate::math::{add, basis, length, normalize, scale, sub};
2use crate::mesh::Mesh;
3use std::f32::consts::TAU;
4
5pub const TREE_SWAY_WGSL: &str = r#"
6fn tree_sway(rest: vec3f, pivot: vec3f, level: f32, stiffness: f32, wind_time: f32, strength: f32, direction: vec3f) -> vec3f {
7    let d = normalize(direction.xz + vec2f(0.00001, 0.0));
8    let arm = length(rest - pivot);
9    let phase = wind_time * (0.7 + level * 0.41) + pivot.x * 0.83 + pivot.z * 0.67;
10    let bend = sin(phase) * strength * (1.0 - stiffness) * arm * (0.012 + 0.009 * level);
11    return rest + vec3f(d.x * bend, -abs(bend) * 0.12, d.y * bend);
12}
13"#;
14
15#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct TreeSpec {
17    pub height: f32,
18    pub trunk_radius: f32,
19    pub branching: u32,
20    pub crown_width: f32,
21    pub crown_height: f32,
22    pub blossom_density: f32,
23    pub bloom: f32,
24    pub leaf_density: f32,
25}
26
27impl TreeSpec {
28    pub fn cherry() -> Self {
29        Self {
30            height: 12.0,
31            trunk_radius: 0.35,
32            branching: 7,
33            crown_width: 4.5,
34            crown_height: 4.2,
35            blossom_density: 1.0,
36            bloom: 1.0,
37            leaf_density: 0.45,
38        }
39    }
40    pub fn plum() -> Self {
41        Self {
42            height: 8.0,
43            trunk_radius: 0.27,
44            branching: 6,
45            crown_width: 3.2,
46            crown_height: 3.3,
47            blossom_density: 0.85,
48            bloom: 1.0,
49            leaf_density: 0.55,
50        }
51    }
52    pub fn magnolia() -> Self {
53        Self {
54            height: 9.0,
55            trunk_radius: 0.32,
56            branching: 5,
57            crown_width: 3.8,
58            crown_height: 4.0,
59            blossom_density: 0.65,
60            bloom: 1.0,
61            leaf_density: 0.7,
62        }
63    }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq)]
67pub struct SwayVertex {
68    pub pivot: [f32; 3],
69    pub level: f32,
70    pub stiffness: f32,
71}
72
73#[derive(Clone, Copy, Debug, PartialEq)]
74pub struct Card {
75    pub position: [f32; 3],
76    pub right: [f32; 3],
77    pub up: [f32; 3],
78    pub size: [f32; 2],
79    pub color: [f32; 4],
80    pub atlas: [f32; 4],
81    pub sway: SwayVertex,
82    pub blossom: bool,
83    pub lod: CardLod,
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub enum CardLod {
88    Always,
89    Individual,
90    Cluster,
91}
92
93#[derive(Clone, Debug)]
94pub struct AlphaAtlas {
95    pub width: u32,
96    pub height: u32,
97    pub pixels: Vec<u8>,
98}
99
100#[derive(Clone, Copy, Debug, PartialEq)]
101pub struct PetalSource {
102    pub position: [f32; 3],
103    pub rate: f32,
104    pub seed: u64,
105    pub sway: SwayVertex,
106}
107
108#[derive(Clone, Debug)]
109pub struct Tree {
110    pub seed: u64,
111    pub spec: TreeSpec,
112    pub wood: Mesh,
113    pub sway: Vec<SwayVertex>,
114    pub cards: Vec<Card>,
115    pub atlas: AlphaAtlas,
116    pub petal_sources: Vec<PetalSource>,
117    pub flower_count: usize,
118    pub max_error: f32,
119}
120
121pub fn tree_sway(
122    rest: [f32; 3],
123    pivot: [f32; 3],
124    level: f32,
125    stiffness: f32,
126    wind_time: f32,
127    strength: f32,
128    direction: [f32; 3],
129) -> [f32; 3] {
130    let d = normalize([direction[0] + 0.00001, 0.0, direction[2]]);
131    let arm = length(sub(rest, pivot));
132    let phase = wind_time * (0.7 + level * 0.41) + pivot[0] * 0.83 + pivot[2] * 0.67;
133    let bend = phase.sin() * strength * (1.0 - stiffness) * arm * (0.012 + 0.009 * level);
134    add(rest, [d[0] * bend, -bend.abs() * 0.12, d[2] * bend])
135}
136
137struct Random(u64);
138impl Random {
139    fn next(&mut self) -> f32 {
140        self.0 ^= self.0 >> 12;
141        self.0 ^= self.0 << 25;
142        self.0 ^= self.0 >> 27;
143        ((self.0.wrapping_mul(0x2545f4914f6cdd1d) >> 40) as u32) as f32 / 16_777_216.0
144    }
145    fn between(&mut self, a: f32, b: f32) -> f32 {
146        a + (b - a) * self.next()
147    }
148}
149
150struct Tube<'a> {
151    positions: &'a mut Vec<[f32; 3]>,
152    normals: &'a mut Vec<[f32; 3]>,
153    tangents: &'a mut Vec<[f32; 4]>,
154    uvs: &'a mut Vec<[f32; 2]>,
155    indices: &'a mut Vec<u32>,
156    sway: &'a mut Vec<SwayVertex>,
157    error: f32,
158}
159
160struct TubeShape {
161    path: [[f32; 3]; 3],
162    radius: f32,
163    tip: f32,
164    pivot: [f32; 3],
165    level: f32,
166    stiffness: f32,
167    uv_start: f32,
168    collar: bool,
169}
170
171impl Tube<'_> {
172    fn add(&mut self, shape: TubeShape) {
173        let TubeShape {
174            path,
175            radius,
176            tip,
177            pivot,
178            level,
179            stiffness,
180            uv_start,
181            collar,
182        } = shape;
183        let [start, control, end] = path;
184        let n = segments(radius, self.error);
185        let path_length = length(sub(control, start)) + length(sub(end, control));
186        let bend = length(add(sub(start, scale(control, 2.0)), end));
187        let curvature_rings = (bend / (4.0 * self.error)).sqrt().ceil() as usize;
188        let rings = ((path_length / (self.error * 12.0).max(0.12)).ceil() as usize)
189            .max(curvature_rings)
190            .clamp(4, 256);
191        let base = self.positions.len() as u32;
192        let mut previous_side = [0.0; 3];
193        for j in 0..=rings {
194            let t = j as f32 / rings as f32;
195            let smooth = t * t * (3.0 - 2.0 * t);
196            let center = add(
197                add(
198                    scale(start, (1.0 - t) * (1.0 - t)),
199                    scale(control, 2.0 * t * (1.0 - t)),
200                ),
201                scale(end, t * t),
202            );
203            let axis = normalize(add(
204                scale(sub(control, start), 1.0 - t),
205                scale(sub(end, control), t),
206            ));
207            let side = if j == 0 {
208                basis(axis).0
209            } else {
210                normalize(sub(
211                    previous_side,
212                    scale(axis, crate::math::dot(previous_side, axis)),
213                ))
214            };
215            previous_side = side;
216            let other = crate::math::cross(axis, side);
217            let flare = if collar {
218                radius * 0.12 * (1.0 - t * 7.0).max(0.0).powi(2)
219            } else {
220                0.0
221            };
222            let r = radius + (tip - radius) * smooth + flare;
223            for i in 0..=n {
224                let a = TAU * i as f32 / n as f32;
225                let normal = add(scale(side, a.cos()), scale(other, a.sin()));
226                let tangent = add(scale(side, -a.sin()), scale(other, a.cos()));
227                self.positions.push(add(center, scale(normal, r)));
228                self.normals.push(normal);
229                self.tangents
230                    .push([tangent[0], tangent[1], tangent[2], 1.0]);
231                self.uvs
232                    .push([i as f32 / n as f32, uv_start + path_length * t]);
233                self.sway.push(SwayVertex {
234                    pivot,
235                    level,
236                    stiffness,
237                });
238            }
239        }
240        for j in 0..rings {
241            for i in 0..n {
242                let a = base + (j * (n + 1) + i) as u32;
243                let b = a + (n + 1) as u32;
244                self.indices.extend([a, a + 1, b, a + 1, b + 1, b]);
245            }
246        }
247    }
248}
249
250pub fn segments(radius: f32, error: f32) -> usize {
251    let r = radius.max(1e-5);
252    let e = error.clamp(1e-5, r);
253    ((TAU / (2.0 * (1.0 - e / r).acos())).ceil() as usize).clamp(12, 256)
254}
255
256#[derive(Clone, Copy)]
257enum Species {
258    Cherry,
259    Plum,
260    Magnolia,
261}
262
263impl Species {
264    fn from_spec(spec: TreeSpec) -> Self {
265        match spec.branching {
266            0..=5 => Self::Magnolia,
267            6 => Self::Plum,
268            _ => Self::Cherry,
269        }
270    }
271
272    fn children(self, level: u8) -> usize {
273        match (self, level) {
274            (Self::Cherry, 1 | 2) => 4,
275            (Self::Plum, 1 | 2) => 4,
276            (Self::Magnolia, 2) => 4,
277            (Self::Magnolia, 3) => 2,
278            (Self::Magnolia, _) => 3,
279            (Self::Cherry, 4) => 2,
280            _ => 3,
281        }
282    }
283}
284
285struct Limb {
286    path: [[f32; 3]; 3],
287    parent: Option<usize>,
288    children: Vec<usize>,
289    level: u8,
290    radius: f32,
291    tip: f32,
292    uv: f32,
293}
294
295fn limb(
296    limbs: &mut Vec<Limb>,
297    parent: Option<usize>,
298    start: [f32; 3],
299    end: [f32; 3],
300    lift: f32,
301    level: u8,
302) -> usize {
303    let control = add(scale(start, 0.53), scale(end, 0.47));
304    let control = add(control, [0.0, lift, 0.0]);
305    let uv = parent.map_or(0.0, |index| {
306        let branch: &Limb = &limbs[index];
307        branch.uv
308            + length(sub(branch.path[1], branch.path[0]))
309            + length(sub(branch.path[2], branch.path[1]))
310    });
311    let index = limbs.len();
312    limbs.push(Limb {
313        path: [start, control, end],
314        parent,
315        children: Vec::new(),
316        level,
317        radius: 0.0,
318        tip: 0.0,
319        uv,
320    });
321    if let Some(parent) = parent {
322        limbs[parent].children.push(index);
323    }
324    index
325}
326
327fn grow(
328    limbs: &mut Vec<Limb>,
329    parent: usize,
330    heading: f32,
331    species: Species,
332    spec: TreeSpec,
333    rng: &mut Random,
334) {
335    let level = limbs[parent].level;
336    if level == 4 && !matches!(species, Species::Cherry) || level == 5 {
337        return;
338    }
339    let count = species.children(level);
340    let start = limbs[parent].path[2];
341    for child in 0..count {
342        let fan = child as f32 - (count - 1) as f32 * 0.5;
343        let spread = if level == 1 { 0.84 } else { 1.06 };
344        let angle = heading + fan * spread + rng.between(-0.48, 0.48);
345        let horizontal = match level {
346            1 => spec.crown_width * 0.33,
347            2 => spec.crown_width * 0.22,
348            3 => spec.crown_width * 0.095,
349            _ => spec.crown_width * 0.055,
350        } * rng.between(0.78, 1.17)
351            * if matches!(species, Species::Magnolia) {
352                0.75
353            } else {
354                1.0
355            };
356        let base_rise = match level {
357            1 => spec.crown_height * 0.23,
358            2 => spec.crown_height * 0.13,
359            3 => spec.crown_height * 0.045,
360            _ => spec.crown_height * 0.035,
361        };
362        let rise = match species {
363            Species::Cherry => base_rise * rng.between(-0.65, 1.75),
364            Species::Plum => base_rise * rng.between(-0.95, 1.85),
365            Species::Magnolia => base_rise * rng.between(0.35, 2.1),
366        };
367        let end = add(
368            start,
369            [angle.cos() * horizontal, rise, angle.sin() * horizontal],
370        );
371        let index = limb(limbs, Some(parent), start, end, rise * 0.11, level + 1);
372        grow(limbs, index, angle, species, spec, rng);
373    }
374}
375
376fn split_scaffold(
377    limbs: &mut Vec<Limb>,
378    parent: usize,
379    heading: f32,
380    species: Species,
381    spec: TreeSpec,
382    rng: &mut Random,
383) {
384    let [start, control, end] = limbs[parent].path;
385    let t = rng.between(0.40, 0.63);
386    let left_control = add(scale(start, 1.0 - t), scale(control, t));
387    let right_control = add(scale(control, 1.0 - t), scale(end, t));
388    let middle = add(scale(left_control, 1.0 - t), scale(right_control, t));
389    limbs[parent].path = [start, left_control, middle];
390    let continuation = limb(limbs, Some(parent), middle, end, 0.0, 1);
391    limbs[continuation].path[1] = right_control;
392    let turn = if rng.next() < 0.5 { -1.0 } else { 1.0 };
393    let angle = heading + turn * rng.between(0.67, 1.20);
394    let reach = spec.crown_width * rng.between(0.20, 0.32);
395    let rise = spec.crown_height
396        * match species {
397            Species::Magnolia => rng.between(0.17, 0.33),
398            _ => rng.between(-0.06, 0.22),
399        };
400    let side_end = add(middle, [angle.cos() * reach, rise, angle.sin() * reach]);
401    let side = limb(limbs, Some(parent), middle, side_end, rise * 0.12, 2);
402    grow(limbs, continuation, heading, species, spec, rng);
403    grow(limbs, side, angle, species, spec, rng);
404}
405
406#[derive(Clone, Copy)]
407struct BloomContext {
408    species: Species,
409    spec: TreeSpec,
410    seed: u64,
411}
412
413pub const TWIG_TIP_RADIUS: f32 = 0.001;
414
415fn atlas_rect(index: f32) -> [f32; 4] {
416    [index / 6.0, 0.0, 1.0 / 6.0, 1.0]
417}
418
419fn bloom_cluster(
420    cards: &mut Vec<Card>,
421    petal_sources: &mut Vec<PetalSource>,
422    branch: &Limb,
423    context: BloomContext,
424    index: usize,
425    center: [f32; 3],
426    rng: &mut Random,
427) -> usize {
428    let BloomContext {
429        species,
430        spec,
431        seed,
432    } = context;
433    let count = if matches!(species, Species::Magnolia) {
434        1
435    } else {
436        7 + (rng.next() * 4.0) as usize
437    };
438    let sway = SwayVertex {
439        pivot: branch.path[0],
440        level: 4.0,
441        stiffness: 0.12,
442    };
443    let diameter = match species {
444        Species::Cherry => rng.between(0.025, 0.040),
445        Species::Plum => rng.between(0.020, 0.025),
446        Species::Magnolia => rng.between(0.10, 0.20),
447    };
448    let size = diameter * 0.5;
449    let count = ((count as f32 * spec.bloom).round() as usize)
450        .min(count)
451        .min((count as f32 * spec.blossom_density).round() as usize);
452    let facing = normalize([
453        rng.between(-1.0, 1.0),
454        rng.between(0.25, 1.0),
455        rng.between(-1.0, 1.0),
456    ]);
457    if count > 0 {
458        let (right, up) = basis(facing);
459        cards.push(Card {
460            position: center,
461            right,
462            up,
463            size: match species {
464                Species::Cherry => [0.105, 0.105],
465                Species::Plum => [0.038, 0.038],
466                Species::Magnolia => [0.13, 0.13],
467            }
468            .map(|size| size * spec.bloom.sqrt()),
469            color: match species {
470                Species::Cherry => [2.1, 1.72, 1.89, 1.0],
471                Species::Plum => [1.9, 1.24, 1.45, 1.0],
472                Species::Magnolia => [2.0, 1.65, 1.83, 1.0],
473            },
474            atlas: atlas_rect(4.0),
475            sway,
476            blossom: true,
477            lod: CardLod::Cluster,
478        });
479    }
480    for flower in 0..count {
481        let angle = TAU * (flower as f32 / count as f32 + rng.between(-0.09, 0.09));
482        let distance = match species {
483            Species::Cherry => rng.between(0.035, 0.095),
484            Species::Plum => rng.between(0.002, 0.006),
485            Species::Magnolia => rng.between(0.015, 0.045),
486        };
487        let position = add(
488            center,
489            [
490                angle.cos() * distance,
491                rng.between(-0.25, 0.45) * distance,
492                angle.sin() * distance,
493            ],
494        );
495        let facing = normalize([
496            angle.cos() + rng.between(-0.45, 0.45),
497            rng.between(0.2, 1.1),
498            angle.sin() + rng.between(-0.45, 0.45),
499        ]);
500        let (right, up) = basis(facing);
501        let shade = rng.between(0.0, 1.0);
502        let color = match species {
503            Species::Cherry => [1.0, 0.69 + 0.28 * shade, 0.79 + 0.18 * shade, 1.0],
504            Species::Plum => [
505                0.85 + 0.15 * shade,
506                0.36 + 0.27 * shade,
507                0.53 + 0.30 * shade,
508                1.0,
509            ],
510            Species::Magnolia => [
511                0.91 + 0.09 * shade,
512                0.71 + 0.26 * shade,
513                0.79 + 0.20 * shade,
514                1.0,
515            ],
516        };
517        cards.push(Card {
518            position,
519            right,
520            up,
521            size: [size, size],
522            color: [color[0] * 2.0, color[1] * 2.0, color[2] * 2.0, 1.0],
523            atlas: atlas_rect(match species {
524                Species::Cherry => 0.0,
525                Species::Plum => 1.0,
526                Species::Magnolia => 2.0,
527            }),
528            sway,
529            blossom: true,
530            lod: CardLod::Individual,
531        });
532    }
533    if rng.next() < spec.leaf_density * 0.54 {
534        let normal = normalize([rng.between(-1.0, 1.0), 0.6, rng.between(-1.0, 1.0)]);
535        let (right, up) = basis(normal);
536        cards.push(Card {
537            position: add(center, [0.0, -0.018, 0.0]),
538            right,
539            up,
540            size: [0.028, 0.04],
541            color: [0.38, 0.57, 0.27, 1.0],
542            atlas: atlas_rect(3.0),
543            sway,
544            blossom: false,
545            lod: CardLod::Always,
546        });
547    }
548    petal_sources.push(PetalSource {
549        position: center,
550        rate: spec.blossom_density * spec.bloom * 0.18,
551        seed: seed ^ (index as u64).wrapping_mul(0x9e3779b97f4a7c15),
552        sway,
553    });
554    count
555}
556
557fn topology(spec: TreeSpec, species: Species, rng: &mut Random) -> Vec<Limb> {
558    let mut limbs = Vec::new();
559    let root_end = [
560        0.0,
561        spec.height
562            * if matches!(species, Species::Magnolia) {
563                0.16
564            } else {
565                0.30
566            },
567        0.0,
568    ];
569    let mut spine = limb(&mut limbs, None, [0.0; 3], root_end, 0.0, 0);
570    for branch in 0..spec.branching {
571        let start = limbs[spine].path[2];
572        let angle = TAU * (branch as f32 / spec.branching as f32 + rng.between(-0.055, 0.055));
573        let reach = if matches!(species, Species::Magnolia) {
574            spec.crown_width * (0.54 - branch as f32 * 0.055) * rng.between(0.85, 1.16)
575        } else {
576            spec.crown_width * rng.between(0.28, 0.58)
577        };
578        let rise = if matches!(species, Species::Magnolia) {
579            spec.crown_height * (0.18 + branch as f32 * 0.055) * rng.between(0.82, 1.16)
580        } else {
581            spec.crown_height * rng.between(0.10, 0.49)
582        };
583        let end = add(start, [angle.cos() * reach, rise, angle.sin() * reach]);
584        let scaffold = limb(&mut limbs, Some(spine), start, end, rise * 0.16, 1);
585        split_scaffold(&mut limbs, scaffold, angle, species, spec, rng);
586        let next = add(
587            start,
588            [
589                rng.between(-0.05, 0.05),
590                spec.height
591                    * if matches!(species, Species::Magnolia) {
592                        0.12
593                    } else {
594                        0.085
595                    },
596                rng.between(-0.05, 0.05),
597            ],
598        );
599        spine = limb(&mut limbs, Some(spine), start, next, 0.0, 0);
600    }
601    let apex_heading = rng.between(0.0, TAU);
602    grow(&mut limbs, spine, apex_heading, species, spec, rng);
603    for index in (0..limbs.len()).rev() {
604        let flow = limbs[index]
605            .children
606            .iter()
607            .map(|child| limbs[*child].radius * limbs[*child].radius)
608            .sum::<f32>()
609            .sqrt();
610        limbs[index].tip = flow;
611        limbs[index].radius = (flow * flow
612            + if limbs[index].children.is_empty() {
613                1.0
614            } else {
615                0.16
616            })
617        .sqrt();
618    }
619    limbs
620}
621
622impl Tree {
623    pub fn new(seed: u64, spec: TreeSpec, screen_error_world: f32) -> Self {
624        let height = spec.height.max(0.5);
625        let blossom_density = if spec.blossom_density.is_finite() {
626            spec.blossom_density.clamp(0.0, 4.0)
627        } else {
628            0.0
629        };
630        let leaf_density = if spec.leaf_density.is_finite() {
631            spec.leaf_density.clamp(0.0, 1.0)
632        } else {
633            0.0
634        };
635        let bloom = if spec.bloom.is_finite() {
636            spec.bloom.clamp(0.0, 1.0)
637        } else {
638            0.0
639        };
640        let spec = TreeSpec {
641            height,
642            trunk_radius: spec.trunk_radius.max(0.01).min(height * 0.1),
643            branching: spec.branching.clamp(1, 32),
644            crown_width: spec.crown_width.max(0.1),
645            crown_height: spec.crown_height.max(0.1),
646            blossom_density,
647            bloom,
648            leaf_density,
649        };
650        let error = if screen_error_world.is_finite() {
651            screen_error_world.clamp(0.001, 0.1)
652        } else {
653            0.01
654        };
655        let mut rng = Random(if seed == 0 { 0x9e3779b97f4a7c15 } else { seed });
656        let mut positions = Vec::new();
657        let mut normals = Vec::new();
658        let mut tangents = Vec::new();
659        let mut uvs = Vec::new();
660        let mut indices = Vec::new();
661        let mut sway = Vec::new();
662        let mut cards = Vec::new();
663        let mut petal_sources = Vec::new();
664        let mut flower_count = 0;
665        let species = Species::from_spec(spec);
666        let limbs = topology(spec, species, &mut rng);
667        let multiplier = spec.trunk_radius / limbs[0].radius;
668        let mut tube = Tube {
669            positions: &mut positions,
670            normals: &mut normals,
671            tangents: &mut tangents,
672            uvs: &mut uvs,
673            indices: &mut indices,
674            sway: &mut sway,
675            error,
676        };
677        for (index, branch) in limbs.iter().enumerate() {
678            let radius = if branch.level == 5 {
679                (branch.radius * multiplier).min(0.005)
680            } else {
681                branch.radius * multiplier
682            };
683            let tip = if branch.children.is_empty() {
684                TWIG_TIP_RADIUS
685            } else {
686                branch.tip * multiplier
687            };
688            let hidden_twig =
689                matches!(species, Species::Cherry) && spec.bloom >= 0.85 && branch.level >= 3;
690            if !hidden_twig && (branch.level < 3 || radius * 2.0 >= error) {
691                let pivot = branch
692                    .parent
693                    .map_or([0.0; 3], |parent| limbs[parent].path[0]);
694                let stiffness = match branch.level {
695                    0 => 0.96,
696                    1 => 0.69,
697                    2 => 0.41,
698                    _ => 0.18,
699                };
700                tube.add(TubeShape {
701                    path: branch.path,
702                    radius,
703                    tip,
704                    pivot,
705                    level: branch.level as f32,
706                    stiffness,
707                    uv_start: branch.uv,
708                    collar: branch.level > 0,
709                });
710            }
711            if branch.level >= 3 || matches!(species, Species::Cherry) && branch.level == 2 {
712                let spurs = match species {
713                    Species::Cherry if branch.level == 2 => 12,
714                    Species::Cherry if branch.level == 5 => 6,
715                    Species::Cherry => 18,
716                    Species::Plum => 13,
717                    Species::Magnolia => 3,
718                };
719                for spur in 0..spurs {
720                    if spur + 1 != spurs && rng.next() > spec.blossom_density.min(1.0) {
721                        continue;
722                    }
723                    let t = (spur as f32 + 1.0) / spurs as f32;
724                    let center = add(
725                        add(
726                            scale(branch.path[0], (1.0 - t).powi(2)),
727                            scale(branch.path[1], 2.0 * t * (1.0 - t)),
728                        ),
729                        scale(branch.path[2], t * t),
730                    );
731                    let mut spur_rng = Random(seed.wrapping_add(
732                        (index as u64 * 32 + spur as u64 + 1).wrapping_mul(0x9e3779b97f4a7c15),
733                    ));
734                    let center = if matches!(species, Species::Cherry) {
735                        let reach = if branch.level == 2 { 0.19 } else { 0.27 };
736                        add(
737                            center,
738                            [
739                                spur_rng.between(-reach, reach),
740                                spur_rng.between(-reach * 0.55, reach * 0.85),
741                                spur_rng.between(-reach, reach),
742                            ],
743                        )
744                    } else {
745                        center
746                    };
747                    flower_count += bloom_cluster(
748                        &mut cards,
749                        &mut petal_sources,
750                        branch,
751                        BloomContext {
752                            species,
753                            spec,
754                            seed,
755                        },
756                        index * spurs + spur,
757                        center,
758                        &mut spur_rng,
759                    );
760                }
761                if branch.children.is_empty() && spec.blossom_density > 0.0 && spec.bloom > 0.0 {
762                    let facing = normalize(sub(branch.path[2], branch.path[1]));
763                    let (right, up) = basis(facing);
764                    let size = match species {
765                        Species::Cherry => 0.018,
766                        Species::Plum => 0.011,
767                        Species::Magnolia => 0.075,
768                    };
769                    cards.push(Card {
770                        position: branch.path[2],
771                        right,
772                        up,
773                        size: [size, size],
774                        color: [2.0, 1.65, 1.8, 1.0],
775                        atlas: atlas_rect(match species {
776                            Species::Cherry => 0.0,
777                            Species::Plum => 1.0,
778                            Species::Magnolia => 2.0,
779                        }),
780                        sway: SwayVertex {
781                            pivot: branch.path[0],
782                            level: branch.level as f32,
783                            stiffness: 0.12,
784                        },
785                        blossom: true,
786                        lod: CardLod::Always,
787                    });
788                    flower_count += 1;
789                }
790                if branch.children.is_empty() && (spec.blossom_density == 0.0 || spec.bloom == 0.0)
791                {
792                    let (right, up) = basis(normalize(sub(branch.path[2], branch.path[1])));
793                    cards.push(Card {
794                        position: branch.path[2],
795                        right,
796                        up,
797                        size: [0.006, 0.012],
798                        color: [0.72, 0.3, 0.38, 1.0],
799                        atlas: atlas_rect(5.0),
800                        sway: SwayVertex {
801                            pivot: branch.path[0],
802                            level: 4.0,
803                            stiffness: 0.12,
804                        },
805                        blossom: false,
806                        lod: CardLod::Always,
807                    });
808                }
809            }
810        }
811        let wood = Mesh::new(positions, normals, tangents, uvs, indices);
812        Self {
813            seed,
814            spec,
815            wood,
816            sway,
817            cards,
818            atlas: atlas(),
819            petal_sources,
820            flower_count,
821            max_error: error,
822        }
823    }
824}
825
826fn flower_sample(u: f32, v: f32, plum: bool) -> [f32; 3] {
827    let mut edge = f32::INFINITY;
828    for petal in 0..5 {
829        let angle = TAU * petal as f32 / 5.0 - std::f32::consts::FRAC_PI_2;
830        let outward = u * angle.cos() + v * angle.sin();
831        let across = -u * angle.sin() + v * angle.cos();
832        let width = if plum { 0.34 } else { 0.39 };
833        let shape = ((outward - 0.45) / 0.51).powi(2) + (across / width).powi(2);
834        let notch = outward > 0.79 && across.abs() < (outward - 0.79) * 0.48;
835        if !notch {
836            edge = edge.min(shape);
837        }
838    }
839    let alpha = ((1.0 - edge) * 13.0).clamp(0.0, 1.0);
840    let radius = (u * u + v * v).sqrt();
841    let center = ((0.22 - radius) * 19.0).clamp(0.0, 1.0) * alpha;
842    let mut stamens: f32 = 0.0;
843    for i in 0..9 {
844        let angle = TAU * i as f32 / 9.0;
845        let tip = [angle.cos() * 0.23, angle.sin() * 0.23];
846        let distance = ((u - tip[0]).powi(2) + (v - tip[1]).powi(2)).sqrt();
847        stamens = stamens.max(((0.045 - distance) * 28.0).clamp(0.0, 1.0));
848    }
849    [alpha, center, stamens * alpha]
850}
851
852pub fn atlas() -> AlphaAtlas {
853    let width = 384;
854    let height = 64;
855    let mut pixels = vec![0; width * height * 4];
856    for y in 0..height {
857        for x in 0..width {
858            let tile = x / 64;
859            let u = ((x % 64) as f32 + 0.5) / 32.0 - 1.0;
860            let v = (y as f32 + 0.5) / 32.0 - 1.0;
861            let sample = match tile {
862                0 => flower_sample(u, v, false),
863                1 => flower_sample(u, v, true),
864                2 => {
865                    let mut coverage: f32 = 0.0;
866                    for petal in 0..6 {
867                        let angle = TAU * petal as f32 / 6.0 - std::f32::consts::FRAC_PI_2;
868                        let outward = u * angle.cos() + v * angle.sin();
869                        let across = -u * angle.sin() + v * angle.cos();
870                        let ellipse = ((outward - 0.37) / 0.62).powi(2) + (across / 0.29).powi(2);
871                        coverage = coverage.max(((1.0 - ellipse) * 11.0).clamp(0.0, 1.0));
872                    }
873                    let center = ((0.27 - (u * u + v * v).sqrt()) * 12.0).clamp(0.0, 1.0);
874                    [coverage, center * coverage, center * coverage * 0.55]
875                }
876                3 => {
877                    let shape = (u / 0.52).powi(2) + ((v + 0.04) / 0.92).powi(2);
878                    [((1.0 - shape) * 12.0).clamp(0.0, 1.0), 0.0, 0.0]
879                }
880                4 => {
881                    let mut result = [0.0_f32; 3];
882                    for (cx, cy) in [(-0.42, -0.25), (0.42, -0.15), (0.0, 0.39), (-0.04, -0.36)] {
883                        let flower = flower_sample((u - cx) * 1.9, (v - cy) * 1.9, false);
884                        for channel in 0..3 {
885                            result[channel] = result[channel].max(flower[channel]);
886                        }
887                    }
888                    result
889                }
890                _ => {
891                    let shape = (u / 0.29).powi(2) + ((v + 0.05) / 0.70).powi(2);
892                    [((1.0 - shape) * 14.0).clamp(0.0, 1.0), 0.12, 0.0]
893                }
894            };
895            let offset = (y * width + x) * 4;
896            pixels[offset] = (sample[0] * 255.0) as u8;
897            pixels[offset + 1] = (sample[1] * 255.0) as u8;
898            pixels[offset + 2] = (sample[2] * 255.0) as u8;
899            pixels[offset + 3] = pixels[offset];
900        }
901    }
902    AlphaAtlas {
903        width: width as u32,
904        height: height as u32,
905        pixels,
906    }
907}
908
909#[cfg(test)]
910mod tests {
911    use super::*;
912
913    #[test]
914    fn branch_radii_and_envelope() {
915        for spec in [TreeSpec::cherry(), TreeSpec::plum(), TreeSpec::magnolia()] {
916            let mut rng = Random(31);
917            let limbs = topology(spec, Species::from_spec(spec), &mut rng);
918            assert!(limbs.iter().any(|branch| branch.level == 4));
919            for branch in &limbs {
920                assert!(branch.radius >= branch.tip);
921                let children_area = branch
922                    .children
923                    .iter()
924                    .map(|child| limbs[*child].radius.powi(2))
925                    .sum::<f32>();
926                assert!((branch.tip.powi(2) - children_area).abs() < 1e-4);
927                if branch.children.is_empty() {
928                    assert_eq!(branch.tip, 0.0);
929                }
930            }
931            let scale_factor = spec.trunk_radius / limbs[0].radius;
932            for branch in &limbs {
933                let mut positions = Vec::new();
934                let mut normals = Vec::new();
935                let mut tangents = Vec::new();
936                let mut uvs = Vec::new();
937                let mut indices = Vec::new();
938                let mut sway = Vec::new();
939                let mut tube = Tube {
940                    positions: &mut positions,
941                    normals: &mut normals,
942                    tangents: &mut tangents,
943                    uvs: &mut uvs,
944                    indices: &mut indices,
945                    sway: &mut sway,
946                    error: 0.01,
947                };
948                let radius = branch.radius * scale_factor;
949                let tip = branch.tip * scale_factor;
950                tube.add(TubeShape {
951                    path: branch.path,
952                    radius,
953                    tip,
954                    pivot: branch.path[0],
955                    level: branch.level as f32,
956                    stiffness: 0.5,
957                    uv_start: branch.uv,
958                    collar: branch.level > 0,
959                });
960                let ring_size = segments(radius, 0.01) + 1;
961                let rings = positions.len() / ring_size - 1;
962                for (vertex, point) in positions.iter().enumerate() {
963                    let t = (vertex / ring_size) as f32 / rings as f32;
964                    let center = add(
965                        add(
966                            scale(branch.path[0], (1.0 - t).powi(2)),
967                            scale(branch.path[1], 2.0 * t * (1.0 - t)),
968                        ),
969                        scale(branch.path[2], t * t),
970                    );
971                    let smooth = t * t * (3.0 - 2.0 * t);
972                    let flare = if branch.level > 0 {
973                        radius * 0.12 * (1.0 - t * 7.0).max(0.0).powi(2)
974                    } else {
975                        0.0
976                    };
977                    let envelope = radius + (tip - radius) * smooth + flare;
978                    assert!(length(sub(*point, center)) <= envelope + 1e-5);
979                }
980            }
981        }
982    }
983
984    #[test]
985    fn flower_sizes_and_spurs() {
986        for (spec, low, high, minimum) in [
987            (TreeSpec::cherry(), 0.025, 0.040, 10_000),
988            (TreeSpec::plum(), 0.020, 0.025, 6_000),
989            (TreeSpec::magnolia(), 0.10, 0.20, 500),
990        ] {
991            let tree = Tree::new(23, spec, 0.001);
992            assert!(tree.flower_count >= minimum, "{}", tree.flower_count);
993            let flowers: Vec<_> = tree
994                .cards
995                .iter()
996                .filter(|card| card.blossom && card.lod != CardLod::Cluster)
997                .collect();
998            assert_eq!(flowers.len(), tree.flower_count);
999            for flower in flowers {
1000                let diameter = flower.size[0] * 2.0;
1001                assert!((low..=high).contains(&diameter), "{diameter}");
1002            }
1003        }
1004        let mut rng = Random(19);
1005        let spec = TreeSpec::cherry();
1006        let branch = Limb {
1007            path: [[0.0; 3]; 3],
1008            parent: None,
1009            children: Vec::new(),
1010            level: 4,
1011            radius: 1.0,
1012            tip: 0.0,
1013            uv: 0.0,
1014        };
1015        let mut cards = Vec::new();
1016        let mut sources = Vec::new();
1017        for index in 0..100 {
1018            let start = cards.len();
1019            let count = bloom_cluster(
1020                &mut cards,
1021                &mut sources,
1022                &branch,
1023                BloomContext {
1024                    species: Species::Cherry,
1025                    spec,
1026                    seed: 19,
1027                },
1028                index,
1029                [0.0; 3],
1030                &mut rng,
1031            );
1032            assert!((7..=10).contains(&count));
1033            assert_eq!(
1034                cards[start..]
1035                    .iter()
1036                    .filter(|card| card.lod == CardLod::Individual)
1037                    .count(),
1038                count
1039            );
1040        }
1041    }
1042
1043    #[test]
1044    fn terminal_nodes_have_buds_or_flowers() {
1045        for mut spec in [TreeSpec::cherry(), TreeSpec::plum(), TreeSpec::magnolia()] {
1046            let blooming = Tree::new(23, spec, 0.001);
1047            spec.blossom_density = 0.0;
1048            let dormant = Tree::new(23, spec, 0.001);
1049            let mut rng = Random(23);
1050            let limbs = topology(spec, Species::from_spec(spec), &mut rng);
1051            for branch in limbs
1052                .iter()
1053                .filter(|branch| branch.children.is_empty() && branch.level >= 3)
1054            {
1055                assert!(
1056                    dormant.cards.iter().any(
1057                        |card| card.position == branch.path[2] && card.atlas == atlas_rect(5.0)
1058                    )
1059                );
1060                assert!(
1061                    blooming
1062                        .cards
1063                        .iter()
1064                        .any(|card| card.blossom && card.position == branch.path[2])
1065                );
1066            }
1067        }
1068        assert_eq!(TWIG_TIP_RADIUS * 2.0, 0.002);
1069    }
1070
1071    #[test]
1072    fn cluster_cards_are_available_at_every_build_detail() {
1073        let near = Tree::new(23, TreeSpec::cherry(), 0.001);
1074        let far = Tree::new(23, TreeSpec::cherry(), 0.01);
1075        assert_eq!(near.flower_count, far.flower_count);
1076        assert_eq!(near.cards.len(), far.cards.len());
1077        assert_eq!(near.cards, far.cards);
1078        assert!(far.cards.iter().any(|card| card.atlas == atlas_rect(4.0)));
1079    }
1080
1081    #[test]
1082    fn bloom_density_is_monotone() {
1083        let mut spec = TreeSpec::cherry();
1084        let mut previous = 0;
1085        for bloom in [0.0, 0.25, 0.5, 0.75, 1.0] {
1086            spec.bloom = bloom;
1087            let tree = Tree::new(23, spec, 0.01);
1088            assert!(tree.flower_count >= previous);
1089            previous = tree.flower_count;
1090        }
1091        assert!(previous > 100_000);
1092    }
1093    #[test]
1094    fn deterministic_and_continuous() {
1095        let a = Tree::new(17, TreeSpec::cherry(), 0.01);
1096        let b = Tree::new(17, TreeSpec::cherry(), 0.01);
1097        assert_eq!(a.wood.bytes(), b.wood.bytes());
1098        assert_eq!(a.cards, b.cards);
1099        assert_ne!(a.cards, Tree::new(18, TreeSpec::cherry(), 0.01).cards);
1100        assert_eq!(a.wood.positions.len(), a.sway.len());
1101        assert!(!a.petal_sources.is_empty());
1102    }
1103    #[test]
1104    fn tessellation_bound() {
1105        for radius in [0.01, 0.05, 0.3] {
1106            for error in [0.001, 0.01] {
1107                let n = segments(radius, error);
1108                let actual = radius * (1.0 - (std::f32::consts::PI / n as f32).cos());
1109                assert!(actual <= error + 1e-6);
1110            }
1111        }
1112        let start = [0.0, 0.0, 0.0];
1113        let control = [3.0, 5.0, 0.0];
1114        let end = [6.0, 0.0, 0.0];
1115        let mut positions = Vec::new();
1116        let mut normals = Vec::new();
1117        let mut tangents = Vec::new();
1118        let mut uvs = Vec::new();
1119        let mut indices = Vec::new();
1120        let mut sway = Vec::new();
1121        let error = 0.005;
1122        let mut tube = Tube {
1123            positions: &mut positions,
1124            normals: &mut normals,
1125            tangents: &mut tangents,
1126            uvs: &mut uvs,
1127            indices: &mut indices,
1128            sway: &mut sway,
1129            error,
1130        };
1131        tube.add(TubeShape {
1132            path: [start, control, end],
1133            radius: 0.3,
1134            tip: 0.03,
1135            pivot: start,
1136            level: 1.0,
1137            stiffness: 0.5,
1138            uv_start: 0.0,
1139            collar: false,
1140        });
1141        let ring_size = segments(0.3, error) + 1;
1142        let rings = positions.len() / ring_size - 1;
1143        for j in 0..rings {
1144            let t0 = j as f32 / rings as f32;
1145            let t1 = (j + 1) as f32 / rings as f32;
1146            let t = (t0 + t1) * 0.5;
1147            let curve = add(
1148                add(
1149                    scale(start, (1.0 - t) * (1.0 - t)),
1150                    scale(control, 2.0 * t * (1.0 - t)),
1151                ),
1152                scale(end, t * t),
1153            );
1154            let a = add(
1155                add(
1156                    scale(start, (1.0 - t0) * (1.0 - t0)),
1157                    scale(control, 2.0 * t0 * (1.0 - t0)),
1158                ),
1159                scale(end, t0 * t0),
1160            );
1161            let b = add(
1162                add(
1163                    scale(start, (1.0 - t1) * (1.0 - t1)),
1164                    scale(control, 2.0 * t1 * (1.0 - t1)),
1165                ),
1166                scale(end, t1 * t1),
1167            );
1168            assert!(length(sub(curve, scale(add(a, b), 0.5))) <= error + 1e-6);
1169        }
1170    }
1171    #[test]
1172    fn radius_matches_at_tube_joint() {
1173        let mut positions = Vec::new();
1174        let mut normals = Vec::new();
1175        let mut tangents = Vec::new();
1176        let mut uvs = Vec::new();
1177        let mut indices = Vec::new();
1178        let mut sway = Vec::new();
1179        let mut tube = Tube {
1180            positions: &mut positions,
1181            normals: &mut normals,
1182            tangents: &mut tangents,
1183            uvs: &mut uvs,
1184            indices: &mut indices,
1185            sway: &mut sway,
1186            error: 0.01,
1187        };
1188        let a = [0.0, 0.0, 0.0];
1189        let b = [0.0, 2.0, 0.0];
1190        let c = [0.0, 4.0, 0.0];
1191        tube.add(TubeShape {
1192            path: [a, [0.0, 1.0, 0.0], b],
1193            radius: 0.3,
1194            tip: 0.2,
1195            pivot: a,
1196            level: 0.0,
1197            stiffness: 0.9,
1198            uv_start: 0.0,
1199            collar: false,
1200        });
1201        let first = tube.positions.len();
1202        tube.add(TubeShape {
1203            path: [b, [0.0, 3.0, 0.0], c],
1204            radius: 0.2,
1205            tip: 0.04,
1206            pivot: b,
1207            level: 1.0,
1208            stiffness: 0.5,
1209            uv_start: 0.0,
1210            collar: false,
1211        });
1212        let end_radius = length(sub(positions[first - 1], b));
1213        let start_radius = length(sub(positions[first], b));
1214        assert!((end_radius - start_radius).abs() < 1e-6);
1215    }
1216    #[test]
1217    fn sway_stiffness() {
1218        let p = [1.0, 6.0, 1.0];
1219        let pivot = [0.0, 5.0, 0.0];
1220        let trunk = tree_sway(p, pivot, 0.0, 0.95, 1.0, 2.0, [1.0, 0.0, 0.3]);
1221        let twig = tree_sway(p, pivot, 2.0, 0.18, 1.0, 2.0, [1.0, 0.0, 0.3]);
1222        assert!(length(sub(trunk, p)) < length(sub(twig, p)));
1223        let phase: f32 = 1.0 * (0.7 + 2.0 * 0.41);
1224        let bend = phase.sin() * 2.0 * (1.0 - 0.18) * length(sub(p, pivot)) * (0.012 + 0.009 * 2.0);
1225        let d = normalize([1.00001, 0.0, 0.3]);
1226        let reference = add(p, [d[0] * bend, -bend.abs() * 0.12, d[2] * bend]);
1227        for (actual, expected) in twig.into_iter().zip(reference) {
1228            assert!((actual - expected).abs() < 1e-6);
1229        }
1230        naga::front::wgsl::parse_str(TREE_SWAY_WGSL).unwrap();
1231    }
1232}