@gg-web-engine/core 0.0.16 → 0.0.18

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.
@@ -24,7 +24,7 @@ export declare type FreeCameraControllerOptions = {
24
24
  /**
25
25
  * Options for configuring mouse input.
26
26
  */
27
- mouseOptions: MouseInputOptions;
27
+ mouseOptions: Partial<MouseInputOptions>;
28
28
  };
29
29
  /**
30
30
  * A controller for a free-moving camera.
@@ -7,7 +7,7 @@ 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, takeUntil } from 'rxjs';
10
+ import { combineLatest, filter, takeUntil } from 'rxjs';
11
11
  import { MouseInput } from '../../../../base/inputs/mouse.input';
12
12
  import { Pnt3 } from '../../../../base/math/point3';
13
13
  import { Pnt2 } from '../../../../base/math/point2';
@@ -59,7 +59,10 @@ export class FreeCameraController extends GgEntity {
59
59
  });
60
60
  // Subscribe to mouse input for camera rotation
61
61
  let rotationDelta = { x: 0, y: 0 };
62
- this.mouseInput.delta$.pipe(takeUntil(this._onRemoved$)).subscribe(delta => {
62
+ let isTouchScreen = MouseInput.isTouchDevice();
63
+ this.mouseInput.delta$
64
+ .pipe(takeUntil(this._onRemoved$), filter(() => isTouchScreen || this.mouseInput.isPointerLocked))
65
+ .subscribe(delta => {
63
66
  rotationDelta = Pnt2.add(rotationDelta, delta);
64
67
  });
65
68
  // Setup updating camera position and rotation based on input
@@ -76,7 +79,11 @@ export class FreeCameraController extends GgEntity {
76
79
  this.camera.object3D.fov += zo ? 1 : -1;
77
80
  this.camera.position = Pnt3.add(this.camera.position, Pnt3.rot(Pnt3.scalarMult(Pnt3.norm(translateVector), this.options.movementOptions.speed), this.camera.rotation));
78
81
  if (rotationDelta.x != 0 || rotationDelta.y != 0) {
79
- this.camera.rotation = Qtrn.combineRotations(Qtrn.fromAngle({ x: 0, y: 0, z: 1 }, -rotationDelta.x / 300), this.camera.rotation, Qtrn.fromAngle({ x: 1, y: 0, z: 0 }, -rotationDelta.y / 300));
82
+ const spherical = Pnt3.toSpherical(Pnt3.rot({ x: 0, y: 0, z: -1 }, this.camera.rotation));
83
+ spherical.theta -= rotationDelta.x / 300;
84
+ spherical.phi += rotationDelta.y / 300;
85
+ spherical.phi = Math.max(0.000001, Math.min(Math.PI - 0.000001, spherical.phi));
86
+ this.camera.rotation = Qtrn.lookAt(this.camera.position, Pnt3.add(this.camera.position, Pnt3.fromSpherical(spherical)), { x: 0, y: 0, z: 1 });
80
87
  rotationDelta = { x: 0, y: 0 };
81
88
  }
82
89
  });
@@ -0,0 +1,33 @@
1
+ import { GgEntity, GGTickOrder } from '../../../../base/entities/gg-entity';
2
+ import { MouseInput, MouseInputOptions } from '../../../../base/inputs/mouse.input';
3
+ import { Gg3dCameraEntity } from '../../gg-3d-camera.entity';
4
+ import { GgWorld } from '../../../../base/gg-world';
5
+ import { MutableSpherical, Point3 } from '../../../../base/models/points';
6
+ export declare type OrbitCameraControllerOptions = {
7
+ mouseOptions: Partial<MouseInputOptions>;
8
+ target: Point3;
9
+ orbiting: {
10
+ sensitivityX: number;
11
+ sensitivityY: number;
12
+ } | false;
13
+ zooming: {
14
+ sensitivity: number;
15
+ } | false;
16
+ panning: {
17
+ sensitivityX: number;
18
+ sensitivityY: number;
19
+ } | false;
20
+ dollying: {
21
+ sensitivity: number;
22
+ } | false;
23
+ };
24
+ export declare class OrbitCameraController extends GgEntity {
25
+ protected readonly camera: Gg3dCameraEntity;
26
+ readonly tickOrder = GGTickOrder.INPUT_CONTROLLERS;
27
+ protected readonly options: OrbitCameraControllerOptions;
28
+ protected readonly mouseInput: MouseInput;
29
+ protected spherical: MutableSpherical;
30
+ constructor(camera: Gg3dCameraEntity, options?: Partial<OrbitCameraControllerOptions>);
31
+ onSpawned(world: GgWorld<any, any>): Promise<void>;
32
+ onRemoved(): Promise<void>;
33
+ }
@@ -0,0 +1,112 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { GgEntity, GGTickOrder } from '../../../../base/entities/gg-entity';
11
+ import { MouseInput, MouseInputState } from '../../../../base/inputs/mouse.input';
12
+ import { filter, takeUntil } from 'rxjs';
13
+ import { Qtrn } from '../../../../base/math/quaternion';
14
+ import { Pnt3 } from '../../../../base/math/point3';
15
+ import { map } from 'rxjs/operators';
16
+ const DEFAULT_OPTIONS = {
17
+ mouseOptions: {},
18
+ target: { x: 0, y: 0, z: 0 },
19
+ orbiting: { sensitivityX: 1, sensitivityY: 1 },
20
+ zooming: { sensitivity: 1 },
21
+ panning: { sensitivityX: 1, sensitivityY: 1 },
22
+ dollying: { sensitivity: 1 },
23
+ };
24
+ export class OrbitCameraController extends GgEntity {
25
+ constructor(camera, options = {}) {
26
+ super();
27
+ this.camera = camera;
28
+ this.tickOrder = GGTickOrder.INPUT_CONTROLLERS;
29
+ this.spherical = { phi: 0, radius: 0, theta: 0 };
30
+ this.options = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
31
+ this.mouseInput = new MouseInput(this.options.mouseOptions);
32
+ }
33
+ onSpawned(world) {
34
+ const _super = Object.create(null, {
35
+ onSpawned: { get: () => super.onSpawned }
36
+ });
37
+ return __awaiter(this, void 0, void 0, function* () {
38
+ yield _super.onSpawned.call(this, world);
39
+ this.spherical = Pnt3.toSpherical(Pnt3.sub(this.camera.position, this.options.target));
40
+ if (this.options.orbiting) {
41
+ this.mouseInput.delta$
42
+ .pipe(takeUntil(this._onRemoved$), filter(() => this.mouseInput.state == MouseInputState.DRAG))
43
+ .subscribe(delta => {
44
+ this.spherical.theta -= (delta.x * this.options.orbiting.sensitivityX * Math.PI) / 900;
45
+ this.spherical.phi -= (delta.y * this.options.orbiting.sensitivityY * Math.PI) / 900;
46
+ this.spherical.phi = Math.max(0.000001, Math.min(Math.PI - 0.000001, this.spherical.phi));
47
+ });
48
+ }
49
+ if (this.options.zooming) {
50
+ this.mouseInput.wheel$.pipe(takeUntil(this._onRemoved$)).subscribe(delta => {
51
+ if (delta != 0) {
52
+ this.spherical.radius *= Math.pow(0.95, this.options.zooming.sensitivity * (delta > 0 ? -1 : 1));
53
+ }
54
+ });
55
+ }
56
+ const performPan = (delta) => {
57
+ const targetCameraVector = Pnt3.fromSpherical(this.spherical);
58
+ const viewUp = Pnt3.rotAround(targetCameraVector, {
59
+ x: -Math.sin(this.spherical.theta),
60
+ y: Math.cos(this.spherical.theta),
61
+ z: 0,
62
+ }, Math.PI / 2);
63
+ const viewRight = Pnt3.rotAround(targetCameraVector, Pnt3.norm(viewUp), Math.PI / 2);
64
+ this.options.target = Pnt3.add(this.options.target, Pnt3.add(Pnt3.scalarMult(viewUp, (-this.options.panning.sensitivityY * delta.y) / 1000), Pnt3.scalarMult(viewRight, (this.options.panning.sensitivityX * delta.x) / 1000)));
65
+ };
66
+ if (this.options.panning) {
67
+ this.mouseInput.delta$
68
+ .pipe(takeUntil(this._onRemoved$), filter(() => this.mouseInput.state == MouseInputState.DRAG_RIGHT_BUTTON))
69
+ .subscribe(delta => {
70
+ performPan(delta);
71
+ });
72
+ }
73
+ if (this.options.dollying) {
74
+ this.mouseInput.delta$
75
+ .pipe(takeUntil(this._onRemoved$), filter(() => this.mouseInput.state == MouseInputState.DRAG_MIDDLE_BUTTON))
76
+ .subscribe(delta => {
77
+ this.spherical.radius *= Math.pow(0.95, (-this.options.dollying.sensitivity * delta.y) / 10);
78
+ });
79
+ }
80
+ if (MouseInput.isTouchDevice() && (this.options.dollying || this.options.panning)) {
81
+ this.mouseInput.twoTouchGestureDelta$.pipe(takeUntil(this._onRemoved$)).subscribe(delta => {
82
+ // dolly on fingers pitch
83
+ if (this.options.dollying) {
84
+ this.spherical.radius *= Math.pow(0.95, (this.options.dollying.sensitivity * delta.distanceDelta) / 10);
85
+ }
86
+ // pan on fingers move
87
+ if (this.options.panning) {
88
+ performPan(delta.centerPointDelta);
89
+ }
90
+ });
91
+ }
92
+ // Setup updating camera position and rotation based on input
93
+ this.camera.tick$
94
+ .pipe(takeUntil(this._onRemoved$), map(() => this.spherical))
95
+ .subscribe(spherical => {
96
+ this.camera.position = Pnt3.add(this.options.target, Pnt3.fromSpherical(spherical));
97
+ this.camera.rotation = Qtrn.lookAt(this.camera.position, this.options.target, { x: 0, y: 0, z: 1 });
98
+ });
99
+ // start input
100
+ yield this.mouseInput.start();
101
+ });
102
+ }
103
+ onRemoved() {
104
+ const _super = Object.create(null, {
105
+ onRemoved: { get: () => super.onRemoved }
106
+ });
107
+ return __awaiter(this, void 0, void 0, function* () {
108
+ yield _super.onRemoved.call(this);
109
+ yield this.mouseInput.stop(true);
110
+ });
111
+ }
112
+ }
@@ -1,20 +1,60 @@
1
1
  import { GgEntity, GGTickOrder } from './gg-entity';
2
2
  import { Point2 } from '../models/points';
3
3
  import { GgWorld } from '../gg-world';
4
+ import { BehaviorSubject, Observable } from 'rxjs';
5
+ /**
6
+ * Represents the options that can be passed to a renderer.
7
+ * @typedef {Object} RendererOptions
8
+ * @property {boolean} transparent - Specifies whether pixels can be transparent. false by default.
9
+ * @property {number} background - Specifies the background color of the renderer. black by default.
10
+ * @property {Point2 | 'fullscreen' | ((pageSize: Point2) => Point2) | Observable<Point2>} size - Specifies the size of the renderer and canvas if set. 'fullscreen' by default.
11
+ * @property {number} [forceResolution] - Specifies the pixel resolution. Not set by default, which means "use device resolution".
12
+ * @property {boolean} antialias - Specifies whether antialiasing is turned on/off. true by default.
13
+ */
4
14
  export declare type RendererOptions = {
5
15
  transparent: boolean;
6
16
  background: number;
7
- forceRendererSize?: Point2;
17
+ size: Point2 | 'fullscreen' | ((pageSize: Point2) => Point2) | Observable<Point2>;
8
18
  forceResolution?: number;
9
19
  antialias: boolean;
10
20
  };
