pub struct LightQuantizationComponent {
pub quant_steps: f32,
}Expand description
Per-renderable light quantization control for the toon shader.
This controls MaterialUBO.quant_steps (see assets/shaders/toon-mesh.frag).
Intended to be attached as a descendant of a RenderableComponent.
Fields§
§quant_steps: f32Implementations§
Source§impl LightQuantizationComponent
impl LightQuantizationComponent
Sourcepub const DEFAULT_STEPS: f32 = 3.0
pub const DEFAULT_STEPS: f32 = 3.0
Default toon quantization steps.
pub fn new() -> Self
Sourcepub fn steps(steps: f32) -> Self
pub fn steps(steps: f32) -> Self
Examples found in repository?
examples/folder-text.rs (line 382)
60fn main() {
61 mittens_engine::example_support::ensure_model_assets();
62 utils::logger::init();
63
64 // Usage:
65 // cargo run --example folder-text -- [folder] [spacing]
66 // Defaults:
67 // folder=src spacing=0.3
68 let mut args = std::env::args().skip(1);
69 let folder = args.next().unwrap_or_else(|| "src".to_string());
70 let spacing: f32 = args
71 .next()
72 .and_then(|s| s.parse::<f32>().ok())
73 .unwrap_or(1.0);
74
75 // Safety caps: this demo can explode the ECS if we load huge projects.
76 const MAX_FILES: usize = 42;
77 const MAX_CHARS_PER_FILE: usize = 10_000;
78 const WRAP_AT: usize = 90;
79 const TEXT_SCALE: f32 = 0.01;
80
81 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
82 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
83 let scan_root = {
84 let p = PathBuf::from(&folder);
85 if p.is_absolute() {
86 p
87 } else {
88 // Resolve relative paths from the crate root, not the process CWD.
89 manifest_dir.join(p)
90 }
91 };
92 eprintln!("[folder-text] cwd={:?}", cwd);
93 eprintln!("[folder-text] manifest_dir={:?}", manifest_dir);
94 eprintln!("[folder-text] scan_root={:?}", scan_root);
95
96 let mut files = Vec::new();
97 collect_rs_files(&scan_root, &mut files);
98 files.sort();
99
100 if files.is_empty() {
101 eprintln!(
102 "[folder-text] No .rs files found under folder={:?} (resolved={:?})",
103 folder, scan_root
104 );
105 }
106
107 let files: Vec<PathBuf> = files.into_iter().take(MAX_FILES).collect();
108 eprintln!(
109 "[folder-text] Loaded {} file(s) from {:?} (spacing={})",
110 files.len(),
111 folder,
112 spacing
113 );
114
115 #[derive(Debug, Clone)]
116 struct FileEntry {
117 path: PathBuf,
118 display_text: String,
119 max_cols: usize,
120 rows: usize,
121 }
122
123 // Group files by folder.
124 // Layout:
125 // - each folder becomes a column along +X
126 // - within a folder, files stack along +Y (8 high)
127 // - after 8, additional files start a new stack further along +Z
128 let mut files_by_folder: BTreeMap<PathBuf, Vec<FileEntry>> = BTreeMap::new();
129 for path in files {
130 let Ok(content) = std::fs::read_to_string(&path) else {
131 eprintln!("[folder-text] Failed to read {:?}", path);
132 continue;
133 };
134
135 let mut display_text = format!(
136 "// {}\n\n{}",
137 path.strip_prefix(&manifest_dir).unwrap_or(&path).display(),
138 content
139 );
140
141 if display_text.chars().count() > MAX_CHARS_PER_FILE {
142 display_text = display_text
143 .chars()
144 .take(MAX_CHARS_PER_FILE)
145 .collect::<String>();
146 display_text.push_str("\n\n// ... truncated ...\n");
147 }
148
149 let (max_cols, rows) = measure_text_bounds(&display_text, WRAP_AT);
150
151 let folder_key = path.parent().unwrap_or(&scan_root).to_path_buf();
152 files_by_folder
153 .entry(folder_key)
154 .or_default()
155 .push(FileEntry {
156 path,
157 display_text,
158 max_cols,
159 rows,
160 });
161 }
162
163 let column_count = files_by_folder.len().max(1);
164
165 let world = engine::ecs::World::default();
166 let mut universe = engine::Universe::new(world);
167
168 let bg_r = 0.25;
169 let bg_g = 0.25;
170 let bg_b = 0.25;
171 let background = universe
172 .world
173 .add_component(engine::ecs::component::BackgroundColorComponent::new());
174 let background_c = universe
175 .world
176 .add_component(engine::ecs::component::ColorComponent::rgba(
177 bg_r, bg_g, bg_b, 1.00,
178 ));
179 let _ = universe.world.add_child(background, background_c);
180 universe.add(background);
181
182 let ambient = universe
183 .world
184 .add_component(engine::ecs::component::AmbientLightComponent::rgb(
185 bg_r, bg_g, bg_b,
186 ));
187 universe.add(ambient);
188
189 // Input-driven camera rig.
190 // Topology: I { T { C3D } }
191 let input = universe
192 .world
193 .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
194
195 // Center the camera horizontally over the spawned columns.
196 let center_x = if column_count <= 1 {
197 0.0
198 } else {
199 ((column_count - 1) as f32) * spacing * 0.5
200 };
201
202 // Bring camera closer: these text blocks are tiny.
203 let rig_transform = universe.world.add_component(
204 engine::ecs::component::TransformComponent::new().with_position(center_x, 0.0, 1.2),
205 );
206 let camera3d = universe
207 .world
208 .add_component(engine::ecs::component::Camera3DComponent::new());
209 let input_mode = universe.world.add_component(
210 engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
211 );
212 let _ = universe.attach(input, input_mode);
213 let _ = universe.attach(input, rig_transform);
214 let _ = universe.attach(rig_transform, camera3d);
215
216 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
217 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
218 universe.add(input);
219
220 // Big floor plane under everything.
221 // RenderableComponent::square() is an XY quad facing +Z; rotate it to XZ facing +Y.
222 let max_stacks = files_by_folder
223 .values()
224 .map(|v| (v.len() + 7) / 8)
225 .max()
226 .unwrap_or(1)
227 .max(1);
228 let total_width = (column_count as f32) * spacing;
229 let total_depth = (max_stacks as f32) * spacing;
230 let floor_w = total_width.max(10.0);
231 let floor_h = total_depth.max(10.0);
232 let floor_transform = universe.world.add_component(
233 engine::ecs::component::TransformComponent::new()
234 .with_position(0.0, -2.0, 0.0)
235 .with_rotation_euler(-std::f32::consts::FRAC_PI_2, 0.0, 0.0)
236 .with_scale(floor_w, floor_h, 1.0),
237 );
238 let floor_renderable = universe
239 .world
240 .add_component(engine::ecs::component::RenderableComponent::square());
241 let floor_color = universe
242 .world
243 .add_component(engine::ecs::component::ColorComponent::rgba(
244 0.88, 0.88, 0.88, 1.0,
245 ));
246 let _ = universe.attach(floor_transform, floor_renderable);
247 let _ = universe.attach(floor_renderable, floor_color);
248 universe.add(floor_transform);
249
250 // 4 red cubes around the perimeter of the world (easy visual anchors).
251 fn spawn_red_cube(universe: &mut engine::Universe, x: f32, y: f32, z: f32, s: f32) {
252 let t = universe.world.add_component(
253 engine::ecs::component::TransformComponent::new()
254 .with_position(x, y, z)
255 .with_scale(s, s, s),
256 );
257 let r = universe
258 .world
259 .add_component(engine::ecs::component::RenderableComponent::cube());
260 let c = universe
261 .world
262 .add_component(engine::ecs::component::ColorComponent::rgba(
263 1.0, 0.0, 0.0, 1.0,
264 ));
265
266 let light_transform = universe.world.add_component(
267 engine::ecs::component::TransformComponent::new().with_position(0.0, s * 0.75, 0.0),
268 );
269 let light = universe.world.add_component(
270 engine::ecs::component::PointLightComponent::new()
271 .with_intensity(1.5)
272 .with_distance(20.0)
273 .with_color(1.0, 0.0, 0.0),
274 );
275
276 let _ = universe.attach(t, r);
277 let _ = universe.attach(r, c);
278 let _ = universe.attach(t, light_transform);
279 let _ = universe.attach(light_transform, light);
280
281 universe.add(t);
282 }
283
284 let p = 1.5;
285 let s = 0.25;
286 spawn_red_cube(&mut universe, -p, -p, 0.0, s);
287 spawn_red_cube(&mut universe, p, -p, 0.0, s);
288 spawn_red_cube(&mut universe, -p, p, 0.0, s);
289 spawn_red_cube(&mut universe, p, p, 0.0, s);
290
291 let start_x = -center_x;
292 let stack_depth = spacing;
293 let row_gap_world = 0.35;
294
295 // One global point light at the top of the overall text "tower".
296 let pad_y = 4.0;
297 let global_max_panel_h_world = files_by_folder
298 .values()
299 .flat_map(|v| v.iter())
300 .map(|e| ((e.rows as f32) + pad_y) * TEXT_SCALE)
301 .fold(0.0_f32, f32::max)
302 .max(0.6);
303 let global_slot_h_world = global_max_panel_h_world + row_gap_world;
304 let tower_top_y = (7.0 * global_slot_h_world) + 4.0;
305 let tower_center_z = total_depth * 0.5;
306
307 let tower_light_transform = universe.world.add_component(
308 engine::ecs::component::TransformComponent::new().with_position(
309 0.0,
310 tower_top_y,
311 tower_center_z,
312 ),
313 );
314 let tower_light = universe.world.add_component(
315 engine::ecs::component::PointLightComponent::new()
316 .with_intensity(2.0)
317 .with_distance(120.0)
318 .with_color(1.0, 1.0, 1.0),
319 );
320 let _ = universe.attach(tower_light_transform, tower_light);
321 universe.add(tower_light_transform);
322
323 for (col_idx, (_folder, mut entries)) in files_by_folder.into_iter().enumerate() {
324 entries.sort_by(|a, b| a.path.cmp(&b.path));
325
326 // Slot height (world units) based on the tallest panel in this folder.
327 let pad_y = 4.0;
328 let max_panel_h_world = entries
329 .iter()
330 .map(|e| ((e.rows as f32) + pad_y) * TEXT_SCALE)
331 .fold(0.0_f32, f32::max)
332 .max(0.6);
333 let slot_h_world = max_panel_h_world + row_gap_world;
334
335 let col_x = start_x + (col_idx as f32) * spacing;
336
337 for (i, entry) in entries.into_iter().enumerate() {
338 let row = (i % 8) as f32;
339 let stack = (i / 8) as f32;
340 let file_y = row * slot_h_world;
341 let file_z = stack * stack_depth;
342
343 let file_group = universe.world.add_component(
344 engine::ecs::component::TransformComponent::new()
345 .with_position(col_x, file_y, file_z),
346 );
347
348 // Text subtree (tiny scale).
349 let file_root = universe.world.add_component(
350 engine::ecs::component::TransformComponent::new()
351 .with_position(0.0, 1.0, 0.0)
352 .with_scale(TEXT_SCALE, TEXT_SCALE, 1.0)
353 .with_rotation_euler(0.0, std::f32::consts::PI / 6.0, 0.0),
354 );
355 let _ = universe.attach(file_group, file_root);
356
357 // Background panel behind the text.
358 // Size is based on wrapped line count (matches TextSystem's strict wrapping behavior).
359 let (max_cols, rows) = (entry.max_cols, entry.rows);
360 let pad_x = 4.0;
361 let pad_y = 4.0;
362 let w = (max_cols as f32) + pad_x;
363 let h = (rows as f32) + pad_y;
364
365 // Text glyph quads are centered at integer (col,row) positions and are 1x1, so the
366 // text's AABB (in text-space) is roughly:
367 // x: [-0.5, max_cols-0.5]
368 // y: [-(rows-1)-0.5, 0.5]
369 // Centering the background quad at those midpoints keeps it aligned as text grows.
370 let bg_x = (max_cols as f32 - 1.0) * 0.5;
371 let bg_y = -((rows as f32 - 1.0) * 0.5);
372
373 let bg_transform = universe.world.add_component(
374 engine::ecs::component::TransformComponent::new()
375 .with_position(bg_x, bg_y, -0.05)
376 .with_scale(w, h, 1.0),
377 );
378 let bg_renderable = universe
379 .world
380 .add_component(engine::ecs::component::RenderableComponent::square());
381 let bg_quant = universe.world.add_component(
382 engine::ecs::component::LightQuantizationComponent::steps(5.0),
383 );
384 let bg_color =
385 universe
386 .world
387 .add_component(engine::ecs::component::ColorComponent::rgba(
388 0.2, 0.2, 0.2, 1.0,
389 ));
390 let _ = universe.attach(file_root, bg_transform);
391 let _ = universe.attach(bg_transform, bg_renderable);
392 let _ = universe.attach(bg_renderable, bg_quant);
393 let _ = universe.attach(bg_renderable, bg_color);
394
395 let text =
396 universe
397 .world
398 .add_component(engine::ecs::component::TextComponent::with_wrap(
399 entry.display_text,
400 WRAP_AT,
401 ));
402 let cutout = universe
403 .world
404 .add_component(engine::ecs::component::TransparentCutoutComponent::new());
405 let filtering = universe.world.add_component(
406 engine::ecs::component::TextureFilteringComponent::nearest_magnification(),
407 );
408 // let color = universe
409 // .world
410 // .add_component(engine::ecs::component::ColorComponent::rgba(0.7, 0.7, 1.0, 1.0));
411 let emissive = universe
412 .world
413 .add_component(engine::ecs::component::EmissiveComponent::on());
414 let _ = universe.attach(file_root, text);
415 let _ = universe.attach(text, cutout);
416 let _ = universe.attach(text, filtering);
417 //let _ = universe.world.add_child(text, color);
418 let _ = universe.attach(text, emissive);
419
420 universe.add(file_group);
421 }
422 }
423
424 // Process init-time registrations (Text expands into glyph subtrees here).
425 universe.systems.process_commands(
426 &mut universe.world,
427 &mut universe.visuals,
428 &mut universe.render_assets,
429 &mut universe.command_queue,
430 );
431
432 engine::Windowing::run_app(universe).expect("Windowing failed");
433}pub fn with_steps(self, steps: f32) -> Self
Trait Implementations§
Source§impl Clone for LightQuantizationComponent
impl Clone for LightQuantizationComponent
Source§fn clone(&self) -> LightQuantizationComponent
fn clone(&self) -> LightQuantizationComponent
Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreSource§impl Component for LightQuantizationComponent
impl Component for LightQuantizationComponent
Source§fn name(&self) -> &'static str
fn name(&self) -> &'static str
Short debug/type name for this component kind (e.g. “transform”, “camera”).
fn as_any(&self) -> &dyn Any
fn as_any_mut(&mut self) -> &mut dyn Any
Source§fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
Called when component is added to the World
Source§fn to_mms_ast(&self, _world: &World) -> ComponentExpression
fn to_mms_ast(&self, _world: &World) -> ComponentExpression
Encode this component as an MMS Component Expression AST node. Read more
fn set_id(&mut self, _component: ComponentId)
Source§fn cleanup(&mut self, _emit: &mut dyn SignalEmitter, _component: ComponentId)
fn cleanup(&mut self, _emit: &mut dyn SignalEmitter, _component: ComponentId)
Called when component is removed from the World.
impl Copy for LightQuantizationComponent
Source§impl Debug for LightQuantizationComponent
impl Debug for LightQuantizationComponent
Auto Trait Implementations§
impl Freeze for LightQuantizationComponent
impl RefUnwindSafe for LightQuantizationComponent
impl Send for LightQuantizationComponent
impl Sync for LightQuantizationComponent
impl Unpin for LightQuantizationComponent
impl UnsafeUnpin for LightQuantizationComponent
impl UnwindSafe for LightQuantizationComponent
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.