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)); let idx_acc = accessors.len();
58 accessors.push(json!({
59 "bufferView": idx_view, "componentType": 5125, "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)); let pos_acc = accessors.len();
74 accessors.push(json!({
75 "bufferView": pos_view, "componentType": 5126, "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 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()); 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()); 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()); out.extend_from_slice(&bin_chunk);
151 out
152}