Premier commit du cinquième front. Trois parties : - `unity/MyInfoMateVR/` : le projet Unity (6000.0.83f1, URP, Meta XR SDK 205), un APK unique pour tous les clients. Menu flottant à sélection au regard, appairage, chargement de scène GLB, POI, cache de contenu, télémétrie. - `unity-overlay/` : les mêmes scripts à recopier sur un projet Unity neuf, avec les pièges rencontrés consignés dans son README. - `viewer/` : viewer et éditeur de scène web autonome (Vite, TypeScript, three.js), partagé avec les autres fronts. - `docs/` : état des lieux, setup Unity, décisions d'architecture et plan d'exécution en 9 étapes. La scène est décrite par un `scene.json` poussé par `adb push` : l'app le préfère à celui embarqué dans l'APK. Les binaires (GLB, textures de l'échantillon Sponza, DLL Meta XR) passent par Git LFS dès ce premier commit — les y faire entrer après coup demanderait de réécrire l'historique. Les artefacts régénérés par l'éditeur et par CMake (`Library/`, `.utmp/`, Burst debug) sont ignorés. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
136 lines
4.9 KiB
Python
136 lines
4.9 KiB
Python
"""Génère calibration.glb — le repère de calibration de l'étape E1.
|
|
|
|
Trois branches de longueurs DIFFÉRENTES, aux couleurs de la convention habituelle :
|
|
+X rouge (1.00 m) +Y vert (0.60 m) +Z bleu (0.30 m)
|
|
|
|
Pourquoi trois longueurs différentes : un repère symétrique ne permet pas de voir
|
|
une inversion d'axe. Ici, si la conversion glTF -> Unity se trompe de signe, la
|
|
branche rouge part du mauvais côté par rapport à la bleue, et ça se voit sans
|
|
mesurer.
|
|
|
|
Le modèle est écrit en coordonnées glTF (main droite, Y-up, mètres) — c'est-à-dire
|
|
exactement la convention du manifeste. C'est ce qui en fait un test valable :
|
|
l'app place un marqueur AUX MÊMES coordonnées, sans passer par l'importeur.
|
|
|
|
Usage : python3 make_calibration_glb.py [chemin/de/sortie.glb]
|
|
"""
|
|
|
|
import base64
|
|
import json
|
|
import struct
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
AXES = [
|
|
# (nom, dimensions du pavé, couleur RGBA)
|
|
("AxisX", (1.00, 0.04, 0.04), (0.85, 0.15, 0.15, 1.0)),
|
|
("AxisY", (0.04, 0.60, 0.04), (0.15, 0.75, 0.20, 1.0)),
|
|
("AxisZ", (0.04, 0.04, 0.30), (0.15, 0.35, 0.90, 1.0)),
|
|
]
|
|
|
|
FACES = [
|
|
((0, 0, 1), [(0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)]),
|
|
((0, 0, -1), [(1, 0, 0), (0, 0, 0), (0, 1, 0), (1, 1, 0)]),
|
|
((1, 0, 0), [(1, 0, 1), (1, 0, 0), (1, 1, 0), (1, 1, 1)]),
|
|
((-1, 0, 0), [(0, 0, 0), (0, 0, 1), (0, 1, 1), (0, 1, 0)]),
|
|
((0, 1, 0), [(0, 1, 1), (1, 1, 1), (1, 1, 0), (0, 1, 0)]),
|
|
((0, -1, 0), [(0, 0, 0), (1, 0, 0), (1, 0, 1), (0, 0, 1)]),
|
|
]
|
|
|
|
|
|
def box(size):
|
|
"""Pavé dont un coin est à l'origine et qui s'étend vers +X, +Y, +Z."""
|
|
positions, normals, indices = [], [], []
|
|
for normal, corners in FACES:
|
|
base = len(positions)
|
|
for cx, cy, cz in corners:
|
|
positions.append((cx * size[0], cy * size[1], cz * size[2]))
|
|
normals.append(normal)
|
|
indices += [base, base + 1, base + 2, base, base + 2, base + 3]
|
|
return positions, normals, indices
|
|
|
|
|
|
def main(out_path):
|
|
buffer = bytearray()
|
|
buffer_views, accessors, meshes, materials, nodes = [], [], [], [], []
|
|
|
|
def add_view(data, target):
|
|
while len(buffer) % 4:
|
|
buffer.append(0)
|
|
offset = len(buffer)
|
|
buffer.extend(data)
|
|
buffer_views.append(
|
|
{"buffer": 0, "byteOffset": offset, "byteLength": len(data), "target": target}
|
|
)
|
|
return len(buffer_views) - 1
|
|
|
|
for name, size, color in AXES:
|
|
positions, normals, indices = box(size)
|
|
|
|
pos_view = add_view(b"".join(struct.pack("<3f", *p) for p in positions), 34962)
|
|
nrm_view = add_view(b"".join(struct.pack("<3f", *n) for n in normals), 34962)
|
|
idx_view = add_view(b"".join(struct.pack("<H", i) for i in indices), 34963)
|
|
|
|
accessors.append({
|
|
"bufferView": pos_view, "componentType": 5126, "count": len(positions),
|
|
"type": "VEC3",
|
|
"min": [min(p[i] for p in positions) for i in range(3)],
|
|
"max": [max(p[i] for p in positions) for i in range(3)],
|
|
})
|
|
accessors.append({
|
|
"bufferView": nrm_view, "componentType": 5126, "count": len(normals),
|
|
"type": "VEC3",
|
|
})
|
|
accessors.append({
|
|
"bufferView": idx_view, "componentType": 5123, "count": len(indices),
|
|
"type": "SCALAR",
|
|
})
|
|
|
|
base = len(accessors) - 3
|
|
materials.append({
|
|
"name": name + "Mat",
|
|
"pbrMetallicRoughness": {
|
|
"baseColorFactor": list(color), "metallicFactor": 0.0, "roughnessFactor": 0.8,
|
|
},
|
|
})
|
|
meshes.append({
|
|
"name": name,
|
|
"primitives": [{
|
|
"attributes": {"POSITION": base, "NORMAL": base + 1},
|
|
"indices": base + 2,
|
|
"material": len(materials) - 1,
|
|
}],
|
|
})
|
|
nodes.append({"name": name, "mesh": len(meshes) - 1})
|
|
|
|
gltf = {
|
|
"asset": {
|
|
"version": "2.0",
|
|
"generator": "MyInfoMate VR — make_calibration_glb.py",
|
|
},
|
|
"scene": 0,
|
|
"scenes": [{"name": "Calibration", "nodes": list(range(len(nodes)))}],
|
|
"nodes": nodes,
|
|
"meshes": meshes,
|
|
"materials": materials,
|
|
"accessors": accessors,
|
|
"bufferViews": buffer_views,
|
|
"buffers": [{"byteLength": len(buffer)}],
|
|
}
|
|
|
|
json_chunk = json.dumps(gltf, separators=(",", ":")).encode("utf-8")
|
|
json_chunk += b" " * (-len(json_chunk) % 4)
|
|
bin_chunk = bytes(buffer) + b"\0" * (-len(buffer) % 4)
|
|
|
|
glb = struct.pack("<III", 0x46546C67, 2, 12 + 8 + len(json_chunk) + 8 + len(bin_chunk))
|
|
glb += struct.pack("<II", len(json_chunk), 0x4E4F534A) + json_chunk
|
|
glb += struct.pack("<II", len(bin_chunk), 0x004E4942) + bin_chunk
|
|
|
|
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
|
|
Path(out_path).write_bytes(glb)
|
|
print(f"{out_path} — {len(glb)} octets, {len(nodes)} branches")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1] if len(sys.argv) > 1 else "calibration.glb")
|