// モジュールの設定 const { Engine, Render, Runner, Bodies, World, Events } = Matter; // エンジンとワールドの作成 const engine = Engine.create(); const world = engine.world; // レンダラの作成 const render = Render.create({ element: document.body, engine: engine, canvas: document.getElementById('world'), options: { width: 800, height: 600, wireframes: false, background: '#ffffff' } }); Render.run(render); const runner = Runner.create(); Runner.run(runner, engine); // シミュレーションに必要な変数 let ball = null; let vxData = []; let vyData = []; let xData = []; let tData = []; let vxyData = []; let breadcrumbs = []; let startTime = null; // キャンバスの境界を取得する関数 function getCanvasBounds() { return render.canvas.getBoundingClientRect(); } // クリックイベントで小球を生成 document.addEventListener('click', (event) => { const canvasBounds = getCanvasBounds(); // キャンバス内のクリックのみ反応 if (event.clientX < canvasBounds.left || event.clientX > canvasBounds.right || event.clientY < canvasBounds.top || event.clientY > canvasBounds.bottom) { return; } // 既存のボールがあれば新しいボールを生成しない if (ball) { return; } const x = event.clientX - canvasBounds.left; const y = event.clientY - canvasBounds.top; const velocityX = parseFloat(document.getElementById('vx').value); // ユーザー入力の初速度X const velocityY = parseFloat(document.getElementById('vy').value); // ユーザー入力の初速度Y ball = Bodies.circle(x, y, 20, { restitution: 0.8 }); Matter.Body.setVelocity(ball, { x: velocityX, y: velocityY }); World.add(world, ball); // データの初期化 startTime = new Date().getTime(); }); // シミュレーションのアップデート Events.on(engine, 'beforeUpdate', () => { if (ball) { const elapsedTime = (new Date().getTime() - startTime) / 1000; vxData.push(ball.velocity.x); vyData.push(ball.velocity.y); xData.push(ball.position.x); tData.push(elapsedTime); vxyData.push({ x: ball.velocity.x, y: ball.velocity.y }); // BreadCrumbsの追加 breadcrumbs.push(Bodies.circle(ball.position.x, ball.position.y, 2, { isStatic: true })); World.add(world, breadcrumbs[breadcrumbs.length - 1]); // 画面外に出た場合、ボールを削除 if (ball.position.x < 0 || ball.position.x > render.options.width || ball.position.y < 0 || ball.position.y > render.options.height) { World.remove(world, ball); ball = null; plotGraphs(); resetVariables(); } } }); // 変数をリセットする関数 function resetVariables() { vxData = []; vyData = []; xData = []; tData = []; vxyData = []; breadcrumbs.forEach(breadcrumb => World.remove(world, breadcrumb)); breadcrumbs = []; } // グラフ描画の関数 function plotGraphs() { plotChart('vxChart', 'Velocity-X vs Time', tData, vxData); plotChart('vyChart', 'Velocity-Y vs Time', tData, vyData); plotChart('xtChart', 'Position-X vs Time', tData, xData); plotScatterChart('vxyChart', 'Velocity-Y vs Velocity-X', vxyData); } function plotChart(canvasId, label, xData, yData) { const ctx = document.getElementById(canvasId).getContext('2d'); new Chart(ctx, { type: 'line', data: { labels: xData, datasets: [{ label: label, data: yData, borderColor: 'rgba(75, 192, 192, 1)', borderWidth: 1, fill: false }] }, options: { scales: { x: { beginAtZero: true }, y: { beginAtZero: true } } } }); } function plotScatterChart(canvasId, label, data) { const ctx = document.getElementById(canvasId).getContext('2d'); new Chart(ctx, { type: 'scatter', data: { datasets: [{ label: label, data: data, borderColor: 'rgba(75, 192, 192, 1)', backgroundColor: 'rgba(75, 192, 192, 0.5)', borderWidth: 1 }] }, options: { scales: { x: { type: 'linear', position: 'bottom', beginAtZero: true, title: { display: true, text: 'Velocity-X' } }, y: { beginAtZero: true, title: { display: true, text: 'Velocity-Y' } } } } }); }