Skip to main content

tmx/
glb.rs

1use serde_json::{json, Value};
2
3#[derive(Clone)]
4pub struct Prim {
5    pub positions: Vec<[f32; 3]>,
6    pub normals: Option<Vec<[f32; 3]>>,
7    pub uvs: Option<Vec<[f32; 2]>>,
8    pub indices: Vec<u32>,
9    pub jpeg: Option<Vec<u8>>,
10}
11
12#[derive(Default)]
13struct Bin {
14    data: Vec<u8>,
15    views: Vec<Value>,
16}
17
18impl Bin {
19    fn view(&mut self, bytes: &[u8], target: Option<u32>) -> usize {
20        while self.data.len() % 4 != 0 {
21            self.data.push(0);
22        }
23        let offset = self.data.len();
24        self.data.extend_from_slice(bytes);
25        let mut v = json!({ "buffer": 0, "byteOffset": offset, "byteLength": bytes.len() });
26        if let Some(t) = target {
27            v["target"] = json!(t);
28        }
29        self.views.push(v);
30        self.views.len() - 1
31    }
32}
33
34fn f32_bytes(v: &[f32]) -> Vec<u8> {
35    let mut b = Vec::with_capacity(v.len() * 4);
36    for x in v {
37        b.extend_from_slice(&x.to_le_bytes());
38    }
39    b
40}
41
42pub fn write(prims: &[Prim]) -> Vec<u8> {
43    let mut bin = Bin::default();
44    let mut accessors: Vec<Value> = Vec::new();
45    let mut images: Vec<Value> = Vec::new();
46    let mut textures: Vec<Value> = Vec::new();
47    let mut materials: Vec<Value> = Vec::new();
48    let mut gltf_prims: Vec<Value> = Vec::new();
49
50    for p in prims {
51        if p.positions.is_empty() || p.indices.is_empty() {
52            continue;
53        }
54
55        let idx_bytes: Vec<u8> = p.indices.iter().flat_map(|i| i.to_le_bytes()).collect();
56        let idx_view = bin.view(&idx_bytes, Some(34963)); // ELEMENT_ARRAY_BUFFER
57        let idx_acc = accessors.len();
58        accessors.push(json!({
59            "bufferView": idx_view, "componentType": 5125, // UNSIGNED_INT
60            "count": p.indices.len(), "type": "SCALAR",
61        }));
62
63        let mut min = [f32::INFINITY; 3];
64        let mut max = [f32::NEG_INFINITY; 3];
65        for v in &p.positions {
66            for k in 0..3 {
67                min[k] = min[k].min(v[k]);
68                max[k] = max[k].max(v[k]);
69            }
70        }
71        let pos_flat: Vec<f32> = p.positions.iter().flatten().copied().collect();
72        let pos_view = bin.view(&f32_bytes(&pos_flat), Some(34962)); // ARRAY_BUFFER
73        let pos_acc = accessors.len();
74        accessors.push(json!({
75            "bufferView": pos_view, "componentType": 5126, // FLOAT
76            "count": p.positions.len(), "type": "VEC3", "min": min, "max": max,
77        }));
78
79        let mut attributes = json!({ "POSITION": pos_acc });
80
81        if let Some(uvs) = &p.uvs {
82            let flat: Vec<f32> = uvs.iter().flatten().copied().collect();
83            let view = bin.view(&f32_bytes(&flat), Some(34962));
84            attributes["TEXCOORD_0"] = json!(accessors.len());
85            accessors.push(json!({
86                "bufferView": view, "componentType": 5126, "count": uvs.len(), "type": "VEC2",
87            }));
88        }
89
90        let mut prim = json!({ "attributes": attributes, "indices": idx_acc });
91
92        if let Some(jpeg) = &p.jpeg {
93            let view = bin.view(jpeg, None);
94            let img = images.len();
95            images.push(json!({ "bufferView": view, "mimeType": "image/jpeg" }));
96            let tex = textures.len();
97            textures.push(json!({ "source": img, "sampler": 0 }));
98            let mat = materials.len();
99            materials.push(json!({
100                "pbrMetallicRoughness": { "baseColorTexture": { "index": tex }, "metallicFactor": 0.0, "roughnessFactor": 1.0 },
101                "extensions": { "KHR_materials_unlit": {} },
102                "doubleSided": true,
103            }));
104            prim["material"] = json!(mat);
105        }
106        gltf_prims.push(prim);
107    }
108
109    let mut gltf = json!({
110        "asset": { "version": "2.0", "generator": "mxmxmx2tiles" },
111        "scene": 0,
112        "scenes": [ { "nodes": [0] } ],
113        "nodes": [ { "mesh": 0 } ],
114        "meshes": [ { "primitives": gltf_prims } ],
115        "accessors": accessors,
116        "bufferViews": bin.views,
117        "buffers": [ { "byteLength": bin.data.len() } ],
118    });
119    if !images.is_empty() {
120        gltf["images"] = json!(images);
121        gltf["textures"] = json!(textures);
122        gltf["materials"] = json!(materials);
123        // CLAMP_TO_EDGE, LINEAR_MIPMAP_LINEAR, LINEAR
124        gltf["samplers"] = json!([ { "wrapS": 33071, "wrapT": 33071, "minFilter": 9987, "magFilter": 9729 } ]);
125        gltf["extensionsUsed"] = json!(["KHR_materials_unlit"]);
126    }
127
128    pack_glb(&gltf, &bin.data)
129}
130
131pub(crate) fn pack_glb(gltf: &Value, bin: &[u8]) -> Vec<u8> {
132    let mut json_chunk = serde_json::to_vec(gltf).unwrap();
133    while json_chunk.len() % 4 != 0 {
134        json_chunk.push(b' ');
135    }
136    let mut bin_chunk = bin.to_vec();
137    while bin_chunk.len() % 4 != 0 {
138        bin_chunk.push(0);
139    }
140    let total = 12 + 8 + json_chunk.len() + 8 + bin_chunk.len();
141    let mut out = Vec::with_capacity(total);
142    out.extend_from_slice(&0x46546C67u32.to_le_bytes()); // "glTF"
143    out.extend_from_slice(&2u32.to_le_bytes());
144    out.extend_from_slice(&(total as u32).to_le_bytes());
145    out.extend_from_slice(&(json_chunk.len() as u32).to_le_bytes());
146    out.extend_from_slice(&0x4E4F534Au32.to_le_bytes()); // "JSON"
147    out.extend_from_slice(&json_chunk);
148    out.extend_from_slice(&(bin_chunk.len() as u32).to_le_bytes());
149    out.extend_from_slice(&0x004E4942u32.to_le_bytes()); // "BIN\0"
150    out.extend_from_slice(&bin_chunk);
151    out
152}