onlinecodesimulator/gameworld.js

397 lines
12 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.levelData = null;
this.currentLevel = 0;
this.waypoints = [];
this.prevWaypointsReached = [];
this.waypointsReached = [];
this.obstacles = [];
this.robots = [];
}
reset(player = null) {
this.robots = []
this.obstacles = []
this.waypoints = [];
this.waypointsReached = [];
this.prevWaypointsReached = [];
Matter.World.clear(this.world); // Clear the world without resetting the engine
let level = this.levelData[this.currentLevel];
if (player) {
console.log("Resetting player position to:", level.robots["player"]["position"]);
console.log(this.robots);
player.x = level.robots["player"]["position"]["x"];
player.y = level.robots["player"]["position"]["y"];
this.addRobot(player);
}
for (let i = 0; i < level.obstacles.length; i++) {
let obstacle = level.obstacles[i];
this.addObstacle(obstacle.vertices, obstacle.position, obstacle.strokeColor, obstacle.fillColor);
}
for (let i = 0; i < level.waypoints.length; i++) {
let obstacle = level.waypoints[i];
console.log("Adding waypoint:", obstacle);
this.addWaypoint(obstacle.vertices, obstacle.position, obstacle.strokeColor, obstacle.fillColor);
this.waypointsReached.push(false); // Initialize as not reached
}
this.prevWaypointsReached = [...this.waypointsReached];
console.log(level.waypointsReached)
// 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: -50 });
}
update() {
// Update the physics simulation
for (let id in this.robots) {
this.robots[id].update(this);
}
for (let i = 0; i < this.waypoints.length; i++) {
if (this.waypointsReached[i]) {
} else {
let waypoint = this.waypoints[i];
let playerPos = this.robots[0].body.position;
let waypointBounds = waypoint.bounds;
if (Matter.Bounds.contains(waypointBounds, playerPos)) {
console.log("Player is inside the waypoint's bounding box.");
this.waypointsReached[i] = true; // Mark this waypoint as reached
}
}
}
//this.checkPlayerCompletedTask();
Matter.Engine.update(this.engine);
}
waypointsReached() {
return this.waypointsReached;
}
waypointsReachedChanged() {
// Check if waypointsReached has changed since the last update
let changed = false;
for (let i = 0; i < this.waypointsReached.length; i++) {
if (this.waypointsReached[i] !== this.prevWaypointsReached[i]) {
changed = true;
break;
}
}
// Update prevWaypointsReached to the current state
this.prevWaypointsReached = [...this.waypointsReached];
return changed;
}
// Needs to be updated to handle different win conditions
// checkPlayerCompletedTask() {
// let playerPos = this.robots[0].body.position;
// let waypointBounds = this.waypoints[0].bounds;
// if (Matter.Bounds.contains(waypointBounds, playerPos)) {
// console.log("Player is inside the waypoint's bounding box.");
// return true;
// }
// return false;
// }
addWaypoint(vertices, position = { x: 0, y: 0 }, strokeColor = "yellow", fillColor = "yellow") {
// Convert the polygon points into a Matter.js body
const sortedVertices = Matter.Vertices.clockwiseSort(vertices);
// Compute centroid
const centroid = this.getCentroid(sortedVertices);
// Create relative vertices centered around centroid
const relativeVertices = sortedVertices.map(v => ({
x: v.x - centroid.x,
y: v.y - centroid.y
}));
const body = Matter.Bodies.fromVertices(
centroid.x,
centroid.y,
[relativeVertices],
{
isSensor: true,
isStatic: true,
label: "zone",
},
true
);
body.strokeColor = 'rgba(0, 0, 255, 0.1)';
body.fillColor = 'rgba(0, 0, 255, 0.1)';
Matter.World.add(this.world, body);
this.waypoints.push(body);
}
getCentroid(vertices) {
let area = 0, cx = 0, cy = 0;
for (let i = 0; i < vertices.length; i++) {
const p1 = vertices[i];
const p2 = vertices[(i + 1) % vertices.length];
const cross = p1.x * p2.y - p2.x * p1.y;
area += cross;
cx += (p1.x + p2.x) * cross;
cy += (p1.y + p2.y) * cross;
}
area *= 0.5;
if (area === 0) return vertices[0]; // fallback to first point
cx /= (6 * area);
cy /= (6 * area);
return { x: cx, y: cy };
}
addObstacle(vertices, strokeColor = "black", fillColor = "gray") {
// Sort vertices clockwise (Matter requires this)
const sortedVertices = Matter.Vertices.clockwiseSort(vertices);
// Compute centroid
const centroid = this.getCentroid(sortedVertices);
// Create relative vertices centered around centroid
const relativeVertices = sortedVertices.map(v => ({
x: v.x - centroid.x,
y: v.y - centroid.y
}));
// Create the body with options to reduce auto corrections
const body = Matter.Bodies.fromVertices(
centroid.x,
centroid.y,
[relativeVertices],
{
isStatic: true,
// If your Matter.js supports these:
// removeCollinear: 0.01,
// minimumArea: 10
},
true // autoHull (convex decomposition) enabled
);
body.strokeColor = strokeColor;
body.fillColor = fillColor;
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.fillStyle = body.fillColor;;
ctx.strokeStyle = body.strokeColor;
ctx.closePath();
ctx.fill();
ctx.stroke();
});
// Draw Waypoints
for (let w = 0; w < this.waypoints.length; w++) {
let body = this.waypoints[w];
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);
}
if (this.waypointsReached[w]) {
ctx.globalAlpha = 0.05; // Applies to all drawing
ctx.fillStyle = "green"; // Color for reached waypoints
ctx.strokeStyle = "rgba(0, 128, 0, 0.2)"; // green with 80% opacity
} else {
ctx.globalAlpha = 0.2; // Applies to all drawing
ctx.fillStyle = body.fillColor; // Default color for waypoints
ctx.strokeStyle = body.strokeColor;
}
ctx.closePath();
ctx.fill();
ctx.globalAlpha = 1.0; // Reset after if needed
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, ignoreBodies = []) {
let closestIntersection = null;
let startPoint = { x: startX, y: startY };
let endPoint = { x: endX, y: endY };
ignoreBodies.push(...this.waypoints); // Add all obstacles to the ignore list
// Get all bodies in the world
let allBodies = Matter.Composite.allBodies(this.engine.world);
// ✅ Filter out ignored bodies instead of removing them
let filteredBodies = allBodies.filter(body => !ignoreBodies.includes(body));
// Debugging
//console.log("Ignoring bodies: ", ignoreBodies.map(b => b.id));
//console.log("Filtered bodies: ", filteredBodies.map(b => b.id));
// Perform raycast only on the filtered bodies
let hit = this.getRayHitPoint(startPoint, endPoint, filteredBodies);
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
}
}