Files
my_site/vue/public/race/draw.js
yoyuzh d669738967 init
2026-02-27 14:29:05 +08:00

472 lines
18 KiB
JavaScript

'use strict';
let cubeMesh, quadMesh, shadowMesh, cylinderMesh, carMesh, carWheel;
const bleedPixels = 8;
const WHITE = rgb();
const BLACK = rgb(0,0,0);
const RED = rgb(1,0,0);
const ORANGE = rgb(1,.5,0);
const YELLOW = rgb(1,1,0);
const GREEN = rgb(0,1,0);
const CYAN = rgb(0,1,1);
const BLUE = rgb(0,0,1);
const PURPLE = rgb(.5,0,1);
const MAGENTA= rgb(1,0,1);
const GRAY = rgb(.5,.5,.5);
let spriteList;
let testGameSprite;
///////////////////////////////////////////////////////////////////////////////
function initSprites()
{
//spriteList
//(tilePos, size=1e3, sizeRandomness=0, windScale=0, collideSize=60)
spriteList = {};
// trees
spriteList.tree_palm = new GameSprite(vec3(0,1),1500,.2,.1,.04);
spriteList.tree_palm.trackFace = 1;
spriteList.tree_oak = new GameSprite(vec3(1,1),2e3,.5,.06,.1);
spriteList.tree_stump = new GameSprite(vec3(2,1),1e3,.6,.04);
spriteList.tree_dead = new GameSprite(vec3(3,1),1e3,.3,.1,.06);
spriteList.tree_pink = new GameSprite(vec3(4,1),1500,.3,.1,.04);
spriteList.tree_pink.trackFace = 1;
spriteList.tree_bush = new GameSprite(vec3(5,1),1e3,.5,.1,.06);
spriteList.tree_fall = new GameSprite(vec3(6,1),1500,.3,.1,.1);
//TB(spriteList.tree_flower = new GameSprite(vec3(7,1),2e3,.3,.05,200));
spriteList.tree_snow = new GameSprite(vec3(4,3),1300,.3,.06,.1)
spriteList.tree_yellow = new GameSprite(vec3(5,3),1e3,.3,.06,.1)
spriteList.tree_huge = new GameSprite(vec3(3,1),1e4,.5,.1,.1)
spriteList.tree_huge.colorHSL = vec3(.8, 0, .5);
spriteList.tree_huge.shadowScale = 0;
// smaller tree shadows
spriteList.tree_palm.shadowScale =
spriteList.tree_oak.shadowScale =
spriteList.tree_stump.shadowScale =
spriteList.tree_dead.shadowScale =
spriteList.tree_pink.shadowScale =
spriteList.tree_bush.shadowScale =
spriteList.tree_fall.shadowScale =
spriteList.tree_snow.shadowScale =
spriteList.tree_yellow.shadowScale = .7;
// grass and flowers
spriteList.grass_plain = new GameSprite(vec3(0,3),500,.5,1);
spriteList.grass_plain.colorHSL = vec3(.3, .4, .5);
spriteList.grass_dead = new GameSprite(vec3(0,3),600,.3,1);
spriteList.grass_dead.colorHSL = vec3(.13, .6, .7);
spriteList.grass_flower1 = new GameSprite(vec3(1,3),500,.3,1);
spriteList.grass_flower2 = new GameSprite(vec3(2,3),500,.3,1);
spriteList.grass_flower3 = new GameSprite(vec3(3,3),500,.3,1);
spriteList.grass_red = new GameSprite(vec3(0,3),700,.3,1)
spriteList.grass_red.colorHSL = vec3(0, .8, .5);
spriteList.grass_snow = new GameSprite(vec3(0,3),300,.5,1)
spriteList.grass_snow.colorHSL = vec3(.4, 1, .9);
spriteList.grass_large = new GameSprite(vec3(0,3),1e3,.5,1);
spriteList.grass_large.colorHSL = vec3(.4, .4, .5);
//spriteList.grass_huge = new GameSprite(vec3(0,3),1e4,.6,.5,5e3);
//spriteList.grass_huge.colorHSL = vec3(.8, .5, .5);
//spriteList.grass_huge.hueRandomness = .2;
// billboards
spriteList.billboards = [];
const PB = (s)=>spriteList.billboards.push(s);
PB(spriteList.sign_opGames = new GameSprite(vec3(5,2),600,0,.02,.5,0));
PB(spriteList.sign_js13k = new GameSprite(vec3(0,2),600,0,.02,1,0));
PB(spriteList.sign_zzfx = new GameSprite(vec3(1,2),500,0,.02,.5,0));
PB(spriteList.sign_avalanche = new GameSprite(vec3(7,2),600,0,.02,1,0));
PB(spriteList.sign_github = new GameSprite(vec3(2,2),750,0,.02,.5,0));
//PB(spriteList.sign_littlejs = new GameSprite(vec3(4,2),600,0,.02,1,0));
spriteList.sign_frankForce = new GameSprite(vec3(3,2),500,0,.02,1,0);
//PB(spriteList.sign_dwitter = new GameSprite(vec3(6,2),550,0,.02,1,0));
// signs
spriteList.sign_turn = new GameSprite(vec3(0,5),500,0,.05,.5);
spriteList.sign_turn.trackFace = 1; // signs face track
//spriteList.sign_curve = new GameSprite(vec3(1,5),500,0,.05,.5);
//spriteList.sign_curve.trackFace = 1; // signs face track
//spriteList.sign_warning = new GameSprite(vec3(2,5),500,0,.05,1,0);
//spriteList.sign_speed = new GameSprite(vec3(4,5),500,0,.05,50,0);
//spriteList.sign_interstate = new GameSprite(vec3(5,5),500,0,.05,50,0);
// rocks
spriteList.rock_tall = new GameSprite(vec3(1,4),1e3,.3,0,.6,0);
spriteList.rock_big = new GameSprite(vec3(2,4),800,.2,0,.57,0);
spriteList.rock_huge = new GameSprite(vec3(1,4),5e3,.7,0,.6,0);
spriteList.rock_huge.shadowScale = 0;
spriteList.rock_huge.colorHSL = vec3(.08, 1, .8);
spriteList.rock_huge.hueRandomness = .01;
spriteList.rock_huge2 = new GameSprite(vec3(2,4),8e3,.5,0,.25,0);
spriteList.rock_huge2.shadowScale = 0;
spriteList.rock_huge2.colorHSL = vec3(.05, 1, .8);
spriteList.rock_huge2.hueRandomness = .01;
spriteList.rock_huge3 = new GameSprite(vec3(2,4),8e3,.7,0,.5,0);
spriteList.rock_huge3.shadowScale = 0;
spriteList.rock_huge3.colorHSL = vec3(.05, 1, .8);
spriteList.rock_huge3.hueRandomness = .01;
spriteList.rock_weird = new GameSprite(vec3(2,4),5e3,.5,0,1,0);
spriteList.rock_weird.shadowScale = 0;
spriteList.rock_weird.colorHSL = vec3(.8, 1, .8);
spriteList.rock_weird.hueRandomness = .2;
spriteList.rock_weird2 = new GameSprite(vec3(1,4),1e3,.5,0,.5,0);
spriteList.rock_weird2.colorHSL = vec3(0, 0, .2);
spriteList.tunnel1 = new GameSprite(vec3(6,4),1e4,.0,0,0,0);
spriteList.tunnel1.shadowScale = 0;
spriteList.tunnel1.colorHSL = vec3(.05, 1, .8);
spriteList.tunnel1.tunnelArch = 1;
spriteList.tunnel2 = new GameSprite(vec3(7,4),5e3,0,0,0,0);
spriteList.tunnel2.shadowScale = 0;
spriteList.tunnel2.colorHSL = vec3(0, 0, .1);
spriteList.tunnel2.tunnelLong = 1;
spriteList.tunnel2Front = new GameSprite(vec3(7,4),5e3,0,0,0,0);
spriteList.tunnel2Front.shadowScale = 0;
spriteList.tunnel2Front.colorHSL = vec3(0,0,.8);
//spriteList.tunnel2_rock = new GameSprite(vec3(6,6),1e4,.2,0,.5,0);
//spriteList.tunnel2_rock.colorHSL = vec3(.15, .5, .8);
// hazards
spriteList.hazard_rocks = new GameSprite(vec3(3,4),600,.2,0,.9);
spriteList.hazard_rocks.shadowScale = 0;
spriteList.hazard_rocks.isBump = 1;
spriteList.hazard_rocks.spriteYOffset = -.02;
spriteList.hazard_sand = new GameSprite(vec3(4,4),600,.2,0,.9);
spriteList.hazard_sand.shadowScale = 0;
spriteList.hazard_sand.isSlow = 1;
spriteList.hazard_sand.spriteYOffset = -.02;
//spriteList.hazard_snow = new GameSprite(vec3(6,6),500,.1,0,300,0);
//spriteList.hazard_snow.isSlow = 1;
// special sprites
spriteList.water = new GameSprite(vec3(5,4),6e3,.5,1);
spriteList.water.shadowScale = 0;
spriteList.sign_start = new GameSprite(vec3(1,6),2300,0,.01,0,0);
spriteList.sign_start.shadowScale = 0;
spriteList.sign_goal = new GameSprite(vec3(0,6),2300,0,.01,0,0);
spriteList.sign_goal.shadowScale = 0;
spriteList.sign_checkpoint1 = new GameSprite(vec3(6,0),1e3,0,.01,0,0);
spriteList.sign_checkpoint1.shadowScale = 0;
spriteList.sign_checkpoint2 = new GameSprite(vec3(7,0),1e3,0,.01,0,0);
spriteList.sign_checkpoint2.shadowScale = 0;
spriteList.telephonePole = new GameSprite(vec3(0,4),1800,0,0,.03,0);
//spriteList.parts_girder = new GameSprite(vec3(0,6),500,0,.05,30,0);
spriteList.telephonePole.shadowScale = .3;
spriteList.grave_stone = new GameSprite(vec3(2,6),500,.3,.05,.5,0);
spriteList.grave_stone.lightnessRandomness = .5;
spriteList.light_tunnel = new GameSprite(vec3(0,0),200,0,0,0,0);
spriteList.light_tunnel.shadowScale = 0;
// horizon sprites
spriteList.horizon_city = new GameSprite(vec3(3,6),0,0,0,0,1);
spriteList.horizon_city.hueRandomness =
spriteList.horizon_city.lightnessRandomness = .15;
spriteList.horizon_city.colorHSL = vec3(1); // vary color
spriteList.horizon_islands = new GameSprite(vec3(7,6));
spriteList.horizon_islands.colorHSL = vec3(.25, .5, .6);
spriteList.horizon_islands.canMirror = 0;
spriteList.horizon_redMountains = new GameSprite(vec3(7,6));
spriteList.horizon_redMountains.colorHSL = vec3(.05, .7, .7);
spriteList.horizon_redMountains.canMirror = 0;
spriteList.horizon_brownMountains = new GameSprite(vec3(7,6));
spriteList.horizon_brownMountains.colorHSL = vec3(.1, .5, .6);
spriteList.horizon_brownMountains.canMirror = 0;
spriteList.horizon_smallMountains = new GameSprite(vec3(6,6));
spriteList.horizon_smallMountains.colorHSL = vec3(.1, .5, .6);
spriteList.horizon_smallMountains.canMirror = 0;
spriteList.horizon_desert = new GameSprite(vec3(6,6));
spriteList.horizon_desert.colorHSL = vec3(.15, .5, .8);
spriteList.horizon_desert.canMirror = 0;
spriteList.horizon_snow = new GameSprite(vec3(7,6));
spriteList.horizon_snow.colorHSL = vec3(0,0,1);
spriteList.horizon_snow.canMirror = 0;
spriteList.horizon_graveyard = new GameSprite(vec3(6,6));
spriteList.horizon_graveyard.colorHSL = vec3(.2, .4, .8);
spriteList.horizon_graveyard.canMirror = 0;
spriteList.horizon_weird = new GameSprite(vec3(7,6));
spriteList.horizon_weird.colorHSL = vec3(.7, .5, .6);
spriteList.horizon_weird.canMirror = 0;
if (!js13kBuildLevel2)
{
spriteList.horizon_mountains = new GameSprite(vec3(7,6));
spriteList.horizon_mountains.colorHSL = vec3(0, 0, .7);
spriteList.horizon_mountains.canMirror = 0;
}
// more sprites
spriteList.circle = new GameSprite(vec3());
spriteList.dot = new GameSprite(vec3(1,0));
spriteList.carShadow = new GameSprite(vec3(2,0));
spriteList.carLicense = new GameSprite(vec3(3,0));
spriteList.carNumber = new GameSprite(vec3(4,0));
}
// a sprite that can be placed on the track
class GameSprite
{
constructor(tilePos, size=1e3, sizeRandomness=0, windScale=0, collideScale=0, canMirror=1)
{
this.spriteTile = vec3(
(tilePos.x * generativeTileSize + bleedPixels) / generativeCanvasSize,
(tilePos.y * generativeTileSize + bleedPixels) / generativeCanvasSize,
);
this.size = size;
this.sizeRandomness = sizeRandomness;
this.windScale = windScale;
this.collideScale = collideScale;
this.canMirror = canMirror; // allow mirroring
this.trackFace = 0; // face track if close
this.spriteYOffset = 0; // how much to offset the sprite from the ground
this.shadowScale = 1.2;
// color
this.colorHSL = vec3(0,0,1);
this.hueRandomness = .05;
this.lightnessRandomness = .01;
}
getRandomSpriteColor()
{
const c = this.colorHSL.copy();
c.x += random.floatSign(this.hueRandomness);
c.z += random.floatSign(this.lightnessRandomness);
return c.getHSLColor();
}
getRandomSpriteScale() { return 1+random.floatSign(this.sizeRandomness); }
randomize()
{
this.colorHSL.x = random.float(-.1,.1);
this.colorHSL.y = clamp(this.colorHSL.y+random.float(-.1,.1));
this.colorHSL.z = clamp(this.colorHSL.z+random.float(-.1,.1));
this.hueRandomness = .05;
this.lightnessRandomness = .01;
}
}
///////////////////////////////////////////////////////////////////////////////
const getAspect =()=> mainCanvasSize.x/mainCanvasSize.y;
function drawInit()
{
{
// cube
const points = [vec3(-1,1),vec3(1,1),vec3(1,-1),vec3(-1,-1)];
cubeMesh = new Mesh().buildExtrude(points);
}
{
// quad
const points1 = [vec3(-1,1),vec3(1,1),vec3(-1,-1),vec3(1,-1)];
const uvs1 = points1.map(p=>p.multiply(vec3(.5,-.5,.5)).add(vec3(.5)));
quadMesh = new Mesh(points1, points1.map(p=>vec3(0,0,1)), uvs1);
shadowMesh = quadMesh.transform(0,vec3(PI/2,0));
}
{
// cylinder
const points = [];
const sides = 12;
for(let i=sides; i--;)
{
const a = i/sides*PI*2;
points.push(vec3(1,0).rotateZ(a));
}
cylinderMesh = new Mesh().buildExtrude(points);
}
{
// car bottom
const points =
[
vec3(-1,.5),
vec3(-.7,.4),
vec3(-.2,.5),
vec3(.1,.5),
vec3(1,.2),
vec3(1,.2),
vec3(1,0),
vec3(-1,0),
]
carMesh = new Mesh().buildExtrude(points,.5);
carMesh = carMesh.transform(0,vec3(0,-PI/2));
carWheel = cylinderMesh.transform(0,vec3(0,-PI/2));
}
}
///////////////////////////////////////////////////////////////////////////////
class Mesh
{
constructor(points, normals, uvs)
{
this.points = points;
this.normals = normals;
this.uvs = uvs;
}
render(transform, color)
{
glPushVerts(this.points, this.normals, color);
glRender(transform);
}
renderTile(transform, color, tile)
{
//ASSERT(tile instanceof SpriteTile);
const uvs = this.uvs.map(uv=>(vec3(spriteSize-spriteSize*uv.x+tile.x,uv.y*spriteSize+tile.y)));
// todo, figure out why this is backwards
//const uvs = this.uvs.map(uv=>uv.multiply(tile.size).add(tile.pos));
glPushVerts(this.points, this.normals, color, uvs);
glRender(transform);
}
buildExtrude(facePoints, size=1)
{
// convert list of 2d points into a 3d shape
const points = [], normals = [];
const vertCount = facePoints.length + 2;
for (let k=2; k--;)
for (let i=vertCount; i--;)
{
// build top and bottom of mesh
const j = clamp(i-1, 0, vertCount-3); // degenerate tri at ends
const h = j>>1;
let m = j%2 == vertCount%2 ? h : vertCount-3-h;
if (!k) // hack to fix glitch in mesh due to concave shape
m = mod(vertCount+2-m, facePoints.length);
const point = facePoints[m].copy();
point.z = k?size:-size;
points.push(point);
normals.push(vec3(0,0,point.z));
}
for (let i = facePoints.length; i--;)
{
// build sides of mesh
const point1 = facePoints[i];
const point2 = facePoints[(i+1)%facePoints.length];
const s = vec3(0,0,size);
const pointA = point1.add(s);
const pointB = point2.add(s);
const pointC = point1.subtract(s);
const pointD = point2.subtract(s);
const sidePoints = [pointA, pointA, pointB, pointC, pointD, pointD];
const normal = pointC.subtract(pointD).cross(pointA.subtract(pointC)).normalize();
for (const p of sidePoints)
{
points.push(p);
normals.push(normal);
}
}
return new Mesh(points, normals);
}
transform(pos, rot, scale)
{
const m = buildMatrix(pos, rot, scale);
const m2 = buildMatrix(0, rot);
return new Mesh(
this.points.map(p=>p.transform(m)),
this.normals.map(p=>p.transform(m2)),
this.uvs
);
}
/*combine(mesh, pos, rot, scale)
{
const m = buildMatrix(pos, rot, scale);
const m2 = buildMatrix(0, rot);
this.points.push(...mesh.points.map(p=>p.transform(m)));
this.normals && this.normals.push(...mesh.normals.map(p=>p.transform(m2)));
this.uvs && this.uvs.push(...mesh.uvs);
return this;
}*/
}
///////////////////////////////////////////////////////////////////////////////
function pushGradient(pos, size, color, color2)
{
const mesh = quadMesh;
const points = mesh.points.map(p=>p.multiply(size).addSelf(pos));
const colors = [color, color, color2, color2];
glPushColoredVerts(points, colors);
}
function pushSprite(pos, size, color, tile, skew=0)
{
const mesh = quadMesh;
const points = mesh.points.map(p=>vec3(p.x*abs(size.x)+pos.x, p.y*abs(size.y)+pos.y,pos.z));
// apply skew
const o = skew*size.y;
points[0].x += o;
points[1].x += o;
// apply texture
if (tile)
{
//ASSERT(tile instanceof SpriteTile);
let tilePosX = tile.x;
let tilePosY = tile.y;
let tileSizeX = spriteSize;
let tileSizeY = spriteSize;
if (size.x < 0)
tilePosX -= tileSizeX *= -1;
if (size.y < 0)
tilePosY -= tileSizeY *= -1;
const uvs = mesh.uvs.map(uv=>
vec3(uv.x*tileSizeX+tilePosX, uv.y*tileSizeY+tilePosY));
glPushVertsCapped(points, 0, color, uvs);
}
else
glPushVertsCapped(points, 0, color);
}
function pushShadow(pos, xSize, zSize)
{
if (optimizedCulling && pos.z > 2e4)
return; // cull far shadows
const color = rgb(0,0,0,.7)
const tile = spriteList.dot.spriteTile;
const mesh = shadowMesh;
const points = mesh.points.map(p=>vec3(p.x*xSize+pos.x,pos.y,p.z*zSize+pos.z));
const uvs = mesh.uvs.map(uv=>
vec3(uv.x*spriteSize+tile.x, uv.y*spriteSize+tile.y));
glPushVertsCapped(points, 0, color, uvs);
}
///////////////////////////////////////////////////////////////////////////////
// Fullscreen mode
/** Returns true if fullscreen mode is active
* @return {Boolean}
* @memberof Draw */
function isFullscreen() { return !!document.fullscreenElement; }
/** Toggle fullsceen mode
* @memberof Draw */
function toggleFullscreen()
{
const element = document.body;
if (isFullscreen())
{
if (document.exitFullscreen)
document.exitFullscreen();
}
else if (element.requestFullscreen)
element.requestFullscreen();
else if (element.webkitRequestFullscreen)
element.webkitRequestFullscreen();
else if (element.mozRequestFullScreen)
element.mozRequestFullScreen();
}