Files
2022-11-07 19:26:26 +02:00

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;
}