UNPKG

@niivue/niivue

Version:

minimal webgl2 nifti image viewer

1,043 lines (1,031 loc) 1.96 MB
(function(Ut,at){typeof exports=="object"&&typeof module<"u"?at(exports):typeof define=="function"&&define.amd?define(["exports"],at):(Ut=typeof globalThis<"u"?globalThis:Ut||self,at(Ut.niivue={}))})(this,function(exports){"use strict";var zp=Object.defineProperty;var Qp=(Ut,at,bt)=>at in Ut?zp(Ut,at,{enumerable:!0,configurable:!0,writable:!0,value:bt}):Ut[at]=bt;var d0=(Ut,at,bt)=>Qp(Ut,typeof at!="symbol"?at+"":at,bt);function _mergeNamespaces(C,u){for(var h=0;h<u.length;h++){const g=u[h];if(typeof g!="string"&&!Array.isArray(g)){for(const p in g)if(p!=="default"&&!(p in C)){const w=Object.getOwnPropertyDescriptor(g,p);w&&Object.defineProperty(C,p,w.get?w:{enumerable:!0,get:()=>g[p]})}}}return Object.freeze(Object.defineProperty(C,Symbol.toStringTag,{value:"Module"}))}var EPSILON=1e-6,ARRAY_TYPE=typeof Float32Array<"u"?Float32Array:Array;Math.hypot||(Math.hypot=function(){for(var C=0,u=arguments.length;u--;)C+=arguments[u]*arguments[u];return Math.sqrt(C)});function create$4(){var C=new ARRAY_TYPE(9);return ARRAY_TYPE!=Float32Array&&(C[1]=0,C[2]=0,C[3]=0,C[5]=0,C[6]=0,C[7]=0),C[0]=1,C[4]=1,C[8]=1,C}function fromValues$4(C,u,h,g,p,w,T,E,L){var U=new ARRAY_TYPE(9);return U[0]=C,U[1]=u,U[2]=h,U[3]=g,U[4]=p,U[5]=w,U[6]=T,U[7]=E,U[8]=L,U}function multiply$1(C,u,h){var g=u[0],p=u[1],w=u[2],T=u[3],E=u[4],L=u[5],U=u[6],O=u[7],d=u[8],M=h[0],e=h[1],b=h[2],z=h[3],W=h[4],o0=h[5],J=h[6],K=h[7],y=h[8];return C[0]=M*g+e*T+b*U,C[1]=M*p+e*E+b*O,C[2]=M*w+e*L+b*d,C[3]=z*g+W*T+o0*U,C[4]=z*p+W*E+o0*O,C[5]=z*w+W*L+o0*d,C[6]=J*g+K*T+y*U,C[7]=J*p+K*E+y*O,C[8]=J*w+K*L+y*d,C}function create$3(){var C=new ARRAY_TYPE(16);return ARRAY_TYPE!=Float32Array&&(C[1]=0,C[2]=0,C[3]=0,C[4]=0,C[6]=0,C[7]=0,C[8]=0,C[9]=0,C[11]=0,C[12]=0,C[13]=0,C[14]=0),C[0]=1,C[5]=1,C[10]=1,C[15]=1,C}function clone$2(C){var u=new ARRAY_TYPE(16);return u[0]=C[0],u[1]=C[1],u[2]=C[2],u[3]=C[3],u[4]=C[4],u[5]=C[5],u[6]=C[6],u[7]=C[7],u[8]=C[8],u[9]=C[9],u[10]=C[10],u[11]=C[11],u[12]=C[12],u[13]=C[13],u[14]=C[14],u[15]=C[15],u}function copy$1(C,u){return C[0]=u[0],C[1]=u[1],C[2]=u[2],C[3]=u[3],C[4]=u[4],C[5]=u[5],C[6]=u[6],C[7]=u[7],C[8]=u[8],C[9]=u[9],C[10]=u[10],C[11]=u[11],C[12]=u[12],C[13]=u[13],C[14]=u[14],C[15]=u[15],C}function fromValues$3(C,u,h,g,p,w,T,E,L,U,O,d,M,e,b,z){var W=new ARRAY_TYPE(16);return W[0]=C,W[1]=u,W[2]=h,W[3]=g,W[4]=p,W[5]=w,W[6]=T,W[7]=E,W[8]=L,W[9]=U,W[10]=O,W[11]=d,W[12]=M,W[13]=e,W[14]=b,W[15]=z,W}function identity$1(C){return C[0]=1,C[1]=0,C[2]=0,C[3]=0,C[4]=0,C[5]=1,C[6]=0,C[7]=0,C[8]=0,C[9]=0,C[10]=1,C[11]=0,C[12]=0,C[13]=0,C[14]=0,C[15]=1,C}function transpose(C,u){if(C===u){var h=u[1],g=u[2],p=u[3],w=u[6],T=u[7],E=u[11];C[1]=u[4],C[2]=u[8],C[3]=u[12],C[4]=h,C[6]=u[9],C[7]=u[13],C[8]=g,C[9]=w,C[11]=u[14],C[12]=p,C[13]=T,C[14]=E}else C[0]=u[0],C[1]=u[4],C[2]=u[8],C[3]=u[12],C[4]=u[1],C[5]=u[5],C[6]=u[9],C[7]=u[13],C[8]=u[2],C[9]=u[6],C[10]=u[10],C[11]=u[14],C[12]=u[3],C[13]=u[7],C[14]=u[11],C[15]=u[15];return C}function invert(C,u){var h=u[0],g=u[1],p=u[2],w=u[3],T=u[4],E=u[5],L=u[6],U=u[7],O=u[8],d=u[9],M=u[10],e=u[11],b=u[12],z=u[13],W=u[14],o0=u[15],J=h*E-g*T,K=h*L-p*T,y=h*U-w*T,Z=g*L-p*E,X=g*U-w*E,e0=p*U-w*L,s0=O*z-d*b,j=O*W-M*b,f=O*o0-e*b,g0=d*W-M*z,c0=d*o0-e*z,A0=M*o0-e*W,v0=J*A0-K*c0+y*g0+Z*f-X*j+e0*s0;return v0?(v0=1/v0,C[0]=(E*A0-L*c0+U*g0)*v0,C[1]=(p*c0-g*A0-w*g0)*v0,C[2]=(z*e0-W*X+o0*Z)*v0,C[3]=(M*X-d*e0-e*Z)*v0,C[4]=(L*f-T*A0-U*j)*v0,C[5]=(h*A0-p*f+w*j)*v0,C[6]=(W*y-b*e0-o0*K)*v0,C[7]=(O*e0-M*y+e*K)*v0,C[8]=(T*c0-E*f+U*s0)*v0,C[9]=(g*f-h*c0-w*s0)*v0,C[10]=(b*X-z*y+o0*J)*v0,C[11]=(d*y-O*X-e*J)*v0,C[12]=(E*j-T*g0-L*s0)*v0,C[13]=(h*g0-g*j+p*s0)*v0,C[14]=(z*K-b*Z-W*J)*v0,C[15]=(O*Z-d*K+M*J)*v0,C):null}function multiply(C,u,h){var g=u[0],p=u[1],w=u[2],T=u[3],E=u[4],L=u[5],U=u[6],O=u[7],d=u[8],M=u[9],e=u[10],b=u[11],z=u[12],W=u[13],o0=u[14],J=u[15],K=h[0],y=h[1],Z=h[2],X=h[3];return C[0]=K*g+y*E+Z*d+X*z,C[1]=K*p+y*L+Z*M+X*W,C[2]=K*w+y*U+Z*e+X*o0,C[3]=K*T+y*O+Z*b+X*J,K=h[4],y=h[5],Z=h[6],X=h[7],C[4]=K*g+y*E+Z*d+X*z,C[5]=K*p+y*L+Z*M+X*W,C[6]=K*w+y*U+Z*e+X*o0,C[7]=K*T+y*O+Z*b+X*J,K=h[8],y=h[9],Z=h[10],X=h[11],C[8]=K*g+y*E+Z*d+X*z,C[9]=K*p+y*L+Z*M+X*W,C[10]=K*w+y*U+Z*e+X*o0,C[11]=K*T+y*O+Z*b+X*J,K=h[12],y=h[13],Z=h[14],X=h[15],C[12]=K*g+y*E+Z*d+X*z,C[13]=K*p+y*L+Z*M+X*W,C[14]=K*w+y*U+Z*e+X*o0,C[15]=K*T+y*O+Z*b+X*J,C}function translate(C,u,h){var g=h[0],p=h[1],w=h[2],T,E,L,U,O,d,M,e,b,z,W,o0;return u===C?(C[12]=u[0]*g+u[4]*p+u[8]*w+u[12],C[13]=u[1]*g+u[5]*p+u[9]*w+u[13],C[14]=u[2]*g+u[6]*p+u[10]*w+u[14],C[15]=u[3]*g+u[7]*p+u[11]*w+u[15]):(T=u[0],E=u[1],L=u[2],U=u[3],O=u[4],d=u[5],M=u[6],e=u[7],b=u[8],z=u[9],W=u[10],o0=u[11],C[0]=T,C[1]=E,C[2]=L,C[3]=U,C[4]=O,C[5]=d,C[6]=M,C[7]=e,C[8]=b,C[9]=z,C[10]=W,C[11]=o0,C[12]=T*g+O*p+b*w+u[12],C[13]=E*g+d*p+z*w+u[13],C[14]=L*g+M*p+W*w+u[14],C[15]=U*g+e*p+o0*w+u[15]),C}function scale$3(C,u,h){var g=h[0],p=h[1],w=h[2];return C[0]=u[0]*g,C[1]=u[1]*g,C[2]=u[2]*g,C[3]=u[3]*g,C[4]=u[4]*p,C[5]=u[5]*p,C[6]=u[6]*p,C[7]=u[7]*p,C[8]=u[8]*w,C[9]=u[9]*w,C[10]=u[10]*w,C[11]=u[11]*w,C[12]=u[12],C[13]=u[13],C[14]=u[14],C[15]=u[15],C}function rotateX(C,u,h){var g=Math.sin(h),p=Math.cos(h),w=u[4],T=u[5],E=u[6],L=u[7],U=u[8],O=u[9],d=u[10],M=u[11];return u!==C&&(C[0]=u[0],C[1]=u[1],C[2]=u[2],C[3]=u[3],C[12]=u[12],C[13]=u[13],C[14]=u[14],C[15]=u[15]),C[4]=w*p+U*g,C[5]=T*p+O*g,C[6]=E*p+d*g,C[7]=L*p+M*g,C[8]=U*p-w*g,C[9]=O*p-T*g,C[10]=d*p-E*g,C[11]=M*p-L*g,C}function rotateZ(C,u,h){var g=Math.sin(h),p=Math.cos(h),w=u[0],T=u[1],E=u[2],L=u[3],U=u[4],O=u[5],d=u[6],M=u[7];return u!==C&&(C[8]=u[8],C[9]=u[9],C[10]=u[10],C[11]=u[11],C[12]=u[12],C[13]=u[13],C[14]=u[14],C[15]=u[15]),C[0]=w*p+U*g,C[1]=T*p+O*g,C[2]=E*p+d*g,C[3]=L*p+M*g,C[4]=U*p-w*g,C[5]=O*p-T*g,C[6]=d*p-E*g,C[7]=M*p-L*g,C}function fromRotation(C,u,h){var g=h[0],p=h[1],w=h[2],T=Math.hypot(g,p,w),E,L,U;return T<EPSILON?null:(T=1/T,g*=T,p*=T,w*=T,E=Math.sin(u),L=Math.cos(u),U=1-L,C[0]=g*g*U+L,C[1]=p*g*U+w*E,C[2]=w*g*U-p*E,C[3]=0,C[4]=g*p*U-w*E,C[5]=p*p*U+L,C[6]=w*p*U+g*E,C[7]=0,C[8]=g*w*U+p*E,C[9]=p*w*U-g*E,C[10]=w*w*U+L,C[11]=0,C[12]=0,C[13]=0,C[14]=0,C[15]=1,C)}function orthoNO(C,u,h,g,p,w,T){var E=1/(u-h),L=1/(g-p),U=1/(w-T);return C[0]=-2*E,C[1]=0,C[2]=0,C[3]=0,C[4]=0,C[5]=-2*L,C[6]=0,C[7]=0,C[8]=0,C[9]=0,C[10]=2*U,C[11]=0,C[12]=(u+h)*E,C[13]=(p+g)*L,C[14]=(T+w)*U,C[15]=1,C}var ortho=orthoNO;function multiplyScalar(C,u,h){return C[0]=u[0]*h,C[1]=u[1]*h,C[2]=u[2]*h,C[3]=u[3]*h,C[4]=u[4]*h,C[5]=u[5]*h,C[6]=u[6]*h,C[7]=u[7]*h,C[8]=u[8]*h,C[9]=u[9]*h,C[10]=u[10]*h,C[11]=u[11]*h,C[12]=u[12]*h,C[13]=u[13]*h,C[14]=u[14]*h,C[15]=u[15]*h,C}var mul=multiply;function create$2(){var C=new ARRAY_TYPE(3);return ARRAY_TYPE!=Float32Array&&(C[0]=0,C[1]=0,C[2]=0),C}function clone$1(C){var u=new ARRAY_TYPE(3);return u[0]=C[0],u[1]=C[1],u[2]=C[2],u}function length$1(C){var u=C[0],h=C[1],g=C[2];return Math.hypot(u,h,g)}function fromValues$2(C,u,h){var g=new ARRAY_TYPE(3);return g[0]=C,g[1]=u,g[2]=h,g}function copy(C,u){return C[0]=u[0],C[1]=u[1],C[2]=u[2],C}function add$1(C,u,h){return C[0]=u[0]+h[0],C[1]=u[1]+h[1],C[2]=u[2]+h[2],C}function subtract$1(C,u,h){return C[0]=u[0]-h[0],C[1]=u[1]-h[1],C[2]=u[2]-h[2],C}function min$j(C,u,h){return C[0]=Math.min(u[0],h[0]),C[1]=Math.min(u[1],h[1]),C[2]=Math.min(u[2],h[2]),C}function max$l(C,u,h){return C[0]=Math.max(u[0],h[0]),C[1]=Math.max(u[1],h[1]),C[2]=Math.max(u[2],h[2]),C}function scale$2(C,u,h){return C[0]=u[0]*h,C[1]=u[1]*h,C[2]=u[2]*h,C}function negate(C,u){return C[0]=-u[0],C[1]=-u[1],C[2]=-u[2],C}function normalize$1(C,u){var h=u[0],g=u[1],p=u[2],w=h*h+g*g+p*p;return w>0&&(w=1/Math.sqrt(w)),C[0]=u[0]*w,C[1]=u[1]*w,C[2]=u[2]*w,C}function dot(C,u){return C[0]*u[0]+C[1]*u[1]+C[2]*u[2]}function cross(C,u,h){var g=u[0],p=u[1],w=u[2],T=h[0],E=h[1],L=h[2];return C[0]=p*L-w*E,C[1]=w*T-g*L,C[2]=g*E-p*T,C}function lerp(C,u,h,g){var p=u[0],w=u[1],T=u[2];return C[0]=p+g*(h[0]-p),C[1]=w+g*(h[1]-w),C[2]=T+g*(h[2]-T),C}function angle(C,u){var h=C[0],g=C[1],p=C[2],w=u[0],T=u[1],E=u[2],L=Math.sqrt(h*h+g*g+p*p),U=Math.sqrt(w*w+T*T+E*E),O=L*U,d=O&&dot(C,u)/O;return Math.acos(Math.min(Math.max(d,-1),1))}var sub$1=subtract$1,len=length$1;(function(){var C=create$2();return function(u,h,g,p,w,T){var E,L;for(h||(h=3),g||(g=0),p?L=Math.min(p*h+g,u.length):L=u.length,E=g;E<L;E+=h)C[0]=u[E],C[1]=u[E+1],C[2]=u[E+2],w(C,C,T),u[E]=C[0],u[E+1]=C[1],u[E+2]=C[2];return u}})();function create$1(){var C=new ARRAY_TYPE(4);return ARRAY_TYPE!=Float32Array&&(C[0]=0,C[1]=0,C[2]=0,C[3]=0),C}function clone(C){var u=new ARRAY_TYPE(4);return u[0]=C[0],u[1]=C[1],u[2]=C[2],u[3]=C[3],u}function fromValues$1(C,u,h,g){var p=new ARRAY_TYPE(4);return p[0]=C,p[1]=u,p[2]=h,p[3]=g,p}function add(C,u,h){return C[0]=u[0]+h[0],C[1]=u[1]+h[1],C[2]=u[2]+h[2],C[3]=u[3]+h[3],C}function subtract(C,u,h){return C[0]=u[0]-h[0],C[1]=u[1]-h[1],C[2]=u[2]-h[2],C[3]=u[3]-h[3],C}function scale$1(C,u,h){return C[0]=u[0]*h,C[1]=u[1]*h,C[2]=u[2]*h,C[3]=u[3]*h,C}function transformMat4(C,u,h){var g=u[0],p=u[1],w=u[2],T=u[3];return C[0]=h[0]*g+h[4]*p+h[8]*w+h[12]*T,C[1]=h[1]*g+h[5]*p+h[9]*w+h[13]*T,C[2]=h[2]*g+h[6]*p+h[10]*w+h[14]*T,C[3]=h[3]*g+h[7]*p+h[11]*w+h[15]*T,C}var sub=subtract;(function(){var C=create$1();return function(u,h,g,p,w,T){var E,L;for(h||(h=4),g||(g=0),p?L=Math.min(p*h+g,u.length):L=u.length,E=g;E<L;E+=h)C[0]=u[E],C[1]=u[E+1],C[2]=u[E+2],C[3]=u[E+3],w(C,C,T),u[E]=C[0],u[E+1]=C[1],u[E+2]=C[2],u[E+3]=C[3];return u}})();function create(){var C=new ARRAY_TYPE(2);return ARRAY_TYPE!=Float32Array&&(C[0]=0,C[1]=0),C}function fromValues(C,u){var h=new ARRAY_TYPE(2);return h[0]=C,h[1]=u,h}function scale(C,u,h){return C[0]=u[0]*h,C[1]=u[1]*h,C}function length(C){var u=C[0],h=C[1];return Math.hypot(u,h)}function normalize(C,u){var h=u[0],g=u[1],p=h*h+g*g;return p>0&&(p=1/Math.sqrt(p)),C[0]=u[0]*p,C[1]=u[1]*p,C}(function(){var C=create();return function(u,h,g,p,w,T){var E,L;for(h||(h=2),g||(g=0),p?L=Math.min(p*h+g,u.length):L=u.length,E=g;E<L;E+=h)C[0]=u[E],C[1]=u[E+1],w(C,C,T),u[E]=C[0],u[E+1]=C[1];return u}})();const version="0.45.1",Ut=class Ut{constructor({name:u="niivue",level:h="info"}={}){d0(this,"level");d0(this,"name");this.name=`${u}`,this.level=h}debug(...u){Ut.levels[this.level]>Ut.levels.debug||console.debug(`${this.name}-debug`,...u)}info(...u){Ut.levels[this.level]>Ut.levels.info||console.info(`${this.name}-info`,...u)}warn(...u){Ut.levels[this.level]>Ut.levels.warn||console.warn(`${this.name}-warn`,...u)}error(...u){Ut.levels[this.level]>Ut.levels.error||console.error(`${this.name}-error`,...u)}fatal(...u){Ut.levels[this.level]>Ut.levels.fatal||console.error(`${this.name}-fatal`,...u)}setLogLevel(u){this.level=u}setName(u){this.name=u}};d0(Ut,"levels",{debug:0,info:1,warn:2,error:3,fatal:4,silent:1/0});let Log=Ut;const log=new Log({name:"niivue",level:"info"}),compileShader=function(C,u,h){const g=C.createShader(C.VERTEX_SHADER);C.shaderSource(g,u),C.compileShader(g);const p=C.createShader(C.FRAGMENT_SHADER);C.shaderSource(p,h),C.compileShader(p);const w=C.createProgram();if(C.attachShader(w,g),C.attachShader(w,p),C.linkProgram(w),!C.getProgramParameter(w,C.LINK_STATUS))throw log.error(C.getProgramInfoLog(w)),new Error("Shader failed to link, see console for log");return w};class Shader{constructor(u,h,g){d0(this,"program");d0(this,"uniforms",{});d0(this,"isMatcap");this.program=compileShader(u,h,g);const p=/uniform[^;]+[ ](\w+);/g,w=/uniform[^;]+[ ](\w+);/,T=h.match(p),E=g.match(p);T&&T.forEach(L=>{const U=L.match(w);this.uniforms[U[1]]=-1}),E&&E.forEach(L=>{const U=L.match(w);this.uniforms[U[1]]=-1});for(const L in this.uniforms)this.uniforms[L]=u.getUniformLocation(this.program,L)}use(u){u.useProgram(this.program)}}const vertRenderShader=`#version 300 es #line 4 layout(location=0) in vec3 pos; layout(location=1) in vec3 texCoords; uniform mat4 mvpMtx; out vec3 vColor; void main(void) { gl_Position = mvpMtx * vec4(pos, 1.0); vColor = texCoords; }`,kDrawFunc=` vec4 drawColor(float scalar, float drawOpacity) { float nlayer = float(textureSize(colormap, 0).y); float layer = (nlayer - 0.5) / nlayer; vec4 dcolor = texture(colormap, vec2((scalar * 255.0)/256.0 + 0.5/256.0, layer)).rgba; dcolor.a *= drawOpacity; return dcolor; }`,kRenderFunc=`vec3 GetBackPosition(vec3 startPositionTex) { vec3 startPosition = startPositionTex * volScale; vec3 invR = 1.0 / rayDir; vec3 tbot = invR * (vec3(0.0)-startPosition); vec3 ttop = invR * (volScale-startPosition); vec3 tmax = max(ttop, tbot); vec2 t = min(tmax.xx, tmax.yz); vec3 endPosition = startPosition + (rayDir * min(t.x, t.y)); //convert world position back to texture position: endPosition = endPosition / volScale; return endPosition; } vec4 applyClip (vec3 dir, inout vec4 samplePos, inout float len, inout bool isClip) { float cdot = dot(dir,clipPlane.xyz); isClip = false; if ((clipPlane.a > 1.0) || (cdot == 0.0)) return samplePos; bool frontface = (cdot > 0.0); float dis = (-clipPlane.a - dot(clipPlane.xyz, samplePos.xyz-0.5)) / cdot; float thick = clipThick; if (thick <= 0.0) thick = 2.0; float disBackFace = (-(clipPlane.a-thick) - dot(clipPlane.xyz, samplePos.xyz-0.5)) / cdot; if (((frontface) && (dis >= len)) || ((!frontface) && (dis <= 0.0))) { samplePos.a = len + 1.0; return samplePos; } if (frontface) { dis = max(0.0, dis); samplePos = vec4(samplePos.xyz+dir * dis, dis); if (dis > 0.0) isClip = true; len = min(disBackFace, len); } if (!frontface) { len = min(dis, len); disBackFace = max(0.0, disBackFace); if (len == dis) isClip = true; samplePos = vec4(samplePos.xyz+dir * disBackFace, disBackFace); } return samplePos; } void clipVolume(inout vec3 startPos, inout vec3 backPos, int dim, float frac, bool isLo) { vec3 dir = backPos - startPos; float len = length(dir); dir = normalize(dir); // Discard if both startPos and backPos are outside the clipping plane if (isLo && startPos[dim] < frac && backPos[dim] < frac) { discard; } if (!isLo && startPos[dim] > frac && backPos[dim] > frac) { discard; } vec4 plane = vec4(0.0, 0.0, 0.0, 0.5 - frac); plane[dim] = 1.0; float cdot = dot(dir, plane.xyz); float dis = (-plane.w - dot(plane.xyz, startPos - vec3(0.5))) / cdot; // Adjust startPos or backPos based on the intersection with the plane bool isFrontFace = (cdot > 0.0); if (!isLo) isFrontFace = !isFrontFace; if (dis > 0.0) { if (isFrontFace) { if (dis <= len) { startPos = startPos + dir * dis; } } else { if (dis < len) { backPos = startPos + dir * dis; } } } } void clipVolumeStart (inout vec3 startPos, inout vec3 backPos) { // vec3 clipLo = vec3(0.1, 0.2, 0.4); // vec3 clipHi = vec3(0.8, 0.7, 0.7); for (int i = 0; i < 3; i++) { if (clipLo[i] > 0.0) clipVolume(startPos, backPos, i, clipLo[i], true); } for (int i = 0; i < 3; i++) { if (clipHi[i] < 1.0) clipVolume(startPos, backPos, i, clipHi[i], false); } } float frac2ndc(vec3 frac) { //https://stackoverflow.com/questions/7777913/how-to-render-depth-linearly-in-modern-opengl-with-gl-fragcoord-z-in-fragment-sh vec4 pos = vec4(frac.xyz, 1.0); //fraction vec4 dim = vec4(vec3(textureSize(volume, 0)), 1.0); pos = pos * dim; vec4 shim = vec4(-0.5, -0.5, -0.5, 0.0); pos += shim; vec4 mm = transpose(matRAS) * pos; float z_ndc = (mvpMtx * vec4(mm.xyz, 1.0)).z; return (z_ndc + 1.0) / 2.0; }`+kDrawFunc,kRenderInit=`void main() { if (fColor.x > 2.0) { fColor = vec4(1.0, 0.0, 0.0, 0.5); return; } fColor = vec4(0.0,0.0,0.0,0.0); vec4 clipPlaneColorX = clipPlaneColor; //if (clipPlaneColor.a < 0.0) // clipPlaneColorX.a = - 1.0; bool isColorPlaneInVolume = false; if (clipPlaneColorX.a < 0.0) { isColorPlaneInVolume = true; clipPlaneColorX.a = 0.0; } //fColor = vec4(vColor.rgb, 1.0); return; vec3 start = vColor; gl_FragDepth = 0.0; vec3 backPosition = GetBackPosition(start); // fColor = vec4(backPosition, 1.0); return; vec3 dir = normalize(backPosition - start); clipVolumeStart(start, backPosition); dir = normalize(dir); float len = length(backPosition - start); float lenVox = length((texVox * start) - (texVox * backPosition)); if ((lenVox < 0.5) || (len > 3.0)) { //length limit for parallel rays return; } float sliceSize = len / lenVox; //e.g. if ray length is 1.0 and traverses 50 voxels, each voxel is 0.02 in unit cube float stepSize = sliceSize; //quality: larger step is faster traversal, but fewer samples float opacityCorrection = stepSize/sliceSize; vec4 deltaDir = vec4(dir.xyz * stepSize, stepSize); vec4 samplePos = vec4(start.xyz, 0.0); //ray position float lenNoClip = len; bool isClip = false; vec4 clipPos = applyClip(dir, samplePos, len, isClip); //if ((clipPos.a != samplePos.a) && (len < 3.0)) { //start: OPTIONAL fast pass: rapid traversal until first hit float stepSizeFast = sliceSize * 1.9; vec4 deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast); while (samplePos.a <= len) { float val = texture(volume, samplePos.xyz).a; if (val > 0.01) break; samplePos += deltaDirFast; //advance ray position } float drawOpacityA = renderDrawAmbientOcclusionXY.y; if ((samplePos.a >= len) && (((overlays < 1.0) && (drawOpacityA <= 0.0) ) || (backgroundMasksOverlays > 0))) { if (isClip) fColor += clipPlaneColorX; return; } fColor = vec4(1.0, 1.0, 1.0, 1.0); //gl_FragDepth = frac2ndc(samplePos.xyz); //crude due to fast pass resolution samplePos -= deltaDirFast; if (samplePos.a < 0.0) vec4 samplePos = vec4(start.xyz, 0.0); //ray position //end: fast pass vec4 colAcc = vec4(0.0,0.0,0.0,0.0); vec4 firstHit = vec4(0.0,0.0,0.0,2.0 * lenNoClip); const float earlyTermination = 0.95; float backNearest = len; //assume no hit float ran = fract(sin(gl_FragCoord.x * 12.9898 + gl_FragCoord.y * 78.233) * 43758.5453); samplePos += deltaDir * ran; //jitter ray `,kRenderTail=` if (firstHit.a < len) gl_FragDepth = frac2ndc(firstHit.xyz); colAcc.a = (colAcc.a / earlyTermination) * backOpacity; fColor = colAcc; //if (isClip) //CR if ((isColorPlaneInVolume) && (clipPos.a != samplePos.a) && (abs(firstHit.a - clipPos.a) < deltaDir.a)) fColor.rgb = mix(fColor.rgb, clipPlaneColorX.rgb, abs(clipPlaneColor.a)); //fColor.rgb = mix(fColor.rgb, clipPlaneColorX.rgb, clipPlaneColorX.a * 0.65); float renderDrawAmbientOcclusionX = renderDrawAmbientOcclusionXY.x; float drawOpacity = renderDrawAmbientOcclusionXY.y; if ((overlays < 1.0) && (drawOpacity <= 0.0)) return; //overlay pass len = lenNoClip; samplePos = vec4(start.xyz, 0.0); //ray position //start: OPTIONAL fast pass: rapid traversal until first hit stepSizeFast = sliceSize * 1.0; deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast); while (samplePos.a <= len) { float val = texture(overlay, samplePos.xyz).a; if (drawOpacity > 0.0) val = max(val, texture(drawing, samplePos.xyz).r); if (val > 0.001) break; samplePos += deltaDirFast; //advance ray position } if (samplePos.a >= len) { if (isClip && (fColor.a == 0.0)) fColor += clipPlaneColorX; return; } samplePos -= deltaDirFast; if (samplePos.a < 0.0) vec4 samplePos = vec4(start.xyz, 0.0); //ray position //end: fast pass float overFarthest = len; colAcc = vec4(0.0, 0.0, 0.0, 0.0); samplePos += deltaDir * ran; //jitter ray vec4 overFirstHit = vec4(0.0,0.0,0.0,2.0 * len); if (backgroundMasksOverlays > 0) samplePos = firstHit; bool firstDraw = true; while (samplePos.a <= len) { vec4 colorSample = texture(overlay, samplePos.xyz); if ((colorSample.a < 0.01) && (drawOpacity > 0.0)) { float val = texture(drawing, samplePos.xyz).r; vec4 draw = drawColor(val, drawOpacity); if ((draw.a > 0.0) && (firstDraw)) { firstDraw = false; float sum = 0.0; const float mn = 1.0 / 256.0; const float sampleRadius = 1.1; float dx = sliceSize * sampleRadius; vec3 center = samplePos.xyz; //six neighbors that share a face sum += min(texture(drawing, center.xyz + cross(vec3(0.0,0.0,+dx), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(0.0,0.0,-dx), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(0.0,+dx,0.0), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(0.0,-dx,0.0), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(+dx,0.0,0.0), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(-dx,0.0,0.0), dir)).r, mn); //float proportion = (sum / mn) / 6.0; //12 neighbors that share an edge dx = sliceSize * sampleRadius * sqrt(2.0) * 0.5; sum += min(texture(drawing, center.xyz + cross(vec3(0.0,+dx,+dx), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(+dx,0.0,+dx), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(+dx,+dx,0.0), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(0.0,-dx,-dx), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(-dx,0.0,-dx), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(-dx,-dx,0.0), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(0.0,+dx,-dx), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(+dx,0.0,-dx), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(+dx,-dx,0.0), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(0.0,-dx,+dx), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(-dx,0.0,+dx), dir)).r, mn); sum += min(texture(drawing, center.xyz + cross(vec3(-dx,+dx,0.0), dir)).r, mn); float proportion = (sum / mn) / 18.0; //proportion of six neighbors is non-zero //a high proportion of hits means crevice //since the AO term adds shadows that darken most voxels, it will result in dark surfaces //the term brighten adds a little illumination to balance this // without brighten, only the most extreme ridges will not be darker const float brighten = 1.2; vec3 ao = draw.rgb * (1.0 - proportion) * brighten; draw.rgb = mix (draw.rgb, ao , renderDrawAmbientOcclusionX); } colorSample = draw; } samplePos += deltaDir; //advance ray position if (colorSample.a >= 0.01) { if (overFirstHit.a > len) overFirstHit = samplePos; colorSample.a *= renderOverlayBlend; colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection); colorSample.rgb *= colorSample.a; colAcc= (1.0 - colAcc.a) * colorSample + colAcc; overFarthest = samplePos.a; if ( colAcc.a > earlyTermination ) break; } } //if (samplePos.a >= len) { if (colAcc.a <= 0.0) { if (isClip && (fColor.a == 0.0)) fColor += clipPlaneColorX; return; } if (overFirstHit.a < firstHit.a) gl_FragDepth = frac2ndc(overFirstHit.xyz); float overMix = colAcc.a; float overlayDepth = 0.3; if (fColor.a <= 0.0) overMix = 1.0; else if (((overFarthest) > backNearest)) { float dx = (overFarthest - backNearest)/1.73; dx = fColor.a * pow(dx, overlayDepth); overMix *= 1.0 - dx; } fColor.rgb = mix(fColor.rgb, colAcc.rgb, overMix); fColor.a = max(fColor.a, colAcc.a); }`,fragRenderSliceShader=`#version 300 es #line 215 precision highp int; precision highp float; uniform vec3 rayDir; uniform vec3 texVox; uniform int backgroundMasksOverlays; uniform vec3 volScale; uniform vec4 clipPlane; uniform highp sampler3D volume, overlay; uniform float overlays; uniform float clipThick; uniform vec3 clipLo; uniform vec3 clipHi; uniform float backOpacity; uniform mat4 mvpMtx; uniform mat4 matRAS; uniform vec4 clipPlaneColor; uniform float renderOverlayBlend; uniform highp sampler3D drawing; uniform highp sampler2D colormap; uniform vec2 renderDrawAmbientOcclusionXY; in vec3 vColor; out vec4 fColor; `+kRenderFunc+` void main() { vec3 start = vColor; gl_FragDepth = 0.0; vec3 backPosition = GetBackPosition(start); vec3 dir = normalize(backPosition - start); clipVolumeStart(start, backPosition); float len = length(backPosition - start); float lenVox = length((texVox * start) - (texVox * backPosition)); if ((lenVox < 0.5) || (len > 3.0)) { //length limit for parallel rays fColor = vec4(0.0,0.0,0.0,0.0); return; } float sliceSize = len / lenVox; //e.g. if ray length is 1.0 and traverses 50 voxels, each voxel is 0.02 in unit cube float stepSize = sliceSize; //quality: larger step is faster traversal, but fewer samples float opacityCorrection = stepSize/sliceSize; vec4 deltaDir = vec4(dir.xyz * stepSize, stepSize); vec4 samplePos = vec4(start.xyz, 0.0); //ray position vec4 colAcc = vec4(0.0,0.0,0.0,0.0); vec4 firstHit = vec4(0.0,0.0,0.0,2.0 * len); const float earlyTermination = 0.95; float backNearest = len; //assume no hit float dis = len; //check if axial plane is closest vec4 aClip = vec4(0.0, 0.0, 1.0, (1.0- clipPlane.z) - 0.5); float adis = (-aClip.a - dot(aClip.xyz, samplePos.xyz-0.5)) / dot(dir,aClip.xyz); if (adis > 0.0) dis = min(adis, dis); //check of coronal plane is closest vec4 cClip = vec4(0.0, 1.0, 0.0, (1.0- clipPlane.y) - 0.5); float cdis = (-cClip.a - dot(cClip.xyz, samplePos.xyz-0.5)) / dot(dir,cClip.xyz); if (cdis > 0.0) dis = min(cdis, dis); //check if coronal slice is closest vec4 sClip = vec4(1.0, 0.0, 0.0, (1.0- clipPlane.x) - 0.5); float sdis = (-sClip.a - dot(sClip.xyz, samplePos.xyz-0.5)) / dot(dir,sClip.xyz); if (sdis > 0.0) dis = min(sdis, dis); if ((dis > 0.0) && (dis < len)) { samplePos = vec4(samplePos.xyz+dir * dis, dis); colAcc = texture(volume, samplePos.xyz); colAcc.a = earlyTermination; firstHit = samplePos; backNearest = min(backNearest, samplePos.a); } //the following are only used by overlays vec4 clipPlaneColorX = clipPlaneColor; bool isColorPlaneInVolume = false; float lenNoClip = len; bool isClip = false; vec4 clipPos = applyClip(dir, samplePos, len, isClip); float stepSizeFast = sliceSize * 1.9; vec4 deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast); if (samplePos.a < 0.0) vec4 samplePos = vec4(start.xyz, 0.0); //ray position float ran = fract(sin(gl_FragCoord.x * 12.9898 + gl_FragCoord.y * 78.233) * 43758.5453); samplePos += deltaDir * ran; //jitter ray `+kRenderTail,fragRenderShader=`#version 300 es #line 215 precision highp int; precision highp float; uniform vec3 rayDir; uniform vec3 texVox; uniform int backgroundMasksOverlays; uniform vec3 volScale; uniform vec4 clipPlane; uniform highp sampler3D volume, overlay; uniform float overlays; uniform float clipThick; uniform vec3 clipLo; uniform vec3 clipHi; uniform float backOpacity; uniform mat4 mvpMtx; uniform mat4 matRAS; uniform vec4 clipPlaneColor; uniform float renderOverlayBlend; uniform highp sampler3D drawing; uniform highp sampler2D colormap; uniform vec2 renderDrawAmbientOcclusionXY; in vec3 vColor; out vec4 fColor; `+kRenderFunc+kRenderInit+`while (samplePos.a <= len) { vec4 colorSample = texture(volume, samplePos.xyz); samplePos += deltaDir; //advance ray position if (colorSample.a >= 0.01) { if (firstHit.a > lenNoClip) firstHit = samplePos; backNearest = min(backNearest, samplePos.a); colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection); colorSample.rgb *= colorSample.a; colAcc= (1.0 - colAcc.a) * colorSample + colAcc; if ( colAcc.a > earlyTermination ) break; } } `+kRenderTail,fragRenderGradientShader=`#version 300 es #line 215 precision highp int; precision highp float; uniform vec3 rayDir; uniform vec3 texVox; uniform int backgroundMasksOverlays; uniform vec3 volScale; uniform vec4 clipPlane; uniform highp sampler3D volume, overlay; uniform float overlays; uniform float clipThick; uniform vec3 clipLo; uniform vec3 clipHi; uniform float backOpacity; uniform mat4 mvpMtx; uniform mat4 normMtx; uniform mat4 matRAS; uniform vec4 clipPlaneColor; uniform float renderOverlayBlend; uniform highp sampler3D drawing, gradient; uniform highp sampler2D colormap; uniform highp sampler2D matCap; uniform vec2 renderDrawAmbientOcclusionXY; uniform float gradientAmount; in vec3 vColor; out vec4 fColor; `+kRenderFunc+kRenderInit+` float startPos = samplePos.a; float clipClose = clipPos.a + 3.0 * deltaDir.a; //do not apply gradients near clip plane float brighten = 2.0; //modulating makes average intensity darker 0.5 * 0.5 = 0.25 //vec4 prevGrad = vec4(0.0); while (samplePos.a <= len) { vec4 colorSample = texture(volume, samplePos.xyz); if (colorSample.a >= 0.0) { vec4 grad = texture(gradient, samplePos.xyz); grad.rgb = normalize(grad.rgb*2.0 - 1.0); //if (grad.a < prevGrad.a) // grad.rgb = prevGrad.rgb; //prevGrad = grad; vec3 n = mat3(normMtx) * grad.rgb; n.y = - n.y; vec4 mc = vec4(texture(matCap, n.xy * 0.5 + 0.5).rgb, 1.0) * brighten; mc = mix(vec4(1.0), mc, gradientAmount); if (samplePos.a > clipClose) colorSample.rgb *= mc.rgb; if (firstHit.a > lenNoClip) firstHit = samplePos; backNearest = min(backNearest, samplePos.a); colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection); colorSample.rgb *= colorSample.a; colAcc= (1.0 - colAcc.a) * colorSample + colAcc; if ( colAcc.a > earlyTermination ) break; } samplePos += deltaDir; //advance ray position } `+kRenderTail,vertSliceMMShader=`#version 300 es #line 392 layout(location=0) in vec3 pos; uniform int axCorSag; uniform mat4 mvpMtx; uniform mat4 frac2mm; uniform float slice; out vec3 texPos; void main(void) { texPos = vec3(pos.x, pos.y, slice); if (axCorSag > 1) texPos = vec3(slice, pos.x, pos.y); else if (axCorSag > 0) texPos = vec3(pos.x, slice, pos.y); vec4 mm = frac2mm * vec4(texPos, 1.0); gl_Position = mvpMtx * mm; }`,kFragSliceHead=`#version 300 es #line 411 precision highp int; precision highp float; uniform highp sampler3D volume, overlay; uniform int backgroundMasksOverlays; uniform float overlayOutlineWidth; uniform float overlayAlphaShader; uniform int axCorSag; uniform float overlays; uniform float opacity; uniform float drawOpacity; uniform bool isAlphaClipDark; uniform highp sampler3D drawing; uniform highp sampler2D colormap; in vec3 texPos; out vec4 color;`+kDrawFunc+`void main() { //color = vec4(1.0, 0.0, 1.0, 1.0);return; vec4 background = texture(volume, texPos); color = vec4(background.rgb, opacity); if ((isAlphaClipDark) && (background.a == 0.0)) color.a = 0.0; //FSLeyes clipping range vec4 ocolor = vec4(0.0); float overlayAlpha = overlayAlphaShader; if (overlays > 0.0) { ocolor = texture(overlay, texPos); //dFdx for "boxing" issue 435 has aliasing on some implementations (coarse vs fine) //however, this only identifies 50% of the edges due to aliasing effects // http://www.aclockworkberry.com/shader-derivative-functions/ // https://bgolus.medium.com/distinctive-derivative-differences-cce38d36797b //if ((ocolor.a >= 1.0) && ((dFdx(ocolor.a) != 0.0) || (dFdy(ocolor.a) != 0.0) )) // ocolor.rbg = vec3(0.0, 0.0, 0.0); bool isOutlineBelowNotAboveThreshold = true; if (isOutlineBelowNotAboveThreshold) { if ((overlayOutlineWidth > 0.0) && (ocolor.a < 1.0)) { //check voxel neighbors for edge vec3 vx = (overlayOutlineWidth ) / vec3(textureSize(overlay, 0)); //6 voxel neighbors that share a face vec3 vxR = vec3(texPos.x+vx.x, texPos.y, texPos.z); vec3 vxL = vec3(texPos.x-vx.x, texPos.y, texPos.z); vec3 vxA = vec3(texPos.x, texPos.y+vx.y, texPos.z); vec3 vxP = vec3(texPos.x, texPos.y-vx.y, texPos.z); vec3 vxS = vec3(texPos.x, texPos.y, texPos.z+vx.z); vec3 vxI = vec3(texPos.x, texPos.y, texPos.z-vx.z); float a = 0.0; if (axCorSag != 2) { a = max(a, texture(overlay, vxR).a); a = max(a, texture(overlay, vxL).a); } if (axCorSag != 1) { a = max(a, texture(overlay, vxA).a); a = max(a, texture(overlay, vxP).a); } if (axCorSag != 0) { a = max(a, texture(overlay, vxS).a); a = max(a, texture(overlay, vxI).a); } bool isCheckCorners = true; if (isCheckCorners) { //12 voxel neighbors that share an edge vec3 vxRA = vec3(texPos.x+vx.x, texPos.y+vx.y, texPos.z); vec3 vxLA = vec3(texPos.x-vx.x, texPos.y+vx.y, texPos.z); vec3 vxRP = vec3(texPos.x+vx.x, texPos.y-vx.y, texPos.z); vec3 vxLP = vec3(texPos.x-vx.x, texPos.y-vx.y, texPos.z); vec3 vxRS = vec3(texPos.x+vx.x, texPos.y, texPos.z+vx.z); vec3 vxLS = vec3(texPos.x-vx.x, texPos.y, texPos.z+vx.z); vec3 vxRI = vec3(texPos.x+vx.x, texPos.y, texPos.z-vx.z); vec3 vxLI = vec3(texPos.x-vx.x, texPos.y, texPos.z-vx.z); vec3 vxAS = vec3(texPos.x, texPos.y+vx.y, texPos.z+vx.z); vec3 vxPS = vec3(texPos.x, texPos.y-vx.y, texPos.z+vx.z); vec3 vxAI = vec3(texPos.x, texPos.y+vx.y, texPos.z-vx.z); vec3 vxPI = vec3(texPos.x, texPos.y-vx.y, texPos.z-vx.z); if (axCorSag == 0) { //axial corners a = max(a, texture(overlay, vxRA).a); a = max(a, texture(overlay, vxLA).a); a = max(a, texture(overlay, vxRP).a); a = max(a, texture(overlay, vxLP).a); } if (axCorSag == 1) { //coronal corners a = max(a, texture(overlay, vxRS).a); a = max(a, texture(overlay, vxLS).a); a = max(a, texture(overlay, vxRI).a); a = max(a, texture(overlay, vxLI).a); } if (axCorSag == 2) { //sagittal corners a = max(a, texture(overlay, vxAS).a); a = max(a, texture(overlay, vxPS).a); a = max(a, texture(overlay, vxAI).a); a = max(a, texture(overlay, vxPI).a); } } if (a >= 1.0) { ocolor = vec4(0.0, 0.0, 0.0, 1.0); overlayAlpha = 1.0; } } } else { if ((overlayOutlineWidth > 0.0) && (ocolor.a >= 1.0)) { //check voxel neighbors for edge vec3 vx = (overlayOutlineWidth ) / vec3(textureSize(overlay, 0)); vec3 vxR = vec3(texPos.x+vx.x, texPos.y, texPos.z); vec3 vxL = vec3(texPos.x-vx.x, texPos.y, texPos.z); vec3 vxA = vec3(texPos.x, texPos.y+vx.y, texPos.z); vec3 vxP = vec3(texPos.x, texPos.y-vx.y, texPos.z); vec3 vxS = vec3(texPos.x, texPos.y, texPos.z+vx.z); vec3 vxI = vec3(texPos.x, texPos.y, texPos.z-vx.z); float a = 1.0; if (axCorSag != 2) { a = min(a, texture(overlay, vxR).a); a = min(a, texture(overlay, vxL).a); } if (axCorSag != 1) { a = min(a, texture(overlay, vxA).a); a = min(a, texture(overlay, vxP).a); } if (axCorSag != 0) { a = min(a, texture(overlay, vxS).a); a = min(a, texture(overlay, vxI).a); } if (a < 1.0) { ocolor = vec4(0.0, 0.0, 0.0, 1.0); overlayAlpha = 1.0; } } } //outline above threshold } `,kFragSliceTail=` ocolor.a *= overlayAlpha; vec4 dcolor = drawColor(texture(drawing, texPos).r, drawOpacity); if (dcolor.a > 0.0) { color.rgb = mix(color.rgb, dcolor.rgb, dcolor.a); color.a = max(drawOpacity, color.a); } if ((backgroundMasksOverlays > 0) && (background.a == 0.0)) return; float a = color.a + ocolor.a * (1.0 - color.a); // premultiplied alpha if (a == 0.0) return; color.rgb = mix(color.rgb, ocolor.rgb, ocolor.a / a); color.a = a; }`,fragSliceMMShader=kFragSliceHead+kFragSliceTail,fragSliceV1Shader=kFragSliceHead+` if (ocolor.a > 0.0) { //https://gamedev.stackexchange.com/questions/102889/is-it-possible-to-convert-vec4-to-int-in-glsl-using-opengl-es uint alpha = uint(ocolor.a * 255.0); vec3 xyzFlip = vec3(float((uint(1) & alpha) > uint(0)), float((uint(2) & alpha) > uint(0)), float((uint(4) & alpha) > uint(0))); //convert from 0 and 1 to -1 and 1 xyzFlip = (xyzFlip * 2.0) - 1.0; //https://math.stackexchange.com/questions/1905533/find-perpendicular-distance-from-point-to-line-in-3d //v1 principle direction of tensor for this voxel vec3 v1 = ocolor.rgb; //flips encode polarity to convert from 0..1 to -1..1 (27 bits vs 24 bit precision) v1 = normalize( v1 * xyzFlip); vec3 vxl = fract(texPos * vec3(textureSize(volume, 0))) - 0.5; //vxl coordinates now -0.5..+0.5 so 0,0,0 is origin vxl.x = -vxl.x; float t = dot(vxl,v1); vec3 P = t * v1; float dx = length(P-vxl); ocolor.a = 1.0 - smoothstep(0.2,0.25, dx); //if modulation was applied, use that to scale alpha not color: ocolor.a *= length(ocolor.rgb); ocolor.rgb = normalize(ocolor.rgb); //compute distance one half voxel closer to viewer: float pan = 0.5; if (axCorSag == 0) vxl.z -= pan; if (axCorSag == 1) vxl.y -= pan; if (axCorSag == 2) vxl.x += pan; t = dot(vxl,v1); P = t * v1; float dx2 = length(P-vxl); ocolor.rgb += (dx2-dx-(0.5 * pan)) * 1.0; } `+kFragSliceTail,fragRectShader=`#version 300 es #line 480 precision highp int; precision highp float; uniform vec4 lineColor; out vec4 color; void main() { color = lineColor; }`,vertColorbarShader=`#version 300 es #line 490 layout(location=0) in vec3 pos; uniform vec2 canvasWidthHeight; uniform vec4 leftTopWidthHeight; out vec2 vColor; void main(void) { //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1 vec2 frac; frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1 frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0 frac = (frac * 2.0) - 1.0; gl_Position = vec4(frac, 0.0, 1.0); vColor = pos.xy; }`,fragColorbarShader=`#version 300 es #line 506 precision highp int; precision highp float; uniform highp sampler2D colormap; uniform float layer; in vec2 vColor; out vec4 color; void main() { float nlayer = float(textureSize(colormap, 0).y); float fmap = (0.5 + layer) / nlayer; color = vec4(texture(colormap, vec2(vColor.x, fmap)).rgb, 1.0); }`,vertRectShader=`#version 300 es #line 520 layout(location=0) in vec3 pos; uniform vec2 canvasWidthHeight; uniform vec4 leftTopWidthHeight; void main(void) { //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1 vec2 frac; frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1 frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0 frac = (frac * 2.0) - 1.0; gl_Position = vec4(frac, 0.0, 1.0); }`,vertLineShader=`#version 300 es #line 534 layout(location=0) in vec3 pos; uniform vec2 canvasWidthHeight; uniform float thickness; uniform vec4 startXYendXY; void main(void) { vec2 posXY = mix(startXYendXY.xy, startXYendXY.zw, pos.x); vec2 dir = normalize(startXYendXY.xy - startXYendXY.zw); posXY += vec2(-dir.y, dir.x) * thickness * (pos.y - 0.5); posXY.x = (posXY.x) / canvasWidthHeight.x; //0..1 posXY.y = 1.0 - (posXY.y / canvasWidthHeight.y); //1..0 gl_Position = vec4((posXY * 2.0) - 1.0, 0.0, 1.0); }`,vertLine3DShader=`#version 300 es #line 534 layout(location=0) in vec3 pos; uniform vec2 canvasWidthHeight; uniform float thickness; uniform vec2 startXY; uniform vec3 endXYZ; // transformed XYZ point void main(void) { vec2 posXY = mix(startXY.xy, endXYZ.xy, pos.x); vec2 startDiff = endXYZ.xy - startXY.xy; float startDistance = length(startDiff); vec2 diff = endXYZ.xy - posXY; float currentDistance = length(diff); vec2 dir = normalize(startXY.xy - endXYZ.xy); posXY += vec2(-dir.y, dir.x) * thickness * (pos.y - 0.5); posXY.x = (posXY.x) / canvasWidthHeight.x; //0..1 posXY.y = 1.0 - (posXY.y / canvasWidthHeight.y); //1..0 float z = endXYZ.z * ( 1.0 - abs(currentDistance/startDistance)); gl_Position = vec4((posXY * 2.0) - 1.0, z, 1.0); }`,vertBmpShader=`#version 300 es #line 549 layout(location=0) in vec3 pos; uniform vec2 canvasWidthHeight; uniform vec4 leftTopWidthHeight; out vec2 vUV; void main(void) { //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1 vec2 frac; frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1 frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0 frac = (frac * 2.0) - 1.0; gl_Position = vec4(frac, 0.0, 1.0); vUV = vec2(pos.x, 1.0 - pos.y); }`,fragBmpShader=`#version 300 es #line 565 precision highp int; precision highp float; uniform highp sampler2D bmpTexture; in vec2 vUV; out vec4 color; void main() { color = texture(bmpTexture, vUV); }`,vertFontShader=`#version 300 es #line 576 layout(location=0) in vec3 pos; uniform vec2 canvasWidthHeight; uniform vec4 leftTopWidthHeight; uniform vec4 uvLeftTopWidthHeight; out vec2 vUV; void main(void) { //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1 vec2 frac; frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1 frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0 frac = (frac * 2.0) - 1.0; gl_Position = vec4(frac, 0.0, 1.0); vUV = vec2(uvLeftTopWidthHeight.x + (pos.x * uvLeftTopWidthHeight.z), uvLeftTopWidthHeight.y + ((1.0 - pos.y) * uvLeftTopWidthHeight.w) ); }`,fragFontShader=`#version 300 es #line 593 precision highp int; precision highp float; uniform highp sampler2D fontTexture; uniform vec4 fontColor; uniform float screenPxRange; in vec2 vUV; out vec4 color; float median(float r, float g, float b) { return max(min(r, g), min(max(r, g), b)); } void main() { vec3 msd = texture(fontTexture, vUV).rgb; float sd = median(msd.r, msd.g, msd.b); float screenPxDistance = screenPxRange*(sd - 0.5); float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); color = vec4(fontColor.rgb , fontColor.a * opacity); }`,vertCircleShader=`#version 300 es layout(location=0) in vec3 pos; uniform vec2 canvasWidthHeight; uniform vec4 leftTopWidthHeight; uniform vec4 uvLeftTopWidthHeight; out vec2 vUV; void main(void) { //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1 vec2 frac; frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1 frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0 frac = (frac * 2.0) - 1.0; gl_Position = vec4(frac, 0.0, 1.0); vUV = pos.xy; }`,fragCircleShader=`#version 300 es precision highp int; precision highp float; uniform vec4 circleColor; uniform float fillPercent; in vec2 vUV; out vec4 color; void main() { /* Check if the pixel is inside the circle and color it with a gradient. Otherwise, color it transparent */ float distance = length(vUV-vec2(0.5,0.5)); if ( distance < 0.5 && distance >= (1.0 - fillPercent) / 2.0){ color = vec4(circleColor.r,circleColor.g,circleColor.b,circleColor.a) ; }else{ color = vec4(0.0,0.0,0.0,0.0); } } `,vertOrientShader=`#version 300 es #line 613 precision highp int; precision highp float; in vec3 vPos; out vec2 TexCoord; void main() { TexCoord = vPos.xy; gl_Position = vec4( (vPos.xy-vec2(0.5,0.5)) * 2.0, 0.0, 1.0); }`,fragOrientShaderU=`#version 300 es uniform highp usampler3D intensityVol; `,fragOrientShaderI=`#version 300 es uniform highp isampler3D intensityVol; `,fragOrientShaderF=`#version 300 es uniform highp sampler3D intensityVol; `,fragOrientShaderAtlas=`#line 636 precision highp int; precision highp float; in vec2 TexCoord; out vec4 FragColor; uniform float coordZ; uniform float layer; uniform highp sampler2D colormap; uniform lowp sampler3D blend3D; uniform float opacity; uniform vec4 xyzaFrac; uniform mat4 mtx; void main(void) { vec4 vx = vec4(TexCoord.x, TexCoord.y, coordZ, 1.0) * mtx; uint idx = uint(texture(intensityVol, vx.xyz).r); FragColor = vec4(0.0, 0.0, 0.0, 0.0); if (idx == uint(0)) return; //idx = ((idx - uint(1)) % uint(100))+uint(1); float textureWidth = float(textureSize(colormap, 0).x); float fx = (float(idx)+0.5) / textureWidth; float nlayer = float(textureSize(colormap, 0).y); float y = ((2.0 * layer) + 1.5)/nlayer; FragColor = texture(colormap, vec2(fx, y)).rgba; float alpha = FragColor.a; FragColor.a *= opacity; if (xyzaFrac.a > 0.0) { //outline vx = vec4(TexCoord.x+xyzaFrac.x, TexCoord.y, coordZ, 1.0) * mtx; uint R = uint(texture(intensityVol, vx.xyz).r); vx = vec4(TexCoord.x-xyzaFrac.x, TexCoord.y, coordZ, 1.0) * mtx; uint L = uint(texture(intensityVol, vx.xyz).r); vx = vec4(TexCoord.x, TexCoord.y+xyzaFrac.y, coordZ, 1.0) * mtx; uint A = uint(texture(intensityVol, vx.xyz).r); vx = vec4(TexCoord.x, TexCoord.y-xyzaFrac.y, coordZ, 1.0) * mtx; uint P = uint(texture(intensityVol, vx.xyz).r); vx = vec4(TexCoord.x, TexCoord.y, coordZ+xyzaFrac.z, 1.0) * mtx; uint S = uint(texture(intensityVol, vx.xyz).r); vx = vec4(TexCoord.x, TexCoord.y, coordZ-xyzaFrac.z, 1.0) * mtx; uint I = uint(texture(intensityVol, vx.xyz).r); if ((idx != R) || (idx != L) || (idx != A) || (idx != P) || (idx != S) || (idx != I)) FragColor.a = alpha * xyzaFrac.a; } }`,fragOrientShader=`#line 691 precision highp int; precision highp float; in vec2 TexCoord; out vec4 FragColor; uniform float coordZ; uniform float layer; uniform float scl_slope; uniform float scl_inter; uniform float cal_max; uniform float cal_min; uniform float cal_maxNeg; uniform float cal_minNeg; uniform bool isAlphaThreshold; uniform bool isAdditiveBlend; uniform highp sampler2D colormap; uniform lowp sampler3D blend3D; uniform int modulation; uniform highp sampler3D modulationVol; uniform float opacity; uniform mat4 mtx; void main(void) { vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx; if ((vx.x < 0.0) || (vx.x > 1.0) || (vx.y < 0.0) || (vx.y > 1.0) || (vx.z < 0.0) || (vx.z > 1.0)) { //set transparent if out of range //https://webglfundamentals.org/webgl/webgl-3d-textures-repeat-clamp.html FragColor = texture(blend3D, vec3(TexCoord.xy, coordZ)); return; } float f = (scl_slope * float(texture(intensityVol, vx.xyz).r)) + scl_inter; float mn = cal_min; float mx = cal_max; if (isAlphaThreshold) mn = 0.0; float r = max(0.00001, abs(mx - mn)); mn = min(mn, mx); float txl = mix(0.0, 1.0, (f - mn) / r); //https://stackoverflow.com/questions/5879403/opengl-texture-coordinates-in-pixel-space float nlayer = float(textureSize(colormap, 0).y); //each volume has two color maps: // (layer*2) = negative and (layer * 2) + 1 = postive float y = ((2.0 * layer) + 1.5)/nlayer; FragColor = texture(colormap, vec2(txl, y)).rgba; //negative colors mn = cal_minNeg; mx = cal_maxNeg; if (isAlphaThreshold) mx = 0.0; //if ((!isnan(cal_minNeg)) && ( f < mx)) { if ((cal_minNeg < cal_maxNeg) && ( f < mx)) { r = max(0.00001, abs(mx - mn)); mn = min(mn, mx); txl = 1.0 - mix(0.0, 1.0, (f - mn) / r); y = ((2.0 * layer) + 0.5)/nlayer; FragColor = texture(colormap, vec2(txl, y)); } if (layer > 0.7) FragColor.a = step(0.00001, FragColor.a); //if (modulation > 10) // FragColor.a *= texture(modulationVol, vx.xyz).r; // FragColor.rgb *= texture(modulationVol, vx.xyz).r; if (isAlphaThreshold) { if ((cal_minNeg != cal_maxNeg) && ( f < 0.0) && (f > cal_maxNeg)) FragColor.a = pow(-f / -cal_maxNeg, 2.0); else if ((f > 0.0) && (cal_min > 0.0)) FragColor.a *= pow(f / cal_min, 2.0); //issue435: A = (V/X)**2 //FragColor.g = 0.0; } if (modulation == 1) { FragColor.rgb *= texture(modulationVol, vx.xyz).r; } else if (modulation == 2) { FragColor.a = texture(modulationVol, vx.xyz).r; } FragColor.a *= opacity; if (layer < 1.0) return; vec4 prevColor = texture(blend3D, vec3(TexCoord.xy, coordZ)); // https://en.wikipedia.org/wiki/Alpha_compositing float aout = FragColor.a + (1.0 - FragColor.a) * prevColor.a; if (aout <= 0.0) return; if (isAdditiveBlend) FragColor.rgb = ((FragColor.rgb * FragColor.a) + (prevColor.rgb * prevColor.a)) / aout; else FragColor.rgb = ((FragColor.rgb * FragColor.a) + (prevColor.rgb * prevColor.a * (1.0 - FragColor.a))) / aout; FragColor.a = aout; }`,fragRGBOrientShader=`#line 773 precision highp int; precision highp float; in vec2 TexCoord; out vec4 FragColor; uniform float coordZ; uniform float layer; uniform float scl_slope; uniform float scl_inter; uniform float cal_max; uniform float cal_min; uniform highp sampler2D colormap; uniform lowp sampler3D blend3D; uniform float opacity; uniform mat4 mtx; uniform bool hasAlpha; uniform int modulation; uniform highp sampler3D modulationVol; void main(void) { vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx; uvec4 aColor = texture(intensityVol, vx.xyz); FragColor = vec4(float(aColor.r) / 255.0, float(aColor.g) / 255.0, float(aColor.b) / 255.0, float(aColor.a) / 255.0); if (modulation == 1) FragColor.rgb *= texture(modulationVol, vx.xyz).r; if (!hasAlpha) { FragColor.a = (FragColor.r * 0.21 + FragColor.g * 0.72 + FragColor.b * 0.07); //next line: we could binarize alpha, but see rendering of visible human //FragColor.a = step(0.01, FragColor.a); } if (modulation == 2) FragColor.a = texture(modulationVol, vx.xyz).r; FragColor.a *= opacity; }`,vertGrowCutShader=`#version 300 es #line 808 precision highp int; precision highp float; in vec3 vPos; out vec2 TexCoord; void main() { TexCoord = vPos.xy; gl_Position = vec4((vPos.x - 0.5) * 2.0, (vPos.y - 0.5) * 2.0, 0.0, 1.0); }`,fragGrowCutShader=`#version 300 es #line 829 precision highp float; precision highp int; precision highp isampler3D; layout(location = 0) out int label; layout(location = 1) out int strength; in vec2 TexCoord; uniform int finalPass; uniform float coordZ; uniform lowp sampler3D in3D; uniform highp isampler3D backTex; // background uniform highp isampler3D labelTex; // label uniform highp isampler3D strengthTex; // strength void main(void) { vec3 interpolatedTextureCoordinate = vec3(TexCoord.xy, coordZ); ivec3 size = textureSize(backTex, 0); ivec3 texelIndex = ivec3(floor(interpolatedTextureCoordinate * vec3(size))); int background = texelFetch(backTex, texelIndex, 0).r; label = texelFetch(labelTex, texelIndex, 0).r; strength = texelFetch(strengthTex, texelIndex, 0).r; for (int k = -1; k <= 1; k++) { for (int j = -1; j <= 1; j++) { for (int i = -1; i <= 1; i++) { if (i != 0 && j != 0 && k != 0) { ivec3 neighborIndex = texelIndex + ivec3(i,j,k); int neighborBackground = texelFetch(backTex, neighborIndex, 0).r; int neighborStrength = texelFetch(strengthTex, neighborIndex, 0).r; int strengthCost = abs(neighborBackground - background); int takeoverStrength = neighborStrength - strengthCost; if