@babylonjs/loaders 9.11.0 → 9.12.1
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.
- package/FBX/fbxFileLoader.d.ts +194 -0
- package/FBX/fbxFileLoader.js +2440 -0
- package/FBX/fbxFileLoader.js.map +1 -0
- package/FBX/fbxFileLoader.metadata.d.ts +11 -0
- package/FBX/fbxFileLoader.metadata.js +11 -0
- package/FBX/fbxFileLoader.metadata.js.map +1 -0
- package/FBX/index.d.ts +3 -0
- package/FBX/index.js +3 -0
- package/FBX/index.js.map +1 -0
- package/FBX/interpreter/animation.d.ts +122 -0
- package/FBX/interpreter/animation.js +648 -0
- package/FBX/interpreter/animation.js.map +1 -0
- package/FBX/interpreter/blendShapes.d.ts +44 -0
- package/FBX/interpreter/blendShapes.js +192 -0
- package/FBX/interpreter/blendShapes.js.map +1 -0
- package/FBX/interpreter/connections.d.ts +95 -0
- package/FBX/interpreter/connections.js +233 -0
- package/FBX/interpreter/connections.js.map +1 -0
- package/FBX/interpreter/fbxInterpreter.d.ts +149 -0
- package/FBX/interpreter/fbxInterpreter.js +496 -0
- package/FBX/interpreter/fbxInterpreter.js.map +1 -0
- package/FBX/interpreter/geometry.d.ts +55 -0
- package/FBX/interpreter/geometry.js +573 -0
- package/FBX/interpreter/geometry.js.map +1 -0
- package/FBX/interpreter/materials.d.ts +50 -0
- package/FBX/interpreter/materials.js +144 -0
- package/FBX/interpreter/materials.js.map +1 -0
- package/FBX/interpreter/propertyTemplates.d.ts +22 -0
- package/FBX/interpreter/propertyTemplates.js +125 -0
- package/FBX/interpreter/propertyTemplates.js.map +1 -0
- package/FBX/interpreter/rig.d.ts +20 -0
- package/FBX/interpreter/rig.js +259 -0
- package/FBX/interpreter/rig.js.map +1 -0
- package/FBX/interpreter/sceneDiagnostics.d.ts +14 -0
- package/FBX/interpreter/sceneDiagnostics.js +55 -0
- package/FBX/interpreter/sceneDiagnostics.js.map +1 -0
- package/FBX/interpreter/skeleton.d.ts +93 -0
- package/FBX/interpreter/skeleton.js +515 -0
- package/FBX/interpreter/skeleton.js.map +1 -0
- package/FBX/interpreter/transform.d.ts +21 -0
- package/FBX/interpreter/transform.js +92 -0
- package/FBX/interpreter/transform.js.map +1 -0
- package/FBX/parsers/fbxAsciiParser.d.ts +5 -0
- package/FBX/parsers/fbxAsciiParser.js +330 -0
- package/FBX/parsers/fbxAsciiParser.js.map +1 -0
- package/FBX/parsers/fbxBinaryParser.d.ts +6 -0
- package/FBX/parsers/fbxBinaryParser.js +255 -0
- package/FBX/parsers/fbxBinaryParser.js.map +1 -0
- package/FBX/parsers/zlibInflate.d.ts +7 -0
- package/FBX/parsers/zlibInflate.js +350 -0
- package/FBX/parsers/zlibInflate.js.map +1 -0
- package/FBX/types/fbxTypes.d.ts +54 -0
- package/FBX/types/fbxTypes.js +66 -0
- package/FBX/types/fbxTypes.js.map +1 -0
- package/SPLAT/gaussianSplattingStream.d.ts +341 -0
- package/SPLAT/gaussianSplattingStream.js +976 -0
- package/SPLAT/gaussianSplattingStream.js.map +1 -0
- package/SPLAT/gaussianSplattingWorkBuffer.d.ts +51 -0
- package/SPLAT/gaussianSplattingWorkBuffer.js +159 -0
- package/SPLAT/gaussianSplattingWorkBuffer.js.map +1 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.d.ts +25 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.js +255 -0
- package/SPLAT/gaussianSplattingWorkBufferShaders.js.map +1 -0
- package/SPLAT/index.d.ts +1 -0
- package/SPLAT/index.js +1 -0
- package/SPLAT/index.js.map +1 -1
- package/SPLAT/sog.js +18 -16
- package/SPLAT/sog.js.map +1 -1
- package/SPLAT/splatFileLoader.d.ts +8 -0
- package/SPLAT/splatFileLoader.js +49 -0
- package/SPLAT/splatFileLoader.js.map +1 -1
- package/dynamic.js +9 -0
- package/dynamic.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention, jsdoc/require-param, jsdoc/require-returns */
|
|
2
|
+
import { findChildByName, findChildrenByName, getPropertyValue, cleanFBXName } from "../types/fbxTypes.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extract geometry data from an FBX Geometry node.
|
|
5
|
+
* Handles polygon triangulation and layer element expansion.
|
|
6
|
+
*/
|
|
7
|
+
export function extractGeometry(geometryNode, nodeId) {
|
|
8
|
+
const name = cleanFBXName(getPropertyValue(geometryNode, 1) ?? "Geometry");
|
|
9
|
+
// Extract raw vertices
|
|
10
|
+
const verticesNode = findChildByName(geometryNode, "Vertices");
|
|
11
|
+
if (!verticesNode) {
|
|
12
|
+
throw new Error(`Geometry '${name}' has no Vertices node`);
|
|
13
|
+
}
|
|
14
|
+
const rawPositions = toFloat64Array(getNodeArrayValue(verticesNode));
|
|
15
|
+
// Extract polygon vertex indices
|
|
16
|
+
const pviNode = findChildByName(geometryNode, "PolygonVertexIndex");
|
|
17
|
+
if (!pviNode) {
|
|
18
|
+
throw new Error(`Geometry '${name}' has no PolygonVertexIndex node`);
|
|
19
|
+
}
|
|
20
|
+
const rawIndices = toInt32Array(getNodeArrayValue(pviNode));
|
|
21
|
+
const diagnostics = [];
|
|
22
|
+
// Parse polygons from the FBX negative-index convention
|
|
23
|
+
const polygons = parsePolygons(rawIndices);
|
|
24
|
+
// Triangulate polygons while preserving polygon-vertex indices for layer data.
|
|
25
|
+
const triangles = triangulatePolygons(polygons, rawPositions, diagnostics);
|
|
26
|
+
// Build the list of polygon-vertex pairs for layer element expansion
|
|
27
|
+
const polyVertexList = buildPolygonVertexList(polygons);
|
|
28
|
+
// Extract normals
|
|
29
|
+
const normalNode = findChildByName(geometryNode, "LayerElementNormal");
|
|
30
|
+
let normals = null;
|
|
31
|
+
if (normalNode) {
|
|
32
|
+
normals = expandLayerElement(normalNode, "Normals", "NormalsIndex", polyVertexList, rawPositions.length / 3, 3, diagnostics);
|
|
33
|
+
}
|
|
34
|
+
// Extract all UV sets
|
|
35
|
+
const uvNodes = findChildrenByName(geometryNode, "LayerElementUV");
|
|
36
|
+
const uvSets = [];
|
|
37
|
+
for (const uvNode of uvNodes) {
|
|
38
|
+
const nameNode = findChildByName(uvNode, "Name");
|
|
39
|
+
const setName = nameNode ? (getPropertyValue(nameNode, 0) ?? `UVSet${uvSets.length}`) : `UVSet${uvSets.length}`;
|
|
40
|
+
const data = expandLayerElement(uvNode, "UV", "UVIndex", polyVertexList, rawPositions.length / 3, 2, diagnostics);
|
|
41
|
+
if (data) {
|
|
42
|
+
uvSets.push({ name: setName, data });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const uvs = uvSets.length > 0 ? uvSets[0].data : null;
|
|
46
|
+
// Extract vertex colors
|
|
47
|
+
const colorNode = findChildByName(geometryNode, "LayerElementColor");
|
|
48
|
+
let colors = null;
|
|
49
|
+
if (colorNode) {
|
|
50
|
+
const colorData = expandLayerElement(colorNode, "Colors", "ColorIndex", polyVertexList, rawPositions.length / 3, 4, diagnostics);
|
|
51
|
+
if (colorData) {
|
|
52
|
+
colors = new Float32Array(colorData.length);
|
|
53
|
+
for (let i = 0; i < colorData.length; i++) {
|
|
54
|
+
colors[i] = colorData[i];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const tangentNode = findChildByName(geometryNode, "LayerElementTangent");
|
|
59
|
+
const binormalNode = findChildByName(geometryNode, "LayerElementBinormal");
|
|
60
|
+
const binormals = binormalNode ? expandLayerElement(binormalNode, "Binormals", "BinormalsIndex", polyVertexList, rawPositions.length / 3, 3, diagnostics) : null;
|
|
61
|
+
const tangents = tangentNode ? expandTangentLayer(tangentNode, polyVertexList, rawPositions.length / 3, normals, binormals, diagnostics) : null;
|
|
62
|
+
// Extract per-polygon material indices
|
|
63
|
+
const matNode = findChildByName(geometryNode, "LayerElementMaterial");
|
|
64
|
+
let polyMaterialIndices = null;
|
|
65
|
+
if (matNode) {
|
|
66
|
+
polyMaterialIndices = extractMaterialIndices(matNode, polygons.length);
|
|
67
|
+
}
|
|
68
|
+
// Build final indexed mesh with expanded per-triangle-vertex attributes
|
|
69
|
+
const result = buildTriangleMesh(rawPositions, triangles, polyVertexList, normals, uvs, uvSets, colors, tangents, binormals);
|
|
70
|
+
// Expand per-polygon material indices to per-triangle
|
|
71
|
+
let materialIndices = null;
|
|
72
|
+
if (polyMaterialIndices) {
|
|
73
|
+
// Check if all polygons use the same material (optimization)
|
|
74
|
+
let allSame = true;
|
|
75
|
+
const firstMat = polyMaterialIndices[0];
|
|
76
|
+
for (let i = 1; i < polyMaterialIndices.length; i++) {
|
|
77
|
+
if (polyMaterialIndices[i] !== firstMat) {
|
|
78
|
+
allSame = false;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!allSame || firstMat !== 0) {
|
|
83
|
+
const triCount = result.indices.length / 3;
|
|
84
|
+
materialIndices = new Int32Array(triCount);
|
|
85
|
+
for (let ti = 0; ti < triangles.length; ti++) {
|
|
86
|
+
materialIndices[ti] = polyMaterialIndices[triangles[ti].polyIndex] ?? 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
id: nodeId,
|
|
92
|
+
name,
|
|
93
|
+
positions: result.positions,
|
|
94
|
+
indices: result.indices,
|
|
95
|
+
normals: result.normals,
|
|
96
|
+
uvs: result.uvs,
|
|
97
|
+
uvSets: result.uvSets,
|
|
98
|
+
colors: result.colors,
|
|
99
|
+
tangents: result.tangents,
|
|
100
|
+
binormals: result.binormals,
|
|
101
|
+
controlPointIndices: result.controlPointIndices,
|
|
102
|
+
materialIndices,
|
|
103
|
+
diagnostics,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function parsePolygons(rawIndices) {
|
|
107
|
+
const polygons = [];
|
|
108
|
+
let currentPoly = [];
|
|
109
|
+
let startIndex = 0;
|
|
110
|
+
for (let i = 0; i < rawIndices.length; i++) {
|
|
111
|
+
const idx = rawIndices[i];
|
|
112
|
+
if (idx < 0) {
|
|
113
|
+
// End of polygon: actual index is -(idx + 1)
|
|
114
|
+
currentPoly.push(-(idx + 1));
|
|
115
|
+
polygons.push({ indices: currentPoly, startIndex });
|
|
116
|
+
currentPoly = [];
|
|
117
|
+
startIndex = i + 1;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
currentPoly.push(idx);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return polygons;
|
|
124
|
+
}
|
|
125
|
+
function triangulatePolygons(polygons, rawPositions, diagnostics) {
|
|
126
|
+
const triangles = [];
|
|
127
|
+
for (let polyIndex = 0; polyIndex < polygons.length; polyIndex++) {
|
|
128
|
+
const poly = polygons[polyIndex];
|
|
129
|
+
triangles.push(...triangulatePolygon(poly, polyIndex, rawPositions, diagnostics));
|
|
130
|
+
}
|
|
131
|
+
return triangles;
|
|
132
|
+
}
|
|
133
|
+
function triangulatePolygon(poly, polyIndex, rawPositions, diagnostics) {
|
|
134
|
+
if (poly.indices.length < 3) {
|
|
135
|
+
diagnostics.push({
|
|
136
|
+
type: "degenerate-polygon",
|
|
137
|
+
message: `Polygon ${polyIndex} has fewer than three vertices.`,
|
|
138
|
+
polygonIndex: polyIndex,
|
|
139
|
+
});
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
if (poly.indices.length === 3) {
|
|
143
|
+
return [{ vertices: [poly.startIndex, poly.startIndex + 1, poly.startIndex + 2], polyIndex }];
|
|
144
|
+
}
|
|
145
|
+
const projected = projectPolygonTo2D(poly, rawPositions);
|
|
146
|
+
if (!projected) {
|
|
147
|
+
diagnostics.push({
|
|
148
|
+
type: "degenerate-polygon",
|
|
149
|
+
message: `Polygon ${polyIndex} has a near-zero normal; using fan triangulation.`,
|
|
150
|
+
polygonIndex: polyIndex,
|
|
151
|
+
});
|
|
152
|
+
return fanTriangulate(poly, polyIndex);
|
|
153
|
+
}
|
|
154
|
+
const polygonArea = signedArea2D(projected);
|
|
155
|
+
if (Math.abs(polygonArea) < 1e-12) {
|
|
156
|
+
diagnostics.push({
|
|
157
|
+
type: "degenerate-polygon",
|
|
158
|
+
message: `Polygon ${polyIndex} projects to near-zero area; using fan triangulation.`,
|
|
159
|
+
polygonIndex: polyIndex,
|
|
160
|
+
});
|
|
161
|
+
return fanTriangulate(poly, polyIndex);
|
|
162
|
+
}
|
|
163
|
+
const isCCW = polygonArea > 0;
|
|
164
|
+
const remaining = poly.indices.map((_, i) => i);
|
|
165
|
+
const clipped = [];
|
|
166
|
+
let guard = 0;
|
|
167
|
+
while (remaining.length > 3 && guard++ < poly.indices.length * poly.indices.length) {
|
|
168
|
+
let clippedEar = false;
|
|
169
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
170
|
+
const prev = remaining[(i + remaining.length - 1) % remaining.length];
|
|
171
|
+
const curr = remaining[i];
|
|
172
|
+
const next = remaining[(i + 1) % remaining.length];
|
|
173
|
+
if (!isConvex(projected[prev], projected[curr], projected[next], isCCW)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (containsAnyPoint(projected, remaining, prev, curr, next)) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
clipped.push({
|
|
180
|
+
vertices: [poly.startIndex + prev, poly.startIndex + curr, poly.startIndex + next],
|
|
181
|
+
polyIndex,
|
|
182
|
+
});
|
|
183
|
+
remaining.splice(i, 1);
|
|
184
|
+
clippedEar = true;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
if (!clippedEar) {
|
|
188
|
+
diagnostics.push({
|
|
189
|
+
type: "triangulation-fallback",
|
|
190
|
+
message: `Polygon ${polyIndex} could not be fully ear-clipped; using fan triangulation.`,
|
|
191
|
+
polygonIndex: polyIndex,
|
|
192
|
+
});
|
|
193
|
+
return fanTriangulate(poly, polyIndex);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
clipped.push({
|
|
197
|
+
vertices: [poly.startIndex + remaining[0], poly.startIndex + remaining[1], poly.startIndex + remaining[2]],
|
|
198
|
+
polyIndex,
|
|
199
|
+
});
|
|
200
|
+
return clipped;
|
|
201
|
+
}
|
|
202
|
+
function fanTriangulate(poly, polyIndex) {
|
|
203
|
+
const triangles = [];
|
|
204
|
+
for (let i = 1; i < poly.indices.length - 1; i++) {
|
|
205
|
+
triangles.push({
|
|
206
|
+
vertices: [poly.startIndex, poly.startIndex + i, poly.startIndex + i + 1],
|
|
207
|
+
polyIndex,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return triangles;
|
|
211
|
+
}
|
|
212
|
+
function projectPolygonTo2D(poly, rawPositions) {
|
|
213
|
+
const normal = computeNewellNormal(poly, rawPositions);
|
|
214
|
+
const ax = Math.abs(normal[0]);
|
|
215
|
+
const ay = Math.abs(normal[1]);
|
|
216
|
+
const az = Math.abs(normal[2]);
|
|
217
|
+
if (ax + ay + az < 1e-12) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const dropAxis = ax > ay && ax > az ? 0 : ay > az ? 1 : 2;
|
|
221
|
+
return poly.indices.map((cp) => {
|
|
222
|
+
const x = rawPositions[cp * 3];
|
|
223
|
+
const y = rawPositions[cp * 3 + 1];
|
|
224
|
+
const z = rawPositions[cp * 3 + 2];
|
|
225
|
+
if (dropAxis === 0) {
|
|
226
|
+
return normal[0] >= 0 ? [y, z] : [z, y];
|
|
227
|
+
}
|
|
228
|
+
if (dropAxis === 1) {
|
|
229
|
+
return normal[1] >= 0 ? [z, x] : [x, z];
|
|
230
|
+
}
|
|
231
|
+
return normal[2] >= 0 ? [x, y] : [y, x];
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
function computeNewellNormal(poly, rawPositions) {
|
|
235
|
+
let nx = 0;
|
|
236
|
+
let ny = 0;
|
|
237
|
+
let nz = 0;
|
|
238
|
+
for (let i = 0; i < poly.indices.length; i++) {
|
|
239
|
+
const current = poly.indices[i] * 3;
|
|
240
|
+
const next = poly.indices[(i + 1) % poly.indices.length] * 3;
|
|
241
|
+
const x0 = rawPositions[current];
|
|
242
|
+
const y0 = rawPositions[current + 1];
|
|
243
|
+
const z0 = rawPositions[current + 2];
|
|
244
|
+
const x1 = rawPositions[next];
|
|
245
|
+
const y1 = rawPositions[next + 1];
|
|
246
|
+
const z1 = rawPositions[next + 2];
|
|
247
|
+
nx += (y0 - y1) * (z0 + z1);
|
|
248
|
+
ny += (z0 - z1) * (x0 + x1);
|
|
249
|
+
nz += (x0 - x1) * (y0 + y1);
|
|
250
|
+
}
|
|
251
|
+
return [nx, ny, nz];
|
|
252
|
+
}
|
|
253
|
+
function signedArea2D(points) {
|
|
254
|
+
let area = 0;
|
|
255
|
+
for (let i = 0; i < points.length; i++) {
|
|
256
|
+
const a = points[i];
|
|
257
|
+
const b = points[(i + 1) % points.length];
|
|
258
|
+
area += a[0] * b[1] - b[0] * a[1];
|
|
259
|
+
}
|
|
260
|
+
return area / 2;
|
|
261
|
+
}
|
|
262
|
+
function isConvex(a, b, c, isCCW) {
|
|
263
|
+
const cross = (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
|
|
264
|
+
return isCCW ? cross > 1e-12 : cross < -1e-12;
|
|
265
|
+
}
|
|
266
|
+
function containsAnyPoint(points, remaining, prev, curr, next) {
|
|
267
|
+
for (const index of remaining) {
|
|
268
|
+
if (index === prev || index === curr || index === next) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (pointInTriangle(points[index], points[prev], points[curr], points[next])) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
function pointInTriangle(p, a, b, c) {
|
|
278
|
+
const area = Math.abs(cross2D(a, b, c));
|
|
279
|
+
const area1 = Math.abs(cross2D(p, a, b));
|
|
280
|
+
const area2 = Math.abs(cross2D(p, b, c));
|
|
281
|
+
const area3 = Math.abs(cross2D(p, c, a));
|
|
282
|
+
return Math.abs(area - (area1 + area2 + area3)) < 1e-10;
|
|
283
|
+
}
|
|
284
|
+
function cross2D(a, b, c) {
|
|
285
|
+
return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
|
|
286
|
+
}
|
|
287
|
+
function buildPolygonVertexList(polygons) {
|
|
288
|
+
const list = [];
|
|
289
|
+
for (let pi = 0; pi < polygons.length; pi++) {
|
|
290
|
+
const poly = polygons[pi];
|
|
291
|
+
for (let vi = 0; vi < poly.indices.length; vi++) {
|
|
292
|
+
list.push({
|
|
293
|
+
polyIndex: pi,
|
|
294
|
+
vertexInPoly: vi,
|
|
295
|
+
controlPointIndex: poly.indices[vi],
|
|
296
|
+
globalIndex: poly.startIndex + vi,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return list;
|
|
301
|
+
}
|
|
302
|
+
// ── Layer Element Expansion ────────────────────────────────────────────────────
|
|
303
|
+
/**
|
|
304
|
+
* Extract per-polygon material indices from LayerElementMaterial.
|
|
305
|
+
* Returns an Int32Array with one material index per polygon.
|
|
306
|
+
*/
|
|
307
|
+
function extractMaterialIndices(matNode, polygonCount) {
|
|
308
|
+
const mappingNode = findChildByName(matNode, "MappingInformationType");
|
|
309
|
+
const referenceNode = findChildByName(matNode, "ReferenceInformationType");
|
|
310
|
+
if (!mappingNode || !referenceNode) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const mapping = getPropertyValue(mappingNode, 0) ?? "";
|
|
314
|
+
const reference = getPropertyValue(referenceNode, 0) ?? "";
|
|
315
|
+
if (mapping === "AllSame") {
|
|
316
|
+
const materialsNode = findChildByName(matNode, "Materials");
|
|
317
|
+
const rawIndices = materialsNode ? toInt32Array(getNodeArrayValue(materialsNode)) : null;
|
|
318
|
+
const materialIndex = rawIndices && rawIndices.length > 0 ? rawIndices[0] : 0;
|
|
319
|
+
const indices = new Int32Array(polygonCount);
|
|
320
|
+
if (materialIndex !== 0) {
|
|
321
|
+
indices.fill(materialIndex);
|
|
322
|
+
}
|
|
323
|
+
return indices;
|
|
324
|
+
}
|
|
325
|
+
if (mapping === "ByPolygon") {
|
|
326
|
+
const materialsNode = findChildByName(matNode, "Materials");
|
|
327
|
+
if (!materialsNode) {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
const rawIndices = toInt32Array(getNodeArrayValue(materialsNode));
|
|
331
|
+
// For Direct reference, the Materials array has one index per polygon
|
|
332
|
+
if (reference === "Direct" || reference === "IndexToDirect") {
|
|
333
|
+
return rawIndices;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
function expandLayerElement(layerNode, dataChildName, indexChildName, polyVertexList, controlPointCount, stride, diagnostics) {
|
|
339
|
+
const mappingNode = findChildByName(layerNode, "MappingInformationType");
|
|
340
|
+
const referenceNode = findChildByName(layerNode, "ReferenceInformationType");
|
|
341
|
+
if (!mappingNode || !referenceNode) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
const mapping = getPropertyValue(mappingNode, 0) ?? "";
|
|
345
|
+
const reference = getPropertyValue(referenceNode, 0) ?? "";
|
|
346
|
+
const dataNode = findChildByName(layerNode, dataChildName);
|
|
347
|
+
if (!dataNode) {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
const data = toFloat64Array(getNodeArrayValue(dataNode));
|
|
351
|
+
let indexData = null;
|
|
352
|
+
if (reference === "IndexToDirect") {
|
|
353
|
+
const indexNode = findChildByName(layerNode, indexChildName);
|
|
354
|
+
if (indexNode) {
|
|
355
|
+
indexData = toInt32Array(getNodeArrayValue(indexNode));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Expand to per-polygon-vertex
|
|
359
|
+
const result = new Float64Array(polyVertexList.length * stride);
|
|
360
|
+
for (let i = 0; i < polyVertexList.length; i++) {
|
|
361
|
+
const pv = polyVertexList[i];
|
|
362
|
+
let dataIndex;
|
|
363
|
+
if (mapping === "ByPolygonVertex") {
|
|
364
|
+
if (reference === "IndexToDirect" && indexData) {
|
|
365
|
+
dataIndex = indexData[pv.globalIndex];
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
// Direct
|
|
369
|
+
dataIndex = pv.globalIndex;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else if (mapping === "ByControlPoint" || mapping === "ByVertice") {
|
|
373
|
+
if (reference === "IndexToDirect" && indexData) {
|
|
374
|
+
dataIndex = indexData[pv.controlPointIndex];
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
dataIndex = pv.controlPointIndex;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
else if (mapping === "ByPolygon") {
|
|
381
|
+
if (reference === "IndexToDirect" && indexData) {
|
|
382
|
+
dataIndex = indexData[pv.polyIndex];
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
dataIndex = pv.polyIndex;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
else if (mapping === "AllSame") {
|
|
389
|
+
dataIndex = 0;
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
dataIndex = pv.globalIndex;
|
|
393
|
+
}
|
|
394
|
+
for (let s = 0; s < stride; s++) {
|
|
395
|
+
const sourceIndex = dataIndex * stride + s;
|
|
396
|
+
if (dataIndex < 0 || sourceIndex >= data.length) {
|
|
397
|
+
diagnostics.push({
|
|
398
|
+
type: sourceIndex >= data.length ? "layer-data-too-short" : "layer-index-out-of-bounds",
|
|
399
|
+
message: `Layer '${layerNode.name}' references unavailable element ${dataIndex}.`,
|
|
400
|
+
layerName: layerNode.name,
|
|
401
|
+
index: dataIndex,
|
|
402
|
+
});
|
|
403
|
+
result[i * stride + s] = 0;
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
result[i * stride + s] = data[sourceIndex];
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
function expandTangentLayer(tangentNode, polyVertexList, controlPointCount, normals, binormals, diagnostics) {
|
|
413
|
+
const sourceStride = inferLayerElementStride(tangentNode, "Tangents", "TangentsIndex", polyVertexList, controlPointCount, diagnostics);
|
|
414
|
+
const expanded = expandLayerElement(tangentNode, "Tangents", "TangentsIndex", polyVertexList, controlPointCount, sourceStride, diagnostics);
|
|
415
|
+
if (!expanded) {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
const tangents = new Float64Array(polyVertexList.length * 4);
|
|
419
|
+
for (let i = 0; i < polyVertexList.length; i++) {
|
|
420
|
+
const sourceOffset = i * sourceStride;
|
|
421
|
+
const destOffset = i * 4;
|
|
422
|
+
tangents[destOffset] = expanded[sourceOffset];
|
|
423
|
+
tangents[destOffset + 1] = expanded[sourceOffset + 1];
|
|
424
|
+
tangents[destOffset + 2] = expanded[sourceOffset + 2];
|
|
425
|
+
tangents[destOffset + 3] = sourceStride >= 4 ? expanded[sourceOffset + 3] : computeTangentHandedness(i, tangents, normals, binormals);
|
|
426
|
+
}
|
|
427
|
+
return tangents;
|
|
428
|
+
}
|
|
429
|
+
function inferLayerElementStride(layerNode, dataChildName, indexChildName, polyVertexList, controlPointCount, diagnostics) {
|
|
430
|
+
const dataNode = findChildByName(layerNode, dataChildName);
|
|
431
|
+
if (!dataNode) {
|
|
432
|
+
return 3;
|
|
433
|
+
}
|
|
434
|
+
const data = toFloat64Array(getNodeArrayValue(dataNode));
|
|
435
|
+
const mapping = getPropertyValue(findChildByName(layerNode, "MappingInformationType") ?? { name: "", properties: [], children: [] }, 0) ?? "";
|
|
436
|
+
const reference = getPropertyValue(findChildByName(layerNode, "ReferenceInformationType") ?? { name: "", properties: [], children: [] }, 0) ?? "";
|
|
437
|
+
const indexNode = findChildByName(layerNode, indexChildName);
|
|
438
|
+
const indexData = indexNode ? toInt32Array(getNodeArrayValue(indexNode)) : null;
|
|
439
|
+
const directCount = reference === "IndexToDirect" && indexData
|
|
440
|
+
? Math.max(...Array.from(indexData), 0) + 1
|
|
441
|
+
: mapping === "ByControlPoint" || mapping === "ByVertice"
|
|
442
|
+
? controlPointCount
|
|
443
|
+
: mapping === "AllSame"
|
|
444
|
+
? 1
|
|
445
|
+
: polyVertexList.length;
|
|
446
|
+
if (directCount > 0 && data.length % directCount === 0) {
|
|
447
|
+
const stride = data.length / directCount;
|
|
448
|
+
if (stride === 3 || stride === 4) {
|
|
449
|
+
return stride;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
diagnostics.push({
|
|
453
|
+
type: "layer-data-too-short",
|
|
454
|
+
message: `Could not infer stride for layer '${layerNode.name}', defaulting to 3.`,
|
|
455
|
+
layerName: layerNode.name,
|
|
456
|
+
});
|
|
457
|
+
return 3;
|
|
458
|
+
}
|
|
459
|
+
function computeTangentHandedness(vertexIndex, tangents, normals, binormals) {
|
|
460
|
+
if (!normals || !binormals) {
|
|
461
|
+
return 1;
|
|
462
|
+
}
|
|
463
|
+
const to = vertexIndex * 4;
|
|
464
|
+
const no = vertexIndex * 3;
|
|
465
|
+
const nx = normals[no];
|
|
466
|
+
const ny = normals[no + 1];
|
|
467
|
+
const nz = normals[no + 2];
|
|
468
|
+
const tx = tangents[to];
|
|
469
|
+
const ty = tangents[to + 1];
|
|
470
|
+
const tz = tangents[to + 2];
|
|
471
|
+
const bx = binormals[no];
|
|
472
|
+
const by = binormals[no + 1];
|
|
473
|
+
const bz = binormals[no + 2];
|
|
474
|
+
const cx = ny * tz - nz * ty;
|
|
475
|
+
const cy = nz * tx - nx * tz;
|
|
476
|
+
const cz = nx * ty - ny * tx;
|
|
477
|
+
return cx * bx + cy * by + cz * bz < 0 ? -1 : 1;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Build the final triangle mesh. Since normals/UVs are per-polygon-vertex,
|
|
481
|
+
* we need to create unique vertices for each polygon-vertex combination.
|
|
482
|
+
*/
|
|
483
|
+
function buildTriangleMesh(rawPositions, triangles, polyVertexList, expandedNormals, expandedUVs, expandedUVSets, expandedColors, expandedTangents, expandedBinormals) {
|
|
484
|
+
// Each polygon-vertex becomes a unique vertex in the output
|
|
485
|
+
const vertexCount = polyVertexList.length;
|
|
486
|
+
const positions = new Float64Array(vertexCount * 3);
|
|
487
|
+
const controlPointIndices = new Uint32Array(vertexCount);
|
|
488
|
+
// Copy positions — keep in original RH space (root node handles RH→LH conversion)
|
|
489
|
+
for (let i = 0; i < polyVertexList.length; i++) {
|
|
490
|
+
const cp = polyVertexList[i].controlPointIndex;
|
|
491
|
+
positions[i * 3] = rawPositions[cp * 3];
|
|
492
|
+
positions[i * 3 + 1] = rawPositions[cp * 3 + 1];
|
|
493
|
+
positions[i * 3 + 2] = rawPositions[cp * 3 + 2];
|
|
494
|
+
controlPointIndices[i] = cp;
|
|
495
|
+
}
|
|
496
|
+
// Normals stay in RH space (root node handles conversion)
|
|
497
|
+
if (expandedNormals) {
|
|
498
|
+
// No transformation needed
|
|
499
|
+
}
|
|
500
|
+
// Keep original winding order — Z negation handles handedness
|
|
501
|
+
const indexCount = triangles.length * 3;
|
|
502
|
+
const indices = new Uint32Array(indexCount);
|
|
503
|
+
for (let i = 0; i < triangles.length; i++) {
|
|
504
|
+
indices[i * 3] = triangles[i].vertices[0];
|
|
505
|
+
indices[i * 3 + 1] = triangles[i].vertices[1];
|
|
506
|
+
indices[i * 3 + 2] = triangles[i].vertices[2];
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
positions,
|
|
510
|
+
indices,
|
|
511
|
+
normals: expandedNormals,
|
|
512
|
+
uvs: expandedUVs,
|
|
513
|
+
uvSets: expandedUVSets,
|
|
514
|
+
colors: expandedColors,
|
|
515
|
+
tangents: expandedTangents,
|
|
516
|
+
binormals: expandedBinormals,
|
|
517
|
+
controlPointIndices,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
// ── Utilities ──────────────────────────────────────────────────────────────────
|
|
521
|
+
function toFloat64Array(value) {
|
|
522
|
+
if (value instanceof Float64Array) {
|
|
523
|
+
return value;
|
|
524
|
+
}
|
|
525
|
+
if (value instanceof Float32Array) {
|
|
526
|
+
return new Float64Array(value);
|
|
527
|
+
}
|
|
528
|
+
if (value instanceof Int32Array) {
|
|
529
|
+
return new Float64Array(value);
|
|
530
|
+
}
|
|
531
|
+
if (Array.isArray(value)) {
|
|
532
|
+
const result = new Float64Array(value.length);
|
|
533
|
+
for (let i = 0; i < value.length; i++) {
|
|
534
|
+
result[i] = Number(value[i]);
|
|
535
|
+
}
|
|
536
|
+
return result;
|
|
537
|
+
}
|
|
538
|
+
throw new Error(`Cannot convert ${typeof value} to Float64Array`);
|
|
539
|
+
}
|
|
540
|
+
function toInt32Array(value) {
|
|
541
|
+
if (value instanceof Int32Array) {
|
|
542
|
+
return value;
|
|
543
|
+
}
|
|
544
|
+
if (value instanceof Float64Array) {
|
|
545
|
+
const result = new Int32Array(value.length);
|
|
546
|
+
for (let i = 0; i < value.length; i++) {
|
|
547
|
+
result[i] = Math.round(value[i]);
|
|
548
|
+
}
|
|
549
|
+
return result;
|
|
550
|
+
}
|
|
551
|
+
if (value instanceof Float32Array) {
|
|
552
|
+
const result = new Int32Array(value.length);
|
|
553
|
+
for (let i = 0; i < value.length; i++) {
|
|
554
|
+
result[i] = Math.round(value[i]);
|
|
555
|
+
}
|
|
556
|
+
return result;
|
|
557
|
+
}
|
|
558
|
+
if (Array.isArray(value)) {
|
|
559
|
+
const result = new Int32Array(value.length);
|
|
560
|
+
for (let i = 0; i < value.length; i++) {
|
|
561
|
+
result[i] = Math.round(Number(value[i]));
|
|
562
|
+
}
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
throw new Error(`Cannot convert ${typeof value} to Int32Array`);
|
|
566
|
+
}
|
|
567
|
+
function getNodeArrayValue(node) {
|
|
568
|
+
if (node.properties.length === 1) {
|
|
569
|
+
return node.properties[0].value;
|
|
570
|
+
}
|
|
571
|
+
return node.properties.map((property) => property.value);
|
|
572
|
+
}
|
|
573
|
+
//# sourceMappingURL=geometry.js.map
|