three-usd-robot 0.9.0 → 0.11.0
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/README.md +76 -7
- package/dist/ThreeUsdRobot-BxnCluuK.d.ts +347 -0
- package/dist/{ThreeUsdRobotLoader-DYtvelif.d.ts → ThreeUsdRobotLoader-C-E9uwEe.d.ts} +15 -2
- package/dist/{buildKinematicTree-DQ4Vr4sJ.d.ts → buildKinematicTree-BCySuZZn.d.ts} +4 -0
- package/dist/chunk-5SVRTKS4.js +966 -0
- package/dist/chunk-5SVRTKS4.js.map +1 -0
- package/dist/{chunk-2UD32EGU.js → chunk-AXPMXEQC.js} +1005 -175
- package/dist/chunk-AXPMXEQC.js.map +1 -0
- package/dist/{chunk-TE4MPP5Q.js → chunk-BCRRXGNB.js} +4 -4
- package/dist/{chunk-TE4MPP5Q.js.map → chunk-BCRRXGNB.js.map} +1 -1
- package/dist/chunk-ENWFOYHU.js +701 -0
- package/dist/chunk-ENWFOYHU.js.map +1 -0
- package/dist/{chunk-IYKPVUZ2.js → chunk-LDO5FKQS.js} +39 -3
- package/dist/chunk-LDO5FKQS.js.map +1 -0
- package/dist/core.d.ts +173 -5
- package/dist/core.js +3 -3
- package/dist/extras.d.ts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.js +2 -2
- package/dist/index.d.ts +101 -17
- package/dist/index.js +8 -8
- package/dist/react.d.ts +3 -3
- package/dist/react.js +4 -4
- package/package.json +1 -1
- package/dist/ThreeUsdRobot-BAh-48DD.d.ts +0 -185
- package/dist/chunk-2UD32EGU.js.map +0 -1
- package/dist/chunk-IYKPVUZ2.js.map +0 -1
- package/dist/chunk-WROYLUSS.js +0 -393
- package/dist/chunk-WROYLUSS.js.map +0 -1
- package/dist/chunk-XEXFGL7R.js +0 -529
- package/dist/chunk-XEXFGL7R.js.map +0 -1
|
@@ -0,0 +1,966 @@
|
|
|
1
|
+
import { getMaterialSubsets, resolveBoundMaterial, isRenderableGprim, COLLISION_API, isNonVisualPurpose, computeLocalTransform, DefaultAssetResolver, toBytes, extractRobotDescription, isZip, CrateReader, openUsdz, buildKinematicTree, loadMdlModules, isUnsupportedGprim, Stage, composeLayer, composeFile, crateToUsdaFile } from './chunk-AXPMXEQC.js';
|
|
2
|
+
import { ThreeUsdRobot } from './chunk-ENWFOYHU.js';
|
|
3
|
+
import { identity4, multiply } from './chunk-LDO5FKQS.js';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
var DEFAULT_COLOR = 10132122;
|
|
7
|
+
function readPrimvar(meshPrim, attrName, isTuple, lens) {
|
|
8
|
+
const attr = meshPrim.GetAttribute(attrName);
|
|
9
|
+
const raw = attr.Get();
|
|
10
|
+
if (!isTuple(raw) || raw.length === 0) return null;
|
|
11
|
+
const base = raw;
|
|
12
|
+
let values = base;
|
|
13
|
+
const idx = meshPrim.GetAttribute(`${attrName}:indices`).Get();
|
|
14
|
+
if (isNumberArray(idx)) values = idx.map((i) => base[i] ?? base[0]);
|
|
15
|
+
const authored = attr.GetMetadata("interpolation");
|
|
16
|
+
let interpolation;
|
|
17
|
+
if (authored === "varying")
|
|
18
|
+
interpolation = "vertex";
|
|
19
|
+
else if (authored === "constant" || authored === "uniform" || authored === "vertex" || authored === "faceVarying") {
|
|
20
|
+
interpolation = authored;
|
|
21
|
+
} else if (values.length === lens.points && lens.points > 0) interpolation = "vertex";
|
|
22
|
+
else if (values.length === lens.faceVertices && lens.faceVertices > 0)
|
|
23
|
+
interpolation = "faceVarying";
|
|
24
|
+
else if (values.length === lens.faces && lens.faces > 0) interpolation = "uniform";
|
|
25
|
+
else interpolation = "constant";
|
|
26
|
+
const needed = interpolation === "vertex" ? lens.points : interpolation === "faceVarying" ? lens.faceVertices : interpolation === "uniform" ? lens.faces : 1;
|
|
27
|
+
if (values.length < needed) interpolation = "constant";
|
|
28
|
+
return { values, interpolation };
|
|
29
|
+
}
|
|
30
|
+
var UV_TYPE_NAMES = /* @__PURE__ */ new Set(["texcoord2f", "float2"]);
|
|
31
|
+
function meshUvSetNames(meshPrim) {
|
|
32
|
+
const sets = [];
|
|
33
|
+
for (const attr of meshPrim.GetAttributes()) {
|
|
34
|
+
const name = attr.GetName();
|
|
35
|
+
if (!name.startsWith("primvars:") || name.endsWith(":indices")) continue;
|
|
36
|
+
if (!attr.IsArray()) continue;
|
|
37
|
+
if (!UV_TYPE_NAMES.has(attr.GetTypeName().toLowerCase())) continue;
|
|
38
|
+
sets.push(name.slice("primvars:".length));
|
|
39
|
+
}
|
|
40
|
+
sets.sort((a, b) => a === "st" ? -1 : b === "st" ? 1 : a < b ? -1 : 1);
|
|
41
|
+
return sets;
|
|
42
|
+
}
|
|
43
|
+
function uvAttrName(index) {
|
|
44
|
+
return index === 0 ? "uv" : `uv${index}`;
|
|
45
|
+
}
|
|
46
|
+
function meshLens(meshPrim) {
|
|
47
|
+
const points = meshPrim.GetAttribute("points").Get();
|
|
48
|
+
const counts = meshPrim.GetAttribute("faceVertexCounts").Get();
|
|
49
|
+
const indices = meshPrim.GetAttribute("faceVertexIndices").Get();
|
|
50
|
+
const hasTopology = isNumberArray(counts) && isNumberArray(indices);
|
|
51
|
+
return {
|
|
52
|
+
points: isVec3Array(points) ? points.length : 0,
|
|
53
|
+
faceVertices: hasTopology ? indices.length : 0,
|
|
54
|
+
faces: hasTopology ? counts.length : 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function displayColorPrimvar(meshPrim) {
|
|
58
|
+
return readPrimvar(meshPrim, "primvars:displayColor", isVec3Array, meshLens(meshPrim));
|
|
59
|
+
}
|
|
60
|
+
function buildMeshGeometry(meshPrim) {
|
|
61
|
+
const points = meshPrim.GetAttribute("points").Get();
|
|
62
|
+
if (!isVec3Array(points) || points.length === 0) return null;
|
|
63
|
+
const countsRaw = meshPrim.GetAttribute("faceVertexCounts").Get();
|
|
64
|
+
const indicesRaw = meshPrim.GetAttribute("faceVertexIndices").Get();
|
|
65
|
+
const counts = isNumberArray(countsRaw) ? countsRaw : null;
|
|
66
|
+
const indices = isNumberArray(indicesRaw) ? indicesRaw : null;
|
|
67
|
+
const lens = {
|
|
68
|
+
points: points.length,
|
|
69
|
+
faceVertices: counts && indices ? indices.length : 0,
|
|
70
|
+
faces: counts && indices ? counts.length : 0
|
|
71
|
+
};
|
|
72
|
+
const uvSets = meshUvSetNames(meshPrim).map(
|
|
73
|
+
(set) => readPrimvar(meshPrim, `primvars:${set}`, isVec2Array, lens)
|
|
74
|
+
);
|
|
75
|
+
const normals = readPrimvar(meshPrim, "primvars:normals", isVec3Array, lens) ?? readPrimvar(meshPrim, "normals", isVec3Array, lens);
|
|
76
|
+
const color = displayColorPrimvar(meshPrim);
|
|
77
|
+
const expand = counts !== null && indices !== null && ([...uvSets, normals].some((p) => p?.interpolation === "faceVarying") || color?.interpolation === "faceVarying" || color?.interpolation === "uniform");
|
|
78
|
+
return expand ? buildExpandedGeometry(meshPrim, points, counts, indices, uvSets, normals, color) : buildIndexedGeometry(meshPrim, points, counts, indices, uvSets, normals, color);
|
|
79
|
+
}
|
|
80
|
+
function buildIndexedGeometry(meshPrim, points, counts, indices, uvSets, normals, color) {
|
|
81
|
+
const geometry = new THREE.BufferGeometry();
|
|
82
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(flat3(points), 3));
|
|
83
|
+
if (counts && indices) {
|
|
84
|
+
const subsets = getMaterialSubsets(meshPrim);
|
|
85
|
+
if (subsets.length > 0) {
|
|
86
|
+
geometry.setIndex(triangulateBySubset(geometry, counts, indices, subsets));
|
|
87
|
+
} else {
|
|
88
|
+
geometry.setIndex(triangulate(counts, indices));
|
|
89
|
+
}
|
|
90
|
+
} else if (indices) {
|
|
91
|
+
geometry.setIndex(indices.slice());
|
|
92
|
+
}
|
|
93
|
+
if (normals?.interpolation === "vertex") {
|
|
94
|
+
geometry.setAttribute(
|
|
95
|
+
"normal",
|
|
96
|
+
new THREE.Float32BufferAttribute(flat3(normals.values.slice(0, points.length)), 3)
|
|
97
|
+
);
|
|
98
|
+
} else {
|
|
99
|
+
geometry.computeVertexNormals();
|
|
100
|
+
}
|
|
101
|
+
for (const [i, uv] of uvSets.entries()) {
|
|
102
|
+
if (uv?.interpolation !== "vertex") continue;
|
|
103
|
+
geometry.setAttribute(
|
|
104
|
+
uvAttrName(i),
|
|
105
|
+
new THREE.Float32BufferAttribute(flat2(uv.values.slice(0, points.length)), 2)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (color?.interpolation === "vertex") {
|
|
109
|
+
geometry.setAttribute(
|
|
110
|
+
"color",
|
|
111
|
+
new THREE.Float32BufferAttribute(flat3(color.values.slice(0, points.length)), 3)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return geometry;
|
|
115
|
+
}
|
|
116
|
+
function buildExpandedGeometry(meshPrim, points, counts, indices, uvSets, normals, color) {
|
|
117
|
+
const { slots, faces, groups } = triangulateToSlots(counts, getMaterialSubsets(meshPrim));
|
|
118
|
+
const sample = (p, corner) => {
|
|
119
|
+
switch (p.interpolation) {
|
|
120
|
+
case "faceVarying":
|
|
121
|
+
return p.values[slots[corner]] ?? p.values[0];
|
|
122
|
+
case "vertex":
|
|
123
|
+
return p.values[indices[slots[corner]]] ?? p.values[0];
|
|
124
|
+
case "uniform":
|
|
125
|
+
return p.values[faces[corner]] ?? p.values[0];
|
|
126
|
+
default:
|
|
127
|
+
return p.values[0];
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const geometry = new THREE.BufferGeometry();
|
|
131
|
+
const position = new Array(slots.length * 3);
|
|
132
|
+
for (let c = 0; c < slots.length; c++) {
|
|
133
|
+
const v = points[indices[slots[c]]] ?? [0, 0, 0];
|
|
134
|
+
position[c * 3] = v[0];
|
|
135
|
+
position[c * 3 + 1] = v[1];
|
|
136
|
+
position[c * 3 + 2] = v[2];
|
|
137
|
+
}
|
|
138
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(position, 3));
|
|
139
|
+
for (const [i, uv] of uvSets.entries()) {
|
|
140
|
+
if (!uv || uv.interpolation === "constant") continue;
|
|
141
|
+
const out = new Array(slots.length * 2);
|
|
142
|
+
for (let c = 0; c < slots.length; c++) {
|
|
143
|
+
const t = sample(uv, c);
|
|
144
|
+
out[c * 2] = t[0] ?? 0;
|
|
145
|
+
out[c * 2 + 1] = t[1] ?? 0;
|
|
146
|
+
}
|
|
147
|
+
geometry.setAttribute(uvAttrName(i), new THREE.Float32BufferAttribute(out, 2));
|
|
148
|
+
}
|
|
149
|
+
const emitVec3 = (p, name) => {
|
|
150
|
+
const out = new Array(slots.length * 3);
|
|
151
|
+
for (let c = 0; c < slots.length; c++) {
|
|
152
|
+
const t = sample(p, c);
|
|
153
|
+
out[c * 3] = t[0] ?? 0;
|
|
154
|
+
out[c * 3 + 1] = t[1] ?? 0;
|
|
155
|
+
out[c * 3 + 2] = t[2] ?? 0;
|
|
156
|
+
}
|
|
157
|
+
geometry.setAttribute(name, new THREE.Float32BufferAttribute(out, 3));
|
|
158
|
+
};
|
|
159
|
+
if (normals && normals.interpolation !== "constant") emitVec3(normals, "normal");
|
|
160
|
+
else geometry.computeVertexNormals();
|
|
161
|
+
if (color && color.interpolation !== "constant") emitVec3(color, "color");
|
|
162
|
+
for (const g of groups) geometry.addGroup(g.start, g.count, g.materialIndex);
|
|
163
|
+
return geometry;
|
|
164
|
+
}
|
|
165
|
+
function triangulateToSlots(faceVertexCounts, subsets) {
|
|
166
|
+
const faceStart = [];
|
|
167
|
+
let offset = 0;
|
|
168
|
+
for (const count of faceVertexCounts) {
|
|
169
|
+
faceStart.push(offset);
|
|
170
|
+
offset += count;
|
|
171
|
+
}
|
|
172
|
+
const slots = [];
|
|
173
|
+
const faces = [];
|
|
174
|
+
const emit = (face) => {
|
|
175
|
+
const start = faceStart[face];
|
|
176
|
+
const count = faceVertexCounts[face];
|
|
177
|
+
for (let k = 2; k < count; k++) {
|
|
178
|
+
slots.push(start, start + k - 1, start + k);
|
|
179
|
+
faces.push(face, face, face);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const groups = [];
|
|
183
|
+
if (subsets.length === 0) {
|
|
184
|
+
for (let face = 0; face < faceVertexCounts.length; face++) emit(face);
|
|
185
|
+
return { slots, faces, groups };
|
|
186
|
+
}
|
|
187
|
+
const claimed = new Uint8Array(faceVertexCounts.length);
|
|
188
|
+
for (const [index, subset] of subsets.entries()) {
|
|
189
|
+
const begin2 = slots.length;
|
|
190
|
+
for (const face of subset.faces) {
|
|
191
|
+
if (face < 0 || face >= faceVertexCounts.length || claimed[face]) continue;
|
|
192
|
+
claimed[face] = 1;
|
|
193
|
+
emit(face);
|
|
194
|
+
}
|
|
195
|
+
if (slots.length > begin2) {
|
|
196
|
+
groups.push({ start: begin2, count: slots.length - begin2, materialIndex: index });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const begin = slots.length;
|
|
200
|
+
for (let face = 0; face < faceVertexCounts.length; face++) {
|
|
201
|
+
if (!claimed[face]) emit(face);
|
|
202
|
+
}
|
|
203
|
+
if (slots.length > begin) {
|
|
204
|
+
groups.push({ start: begin, count: slots.length - begin, materialIndex: subsets.length });
|
|
205
|
+
}
|
|
206
|
+
return { slots, faces, groups };
|
|
207
|
+
}
|
|
208
|
+
function buildGprimGeometry(prim) {
|
|
209
|
+
switch (prim.GetTypeName()) {
|
|
210
|
+
case "Mesh":
|
|
211
|
+
return buildMeshGeometry(prim);
|
|
212
|
+
case "Cube": {
|
|
213
|
+
const size = getNumber(prim, "size") ?? 2;
|
|
214
|
+
return new THREE.BoxGeometry(size, size, size);
|
|
215
|
+
}
|
|
216
|
+
case "Sphere":
|
|
217
|
+
return new THREE.SphereGeometry(getNumber(prim, "radius") ?? 1, 32, 16);
|
|
218
|
+
case "Cylinder": {
|
|
219
|
+
const radius = getNumber(prim, "radius") ?? 1;
|
|
220
|
+
const height = getNumber(prim, "height") ?? 2;
|
|
221
|
+
return orientSpine(new THREE.CylinderGeometry(radius, radius, height, 32), prim);
|
|
222
|
+
}
|
|
223
|
+
case "Capsule": {
|
|
224
|
+
const radius = getNumber(prim, "radius") ?? 0.5;
|
|
225
|
+
const height = getNumber(prim, "height") ?? 1;
|
|
226
|
+
return orientSpine(new THREE.CapsuleGeometry(radius, height, 8, 32), prim);
|
|
227
|
+
}
|
|
228
|
+
case "Cone": {
|
|
229
|
+
const radius = getNumber(prim, "radius") ?? 1;
|
|
230
|
+
const height = getNumber(prim, "height") ?? 2;
|
|
231
|
+
return orientSpine(new THREE.ConeGeometry(radius, height, 32), prim);
|
|
232
|
+
}
|
|
233
|
+
default:
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function buildGprimObject(prim, stage, options = {}) {
|
|
238
|
+
switch (prim.GetTypeName()) {
|
|
239
|
+
case "Points":
|
|
240
|
+
return buildPointsObject(prim, stage, options.mdl);
|
|
241
|
+
case "BasisCurves":
|
|
242
|
+
return buildBasisCurvesObject(prim, stage, options.curveTubes ?? false, options.mdl);
|
|
243
|
+
default: {
|
|
244
|
+
const geometry = buildGprimGeometry(prim);
|
|
245
|
+
if (!geometry) return null;
|
|
246
|
+
return new THREE.Mesh(
|
|
247
|
+
geometry,
|
|
248
|
+
buildMeshMaterials(prim, stage, options.textureProvider, options.onWarn, options.mdl)
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function resolveFlatColor(prim, stage, mdl) {
|
|
254
|
+
const color = new THREE.Color(DEFAULT_COLOR);
|
|
255
|
+
const bound = stage ? resolveBoundMaterial(stage, prim, mdl ? { mdl } : {}) : void 0;
|
|
256
|
+
if (bound?.color) {
|
|
257
|
+
color.setRGB(bound.color[0], bound.color[1], bound.color[2]);
|
|
258
|
+
} else {
|
|
259
|
+
const displayColor = prim.GetAttribute("primvars:displayColor").Get();
|
|
260
|
+
if (isVec3Array(displayColor) && displayColor[0]) {
|
|
261
|
+
color.setRGB(displayColor[0][0], displayColor[0][1], displayColor[0][2]);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const result = {
|
|
265
|
+
color,
|
|
266
|
+
opacity: bound?.opacity ?? 1
|
|
267
|
+
};
|
|
268
|
+
if (bound?.name) result.name = bound.name;
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
function meanWidth(prim) {
|
|
272
|
+
const widths = prim.GetAttribute("widths").Get();
|
|
273
|
+
if (!isNumberArray(widths) || widths.length === 0) return void 0;
|
|
274
|
+
return widths.reduce((a, b) => a + b, 0) / widths.length;
|
|
275
|
+
}
|
|
276
|
+
function buildPointsObject(prim, stage, mdl) {
|
|
277
|
+
const points = prim.GetAttribute("points").Get();
|
|
278
|
+
if (!isVec3Array(points) || points.length === 0) return null;
|
|
279
|
+
const geometry = new THREE.BufferGeometry();
|
|
280
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(flat3(points), 3));
|
|
281
|
+
const flat = resolveFlatColor(prim, stage, mdl);
|
|
282
|
+
const material = new THREE.PointsMaterial({ color: flat.color });
|
|
283
|
+
if (flat.opacity < 1) {
|
|
284
|
+
material.transparent = true;
|
|
285
|
+
material.opacity = flat.opacity;
|
|
286
|
+
}
|
|
287
|
+
if (flat.name) material.name = flat.name;
|
|
288
|
+
const displayColor = prim.GetAttribute("primvars:displayColor").Get();
|
|
289
|
+
if (isVec3Array(displayColor) && displayColor.length === points.length && points.length > 1) {
|
|
290
|
+
geometry.setAttribute("color", new THREE.Float32BufferAttribute(flat3(displayColor), 3));
|
|
291
|
+
material.vertexColors = true;
|
|
292
|
+
material.color.set(16777215);
|
|
293
|
+
}
|
|
294
|
+
const width = meanWidth(prim);
|
|
295
|
+
if (width !== void 0 && width > 0) {
|
|
296
|
+
material.size = width;
|
|
297
|
+
material.sizeAttenuation = true;
|
|
298
|
+
} else {
|
|
299
|
+
material.size = 3;
|
|
300
|
+
material.sizeAttenuation = false;
|
|
301
|
+
}
|
|
302
|
+
return new THREE.Points(geometry, material);
|
|
303
|
+
}
|
|
304
|
+
var CURVE_DIVISIONS = 8;
|
|
305
|
+
var CUBIC_BASES = {
|
|
306
|
+
// biome-ignore format: keep matrix layout readable
|
|
307
|
+
bezier: {
|
|
308
|
+
m: [
|
|
309
|
+
[-1, 3, -3, 1],
|
|
310
|
+
[3, -6, 3, 0],
|
|
311
|
+
[-3, 3, 0, 0],
|
|
312
|
+
[1, 0, 0, 0]
|
|
313
|
+
],
|
|
314
|
+
vstep: 3
|
|
315
|
+
},
|
|
316
|
+
// biome-ignore format: keep matrix layout readable
|
|
317
|
+
bspline: {
|
|
318
|
+
m: [
|
|
319
|
+
[-1 / 6, 3 / 6, -3 / 6, 1 / 6],
|
|
320
|
+
[3 / 6, -6 / 6, 3 / 6, 0],
|
|
321
|
+
[-3 / 6, 0, 3 / 6, 0],
|
|
322
|
+
[1 / 6, 4 / 6, 1 / 6, 0]
|
|
323
|
+
],
|
|
324
|
+
vstep: 1
|
|
325
|
+
},
|
|
326
|
+
// biome-ignore format: keep matrix layout readable
|
|
327
|
+
catmullRom: {
|
|
328
|
+
m: [
|
|
329
|
+
[-1 / 2, 3 / 2, -3 / 2, 1 / 2],
|
|
330
|
+
[2 / 2, -5 / 2, 4 / 2, -1 / 2],
|
|
331
|
+
[-1 / 2, 0, 1 / 2, 0],
|
|
332
|
+
[0, 2 / 2, 0, 0]
|
|
333
|
+
],
|
|
334
|
+
vstep: 1
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
function sampleCubicCurve(cvs, basisName, periodic) {
|
|
338
|
+
const basis = CUBIC_BASES[basisName] ?? CUBIC_BASES.bezier;
|
|
339
|
+
const n = cvs.length;
|
|
340
|
+
const nseg = periodic ? Math.floor(n / basis.vstep) : Math.floor((n - 4) / basis.vstep) + 1;
|
|
341
|
+
if (n < 4 || nseg < 1) return null;
|
|
342
|
+
const out = [];
|
|
343
|
+
for (let s = 0; s < nseg; s++) {
|
|
344
|
+
const i0 = s * basis.vstep;
|
|
345
|
+
const P = [cvs[i0 % n], cvs[(i0 + 1) % n], cvs[(i0 + 2) % n], cvs[(i0 + 3) % n]];
|
|
346
|
+
const last = !periodic && s === nseg - 1 ? CURVE_DIVISIONS : CURVE_DIVISIONS - 1;
|
|
347
|
+
for (let k = 0; k <= last; k++) {
|
|
348
|
+
const t = k / CURVE_DIVISIONS;
|
|
349
|
+
const w = [t * t * t, t * t, t, 1];
|
|
350
|
+
const point = [0, 0, 0];
|
|
351
|
+
for (let i = 0; i < 4; i++) {
|
|
352
|
+
let b = 0;
|
|
353
|
+
for (let j = 0; j < 4; j++) b += w[j] * basis.m[j][i];
|
|
354
|
+
point[0] += b * P[i][0];
|
|
355
|
+
point[1] += b * P[i][1];
|
|
356
|
+
point[2] += b * P[i][2];
|
|
357
|
+
}
|
|
358
|
+
out.push(point);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return out;
|
|
362
|
+
}
|
|
363
|
+
function polylinePath(samples, closed) {
|
|
364
|
+
const path = new THREE.CurvePath();
|
|
365
|
+
const vecs = samples.map(([x, y, z]) => new THREE.Vector3(x, y, z));
|
|
366
|
+
for (let i = 0; i < vecs.length - 1; i++) path.add(new THREE.LineCurve3(vecs[i], vecs[i + 1]));
|
|
367
|
+
if (closed) path.add(new THREE.LineCurve3(vecs[vecs.length - 1], vecs[0]));
|
|
368
|
+
return path;
|
|
369
|
+
}
|
|
370
|
+
function buildBasisCurvesObject(prim, stage, curveTubes, mdl) {
|
|
371
|
+
const points = prim.GetAttribute("points").Get();
|
|
372
|
+
const counts = prim.GetAttribute("curveVertexCounts").Get();
|
|
373
|
+
if (!isVec3Array(points) || !isNumberArray(counts) || counts.length === 0) return null;
|
|
374
|
+
const curveType = prim.GetAttribute("type").Get() ?? "cubic";
|
|
375
|
+
const basisName = prim.GetAttribute("basis").Get() ?? "bezier";
|
|
376
|
+
const periodic = prim.GetAttribute("wrap").Get() === "periodic";
|
|
377
|
+
const flat = resolveFlatColor(prim, stage, mdl);
|
|
378
|
+
const width = meanWidth(prim);
|
|
379
|
+
const asTubes = curveTubes && width !== void 0 && width > 0;
|
|
380
|
+
const material = asTubes ? new THREE.MeshStandardMaterial({ color: flat.color, roughness: 0.8, metalness: 0.1 }) : new THREE.LineBasicMaterial({ color: flat.color });
|
|
381
|
+
if (flat.opacity < 1) {
|
|
382
|
+
material.transparent = true;
|
|
383
|
+
material.opacity = flat.opacity;
|
|
384
|
+
}
|
|
385
|
+
if (flat.name) material.name = flat.name;
|
|
386
|
+
const group = new THREE.Group();
|
|
387
|
+
let offset = 0;
|
|
388
|
+
for (const count of counts) {
|
|
389
|
+
const cvs = points.slice(offset, offset + count);
|
|
390
|
+
offset += count;
|
|
391
|
+
const samples = curveType === "linear" ? cvs.length >= 2 ? cvs : null : sampleCubicCurve(cvs, typeof basisName === "string" ? basisName : "bezier", periodic);
|
|
392
|
+
if (!samples || samples.length < 2) continue;
|
|
393
|
+
if (asTubes) {
|
|
394
|
+
const path = polylinePath(samples, periodic);
|
|
395
|
+
const geometry = new THREE.TubeGeometry(
|
|
396
|
+
path,
|
|
397
|
+
Math.max(samples.length, 2),
|
|
398
|
+
width / 2,
|
|
399
|
+
8,
|
|
400
|
+
periodic
|
|
401
|
+
);
|
|
402
|
+
group.add(new THREE.Mesh(geometry, material));
|
|
403
|
+
} else {
|
|
404
|
+
const geometry = new THREE.BufferGeometry();
|
|
405
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(flat3(samples), 3));
|
|
406
|
+
group.add(
|
|
407
|
+
periodic ? new THREE.LineLoop(geometry, material) : new THREE.Line(geometry, material)
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return group.children.length > 0 ? group : null;
|
|
412
|
+
}
|
|
413
|
+
function getNumber(prim, name) {
|
|
414
|
+
const v = prim.GetAttribute(name).Get();
|
|
415
|
+
return typeof v === "number" ? v : void 0;
|
|
416
|
+
}
|
|
417
|
+
function orientSpine(geometry, prim) {
|
|
418
|
+
const axis = prim.GetAttribute("axis").Get();
|
|
419
|
+
if (axis === "Y") return geometry;
|
|
420
|
+
if (axis === "X") return geometry.rotateZ(-Math.PI / 2);
|
|
421
|
+
return geometry.rotateX(Math.PI / 2);
|
|
422
|
+
}
|
|
423
|
+
function buildMeshMaterial(meshPrim, stage, textures, bindingPrim, onWarn, mdl) {
|
|
424
|
+
const color = new THREE.Color(DEFAULT_COLOR);
|
|
425
|
+
let opacity = 1;
|
|
426
|
+
let vertexColors = false;
|
|
427
|
+
const resolveOptions = {
|
|
428
|
+
...mdl ? { mdl } : {},
|
|
429
|
+
...onWarn ? { onWarn } : {}
|
|
430
|
+
};
|
|
431
|
+
const bound = stage ? resolveBoundMaterial(stage, bindingPrim ?? meshPrim, resolveOptions) : void 0;
|
|
432
|
+
if (bound?.color) {
|
|
433
|
+
color.setRGB(bound.color[0], bound.color[1], bound.color[2]);
|
|
434
|
+
} else {
|
|
435
|
+
const displayColor = displayColorPrimvar(meshPrim);
|
|
436
|
+
if (displayColor && displayColor.interpolation !== "constant") {
|
|
437
|
+
vertexColors = true;
|
|
438
|
+
color.setRGB(1, 1, 1);
|
|
439
|
+
} else if (displayColor?.values[0]) {
|
|
440
|
+
const [r, g, b] = displayColor.values[0];
|
|
441
|
+
color.setRGB(r ?? 0, g ?? 0, b ?? 0);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (bound?.opacity !== void 0) opacity = bound.opacity;
|
|
445
|
+
const uvSetNames = meshUvSetNames(meshPrim);
|
|
446
|
+
const tex = (rt, cs) => {
|
|
447
|
+
if (!rt || !textures) return null;
|
|
448
|
+
const channel = rt.uvSet ? Math.max(uvSetNames.indexOf(rt.uvSet), 0) : 0;
|
|
449
|
+
const colorSpace = rt.sourceColorSpace === "raw" ? "linear" : rt.sourceColorSpace === "sRGB" ? "srgb" : cs;
|
|
450
|
+
return textures(rt.path, {
|
|
451
|
+
colorSpace,
|
|
452
|
+
...rt.wrapS ? { wrapS: rt.wrapS } : {},
|
|
453
|
+
...rt.wrapT ? { wrapT: rt.wrapT } : {},
|
|
454
|
+
...rt.transform ? { transform: rt.transform } : {},
|
|
455
|
+
...channel > 0 ? { channel } : {}
|
|
456
|
+
});
|
|
457
|
+
};
|
|
458
|
+
if (onWarn) {
|
|
459
|
+
const packed = [
|
|
460
|
+
[bound?.roughnessTexture, "g", "roughness"],
|
|
461
|
+
[bound?.metalnessTexture, "b", "metalness"],
|
|
462
|
+
[bound?.occlusionTexture, "r", "occlusion"]
|
|
463
|
+
];
|
|
464
|
+
for (const [rt, expected, label] of packed) {
|
|
465
|
+
const channel = rt?.outputChannel;
|
|
466
|
+
if (channel && channel !== "rgb" && channel !== expected) {
|
|
467
|
+
onWarn(
|
|
468
|
+
`${meshPrim.GetPath()}: ${label} reads outputs:${channel}, but three.js samples the "${expected}" channel of the map \u2014 repack the texture to the glTF ORM layout`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const map = tex(bound?.colorTexture, "srgb");
|
|
474
|
+
if (map) {
|
|
475
|
+
const s = bound?.colorTexture?.scale;
|
|
476
|
+
if (s) color.setRGB(s[0], s[1], s[2]);
|
|
477
|
+
else color.setRGB(1, 1, 1);
|
|
478
|
+
}
|
|
479
|
+
const normalMap = tex(bound?.normalTexture, "linear");
|
|
480
|
+
const roughnessMap = tex(bound?.roughnessTexture, "linear");
|
|
481
|
+
const metalnessMap = tex(bound?.metalnessTexture, "linear");
|
|
482
|
+
const aoMap = tex(bound?.occlusionTexture, "linear");
|
|
483
|
+
const emissiveMap = tex(bound?.emissiveTexture, "srgb");
|
|
484
|
+
const metalness = bound?.metalness ?? bound?.metalnessTexture?.scale?.[0] ?? (metalnessMap ? 1 : 0.1);
|
|
485
|
+
const roughness = bound?.roughness ?? bound?.roughnessTexture?.scale?.[0] ?? (roughnessMap ? 1 : 0.8);
|
|
486
|
+
const emissive = new THREE.Color(0);
|
|
487
|
+
if (bound?.emissiveColor) {
|
|
488
|
+
emissive.setRGB(bound.emissiveColor[0], bound.emissiveColor[1], bound.emissiveColor[2]);
|
|
489
|
+
} else if (emissiveMap) {
|
|
490
|
+
emissive.setRGB(1, 1, 1);
|
|
491
|
+
}
|
|
492
|
+
const opacityTex = bound?.opacityTexture;
|
|
493
|
+
const sharesColorMap = !!(opacityTex && opacityTex.path === bound?.colorTexture?.path);
|
|
494
|
+
const alphaMap = sharesColorMap ? null : tex(opacityTex, "linear");
|
|
495
|
+
const hasAlphaSource = opacity < 1 || sharesColorMap || !!alphaMap;
|
|
496
|
+
const threshold = bound?.opacityThreshold;
|
|
497
|
+
const alphaTest = threshold !== void 0 && threshold > 0 ? threshold : 0;
|
|
498
|
+
const transparent = alphaTest === 0 && hasAlphaSource;
|
|
499
|
+
const doubleSided = meshPrim.GetAttribute("doubleSided").Get() === true;
|
|
500
|
+
const params = {
|
|
501
|
+
color,
|
|
502
|
+
metalness,
|
|
503
|
+
roughness,
|
|
504
|
+
emissive,
|
|
505
|
+
transparent,
|
|
506
|
+
opacity,
|
|
507
|
+
...vertexColors ? { vertexColors: true } : {},
|
|
508
|
+
...alphaTest > 0 ? { alphaTest } : {},
|
|
509
|
+
side: doubleSided ? THREE.DoubleSide : THREE.FrontSide,
|
|
510
|
+
...map ? { map } : {},
|
|
511
|
+
...alphaMap ? { alphaMap } : {},
|
|
512
|
+
...normalMap ? { normalMap } : {},
|
|
513
|
+
...roughnessMap ? { roughnessMap } : {},
|
|
514
|
+
...metalnessMap ? { metalnessMap } : {},
|
|
515
|
+
...aoMap ? { aoMap } : {},
|
|
516
|
+
...emissiveMap ? { emissiveMap } : {}
|
|
517
|
+
};
|
|
518
|
+
const physical = bound !== void 0 && (bound.ior !== void 0 || bound.clearcoat !== void 0 || bound.clearcoatRoughness !== void 0 || bound.specularColor !== void 0 || bound.transmission !== void 0 || bound.clearcoatNormalTexture !== void 0);
|
|
519
|
+
const material = physical ? new THREE.MeshPhysicalMaterial(params) : new THREE.MeshStandardMaterial(params);
|
|
520
|
+
if (material instanceof THREE.MeshPhysicalMaterial && bound) {
|
|
521
|
+
if (bound.ior !== void 0) material.ior = bound.ior;
|
|
522
|
+
if (bound.clearcoat !== void 0) material.clearcoat = bound.clearcoat;
|
|
523
|
+
if (bound.clearcoatRoughness !== void 0)
|
|
524
|
+
material.clearcoatRoughness = bound.clearcoatRoughness;
|
|
525
|
+
if (bound.specularColor) {
|
|
526
|
+
material.specularColor.setRGB(
|
|
527
|
+
bound.specularColor[0],
|
|
528
|
+
bound.specularColor[1],
|
|
529
|
+
bound.specularColor[2]
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
if (bound.transmission !== void 0) material.transmission = bound.transmission;
|
|
533
|
+
if (bound.thickness !== void 0) material.thickness = bound.thickness;
|
|
534
|
+
const clearcoatNormalMap = tex(bound.clearcoatNormalTexture, "linear");
|
|
535
|
+
if (clearcoatNormalMap) material.clearcoatNormalMap = clearcoatNormalMap;
|
|
536
|
+
}
|
|
537
|
+
if (bound?.emissiveIntensity !== void 0) material.emissiveIntensity = bound.emissiveIntensity;
|
|
538
|
+
if (normalMap && bound?.normalTexture?.scale) {
|
|
539
|
+
material.normalScale.set(bound.normalTexture.scale[0] / 2, bound.normalTexture.scale[1] / 2);
|
|
540
|
+
}
|
|
541
|
+
if (bound?.name) material.name = bound.name;
|
|
542
|
+
return material;
|
|
543
|
+
}
|
|
544
|
+
function bindRobotMeshes(stage, robot3d, desc, options = {}) {
|
|
545
|
+
const loadVisuals = options.loadVisuals ?? true;
|
|
546
|
+
const loadCollisions = options.loadCollisions ?? false;
|
|
547
|
+
const gprimOptions = {
|
|
548
|
+
...options.textureProvider ? { textureProvider: options.textureProvider } : {},
|
|
549
|
+
...options.curveTubes ? { curveTubes: true } : {},
|
|
550
|
+
...options.onWarn ? { onWarn: options.onWarn } : {},
|
|
551
|
+
...options.mdl ? { mdl: options.mdl } : {}
|
|
552
|
+
};
|
|
553
|
+
for (const [key, link] of Object.entries(desc.links)) {
|
|
554
|
+
const linkObj = robot3d.getLinkObject(key);
|
|
555
|
+
const linkPrim = stage.GetPrimAtPath(link.primPath);
|
|
556
|
+
if (!linkObj || !linkPrim) continue;
|
|
557
|
+
const visualSet = new Set(link.visualPrims);
|
|
558
|
+
if (loadVisuals) {
|
|
559
|
+
for (const meshPath of link.visualPrims) {
|
|
560
|
+
attachGprim(stage, linkPrim, meshPath, linkObj, "visual", gprimOptions);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (loadCollisions) {
|
|
564
|
+
for (const meshPath of link.collisionPrims ?? []) {
|
|
565
|
+
if (loadVisuals && visualSet.has(meshPath)) continue;
|
|
566
|
+
attachGprim(stage, linkPrim, meshPath, linkObj, "collision", gprimOptions);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function bindSceneMeshes(stage, robot3d, desc, options = {}) {
|
|
572
|
+
const owned = /* @__PURE__ */ new Set();
|
|
573
|
+
for (const link of Object.values(desc.links)) {
|
|
574
|
+
for (const path of link.visualPrims) owned.add(path);
|
|
575
|
+
for (const path of link.collisionPrims ?? []) owned.add(path);
|
|
576
|
+
owned.add(link.primPath);
|
|
577
|
+
}
|
|
578
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
579
|
+
const containerFor = (prim) => {
|
|
580
|
+
const parent = prim.GetParent();
|
|
581
|
+
if (!parent || parent.IsPseudoRoot()) return robot3d;
|
|
582
|
+
let node = nodes.get(parent.GetPath());
|
|
583
|
+
if (!node) {
|
|
584
|
+
node = new THREE.Group();
|
|
585
|
+
node.name = parent.GetName();
|
|
586
|
+
node.userData.kind = "scene";
|
|
587
|
+
node.userData.primPath = parent.GetPath();
|
|
588
|
+
placeAtLocal(node, parent);
|
|
589
|
+
containerFor(parent).add(node);
|
|
590
|
+
nodes.set(parent.GetPath(), node);
|
|
591
|
+
}
|
|
592
|
+
return node;
|
|
593
|
+
};
|
|
594
|
+
const gprimOptions = {
|
|
595
|
+
...options.textureProvider ? { textureProvider: options.textureProvider } : {},
|
|
596
|
+
...options.curveTubes ? { curveTubes: true } : {},
|
|
597
|
+
...options.onWarn ? { onWarn: options.onWarn } : {},
|
|
598
|
+
...options.mdl ? { mdl: options.mdl } : {}
|
|
599
|
+
};
|
|
600
|
+
let attached = 0;
|
|
601
|
+
for (const prim of stage.Traverse()) {
|
|
602
|
+
if (!isRenderableGprim(prim) || owned.has(prim.GetPath())) continue;
|
|
603
|
+
if (prim.HasAPI(COLLISION_API) || isNonVisualPurpose(prim)) continue;
|
|
604
|
+
if ([...owned].some((path) => prim.GetPath().startsWith(`${path}/`))) continue;
|
|
605
|
+
const object = buildGprimObject(prim, stage, gprimOptions);
|
|
606
|
+
if (!object) continue;
|
|
607
|
+
object.name = prim.GetName();
|
|
608
|
+
object.userData.kind = "scene";
|
|
609
|
+
object.userData.primPath = prim.GetPath();
|
|
610
|
+
placeAtLocal(object, prim);
|
|
611
|
+
containerFor(prim).add(object);
|
|
612
|
+
nodes.set(prim.GetPath(), object);
|
|
613
|
+
attached++;
|
|
614
|
+
}
|
|
615
|
+
return attached;
|
|
616
|
+
}
|
|
617
|
+
function placeAtLocal(object, prim) {
|
|
618
|
+
object.matrixAutoUpdate = false;
|
|
619
|
+
object.matrix.fromArray(computeLocalTransform(prim).matrix);
|
|
620
|
+
object.matrixWorldNeedsUpdate = true;
|
|
621
|
+
}
|
|
622
|
+
function buildMeshMaterials(meshPrim, stage, textures, onWarn, mdl) {
|
|
623
|
+
const subsets = stage ? getMaterialSubsets(meshPrim) : [];
|
|
624
|
+
if (subsets.length === 0)
|
|
625
|
+
return buildMeshMaterial(meshPrim, stage, textures, void 0, onWarn, mdl);
|
|
626
|
+
return [
|
|
627
|
+
...subsets.map((s) => buildMeshMaterial(meshPrim, stage, textures, s.prim, onWarn, mdl)),
|
|
628
|
+
buildMeshMaterial(meshPrim, stage, textures, void 0, onWarn, mdl)
|
|
629
|
+
];
|
|
630
|
+
}
|
|
631
|
+
function attachGprim(stage, linkPrim, meshPath, parent, kind, options) {
|
|
632
|
+
const meshPrim = stage.GetPrimAtPath(meshPath);
|
|
633
|
+
if (!meshPrim) return;
|
|
634
|
+
const object = buildGprimObject(meshPrim, stage, options);
|
|
635
|
+
if (!object) return;
|
|
636
|
+
object.name = meshPrim.GetName();
|
|
637
|
+
object.userData.kind = kind;
|
|
638
|
+
object.userData.primPath = meshPath;
|
|
639
|
+
if (kind === "collision") object.visible = false;
|
|
640
|
+
object.matrixAutoUpdate = false;
|
|
641
|
+
object.matrix.fromArray(relativeTransform(linkPrim, meshPrim));
|
|
642
|
+
object.matrixWorldNeedsUpdate = true;
|
|
643
|
+
parent.add(object);
|
|
644
|
+
}
|
|
645
|
+
function relativeTransform(linkPrim, meshPrim) {
|
|
646
|
+
const chain = [];
|
|
647
|
+
let p = meshPrim;
|
|
648
|
+
const stop = linkPrim.GetPath();
|
|
649
|
+
while (p && p.GetPath() !== stop) {
|
|
650
|
+
chain.push(p);
|
|
651
|
+
p = p.GetParent();
|
|
652
|
+
}
|
|
653
|
+
chain.reverse();
|
|
654
|
+
let m = identity4();
|
|
655
|
+
for (const prim of chain) {
|
|
656
|
+
m = multiply(m, computeLocalTransform(prim).matrix);
|
|
657
|
+
}
|
|
658
|
+
return m;
|
|
659
|
+
}
|
|
660
|
+
function triangulate(faceVertexCounts, faceVertexIndices) {
|
|
661
|
+
const tris = [];
|
|
662
|
+
let offset = 0;
|
|
663
|
+
for (const count of faceVertexCounts) {
|
|
664
|
+
for (let k = 2; k < count; k++) {
|
|
665
|
+
tris.push(
|
|
666
|
+
faceVertexIndices[offset],
|
|
667
|
+
faceVertexIndices[offset + k - 1],
|
|
668
|
+
faceVertexIndices[offset + k]
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
offset += count;
|
|
672
|
+
}
|
|
673
|
+
return tris;
|
|
674
|
+
}
|
|
675
|
+
function triangulateBySubset(geometry, faceVertexCounts, faceVertexIndices, subsets) {
|
|
676
|
+
const faceStart = [];
|
|
677
|
+
let offset = 0;
|
|
678
|
+
for (const count of faceVertexCounts) {
|
|
679
|
+
faceStart.push(offset);
|
|
680
|
+
offset += count;
|
|
681
|
+
}
|
|
682
|
+
const tris = [];
|
|
683
|
+
const emit = (face) => {
|
|
684
|
+
const start = faceStart[face];
|
|
685
|
+
const count = faceVertexCounts[face];
|
|
686
|
+
for (let k = 2; k < count; k++) {
|
|
687
|
+
tris.push(
|
|
688
|
+
faceVertexIndices[start],
|
|
689
|
+
faceVertexIndices[start + k - 1],
|
|
690
|
+
faceVertexIndices[start + k]
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
const claimed = new Uint8Array(faceVertexCounts.length);
|
|
695
|
+
subsets.forEach((subset, index) => {
|
|
696
|
+
const begin2 = tris.length;
|
|
697
|
+
for (const face of subset.faces) {
|
|
698
|
+
if (face < 0 || face >= faceVertexCounts.length || claimed[face]) continue;
|
|
699
|
+
claimed[face] = 1;
|
|
700
|
+
emit(face);
|
|
701
|
+
}
|
|
702
|
+
if (tris.length > begin2) geometry.addGroup(begin2, tris.length - begin2, index);
|
|
703
|
+
});
|
|
704
|
+
const begin = tris.length;
|
|
705
|
+
for (let face = 0; face < faceVertexCounts.length; face++) {
|
|
706
|
+
if (!claimed[face]) emit(face);
|
|
707
|
+
}
|
|
708
|
+
if (tris.length > begin) geometry.addGroup(begin, tris.length - begin, subsets.length);
|
|
709
|
+
return tris;
|
|
710
|
+
}
|
|
711
|
+
function flat3(v) {
|
|
712
|
+
const out = new Array(v.length * 3);
|
|
713
|
+
for (let i = 0; i < v.length; i++) {
|
|
714
|
+
out[i * 3] = v[i][0];
|
|
715
|
+
out[i * 3 + 1] = v[i][1];
|
|
716
|
+
out[i * 3 + 2] = v[i][2];
|
|
717
|
+
}
|
|
718
|
+
return out;
|
|
719
|
+
}
|
|
720
|
+
function flat2(v) {
|
|
721
|
+
const out = new Array(v.length * 2);
|
|
722
|
+
for (let i = 0; i < v.length; i++) {
|
|
723
|
+
out[i * 2] = v[i][0];
|
|
724
|
+
out[i * 2 + 1] = v[i][1];
|
|
725
|
+
}
|
|
726
|
+
return out;
|
|
727
|
+
}
|
|
728
|
+
function isNumberArray(v) {
|
|
729
|
+
return Array.isArray(v) && v.every((n) => typeof n === "number");
|
|
730
|
+
}
|
|
731
|
+
function isVec3Array(v) {
|
|
732
|
+
return Array.isArray(v) && v.every((e) => Array.isArray(e) && e.length === 3);
|
|
733
|
+
}
|
|
734
|
+
function isVec2Array(v) {
|
|
735
|
+
return Array.isArray(v) && v.every((e) => Array.isArray(e) && e.length === 2);
|
|
736
|
+
}
|
|
737
|
+
var DEG2RAD = Math.PI / 180;
|
|
738
|
+
function createTextureProvider(resolver, baseUrl) {
|
|
739
|
+
const images = /* @__PURE__ */ new Map();
|
|
740
|
+
const imageFor = (url) => {
|
|
741
|
+
let p = images.get(url);
|
|
742
|
+
if (!p) {
|
|
743
|
+
p = loadImage(resolver, url);
|
|
744
|
+
images.set(url, p);
|
|
745
|
+
}
|
|
746
|
+
return p;
|
|
747
|
+
};
|
|
748
|
+
return (assetPath, options = {}) => {
|
|
749
|
+
let url;
|
|
750
|
+
try {
|
|
751
|
+
url = resolver.resolve(assetPath, baseUrl);
|
|
752
|
+
} catch {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
const texture = new THREE.Texture();
|
|
756
|
+
texture.colorSpace = options.colorSpace === "linear" ? THREE.NoColorSpace : THREE.SRGBColorSpace;
|
|
757
|
+
texture.wrapS = toThreeWrap(options.wrapS);
|
|
758
|
+
texture.wrapT = toThreeWrap(options.wrapT);
|
|
759
|
+
if (options.channel !== void 0) texture.channel = options.channel;
|
|
760
|
+
applyTransform(texture, options.transform);
|
|
761
|
+
imageFor(url).then((image) => {
|
|
762
|
+
texture.image = image;
|
|
763
|
+
texture.needsUpdate = true;
|
|
764
|
+
}).catch(() => {
|
|
765
|
+
});
|
|
766
|
+
return texture;
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
async function loadImage(resolver, url) {
|
|
770
|
+
const bytes = resolver.fetchBytes ? await resolver.fetchBytes(url) : new TextEncoder().encode(await resolver.fetchText(url));
|
|
771
|
+
const blob = new Blob([bytes], { type: mimeOf(url) });
|
|
772
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
773
|
+
try {
|
|
774
|
+
return await new Promise((resolve, reject) => {
|
|
775
|
+
const img = new Image();
|
|
776
|
+
img.onload = () => resolve(img);
|
|
777
|
+
img.onerror = () => reject(new Error(`failed to decode texture: ${url}`));
|
|
778
|
+
img.src = objectUrl;
|
|
779
|
+
});
|
|
780
|
+
} finally {
|
|
781
|
+
URL.revokeObjectURL(objectUrl);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function toThreeWrap(wrap) {
|
|
785
|
+
switch (wrap) {
|
|
786
|
+
case "clamp":
|
|
787
|
+
case "black":
|
|
788
|
+
return THREE.ClampToEdgeWrapping;
|
|
789
|
+
case "mirror":
|
|
790
|
+
return THREE.MirroredRepeatWrapping;
|
|
791
|
+
default:
|
|
792
|
+
return THREE.RepeatWrapping;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function applyTransform(texture, t) {
|
|
796
|
+
if (!t) return;
|
|
797
|
+
if (t.scale) texture.repeat.set(t.scale[0], t.scale[1]);
|
|
798
|
+
if (t.translation) texture.offset.set(t.translation[0], t.translation[1]);
|
|
799
|
+
if (t.rotation !== void 0) {
|
|
800
|
+
texture.rotation = t.rotation * DEG2RAD;
|
|
801
|
+
texture.center.set(0, 0);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function mimeOf(url) {
|
|
805
|
+
if (/\.jpe?g$/i.test(url)) return "image/jpeg";
|
|
806
|
+
if (/\.webp$/i.test(url)) return "image/webp";
|
|
807
|
+
return "image/png";
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/three/ThreeUsdRobotLoader.ts
|
|
811
|
+
var ThreeUsdRobotLoader = class {
|
|
812
|
+
options;
|
|
813
|
+
constructor(options = {}) {
|
|
814
|
+
this.options = options;
|
|
815
|
+
}
|
|
816
|
+
get resolver() {
|
|
817
|
+
return this.options.assetResolver ?? new DefaultAssetResolver();
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Fetch an asset by URL and build the robot. `.usdz` packages are unzipped and
|
|
821
|
+
* composed from their entries; everything else is sniffed for the zip / crate
|
|
822
|
+
* magic and parsed as USDZ, binary USDC, or USDA text.
|
|
823
|
+
*/
|
|
824
|
+
async loadAsync(url) {
|
|
825
|
+
const bytes = await this.fetchRootBytes(url);
|
|
826
|
+
if (/\.usdz$/i.test(url)) return this.parseUsdz(bytes);
|
|
827
|
+
return this.parse(bytes, url);
|
|
828
|
+
}
|
|
829
|
+
async fetchRootBytes(url) {
|
|
830
|
+
const resolver = this.resolver;
|
|
831
|
+
if (resolver.fetchBytes) return resolver.fetchBytes(url);
|
|
832
|
+
return new TextEncoder().encode(await resolver.fetchText(url));
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Build a robot from in-memory content — no fetch involved. Accepts USDA
|
|
836
|
+
* source text, or an `ArrayBuffer` / typed array / `Blob` (e.g. a dropped
|
|
837
|
+
* `File`) holding USDA text, a binary crate, or a `.usdz` package; binary
|
|
838
|
+
* input is sniffed for the zip / crate magic. `baseUrl` anchors relative
|
|
839
|
+
* references, payloads, and texture paths of non-package input.
|
|
840
|
+
*/
|
|
841
|
+
async parse(data, baseUrl = "") {
|
|
842
|
+
return this.buildFromStage(await this.openSource(data, baseUrl));
|
|
843
|
+
}
|
|
844
|
+
/** Build a robot from a `.usdz` package (bytes, `ArrayBuffer`, or `Blob`). */
|
|
845
|
+
async parseUsdz(data) {
|
|
846
|
+
return this.buildFromStage(await this.openUsdzStage(await toBytes(data)));
|
|
847
|
+
}
|
|
848
|
+
/** Build a robot from a binary crate (`.usdc` / binary `.usd`). */
|
|
849
|
+
async parseCrate(data, baseUrl = "") {
|
|
850
|
+
const stage = await this.composeStageFromBytes(await toBytes(data), baseUrl, this.resolver);
|
|
851
|
+
return this.buildFromStage({ stage, baseUrl, resolver: this.resolver });
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Parse + compose in-memory content (same formats as {@link parse}) into the
|
|
855
|
+
* Three.js-independent robot IR.
|
|
856
|
+
*/
|
|
857
|
+
async parseRobotDescription(data, baseUrl = "") {
|
|
858
|
+
const { stage } = await this.openSource(data, baseUrl);
|
|
859
|
+
return extractRobotDescription(stage, this.extractOptions());
|
|
860
|
+
}
|
|
861
|
+
/** Sniff in-memory content (text / zip / crate) and compose it into a stage. */
|
|
862
|
+
async openSource(data, baseUrl) {
|
|
863
|
+
const resolver = this.resolver;
|
|
864
|
+
if (typeof data === "string") {
|
|
865
|
+
return { stage: await this.composeStage(data, baseUrl, resolver), baseUrl, resolver };
|
|
866
|
+
}
|
|
867
|
+
const bytes = await toBytes(data);
|
|
868
|
+
if (isZip(bytes)) return this.openUsdzStage(bytes);
|
|
869
|
+
const stage = CrateReader.isCrate(bytes) ? await this.composeStageFromBytes(bytes, baseUrl, resolver) : await this.composeStage(new TextDecoder().decode(bytes), baseUrl, resolver);
|
|
870
|
+
return { stage, baseUrl, resolver };
|
|
871
|
+
}
|
|
872
|
+
async openUsdzStage(bytes) {
|
|
873
|
+
const pkg = openUsdz(bytes);
|
|
874
|
+
const rootBytes = await pkg.resolver.fetchBytes(pkg.rootEntry);
|
|
875
|
+
const stage = await this.composeStageFromBytes(rootBytes, pkg.rootEntry, pkg.resolver);
|
|
876
|
+
return { stage, baseUrl: pkg.rootEntry, resolver: pkg.resolver };
|
|
877
|
+
}
|
|
878
|
+
async buildFromStage({ stage, baseUrl, resolver }) {
|
|
879
|
+
const robot = extractRobotDescription(stage, this.extractOptions());
|
|
880
|
+
const tree = buildKinematicTree(robot);
|
|
881
|
+
const robot3d = new ThreeUsdRobot(robot, tree, this.robotOptions());
|
|
882
|
+
robot3d.stage = stage;
|
|
883
|
+
const loadVisuals = this.options.loadVisuals ?? true;
|
|
884
|
+
const loadCollisions = this.options.loadCollisions ?? false;
|
|
885
|
+
const isStaticScene = Object.keys(robot.links).length === 0 && Object.keys(robot.joints).length === 0;
|
|
886
|
+
if (isStaticScene && this.options.loadSceneGeometry === void 0) {
|
|
887
|
+
this.options.onWarn?.(
|
|
888
|
+
"no articulation found (0 links / 0 joints); rendering the stage as static scene geometry"
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
const loadScene = this.options.loadSceneGeometry ?? isStaticScene;
|
|
892
|
+
if (loadVisuals || loadCollisions || loadScene) {
|
|
893
|
+
const textureProvider = this.options.loadTextures ?? true ? createTextureProvider(resolver, baseUrl) : void 0;
|
|
894
|
+
const curveTubes = this.options.curveTubes ?? false;
|
|
895
|
+
const onWarn = this.options.onWarn;
|
|
896
|
+
const mdl = await loadMdlModules(stage, resolver, baseUrl);
|
|
897
|
+
if (loadVisuals || loadCollisions) {
|
|
898
|
+
bindRobotMeshes(stage, robot3d, robot, {
|
|
899
|
+
loadVisuals,
|
|
900
|
+
loadCollisions,
|
|
901
|
+
...textureProvider ? { textureProvider } : {},
|
|
902
|
+
...curveTubes ? { curveTubes } : {},
|
|
903
|
+
...onWarn ? { onWarn } : {},
|
|
904
|
+
...mdl ? { mdl } : {}
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
if (loadScene) {
|
|
908
|
+
bindSceneMeshes(stage, robot3d, robot, {
|
|
909
|
+
...textureProvider ? { textureProvider } : {},
|
|
910
|
+
...curveTubes ? { curveTubes } : {},
|
|
911
|
+
...onWarn ? { onWarn } : {},
|
|
912
|
+
...mdl ? { mdl } : {}
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
this.warnUnsupportedGprims(stage);
|
|
916
|
+
}
|
|
917
|
+
return robot3d;
|
|
918
|
+
}
|
|
919
|
+
/** Surface recognized-but-unrenderable gprim schemas instead of silence (M18). */
|
|
920
|
+
warnUnsupportedGprims(stage) {
|
|
921
|
+
const firstByType = /* @__PURE__ */ new Map();
|
|
922
|
+
for (const prim of stage.Traverse()) {
|
|
923
|
+
const type = prim.GetTypeName();
|
|
924
|
+
if (isUnsupportedGprim(prim) && !firstByType.has(type)) {
|
|
925
|
+
firstByType.set(type, prim.GetPath());
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
for (const [type, path] of firstByType) {
|
|
929
|
+
this.options.onWarn?.(`${type} gprims are not supported yet; skipping (e.g. ${path})`);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
async composeStage(text, baseUrl, resolver) {
|
|
933
|
+
const composeOptions = this.options.onWarn ? { onWarn: this.options.onWarn } : {};
|
|
934
|
+
return Stage.OpenFromFile(await composeLayer(text, baseUrl, resolver, composeOptions));
|
|
935
|
+
}
|
|
936
|
+
/** Compose a layer from raw bytes, sniffing binary crate vs USDA text. */
|
|
937
|
+
async composeStageFromBytes(bytes, baseUrl, resolver) {
|
|
938
|
+
const composeOptions = this.options.onWarn ? { onWarn: this.options.onWarn } : {};
|
|
939
|
+
const composed = CrateReader.isCrate(bytes) ? await composeFile(
|
|
940
|
+
crateToUsdaFile(new CrateReader(bytes)),
|
|
941
|
+
baseUrl,
|
|
942
|
+
resolver,
|
|
943
|
+
composeOptions
|
|
944
|
+
) : await composeLayer(new TextDecoder().decode(bytes), baseUrl, resolver, composeOptions);
|
|
945
|
+
return Stage.OpenFromFile(composed);
|
|
946
|
+
}
|
|
947
|
+
robotOptions() {
|
|
948
|
+
return {
|
|
949
|
+
...this.options.worldUp ? { worldUp: this.options.worldUp } : { upAxisConversion: this.options.upAxisConversion ?? "auto" },
|
|
950
|
+
...this.options.clampJointLimits !== void 0 ? { clampJointLimits: this.options.clampJointLimits } : {},
|
|
951
|
+
...this.options.unitScale !== void 0 ? { unitScale: this.options.unitScale } : {},
|
|
952
|
+
...this.options.applyDriveTargetsAsInitialPose !== void 0 ? { applyInitialPose: this.options.applyDriveTargetsAsInitialPose } : {},
|
|
953
|
+
...this.options.debugBakedTransforms !== void 0 ? { debugBakedTransforms: this.options.debugBakedTransforms } : {}
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
extractOptions() {
|
|
957
|
+
return {
|
|
958
|
+
...this.options.robotName !== void 0 ? { robotName: this.options.robotName } : {},
|
|
959
|
+
...this.options.onWarn !== void 0 ? { onWarn: this.options.onWarn } : {}
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
export { ThreeUsdRobotLoader, bindRobotMeshes, bindSceneMeshes, buildGprimGeometry, buildGprimObject, buildMeshGeometry, buildMeshMaterial, createTextureProvider };
|
|
965
|
+
//# sourceMappingURL=chunk-5SVRTKS4.js.map
|
|
966
|
+
//# sourceMappingURL=chunk-5SVRTKS4.js.map
|