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

274 lines
10 KiB
JavaScript

'use strict';
const testTrackBillboards=0;
// build the road with procedural generation
function buildTrack()
{
// set random seed & time
random.setSeed(trackSeed);
track = [];
let sectionXEndDistance = 0;
let sectionYEndDistance = 0;
let sectionTurn = 0;
let noisePos = random.int(1e5);
let sectionBumpFrequency = 0;
let sectionBumpScale = 1;
let currentNoiseFrequency = 0;
let currentNoiseScale = 1;
let turn = 0;
// generate the road
const trackEnd = levelGoal*checkpointTrackSegments;
const roadTransitionRange = testQuick?min(checkpointTrackSegments,500):500;
for(let i=0; i < trackEnd + 5e4; ++i)
{
const levelFloat = i/checkpointTrackSegments;
const level = levelFloat|0;
const levelInfo = getLevelInfo(level);
const levelInfoLast = getLevelInfo(levelFloat-1);
const levelLerpPercent = percent(i%checkpointTrackSegments, 0, roadTransitionRange);
if (js13kBuild && i==31496)
random.setSeed(7); // mess with seed to randomize jungle
const roadGenWidth = laneWidth/2*lerp(levelLerpPercent, levelInfoLast.laneCount, levelInfo.laneCount);
let height = 0;
let width = roadGenWidth;
const startOfTrack = !level && i < 400;
const checkpointSegment = i%checkpointTrackSegments;
const levelBetweenRange = 100;
let isBetweenLevels = checkpointSegment < levelBetweenRange ||
checkpointSegment > checkpointTrackSegments - levelBetweenRange;
isBetweenLevels |= startOfTrack; // start of track
//const nextCheckpoint = (level+1)*checkpointTrackSegments;
if (isBetweenLevels)
{
// transition at start or end of level
sectionXEndDistance = sectionYEndDistance = sectionTurn = 0;
}
else
{
// turns
const turnChance = levelInfo.turnChance; // chance of turn
const turnMin = levelInfo.turnMin; // min turn
const turnMax = levelInfo.turnMax; // max turn
const sectionDistanceMin = 100;
const sectionDistanceMax = 400;
if (sectionXEndDistance-- < 0)
{
// pick random section distance
sectionXEndDistance = random.int(sectionDistanceMin,sectionDistanceMax);
sectionTurn = random.bool(turnChance) ? random.floatSign(turnMin,turnMax) : 0;
}
// bumps
const bumpChance = levelInfo.bumpChance; // chance of bump
const bumpFreqMin = levelInfo.bumpFreqMin; // no bumps
const bumpFreqMax = levelInfo.bumpFreqMax; // raipd bumps
const bumpScaleMin = levelInfo.bumpScaleMin; // small rapid bumps
const bumpScaleMax = levelInfo.bumpScaleMax; // large hills
if (sectionYEndDistance-- < 0)
{
// pick random section distance
sectionYEndDistance = random.int(sectionDistanceMin,sectionDistanceMax);
if (random.bool(bumpChance))
{
sectionBumpFrequency = random.float(bumpFreqMin,bumpFreqMax);
sectionBumpScale = random.float(bumpScaleMin,bumpScaleMax);
}
else
{
sectionBumpFrequency = 0;
sectionBumpScale = bumpScaleMin;
}
}
}
if (i > trackEnd - 500)
sectionTurn = 0; // no turns at end
turn = lerp(.02,turn, sectionTurn); // smooth out turns
// apply noise to height
const noiseFrequency = currentNoiseFrequency
= lerp(.01, currentNoiseFrequency, sectionBumpFrequency);
const noiseSize = currentNoiseScale
= lerp(.01, currentNoiseScale, sectionBumpScale);
//noiseFrequency = 1; noiseSize = 50;
if (currentNoiseFrequency)
noisePos += noiseFrequency/noiseSize;
const noiseConstant = 20;
height = noise1D(noisePos)*noiseConstant*noiseSize;
//turn = .7; height = 0;
//turn = Math.sin(i/100)*.7;
//height = noise1D((i-50)/99)*2700;turn =0; // jumps test
// create track segment
const o = vec3(turn, height, i*trackSegmentLength);
track[i] = new TrackSegment(i, o, width);
}
// second pass
let hazardWait = 0;
let tunnelOn = 0;
let tunnelTime = 0;
let trackSideChanceScale = 1;
for(let i=0; i < track.length; ++i)
{
// calculate pitch
const iCheckpoint = i%checkpointTrackSegments;
const t = track[i];
const levelInfo = getLevelInfo(t.level);
ASSERT(t.level == levelInfo.level || t.level > levelGoal);
const previous = track[i-1];
if (previous)
{
t.pitch = Math.atan2(previous.offset.y-t.offset.y, trackSegmentLength);
const d = vec3(0,t.offset.y-previous.offset.y, trackSegmentLength);
t.normal = d.cross(vec3(1,0)).normalize();
}
if (!iCheckpoint)
{
// reset level settings
trackSideChanceScale = 1;
}
if (t.sideStreet || i < 50)
{
tunnelOn = 0;
continue; // no objects on side streets
}
// check what kinds of turns are ahead
const lookAheadTurn = 150;
const lookAheadStep = 20;
let leftTurns = 0, rightTurns = 0;
for(let k=0; k<lookAheadTurn; k+=lookAheadStep)
{
const t2 = track[i+k];
if (!t2)
continue;
if (k < lookAheadTurn)
{
const x = t2.offset.x;
if (x > 0) leftTurns = max(leftTurns, x);
else rightTurns = max(rightTurns, -x);
}
}
// spawn road signs
const roadSignRate = 10;
const turnWarning = 0.5;
let signSide;
if (i < levelGoal*checkpointTrackSegments) // end of level
if (rightTurns > turnWarning || leftTurns > turnWarning)
{
// turn
signSide = sign(rightTurns - leftTurns);
if (i%roadSignRate == 0)
t.addSprite(spriteList.sign_turn,signSide*(t.width+500));
}
// todo prevent sprites from spawning near road signs?
//levelInfo.tunnel = spriteList.tunnel2; // test tuns
if (levelInfo.tunnel)
{
const isRockArch = levelInfo.tunnel.tunnelArch;
const isLongTunnel = levelInfo.tunnel.tunnelLong;
if (iCheckpoint > 100 && iCheckpoint < checkpointTrackSegments - 100)
{
const wasOn = tunnelOn;
if (tunnelTime-- < 0)
{
tunnelOn = !tunnelOn;
tunnelTime = tunnelOn?
isRockArch ? 10 : random.int(200,600) :
tunnelTime = random.int(300,600); // longer when off
}
if (tunnelOn)
{
// brighter front of tunnel
const sprite = isLongTunnel && !wasOn ?
spriteList.tunnel2Front : levelInfo.tunnel;
t.addSprite(sprite, 0);
if (isLongTunnel && i%50==0)
{
// lights on top of tunnel
const lightSprite = spriteList.light_tunnel;
const tunnelHeight = 1600;
t.addSprite(lightSprite, 0, tunnelHeight);
}
continue;
}
}
}
else
{
// restart tunnel wait
tunnelOn = tunnelTime = 0;
}
{
// sprites on sides of track
const billboardChance = levelInfo.billboardChance;
const billboardRate = levelInfo.billboardRate;
if (i%billboardRate == 0 && random.bool(billboardChance))
{
// random billboards
const extraScale = levelInfo.billboardScale; // larger in desert
const width = t.width*extraScale;
const count = spriteList.billboards.length;
const billboardSprite = spriteList.billboards[random.int(count)];
const billboardSide = signSide ? -signSide : random.sign();
t.addSprite(billboardSprite,billboardSide*random.float(width+600,width+800),0,extraScale);
continue;
}
if (levelInfo.trackSideSprite)
{
// vary how often side objects spawn
if (random.bool(.001))
{
trackSideChanceScale =
random.bool(.4) ? 1 : // normal to spawn often
random.bool(.1) ? 0 : // small chance of none
random.float(); // random scale
}
// track side objects
const trackSideRate = levelInfo.trackSideRate;
const trackSideChance = levelInfo.trackSideChance;
if (i%trackSideRate == 0 && random.bool(trackSideChance*trackSideChanceScale))
{
const trackSideForce = levelInfo.trackSideForce;
const side = trackSideForce || (i%(trackSideRate*2)<trackSideRate?1:-1);
t.addSprite(levelInfo.trackSideSprite, side*(t.width+random.float(700,1e3)));
continue;
}
}
if (iCheckpoint > 40 && iCheckpoint < checkpointTrackSegments - 40)
if (hazardWait-- < 0 && levelInfo.hazardType && random.bool(levelInfo.hazardChance))
{
// hazards on the ground in road to slow player
const sprite = levelInfo.hazardType;
t.addSprite(sprite,random.floatSign(t.width/.9));
// wait to spawn another hazard
hazardWait = random.float(40,80);
}
}
}
}