---
# System prepended metadata

title: self driving car

---

# 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();
        })

    }
}
```