@needle-tools/engine 5.1.0-canary.deec6e4 → 5.1.0-canary.e6680fa
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 +99 -1
- package/SKILL.md +4 -1
- package/components.needle.json +1 -1
- package/dist/needle-engine.bundle-Bl_hyH5G.umd.cjs +1734 -0
- package/dist/needle-engine.bundle-Cduc1gj6.min.js +1734 -0
- package/dist/{needle-engine.bundle-CvtELXh0.js → needle-engine.bundle-DNcqT8nJ.js} +19415 -18452
- package/dist/needle-engine.d.ts +1588 -374
- package/dist/needle-engine.js +572 -569
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/three.js +1 -0
- package/dist/three.min.js +21 -21
- package/dist/three.umd.cjs +16 -16
- package/lib/engine/api.d.ts +8 -1
- package/lib/engine/api.js +7 -1
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/codegen/register_types.js +10 -18
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_audio.d.ts +68 -0
- package/lib/engine/engine_audio.js +172 -0
- package/lib/engine/engine_audio.js.map +1 -1
- package/lib/engine/engine_camera.fit.js +16 -4
- package/lib/engine/engine_camera.fit.js.map +1 -1
- package/lib/engine/engine_components.js +1 -1
- package/lib/engine/engine_components.js.map +1 -1
- package/lib/engine/engine_context.d.ts +21 -8
- package/lib/engine/engine_context.js +46 -16
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_context_eventbus.d.ts +47 -0
- package/lib/engine/engine_context_eventbus.js +47 -0
- package/lib/engine/engine_context_eventbus.js.map +1 -0
- package/lib/engine/engine_disposable.d.ts +172 -0
- package/lib/engine/engine_disposable.js +136 -0
- package/lib/engine/engine_disposable.js.map +1 -0
- package/lib/engine/engine_gameobject.d.ts +1 -10
- package/lib/engine/engine_gameobject.js +22 -120
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +7 -69
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_init.js +7 -7
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_input.d.ts +24 -5
- package/lib/engine/engine_input.js +3 -2
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_instantiate_resolve.d.ts +42 -0
- package/lib/engine/engine_instantiate_resolve.js +372 -0
- package/lib/engine/engine_instantiate_resolve.js.map +1 -0
- package/lib/engine/engine_license.d.ts +7 -7
- package/lib/engine/engine_license.js +186 -58
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_mainloop_utils.js +7 -4
- package/lib/engine/engine_mainloop_utils.js.map +1 -1
- package/lib/engine/engine_networking.d.ts +51 -37
- package/lib/engine/engine_networking.js +132 -82
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_networking.transport.websocket.d.ts +15 -0
- package/lib/engine/engine_networking.transport.websocket.js +38 -0
- package/lib/engine/engine_networking.transport.websocket.js.map +1 -0
- package/lib/engine/engine_networking_blob.js +4 -4
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_networking_instantiate.js +2 -2
- package/lib/engine/engine_networking_instantiate.js.map +1 -1
- package/lib/engine/engine_networking_types.d.ts +39 -1
- package/lib/engine/engine_networking_types.js +7 -0
- package/lib/engine/engine_networking_types.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +21 -3
- package/lib/engine/engine_physics_rapier.js +94 -25
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_pmrem.js +51 -3
- package/lib/engine/engine_pmrem.js.map +1 -1
- package/lib/engine/engine_scenedata.js +2 -2
- package/lib/engine/engine_scenedata.js.map +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js +28 -5
- package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
- package/lib/engine/engine_serialization_core.d.ts +1 -0
- package/lib/engine/engine_serialization_core.js +7 -0
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_types.d.ts +29 -11
- package/lib/engine/engine_types.js +1 -1
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_util_decorator.js +7 -2
- package/lib/engine/engine_util_decorator.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +1 -1
- package/lib/engine/engine_utils.js +19 -5
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/engine_utils_format.js +20 -14
- package/lib/engine/engine_utils_format.js.map +1 -1
- package/lib/engine/engine_utils_qrcode.js +2 -2
- package/lib/engine/engine_utils_qrcode.js.map +1 -1
- package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
- package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js +6 -6
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +10 -4
- package/lib/engine/webcomponents/needle-engine.js +3 -3
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.d.ts +3 -2
- package/lib/engine/xr/NeedleXRSession.js +50 -14
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine/xr/TempXRContext.js +2 -2
- package/lib/engine/xr/TempXRContext.js.map +1 -1
- package/lib/engine/xr/events.d.ts +1 -1
- package/lib/engine/xr/events.js.map +1 -1
- package/lib/engine-components/Animation.js +17 -16
- package/lib/engine-components/Animation.js.map +1 -1
- package/lib/engine-components/AnimationBuilder.d.ts +158 -0
- package/lib/engine-components/AnimationBuilder.js +305 -0
- package/lib/engine-components/AnimationBuilder.js.map +1 -0
- package/lib/engine-components/Animator.d.ts +6 -0
- package/lib/engine-components/Animator.js +23 -13
- package/lib/engine-components/Animator.js.map +1 -1
- package/lib/engine-components/AnimatorController.builder.d.ts +191 -0
- package/lib/engine-components/AnimatorController.builder.js +263 -0
- package/lib/engine-components/AnimatorController.builder.js.map +1 -0
- package/lib/engine-components/AnimatorController.d.ts +2 -119
- package/lib/engine-components/AnimatorController.js +33 -232
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/AudioSource.d.ts +19 -3
- package/lib/engine-components/AudioSource.js +121 -68
- package/lib/engine-components/AudioSource.js.map +1 -1
- package/lib/engine-components/Camera.d.ts +6 -1
- package/lib/engine-components/Camera.js +16 -3
- package/lib/engine-components/Camera.js.map +1 -1
- package/lib/engine-components/CameraUtils.js +14 -6
- package/lib/engine-components/CameraUtils.js.map +1 -1
- package/lib/engine-components/Collider.d.ts +18 -9
- package/lib/engine-components/Collider.js +61 -14
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Component.d.ts +72 -9
- package/lib/engine-components/Component.js +114 -10
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/ContactShadows.d.ts +1 -0
- package/lib/engine-components/ContactShadows.js +14 -1
- package/lib/engine-components/ContactShadows.js.map +1 -1
- package/lib/engine-components/DragControls.d.ts +7 -0
- package/lib/engine-components/DragControls.js +19 -7
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/DropListener.js +4 -0
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/EventList.d.ts +31 -9
- package/lib/engine-components/EventList.js +37 -76
- package/lib/engine-components/EventList.js.map +1 -1
- package/lib/engine-components/Joints.d.ts +4 -2
- package/lib/engine-components/Joints.js +19 -3
- package/lib/engine-components/Joints.js.map +1 -1
- package/lib/engine-components/Light.js +9 -1
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/Networking.d.ts +1 -1
- package/lib/engine-components/Networking.js +1 -1
- package/lib/engine-components/OrbitControls.d.ts +1 -2
- package/lib/engine-components/OrbitControls.js +37 -14
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/RigidBody.d.ts +12 -4
- package/lib/engine-components/RigidBody.js +18 -4
- package/lib/engine-components/RigidBody.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.js +3 -0
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/SeeThrough.js +2 -2
- package/lib/engine-components/SeeThrough.js.map +1 -1
- package/lib/engine-components/VideoPlayer.d.ts +8 -2
- package/lib/engine-components/VideoPlayer.js +42 -19
- package/lib/engine-components/VideoPlayer.js.map +1 -1
- package/lib/engine-components/Voip.d.ts +16 -7
- package/lib/engine-components/Voip.js +90 -53
- package/lib/engine-components/Voip.js.map +1 -1
- package/lib/engine-components/api.d.ts +3 -1
- package/lib/engine-components/api.js +3 -1
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +7 -13
- package/lib/engine-components/codegen/components.js +7 -13
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +21 -11
- package/lib/engine-components/timeline/PlayableDirector.js +75 -67
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/SignalAsset.d.ts +3 -1
- package/lib/engine-components/timeline/SignalAsset.js +1 -0
- package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
- package/lib/engine-components/timeline/TimelineBuilder.d.ts +413 -0
- package/lib/engine-components/timeline/TimelineBuilder.js +506 -0
- package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -0
- package/lib/engine-components/timeline/TimelineModels.d.ts +2 -1
- package/lib/engine-components/timeline/TimelineModels.js +3 -0
- package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +37 -6
- package/lib/engine-components/timeline/TimelineTracks.js +92 -26
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/timeline/index.d.ts +2 -1
- package/lib/engine-components/timeline/index.js +2 -0
- package/lib/engine-components/timeline/index.js.map +1 -1
- package/lib/engine-components/ui/Canvas.d.ts +1 -1
- package/lib/engine-components/ui/Canvas.js +2 -8
- package/lib/engine-components/ui/Canvas.js.map +1 -1
- package/lib/engine-components/ui/Text.d.ts +1 -0
- package/lib/engine-components/ui/Text.js +10 -7
- package/lib/engine-components/ui/Text.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.d.ts +0 -1
- package/lib/engine-components/web/CursorFollow.js +21 -13
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.d.ts +62 -1
- package/lib/engine-components/webxr/WebXRImageTracking.js +59 -2
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/package.json +2 -83
- package/plugins/common/cloud.js +6 -1
- package/plugins/common/license.js +55 -12
- package/plugins/common/worker.js +9 -4
- package/plugins/types/userconfig.d.ts +4 -1
- package/plugins/vite/asap.js +17 -8
- package/plugins/vite/build-pipeline.js +57 -20
- package/plugins/vite/dependencies.js +29 -10
- package/plugins/vite/dependency-watcher.js +2 -2
- package/plugins/vite/editor-connection.js +3 -3
- package/plugins/vite/license.js +42 -7
- package/plugins/vite/local-files-core.js +3 -3
- package/plugins/vite/local-files-utils.d.ts +3 -1
- package/plugins/vite/local-files-utils.js +29 -5
- package/plugins/vite/reload.js +1 -1
- package/plugins/vite/server.js +2 -1
- package/src/engine/api.ts +11 -1
- package/src/engine/codegen/register_types.ts +10 -18
- package/src/engine/engine_audio.ts +184 -0
- package/src/engine/engine_camera.fit.ts +15 -4
- package/src/engine/engine_components.ts +1 -1
- package/src/engine/engine_context.ts +52 -19
- package/src/engine/engine_context_eventbus.ts +73 -0
- package/src/engine/engine_disposable.ts +214 -0
- package/src/engine/engine_gameobject.ts +54 -159
- package/src/engine/engine_gltf_builtin_components.ts +7 -76
- package/src/engine/engine_init.ts +7 -7
- package/src/engine/engine_input.ts +28 -7
- package/src/engine/engine_instantiate_resolve.ts +407 -0
- package/src/engine/engine_license.ts +202 -56
- package/src/engine/engine_mainloop_utils.ts +7 -4
- package/src/engine/engine_networking.transport.websocket.ts +45 -0
- package/src/engine/engine_networking.ts +161 -137
- package/src/engine/engine_networking_blob.ts +4 -4
- package/src/engine/engine_networking_instantiate.ts +2 -2
- package/src/engine/engine_networking_types.ts +41 -1
- package/src/engine/engine_physics_rapier.ts +102 -33
- package/src/engine/engine_pmrem.ts +53 -3
- package/src/engine/engine_scenedata.ts +3 -3
- package/src/engine/engine_serialization_builtin_serializer.ts +32 -9
- package/src/engine/engine_serialization_core.ts +9 -0
- package/src/engine/engine_types.ts +46 -27
- package/src/engine/engine_util_decorator.ts +7 -2
- package/src/engine/engine_utils.ts +16 -5
- package/src/engine/engine_utils_format.ts +20 -14
- package/src/engine/engine_utils_qrcode.ts +2 -2
- package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +6 -6
- package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
- package/src/engine/webcomponents/needle-engine.ts +12 -6
- package/src/engine/xr/NeedleXRSession.ts +48 -13
- package/src/engine/xr/TempXRContext.ts +2 -2
- package/src/engine/xr/events.ts +1 -1
- package/src/engine-components/Animation.ts +19 -16
- package/src/engine-components/AnimationBuilder.ts +472 -0
- package/src/engine-components/Animator.ts +24 -12
- package/src/engine-components/AnimatorController.builder.ts +387 -0
- package/src/engine-components/AnimatorController.ts +20 -291
- package/src/engine-components/AudioSource.ts +130 -79
- package/src/engine-components/Camera.ts +16 -3
- package/src/engine-components/CameraUtils.ts +12 -5
- package/src/engine-components/Collider.ts +66 -18
- package/src/engine-components/Component.ts +118 -20
- package/src/engine-components/ContactShadows.ts +15 -1
- package/src/engine-components/DragControls.ts +18 -11
- package/src/engine-components/DropListener.ts +4 -0
- package/src/engine-components/EventList.ts +45 -83
- package/src/engine-components/Joints.ts +20 -4
- package/src/engine-components/Light.ts +10 -2
- package/src/engine-components/Networking.ts +1 -1
- package/src/engine-components/OrbitControls.ts +42 -16
- package/src/engine-components/RigidBody.ts +18 -4
- package/src/engine-components/SceneSwitcher.ts +3 -0
- package/src/engine-components/SeeThrough.ts +2 -2
- package/src/engine-components/VideoPlayer.ts +40 -17
- package/src/engine-components/Voip.ts +88 -53
- package/src/engine-components/api.ts +3 -1
- package/src/engine-components/codegen/components.ts +7 -13
- package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
- package/src/engine-components/timeline/PlayableDirector.ts +83 -81
- package/src/engine-components/timeline/SignalAsset.ts +4 -1
- package/src/engine-components/timeline/TimelineBuilder.ts +824 -0
- package/src/engine-components/timeline/TimelineModels.ts +5 -1
- package/src/engine-components/timeline/TimelineTracks.ts +96 -27
- package/src/engine-components/timeline/index.ts +2 -1
- package/src/engine-components/ui/Canvas.ts +2 -8
- package/src/engine-components/ui/Text.ts +12 -8
- package/src/engine-components/web/CursorFollow.ts +21 -14
- package/src/engine-components/webxr/WebXRImageTracking.ts +79 -7
- package/dist/needle-engine.bundle-1s2gOoKZ.min.js +0 -1732
- package/dist/needle-engine.bundle-j4nGJXCs.umd.cjs +0 -1732
- package/lib/engine-components/AvatarLoader.d.ts +0 -80
- package/lib/engine-components/AvatarLoader.js +0 -232
- package/lib/engine-components/AvatarLoader.js.map +0 -1
- package/lib/engine-components/avatar/AvatarBlink_Simple.d.ts +0 -11
- package/lib/engine-components/avatar/AvatarBlink_Simple.js +0 -77
- package/lib/engine-components/avatar/AvatarBlink_Simple.js.map +0 -1
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.d.ts +0 -14
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js +0 -69
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.d.ts +0 -29
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js +0 -122
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MouthShapes.d.ts +0 -15
- package/lib/engine-components/avatar/Avatar_MouthShapes.js +0 -80
- package/lib/engine-components/avatar/Avatar_MouthShapes.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MustacheShake.d.ts +0 -9
- package/lib/engine-components/avatar/Avatar_MustacheShake.js +0 -30
- package/lib/engine-components/avatar/Avatar_MustacheShake.js.map +0 -1
- package/src/engine-components/AvatarLoader.ts +0 -264
- package/src/engine-components/avatar/AvatarBlink_Simple.ts +0 -70
- package/src/engine-components/avatar/AvatarEyeLook_Rotation.ts +0 -64
- package/src/engine-components/avatar/Avatar_Brain_LookAt.ts +0 -140
- package/src/engine-components/avatar/Avatar_MouthShapes.ts +0 -84
- package/src/engine-components/avatar/Avatar_MustacheShake.ts +0 -32
- package/src/vite-env.d.ts +0 -16
|
@@ -582,7 +582,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
582
582
|
};
|
|
583
583
|
|
|
584
584
|
private _onPointerUpLate = (evt: NEPointerEvent) => {
|
|
585
|
-
if (this.doubleClickToFocus && evt.isDoubleClick && !evt.used) {
|
|
585
|
+
if (this.doubleClickToFocus && evt.isDoubleClick && !evt.used && this.canFocusAtPointer()) {
|
|
586
586
|
this.setTargetFromRaycast();
|
|
587
587
|
}
|
|
588
588
|
// Automatically update the camera focus
|
|
@@ -663,7 +663,15 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
663
663
|
}
|
|
664
664
|
this._controls.enabled = true;
|
|
665
665
|
|
|
666
|
-
|
|
666
|
+
// Interrupt programmatic transitions on meaningful new user interaction:
|
|
667
|
+
// - Middle/right button down (always intentional camera control)
|
|
668
|
+
// - Mouse wheel (zoom intent)
|
|
669
|
+
// - Left button drag start: getPointerDown(0) with a position delta — a bare click
|
|
670
|
+
// without movement shouldn't cancel an animation, but starting to drag should.
|
|
671
|
+
// Using getPointerDown (not getPointerPressed) ensures we only interrupt once at
|
|
672
|
+
// drag onset, not continuously every frame during a drag.
|
|
673
|
+
const leftDragStart = this.context.input.getPointerDown(0) && (this.context.input.getPointerPositionDelta(0)?.length() || 0) > .1;
|
|
674
|
+
if (this.context.input.getPointerDown(1) || this.context.input.getPointerDown(2) || this.context.input.mouseWheelChanged || leftDragStart) {
|
|
667
675
|
this._inputs += 1;
|
|
668
676
|
}
|
|
669
677
|
if (this._inputs > 0 && this.allowInterrupt) {
|
|
@@ -708,7 +716,7 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
708
716
|
}
|
|
709
717
|
}
|
|
710
718
|
|
|
711
|
-
const focusAtPointer = (this.middleClickToFocus && this.context.input.getPointerClicked(1));
|
|
719
|
+
const focusAtPointer = (this.middleClickToFocus && this.context.input.getPointerClicked(1) && this.canFocusAtPointer());
|
|
712
720
|
if (focusAtPointer) {
|
|
713
721
|
this.setTargetFromRaycast();
|
|
714
722
|
}
|
|
@@ -758,19 +766,26 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
758
766
|
|
|
759
767
|
if (this.targetBounds) {
|
|
760
768
|
// #region target bounds
|
|
761
|
-
const targetVector = this._controls.target;
|
|
762
769
|
const boundsCenter = this.targetBounds.worldPosition;
|
|
763
770
|
const boundsHalfSize = getTempVector(this.targetBounds.worldScale).multiplyScalar(0.5);
|
|
764
771
|
const min = getTempVector(boundsCenter).sub(boundsHalfSize);
|
|
765
772
|
const max = getTempVector(boundsCenter).add(boundsHalfSize);
|
|
766
|
-
|
|
767
|
-
const duration = .1;
|
|
768
|
-
if (duration <= 0) targetVector.copy(newTarget);
|
|
769
|
-
else targetVector.lerp(newTarget, this.context.time.deltaTime / duration);
|
|
773
|
+
|
|
770
774
|
if (this._lookTargetLerpActive) {
|
|
771
|
-
|
|
772
|
-
|
|
775
|
+
// During a programmatic transition (fitCamera / setLookTargetPosition with immediate: false),
|
|
776
|
+
// only clamp the destination. The look-target lerp (above) handles moving _controls.target
|
|
777
|
+
// towards the endpoint — we must not fight it by also lerping _controls.target here.
|
|
778
|
+
this._lookTargetEndPosition.clamp(min, max);
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
// Interactive use (pan/orbit): smoothly push the target back into bounds
|
|
782
|
+
const targetVector = this._controls.target;
|
|
783
|
+
const newTarget = getTempVector(targetVector).clamp(min, max);
|
|
784
|
+
const duration = .1;
|
|
785
|
+
if (duration <= 0) targetVector.copy(newTarget);
|
|
786
|
+
else targetVector.lerp(newTarget, Math.min(1, this.context.time.deltaTime / duration));
|
|
773
787
|
}
|
|
788
|
+
|
|
774
789
|
if (debug) {
|
|
775
790
|
Gizmos.DrawWireBox(boundsCenter, boundsHalfSize.multiplyScalar(2), 0xffaa00);
|
|
776
791
|
}
|
|
@@ -847,7 +862,8 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
847
862
|
this._controls.update(this.context.time.deltaTime);
|
|
848
863
|
|
|
849
864
|
if (debug) {
|
|
850
|
-
|
|
865
|
+
const distance = this._controls.getDistance();
|
|
866
|
+
Gizmos.DrawWireSphere(this._controls.target, 0.01 * distance, 0x00ff00);
|
|
851
867
|
}
|
|
852
868
|
}
|
|
853
869
|
}
|
|
@@ -1022,7 +1038,8 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1022
1038
|
|
|
1023
1039
|
if (debug) {
|
|
1024
1040
|
console.warn("OrbitControls: setLookTargetPosition", position, immediateOrDuration);
|
|
1025
|
-
|
|
1041
|
+
const distance = this._controls.getDistance();
|
|
1042
|
+
Gizmos.DrawWireSphere(this._lookTargetEndPosition, 0.01 * distance, 0xff5500, 2);
|
|
1026
1043
|
}
|
|
1027
1044
|
|
|
1028
1045
|
if (immediateOrDuration === true) {
|
|
@@ -1063,6 +1080,12 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1063
1080
|
if (this.lookAtTarget && this.lockLookAtTarget) this.lookAtTarget.worldPosition = this._controls.target;
|
|
1064
1081
|
}
|
|
1065
1082
|
|
|
1083
|
+
private canFocusAtPointer(): boolean {
|
|
1084
|
+
// A locked lookAtTarget is the authoritative orbit center. Pointer focus would
|
|
1085
|
+
// otherwise move the assigned scene object via lerpLookTarget's back-sync.
|
|
1086
|
+
return !(this.lookAtTarget && this.lockLookAtTarget);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1066
1089
|
private setTargetFromRaycast(ray?: Ray, immediateOrDuration: number | boolean = false): boolean {
|
|
1067
1090
|
if (!this.controls) return false;
|
|
1068
1091
|
const rc = ray ? this.context.physics.raycastFromRay(ray) : this.context.physics.raycast();
|
|
@@ -1086,13 +1109,16 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
1086
1109
|
// Adapted from https://discourse.threejs.org/t/camera-zoom-to-fit-object/936/24
|
|
1087
1110
|
// Slower but better implementation that takes bones and exact vertex positions into account: https://github.com/google/model-viewer/blob/04e900c5027de8c5306fe1fe9627707f42811b05/packages/model-viewer/src/three-components/ModelScene.ts#L321
|
|
1088
1111
|
|
|
1089
|
-
/**
|
|
1090
|
-
* Fits the camera to show the objects provided (defaults to the scene if no objects are passed in)
|
|
1112
|
+
/**
|
|
1113
|
+
* Fits the camera to show the objects provided (defaults to the scene if no objects are passed in)
|
|
1091
1114
|
* @param options The options for fitting the camera. Use to provide objects to fit to, fit direction and size and other settings.
|
|
1092
1115
|
*/
|
|
1093
1116
|
fitCamera(options?: OrbitFitCameraOptions);
|
|
1094
|
-
|
|
1095
|
-
|
|
1117
|
+
// Deprecated overload commented out: it accepted Object3D as first arg, which caused
|
|
1118
|
+
// TypeScript autocomplete to show Object3D properties (position, worldPosition, etc.)
|
|
1119
|
+
// instead of OrbitFitCameraOptions. The implementation still handles Object3D at runtime
|
|
1120
|
+
// for backwards-compat — use fitCamera({ objects: [...] }) instead.
|
|
1121
|
+
// fitCamera(objects?: Object3D | Array<Object3D>, options?: Omit<OrbitFitCameraOptions, "objects">);
|
|
1096
1122
|
fitCamera(objectsOrOptions?: Object3D | Array<Object3D> | OrbitFitCameraOptions, options?: OrbitFitCameraOptions): void {
|
|
1097
1123
|
|
|
1098
1124
|
|
|
@@ -204,12 +204,19 @@ export class Rigidbody extends Behaviour implements IRigidbody {
|
|
|
204
204
|
|
|
205
205
|
get isRigidbody() { return true; }
|
|
206
206
|
|
|
207
|
-
/** When true the mass
|
|
207
|
+
/** When true the mass is automatically computed from the attached colliders using `mass = density × volume`.
|
|
208
|
+
* Each collider's {@link Collider.density} determines how heavy it contributes to the total mass.
|
|
209
|
+
* Disable to set mass explicitly via the `mass` property.
|
|
210
|
+
*/
|
|
208
211
|
@validate()
|
|
209
212
|
autoMass: boolean = true;
|
|
210
213
|
|
|
211
|
-
/**
|
|
212
|
-
*
|
|
214
|
+
/** The mass of the rigidbody in kg (when `autoMass` is disabled).
|
|
215
|
+
* When `autoMass` is enabled, reading this returns the computed mass from `density × volume` of all attached colliders.
|
|
216
|
+
* Setting this property automatically disables `autoMass`.
|
|
217
|
+
*
|
|
218
|
+
* **Prefer using {@link Collider.density}** with `autoMass` enabled instead — density scales
|
|
219
|
+
* naturally with collider size, while explicit mass stays fixed regardless of shape changes.
|
|
213
220
|
*/
|
|
214
221
|
@serializable()
|
|
215
222
|
set mass(value: number) {
|
|
@@ -403,9 +410,16 @@ export class Rigidbody extends Behaviour implements IRigidbody {
|
|
|
403
410
|
this.context.physics.engine?.removeBody(this);
|
|
404
411
|
}
|
|
405
412
|
|
|
406
|
-
onValidate() {
|
|
413
|
+
onValidate(property?: string) {
|
|
407
414
|
this._propertiesChanged = true;
|
|
415
|
+
if (property === "autoMass" && !this.autoMass) {
|
|
416
|
+
if (isDevEnvironment() && !Rigidbody._didWarnAutoMass) {
|
|
417
|
+
Rigidbody._didWarnAutoMass = true;
|
|
418
|
+
console.warn("[Rigidbody] autoMass disabled — consider using Collider.density instead of setting mass explicitly. Density scales naturally with collider size.");
|
|
419
|
+
}
|
|
420
|
+
}
|
|
408
421
|
}
|
|
422
|
+
private static _didWarnAutoMass = false;
|
|
409
423
|
|
|
410
424
|
// need to do this right before updating physics to prevent rendered object glitching through physical bodies
|
|
411
425
|
* beforePhysics() {
|
|
@@ -747,6 +747,9 @@ export class SceneSwitcher extends Behaviour {
|
|
|
747
747
|
const openedEvt = new CustomEvent<LoadSceneEvent>("scene-opened", { detail: { scene: scene, switcher: this, index: index } });
|
|
748
748
|
this.dispatchEvent(openedEvt);
|
|
749
749
|
this.sceneLoaded?.invoke(this);
|
|
750
|
+
if (this._currentSceneAsset) {
|
|
751
|
+
this.context.events.emit("scene-content-changed", { source: this, object: this._currentSceneAsset });
|
|
752
|
+
}
|
|
750
753
|
return true;
|
|
751
754
|
}
|
|
752
755
|
}
|
|
@@ -249,10 +249,10 @@ export class SeeThrough extends Behaviour {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
const materials = renderer.sharedMaterials;// : this.rendererMaterials.get(renderer);
|
|
252
|
-
if (!materials) return;
|
|
252
|
+
if (!materials?.length) return;
|
|
253
253
|
|
|
254
254
|
const block = MaterialPropertyBlock.get(renderer.gameObject);
|
|
255
|
-
const currentOpacity = (block.getOverride("opacity")?.value ?? materials[0]
|
|
255
|
+
const currentOpacity = (block.getOverride("opacity")?.value ?? materials[0]?.opacity ?? 1);
|
|
256
256
|
|
|
257
257
|
let newAlpha = Mathf.lerp(currentOpacity, targetAlpha, duration <= 0 ? 1 : this.context.time.deltaTime / duration);;
|
|
258
258
|
if (newAlpha >= 0.99) newAlpha = 1;
|
|
@@ -207,16 +207,13 @@ export class VideoPlayer extends Behaviour {
|
|
|
207
207
|
*/
|
|
208
208
|
get isPlaying(): boolean {
|
|
209
209
|
const video = this._videoElement;
|
|
210
|
-
if (video)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const stream = video.srcObject as MediaStream;
|
|
216
|
-
if (stream.active) return true;
|
|
217
|
-
}
|
|
210
|
+
if (!video) return false;
|
|
211
|
+
if (video.paused || video.ended) return false;
|
|
212
|
+
if (video.srcObject) {
|
|
213
|
+
const stream = video.srcObject as MediaStream;
|
|
214
|
+
return stream.active;
|
|
218
215
|
}
|
|
219
|
-
return
|
|
216
|
+
return video.currentTime > 0 && video.readyState > video.HAVE_CURRENT_DATA;
|
|
220
217
|
}
|
|
221
218
|
|
|
222
219
|
get crossOrigin(): string | null {
|
|
@@ -430,26 +427,52 @@ export class VideoPlayer extends Behaviour {
|
|
|
430
427
|
|
|
431
428
|
private _playErrors: number = 0;
|
|
432
429
|
|
|
433
|
-
/**
|
|
434
|
-
|
|
430
|
+
/**
|
|
431
|
+
* Plays the assigned video clip, URL, or MediaStream.
|
|
432
|
+
* If a `clip` argument is passed, it is used as the new video source (mirroring {@link AudioSource.play}).
|
|
433
|
+
*
|
|
434
|
+
* @param clip - Optional video URL string or {@link MediaStream} to play. If omitted, plays the currently assigned source.
|
|
435
|
+
* @returns A promise that resolves to `true` when playback was successfully started, or `false` on error.
|
|
436
|
+
*/
|
|
437
|
+
async play(clip?: string | MediaStream): Promise<boolean> {
|
|
438
|
+
// Defensive: if called from an event handler with a non-string/non-MediaStream argument
|
|
439
|
+
// (e.g. SpatialTrigger.onEnter passes a receiver object), ignore the arg and fall back to the assigned source.
|
|
440
|
+
// Same pattern as AudioSource.play.
|
|
441
|
+
if (clip !== undefined && typeof clip !== "string" && !(clip instanceof MediaStream)) {
|
|
442
|
+
if (isDevEnvironment()) console.warn("[VideoPlayer] Called play with unknown argument type. Using assigned source instead.", clip);
|
|
443
|
+
clip = undefined;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (typeof clip === "string") {
|
|
447
|
+
this.setClipURL(clip);
|
|
448
|
+
}
|
|
449
|
+
else if (clip instanceof MediaStream) {
|
|
450
|
+
this.setVideo(clip);
|
|
451
|
+
}
|
|
452
|
+
|
|
435
453
|
if (!this._videoElement) this.create(false);
|
|
436
454
|
if (!this._videoElement) {
|
|
437
455
|
if (debug) console.warn("Can not play: no video element found", this);
|
|
438
|
-
return
|
|
456
|
+
return false;
|
|
439
457
|
}
|
|
440
|
-
if (this._isPlaying && !this._videoElement?.ended && !this._videoElement?.paused) return;
|
|
458
|
+
if (this._isPlaying && !this._videoElement?.ended && !this._videoElement?.paused) return true;
|
|
441
459
|
this._isPlaying = true;
|
|
442
460
|
if (!this._receivedInput) this._videoElement.muted = true;
|
|
443
461
|
this.handleBeginPlaying(false);
|
|
444
462
|
|
|
445
463
|
if (this.shouldUseM3U) {
|
|
446
464
|
this.ensureM3UCanBePlayed();
|
|
447
|
-
return;
|
|
465
|
+
return true;
|
|
448
466
|
}
|
|
449
467
|
|
|
450
468
|
if (debug) console.log("Video Play()", this.clip, this._videoElement, this.time);
|
|
451
469
|
this._videoElement.currentTime = this.time;
|
|
452
|
-
|
|
470
|
+
try {
|
|
471
|
+
await this._videoElement.play();
|
|
472
|
+
if (debug) console.log("play", this._videoElement, this.time);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
catch (err: any) {
|
|
453
476
|
if (this._playErrors++ < 10) console.error(err);
|
|
454
477
|
else if (this._playErrors === 10) console.error("Multiple errors playing video, further errors will be suppressed. Use 'debugvideo' param to see all errors.");
|
|
455
478
|
// https://developer.chrome.com/blog/play-request-was-interrupted/
|
|
@@ -459,8 +482,8 @@ export class VideoPlayer extends Behaviour {
|
|
|
459
482
|
if (this._isPlaying && !this.destroyed && this.activeAndEnabled)
|
|
460
483
|
this.play();
|
|
461
484
|
}, 1000);
|
|
462
|
-
|
|
463
|
-
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
464
487
|
}
|
|
465
488
|
|
|
466
489
|
/**
|
|
@@ -36,9 +36,9 @@ const debugParam = getParam("debugvoip");
|
|
|
36
36
|
* voip.createMenuButton = true;
|
|
37
37
|
*
|
|
38
38
|
* // Manual control
|
|
39
|
-
* voip.connect(); // Start sending
|
|
40
|
-
* voip.disconnect(); // Stop sending
|
|
41
|
-
* voip.setMuted(true); // Mute
|
|
39
|
+
* voip.connect(); // Start sending your microphone
|
|
40
|
+
* voip.disconnect(); // Stop sending your microphone
|
|
41
|
+
* voip.setMuted(true); // Mute incoming audio (silence other users)
|
|
42
42
|
* ```
|
|
43
43
|
*
|
|
44
44
|
* @summary Voice over IP for networked audio communication
|
|
@@ -84,9 +84,13 @@ export class Voip extends Behaviour {
|
|
|
84
84
|
@serializable()
|
|
85
85
|
get volume(): number { return this._volume; }
|
|
86
86
|
set volume(val: number) {
|
|
87
|
-
|
|
87
|
+
// HTMLMediaElement.volume throws IndexSizeError outside [0,1] — clamp before assigning.
|
|
88
|
+
// Reject NaN so we don't poison _volume and break serialization.
|
|
89
|
+
if (Number.isNaN(val)) return;
|
|
90
|
+
const clamped = Math.max(0, Math.min(1, val));
|
|
91
|
+
this._volume = clamped;
|
|
88
92
|
for (const audio of this._incomingStreams.values()) {
|
|
89
|
-
audio.volume =
|
|
93
|
+
audio.volume = clamped;
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
|
|
@@ -119,11 +123,11 @@ export class Voip extends Behaviour {
|
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
/**
|
|
122
|
-
*
|
|
123
|
-
* they are considered "speaking". Default is
|
|
126
|
+
* Normalized amplitude threshold for speaking detection (0–1). When a user's average
|
|
127
|
+
* audio amplitude exceeds this, they are considered "speaking". Default is 0.1.
|
|
124
128
|
*/
|
|
125
129
|
@serializable()
|
|
126
|
-
speakingThreshold: number =
|
|
130
|
+
speakingThreshold: number = 0.1;
|
|
127
131
|
|
|
128
132
|
/**
|
|
129
133
|
* Event fired when a user's speaking state changes.
|
|
@@ -133,7 +137,11 @@ export class Voip extends Behaviour {
|
|
|
133
137
|
onSpeakingChanged: EventList = new EventList();
|
|
134
138
|
|
|
135
139
|
private _speakingStates = new Map<string, boolean>();
|
|
136
|
-
private _analysers = new Map<string, { analyser: AnalyserNode, data: Uint8Array
|
|
140
|
+
private _analysers = new Map<string, { source: MediaStreamAudioSourceNode, analyser: AnalyserNode, data: Uint8Array }>();
|
|
141
|
+
// Single shared AudioContext for all remote-user analysers. Browsers cap live AudioContexts at ~6 per tab,
|
|
142
|
+
// so one-per-user would break voice rooms of 7+ participants. Lazily created on first setupAnalyser.
|
|
143
|
+
private _sharedAudioContext?: AudioContext;
|
|
144
|
+
private _lastSpeakingPollMs = 0;
|
|
137
145
|
|
|
138
146
|
private _net?: NetworkedStreams;
|
|
139
147
|
private _menubutton?: HTMLElement;
|
|
@@ -142,12 +150,12 @@ export class Voip extends Behaviour {
|
|
|
142
150
|
awake() {
|
|
143
151
|
if (debugParam) this.debug = true;
|
|
144
152
|
if (this.debug) {
|
|
145
|
-
console.log("VOIP debugging: press 'v' to toggle mute
|
|
153
|
+
console.log("VOIP debugging: press 'v' to toggle incoming mute, 'c' to toggle connect/disconnect");
|
|
146
154
|
window.addEventListener("keydown", async (evt) => {
|
|
147
155
|
const key = evt.key.toLowerCase();
|
|
148
156
|
switch (key) {
|
|
149
157
|
case "v":
|
|
150
|
-
console.log("
|
|
158
|
+
console.log("VOIP: toggle incoming mute → ", !this.isMuted);
|
|
151
159
|
this.setMuted(!this.isMuted);
|
|
152
160
|
break;
|
|
153
161
|
case "c":
|
|
@@ -156,15 +164,6 @@ export class Voip extends Behaviour {
|
|
|
156
164
|
break;
|
|
157
165
|
}
|
|
158
166
|
});
|
|
159
|
-
// mute unfocused
|
|
160
|
-
window.addEventListener("blur", () => {
|
|
161
|
-
console.log("VOIP: MUTE ON BLUR")
|
|
162
|
-
this.setMuted(true);
|
|
163
|
-
});
|
|
164
|
-
window.addEventListener("focus", () => {
|
|
165
|
-
console.log("VOIP: UNMUTE ON FOCUS")
|
|
166
|
-
this.setMuted(false);
|
|
167
|
-
});
|
|
168
167
|
}
|
|
169
168
|
}
|
|
170
169
|
|
|
@@ -201,10 +200,11 @@ export class Voip extends Behaviour {
|
|
|
201
200
|
this.onEnabledChanged();
|
|
202
201
|
this.updateButton();
|
|
203
202
|
window.removeEventListener("visibilitychange", this.onVisibilityChanged);
|
|
204
|
-
// Clean up analysers
|
|
203
|
+
// Clean up analysers and the shared AudioContext (lazily recreated on next setupAnalyser).
|
|
205
204
|
for (const userId of [...this._analysers.keys()]) {
|
|
206
205
|
this.cleanupAnalyser(userId);
|
|
207
206
|
}
|
|
207
|
+
this.closeSharedAudioContext();
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/** @internal */
|
|
@@ -215,6 +215,7 @@ export class Voip extends Behaviour {
|
|
|
215
215
|
for (const userId of [...this._analysers.keys()]) {
|
|
216
216
|
this.cleanupAnalyser(userId);
|
|
217
217
|
}
|
|
218
|
+
this.closeSharedAudioContext();
|
|
218
219
|
for (const incoming of this._incomingStreams.values()) {
|
|
219
220
|
disposeStream(incoming.srcObject as MediaStream);
|
|
220
221
|
}
|
|
@@ -225,6 +226,10 @@ export class Voip extends Behaviour {
|
|
|
225
226
|
/** Set via the mic button (e.g. when the websocket connection closes and rejoins but the user was muted before we don't want to enable VOIP again automatically) */
|
|
226
227
|
private _allowSending = true;
|
|
227
228
|
private _outputStream: MediaStream | null = null;
|
|
229
|
+
// Tracks an in-flight connect() so concurrent callers coalesce onto the same promise
|
|
230
|
+
// instead of each running getUserMedia in parallel (which would leak a MediaStream and
|
|
231
|
+
// briefly transmit then kill the first acquired stream).
|
|
232
|
+
private _connectInFlight?: Promise<boolean>;
|
|
228
233
|
|
|
229
234
|
/**
|
|
230
235
|
* @returns true if the component is currently sending audio
|
|
@@ -233,7 +238,18 @@ export class Voip extends Behaviour {
|
|
|
233
238
|
|
|
234
239
|
|
|
235
240
|
/** Start sending audio. */
|
|
236
|
-
|
|
241
|
+
connect(audioSource?: MediaTrackConstraints): Promise<boolean> {
|
|
242
|
+
// Coalesce concurrent callers. Without this, two near-simultaneous connect() calls
|
|
243
|
+
// each call getUserMedia in parallel, the first stream gets disposed mid-broadcast
|
|
244
|
+
// by the second, and a MediaStream leaks.
|
|
245
|
+
if (this._connectInFlight) return this._connectInFlight;
|
|
246
|
+
this._connectInFlight = this._connectImpl(audioSource).finally(() => {
|
|
247
|
+
this._connectInFlight = undefined;
|
|
248
|
+
});
|
|
249
|
+
return this._connectInFlight;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private async _connectImpl(audioSource?: MediaTrackConstraints): Promise<boolean> {
|
|
237
253
|
if (!this._net) {
|
|
238
254
|
console.error("Cannot connect to voice chat - NetworkedStreams not initialized. Make sure the component is enabled before calling this method.");
|
|
239
255
|
return false;
|
|
@@ -281,27 +297,25 @@ export class Voip extends Behaviour {
|
|
|
281
297
|
}
|
|
282
298
|
|
|
283
299
|
/**
|
|
284
|
-
* Mute or unmute the audio
|
|
300
|
+
* Mute or unmute the audio you hear from other users (incoming streams).
|
|
301
|
+
* This does NOT mute your own microphone — use {@link disconnect} to stop sending your microphone.
|
|
285
302
|
*/
|
|
286
303
|
setMuted(mute: boolean) {
|
|
287
|
-
const audio
|
|
288
|
-
|
|
289
|
-
for (const track of audio) {
|
|
290
|
-
track.enabled = !mute
|
|
291
|
-
}
|
|
304
|
+
for (const audio of this._incomingStreams.values()) {
|
|
305
|
+
audio.muted = mute;
|
|
292
306
|
}
|
|
293
307
|
}
|
|
294
308
|
|
|
295
|
-
/**
|
|
309
|
+
/**
|
|
310
|
+
* Returns true if incoming audio is currently muted (you can't hear other users).
|
|
311
|
+
* When there are no incoming streams, returns false.
|
|
312
|
+
*/
|
|
296
313
|
get isMuted() {
|
|
297
|
-
if (this.
|
|
298
|
-
const audio
|
|
299
|
-
|
|
300
|
-
for (const track of audio) {
|
|
301
|
-
if (!track.enabled) return true;
|
|
302
|
-
}
|
|
314
|
+
if (this._incomingStreams.size === 0) return false;
|
|
315
|
+
for (const audio of this._incomingStreams.values()) {
|
|
316
|
+
if (!audio.muted) return false;
|
|
303
317
|
}
|
|
304
|
-
return
|
|
318
|
+
return true;
|
|
305
319
|
}
|
|
306
320
|
|
|
307
321
|
private async updateButton() {
|
|
@@ -381,7 +395,7 @@ export class Voip extends Behaviour {
|
|
|
381
395
|
.catch((err) => {
|
|
382
396
|
console.warn("VOIP failed getting audio stream", err);
|
|
383
397
|
return null;
|
|
384
|
-
})
|
|
398
|
+
});
|
|
385
399
|
}
|
|
386
400
|
|
|
387
401
|
const stream = await getUserMedia(audio);
|
|
@@ -397,7 +411,13 @@ export class Voip extends Behaviour {
|
|
|
397
411
|
if (nonBuiltInAudioSource) {
|
|
398
412
|
const constraints = Object.assign({}, audio);
|
|
399
413
|
constraints.deviceId = nonBuiltInAudioSource.deviceId;
|
|
400
|
-
|
|
414
|
+
const externalStream = await getUserMedia(constraints);
|
|
415
|
+
if (externalStream) {
|
|
416
|
+
// Release the built-in mic stream we grabbed first — otherwise its tracks stay live.
|
|
417
|
+
disposeStream(stream);
|
|
418
|
+
return externalStream;
|
|
419
|
+
}
|
|
420
|
+
// External device acquisition failed — keep the original stream rather than returning null.
|
|
401
421
|
}
|
|
402
422
|
}
|
|
403
423
|
|
|
@@ -431,20 +451,25 @@ export class Voip extends Behaviour {
|
|
|
431
451
|
update() {
|
|
432
452
|
// Only run speaking detection if someone is listening
|
|
433
453
|
if (!this.onSpeakingChanged || this.onSpeakingChanged.listenerCount <= 0) return;
|
|
454
|
+
// Rate-limit analysis to ~10Hz. Speaking-state is a coarse UI signal; running FFT per
|
|
455
|
+
// remote user every frame (60Hz) is wasteful and scales linearly with participant count.
|
|
456
|
+
const now = performance.now();
|
|
457
|
+
if (now - this._lastSpeakingPollMs < 100) return;
|
|
458
|
+
this._lastSpeakingPollMs = now;
|
|
434
459
|
|
|
435
460
|
for (const [userId, info] of this._analysers) {
|
|
436
461
|
info.analyser.getByteFrequencyData(info.data as Uint8Array<ArrayBuffer>);
|
|
437
|
-
// Average amplitude
|
|
462
|
+
// Average amplitude normalized to 0–1
|
|
438
463
|
let sum = 0;
|
|
439
464
|
for (let i = 0; i < info.data.length; i++) sum += info.data[i];
|
|
440
|
-
const
|
|
465
|
+
const volume = sum / info.data.length / 255;
|
|
441
466
|
|
|
442
467
|
const wasSpeaking = this._speakingStates.get(userId) ?? false;
|
|
443
|
-
const isSpeaking =
|
|
468
|
+
const isSpeaking = volume > this.speakingThreshold;
|
|
444
469
|
|
|
445
470
|
if (isSpeaking !== wasSpeaking) {
|
|
446
471
|
this._speakingStates.set(userId, isSpeaking);
|
|
447
|
-
this.onSpeakingChanged.invoke({ userId, isSpeaking, volume
|
|
472
|
+
this.onSpeakingChanged.invoke({ userId, isSpeaking, volume });
|
|
448
473
|
}
|
|
449
474
|
}
|
|
450
475
|
}
|
|
@@ -453,13 +478,16 @@ export class Voip extends Behaviour {
|
|
|
453
478
|
// Only set up if someone is listening or might listen
|
|
454
479
|
if (this._analysers.has(userId)) return;
|
|
455
480
|
try {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
481
|
+
if (!this._sharedAudioContext) {
|
|
482
|
+
this._sharedAudioContext = new AudioContext();
|
|
483
|
+
}
|
|
484
|
+
const ctx = this._sharedAudioContext;
|
|
485
|
+
const source = ctx.createMediaStreamSource(stream);
|
|
486
|
+
const analyser = ctx.createAnalyser();
|
|
459
487
|
analyser.fftSize = 256;
|
|
460
488
|
source.connect(analyser);
|
|
461
489
|
const data = new Uint8Array(analyser.frequencyBinCount);
|
|
462
|
-
this._analysers.set(userId, { analyser, data
|
|
490
|
+
this._analysers.set(userId, { source, analyser, data });
|
|
463
491
|
}
|
|
464
492
|
catch (err) {
|
|
465
493
|
if (this.debug) console.warn("VOIP: Failed to create analyser for", userId, err);
|
|
@@ -469,12 +497,20 @@ export class Voip extends Behaviour {
|
|
|
469
497
|
private cleanupAnalyser(userId: string) {
|
|
470
498
|
const info = this._analysers.get(userId);
|
|
471
499
|
if (info) {
|
|
472
|
-
info.
|
|
500
|
+
info.source.disconnect();
|
|
501
|
+
info.analyser.disconnect();
|
|
473
502
|
this._analysers.delete(userId);
|
|
474
503
|
}
|
|
475
504
|
this._speakingStates.delete(userId);
|
|
476
505
|
}
|
|
477
506
|
|
|
507
|
+
private closeSharedAudioContext() {
|
|
508
|
+
if (this._sharedAudioContext) {
|
|
509
|
+
this._sharedAudioContext.close();
|
|
510
|
+
this._sharedAudioContext = undefined;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
478
514
|
private onReceiveStream = (evt: StreamReceivedEvent) => {
|
|
479
515
|
const userId = evt.target.userId;
|
|
480
516
|
const stream = evt.stream;
|
|
@@ -512,12 +548,11 @@ export class Voip extends Behaviour {
|
|
|
512
548
|
|
|
513
549
|
private onVisibilityChanged = () => {
|
|
514
550
|
if (this.runInBackground) return;
|
|
515
|
-
const
|
|
516
|
-
|
|
551
|
+
const muted = document.visibilityState !== "visible";
|
|
552
|
+
// Mute incoming so we don't hear other users while tab is hidden.
|
|
517
553
|
this.setMuted(muted);
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
554
|
+
// Also disable our outgoing mic tracks (cheaper than disconnect/reconnect — keeps the mic permission).
|
|
555
|
+
const tracks = this._outputStream?.getAudioTracks();
|
|
556
|
+
if (tracks) for (const t of tracks) t.enabled = !muted;
|
|
522
557
|
};
|
|
523
558
|
}
|
|
@@ -34,12 +34,14 @@
|
|
|
34
34
|
* @module Built-in Components
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
|
+
export { AnimationBuilder, type AnimationKeyframe, type Tween, type AnimationInterpolation } from "./AnimationBuilder.js";
|
|
38
|
+
export { AnimatorControllerBuilder, type ConditionMode, type StateOptions, type TransitionOptions } from "./AnimatorController.builder.js";
|
|
37
39
|
export * from "./codegen/components.js";
|
|
38
|
-
export { AnimatorControllerBuilder, type ConditionMode, type StateOptions, type TransitionOptions } from "./AnimatorController.js";
|
|
39
40
|
export { Collider } from "./Collider.js"; // export abstract type
|
|
40
41
|
export { Behaviour, Component, GameObject } from "./Component.js";
|
|
41
42
|
|
|
42
43
|
// We dont want to export everything in the extensions
|
|
44
|
+
export { AudioRolloffMode } from "./AudioSource.js";
|
|
43
45
|
export { ClearFlags } from "./Camera.js"
|
|
44
46
|
export { DragMode } from "./DragControls.js";
|
|
45
47
|
export type { DropListenerNetworkEventArguments, DropListenerOnDropArguments } from "./DropListener.js";
|
|
@@ -3,21 +3,13 @@
|
|
|
3
3
|
export class __Ignore {}
|
|
4
4
|
export { AlignmentConstraint } from "../AlignmentConstraint.js";
|
|
5
5
|
export { Animation } from "../Animation.js";
|
|
6
|
+
export { AnimationBuilder } from "../AnimationBuilder.js";
|
|
6
7
|
export { Keyframe } from "../AnimationCurve.js";
|
|
7
8
|
export { AnimationCurve } from "../AnimationCurve.js";
|
|
8
9
|
export { Animator } from "../Animator.js";
|
|
9
|
-
export { AnimatorControllerBuilder } from "../AnimatorController.js";
|
|
10
10
|
export { AnimatorController } from "../AnimatorController.js";
|
|
11
11
|
export { AudioListener } from "../AudioListener.js";
|
|
12
12
|
export { AudioSource } from "../AudioSource.js";
|
|
13
|
-
export { Avatar_POI } from "../avatar/Avatar_Brain_LookAt.js";
|
|
14
|
-
export { Avatar_Brain_LookAt } from "../avatar/Avatar_Brain_LookAt.js";
|
|
15
|
-
export { Avatar_MouthShapes } from "../avatar/Avatar_MouthShapes.js";
|
|
16
|
-
export { Avatar_MustacheShake } from "../avatar/Avatar_MustacheShake.js";
|
|
17
|
-
export { AvatarBlink_Simple } from "../avatar/AvatarBlink_Simple.js";
|
|
18
|
-
export { AvatarEyeLook_Rotation } from "../avatar/AvatarEyeLook_Rotation.js";
|
|
19
|
-
export { AvatarModel } from "../AvatarLoader.js";
|
|
20
|
-
export { AvatarLoader } from "../AvatarLoader.js";
|
|
21
13
|
export { AxesHelper } from "../AxesHelper.js";
|
|
22
14
|
export { BasicIKConstraint } from "../BasicIKConstraint.js";
|
|
23
15
|
export { BoxHelperComponent } from "../BoxHelperComponent.js";
|
|
@@ -168,11 +160,13 @@ export { PlayableDirector } from "../timeline/PlayableDirector.js";
|
|
|
168
160
|
export { SignalAsset } from "../timeline/SignalAsset.js";
|
|
169
161
|
export { SignalReceiverEvent } from "../timeline/SignalAsset.js";
|
|
170
162
|
export { SignalReceiver } from "../timeline/SignalAsset.js";
|
|
171
|
-
export {
|
|
172
|
-
export {
|
|
173
|
-
export {
|
|
163
|
+
export { TimelineBuilder } from "../timeline/TimelineBuilder.js";
|
|
164
|
+
export { TimelineAnimationTrack } from "../timeline/TimelineTracks.js";
|
|
165
|
+
export { TimelineAudioTrack } from "../timeline/TimelineTracks.js";
|
|
166
|
+
export { TimelineMarkerTrack } from "../timeline/TimelineTracks.js";
|
|
174
167
|
export { SignalTrackHandler } from "../timeline/TimelineTracks.js";
|
|
175
|
-
export {
|
|
168
|
+
export { TimelineActivationTrack } from "../timeline/TimelineTracks.js";
|
|
169
|
+
export { TimelineControlTrack } from "../timeline/TimelineTracks.js";
|
|
176
170
|
export { TransformGizmo } from "../TransformGizmo.js";
|
|
177
171
|
export { BaseUIComponent } from "../ui/BaseUIComponent.js";
|
|
178
172
|
export { UIRootComponent } from "../ui/BaseUIComponent.js";
|