@jfk-solutions/demo3d-file-format 1.7.0 → 1.9.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 +289 -1
- package/dist/examples/export-embedded-viewer-standalone.html +193 -0
- package/dist/examples/three-render-smoke-standalone.html +4997 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +20 -3
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +89 -11
- package/dist/model.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +135 -9
- package/dist/parser.js.map +1 -1
- package/dist/raw3d-animation.d.ts +63 -0
- package/dist/raw3d-animation.d.ts.map +1 -0
- package/dist/raw3d-animation.js +200 -0
- package/dist/raw3d-animation.js.map +1 -0
- package/dist/raw3d-effects.d.ts +66 -0
- package/dist/raw3d-effects.d.ts.map +1 -0
- package/dist/raw3d-effects.js +172 -0
- package/dist/raw3d-effects.js.map +1 -0
- package/dist/raw3d-skinning.d.ts +8 -0
- package/dist/raw3d-skinning.d.ts.map +1 -0
- package/dist/raw3d-skinning.js +37 -0
- package/dist/raw3d-skinning.js.map +1 -0
- package/dist/raw3d.d.ts +84 -4
- package/dist/raw3d.d.ts.map +1 -1
- package/dist/raw3d.js +111 -9
- package/dist/raw3d.js.map +1 -1
- package/dist/rigid-interpolation.d.ts +16 -0
- package/dist/rigid-interpolation.d.ts.map +1 -0
- package/dist/rigid-interpolation.js +52 -0
- package/dist/rigid-interpolation.js.map +1 -0
- package/dist/three/backend.d.ts +13 -0
- package/dist/three/backend.d.ts.map +1 -0
- package/dist/three/backend.js +9 -0
- package/dist/three/backend.js.map +1 -0
- package/dist/three/effects.d.ts +7 -0
- package/dist/three/effects.d.ts.map +1 -0
- package/dist/three/effects.js +127 -0
- package/dist/three/effects.js.map +1 -0
- package/dist/three/index.d.ts +5 -1
- package/dist/three/index.d.ts.map +1 -1
- package/dist/three/index.js +35 -1
- package/dist/three/index.js.map +1 -1
- package/dist/three/native-lighting.d.ts +5 -0
- package/dist/three/native-lighting.d.ts.map +1 -0
- package/dist/three/native-lighting.js +86 -0
- package/dist/three/native-lighting.js.map +1 -0
- package/dist/three/parallax.d.ts +5 -0
- package/dist/three/parallax.d.ts.map +1 -0
- package/dist/three/parallax.js +63 -0
- package/dist/three/parallax.js.map +1 -0
- package/dist/three/presentation.d.ts +31 -0
- package/dist/three/presentation.d.ts.map +1 -0
- package/dist/three/presentation.js +137 -0
- package/dist/three/presentation.js.map +1 -0
- package/dist/three/raw3d.d.ts +27 -0
- package/dist/three/raw3d.d.ts.map +1 -1
- package/dist/three/raw3d.js +536 -25
- package/dist/three/raw3d.js.map +1 -1
- package/dist/three/renderer.d.ts.map +1 -1
- package/dist/three/renderer.js +3 -0
- package/dist/three/renderer.js.map +1 -1
- package/dist/three/skinning.d.ts +11 -0
- package/dist/three/skinning.d.ts.map +1 -0
- package/dist/three/skinning.js +92 -0
- package/dist/three/skinning.js.map +1 -0
- package/dist/three/tsl.d.ts +60 -0
- package/dist/three/tsl.d.ts.map +1 -0
- package/dist/three/tsl.js +3 -0
- package/dist/three/tsl.js.map +1 -0
- package/dist/three/webgpu-native.d.ts +3 -0
- package/dist/three/webgpu-native.d.ts.map +1 -0
- package/dist/three/webgpu-native.js +58 -0
- package/dist/three/webgpu-native.js.map +1 -0
- package/dist/three/webgpu-relief.d.ts +3 -0
- package/dist/three/webgpu-relief.d.ts.map +1 -0
- package/dist/three/webgpu-relief.js +61 -0
- package/dist/three/webgpu-relief.js.map +1 -0
- package/dist/xml.d.ts +4 -1
- package/dist/xml.d.ts.map +1 -1
- package/dist/xml.js +14 -3
- package/dist/xml.js.map +1 -1
- package/package.json +6 -2
package/dist/three/raw3d.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { bindRaw3DThreeSkin } from "./skinning.js";
|
|
2
|
+
import { applyRaw3DThreeEffects, configureRaw3DDrawable } from "./effects.js";
|
|
3
|
+
import { raw3DNodeBackend } from "./backend.js";
|
|
1
4
|
import { Raw3DMaterial, Raw3DMesh, Raw3DPackage, Raw3DProject } from "../raw3d.js";
|
|
2
5
|
export async function createRaw3DThreeScene(parsed, options = {}) {
|
|
3
6
|
const three = await resolveThree(options);
|
|
@@ -14,6 +17,9 @@ export async function createRaw3DThreeScene(parsed, options = {}) {
|
|
|
14
17
|
};
|
|
15
18
|
}
|
|
16
19
|
export async function createRaw3DThreeGroup(parsed, options = {}) {
|
|
20
|
+
return (await buildRaw3DThreeGroup(parsed, options)).group;
|
|
21
|
+
}
|
|
22
|
+
async function buildRaw3DThreeGroup(parsed, options) {
|
|
17
23
|
const three = await resolveThree(options);
|
|
18
24
|
const project = parsed instanceof Raw3DPackage ? parsed.model : parsed;
|
|
19
25
|
const warnings = [];
|
|
@@ -36,12 +42,16 @@ export async function createRaw3DThreeGroup(parsed, options = {}) {
|
|
|
36
42
|
};
|
|
37
43
|
const textures = await loadTextures(project, three, stats, warn);
|
|
38
44
|
const materials = project.materials.map((material) => createMaterial(material, textures, three));
|
|
45
|
+
for (const material of materials)
|
|
46
|
+
material.userData.raw3DHeightChannel = options.parallaxHeightChannel ?? "a";
|
|
39
47
|
const defaultMaterial = new three.MeshStandardMaterial({ color: 0x9aa0a6, roughness: 0.8 });
|
|
40
48
|
const geometryCache = new Map();
|
|
41
49
|
const objects = new Map();
|
|
50
|
+
const sourceNodes = new Map(project.nodes.map((node) => [node.index, node]));
|
|
42
51
|
const root = new three.Group();
|
|
43
52
|
root.name = "RAW3D";
|
|
44
53
|
for (const node of project.nodes) {
|
|
54
|
+
const annotationHidden = !options.showAnnotations && isAnnotationNode(node, sourceNodes);
|
|
45
55
|
const object = new three.Group();
|
|
46
56
|
object.name = node.name;
|
|
47
57
|
applyTransform(object, node);
|
|
@@ -61,10 +71,11 @@ export async function createRaw3DThreeGroup(parsed, options = {}) {
|
|
|
61
71
|
endPosition: text.endPosition
|
|
62
72
|
} : undefined,
|
|
63
73
|
interactionMode: node.interactionMode,
|
|
74
|
+
annotationHidden,
|
|
64
75
|
customAttributes: Object.fromEntries(node.customAttributes)
|
|
65
76
|
};
|
|
66
77
|
objects.set(node.index, object);
|
|
67
|
-
if (node.meshIndex !== undefined) {
|
|
78
|
+
if (!annotationHidden && node.meshIndex !== undefined) {
|
|
68
79
|
const sourceMesh = project.meshes[node.meshIndex];
|
|
69
80
|
if (!sourceMesh) {
|
|
70
81
|
warn({
|
|
@@ -93,21 +104,36 @@ export async function createRaw3DThreeGroup(parsed, options = {}) {
|
|
|
93
104
|
}
|
|
94
105
|
if (geometry) {
|
|
95
106
|
const nodeMaterials = resolveNodeMaterials(node, sourceMesh, project, materials, defaultMaterial);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
const drawable = createDrawable(sourceMesh, geometry, nodeMaterials, three);
|
|
108
|
+
drawable.name = `${node.name} ${sourceMesh.meshType}`;
|
|
109
|
+
drawable.castShadow = node.castsShadow && drawable instanceof three.Mesh;
|
|
110
|
+
drawable.visible = node.visible;
|
|
111
|
+
drawable.receiveShadow = drawable instanceof three.Mesh;
|
|
112
|
+
drawable.userData.raw3d = {
|
|
113
|
+
kind: primitiveKind(sourceMesh.meshType),
|
|
114
|
+
nodeIndex: node.index,
|
|
115
|
+
meshIndex: node.meshIndex,
|
|
116
|
+
meshType: sourceMesh.meshType,
|
|
117
|
+
sourceVisible: node.visible
|
|
118
|
+
};
|
|
119
|
+
object.add(drawable);
|
|
102
120
|
stats.meshes += 1;
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
123
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
124
|
+
if (!annotationHidden && text) {
|
|
125
|
+
const textMaterialIndex = text.materialIndex ?? node.materialIndices[0];
|
|
126
|
+
const sourceMaterial = textMaterialIndex === undefined ? undefined : project.materials[textMaterialIndex];
|
|
127
|
+
const sprite = createTextSprite(text, sourceMaterial, three);
|
|
128
|
+
if (sprite) {
|
|
129
|
+
sprite.name = `${node.name} Text`;
|
|
130
|
+
sprite.visible = node.visible;
|
|
131
|
+
sprite.userData.raw3d = { kind: "text", nodeIndex: node.index, textIndex: node.textIndex, sourceVisible: node.visible };
|
|
132
|
+
object.add(sprite);
|
|
133
|
+
}
|
|
110
134
|
}
|
|
135
|
+
}
|
|
136
|
+
for (const [sourceIndex, source] of project.lights.entries()) {
|
|
111
137
|
const parent = source.nodeIndex === undefined ? undefined : objects.get(source.nodeIndex);
|
|
112
138
|
if (!parent) {
|
|
113
139
|
warn({
|
|
@@ -127,10 +153,12 @@ export async function createRaw3DThreeGroup(parsed, options = {}) {
|
|
|
127
153
|
light = new three.AmbientLight(color, 1);
|
|
128
154
|
break;
|
|
129
155
|
case "point":
|
|
130
|
-
light = new three.PointLight(color, 1, 0, 2);
|
|
156
|
+
light = raw3DNodeBackend(three)?.point() ?? new three.PointLight(color, 1, 0, 2);
|
|
157
|
+
light.color.copy(color);
|
|
131
158
|
break;
|
|
132
159
|
case "spot":
|
|
133
|
-
light = new three.SpotLight(color, 1);
|
|
160
|
+
light = raw3DNodeBackend(three)?.spot() ?? new three.SpotLight(color, 1);
|
|
161
|
+
light.color.copy(color);
|
|
134
162
|
break;
|
|
135
163
|
default: {
|
|
136
164
|
const directional = new three.DirectionalLight(color, 1);
|
|
@@ -141,8 +169,10 @@ export async function createRaw3DThreeGroup(parsed, options = {}) {
|
|
|
141
169
|
}
|
|
142
170
|
}
|
|
143
171
|
light.name = `${parent.name} ${source.type} light`;
|
|
172
|
+
light.visible = source.enabled;
|
|
144
173
|
light.userData.raw3d = {
|
|
145
174
|
kind: "light",
|
|
175
|
+
index: source.index ?? sourceIndex,
|
|
146
176
|
nodeIndex: source.nodeIndex,
|
|
147
177
|
materialIndex: source.materialIndex,
|
|
148
178
|
type: source.type,
|
|
@@ -177,7 +207,327 @@ export async function createRaw3DThreeGroup(parsed, options = {}) {
|
|
|
177
207
|
// Match the metadata shape used by the Demo3D adapter so generic viewers can
|
|
178
208
|
// display stats without special-casing the property name.
|
|
179
209
|
root.userData.demo3d = { kind: "raw3d-package", stats, warnings };
|
|
180
|
-
|
|
210
|
+
const skinBindings = new Map();
|
|
211
|
+
for (const node of project.nodes) {
|
|
212
|
+
const owner = objects.get(node.index);
|
|
213
|
+
const drawable = owner.children.find(child => child instanceof three.Mesh);
|
|
214
|
+
if (drawable && node.meshIndex !== undefined && project.meshes[node.meshIndex]?.bones.length) {
|
|
215
|
+
const binding = bindRaw3DThreeSkin(drawable, node.meshIndex, project, objects, three);
|
|
216
|
+
skinBindings.set(binding.drawable, binding);
|
|
217
|
+
}
|
|
218
|
+
for (const child of owner.children)
|
|
219
|
+
if (["mesh", "lines", "points"].includes(child.userData.raw3d?.kind))
|
|
220
|
+
configureRaw3DDrawable(child, node.isBillboard, node.isDecal, three);
|
|
221
|
+
}
|
|
222
|
+
root.updateMatrixWorld(true);
|
|
223
|
+
for (const binding of skinBindings.values())
|
|
224
|
+
binding.update();
|
|
225
|
+
applyRaw3DThreeEffects(root, project.animation.effects.sample(0), project, three);
|
|
226
|
+
return { group: root, three, project, objects, materials, textures, geometryCache, defaultMaterial, skinBindings };
|
|
227
|
+
}
|
|
228
|
+
/** Optional Three.js binding. Animation data and interpolation live in the core. */
|
|
229
|
+
export async function createRaw3DThreePlayback(parsed, options = {}) {
|
|
230
|
+
const project = parsed instanceof Raw3DPackage ? parsed.model : parsed;
|
|
231
|
+
const animation = project.animation;
|
|
232
|
+
const resources = await buildRaw3DThreeGroup(parsed, options);
|
|
233
|
+
const { group, three, objects, materials, geometryCache, defaultMaterial, skinBindings } = resources;
|
|
234
|
+
let frame = animation.sampleFrame(0);
|
|
235
|
+
for (const warning of animation.warnings) {
|
|
236
|
+
const item = { code: warning.code, message: warning.message };
|
|
237
|
+
group.userData.raw3d.warnings.push(item);
|
|
238
|
+
options.onWarning?.(item);
|
|
239
|
+
}
|
|
240
|
+
const variants = new Map();
|
|
241
|
+
const archivedStreams = new Map([...resources.textures].filter(([key]) => key.startsWith("stream:")));
|
|
242
|
+
const streamRevisions = new Map();
|
|
243
|
+
const streamWarnings = new Set();
|
|
244
|
+
const ownedMaterials = new Set([...materials, defaultMaterial]);
|
|
245
|
+
const ownedTextures = new Set(resources.textures.values());
|
|
246
|
+
const capture = () => group.traverse(object => {
|
|
247
|
+
const drawable = object;
|
|
248
|
+
if (drawable.material)
|
|
249
|
+
for (const material of Array.isArray(drawable.material) ? drawable.material : [drawable.material]) {
|
|
250
|
+
ownedMaterials.add(material);
|
|
251
|
+
const map = material.map;
|
|
252
|
+
if (map)
|
|
253
|
+
ownedTextures.add(map);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
capture();
|
|
257
|
+
let disposed = false;
|
|
258
|
+
const palette = (node, state, mesh) => {
|
|
259
|
+
const layerMaterial = state.layerIndex === undefined ? undefined : project.layers[state.layerIndex]?.materialIndex;
|
|
260
|
+
return Array.from({ length: Math.max(1, mesh.indexBufferIndices.length) }, (_, slot) => {
|
|
261
|
+
const index = node.materialIndices[slot] ?? node.materialIndices[0] ?? layerMaterial;
|
|
262
|
+
const base = (index === undefined ? defaultMaterial : materials[index] ?? defaultMaterial);
|
|
263
|
+
const values = state.materialProperties[slot] ?? {};
|
|
264
|
+
const streamIndex = values.StreamableTexture ?? (index === undefined ? -1 : project.materials[index]?.streamableTextureIndex ?? -1);
|
|
265
|
+
const ordinaryMap = values.Texture === undefined ? base.map : values.Texture < 0 ? null : resources.textures.get(`${values.Texture}:color`);
|
|
266
|
+
const streamMap = streamIndex < 0 ? undefined : resources.textures.get(`stream:${streamIndex}:color`);
|
|
267
|
+
const mapSource = streamMap ?? ordinaryMap;
|
|
268
|
+
if (streamIndex >= 0 && !streamMap && !streamWarnings.has(streamIndex)) {
|
|
269
|
+
streamWarnings.add(streamIndex);
|
|
270
|
+
const warning = { code: "RAW3D_STREAM_TEXTURE_UNAVAILABLE", message: `Streamable texture ${streamIndex} has no archived image or resolved external frame.` };
|
|
271
|
+
group.userData.raw3d.warnings.push(warning);
|
|
272
|
+
options.onWarning?.(warning);
|
|
273
|
+
}
|
|
274
|
+
const key = `${node.index}:${slot}:${index ?? -1}:${values.Texture ?? "base"}:${values.NormalMap ?? values.NormalTexture ?? "base"}:stream:${streamIndex}`;
|
|
275
|
+
let entry = variants.get(key);
|
|
276
|
+
if (!entry) {
|
|
277
|
+
const material = base.clone();
|
|
278
|
+
const normalIndex = values.NormalMap ?? values.NormalTexture;
|
|
279
|
+
const normalSource = normalIndex === undefined ? base.normalMap : normalIndex < 0 ? null : resources.textures.get(`${normalIndex}:normal`);
|
|
280
|
+
if ((values.Texture !== undefined && values.Texture >= 0 && !mapSource) || (normalIndex !== undefined && normalIndex >= 0 && !normalSource))
|
|
281
|
+
throw new Error("An animated RAW3D texture resource could not be loaded.");
|
|
282
|
+
entry = { base, material, map: mapSource?.clone(), normalMap: normalSource?.clone(), mapSource: mapSource ?? undefined };
|
|
283
|
+
variants.set(key, entry);
|
|
284
|
+
ownedMaterials.add(material);
|
|
285
|
+
if (entry.map)
|
|
286
|
+
ownedTextures.add(entry.map);
|
|
287
|
+
if (entry.normalMap)
|
|
288
|
+
ownedTextures.add(entry.normalMap);
|
|
289
|
+
}
|
|
290
|
+
if (entry.mapSource !== (mapSource ?? undefined)) {
|
|
291
|
+
if (entry.map) {
|
|
292
|
+
entry.map.dispose();
|
|
293
|
+
ownedTextures.delete(entry.map);
|
|
294
|
+
}
|
|
295
|
+
entry.map = mapSource?.clone();
|
|
296
|
+
entry.mapSource = mapSource ?? undefined;
|
|
297
|
+
if (entry.map)
|
|
298
|
+
ownedTextures.add(entry.map);
|
|
299
|
+
entry.material.needsUpdate = true;
|
|
300
|
+
}
|
|
301
|
+
if (streamIndex >= 0 && entry.streamRevision !== streamRevisions.get(streamIndex)) {
|
|
302
|
+
if (entry.map)
|
|
303
|
+
entry.map.needsUpdate = true;
|
|
304
|
+
entry.streamRevision = streamRevisions.get(streamIndex);
|
|
305
|
+
}
|
|
306
|
+
const material = entry.material;
|
|
307
|
+
const wasTransparent = material.transparent;
|
|
308
|
+
material.copy(base);
|
|
309
|
+
material.map = entry.map ?? null;
|
|
310
|
+
material.normalMap = entry.normalMap ?? null;
|
|
311
|
+
if (values.Diffuse !== undefined) {
|
|
312
|
+
material.color.setHex(values.Diffuse & 0xffffff, three.SRGBColorSpace);
|
|
313
|
+
material.opacity = (values.Diffuse >>> 24) / 255;
|
|
314
|
+
}
|
|
315
|
+
if (values.Transparency !== undefined)
|
|
316
|
+
material.opacity = 1 - values.Transparency;
|
|
317
|
+
if (values.Reflectivity !== undefined)
|
|
318
|
+
material.roughness = .9 - clamp01(values.Reflectivity) * .75;
|
|
319
|
+
if (values.Luminosity !== undefined && material.emissive)
|
|
320
|
+
material.emissive.copy(material.color).multiplyScalar(values.Luminosity);
|
|
321
|
+
if (values.NormalScale !== undefined && material.normalScale)
|
|
322
|
+
material.normalScale.setScalar(values.NormalScale);
|
|
323
|
+
if (values.NormalDepth !== undefined)
|
|
324
|
+
material.userData.raw3dNormalDepth = values.NormalDepth;
|
|
325
|
+
for (const texture of [material.map, material.normalMap]) {
|
|
326
|
+
if (!texture)
|
|
327
|
+
continue;
|
|
328
|
+
texture.offset.set(values.U ?? base.map?.offset.x ?? 0, values.V ?? base.map?.offset.y ?? 0);
|
|
329
|
+
texture.repeat.set(values.ScaleU ?? base.map?.repeat.x ?? 1, values.ScaleV ?? base.map?.repeat.y ?? 1);
|
|
330
|
+
if (values.U !== undefined)
|
|
331
|
+
texture.offset.x = values.U;
|
|
332
|
+
if (values.V !== undefined)
|
|
333
|
+
texture.offset.y = values.V;
|
|
334
|
+
if (values.ScaleU !== undefined)
|
|
335
|
+
texture.repeat.x = values.ScaleU;
|
|
336
|
+
if (values.ScaleV !== undefined)
|
|
337
|
+
texture.repeat.y = values.ScaleV;
|
|
338
|
+
if (values.U !== undefined || values.V !== undefined)
|
|
339
|
+
texture.wrapS = texture.wrapT = three.RepeatWrapping;
|
|
340
|
+
}
|
|
341
|
+
material.transparent = material.opacity < 1 || base.transparent;
|
|
342
|
+
material.depthWrite = base.depthWrite && !material.transparent;
|
|
343
|
+
if (wasTransparent !== material.transparent)
|
|
344
|
+
material.needsUpdate = true;
|
|
345
|
+
return material;
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
let seekRevision = 0, pendingFrames;
|
|
349
|
+
const apply = (time) => {
|
|
350
|
+
if (disposed)
|
|
351
|
+
throw new Error("RAW3D playback has been disposed.");
|
|
352
|
+
frame = animation.sampleFrame(time);
|
|
353
|
+
const states = frame.nodes;
|
|
354
|
+
// Detach first so valid hierarchy changes do not depend on source node order.
|
|
355
|
+
for (const object of objects.values())
|
|
356
|
+
group.add(object);
|
|
357
|
+
for (const node of project.nodes) {
|
|
358
|
+
const object = objects.get(node.index), state = states.get(node.index);
|
|
359
|
+
object.position.set(state.location[0], state.location[1], -state.location[2]);
|
|
360
|
+
object.quaternion.set(-state.quaternion[0], -state.quaternion[1], state.quaternion[2], state.quaternion[3]);
|
|
361
|
+
object.scale.set(...state.scale);
|
|
362
|
+
object.visible = !state.deleted;
|
|
363
|
+
object.userData.raw3d.layerIndex = state.layerIndex;
|
|
364
|
+
object.userData.raw3d.interactionMode = state.interactionMode;
|
|
365
|
+
object.userData.raw3d.highlight = state.highlight;
|
|
366
|
+
object.userData.raw3d.userData = state.userData;
|
|
367
|
+
object.userData.raw3d.clientOwner = state.clientOwner;
|
|
368
|
+
object.userData.raw3d.hasBeenOptimized = state.hasBeenOptimized;
|
|
369
|
+
const parent = state.parentIndex === undefined ? undefined : objects.get(state.parentIndex);
|
|
370
|
+
if (parent && parent !== object && !isDescendantOf(parent, object))
|
|
371
|
+
parent.add(object);
|
|
372
|
+
else if (state.parentIndex !== undefined)
|
|
373
|
+
throw new Error(`Invalid animated parent ${state.parentIndex} for RAW3D node ${node.index}.`);
|
|
374
|
+
let drawable = object.children.find(child => ["mesh", "lines", "points"].includes(child.userData.raw3d?.kind));
|
|
375
|
+
const mesh = state.meshIndex === undefined ? undefined : project.meshes[state.meshIndex];
|
|
376
|
+
if (state.meshIndex !== undefined && !mesh)
|
|
377
|
+
throw new Error(`Missing animated RAW3D mesh ${state.meshIndex}.`);
|
|
378
|
+
if (mesh && !object.userData.raw3d.annotationHidden) {
|
|
379
|
+
if (!geometryCache.has(state.meshIndex))
|
|
380
|
+
geometryCache.set(state.meshIndex, decodeRaw3DThreeGeometry(mesh, project, three));
|
|
381
|
+
if (!drawable || drawable.userData.raw3d.meshType !== mesh.meshType || (drawable.userData.raw3d.meshIndex !== state.meshIndex && (skinBindings.has(drawable) || mesh.bones.length > 0))) {
|
|
382
|
+
if (drawable) {
|
|
383
|
+
object.remove(drawable);
|
|
384
|
+
skinBindings.get(drawable)?.dispose();
|
|
385
|
+
skinBindings.delete(drawable);
|
|
386
|
+
}
|
|
387
|
+
drawable = createDrawable(mesh, geometryCache.get(state.meshIndex), [defaultMaterial], three);
|
|
388
|
+
drawable.userData.raw3d = { kind: primitiveKind(mesh.meshType), nodeIndex: node.index, meshType: mesh.meshType };
|
|
389
|
+
drawable.castShadow = node.castsShadow && drawable instanceof three.Mesh;
|
|
390
|
+
object.add(drawable);
|
|
391
|
+
if (mesh.bones.length && drawable instanceof three.Mesh) {
|
|
392
|
+
const binding = bindRaw3DThreeSkin(drawable, state.meshIndex, project, objects, three);
|
|
393
|
+
drawable = binding.drawable;
|
|
394
|
+
skinBindings.set(drawable, binding);
|
|
395
|
+
}
|
|
396
|
+
// The line/point factory creates topology-specific materials.
|
|
397
|
+
for (const material of Array.isArray(drawable.material) ? drawable.material : [drawable.material])
|
|
398
|
+
ownedMaterials.add(material);
|
|
399
|
+
}
|
|
400
|
+
if (!skinBindings.has(drawable))
|
|
401
|
+
drawable.geometry = geometryCache.get(state.meshIndex);
|
|
402
|
+
drawable.userData.raw3d.meshIndex = state.meshIndex;
|
|
403
|
+
const selected = palette(node, state, mesh);
|
|
404
|
+
if (drawable instanceof three.Mesh)
|
|
405
|
+
drawable.material = selected.length === 1 ? selected[0] : selected;
|
|
406
|
+
else {
|
|
407
|
+
const current = Array.isArray(drawable.material) ? drawable.material : [drawable.material];
|
|
408
|
+
// Keep topology-specific materials instead of assigning mesh shaders to lines/points.
|
|
409
|
+
const adjusted = selected.map((source, slot) => {
|
|
410
|
+
const material = current[slot] ?? (drawable instanceof three.Points ? toPointMaterial(source, three) : toLineMaterial(source, three));
|
|
411
|
+
ownedMaterials.add(material);
|
|
412
|
+
const target = material, original = source;
|
|
413
|
+
target.color.copy(original.color);
|
|
414
|
+
target.opacity = original.opacity;
|
|
415
|
+
target.transparent = original.transparent;
|
|
416
|
+
target.depthWrite = original.depthWrite;
|
|
417
|
+
target.needsUpdate = true;
|
|
418
|
+
if (drawable instanceof three.Points)
|
|
419
|
+
target.size = state.materialProperties[slot]?.PointSize ?? project.materials[node.materialIndices[slot] ?? node.materialIndices[0] ?? (state.layerIndex === undefined ? -1 : project.layers[state.layerIndex]?.materialIndex ?? -1)]?.pointSize ?? 1;
|
|
420
|
+
return material;
|
|
421
|
+
});
|
|
422
|
+
drawable.material = adjusted.length === 1 ? adjusted[0] : adjusted;
|
|
423
|
+
}
|
|
424
|
+
configureRaw3DDrawable(drawable, state.isBillboard, state.isDecal, three);
|
|
425
|
+
}
|
|
426
|
+
if (drawable) {
|
|
427
|
+
drawable.visible = state.visible && !!mesh && !object.userData.raw3d.annotationHidden;
|
|
428
|
+
drawable.userData.raw3d.sourceVisible = drawable.visible;
|
|
429
|
+
}
|
|
430
|
+
for (const child of object.children)
|
|
431
|
+
if (child.userData.raw3d?.kind === "text") {
|
|
432
|
+
child.visible = state.visible && !object.userData.raw3d.annotationHidden;
|
|
433
|
+
child.userData.raw3d.sourceVisible = child.visible;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Retain only geometry currently used by nodes; decoded mesh samples are recreatable.
|
|
437
|
+
const used = new Set([...states.values()].map(state => state.meshIndex));
|
|
438
|
+
for (const [index, geometry] of geometryCache)
|
|
439
|
+
if (!used.has(index)) {
|
|
440
|
+
geometry.dispose();
|
|
441
|
+
geometryCache.delete(index);
|
|
442
|
+
}
|
|
443
|
+
group.updateMatrixWorld(true);
|
|
444
|
+
for (const binding of skinBindings.values())
|
|
445
|
+
binding.update();
|
|
446
|
+
applyRaw3DThreeEffects(group, frame, project, three);
|
|
447
|
+
};
|
|
448
|
+
const seek = (time) => { seekRevision++; pendingFrames?.abort(); apply(time); };
|
|
449
|
+
const seekAsync = async (time) => {
|
|
450
|
+
if (disposed)
|
|
451
|
+
throw new Error("RAW3D playback has been disposed.");
|
|
452
|
+
const next = animation.sampleFrame(time), revision = ++seekRevision;
|
|
453
|
+
pendingFrames?.abort();
|
|
454
|
+
const controller = new AbortController();
|
|
455
|
+
pendingFrames = controller;
|
|
456
|
+
const requested = new Set();
|
|
457
|
+
for (const node of project.nodes) {
|
|
458
|
+
const state = next.nodes.get(node.index);
|
|
459
|
+
const mesh = state.meshIndex === undefined ? undefined : project.meshes[state.meshIndex];
|
|
460
|
+
for (let slot = 0; slot < Math.max(1, mesh?.indexBufferIndices.length ?? 1); slot++) {
|
|
461
|
+
const materialIndex = node.materialIndices[slot] ?? node.materialIndices[0] ?? (state.layerIndex === undefined ? undefined : project.layers[state.layerIndex]?.materialIndex);
|
|
462
|
+
const stream = state.materialProperties[slot]?.StreamableTexture ?? (materialIndex === undefined ? -1 : project.materials[materialIndex]?.streamableTextureIndex ?? -1);
|
|
463
|
+
if (stream >= 0)
|
|
464
|
+
requested.add(stream);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
try {
|
|
468
|
+
const resolved = options.resolveStreamTexture ? await Promise.all([...requested].map(async (index) => [index, await options.resolveStreamTexture({ index, time: Math.max(0, time), source: project.streamableTextures.find(source => source.index === index), signal: controller.signal })])) : [];
|
|
469
|
+
if (disposed || revision !== seekRevision)
|
|
470
|
+
return false;
|
|
471
|
+
for (const [index, texture] of resolved) {
|
|
472
|
+
if (texture !== undefined && !texture.isTexture)
|
|
473
|
+
throw new Error("A stream texture resolver must return a Three.js texture or undefined.");
|
|
474
|
+
const key = `stream:${index}:color`, selected = texture ?? archivedStreams.get(key);
|
|
475
|
+
if (selected)
|
|
476
|
+
resources.textures.set(key, selected);
|
|
477
|
+
else
|
|
478
|
+
resources.textures.delete(key);
|
|
479
|
+
streamRevisions.set(index, (streamRevisions.get(index) ?? 0) + 1);
|
|
480
|
+
}
|
|
481
|
+
apply(time);
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
if (disposed || revision !== seekRevision)
|
|
486
|
+
return false;
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
finally {
|
|
490
|
+
if (revision === seekRevision)
|
|
491
|
+
pendingFrames = undefined;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
const dispose = () => {
|
|
495
|
+
if (disposed)
|
|
496
|
+
return;
|
|
497
|
+
disposed = true;
|
|
498
|
+
seekRevision++;
|
|
499
|
+
pendingFrames?.abort();
|
|
500
|
+
for (const binding of skinBindings.values())
|
|
501
|
+
binding.dispose();
|
|
502
|
+
skinBindings.clear();
|
|
503
|
+
for (const geometry of geometryCache.values())
|
|
504
|
+
geometry.dispose();
|
|
505
|
+
group.traverse(object => { if (object.userData.raw3d?.kind === "scene-floor")
|
|
506
|
+
object.geometry.dispose(); });
|
|
507
|
+
capture();
|
|
508
|
+
for (const material of ownedMaterials) {
|
|
509
|
+
const source = material;
|
|
510
|
+
if (source.map)
|
|
511
|
+
ownedTextures.add(source.map);
|
|
512
|
+
if (source.normalMap)
|
|
513
|
+
ownedTextures.add(source.normalMap);
|
|
514
|
+
}
|
|
515
|
+
for (const material of ownedMaterials)
|
|
516
|
+
material.dispose();
|
|
517
|
+
for (const texture of ownedTextures)
|
|
518
|
+
texture.dispose();
|
|
519
|
+
group.clear();
|
|
520
|
+
variants.clear();
|
|
521
|
+
geometryCache.clear();
|
|
522
|
+
};
|
|
523
|
+
try {
|
|
524
|
+
await seekAsync(0);
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
dispose();
|
|
528
|
+
throw error;
|
|
529
|
+
}
|
|
530
|
+
return { group, animation, duration: animation.duration, get state() { return frame; }, seek, seekAsync, dispose };
|
|
181
531
|
}
|
|
182
532
|
export function decodeRaw3DThreeGeometry(mesh, project, three) {
|
|
183
533
|
const vertexBuffer = project.vertexBuffers[mesh.vertexBufferIndex];
|
|
@@ -199,10 +549,25 @@ export function decodeRaw3DThreeGeometry(mesh, project, three) {
|
|
|
199
549
|
for (let vertex = 0; vertex < vertexCount; vertex += 1) {
|
|
200
550
|
const base = vertex * vertexBuffer.stride + sourceAttribute.offset;
|
|
201
551
|
for (let component = 0; component < sourceAttribute.componentCount; component += 1) {
|
|
202
|
-
let value
|
|
552
|
+
let value;
|
|
553
|
+
if (/^Color$/i.test(sourceAttribute.type)) {
|
|
554
|
+
const packed = view.getUint32(base, true);
|
|
555
|
+
value = ((packed >>> [16, 8, 0, 24][component]) & 255) / 255;
|
|
556
|
+
}
|
|
557
|
+
else if (/^Byte4$/i.test(sourceAttribute.type))
|
|
558
|
+
value = view.getUint8(base + component);
|
|
559
|
+
else if (/^(?:Normalized)?Short[24]$/i.test(sourceAttribute.type)) {
|
|
560
|
+
value = view.getInt16(base + component * 2, true);
|
|
561
|
+
if (/^Normalized/i.test(sourceAttribute.type))
|
|
562
|
+
value = Math.max(-1, value / 32767);
|
|
563
|
+
}
|
|
564
|
+
else
|
|
565
|
+
value = view.getFloat32(base + component * 4, true);
|
|
203
566
|
if ((name === "position" || name === "normal" || name === "tangent") && component === 2) {
|
|
204
567
|
value = -value;
|
|
205
568
|
}
|
|
569
|
+
if (name === "tangent" && component === 3)
|
|
570
|
+
value = -value;
|
|
206
571
|
values[vertex * sourceAttribute.componentCount + component] = value;
|
|
207
572
|
}
|
|
208
573
|
}
|
|
@@ -230,10 +595,12 @@ export function decodeRaw3DThreeGeometry(mesh, project, three) {
|
|
|
230
595
|
? indexView.getUint32(offset * 4, true)
|
|
231
596
|
: indexView.getUint16(offset * 2, true);
|
|
232
597
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
598
|
+
if (isTriangleList(mesh.meshType)) {
|
|
599
|
+
for (let offset = 0; offset + 2 < indices.length; offset += 3) {
|
|
600
|
+
const second = indices[offset + 1];
|
|
601
|
+
indices[offset + 1] = indices[offset + 2];
|
|
602
|
+
indices[offset + 2] = second;
|
|
603
|
+
}
|
|
237
604
|
}
|
|
238
605
|
indexArrays.push(indices);
|
|
239
606
|
totalIndexCount += indices.length;
|
|
@@ -249,13 +616,117 @@ export function decodeRaw3DThreeGeometry(mesh, project, three) {
|
|
|
249
616
|
}
|
|
250
617
|
geometry.setIndex(new three.BufferAttribute(combined, 1));
|
|
251
618
|
}
|
|
252
|
-
if (!geometry.getAttribute("normal")) {
|
|
619
|
+
if (isTriangleList(mesh.meshType) && !geometry.getAttribute("normal")) {
|
|
253
620
|
geometry.computeVertexNormals();
|
|
254
621
|
}
|
|
255
622
|
geometry.computeBoundingBox();
|
|
256
623
|
geometry.computeBoundingSphere();
|
|
257
624
|
return geometry;
|
|
258
625
|
}
|
|
626
|
+
function createDrawable(mesh, geometry, materials, three) {
|
|
627
|
+
const material = materials[0] ?? new three.MeshStandardMaterial({ color: 0x9aa0a6, roughness: 0.8 });
|
|
628
|
+
switch (mesh.meshType.toLowerCase()) {
|
|
629
|
+
case "linelist": {
|
|
630
|
+
const lineMaterials = materials.map((source) => toLineMaterial(source, three));
|
|
631
|
+
return new three.LineSegments(geometry, lineMaterials.length === 1 ? lineMaterials[0] : lineMaterials);
|
|
632
|
+
}
|
|
633
|
+
case "pointlist": {
|
|
634
|
+
const pointMaterials = materials.map((source) => toPointMaterial(source, three));
|
|
635
|
+
return new three.Points(geometry, pointMaterials.length === 1 ? pointMaterials[0] : pointMaterials);
|
|
636
|
+
}
|
|
637
|
+
default:
|
|
638
|
+
return new three.Mesh(geometry, materials.length === 1 ? material : [...materials]);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function toLineMaterial(material, three) {
|
|
642
|
+
const source = material;
|
|
643
|
+
return new three.LineBasicMaterial({
|
|
644
|
+
color: source.color,
|
|
645
|
+
opacity: source.opacity,
|
|
646
|
+
transparent: source.transparent,
|
|
647
|
+
depthWrite: source.depthWrite
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
function toPointMaterial(material, three) {
|
|
651
|
+
const source = material;
|
|
652
|
+
return new three.PointsMaterial({
|
|
653
|
+
color: source.color,
|
|
654
|
+
opacity: source.opacity,
|
|
655
|
+
transparent: source.transparent,
|
|
656
|
+
depthWrite: source.depthWrite,
|
|
657
|
+
size: source.userData.raw3dPointSize ?? 1,
|
|
658
|
+
vertexColors: /^(pointcloud|1)$/i.test(source.userData.raw3dShader ?? ""),
|
|
659
|
+
sizeAttenuation: true
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
function isTriangleList(meshType) {
|
|
663
|
+
return meshType.toLowerCase() === "trianglelist";
|
|
664
|
+
}
|
|
665
|
+
function primitiveKind(meshType) {
|
|
666
|
+
switch (meshType.toLowerCase()) {
|
|
667
|
+
case "linelist": return "lines";
|
|
668
|
+
case "pointlist": return "points";
|
|
669
|
+
default: return "mesh";
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function isAnnotationNode(node, nodes) {
|
|
673
|
+
for (let current = node; current; current = current.parentIndex === undefined
|
|
674
|
+
? undefined
|
|
675
|
+
: nodes.get(current.parentIndex)) {
|
|
676
|
+
if (/^(DrawingBlock|Dimension)/i.test(current.name)) {
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
function createTextSprite(text, sourceMaterial, three) {
|
|
683
|
+
if (typeof document === "undefined") {
|
|
684
|
+
return undefined;
|
|
685
|
+
}
|
|
686
|
+
const lines = text.value.replace(/\r\n?/g, "\n").split("\n");
|
|
687
|
+
const fontSize = 100;
|
|
688
|
+
const lineHeight = 120;
|
|
689
|
+
const padding = 20;
|
|
690
|
+
const canvas = document.createElement("canvas");
|
|
691
|
+
const context = canvas.getContext("2d");
|
|
692
|
+
if (!context) {
|
|
693
|
+
return undefined;
|
|
694
|
+
}
|
|
695
|
+
context.font = `${fontSize}px ${text.fontFamily || "Arial"}`;
|
|
696
|
+
const width = Math.max(1, ...lines.map((line) => context.measureText(line).width));
|
|
697
|
+
canvas.width = Math.ceil(width + padding * 2);
|
|
698
|
+
canvas.height = Math.max(1, lines.length * lineHeight + padding * 2);
|
|
699
|
+
const drawContext = canvas.getContext("2d");
|
|
700
|
+
if (!drawContext) {
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
drawContext.font = `${fontSize}px ${text.fontFamily || "Arial"}`;
|
|
704
|
+
drawContext.textAlign = "center";
|
|
705
|
+
drawContext.textBaseline = "middle";
|
|
706
|
+
const color = sourceMaterial
|
|
707
|
+
? `rgb(${Math.round(clamp01(sourceMaterial.red) * 255)}, ${Math.round(clamp01(sourceMaterial.green) * 255)}, ${Math.round(clamp01(sourceMaterial.blue) * 255)})`
|
|
708
|
+
: "rgb(0, 0, 0)";
|
|
709
|
+
drawContext.fillStyle = color;
|
|
710
|
+
lines.forEach((line, index) => {
|
|
711
|
+
drawContext.fillText(line, canvas.width / 2, padding + lineHeight * (index + 0.5));
|
|
712
|
+
});
|
|
713
|
+
const opacity = sourceMaterial ? 1 - clamp01(sourceMaterial.transparency) : 1;
|
|
714
|
+
const texture = new three.CanvasTexture(canvas);
|
|
715
|
+
texture.colorSpace = three.SRGBColorSpace;
|
|
716
|
+
const sprite = new three.Sprite(new three.SpriteMaterial({
|
|
717
|
+
map: texture,
|
|
718
|
+
transparent: true,
|
|
719
|
+
opacity,
|
|
720
|
+
// Labels share planes with their dimension geometry. Draw them as a readable
|
|
721
|
+
// annotation layer instead of letting those lines occlude the canvas.
|
|
722
|
+
depthTest: false,
|
|
723
|
+
depthWrite: false
|
|
724
|
+
}));
|
|
725
|
+
sprite.renderOrder = 1;
|
|
726
|
+
const scale = Math.max(text.size, 0.001) / fontSize;
|
|
727
|
+
sprite.scale.set(canvas.width * scale, canvas.height * scale, 1);
|
|
728
|
+
return sprite;
|
|
729
|
+
}
|
|
259
730
|
function resolveNodeMaterials(node, mesh, project, materials, fallback) {
|
|
260
731
|
const layerMaterial = node.layerIndex === undefined
|
|
261
732
|
? undefined
|
|
@@ -270,6 +741,9 @@ function resolveNodeMaterials(node, mesh, project, materials, fallback) {
|
|
|
270
741
|
}
|
|
271
742
|
async function loadTextures(project, three, stats, warn) {
|
|
272
743
|
const wanted = new Map();
|
|
744
|
+
for (const stream of project.streamableTextures)
|
|
745
|
+
if (stream.data)
|
|
746
|
+
wanted.set(`stream:${stream.index}:color`, { texture: stream, color: true });
|
|
273
747
|
for (const material of project.materials) {
|
|
274
748
|
if (material.textureIndex !== undefined && project.textures[material.textureIndex]) {
|
|
275
749
|
wanted.set(`${material.textureIndex}:color`, { texture: project.textures[material.textureIndex], color: true });
|
|
@@ -278,6 +752,21 @@ async function loadTextures(project, three, stats, warn) {
|
|
|
278
752
|
wanted.set(`${material.normalTextureIndex}:normal`, { texture: project.textures[material.normalTextureIndex], color: false });
|
|
279
753
|
}
|
|
280
754
|
}
|
|
755
|
+
for (const update of project.root.child("Updates")?.children ?? []) {
|
|
756
|
+
if (update.localName !== "Material")
|
|
757
|
+
continue;
|
|
758
|
+
const name = update.attributes.find(attribute => attribute.localName === "Name")?.value;
|
|
759
|
+
if (!["Texture", "NormalMap", "NormalTexture"].includes(name ?? ""))
|
|
760
|
+
continue;
|
|
761
|
+
const value = update.attributes.find(attribute => attribute.localName === "Value")?.value ?? "";
|
|
762
|
+
for (const token of value.trim().split(/\s+/)) {
|
|
763
|
+
const index = Number(token), texture = project.textures[index];
|
|
764
|
+
if (texture) {
|
|
765
|
+
const color = name === "Texture";
|
|
766
|
+
wanted.set(`${index}:${color ? "color" : "normal"}`, { texture, color });
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
281
770
|
const loaded = new Map();
|
|
282
771
|
await Promise.all([...wanted].map(async ([key, item]) => {
|
|
283
772
|
if (!item.texture.data) {
|
|
@@ -321,10 +810,13 @@ function createMaterial(source, textures, three) {
|
|
|
321
810
|
: textures.get(`${source.normalTextureIndex}:normal`);
|
|
322
811
|
const map = sharedMap?.clone();
|
|
323
812
|
const normalMap = sharedNormalMap?.clone();
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
813
|
+
for (const texture of [map, normalMap]) {
|
|
814
|
+
if (!texture)
|
|
815
|
+
continue;
|
|
816
|
+
if (source.scaleU !== 1 || source.scaleV !== 1 || source.offsetU !== 0 || source.offsetV !== 0)
|
|
817
|
+
texture.wrapS = texture.wrapT = three.RepeatWrapping;
|
|
818
|
+
texture.repeat.set(source.scaleU, source.scaleV);
|
|
819
|
+
texture.offset.set(source.offsetU, source.offsetV);
|
|
328
820
|
}
|
|
329
821
|
const parameters = {
|
|
330
822
|
color: new three.Color(clamp01(source.red), clamp01(source.green), clamp01(source.blue)),
|
|
@@ -341,7 +833,26 @@ function createMaterial(source, textures, three) {
|
|
|
341
833
|
parameters.normalMap = normalMap;
|
|
342
834
|
parameters.normalScale = new three.Vector2(source.normalScale, source.normalScale);
|
|
343
835
|
}
|
|
344
|
-
|
|
836
|
+
let result;
|
|
837
|
+
switch (source.shader.toLowerCase()) {
|
|
838
|
+
case "flattext":
|
|
839
|
+
case "2":
|
|
840
|
+
result = new three.MeshBasicMaterial({ color: parameters.color, map: map ?? null, opacity, transparent: opacity < 1 || !!map, depthWrite: opacity >= 1, side: three.DoubleSide });
|
|
841
|
+
break;
|
|
842
|
+
case "default":
|
|
843
|
+
case "0":
|
|
844
|
+
case "pointcloud":
|
|
845
|
+
case "1":
|
|
846
|
+
result = raw3DNodeBackend(three)?.standard(parameters) ?? new three.MeshStandardMaterial(parameters);
|
|
847
|
+
break;
|
|
848
|
+
default: throw new Error(`Unsupported RAW3D material shader ${source.shader}.`);
|
|
849
|
+
}
|
|
850
|
+
result.userData.raw3dShader = source.shader;
|
|
851
|
+
result.userData.raw3dPointSize = source.pointSize;
|
|
852
|
+
result.userData.raw3dNormalDepth = source.normalDepth;
|
|
853
|
+
if ("emissive" in result)
|
|
854
|
+
result.emissive.copy(result.color).multiplyScalar(source.luminosity);
|
|
855
|
+
return result;
|
|
345
856
|
}
|
|
346
857
|
function applyTransform(object, node) {
|
|
347
858
|
object.position.set(node.location[0] ?? 0, node.location[1] ?? 0, -(node.location[2] ?? 0));
|