'use strict'; const hardAlpha = 1; const generativeTileSize = 512; const generativeCanvasSize = generativeTileSize*8; const fixFirefoxFontBug = 1; // fix firefox not drawing fonts below a min size const spriteSize = (generativeTileSize - 2*bleedPixels) / generativeCanvasSize; function initGenerative() { // create the textures generateTetures(); if (debug) { debugGenerativeCanvasCached = document.createElement('canvas'); debugGenerativeCanvasCached.height = debugGenerativeCanvasCached.width = generativeCanvasSize; const context = debugGenerativeCanvasCached.getContext('2d'); context.drawImage(mainCanvas, 0, 0); } // create webgl texture glContext.bindTexture(gl_TEXTURE_2D, glCreateTexture(mainCanvas)); } function generateTetures() { const context = mainContext; mainCanvas.height = mainCanvas.width = generativeCanvasSize; random.setSeed(13); class Particle { constructor(x, y, vx, vy, accel, sizeStart=.1, sizeEnd=0, c=BLACK, mutateColor=.1) { this.x = x; this.y = y; this.vx = vx; this.vy = vy; this.accel = accel; this.sizeStart = sizeStart; this.sizeEnd = sizeEnd; this.color = c; this.style = this.colorRandom = 0; this.iterations = 50; this.mutateColor = mutateColor; } draw() { const pos = vec3(); for(let i=0; i { //if (p.x < 0 || p.x > 1 || p.y < 0 || p.y > 1) return; if (treeOverflow++ >1e4) { debug && console.log('Tree overflow!') return; } if (w < minBranchSize) { // leaf if (!random.bool(leafChance)) return; for(let i=max(1,leafChance|0);i--;) leafList.push(p); return; } // draw limb const d = vec3(0,-l).rotateZ(a); const p2 = p.add(d); color(hsl(.1,.6,random.float(.1,.2))); rectLine(p.x,p.y,p2.x,p2.y,w); // branch if (b>branchRate*w) { const s1 = random.float(.5,1)*this.branchScale; const s2 = random.float(.5,1)*this.branchScale; treeLimb(p2,w*s1,l*s2,a+bs*(branchAngle+random.floatSign(branchAngleRandomness))); bs *= -1 b = 0; if (random.bool(multiBranch)) treeLimb(p2,w*s1,l*s2,a+bs*(branchAngle+random.floatSign(branchAngleRandomness))); w *= branchLoss; } if (w < startWidth/2 && random.bool(branchDieChance)) // dead branches return; if (this.stump && treeOverflow > 300) return; // continue limb a *= 1-lightPower; treeLimb(p2, w*=nodeScale, l, a+=random.floatSign(crookedness), b+1,bs); } treeLimb(pos,startWidth,startLength); for(const i in shuffle(leafList)) { let leafPos = leafList[i]; const p = i/leafList.length; const leafSize = (1-p)*leafMaxSize; const sat = leafSat + random.floatSign(.2); const brightness = .1+random.float(p,1)*leafBrightness; color(random.mutateColor(hsl(leafHue,sat,brightness),.1)); leafPos = leafPos.add(random.circle(leafOffset)); rect(leafPos.x,leafPos.y,leafSize,leafSize,random.float(2*PI)); if (random.bool(this.flowerChance)) drawFlower(leafPos, 5, random.float(.01,.02), this.flowerColor) } } } { // basic shapes color(WHITE); setupContext(0,0); circle(.5,.5,.45); setupContext(1,0); // radial gradient for(let i=40;i--;) color(hsl(0,0,1,i/300)), circle(.5,.5,.5-i/80); setupContext(2,0); // rectangle gradient for car shadow for(let i=40,a;i--;) { color(hsl(0,0,1,a=i/40)), rect(.5,.5,.5-a/3,.9-a/3); } setupContext(3,0); drawLicensePlate(); setupContext(4,0); text('30',.5,.6,1,1,.04,undefined,undefined,900); setupContext(6,0); drawCheckpointSign(1); setupContext(7,0); drawCheckpointSign(-1); // plants setupContext(0,1); drawPalmTree(); setupContext(1,1); { // green tree random.setSeed(13); const t = new Tree; t.draw(); } setupContext(2,1); { // dead tree stump random.setSeed(192); const t = new Tree; t.startWidth = .1; t.branchRate = 300; t.leafChance = 0; t.branchAngle = 1.2; t.branchAngleRandomness = .2; t.branchDieChance = .03; t.lightPower = .05 t.crookedness = .1; t.draw(); } setupContext(3,1); { // dead tree random.setSeed(131); const t = new Tree; t.leafChance = 0; t.startWidth = .08; t.branchAngleRandomness = .5; t.branchAngle = 1.5; t.crookedness = .05; t.lightPower = .01; t.draw(); } setupContext(4,1); { // pink tree random.setSeed(400); const t = new Tree; t.crookedness = .1; t.branchAngle = 1.5; t.branchAngleRandomness = .1; t.leafHue = 0; t.leafSat = .7; t.leafBrightness = .7; t.lightPower = .02; t.flowerChance = .05; t.flowerColor = WHITE; t.draw(); } setupContext(5,1); { // low bush random.setSeed(1333); const t = new Tree; t.multiBranch = 1; t.branchRate = 800; t.startWidth = .04; t.minBranchSize = .005; t.branchAngle = 1.5; t.crookedness = .1; t.branchAngleRandomness = .2; t.leafChance = 4; t.leafOffset = .04; t.leafHue = .2; t.lightPower = .002; t.flowerChance = .01; t.draw(); } setupContext(6,1); { // fall tree random.setSeed(293); const t = new Tree; t.startWidth = .07; t.crookedness = .05; t.leafHue = .08; t.leafBrightness= .6; t.leafSat = 1; t.draw(); } /*setupContext(7,1); { // tree with flowers random.setSeed(192); const t = new Tree; t.startWidth = .07; t.branchRate = 700; t.leafChance = .5; t.multiBranch = 1; t.branchAngle = 1.2; t.branchAngleRandomness = .2; t.leafHue = .35; t.flowerChance = .1; t.draw(); }*/ // signs random.setSeed(13); setupContext(0,2); drawJS13kSign(); setupContext(1,2); drawZZFXSign(); setupContext(2,2); drawGitHubSign(); setupContext(3,2); //drawGenericSign('GAME BY FRANK FORCE',.25,BLACK,WHITE); drawDoubleLineSign('爱GZY','大神',BLACK,0,.42,.2); //drawGenericSign('Frank Force Games',.3,undefined,undefined,'monospace'); setupContext(4,2); //drawDoubleLineSign('DRIVE','SAFELY',hsl(.35,1,.2)); //drawLittleJSSign(); setupContext(5,2); drawOPSign(); //drawGenericSign('VOTE',.5,WHITE,hsl(0,.9,.4),hsl(.6,.9,.3),0,'impact'); //setupContext(6,2); //drawDwitterSign(); setupContext(7,2); drawAvalancheSign(); // grass,flowers, more trees setupContext(0,3); drawGrass(); setupContext(1,3); drawGrass(.23,.5,.3,.3); setupContext(2,3); drawGrass(.35,.5,.3,.3,YELLOW); setupContext(3,3); random.setSeed(5); drawGrass(.3,.5,.3,.64,BLUE); setupContext(4,3); { // snowy tree random.setSeed(5); const t = new Tree; t.leafHue = .5; t.leafBrightness = 1.2; t.leafMaxSize = .04; t.branchRate = 300; t.lightPower = 0.01; t.flowerChance = .01; t.flowerColor = BLUE; t.draw(); } setupContext(5,3); { // yellow tree random.setSeed(9); const t = new Tree; t.leafHue = .13; t.leafBrightness = .5; t.leafSat = .9; t.multiBranch = 1; t.branchRate = 500; t.minBranchSize = .008; t.startWidth = .1; t.draw(); } // track objects setupContext(0,4); { // telephone pole const c = hsl(.08,.4,.2) messyRect(.5,.5,.04,1,0,c); messyRect(.5,.06,.03,.2,PI/2,c); messyRect(.5,.12,.03,.2,PI/2,c); } setupContext(1,4); drawRock();// tall rock setupContext(2,4); { // big rocks drawRock(.55,.08,.53,.015,.6,.7, .5); drawRock(.4,.1,.45,.012,.3,.7); drawRock(.6,.2,.2,.005,.4,.6); } setupContext(3,4); for(let i=39; i--;) // small rocks { const y = .02; const z = random.float(.002,.015); drawRock(.5+random.floatSign(.45),random.float(.005,.02),random.float(.02,.05),.005,.4,.3,.3,y,z); } setupContext(4,4); for(let i=199; i--;) // sand { const x = .5+random.floatSign(.45) const y = .03; const z = random.float(.003,.006); const cHSL = vec3(.13,.3,.7); drawRock(x,random.float(.03),random.float(.05),.005,.4,.3,.3,y,z,500,cHSL,.4); } setupContext(5,4); for(let i=99; i--;) // water { const p = i/99; const w = .01; const x = lerp(p,.05,.95); const h = lerp(p,.02,.13); const cHSL = vec3(.53,1,1); drawRock(x,w,h,.01,.3,.6,.5,0,.01,500,cHSL,.4-p*.2); } setupContext(6,4); { // tunnel drawRock(.85,.05,.53,.002,.6,.7, .5,-.1,.02,1e3); drawRock(.15,.05,.53,.002,.6,.7, .5,-.1,.02,1e3); drawRock(.5,.42,.25,.002,.5,.7, .5,.2,.02,1e3,undefined,undefined,0); } setupContext(7,4); { // tunnel 2 color(hsl(0,0,1)); rect(1,1,.4,.5); rect(0,1,.4,.5); color(hsl(0,0,.7)); rect(.5,.75,1,.15); } // road signs setupContext(0,5); // turn left { drawSignBackground(.5,.5,WHITE,BLACK,.05,GRAY,.3,.3,1); color(BLACK); triangle(.42,.5,.12,-PI/2) context.lineWidth=.09; context.beginPath(); context.arc(.44,.7,.2,PI*3/2,PI*2); context.stroke(); } /*setupContext(1,5); // curvy road { drawSignBackground(.5,.5,WHITE,BLACK,.04,GRAY,.3,.3,1); color(BLACK); triangle(.5,.46,.12) for(let i=99; i--;) { const p = i/99; rect(.5-.1*Math.cos(p*10)*Math.sin(p*3)**2,.5+p/4,.09,.01); } }*/ /*const warningColor = hsl(.14,1,.5); setupContext(0,5,1); // turn left { drawRoadSignBackground(); color(BLACK); triangle(.44,.23,.07,-PI/2) context.lineWidth=.05; context.lineCap='butt'; context.beginPath(); context.arc(.47,.35,.12,PI*3/2,PI*2); context.stroke(); } setupContext(1,5); { // curvy road drawRoadSignBackground(); color(BLACK); triangle(.5,.18,.07) for(let i=99; i--;) { const p = i/99; rect(.5+.05*Math.cos(p*10)*Math.sin(p*3)**2,.22+p*.2,.04,.01); } } setupContext(2,5); { // big turn left drawSignBackground(.4,.5,warningColor,BLACK,.04,GRAY,.3,.3,1); color(BLACK); triangle(.53,.55,.17,-PI/2) }*/ /*setupContext(2,5); { // warning drawSignBackground(.8,.3,WHITE,BLACK,.04,GRAY,.3,.4,1); color(BLACK); // set up clip const w=.79,h=.29; context.save(); context.beginPath(); context.rect(.5-w/2,.55-h/2,w,h); context.clip();//context.fill(); for(let j=5; j--;) { const x=j*.18,y=.4,h2=.4; rectLine(x,y,x+h2,y+h2,.045); } context.restore(); }*/ /*setupContext(4,5); { // speed limit drawSignBackground(.35,.43,WHITE,BLACK,.04,GRAY,0,.03,1,.05); color(BLACK); text('SPEED',.5,.1,.08,1,0,undefined,undefined,600); text('LIMIT',.5,.185,.08,1,0,undefined,undefined,600); text(55,.5,.34,.24,1,0,undefined,undefined,600); } setupContext(5,5); { // interstate 13 drawSignBackground(0,0,WHITE,BLACK,.04,GRAY,0,.1,1,.05); for(let k=2; k--;) for(let i=99; i--;) { color(k?WHITE:hsl(.6,.9,.4)); const p = i/99; const w = k?.5:.47; const h = k?.6:.57; rect(.5,h-p*.5,w*Math.sin(p*2.2-.2)**.7,.01); } color(WHITE) rect(.5,.1,.5,.15) color(hsl(0,.7,.5)) rect(.5,.1,.45,.1) color(WHITE) lineColor(WHITE) text('INTERSTATE',.5,.105,.1,.43,0,undefined,undefined,600); text(13,.48,.33,.3,1,.007); }*/ // more stuff setupContext(0,6); drawStartSign('终点'); setupContext(1,6); drawStartSign('开始'); /*setupContext(1,6); { // grave cross for(let i=2; i--;) { const o = i*.02; color(hsl(0,0,i?.1:1)); rect(.5+o,.6,.08,.8); rect(.5+o,.4,.4,.08); } }*/ setupContext(2,6); { // grave stone for(let k=2;k--;) for(let i=9;i--;) { const p = i/9; color(hsl(0,0,k?.2:.9)); circle(.5+k*.05,.5+p/2,.3,.4); } } /*setupContext(2,6,1); { // grave stone drawRock(.5,.2,.7,0.003,.9,1,undefined,undefined,undefined,undefined,vec3(0,0,2)); }*/ setupContext(3,6); if (js13kBuildLevel2) { // city building color(BLACK); rect(.5,.57,.3,1); for(let i=19; i--;) rect(.5+random.floatSign(.15),random.float(.5,.6),i/2e3,1); for(let j=30; j--;) for(let i=9; i--;) { const w = .03; const x = .38+i*w; const y = .1+j*w; color(hsl(random.float(.07,.15),random.float(.5,1),(i&j)%2?0:random.float(.3,1)**3)); rect(x,y,w*.7,w*.7); } } else { // y flippable city building color(BLACK); for(let i=19; i--;) { const p = i/19; const h = lerp(p,.9,.86) rect(lerp(p,.36,.64),.07+h/2,.03,h); rect(.5-random.floatSign(.14),.5,random.float(.02),random.float(.85,1)); } for(let j=28; j--;) for(let i=9; i--;) { const w = .03; const x = .372+i*w; const y = .1+j*w; color(hsl(random.float(.07,.15),random.float(.5,1),(i&j)%2?0:random.float(.3,1)**3)); rect(x,y,w*.7,w*.7); } } /*setupContext(5,6); { // green mountains random.setSeed(43); drawRock(.5,.1,.35,.03,.4,1, 1,undefined,undefined,undefined,vec3(.35,.4,.5),.3); drawRock(.5,.1,.08,.2,.4,1, 1,-.05,undefined,undefined,vec3(.1,.4,.5),.3); //messyRect(.5,1,.1,1,PI/2,hsl(.6,1,.7)); }*/ setupContext(6,6); { // dunes random.setSeed(9); drawRock(.5,0,.25,.04,.5,1,1,-.1,undefined,1e3,vec3(0,0,.7),.7); } setupContext(7,6); { // background mountains drawRock(.45,.1,.5,.025,.3,.7, .8,undefined,undefined,undefined,vec3(0,0,.7),.7); drawRock(.7,.1,.25,.02,.3,.8,undefined,undefined,undefined,undefined,vec3(0,0,.7),.6); } /*setupContext(1,6); { // road noise for(let i=9; i--;) for(let j=200; j--;) { color(hsl(0,0,random.float(.9,1))); rect(i/9,j/200,.3,.02); } }*/ //setupContext(0,6); //drawGirders(); //setupContext(1,6); //drawGirders(-.05,1); /*function drawGirders(o=.01,lit=.8) { // girders for(let i=3; i--;) { lineColor(hsl(0,0,lit-i/3)); const x = .5+i*.01; const lw = .02; const w = .1; for(let i=9; i--;) for(let j=2; j--;) { const k = j?1:-1; const x1 = x-k*w; const y1 = i*.2; const x2 = x+k*w; const y2 = (i+1)*.2; line(x1,y1,x2,y2,lw); } const w2 = w+o; line(x-w2,0,x-w2,1,lw); line(x+w2,0,x+w2,1,lw); } }*/ /*function drawRoadSignBackground() { const y = .28; color(hsl(0,0,.1)); rect(.51,.9,.04,1); color(GRAY); rect(.5,.9,.04,1); color(warningColor); rect(.5,y,.36,.36,PI/4); color(BLACK); rect(.5,y,.33,.33,PI/4); color(warningColor); rect(.5,y,.3,.3,PI/4); }*/ } if (hardAlpha) { // make hard alpha const minAlpha = 99; const s = generativeCanvasSize; const imageData = context.getImageData(0, generativeTileSize, s, s); const data = imageData.data; for (let i=3; i0; w+=random.float(wdelta)) for(let icount=density*w, i=icount, dh=0, h=hstart; i-->0;) { const p = i/icount; const pj = j/jcount; let l = random.float(.2,.5); if (pj < abs(cornerX-p)/20) l = random.float(.2); // dark on bottom if (random.bool(.05)) l = random.float(1); // random bright spot else if (pj > cornerY+abs(cornerX-p)*cornerAngle) l*=2; // bright on top else if (p > cornerX) l/=3; // darker on right side const zz = random.float(z,z*2) h += dh = -sign(dh)*random.float(randomness); const c2 = colorVecHSL.copy(); c2.z = l*c2.z+addLit; const c = c2.getHSLColor(.3); color(c); rect(x+lerp(p,-w,w),1-pj*h-y-zz+random.floatSign(randomness),zz,zz,random.floatSign(2)); } } function drawPalmTree() { const p = new Particle(.3,.29,.3,.5,.5,.02,.06); p.color = hsl(.1,.5,.1); p.colorRandom = .1; p.draw(); for(let j=12; j--;) { const v = .3, a = j/12*2*PI const vx = Math.sin(a) * v, vy = Math.cos(a) * v; const p = new Particle(.3,.23,vx,vy-.1,.2,.05,.005); p.style = 1; p.color = hsl(.3,.6,random.float(.3,.5)); p.colorRandom = .1; p.draw(); } } function drawFlower(pos, flowerPetals, flowerSize, c=RED) { const flowerAngle = random.float(2*PI); const regularity = 1+random.floatSign(.08); flowerSize = random.float(flowerSize*.6,flowerSize); color(random.mutateColor(WHITE,.2)); circle(pos.x,pos.y,flowerSize*random.float(.5,1)); c = random.mutateColor(c,.3); for(let i=flowerPetals; i--;) { const a = i/flowerPetals*PI*2+flowerAngle; const pos2 = pos.add(vec3(flowerSize/.8,0).rotateZ(a)); color(random.mutateColor(c,.2)); ellipse(pos2.x,pos2.y,flowerSize, flowerSize/2,a**regularity); } } function drawGrass(h=0,s=0,l=.6,flowerChance=0,flowerColor) { const flowerPetals = random.int(5,9); const flowerSize = random.float(.03,.05); for(let i=70; i--;) { const x = .5+random.floatSign(.25); const p = new Particle(x,1,random.floatSign(.25),random.floatSign(-.6,-1),.5,.02); p.color = hsl(h,s,l+random.float(.4)); p.iterations = 99; const pos = p.draw(); if (random.bool(flowerChance)) drawFlower(pos,flowerPetals,flowerSize,flowerColor); } } function drawSignBackground(w=1,h=.9,c=hsl(0,0,.1),outlineColor=WHITE,outline=.05,legColor=outlineColor, legSeparation=.2,yo=0,doubleOutline=0,legWidth=.1) { for(let i=2; i--;) { color(i?hsl(0,0,.1):legColor); rect(.5-legSeparation*w+.01*i,.5+yo,legWidth); rect(.5+legSeparation*w+.01*i,.5+yo,legWidth); } color(c); doubleOutline && rect(.5,h/2+yo,w+outline,h+outline); color(outlineColor); rect(.5,h/2+yo,w,h); color(c); rect(.5,h/2+yo,w-outline,h-outline); } function drawJS13kSign() { drawSignBackground(); color(WHITE,1); text('LUMIN',.5,.27,.5,.9,.02,'courier'); text('制作',.5,.66,.5,.9,.02,'courier'); } function drawDwitterSign() { drawSignBackground(1,.6,WHITE,BLACK); color(BLACK,1); text('dwitter.net',.5,.18,.2,.9,.01,'courier'); const w = .04; for(let i=9; i--;) rect(.18+i*w*2,.4,w,w*4); } function drawAvalancheSign() { const c = hsl(0, .9, .6); drawSignBackground(.9,.9,WHITE,hsl(0,0,.2)); color(c,1); const y = .37; circle(.5,y,.32); text('雪崩',.5,.8,.13,1,.003); color(WHITE); triangle(.5, y, .23); const r = .15; const ry = .26;//r*Math.sin(PI/3)*2; const x = .47; color(c); rectLine(x,y+.15,x+r,y+.15-ry,.07); } function drawGenericSign() { drawSignBackground(1,size+.1,c2,c1,undefined,c3); color(c1,1); text(t,.5,(size+.15)/2,size,.8+lineWidth*10,lineWidth,font); } function drawGitHubSign() { drawSignBackground(1,.4,WHITE,BLACK); color(BLACK,1); text('零界时速',.5,.22,.3,.9,.01); } function drawOPSign() { drawSignBackground(1,.6,WHITE,BLACK); color(BLACK,1); text('噶龟甲',.5,.5,.3,.9,.01); } function drawZZFXSign() { const t='SDFZ'; drawSignBackground(1,.6,BLACK,hsl(0,0,.2)); color(hsl(.6,1,.5),1); const x = .47, y = .38, o = .03; text(t,x,y,.55,.8,.05); color(YELLOW,1); text(t,x+o,y-o,.55,.8,.05); color(hsl(.96,1,.5),1); text(t,x+2*o,y-2*o,.55,.8,.05); } function drawDoubleLineSign(t1,t2,c,legSeparation=.5,y1=.24,y2=.46) { drawSignBackground(1,.6,c,WHITE,.05,BLACK,legSeparation); color(WHITE,1); text(t1,.5,y1,.31,.85,.01); text(t2,.5,y2,.2,.8,.01); } function drawLittleJSSign() { drawSignBackground(1,.7,WHITE,BLACK,.05,WHITE,.4,.2); ljsText('LittleJS',0.05,.45); ljsText('Engine',0.11,.67,2); function ljsText(t,x,y,o=0) { for(let i=0; i