21
+ /**
22
+ * Represents an abstract base class for a renderer controller.
23
+ * @abstract
24
+ * @class
25
+ */
11
26
  export declare abstract class BaseGgRenderer extends GgEntity {
12
27
  protected readonly canvas?: HTMLCanvasElement | undefined;
13
28
  readonly tickOrder = GGTickOrder.RENDERING;
29
+ /** Specifies the options for the renderer. */
14
30
  readonly rendererOptions: RendererOptions;
31
+ /** Represents the current size of the renderer. */
32
+ protected _rendererSize$: BehaviorSubject<Point2 | null>;
33
+ /**
34
+ * Gets the observable that represents the current size of the renderer.
35
+ * @returns {Observable<Point2 | null>} - An observable that represents the size of the renderer.
36
+ */
37
+ get rendererSize$(): Observable<Point2 | null>;
38
+ /**
39
+ Gets the current size of the renderer.
40
+ @returns {Point2 | null} - The size of the renderer.
41
+ */
42
+ get rendererSize(): Point2 | null;
43
+ /**
44
+ Initializes a new instance of the BaseGgRenderer class.
45
+ @param {HTMLCanvasElement} [canvas] - The canvas element to render onto.
46
+ @param {Partial<RendererOptions>} [options={}] - The options to configure the renderer.
47
+ */
15
48
  protected constructor(canvas?: HTMLCanvasElement | undefined, options?: Partial<RendererOptions>);
49
+ /**
50
+ * Renders the scene.
51
+ */
16
52
  abstract render(): void;
17
- abstract resize(newSize: Point2): void;
53
+ /**
54
+ * Resizes the renderer to the specified size.
55
+ * @param {Point2} newSize - The new size of the renderer.
56
+ */
57
+ protected abstract resizeRenderer(newSize: Point2): void;
18
58
  onSpawned(world: GgWorld<any, any>): void;
19
- onRemoved(): void;
59
+ dispose(): void;
20
60
  }
