ЮЬЬЬЬˆˆˆЌЊЊЊЌˆˆˆЌЬЬЬЌЬРЬЌЬРЬЌЬРЬЬЬьюˆˆЬюЊЊРюˆЈРюЬЌРЬРЌРРРЌРРРЌРРЮЬЬЬЬˆˆˆЌЊЊЊЌˆˆˆЌЬЬЬЌЬЬЬЌЬРЬЌЬРЬЬЬьюˆˆЬюЊЊРюˆЈРюЬЌРЬЬЌРРРЌРРРЌРРЌЬЬЬЌЊЊЊЌЊЌЊЌЊЪЬЌЊЊЊŒˆˆˆЬРЬЮЬЬЮЬЌЬЊЊРьЌЊРюЊЊРюЊЊРюˆˆЬюРьюЬЬююЌЬЬЬЌЊЊЊЌЊЌЊЌЊЪЬЌЊЊЊŒˆˆˆЬРЬЮЬЬЮЬЌЬЊЊРьЌЊРюЊЊРюЊЊРюˆˆЬюРьюЬЬюю >Р?џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџODџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџODDDDDDџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџwwџџџџџџџџџџџODDDDDDDDDџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџwwwwџџџџџџџџџџODDDDDDDDDџџџџџџџџџџџџџџѓѓџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџЯЬDџџџџџџџџџџџџџwwwWuwџџџџџџџџџODDDDDDD$Bџџџџџџџџџџџџџџ33џџџџџџџџџџџџџџџџџџџџџџџџџџoffџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџOЬЬLDOєџџџџџџџџџџwwWUUUuїџџџџџџџџODD$"""""Bџџџџџџџџџџџџџ333ѓџџџџџџџџџџџџџџџџџџџџџџџџofffcџџџџџџџџџџџ/"""""""ђџџџџџџџџџџOЬЬЬЬDDDџџџџџџџџџџWU\ХЬUuїџџџџџџџџO"""ЬЬЬ""Bџџџџџџџџџџџџ33Ь<ѓџџџџџџџџџџџџџџџџ"ђџџџџџџf6УЬ3іџџџџџџџџџџџ"""",""ђџџџџџџџџџџФЬЬЬЬDDDєџџџџџџџџUХЬХЬЬuwџџџџџџџџO""ТЬЬЬ,"Bџџџџџџџџџџџ33УЬЬѓџџџџџџџџџџџџ"""""ђџџџџџ?fЬЬЬlfџџџџџџџџџџџ""ЬЬЬ,"ђџџџџџџџџџџЬЬЬЬЬDDDєџџџџџџџџ_UЬЬЬЬЬЬwџџџџџџџџO$"ЬЬРРЬ"Bєџџџџџџџџ?333ЬРЬ<џџџџџџџџџџџ"""""""џџџџџ3УЬЬЬmax then return max else return a end end mLerp=function(a,min,max) return min+a*(max-min) end mRandom=math.random mFRandom=function(min,max) return min+mRandom()*(max-min) end mPi=math.pi mTwoPi=mPi*2 mSin=math.sin mCos=math.cos -- The heart of this code is a set of -- turtle-like drawing routines. These -- allow us to draw shapes that can be -- abritrarily rotated and scaled -- hierarchicly. function gReset() gX=120 gY=68 gH=0 gS=1 gStack={} end function gPush() table.insert(gStack,{gX,gY,gH,gS}) end function gPop() local r=table.remove(gStack) gX=r[1] gY=r[2] gH=r[3] gS=r[4] end function gTurn(a) gH=gH+a end function gScale(s) gS=gS*s end function gMove(x,y) local nX=mSin(gH)*gS local nY=-mCos(gH)*gS gX=gX+nX*y-nY*x gY=gY+nY*y+nX*x end function gCirc(s,c) circ(gX,gY,s*gS,c) end function gRect(w,h,c) local rX=mSin(-gH) local rY=mCos(-gH) local wS=w*gS local hS=h*gS local x1=gX-rX*hS-rY*wS local y1=gY-rY*hS+rX*wS local x2=gX-rX*hS+rY*wS local y2=gY-rY*hS-rX*wS local x3=gX+rX*hS+rY*wS local y3=gY+rY*hS-rX*wS local x4=gX+rX*hS-rY*wS local y4=gY+rY*hS+rX*wS tri(x1,y1,x2,y2,x3,y3,c) tri(x3,y3,x4,y4,x1,y1,c) end -- Heads are controlled by, lets call -- it, 'DNA'. This is basically just a -- set of 34 random numbers in the -- range 0 to 1. Each of these numbers -- controls a different aspect of the -- rendering process. dnaSize=34 -- Draw a single head. function drawHead(t,dna) -- This is a helper function to fetch -- the next available DNA value. local dnaIndex=-1 function getDna(min,max) dnaIndex=dnaIndex+1 return min+(max-min)*dna[dnaIndex] end -- A little bit of head bobbing. local r=mSin(t+getDna(-.5,.5))^3*getDna(.3,.7) gTurn(r) -- Overall scale. local globalSize=getDna(0.7,1.1) gScale(globalSize) -- Head and hair. local headColor=getDna(1,11) local headShape=getDna(0,1) local hairDistance=getDna(0.2,.4) local hairSize=getDna(0,.2) local hairColor=getDna(headColor+1,headColor+3) local hairTrim=getDna(0.1,.7) if headShape>=.66 then if hairSize>.1 then gPush() gMove(0,hairDistance) gCirc(1+hairSize,hairColor) gPop() end gCirc(1,headColor) elseif headShape>=.33 then if hairSize>.1 then gPush() gMove(0,hairDistance+hairTrim) gRect(1+hairSize,1+hairSize-hairTrim,hairColor) gPop() end gCirc(1,headColor) gMove(0,.5) gRect(1,.5,headColor) gMove(0,-.5) else if hairSize>.1 then gPush() gMove(0,hairDistance+hairTrim) gRect(1+hairSize,1+hairSize-hairTrim,hairColor) gPop() end gRect(1,1,headColor) end -- Ears. local earShape=getDna(0,1) local earDistance=getDna(.8,1.1) local earSize=getDna(0.2,.4) local earAngle=getDna(.6,1.5) for i=-1,1,2 do gPush() gTurn(earAngle*i) gMove(0,earDistance) if earShape>=0.5 then gCirc(earSize,headColor) else gRect(earSize,earSize,headColor) end gPop() end -- Eyes. local eyeSize=getDna(0.2,0.4) local eyeAngle=getDna(0.2,1) local eyeDistance=getDna(0.4,0.6) local eyeShape=getDna(0,1) local irisDistance=getDna(0,.5)*eyeSize local irisAngle=getDna(0,mTwoPi) local irisSize=getDna(.1,.5)*eyeSize local blinkStart=getDna(0,100) local blinkDelay=getDna(10,60) local blinkLength=getDna(.5,2) if (blinkStart+t)%blinkDelay>blinkLength then for i=-1,1,2 do gPush() gTurn(eyeAngle*i) gMove(0,eyeDistance) if eyeShape>.25 then gCirc(eyeSize,12) else gRect(eyeSize,eyeSize,12) end gPop() end for i=-1,1,2 do gPush() gTurn(eyeAngle*i) gMove(0,eyeDistance) gTurn(eyeAngle*-i) gTurn(irisAngle) gMove(0,irisDistance) gCirc(irisSize,0) gPop() end end -- Mouth. local mouthAngle=getDna(-.5,.5) local mouthDistance=getDna(-.3,-.7) local mouthShape=getDna(0,1) local mouthSize=getDna(.1,.3) local mouthHeight=getDna(.1,.7) local mouthColor=headColor+getDna(-1,-2) gPush() gTurn(mouthAngle) gMove(0,mouthDistance) if mouthShape>=.5 then gCirc(mouthSize,mouthColor) else gRect(mouthSize,mouthSize*mouthHeight,mouthColor-1) end gPop() -- And finally, the nose. local noseDistance=getDna(.2,-.1) local noseShape=getDna(0,1) local noseSize=getDna(.2,.3) local noseWidth=getDna(.4,.7) local noseColor=headColor+getDna(1,2) gPush() gMove(0,noseDistance) if noseShape>=.5 then gCirc(noseSize,noseColor) else gRect(noseSize*noseWidth,noseSize,noseColor) end gPop() end -- Create random DNA for a head. function randomHead() local dna={} for i=0,dnaSize-1 do dna[i]=mRandom() end return dna end -- Randomly mutate a head's DNA. function mutateHead(index) local dna={} for i=0,dnaSize-1 do if mRandom()<0.25 then dna[i]=mRandom() else dna[i]=headsDna[index][i] end end return dna end -- Lerp between two heads DNA. function lerpHeadDnaByIndex(a,i1,i2) return lerpHeadDna(a,headsDna[i1],headsDna[i2]) end -- Lerp between two heads DNA. function lerpHeadDna(a,dna1,dna2) local dna={} for i=0,dnaSize-1 do local v1=dna1[i] local v2=dna2[i] dna[i]=v1+a*(v2-v1) end return dna end -- Convert a (possibly negative) pair -- of x/y indices into an index into -- the grid of heads. function getHeadIndex(iX,iY) return (iX%headGridSize)+((iY%headGridSize)*headGridSize) end -- Initialization. function BOOT() -- Grid of DNA for all heads. headsDna={} for i=0,headGridSize^2-1 do headsDna[i]=randomHead() end -- Sparse array to indicate that a -- head is highlighted by the cursor. headHilights={} -- Sparse array of DNA targete for -- heads. We morph slowly towards -- these targets over a few frames -- because a) it looks cool, and -- b) it gives nice user feedback -- that something is happening. headTargets={} actionString=nil actionTime=0 -- Current corsor position and zoom. cX=0 cY=0 zoom=9 -- Scroll velocity. vX=0 vY=0 -- Remember last mouse position so -- that we can calculate a delta. lastMX=0 lastMY=0 end -- This is pure madness. If you start -- the app with CTRL+R then you get a -- keyp() for R on the first frame, so -- we skip the first frame with this -- little bit of magic :D function TIC() TIC=_TIC end function _TIC() -- Some local config. spacing=4 -- Fade out any active highlights. for _,v in pairs(headHilights) do local i=v[2] headHilights[i][1]=headHilights[i][1]-.05 if headHilights[i][1]<0 then headHilights[i]=nil end end -- Morph towards targets. for _,v in pairs(headTargets) do local i=v[2] local src=v[1] local dst=headsDna[i] local changed=false for j=0,dnaSize do local srcV=src[j] local dstV=dst[j] if dstV==srcV then elseif dstVsrcV then headsDna[i][j]=mMax(srcV,dstV-0.1) changed=true end end if changed==false then headTargets[i]=nil end end -- Handle mouse input. local mouseX,mouseY,mouseLeft,mouseMiddle,mouseRight,mouseScrollX,mouseScrollY=mouse() local shiftPressed=key(64) if mouseLeft then vX=(lastMX-mouseX)/zoom vY=(mouseY-lastMY)/zoom else vX=vX*(1-scrollDamping) vY=vY*(1-scrollDamping) end cX=cX+vX cY=cY+vY lastMX=mouseX lastMY=mouseY if mouseScrollY~=0 then local offX=(mouseX-120) local offY=(mouseY-68) local oldZoom=zoom zoom=mClamp(zoom+mouseScrollY,3,30) cX=cX+offX/oldZoom-offX/zoom cY=cY-offY/oldZoom+offY/zoom end local mouseBorder=spacing*-.1*zoom local xNum=(120/zoom/spacing)//1+2 local yNum=(68/zoom/spacing)//1+2 local cXA=cX%spacing local cXO=(cX/spacing)//1 local cYA=cY%spacing local cYO=(cY/spacing)//1 -- Get ready to render. local t=time()/1000*4.23 cls(15) -- Render head grid. local selectedX=nil local selectedY=nil local drawnIndexList={} for y=-yNum,yNum do local iY=(cYO+y//1)%headGridSize local gY=spacing*y-cYA local sY=68-gY*zoom for x=-xNum,xNum do local iX=(cXO+x//1)%headGridSize local gX=spacing*x-cXA local sX=120+gX*zoom local i=iX+iY*headGridSize if sX>mouseBorder and sX<240-mouseBorder and sY>mouseBorder and sY<136-mouseBorder then table.insert(drawnIndexList,getHeadIndex(iX,iY)) if (sX-mouseX)^2+(sY-mouseY)^2<(spacing*zoom/2)^2 then local headHilight=headHilights[i] if headHilight==nil then headHilights[i]={0,i} headHilight=headHilights[i] end headHilight[1]=mMin(headHilight[1]+.3,1) selectedX=iX selectedY=iY end end local headDna=headsDna[i] gReset() gScale(zoom) gMove(gX,gY) if headHilights[i] then local zoomScale=headHilights[i][1] gCirc(mLerp(headHilights[i][1],0,2.0),14) gScale(mLerp(zoomScale,1,1.3)) end drawHead(t,headDna) end end -- Key input. local newActionString=nil if keyp(18) then -- Random if shiftPressed then newActionString="Randomize All" for _,srcI in ipairs(drawnIndexList) do headTargets[srcI]={randomHead(),srcI} end elseif selectedX~=nil then newActionString="Randomize Single" local srcI=getHeadIndex(selectedX,selectedY) headTargets[srcI]={randomHead(),srcI} end elseif keyp(13) then -- Mutate if shiftPressed then newActionString="Mutate All" for _,srcI in ipairs(drawnIndexList) do headTargets[srcI]={mutateHead(srcI),srcI} end elseif selectedX~=nil then newActionString="Mutate Single" local srcI=getHeadIndex(selectedX,selectedY) headTargets[srcI]={mutateHead(srcI),srcI} end elseif keyp(1) then -- Average if selectedX~=nil then local srcI=getHeadIndex(selectedX,selectedY) if shiftPressed then newActionString="Average From Target" for _,dstI in ipairs(drawnIndexList) do headTargets[dstI]={lerpHeadDnaByIndex(0.75,srcI,dstI),dstI} end end end elseif keyp(8) then -- Help if instructionsTime>0 then instructionsTime=0 else instructionsTime=1 end end -- If we did some control then store -- descriptive text about it. if newActionString then actionString=newActionString actionTime=1 end end -- Instructions on the overlay. instructionsTime=1 local instructionsText={ "Head-O-Matic 3000", "", "Click+Drag to move", "Scroll to zoom", "", "R to randomize", "M to mutate", "A to average", "Add SHIFT to affect the whole screen", "", "H to show/hide help", } function OVR() poke(0x3fc3,0) poke(0x3fc4,0) poke(0x3fc5,0) -- Show description of last action. if actionTime>0 then actionTime=actionTime-.01 local yOff=mMin(mSin(((1-actionTime)^.4)*mPi)*1.3,1)*18 local x=4 local y=138-yOff print(actionString,x-2,y,1,false,2) print(actionString,x+2,y,1,false,2) print(actionString,x-2,y+1,1,false,2) print(actionString,x+2,y+1,1,false,2) print(actionString,x-1,y-1,1,false,2) print(actionString,x+1,y-1,1,false,2) print(actionString,x-1,y+2,1,false,2) print(actionString,x+1,y+2,1,false,2) print(actionString,x,y,11,false,2) print(actionString,x,y+1,12,false,2) end -- Overlay instructions if active. if instructionsTime>0 then instructionsTime=instructionsTime-.0025 for y=0,135 do for x=y&1,239,2 do pix(x,y,15) end end for i,txt in ipairs(instructionsText) do local w=print(txt,-100,-100,0,false,1) local x=(240-w)/2 local y=5+12*(i-1) print(txt,x-1,y-1,1,true) print(txt,x-1,y+1,1,true) print(txt,x+1,y-1,1,true) print(txt,x+1,y+1,1,true) print(txt,x-1,y,1,true) print(txt,x+1,y,1,true) print(txt,x,y-1,1,true) print(txt,x,y+1,1,true) print(txt,x,y,12,true) end end end