@gg-web-engine/core 0.0.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/.prettierrc +8 -0
- package/README.md +8 -0
- package/blender_exporter/build_blender_scene.py +180 -0
- package/blender_exporter/spherical_to_cube_texture.py +116 -0
- package/dist/2d/entities/gg-2d-entity.d.ts +20 -0
- package/dist/2d/entities/gg-2d-entity.js +84 -0
- package/dist/2d/entities/gg-2d-trigger.entity.d.ts +10 -0
- package/dist/2d/entities/gg-2d-trigger.entity.js +17 -0
- package/dist/2d/entities/gg-positionable-2d-entity.d.ts +7 -0
- package/dist/2d/entities/gg-positionable-2d-entity.js +16 -0
- package/dist/2d/factories.d.ts +18 -0
- package/dist/2d/factories.js +13 -0
- package/dist/2d/gg-2d-world.d.ts +9 -0
- package/dist/2d/gg-2d-world.js +34 -0
- package/dist/2d/interfaces.d.ts +26 -0
- package/dist/2d/interfaces.js +7 -0
- package/dist/2d/models/body-options.d.ts +3 -0
- package/dist/2d/models/body-options.js +2 -0
- package/dist/2d/models/shapes.d.ts +13 -0
- package/dist/2d/models/shapes.js +2 -0
- package/dist/3d/controllers/car-keyboard.controller.d.ts +25 -0
- package/dist/3d/controllers/car-keyboard.controller.js +98 -0
- package/dist/3d/controllers/free-camera.controller.d.ts +23 -0
- package/dist/3d/controllers/free-camera.controller.js +79 -0
- package/dist/3d/entities/controllers/entity-motion.controller.d.ts +32 -0
- package/dist/3d/entities/controllers/entity-motion.controller.js +100 -0
- package/dist/3d/entities/gg-3d-camera.entity.d.ts +7 -0
- package/dist/3d/entities/gg-3d-camera.entity.js +12 -0
- package/dist/3d/entities/gg-3d-entity.d.ts +25 -0
- package/dist/3d/entities/gg-3d-entity.js +108 -0
- package/dist/3d/entities/gg-3d-map-graph.entity.d.ts +51 -0
- package/dist/3d/entities/gg-3d-map-graph.entity.js +164 -0
- package/dist/3d/entities/gg-3d-raycast-vehicle.entity.d.ts +107 -0
- package/dist/3d/entities/gg-3d-raycast-vehicle.entity.js +372 -0
- package/dist/3d/entities/gg-3d-trigger.entity.d.ts +10 -0
- package/dist/3d/entities/gg-3d-trigger.entity.js +21 -0
- package/dist/3d/entities/gg-positionable-3d-entity.d.ts +11 -0
- package/dist/3d/entities/gg-positionable-3d-entity.js +27 -0
- package/dist/3d/factories.d.ts +21 -0
- package/dist/3d/factories.js +22 -0
- package/dist/3d/gg-3d-world.d.ts +11 -0
- package/dist/3d/gg-3d-world.js +36 -0
- package/dist/3d/interfaces.d.ts +65 -0
- package/dist/3d/interfaces.js +40 -0
- package/dist/3d/loader.d.ts +40 -0
- package/dist/3d/loader.js +129 -0
- package/dist/3d/models/body-options.d.ts +3 -0
- package/dist/3d/models/body-options.js +2 -0
- package/dist/3d/models/gg-meta.d.ts +22 -0
- package/dist/3d/models/gg-meta.js +2 -0
- package/dist/3d/models/shapes.d.ts +35 -0
- package/dist/3d/models/shapes.js +2 -0
- package/dist/base/clock.d.ts +26 -0
- package/dist/base/clock.js +82 -0
- package/dist/base/controllers/common.d.ts +8 -0
- package/dist/base/controllers/common.js +32 -0
- package/dist/base/controllers/i-controller.d.ts +4 -0
- package/dist/base/controllers/i-controller.js +2 -0
- package/dist/base/controllers/keyboard.controller.d.ts +16 -0
- package/dist/base/controllers/keyboard.controller.js +109 -0
- package/dist/base/controllers/mouse.controller.d.ts +24 -0
- package/dist/base/controllers/mouse.controller.js +68 -0
- package/dist/base/data-structures/graph.d.ts +20 -0
- package/dist/base/data-structures/graph.js +118 -0
- package/dist/base/entities/base-gg-renderer.d.ts +28 -0
- package/dist/base/entities/base-gg-renderer.js +49 -0
- package/dist/base/entities/gg-entity.d.ts +14 -0
- package/dist/base/entities/gg-entity.js +55 -0
- package/dist/base/entities/gg-positionable-entity.d.ts +23 -0
- package/dist/base/entities/gg-positionable-entity.js +47 -0
- package/dist/base/entities/inline-controller.d.ts +3 -0
- package/dist/base/entities/inline-controller.js +25 -0
- package/dist/base/entities/interfaces/i-tick-listener.d.ts +7 -0
- package/dist/base/entities/interfaces/i-tick-listener.js +6 -0
- package/dist/base/gg-static.d.ts +9 -0
- package/dist/base/gg-static.js +40 -0
- package/dist/base/gg-viewport-manager.d.ts +14 -0
- package/dist/base/gg-viewport-manager.js +125 -0
- package/dist/base/gg-viewport.d.ts +23 -0
- package/dist/base/gg-viewport.js +123 -0
- package/dist/base/gg-world.d.ts +32 -0
- package/dist/base/gg-world.js +172 -0
- package/dist/base/interfaces/gg-body.d.ts +15 -0
- package/dist/base/interfaces/gg-body.js +2 -0
- package/dist/base/interfaces/gg-debug-physics-drawer.d.ts +7 -0
- package/dist/base/interfaces/gg-debug-physics-drawer.js +2 -0
- package/dist/base/interfaces/gg-object.d.ts +16 -0
- package/dist/base/interfaces/gg-object.js +2 -0
- package/dist/base/interfaces/gg-physics-world.d.ts +18 -0
- package/dist/base/interfaces/gg-physics-world.js +2 -0
- package/dist/base/interfaces/gg-trigger.d.ts +8 -0
- package/dist/base/interfaces/gg-trigger.js +2 -0
- package/dist/base/interfaces/gg-visual-scene.d.ts +9 -0
- package/dist/base/interfaces/gg-visual-scene.js +2 -0
- package/dist/base/math/box.d.ts +5 -0
- package/dist/base/math/box.js +21 -0
- package/dist/base/math/matrix4.d.ts +5 -0
- package/dist/base/math/matrix4.js +33 -0
- package/dist/base/math/numbers.d.ts +2 -0
- package/dist/base/math/numbers.js +14 -0
- package/dist/base/math/point2.d.ts +15 -0
- package/dist/base/math/point2.js +40 -0
- package/dist/base/math/point3.d.ts +25 -0
- package/dist/base/math/point3.js +104 -0
- package/dist/base/math/quaternion.d.ts +28 -0
- package/dist/base/math/quaternion.js +145 -0
- package/dist/base/models/axis-directions.d.ts +3 -0
- package/dist/base/models/axis-directions.js +2 -0
- package/dist/base/models/body-options.d.ts +6 -0
- package/dist/base/models/body-options.js +2 -0
- package/dist/base/models/geometry-nodes.d.ts +7 -0
- package/dist/base/models/geometry-nodes.js +2 -0
- package/dist/base/models/points.d.ts +15 -0
- package/dist/base/models/points.js +2 -0
- package/dist/base/ui/gg-console.ui.d.ts +21 -0
- package/dist/base/ui/gg-console.ui.js +162 -0
- package/dist/base/ui/gg-debugger.ui.d.ts +10 -0
- package/dist/base/ui/gg-debugger.ui.js +68 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +67 -0
- package/package.json +64 -0
- package/src/2d/entities/gg-2d-entity.ts +86 -0
- package/src/2d/entities/gg-2d-trigger.entity.ts +17 -0
- package/src/2d/entities/gg-positionable-2d-entity.ts +16 -0
- package/src/2d/factories.ts +20 -0
- package/src/2d/gg-2d-world.ts +32 -0
- package/src/2d/interfaces.ts +31 -0
- package/src/2d/models/body-options.ts +3 -0
- package/src/2d/models/shapes.ts +7 -0
- package/src/3d/controllers/car-keyboard.controller.ts +128 -0
- package/src/3d/controllers/free-camera.controller.ts +86 -0
- package/src/3d/entities/controllers/entity-motion.controller.ts +122 -0
- package/src/3d/entities/gg-3d-camera.entity.ts +8 -0
- package/src/3d/entities/gg-3d-entity.ts +116 -0
- package/src/3d/entities/gg-3d-map-graph.entity.ts +204 -0
- package/src/3d/entities/gg-3d-raycast-vehicle.entity.ts +468 -0
- package/src/3d/entities/gg-3d-trigger.entity.ts +22 -0
- package/src/3d/entities/gg-positionable-3d-entity.ts +31 -0
- package/src/3d/factories.ts +29 -0
- package/src/3d/gg-3d-world.ts +36 -0
- package/src/3d/interfaces.ts +96 -0
- package/src/3d/loader.ts +165 -0
- package/src/3d/models/body-options.ts +3 -0
- package/src/3d/models/gg-meta.ts +12 -0
- package/src/3d/models/shapes.ts +13 -0
- package/src/base/clock.ts +97 -0
- package/src/base/controllers/common.ts +37 -0
- package/src/base/controllers/i-controller.ts +5 -0
- package/src/base/controllers/keyboard.controller.ts +109 -0
- package/src/base/controllers/mouse.controller.ts +73 -0
- package/src/base/data-structures/graph.ts +125 -0
- package/src/base/entities/base-gg-renderer.ts +77 -0
- package/src/base/entities/gg-entity.ts +61 -0
- package/src/base/entities/gg-positionable-entity.ts +64 -0
- package/src/base/entities/inline-controller.ts +31 -0
- package/src/base/entities/interfaces/i-tick-listener.ts +15 -0
- package/src/base/gg-static.ts +30 -0
- package/src/base/gg-viewport-manager.ts +122 -0
- package/src/base/gg-viewport.ts +159 -0
- package/src/base/gg-world.ts +198 -0
- package/src/base/interfaces/gg-body.ts +23 -0
- package/src/base/interfaces/gg-debug-physics-drawer.ts +10 -0
- package/src/base/interfaces/gg-object.ts +26 -0
- package/src/base/interfaces/gg-physics-world.ts +24 -0
- package/src/base/interfaces/gg-trigger.ts +10 -0
- package/src/base/interfaces/gg-visual-scene.ts +11 -0
- package/src/base/math/box.ts +19 -0
- package/src/base/math/matrix4.ts +29 -0
- package/src/base/math/numbers.ts +10 -0
- package/src/base/math/point2.ts +43 -0
- package/src/base/math/point3.ts +119 -0
- package/src/base/math/quaternion.ts +172 -0
- package/src/base/models/axis-directions.ts +3 -0
- package/src/base/models/body-options.ts +6 -0
- package/src/base/models/geometry-nodes.ts +5 -0
- package/src/base/models/points.ts +3 -0
- package/src/base/ui/gg-console.ui.ts +165 -0
- package/src/base/ui/gg-debugger.ui.ts +69 -0
- package/src/index.ts +53 -0
- package/test/base/data-structures/graph.spec.ts +76 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { IController } from '../../base/controllers/i-controller';
|
|
2
|
+
import { combineLatest, Subject, takeUntil } from 'rxjs';
|
|
3
|
+
import { MouseController, MouseControllerOptions } from '../../base/controllers/mouse.controller';
|
|
4
|
+
import { Pnt3 } from '../../base/math/point3';
|
|
5
|
+
import { Gg3dCameraEntity } from '../entities/gg-3d-camera.entity';
|
|
6
|
+
import { KeyboardController } from '../../base/controllers/keyboard.controller';
|
|
7
|
+
import { Pnt2 } from '../../base/math/point2';
|
|
8
|
+
import { Point2 } from '../../base/models/points';
|
|
9
|
+
import { Qtrn } from '../../base/math/quaternion';
|
|
10
|
+
import { bindDirectionKeys, DirectionKeymap, DirectionOutput } from '../../base/controllers/common';
|
|
11
|
+
|
|
12
|
+
type FreeCameraControllerOptions = {
|
|
13
|
+
keymap: DirectionKeymap;
|
|
14
|
+
movementOptions: {
|
|
15
|
+
speed: number;
|
|
16
|
+
};
|
|
17
|
+
mouseOptions: MouseControllerOptions;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class FreeCameraController implements IController {
|
|
21
|
+
private readonly mController: MouseController;
|
|
22
|
+
private readonly stop$: Subject<void> = new Subject<void>();
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
private readonly keyboardController: KeyboardController,
|
|
26
|
+
private readonly camera: Gg3dCameraEntity,
|
|
27
|
+
private readonly options: FreeCameraControllerOptions = {
|
|
28
|
+
keymap: 'wasd',
|
|
29
|
+
movementOptions: { speed: 0.5 },
|
|
30
|
+
mouseOptions: {},
|
|
31
|
+
},
|
|
32
|
+
) {
|
|
33
|
+
this.mController = new MouseController(options.mouseOptions);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async start(): Promise<void> {
|
|
37
|
+
let controls: { direction: DirectionOutput; rest: boolean[] } = { direction: {}, rest: [] };
|
|
38
|
+
const keys = ['KeyE', 'KeyQ'];
|
|
39
|
+
if (this.camera.object3D.supportsFov) {
|
|
40
|
+
keys.push('KeyZ', 'KeyC');
|
|
41
|
+
}
|
|
42
|
+
bindDirectionKeys(this.keyboardController, this.options.keymap)
|
|
43
|
+
.pipe(takeUntil(this.stop$))
|
|
44
|
+
.subscribe(d => {
|
|
45
|
+
controls.direction = d;
|
|
46
|
+
});
|
|
47
|
+
combineLatest(keys.map(c => this.keyboardController.bind(c)))
|
|
48
|
+
.pipe(takeUntil(this.stop$))
|
|
49
|
+
.subscribe((d: boolean[]) => {
|
|
50
|
+
controls.rest = d;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let rotationDelta: Point2 = { x: 0, y: 0 };
|
|
54
|
+
this.mController.delta$.pipe(takeUntil(this.stop$)).subscribe(delta => {
|
|
55
|
+
rotationDelta = Pnt2.add(rotationDelta, delta);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.camera.tick$.pipe(takeUntil(this.stop$)).subscribe(() => {
|
|
59
|
+
let translateVector = { x: 0, y: 0, z: 0 };
|
|
60
|
+
const [u, d, zo, zi] = controls.rest;
|
|
61
|
+
if (controls.direction.upDown !== undefined) translateVector.z = controls.direction.upDown ? -1 : 1;
|
|
62
|
+
if (controls.direction.leftRight !== undefined) translateVector.x = controls.direction.leftRight ? -1 : 1;
|
|
63
|
+
if (u != d) translateVector.y = d ? -1 : 1;
|
|
64
|
+
if (zo != zi) this.camera.object3D.fov += zo ? 1 : -1;
|
|
65
|
+
this.camera.position = Pnt3.add(
|
|
66
|
+
this.camera.position,
|
|
67
|
+
Pnt3.rot(Pnt3.scalarMult(Pnt3.norm(translateVector), this.options.movementOptions.speed), this.camera.rotation),
|
|
68
|
+
);
|
|
69
|
+
if (rotationDelta.x != 0 || rotationDelta.y != 0) {
|
|
70
|
+
this.camera.rotation = Qtrn.combineRotations(
|
|
71
|
+
Qtrn.fromAngle({ x: 0, y: 0, z: 1 }, -rotationDelta.x / 300),
|
|
72
|
+
this.camera.rotation,
|
|
73
|
+
Qtrn.fromAngle({ x: 1, y: 0, z: 0 }, -rotationDelta.y / 300),
|
|
74
|
+
);
|
|
75
|
+
rotationDelta = { x: 0, y: 0 };
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await this.mController.start();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async stop(unlockPointer: boolean = true): Promise<void> {
|
|
83
|
+
this.stop$.next();
|
|
84
|
+
await this.mController.stop(unlockPointer);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { GgEntity } from '../../../base/entities/gg-entity';
|
|
2
|
+
import { ITickListener } from '../../../base/entities/interfaces/i-tick-listener';
|
|
3
|
+
import { Subject, Subscription, takeUntil } from 'rxjs';
|
|
4
|
+
import { Gg3dWorld } from '../../gg-3d-world';
|
|
5
|
+
import { GgPositionable3dEntity } from '../gg-positionable-3d-entity';
|
|
6
|
+
import { Point3, Point4 } from '../../../base/models/points';
|
|
7
|
+
import { Pnt3 } from '../../../base/math/point3';
|
|
8
|
+
import { Qtrn } from '../../../base/math/quaternion';
|
|
9
|
+
|
|
10
|
+
export type MotionControlFuncReturn = { position: Point3; rotation: Point4; customParameters: { [key: string]: any } };
|
|
11
|
+
export type MotionControlFunction = (delta: number) => MotionControlFuncReturn;
|
|
12
|
+
|
|
13
|
+
export class EntityMotionController extends GgEntity implements ITickListener {
|
|
14
|
+
get motionControlFunction(): MotionControlFunction {
|
|
15
|
+
return this._motionControlFunction;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set motionControlFunction(value: MotionControlFunction) {
|
|
19
|
+
if (this.currentTransition) {
|
|
20
|
+
this.currentTransition.unsubscribe();
|
|
21
|
+
this.currentTransition = null;
|
|
22
|
+
}
|
|
23
|
+
this._motionControlFunction = value;
|
|
24
|
+
}
|
|
25
|
+
public readonly tick$: Subject<[number, number]> = new Subject<[number, number]>();
|
|
26
|
+
|
|
27
|
+
protected readonly removed$: Subject<void> = new Subject<void>();
|
|
28
|
+
public readonly tickOrder: number = 750;
|
|
29
|
+
|
|
30
|
+
constructor(
|
|
31
|
+
public target: GgPositionable3dEntity,
|
|
32
|
+
protected _motionControlFunction: MotionControlFunction,
|
|
33
|
+
public customParametersHandleFunc: (target: GgPositionable3dEntity, params: any) => void = () => {},
|
|
34
|
+
) {
|
|
35
|
+
super();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected lastValue: MotionControlFuncReturn | undefined;
|
|
39
|
+
private currentTransition: Subscription | null = null;
|
|
40
|
+
|
|
41
|
+
// smoothly set controller function, animate from some determined state
|
|
42
|
+
transitFromStaticState(
|
|
43
|
+
state: MotionControlFuncReturn,
|
|
44
|
+
newFunc: MotionControlFunction,
|
|
45
|
+
transitionDuration: number,
|
|
46
|
+
easing: (t: number) => number = x => x,
|
|
47
|
+
customParametersLerpFunc: (a: any, b: any, t: number) => any = (a, b, t) => b,
|
|
48
|
+
) {
|
|
49
|
+
this.lastValue = state;
|
|
50
|
+
this._motionControlFunction = () => state;
|
|
51
|
+
return this.transitControlFunction(newFunc, transitionDuration, easing, customParametersLerpFunc);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// smoothly change controller function
|
|
55
|
+
transitControlFunction(
|
|
56
|
+
newFunc: MotionControlFunction,
|
|
57
|
+
transitionDuration: number,
|
|
58
|
+
easing: (t: number) => number = x => x,
|
|
59
|
+
customParametersLerpFunc: (a: any, b: any, t: number) => any = (a, b, t) => b,
|
|
60
|
+
) {
|
|
61
|
+
let oldControlFunc: MotionControlFunction;
|
|
62
|
+
if (this.currentTransition) {
|
|
63
|
+
this.currentTransition.unsubscribe();
|
|
64
|
+
this.currentTransition = null;
|
|
65
|
+
if (this.lastValue) {
|
|
66
|
+
oldControlFunc = () => this.lastValue!;
|
|
67
|
+
} else {
|
|
68
|
+
this._motionControlFunction = newFunc;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
oldControlFunc = this._motionControlFunction;
|
|
73
|
+
}
|
|
74
|
+
let k = 0;
|
|
75
|
+
this._motionControlFunction = delta => {
|
|
76
|
+
const oldRes = oldControlFunc(delta);
|
|
77
|
+
const newRes = newFunc(delta);
|
|
78
|
+
return {
|
|
79
|
+
customParameters: customParametersLerpFunc(oldRes.customParameters, newRes.customParameters, k),
|
|
80
|
+
position: Pnt3.lerp(oldRes.position, newRes.position, k),
|
|
81
|
+
rotation: Qtrn.slerp(oldRes.rotation, newRes.rotation, k),
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
let startTime: number | undefined;
|
|
85
|
+
this.currentTransition = this.tick$.pipe(takeUntil(this.removed$)).subscribe(([elapsed, _]) => {
|
|
86
|
+
if (startTime === undefined) {
|
|
87
|
+
startTime = elapsed;
|
|
88
|
+
} else {
|
|
89
|
+
k = (elapsed - startTime) / transitionDuration;
|
|
90
|
+
if (k >= 1) {
|
|
91
|
+
k = 1;
|
|
92
|
+
this.currentTransition!.unsubscribe();
|
|
93
|
+
this.currentTransition = null;
|
|
94
|
+
this._motionControlFunction = newFunc;
|
|
95
|
+
} else {
|
|
96
|
+
k = easing(k);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
onSpawned(world: Gg3dWorld) {
|
|
103
|
+
super.onSpawned(world);
|
|
104
|
+
this.tick$.pipe(takeUntil(this.removed$)).subscribe(([_, delta]) => {
|
|
105
|
+
this.lastValue = this._motionControlFunction(delta);
|
|
106
|
+
this.target.position = this.lastValue.position;
|
|
107
|
+
this.target.rotation = this.lastValue.rotation;
|
|
108
|
+
this.customParametersHandleFunc(this.target, this.lastValue.customParameters);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
onRemoved() {
|
|
113
|
+
super.onRemoved();
|
|
114
|
+
this.removed$.next();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
dispose(): void {
|
|
118
|
+
super.dispose();
|
|
119
|
+
this.tick$.unsubscribe();
|
|
120
|
+
this.tick$.complete();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IGg3dBody, IGg3dCamera } from '../interfaces';
|
|
2
|
+
import { Gg3dEntity } from './gg-3d-entity';
|
|
3
|
+
|
|
4
|
+
export class Gg3dCameraEntity<T extends IGg3dCamera = IGg3dCamera> extends Gg3dEntity {
|
|
5
|
+
constructor(public readonly object3D: T, public readonly objectBody: IGg3dBody | null = null) {
|
|
6
|
+
super(object3D, objectBody);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Subject } from 'rxjs';
|
|
2
|
+
import { GgPositionable3dEntity } from './gg-positionable-3d-entity';
|
|
3
|
+
import { ITickListener } from '../../base/entities/interfaces/i-tick-listener';
|
|
4
|
+
import { Point3, Point4 } from '../../base/models/points';
|
|
5
|
+
import { Gg3dWorld } from '../gg-3d-world';
|
|
6
|
+
import { IGg3dBody, IGg3dObject } from '../interfaces';
|
|
7
|
+
|
|
8
|
+
export class Gg3dEntity extends GgPositionable3dEntity implements ITickListener {
|
|
9
|
+
public readonly tick$: Subject<[number, number]> = new Subject<[number, number]>();
|
|
10
|
+
public readonly tickOrder = 750;
|
|
11
|
+
|
|
12
|
+
public get position(): Point3 {
|
|
13
|
+
return super.position;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
set position(value: Point3) {
|
|
17
|
+
if (this.object3D) {
|
|
18
|
+
this.object3D.position = value;
|
|
19
|
+
}
|
|
20
|
+
if (this.objectBody) {
|
|
21
|
+
this.objectBody.position = value;
|
|
22
|
+
}
|
|
23
|
+
super.position = value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public get rotation(): Point4 {
|
|
27
|
+
return super.rotation;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
set rotation(value: Point4) {
|
|
31
|
+
if (this.object3D) {
|
|
32
|
+
this.object3D.rotation = value;
|
|
33
|
+
}
|
|
34
|
+
if (this.objectBody) {
|
|
35
|
+
this.objectBody.rotation = value;
|
|
36
|
+
}
|
|
37
|
+
super.rotation = value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public get scale(): Point3 {
|
|
41
|
+
return super.scale;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
set scale(value: Point3) {
|
|
45
|
+
if (this.object3D) {
|
|
46
|
+
this.object3D.scale = value;
|
|
47
|
+
}
|
|
48
|
+
if (this.objectBody) {
|
|
49
|
+
this.objectBody.scale = value;
|
|
50
|
+
}
|
|
51
|
+
super.scale = value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public get visible(): boolean {
|
|
55
|
+
return !!this.object3D?.visible;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set visible(value: boolean) {
|
|
59
|
+
if (this.object3D) {
|
|
60
|
+
this.object3D.visible = value;
|
|
61
|
+
}
|
|
62
|
+
for (const entity of this._children) {
|
|
63
|
+
if (entity instanceof Gg3dEntity && entity.object3D) {
|
|
64
|
+
entity.object3D.visible = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
protected runTransformBinding(objectBody: IGg3dBody, object3D: IGg3dObject): void {
|
|
70
|
+
// bind physics body transform to mesh transform
|
|
71
|
+
const pos = objectBody.position;
|
|
72
|
+
const quat = objectBody.rotation;
|
|
73
|
+
object3D.position = pos;
|
|
74
|
+
object3D.rotation = quat;
|
|
75
|
+
this._position$.next(pos);
|
|
76
|
+
this._rotation$.next(quat);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
constructor(public readonly object3D: IGg3dObject | null, public readonly objectBody: IGg3dBody | null = null) {
|
|
80
|
+
super();
|
|
81
|
+
if (objectBody && object3D) {
|
|
82
|
+
this.tick$.subscribe(() => {
|
|
83
|
+
this.runTransformBinding(objectBody, object3D);
|
|
84
|
+
});
|
|
85
|
+
this.runTransformBinding(objectBody, object3D);
|
|
86
|
+
} else if (!objectBody && !object3D) {
|
|
87
|
+
throw new Error('Cannot create entity without a mesh and a body');
|
|
88
|
+
}
|
|
89
|
+
if (objectBody) {
|
|
90
|
+
objectBody.entity = this;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onSpawned(world: Gg3dWorld) {
|
|
95
|
+
super.onSpawned(world);
|
|
96
|
+
this.object3D?.addToWorld(world.visualScene);
|
|
97
|
+
this.objectBody?.addToWorld(world.physicsWorld);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onRemoved() {
|
|
101
|
+
this.object3D?.removeFromWorld(this.world!.visualScene);
|
|
102
|
+
this.objectBody?.removeFromWorld(this.world!.physicsWorld);
|
|
103
|
+
super.onRemoved();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
dispose(): void {
|
|
107
|
+
super.dispose();
|
|
108
|
+
this.tick$.unsubscribe();
|
|
109
|
+
if (this.object3D) {
|
|
110
|
+
this.object3D.dispose();
|
|
111
|
+
}
|
|
112
|
+
if (this.objectBody) {
|
|
113
|
+
this.objectBody.dispose();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { BehaviorSubject, NEVER, Observable, startWith, Subject, switchMap } from 'rxjs';
|
|
2
|
+
import { distinctUntilChanged, map, tap, throttleTime } from 'rxjs/operators';
|
|
3
|
+
import { Point3, Point4 } from '../../base/models/points';
|
|
4
|
+
import { Graph } from '../../base/data-structures/graph';
|
|
5
|
+
import { GgEntity } from '../../base/entities/gg-entity';
|
|
6
|
+
import { ITickListener } from '../../base/entities/interfaces/i-tick-listener';
|
|
7
|
+
import { GgPositionable3dEntity } from './gg-positionable-3d-entity';
|
|
8
|
+
import { Gg3dWorld } from '../gg-3d-world';
|
|
9
|
+
import { Gg3dEntity } from './gg-3d-entity';
|
|
10
|
+
import { LoadOptions, LoadResultWithProps } from '../loader';
|
|
11
|
+
|
|
12
|
+
type MapGraphNodeType = {
|
|
13
|
+
path: string;
|
|
14
|
+
position: Point3;
|
|
15
|
+
rotation?: Point4;
|
|
16
|
+
loadOptions: Partial<Omit<LoadOptions, 'position' | 'rotation'>>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class MapGraph extends Graph<MapGraphNodeType> {
|
|
20
|
+
static fromMapArray(array: MapGraphNodeType[]): MapGraph {
|
|
21
|
+
const root = new MapGraph(array[0]);
|
|
22
|
+
let tail = root;
|
|
23
|
+
for (let i = 1; i < array.length; i++) {
|
|
24
|
+
const newTail = new MapGraph(array[i]);
|
|
25
|
+
tail.addAdjacent(newTail);
|
|
26
|
+
tail = newTail;
|
|
27
|
+
}
|
|
28
|
+
return root;
|
|
29
|
+
}
|
|
30
|
+
static fromMapSquareGrid(grid: MapGraphNodeType[][]): MapGraph {
|
|
31
|
+
const nodes = grid.map(sgrid => sgrid.map(item => new MapGraph(item)));
|
|
32
|
+
// bind them
|
|
33
|
+
for (let j = 0; j < nodes.length; j++) {
|
|
34
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
35
|
+
if (i > 0) {
|
|
36
|
+
nodes[j][i].addAdjacent(nodes[j][i - 1]);
|
|
37
|
+
}
|
|
38
|
+
if (j > 0) {
|
|
39
|
+
nodes[j][i].addAdjacent(nodes[j - 1][i]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return nodes[0][0];
|
|
44
|
+
}
|
|
45
|
+
public getNearestDummy(thisNodes: Graph<MapGraphNodeType>[], cursor: Point3): Graph<MapGraphNodeType> {
|
|
46
|
+
let min = Infinity;
|
|
47
|
+
let node: Graph<MapGraphNodeType> = this;
|
|
48
|
+
// TODO should be heavily optimized with kd-tree-javascript, but building spatial index takes too much time. Serialize to file?
|
|
49
|
+
thisNodes.forEach(n => {
|
|
50
|
+
let dist = Math.sqrt(
|
|
51
|
+
Math.pow(cursor.x - n.data.position.x, 2) +
|
|
52
|
+
Math.pow(cursor.y - n.data.position.y, 2) +
|
|
53
|
+
Math.pow(cursor.z - n.data.position.z, 2),
|
|
54
|
+
);
|
|
55
|
+
if (dist < min) {
|
|
56
|
+
min = dist;
|
|
57
|
+
node = n;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return node;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// TODO return iterable?
|
|
64
|
+
nodes(): MapGraph[] {
|
|
65
|
+
return Array.from(this.walkRead(-1)) as MapGraph[];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type Gg3dMapGraphEntityOptions = {
|
|
70
|
+
// depth in tree to load. 0 means load only the nearest node, 1 means nearest + all of it's neighbours etc.
|
|
71
|
+
loadDepth: number;
|
|
72
|
+
// additional depth, means unload delay. Nodes with this depth won't load, but if already loaded, will not be destroyed
|
|
73
|
+
inertia: number;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const defaultOptions: Gg3dMapGraphEntityOptions = {
|
|
77
|
+
loadDepth: 5,
|
|
78
|
+
inertia: 0,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export class Gg3dMapGraphEntity extends GgEntity implements ITickListener {
|
|
82
|
+
public readonly tick$: Subject<[number, number]> = new Subject<[number, number]>();
|
|
83
|
+
public readonly tickOrder = 1500;
|
|
84
|
+
|
|
85
|
+
public readonly loaderCursorEntity$: BehaviorSubject<GgPositionable3dEntity | null> =
|
|
86
|
+
new BehaviorSubject<GgPositionable3dEntity | null>(null);
|
|
87
|
+
readonly loaded: Map<MapGraphNodeType, GgPositionable3dEntity[]> = new Map();
|
|
88
|
+
|
|
89
|
+
private _initialLoadComplete$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
90
|
+
public get initialLoadComplete$(): Observable<boolean> {
|
|
91
|
+
return this._initialLoadComplete$.asObservable();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private _nearestDummy$: BehaviorSubject<Graph<MapGraphNodeType> | null> =
|
|
95
|
+
new BehaviorSubject<Graph<MapGraphNodeType> | null>(null);
|
|
96
|
+
public get nearestDummy(): Graph<MapGraphNodeType> | null {
|
|
97
|
+
return this._nearestDummy$.getValue();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private _chunkLoaded$: Subject<[LoadResultWithProps, { position: Point3; rotation: Point4 }]> = new Subject<
|
|
101
|
+
[LoadResultWithProps, { position: Point3; rotation: Point4 }]
|
|
102
|
+
>();
|
|
103
|
+
public get chunkLoaded$(): Observable<[LoadResultWithProps, { position: Point3; rotation: Point4 }]> {
|
|
104
|
+
return this._chunkLoaded$.asObservable();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get world(): Gg3dWorld | null {
|
|
108
|
+
return this._world;
|
|
109
|
+
}
|
|
110
|
+
protected _world: Gg3dWorld | null = null;
|
|
111
|
+
private readonly mapGraphNodes: MapGraph[];
|
|
112
|
+
|
|
113
|
+
protected readonly options: Gg3dMapGraphEntityOptions;
|
|
114
|
+
|
|
115
|
+
constructor(public readonly mapGraph: MapGraph, options: Partial<Gg3dMapGraphEntityOptions> = {}) {
|
|
116
|
+
super();
|
|
117
|
+
this.options = { ...defaultOptions, ...options };
|
|
118
|
+
this.mapGraphNodes = mapGraph.nodes();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
onSpawned(world: Gg3dWorld) {
|
|
122
|
+
super.onSpawned(world);
|
|
123
|
+
// TODO takeUntil removed from world?
|
|
124
|
+
this.loaderCursorEntity$
|
|
125
|
+
.pipe(
|
|
126
|
+
switchMap(entity =>
|
|
127
|
+
entity
|
|
128
|
+
? this.tick$.pipe(
|
|
129
|
+
startWith(null), // map will perform initial loading even if world not started yet. Handy to preload map
|
|
130
|
+
throttleTime(1000),
|
|
131
|
+
map(() => entity.position),
|
|
132
|
+
)
|
|
133
|
+
: NEVER,
|
|
134
|
+
),
|
|
135
|
+
map(pos => this.mapGraph.getNearestDummy(this.mapGraphNodes, pos)),
|
|
136
|
+
tap(node => this._nearestDummy$.next(node)),
|
|
137
|
+
distinctUntilChanged(),
|
|
138
|
+
)
|
|
139
|
+
.subscribe(currentChunk => {
|
|
140
|
+
let haveToBeLoaded: Set<MapGraphNodeType> = new Set();
|
|
141
|
+
let canBeLoaded: Set<MapGraphNodeType>;
|
|
142
|
+
if (this.options.inertia > 0) {
|
|
143
|
+
canBeLoaded = new Set();
|
|
144
|
+
const nodes = currentChunk.walkReadPreserveDepth(this.options.loadDepth + this.options.inertia);
|
|
145
|
+
for (let distance = 0; distance < nodes.length; distance++) {
|
|
146
|
+
nodes[distance].forEach(node => canBeLoaded.add(node.data));
|
|
147
|
+
if (distance <= this.options.loadDepth) {
|
|
148
|
+
nodes[distance].forEach(node => haveToBeLoaded.add(node.data));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
currentChunk.walkRead(this.options.loadDepth).forEach(node => haveToBeLoaded.add(node.data));
|
|
153
|
+
canBeLoaded = haveToBeLoaded;
|
|
154
|
+
}
|
|
155
|
+
for (const loadedNode of this.loaded.keys()) {
|
|
156
|
+
if (!canBeLoaded.has(loadedNode)) {
|
|
157
|
+
this.disposeChunk(loadedNode);
|
|
158
|
+
} else {
|
|
159
|
+
haveToBeLoaded.delete(loadedNode);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
Promise.all(Array.from(haveToBeLoaded.keys()).map(n => this.loadChunk(n))).then(() =>
|
|
163
|
+
this._initialLoadComplete$.next(true),
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
onRemoved() {
|
|
169
|
+
super.onRemoved();
|
|
170
|
+
this.loaderCursorEntity$.next(null);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected async loadChunk(node: MapGraphNodeType): Promise<Gg3dEntity[]> {
|
|
174
|
+
const loaded = await this.world!.loader.loadGgGlb(node.path, {
|
|
175
|
+
position: node.position,
|
|
176
|
+
rotation: node.rotation || { x: 0, y: 0, z: 0, w: 1 },
|
|
177
|
+
...node.loadOptions,
|
|
178
|
+
});
|
|
179
|
+
const entities = [
|
|
180
|
+
...loaded.entities,
|
|
181
|
+
...(loaded.props || [])
|
|
182
|
+
.map(p => p.entities)
|
|
183
|
+
.reduce((p, c) => {
|
|
184
|
+
p.push(...c);
|
|
185
|
+
return p;
|
|
186
|
+
}, []),
|
|
187
|
+
];
|
|
188
|
+
this.loaded.set(node, entities);
|
|
189
|
+
this.addChildren(...entities);
|
|
190
|
+
this._chunkLoaded$.next([
|
|
191
|
+
loaded,
|
|
192
|
+
{ position: node.position, rotation: node.rotation || { x: 0, y: 0, z: 0, w: 1 } },
|
|
193
|
+
]);
|
|
194
|
+
return entities;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
protected disposeChunk(node: MapGraphNodeType) {
|
|
198
|
+
if (!this.loaded.has(node)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.removeChildren(this.loaded.get(node)!, true);
|
|
202
|
+
this.loaded.delete(node);
|
|
203
|
+
}
|
|
204
|
+
}
|