175 lines
4.3 KiB
JavaScript
175 lines
4.3 KiB
JavaScript
class Car {
|
|
constructor(
|
|
x,
|
|
y,
|
|
controlType,
|
|
color = "blue",
|
|
maxSpeed = 4,
|
|
width = 30,
|
|
height = 50
|
|
) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
|
|
this.speed = 0;
|
|
this.acceleration = 0.2;
|
|
this.maxSpeed = maxSpeed;
|
|
this.friction = 0.05;
|
|
this.angle = 0;
|
|
this.useBrain = controlType == "AI";
|
|
if (controlType != "DUMMY") {
|
|
this.sensor = new Sensor(this);
|
|
this.brain = new NeuralNetwork([
|
|
this.sensor.rayCount,
|
|
6,
|
|
4,
|
|
]);
|
|
}
|
|
this.controls = new Controls(controlType);
|
|
this.img = new Image();
|
|
this.img.src = "car.png";
|
|
this.mask = document.createElement("canvas");
|
|
this.mask.width = width;
|
|
this.mask.height = height;
|
|
|
|
const maskCtx = this.mask.getContext("2d");
|
|
this.img.onload = () => {
|
|
maskCtx.fillStyle = color;
|
|
maskCtx.rect(0, 0, this.width, this.height);
|
|
maskCtx.fill();
|
|
|
|
maskCtx.globalCompositeOperation = "destination-atop";
|
|
maskCtx.drawImage(
|
|
this.img,
|
|
0,
|
|
0,
|
|
this.width,
|
|
this.height
|
|
);
|
|
};
|
|
}
|
|
update(roadBorders, traffic) {
|
|
if (!this.damaged) {
|
|
this.#move();
|
|
this.polygon = this.#createPolygon();
|
|
this.damaged = this.#assessDamage(roadBorders, traffic);
|
|
}
|
|
if (this.sensor) {
|
|
this.sensor.update(roadBorders, traffic);
|
|
const offsets = this.sensor.readings.map((s) =>
|
|
s == null ? 0 : 1 - s.offset
|
|
);
|
|
const outputs = NeuralNetwork.feedForward(
|
|
offsets,
|
|
this.brain
|
|
);
|
|
if (this.useBrain) {
|
|
this.controls.forward = outputs[0];
|
|
this.controls.left = outputs[1];
|
|
this.controls.right = outputs[2];
|
|
this.controls.reverse = outputs[3];
|
|
}
|
|
}
|
|
}
|
|
#assessDamage(roadBorders, traffic) {
|
|
for (let i = 0; i < roadBorders.length; i++) {
|
|
if (polyIntersect(this.polygon, roadBorders[i]))
|
|
return true;
|
|
}
|
|
for (let i = 0; i < traffic.length; i++) {
|
|
if (polyIntersect(this.polygon, traffic[i].polygon)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#createPolygon() {
|
|
const points = [];
|
|
const rad = Math.hypot(this.width, this.height) / 2;
|
|
const alpha = Math.atan2(this.width, this.height);
|
|
points.push({
|
|
x: this.x - Math.sin(this.angle - alpha) * rad,
|
|
y: this.y - Math.cos(this.angle - alpha) * rad,
|
|
});
|
|
points.push({
|
|
x: this.x - Math.sin(this.angle + alpha) * rad,
|
|
y: this.y - Math.cos(this.angle + alpha) * rad,
|
|
});
|
|
points.push({
|
|
x:
|
|
this.x -
|
|
Math.sin(Math.PI + this.angle - alpha) * rad,
|
|
y:
|
|
this.y -
|
|
Math.cos(Math.PI + this.angle - alpha) * rad,
|
|
});
|
|
points.push({
|
|
x:
|
|
this.x -
|
|
Math.sin(Math.PI + this.angle + alpha) * rad,
|
|
y:
|
|
this.y -
|
|
Math.cos(Math.PI + this.angle + alpha) * rad,
|
|
});
|
|
return points;
|
|
}
|
|
#move() {
|
|
if (this.controls.forward) this.speed += this.acceleration;
|
|
else if (this.controls.reverse) this.speed -= this.acceleration;
|
|
if (this.speed > this.maxSpeed) this.speed = this.maxSpeed;
|
|
else if (this.speed < -this.maxSpeed / 2)
|
|
this.speed = -this.maxSpeed / 2;
|
|
if (this.speed > 0) this.speed -= this.friction;
|
|
if (this.speed < 0) this.speed += this.friction;
|
|
if (Math.abs(this.speed) < this.friction) this.speed = 0;
|
|
if (this.speed != 0) {
|
|
const flip = this.speed > 0 ? 1 : -1;
|
|
if (this.controls.left) this.angle += 0.03 * flip;
|
|
if (this.controls.right) this.angle -= 0.03 * flip;
|
|
}
|
|
this.x -= Math.sin(this.angle) * this.speed;
|
|
this.y -= Math.cos(this.angle) * this.speed;
|
|
}
|
|
draw(ctx, color, drawSensor = false) {
|
|
if (this.sensor && drawSensor) this.sensor.draw(ctx);
|
|
ctx.save();
|
|
ctx.translate(this.x, this.y);
|
|
ctx.rotate(-this.angle);
|
|
if (!this.damaged) {
|
|
ctx.drawImage(
|
|
this.mask,
|
|
-this.width / 2,
|
|
-this.height / 2,
|
|
this.width,
|
|
this.height
|
|
);
|
|
}
|
|
ctx.globalCompositeOperation = "multiply";
|
|
ctx.drawImage(
|
|
this.img,
|
|
-this.width / 2,
|
|
-this.height / 2,
|
|
this.width,
|
|
this.height
|
|
);
|
|
ctx.restore();
|
|
}
|
|
}
|
|
|
|
function polyIntersect(poly1, poly2) {
|
|
for (let i = 0; i < poly1.length; i++) {
|
|
for (let j = 0; j < poly2.length; j++) {
|
|
const touch = getIntersection(
|
|
poly1[i],
|
|
poly1[(i + 1) % poly1.length],
|
|
poly2[j],
|
|
poly2[(j + 1) % poly2.length]
|
|
);
|
|
if (touch) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|