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