@jfk-solutions/demo3d-file-format 1.5.0 → 1.7.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 +25 -2
- package/dist/binary.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fast-xml.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +17 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +91 -2
- package/dist/model.js.map +1 -1
- package/dist/parser.js.map +1 -1
- package/dist/raw3d.d.ts +133 -0
- package/dist/raw3d.d.ts.map +1 -0
- package/dist/raw3d.js +332 -0
- package/dist/raw3d.js.map +1 -0
- package/dist/three/index.d.ts +2 -0
- package/dist/three/index.d.ts.map +1 -1
- package/dist/three/index.js +250 -34
- package/dist/three/index.js.map +1 -1
- package/dist/three/raw3d.d.ts +36 -0
- package/dist/three/raw3d.d.ts.map +1 -0
- package/dist/three/raw3d.js +384 -0
- package/dist/three/raw3d.js.map +1 -0
- package/dist/three/renderer.js.map +1 -1
- package/dist/xml.d.ts.map +1 -1
- package/dist/xml.js.map +1 -1
- package/dist/zip.d.ts.map +1 -1
- package/dist/zip.js.map +1 -1
- package/package.json +7 -6
package/dist/three/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Demo3DConveyorSideProperties, Demo3DDimensionAspect, Demo3DDimensionPoint, Demo3DExtrusionPolygon, Demo3DExtrusionProfile, Demo3DMaterial, Demo3DMesh, Demo3DPackage, Demo3DProject, Demo3DPhotoEye, Demo3DResource, Demo3DSupportStand, Demo3DVisual } from "../model.js";
|
|
1
|
+
import { Demo3DConveyorSideProperties, Demo3DDimensionAspect, Demo3DDimensionPoint, Demo3DExtrusionPolygon, Demo3DExtrusionProfile, Demo3DMaterial, Demo3DMesh, Demo3DPackage, Demo3DPointCloud, Demo3DProject, Demo3DPhotoEye, Demo3DResource, Demo3DSupportStand, Demo3DVisual } from "../model.js";
|
|
2
2
|
import { Demo3DBinaryBlock, Demo3DXmlElement } from "../xml.js";
|
|
3
|
+
export { createRaw3DThreeGroup, createRaw3DThreeScene, decodeRaw3DThreeGeometry } from "./raw3d.js";
|
|
3
4
|
export { createDemo3DThreeRenderer } from "./renderer.js";
|
|
4
5
|
export async function createDemo3DThreeScene(parsed, options = {}) {
|
|
5
6
|
const three = await resolveThree(options);
|
|
@@ -88,6 +89,7 @@ export function decodeDemo3DThreeGeometry(mesh, three) {
|
|
|
88
89
|
if (mesh.indices) {
|
|
89
90
|
geometry.setIndex(new three.BufferAttribute(readIndexBuffer(mesh.indices, mesh.indexFormat), 1));
|
|
90
91
|
}
|
|
92
|
+
addMeshSubsetGroups(geometry, mesh);
|
|
91
93
|
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
92
94
|
if (!normals) {
|
|
93
95
|
geometry.computeVertexNormals();
|
|
@@ -96,6 +98,25 @@ export function decodeDemo3DThreeGeometry(mesh, three) {
|
|
|
96
98
|
geometry.computeBoundingSphere();
|
|
97
99
|
return geometry;
|
|
98
100
|
}
|
|
101
|
+
function addMeshSubsetGroups(geometry, mesh) {
|
|
102
|
+
const subsets = mesh.auxiliary?.toUint8Array();
|
|
103
|
+
const drawCount = geometry.getIndex()?.count ?? geometry.getAttribute("position").count;
|
|
104
|
+
const faceCount = Math.floor(drawCount / 3);
|
|
105
|
+
if (!subsets || subsets.length !== faceCount || faceCount === 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
let runStart = 0;
|
|
109
|
+
let materialIndex = subsets[0] ?? 0;
|
|
110
|
+
for (let face = 1; face <= faceCount; face += 1) {
|
|
111
|
+
const nextMaterialIndex = face < faceCount ? subsets[face] : undefined;
|
|
112
|
+
if (face < faceCount && nextMaterialIndex === materialIndex) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
geometry.addGroup(runStart * 3, (face - runStart) * 3, materialIndex);
|
|
116
|
+
runStart = face;
|
|
117
|
+
materialIndex = nextMaterialIndex ?? 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
99
120
|
export function createDemo3DThreeMaterial(material, three) {
|
|
100
121
|
const diffuse = material?.diffuse;
|
|
101
122
|
const color = diffuse === undefined ? 0x9aa0a6 : demo3dColorToHex(diffuse);
|
|
@@ -126,6 +147,7 @@ function createState(three, project, options, buffers) {
|
|
|
126
147
|
options,
|
|
127
148
|
warnings: [],
|
|
128
149
|
meshById: new Map(project.meshes.flatMap((mesh) => (mesh.id ? [[mesh.id, mesh]] : []))),
|
|
150
|
+
pointCloudById: new Map(project.pointClouds.flatMap((pointCloud) => pointCloud.id ? [[pointCloud.id, pointCloud]] : [])),
|
|
129
151
|
serializedObjectById: indexSerializedObjects(project),
|
|
130
152
|
textureImageById: indexTextureImages(project),
|
|
131
153
|
drawingBlockById: indexDrawingBlocks(project),
|
|
@@ -161,6 +183,7 @@ function createState(three, project, options, buffers) {
|
|
|
161
183
|
unreconstructedProceduralVisuals: 0,
|
|
162
184
|
imageVisuals: 0,
|
|
163
185
|
lights: 0,
|
|
186
|
+
pointClouds: 0,
|
|
164
187
|
missingGeometryPlaceholders: 0,
|
|
165
188
|
serializedRenderables: 0,
|
|
166
189
|
unsupported: 0
|
|
@@ -192,7 +215,7 @@ function createVisualObject(visual, state, inheritedLayer, parentVisual) {
|
|
|
192
215
|
meshes.push(directVisualObject);
|
|
193
216
|
}
|
|
194
217
|
for (const ref of refs) {
|
|
195
|
-
const mesh = createMeshObject(ref, visual, visual.materials
|
|
218
|
+
const mesh = createMeshObject(ref, visual, visual.materials, state);
|
|
196
219
|
if (mesh) {
|
|
197
220
|
if (mesh.userData.demo3d?.kind === "missing-geometry-placeholder") {
|
|
198
221
|
placeholderObjects.push(mesh);
|
|
@@ -281,11 +304,13 @@ function createAspectRenderableObjects(visual, state) {
|
|
|
281
304
|
for (const renderable of renderables.children) {
|
|
282
305
|
const meshIds = findMeshReferenceIds(renderable);
|
|
283
306
|
for (const meshId of meshIds) {
|
|
284
|
-
const
|
|
307
|
+
const serializedMaterials = renderableMaterials(renderable);
|
|
308
|
+
const materials = serializedMaterials.length > 0 ? serializedMaterials : visual.materials;
|
|
309
|
+
const primaryMaterial = materials[0];
|
|
285
310
|
const mesh = state.meshById.has(meshId)
|
|
286
|
-
? createMeshObject(meshId, renderable,
|
|
287
|
-
: createPrimitiveRenderableObject(aspect, renderable, meshId,
|
|
288
|
-
createMeshObject(meshId, renderable,
|
|
311
|
+
? createMeshObject(meshId, renderable, materials, state)
|
|
312
|
+
: createPrimitiveRenderableObject(aspect, renderable, meshId, primaryMaterial, state) ??
|
|
313
|
+
createMeshObject(meshId, renderable, materials, state);
|
|
289
314
|
if (!mesh) {
|
|
290
315
|
continue;
|
|
291
316
|
}
|
|
@@ -381,6 +406,9 @@ function createDirectVisualObject(visual, state, parentVisual) {
|
|
|
381
406
|
if (visual.typeName === "e3d:ImportedImageVisual") {
|
|
382
407
|
return createImportedImageObject(visual, state);
|
|
383
408
|
}
|
|
409
|
+
if (visual.typeName === "e3d:PointCloudVisual") {
|
|
410
|
+
return createPointCloudObject(visual, state);
|
|
411
|
+
}
|
|
384
412
|
if (visual.typeName === "e3d:StraightBeltConveyor" ||
|
|
385
413
|
visual.typeName === "e3d:CurveBeltConveyor" ||
|
|
386
414
|
visual.typeName === "e3d:InjectorBeltConveyor") {
|
|
@@ -423,6 +451,89 @@ function createDirectVisualObject(visual, state, parentVisual) {
|
|
|
423
451
|
state.stats.directVisuals += 1;
|
|
424
452
|
return object;
|
|
425
453
|
}
|
|
454
|
+
function createPointCloudObject(visual, state) {
|
|
455
|
+
const referenceId = visual.properties?.textOf("PointCloudRef");
|
|
456
|
+
const pointCloud = referenceId ? state.pointCloudById.get(referenceId) : undefined;
|
|
457
|
+
if (!pointCloud) {
|
|
458
|
+
warn(state, {
|
|
459
|
+
code: "DEMO3D_THREE_MISSING_POINT_CLOUD",
|
|
460
|
+
message: `Point cloud reference ${referenceId ?? "<missing>"} could not be resolved.`,
|
|
461
|
+
sourceId: visual.id,
|
|
462
|
+
sourceType: visual.typeName,
|
|
463
|
+
xmlPath: visual.xml.path
|
|
464
|
+
});
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
const group = new state.three.Group();
|
|
468
|
+
group.name = visual.displayName ?? pointCloud.name ?? referenceId ?? visual.typeName;
|
|
469
|
+
const fallbackColor = primaryMaterial(visual)?.diffuse;
|
|
470
|
+
const pointSize = Math.min(128, Math.max(1, numberChild(visual.properties, "PointSize", 1)));
|
|
471
|
+
for (const [primitiveIndex, primitive] of pointCloud.primitives.entries()) {
|
|
472
|
+
const bufferName = primitive.pointsBufferName;
|
|
473
|
+
const buffer = bufferName ? state.bufferByName.get(bufferName.toLowerCase()) : undefined;
|
|
474
|
+
if (!buffer) {
|
|
475
|
+
warn(state, {
|
|
476
|
+
code: "DEMO3D_THREE_MISSING_POINT_CLOUD_BUFFER",
|
|
477
|
+
message: `Point cloud buffer ${bufferName ?? "<missing>"} could not be resolved.`,
|
|
478
|
+
sourceId: visual.id,
|
|
479
|
+
sourceType: visual.typeName,
|
|
480
|
+
xmlPath: primitive.xml.path
|
|
481
|
+
});
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (buffer.byteLength % 12 !== 0) {
|
|
485
|
+
warn(state, {
|
|
486
|
+
code: "DEMO3D_THREE_INVALID_POINT_CLOUD_BUFFER",
|
|
487
|
+
message: `Point cloud buffer ${bufferName} has ${buffer.byteLength} bytes; expected packed Float32 XYZ triples.`,
|
|
488
|
+
sourceId: visual.id,
|
|
489
|
+
sourceType: visual.typeName,
|
|
490
|
+
xmlPath: primitive.xml.path
|
|
491
|
+
});
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const positions = new Float32Array(buffer.byteLength / 4);
|
|
495
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
496
|
+
for (let index = 0; index < positions.length; index += 3) {
|
|
497
|
+
positions[index] = view.getFloat32(index * 4, true);
|
|
498
|
+
positions[index + 1] = view.getFloat32((index + 1) * 4, true);
|
|
499
|
+
positions[index + 2] = -view.getFloat32((index + 2) * 4, true);
|
|
500
|
+
}
|
|
501
|
+
const geometry = new state.three.BufferGeometry();
|
|
502
|
+
geometry.setAttribute("position", new state.three.BufferAttribute(positions, 3));
|
|
503
|
+
geometry.computeBoundingBox();
|
|
504
|
+
geometry.computeBoundingSphere();
|
|
505
|
+
const material = new state.three.PointsMaterial({
|
|
506
|
+
color: demo3dColorToHex(primitive.color ?? fallbackColor ?? -1),
|
|
507
|
+
size: pointSize,
|
|
508
|
+
sizeAttenuation: false
|
|
509
|
+
});
|
|
510
|
+
const points = new state.three.Points(geometry, material);
|
|
511
|
+
points.name = pointCloud.primitives.length > 1 ? `${group.name} ${primitiveIndex + 1}` : group.name;
|
|
512
|
+
points.userData.demo3d = {
|
|
513
|
+
kind: "point-cloud-primitive",
|
|
514
|
+
pointCloudId: pointCloud.id,
|
|
515
|
+
bufferName,
|
|
516
|
+
pointCount: positions.length / 3
|
|
517
|
+
};
|
|
518
|
+
group.add(points);
|
|
519
|
+
state.stats.geometries += 1;
|
|
520
|
+
state.stats.materials += 1;
|
|
521
|
+
}
|
|
522
|
+
if (group.children.length === 0) {
|
|
523
|
+
return undefined;
|
|
524
|
+
}
|
|
525
|
+
group.userData.demo3d = {
|
|
526
|
+
kind: "point-cloud",
|
|
527
|
+
id: visual.id,
|
|
528
|
+
pointCloudId: pointCloud.id,
|
|
529
|
+
typeName: visual.typeName,
|
|
530
|
+
xmlPath: visual.xml.path
|
|
531
|
+
};
|
|
532
|
+
state.stats.groups += 1;
|
|
533
|
+
state.stats.directVisuals += 1;
|
|
534
|
+
state.stats.pointClouds += 1;
|
|
535
|
+
return group;
|
|
536
|
+
}
|
|
426
537
|
function createBeltConveyorObject(visual, state) {
|
|
427
538
|
if (visual.typeName === "e3d:CurveBeltConveyor") {
|
|
428
539
|
return createCurveBeltConveyorObject(visual, state);
|
|
@@ -606,17 +717,10 @@ function readProceduralBelt(visual, state) {
|
|
|
606
717
|
if (!properties || isFalse(properties.textOf("CenterVisible"))) {
|
|
607
718
|
return undefined;
|
|
608
719
|
}
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
message: "Straight belt conveyors with a custom CenterProfile are not supported.",
|
|
614
|
-
sourceId: visual.id,
|
|
615
|
-
sourceType: visual.typeName,
|
|
616
|
-
xmlPath: visual.xml.path
|
|
617
|
-
});
|
|
618
|
-
return undefined;
|
|
619
|
-
}
|
|
720
|
+
const centerProfileElement = properties.child("CenterProfile");
|
|
721
|
+
const centerProfile = centerProfileElement && centerProfileElement.children.length > 0
|
|
722
|
+
? new Demo3DExtrusionProfile(centerProfileElement)
|
|
723
|
+
: undefined;
|
|
620
724
|
const startPadding = numberChild(properties, "StartPadding", 0);
|
|
621
725
|
const endPadding = numberChild(properties, "EndPadding", 0);
|
|
622
726
|
const leftPadding = numberChild(properties, "LeftPadding", 0);
|
|
@@ -659,6 +763,7 @@ function readProceduralBelt(visual, state) {
|
|
|
659
763
|
zOffset: (rightPadding - leftPadding) / 2,
|
|
660
764
|
startCap,
|
|
661
765
|
endCap,
|
|
766
|
+
centerProfile,
|
|
662
767
|
surfaceMaterial,
|
|
663
768
|
sideMaterial
|
|
664
769
|
};
|
|
@@ -680,21 +785,55 @@ function beltCap(value, visual, state) {
|
|
|
680
785
|
return "Box";
|
|
681
786
|
}
|
|
682
787
|
function getProceduralBeltGeometry(belt, state) {
|
|
683
|
-
const
|
|
788
|
+
const profileKey = belt.centerProfile?.polygons
|
|
789
|
+
.map((polygon) => polygon.points.map((point) => `${point.x},${point.y}`).join(";"))
|
|
790
|
+
.join("/") ?? "";
|
|
791
|
+
const key = `belt:${belt.length}:${belt.width}:${belt.thickness}:${belt.endRadius}:${belt.xOffset}:${belt.zOffset}:${belt.startCap}:${belt.endCap}:${profileKey}`;
|
|
684
792
|
const cached = state.primitiveGeometryCache.get(key);
|
|
685
793
|
if (cached) {
|
|
686
794
|
return cached;
|
|
687
795
|
}
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
796
|
+
const profilePolygon = belt.centerProfile?.polygons.find((polygon) => polygon.points.length >= 3);
|
|
797
|
+
const geometry = belt.centerProfile && profilePolygon
|
|
798
|
+
? createProfileBeltGeometry(belt, belt.centerProfile, profilePolygon, state)
|
|
799
|
+
: belt.startCap === "Box" && belt.endCap === "Box"
|
|
800
|
+
? createRectangularBeltGeometry(belt, state)
|
|
801
|
+
: createRoundedBeltGeometry(belt, state);
|
|
802
|
+
if (!profilePolygon) {
|
|
803
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
804
|
+
}
|
|
692
805
|
geometry.computeBoundingBox();
|
|
693
806
|
geometry.computeBoundingSphere();
|
|
694
807
|
state.primitiveGeometryCache.set(key, geometry);
|
|
695
808
|
state.stats.geometries += 1;
|
|
696
809
|
return geometry;
|
|
697
810
|
}
|
|
811
|
+
function createProfileBeltGeometry(belt, profile, polygon, state) {
|
|
812
|
+
const shape = new state.three.Shape();
|
|
813
|
+
polygon.points.forEach((point, index) => {
|
|
814
|
+
const lateral = -(point.x - profile.anchor.x);
|
|
815
|
+
const vertical = point.y - profile.anchor.y;
|
|
816
|
+
if (index === 0) {
|
|
817
|
+
shape.moveTo(lateral, vertical);
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
shape.lineTo(lateral, vertical);
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
shape.closePath();
|
|
824
|
+
const geometry = new state.three.ExtrudeGeometry(shape, {
|
|
825
|
+
bevelEnabled: false,
|
|
826
|
+
curveSegments: 1,
|
|
827
|
+
depth: belt.length,
|
|
828
|
+
steps: 1
|
|
829
|
+
});
|
|
830
|
+
geometry.rotateY(Math.PI / 2);
|
|
831
|
+
geometry.translate(belt.xOffset, 0, belt.zOffset);
|
|
832
|
+
for (const group of geometry.groups) {
|
|
833
|
+
group.materialIndex = group.materialIndex === 0 ? 1 : 0;
|
|
834
|
+
}
|
|
835
|
+
return geometry;
|
|
836
|
+
}
|
|
698
837
|
function createCurveBeltConveyorObject(visual, state) {
|
|
699
838
|
const belt = readProceduralCurveBelt(visual, state);
|
|
700
839
|
if (!belt) {
|
|
@@ -871,7 +1010,8 @@ function createRackObject(visual, state) {
|
|
|
871
1010
|
}
|
|
872
1011
|
const frameHeight = numberChild(properties, "FrameHeight", 0);
|
|
873
1012
|
const uprightWidth = numberChild(properties, "UprightWidth", 0.05);
|
|
874
|
-
const
|
|
1013
|
+
const serializedUprightDepth = numberChild(properties, "UprightDepth", 0.05);
|
|
1014
|
+
const uprightDepth = serializedUprightDepth > 0 ? serializedUprightDepth : uprightWidth;
|
|
875
1015
|
const frameDepth = numberChildOptional(properties, "FrameDepth") ?? numberChild(properties, "BayDepth", 0);
|
|
876
1016
|
const strutWidth = numberChild(properties, "StrutWidth", 0.05);
|
|
877
1017
|
if (frameHeight <= 0 || frameDepth <= 0 || uprightWidth <= 0 || uprightDepth <= 0 || strutWidth <= 0) {
|
|
@@ -1604,6 +1744,7 @@ function getDirectVisualGeometry(visual, state) {
|
|
|
1604
1744
|
properties.textOf("Length") ?? "",
|
|
1605
1745
|
properties.textOf("Radius") ?? "",
|
|
1606
1746
|
properties.textOf("StartAngle") ?? "",
|
|
1747
|
+
properties.textOf("Thickness") ?? "",
|
|
1607
1748
|
properties.textOf("Width") ?? ""
|
|
1608
1749
|
].join("|")}`;
|
|
1609
1750
|
const cached = state.primitiveGeometryCache.get(key);
|
|
@@ -1611,25 +1752,70 @@ function getDirectVisualGeometry(visual, state) {
|
|
|
1611
1752
|
return cached;
|
|
1612
1753
|
}
|
|
1613
1754
|
let geometry;
|
|
1614
|
-
if (visual.typeName === "e3d:BoxVisual"
|
|
1755
|
+
if (visual.typeName === "e3d:BoxVisual") {
|
|
1615
1756
|
geometry = new state.three.BoxGeometry(numberChild(properties, "Width", 1), numberChild(properties, "Height", 1), numberChild(properties, "Depth", 1));
|
|
1616
1757
|
}
|
|
1758
|
+
else if (visual.typeName === "e3d:BoxTubeVisual") {
|
|
1759
|
+
geometry = createBoxTubeGeometry(numberChild(properties, "Width", 0.25), numberChild(properties, "Height", 1), numberChild(properties, "Depth", 0.25), numberChild(properties, "Thickness", 0.015), state);
|
|
1760
|
+
}
|
|
1617
1761
|
else if (visual.typeName === "e3d:CylinderVisual") {
|
|
1618
1762
|
geometry = new state.three.CylinderGeometry(numberChild(properties, "Radius", 0.5), numberChild(properties, "Radius", 0.5), numberChild(properties, "Length", 1), 32, 1, false, degreesToRadians(numberChild(properties, "StartAngle", 0)), degreesToRadians(numberChild(properties, "Angle", 360)));
|
|
1619
1763
|
}
|
|
1764
|
+
else if (visual.typeName === "e3d:SphereVisual") {
|
|
1765
|
+
geometry = new state.three.SphereGeometry(numberChild(properties, "Radius", 0.5), 32, 16);
|
|
1766
|
+
}
|
|
1620
1767
|
else if (visual.typeName === "e3d:WedgeVisual") {
|
|
1621
1768
|
geometry = createWedgeGeometry(numberChild(properties, "Width", 1), numberChild(properties, "Height", 1), numberChild(properties, "Length", 1), state);
|
|
1622
1769
|
}
|
|
1623
1770
|
if (!geometry) {
|
|
1624
1771
|
return undefined;
|
|
1625
1772
|
}
|
|
1626
|
-
|
|
1773
|
+
// BoxGeometry and the box-tube extrusion are already created in Three's
|
|
1774
|
+
// coordinate system. Reflecting these symmetric, Three-native geometries
|
|
1775
|
+
// swaps their front/back UV layouts even though their bounds do not change.
|
|
1776
|
+
// Other direct primitives still need the Demo3D-to-Three Z reflection.
|
|
1777
|
+
if (visual.typeName !== "e3d:BoxVisual" &&
|
|
1778
|
+
visual.typeName !== "e3d:BoxTubeVisual" &&
|
|
1779
|
+
visual.typeName !== "e3d:SphereVisual") {
|
|
1780
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
1781
|
+
}
|
|
1627
1782
|
geometry.computeBoundingBox();
|
|
1628
1783
|
geometry.computeBoundingSphere();
|
|
1629
1784
|
state.primitiveGeometryCache.set(key, geometry);
|
|
1630
1785
|
state.stats.geometries += 1;
|
|
1631
1786
|
return geometry;
|
|
1632
1787
|
}
|
|
1788
|
+
function createBoxTubeGeometry(width, height, depth, thickness, state) {
|
|
1789
|
+
const halfWidth = Math.max(width, 0) / 2;
|
|
1790
|
+
const halfDepth = Math.max(depth, 0) / 2;
|
|
1791
|
+
const edge = clamp(thickness, 0, Math.min(halfWidth, halfDepth));
|
|
1792
|
+
const shape = new state.three.Shape();
|
|
1793
|
+
shape.moveTo(-halfWidth, -halfDepth);
|
|
1794
|
+
shape.lineTo(-halfWidth, halfDepth);
|
|
1795
|
+
shape.lineTo(halfWidth, halfDepth);
|
|
1796
|
+
shape.lineTo(halfWidth, -halfDepth);
|
|
1797
|
+
shape.closePath();
|
|
1798
|
+
const innerHalfWidth = halfWidth - edge;
|
|
1799
|
+
const innerHalfDepth = halfDepth - edge;
|
|
1800
|
+
if (innerHalfWidth > 0 && innerHalfDepth > 0) {
|
|
1801
|
+
const hole = new state.three.Path();
|
|
1802
|
+
hole.moveTo(-innerHalfWidth, -innerHalfDepth);
|
|
1803
|
+
hole.lineTo(innerHalfWidth, -innerHalfDepth);
|
|
1804
|
+
hole.lineTo(innerHalfWidth, innerHalfDepth);
|
|
1805
|
+
hole.lineTo(-innerHalfWidth, innerHalfDepth);
|
|
1806
|
+
hole.closePath();
|
|
1807
|
+
shape.holes.push(hole);
|
|
1808
|
+
}
|
|
1809
|
+
const geometry = new state.three.ExtrudeGeometry(shape, {
|
|
1810
|
+
depth: Math.max(height, 0),
|
|
1811
|
+
steps: 1,
|
|
1812
|
+
bevelEnabled: false
|
|
1813
|
+
});
|
|
1814
|
+
// ExtrudeGeometry uses Z for its extrusion. Demo3D box tubes extrude along Y.
|
|
1815
|
+
geometry.rotateX(-Math.PI / 2);
|
|
1816
|
+
geometry.translate(0, -Math.max(height, 0) / 2, 0);
|
|
1817
|
+
return geometry;
|
|
1818
|
+
}
|
|
1633
1819
|
function createWedgeGeometry(width, height, length, state) {
|
|
1634
1820
|
const x = width / 2;
|
|
1635
1821
|
const y = height / 2;
|
|
@@ -1696,8 +1882,12 @@ function getDrawingBlockGeometry(blockId, state) {
|
|
|
1696
1882
|
state.stats.geometries += 1;
|
|
1697
1883
|
return geometry;
|
|
1698
1884
|
}
|
|
1699
|
-
function createMeshObject(meshId, source,
|
|
1885
|
+
function createMeshObject(meshId, source, materialSelection, state) {
|
|
1700
1886
|
const mesh = state.meshById.get(meshId);
|
|
1887
|
+
const materials = Array.isArray(materialSelection)
|
|
1888
|
+
? materialSelection
|
|
1889
|
+
: materialSelection ? [materialSelection] : [];
|
|
1890
|
+
const primaryMaterial = materials[0];
|
|
1701
1891
|
const sourceId = source instanceof Demo3DVisual ? source.id : source.textOf("Id");
|
|
1702
1892
|
const sourceType = source instanceof Demo3DVisual ? source.typeName : source.xsiType ?? source.localName;
|
|
1703
1893
|
const xmlPath = source instanceof Demo3DVisual ? source.xml.path : source.path;
|
|
@@ -1710,20 +1900,24 @@ function createMeshObject(meshId, source, material, state) {
|
|
|
1710
1900
|
xmlPath
|
|
1711
1901
|
});
|
|
1712
1902
|
return state.options.showPlaceholders === true
|
|
1713
|
-
? createPlaceholderMesh(sourceId ?? meshId, meshId,
|
|
1903
|
+
? createPlaceholderMesh(sourceId ?? meshId, meshId, primaryMaterial, state)
|
|
1714
1904
|
: undefined;
|
|
1715
1905
|
}
|
|
1716
1906
|
const geometry = getGeometry(mesh, state);
|
|
1717
1907
|
if (!geometry) {
|
|
1718
1908
|
return state.options.showPlaceholders === true
|
|
1719
|
-
? createPlaceholderMesh(sourceId ?? meshId, meshId,
|
|
1909
|
+
? createPlaceholderMesh(sourceId ?? meshId, meshId, primaryMaterial, state)
|
|
1720
1910
|
: undefined;
|
|
1721
1911
|
}
|
|
1722
|
-
const
|
|
1912
|
+
const threeMaterial = materials.length > 1
|
|
1913
|
+
? materials.map((material) => getMaterial(material, state))
|
|
1914
|
+
: getMaterial(primaryMaterial, state);
|
|
1915
|
+
const object = new state.three.Mesh(geometry, threeMaterial);
|
|
1723
1916
|
object.name = source instanceof Demo3DVisual ? source.displayName ?? meshId : meshId;
|
|
1724
1917
|
object.userData.demo3d = {
|
|
1725
1918
|
kind: "mesh",
|
|
1726
1919
|
meshId,
|
|
1920
|
+
materialSlots: materials.length,
|
|
1727
1921
|
sourceId,
|
|
1728
1922
|
sourceType,
|
|
1729
1923
|
xmlPath
|
|
@@ -1801,7 +1995,9 @@ function getPrimitiveGeometry(aspect, renderable, state) {
|
|
|
1801
1995
|
if (!geometry) {
|
|
1802
1996
|
return undefined;
|
|
1803
1997
|
}
|
|
1804
|
-
|
|
1998
|
+
if (type !== "e3d:BoxRendererAspect" && type !== "e3d:ContainerRendererAspect") {
|
|
1999
|
+
convertTriangleGeometryToThreeCoordinates(geometry);
|
|
2000
|
+
}
|
|
1805
2001
|
geometry.computeBoundingBox();
|
|
1806
2002
|
geometry.computeBoundingSphere();
|
|
1807
2003
|
state.primitiveGeometryCache.set(key, geometry);
|
|
@@ -1982,7 +2178,9 @@ function getTexture(textureReference, state) {
|
|
|
1982
2178
|
texture.colorSpace = state.three.SRGBColorSpace;
|
|
1983
2179
|
}
|
|
1984
2180
|
texture.name = image.name ?? image.id;
|
|
1985
|
-
|
|
2181
|
+
// Demo3D's generated primitive UVs use the conventional bottom-left texture
|
|
2182
|
+
// origin expected by Three's TextureLoader.
|
|
2183
|
+
texture.flipY = true;
|
|
1986
2184
|
state.textureCache.set(textureReference, texture);
|
|
1987
2185
|
state.stats.textures += 1;
|
|
1988
2186
|
return texture;
|
|
@@ -2217,8 +2415,7 @@ function createSerializedRenderableGroup(state) {
|
|
|
2217
2415
|
}
|
|
2218
2416
|
const meshIds = findMeshReferenceIds(renderable);
|
|
2219
2417
|
for (const meshId of meshIds) {
|
|
2220
|
-
const
|
|
2221
|
-
const mesh = createMeshObject(meshId, renderable, material, state);
|
|
2418
|
+
const mesh = createMeshObject(meshId, renderable, renderableMaterials(renderable), state);
|
|
2222
2419
|
if (!mesh) {
|
|
2223
2420
|
continue;
|
|
2224
2421
|
}
|
|
@@ -2418,6 +2615,23 @@ function findVisualMeshReferenceIds(root) {
|
|
|
2418
2615
|
}
|
|
2419
2616
|
return [...ids];
|
|
2420
2617
|
}
|
|
2618
|
+
function renderableMaterials(renderable) {
|
|
2619
|
+
const materials = [];
|
|
2620
|
+
const materialProperties = renderable.child("MaterialProperties");
|
|
2621
|
+
for (const entry of materialProperties?.children ?? []) {
|
|
2622
|
+
const material = firstMaterial(entry);
|
|
2623
|
+
if (material) {
|
|
2624
|
+
materials.push(material);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
if (materials.length === 0) {
|
|
2628
|
+
const material = firstMaterial(renderable);
|
|
2629
|
+
if (material) {
|
|
2630
|
+
materials.push(material);
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
return materials;
|
|
2634
|
+
}
|
|
2421
2635
|
function firstMaterial(root) {
|
|
2422
2636
|
let found;
|
|
2423
2637
|
visit(root, (node) => {
|
|
@@ -2501,7 +2715,9 @@ function applyDemo3DTransform(object, transformText) {
|
|
|
2501
2715
|
return;
|
|
2502
2716
|
}
|
|
2503
2717
|
object.position.set(values[0] ?? 0, values[1] ?? 0, -(values[2] ?? 0));
|
|
2504
|
-
|
|
2718
|
+
// Demo3D rotation triples are pitch (X), yaw (Y), and roll (Z). Its
|
|
2719
|
+
// RotationYawPitchRoll matrix corresponds to Three's intrinsic YXZ order.
|
|
2720
|
+
object.rotation.set(-(values[3] ?? 0), -(values[4] ?? 0), values[5] ?? 0, "YXZ");
|
|
2505
2721
|
}
|
|
2506
2722
|
function convertTriangleGeometryToThreeCoordinates(geometry) {
|
|
2507
2723
|
geometry.scale(1, 1, -1);
|