Skip to main content

vtr_roundtrip/
vtr_roundtrip.rs

1//! # VTR Round-trip
2//!
3//! A **headless** (no window) example that exercises the VTR binary scene
4//! format end-to-end:
5//!
6//! 1. Build a small scene (camera + three objects) in memory.
7//! 2. Serialize it to a `Vec<u8>` using [`vertra::vtr::write`].
8//! 3. Peek at the header with [`vertra::vtr::read_header`].
9//! 4. Deserialize back with [`vertra::vtr::read`].
10//! 5. Assert that the round-tripped scene matches the original.
11//!
12//! The same `write` / `read` functions back [`Scene::save_vtr_file`] and
13//! [`Scene::load_vtr_file`], so this example demonstrates exactly what those
14//! convenience helpers do internally.
15//!
16//! **Run:**
17//! ```sh
18//! cargo run --example vtr_roundtrip
19//! ```
20
21use std::io::Cursor;
22
23use vertra::camera::Camera;
24use vertra::geometry::Geometry;
25use vertra::objects::Object;
26use vertra::transform::Transform;
27use vertra::vtr;
28use vertra::world::World;
29
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}
149