1use super::preset_slot::{PresetSlot, run_block_with_logger};
12use onedrop_eval::ShapeInstance as EvalShapeInstance;
13use onedrop_parser::preset::ShapeCode;
14use onedrop_renderer::{CustomShapeBatch, CustomShapeInstance, ShapeFlags};
15
16pub(super) fn compute_custom_shapes(
17 slot: &mut PresetSlot,
18) -> (Vec<CustomShapeInstance>, Vec<CustomShapeBatch>) {
19 let Some(preset) = slot.preset.as_ref() else {
20 return (Vec::new(), Vec::new());
21 };
22 if preset.shapes.is_empty()
23 || preset.shapes.iter().all(|s| !s.enabled)
24 || slot.compiled_shapes.is_empty()
25 {
26 return (Vec::new(), Vec::new());
27 }
28
29 let mut instances: Vec<CustomShapeInstance> = Vec::with_capacity(4 * 32);
30 let mut batches: Vec<CustomShapeBatch> = Vec::with_capacity(4);
31
32 let PresetSlot {
38 compiled_shapes,
39 shapes_need_init,
40 evaluator,
41 ..
42 } = slot;
43 let shapes: &[ShapeCode] = &preset.shapes;
44
45 for (i, shape) in shapes.iter().enumerate() {
46 if !shape.enabled || i >= compiled_shapes.len() {
47 continue;
48 }
49 let cs = &compiled_shapes[i];
50
51 if shapes_need_init.get(i).copied().unwrap_or(false) {
52 run_block_with_logger(evaluator, &cs.init, |idx, e| {
53 log::warn!("shape[{}] init equation[{}] failed: {}", i, idx, e);
54 });
55 shapes_need_init[i] = false;
56 }
57
58 let num_inst = shape.num_inst.clamp(1, 1024) as u32;
59 let sides = shape.sides.clamp(3, 64) as u32;
60 let start = instances.len() as u32;
61
62 for inst_idx in 0..num_inst {
63 let seed = EvalShapeInstance {
64 instance: inst_idx as f64,
65 num_inst: num_inst as f64,
66 sides: shape.sides as f64,
67 x: shape.x as f64,
68 y: shape.y as f64,
69 rad: shape.rad as f64,
70 ang: shape.ang as f64,
71 tex_zoom: shape.tex_zoom as f64,
72 tex_ang: shape.tex_ang as f64,
73 r: shape.r as f64,
74 g: shape.g as f64,
75 b: shape.b as f64,
76 a: shape.a as f64,
77 r2: shape.r2 as f64,
78 g2: shape.g2 as f64,
79 b2: shape.b2 as f64,
80 a2: shape.a2 as f64,
81 border_r: shape.border_r as f64,
82 border_g: shape.border_g as f64,
83 border_b: shape.border_b as f64,
84 border_a: shape.border_a as f64,
85 thick: if shape.thick_outline { 1.0 } else { 0.0 },
86 additive: if shape.additive { 1.0 } else { 0.0 },
87 };
88 let out = if cs.per_frame.is_empty() {
89 seed
90 } else {
91 match evaluator.run_per_shape_instance(seed, &cs.per_frame) {
92 Ok(o) => o,
93 Err(e) => {
94 log::warn!("shape[{}] per_frame[{}] failed: {}", i, inst_idx, e);
95 seed
96 }
97 }
98 };
99
100 let textured = shape.textured;
101 let mut flags = ShapeFlags::empty();
102 if textured {
103 flags |= ShapeFlags::TEXTURED;
104 }
105 if out.additive > 0.5 {
106 flags |= ShapeFlags::ADDITIVE;
107 }
108 if out.thick > 0.5 {
109 flags |= ShapeFlags::THICK_OUTLINE;
110 }
111
112 let color_center = [
113 out.r.clamp(0.0, 1.0) as f32,
114 out.g.clamp(0.0, 1.0) as f32,
115 out.b.clamp(0.0, 1.0) as f32,
116 out.a.clamp(0.0, 1.0) as f32,
117 ];
118 let color_edge = [
119 out.r2.clamp(0.0, 1.0) as f32,
120 out.g2.clamp(0.0, 1.0) as f32,
121 out.b2.clamp(0.0, 1.0) as f32,
122 out.a2.clamp(0.0, 1.0) as f32,
123 ];
124 let border_color = [
125 out.border_r.clamp(0.0, 1.0) as f32,
126 out.border_g.clamp(0.0, 1.0) as f32,
127 out.border_b.clamp(0.0, 1.0) as f32,
128 out.border_a.clamp(0.0, 1.0) as f32,
129 ];
130
131 instances.push(onedrop_renderer::custom_shape::instance_from_md2_state(
132 out.x,
133 out.y,
134 out.rad,
135 out.ang,
136 out.tex_zoom,
137 out.tex_ang,
138 sides,
139 color_center,
140 color_edge,
141 border_color,
142 0.0,
143 flags,
144 ));
145 }
146
147 let count = instances.len() as u32 - start;
148 if count > 0 {
149 batches.push(CustomShapeBatch {
150 start_instance: start,
151 instance_count: count,
152 sides,
153 additive: shape.additive,
154 thick_outline: shape.thick_outline,
155 });
156 }
157 }
158
159 (instances, batches)
160}