diff --git a/src/fft.h b/src/fft.h index 6e424bc..27a108e 100644 --- a/src/fft.h +++ b/src/fft.h @@ -2,6 +2,6 @@ #include #include -#define FFT_SIZE 4096 +#define FFT_SIZE 2048 void init_hamming_window(); void compute_fft(float* time_data, float* freq_out); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 9052d3b..474a6d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -169,9 +169,12 @@ int __cdecl main(int argc, char* argv[]) HRESULT hr = IDirectSoundBuffer_Lock(direct_sound_buffer, 0, FFT_SIZE * sizeof(SUsample), &audio_ptr, &audio_size, NULL, NULL, DSBLOCK_FROMWRITECURSOR); if (SUCCEEDED(hr) && audio_ptr) { - SUsample* samples = (SUsample*)audio_ptr; - for (int i = 0; i < FFT_SIZE; ++i) { - fft_input[i] = (float)samples[i]; + if (playCursor < ((SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT * SU_SAMPLE_SIZE) - (FFT_SIZE* SU_CHANNEL_COUNT * SU_SAMPLE_SIZE))) + { + SUsample* samples = (SUsample*)audio_ptr; + for (int i = 0; i < FFT_SIZE; ++i) { + fft_input[i] = (float)samples[i]; + } } IDirectSoundBuffer_Unlock(direct_sound_buffer, audio_ptr, audio_size, NULL, 0); @@ -183,15 +186,15 @@ int __cdecl main(int argc, char* argv[]) // Normalize output for (int i = 0; i < (FFT_SIZE / 2); i++) { - maximum = max(fft_output[i], maximum); - fft_output[i] = fft_output[i] / maximum; + //maximum = max(fft_output[i], maximum); + //fft_output[i] = fft_output[i] / maximum; if (i < (FFT_SIZE / 4)) // Limit uniform fft size. High frequencys contain nothing interesing. { - fft_uniform[i] = fft_output[i]; + fft_uniform[i] = min(1.0,fft_output[i]*20.0); } } - syncs[0] = (float)playCursor / (2 * sizeof(SUsample)); + syncs[0] = (float)playCursor / (SU_SAMPLE_RATE * SU_CHANNEL_COUNT * SU_SAMPLE_SIZE); // Aika sekunteina. for (int i = 0; i < SU_NUMSYNCS; ++i) { diff --git a/src/shaders/fragment.frag b/src/shaders/fragment.frag index 2e98c1c..f500f7d 100644 --- a/src/shaders/fragment.frag +++ b/src/shaders/fragment.frag @@ -1,41 +1,319 @@ #version 460 precision mediump float; out vec4 o; -const float PI = 22./7.; +const float PI = 3.14159265; +const float TAU = (2. * PI); +const float PHI = sqrt(5.) * 0.5 + 0.5; layout(location = 0) uniform float syncs[7]; -layout(location = 8) uniform float fft_output[1024]; // FFT_SIZE / 4 +layout(location = 8) uniform float fft_output[512]; // FFT_SIZE / 4 float u_time = syncs[0]; -vec3 render(vec2 uv, float time) { - float pos = syncs[4]; - if (uv.x >= pos && uv.x <= pos+0.01 ) { - return vec3(1.); +vec2 getUV(vec2 offset) { + vec2 uv = 2.0 * ((gl_FragCoord.xy + offset *0.5) / vec2(1920,1080) - 0.5); + uv.x *= 1920/1080; + return uv; +} + +float noise(in vec2 xy, in float seed) { + return fract(tan(distance(xy * PHI, xy) * seed) * xy.x); +} + +// Hexagonal prism, circumcircle variant +float fHexagonCircumcircle(vec3 p, vec2 h) { + vec3 q = abs(p); + return max(q.y - h.y, max(q.x * sqrt(3.) * 0.5 + q.z * 0.5, q.z) - h.x); + //this is mathematically equivalent to this line, but less efficient: + //return max(q.y - h.y, max(dot(vec2(cos(PI/3), sin(PI/3)), q.zx), q.z) - h.x); +} + +float sdHex(vec3 pos, float i, float angle) { + float d1 = fHexagonCircumcircle(pos, vec2(0.86, i)); + return d1; +} + +// Modify your mapScene function +vec2 mapScene(in vec3 p) { + float mat = 0.; + float d = 1e9; + float a = 0.; + vec3 po = p; + + + vec3 rippleCenter = vec3(7.5, 0, 7.5); + float rippleSpeed = 4.0; + float rippleFreq = 1.0; + float rippleDecay = 0.25; + + + // Hexagonal grid + float counter = 0.; + float hexGap = 0.2; + + for(float j = 0.; j < 15.; j++) { + po = p; + po += vec3(1.5 * 7.5, 0., 7.5); + po += vec3(0, 0., (1.88 + hexGap)*j); + + for(float i = 0.; i < 15.; i++) { + if( mod(i, 2.) == 0.) { + po -= vec3(1.6+hexGap, 0., 1.); + } else { + po += vec3(-(1.6+hexGap), 0., 1.); + } + + // Add individual hexagon ripples based on distance from center + float hexDist = length(vec2(i , j) - rippleCenter.xz); + float wave = sin(hexDist * rippleFreq - u_time * rippleSpeed) * exp(-hexDist * rippleDecay); + + + // Apply ripple to hexagon size and position + float hexSize = fft_output[int(i+1)*int(j+1)]*5.0; // sin(1.5*u_time)+ wave + a = sdHex(po, 1. + hexSize, 0.); + d = min(d, a); + + if (d == a) { + mat = 4.; + } + counter += 1.; + } } - - if (uv.x >= 0.0 && uv.x <= 0.01 ) { - return vec3(abs(syncs[3]*2)); + + return vec2(d, mat); +} + +vec3 castRay(vec3 ro, vec3 rd, inout vec3 pos) { + float t = 0.; + float mat = 0.; + float hit = 0.; + // Reduced from 40 to 24 steps + for(int i = 0; i < 24; i++) { + pos = ro + rd * t; + vec2 res = mapScene(pos); + // Increase step size multiplier for faster marching + t += res.x * 1.2; + mat = res.y; + if(t > 60.) { // Reduced max distance + break; + } + if(res.x < 0.001 * t) { // Less precise hit detection + hit = 1.; + break; + } } - - return vec3(0.1, 0.2, 0.3) * abs(syncs[1]*1.2); + if(t > 60.) t = 0.; + + return vec3(t, mat, hit); +} + +float softshadow(in vec3 ro, in vec3 rd, float mint, float maxt, float w) { + float res = 1.0; + float t = mint; + for(int i = 0; i < 6; i++) { + if(t > maxt) + break; + float h = mapScene(ro + t * rd).x; + res = min(res, h / (w * t)); + t += clamp(h, 0.1, 0.80); + if(res < -1.0) + break; + } + res = max(res, -1.0); + return 0.25 * (1.0 + res) * (1.0 + res) * (2.0 - res); +} + +vec3 calcNormal(vec3 pos) { + vec2 e = vec2(.01, 0.); + vec3 n = vec3(mapScene(pos + e.xyy).x - mapScene(pos - e.xyy).x, + mapScene(pos + e.yxy).x - mapScene(pos - e.yxy).x, + mapScene(pos + e.yyx).x - mapScene(pos - e.yyx).x); + return normalize(n); +} + +vec3 fresnel(vec3 F0, vec3 h, vec3 l) { + return F0 + (1.0 - F0) * pow(clamp(1.0 - dot(h, l), 0.0, 1.0), 5.0); +} + +vec3 addPointLight(vec3 light_pos, vec3 light_color, float shininess, vec3 v, vec3 dir, vec3 n, float occ) { + vec3 Ks = vec3(.4545); + vec3 Kd = vec3(1.); + vec3 ref = reflect(dir, n); + vec3 vl = normalize(v); + vec3 diffuse = Kd * vec3(max(0.0, dot(vl, n))); + vec3 specular = vec3(max(0.0, dot(vl, ref))); + vec3 F = fresnel(Ks, normalize(vl - dir), vl); + float shadow = softshadow(v + n * 0.054, light_pos, .01, 30., 8.); + + //specular = pow(specular, vec3(shininess)) * occ; + return light_color * mix(diffuse, specular, F) * shadow; } +float getAmbientOcc(vec3 p, vec3 n) { + float occ = 0.; + float weight = 1.; + for(int i = 0; i < 8; i++) { + float len = 0.01 + 0.02 * float(i * i); + float dist = mapScene(p + n * len).x; + occ += (len - dist) * weight; + weight *= 0.85; + } + return 1.0 - clamp(0.6 * occ, 0., 1.); +} + +vec3 shading(vec3 v, vec3 n, vec3 dir, float material) { + float shininess = 0.1; + //float occ = getAmbientOcc(v, n); + float occ = 0.8; + vec3 outMaterial = vec3(0.0, 0.0, 0.0); + + if(material == 0.) { + outMaterial = vec3(0.8314, 0.2941, 0.2941); + shininess = 0.5; + } else if(material == 1.) { + outMaterial = vec3(0.6275, 0.1569, 0.9412); + shininess = 2.; + } else if(material == 2.) { + outMaterial = vec3(0.3255, 0.4784, 0.3255); + shininess = .2; + } else if(material == 3.) { + outMaterial = vec3(0.2471, 0.3059, 0.6314); + shininess = 1.0; + } else if(material == 4.) { + outMaterial = vec3(0.9961, 1.0, 0.9922); + shininess = .3; + } + else if(material == 5.) { + outMaterial = vec3(0.9961, 1.0, 0.9922); + shininess = .3; + } + + vec3 lights = vec3(0.); + // lights += addPointLight(vec3(-2., 10., 0.), vec3(0.73, 0.73, 0.64), shininess, v, dir, n, occ); + lights += addPointLight(vec3(-2., 10., -5.), vec3(0.77, 0.26, 0.73) * 1., shininess, v, dir, n, occ); + lights += addPointLight(vec3( 20., 10.0, -5.0 ),vec3(0.08, 0.62, 0.75)*1., shininess, v, dir, n, occ); + + vec3 lightDir = vec3(0., 1., 6.); + //float sun_dif = clamp(dot(n, lightDir), 0., 1.); + //float shadow = softshadow(v + n * 0.01, lightDir, .01, 30., 18.); + //lights += vec3(0.6431, 0.7804, 0.8588) * sun_dif * shadow * occ; + + float ind = clamp(dot(n, normalize(lightDir * vec3(.0, -1.0, -1.0))), 0.0, 1.0); + lights += vec3(0.1255, 0.1255, 0.1255) * ind; + + return outMaterial * max(vec3(0.), lights); +} + +vec3 postProcess(vec3 col) { + // float random = noise(gl_FragCoord.xy, 0.01+u_time); + // float random2 = noise(gl_FragCoord.xy, .2+u_time); + //col += 0.075*clamp(vec3(0.5*random, 0.5*random2, 0.5*random), 0.02, 1.); // dither + + // Normalized pixel coordinates (from 0 to 1) + vec2 screenCoord = getUV(vec2(0., 0.)); //gl_FragCoord.xy / u_resolution.xy; + + // Vignette + float radius = 0.8; + float d = smoothstep(radius, radius - 0.4, length(screenCoord - vec2(0.5))); + col = mix(col, col * d, 1.); + + // Contrast + float contrast = .75; + col = mix(col, smoothstep(0.0, 1.0, col), contrast); + + // Colour mapping + col *= vec3(1.0, 1.0, 1.0); + + col = pow(col, vec3(0.4545)); // gamma + + // fade in at the beginning + //col*=vec3(clamp((u_time-1.8)*0.5,0., 1.)); + + // fade out at the end + // col*=vec3(clamp((120.-u_time)*.35, 0., 1.)); + + return col; +} + +vec3 getCameraRayDir(vec2 uv, vec3 camPos, vec3 camTarget) { + // Calculate camera's "orthonormal basis", i.e. its transform matrix components + vec3 camForward = normalize(camTarget - camPos); + vec3 camRight = normalize(cross(vec3(.0, 1.0, 0.0), camForward)); + vec3 camUp = normalize(cross(camForward, camRight)); + float fov = 0.7; + vec3 vDir = normalize(uv.x * camRight + uv.y * camUp + camForward * fov); + return vDir; +} + +vec3 getCameraRayDir2(vec2 uv, vec3 camPos, vec3 lookAt, float zoom) { + vec3 f = normalize(lookAt - camPos); + vec3 r = cross(vec3(0.0, 1.0, 0.0), f); + vec3 u = cross(f, r); + vec3 c = camPos + f * zoom; + vec3 i = c + uv.x * r + uv.y * u; + return normalize(i - camPos); +} + +vec3 getCameraFov(vec2 uv, vec3 camPos, vec3 camTarget) { + vec3 camForward = normalize(camTarget - camPos); + vec3 camRight = normalize(cross(vec3(0.0, 1.0, 0.0), camForward)); + vec3 camUp = normalize(cross(camForward, camRight)); + float fov = 1.7; + // Depth of field + float dof = .3; + vec2 h = vec2(noise(gl_FragCoord.xy, u_time * 0.1)); + vec3 voff = sqrt(h.x) * (camRight * sin(h.y * 6.283) + camUp * cos(h.y * 6.283)) * dof; + voff -= camTarget; + float focusdistance = 50.; + return normalize(uv.x * camRight + uv.y * camUp + fov * camForward + voff * fov / focusdistance); +} + +vec3 render(vec2 uv) { + + bool useDof = !true; + // camera + + vec3 camTarget = vec3(15., 20., -20.); // Center point to orbit around + float orbitRadius = 20.0; // Distance from target + float orbitSpeed = 0.2; // Speed of orbit + float orbitHeight = 5.0; // Height above target + + // Calculate orbiting position + float angle = 0. * orbitSpeed; + vec3 camPos = camTarget + vec3( + cos(angle) * orbitRadius, + orbitHeight + sin(0. * 0.8) * 2.0, // Optional vertical movement + sin(angle) * orbitRadius + ); + + vec3 rayDir; + + // make orbit cam + + if(useDof) { + rayDir = getCameraFov(uv, camPos, camTarget); + } else { + rayDir = getCameraRayDir2(uv, camPos, camTarget, 1.0); + } + + vec3 col = vec3(0.102, 0.2431, 0.3412); + + vec3 hitPos = vec3(0.); + vec3 t = castRay(camPos, rayDir, hitPos); + + if (t.z == 1.) { + vec3 nor = calcNormal(hitPos); + col = shading(hitPos, nor, rayDir, t.y); + } + + return col; +} void main() { - vec2 uv = gl_FragCoord.xy * 2. / vec2(1920,1080); - vec3 col = render(uv, u_time); - - // Determine FFT bin index for current x position - int index = int(floor(uv.x*128.0)); - index = clamp(index, 0, 255); - // Get the FFT energy (clamped to avoid NaNs or overflow) - float energy = clamp(fft_output[index], .0, 1.0); + vec3 finalColor = render(getUV(vec2(0., 0.))); - float bar_height = energy; - float fade = smoothstep(bar_height, bar_height + 0.02, 1.0 - uv.y); + //finalColor = postProcess(finalColor); - vec3 color = vec3(fade); + o = vec4(finalColor, 1.); - o = vec4(color, 1.0); -} +} \ No newline at end of file diff --git a/src/shaders/fragment.inl b/src/shaders/fragment.inl index 2a78d16..812cc11 100644 --- a/src/shaders/fragment.inl +++ b/src/shaders/fragment.inl @@ -1,23 +1,153 @@ // Generated with Shader Minifier 1.5.1 (https://github.com/laurentlb/Shader_Minifier/) #ifndef FRAGMENT_INL_ # define FRAGMENT_INL_ -# define VAR_fft_output "l" +# define VAR_fft_output "m" # define VAR_o "f" -# define VAR_syncs "v" +# define VAR_syncs "n" const char *fragment_frag = "#version 460\n" "precision mediump float;" "out vec4 f;" - "const float m=22./7.;" - "layout(location=0)uniform float v[7];" - "layout(location=8)uniform float l[1024];" - "float n=v[0];" + "const float i=2.*acos(-1.),v=sqrt(5.)*.5+.5;" + "layout(location=0)uniform float n[7];" + "layout(location=8)uniform float m[512];" + "float x=n[0];" + "vec2 t()" + "{" + "vec2 f=2.*((gl_FragCoord.xy+vec2(0)*.5)/vec2(1920,1080)-.5);" + "f.x*=1;" + "return f;" + "}" + "float t(vec2 f)" + "{" + "return fract(tan(x*.1*length(f*v-f))*f.x);" + "}" + "float t(vec3 v,vec2 f)" + "{" + "v=abs(v);" + "return max(v.y-f.y,max(v.x*sqrt(3.)*.5+v.z*.5,v.z)-f.x);" + "}" + "vec2 t(vec3 v)" + "{" + "float f=0.,i=1e9,r=0.;" + "vec3 x=v;" + "float e=0.;" + "for(float c=0.;c<15.;c++)" + "{" + "x=v+vec3(11.25,0,7.5)+vec3(0,0,2.08*c);" + "for(float v=0.;v<15.;v++)" + "{" + "x=mod(v,2.)==0.?" + "x-vec3(1.8,0,1):" + "x+vec3(-1.8,0,1);" + "float n=m[int(v+1)*int(c+1)]*5.;" + "r=t(x,vec2(.86,1.+n));" + "i=min(i,r);" + "if(i==r)" + "f=4.;" + "e+=1.;" + "}" + "}" + "return vec2(i,f);" + "}" + "vec3 t(vec3 v,vec3 f,inout vec3 i)" + "{" + "float x=0.,r=0.,y=0.;" + "for(int e=0;e<24;e++)" + "{" + "i=v+f*x;" + "vec2 n=t(i);" + "x+=n.x*1.2;" + "r=n.y;" + "if(x>60.)" + "break;" + "if(n.x<.001*x)" + "{" + "y=1.;" + "break;" + "}" + "}" + "if(x>60.)" + "x=0.;" + "return vec3(x,r,y);" + "}" + "float t(vec3 v,vec3 f)" + "{" + "float i=1.,x=.01;" + "for(int e=0;e<6;e++)" + "{" + "if(x>30.)" + "break;" + "float n=t(v+x*f).x;" + "i=min(i,n/(8.*x));" + "x+=clamp(n,.1,.8);" + "if(i<-1.)" + "break;" + "}" + "i=max(i,-1.);" + "return.25*(1.+i)*(1.+i)*(2.-i);" + "}" + "vec3 e(vec3 v)" + "{" + "vec2 f=vec2(.01,0);" + "return normalize(vec3(t(v+f.xyy).x-t(v-f.xyy).x,t(v+f.yxy).x-t(v-f.yxy).x,t(v+f.yyx).x-t(v-f.yyx).x));" + "}" + "vec3 e(vec3 v,vec3 f,float i,vec3 x,vec3 n,vec3 y)" + "{" + "vec3 r=normalize(x);" + "return f*mix(vec3(1)*vec3(max(0.,dot(r,y))),vec3(max(0.,dot(r,reflect(n,y)))),vec3(.4545)+(1.-vec3(.4545))*pow(clamp(1.-dot(normalize(r-n),r),0.,1.),5.))*t(x+y*.054,v);" + "}" + "vec3 e(vec3 v,vec3 f,vec3 i,float x)" + "{" + "float n=.1;" + "vec3 y=vec3(0);" + "if(x==0.)" + "y=vec3(.8314,.2941,.2941),n=.5;" + "else if(x==1.)" + "y=vec3(.6275,.1569,.9412),n=2.;" + "else if(x==2.)" + "y=vec3(.3255,.4784,.3255),n=.2;" + "else if(x==3.)" + "y=vec3(.2471,.3059,.6314),n=1.;" + "else if(x==4.)" + "y=vec3(.9961,1,.9922),n=.3;" + "else if(x==5.)" + "y=vec3(.9961,1,.9922),n=.3;" + "v=vec3(0)+e(vec3(-2,10,-5),vec3(.77,.26,.73),n,v,i,f)+e(vec3(20,10,-5),vec3(.08,.62,.75),n,v,i,f)+vec3(.1255)*clamp(dot(f,normalize(vec3(0,1,6)*vec3(0,-1,-1))),0.,1.);" + "return y*max(vec3(0),v);" + "}" + "vec3 e(vec2 f,vec3 v,vec3 x)" + "{" + "x=normalize(x-v);" + "vec3 n=cross(vec3(0,1,0),x);" + "return normalize(v+x+f.x*n+f.y*cross(x,n)-v);" + "}" + "vec3 t(vec2 v,vec3 x,vec3 f)" + "{" + "x=normalize(f-x);" + "vec3 n=normalize(cross(vec3(0,1,0),x)),y=normalize(cross(x,n));" + "vec2 i=vec2(t(gl_FragCoord.xy));" + "return normalize(v.x*n+v.y*y+1.7*x+(sqrt(i.x)*(n*sin(i.y*6.283)+y*cos(i.y*6.283))*.3-f)*1.7/50.);" + "}" + "vec3 e(vec2 v)" + "{" + "bool n=!true;" + "vec3 f=vec3(15,20,-20),x=f+vec3(cos(0.)*20.,5.+sin(0.)*2.,sin(0.)*20.),i=n?" + "t(v,x,f):" + "e(v,x,f),r=vec3(.102,.2431,.3412),y=vec3(0);" + "f=t(x,i,y);" + "if(f.z==1.)" + "{" + "vec3 v=e(y);" + "r=e(y,v,i,f.y);" + "}" + "return r;" + "}" "void main()" "{" - "vec2 m=gl_FragCoord.xy*2./vec2(1920,1080);" - "float n=clamp(l[clamp(int(floor(m.x*128.)),0,255)],0.,1.);" - "f=vec4(vec3(smoothstep(n,n+.02,1.-m.y)),1);" + "vec3 v=e(t());" + "f=vec4(v,1);" "}"; #endif // FRAGMENT_INL_ diff --git a/src/shaders/random.frag b/src/shaders/random.frag new file mode 100644 index 0000000..535995b --- /dev/null +++ b/src/shaders/random.frag @@ -0,0 +1,654 @@ + + +//////////////////////////////////////////////////////////////// +// +// HG_SDF +// +// GLSL LIBRARY FOR BUILDING SIGNED DISTANCE BOUNDS +// +// version 2021-07-28 +// +// Check https://mercury.sexy/hg_sdf for updates +// and usage examples. Send feedback to spheretracing@mercury.sexy. +// +// Brought to you by MERCURY https://mercury.sexy/ +// +// +// +// Released dual-licensed under +// Creative Commons Attribution-NonCommercial (CC BY-NC) +// or +// MIT License +// at your choice. +// +// SPDX-License-Identifier: MIT OR CC-BY-NC-4.0 +// +// ///// + +//////////////////////////////////////////////////////////////// +// +// HELPER FUNCTIONS/MACROS +// +//////////////////////////////////////////////////////////////// + +const float PI = 3.14159265; +const float TAU = (2. * PI); +const float PHI = sqrt(5.) * 0.5 + 0.5; + +vec3 applyFog(vec3 col, float t, vec3 rd, vec3 lightDir, float b) { + float fogAmount = 1.0 - exp(-t * b); + float sunAmount = max(dot(rd, lightDir), 0.); + vec3 fogColor = mix(vec3(0.1529, 0.1137, 0.2), // blue + vec3(0.2588, 0.1765, 0.3529), // yellow + pow(sunAmount, 1.0)); + return mix(col, fogColor, fogAmount); +} + +mat2 scale(vec2 scale) { + return mat2(1. / scale.x, 0.0, 0.0, 1. / scale.y); +} + +// Sign function that doesn't return 0 +float sgn(float x) { + return (x < 0.) ? -1. : 1.; +} + +vec2 sgn(vec2 v) { + return vec2((v.x < 0.) ? -1. : 1., (v.y < 0.) ? -1. : 1.); +} + +float square(float x) { + return x * x; +} + +vec2 square(vec2 x) { + return x * x; +} + +vec3 square(vec3 x) { + return x * x; +} + +float lengthSqr(vec3 x) { + return dot(x, x); +} + +// Maximum/minumum elements of a vector +float vmax(vec2 v) { + return max(v.x, v.y); +} + +float vmax(vec3 v) { + return max(max(v.x, v.y), v.z); +} + +float vmax(vec4 v) { + return max(max(v.x, v.y), max(v.z, v.w)); +} + +float vmin(vec2 v) { + return min(v.x, v.y); +} + +float vmin(vec3 v) { + return min(min(v.x, v.y), v.z); +} + +float vmin(vec4 v) { + return min(min(v.x, v.y), min(v.z, v.w)); +} + +// Hexagonal prism, circumcircle variant +float fHexagonCircumcircle(vec3 p, vec2 h) { + vec3 q = abs(p); + return max(q.y - h.y, max(q.x * sqrt(3.) * 0.5 + q.z * 0.5, q.z) - h.x); + //this is mathematically equivalent to this line, but less efficient: + //return max(q.y - h.y, max(dot(vec2(cos(PI/3), sin(PI/3)), q.zx), q.z) - h.x); +} + +//////////////////////////////////////////////////////////////// +// +// PRIMITIVE DISTANCE FUNCTIONS +// +//////////////////////////////////////////////////////////////// +// +// Conventions: +// +// Everything that is a distance function is called fSomething. +// The first argument is always a point in 2 or 3-space called

