const BLOOD_LAYER=3,SHELL_COLORS={art:{thin:14168634,deep:5114384,fresh:16729654,old:5901332},ven:{thin:9050664,deep:4329498,fresh:12595278,old:3804700}},SHELL_VOL={step:.08,density:1.2,oldDensity:.2,iso:.45,isoAlpha:.55},THICK_VERT=`
#include <morphtarget_pars_vertex>
#include <clipping_planes_pars_vertex>
varying float vViewZ;
void main(){
  #include <begin_vertex>
  #include <morphtarget_vertex>
  vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.0);
  vViewZ = -mvPosition.z;
  gl_Position = projectionMatrix * mvPosition;
  #include <clipping_planes_vertex>
}`,THICK_FRAG=`
#include <clipping_planes_pars_fragment>
varying float vViewZ;
void main(){
  #include <clipping_planes_fragment>
  gl_FragColor = vec4(gl_FrontFacing ? -vViewZ : vViewZ, 0.0, 0.0, 1.0);
}`,SHELL_VERT=`
#include <morphtarget_pars_vertex>
#include <clipping_planes_pars_vertex>
attribute float winF;          // \u5F00\u7A97\u7684\u8840\u7BA1\uFF1A\u692D\u7403\u5207\u5200\u573A\uFF08flow-viz.jsx\uFF09\uFF0C< 0 \u5728\u7A97\u91CC\uFF1B\u6CA1\u6709\u8FD9\u4E2A\u5C5E\u6027\u7684\u7F51\u683C\u8BFB\u5230 0
uniform float uWinOn, uRecess; // \u7A97\u53E3\u6A21\u5F0F\uFF08\u53EA\u753B\u7A97\u91CC\u7684\u8840\u4F53\uFF09\uFF1B\u8840\u4F53\u8868\u9762\u6CBF\u6CD5\u7EBF\u5185\u7F29\u4E00\u4E2A\u58C1\u539A\uFF0C\u7A97\u6846\u73AF\uFF08flow-viz \u7684 rim\uFF09\u9732\u5728\u8840\u9762\u4E4B\u5916
varying vec3 vBasis, vNormalV, vViewPos, vPosed;
varying float vWinF;
void main(){
  vBasis = position;
  vWinF = winF;
  #include <beginnormal_vertex>
  #include <morphnormal_vertex>
  vNormalV = normalize(normalMatrix * objectNormal);
  #include <begin_vertex>
  #include <morphtarget_vertex>
  vPosed = transformed;            // \u5F62\u6001\u952E\u4E4B\u540E\u3001\u67A2\u8F74\u7F29\u653E\u4E4B\u524D\u7684\u5C40\u90E8\u5750\u6807 = \u771F\u6DB2\u4F53\u6C42\u89E3\u5668\u683C\u7684\u5750\u6807
  if (uWinOn > 0.5) transformed -= normalize(objectNormal) * uRecess;
  vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.0);
  vViewPos = mvPosition.xyz;
  gl_Position = projectionMatrix * mvPosition;
  #include <clipping_planes_vertex>
}`,SHELL_FRAG=`
precision highp sampler3D;
#include <clipping_planes_pars_fragment>
uniform sampler2D tThick;
uniform vec2 uResolution;
uniform sampler3D uFlowA, uFlowB;
uniform vec3 uOrigin, uSize;
uniform vec4 uSwirl;        // \u5FC3\u8154\u5927\u6DA1\u65CB\uFF08\u4E0E\u9897\u7C92\u540C\u4E00\u4EFD\uFF1Ablood.jsx swirlU / swirlC\uFF09
uniform vec3 uSwirlC;
uniform float uSwirlMax;
vec3 swirlAt(vec3 p){
  if (uSwirl.w <= 0.0) return vec3(0.0);
  vec3 t = cross(uSwirl.xyz, p - uSwirlC);
  float tl = length(t);
  float sp = min(uSwirl.w * tl, uSwirlMax);
  return tl > 1e-5 ? t * (sp / tl) : vec3(0.0);
}
uniform float uUmaxA, uUmaxB, uQa, uQb, uHasB, uVolScale, uTime;
uniform vec3 uColThin, uColDeep, uWallTint;
uniform float uClear, uAbsorb, uAlphaDist, uWallRim, uNoiseScale, uCmPerUnit;   // \u539A\u5EA6\u7EB9\u7406\u662F\u573A\u666F\u5355\u4F4D\uFF08\u5FC3\u810F\u6839\u7F29\u653E 0.24\uFF09\uFF0C\u6362\u6210 cm
uniform float uWinOn;          // \u5B8C\u6574\u5916\u89C2\u5F00\u7A97\uFF1A\u53EA\u753B\u7A97\u91CC\u7684\u8840\u4F53\uFF08\u7BA1\u58C1\u7531\u8840\u7BA1\u539F\u6750\u8D28\u5728\u4E0D\u900F\u660E\u901A\u9053\u753B\uFF09
varying vec3 vBasis, vNormalV, vViewPos, vPosed;
varying float vWinF;
// \u771F\u6DB2\u4F53\u4F53\u79EF\uFF1Ar \u65B0\u8840\u67D3\u6599\uFF080\u20131\uFF09\u3001g \u6D41\u4F53\u5206\u6570\u3001b \u901F\u5EA6\uFF08cm/s\uFF09\uFF1BuCamLocal = \u76F8\u673A\u5728\u672C\u7F51\u683C\u5C40\u90E8\u5750\u6807\u91CC\u7684\u4F4D\u7F6E
${FLUID_VOL_GLSL}
uniform float uVolOn, uVolStep, uVolDensity, uVolOld, uVolIso, uVolIsoAlpha;
uniform vec3 uVolOrigin, uVolSize, uVolTexel, uCamLocal, uLightLocal, uColFresh, uColOld;
vec3 volGrad(vec3 uvw){
  return vec3(volAt(uvw + vec3(uVolTexel.x, 0.0, 0.0)).r - volAt(uvw - vec3(uVolTexel.x, 0.0, 0.0)).r,
              volAt(uvw + vec3(0.0, uVolTexel.y, 0.0)).r - volAt(uvw - vec3(0.0, uVolTexel.y, 0.0)).r,
              volAt(uvw + vec3(0.0, 0.0, uVolTexel.z)).r - volAt(uvw - vec3(0.0, 0.0, uVolTexel.z)).r);
}
// \u4F53\u79EF\uFF1A\u8001\u8840\u662F\u4E00\u5C42\u6697\u7EA2\u7684\u96FE\uFF0C\u65B0\u8840\u66F4\u6D53\uFF1B\u65B0\u8840\u56E2\u7684\u8FB9\u754C\uFF08\u67D3\u6599 = uVolIso \u7684\u7B49\u503C\u9762\uFF09\u5F53\u4E00\u5C42\u534A\u900F\u7684\u6E7F\u4EAE\u9762\u753B\uFF08\u68AF\u5EA6\u5F53\u6CD5\u7EBF\u6253\u5149 + \u9AD8\u5149\uFF09\uFF0C
// \u4E0D\u7136 5 cm \u6DF1\u7684\u8154\u6CBF\u89C6\u7EBF\u4E00\u79EF\u5206\uFF0C\u6DA1\u73AF\u3001\u5C04\u6D41\u5934\u8FD9\u4E9B\u5F62\u72B6\u5C31\u7CCA\u6210\u4E00\u56E2\u7EA2\u5149\uFF0810-04 \u622A\u56FE\uFF09
vec4 marchVolume(){
  vec3 dir = normalize(vPosed - uCamLocal);
  // \u8D77\u70B9\u6309\u50CF\u7D20\u6296\u52A8\u4E0D\u5230\u4E00\u6B65\uFF1A\u6B65\u957F\u548C\u683C\u5B50\u7684\u62CD\u9891\u5728\u7B49\u503C\u9762\u4E0A\u753B\u51FA\u6728\u7EB9\uFF0810-04 \u622A\u56FE\uFF09
  vec3 p = vPosed + dir * uVolStep * fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));
  vec3 acc = vec3(0.0);
  float aacc = 0.0, outRun = 0.0, prevF = -1.0;
  bool seen = false;
  float base = uVolDensity * (1.0 - uClear);
  vec3 Hl = normalize(uLightLocal - dir);
  for (int i = 0; i < 200; i++){
    vec3 uvw = (p - uVolOrigin) / uVolSize;
    if (any(lessThan(uvw, vec3(0.0))) || any(greaterThan(uvw, vec3(1.0)))) break;
    vec4 s = volAt(uvw);
    if (s.g > 0.5){
      seen = true; outRun = 0.0;
      float fresh = clamp(s.r, 0.0, 1.0);
      if (prevF >= 0.0 && prevF < uVolIso && fresh >= uVolIso){
        // \u4EA4\u70B9\u6309\u7EBF\u6027\u63D2\u503C\u9000\u56DE\u53BB\uFF0C\u5728\u4EA4\u70B9\u4E0A\u53D6\u68AF\u5EA6\uFF1B\u68AF\u5EA6\u5F31\uFF08\u65B0\u8001\u8840\u5DF2\u7ECF\u6DF7\u5F00\u3001\u8FB9\u754C\u662F\u8F6F\u7684\uFF09\u7684\u4EA4\u70B9\u4EAE\u9762\u4E5F\u6DE1\uFF0C\u566A\u58F0\u91CC\u7684\u788E\u7247\u4E0D\u53D1\u4EAE
        vec3 pc = p - dir * uVolStep * (fresh - uVolIso) / max(fresh - prevF, 1e-4);
        vec3 g = volGrad((pc - uVolOrigin) / uVolSize);
        float gl = length(g);
        if (gl > 1e-3){
          vec3 n = -g / gl;
          float diff = max(0.0, dot(n, uLightLocal));
          vec3 cs = uColFresh * (0.4 + 0.8 * diff) + vec3(1.0, 0.92, 0.88) * pow(max(0.0, dot(n, Hl)), 40.0) * 0.6;
          float as = uVolIsoAlpha * (1.0 - uClear) * smoothstep(0.08, 0.3, gl);
          acc += (1.0 - aacc) * as * cs;
          aacc += (1.0 - aacc) * as;
        }
      }
      prevF = fresh;
      float a1 = 1.0 - exp(-base * mix(uVolOld, 1.0, fresh) * uVolStep);
      vec3 c = mix(uColOld, uColFresh, fresh) * (1.0 + 0.35 * smoothstep(15.0, 80.0, s.b));
      acc += (1.0 - aacc) * a1 * c;
      aacc += (1.0 - aacc) * a1;
      if (aacc > 0.985) break;
    } else if (seen){ outRun += uVolStep; prevF = -1.0; if (outRun > 0.3) break; }   // \u51FA\u4E86\u8FD9\u4E00\u6BB5\u8154\u5C31\u505C\uFF1A\u51F9\u5904\u518D\u8FDB\u8154\u7684\u90A3\u6BB5\u7531\u5B83\u81EA\u5DF1\u7684\u6B63\u9762\u7247\u5143\u753B
    p += dir * uVolStep;
  }
  return vec4(aacc > 1e-4 ? acc / aacc : uColOld, aacc);
}

float hash3(vec3 p){ p = fract(p * 0.3183099 + vec3(0.1, 0.2, 0.3)); p *= 17.0; return fract(p.x * p.y * p.z * (p.x + p.y + p.z)); }
float vnoise(vec3 x){
  vec3 i = floor(x), f = fract(x); f = f * f * (3.0 - 2.0 * f);
  return mix(mix(mix(hash3(i), hash3(i + vec3(1,0,0)), f.x), mix(hash3(i + vec3(0,1,0)), hash3(i + vec3(1,1,0)), f.x), f.y),
             mix(mix(hash3(i + vec3(0,0,1)), hash3(i + vec3(1,0,1)), f.x), mix(hash3(i + vec3(0,1,1)), hash3(i + vec3(1,1,1)), f.x), f.y), f.z);
}
float fbm(vec3 p){ return 0.55 * vnoise(p) + 0.3 * vnoise(p * 2.1 + 7.0) + 0.15 * vnoise(p * 4.3 + 19.0); }
vec3 velAt(vec3 p){
  vec3 uvw = (p - uOrigin) / uSize;
  vec4 a = texture(uFlowA, uvw);
  vec3 v = (a.xyz * 2.0 - 1.0) * a.a * uUmaxA * uQa;
  if (uHasB > 0.5){ vec4 b = texture(uFlowB, uvw); v += (b.xyz * 2.0 - 1.0) * b.a * uUmaxB * uQb; }
  return v * uVolScale;
}
void main(){
  #include <clipping_planes_fragment>
  // \u7A97\u53E3\u6A21\u5F0F\uFF1A\u7A97\u5916\uFF08NaN \u4E5F\u7B97\uFF09\u4E0D\u753B\u3002\u8840\u4F53\u6BD4\u6D1E\u5927\u4E00\u5927\u5708\uFF08winF < 1.5 \u2248 \u6D1E\u534A\u5F84\u7684 1.6 \u500D\uFF09\uFF1A\u8840\u9762\u6CBF\u6CD5\u7EBF\u5185\u7F29\u4E86\u4E00\u4E2A\u58C1\u539A\uFF0C
  // \u659C\u7740\u770B\u6D1E\u7684\u8FB9\u7F18\u65F6\u89C6\u7EBF\u4F1A\u4ECE\u8840\u9762\u65C1\u8FB9\u64E6\u8FC7\u53BB\u76F4\u63A5\u770B\u5230\u7BA1\u8154\u91CC\u7684\u9897\u7C92\uFF08\u53F3\u4FA7\u89C2\u80BA\u52A8\u8109\u7A97\u4E0A\u7F18\u4E00\u4E32\u77ED\u5212\uFF1B\u63A0\u5C04 80\xB0 \u65F6\u7F1D\u6709 0.1 \xD7 tan80\xB0 \u2248 0.6 cm\uFF09\uFF1B
  // \u591A\u51FA\u7684\u4E00\u5708\u8D34\u5728\u58C1\u5185\u4FA7\u3001\u53EA\u80FD\u900F\u8FC7\u6D1E\u770B\u5230\uFF0C\u58C1\u5916\u7531\u6DF1\u5EA6\u6D4B\u8BD5\u6321\u6389\uFF0C\u6240\u4EE5\u5927\u4E00\u70B9\u4E0D\u82B1\u94B1
  if (uWinOn > 0.5 && !(vWinF < 1.5)) discard;
  if (uVolOn > 0.5){
    vec4 vol = marchVolume();
    vec3 Nv = normalize(vNormalV), Vv = normalize(-vViewPos);
    float Fv = pow(1.0 - max(0.0, dot(Nv, Vv)), 3.0);
    vec3 Hv = normalize(normalize(vec3(0.35, 0.6, 0.72)) + Vv);
    vec3 colv = vol.rgb + vec3(1.0, 0.9, 0.85) * pow(max(0.0, dot(Nv, Hv)), 48.0) * 0.45 + uWallTint * Fv * uWallRim;
    gl_FragColor = vec4(colv, clamp(max(vol.a, Fv * 0.35 * (1.0 - uClear)), 0.0, 0.97));
  } else {                         // \u4E0D\u80FD\u5728 main \u91CC\u63D0\u524D return\uFF1AOIT \u53D8\u4F53\u628A\u6743\u91CD\u4EE3\u7801\u63A5\u5728 main \u7684\u672B\u5C3E\uFF08oit.jsx\uFF09\uFF0C\u63D0\u524D\u8FD4\u56DE\u7684\u7247\u5143\u6743\u91CD\u662F\u7A7A\u7684\uFF0C\u6574\u5757\u4F53\u79EF\u770B\u4E0D\u89C1
  float thick = max(0.0, texture2D(tThick, gl_FragCoord.xy / uResolution).r) * uCmPerUnit;
  // \u4E24\u76F8 flow-map\uFF1A\u7EB9\u7406\u5750\u6807\u6CBF\u5F53\u524D\u6D41\u901F\u5012\u63A8\uFF0C\u4E24\u76F8\u9519\u534A\u5468\u671F\u4EA4\u66FF\uFF0C\u907F\u514D\u65E0\u9650\u62C9\u4F38
  vec3 v = velAt(vBasis) + swirlAt(vBasis);
  float T = 1.2;
  float ph1 = fract(uTime / T), ph2 = fract(uTime / T + 0.5);
  float w1 = 1.0 - abs(2.0 * ph1 - 1.0);
  vec3 p = vBasis * uNoiseScale;
  // \u4E24\u5C42\u968F\u6D41\u573A\u6F02\u7684\u6DA1\u6D41\u7EB9\u7406\uFF08\u7C97\u5C42\u6162\u3001\u7EC6\u5C42\u5FEB\uFF09\uFF0C\u4E24\u76F8\u4EA4\u66FF\u907F\u514D\u65E0\u9650\u62C9\u4F38
  float n1 = mix(fbm(p - v * ph2 * T * uNoiseScale * 0.5), fbm(p - v * ph1 * T * uNoiseScale * 0.5), w1);
  float n2 = mix(fbm(p * 2.4 + 5.0 - v * ph2 * T * uNoiseScale * 0.9), fbm(p * 2.4 + 5.0 - v * ph1 * T * uNoiseScale * 0.9), w1);
  float n = 0.65 * n1 + 0.35 * n2;
  float dens = 1.0 - exp(-thick / uAbsorb);
  vec3 col = mix(uColThin, uColDeep, dens);
  col *= 0.7 + 0.7 * n;                                     // \u7EC6\u80DE\u56E2\u7684\u660E\u6697\u6DA1\u6D41
  vec3 N = normalize(vNormalV), V = normalize(-vViewPos);
  float ndv = max(0.0, dot(N, V));
  float F = pow(1.0 - ndv, 3.0);
  vec3 L = normalize(vec3(0.35, 0.6, 0.72)), H = normalize(L + V);
  float spec = pow(max(0.0, dot(N, H)), 48.0);
  col += vec3(1.0, 0.9, 0.85) * spec * 0.45 + uWallTint * F * uWallRim;   // \u6E7F\u4EAE\u9AD8\u5149 + \u7BA1\u58C1 / \u5185\u819C\u7684\u8FB9\u7F18\u8272
  float alpha = (1.0 - uClear) * (1.0 - exp(-thick / uAlphaDist));
  alpha = max(alpha, F * 0.35 * (1.0 - uClear));
  // \u7A97\u53E3\u8840\u4F53\uFF1A\u659C\u7740\u770B\u7A97\uFF08\u53F3\u4FA7\u89C2\u770B\u80BA\u52A8\u8109\u5E72\u6B63\u9762\u7684\u7A97\uFF09\u89C6\u7EBF\u7A7F\u8FC7\u7684\u8840\u5F88\u8584\uFF0C\u6309\u539A\u5EA6\u7B97\u7684 alpha \u63A5\u8FD1 0\uFF0C\u7A97\u91CC\u9732\u51FA\u7684\u662F\u6697\u8272\u7BA1\u8154\u80CC\u9762 + \u9897\u7C92\uFF0C
  // \u8BFB\u8D77\u6765\u50CF"\u58C1\u4E0A\u900F\u51FA\u6D41\u7EBF"\uFF1B\u7A97\u91CC\u7684\u8840\u81F3\u5C11\u4FDD\u6301 (1 \u2212 \u900F\u89C6\u5EA6) \xD7 0.6 \u7684\u4E0D\u900F\u660E
  if (uWinOn > 0.5) alpha = max(alpha, 0.6 * (1.0 - uClear));
  gl_FragColor = vec4(col, clamp(alpha, 0.0, 0.95));
  }
  #include <tonemapping_fragment>
  #include <colorspace_fragment>
}`,CAP_VERT=`
#include <clipping_planes_pars_vertex>
varying vec3 vLocal;
void main(){
  vLocal = position;
  vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
  gl_Position = projectionMatrix * mvPosition;
  #include <clipping_planes_vertex>
}`,CAP_FRAG=`
precision highp sampler3D;
#include <clipping_planes_pars_fragment>
uniform sampler3D uFlowA, uFlowB;
uniform vec3 uOrigin, uSize;
uniform vec4 uSwirl;        // \u5FC3\u8154\u5927\u6DA1\u65CB\uFF08\u4E0E\u9897\u7C92\u540C\u4E00\u4EFD\uFF1Ablood.jsx swirlU / swirlC\uFF09
uniform vec3 uSwirlC;
uniform float uSwirlMax;
vec3 swirlAt(vec3 p){
  if (uSwirl.w <= 0.0) return vec3(0.0);
  vec3 t = cross(uSwirl.xyz, p - uSwirlC);
  float tl = length(t);
  float sp = min(uSwirl.w * tl, uSwirlMax);
  return tl > 1e-5 ? t * (sp / tl) : vec3(0.0);
}
uniform float uUmaxA, uUmaxB, uQa, uQb, uHasB, uVolScale, uTime;
uniform vec3 uColThin, uColDeep;
uniform float uClear, uCapThick, uAbsorb, uNoiseScale, uCapZ;
${FLUID_VOL_GLSL}
uniform float uVolOn;
uniform vec3 uVolOrigin, uVolSize, uColFresh, uColOld;
uniform mat4 uCapToVol;
varying vec3 vLocal;
float hash3(vec3 p){ p = fract(p * 0.3183099 + vec3(0.1, 0.2, 0.3)); p *= 17.0; return fract(p.x * p.y * p.z * (p.x + p.y + p.z)); }
float vnoise(vec3 x){
  vec3 i = floor(x), f = fract(x); f = f * f * (3.0 - 2.0 * f);
  return mix(mix(mix(hash3(i), hash3(i + vec3(1,0,0)), f.x), mix(hash3(i + vec3(0,1,0)), hash3(i + vec3(1,1,0)), f.x), f.y),
             mix(mix(hash3(i + vec3(0,0,1)), hash3(i + vec3(1,0,1)), f.x), mix(hash3(i + vec3(0,1,1)), hash3(i + vec3(1,1,1)), f.x), f.y), f.z);
}
float fbm(vec3 p){ return 0.55 * vnoise(p) + 0.3 * vnoise(p * 2.1 + 7.0) + 0.15 * vnoise(p * 4.3 + 19.0); }
void main(){
  #include <clipping_planes_fragment>
  vec3 col;
  if (uVolOn > 0.5){
    vec4 s = volAt(clamp(((uCapToVol * vec4(vLocal, 1.0)).xyz - uVolOrigin) / uVolSize, 0.0, 1.0));
    col = mix(uColOld, uColFresh, clamp(s.r, 0.0, 1.0)) * (1.0 + 0.35 * smoothstep(15.0, 80.0, s.b));
  } else {
  vec3 basis = vec3(vLocal.xy, uCapZ);            // \u5C01\u76D6\u51E0\u4F55\u5728\u5C40\u90E8 XY \u9762\uFF0C\u7EC4\u6CBF z \u5E73\u79FB\u5230\u5207\u9762\u6DF1\u5EA6
  vec3 uvw = (basis - uOrigin) / uSize;
  vec3 v = vec3(0.0);
  if (all(greaterThan(uvw, vec3(0.0))) && all(lessThan(uvw, vec3(1.0)))){
    vec4 a = texture(uFlowA, uvw);
    v = (a.xyz * 2.0 - 1.0) * a.a * uUmaxA * uQa;
    if (uHasB > 0.5){ vec4 b = texture(uFlowB, uvw); v += (b.xyz * 2.0 - 1.0) * b.a * uUmaxB * uQb; }
    v *= uVolScale;
  }
  v += swirlAt(basis);
  float T = 1.2;
  float ph1 = fract(uTime / T), ph2 = fract(uTime / T + 0.5);
  float w1 = 1.0 - abs(2.0 * ph1 - 1.0);
  vec3 p = basis * uNoiseScale;
  float n = mix(fbm(p - v * ph2 * T * uNoiseScale * 0.35), fbm(p - v * ph1 * T * uNoiseScale * 0.35), w1);
  float dens = 1.0 - exp(-uCapThick / uAbsorb);
  col = mix(uColThin, uColDeep, dens) * (0.8 + 0.5 * n);
  col += vec3(0.25, 0.08, 0.06) * pow(n, 4.0);     // \u65AD\u9762\u4E0A\u7EC6\u80DE\u56E2\u7684\u6E7F\u4EAE\u70B9
  }
  // \u771F\u6DB2\u4F53\u7684\u65AD\u9762\u66F4\u5B9E\u4E00\u70B9\uFF1A\u540E\u9762\u7684\u9897\u7C92\u900F\u5F97\u5C11\uFF0C\u65AD\u9762\u4E0A\u65B0\u8001\u8840\u5377\u51FA\u7684\u82B1\u7EB9\u770B\u5F97\u6E05
  gl_FragColor = vec4(col, uVolOn > 0.5 ? 0.97 - 0.3 * uClear : 0.92 - 0.45 * uClear);
  #include <tonemapping_fragment>
  #include <colorspace_fragment>
}`;function bloodCapMaterial(m,l,u){const s=SHELL_COLORS[l?"art":"ven"],v=!!m.flowTex.fill,c=m.meta.fields,n=u.userData.capOffset||0,d=new THREE.ShaderMaterial({uniforms:{uFlowA:{value:v?m.flowTex.fill:m.flowTex.flow},uFlowB:{value:v?m.flowTex.eject:m.flowTex.flow},uOrigin:{value:new THREE.Vector3(...m.origin)},uSize:{value:new THREE.Vector3(...m.size)},uUmaxA:{value:v?c.fill.umax:c.flow.umax},uUmaxB:{value:v?c.eject.umax:0},uQa:{value:0},uQb:{value:0},uHasB:{value:v?1:0},uVolScale:{value:1},uTime:{value:0},uColThin:{value:new THREE.Color(s.thin)},uColDeep:{value:new THREE.Color(s.deep)},uClear:{value:BLOOD.clear},uCapThick:{value:2},uAbsorb:{value:BLOOD.absorbCm},uNoiseScale:{value:2.2},uCapZ:{value:0},uSwirl:{value:new THREE.Vector4(0,1,0,0)},uSwirlC:{value:new THREE.Vector3},uSwirlMax:{value:BLOOD.swirlMaxCm},uVol:{value:null},uVolDims:{value:new THREE.Vector3(1,1,1)},uVolAtlas:{value:new THREE.Vector2(1,1)},uVolCols:{value:1},uVolOn:{value:0},uVolOrigin:{value:new THREE.Vector3},uVolSize:{value:new THREE.Vector3(1,1,1)},uColFresh:{value:new THREE.Color(s.fresh)},uColOld:{value:new THREE.Color(s.old)},uCapToVol:{value:new THREE.Matrix4}},vertexShader:CAP_VERT,fragmentShader:CAP_FRAG,transparent:!1,blending:THREE.CustomBlending,blendSrc:THREE.SrcAlphaFactor,blendDst:THREE.OneMinusSrcAlphaFactor,blendEquation:THREE.AddEquation,depthWrite:!0,side:THREE.DoubleSide,stencilWrite:!0,stencilRef:0,stencilFunc:THREE.NotEqualStencilFunc,stencilFail:THREE.ReplaceStencilOp,stencilZFail:THREE.ReplaceStencilOp,stencilZPass:THREE.ReplaceStencilOp,polygonOffset:!0,polygonOffsetFactor:n,polygonOffsetUnits:n});return d.userData.bloodCap=!0,d}const GLSL_SHELL={thickMaterial(){return new THREE.ShaderMaterial({vertexShader:THICK_VERT,fragmentShader:THICK_FRAG,side:THREE.DoubleSide,blending:THREE.CustomBlending,blendEquation:THREE.AddEquation,blendSrc:THREE.OneFactor,blendDst:THREE.OneFactor,depthTest:!1,depthWrite:!1,transparent:!0,clipping:!0})},makeRT(m,l){return new THREE.WebGLRenderTarget(m,l,{type:THREE.FloatType,format:THREE.RGBAFormat,depthBuffer:!1,stencilBuffer:!1,minFilter:THREE.NearestFilter,magFilter:THREE.NearestFilter})},shellMaterial(m,l,u,s,v){const c=SHELL_COLORS[u?"art":"ven"],n=!!l.flowTex.fill,d=l.meta.fields;return new THREE.ShaderMaterial({uniforms:{tThick:{value:null},uResolution:{value:new THREE.Vector2(1,1)},uFlowA:{value:n?l.flowTex.fill:l.flowTex.flow},uFlowB:{value:n?l.flowTex.eject:l.flowTex.flow},uOrigin:{value:new THREE.Vector3(...l.origin)},uSize:{value:new THREE.Vector3(...l.size)},uUmaxA:{value:n?d.fill.umax:d.flow.umax},uUmaxB:{value:n?d.eject.umax:0},uQa:{value:0},uQb:{value:0},uHasB:{value:n?1:0},uVolScale:{value:1},uTime:{value:0},uColThin:{value:new THREE.Color(c.thin)},uColDeep:{value:new THREE.Color(c.deep)},uWallTint:{value:new THREE.Color(s).lerp(new THREE.Color(16777215),.45)},uWallRim:{value:n?.25:.32},uClear:{value:BLOOD.clear},uAbsorb:{value:BLOOD.absorbCm},uAlphaDist:{value:BLOOD.alphaCm},uNoiseScale:{value:2.2},uCmPerUnit:{value:v},uWinOn:{value:0},uRecess:{value:0},uSwirl:{value:new THREE.Vector4(0,1,0,0)},uSwirlC:{value:new THREE.Vector3},uSwirlMax:{value:BLOOD.swirlMaxCm},uVol:{value:null},uVolDims:{value:new THREE.Vector3(1,1,1)},uVolAtlas:{value:new THREE.Vector2(1,1)},uVolCols:{value:1},uVolOn:{value:0},uVolStep:{value:SHELL_VOL.step},uVolDensity:{value:SHELL_VOL.density},uVolOld:{value:SHELL_VOL.oldDensity},uVolIso:{value:SHELL_VOL.iso},uVolIsoAlpha:{value:SHELL_VOL.isoAlpha},uVolOrigin:{value:new THREE.Vector3},uVolSize:{value:new THREE.Vector3(1,1,1)},uVolTexel:{value:new THREE.Vector3(.01,.01,.01)},uCamLocal:{value:new THREE.Vector3},uLightLocal:{value:new THREE.Vector3(0,1,0)},uColFresh:{value:new THREE.Color(c.fresh)},uColOld:{value:new THREE.Color(c.old)}},vertexShader:SHELL_VERT,fragmentShader:SHELL_FRAG,transparent:!0,depthWrite:!1,side:THREE.FrontSide,clipping:!0})},capMaterial:bloodCapMaterial},shellFactory=()=>window.GFX&&window.GFX.tsl?BLOOD_SHELL_TSL:GLSL_SHELL;class BloodShell{constructor(l,u,s,v,c){this.mesh=l,this.region=u,this.mat=shellFactory().shellMaterial(l,u,s,v,c),this.mat.userData.bloodShell=!0,this.original=l.material,this.winMesh=null,l.userData.matTransp||(l.userData.tissueMat=l.material)}ensureWinMesh(){if(this.winMesh)return this.winMesh;const l=this.mesh,u=new THREE.Mesh(l.geometry,this.mat);u.name=l.name+"_window_blood",u.morphTargetInfluences=l.morphTargetInfluences,u.morphTargetDictionary=l.morphTargetDictionary,u.userData.morphFollower=!0,u.frustumCulled=!1,u.castShadow=!1,u.receiveShadow=!1,u.raycast=()=>{},u.renderOrder=3,u.visible=!1,l.add(u);const s=(window.VESSEL_WINDOWS||[]).find(v=>v.vessel===l.name);return this.mat.uniforms.uRecess.value=s?s.wall_cm:.1,this.winMesh=u,u}update(l,u,s,v){const c=this.mat.uniforms;c.uQa.value=l,c.uQb.value=u,c.uVolScale.value=s,c.uTime.value=v}dispose(){this.mat.dispose()}}function buildBloodShells(m,l,u,s){const v=m.userData.parts,c=v.chambers.userData.cavities,n={},d={AORTA:COL.aorta,PA:COL.pulm,SVC:COL.deoxyMid,IVC:COL.deoxyMid,PULM_VEINS:COL.oxyMid};for(const[e,a]of Object.entries(u.regions)){const i=BLOOD.chambers[e],t=i?c[i]:v.vessels.children.find(o=>o.name===e);if(!t)continue;n[e]=new BloodShell(t,a,BLOOD.arterial.includes(e),i?COL.endocardium:d[e],1/(m.userData.autoScale||1));const r=s[e];r&&r.swirlU&&(n[e].mat.uniforms.uSwirl.value=r.swirlU,n[e].mat.uniforms.uSwirlC.value=r.swirlC)}const V=shellFactory(),x=V.thickMaterial();let h=null,b=0,S=0;const y=()=>{const e=l.domElement.width,a=l.domElement.height;if(!(h&&b===e&&S===a)){h&&h.dispose(),h=V.makeRT(e,a),b=e,S=a;for(const i of Object.values(n))i.mat.uniforms.tThick.value=h.texture,i.mat.uniforms.uResolution.value.set(e,a)}};let R=!1;const w=[];let E=[],g=[],C=null;const L=()=>{g=[];for(const e of w){const a=e.mat.uniforms,i=C&&C.solvers[e.name],t=n[e.name];a.uVolOn&&(a.uVolOn.value=i&&t?1:0,!(!i||!t)&&(i.bindVol(a),a.uVolOrigin.value.set(...i.origin),a.uVolSize.value.set(...i.size),g.push({c:e,mesh:t.mesh})))}},_=new THREE.Vector3,O=new THREE.Matrix4,H=new THREE.Vector3,D=new THREE.Vector3(.35,.6,.72).normalize();return{shells:n,thickMat:x,caps:w,get rt(){return h},setFluid(e){C=e,L(),E=[];for(const[a,i]of Object.entries(n)){const t=e&&e.solvers[a],r=i.mat.uniforms;r.uVolOn&&(r.uVolOn.value=t?1:0,t&&(t.bindVol(r),r.uVolOrigin.value.set(...t.origin),r.uVolSize.value.set(...t.size),r.uVolTexel.value.set(...t.dims.map(o=>1/o)),E.push({sh:i,solver:t})))}},attachSection(e){for(const a of e.caps){const i=a.userData.blood;if(!i||!u.regions[i])continue;const t=u.regions[i],r=V.capMaterial(t,BLOOD.arterial.includes(i),a),o=s[i];o&&o.swirlU&&(r.uniforms.uSwirl.value=o.swirlU,r.uniforms.uSwirlC.value=o.swirlC),a.material=r,w.push({cap:a,mat:r,region:t,name:i})}L()},setCapDepth(e){for(const a of w)a.mat.uniforms.uCapZ.value=e},setMode(e,a){const i=e==="transp",t=e==="full"&&a;R=i||t;for(const[r,o]of Object.entries(n)){const f=!!BLOOD.chambers[r],p=!f&&!!o.mesh.geometry.attributes.winF,T=t&&p;if(o.mat.uniforms.uWinOn.value=T?1:0,i)o.mesh.material=o.mat,o.mesh.layers.enable(3),o.winMesh&&(o.winMesh.visible=!1,o.winMesh.layers.disable(3));else if(o.mesh.layers.disable(3),f?o.mesh.material===o.mat&&(o.mesh.material=o.mesh.userData.matTransp):o.mesh.material=o.original,T){const F=o.ensureWinMesh();F.visible=!0,F.layers.enable(3)}else o.winMesh&&(o.winMesh.visible=!1,o.winMesh.layers.disable(3))}},setClipping(e){for(const a of Object.values(n))a.mat.clippingPlanes=e||null,a.mat.needsUpdate=!0;x.clippingPlanes=e||null,x.needsUpdate=!0},setClear(e){for(const a of Object.values(n))a.mat.uniforms.uClear.value=e;for(const a of w)a.mat.uniforms.uClear.value=e},update(e,a,i){for(const[t,r]of Object.entries(n)){const o=s[t];if(!o)continue;const f=o.posVar.material.uniforms;r.update(f.uQa.value,f.uQb.value,f.uVolScale.value,i)}for(const t of w){const r=s[t.name];if(!r)continue;const o=r.posVar.material.uniforms,f=t.mat.uniforms;f.uQa.value=o.uQa.value,f.uQb.value=o.uQb.value,f.uVolScale.value=o.uVolScale.value,f.uTime.value=i}},preRender(e,a){if(E.length){_.setFromMatrixPosition(a.matrixWorld),H.copy(D).transformDirection(a.matrixWorld);for(const{sh:p}of E)p.mesh.updateMatrixWorld(),O.copy(p.mesh.matrixWorld).invert(),p.mat.uniforms.uCamLocal.value.copy(_).applyMatrix4(O),p.mat.uniforms.uLightLocal.value.copy(H).transformDirection(O)}for(const{c:p,mesh:T}of g)p.cap.visible&&(T.updateMatrixWorld(),p.cap.updateMatrixWorld(),p.mat.uniforms.uCapToVol.value.copy(T.matrixWorld).invert().multiply(p.cap.matrixWorld));if(!R)return;y();const i=l.getRenderTarget(),t=a.layers.mask,r=e.overrideMaterial,o=l.autoClear,f=e.background;l.setRenderTarget(h),l.setClearColor(0,0),l.clear(!0,!1,!1),a.layers.set(3),e.overrideMaterial=x,e.background=null,l.render(e,a),e.overrideMaterial=r,e.background=f,a.layers.mask=t,l.setRenderTarget(i),l.autoClear=o},dispose(){for(const e of Object.values(n))e.winMesh&&e.mesh.remove(e.winMesh),e.dispose();x.dispose(),h&&h.dispose()}}}Object.assign(window,{buildBloodShells,BLOOD_LAYER:3});
