Ramesh-vani commited on
Commit
e1fc0fd
·
verified ·
1 Parent(s): 0bb84f1

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +411 -531
index.html CHANGED
@@ -2,161 +2,188 @@
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,
@@ -164,350 +191,265 @@
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);
@@ -515,193 +457,131 @@
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>
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <title>Physics Simulator for IIT Aspirants</title>
6
  <style>
7
  body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
8
+ canvas { display: block; width: 100%; height: 100vh; }
9
+
10
+ /* Top Navbar for Controls */
11
+ #navbar {
12
+ position: fixed;
13
+ top: 0;
14
+ left: 0;
15
+ width: 100%;
16
+ background: rgba(255, 255, 255, 0.95);
17
+ padding: 10px 20px;
18
  z-index: 1000;
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  display: flex;
20
  justify-content: space-between;
21
+ align-items: center;
22
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
23
  }
24
+ #controls {
25
+ display: flex;
26
+ align-items: center;
27
+ gap: 15px;
28
+ flex-wrap: wrap;
29
+ max-width: 70%;
 
30
  }
31
+ #controls label { margin: 0 5px 0 0; font-size: 14px; }
32
+ #controls select, #controls button {
33
+ padding: 6px;
34
+ font-size: 14px;
35
+ border-radius: 4px;
36
+ border: 1px solid #ccc;
37
  }
38
+ #controls select { min-width: 150px; }
39
+ #buttonContainer { display: flex; gap: 10px; }
40
+
41
+ /* Suboptions Sidebar (Left) */
42
+ #subOptionsSidebar {
43
+ position: fixed;
44
+ top: 60px;
45
+ left: 0;
46
+ width: 250px;
47
+ height: calc(100vh - 60px);
48
+ background: rgba(245, 245, 245, 0.95);
49
+ padding: 15px;
50
+ z-index: 900;
51
+ overflow-y: auto;
52
+ box-shadow: 2px 0 5px rgba(0,0,0,0.1);
53
+ transition: transform 0.3s ease;
54
  }
55
+ #subOptionsSidebar.hidden { transform: translateX(-100%); }
56
+ #subOptionsContainer label { display: block; margin: 10px 0 5px; font-size: 14px; }
57
+ #subOptionsContainer input, #subOptionsContainer select {
58
+ width: 100%; padding: 6px; font-size: 14px; border-radius: 4px; border: 1px solid #ccc; }
59
+
60
+ /* Right Sidebar for Edit and Connection Panels */
61
+ #rightSidebar {
62
+ position: fixed;
63
+ top: 60px;
64
+ right: 0;
65
+ width: 300px;
66
+ height: calc(100vh - 60px);
67
+ background: rgba(245, 245, 245, 0.95);
68
+ padding: 15px;
69
+ z-index: 900;
70
+ overflow-y: auto;
71
+ box-shadow: -2px 0 5px rgba(0,0,0,0.1);
72
+ transition: transform 0.3s ease;
73
  }
74
+ #rightSidebar.hidden { transform: translateX(100%); }
75
+ #editPanel, #connectionPanel { margin-bottom: 20px; }
76
+ #editPanel h3, #connectionPanel h3 { margin: 0 0 10px; font-size: 16px; }
77
+ #editPanel label, #connectionPanel label { display: block; margin: 10px 0 5px; font-size: 14px; }
78
+ #editPanel input, #editPanel select, #editPanel button,
79
+ #connectionPanel input, #connectionPanel select, #connectionPanel button {
80
+ width: 100%; padding: 6px; font-size: 14px; border-radius: 4px; border: 1px solid #ccc; }
81
+ #editPanel button, #connectionPanel button { margin-top: 10px; }
82
+
83
+ /* Bottom Bar for Real-Time Data and Constraint Edit */
84
+ #bottomBar {
85
+ position: fixed;
86
+ bottom: 0;
87
+ left: 0;
88
+ width: 100%;
89
+ background: rgba(255, 255, 255, 0.95);
90
+ padding: 10px 20px;
91
+ z-index: 1000;
92
+ display: flex;
93
+ justify-content: space-between;
94
+ align-items: center;
95
+ box-shadow: 0 -2px 5px rgba(0,0,0,0.1);
96
  }
