Skip to main content

text_example/
text-example.rs

1use mittens_engine::{engine, utils};
2
3#[path = "example_util/mod.rs"]
4mod example_util;
5
6fn main() {
7    mittens_engine::example_support::ensure_model_assets();
8    utils::logger::init();
9
10    let world = engine::ecs::World::default();
11    let mut universe = engine::Universe::new(world);
12
13    // Input-driven camera rig.
14    // Topology: I { T { C3D } }
15    let input = universe
16        .world
17        .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
18    let camera3d = universe
19        .world
20        .add_component(engine::ecs::component::Camera3DComponent::new());
21    let rig_transform = universe.world.add_component(
22        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.5),
23    );
24    let input_mode = universe.world.add_component(
25        engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
26    );
27    let _ = universe.attach(input, input_mode);
28    let _ = universe.attach(input, rig_transform);
29    let _ = universe.attach(rig_transform, camera3d);
30
31    // Topology: I { T { C3D } } — add a small camera-attached controls hint.
32    example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
33    universe.add(input);
34
35    // Debug square: show the full font texture.
36    let debug_root = universe.world.add_component(
37        engine::ecs::component::TransformComponent::new()
38            .with_position(0.6, -0.2, 0.0)
39            .with_scale(0.8, 0.8, 1.0),
40    );
41    let debug_renderable = universe
42        .world
43        .add_component(engine::ecs::component::RenderableComponent::square());
44    let debug_tex =
45        universe
46            .world
47            .add_component(engine::ecs::component::TextureComponent::with_uri(
48                "assets/textures/font_system.dds",
49            ));
50    let debug_filtering = universe
51        .world
52        .add_component(engine::ecs::component::TextureFilteringComponent::nearest_magnification());
53    let _ = universe.attach(debug_root, debug_renderable);
54    let _ = universe.attach(debug_renderable, debug_tex);
55    let _ = universe.attach(debug_renderable, debug_filtering);
56    universe.add(debug_root);
57
58    // Light so we can actually see non-emissive materials.
59    let light_transform = universe.world.add_component(
60        engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 2.0),
61    );
62    let light = universe.world.add_component(
63        engine::ecs::component::PointLightComponent::new()
64            .with_distance(25.0)
65            .with_color(1.0, 1.0, 1.0),
66    );
67    let _ = universe.attach(light_transform, light);
68    universe.add(light_transform);
69
70    // 4 red cubes around the perimeter of the world (easy visual anchors).
71    fn spawn_red_cube(universe: &mut engine::Universe, x: f32, y: f32, z: f32, s: f32) {
72        let t = universe.world.add_component(
73            engine::ecs::component::TransformComponent::new()
74                .with_position(x, y, z)
75                .with_scale(s, s, s),
76        );
77        let r = universe
78            .world
79            .add_component(engine::ecs::component::RenderableComponent::cube());
80        let c = universe
81            .world
82            .add_component(engine::ecs::component::ColorComponent::rgba(
83                1.0, 0.0, 0.0, 1.0,
84            ));
85        let e = universe
86            .world
87            .add_component(engine::ecs::component::EmissiveComponent::on());
88
89        let _ = universe.attach(t, r);
90        let _ = universe.attach(r, c);
91        let _ = universe.attach(r, e);
92
93        universe.add(t);
94    }
95
96    let p = 1.5;
97    let s = 0.25;
98    spawn_red_cube(&mut universe, -p, -p, 0.0, s);
99    spawn_red_cube(&mut universe, p, -p, 0.0, s);
100    spawn_red_cube(&mut universe, -p, p, 0.0, s);
101    spawn_red_cube(&mut universe, p, p, 0.0, s);
102
103    use engine::ecs::component::{
104        ColorComponent, TextComponent, TextShadowComponent, TextureComponent,
105        TextureFilteringComponent, TransformComponent, TransparentCutoutComponent,
106    };
107
108    fn spawn_text_style(
109        universe: &mut engine::Universe,
110        pos: [f32; 3],
111        scale: f32,
112        text: &str,
113        color: [f32; 4],
114        shadow: TextShadowComponent,
115        filtering: TextureFilteringComponent,
116    ) {
117        let root = universe.world.add_component(
118            TransformComponent::new()
119                .with_position(pos[0], pos[1], pos[2])
120                .with_scale(scale, scale, 1.0),
121        );
122
123        // Color must be an ancestor of the glyph renderables.
124        let color_id = universe
125            .world
126            .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
127        let _ = universe.attach(root, color_id);
128
129        let text_id = universe.world.add_component(TextComponent::new(text));
130        let _ = universe.attach(color_id, text_id);
131
132        // Route into cutout pass for cleaner edges.
133        let cutout = universe
134            .world
135            .add_component(TransparentCutoutComponent::new());
136        let _ = universe.attach(text_id, cutout);
137
138        // Use the same atlas as the debug quad.
139        let tex = universe
140            .world
141            .add_component(TextureComponent::with_uri("assets/textures/font.dds"));
142        let _ = universe.attach(text_id, tex);
143
144        let shadow_id = universe.world.add_component(shadow);
145        let _ = universe.attach(text_id, shadow_id);
146
147        let filtering_id = universe.world.add_component(filtering);
148        let _ = universe.attach(text_id, filtering_id);
149
150        universe.add(root);
151    }
152
153    // Multiple text samples to show:
154    // - different inherited colors
155    // - different shadow settings
156    // - different texture filtering
157    spawn_text_style(
158        &mut universe,
159        [-0.95, 0.45, 0.0],
160        0.12,
161        "NEAREST_MAG\n(crisp)\nAaBbCc 123",
162        [1.0, 1.0, 1.0, 1.0],
163        TextShadowComponent::new()
164            .with_scale(1.35)
165            .with_offset([0.06, -0.06, 0.0015]),
166        TextureFilteringComponent::nearest_magnification(),
167    );
168
169    spawn_text_style(
170        &mut universe,
171        [-0.95, 0.05, 0.0],
172        0.12,
173        "LINEAR\n(softer)\nAaBbCc 123",
174        [0.55, 0.90, 1.0, 1.0],
175        TextShadowComponent::new()
176            .with_rgba([0.0, 0.0, 0.15, 1.0])
177            .with_scale(1.20)
178            .with_offset([0.05, -0.04, 0.0015]),
179        TextureFilteringComponent::linear(),
180    );
181
182    spawn_text_style(
183        &mut universe,
184        [-0.95, -0.35, 0.0],
185        0.12,
186        "NEAREST\n(pixelly)\nAaBbCc 123",
187        [1.0, 0.85, 0.35, 1.0],
188        TextShadowComponent::new()
189            .with_rgba([0.15, 0.0, 0.0, 1.0])
190            .with_scale(1.55)
191            .with_offset([0.08, -0.08, 0.0015]),
192        TextureFilteringComponent::nearest(),
193    );
194
195    // Process init-time registrations (Text expands into glyph subtrees here).
196    universe.systems.process_commands(
197        &mut universe.world,
198        &mut universe.visuals,
199        &mut universe.render_assets,
200        &mut universe.command_queue,
201    );
202
203    engine::Windowing::run_app(universe).expect("Windowing failed");
204}