self driving car
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user