97
+ #dataDisplay { flex: 1; font-size: 14px; }
98
+ #dataDisplay h3 { margin: 0 0 5px; font-size: 16px; }
99
+ #constraintEditPanel { flex: 0 0 300px; display: none; }
100
+ #constraintEditPanel h3 { margin: 0 0 10px; font-size: 16px; }
101
+ #constraintEditPanel label { display: block; margin: 10px 0 5px; font-size: 14px; }
102
  #constraintEditPanel input, #constraintEditPanel select, #constraintEditPanel button {
103
+ width: 100%; padding: 6px; font-size: 14px; border-radius: 4px; border: 1px solid #ccc; }
104
+ #constraintEditPanel button { margin-top: 10px; }
105
  </style>
106
  </head>
107
  <body>
108
+ <!-- Top Navbar -->
109
+ <div id="navbar">
110
+ <div id="controls">
111
+ <label for="simulationSelect">Select Simulation:</label>
112
+ <select id="simulationSelect">
113
+ <option value="bouncingBall">Bouncing Ball</option>
114
+ <option value="pendulum">Pendulum</option>
115
+ <option value="projectile">Projectile Motion</option>
116
+ <option value="inclinedPlane">Inclined Plane</option>
117
+ <option value="springMass">Spring-Mass System</option>
118
+ </select>
119
+ <div id="buttonContainer">
120
+ <button id="addElement">Add Element</button>
121
+ <button id="reset">Reset</button>
122
+ </div>
 
 
123
  </div>
124
  </div>
125
 
126
+ <!-- Left Sidebar for Suboptions -->
127
+ <div id="subOptionsSidebar">
128
+ <div id="subOptionsContainer"></div>
129
+ </div>
130
 
131
+ <!-- Right Sidebar for Edit and Connection Panels -->
132
+ <div id="rightSidebar" class="hidden">
133
+ <div id="editPanel"></div>
134
+ <div id="connectionPanel">
135
+ <h3>Connect Elements</h3>
136
+ <label for="connType">Connection Type:</label>
137
+ <select id="connType">
138
+ <option value="string">String (Inextensible)</option>
139
+ <option value="spring">Spring (Elastic)</option>
140
+ <option value="stick">Rigid Stick</option>
 
 
 
 
 
 
 
 
 
 
141
  </select>
142
+ <div id="sourceEndpointDiv">
143
+ <label for="sourceEndpoint">Source Endpoint:</label>
144
+ <select id="sourceEndpoint">
145
+ <option value="mass">Mass</option>
146
+ <option value="fixed">Fixed Point</option>
147
+ </select>
148
+ </div>
149
+ <button id="cancelConn">Cancel</button>
150
+ <p style="font-size: 12px; color: #555;">Click the target element to connect.</p>
151
  </div>
 
 
152
  </div>
153
 
154
+ <!-- Bottom Bar for Data and Constraints -->
155
+ <div id="bottomBar">
156
+ <div id="dataDisplay">
157
+ <h3>Real-Time Data</h3>
158
+ <p id="dataContent">Select an element to view data.</p>
159
+ </div>
160
+ <div id="constraintEditPanel">
161
+ <h3>Edit Constraint</h3>
162
+ <label for="consA_X">Endpoint A X:</label>
163
+ <input type="number" id="consA_X" step="0.1">
164
+ <label for="consA_Y">Endpoint A Y:</label>
165
+ <input type="number" id="consA_Y" step="0.1">
166
+ <label for="consB_X">Endpoint B X:</label>
167
+ <input type="number" id="consB_X" step="0.1">
168
+ <label for="consB_Y">Endpoint B Y:</label>
169
+ <input type="number" id="consB_Y" step="0.1">
170
+ <label for="consType">Connection Type:</label>
171
+ <select id="consType">
172
+ <option value="string">String</option>
173
+ <option value="spring">Spring</option>
174
+ <option value="stick">Stick</option>
175
+ </select>
176
+ <button id="updateConstraint">Update</button>
177
+ <button id="deleteConstraint">Delete</button>
178
+ </div>
179
  </div>
180
 
 
181
  <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
182
  <script>
183
+ const { Engine, Render, Runner, World, Bodies, Body, Constraint, Mouse, MouseConstraint, Events, Vector } = Matter;
 
 
 
 
 
 
 
 
 
 
 
184
  const engine = Engine.create();
185
  const world = engine.world;
186
+ engine.gravity.scale = 0.001;
187
  const render = Render.create({
188
  element: document.body,
189
  engine: engine,
 
191
  width: window.innerWidth,
192
  height: window.innerHeight,
193
  wireframes: false,
194
+ background: '#e9ecef'
195
  }
196
  });
197
  Render.run(render);
