//Marble Race in Truchet City
precision highp float;
uniform vec2 resolution;
uniform vec2 mouse;
uniform float time;
uniform sampler2D backbuffer;
out vec4 outColor;
#define pi acos(-1.)
const float tau=2.*pi;
#define rot(spin)mat2(cos(spin),sin(spin),-sin(spin),cos(spin))
const bool ONGRID = false;
const bool ONRACEORDER = false;
const bool ONDRAWCENTER=false;
const float COURCEID = 217.;
const float onBoardCameraProb=.3;
const bool onBoardCamera=false;
const bool backCamera=false;
const int RACERNUM = 8;
// LAPDATA
const float ALLLAP = 50.;
const float BEFORELAP = 5.;
const float STARTLAP = 1.;
const float AFTERLAP = 5.;
const float ENDLAP = 5.;
const float RACELAP = ALLLAP - BEFORELAP - AFTERLAP - ENDLAP;
// スタート時の逆流を防ぐ応急処理
float extraMileage = 1.75;
float progress;
float beforeProgress;
float startProgress;
float finishProgress;
const float marbleSize = .008;
const float roadWidth = .1;
const float cityHeight = 0.05;
const float cityScale = 32.;
#define STARTPOSITION vec2(0.,0.)
#define INITIALPOS vec2(0.,.5)+STARTPOSITION
#define INITIALDIR vec2(1.,0.)
const float fogDensity=1.5;
const float fogDistance=0.1;
const vec3 fogColor=vec3(.34,.37,.4);
const float windowSize=.3/cityScale;
const float windowDivergence=.2/cityScale;
const vec3 windowColor=vec3(.1,.2,.5);
const float streetDistance=.015;
const vec3 streetColor=vec3(4.,8.,10.);
const float beaconProb=.003;
const float beaconFreq=.6;
const vec3 beaconColor=vec3(1.5,.2,0.);
const vec3 beaconGreenColor=vec3(.2,1.5,0.);
const vec3 roadGlow=vec3(1,.3,.2)*2E-5;
vec4 RACERNUMCOLOR[8]=vec4[](
vec4(1.,0.,0.,1.),// Red
vec4(0.,1.,0.,1.),// Green
vec4(0.,0.,1.,1.),// Blue
vec4(1.,1.,0.,1.),// Yellow
vec4(1.,0.,1.,1.),// Magenta
vec4(0.,1.,1.,1.),// Cyan
vec4(.5,.5,.5,1.),// Gray
vec4(1.,1.,1.,1.)// White
);
// _ __ ___ (_)___ ___
// | '_ \ / _ \| / __|/ _ \
// | | | | (_) | \__ \ __/
// |_| |_|\___/|_|___/\___|
float hash1(vec2 p2){
p2=fract(p2*vec2(5.3983,5.4427));
p2+=dot(p2.yx,p2.xy+vec2(21.5351,14.3137));
return fract(p2.x*p2.y*95.4337);
}
float hash1(vec2 p2,float p){
vec3 p3=fract(vec3(5.3983*p2.x,5.4427*p2.y,6.9371*p));
p3+=dot(p3,p3.yzx+19.19);
return fract((p3.x+p3.y)*p3.z);
}
vec2 hash2(vec2 p2){
vec3 p3=fract(vec3(5.3983*p2.x,5.4427*p2.y,6.9371*p2.x));
p3+=dot(p3,p3.yzx+19.19);
return fract((p3.xx+p3.yz)*p3.zy);
}
vec2 hash2(vec2 p2,float p){
vec3 p3=fract(vec3(5.3983*p2.x,5.4427*p2.y,6.9371*p));
p3+=dot(p3,p3.yzx+19.19);
return fract((p3.xx+p3.yz)*p3.zy);
}
vec3 hash3(vec2 p2){
vec3 p3=fract(vec3(p2.xyx)*vec3(5.3983,5.4427,6.9371));
p3+=dot(p3,p3.yxz+19.19);
return fract((p3.xxy+p3.yzz)*p3.zyx);
}
float noise1(vec2 p){
vec2 i=floor(p);
vec2 f=fract(p);
vec2 u=f*f*(3.-2.*f);
return mix(mix(hash1(i+vec2(0.,0.)),
hash1(i+vec2(1.,0.)),u.x),
mix(hash1(i+vec2(0.,1.)),
hash1(i+vec2(1.,1.)),u.x),u.y);
}
float Hash21(vec2 p){
p=fract(p*vec2(234.24,234.535));
p+=dot(p,p+34.23+COURCEID);
return fract(p.x*p.y);
}
// Simplex 2D noise
vec3 permute(vec3 x){return mod(((x*34.)+1.)*x,289.);}
float snoise(vec2 v){
const vec4 C=vec4(.211324865405187,.366025403784439,
-.577350269189626,.024390243902439);
vec2 i=floor(v+dot(v,C.yy));
vec2 x0=v-i+dot(i,C.xx);
vec2 i1;
i1=(x0.x>x0.y)?vec2(1.,0.):vec2(0.,1.);
vec4 x12=x0.xyxy+C.xxzz;
x12.xy-=i1;
i=mod(i,289.);
vec3 p=permute(permute(i.y+vec3(0.,i1.y,1.))
+i.x+vec3(0.,i1.x,1.));
vec3 m=max(.5-vec3(dot(x0,x0),dot(x12.xy,x12.xy),
dot(x12.zw,x12.zw)),0.);
m=m*m;
m=m*m;
vec3 x=2.*fract(p*C.www)-1.;
vec3 h=abs(x)-.5;
vec3 ox=floor(x+.5);
vec3 a0=x-ox;
m*=1.79284291400159-.85373472095314*(a0*a0+h*h);
vec3 g;
g.x=a0.x*x0.x+h.x*x0.y;
g.yz=a0.yz*x12.xz+h.yz*x12.yw;
return 130.*dot(m,g);
}
// _____ _
// | ___|__ _ __ | |_
// | |_ / _ \| '_ \| __|
// | _| (_) | | | | |_
// |_| \___/|_| |_|\__|
// kinankomoti font_logging
https://www.shadertoy.com/view/mdVSWz
#define FontWidth 8
#define FontHeight 8
#define LineMaxLength 40
ivec2 font_data[94]=ivec2[](
//0
ivec2(0x00000000,0x00000000),//space
//1~10
ivec2(0x7e91897e,0x00000000),//0
ivec2(0x01ff4121,0x00000000),//1
ivec2(0x71898543,0x00000000),//2
ivec2(0x6e919142,0x00000000),//3
ivec2(0x08ff4838,0x00000000),//4
ivec2(0x8e9191f2,0x00000000),//5
ivec2(0x0e91916e,0x00000000),//6
ivec2(0xc0b08f80,0x00000000),//7
ivec2(0x6e91916e,0x00000000),//8
ivec2(0x6e919162,0x00000000),//9
//11~36
ivec2(0x1e11110e,0x00000001),//a
ivec2(0x0e11117f,0x00000000),//b
ivec2(0x0a11110e,0x00000000),//c
ivec2(0x7f11110e,0x00000000),//d
ivec2(0x0815150e,0x00000000),//e
ivec2(0x48483f08,0x00000000),//f
ivec2(0x3e494930,0x00000000),//g
ivec2(0x0708087f,0x00000000),//h
ivec2(0x012f0900,0x00000000),//i
ivec2(0x5e111102,0x00000000),//j
ivec2(0x000b047f,0x00000000),//k
ivec2(0x017f4100,0x00000000),//l
ivec2(0x0807080f,0x00000007),//m
ivec2(0x0708080f,0x00000000),//n
ivec2(0x06090906,0x00000000),//o
ivec2(0x1824243f,0x00000000),//p
ivec2(0x3f242418,0x00000000),//q
ivec2(0x0010081f,0x00000000),//r
ivec2(0x0012150d,0x00000000),//s
ivec2(0x11113e10,0x00000000),//t
ivec2(0x0f01010e,0x00000000),//u
ivec2(0x000e010e,0x00000000),//v
ivec2(0x010e010e,0x0000000f),//w
ivec2(0x0a040a11,0x00000011),//x
ivec2(0x3e090930,0x00000000),//y
ivec2(0x00191513,0x00000000),//z
//36~63
ivec2(0x7f88887f,0x00000000),//A
ivec2(0x6e9191ff,0x00000000),//B
ivec2(0x4281817e,0x00000000),//C
ivec2(0x7e8181ff,0x00000000),//D
ivec2(0x919191ff,0x00000000),//E
ivec2(0x909090ff,0x00000000),//F
ivec2(0x4685817e,0x00000000),//G
ivec2(0xff1010ff,0x00000000),//H
ivec2(0x0081ff81,0x00000000),//I
ivec2(0x80fe8182,0x00000000),//J
ivec2(0x413608ff,0x00000000),//K
ivec2(0x010101ff,0x00000000),//L
ivec2(0x601060ff,0x000000ff),//M
ivec2(0x0c1060ff,0x000000ff),//N
ivec2(0x7e81817e,0x00000000),//O
ivec2(0x609090ff,0x00000000),//P
ivec2(0x7f83817e,0x00000001),//Q
ivec2(0x619698ff,0x00000000),//R
ivec2(0x4e919162,0x00000000),//S
ivec2(0x80ff8080,0x00000080),//T
ivec2(0xfe0101fe,0x00000000),//U
ivec2(0x0e010ef0,0x000000f0),//V
ivec2(0x031c03fc,0x000000fc),//W
ivec2(0x340834c3,0x000000c3),//X
ivec2(0x300f30c0,0x000000c0),//Y
ivec2(0xe1918d83,0x00000081),//Z
//63~
ivec2(0x00007d00,0x00000000),//!
ivec2(0x60006000,0x00000000),//"
ivec2(0x3f123f12,0x00000012),//#
ivec2(0x52ff5224,0x0000000c),//$
ivec2(0x33086661,0x00000043),//%
ivec2(0x374d5926,0x00000001),//&
ivec2(0x00006000,0x00000000),//'
ivec2(0x0081423c,0x00000000),//(
ivec2(0x003c4281,0x00000000),//)
ivec2(0x00143814,0x00000000),//*
ivec2(0x00103810,0x00000000),//+
ivec2(0x00020100,0x00000000),//,
ivec2(0x08080808,0x00000000),//-
ivec2(0x00000100,0x00000000),//.
ivec2(0x30080601,0x00000040),///
ivec2(0x00240000,0x00000000),//:
ivec2(0x00240200,0x00000000),//;
ivec2(0x41221408,0x00000000),//<
ivec2(0x00141414,0x00000000),//=
ivec2(0x08142241,0x00000000),//>
ivec2(0xa999423c,0x0000007c),//@
ivec2(0x008181ff,0x00000000),//[
ivec2(0x06083040,0x00000001),//\
ivec2(0x00000000,0x00000000),//] 何故か表示されない
ivec2(0x00ff8181,0x00000000),//]
ivec2(0x20402010,0x00000010),//^
ivec2(0x01010101,0x00000000),//_
ivec2(0x40408080,0x00000000),//`
ivec2(0x41413608,0x00000000),//{
ivec2(0x00ff0000,0x00000000),//|
ivec2(0x08364141,0x00000000),//}
ivec2(0x08101008,0x00000010)//~
);
vec3 font(vec2 uv,int id){
vec2 uv1=uv;
uv=uv*8.;
ivec2 texel=ivec2(uv);
int bit_offset=texel.x*FontWidth+texel.y;
int s,t;
s=font_data[id].x;
t=font_data[id].y;
int tex=0;
if(bit_offset<=31){
s=s>>bit_offset;
s=s&0x00000001;
tex=s;
}
else{
t=t>>(bit_offset-32);
t=t&0x00000001;
tex=t;
}
tex=(abs(uv1.x-.5)<.5&&abs(uv1.y-.5)<.5)?tex:0;
return vec3(tex);
}
// _ __
// ___ __| |/ _|
// / __|/ _` | |_
// \__ \ (_| | _|
// |___/\__,_|_|
bool drawCircle(vec2 uv,vec2 pos){
float posPoint=.075-length(pos-uv);
return ceil(posPoint)==1.;
}
float calculateGridLine(vec2 uv,float grid_size,float line_width){
// グリッドの線を計算
float line_x=step(grid_size-line_width,mod(uv.x-line_width/2.,grid_size));
float line_y=step(grid_size-line_width,mod(uv.y-line_width/2.,grid_size));
float line=line_x+line_y;
return line;
}
float sdSphere(vec3 p,float r)
{
return length(p)-r;
}
// ____ _____ _ _
// / ___| ____| | | |
// | | | _| | | | |
// | |___| |___| |___| |___
// \____|_____|_____|_____|
// inspired by 2d truchet traveler abje
https://www.shadertoy.com/view/4tfcDl
int getcell(vec2 pos){
return int(floor(Hash21(mod(pos,vec2(64.)))*3.));
}
float passedCellNum(float mileage){
// mileage=mod(mileage,128.);
return floor(mileage);
}
struct TruchetData{
vec2 pos;
vec2 dir;
vec2 floorpos;
};
TruchetData allSkippedData[128];
int lastSkippedDataIndex;
#define EXTRACELL 5.
void setSkipTruchetData(vec2 pos,vec2 dir, vec2 floorpos, float mileage){
for(float i=0.;i<passedCellNum(RACELAP) + EXTRACELL;i++){
// for(float i=0.;i<floor(fract(time*.01)*100.);i++){
pos+=dir*.5;// centerPosがタイルの中央に乗る
int celltype=getcell(floorpos);//現在の位置のセルタイプを取得
// セルタイプが0の場合、左に曲がるよう方向を転換
if(celltype==0){
dir=dir.yx;
// セルタイプが1の場合、右に曲がるよう方向を転換
}else if(celltype==1){
dir=-dir.yx;
}
// セルタイプが2の場合、方向を変えずに進むため方向転換無し
floorpos+=dir;// 現在のタイル位置を次のタイル位置に更新
pos+=dir*.5;// centerPosが次のタイルの端に乗る
allSkippedData[int(i)]=TruchetData(pos,dir,floorpos);
}
}
vec2 getCachedSkipTruchetPos(inout vec2 pos,inout vec2 dir,inout vec2 floorpos, float mileage){
int index = int(passedCellNum(mileage));
TruchetData data=allSkippedData[index];
pos=data.pos;
dir=data.dir;
floorpos=data.floorpos;
return pos;
}
vec4 getNowOnTruchetPos(inout vec2 pos,inout vec2 dir,inout vec2 floorpos,float mileage,float sidePosition){
float multiplier=1.;
int celltype=getcell(floorpos);
vec2 nextDir;
if(celltype==0){
nextDir=dir.yx;
}else if(celltype==1){
nextDir=-dir.yx;
}
float corner=dir.x*nextDir.y-dir.y*nextDir.x;
// 仕組みがわからないですが観察結果から、横移動の場合は左が逆転する
float side = 1.;
if(dir.x!=0.) side = -1.;
// affectが正なら右に移動
float affect = sidePosition*(roadWidth*5.);
// 左回転
if(0.<corner){
pos+=dir.yx * affect * .5 * side;
multiplier=1.+affect;
}
// 右回転
if(corner<0.){
pos+=dir.yx * affect * .5 * side;
multiplier=1.-affect;
}
// 直進
if(corner==0.){
//dirのy,xを入れ替えたものに、xに-1をかけたものが左になる
pos+=dir.yx * vec2(1,-1) * affect * .5;
}
// mileageに応じて位置調整
pos+=dir*sin(fract(mileage)*pi*.5)*.5 * multiplier;
vec2 dirForRacingObject=dir;
if(celltype==0){
dirForRacingObject*=rot(dot(abs(dir),vec2(-1.,1.))*fract(mileage)*pi*.5);
dir=dir.yx;
}else if(celltype==1){
dirForRacingObject*=rot(dot(abs(dir),vec2(1.,-1.))*fract(mileage)*pi*.5);
dir=-dir.yx;
}
pos+=dir*(.5-cos(fract(mileage)*pi*.5)*.5) * multiplier;
return vec4(pos,dirForRacingObject);
}
// ____
// | _ \ __ _ ___ ___ _ __
// | |_) / _` |/ __/ _ \ '__|
// | _ < (_| | (_| __/ |
// |_| \_\__,_|\___\___|_|
struct Racer{
vec2 pos;
vec2 dirForRacingObject;
vec4 color;
float ID;
float mileage;
};
Racer createRacerFromTruchet(float mileage,float sidePosition, vec4 color, float ID){
vec2 racerPos;
vec2 dir;
vec2 floorpos;
racerPos=getCachedSkipTruchetPos(racerPos,dir,floorpos,mileage);
vec4 getNowOnTruchetPosResult = getNowOnTruchetPos(racerPos,dir,floorpos,mileage,sidePosition);
return Racer(getNowOnTruchetPosResult.xy,getNowOnTruchetPosResult.zw,color,ID, mileage);
}
// ____ _
// / ___| ___ _ __| |_
// \___ \ / _ \| '__| __|
// ___) | (_) | | | |_
// |____/ \___/|_| \__|
// バブルソートで任意の数の構造体配列をソートする関数
void bubbleSort(inout Racer arr[RACERNUM],int size){
for(int i=0;i<size-1;i++){
for(int j=0;j<size-i-1;j++){
if(arr[j].mileage<arr[j+1].mileage){
// 値を入れ替える
Racer temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
// ____
// / ___| ___ ___ _ __ ___
// \___ \ / __/ _ \ '_ \ / _ \
// ___) | (_| __/ | | | __/
// |____/ \___\___|_| |_|\___|
float getRoadOrCity(vec2 block, float roadWidth){
// ■ タイルごとのUV生成
vec2 flooruv=floor(block);
vec2 uvOfTile=block-flooruv-.5;
int celltype=getcell(flooruv.xy);
// ■ 道の描画
//uvから道のもとになるlenを作る
vec2 uvOfTileForRoad=uvOfTile;
if(celltype==0)uvOfTileForRoad*=vec2(1.,-1.);
uvOfTileForRoad*=step(0.,uvOfTileForRoad.x+uvOfTileForRoad.y)*2.-1.;
float len;
if(celltype==2){
len=min(abs(uvOfTileForRoad.x),abs(uvOfTileForRoad.y));
}else{
len=length(uvOfTileForRoad-.5)-.5;
}
//道を描画
float roadSize=(abs(len)-roadWidth)*resolution.y*.1;
float clampedRoad=clamp(roadSize,0.,1.);
return clampedRoad;
}
// inspired by Glow City mhnewman
https://www.shadertoy.com/view/XlsyWB
#define NOHIT 0.
#define HIT_CITY 1.
#define HIT_ROAD 2.
struct CityResult{
float distance;
float beacon;
float face;
float hittedTarget;
float distanceFromEye;
vec3 hitPos;
};
CityResult cityRay(vec3 eye,vec3 ray){
eye.xyz = eye.xzy;
ray.xyz = ray.xzy;
vec2 block=floor(eye.xy*cityScale)/cityScale;
vec3 ri=1./ray;
vec3 rs=sign(ray);
vec3 side=.5+.5*rs;
vec2 ris=ri.xy*rs.xy;
vec2 dis=(block-eye.xy+.5/cityScale+rs.xy*.5/cityScale)*ri.xy;
float hit=0.;
float beacon=0.;
vec3 resultPos;
float dist=500.;
float face=0.;
for(int i=0;i<150;++i){
// 高さの計算を元に戻し、建物ごとにランダム化
// 面の交差判定を安定させるために、1/3スケールに調整
vec2 lo0=vec2(block+.01/cityScale);// 面の下端を調整
vec2 loX=vec2(.3,.3)/cityScale;
vec2 hi0=vec2(block+.69/cityScale);// 面の上端を調整
vec2 hiX=vec2(.3,.3)/cityScale;
float height=(.5+hash1(block))*(2.+4.*pow(noise1(.1*block),2.5))*cityHeight;
float clampedRoad=getRoadOrCity(block,roadWidth);
height *= clampedRoad;
dist=500.;
face=0.;
// ビルの各面について交差判定を行う
for(int j=0;j<int(float(3)*clampedRoad);++j){
// for(int j=0;j<1;++j){
float top=height*(1.-.1*float(j));
vec3 lo=vec3(lo0+loX*hash2(block,float(j)),0.);
vec3 hi=vec3(hi0+hiX*hash2(block,float(j)+.5),top);
vec3 wall=mix(hi,lo,side);// 交差面の位置
vec3 t=(wall-eye)*ri;
vec3 dim=step(t.zxy,t)*step(t.yzx,t);
float maxT=dot(dim,t);
float maxFace=1.-dim.z;
resultPos=eye+maxT*ray;
if(resultPos.z<0.0){
return CityResult(dist,beacon,face,HIT_ROAD,length(resultPos-eye),resultPos.xzy);
}
dim+=step(lo,resultPos)*step(resultPos,hi);// 面内判定
if(dim.x*dim.y*dim.z>.5&&maxT<dist){
dist=maxT;
face=maxFace;
}
}
vec2 h=hash2(block);
if(h.x<beaconProb){
vec3 center=vec3(block+.5/cityScale,height);
float t=dot(center-eye,ray);
if(t<dist){
vec3 p=eye+t*ray;
float fog=(exp(-p.z/fogDistance)-exp(-eye.z/fogDistance))/ray.z;
fog=exp(fogDensity*fog);
t=distance(center,p);
fog*=smoothstep(1.,.5,cos(tau*(beaconFreq*time+h.y)));
beacon+=fog*pow(clamp(1.-t*25.0,0.,1.),4.);
}
}
// スタートシグナル
if(0. < beforeProgress && startProgress < 1.){
int index=int(passedCellNum(0.));
TruchetData data=allSkippedData[index];
vec2 startLine = floor(data.pos*cityScale)/cityScale;
if(startLine.x - roadWidth + 1./cityScale < block.x
&& block.x < startLine.x + roadWidth * (beforeProgress+0.2) - 1./cityScale
&& startLine.y + 0.25 < block.y
&& block.y< startLine.y+.25+2./cityScale
){
vec3 center=vec3(block+.5/cityScale,.03);
float t=dot(center-eye,ray);
if(t<dist){
vec3 p=eye+t*ray;
float fog=(exp(-p.z/fogDistance)-exp(-eye.z/fogDistance))/ray.z;
fog=exp(fogDensity*fog);
t=distance(center,p);
beacon+=fog*pow(clamp(1.-t*25.,0.,1.),4.)*10.;
}
}
}
// ゴールポイント
int index=int(passedCellNum(RACELAP + extraMileage));
TruchetData data=allSkippedData[int(index)];
vec2 startLine=floor(data.pos*cityScale)/cityScale;
if(startLine.x-roadWidth<block.x
&&block.x<startLine.x+roadWidth
&&block.y<startLine.y+roadWidth
&&startLine.y-roadWidth<block.y){
vec3 center=vec3(block+.5/cityScale,.03);
float t=dot(center-eye,ray);
if(t<dist){
vec3 p=eye+t*ray;
float fog=(exp(-p.z/fogDistance)-exp(-eye.z/fogDistance))/ray.z;
fog=exp(fogDensity*fog);
t=distance(center,p);
beacon+=fog*pow(clamp(1.-t*3.,0.,1.),4.);
}
}
if(dist<400.){
return CityResult(dist,beacon,face,HIT_CITY,length(resultPos-eye),resultPos.xzy);
}
vec2 dim=step(dis.xy,dis.yx);
dis+=dim*ris/cityScale;
block+=dim*rs.xy/cityScale;
}
if(ray.z<0.){
return CityResult(-eye.z*ri.z,beacon,0.,1.,1000.,(eye+1000.*ray).xzy);
}
return CityResult(0.,beacon,0.,NOHIT,0.,vec3(0.));
}
CityResult cityScene(vec3 ro,vec3 rd,inout vec4 returnColor){
float dist=-(ro.y)/rd.y;// 右はri
vec2 hit1=ro.xz+rd.xz*dist;
// ■ シティの描画
CityResult cityResult=cityRay(ro,rd);
// vec3 p=ro+cityResult.distance*rd;
vec3 p=cityResult.hitPos;
vec2 block=floor(p.xz*cityScale)/cityScale;
vec3 window=floor(p/windowSize);
float x=hash1(block,window.x);
float y=hash1(block,window.y);
float z=hash1(block,window.z);
vec3 color=windowColor+windowDivergence*(hash3(block)-.5);
color*=smoothstep(.1,.9,fract(2.5*(x*y*z)));
vec3 streetLevel=streetColor*exp(-p.y/streetDistance);
color+=streetLevel;
color=clamp(mix(.25*streetLevel,color,cityResult.face),0.,1.);
float fog=(exp(-p.y/fogDistance)-exp(-ro.y/fogDistance))/rd.y;
fog=exp(fogDensity*fog);
color=mix(fogColor,color,fog);
color=mix(fogColor,color,clamp(cityResult.hittedTarget,0.,1.));
vec3 tmpBeaconColor=beaconColor;
if(0. < startProgress && startProgress < 1.) tmpBeaconColor = beaconGreenColor;
color+=cityResult.beacon*tmpBeaconColor;
returnColor=vec4(color,1.);
// ■ タイルごとのUV生成
vec2 flooruv=floor(hit1);
vec2 uvOfTile=hit1-flooruv-.5;
int celltype=getcell(flooruv.xy);
// ■ 道の描画
// inspired by MountainBytes: PPPP 4KiB Windows mrange & virgill
https://www.shadertoy.com/view/lX2GzD
//uvから道のもとになるlenを作る
vec2 uvOfTileForRoad=uvOfTile;
if(celltype==0)uvOfTileForRoad*=vec2(1.,-1.);
uvOfTileForRoad*=step(0.,uvOfTileForRoad.x+uvOfTileForRoad.y)*2.-1.;
float len;
float dashed;
float dashLineSize=.1;
float dashLineRatio=.3;
if(celltype==2){
len=min(abs(uvOfTileForRoad.x),abs(uvOfTileForRoad.y));
dashed=step(fract((uvOfTileForRoad.x+uvOfTileForRoad.y)/dashLineSize),dashLineRatio);
}else{
len=length(uvOfTileForRoad-.5)-.5;
float polar=atan(uvOfTileForRoad.x-.5,uvOfTileForRoad.y-.5)/(pi*2.)+.5;
dashed=step(fract(polar/dashLineSize*4.),dashLineRatio);
}
//道を描画
float roadSize=(abs(len)-roadWidth)*resolution.y*.1;
float lineSize=.1;
float clampedRoad=clamp(roadSize,0.,1.);
if(cityResult.hittedTarget==HIT_ROAD){
bool is1Load=false;
//bpは普通の線
//lenは点線
float bp;
if(is1Load){
bp=abs(len)-.2;//1road
}
else{
float centerSideLine=abs(len)-.01;
returnColor+=vec4(roadGlow/(centerSideLine*centerSideLine)*lineSize,1.);
bp=abs(len)-.085;//2road
len=abs(len)-.05;//2road
}
vec3 glowIntensity=roadGlow/((len*len))*lineSize;
returnColor+=vec4(glowIntensity*dashed,1.);
float baseGlowIntensity=.25/(bp*bp)*lineSize;
returnColor+=vec4(baseGlowIntensity*roadGlow,1.);
}
return cityResult;
}
Racer allRacers[RACERNUM];
struct BallResult{
float distance;
int racerNum;
float distanceFromEye;
vec3 hitPos;
};
BallResult scene(vec3 p){
// ■ レーサーの描画
float minD=1000.;
int racerNum=-1;
for(int i=0;i<RACERNUM;i++){
float d=sdSphere(p-vec3(allRacers[i].pos.x,marbleSize,allRacers[i].pos.y),marbleSize);
if(d<minD){
minD=d;
racerNum=i;
}
}
return BallResult(minD,racerNum,0.,p);
}
// レイマーチングによる距離探査
BallResult rayMarch(vec3 ro, vec3 rd, inout vec3 hitPos){
float t=0.;
float tolerance=.001;
// 近景
for(int i=0;i<50;i++){
hitPos=ro+rd*t;
BallResult sr=scene(hitPos);
if(sr.distance<tolerance){
return BallResult(sr.distance,sr.racerNum,length(hitPos-ro),hitPos);
// vec2(t,KINKEI);// ヒット時
}
t+=sr.distance;
}
return BallResult(1000.,-1,length(hitPos-ro),hitPos);
}
BallResult ballScene(vec3 ro,vec3 rd,inout vec3 hitPos, out vec4 color){
BallResult sr = rayMarch(ro,rd,hitPos);
vec2 o=vec2(.001,0);
vec3 n=normalize(vec3(
scene(sr.hitPos+o.xyy).distance-scene(sr.hitPos-o.xyy).distance,
scene(sr.hitPos+o.yxy).distance-scene(sr.hitPos-o.yxy).distance,
scene(sr.hitPos+o.yyx).distance-scene(sr.hitPos-o.yyx).distance
));
// balls
float fresnel=pow(1.-dot(-rd,n),5.);
fresnel=mix(.5,1.,fresnel);
ro=sr.hitPos+n*.0015;
rd=reflect(rd,n);
CityResult bounceCr = cityScene(ro,rd,color);
color *=fresnel;
return sr;
}
// ____
// / ___|__ _ _ __ ___ ___ _ __ __ _
// | | / _` | '_ ` _ \ / _ \ '__/ _` |
// | |__| (_| | | | | | | __/ | | (_| |
// \____\__,_|_| |_| |_|\___|_| \__,_|
struct Camera{
vec3 ro;
vec3 rd;
};
vec3 createRd(in vec2 fragCoord, vec3 forward){
vec3 right=normalize(cross(forward,vec3(0.,1.,0.)));
vec3 up=cross(right,forward);
vec2 xy=2.*fragCoord-resolution.xy;
float zoom=3.;
zoom*=resolution.y;
vec3 rd=normalize(xy.x*-right+xy.y*up+zoom*forward);
return rd;
}
Camera makeCamera(in vec2 fragCoord, float time, bool isStarting, vec3 hashedValue, bool isFinished){
// ■ 画面全体のUV
vec2 realUV=(fragCoord.xy*2.-resolution.xy)/resolution.y;
vec3 h=hashedValue;
bool onBoardCamera=h.x<onBoardCameraProb;
bool backCamera=h.y<onBoardCameraProb;
int racerNum=int(h.z*float(RACERNUM));
// オンボードカメラ
if(!isFinished&&!isStarting&&onBoardCamera){
float isBack=1.;
if(backCamera)isBack=-1.;
vec2 center2d=allRacers[racerNum].pos;
vec2 dirForRacingObject=allRacers[racerNum].dirForRacingObject;
vec3 ro=vec3(center2d.x-dirForRacingObject.x*.05*isBack,.05,center2d.y-dirForRacingObject.y*.04*isBack);
vec2 tmpUV=vec2(-realUV.x,realUV.y);
vec3 rd=normalize(vec3(tmpUV-vec2(0.,.5),1.));
rd.xz*=mat2(vec2(-dirForRacingObject.y,dirForRacingObject.x),dirForRacingObject)*isBack;
return Camera(ro,rd);
}
vec2 center2d=allRacers[racerNum].pos;
if(isFinished){
int index=int(passedCellNum(RACELAP + extraMileage));
TruchetData data=allSkippedData[index];
center2d=data.pos;
}
// roの定義
vec3 center=vec3(center2d.x,.1,center2d.y);
vec2 m=vec2(.01*time,.5+sin(.02*time)*.2);
// if(mouse.z>0.)m=mouse.xy/resolution.xy;
m*=tau*vec2(1.,.25);
float cameraDist=2.;
vec3 mouseFix=vec3(
cameraDist*sin(m.x)*sin(m.y),
cameraDist*cos(m.y),
cameraDist*cos(m.x)*sin(m.y));
// 固定カメラ
bool isKoteiCamera=backCamera&&!onBoardCamera;
if(!isFinished&&!isStarting&&isKoteiCamera){
int index=int(passedCellNum(allRacers[racerNum].mileage));
TruchetData data=allSkippedData[index+1];
center=vec3(data.pos.x,.1,data.pos.y);
mouseFix=vec3(
roadWidth*sin(m.x)*sin(m.y),
roadWidth*cos(m.y),
roadWidth*cos(m.x)*sin(m.y));
vec3 ro = center+mouseFix;
vec3 target =vec3(allRacers[racerNum].pos.x,marbleSize,allRacers[racerNum].pos.y);
vec3 forward = normalize(target-ro);
return Camera(ro,createRd(gl_FragCoord.xy,forward));
}
vec3 ro=center+mouseFix;
if(isStarting){
int index=int(passedCellNum(0.));
TruchetData data=allSkippedData[index];
center=vec3(data.pos.x,.0,data.pos.y);
ro = center + vec3(data.dir.x,0.3,data.dir.y) + mouseFix*0.05;
}
// rdの定義
vec3 forward=normalize(center-ro);
vec3 rd=createRd(gl_FragCoord.xy,forward);
return Camera(ro,rd);
}
// __ __ _ ___ _ _
// | \/ | / \ |_ _| \ | |
// | |\/| | / _ \ | || \| |
// | | | |/ ___ \ | || |\ |
// |_| |_/_/ \_\___|_| \_|
void main()
{
vec2 floorpos=STARTPOSITION;
vec2 initialPos=INITIALPOS;
vec2 dir=INITIALDIR;// 初期の方向 以降最初はx=1の方向に進む前提のコード
// ■ 時間の設定
float gameSpeed = time * 0.75;
float time = fract(gameSpeed / (ALLLAP)) * (ALLLAP);
float raceTime = BEFORELAP < time ? time - BEFORELAP: 0.;
progress = raceTime / RACELAP;
beforeProgress = clamp(time/(BEFORELAP),0.,1.);
startProgress = clamp((raceTime/STARTLAP),0.,1.);
finishProgress = clamp((time-(ALLLAP-AFTERLAP-ENDLAP))/AFTERLAP,0.,1.);
// ■ トルシェのデータの生成
setSkipTruchetData(initialPos,dir,floorpos,time);
// ■ レーサーの生成
for(int i=0;i<RACERNUM;i++){
float noiseVert=snoise(vec2(time*.01+COURCEID,float(i+1)*100.))*3.*progress-2.;
float noiseSide=snoise(vec2(time*.1+COURCEID,float(i+1)*10.*COURCEID))*.25;
float normalMileage = raceTime + noiseVert + extraMileage;
normalMileage = min(normalMileage, floor(RACELAP + extraMileage));
float normalSidePosition = noiseSide;
float beforeMileage = float(i) / float(RACERNUM) * 0.25;
float beforeSidePosition = (i % 2 == 0) ? 0.2 : -0.2;
// スタート時に徐々にスタート
float mileage = mix(beforeMileage, normalMileage, startProgress);
float sidePosition = mix(beforeSidePosition, normalSidePosition, startProgress);
allRacers[i]=createRacerFromTruchet(mileage,sidePosition,RACERNUMCOLOR[i],54321.);
}
bubbleSort(allRacers,RACERNUM);
// ランダム値
vec3 h=hash3(floor(vec2(time/3.))+COURCEID);
int racerNum=int(h.z*float(RACERNUM));
// ■ カメラの生成
Camera camera = makeCamera(gl_FragCoord.xy, raceTime, startProgress*2.<1., h, 0.<finishProgress);
vec3 ro=camera.ro;
vec3 rd=camera.rd;
// ■ レイキャスティング
float dist=-(ro.y)/rd.y; // 右はri
vec2 hit1=ro.xz+rd.xz*dist;
// _ _ ____ _
// ___| |_ __ _ _ __| |_| _ \ _ __ __ ___ _(_)_ __ __ _
// / __| __/ _` | '__| __| | | | '__/ _` \ \ /\ / / | '_ \ / _` |
// \__ \ || (_| | | | |_| |_| | | | (_| |\ V V /| | | | | (_| |
// |___/\__\__,_|_| \__|____/|_| \__,_| \_/\_/ |_|_| |_|\__, |
// |___/
// 空の描画
outColor=vec4(fogColor,1);
// ■ シティの描画
CityResult cityResult = cityScene(ro,rd,outColor);
// ■ レーサーの描画
vec3 hitPos;
vec4 bounceColor;
BallResult sr=ballScene(ro,rd,hitPos,bounceColor);
if(sr.racerNum!=-1&&sr.distanceFromEye<cityResult.distanceFromEye){
outColor=mix(bounceColor*3.,allRacers[sr.racerNum].color,.4);
// outColor=allRacers[sr.racerNum].color;
}
// _ _
// __| | ___| |__ _ _ __ _
// / _` |/ _ \ '_ \| | | |/ _` |
// | (_| | __/ |_) | |_| | (_| |
// \__,_|\___|_.__/ \__,_|\__, |
// |___/
// ■グリッドの描画
float grid=calculateGridLine(hit1,1.,.02);
if(ONGRID) outColor=mix(outColor,vec4(0., 0.0, 1.0, 1.0),grid);
// _
// _____ _____ _ __| | __ _ _ _
// / _ \ \ / / _ \ '__| |/ _` | | | |
// | (_) \ V / __/ | | | (_| | |_| |
// \___/ \_/ \___|_| |_|\__,_|\__, |
// |___/
// ■ 画面UVの作成
vec2 uvForScreen=gl_FragCoord.xy/resolution.x;
// ■ mapの描画
if(.75<uvForScreen.x && .25<uvForScreen.y){
vec2 baseUV = (gl_FragCoord.xy*2.-resolution.xy+vec2(-.75,-.25)*resolution.xy)/resolution.x;
vec2 gameUV=8.*baseUV+allRacers[racerNum].pos;//3dでは不要
float mapRoadWidth=.15;
float mapClampedRoad = getRoadOrCity(gameUV,mapRoadWidth);
vec4 roadColor = vec4(0);
vec4 cityColor = vec4(1);
// スタート地点
if(floor(gameUV)==allSkippedData[0].floorpos){
cityColor = (0. < startProgress) ? vec4(0.,1.,0.,1.):vec4(1.,0.,0.,1.);
}
// ゴール地点
TruchetData data=allSkippedData[int(RACELAP)];
if(floor(gameUV)==floor(data.floorpos)){
cityColor = vec4(1.,0.,0.,1.);
}
outColor=mix(roadColor,cityColor,mapClampedRoad);
for(int i=RACERNUM-1;0<=i;i--){
if(drawCircle(gameUV,allRacers[i].pos)){
outColor=allRacers[i].color;
}
}
}
// ■ 順位の描画
if(uvForScreen.x<0.15){
uvForScreen*=40.;
int lineNum=int(floor((uvForScreen.y)/1.2));
ivec2 index=ivec2(uvForScreen/vec2(.8,1.2));
uvForScreen.x=mod(uvForScreen.x,.8);
uvForScreen.y=mod(uvForScreen.y,1.2);
int max_char = 1;
int raceOrderInIndex=15-lineNum;
if(0 <= raceOrderInIndex && raceOrderInIndex <= RACERNUM){
// 順序
if(0==index.x)outColor=vec4(font(uvForScreen,raceOrderInIndex+1),1.);
// 色
if(1 == index.x){
outColor=allRacers[raceOrderInIndex-1].color;
}
// Interval表示
// 先頭
if(raceOrderInIndex == 1){
if(2==index.x)outColor=vec4(0.,0.,0.,1.);
if(3==index.x)outColor=vec4(0.,0.,0.,1.);
if(4==index.x)outColor=vec4(font(uvForScreen,45),1.);
if(5==index.x)outColor=vec4(font(uvForScreen,50),1.);
if(6==index.x)outColor=vec4(font(uvForScreen,56),1.);
}
else{ // 先頭以外
float interval = allRacers[raceOrderInIndex-2].mileage - allRacers[raceOrderInIndex-1].mileage;
if(2==index.x)outColor=vec4(font(uvForScreen,73),1.);
if(3==index.x)outColor=vec4(font(uvForScreen,int(interval+1.)),1.);
if(4==index.x)outColor=vec4(font(uvForScreen,76),1.);
if(5==index.x)outColor=vec4(font(uvForScreen,int(fract(interval)*10.+1.)),1.);
if(6==index.x)outColor=vec4(font(uvForScreen,int(fract(interval*10.)*10.+1.)),1.);
}
//選択中のレーサーの色を反転
if(raceOrderInIndex-1 == racerNum && 2 <= index.x && index.x <= 6){
outColor=1.0-outColor;
}
// ラップ数表示
if(raceOrderInIndex==0){
raceTime=min(raceTime+extraMileage,RACELAP);
int lap10=int(fract(raceTime*.01)*10.);
int lap1=int(fract(raceTime*.1)*10.);
if(0==index.x)outColor=vec4(0.,0.,0.,1.);
if(1==index.x)outColor=vec4(font(uvForScreen,lap10+1),1.);
if(2==index.x)outColor=vec4(font(uvForScreen,lap1+1),1.);
if(3==index.x)outColor=vec4(font(uvForScreen,77),1.);
int niketame=int(fract(RACELAP*.01)*10.);
int hitoketame=int(fract(RACELAP*.1)*10.);
if(4==index.x)outColor=vec4(font(uvForScreen,niketame+1),1.);
if(5==index.x)outColor=vec4(font(uvForScreen,hitoketame+1),1.);
if(6==index.x)outColor=vec4(0.,0.,0.,1.);
}
}
}
// 終了後暗転
if(0.<finishProgress){
outColor=mix(clamp(outColor,0.,1.),vec4(0.),finishProgress);
return;
}
}