0 D$404mNJN…L04e$ÐFHuqaY}ÎÒ},…•¡mª,Òª™mÂÊÚÔ^ÞîÖ ‡™ºÌÌÝÝÝ̼»«‰gE3ÿÿÿÿîÝͼ«™ˆgU4"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ Ì  Q qBSDDFGHIIJJJJJðJðJàJðJKKK K LLLMðMðN@` P@ 0PPQSUVX[^_PPPPPPPPPPPPPPPPPPPP "PPP P_PPPPPPPPPPPPPPPPPPPPPPPP„ý T¢‚¢‚`¢‚¢‚‚ ‚ ‚ ‚ ‚ ‚x‚‚‚‚‚bb b¢ ‚¢§‚¢‚¢¢¢¢‚‚ ‚ ‚ ‚ ‚ ‚‚‚‚‚‚¢‚¢‚¢‚b b b b b bbb‚‚‚‚‚ ‚ ‚+0‚¢‚‚‚‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚‚‚‚‚ ‚‚‚‚‚ ‚‚‚‚‚ b b bbb‚‚‚‚‚ ‚‚ ‚ ‚ cD'a CD'a cD'a CD'a cD'a CD'a cD'a CD'a cd)a Cd)a c^)a C@^)a c?(a Cd)a c?(a C?(aô%Åå%Å%Åå%Å%Åå%%Åå%Å%%%åÅ墂‚‚‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚‚‚‚‚‚‚‚ ‚b ‚ ‚ ‚ ‚‚b‚b‚b‚‚b‚bT!A !!A!aAd!A$!A!AtaA!A!A!A!§aA!A'!A!AaA!!Aa!AaAa!A!a(Aa!AaAa!A!øaTÅåÅ%ÅåÅåÅåÅåå$ÅåÅ%ÅåÅåÅåÅ%å$ÅåÅ%ÅåÅåÅåÅåå$ÅåÅ%ÅåÅåÅåÅ%å cD'a CD'a cD'a CD'a cD'a CD'a cD'a CD'a c6%a C?&a c6%a C?&a c6%a C6%a cD'a CD'a¢‚‚‚‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚‚‚‚‚b‚b ‚‚‚‚‚‚‚¢¢¢‚ ‚ ‚ cD'a CD'a cD'a CD'a cD'a CD'a cD'a CD'a cF'a cF'a c^)a c@G'a c?(a c?(a c?(a cK(a‚ b‚b‚‚‚‚ ‚‚‚ ‚ ‚ ‚ ‚ ‚‚‚‚‚‚ ‚‚ b‚b‚‚‚ ‚ ‚ ‚¢‚¢¢¢¢ ‚¢‚ ‚ ‚ ‚‚‚‚ ‚ 0 cD'a CD'a cD'a CD'a cI'a CI'a cI'a CI'a c=(a C=(a c=(a C=(a cD'a CD'a c?(a C;(a cD'a CD'a cD'a CD'a c4)a c4)a c4)a c4)a c?(a c?(a co(a co(a cD'a CD'a cD'a CD'a3Á$$ƒƒ Íö%-- Assembled by the RiFT bundler rift_soundmusic=function() return function(R) R=R or {} R.music={} -- These are hard reset here, otherwise, you can call music() and these may or may not update for a frame -- because music() doesn't necessarily start immediately poke(0x13FFc,0) poke(0x13FFd,0) poke(0x13FFe,0) R.music.getPos=function() return { track=peek(0x13FFc), pattern=peek(0x13FFd), row=peek(0x13ffe), } end -- (https://github.com/nesbox/TIC-80/wiki/RAM#music-tracks) -- NB Some swizzling required here because there are signed values to decode... R.music.getTrackMeta=function(track) local aTrack=0x13E64+track*51 local tempo=peek(aTrack+48) tempo = (tempo&128)>0 and (150-(256-tempo)) or (150+tempo) local rows=64-peek(aTrack+49) local speed=peek(aTrack+50) speed = (speed&128)>0 and (6-(256-speed)) or (6+speed) local bpm=(6*tempo)/speed return { tempo=tempo, rows=rows, speed=speed, bpm=bpm, rowsPerSec=bpm/(60/4) } end --[[ Looks like it's possible to poke track and pattern, when music is running, but not row? R.music.setPos=function(track,pattern,row) poke(0x13FFc,track) poke(0x13FFd,pattern) poke(0x13ffe,row) end --]] end end rift_model3dspecs=function() -- 1-------------------2-----------------12 -- 8__________9 | 18_____17 \ -- 7_6 | | | / | \ -- \ 11________10 3-----/ 16 \ 13 -- 5_____________4/ 19 \__/ -- 15 14 -- xMult: 1 or -1 function getModelSide(xMult) local sideFlag=(xMult>0) and "l" or "r" local zFront=0 local yTop=-.25 local yBottom=.2 local yLensTop=-.15 local yLensBottom=.1 local yNoseHeight=.05 local xFullWidth=.8*xMult local xNickWidth=.7*xMult local xNoseOuterWidth=.15*xMult local xNoseInnerWidth=.05*xMult local xLensInner=.2*xMult local xLensOuter=.65*xMult local zArmTop=1.3 local zArmMax=1.5 local zArmHookMax=1.4 local zArmHookMid=1.3 local zArmHookMin=1.2 local zArmInner1=.7 local zArmInner2=.2 local points={ -- Rim {x=0,y=yTop,z=zFront}, {x=xFullWidth,y=yTop,z=zFront}, {x=xFullWidth,y=yLensBottom,z=zFront}, {x=xNickWidth,y=yBottom,z=zFront}, {x=xNoseOuterWidth,y=yBottom,z=zFront}, {x=xNoseInnerWidth,y=yNoseHeight,z=zFront}, {x=0,y=yNoseHeight,z=zFront}, -- Lens {x=xLensInner,y=yLensTop,z=zFront}, {x=xLensOuter,y=yLensTop,z=zFront}, {x=xLensOuter,y=yLensBottom,z=zFront}, {x=xLensInner,y=yLensBottom,z=zFront}, -- Arm {x=xFullWidth,y=yTop,z=zArmTop}, {x=xFullWidth,y=yLensBottom,z=zArmMax}, {x=xFullWidth,y=yBottom,z=zArmHookMax}, {x=xFullWidth,y=yBottom,z=zArmHookMid}, {x=xFullWidth,y=yLensBottom,z=zArmHookMin}, {x=xFullWidth,y=yLensTop,z=zArmHookMin}, {x=xFullWidth,y=yLensTop,z=zArmInner1}, {x=xFullWidth,y=yLensBottom,z=zArmInner2}, } local triPoints={ -- Rim {1,2,8}, {2,9,8}, {2,3,9}, {3,10,9}, {3,4,10}, {4,11,10}, {4,5,11}, {5,6,11}, {6,8,11}, {1,8,6}, {1,6,7}, -- Lens {8,9,10,sideFlag}, {8,10,11,sideFlag}, -- Arm {2,12,18}, {12,17,18}, {12,13,17}, {13,16,17}, {13,14,16}, {14,15,16}, {2,18,19}, {2,19,3}, } return { points=points, triPoints=triPoints, } end function mixModels(m1,m2) local pSh=#m1.points -- Add points for _,p in ipairs(m2.points) do m1.points[#m1.points+1]=p end -- Add tris (with shifted points) for _,t in ipairs(m2.triPoints) do local render=#t>3 and t[4] or nil m1.triPoints[#m1.triPoints+1]={t[1]+pSh,t[2]+pSh,t[3]+pSh,render} end return m1 end return function() local modelDef=mixModels(getModelSide(-1),getModelSide(1)) local model=PointsModel:new(drawTTris('tile',-1)) model:addPoints(modelDef.points) local tris={} for _,t in ipairs(modelDef.triPoints) do -- todo: unhack local render="" if #t==4 then render=t[4] end tris[#tris+1]={points={{p=t[1],tx=2,ty=0},{p=t[2],tx=2,ty=0},{p=t[3],tx=2,ty=0}},render=render} end model:addTris(tris) return model end end rift_layertunnel=function() local S,C=math.sin,math.cos local PI=math.pi local TAU=2*PI return function(models, tics) local tunnelSeg={} local iSegs=3 for i=1,iSegs do local a=i*TAU/iSegs local x=S(a)*.5 local y=C(a)*.5 tunnelSeg[#tunnelSeg+1]={x=x,y=y,z=0,r=1} end local c=20 for i=0,c do local model=PointsModel:new(drawDots) local d=-tics*.1 local z=1+(d+i)%c local x=S((z+d)*.3) local y=S((z+d)*.22) local r=S((z+d)*.25)*.6 ps=pointsTrans(pointsRot(copy(tunnelSeg),{x=0,y=0,z=r}),{x=x,y=y,z=z}) model:addPoints(ps) models[#models+1]=model end return models end end rift_datafontciduso24x24ciduso24x242bplraw=function() --- Data block for font: Ciduso 24x24 -- @module data.font.ciduso-24x24 -- @author Decca / RiFT -- @treturn {string,string,string,{string,...}} {name,rle,chars,layout} Data for the font constructor return { name="Ciduso 24x24", chars="ABCDEFIGHJKLPMNOQRSTUVWX.YZ()-10 ", bitsPerPixel=2, raw="0800040004f1d7000df7ff1007cff43000cff00000dff10000fff30000fff30000f7f3000004f1d0000df7f00007cf300000cf300000cf300000cf300000cf300000cf3ff10000dff70004fdff000df4ff100f70ff300f30ff304f30ff10cf34ff00cf3f7434f1dffd3df7f5ff37cf30df30cf304f30cf300f30cf300f30cf300f30cf3ff100000ff7000004ff100000df300000cf700000cff00000cff00000cff00004f1df743df7fffd37cff5ff30cf70df30cf304f30cf300f30cf300f30cf300004f1df743df7fffd37cff5ff30cf70df30cf304f30cf300f30cf300f30cf300004f1d7000df7ff1007cff43000cff00000cff00000cff00000cff00000cff0000004f3f7000cf3ff000cf1df000cf0cf000df0cf100fffff300fffff300fffff300000df300004fff0000dfff00004fff00000df300000cf300000cf300000cf3df700cf3ff100df7ff104fffff70dfffdff04fff4ff10df70ff30cf30ff30cf300000df300004ff71000dfff70004ff710000df300000cf300000cf300f30cf30cff10000dff70005ffff1000dff70000cff10000cff00000cff00000cff00000cf3c7000df34f004ffff700dffff1004ffff7000df34f000cf3c7000cf300000cf3c7000df34f004ffff700dffff1004ffff7000df34f000cf3c7000cf300000dff10004fff7000dffff1004fff70000dff10000cff00000cff00000cff000004f704f70cf300ff0cf300ff0cf300ff7cf737ffdf7f3f7f4f1d1d1d0000000000000cf300000cf300000cf300000cf343007cfff100dfff700047ff000000000ff30cf30ff104f74ff000ffdf7000dfff3000cfff10000df10000040000000000f30cf300f30cf304f30cf31df10cf3fff07cffff70dffff70047ff000000000cff00000cf700000df300004ff10000fff00000ff700000f7000000000000000cf300f30cf700f30cff04f30cff1df17cfffff0dfffff7047fff700000000000cf300000cf300000cf300000cf300007cf33000dfdf100047470000000000000cff00000cff00000cff00000cff0000ccffc0004f7f70000d1d100000000000000df74300cfffd300df5ff300f70df300f304f304f300f30cf300f30cf300f300004f1d0000df7f00007cf700000cf300000cf300000cf300000cf300000cf31d1d10003f7f700037ffd00000ff000000ff000000ff000000ff000000ff00000004f1d7000df7ff0007cff40000cff00000cff00000cff00000cff00000cff000004f1d1000df7f30007cf700000cf300000cf300000cf300000cf300000cf71d1d10003f7f700037fff10004ff70000df700004ff10000df700000ff1000004f1d7000df7ff1007cff43000cff00000cff00000cff00000cff00000cff00004f1dff10df7fff707cf3dff00cf34ff10cf30ff30cf30ff30cf30ff10cf34ff00cf300f30df700004fff1000dfff704f4fff10df0df70dff0cf3dff70cf34ff300000cf300000df700004fff7000dffff0004fff10000df700000cf300000cf300ff000004ff1000ffff7000fffff000ffff700004ff100000ff000000ff00000000dff10004fff7000dffff0004fff70000dff10000cff0d1d1cff0f7f3cff000000cff00000dff10004fff0000dfff00004fff00000df700000cf300000cf3f7000000f1000000f7000000ff100000ff300000ff300000df700000cff000000dff00004fff1000dfff70004fff10000dff00000cff00000cff00000cff00f30df3df704fffff10dffff7004ffff1000dff10000cf300000cf300000cf300000cf30df304f70cf300ff0cf300df1df300cffff3000dfff30004fff30000000000000cf300000cf300000cf300000cf300007cf70000df7f00004f1d0000000000ff000000ff000000ff000000ff000037ffd0003f7f70001d1d1000000000007f73cff00f30cff00f70cff00df1dff004ffff7000dfff10004ff7000000000000000cf300000cf300000cf300000cf300007cf70000df7f00004f1d000000004ff100000ff300000df700000cff000034ffd00030ff700010df1000000000000cff00f30cff00f30cff04f30cff1df17cfffff0dfffff7047fff700000000000cf300000cf300000cf300000cf300007cf33000dfdf10004747000000000000004fd1dd00dff3ff007cf1df000cf0cf000df0cf000ff1df000ff3ff000ff3ff70004ff1f100dfff43007cff00000cff10000cff30000cff30000cff30000cff0d1d10000f7f700017ffd00030ff000030ff000070ff0000f0ff0000f0ff0000000dff10004fff7000df5ff100f70df300f304f704f300ff0cf300ff0cf300ff0000000d0000004f000000df000000f7000000f3000004f300000cf300000cf3ff100000ff7000005ff100000df3000004f7000000ff000000ff000000ff00004f1dff10df7fff707cf3dff00cf34ff10cf30ff30cf30ff30cf30ff10cf34ff000df743004fffd300df5ff300f70df300f304f304f300f30cf700f30cff10f30004ff3ff00cff3ff00cfd7fd00cfcffc00dfcffc00ffcffc00ffcffc00ff4f7c70000dfff0004ffff000dffff0004ff7f1000df3f3000cf3f3000cf3f3000cf3f1ff1000f3ff7000f3fff100f7ff7000ffff1000ffff0000dfff0000cfff00000cf300ff0df300ff4ff704ffdfff1dff4ff704ff0df300ff0cf300ff0cf300ff00000df310004ff77000dffff1004ff770000df310000cf300000cf300000cf300ff100004ff70001dfff10004ff700000ff100000ff000000ff000000ff00000df3df704fffff10dfffff104fffff700df3dff00cf34ff10cf30ff30cf30ff3cfff10004ffff1000ffff7000dffff1000dfff30000dff70d1d1dff0f7f34ff004f70f340cf30f300cf30f300cf300007cf73007df7f300f4f1d100d00000000f7000cf3ff000cf3ff000cf3ff000cf3ff437cf77ff1df7f1d704f1d00000000cfff00004fff00000fff00000fff00003dff43003cfff10010df7000000000000cf300ff04f700f700ff04f300df1df100cffff0000dff700004f70000000000000004f7000000ff000000df000000cf0000000d00000004000000000000000000f7000004f300001df10000fff70000ffff4300fdfff10000df7000000000000cf30ff10cf30ff10cf30ff30cf30ff77cf33fffdfdf1df7474704f1000000007f730ff00f300f700f704f300df1df100cffff0000dff700004f700000000000d7000004ffffffffdfffffffffffffffd70cff04000cff00000cff00000cff00f1004f1df300df7ff1007cf7f3000cf3f1000cf300000cf300000cf300000cf31d1d10003f7f700037ffd00000ff000000ff000000ff000000ff000000ff00004f1d1d1ddf7f3f7f7cf737ff0cf300ff0cf300ff0cf300ff04f300f700f704f370004f1df100df7f43007cf700000cf300000cf300000cf3000004f7000000ff100d1d70300f7ff13007ff430000ff000f30ff000f30ff000f34f7004f7cf3004f1d1d1ddf7f3f7f7cf737ff0cf304ff04f70cf700ff0df300df1ff100cf7ff01000000070000000d00000000000000000000000000000000000000000000000000dff10004fff7000dffff1004fff70000dff10000cff00000cff00000cff0000000cf300000df300004ff70000dfff00004ff700000df300000cf300000cf300ff000000ff100004ff70001dfff10004ff700000ff100000ff000000ff000000ff0cf300ff0cf300df0cf100cf1df000cf3ff000cf3ff0004f3f70000f7f30000000ff000000ff000000df000000cf000000cf000000cf0000004f0000000fcffcf300cffcf300cffcf100cffcf000d7fdf000f3fff000f3ff7000f3ff3000004fff70000fff30000dff10000fff00004fff1000cfff3000df7f7000ff3ff00000000000000000000000000000000000000000000000000000000000000000000cff00000cff00000cff00000cff0000ccffc0004f7f70000d1d100000000000000cf3000004f7000000ff000000df000000cf0000000d000000040000000000ff000000f7000004f300001df10000fff00000ff700000f700000000000000000fff30000fff30000dff10000cff00007cff4300df7ff1004f1d70000000000000000f0000000f0000000d0000000c0000007c000000df0000004f00000000f3ff3000f1df3000f0cf1000f0cf0000f1df4300f3fff100d1dd70000000000004ff1df10cff0cf30df704f70ff300ff7ff737ffdf7f3f7f4f1d1d1d0000000000000000000000000000000000004f10d000cf307000cf3010004f10000000004f1d1d1ddf7f3f7f7cf737ff0cf704ff04ff0cf700ff1df300df3ff100cf7ff01000c1df7000c7ffd000cff50000cf700000cf100000cf040000cf0c0000cf0d7430000dfd3000cfff3000dfdf3000f7ff3000f3ff1004f3ff000cf3f7000cf3ff00ff70f700dff3100004f7000000df000000cf000000cf000000cf000000cf000000000000000000000000000000000000000010000000300000003000000000004f1d0000df7f0000fdff00007cff00000cff00000cff00000cff00000cff0000000d0000004f000000df000000f7000000f7000004ff00000cff00000cfff7000000ff1000005f7000000ff000000df100001cf300003cf300007cf30000004fff70000fff30000dff10000cff00000cff00000cff00000cff00000cff000000000f0000004f000000cf000000df000000ff000004ff00000cff00000df7f3000cf3f1000df7f0004fff7000dfff30004fff10000df700000cf30f300cf3000000cf000000df100004ff70000dff100004ff000000df000000cf000000cf3000d3007000f100f100dffff7004ffff100dfff7000f1003000d3003000000000c70dff004f4ffffff7dffffff14ffffff70dff004f0cff00c70cff00000cff10000cff70000dfff1004fff7000dfff10004ff700000df300000cf300000cf3fcf30000fdf70000ffff1000ffff7000ffff1000fff70000dff30000cff30000000cff00000cff00000cff00000cff0000ccffc0004f7f70000d1d100000000000000ff300004ff10000cff00000dff10000ffff0000fdff000074ff000000000f300cf30f3004f74f3000ffdf3000dfff3000cfff10000df700000400000000000000cf000000df000000ff100004f770000df3f700df70ff00ff1000000000300000001000000000000000000000000000000000000000000000000000000000000cff00000cff00000cff00000cff0000ccff00004f7f00000d1d0000000000000cf3000004f7000000ff000000dfc00000cf7000000d10000004000000004ff300000df100000df000005f700000ff300000ff100000f10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", layout={ {0,0,3,3,19,23},{3,0,2,3,15,23},{5,0,2,3,15,23},{7,0,3,3,19,23},{10,0,2,3,15,23},{12,0,2,3,15,23},{14,0,2,3,11,23}, {96,0,3,3,18,23},{99,0,3,3,18,23},{102,0,3,3,17,23},{105,0,3,3,19,23},{108,0,2,3,15,23},{110,0,2,3,15,23}, {192,0,3,3,23,23},{195,0,3,3,19,23},{198,0,3,3,19,23},{201,0,3,3,19,23},{204,0,2,3,16,23},{206,0,2,3,14,23}, {288,0,3,3,19,23},{291,0,3,3,19,23},{294,0,3,3,19,23},{297,0,3,3,23,23},{300,0,3,3,18,23},{303,0,1,3,2,23}, {384,0,3,3,18,23},{387,0,2,3,13,23},{389,0,2,3,12,23},{391,0,2,3,12,23},{393,0,2,3,16,23},{395,0,2,3,11,23},{397,0,3,3,18,23}, {480,0,1,3,8,23}, } } end rift_modelplane=function() return function(w,h,xTiles,yTiles,texX0,texY0,texW,texH) local points={} local tris={} for yPoint=0,yTiles do local yUnit=yPoint/yTiles for xPoint=0,xTiles do local xUnit=xPoint/xTiles points[#points+1]={ x=(xUnit*2-1)*w, y=(yUnit*2-1)*h, z=0 } if xPoint>0 and yPoint>0 then local xPointPrev=xPoint-1 local yPointPrev=yPoint-1 local p00=1+yPointPrev*(xTiles+1)+xPointPrev local p10=1+yPointPrev*(xTiles+1)+xPoint local p01=1+yPoint*(xTiles+1)+xPointPrev local p11=1+yPoint*(xTiles+1)+xPoint local tx0=texX0+(xPointPrev/xTiles)*texW local tx1=texX0+xUnit*texW local ty0=texY0+(yPointPrev/yTiles)*texH local ty1=texY0+yUnit*texH tris[#tris+1]={points={{p=p00,tx=tx0,ty=ty0},{p=p10,tx=tx1,ty=ty0},{p=p11,tx=tx1,ty=ty1}}} tris[#tris+1]={points={{p=p11,tx=tx1,ty=ty1},{p=p01,tx=tx0,ty=ty1},{p=p00,tx=tx0,ty=ty0}}} end end end return { points=points, tris=tris, } end end rift_modelfigure=function() -- 23 -- 21/ \22 -- \ / -- \ / -- 14=16=18=20=19=17=15 -- =12=. .=13= -- |10 11| -- | | -- 8/ /1 \ \9 -- / / \ \ -- 6| /2 3\ |7 -- |/ \| -- 4 5 -- .75 R L .25 local S,C,PI,A=math.sin,math.cos,math.pi,math.abs local TAU=PI*2 -- takes a point and adds distance at angle function skelPointNew(p,a,d) a=a*TAU return { x=p.x+S(a)*d, y=p.y-C(a)*d, z=p.z, } end function getModel(body,angs,ex) local bounce=ex.bounce or 0 local bounce=S(bounce*TAU) angs.pelvis=angs.pelvis*.1 angs.torso=angs.torso*.05 angs.rthigh=angs.pelvis+angs.rthigh*.1 angs.rknee=angs.rthigh+angs.rknee*.05 angs.lthigh=angs.pelvis+angs.lthigh*.1 angs.lknee=angs.lthigh+angs.lknee*.05 angs.head=angs.torso+angs.head*.05+bounce*.03 angs.rshoulder=angs.rshoulder*.2 angs.relbow=angs.rshoulder+angs.relbow*.1 angs.lshoulder=angs.lshoulder*.2 angs.lelbow=angs.lshoulder+angs.lelbow*.1 local pelvis={x=0,y=-A(bounce)*.1,z=0} local danglies=skelPointNew(pelvis,0.5,.1) local sternum=skelPointNew(pelvis,0,body.torso) local neck=skelPointNew(sternum,0,body.neck) local rShoulder=skelPointNew(sternum,.75+angs.torso,body.wshoulder) local rElbow=skelPointNew(rShoulder,.75+angs.rshoulder,body.lupperarm) local rHand=skelPointNew(rElbow,.75+angs.relbow,body.lforearm) local lShoulder=skelPointNew(sternum,.25+angs.torso,body.wshoulder) local lElbow=skelPointNew(lShoulder,.25+angs.lshoulder,body.lupperarm) local lHand=skelPointNew(lElbow,.25+angs.lelbow,body.lforearm) local rLegTop=skelPointNew(pelvis,.75+angs.pelvis,.2) local rKnee=skelPointNew(rLegTop,.6+angs.rthigh,body.lthigh) local rFoot=skelPointNew(rKnee,.6+angs.rknee,body.lcalf) local lLegTop=skelPointNew(pelvis,.25+angs.pelvis,.2) local lKnee=skelPointNew(lLegTop,.4+angs.lthigh,body.lthigh) local lFoot=skelPointNew(lKnee,.4+angs.lknee,body.lcalf) local face=skelPointNew(neck,angs.head,body.hheadlow) local points={ [1]=danglies, [2]=skelPointNew(rKnee,.25+angs.rknee/2,body.limbw2), [3]=skelPointNew(lKnee,.75+angs.lknee/2,body.limbw2), [4]=rFoot, [5]=lFoot, [6]=skelPointNew(rKnee,.75+angs.rknee,body.limbw2), [7]=skelPointNew(lKnee,.25+angs.lknee,body.limbw2), [8]=skelPointNew(pelvis,.75+body.pelvisangle+angs.pelvis,body.wpelvis), [9]=skelPointNew(pelvis,.25-body.pelvisangle+angs.pelvis,body.wpelvis), [10]=skelPointNew(rShoulder,.5+angs.rshoulder/2,body.limbw), [11]=skelPointNew(lShoulder,.5+angs.lshoulder/2,body.limbw), [12]=skelPointNew(rElbow,.5+angs.relbow,body.limbw2), [13]=skelPointNew(lElbow,.5+angs.lelbow,body.limbw2), [14]=rHand, [15]=lHand, [16]=skelPointNew(rElbow,angs.relbow,body.limbw2), [17]=skelPointNew(lElbow,angs.lelbow,body.limbw2), [18]=skelPointNew(rShoulder,angs.rshoulder/2,body.limbw), [19]=skelPointNew(lShoulder,angs.lshoulder/2,body.limbw), [20]=neck, [21]=skelPointNew(face,.75+angs.head,.2), [22]=skelPointNew(face,.25+angs.head,.2), [23]=skelPointNew(face,angs.head,body.hheadhigh), } local triPoints={ -- rleg {1,2,8}, {2,6,8}, {2,4,6}, -- lleg {1,9,3}, {9,7,3}, {3,7,5}, -- rarm {10,12,18}, {12,16,18}, {12,14,16}, -- larm {11,13,19}, {13,19,17}, {13,17,15}, -- head {20,21,22}, {21,22,23}, -- torso {1,8,10}, {1,11,9}, {1,10,11}, {10,12,18}, {11,19,13}, {10,20,11}, {10,18,20}, {11,20,19}, } local model=PointsModel:new(drawTTris('tile',0)) model:addPoints(points) local tris={} for _,tp in ipairs(triPoints) do tris[#tris+1]={points={{p=tp[1],tx=2,ty=0},{p=tp[2],tx=2,ty=0},{p=tp[3],tx=2,ty=0}},render=""} end model:addTris(tris) return model end function getBody() return { lupperarm=.3, lforearm=.5, lthigh=.3, lcalf=.5, wshoulder=.4, wpelvis=.2, pelvisangle=.2, hheadlow=.3, hheadhigh=.2, } end local function getBodyStrongman() return { torso=.8, neck=.1, lupperarm=.4, lforearm=.5, lthigh=.5, lcalf=.5, wshoulder=.5, wpelvis=.2, pelvisangle=0, hheadlow=.2, hheadhigh=.2, limbw=.2, limbw2=.08, } end function getBodyAcrobat() return { torso=.6, neck=0.1, lupperarm=.5, lforearm=.5, lthigh=.6, lcalf=.6, wshoulder=.3, wpelvis=.15, pelvisangle=.1, hheadlow=.2, hheadhigh=.2, limbw=.1, limbw2=.05, } end function getBodyRingmaster() return { torso=.7, neck=.25, lupperarm=.3, lforearm=.4, lthigh=.4, lcalf=.5, wshoulder=.4, wpelvis=.55, pelvisangle=.1, hheadlow=.2, hheadhigh=.2, limbw=.1, limbw2=.07, } end return { getBodyStrongman=getBodyStrongman, getBodyAcrobat=getBodyAcrobat, getBodyRingmaster=getBodyRingmaster, getModel=getModel, } end rift_syssys=function() --- Helpers for System level functions -- @module sys.sys return function(R) R=R or {} rift_uimouse()(R) local mouse=R.Mouse:new() local mouseState=nil local tics=0 local startTime=nil local _clip={x=0,y=0,w=240,h=136} --- Exit immediately if a specified key is pressed. -- Useful for some (not-latest) versions of TIC where CTRL-C is erroneously disabled. -- -- [!] Call at the start of the TIC loop. -- @param key The key to break on, defaults to 51 (ESC). [Keycodes here](https://github-wiki-see.page/m/nesbox/TIC-80/wiki/key) e.g. 48 is spacebar. -- @usage -- function TIC() -- R.tryBreak() -- ... -- end R.tryBreak=function(key) key=key or 51 if keyp(key) then exit() end end --- Removes the mouse cursor from the screen. -- -- [!] Call at the start of the TIC loop. R.hideMouse=function() poke(0x3FFB,0) end --- Returns the number of times the TIC loop has been run. -- -- [!] Wrap your TIC loop in R.ticWrap to use this. R.getTics=function() return tics end --- Returns the time in milliseconds since the first TIC loop. -- -- [!] Wrap your TIC loop in R.ticWrap to use this. R.getTimeMS=function() return time()-startTime end --- Enables some system level tracking, and mouse state support. -- @usage -- R.ticWrap(function() -- -- your usual tic loop -- end) R.ticWrap=function(tic) return function() if startTime==nil then startTime=time() end mouseState=mouse:update() tic() tics=tics+1 end end --- Returns the mouse state. -- -- [!] Wrap your TIC loop in R.ticWrap to use this. function R.getMouse() return mouseState end --- Wrappers for TIC's clip functionality. -- This wrapping allows us (and system calls) to check the currently set clip area. -- -- [!] Mixing this with the regular, unwrapped 'clip()' will cause confusion! function R.clearClip() _clip={x=0,y=0,w=240,h=136} clip() end --- Wrappers for TIC's clip functionality. -- This wrapping allows us (and system calls) to check the currently set clip area. -- -- [!] Mixing this with the regular, unwrapped 'clip()' will cause confusion! -- @param x top -- @param y left -- @param w width -- @param h height function R.setClip(x,y,w,h) _clip={x=x,y=y,w=w,h=h} clip(x,y,w,h) end --- Wrappers for TIC's clip functionality. -- This wrapping allows us (and system calls) to check the currently set clip area. -- -- [!] Mixing this with the regular, unwrapped 'clip()' will cause confusion! -- @return {x,y,w,h} function R.getClip() return _clip end end end rift_scene3dspecs=function() R=R or {} local S,PI=math.sin,math.pi local TAU=PI*2 rift_demopart()(R) local getModel3DSpecs=rift_model3dspecs() local getModel3DFont=rift_modelfont() return R.DemoPart:new({ bdr=function(y,pMetrics) end, tic=function(pMetrics) if pMetrics.isFirstRun then basePixelsToBank() anaglyphSetPalette(1) CAMX,CAMY,CAMZ=0,0,0 end local fadev=fader(pMetrics.progress,0.3,0.8) anaglyphSetPalette(fadev) local s=2 local textScale={x=s,y=s,z=1} local textZ=2 local modelsText={ getModel3DFont("PLEASE"):scale(textScale):trans({x=0,y=-.3*s,z=textZ}), getModel3DFont("ENGAGE"):scale(textScale):trans({x=0,y=-.1*s,z=textZ}), getModel3DFont("APPROPRIATE"):scale(textScale):trans({x=0,y=.1*s,z=textZ}), getModel3DFont("EYEWEAR"):scale(textScale):trans({x=0,y=.3*s,z=textZ}), } local x=0 local y=3.5-S(pMetrics.progress^.25*PI/2)*3.5 local r=pMetrics.progress*30 local scale=.25 local z=0.75+S(pMetrics.progress*20)*0.3 local modelsScene={ getModel3DSpecs():scale({x=scale,y=scale,z=scale}):rot({x=0,y=r,z=r/2}):trans({x=x,y=y,z=z}) } -- Draw local txsh=-1 vbank(1) cls() anaglyphSwitchRGBsForLeft() copyBankToTileRAM(1) renderModels(modelsScene,-1) copyBankToTileRAM(0) renderModelsText(modelsText,-1) anaglyphGrab() cls() vbank(0) cls() anaglyphResetRGBs() copyBankToTileRAM(1) renderModels(modelsScene,1) copyBankToTileRAM(0) renderModelsText(modelsText,1) anaglyphMix() end, }) end rift_sceneacrobats=function() R=R or {} local S,C=math.sin,math.cos local A=math.abs local PI=math.pi local TAU=2*PI rift_demopart()(R) local fnsActors=rift_layeractors() return R.DemoPart:new({ tic=function(pMetrics) if pMetrics.isFirstRun then vbank(0) cls() basePixelsToBank() anaglyphSetPalette(1) end local fadev=fader(pMetrics.progress,.1,.9) anaglyphSetPalette(fadev) local _,bounce=math.modf((pMetrics.progress)*16) local modelsScene={} CAMX=S(pMetrics.progress*14)+S(pMetrics.progress*26) CAMY=-1+C(pMetrics.progress*9)*.7 CAMZ=pMetrics.progress*100 local actors={} fnsActors.addRingmaster(actors,{x=0,y=0,z=10},pMetrics.progress*20) fnsActors.addJuggler(actors,{x=0,y=0,z=20},pMetrics.progress*60) fnsActors.addJuggler(actors,{x=-2,y=0,z=30},-pMetrics.progress*60) fnsActors.addStrongman(actors,{x=-2,y=0,z=40},pMetrics.progress*4) fnsActors.addTumbler(actors,{x=-43+pMetrics.progress*100,y=-.5,z=45},pMetrics.progress*20) fnsActors.addJuggler(actors,{x=-.8,y=0,z=50},pMetrics.progress*60) fnsActors.addJuggler(actors,{x=6,y=0,z=50},-pMetrics.progress*60) fnsActors.addTumbler(actors,{x=59-pMetrics.progress*100,y=-.3,z=60},-pMetrics.progress*20) fnsActors.addStrongmanTower(actors,{x=0,y=0,z=70},pMetrics.progress*4) if pMetrics.progress <.7 then local vSwing=pMetrics.progress*20 local x=S(vSwing)*10 local y=-7+A(C(PI+vSwing))*5 fnsActors.addSwinger(actors,{x=x,y=y,z=80},-pMetrics.progress*18,{x=0,y=PI/6,z=0}) fnsActors.addSwinger(actors,{x=-x,y=y,z=80},-pMetrics.progress*18,{x=0,y=-PI/6,z=0}) end fnsActors.addStrongman(actors,{x=-4,y=0,z=85},pMetrics.progress*4) fnsActors.addRingmaster(actors,{x=0,y=0,z=90},pMetrics.progress*20) fnsActors.addJuggler(actors,{x=2,y=0,z=100},pMetrics.progress*60) fnsActors.addJuggler(actors,{x=-2,y=0,z=100},-pMetrics.progress*60) local cannonStart=.85 local cannonLength=.1 if pMetrics.progress>cannonStart then local anim=(pMetrics.progress-cannonStart)/cannonLength local x=30-anim*60 local y=-S(anim)*15 fnsActors.addCannonman(actors,{x=x,y=y,z=110},-pMetrics.progress*20) end local modelsScene=fnsActors.actorsToModels(actors,bounce,pMetrics.progress) -- Draw vbank(1) cls() anaglyphSwitchRGBsForLeft() copyBankToTileRAM(1) renderModels(modelsScene,-1) anaglyphGrab() cls() vbank(0) cls() anaglyphResetRGBs() copyBankToTileRAM(1) renderModels(modelsScene,1) anaglyphMix() end, }) end rift_modeldeadlinelogo=function() -- 3__4 7____8 _ yu3 -- / / / 11 \ _ yu2 -- / / / ^ \ 14__15 _ yu1 -- / / / / \10_\/ / _ y0 -- 1__2/ /_/ /______/9 / _ yd1 -- \ 5 6 12 13 / -- \___________________/ _ yd2 -- 17 16 return function() local model=PointsModel:new(drawLines) local yu3,yu2,yu1,y0,yd1,yd2=-.4,-.25,-.2,0,.15,.4 local xl8,xl7,xl6,xl5,xl4,xl3,xl2,xl1,x0,xr1,xr2,xr3,xr4,xr5=-1,-.8,-.7,-.55,-.45,-.35,-.25,-.2,0,.2,.4,.5,.7,1 model:addPoints({ {x=xl8,y=yd1,z=0}, {x=xl7,y=yd1,z=0}, {x=xl4,y=yu3,z=0}, {x=xl3,y=yu3,z=0}, {x=xl6,y=yd1,z=0}, {x=xl5,y=yd1,z=0}, {x=xl1,y=yu3,z=0}, {x=xr1,y=yu3,z=0}, {x=xr3,y=y0,z=0}, {x=xr1,y=y0,z=0}, {x=x0,y=yu2,z=0}, {x=xl2,y=yd1,z=0}, {x=xr2,y=yd1,z=0}, {x=xr4,y=yu1,z=0}, {x=xr5,y=yu1,z=0}, {x=xr3,y=yd2,z=0}, {x=xl7,y=yd2,z=0}, -- Close line {x=xl8,y=yd1,z=0}, }) return model end end rift_uimouse=function() return function(R) R=R or {} R.Mouse={} function R.Mouse:new() local o={ last=nil, now=nil, } setmetatable(o,self) self.__index=self return o end function R.Mouse:update() self.last=self.now local x,y,lb,mb,rb,sx,sy=mouse() self.now={ x=x,y=y, lb=lb,mb=mb,rb=rb, sx=sx,sy=sy, } local lastLb,lastMb,lastRb=false,false,false if self.last~=nil then lastLb,lastMb,lastRb=self.last.lb,self.last.mb,self.last.rb end self.now.lbp=self.now.lb and not lastLb self.now.mbp=self.now.mb and not lastMb self.now.rbp=self.now.rb and not lastRb return self.now end end end rift_text3d=function() local S=math.sin local CHARS={} function setupChars() cls() local charset="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=)!" local x,y=0,0 for i=1,#charset do local ch=charset:sub(i,i) print(ch,x+1,y+1,15,true) local w=print(ch,x,y,14,true) CHARS[ch]={ x=x,y=y,w=w,h=7 } x=x+w+1 if x>120 then x=0 y=y+7 end end swizzleScreenToVRAM('tile',0,0,128,32) end function drawText(text,ofs,dx,dy,dh,r) local z=1.5 dx=dx-ofs*z for i=1,#text do local ch=text:sub(i,i) local c=CHARS[ch] if c then local r=S(i*.1+r*.01) cy=dy+r*dh local s00={x=c.x,y=c.y} local s10={x=c.x+c.w,y=c.y} local s01={x=c.x,y=c.y+c.h} local s11={x=c.x+c.w,y=c.y+c.h} local dw=c.w/2 local dh=c.h/2 local a=-r*.3 local x,y=rot(-dw,-dh,a) local d00={x=dx+x*z,y=cy+y*z} local x,y=rot(dw,-dh,a) local d10={x=dx+x*z,y=cy+y*z} local x,y=rot(-dw,dh,a) local d01={x=dx+x*z,y=cy+y*z} local x,y=rot(dw,dh,a) local d11={x=dx+x*z,y=cy+y*z} tquad(s00,s10,s01,s11,d00,d10,d01,d11) dx=dx+c.w*z+1 if dx>=250 then return end else dx=dx+5*z end end end function tquad(s00,s10,s01,s11, d00,d10,d01,d11) ttri(d00.x,d00.y,d10.x,d10.y,d01.x,d01.y, s00.x,s00.y,s10.x,s10.y,s01.x,s01.y,0,0) ttri(d01.x,d01.y,d11.x,d11.y,d10.x,d10.y, s01.x,s01.y,s11.x,s11.y,s10.x,s10.y,0,0) end end rift_scenecredits=function() R=R or {} local S,C,A,PI=math.sin,math.cos,math.abs,math.pi local MAX=math.max local TAU=PI*2 rift_demopart()(R) local fnsActors=rift_layeractors() local getModel3DSpecs=rift_model3dspecs() local getModel3DFont=rift_modelfont() local CREDITS={ {role={}, name={}}, {role={"DESIGN", "AND CODE"}, name={"JTRUK"}}, {role={"FONT"}, name={"DECCA"}}, {role={"ORIGINAL", "MUSIC"}, name={"JULIUS FUCIK"}}, {role={"MUSIC", "RENDITION"}, name={"JTRUK"}}, {role={"MUSIC", "CONSULTANT"}, name={"ENFYS"}}, {role={"THANKS"}, name={"KIERAN", "(BITSHIFTERS)"}}, {role={"THANKS"}, name={"KB", "AND", "DEADLINE ORGAS"}}, {role={}, name={}}, -- "RIFT LIBS" -- "MAY THE DEMOLITION BE WITH YOU", -- "RIFT", } function getDampedCycle(unitCycle) local dampen=(A(unitCycle-.5)^3.2)*20 if unitCycle>.5 then dampen=-dampen end return dampen end -- cycle: 0->offscreen, (.3) centre (.7) 1->offscreen function getModelTextWheel(txt,ox,oy,dx,dy,finalAngle,cyclePos) local angle=(cyclePos)+finalAngle x=ox+C(angle)*dx y=oy-S(angle)*dy return getModel3DFont(txt):rot({x=0,y=0,z=cyclePos}):trans({x=x,y=y,z=0}) end function drawBackground(progress,shift) cls() local t=(progress*1000) for y=0,135 do local size=S(S(y*.03-t*.02)+S(y*.017)) local step=MAX(32+size*28,2) local xAnaglyphOfs=shift*size*XANASHIFT*30 local xwobble=S(y*.03+t*.08)*20-xAnaglyphOfs local xmax=119+step local o=0 for x=0,xmax,step do if o==0 then local x1=120-x+xwobble line(x1,y,x1-step,y,1) else local x2=120+x+xwobble line(x2,y,x2+step,y,1) end o=(o+1)%2 end end end return R.DemoPart:new({ bdr=function(y,pMetrics) end, tic=function(pMetrics) if pMetrics.isFirstRun then basePixelsToBank() anaglyphSetPalette(1) CAMX,CAMY,CAMZ=0,0,0 end local fadeP=fader(pMetrics.progress,0.1,0.9) anaglyphSetPalette(fadeP) local fadeV=fader(pMetrics.progress,0,0.85) setGlobalVolume(.2+fadeV*.8) local _,bounce=math.modf((pMetrics.progress)*16) local modelsScene={} local iCredit=(pMetrics.progress*#CREDITS)//1+1 local credit=CREDITS[iCredit] local modelsText={} local actors={} if credit then local partial=1+(#CREDITS*pMetrics.progress)-iCredit local partialDamped=getDampedCycle(partial) local zText=1.5+S(partial*TAU)*1 if #credit.role>0 then local iActor=iCredit%5 local xPos=partialDamped*10 local yPos=1 local zPos=12-S(partial*PI)*8 if iActor==0 then fnsActors.addJuggler(actors,{x=xPos-2,y=yPos,z=zPos+1},-pMetrics.progress*60) fnsActors.addJuggler(actors,{x=xPos+2,y=yPos,z=zPos+1},pMetrics.progress*60) elseif iActor==1 then fnsActors.addStrongmanTower(actors,{x=xPos,y=yPos+2,z=zPos+2},pMetrics.progress*4) elseif iActor==2 then fnsActors.addRingmaster(actors,{x=xPos,y=yPos,z=zPos},pMetrics.progress*20) elseif iActor==3 then fnsActors.addTumbler(actors,{x=xPos,y=yPos,z=zPos},pMetrics.progress*20) elseif iActor==4 then fnsActors.addStrongman(actors,{x=xPos,y=yPos,z=zPos},pMetrics.progress*4) end end for i,txt in ipairs(credit.role) do local xofs=-.5+#txt*.045 local yofs=-.95+(i-1)*.2 modelsText[#modelsText+1]=getModelTextWheel(txt,xofs,yofs,.5,.7,-PI/2,partialDamped):trans({x=0,y=0,z=zText}) end for i,txt in ipairs(credit.name) do local xofs=.6-#txt*.045 local yofs=.95+(i-#credit.name)*.2 modelsText[#modelsText+1]=getModelTextWheel(txt,xofs,yofs,.5,.7,PI/2,partialDamped):trans({x=0,y=0,z=zText}) end end local modelsScene=fnsActors.actorsToModels(actors,bounce,pMetrics.progress) -- Draw vbank(1) cls() anaglyphSwitchRGBsForLeft() drawBackground(pMetrics.progress,-1) copyBankToTileRAM(1) renderModels(modelsScene,-1) copyBankToTileRAM(0) renderModelsText(modelsText,-1,true) anaglyphGrab() cls() vbank(0) cls() anaglyphResetRGBs() drawBackground(pMetrics.progress,1) copyBankToTileRAM(1) renderModels(modelsScene,1) copyBankToTileRAM(0) renderModelsText(modelsText,1,true) anaglyphMix() end, }) end rift_sysmath=function() --- Helpers for Maths - clamps and easing -- @module sys.math return function(R) R=R or {} --- Clamp a value -- @param v A value -- @param min The lower bound -- @param max The upper bound R.clamp=function(v,min,max) return math.min(math.max(v,min),max) end --- Linear Interpolation bewteen two values -- @param t Time (0-1) -- @param v0 The value at T=0 -- @param v1 The value at T=1 R.lerp=function(t,v0,v1) return (1-t)*v0+t*v1 end --- Quadratic Bézier -- @param t Time (0-1) -- @param v0 The value at T=0 -- @param v1 The mid-value modifier -- @param v2 The value at T=1 R.qBezier=function(t,v0,v1,v2) local tInv=1-t return (tInv*tInv*v0)+(tInv*t*v1)+(t*t*v2) end --- Cubic Bézier -- @param t Time (0-1) -- @param v0 The value at T=0 -- @param v1 The first mid-value modifier -- @param v2 The second mid-value modifier -- @param v3 The value at T=1 R.cBezier=function(t,v0,v1,v2,v3) local tInv=1-t return (tInv*tInv*tInv*v0)+(3*tInv*tInv*t*v1)+(3*tInv*t*t*v2)+(t*t*t*v3) end --- Smoothstep Easing function -- @param t Time (0-1) -- @param e0 The value at T=0 -- @param e1 The value at T=1 R.smoothStep=function(t,e0,e1) local s = (t-e0)/(e1-e0) local x = R.clamp(s,0,1) return e0 + (x * x * (3.0 - 2.0 * x))*(e1-e0) end --- Smootherstep Easing function -- @param t Time (0-1) -- @param e0 The value at T=0 -- @param e1 The value at T=1 R.smootherStep=function(t,e0,e1) local s = (t-e0)/(e1-e0) local x = R.clamp(s,0,1) return e0 + (x * x * x * (3.0 * x * (2.0 * x - 5.0) + 10.0))*(e1-e0) end end end rift_layeractors=function() local fnsFigure=rift_modelfigure() local getModelDisc=rift_modeldisc() local S,C=math.sin,math.cos local A=math.abs local PI=math.pi local TAU=2*PI function actorsToModels(actors,bounce,unitProgress) local models={} local ex={bounce=bounce} for _,actor in ipairs(actors) do if actor.type=="figure" then models[#models+1]=fnsFigure.getModel(actor.body,actor.pose,ex):rot(actor.rot):trans(actor.pos) elseif actor.type=="ball" then models[#models+1]=getModelDisc(.1,.1,5,0,0) :rot(actor.rot) :trans(actor.pos) end end return models end function addRingmaster(actors,opos,unitCycle) unitCycle=unitCycle%1 local rUnitCycle=unitCycle*TAU local rot={x=0,y=0,z=0} actors[#actors+1]={ type="figure", body=fnsFigure.getBodyRingmaster(), pos=pointTrans(opos,{x=0,y=0,z=0}), rot=rot, pose={ pelvis=S(rUnitCycle)*.2, head=0,--S(ofs)/2, torso=S(rUnitCycle)*.8, rthigh=-1,--S(ofs), lthigh=1,--S(ofs), rknee=0,---S(ofs), lknee=0,---S(ofs),--S(pMetrics.progress*TAU*2), rshoulder=-1-S(rUnitCycle)*.1, relbow=-.8+S(rUnitCycle)*.2, lshoulder=-1.3+S(rUnitCycle), lelbow=-.5+S(rUnitCycle), } } end function addCannonman(actors,opos,unitCycle) unitCycle=unitCycle%1 local rUnitCycle=unitCycle*TAU local rot={x=0,y=0,z=rUnitCycle*3} actors[#actors+1]={ type="figure", body=fnsFigure.getBodyAcrobat(), pos=pointTrans(opos,{x=0,y=0,z=0}), rot=rot, pose={ pelvis=-.5+S(rUnitCycle*4)*.5, head=0, torso=S(rUnitCycle*4), rthigh=-1.5+S(rUnitCycle)*.5, lthigh=.5+S(rUnitCycle)*.5, rknee=0, lknee=0, rshoulder=.5+S(rUnitCycle*4)*.3, relbow=1, lshoulder=-1.5+S(rUnitCycle*4)*.3, lelbow=-1, } } end function addStrongman(actors,opos,unitCycle) unitCycle=unitCycle%1 local rUnitCycle=unitCycle*TAU local rot={x=0,y=0,z=0} actors[#actors+1]={ type="figure", body=fnsFigure.getBodyStrongman(), pos=pointTrans(opos,{x=0,y=0,z=0}), rot=rot, pose={ pelvis=-S(rUnitCycle)*.2, head=S(rUnitCycle)*.6, torso=-1+S(rUnitCycle)/2, rthigh=-1-S(PI+rUnitCycle)*.8, lthigh=-.3+S(rUnitCycle), rknee=3-S(rUnitCycle)*2.5, lknee=2-S(rUnitCycle)*3, rshoulder=-.8-S(rUnitCycle)*.4, relbow=-2+S(rUnitCycle)*1.5, lshoulder=.4+S(rUnitCycle)*.8, lelbow=-2+S(rUnitCycle)*1.5, } } end function addSwinger(actors,opos,unitCycle,rot) unitCycle=unitCycle%1 local rUnitCycle=unitCycle*TAU actors[#actors+1]={ type="figure", body=fnsFigure.getBodyAcrobat(), pos=pointTrans(opos,{x=0,y=0,z=0}), rot=rot, pose={ pelvis=S(rUnitCycle)*.2, head=0, torso=S(rUnitCycle)*.2, rthigh=-.5-S(rUnitCycle)*.5, lthigh=1.5-S(rUnitCycle)*.5, rknee=0, lknee=0, rshoulder=1, relbow=1, lshoulder=-1, lelbow=-1, } } end function addTumbler(actors,opos,unitCycle) unitCycle=unitCycle%1 local rUnitCycle=unitCycle*TAU local rot={x=0,y=0,z=rUnitCycle} actors[#actors+1]={ type="figure", body=fnsFigure.getBodyAcrobat(), pos=pointTrans(opos,{x=0,y=S(.6+rUnitCycle)*.4,z=0}), rot=rot, pose={ pelvis=S(-rUnitCycle)*2, head=S(rUnitCycle), torso=S(rUnitCycle), rthigh=S(PI/2+rUnitCycle)/2, lthigh=S(PI/2+rUnitCycle)/2, rknee=S(rUnitCycle), lknee=S(rUnitCycle), rshoulder=S(PI-rUnitCycle)/2, relbow=S(rUnitCycle), lshoulder=S(PI-rUnitCycle)/2, lelbow=S(rUnitCycle), } } end function addJuggler(actors,opos,unitCycle) unitCycle=unitCycle%1 local rUnitCycle=unitCycle*TAU local rot={x=0,y=0,z=0} local rightThrow=.25+S(rUnitCycle)*.5 local leftThrow=-.25+S(rUnitCycle)*.5 actors[#actors+1]={ type="figure", body=fnsFigure.getBodyAcrobat(), pos=opos, rot=rot, pose={ pelvis=S(rUnitCycle), head=0, torso=S(rUnitCycle), rthigh=-.5+-S(rUnitCycle), lthigh=.5+-S(rUnitCycle), rknee=-.3, lknee=.3, rshoulder=rightThrow, relbow=rightThrow, lshoulder=leftThrow, lelbow=leftThrow, } } -- balls --rot.z? for i=0,5 do local ofs=i/5*TAU+rUnitCycle/5 actors[#actors+1]={ type="ball", pos={x=opos.x+C(ofs),y=opos.y+S(ofs*2)*2-3.5,z=opos.z}, rot=rot, } end end function addStrongmanTower(actors,opos,unitCycle) unitCycle=unitCycle%1 local rUnitCycle=unitCycle*TAU local men={ {pos={x=-2,y=0,z=0},side=-1}, {pos={x=0,y=0,z=0},side=0}, {pos={x=2,y=0,z=0},side=1}, {pos={x=-1,y=-2,z=0},side=-1}, {pos={x=1,y=-2,z=0},side=1}, {pos={x=0,y=-4,z=0},side=0}, } local rot={x=0,y=0,z=0} for _,man in ipairs(men) do local ofs=man.side*(unitCycle*TAU*4) actors[#actors+1]={ type="figure", body=fnsFigure.getBodyStrongman(), pos=pointTrans(opos,man.pos), rot=rot, pose={ pelvis=man.side*.3, head=0,--S(ofs)/2, torso=man.side,--S(ofs), rthigh=0,--S(ofs), lthigh=0,--S(ofs), rknee=0,---S(ofs), lknee=0,---S(ofs),--S(pMetrics.progress*TAU*2), rshoulder=man.side==-1 and -S(ofs)/2 or 0, relbow=man.side==-1 and -S(ofs) or 0, lshoulder=man.side==1 and -S(ofs)/2 or 0, lelbow=man.side==1 and -S(ofs) or 0, } } end end return { actorsToModels=actorsToModels, addTumbler=addTumbler, addJuggler=addJuggler, addStrongmanTower=addStrongmanTower, addStrongman=addStrongman, addSwinger=addSwinger, addCannonman=addCannonman, addRingmaster=addRingmaster, } end rift_sysrgb=function() --- Helpers for RGB colour -- @module sys.rgb return function(R) R=R or {} R.RGB={} --- Create a new RGB object -- @tparam number r 0-1 -- @tparam number g 0-1 -- @tparam number b 0-1 function R.RGB:new(r,g,b) local o={r=r,g=g,b=b} setmetatable(o,self) self.__index=self return o end --- Create a new RGB object by blending between to other RGB objects -- @param toRgb The RGB object to blend to -- @tparam number t (0 is all of self) -> (1 is all of toRgb) -- @return A new RGB object function R.RGB:getBlendTo(toRgb,t) local tInv=1-t return R.RGB:new( self.r*tInv+toRgb.r*t, self.g*tInv+toRgb.g*t, self.b*tInv+toRgb.b*t ) end --- Sets a colour in the TIC palette -- @tparam int i index (0-15) -- @param rgb An RGB object R.setRGB=function(i,rgb) local a=0x3FC0+i*3 poke(a,(rgb.r*255)//1) poke(a+1,(rgb.g*255)//1) poke(a+2,(rgb.b*255)//1) end --- Gets a colour from the TIC palette -- Useful for finding the current colour to blend from -- @tparam int i index (0-15) -- @return An RGB object R.getRGB=function(i) local a=0x3FC0+i*3 return R.RGB:new( peek(a)/255, peek(a+1)/255, peek(a+2)/255 ) end end end rift_simple3d=function() local M=math local S,C,A,R=M.sin,M.cos,M.abs,M.random local MIN,MAX,PI=M.min,M.max,M.pi CAMX,CAMY,CAMZ=0,0,0 local ANAPLANE=5 function setCam(x,y,z) CAMX,CAMY,CAMZ=x,y,z end function rot(a,b,r) return a*C(r)-b*S(r),a*S(r)+b*C(r) end function proj(p,xsh) local z=(p.z-CAMZ) local zD=5/z local anaSh=(ANAPLANE-z)*xsh*zD return { x=120+(p.x-CAMX)*zD*30+anaSh, y=68+(p.y-CAMY)*zD*30, z=z, r=p.r, } end end rift_pointsmodel=function() local M=math local S,C,MIN,MAX=M.sin,M.cos,M.min,M.max local XANASHIFT=.04 local COLOUR_DEPTH=10 PointsModel={} function PointsModel:new(drawFn) local o={ drawFn=drawFn, points={}, tris={}, } setmetatable(o,self) self.__index=self return o end function PointsModel:draw(drawList,xsh,c) return self.drawFn(self,drawList,xsh,c) end -- Collect the [...points] into one table function PointsModel:addPoints(...) local args={...} for _,points in pairs(args) do for _,p in pairs(points) do self.points[#self.points+1]=p end end end function PointsModel:addTris(tris) for _,t in pairs(tris) do self.tris[#self.tris+1]=t end end -- scale={x=x,y=y,z=z} function PointsModel:scale(scale) pointsScale(self.points,scale) return self end function PointsModel:trans(trans) pointsTrans(self.points,trans) return self end function PointsModel:rot(rot) pointsRot(self.points,rot) return self end function pointsMakeLine(p1,p2,steps,dr) local points={} local dx,dy,dz=p2.x-p1.x,p2.y-p1.y,p2.z-p1.z steps=steps-1 for i=0,steps do local v=i/steps points[#points+1]={ x=p1.x+v*dx, y=p1.y+v*dy, z=p1.z+v*dz, r=dr, } end return points end function pointsMakeArc(ang0,ang1,steps,dr) local points={} steps=steps-1 local aDiff=ang1-ang0 local first=#points+1 local last=first+steps for i=0,steps do local a=ang0+i/steps*aDiff points[#points+1]={x=C(a),y=S(a),z=0,r=dr} end return points end -- returns updated function pointTrans(p,t) return {x=p.x+t.x,y=p.y+t.y,z=p.z+t.z} end -- updates in place and returns function pointsScale(points,s) for _,p in pairs(points) do p.x,p.y,p.z=p.x*s.x,p.y*s.y,p.z*s.z end return points end -- updates in place and returns function pointsTrans(points,t) for _,p in pairs(points) do p.x,p.y,p.z=p.x+t.x,p.y+t.y,p.z+t.z end return points end -- updates in place and returns function pointsRot(points,r) for _,p in pairs(points) do p.y,p.z=rot(p.y,p.z,r.x) p.x,p.z=rot(p.x,p.z,r.y) p.x,p.y=rot(p.x,p.y,r.z) end return points end function copy(obj, seen) if type(obj) ~= 'table' then return obj end if seen and seen[obj] then return seen[obj] end local s = seen or {} local res = setmetatable({}, getmetatable(obj)) s[obj] = res for k, v in pairs(obj) do res[copy(k, s)] = copy(v, s) end return res end function drawDots(model,drawList,sideShift) local points=model.points local xsh=sideShift*XANASHIFT for i=1,#points do local point=proj(points[i],xsh) if point.z>0 then local r=point.r or 1 local dScale=r/point.z local r=MIN(80,10*dScale) local c=MAX(1,MIN(3,dScale*COLOUR_DEPTH)) drawList[#drawList+1]={t="c",x=point.x,y=point.y,z=point.z,r=r,c=c} end end return drawList end function drawLines(model,drawList,sideShift) local points=model.points local xsh=sideShift*XANASHIFT local lastPoint=nil for i=1,#points do local point=proj(points[i],xsh) if i>1 and point.z>0 then local c=MAX(1,MIN(3,point.z*COLOUR_DEPTH)) drawList[#drawList+1]={ t="l", z=point.z, c=c, x0=lastPoint.x, y0=lastPoint.y, x1=point.x, y1=point.y, } end lastPoint=point end return drawList end -- ramArea: 'sprite' or 'tile' -- sideShift: -1, 0, 1 function drawTTris(ramArea,chromakey) local texsrc=(ramArea=='tile') and 0 or 1 return function(model,drawList,sideShift) local points=model.points local xsh=sideShift*-XANASHIFT local ps={} for i=1,#points do ps[i]=proj(points[i],xsh) end for _,t in ipairs(model.tris) do local render=true -- #TODO: render==false should maybe be colour 0, non-transparent? if t.render then if t.render=="l" then render=(sideShift==-1) elseif t.render=="r" then render=(sideShift==1) end end if render then local tp0=t.points[1] local tp1=t.points[2] local tp2=t.points[3] local p0=ps[tp0.p] local p1=ps[tp1.p] local p2=ps[tp2.p] if p0.z and p1.z>0 and p2.z>0 then -- c for colour depth swap drawList[#drawList+1]={ t="x", -- #TODO: do we need this? z=(p0.z+p1.z+p2.z)/3, x0=p0.x,y0=p0.y,z0=p0.z, x1=p1.x,y1=p1.y,z1=p1.z, x2=p2.x,y2=p2.y,z2=p2.z, tx0=tp0.tx,ty0=tp0.ty, tx1=tp1.tx,ty1=tp1.ty, tx2=tp2.tx,ty2=tp2.ty, texsrc=texsrc, chromakey=chromakey, } end end end return drawList end end end rift_sceneblank=function() R=R or {} rift_demopart()(R) return R.DemoPart:new({ bdr=function(y,pMetrics) end, tic=function(pMetrics) if pMetrics.isFirstRun then anaglyphSetPalette(0) end cls() music(2) setGlobalVolume(0) end, }) end rift_modelfont=function() return function(text) local model = PointsModel:new(drawTTris('tile',0)) local points={} local tris={} local x=-0.5 local mult=1/150 for iChar=1,#text do local props=FONT:getCharProperties(text:sub(iChar,iChar)) if props then local iSpr=props.iSpr local iSprY=iSpr//32 iSpr=iSpr-iSprY*16 local iSprX=iSpr local srcX0=iSprX*8 local srcY0=iSprY*8 local srcX1=srcX0+props.pixW local srcY1=srcY0+props.pixH local destX00=x local destY00=0 local destX10=destX00+props.pixW local destY10=destY00 local destX01=destX00 local destY01=destY00+props.pixH local destX11=destX00+props.pixW local destY11=destY00+props.pixH points[#points+1]={x=destX00*mult,y=destY00*mult,z=0} local p00=#points points[#points+1]={x=destX10*mult,y=destY10*mult,z=0} points[#points+1]={x=destX11*mult,y=destY11*mult,z=0} points[#points+1]={x=destX01*mult,y=destY01*mult,z=0} tris[#tris+1]={points={{p=p00,tx=srcX0,ty=srcY0},{p=p00+1,tx=srcX1,ty=srcY0},{p=p00+2,tx=srcX1,ty=srcY1}}, render=""} tris[#tris+1]={points={{p=p00+2,tx=srcX1,ty=srcY1},{p=p00+3,tx=srcX0,ty=srcY1},{p=p00,tx=srcX0,ty=srcY0}}, render=""} x=x+props.pixW+1 else x=x+30 end end -- centre for _,p in ipairs(points) do p.x=p.x-(x*mult)/2 p.y=p.y-(24*mult)/2 end model:addPoints(points) model:addTris(tris) return model end end rift_modeldisc=function() local S,C,PI=math.sin,math.cos,math.pi local TAU=PI*2 return function(w,h,nAngles,tx,ty) local points={} local tris={} points[1]={x=0,y=0,z=0} for i=0,nAngles do local angle=i/nAngles*TAU local x=C(angle)*w local y=S(angle)*h local nPoint=#points+1 points[nPoint]={x=x,y=y,z=0} if i>0 then tris[#tris+1]={points={{p=1,tx=tx,ty=ty},{p=nPoint-1,tx=tx,ty=ty},{p=nPoint,tx=tx,ty=ty}}} end end local model=PointsModel:new(drawTTris('tile',-1)) model:addPoints(points) model:addTris(tris) return model end end rift_demorunner=function() --- Functions to control demo timing and coordinate parts -- @module demo.demorunner -- @author jtruk return function(R) R=R or {} rift_soundmusic()(R) rift_sysmath()(R) R.DemoRunner={} --- Create a new DemoRunner object. function R.DemoRunner:new() local o={ isShowDebug=false, parts={}, currentPart=nil, isPartFirstRun=true, prevMusicFrame=0, cuedMusic=nil, musicFrames=0, iCleanupPart=nil, prevFrameBDRms=nil, } setmetatable(o,self) self.__index=self return o end --- Add a demo part that runs for a number of tics. -- @tparam DemoPart part -- @tparam ?int tics (default=100) function R.DemoRunner:addPartTics(part, tics) tics=tics or 100 self.parts[#self.parts+1]={type='tics',part=part,tics=tics} end --- Add a demo part that runs for [frames] music frames. -- @tparam DemoPart part -- @tparam int frames (default=1) function R.DemoRunner:addPartMusic(part, frames) frames=frames or 1 self.parts[#self.parts+1]={type='music',part=part,frames=frames} end --- Cue a track to start. -- @tparam int track function R.DemoRunner:addCueMusic(track, frame) frame = frame or -1 self.parts[#self.parts+1]={type='cue-music',track=track,frame=frame} end --- Cue a track to stop at this point. function R.DemoRunner:addStopMusic() self.parts[#self.parts+1]={type='stop-music'} end --- Switch the debug display on or off. -- @tparam bool flag function R.DemoRunner:setShowDebug(flag) self.isShowDebug=flag end --- Run the demorunner (call this once per tic). -- @treturn bool isExit - if this is true then the demo runner is done function R.DemoRunner:run() if self.currentPart==nil then -- do preboots... for i=1,#self.parts do local part=self.parts[i] -- yuk if part.part and part.part.preboot then part.part.preboot() end end self.currentPart=1 end -- Repeat over the non-frame ending operations. -- Also manage exiting. repeat if self.iCleanupPart~=nil then local partDef=self.parts[self.iCleanupPart] if partDef.part and partDef.part.cleanup then partDef.part.cleanup() end self.iCleanupPart=nil end if self.currentPart>#self.parts then return true end local partDef=self.parts[self.currentPart] local incPart,isFrame=self:doCurrentPart(partDef) if incPart then self.iCleanupPart=self.currentPart self.currentPart=self.currentPart+1 self.isPartFirstRun=true end until(isFrame) return false end --- Run the demorunner (call this once per tic). -- @local -- @tparam DemoPart partDef -- @treturn {incPart,isFrame} -- -- incPart -> Move on to next demopart. -- isFrame -> true if we should end this frame, if it's false then keep running through demoparts without releasing control to the main loop. function R.DemoRunner:doCurrentPart(partDef) if partDef.type=='cue-music' then self.cuedMusic={track=partDef.track,frame=partDef.frame} return true,false elseif partDef.type=='stop-music' then music() return true,false end local part=partDef.part local partMetrics={ isFirstRun=self.isPartFirstRun, isLastRun=false, progress=0, musFrameProgress=nil, } partMetrics.ticsTotal=R.getTics() if partMetrics.isFirstRun then self.startTics=partMetrics.ticsTotal self.musicFrames=0 end partMetrics.tics=partMetrics.ticsTotal-self.startTics local musPos=R.music.getPos() local musMeta=R.music.getTrackMeta(musPos.track) local isEndOfPart=false if partMetrics.isFirstRun and self.cuedMusic then music(self.cuedMusic.track,self.cuedMusic.frame) musPos.pattern=self.cuedMusic.frame self.prevMusicFrame=self.cuedMusic.frame self.cuedMusic=nil end if partDef.type=='tics' then partMetrics.progress=partMetrics.tics/partDef.tics partMetrics.isLastRun=partMetrics.progress>=1 elseif partDef.type=='music' then -- #TODO: This won't work if we're jumping music frames if musPos.pattern~=self.prevMusicFrame then self.musicFrames=self.musicFrames+1 partMetrics.isLastRun=self.musicFrames>=partDef.frames end local elapsed=partMetrics.tics/60 local rowsDone=elapsed*musMeta.rowsPerSec local frames,frameProgress=math.modf(rowsDone/musMeta.rows) partMetrics.musFrameProgress=frames+frameProgress -- This is the actual music progress: -- #TODO~: Sync if this falls behind? -- partMetrics.musFrameProgress=self.musicFrames+musPos.row/64 partMetrics.progress=(partMetrics.musFrameProgress)/partDef.frames self.prevMusicFrame=musPos.pattern end partMetrics.progress=R.clamp(partMetrics.progress,0,1) local ts=time() local isPartExit=part.tic(partMetrics) local elapsed=time()-ts -- This means extra function calls when debugging on each scanline, which is suboptimal! if not part.bdr then self.prevFrameBDRms=nil BDR=nil elseif self.isShowDebug==false then -- run as optimally as possible... BDR=function(y) part.bdr(y,partMetrics) end else -- NB The palette switch sets vbank to 1 behind the user's back - not ideal -- But I don't think we have access to the current vbank id to reset it -- Assume vbank=0 is desirable for exit -- #TODO: Maybe wrap vbank() call to track it? local stashPal={} local debugPal={64,64,64,255,255,255,255,64,128} BDR=function(y) if y==0 then ts=time() end if y==140 then -- Unstash palette (see stashing below) for i=1,#stashPal do poke(0x3fc0+i+2,stashPal[i]) end end part.bdr(y,partMetrics) -- Switch to alternate palette for rendering the debug info... if y>=132 and y<=139 then vbank(1) if y==132 then -- stash palette for i=1,#debugPal do stashPal[i]=peek(0x3fc0+i+2) end end for i=1,#debugPal do poke(0x3fc0+i+2,debugPal[i]) end end if y==143 then -- Final BDR line - get frame time self.prevFrameBDRms=time()-ts end end end self.isPartFirstRun=false partMetrics.isLastRun=partMetrics.isLastRun or isPartExit if self.isShowDebug then vbank(1) rect(0,128,240,136,1) rect(0,128,partMetrics.progress*240,136,3) print(string.format("T:%.1f", R.getTimeMS()/1000),2,129,2,true) print(string.format("TIC:%d", R.getTics()),50,129,2,true) -- NB: This is the previous frame's BDR time! local prevFrameBDRms=(self.prevFrameBDRms==nil) and 0 or self.prevFrameBDRms local allTime=elapsed+prevFrameBDRms print(string.format("FR:%2d(%2d/%2d)", allTime//1, elapsed//1, prevFrameBDRms//1),100,129,2,true) local musText=musPos.track==255 and "M: (none)" or string.format("M:%02d-%02d-%02d", musPos.track, musPos.pattern, musPos.row) print(musText,180,129,2,true) local doTimeBar=function(x,h) local ySt=127-h line(x,ySt,x,127,1) rect(x+1,ySt,10,h+1,8) line(x+11,ySt,x+11,127,15) end doTimeBar(20*6,allTime) doTimeBar(23*6,elapsed) doTimeBar(26*6,prevFrameBDRms) end return partMetrics.isLastRun,true end end end rift_sceneintro=function() R=R or {} local S,C=math.sin,math.cos local PI=math.pi local TAU=PI*2 rift_demopart()(R) local getModelDeadlineLogo=rift_modeldeadlinelogo() local getModelStar=rift_modelstar() local getModel3DFont=rift_modelfont() local fnMakePlane=rift_modelplane() local fnsTicket=rift_layerticket() local SHAPSHOT_TAKEN=false return R.DemoPart:new({ bdr=function(y,pMetrics) end, tic=function(pMetrics) if pMetrics.isFirstRun then basePixelsToBank() anaglyphSetPalette(1) CAMX,CAMY,CAMZ=0,0,0 end local fadev=fader(pMetrics.progress,0.2,1) anaglyphSetPalette(fadev) local modelsScene={} local nStars=50 local swirlMult=(TAU/nStars)*(10+S(pMetrics.progress)*3) local swirlTwist=S(pMetrics.progress*TAU)*3 for i=1,nStars do local zDist=(i/nStars+pMetrics.progress*3)%1 local swirl=(i*swirlMult+pMetrics.progress)+swirlTwist local x=C(swirl)*3 local y=S(swirl)*3 local z=100-(zDist*98)--30+S(i)*28--+S(pMetrics.progress*20)*2 local r=i*30+pMetrics.progress*100 modelsScene[#modelsScene+1]=getModelStar():rot({x=0,y=0,z=r}):trans({x=x,y=y,z=z}) end if not SHAPSHOT_TAKEN then local x=0 local y=0 local z=3.5+S(pMetrics.progress*20+PI)*2.5 local r=(pMetrics.progress*TAU*3-PI/2) modelsScene[#modelsScene+1]=getModelDeadlineLogo():rot({x=0,y=r,z=S(r)*.3}):trans({x=x,y=y,z=z}) end local modelsTicket={} if SHAPSHOT_TAKEN then modelsTicket={ fnsTicket.getModel(pMetrics.ticsTotal,0,0,0) } end local modelsText={ getModel3DFont("SHORT"):trans({x=-.09,y=-.1,z=1}), getModel3DFont("CIRCUS"):trans({x=.07,y=.1,z=1}), } -- Take snapshot if not SHAPSHOT_TAKEN and pMetrics.progress>.75 then vbank(0) cls() local textScale={x=.5,y=.5,z=1} modelsText[#modelsText+1]=getModel3DFont("ADMIT ONE"):scale(textScale):trans({x=.13,y=.25,z=1}), anaglyphSwitchRGBsForBakedMix() renderModels(modelsScene,0) renderModelsText(modelsText,0) fnsTicket.takeSnapshot() copyTileRAMToBank(2) SHAPSHOT_TAKEN=true cls() end -- Draw vbank(1) cls() anaglyphSwitchRGBsForLeft() copyBankToTileRAM(1) renderModels(modelsScene,-1) copyBankToTileRAM(2) renderModels(modelsTicket,-1) -- print("IN HONOUR OF DEADLINE'S 10th ANNIVERSARY") -- print("RIFT INVITE YOU TO",0,10) if not SHAPSHOT_TAKEN then copyBankToTileRAM(0) renderModelsText(modelsText,-1) end anaglyphGrab() cls() vbank(0) cls() anaglyphResetRGBs() copyBankToTileRAM(1) renderModels(modelsScene,1) copyBankToTileRAM(2) renderModels(modelsTicket,1) if not SHAPSHOT_TAKEN then copyBankToTileRAM(0) renderModelsText(modelsText,1) end anaglyphMix() end, }) end rift_sceneclownface=function() R=R or {} local S,C,MIN,MAX=math.sin,math.cos,math.min,math.max local PI=math.pi local TAU=PI*2 rift_demopart()(R) local fnMakePlane=rift_modelplane() local getModel3DSpecs=rift_model3dspecs() local getModel3DFont=rift_modelfont() local getModelDisc=rift_modeldisc() local addTunnelRenderModels=rift_layertunnel() local GREETZ={ "", "", "ATE BIT", "BYTEJAMMERS", "DARKLITE", "DESIRE", "GHOSTTOWN", "HENEARXN", "HOOY-PROGRAM", "ICEOLATE", "IVORY LABS", "LAME", "LOGICOMA", "LSQUARED", "MARQUEE DESIGN", "MFX", "NE-SEVEN", "NESBOX", "NEWLINE", "NUANCE", "OFFENCE", "OFTENHIDE", "PLAYPSYCO", "POOBRAIN", "QUADTRIP", "RABENAUGE", "SMFX", "SLIPSTREAM", "SPECTRALS", "SPINNING CUBE", "STARCHASER", "TEK", "TPOLM", "TPOLUAM", "TUHB", "(AND YOU)", "(AND YOU)", "(AND YOU)", "(AND YOU)", "(AND YOU)", "(AND YOU)", } function addDisc(models,x,y,z,w,h,colour) local model=PointsModel:new(drawTTris('tile',-1)) local discData=getModelDisc(w,h,16,colour,0) local ps=discData.points local ps=pointsTrans(pointsRot(ps,{x=0,y=0,z=0}),{x=x,y=y,z=z}) model:addPoints(ps) model:addTris(discData.tris) models[#models+1]=model end function addFaceModels(models,pose) -- face addDisc(models,0,-.2,0,1,1.5,3) addDisc(models,0,.4,0,1.3,1,3) -- nose addDisc(models,0,0,-.5,.6,.3,0) -- leye addDisc(models,-.3,-.6,-.1,.3,.5,1) addDisc(models,-.3,-.5,-.15,.3,.2,2) addDisc(models,-.3+pose.lEyeX*.18,-.5+pose.lEyeY*.1,-.17,.1,.1,0) -- reye addDisc(models,.3,-.6,-.1,.3,.5,1) addDisc(models,.3,-.5,-.15,.3,.2,2) addDisc(models,.3+pose.rEyeX*.18,-.5+pose.rEyeY*.1,-.17,.1,.1,0) -- mouth addDisc(models,pose.mouthX*.2,.7+pose.mouthY*.2,-.3,.4+pose.mouthW*.3,.2+pose.mouthH*.1,1) end return R.DemoPart:new({ bdr=function(y,pMetrics) end, tic=function(pMetrics) if pMetrics.isFirstRun then basePixelsToBank() anaglyphSetPalette(1) CAMX,CAMY,CAMZ=0,0,0 end vbank(0) cls() local modelsFace={} addFaceModels(modelsFace, { rEyeX=S(pMetrics.progress*22), rEyeY=S(pMetrics.progress*36), lEyeX=S(pMetrics.progress*30), lEyeY=S(pMetrics.progress*19), mouthX=S(pMetrics.progress*12), mouthY=S(pMetrics.progress*14), mouthW=S(pMetrics.progress*195), mouthH=S(pMetrics.progress*167), }) local faceMoveMult=MAX(0.8-pMetrics.progress,0)^.7 local x=0 local y=0 local zMove=C(pMetrics.progress*TAU*4)*3 for _,model in ipairs(modelsFace) do model:rot({x=faceMoveMult*S(pMetrics.progress*11)*.3,y=faceMoveMult*S(pMetrics.progress*7)*.2,z=faceMoveMult*S(pMetrics.progress*10)*.5}) model:trans({x=faceMoveMult*S(pMetrics.progress*9.6),y=faceMoveMult*S(pMetrics.progress*4.6),z=faceMoveMult*zMove+6}) end local x=0 local y=-.17-10+MIN(pMetrics.progress+.2,1)*10 local z=2 local r=0 local scale=.6 local modelsSpecs={ getModel3DSpecs():scale({x=scale,y=scale,z=scale}):rot({x=0,y=r,z=r/2}):trans({x=x,y=y,z=z}) } local textScale={x=1,y=1,z=1} local modelsText={} if pMetrics.progress>.9 then modelsText[#modelsText+1]=getModel3DFont("DEAL WITH IT"):scale(textScale):trans({x=0,y=.35,z=1}) end local iGreet=(pMetrics.progress*#GREETZ)//1+1 local greet=GREETZ[iGreet] if greet then local partial=1+(#GREETZ*pMetrics.progress)-iGreet local rot={x=S(pMetrics.progress*21)*.3,y=S(pMetrics.progress*14)*.3,z=S(pMetrics.progress*10)*.1} modelsText[#modelsText+1]=getModel3DFont(greet):scale(textScale):rot(rot):trans({x=0,y=-.28,z=1}) end -- Draw local txsh=-2 vbank(1) cls() anaglyphSwitchRGBsForLeft() copyBankToTileRAM(1) renderModels(modelsFace,-1) renderModels(modelsSpecs,-1) copyBankToTileRAM(0) renderModelsText(modelsText,-1) anaglyphGrab() cls() vbank(0) cls() anaglyphResetRGBs() copyBankToTileRAM(1) renderModels(modelsFace,1) renderModels(modelsSpecs,1) copyBankToTileRAM(0) renderModelsText(modelsText,1) anaglyphMix() end, }) end rift_demopart=function() --- A section of a demo (usually viewed for a few seconds upwards, or one/multiple frames of music) -- @module demo.demopart -- @author jtruk return function(R) R=R or {} R.DemoPart={} --- Create a new DemoPart object. -- @tparam {tab} -- preboot (func) () -- cleanup (func) () -- tic (func) (partMetrics) -- partMetrics={ -- isFirstRun -- isLastRun, -- progress -- musFrameProgress -- } -- bdr (func) (y, partMetrics) function R.DemoPart:new(o) setmetatable(o,self) self.__index=self return o end --[[ function R.DemoPart:setPos(pos) self.pos=pos end --]] end end rift_layerticket=function() local fnMakePlane=rift_modelplane() local S,C=math.sin,math.cos local TICKET_W,TICKET_H=128,96 local TICKET_H_SCALE=TICKET_H/TICKET_W -- ticket: 0,0 -> 127,128 function takeSnapshot() local w,h=128,96 local x0,y0=120-TICKET_W/2,68-TICKET_H/2 rectb(x0,y0,TICKET_W-1,TICKET_H-1,2) swizzleScreenToVRAM('tile',x0,y0,TICKET_W-1,TICKET_H-1,0,0) end function getModel(tics,fadeWobble,fadeRipple,fadeMove) local model = PointsModel:new(drawTTris('tile',-1)) local planeData=fnMakePlane(1,TICKET_H_SCALE,8,8,0,0,TICKET_W,TICKET_H) local ps=planeData.points local x=(5.5*C(tics*.05))*fadeMove local y=(3*S(tics*.07))*fadeMove local z=2.35+S(tics/20)*1 local rotZ=(S(tics/10)*.3+S(tics/18)*.2)*fadeWobble local ps=planeData.points for _,p in ipairs(ps) do local d=(p.x^2+p.y^2)^.5 p.z=p.z+S(d+tics*.1+p.x+p.y)*fadeRipple end model:addPoints(ps) model:addTris(planeData.tris) return model:rot({x=0,y=0,z=rotZ}):trans({x=x,y=y,z=z}) end return { takeSnapshot=takeSnapshot, getModel=getModel, } end rift_fontfont=function() --- Functions to handle Font data. -- These are 'full fat' fonts, using their own special print function, rather than system fonts, which use the standard print. -- @module gfx.font -- @author Decca, Mantratronic return function(R) R=R or {} R.Font={} --- Create a new Font object function R.Font:new() local o={ -- this could be useful for compression later font={}, blitSegment=niƒ9l, colourMap=nil, } setmetatable(o,self) self.__index=self return o end --- Load font data into our object -- @tparam table font Font data (RLE, or RAW) function R.Font:load(font) if(font.rle) then self:_loadRLE(font) else self:_loadRAW(font) end self.blitSegment=self:_getBlitSegmentValue(font.bitsPerPixel) self.colourMap=self:_getDefaultColourMap(font.bitsPerPixel) self:_init(font) end -- set blit segment (2=4bpp, 4=2bpp, 8=1bpp) function R.Font:_getBlitSegmentValue(bitsPerPixel) local map={[1]=8,[2]=4,[4]=2} return map[bitsPerPixel] end -- We remap the colours before printing -- If you're tight on cycles, this could be optimised/worked around function R.Font:_getDefaultColourMap(bitsPerPixel) local map={ [1]={[0]=0,[1]=1}, [2]={[0]=0,[1]=1,[2]=2,[3]=3}, [4]={[0]=0,[1]=1,[2]=2,[3]=3,[4]=4,[5]=5,[6]=6,[7]=7,[8]=8,[9]=9,[10]=10,[11]=11,[12]=12,[13]=13,[14]=14,[15]=15}, } return map[bitsPerPixel] end --- Load font data -- @tparam table font Font data (RLE) -- @local function R.Font:_loadRLE(font) local str=font.rle local o=tonumber(str:sub(1,5),16) -- get (o)ffset local w=tonumber(str:sub(6,7),16)*8-1 -- get (w)idth local e=str:sub(8,str:len()) -- remove header to get (e)ncoded data local d = "" -- (d)ecoded data for m, c in e:gmatch("(%u+)([^%u]+)") do -- decode rle, (m)atch & (c)ounter d = d .. m .. (m:sub(-1):rep(c)) end local y=0 for x = 1,#d,1 do -- write to mem local c=string.byte(d:sub(x,x))-65 -- get (c)olor value poke4(o+y,c) y=y+1 if y>w then y=0 o=o+1024 end end end --- Load font data -- @tparam table font Font data (RAW) -- @local function R.Font:_loadRAW(font) local str=font.raw local tnb=tonumber local o=tonumber(str:sub(1,5),16) -- get (o)ffset local w=tnb(str:sub(6,7),16)*8-1 -- get (w)idth local d=str:sub(8,str:len()) -- remove header to get (d)ata local y=0 local cs={} for x = 1,#d,1 do -- write to mem local c=tnb(d:sub(x,x),16) -- get (c)olor value poke4(o+y,c) y=y+1 if y>w then y=0 o=o+1024 end end end --- Init font data -- Decode the font data -- @tparam table font Font data (unpacked) -- @local function R.Font:_init(font) for i=1,#font.layout do self.font[font.chars:sub(i,i)]=font.layout[i] end end --- Get the width of a string when printed -- @tparam ?number kx Kerning in pixels (defaults to 1) function R.Font:width(str,kx) kx = kx or 1 local pcx = 0 for i=1,#str do local letter = self.font[str:sub(i,i)] -- update kerning pcx = pcx + kx + (letter and letter[5] or 0) end return pcx end -- We can remap the colours before printing -- use something like: {[1]=10,[2]=11,[3]=12} function R.Font:setColourMap(colourMap) self.colourMap=colourMap end -- Use this before rendering if you are drawing yourself. print automatically calls this -- returns resetValues function R.Font:renderPre() local resetValues={ blit=peek4(2*0x03ffc), colours={}, } -- set blit segment (bits per pixel) poke4(2*0x3ffc, self.blitSegment) -- set colour map for iColour,switchColour in ipairs(self.colourMap) do local a=2*0x03FF0+iColour resetValues.colours[iColour]=peek4(a) poke4(a,switchColour) end return resetValues end -- Use this after rendering if you are drawing yourself. print automatically calls this -- use resetValues from renderPre function R.Font:renderPost(resetValues) -- Reset the values poke4(2*0x03ffc,resetValues.blit) -- set colour map for iColour,switchColour in ipairs(resetValues.colours) do poke4(2*0x03FF0+iColour,switchColour) end end --- Print a string -- @tparam string str -- @tparam number x Screen X -- @tparam number y Screen Y -- @tparam ?number kx X kerning in pixels (defaults to 1) -- @tparam ?number ky Y kerning in pixels (defaults to 1) function R.Font:print(str,tx,ty,kx,ky) local lineHeight=24 -- #TODO: in font data kx = kx or 1 ky = ky or 1 tc = tc or 10 local pcx = 0 local pcy = 0 local resetValues=self:renderPre() -- print each letter for i=1,#str do local char=str:sub(i,i) if char=="\n" then pcx=0 pcy=pcy+lineHeight+ky else local letter = self.font[char] if letter then spr(letter[1],tx+pcx,ty+pcy,0,1,0,0,letter[3],letter[4]) -- update kerning pcx = pcx + letter[5] + kx end end end self:renderPost(resetValues) end function R.Font:getCharProperties(char) local letter = self.font[char] if not letter then return nil end return { iSpr=letter[1], --[2]? sprW=letter[3], sprH=letter[4], pixW=letter[5], pixH=letter[6], } end end end rift_modelstar=function() local S,C=math.sin,math.cos local PI=math.pi local TAU=PI*2 return function() local model=PointsModel:new(drawTTris('tile',0)) local points={} local tris={} local d=.4 for p=0,4 do local a=TAU/5*p local x,y=C(a),S(a) points[#points+1]={x=x,y=y,z=0} end for p=0,4 do local a=TAU/5*p+TAU/10 local x,y=C(a)*d,S(a)*d points[#points+1]={x=x,y=y,z=0} end for p=0,4 do local p2=6+(p+1)%5 local p3=6+(p-2)%5 tris[#tris+1]={points={{p=p+1,tx=1,ty=0},{p=p2,tx=1,ty=0},{p=p3,tx=1,ty=0}}} end model:addPoints(points) model:addTris(tris) return model end end rift_anaglyph=function() local R=R or {} rift_sysrgb()(R) local ANAGLYPH_GRAB={} -- red: left in foreground (0,1,2,3) -- cyan: right in foreground (0,4,8,12) function anaglyphSetPalette(strength) for iR=0,3 do for iC=0,3 do local r=(iR==0) and 0 or (.5+.5*iR/3)*strength local c=(iC==0) and 0 or (.5+.5*iC/3)*strength R.setRGB(iR*4+iC,R.RGB:new(r,c,c)) end end end -- Puts a screen full of pixels into the GRAB table -- (this could be optimised) function anaglyphGrab() for i=0,16319 do ANAGLYPH_GRAB[i]=peek(i) end end -- Mixes a screen full of pixels with the GRAB table -- (this could be optimised) function anaglyphMix() for i=0,16319 do poke(i,ANAGLYPH_GRAB[i]|peek(i)) end end -- Resets colour mapping 1-3 -> (red) 1-3 function anaglyphResetRGBs() local a=0x3ff0*2 poke4(a+1, 1) poke4(a+2, 2) poke4(a+3, 3) end -- Maps colour indices 1-3 -> (cyan) 4,8,12 function anaglyphSwitchRGBsForLeft() local a=0x3ff0*2 poke4(a+1, 4) poke4(a+2, 8) poke4(a+3, 12) end -- Maps colour indices 1-3 -> (whitescale) 5,10,15 function anaglyphSwitchRGBsForBakedMix() local a=0x3ff0*2 poke4(a+1, 5) poke4(a+2, 10) poke4(a+3, 15) end -- Maps colour indices 1-3 to black function anaglyphSwitchRGBsForShadow() local a=0x3ff0*2 poke4(a+1, 0) poke4(a+2, 0) poke4(a+3, 0) end end rift_scenetent=function() R=R or {} local S,C,PI,A,MIN=math.sin,math.cos,math.pi,math.abs,math.min local TAU=PI*2 rift_demopart()(R) local fnMakePlane=rift_modelplane() local fnsFigure=rift_modelfigure() local fnsTicket=rift_layerticket() local getFont3DModel=rift_modelfont() function setupTextures() cls() for x=-8,64,32 do rect(x,0,16,64,3) end copyBankToTileRAM(1) swizzleScreenToVRAM('tile',0,0,64,64,0,64) copyTileRAMToBank(1) cls() end function getPosedRingmasterModel(unitProgress) local model=PointsModel:new(drawTTris(1)) local ofs=unitProgress*TAU*4 local pose={ pelvis=0,--S(ofs), head=0,--S(ofs)/2, torso=0,---S(ofs), rthigh=-.6,--S(unitProgress*TAU*2), lthigh=.6, rknee=0,--S(ofs), lknee=0,--S(ofs),--S(unitProgress*TAU*2), rshoulder=S(ofs), relbow=S(ofs), lshoulder=-S(ofs), lelbow=-S(ofs), } local ex={ bounce=0, } return fnsFigure.getModel(fnsFigure.getBodyRingmaster(),pose,ex) end return R.DemoPart:new({ bdr=function(y,pMetrics) end, tic=function(pMetrics) if pMetrics.isFirstRun then setupTextures() vbank(0) anaglyphSetPalette(1) CAMX,CAMY,CAMZ=0,0,0 end local fadev=fader(pMetrics.progress*1.334,0,.9) anaglyphSetPalette(fadev) CAMZ=-40+S(pMetrics.progress*PI/2)*35 CAMY=0---.4+A(S(pMetrics.progress*64))*.05 local modelsScene={} local nWalls=10 local xTent=0 local zTent=0 for i=0,nWalls-1 do local model=PointsModel:new(drawTTris('tile',-1)) local a=i*TAU/nWalls+pMetrics.progress*2-1.6 local d=6 local xWall,zWall=xTent+C(a)*d,zTent+S(a)*d local planeData if i==0 then planeData=fnMakePlane(1,1,8,8,0,64,64,64) else planeData=fnMakePlane(1,1,4,4,0,64,64,64) end local y=0 local ps=planeData.points if i==0 then for _,p in ipairs(ps) do -- remap to 0-1 local xP=(p.x+1)/2 local yP=(p.y+1)/2 local shift=pMetrics.progress xP=xP*(1-shift*yP^2) p.x=(xP*2-1)*(-i) p.y=yP*2-1 end end ps=pointsTrans(pointsRot(ps,{x=0,y=a+PI/2,z=0}),{x=xWall,y=y,z=zWall}) model:addPoints(ps) model:addTris(planeData.tris) modelsScene[#modelsScene+1]=model -- make roof local model=PointsModel:new(drawTTris('tile',-1)) local ps={ {x=0,y=-2,z=d}, {x=-1,y=0,z=0}, {x=1,y=0,z=0}, } local tris={{points={{p=1,tx=32,ty=64},{p=2,tx=0,ty=128},{p=3,tx=64,ty=128}}}} ps=pointsTrans(pointsRot(ps,{x=0,y=a+PI/2,z=0}),{x=xWall,y=y-1,z=zWall}) model:addPoints(ps) model:addTris(tris) modelsScene[#modelsScene+1]=model end local ticketAnimate=MIN(1,pMetrics.progress*5) local sc=1-pMetrics.progress*.6 local ticketScale={x=sc,y=sc,z=1} local zTicket=CAMZ+(S(pMetrics.progress*TAU*3.2))*4+pMetrics.progress*18 local modelsTicket={ fnsTicket.getModel(pMetrics.ticsTotal,ticketAnimate,ticketAnimate,pMetrics.progress) :scale(ticketScale) :trans({x=0,y=0,z=zTicket}) } local ringmasterScale={x=.5,y=.5,z=1} modelsScene[#modelsScene+1]=getPosedRingmasterModel(pMetrics.progress) :scale(ringmasterScale) :trans({x=-1,y=.5,z=zTent-8}) modelsText={ getFont3DModel("ROLL UP"):trans({x=0,y=-.3,z=-30}), getFont3DModel("ROLL UP"):trans({x=0,y=-.2,z=-27}), getFont3DModel("FOR THE"):trans({x=0,y=-.1,z=-24}), getFont3DModel("10TH"):trans({x=0,y=0,z=-21}), getFont3DModel("ANNIVERSARY"):trans({x=0,y=.1,z=-18}), getFont3DModel("DEADLINE"):trans({x=0,y=.2,z=-15}), getFont3DModel("DEMOPARTY"):trans({x=0,y=.3,z=-12}), } -- Draw local txsh=-2 vbank(1) cls() anaglyphSwitchRGBsForLeft() copyBankToTileRAM(1) renderModels(modelsScene,-1) copyBankToTileRAM(2) renderModels(modelsTicket,-1) copyBankToTileRAM(0) renderModelsText(modelsText,-1) anaglyphGrab() anaglyphResetRGBs() cls() vbank(0) cls() copyBankToTileRAM(1) renderModels(modelsScene,1) copyBankToTileRAM(2) renderModels(modelsTicket,1) copyBankToTileRAM(0) renderModelsText(modelsText,1) anaglyphMix() end, }) end local R={} rift_anaglyph() rift_simple3d() rift_pointsmodel() rift_text3d() local IS_DEBUG=false FONT=nil COLOUR_MAP_L={[1]=4,[2]=8,[3]=12} COLOUR_MAP_R={[1]=1,[2]=2,[3]=3} COLOUR_MAP_REV={[1]=0,[2]=0,[3]=0} XANASHIFT=.04 local MAX,MIN=math.max,math.min rift_syssys()(R) rift_demorunner()(R) rift_fontfont()(R) --rift_soundmusic()(R) DEMO_RUNNER=R.DemoRunner:new() DEMO_RUNNER:setShowDebug(IS_DEBUG) DEMO_RUNNER:addPartTics(rift_sceneblank(),50) DEMO_RUNNER:addPartTics(rift_scene3dspecs(),1000) DEMO_RUNNER:addPartTics(rift_sceneintro(),1000) DEMO_RUNNER:addCueMusic(0,0) DEMO_RUNNER:addPartMusic(rift_scenetent(),2) DEMO_RUNNER:addPartMusic(rift_sceneacrobats(),4) DEMO_RUNNER:addPartMusic(rift_sceneclownface(),2) DEMO_RUNNER:addPartMusic(rift_scenecredits(),4) DEMO_RUNNER:addPartTics(rift_sceneblank(),200) TIC=R.ticWrap(function() R.tryBreak() R.hideMouse() -- defaultTileMapVRAM() FONT=R.Font:new() FONT:load(rift_datafontciduso24x24ciduso24x242bplraw()) copyTileRAMToBank(0) local isExit=DEMO_RUNNER:run() if isExit then trace("- Thanks for watching! :)",12) exit() end end) function renderDrawlist(drawList,x,y) x,y=x or 0,y or 0 table.sort(drawList, function(a,b) return a.zb then return MIN(MAX(1-(v-b)/(1-b),0),1) end return 1 end -- xshift = -1, 1 (or maybe 0?) function renderModels(models,xAnaShift,x,y) local drawList={} for i=1,#models do local model=models[i] drawList=model:draw(drawList,xAnaShift) end renderDrawlist(drawList,x,y) end function renderModelsText(models,xShift,addShadow) if addShadow then -- OOF! This is a bit of a hack for _,model in ipairs(models) do model:trans({x=0,y=0,z=.001}) end FONT:setColourMap(COLOUR_MAP_REV) local fontResetValues=FONT:renderPre() renderModels(models,xShift,1,1) FONT:renderPost(fontResetValues) -- OOF! This is a bit of a hack for _,model in ipairs(models) do model:trans({x=0,y=0,z=-.001}) end end if xShift==-1 then FONT:setColourMap(COLOUR_MAP_L) elseif xShift==1 then FONT:setColourMap(COLOUR_MAP_R) end local fontResetValues=FONT:renderPre() renderModels(models,xShift) FONT:renderPost(fontResetValues) end function setGlobalVolume(vUnit) local v=(vUnit*15)//1 for iCh=0,3 do local a=(0x14000+iCh)*2 poke4(a,v) poke4(a+1,v) end end