implementing matter.js, added obstacles and tank, need to convert to rigidbody driven rather than just moving pixels for movement

master
Jake 2025-03-27 18:58:27 +08:00
parent 0674e5ca2f
commit c7a0c9c834
5 changed files with 245 additions and 41 deletions

View File

@ -9,6 +9,8 @@ const gameWorld = new GameWorld();
let pyodideWorker = startPyodideWorker();
let robots = createInitialRobots();
console.log(robots);
gameWorld.addRobot(robots["player"]);
let paused = false;
let scale = 1; // Zoom level
@ -113,11 +115,12 @@ function gameLoop() {
ctx.translate(offsetX, offsetY); // Apply panning
ctx.scale(scale, scale); // Apply zooming
gameWorld.update();
gameWorld.draw(ctx);
for (let id in robots) {
robots[id].update(ctx, gameWorld);
gameWorld.checkAndResolveCollision(robots[id]);
}
}
requestAnimationFrame(gameLoop);

View File

@ -1,33 +1,67 @@
import Matter from "https://cdn.jsdelivr.net/npm/matter-js@0.19.0/+esm";
export class GameWorld {
constructor() {
let polygon1 = [
{ x: 80, y: 100 }, // Vertex 1
{ x: 200, y: 100 }, // Vertex 2
{ x: 200, y: 200 }, // Vertex 3
{ x: 100, y: 200 } // Vertex 4
];
this.engine = Matter.Engine.create();
this.world = this.engine.world;
this.engine.world.gravity.y = 0.01;
Matter.Runner.run(this.engine);
let polygon2 = [
{ x: 300, y: 400 }, // Vertex 1
{ x: 320, y: 450 }, // Vertex 2
{ x: 250, y: 550 }, // Vertex 3
let ground = Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true });
Matter.World.add(this.world, ground);
this.obstacles = [];
this.robots = [];
this.addObstacle([
{ x: 80 + 100, y: 0 }, // Vertex 1
{ x: 250 + 100, y: 370 }, // Vertex 2
{ x: 200 + 100, y: 370 }, // Vertex 3
{ x: 150 + 100, y: 200 } // Vertex 4
]);
this.addObstacle([
{ x: 300, y: 380 }, // Vertex 1
{ x: 420, y: 380 }, // Vertex 2
{ x: 350, y: 550 }, // Vertex 3
{ x: 280, y: 420 } // Vertex 4
];
]);
this.obstacles = [
polygon1, // Example obstacle
polygon2 // Another obstacle
];
}
// Check if a point (x, y) intersects with any obstacles
isObstacle(x, y) {
// return this.obstacles.some(
// obj => x > obj.x && x < obj.x + obj.width && y > obj.y && y < obj.y + obj.height
// );
return true;
update() {
// Update the physics simulation
Matter.Engine.update(this.engine);
}
addObstacle(vertices) {
// Convert the polygon points into a Matter.js body
let body = Matter.Bodies.fromVertices(-30, 300, [vertices], {
isStatic: true, // Obstacles shouldn't move
});
// Add body to world and store it
Matter.World.add(this.world, body);
this.obstacles.push(body);
}
addRobot(robot) {
console.log("added robot");
// Create the robot's Matter.js body
let robotBody = Matter.Bodies.fromVertices(robot.x, robot.y, [robot.hull], {
friction: 0.05,
restitution: 0.2, // Slight bounce
});
// Add to the world
Matter.World.add(this.world, robotBody);
this.robots.push(robotBody);
}
// Return the floor color based on (x, y) coordinates
@ -41,19 +75,28 @@ export class GameWorld {
ctx.strokeStyle = "gray"; // Obstacle outline color
ctx.lineWidth = 2; // Optional: to make the outline thicker
this.obstacles.forEach(obstacle => {
// Draw obstacles
this.obstacles.forEach(body => {
ctx.beginPath();
ctx.moveTo(obstacle[0].x, obstacle[0].y); // Start at the first vertex
// Loop through the rest of the vertices and draw lines
for (let i = 1; i < obstacle.length; i++) {
ctx.lineTo(obstacle[i].x, obstacle[i].y);
let vertices = body.vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let i = 1; i < vertices.length; i++) {
ctx.lineTo(vertices[i].x, vertices[i].y);
}
// Close the path to form a closed polygon
ctx.closePath();
ctx.stroke();
});
ctx.stroke(); // Draw the outline
this.robots.forEach(body => {
ctx.strokeStyle = "blue";
let vertices = body.vertices;
ctx.beginPath();
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let i = 1; i < vertices.length; i++) {
ctx.lineTo(vertices[i].x, vertices[i].y);
}
ctx.closePath();
ctx.stroke();
});
}
@ -110,4 +153,112 @@ export class GameWorld {
return null; // No intersection with any edge of the polygon
}
lineSegmentIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
let den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (den === 0) return null; // Parallel lines
let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;
if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
return { x: x1 + t * (x2 - x1), y: y1 + t * (y2 - y1) };
}
return null;
}
polygonsIntersect(poly1, poly2) {
for (let i = 0; i < poly1.length; i++) {
let p1 = poly1[i];
let p2 = poly1[(i + 1) % poly1.length]; // Next point, wrap around
// Loop through each edge of the second polygon
for (let j = 0; j < poly2.length; j++) {
let p3 = poly2[j];
let p4 = poly2[(j + 1) % poly2.length]; // Next point, wrap around
let intersection = this.lineIntersection(p1, p2, p3, p4);
if (intersection) {
// Calculate the normal of the intersecting edge
let normal = this.calculateNormal(p1, p2);
return { intersection, normal };
}
}
}
return null; // No intersection
}
calculateNormal(p1, p2) {
// Vector of the edge (from p1 to p2)
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
// The normal is the perpendicular vector to the edge
// We can rotate the vector 90 degrees counterclockwise (for a counterclockwise normal)
let normal = { x: -dy, y: dx };
// Normalize the normal vector
let length = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= length;
normal.y /= length;
return normal;
}
polygonLineIntersection(polygon, p1, p2) {
for (let i = 0; i < polygon.length; i++) {
let p3 = polygon[i];
let p4 = polygon[(i + 1) % polygon.length]; // Next point, wrap around
let intersection = this.lineIntersection(p1, p2, p3, p4);
if (intersection) {
return intersection;
}
}
return null;
}
checkAndResolveCollision(robot) {
for (let obstacle of this.obstacles) {
let result = this.polygonsIntersect(robot.get_hull(), obstacle);
if (result) {
let { intersection, normal } = result;
//console.log("Intersection point:", intersection);
//console.log("Normal vector:", normal);
// Resolve the collision by sliding the robot along the normal
this.resolveSlide(robot, normal);
return true;
}
}
return false;
}
resolveSlide(robot, normal) {
const radians = (robot.angle * Math.PI) / 180;
let vx = Math.cos(radians) * robot.velocity;
let vy = Math.sin(radians) * robot.velocity;
let dot = vx * normal.x + vy * normal.y;
// Subtract the normal component from velocity to make it slide
vx -= 2 * dot * normal.x; // Reflect velocity along the normal (away from the surface)
vy -= 2 * dot * normal.y; // Reflect velocity along the normal (away from the surface)
// Apply the adjusted movement
robot.x = robot.prevX;
robot.y = robot.prevY;
robot.x += vx;
robot.y += vy;
//robot.updateHull();
}
}