198
  const runner = Runner.create();
199
  Runner.run(runner, engine);
200
 
 
201
  let floor, wallLeft, wallRight;
202
  function updateBoundaries() {
203
+ if (floor && wallLeft && wallRight) World.remove(world, [floor, wallLeft, wallRight]);
204
+ floor = Bodies.rectangle(window.innerWidth / 2, window.innerHeight + 50, window.innerWidth * 2, 100, { isStatic: true, render: { fillStyle: '#343a40' } });
205
+ wallLeft = Bodies.rectangle(-50, window.innerHeight / 2, 100, window.innerHeight * 2, { isStatic: true, render: { fillStyle: '#343a40' } });
206
+ wallRight = Bodies.rectangle(window.innerWidth + 50, window.innerHeight / 2, 100, window.innerHeight * 2, { isStatic: true, render: { fillStyle: '#343a40' } });
 
 
 
 
 
207
  World.add(world, [floor, wallLeft, wallRight]);
208
  }
209
  updateBoundaries();
210
 
211
+ const mouse = Mouse.create(render.canvas);
212
+ const mouseConstraint = MouseConstraint.create(engine, { mouse: mouse, constraint: { stiffness: 0.2, render: { visible: false } } });
 
 
 
 
213
  World.add(world, mouseConstraint);
214
  render.mouse = mouse;
215
 
216
+ let selectedBody = null, selectedConstraint = null, attachMode = false, attachFrom = null;
217
+ const subOptionsSidebar = document.getElementById('subOptionsSidebar'),
218
+ rightSidebar = document.getElementById('rightSidebar'),
219
+ editPanel = document.getElementById('editPanel'),
220
+ connectionPanel = document.getElementById('connectionPanel'),
221
+ constraintEditPanel = document.getElementById('constraintEditPanel'),
222
+ dataContent = document.getElementById('dataContent');
223
 
224
+ function getRandomColor() { return '#' + Math.floor(Math.random() * 16777215).toString(16); }
 
 
 