@@ -1,29 +1,81 @@
1
1
  import { GgEntity, GGTickOrder } from './gg-entity';
2
- import { GgViewportManager } from '../gg-viewport-manager';
2
+ import { auditTime, BehaviorSubject, fromEvent, merge, Observable, takeUntil } from 'rxjs';
3
+ import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
4
+ const DEFAULT_RENDERER_OPTIONS = {
5
+ transparent: false,
6
+ background: 0x000000,
7
+ size: 'fullscreen',
8
+ antialias: true,
9
+ };
10
+ /**
11
+ * Represents an abstract base class for a renderer controller.
12
+ * @abstract
13
+ * @class
14
+ */
3
15
  export class BaseGgRenderer extends GgEntity {
16
+ /**
17
+ Initializes a new instance of the BaseGgRenderer class.
18
+ @param {HTMLCanvasElement} [canvas] - The canvas element to render onto.
19
+ @param {Partial<RendererOptions>} [options={}] - The options to configure the renderer.
20
+ */
4
21
  constructor(canvas, options = {}) {
5
22
  super();
6
23
  this.canvas = canvas;
7
24
  this.tickOrder = GGTickOrder.RENDERING;
8
- this.rendererOptions = Object.assign({ transparent: false, background: 0x000000, antialias: true }, (options || {}));
25
+ /** Represents the current size of the renderer. */
26
+ this._rendererSize$ = new BehaviorSubject(null);
27
+ this.rendererOptions = Object.assign(Object.assign({}, DEFAULT_RENDERER_OPTIONS), (options || {}));
9
28
  this.tick$.subscribe(() => {
10
29
  this.render();
11
30
  });
12
31
  }
32
+ /**
33
+ * Gets the observable that represents the current size of the renderer.
34
+ * @returns {Observable<Point2 | null>} - An observable that represents the size of the renderer.
35
+ */
36
+ get rendererSize$() {
37
+ return this._rendererSize$.asObservable();
38
+ }
39
+ /**
40
+ Gets the current size of the renderer.
41
+ @returns {Point2 | null} - The size of the renderer.
42
+ */
43
+ get rendererSize() {
44
+ return this._rendererSize$.getValue();
45
+ }
13
46
  onSpawned(world) {
14
47
  super.onSpawned(world);
15
- if (this.canvas) {
16
- setTimeout(() => {
17
- GgViewportManager.instance.assignRendererToCanvas(this, this.canvas).then();
18
- }, 0);
48
+ this._rendererSize$.next(null);
49
+ if (this.rendererOptions.size == 'fullscreen' || this.rendererOptions.size instanceof Function) {
50
+ if (this.canvas) {
51
+ this.canvas.style.position = 'absolute';
52
+ }
53
+ merge(fromEvent(window, 'resize').pipe(auditTime(100)), fromEvent(window, 'orientationchange'))
54
+ .pipe(takeUntil(this._onRemoved$), map(() => ({ x: window.innerWidth, y: window.innerHeight })), startWith({ x: window.innerWidth, y: window.innerHeight }))
55
+ .subscribe(size => {
56
+ this._rendererSize$.next(this.rendererOptions.size instanceof Function ? this.rendererOptions.size(size) : size);
57
+ });
19
58
  }
20
- }
21
- onRemoved() {
22
- super.onRemoved();
23
- if (this.canvas) {
24
- setTimeout(() => {
25
- GgViewportManager.instance.deregisterCanvas(this.canvas).then();
26
- }, 0);
59
+ else if (this.rendererOptions.size instanceof Observable ||
60
+ // for cases when project uses two separate rxjs packages, instanceof can return false for observable
61
+ (!!this.rendererOptions.subscribe !== undefined && !!this.rendererOptions.pipe !== undefined)) {
62
+ this.rendererOptions.size.pipe(takeUntil(this._onRemoved$)).subscribe(newSize => {
63
+ this._rendererSize$.next(newSize);
64
+ });
27
65
  }
66
+ else {
67
+ this._rendererSize$.next(this.rendererOptions.size);
68
+ }
69
+ this._rendererSize$
70
+ .pipe(takeUntil(this._onRemoved$), distinctUntilChanged((a, b) => (a === null || a === void 0 ? void 0 : a.x) == (b === null || b === void 0 ? void 0 : b.x) && (a === null || a === void 0 ? void 0 : a.y) == (b === null || b === void 0 ? void 0 : b.y)))
71
+ .subscribe(size => {
72
+ if (size) {
73
+ this.resizeRenderer(size);
74
+ }
75
+ });
76
+ }
77
+ dispose() {
78
+ super.dispose();
79
+ this._rendererSize$.complete();
28
80
  }
29
81
  }
@@ -1,49 +1,84 @@
1
1
  import { Input } from './input';
2
2
  import { Observable } from 'rxjs';
3
3
  import { Point2 } from '../models/points';
4
- /**
5
- * Options for pointer lock in a MouseInput.
6
- *
7
- * ignoreMovementWhenNotLocked: Whether to ignore mouse movement when pointer lock is not active.
8
- *
9
- * canvas: The canvas element to request pointer lock on.
10
- */
11
- export declare type MouseInputPointLockOptions = {
12
- ignoreMovementWhenNotLocked: boolean;
13
- canvas: HTMLCanvasElement;
14
- };
15
4
  /**
16
5
  * Options for a MouseInput.
17
6
  *
18
- * pointerLock: The options for pointer lock. Do not provide it to disable pointer lock functionality
7
+ * canvas?: Canvas element. If not provided, mouse events will be listened on the whole window
8
+ * pointerLock: The flag to enable pointer lock when clicking on canvas
19
9
  */
20
10
  export declare type MouseInputOptions = {
21
- pointerLock?: MouseInputPointLockOptions;
11
+ canvas?: HTMLCanvasElement;
12
+ pointerLock: boolean;
22
13
  };
23
14
  /**
24
- * A class representing mouse input.
15
+ Represents the state of the mouse/touch input on the screen.
25
16
  */
26
- export declare class MouseInput extends Input<[], [unlockPointer?: boolean]> {
27
- private readonly options;
17
+ export declare enum MouseInputState {
18
+ /**
19
+ No mouse or touch input is detected. Mouse move can still be emitted
20
+ */
21
+ NONE = 0,
22
+ /**
23
+ The left mouse button or a single touch is being dragged on the screen.
24
+ */
25
+ DRAG = 1,
26
+ /**
27
+ The middle mouse button is being dragged on the screen.
28
+ */
29
+ DRAG_MIDDLE_BUTTON = 2,
28
30
  /**
29
- * An observable of the change in the x position of the mouse.
31
+ The right mouse button is being dragged on the screen.
30
32
  */
31
- get deltaX$(): Observable<number>;
33
+ DRAG_RIGHT_BUTTON = 3,
32
34
  /**
33
- * An observable of the change in the y position of the mouse.
35
+ *Two fingers are being used to drag on the screen.
34
36
  */
35
- get deltaY$(): Observable<number>;
37
+ DRAG_TOUCH_TWO_FINGERS = 4
38
+ }
39
+ /**
40
+ * A class representing mouse input.
41
+ */
42
+ export declare class MouseInput extends Input<[], [unlockPointer?: boolean]> {
43
+ static isTouchDevice(): boolean;
36
44
  /**
37
45
  An observable of the change in the position of the mouse.
38
46
  */
39
47
  get delta$(): Observable<Point2>;
48
+ /**
49
+ A global position of the mouse.
50
+ */
51
+ get position(): Point2;
52
+ /**
53
+ An observable of the global position of the mouse.
54
+ */
55
+ get position$(): Observable<Point2>;
56
+ /**
57
+ An observable of the wheel scrolling.
58
+ */
59
+ get wheel$(): Observable<number>;
60
+ get isPointerLocked(): boolean;
61
+ private readonly options;
40
62
  private _delta$;
63
+ private _position$;
64
+ private _multiTouchPositions$;
65
+ private _wheel$;
41
66
  private stopped$;
67
+ private _state$;
68
+ get state(): MouseInputState;
69
+ get state$(): Observable<MouseInputState>;
70
+ get multiTouchPositions$(): Observable<Point2[]>;
71
+ get twoTouchGestureDelta$(): Observable<{
72
+ centerPointDelta: Point2;
73
+ angleDelta: number;
74
+ distanceDelta: number;
75
+ }>;
76
+ private get _element();
42
77
  /**
43
78
  Creates an instance of MouseInput.
44
79
  @param {MouseInputOptions} options - The options for the MouseInput.
45
80
  */
46
- constructor(options?: MouseInputOptions);
81
+ constructor(options?: Partial<MouseInputOptions>);
47
82
  protected startInternal(): Promise<void>;
48
83
  /**
49
84
  Stop listening for mouse movement events.
@@ -8,8 +8,38 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { Input } from './input';
11
- import { filter, fromEvent, Subject, takeUntil } from 'rxjs';
12
- import { map } from 'rxjs/operators';
11
+ import { BehaviorSubject, filter, fromEvent, NEVER, Subject, takeUntil } from 'rxjs';
12
+ import { map, pairwise, switchMap } from 'rxjs/operators';
13
+ import { Pnt2 } from '../math/point2';
14
+ const DEFAULT_MOUSE_INPUT_OPTIONS = {
15
+ pointerLock: false,
16
+ };
17
+ /**
18
+ Represents the state of the mouse/touch input on the screen.
19
+ */
20
+ export var MouseInputState;
21
+ (function (MouseInputState) {
22
+ /**
23
+ No mouse or touch input is detected. Mouse move can still be emitted
24
+ */
25
+ MouseInputState[MouseInputState["NONE"] = 0] = "NONE";
26
+ /**
27
+ The left mouse button or a single touch is being dragged on the screen.
28
+ */
29
+ MouseInputState[MouseInputState["DRAG"] = 1] = "DRAG";
30
+ /**
31
+ The middle mouse button is being dragged on the screen.
32
+ */
33
+ MouseInputState[MouseInputState["DRAG_MIDDLE_BUTTON"] = 2] = "DRAG_MIDDLE_BUTTON";
34
+ /**
35
+ The right mouse button is being dragged on the screen.
36
+ */
37
+ MouseInputState[MouseInputState["DRAG_RIGHT_BUTTON"] = 3] = "DRAG_RIGHT_BUTTON";
38
+ /**
39
+ *Two fingers are being used to drag on the screen.
40
+ */
41
+ MouseInputState[MouseInputState["DRAG_TOUCH_TWO_FINGERS"] = 4] = "DRAG_TOUCH_TWO_FINGERS";
42
+ })(MouseInputState || (MouseInputState = {}));
13
43
  /**
14
44
  * A class representing mouse input.
15
45
  */
@@ -20,39 +50,151 @@ export class MouseInput extends Input {
20
50
  */
21
51
  constructor(options = {}) {
22
52
  super();
23
- this.options = options;
24
53
  this._delta$ = new Subject();
54
+ this._position$ = new BehaviorSubject({ x: 0, y: 0 });
55
+ this._multiTouchPositions$ = new BehaviorSubject([]);
56
+ this._wheel$ = new Subject();
25
57
  this.stopped$ = new Subject();
58
+ this._state$ = new BehaviorSubject(MouseInputState.NONE);
59
+ this.options = Object.assign(Object.assign({}, DEFAULT_MOUSE_INPUT_OPTIONS), options);
26
60
  this.canvasClickListener = this.canvasClickListener.bind(this);
27
61
  }
62
+ static isTouchDevice() {
63
+ return ('createTouch' in document ||
64
+ !!navigator.userAgent.match(/(iPhone|iPod|iPad)/) ||
65
+ !!navigator.userAgent.match(/Android/));
66
+ }
67
+ /**
68
+ An observable of the change in the position of the mouse.
69
+ */
70
+ get delta$() {
71
+ return this._delta$.asObservable();
72
+ }
28
73
  /**
29
- * An observable of the change in the x position of the mouse.
74
+ A global position of the mouse.
30
75
  */
31
- get deltaX$() {
32
- return this._delta$.pipe(map(d => d.x));
76
+ get position() {
77
+ return this._position$.getValue();
33
78
  }
34
79
  /**
35
- * An observable of the change in the y position of the mouse.
80
+ An observable of the global position of the mouse.
36
81
  */
37
- get deltaY$() {
38
- return this._delta$.pipe(map(d => d.y));
82
+ get position$() {
83
+ return this._position$.asObservable();
39
84
  }
40
85
  /**
41
- An observable of the change in the position of the mouse.
86
+ An observable of the wheel scrolling.
42
87
  */
43
- get delta$() {
44
- return this._delta$.asObservable();
88
+ get wheel$() {
89
+ return this._wheel$.asObservable();
90
+ }
91
+ get isPointerLocked() {
92
+ return !!document.pointerLockElement;
93
+ }
94
+ get state() {
95
+ return this._state$.getValue();
96
+ }
97
+ get state$() {
98
+ return this._state$.asObservable();
99
+ }
100
+ get multiTouchPositions$() {
101
+ return this._multiTouchPositions$.asObservable();
102
+ }
103
+ get twoTouchGestureDelta$() {
104
+ return this.state$.pipe(switchMap((s) => s == MouseInputState.DRAG_TOUCH_TWO_FINGERS ? this.multiTouchPositions$ : NEVER), map((p) => p.map(v => ({ x: v.x, y: v.y }))), pairwise(), filter(([prev, cur]) => prev.length > 1 && cur.length > 1), map(([prev, cur]) => ({
105
+ centerPointDelta: Pnt2.sub(Pnt2.scalarMult(cur.reduce((p, c) => Pnt2.add(p, c), { x: 0, y: 0 }), 1 / cur.length), Pnt2.scalarMult(prev.reduce((p, c) => Pnt2.add(p, c), { x: 0, y: 0 }), 1 / cur.length)),
106
+ angleDelta: Pnt2.angle(cur[1], cur[0]) - Pnt2.angle(prev[1], prev[0]),
107
+ distanceDelta: Pnt2.dist(cur[1], cur[0]) - Pnt2.dist(prev[1], prev[0]),
108
+ })));
109
+ }
110
+ get _element() {
111
+ return this.options.canvas || window;
45
112
  }
46
113
  startInternal() {
47
114
  return __awaiter(this, void 0, void 0, function* () {
48
- fromEvent(window, 'mousemove')
49
- .pipe(takeUntil(this.stopped$), filter(() => !this.options.pointerLock ||
50
- !this.options.pointerLock.ignoreMovementWhenNotLocked ||
51
- !!document.pointerLockElement), map((e) => ({ x: e.movementX, y: e.movementY })))
52
- .subscribe(v => this._delta$.next(v));
53
- if (!!this.options.pointerLock) {
54
- this.options.pointerLock.canvas.addEventListener('click', this.canvasClickListener);
115
+ if (this.options.canvas) {
116
+ this.options.canvas.style.touchAction = 'none';
117
+ }
118
+ this._state$.next(MouseInputState.NONE);
119
+ const mouseButtonStateMap = [
120
+ MouseInputState.DRAG,
121
+ MouseInputState.DRAG_MIDDLE_BUTTON,
122
+ MouseInputState.DRAG_RIGHT_BUTTON,
123
+ ];
124
+ const pointerLengthsStateMap = [MouseInputState.NONE, MouseInputState.DRAG, MouseInputState.DRAG_TOUCH_TWO_FINGERS];
125
+ const pointers = [];
126
+ const pointerPositions = {};
127
+ fromEvent(this._element, 'mousemove')
128
+ .pipe(takeUntil(this.stopped$))
129
+ .subscribe((event) => {
130
+ this._delta$.next({ x: event.movementX, y: event.movementY });
131
+ });
132
+ fromEvent(this._element, 'pointermove')
133
+ .pipe(takeUntil(this.stopped$))
134
+ .subscribe((event) => {
135
+ if (event instanceof PointerEvent) {
136
+ if (event.pointerType === 'touch') {
137
+ pointerPositions[event.pointerId] = { x: event.pageX, y: event.pageY };
138
+ }
139
+ const newPosition = { x: event.pageX, y: event.pageY };
140
+ this._position$.next(newPosition);
141
+ this._multiTouchPositions$.next(Object.values(pointerPositions));
142
+ }
143
+ else {
144
+ this._position$.next({ x: event.clientX, y: event.clientY });
145
+ }
146
+ this._delta$.next({ x: event.movementX, y: event.movementY });
147
+ });
148
+ if (!MouseInput.isTouchDevice() && this.options.pointerLock && this.options.canvas) {
149
+ this.options.canvas.addEventListener('click', this.canvasClickListener);
55
150
  }
151
+ const onPointerUp = (event) => {
152
+ delete pointerPositions[event.pointerId];
153
+ for (let i = 0; i < pointers.length; i++) {
154
+ if (pointers[i].pointerId == event.pointerId) {
155
+ pointers.splice(i, 1);
156
+ break;
157
+ }
158
+ }
159
+ if (pointers.length === 0) {
160
+ if (this.options.canvas) {
161
+ this.options.canvas.releasePointerCapture(event.pointerId);
162
+ }
163
+ this._element.removeEventListener('pointerup', onPointerUp);
164
+ this._element.removeEventListener('pointercancel', onPointerUp);
165
+ }
166
+ this._state$.next(pointerLengthsStateMap[Math.min(pointers.length, 2)]);
167
+ };
168
+ fromEvent(this._element, 'pointerdown')
169
+ .pipe(takeUntil(this.stopped$))
170
+ .subscribe((event) => {
171
+ if (pointers.length === 0) {
172
+ if (this.options.canvas) {
173
+ this.options.canvas.setPointerCapture(event.pointerId);
174
+ }
175
+ this._element.addEventListener('pointerup', onPointerUp);
176
+ this._element.addEventListener('pointercancel', onPointerUp);
177
+ }
178
+ pointers.push(event);
179
+ if (event.pointerType === 'touch') {
180
+ pointerPositions[event.pointerId] = { x: event.pageX, y: event.pageY };
181
+ this._state$.next(pointerLengthsStateMap[Math.min(pointers.length, 2)]);
182
+ }
183
+ else {
184
+ this._state$.next(mouseButtonStateMap[event.button] || MouseInputState.NONE);
185
+ }
186
+ });
187
+ fromEvent(this._element, 'contextmenu')
188
+ .pipe(takeUntil(this.stopped$))
189
+ .subscribe(event => {
190
+ event.preventDefault();
191
+ });
192
+ fromEvent(this._element, 'wheel', { passive: false })
193
+ .pipe(takeUntil(this.stopped$))
194
+ .subscribe(e => {
195
+ e.preventDefault();
196
+ this._wheel$.next(e.deltaY);
197
+ });
56
198
  });
57
199
  }
58
200
  /**
@@ -62,8 +204,8 @@ export class MouseInput extends Input {
62
204
  stopInternal(unlockPointer = true) {
63
205
  return __awaiter(this, void 0, void 0, function* () {
64
206
  this.stopped$.next();
65
- if (unlockPointer && !!this.options.pointerLock) {
66
- this.options.pointerLock.canvas.removeEventListener('click', this.canvasClickListener);
207
+ if (unlockPointer && !!this.options.canvas) {
208
+ this.options.canvas.removeEventListener('click', this.canvasClickListener);
67
209
  document.exitPointerLock();
68
210
  }
69
211
  });
@@ -72,6 +214,6 @@ export class MouseInput extends Input {
72
214
  Request pointer lock on the canvas element.
73
215
  */
74
216
  canvasClickListener() {
75
- this.options.pointerLock.canvas.requestPointerLock();
217
+ this.options.canvas.requestPointerLock();
76
218
  }
77
219
  }
@@ -6,6 +6,12 @@ export declare class Pnt2 {
6
6
  static add(a: Point2, b: Point2): Point2;
7
7
  /** subtract point b from point a */
8
8
  static sub(a: Point2, b: Point2): Point2;
9
+ /** calculate vector length (squared) */
10
+ static lenSq(v: Point2): number;
11
+ /** calculate vector length */
12
+ static len(v: Point2): number;
13
+ /** distance between points */
14
+ static dist(a: Point2, b: Point2): number;
9
15
  /** normalize */
10
16
  static norm(p: Point2): Point2;
11
17
  /** scalar multiplication */
@@ -11,6 +11,18 @@ export class Pnt2 {
11
11
  static sub(a, b) {
12
12
  return { x: a.x - b.x, y: a.y - b.y };
13
13
  }
14
+ /** calculate vector length (squared) */
15
+ static lenSq(v) {
16
+ return v.x * v.x + v.y * v.y;
17
+ }
18
+ /** calculate vector length */
19
+ static len(v) {
20
+ return Math.sqrt(v.x * v.x + v.y * v.y);
21
+ }
22
+ /** distance between points */
23
+ static dist(a, b) {
24
+ return Pnt2.len(Pnt2.sub(a, b));
25
+ }
14
26
  /** normalize */
15
27
  static norm(p) {
16
28
  const length = Math.sqrt(p.x ** 2 + p.y ** 2);
@@ -1,4 +1,4 @@
1
- import { Point3, Point4 } from '../models/points';
1
+ import { Point3, Point4, Spherical } from '../models/points';
2
2
  export declare class Pnt3 {
3
3
  /** clone point */
4
4
  static clone(p: Point3): Point3;
@@ -10,6 +10,8 @@ export declare class Pnt3 {
10
10
  static lenSq(v: Point3): number;
11
11
  /** calculate vector length */
12
12
  static len(v: Point3): number;
13
+ /** distance between points */
14
+ static dist(a: Point3, b: Point3): number;
13
15
  /** cross vectors */
14
16
  static cross(a: Point3, b: Point3): Point3;
15
17
  /** normalize */
@@ -24,4 +26,18 @@ export declare class Pnt3 {
24
26
  static rot(p: Point3, q: Point4): Point3;
25
27
  /** rotate point around axis a (normalized vector) */
26
28
  static rotAround(p: Point3, axis: Point3, angle: number): Point3;
29
+ /**
30
+ * Converts a cartesian 3D point to a spherical coordinate system, where theta is azimuth and phi is inclination,
31
+ * theta == 0 is faced towards X axis direction, and phi == 0 is faced towards zenith (Z axis)
32
+ * @param p - The cartesian 3D point.
33
+ * @returns The spherical coordinates as an object with radius, theta, and phi properties.
34
+ */
35
+ static toSpherical(p: Point3): Spherical;
36
+ /**
37
+ * Converts a spherical coordinate system to a cartesian 3D point. Used spherical coordinates, where theta is azimuth
38
+ * and phi is inclination, theta == 0 is faced towards X axis direction, and phi == 0 is faced towards zenith (Z axis)
39
+ * @param s - The spherical coordinate system.
40
+ * @returns The cartesian 3D point as an object with x, y, and z properties.
41
+ */
42
+ static fromSpherical(s: Spherical): Point3;
27
43
  }
@@ -20,6 +20,10 @@ export class Pnt3 {
20
20
  static len(v) {
21
21
  return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
22
22
  }
23
+ /** distance between points */
24
+ static dist(a, b) {
25
+ return Pnt3.len(Pnt3.sub(a, b));
26
+ }
23
27
  /** cross vectors */
24
28
  static cross(a, b) {
25
29
  const ax = a.x, ay = a.y, az = a.z;
@@ -103,4 +107,31 @@ export class Pnt3 {
103
107
  static rotAround(p, axis, angle) {
104
108
  return this.rot(p, Qtrn.fromAngle(axis, angle));
105
109
  }
110
+ /**
111
+ * Converts a cartesian 3D point to a spherical coordinate system, where theta is azimuth and phi is inclination,
112
+ * theta == 0 is faced towards X axis direction, and phi == 0 is faced towards zenith (Z axis)
113
+ * @param p - The cartesian 3D point.
114
+ * @returns The spherical coordinates as an object with radius, theta, and phi properties.
115
+ */
116
+ static toSpherical(p) {
117
+ const radius = Math.sqrt(p.x * p.x + p.y * p.y + p.z * p.z);
118
+ return {
119
+ radius,
120
+ theta: radius == 0 ? 0 : Math.atan2(p.y, p.x),
121
+ phi: radius == 0 ? 0 : Math.atan2(Math.sqrt(p.x * p.x + p.y * p.y), p.z),
122
+ };
123
+ }
124
+ /**
125
+ * Converts a spherical coordinate system to a cartesian 3D point. Used spherical coordinates, where theta is azimuth
126
+ * and phi is inclination, theta == 0 is faced towards X axis direction, and phi == 0 is faced towards zenith (Z axis)
127
+ * @param s - The spherical coordinate system.
128
+ * @returns The cartesian 3D point as an object with x, y, and z properties.
129
+ */
130
+ static fromSpherical(s) {
131
+ return {
132
+ x: s.radius * Math.sin(s.phi) * Math.cos(s.theta),
133
+ y: s.radius * Math.sin(s.phi) * Math.sin(s.theta),
134
+ z: s.radius * Math.cos(s.phi),
135
+ };
136
+ }
106
137
  }
@@ -13,3 +13,13 @@ export declare type Point4 = {
13
13
  readonly z: number;
14
14
  readonly w: number;
15
15
  };
16
+ export declare type Spherical = {
17
+ readonly radius: number;
18
+ readonly phi: number;
19
+ readonly theta: number;
20
+ };
21
+ export declare type MutableSpherical = {
22
+ radius: number;
23
+ phi: number;
24
+ theta: number;
25
+ };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  export * from './base/clock/i-clock';
2
2
  export * from './base/clock/pausable-clock';
3
3
  export * from './base/clock/global-clock';
4
- export * from './base/gg-viewport';
5
- export * from './base/gg-viewport-manager';
6
4
  export * from './base/entities/controllers/animation-mixer';
7
5
  export * from './base/entities/gg-entity';
8
6
  export * from './base/entities/base-gg-renderer';
@@ -46,6 +44,7 @@ export * from './3d/entities/gg-3d-raycast-vehicle.entity';
46
44
  export * from './3d/entities/gg-3d-trigger.entity';
47
45
  export * from './3d/entities/controllers/input/car-keyboard-handling.controller';
48
46
  export * from './3d/entities/controllers/input/free-camera.controller';
47
+ export * from './3d/entities/controllers/input/orbit-camera.controller';
49
48
  export * from './3d/models/body-options';
50
49
  export * from './3d/models/gg-meta';
51
50
  export * from './3d/models/shapes';
package/dist/index.js CHANGED
@@ -1,8 +1,6 @@
1
1
  export * from './base/clock/i-clock';
2
2
  export * from './base/clock/pausable-clock';
3
3
  export * from './base/clock/global-clock';
4
- export * from './base/gg-viewport';
5
- export * from './base/gg-viewport-manager';
6
4
  export * from './base/entities/controllers/animation-mixer';
7
5
  export * from './base/entities/gg-entity';
8
6
  export * from './base/entities/base-gg-renderer';
@@ -46,6 +44,7 @@ export * from './3d/entities/gg-3d-raycast-vehicle.entity';
46
44
  export * from './3d/entities/gg-3d-trigger.entity';
47
45
  export * from './3d/entities/controllers/input/car-keyboard-handling.controller';
48
46
  export * from './3d/entities/controllers/input/free-camera.controller';
47
+ export * from './3d/entities/controllers/input/orbit-camera.controller';
49
48
  export * from './3d/models/body-options';
50
49
  export * from './3d/models/gg-meta';
51
50
  export * from './3d/models/shapes';
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@gg-web-engine/core",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "An attempt to create open source game engine for browser",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "test": "jest",
9
9
  "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write",
10
- "prepublish": "tsc",
10
+ "prepublish": "rm -rf ./dist/ && tsc",
11
11
  "build": "tsc"
12
12
  },
13
13
  "repository": {
@@ -1,19 +0,0 @@
1
- import { Subject } from 'rxjs';
2
- import { BaseGgRenderer } from './entities/base-gg-renderer';
3
- export declare type CanvasAppDescr = {
4
- canvas: HTMLCanvasElement;
5
- renderer?: BaseGgRenderer;
6
- };
7
- export declare class GgViewportManager {
8
- private static _instance;
9
- static get instance(): GgViewportManager;
10
- private constructor();
11
- private readonly canvases;
12
- private gameStage$;
13
- protected destroyed: Subject<void>;
14
- private getStageAsync;
15
- createCanvas(zIndex: number): Promise<HTMLCanvasElement>;
16
- registerCanvas(canvas: HTMLCanvasElement, zIndex: number): Promise<void>;
17
- deregisterCanvas(canvas: HTMLCanvasElement): Promise<void>;
18
- assignRendererToCanvas(renderer: BaseGgRenderer, canvas: HTMLCanvasElement): Promise<void>;
19
- }
@@ -1,131 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import { BehaviorSubject, filter, first, Subject, takeUntil } from 'rxjs';
11
- import { GgViewport } from './gg-viewport';
12
- export class GgViewportManager {
13
- constructor() {
14
- this.canvases = {};
15
- this.gameStage$ = new BehaviorSubject(null);
16
- this.destroyed = new Subject();
17
- setTimeout(() => __awaiter(this, void 0, void 0, function* () {
18
- let retries = 120;
19
- let stage = null;
20
- while (retries > 0) {
21
- retries--;
22
- stage = document.getElementById('gg-stage');
23
- if (stage) {
24
- break;
25
- }
26
- yield 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
- }
31
- else {
32
- GgViewport.instance
33
- .subscribeOnViewportSize()
34
- .pipe(takeUntil(this.destroyed))
35
- .subscribe(size => {
36
- for (const zIndex of Object.keys(this.canvases)) {
37
- const canvasDescr = this.canvases[+zIndex];
38
- if (canvasDescr.renderer && !canvasDescr.renderer.rendererOptions.forceRendererSize) {
39
- canvasDescr.renderer.resize(size);
40
- }
41
- canvasDescr.canvas.width = size.x;
42
- canvasDescr.canvas.height = size.y;
43
- }
44
- });
45
- this.gameStage$.next(stage);
46
- }
47
- }), 0);
48
- }
49
- static get instance() {
50
- if (!this._instance) {
51
- this._instance = new GgViewportManager();
52
- }
53
- return this._instance;
54
- }
55
- getStageAsync() {
56
- const current = this.gameStage$.getValue();
57
- if (current) {
58
- return Promise.resolve(current);
59
- }
60
- return Promise.race([
61
- this.gameStage$
62
- .pipe(takeUntil(this.destroyed), filter(x => !!x), first())
63
- .toPromise(),
64
- new Promise((resolve, reject) => {
65
- setTimeout(() => {
66
- reject('Cannot find div with id "gg-stage" in 30 seconds');
67
- }, 30000);
68
- }),
69
- ]);
70
- }
71
- createCanvas(zIndex) {
72
- return __awaiter(this, void 0, void 0, function* () {
73
- zIndex = Math.round(zIndex);
74
- if (this.canvases[zIndex]) {
75
- throw new Error(`Cannot add canvas on zIndex ${zIndex}. Index is locked by another canvas`);
76
- }
77
- const stage = yield this.getStageAsync();
78
- const canvas = document.createElement('canvas');
79
- yield this.registerCanvas(canvas, zIndex);
80
- stage.appendChild(canvas);
81
- return canvas;
82
- });
83
- }
84
- registerCanvas(canvas, zIndex) {
85
- return __awaiter(this, void 0, void 0, function* () {
86
- canvas.style.zIndex = '' + zIndex;
87
- canvas.style.position = 'absolute';
88
- canvas.style.width = '100%';
89
- canvas.style.height = '100%';
90
- canvas.id = 'canvas-' + zIndex;
91
- this.canvases[zIndex] = { canvas };
92
- const size = GgViewport.instance.getCurrentViewportSize();
93
- canvas.width = size.x;
94
- canvas.height = size.y;
95
- });
96
- }
97
- deregisterCanvas(canvas) {
98
- return __awaiter(this, void 0, void 0, function* () {
99
- for (const index in this.canvases) {
100
- if (this.canvases[index].canvas === canvas) {
101
- delete this.canvases[index];
102
- break;
103
- }
104
- }
105
- });
106
- }
107
- assignRendererToCanvas(renderer, canvas) {
108
- return __awaiter(this, void 0, void 0, function* () {
109
- if (!GgViewport.instance.isActive) {
110
- GgViewport.instance.activate();
111
- }
112
- let zIndex = null;
113
- let maxExistingZIndex = 0;
114
- for (const index in this.canvases) {
115
- if (this.canvases[index].canvas === canvas) {
116
- zIndex = +index;
117
- break;
118
- }
119
- maxExistingZIndex = +index;
120
- }
121
- if (!zIndex) {
122
- zIndex = maxExistingZIndex + 1;
123
- yield this.registerCanvas(canvas, zIndex);
124
- }
125
- this.canvases[zIndex].renderer = renderer;
126
- if (!renderer.rendererOptions.forceRendererSize) {
127
- renderer.resize(GgViewport.instance.getCurrentViewportSize());
128
- }
129
- });
130
- }
131
- }
@@ -1,23 +0,0 @@
1
- import { Observable, Subject } from 'rxjs';
2
- import { Point2 } from './models/points';
3
- export declare class GgViewport {
4
- private static _instance;
5
- static get instance(): GgViewport;
6
- private constructor();
7
- protected destroy$: Subject<void> | null;
8
- private scenes;
9
- get isActive(): boolean;
10
- activate(): void;
11
- deactivate(): void;
12
- private viewportSize;
13
- getCurrentViewportSize(): Point2;
14
- subscribeOnViewportSize(): Observable<Point2>;
15
- private mousePosition;
16
- private mouseClicked;
17
- private isMouseDown;
18
- isMouseEnabled(): boolean;
19
- isTouchDevice(): boolean;
20
- subscribeOnMouseMove(): Observable<Point2>;
21
- subscribeOnIsMouseDown(): Observable<boolean>;
22
- subscribeOnMouseClick(): Observable<Point2>;
23
- }
@@ -1,119 +0,0 @@
1
- import { BehaviorSubject, distinctUntilChanged, fromEvent, merge, Subject, takeUntil } from 'rxjs';
2
- import { map } from 'rxjs/operators';
3
- const getCurrentWindowSize = () => {
4
- return {
5
- x: window.innerWidth,
6
- y: window.innerHeight,
7
- };
8
- };
9
- const getMousePositionFromEvent = (event) => {
10
- if (event instanceof MouseEvent) {
11
- return { x: event.x, y: event.y };
12
- }
13
- else if (event instanceof TouchEvent) {
14
- if (event.touches.length === 0) {
15
- return null;
16
- }
17
- return { x: event.touches[0].clientX, y: event.touches[0].clientY };
18
- }
19
- console.warn('Cannot determine mouse position from event', event);
20
- return null;
21
- };
22
- export class GgViewport {
23
- constructor() {
24
- this.destroy$ = null;
25
- this.scenes = [];
26
- // ================================================== VIEWPORT SIZE ==================================================
27
- this.viewportSize = new BehaviorSubject(getCurrentWindowSize());
28
- // ================================================== VIEWPORT SIZE ==================================================
29
- // ================================================== POINTER LOGIC ==================================================
30
- this.mousePosition = new BehaviorSubject({ x: 0, y: 0 });
31
- this.mouseClicked = new Subject();
32
- this.isMouseDown = new Subject();
33
- }
34
- static get instance() {
35
- if (!this._instance) {
36
- this._instance = new GgViewport();
37
- }
38
- return this._instance;
39
- }
40
- get isActive() {
41
- return !!this.destroy$;
42
- }
43
- activate() {
44
- if (this.destroy$) {
45
- throw new Error('GgViewport is already active');
46
- }
47
- this.destroy$ = new Subject();
48
- // cursor position
49
- merge(fromEvent(window, 'mousemove'), fromEvent(window, 'touchstart'), fromEvent(window, 'touchmove'))
50
- .pipe(takeUntil(this.destroy$), map(event => event))
51
- .subscribe(event => {
52
- const point = getMousePositionFromEvent(event);
53
- if (point) {
54
- this.mousePosition.next(point);
55
- }
56
- });
57
- // clicks
58
- merge(fromEvent(window, 'mousedown'), fromEvent(window, 'touchstart'))
59
- .pipe(takeUntil(this.destroy$), map(event => event))
60
- .subscribe(event => {
61
- const point = getMousePositionFromEvent(event);
62
- if (point) {
63
- this.mousePosition.next(point);
64
- }
65
- this.isMouseDown.next(true);
66
- });
67
- merge(fromEvent(window, 'mouseup'), fromEvent(window, 'click'), fromEvent(window, 'touchend'))
68
- .pipe(takeUntil(this.destroy$), map(event => event))
69
- .subscribe(event => {
70
- const point = getMousePositionFromEvent(event);
71
- if (point) {
72
- this.mousePosition.next(point);
73
- }
74
- this.isMouseDown.next(false);
75
- if (event.target['nodeName'] === 'canvas') {
76
- this.mouseClicked.next(this.mousePosition.getValue());
77
- }
78
- });
79
- // viewport size
80
- merge(fromEvent(window, 'resize'), fromEvent(window, 'orientationchange'))
81
- .pipe(takeUntil(this.destroy$), map(() => getCurrentWindowSize()))
82
- .subscribe(this.viewportSize);
83
- }
84
- deactivate() {
85
- if (!this.destroy$) {
86
- throw new Error('GgViewport is already inactive');
87
- }
88
- this.destroy$.next();
89
- this.destroy$.complete();
90
- this.destroy$ = null;
91
- }
92
- getCurrentViewportSize() {
93
- return this.viewportSize.getValue();
94
- }
95
- subscribeOnViewportSize() {
96
- return this.viewportSize.asObservable().pipe(distinctUntilChanged((v1, v2) => {
97
- return v1.x === v2.x && v1.y === v2.y;
98
- }));
99
- }
100
- isMouseEnabled() {
101
- return matchMedia('(hover:hover)').matches && matchMedia('(pointer:fine)').matches;
102
- }
103
- isTouchDevice() {
104
- return ('createTouch' in document ||
105
- !!navigator.userAgent.match(/(iPhone|iPod|iPad)/) ||
106
- !!navigator.userAgent.match(/Android/));
107
- }
108
- subscribeOnMouseMove() {
109
- return this.mousePosition.asObservable().pipe(distinctUntilChanged((v1, v2) => {
110
- return v1.x === v2.x && v1.y === v2.y;
111
- }));
112
- }
113
- subscribeOnIsMouseDown() {
114
- return this.isMouseDown.asObservable().pipe(distinctUntilChanged());
115
- }
116
- subscribeOnMouseClick() {
117
- return this.mouseClicked.asObservable();
118
- }
119
- }