. +// Unless otherwise noted, (if the object has an intrinsic "up" +// side or direction) the y axis is "up" and the object is +// centered at the origin. +// +//////////////////////////////////////////////////////////////// + +float fSphere(vec3 p, float r) { + return length(p) - r; +} + +// Plane with normal n (n is normalized) at some distance from the origin +float fPlane(vec3 p, vec3 n, float distanceFromOrigin) { + return dot(p, n) + distanceFromOrigin; +} + +// Cheap Box: distance to corners is overestimated +float fBoxCheap(vec3 p, vec3 b) { //cheap box + return vmax(abs(p) - b); +} + +// Box: correct distance to corners +float fBox(vec3 p, vec3 b) { + vec3 d = abs(p) - b; + return length(max(d, vec3(0))) + vmax(min(d, vec3(0))); +} + +// Same as above, but in two dimensions (an endless box) +float fBox2Cheap(vec2 p, vec2 b) { + return vmax(abs(p) - b); +} + +float fBox2(vec2 p, vec2 b) { + vec2 d = abs(p) - b; + return length(max(d, vec2(0.))) + vmax(min(d, vec2(0.))); +} + +// Endless "corner" +float fCorner(vec2 p) { + return length(max(p, vec2(0.))) + vmax(min(p, vec2(0.))); +} + +// Cylinder standing upright on the xz plane +float fCylinder(vec3 p, float r, float height) { + float d = length(p.xz) - r; + d = max(d, abs(p.y) - height); + return d; +} + +// Capsule: A Cylinder with round caps on both sides +float fCapsule(vec3 p, float r, float c) { + return mix(length(p.xz) - r, length(vec3(p.x, abs(p.y) - c, p.z)) - r, step(c, abs(p.y))); +} + +// Distance to line segment between and , used for fCapsule() version 2below +float fLineSegment(vec3 p, vec3 a, vec3 b) { + vec3 ab = b - a; + float t = clamp(dot(p - a, ab) / dot(ab, ab), 0., 1.); + return length((ab * t + a) - p); +} + +// Capsule version 2: between two end points and with radius r +float fCapsule(vec3 p, vec3 a, vec3 b, float r) { + return fLineSegment(p, a, b) - r; +} + +// Torus in the XZ-plane +float fTorus(vec3 p, float smallRadius, float largeRadius) { + return length(vec2(length(p.xz) - largeRadius, p.y)) - smallRadius; +} + +// A circle line. Can also be used to make a torus by subtracting the smaller radius of the torus. +float fCircle(vec3 p, float r) { + float l = length(p.xz) - r; + return length(vec2(p.y, l)); +} + +// A circular disc with no thickness (i.e. a cylinder with no height). +// Subtract some value to make a flat disc with rounded edge. +float fDisc(vec3 p, float r) { + float l = length(p.xz) - r; + return l < 0. ? abs(p.y) : length(vec2(p.y, l)); +} + +// Hexagonal prism, incircle variant +float fHexagonIncircle(vec3 p, vec2 h) { + return fHexagonCircumcircle(p, vec2(h.x * sqrt(3.) * 0.5, h.y)); +} + +// Cone with correct distances to tip and base circle. Y is up, 0 is in the middle of the base. +float fCone(vec3 p, float radius, float height) { + vec2 q = vec2(length(p.xz), p.y); + vec2 tip = q - vec2(0, height); + vec2 mantleDir = normalize(vec2(height, radius)); + float mantle = dot(tip, mantleDir); + float d = max(mantle, -q.y); + float projected = dot(tip, vec2(mantleDir.y, -mantleDir.x)); + + // distance to tip + if((q.y > height) && (projected < 0.)) { + d = max(d, length(tip)); + } + + // distance to base ring + if((q.x > radius) && (projected > length(vec2(height, radius)))) { + d = max(d, length(q - vec2(radius, 0))); + } + return d; +} + +//////////////////////////////////////////////////////////////// +// +// DOMAIN MANIPULATION OPERATORS +// +//////////////////////////////////////////////////////////////// +// +// Conventions: +// +// Everything that modifies the domain is named pSomething. +// +// Many operate only on a subset of the three dimensions. For those, +// you must choose the dimensions that you want manipulated +// by supplying e.g. or +// +// is always the first argument and modified in place. +// +// Many of the operators partition space into cells. An identifier +// or cell index is returned, if possible. This return value is +// intended to be optionally used e.g. as a random seed to change +// parameters of the distance functions inside the cells. +// +// Unless stated otherwise, for cell index 0,

