Source: lib/xutils/shaders/shape-layers.glsl.js


import {glx, initPhongUni, updatePhongUni} from './glx.glsl';

/**<p>Rendering tessellated planes in a box model.</p>
 * Issue:<br>
 * The problem is it can't finding out distance to a polygon to restrict the floor
 * area. There is an example by Inigo Quilez [2] that can figure out distance
 * quickly in fragment shader but the problem is we can't find out an efficient
 * way to send polygon info into fragment with webgl<sup>[3]</sup>.<br>
 * Reference:<br>
 * [1] <a href='https://www.shadertoy.com/view/MsjSzz'>Da Rasterizer,
 * Example of fragment matrix operation</a>.<br>
 * Simplified Example: <pre>
    #define WEIGHT (12.0 / iResolution.x)

    // rasterize functions
    float line(vec2 p, vec2 p0, vec2 p1, float w) {
        vec2 d = p1 - p0;
        float t = clamp(dot(d,p-p0) / dot(d,d), 0.0,1.0);
        vec2 proj = p0 + d * t;
        float dist = length(p - proj);
        dist = 1.0/dist * WEIGHT * w;
        return min(dist*dist,1.0);
    }

    // matrices
    mat4 getRotMatrix(vec3 a) {
        vec3 s = sin(a);
        vec3 c = cos(a);
        mat4 ret;
        ret[0] = vec4(c.y*c.z,c.y*s.z,-s.y,0.0);
        ret[1] = vec4(s.x*s.y*c.z-c.x*s.z,s.x*s.y*s.z+c.x*c.z,s.x*c.y,0.0);
        ret[2] = vec4(c.x*s.y*c.z+s.x*s.z, c.x*s.y*s.z-s.x*c.z,   c.x*c.y,0.0);
        ret[3] = vec4(0.0,0.0,0.0,1.0);
        return ret;
    }
    mat4 getPosMatrix(vec3 p) {
        mat4 ret;
        ret[0] = vec4(1.0,0.0,0.0,p.x);
        ret[1] = vec4(0.0,1.0,0.0,p.y);
        ret[2] = vec4(0.0,0.0,1.0,p.z);
        ret[3] = vec4(0.0,0.0,0.0,1.0);
        return ret;
    }

    void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
        vec2 uv = fragCoord.xy / iResolution.xy;
        uv = uv * 2.0 - 1.0;
        uv.x *= iResolution.x / iResolution.y;

        float line_width = 0.4;
        float time = iTime * 0.31415;
        vec3 c = vec3(mix(vec3(0.19,0.13,0.1),vec3(1.0), 0.5*pow(length(uv)*0.5,2.0)));
        mat4 cam = getPosMatrix(vec3(0.0,0.0,10.0));
        mat4 rot = getRotMatrix(vec3(time,time*0.86,time*0.473));

        vec3 instances[2];
        instances[0] = vec3( 0.0, 0.0,-1.0);
        // box pipeline
        for(int dip = 0; dip < 2; dip++) {
            // input assembly
            vec3 vert[8];
            vert[0] = vec3(-1.0,-1.0, 1.0);
            vert[1] = vec3(-1.0, 1.0, 1.0);
            vert[2] = vec3( 1.0, 1.0, 1.0);
            vert[3] = vec3( 1.0,-1.0, 1.0);
            vert[4] = vec3(-1.0,-1.0,-1.0);
            vert[5] = vec3(-1.0, 1.0,-1.0);
            vert[6] = vec3( 1.0, 1.0,-1.0);
            vert[7] = vec3( 1.0,-1.0,-1.0);

            // vertex processing
            mat4 pos = getPosMatrix(instances[dip] * 4.0);
            mat4 mat = pos * rot * cam;

            for(int i = 0; i < 8; i++) {
                // transform
                vert[i] = (vec4(vert[i],1.0) * mat).xyz;
                // perspective
                vert[i].z = 1.0 / vert[i].z;
                vert[i].xy *= vert[i].z;
            }

            // primitive assembly and rasterize
            float i;
            i  = line(uv,vert[0].xy,vert[1].xy,line_width);
            i += line(uv,vert[1].xy,vert[2].xy,line_width);
            i += line(uv,vert[2].xy,vert[3].xy,line_width);
            i += line(uv,vert[3].xy,vert[0].xy,line_width);
            i += line(uv,vert[4].xy,vert[5].xy,line_width);
            i += line(uv,vert[5].xy,vert[6].xy,line_width);
            i += line(uv,vert[6].xy,vert[7].xy,line_width);
            i += line(uv,vert[7].xy,vert[4].xy,line_width);
            i += line(uv,vert[0].xy,vert[4].xy,line_width);
            i += line(uv,vert[1].xy,vert[5].xy,line_width);
            i += line(uv,vert[2].xy,vert[6].xy,line_width);
            i += line(uv,vert[3].xy,vert[7].xy,line_width);

            c += clamp(i, 0., 1.);
        }

        fragColor = vec4(c,1.0);
    } </pre>
 * See also another
 * <a href='https://www.shadertoy.com/view/Xlf3zl'>interesting example</a>.<br>
 * [2] <a href='http://geomalgorithms.com/a03-_inclusion.html'>the Winding Number Algorithm</a>.<br>
 * To find out distance to a polygon in a plane, one can use [2].
 * The shadertoy example can be found <a href='https://www.shadertoy.com/view/wdBXRW'>here</a>.
 * A simplified version showing wn=2 are rendered as outside:<pre>
    // The MIT License
    // Copyright © 2019 Inigo Quilez
    // Distance to a regular pentagon, without trigonometric functions.
    float dot2( in vec2 v ) { return dot(v,v); }
    float cross2d( in vec2 v0, in vec2 v1) { return v0.x*v1.y - v0.y*v1.x; }
    const int N = 9;

    float sdPoly( in vec2[N] v, in vec2 p ) {
        const int num = v.length();
        float d = dot(p-v[0],p-v[0]);
        float s = 1.0;
        for( int i=0, j=num-1; i<num; j=i, i++ ) {
            // distance
            vec2 e = v[j] - v[i];
            vec2 w =    p - v[i];
            vec2 b = w - e*clamp( dot(w,e)/dot(e,e), 0.0, 1.0 );
            d = min( d, dot(b,b) );
            // winding number from http://geomalgorithms.com/a03-_inclusion.html
            bvec3 cond = bvec3( p.y>=v[i].y, p.y<v[j].y, e.x*w.y>e.y*w.x ); // e.x / e.y > w.x / w.y
            if( all(cond) || all(not(cond)) ) s *= -1.0;
        }
        return s * sqrt(d);
    }

    void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
        vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;

        vec2 v0 = 0.8*cos( 0.40*iTime + vec2(0.0,2.00) + 0.0 );
        vec2 v1 = 0.8*cos( 0.45*iTime + vec2(0.0,1.50) + 1.0 );
        vec2 v2 = 0.8*cos( 0.50*iTime + vec2(0.0,3.00) + 2.0 );
        vec2 v3 = 0.8*cos( 0.55*iTime + vec2(0.0,2.00) + 4.0 );
        vec2 v4 = 0.8*cos( 0.60*iTime + vec2(0.0,1.00) + 5.0 );
        vec2 v5 = 0.8*cos( 0.45*iTime + vec2(0.0,1.50) + 6.0 );
        vec2 v6 = 0.8*cos( 0.50*iTime + vec2(0.0,3.00) + 7.0 );
        vec2 v7 = 0.8*cos( 0.55*iTime + vec2(0.0,2.00) + 8.0 );
        vec2 v8 = 0.8*cos( 0.60*iTime + vec2(0.0,1.00) + 9.0 );

        // add more points
        vec2[] poly = vec2[](v0,v1,v2,v3,v4,v5,v6,v7,v8);

        float d = sdPoly(poly, p );

        vec3 col = vec3(0.);
        col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.015,abs(d)) );
        fragColor = vec4(col,1.0);
        if (d < 0.)
            fragColor.b = .4;
    } </pre>
 * [3] <a href='https://stackoverflow.com/questions/28437241/get-vertex-positions-in-fragment-shader'>
 * discussion on stackoverflow: Get vertex positions in fragment shader</a><br>
 * @param {object} vparas Visual.paras
 * @return {object} {fragmentShader, vertexShader}
 * @member xglsl.xyzLayer2
 * @function
 */
