const COR_VERT=`
uniform float uSizeCm, uViewH;
#include <clipping_planes_pars_vertex>
void main(){
  vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
  gl_Position = projectionMatrix * mvPosition;
  float sc = length(vec3(modelViewMatrix[0]));
  gl_PointSize = uSizeCm * sc * projectionMatrix[1][1] * uViewH * 0.5 / max(0.05, -mvPosition.z);
  #include <clipping_planes_vertex>
}`,COR_FRAG=`
uniform vec3 uColor;
uniform vec3 uLight;
#include <clipping_planes_pars_fragment>
void main(){
  #include <clipping_planes_fragment>
  vec2 c = gl_PointCoord * 2.0 - 1.0;
  c.y = -c.y;
  float r2 = dot(c, c);
  if (r2 > 1.0) discard;
  float z = sqrt(1.0 - r2);
  float rim = smoothstep(0.0, 0.5, r2);
  vec3 n = normalize(vec3(c * (0.6 + 0.7 * rim), z * (0.4 + 0.6 * rim)));
  float diff = max(0.0, dot(n, uLight));
  float spec = pow(max(0.0, dot(normalize(uLight + vec3(0.0, 0.0, 1.0)), n)), 28.0);
  vec3 col = uColor * (0.32 + 0.78 * diff) + vec3(spec * 0.22);
  gl_FragColor = vec4(col, 1.0);
  #include <tonemapping_fragment>
  #include <colorspace_fragment>
}`;function keptNodes(m,c){const r=m.r.length,n=new Uint8Array(r);n[0]=1;for(let h=1;h<r;h++){const d=m.parent[h];n[h]=d>=0&&n[d]&&m.r[h]>=c?1:0}return n}function coronaryPointsMaterial(m,c){return window.GFX&&window.GFX.tsl?coronaryPointsMaterialTSL(m,c):new THREE.ShaderMaterial({uniforms:{uSizeCm:{value:c},uViewH:{value:1e3},uColor:{value:new THREE.Color(m)},uLight:{value:new THREE.Vector3(.35,.6,.72).normalize()}},vertexShader:COR_VERT,fragmentShader:COR_FRAG,transparent:!1,depthWrite:!0,clipping:!0})}class CoronaryParticles{constructor(c,r,n,h,d,y,a){this.name=c,this.data=r,this.mesh=n;const o=r.r.length;this.n=o,this.pos=Float32Array.from(r.pos),this.r=Float32Array.from(r.r),this.parent=Int32Array.from(r.parent),this.qfrac=Float32Array.from(r.qfrac),this.deltas={};for(const t of r.keys)this.deltas[t]=Float32Array.from(r.deltas[t]);this.keep=a,this.children=Array.from({length:o},()=>[]);for(let t=1;t<o;t++)this.parent[t]>=0&&a[t]&&this.children[this.parent[t]].push(t);this.leaves=[];for(let t=0;t<o;t++)t>0&&a[t]&&this.children[t].length===0&&this.leaves.push(t);this.edgeLen=new Float32Array(o);const e=new Float32Array(o);let s=0;for(let t=1;t<o;t++){const l=this.parent[t];l<0||!a[t]||(this.edgeLen[t]=Math.hypot(this.pos[3*t]-this.pos[3*l],this.pos[3*t+1]-this.pos[3*l+1],this.pos[3*t+2]-this.pos[3*l+2]),e[t]=Math.PI*this.r[t]*this.r[t]*this.edgeLen[t],s+=e[t])}this.count=h,this.edge=new Int32Array(h),this.f=new Float32Array(h),this.rho=new Float32Array(h),this.theta=new Float32Array(h);for(let t=0;t<h;t++){let l=Math.random()*s,u=1;for(;u<o-1&&(l-=e[u],!(l<=0&&a[u]));u++);a[u]||(u=this.children[0][0]||1),this.edge[t]=u,this.f[t]=Math.random(),this.rho[t]=Math.sqrt(Math.random())*.6,this.theta[t]=Math.random()*Math.PI*2}const i=new THREE.BufferGeometry;this.attr=new THREE.BufferAttribute(new Float32Array(h*3),3),this.attr.setUsage(THREE.DynamicDrawUsage),i.setAttribute("position",this.attr),this.mat=coronaryPointsMaterial(d,y),this.points=new THREE.Points(i,this.mat),this.points.name=c+"_blood",this.points.frustumCulled=!1,this.points.raycast=()=>{},this.points.castShadow=!1,this.points.renderOrder=2,this._tmp={t:new THREE.Vector3,u:new THREE.Vector3,w:new THREE.Vector3,h:new THREE.Vector3(0,1,0)}}_pickChild(c){const r=this.children[c];if(!r.length)return-1;let n=0;for(const d of r)n+=this.qfrac[d];let h=Math.random()*n;for(const d of r)if(h-=this.qfrac[d],h<=0)return d;return r[r.length-1]}_pickLeaf(){let c=0;for(const n of this.leaves)c+=this.r[n]*this.r[n];let r=Math.random()*c;for(const n of this.leaves)if(r-=this.r[n]*this.r[n],r<=0)return n;return this.leaves[this.leaves.length-1]}update(c,r,n,h){const d=this.data.toward_root,y=this.attr.array,a=this._tmp,o=this.data.keys.map(e=>n[e]||0);for(let e=0;e<this.count;e++){let s=this.edge[e],i=this.f[e];if(c>0&&r>0){let v=r*this.qfrac[s]/(Math.PI*this.r[s]*this.r[s])*c/Math.max(this.edgeLen[s],.05);if(d)for(i-=v;i<0;){const f=this.parent[s];if(f<=0){s=this._pickLeaf(),i=1+i;break}i=1+i*this.edgeLen[s]/Math.max(this.edgeLen[f],.05),s=f}else for(i+=v;i>=1;){const f=this._pickChild(s);if(i-=1,f<0){s=this._pickChild(0),s<0&&(s=1),i=Math.random()*i;break}i*=this.edgeLen[s]/Math.max(this.edgeLen[f],.05),s=f}i=Math.min(.999,Math.max(0,i)),this.edge[e]=s,this.f[e]=i}const t=this.parent[s],l=this.pos[3*s],u=this.pos[3*s+1],g=this.pos[3*s+2],p=this.pos[3*t],w=this.pos[3*t+1],_=this.pos[3*t+2];a.t.set(l-p,u-w,g-_).normalize(),a.u.crossVectors(a.t,Math.abs(a.t.y)>.9?a.h.set(1,0,0):a.h.set(0,1,0)).normalize(),a.w.crossVectors(a.t,a.u);const C=this.rho[e]*(this.r[t]*(1-i)+this.r[s]*i),E=Math.cos(this.theta[e])*C,x=Math.sin(this.theta[e])*C;let b=p+(l-p)*i+a.u.x*E+a.w.x*x,A=w+(u-w)*i+a.u.y*E+a.w.y*x,z=_+(g-_)*i+a.u.z*E+a.w.z*x;for(let M=0;M<o.length;M++){const v=o[M];if(!v)continue;const f=this.deltas[this.data.keys[M]];b+=v*(f[3*t]*(1-i)+f[3*s]*i),A+=v*(f[3*t+1]*(1-i)+f[3*s+1]*i),z+=v*(f[3*t+2]*(1-i)+f[3*s+2]*i)}y[3*e]=b,y[3*e+1]=A,y[3*e+2]=z}this.attr.needsUpdate=!0,this.mat.uniforms.uViewH.value=h}dispose(){this.points.geometry.dispose(),this.mat.dispose()}}function buildCoronaryBlood(m,c,r={}){const n={},h=m.coronaries||{},d=r.perCm3||1e3,y=r.minR||0;for(const[o,e]of Object.entries(h)){const s=c.children.find(p=>p.name===o);if(!s)continue;const i=keptNodes(e,y);let t=0,l=0;for(let p=1;p<e.r.length;p++){const w=e.parent[p];w<0||!i[p]||(l++,t+=Math.PI*e.r[p]*e.r[p]*Math.hypot(e.pos[3*p]-e.pos[3*w],e.pos[3*p+1]-e.pos[3*w+1],e.pos[3*p+2]-e.pos[3*w+2]))}if(!l||t<.02)continue;const u=Math.max(100,Math.round(t*d*1.5)),g=new CoronaryParticles(o,e,s,u,e.toward_root?BLOOD.colors.ven:BLOOD.colors.art,r.sizeCm||.07,i);g.points.position.copy(s.position),g.points.quaternion.copy(s.quaternion),g.points.scale.copy(s.scale),c.add(g.points),n[o]=g}const a=o=>{const e={},s=o.morphTargetDictionary,i=o.morphTargetInfluences;if(s&&i)for(const[t,l]of Object.entries(s))e[t]=i[l];return e};return{systems:n,update(o,e,s,i){for(const t of Object.values(n)){if(!t.points.visible)continue;const l=Math.max(0,flowAt(s,t.data.flow,o))*t.data.share;t.update(e,l,a(t.mesh),i)}},setVisible(o){for(const e of Object.values(n))e.points.visible=o},setClipping(o){for(const e of Object.values(n))e.mat.clippingPlanes=o||null,e.mat.needsUpdate=!0},dispose(){for(const o of Object.values(n))o.points.parent&&o.points.parent.remove(o.points),o.dispose()}}}Object.assign(window,{buildCoronaryBlood});