is unchanged and cells +// are centered on the origin so objects don't have to be moved to fit. +// +// +//////////////////////////////////////////////////////////////// + +// Rotate around a coordinate axis (i.e. in a plane perpendicular to that axis) by angle . +// Read like this: R(p.xz, a) rotates "x towards z". +// This is fast if is a compile-time constant and slower (but still practical) if not. +void pR(inout vec2 p, float a) { + p = cos(a) * p + sin(a) * vec2(p.y, -p.x); +} + +// Shortcut for 45-degrees rotation +void pR45(inout vec2 p) { + p = (p + vec2(p.y, -p.x)) * sqrt(0.5); +} + +// Repeat space along one axis. Use like this to repeat along the x axis: +// - using the return value is optional. +float pMod1(inout float p, float size) { + float halfsize = size * 0.5; + float c = floor((p + halfsize) / size); + p = mod(p + halfsize, size) - halfsize; + return c; +} + +// Same, but mirror every second cell so they match at the boundaries +float pModMirror1(inout float p, float size) { + float halfsize = size * 0.5; + float c = floor((p + halfsize) / size); + p = mod(p + halfsize, size) - halfsize; + p *= mod(c, 2.0) * 2. - 1.; + return c; +} + +// Repeat the domain only in positive direction. Everything in the negative half-space is unchanged. +float pModSingle1(inout float p, float size) { + float halfsize = size * 0.5; + float c = floor((p + halfsize) / size); + if(p >= 0.) + p = mod(p + halfsize, size) - halfsize; + return c; +} + +// Repeat only a few times: from indices to (similar to above, but more flexible) +float pModInterval1(inout float p, float size, float start, float stop) { + float halfsize = size * 0.5; + float c = floor((p + halfsize) / size); + p = mod(p + halfsize, size) - halfsize; + if(c > stop) { //yes, this might not be the best thing numerically. + p += size * (c - stop); + c = stop; + } + if(c < start) { + p += size * (c - start); + c = start; + } + return c; +} + +// Repeat around the origin by a fixed angle. +// For easier use, num of repetitions is use to specify the angle. +float pModPolar(inout vec2 p, float repetitions) { + float angle = 2. * PI / repetitions; + float a = atan(p.y, p.x) + angle / 2.; + float r = length(p); + float c = floor(a / angle); + a = mod(a, angle) - angle / 2.; + p = vec2(cos(a), sin(a)) * r; + // For an odd number of repetitions, fix cell index of the cell in -x direction + // (cell index would be e.g. -5 and 5 in the two halves of the cell): + if(abs(c) >= (repetitions / 2.)) + c = abs(c); + return c; +} + +// Repeat in two dimensions +vec2 pMod2(inout vec2 p, vec2 size) { + vec2 c = floor((p + size * 0.5) / size); + p = mod(p + size * 0.5, size) - size * 0.5; + return c; +} + +// Same, but mirror every second cell so all boundaries match +vec2 pModMirror2(inout vec2 p, vec2 size) { + vec2 halfsize = size * 0.5; + vec2 c = floor((p + halfsize) / size); + p = mod(p + halfsize, size) - halfsize; + p *= mod(c, vec2(2.)) * 2. - vec2(1); + return c; +} + +// Same, but mirror every second cell at the diagonal as well +vec2 pModGrid2(inout vec2 p, vec2 size) { + vec2 c = floor((p + size * 0.5) / size); + p = mod(p + size * 0.5, size) - size * 0.5; + p *= mod(c, vec2(2.)) * 2. - vec2(1.); + p -= size / 2.; + if(p.x > p.y) + p.xy = p.yx; + return floor(c / 2.); +} + +// Repeat in three dimensions +vec3 pMod3(inout vec3 p, vec3 size) { + vec3 c = floor((p + size * 0.5) / size); + p = mod(p + size * 0.5, size) - size * 0.5; + return c; +} + +// Mirror at an axis-aligned plane which is at a specified distance from the origin. +float pMirror(inout float p, float dist) { + float s = sgn(p); + p = abs(p) - dist; + return s; +} + +// Mirror in both dimensions and at the diagonal, yielding one eighth of the space. +// translate by dist before mirroring. +vec2 pMirrorOctant(inout vec2 p, vec2 dist) { + vec2 s = sgn(p); + pMirror(p.x, dist.x); + pMirror(p.y, dist.y); + if(p.y > p.x) + p.xy = p.yx; + return s; +} + +// Reflect space at a plane +float pReflect(inout vec3 p, vec3 planeNormal, float offset) { + float t = dot(p, planeNormal) + offset; + if(t < 0.) { + p = p - (2. * t) * planeNormal; + } + return sgn(t); +} + +//////////////////////////////////////////////////////////////// +// +// OBJECT COMBINATION OPERATORS +// +//////////////////////////////////////////////////////////////// +// +// We usually need the following boolean operators to combine two objects: +// Union: OR(a,b) +// Intersection: AND(a,b) +// Difference: AND(a,!b) +// (a and b being the distances to the objects). +// +// The trivial implementations are min(a,b) for union, max(a,b) for intersection +// and max(a,-b) for difference. To combine objects in more interesting ways to +// produce rounded edges, chamfers, stairs, etc. instead of plain sharp edges we +// can use combination operators. It is common to use some kind of "smooth minimum" +// instead of min(), but we don't like that because it does not preserve Lipschitz +// continuity in many cases. +// +// Naming convention: since they return a distance, they are called fOpSomething. +// The different flavours usually implement all the boolean operators above +// and are called fOpUnionRound, fOpIntersectionRound, etc. +// +// The basic idea: Assume the object surfaces intersect at a right angle. The two +// distances and constitute a new local two-dimensional coordinate system +// with the actual intersection as the origin. In this coordinate system, we can +// evaluate any 2D distance function we want in order to shape the edge. +// +// The operators below are just those that we found useful or interesting and should +// be seen as examples. There are infinitely more possible operators. +// +// They are designed to actually produce correct distances or distance bounds, unlike +// popular "smooth minimum" operators, on the condition that the gradients of the two +// SDFs are at right angles. When they are off by more than 30 degrees or so, the +// Lipschitz condition will no longer hold (i.e. you might get artifacts). The worst +// case is parallel surfaces that are close to each other. +// +// Most have a float argument to specify the radius of the feature they represent. +// This should be much smaller than the object size. +// +// Some of them have checks like "if ((-a < r) && (-b < r))" that restrict +// their influence (and computation cost) to a certain area. You might +// want to lift that restriction or enforce it. We have left it as comments +// in some cases. +// +// usage example: +// +// float fTwoBoxes(vec3 p) { +// float box0 = fBox(p, vec3(1)); +// float box1 = fBox(p-vec3(1), vec3(1)); +// return fOpUnionChamfer(box0, box1, 0.2); +// } +// +//////////////////////////////////////////////////////////////// + +// The "Chamfer" flavour makes a 45-degree chamfered edge (the diagonal of a square of size ): +float fOpUnionChamfer(float a, float b, float r) { + return min(min(a, b), (a - r + b) * sqrt(0.5)); +} + +// Intersection has to deal with what is normally the inside of the resulting object +// when using union, which we normally don't care about too much. Thus, intersection +// implementations sometimes differ from union implementations. +float fOpIntersectionChamfer(float a, float b, float r) { + return max(max(a, b), (a + r + b) * sqrt(0.5)); +} + +// Difference can be built from Intersection or Union: +float fOpDifferenceChamfer(float a, float b, float r) { + return fOpIntersectionChamfer(a, -b, r); +} + +// The "Round" variant uses a quarter-circle to join the two objects smoothly: +float fOpUnionRound(float a, float b, float r) { + vec2 u = max(vec2(r - a, r - b), vec2(0)); + return max(r, min(a, b)) - length(u); +} + +float fOpIntersectionRound(float a, float b, float r) { + vec2 u = max(vec2(r + a, r + b), vec2(0)); + return min(-r, max(a, b)) + length(u); +} + +float fOpDifferenceRound(float a, float b, float r) { + return fOpIntersectionRound(a, -b, r); +} + +// The "Columns" flavour makes n-1 circular columns at a 45 degree angle: +float fOpUnionColumns(float a, float b, float r, float n) { + if((a < r) && (b < r)) { + vec2 p = vec2(a, b); + float columnradius = r * sqrt(2.) / ((n - 1.) * 2. + sqrt(2.)); + pR45(p); + p.x -= sqrt(2.) / 2. * r; + p.x += columnradius * sqrt(2.); + if(mod(n, 2.) == 1.) { + p.y += columnradius; + } + // At this point, we have turned 45 degrees and moved at a point on the + // diagonal that we want to place the columns on. + // Now, repeat the domain along this direction and place a circle. + pMod1(p.y, columnradius * 2.); + float result = length(p) - columnradius; + result = min(result, p.x); + result = min(result, a); + return min(result, b); + } else { + return min(a, b); + } +} + +float fOpDifferenceColumns(float a, float b, float r, float n) { + a = -a; + float m = min(a, b); + //avoid the expensive computation where not needed (produces discontinuity though) + if((a < r) && (b < r)) { + vec2 p = vec2(a, b); + float columnradius = r * sqrt(2.) / n / 2.0; + columnradius = r * sqrt(2.) / ((n - 1.) * 2. + sqrt(2.)); + + pR45(p); + p.y += columnradius; + p.x -= sqrt(2.) / 2. * r; + p.x += -columnradius * sqrt(2.) / 2.; + + if(mod(n, 2.) == 1.) { + p.y += columnradius; + } + pMod1(p.y, columnradius * 2.); + + float result = -length(p) + columnradius; + result = max(result, p.x); + result = min(result, a); + return -min(result, b); + } else { + return -m; + } +} + +float fOpIntersectionColumns(float a, float b, float r, float n) { + return fOpDifferenceColumns(a, -b, r, n); +} + +// The "Stairs" flavour produces n-1 steps of a staircase: +// much less stupid version by paniq +float fOpUnionStairs(float a, float b, float r, float n) { + float s = r / n; + float u = b - r; + return min(min(a, b), 0.5 * (u + a + abs((mod(u - a + s, 2. * s)) - s))); +} + +// We can just call Union since stairs are symmetric. +float fOpIntersectionStairs(float a, float b, float r, float n) { + return -fOpUnionStairs(-a, -b, r, n); +} + +float fOpDifferenceStairs(float a, float b, float r, float n) { + return -fOpUnionStairs(-a, b, r, n); +} + +// Similar to fOpUnionRound, but more lipschitz-y at acute angles +// (and less so at 90 degrees). Useful when fudging around too much +// by MediaMolecule, from Alex Evans' siggraph slides +float fOpUnionSoft(float a, float b, float r) { + float e = max(r - abs(a - b), 0.); + return min(a, b) - e * e * 0.25 / r; +} + +// produces a cylindical pipe that runs along the intersection. +// No objects remain, only the pipe. This is not a boolean operator. +float fOpPipe(float a, float b, float r) { + return length(vec2(a, b)) - r; +} + +// first object gets a v-shaped engraving where it intersect the second +float fOpEngrave(float a, float b, float r) { + return max(a, (a + r - abs(b)) * sqrt(0.5)); +} + +// first object gets a capenter-style groove cut out +float fOpGroove(float a, float b, float ra, float rb) { + return max(a, min(a + ra, rb - abs(b))); +} + +// first object gets a capenter-style tongue attached +float fOpTongue(float a, float b, float ra, float rb) { + return min(a, max(a - ra, abs(b) - rb)); +} + +//#endSection End of library + +// https://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl +// golden_noise +float noise(in vec2 xy, in float seed) { + return fract(tan(distance(xy * PHI, xy) * seed) * xy.x); +} + +vec3 rnd23(vec2 p) { + vec3 p3 = fract(p.xyx * vec3(.1031, .1030, .0973)); + p3 += dot(p3, p3.yxz + 33.33); + return fract((p3.xxy + p3.yzz) * p3.zyx); +} + +mat2 Rot(float a) { + float s = sin(a), c = cos(a); + return mat2(c, -s, s, c); +} + +float opExtrusion(in vec3 p, in float sdf, in float h) { + vec2 w = vec2(sdf, abs(p.z) - h); + return min(max(w.x, w.y), 0.0) + length(max(w, 0.0)); +} + +float sdCog2d(vec2 pos) { + float r = length(pos) * 2.; + float a = atan(pos.y, pos.x); + float f = 1. - smoothstep(-0.2, .8, sin(a * 12.)) * 0.14; + f = smoothstep(f, f + 2., r); + return f; +} + +float sdCog(vec3 pos, float angle) { + pos.xy *= Rot(angle); + float d1 = opExtrusion(pos, sdCog2d(pos.xy), 0.05); + float d2 = fCapsule(pos, vec3(0., 0.0, 0.), vec3(0., 0., 1.), 0.2); + return 0.8 * fOpDifferenceRound(d1, d2, 0.05) - 0.003; +} + +float sdCapsule(vec3 p, vec3 a, vec3 b, float r) { + vec3 pa = p - a, ba = b - a; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return length(pa - ba * h) - r; +} + +float sdCylinder(vec3 p, vec3 a, vec3 b, float r) { + + vec3 ba = b - a; + vec3 pa = p - a; + float baba = dot(ba, ba); + float paba = dot(pa, ba); + float x = length(pa * baba - ba * paba) - r * baba; + float y = abs(paba - baba * 0.5) - baba * 0.5; + float x2 = x * x; + float y2 = y * y * baba; + float d = (max(x, y) < 0.0) ? -min(x2, y2) : (((x > 0.0) ? x2 : 0.0) + ((y > 0.0) ? y2 : 0.0)); + + return sign(d) * sqrt(abs(d)) / baba; +} + +float sdPlane(vec3 p, vec4 n) { + // n must be normalized + return dot(p, n.xyz) + n.w; +} + +// Add this function before mapScene +float rippleEffect(vec3 p, vec3 center, float time) { + float dist = length(p.xz - center.xz); + float wave = sin(dist * 2.0 - time * 8.0) * exp(-dist * 0.3); + return wave * 0.5; // Adjust amplitude as needed +} + +/*// Hexagonal prism, circumcircle variant +float fHexagonCircumcircle(vec3 p, vec2 h) { + vec3 q = abs(p); + return max(q.y - h.y, max(q.x * sqrt(3.) * 0.5 + q.z * 0.5, q.z) - h.x); + //this is mathematically equivalent to this line, but less efficient: + //return max(q.y - h.y, max(dot(vec2(cos(PI/3), sin(PI/3)), q.zx), q.z) - h.x); +}*/ \ No newline at end of file