@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,77 @@
|
|
|
1
|
+
import { ITickListener } from './interfaces/i-tick-listener';
|
|
2
|
+
import { Subject, Subscription } from 'rxjs';
|
|
3
|
+
import { GgEntity } from './gg-entity';
|
|
4
|
+
import { Point2 } from '../models/points';
|
|
5
|
+
import { GgViewportManager } from '../gg-viewport-manager';
|
|
6
|
+
|
|
7
|
+
export type RendererOptions = {
|
|
8
|
+
transparent: boolean;
|
|
9
|
+
background: number;
|
|
10
|
+
forceRendererSize?: Point2;
|
|
11
|
+
forceResolution?: number;
|
|
12
|
+
antialias: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export abstract class BaseGgRenderer extends GgEntity implements ITickListener {
|
|
16
|
+
protected _permanentRenderMethods: Map<number, Function>;
|
|
17
|
+
protected _singularRenderMethods: Map<number, Function>;
|
|
18
|
+
private initialized = false;
|
|
19
|
+
|
|
20
|
+
readonly tick$: Subject<[number, number]>;
|
|
21
|
+
public readonly tickOrder = 1000;
|
|
22
|
+
protected tickListener: Subscription | null = null;
|
|
23
|
+
|
|
24
|
+
public readonly rendererOptions: RendererOptions;
|
|
25
|
+
|
|
26
|
+
protected constructor(canvas?: HTMLCanvasElement, options: Partial<RendererOptions> = {}) {
|
|
27
|
+
super();
|
|
28
|
+
this.rendererOptions = {
|
|
29
|
+
transparent: false,
|
|
30
|
+
background: 0x000000,
|
|
31
|
+
antialias: true,
|
|
32
|
+
...(options || {}),
|
|
33
|
+
};
|
|
34
|
+
this._permanentRenderMethods = new Map<number, Function>();
|
|
35
|
+
this._singularRenderMethods = new Map<number, Function>();
|
|
36
|
+
this.tick$ = new Subject<[number, number]>();
|
|
37
|
+
if (canvas) {
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
GgViewportManager.instance.assignRendererToCanvas(this, canvas).then();
|
|
40
|
+
}, 0);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public get isActive(): boolean {
|
|
45
|
+
return !!this.tickListener;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public activate(): void {
|
|
49
|
+
if (this.isActive) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!this.initialized) {
|
|
53
|
+
this.init();
|
|
54
|
+
}
|
|
55
|
+
this.tickListener = this.tick$.subscribe(() => {
|
|
56
|
+
this.render();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public deactivate(): void {
|
|
61
|
+
if (!this.isActive) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.tickListener?.unsubscribe();
|
|
65
|
+
this.tickListener = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
init(): void {
|
|
69
|
+
this.initialized = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
abstract render(): void;
|
|
73
|
+
|
|
74
|
+
abstract resize(newSize: Point2): void;
|
|
75
|
+
|
|
76
|
+
abstract dispose(): void;
|
|
77
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { GgWorld } from '../gg-world';
|
|
2
|
+
|
|
3
|
+
export abstract class GgEntity {
|
|
4
|
+
get world(): GgWorld<any, any> | null {
|
|
5
|
+
return this._world;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
protected _world: GgWorld<any, any> | null = null;
|
|
9
|
+
|
|
10
|
+
protected _name: string = '';
|
|
11
|
+
|
|
12
|
+
public get name(): string {
|
|
13
|
+
return this._name;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public set name(value: string) {
|
|
17
|
+
this._name = value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected _children: GgEntity[] = [];
|
|
21
|
+
|
|
22
|
+
public addChildren(...entities: GgEntity[]) {
|
|
23
|
+
this._children.push(...entities);
|
|
24
|
+
if (this._world) {
|
|
25
|
+
for (const item of entities) {
|
|
26
|
+
this._world.addEntity(item);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public removeChildren(entities: GgEntity[], dispose: boolean = false) {
|
|
32
|
+
this._children = this._children.filter(c => !entities.includes(c));
|
|
33
|
+
if (this._world) {
|
|
34
|
+
for (const item of entities) {
|
|
35
|
+
this._world.removeEntity(item, dispose);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public onSpawned(world: GgWorld<any, any>) {
|
|
41
|
+
this._world = world;
|
|
42
|
+
for (const c of this._children) {
|
|
43
|
+
world.addEntity(c);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public onRemoved() {
|
|
48
|
+
const world = this._world!;
|
|
49
|
+
this._world = null;
|
|
50
|
+
for (const c of this._children) {
|
|
51
|
+
world.removeEntity(c);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// TODO add some flag to entity that it is disposed, and throw a normal error when trying to add such entity to world again
|
|
56
|
+
public dispose(): void {
|
|
57
|
+
for (const c of this._children) {
|
|
58
|
+
c.dispose();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { BehaviorSubject, Observable } from 'rxjs';
|
|
2
|
+
import { GgEntity } from './gg-entity';
|
|
3
|
+
import { GgWorld } from '../gg-world';
|
|
4
|
+
|
|
5
|
+
export abstract class GgPositionableEntity<D, R> extends GgEntity {
|
|
6
|
+
protected readonly _position$: BehaviorSubject<D>;
|
|
7
|
+
protected readonly _rotation$: BehaviorSubject<R>;
|
|
8
|
+
protected readonly _scale$: BehaviorSubject<D>;
|
|
9
|
+
|
|
10
|
+
abstract getDefaultPosition(): D;
|
|
11
|
+
abstract getDefaultRotation(): R;
|
|
12
|
+
abstract getDefaultScale(): D;
|
|
13
|
+
|
|
14
|
+
get world(): GgWorld<D, R> | null {
|
|
15
|
+
return super.world;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public get position(): D {
|
|
19
|
+
return this._position$.getValue();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public get position$(): Observable<D> {
|
|
23
|
+
return this._position$.asObservable();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public set position(value: D) {
|
|
27
|
+
this._position$.next(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public get rotation(): R {
|
|
31
|
+
return this._rotation$.getValue();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public get rotation$(): Observable<R> {
|
|
35
|
+
return this._rotation$.asObservable();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public set rotation(value: R) {
|
|
39
|
+
this._rotation$.next(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public get scale(): D {
|
|
43
|
+
return this._scale$.getValue();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public get scale$(): Observable<D> {
|
|
47
|
+
return this._scale$.asObservable();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public set scale(value: D) {
|
|
51
|
+
this._scale$.next(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected constructor() {
|
|
55
|
+
super();
|
|
56
|
+
this._position$ = new BehaviorSubject<D>(this.getDefaultPosition());
|
|
57
|
+
this._rotation$ = new BehaviorSubject<R>(this.getDefaultRotation());
|
|
58
|
+
this._scale$ = new BehaviorSubject<D>(this.getDefaultScale());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onSpawned(world: GgWorld<D, R>) {
|
|
62
|
+
super.onSpawned(world);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { GgWorld } from '../gg-world';
|
|
2
|
+
import { GgEntity } from './gg-entity';
|
|
3
|
+
import { ITickListener } from './interfaces/i-tick-listener';
|
|
4
|
+
import { finalize, Observable, Subject } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
class InlineTickController extends GgEntity implements ITickListener {
|
|
7
|
+
public readonly tick$: Subject<[number, number]> = new Subject<[number, number]>();
|
|
8
|
+
|
|
9
|
+
constructor(public readonly tickOrder: number) {
|
|
10
|
+
super();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
dispose(): void {
|
|
14
|
+
super.dispose();
|
|
15
|
+
this.tick$.unsubscribe();
|
|
16
|
+
this.tick$.complete();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createInlineTickController(
|
|
21
|
+
world: GgWorld<any, any>,
|
|
22
|
+
tickOrder: number = 750,
|
|
23
|
+
): Observable<[number, number]> {
|
|
24
|
+
const controller: InlineTickController = new InlineTickController(tickOrder);
|
|
25
|
+
world.addEntity(controller);
|
|
26
|
+
return controller.tick$.pipe(
|
|
27
|
+
finalize(() => {
|
|
28
|
+
world.removeEntity(controller);
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Subject } from 'rxjs';
|
|
2
|
+
import { GgEntity } from '../gg-entity';
|
|
3
|
+
|
|
4
|
+
export interface ITickListener {
|
|
5
|
+
// will receive [elapsed time, delta] of each world clock tick
|
|
6
|
+
tick$: Subject<[number, number]>;
|
|
7
|
+
// the priority of ticker: the less value, the earlier tick will be run.
|
|
8
|
+
// 500 is a physics tick order
|
|
9
|
+
// 750 is a default objects binding tick order
|
|
10
|
+
// 1000 is a rendering tick order
|
|
11
|
+
// e.g. 499 will run before physics, 1001 will run after rendering
|
|
12
|
+
readonly tickOrder: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const isITickListener: (entity: GgEntity) => boolean = entity => (entity as any)?.tick$ instanceof Subject;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { GgWorld } from './gg-world';
|
|
2
|
+
|
|
3
|
+
export class GgStatic {
|
|
4
|
+
private static _instance: GgStatic;
|
|
5
|
+
public static get instance(): GgStatic {
|
|
6
|
+
if (!GgStatic._instance) {
|
|
7
|
+
GgStatic._instance = new GgStatic();
|
|
8
|
+
}
|
|
9
|
+
return GgStatic._instance;
|
|
10
|
+
}
|
|
11
|
+
private constructor() {
|
|
12
|
+
(window as any).gg = this;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public readonly worlds: GgWorld<any, any>[] = [];
|
|
16
|
+
public selectedWorld: GgWorld<any, any> | null = null;
|
|
17
|
+
|
|
18
|
+
async console(input: string): Promise<string> {
|
|
19
|
+
if (!this.selectedWorld) {
|
|
20
|
+
return 'World not selected';
|
|
21
|
+
}
|
|
22
|
+
const commands: string[] = input.split('\n');
|
|
23
|
+
let output: string[] = [];
|
|
24
|
+
for (const command of commands) {
|
|
25
|
+
const parts = command.split(' ');
|
|
26
|
+
output.push(await this.selectedWorld.runConsoleCommand(parts.splice(0, 1)[0], parts));
|
|
27
|
+
}
|
|
28
|
+
return output.join('\n');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { BehaviorSubject, filter, first, Subject, takeUntil } from 'rxjs';
|
|
2
|
+
import { GgViewport } from './gg-viewport';
|
|
3
|
+
import { BaseGgRenderer } from './entities/base-gg-renderer';
|
|
4
|
+
|
|
5
|
+
type CanvasAppDescr = { canvas: HTMLCanvasElement; renderer?: BaseGgRenderer };
|
|
6
|
+
|
|
7
|
+
export class GgViewportManager {
|
|
8
|
+
private static _instance: GgViewportManager | null;
|
|
9
|
+
static get instance(): GgViewportManager {
|
|
10
|
+
if (!this._instance) {
|
|
11
|
+
this._instance = new GgViewportManager();
|
|
12
|
+
}
|
|
13
|
+
return this._instance;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private constructor() {
|
|
17
|
+
setTimeout(async () => {
|
|
18
|
+
let retries = 120;
|
|
19
|
+
let stage: HTMLDivElement | null = null;
|
|
20
|
+
while (retries > 0) {
|
|
21
|
+
retries--;
|
|
22
|
+
stage = document.getElementById('gg-stage') as HTMLDivElement;
|
|
23
|
+
if (stage) {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
await new Promise(r => setTimeout(r, 250));
|
|
27
|
+
}
|
|
28
|
+
if (!stage) {
|
|
29
|
+
this.gameStage$.error(new Error('Div with id "gg-stage" not found in 30 seconds'));
|
|
30
|
+
} else {
|
|
31
|
+
GgViewport.instance
|
|
32
|
+
.subscribeOnViewportSize()
|
|
33
|
+
.pipe(takeUntil(this.destroyed))
|
|
34
|
+
.subscribe(size => {
|
|
35
|
+
for (const zIndex of Object.keys(this.canvases)) {
|
|
36
|
+
const canvasDescr = this.canvases[+zIndex];
|
|
37
|
+
if (canvasDescr.renderer && !canvasDescr.renderer.rendererOptions.forceRendererSize) {
|
|
38
|
+
canvasDescr.renderer.resize(size);
|
|
39
|
+
}
|
|
40
|
+
canvasDescr.canvas.width = size.x;
|
|
41
|
+
canvasDescr.canvas.height = size.y;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
this.gameStage$.next(stage);
|
|
45
|
+
}
|
|
46
|
+
}, 0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private readonly canvases: { [key: number]: CanvasAppDescr } = {};
|
|
50
|
+
private gameStage$: BehaviorSubject<HTMLDivElement | null> = new BehaviorSubject<HTMLDivElement | null>(null);
|
|
51
|
+
protected destroyed: Subject<void> = new Subject();
|
|
52
|
+
|
|
53
|
+
private getStageAsync(): Promise<HTMLDivElement> {
|
|
54
|
+
const current = this.gameStage$.getValue();
|
|
55
|
+
if (current) {
|
|
56
|
+
return Promise.resolve(current);
|
|
57
|
+
}
|
|
58
|
+
return Promise.race([
|
|
59
|
+
this.gameStage$
|
|
60
|
+
.pipe(
|
|
61
|
+
takeUntil(this.destroyed),
|
|
62
|
+
filter(x => !!x),
|
|
63
|
+
first(),
|
|
64
|
+
)
|
|
65
|
+
.toPromise(),
|
|
66
|
+
new Promise((resolve, reject) => {
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
reject('Cannot find div with id "gg-stage" in 30 seconds');
|
|
69
|
+
}, 30000);
|
|
70
|
+
}),
|
|
71
|
+
]) as Promise<HTMLDivElement>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public async createCanvas(zIndex: number): Promise<HTMLCanvasElement> {
|
|
75
|
+
zIndex = Math.round(zIndex);
|
|
76
|
+
if (this.canvases[zIndex]) {
|
|
77
|
+
throw new Error(`Cannot add canvas on zIndex ${zIndex}. Index is locked by another canvas`);
|
|
78
|
+
}
|
|
79
|
+
const stage = await this.getStageAsync();
|
|
80
|
+
const canvas: HTMLCanvasElement = document.createElement('canvas');
|
|
81
|
+
await this.registerCanvas(canvas, zIndex);
|
|
82
|
+
stage.appendChild(canvas);
|
|
83
|
+
return canvas;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public async registerCanvas(canvas: HTMLCanvasElement, zIndex: number): Promise<void> {
|
|
87
|
+
canvas.style.zIndex = '' + zIndex;
|
|
88
|
+
canvas.style.position = 'absolute';
|
|
89
|
+
canvas.style.width = '100%';
|
|
90
|
+
canvas.style.height = '100%';
|
|
91
|
+
canvas.id = 'canvas-' + zIndex;
|
|
92
|
+
this.canvases[zIndex] = { canvas };
|
|
93
|
+
const size = GgViewport.instance.getCurrentViewportSize();
|
|
94
|
+
canvas.width = size.x;
|
|
95
|
+
canvas.height = size.y;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public async assignRendererToCanvas(renderer: BaseGgRenderer, canvas: HTMLCanvasElement): Promise<void> {
|
|
99
|
+
if (!GgViewport.instance.isActive) {
|
|
100
|
+
GgViewport.instance.activate();
|
|
101
|
+
}
|
|
102
|
+
let zIndex: number | null = null;
|
|
103
|
+
let maxExistingZIndex = 0;
|
|
104
|
+
for (const index in this.canvases) {
|
|
105
|
+
if (this.canvases[index].canvas === canvas) {
|
|
106
|
+
zIndex = +index;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
maxExistingZIndex = +index;
|
|
110
|
+
}
|
|
111
|
+
if (!zIndex) {
|
|
112
|
+
zIndex = maxExistingZIndex + 1;
|
|
113
|
+
await this.registerCanvas(canvas, zIndex);
|
|
114
|
+
}
|
|
115
|
+
this.canvases[zIndex].renderer = renderer;
|
|
116
|
+
if (!renderer.rendererOptions.forceRendererSize) {
|
|
117
|
+
renderer.resize(GgViewport.instance.getCurrentViewportSize());
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// TODO remove canvas logic
|
|
122
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { BehaviorSubject, distinctUntilChanged, fromEvent, merge, Observable, Subject, takeUntil } from 'rxjs';
|
|
2
|
+
import { map } from 'rxjs/operators';
|
|
3
|
+
import { Point2 } from './models/points';
|
|
4
|
+
import { GgVisualScene } from './interfaces/gg-visual-scene';
|
|
5
|
+
|
|
6
|
+
const getCurrentWindowSize = (): Point2 => {
|
|
7
|
+
return {
|
|
8
|
+
x: window.innerWidth,
|
|
9
|
+
y: window.innerHeight,
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const getMousePositionFromEvent = (event: MouseEvent | TouchEvent): Point2 | null => {
|
|
14
|
+
if (event instanceof MouseEvent) {
|
|
15
|
+
return { x: event.x, y: event.y };
|
|
16
|
+
} else if (event instanceof TouchEvent) {
|
|
17
|
+
if (event.touches.length === 0) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return { x: event.touches[0].clientX, y: event.touches[0].clientY };
|
|
21
|
+
}
|
|
22
|
+
console.warn('Cannot determine mouse position from event', event);
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export class GgViewport {
|
|
27
|
+
private static _instance: GgViewport | null;
|
|
28
|
+
static get instance(): GgViewport {
|
|
29
|
+
if (!this._instance) {
|
|
30
|
+
this._instance = new GgViewport();
|
|
31
|
+
}
|
|
32
|
+
return this._instance;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private constructor() {}
|
|
36
|
+
|
|
37
|
+
protected destroy$: Subject<void> | null = null;
|
|
38
|
+
|
|
39
|
+
private scenes: GgVisualScene<any, any>[] = [];
|
|
40
|
+
|
|
41
|
+
public get isActive(): boolean {
|
|
42
|
+
return !!this.destroy$;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public activate() {
|
|
46
|
+
if (this.destroy$) {
|
|
47
|
+
throw new Error('GgViewport is already active');
|
|
48
|
+
}
|
|
49
|
+
this.destroy$ = new Subject();
|
|
50
|
+
// cursor position
|
|
51
|
+
merge(fromEvent(window, 'mousemove'), fromEvent(window, 'touchstart'), fromEvent(window, 'touchmove'))
|
|
52
|
+
.pipe(
|
|
53
|
+
takeUntil(this.destroy$),
|
|
54
|
+
map(event => event as MouseEvent | TouchEvent),
|
|
55
|
+
)
|
|
56
|
+
.subscribe(event => {
|
|
57
|
+
const point = getMousePositionFromEvent(event);
|
|
58
|
+
if (point) {
|
|
59
|
+
this.mousePosition.next(point);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
// clicks
|
|
63
|
+
merge(fromEvent(window, 'mousedown'), fromEvent(window, 'touchstart'))
|
|
64
|
+
.pipe(
|
|
65
|
+
takeUntil(this.destroy$),
|
|
66
|
+
map(event => event as MouseEvent | TouchEvent),
|
|
67
|
+
)
|
|
68
|
+
.subscribe(event => {
|
|
69
|
+
const point = getMousePositionFromEvent(event);
|
|
70
|
+
if (point) {
|
|
71
|
+
this.mousePosition.next(point);
|
|
72
|
+
}
|
|
73
|
+
this.isMouseDown.next(true);
|
|
74
|
+
});
|
|
75
|
+
merge(fromEvent(window, 'mouseup'), fromEvent(window, 'click'), fromEvent(window, 'touchend'))
|
|
76
|
+
.pipe(
|
|
77
|
+
takeUntil(this.destroy$),
|
|
78
|
+
map(event => event as MouseEvent | TouchEvent),
|
|
79
|
+
)
|
|
80
|
+
.subscribe(event => {
|
|
81
|
+
const point = getMousePositionFromEvent(event);
|
|
82
|
+
if (point) {
|
|
83
|
+
this.mousePosition.next(point);
|
|
84
|
+
}
|
|
85
|
+
this.isMouseDown.next(false);
|
|
86
|
+
if ((event.target as any)['nodeName'] === 'canvas') {
|
|
87
|
+
this.mouseClicked.next(this.mousePosition.getValue());
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// viewport size
|
|
91
|
+
merge(fromEvent(window, 'resize'), fromEvent(window, 'orientationchange'))
|
|
92
|
+
.pipe(
|
|
93
|
+
takeUntil(this.destroy$),
|
|
94
|
+
map(() => getCurrentWindowSize()),
|
|
95
|
+
)
|
|
96
|
+
.subscribe(this.viewportSize);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public deactivate(): void {
|
|
100
|
+
if (!this.destroy$) {
|
|
101
|
+
throw new Error('GgViewport is already inactive');
|
|
102
|
+
}
|
|
103
|
+
this.destroy$.next();
|
|
104
|
+
this.destroy$.complete();
|
|
105
|
+
this.destroy$ = null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ================================================== VIEWPORT SIZE ==================================================
|
|
109
|
+
private viewportSize: BehaviorSubject<Point2> = new BehaviorSubject<Point2>(getCurrentWindowSize());
|
|
110
|
+
|
|
111
|
+
getCurrentViewportSize(): Point2 {
|
|
112
|
+
return this.viewportSize.getValue();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
subscribeOnViewportSize(): Observable<Point2> {
|
|
116
|
+
return this.viewportSize.asObservable().pipe(
|
|
117
|
+
distinctUntilChanged((v1, v2) => {
|
|
118
|
+
return v1.x === v2.x && v1.y === v2.y;
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ================================================== VIEWPORT SIZE ==================================================
|
|
124
|
+
|
|
125
|
+
// ================================================== POINTER LOGIC ==================================================
|
|
126
|
+
private mousePosition: BehaviorSubject<Point2> = new BehaviorSubject<Point2>({ x: 0, y: 0 });
|
|
127
|
+
private mouseClicked: Subject<Point2> = new Subject<Point2>();
|
|
128
|
+
private isMouseDown: Subject<boolean> = new Subject<boolean>();
|
|
129
|
+
|
|
130
|
+
isMouseEnabled(): boolean {
|
|
131
|
+
return matchMedia('(hover:hover)').matches && matchMedia('(pointer:fine)').matches;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
isTouchDevice(): boolean {
|
|
135
|
+
return (
|
|
136
|
+
'createTouch' in document ||
|
|
137
|
+
!!navigator.userAgent.match(/(iPhone|iPod|iPad)/) ||
|
|
138
|
+
!!navigator.userAgent.match(/Android/)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
subscribeOnMouseMove(): Observable<Point2> {
|
|
143
|
+
return this.mousePosition.asObservable().pipe(
|
|
144
|
+
distinctUntilChanged((v1, v2) => {
|
|
145
|
+
return v1.x === v2.x && v1.y === v2.y;
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
subscribeOnIsMouseDown(): Observable<boolean> {
|
|
151
|
+
return this.isMouseDown.asObservable().pipe(distinctUntilChanged());
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
subscribeOnMouseClick(): Observable<Point2> {
|
|
155
|
+
return this.mouseClicked.asObservable();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ================================================== POINTER LOGIC ==================================================
|
|
159
|
+
}
|