-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcreateCamera.ts
More file actions
159 lines (144 loc) · 5.15 KB
/
Copy pathcreateCamera.ts
File metadata and controls
159 lines (144 loc) · 5.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Adapted from https://github.com/latticexyz/mud/blob/main/packages/phaserx/src/createCamera.ts
import {
BehaviorSubject,
Subject,
filter,
map,
sampleTime,
scan,
throttleTime,
} from "rxjs";
import { EventTypes, Gesture, UserHandlers } from "@use-gesture/vanilla";
export type GestureState<T extends keyof UserHandlers<EventTypes>> = Parameters<
UserHandlers<EventTypes>[T]
>[0];
export const createCamera = (
phaserCamera: Phaser.Cameras.Scene2D.Camera,
minZoom: number,
maxZoom: number,
pinchSpeed: number,
wheelSpeed: number,
dragSpeed: number = wheelSpeed
) => {
// Stop default gesture events to not collide with use-gesture
// https://github.com/pmndrs/use-gesture/blob/404e2b2ac145a45aff179c1faf5097b97414731c/documentation/pages/docs/gestures.mdx#about-the-pinch-gesture
document.addEventListener("gesturestart", (e) => e.preventDefault());
document.addEventListener("gesturechange", (e) => e.preventDefault());
const worldView$ = new BehaviorSubject<
Phaser.Cameras.Scene2D.Camera["worldView"]
>(phaserCamera.worldView);
const zoom$ = new BehaviorSubject<number>(phaserCamera.zoom);
const wheelStream$ = new Subject<GestureState<"onWheel">>();
const pinchStream$ = new Subject<GestureState<"onPinch">>();
const dragStream$ = new Subject<GestureState<"onDrag">>();
const gesture = new Gesture(
phaserCamera.scene.game.canvas,
{
onPinch: (state) => pinchStream$.next(state),
onWheel: (state) => wheelStream$.next(state),
onDrag: (state) => dragStream$.next(state),
},
{}
);
const onResize = () => {
requestAnimationFrame(() => worldView$.next(phaserCamera.worldView));
};
phaserCamera.scene.scale.addListener("resize", onResize);
function setZoom(zoom: number) {
phaserCamera.setZoom(zoom);
worldView$.next(phaserCamera.worldView);
zoom$.next(zoom);
}
function zoomTo(zoom: number, duration: number = 200) {
phaserCamera.zoomTo(zoom, duration, undefined, undefined, () => {
worldView$.next(phaserCamera.worldView);
});
zoom$.next(zoom);
}
const pinchSub = pinchStream$
.pipe(
throttleTime(10),
map((state) => {
// Because this event stream is throttled, we're dropping events which contain delta data, so we need to calculate the delta ourselves.
const zoom = zoom$.getValue();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const delta = state.offset[0] - zoom;
const scaledDelta = delta * pinchSpeed;
return zoom + scaledDelta;
}), // Compute pinch speed
map((zoom) => Math.min(Math.max(zoom, minZoom), maxZoom)), // Limit zoom values
scan((acc, curr) => [acc[1], curr], [1, 1]) // keep track of the last value to offset the map position (not implemented yet)
)
.subscribe(([, zoom]) => {
// Set the gesture zoom state to the current zoom value to avoid zooming beyond the max values
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (gesture._ctrl.state.pinch) gesture._ctrl.state.pinch.offset[0] = zoom;
setZoom(zoom);
});
const wheelSub = wheelStream$
.pipe(
filter((state) => !state.pinching),
sampleTime(10),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
map((state) => state.delta.map((x) => x * wheelSpeed)), // Compute wheel speed
map((movement) => movement.map((m: number) => m / phaserCamera.zoom)), // Adjust for current zoom value
map((movement) => [
phaserCamera.scrollX + movement[0],
phaserCamera.scrollY + movement[1],
]) // Compute new pinch
)
.subscribe(([x, y]) => {
phaserCamera.setScroll(x, y);
worldView$.next(phaserCamera.worldView);
});
const dragSub = dragStream$
.pipe(
filter((state) => !state.pinching || !state.wheeling),
sampleTime(10),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
map((state) => state.delta.map((x) => x * dragSpeed)),
map((movement) => movement.map((m: number) => m / phaserCamera.zoom)),
map((movement) => [
phaserCamera.scrollX - movement[0],
phaserCamera.scrollY - movement[1],
])
)
.subscribe(([x, y]) => {
phaserCamera.setScroll(x, y);
worldView$.next(phaserCamera.worldView);
});
function centerOn(x: number, y: number) {
phaserCamera.centerOn(x, y);
requestAnimationFrame(() => worldView$.next(phaserCamera.worldView));
}
function setScroll(x: number, y: number) {
phaserCamera.setScroll(x, y);
requestAnimationFrame(() => worldView$.next(phaserCamera.worldView));
}
function pan(x: number, y: number, duration: number = 200) {
phaserCamera.pan(x, y, duration, undefined, undefined, () => {
worldView$.next(phaserCamera.worldView);
});
}
return {
phaserCamera,
worldView$,
zoom$,
dispose: () => {
pinchSub.unsubscribe();
wheelSub.unsubscribe();
dragSub.unsubscribe();
gesture.destroy();
phaserCamera.scene.scale.removeListener("resize", onResize);
},
centerOn,
setScroll,
setZoom,
zoomTo,
pan,
};
};