Ramesh-vani commited on
Commit
1ab6ab7
·
verified ·
1 Parent(s): 4e6dd2f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +693 -249
index.html CHANGED
@@ -1,263 +1,707 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Physics Simulator</title>
7
- <style>
8
- /* Basic Reset */
9
- * { margin: 0; padding: 0; box-sizing: border-box; }
10
-
11
- /* Body */
12
- body { font-family: Arial, sans-serif; overflow: hidden; }
13
-
14
- /* Menu Bar */
15
- .menu-bar {
16
- display: flex;
17
- background-color: #0078D7;
18
- color: white;
19
- padding: 10px;
20
- position: fixed;
21
- width: 100%;
22
- top: 0;
23
- z-index: 10;
24
- }
25
-
26
- .menu-bar div {
27
- margin: 0 15px;
28
- cursor: pointer;
29
- position: relative;
30
- }
31
-
32
- .menu-bar div:hover { background-color: #005A9E; }
33
-
34
- /* Dropdown */
35
- .dropdown {
36
- display: none;
37
- position: absolute;
38
- background-color: white;
39
- color: black;
40
- border: 1px solid #ccc;
41
- top: 35px;
42
- left: 0;
43
- z-index: 20;
44
- }
45
-
46
- .dropdown div {
47
- padding: 8px 12px;
48
- cursor: pointer;
49
- }
50
-
51
- .dropdown div:hover { background-color: #f0f0f0; }
52
-
53
- /* Canvas */
54
- #physicsCanvas {
55
- position: absolute;
56
- top: 50px;
57
- left: 0;
58
- width: 100%;
59
- height: calc(100vh - 50px);
60
- background-color: #f5f5f5;
61
- }
62
-
63
- /* Elements */
64
- .element {
65
- width: 50px;
66
- height: 50px;
67
- background-color: #ff9800;
68
- position: absolute;
69
- cursor: move;
70
- border-radius: 5px;
71
- }
72
-
73
- /* Edit Menu */
74
- .edit-menu {
75
- position: absolute;
76
- background-color: white;
77
- border: 1px solid #ccc;
78
- padding: 10px;
79
- display: none;
80
- z-index: 50;
81
- }
82
-
83
- .edit-menu input { width: 100%; margin-top: 5px; }
84
- .edit-menu button { margin-top: 10px; width: 100%; }
85
- </style>
86
  </head>
87
  <body>
88
-
89
- <!-- Menu Bar -->
90
- <div class="menu-bar">
91
- <div id="fileMenu">File
92
- <div class="dropdown" id="fileDropdown">
93
- <div onclick="addElement()">Add Element</div>
94
- <div onclick="clearCanvas()">Clear Canvas</div>
95
- </div>
96
- </div>
97
-
98
- <div id="editMenu">Edit
99
- <div class="dropdown" id="editDropdown">
100
- <div onclick="undo()">Undo</div>
101
- <div onclick="redo()">Redo</div>
102
- <div onclick="deleteElement()">Delete Selected</div>
103
- </div>
104
- </div>
105
-
106
- <div id="viewMenu">View
107
- <div class="dropdown" id="viewDropdown">
108
- <div onclick="toggleGrid()">Toggle Grid</div>
109
- </div>
110
- </div>
111
  </div>
112
-
113
- <!-- Canvas -->
114
- <div id="physicsCanvas"></div>
115
-
116
- <!-- Edit Element Menu -->
117
- <div class="edit-menu" id="editElementMenu">
118
- <label>Change Color:</label>
119
- <input type="color" id="colorPicker">
120
- <button onclick="applyEdit()">Apply</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  </div>
122
-
123
- <!-- JavaScript -->
124
- <script>
125
- const physicsCanvas = document.getElementById('physicsCanvas');
126
- let selectedElement = null;
127
- let undoStack = [];
128
- let redoStack = [];
129
-
130
- // Menu Toggle
131
- document.querySelectorAll('.menu-bar div').forEach(menu => {
132
- menu.addEventListener('click', () => {
133
- const dropdown = menu.querySelector('.dropdown');
134
- document.querySelectorAll('.dropdown').forEach(d => {
135
- if (d !== dropdown) d.style.display = 'none';
136
- });
137
- dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
138
- });
139
- });
140
-
141
- // Add Element
142
- function addElement() {
143
- const elem = document.createElement('div');
144
- elem.classList.add('element');
145
- elem.style.top = '100px';
146
- elem.style.left = '100px';
147
-
148
- elem.addEventListener('mousedown', startDrag);
149
- elem.addEventListener('dblclick', openEditMenu);
150
-
151
- physicsCanvas.appendChild(elem);
152
- saveState();
153
- }
154
-
155
- // Drag & Drop
156
- let offsetX, offsetY;
157
-
158
- function startDrag(e) {
159
- selectedElement = e.target;
160
- offsetX = e.clientX - selectedElement.offsetLeft;
161
- offsetY = e.clientY - selectedElement.offsetTop;
162
-
163
- document.addEventListener('mousemove', dragElement);
164
- document.addEventListener('mouseup', stopDrag);
165
- }
166
-
167
- function dragElement(e) {
168
- if (selectedElement) {
169
- selectedElement.style.left = (e.clientX - offsetX) + 'px';
170
- selectedElement.style.top = (e.clientY - offsetY) + 'px';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  }
172
- }
173
-
174
- function stopDrag() {
175
- document.removeEventListener('mousemove', dragElement);
176
- document.removeEventListener('mouseup', stopDrag);
177
- saveState();
178
- }
179
-
180
- // Open Edit Menu
181
- function openEditMenu(e) {
182
- selectedElement = e.target;
183
- const menu = document.getElementById('editElementMenu');
184
- menu.style.left = e.clientX + 'px';
185
- menu.style.top = e.clientY + 'px';
186
- document.getElementById('colorPicker').value = rgbToHex(selectedElement.style.backgroundColor);
187
- menu.style.display = 'block';
188
- }
189
-
190
- // Apply Edit
191
- function applyEdit() {
192
- const color = document.getElementById('colorPicker').value;
193
- selectedElement.style.backgroundColor = color;
194
- document.getElementById('editElementMenu').style.display = 'none';
195
- saveState();
196
- }
197
-
198
- // Convert RGB to HEX
199
- function rgbToHex(rgb) {
200
- if (!rgb) return '#ff9800';
201
- const result = rgb.match(/\d+/g);
202
- return "#" + result.map(x => parseInt(x).toString(16).padStart(2, '0')).join('');
203
- }
204
-
205
- // Delete Element
206
- function deleteElement() {
207
- if (selectedElement) {
208
- selectedElement.remove();
209
- selectedElement = null;
210
- saveState();
211
  }
212
- }
213
-
214
- // Clear Canvas
215
- function clearCanvas() {
216
- physicsCanvas.innerHTML = '';
217
- saveState();
218
- }
219
-
220
- // Undo & Redo
221
- function saveState() {
222
- undoStack.push(physicsCanvas.innerHTML);
223
- redoStack = [];
224
- }
225
-
226
- function undo() {
227
- if (undoStack.length > 1) {
228
- redoStack.push(undoStack.pop());
229
- physicsCanvas.innerHTML = undoStack[undoStack.length - 1];
230
- rebindElements();
231
  }
232
- }
233
-
234
- function redo() {
235
- if (redoStack.length) {
236
- undoStack.push(redoStack.pop());
237
- physicsCanvas.innerHTML = undoStack[undoStack.length - 1];
238
- rebindElements();
239
  }
240
- }
241
-
242
- // Rebind Events after Undo/Redo
243
- function rebindElements() {
244
- document.querySelectorAll('.element').forEach(elem => {
245
- elem.addEventListener('mousedown', startDrag);
246
- elem.addEventListener('dblclick', openEditMenu);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  });
248
- }
249
-
250
- // Toggle Grid View
251
- let gridOn = false;
252
- function toggleGrid() {
253
- gridOn = !gridOn;
254
- physicsCanvas.style.backgroundImage = gridOn ? 'linear-gradient(#ccc 1px, transparent 1px), linear-gradient(90deg, #ccc 1px, transparent 1px)' : 'none';
255
- physicsCanvas.style.backgroundSize = gridOn ? '20px 20px' : 'none';
256
- }
257
-
258
- // Initial State Save
259
- saveState();
260
- </script>
261
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  </body>
263
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <title>Advanced Physics Simulator – Connect Systems & More Elements</title>
6
+ <style>
7
+ body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
8
+ canvas { display: block; }
9
+ /* Menus always on top */
10
+ #controls, #editPanel, #connectionPanel, #constraintEditPanel {
11
+ position: absolute;
12
+ background: rgba(255,255,255,0.95);
13
+ padding: 10px;
14
+ border-radius: 5px;
15
+ z-index: 1000;
16
+ }
17
+ #controls {
18
+ top: 10px;
19
+ left: 10px;
20
+ max-width: 320px;
21
+ max-height: 90vh;
22
+ overflow-y: auto;
23
+ }
24
+ #controls label { display: block; margin-top: 8px; }
25
+ #controls input, #controls select, #controls button {
26
+ width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
27
+ }
28
+ #buttonContainer {
29
+ display: flex;
30
+ justify-content: space-between;
31
+ gap: 5px;
32
+ }
33
+ #editPanel {
34
+ top: 10px;
35
+ right: 10px;
36
+ max-width: 300px;
37
+ max-height: 90vh;
38
+ overflow-y: auto;
39
+ display: none;
40
+ }
41
+ #editPanel h3 { margin: 0 0 5px 0; }
42
+ #editPanel label { display: block; margin-top: 8px; }
43
+ #editPanel input, #editPanel select, #editPanel button {
44
+ width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
45
+ }
46
+ /* Connection Options Panel */
47
+ #connectionPanel {
48
+ top: 150px;
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
+ #constraintEditPanel h3 { margin: 0 0 5px 0; }
66
+ #constraintEditPanel label { display: block; margin-top: 8px; }
67
+ #constraintEditPanel input, #constraintEditPanel select, #constraintEditPanel button {
68
+ width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
69
+ }
70
+ </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  </head>
72
  <body>
