@needle-tools/engine 4.8.9 → 4.9.0-alpha.1
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 +2 -0
- package/dist/gltf-progressive-DWiyqrwB.umd.cjs +8 -0
- package/dist/gltf-progressive-DhE1A6hX.min.js +8 -0
- package/dist/{gltf-progressive-BcHT3Nyo.js → gltf-progressive-egsMzRdv.js} +384 -369
- package/dist/{needle-engine.bundle-BdgNRB4B.js → needle-engine.bundle-CVeZ2dVS.js} +8003 -7731
- package/dist/{needle-engine.bundle-DGca1cic.umd.cjs → needle-engine.bundle-CokWPL7y.umd.cjs} +138 -138
- package/dist/{needle-engine.bundle-TQyyn3pE.min.js → needle-engine.bundle-DW-9zmeW.min.js} +142 -142
- package/dist/needle-engine.js +305 -301
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-Ywv5oKkX.min.js → postprocessing-B_FzkwDz.min.js} +1 -1
- package/dist/{postprocessing-ORx-0eCx.js → postprocessing-DP1U_BpT.js} +2 -2
- package/dist/{postprocessing-CVb_x9YY.umd.cjs → postprocessing-DTX5VXrj.umd.cjs} +1 -1
- package/dist/rapier-BJaux8TQ.js +5217 -0
- package/dist/rapier-Bd0qRV1r.umd.cjs +1 -0
- package/dist/rapier-CnHGx3sO.min.js +1 -0
- package/dist/{three-Dceyffus.umd.cjs → three-BK56xWDs.umd.cjs} +24 -24
- package/dist/{three-BRSLmpyi.js → three-CsHK73Zc.js} +1 -0
- package/dist/{three-CsmWHVn7.min.js → three-TNFQHSFa.min.js} +25 -25
- package/dist/{three-examples-BX_Sktc9.min.js → three-examples-Bph291U2.min.js} +1 -1
- package/dist/{three-examples-CNexix3E.js → three-examples-BvMpKSun.js} +1 -1
- package/dist/{three-examples-DWxXVnws.umd.cjs → three-examples-C9WfZu-X.umd.cjs} +1 -1
- package/dist/{three-mesh-ui-gqAXlGNB.js → three-mesh-ui-CN6aRT7i.js} +1 -1
- package/dist/{three-mesh-ui-Cdh2iW8b.umd.cjs → three-mesh-ui-DnxkZWNA.umd.cjs} +1 -1
- package/dist/{three-mesh-ui-Bwy12Qvg.min.js → three-mesh-ui-n_qS2BM-.min.js} +1 -1
- package/dist/{vendor-xfQ8tKF3.umd.cjs → vendor-8le8g4MS.umd.cjs} +1 -1
- package/dist/{vendor-Z4SPrTcP.js → vendor-Msb9AgYE.js} +1 -1
- package/dist/{vendor-C43vobGc.min.js → vendor-yDFiCnCw.min.js} +1 -1
- package/lib/engine/codegen/register_types.js +4 -0
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_addressables.d.ts +5 -0
- package/lib/engine/engine_addressables.js +19 -8
- package/lib/engine/engine_addressables.js.map +1 -1
- package/lib/engine/engine_assetdatabase.js +10 -8
- package/lib/engine/engine_assetdatabase.js.map +1 -1
- package/lib/engine/engine_physics_rapier.js +6 -4
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_three_utils.js +4 -2
- package/lib/engine/engine_three_utils.js.map +1 -1
- package/lib/engine/xr/NeedleXRController.js +30 -9
- package/lib/engine/xr/NeedleXRController.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.js +18 -13
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine-components/RendererLightmap.js +3 -1
- package/lib/engine-components/RendererLightmap.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -0
- package/lib/engine-components/api.js +1 -0
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +3 -0
- package/lib/engine-components/codegen/components.js +3 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.d.ts +1 -1
- package/lib/engine-components/splines/Spline.d.ts +58 -0
- package/lib/engine-components/splines/Spline.js +270 -0
- package/lib/engine-components/splines/Spline.js.map +1 -0
- package/lib/engine-components/splines/SplineUtils.d.ts +12 -0
- package/lib/engine-components/splines/SplineUtils.js +33 -0
- package/lib/engine-components/splines/SplineUtils.js.map +1 -0
- package/lib/engine-components/splines/SplineWalker.d.ts +47 -0
- package/lib/engine-components/splines/SplineWalker.js +113 -0
- package/lib/engine-components/splines/SplineWalker.js.map +1 -0
- package/lib/engine-components/splines/index.d.ts +3 -0
- package/lib/engine-components/splines/index.js +4 -0
- package/lib/engine-components/splines/index.js.map +1 -0
- package/lib/engine-components/webxr/WebXRImageTracking.d.ts +23 -1
- package/lib/engine-components/webxr/WebXRImageTracking.js +57 -3
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/lib/engine-components/webxr/controllers/XRControllerModel.js +2 -2
- package/lib/engine-components/webxr/controllers/XRControllerModel.js.map +1 -1
- package/lib/engine-components/webxr/controllers/XRControllerMovement.js +4 -0
- package/lib/engine-components/webxr/controllers/XRControllerMovement.js.map +1 -1
- package/package.json +3 -3
- package/plugins/vite/alias.js +3 -2
- package/plugins/vite/peer.js +7 -3
- package/src/engine/codegen/register_types.ts +4 -0
- package/src/engine/engine_addressables.ts +21 -6
- package/src/engine/engine_assetdatabase.ts +9 -7
- package/src/engine/engine_physics_rapier.ts +7 -5
- package/src/engine/engine_three_utils.ts +5 -2
- package/src/engine/xr/NeedleXRController.ts +39 -15
- package/src/engine/xr/NeedleXRSession.ts +18 -13
- package/src/engine-components/RendererLightmap.ts +2 -1
- package/src/engine-components/api.ts +1 -0
- package/src/engine-components/codegen/components.ts +3 -0
- package/src/engine-components/splines/Spline.ts +284 -0
- package/src/engine-components/splines/SplineUtils.ts +32 -0
- package/src/engine-components/splines/SplineWalker.ts +106 -0
- package/src/engine-components/splines/index.ts +3 -0
- package/src/engine-components/webxr/WebXRImageTracking.ts +59 -4
- package/src/engine-components/webxr/controllers/XRControllerModel.ts +3 -2
- package/src/engine-components/webxr/controllers/XRControllerMovement.ts +5 -1
- package/dist/gltf-progressive-CH3Q4H06.umd.cjs +0 -8
- package/dist/gltf-progressive-DR6HqF_h.min.js +0 -8
- package/dist/rapier--oeYP_h7.umd.cjs +0 -1
- package/dist/rapier-B3xpyPtq.js +0 -5142
- package/dist/rapier-CyWhltHY.min.js +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AnimationAction, Box3, Box3Helper, Camera, Color, DepthTexture, Euler, GridHelper, Layers, Material, Mesh, MeshStandardMaterial, Object3D, OrthographicCamera, PerspectiveCamera, PlaneGeometry, Quaternion, Scene, ShadowMaterial, Texture, Uniform, Vector2Like, Vector3, WebGLRenderTarget } from "three";
|
|
2
2
|
import { ShaderMaterial, WebGLRenderer } from "three";
|
|
3
3
|
import { GroundedSkybox } from "three/examples/jsm/objects/GroundedSkybox.js";
|
|
4
|
+
import { rendererReference } from "three/src/nodes/TSL.js";
|
|
4
5
|
|
|
5
6
|
import { useForAutoFit } from "./engine_camera.js";
|
|
6
7
|
import { Mathf } from "./engine_math.js"
|
|
@@ -587,8 +588,10 @@ export class Graphics {
|
|
|
587
588
|
renderer.state.buffers.depth.setMask(depthWrite);
|
|
588
589
|
|
|
589
590
|
renderer.setClearColor(new Color(0, 0, 0), 0);
|
|
590
|
-
|
|
591
|
-
|
|
591
|
+
if (renderer.pixelRatio !== window.devicePixelRatio) {
|
|
592
|
+
if (renderer.xr.isPresenting === false)
|
|
593
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
594
|
+
}
|
|
592
595
|
renderer.setRenderTarget(target);
|
|
593
596
|
renderer.clear();
|
|
594
597
|
renderer.render(this.scene, this.perspectiveCam);
|
|
@@ -398,8 +398,8 @@ export class NeedleXRController implements IPointerHitEventReceiver {
|
|
|
398
398
|
C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inputSource.hand ? "x" : "-"} Pen: ${this._isMxInk ? "x" : "-"}`;
|
|
399
399
|
if (this.inputSource.hand) debugStr += `\nPinch: ${this.getGesture("pinch")?.value.toFixed(3)}`;
|
|
400
400
|
debugStr += "\n" + profileStr;
|
|
401
|
-
debugStr += "\n" + (this.inputSource.targetRaySpace ? `Ray: x` : "Ray: -") +
|
|
402
|
-
(this.inputSource.gripSpace ? " Grip: x" : " Grip: -") +
|
|
401
|
+
debugStr += "\n" + (this.inputSource.targetRaySpace ? `Ray: x` : "Ray: -") +
|
|
402
|
+
(this.inputSource.gripSpace ? " Grip: x" : " Grip: -") +
|
|
403
403
|
(this.inputSource.gamepad ? ` Gamepad: ${this.inputSource.gamepad.mapping}` : " Gamepad: -");
|
|
404
404
|
if (this.inputSource.gamepad) {
|
|
405
405
|
const gp = this.inputSource.gamepad;
|
|
@@ -415,7 +415,7 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
|
|
|
415
415
|
this._handJointPoses.clear();
|
|
416
416
|
this._hand_wristDotUp = undefined;
|
|
417
417
|
|
|
418
|
-
if (!this.xr.referenceSpace) {
|
|
418
|
+
if (!this.xr.referenceSpace || !this.inputSource.gamepad?.connected) {
|
|
419
419
|
this._isTracking = false;
|
|
420
420
|
return;
|
|
421
421
|
}
|
|
@@ -460,7 +460,7 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
|
|
|
460
460
|
|
|
461
461
|
// update controller object parent – needs to be parented to the rig, which
|
|
462
462
|
// implicitly is the same object as the camera parent.
|
|
463
|
-
if (this.xr.context.mainCamera?.parent) {
|
|
463
|
+
if (this.xr.context.mainCamera?.parent) {
|
|
464
464
|
if (this._object.parent !== this.xr.context.mainCamera?.parent)
|
|
465
465
|
this.xr.context.mainCamera.parent.add(this._object);
|
|
466
466
|
if (this._gripSpaceObject !== undefined && this._gripSpaceObject?.parent !== this.xr.context.mainCamera?.parent)
|
|
@@ -594,6 +594,24 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
|
|
|
594
594
|
return this.getGesture("pinch");
|
|
595
595
|
}
|
|
596
596
|
return this.toNeedleGamepadButton(0, key);
|
|
597
|
+
|
|
598
|
+
case "xr-standard-trigger":
|
|
599
|
+
if (this.inputSource.gamepad) {
|
|
600
|
+
return this.toNeedleGamepadButton(0, key);
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
603
|
+
|
|
604
|
+
case "xr-standard-squeeze":
|
|
605
|
+
if (this.inputSource.gamepad) {
|
|
606
|
+
return this.toNeedleGamepadButton(1, key);
|
|
607
|
+
}
|
|
608
|
+
break;
|
|
609
|
+
|
|
610
|
+
case "xr-standard-thumbstick":
|
|
611
|
+
if (this.inputSource.gamepad) {
|
|
612
|
+
return this.toNeedleGamepadButton(3, key);
|
|
613
|
+
}
|
|
614
|
+
break;
|
|
597
615
|
}
|
|
598
616
|
|
|
599
617
|
|
|
@@ -694,12 +712,18 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
|
|
|
694
712
|
getStick(key: StickName | "primary"): Vec3 {
|
|
695
713
|
if (!this._layout) return { x: 0, y: 0, z: 0 };
|
|
696
714
|
|
|
715
|
+
// Hands dont have thumbsicks
|
|
716
|
+
if (this.isHand) {
|
|
717
|
+
return { x: 0, y: 0, z: 0 };
|
|
718
|
+
}
|
|
719
|
+
|
|
697
720
|
if (key === "primary") {
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
//
|
|
701
|
-
|
|
702
|
-
|
|
721
|
+
if (this._layout.components["xr-standard-thumbstick"]) key = "xr-standard-thumbstick";
|
|
722
|
+
// const x = this.inputSource.gamepad?.axes[0] || 0;
|
|
723
|
+
// const y = this.inputSource.gamepad?.axes[1] || 0;
|
|
724
|
+
// // the primary thumbstick is button 3 (see gamepads module explainer)
|
|
725
|
+
// const z = this.inputSource.gamepad?.buttons[3]?.value || 0;
|
|
726
|
+
// return { x, y, z }
|
|
703
727
|
}
|
|
704
728
|
|
|
705
729
|
const componentModel = this._layout?.components[key];
|
|
@@ -709,12 +733,12 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
|
|
|
709
733
|
if (this.inputSource.gamepad) {
|
|
710
734
|
const xIndex = componentModel.gamepadIndices!.xAxis!;
|
|
711
735
|
const yIndex = componentModel.gamepadIndices!.yAxis!;
|
|
712
|
-
let x = this.inputSource.gamepad
|
|
713
|
-
let y = this.inputSource.gamepad
|
|
736
|
+
let x = this.inputSource.gamepad.axes[xIndex] || 0;
|
|
737
|
+
let y = this.inputSource.gamepad.axes[yIndex] || 0;
|
|
714
738
|
x *= -1;
|
|
715
739
|
y *= -1;
|
|
716
740
|
const buttonIndex = componentModel.gamepadIndices!.button!;
|
|
717
|
-
const z = this.inputSource.gamepad?.buttons[buttonIndex]?.value;
|
|
741
|
+
const z = this.inputSource.gamepad?.buttons[buttonIndex]?.value || 0;
|
|
718
742
|
return { x, y, z }
|
|
719
743
|
}
|
|
720
744
|
}
|
|
@@ -731,13 +755,13 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
|
|
|
731
755
|
private initialize() {
|
|
732
756
|
// WORKAROUND for hand controllers that don't have a select event
|
|
733
757
|
this._hasSelectEvent = this.profiles.includes("generic-hand-select") || this.profiles.some(p => p.startsWith("generic-trigger"));
|
|
734
|
-
|
|
758
|
+
|
|
735
759
|
// Used to determine special layout for Quest controllers, e.g. last button is menu button
|
|
736
760
|
this._isMetaQuestTouchController = this.profiles.includes("meta-quest-touch-plus") || this.profiles.includes("oculus-touch-v3");
|
|
737
761
|
|
|
738
762
|
// Proper profile starting with v69 and browser 35.1
|
|
739
763
|
this._isMxInk = this.profiles.includes("logitech-mx-ink")
|
|
740
|
-
|
|
764
|
+
|
|
741
765
|
if (!this._layout) {
|
|
742
766
|
// Ignore transient-pointer since we likely don't want to spawn a controller visual just for a temporary pointer.
|
|
743
767
|
// TODO we should check how this is actually handled on Quest Browser when the transient-pointer flag is on.
|
|
@@ -953,7 +977,7 @@ C:${this.connected ? "x" : "-"} T:${this.isTracking ? "x" : "-"} Hand:${this.inp
|
|
|
953
977
|
if (menuButtonState) {
|
|
954
978
|
if (menuButtonState.isDown) {
|
|
955
979
|
const menu = this.context.menu;
|
|
956
|
-
if (menu.spatialMenuIsVisible)
|
|
980
|
+
if (menu.spatialMenuIsVisible)
|
|
957
981
|
menu.setSpatialMenuVisible(false);
|
|
958
982
|
else
|
|
959
983
|
this.context.menu.setSpatialMenuVisible(true);
|
|
@@ -530,7 +530,7 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
530
530
|
listener({ mode, init });
|
|
531
531
|
}
|
|
532
532
|
if (debug) showBalloonMessage("Requesting " + mode + " session (" + Date.now() + ")");
|
|
533
|
-
this._currentSessionRequest = navigator
|
|
533
|
+
this._currentSessionRequest = navigator?.xr?.requestSession(mode, init);
|
|
534
534
|
this._currentSessionRequestMode = mode;
|
|
535
535
|
/**@type {XRSystem} */
|
|
536
536
|
const newSession = await (this._currentSessionRequest)?.catch(e => {
|
|
@@ -1053,10 +1053,13 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
1053
1053
|
|
|
1054
1054
|
/** Disconnects the controller, invokes events and notifies previou controller (if any) */
|
|
1055
1055
|
private disconnectInputSource(inputSource: XRInputSource) {
|
|
1056
|
-
const handleRemove = (oldController: NeedleXRController,
|
|
1056
|
+
const handleRemove = (oldController: NeedleXRController, array: Array<NeedleXRController>) => {
|
|
1057
1057
|
if (oldController.inputSource === inputSource) {
|
|
1058
1058
|
if (debug) console.log("Disconnecting controller", oldController.index);
|
|
1059
|
-
|
|
1059
|
+
|
|
1060
|
+
const index = array.indexOf(oldController);
|
|
1061
|
+
if(index >= 0) array.splice(index, 1);
|
|
1062
|
+
|
|
1060
1063
|
this.invokeControllerEvent(oldController, this._controllerRemoved, "removed");
|
|
1061
1064
|
const args: NeedleXRControllerEventArgs = {
|
|
1062
1065
|
xr: this,
|
|
@@ -1069,13 +1072,15 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
1069
1072
|
oldController.onDisconnected();
|
|
1070
1073
|
}
|
|
1071
1074
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
+
const copy = [...this.controllers];
|
|
1076
|
+
for (let i = copy.length - 1; i >= 0; i--) {
|
|
1077
|
+
const oldController = copy[i];
|
|
1078
|
+
handleRemove(oldController, this.controllers);
|
|
1075
1079
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1080
|
+
const copyNew = [...this._newControllers];
|
|
1081
|
+
for (let i = copyNew.length - 1; i >= 0; i--) {
|
|
1082
|
+
const oldController = copyNew[i];
|
|
1083
|
+
handleRemove(oldController, this._newControllers);
|
|
1079
1084
|
}
|
|
1080
1085
|
}
|
|
1081
1086
|
|
|
@@ -1130,8 +1135,8 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
1130
1135
|
for (let i = 0; i < copy.length; i++) {
|
|
1131
1136
|
this.disconnectInputSource(copy[i].inputSource);
|
|
1132
1137
|
}
|
|
1133
|
-
this._newControllers.length = 0;
|
|
1134
1138
|
this.controllers.length = 0;
|
|
1139
|
+
this._newControllers.length = 0;
|
|
1135
1140
|
|
|
1136
1141
|
// we want to call leave XR for *all* scripts that are still registered
|
|
1137
1142
|
// even if they might already be destroyed e.g. by the WebXR component (it destroys the default controller scripts)
|
|
@@ -1318,8 +1323,6 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
1318
1323
|
continue;
|
|
1319
1324
|
}
|
|
1320
1325
|
this.invokeCallback_ControllerAdded(script, controller);
|
|
1321
|
-
// if (script.onXRControllerAdded)
|
|
1322
|
-
// script.onXRControllerAdded({ xr: this, controller, change: "added" });
|
|
1323
1326
|
}
|
|
1324
1327
|
}
|
|
1325
1328
|
this.controllers.sort((a, b) => a.index - b.index);
|
|
@@ -1558,9 +1561,10 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
1558
1561
|
const rigWorldScale = getWorldScale(this.rig.gameObject);
|
|
1559
1562
|
minNearPlane *= rigWorldScale.x;
|
|
1560
1563
|
}
|
|
1561
|
-
if (this._camera instanceof PerspectiveCamera && this._camera.near
|
|
1564
|
+
if (this._camera instanceof PerspectiveCamera && Math.abs(this._camera.near - minNearPlane) > .0001) {
|
|
1562
1565
|
this.originalCameraNearPlane = this._camera.near;
|
|
1563
1566
|
this._camera.near = minNearPlane;
|
|
1567
|
+
if(debug) console.debug(`Setting camera near plane to ${minNearPlane} (was ${this.originalCameraNearPlane}) to account for XR rendering scale`);
|
|
1564
1568
|
}
|
|
1565
1569
|
}
|
|
1566
1570
|
}
|
|
@@ -1572,6 +1576,7 @@ export class NeedleXRSession implements INeedleXRSession {
|
|
|
1572
1576
|
|
|
1573
1577
|
if (this._camera instanceof PerspectiveCamera && this.originalCameraNearPlane != undefined) {
|
|
1574
1578
|
this._camera.near = this.originalCameraNearPlane;
|
|
1579
|
+
this.originalCameraNearPlane = undefined;
|
|
1575
1580
|
}
|
|
1576
1581
|
}
|
|
1577
1582
|
|
|
@@ -127,6 +127,7 @@ export class RendererLightmap {
|
|
|
127
127
|
if (material["NEEDLE:lightmap-material-version"] == undefined) {
|
|
128
128
|
if (debug) console.warn("Cloning material for lightmap " + material.name);
|
|
129
129
|
const mat: Material = material.clone();
|
|
130
|
+
if(!mat.name?.includes("(lightmap)")) mat.name = material.name + " (lightmap)";
|
|
130
131
|
material = mat;
|
|
131
132
|
material.onBeforeCompile = this.onBeforeCompile;
|
|
132
133
|
}
|
|
@@ -147,7 +148,7 @@ export class RendererLightmap {
|
|
|
147
148
|
return;
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
if (debug) console.log("Assigning lightmap", material.name, material.version);
|
|
151
|
+
if (debug) console.log("Assigning lightmap", material.name, material.version, material);
|
|
151
152
|
|
|
152
153
|
// assign the lightmap
|
|
153
154
|
material.lightMap = this.lightmapTexture;
|
|
@@ -55,6 +55,7 @@ export { DragMode } from "./DragControls.js";
|
|
|
55
55
|
export type { DropListenerNetworkEventArguments, DropListenerOnDropArguments } from "./DropListener.js";
|
|
56
56
|
export { type FitCameraOptions } from "./OrbitControls.js";
|
|
57
57
|
export * from "./particlesystem/api.js"
|
|
58
|
+
export * from "./splines/index.js";
|
|
58
59
|
|
|
59
60
|
// for correct type resolution in JSDoc
|
|
60
61
|
import type { PhysicsMaterial } from "../engine/engine_physics.types.js";
|
|
@@ -150,6 +150,9 @@ export { SmoothFollow } from "../SmoothFollow.js";
|
|
|
150
150
|
export { SpatialTriggerReceiver } from "../SpatialTrigger.js";
|
|
151
151
|
export { SpatialTrigger } from "../SpatialTrigger.js";
|
|
152
152
|
export { SpectatorCamera } from "../SpectatorCamera.js";
|
|
153
|
+
export { SplineData } from "../splines/Spline.js";
|
|
154
|
+
export { SplineContainer } from "../splines/Spline.js";
|
|
155
|
+
export { SplineWalker } from "../splines/SplineWalker.js";
|
|
153
156
|
export { Sprite } from "../SpriteRenderer.js";
|
|
154
157
|
export { SpriteSheet } from "../SpriteRenderer.js";
|
|
155
158
|
export { SpriteData } from "../SpriteRenderer.js";
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
|
|
2
|
+
import { BufferGeometry, CatmullRomCurve3, CubicBezierCurve3, Curve, Line, LineBasicMaterial,LineCurve3, Object3D, Quaternion, Vector3 } from "three";
|
|
3
|
+
|
|
4
|
+
import { Mathf } from "../../engine/engine_math.js";
|
|
5
|
+
import { serializeable } from "../../engine/engine_serialization.js";
|
|
6
|
+
import { getParam } from "../../engine/engine_utils.js";
|
|
7
|
+
import { Behaviour } from "../Component.js";
|
|
8
|
+
|
|
9
|
+
const debug = getParam("debugsplines");
|
|
10
|
+
|
|
11
|
+
export class SplineData {
|
|
12
|
+
@serializeable(Vector3)
|
|
13
|
+
position: Vector3 = new Vector3();
|
|
14
|
+
|
|
15
|
+
@serializeable(Quaternion)
|
|
16
|
+
rotation: Quaternion = new Quaternion();
|
|
17
|
+
|
|
18
|
+
@serializeable(Vector3)
|
|
19
|
+
tangentIn: Vector3 = new Vector3();
|
|
20
|
+
|
|
21
|
+
@serializeable(Vector3)
|
|
22
|
+
tangentOut: Vector3 = new Vector3();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// enum SplineTypeEnum {
|
|
26
|
+
// CatmullRom = 0,
|
|
27
|
+
// Bezier = 1,
|
|
28
|
+
// Linear = 2
|
|
29
|
+
// }
|
|
30
|
+
// type SplineType = "CatmullRom" | "Bezier" | "Linear";
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
//@dont-generate-component
|
|
34
|
+
/**
|
|
35
|
+
* Holds spline data and generates a spline curve. Use with SplineWalker to move objects along the spline or call getPointAt to sample points on the spline.
|
|
36
|
+
* The spline is defined by an array of knots (SplineData) which define position, rotation and tangents.
|
|
37
|
+
*
|
|
38
|
+
* You can create a SplineContainer from an array of points using the static method 'createFromPoints'.
|
|
39
|
+
*/
|
|
40
|
+
export class SplineContainer extends Behaviour {
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Adds a knot to the end of the spline.
|
|
44
|
+
*/
|
|
45
|
+
addKnot(knot: SplineData | { position: Vector3 }): SplineContainer {
|
|
46
|
+
if (knot instanceof SplineData) {
|
|
47
|
+
this.spline.push(knot);
|
|
48
|
+
this._isDirty = true;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const k = new SplineData();
|
|
52
|
+
k.position.copy(knot.position);
|
|
53
|
+
this.spline.push(k);
|
|
54
|
+
this._isDirty = true;
|
|
55
|
+
}
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Removes a knot by index or by reference.
|
|
61
|
+
*/
|
|
62
|
+
removeKnot(index: number | SplineData): SplineContainer {
|
|
63
|
+
if (typeof index === "number") {
|
|
64
|
+
this.spline.splice(index, 1);
|
|
65
|
+
this._isDirty = true;
|
|
66
|
+
} else {
|
|
67
|
+
const i = this.spline.indexOf(index);
|
|
68
|
+
if (i !== -1) {
|
|
69
|
+
this.spline.splice(i, 1);
|
|
70
|
+
this._isDirty = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Gets a point on the spline in world space.
|
|
78
|
+
*/
|
|
79
|
+
getPointAt(t: number, target?: Vector3): Vector3 {
|
|
80
|
+
if (!this.curve) return new Vector3();
|
|
81
|
+
const pos = this.curve.getPointAt(Mathf.clamp01(t), target);
|
|
82
|
+
const worldMatrix = this.gameObject.matrixWorld ?? undefined;
|
|
83
|
+
if (worldMatrix) {
|
|
84
|
+
pos.applyMatrix4(worldMatrix);
|
|
85
|
+
}
|
|
86
|
+
return pos;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Marks the spline as dirty, causing it to be rebuilt on the next update.
|
|
91
|
+
*/
|
|
92
|
+
markDirty() {
|
|
93
|
+
this._isDirty = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gets the tangent vector on the spline in world space.
|
|
98
|
+
*/
|
|
99
|
+
getTangentAt(t: number, target?: Vector3): Vector3 {
|
|
100
|
+
if (!this.curve) return target ?? new Vector3();
|
|
101
|
+
const wr = this.gameObject.worldQuaternion;
|
|
102
|
+
return this.curve.getTangentAt(Mathf.clamp01(t), target).applyQuaternion(wr);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@serializeable()
|
|
106
|
+
set closed(value: boolean) {
|
|
107
|
+
this._closed = value;
|
|
108
|
+
this._isDirty = true;
|
|
109
|
+
}
|
|
110
|
+
get closed() { return this._closed; }
|
|
111
|
+
private _closed: boolean = false;
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
/** Spline data. Call 'markDirty' if modified */
|
|
115
|
+
@serializeable(SplineData)
|
|
116
|
+
spline: SplineData[] = [];
|
|
117
|
+
|
|
118
|
+
/** Enable to render the spline curve for debugging */
|
|
119
|
+
set debug(debug: boolean) {
|
|
120
|
+
if (debug && !this._builtCurve) this.buildCurve();
|
|
121
|
+
if (!this._debugLine) return;
|
|
122
|
+
this._debugLine.visible = debug;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Gets the spline curve generated from the 'spline' data */
|
|
126
|
+
get curve(): Curve<Vector3> | null {
|
|
127
|
+
return this._curve;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get isDirty() { return this._isDirty; }
|
|
131
|
+
|
|
132
|
+
private _isDirty: boolean = false;
|
|
133
|
+
|
|
134
|
+
private _curve: Curve<Vector3> | null = null;
|
|
135
|
+
private _builtCurve: boolean = false;
|
|
136
|
+
private _debugLine: Object3D | null = null;
|
|
137
|
+
|
|
138
|
+
/** @internal */
|
|
139
|
+
awake() {
|
|
140
|
+
if (debug) {
|
|
141
|
+
console.log(`[Spline] ${this.name}`, this);
|
|
142
|
+
this.buildCurve();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** @internal */
|
|
147
|
+
update() {
|
|
148
|
+
if (this._isDirty) {
|
|
149
|
+
this.buildCurve(true);
|
|
150
|
+
}
|
|
151
|
+
if (this._debugLine && this._debugLine.parent !== this.gameObject) this.gameObject.add(this._debugLine);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private buildCurve(force: boolean = false) {
|
|
155
|
+
if (this._builtCurve && !force) return;
|
|
156
|
+
this._builtCurve = true;
|
|
157
|
+
|
|
158
|
+
if (!this.spline) {
|
|
159
|
+
console.error("[Spline] Can not build curve, no spline data", this.name);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this._isDirty = false;
|
|
163
|
+
this._curve = createCatmullRomCurve(this.spline, this.closed);
|
|
164
|
+
this.buildDebugCurve();
|
|
165
|
+
// TODO: Unity supports spline interpolation type per knot which we don't support right now. Additionally EditType is deprecated. For simplicity we're just supporting CatmullRom for now.
|
|
166
|
+
// switch (this.editType) {
|
|
167
|
+
// case SplineType.CatmullRom:
|
|
168
|
+
// this.createCatmullRomCurve();
|
|
169
|
+
// break;
|
|
170
|
+
// case SplineType.Bezier:
|
|
171
|
+
// console.warn("Bezier spline not implemented yet", this.name);
|
|
172
|
+
// this.createCatmullRomCurve();
|
|
173
|
+
// // this.createBezierCurve();
|
|
174
|
+
// break;
|
|
175
|
+
// case SplineType.Linear:
|
|
176
|
+
// this.createLinearCurve();
|
|
177
|
+
// break;
|
|
178
|
+
// }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private buildDebugCurve() {
|
|
182
|
+
if (debug && this.spline && this._curve) {
|
|
183
|
+
this._debugLine?.removeFromParent();
|
|
184
|
+
this._debugLine = null;
|
|
185
|
+
|
|
186
|
+
const material = new LineBasicMaterial({
|
|
187
|
+
color: 0x6600ff,
|
|
188
|
+
});
|
|
189
|
+
const res = this.spline.length * 10;
|
|
190
|
+
const splinePoints = this._curve.getPoints(res);
|
|
191
|
+
const geometry = new BufferGeometry().setFromPoints(splinePoints);
|
|
192
|
+
this._debugLine = new Line(geometry, material);
|
|
193
|
+
this.gameObject?.add(this._debugLine);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
function createCatmullRomCurve(data: SplineData[], closed: boolean): CatmullRomCurve3 {
|
|
200
|
+
const points = data.map(knot => new Vector3(-knot.position.x, knot.position.y, knot.position.z));
|
|
201
|
+
if (points.length === 1) points.push(points[0]);
|
|
202
|
+
const averageTension = data.reduce((acc, knot) => acc + Math.abs(knot.tangentOut.x) + Math.abs(knot.tangentOut.y) + Math.abs(knot.tangentOut.z), 0) / data.length;
|
|
203
|
+
const tension = Mathf.remap(averageTension, 0, 0.3, 0, .5);
|
|
204
|
+
return new CatmullRomCurve3(points, closed, "catmullrom", tension);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function createLinearCurve(data: SplineData[], closed: boolean): LineCurve3 | null {
|
|
208
|
+
if (!data || data.length < 2) return null;
|
|
209
|
+
const points = data.map(knot => new Vector3(-knot.position.x, knot.position.y, knot.position.z));
|
|
210
|
+
if (closed) points.push(points[0]);
|
|
211
|
+
return new LineCurve3(points.at(0), points.at(1));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
// function createBezierCurve(data: SplineData[], closed: boolean): CubicBezierCurve3 | null {
|
|
216
|
+
// if (!data || data.length < 2) return null;
|
|
217
|
+
|
|
218
|
+
// for (let k = 0; k < data.length; k++) {
|
|
219
|
+
// const k0 = data[k];
|
|
220
|
+
// let nextIndex = k + 1;
|
|
221
|
+
// if (nextIndex >= data.length) {
|
|
222
|
+
// if (!closed) break;
|
|
223
|
+
// nextIndex = 0;
|
|
224
|
+
// }
|
|
225
|
+
// const k1 = data[nextIndex];
|
|
226
|
+
// // points
|
|
227
|
+
// const p0 = new Vector3(-k0.position.x, k0.position.y, k0.position.z);
|
|
228
|
+
// const p1 = new Vector3(-k1.position.x, k1.position.y, k1.position.z);
|
|
229
|
+
// // tangents
|
|
230
|
+
// const t0 = new Vector3(-k0.tangentOut.x, k0.tangentOut.y, k0.tangentOut.z);
|
|
231
|
+
// const t1 = new Vector3(-k1.tangentIn.x, k1.tangentIn.y, k1.tangentIn.z);
|
|
232
|
+
// // rotations
|
|
233
|
+
// // const q0 = k0.rotation;// new Quaternion(k0.rotation.value.x, k0.rotation.value.y, k0.rotation.value.z, k0.rotation.value.w);
|
|
234
|
+
// // const q1 = k1.rotation;// new Quaternion(k1.rotation.value.x, k1.rotation.value.y, k1.rotation.value.z, k1.rotation.value.w);
|
|
235
|
+
// // const a = new Vector3(0,1,0);
|
|
236
|
+
// // const angle = Math.PI*.5;
|
|
237
|
+
// // t0.sub(p0).applyQuaternion(q0).add(p0);
|
|
238
|
+
// // t1.sub(p1).applyQuaternion(q1).add(p1);
|
|
239
|
+
// t0.add(p0);
|
|
240
|
+
// // t0.applyQuaternion(q0);
|
|
241
|
+
// t1.add(p1);
|
|
242
|
+
// const curve = new CubicBezierCurve3(p0, t0, t1, p1);
|
|
243
|
+
// return curve;
|
|
244
|
+
// }
|
|
245
|
+
// return null;
|
|
246
|
+
// }
|
|
247
|
+
|
|
248
|
+
// class SplineCurve {
|
|
249
|
+
|
|
250
|
+
// private spline: Spline;
|
|
251
|
+
|
|
252
|
+
// constructor(spline: Spline) {
|
|
253
|
+
// this.spline = spline;
|
|
254
|
+
// }
|
|
255
|
+
|
|
256
|
+
// getPoints(num: number): Vector3[] {
|
|
257
|
+
// const points: Vector3[] = [];
|
|
258
|
+
// const samplePerKnot = num / this.spline.length;
|
|
259
|
+
// for (let k = 1; k < this.spline.length; k++) {
|
|
260
|
+
// const cur = this.spline[k];
|
|
261
|
+
// const prev = this.spline[k - 1];
|
|
262
|
+
|
|
263
|
+
// for (let i = 0; i < samplePerKnot; i++) {
|
|
264
|
+
// const t = i / (samplePerKnot - 1);
|
|
265
|
+
// console.log(CurveUtils);
|
|
266
|
+
// const x = this.interpolate(-prev.Position.x, -cur.Position.x, -prev.tangentOut.x, -cur.TangentIn.x, t);
|
|
267
|
+
// const y = this.interpolate(prev.Position.y, cur.Position.y, prev.tangentOut.y, cur.TangentIn.y, t);
|
|
268
|
+
// const z = this.interpolate(prev.Position.z, cur.Position.z, prev.tangentOut.z, cur.TangentIn.z, t);
|
|
269
|
+
// points.push(new Vector3(x, y, z));
|
|
270
|
+
// }
|
|
271
|
+
// }
|
|
272
|
+
|
|
273
|
+
// return points;
|
|
274
|
+
// }
|
|
275
|
+
|
|
276
|
+
// interpolate(p0, p1, p2, p3, t) {
|
|
277
|
+
|
|
278
|
+
// var v0 = (p2 - p0) * 0.5;
|
|
279
|
+
// var v1 = (p3 - p1) * 0.5;
|
|
280
|
+
// var t2 = t * t;
|
|
281
|
+
// var t3 = t * t2;
|
|
282
|
+
// return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (- 3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
|
|
283
|
+
// }
|
|
284
|
+
// }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Vector3 } from "three";
|
|
2
|
+
|
|
3
|
+
import { Mathf } from "../../engine/engine_math.js";
|
|
4
|
+
import { SplineContainer, SplineData } from "./Spline.js";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export namespace SplineUtils {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a SplineContainer from an array of points.
|
|
11
|
+
* @param positions The positions of the knots.
|
|
12
|
+
* @param closed Whether the spline is closed (the last knot connects to the first).
|
|
13
|
+
* @param tension The tension of the spline. 0 is no tension, 1 is high tension (straight lines between knots). Default is 0.75.
|
|
14
|
+
* @return The created SplineContainer component - add it to an Object3D to use it.
|
|
15
|
+
*/
|
|
16
|
+
export function createFromPoints(positions: Vector3[], closed: boolean = false, tension: number = .75): SplineContainer {
|
|
17
|
+
const spline = new SplineContainer();
|
|
18
|
+
const tangentFactor = 1 - Mathf.clamp(tension, 0, 1);
|
|
19
|
+
positions.forEach((pos, index) => {
|
|
20
|
+
const tangent = new Vector3();
|
|
21
|
+
if (index < positions.length - 1) tangent.subVectors(positions[index + 1], pos).normalize().multiplyScalar(tangentFactor);
|
|
22
|
+
else if (closed && positions.length > 1) tangent.subVectors(positions[0], pos).normalize().multiplyScalar(tangentFactor);
|
|
23
|
+
const knot = new SplineData();
|
|
24
|
+
knot.position.copy(pos);
|
|
25
|
+
knot.tangentIn.copy(tangent);
|
|
26
|
+
knot.tangentOut.copy(tangent);
|
|
27
|
+
spline.addKnot(knot);
|
|
28
|
+
});
|
|
29
|
+
spline.closed = closed;
|
|
30
|
+
return spline;
|
|
31
|
+
}
|
|
32
|
+
}
|