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}