73
+ <!-- Controls Panel -->
74
+ <div id="controls">
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>
79
+ <option value="projectile">Projectile Motion</option>
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
+ <!-- Dynamic Suboptions for creation parameters -->
85
+ <div id="subOptionsContainer"></div>
86
+ <div id="buttonContainer">
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>
99
+ <label for="connType">Connection Type:</label>
100
+ <select id="connType">
101
+ <option value="string">String</option>
102
+ <option value="spring">Spring</option>
103
+ <option value="stick">Stick</option>
104
+ </select>
105
+ <label for="connMode">Connection Mode:</label>
106
+ <select id="connMode">
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">
114
+ <option value="mass">Mass</option>
115
+ <option value="fixed">Fixed</option>
116
+ </select>
117
  </div>
118
+ <button id="cancelConn">Cancel</button>
119
+ <p style="font-size: 12px; color: #555;">Now click on the target element to attach.</p>
120
+ </div>
121
+
122
+ <!-- Constraint Edit Panel -->
123
+ <div id="constraintEditPanel">
124
+ <h3>Edit Constraint</h3>
125
+ <label for="consA_X">Endpoint A X:</label>
126
+ <input type="number" id="consA_X" step="1">
127
+ <label for="consA_Y">Endpoint A Y:</label>
128
+ <input type="number" id="consA_Y" step="1">
129
+ <label for="consB_X">Endpoint B X:</label>
130
+ <input type="number" id="consB_X" step="1">
131
+ <label for="consB_Y">Endpoint B Y:</label>
132
+ <input type="number" id="consB_Y" step="1">
133
+ <label for="consType">Connection Type:</label>
134
+ <select id="consType">
135
+ <option value="string">String</option>
136
+ <option value="spring">Spring</option>
137
+ <option value="stick">Stick</option>
138
+ </select>
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 Engine Setup
147
+ const Engine = Matter.Engine,
148
+ Render = Matter.Render,
149
+ Runner = Matter.Runner,
150
+ World = Matter.World,
151
+ Bodies = Matter.Bodies,
152
+ Body = Matter.Body,
153
+ Constraint = Matter.Constraint,
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({
161
+ element: document.body,
162
+ engine: engine,
163
+ options: {
164
+ width: window.innerWidth,
165
+ height: window.innerHeight,
166
+ wireframes: false,
167
+ background: '#f0f0f0'
168
+ }
169
+ });
170
+ Render.run(render);
171
+ const runner = Runner.create();
172
+ Runner.run(runner, engine);
173
+
174
+ // Global boundaries (to be updated on resize)
175
+ let floor, wallLeft, wallRight;
176
+ function updateBoundaries() {
177
+ if (floor && wallLeft && wallRight) {
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
+ });
184
+ wallLeft = Bodies.rectangle(-50, window.innerHeight/2, 100, window.innerHeight, { isStatic: true });
185
+ wallRight = Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, { isStatic: true });
186
+ World.add(world, [floor, wallLeft, wallRight]);
187
+ }
188
+ updateBoundaries();
189
+
190
+ // Mouse control for drag & drop
191
+ let mouse = Mouse.create(render.canvas);
192
+ let mouseConstraint = MouseConstraint.create(engine, {
193
+ mouse: mouse,
194
+ constraint: { stiffness: 0.2, render: { visible: false } }
195
+ });
196
+ World.add(world, mouseConstraint);
197
+ render.mouse = mouse;
198
+
199
+ // Global variables for editing & connection
200
+ let selectedBody = null;
201
+ let selectedConstraint = null;
202
+ let attachMode = false; // if true, next click will pick connection target
203
+ let attachFrom = null;
204
+ const connectionPanel = document.getElementById('connectionPanel');
205
+ const editPanel = document.getElementById('editPanel');
206
+
207
+ // Helper: random color
208
+ function getRandomColor() {
209
+ return '#' + Math.floor(Math.random()*16777215).toString(16);
210
+ }
211
+
212
+ /* ---------- Dynamic Suboptions for Adding Elements ---------- */
213
+ function updateSubOptions() {
214
+ const simulationSelect = document.getElementById('simulationSelect');
215
+ const subOptionsContainer = document.getElementById('subOptionsContainer');
216
+ let selected = simulationSelect.value;
217
+ let html = '';
218
+ if(selected === 'bouncingBall'){
219
+ html += `
220
+ <label for="ballInitialX">Initial X:</label>
221
+ <input type="number" id="ballInitialX" value="100">
222
+ <label for="ballInitialY">Initial Y:</label>
223
+ <input type="number" id="ballInitialY" value="50">
224
+ <label for="ballRadius">Radius:</label>
225
+ <input type="number" id="ballRadius" value="30">
226
+ <label for="ballRestitution">Restitution:</label>
227
+ <input type="number" id="ballRestitution" value="0.9" step="0.1" min="0" max="1">
228
+ <label for="ballFriction">Friction:</label>
229
+ <input type="number" id="ballFriction" value="0.005" step="0.001" min="0" max="1">
230
+ <label for="ballColor">Color:</label>
231
+ <input type="color" id="ballColor" value="#3498db">
232
+ `;
233
+ } else if(selected === 'pendulum'){
234
+ html += `
235
+ <label for="pendulumPivotX">Pivot X:</label>
236
+ <input type="number" id="pendulumPivotX" value="${window.innerWidth-200}">
237
+ <label for="pendulumPivotY">Pivot Y:</label>
238
+ <input type="number" id="pendulumPivotY" value="50">
239
+ <label for="pendulumBobRadius">Bob Radius:</label>
240
+ <input type="number" id="pendulumBobRadius" value="40">
241
+ <label for="pendulumLength">Length:</label>
242
+ <input type="number" id="pendulumLength" value="250">
243
+ <label for="pendulumStiffness">Stiffness:</label>
244
+ <input type="number" id="pendulumStiffness" value="1" step="0.1" min="0" max="1">
245
+ <label for="pendulumBobColor">Color:</label>
246
+ <input type="color" id="pendulumBobColor" value="#ff0000">
247
+ `;
248
+ } else if(selected === 'projectile'){
249
+ html += `
250
+ <label for="projectileInitialX">Initial X:</label>
251
+ <input type="number" id="projectileInitialX" value="100">
252
+ <label for="projectileInitialY">Initial Y:</label>
253
+ <input type="number" id="projectileInitialY" value="300">
254
+ <label for="projectileRadius">Radius:</label>
255
+ <input type="number" id="projectileRadius" value="20">
256
+ <label for="projectileVelX">Velocity X:</label>
257
+ <input type="number" id="projectileVelX" value="15">
258
+ <label for="projectileVelY">Velocity Y:</label>
259
+ <input type="number" id="projectileVelY" value="-15">
260
+ <label for="projectileColor">Color:</label>
261
+ <input type="color" id="projectileColor" value="#e67e22">
262
+ `;
263
+ } else if(selected === 'inclinedPlane'){
264
+ html += `
265
+ <label for="rampWidth">Ramp Width:</label>
266
+ <input type="number" id="rampWidth" value="300">
267
+ <label for="rampHeight">Ramp Height:</label>
268
+ <input type="number" id="rampHeight" value="20">
269
+ <label for="rampAngle">Ramp Angle (°):</label>
270
+ <input type="number" id="rampAngle" value="30">
271
+ <label for="rampX">Ramp X:</label>
272
+ <input type="number" id="rampX" value="400">
273
+ <label for="rampY">Ramp Y:</label>
274
+ <input type="number" id="rampY" value="${window.innerHeight-150}">
275
+ <label for="rampColor">Ramp Color:</label>
276
+ <input type="color" id="rampColor" value="#8e44ad">
277
+ <hr>
278
+ <label for="blockWidth">Block Width:</label>
279
+ <input type="number" id="blockWidth" value="40">
280
+ <label for="blockHeight">Block Height:</label>
281
+ <input type="number" id="blockHeight" value="40">
282
+ <label for="blockFriction">Block Friction:</label>
283
+ <input type="number" id="blockFriction" value="0.05" step="0.01" min="0" max="1">
284
+ <label for="blockColor">Block Color:</label>
285
+ <input type="color" id="blockColor" value="#1abc9c">
286
+ `;
287
+ } else if(selected === 'springMass'){
288
+ html += `
289
+ <label for="springMassRadius">Mass Radius:</label>
290
+ <input type="number" id="springMassRadius" value="25">
291
+ <label for="springMassRestitution">Restitution:</label>
292
+ <input type="number" id="springMassRestitution" value="0.8" step="0.1" min="0" max="1">
293
+ <label for="fixedPointX">Fixed X:</label>
294
+ <input type="number" id="fixedPointX" value="${Math.floor(window.innerWidth/2)}">
295
+ <label for="fixedPointY">Fixed Y:</label>
296
+ <input type="number" id="fixedPointY" value="100">
297
+ <label for="springLength">Spring Length:</label>
298
+ <input type="number" id="springLength" value="200">
299
+ <label for="springStiffness">Stiffness:</label>
300
+ <input type="number" id="springStiffness" value="0.02" step="0.01" min="0" max="1">
301
+ <label for="springMassColor">Color:</label>
302
+ <input type="color" id="springMassColor" value="#f39c12">
303
+ `;
304
+ } else if(selected === 'rectangleBlock'){
305
+ html += `
306
+ <label for="blockInitX">Initial X:</label>
307
+ <input type="number" id="blockInitX" value="150">
308
+ <label for="blockInitY">Initial Y:</label>
309
+ <input type="number" id="blockInitY" value="150">
310
+ <label for="blockWidth">Width:</label>
311
+ <input type="number" id="blockWidth" value="80">
312
+ <label for="blockHeight">Height:</label>
313
+ <input type="number" id="blockHeight" value="40">
314
+ <label for="blockFriction">Friction:</label>
315
+ <input type="number" id="blockFriction" value="0.1" step="0.01" min="0" max="1">
316
+ <label for="blockRestitution">Restitution:</label>
317
+ <input type="number" id="blockRestitution" value="0.3" step="0.1" min="0" max="1">
318
+ <label for="blockColor">Color:</label>
319
+ <input type="color" id="blockColor" value="#2ecc71">
320
+ `;
321
+ }
322
+ subOptionsContainer.innerHTML = html;
323
+ }
324
+ updateSubOptions();
325
+ document.getElementById('simulationSelect').addEventListener('change', updateSubOptions);
326
+
327
+ /* ---------- Functions to Add Simulation Elements ---------- */
328
+ function addBouncingBall(){
329
+ const x = parseFloat(document.getElementById("ballInitialX").value);
330
+ const y = parseFloat(document.getElementById("ballInitialY").value);
331
+ const radius = parseFloat(document.getElementById("ballRadius").value);
332
+ const restitution = parseFloat(document.getElementById("ballRestitution").value);
333
+ const friction = parseFloat(document.getElementById("ballFriction").value);
334
+ const color = document.getElementById("ballColor").value || getRandomColor();
335
+ const ball = Bodies.circle(x, y, radius, { restitution, friction, render: { fillStyle: color } });
336
+ ball.elementType = "bouncingBall";
337
+ ball.customOptions = { x, y, radius, restitution, friction, color, isSystemRoot: false };
338
+ World.add(world, ball);
339
+ }
340
+ function addPendulum(){
341
+ const pivotX = parseFloat(document.getElementById("pendulumPivotX").value);
342
+ const pivotY = parseFloat(document.getElementById("pendulumPivotY").value);
343
+ const bobRadius = parseFloat(document.getElementById("pendulumBobRadius").value);
344
+ const pendulumLength = parseFloat(document.getElementById("pendulumLength").value);
345
+ const stiffness = parseFloat(document.getElementById("pendulumStiffness").value);
346
+ const bobColor = document.getElementById("pendulumBobColor").value || "#ff0000";
347
+ const bob = Bodies.circle(pivotX, pivotY+pendulumLength, bobRadius, { restitution: 1, density: 0.005, render: { fillStyle: bobColor } });
348
+ bob.elementType = "pendulum";
349
+ bob.customOptions = { pivotX, pivotY, bobRadius, pendulumLength, stiffness, bobColor, isSystemRoot: false };
350
+ const pendulumConstraint = Constraint.create({
351
+ pointA: { x: pivotX, y: pivotY },
352
+ bodyB: bob,
353
+ length: pendulumLength,
354
+ stiffness: stiffness,
355
+ render: { strokeStyle: '#000', lineWidth: 2 }
356
+ });
357
+ World.add(world, [bob, pendulumConstraint]);
358
+ }
359
+ function addProjectile(){
360
+ const x = parseFloat(document.getElementById("projectileInitialX").value);
361
+ const y = parseFloat(document.getElementById("projectileInitialY").value);
362
+ const radius = parseFloat(document.getElementById("projectileRadius").value);
363
+ const velX = parseFloat(document.getElementById("projectileVelX").value);
364
+ const velY = parseFloat(document.getElementById("projectileVelY").value);
365
+ const color = document.getElementById("projectileColor").value || getRandomColor();
366
+ const proj = Bodies.circle(x, y, radius, { restitution: 0.8, frictionAir: 0.001, render: { fillStyle: color } });
367
+ proj.elementType = "projectile";
368
+ proj.customOptions = { x, y, radius, velX, velY, color, isSystemRoot: false };
369
+ Body.setVelocity(proj, { x: velX, y: velY });
370
+ World.add(world, proj);
371
+ }
372
+ function addInclinedPlane(){
373
+ const rampWidth = parseFloat(document.getElementById("rampWidth").value);
374
+ const rampHeight = parseFloat(document.getElementById("rampHeight").value);
375
+ const angleDeg = parseFloat(document.getElementById("rampAngle").value);
376
+ const angle = angleDeg * Math.PI/180;
377
+ const rampX = parseFloat(document.getElementById("rampX").value);
378
+ const rampY = parseFloat(document.getElementById("rampY").value);
379
+ const rampColor = document.getElementById("rampColor").value || "#8e44ad";
380
+ const ramp = Bodies.rectangle(rampX, rampY, rampWidth, rampHeight, {
381
+ isStatic: true,
382
+ angle: angle,
383
+ render: { fillStyle: rampColor }
384
+ });
385
+ ramp.elementType = "inclinedPlane_ramp";
386
+ ramp.customOptions = { rampWidth, rampHeight, angleDeg, rampX, rampY, rampColor, isSystemRoot: false };
387
+ const blockWidth = parseFloat(document.getElementById("blockWidth").value);
388
+ const blockHeight = parseFloat(document.getElementById("blockHeight").value);
389
+ const blockFriction = parseFloat(document.getElementById("blockFriction").value);
390
+ const blockColor = document.getElementById("blockColor").value || "#1abc9c";
391
+ const block = Bodies.rectangle(rampX - rampWidth/4, rampY - 50, blockWidth, blockHeight, {
392
+ friction: blockFriction,
393
+ render: { fillStyle: blockColor }
394
+ });
395
+ block.elementType = "inclinedPlane_block";
396
+ block.customOptions = { blockWidth, blockHeight, blockFriction, blockColor, isSystemRoot: false };
397
+ World.add(world, [ramp, block]);
398
+ }
399
+ function addSpringMass(){
400
+ const massRadius = parseFloat(document.getElementById("springMassRadius").value);
401
+ const massRestitution = parseFloat(document.getElementById("springMassRestitution").value);
402
+ const fixedPointX = parseFloat(document.getElementById("fixedPointX").value);
403
+ const fixedPointY = parseFloat(document.getElementById("fixedPointY").value);
404
+ const springLength = parseFloat(document.getElementById("springLength").value);
405
+ const springStiffness = parseFloat(document.getElementById("springStiffness").value);
406
+ const massColor = document.getElementById("springMassColor").value || getRandomColor();
407
+ const mass = Bodies.circle(fixedPointX, fixedPointY+springLength, massRadius, {
408
+ restitution: massRestitution,
409
+ density: 0.004,
410
+ render: { fillStyle: massColor }
411
+ });
412
+ mass.elementType = "springMass";
413
+ mass.customOptions = { massRadius, massRestitution, fixedPointX, fixedPointY, springLength, springStiffness, massColor, isSystemRoot: false };
414
+ mass.fixedPoint = { x: fixedPointX, y: fixedPointY };
415
+ const spring = Constraint.create({
416
+ pointA: { x: fixedPointX, y: fixedPointY },
417
+ bodyB: mass,
418
+ length: springLength,
419
+ stiffness: springStiffness,
420
+ damping: 0.05,
421
+ render: { strokeStyle: '#000', lineWidth: 2 }
422
+ });
423
+ World.add(world, [mass, spring]);
424
+ }
425
+ function addRectangleBlock(){
426
+ const x = parseFloat(document.getElementById("blockInitX").value);
427
+ const y = parseFloat(document.getElementById("blockInitY").value);
428
+ const width = parseFloat(document.getElementById("blockWidth").value);
429
+ const height = parseFloat(document.getElementById("blockHeight").value);
430
+ const friction = parseFloat(document.getElementById("blockFriction").value);
431
+ const restitution = parseFloat(document.getElementById("blockRestitution").value);
432
+ const color = document.getElementById("blockColor").value || getRandomColor();
433
+ const block = Bodies.rectangle(x, y, width, height, { friction, restitution, render: { fillStyle: color } });
434
+ block.elementType = "rectangleBlock";
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();
441
+ else if(selected === 'pendulum') addPendulum();
442
+ else if(selected === 'projectile') addProjectile();
443
+ else if(selected === 'inclinedPlane') addInclinedPlane();
444
+ else if(selected === 'springMass') addSpringMass();
445
+ else if(selected === 'rectangleBlock') addRectangleBlock();
446
+ });
447
+ // "Connect Systems" button (sets attach mode in SYSTEM mode)
448
+ document.getElementById('connectSystems').addEventListener('click', function(){
449
+ attachMode = true;
450
+ attachFrom = null; // will be set on first click
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
+ });
456
+ document.getElementById('reset').addEventListener('click', function(){
457
+ World.clear(world);
458
+ Engine.clear(engine);
459
+ updateBoundaries();
460
+ mouse = Mouse.create(render.canvas);
461
+ mouseConstraint = MouseConstraint.create(engine, { mouse: mouse, constraint: { stiffness: 0.2, render: { visible: false } } });
462
+ World.add(world, mouseConstraint);
463
+ render.mouse = mouse;
464
+ hideEditPanel();
465
+ hideConnectionPanel();
466
+ hideConstraintEditPanel();
467
+ });
468
+
469
+ /* ---------- Edit Panel (opens on double-click) ---------- */
470
+ function openEditPanel(body) {
471
+ selectedBody = body;
472
+ let html = `<h3>Edit Element</h3>
473
+ <label for="editPosX">Position X:</label>
474
+ <input type="number" id="editPosX" value="${body.position.x.toFixed(2)}">
475
+ <label for="editPosY">Position Y:</label>
476
+ <input type="number" id="editPosY" value="${body.position.y.toFixed(2)}">
477
+ <label for="editAngle">Angle (radians):</label>
478
+ <input type="number" id="editAngle" value="${body.angle.toFixed(2)}">
479
+ <label for="editColor">Color:</label>
480
+ <input type="color" id="editColor" value="${body.render.fillStyle || '#ffffff'}">
481
+ <label for="editSystemRoot">Is System Root?</label>
482
+ <input type="checkbox" id="editSystemRoot" ${body.customOptions.isSystemRoot ? "checked" : ""}>`;
483
+ if(body.elementType === "bouncingBall"){
484
+ const opts = body.customOptions;
485
+ html += `<label for="editBallRadius">Radius:</label>
486
+ <input type="number" id="editBallRadius" value="${opts.radius}">
487
+ <label for="editBallRestitution">Restitution:</label>
488
+ <input type="number" id="editBallRestitution" value="${opts.restitution}" step="0.1" min="0" max="1">
489
+ <label for="editBallFriction">Friction:</label>
490
+ <input type="number" id="editBallFriction" value="${opts.friction}" step="0.001" min="0" max="1">`;
491
+ }
492
+ // (Additional type-specific options can be added similarly for other element types)
493
+ html += `<div style="margin-top:10px;">
494
+ <button id="updateElement">Update</button>
495
+ <button id="deleteElement">Delete</button>
496
+ </div>`;
497
+ editPanel.innerHTML = html;
498
+ editPanel.style.display = "block";
499
+ document.getElementById("updateElement").addEventListener("click", updateElementFromEditPanel);
500
+ document.getElementById("deleteElement").addEventListener("click", function(){
501
+ World.remove(world, selectedBody);
502
+ hideEditPanel();
503
+ });
504
+ }
505
+ function hideEditPanel() {
506
+ editPanel.style.display = "none";
507
+ selectedBody = null;
508
+ }
509
+ function updateElementFromEditPanel(){
510
+ if(!selectedBody) return;
511
+ const newX = parseFloat(document.getElementById("editPosX").value);
512
+ const newY = parseFloat(document.getElementById("editPosY").value);
513
+ const newAngle = parseFloat(document.getElementById("editAngle").value);
514
+ const newColor = document.getElementById("editColor").value;
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"){
522
+ const oldRadius = selectedBody.circleRadius;
523
+ const newRadius = parseFloat(document.getElementById("editBallRadius").value);
524
+ const newRestitution = parseFloat(document.getElementById("editBallRestitution").value);
525
+ const newFriction = parseFloat(document.getElementById("editBallFriction").value);
526
+ if(newRadius && oldRadius && newRadius !== oldRadius){
527
+ const scaleFactor = newRadius / oldRadius;
528
+ Body.scale(selectedBody, scaleFactor, scaleFactor);
529
+ }
530
+ selectedBody.restitution = newRestitution;
531
+ selectedBody.friction = newFriction;
532
+ selectedBody.customOptions = { x: newX, y: newY, radius: newRadius, restitution: newRestitution, friction: newFriction, color: newColor, isSystemRoot: isSysRoot };
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 };
541
+ const bodies = Matter.Composite.allBodies(world);
542
+ const clicked = Matter.Query.point(bodies, mousePos);
543
+ if(clicked.length > 0){
544
+ openEditPanel(clicked[0]);
545
+ } else {
546
+ hideEditPanel();
547
+ }
548
+ });
549
+
550
+ /* ---------- Connection (Attachment) ---------- */
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);
555
+ const clickedBodies = Matter.Query.point(bodies, mousePos);
556
+ if(clickedBodies.length > 0){
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 (clickedBody === attachFrom) {
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
+ // If connection mode is "system", verify both elements are marked as system root
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;
576
+ attachFrom = null;
577
+ connectionPanel.style.display = "none";
578
+ return;
579
+ }
580
+ if(!clickedBody.customOptions.isSystemRoot){
581
+ alert("Target element is not marked as System Root. Please mark it in its edit panel.");
582
+ attachMode = false;
583
+ attachFrom = null;
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
+ sourceEndpoint = document.getElementById('sourceEndpoint').value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
  }
594
+ let targetEndpoint = "mass";
595
+ if(clickedBody.elementType === "springMass"){
596
+ targetEndpoint = prompt("For target springMass, choose 'fixed' or 'mass' (default: mass):", "mass") || "mass";
597
+ if(targetEndpoint !== "fixed") { targetEndpoint = "mass"; }
 
 
 
598
  }
599
+ // Determine attachment points
600
+ let pointA = { x: 0, y: 0 }, bodyA = attachFrom;
601
+ if(attachFrom.elementType === "springMass" && sourceEndpoint === "fixed"){
602
+ bodyA = null;
603
+ pointA = attachFrom.fixedPoint;
604
+ }
605
+ let pointB = { x: 0, y: 0 }, bodyB = clickedBody;
606
+ if(clickedBody.elementType === "springMass" && targetEndpoint === "fixed"){
607
+ bodyB = null;
608
+ pointB = clickedBody.fixedPoint || { x: clickedBody.position.x, y: clickedBody.position.y };
609
+ }
610
+ // If connection mode is "system", override to use the elements as-is
611
+ if(connMode === "system") {
612
+ bodyA = attachFrom;
613
+ bodyB = clickedBody;
614
+ pointA = { x: 0, y: 0 };
615
+ pointB = { x: 0, y: 0 };
616
+ }
617
+ let posA = bodyA ? attachFrom.position : pointA;
618
+ let posB = bodyB ? clickedBody.position : pointB;
619
+ let dx = posB.x - posA.x, dy = posB.y - posA.y;
620
+ let length = Math.sqrt(dx*dx + dy*dy);
621
+ let stiffness = (connType === "spring") ? 0.05 : 1;
622
+ const newConstraint = Constraint.create({
623
+ bodyA: bodyA,
624
+ pointA: pointA,
625
+ bodyB: bodyB,
626
+ pointB: pointB,
627
+ length: length,
628
+ stiffness: stiffness,
629
+ render: { strokeStyle: '#000', lineWidth: 2 }
630
  });
631
+ World.add(world, newConstraint);
632
+ attachMode = false;
633
+ attachFrom = null;
634
+ connectionPanel.style.display = "none";
635
+ alert("Attachment created using " + connType + " connection in " + connMode + " mode.");
636
+ }
637
+ } else {
638
+ // Check if near a constraint line for editing…
639
+ const constraints = Matter.Composite.allConstraints(world);
640
+ for(let cons of constraints){
641
+ let posA = cons.bodyA ? { x: cons.bodyA.position.x + cons.pointA.x, y: cons.bodyA.position.y + cons.pointA.y } : cons.pointA;
642
+ let posB = cons.bodyB ? { x: cons.bodyB.position.x + cons.pointB.x, y: cons.bodyB.position.y + cons.pointB.y } : cons.pointB;
643
+ let dist = distanceToSegment(mousePos, posA, posB);
644
+ if(dist < 5){
645
+ showConstraintEditPanel(cons);
646
+ return;
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);
656
+ let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
657
+ t = Math.max(0, Math.min(1, t));
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){
665
+ selectedConstraint = cons;
666
+ let posA = cons.bodyA ? { x: cons.bodyA.position.x + cons.pointA.x, y: cons.bodyA.position.y + cons.pointA.y } : cons.pointA;
667
+ let posB = cons.bodyB ? { x: cons.bodyB.position.x + cons.pointB.x, y: cons.bodyB.position.y + cons.pointB.y } : cons.pointB;
668
+ document.getElementById('consA_X').value = posA.x.toFixed(2);
669
+ document.getElementById('consA_Y').value = posA.y.toFixed(2);
670
+ document.getElementById('consB_X').value = posB.x.toFixed(2);
671
+ document.getElementById('consB_Y').value = posB.y.toFixed(2);
672
+ let connType = (cons.stiffness < 0.1) ? "spring" : "string";
673
+ document.getElementById('consType').value = connType;
674
+ constraintEditPanel.style.display = "block";
675
+ }
676
+ function hideConstraintEditPanel(){
677
+ constraintEditPanel.style.display = "none";
678
+ selectedConstraint = null;
679
+ }
680
+ document.getElementById('updateConstraint').addEventListener('click', function(){
681
+ if(selectedConstraint){
682
+ let aX = parseFloat(document.getElementById('consA_X').value);
683
+ let aY = parseFloat(document.getElementById('consA_Y').value);
684
+ let bX = parseFloat(document.getElementById('consB_X').value);
685
+ let bY = parseFloat(document.getElementById('consB_Y').value);
686
+ if(!selectedConstraint.bodyA) { selectedConstraint.pointA = { x: aX, y: aY }; }
687
+ if(!selectedConstraint.bodyB) { selectedConstraint.pointB = { x: bX, y: bY }; }
688
+ let connType = document.getElementById('consType').value;
689
+ selectedConstraint.stiffness = (connType === "spring") ? 0.05 : 1;
690
+ hideConstraintEditPanel();
691
+ }
692
+ });
693
+ document.getElementById('deleteConstraint').addEventListener('click', function(){
694
+ if(selectedConstraint){
695
+ World.remove(world, selectedConstraint);
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 } });
703
+ updateBoundaries();
704
+ });
705
+ </script>
706
  </body>
707
  </html>