pub struct World {
pub objects: HashMap<usize, Object>,
pub roots: Vec<usize>,
pub name_handles: HashMap<String, usize>,
pub on_scene_graph_modified: Option<SceneGraphCallback>,
/* private fields */
}Fields§
§objects: HashMap<usize, Object>§roots: Vec<usize>§name_handles: HashMap<String, usize>§on_scene_graph_modified: Option<SceneGraphCallback>Optional callback invoked after every structural scene-graph change.
Implementations§
Source§impl World
impl World
Sourcepub fn new() -> Self
pub fn new() -> Self
Examples found in repository?
30fn main() {
31 // ── 1. Build a scene in memory ────────────────────────────────────────────
32
33 let camera = Camera::new()
34 .with_position([0.0, 5.0, -12.0])
35 .with_rotation(90.0, -20.0);
36
37 let mut world = World::new();
38
39 // Root object
40 let root_id = world.spawn_object(
41 Object {
42 name: "Root".to_string(),
43 str_id: "root".to_string(),
44 geometry: Some(Geometry::Cube { size: 1.0 }),
45 color: [1.0, 0.4, 0.4, 1.0],
46 transform: Transform::from_position(0.0, 0.0, 0.0),
47 ..Default::default()
48 },
49 None,
50 );
51
52 // Child of root
53 let child_id = world.spawn_object(
54 Object {
55 name: "Child".to_string(),
56 str_id: "child".to_string(),
57 geometry: Some(Geometry::Sphere {
58 radius: 0.5,
59 subdivisions: 16,
60 }),
61 color: [0.4, 0.8, 0.4, 1.0],
62 transform: Transform::from_position(3.0, 0.0, 0.0),
63 ..Default::default()
64 },
65 Some(root_id),
66 );
67
68 // Grandchild
69 world.spawn_object(
70 Object {
71 name: "Grandchild".to_string(),
72 str_id: "grandchild".to_string(),
73 geometry: Some(Geometry::Pyramid {
74 base_size: 0.8,
75 height: 1.2,
76 }),
77 color: [0.4, 0.4, 1.0, 1.0],
78 transform: Transform::from_position(2.0, 0.0, 0.0),
79 ..Default::default()
80 },
81 Some(child_id),
82 );
83
84 println!("Original scene: {} object(s)", world.objects.len());
85
86 // ── 2. Serialize ──────────────────────────────────────────────────────────
87
88 let mut buf: Vec<u8> = Vec::new();
89 vtr::write(&mut buf, &camera, &world).expect("serialization failed");
90 println!("Serialized to {} byte(s)", buf.len());
91
92 // ── 3. Peek at the header ─────────────────────────────────────────────────
93
94 let header = vtr::read_header(&mut Cursor::new(&buf)).expect("header read failed");
95 println!(
96 "Header: format v{}, engine v{}, {} object(s)",
97 header.format_version,
98 header.engine_version_string(),
99 header.object_count,
100 );
101
102 assert_eq!(header.object_count as usize, world.objects.len());
103
104 // ── 4. Deserialize ────────────────────────────────────────────────────────
105
106 let loaded = vtr::read(&mut Cursor::new(&buf)).expect("deserialization failed");
107 println!("Loaded scene: {} object(s)", loaded.world.objects.len());
108
109 // ── 5. Verify ─────────────────────────────────────────────────────────────
110
111 assert_eq!(
112 loaded.world.objects.len(),
113 world.objects.len(),
114 "object count mismatch"
115 );
116
117 // Check that hierarchy is preserved.
118 for str_id in ["root", "child", "grandchild"] {
119 let orig_id = world.get_id(str_id).expect("str_id missing in original");
120 let load_id = loaded
121 .world
122 .get_id(str_id)
123 .expect("str_id missing after round-trip");
124
125 let orig = &world.objects[&orig_id];
126 let load = &loaded.world.objects[&load_id];
127
128 assert_eq!(orig.name, load.name, "name mismatch for {str_id}");
129 assert_eq!(orig.color, load.color, "color mismatch for {str_id}");
130 assert_eq!(
131 orig.transform.position, load.transform.position,
132 "position mismatch for {str_id}"
133 );
134 assert_eq!(
135 orig.geometry, load.geometry,
136 "geometry mismatch for {str_id}"
137 );
138
139 println!(" ✓ {str_id:12} name={:?}", load.name);
140 }
141
142 // Camera round-trip
143 assert_eq!(camera.eye, loaded.camera.eye, "camera eye mismatch");
144 assert_eq!(camera.fov, loaded.camera.fov, "camera fov mismatch");
145 println!(" ✓ camera");
146
147 println!("\nRound-trip verified ✓");
148}Sourcepub fn from_parts(
objects: HashMap<usize, Object>,
roots: Vec<usize>,
next_id: usize,
) -> Self
pub fn from_parts( objects: HashMap<usize, Object>, roots: Vec<usize>, next_id: usize, ) -> Self
Reconstruct a World directly from its constituent parts.
Used by the VTR deserializer to rebuild a world without going through
spawn_object, so that the original IDs and hierarchy are preserved
exactly.
next_id should be set to max(existing_ids) + 1 so that future
calls to spawn_object never collide with the loaded objects.
Sourcepub fn spawn_object(
&mut self,
object: Object,
parent_id: Option<usize>,
) -> usize
pub fn spawn_object( &mut self, object: Object, parent_id: Option<usize>, ) -> usize
Examples found in repository?
30fn main() {
31 // ── 1. Build a scene in memory ────────────────────────────────────────────
32
33 let camera = Camera::new()
34 .with_position([0.0, 5.0, -12.0])
35 .with_rotation(90.0, -20.0);
36
37 let mut world = World::new();
38
39 // Root object
40 let root_id = world.spawn_object(
41 Object {
42 name: "Root".to_string(),
43 str_id: "root".to_string(),
44 geometry: Some(Geometry::Cube { size: 1.0 }),
45 color: [1.0, 0.4, 0.4, 1.0],
46 transform: Transform::from_position(0.0, 0.0, 0.0),
47 ..Default::default()
48 },
49 None,
50 );
51
52 // Child of root
53 let child_id = world.spawn_object(
54 Object {
55 name: "Child".to_string(),
56 str_id: "child".to_string(),
57 geometry: Some(Geometry::Sphere {
58 radius: 0.5,
59 subdivisions: 16,
60 }),
61 color: [0.4, 0.8, 0.4, 1.0],
62 transform: Transform::from_position(3.0, 0.0, 0.0),
63 ..Default::default()
64 },
65 Some(root_id),
66 );
67
68 // Grandchild
69 world.spawn_object(
70 Object {
71 name: "Grandchild".to_string(),
72 str_id: "grandchild".to_string(),
73 geometry: Some(Geometry::Pyramid {
74 base_size: 0.8,
75 height: 1.2,
76 }),
77 color: [0.4, 0.4, 1.0, 1.0],
78 transform: Transform::from_position(2.0, 0.0, 0.0),
79 ..Default::default()
80 },
81 Some(child_id),
82 );
83
84 println!("Original scene: {} object(s)", world.objects.len());
85
86 // ── 2. Serialize ──────────────────────────────────────────────────────────
87
88 let mut buf: Vec<u8> = Vec::new();
89 vtr::write(&mut buf, &camera, &world).expect("serialization failed");
90 println!("Serialized to {} byte(s)", buf.len());
91
92 // ── 3. Peek at the header ─────────────────────────────────────────────────
93
94 let header = vtr::read_header(&mut Cursor::new(&buf)).expect("header read failed");
95 println!(
96 "Header: format v{}, engine v{}, {} object(s)",
97 header.format_version,
98 header.engine_version_string(),
99 header.object_count,
100 );
101
102 assert_eq!(header.object_count as usize, world.objects.len());
103
104 // ── 4. Deserialize ────────────────────────────────────────────────────────
105
106 let loaded = vtr::read(&mut Cursor::new(&buf)).expect("deserialization failed");
107 println!("Loaded scene: {} object(s)", loaded.world.objects.len());
108
109 // ── 5. Verify ─────────────────────────────────────────────────────────────
110
111 assert_eq!(
112 loaded.world.objects.len(),
113 world.objects.len(),
114 "object count mismatch"
115 );
116
117 // Check that hierarchy is preserved.
118 for str_id in ["root", "child", "grandchild"] {
119 let orig_id = world.get_id(str_id).expect("str_id missing in original");
120 let load_id = loaded
121 .world
122 .get_id(str_id)
123 .expect("str_id missing after round-trip");
124
125 let orig = &world.objects[&orig_id];
126 let load = &loaded.world.objects[&load_id];
127
128 assert_eq!(orig.name, load.name, "name mismatch for {str_id}");
129 assert_eq!(orig.color, load.color, "color mismatch for {str_id}");
130 assert_eq!(
131 orig.transform.position, load.transform.position,
132 "position mismatch for {str_id}"
133 );
134 assert_eq!(
135 orig.geometry, load.geometry,
136 "geometry mismatch for {str_id}"
137 );
138
139 println!(" ✓ {str_id:12} name={:?}", load.name);
140 }
141
142 // Camera round-trip
143 assert_eq!(camera.eye, loaded.camera.eye, "camera eye mismatch");
144 assert_eq!(camera.fov, loaded.camera.fov, "camera fov mismatch");
145 println!(" ✓ camera");
146
147 println!("\nRound-trip verified ✓");
148}Sourcepub fn get_id(&self, str_id: &str) -> Option<usize>
pub fn get_id(&self, str_id: &str) -> Option<usize>
Returns the unique integer ID associated with a given string identifier (str_id).
This method performs a lookup in the internal handle cache. While the lookup is
technically $O(1)$ on average, it involves hashing the input string and searching
a HashMap.
§Performance Warning
Do not use this method inside on_update or other high-frequency loops.
Calling this every frame for multiple objects will cause significant performance
degradation due to repeated string hashing and cache misses. Instead, “memoize”
the ID: call this method once during on_startup, store the resulting usize
in your application state, and use that integer ID for direct access during updates.
§Examples
// Correct: Resolve once during initialization
let sun_id = scene.get_id("sun_center").expect("Sun not found in scene!");
state.sun_id = Some(sun_id);Examples found in repository?
42fn main() {
43 let initial_state = AppState {
44 sun_id: None,
45 earth_id: None,
46 moon_id: None,
47 };
48
49 Window::new(initial_state)
50 .with_title("Simple Solar Simulation")
51 .with_camera(
52 Camera::new()
53 .with_position([0.0, 8.0, -12.0])
54 .with_rotation(90.0, -30.0)
55 )
56 .on_startup(|state, scene, _| {
57 // 1. The Sun (Center)
58 let sun = Object {
59 name: "Sun".to_string(),
60 str_id: "sun".to_string (),
61 transform: Transform::from_position(0.0, 0.0, 0.0),
62 geometry: Some(Geometry::Cube { size: 2.0 }),
63 color: [1.0, 0.9, 0.2, 1.0],
64 ..Default::default()
65 };
66 let sun_id = scene.spawn(sun, None);
67
68 // 2. The Planet (Child)
69 let planet = Object {
70 name: "Planet".to_string(),
71 str_id: "earth".to_string(),
72 transform: Transform::from_position(6.0, 0.0, 0.0),
73 geometry: Some(Geometry::Sphere { radius: 0.8, subdivisions: 24 }),
74 color: [0.2, 0.5, 1.0, 1.0],
75 ..Default::default()
76 };
77 let planet_id = scene.spawn(planet, Some(sun_id));
78
79 // 3. The Moon (Grandchild)
80 let moon = Object {
81 name: "Moon".to_string(),
82 str_id: "moon".to_string(),
83 transform: Transform::from_position(1.5, 0.0, 0.0),
84 geometry: Some(Geometry::Sphere { radius: 0.3, subdivisions: 16 }),
85 color: [0.7, 0.7, 0.7, 1.0],
86 ..Default::default()
87 };
88 scene.spawn(moon, Some(planet_id));
89
90 state.sun_id = scene.world.get_id("sun");
91 state.earth_id = scene.world.get_id("earth");
92 state.moon_id = scene.world.get_id("moon");
93 scene.enable_editor_mode();
94 })
95 .on_update(|state, scene, ctx| {
96 // Rotate the Sun (the planet will orbit automatically)
97 if let Some(sun) = state.sun_id.and_then(|id| scene.world.get_mut(id)) {
98 sun.transform.rotation[1] += 30.0 * ctx.dt;
99 }
100
101 // Rotate the Planet (Earth)
102 if let Some(planet) = state.earth_id.and_then(|id| scene.world.get_mut(id)) {
103 planet.transform.rotation[1] += 100.0 * ctx.dt;
104 }
105 })
106 // on_editor_event fires whenever the editor's internal state changes:
107 // • T / R / E keys → GizmoModeChanged
108 // • Gizmo axis drag → DragStart / DragEnd
109 // on_update is suppressed in editor mode, so game logic here is safe.
110 .on_editor_event(|_state, _scene, event, object| {
111 match event {
112 EditorStateEvent::GizmoModeChanged(mode) => {
113 let label = match mode {
114 GizmoMode::Translate => "Translate",
115 GizmoMode::Rotate => "Rotate",
116 GizmoMode::Scale => "Scale",
117 };
118 println!("[Editor] Gizmo mode → {label} {object:?}");
119 }
120 EditorStateEvent::DragStart { axis } => {
121 let label = match axis {
122 DragAxis::X => "X",
123 DragAxis::Y => "Y",
124 DragAxis::Z => "Z",
125 };
126 println!("[Editor] Drag started — axis: {label} {object:?}");
127 }
128 EditorStateEvent::DragEnd => {
129 println!("[Editor] Drag ended {object:?}");
130 }
131 EditorStateEvent::SelectionChanged => {
132 println!("[Editor] Selection changed {object:?}");
133 }
134 }
135 })
136 .create();
137}More examples
30fn main() {
31 // ── 1. Build a scene in memory ────────────────────────────────────────────
32
33 let camera = Camera::new()
34 .with_position([0.0, 5.0, -12.0])
35 .with_rotation(90.0, -20.0);
36
37 let mut world = World::new();
38
39 // Root object
40 let root_id = world.spawn_object(
41 Object {
42 name: "Root".to_string(),
43 str_id: "root".to_string(),
44 geometry: Some(Geometry::Cube { size: 1.0 }),
45 color: [1.0, 0.4, 0.4, 1.0],
46 transform: Transform::from_position(0.0, 0.0, 0.0),
47 ..Default::default()
48 },
49 None,
50 );
51
52 // Child of root
53 let child_id = world.spawn_object(
54 Object {
55 name: "Child".to_string(),
56 str_id: "child".to_string(),
57 geometry: Some(Geometry::Sphere {
58 radius: 0.5,
59 subdivisions: 16,
60 }),
61 color: [0.4, 0.8, 0.4, 1.0],
62 transform: Transform::from_position(3.0, 0.0, 0.0),
63 ..Default::default()
64 },
65 Some(root_id),
66 );
67
68 // Grandchild
69 world.spawn_object(
70 Object {
71 name: "Grandchild".to_string(),
72 str_id: "grandchild".to_string(),
73 geometry: Some(Geometry::Pyramid {
74 base_size: 0.8,
75 height: 1.2,
76 }),
77 color: [0.4, 0.4, 1.0, 1.0],
78 transform: Transform::from_position(2.0, 0.0, 0.0),
79 ..Default::default()
80 },
81 Some(child_id),
82 );
83
84 println!("Original scene: {} object(s)", world.objects.len());
85
86 // ── 2. Serialize ──────────────────────────────────────────────────────────
87
88 let mut buf: Vec<u8> = Vec::new();
89 vtr::write(&mut buf, &camera, &world).expect("serialization failed");
90 println!("Serialized to {} byte(s)", buf.len());
91
92 // ── 3. Peek at the header ─────────────────────────────────────────────────
93
94 let header = vtr::read_header(&mut Cursor::new(&buf)).expect("header read failed");
95 println!(
96 "Header: format v{}, engine v{}, {} object(s)",
97 header.format_version,
98 header.engine_version_string(),
99 header.object_count,
100 );
101
102 assert_eq!(header.object_count as usize, world.objects.len());
103
104 // ── 4. Deserialize ────────────────────────────────────────────────────────
105
106 let loaded = vtr::read(&mut Cursor::new(&buf)).expect("deserialization failed");
107 println!("Loaded scene: {} object(s)", loaded.world.objects.len());
108
109 // ── 5. Verify ─────────────────────────────────────────────────────────────
110
111 assert_eq!(
112 loaded.world.objects.len(),
113 world.objects.len(),
114 "object count mismatch"
115 );
116
117 // Check that hierarchy is preserved.
118 for str_id in ["root", "child", "grandchild"] {
119 let orig_id = world.get_id(str_id).expect("str_id missing in original");
120 let load_id = loaded
121 .world
122 .get_id(str_id)
123 .expect("str_id missing after round-trip");
124
125 let orig = &world.objects[&orig_id];
126 let load = &loaded.world.objects[&load_id];
127
128 assert_eq!(orig.name, load.name, "name mismatch for {str_id}");
129 assert_eq!(orig.color, load.color, "color mismatch for {str_id}");
130 assert_eq!(
131 orig.transform.position, load.transform.position,
132 "position mismatch for {str_id}"
133 );
134 assert_eq!(
135 orig.geometry, load.geometry,
136 "geometry mismatch for {str_id}"
137 );
138
139 println!(" ✓ {str_id:12} name={:?}", load.name);
140 }
141
142 // Camera round-trip
143 assert_eq!(camera.eye, loaded.camera.eye, "camera eye mismatch");
144 assert_eq!(camera.fov, loaded.camera.fov, "camera fov mismatch");
145 println!(" ✓ camera");
146
147 println!("\nRound-trip verified ✓");
148}Sourcepub fn get_mut(&mut self, id: usize) -> Option<&mut Object>
pub fn get_mut(&mut self, id: usize) -> Option<&mut Object>
Examples found in repository?
26fn main() {
27 Window::new(AppState { cube_id: None })
28 .with_title("Hello, Cube!")
29 .with_camera(
30 Camera::new()
31 .with_position([0.0, 2.0, -5.0])
32 .with_rotation(90.0, -15.0),
33 )
34 .on_startup(|state, scene, _| {
35 // Spawn a single cube at the world origin.
36 let id = scene.spawn(
37 Object {
38 name: "Cube".to_string(),
39 str_id: "cube".to_string(),
40 geometry: Some(Geometry::Cube { size: 1.5 }),
41 color: [0.9, 0.5, 0.2, 1.0], // warm orange
42 transform: Transform::default(),
43 ..Default::default()
44 },
45 None, // no parent — this is a root object
46 );
47 // Cache the ID so on_update can find it cheaply.
48 state.cube_id = Some(id);
49 })
50 .on_update(|state, scene, ctx| {
51 // Rotate 45° per second around the Y-axis.
52 if let Some(cube) = state.cube_id.and_then(|id| scene.world.get_mut(id)) {
53 cube.transform.rotation[1] += 45.0 * ctx.dt;
54 }
55 })
56 .create();
57}More examples
40fn main() {
41 Window::new(AppState { cube_id: None })
42 .with_title("Textured Cube — Vertra")
43 .with_camera(
44 Camera::new()
45 .with_position([0.0, 2.5, -6.0])
46 .with_rotation(90.0, -15.0),
47 )
48 .on_startup(|state, scene, _| {
49 // ----------------------------------------------------------------
50 // 1. Load the texture from disk.
51 // The key must match the `texture_path` set on the object.
52 // ----------------------------------------------------------------
53 #[cfg(not(target_arch = "wasm32"))]
54 match scene.load_texture(TEXTURE_PATH) {
55 Ok(()) => println!("[info] Texture loaded: {TEXTURE_PATH}"),
56 Err(e) => eprintln!("[warn] {e} – cube will render with vertex colour"),
57 }
58
59 // ----------------------------------------------------------------
60 // 2. Textured cube (white vertex colour so the image shows cleanly)
61 // ----------------------------------------------------------------
62 let cube_id = scene.spawn(
63 Object {
64 name: "Textured Cube".to_string(),
65 str_id: "cube".to_string(),
66 geometry: Some(Geometry::Cube { size: 2.0 }),
67 // White vertex colour = texture displayed without tint.
68 // Change this to tint the texture (e.g. [1.0, 0.5, 0.5, 1.0] = reddish).
69 color: [1.0, 1.0, 1.0, 1.0],
70 transform: Transform::from_position(0.0, 1.0, 0.0),
71 texture_path: Some(TEXTURE_PATH.to_string()),
72 ..Default::default()
73 },
74 None,
75 );
76 state.cube_id = Some(cube_id);
77 })
78 .on_update(|state, scene, ctx| {
79 // Slowly rotate the cube so all faces of the texture are visible.
80 if let Some(cube) = state.cube_id.and_then(|id| scene.world.get_mut(id)) {
81 cube.transform.rotation[1] += 40.0 * ctx.dt; // 40°/s around Y
82 cube.transform.rotation[0] += 15.0 * ctx.dt; // 15°/s around X
83 }
84 })
85 .create();
86}44fn main() {
45 Window::new(AppState {
46 ball_id: None,
47 cube_id: None,
48 ball_vy: 8.0, // initial upward kick
49 })
50 .with_title("Fixed Update — Bouncing Ball")
51 .with_camera(
52 Camera::new()
53 .with_position([0.0, 3.0, -8.0])
54 .with_rotation(90.0, -15.0),
55 )
56 .on_startup(|state, scene, _| {
57 // Bouncing sphere
58 let ball_id = scene.spawn(
59 Object {
60 name: "Ball".to_string(),
61 str_id: "ball".to_string(),
62 geometry: Some(Geometry::Sphere {
63 radius: 0.5,
64 subdivisions: 20,
65 }),
66 color: [0.3, 0.7, 1.0, 1.0],
67 transform: Transform::from_position(0.0, 4.0, 0.0),
68 ..Default::default()
69 },
70 None,
71 );
72
73 // Ground plane
74 scene.spawn(
75 Object {
76 name: "Ground".to_string(),
77 str_id: "ground".to_string(),
78 geometry: Some(Geometry::Plane { size: 12.0 }),
79 color: [0.3, 0.6, 0.3, 1.0],
80 transform: Transform::from_position(0.0, 0.0, 0.0),
81 ..Default::default()
82 },
83 None,
84 );
85
86 // Spinning cube for on_update comparison
87 let cube_id = scene.spawn(
88 Object {
89 name: "Spinner".to_string(),
90 str_id: "spinner".to_string(),
91 geometry: Some(Geometry::Cube { size: 1.0 }),
92 color: [1.0, 0.5, 0.2, 1.0],
93 transform: Transform::from_position(3.5, 1.0, 0.0),
94 ..Default::default()
95 },
96 None,
97 );
98
99 state.ball_id = Some(ball_id);
100 state.cube_id = Some(cube_id);
101 })
102 // ── Physics tick (fixed 60 Hz) ────────────────────────────────────────────
103 .on_fixed_update(|state, scene, ctx| {
104 const GRAVITY: f32 = -12.0; // m/s²
105 const RESTITUTION: f32 = 0.78; // energy retained on bounce (0–1)
106 const GROUND_Y: f32 = 0.5; // ball radius — lowest allowed centre
107
108 if let Some(id) = state.ball_id {
109 // Integrate gravity.
110 state.ball_vy += GRAVITY * ctx.dt;
111
112 if let Some(ball) = scene.world.get_mut(id) {
113 ball.transform.position[1] += state.ball_vy * ctx.dt;
114
115 // Ground collision: reflect and damp.
116 if ball.transform.position[1] < GROUND_Y {
117 ball.transform.position[1] = GROUND_Y;
118 state.ball_vy = state.ball_vy.abs() * RESTITUTION;
119
120 // Stop micro-bounces that would never settle.
121 if state.ball_vy < 0.2 {
122 state.ball_vy = 0.0;
123 }
124 }
125 }
126 }
127 })
128 // ── Visual update (variable dt) ───────────────────────────────────────────
129 .on_update(|state, scene, ctx| {
130 // Spin the reference cube at 90 °/s — purely cosmetic.
131 if let Some(spinner) = state.cube_id.and_then(|id| scene.world.get_mut(id)) {
132 spinner.transform.rotation[1] += 90.0 * ctx.dt;
133 spinner.transform.rotation[0] += 45.0 * ctx.dt;
134 }
135 })
136 .create();
137}42fn main() {
43 let initial_state = AppState {
44 sun_id: None,
45 earth_id: None,
46 moon_id: None,
47 };
48
49 Window::new(initial_state)
50 .with_title("Simple Solar Simulation")
51 .with_camera(
52 Camera::new()
53 .with_position([0.0, 8.0, -12.0])
54 .with_rotation(90.0, -30.0)
55 )
56 .on_startup(|state, scene, _| {
57 // 1. The Sun (Center)
58 let sun = Object {
59 name: "Sun".to_string(),
60 str_id: "sun".to_string (),
61 transform: Transform::from_position(0.0, 0.0, 0.0),
62 geometry: Some(Geometry::Cube { size: 2.0 }),
63 color: [1.0, 0.9, 0.2, 1.0],
64 ..Default::default()
65 };
66 let sun_id = scene.spawn(sun, None);
67
68 // 2. The Planet (Child)
69 let planet = Object {
70 name: "Planet".to_string(),
71 str_id: "earth".to_string(),
72 transform: Transform::from_position(6.0, 0.0, 0.0),
73 geometry: Some(Geometry::Sphere { radius: 0.8, subdivisions: 24 }),
74 color: [0.2, 0.5, 1.0, 1.0],
75 ..Default::default()
76 };
77 let planet_id = scene.spawn(planet, Some(sun_id));
78
79 // 3. The Moon (Grandchild)
80 let moon = Object {
81 name: "Moon".to_string(),
82 str_id: "moon".to_string(),
83 transform: Transform::from_position(1.5, 0.0, 0.0),
84 geometry: Some(Geometry::Sphere { radius: 0.3, subdivisions: 16 }),
85 color: [0.7, 0.7, 0.7, 1.0],
86 ..Default::default()
87 };
88 scene.spawn(moon, Some(planet_id));
89
90 state.sun_id = scene.world.get_id("sun");
91 state.earth_id = scene.world.get_id("earth");
92 state.moon_id = scene.world.get_id("moon");
93 scene.enable_editor_mode();
94 })
95 .on_update(|state, scene, ctx| {
96 // Rotate the Sun (the planet will orbit automatically)
97 if let Some(sun) = state.sun_id.and_then(|id| scene.world.get_mut(id)) {
98 sun.transform.rotation[1] += 30.0 * ctx.dt;
99 }
100
101 // Rotate the Planet (Earth)
102 if let Some(planet) = state.earth_id.and_then(|id| scene.world.get_mut(id)) {
103 planet.transform.rotation[1] += 100.0 * ctx.dt;
104 }
105 })
106 // on_editor_event fires whenever the editor's internal state changes:
107 // • T / R / E keys → GizmoModeChanged
108 // • Gizmo axis drag → DragStart / DragEnd
109 // on_update is suppressed in editor mode, so game logic here is safe.
110 .on_editor_event(|_state, _scene, event, object| {
111 match event {
112 EditorStateEvent::GizmoModeChanged(mode) => {
113 let label = match mode {
114 GizmoMode::Translate => "Translate",
115 GizmoMode::Rotate => "Rotate",
116 GizmoMode::Scale => "Scale",
117 };
118 println!("[Editor] Gizmo mode → {label} {object:?}");
119 }
120 EditorStateEvent::DragStart { axis } => {
121 let label = match axis {
122 DragAxis::X => "X",
123 DragAxis::Y => "Y",
124 DragAxis::Z => "Z",
125 };
126 println!("[Editor] Drag started — axis: {label} {object:?}");
127 }
128 EditorStateEvent::DragEnd => {
129 println!("[Editor] Drag ended {object:?}");
130 }
131 EditorStateEvent::SelectionChanged => {
132 println!("[Editor] Selection changed {object:?}");
133 }
134 }
135 })
136 .create();
137}Sourcepub fn rename_str_id(&mut self, id: usize, new_str_id: String) -> bool
pub fn rename_str_id(&mut self, id: usize, new_str_id: String) -> bool
Rename the stable string identifier of a live object and keep the
internal name_handles cache in sync.
Always prefer this over writing to object.str_id directly when the
object is already inside a World. Direct field assignment bypasses the
cache and will silently break every subsequent World::get_id call for
the old or new identifier.
Returns false (no-op) when id does not exist.
pub fn delete(&mut self, id: usize)
Sourcepub fn reparent(&mut self, id: usize, new_parent: Option<usize>) -> bool
pub fn reparent(&mut self, id: usize, new_parent: Option<usize>) -> bool
Move id to a new parent (or to the scene root when new_parent is None).
§No-op conditions
iddoes not exist in the world.new_parentis the same as the current parent.new_parent == Some(id)(self-parenting).new_parentdoes not exist in the world (guards against dangling links).new_parentis a descendant ofid(would create a cycle).
The object’s children are carried along unchanged.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for World
impl !RefUnwindSafe for World
impl !Send for World
impl !Sync for World
impl Unpin for World
impl UnsafeUnpin for World
impl !UnwindSafe for World
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
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>
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>
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)
&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)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.