const FLUID_MAX_CAPS=16,FLUID_COMMON=`
precision highp float;
precision highp sampler3D;
precision highp int;
uniform sampler3D uDist;                 // Basis \u8DDD\u58C1\u573A\uFF08flowfield \u7EC6\u683C\uFF0CR8 \u7F16\u7801\uFF09
uniform vec3 uDistOrigin, uDistSize;
uniform vec3 uOrigin, uSize;             // \u6C42\u89E3\u5668\u683C\uFF08\u5F62\u53D8\u540E\u5C40\u90E8\u5750\u6807\uFF1B\u5FC3\u623F\u6BD4\u8DDD\u58C1\u573A\u683C\u56DB\u5468\u591A\u51FA margin\uFF09
uniform sampler3D uDispNow, uDispPrev;   // \u4F4D\u79FB\u573A\uFF08Basis \u7C97\u683C\uFF0Ccm\uFF09\uFF1A\u5355\u952E = \u952E\u4F4D\u79FB\u573A \xD7 \u6743\u91CD\uFF1B\u591A\u952E = DISP \u5408\u6210 \xD7 1
uniform float uDispScaleNow, uDispScalePrev;
uniform vec3 uDefOrigin, uDefSize;
uniform ivec3 uDims;                     // \u6C42\u89E3\u5668\u683C\u6570
uniform int uCols;                       // \u56FE\u96C6\u6BCF\u884C tile \u6570
uniform float uH, uDt;
uniform sampler2D tVel, tPhi, tDiv, tWall, tWallVel, tDep, tCapVel;
vec3 dAt(vec3 q){ return uDispScaleNow * texture(uDispNow, (q - uDefOrigin) / uDefSize).xyz; }
vec3 dPrevAt(vec3 q){ return uDispScalePrev * texture(uDispPrev, (q - uDefOrigin) / uDefSize).xyz; }
float distB(vec3 p){ return texture(uDist, (p - uDistOrigin) / uDistSize).r * 2.5 - 0.5; }   // \u6709\u7B26\u53F7\u8DDD\u58C1\uFF08cm\uFF09\uFF0C\u8154\u5916\u4E3A\u8D1F
bool cellOf(out ivec3 c){
  ivec2 fc = ivec2(gl_FragCoord.xy);
  int tx = fc.x / uDims.x, ty = fc.y / uDims.y;
  int k = tx + ty * uCols;
  c = ivec3(fc.x - tx * uDims.x, fc.y - ty * uDims.y, k);
  return k < uDims.z;
}
ivec2 texelOf(ivec3 c){ int ty = c.z / uCols; int tx = c.z - ty * uCols; return ivec2(tx * uDims.x + c.x, ty * uDims.y + c.y); }
bool inGrid(ivec3 c){ return all(greaterThanEqual(c, ivec3(0))) && all(lessThan(c, uDims)); }
ivec3 clampCell(ivec3 c){ return clamp(c, ivec3(0), uDims - 1); }
vec3 centerOf(ivec3 c){ return uOrigin + (vec3(c) + 0.5) * uH; }
// \u53CD\u67E5\uFF1Ax = p + d(p) \u7684 p\uFF08\u5B9A\u70B9\u8FED\u4EE3\u4E09\u6B21\uFF1B\u4F4D\u79FB\u573A\u5168\u683C\u6709\u503C\uFF09
vec3 basisOf(vec3 x){ vec3 q = x; for (int it = 0; it < 3; it++) q = x - dAt(q); return q; }
float typeAt(ivec3 c){ return inGrid(c) ? texelFetch(tWall, texelOf(c), 0).r : 1.0; }
bool isSolid(float t){ return t > 0.5 && t < 1.5; }
bool isOpen(float t){ return t > 1.5 && t < 2.5; }
bool isCap(float t){ return t > 3.5 && t < 19.5; }      // \u7AEF\u5E3D 4 + n\uFF08n < 16\uFF09
bool isCand(float t){ return t > 31.5; }                 // \u7AEF\u5E3D\u5019\u9009 32 + n\uFF08WALLFIX \u4E4B\u524D\uFF09
int capOf(float t){ return int(t - 4.0 + 0.5); }
vec4 capInfo(int n){ return texelFetch(tCapVel, ivec2(n, 0), 0); }      // xyz \u7ED9\u5B9A\u901F\u5EA6\uFF08cm/s\uFF09\uFF0Cw \u6D41\u91CF \u03A6\uFF08mL/s\uFF0C\u8FDB\u672C\u8154\u4E3A\u6B63\uFF09
vec3 fetchV(ivec3 c){ return texelFetch(tVel, texelOf(clampCell(c)), 0).xyz; }
vec3 fetchWV(ivec3 c){ return texelFetch(tWallVel, texelOf(clampCell(c)), 0).xyz; }
float fetchPhi(ivec3 c){ return inGrid(c) ? texelFetch(tPhi, texelOf(c), 0).r : 0.0; }
float comp3(vec3 v, int k){ return k == 0 ? v.x : (k == 1 ? v.y : v.z); }
ivec3 axis3(int k){ return ivec3(k == 0 ? 1 : 0, k == 1 ? 1 : 0, k == 2 ? 1 : 0); }
ivec3 dir6(int k){ int a = k - 3 * (k / 3); return axis3(a) * (k < 3 ? 1 : -1); }
float fracAt(ivec3 c){ return inGrid(c) ? texelFetch(tWall, texelOf(c), 0).b : 0.0; }
// \u7AEF\u5E3D\u4E0E\u6D41\u4F53\u683C\u4E4B\u95F4\u7684\u9762\uFF1A\u6D41\u4F53\u683C\u5927\u534A\u5728\u58C1\u91CC\uFF08\u4F53\u79EF\u5206\u6570 < 0.5\uFF09\u65F6\u8FD9\u9762\u5F53\u58C1\u3002\u7AEF\u5E3D\u76D8\u6BD4\u7BA1\u8154\u5927\u3001\u6216\u7AEF\u5E3D\u5C42\u8D34\u7740\u58C1\u65F6\uFF0C\u8FB9\u4E0A\u4E00\u5708\u788E\u683C\u88AB\u7AEF\u5E3D
// \u786C\u704C / \u786C\u62BD\uFF0C\u8584\u5C42\u91CC\u538B\u51FA 1000 cm/s\uFF0810-05 \u80BA\u52A8\u8109\u8FDC\u7AEF\u65AD\u53E3\u3001\u4E3B\u52A8\u8109\u4E00\u4FA7\u7684\u74E3\u53E3\uFF09
bool capFaceOpen(ivec3 fluidCell){ return fracAt(fluidCell) >= 0.5; }
// \u7AEF\u5E3D\u683C\u81EA\u5DF1\u7684\u4E09\u4E2A\u4E0B\u4FA7\u9762\uFF1A\u4E0B\u90BB\u662F\u788E\u6D41\u4F53\u683C\u7684\u90A3\u9762\u7ED9\u58C1\u901F\uFF0C\u5176\u4F59\u7ED9\u7AEF\u5E3D\u901F\u5EA6
vec3 capCellVel(ivec3 c, float tc){
  vec3 cv = capInfo(capOf(tc)).xyz, wv = fetchWV(c), o = vec3(0.0);
  for (int k = 0; k < 3; k++){ ivec3 e = axis3(k); ivec3 n = c - e; bool wall = typeAt(n) < 0.5 && !capFaceOpen(n); o += vec3(e) * (wall ? comp3(wv, k) : comp3(cv, k)); }
  return o;
}
// \u6D41\u4F53\u683C c \u7684\u4E0B\u4FA7\u9762\u6328\u7740\u7AEF\u5E3D\uFF08c \u2212 e\uFF0C\u7C7B\u578B tn\uFF09\u65F6\u8FD9\u4E00\u9762\u7684\u901F\u5EA6\u5206\u91CF
float capFaceVel(ivec3 c, ivec3 e, float tn, int k){ return capFaceOpen(c) ? comp3(capInfo(capOf(tn)).xyz, k) : comp3(fetchWV(c - e), k); }
float sampleComp(int k, vec3 L){
  vec3 Lc = L - 0.5 + 0.5 * vec3(axis3(k));
  vec3 b = floor(Lc);
  vec3 f = Lc - b;
  ivec3 i0 = ivec3(b);
  float s = 0.0;
  for (int dz = 0; dz < 2; dz++) for (int dy = 0; dy < 2; dy++) for (int dx = 0; dx < 2; dx++){
    float w = (dx == 1 ? f.x : 1.0 - f.x) * (dy == 1 ? f.y : 1.0 - f.y) * (dz == 1 ? f.z : 1.0 - f.z);
    s += w * comp3(fetchV(i0 + ivec3(dx, dy, dz)), k);
  }
  return s;
}
vec3 sampleVel(vec3 x){ vec3 L = (x - uOrigin) / uH; return vec3(sampleComp(0, L), sampleComp(1, L), sampleComp(2, L)); }
vec3 centerVel(ivec3 c){ vec3 a = fetchV(c); return 0.5 * (a + vec3(fetchV(c + ivec3(1, 0, 0)).x, fetchV(c + ivec3(0, 1, 0)).y, fetchV(c + ivec3(0, 0, 1)).z)); }
// \u4ECE x \u5F80\u56DE\u8FFD dt \u7684\u8F68\u8FF9\u3002\u5FEB\u8DEF\uFF1ARK2 \u4E00\u6B65\uFF0C\u4E2D\u70B9\u548C\u7EC8\u70B9\u90FD\u5728\u6D41\u4F53\u91CC\u5C31\u6536\u3002\u5426\u5219\u6539\u6210\u6BCF\u6B65\u4E0D\u8D85\u8FC7\u4E00\u683C\u5730\u8D70\uFF1A\u78B0\u5230\u7AEF\u5E3D \u2192 hit = \u7AEF\u5E3D\u53F7
// \uFF08\u53D6\u7AEF\u5E3D\u7684\u503C\uFF1A\u5165\u6D41\u7684\u67D3\u6599 1\u3001\u7ED9\u5B9A\u901F\u5EA6\uFF09\uFF0C\u78B0\u5230\u5B9E\u4F53 \u2192 \u505C\u5728\u6700\u540E\u4E00\u4E2A\u6D41\u4F53\u70B9\u3002\u7AEF\u5E3D\u53EA\u6709\u4E00\u683C\u539A\uFF1A\u4E00\u5E27\u56DE\u6EAF 1\u20132 cm \u4F1A\u8DF3\u8FC7\u5B83\uFF0C
// RK2 \u7684\u4E2D\u70B9\u843D\u5230\u74E3\u73AF\u5916\u7684\u5B9E\u4F53\u91CC\u8BFB\u5230\u58C1\u901F\uFF08\u22480\uFF09\uFF0C\u7EC8\u70B9\u53C8\u56DE\u5230\u6D41\u4F53 \u2014\u2014 10-04 \u5DE6\u5BA4 E \u5CF0\u5C04\u6D41\u8F74\u4E0A\u65B0\u8840 / \u8001\u8840\u9010\u683C\u4EA4\u66FF
ivec3 cellAt(vec3 x){ return ivec3(floor((x - uOrigin) / uH)); }
vec3 traceBack(vec3 x, float dt, out int hit){
  hit = -1;
  vec3 v1 = sampleVel(x);
  vec3 xm = x - 0.5 * dt * v1;
  if (typeAt(cellAt(xm)) < 0.5){
    vec3 xb = x - dt * sampleVel(xm);
    if (typeAt(cellAt(xb)) < 0.5) return clamp(xb, uOrigin + 0.5 * uH, uOrigin + uSize - 0.5 * uH);
  }
  int n = int(clamp(ceil(abs(dt) * length(v1) / uH), 1.0, 64.0));   // \u6BCF\u6B65\u4E0D\u8D85\u8FC7\u4E00\u683C\uFF1A\u72ED\u7A84\u74E3\u53E3\u7684\u5C04\u6D41 340 cm/s \xD7 \u4E00\u5E27 0.022 s = 50 \u683C
  float sdt = dt / float(n);
  vec3 p = x;
  for (int i = 0; i < 64; i++){
    if (i >= n) break;
    vec3 q = p - sdt * sampleVel(p);
    float tq = typeAt(cellAt(q));
    if (isCap(tq)){ hit = capOf(tq); return p; }
    if (tq > 0.5) return p;
    p = q;
  }
  return p;
}
`,FLUID_DISP_GLSL=`
precision highp float;
precision highp sampler3D;
precision highp int;
uniform sampler3D uDef0, uDef1, uDef2, uDef3, uDef4, uDef5, uDef6;
uniform float uW[7];
uniform int uNDef, uLayer;
void main(){
  ivec3 c = ivec3(int(gl_FragCoord.x), int(gl_FragCoord.y), uLayer);
  vec3 d = vec3(0.0);
  if (uNDef > 0) d += uW[0] * texelFetch(uDef0, c, 0).xyz;
  if (uNDef > 1) d += uW[1] * texelFetch(uDef1, c, 0).xyz;
  if (uNDef > 2) d += uW[2] * texelFetch(uDef2, c, 0).xyz;
  if (uNDef > 3) d += uW[3] * texelFetch(uDef3, c, 0).xyz;
  if (uNDef > 4) d += uW[4] * texelFetch(uDef4, c, 0).xyz;
  if (uNDef > 5) d += uW[5] * texelFetch(uDef5, c, 0).xyz;
  if (uNDef > 6) d += uW[6] * texelFetch(uDef6, c, 0).xyz;
  gl_FragColor = vec4(d, 0.0);
}`,FLUID_WALL_GLSL=FLUID_COMMON+`
uniform vec4 uDisc[16];        // \u4E2D\u5FC3 xyz\uFF08Basis\uFF09\u3001\u534A\u5F84\uFF08cm\uFF09
uniform vec4 uDiscAxis[16];    // \u8F74 xyz\u3001\u5F00\u653E\u5EA6\uFF080 = \u5173\uFF09
uniform vec4 uDiscEnd[16];     // out\uFF08\u79BB\u5F00\u672C\u8154\uFF09\u65B9\u5411 xyz\u3001\u8840\u6C60\u5C3D\u5934\u79BB\u76D8\u4E2D\u5FC3\u7684\u8DDD\u79BB\uFF08cm\uFF09
uniform int uNDisc, uFirst;
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(1.0, -1.0, 0.0, 0.0); return; }
  vec3 x = centerOf(c);
  vec3 q = basisOf(x);
  float sd = distB(q);
  float frac = clamp(0.5 + sd / uH, 0.0, 1.0);
  float prev = uFirst > 0 ? frac : texelFetch(tWall, texelOf(c), 0).b;
  float type = frac > 0.0 ? 0.0 : 1.0;
  for (int n = 0; n < 16; n++){
    if (n >= uNDisc) break;
    vec4 dc = uDisc[n], ax = uDiscAxis[n];
    if (ax.w <= 0.0) continue;
    float ro = dc.w * sqrt(ax.w);
    if (ro < 1.5 * uH) continue;                                   // \u53E3\u5F84\u4E0D\u5230\u4E00\u683C\u534A\u5F53\u5173\uFF08\u521A\u5F00\u7684\u74E3\u4E00\u4E24\u4E2A\u683C\u627F\u62C5\u6574\u8154\u63A8\u91CF\u4F1A\u51FA\u5C16\u523A\uFF09
    // \u4EE5\u8840\u6C60\u5C3D\u5934\uFF08Basis \u91CC\u4ECE\u76D8\u5FC3\u6CBF out \u8D70 uEnd\uFF09\u8FD9\u4E00\u5E27\u7684\u4F4D\u7F6E\u4E3A\u51C6\uFF0C\u4E0D\u4EE5\u76D8\u5FC3\uFF1A\u74E3\u73AF\u4E00\u5E26\u88AB\u5FC3\u5BA4\u62FD\u7740\u8D70\u5F97\u6BD4\u76D8\u5FC3\u591A\uFF0C
    // \u4E3B\u52A8\u8109\u4E00\u4FA7\u6536\u7F29\u671F\u5DEE 0.7 cm\uFF0C\u7AEF\u5E3D\u5019\u9009\u5C42\u591F\u4E0D\u5230\u771F\u6B63\u7684\u6C60\u5E95\uFF0C\u5F00\u74E3\u65F6\u53EA\u5269 18 \u683C\uFF0810-05\uFF09
    vec3 pe = dc.xyz + uDiscEnd[n].xyz * uDiscEnd[n].w;
    vec3 rel = x - (pe + dAt(pe));
    float al = dot(rel, ax.xyz);
    float rad = length(rel - al * ax.xyz);
    float a2 = dot(rel, uDiscEnd[n].xyz);
    if (a2 >= -1.2 && a2 <= 0.6 && rad <= ro && sd > -uH) type = 32.0 + float(n);
  }
  gl_FragColor = vec4(type, sd, frac, prev);
}`,FLUID_WALLFIX_GLSL=FLUID_COMMON+`
uniform vec4 uDiscEnd[16];     // out\uFF08\u79BB\u5F00\u672C\u533A\uFF09\u65B9\u5411 xyz
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(1.0, -1.0, 0.0, 0.0); return; }
  vec4 w = texelFetch(tWall, texelOf(c), 0);
  if (isCand(w.r)){
    int n = int(w.r - 32.0 + 0.5);
    vec3 o = uDiscEnd[n].xyz;
    float tu = typeAt(cellAt(centerOf(c) + o * (uH * (abs(o.x) + abs(o.y) + abs(o.z)))));
    w.r = isSolid(tu) ? 4.0 + float(n) : 0.0;
  }
  if (w.r < 0.5){
    bool any = false;
    for (int k = 0; k < 6; k++){ float tn = typeAt(c + dir6(k)); if (tn < 0.5 || isOpen(tn) || isCand(tn)) any = true; }
    if (!any) w.r = 1.0;
  }
  gl_FragColor = w;
}`,FLUID_WALLVEL_GLSL=FLUID_COMMON+`
void main(){
  ivec3 c;
  if (!cellOf(c) || uDt <= 0.0){ gl_FragColor = vec4(0.0); return; }
  vec3 q = basisOf(centerOf(c));
  gl_FragColor = vec4((dAt(q) - dPrevAt(q)) / uDt, 0.0);
}`,FLUID_DEPOSIT_GLSL=FLUID_COMMON+`
uniform int uHop;
float sdAt(ivec3 c){ return inGrid(c) ? texelFetch(tWall, texelOf(c), 0).g : -9.0; }
int bestOf(ivec3 n){ int best = -1; float sb = -8.0; for (int k = 0; k < 6; k++){ float s = sdAt(n + dir6(k)); if (s > sb){ sb = s; best = k; } } return best; }
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(0.0); return; }
  vec4 wc = texelFetch(tWall, texelOf(c), 0);
  float held = uHop == 0 ? 0.0 : texelFetch(tDep, texelOf(c), 0).r;
  float acc = 0.0;
  for (int k = 0; k < 6; k++){
    ivec3 n = c + dir6(k);
    if (!inGrid(n)) continue;
    vec4 wn = texelFetch(tWall, texelOf(n), 0);
    if (!isSolid(wn.r)) continue;
    float amt = uHop == 0 ? max(wn.a - wn.b, 0.0) : texelFetch(tDep, texelOf(n), 0).r;
    if (amt <= 0.0) continue;
    if (all(equal(n + dir6(bestOf(n)), c))) acc += amt;
  }
  gl_FragColor = vec4(isSolid(wc.r) ? acc : held + acc, 0.0, 0.0, 0.0);
}`,FLUID_CAPVEL_GLSL=`
precision highp float;
precision highp int;
uniform sampler2D tStatFrame, tStatCaps0, tStatCaps1, tStatCaps2, tStatCaps3, tStatLink, tLinkCap;
uniform float uAlpha[16], uBeta[16], uGamma[16], uLinkIdx[16], uCapVmax[16], uGroup[16], uLocalLink[16];
uniform vec4 uDiscEnd[16];
uniform int uNDisc;
uniform float uH, uDtFrame;
float capFe(int n){
  int g = n / 4, m = n - 4 * g;
  vec4 fg = g == 0 ? texelFetch(tStatCaps0, ivec2(0), 0) : (g == 1 ? texelFetch(tStatCaps1, ivec2(0), 0) :
            (g == 2 ? texelFetch(tStatCaps2, ivec2(0), 0) : texelFetch(tStatCaps3, ivec2(0), 0)));
  return fg[m];
}
// \u672C\u533A\u63A5\u5FC3\u8154\u90A3\u4E2A\u53E3\uFF08\u03B3 = \u22121\u3001\u03B1 = \u03B2 = 0\uFF09\u8FD9\u4E00\u5E27\u5B9E\u9645\u9001\u5F97\u8FDB\u6765\u7684\u6D41\u91CF\uFF1A\u53E3\u5FEB\u5173\u65F6\u8FD9\u4E00\u4FA7\u7684\u7AEF\u5E3D\u53EF\u80FD\u53EA\u5269\u4E00\u4E24\u683C\uFF08\u4E3B\u52A8\u8109\u4E00\u4FA7\u6BD4\u5DE6\u5BA4\u4E00\u4FA7\u5148\u6CA1\uFF09\uFF0C
// \u7ED9\u5B9A\u6D41\u91CF\u9001\u4E0D\u5B8C \u2014\u2014 \u8FDC\u7AEF\u65AD\u53E3\u6309"\u5B9E\u9645\u9001\u8FDB\u6765\u7684"\u8865\u9F50\uFF0C\u672C\u533A\u81EA\u5DF1\u5B88\u6052\uFF0810-05 \u4E3B\u52A8\u8109\u74E3\u5FEB\u5173\u65F6\u5DEE 18 mL/s \u4EA4\u7ED9 D8 \u644A\uFF09
float delivered(int L){
  float pl = uGamma[L] * texelFetch(tLinkCap, ivec2(int(uLinkIdx[L] + 0.5), 0), 0).w;
  float fl = capFe(L);
  return fl > 0.5 ? clamp(pl / (fl * uH * uH), -uCapVmax[L], uCapVmax[L]) * fl * uH * uH : 0.0;
}
void main(){
  int n = int(gl_FragCoord.x);
  if (n >= uNDisc || uDtFrame <= 0.0){ gl_FragColor = vec4(0.0); return; }
  float h3 = uH * uH * uH;
  float qs = texelFetch(tStatFrame, ivec2(0), 0).x * h3 / uDtFrame;
  float ql = texelFetch(tStatLink, ivec2(0), 0).x * h3 / uDtFrame;
  float phi = uAlpha[n] * qs + uBeta[n] * ql;
  if (uGamma[n] != 0.0) phi += uGamma[n] * texelFetch(tLinkCap, ivec2(int(uLinkIdx[n] + 0.5), 0), 0).w;
  if (uLocalLink[n] >= 0.0) phi -= delivered(int(uLocalLink[n] + 0.5));
  float fe = capFe(n);
  // \u540C\u4E00\u7EC4\uFF08\u540C\u4E00\u9759\u8109\u7684\u51E0\u4E2A\u53E3 / \u5927\u8840\u7BA1\u540C\u4E00\u6839\u7BA1\u7684\u51E0\u4E2A\u8FDC\u7AEF\u65AD\u53E3\uFF09\u6309\u6709\u6548\u9762\u79EF\u5206\u7EC4\u91CC\u7684\u603B\u6D41\u91CF\uFF1A\u53E3\u901F\u4E00\u6837\uFF0C\u7EC6\u53E3\u4E0D\u88AB\u9876\u5230\u5C01\u9876\u901F\u5EA6
  //\uFF0810-05 \u5DE6\u623F\u56DB\u4E2A\u80BA\u9759\u8109\u53E3\u6309\u53E3\u73AF\u534A\u5F84\u5747\u5206\u65F6\uFF0C\u6700\u7A84\u90A3\u4E2A\u6709\u6548\u9762\u53EA\u6709\u6700\u5BBD\u7684 1/7\uFF0CE \u5CF0\u53E3\u901F\u9876\u5728 150\u3001\u5DE6\u623F\u9010\u5E27\u901A\u91CF\u5DEE 3.3%\uFF09
  if (uGroup[n] >= 0.0){
    float fs = 0.0;
    for (int m = 0; m < 16; m++){ if (m >= uNDisc) break; if (abs(uGroup[m] - uGroup[n]) < 0.5) fs += max(capFe(m), 0.0); }
    phi *= fs > 0.5 ? max(fe, 0.0) / fs : 0.0;
  }
  float v = fe > 0.5 ? clamp(phi / (fe * uH * uH), -uCapVmax[n], uCapVmax[n]) : 0.0;   // \u5C01\u9876\uFF1A\u7AEF\u5E3D\u683C\u592A\u5C11\u65F6\u9001\u4E0D\u5B8C\u7684\u6D41\u91CF\u4EA4\u7ED9 D8\uFF0C\u4E0D\u628A\u52A8\u91CF\u5C16\u523A\u704C\u8FDB\u8154\u91CC
  gl_FragColor = vec4(-uDiscEnd[n].xyz * v, phi);
}`,FLUID_ADVECT_GLSL=FLUID_COMMON+`
uniform sampler2D tAdvSrc;
uniform float uAdvDt, uVClamp;
float srcComp(int k, vec3 L){
  vec3 Lc = L - 0.5 + 0.5 * vec3(axis3(k));
  vec3 b = floor(Lc); vec3 f = Lc - b; ivec3 i0 = ivec3(b);
  float s = 0.0;
  for (int dz = 0; dz < 2; dz++) for (int dy = 0; dy < 2; dy++) for (int dx = 0; dx < 2; dx++){
    float w = (dx == 1 ? f.x : 1.0 - f.x) * (dy == 1 ? f.y : 1.0 - f.y) * (dz == 1 ? f.z : 1.0 - f.z);
    s += w * comp3(texelFetch(tAdvSrc, texelOf(clampCell(i0 + ivec3(dx, dy, dz))), 0).xyz, k);
  }
  return s;
}
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(0.0); return; }
  float tc = typeAt(c);
  if (isSolid(tc)){ gl_FragColor = vec4(fetchWV(c), 0.0); return; }
  if (isCap(tc)){ gl_FragColor = vec4(capCellVel(c, tc), 0.0); return; }
  vec3 x = centerOf(c);
  vec3 outv = vec3(0.0);
  for (int k = 0; k < 3; k++){
    ivec3 e = axis3(k);
    float val, tn = typeAt(c - e);
    if (isSolid(tn)) val = comp3(fetchWV(c - e), k);
    else if (isCap(tn)) val = capFaceVel(c, e, tn, k);
    else {
      int hit;
      vec3 xb = traceBack(x - 0.5 * uH * vec3(e), uAdvDt, hit);
      val = hit >= 0 ? comp3(capInfo(hit).xyz, k) : clamp(srcComp(k, (xb - uOrigin) / uH), -uVClamp, uVClamp);
    }
    outv += vec3(e) * val;
  }
  gl_FragColor = vec4(outv, 0.0);
}`,FLUID_MACCORMACK_GLSL=FLUID_COMMON+`
uniform sampler2D tAdvSrc, tMcBack;
uniform float uAdvDt, uVClamp;
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(0.0); return; }
  float tc = typeAt(c);
  if (isSolid(tc)){ gl_FragColor = vec4(fetchWV(c), 0.0); return; }
  if (isCap(tc)){ gl_FragColor = vec4(capCellVel(c, tc), 0.0); return; }
  vec3 x = centerOf(c);
  vec3 hat = texelFetch(tAdvSrc, texelOf(c), 0).xyz, back = texelFetch(tMcBack, texelOf(c), 0).xyz, un = fetchV(c);
  vec3 outv = vec3(0.0);
  for (int k = 0; k < 3; k++){
    ivec3 e = axis3(k);
    float val, tn = typeAt(c - e);
    if (isSolid(tn)) val = comp3(fetchWV(c - e), k);
    else if (isCap(tn)) val = capFaceVel(c, e, tn, k);
    else {
      int hit;
      vec3 xb = traceBack(x - 0.5 * uH * vec3(e), uAdvDt, hit);
      vec3 L = (xb - uOrigin) / uH - 0.5 + 0.5 * vec3(e);
      ivec3 i0 = ivec3(floor(L));
      float lo = 1e9, hi = -1e9;
      for (int dz = 0; dz < 2; dz++) for (int dy = 0; dy < 2; dy++) for (int dx = 0; dx < 2; dx++){
        float s = comp3(fetchV(i0 + ivec3(dx, dy, dz)), k); lo = min(lo, s); hi = max(hi, s);
      }
      val = hit >= 0 ? comp3(capInfo(hit).xyz, k) : clamp(clamp(comp3(hat, k) + 0.5 * (comp3(un, k) - comp3(back, k)), lo, hi), -uVClamp, uVClamp);
    }
    outv += vec3(e) * val;
  }
  gl_FragColor = vec4(outv, 0.0);
}`,FLUID_VORT_GLSL=FLUID_COMMON+`
void main(){
  ivec3 c;
  if (!cellOf(c) || typeAt(c) > 0.5){ gl_FragColor = vec4(0.0); return; }
  for (int k = 0; k < 6; k++){ if (typeAt(c + dir6(k)) > 0.5){ gl_FragColor = vec4(0.0); return; } }
  vec3 vxp = centerVel(c + ivec3(1, 0, 0)), vxm = centerVel(c - ivec3(1, 0, 0));
  vec3 vyp = centerVel(c + ivec3(0, 1, 0)), vym = centerVel(c - ivec3(0, 1, 0));
  vec3 vzp = centerVel(c + ivec3(0, 0, 1)), vzm = centerVel(c - ivec3(0, 0, 1));
  vec3 w = vec3((vyp.z - vym.z) - (vzp.y - vzm.y), (vzp.x - vzm.x) - (vxp.z - vxm.z), (vxp.y - vxm.y) - (vyp.x - vym.x)) * (0.5 / uH);
  gl_FragColor = vec4(w, length(w));
}`,FLUID_CONFINE_GLSL=FLUID_COMMON+`
uniform sampler2D tVort;
uniform float uVortEps;
vec4 wAt(ivec3 q){ return inGrid(q) ? texelFetch(tVort, texelOf(q), 0) : vec4(0.0); }
vec3 forceAt(ivec3 q){
  vec4 w = wAt(q);
  if (w.w <= 1e-6) return vec3(0.0);
  vec3 eta = vec3(wAt(q + ivec3(1, 0, 0)).w - wAt(q - ivec3(1, 0, 0)).w, wAt(q + ivec3(0, 1, 0)).w - wAt(q - ivec3(0, 1, 0)).w, wAt(q + ivec3(0, 0, 1)).w - wAt(q - ivec3(0, 0, 1)).w);
  float el = length(eta);
  if (el < 1e-6) return vec3(0.0);
  return uVortEps * uH * cross(eta / el, w.xyz);
}
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(0.0); return; }
  vec3 u = fetchV(c);
  if (typeAt(c) > 0.5){ gl_FragColor = vec4(u, 0.0); return; }
  vec3 fc = forceAt(c), outv = vec3(0.0);
  for (int k = 0; k < 3; k++){
    ivec3 e = axis3(k);
    float val = comp3(u, k);
    if (typeAt(c - e) < 0.5) val += uDt * 0.5 * (comp3(fc, k) + comp3(forceAt(c - e), k));
    outv += vec3(e) * val;
  }
  gl_FragColor = vec4(outv, 0.0);
}`,FLUID_DIV_GLSL=FLUID_COMMON+`
uniform float uDtFrame;
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(0.0); return; }
  vec4 w = texelFetch(tWall, texelOf(c), 0);
  if (w.r > 0.5){ gl_FragColor = vec4(0.0); return; }
  vec3 u0 = fetchV(c);
  float d = 0.0;
  bool capShut = !capFaceOpen(c);           // \u672C\u683C\u5927\u534A\u5728\u58C1\u91CC\uFF1A\u6328\u7740\u7AEF\u5E3D\u7684\u9762\u5F53\u58C1\uFF08capFaceVel \u7ED9\u7684\u662F\u58C1\u901F\uFF09\uFF0C\u548C\u5B9E\u4F53\u9762\u4E00\u6837\u4E0D\u8BB0\u901A\u91CF \u2014\u2014 \u58C1\u7684\u79FB\u52A8\u5DF2\u7ECF\u5728\u4F53\u79EF\u5206\u6570\u53D8\u5316\u91CC
  for (int k = 0; k < 3; k++){
    ivec3 e = axis3(k);
    float tp = typeAt(c + e), tm = typeAt(c - e);
    if (!isSolid(tp) && !(capShut && isCap(tp))) d += comp3(fetchV(c + e), k);
    if (!isSolid(tm) && !(capShut && isCap(tm))) d -= comp3(u0, k);
  }
  d /= uH;
  if (uDtFrame > 0.0) d += (w.b - w.a - texelFetch(tDep, texelOf(c), 0).r) / uDtFrame;
  gl_FragColor = vec4(d, 0.0, 0.0, 0.0);
}`,FLUID_PROJECT_GLSL=FLUID_COMMON+`
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(0.0); return; }
  float tc = typeAt(c);
  if (isSolid(tc)){ gl_FragColor = vec4(fetchWV(c), 0.0); return; }
  if (isCap(tc)){ gl_FragColor = vec4(capCellVel(c, tc), 0.0); return; }
  vec3 u = fetchV(c);
  float phic = fetchPhi(c);
  vec3 outv = vec3(0.0);
  for (int k = 0; k < 3; k++){
    ivec3 e = axis3(k);
    float tn = typeAt(c - e);
    float val = isSolid(tn) ? comp3(fetchWV(c - e), k) : (isCap(tn) ? capFaceVel(c, e, tn, k) : comp3(u, k) - (phic - fetchPhi(c - e)) / uH);
    outv += vec3(e) * val;
  }
  gl_FragColor = vec4(outv, 0.0);
}`,FLUID_RFRAME_GLSL=FLUID_COMMON+`
void main(){
  ivec2 o = ivec2(gl_FragCoord.xy) * 8;
  ivec2 sz = textureSize(tWall, 0);
  float s = 0.0, fr = 0.0, nf = 0.0;
  for (int y = 0; y < 8; y++) for (int x = 0; x < 8; x++){
    ivec2 t = o + ivec2(x, y);
    if (t.x >= sz.x || t.y >= sz.y) continue;
    vec4 w = texelFetch(tWall, t, 0);
    if (w.r < 0.5){ s += w.b - w.a - texelFetch(tDep, t, 0).r; fr += w.b; nf += 1.0; }
    else if (isCap(w.r)) fr += w.b;
  }
  gl_FragColor = vec4(s, fr, nf, 0.0);     // \u03A3 \u6E90\u9879\uFF08\u683C\u5206\u6570\uFF09\u3001\u03A3 \u6D41\u4F53 + \u7AEF\u5E3D\u5206\u6570\uFF08\xD7 h\xB3 = \u8154\u5BB9\u79EF\uFF09\u3001\u6D41\u4F53\u683C\u6570
}`,FLUID_RCAPS_GLSL=FLUID_COMMON+`
uniform int uCapBase;
uniform vec4 uDiscEnd[16];
void main(){
  ivec2 o = ivec2(gl_FragCoord.xy) * 8;
  ivec2 sz = textureSize(tWall, 0);
  vec4 fe = vec4(0.0);
  for (int y = 0; y < 8; y++) for (int x = 0; x < 8; x++){
    ivec2 t = o + ivec2(x, y);
    if (t.x >= sz.x || t.y >= sz.y) continue;
    float ty = texelFetch(tWall, t, 0).r;
    if (!isCap(ty)) continue;
    int n = capOf(ty) - uCapBase;
    if (n < 0 || n > 3) continue;
    int tx = t.x / uDims.x, tyy = t.y / uDims.y;
    ivec3 c = ivec3(t.x - tx * uDims.x, t.y - tyy * uDims.y, tx + tyy * uCols);
    vec3 din = -uDiscEnd[n + uCapBase].xyz;
    float f = 0.0;
    for (int k = 0; k < 6; k++){ ivec3 e = dir6(k); if (typeAt(c + e) < 0.5 && capFaceOpen(c + e)) f += dot(din, vec3(e)); }
    fe[n] += f;
  }
  gl_FragColor = fe;                       // \u6BCF\u4E2A\u7AEF\u5E3D\uFF1A\u8D34\u6D41\u4F53\u7684\u9762\u6309\u8FDB\u8154\u65B9\u5411\u5206\u91CF\u52A0\u6743\u7684\u9762\u6570\uFF08\xD7 h\xB2 \xD7 v = \u9001\u8FDB\u8154\u7684\u6D41\u91CF\uFF09
}`,FLUID_RDIV_GLSL=FLUID_COMMON+`
void main(){
  ivec2 o = ivec2(gl_FragCoord.xy) * 8;
  ivec2 sz = textureSize(tWall, 0);
  float s = 0.0, nf = 0.0;
  for (int y = 0; y < 8; y++) for (int x = 0; x < 8; x++){
    ivec2 t = o + ivec2(x, y);
    if (t.x >= sz.x || t.y >= sz.y) continue;
    if (texelFetch(tWall, t, 0).r < 0.5){ s += texelFetch(tDiv, t, 0).r; nf += 1.0; }
  }
  gl_FragColor = vec4(s, nf, 0.0, 0.0);
}`,FLUID_SUM8_GLSL=`
precision highp float;
precision highp int;
uniform sampler2D tBlock;
void main(){
  ivec2 o = ivec2(gl_FragCoord.xy) * 8, sz = textureSize(tBlock, 0);
  vec4 s = vec4(0.0);
  for (int y = 0; y < 8; y++) for (int x = 0; x < 8; x++){
    ivec2 t = o + ivec2(x, y);
    if (t.x < sz.x && t.y < sz.y) s += texelFetch(tBlock, t, 0);
  }
  gl_FragColor = s;
}`,FLUID_FINAL_GLSL=`
precision highp float;
precision highp int;
uniform sampler2D tBlock;
uniform ivec2 uBlockSize;
void main(){
  vec4 s = vec4(0.0);
  for (int y = 0; y < 4096; y++){
    if (y >= uBlockSize.y) break;
    for (int x = 0; x < 4096; x++){
      if (x >= uBlockSize.x) break;
      s += texelFetch(tBlock, ivec2(x, y), 0);
    }
  }
  gl_FragColor = s;
}`,FLUID_DYE_ADV_GLSL=FLUID_COMMON+`
uniform sampler2D tDyeSrc;
uniform float uAdvDt;
float dyeAt(vec3 x){
  vec3 L = (x - uOrigin) / uH - 0.5;
  vec3 b = floor(L); vec3 f = L - b; ivec3 i0 = ivec3(b);
  float s = 0.0;
  for (int dz = 0; dz < 2; dz++) for (int dy = 0; dy < 2; dy++) for (int dx = 0; dx < 2; dx++){
    float w = (dx == 1 ? f.x : 1.0 - f.x) * (dy == 1 ? f.y : 1.0 - f.y) * (dz == 1 ? f.z : 1.0 - f.z);
    s += w * texelFetch(tDyeSrc, texelOf(clampCell(i0 + ivec3(dx, dy, dz))), 0).r;
  }
  return s;
}
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(0.0); return; }
  float a0 = texelFetch(tDyeSrc, texelOf(c), 0).r;
  if (typeAt(c) > 0.5){ gl_FragColor = vec4(a0, 0.0, 0.0, 0.0); return; }
  int hit;
  vec3 xb = traceBack(centerOf(c), uAdvDt, hit);
  gl_FragColor = vec4(hit >= 0 && capInfo(hit).w > 0.0 ? 1.0 : dyeAt(xb), 0.0, 0.0, 0.0);   // \u4ECE\u5165\u6D41\u7AEF\u5E3D\u8FDB\u6765\u7684 = \u65B0\u8840
}`,FLUID_DYE_FIX_GLSL=FLUID_COMMON+`
uniform sampler2D tDyeN, tDyeHat, tDyeBack;
uniform float uAdvDt, uDecay;
uniform int uMac;
float avgFluidNbr(sampler2D tex, ivec3 c, float fallback){
  float s = 0.0, n = 0.0;
  for (int k = 0; k < 6; k++){ ivec3 q = c + dir6(k); if (inGrid(q) && texelFetch(tWall, texelOf(q), 0).r < 0.5){ s += texelFetch(tex, texelOf(q), 0).r; n += 1.0; } }
  return n > 0.0 ? s / n : fallback;
}
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(0.0); return; }
  float tc = typeAt(c);
  float hat = texelFetch(tDyeHat, texelOf(c), 0).r;
  if (isCap(tc)){
    float phi = capInfo(capOf(tc)).w;
    gl_FragColor = vec4(phi > 0.0 ? 1.0 : avgFluidNbr(tDyeHat, c, hat), 0.0, 0.0, 0.0);
    return;
  }
  if (tc > 0.5){ gl_FragColor = vec4(avgFluidNbr(tDyeHat, c, hat), 0.0, 0.0, 0.0); return; }
  float a = hat;
  int hit;
  vec3 xb = traceBack(centerOf(c), uAdvDt, hit);
  if (uMac > 0 && hit < 0){
    vec3 L = (xb - uOrigin) / uH - 0.5;
    ivec3 i0 = ivec3(floor(L));
    float lo = 1e9, hi = -1e9;
    for (int dz = 0; dz < 2; dz++) for (int dy = 0; dy < 2; dy++) for (int dx = 0; dx < 2; dx++){
      float s = texelFetch(tDyeN, texelOf(clampCell(i0 + ivec3(dx, dy, dz))), 0).r; lo = min(lo, s); hi = max(hi, s);
    }
    float back = texelFetch(tDyeBack, texelOf(c), 0).r, an = texelFetch(tDyeN, texelOf(c), 0).r;
    a = clamp(hat + 0.5 * (an - back), lo, hi);
  }
  gl_FragColor = vec4(clamp(a * uDecay, 0.0, 1.0), 0.0, 0.0, 0.0);
}`,FLUID_EXPORT_GLSL=FLUID_COMMON+`
uniform sampler2D tDyeCur, tVortCur;
void main(){
  ivec3 c;
  if (!cellOf(c)){ gl_FragColor = vec4(0.0); return; }
  vec4 w = texelFetch(tWall, texelOf(c), 0);
  float inside = isSolid(w.r) ? 0.0 : (isCap(w.r) ? 1.0 : w.b);
  // \u67D3\u6599\u53EA\u7ED9\u753B\u9762\u7528\uFF1A\u548C 6 \u90BB\u683C\u5E73\u5747\u4E00\u534A\u518D\u5BFC\u51FA\u3002MacCormack \u7684\u9650\u5E45\u5728\u5C04\u6D41\u91CC\u7559\u4E0B\u9010\u683C\u7684\u660E\u6697\uFF08f \u5728 0.7\u20131 \u4E4B\u95F4\u8DF3\uFF09\uFF0C\u7EBF\u6027\u8FC7\u6EE4\u540E\u5728\u65AD\u9762\u4E0A\u662F\u4E00\u683C\u683C\u7684\u65B9\u5757
  float dye = texelFetch(tDyeCur, texelOf(c), 0).r, nb = 0.0;
  for (int k = 0; k < 6; k++) nb += texelFetch(tDyeCur, texelOf(clampCell(c + dir6(k))), 0).r;
  dye = 0.5 * dye + nb / 12.0;
  float sp = isSolid(w.r) ? 0.0 : length(centerVel(c));
  float vort = texelFetch(tVortCur, texelOf(c), 0).w;
  gl_FragColor = vec4(dye, inside, sp, vort);
}`,FLUID_VOL_GLSL=`
uniform sampler2D uVol;
uniform vec3 uVolDims;
uniform vec2 uVolAtlas;
uniform float uVolCols;
vec4 volSlice(float z, vec2 xy){
  float ty = floor(z / uVolCols), tx = z - ty * uVolCols;
  vec2 p = vec2(tx * uVolDims.x, ty * uVolDims.y) + clamp(xy, vec2(0.5), uVolDims.xy - 0.5);
  return texture(uVol, p / uVolAtlas);
}
vec4 volAt(vec3 uvw){
  float g = clamp(uvw.z * uVolDims.z - 0.5, 0.0, uVolDims.z - 1.0);
  float z0 = floor(g), z1 = min(z0 + 1.0, uVolDims.z - 1.0);
  vec2 xy = uvw.xy * uVolDims.xy;
  return mix(volSlice(z0, xy), volSlice(z1, xy), g - z0);
}
`,FLUID_FILL_GLSL=`
precision highp float;
uniform vec4 uFill;
void main(){ gl_FragColor = uFill; }`,FLUID_MG_COMMON=`
precision highp float;
precision highp int;
uniform sampler2D tTypeF, tRhsF, tPhiF, tResF, tTypeC, tPhiC, tStatDiv;
uniform ivec3 uDimsF, uDimsC;
uniform int uColsF, uColsC, uParity;
uniform float uH, uOmega, uUseStatDiv;
bool cellF(out ivec3 c){
  ivec2 fc = ivec2(gl_FragCoord.xy);
  int tx = fc.x / uDimsF.x, ty = fc.y / uDimsF.y;
  int k = tx + ty * uColsF;
  c = ivec3(fc.x - tx * uDimsF.x, fc.y - ty * uDimsF.y, k);
  return k < uDimsF.z;
}
bool cellC(out ivec3 c){
  ivec2 fc = ivec2(gl_FragCoord.xy);
  int tx = fc.x / uDimsC.x, ty = fc.y / uDimsC.y;
  int k = tx + ty * uColsC;
  c = ivec3(fc.x - tx * uDimsC.x, fc.y - ty * uDimsC.y, k);
  return k < uDimsC.z;
}
ivec2 texelF(ivec3 c){ int ty = c.z / uColsF; int tx = c.z - ty * uColsF; return ivec2(tx * uDimsF.x + c.x, ty * uDimsF.y + c.y); }
ivec2 texelC(ivec3 c){ int ty = c.z / uColsC; int tx = c.z - ty * uColsC; return ivec2(tx * uDimsC.x + c.x, ty * uDimsC.y + c.y); }
bool inF(ivec3 c){ return all(greaterThanEqual(c, ivec3(0))) && all(lessThan(c, uDimsF)); }
float typeF(ivec3 c){ return inF(c) ? texelFetch(tTypeF, texelF(c), 0).r : 1.0; }
float phiF(ivec3 c){ return inF(c) ? texelFetch(tPhiF, texelF(c), 0).r : 0.0; }
ivec3 axis6(int k){ int a = k - 3 * (k / 3); return ivec3(a == 0 ? 1 : 0, a == 1 ? 1 : 0, a == 2 ? 1 : 0) * (k < 3 ? 1 : -1); }
float rhsF(ivec3 c){
  float r = texelFetch(tRhsF, texelF(c), 0).r;
  if (uUseStatDiv > 0.5){ vec4 s = texelFetch(tStatDiv, ivec2(0), 0); r -= s.x / max(s.y, 1.0); }
  return r;
}
`,FLUID_MG_SMOOTH_GLSL=FLUID_MG_COMMON+`
void main(){
  ivec3 c;
  if (!cellF(c)){ gl_FragColor = vec4(0.0); return; }
  float phi0 = texelFetch(tPhiF, texelF(c), 0).r;
  if (typeF(c) > 0.5){ gl_FragColor = vec4(0.0); return; }
  int par = (c.x + c.y + c.z) - 2 * ((c.x + c.y + c.z) / 2);
  if (par != uParity){ gl_FragColor = vec4(phi0, 0.0, 0.0, 0.0); return; }
  float S = 0.0, N = 0.0;
  for (int k = 0; k < 6; k++){
    float tn = typeF(c + axis6(k));
    if (tn < 0.5){ S += phiF(c + axis6(k)); N += 1.0; }
    else if (tn > 1.5 && tn < 2.5) N += 1.0;
  }
  float gs = N > 0.0 ? (S - uH * uH * rhsF(c)) / N : 0.0;
  gl_FragColor = vec4(phi0 + uOmega * (gs - phi0), 0.0, 0.0, 0.0);
}`,FLUID_MG_RESIDUAL_GLSL=FLUID_MG_COMMON+`
void main(){
  ivec3 c;
  if (!cellF(c) || typeF(c) > 0.5){ gl_FragColor = vec4(0.0); return; }
  float phic = texelFetch(tPhiF, texelF(c), 0).r;
  float L = 0.0;
  for (int k = 0; k < 6; k++){
    float tn = typeF(c + axis6(k));
    if (tn < 0.5) L += phiF(c + axis6(k)) - phic;
    else if (tn > 1.5 && tn < 2.5) L -= phic;
  }
  gl_FragColor = vec4(rhsF(c) - L / (uH * uH), 0.0, 0.0, 0.0);
}`,FLUID_MG_COARSEN_GLSL=FLUID_MG_COMMON+`
void main(){
  ivec3 C;
  if (!cellC(C)){ gl_FragColor = vec4(1.0, 0.0, 0.0, 0.0); return; }
  bool anyOpen = false, anyFluid = false;
  for (int dz = 0; dz < 2; dz++) for (int dy = 0; dy < 2; dy++) for (int dx = 0; dx < 2; dx++){
    ivec3 c = 2 * C + ivec3(dx, dy, dz);
    if (!inF(c)) continue;
    float t = texelFetch(tTypeF, texelF(c), 0).r;
    if (t > 1.5 && t < 2.5) anyOpen = true; else if (t < 0.5) anyFluid = true;
  }
  gl_FragColor = vec4(anyOpen ? 2.0 : (anyFluid ? 0.0 : 1.0), 0.0, 0.0, 0.0);
}`,FLUID_MG_RESTRICT_GLSL=FLUID_MG_COMMON+`
void main(){
  ivec3 C;
  if (!cellC(C)){ gl_FragColor = vec4(0.0); return; }
  float s = 0.0;
  for (int dz = 0; dz < 2; dz++) for (int dy = 0; dy < 2; dy++) for (int dx = 0; dx < 2; dx++){
    ivec3 c = 2 * C + ivec3(dx, dy, dz);
    if (inF(c) && texelFetch(tTypeF, texelF(c), 0).r < 0.5) s += texelFetch(tResF, texelF(c), 0).r;
  }
  gl_FragColor = vec4(s / 8.0, 0.0, 0.0, 0.0);     // \u03A3/8\uFF08\u4E0D\u662F\u5B50\u683C\u5747\u503C\uFF09\uFF1A\u7EAF Neumann \u65F6\u7C97\u5C42\u624D\u76F8\u5BB9\uFF08notes \xA71.3\uFF09
}`,FLUID_MG_PROLONG_GLSL=FLUID_MG_COMMON+`
void main(){
  ivec3 c;
  if (!cellF(c)){ gl_FragColor = vec4(0.0); return; }
  float phi0 = texelFetch(tPhiF, texelF(c), 0).r;
  if (typeF(c) > 0.5){ gl_FragColor = vec4(0.0); return; }
  gl_FragColor = vec4(phi0 + texelFetch(tPhiC, texelC(c / 2), 0).r, 0.0, 0.0, 0.0);
}`;Object.assign(window,{FLUID_MAX_CAPS:16,FLUID_COMMON,FLUID_DISP_GLSL,FLUID_WALL_GLSL,FLUID_WALLFIX_GLSL,FLUID_WALLVEL_GLSL,FLUID_DEPOSIT_GLSL,FLUID_CAPVEL_GLSL,FLUID_ADVECT_GLSL,FLUID_MACCORMACK_GLSL,FLUID_VORT_GLSL,FLUID_CONFINE_GLSL,FLUID_DIV_GLSL,FLUID_PROJECT_GLSL,FLUID_RFRAME_GLSL,FLUID_RCAPS_GLSL,FLUID_RDIV_GLSL,FLUID_SUM8_GLSL,FLUID_FINAL_GLSL,FLUID_DYE_ADV_GLSL,FLUID_DYE_FIX_GLSL,FLUID_EXPORT_GLSL,FLUID_VOL_GLSL,FLUID_FILL_GLSL,FLUID_MG_SMOOTH_GLSL,FLUID_MG_RESIDUAL_GLSL,FLUID_MG_COARSEN_GLSL,FLUID_MG_RESTRICT_GLSL,FLUID_MG_PROLONG_GLSL});
