const _lowestY=i=>i.reduce((c,e)=>e.center[1]<c.center[1]?e:c),_upperPV=i=>c=>c.filter(e=>e.center[0]>0===i).reduce((e,t)=>t.center[1]>e.center[1]?t:e),VESSEL_WINDOWS=[{vessel:"PA",valve:"PV",along:2.3,dir:[.15,.1,1],semi:[1.35,.9,.95],wall_cm:.1,rim:12173270,lumen:1841216},{vessel:"AORTA",valve:"AV",along:3.2,dir:[.3,.1,1],semi:[1.6,1,1.1],wall_cm:.17,rim:14267824,lumen:3803664},{vessel:"AORTA",bound:{side:"outlets",pick:_lowestY},along:4.2,dir:[0,0,-1],semi:[3.6,.8,.9],wall_cm:.15,rim:14267824,lumen:3803664},{vessel:"SVC",bound:{side:"inlets",pick:i=>i[0]},along:1.6,dir:[0,0,1],semi:[1.3,.7,.8],wall_cm:.08,rim:12173270,lumen:1841216},{vessel:"IVC",bound:{side:"inlets",pick:i=>i[0]},along:1.6,dir:[0,0,1],semi:[1.3,.75,.85],wall_cm:.08,rim:12173270,lumen:1841216},{vessel:"PULM_VEINS",bound:{side:"outlets",pick:_upperPV(!0)},along:1.3,dir:[0,0,-1],semi:[1.1,.5,.55],wall_cm:.07,rim:14267824,lumen:3803664},{vessel:"PULM_VEINS",bound:{side:"outlets",pick:_upperPV(!1)},along:1.3,dir:[0,0,-1],semi:[1.1,.5,.55],wall_cm:.07,rim:14267824,lumen:3803664}];function _meshByName(i,c){let e=null;return i.traverse(t=>{!e&&t.isMesh&&t.name===c&&!t.userData.stencilWriter&&(e=t)}),e}function _valveFrameIn(i,c,e){const t=i.userData.parts.valves.userData.valves[c],a=t&&t.children[0];if(!a)return null;const o=new THREE.Matrix4().copy(e.matrix).invert(),r=t.userData.center.clone().applyMatrix4(o),s=new THREE.Vector3().setFromMatrixColumn(a.matrix,2).transformDirection(o).normalize();return{center:r,axis:s}}function _boundFrameIn(i,c,e){const t=i&&i.regions&&i.regions[e.vessel],a=t&&t.bounds&&t.bounds[e.bound.side];if(!a||!a.length)return null;const o=e.bound.pick(a);if(!o||!o.center||!o.axis)return null;const r=new THREE.Matrix4().copy(c.matrix).invert(),s=new THREE.Vector3(...o.center).applyMatrix4(r),l=new THREE.Vector3(...o.axis).transformDirection(r).normalize(),d=c.geometry.attributes.position,p=new THREE.Vector3,b=new THREE.Vector3;let w=0;for(let u=0;u<d.count;u++)p.fromBufferAttribute(d,u),p.distanceTo(s)<2.5&&(b.add(p),w++);return w?(b.divideScalar(w),l.dot(b.sub(s))<0&&l.negate(),{center:s,axis:l,radius:o.radius}):null}function _windowFrame(i,c,e,t,a=null){const o=i.geometry.attributes.position,r=c.clone().addScaledVector(e,t.along),s=new THREE.Vector3(...t.dir),l=s.sub(e.clone().multiplyScalar(s.dot(e))).normalize(),d=new THREE.Vector3;let p=-1/0;const b=a?a*2.5:1/0;for(let g=0;g<o.count;g++){if(d.fromBufferAttribute(o,g).sub(r),Math.abs(d.dot(e))>.35||d.length()>b)continue;const n=d.dot(l);n>p&&(p=n)}if(!Number.isFinite(p))return null;const w=r.clone().addScaledVector(l,p),u=new THREE.Vector3().crossVectors(l,e).normalize();return{origin:w.addScaledVector(l,-.05),e:[e,u,l],radius:p}}const WINDOW_NORMAL_DOT=.6;function _windowField(i,c){const e=i.geometry.attributes.position,t=i.geometry.attributes.normal,a=new Float32Array(e.count).fill(1e3),o=new THREE.Vector3,r=new THREE.Vector3;for(const s of c){const[l,d,p]=s.semi;for(let b=0;b<e.count;b++){o.fromBufferAttribute(e,b).sub(s.origin);const w=o.dot(s.e[0])/l,u=o.dot(s.e[1])/d,g=o.dot(s.e[2])/p;let n=w*w+u*u+g*g-1;t&&(r.fromBufferAttribute(t,b),n=Math.max(n,(WINDOW_NORMAL_DOT-r.dot(s.e[2]))*4)),a[b]=Math.min(a[b],n)}}return a}function _patchVesselMaterial(i,c){const e={uWinOn:{value:0},uLumen:{value:new THREE.Color(c)}};return i.onBeforeCompile=t=>{t.uniforms.uWinOn=e.uWinOn,t.uniforms.uLumen=e.uLumen,t.vertexShader=t.vertexShader.replace("#include <common>",`#include <common>
attribute float winF;
varying float vWinF;`).replace("#include <begin_vertex>",`#include <begin_vertex>
vWinF = winF;`),t.fragmentShader=t.fragmentShader.replace("#include <common>",`#include <common>
uniform float uWinOn;
uniform vec3 uLumen;
varying float vWinF;`).replace("#include <clipping_planes_fragment>",`#include <clipping_planes_fragment>
if (uWinOn > 0.5 && vWinF < 0.0) discard;`).replace("#include <dithering_fragment>",`#include <dithering_fragment>
if (uWinOn > 0.5 && !gl_FrontFacing) gl_FragColor.rgb = mix(uLumen, gl_FragColor.rgb, 0.22);`)},i.customProgramCacheKey=()=>"vessel-window",e}function _patchVesselMaterialTSL(i,c){const e=window.TSL,t=i.material,a=e.uniform(0),o=e.uniform(new THREE.Color(c)),r=e.attribute("winF","float");return t.colorNode=e.Fn(()=>{const s=a.greaterThan(.5);return e.If(s.and(r.lessThan(0)),()=>{e.Discard()}),e.select(s.and(e.frontFacing.not()),e.mix(o,e.materialColor,.22),e.materialColor)})(),t.needsUpdate=!0,{uWinOn:a,uLumen:o}}function _isoLoops(i,c){const e=i.geometry,t=e.attributes.position,a=e.attributes.normal,o=e.index?e.index.array:null,r=e.morphAttributes.position||[],s=o?o.length/3:t.count/3,l=new Map,d=new THREE.Vector3,p=new THREE.Vector3,b=n=>`${Math.round(n.x*1e4)},${Math.round(n.y*1e4)},${Math.round(n.z*1e4)}`,w=(n,f)=>{const m=c[n]/(c[n]-c[f]),E=d.fromBufferAttribute(t,n).clone().lerp(p.fromBufferAttribute(t,f),m),h=b(E);if(!l.has(h)){const v=d.fromBufferAttribute(a,n).clone().lerp(p.fromBufferAttribute(a,f),m).normalize(),T=r.map(x=>d.fromBufferAttribute(x,n).clone().lerp(p.fromBufferAttribute(x,f),m));l.set(h,{p:E,n:v,d:T,nb:[]})}return h};for(let n=0;n<s;n++){const f=o?[o[3*n],o[3*n+1],o[3*n+2]]:[3*n,3*n+1,3*n+2],m=[];for(const[E,h]of[[f[0],f[1]],[f[1],f[2]],[f[2],f[0]]])c[E]<0!=c[h]<0&&m.push(w(E,h));m.length===2&&m[0]!==m[1]&&(l.get(m[0]).nb.push(m[1]),l.get(m[1]).nb.push(m[0]))}const u=[],g=new Set;for(const n of l.keys()){if(g.has(n))continue;const f=[n];g.add(n);for(let m=n,E=null;;){const h=l.get(m).nb.find(v=>v!==E&&!g.has(v));if(!h)break;f.push(h),g.add(h),E=m,m=h}f.length>=6&&u.push(f.map(m=>l.get(m)))}return{loops:u,nMorph:r.length}}function _rimMesh(i,c,e,t,a){const o=[],r=[],s=Array.from({length:e},()=>[]);for(const p of c){const b=o.length/3,w=p.length;for(const u of p)for(const g of[.004,-t])o.push(u.p.x+u.n.x*g,u.p.y+u.n.y*g,u.p.z+u.n.z*g),u.d.forEach((n,f)=>s[f].push(n.x,n.y,n.z));for(let u=0;u<w;u++){const g=(u+1)%w,n=b+2*u,f=n+1,m=b+2*g,E=m+1;r.push(n,m,f,f,m,E)}}const l=new THREE.BufferGeometry;l.setAttribute("position",new THREE.Float32BufferAttribute(o,3)),l.setIndex(r),l.morphAttributes.position=s.map(p=>new THREE.Float32BufferAttribute(p,3)),l.morphTargetsRelative=!0,l.computeVertexNormals();const d=new THREE.Mesh(l,new GFX.M.Standard({color:a,roughness:.6,side:THREE.DoubleSide}));return d.name=i.name+"_window_rim",d.morphTargetDictionary=i.morphTargetDictionary,d.morphTargetInfluences=i.morphTargetInfluences,d.userData.morphFollower=!0,d.castShadow=!1,d.raycast=()=>{},d}function buildFlowViz(i,c=null){i.updateMatrixWorld(!0);const e=[],t={};for(const a of VESSEL_WINDOWS)(t[a.vessel]=t[a.vessel]||[]).push(a);for(const[a,o]of Object.entries(t)){const r=_meshByName(i,a);if(!r)continue;const s=[];for(const n of o){const f=n.valve?_valveFrameIn(i,n.valve,r):_boundFrameIn(c,r,n),m=f&&_windowFrame(r,f.center,f.axis,n,f.radius||null);if(!m){console.warn("[flow-viz] \u5F00\u4E0D\u4E86\u7A97",n.vessel,n.valve||n.bound.side);continue}s.push({...n,...m})}if(!s.length)continue;const l=_windowField(r,s);r.geometry.setAttribute("winF",new THREE.Float32BufferAttribute(l,1));const p=!!(window.GFX&&window.GFX.tsl)?_patchVesselMaterialTSL(r,o[0].lumen):_patchVesselMaterial(r.material,o[0].lumen),b=r.material,{loops:w,nMorph:u}=_isoLoops(r,l),g=_rimMesh(r,w,u,o[0].wall_cm,o[0].rim);g.visible=!1,r.add(g),e.push({mesh:r,original:b,uniforms:p,rim:g,loops:w.length,frames:s.length})}return{windows:e,setMode(a,o){const r=a==="full"&&o;for(const s of e){s.uniforms.uWinOn.value=r?1:0;const l=r?THREE.DoubleSide:THREE.FrontSide;s.original.side!==l&&(s.original.side=l,s.original.needsUpdate=!0),s.rim.visible=r}}}}Object.assign(window,{buildFlowViz,VESSEL_WINDOWS});
