const width = 600; const height = 300; const radius = 25; const settings = { physics: true, speed: 2, snapToPadding: 10 }; const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); canvas.width = width; canvas.height = height; const states = []; const connections = []; function tick() { states.forEach(stateA => { states.forEach(stateB => { if (stateA !== stateB && stateA.intersects(stateB)) { const inter = stateA.intersection(stateB); const angle1 = stateA.directionTo(stateB); const angle2 = angle1 + Math.PI; const x1 = Math.cos(angle1) * inter + stateA.x; const y1 = Math.sin(angle1) * inter + stateA.y; const x2 = Math.cos(angle2) * inter + stateB.x; const y2 = Math.sin(angle2) * inter + stateB.y; stateA.moveTo(x1, y1); stateB.moveTo(x2, y2); } }); }); states.forEach(state => { if (state.v) { state.moveToStep(); } }); setTimeout(tick, 1000 / 30); } function draw() { ctx.clearRect(0, 0, width, height); connections.forEach(connection => connection.draw()); states.forEach(state => state.draw()); setTimeout(draw, 1000 / 60); } function selectObject(x, y) { for (let state of states) { if ((x - state.x) * (x - state.x) + (y - state.y) * (y - state.y) < radius * radius) { return state; } } } function getStateByPos(x, y) { } function checkForState(x, y) { return !!getStateByPos(x, y); } function addState(x, y) { const state = new State(x, y, '#f0f'); states.push(state); } function addConnection(stateA, stateB, angleA, angleB) { const connection = new Connection(stateA, stateB, angleA, angleB); connections.push(connection); } let isMouseDown = false, isDragging = false, lastDrag = 0, selectedObject = null, shiftPressed = false, currentConnection = null, movingObject = false; canvas.addEventListener('mousedown', (event) => { selectedObject = selectObject(event.x, event.y); shiftPressed = event.shiftKey; if (!!selectedObject) { if (shiftPressed && selectedObject instanceof State) { currentConnection = new SelfConnection(selectedObject, event); } else { movingObject = true; } } }); canvas.addEventListener('mousemove', (event) => { if (isMouseDown && !isDragging) isDragging = true; if (isMouseDown && isDragging) { if (!shiftPressed) { selectedObject.jumpTo(event.clientX, event.clientY); } selectedObject.setAnchorPoint(event.clientX, event.clientY); } if (!!currentConnection) { let targetState = selectObject(event.x, event.y); if (!targetState instanceof State) { targetState = null; } if (!!selectedObject) { if (targetState === selectedObject) { currentConnection = new SelfConnection(selectedObject, event); } else if(!!targetState) { currentConnection = new Connection(selectedObject, targetState); } else { // currentConnection = new } } else { } } }); canvas.addEventListener('mouseup', (event) => { if (isDragging) { lastDrag = Date.now() } if (shiftPressed) { const state = getStateByPos(event.clientX, event.clientY); addConnection(selectedObject, state, Math.PI, -Math.PI); } isMouseDown = false; isDragging = false; selectedObject = null; }); canvas.addEventListener('click', (event) => { const x = event.clientX, y = event.clientY; if (Date.now() - lastDrag < 100) { return; } if (checkForState(x, y)) { } }); canvas.addEventListener('dblclick', (event) => { const x = event.clientX, y = event.clientY; addState(x, y); }); CanvasRenderingContext2D.prototype.drawArrow = function (x, y, angle) { const dx = Math.cos(angle); const dy = Math.sin(angle); this.beginPath(); this.moveTo(x, y); this.lineTo(x - 8 * dx + 5 * dy, y - 8 * dy - 5 * dx); this.lineTo(x - 8 * dx - 5 * dy, y - 8 * dy + 5 * dx); this.fill(); }; tick(); draw();