Spaces:
Running
Running
Update index.html
Browse files- index.html +147 -496
index.html
CHANGED
@@ -2,12 +2,12 @@
|
|
2 |
<html lang="hi">
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
-
<title>Responsive Physics Simulator with
|
6 |
<style>
|
7 |
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
|
8 |
/* Full-page canvas */
|
9 |
canvas { display: block; }
|
10 |
-
/* Controls Panel
|
11 |
#controls {
|
12 |
position: absolute;
|
13 |
top: 10px;
|
@@ -24,8 +24,7 @@
|
|
24 |
#controls input, #controls select, #controls button {
|
25 |
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
|
26 |
}
|
27 |
-
|
28 |
-
/* Edit Panel – Always on top */
|
29 |
#editPanel {
|
30 |
position: absolute;
|
31 |
top: 10px;
|
@@ -44,7 +43,7 @@
|
|
44 |
#editPanel input, #editPanel select, #editPanel button {
|
45 |
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
|
46 |
}
|
47 |
-
/* Connection
|
48 |
#connectionPanel {
|
49 |
position: absolute;
|
50 |
top: 150px;
|
@@ -61,92 +60,57 @@
|
|
61 |
#connectionPanel input, #connectionPanel select, #connectionPanel button {
|
62 |
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
|
63 |
}
|
64 |
-
/* Constraint Edit Panel */
|
65 |
-
#constraintEditPanel {
|
66 |
-
position: absolute;
|
67 |
-
bottom: 10px;
|
68 |
-
right: 10px;
|
69 |
-
z-index: 1000;
|
70 |
-
background: rgba(255,255,255,0.95);
|
71 |
-
padding: 10px;
|
72 |
-
border-radius: 5px;
|
73 |
-
max-width: 250px;
|
74 |
-
display: none;
|
75 |
-
}
|
76 |
-
#constraintEditPanel h3 { margin: 0 0 5px 0; }
|
77 |
-
#constraintEditPanel label { display: block; margin-top: 8px; }
|
78 |
-
#constraintEditPanel input, #constraintEditPanel select, #constraintEditPanel button {
|
79 |
-
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
|
80 |
-
}
|
81 |
</style>
|
82 |
</head>
|
83 |
<body>
|
84 |
-
<!-- Controls Panel
|
85 |
<div id="controls">
|
86 |
-
<label for="
|
87 |
-
<select id="
|
88 |
-
<option value="
|
89 |
-
<option value="
|
90 |
-
<option value="projectile">Projectile Motion</option>
|
91 |
-
<option value="inclinedPlane">Inclined Plane</option>
|
92 |
-
<option value="springMass">Spring–Mass System</option>
|
93 |
</select>
|
94 |
-
|
95 |
-
<
|
96 |
-
<
|
97 |
-
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
</div>
|
|
|
|
|
|
|
|
|
100 |
</div>
|
101 |
|
102 |
-
<!-- Edit Panel (
|
103 |
<div id="editPanel"></div>
|
104 |
|
105 |
-
<!-- Connection
|
106 |
<div id="connectionPanel">
|
107 |
-
<h3>
|
108 |
<label for="connType">Connection Type:</label>
|
109 |
<select id="connType">
|
110 |
-
<option value="string">String</option>
|
111 |
<option value="spring">Spring</option>
|
112 |
-
<option value="stick">Stick</option>
|
113 |
-
</select>
|
114 |
-
<div id="sourceEndpointDiv">
|
115 |
-
<label for="sourceEndpoint">Source Endpoint:</label>
|
116 |
-
<select id="sourceEndpoint">
|
117 |
-
<option value="mass">Mass</option>
|
118 |
-
<option value="fixed">Fixed</option>
|
119 |
-
</select>
|
120 |
-
</div>
|
121 |
-
<button id="cancelConn">Cancel</button>
|
122 |
-
<p style="font-size: 12px; color: #555;">Ab target element par click karein jisse attach karna hai.</p>
|
123 |
-
</div>
|
124 |
-
|
125 |
-
<!-- Constraint Edit Panel -->
|
126 |
-
<div id="constraintEditPanel">
|
127 |
-
<h3>Edit Constraint</h3>
|
128 |
-
<label for="consA_X">Endpoint A X:</label>
|
129 |
-
<input type="number" id="consA_X" step="1">
|
130 |
-
<label for="consA_Y">Endpoint A Y:</label>
|
131 |
-
<input type="number" id="consA_Y" step="1">
|
132 |
-
<label for="consB_X">Endpoint B X:</label>
|
133 |
-
<input type="number" id="consB_X" step="1">
|
134 |
-
<label for="consB_Y">Endpoint B Y:</label>
|
135 |
-
<input type="number" id="consB_Y" step="1">
|
136 |
-
<label for="consType">Connection Type:</label>
|
137 |
-
<select id="consType">
|
138 |
<option value="string">String</option>
|
139 |
-
<option value="spring">Spring</option>
|
140 |
-
<option value="stick">Stick</option>
|
141 |
</select>
|
142 |
-
<button id="
|
143 |
-
<
|
144 |
</div>
|
145 |
|
146 |
-
<!-- Matter.js Library
|
147 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
|
148 |
<script>
|
149 |
-
// Module aliases
|
150 |
const Engine = Matter.Engine,
|
151 |
Render = Matter.Render,
|
152 |
Runner = Matter.Runner,
|
@@ -158,6 +122,7 @@
|
|
158 |
MouseConstraint = Matter.MouseConstraint,
|
159 |
Events = Matter.Events;
|
160 |
|
|
|
161 |
const engine = Engine.create();
|
162 |
const world = engine.world;
|
163 |
const render = Render.create({
|
@@ -174,16 +139,13 @@
|
|
174 |
const runner = Runner.create();
|
175 |
Runner.run(runner, engine);
|
176 |
|
177 |
-
// Global
|
178 |
let floor, wallLeft, wallRight;
|
179 |
-
|
180 |
-
// Function to create/update boundaries based on current window dimensions
|
181 |
function updateBoundaries() {
|
182 |
-
|
183 |
-
if(floor && wallLeft && wallRight) {
|
184 |
World.remove(world, [floor, wallLeft, wallRight]);
|
185 |
}
|
186 |
-
floor = Bodies.rectangle(window.innerWidth/2, window.innerHeight
|
187 |
isStatic: true,
|
188 |
render: { fillStyle: '#060a19' }
|
189 |
});
|
@@ -191,7 +153,6 @@
|
|
191 |
wallRight = Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, { isStatic: true });
|
192 |
World.add(world, [floor, wallLeft, wallRight]);
|
193 |
}
|
194 |
-
// Initial boundaries creation
|
195 |
updateBoundaries();
|
196 |
|
197 |
// Mouse control for drag & drop
|
@@ -203,331 +164,107 @@
|
|
203 |
World.add(world, mouseConstraint);
|
204 |
render.mouse = mouse;
|
205 |
|
206 |
-
// Global
|
207 |
let selectedBody = null;
|
208 |
-
let selectedConstraint = null;
|
209 |
let attachMode = false;
|
210 |
let attachFrom = null;
|
211 |
-
const
|
212 |
-
const
|
213 |
|
214 |
-
// Helper
|
215 |
function getRandomColor() {
|
216 |
return '#' + Math.floor(Math.random()*16777215).toString(16);
|
217 |
}
|
218 |
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
<input type="number" id="ballInitialX" value="100">
|
229 |
-
<label for="ballInitialY">Initial Y:</label>
|
230 |
-
<input type="number" id="ballInitialY" value="50">
|
231 |
-
<label for="ballRadius">Radius:</label>
|
232 |
-
<input type="number" id="ballRadius" value="30">
|
233 |
-
<label for="ballRestitution">Restitution:</label>
|
234 |
-
<input type="number" id="ballRestitution" value="0.9" step="0.1" min="0" max="1">
|
235 |
-
<label for="ballFriction">Friction:</label>
|
236 |
-
<input type="number" id="ballFriction" value="0.005" step="0.001" min="0" max="1">
|
237 |
-
<label for="ballColor">Color:</label>
|
238 |
-
<input type="color" id="ballColor" value="#3498db">
|
239 |
-
`;
|
240 |
-
} else if(selected === 'pendulum'){
|
241 |
-
html += `
|
242 |
-
<label for="pendulumPivotX">Pivot X:</label>
|
243 |
-
<input type="number" id="pendulumPivotX" value="${window.innerWidth-200}">
|
244 |
-
<label for="pendulumPivotY">Pivot Y:</label>
|
245 |
-
<input type="number" id="pendulumPivotY" value="50">
|
246 |
-
<label for="pendulumBobRadius">Bob Radius:</label>
|
247 |
-
<input type="number" id="pendulumBobRadius" value="40">
|
248 |
-
<label for="pendulumLength">Length:</label>
|
249 |
-
<input type="number" id="pendulumLength" value="250">
|
250 |
-
<label for="pendulumStiffness">Stiffness:</label>
|
251 |
-
<input type="number" id="pendulumStiffness" value="1" step="0.1" min="0" max="1">
|
252 |
-
<label for="pendulumBobColor">Color:</label>
|
253 |
-
<input type="color" id="pendulumBobColor" value="#ff0000">
|
254 |
-
`;
|
255 |
-
} else if(selected === 'projectile'){
|
256 |
-
html += `
|
257 |
-
<label for="projectileInitialX">Initial X:</label>
|
258 |
-
<input type="number" id="projectileInitialX" value="100">
|
259 |
-
<label for="projectileInitialY">Initial Y:</label>
|
260 |
-
<input type="number" id="projectileInitialY" value="300">
|
261 |
-
<label for="projectileRadius">Radius:</label>
|
262 |
-
<input type="number" id="projectileRadius" value="20">
|
263 |
-
<label for="projectileVelX">Velocity X:</label>
|
264 |
-
<input type="number" id="projectileVelX" value="15">
|
265 |
-
<label for="projectileVelY">Velocity Y:</label>
|
266 |
-
<input type="number" id="projectileVelY" value="-15">
|
267 |
-
<label for="projectileColor">Color:</label>
|
268 |
-
<input type="color" id="projectileColor" value="#e67e22">
|
269 |
-
`;
|
270 |
-
} else if(selected === 'inclinedPlane'){
|
271 |
-
html += `
|
272 |
-
<label for="rampWidth">Ramp Width:</label>
|
273 |
-
<input type="number" id="rampWidth" value="300">
|
274 |
-
<label for="rampHeight">Ramp Height:</label>
|
275 |
-
<input type="number" id="rampHeight" value="20">
|
276 |
-
<label for="rampAngle">Ramp Angle (°):</label>
|
277 |
-
<input type="number" id="rampAngle" value="30">
|
278 |
-
<label for="rampX">Ramp X:</label>
|
279 |
-
<input type="number" id="rampX" value="400">
|
280 |
-
<label for="rampY">Ramp Y:</label>
|
281 |
-
<input type="number" id="rampY" value="${window.innerHeight-150}">
|
282 |
-
<label for="rampColor">Ramp Color:</label>
|
283 |
-
<input type="color" id="rampColor" value="#8e44ad">
|
284 |
-
<hr>
|
285 |
-
<label for="blockWidth">Block Width:</label>
|
286 |
-
<input type="number" id="blockWidth" value="40">
|
287 |
-
<label for="blockHeight">Block Height:</label>
|
288 |
-
<input type="number" id="blockHeight" value="40">
|
289 |
-
<label for="blockFriction">Block Friction:</label>
|
290 |
-
<input type="number" id="blockFriction" value="0.05" step="0.01" min="0" max="1">
|
291 |
-
<label for="blockColor">Block Color:</label>
|
292 |
-
<input type="color" id="blockColor" value="#1abc9c">
|
293 |
-
`;
|
294 |
-
} else if(selected === 'springMass'){
|
295 |
-
html += `
|
296 |
-
<label for="springMassRadius">Mass Radius:</label>
|
297 |
-
<input type="number" id="springMassRadius" value="25">
|
298 |
-
<label for="springMassRestitution">Restitution:</label>
|
299 |
-
<input type="number" id="springMassRestitution" value="0.8" step="0.1" min="0" max="1">
|
300 |
-
<label for="fixedPointX">Fixed X:</label>
|
301 |
-
<input type="number" id="fixedPointX" value="${Math.floor(window.innerWidth/2)}">
|
302 |
-
<label for="fixedPointY">Fixed Y:</label>
|
303 |
-
<input type="number" id="fixedPointY" value="100">
|
304 |
-
<label for="springLength">Spring Length:</label>
|
305 |
-
<input type="number" id="springLength" value="200">
|
306 |
-
<label for="springStiffness">Stiffness:</label>
|
307 |
-
<input type="number" id="springStiffness" value="0.02" step="0.01" min="0" max="1">
|
308 |
-
<label for="springMassColor">Color:</label>
|
309 |
-
<input type="color" id="springMassColor" value="#f39c12">
|
310 |
-
`;
|
311 |
}
|
312 |
-
|
313 |
-
}
|
314 |
-
updateSubOptions();
|
315 |
-
document.getElementById('simulationSelect').addEventListener('change', updateSubOptions);
|
316 |
|
317 |
-
|
318 |
-
function
|
319 |
-
const x = parseFloat(document.getElementById("
|
320 |
-
const y = parseFloat(document.getElementById("
|
321 |
-
const
|
322 |
-
const
|
323 |
-
const
|
324 |
-
const
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
World.add(world, ball);
|
329 |
-
}
|
330 |
-
function addPendulum(){
|
331 |
-
const pivotX = parseFloat(document.getElementById("pendulumPivotX").value);
|
332 |
-
const pivotY = parseFloat(document.getElementById("pendulumPivotY").value);
|
333 |
-
const bobRadius = parseFloat(document.getElementById("pendulumBobRadius").value);
|
334 |
-
const pendulumLength = parseFloat(document.getElementById("pendulumLength").value);
|
335 |
-
const stiffness = parseFloat(document.getElementById("pendulumStiffness").value);
|
336 |
-
const bobColor = document.getElementById("pendulumBobColor").value || "#ff0000";
|
337 |
-
const bob = Bodies.circle(pivotX, pivotY+pendulumLength, bobRadius, { restitution: 1, density: 0.005, render: { fillStyle: bobColor } });
|
338 |
-
bob.elementType = "pendulum";
|
339 |
-
bob.customOptions = { pivotX, pivotY, bobRadius, pendulumLength, stiffness, bobColor };
|
340 |
-
const pendulumConstraint = Constraint.create({
|
341 |
-
pointA: { x: pivotX, y: pivotY },
|
342 |
-
bodyB: bob,
|
343 |
-
length: pendulumLength,
|
344 |
-
stiffness: stiffness,
|
345 |
-
render: { strokeStyle: '#000', lineWidth: 2 }
|
346 |
-
});
|
347 |
-
World.add(world, [bob, pendulumConstraint]);
|
348 |
-
}
|
349 |
-
function addProjectile(){
|
350 |
-
const x = parseFloat(document.getElementById("projectileInitialX").value);
|
351 |
-
const y = parseFloat(document.getElementById("projectileInitialY").value);
|
352 |
-
const radius = parseFloat(document.getElementById("projectileRadius").value);
|
353 |
-
const velX = parseFloat(document.getElementById("projectileVelX").value);
|
354 |
-
const velY = parseFloat(document.getElementById("projectileVelY").value);
|
355 |
-
const color = document.getElementById("projectileColor").value || getRandomColor();
|
356 |
-
const proj = Bodies.circle(x, y, radius, { restitution: 0.8, frictionAir: 0.001, render: { fillStyle: color } });
|
357 |
-
proj.elementType = "projectile";
|
358 |
-
proj.customOptions = { x, y, radius, velX, velY, color };
|
359 |
-
Body.setVelocity(proj, { x: velX, y: velY });
|
360 |
-
World.add(world, proj);
|
361 |
-
}
|
362 |
-
function addInclinedPlane(){
|
363 |
-
// Do bodies: ek ramp (static) aur ek block.
|
364 |
-
const rampWidth = parseFloat(document.getElementById("rampWidth").value);
|
365 |
-
const rampHeight = parseFloat(document.getElementById("rampHeight").value);
|
366 |
-
const angleDeg = parseFloat(document.getElementById("rampAngle").value);
|
367 |
-
const angle = angleDeg * Math.PI/180;
|
368 |
-
const rampX = parseFloat(document.getElementById("rampX").value);
|
369 |
-
const rampY = parseFloat(document.getElementById("rampY").value);
|
370 |
-
const rampColor = document.getElementById("rampColor").value || "#8e44ad";
|
371 |
-
const ramp = Bodies.rectangle(rampX, rampY, rampWidth, rampHeight, {
|
372 |
-
isStatic: true,
|
373 |
-
angle: angle,
|
374 |
-
render: { fillStyle: rampColor }
|
375 |
});
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
const blockFriction = parseFloat(document.getElementById("blockFriction").value);
|
381 |
-
const blockColor = document.getElementById("blockColor").value || "#1abc9c";
|
382 |
-
const block = Bodies.rectangle(rampX - rampWidth/4, rampY - 50, blockWidth, blockHeight, {
|
383 |
-
friction: blockFriction,
|
384 |
-
render: { fillStyle: blockColor }
|
385 |
-
});
|
386 |
-
block.elementType = "inclinedPlane_block";
|
387 |
-
block.customOptions = { blockWidth, blockHeight, blockFriction, blockColor };
|
388 |
-
World.add(world, [ramp, block]);
|
389 |
}
|
390 |
-
function
|
391 |
-
const
|
392 |
-
const
|
393 |
-
const
|
394 |
-
const
|
395 |
-
const
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
restitution: massRestitution,
|
400 |
-
density: 0.004,
|
401 |
-
render: { fillStyle: massColor }
|
402 |
-
});
|
403 |
-
mass.elementType = "springMass";
|
404 |
-
mass.customOptions = { massRadius, massRestitution, fixedPointX, fixedPointY, springLength, springStiffness, massColor };
|
405 |
-
mass.fixedPoint = { x: fixedPointX, y: fixedPointY };
|
406 |
-
const spring = Constraint.create({
|
407 |
-
pointA: { x: fixedPointX, y: fixedPointY },
|
408 |
-
bodyB: mass,
|
409 |
-
length: springLength,
|
410 |
-
stiffness: springStiffness,
|
411 |
-
damping: 0.05,
|
412 |
-
render: { strokeStyle: '#000', lineWidth: 2 }
|
413 |
});
|
414 |
-
|
|
|
|
|
415 |
}
|
416 |
-
document.getElementById(
|
417 |
-
|
418 |
-
|
419 |
-
else if(selected === 'pendulum') addPendulum();
|
420 |
-
else if(selected === 'projectile') addProjectile();
|
421 |
-
else if(selected === 'inclinedPlane') addInclinedPlane();
|
422 |
-
else if(selected === 'springMass') addSpringMass();
|
423 |
});
|
424 |
-
document.getElementById(
|
425 |
World.clear(world);
|
426 |
Engine.clear(engine);
|
427 |
-
// Re-create boundaries on reset
|
428 |
updateBoundaries();
|
429 |
mouse = Mouse.create(render.canvas);
|
430 |
-
mouseConstraint = MouseConstraint.create(engine, {
|
|
|
|
|
|
|
431 |
World.add(world, mouseConstraint);
|
432 |
render.mouse = mouse;
|
433 |
hideEditPanel();
|
434 |
hideConnectionPanel();
|
435 |
-
hideConstraintEditPanel();
|
436 |
});
|
437 |
|
438 |
-
|
439 |
function openEditPanel(body) {
|
440 |
selectedBody = body;
|
441 |
let html = `<h3>Edit Element</h3>
|
442 |
-
<label
|
443 |
<input type="number" id="editPosX" value="${body.position.x.toFixed(2)}">
|
444 |
-
<label
|
445 |
<input type="number" id="editPosY" value="${body.position.y.toFixed(2)}">
|
446 |
-
<label
|
447 |
<input type="number" id="editAngle" value="${body.angle.toFixed(2)}">
|
448 |
-
<label
|
449 |
-
<input type="color" id="editColor" value="${body.render.fillStyle || '#ffffff'}"
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
<input type="number" id="editBallRadius" value="${opts.radius}">
|
454 |
-
<label for="editBallRestitution">Restitution:</label>
|
455 |
-
<input type="number" id="editBallRestitution" value="${opts.restitution}" step="0.1" min="0" max="1">
|
456 |
-
<label for="editBallFriction">Friction:</label>
|
457 |
-
<input type="number" id="editBallFriction" value="${opts.friction}" step="0.001" min="0" max="1">`;
|
458 |
-
} else if(body.elementType === "pendulum"){
|
459 |
-
const opts = body.customOptions;
|
460 |
-
html += `<label for="editPendulumPivotX">Pivot X:</label>
|
461 |
-
<input type="number" id="editPendulumPivotX" value="${opts.pivotX}">
|
462 |
-
<label for="editPendulumPivotY">Pivot Y:</label>
|
463 |
-
<input type="number" id="editPendulumPivotY" value="${opts.pivotY}">
|
464 |
-
<label for="editPendulumBobRadius">Bob Radius:</label>
|
465 |
-
<input type="number" id="editPendulumBobRadius" value="${opts.bobRadius}">
|
466 |
-
<label for="editPendulumLength">Length:</label>
|
467 |
-
<input type="number" id="editPendulumLength" value="${opts.pendulumLength}">
|
468 |
-
<label for="editPendulumStiffness">Stiffness:</label>
|
469 |
-
<input type="number" id="editPendulumStiffness" value="${opts.stiffness}" step="0.1" min="0" max="1">
|
470 |
-
<label for="editPendulumBobColor">Bob Color:</label>
|
471 |
-
<input type="color" id="editPendulumBobColor" value="${opts.bobColor}">`;
|
472 |
-
} else if(body.elementType === "projectile"){
|
473 |
-
const opts = body.customOptions;
|
474 |
-
html += `<label for="editProjectileRadius">Radius:</label>
|
475 |
-
<input type="number" id="editProjectileRadius" value="${opts.radius}">
|
476 |
-
<label for="editProjectileVelX">Velocity X:</label>
|
477 |
-
<input type="number" id="editProjectileVelX" value="${opts.velX}">
|
478 |
-
<label for="editProjectileVelY">Velocity Y:</label>
|
479 |
-
<input type="number" id="editProjectileVelY" value="${opts.velY}">`;
|
480 |
-
} else if(body.elementType === "inclinedPlane_ramp"){
|
481 |
-
const opts = body.customOptions;
|
482 |
-
html += `<label for="editRampWidth">Ramp Width:</label>
|
483 |
-
<input type="number" id="editRampWidth" value="${opts.rampWidth}">
|
484 |
-
<label for="editRampHeight">Ramp Height:</label>
|
485 |
-
<input type="number" id="editRampHeight" value="${opts.rampHeight}">
|
486 |
-
<label for="editRampAngle">Ramp Angle (°):</label>
|
487 |
-
<input type="number" id="editRampAngle" value="${opts.angleDeg}">
|
488 |
-
<label for="editRampX">Ramp X:</label>
|
489 |
-
<input type="number" id="editRampX" value="${opts.rampX}">
|
490 |
-
<label for="editRampY">Ramp Y:</label>
|
491 |
-
<input type="number" id="editRampY" value="${opts.rampY}">
|
492 |
-
<label for="editRampColor">Ramp Color:</label>
|
493 |
-
<input type="color" id="editRampColor" value="${opts.rampColor}">`;
|
494 |
-
} else if(body.elementType === "inclinedPlane_block"){
|
495 |
-
const opts = body.customOptions;
|
496 |
-
html += `<label for="editBlockWidth">Block Width:</label>
|
497 |
-
<input type="number" id="editBlockWidth" value="${opts.blockWidth}">
|
498 |
-
<label for="editBlockHeight">Block Height:</label>
|
499 |
-
<input type="number" id="editBlockHeight" value="${opts.blockHeight}">
|
500 |
-
<label for="editBlockFriction">Block Friction:</label>
|
501 |
-
<input type="number" id="editBlockFriction" value="${opts.blockFriction}" step="0.01" min="0" max="1">
|
502 |
-
<label for="editBlockColor">Block Color:</label>
|
503 |
-
<input type="color" id="editBlockColor" value="${opts.blockColor}">`;
|
504 |
-
} else if(body.elementType === "springMass"){
|
505 |
-
const opts = body.customOptions;
|
506 |
-
html += `<label for="editSpringMassRadius">Mass Radius:</label>
|
507 |
-
<input type="number" id="editSpringMassRadius" value="${opts.massRadius}">
|
508 |
-
<label for="editSpringMassRestitution">Restitution:</label>
|
509 |
-
<input type="number" id="editSpringMassRestitution" value="${opts.massRestitution}" step="0.1" min="0" max="1">
|
510 |
-
<label for="editFixedPointX">Fixed X:</label>
|
511 |
-
<input type="number" id="editFixedPointX" value="${opts.fixedPointX}">
|
512 |
-
<label for="editFixedPointY">Fixed Y:</label>
|
513 |
-
<input type="number" id="editFixedPointY" value="${opts.fixedPointY}">
|
514 |
-
<label for="editSpringLength">Spring Length:</label>
|
515 |
-
<input type="number" id="editSpringLength" value="${opts.springLength}">
|
516 |
-
<label for="editSpringStiffness">Spring Stiffness:</label>
|
517 |
-
<input type="number" id="editSpringStiffness" value="${opts.springStiffness}" step="0.01" min="0" max="1">
|
518 |
-
<label for="editSpringMassColor">Mass Color:</label>
|
519 |
-
<input type="color" id="editSpringMassColor" value="${opts.massColor}">`;
|
520 |
-
}
|
521 |
-
html += `<div style="margin-top:10px;">
|
522 |
-
<button id="updateElement">Update</button>
|
523 |
-
<button id="deleteElement">Delete</button>
|
524 |
-
</div>`;
|
525 |
editPanel.innerHTML = html;
|
526 |
editPanel.style.display = "block";
|
527 |
document.getElementById("updateElement").addEventListener("click", updateElementFromEditPanel);
|
528 |
document.getElementById("deleteElement").addEventListener("click", function(){
|
529 |
-
|
530 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
531 |
});
|
532 |
}
|
533 |
function hideEditPanel() {
|
@@ -543,150 +280,64 @@
|
|
543 |
Body.setPosition(selectedBody, { x: newX, y: newY });
|
544 |
Body.setAngle(selectedBody, newAngle);
|
545 |
selectedBody.render.fillStyle = newColor;
|
546 |
-
if(selectedBody.elementType === "bouncingBall"){
|
547 |
-
const oldRadius = selectedBody.circleRadius;
|
548 |
-
const newRadius = parseFloat(document.getElementById("editBallRadius").value);
|
549 |
-
const newRestitution = parseFloat(document.getElementById("editBallRestitution").value);
|
550 |
-
const newFriction = parseFloat(document.getElementById("editBallFriction").value);
|
551 |
-
if(newRadius && oldRadius && newRadius !== oldRadius){
|
552 |
-
const scaleFactor = newRadius / oldRadius;
|
553 |
-
Body.scale(selectedBody, scaleFactor, scaleFactor);
|
554 |
-
}
|
555 |
-
selectedBody.restitution = newRestitution;
|
556 |
-
selectedBody.friction = newFriction;
|
557 |
-
selectedBody.customOptions = { x: newX, y: newY, radius: newRadius, restitution: newRestitution, friction: newFriction, color: newColor };
|
558 |
-
}
|
559 |
-
// (Update code for other types as needed...)
|
560 |
hideEditPanel();
|
561 |
}
|
562 |
-
|
563 |
-
// Open edit panel on double–click on an element
|
564 |
render.canvas.addEventListener("dblclick", function(event){
|
565 |
const rect = render.canvas.getBoundingClientRect();
|
566 |
const mousePos = { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
567 |
const bodies = Matter.Composite.allBodies(world);
|
568 |
const clicked = Matter.Query.point(bodies, mousePos);
|
569 |
-
if(clicked.length > 0){
|
570 |
-
|
571 |
-
} else {
|
572 |
-
hideEditPanel();
|
573 |
-
}
|
574 |
});
|
575 |
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
attachFrom = null;
|
581 |
-
});
|
582 |
-
Events.on(mouseConstraint, 'mouseup', function(event){
|
583 |
const mousePos = event.mouse.position;
|
584 |
const bodies = Matter.Composite.allBodies(world);
|
585 |
const clickedBodies = Matter.Query.point(bodies, mousePos);
|
586 |
-
if(clickedBodies.length > 0){
|
587 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
-
|
604 |
-
|
605 |
-
|
606 |
-
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
bodyA: bodyA,
|
616 |
-
pointA: pointA,
|
617 |
-
bodyB: bodyB,
|
618 |
-
pointB: pointB,
|
619 |
-
length: length,
|
620 |
-
stiffness: stiffness,
|
621 |
-
render: { strokeStyle: '#000', lineWidth: 2 }
|
622 |
-
});
|
623 |
-
World.add(world, newConstraint);
|
624 |
-
attachMode = false;
|
625 |
-
attachFrom = null;
|
626 |
-
connectionPanel.style.display = "none";
|
627 |
-
alert("Attachment created using " + connType + " connection.");
|
628 |
-
}
|
629 |
-
} else {
|
630 |
-
const constraints = Matter.Composite.allConstraints(world);
|
631 |
-
for(let cons of constraints){
|
632 |
-
let posA = cons.bodyA ? { x: cons.bodyA.position.x + cons.pointA.x, y: cons.bodyA.position.y + cons.pointA.y } : cons.pointA;
|
633 |
-
let posB = cons.bodyB ? { x: cons.bodyB.position.x + cons.pointB.x, y: cons.bodyB.position.y + cons.pointB.y } : cons.pointB;
|
634 |
-
let dist = distanceToSegment(mousePos, posA, posB);
|
635 |
-
if(dist < 5){
|
636 |
-
showConstraintEditPanel(cons);
|
637 |
-
return;
|
638 |
-
}
|
639 |
-
}
|
640 |
-
hideConstraintEditPanel();
|
641 |
-
}
|
642 |
-
});
|
643 |
-
function distanceToSegment(p, v, w){
|
644 |
-
let l2 = (w.x-v.x)**2 + (w.y-v.y)**2;
|
645 |
-
if(l2 === 0) return Math.hypot(p.x-v.x, p.y-v.y);
|
646 |
-
let t = ((p.x-v.x)*(w.x-v.x) + (p.y-v.y)*(w.y-v.y)) / l2;
|
647 |
-
t = Math.max(0, Math.min(1, t));
|
648 |
-
let proj = { x: v.x + t*(w.x-v.x), y: v.y + t*(w.y-v.y) };
|
649 |
-
return Math.hypot(p.x-proj.x, p.y-proj.y);
|
650 |
-
}
|
651 |
-
const constraintEditPanel = document.getElementById('constraintEditPanel');
|
652 |
-
function showConstraintEditPanel(cons){
|
653 |
-
selectedConstraint = cons;
|
654 |
-
let posA = cons.bodyA ? { x: cons.bodyA.position.x + cons.pointA.x, y: cons.bodyA.position.y + cons.pointA.y } : cons.pointA;
|
655 |
-
let posB = cons.bodyB ? { x: cons.bodyB.position.x + cons.pointB.x, y: cons.bodyB.position.y + cons.pointB.y } : cons.pointB;
|
656 |
-
document.getElementById('consA_X').value = posA.x.toFixed(2);
|
657 |
-
document.getElementById('consA_Y').value = posA.y.toFixed(2);
|
658 |
-
document.getElementById('consB_X').value = posB.x.toFixed(2);
|
659 |
-
document.getElementById('consB_Y').value = posB.y.toFixed(2);
|
660 |
-
let connType = (cons.stiffness < 0.1) ? "spring" : "string";
|
661 |
-
document.getElementById('consType').value = connType;
|
662 |
-
constraintEditPanel.style.display = "block";
|
663 |
-
}
|
664 |
-
function hideConstraintEditPanel(){
|
665 |
-
constraintEditPanel.style.display = "none";
|
666 |
-
selectedConstraint = null;
|
667 |
-
}
|
668 |
-
document.getElementById('updateConstraint').addEventListener('click', function(){
|
669 |
-
if(selectedConstraint){
|
670 |
-
let aX = parseFloat(document.getElementById('consA_X').value);
|
671 |
-
let aY = parseFloat(document.getElementById('consA_Y').value);
|
672 |
-
let bX = parseFloat(document.getElementById('consB_X').value);
|
673 |
-
let bY = parseFloat(document.getElementById('consB_Y').value);
|
674 |
-
if(!selectedConstraint.bodyA) { selectedConstraint.pointA = { x: aX, y: aY }; }
|
675 |
-
if(!selectedConstraint.bodyB) { selectedConstraint.pointB = { x: bX, y: bY }; }
|
676 |
-
let connType = document.getElementById('consType').value;
|
677 |
-
selectedConstraint.stiffness = (connType === "spring") ? 0.05 : 1;
|
678 |
-
hideConstraintEditPanel();
|
679 |
}
|
680 |
});
|
681 |
-
document.getElementById(
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
}
|
686 |
});
|
687 |
|
688 |
-
//
|
689 |
-
window.addEventListener(
|
690 |
Render.lookAt(render, { min: { x: 0, y: 0 }, max: { x: window.innerWidth, y: window.innerHeight } });
|
691 |
updateBoundaries();
|
692 |
});
|
|
|
2 |
<html lang="hi">
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
+
<title>Responsive Physics Simulator with Flexible Attachments</title>
|
6 |
<style>
|
7 |
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
|
8 |
/* Full-page canvas */
|
9 |
canvas { display: block; }
|
10 |
+
/* Controls Panel – always on top */
|
11 |
#controls {
|
12 |
position: absolute;
|
13 |
top: 10px;
|
|
|
24 |
#controls input, #controls select, #controls button {
|
25 |
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
|
26 |
}
|
27 |
+
/* Edit Panel – appears on double–click */
|
|
|
28 |
#editPanel {
|
29 |
position: absolute;
|
30 |
top: 10px;
|
|
|
43 |
#editPanel input, #editPanel select, #editPanel button {
|
44 |
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
|
45 |
}
|
46 |
+
/* Connection Panel – for attachments */
|
47 |
#connectionPanel {
|
48 |
position: absolute;
|
49 |
top: 150px;
|
|
|
60 |
#connectionPanel input, #connectionPanel select, #connectionPanel button {
|
61 |
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
|
62 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
</style>
|
64 |
</head>
|
65 |
<body>
|
66 |
+
<!-- Controls Panel -->
|
67 |
<div id="controls">
|
68 |
+
<label for="elementType">Element Type:</label>
|
69 |
+
<select id="elementType">
|
70 |
+
<option value="block">Block (Rectangle)</option>
|
71 |
+
<option value="circle">Circle</option>
|
|
|
|
|
|
|
72 |
</select>
|
73 |
+
<label for="initX">Initial X:</label>
|
74 |
+
<input type="number" id="initX" value="150">
|
75 |
+
<label for="initY">Initial Y:</label>
|
76 |
+
<input type="number" id="initY" value="100">
|
77 |
+
<!-- For block -->
|
78 |
+
<div id="blockOptions">
|
79 |
+
<label for="blockWidth">Width:</label>
|
80 |
+
<input type="number" id="blockWidth" value="80">
|
81 |
+
<label for="blockHeight">Height:</label>
|
82 |
+
<input type="number" id="blockHeight" value="60">
|
83 |
+
</div>
|
84 |
+
<!-- For circle -->
|
85 |
+
<div id="circleOptions" style="display:none;">
|
86 |
+
<label for="circleRadius">Radius:</label>
|
87 |
+
<input type="number" id="circleRadius" value="30">
|
88 |
</div>
|
89 |
+
<label for="initColor">Color:</label>
|
90 |
+
<input type="color" id="initColor" value="#3498db">
|
91 |
+
<button id="addElement">Add Element</button>
|
92 |
+
<button id="reset">Reset Simulation</button>
|
93 |
</div>
|
94 |
|
95 |
+
<!-- Edit Panel (opens on double–click) -->
|
96 |
<div id="editPanel"></div>
|
97 |
|
98 |
+
<!-- Connection Panel (for attachments) -->
|
99 |
<div id="connectionPanel">
|
100 |
+
<h3>Attachment Options</h3>
|
101 |
<label for="connType">Connection Type:</label>
|
102 |
<select id="connType">
|
|
|
103 |
<option value="spring">Spring</option>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
<option value="string">String</option>
|
|
|
|
|
105 |
</select>
|
106 |
+
<button id="cancelConn">Cancel Attachment</button>
|
107 |
+
<p style="font-size:12px; color:#555;">Ab target element par click karein.</p>
|
108 |
</div>
|
109 |
|
110 |
+
<!-- Matter.js Library -->
|
111 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
|
112 |
<script>
|
113 |
+
// Module aliases
|
114 |
const Engine = Matter.Engine,
|
115 |
Render = Matter.Render,
|
116 |
Runner = Matter.Runner,
|
|
|
122 |
MouseConstraint = Matter.MouseConstraint,
|
123 |
Events = Matter.Events;
|
124 |
|
125 |
+
// Create engine and world
|
126 |
const engine = Engine.create();
|
127 |
const world = engine.world;
|
128 |
const render = Render.create({
|
|
|
139 |
const runner = Runner.create();
|
140 |
Runner.run(runner, engine);
|
141 |
|
142 |
+
// Global boundaries (will be updated on resize)
|
143 |
let floor, wallLeft, wallRight;
|
|
|
|
|
144 |
function updateBoundaries() {
|
145 |
+
if(floor && wallLeft && wallRight){
|
|
|
146 |
World.remove(world, [floor, wallLeft, wallRight]);
|
147 |
}
|
148 |
+
floor = Bodies.rectangle(window.innerWidth/2, window.innerHeight-50, window.innerWidth, 100, {
|
149 |
isStatic: true,
|
150 |
render: { fillStyle: '#060a19' }
|
151 |
});
|
|
|
153 |
wallRight = Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, { isStatic: true });
|
154 |
World.add(world, [floor, wallLeft, wallRight]);
|
155 |
}
|
|
|
156 |
updateBoundaries();
|
157 |
|
158 |
// Mouse control for drag & drop
|
|
|
164 |
World.add(world, mouseConstraint);
|
165 |
render.mouse = mouse;
|
166 |
|
167 |
+
// Global variables for editing & attachment
|
168 |
let selectedBody = null;
|
|
|
169 |
let attachMode = false;
|
170 |
let attachFrom = null;
|
171 |
+
const editPanel = document.getElementById("editPanel");
|
172 |
+
const connectionPanel = document.getElementById("connectionPanel");
|
173 |
|
174 |
+
// Helper: random color
|
175 |
function getRandomColor() {
|
176 |
return '#' + Math.floor(Math.random()*16777215).toString(16);
|
177 |
}
|
178 |
|
179 |
+
// Toggle suboptions based on element type
|
180 |
+
const elementTypeSelect = document.getElementById("elementType");
|
181 |
+
elementTypeSelect.addEventListener("change", function(){
|
182 |
+
if(this.value === "block"){
|
183 |
+
document.getElementById("blockOptions").style.display = "block";
|
184 |
+
document.getElementById("circleOptions").style.display = "none";
|
185 |
+
} else {
|
186 |
+
document.getElementById("blockOptions").style.display = "none";
|
187 |
+
document.getElementById("circleOptions").style.display = "block";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
}
|
189 |
+
});
|
|
|
|
|
|
|
190 |
|
191 |
+
// Functions to add elements
|
192 |
+
function addBlock(){
|
193 |
+
const x = parseFloat(document.getElementById("initX").value);
|
194 |
+
const y = parseFloat(document.getElementById("initY").value);
|
195 |
+
const width = parseFloat(document.getElementById("blockWidth").value);
|
196 |
+
const height = parseFloat(document.getElementById("blockHeight").value);
|
197 |
+
const color = document.getElementById("initColor").value || getRandomColor();
|
198 |
+
const block = Bodies.rectangle(x, y, width, height, {
|
199 |
+
restitution: 0.1,
|
200 |
+
friction: 0.5,
|
201 |
+
render: { fillStyle: color }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
});
|
203 |
+
// Store creation options for later editing
|
204 |
+
block.customOptions = { x, y, width, height, color };
|
205 |
+
block.elementType = "block";
|
206 |
+
World.add(world, block);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
}
|
208 |
+
function addCircle(){
|
209 |
+
const x = parseFloat(document.getElementById("initX").value);
|
210 |
+
const y = parseFloat(document.getElementById("initY").value);
|
211 |
+
const radius = parseFloat(document.getElementById("circleRadius").value);
|
212 |
+
const color = document.getElementById("initColor").value || getRandomColor();
|
213 |
+
const circle = Bodies.circle(x, y, radius, {
|
214 |
+
restitution: 0.9,
|
215 |
+
friction: 0.005,
|
216 |
+
render: { fillStyle: color }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
});
|
218 |
+
circle.customOptions = { x, y, radius, color };
|
219 |
+
circle.elementType = "circle";
|
220 |
+
World.add(world, circle);
|
221 |
}
|
222 |
+
document.getElementById("addElement").addEventListener("click", function(){
|
223 |
+
if(elementTypeSelect.value === "block") addBlock();
|
224 |
+
else addCircle();
|
|
|
|
|
|
|
|
|
225 |
});
|
226 |
+
document.getElementById("reset").addEventListener("click", function(){
|
227 |
World.clear(world);
|
228 |
Engine.clear(engine);
|
|
|
229 |
updateBoundaries();
|
230 |
mouse = Mouse.create(render.canvas);
|
231 |
+
mouseConstraint = MouseConstraint.create(engine, {
|
232 |
+
mouse: mouse,
|
233 |
+
constraint: { stiffness: 0.2, render: { visible: false } }
|
234 |
+
});
|
235 |
World.add(world, mouseConstraint);
|
236 |
render.mouse = mouse;
|
237 |
hideEditPanel();
|
238 |
hideConnectionPanel();
|
|
|
239 |
});
|
240 |
|
241 |
+
// Edit Panel functions – open on double–click
|
242 |
function openEditPanel(body) {
|
243 |
selectedBody = body;
|
244 |
let html = `<h3>Edit Element</h3>
|
245 |
+
<label>Position X:</label>
|
246 |
<input type="number" id="editPosX" value="${body.position.x.toFixed(2)}">
|
247 |
+
<label>Position Y:</label>
|
248 |
<input type="number" id="editPosY" value="${body.position.y.toFixed(2)}">
|
249 |
+
<label>Angle (radians):</label>
|
250 |
<input type="number" id="editAngle" value="${body.angle.toFixed(2)}">
|
251 |
+
<label>Color:</label>
|
252 |
+
<input type="color" id="editColor" value="${body.render.fillStyle || '#ffffff'}">
|
253 |
+
<button id="updateElement">Update</button>
|
254 |
+
<button id="deleteElement">Delete</button>
|
255 |
+
<button id="attachElement">Attach</button>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
editPanel.innerHTML = html;
|
257 |
editPanel.style.display = "block";
|
258 |
document.getElementById("updateElement").addEventListener("click", updateElementFromEditPanel);
|
259 |
document.getElementById("deleteElement").addEventListener("click", function(){
|
260 |
+
World.remove(world, selectedBody);
|
261 |
+
hideEditPanel();
|
262 |
+
});
|
263 |
+
document.getElementById("attachElement").addEventListener("click", function(){
|
264 |
+
attachMode = true;
|
265 |
+
attachFrom = selectedBody;
|
266 |
+
hideEditPanel();
|
267 |
+
connectionPanel.style.display = "block";
|
268 |
});
|
269 |
}
|
270 |
function hideEditPanel() {
|
|
|
280 |
Body.setPosition(selectedBody, { x: newX, y: newY });
|
281 |
Body.setAngle(selectedBody, newAngle);
|
282 |
selectedBody.render.fillStyle = newColor;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
283 |
hideEditPanel();
|
284 |
}
|
285 |
+
// Open edit panel on double–click (using canvas dblclick)
|
|
|
286 |
render.canvas.addEventListener("dblclick", function(event){
|
287 |
const rect = render.canvas.getBoundingClientRect();
|
288 |
const mousePos = { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
289 |
const bodies = Matter.Composite.allBodies(world);
|
290 |
const clicked = Matter.Query.point(bodies, mousePos);
|
291 |
+
if(clicked.length > 0) { openEditPanel(clicked[0]); }
|
292 |
+
else { hideEditPanel(); }
|
|
|
|
|
|
|
293 |
});
|
294 |
|
295 |
+
// Attachment (Connection) creation:
|
296 |
+
// When attachMode is true and user clicks on a target body,
|
297 |
+
// create a constraint between attachFrom and that body using selected connection type.
|
298 |
+
Events.on(mouseConstraint, "mouseup", function(event){
|
|
|
|
|
|
|
299 |
const mousePos = event.mouse.position;
|
300 |
const bodies = Matter.Composite.allBodies(world);
|
301 |
const clickedBodies = Matter.Query.point(bodies, mousePos);
|
302 |
+
if(attachMode && attachFrom && clickedBodies.length > 0) {
|
303 |
+
const target = clickedBodies[0];
|
304 |
+
if(target === attachFrom) return;
|
305 |
+
const connType = document.getElementById("connType").value;
|
306 |
+
// Default endpoints: attach from centers
|
307 |
+
const pointA = { x: 0, y: 0 };
|
308 |
+
const pointB = { x: 0, y: 0 };
|
309 |
+
// Compute current distance between centers
|
310 |
+
const dx = target.position.x - attachFrom.position.x;
|
311 |
+
const dy = target.position.y - attachFrom.position.y;
|
312 |
+
const length = Math.sqrt(dx*dx + dy*dy);
|
313 |
+
// Set stiffness based on connection type:
|
314 |
+
let stiffness = (connType === "spring") ? 0.05 : 1;
|
315 |
+
let damping = (connType === "spring") ? 0.05 : 0;
|
316 |
+
const newConstraint = Constraint.create({
|
317 |
+
bodyA: attachFrom,
|
318 |
+
pointA: pointA,
|
319 |
+
bodyB: target,
|
320 |
+
pointB: pointB,
|
321 |
+
length: length,
|
322 |
+
stiffness: stiffness,
|
323 |
+
damping: damping,
|
324 |
+
render: { strokeStyle: '#000', lineWidth: 2 }
|
325 |
+
});
|
326 |
+
World.add(world, newConstraint);
|
327 |
+
attachMode = false;
|
328 |
+
attachFrom = null;
|
329 |
+
connectionPanel.style.display = "none";
|
330 |
+
alert("Attachment created (" + connType + ").");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
331 |
}
|
332 |
});
|
333 |
+
document.getElementById("cancelConn").addEventListener("click", function(){
|
334 |
+
connectionPanel.style.display = "none";
|
335 |
+
attachMode = false;
|
336 |
+
attachFrom = null;
|
|
|
337 |
});
|
338 |
|
339 |
+
// Update boundaries and renderer on window resize
|
340 |
+
window.addEventListener("resize", function(){
|
341 |
Render.lookAt(render, { min: { x: 0, y: 0 }, max: { x: window.innerWidth, y: window.innerHeight } });
|
342 |
updateBoundaries();
|
343 |
});
|