#!/usr/bin/env python3 """Generate a small self-contained textured cube GLB (web/Cube.glb). Uses the tracked Cube assets from models/ (Cube.gltf geometry in Cube.bin) plus a tiny checkerboard PNG generated with only the standard library, so the demo ships without large binary assets. """ import base64 import json import struct import sys import zlib from pathlib import Path ROOT = Path(__file__).resolve().parent.parent BIN = ROOT / "models" / "Cube" / "Cube.bin" OUT = Path(__file__).resolve().parent / "Cube.glb" def make_png(width=64, height=64, rgb=(230, 140, 60), rgb2=(245, 245, 245)): """Minimal zlib-based RGB PNG writer.""" def chunk(tag, data): c = tag + data return struct.pack(">I", len(data)) + c + struct.pack(">I", zlib.crc32(c) & 0xFFFFFFFF) ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0) # 8-bit RGB raw = bytearray() for y in range(height): raw.append(0) # filter: none for x in range(width): c = rgb if (x // 8 + y // 8) % 2 == 0 else rgb2 raw += bytes(c) return ( b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(bytes(raw), 9)) + chunk(b"IEND", b"") ) def main(): bin_data = BIN.read_bytes() png = make_png() total = len(bin_data) + len(png) j = { "asset": {"version": "2.0", "generator": "tinygltf web demo (gen_sample.py)"}, "scene": 0, "scenes": [{"nodes": [0]}], "nodes": [{"mesh": 0, "name": "Cube"}], "meshes": [{ "primitives": [{ "attributes": { "POSITION": 1, "NORMAL": 2, "TANGENT": 3, "TEXCOORD_0": 4, }, "indices": 0, "material": 0, }] }], "materials": [{ "name": "checker", "pbrMetallicRoughness": { "baseColorFactor": [1.0, 1.0, 1.0, 1.0], "baseColorTexture": {"index": 0}, "metallicFactor": 0.0, "roughnessFactor": 1.0, }, }], "textures": [{"source": 0, "sampler": 0}], "images": [{"bufferView": 5, "mimeType": "image/png"}], "samplers": [{"magFilter": 9729, "minFilter": 9987, "wrapS": 10497, "wrapT": 10497}], "buffers": [{"byteLength": total}], "bufferViews": [ {"buffer": 0, "byteOffset": 0, "byteLength": 72, "target": 34963}, # indices {"buffer": 0, "byteOffset": 72, "byteLength": 432, "target": 34962}, # positions {"buffer": 0, "byteOffset": 504, "byteLength": 432, "target": 34962}, # normals {"buffer": 0, "byteOffset": 936, "byteLength": 576, "target": 34962}, # tangents {"buffer": 0, "byteOffset": 1512, "byteLength": 288, "target": 34962}, # uvs {"buffer": 0, "byteOffset": len(bin_data), "byteLength": len(png)}, # image ], "accessors": [ {"bufferView": 0, "componentType": 5123, "count": 36, "type": "SCALAR", "min": [0], "max": [35]}, {"bufferView": 1, "componentType": 5126, "count": 36, "type": "VEC3", "min": [-1, -1, -1], "max": [1, 1, 1]}, {"bufferView": 2, "componentType": 5126, "count": 36, "type": "VEC3"}, {"bufferView": 3, "componentType": 5126, "count": 36, "type": "VEC4"}, {"bufferView": 4, "componentType": 5126, "count": 36, "type": "VEC2"}, ], } json_chunk = json.dumps(j, separators=(",", ":")).encode("utf-8") json_chunk += b" " * ((4 - len(json_chunk) % 4) % 4) bin_chunk = bin_data + png bin_chunk += b"\x00" * ((4 - len(bin_chunk) % 4) % 4) total_len = 12 + 8 + len(json_chunk) + 8 + len(bin_chunk) glb = struct.pack("<4sII", b"glTF", 2, total_len) glb += struct.pack("