export function xyzLayer2(vparas) {
 var {edgeWeight, xytile, yztile, xztile} = para4Layers(vparas);

 return { fragmentShader: `
  #define WEIGHT ${edgeWeight}

  uniform sampler2D u_tex;
  uniform float now;
  uniform vec4 u_hue;

  varying vec3 P;
  varying vec4 vcent;
  varying vec3 vxyzOffset;
  varying float vnorth;
  varying vec3 vsize[3];
  varying vec2 vtiles[3];

  varying float va;
  varying vec4 vColor;

  ${glx.box3}
  ${glx.rayPlaneInsec}
  ${glx.rotateY}

  float tessellate2( vec2 xz, vec2 c0, vec2 rectSize ) {
      vec2 d = xz - c0;
      vec2 modxz = mod ( d / rectSize, 2. );
      return modxz.x > 1. && modxz.y > 1. || modxz.x < 1. && modxz.y < 1. ?
             0.5 : 0.001;
  }

  // TODO use glx.box3Color
  float box3Color(vec3 e, vec3 i, vec3 c0, vec3 n0, float radi, vec3 size, vec2 tiles, float w) {
    n0 = rotateY(-radi, n0);
    vec4 p0 = rayPlaneInsec( e, i, c0, n0 );

    if (p0.w > 0.) {
      vec3 p_ = p0.xyz - c0;
      vec3 p_0 = rotateY(radi, p_);

      float box = box3( p_0, size * 0.5, 0.5 );
      box = 1.0/box * w * (1. - va);

      float tes = 0.02 * (1. - va) * ( 1. - abs( sin(now * 0.0005) ) );
      return max(box, 0.05) * tes + abs(box) * 0.004;
    }
    else return 0.;
  }

  vec4 mainImage( in vec2 fragCoord ) {
    float col = 0.;

    vec3 i = normalize(P - cameraPosition);
    vec3 e = cameraPosition;

    for (int cx = 0; cx < ${yztile.layrs}; cx++) {
      vec3 ci = vcent.xyz + rotateY(-vnorth, vec3(float(cx) * ${yztile.layerDist} + vxyzOffset.x, 0., 0.));
      col += box3Color(e, i, ci, vec3(1., 0., 0.), vnorth, vsize[0], vtiles[1], WEIGHT);
    }

    for (int cx = 0; cx < ${xztile.layrs}; cx++) {
      vec3 ci = vcent.xyz + rotateY(-vnorth, vec3(0., float(cx) * ${xztile.layerDist} + vxyzOffset.y, 0.));
      col += box3Color(e, i, ci, vec3(0., 1., 0.), vnorth, vsize[1], vtiles[2], WEIGHT);
    }

    for (int cx = 0; cx < ${xytile.layrs}; cx++) {
      vec3 ci = vcent.xyz + rotateY(-vnorth, vec3(0., 0., float(cx) * ${xytile.layerDist} + vxyzOffset.z));
      col += box3Color(e, i, ci, vec3(0., 0., 1.), vnorth, vsize[2], vtiles[0], WEIGHT);
    }

    return u_hue * col;
	// return vec4(0.2, 0.8, 0.2, 1.) * col;
  }

  void main() {
    gl_FragColor += mainImage(gl_FragCoord.xy);
    gl_FragColor = mix(gl_FragColor, vColor, va * 0.9 + 0.1);
  }`,

 // a_box - xz: floor size, y: height ( layer's offset )
 // TODO replace with fresnelAlpha
 vertexShader: `
  uniform float u_shininess;
  uniform vec4 u_color; // diffuse color
  uniform vec3 u_ambientColor;
  uniform vec3 u_specularColor;
  uniform vec3 u_lightColor;
  uniform vec3 u_lightPos;
  uniform float u_lightIntensity;
  ${glx.u_alpha}

  uniform vec3 wpos;
  uniform vec3 u_offsetxyz;
  uniform float u_north;


  attribute vec3 a_box;
  attribute vec2 a_tiles;
  attribute vec3 a_loc;
  attribute float a_north;

  varying vec3 P;
  varying vec4 vcent;
  varying vec3 vxyzOffset;
  varying float vnorth;
  varying float va;
  varying vec3 vsize[3];
  varying vec2 vtiles[3];
  varying vec4 vColor;

  ${glx.buildingAlpha}
  ${glx.phongLight}

  void main() {
    vec4 worldPosition = modelMatrix * vec4(position, 1.0);

    vsize[0] = a_box ${yztile.boxsize};
    vsize[1] = a_box ${xztile.boxsize};
    vsize[2] = a_box ${xytile.boxsize};

    vtiles[0] = a_tiles ${xytile.tiles};
    vtiles[1] = a_tiles ${yztile.tiles};
    vtiles[2] = a_tiles ${xztile.tiles};

    vnorth = a_north + u_north;
    vxyzOffset = u_offsetxyz;

    P = worldPosition.xyz; // /worldPosition.w; // v0.3.33
    va = buildingAlpha(cameraPosition, P, normal);
    va *= u_alpha;

    vcent = modelMatrix * vec4( a_loc + wpos, 1. );

    gl_Position = projectionMatrix * viewMatrix * worldPosition;

    vColor = phongLight(normal, u_lightPos, cameraPosition, worldPosition.xyz,
            u_ambientColor.xyz, u_color.xyz,
            u_specularColor.xyz,
            u_shininess)
            * u_lightIntensity;
  } `
 }
}