View File

@ -6,6 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pyodide Real-Time Python Execution with Console</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<style>
#console {
width: 100%;

View File

@ -13,9 +13,19 @@ export class Robot {
this.color = color;
this.sensors = [];
this.hull = [
{ x: -this.width / 2, y: -this.height / 2 }, // Vertex 1
{ x: this.width / 2, y: -this.height / 2 }, // Vertex 2
{ x: this.width, y: 0 }, // Vertex 2
{ x: this.width / 2, y: this.height / 2 }, // Vertex 3
{ x: -this.width / 2, y: this.height / 2 } // Vertex 4
];
this.addSensor(new RaycastSensor(this, -40, 13, -45, 60));
this.addSensor(new RaycastSensor(this, 40, 13, 45, 60));
this.addSensor(new FloorColorSensor(this, 0, 0));
}
update(ctx, gameWorld) {
@ -36,8 +46,12 @@ export class Robot {
update_position() {
const radians = (this.angle * Math.PI) / 180;
this.prevX = this.x;
this.prevY = this.y;
this.x += Math.cos(radians) * this.velocity;
this.y += Math.sin(radians) * this.velocity;
}
move(velocity) {
@ -49,6 +63,7 @@ export class Robot {
}
draw(ctx) {
return ;
ctx.fillStyle = this.color;
ctx.save();
ctx.translate(this.x, this.y);
@ -58,6 +73,8 @@ export class Robot {
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
// Draw the triangle (direction indicator)
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(this.width / 2, -this.height / 2); // Tip of the triangle (front)
ctx.lineTo(this.width, 0); // Bottom left of triangle
@ -66,12 +83,42 @@ export class Robot {
ctx.fill(); // Fill both the rectangle and the triangle
ctx.beginPath();
ctx.moveTo(this.hull[0].x, this.hull[0].y); // Start at the first vertex
// Loop through the rest of the vertices and draw lines
for (let i = 1; i < this.hull.length; i++) {
ctx.lineTo(this.hull[i].x, this.hull[i].y);
}
// Close the path to form a closed polygon
ctx.closePath();
ctx.stroke(); // Draw the outline
ctx.restore();
this.sensors.forEach(sensor => sensor.draw(ctx));
}
// Takes the polygon hull and updates it to local position and rotation.
get_hull() {
let angle = this.angle * (Math.PI / 180); // Convert degrees to radians
//console.log(angle);
this.transformedHull = this.hull.map(point => {
let xRotated = point.x * Math.cos(angle) - point.y * Math.sin(angle);
let yRotated = point.x * Math.sin(angle) + point.y * Math.cos(angle);
return {
x: this.x + xRotated, // Translate to robot's position
y: this.y + yRotated
};
});
//console.log(this.transformedHull );
return this.transformedHull;
}
draw_sensors(ctx) {
ctx.fillStyle = "yellow";

View File

@ -4,6 +4,7 @@ Add robot sensor data connections to detect and change parameters
Add pan and zoom to canvas
IN PROGRESS
Collision between robots and objects
DONE
Add line sensor object collisions