Backgammon Club
perspective.js1.9 KB
1// Shared "first-person" board view: tilts the canvas toward the player (CSS perspective) and maps2// pointer input back through the inverse projection, so game code keeps using flat canvas coordinates.3(() => {4 const c = document.getElementById("c");5 if (!c) return;6 const TILT = 30; // degrees7 function apply() {8 const h = c.offsetHeight || 800;9 c.style.transformOrigin = "50% 100%";10 c.style.transform = "perspective(" + Math.round(h * 1.3) + "px) rotateX(" + TILT + "deg)";11 c.style.willChange = "transform";12 }13 apply();14 if (window.ResizeObserver) new ResizeObserver(apply).observe(c);15 addEventListener("resize", apply);16 function toLocal(sx, sy) {17 const w = c.offsetWidth, h = c.offsetHeight;18 const m = new DOMMatrix(getComputedStyle(c).transform);19 const dx = sx - (c.offsetLeft + w / 2), dy = sy - (c.offsetTop + h);20 const a = m.m11 - dx * m.m14, b = m.m21 - dx * m.m24, e = dx * m.m44 - m.m41;21 const d = m.m12 - dy * m.m14, f = m.m22 - dy * m.m24, g = dy * m.m44 - m.m42;22 const det = a * f - b * d;23 if (!det) return null;24 return { x: (e * f - b * g) / det + w / 2, y: (a * g - e * d) / det + h, w, h };25 }26 let busy = false;27 ["pointerdown", "pointermove", "pointerup"].forEach((type) => {28 addEventListener(type, (e) => {29 if (busy || e.target !== c || !e.isTrusted) return;30 const p = toLocal(e.clientX, e.clientY);31 if (!p) return;32 e.stopImmediatePropagation();33 const r = c.getBoundingClientRect();34 const ev = new PointerEvent(type, {35 bubbles: true, cancelable: true, pointerId: e.pointerId, pointerType: e.pointerType, isPrimary: e.isPrimary,36 button: e.button, buttons: e.buttons, clientX: r.left + (p.x / p.w) * r.width, clientY: r.top + (p.y / p.h) * r.height,37 });38 busy = true;39 try { c.dispatchEvent(ev); } finally { busy = false; }40 }, true);41 });42})();43