399 lines
10 KiB
JavaScript
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();
|
|
}
|
|
}
|