EventMapper/assets/scripts/KaplayMap/map.js

399 lines
10 KiB
JavaScript

export class KaplayMap {
kp;
opts = {
minZoomLevel: 1,
maxZoomLevel: 6,
dblClickDuration: 0.2, // s
dblClickForgiveness: 100, // px
};
uiLayer;
#zoomLevel = this.opts.minZoomLevel;
#scaleTween = null;
startMousePos = null;
startCamPos = null;
startZoomLevel = null;
prevCamPos = null;
moveFriction = 0.25;
moveDead = 1;
#moveTween = null;
camBounds = null;
lastReleaseTime;
lastReleasePos;
lastPressTime;
lastPressPos;
mouseMode = "drag";
fingers = new Map([]);
startFingerDiff = null;
startFingerCenterPos = null;
startFingerZoomLevel = null;
// Map event listener
events;
constructor(kp, opts) {
this.kp = kp;
this.opts = {
...this.opts,
...opts,
};
this.events = new EventTarget();
this.zoomLevel_NoTween = this.opts.minZoomLevel;
this.kp.camPos(0);
this.camBounds = new this.kp.Rect(this.kp.vec2(-1), 2, 2);
this.kp.onScroll((delta) => {
const scrollDist = -delta.y / 120;
this.zoomTo(this.zoomLevel + scrollDist, this.kp.mousePos());
});
// Dragging
this.kp.onTouchStart((pos, touch) => {
this.fingers.set(touch.identifier, pos);
const fingerArr = Array.from(this.fingers.values());
this.startCamPos = this.kp.camPos();
this.startFingerCenterPos = fingerArr[0];
if (this.fingers.size > 1) {
this.clearMouseMode();
this.startFingerDiff = fingerArr[0].dist(fingerArr[1]);
const fingerDifference = fingerArr[0].sub(fingerArr[1]);
this.startFingerCenterPos = fingerArr[1].add(
fingerDifference.scale(0.5)
);
this.startFingerZoomLevel = this.zoomLevel;
this.mouseMode = "pinch";
} else if (this.fingers.size < 2) {
this.mouseMode = "drag";
}
});
this.kp.onTouchMove((pos, touch) => {
this.fingers.set(touch.identifier, pos);
});
this.kp.onTouchEnd((pos, touch) => {
if (this.fingers.size > 1) {
this.clearMouseMode();
}
this.fingers.delete(touch.identifier);
if (this.fingers.size < 1) {
this.checkBounds();
}
if (this.fingers.size < 2) {
const fingerArr = Array.from(this.fingers.values());
this.startFingerCenterPos = fingerArr[0];
this.startCamPos = this.kp.camPos();
this.startFingerDiff = null;
}
});
this.kp.onUpdate(() => {
const camScale = 1 / this.kp.camScale().y;
const fingerArr = Array.from(this.fingers.values());
if (this.fingers.size > 1) {
const curFingerDiff = fingerArr[0].dist(fingerArr[1]);
// Get centerpoint
const fingerDifference = fingerArr[0].sub(fingerArr[1]);
const fingerCenter = fingerArr[1].add(fingerDifference.scale(0.5));
this.zoomToNoTweenAbs(
this.startFingerZoomLevel +
Math.log2(curFingerDiff / this.startFingerDiff),
this.startCamPos.sub(
fingerCenter.sub(this.startFingerCenterPos).scale(camScale)
)
);
} else if (this.fingers.size == 1) {
if (this.mouseMode != "zoom") {
this.kp.camPos(
this.startCamPos.sub(
fingerArr[0].sub(this.startFingerCenterPos).scale(camScale)
)
);
}
}
});
this.kp.onUpdate(() => {
const curCamPos = this.kp.camPos();
const camScale = 1 / this.kp.camScale().y;
if (this.kp.isMousePressed()) {
// ignore if no double click
if (this.lastPressTime != null && this.lastPressPos != null) {
const clickDifference = this.kp.time() - this.lastPressTime;
const clickDistance = this.kp.mousePos().dist(this.lastPressPos);
// Double-click!
if (
clickDifference <= this.opts.dblClickDuration &&
clickDistance <= this.opts.dblClickForgiveness
) {
this.mouseMode = "zoom";
}
}
if (this.mouseMode == "drag") {
this.startMousePos = this.kp.mousePos();
this.startCamPos = this.kp.camPos();
} else if (this.mouseMode == "zoom") {
this.startMousePos = this.kp.mousePos();
this.startZoomLevel = this.zoomLevel;
}
// Set variables
this.lastPressTime = this.kp.time();
this.lastPressPos = this.kp.mousePos();
} else if (this.kp.isMouseReleased()) {
if (this.mouseMode == "pinch") return;
this.mouseMode = "drag";
// ignore if no double click
if (this.lastReleaseTime != null && this.lastReleasePos != null) {
const clickDifference = this.kp.time() - this.lastReleaseTime;
const clickDistance = this.kp.mousePos().dist(this.lastReleasePos);
// Double-click!
if (
clickDifference <= this.opts.dblClickDuration &&
clickDistance <= this.opts.dblClickForgiveness
) {
this.zoomTo(this.zoomLevel + 1, this.kp.mousePos());
}
}
// Set variables
this.lastReleaseTime = this.kp.time();
this.lastReleasePos = this.kp.mousePos();
// Check bounds
this.checkBounds();
}
if (this.kp.isMouseDown()) {
if (this.mouseMode == "drag") {
this.prevCamPos = this.kp.camPos();
this.kp.camPos(
this.startCamPos.sub(
this.kp.mousePos().sub(this.startMousePos).scale(camScale)
)
);
} else if (this.mouseMode == "zoom") {
this.zoomToNoTween(
this.startZoomLevel +
-this.kp.mousePos().sub(this.startMousePos).y /
(this.kp.height() / 2),
this.startMousePos
);
}
}
});
}
dispatchEvent(eventName, data) {
const event = new CustomEvent(eventName, { detail: data });
this.events.dispatchEvent(event);
}
clearMouseMode() {
this.lastReleaseTime = null;
this.lastReleasePos = null;
this.lastPressTime = null;
this.lastPressPos = null;
this.prevCamPos = null;
}
checkBounds() {
const camPos = this.kp.camPos();
if (this.camBounds == null) return;
if (this.kp.testRectPoint(this.camBounds, camPos)) return;
const boundsCenter = this.camBounds.center();
const cast = this.camBounds.raycast(
camPos,
this.kp.Vec2.fromAngle(camPos.angle(boundsCenter) + 180).scale(40000)
);
if (cast == null) return;
if (this.#moveTween != null) {
this.#moveTween.finish();
}
this.#moveTween = this.kp.tween(
camPos,
cast.point,
0.25,
this.kp.camPos,
this.kp.easings.easeOutQuad
);
this.#moveTween.then(() => {
this.#moveTween = null;
});
}
get zoomLevelLimit() {
if (this.#zoomLevel == this.opts.minZoomLevel) return -1;
if (this.#zoomLevel == this.opts.maxZoomLevel) return 1;
return 0;
}
get zoomLevel() {
return this.#zoomLevel;
}
zoomIn() {
this.zoomLevel += 1;
}
zoomOut() {
this.zoomLevel -= 1;
}
set zoomLevel(newZoom) {
const cameraZoom = this.kp.camScale().y;
let addLinear = newZoom;
if (addLinear <= this.opts.minZoomLevel) {
addLinear = this.opts.minZoomLevel;
this.dispatchEvent("zoomMin");
} else if (addLinear >= this.opts.maxZoomLevel) {
addLinear = this.opts.maxZoomLevel;
this.dispatchEvent("zoomMax");
} else {
this.dispatchEvent("zoom");
}
let linearToLog = Math.pow(2, addLinear);
this.#zoomLevel = addLinear;
if (this.#scaleTween != null) {
this.#scaleTween.finish();
}
this.#scaleTween = this.kp.tween(
cameraZoom,
linearToLog,
0.25,
this.kp.camScale,
this.kp.easings.easeOutQuad
);
this.#scaleTween.then(() => {
this.#scaleTween = null;
});
}
set zoomLevel_NoTween(newZoom) {
let addLinear = newZoom;
if (addLinear <= this.opts.minZoomLevel) {
addLinear = this.opts.minZoomLevel;
this.dispatchEvent("zoomMin");
} else if (addLinear >= this.opts.maxZoomLevel) {
addLinear = this.opts.maxZoomLevel;
this.dispatchEvent("zoomMax");
} else {
this.dispatchEvent("zoom");
}
let linearToLog = Math.pow(2, addLinear);
this.#zoomLevel = addLinear;
this.kp.camScale(linearToLog);
}
zoomTo(newZoom, position) {
const curCamPos = this.kp.camPos();
const camScl = 1 / this.kp.camScale().y;
const diff = this.kp.center().sub(position).scale(camScl);
this.zoomLevel = newZoom;
let newZoomLog = 1 / Math.pow(2, newZoom);
const newDiff = this.kp.center().sub(position).scale(newZoomLog);
if (newZoom < this.opts.minZoomLevel) return;
if (newZoom > this.opts.maxZoomLevel) return;
if (this.#moveTween != null) {
this.#moveTween.finish();
}
this.#moveTween = this.kp.tween(
curCamPos,
curCamPos.sub(diff).add(newDiff),
0.25,
this.kp.camPos,
this.kp.easings.easeOutQuad
);
this.#moveTween.then(() => {
this.#moveTween = null;
this.checkBounds();
});
}
zoomToAbs(newZoom, position) {
const curCamPos = this.kp.camPos();
this.zoomLevel = newZoom;
if (this.#moveTween != null) {
this.#moveTween.finish();
}
this.#moveTween = this.kp.tween(
curCamPos,
position,
0.25,
this.kp.camPos,
this.kp.easings.easeOutQuad
);
this.#moveTween.then(() => {
this.#moveTween = null;
this.checkBounds();
});
}
zoomToNoTween(newZoom, position) {
const curCamPos = this.kp.camPos();
const camScl = 1 / this.kp.camScale().y;
const diff = this.kp.center().sub(position).scale(camScl);
this.zoomLevel_NoTween = newZoom;
let newZoomLog = 1 / Math.pow(2, newZoom);
const newDiff = this.kp.center().sub(position).scale(newZoomLog);
if (newZoom < this.opts.minZoomLevel) return;
if (newZoom > this.opts.maxZoomLevel) return;
this.kp.camPos(curCamPos.sub(diff).add(newDiff));
this.checkBounds();
}
zoomToNoTweenAbs(newZoom, position) {
this.zoomLevel_NoTween = newZoom;
this.kp.camPos(position);
this.checkBounds();
}
}