phy / index.html
Ramesh-vani's picture
Update index.html
2df038e verified
raw
history blame
13.3 kB
<!DOCTYPE html>
<html lang="hi">
<head>
<meta charset="UTF-8">
<title>Responsive Physics Simulator with Flexible Attachments</title>
<style>
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; }
/* Full-page canvas */
canvas { display: block; }
/* Controls Panel – always on top */
#controls {
position: absolute;
top: 10px;
left: 10px;
z-index: 1000;
background: rgba(255,255,255,0.95);
padding: 10px;
border-radius: 5px;
max-width: 320px;
max-height: 90vh;
overflow-y: auto;
}
#controls label { display: block; margin-top: 8px; }
#controls input, #controls select, #controls button {
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
}
/* Edit Panel – appears on double–click */
#editPanel {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
background: rgba(255,255,255,0.95);
padding: 10px;
border-radius: 5px;
max-width: 300px;
max-height: 90vh;
overflow-y: auto;
display: none;
}
#editPanel h3 { margin: 0 0 5px 0; }
#editPanel label { display: block; margin-top: 8px; }
#editPanel input, #editPanel select, #editPanel button {
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
}
/* Connection Panel – for attachments */
#connectionPanel {
position: absolute;
top: 150px;
right: 10px;
z-index: 1000;
background: rgba(255,255,255,0.95);
padding: 10px;
border-radius: 5px;
max-width: 250px;
display: none;
}
#connectionPanel h3 { margin: 0 0 5px 0; }
#connectionPanel label { display: block; margin-top: 8px; }
#connectionPanel input, #connectionPanel select, #connectionPanel button {
width: 100%; padding: 6px; margin-top: 4px; box-sizing: border-box;
}
</style>
</head>
<body>
<!-- Controls Panel -->
<div id="controls">
<label for="elementType">Element Type:</label>
<select id="elementType">
<option value="block">Block (Rectangle)</option>
<option value="circle">Circle</option>
</select>
<label for="initX">Initial X:</label>
<input type="number" id="initX" value="150">
<label for="initY">Initial Y:</label>
<input type="number" id="initY" value="100">
<!-- For block -->
<div id="blockOptions">
<label for="blockWidth">Width:</label>
<input type="number" id="blockWidth" value="80">
<label for="blockHeight">Height:</label>
<input type="number" id="blockHeight" value="60">
</div>
<!-- For circle -->
<div id="circleOptions" style="display:none;">
<label for="circleRadius">Radius:</label>
<input type="number" id="circleRadius" value="30">
</div>
<label for="initColor">Color:</label>
<input type="color" id="initColor" value="#3498db">
<button id="addElement">Add Element</button>
<button id="reset">Reset Simulation</button>
</div>
<!-- Edit Panel (opens on double–click) -->
<div id="editPanel"></div>
<!-- Connection Panel (for attachments) -->
<div id="connectionPanel">
<h3>Attachment Options</h3>
<label for="connType">Connection Type:</label>
<select id="connType">
<option value="spring">Spring</option>
<option value="string">String</option>
</select>
<button id="cancelConn">Cancel Attachment</button>
<p style="font-size:12px; color:#555;">Ab target element par click karein.</p>
</div>
<!-- Matter.js Library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
// Module aliases
const Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
World = Matter.World,
Bodies = Matter.Bodies,
Body = Matter.Body,
Constraint = Matter.Constraint,
Mouse = Matter.Mouse,
MouseConstraint = Matter.MouseConstraint,
Events = Matter.Events;
// Create engine and world
const engine = Engine.create();
const world = engine.world;
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: window.innerWidth,
height: window.innerHeight,
wireframes: false,
background: '#f0f0f0'
}
});
Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);
// Global boundaries (will be updated on resize)
let floor, wallLeft, wallRight;
function updateBoundaries() {
if(floor && wallLeft && wallRight){
World.remove(world, [floor, wallLeft, wallRight]);
}
floor = Bodies.rectangle(window.innerWidth/2, window.innerHeight-50, window.innerWidth, 100, {
isStatic: true,
render: { fillStyle: '#060a19' }
});
wallLeft = Bodies.rectangle(-50, window.innerHeight/2, 100, window.innerHeight, { isStatic: true });
wallRight = Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, { isStatic: true });
World.add(world, [floor, wallLeft, wallRight]);
}
updateBoundaries();
// Mouse control for drag & drop
let mouse = Mouse.create(render.canvas);
let mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: { stiffness: 0.2, render: { visible: false } }
});
World.add(world, mouseConstraint);
render.mouse = mouse;
// Global variables for editing & attachment
let selectedBody = null;
let attachMode = false;
let attachFrom = null;
const editPanel = document.getElementById("editPanel");
const connectionPanel = document.getElementById("connectionPanel");
// Helper: random color
function getRandomColor() {
return '#' + Math.floor(Math.random()*16777215).toString(16);
}
// Toggle suboptions based on element type
const elementTypeSelect = document.getElementById("elementType");
elementTypeSelect.addEventListener("change", function(){
if(this.value === "block"){
document.getElementById("blockOptions").style.display = "block";
document.getElementById("circleOptions").style.display = "none";
} else {
document.getElementById("blockOptions").style.display = "none";
document.getElementById("circleOptions").style.display = "block";
}
});
// Functions to add elements
function addBlock(){
const x = parseFloat(document.getElementById("initX").value);
const y = parseFloat(document.getElementById("initY").value);
const width = parseFloat(document.getElementById("blockWidth").value);
const height = parseFloat(document.getElementById("blockHeight").value);
const color = document.getElementById("initColor").value || getRandomColor();
const block = Bodies.rectangle(x, y, width, height, {
restitution: 0.1,
friction: 0.5,
render: { fillStyle: color }
});
// Store creation options for later editing
block.customOptions = { x, y, width, height, color };
block.elementType = "block";
World.add(world, block);
}
function addCircle(){
const x = parseFloat(document.getElementById("initX").value);
const y = parseFloat(document.getElementById("initY").value);
const radius = parseFloat(document.getElementById("circleRadius").value);
const color = document.getElementById("initColor").value || getRandomColor();
const circle = Bodies.circle(x, y, radius, {
restitution: 0.9,
friction: 0.005,
render: { fillStyle: color }
});
circle.customOptions = { x, y, radius, color };
circle.elementType = "circle";
World.add(world, circle);
}
document.getElementById("addElement").addEventListener("click", function(){
if(elementTypeSelect.value === "block") addBlock();
else addCircle();
});
document.getElementById("reset").addEventListener("click", function(){
World.clear(world);
Engine.clear(engine);
updateBoundaries();
mouse = Mouse.create(render.canvas);
mouseConstraint = MouseConstraint.create(engine, {
mouse: mouse,
constraint: { stiffness: 0.2, render: { visible: false } }
});
World.add(world, mouseConstraint);
render.mouse = mouse;
hideEditPanel();
hideConnectionPanel();
});
// Edit Panel functions – open on double–click
function openEditPanel(body) {
selectedBody = body;
let html = `<h3>Edit Element</h3>
<label>Position X:</label>
<input type="number" id="editPosX" value="${body.position.x.toFixed(2)}">
<label>Position Y:</label>
<input type="number" id="editPosY" value="${body.position.y.toFixed(2)}">
<label>Angle (radians):</label>
<input type="number" id="editAngle" value="${body.angle.toFixed(2)}">
<label>Color:</label>
<input type="color" id="editColor" value="${body.render.fillStyle || '#ffffff'}">
<button id="updateElement">Update</button>
<button id="deleteElement">Delete</button>
<button id="attachElement">Attach</button>`;
editPanel.innerHTML = html;
editPanel.style.display = "block";
document.getElementById("updateElement").addEventListener("click", updateElementFromEditPanel);
document.getElementById("deleteElement").addEventListener("click", function(){
World.remove(world, selectedBody);
hideEditPanel();
});
document.getElementById("attachElement").addEventListener("click", function(){
attachMode = true;
attachFrom = selectedBody;
hideEditPanel();
connectionPanel.style.display = "block";
});
}
function hideEditPanel() {
editPanel.style.display = "none";
selectedBody = null;
}
function updateElementFromEditPanel(){
if(!selectedBody) return;
const newX = parseFloat(document.getElementById("editPosX").value);
const newY = parseFloat(document.getElementById("editPosY").value);
const newAngle = parseFloat(document.getElementById("editAngle").value);
const newColor = document.getElementById("editColor").value;
Body.setPosition(selectedBody, { x: newX, y: newY });
Body.setAngle(selectedBody, newAngle);
selectedBody.render.fillStyle = newColor;
hideEditPanel();
}
// Open edit panel on double–click (using canvas dblclick)
render.canvas.addEventListener("dblclick", function(event){
const rect = render.canvas.getBoundingClientRect();
const mousePos = { x: event.clientX - rect.left, y: event.clientY - rect.top };
const bodies = Matter.Composite.allBodies(world);
const clicked = Matter.Query.point(bodies, mousePos);
if(clicked.length > 0) { openEditPanel(clicked[0]); }
else { hideEditPanel(); }
});
// Attachment (Connection) creation:
// When attachMode is true and user clicks on a target body,
// create a constraint between attachFrom and that body using selected connection type.
Events.on(mouseConstraint, "mouseup", function(event){
const mousePos = event.mouse.position;
const bodies = Matter.Composite.allBodies(world);
const clickedBodies = Matter.Query.point(bodies, mousePos);
if(attachMode && attachFrom && clickedBodies.length > 0) {
const target = clickedBodies[0];
if(target === attachFrom) return;
const connType = document.getElementById("connType").value;
// Default endpoints: attach from centers
const pointA = { x: 0, y: 0 };
const pointB = { x: 0, y: 0 };
// Compute current distance between centers
const dx = target.position.x - attachFrom.position.x;
const dy = target.position.y - attachFrom.position.y;
const length = Math.sqrt(dx*dx + dy*dy);
// Set stiffness based on connection type:
let stiffness = (connType === "spring") ? 0.05 : 1;
let damping = (connType === "spring") ? 0.05 : 0;
const newConstraint = Constraint.create({
bodyA: attachFrom,
pointA: pointA,
bodyB: target,
pointB: pointB,
length: length,
stiffness: stiffness,
damping: damping,
render: { strokeStyle: '#000', lineWidth: 2 }
});
World.add(world, newConstraint);
attachMode = false;
attachFrom = null;
connectionPanel.style.display = "none";
alert("Attachment created (" + connType + ").");
}
});
document.getElementById("cancelConn").addEventListener("click", function(){
connectionPanel.style.display = "none";
attachMode = false;
attachFrom = null;
});
// Update boundaries and renderer on window resize
window.addEventListener("resize", function(){
Render.lookAt(render, { min: { x: 0, y: 0 }, max: { x: window.innerWidth, y: window.innerHeight } });
updateBoundaries();
});
</script>
</body>
</html>