Skip to main content

alice_sdf/compiled/
compiler.rs

1//! Compiler: SdfNode tree → CompiledSdf bytecode
2//!
3//! Converts the recursive Arc-based tree structure into a flat
4//! instruction array for cache-efficient evaluation.
5//!
6//! Author: Moroya Sakamoto
7
8use super::instruction::Instruction;
9use crate::types::SdfNode;
10
11/// Error type for SDF compilation failures.
12#[derive(Debug, thiserror::Error)]
13pub enum CompileError {
14    /// A primitive in the SDF tree is not supported by the bytecode compiler.
15    #[error(
16        "Unsupported primitive for bytecode compilation: {0}. Use eval() or transpiler instead."
17    )]
18    UnsupportedPrimitive(String),
19
20    /// The SDF tree is too deep and would overflow the evaluation stack.
21    #[error("SDF tree requires stack depth {required} but maximum is {limit} ({kind} stack)")]
22    StackOverflow {
23        /// Kind of stack that overflows ("value" or "coordinate")
24        kind: &'static str,
25        /// Required depth
26        required: usize,
27        /// Maximum allowed depth
28        limit: usize,
29    },
30}
31
32/// Compiled SDF representation
33///
34/// A flat array of instructions that can be evaluated without
35/// recursion or pointer chasing. Construct it with [`CompiledSdf::compile`]
36/// / [`CompiledSdf::try_compile`]; the bytecode is read through
37/// [`instructions`](Self::instructions) / [`aux_data`](Self::aux_data) and
38/// the compile-time facts through [`node_count`](Self::node_count) /
39/// [`lipschitz`](Self::lipschitz) (fields are private since 2.0, so new
40/// compile-time data can be added without a major bump).
41#[derive(Clone, Debug)]
42pub struct CompiledSdf {
43    /// The instruction bytecode
44    pub(crate) instructions: Vec<Instruction>,
45    /// Auxiliary data buffer for operations whose data exceeds `Instruction::params`.
46    /// Indexed by `Instruction::aux_offset` / `Instruction::aux_len`.
47    pub(crate) aux_data: Vec<f32>,
48    /// Original node count (for statistics)
49    pub(crate) node_count: usize,
50    /// Lipschitz bound of the field on its exterior, see [`Self::lipschitz`].
51    pub(crate) lipschitz: f32,
52}
53
54impl CompiledSdf {
55    /// The instruction bytecode (postfix, with `PopTransform` frames).
56    #[inline]
57    #[must_use]
58    pub fn instructions(&self) -> &[Instruction] {
59        &self.instructions
60    }
61
62    /// Auxiliary data for operations whose data exceeds `Instruction::params`
63    /// (heightmaps, lattices, bones, polygon vertices), indexed by
64    /// `Instruction::aux_offset` / `aux_len`.
65    #[inline]
66    #[must_use]
67    pub fn aux_data(&self) -> &[f32] {
68        &self.aux_data
69    }
70
71    /// Number of `SdfNode`s the bytecode was compiled from.
72    #[inline]
73    #[must_use]
74    pub const fn node_count(&self) -> usize {
75        self.node_count
76    }
77
78    /// Lipschitz bound of the field on its exterior
79    /// ([`crate::interval::eval_lipschitz`] at compile time). The marchers
80    /// step by `d / lipschitz`, so a compiled TPMS (√3 … 7) traces correctly
81    /// with the default config; `f32::INFINITY` when the tree has no finite
82    /// bound (domain repetition, taper, …), in which case they step by `d`.
83    #[inline]
84    #[must_use]
85    pub const fn lipschitz(&self) -> f32 {
86        self.lipschitz
87    }
88
89    /// Compile an SdfNode tree into bytecode.
90    ///
91    /// # Panics
92    ///
93    /// Panics if the tree contains unsupported primitives (Triangle, Bezier).
94    /// Use [`try_compile`](Self::try_compile) for a non-panicking alternative.
95    pub fn compile(node: &SdfNode) -> Self {
96        Self::try_compile(node)
97            .expect("CompiledSdf::compile() failed: unsupported primitive in SDF tree")
98    }
99
100    /// Compile an SdfNode tree into bytecode, returning an error on failure.
101    ///
102    /// Use this instead of [`compile`](Self::compile) when the SDF tree may contain
103    /// primitives that cannot be represented in bytecode (e.g. Triangle, Bezier which
104    /// exceed the params\[6\] limit) or when the tree may be too deep for the
105    /// fixed-size evaluation stacks (value: 64, coordinate: 32).
106    pub fn try_compile(node: &SdfNode) -> Result<Self, CompileError> {
107        validate_for_compile(node)?;
108
109        let (value_depth, coord_depth) = compute_stack_depths(node);
110        if value_depth > MAX_VALUE_STACK {
111            return Err(CompileError::StackOverflow {
112                kind: "value",
113                required: value_depth,
114                limit: MAX_VALUE_STACK,
115            });
116        }
117        if coord_depth > MAX_COORD_STACK {
118            return Err(CompileError::StackOverflow {
119                kind: "coordinate",
120                required: coord_depth,
121                limit: MAX_COORD_STACK,
122            });
123        }
124
125        let mut compiler = Compiler::new();
126        compiler.compile_node(node);
127        compiler.instructions.push(Instruction::end());
128
129        Ok(Self {
130            instructions: compiler.instructions,
131            aux_data: compiler.aux_data,
132            node_count: compiler.node_count,
133            lipschitz: crate::interval::eval_lipschitz(node),
134        })
135    }
136
137    /// Get the number of instructions
138    #[inline]
139    pub fn instruction_count(&self) -> usize {
140        self.instructions.len()
141    }
142
143    /// Get memory usage in bytes
144    #[inline]
145    pub fn memory_size(&self) -> usize {
146        self.instructions.len() * std::mem::size_of::<Instruction>()
147            + self.aux_data.len() * std::mem::size_of::<f32>()
148    }
149}
150
151/// Internal compiler state
152struct Compiler {
153    instructions: Vec<Instruction>,
154    aux_data: Vec<f32>,
155    node_count: usize,
156}
157
158impl Compiler {
159    fn new() -> Self {
160        Self {
161            instructions: Vec::with_capacity(256),
162            aux_data: Vec::new(),
163            node_count: 0,
164        }
165    }
166
167    /// Append floats to the auxiliary data buffer, returning (offset, len).
168    fn push_aux(&mut self, data: &[f32]) -> (u32, u32) {
169        let offset = self.aux_data.len() as u32;
170        self.aux_data.extend_from_slice(data);
171        (offset, data.len() as u32)
172    }
173
174    /// Compile a single node (recursive)
175    fn compile_node(&mut self, node: &SdfNode) {
176        self.node_count += 1;
177
178        match node {
179            // === Primitives ===
180            SdfNode::Sphere { radius } => {
181                self.instructions.push(Instruction::sphere(*radius));
182            }
183
184            SdfNode::Box3d { half_extents } => {
185                self.instructions.push(Instruction::box3d(
186                    half_extents.x,
187                    half_extents.y,
188                    half_extents.z,
189                ));
190            }
191
192            SdfNode::Cylinder {
193                radius,
194                half_height,
195            } => {
196                self.instructions
197                    .push(Instruction::cylinder(*radius, *half_height));
198            }
199
200            SdfNode::Torus {
201                major_radius,
202                minor_radius,
203            } => {
204                self.instructions
205                    .push(Instruction::torus(*major_radius, *minor_radius));
206            }
207
208            SdfNode::Plane { normal, distance } => {
209                self.instructions
210                    .push(Instruction::plane(normal.x, normal.y, normal.z, *distance));
211            }
212
213            SdfNode::Capsule {
214                point_a,
215                point_b,
216                radius,
217            } => {
218                self.instructions.push(Instruction::capsule(
219                    point_a.x, point_a.y, point_a.z, point_b.x, point_b.y, point_b.z, *radius,
220                ));
221            }
222
223            SdfNode::Cone {
224                radius,
225                half_height,
226            } => {
227                self.instructions
228                    .push(Instruction::cone(*radius, *half_height));
229            }
230
231            SdfNode::Ellipsoid { radii } => {
232                self.instructions
233                    .push(Instruction::ellipsoid(radii.x, radii.y, radii.z));
234            }
235
236            SdfNode::RoundedCone {
237                r1,
238                r2,
239                half_height,
240            } => {
241                self.instructions
242                    .push(Instruction::rounded_cone(*r1, *r2, *half_height));
243            }
244
245            SdfNode::Pyramid { half_height } => {
246                self.instructions.push(Instruction::pyramid(*half_height));
247            }
248
249            SdfNode::Octahedron { size } => {
250                self.instructions.push(Instruction::octahedron(*size));
251            }
252
253            SdfNode::HexPrism {
254                hex_radius,
255                half_height,
256            } => {
257                self.instructions
258                    .push(Instruction::hex_prism(*hex_radius, *half_height));
259            }
260
261            SdfNode::Link {
262                half_length,
263                r1,
264                r2,
265            } => {
266                self.instructions
267                    .push(Instruction::link(*half_length, *r1, *r2));
268            }
269
270            SdfNode::Triangle { .. } => {
271                unreachable!("Triangle: validated by validate_for_compile()");
272            }
273
274            SdfNode::Bezier { .. } => {
275                unreachable!("Bezier: validated by validate_for_compile()");
276            }
277
278            // === Extended Primitives (38 new) ===
279            SdfNode::RoundedBox {
280                half_extents,
281                round_radius,
282            } => {
283                self.instructions.push(Instruction::rounded_box(
284                    half_extents.x,
285                    half_extents.y,
286                    half_extents.z,
287                    *round_radius,
288                ));
289            }
290
291            SdfNode::CappedCone {
292                half_height,
293                r1,
294                r2,
295            } => {
296                self.instructions
297                    .push(Instruction::capped_cone(*half_height, *r1, *r2));
298            }
299
300            SdfNode::CappedTorus {
301                major_radius,
302                minor_radius,
303                cap_angle,
304            } => {
305                self.instructions.push(Instruction::capped_torus(
306                    *major_radius,
307                    *minor_radius,
308                    *cap_angle,
309                ));
310            }
311
312            SdfNode::RoundedCylinder {
313                radius,
314                round_radius,
315                half_height,
316            } => {
317                self.instructions.push(Instruction::rounded_cylinder(
318                    *radius,
319                    *round_radius,
320                    *half_height,
321                ));
322            }
323
324            SdfNode::TriangularPrism { width, half_depth } => {
325                self.instructions
326                    .push(Instruction::triangular_prism(*width, *half_depth));
327            }
328
329            SdfNode::CutSphere { radius, cut_height } => {
330                self.instructions
331                    .push(Instruction::cut_sphere(*radius, *cut_height));
332            }
333
334            SdfNode::CutHollowSphere {
335                radius,
336                cut_height,
337                thickness,
338            } => {
339                self.instructions.push(Instruction::cut_hollow_sphere(
340                    *radius,
341                    *cut_height,
342                    *thickness,
343                ));
344            }
345
346            SdfNode::DeathStar { ra, rb, d } => {
347                self.instructions
348                    .push(Instruction::death_star(*ra, *rb, *d));
349            }
350
351            SdfNode::SolidAngle { angle, radius } => {
352                self.instructions
353                    .push(Instruction::solid_angle(*angle, *radius));
354            }
355
356            SdfNode::Rhombus {
357                la,
358                lb,
359                half_height,
360                round_radius,
361            } => {
362                self.instructions
363                    .push(Instruction::rhombus(*la, *lb, *half_height, *round_radius));
364            }
365
366            SdfNode::Horseshoe {
367                angle,
368                radius,
369                half_length,
370                width,
371                thickness,
372            } => {
373                self.instructions.push(Instruction::horseshoe(
374                    *angle,
375                    *radius,
376                    *half_length,
377                    *width,
378                    *thickness,
379                ));
380            }
381
382            SdfNode::Vesica { radius, half_dist } => {
383                self.instructions
384                    .push(Instruction::vesica(*radius, *half_dist));
385            }
386
387            SdfNode::InfiniteCylinder { radius } => {
388                self.instructions
389                    .push(Instruction::infinite_cylinder(*radius));
390            }
391
392            SdfNode::InfiniteCone { angle } => {
393                self.instructions.push(Instruction::infinite_cone(*angle));
394            }
395
396            SdfNode::Gyroid { scale, thickness } => {
397                self.instructions
398                    .push(Instruction::gyroid(*scale, *thickness));
399            }
400
401            SdfNode::MetricBall {
402                radius,
403                w_l1,
404                w_l2,
405                w_linf,
406            } => {
407                self.instructions
408                    .push(Instruction::metric_ball(*radius, *w_l1, *w_l2, *w_linf));
409            }
410
411            SdfNode::MetricBlend {
412                inner,
413                outer,
414                center,
415                radius,
416                skin,
417            } => {
418                // stack order matches the binary ops: inner first, outer second
419                self.compile_node(inner);
420                self.compile_node(outer);
421                self.instructions.push(Instruction::metric_blend(
422                    center.x, center.y, center.z, *radius, *skin,
423                ));
424            }
425
426            SdfNode::Heart { size } => {
427                self.instructions.push(Instruction::heart(*size));
428            }
429
430            SdfNode::Tube {
431                outer_radius,
432                thickness,
433                half_height,
434            } => {
435                self.instructions
436                    .push(Instruction::tube(*outer_radius, *thickness, *half_height));
437            }
438
439            SdfNode::Barrel {
440                radius,
441                half_height,
442                bulge,
443            } => {
444                self.instructions
445                    .push(Instruction::barrel(*radius, *half_height, *bulge));
446            }
447
448            SdfNode::Diamond {
449                radius,
450                half_height,
451            } => {
452                self.instructions
453                    .push(Instruction::diamond(*radius, *half_height));
454            }
455
456            SdfNode::ChamferedCube {
457                half_extents,
458                chamfer,
459            } => {
460                self.instructions.push(Instruction::chamfered_cube(
461                    half_extents.x,
462                    half_extents.y,
463                    half_extents.z,
464                    *chamfer,
465                ));
466            }
467
468            SdfNode::SchwarzP { scale, thickness } => {
469                self.instructions
470                    .push(Instruction::schwarz_p(*scale, *thickness));
471            }
472
473            SdfNode::Superellipsoid {
474                half_extents,
475                e1,
476                e2,
477            } => {
478                self.instructions.push(Instruction::superellipsoid(
479                    half_extents.x,
480                    half_extents.y,
481                    half_extents.z,
482                    *e1,
483                    *e2,
484                ));
485            }
486
487            SdfNode::RoundedX {
488                width,
489                round_radius,
490                half_height,
491            } => {
492                self.instructions
493                    .push(Instruction::rounded_x(*width, *round_radius, *half_height));
494            }
495
496            SdfNode::Pie {
497                angle,
498                radius,
499                half_height,
500            } => {
501                self.instructions
502                    .push(Instruction::pie(*angle, *radius, *half_height));
503            }
504
505            SdfNode::Trapezoid {
506                r1,
507                r2,
508                trap_height,
509                half_depth,
510            } => {
511                self.instructions
512                    .push(Instruction::trapezoid(*r1, *r2, *trap_height, *half_depth));
513            }
514
515            SdfNode::Parallelogram {
516                width,
517                para_height,
518                skew,
519                half_depth,
520            } => {
521                self.instructions.push(Instruction::parallelogram(
522                    *width,
523                    *para_height,
524                    *skew,
525                    *half_depth,
526                ));
527            }
528
529            SdfNode::Tunnel {
530                width,
531                height_2d,
532                half_depth,
533            } => {
534                self.instructions
535                    .push(Instruction::tunnel(*width, *height_2d, *half_depth));
536            }
537
538            SdfNode::UnevenCapsule {
539                r1,
540                r2,
541                cap_height,
542                half_depth,
543            } => {
544                self.instructions.push(Instruction::uneven_capsule(
545                    *r1,
546                    *r2,
547                    *cap_height,
548                    *half_depth,
549                ));
550            }
551
552            SdfNode::Egg { ra, rb } => {
553                self.instructions.push(Instruction::egg(*ra, *rb));
554            }
555
556            SdfNode::ArcShape {
557                aperture,
558                radius,
559                thickness,
560                half_height,
561            } => {
562                self.instructions.push(Instruction::arc_shape(
563                    *aperture,
564                    *radius,
565                    *thickness,
566                    *half_height,
567                ));
568            }
569
570            SdfNode::Moon {
571                d,
572                ra,
573                rb,
574                half_height,
575            } => {
576                self.instructions
577                    .push(Instruction::moon(*d, *ra, *rb, *half_height));
578            }
579
580            SdfNode::CrossShape {
581                length,
582                thickness,
583                round_radius,
584                half_height,
585            } => {
586                self.instructions.push(Instruction::cross_shape(
587                    *length,
588                    *thickness,
589                    *round_radius,
590                    *half_height,
591                ));
592            }
593
594            SdfNode::BlobbyCross { size, half_height } => {
595                self.instructions
596                    .push(Instruction::blobby_cross(*size, *half_height));
597            }
598
599            SdfNode::ParabolaSegment {
600                width,
601                para_height,
602                half_depth,
603            } => {
604                self.instructions.push(Instruction::parabola_segment(
605                    *width,
606                    *para_height,
607                    *half_depth,
608                ));
609            }
610
611            SdfNode::RegularPolygon {
612                radius,
613                n_sides,
614                half_height,
615            } => {
616                self.instructions.push(Instruction::regular_polygon(
617                    *radius,
618                    *n_sides,
619                    *half_height,
620                ));
621            }
622
623            SdfNode::StarPolygon {
624                radius,
625                n_points,
626                m,
627                half_height,
628            } => {
629                self.instructions.push(Instruction::star_polygon(
630                    *radius,
631                    *n_points,
632                    *m,
633                    *half_height,
634                ));
635            }
636
637            SdfNode::Stairs {
638                step_width,
639                step_height,
640                n_steps,
641                half_depth,
642            } => {
643                self.instructions.push(Instruction::stairs(
644                    *step_width,
645                    *step_height,
646                    *n_steps,
647                    *half_depth,
648                ));
649            }
650
651            SdfNode::Helix {
652                major_r,
653                minor_r,
654                pitch,
655                half_height,
656            } => {
657                self.instructions.push(Instruction::helix(
658                    *major_r,
659                    *minor_r,
660                    *pitch,
661                    *half_height,
662                ));
663            }
664
665            SdfNode::Tetrahedron { size } => {
666                self.instructions.push(Instruction::tetrahedron(*size));
667            }
668
669            SdfNode::Dodecahedron { radius } => {
670                self.instructions.push(Instruction::dodecahedron(*radius));
671            }
672
673            SdfNode::Icosahedron { radius } => {
674                self.instructions.push(Instruction::icosahedron(*radius));
675            }
676
677            SdfNode::TruncatedOctahedron { radius } => {
678                self.instructions
679                    .push(Instruction::truncated_octahedron(*radius));
680            }
681
682            SdfNode::TruncatedIcosahedron { radius } => {
683                self.instructions
684                    .push(Instruction::truncated_icosahedron(*radius));
685            }
686
687            SdfNode::BoxFrame { half_extents, edge } => {
688                self.instructions
689                    .push(Instruction::box_frame(*half_extents, *edge));
690            }
691
692            SdfNode::DiamondSurface { scale, thickness } => {
693                self.instructions
694                    .push(Instruction::diamond_surface(*scale, *thickness));
695            }
696
697            SdfNode::Neovius { scale, thickness } => {
698                self.instructions
699                    .push(Instruction::neovius(*scale, *thickness));
700            }
701
702            SdfNode::Lidinoid { scale, thickness } => {
703                self.instructions
704                    .push(Instruction::lidinoid(*scale, *thickness));
705            }
706
707            SdfNode::IWP { scale, thickness } => {
708                self.instructions.push(Instruction::iwp(*scale, *thickness));
709            }
710
711            SdfNode::FRD { scale, thickness } => {
712                self.instructions.push(Instruction::frd(*scale, *thickness));
713            }
714
715            SdfNode::FischerKochS { scale, thickness } => {
716                self.instructions
717                    .push(Instruction::fischer_koch_s(*scale, *thickness));
718            }
719
720            SdfNode::PMY { scale, thickness } => {
721                self.instructions.push(Instruction::pmy(*scale, *thickness));
722            }
723
724            // === Binary Operations ===
725            // For binary operations, we use post-order: left, right, op
726            SdfNode::Union { a, b } => {
727                self.compile_node(a);
728                self.compile_node(b);
729                self.instructions.push(Instruction::union());
730            }
731
732            SdfNode::Intersection { a, b } => {
733                self.compile_node(a);
734                self.compile_node(b);
735                self.instructions.push(Instruction::intersection());
736            }
737
738            SdfNode::Subtraction { a, b } => {
739                self.compile_node(a);
740                self.compile_node(b);
741                self.instructions.push(Instruction::subtraction());
742            }
743
744            SdfNode::SmoothUnion { a, b, k } => {
745                self.compile_node(a);
746                self.compile_node(b);
747                self.instructions.push(Instruction::smooth_union(*k));
748            }
749
750            SdfNode::SmoothIntersection { a, b, k } => {
751                self.compile_node(a);
752                self.compile_node(b);
753                self.instructions.push(Instruction::smooth_intersection(*k));
754            }
755
756            SdfNode::SmoothSubtraction { a, b, k } => {
757                self.compile_node(a);
758                self.compile_node(b);
759                self.instructions.push(Instruction::smooth_subtraction(*k));
760            }
761
762            SdfNode::ChamferUnion { a, b, r } => {
763                self.compile_node(a);
764                self.compile_node(b);
765                self.instructions.push(Instruction::chamfer_union(*r));
766            }
767
768            SdfNode::ChamferIntersection { a, b, r } => {
769                self.compile_node(a);
770                self.compile_node(b);
771                self.instructions
772                    .push(Instruction::chamfer_intersection(*r));
773            }
774
775            SdfNode::ChamferSubtraction { a, b, r } => {
776                self.compile_node(a);
777                self.compile_node(b);
778                self.instructions.push(Instruction::chamfer_subtraction(*r));
779            }
780
781            SdfNode::StairsUnion { a, b, r, n } => {
782                self.compile_node(a);
783                self.compile_node(b);
784                self.instructions.push(Instruction::stairs_union(*r, *n));
785            }
786
787            SdfNode::StairsIntersection { a, b, r, n } => {
788                self.compile_node(a);
789                self.compile_node(b);
790                self.instructions
791                    .push(Instruction::stairs_intersection(*r, *n));
792            }
793
794            SdfNode::StairsSubtraction { a, b, r, n } => {
795                self.compile_node(a);
796                self.compile_node(b);
797                self.instructions
798                    .push(Instruction::stairs_subtraction(*r, *n));
799            }
800
801            SdfNode::XOR { a, b } => {
802                self.compile_node(a);
803                self.compile_node(b);
804                self.instructions.push(Instruction::xor());
805            }
806
807            SdfNode::Morph { a, b, t } => {
808                self.compile_node(a);
809                self.compile_node(b);
810                self.instructions.push(Instruction::morph(*t));
811            }
812
813            SdfNode::ColumnsUnion { a, b, r, n } => {
814                self.compile_node(a);
815                self.compile_node(b);
816                self.instructions.push(Instruction::columns_union(*r, *n));
817            }
818
819            SdfNode::ColumnsIntersection { a, b, r, n } => {
820                self.compile_node(a);
821                self.compile_node(b);
822                self.instructions
823                    .push(Instruction::columns_intersection(*r, *n));
824            }
825
826            SdfNode::ColumnsSubtraction { a, b, r, n } => {
827                self.compile_node(a);
828                self.compile_node(b);
829                self.instructions
830                    .push(Instruction::columns_subtraction(*r, *n));
831            }
832
833            SdfNode::Pipe { a, b, r } => {
834                self.compile_node(a);
835                self.compile_node(b);
836                self.instructions.push(Instruction::pipe(*r));
837            }
838
839            SdfNode::Engrave { a, b, r } => {
840                self.compile_node(a);
841                self.compile_node(b);
842                self.instructions.push(Instruction::engrave(*r));
843            }
844
845            SdfNode::Groove { a, b, ra, rb } => {
846                self.compile_node(a);
847                self.compile_node(b);
848                self.instructions.push(Instruction::groove(*ra, *rb));
849            }
850
851            SdfNode::Tongue { a, b, ra, rb } => {
852                self.compile_node(a);
853                self.compile_node(b);
854                self.instructions.push(Instruction::tongue(*ra, *rb));
855            }
856
857            // === Transforms ===
858            // For transforms, we: emit transform, compile child, emit pop
859            SdfNode::Translate { child, offset } => {
860                let inst_idx = self.instructions.len();
861                self.instructions
862                    .push(Instruction::translate(offset.x, offset.y, offset.z));
863                self.compile_node(child);
864                self.instructions.push(Instruction::pop_transform());
865                // Update skip_offset to point past the pop
866                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
867            }
868
869            SdfNode::Rotate { child, rotation } => {
870                let inst_idx = self.instructions.len();
871                self.instructions.push(Instruction::rotate(
872                    rotation.x, rotation.y, rotation.z, rotation.w,
873                ));
874                self.compile_node(child);
875                self.instructions.push(Instruction::pop_transform());
876                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
877            }
878
879            SdfNode::Scale { child, factor } => {
880                let inst_idx = self.instructions.len();
881                self.instructions.push(Instruction::scale(*factor));
882                self.compile_node(child);
883                self.instructions.push(Instruction::pop_transform());
884                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
885            }
886
887            SdfNode::ScaleNonUniform { child, factors } => {
888                let inst_idx = self.instructions.len();
889                self.instructions.push(Instruction::scale_non_uniform(
890                    factors.x, factors.y, factors.z,
891                ));
892                self.compile_node(child);
893                self.instructions.push(Instruction::pop_transform());
894                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
895            }
896
897            // === Modifiers ===
898            SdfNode::Twist { child, strength } => {
899                let inst_idx = self.instructions.len();
900                self.instructions.push(Instruction::twist(*strength));
901                self.compile_node(child);
902                self.instructions.push(Instruction::pop_transform());
903                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
904            }
905
906            SdfNode::Bend { child, curvature } => {
907                let inst_idx = self.instructions.len();
908                self.instructions.push(Instruction::bend(*curvature));
909                self.compile_node(child);
910                self.instructions.push(Instruction::pop_transform());
911                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
912            }
913
914            SdfNode::RepeatInfinite { child, spacing } => {
915                let inst_idx = self.instructions.len();
916                self.instructions.push(Instruction::repeat_infinite(
917                    spacing.x, spacing.y, spacing.z,
918                ));
919                self.compile_node(child);
920                self.instructions.push(Instruction::pop_transform());
921                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
922            }
923
924            SdfNode::RepeatFinite {
925                child,
926                count,
927                spacing,
928            } => {
929                let inst_idx = self.instructions.len();
930                self.instructions.push(Instruction::repeat_finite(
931                    count[0] as f32,
932                    count[1] as f32,
933                    count[2] as f32,
934                    spacing.x,
935                    spacing.y,
936                    spacing.z,
937                ));
938                self.compile_node(child);
939                self.instructions.push(Instruction::pop_transform());
940                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
941            }
942
943            SdfNode::Noise {
944                child,
945                amplitude,
946                frequency,
947                seed,
948            } => {
949                // Noise is a post-processing modifier that adds noise to the distance
950                let inst_idx = self.instructions.len();
951                self.instructions
952                    .push(Instruction::noise(*amplitude, *frequency, *seed));
953                self.compile_node(child);
954                self.instructions.push(Instruction::pop_transform());
955                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
956            }
957
958            SdfNode::Round { child, radius } => {
959                let inst_idx = self.instructions.len();
960                self.instructions.push(Instruction::round(*radius));
961                self.compile_node(child);
962                self.instructions.push(Instruction::pop_transform());
963                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
964            }
965
966            SdfNode::Onion { child, thickness } => {
967                let inst_idx = self.instructions.len();
968                self.instructions.push(Instruction::onion(*thickness));
969                self.compile_node(child);
970                self.instructions.push(Instruction::pop_transform());
971                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
972            }
973
974            SdfNode::Elongate { child, amount } => {
975                let inst_idx = self.instructions.len();
976                self.instructions
977                    .push(Instruction::elongate(amount.x, amount.y, amount.z));
978                self.compile_node(child);
979                self.instructions.push(Instruction::pop_transform());
980                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
981            }
982
983            SdfNode::Mirror { child, axes } => {
984                let inst_idx = self.instructions.len();
985                self.instructions
986                    .push(Instruction::mirror(axes.x, axes.y, axes.z));
987                self.compile_node(child);
988                self.instructions.push(Instruction::pop_transform());
989                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
990            }
991
992            SdfNode::OctantMirror { child } => {
993                let inst_idx = self.instructions.len();
994                self.instructions.push(Instruction::octant_mirror());
995                self.compile_node(child);
996                self.instructions.push(Instruction::pop_transform());
997                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
998            }
999
1000            SdfNode::Revolution { child, offset } => {
1001                let inst_idx = self.instructions.len();
1002                self.instructions.push(Instruction::revolution(*offset));
1003                self.compile_node(child);
1004                self.instructions.push(Instruction::pop_transform());
1005                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1006            }
1007
1008            SdfNode::Extrude { child, half_height } => {
1009                let inst_idx = self.instructions.len();
1010                self.instructions.push(Instruction::extrude(*half_height));
1011                self.compile_node(child);
1012                self.instructions.push(Instruction::pop_transform());
1013                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1014            }
1015
1016            SdfNode::SweepBezier { child, p0, p1, p2 } => {
1017                let inst_idx = self.instructions.len();
1018                self.instructions.push(Instruction::sweep_bezier(
1019                    p0.x, p0.y, p1.x, p1.y, p2.x, p2.y,
1020                ));
1021                self.compile_node(child);
1022                self.instructions.push(Instruction::pop_transform());
1023                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1024            }
1025
1026            SdfNode::Taper {
1027                child,
1028                factor,
1029                reach,
1030            } => {
1031                let inst_idx = self.instructions.len();
1032                self.instructions.push(Instruction::taper(*factor, *reach));
1033                self.compile_node(child);
1034                self.instructions.push(Instruction::pop_transform());
1035                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1036            }
1037
1038            SdfNode::Displacement { child, strength } => {
1039                let inst_idx = self.instructions.len();
1040                self.instructions.push(Instruction::displacement(*strength));
1041                self.compile_node(child);
1042                self.instructions.push(Instruction::pop_transform());
1043                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1044            }
1045
1046            // Phase 27: SineDisplacement (= bytecode 経路は未実装、 raymarch は eval/mod.rs 直接経路使用)
1047            // 注: 現状 Displacement opcode (freq=5 固定) で fallback、 frequency 引数は無視される
1048            // raymarch_batch_parallel は src/eval/mod.rs::eval を使うため bytecode 経路は不要
1049            SdfNode::SineDisplacement {
1050                child,
1051                amplitude,
1052                frequency,
1053            } => {
1054                let inst_idx = self.instructions.len();
1055                self.instructions.push(Instruction::sine_displacement(
1056                    *amplitude,
1057                    frequency.x,
1058                    frequency.y,
1059                    frequency.z,
1060                ));
1061                self.compile_node(child);
1062                self.instructions.push(Instruction::pop_transform());
1063                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1064            }
1065
1066            SdfNode::PolarRepeat { child, count } => {
1067                let inst_idx = self.instructions.len();
1068                self.instructions
1069                    .push(Instruction::polar_repeat(*count as f32));
1070                self.compile_node(child);
1071                self.instructions.push(Instruction::pop_transform());
1072                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1073            }
1074
1075            // WithMaterial is transparent for distance evaluation
1076            SdfNode::WithMaterial { child, .. } => {
1077                self.compile_node(child);
1078            }
1079
1080            // === 2D Primitives ===
1081            SdfNode::Circle2D {
1082                radius,
1083                half_height,
1084            } => {
1085                self.instructions
1086                    .push(Instruction::circle_2d(*radius, *half_height));
1087            }
1088
1089            SdfNode::Rect2D {
1090                half_extents,
1091                half_height,
1092            } => {
1093                self.instructions
1094                    .push(Instruction::rect_2d(*half_extents, *half_height));
1095            }
1096
1097            SdfNode::Segment2D {
1098                a,
1099                b,
1100                thickness,
1101                half_height,
1102            } => {
1103                self.instructions
1104                    .push(Instruction::segment_2d(*a, *b, *thickness, *half_height));
1105            }
1106
1107            SdfNode::Polygon2D {
1108                vertices,
1109                half_height,
1110            } => {
1111                // Serialize vertices as flat [x0, y0, x1, y1, ...] into aux_data
1112                let flat: Vec<f32> = vertices.iter().flat_map(|v| [v.x, v.y]).collect();
1113                let (aux_off, aux_len) = self.push_aux(&flat);
1114                let mut inst = Instruction::polygon_2d(*half_height);
1115                inst.aux_offset = aux_off;
1116                inst.aux_len = aux_len;
1117                self.instructions.push(inst);
1118            }
1119
1120            SdfNode::RoundedRect2D {
1121                half_extents,
1122                round_radius,
1123                half_height,
1124            } => {
1125                self.instructions.push(Instruction::rounded_rect_2d(
1126                    *half_extents,
1127                    *round_radius,
1128                    *half_height,
1129                ));
1130            }
1131
1132            SdfNode::Annular2D {
1133                outer_radius,
1134                thickness,
1135                half_height,
1136            } => {
1137                self.instructions.push(Instruction::annular_2d(
1138                    *outer_radius,
1139                    *thickness,
1140                    *half_height,
1141                ));
1142            }
1143
1144            // Terrain has no bytecode law yet (FBM heightfield lives only in the tree
1145            // evaluator); rejected by `validate_for_compile` so this arm is unreachable.
1146            SdfNode::Terrain { .. } => {
1147                unreachable!("Terrain rejected by validate_for_compile()");
1148            }
1149
1150            // === ExpSmooth operations ===
1151            SdfNode::ExpSmoothUnion { a, b, k } => {
1152                self.compile_node(a);
1153                self.compile_node(b);
1154                self.instructions.push(Instruction::exp_smooth_union(*k));
1155            }
1156
1157            SdfNode::ExpSmoothIntersection { a, b, k } => {
1158                self.compile_node(a);
1159                self.compile_node(b);
1160                self.instructions
1161                    .push(Instruction::exp_smooth_intersection(*k));
1162            }
1163
1164            SdfNode::ExpSmoothSubtraction { a, b, k } => {
1165                self.compile_node(a);
1166                self.compile_node(b);
1167                self.instructions
1168                    .push(Instruction::exp_smooth_subtraction(*k));
1169            }
1170
1171            // === Shear modifier (point transform) ===
1172            SdfNode::Shear { child, shear } => {
1173                let inst_idx = self.instructions.len();
1174                self.instructions.push(Instruction::shear(*shear));
1175                self.compile_node(child);
1176                self.instructions.push(Instruction::pop_transform());
1177                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1178            }
1179
1180            // === Animated modifier (passthrough) ===
1181            SdfNode::Animated { child, .. } => {
1182                self.compile_node(child);
1183            }
1184
1185            // === New Transforms (7 variants) ===
1186            SdfNode::ProjectiveTransform {
1187                child,
1188                inv_matrix,
1189                lipschitz_bound,
1190            } => {
1191                let (aux_off, aux_len) = self.push_aux(inv_matrix);
1192                let inst_idx = self.instructions.len();
1193                let mut inst = Instruction::projective_transform(*lipschitz_bound);
1194                inst.aux_offset = aux_off;
1195                inst.aux_len = aux_len;
1196                self.instructions.push(inst);
1197                self.compile_node(child);
1198                self.instructions.push(Instruction::pop_transform());
1199                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1200            }
1201
1202            SdfNode::LatticeDeform {
1203                child,
1204                control_points,
1205                nx,
1206                ny,
1207                nz,
1208                bbox_min,
1209                bbox_max,
1210            } => {
1211                // Serialize: [nx, ny, nz, bbox_min.x/y/z, bbox_max.x/y/z, cp0.x, cp0.y, cp0.z, ...]
1212                let mut aux = Vec::with_capacity(9 + control_points.len() * 3);
1213                aux.push(*nx as f32);
1214                aux.push(*ny as f32);
1215                aux.push(*nz as f32);
1216                aux.push(bbox_min.x);
1217                aux.push(bbox_min.y);
1218                aux.push(bbox_min.z);
1219                aux.push(bbox_max.x);
1220                aux.push(bbox_max.y);
1221                aux.push(bbox_max.z);
1222                for cp in control_points {
1223                    aux.push(cp.x);
1224                    aux.push(cp.y);
1225                    aux.push(cp.z);
1226                }
1227                let (aux_off, aux_len) = self.push_aux(&aux);
1228                let inst_idx = self.instructions.len();
1229                let mut inst = Instruction::lattice_deform();
1230                inst.aux_offset = aux_off;
1231                inst.aux_len = aux_len;
1232                self.instructions.push(inst);
1233                self.compile_node(child);
1234                self.instructions.push(Instruction::pop_transform());
1235                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1236            }
1237
1238            SdfNode::SdfSkinning { child, bones } => {
1239                // Serialize: [bone_count, (inv_bind_pose[16], current_pose[16], weight) × N]
1240                let mut aux = Vec::with_capacity(1 + bones.len() * 33);
1241                aux.push(bones.len() as f32);
1242                for bone in bones {
1243                    aux.extend_from_slice(&bone.inv_bind_pose);
1244                    aux.extend_from_slice(&bone.current_pose);
1245                    aux.push(bone.weight);
1246                }
1247                let (aux_off, aux_len) = self.push_aux(&aux);
1248                let inst_idx = self.instructions.len();
1249                let mut inst = Instruction::sdf_skinning();
1250                inst.aux_offset = aux_off;
1251                inst.aux_len = aux_len;
1252                self.instructions.push(inst);
1253                self.compile_node(child);
1254                self.instructions.push(Instruction::pop_transform());
1255                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1256            }
1257
1258            // === New Modifiers (4 variants) ===
1259            SdfNode::IcosahedralSymmetry { child } => {
1260                let inst_idx = self.instructions.len();
1261                self.instructions.push(Instruction::icosahedral_symmetry());
1262                self.compile_node(child);
1263                self.instructions.push(Instruction::pop_transform());
1264                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1265            }
1266
1267            SdfNode::IFS {
1268                child,
1269                transforms,
1270                iterations,
1271            } => {
1272                // Serialize: [transform_count, mat4[0..16], mat4[16..32], ...]
1273                let mut aux = Vec::with_capacity(1 + transforms.len() * 16);
1274                aux.push(transforms.len() as f32);
1275                for t in transforms {
1276                    aux.extend_from_slice(t);
1277                }
1278                let (aux_off, aux_len) = self.push_aux(&aux);
1279                let inst_idx = self.instructions.len();
1280                let mut inst = Instruction::ifs(*iterations);
1281                inst.aux_offset = aux_off;
1282                inst.aux_len = aux_len;
1283                self.instructions.push(inst);
1284                self.compile_node(child);
1285                self.instructions.push(Instruction::pop_transform());
1286                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1287            }
1288
1289            SdfNode::HeightmapDisplacement {
1290                child,
1291                heightmap,
1292                width,
1293                height,
1294                amplitude,
1295                scale,
1296            } => {
1297                // Serialize: [width, height, heightmap_data...]
1298                let mut aux = Vec::with_capacity(2 + heightmap.len());
1299                aux.push(*width as f32);
1300                aux.push(*height as f32);
1301                aux.extend_from_slice(heightmap);
1302                let (aux_off, aux_len) = self.push_aux(&aux);
1303                let inst_idx = self.instructions.len();
1304                let mut inst = Instruction::heightmap_displacement(*amplitude, *scale);
1305                inst.aux_offset = aux_off;
1306                inst.aux_len = aux_len;
1307                self.instructions.push(inst);
1308                self.compile_node(child);
1309                self.instructions.push(Instruction::pop_transform());
1310                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1311            }
1312
1313            SdfNode::SurfaceRoughness {
1314                child,
1315                frequency,
1316                amplitude,
1317                octaves,
1318            } => {
1319                let inst_idx = self.instructions.len();
1320                self.instructions.push(Instruction::surface_roughness(
1321                    *frequency, *amplitude, *octaves,
1322                ));
1323                self.compile_node(child);
1324                self.instructions.push(Instruction::pop_transform());
1325                self.instructions[inst_idx].skip_offset = self.instructions.len() as u32;
1326            }
1327        }
1328    }
1329}
1330
1331/// Maximum stack depth for value stack (must match eval.rs / eval_simd.rs).
1332const MAX_VALUE_STACK: usize = 64;
1333/// Maximum stack depth for coordinate transform stack.
1334const MAX_COORD_STACK: usize = 32;
1335
1336/// Compute the maximum value stack depth and coordinate stack depth required
1337/// to evaluate the given SDF tree.
1338///
1339/// Returns `(value_depth, coord_depth)`.
1340pub fn compute_stack_depths(node: &SdfNode) -> (usize, usize) {
1341    match node {
1342        // Primitives push 1 value onto the value stack, no coord push
1343        SdfNode::Sphere { .. }
1344        | SdfNode::Box3d { .. }
1345        | SdfNode::Cylinder { .. }
1346        | SdfNode::Torus { .. }
1347        | SdfNode::Plane { .. }
1348        | SdfNode::Capsule { .. }
1349        | SdfNode::Cone { .. }
1350        | SdfNode::Ellipsoid { .. }
1351        | SdfNode::RoundedCone { .. }
1352        | SdfNode::Pyramid { .. }
1353        | SdfNode::Octahedron { .. }
1354        | SdfNode::HexPrism { .. }
1355        | SdfNode::Link { .. }
1356        | SdfNode::Triangle { .. }
1357        | SdfNode::Bezier { .. }
1358        | SdfNode::RoundedBox { .. }
1359        | SdfNode::CappedCone { .. }
1360        | SdfNode::CappedTorus { .. }
1361        | SdfNode::TriangularPrism { .. }
1362        | SdfNode::CutSphere { .. }
1363        | SdfNode::CutHollowSphere { .. }
1364        | SdfNode::DeathStar { .. }
1365        | SdfNode::SolidAngle { .. }
1366        | SdfNode::Rhombus { .. }
1367        | SdfNode::Horseshoe { .. }
1368        | SdfNode::Vesica { .. }
1369        | SdfNode::InfiniteCylinder { .. }
1370        | SdfNode::InfiniteCone { .. }
1371        | SdfNode::Gyroid { .. }
1372        | SdfNode::Heart { .. }
1373        | SdfNode::Tube { .. }
1374        | SdfNode::Barrel { .. }
1375        | SdfNode::Diamond { .. }
1376        | SdfNode::ChamferedCube { .. }
1377        | SdfNode::SchwarzP { .. }
1378        | SdfNode::Superellipsoid { .. }
1379        | SdfNode::RoundedX { .. }
1380        | SdfNode::Pie { .. }
1381        | SdfNode::Trapezoid { .. }
1382        | SdfNode::Parallelogram { .. }
1383        | SdfNode::Tunnel { .. }
1384        | SdfNode::UnevenCapsule { .. }
1385        | SdfNode::Egg { .. }
1386        | SdfNode::ArcShape { .. }
1387        | SdfNode::Moon { .. }
1388        | SdfNode::CrossShape { .. }
1389        | SdfNode::BlobbyCross { .. }
1390        | SdfNode::ParabolaSegment { .. }
1391        | SdfNode::RegularPolygon { .. }
1392        | SdfNode::StarPolygon { .. }
1393        | SdfNode::Stairs { .. }
1394        | SdfNode::Helix { .. }
1395        | SdfNode::Tetrahedron { .. }
1396        | SdfNode::Dodecahedron { .. }
1397        | SdfNode::Icosahedron { .. }
1398        | SdfNode::TruncatedOctahedron { .. }
1399        | SdfNode::TruncatedIcosahedron { .. }
1400        | SdfNode::BoxFrame { .. }
1401        | SdfNode::DiamondSurface { .. }
1402        | SdfNode::Neovius { .. }
1403        | SdfNode::Lidinoid { .. }
1404        | SdfNode::IWP { .. }
1405        | SdfNode::FRD { .. }
1406        | SdfNode::FischerKochS { .. }
1407        | SdfNode::PMY { .. }
1408        | SdfNode::Circle2D { .. }
1409        | SdfNode::Rect2D { .. }
1410        | SdfNode::Segment2D { .. }
1411        | SdfNode::Polygon2D { .. }
1412        | SdfNode::RoundedRect2D { .. }
1413        | SdfNode::Annular2D { .. }
1414        | SdfNode::Terrain { .. } => (1, 0),
1415
1416        // Binary operations: compile left, then right (both on stack), then consume 2 → produce 1.
1417        // Peak value depth = max(left_depth, left_result + right_depth)
1418        // where left_result = 1 (left subtree leaves one value on stack).
1419        SdfNode::Union { a, b }
1420        | SdfNode::Intersection { a, b }
1421        | SdfNode::Subtraction { a, b }
1422        | SdfNode::SmoothUnion { a, b, .. }
1423        | SdfNode::SmoothIntersection { a, b, .. }
1424        | SdfNode::SmoothSubtraction { a, b, .. }
1425        | SdfNode::ChamferUnion { a, b, .. }
1426        | SdfNode::ChamferIntersection { a, b, .. }
1427        | SdfNode::ChamferSubtraction { a, b, .. }
1428        | SdfNode::StairsUnion { a, b, .. }
1429        | SdfNode::StairsIntersection { a, b, .. }
1430        | SdfNode::StairsSubtraction { a, b, .. }
1431        | SdfNode::XOR { a, b }
1432        | SdfNode::Morph { a, b, .. }
1433        | SdfNode::ColumnsUnion { a, b, .. }
1434        | SdfNode::ColumnsIntersection { a, b, .. }
1435        | SdfNode::ColumnsSubtraction { a, b, .. }
1436        | SdfNode::Pipe { a, b, .. }
1437        | SdfNode::Engrave { a, b, .. }
1438        | SdfNode::Groove { a, b, .. }
1439        | SdfNode::Tongue { a, b, .. }
1440        | SdfNode::ExpSmoothUnion { a, b, .. }
1441        | SdfNode::ExpSmoothIntersection { a, b, .. }
1442        | SdfNode::ExpSmoothSubtraction { a, b, .. } => {
1443            let (va, ca) = compute_stack_depths(a);
1444            let (vb, cb) = compute_stack_depths(b);
1445            // Left is evaluated first, leaving 1 value. Then right is evaluated on top.
1446            let value_depth = va.max(1 + vb);
1447            let coord_depth = ca.max(cb);
1448            (value_depth, coord_depth)
1449        }
1450
1451        // Transforms: push 1 coord frame, evaluate child, pop coord frame.
1452        // Value depth = child's value depth.
1453        // Coord depth = 1 + child's coord depth.
1454        SdfNode::Translate { child, .. }
1455        | SdfNode::Rotate { child, .. }
1456        | SdfNode::Scale { child, .. }
1457        | SdfNode::ScaleNonUniform { child, .. }
1458        | SdfNode::Twist { child, .. }
1459        | SdfNode::Bend { child, .. }
1460        | SdfNode::RepeatInfinite { child, .. }
1461        | SdfNode::RepeatFinite { child, .. }
1462        | SdfNode::Noise { child, .. }
1463        | SdfNode::Elongate { child, .. }
1464        | SdfNode::Mirror { child, .. }
1465        | SdfNode::OctantMirror { child }
1466        | SdfNode::Revolution { child, .. }
1467        | SdfNode::Extrude { child, .. }
1468        | SdfNode::SweepBezier { child, .. }
1469        | SdfNode::Taper { child, .. }
1470        | SdfNode::Displacement { child, .. }
1471        | SdfNode::SineDisplacement { child, .. }
1472        | SdfNode::PolarRepeat { child, .. }
1473        | SdfNode::Animated { child, .. }
1474        | SdfNode::Shear { child, .. }
1475        | SdfNode::ProjectiveTransform { child, .. }
1476        | SdfNode::LatticeDeform { child, .. }
1477        | SdfNode::SdfSkinning { child, .. }
1478        | SdfNode::IcosahedralSymmetry { child }
1479        | SdfNode::IFS { child, .. }
1480        | SdfNode::HeightmapDisplacement { child, .. }
1481        | SdfNode::SurfaceRoughness { child, .. } => {
1482            let (vc, cc) = compute_stack_depths(child);
1483            (vc, 1 + cc)
1484        }
1485
1486        // Modifiers that don't push a coord frame (value-only modifiers)
1487        SdfNode::Round { child, .. }
1488        | SdfNode::Onion { child, .. }
1489        | SdfNode::WithMaterial { child, .. } => compute_stack_depths(child),
1490
1491        // Catch-all for future variants
1492        #[allow(unreachable_patterns)]
1493        _ => (1, 0),
1494    }
1495}
1496
1497/// Recursively validate that all nodes in the tree are supported by the bytecode compiler.
1498fn validate_for_compile(node: &SdfNode) -> Result<(), CompileError> {
1499    match node {
1500        // Metric nodes: four and five params respectively, both within the
1501        // instruction's seven slots, and both have a VM law.
1502        SdfNode::MetricBall { .. } => {}
1503        SdfNode::MetricBlend { inner, outer, .. } => {
1504            validate_for_compile(inner)?;
1505            validate_for_compile(outer)?;
1506        }
1507        // Unsupported primitives (exceed params[6] limit)
1508        SdfNode::Triangle { .. } => {
1509            return Err(CompileError::UnsupportedPrimitive("Triangle".into()));
1510        }
1511        SdfNode::Bezier { .. } => {
1512            return Err(CompileError::UnsupportedPrimitive("Bezier".into()));
1513        }
1514        // Unsupported: no bytecode law (previously compiled to a silent sphere)
1515        SdfNode::Terrain { .. } => {
1516            return Err(CompileError::UnsupportedPrimitive("Terrain".into()));
1517        }
1518        // Binary operations — validate both children
1519        SdfNode::Union { a, b }
1520        | SdfNode::Intersection { a, b }
1521        | SdfNode::Subtraction { a, b }
1522        | SdfNode::SmoothUnion { a, b, .. }
1523        | SdfNode::SmoothIntersection { a, b, .. }
1524        | SdfNode::SmoothSubtraction { a, b, .. }
1525        | SdfNode::ChamferUnion { a, b, .. }
1526        | SdfNode::ChamferIntersection { a, b, .. }
1527        | SdfNode::ChamferSubtraction { a, b, .. }
1528        | SdfNode::StairsUnion { a, b, .. }
1529        | SdfNode::StairsIntersection { a, b, .. }
1530        | SdfNode::StairsSubtraction { a, b, .. }
1531        | SdfNode::XOR { a, b }
1532        | SdfNode::Morph { a, b, .. }
1533        | SdfNode::ColumnsUnion { a, b, .. }
1534        | SdfNode::ColumnsIntersection { a, b, .. }
1535        | SdfNode::ColumnsSubtraction { a, b, .. }
1536        | SdfNode::Pipe { a, b, .. }
1537        | SdfNode::Engrave { a, b, .. }
1538        | SdfNode::Groove { a, b, .. }
1539        | SdfNode::Tongue { a, b, .. }
1540        | SdfNode::ExpSmoothUnion { a, b, .. }
1541        | SdfNode::ExpSmoothIntersection { a, b, .. }
1542        | SdfNode::ExpSmoothSubtraction { a, b, .. } => {
1543            validate_for_compile(a)?;
1544            validate_for_compile(b)?;
1545        }
1546        // Transforms and modifiers — validate child
1547        SdfNode::Translate { child, .. }
1548        | SdfNode::Rotate { child, .. }
1549        | SdfNode::Scale { child, .. }
1550        | SdfNode::ScaleNonUniform { child, .. }
1551        | SdfNode::Twist { child, .. }
1552        | SdfNode::Bend { child, .. }
1553        | SdfNode::RepeatInfinite { child, .. }
1554        | SdfNode::RepeatFinite { child, .. }
1555        | SdfNode::Noise { child, .. }
1556        | SdfNode::Round { child, .. }
1557        | SdfNode::Onion { child, .. }
1558        | SdfNode::Elongate { child, .. }
1559        | SdfNode::Mirror { child, .. }
1560        | SdfNode::OctantMirror { child }
1561        | SdfNode::Revolution { child, .. }
1562        | SdfNode::Extrude { child, .. }
1563        | SdfNode::SweepBezier { child, .. }
1564        | SdfNode::Taper { child, .. }
1565        | SdfNode::Displacement { child, .. }
1566        | SdfNode::SineDisplacement { child, .. }
1567        | SdfNode::PolarRepeat { child, .. }
1568        | SdfNode::WithMaterial { child, .. }
1569        | SdfNode::Animated { child, .. }
1570        | SdfNode::Shear { child, .. }
1571        | SdfNode::ProjectiveTransform { child, .. }
1572        | SdfNode::LatticeDeform { child, .. }
1573        | SdfNode::SdfSkinning { child, .. }
1574        | SdfNode::IcosahedralSymmetry { child }
1575        | SdfNode::IFS { child, .. }
1576        | SdfNode::HeightmapDisplacement { child, .. }
1577        | SdfNode::SurfaceRoughness { child, .. } => {
1578            validate_for_compile(child)?;
1579        }
1580        // All supported primitives (leaf nodes, no children to validate)
1581        SdfNode::Sphere { .. }
1582        | SdfNode::Box3d { .. }
1583        | SdfNode::Cylinder { .. }
1584        | SdfNode::Torus { .. }
1585        | SdfNode::Plane { .. }
1586        | SdfNode::Capsule { .. }
1587        | SdfNode::Cone { .. }
1588        | SdfNode::Ellipsoid { .. }
1589        | SdfNode::RoundedCone { .. }
1590        | SdfNode::Pyramid { .. }
1591        | SdfNode::Octahedron { .. }
1592        | SdfNode::HexPrism { .. }
1593        | SdfNode::Link { .. }
1594        | SdfNode::RoundedBox { .. }
1595        | SdfNode::CappedCone { .. }
1596        | SdfNode::CappedTorus { .. }
1597        | SdfNode::RoundedCylinder { .. }
1598        | SdfNode::TriangularPrism { .. }
1599        | SdfNode::CutSphere { .. }
1600        | SdfNode::CutHollowSphere { .. }
1601        | SdfNode::DeathStar { .. }
1602        | SdfNode::SolidAngle { .. }
1603        | SdfNode::Rhombus { .. }
1604        | SdfNode::Horseshoe { .. }
1605        | SdfNode::Vesica { .. }
1606        | SdfNode::InfiniteCylinder { .. }
1607        | SdfNode::InfiniteCone { .. }
1608        | SdfNode::Gyroid { .. }
1609        | SdfNode::Heart { .. }
1610        | SdfNode::Tube { .. }
1611        | SdfNode::Barrel { .. }
1612        | SdfNode::Diamond { .. }
1613        | SdfNode::ChamferedCube { .. }
1614        | SdfNode::SchwarzP { .. }
1615        | SdfNode::Superellipsoid { .. }
1616        | SdfNode::RoundedX { .. }
1617        | SdfNode::Pie { .. }
1618        | SdfNode::Trapezoid { .. }
1619        | SdfNode::Parallelogram { .. }
1620        | SdfNode::Tunnel { .. }
1621        | SdfNode::UnevenCapsule { .. }
1622        | SdfNode::Egg { .. }
1623        | SdfNode::ArcShape { .. }
1624        | SdfNode::Moon { .. }
1625        | SdfNode::CrossShape { .. }
1626        | SdfNode::BlobbyCross { .. }
1627        | SdfNode::ParabolaSegment { .. }
1628        | SdfNode::RegularPolygon { .. }
1629        | SdfNode::StarPolygon { .. }
1630        | SdfNode::Stairs { .. }
1631        | SdfNode::Helix { .. }
1632        | SdfNode::Tetrahedron { .. }
1633        | SdfNode::Dodecahedron { .. }
1634        | SdfNode::Icosahedron { .. }
1635        | SdfNode::TruncatedOctahedron { .. }
1636        | SdfNode::TruncatedIcosahedron { .. }
1637        | SdfNode::BoxFrame { .. }
1638        | SdfNode::DiamondSurface { .. }
1639        | SdfNode::Neovius { .. }
1640        | SdfNode::Lidinoid { .. }
1641        | SdfNode::IWP { .. }
1642        | SdfNode::FRD { .. }
1643        | SdfNode::FischerKochS { .. }
1644        | SdfNode::PMY { .. }
1645        | SdfNode::Circle2D { .. }
1646        | SdfNode::Rect2D { .. }
1647        | SdfNode::Segment2D { .. }
1648        | SdfNode::Polygon2D { .. }
1649        | SdfNode::RoundedRect2D { .. }
1650        | SdfNode::Annular2D { .. } => {}
1651    }
1652    Ok(())
1653}
1654
1655#[cfg(test)]
1656mod tests {
1657    use super::super::opcode::OpCode;
1658    use super::*;
1659
1660    #[test]
1661    fn test_compile_sphere() {
1662        let node = SdfNode::sphere(1.0);
1663        let compiled = CompiledSdf::compile(&node);
1664
1665        assert_eq!(compiled.node_count, 1);
1666        assert_eq!(compiled.instruction_count(), 2); // sphere + end
1667        assert_eq!(compiled.instructions[0].opcode, OpCode::Sphere);
1668        assert_eq!(compiled.instructions[0].params[0], 1.0);
1669        assert_eq!(compiled.instructions[1].opcode, OpCode::End);
1670    }
1671
1672    #[test]
1673    fn test_compile_union() {
1674        let a = SdfNode::sphere(1.0);
1675        let b = SdfNode::sphere(2.0);
1676        let node = a.union(b);
1677        let compiled = CompiledSdf::compile(&node);
1678
1679        assert_eq!(compiled.node_count, 3);
1680        // sphere(1.0), sphere(2.0), union, end
1681        assert_eq!(compiled.instruction_count(), 4);
1682        assert_eq!(compiled.instructions[0].opcode, OpCode::Sphere);
1683        assert_eq!(compiled.instructions[1].opcode, OpCode::Sphere);
1684        assert_eq!(compiled.instructions[2].opcode, OpCode::Union);
1685        assert_eq!(compiled.instructions[3].opcode, OpCode::End);
1686    }
1687
1688    #[test]
1689    fn test_compile_translate() {
1690        let node = SdfNode::sphere(1.0).translate(1.0, 2.0, 3.0);
1691        let compiled = CompiledSdf::compile(&node);
1692
1693        // translate, sphere, pop_transform, end
1694        assert_eq!(compiled.instruction_count(), 4);
1695        assert_eq!(compiled.instructions[0].opcode, OpCode::Translate);
1696        assert_eq!(compiled.instructions[1].opcode, OpCode::Sphere);
1697        assert_eq!(compiled.instructions[2].opcode, OpCode::PopTransform);
1698        assert_eq!(compiled.instructions[3].opcode, OpCode::End);
1699    }
1700
1701    #[test]
1702    fn test_compile_complex() {
1703        // sphere.subtract(box).translate
1704        let node = SdfNode::sphere(1.0)
1705            .subtract(SdfNode::box3d(0.5, 0.5, 0.5))
1706            .translate(1.0, 0.0, 0.0);
1707        let compiled = CompiledSdf::compile(&node);
1708
1709        assert_eq!(compiled.node_count, 4);
1710        // translate, sphere, box, subtract, pop_transform, end
1711        assert_eq!(compiled.instruction_count(), 6);
1712    }
1713
1714    #[test]
1715    fn test_memory_size() {
1716        let node = SdfNode::sphere(1.0);
1717        let compiled = CompiledSdf::compile(&node);
1718
1719        // 2 instructions * 64 bytes = 128 bytes
1720        assert_eq!(compiled.memory_size(), 128);
1721    }
1722
1723    /// Helper: compare compiled eval vs tree-walker eval at several points
1724    fn assert_roundtrip(node: &crate::types::SdfNode, tolerance: f32) {
1725        use crate::compiled::eval::eval_compiled;
1726        use crate::eval::eval;
1727        use glam::Vec3;
1728
1729        let compiled = CompiledSdf::compile(node);
1730        let test_points = [
1731            Vec3::new(0.5, 0.3, 0.1),
1732            Vec3::new(-0.2, 0.8, -0.4),
1733            Vec3::new(1.0, 0.0, 0.0),
1734            Vec3::new(0.0, 0.0, 0.0),
1735        ];
1736        for pt in &test_points {
1737            let d_tree = eval(node, *pt);
1738            let d_compiled = eval_compiled(&compiled, *pt);
1739            assert!(
1740                (d_tree - d_compiled).abs() < tolerance,
1741                "Mismatch at {pt}: tree={d_tree}, compiled={d_compiled}"
1742            );
1743        }
1744    }
1745
1746    #[test]
1747    fn test_roundtrip_icosahedral_symmetry() {
1748        let node = crate::types::SdfNode::sphere(1.0).icosahedral_symmetry();
1749        assert_roundtrip(&node, 1e-5);
1750    }
1751
1752    #[test]
1753    fn test_roundtrip_surface_roughness() {
1754        let node = crate::types::SdfNode::sphere(1.0).surface_roughness(2.0, 0.05, 3);
1755        assert_roundtrip(&node, 1e-5);
1756    }
1757
1758    #[test]
1759    fn test_roundtrip_heightmap_displacement() {
1760        // Simple 4x4 flat heightmap (all zeros = no displacement)
1761        let heightmap = vec![0.0f32; 16];
1762        let node =
1763            crate::types::SdfNode::sphere(1.0).heightmap_displacement(heightmap, 4, 4, 0.1, 1.0);
1764        assert_roundtrip(&node, 1e-5);
1765    }
1766
1767    #[test]
1768    fn test_roundtrip_projective_transform() {
1769        // Identity inverse matrix
1770        #[rustfmt::skip]
1771        let identity = [
1772            1.0, 0.0, 0.0, 0.0,
1773            0.0, 1.0, 0.0, 0.0,
1774            0.0, 0.0, 1.0, 0.0,
1775            0.0, 0.0, 0.0, 1.0,
1776        ];
1777        let node = crate::types::SdfNode::sphere(1.0).projective_transform(identity, 1.0);
1778        assert_roundtrip(&node, 1e-5);
1779    }
1780
1781    #[test]
1782    fn test_roundtrip_ifs() {
1783        // Single identity transform, 1 iteration (should be close to original)
1784        #[rustfmt::skip]
1785        let identity = [
1786            1.0, 0.0, 0.0, 0.0,
1787            0.0, 1.0, 0.0, 0.0,
1788            0.0, 0.0, 1.0, 0.0,
1789            0.0, 0.0, 0.0, 1.0,
1790        ];
1791        let node = crate::types::SdfNode::sphere(1.0).ifs(vec![identity], 1);
1792        assert_roundtrip(&node, 1e-5);
1793    }
1794
1795    #[test]
1796    fn test_compile_noise() {
1797        let node = SdfNode::sphere(1.0).noise(0.1, 2.0, 42);
1798        let compiled = CompiledSdf::compile(&node);
1799
1800        // noise, sphere, pop_transform, end
1801        assert_eq!(compiled.instruction_count(), 4);
1802        assert_eq!(compiled.instructions[0].opcode, OpCode::Noise);
1803        assert_eq!(compiled.instructions[0].params[0], 0.1); // amplitude
1804        assert_eq!(compiled.instructions[0].params[1], 2.0); // frequency
1805        assert_eq!(compiled.instructions[0].params[2], 42.0); // seed
1806        assert_eq!(compiled.instructions[1].opcode, OpCode::Sphere);
1807        assert_eq!(compiled.instructions[2].opcode, OpCode::PopTransform);
1808        assert_eq!(compiled.instructions[3].opcode, OpCode::End);
1809    }
1810}