xyzLayer2.initUniform = initPhongUni;
xyzLayer2.updateUniform = updatePhongUni;

function para4Layers(vparas) {
    const w = vparas.edgeWeight;
    const edgeWeight = w !== undefined ?
                        `(float(${w}))` : '(3.4)';

    if (vparas.xytile && vparas.xytile.box.length === 2)
         vparas.xytile.box = [vparas.xytile.box[0], vparas.xytile.box[1], 0];

    if (vparas.yztile && vparas.yztile.box.length === 2)
         vparas.yztile.box = [0, vparas.yztile.box[0], vparas.yztile.box[1]];

    if (vparas.xztile && vparas.xztile.box.length === 2)
         vparas.xztile.box = [vparas.xztile.box[0], 0, vparas.xztile.box[1]];


    return { edgeWeight,
            xytile: tile(vparas.xytile),
            yztile: tile(vparas.yztile),
            xztile: tile(vparas.xztile) };

    function tile( ptile = {} ) {
        var layrs = ptile.layers || 0;
        var vtiles = ptile.change ? ' vtiles / float(cx + 1) ' : ' vtiles ';

        // boxsize can preventing vsize = vec2(0.) when using three.js geometry (no a_box)
        var boxsize = '';
        if (ptile.box) {
            boxsize = `+ vec3(float(${ptile.box[0]}), float(${ptile.box[1]}), float(${ptile.box[2]}))`;
        }

        // fragmentShader: vtiles[i] = a_tiles ${xztile.tiles};
        var tiles = '';
        if (ptile.tiles) {
            tiles = `+ vec2(float(${ptile.tiles[0]}), float(${ptile.tiles[1]}))`;
        }

        var layerDist = ptile.layerDist ?
                        `(float(${ptile.layerDist}))` : '(0.)';

        return {layrs, boxsize, tiles, layerDist};
    }
}