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 pyodideWorker = startPyodideWorker();
let robots = createInitialRobots(); let robots = createInitialRobots();
console.log(robots);
gameWorld.addRobot(robots["player"]);
let paused = false; let paused = false;
let scale = 1; // Zoom level let scale = 1; // Zoom level
@ -113,11 +115,12 @@ function gameLoop() {
ctx.translate(offsetX, offsetY); // Apply panning ctx.translate(offsetX, offsetY); // Apply panning
ctx.scale(scale, scale); // Apply zooming ctx.scale(scale, scale); // Apply zooming
gameWorld.update();
gameWorld.draw(ctx); gameWorld.draw(ctx);
for (let id in robots) { for (let id in robots) {
robots[id].update(ctx, gameWorld); robots[id].update(ctx, gameWorld);
gameWorld.checkAndResolveCollision(robots[id]);
} }
} }
requestAnimationFrame(gameLoop); 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 { export class GameWorld {
constructor() { constructor() {
let polygon1 = [ this.engine = Matter.Engine.create();
{ x: 80, y: 100 }, // Vertex 1 this.world = this.engine.world;
{ x: 200, y: 100 }, // Vertex 2 this.engine.world.gravity.y = 0.01;
{ x: 200, y: 200 }, // Vertex 3 Matter.Runner.run(this.engine);
{ x: 100, y: 200 } // Vertex 4
];
let polygon2 = [ let ground = Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true });
{ x: 300, y: 400 }, // Vertex 1 Matter.World.add(this.world, ground);
{ x: 320, y: 450 }, // Vertex 2
{ x: 250, y: 550 }, // Vertex 3
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 { x: 280, y: 420 } // Vertex 4
]; ]);
this.obstacles = [
polygon1, // Example obstacle
polygon2 // Another obstacle
];
} }
// Check if a point (x, y) intersects with any obstacles update() {
isObstacle(x, y) { // Update the physics simulation
// return this.obstacles.some( Matter.Engine.update(this.engine);
// obj => x > obj.x && x < obj.x + obj.width && y > obj.y && y < obj.y + obj.height
// );
return true; }
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 // Return the floor color based on (x, y) coordinates
@ -41,19 +75,28 @@ export class GameWorld {
ctx.strokeStyle = "gray"; // Obstacle outline color ctx.strokeStyle = "gray"; // Obstacle outline color
ctx.lineWidth = 2; // Optional: to make the outline thicker ctx.lineWidth = 2; // Optional: to make the outline thicker
this.obstacles.forEach(obstacle => { // Draw obstacles
this.obstacles.forEach(body => {
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(obstacle[0].x, obstacle[0].y); // Start at the first vertex let vertices = body.vertices;
ctx.moveTo(vertices[0].x, vertices[0].y);
// Loop through the rest of the vertices and draw lines for (let i = 1; i < vertices.length; i++) {
for (let i = 1; i < obstacle.length; i++) { ctx.lineTo(vertices[i].x, vertices[i].y);
ctx.lineTo(obstacle[i].x, obstacle[i].y);
} }
// Close the path to form a closed polygon
ctx.closePath(); 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 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"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pyodide Real-Time Python Execution with Console</title> <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://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> <style>
#console { #console {
width: 100%; width: 100%;

View File

@ -13,9 +13,19 @@ export class Robot {
this.color = color; this.color = color;
this.sensors = []; 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 RaycastSensor(this, 40, 13, 45, 60)); this.addSensor(new RaycastSensor(this, 40, 13, 45, 60));
this.addSensor(new FloorColorSensor(this, 0, 0)); this.addSensor(new FloorColorSensor(this, 0, 0));
} }
update(ctx, gameWorld) { update(ctx, gameWorld) {
@ -36,8 +46,12 @@ export class Robot {
update_position() { update_position() {
const radians = (this.angle * Math.PI) / 180; const radians = (this.angle * Math.PI) / 180;
this.prevX = this.x;
this.prevY = this.y;
this.x += Math.cos(radians) * this.velocity; this.x += Math.cos(radians) * this.velocity;
this.y += Math.sin(radians) * this.velocity; this.y += Math.sin(radians) * this.velocity;
} }
move(velocity) { move(velocity) {
@ -49,6 +63,7 @@ export class Robot {
} }
draw(ctx) { draw(ctx) {
return ;
ctx.fillStyle = this.color; ctx.fillStyle = this.color;
ctx.save(); ctx.save();
ctx.translate(this.x, this.y); 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); ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
// Draw the triangle (direction indicator) // Draw the triangle (direction indicator)
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(this.width / 2, -this.height / 2); // Tip of the triangle (front) ctx.moveTo(this.width / 2, -this.height / 2); // Tip of the triangle (front)
ctx.lineTo(this.width, 0); // Bottom left of triangle 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.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(); ctx.restore();
this.sensors.forEach(sensor => sensor.draw(ctx)); 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) { draw_sensors(ctx) {
ctx.fillStyle = "yellow"; 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 Add pan and zoom to canvas
IN PROGRESS IN PROGRESS
Collision between robots and objects
DONE DONE
Add line sensor object collisions Add line sensor object collisions