@needle-tools/engine 4.11.5-next.ca8b86d → 4.11.5-next.d9019e4
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/CHANGELOG.md +7 -5
- package/components.needle.json +1 -1
- package/dist/generateMeshBVH.worker-mO20N_b8.js +21 -0
- package/dist/{gltf-progressive-GwdQV1Qx.umd.cjs → gltf-progressive-DWcmTMCh.umd.cjs} +1 -1
- package/dist/{gltf-progressive-CftVUJy3.min.js → gltf-progressive-DZrY8VT6.min.js} +2 -2
- package/dist/{gltf-progressive-BvlZQAkt.js → gltf-progressive-DgYz5BYa.js} +19 -19
- package/dist/loader.worker-Dip-PthR.js +23 -0
- package/dist/{needle-engine.bundle-9X7aq_Lu.js → needle-engine.bundle-D4DKOVeW.js} +4821 -4705
- package/dist/{needle-engine.bundle-DsQVlU6y.umd.cjs → needle-engine.bundle-DbRanrPL.umd.cjs} +141 -146
- package/dist/{needle-engine.bundle-DAt-oKnG.min.js → needle-engine.bundle-GILPT50v.min.js} +147 -152
- package/dist/needle-engine.js +336 -335
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-CJC0Npcd.js → postprocessing-BTW9pD_s.js} +1822 -1723
- package/dist/{postprocessing-DrM4PWU3.umd.cjs → postprocessing-CMgoN5t5.umd.cjs} +229 -158
- package/dist/{postprocessing-l7zsdO_Q.min.js → postprocessing-DYDtB188.min.js} +227 -156
- package/dist/rapier-B3oL1ap-.js +5217 -0
- package/dist/rapier-DJ-luMxr.min.js +1 -0
- package/dist/rapier-DQltNJbN.umd.cjs +1 -0
- package/dist/{three-BDW9I486.min.js → three-B7CT31Bt.min.js} +1 -5
- package/dist/{three-MHVqtJYj.js → three-DfMvBzXi.js} +0 -5
- package/dist/{three-examples-CgwGHSgz.umd.cjs → three-examples-CsW4_6LI.umd.cjs} +2 -7
- package/dist/{three-examples-fvEPSC8L.min.js → three-examples-D1P7eEhn.min.js} +2 -7
- package/dist/{three-examples-C5Ht-QFN.js → three-examples-D1SK93ek.js} +1 -7
- package/dist/{three-mesh-ui-BjWTTk1R.js → three-mesh-ui-C_uSB5dD.js} +1 -1
- package/dist/{three-mesh-ui-Bm32sS2a.umd.cjs → three-mesh-ui-DpATDXwU.umd.cjs} +1 -1
- package/dist/{three-mesh-ui-CLdkp21K.min.js → three-mesh-ui-LQ44s0AL.min.js} +1 -1
- package/dist/{three-iFaDq9U3.umd.cjs → three-qj71I7J3.umd.cjs} +2 -6
- package/dist/{vendor-CsyK1CFs.min.js → vendor-BKGa4GE0.min.js} +34 -39
- package/dist/{vendor-petGQl0N.js → vendor-D0zoswDa.js} +1626 -1605
- package/dist/{vendor-6kAXU6fm.umd.cjs → vendor-UCpFAwt1.umd.cjs} +30 -35
- package/lib/engine/debug/debug_spector.d.ts +16 -0
- package/lib/engine/debug/debug_spector.js +28 -0
- package/lib/engine/debug/debug_spector.js.map +1 -0
- package/lib/engine/engine_application.d.ts +7 -0
- package/lib/engine/engine_application.js +8 -1
- package/lib/engine/engine_application.js.map +1 -1
- package/lib/engine/engine_context.d.ts +15 -5
- package/lib/engine/engine_context.js +29 -5
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_physics.js +6 -2
- package/lib/engine/engine_physics.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +11 -2
- package/lib/engine/engine_physics_rapier.js +9 -0
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_scenelighting.d.ts +12 -1
- package/lib/engine/engine_scenelighting.js +21 -1
- package/lib/engine/engine_scenelighting.js.map +1 -1
- package/lib/engine/engine_types.d.ts +16 -0
- package/lib/engine/engine_typestore.d.ts +1 -0
- package/lib/engine/engine_typestore.js +5 -6
- package/lib/engine/engine_typestore.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +1 -0
- package/lib/engine/webcomponents/needle-engine.js +3 -0
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.js +2 -1
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine-components/Component.d.ts +5 -0
- package/lib/engine-components/Component.js +7 -0
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.js +16 -10
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/Renderer.js +2 -2
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/RendererInstancing.d.ts +5 -3
- package/lib/engine-components/RendererInstancing.js +64 -31
- package/lib/engine-components/RendererInstancing.js.map +1 -1
- package/lib/engine-components/Skybox.d.ts +15 -5
- package/lib/engine-components/Skybox.js +37 -25
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.d.ts +24 -3
- package/lib/engine-components/export/usdz/USDZExporter.js +36 -2
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.d.ts +1 -0
- package/lib/engine-components/web/CursorFollow.js +2 -0
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/lib/engine-components/webxr/WebXR.js +4 -0
- package/lib/engine-components/webxr/WebXR.js.map +1 -1
- package/lib/needle-engine.js +2 -1
- package/lib/needle-engine.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/debug/debug_spector.ts +43 -0
- package/src/engine/engine_application.ts +16 -1
- package/src/engine/engine_context.ts +41 -7
- package/src/engine/engine_physics.ts +6 -2
- package/src/engine/engine_physics_rapier.ts +11 -2
- package/src/engine/engine_scenelighting.ts +30 -8
- package/src/engine/engine_types.ts +17 -0
- package/src/engine/engine_typestore.ts +5 -6
- package/src/engine/webcomponents/needle-engine.ts +4 -0
- package/src/engine/xr/NeedleXRSession.ts +3 -1
- package/src/engine-components/Component.ts +9 -1
- package/src/engine-components/ReflectionProbe.ts +18 -10
- package/src/engine-components/Renderer.ts +2 -2
- package/src/engine-components/RendererInstancing.ts +69 -33
- package/src/engine-components/Skybox.ts +47 -36
- package/src/engine-components/export/usdz/USDZExporter.ts +52 -5
- package/src/engine-components/web/CursorFollow.ts +3 -0
- package/src/engine-components/webxr/WebXR.ts +4 -0
- package/src/needle-engine.ts +4 -2
- package/dist/generateMeshBVH.worker-B9bjdr6J.js +0 -25
- package/dist/loader.worker-CiTwpNPW.js +0 -27
- package/dist/rapier-BqdcSmKY.umd.cjs +0 -1
- package/dist/rapier-Cg3w3nFI.min.js +0 -1
- package/dist/rapier-sU12SWAs.js +0 -5217
|
@@ -29,8 +29,9 @@ export class InstancingHandler {
|
|
|
29
29
|
* (The instancing mesh renderer will grow x2 if the max instance count is reached)
|
|
30
30
|
* @default 4
|
|
31
31
|
* @returns The initial instance count
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
*/
|
|
33
|
+
// @ts-ignore (ignore the unused parameter warning)
|
|
34
|
+
static getStartInstanceCount = (obj: Object3D) => {
|
|
34
35
|
return 4;
|
|
35
36
|
};
|
|
36
37
|
|
|
@@ -45,20 +46,24 @@ export class InstancingHandler {
|
|
|
45
46
|
if (res) {
|
|
46
47
|
if (handlesArray === null) handlesArray = [];
|
|
47
48
|
handlesArray.push(res);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
NEEDLE_progressive.assignMeshLOD(mesh, 0).then(lod => {
|
|
57
|
-
if (lod && renderer.activeAndEnabled && geometry != lod) {
|
|
58
|
-
res.setGeometry(lod);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
49
|
+
|
|
50
|
+
// Load LOD for textures
|
|
51
|
+
const mat = res.object.material;
|
|
52
|
+
if(Array.isArray(mat)) {
|
|
53
|
+
mat.forEach(m => NEEDLE_progressive.assignTextureLOD(m, 0));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
NEEDLE_progressive.assignTextureLOD(mat, 0);
|
|
61
57
|
}
|
|
58
|
+
|
|
59
|
+
// Load LOD for geometry
|
|
60
|
+
const mesh = res.object;
|
|
61
|
+
const geometry = mesh.geometry;
|
|
62
|
+
NEEDLE_progressive.assignMeshLOD(mesh, 0).then(lod => {
|
|
63
|
+
if (lod && geometry != lod) {
|
|
64
|
+
res.setGeometry(lod);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
else if (level <= 0 && obj.type !== "Mesh") {
|
|
@@ -349,7 +354,7 @@ class InstancedMeshRenderer {
|
|
|
349
354
|
private _context: Context;
|
|
350
355
|
private _batchedMesh: BatchedMesh;
|
|
351
356
|
private _handles: (InstanceHandle | null)[] = [];
|
|
352
|
-
private
|
|
357
|
+
private _geometryIds = new WeakMap<BufferGeometry, number>();
|
|
353
358
|
private _maxInstanceCount: number;
|
|
354
359
|
|
|
355
360
|
private _currentInstanceCount = 0;
|
|
@@ -439,6 +444,10 @@ class InstancedMeshRenderer {
|
|
|
439
444
|
private _needUpdateBounds: boolean = false;
|
|
440
445
|
private _debugMaterial: MeshStandardMaterial | null = null;
|
|
441
446
|
|
|
447
|
+
private getBatchedMeshName() {
|
|
448
|
+
return this.name ? `${this.name} (BatchedMesh)` : "BatchedMesh";
|
|
449
|
+
}
|
|
450
|
+
|
|
442
451
|
constructor(name: string, geo: BufferGeometry, material: Material, initialMaxCount: number, context: Context) {
|
|
443
452
|
this.name = name;
|
|
444
453
|
this.geometry = geo;
|
|
@@ -452,6 +461,7 @@ class InstancedMeshRenderer {
|
|
|
452
461
|
this._maxVertexCount = estimate.vertexCount;
|
|
453
462
|
this._maxIndexCount = estimate.indexCount;
|
|
454
463
|
this._batchedMesh = new BatchedMesh(this._maxInstanceCount, this._maxVertexCount, this._maxIndexCount, this._debugMaterial ?? this.material);
|
|
464
|
+
this._batchedMesh.name = this.getBatchedMeshName();
|
|
455
465
|
// this.inst = new InstancedMesh(geo, material, count);
|
|
456
466
|
this._batchedMesh[$instancingAutoUpdateBounds] = true;
|
|
457
467
|
// this.inst.count = 0;
|
|
@@ -475,7 +485,7 @@ class InstancedMeshRenderer {
|
|
|
475
485
|
context.post_render_callbacks.push(this.onAfterRender);
|
|
476
486
|
|
|
477
487
|
if (debugInstancing) {
|
|
478
|
-
console.log(`Instanced renderer created with ${this._maxInstanceCount} instances, ${this._maxVertexCount} max vertices and ${this._maxIndexCount} max indices for \"${name}\"`)
|
|
488
|
+
console.log(`Instanced renderer (${this.name}) created with ${this._maxInstanceCount} instances, ${this._maxVertexCount} max vertices and ${this._maxIndexCount} max indices for \"${name}\"`)
|
|
479
489
|
}
|
|
480
490
|
}
|
|
481
491
|
|
|
@@ -522,7 +532,8 @@ class InstancedMeshRenderer {
|
|
|
522
532
|
return false;
|
|
523
533
|
}
|
|
524
534
|
|
|
525
|
-
|
|
535
|
+
const newInstanceCount = this._currentInstanceCount + 1;
|
|
536
|
+
if (newInstanceCount > this._maxInstanceCount || this.mustGrow(geo)) {
|
|
526
537
|
if (this.allowResize) {
|
|
527
538
|
this.grow(geo);
|
|
528
539
|
}
|
|
@@ -644,34 +655,42 @@ class InstancedMeshRenderer {
|
|
|
644
655
|
private mustGrow(geo?: BufferGeometry): boolean {
|
|
645
656
|
if (this.count >= this._maxInstanceCount) return true;
|
|
646
657
|
if (!geo || !geo.attributes) return false;
|
|
658
|
+
|
|
659
|
+
const isKnownGeometry = this._geometryIds.has(geo);
|
|
660
|
+
if (isKnownGeometry) return false;
|
|
661
|
+
|
|
647
662
|
const meshInfo = getMeshInformation(geo);
|
|
648
663
|
const newVertexCount = meshInfo.vertexCount;
|
|
649
664
|
const newIndexCount = meshInfo.indexCount;
|
|
650
665
|
return this._currentVertexCount + newVertexCount > this._maxVertexCount || this._currentIndexCount + newIndexCount > this._maxIndexCount;
|
|
651
666
|
}
|
|
652
667
|
|
|
668
|
+
private _growId = 0;
|
|
653
669
|
private grow(geometry: BufferGeometry) {
|
|
670
|
+
const id = ++this._growId;
|
|
654
671
|
const growFactor = 2;
|
|
655
672
|
const newSize = Math.ceil(this._maxInstanceCount * growFactor);
|
|
656
673
|
|
|
657
674
|
// create a new BatchedMesh instance
|
|
675
|
+
// TODO: we should keep track of how many instances for each geometry we have and consider that when estimating new space
|
|
658
676
|
const estimatedSpace = this.tryEstimateVertexCountSize(newSize, [geometry]);// geometry.attributes.position.count;
|
|
659
677
|
// const indices = geometry.index ? geometry.index.count : 0;
|
|
660
678
|
const newMaxVertexCount = Math.max(this._maxVertexCount, estimatedSpace.vertexCount);
|
|
661
|
-
const newMaxIndexCount = Math.max(this._maxIndexCount, estimatedSpace.indexCount
|
|
679
|
+
const newMaxIndexCount = Math.max(this._maxIndexCount, estimatedSpace.indexCount);//, Math.ceil(this._maxVertexCount * growFactor));
|
|
662
680
|
|
|
663
681
|
if (debugInstancing) {
|
|
664
682
|
const geometryInfo = getMeshInformation(geometry);
|
|
665
|
-
console.warn(`[Instancing] Growing Buffer\nMesh: \"${this.name}${geometry.name?.length ? "/" + geometry.name : ""}\"
|
|
683
|
+
console.warn(`[Instancing] Growing Buffer\nMesh: \"${this.name}${geometry.name?.length ? "/" + geometry.name : ""}\" (${geometryInfo.vertexCount.toLocaleString()} vertices, ${geometryInfo.indexCount.toLocaleString()} indices)\nMax count ${this._maxInstanceCount.toLocaleString()} → ${newSize.toLocaleString()}\nMax vertex count ${this._maxVertexCount.toLocaleString()} -> ${newMaxVertexCount.toLocaleString()}\nMax index count ${this._maxIndexCount.toLocaleString()} -> ${newMaxIndexCount.toLocaleString()}`);
|
|
666
684
|
this._debugMaterial = createDebugMaterial();
|
|
667
685
|
}
|
|
668
686
|
else if (isDevEnvironment()) {
|
|
669
|
-
console.debug(`[Instancing] Growing Buffer\nMesh: \"${this.name}${geometry.name?.length ? "/" + geometry.name : ""}\"\nMax count ${this._maxInstanceCount} → ${newSize}\nMax vertex count ${this._maxVertexCount} -> ${newMaxVertexCount}\nMax index count ${this._maxIndexCount} -> ${newMaxIndexCount}`);
|
|
687
|
+
console.debug(`[Instancing] Growing Buffer\nMesh: \"${this.name}${geometry.name?.length ? "/" + geometry.name : ""}\"\nMax count ${this._maxInstanceCount} → ${newSize}\nMax vertex count ${this._maxVertexCount.toLocaleString()} -> ${newMaxVertexCount.toLocaleString()}\nMax index count ${this._maxIndexCount.toLocaleString()} -> ${newMaxIndexCount.toLocaleString()}`);
|
|
670
688
|
}
|
|
671
689
|
|
|
672
690
|
this._maxVertexCount = newMaxVertexCount;
|
|
673
691
|
this._maxIndexCount = newMaxIndexCount;
|
|
674
692
|
const newInst = new BatchedMesh(newSize, this._maxVertexCount, this._maxIndexCount, this._debugMaterial ?? this.material);
|
|
693
|
+
newInst.name = this.getBatchedMeshName();
|
|
675
694
|
newInst.layers = this._batchedMesh.layers;
|
|
676
695
|
newInst.castShadow = this._batchedMesh.castShadow;
|
|
677
696
|
newInst.receiveShadow = this._batchedMesh.receiveShadow;
|
|
@@ -686,7 +705,7 @@ class InstancedMeshRenderer {
|
|
|
686
705
|
// dispose the old batched mesh
|
|
687
706
|
this._batchedMesh.dispose();
|
|
688
707
|
this._batchedMesh.removeFromParent();
|
|
689
|
-
this._geometryIds
|
|
708
|
+
this._geometryIds = new WeakMap<BufferGeometry, number>();
|
|
690
709
|
|
|
691
710
|
this._batchedMesh = newInst;
|
|
692
711
|
this._maxInstanceCount = newSize;
|
|
@@ -698,6 +717,11 @@ class InstancedMeshRenderer {
|
|
|
698
717
|
const original = [...this._handles];
|
|
699
718
|
this._handles = [];
|
|
700
719
|
for (const handle of original) {
|
|
720
|
+
if (id !== this._growId) {
|
|
721
|
+
// another grow happened in the meantime
|
|
722
|
+
if (debugInstancing) console.warn("[Instancing] Aborting grow since another grow happened in the meantime");
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
701
725
|
if (handle && handle.__instanceIndex >= 0) {
|
|
702
726
|
this.addGeometry(handle);
|
|
703
727
|
this._handles[handle.__instanceIndex] = handle;
|
|
@@ -722,23 +746,31 @@ class InstancedMeshRenderer {
|
|
|
722
746
|
entry.count += 1;
|
|
723
747
|
}
|
|
724
748
|
|
|
749
|
+
if (_newGeometries && _newGeometries?.length > 0) {
|
|
750
|
+
const index = _newGeometries.indexOf(handle.object.geometry as BufferGeometry);
|
|
751
|
+
if (index !== -1) {
|
|
752
|
+
_newGeometries.splice(index, 1);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
725
755
|
}
|
|
726
756
|
}
|
|
727
757
|
|
|
728
758
|
// then calculate the total vertex count
|
|
729
759
|
let totalVertices = 0;
|
|
730
760
|
let totalIndices = 0;
|
|
761
|
+
let totalGeometries = 0;
|
|
731
762
|
// let maxVertices = 0;
|
|
732
763
|
for (const [_geo, data] of usedGeometries) {
|
|
733
|
-
|
|
734
|
-
|
|
764
|
+
totalGeometries += 1;
|
|
765
|
+
totalVertices += data.vertexCount;
|
|
766
|
+
totalIndices += data.indexCount;
|
|
735
767
|
// maxVertices = Math.max(maxVertices, geo.attributes.position.count * count);
|
|
736
768
|
}
|
|
737
769
|
// we calculate the average to make an educated guess of how many vertices will be needed with the new buffer count
|
|
738
|
-
const averageVerts = Math.ceil(totalVertices / Math.max(1,
|
|
739
|
-
let maxVertexCount = averageVerts *
|
|
740
|
-
const averageIndices = Math.ceil(totalIndices / Math.max(1,
|
|
741
|
-
let maxIndexCount = averageIndices *
|
|
770
|
+
const averageVerts = Math.ceil(totalVertices / Math.max(1, totalGeometries));
|
|
771
|
+
let maxVertexCount = averageVerts * totalGeometries;
|
|
772
|
+
const averageIndices = Math.ceil(totalIndices / Math.max(1, totalGeometries));
|
|
773
|
+
let maxIndexCount = averageIndices * totalGeometries;
|
|
742
774
|
|
|
743
775
|
// if new geometries are provided we *know* that they will be added
|
|
744
776
|
// so we make sure to include them in the calculation
|
|
@@ -753,6 +785,10 @@ class InstancedMeshRenderer {
|
|
|
753
785
|
}
|
|
754
786
|
}
|
|
755
787
|
|
|
788
|
+
if (debugInstancing) {
|
|
789
|
+
console.log(`[Instancing] Estimated size for new buffer ${this.name}\nGeometries: ${totalGeometries} (New: ${_newGeometries?.length || 0})\nInstances: ${newMaxInstances}\nEstimated Vertices: ${maxVertexCount.toLocaleString()}\nEstimated Indices: ${maxIndexCount.toLocaleString()}`);
|
|
790
|
+
}
|
|
791
|
+
|
|
756
792
|
return { vertexCount: maxVertexCount, indexCount: maxIndexCount };
|
|
757
793
|
}
|
|
758
794
|
|
|
@@ -770,16 +806,16 @@ class InstancedMeshRenderer {
|
|
|
770
806
|
let geometryId = this._geometryIds.get(geo);
|
|
771
807
|
if (geometryId === undefined || geometryId === null) {
|
|
772
808
|
if (debugInstancing)
|
|
773
|
-
console.
|
|
809
|
+
console.warn(`[Instancing] > ADD NEW GEOMETRY \"${handle.name} (${geo.name}; ${geo.uuid})\"\nCurrent Instances: ${this._currentInstanceCount}\nMax Vertices: ${handle.maxVertexCount.toLocaleString()}\nMax Indices: ${handle.maxIndexCount.toLocaleString()}\nMax Triangles: ${(handle.maxIndexCount / 3).toLocaleString()}`);
|
|
774
810
|
|
|
775
811
|
geometryId = this._batchedMesh.addGeometry(geo, handle.maxVertexCount, handle.maxIndexCount);
|
|
776
812
|
this._geometryIds.set(geo, geometryId);
|
|
813
|
+
this._currentVertexCount += handle.maxVertexCount;
|
|
814
|
+
this._currentIndexCount += handle.maxIndexCount;
|
|
777
815
|
}
|
|
778
816
|
else {
|
|
779
817
|
if (debugInstancing === "verbose") console.log(`[Instancing] > ADD INSTANCE \"${handle.name}\"\nGEOMETRY_ID=${geometryId}\n${this._currentInstanceCount} instances`);
|
|
780
818
|
}
|
|
781
|
-
this._currentVertexCount += handle.maxVertexCount;
|
|
782
|
-
this._currentIndexCount += handle.maxIndexCount;
|
|
783
819
|
const i = this._batchedMesh.addInstance(geometryId);
|
|
784
820
|
handle.__geometryIndex = geometryId;
|
|
785
821
|
handle.__instanceIndex = i;
|
|
@@ -787,7 +823,7 @@ class InstancedMeshRenderer {
|
|
|
787
823
|
handle.__reservedIndexRange = handle.maxIndexCount;
|
|
788
824
|
this._batchedMesh.setMatrixAt(i, handle.object.matrixWorld);
|
|
789
825
|
if (debugInstancing)
|
|
790
|
-
console.debug(`[Instancing] > ADDED INSTANCE \"${handle.name}\"\nGEOMETRY_ID=${geometryId}\n${this._currentInstanceCount} instances\nIndex: ${handle.__instanceIndex}`);
|
|
826
|
+
console.debug(`[Instancing] > ADDED INSTANCE \"${handle.name}\"\nGEOMETRY_ID=${geometryId}\n${this._currentInstanceCount} instances\nIndex: ${handle.__instanceIndex}\nVertices: ${this._currentVertexCount.toLocaleString()}/${this._maxVertexCount.toLocaleString()},\nIndices: ${this._currentIndexCount.toLocaleString()}/${this._maxIndexCount.toLocaleString()}`);
|
|
791
827
|
|
|
792
828
|
}
|
|
793
829
|
|
|
@@ -803,7 +839,7 @@ class InstancedMeshRenderer {
|
|
|
803
839
|
// this.inst.deleteGeometry(handle.__instanceIndex);
|
|
804
840
|
// else
|
|
805
841
|
// this._batchedMesh.setVisibleAt(handle.__instanceIndex, false);
|
|
806
|
-
if(debugInstancing) {
|
|
842
|
+
if (debugInstancing) {
|
|
807
843
|
console.debug(`[Instancing] < REMOVE INSTANCE \"${handle.name}\" at [${handle.__instanceIndex}]\nGEOMETRY_ID=${handle.__geometryIndex}\n${this._currentInstanceCount} instances\nIndex: ${handle.__instanceIndex}`);
|
|
808
844
|
}
|
|
809
845
|
this._batchedMesh.deleteInstance(handle.__instanceIndex);
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import { CompressedCubeTexture,
|
|
2
|
-
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
|
|
3
|
-
import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";
|
|
4
|
-
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
|
|
1
|
+
import { CompressedCubeTexture, CubeTexture, CubeUVReflectionMapping, EquirectangularRefractionMapping, Texture } from "three"
|
|
5
2
|
|
|
3
|
+
import { isDevEnvironment } from "../engine/debug/debug.js";
|
|
6
4
|
import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
|
|
7
5
|
import { syncField } from "../engine/engine_networking_auto.js";
|
|
8
6
|
import { loadPMREM } from "../engine/engine_pmrem.js";
|
|
9
7
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
10
8
|
import { type IContext } from "../engine/engine_types.js";
|
|
11
|
-
import { addAttributeChangeCallback, getParam,
|
|
9
|
+
import { addAttributeChangeCallback, getParam, removeAttributeChangeCallback, toSourceId } from "../engine/engine_utils.js";
|
|
12
10
|
import { registerObservableAttribute } from "../engine/webcomponents/needle-engine.extras.js";
|
|
13
|
-
import { Camera
|
|
11
|
+
import { Camera } from "./Camera.js";
|
|
14
12
|
import { Behaviour, GameObject } from "./Component.js";
|
|
15
13
|
|
|
16
14
|
const debug = getParam("debugskybox");
|
|
@@ -18,6 +16,29 @@ const debug = getParam("debugskybox");
|
|
|
18
16
|
registerObservableAttribute("background-image");
|
|
19
17
|
registerObservableAttribute("environment-image");
|
|
20
18
|
|
|
19
|
+
|
|
20
|
+
type MagicSkyboxName = "studio" | "blurred-skybox" | "quicklook-ar" | "quicklook";
|
|
21
|
+
const MagicSkyboxNames: Record<MagicSkyboxName, { url: string, url_low: string }> = {
|
|
22
|
+
"studio": {
|
|
23
|
+
url: "https://cdn.needle.tools/static/skybox/modelviewer-Neutral.pmrem4x4.ktx2?pmrem",
|
|
24
|
+
url_low: "https://cdn.needle.tools/static/skybox/modelviewer-Neutral-small.pmrem4x4.ktx2?pmrem"
|
|
25
|
+
},
|
|
26
|
+
"blurred-skybox": {
|
|
27
|
+
url: "https://cdn.needle.tools/static/skybox/blurred-skybox.pmrem4x4.ktx2?pmrem",
|
|
28
|
+
url_low: "https://cdn.needle.tools/static/skybox/blurred-skybox-small.pmrem4x4.ktx2?pmrem"
|
|
29
|
+
},
|
|
30
|
+
"quicklook-ar": {
|
|
31
|
+
url: "https://cdn.needle.tools/static/skybox/QuickLook-ARMode.pmrem4x4.ktx2?pmrem",
|
|
32
|
+
url_low: "https://cdn.needle.tools/static/skybox/QuickLook-ARMode-small.pmrem4x4.ktx2?pmrem"
|
|
33
|
+
},
|
|
34
|
+
"quicklook": {
|
|
35
|
+
url: "https://cdn.needle.tools/static/skybox/QuickLook-ObjectMode.pmrem4x4.ktx2?pmrem",
|
|
36
|
+
url_low: "https://cdn.needle.tools/static/skybox/QuickLook-ObjectMode-small.pmrem4x4.ktx2?pmrem"
|
|
37
|
+
}
|
|
38
|
+
} as const;
|
|
39
|
+
type AnyString = string & { _brand?: never };
|
|
40
|
+
|
|
41
|
+
|
|
21
42
|
function createRemoteSkyboxComponent(context: IContext, url: string, skybox: boolean, environment: boolean, attribute: "background-image" | "environment-image") {
|
|
22
43
|
|
|
23
44
|
// when the user sets the attribute to a color we can not handle it as a skybox url.
|
|
@@ -91,8 +112,9 @@ ContextRegistry.registerCallback(ContextEvent.ContextCreationStart, () => {
|
|
|
91
112
|
});
|
|
92
113
|
|
|
93
114
|
|
|
115
|
+
|
|
94
116
|
/**
|
|
95
|
-
* RemoteSkybox is a component that allows you to set the skybox of a scene from a URL or a
|
|
117
|
+
* RemoteSkybox is a component that allows you to set the skybox or environment texture of a scene from a URL, a local file or a static skybox name.
|
|
96
118
|
* It supports .hdr, .exr, .jpg, .png files.
|
|
97
119
|
*
|
|
98
120
|
* ### Events
|
|
@@ -127,8 +149,13 @@ ContextRegistry.registerCallback(ContextEvent.ContextCreationStart, () => {
|
|
|
127
149
|
export class RemoteSkybox extends Behaviour {
|
|
128
150
|
|
|
129
151
|
/**
|
|
130
|
-
* URL to a remote skybox.
|
|
131
|
-
* To update the skybox/environment map use `setSkybox(url)
|
|
152
|
+
* URL to a remote skybox.
|
|
153
|
+
* To update the skybox/environment map use `setSkybox(url)`.
|
|
154
|
+
*
|
|
155
|
+
* The url can also be set to a magic skybox name.
|
|
156
|
+
* Magic name options are: "quicklook", "quicklook-ar", "studio", "blurred-skybox".
|
|
157
|
+
* These will resolve to built-in skyboxes hosted on the Needle CDN that are static, optimized for the web and will never change.
|
|
158
|
+
*
|
|
132
159
|
* @example
|
|
133
160
|
* ```ts
|
|
134
161
|
* skybox.url = "https://example.com/skybox.hdr";
|
|
@@ -136,7 +163,7 @@ export class RemoteSkybox extends Behaviour {
|
|
|
136
163
|
*/
|
|
137
164
|
@syncField(RemoteSkybox.prototype.urlChangedSyncField)
|
|
138
165
|
@serializable(URL)
|
|
139
|
-
url
|
|
166
|
+
url: MagicSkyboxName | AnyString = "studio";
|
|
140
167
|
|
|
141
168
|
/**
|
|
142
169
|
* When enabled a user can drop a link to a skybox image on the scene to set the skybox.
|
|
@@ -208,7 +235,7 @@ export class RemoteSkybox extends Behaviour {
|
|
|
208
235
|
* @param name Define name of the file with extension if it isn't apart of the url
|
|
209
236
|
* @returns Whether the skybox was successfully set
|
|
210
237
|
*/
|
|
211
|
-
async setSkybox(url:
|
|
238
|
+
async setSkybox(url: MagicSkyboxName | AnyString | undefined | null, name?: string) {
|
|
212
239
|
if (!this.activeAndEnabled) return false;
|
|
213
240
|
|
|
214
241
|
url = tryParseMagicSkyboxName(url, this.environment, this.background);
|
|
@@ -404,38 +431,22 @@ export class RemoteSkybox extends Behaviour {
|
|
|
404
431
|
|
|
405
432
|
|
|
406
433
|
|
|
407
|
-
|
|
408
434
|
function tryParseMagicSkyboxName(str: string | null | undefined, environment: boolean, background: boolean): string | null {
|
|
409
435
|
|
|
410
436
|
if (str === null || str === undefined) return null;
|
|
411
437
|
|
|
412
438
|
const useLowRes = environment && !background;
|
|
413
439
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
return "https://cdn.needle.tools/static/skybox/blurred-skybox-small.pmrem4x4.ktx2?pmrem";
|
|
424
|
-
}
|
|
425
|
-
return "https://cdn.needle.tools/static/skybox/blurred-skybox.pmrem4x4.ktx2?pmrem";
|
|
426
|
-
case "quicklook-ar":
|
|
427
|
-
if (useLowRes) {
|
|
428
|
-
return "https://cdn.needle.tools/static/skybox/QuickLook-ARMode-small.pmrem4x4.ktx2?pmrem";
|
|
429
|
-
}
|
|
430
|
-
return "https://cdn.needle.tools/static/skybox/QuickLook-ARMode.pmrem4x4.ktx2?pmrem";
|
|
431
|
-
case "quicklook":
|
|
432
|
-
if (useLowRes) {
|
|
433
|
-
return "https://cdn.needle.tools/static/skybox/QuickLook-ObjectMode-small.pmrem4x4.ktx2?pmrem";
|
|
434
|
-
}
|
|
435
|
-
return "https://cdn.needle.tools/static/skybox/QuickLook-ObjectMode.pmrem4x4.ktx2?pmrem";
|
|
440
|
+
const value = MagicSkyboxNames[str.toLowerCase() as MagicSkyboxName];
|
|
441
|
+
if (value) {
|
|
442
|
+
return useLowRes ? value.url_low : value.url;
|
|
443
|
+
}
|
|
444
|
+
else if (typeof str === "string" && str?.length && (isDevEnvironment() || debug)) {
|
|
445
|
+
const noUrlOrFile = !str.startsWith("http") && !str.startsWith("file:") && !str.startsWith("blob:") && !str.startsWith("data:");
|
|
446
|
+
if(noUrlOrFile) {
|
|
447
|
+
console.warn(`RemoteSkybox: Unknown magic skybox name "${str}". Valid names are: ${Object.keys(MagicSkyboxNames).map(n => `"${n}"`).join(", ")}`);
|
|
448
|
+
}
|
|
436
449
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
450
|
|
|
440
451
|
return str;
|
|
441
452
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NEEDLE_progressive } from "@needle-tools/gltf-progressive";
|
|
2
|
-
import { Euler,
|
|
2
|
+
import { Euler, Material, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from "three";
|
|
3
3
|
|
|
4
4
|
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/index.js";
|
|
5
5
|
import { hasProLicense } from "../../../engine/engine_license.js";
|
|
@@ -53,6 +53,23 @@ export class CustomBranding {
|
|
|
53
53
|
callToActionURL?: string;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
type BeforeLODExportArguments = {
|
|
57
|
+
readonly exporter: USDZExporter;
|
|
58
|
+
/** The type of LOD being exported, either a texture or a mesh */
|
|
59
|
+
readonly type: "texture" | "mesh";
|
|
60
|
+
/** The Renderer gameobject */
|
|
61
|
+
readonly object: Object3D;
|
|
62
|
+
/** The mesh being exported, if applicable */
|
|
63
|
+
readonly mesh?: Mesh;
|
|
64
|
+
/** The material being exported, if applicable */
|
|
65
|
+
readonly material?: Material;
|
|
66
|
+
/** By default LOD 0 will be exported (highest quality).
|
|
67
|
+
* Set to a different LOD level if needed.
|
|
68
|
+
* Set to -1 to disable LOD export and simply export the current version.
|
|
69
|
+
*/
|
|
70
|
+
overrideLevel?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
/**
|
|
57
74
|
* Exports the current scene or a specific object as USDZ file and opens it in QuickLook on iOS/iPadOS/visionOS.
|
|
58
75
|
* The USDZ file is generated using the Needle Engine ThreeUSDZExporter.
|
|
@@ -72,8 +89,12 @@ export class CustomBranding {
|
|
|
72
89
|
*/
|
|
73
90
|
export class USDZExporter extends Behaviour {
|
|
74
91
|
|
|
75
|
-
|
|
76
|
-
static readonly
|
|
92
|
+
/** Called before the USDZ file is exported */
|
|
93
|
+
static readonly beforeExport = new EventList<{ readonly exporter: USDZExporter }>();
|
|
94
|
+
/** Called after the USDZ file has been exported */
|
|
95
|
+
static readonly afterExport = new EventList<{ readonly exporter: USDZExporter }>();
|
|
96
|
+
/** Called before LOD level are exported. Can be used to override the LOD export settings */
|
|
97
|
+
static readonly beforeLODExport = new EventList<BeforeLODExportArguments>();
|
|
77
98
|
|
|
78
99
|
/**
|
|
79
100
|
* Assign the object to export as USDZ file. If undefined or null, the whole scene will be exported.
|
|
@@ -329,7 +350,20 @@ export class USDZExporter extends Behaviour {
|
|
|
329
350
|
for (const rend of renderers) {
|
|
330
351
|
for (const mesh of rend.sharedMeshes) {
|
|
331
352
|
if (mesh) {
|
|
332
|
-
|
|
353
|
+
let lodLevel = 0;
|
|
354
|
+
const args: BeforeLODExportArguments = { exporter: this, type: "mesh" as const, object: rend.gameObject, mesh: mesh, };
|
|
355
|
+
USDZExporter.beforeLODExport.invoke(args);
|
|
356
|
+
if (args.overrideLevel !== undefined) {
|
|
357
|
+
if (args.overrideLevel === -1) {
|
|
358
|
+
if (debug) console.warn("Skipping LOD export for mesh due to overrideLevel -1", rend.gameObject, mesh);
|
|
359
|
+
continue; // skip LOD assignment
|
|
360
|
+
}
|
|
361
|
+
else if (args.overrideLevel >= 0) {
|
|
362
|
+
lodLevel = args.overrideLevel;
|
|
363
|
+
if (debug) console.log("Overriding LOD level for mesh export to level " + lodLevel + " " + mesh.name);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const task = NEEDLE_progressive.assignMeshLOD(mesh, lodLevel);
|
|
333
367
|
if (task instanceof Promise)
|
|
334
368
|
progressiveLoading.push(new Promise<void>((resolve, reject) => {
|
|
335
369
|
task.then(() => {
|
|
@@ -342,7 +376,20 @@ export class USDZExporter extends Behaviour {
|
|
|
342
376
|
}
|
|
343
377
|
for (const mat of rend.sharedMaterials) {
|
|
344
378
|
if (mat) {
|
|
345
|
-
|
|
379
|
+
let lodLevel = 0;
|
|
380
|
+
const args: BeforeLODExportArguments = { exporter: this, type: "texture" as const, object: rend.gameObject, material: mat };
|
|
381
|
+
USDZExporter.beforeLODExport.invoke(args);
|
|
382
|
+
if (args.overrideLevel !== undefined) {
|
|
383
|
+
if (args.overrideLevel === -1) {
|
|
384
|
+
if (debug) console.warn("Skipping LOD assignment due to overrideLevel -1", rend.gameObject, mat);
|
|
385
|
+
continue; // skip LOD assignment
|
|
386
|
+
}
|
|
387
|
+
else if (args.overrideLevel >= 0) {
|
|
388
|
+
lodLevel = args.overrideLevel;
|
|
389
|
+
if (debug) console.log("Overriding LOD level for texture export to level " + lodLevel + " " + mat.name);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const task = NEEDLE_progressive.assignTextureLOD(mat, lodLevel);
|
|
346
393
|
if (task instanceof Promise)
|
|
347
394
|
progressiveLoading.push(new Promise<void>((resolve, reject) => {
|
|
348
395
|
task.then(() => {
|
|
@@ -20,6 +20,9 @@ const debug = getParam("debugcursor");
|
|
|
20
20
|
*/
|
|
21
21
|
export class CursorFollow extends Behaviour {
|
|
22
22
|
|
|
23
|
+
// testing this for compilation
|
|
24
|
+
static readonly NAME = "CursorFollow";
|
|
25
|
+
|
|
23
26
|
/**
|
|
24
27
|
* Damping for the movement, set to 0 for instant movement
|
|
25
28
|
* @default 0
|
|
@@ -331,6 +331,10 @@ export class WebXR extends Behaviour {
|
|
|
331
331
|
console.warn(`WebXR: another WebXR component is already active (${WebXR.activeWebXRComponent?.name}). This is ignored: ${this.name}`);
|
|
332
332
|
return;
|
|
333
333
|
}
|
|
334
|
+
if (this.activeAndEnabled === false || this.destroyed) {
|
|
335
|
+
console.debug("[WebXR] onBeforeXR called on disabled or destroyed component");
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
334
338
|
WebXR.activeWebXRComponent = this;
|
|
335
339
|
|
|
336
340
|
if (_mode == "immersive-ar" && this.useDepthSensing) {
|
package/src/needle-engine.ts
CHANGED
|
@@ -7,7 +7,7 @@ export * from "./engine-components-experimental/api.js";
|
|
|
7
7
|
export * from "./engine-schemes/api.js";
|
|
8
8
|
|
|
9
9
|
// make accessible for external javascript
|
|
10
|
-
import { Context, loadAsset, NeedleXRSession, onAfterRender, onBeforeRender, onClear, onDestroy, onInitialized, onStart, onUpdate, VERSION } from "./engine/api.js";
|
|
10
|
+
import { Context, loadAsset, NeedleXRSession, onAfterRender, onBeforeRender, onClear, onDestroy, onInitialized, onStart, onUpdate, TypeStore, VERSION } from "./engine/api.js";
|
|
11
11
|
const Needle = {
|
|
12
12
|
VERSION: VERSION,
|
|
13
13
|
Context: Context,
|
|
@@ -15,6 +15,7 @@ const Needle = {
|
|
|
15
15
|
assets: {
|
|
16
16
|
loadFromURL: loadAsset,
|
|
17
17
|
},
|
|
18
|
+
types: TypeStore,
|
|
18
19
|
onStart: onStart,
|
|
19
20
|
onUpdate: onUpdate,
|
|
20
21
|
onBeforeRender: onBeforeRender,
|
|
@@ -22,7 +23,8 @@ const Needle = {
|
|
|
22
23
|
onInitializedContext: onInitialized,
|
|
23
24
|
onDestroyContext: onDestroy,
|
|
24
25
|
onClearContext: onClear,
|
|
25
|
-
};
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
26
28
|
if (globalThis["Needle"]?.VERSION !== undefined) {
|
|
27
29
|
console.warn(`Needle Engine is already imported: ${globalThis["Needle"].VERSION}`);
|
|
28
30
|
}
|