From 938d5790ae30fb23d130abdbf49005117f7785a0 Mon Sep 17 00:00:00 2001 From: Jake Date: Sat, 29 Mar 2025 23:10:25 +0800 Subject: [PATCH] line sensors now drawn correctly and work with matter.js physics system --- game.js | 5 +-- gameworld.js | 104 ++++++++++++++++++++++++++++++++++++++++++--------- robot.js | 101 +++++++++++++++++++++++++++---------------------- sensor.js | 18 ++++----- 4 files changed, 153 insertions(+), 75 deletions(-) diff --git a/game.js b/game.js index 9c2fd35..6c85c03 100644 --- a/game.js +++ b/game.js @@ -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); } diff --git a/gameworld.js b/gameworld.js index 843ff7c..b80f079 100644 --- a/gameworld.js +++ b/gameworld.js @@ -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 @@ -86,9 +95,11 @@ export class GameWorld { ctx.closePath(); 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); diff --git a/robot.js b/robot.js index 9b64b9c..630f034 100644 --- a/robot.js +++ b/robot.js @@ -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; @@ -25,12 +26,24 @@ export class Robot { this.addSensor(new RaycastSensor(this, 40, 13, 45, 60)); this.addSensor(new FloorColorSensor(this, 0, 0)); - + } - 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); + + // 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); + // // Draw the rectangle (tank body) + // 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 - ctx.lineTo(this.width / 2, this.height / 2); // Bottom right of triangle - ctx.closePath(); + // // 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.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 + // 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); - } + // // 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(); + // // Close the path to form a closed polygon + // ctx.closePath(); - ctx.stroke(); // Draw the outline + // ctx.stroke(); // Draw the outline - ctx.restore(); - + // ctx.restore(); this.sensors.forEach(sensor => sensor.draw(ctx)); } @@ -109,10 +121,10 @@ export class Robot { 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 + 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); diff --git a/sensor.js b/sensor.js index 72008ae..083c9cc 100644 --- a/sensor.js +++ b/sensor.js @@ -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; @@ -48,7 +49,7 @@ export class RaycastSensor extends Sensor { //console.log(robot); //console.log(gameWorld); this.updatePosition(); - + const angleInRadians = (this.robot.direction + this.angle) * Math.PI / 180; const x = this.robot.x + Math.cos(angleInRadians) * this.distance; const y = this.robot.y + Math.sin(angleInRadians) * this.distance; @@ -71,7 +72,7 @@ export class RaycastSensor extends Sensor { } draw(ctx) { - + ctx.strokeStyle = "lime"; ctx.fillStyle = "green" @@ -98,7 +99,6 @@ export class RaycastSensor extends Sensor { ctx.moveTo(this.startX, this.startY); ctx.lineTo(this.endX, this.endY); ctx.stroke(); - } }