Repository: jes/nightdrive
Branch: master
Commit: 4a8899372bf6
Files: 6
Total size: 15.1 KB
Directory structure:
gitextract_olwi0mbm/
├── index.html
└── js/
├── car.js
├── nightdrive.js
├── plane.js
├── scene.js
└── vector.js
================================================
FILE CONTENTS
================================================
================================================
FILE: index.html
================================================
<!doctype html>
<html>
<head>
<title>Nightdrive</title>
<style type="text/css">
html,body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
cursor: none;
}
</style>
</head>
<body>
<span style="font-size:2em; position:absolute; color: #888; font-family: monospace" id="clickformusic">Click to toggle music</span>
<canvas id="canvas" style="display:block; width:100vw; height:100vh"></canvas>
<!-- music from https://www.youtube.com/watch?v=4s%2d%2dbtQafWs -->
<audio id="audio" src="music.mp3" loop></audio>
<script src="js/vector.js?v3"></script>
<script src="js/scene.js?v3"></script>
<script src="js/car.js?v3"></script>
<script src="js/plane.js?v3"></script>
<script src="js/nightdrive.js?v3"></script>
</body>
</html>
================================================
FILE: js/car.js
================================================
function Car() {
// physics
this.pos = new V2d(0,1);
this.vel = new V2d(0,0.005);
// lights
this.braking = false;
this.mainbeam = false;
this.leftindicator = false;
this.rightindicator = false;
this.indicatorperiod = 0.9 + Math.random()*0.2; // seconds
this.indication = 0;
this.lanes = [0]; // x coordinates of lanes
this.lane = 0;
// car configuration
const lightheight = Math.random()*0.2+0.4;
const lightwidth = Math.random()*0.3+1.3;
const lightradius = Math.random()*0.05+0.1;
const rearlightcolour = colour(180,255, 0,50, 0,50);
this.rearlights = [
{xy: new V2d(-lightwidth/2, 0.0), z: lightheight, r: lightradius, col: rearlightcolour},
{xy: new V2d( lightwidth/2, 0.0), z: lightheight, r: lightradius, col: rearlightcolour},
];
const headlightcolour = colour(180,255, 180,255, 180,255);
this.headlights = [
{xy: new V2d(-lightwidth/2, 0.0), z: lightheight, r: lightradius, col: headlightcolour},
{xy: new V2d( lightwidth/2, 0.0), z: lightheight, r: lightradius, col: headlightcolour},
];
const indicatorcolour = colour(180,255, 100,200, 0,100);
const indicatorwidth = lightwidth + 0.2 + Math.random()*0.1;
const indicatorheight = lightheight - 0.1 + Math.random()*0.2;
const indicatorradius = lightradius - (0.04 + Math.random()*0.03);
this.leftindicatorlights = [
{xy: new V2d(-indicatorwidth/2, 0.0), z: indicatorheight, r: indicatorradius, col: indicatorcolour},
];
this.rightindicatorlights = [
{xy: new V2d( indicatorwidth/2, 0.0), z: indicatorheight, r: indicatorradius, col: indicatorcolour},
];
};
Car.prototype.render = function(scene) {
if (this.vel.y > 0) {
// moving in same direction as viewer: draw rearlights
this.drawLights(scene, this.rearlights);
} else {
// moving in opposite direction to viewer: draw headlights
this.drawLights(scene, this.headlights);
}
if (this.leftindicator && this.indication > (this.indicatorperiod/2)) this.drawLights(scene, this.leftindicatorlights);
if (this.rightindicator && this.indication > (this.indicatorperiod/2)) this.drawLights(scene, this.rightindicatorlights);
};
Car.prototype.drawLights = function(scene, lights) {
for (l of lights) {
scene.drawCircle(this.pos.add(l.xy), l.z, l.r, l.col);
}
};
Car.prototype.step = function(dt) {
this.pos = this.pos.add(this.vel.mul(dt));
this.indication += dt;
while (this.indication > this.indicatorperiod)
this.indication -= this.indicatorperiod;
const wrapy = 2500;
while (this.pos.y > observer.pos.y+wrapy && this.vel.y > observer.vel.y)
this.pos.y -= wrapy;
while (this.pos.y < observer.pos.y && this.vel.y < observer.vel.y)
this.pos.y += wrapy;
// which lane are we in?
let mylane = 0;
for (let i = 0; i < this.lanes.length; i++) {
if (Math.abs(this.pos.x-this.lanes[i]) < (Math.abs(this.pos.x-this.lanes[mylane]))) mylane = i;
}
this.lane = mylane;
const k = Math.sign(this.vel.y);
if (this.changelane) {
const m = this.targetlane - this.sourcelane;
if (this.lane == this.targetlane) {
// decelerate in x
this.vel = new V2d(this.vel.x-k*m*0.79*dt, this.vel.y);
} else {
// accelerate in x
this.vel = new V2d(this.vel.x+k*m*0.8*dt, this.vel.y);
}
// once we reach the centre of the target lane, snap to centre and stop changing lane
if (Math.sign(this.pos.x-this.lanes[this.targetlane]) != Math.sign(this.pos.x+this.vel.x*4*dt - this.lanes[this.targetlane])) {
this.lane = this.targetlane;
this.pos = new V2d(this.lanes[this.lane], this.pos.y);
this.vel = new V2d(0, this.vel.y);
this.changelane = false;
}
}
const collision_secs = 7.0;
const min_clearance = 2.0; // metres
let leftlanesafe = this.lane > 0;
loop:
//for (car of cars) {
for (let j = 0; j < cars.length; j++) {
const car = cars[j];
if (car == this) continue;
if (Math.sign(car.vel.y*this.vel.y) == -1) continue;
for (let i = -1; i <= 1; i++) {
const yoff = i*wrapy;
// if we're not in the fast lane, and we're behind this car, and it's in our lane, and we're going faster, and we'll hit it within N seconds, change lanes
if (Math.sign(car.vel.y)==k && this.lane < this.lanes.length-1 && this.lane<=car.lane && k*(this.pos.y+yoff) < k*car.pos.y && k*this.vel.y > k*car.vel.y && k*(this.pos.y+yoff+this.vel.y*collision_secs+min_clearance) > k*(car.pos.y+car.vel.y*collision_secs)) {
this.changelane = true;
this.sourcelane = this.lane;
this.targetlane = this.lane+1;
break loop;
}
// the left lane is not safe if there is a car in it that we'd hit within 3N seconds
if (Math.sign(car.vel.y)==k && car.lane==this.lane-1 && k*(this.pos.y+yoff) < k*car.pos.y && k*this.vel.y > k*car.vel.y && k*(this.pos.y+yoff+this.vel.y*collision_secs*3) > k*(car.pos.y+car.vel.y*collision_secs*3+min_clearance)) {
leftlanesafe = false;
}
}
}
// move left if we're not changing lane and the left lane is safe
if (!this.changelane && leftlanesafe) {
this.changelane = true;
this.sourcelane = this.lane;
this.targetlane = this.lane-1;
}
if (this.changelane) {
this.rightindicator = this.targetlane > this.sourcelane;
this.leftindicator = this.targetlane < this.sourcelane;
} else {
this.indication = 0;
this.leftindicator = false;
this.rightindicator = false;
}
};
function colour(r1,r2, g1,g2, b1,b2) {
const r = r1 + Math.random() * (r2-r1);
const g = g1 + Math.random() * (g2-g1);
const b = b1 + Math.random() * (b2-b1);
return `rgb(${r},${g},${b})`;
}
================================================
FILE: js/nightdrive.js
================================================
let laststep = null;
let observer;
let cars = [];
let planes = [];
let lastwidth;
let lastheight;
const lanes = [-10,-7,-4];
const speed = [60,66,80]; // mph
const catseyedist = 20; // metres
const streetlightdist = 107; // metres
const started = Date.now();
const musiclabeltime = 5000; // ms
function init() {
observer = new Car();
observer.pos.x = lanes[1];
observer.vel.y = speed[1] * 1600/3600;
observer.lanes = lanes;
const ourlanes = lanes;
const theirlanes = ourlanes.map((x) => -x);
for (let i = 0; i < 100; i++) {
const car = new Car();
const lane = Math.floor(Math.random()*lanes.length);
car.pos = new V2d(lanes[lane],i*25);
const mph = speed[lane];
car.vel = new V2d(0, mph * 1600 / 3600);
car.lanes = ourlanes;
if (Math.random() < 0.5) {
car.vel.y = -car.vel.y;
car.pos.x = -car.pos.x;
car.lanes = theirlanes;
}
car.vel.y += Math.random() * 20 - 10;
cars.push(car);
}
cars.push(observer);
const canvas = document.getElementById('canvas');
resize(canvas);
render();
}
function render() {
step();
const canvas = document.getElementById('canvas');
if (canvas.clientWidth != lastwidth || canvas.clientHeight != lastheight) {
resize(canvas);
}
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(1,1,1,0.3)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const scene = new Scene(ctx);
scene.viewpoint = observer.pos;
scene.viewz = 1.0; // 1 metre off ground
catseyes(scene, lanes[0]-1.5, '#811');
catseyes(scene, lanes[0]+1.5, '#666');
catseyes(scene, lanes[1]+1.5, '#666');
catseyes(scene, lanes[2]+1.5, '#861');
streetlights(scene, lanes[0]-3.5);
streetlights(scene, -0.5);
streetlights(scene, -(lanes[0]-3.5));
streetlights(scene, 0.5);
for (car of cars) {
car.render(scene);
}
for (plane of planes) {
plane.render(scene);
}
const label = document.getElementById('clickformusic');
label.style.top = `${canvas.height/2 - 100}px`;
label.style.left = `${canvas.width/2 - label.clientWidth/2}px`;
const time = Date.now() - started;
if (time < musiclabeltime) {
const col = (musiclabeltime-time) * (200/musiclabeltime);
label.style.color = `rgb(${col}, ${col}, ${col})`;
} else {
label.style.display = 'none';
}
scene.render();
window.requestAnimationFrame(render);
}
function catseyes(scene, x, col) {
const numlines = Math.floor(observer.pos.y / catseyedist);
const starty = (numlines-1)*catseyedist;
for (let y = starty; y < observer.pos.y+100; y += catseyedist) {
scene.drawCircle(new V2d(x-0.02, y), 0.01, 0.015, col, {no_occlude: true});
scene.drawCircle(new V2d(x+0.02, y), 0.01, 0.015, col, {no_occlude: true});
}
}
function streetlights(scene, x) {
const numlines = Math.floor(observer.pos.y / streetlightdist);
const starty = (numlines-1)*streetlightdist;
for (let y = starty; y < observer.pos.y+3000; y += streetlightdist) {
const col = `rgb(255,255,${150+((99*x)+(31*y))%101})`;
scene.drawCircle(new V2d(x, y), 5, 0.2, col);
}
}
function step() {
const now = Date.now();
if (!laststep) {
laststep = now;
return;
}
const dt = (now - laststep) / 1000;
laststep = now;
for (car of cars) {
car.step(dt);
}
for (plane of planes) {
plane.step(dt);
}
// delete planes that are behind the viewer
planes = planes.filter((p) => p.pos.y > observer.pos.y);
// add new planes occasionally
if (Math.random() < 0.00005 && planes.length < 4) {
let plane = new Plane();
plane.pos = new V2d(observer.pos.x-10000-Math.random()*2000, observer.pos.y+20000+Math.random()*5000);
plane.vel = new V2d(30+Math.random()*20, Math.random()*10-5);
if (Math.random() < 0.5) {
plane.pos = new V2d(-plane.pos.x, plane.pos.y);
plane.vel.x = -plane.vel.x;
}
plane.z = 3000+Math.random()*2000;
planes.push(plane);
}
}
function resize(canvas) {
lastwidth = canvas.width = canvas.clientWidth;
lastheight = canvas.height = canvas.clientHeight;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(0,0,0)';
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fill();
}
init();
let playing = false;
document.getElementById('canvas').onclick = function() {
if (playing) document.getElementById('audio').pause();
else document.getElementById('audio').play();
playing = !playing;
}
================================================
FILE: js/plane.js
================================================
function Plane() {
// physics
this.pos = new V2d(0,0);
this.vel = new V2d(25,15);
this.z = 3000;
this.t = 0;
this.period = 1.2+Math.random()*0.2;
};
Plane.prototype.render = function(scene) {
// red when moving left, green when moving right
const col = this.vel.x > 0 ? '#585' : '#855';
if (this.t > this.period/2)
scene.drawCircle(this.pos, this.z, 12, col);
};
Plane.prototype.step = function(dt) {
this.pos = this.pos.add(this.vel.mul(dt));
this.t += dt;
while (this.t > this.period)
this.t -= this.period;
};
================================================
FILE: js/scene.js
================================================
const fullcircle = 180*Math.PI;
function Scene(ctx) {
this.ctx = ctx;
this.viewpoint = new V2d(0,0);
this.viewz = 1.0;
this.viewscale = 1200;
this.distscale = 2;
this.circles = [];
}
Scene.prototype.drawCircle = function(pos, z, r, col, opts) {
let circle = this.project(pos, z, r);
if (!circle) return;
circle.col = col;
circle.roady = pos.y;
let ground = this.project(pos, 0, 0);
circle.yground = ground.y;
if (opts && opts.no_occlude) circle.no_occlude = true;
this.circles.push(circle);
};
Scene.prototype.render = function() {
this.ctx.globalCompositeOperation = 'lighter';
this.ctx.globalAlpha = 0.2;
// get the nearest circles first
this.circles.sort((a,b) => {
return a.roady - b.roady;
});
// work out which circles are occluded by the road
let highestroad = this.ctx.canvas.height;
for (circle of this.circles) {
if (circle.yground < highestroad) highestroad = circle.yground;
if (circle.y > highestroad && !circle.no_occlude) circle.occluded = true;
}
for (circle of this.circles) {
if (circle.occluded) continue;
this.ctx.fillStyle = circle.col;
for (let k = 1.0; k > 0; k -= 0.15) {
this.ctx.beginPath();
this.ctx.arc(circle.x, circle.y, circle.r*1.5*k*k, 0, fullcircle);
this.ctx.fill();
}
}
this.ctx.globalCompositeOperation = 'source-over';
this.ctx.globalAlpha = 1.0;
};
Scene.prototype.project = function(pos, z, r) {
const dy = 0.1;
const dx = bend(this.viewpoint.y+dy) - bend(this.viewpoint.y);
const theta = Math.atan2(dx,dy);
const posrel1 = pos.add(new V2d(bend(pos.y),0)).sub(this.viewpoint.add(new V2d(bend(this.viewpoint.y),0)));
const posrel = posrel1.rotate(theta);
z = z + terrain(pos.y);
const zrel = z - (this.viewz + terrain(this.viewpoint.y));
// things behind the viewer are not visible
if (posrel.y <= 0) return null;
const dist = this.distscale * Math.sqrt(posrel.y*posrel.y + zrel*zrel);
// things too close are not visible
if (dist < 0.5) return null;
const scaleratio = this.viewscale * this.ctx.canvas.width / 640;
const screenx = (this.ctx.canvas.width/2) + scaleratio * (posrel.x / dist);
const screeny = (this.ctx.canvas.height/2) - scaleratio * (zrel / dist);
const screenr = scaleratio * (r / dist); // px
return {
x: screenx,
y: screeny,
r: screenr,
};
};
function terrain(y) {
return 10*Math.sin(y/1000) + 5*Math.cos(y/527) + 2*Math.sin(y/219);
}
function bend(y) {
return 200*Math.sin(y/909) + 51*Math.cos(y/517) + 23*Math.sin(y/201);
}
================================================
FILE: js/vector.js
================================================
function V2d(x,y) {
this.x = x;
this.y = y;
}
V2d.prototype.add = function(v) {
return new V2d(this.x + v.x, this.y + v.y);
};
V2d.prototype.sub = function(v) {
return new V2d(this.x - v.x, this.y - v.y);
};
V2d.prototype.mul = function(k) {
return new V2d(this.x * k, this.y * k);
};
V2d.prototype.length = function() {
return Math.sqrt(this.x*this.x + this.y*this.y);
};
V2d.prototype.angle = function() {
return Math.atan2(this.x, this.y);
};
V2d.prototype.rotate = function(theta) {
return new V2d(Math.cos(theta)*this.x - Math.sin(theta)*this.y, Math.sin(theta)*this.x + Math.cos(theta)*this.y);
};
gitextract_olwi0mbm/
├── index.html
└── js/
├── car.js
├── nightdrive.js
├── plane.js
├── scene.js
└── vector.js
SYMBOL INDEX (13 symbols across 5 files)
FILE: js/car.js
function Car (line 1) | function Car() {
function colour (line 154) | function colour(r1,r2, g1,g2, b1,b2) {
FILE: js/nightdrive.js
function init (line 16) | function init() {
function render (line 49) | function render() {
function catseyes (line 97) | function catseyes(scene, x, col) {
function streetlights (line 106) | function streetlights(scene, x) {
function step (line 115) | function step() {
function resize (line 147) | function resize(canvas) {
FILE: js/plane.js
function Plane (line 1) | function Plane() {
FILE: js/scene.js
function Scene (line 3) | function Scene(ctx) {
function terrain (line 88) | function terrain(y) {
function bend (line 92) | function bend(y) {
FILE: js/vector.js
function V2d (line 1) | function V2d(x,y) {
Condensed preview — 6 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (16K chars).
[
{
"path": "index.html",
"chars": 746,
"preview": "<!doctype html>\n<html>\n<head>\n<title>Nightdrive</title>\n<style type=\"text/css\">\nhtml,body {\n width: 100vw;\n height"
},
{
"path": "js/car.js",
"chars": 6047,
"preview": "function Car() {\n // physics\n this.pos = new V2d(0,1);\n this.vel = new V2d(0,0.005);\n\n // lights\n this.br"
},
{
"path": "js/nightdrive.js",
"chars": 4720,
"preview": "let laststep = null;\nlet observer;\nlet cars = [];\nlet planes = [];\n\nlet lastwidth;\nlet lastheight;\n\nconst lanes = [-10,-"
},
{
"path": "js/plane.js",
"chars": 581,
"preview": "function Plane() {\n // physics\n this.pos = new V2d(0,0);\n this.vel = new V2d(25,15);\n this.z = 3000;\n thi"
},
{
"path": "js/scene.js",
"chars": 2710,
"preview": "const fullcircle = 180*Math.PI;\n\nfunction Scene(ctx) {\n this.ctx = ctx;\n this.viewpoint = new V2d(0,0);\n this.v"
},
{
"path": "js/vector.js",
"chars": 642,
"preview": "function V2d(x,y) {\n this.x = x;\n this.y = y;\n}\n\nV2d.prototype.add = function(v) {\n return new V2d(this.x + v.x"
}
]
About this extraction
This page contains the full source code of the jes/nightdrive GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 6 files (15.1 KB), approximately 4.7k tokens, and a symbol index with 13 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.