335 lines
11 KiB
JavaScript
335 lines
11 KiB
JavaScript
import Matter from "https://cdn.jsdelivr.net/npm/matter-js@0.19.0/+esm";
|
|
|
|
export class GameWorld {
|
|
constructor() {
|
|
|
|
this.engine = Matter.Engine.create();
|
|
this.world = this.engine.world;
|
|
this.engine.world.gravity.y = 0;//0.01;
|
|
Matter.Runner.run(this.engine);
|
|
|
|
let ground = Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true });
|
|
Matter.World.add(this.world, ground);
|
|
|
|
|
|
this.obstacles = [];
|
|
this.robots = [];
|
|
|
|
|
|
this.addObstacle([
|
|
{ x: -100, y: -100 }, // Vertex 1
|
|
{ x: 100, y: -100 }, // Vertex 2
|
|
{ x: 100, y: 100 }, // Vertex 3
|
|
{ x: -100, y: 100 } // Vertex 4
|
|
], { x: 400, y: 300 });
|
|
|
|
|
|
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: 400, y: 0 });
|
|
|
|
|
|
|
|
}
|
|
|
|
update() {
|
|
// Update the physics simulation
|
|
|
|
for (let id in this.robots) {
|
|
this.robots[id].update(this);
|
|
}
|
|
|
|
Matter.Engine.update(this.engine);
|
|
|
|
|
|
}
|
|
|
|
addObstacle(vertices, position = { x: 0, y: 0 }) {
|
|
// Convert the polygon points into a Matter.js body
|
|
let body = Matter.Bodies.fromVertices(position.x, position.y, [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,
|
|
frictionAir: 0.02, // Air resistance slows it down naturally
|
|
restitution: 0.2, // Slight bounce
|
|
});
|
|
|
|
// Add to the world
|
|
Matter.World.add(this.world, robotBody);
|
|
robot.body = robotBody;
|
|
this.robots.push(robot);
|
|
}
|
|
|
|
// Return the floor color based on (x, y) coordinates
|
|
getFloorColor(x, y) {
|
|
return (x + y) % 50 < 25 ? "black" : "white"; // Example pattern
|
|
}
|
|
|
|
// Draw the game world (e.g., obstacles, background)
|
|
draw(ctx) {
|
|
// Draw obstacles
|
|
ctx.strokeStyle = "gray"; // Obstacle outline color
|
|
ctx.lineWidth = 2; // Optional: to make the outline thicker
|
|
|
|
// Draw obstacles
|
|
this.obstacles.forEach(body => {
|
|
ctx.beginPath();
|
|
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);
|
|
}
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
});
|
|
|
|
this.robots.forEach(robot => {
|
|
let body = robot.body; // Get the Matter.js body from the robot object
|
|
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();
|
|
|
|
robot.draw(ctx); // Draw the robot's hull and sensors
|
|
});
|
|
|
|
}
|
|
|
|
rayCast(startX, startY, endX, endY) {
|
|
let closestIntersection = null;
|
|
let startPoint = { x: startX, y: startY };
|
|
let endPoint = { x: endX, y: endY };
|
|
// // Loop through all obstacles
|
|
// for (let i = 0; i < this.obstacles.length; i++) {
|
|
// let obstacle = this.obstacles[i];
|
|
|
|
// return this.lineIntersectsPolygon(startX, startY, endX, endY, obstacle);
|
|
// }
|
|
|
|
// Get all bodies in the world
|
|
let allBodies = Matter.Composite.allBodies(this.engine.world);
|
|
|
|
let hit = this.getRayHitPoint(startPoint, endPoint, allBodies);
|
|
if (hit) {
|
|
closestIntersection = hit.point;
|
|
}
|
|
return closestIntersection;
|
|
}
|
|
|
|
getRayHitPoint(start, end, bodies) {
|
|
let closestHit = null;
|
|
let minDistance = Infinity;
|
|
|
|
bodies.forEach(body => {
|
|
let vertices = body.vertices;
|
|
|
|
// Loop through each edge of the polygon body
|
|
for (let i = 0; i < vertices.length; i++) {
|
|
let v1 = vertices[i];
|
|
let v2 = vertices[(i + 1) % vertices.length]; // Wrap around to close the shape
|
|
|
|
let intersection = this.getLineIntersection(start, end, v1, v2);
|
|
if (intersection) {
|
|
let distance = Matter.Vector.magnitude(Matter.Vector.sub(intersection, start));
|
|
|
|
// Keep track of the closest intersection
|
|
if (distance < minDistance) {
|
|
minDistance = distance;
|
|
closestHit = { point: intersection, body };
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return closestHit;
|
|
}
|
|
|
|
// Line intersection helper function
|
|
getLineIntersection(p1, p2, p3, p4) {
|
|
let denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
|
|
|
|
if (denom === 0) return null; // Parallel lines, no intersection
|
|
|
|
let ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;
|
|
let ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;
|
|
|
|
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
|
|
return {
|
|
x: p1.x + ua * (p2.x - p1.x),
|
|
y: p1.y + ua * (p2.y - p1.y)
|
|
};
|
|
}
|
|
|
|
return null; // No valid intersection
|
|
}
|
|
|
|
|
|
lineIntersection(line1Start, line1End, line2Start, line2End) {
|
|
let denom = (line1Start.x - line1End.x) * (line2Start.y - line2End.y) -
|
|
(line1Start.y - line1End.y) * (line2Start.x - line2End.x);
|
|
|
|
if (denom === 0) return null; // Lines are parallel
|
|
|
|
let t = ((line1Start.x - line2Start.x) * (line2Start.y - line2End.y) -
|
|
(line1Start.y - line2Start.y) * (line2Start.x - line2End.x)) / denom;
|
|
let u = ((line1Start.x - line2Start.x) * (line1Start.y - line1End.y) -
|
|
(line1Start.y - line2Start.y) * (line1Start.x - line1End.x)) / denom;
|
|
|
|
if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
|
|
let ix = line1Start.x + t * (line1End.x - line1Start.x);
|
|
let iy = line1Start.y + t * (line1End.y - line1Start.y);
|
|
return { x: ix, y: iy };
|
|
}
|
|
|
|
return null; // No intersection
|
|
}
|
|
|
|
lineIntersectsPolygon(startX, startY, endX, endY, polygon) {
|
|
|
|
|
|
let lineStart = { x: startX, y: startY };
|
|
let lineEnd = { x: endX, y: endY };
|
|
|
|
// Loop through all edges of the polygon
|
|
for (let i = 0; i < polygon.length; i++) {
|
|
let currentVertex = polygon[i];
|
|
let nextVertex = polygon[(i + 1) % polygon.length]; // Loop back to the first vertex
|
|
|
|
let intersection = this.lineIntersection(lineStart, lineEnd, currentVertex, nextVertex);
|
|
if (intersection) {
|
|
return intersection; // Return the first intersection found
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
|
|
|
|
}
|