# Self Driving Cars
Interenet Connection
Network: D Block Workspace
Email: gustaw.daniel@gmail
PIN: 10675
# Online
Participation link:
https://meet.jit.si/LetsLearnTogether
Git repo (starting point)
https://github.com/gniziemazity/self-driving-car/tree/main
Meetup event
www.meetup.com/lets-learn-together/events/303977800/?eventOrigin=home_page_upcoming_events$all
---
# Env notes
screen replication
```
xrandr --output DP-3 --same-as eDP-1
```
# Plan
1. Car driving Mechanics
2. Road Definition
3. Artifical sensors
4. Collision detection
5. Traffic simulation
6. Neural network
7. Visualizing neural network
8. Optimizing neural network
---
Our REPO
https://github.com/gustawdaniel/js-self-driving-car-demo
style.css
```css
body {
margin: 0;
background: darkgray;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
#carCanvas {
background: lightgray;
}
```
index.html
```html
<html>
<head>
<link rel="stylesheet" href="./style.css">
</head>
<canvas id="carCanvas"></canvas>
<script src="./utils.js"></script>
<script src="./sensor.js"></script>
<script src="./road.js"></script>
<script src="./controls.js"></script>
<script src="./car.js"></script>
<script src="./main.js"></script>
</html>
```
cmd
```
python -m http.server 9000
```
main.js
```js
const carCanvas = document
.querySelector('canvas#carCanvas');
carCanvas.width = 200;
const carCtx = carCanvas.getContext('2d');
const road = new Road(carCanvas.width/2, carCanvas.width*0.9);
const car = new Car(100, 100, 30, 50);
const traffic = [];
animate()
function animate() {
carCanvas.height = window.innerHeight;
car.update(road.borders, traffic);
road.draw(carCtx);
car.draw(carCtx, 'red', true);
requestAnimationFrame(animate)
}
```
car.js
```js
class Car {
constructor(x,y,width,height, maxSpeed = 1) {
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.controlls = new Controls();
this.sensor = new Sensor(this);
}
update(roadBorders, traffic) {
this.polygon = this.#createPolygon();
this.#move();
if(this.sensor) {
this.sensor.update(roadBorders, traffic);
}
}
#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.controlls.forward) {
this.speed += this.acceleration;
}
if(this.controlls.reverse){
this.speed -= this.acceleration;
}
if(this.speed > this.maxSpeed) {
this.speed = this.maxSpeed;
}
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.controlls.left) {
this.angle += 0.03 * flip;
}
if(this.controlls.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,drawSensors = false) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.lineWidth = 0;
ctx.strokeStyle = 'transparent';
ctx.moveTo(this.polygon[0].x, this.polygon[0].y);
for (let i = 1; i < this.polygon.length; i++) {
ctx.lineTo(this.polygon[i].x, this.polygon[i].y);
}
ctx.fill();
ctx.stroke();
if (this.sensor && drawSensors) {
this.sensor.draw(ctx);
}
}
}
```
controls.js
```js
class Controls {
constructor() {
this.forward = false;
this.right = false;
this.left = false;
this.reverse = false;
this.#addKeyboardListener()
}
#addKeyboardListener() {
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'ArrowUp':
this.forward = true;
break;
case 'ArrowLeft':
this.left = true;
break;
case 'ArrowRight':
this.right = true;
break;
case 'ArrowDown':
this.reverse = true;
break;
}
});
document.addEventListener('keyup', (e) => {
switch(e.key) {
case 'ArrowUp':
this.forward = false;
break;
case 'ArrowLeft':
this.left = false;
break;
case 'ArrowRight':
this.right = false;
break;
case 'ArrowDown':
this.reverse = false;
break;
}
});
}
}
```
sensor.js
```js
class Sensor {
constructor(car) {
this.car = car;
this.rayCount = 5;
this.rayLength = 150;
this.raySpread = Math.PI / 2;
this.rays = [];
this.readings = [];
}
update(roadBorders, traffic) {
this.#castRays()
this.readings = [];
for (let i = 0; i < this.rays.length; i++) {
this.readings.push(
this.#getReading(this.rays[i], roadBorders, traffic)
)
}
}
#getReading(ray, roadBorders, traffic) {
const touches = [];
for (let i = 0; i < roadBorders.length; i++) {
const touch = getIntersection(
ray[0],
ray[1],
roadBorders[i][0],
roadBorders[i][1],
)
if (touch) {
touches.push(touch);
}
}
for(let i = 0; i<traffic.length; i++) {
const poly = traffic[i].polygon;
for (let j = 0; j < poly.length; j++) {
const touch = getIntersection(
ray[0],
ray[1],
poly[j],
poly[(j+1)%poly.length],
)
if (touch) {
touches.push(touch);
}
}
}
if (touches.length === 0) {
return null;
} else {
const offsets = touches.map(e => e.offset);
const minOffset = Math.min(...offsets);
return touches.find(e => e.offset === minOffset);
}
}
#castRays() {
this.rays = [];
for (let i = 0; i < this.rayCount; i++) {
const rayAngle = lerp(
this.raySpread / 2,
-this.raySpread / 2,
this.rayCount === 1 ? 0.5 : i / (this.rayCount - 1)
) + this.car.angle
const start = {x: this.car.x, y: this.car.y};
const end = {
x: this.car.x - Math.sin(rayAngle) * this.rayLength,
y: this.car.y - Math.cos(rayAngle) * this.rayLength,
};
this.rays.push([start, end]);
}
}
draw(ctx) {
for (let i = 0; i < this.rayCount; i++) {
let end = this.rays[i][1];
if (this.readings[i]) {
end = this.readings[i];
}
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = 'yellow';
ctx.moveTo(this.rays[i][0].x, this.rays[i][0].y);
// ctx.lineTo(this.rays[i][1].x, this.rays[i][1].y);
ctx.lineTo(end.x, end.y);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.moveTo(this.rays[i][1].x, this.rays[i][1].y);
ctx.lineTo(end.x, end.y);
ctx.stroke();
}
}
}
```
utils.js
```js
function lerp(A,B,t) {
return A+(B-A)*t
}
function getIntersection(A, B, C, D) {
const tTop = (D.x - C.x) * (A.y - C.y) - (D.y - C.y) * (A.x - C.x);
const uTop = (C.y - A.y) * (A.x - B.x) - (C.x - A.x) * (A.y - B.y);
const bottom = (D.y - C.y) * (B.x - A.x) - (D.x - C.x) * (B.y - A.y);
if (bottom !== 0) {
const t = tTop / bottom;
const u = uTop / bottom;
if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
return {
x: lerp(A.x, B.x, t),
y: lerp(A.y, B.y, t),
offset: t
}
}
}
return null;
}
function polysIntersect(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;
}
function getRGBA(value) {
const alpha = Math.abs(value);
const R = value < 0 ? 255 : 255;
const G = value < 0 ? 0 : 255;
const B = value < 0 ? 255 : 0;
return `rgba(${R},${G},${B}${alpha})`;
}
```
road.js
```js
class Road {
constructor(x, width, laneCount = 3) {
this.x = x;
this.width = width;
this.laneCount = laneCount;
this.left = x - width / 2
this.right = x + width / 2
const infinity = 1000000;
this.top = -infinity;
this.bottom = infinity;
const topLeft = {x: this.left, y: this.top};
const topRight = {x: this.right, y: this.top};
const bottomLeft = {x: this.left, y: this.bottom};
const bottomRight = {x: this.right, y: this.bottom};
this.borders = [
[topLeft, bottomLeft],
[topRight, bottomRight],
]
}
getLaneCenter(laneIndex) {
const laneWidth = this.width / this.laneCount;
return this.left + laneWidth / 2 + Math.min(laneIndex, this.laneCount - 1) * laneWidth;
}
draw(ctx) {
ctx.lineWidth = 5;
ctx.strokeStyle = "white";
for (let i = 1; i <= this.laneCount - 1; i++) {
const x = lerp(this.left, this.right, i / this.laneCount)
ctx.setLineDash([20, 20])
ctx.beginPath();
ctx.moveTo(x, this.top);
ctx.lineTo(x, this.bottom);
ctx.stroke();
}
ctx.setLineDash([])
this.borders.forEach((border) => {
ctx.beginPath();
ctx.moveTo(border[0].x, border[0].y);
ctx.lineTo(border[1].x, border[1].y);
ctx.stroke();
})
}
}
```