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