line sensors now drawn correctly and work with matter.js physics system

master
Jake 2025-03-29 23:10:25 +08:00
parent c7a0c9c834
commit 938d5790ae
4 changed files with 153 additions and 75 deletions

View File

@ -118,10 +118,7 @@ function gameLoop() {
gameWorld.update();
gameWorld.draw(ctx);
for (let id in robots) {
robots[id].update(ctx, gameWorld);
gameWorld.checkAndResolveCollision(robots[id]);
}
}
requestAnimationFrame(gameLoop);
}

View File

@ -5,7 +5,7 @@ export class GameWorld {
this.engine = Matter.Engine.create();
this.world = this.engine.world;
this.engine.world.gravity.y = 0.01;
this.engine.world.gravity.y = 0;//0.01;
Matter.Runner.run(this.engine);
let ground = Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true });
@ -15,19 +15,21 @@ export class GameWorld {
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
]);
{ 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 });
@ -35,14 +37,19 @@ export class GameWorld {
update() {
// Update the physics simulation
for (let id in this.robots) {
this.robots[id].update(this);
}
Matter.Engine.update(this.engine);
}
addObstacle(vertices) {
addObstacle(vertices, position = { x: 0, y: 0 }) {
// Convert the polygon points into a Matter.js body
let body = Matter.Bodies.fromVertices(-30, 300, [vertices], {
let body = Matter.Bodies.fromVertices(position.x, position.y, [vertices], {
isStatic: true, // Obstacles shouldn't move
});
@ -56,12 +63,14 @@ export class GameWorld {
// 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);
this.robots.push(robotBody);
robot.body = robotBody;
this.robots.push(robot);
}
// Return the floor color based on (x, y) coordinates
@ -87,8 +96,10 @@ export class GameWorld {
ctx.stroke();
});
this.robots.forEach(body => {
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);
@ -97,22 +108,81 @@ export class GameWorld {
}
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];
// 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);
// }
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);

View File

@ -7,7 +7,8 @@ export class Robot {
this.x = x;
this.y = y;
this.velocity = 0;
this.angle = 0; // Degrees
this.thrust = 0;
this.angularThrust = 0
this.width = 20;
this.height = 20;
this.color = color;
@ -28,9 +29,21 @@ export class Robot {
}
update(ctx, gameWorld) {
this.update_position();
update(gameWorld) {
let angle = this.body.angle; // Get current rotation in radians
let force = {
x: Math.cos(angle) * this.thrust,
y: Math.sin(angle) * this.thrust
};
Matter.Body.applyForce(this.body, this.body.position, force);
Matter.Body.setAngularVelocity(this.body, this.angularThrust);
//console.log(angle);
this.update_sensors(gameWorld);
}
draw(ctx){
this.draw(ctx);
}
@ -45,60 +58,59 @@ 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;
// 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) {
this.velocity = velocity;
move(thrust) {
this.thrust = thrust;
}
turn(degrees) {
this.angle += degrees;
this.angularThrust += degrees;
}
draw(ctx) {
return ;
ctx.fillStyle = this.color;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate((this.angle * Math.PI) / 180);
// Draw the rectangle (tank body)
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
// ctx.fillStyle = this.color;
// ctx.save();
// ctx.translate(this.x, this.y);
// ctx.rotate((this.angle * Math.PI) / 180);
// 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
ctx.lineTo(this.width / 2, this.height / 2); // Bottom right of triangle
ctx.closePath();
// // Draw the rectangle (tank body)
// ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
ctx.fill(); // Fill both the rectangle and the triangle
// // 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
// ctx.lineTo(this.width / 2, this.height / 2); // Bottom right of triangle
// ctx.closePath();
ctx.beginPath();
ctx.moveTo(this.hull[0].x, this.hull[0].y); // Start at the first vertex
// ctx.fill(); // Fill both the rectangle and the triangle
// 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);
}
// ctx.beginPath();
// ctx.moveTo(this.hull[0].x, this.hull[0].y); // Start at the first vertex
// Close the path to form a closed polygon
ctx.closePath();
// // 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);
// }
ctx.stroke(); // Draw the outline
// // 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));
}
@ -111,8 +123,8 @@ export class Robot {
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
x: this.body.position.x + xRotated, // Translate to robot's position
y: this.body.position.y + yRotated
};
});
//console.log(this.transformedHull );
@ -121,11 +133,10 @@ export class Robot {
draw_sensors(ctx) {
ctx.fillStyle = "yellow";
this.sensors.forEach(sensor => {
const radians = ((this.angle + sensor.angleOffset) * Math.PI) / 180;
const sensorX = this.x + Math.cos(radians) * 15;
const sensorY = this.y + Math.sin(radians) * 15;
const sensorX = this.body.position.x + Math.cos(radians) * 15;
const sensorY = this.body.position.y + Math.sin(radians) * 15;
ctx.beginPath();
ctx.arc(sensorX, sensorY, 3, 0, 2 * Math.PI);

View File

@ -11,10 +11,11 @@ export class Sensor {
}
updatePosition() {
const robotAngle = this.robot.angle * (Math.PI / 180);
const robotAngle = this.robot.body.angle;
const offsetAngleRad = this.offsetAngle * (Math.PI / 180);
this.startX = this.robot.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
this.startY = this.robot.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
this.startX = this.robot.body.position.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
this.startY = this.robot.body.position.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
}
read(robot, gameWorld) {
@ -35,10 +36,10 @@ export class RaycastSensor extends Sensor {
}
updatePosition() {
const robotAngle = this.robot.angle * (Math.PI / 180);
const robotAngle = this.robot.body.angle;
const offsetAngleRad = this.offsetAngle * (Math.PI / 180);
this.startX = this.robot.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
this.startY = this.robot.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
this.startX = this.robot.body.position.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
this.startY = this.robot.body.position.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
const sensorAngle = robotAngle + (this.angle * (Math.PI / 180));
this.endX = this.startX + Math.cos(sensorAngle) * this.range;
this.endY = this.startY + Math.sin(sensorAngle) * this.range;
@ -98,7 +99,6 @@ export class RaycastSensor extends Sensor {
ctx.moveTo(this.startX, this.startY);
ctx.lineTo(this.endX, this.endY);
ctx.stroke();
}
}