@gg-web-engine/core 0.0.1 → 0.0.3
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/entities/controllers/entity-2d-positioning.animator.d.ts +16 -0
- package/dist/2d/entities/controllers/entity-2d-positioning.animator.js +26 -0
- package/dist/2d/entities/gg-2d-entity.d.ts +9 -1
- package/dist/2d/entities/gg-2d-entity.js +50 -17
- package/dist/2d/gg-2d-world.d.ts +3 -0
- package/dist/2d/gg-2d-world.js +11 -0
- package/dist/3d/controllers/car-keyboard.controller.d.ts +10 -10
- package/dist/3d/controllers/car-keyboard.controller.js +21 -20
- package/dist/3d/controllers/free-camera.controller.d.ts +8 -8
- package/dist/3d/entities/controllers/camera-3d.animator.d.ts +17 -0
- package/dist/3d/entities/controllers/camera-3d.animator.js +32 -0
- package/dist/3d/entities/controllers/entity-3d-positioning.animator.d.ts +16 -0
- package/dist/3d/entities/controllers/entity-3d-positioning.animator.js +26 -0
- package/dist/3d/entities/gg-3d-camera.entity.d.ts +2 -0
- package/dist/3d/entities/gg-3d-camera.entity.js +6 -0
- package/dist/3d/entities/gg-3d-entity.d.ts +4 -1
- package/dist/3d/entities/gg-3d-entity.js +19 -10
- package/dist/3d/entities/gg-3d-map-graph.entity.d.ts +7 -5
- package/dist/3d/entities/gg-3d-map-graph.entity.js +1 -1
- package/dist/3d/entities/gg-3d-raycast-vehicle.entity.d.ts +6 -5
- package/dist/3d/gg-3d-world.d.ts +3 -0
- package/dist/3d/gg-3d-world.js +12 -0
- package/dist/base/clock/clock.d.ts +25 -0
- package/dist/base/clock/clock.js +79 -0
- package/dist/base/clock/global-clock.d.ts +15 -0
- package/dist/base/clock/global-clock.js +36 -0
- package/dist/base/entities/controllers/animation-mixer.d.ts +83 -0
- package/dist/base/entities/controllers/animation-mixer.js +152 -0
- package/dist/base/entities/gg-entity.d.ts +4 -1
- package/dist/base/entities/gg-entity.js +7 -0
- package/dist/base/entities/inline-controller.js +0 -1
- package/dist/base/gg-viewport-manager.d.ts +4 -0
- package/dist/base/gg-world.d.ts +3 -1
- package/dist/base/gg-world.js +6 -5
- package/dist/base/math/matrix4.js +3 -3
- package/dist/base/math/point2.js +1 -1
- package/dist/base/math/point3.js +1 -1
- package/dist/base/math/quaternion.d.ts +83 -9
- package/dist/base/math/quaternion.js +93 -10
- package/dist/base/models/points.d.ts +9 -9
- package/dist/index.d.ts +6 -2
- package/dist/index.js +6 -2
- package/package.json +1 -1
- package/.prettierrc +0 -8
- package/src/2d/entities/gg-2d-entity.ts +0 -86
- package/src/2d/entities/gg-2d-trigger.entity.ts +0 -17
- package/src/2d/entities/gg-positionable-2d-entity.ts +0 -16
- package/src/2d/factories.ts +0 -20
- package/src/2d/gg-2d-world.ts +0 -32
- package/src/2d/interfaces.ts +0 -31
- package/src/2d/models/body-options.ts +0 -3
- package/src/2d/models/shapes.ts +0 -7
- package/src/3d/controllers/car-keyboard.controller.ts +0 -128
- package/src/3d/controllers/free-camera.controller.ts +0 -86
- package/src/3d/entities/controllers/entity-motion.controller.ts +0 -122
- package/src/3d/entities/gg-3d-camera.entity.ts +0 -8
- package/src/3d/entities/gg-3d-entity.ts +0 -116
- package/src/3d/entities/gg-3d-map-graph.entity.ts +0 -204
- package/src/3d/entities/gg-3d-raycast-vehicle.entity.ts +0 -468
- package/src/3d/entities/gg-3d-trigger.entity.ts +0 -22
- package/src/3d/entities/gg-positionable-3d-entity.ts +0 -31
- package/src/3d/factories.ts +0 -29
- package/src/3d/gg-3d-world.ts +0 -36
- package/src/3d/interfaces.ts +0 -96
- package/src/3d/loader.ts +0 -165
- package/src/3d/models/body-options.ts +0 -3
- package/src/3d/models/gg-meta.ts +0 -12
- package/src/3d/models/shapes.ts +0 -13
- package/src/base/clock.ts +0 -97
- package/src/base/controllers/common.ts +0 -37
- package/src/base/controllers/i-controller.ts +0 -5
- package/src/base/controllers/keyboard.controller.ts +0 -109
- package/src/base/controllers/mouse.controller.ts +0 -73
- package/src/base/data-structures/graph.ts +0 -125
- package/src/base/entities/base-gg-renderer.ts +0 -77
- package/src/base/entities/gg-entity.ts +0 -61
- package/src/base/entities/gg-positionable-entity.ts +0 -64
- package/src/base/entities/inline-controller.ts +0 -31
- package/src/base/entities/interfaces/i-tick-listener.ts +0 -15
- package/src/base/gg-static.ts +0 -30
- package/src/base/gg-viewport-manager.ts +0 -122
- package/src/base/gg-viewport.ts +0 -159
- package/src/base/gg-world.ts +0 -198
- package/src/base/interfaces/gg-body.ts +0 -23
- package/src/base/interfaces/gg-debug-physics-drawer.ts +0 -10
- package/src/base/interfaces/gg-object.ts +0 -26
- package/src/base/interfaces/gg-physics-world.ts +0 -24
- package/src/base/interfaces/gg-trigger.ts +0 -10
- package/src/base/interfaces/gg-visual-scene.ts +0 -11
- package/src/base/math/box.ts +0 -19
- package/src/base/math/matrix4.ts +0 -29
- package/src/base/math/numbers.ts +0 -10
- package/src/base/math/point2.ts +0 -43
- package/src/base/math/point3.ts +0 -119
- package/src/base/math/quaternion.ts +0 -172
- package/src/base/models/axis-directions.ts +0 -3
- package/src/base/models/body-options.ts +0 -6
- package/src/base/models/geometry-nodes.ts +0 -5
- package/src/base/models/points.ts +0 -3
- package/src/base/ui/gg-console.ui.ts +0 -165
- package/src/base/ui/gg-debugger.ui.ts +0 -69
- package/src/index.ts +0 -53
- package/test/base/data-structures/graph.spec.ts +0 -76
package/src/3d/loader.ts
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { Gg3dWorld } from './gg-3d-world';
|
|
2
|
-
import { GgMeta } from './models/gg-meta';
|
|
3
|
-
import { Gg3dEntity } from './entities/gg-3d-entity';
|
|
4
|
-
import { Point3, Point4 } from '../base/models/points';
|
|
5
|
-
import { Pnt3 } from '../base/math/point3';
|
|
6
|
-
import { Qtrn } from '../base/math/quaternion';
|
|
7
|
-
import { IGg3dBody, IGg3dObject } from './interfaces';
|
|
8
|
-
|
|
9
|
-
export enum CachingStrategy {
|
|
10
|
-
Nothing,
|
|
11
|
-
Files,
|
|
12
|
-
Entities,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type LoadOptions = {
|
|
16
|
-
// whether to cache anything
|
|
17
|
-
// "Nothing" does not cache anything
|
|
18
|
-
// "Files" caches GLB+Meta file contents
|
|
19
|
-
// "Entities" clones and saves parsed from GLB+Meta objects and bodies
|
|
20
|
-
cachingStrategy: CachingStrategy;
|
|
21
|
-
// initial position
|
|
22
|
-
position: Point3;
|
|
23
|
-
// initial rotation
|
|
24
|
-
rotation: Point4;
|
|
25
|
-
// process dummies with flag is_prop
|
|
26
|
-
loadProps: boolean;
|
|
27
|
-
// path where to find prop scenes
|
|
28
|
-
propsPath?: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const defaultLoadOptions: LoadOptions = {
|
|
32
|
-
cachingStrategy: CachingStrategy.Nothing,
|
|
33
|
-
position: { x: 0, y: 0, z: 0 },
|
|
34
|
-
rotation: { x: 0, y: 0, z: 0, w: 1 },
|
|
35
|
-
loadProps: true,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type LoadResourcesResult = {
|
|
39
|
-
resources: { object3D: IGg3dObject | null; body: IGg3dBody | null }[];
|
|
40
|
-
meta: GgMeta;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export type LoadResult = {
|
|
44
|
-
entities: Gg3dEntity[];
|
|
45
|
-
meta: GgMeta;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const cloneLoadResourcesResult = (loadResult: LoadResourcesResult) => ({
|
|
49
|
-
meta: loadResult.meta, // TODO deep clone it
|
|
50
|
-
resources: loadResult.resources.map(({ object3D, body }) => ({
|
|
51
|
-
object3D: object3D && object3D.clone(),
|
|
52
|
-
body: body && body.clone(),
|
|
53
|
-
})),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
export type LoadResultWithProps = LoadResult & { props?: LoadResult[] };
|
|
57
|
-
|
|
58
|
-
export class Gg3dLoader {
|
|
59
|
-
readonly filesCache: Map<string, [ArrayBuffer, GgMeta] | Promise<[ArrayBuffer, GgMeta]>> = new Map<
|
|
60
|
-
string,
|
|
61
|
-
[ArrayBuffer, GgMeta] | Promise<[ArrayBuffer, GgMeta]>
|
|
62
|
-
>();
|
|
63
|
-
|
|
64
|
-
readonly loadResultCache: Map<string, LoadResourcesResult | Promise<LoadResourcesResult>> = new Map<
|
|
65
|
-
string,
|
|
66
|
-
LoadResourcesResult | Promise<LoadResourcesResult>
|
|
67
|
-
>();
|
|
68
|
-
|
|
69
|
-
constructor(protected readonly world: Gg3dWorld) {}
|
|
70
|
-
|
|
71
|
-
public async loadGgGlbFiles(path: string, useCache: boolean = false): Promise<[ArrayBuffer, GgMeta]> {
|
|
72
|
-
if (useCache && this.filesCache.has(path)) {
|
|
73
|
-
return this.filesCache.get(path)!;
|
|
74
|
-
}
|
|
75
|
-
const loadPromise = Promise.all([
|
|
76
|
-
fetch(`${path}.glb`).then(r => r.arrayBuffer()),
|
|
77
|
-
fetch(`${path}.meta`)
|
|
78
|
-
.then(r => r.text())
|
|
79
|
-
.then(r => JSON.parse(r)),
|
|
80
|
-
]);
|
|
81
|
-
if (useCache) {
|
|
82
|
-
this.filesCache.set(path, loadPromise);
|
|
83
|
-
}
|
|
84
|
-
const result = await loadPromise;
|
|
85
|
-
if (useCache) {
|
|
86
|
-
this.filesCache.set(path, result);
|
|
87
|
-
}
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
public async loadGgGlbResources(
|
|
92
|
-
path: string,
|
|
93
|
-
cachingStrategy: CachingStrategy = CachingStrategy.Nothing,
|
|
94
|
-
): Promise<LoadResourcesResult> {
|
|
95
|
-
if (cachingStrategy == CachingStrategy.Entities && this.loadResultCache.has(path)) {
|
|
96
|
-
const cached = this.loadResultCache.get(path);
|
|
97
|
-
const cachedResult = cached instanceof Promise ? await cached : cached;
|
|
98
|
-
return cloneLoadResourcesResult(cachedResult!);
|
|
99
|
-
}
|
|
100
|
-
const [glb, meta] = await this.loadGgGlbFiles(path, cachingStrategy == CachingStrategy.Files);
|
|
101
|
-
if (!glb) {
|
|
102
|
-
throw new Error('GLB not found');
|
|
103
|
-
}
|
|
104
|
-
const [object, bodies] = await Promise.all([
|
|
105
|
-
this.world.visualScene.loader.loadFromGgGlb(glb, meta),
|
|
106
|
-
this.world.physicsWorld.loader.loadFromGgGlb(glb, meta),
|
|
107
|
-
]);
|
|
108
|
-
const result: LoadResourcesResult = { resources: [], meta };
|
|
109
|
-
if (!object) {
|
|
110
|
-
return result;
|
|
111
|
-
}
|
|
112
|
-
if (bodies.length == 0) {
|
|
113
|
-
result.resources.push({ object3D: object, body: null });
|
|
114
|
-
} else if (bodies.length == 1) {
|
|
115
|
-
result.resources.push({ object3D: object, body: bodies[0] });
|
|
116
|
-
} else {
|
|
117
|
-
for (const body of bodies) {
|
|
118
|
-
result.resources.push({ object3D: object.popChild(body.name), body });
|
|
119
|
-
}
|
|
120
|
-
if (!object.isEmpty()) {
|
|
121
|
-
result.resources.push({ object3D: object, body: null });
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (cachingStrategy == CachingStrategy.Entities) {
|
|
125
|
-
this.loadResultCache.set(path, cloneLoadResourcesResult(result));
|
|
126
|
-
}
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public async loadGgGlb(
|
|
131
|
-
path: string,
|
|
132
|
-
options: Partial<LoadOptions> = defaultLoadOptions,
|
|
133
|
-
): Promise<LoadResultWithProps> {
|
|
134
|
-
const loadOptions = { ...defaultLoadOptions, ...options };
|
|
135
|
-
const { resources, meta } = await this.loadGgGlbResources(path, loadOptions.cachingStrategy);
|
|
136
|
-
const result: LoadResultWithProps = {
|
|
137
|
-
entities: resources.map(({ object3D, body }) => new Gg3dEntity(object3D, body)),
|
|
138
|
-
meta,
|
|
139
|
-
};
|
|
140
|
-
if (loadOptions.loadProps) {
|
|
141
|
-
result.props = await Promise.all(
|
|
142
|
-
(meta as GgMeta).dummies
|
|
143
|
-
.filter(x => x.is_prop || x.is_scene)
|
|
144
|
-
.map(dummy =>
|
|
145
|
-
this.loadGgGlb(
|
|
146
|
-
dummy.is_prop
|
|
147
|
-
? (loadOptions.propsPath || path.substring(0, path.lastIndexOf('/') + 1)) + dummy.prop_id
|
|
148
|
-
: dummy.scene_id,
|
|
149
|
-
{
|
|
150
|
-
loadProps: !!dummy.is_scene,
|
|
151
|
-
position: Pnt3.add(Pnt3.rot(dummy.position, loadOptions.rotation), loadOptions.position),
|
|
152
|
-
rotation: Qtrn.combineRotations(dummy.rotation, loadOptions.rotation),
|
|
153
|
-
},
|
|
154
|
-
),
|
|
155
|
-
),
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
result.entities.forEach(e => {
|
|
159
|
-
e.position = Pnt3.add(Pnt3.rot(Pnt3.clone(e.position), loadOptions.rotation), loadOptions.position);
|
|
160
|
-
// FIXME this rotation is wrong
|
|
161
|
-
e.rotation = Qtrn.mult(Qtrn.clone(e.rotation), loadOptions.rotation);
|
|
162
|
-
});
|
|
163
|
-
return result;
|
|
164
|
-
}
|
|
165
|
-
}
|
package/src/3d/models/gg-meta.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Point3, Point4 } from '../../base/models/points';
|
|
2
|
-
import { BodyShape3DDescriptor } from './shapes';
|
|
3
|
-
|
|
4
|
-
export type GgDummy = { name: string; position: Point3; rotation: Point4 } & any;
|
|
5
|
-
export type GgCurve = { name: string; cyclic: boolean; points: Point3[] } & any;
|
|
6
|
-
export type GgRigidBody = { name: string; position: Point3; rotation: Point4 } & BodyShape3DDescriptor;
|
|
7
|
-
|
|
8
|
-
export type GgMeta = {
|
|
9
|
-
dummies: GgDummy[];
|
|
10
|
-
curves: GgCurve[];
|
|
11
|
-
rigidBodies: GgRigidBody[];
|
|
12
|
-
};
|
package/src/3d/models/shapes.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Point3, Point4 } from '../../base/models/points';
|
|
2
|
-
import { Body3DOptions } from './body-options';
|
|
3
|
-
|
|
4
|
-
export type Shape3DDescriptor =
|
|
5
|
-
| { shape: 'BOX'; dimensions: Point3 }
|
|
6
|
-
| { shape: 'CONE' | 'CYLINDER'; radius: number; height: number }
|
|
7
|
-
| { shape: 'CAPSULE'; radius: number; centersDistance: number }
|
|
8
|
-
| { shape: 'SPHERE'; radius: number }
|
|
9
|
-
| { shape: 'COMPOUND'; children: { position?: Point3; rotation?: Point4; shape: Shape3DDescriptor }[] }
|
|
10
|
-
| { shape: 'CONVEX_HULL'; vertices: Point3[] }
|
|
11
|
-
| { shape: 'MESH'; vertices: Point3[]; faces: [number, number, number][] };
|
|
12
|
-
|
|
13
|
-
export type BodyShape3DDescriptor = { shape: Shape3DDescriptor; body: Partial<Body3DOptions> };
|
package/src/base/clock.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { animationFrameScheduler, Observable, of, Subject, Subscription } from 'rxjs';
|
|
2
|
-
import { map, repeat, tap } from 'rxjs/operators';
|
|
3
|
-
|
|
4
|
-
const now = () => {
|
|
5
|
-
return (typeof performance === 'undefined' ? Date : performance).now();
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A class, providing ability to track time, fire ticks, provide time elapsed + tick delta with ability to suspend/resume it.
|
|
10
|
-
*/
|
|
11
|
-
export class Clock {
|
|
12
|
-
/**
|
|
13
|
-
* A global clock, based on animation frame ticks
|
|
14
|
-
*/
|
|
15
|
-
public static readonly animationFrameClock: Clock = new Clock(
|
|
16
|
-
of(undefined, animationFrameScheduler).pipe(repeat()),
|
|
17
|
-
true,
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
// value is global clock time, delta ms from last tick
|
|
21
|
-
public get tick$(): Observable<[number, number]> {
|
|
22
|
-
return this._tick$.pipe(map(([oldTime, newTime]) => [newTime, newTime - oldTime]));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public get isRunning(): boolean {
|
|
26
|
-
return !!this.tickSub;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public get isPaused(): boolean {
|
|
30
|
-
return this.pausedAt !== -1;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public get elapsedTime(): number {
|
|
34
|
-
if (this.isPaused) {
|
|
35
|
-
return this.pausedAt - this.startedAt;
|
|
36
|
-
}
|
|
37
|
-
return now() - this.startedAt;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private tickSub: Subscription | null = null;
|
|
41
|
-
private readonly _tick$: Subject<[number, number]> = new Subject<[number, number]>();
|
|
42
|
-
|
|
43
|
-
// state
|
|
44
|
-
private startedAt: number = -1;
|
|
45
|
-
private pausedAt: number = -1;
|
|
46
|
-
|
|
47
|
-
constructor(private readonly tickSource: Observable<any>, autoStart: boolean = false) {
|
|
48
|
-
if (autoStart) {
|
|
49
|
-
this.start();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
start() {
|
|
54
|
-
if (this.isRunning) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
this.startedAt = now();
|
|
58
|
-
this.startListeningTicks();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
stop() {
|
|
62
|
-
this.stopListeningTicks();
|
|
63
|
-
this.startedAt = this.pausedAt = -1;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
pause() {
|
|
67
|
-
this.stopListeningTicks();
|
|
68
|
-
this.pausedAt = now();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
resume() {
|
|
72
|
-
if (this.isRunning || this.pausedAt == -1) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
this.startedAt += now() - this.pausedAt;
|
|
76
|
-
this.pausedAt = -1;
|
|
77
|
-
this.startListeningTicks();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private startListeningTicks() {
|
|
81
|
-
if (this.tickSub) {
|
|
82
|
-
throw new Error('Clock is already ticking!');
|
|
83
|
-
}
|
|
84
|
-
let oldRelativeTime = 0;
|
|
85
|
-
this.tickSub = this.tickSource
|
|
86
|
-
.pipe(
|
|
87
|
-
map(() => [oldRelativeTime, now() - this.startedAt] as [number, number]),
|
|
88
|
-
tap(([_, cur]) => (oldRelativeTime = cur)),
|
|
89
|
-
)
|
|
90
|
-
.subscribe(this._tick$);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private stopListeningTicks() {
|
|
94
|
-
this.tickSub?.unsubscribe();
|
|
95
|
-
this.tickSub = null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { combineLatest, Observable } from 'rxjs';
|
|
2
|
-
import { KeyboardController } from './keyboard.controller';
|
|
3
|
-
import { map } from 'rxjs/operators';
|
|
4
|
-
|
|
5
|
-
export type DirectionKeymap = 'arrows' | 'wasd' | 'wasd+arrows';
|
|
6
|
-
|
|
7
|
-
export type DirectionOutput = { upDown?: boolean; leftRight?: boolean };
|
|
8
|
-
|
|
9
|
-
export const bindDirectionKeys: (
|
|
10
|
-
keyboard: KeyboardController,
|
|
11
|
-
keymap: DirectionKeymap,
|
|
12
|
-
) => Observable<DirectionOutput> = (keyboard: KeyboardController, keymap: DirectionKeymap) => {
|
|
13
|
-
const keys = [[], [], [], []] as string[][];
|
|
14
|
-
if (keymap.includes('wasd')) {
|
|
15
|
-
keys[0].push('KeyW');
|
|
16
|
-
keys[1].push('KeyA');
|
|
17
|
-
keys[2].push('KeyS');
|
|
18
|
-
keys[3].push('KeyD');
|
|
19
|
-
}
|
|
20
|
-
if (keymap.includes('arrows')) {
|
|
21
|
-
keys[0].push('ArrowUp');
|
|
22
|
-
keys[1].push('ArrowLeft');
|
|
23
|
-
keys[2].push('ArrowDown');
|
|
24
|
-
keys[3].push('ArrowRight');
|
|
25
|
-
}
|
|
26
|
-
return combineLatest(keys.map(x => keyboard.bindMany(...x))).pipe(
|
|
27
|
-
map(moveDirection => {
|
|
28
|
-
const result: DirectionOutput = {};
|
|
29
|
-
if (moveDirection.includes(true)) {
|
|
30
|
-
const [f, l, b, r] = moveDirection;
|
|
31
|
-
if (f != b) result.upDown = f;
|
|
32
|
-
if (l != r) result.leftRight = l;
|
|
33
|
-
}
|
|
34
|
-
return result;
|
|
35
|
-
}),
|
|
36
|
-
);
|
|
37
|
-
};
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { IController } from './i-controller';
|
|
2
|
-
import { BehaviorSubject, combineLatest, finalize, NEVER, Observable } from 'rxjs';
|
|
3
|
-
import { map } from 'rxjs/operators';
|
|
4
|
-
|
|
5
|
-
export class KeyboardController implements IController {
|
|
6
|
-
private bindings: { [code: string]: BehaviorSubject<boolean>[] } = {};
|
|
7
|
-
|
|
8
|
-
constructor() {
|
|
9
|
-
this.handleKeys = this.handleKeys.bind(this);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
private _running: boolean = false;
|
|
13
|
-
public get running(): boolean {
|
|
14
|
-
return this._running;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
bind(code: string): Observable<boolean> {
|
|
18
|
-
if (!this.bindings[code]) {
|
|
19
|
-
this.bindings[code] = [];
|
|
20
|
-
}
|
|
21
|
-
const subj = new BehaviorSubject<boolean>(false);
|
|
22
|
-
this.bindings[code].push(subj);
|
|
23
|
-
return subj.pipe(
|
|
24
|
-
finalize(() => {
|
|
25
|
-
this.bindings[code].splice(this.bindings[code].indexOf(subj), 1);
|
|
26
|
-
subj.complete();
|
|
27
|
-
}),
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
bindMany(...codes: string[]): Observable<boolean> {
|
|
32
|
-
if (codes.length == 0) {
|
|
33
|
-
console.warn('[KeyboardController] bindMany called without any key code');
|
|
34
|
-
return NEVER;
|
|
35
|
-
}
|
|
36
|
-
if (codes.length == 1) {
|
|
37
|
-
return this.bind(codes[0]);
|
|
38
|
-
}
|
|
39
|
-
const subjects: BehaviorSubject<boolean>[] = [];
|
|
40
|
-
for (const code of codes) {
|
|
41
|
-
if (!this.bindings[code]) {
|
|
42
|
-
this.bindings[code] = [];
|
|
43
|
-
}
|
|
44
|
-
const subj = new BehaviorSubject<boolean>(false);
|
|
45
|
-
this.bindings[code].push(subj);
|
|
46
|
-
subjects.push(subj);
|
|
47
|
-
}
|
|
48
|
-
return combineLatest(subjects).pipe(
|
|
49
|
-
finalize(() => {
|
|
50
|
-
for (let i = 0; i < codes.length; i++) {
|
|
51
|
-
this.bindings[codes[i]].splice(this.bindings[codes[i]].indexOf(subjects[i]), 1);
|
|
52
|
-
subjects[i].complete();
|
|
53
|
-
}
|
|
54
|
-
}),
|
|
55
|
-
map(values => values.includes(true)),
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
emulateKeyDown(code: string): void {
|
|
60
|
-
if (!this.running) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
for (const subj of this.bindings[code] || []) {
|
|
64
|
-
subj.next(true);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
emulateKeyUp(code: string): void {
|
|
69
|
-
if (!this.running) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
for (const subj of this.bindings[code] || []) {
|
|
73
|
-
subj.next(false);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
emulateKeyPress(code: string): void {
|
|
78
|
-
this.emulateKeyDown(code);
|
|
79
|
-
this.emulateKeyUp(code);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async start() {
|
|
83
|
-
if (this.running) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
window.addEventListener('keydown', this.handleKeys);
|
|
87
|
-
window.addEventListener('keyup', this.handleKeys);
|
|
88
|
-
this._running = true;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private handleKeys(e: KeyboardEvent) {
|
|
92
|
-
if (e.type != 'keydown' && e.type != 'keyup') {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const pressed = e.type == 'keydown';
|
|
96
|
-
for (const subj of this.bindings[e.code] || []) {
|
|
97
|
-
subj.next(pressed);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async stop() {
|
|
102
|
-
if (!this.running) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
window.removeEventListener('keydown', this.handleKeys);
|
|
106
|
-
window.removeEventListener('keyup', this.handleKeys);
|
|
107
|
-
this._running = false;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { IController } from './i-controller';
|
|
2
|
-
import { filter, fromEvent, Observable, Subject, takeUntil } from 'rxjs';
|
|
3
|
-
import { map } from 'rxjs/operators';
|
|
4
|
-
import { Point2 } from '../models/points';
|
|
5
|
-
|
|
6
|
-
export type MouseControllerPointLockOptions = { ignoreMovementWhenNotLocked: boolean; canvas: HTMLCanvasElement };
|
|
7
|
-
export type MouseControllerOptions = {
|
|
8
|
-
pointerLock?: MouseControllerPointLockOptions;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export class MouseController implements IController {
|
|
12
|
-
private readonly _delta$: Subject<Point2> = new Subject<Point2>();
|
|
13
|
-
|
|
14
|
-
public get deltaX$(): Observable<number> {
|
|
15
|
-
return this._delta$.pipe(map(d => d.x));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
public get deltaY$(): Observable<number> {
|
|
19
|
-
return this._delta$.pipe(map(d => d.y));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public get delta$(): Observable<Point2> {
|
|
23
|
-
return this._delta$.asObservable();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
constructor(private readonly options: MouseControllerOptions = {}) {
|
|
27
|
-
this.canvasClickListener = this.canvasClickListener.bind(this);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
private stopped$: Subject<void> = new Subject();
|
|
31
|
-
private _running: boolean = false;
|
|
32
|
-
public get running(): boolean {
|
|
33
|
-
return this._running;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async start() {
|
|
37
|
-
if (this.running) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
fromEvent(window, 'mousemove')
|
|
41
|
-
.pipe(
|
|
42
|
-
takeUntil(this.stopped$),
|
|
43
|
-
filter(
|
|
44
|
-
() =>
|
|
45
|
-
!this.options.pointerLock ||
|
|
46
|
-
!this.options.pointerLock.ignoreMovementWhenNotLocked ||
|
|
47
|
-
!!document.pointerLockElement,
|
|
48
|
-
),
|
|
49
|
-
map((e: any) => ({ x: e.movementX, y: e.movementY })),
|
|
50
|
-
)
|
|
51
|
-
.subscribe(v => this._delta$.next(v));
|
|
52
|
-
if (!!this.options.pointerLock) {
|
|
53
|
-
this.options.pointerLock.canvas.addEventListener('click', this.canvasClickListener);
|
|
54
|
-
}
|
|
55
|
-
this._running = true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private canvasClickListener(): void {
|
|
59
|
-
this.options.pointerLock!.canvas.requestPointerLock();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async stop(unlockPointer: boolean = true) {
|
|
63
|
-
if (!this.running) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
this.stopped$.next();
|
|
67
|
-
if (unlockPointer && !!this.options.pointerLock) {
|
|
68
|
-
this.options.pointerLock.canvas.removeEventListener('click', this.canvasClickListener);
|
|
69
|
-
document.exitPointerLock();
|
|
70
|
-
}
|
|
71
|
-
this._running = false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
export class Graph<T> {
|
|
2
|
-
static fromArray<T>(array: T[]): Graph<T> {
|
|
3
|
-
const root = new Graph(array[0]);
|
|
4
|
-
let tail = root;
|
|
5
|
-
for (let i = 1; i < array.length; i++) {
|
|
6
|
-
const newTail = new Graph<T>(array[i]);
|
|
7
|
-
tail.addAdjacent(newTail);
|
|
8
|
-
tail = newTail;
|
|
9
|
-
}
|
|
10
|
-
return root;
|
|
11
|
-
}
|
|
12
|
-
static fromSquareGrid<T>(grid: T[][]): Graph<T> {
|
|
13
|
-
const nodes = grid.map(sgrid => sgrid.map(item => new Graph<T>(item)));
|
|
14
|
-
// bind them
|
|
15
|
-
for (let j = 0; j < nodes.length; j++) {
|
|
16
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
17
|
-
if (i > 0) {
|
|
18
|
-
nodes[j][i].addAdjacent(nodes[j][i - 1]);
|
|
19
|
-
}
|
|
20
|
-
if (j > 0) {
|
|
21
|
-
nodes[j][i].addAdjacent(nodes[j - 1][i]);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return nodes[0][0];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
data: T;
|
|
29
|
-
private adjacent: Set<Graph<T>>;
|
|
30
|
-
|
|
31
|
-
constructor(data: T) {
|
|
32
|
-
this.data = data;
|
|
33
|
-
this.adjacent = new Set();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
addAdjacent(node: Graph<T>): boolean {
|
|
37
|
-
if (this.adjacent.has(node)) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
this.adjacent.add(node);
|
|
41
|
-
node.adjacent.add(this);
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
removeAdjacent(node: Graph<T>): boolean {
|
|
46
|
-
if (!this.adjacent.has(node)) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
this.adjacent.delete(node);
|
|
50
|
-
node.adjacent.delete(this);
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Create an edge between two nodes
|
|
56
|
-
*/
|
|
57
|
-
addEdge(source: Graph<T>, destination: Graph<T>): boolean {
|
|
58
|
-
if (!this.removeAdjacent(destination)) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
this.addAdjacent(source);
|
|
62
|
-
source.addAdjacent(destination);
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Remove an edge between two nodes
|
|
68
|
-
*/
|
|
69
|
-
removeEdge(source: Graph<T>, destination: Graph<T>): boolean {
|
|
70
|
-
if (!source.adjacent.has(this) || !source.adjacent.has(destination)) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
source.removeAdjacent(this);
|
|
74
|
-
source.removeAdjacent(destination);
|
|
75
|
-
return this.addAdjacent(destination);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
walkRead(depth: number): Set<Graph<T>> {
|
|
79
|
-
const visited: Set<Graph<T>> = new Set();
|
|
80
|
-
const stack: [Graph<T>, number][] = [[this, depth]];
|
|
81
|
-
visited.add(this);
|
|
82
|
-
while (stack.length) {
|
|
83
|
-
const next = stack.shift()!;
|
|
84
|
-
const [node, depthLeft] = next;
|
|
85
|
-
if (depthLeft !== 0) {
|
|
86
|
-
const children = Array.from(node.adjacent).filter(c => !visited.has(c));
|
|
87
|
-
for (const child of children) {
|
|
88
|
-
visited.add(child);
|
|
89
|
-
stack.push([child, depthLeft - 1]);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return visited;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// the same as "walkRead", but returns array of sets (per depth), so distance to each found node can be tracked
|
|
97
|
-
walkReadPreserveDepth(depth: number): Set<Graph<T>>[] {
|
|
98
|
-
const visited: Set<Graph<T>> = new Set();
|
|
99
|
-
const result: Set<Graph<T>>[] = [];
|
|
100
|
-
const stack: [Graph<T>, number][] = [[this, depth]];
|
|
101
|
-
visited.add(this);
|
|
102
|
-
while (stack.length) {
|
|
103
|
-
const next = stack.shift()!;
|
|
104
|
-
const [node, depthLeft] = next;
|
|
105
|
-
const distance = depth - depthLeft;
|
|
106
|
-
if (!result[distance]) {
|
|
107
|
-
result[distance] = new Set<Graph<T>>();
|
|
108
|
-
}
|
|
109
|
-
result[distance].add(node);
|
|
110
|
-
if (depthLeft !== 0) {
|
|
111
|
-
const children = Array.from(node.adjacent).filter(c => !visited.has(c));
|
|
112
|
-
for (const child of children) {
|
|
113
|
-
visited.add(child);
|
|
114
|
-
stack.push([child, depthLeft - 1]);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// TODO return iterable?
|
|
122
|
-
nodes(): Graph<T>[] {
|
|
123
|
-
return Array.from(this.walkRead(-1));
|
|
124
|
-
}
|
|
125
|
-
}
|