Open donmccurdy opened 1 month ago
tl;dr — I think we should perhaps stop trying to automatically flatten the scene graph in GLTFLoader (reversing some changes from #11944) and instead return a consistent mapping...
# gltf
- node: GLTF.Node
- mesh: GLTF.Mesh
- prim: GLTF.MeshPrimitive
- attribute: Record<string, GLTF.Accessor>
- material: GLTF.Material
...
# three.js
- node: THREE.Object3D
- mesh: THREE.Group
- prim: THREE.Mesh<BufferGeometry, Material>
...
... even if there's only one THREE.Mesh in a THREE.Group.
Description
In glTF's data model, we have:
Note that there is no distinct concept of a "geometry" here. Instead, we look for attributes (collections of named accessors) that happen to contain the same accessors, and cache them...
https://github.com/mrdoob/three.js/blob/09c38ab406fc42c8207559df983fb25766b591f6/examples/jsm/loaders/GLTFLoader.js#L2450-L2456
... so that if other primitives use the same attributes, they refer to the same BufferGeometry and we avoid a duplicate upload. If any attributes differ, the whole BufferGeometry must be duplicated (see #17089).
If (like the example above) there are multiple primitives in the mesh, we get this in three.js...
... and if there were only one primitive in the mesh, we'd drop the THREE.Group and try to "merge" the mesh and primitive concepts, which inherently could lose names or .userData.
I noticed today that:
The userData caching issue isn't urgent; I'm not aware that it's affecting users.
But relatedly (reported in #29753) if a glTF mesh has only one primitive, then GLTFLoader will collapse the primitive and the mesh into one THREE.Mesh object, and the mesh name appears nowhere in the resulting scene.
We could fix the .userData issue just by including .extras/userData in the cache key. May duplicate geometry and raise VRAM cost in rare cases.
To fix that and the missing mesh name issue, we would probably want to avoid 'flattening' the scene graph: when a mesh has only one primitive, still return a "Group>Mesh", not just a "Mesh", corresponding to the glTF "Mesh>Prim" pair. Then assign the primitive's .extras/userData to the Mesh, not the BufferGeometry. Arguably makes more sense than assigning .extras/userData to the Geometry, because a glTF primitive has a material and is uniquely mappable to a three.js Mesh, whereas we want to aggressively cache geometries for performance.
Reproduction steps
prim_extras_test.gltf
(attached)prim_extras_test.zip
The mesh's name is lost because we've flattened the scene graph slightly: if a mesh has more than one primitive, the mesh corresponds to a Group, if the mesh has only one primitive, we skip the Group. I think this might be too complex.
Code
The model used to test this issue was generated with the glTF Transform script below.
script.js
```javascript import { NodeIO, Document, Primitive } from '@gltf-transform/core'; const document = new Document(); const buffer = document.createBuffer(); const primA = createPointsPrim(document, buffer).setExtras({ data: 'PrimA' }); const primB = primA.clone().setExtras({ data: 'PrimB' }); const meshA = document.createMesh('MeshA').addPrimitive(primA); const meshB = document.createMesh('MeshB').addPrimitive(primB); const nodeA = document.createNode('NodeA').setMesh(meshA).setTranslation([0, 0, 0]); const nodeB = document.createNode('NodeB').setMesh(meshB).setTranslation([0, 0, 1]); const scene = document.createScene().addChild(nodeA).addChild(nodeB); document.getRoot().setDefaultScene(scene); const io = new NodeIO(); await io.write('./prim_extras_test.gltf', document); function createPointsPrim(document, buffer) { const position = document .createAccessor() .setType('VEC3') .setBuffer(buffer) .setArray( // prettier-ignore new Float32Array([ 0, 0, 0, // ax,ay,az 0, 0, 1, // bx,by,bz 0, 1, 0, // ... 1, 0, 0, ]), ); const color = document .createAccessor() .setType('VEC4') .setBuffer(buffer) .setNormalized(true) .setArray( // prettier-ignore new Uint8Array([ 0, 0, 0, 255, 0, 0, 255, 255, 0, 255, 0, 255, 255, 0, 0, 255, ]), ); return document .createPrimitive() .setMode(Primitive.Mode.POINTS) .setAttribute('POSITION', position) .setAttribute('COLOR_0', color); } ```Live example
Open the model attached above in https://threejs.org/editor/.
Screenshots
No response
Version
r168
Device
Desktop, Mobile, Headset
Browser
Chrome, Firefox, Safari, Edge
OS
Windows, MacOS, Linux, ChromeOS, Android, iOS