(() => { const { Query, Constraint, World, Vector } = Matter; const create = ({ render, world, balls, boundaries, chain, config, getCurrentScene, isPaused, isLevelWon, isGameOver, getMaxLinkDistance, setHighlight, removeLastFromChain, addToChain, finishChain, updateHud, }) => { let dragConstraint = null; const getPointerPosition = (evt) => { const rect = render.canvas.getBoundingClientRect(); const clientX = evt.touches ? evt.touches[0].clientX : evt.clientX; const clientY = evt.touches ? evt.touches[0].clientY : evt.clientY; return { x: clientX - rect.left, y: clientY - rect.top, }; }; const getDraggableBody = (point) => { const draggables = [ ...boundaries.filter((b) => b.plugin?.draggable), ...balls.filter((b) => b.plugin?.draggable), ]; const hits = Query.point(draggables, point); return hits[0]; }; const pickBody = (point) => { const hits = Query.point(balls, point); return hits[0]; }; const startDrag = (body, point) => { endDrag(); dragConstraint = Constraint.create({ pointA: point, bodyB: body, stiffness: 0.2, damping: 0.3, render: { visible: false }, }); World.add(world, dragConstraint); }; const updateDrag = (point) => { if (!dragConstraint) return; dragConstraint.pointA = point; }; const endDrag = () => { if (dragConstraint) { World.remove(world, dragConstraint); dragConstraint = null; } }; const handlePointerDown = (evt) => { if (isGameOver() || isPaused() || isLevelWon()) return; const point = getPointerPosition(evt); const dragTarget = getDraggableBody(point); if (dragTarget) { startDrag(dragTarget, point); return; } const body = pickBody(point); if (!body) return; const scene = getCurrentScene(); if (!scene?.config?.relaxMode) { chain.color = body.plugin.color; } else { chain.color = body.plugin.color; } chain.active = true; chain.bodies = [body]; chain.constraints = []; chain.pointer = point; setHighlight(body, true); updateHud(); }; const handlePointerMove = (evt) => { if (dragConstraint) { updateDrag(getPointerPosition(evt)); return; } if (!chain.active) return; if (isGameOver() || isPaused() || isLevelWon()) return; const point = getPointerPosition(evt); chain.pointer = point; const body = pickBody(point); if (!body) return; const alreadyInChain = chain.bodies.includes(body); if (alreadyInChain) { const targetIndex = chain.bodies.indexOf(body); if (chain.bodies.length > 1 && targetIndex === chain.bodies.length - 2) { removeLastFromChain(); } return; } const scene = getCurrentScene(); if (!scene?.config?.relaxMode && body.plugin.color !== chain.color) return; const maxLinkDist = getMaxLinkDistance(); const dist = Vector.magnitude( Vector.sub(chain.bodies[chain.bodies.length - 1].position, body.position), ); if (dist > maxLinkDist) return; addToChain(body); }; const handlePointerUp = () => { if (dragConstraint) { endDrag(); return; } finishChain(chain.pointer); }; render.canvas.addEventListener("mousedown", handlePointerDown); render.canvas.addEventListener("mousemove", handlePointerMove); window.addEventListener("mouseup", handlePointerUp); render.canvas.addEventListener( "touchstart", (e) => { e.preventDefault(); handlePointerDown(e); }, { passive: false }, ); render.canvas.addEventListener( "touchmove", (e) => { e.preventDefault(); handlePointerMove(e); }, { passive: false }, ); render.canvas.addEventListener( "touchend", (e) => { e.preventDefault(); handlePointerUp(e); }, { passive: false }, ); return { endDrag, }; }; window.PhysilinksInput = { create }; })();