@gg-web-engine/core 0.0.48 → 0.0.49
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/dist/2d/models/shapes.d.ts +3 -1
- package/dist/3d/entities/controllers/input/car-keyboard-handling.controller.d.ts +1 -1
- package/dist/3d/entities/controllers/input/free-camera.controller.d.ts +23 -12
- package/dist/3d/entities/controllers/input/free-camera.controller.js +65 -44
- package/dist/3d/entities/controllers/input/gg-car-keyboard-handling.controller.d.ts +1 -1
- package/dist/3d/entities/controllers/input/orbit-camera.controller.d.ts +20 -1
- package/dist/3d/entities/controllers/input/orbit-camera.controller.js +27 -10
- package/dist/3d/models/shapes.d.ts +3 -1
- package/dist/base/index.d.ts +1 -0
- package/dist/base/index.js +1 -0
- package/dist/base/inputs/mouse.input.d.ts +1 -0
- package/dist/base/inputs/mouse.input.js +3 -0
- package/dist/base/pipes/gg-elastic.pipe.d.ts +5 -0
- package/dist/base/pipes/gg-elastic.pipe.js +44 -0
- package/package.json +1 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Point2 } from '../../base';
|
|
2
2
|
import { Body2DOptions } from './body-options';
|
|
3
3
|
export type Shape2DDescriptor = {
|
|
4
|
+
collisionMargin?: number;
|
|
5
|
+
} & ({
|
|
4
6
|
shape: 'SQUARE';
|
|
5
7
|
dimensions: Point2;
|
|
6
8
|
} | {
|
|
7
9
|
shape: 'CIRCLE';
|
|
8
10
|
radius: number;
|
|
9
|
-
};
|
|
11
|
+
});
|
|
10
12
|
export type BodyShape2DDescriptor = {
|
|
11
13
|
shape: Shape2DDescriptor;
|
|
12
14
|
body: Partial<Body2DOptions>;
|
|
@@ -12,7 +12,7 @@ export declare class CarKeyboardHandlingController extends IEntity {
|
|
|
12
12
|
protected readonly keyboard: KeyboardInput;
|
|
13
13
|
protected readonly options: CarKeyboardControllerOptions;
|
|
14
14
|
readonly tickOrder = TickOrder.INPUT_CONTROLLERS;
|
|
15
|
-
|
|
15
|
+
readonly directionsInput: DirectionKeyboardInput;
|
|
16
16
|
private _output$;
|
|
17
17
|
get output$(): Observable<CarHandlingOutput>;
|
|
18
18
|
constructor(keyboard: KeyboardInput, options?: CarKeyboardControllerOptions);
|
|
@@ -5,24 +5,35 @@ import { Renderer3dEntity } from '../../renderer-3d.entity';
|
|
|
5
5
|
*/
|
|
6
6
|
export type FreeCameraControllerOptions = {
|
|
7
7
|
/**
|
|
8
|
-
* A keymap for controlling camera movement, where each key corresponds to a movement direction.
|
|
8
|
+
* A keymap for controlling camera movement, where each key corresponds to a movement direction. 'wasd' by default
|
|
9
9
|
*/
|
|
10
10
|
keymap: DirectionKeyboardKeymap;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* The speed of camera movement in meters per second. 20 by default
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* The speed of camera movement.
|
|
17
|
-
*/
|
|
18
|
-
speed: number;
|
|
19
|
-
};
|
|
14
|
+
cameraLinearSpeed: number;
|
|
20
15
|
/**
|
|
21
|
-
*
|
|
16
|
+
* An elasticity factor for camera movement. 0 by default (no elastic motion)
|
|
17
|
+
*/
|
|
18
|
+
cameraMovementElasticity: number;
|
|
19
|
+
/**
|
|
20
|
+
* A linear speed multiplier when user holds shift key. 2.5 by default
|
|
21
|
+
*/
|
|
22
|
+
cameraBoostMultiplier: number;
|
|
23
|
+
/**
|
|
24
|
+
* The speed of camera rotation in radians per 1000px mouse movement. 1 by default
|
|
25
|
+
*/
|
|
26
|
+
cameraRotationSensitivity: number;
|
|
27
|
+
/**
|
|
28
|
+
* An elasticity factor for camera rotation. 0 by default (no elastic motion)
|
|
29
|
+
*/
|
|
30
|
+
cameraRotationElasticity: number;
|
|
31
|
+
/**
|
|
32
|
+
* Flag to ignore cursor movement if pointer was not locked. false by default
|
|
22
33
|
*/
|
|
23
34
|
ignoreMouseUnlessPointerLocked: boolean;
|
|
24
35
|
/**
|
|
25
|
-
* Flag to ignore keyboard events if pointer was not locked.
|
|
36
|
+
* Flag to ignore keyboard events if pointer was not locked. false by default
|
|
26
37
|
*/
|
|
27
38
|
ignoreKeyboardUnlessPointerLocked: boolean;
|
|
28
39
|
/**
|
|
@@ -41,11 +52,11 @@ export declare class FreeCameraController extends IEntity {
|
|
|
41
52
|
/**
|
|
42
53
|
* The mouse input controller used for camera rotation.
|
|
43
54
|
*/
|
|
44
|
-
|
|
55
|
+
readonly mouseInput: MouseInput;
|
|
45
56
|
/**
|
|
46
57
|
* The keyboard input controller used for camera movement.
|
|
47
58
|
*/
|
|
48
|
-
|
|
59
|
+
readonly directionsInput: DirectionKeyboardInput;
|
|
49
60
|
/**
|
|
50
61
|
* Creates a new FreeCameraInput instance.
|
|
51
62
|
* @param keyboard The keyboard input controller to use for camera movement.
|
|
@@ -7,11 +7,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { combineLatest, filter, takeUntil } from 'rxjs';
|
|
11
|
-
import { DirectionKeyboardInput, IEntity, MouseInput, Pnt2, Pnt3, Qtrn, TickOrder, } from '../../../../base';
|
|
10
|
+
import { BehaviorSubject, combineLatest, filter, takeUntil } from 'rxjs';
|
|
11
|
+
import { DirectionKeyboardInput, ggElastic, IEntity, MouseInput, Pnt2, Pnt3, Qtrn, TickOrder, } from '../../../../base';
|
|
12
|
+
import { map } from 'rxjs/operators';
|
|
12
13
|
const DEFAULT_FREE_CAMERA_CONTROLLER_OPTIONS = {
|
|
13
14
|
keymap: 'wasd',
|
|
14
|
-
|
|
15
|
+
cameraLinearSpeed: 20,
|
|
16
|
+
cameraMovementElasticity: 0,
|
|
17
|
+
cameraBoostMultiplier: 2.5,
|
|
18
|
+
cameraRotationSensitivity: 1,
|
|
19
|
+
cameraRotationElasticity: 0,
|
|
15
20
|
mouseOptions: {},
|
|
16
21
|
ignoreMouseUnlessPointerLocked: false,
|
|
17
22
|
ignoreKeyboardUnlessPointerLocked: false,
|
|
@@ -33,9 +38,9 @@ export class FreeCameraController extends IEntity {
|
|
|
33
38
|
this.tickOrder = TickOrder.INPUT_CONTROLLERS;
|
|
34
39
|
this.options = Object.assign(Object.assign({}, DEFAULT_FREE_CAMERA_CONTROLLER_OPTIONS), options);
|
|
35
40
|
if (options.mouseOptions) {
|
|
36
|
-
this.options.
|
|
41
|
+
this.options.mouseOptions = Object.assign(Object.assign({}, DEFAULT_FREE_CAMERA_CONTROLLER_OPTIONS.mouseOptions), options.mouseOptions);
|
|
37
42
|
}
|
|
38
|
-
this.mouseInput = new MouseInput(options.mouseOptions);
|
|
43
|
+
this.mouseInput = new MouseInput(this.options.mouseOptions);
|
|
39
44
|
this.directionsInput = new DirectionKeyboardInput(keyboard, this.options.keymap);
|
|
40
45
|
}
|
|
41
46
|
onSpawned(world) {
|
|
@@ -45,61 +50,77 @@ export class FreeCameraController extends IEntity {
|
|
|
45
50
|
return __awaiter(this, void 0, void 0, function* () {
|
|
46
51
|
yield _super.onSpawned.call(this, world);
|
|
47
52
|
// Subscribe to keyboard input for movement controls
|
|
48
|
-
let controls = { direction: {}, rest: [] };
|
|
49
53
|
const keys = ['KeyE', 'KeyQ'];
|
|
50
54
|
if (this.camera.camera.supportsFov) {
|
|
51
55
|
keys.push('KeyZ', 'KeyC');
|
|
52
56
|
}
|
|
53
57
|
keys.push('ShiftLeft');
|
|
54
|
-
this.directionsInput.output
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.pipe(takeUntil(this._onRemoved$))
|
|
59
|
-
.subscribe((d) => {
|
|
60
|
-
controls.rest = d;
|
|
61
|
-
});
|
|
62
|
-
// Subscribe to mouse input for camera rotation
|
|
63
|
-
let rotationDelta = Pnt2.O;
|
|
64
|
-
let isTouchScreen = MouseInput.isTouchDevice();
|
|
65
|
-
this.mouseInput.delta$
|
|
66
|
-
.pipe(takeUntil(this._onRemoved$), filter(() => isTouchScreen || !this.options.ignoreMouseUnlessPointerLocked || this.mouseInput.isPointerLocked))
|
|
67
|
-
.subscribe(delta => {
|
|
68
|
-
rotationDelta = Pnt2.add(rotationDelta, delta);
|
|
69
|
-
});
|
|
70
|
-
// Setup updating camera position and rotation based on input
|
|
71
|
-
this.camera.tick$.pipe(takeUntil(this._onRemoved$)).subscribe(() => {
|
|
72
|
-
let c = controls;
|
|
73
|
-
if (this.options.ignoreKeyboardUnlessPointerLocked && !this.mouseInput.isPointerLocked) {
|
|
74
|
-
c = { direction: {}, rest: [false, false, false, false, false] };
|
|
58
|
+
let controlsObs = combineLatest([this.directionsInput.output$, ...keys.map(c => this.keyboard.bind(c))]).pipe(takeUntil(this._onRemoved$), map(([direction, ...rest]) => {
|
|
59
|
+
let c = { direction: {}, rest: [] };
|
|
60
|
+
if (!this.options.ignoreKeyboardUnlessPointerLocked || this.mouseInput.isPointerLocked) {
|
|
61
|
+
c = { direction, rest };
|
|
75
62
|
}
|
|
76
|
-
let
|
|
63
|
+
let translate = Object.assign({}, Pnt3.O);
|
|
77
64
|
const [u, d, zo, zi, speedBoost] = c.rest;
|
|
78
65
|
if (c.direction.upDown !== undefined)
|
|
79
|
-
|
|
66
|
+
translate.z = c.direction.upDown ? -1 : 1;
|
|
80
67
|
if (c.direction.leftRight !== undefined)
|
|
81
|
-
|
|
68
|
+
translate.x = c.direction.leftRight ? -1 : 1;
|
|
82
69
|
if (u != d)
|
|
83
|
-
|
|
70
|
+
translate.y = d ? -1 : 1;
|
|
71
|
+
let cameraFovInc = 0;
|
|
84
72
|
if (zo != zi)
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
cameraFovInc = zo ? 1 : -1;
|
|
74
|
+
translate = Pnt3.norm(translate);
|
|
87
75
|
if (speedBoost) {
|
|
88
|
-
|
|
76
|
+
translate = Pnt3.scalarMult(translate, this.options.cameraBoostMultiplier);
|
|
89
77
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
78
|
+
return [translate, cameraFovInc];
|
|
79
|
+
}));
|
|
80
|
+
if (this.options.cameraMovementElasticity > 0) {
|
|
81
|
+
controlsObs = controlsObs.pipe(ggElastic(this.camera.tick$, this.options.cameraMovementElasticity, ([at, _], [bt, bf], f) => [Pnt3.lerp(at, bt, f), bf], ([at, af], [bt, bf]) => af == bf && Pnt3.dist(at, bt) < 0.001));
|
|
82
|
+
}
|
|
83
|
+
let translateVector = Pnt3.O;
|
|
84
|
+
let cameraFovInc = 0;
|
|
85
|
+
controlsObs.subscribe(([t, f]) => {
|
|
86
|
+
translateVector = t;
|
|
87
|
+
cameraFovInc = f;
|
|
88
|
+
});
|
|
89
|
+
// Subscribe to mouse input for camera rotation
|
|
90
|
+
const spherical = Pnt3.toSpherical(Pnt3.rot({ x: 0, y: 0, z: -1 }, this.camera.rotation));
|
|
91
|
+
let isTouchScreen = MouseInput.isTouchDevice();
|
|
92
|
+
let mouseDelta$ = this.mouseInput.delta$.pipe(takeUntil(this._onRemoved$), filter(() => isTouchScreen || !this.options.ignoreMouseUnlessPointerLocked || this.mouseInput.isPointerLocked));
|
|
93
|
+
if (this.options.cameraRotationElasticity > 0) {
|
|
94
|
+
const s$ = new BehaviorSubject(spherical);
|
|
95
|
+
mouseDelta$.subscribe(delta => {
|
|
96
|
+
const s = s$.getValue();
|
|
97
|
+
s$.next({
|
|
98
|
+
phi: Math.max(0.000001, Math.min(Math.PI - 0.000001, s.phi + (delta.y * this.options.cameraRotationSensitivity) / 1000)),
|
|
99
|
+
theta: s.theta - (delta.x * this.options.cameraRotationSensitivity) / 1000,
|
|
100
|
+
radius: 1,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
s$.pipe(takeUntil(this._onRemoved$), ggElastic(this.tick$, this.options.cameraRotationElasticity, (a, b, f) => ({ phi: a.phi + f * (b.phi - a.phi), theta: a.theta + f * (b.theta - a.theta), radius: 1 }), (a, b) => Pnt2.dist({ x: a.phi, y: a.theta }, { x: b.phi, y: b.theta }) < 0.0001)).subscribe(s => {
|
|
104
|
+
spherical.theta = s.theta;
|
|
105
|
+
spherical.phi = s.phi;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
mouseDelta$.subscribe(delta => {
|
|
110
|
+
spherical.theta -= (delta.x * this.options.cameraRotationSensitivity) / 1000;
|
|
111
|
+
spherical.phi += (delta.y * this.options.cameraRotationSensitivity) / 1000;
|
|
95
112
|
spherical.phi = Math.max(0.000001, Math.min(Math.PI - 0.000001, spherical.phi));
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// Setup updating camera position and rotation based on input
|
|
116
|
+
this.camera.tick$.pipe(takeUntil(this._onRemoved$)).subscribe(([_, delta]) => {
|
|
117
|
+
this.camera.camera.fov += cameraFovInc;
|
|
118
|
+
this.camera.position = Pnt3.add(this.camera.position, Pnt3.rot(Pnt3.scalarMult(translateVector, (this.options.cameraLinearSpeed * delta) / 1000), this.camera.rotation));
|
|
119
|
+
this.camera.rotation = Qtrn.lookAt(this.camera.position, Pnt3.add(this.camera.position, Pnt3.fromSpherical(spherical)));
|
|
99
120
|
});
|
|
100
121
|
// start input
|
|
101
|
-
|
|
102
|
-
|
|
122
|
+
this.mouseInput.start();
|
|
123
|
+
this.directionsInput.start();
|
|
103
124
|
});
|
|
104
125
|
}
|
|
105
126
|
onRemoved() {
|
|
@@ -11,7 +11,7 @@ export declare class GgCarKeyboardHandlingController extends IEntity {
|
|
|
11
11
|
car: GgCarEntity | null;
|
|
12
12
|
protected readonly options: GgCarKeyboardControllerOptions;
|
|
13
13
|
readonly tickOrder = TickOrder.INPUT_CONTROLLERS;
|
|
14
|
-
|
|
14
|
+
readonly carHandlingInput: CarKeyboardHandlingController;
|
|
15
15
|
switchingGearsEnabled: boolean;
|
|
16
16
|
constructor(keyboard: KeyboardInput, car: GgCarEntity | null, options?: GgCarKeyboardControllerOptions);
|
|
17
17
|
onSpawned(world: GgWorld<any, any>): Promise<void>;
|
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import { GgWorld, IEntity, MouseInput, MouseInputOptions, MutableSpherical, Point3, TickOrder } from '../../../../base';
|
|
2
2
|
import { Renderer3dEntity } from '../../renderer-3d.entity';
|
|
3
3
|
export type OrbitCameraControllerOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Options for configuring mouse input.
|
|
6
|
+
*/
|
|
4
7
|
mouseOptions: Partial<MouseInputOptions>;
|
|
8
|
+
/**
|
|
9
|
+
* Orbiting options. false disables orbiting, sensitivity fields are the speed in radians per 1000px mouse movement. 1 by default
|
|
10
|
+
*/
|
|
5
11
|
orbiting: {
|
|
6
12
|
sensitivityX: number;
|
|
7
13
|
sensitivityY: number;
|
|
8
14
|
} | false;
|
|
15
|
+
/**
|
|
16
|
+
* An elasticity factor for orbiting. 0 by default (no elastic motion)
|
|
17
|
+
*/
|
|
18
|
+
orbitingElasticity: number;
|
|
19
|
+
/**
|
|
20
|
+
* Zooming options. false disables zooming. Enabled by default
|
|
21
|
+
*/
|
|
9
22
|
zooming: {
|
|
10
23
|
sensitivity: number;
|
|
11
24
|
} | false;
|
|
25
|
+
/**
|
|
26
|
+
* Panning options. false disables panning. Enabled by default
|
|
27
|
+
*/
|
|
12
28
|
panning: {
|
|
13
29
|
sensitivityX: number;
|
|
14
30
|
sensitivityY: number;
|
|
15
31
|
} | false;
|
|
32
|
+
/**
|
|
33
|
+
* Dollying options. false disables dollying. Enabled by default
|
|
34
|
+
*/
|
|
16
35
|
dollying: {
|
|
17
36
|
sensitivity: number;
|
|
18
37
|
} | false;
|
|
@@ -21,7 +40,7 @@ export declare class OrbitCameraController extends IEntity {
|
|
|
21
40
|
protected readonly camera: Renderer3dEntity;
|
|
22
41
|
readonly tickOrder = TickOrder.INPUT_CONTROLLERS;
|
|
23
42
|
protected readonly options: OrbitCameraControllerOptions;
|
|
24
|
-
|
|
43
|
+
readonly mouseInput: MouseInput;
|
|
25
44
|
protected spherical: MutableSpherical;
|
|
26
45
|
target: Point3;
|
|
27
46
|
get radius(): number;
|
|
@@ -7,12 +7,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { IEntity, MouseInput, MouseInputState, Pnt3, Qtrn, TickOrder, } from '../../../../base';
|
|
11
|
-
import { filter, takeUntil } from 'rxjs';
|
|
10
|
+
import { ggElastic, IEntity, MouseInput, MouseInputState, Pnt2, Pnt3, Qtrn, TickOrder, } from '../../../../base';
|
|
11
|
+
import { BehaviorSubject, filter, takeUntil } from 'rxjs';
|
|
12
12
|
import { map } from 'rxjs/operators';
|
|
13
13
|
const DEFAULT_OPTIONS = {
|
|
14
14
|
mouseOptions: {},
|
|
15
|
-
orbiting: { sensitivityX: 1, sensitivityY: 1 },
|
|
15
|
+
orbiting: { sensitivityX: 1, sensitivityY: 1 },
|
|
16
|
+
orbitingElasticity: 0,
|
|
16
17
|
zooming: { sensitivity: 1 },
|
|
17
18
|
panning: { sensitivityX: 1, sensitivityY: 1 },
|
|
18
19
|
dollying: { sensitivity: 1 },
|
|
@@ -53,13 +54,29 @@ export class OrbitCameraController extends IEntity {
|
|
|
53
54
|
yield _super.onSpawned.call(this, world);
|
|
54
55
|
this.spherical = Pnt3.toSpherical(Pnt3.sub(this.camera.position, this.target));
|
|
55
56
|
if (this.options.orbiting) {
|
|
56
|
-
this.mouseInput.delta$
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
let mouseDelta$ = this.mouseInput.delta$.pipe(takeUntil(this._onRemoved$), filter(() => this.mouseInput.state == MouseInputState.DRAG));
|
|
58
|
+
if (this.options.orbitingElasticity > 0) {
|
|
59
|
+
const s$ = new BehaviorSubject(this.spherical);
|
|
60
|
+
mouseDelta$.subscribe(delta => {
|
|
61
|
+
const s = s$.getValue();
|
|
62
|
+
s$.next({
|
|
63
|
+
phi: Math.max(0.000001, Math.min(Math.PI - 0.000001, s.phi - (delta.y * this.options.orbiting.sensitivityY) / 1000)),
|
|
64
|
+
theta: s.theta - (delta.x * this.options.orbiting.sensitivityX) / 1000,
|
|
65
|
+
radius: 1,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
s$.pipe(takeUntil(this._onRemoved$), ggElastic(this.tick$, this.options.orbitingElasticity, (a, b, f) => ({ phi: a.phi + f * (b.phi - a.phi), theta: a.theta + f * (b.theta - a.theta), radius: 1 }), (a, b) => Pnt2.dist({ x: a.phi, y: a.theta }, { x: b.phi, y: b.theta }) < 0.0001)).subscribe(s => {
|
|
69
|
+
this.spherical.theta = s.theta;
|
|
70
|
+
this.spherical.phi = s.phi;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
mouseDelta$.subscribe(delta => {
|
|
75
|
+
this.spherical.theta -= (delta.x * this.options.orbiting.sensitivityX) / 1000;
|
|
76
|
+
this.spherical.phi -= (delta.y * this.options.orbiting.sensitivityY) / 1000;
|
|
77
|
+
this.spherical.phi = Math.max(0.000001, Math.min(Math.PI - 0.000001, this.spherical.phi));
|
|
78
|
+
});
|
|
79
|
+
}
|
|
63
80
|
}
|
|
64
81
|
if (this.options.zooming) {
|
|
65
82
|
this.mouseInput.wheel$.pipe(takeUntil(this._onRemoved$)).subscribe(delta => {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Point2, Point3, Point4 } from '../../base';
|
|
2
2
|
import { Body3DOptions } from './body-options';
|
|
3
3
|
export type Shape3DDescriptor = {
|
|
4
|
+
collisionMargin?: number;
|
|
5
|
+
} & ({
|
|
4
6
|
shape: 'PLANE';
|
|
5
7
|
} | {
|
|
6
8
|
shape: 'BOX';
|
|
@@ -30,7 +32,7 @@ export type Shape3DDescriptor = {
|
|
|
30
32
|
shape: 'MESH';
|
|
31
33
|
vertices: Point3[];
|
|
32
34
|
faces: [number, number, number][];
|
|
33
|
-
};
|
|
35
|
+
});
|
|
34
36
|
export type Shape3DMeshDescriptor = {
|
|
35
37
|
shape: 'PLANE';
|
|
36
38
|
dimensions?: Point2;
|
package/dist/base/index.d.ts
CHANGED
package/dist/base/index.js
CHANGED
|
@@ -59,6 +59,7 @@ export declare class MouseInput extends IInput<[], [unlockPointer?: boolean]> {
|
|
|
59
59
|
private _wheel$;
|
|
60
60
|
get wheel$(): Observable<number>;
|
|
61
61
|
get isPointerLocked(): boolean;
|
|
62
|
+
get isPointerLocked$(): Observable<boolean>;
|
|
62
63
|
private readonly options;
|
|
63
64
|
private _delta$;
|
|
64
65
|
private _position$;
|
|
@@ -67,6 +67,9 @@ export class MouseInput extends IInput {
|
|
|
67
67
|
get isPointerLocked() {
|
|
68
68
|
return !!document.pointerLockElement;
|
|
69
69
|
}
|
|
70
|
+
get isPointerLocked$() {
|
|
71
|
+
return fromEvent(document, 'pointerlockchange').pipe(map(() => this.isPointerLocked));
|
|
72
|
+
}
|
|
70
73
|
get state() {
|
|
71
74
|
return this._state$.getValue();
|
|
72
75
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Observable, OperatorFunction } from 'rxjs';
|
|
2
|
+
export declare function ggElastic<T>(tick$: Observable<[number, number]>, // [elapsed, delta]
|
|
3
|
+
elasticity: number, // Elasticity parameter
|
|
4
|
+
mix: (a: T, b: T, factor: number) => T, // Mixing function
|
|
5
|
+
equals: (a: T, b: T) => boolean): OperatorFunction<T, T>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { filter, map, scan, switchMap } from 'rxjs';
|
|
2
|
+
export function ggElastic(tick$, // [elapsed, delta]
|
|
3
|
+
elasticity, // Elasticity parameter
|
|
4
|
+
mix, // Mixing function
|
|
5
|
+
equals) {
|
|
6
|
+
return (source$) => {
|
|
7
|
+
return source$.pipe(
|
|
8
|
+
// Track the target value
|
|
9
|
+
scan((acc, newValue) => {
|
|
10
|
+
var _a;
|
|
11
|
+
return ({
|
|
12
|
+
targetValue: newValue,
|
|
13
|
+
currentValue: (_a = acc.currentValue) !== null && _a !== void 0 ? _a : newValue, // Initialize to the first value on startup
|
|
14
|
+
hasReachedTarget: false, // Reset reach status on new target
|
|
15
|
+
});
|
|
16
|
+
}, { targetValue: null, currentValue: null, hasReachedTarget: false }),
|
|
17
|
+
// Switch to using tick$ for timing, emitting interpolated values with each tick
|
|
18
|
+
switchMap(state => tick$.pipe(map(([_, delta]) => {
|
|
19
|
+
if (state.targetValue === null || state.currentValue === null) {
|
|
20
|
+
return state.currentValue; // If we have no values, output nothing
|
|
21
|
+
}
|
|
22
|
+
// Skip processing if we’ve reached the target and stop emitting
|
|
23
|
+
if (state.hasReachedTarget) {
|
|
24
|
+
return undefined; // Stop emitting by returning `undefined`
|
|
25
|
+
}
|
|
26
|
+
// Adjust factor based on elasticity and delta time
|
|
27
|
+
const factor = 1 - Math.exp(-delta / elasticity);
|
|
28
|
+
// Blend halfway towards the target on each tick
|
|
29
|
+
const newInterpolatedValue = mix(state.currentValue, state.targetValue, factor);
|
|
30
|
+
// Update the currentValue to the new interpolated position
|
|
31
|
+
state.currentValue = newInterpolatedValue;
|
|
32
|
+
// Check if the interpolated value is "close enough" to the target
|
|
33
|
+
if (equals(newInterpolatedValue, state.targetValue)) {
|
|
34
|
+
state.hasReachedTarget = true; // Mark as reached
|
|
35
|
+
return state.targetValue; // Emit exact target value
|
|
36
|
+
}
|
|
37
|
+
return newInterpolatedValue;
|
|
38
|
+
}))),
|
|
39
|
+
// Filter out `undefined` emissions (to stop emitting when settled)
|
|
40
|
+
map(value => (value !== undefined ? value : null)),
|
|
41
|
+
// Remove `null` values (after stopping)
|
|
42
|
+
filter(value => value !== null));
|
|
43
|
+
};
|
|
44
|
+
}
|