| 1 | #!/usr/bin/env python3 |
| 2 | """CC0 regression fixture: a skinned glTF body with an external .bin, three |
| 3 | external PNG textures and three materials (factor only, texture, texture x |
| 4 | factor), one primitive per material. Three quads side by side facing +Z. |
| 5 | KHR_materials_specular: Green has specularFactor 0.5; YellowTimesFactor has |
| 6 | specularFactor 1 with an RGBA specular texture whose alpha is 102 (0.4). |
| 7 | |
| 8 | Writes tests/fixtures/bodies/multi-material/{body.gltf,body.bin,textures/*.png}. |
| 9 | """ |
| 10 | import json, os, struct, zlib |
| 11 | |
| 12 | OUT = os.path.join(os.path.dirname(__file__), "..", "tests", "fixtures", "bodies", "multi-material") |
| 13 | |
| 14 | |
| 15 | def png(path, w, h, pixel, color_type=2): |
| 16 | raw = b"".join(b"\0" + b"".join(bytes(pixel(x, y)) for x in range(w)) for y in range(h)) |
| 17 | def chunk(t, d): |
| 18 | return struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xFFFFFFFF) |
| 19 | data = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, color_type, 0, 0, 0)) |
| 20 | data += chunk(b"IDAT", zlib.compress(raw)) + chunk(b"IEND", b"") |
| 21 | with open(path, "wb") as f: |
| 22 | f.write(data) |
| 23 | |
| 24 | |
| 25 | def main(): |
| 26 | os.makedirs(os.path.join(OUT, "textures"), exist_ok=True) |
| 27 | # Texture A: solid green. Texture B: solid yellow (multiplied by factor 0.5,1,1). |
| 28 | png(os.path.join(OUT, "textures", "green_baseColor.png"), 4, 4, lambda x, y: (0, 200, 0)) |
| 29 | png(os.path.join(OUT, "textures", "yellow_baseColor.png"), 4, 4, lambda x, y: (200, 200, 0)) |
| 30 | png(os.path.join(OUT, "textures", "specular.png"), 4, 4, lambda x, y: (0, 0, 0, 102), color_type=6) |
| 31 | blob = b"" |
| 32 | views, accessors, prims = [], [], [] |
| 33 | |
| 34 | def add(data, target=None): |
| 35 | nonlocal blob |
| 36 | while len(blob) % 4: |
| 37 | blob += b"\0" |
| 38 | view = {"buffer": 0, "byteOffset": len(blob), "byteLength": len(data)} |
| 39 | if target: |
| 40 | view["target"] = target |
| 41 | views.append(view) |
| 42 | blob += data |
| 43 | return len(views) - 1 |
| 44 | |
| 45 | def accessor(view, ctype, count, typ, **kw): |
| 46 | accessors.append({"bufferView": view, "componentType": ctype, "count": count, "type": typ, **kw}) |
| 47 | return len(accessors) - 1 |
| 48 | |
| 49 | for i, x0 in enumerate([-0.75, -0.25, 0.25]): |
| 50 | x1 = x0 + 0.5 |
| 51 | pos = [(x0, 0.0, 0.0), (x1, 0.0, 0.0), (x1, 1.6, 0.0), (x0, 1.6, 0.0)] |
| 52 | p = accessor(add(b"".join(struct.pack("<3f", *v) for v in pos), 34962), 5126, 4, "VEC3", |
| 53 | min=[x0, 0.0, 0.0], max=[x1, 1.6, 0.0]) |
| 54 | n = accessor(add(struct.pack("<3f", 0, 0, 1) * 4, 34962), 5126, 4, "VEC3") |
| 55 | uv = accessor(add(b"".join(struct.pack("<2f", *t) for t in [(0, 1), (1, 1), (1, 0), (0, 0)]), 34962), 5126, 4, "VEC2") |
| 56 | j = accessor(add(struct.pack("<4H", 0, 0, 0, 0) * 4, 34962), 5123, 4, "VEC4") |
| 57 | w = accessor(add(struct.pack("<4f", 1, 0, 0, 0) * 4, 34962), 5126, 4, "VEC4") |
| 58 | idx = accessor(add(struct.pack("<6H", 0, 1, 2, 0, 2, 3), 34963), 5123, 6, "SCALAR") |
| 59 | prims.append({"attributes": {"POSITION": p, "NORMAL": n, "TEXCOORD_0": uv, "JOINTS_0": j, "WEIGHTS_0": w}, |
| 60 | "indices": idx, "material": i}) |
| 61 | ibm = accessor(add(struct.pack("<16f", 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)), 5126, 1, "MAT4") |
| 62 | times = accessor(add(struct.pack("<2f", 0.0, 1.0)), 5126, 2, "SCALAR", min=[0.0], max=[1.0]) |
| 63 | rots = accessor(add(struct.pack("<8f", 0, 0, 0, 1, 0, 0, 0, 1)), 5126, 2, "VEC4") |
| 64 | gltf = { |
| 65 | "extensionsUsed": ["KHR_materials_specular"], |
| 66 | "asset": {"version": "2.0", "generator": "paperdoll generate_multi_material_body.py (CC0)"}, |
| 67 | "extras": {"forward": "+Z"}, |
| 68 | "scene": 0, |
| 69 | "scenes": [{"nodes": [0, 1]}], |
| 70 | "nodes": [{"name": "Root"}, {"name": "Body", "mesh": 0, "skin": 0}], |
| 71 | "skins": [{"joints": [0], "inverseBindMatrices": ibm}], |
| 72 | "meshes": [{"name": "Body", "primitives": prims}], |
| 73 | "materials": [ |
| 74 | {"name": "FactorOnly", "pbrMetallicRoughness": {"baseColorFactor": [0.2, 0.4, 0.9, 1.0]}}, |
| 75 | {"name": "Green", "pbrMetallicRoughness": {"baseColorTexture": {"index": 0}}, |
| 76 | "extensions": {"KHR_materials_specular": {"specularFactor": 0.5}}}, |
| 77 | {"name": "YellowTimesFactor", "pbrMetallicRoughness": {"baseColorTexture": {"index": 1}, |
| 78 | "baseColorFactor": [0.5, 1.0, 1.0, 1.0]}, |
| 79 | "extensions": {"KHR_materials_specular": {"specularTexture": {"index": 2}}}}, |
| 80 | ], |
| 81 | "textures": [{"sampler": 0, "source": 0}, {"sampler": 0, "source": 1}, {"sampler": 0, "source": 2}], |
| 82 | "samplers": [{"magFilter": 9728, "minFilter": 9728}], |
| 83 | "images": [{"uri": "textures/green_baseColor.png"}, {"uri": "textures/yellow_baseColor.png"}, |
| 84 | {"uri": "textures/specular.png"}], |
| 85 | "animations": [{"name": "idle", "samplers": [{"input": times, "output": rots}], |
| 86 | "channels": [{"sampler": 0, "target": {"node": 0, "path": "rotation"}}]}], |
| 87 | "buffers": [{"uri": "body.bin", "byteLength": len(blob)}], |
| 88 | "bufferViews": views, |
| 89 | "accessors": accessors, |
| 90 | } |
| 91 | with open(os.path.join(OUT, "body.bin"), "wb") as f: |
| 92 | f.write(blob) |
| 93 | with open(os.path.join(OUT, "body.gltf"), "w") as f: |
| 94 | json.dump(gltf, f, indent=1) |
| 95 | |
| 96 | |
| 97 | if __name__ == "__main__": |
| 98 | main() |