Spaces:
Running
Running
Update index.html
Browse files- index.html +161 -109
index.html
CHANGED
|
@@ -2,77 +2,76 @@
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
-
<title>Advanced Physics Simulator –
|
| 6 |
<style>
|
| 7 |
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
|
|
|
|
| 8 |
canvas { display: block; }
|
| 9 |
-
/*
|
| 10 |
-
#
|
| 11 |
-
position:
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
overflow-y: auto;
|
| 23 |
}
|
| 24 |
-
#
|
| 25 |
-
|
| 26 |
-
|
| 27 |
}
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
|
|
|
| 33 |
#editPanel {
|
| 34 |
-
|
|
|
|
| 35 |
right: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
max-width: 300px;
|
| 37 |
-
max-height:
|
| 38 |
overflow-y: auto;
|
| 39 |
display: none;
|
| 40 |
}
|
| 41 |
-
|
| 42 |
-
#
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
right: 10px;
|
| 50 |
-
max-width: 250px;
|
| 51 |
-
display: none;
|
| 52 |
-
}
|
| 53 |
-
#connectionPanel h3 { margin: 0 0 5px 0; }
|
| 54 |
-
#connectionPanel label { display: block; margin-top: 8px; }
|
| 55 |
-
#connectionPanel input, #connectionPanel select, #connectionPanel button {
|
| 56 |
-
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
|
| 57 |
-
}
|
| 58 |
-
/* Constraint Edit Panel */
|
| 59 |
-
#constraintEditPanel {
|
| 60 |
-
bottom: 10px;
|
| 61 |
-
right: 10px;
|
| 62 |
max-width: 250px;
|
| 63 |
display: none;
|
| 64 |
}
|
| 65 |
-
#
|
| 66 |
-
#constraintEditPanel
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
}
|
| 70 |
</style>
|
| 71 |
</head>
|
| 72 |
<body>
|
| 73 |
-
<!--
|
| 74 |
-
<div id="
|
| 75 |
-
<label for="simulationSelect">Choose Simulation Element:</label>
|
| 76 |
<select id="simulationSelect">
|
| 77 |
<option value="bouncingBall">Bouncing Ball</option>
|
| 78 |
<option value="pendulum">Pendulum</option>
|
|
@@ -80,19 +79,20 @@
|
|
| 80 |
<option value="inclinedPlane">Inclined Plane</option>
|
| 81 |
<option value="springMass">Spring–Mass System</option>
|
| 82 |
<option value="rectangleBlock">Rectangle Block</option>
|
|
|
|
|
|
|
| 83 |
</select>
|
| 84 |
-
<
|
| 85 |
-
<
|
| 86 |
-
<
|
| 87 |
-
<button id="addElement">Add Element</button>
|
| 88 |
-
<button id="connectSystems">Connect Systems</button>
|
| 89 |
-
<button id="reset">Reset Simulation</button>
|
| 90 |
-
</div>
|
| 91 |
</div>
|
| 92 |
|
|
|
|
|
|
|
|
|
|
| 93 |
<!-- Edit Panel (opens on double-click on an element) -->
|
| 94 |
<div id="editPanel"></div>
|
| 95 |
-
|
| 96 |
<!-- Connection Options Panel -->
|
| 97 |
<div id="connectionPanel">
|
| 98 |
<h3>Connection Options</h3>
|
|
@@ -107,7 +107,6 @@
|
|
| 107 |
<option value="element">Element</option>
|
| 108 |
<option value="system">System</option>
|
| 109 |
</select>
|
| 110 |
-
<!-- Show endpoint options only for springMass systems -->
|
| 111 |
<div id="sourceEndpointDiv">
|
| 112 |
<label for="sourceEndpoint">Source Endpoint:</label>
|
| 113 |
<select id="sourceEndpoint">
|
|
@@ -116,9 +115,9 @@
|
|
| 116 |
</select>
|
| 117 |
</div>
|
| 118 |
<button id="cancelConn">Cancel</button>
|
| 119 |
-
<p style="font-size: 12px; color: #555;">
|
| 120 |
</div>
|
| 121 |
-
|
| 122 |
<!-- Constraint Edit Panel -->
|
| 123 |
<div id="constraintEditPanel">
|
| 124 |
<h3>Edit Constraint</h3>
|
|
@@ -139,11 +138,11 @@
|
|
| 139 |
<button id="updateConstraint">Update Constraint</button>
|
| 140 |
<button id="deleteConstraint">Delete Constraint</button>
|
| 141 |
</div>
|
| 142 |
-
|
| 143 |
<!-- Matter.js Library -->
|
| 144 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
|
| 145 |
<script>
|
| 146 |
-
// Module aliases and
|
| 147 |
const Engine = Matter.Engine,
|
| 148 |
Render = Matter.Render,
|
| 149 |
Runner = Matter.Runner,
|
|
@@ -154,7 +153,7 @@
|
|
| 154 |
Mouse = Matter.Mouse,
|
| 155 |
MouseConstraint = Matter.MouseConstraint,
|
| 156 |
Events = Matter.Events;
|
| 157 |
-
|
| 158 |
const engine = Engine.create();
|
| 159 |
const world = engine.world;
|
| 160 |
const render = Render.create({
|
|
@@ -171,13 +170,13 @@
|
|
| 171 |
const runner = Runner.create();
|
| 172 |
Runner.run(runner, engine);
|
| 173 |
|
| 174 |
-
//
|
| 175 |
let floor, wallLeft, wallRight;
|
| 176 |
function updateBoundaries() {
|
| 177 |
-
if
|
| 178 |
World.remove(world, [floor, wallLeft, wallRight]);
|
| 179 |
}
|
| 180 |
-
floor = Bodies.rectangle(window.innerWidth/2, window.innerHeight-50, window.innerWidth, 100, {
|
| 181 |
isStatic: true,
|
| 182 |
render: { fillStyle: '#060a19' }
|
| 183 |
});
|
|
@@ -187,7 +186,7 @@
|
|
| 187 |
}
|
| 188 |
updateBoundaries();
|
| 189 |
|
| 190 |
-
// Mouse control
|
| 191 |
let mouse = Mouse.create(render.canvas);
|
| 192 |
let mouseConstraint = MouseConstraint.create(engine, {
|
| 193 |
mouse: mouse,
|
|
@@ -196,10 +195,10 @@
|
|
| 196 |
World.add(world, mouseConstraint);
|
| 197 |
render.mouse = mouse;
|
| 198 |
|
| 199 |
-
// Global variables for editing
|
| 200 |
let selectedBody = null;
|
| 201 |
let selectedConstraint = null;
|
| 202 |
-
let attachMode = false;
|
| 203 |
let attachFrom = null;
|
| 204 |
const connectionPanel = document.getElementById('connectionPanel');
|
| 205 |
const editPanel = document.getElementById('editPanel');
|
|
@@ -318,6 +317,40 @@
|
|
| 318 |
<label for="blockColor">Color:</label>
|
| 319 |
<input type="color" id="blockColor" value="#2ecc71">
|
| 320 |
`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
}
|
| 322 |
subOptionsContainer.innerHTML = html;
|
| 323 |
}
|
|
@@ -435,6 +468,36 @@
|
|
| 435 |
block.customOptions = { x, y, width, height, friction, restitution, color, isSystemRoot: false };
|
| 436 |
World.add(world, block);
|
| 437 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
document.getElementById('addElement').addEventListener('click', function(){
|
| 439 |
const selected = document.getElementById('simulationSelect').value;
|
| 440 |
if(selected === 'bouncingBall') addBouncingBall();
|
|
@@ -443,13 +506,14 @@
|
|
| 443 |
else if(selected === 'inclinedPlane') addInclinedPlane();
|
| 444 |
else if(selected === 'springMass') addSpringMass();
|
| 445 |
else if(selected === 'rectangleBlock') addRectangleBlock();
|
|
|
|
|
|
|
| 446 |
});
|
| 447 |
-
// "Connect Systems" button
|
| 448 |
document.getElementById('connectSystems').addEventListener('click', function(){
|
| 449 |
attachMode = true;
|
| 450 |
-
attachFrom = null;
|
| 451 |
connectionPanel.style.display = "block";
|
| 452 |
-
// Set connection mode to system by default
|
| 453 |
document.getElementById('connMode').value = "system";
|
| 454 |
alert("System connection mode activated. Click on the first system element (must be marked as system root) then the target system element.");
|
| 455 |
});
|
|
@@ -465,8 +529,8 @@
|
|
| 465 |
hideConnectionPanel();
|
| 466 |
hideConstraintEditPanel();
|
| 467 |
});
|
| 468 |
-
|
| 469 |
-
/* ---------- Edit Panel (
|
| 470 |
function openEditPanel(body) {
|
| 471 |
selectedBody = body;
|
| 472 |
let html = `<h3>Edit Element</h3>
|
|
@@ -489,7 +553,7 @@
|
|
| 489 |
<label for="editBallFriction">Friction:</label>
|
| 490 |
<input type="number" id="editBallFriction" value="${opts.friction}" step="0.001" min="0" max="1">`;
|
| 491 |
}
|
| 492 |
-
//
|
| 493 |
html += `<div style="margin-top:10px;">
|
| 494 |
<button id="updateElement">Update</button>
|
| 495 |
<button id="deleteElement">Delete</button>
|
|
@@ -515,7 +579,6 @@
|
|
| 515 |
Body.setPosition(selectedBody, { x: newX, y: newY });
|
| 516 |
Body.setAngle(selectedBody, newAngle);
|
| 517 |
selectedBody.render.fillStyle = newColor;
|
| 518 |
-
// Update the "isSystemRoot" flag
|
| 519 |
const isSysRoot = document.getElementById("editSystemRoot").checked;
|
| 520 |
selectedBody.customOptions.isSystemRoot = isSysRoot;
|
| 521 |
if(selectedBody.elementType === "bouncingBall"){
|
|
@@ -533,8 +596,6 @@
|
|
| 533 |
}
|
| 534 |
hideEditPanel();
|
| 535 |
}
|
| 536 |
-
|
| 537 |
-
// Open edit panel on double-click
|
| 538 |
render.canvas.addEventListener("dblclick", function(event){
|
| 539 |
const rect = render.canvas.getBoundingClientRect();
|
| 540 |
const mousePos = { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
@@ -546,9 +607,8 @@
|
|
| 546 |
hideEditPanel();
|
| 547 |
}
|
| 548 |
});
|
| 549 |
-
|
| 550 |
-
/* ---------- Connection
|
| 551 |
-
// In attach mode, when clicking on elements, create a constraint
|
| 552 |
Events.on(mouseConstraint, 'mouseup', function(event){
|
| 553 |
const mousePos = event.mouse.position;
|
| 554 |
const bodies = Matter.Composite.allBodies(world);
|
|
@@ -557,19 +617,16 @@
|
|
| 557 |
const clickedBody = clickedBodies[0];
|
| 558 |
if(attachMode){
|
| 559 |
if(!attachFrom){
|
| 560 |
-
// First selection for attachment
|
| 561 |
attachFrom = clickedBody;
|
| 562 |
alert("First element selected. Now click on the target element.");
|
| 563 |
return;
|
| 564 |
-
} else if
|
| 565 |
alert("Please select a different element.");
|
| 566 |
return;
|
| 567 |
}
|
| 568 |
-
// Get connection options from the connection panel
|
| 569 |
const connType = document.getElementById('connType').value;
|
| 570 |
const connMode = document.getElementById('connMode').value;
|
| 571 |
-
|
| 572 |
-
if(connMode === "system") {
|
| 573 |
if(!attachFrom.customOptions.isSystemRoot){
|
| 574 |
alert("Source element is not marked as System Root. Please mark it in the edit panel.");
|
| 575 |
attachMode = false;
|
|
@@ -584,19 +641,16 @@
|
|
| 584 |
connectionPanel.style.display = "none";
|
| 585 |
return;
|
| 586 |
}
|
| 587 |
-
// Use the system root's position (we assume the element itself is the root)
|
| 588 |
}
|
| 589 |
-
// For springMass endpoints, get endpoint options if needed
|
| 590 |
let sourceEndpoint = "mass";
|
| 591 |
if(attachFrom.elementType === "springMass"){
|
| 592 |
-
|
| 593 |
}
|
| 594 |
let targetEndpoint = "mass";
|
| 595 |
if(clickedBody.elementType === "springMass"){
|
| 596 |
-
|
| 597 |
-
|
| 598 |
}
|
| 599 |
-
// Determine attachment points
|
| 600 |
let pointA = { x: 0, y: 0 }, bodyA = attachFrom;
|
| 601 |
if(attachFrom.elementType === "springMass" && sourceEndpoint === "fixed"){
|
| 602 |
bodyA = null;
|
|
@@ -607,8 +661,7 @@
|
|
| 607 |
bodyB = null;
|
| 608 |
pointB = clickedBody.fixedPoint || { x: clickedBody.position.x, y: clickedBody.position.y };
|
| 609 |
}
|
| 610 |
-
|
| 611 |
-
if(connMode === "system") {
|
| 612 |
bodyA = attachFrom;
|
| 613 |
bodyB = clickedBody;
|
| 614 |
pointA = { x: 0, y: 0 };
|
|
@@ -635,21 +688,20 @@
|
|
| 635 |
alert("Attachment created using " + connType + " connection in " + connMode + " mode.");
|
| 636 |
}
|
| 637 |
} else {
|
| 638 |
-
// Check if near a constraint
|
| 639 |
const constraints = Matter.Composite.allConstraints(world);
|
| 640 |
for(let cons of constraints){
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
}
|
| 649 |
hideConstraintEditPanel();
|
| 650 |
}
|
| 651 |
});
|
| 652 |
-
// Helper: distance from point to segment
|
| 653 |
function distanceToSegment(p, v, w){
|
| 654 |
let l2 = (w.x - v.x)**2 + (w.y - v.y)**2;
|
| 655 |
if(l2 === 0) return Math.hypot(p.x - v.x, p.y - v.y);
|
|
@@ -658,7 +710,7 @@
|
|
| 658 |
let proj = { x: v.x + t*(w.x-v.x), y: v.y + t*(w.y-v.y) };
|
| 659 |
return Math.hypot(p.x - proj.x, p.y - proj.y);
|
| 660 |
}
|
| 661 |
-
|
| 662 |
/* ---------- Constraint Edit Panel ---------- */
|
| 663 |
const constraintEditPanel = document.getElementById('constraintEditPanel');
|
| 664 |
function showConstraintEditPanel(cons){
|
|
@@ -696,7 +748,7 @@
|
|
| 696 |
hideConstraintEditPanel();
|
| 697 |
}
|
| 698 |
});
|
| 699 |
-
|
| 700 |
// Window resize: update renderer and boundaries
|
| 701 |
window.addEventListener('resize', function(){
|
| 702 |
Render.lookAt(render, { min: { x: 0, y: 0 }, max: { x: window.innerWidth, y: window.innerHeight } });
|
|
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
+
<title>Advanced Physics Simulator – Broad Elements Menu</title>
|
| 6 |
<style>
|
| 7 |
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
|
| 8 |
+
/* The canvas fills the viewport */
|
| 9 |
canvas { display: block; }
|
| 10 |
+
/* Top horizontal menu bar (outside the canvas) */
|
| 11 |
+
#topMenu {
|
| 12 |
+
position: fixed;
|
| 13 |
+
top: 0;
|
| 14 |
+
left: 0;
|
| 15 |
+
right: 0;
|
| 16 |
+
background: #ececec;
|
| 17 |
+
border-bottom: 1px solid #ccc;
|
| 18 |
+
padding: 5px;
|
| 19 |
+
z-index: 2000;
|
| 20 |
+
display: flex;
|
| 21 |
+
align-items: center;
|
| 22 |
+
gap: 15px;
|
|
|
|
| 23 |
}
|
| 24 |
+
#topMenu select, #topMenu button {
|
| 25 |
+
padding: 5px 10px;
|
| 26 |
+
font-size: 14px;
|
| 27 |
}
|
| 28 |
+
/* A container below the menu for additional options */
|
| 29 |
+
#subOptionsContainer {
|
| 30 |
+
position: fixed;
|
| 31 |
+
top: 45px;
|
| 32 |
+
left: 10px;
|
| 33 |
+
z-index: 2000;
|
| 34 |
+
background: rgba(255,255,255,0.95);
|
| 35 |
+
padding: 10px;
|
| 36 |
+
border: 1px solid #ccc;
|
| 37 |
+
border-radius: 4px;
|
| 38 |
}
|
| 39 |
+
/* Edit Panel (floating, triggered on double-click) */
|
| 40 |
#editPanel {
|
| 41 |
+
position: fixed;
|
| 42 |
+
top: 45px;
|
| 43 |
right: 10px;
|
| 44 |
+
z-index: 2000;
|
| 45 |
+
background: rgba(255,255,255,0.95);
|
| 46 |
+
padding: 10px;
|
| 47 |
+
border: 1px solid #ccc;
|
| 48 |
+
border-radius: 4px;
|
| 49 |
max-width: 300px;
|
| 50 |
+
max-height: 80vh;
|
| 51 |
overflow-y: auto;
|
| 52 |
display: none;
|
| 53 |
}
|
| 54 |
+
/* Connection and Constraint panels (similar styling) */
|
| 55 |
+
#connectionPanel, #constraintEditPanel {
|
| 56 |
+
position: fixed;
|
| 57 |
+
z-index: 2000;
|
| 58 |
+
background: rgba(255,255,255,0.95);
|
| 59 |
+
padding: 10px;
|
| 60 |
+
border: 1px solid #ccc;
|
| 61 |
+
border-radius: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
max-width: 250px;
|
| 63 |
display: none;
|
| 64 |
}
|
| 65 |
+
#connectionPanel { top: 45px; left: 50%; transform: translateX(-50%); }
|
| 66 |
+
#constraintEditPanel { bottom: 10px; right: 10px; }
|
| 67 |
+
/* Common styling for labels/inputs */
|
| 68 |
+
label { display: block; margin-top: 8px; font-size: 13px; }
|
| 69 |
+
input, select, button { width: 100%; padding: 5px; margin-top: 4px; font-size: 13px; }
|
| 70 |
</style>
|
| 71 |
</head>
|
| 72 |
<body>
|
| 73 |
+
<!-- Top horizontal menu bar (outside the canvas) -->
|
| 74 |
+
<div id="topMenu">
|
|
|
|
| 75 |
<select id="simulationSelect">
|
| 76 |
<option value="bouncingBall">Bouncing Ball</option>
|
| 77 |
<option value="pendulum">Pendulum</option>
|
|
|
|
| 79 |
<option value="inclinedPlane">Inclined Plane</option>
|
| 80 |
<option value="springMass">Spring–Mass System</option>
|
| 81 |
<option value="rectangleBlock">Rectangle Block</option>
|
| 82 |
+
<option value="rotatingRectangle">Rotating Rectangle</option>
|
| 83 |
+
<option value="triangle">Triangle</option>
|
| 84 |
</select>
|
| 85 |
+
<button id="addElement">Add Element</button>
|
| 86 |
+
<button id="connectSystems">Connect Systems</button>
|
| 87 |
+
<button id="reset">Reset Simulation</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
</div>
|
| 89 |
|
| 90 |
+
<!-- Container for dynamic suboptions -->
|
| 91 |
+
<div id="subOptionsContainer"></div>
|
| 92 |
+
|
| 93 |
<!-- Edit Panel (opens on double-click on an element) -->
|
| 94 |
<div id="editPanel"></div>
|
| 95 |
+
|
| 96 |
<!-- Connection Options Panel -->
|
| 97 |
<div id="connectionPanel">
|
| 98 |
<h3>Connection Options</h3>
|
|
|
|
| 107 |
<option value="element">Element</option>
|
| 108 |
<option value="system">System</option>
|
| 109 |
</select>
|
|
|
|
| 110 |
<div id="sourceEndpointDiv">
|
| 111 |
<label for="sourceEndpoint">Source Endpoint:</label>
|
| 112 |
<select id="sourceEndpoint">
|
|
|
|
| 115 |
</select>
|
| 116 |
</div>
|
| 117 |
<button id="cancelConn">Cancel</button>
|
| 118 |
+
<p style="font-size: 12px; color: #555;">Click on the target element to attach.</p>
|
| 119 |
</div>
|
| 120 |
+
|
| 121 |
<!-- Constraint Edit Panel -->
|
| 122 |
<div id="constraintEditPanel">
|
| 123 |
<h3>Edit Constraint</h3>
|
|
|
|
| 138 |
<button id="updateConstraint">Update Constraint</button>
|
| 139 |
<button id="deleteConstraint">Delete Constraint</button>
|
| 140 |
</div>
|
| 141 |
+
|
| 142 |
<!-- Matter.js Library -->
|
| 143 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
|
| 144 |
<script>
|
| 145 |
+
// Module aliases and engine setup
|
| 146 |
const Engine = Matter.Engine,
|
| 147 |
Render = Matter.Render,
|
| 148 |
Runner = Matter.Runner,
|
|
|
|
| 153 |
Mouse = Matter.Mouse,
|
| 154 |
MouseConstraint = Matter.MouseConstraint,
|
| 155 |
Events = Matter.Events;
|
| 156 |
+
|
| 157 |
const engine = Engine.create();
|
| 158 |
const world = engine.world;
|
| 159 |
const render = Render.create({
|
|
|
|
| 170 |
const runner = Runner.create();
|
| 171 |
Runner.run(runner, engine);
|
| 172 |
|
| 173 |
+
// Boundaries (responsive)
|
| 174 |
let floor, wallLeft, wallRight;
|
| 175 |
function updateBoundaries() {
|
| 176 |
+
if(floor && wallLeft && wallRight) {
|
| 177 |
World.remove(world, [floor, wallLeft, wallRight]);
|
| 178 |
}
|
| 179 |
+
floor = Bodies.rectangle(window.innerWidth/2, window.innerHeight - 50, window.innerWidth, 100, {
|
| 180 |
isStatic: true,
|
| 181 |
render: { fillStyle: '#060a19' }
|
| 182 |
});
|
|
|
|
| 186 |
}
|
| 187 |
updateBoundaries();
|
| 188 |
|
| 189 |
+
// Mouse control
|
| 190 |
let mouse = Mouse.create(render.canvas);
|
| 191 |
let mouseConstraint = MouseConstraint.create(engine, {
|
| 192 |
mouse: mouse,
|
|
|
|
| 195 |
World.add(world, mouseConstraint);
|
| 196 |
render.mouse = mouse;
|
| 197 |
|
| 198 |
+
// Global variables for editing and connection
|
| 199 |
let selectedBody = null;
|
| 200 |
let selectedConstraint = null;
|
| 201 |
+
let attachMode = false;
|
| 202 |
let attachFrom = null;
|
| 203 |
const connectionPanel = document.getElementById('connectionPanel');
|
| 204 |
const editPanel = document.getElementById('editPanel');
|
|
|
|
| 317 |
<label for="blockColor">Color:</label>
|
| 318 |
<input type="color" id="blockColor" value="#2ecc71">
|
| 319 |
`;
|
| 320 |
+
} else if(selected === 'rotatingRectangle'){
|
| 321 |
+
html += `
|
| 322 |
+
<label for="rotRectInitX">Initial X:</label>
|
| 323 |
+
<input type="number" id="rotRectInitX" value="200">
|
| 324 |
+
<label for="rotRectInitY">Initial Y:</label>
|
| 325 |
+
<input type="number" id="rotRectInitY" value="200">
|
| 326 |
+
<label for="rotRectWidth">Width:</label>
|
| 327 |
+
<input type="number" id="rotRectWidth" value="100">
|
| 328 |
+
<label for="rotRectHeight">Height:</label>
|
| 329 |
+
<input type="number" id="rotRectHeight" value="50">
|
| 330 |
+
<label for="rotRectRotationSpeed">Rotation Speed (rad/s):</label>
|
| 331 |
+
<input type="number" id="rotRectRotationSpeed" value="0.05" step="0.01">
|
| 332 |
+
<label for="rotRectFriction">Friction:</label>
|
| 333 |
+
<input type="number" id="rotRectFriction" value="0.1" step="0.01" min="0" max="1">
|
| 334 |
+
<label for="rotRectRestitution">Restitution:</label>
|
| 335 |
+
<input type="number" id="rotRectRestitution" value="0.3" step="0.1" min="0" max="1">
|
| 336 |
+
<label for="rotRectColor">Color:</label>
|
| 337 |
+
<input type="color" id="rotRectColor" value="#9b59b6">
|
| 338 |
+
`;
|
| 339 |
+
} else if(selected === 'triangle'){
|
| 340 |
+
html += `
|
| 341 |
+
<label for="triInitX">Initial X:</label>
|
| 342 |
+
<input type="number" id="triInitX" value="300">
|
| 343 |
+
<label for="triInitY">Initial Y:</label>
|
| 344 |
+
<input type="number" id="triInitY" value="300">
|
| 345 |
+
<label for="triSize">Size (radius):</label>
|
| 346 |
+
<input type="number" id="triSize" value="40">
|
| 347 |
+
<label for="triFriction">Friction:</label>
|
| 348 |
+
<input type="number" id="triFriction" value="0.1" step="0.01" min="0" max="1">
|
| 349 |
+
<label for="triRestitution">Restitution:</label>
|
| 350 |
+
<input type="number" id="triRestitution" value="0.3" step="0.1" min="0" max="1">
|
| 351 |
+
<label for="triColor">Color:</label>
|
| 352 |
+
<input type="color" id="triColor" value="#e74c3c">
|
| 353 |
+
`;
|
| 354 |
}
|
| 355 |
subOptionsContainer.innerHTML = html;
|
| 356 |
}
|
|
|
|
| 468 |
block.customOptions = { x, y, width, height, friction, restitution, color, isSystemRoot: false };
|
| 469 |
World.add(world, block);
|
| 470 |
}
|
| 471 |
+
function addRotatingRectangle(){
|
| 472 |
+
const x = parseFloat(document.getElementById("rotRectInitX").value);
|
| 473 |
+
const y = parseFloat(document.getElementById("rotRectInitY").value);
|
| 474 |
+
const width = parseFloat(document.getElementById("rotRectWidth").value);
|
| 475 |
+
const height = parseFloat(document.getElementById("rotRectHeight").value);
|
| 476 |
+
const rotationSpeed = parseFloat(document.getElementById("rotRectRotationSpeed").value);
|
| 477 |
+
const friction = parseFloat(document.getElementById("rotRectFriction").value);
|
| 478 |
+
const restitution = parseFloat(document.getElementById("rotRectRestitution").value);
|
| 479 |
+
const color = document.getElementById("rotRectColor").value || getRandomColor();
|
| 480 |
+
const rect = Bodies.rectangle(x, y, width, height, { friction, restitution, render: { fillStyle: color } });
|
| 481 |
+
rect.elementType = "rotatingRectangle";
|
| 482 |
+
rect.customOptions = { x, y, width, height, rotationSpeed, friction, restitution, color, isSystemRoot: false };
|
| 483 |
+
// Continuously rotate the rectangle
|
| 484 |
+
Events.on(engine, "beforeUpdate", function(){
|
| 485 |
+
Body.rotate(rect, rotationSpeed);
|
| 486 |
+
});
|
| 487 |
+
World.add(world, rect);
|
| 488 |
+
}
|
| 489 |
+
function addTriangle(){
|
| 490 |
+
const x = parseFloat(document.getElementById("triInitX").value);
|
| 491 |
+
const y = parseFloat(document.getElementById("triInitY").value);
|
| 492 |
+
const radius = parseFloat(document.getElementById("triSize").value);
|
| 493 |
+
const friction = parseFloat(document.getElementById("triFriction").value);
|
| 494 |
+
const restitution = parseFloat(document.getElementById("triRestitution").value);
|
| 495 |
+
const color = document.getElementById("triColor").value || getRandomColor();
|
| 496 |
+
const tri = Bodies.polygon(x, y, 3, radius, { friction, restitution, render: { fillStyle: color } });
|
| 497 |
+
tri.elementType = "triangle";
|
| 498 |
+
tri.customOptions = { x, y, radius, friction, restitution, color, isSystemRoot: false };
|
| 499 |
+
World.add(world, tri);
|
| 500 |
+
}
|
| 501 |
document.getElementById('addElement').addEventListener('click', function(){
|
| 502 |
const selected = document.getElementById('simulationSelect').value;
|
| 503 |
if(selected === 'bouncingBall') addBouncingBall();
|
|
|
|
| 506 |
else if(selected === 'inclinedPlane') addInclinedPlane();
|
| 507 |
else if(selected === 'springMass') addSpringMass();
|
| 508 |
else if(selected === 'rectangleBlock') addRectangleBlock();
|
| 509 |
+
else if(selected === 'rotatingRectangle') addRotatingRectangle();
|
| 510 |
+
else if(selected === 'triangle') addTriangle();
|
| 511 |
});
|
| 512 |
+
// "Connect Systems" button to initiate attach mode (for connecting elements or systems)
|
| 513 |
document.getElementById('connectSystems').addEventListener('click', function(){
|
| 514 |
attachMode = true;
|
| 515 |
+
attachFrom = null;
|
| 516 |
connectionPanel.style.display = "block";
|
|
|
|
| 517 |
document.getElementById('connMode').value = "system";
|
| 518 |
alert("System connection mode activated. Click on the first system element (must be marked as system root) then the target system element.");
|
| 519 |
});
|
|
|
|
| 529 |
hideConnectionPanel();
|
| 530 |
hideConstraintEditPanel();
|
| 531 |
});
|
| 532 |
+
|
| 533 |
+
/* ---------- Edit Panel Functions (Double-click on element) ---------- */
|
| 534 |
function openEditPanel(body) {
|
| 535 |
selectedBody = body;
|
| 536 |
let html = `<h3>Edit Element</h3>
|
|
|
|
| 553 |
<label for="editBallFriction">Friction:</label>
|
| 554 |
<input type="number" id="editBallFriction" value="${opts.friction}" step="0.001" min="0" max="1">`;
|
| 555 |
}
|
| 556 |
+
// Additional type-specific options can be added similarly
|
| 557 |
html += `<div style="margin-top:10px;">
|
| 558 |
<button id="updateElement">Update</button>
|
| 559 |
<button id="deleteElement">Delete</button>
|
|
|
|
| 579 |
Body.setPosition(selectedBody, { x: newX, y: newY });
|
| 580 |
Body.setAngle(selectedBody, newAngle);
|
| 581 |
selectedBody.render.fillStyle = newColor;
|
|
|
|
| 582 |
const isSysRoot = document.getElementById("editSystemRoot").checked;
|
| 583 |
selectedBody.customOptions.isSystemRoot = isSysRoot;
|
| 584 |
if(selectedBody.elementType === "bouncingBall"){
|
|
|
|
| 596 |
}
|
| 597 |
hideEditPanel();
|
| 598 |
}
|
|
|
|
|
|
|
| 599 |
render.canvas.addEventListener("dblclick", function(event){
|
| 600 |
const rect = render.canvas.getBoundingClientRect();
|
| 601 |
const mousePos = { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
|
|
| 607 |
hideEditPanel();
|
| 608 |
}
|
| 609 |
});
|
| 610 |
+
|
| 611 |
+
/* ---------- Connection / Attachment ---------- */
|
|
|
|
| 612 |
Events.on(mouseConstraint, 'mouseup', function(event){
|
| 613 |
const mousePos = event.mouse.position;
|
| 614 |
const bodies = Matter.Composite.allBodies(world);
|
|
|
|
| 617 |
const clickedBody = clickedBodies[0];
|
| 618 |
if(attachMode){
|
| 619 |
if(!attachFrom){
|
|
|
|
| 620 |
attachFrom = clickedBody;
|
| 621 |
alert("First element selected. Now click on the target element.");
|
| 622 |
return;
|
| 623 |
+
} else if(clickedBody === attachFrom){
|
| 624 |
alert("Please select a different element.");
|
| 625 |
return;
|
| 626 |
}
|
|
|
|
| 627 |
const connType = document.getElementById('connType').value;
|
| 628 |
const connMode = document.getElementById('connMode').value;
|
| 629 |
+
if(connMode === "system"){
|
|
|
|
| 630 |
if(!attachFrom.customOptions.isSystemRoot){
|
| 631 |
alert("Source element is not marked as System Root. Please mark it in the edit panel.");
|
| 632 |
attachMode = false;
|
|
|
|
| 641 |
connectionPanel.style.display = "none";
|
| 642 |
return;
|
| 643 |
}
|
|
|
|
| 644 |
}
|
|
|
|
| 645 |
let sourceEndpoint = "mass";
|
| 646 |
if(attachFrom.elementType === "springMass"){
|
| 647 |
+
sourceEndpoint = document.getElementById('sourceEndpoint').value;
|
| 648 |
}
|
| 649 |
let targetEndpoint = "mass";
|
| 650 |
if(clickedBody.elementType === "springMass"){
|
| 651 |
+
targetEndpoint = prompt("For target springMass, choose 'fixed' or 'mass' (default: mass):", "mass") || "mass";
|
| 652 |
+
if(targetEndpoint !== "fixed") { targetEndpoint = "mass"; }
|
| 653 |
}
|
|
|
|
| 654 |
let pointA = { x: 0, y: 0 }, bodyA = attachFrom;
|
| 655 |
if(attachFrom.elementType === "springMass" && sourceEndpoint === "fixed"){
|
| 656 |
bodyA = null;
|
|
|
|
| 661 |
bodyB = null;
|
| 662 |
pointB = clickedBody.fixedPoint || { x: clickedBody.position.x, y: clickedBody.position.y };
|
| 663 |
}
|
| 664 |
+
if(connMode === "system"){
|
|
|
|
| 665 |
bodyA = attachFrom;
|
| 666 |
bodyB = clickedBody;
|
| 667 |
pointA = { x: 0, y: 0 };
|
|
|
|
| 688 |
alert("Attachment created using " + connType + " connection in " + connMode + " mode.");
|
| 689 |
}
|
| 690 |
} else {
|
| 691 |
+
// Check if near a constraint for editing
|
| 692 |
const constraints = Matter.Composite.allConstraints(world);
|
| 693 |
for(let cons of constraints){
|
| 694 |
+
let posA = cons.bodyA ? { x: cons.bodyA.position.x + cons.pointA.x, y: cons.bodyA.position.y + cons.pointA.y } : cons.pointA;
|
| 695 |
+
let posB = cons.bodyB ? { x: cons.bodyB.position.x + cons.pointB.x, y: cons.bodyB.position.y + cons.pointB.y } : cons.pointB;
|
| 696 |
+
let dist = distanceToSegment(mousePos, posA, posB);
|
| 697 |
+
if(dist < 5){
|
| 698 |
+
showConstraintEditPanel(cons);
|
| 699 |
+
return;
|
| 700 |
+
}
|
| 701 |
}
|
| 702 |
hideConstraintEditPanel();
|
| 703 |
}
|
| 704 |
});
|
|
|
|
| 705 |
function distanceToSegment(p, v, w){
|
| 706 |
let l2 = (w.x - v.x)**2 + (w.y - v.y)**2;
|
| 707 |
if(l2 === 0) return Math.hypot(p.x - v.x, p.y - v.y);
|
|
|
|
| 710 |
let proj = { x: v.x + t*(w.x-v.x), y: v.y + t*(w.y-v.y) };
|
| 711 |
return Math.hypot(p.x - proj.x, p.y - proj.y);
|
| 712 |
}
|
| 713 |
+
|
| 714 |
/* ---------- Constraint Edit Panel ---------- */
|
| 715 |
const constraintEditPanel = document.getElementById('constraintEditPanel');
|
| 716 |
function showConstraintEditPanel(cons){
|
|
|
|
| 748 |
hideConstraintEditPanel();
|
| 749 |
}
|
| 750 |
});
|
| 751 |
+
|
| 752 |
// Window resize: update renderer and boundaries
|
| 753 |
window.addEventListener('resize', function(){
|
| 754 |
Render.lookAt(render, { min: { x: 0, y: 0 }, max: { x: window.innerWidth, y: window.innerHeight } });
|