225
 
 
226
  function updateSubOptions() {
227
  const simulationSelect = document.getElementById('simulationSelect');
228
  const subOptionsContainer = document.getElementById('subOptionsContainer');
229
+ const selected = simulationSelect.value;
230
  let html = '';
231
+ if (selected === 'bouncingBall') {
232
+ html = `
233
+ <label for="ballInitialX">Initial X (m):</label><input type="number" id="ballInitialX" value="200">
234
+ <label for="ballInitialY">Initial Y (m):</label><input type="number" id="ballInitialY" value="50">
235
+ <label for="ballRadius">Radius (m):</label><input type="number" id="ballRadius" value="20" min="5">
236
+ <label for="ballRestitution">Restitution:</label><input type="number" id="ballRestitution" value="0.9" step="0.1" min="0" max="1">
237
+ <label for="ballFriction">Friction:</label><input type="number" id="ballFriction" value="0.01" step="0.01" min="0" max="1">
238
+ <label for="ballColor">Color:</label><input type="color" id="ballColor" value="#3498db">
239
+ `;
240
+ } else if (selected === 'pendulum') {
241
+ html = `
242
+ <label for="pendulumPivotX">Pivot X (m):</label><input type="number" id="pendulumPivotX" value="${Math.floor(window.innerWidth / 2)}">
243
+ <label for="pendulumPivotY">Pivot Y (m):</label><input type="number" id="pendulumPivotY" value="50">
244
+ <label for="pendulumBobRadius">Bob Radius (m):</label><input type="number" id="pendulumBobRadius" value="30" min="5">
245
+ <label for="pendulumLength">Length (m):</label><input type="number" id="pendulumLength" value="300" min="50">
246
+ <label for="pendulumStiffness">Stiffness:</label><input type="number" id="pendulumStiffness" value="1" step="0.1" min="0" max="1">
247
+ <label for="pendulumBobColor">Color:</label><input type="color" id="pendulumBobColor" value="#e74c3c">
248
+ `;
249
+ } else if (selected === 'projectile') {
250
+ html = `
251
+ <label for="projectileInitialX">Initial X (m):</label><input type="number" id="projectileInitialX" value="100">
252
+ <label for="projectileInitialY">Initial Y (m):</label><input type="number" id="projectileInitialY" value="${window.innerHeight - 150}">
253
+ <label for="projectileRadius">Radius (m):</label><input type="number" id="projectileRadius" value="15" min="5">
254
+ <label for="projectileVelX">Velocity X (m/s):</label><input type="number" id="projectileVelX" value="20">
255
+ <label for="projectileVelY">Velocity Y (m/s):</label><input type="number" id="projectileVelY" value="-25">
256
+ <label for="projectileColor">Color:</label><input type="color" id="projectileColor" value="#f1c40f">
257
+ `;
258
+ } else if (selected === 'inclinedPlane') {
259
+ html = `
260
+ <label for="rampWidth">Ramp Width (m):</label><input type="number" id="rampWidth" value="400" min="100">
261
+ <label for="rampHeight">Ramp Height (m):</label><input type="number" id="rampHeight" value="20" min="10">
262
+ <label for="rampAngle">Angle (°):</label><input type="number" id="rampAngle" value="30" min="0" max="90">
263
+ <label for="rampX">Ramp X (m):</label><input type="number" id="rampX" value="400">
264
+ <label for="rampY">Ramp Y (m):</label><input type="number" id="rampY" value="${window.innerHeight - 100}">
265
+ <label for="rampColor">Ramp Color:</label><input type="color" id="rampColor" value="#8e44ad">
266
+ <label for="blockWidth">Block Width (m):</label><input type="number" id="blockWidth" value="50" min="10">
267
+ <label for="blockHeight">Block Height (m):</label><input type="number" id="blockHeight" value="50" min="10">
268
+ <label for="blockFriction">Block Friction:</label><input type="number" id="blockFriction" value="0.3" step="0.1" min="0" max="1">
269
+ <label for="blockColor">Block Color:</label><input type="color" id="blockColor" value="#2ecc71">
270
+ `;
271
+ } else if (selected === 'springMass') {
272
+ html = `
273
+ <label for="springMassRadius">Mass Radius (m):</label><input type="number" id="springMassRadius" value="25" min="5">
274
+ <label for="springMassRestitution">Restitution:</label><input type="number" id="springMassRestitution" value="0.5" step="0.1" min="0" max="1">
275
+ <label for="fixedPointX">Fixed X (m):</label><input type="number" id="fixedPointX" value="${Math.floor(window.innerWidth / 2)}">
276
+ <label for="fixedPointY">Fixed Y (m):</label><input type="number" id="fixedPointY" value="100">
277
+ <label for="springLength">Spring Length (m):</label><input type="number" id="springLength" value="200" min="50">
278
+ <label for="springStiffness">Stiffness:</label><input type="number" id="springStiffness" value="0.05" step="0.01" min="0" max="1">
279
+ <label for="springMassColor">Color:</label><input type="color" id="springMassColor" value="#e67e22">
280
+ `;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  }
282
  subOptionsContainer.innerHTML = html;
283
+ subOptionsSidebar.classList.remove('hidden');
284
  }
285
  updateSubOptions();
286
  document.getElementById('simulationSelect').addEventListener('change', updateSubOptions);
287
 
288
+ function addBouncingBall() {
 
289
  const x = parseFloat(document.getElementById("ballInitialX").value);
290
  const y = parseFloat(document.getElementById("ballInitialY").value);
291
  const radius = parseFloat(document.getElementById("ballRadius").value);
292
  const restitution = parseFloat(document.getElementById("ballRestitution").value);
293
  const friction = parseFloat(document.getElementById("ballFriction").value);
294
+ const color = document.getElementById("ballColor").value;
295
+ const ball = Bodies.circle(x, y, radius, { restitution, friction, render: { fillStyle: color }, density: 0.001 });
296
  ball.elementType = "bouncingBall";
297
+ ball.customOptions = { x, y, radius, restitution, friction, color };
298
  World.add(world, ball);
299
  }
300
+
301
+ function addPendulum() {
302
  const pivotX = parseFloat(document.getElementById("pendulumPivotX").value);
303
  const pivotY = parseFloat(document.getElementById("pendulumPivotY").value);
304
  const bobRadius = parseFloat(document.getElementById("pendulumBobRadius").value);
305
+ const length = parseFloat(document.getElementById("pendulumLength").value);
306
  const stiffness = parseFloat(document.getElementById("pendulumStiffness").value);
307
+ const color = document.getElementById("pendulumBobColor").value;
308
+ const bob = Bodies.circle(pivotX, pivotY + length, bobRadius, { restitution: 0.9, density: 0.001, render: { fillStyle: color } });
309
+ const constraint = Constraint.create({
 
 
310
  pointA: { x: pivotX, y: pivotY },
311
  bodyB: bob,
312
+ length,
313
+ stiffness,
314
+ render: { strokeStyle: '#333', lineWidth: 2 }
315
  });
316
+ bob.elementType = "pendulum";
317
+ bob.customOptions = { pivotX, pivotY, bobRadius, length, stiffness, color };
318
+ World.add(world, [bob, constraint]);
319
  }
