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 } });
|