@expofp/renderer 3.45.7 → 3.45.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -32,6 +32,12 @@ export interface ModelAsset {
|
|
|
32
32
|
* the merge happen at all. Then it is filed under its material, sub-keyed by {@link mergeSignature},
|
|
33
33
|
* because `mergeGeometries` requires its inputs to agree about their attributes.
|
|
34
34
|
*
|
|
35
|
+
* Two shapes compressors emit are unpacked on the way in. An `EXT_mesh_gpu_instancing` node (an
|
|
36
|
+
* `InstancedMesh`, from gltfpack `-mi`) is baked once per instance, since the def is our only
|
|
37
|
+
* instanced unit; its per-instance colors and custom attributes are dropped with a warning. Quantized
|
|
38
|
+
* attributes (`KHR_mesh_quantization`, from gltfpack) are widened to floats first — see
|
|
39
|
+
* {@link toFloatGeometry}.
|
|
40
|
+
*
|
|
35
41
|
* **Nothing here knows about the plan.** The geometry stays in the model's own space, glTF's y-up,
|
|
36
42
|
* and the mapping onto the plan is a transform the *mesh* carries — see `ModelContainer`. That is
|
|
37
43
|
* not a tidiness argument: a mapping baked into vertices is one three cannot see when it decides
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box3, Mesh } from 'three';
|
|
1
|
+
import { Box3, BufferAttribute, InstancedMesh, Matrix4, Mesh, } from 'three';
|
|
2
2
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
3
3
|
import { createLogger } from '../../utils/logging';
|
|
4
4
|
const logger = createLogger('model');
|
|
@@ -10,6 +10,12 @@ const logger = createLogger('model');
|
|
|
10
10
|
* the merge happen at all. Then it is filed under its material, sub-keyed by {@link mergeSignature},
|
|
11
11
|
* because `mergeGeometries` requires its inputs to agree about their attributes.
|
|
12
12
|
*
|
|
13
|
+
* Two shapes compressors emit are unpacked on the way in. An `EXT_mesh_gpu_instancing` node (an
|
|
14
|
+
* `InstancedMesh`, from gltfpack `-mi`) is baked once per instance, since the def is our only
|
|
15
|
+
* instanced unit; its per-instance colors and custom attributes are dropped with a warning. Quantized
|
|
16
|
+
* attributes (`KHR_mesh_quantization`, from gltfpack) are widened to floats first — see
|
|
17
|
+
* {@link toFloatGeometry}.
|
|
18
|
+
*
|
|
13
19
|
* **Nothing here knows about the plan.** The geometry stays in the model's own space, glTF's y-up,
|
|
14
20
|
* and the mapping onto the plan is a transform the *mesh* carries — see `ModelContainer`. That is
|
|
15
21
|
* not a tidiness argument: a mapping baked into vertices is one three cannot see when it decides
|
|
@@ -38,16 +44,36 @@ export function bakeGltf(scene, url) {
|
|
|
38
44
|
const material = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
|
|
39
45
|
if (!material)
|
|
40
46
|
return;
|
|
41
|
-
const
|
|
42
|
-
baked.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
const source = toFloatGeometry(mesh.geometry);
|
|
48
|
+
// Per-instance attributes three parked on the shared geometry; each baked copy sheds them.
|
|
49
|
+
const dropped = mesh instanceof InstancedMesh
|
|
50
|
+
? Object.keys(source.attributes).filter((name) => name.startsWith('_'))
|
|
51
|
+
: [];
|
|
52
|
+
const bake = (matrix) => {
|
|
53
|
+
const baked = source.clone();
|
|
54
|
+
for (const name of dropped)
|
|
55
|
+
baked.deleteAttribute(name);
|
|
56
|
+
baked.applyMatrix4(matrix);
|
|
57
|
+
baked.computeBoundingBox();
|
|
58
|
+
if (baked.boundingBox)
|
|
59
|
+
extents.union(baked.boundingBox);
|
|
60
|
+
const signature = mergeSignature(baked);
|
|
61
|
+
const id = `${material.uuid}|${signature}`;
|
|
62
|
+
const bucket = buckets.get(id) ?? { material, signature, geometries: [] };
|
|
63
|
+
bucket.geometries.push(baked);
|
|
64
|
+
buckets.set(id, bucket);
|
|
65
|
+
};
|
|
66
|
+
if (!(mesh instanceof InstancedMesh)) {
|
|
67
|
+
bake(mesh.matrixWorld);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const ignored = mesh.instanceColor ? [...dropped, '_COLOR_0'] : dropped;
|
|
71
|
+
if (ignored.length > 0) {
|
|
72
|
+
logger.warn('model %s: ignored per-instance attributes %s on mesh "%s"', url, ignored.join(', '), mesh.name);
|
|
73
|
+
}
|
|
74
|
+
for (let i = 0; i < mesh.count; i++) {
|
|
75
|
+
bake(_matrix.multiplyMatrices(mesh.matrixWorld, mesh.getMatrixAt(i, _instanceMatrix)));
|
|
76
|
+
}
|
|
51
77
|
});
|
|
52
78
|
const parts = [];
|
|
53
79
|
for (const [id, { material, signature, geometries }] of buckets) {
|
|
@@ -75,3 +101,44 @@ function mergeSignature(geometry) {
|
|
|
75
101
|
const indexed = geometry.getIndex() ? 'indexed' : 'non-indexed';
|
|
76
102
|
return `${indexed} [${Object.keys(geometry.attributes).sort().join(', ')}]`;
|
|
77
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* A copy of the geometry whose every attribute is a plain, non-normalized `Float32Array`.
|
|
106
|
+
*
|
|
107
|
+
* `KHR_mesh_quantization` stores attributes as (possibly normalized) integers, with the scale moved
|
|
108
|
+
* into the node transform. `applyMatrix4` writes its result back into the same array, so baking a
|
|
109
|
+
* world transform into an `Int16Array` rounds every coordinate to a whole unit. Widening first also
|
|
110
|
+
* gives `mergeGeometries` matching array types, which it requires but {@link mergeSignature} does not
|
|
111
|
+
* check. A geometry that is already all floats is returned as is, uncopied: the caller clones it.
|
|
112
|
+
* @param geometry - The geometry to widen; left untouched.
|
|
113
|
+
* @returns The geometry itself, or a widened copy.
|
|
114
|
+
*/
|
|
115
|
+
function toFloatGeometry(geometry) {
|
|
116
|
+
const narrow = Object.entries(geometry.attributes).filter(([, a]) => !isPlainFloat(a));
|
|
117
|
+
if (narrow.length === 0)
|
|
118
|
+
return geometry;
|
|
119
|
+
const widened = geometry.clone();
|
|
120
|
+
for (const [name, attribute] of narrow) {
|
|
121
|
+
const { count, itemSize } = attribute;
|
|
122
|
+
const array = new Float32Array(count * itemSize);
|
|
123
|
+
for (let i = 0; i < count; i++) {
|
|
124
|
+
for (let component = 0; component < itemSize; component++) {
|
|
125
|
+
// `getComponent` denormalizes, and reads through an interleaved buffer's stride.
|
|
126
|
+
array[i * itemSize + component] = attribute.getComponent(i, component);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
widened.setAttribute(name, new BufferAttribute(array, itemSize));
|
|
130
|
+
}
|
|
131
|
+
return widened;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Whether an attribute is already what {@link toFloatGeometry} produces.
|
|
135
|
+
* @param attribute - The attribute to test.
|
|
136
|
+
* @returns `true` for a plain, non-normalized `Float32Array` attribute.
|
|
137
|
+
*/
|
|
138
|
+
function isPlainFloat(attribute) {
|
|
139
|
+
return (attribute instanceof BufferAttribute &&
|
|
140
|
+
attribute.array instanceof Float32Array &&
|
|
141
|
+
!attribute.normalized);
|
|
142
|
+
}
|
|
143
|
+
const _matrix = new Matrix4();
|
|
144
|
+
const _instanceMatrix = new Matrix4();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expofp/renderer",
|
|
3
|
-
"version": "3.45.
|
|
3
|
+
"version": "3.45.9",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/expofp/efp-app.git",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"troika-three-text": "^0.52.5",
|
|
43
43
|
"tslib": "^2.3.0",
|
|
44
44
|
"webgl-memory": "^1.1.3",
|
|
45
|
-
"@expofp/geometry": "3.45.
|
|
45
|
+
"@expofp/geometry": "3.45.9"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@react-three/test-renderer": "^9.1.0",
|