320
+
321
+ function addProjectile() {
322
  const x = parseFloat(document.getElementById("projectileInitialX").value);
323
  const y = parseFloat(document.getElementById("projectileInitialY").value);
324
  const radius = parseFloat(document.getElementById("projectileRadius").value);
325
  const velX = parseFloat(document.getElementById("projectileVelX").value);
326
  const velY = parseFloat(document.getElementById("projectileVelY").value);
327
+ const color = document.getElementById("projectileColor").value;
328
+ const proj = Bodies.circle(x, y, radius, { restitution: 0.8, frictionAir: 0.005, render: { fillStyle: color }, density: 0.001 });
 
 
329
  Body.setVelocity(proj, { x: velX, y: velY });
330
+ proj.elementType = "projectile";
331
+ proj.customOptions = { x, y, radius, velX, velY, color };
332
  World.add(world, proj);
333
  }
334
+
335
+ function addInclinedPlane() {
336
  const rampWidth = parseFloat(document.getElementById("rampWidth").value);
337
  const rampHeight = parseFloat(document.getElementById("rampHeight").value);
338
  const angleDeg = parseFloat(document.getElementById("rampAngle").value);
339
+ const angle = angleDeg * Math.PI / 180;
340
  const rampX = parseFloat(document.getElementById("rampX").value);
341
  const rampY = parseFloat(document.getElementById("rampY").value);
342
+ const rampColor = document.getElementById("rampColor").value;
343
  const ramp = Bodies.rectangle(rampX, rampY, rampWidth, rampHeight, {
344
  isStatic: true,
345
+ angle,
346
+ friction: 0.1,
347
  render: { fillStyle: rampColor }
348
  });
 
 
349
  const blockWidth = parseFloat(document.getElementById("blockWidth").value);
350
  const blockHeight = parseFloat(document.getElementById("blockHeight").value);
351
  const blockFriction = parseFloat(document.getElementById("blockFriction").value);
352
+ const blockColor = document.getElementById("blockColor").value;
353
+ const blockX = rampX - rampWidth / 4 * Math.cos(angle);
354
+ const blockY = rampY - rampHeight / 2 - blockHeight / 2 - 10;
355
+ const block = Bodies.rectangle(blockX, blockY, blockWidth, blockHeight, {
356
  friction: blockFriction,
357
+ render: { fillStyle: blockColor },
358
+ density: 0.001
359
  });
360
+ ramp.elementType = "inclinedPlane_ramp";
361
+ ramp.customOptions = { rampWidth, rampHeight, angleDeg, rampX, rampY, rampColor };
362
  block.elementType = "inclinedPlane_block";
363
+ block.customOptions = { blockWidth, blockHeight, blockFriction, blockColor };
364
  World.add(world, [ramp, block]);
365
  }
366
+
367
+ function addSpringMass() {
368
+ const radius = parseFloat(document.getElementById("springMassRadius").value);
369
+ const restitution = parseFloat(document.getElementById("springMassRestitution").value);
370
+ const fixedX = parseFloat(document.getElementById("fixedPointX").value);
371
+ const fixedY = parseFloat(document.getElementById("fixedPointY").value);
372
+ const length = parseFloat(document.getElementById("springLength").value);
373
+ const stiffness = parseFloat(document.getElementById("springStiffness").value);
374
+ const color = document.getElementById("springMassColor").value;
375
+ const mass = Bodies.circle(fixedX, fixedY + length, radius, { restitution, density: 0.001, render: { fillStyle: color } });
 
 
 
 
 
 
376
  const spring = Constraint.create({
377
+ pointA: { x: fixedX, y: fixedY },
378
  bodyB: mass,
379
+ length,
380
+ stiffness,
381
  damping: 0.05,
382
+ render: { strokeStyle: '#333', lineWidth: 2 }
383
  });
384
+ mass.elementType = "springMass";
385
+ mass.customOptions = { radius, restitution, fixedX, fixedY, length, stiffness, color };
386
+ mass.fixedPoint = { x: fixedX, y: fixedY };
387
  World.add(world, [mass, spring]);
388
  }
