phy / index.html
Ramesh-vani's picture
Update index.html
43a64d4 verified
raw
history blame
31 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Physics Simulator for IIT Aspirants</title>
<style>
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
canvas { display: block; width: 100%; height: 100vh; }
/* Top Navbar for Controls */
#navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
background: rgba(255, 255, 255, 0.95);
padding: 10px 20px;
z-index: 1000;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
#controls {
display: flex;
align-items: center;
gap: 15px;
flex-wrap: wrap;
max-width: 70%;
}
#controls label { margin: 0 5px 0 0; font-size: 14px; }
#controls select, #controls button {
padding: 6px;
font-size: 14px;
border-radius: 4px;
border: 1px solid #ccc;
}
#controls select { min-width: 150px; }
#buttonContainer { display: flex; gap: 10px; }
/* Suboptions Sidebar (Left) */
#subOptionsSidebar {
position: fixed;
top: 60px;
left: 0;
width: 250px;
height: calc(100vh - 60px);
background: rgba(245, 245, 245, 0.95);
padding: 15px;
z-index: 900;
overflow-y: auto;
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
#subOptionsSidebar.hidden { transform: translateX(-100%); }
#subOptionsContainer label { display: block; margin: 10px 0 5px; font-size: 14px; }
#subOptionsContainer input, #subOptionsContainer select {
width: 100%; padding: 6px; font-size: 14px; border-radius: 4px; border: 1px solid #ccc; }
/* Right Sidebar for Edit and Connection Panels */
#rightSidebar {
position: fixed;
top: 60px;
right: 0;
width: 300px;
height: calc(100vh - 60px);
background: rgba(245, 245, 245, 0.95);
padding: 15px;
z-index: 900;
overflow-y: auto;
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
#rightSidebar.hidden { transform: translateX(100%); }
#editPanel, #connectionPanel { margin-bottom: 20px; }
#editPanel h3, #connectionPanel h3 { margin: 0 0 10px; font-size: 16px; }
#editPanel label, #connectionPanel label { display: block; margin: 10px 0 5px; font-size: 14px; }
#editPanel input, #editPanel select, #editPanel button,
#connectionPanel input, #connectionPanel select, #connectionPanel button {
width: 100%; padding: 6px; font-size: 14px; border-radius: 4px; border: 1px solid #ccc; }
#editPanel button, #connectionPanel button { margin-top: 10px; }
/* Bottom Bar for Real-Time Data and Constraint Edit */
#bottomBar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: rgba(255, 255, 255, 0.95);
padding: 10px 20px;
z-index: 1000;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 -2px 5px rgba(0,0,0,0.1);
disply:none;
}
#dataDisplay { flex: 1; font-size: 14px; }
#dataDisplay h3 { margin: 0 0 5px; font-size: 16px; }
#constraintEditPanel { flex: 0 0 300px; display: none; }
#constraintEditPanel h3 { margin: 0 0 10px; font-size: 16px; }
#constraintEditPanel label { display: block; margin: 10px 0 5px; font-size: 14px; }
#constraintEditPanel input, #constraintEditPanel select, #constraintEditPanel button {
width: 100%; padding: 6px; font-size: 14px; border-radius: 4px; border: 1px solid #ccc; }
#constraintEditPanel button { margin-top: 10px; }
</style>
</head>
<body>
<!-- Top Navbar -->
<div id="navbar">
<div id="controls">
<label for="simulationSelect">Select Simulation:</label>
<select id="simulationSelect">
<option value="bouncingBall">Bouncing Ball</option>
<option value="pendulum">Pendulum</option>
<option value="projectile">Projectile Motion</option>
<option value="inclinedPlane">Inclined Plane</option>
<option value="springMass">Spring-Mass System</option>
</select>
<div id="buttonContainer">
<button id="addElement">Add Element</button>
<button id="reset">Reset</button>
</div>
</div>
</div>
<!-- Left Sidebar for Suboptions -->
<div id="subOptionsSidebar">
<div id="subOptionsContainer"></div>
</div>
<!-- Right Sidebar for Edit and Connection Panels -->
<div id="rightSidebar" class="hidden">
<div id="editPanel"></div>
<div id="connectionPanel">
<h3>Connect Elements</h3>
<label for="connType">Connection Type:</label>
<select id="connType">
<option value="string">String (Inextensible)</option>
<option value="spring">Spring (Elastic)</option>
<option value="stick">Rigid Stick</option>
</select>
<div id="sourceEndpointDiv">
<label for="sourceEndpoint">Source Endpoint:</label>
<select id="sourceEndpoint">
<option value="mass">Mass</option>
<option value="fixed">Fixed Point</option>
</select>
</div>
<button id="cancelConn">Cancel</button>
<p style="font-size: 12px; color: #555;">Click the target element to connect.</p>
</div>
</div>
<!-- Bottom Bar for Data and Constraints -->
<div id="bottomBar">
<div id="dataDisplay">
<h3>Real-Time Data</h3>
<p id="dataContent">Select an element to view data.</p>
</div>
<div id="constraintEditPanel">
<h3>Edit Constraint</h3>
<label for="consA_X">Endpoint A X:</label>
<input type="number" id="consA_X" step="0.1">
<label for="consA_Y">Endpoint A Y:</label>
<input type="number" id="consA_Y" step="0.1">
<label for="consB_X">Endpoint B X:</label>
<input type="number" id="consB_X" step="0.1">
<label for="consB_Y">Endpoint B Y:</label>
<input type="number" id="consB_Y" step="0.1">
<label for="consType">Connection Type:</label>
<select id="consType">
<option value="string">String</option>
<option value="spring">Spring</option>
<option value="stick">Stick</option>
</select>
<button id="updateConstraint">Update</button>
<button id="deleteConstraint">Delete</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
const { Engine, Render, Runner, World, Bodies, Body, Constraint, Mouse, MouseConstraint, Events, Vector } = Matter;
const engine = Engine.create();
const world = engine.world;
engine.gravity.scale = 0.001;
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: window.innerWidth,
height: window.innerHeight,
wireframes: false,
background: '#e9ecef'
}
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);
let floor, wallLeft, wallRight;
function updateBoundaries() {
if (floor && wallLeft && wallRight) World.remove(world, [floor, wallLeft, wallRight]);
floor = Bodies.rectangle(window.innerWidth / 2, window.innerHeight + 50, window.innerWidth * 2, 100, { isStatic: true, render: { fillStyle: '#343a40' } });
wallLeft = Bodies.rectangle(-50, window.innerHeight / 2, 100, window.innerHeight * 2, { isStatic: true, render: { fillStyle: '#343a40' } });
wallRight = Bodies.rectangle(window.innerWidth + 50, window.innerHeight / 2, 100, window.innerHeight * 2, { isStatic: true, render: { fillStyle: '#343a40' } });
World.add(world, [floor, wallLeft, wallRight]);
}
updateBoundaries();
const mouse = Mouse.create(render.canvas);
const mouseConstraint = MouseConstraint.create(engine, { mouse: mouse, constraint: { stiffness: 0.2, render: { visible: false } } });
World.add(world, mouseConstraint);
render.mouse = mouse;
let selectedBody = null, selectedConstraint = null, attachMode = false, attachFrom = null;
const subOptionsSidebar = document.getElementById('subOptionsSidebar'),
rightSidebar = document.getElementById('rightSidebar'),
editPanel = document.getElementById('editPanel'),
connectionPanel = document.getElementById('connectionPanel'),
constraintEditPanel = document.getElementById('constraintEditPanel'),
dataContent = document.getElementById('dataContent');
function getRandomColor() { return '#' + Math.floor(Math.random() * 16777215).toString(16); }
function updateSubOptions() {
const simulationSelect = document.getElementById('simulationSelect');
const subOptionsContainer = document.getElementById('subOptionsContainer');
const selected = simulationSelect.value;
let html = '';
if (selected === 'bouncingBall') {
html = `
<label for="ballInitialX">Initial X (m):</label><input type="number" id="ballInitialX" value="200">
<label for="ballInitialY">Initial Y (m):</label><input type="number" id="ballInitialY" value="50">
<label for="ballRadius">Radius (m):</label><input type="number" id="ballRadius" value="20" min="5">
<label for="ballRestitution">Restitution:</label><input type="number" id="ballRestitution" value="0.9" step="0.1" min="0" max="1">
<label for="ballFriction">Friction:</label><input type="number" id="ballFriction" value="0.01" step="0.01" min="0" max="1">
<label for="ballColor">Color:</label><input type="color" id="ballColor" value="#3498db">
`;
} else if (selected === 'pendulum') {
html = `
<label for="pendulumPivotX">Pivot X (m):</label><input type="number" id="pendulumPivotX" value="${Math.floor(window.innerWidth / 2)}">
<label for="pendulumPivotY">Pivot Y (m):</label><input type="number" id="pendulumPivotY" value="50">
<label for="pendulumBobRadius">Bob Radius (m):</label><input type="number" id="pendulumBobRadius" value="30" min="5">
<label for="pendulumLength">Length (m):</label><input type="number" id="pendulumLength" value="300" min="50">
<label for="pendulumStiffness">Stiffness:</label><input type="number" id="pendulumStiffness" value="1" step="0.1" min="0" max="1">
<label for="pendulumBobColor">Color:</label><input type="color" id="pendulumBobColor" value="#e74c3c">
`;
} else if (selected === 'projectile') {
html = `
<label for="projectileInitialX">Initial X (m):</label><input type="number" id="projectileInitialX" value="100">
<label for="projectileInitialY">Initial Y (m):</label><input type="number" id="projectileInitialY" value="${window.innerHeight - 150}">
<label for="projectileRadius">Radius (m):</label><input type="number" id="projectileRadius" value="15" min="5">
<label for="projectileVelX">Velocity X (m/s):</label><input type="number" id="projectileVelX" value="20">
<label for="projectileVelY">Velocity Y (m/s):</label><input type="number" id="projectileVelY" value="-25">
<label for="projectileColor">Color:</label><input type="color" id="projectileColor" value="#f1c40f">
`;
} else if (selected === 'inclinedPlane') {
html = `
<label for="rampWidth">Ramp Width (m):</label><input type="number" id="rampWidth" value="400" min="100">
<label for="rampHeight">Ramp Height (m):</label><input type="number" id="rampHeight" value="20" min="10">
<label for="rampAngle">Angle (°):</label><input type="number" id="rampAngle" value="30" min="0" max="90">
<label for="rampX">Ramp X (m):</label><input type="number" id="rampX" value="400">
<label for="rampY">Ramp Y (m):</label><input type="number" id="rampY" value="${window.innerHeight - 100}">
<label for="rampColor">Ramp Color:</label><input type="color" id="rampColor" value="#8e44ad">
<label for="blockWidth">Block Width (m):</label><input type="number" id="blockWidth" value="50" min="10">
<label for="blockHeight">Block Height (m):</label><input type="number" id="blockHeight" value="50" min="10">
<label for="blockFriction">Block Friction:</label><input type="number" id="blockFriction" value="0.3" step="0.1" min="0" max="1">
<label for="blockColor">Block Color:</label><input type="color" id="blockColor" value="#2ecc71">
`;
} else if (selected === 'springMass') {
html = `
<label for="springMassRadius">Mass Radius (m):</label><input type="number" id="springMassRadius" value="25" min="5">
<label for="springMassRestitution">Restitution:</label><input type="number" id="springMassRestitution" value="0.5" step="0.1" min="0" max="1">
<label for="fixedPointX">Fixed X (m):</label><input type="number" id="fixedPointX" value="${Math.floor(window.innerWidth / 2)}">
<label for="fixedPointY">Fixed Y (m):</label><input type="number" id="fixedPointY" value="100">
<label for="springLength">Spring Length (m):</label><input type="number" id="springLength" value="200" min="50">
<label for="springStiffness">Stiffness:</label><input type="number" id="springStiffness" value="0.05" step="0.01" min="0" max="1">
<label for="springMassColor">Color:</label><input type="color" id="springMassColor" value="#e67e22">
`;
}
subOptionsContainer.innerHTML = html;
subOptionsSidebar.classList.remove('hidden');
}
updateSubOptions();
document.getElementById('simulationSelect').addEventListener('change', updateSubOptions);
function addBouncingBall() {
const x = parseFloat(document.getElementById("ballInitialX").value);
const y = parseFloat(document.getElementById("ballInitialY").value);
const radius = parseFloat(document.getElementById("ballRadius").value);
const restitution = parseFloat(document.getElementById("ballRestitution").value);
const friction = parseFloat(document.getElementById("ballFriction").value);
const color = document.getElementById("ballColor").value;
const ball = Bodies.circle(x, y, radius, { restitution, friction, render: { fillStyle: color }, density: 0.001 });
ball.elementType = "bouncingBall";
ball.customOptions = { x, y, radius, restitution, friction, color };
World.add(world, ball);
}
function addPendulum() {
const pivotX = parseFloat(document.getElementById("pendulumPivotX").value);
const pivotY = parseFloat(document.getElementById("pendulumPivotY").value);
const bobRadius = parseFloat(document.getElementById("pendulumBobRadius").value);
const length = parseFloat(document.getElementById("pendulumLength").value);
const stiffness = parseFloat(document.getElementById("pendulumStiffness").value);
const color = document.getElementById("pendulumBobColor").value;
const bob = Bodies.circle(pivotX, pivotY + length, bobRadius, { restitution: 0.9, density: 0.001, render: { fillStyle: color } });
const constraint = Constraint.create({
pointA: { x: pivotX, y: pivotY },
bodyB: bob,
length,
stiffness,
render: { strokeStyle: '#333', lineWidth: 2 }
});
bob.elementType = "pendulum";
bob.customOptions = { pivotX, pivotY, bobRadius, length, stiffness, color };
World.add(world, [bob, constraint]);
}
function addProjectile() {
const x = parseFloat(document.getElementById("projectileInitialX").value);
const y = parseFloat(document.getElementById("projectileInitialY").value);
const radius = parseFloat(document.getElementById("projectileRadius").value);
const velX = parseFloat(document.getElementById("projectileVelX").value);
const velY = parseFloat(document.getElementById("projectileVelY").value);
const color = document.getElementById("projectileColor").value;
const proj = Bodies.circle(x, y, radius, { restitution: 0.8, frictionAir: 0.005, render: { fillStyle: color }, density: 0.001 });
Body.setVelocity(proj, { x: velX, y: velY });
proj.elementType = "projectile";
proj.customOptions = { x, y, radius, velX, velY, color };
World.add(world, proj);
}
function addInclinedPlane() {
const rampWidth = parseFloat(document.getElementById("rampWidth").value);
const rampHeight = parseFloat(document.getElementById("rampHeight").value);
const angleDeg = parseFloat(document.getElementById("rampAngle").value);
const angle = angleDeg * Math.PI / 180;
const rampX = parseFloat(document.getElementById("rampX").value);
const rampY = parseFloat(document.getElementById("rampY").value);
const rampColor = document.getElementById("rampColor").value;
const ramp = Bodies.rectangle(rampX, rampY, rampWidth, rampHeight, {
isStatic: true,
angle,
friction: 0.1,
render: { fillStyle: rampColor }
});
const blockWidth = parseFloat(document.getElementById("blockWidth").value);
const blockHeight = parseFloat(document.getElementById("blockHeight").value);
const blockFriction = parseFloat(document.getElementById("blockFriction").value);
const blockColor = document.getElementById("blockColor").value;
const blockX = rampX - rampWidth / 4 * Math.cos(angle);
const blockY = rampY - rampHeight / 2 - blockHeight / 2 - 10;
const block = Bodies.rectangle(blockX, blockY, blockWidth, blockHeight, {
friction: blockFriction,
render: { fillStyle: blockColor },
density: 0.001
});
ramp.elementType = "inclinedPlane_ramp";
ramp.customOptions = { rampWidth, rampHeight, angleDeg, rampX, rampY, rampColor };
block.elementType = "inclinedPlane_block";
block.customOptions = { blockWidth, blockHeight, blockFriction, blockColor };
World.add(world, [ramp, block]);
}
function addSpringMass() {
const radius = parseFloat(document.getElementById("springMassRadius").value);
const restitution = parseFloat(document.getElementById("springMassRestitution").value);
const fixedX = parseFloat(document.getElementById("fixedPointX").value);
const fixedY = parseFloat(document.getElementById("fixedPointY").value);
const length = parseFloat(document.getElementById("springLength").value);
const stiffness = parseFloat(document.getElementById("springStiffness").value);
const color = document.getElementById("springMassColor").value;
const mass = Bodies.circle(fixedX, fixedY + length, radius, { restitution, density: 0.001, render: { fillStyle: color } });
const spring = Constraint.create({
pointA: { x: fixedX, y: fixedY },
bodyB: mass,
length,
stiffness,
damping: 0.05,
render: { strokeStyle: '#333', lineWidth: 2 }
});
mass.elementType = "springMass";
mass.customOptions = { radius, restitution, fixedX, fixedY, length, stiffness, color };
mass.fixedPoint = { x: fixedX, y: fixedY };
World.add(world, [mass, spring]);
}
document.getElementById('addElement').addEventListener('click', () => {
const selected = document.getElementById('simulationSelect').value;
if (selected === 'bouncingBall') addBouncingBall();
else if (selected === 'pendulum') addPendulum();
else if (selected === 'projectile') addProjectile();
else if (selected === 'inclinedPlane') addInclinedPlane();
else if (selected === 'springMass') addSpringMass();
});
document.getElementById('reset').addEventListener('click', () => {
World.clear(world);
Engine.clear(engine);
updateBoundaries();
const newMouse = Mouse.create(render.canvas);
const newMouseConstraint = MouseConstraint.create(engine, { mouse: newMouse, constraint: { stiffness: 0.2, render: { visible: false } } });
World.add(world, newMouseConstraint);
render.mouse = newMouse;
hideEditPanel();
hideConnectionPanel();
hideConstraintEditPanel();
dataContent.textContent = "Select an element to view data.";
});
function openEditPanel(body) {
selectedBody = body;
rightSidebar.classList.remove('hidden');
let html = `<h3>Edit Element</h3>
<label for="editPosX">Position X (m):</label><input type="number" id="editPosX" value="${body.position.x.toFixed(1)}" step="0.1">
<label for="editPosY">Position Y (m):</label><input type="number" id="editPosY" value="${body.position.y.toFixed(1)}" step="0.1">
<label for="editAngle">Angle (rad):</label><input type="number" id="editAngle" value="${body.angle.toFixed(2)}" step="0.01">
<label for="editColor">Color:</label><input type="color" id="editColor" value="${body.render.fillStyle}">`;
if (body.elementType === "bouncingBall") {
const opts = body.customOptions;
html += `<label for="editBallRadius">Radius (m):</label><input type="number" id="editBallRadius" value="${opts.radius}" min="5">
<label for="editBallRestitution">Restitution:</label><input type="number" id="editBallRestitution" value="${opts.restitution}" step="0.1" min="0" max="1">
<label for="editBallFriction">Friction:</label><input type="number" id="editBallFriction" value="${opts.friction}" step="0.01" min="0" max="1">`;
} else if (body.elementType === "pendulum") {
const opts = body.customOptions;
html += `<label for="editPendulumPivotX">Pivot X (m):</label><input type="number" id="editPendulumPivotX" value="${opts.pivotX}">
<label for="editPendulumPivotY">Pivot Y (m):</label><input type="number" id="editPendulumPivotY" value="${opts.pivotY}">
<label for="editPendulumBobRadius">Radius (m):</label><input type="number" id="editPendulumBobRadius" value="${opts.bobRadius}" min="5">
<label for="editPendulumLength">Length (m):</label><input type="number" id="editPendulumLength" value="${opts.length}" min="50">
<label for="editPendulumStiffness">Stiffness:</label><input type="number" id="editPendulumStiffness" value="${opts.stiffness}" step="0.1" min="0" max="1">`;
}
html += `<div style="margin-top:15px;">
<button id="updateElement">Update</button>
<button id="deleteElement">Delete</button>
<button id="connectElement">Connect</button>
</div>`;
editPanel.innerHTML = html;
editPanel.style.display = "block";
connectionPanel.style.display = "none";
document.getElementById("updateElement").addEventListener("click", updateElementFromEditPanel);
document.getElementById("deleteElement").addEventListener("click", () => { World.remove(world, selectedBody); hideEditPanel(); });
document.getElementById("connectElement").addEventListener("click", () => { attachMode = true; attachFrom = body; editPanel.style.display = "none"; connectionPanel.style.display = "block"; });
}
function hideEditPanel() { editPanel.style.display = "none"; connectionPanel.style.display = "none"; rightSidebar.classList.add('hidden'); selectedBody = null; }
function hideConnectionPanel() { connectionPanel.style.display = "none"; attachMode = false; attachFrom = null; if (!selectedBody) rightSidebar.classList.add('hidden'); }
function hideConstraintEditPanel() { constraintEditPanel.style.display = "none"; selectedConstraint = null; }
function updateElementFromEditPanel() {
if (!selectedBody) return;
const newX = parseFloat(document.getElementById("editPosX").value);
const newY = parseFloat(document.getElementById("editPosY").value);
const newAngle = parseFloat(document.getElementById("editAngle").value);
const newColor = document.getElementById("editColor").value;
Body.setPosition(selectedBody, { x: newX, y: newY });
Body.setAngle(selectedBody, newAngle);
selectedBody.render.fillStyle = newColor;
if (selectedBody.elementType === "bouncingBall") {
const newRadius = parseFloat(document.getElementById("editBallRadius").value);
const newRestitution = parseFloat(document.getElementById("editBallRestitution").value);
const newFriction = parseFloat(document.getElementById("editBallFriction").value);
Body.scale(selectedBody, newRadius / selectedBody.circleRadius, newRadius / selectedBody.circleRadius);
selectedBody.restitution = newRestitution;
selectedBody.friction = newFriction;
selectedBody.customOptions = { ...selectedBody.customOptions, x: newX, y: newY, radius: newRadius, restitution: newRestitution, friction: newFriction, color: newColor };
}
hideEditPanel();
}
render.canvas.addEventListener("dblclick", (event) => {
const rect = render.canvas.getBoundingClientRect();
const mousePos = { x: event.clientX - rect.left, y: event.clientY - rect.top };
const bodies = Matter.Composite.allBodies(world);
const clicked = Matter.Query.point(bodies, mousePos);
if (clicked.length > 0 && !clicked[0].isStatic) openEditPanel(clicked[0]);
else hideEditPanel();
});
document.getElementById('cancelConn').addEventListener('click', hideConnectionPanel);
Events.on(mouseConstraint, 'mouseup', (event) => {
const mousePos = event.mouse.position;
const bodies = Matter.Composite.allBodies(world);
const clickedBodies = Matter.Query.point(bodies, mousePos);
if (attachMode && clickedBodies.length > 0 && attachFrom && clickedBodies[0] !== attachFrom) {
const connType = document.getElementById('connType').value;
const sourceEndpoint = attachFrom.elementType === "springMass" ? document.getElementById('sourceEndpoint').value : "mass";
const targetBody = clickedBodies[0];
let pointA = sourceEndpoint === "fixed" ? attachFrom.fixedPoint : attachFrom.position;
let pointB = targetBody.elementType === "springMass" && confirm("Connect to fixed point?") ? targetBody.fixedPoint : targetBody.position;
const length = Math.hypot(pointB.x - pointA.x, pointB.y - pointA.y);
const stiffness = connType === "spring" ? 0.05 : (connType === "stick" ? 1 : 0.9);
const constraint = Constraint.create({
bodyA: sourceEndpoint === "mass" ? attachFrom : null,
pointA: sourceEndpoint === "fixed" ? pointA : { x: 0, y: 0 },
bodyB: targetBody.elementType === "springMass" && pointB === targetBody.fixedPoint ? null : targetBody,
pointB: targetBody.elementType === "springMass" && pointB === targetBody.fixedPoint ? pointB : { x: 0, y: 0 },
length,
stiffness,
render: { strokeStyle: '#333', lineWidth: 2 }
});
World.add(world, constraint);
hideConnectionPanel();
} else if (!clickedBodies.length) {
const constraints = Matter.Composite.allConstraints(world);
for (let cons of constraints) {
let posA = cons.bodyA ? Vector.add(cons.bodyA.position, cons.pointA) : cons.pointA;
let posB = cons.bodyB ? Vector.add(cons.bodyB.position, cons.pointB) : cons.pointB;
if (distanceToSegment(mousePos, posA, posB) < 5) {
showConstraintEditPanel(cons);
return;
}
}
hideConstraintEditPanel();
}
});
function distanceToSegment(p, v, w) {
const l2 = Vector.magnitudeSquared(Vector.sub(w, v));
if (l2 === 0) return Math.hypot(p.x - v.x, p.y - v.y);
let t = Math.max(0, Math.min(1, ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2));
const proj = Vector.add(v, Vector.mult(Vector.sub(w, v), t));
return Math.hypot(p.x - proj.x, p.y - proj.y);
}
function showConstraintEditPanel(cons) {
selectedConstraint = cons;
let posA = cons.bodyA ? Vector.add(cons.bodyA.position, cons.pointA) : cons.pointA;
let posB = cons.bodyB ? Vector.add(cons.bodyB.position, cons.pointB) : cons.pointB;
document.getElementById('consA_X').value = posA.x.toFixed(1);
document.getElementById('consA_Y').value = posA.y.toFixed(1);
document.getElementById('consB_X').value = posB.x.toFixed(1);
document.getElementById('consB_Y').value = posB.y.toFixed(1);
document.getElementById('consType').value = cons.stiffness < 0.1 ? "spring" : (cons.stiffness > 0.95 ? "stick" : "string");
constraintEditPanel.style.display = "block";
}
document.getElementById('updateConstraint').addEventListener('click', () => {
if (selectedConstraint) {
const aX = parseFloat(document.getElementById('consA_X').value);
const aY = parseFloat(document.getElementById('consA_Y').value);
const bX = parseFloat(document.getElementById('consB_X').value);
const bY = parseFloat(document.getElementById('consB_Y').value);
const connType = document.getElementById('consType').value;
if (!selectedConstraint.bodyA) selectedConstraint.pointA = { x: aX, y: aY };
if (!selectedConstraint.bodyB) selectedConstraint.pointB = { x: bX, y: bY };
selectedConstraint.stiffness = connType === "spring" ? 0.05 : (connType === "stick" ? 1 : 0.9);
selectedConstraint.length = Math.hypot(bX - aX, bY - aY);
hideConstraintEditPanel();
}
});
document.getElementById('deleteConstraint').addEventListener('click', () => {
if (selectedConstraint) {
World.remove(world, selectedConstraint);
hideConstraintEditPanel();
}
});
Events.on(engine, 'afterUpdate', () => {
if (selectedBody && !selectedBody.isStatic) {
const v = selectedBody.velocity;
const ke = 0.5 * selectedBody.mass * (v.x * v.x + v.y * v.y);
const pe = selectedBody.elementType === "springMass" ?
0.5 * selectedBody.constraints[0].stiffness * Math.pow(Vector.magnitude(Vector.sub(selectedBody.position, selectedBody.fixedPoint)) - selectedBody.customOptions.length, 2) :
selectedBody.mass * engine.gravity.y * (window.innerHeight - selectedBody.position.y);
dataContent.innerHTML = `
Position: (${selectedBody.position.x.toFixed(1)}, ${selectedBody.position.y.toFixed(1)}) m<br>
Velocity: (${v.x.toFixed(1)}, ${v.y.toFixed(1)}) m/s<br>
Kinetic Energy: ${ke.toFixed(2)} J<br>
Potential Energy: ${pe.toFixed(2)} J<br>
Total Energy: ${(ke + pe).toFixed(2)} J
`;
}
});
window.addEventListener('resize', () => {
render.canvas.width = window.innerWidth;
render.canvas.height = window.innerHeight;
Render.lookAt(render, { min: { x: 0, y: 0 }, max: { x: window.innerWidth, y: window.innerHeight } });
updateBoundaries();
});
</script>
</body>
</html>