← geometric-flows

3d Flow

Flowing 3d lattice

Source

common.glsl

//vec3 lat1 = vec3(1.,1.618033988749895,-1.618033988749895);
//vec3 lat2 = vec3(1.618033988749895,1.,-1.618033988749895);
//vec3 lat3 = vec3(1.618033988749895,1.618033988749895,1.);

vec3 lat1 = vec3(1.,0.,0.);
vec3 lat2 = vec3(-.5,0.866,.9);
vec3 lat3 = vec3(-.5,-0.8659,.99);
//vec3 lat1 = vec3(.25,0.,0.);
//vec3 lat2 = vec3(0.,2.,0.);
//vec3 lat3 = vec3(0.,0.,2.);

vec4 dotCol = vec4(.8,.05,.04,1.);
vec4 backCol = vec4(1.,1.,1.,0);
vec4 basisCol = vec4(.651,.669,.95,1.);

mat3 flow3g(in float dTime, float a, float s)
{

    float s1 = exp(dTime*s);
    float s2 = exp(dTime*(1.-s));
    
    return mat3(
        s1*cos(a*dTime),s2*sin(a*dTime),0.,
        -s1*sin(a*dTime),s2*cos(a*dTime),0.,
        0.,0.,exp(-dTime)
    );
}

mat3 flow3h(in float dTime, float a, float s)
{    
    return mat3(
       1.,dTime,dTime*dTime/2.,
       0.,1.,dTime,
        0.,0.,1.
    );
}

float sdSphere(vec3 p, float r) { return length(p) - r; }

// Distance to sphere centered at closest lattice point.
float sdLatticeBalls(mat3 L, vec3 p, float r, out vec3 lat)
{
    mat3 Linv = inverse(L);

    vec3 q = Linv * p;         // lattice coordinates
    vec3 n = floor(q + 0.5);   // nearest integer lattice point

    float d = 1e9;

    lat = L*n;
    
    return sdSphere(p - lat, r);
}

vec3 getRayDir(vec2 uv, float fov)
{
    // uv is on shader plane; camera looks down +z
    return normalize(vec3(uv, fov));
}
void reducePair(inout vec3 bi, vec3 bj)
{
    float m = round(dot(bi, bj) / dot(bj, bj));
    bi -= m * bj;
}

void reduceLattice(inout vec3 b0,
                   inout vec3 b1,
                   inout vec3 b2)
{
    // fixed number of passes is sufficient in practice
    for (int k = 0; k < 100; ++k)
    {
        reducePair(b0, b1);
        reducePair(b0, b2);

        reducePair(b1, b0);
        reducePair(b1, b2);

        reducePair(b2, b0);
        reducePair(b2, b1);
    }
}

image.glsl

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    
    float r = .16;
    vec2 uv = (fragCoord - 0.5*iResolution.xy) / iResolution.y;

    vec3 M1 = texelFetch(notshaders, ivec2(1,1), 0).xyz;
    vec3 M2 = texelFetch(notshaders, ivec2(2,1), 0).xyz;
    vec3 M3 = texelFetch(notshaders, ivec2(3,1), 0).xyz;
    
    mat3 L = mat3(M1,M2,M3);

    // Simple camera
    float camdist = 12.;
    vec3 ro = vec3(0.0, 0.0, -camdist);
    vec3 rd = getRayDir(uv, 1.);

    // Raymarch
    float startd = camdist/rd.z;
    //startd = 0.;

    float t = 0.;
    float hit = 0.0;
    vec3  p;
    vec3 lat;
    
    int draw = 100;

    for (int it = 0; it < draw*2; it++)
    {
        p = ro + (t+startd)*rd;
        float d = sdLatticeBalls(L, p, r, lat);

        if (d < 0.) { d = -3.*r; }
        else if (d < .005) { hit = 1.0; break; }
       
        t += d * 0.95;
        if (t+startd > float(draw)) break;
    }

    vec3 col = vec3(1.0);

    if (hit > 0.5)
    {
        vec3 n = normalize(p-lat);
        vec3 l = vec3(0.,0.,-100.);
        
        l = normalize(l-p);
        float light = dot(n,l)*0.9 + 0.1;
        
        col = vec3(.1+light*.7,0.05,0.04);
        
        float B = min(min(length(lat), length(lat-M1)), min(length(lat-M2), length(lat-M3)));
        
        B = min(min(B, length(lat+M1)), min(length(lat+M2), length(lat+M3)));
        
        if( B < .1 ) { col = vec3(0.04, .05,.1+light*.7); }
        col = mix(col, backCol.xyz, t/float(draw));
    }


    fragColor = vec4(col, 1.0);
}

notshaders.glsl

// Stored values

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec4 col = vec4(1.,.5,.5,1.);
    vec2 mp = iMouse.xy/iResolution.xy;
    
    // Update the lattice
    if(fragCoord.y == 1.5 && fragCoord.x < 4.)
    {
        vec3 M1 = lat1;
        vec3 M2 = lat2;
        vec3 M3 = lat3;
        
        if(iFrame > 0)
        {
            M1 = texelFetch(notshaders, ivec2(1,1), 0).xyz;
            M2 = texelFetch(notshaders, ivec2(2,1), 0).xyz;
            M3 = texelFetch(notshaders, ivec2(3,1), 0).xyz;
            
            mat3 m = flow3g(.001, 5.*(mp.x-.5),mp.y);
            
            M1 = m*M1;
            M2 = m*M2;
            M3 = m*M3;   
        }
        
        reduceLattice(M1,M2,M3); 
        if(fragCoord.x == 1.5) { col = vec4(M1,0); }
        if(fragCoord.x == 2.5) { col = vec4(M2,0); }
        if(fragCoord.x == 3.5) { col = vec4(M3,0); }      
    }
       
    fragColor = col;
}

3dlattice/script.js

// Particle state - positions and velocities
const particles = [];
const NUM_PARTICLES = 50;

export function setup(engine) {
  // Initialize particles with random positions and velocities
  for (let i = 0; i < NUM_PARTICLES; i++) {
    particles.push({
      x: Math.random(),
      y: Math.random(),
      vx: (Math.random() - 0.5) * 0.3,
      vy: (Math.random() - 0.5) * 0.3,
    });
  }
}

export function onFrame(engine, time, deltaTime, frame) {
  // Update particle positions
  for (const p of particles) {
    p.x += p.vx * deltaTime;
    p.y += p.vy * deltaTime;

    // Bounce off walls
    if (p.x < 0 || p.x > 1) {
      p.vx *= -1;
      p.x = Math.max(0, Math.min(1, p.x));
    }
    if (p.y < 0 || p.y > 1) {
      p.vy *= -1;
      p.y = Math.max(0, Math.min(1, p.y));
    }
  }

  // Pack particle data into Float32Array for the shader
  // Each particle is a vec4: (x, y, vx, vy)
  const data = new Float32Array(NUM_PARTICLES * 4);
  for (let i = 0; i < NUM_PARTICLES; i++) {
    const p = particles[i];
    data[i * 4 + 0] = p.x;
    data[i * 4 + 1] = p.y;
    data[i * 4 + 2] = p.vx;
    data[i * 4 + 3] = p.vy;
  }

  engine.setUniformValue('uParticles', data);
}