389
+
390
+ document.getElementById('addElement').addEventListener('click', () => {
 
 
 
 
 
 
 
 
 
 
 
 
391
  const selected = document.getElementById('simulationSelect').value;
392
+ if (selected === 'bouncingBall') addBouncingBall();
393
+ else if (selected === 'pendulum') addPendulum();
394
+ else if (selected === 'projectile') addProjectile();
395
+ else if (selected === 'inclinedPlane') addInclinedPlane();
396
+ else if (selected === 'springMass') addSpringMass();
 
 
 
 
 
 
 
 
 
 
397
  });
398
+
399
+ document.getElementById('reset').addEventListener('click', () => {
400
  World.clear(world);
401
  Engine.clear(engine);
402
  updateBoundaries();
403
+ const newMouse = Mouse.create(render.canvas);
404
+ const newMouseConstraint = MouseConstraint.create(engine, { mouse: newMouse, constraint: { stiffness: 0.2, render: { visible: false } } });
405
+ World.add(world, newMouseConstraint);
406
+ render.mouse = newMouse;
407
  hideEditPanel();
408
  hideConnectionPanel();
409
  hideConstraintEditPanel();
410
+ dataContent.textContent = "Select an element to view data.";
411
  });
412
 
 
413
  function openEditPanel(body) {
414
  selectedBody = body;
415
+ rightSidebar.classList.remove('hidden');
416
  let html = `<h3>Edit Element</h3>
417
+ <label for="editPosX">Position X (m):</label><input type="number" id="editPosX" value="${body.position.x.toFixed(1)}" step="0.1">
418
+ <label for="editPosY">Position Y (m):</label><input type="number" id="editPosY" value="${body.position.y.toFixed(1)}" step="0.1">
419
+ <label for="editAngle">Angle (rad):</label><input type="number" id="editAngle" value="${body.angle.toFixed(2)}" step="0.01">
420
+ <label for="editColor">Color:</label><input type="color" id="editColor" value="${body.render.fillStyle}">`;
421
+ if (body.elementType === "bouncingBall") {
422
+ const opts = body.customOptions;
423
+ html += `<label for="editBallRadius">Radius (m):</label><input type="number" id="editBallRadius" value="${opts.radius}" min="5">
424
+ <label for="editBallRestitution">Restitution:</label><input type="number" id="editBallRestitution" value="${opts.restitution}" step="0.1" min="0" max="1">
425
+ <label for="editBallFriction">Friction:</label><input type="number" id="editBallFriction" value="${opts.friction}" step="0.01" min="0" max="1">`;
426
+ } else if (body.elementType === "pendulum") {
427
+ const opts = body.customOptions;
428
+ html += `<label for="editPendulumPivotX">Pivot X (m):</label><input type="number" id="editPendulumPivotX" value="${opts.pivotX}">
429
+ <label for="editPendulumPivotY">Pivot Y (m):</label><input type="number" id="editPendulumPivotY" value="${opts.pivotY}">
430
+ <label for="editPendulumBobRadius">Radius (m):</label><input type="number" id="editPendulumBobRadius" value="${opts.bobRadius}" min="5">
431
+ <label for="editPendulumLength">Length (m):</label><input type="number" id="editPendulumLength" value="${opts.length}" min="50">
432
+ <label for="editPendulumStiffness">Stiffness:</label><input type="number" id="editPendulumStiffness" value="${opts.stiffness}" step="0.1" min="0" max="1">`;
 
 
433
  }
434
+ html += `<div style="margin-top:15px;">
 
435
  <button id="updateElement">Update</button>
436
  <button id="deleteElement">Delete</button>
437
+ <button id="connectElement">Connect</button>
438
  </div>`;
439
  editPanel.innerHTML = html;
440
  editPanel.style.display = "block";
441
+ connectionPanel.style.display = "none";
442
  document.getElementById("updateElement").addEventListener("click", updateElementFromEditPanel);
443
+ document.getElementById("deleteElement").addEventListener("click", () => { World.remove(world, selectedBody); hideEditPanel(); });
444
+ document.getElementById("connectElement").addEventListener("click", () => { attachMode = true; attachFrom = body; editPanel.style.display = "none"; connectionPanel.style.display = "block"; });
 
 
 
 
 
 
445
  }
