phy / index.html
Ramesh-vani's picture
Update index.html
15f25fe verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Advanced Physics Simulator – Broad Elements Menu</title>
<style>
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
/* The canvas fills the viewport */
canvas { display: block; }
/* Top horizontal menu bar (outside the canvas) */
canvas {
display: block;
margin-top: 94px;
}
#topMenu {
position: fixed;
top: 0;
left: 0;
right: 0;
background: transparent;
border-bottom: 1px solid #ccc;
padding: 5px;
z-index: 2000;
display: flex;
align-items: center;
gap: 15px;
backdrop-filter: blur(10px);
}
#topMenu select, #topMenu button {
padding: 5px 10px;
font-size: 14px;
}
/* A container below the menu for additional options */
#subOptionsContainer {
position: fixed;
top: 45px;
left: 0;
z-index: 2000;
background: transparent;
padding: 5px;
border: 1px solid #ccc;
border-radius: 0;
display: flex
;
width: -webkit-fill-available;
align-items: baseline;
backdrop-filter: blur(10px);
}
/* Edit Panel (floating, triggered on double-click) */
#editPanel {
position: fixed;
top: 45px;
right: 0;
z-index: 2000;
background: transparent;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
max-width: 100%;
max-height: 72vh;
overflow-y: auto;
display: none;
backdrop-filter: blur(10px);
}
/* Connection and Constraint panels (similar styling) */
#connectionPanel, #constraintEditPanel {
position: fixed;
z-index: 2000;
background: rgba(255,255,255,0.95);
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
max-width: 250px;
display: none;
}
#connectionPanel { top: 45px; left: 50%; transform: translateX(-50%); }
#constraintEditPanel { bottom: 10px; right: 10px; }
/* Common styling for labels/inputs */
label { display: block; margin-top: 8px; font-size: 13px; }
input, select, button {
width: fit-content;
padding: 5px;
margin-top: 4px;
font-size: 13px;
margin: 4px;
}
input[type="color" i] {
width: 42px;
height: 28px;
}
</style>
</head>
<body>
<!-- Top horizontal menu bar (outside the canvas) -->
<div id="topMenu">
<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>
<option value="rectangleBlock">Rectangle Block</option>
<option value="rotatingRectangle">Rotating Rectangle</option>
<option value="triangle">Triangle</option>
</select>
<button id="addElement">Add Element</button>
<button id="connectSystems">Connect Systems</button>
<button id="reset">Reset Simulation</button>
</div>
<!-- Container for dynamic suboptions -->
<div id="subOptionsContainer"></div>
<!-- Edit Panel (opens on double-click on an element) -->
<div id="editPanel"></div>
<!-- Connection Options Panel -->
<div id="connectionPanel">
<h3>Connection Options</h3>
<label for="connType">Connection Type:</label>
<select id="connType">
<option value="string">String</option>
<option value="spring">Spring</option>
<option value="stick">Stick</option>
</select>
<label for="connMode">Connection Mode:</label>
<select id="connMode">
<option value="element">Element</option>
<option value="system">System</option>
</select>
<div id="sourceEndpointDiv">
<label for="sourceEndpoint">Source Endpoint:</label>
<select id="sourceEndpoint">
<option value="mass">Mass</option>
<option value="fixed">Fixed</option>
</select>
</div>
<button id="cancelConn">Cancel</button>
<p style="font-size: 12px; color: #555;">Click on the target element to attach.</p>
</div>
<!-- Constraint Edit Panel -->
<div id="constraintEditPanel">
<h3>Edit Constraint</h3>
<label for="consA_X">Endpoint A X:</label>
<input type="number" id="consA_X" step="1">
<label for="consA_Y">Endpoint A Y:</label>
<input type="number" id="consA_Y" step="1">
<label for="consB_X">Endpoint B X:</label>
<input type="number" id="consB_X" step="1">
<label for="consB_Y">Endpoint B Y:</label>
<input type="number" id="consB_Y" step="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 Constraint</button>
<button id="deleteConstraint">Delete Constraint</button>
</div>
<!-- Matter.js Library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
// Module aliases and engine setup
const Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
World = Matter.World,
Bodies = Matter.Bodies,
Body = Matter.Body,
Constraint = Matter.Constraint,
Mouse = Matter.Mouse,
MouseConstraint = Matter.MouseConstraint,
Events = Matter.Events;
const engine = Engine.create();
const world = engine.world;
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: window.innerWidth,
height: window.innerHeight,
wireframes: false,
background: '#f0f0f0'
}
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);
// Boundaries (responsive)
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, 100, {
isStatic: true,
render: { fillStyle: '#060a19' }
});
wallLeft = Bodies.rectangle(-50, window.innerHeight/2, 100, window.innerHeight, { isStatic: true });
wallRight = Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, { isStatic: true });
World.add(world, [floor, wallLeft, wallRight]);
}
updateBoundaries();
// Mouse control
let mouse = Mouse.create(render.canvas);
let mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: { stiffness: 0.2, render: { visible: false } }
});
World.add(world, mouseConstraint);
render.mouse = mouse;
// Global variables for editing and connection
let selectedBody = null;
let selectedConstraint = null;
let attachMode = false;
let attachFrom = null;
const connectionPanel = document.getElementById('connectionPanel');
const editPanel = document.getElementById('editPanel');
// Helper: random color
function getRandomColor() {
return '#' + Math.floor(Math.random()*16777215).toString(16);
}
/* ---------- Dynamic Suboptions for Adding Elements ---------- */
function updateSubOptions() {
const simulationSelect = document.getElementById('simulationSelect');
const subOptionsContainer = document.getElementById('subOptionsContainer');
let selected = simulationSelect.value;
let html = '';
if(selected === 'bouncingBall'){
html += `
<label for="ballInitialX">Initial X:</label>
<input type="number" id="ballInitialX" value="100">
<label for="ballInitialY">Initial Y:</label>
<input type="number" id="ballInitialY" value="50">
<label for="ballRadius">Radius:</label>
<input type="number" id="ballRadius" value="30">
<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.005" step="0.001" 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:</label>
<input type="number" id="pendulumPivotX" value="${window.innerWidth-200}">
<label for="pendulumPivotY">Pivot Y:</label>
<input type="number" id="pendulumPivotY" value="50">
<label for="pendulumBobRadius">Bob Radius:</label>
<input type="number" id="pendulumBobRadius" value="40">
<label for="pendulumLength">Length:</label>
<input type="number" id="pendulumLength" value="250">
<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="#ff0000">
`;
} else if(selected === 'projectile'){
html += `
<label for="projectileInitialX">Initial X:</label>
<input type="number" id="projectileInitialX" value="100">
<label for="projectileInitialY">Initial Y:</label>
<input type="number" id="projectileInitialY" value="300">
<label for="projectileRadius">Radius:</label>
<input type="number" id="projectileRadius" value="20">
<label for="projectileVelX">Velocity X:</label>
<input type="number" id="projectileVelX" value="15">
<label for="projectileVelY">Velocity Y:</label>
<input type="number" id="projectileVelY" value="-15">
<label for="projectileColor">Color:</label>
<input type="color" id="projectileColor" value="#e67e22">
`;
} else if(selected === 'inclinedPlane'){
html += `
<label for="rampWidth">Ramp Width:</label>
<input type="number" id="rampWidth" value="300">
<label for="rampHeight">Ramp Height:</label>
<input type="number" id="rampHeight" value="20">
<label for="rampAngle">Ramp Angle (°):</label>
<input type="number" id="rampAngle" value="30">
<label for="rampX">Ramp X:</label>
<input type="number" id="rampX" value="400">
<label for="rampY">Ramp Y:</label>
<input type="number" id="rampY" value="${window.innerHeight-150}">
<label for="rampColor">Ramp Color:</label>
<input type="color" id="rampColor" value="#8e44ad">
<hr>
<label for="blockWidth">Block Width:</label>
<input type="number" id="blockWidth" value="40">
<label for="blockHeight">Block Height:</label>
<input type="number" id="blockHeight" value="40">
<label for="blockFriction">Block Friction:</label>
<input type="number" id="blockFriction" value="0.05" step="0.01" min="0" max="1">
<label for="blockColor">Block Color:</label>
<input type="color" id="blockColor" value="#1abc9c">
`;
} else if(selected === 'springMass'){
html += `
<label for="springMassRadius">Mass Radius:</label>
<input type="number" id="springMassRadius" value="25">
<label for="springMassRestitution">Restitution:</label>
<input type="number" id="springMassRestitution" value="0.8" step="0.1" min="0" max="1">
<label for="fixedPointX">Fixed X:</label>
<input type="number" id="fixedPointX" value="${Math.floor(window.innerWidth/2)}">
<label for="fixedPointY">Fixed Y:</label>
<input type="number" id="fixedPointY" value="100">
<label for="springLength">Spring Length:</label>
<input type="number" id="springLength" value="200">
<label for="springStiffness">Stiffness:</label>
<input type="number" id="springStiffness" value="0.02" step="0.01" min="0" max="1">
<label for="springMassColor">Color:</label>
<input type="color" id="springMassColor" value="#f39c12">
`;
} else if(selected === 'rectangleBlock'){
html += `
<label for="blockInitX">Initial X:</label>
<input type="number" id="blockInitX" value="150">
<label for="blockInitY">Initial Y:</label>
<input type="number" id="blockInitY" value="150">
<label for="blockWidth">Width:</label>
<input type="number" id="blockWidth" value="80">
<label for="blockHeight">Height:</label>
<input type="number" id="blockHeight" value="40">
<label for="blockFriction">Friction:</label>
<input type="number" id="blockFriction" value="0.1" step="0.01" min="0" max="1">
<label for="blockRestitution">Restitution:</label>
<input type="number" id="blockRestitution" value="0.3" step="0.1" min="0" max="1">
<label for="blockColor">Color:</label>
<input type="color" id="blockColor" value="#2ecc71">
`;
} else if(selected === 'rotatingRectangle'){
html += `
<label for="rotRectInitX">Initial X:</label>
<input type="number" id="rotRectInitX" value="200">
<label for="rotRectInitY">Initial Y:</label>
<input type="number" id="rotRectInitY" value="200">
<label for="rotRectWidth">Width:</label>
<input type="number" id="rotRectWidth" value="100">
<label for="rotRectHeight">Height:</label>
<input type="number" id="rotRectHeight" value="50">
<label for="rotRectRotationSpeed">Rotation Speed (rad/s):</label>
<input type="number" id="rotRectRotationSpeed" value="0.05" step="0.01">
<label for="rotRectFriction">Friction:</label>
<input type="number" id="rotRectFriction" value="0.1" step="0.01" min="0" max="1">
<label for="rotRectRestitution">Restitution:</label>
<input type="number" id="rotRectRestitution" value="0.3" step="0.1" min="0" max="1">
<label for="rotRectColor">Color:</label>
<input type="color" id="rotRectColor" value="#9b59b6">
`;
} else if(selected === 'triangle'){
html += `
<label for="triInitX">Initial X:</label>
<input type="number" id="triInitX" value="300">
<label for="triInitY">Initial Y:</label>
<input type="number" id="triInitY" value="300">
<label for="triSize">Size (radius):</label>
<input type="number" id="triSize" value="40">
<label for="triFriction">Friction:</label>
<input type="number" id="triFriction" value="0.1" step="0.01" min="0" max="1">
<label for="triRestitution">Restitution:</label>
<input type="number" id="triRestitution" value="0.3" step="0.1" min="0" max="1">
<label for="triColor">Color:</label>
<input type="color" id="triColor" value="#e74c3c">
`;
}
subOptionsContainer.innerHTML = html;
}
updateSubOptions();
document.getElementById('simulationSelect').addEventListener('change', updateSubOptions);
/* ---------- Functions to Add Simulation Elements ---------- */
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 || getRandomColor();
const ball = Bodies.circle(x, y, radius, { restitution, friction, render: { fillStyle: color } });
ball.elementType = "bouncingBall";
ball.customOptions = { x, y, radius, restitution, friction, color, isSystemRoot: false };
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 pendulumLength = parseFloat(document.getElementById("pendulumLength").value);
const stiffness = parseFloat(document.getElementById("pendulumStiffness").value);
const bobColor = document.getElementById("pendulumBobColor").value || "#ff0000";
const bob = Bodies.circle(pivotX, pivotY+pendulumLength, bobRadius, { restitution: 1, density: 0.005, render: { fillStyle: bobColor } });
bob.elementType = "pendulum";
bob.customOptions = { pivotX, pivotY, bobRadius, pendulumLength, stiffness, bobColor, isSystemRoot: false };
const pendulumConstraint = Constraint.create({
pointA: { x: pivotX, y: pivotY },
bodyB: bob,
length: pendulumLength,
stiffness: stiffness,
render: { strokeStyle: '#000', lineWidth: 2 }
});
World.add(world, [bob, pendulumConstraint]);
}
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 || getRandomColor();
const proj = Bodies.circle(x, y, radius, { restitution: 0.8, frictionAir: 0.001, render: { fillStyle: color } });
proj.elementType = "projectile";
proj.customOptions = { x, y, radius, velX, velY, color, isSystemRoot: false };
Body.setVelocity(proj, { x: velX, y: velY });
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 || "#8e44ad";
const ramp = Bodies.rectangle(rampX, rampY, rampWidth, rampHeight, {
isStatic: true,
angle: angle,
render: { fillStyle: rampColor }
});
ramp.elementType = "inclinedPlane_ramp";
ramp.customOptions = { rampWidth, rampHeight, angleDeg, rampX, rampY, rampColor, isSystemRoot: false };
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 || "#1abc9c";
const block = Bodies.rectangle(rampX - rampWidth/4, rampY - 50, blockWidth, blockHeight, {
friction: blockFriction,
render: { fillStyle: blockColor }
});
block.elementType = "inclinedPlane_block";
block.customOptions = { blockWidth, blockHeight, blockFriction, blockColor, isSystemRoot: false };
World.add(world, [ramp, block]);
}
function addSpringMass(){
const massRadius = parseFloat(document.getElementById("springMassRadius").value);
const massRestitution = parseFloat(document.getElementById("springMassRestitution").value);
const fixedPointX = parseFloat(document.getElementById("fixedPointX").value);
const fixedPointY = parseFloat(document.getElementById("fixedPointY").value);
const springLength = parseFloat(document.getElementById("springLength").value);
const springStiffness = parseFloat(document.getElementById("springStiffness").value);
const massColor = document.getElementById("springMassColor").value || getRandomColor();
const mass = Bodies.circle(fixedPointX, fixedPointY+springLength, massRadius, {
restitution: massRestitution,
density: 0.004,
render: { fillStyle: massColor }
});
mass.elementType = "springMass";
mass.customOptions = { massRadius, massRestitution, fixedPointX, fixedPointY, springLength, springStiffness, massColor, isSystemRoot: false };
mass.fixedPoint = { x: fixedPointX, y: fixedPointY };
const spring = Constraint.create({
pointA: { x: fixedPointX, y: fixedPointY },
bodyB: mass,
length: springLength,
stiffness: springStiffness,
damping: 0.05,
render: { strokeStyle: '#000', lineWidth: 2 }
});
World.add(world, [mass, spring]);
}
function addRectangleBlock(){
const x = parseFloat(document.getElementById("blockInitX").value);
const y = parseFloat(document.getElementById("blockInitY").value);
const width = parseFloat(document.getElementById("blockWidth").value);
const height = parseFloat(document.getElementById("blockHeight").value);
const friction = parseFloat(document.getElementById("blockFriction").value);
const restitution = parseFloat(document.getElementById("blockRestitution").value);
const color = document.getElementById("blockColor").value || getRandomColor();
const block = Bodies.rectangle(x, y, width, height, { friction, restitution, render: { fillStyle: color } });
block.elementType = "rectangleBlock";
block.customOptions = { x, y, width, height, friction, restitution, color, isSystemRoot: false };
World.add(world, block);
}
function addRotatingRectangle(){
const x = parseFloat(document.getElementById("rotRectInitX").value);
const y = parseFloat(document.getElementById("rotRectInitY").value);
const width = parseFloat(document.getElementById("rotRectWidth").value);
const height = parseFloat(document.getElementById("rotRectHeight").value);
const rotationSpeed = parseFloat(document.getElementById("rotRectRotationSpeed").value);
const friction = parseFloat(document.getElementById("rotRectFriction").value);
const restitution = parseFloat(document.getElementById("rotRectRestitution").value);
const color = document.getElementById("rotRectColor").value || getRandomColor();
const rect = Bodies.rectangle(x, y, width, height, { friction, restitution, render: { fillStyle: color } });
rect.elementType = "rotatingRectangle";
rect.customOptions = { x, y, width, height, rotationSpeed, friction, restitution, color, isSystemRoot: false };
// Continuously rotate the rectangle
Events.on(engine, "beforeUpdate", function(){
Body.rotate(rect, rotationSpeed);
});
World.add(world, rect);
}
function addTriangle(){
const x = parseFloat(document.getElementById("triInitX").value);
const y = parseFloat(document.getElementById("triInitY").value);
const radius = parseFloat(document.getElementById("triSize").value);
const friction = parseFloat(document.getElementById("triFriction").value);
const restitution = parseFloat(document.getElementById("triRestitution").value);
const color = document.getElementById("triColor").value || getRandomColor();
const tri = Bodies.polygon(x, y, 3, radius, { friction, restitution, render: { fillStyle: color } });
tri.elementType = "triangle";
tri.customOptions = { x, y, radius, friction, restitution, color, isSystemRoot: false };
World.add(world, tri);
}
document.getElementById('addElement').addEventListener('click', function(){
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();
else if(selected === 'rectangleBlock') addRectangleBlock();
else if(selected === 'rotatingRectangle') addRotatingRectangle();
else if(selected === 'triangle') addTriangle();
});
// "Connect Systems" button to initiate attach mode (for connecting elements or systems)
document.getElementById('connectSystems').addEventListener('click', function(){
attachMode = true;
attachFrom = null;
connectionPanel.style.display = "block";
document.getElementById('connMode').value = "system";
alert("System connection mode activated. Click on the first system element (must be marked as system root) then the target system element.");
});
document.getElementById('reset').addEventListener('click', function(){
World.clear(world);
Engine.clear(engine);
updateBoundaries();
mouse = Mouse.create(render.canvas);
mouseConstraint = MouseConstraint.create(engine, { mouse: mouse, constraint: { stiffness: 0.2, render: { visible: false } } });
World.add(world, mouseConstraint);
render.mouse = mouse;
hideEditPanel();
hideConnectionPanel();
hideConstraintEditPanel();
});
/* ---------- Edit Panel Functions (Double-click on element) ---------- */
function openEditPanel(body) {
selectedBody = body;
let html = `<h3>Edit Element</h3>
<label for="editPosX">Position X:</label>
<input type="number" id="editPosX" value="${body.position.x.toFixed(2)}">
<label for="editPosY">Position Y:</label>
<input type="number" id="editPosY" value="${body.position.y.toFixed(2)}">
<label for="editAngle">Angle (radians):</label>
<input type="number" id="editAngle" value="${body.angle.toFixed(2)}">
<label for="editColor">Color:</label>
<input type="color" id="editColor" value="${body.render.fillStyle || '#ffffff'}">
<label for="editSystemRoot">Is System Root?</label>
<input type="checkbox" id="editSystemRoot" ${body.customOptions.isSystemRoot ? "checked" : ""}>`;
if(body.elementType === "bouncingBall"){
const opts = body.customOptions;
html += `<label for="editBallRadius">Radius:</label>
<input type="number" id="editBallRadius" value="${opts.radius}">
<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.001" min="0" max="1">`;
}
// Additional type-specific options can be added similarly
html += `<div style="margin-top:10px;">
<button id="updateElement">Update</button>
<button id="deleteElement">Delete</button>
</div>`;
editPanel.innerHTML = html;
editPanel.style.display = "block";
document.getElementById("updateElement").addEventListener("click", updateElementFromEditPanel);
document.getElementById("deleteElement").addEventListener("click", function(){
World.remove(world, selectedBody);
hideEditPanel();
});
}
function hideEditPanel() {
editPanel.style.display = "none";
selectedBody = 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;
const isSysRoot = document.getElementById("editSystemRoot").checked;
selectedBody.customOptions.isSystemRoot = isSysRoot;
if(selectedBody.elementType === "bouncingBall"){
const oldRadius = selectedBody.circleRadius;
const newRadius = parseFloat(document.getElementById("editBallRadius").value);
const newRestitution = parseFloat(document.getElementById("editBallRestitution").value);
const newFriction = parseFloat(document.getElementById("editBallFriction").value);
if(newRadius && oldRadius && newRadius !== oldRadius){
const scaleFactor = newRadius / oldRadius;
Body.scale(selectedBody, scaleFactor, scaleFactor);
}
selectedBody.restitution = newRestitution;
selectedBody.friction = newFriction;
selectedBody.customOptions = { x: newX, y: newY, radius: newRadius, restitution: newRestitution, friction: newFriction, color: newColor, isSystemRoot: isSysRoot };
}
hideEditPanel();
}
render.canvas.addEventListener("dblclick", function(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){
openEditPanel(clicked[0]);
} else {
hideEditPanel();
}
});
/* ---------- Connection / Attachment ---------- */
Events.on(mouseConstraint, 'mouseup', function(event){
const mousePos = event.mouse.position;
const bodies = Matter.Composite.allBodies(world);
const clickedBodies = Matter.Query.point(bodies, mousePos);
if(clickedBodies.length > 0){
const clickedBody = clickedBodies[0];
if(attachMode){
if(!attachFrom){
attachFrom = clickedBody;
alert("First element selected. Now click on the target element.");
return;
} else if(clickedBody === attachFrom){
alert("Please select a different element.");
return;
}
const connType = document.getElementById('connType').value;
const connMode = document.getElementById('connMode').value;
if(connMode === "system"){
if(!attachFrom.customOptions.isSystemRoot){
alert("Source element is not marked as System Root. Please mark it in the edit panel.");
attachMode = false;
attachFrom = null;
connectionPanel.style.display = "none";
return;
}
if(!clickedBody.customOptions.isSystemRoot){
alert("Target element is not marked as System Root. Please mark it in its edit panel.");
attachMode = false;
attachFrom = null;
connectionPanel.style.display = "none";
return;
}
}
let sourceEndpoint = "mass";
if(attachFrom.elementType === "springMass"){
sourceEndpoint = document.getElementById('sourceEndpoint').value;
}
let targetEndpoint = "mass";
if(clickedBody.elementType === "springMass"){
targetEndpoint = prompt("For target springMass, choose 'fixed' or 'mass' (default: mass):", "mass") || "mass";
if(targetEndpoint !== "fixed") { targetEndpoint = "mass"; }
}
let pointA = { x: 0, y: 0 }, bodyA = attachFrom;
if(attachFrom.elementType === "springMass" && sourceEndpoint === "fixed"){
bodyA = null;
pointA = attachFrom.fixedPoint;
}
let pointB = { x: 0, y: 0 }, bodyB = clickedBody;
if(clickedBody.elementType === "springMass" && targetEndpoint === "fixed"){
bodyB = null;
pointB = clickedBody.fixedPoint || { x: clickedBody.position.x, y: clickedBody.position.y };
}
if(connMode === "system"){
bodyA = attachFrom;
bodyB = clickedBody;
pointA = { x: 0, y: 0 };
pointB = { x: 0, y: 0 };
}
let posA = bodyA ? attachFrom.position : pointA;
let posB = bodyB ? clickedBody.position : pointB;
let dx = posB.x - posA.x, dy = posB.y - posA.y;
let length = Math.sqrt(dx*dx + dy*dy);
let stiffness = (connType === "spring") ? 0.05 : 1;
const newConstraint = Constraint.create({
bodyA: bodyA,
pointA: pointA,
bodyB: bodyB,
pointB: pointB,
length: length,
stiffness: stiffness,
render: { strokeStyle: '#000', lineWidth: 2 }
});
World.add(world, newConstraint);
attachMode = false;
attachFrom = null;
connectionPanel.style.display = "none";
alert("Attachment created using " + connType + " connection in " + connMode + " mode.");
}
} else {
// Check if near a constraint for editing
const constraints = Matter.Composite.allConstraints(world);
for(let cons of constraints){
let posA = cons.bodyA ? { x: cons.bodyA.position.x + cons.pointA.x, y: cons.bodyA.position.y + cons.pointA.y } : cons.pointA;
let posB = cons.bodyB ? { x: cons.bodyB.position.x + cons.pointB.x, y: cons.bodyB.position.y + cons.pointB.y } : cons.pointB;
let dist = distanceToSegment(mousePos, posA, posB);
if(dist < 5){
showConstraintEditPanel(cons);
return;
}
}
hideConstraintEditPanel();
}
});
function distanceToSegment(p, v, w){
let l2 = (w.x - v.x)**2 + (w.y - v.y)**2;
if(l2 === 0) return Math.hypot(p.x - v.x, p.y - v.y);
let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
t = Math.max(0, Math.min(1, t));
let proj = { x: v.x + t*(w.x-v.x), y: v.y + t*(w.y-v.y) };
return Math.hypot(p.x - proj.x, p.y - proj.y);
}
/* ---------- Constraint Edit Panel ---------- */
const constraintEditPanel = document.getElementById('constraintEditPanel');
function showConstraintEditPanel(cons){
selectedConstraint = cons;
let posA = cons.bodyA ? { x: cons.bodyA.position.x + cons.pointA.x, y: cons.bodyA.position.y + cons.pointA.y } : cons.pointA;
let posB = cons.bodyB ? { x: cons.bodyB.position.x + cons.pointB.x, y: cons.bodyB.position.y + cons.pointB.y } : cons.pointB;
document.getElementById('consA_X').value = posA.x.toFixed(2);
document.getElementById('consA_Y').value = posA.y.toFixed(2);
document.getElementById('consB_X').value = posB.x.toFixed(2);
document.getElementById('consB_Y').value = posB.y.toFixed(2);
let connType = (cons.stiffness < 0.1) ? "spring" : "string";
document.getElementById('consType').value = connType;
constraintEditPanel.style.display = "block";
}
function hideConstraintEditPanel(){
constraintEditPanel.style.display = "none";
selectedConstraint = null;
}
document.getElementById('updateConstraint').addEventListener('click', function(){
if(selectedConstraint){
let aX = parseFloat(document.getElementById('consA_X').value);
let aY = parseFloat(document.getElementById('consA_Y').value);
let bX = parseFloat(document.getElementById('consB_X').value);
let bY = parseFloat(document.getElementById('consB_Y').value);
if(!selectedConstraint.bodyA) { selectedConstraint.pointA = { x: aX, y: aY }; }
if(!selectedConstraint.bodyB) { selectedConstraint.pointB = { x: bX, y: bY }; }
let connType = document.getElementById('consType').value;
selectedConstraint.stiffness = (connType === "spring") ? 0.05 : 1;
hideConstraintEditPanel();
}
});
document.getElementById('deleteConstraint').addEventListener('click', function(){
if(selectedConstraint){
World.remove(world, selectedConstraint);
hideConstraintEditPanel();
}
});
// Window resize: update renderer and boundaries
window.addEventListener('resize', function(){
Render.lookAt(render, { min: { x: 0, y: 0 }, max: { x: window.innerWidth, y: window.innerHeight } });
updateBoundaries();
});
</script>
</body>
</html>