446
+
447
+ function hideEditPanel() { editPanel.style.display = "none"; connectionPanel.style.display = "none"; rightSidebar.classList.add('hidden'); selectedBody = null; }
448
+ function hideConnectionPanel() { connectionPanel.style.display = "none"; attachMode = false; attachFrom = null; if (!selectedBody) rightSidebar.classList.add('hidden'); }
449
+ function hideConstraintEditPanel() { constraintEditPanel.style.display = "none"; selectedConstraint = null; }
450
+
451
+ function updateElementFromEditPanel() {
452
+ if (!selectedBody) return;
453
  const newX = parseFloat(document.getElementById("editPosX").value);
454
  const newY = parseFloat(document.getElementById("editPosY").value);
455
  const newAngle = parseFloat(document.getElementById("editAngle").value);
 
457
  Body.setPosition(selectedBody, { x: newX, y: newY });
458
  Body.setAngle(selectedBody, newAngle);
459
  selectedBody.render.fillStyle = newColor;
460
+ if (selectedBody.elementType === "bouncingBall") {
461
+ const newRadius = parseFloat(document.getElementById("editBallRadius").value);
462
+ const newRestitution = parseFloat(document.getElementById("editBallRestitution").value);
463
+ const newFriction = parseFloat(document.getElementById("editBallFriction").value);
464
+ Body.scale(selectedBody, newRadius / selectedBody.circleRadius, newRadius / selectedBody.circleRadius);
465
+ selectedBody.restitution = newRestitution;
466
+ selectedBody.friction = newFriction;
467
+ selectedBody.customOptions = { ...selectedBody.customOptions, x: newX, y: newY, radius: newRadius, restitution: newRestitution, friction: newFriction, color: newColor };
 
 
 
 
 
 
 
468
  }
469
  hideEditPanel();
470
  }
471
 
472
+ render.canvas.addEventListener("dblclick", (event) => {
 
473
  const rect = render.canvas.getBoundingClientRect();
474
  const mousePos = { x: event.clientX - rect.left, y: event.clientY - rect.top };
475
  const bodies = Matter.Composite.allBodies(world);
476
  const clicked = Matter.Query.point(bodies, mousePos);
477
+ if (clicked.length > 0 && !clicked[0].isStatic) openEditPanel(clicked[0]);
478
+ else hideEditPanel();
 
 
 
479
  });
480
 
481
+ document.getElementById('cancelConn').addEventListener('click', hideConnectionPanel);
482
+
483
+ Events.on(mouseConstraint, 'mouseup', (event) => {
484
  const mousePos = event.mouse.position;
485
  const bodies = Matter.Composite.allBodies(world);
486
  const clickedBodies = Matter.Query.point(bodies, mousePos);
487
+ if (attachMode && clickedBodies.length > 0 && attachFrom && clickedBodies[0] !== attachFrom) {
488
+ const connType = document.getElementById('connType').value;
489
+ const sourceEndpoint = attachFrom.elementType === "springMass" ? document.getElementById('sourceEndpoint').value : "mass";
490
+ const targetBody = clickedBodies[0];
491
+ let pointA = sourceEndpoint === "fixed" ? attachFrom.fixedPoint : attachFrom.position;
492
+ let pointB = targetBody.elementType === "springMass" && confirm("Connect to fixed point?") ? targetBody.fixedPoint : targetBody.position;
493
+ const length = Math.hypot(pointB.x - pointA.x, pointB.y - pointA.y);
494
+ const stiffness = connType === "spring" ? 0.05 : (connType === "stick" ? 1 : 0.9);
495
+ const constraint = Constraint.create({
496
+ bodyA: sourceEndpoint === "mass" ? attachFrom : null,
497
+ pointA: sourceEndpoint === "fixed" ? pointA : { x: 0, y: 0 },
498
+ bodyB: targetBody.elementType === "springMass" && pointB === targetBody.fixedPoint ? null : targetBody,
499
+ pointB: targetBody.elementType === "springMass" && pointB === targetBody.fixedPoint ? pointB : { x: 0, y: 0 },
500
+ length,
501
+ stiffness,
502
+ render: { strokeStyle: '#333', lineWidth: 2 }
503
+ });
504
+ World.add(world, constraint);
505
+ hideConnectionPanel();
506
+ } else if (!clickedBodies.length) {
507
+ const constraints = Matter.Composite.allConstraints(world);
508
+ for (let cons of constraints) {
509
+ let posA = cons.bodyA ? Vector.add(cons.bodyA.position, cons.pointA) : cons.pointA;
510
+ let posB = cons.bodyB ? Vector.add(cons.bodyB.position, cons.pointB) : cons.pointB;
511
+ if (distanceToSegment(mousePos, posA, posB) < 5) {
512
+ showConstraintEditPanel(cons);
513
+ return;
514
+ }
515
+ }
516
+ hideConstraintEditPanel();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  }
518
  });
519
+
520
+ function distanceToSegment(p, v, w) {
521
+ const l2 = Vector.magnitudeSquared(Vector.sub(w, v));
522
+ if (l2 === 0) return Math.hypot(p.x - v.x, p.y - v.y);
523
+ let t = Math.max(0, Math.min(1, ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2));
524
+ const proj = Vector.add(v, Vector.mult(Vector.sub(w, v), t));
 
525
  return Math.hypot(p.x - proj.x, p.y - proj.y);
526
  }
527
 
528
+ function showConstraintEditPanel(cons) {
 
 
529
  selectedConstraint = cons;
530
+ let posA = cons.bodyA ? Vector.add(cons.bodyA.position, cons.pointA) : cons.pointA;
531
+ let posB = cons.bodyB ? Vector.add(cons.bodyB.position, cons.pointB) : cons.pointB;
532
+ document.getElementById('consA_X').value = posA.x.toFixed(1);
533
+ document.getElementById('consA_Y').value = posA.y.toFixed(1);
534
+ document.getElementById('consB_X').value = posB.x.toFixed(1);
535
+ document.getElementById('consB_Y').value = posB.y.toFixed(1);
536
+ document.getElementById('consType').value = cons.stiffness < 0.1 ? "spring" : (cons.stiffness > 0.95 ? "stick" : "string");
 
537
  constraintEditPanel.style.display = "block";
538
  }
539
+
540
+ document.getElementById('updateConstraint').addEventListener('click', () => {
541
+ if (selectedConstraint) {
542
+ const aX = parseFloat(document.getElementById('consA_X').value);
543
+ const aY = parseFloat(document.getElementById('consA_Y').value);
544
+ const bX = parseFloat(document.getElementById('consB_X').value);
545
+ const bY = parseFloat(document.getElementById('consB_Y').value);
546
+ const connType = document.getElementById('consType').value;
547
+ if (!selectedConstraint.bodyA) selectedConstraint.pointA = { x: aX, y: aY };
548
+ if (!selectedConstraint.bodyB) selectedConstraint.pointB = { x: bX, y: bY };
549
+ selectedConstraint.stiffness = connType === "spring" ? 0.05 : (connType === "stick" ? 1 : 0.9);
550
+ selectedConstraint.length = Math.hypot(bX - aX, bY - aY);
 
 
551
  hideConstraintEditPanel();
552
  }
553
  });
554
+
555
+ document.getElementById('deleteConstraint').addEventListener('click', () => {
556
+ if (selectedConstraint) {
557
  World.remove(world, selectedConstraint);
558
  hideConstraintEditPanel();
559
  }
560
  });
561
 
562
+ Events.on(engine, 'afterUpdate', () => {
563
+ if (selectedBody && !selectedBody.isStatic) {
564
+ const v = selectedBody.velocity;
565
+ const ke = 0.5 * selectedBody.mass * (v.x * v.x + v.y * v.y);
566
+ const pe = selectedBody.elementType === "springMass" ?
567
+ 0.5 * selectedBody.constraints[0].stiffness * Math.pow(Vector.magnitude(Vector.sub(selectedBody.position, selectedBody.fixedPoint)) - selectedBody.customOptions.length, 2) :
568
+ selectedBody.mass * engine.gravity.y * (window.innerHeight - selectedBody.position.y);
569
+ dataContent.innerHTML = `
570
+ Position: (${selectedBody.position.x.toFixed(1)}, ${selectedBody.position.y.toFixed(1)}) m<br>
571
+ Velocity: (${v.x.toFixed(1)}, ${v.y.toFixed(1)}) m/s<br>
572
+ Kinetic Energy: ${ke.toFixed(2)} J<br>
573
+ Potential Energy: ${pe.toFixed(2)} J<br>
574
+ Total Energy: ${(ke + pe).toFixed(2)} J
575
+ `;
576
+ }
577
+ });
578
+
579
+ window.addEventListener('resize', () => {
580
+ render.canvas.width = window.innerWidth;
581
+ render.canvas.height = window.innerHeight;
582
  Render.lookAt(render, { min: { x: 0, y: 0 }, max: { x: window.innerWidth, y: window.innerHeight } });
583
  updateBoundaries();
584
  });
585
  </script>
586
  </body>
587
+ </html>