@niivue/niivue
Version:
minimal webgl2 nifti image viewer
1,073 lines (1,051 loc) • 708 kB
JavaScript
(function(H,it){typeof exports=="object"&&typeof module<"u"?it(exports):typeof define=="function"&&define.amd?define(["exports"],it):(H=typeof globalThis<"u"?globalThis:H||self,it(H.niivue={}))})(this,function(H){"use strict";var Zn=Object.defineProperty;var Ts=H=>{throw TypeError(H)};var Jn=(H,it,_)=>it in H?Zn(H,it,{enumerable:!0,configurable:!0,writable:!0,value:_}):H[it]=_;var b=(H,it,_)=>Jn(H,typeof it!="symbol"?it+"":it,_),Ms=(H,it,_)=>it.has(H)||Ts("Cannot "+_);var W=(H,it,_)=>(Ms(H,it,"read from private field"),_?_.call(H):it.get(H)),me=(H,it,_)=>it.has(H)?Ts("Cannot add the same private member more than once"):it instanceof WeakSet?it.add(H):it.set(H,_),se=(H,it,_,Se)=>(Ms(H,it,"write to private field"),Se?Se.call(H,_):it.set(H,_),_);var Q,It,Fe,je,ue;var it=1e-6,_=typeof Float32Array<"u"?Float32Array:Array;Math.hypot||(Math.hypot=function(){for(var c=0,t=arguments.length;t--;)c+=arguments[t]*arguments[t];return Math.sqrt(c)});function Se(){var c=new _(9);return _!=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 Jt(c,t,e,s,i,n,r,o,l){var a=new _(9);return a[0]=c,a[1]=t,a[2]=e,a[3]=s,a[4]=i,a[5]=n,a[6]=r,a[7]=o,a[8]=l,a}function bi(c,t,e){var s=t[0],i=t[1],n=t[2],r=t[3],o=t[4],l=t[5],a=t[6],h=t[7],f=t[8],d=e[0],u=e[1],A=e[2],m=e[3],g=e[4],v=e[5],x=e[6],p=e[7],w=e[8];return c[0]=d*s+u*r+A*a,c[1]=d*i+u*o+A*h,c[2]=d*n+u*l+A*f,c[3]=m*s+g*r+v*a,c[4]=m*i+g*o+v*h,c[5]=m*n+g*l+v*f,c[6]=x*s+p*r+w*a,c[7]=x*i+p*o+w*h,c[8]=x*n+p*l+w*f,c}function J(){var c=new _(16);return _!=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 ct(c){var t=new _(16);return t[0]=c[0],t[1]=c[1],t[2]=c[2],t[3]=c[3],t[4]=c[4],t[5]=c[5],t[6]=c[6],t[7]=c[7],t[8]=c[8],t[9]=c[9],t[10]=c[10],t[11]=c[11],t[12]=c[12],t[13]=c[13],t[14]=c[14],t[15]=c[15],t}function Es(c,t){return c[0]=t[0],c[1]=t[1],c[2]=t[2],c[3]=t[3],c[4]=t[4],c[5]=t[5],c[6]=t[6],c[7]=t[7],c[8]=t[8],c[9]=t[9],c[10]=t[10],c[11]=t[11],c[12]=t[12],c[13]=t[13],c[14]=t[14],c[15]=t[15],c}function Dt(c,t,e,s,i,n,r,o,l,a,h,f,d,u,A,m){var g=new _(16);return g[0]=c,g[1]=t,g[2]=e,g[3]=s,g[4]=i,g[5]=n,g[6]=r,g[7]=o,g[8]=l,g[9]=a,g[10]=h,g[11]=f,g[12]=d,g[13]=u,g[14]=A,g[15]=m,g}function Ci(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 Tt(c,t){if(c===t){var e=t[1],s=t[2],i=t[3],n=t[6],r=t[7],o=t[11];c[1]=t[4],c[2]=t[8],c[3]=t[12],c[4]=e,c[6]=t[9],c[7]=t[13],c[8]=s,c[9]=n,c[11]=t[14],c[12]=i,c[13]=r,c[14]=o}else c[0]=t[0],c[1]=t[4],c[2]=t[8],c[3]=t[12],c[4]=t[1],c[5]=t[5],c[6]=t[9],c[7]=t[13],c[8]=t[2],c[9]=t[6],c[10]=t[10],c[11]=t[14],c[12]=t[3],c[13]=t[7],c[14]=t[11],c[15]=t[15];return c}function Ct(c,t){var e=t[0],s=t[1],i=t[2],n=t[3],r=t[4],o=t[5],l=t[6],a=t[7],h=t[8],f=t[9],d=t[10],u=t[11],A=t[12],m=t[13],g=t[14],v=t[15],x=e*o-s*r,p=e*l-i*r,w=e*a-n*r,I=s*l-i*o,C=s*a-n*o,T=i*a-n*l,y=h*m-f*A,M=h*g-d*A,E=h*v-u*A,F=f*g-d*m,D=f*v-u*m,V=d*v-u*g,B=x*V-p*D+w*F+I*E-C*M+T*y;return B?(B=1/B,c[0]=(o*V-l*D+a*F)*B,c[1]=(i*D-s*V-n*F)*B,c[2]=(m*T-g*C+v*I)*B,c[3]=(d*C-f*T-u*I)*B,c[4]=(l*E-r*V-a*M)*B,c[5]=(e*V-i*E+n*M)*B,c[6]=(g*w-A*T-v*p)*B,c[7]=(h*T-d*w+u*p)*B,c[8]=(r*D-o*E+a*y)*B,c[9]=(s*E-e*D-n*y)*B,c[10]=(A*C-m*w+v*x)*B,c[11]=(f*w-h*C-u*x)*B,c[12]=(o*M-r*F-l*y)*B,c[13]=(e*F-s*M+i*y)*B,c[14]=(m*p-A*I-g*x)*B,c[15]=(h*I-f*p+d*x)*B,c):null}function Wt(c,t,e){var s=t[0],i=t[1],n=t[2],r=t[3],o=t[4],l=t[5],a=t[6],h=t[7],f=t[8],d=t[9],u=t[10],A=t[11],m=t[12],g=t[13],v=t[14],x=t[15],p=e[0],w=e[1],I=e[2],C=e[3];return c[0]=p*s+w*o+I*f+C*m,c[1]=p*i+w*l+I*d+C*g,c[2]=p*n+w*a+I*u+C*v,c[3]=p*r+w*h+I*A+C*x,p=e[4],w=e[5],I=e[6],C=e[7],c[4]=p*s+w*o+I*f+C*m,c[5]=p*i+w*l+I*d+C*g,c[6]=p*n+w*a+I*u+C*v,c[7]=p*r+w*h+I*A+C*x,p=e[8],w=e[9],I=e[10],C=e[11],c[8]=p*s+w*o+I*f+C*m,c[9]=p*i+w*l+I*d+C*g,c[10]=p*n+w*a+I*u+C*v,c[11]=p*r+w*h+I*A+C*x,p=e[12],w=e[13],I=e[14],C=e[15],c[12]=p*s+w*o+I*f+C*m,c[13]=p*i+w*l+I*d+C*g,c[14]=p*n+w*a+I*u+C*v,c[15]=p*r+w*h+I*A+C*x,c}function re(c,t,e){var s=e[0],i=e[1],n=e[2],r,o,l,a,h,f,d,u,A,m,g,v;return t===c?(c[12]=t[0]*s+t[4]*i+t[8]*n+t[12],c[13]=t[1]*s+t[5]*i+t[9]*n+t[13],c[14]=t[2]*s+t[6]*i+t[10]*n+t[14],c[15]=t[3]*s+t[7]*i+t[11]*n+t[15]):(r=t[0],o=t[1],l=t[2],a=t[3],h=t[4],f=t[5],d=t[6],u=t[7],A=t[8],m=t[9],g=t[10],v=t[11],c[0]=r,c[1]=o,c[2]=l,c[3]=a,c[4]=h,c[5]=f,c[6]=d,c[7]=u,c[8]=A,c[9]=m,c[10]=g,c[11]=v,c[12]=r*s+h*i+A*n+t[12],c[13]=o*s+f*i+m*n+t[13],c[14]=l*s+d*i+g*n+t[14],c[15]=a*s+u*i+v*n+t[15]),c}function yi(c,t,e){var s=e[0],i=e[1],n=e[2];return c[0]=t[0]*s,c[1]=t[1]*s,c[2]=t[2]*s,c[3]=t[3]*s,c[4]=t[4]*i,c[5]=t[5]*i,c[6]=t[6]*i,c[7]=t[7]*i,c[8]=t[8]*n,c[9]=t[9]*n,c[10]=t[10]*n,c[11]=t[11]*n,c[12]=t[12],c[13]=t[13],c[14]=t[14],c[15]=t[15],c}function Be(c,t,e){var s=Math.sin(e),i=Math.cos(e),n=t[4],r=t[5],o=t[6],l=t[7],a=t[8],h=t[9],f=t[10],d=t[11];return t!==c&&(c[0]=t[0],c[1]=t[1],c[2]=t[2],c[3]=t[3],c[12]=t[12],c[13]=t[13],c[14]=t[14],c[15]=t[15]),c[4]=n*i+a*s,c[5]=r*i+h*s,c[6]=o*i+f*s,c[7]=l*i+d*s,c[8]=a*i-n*s,c[9]=h*i-r*s,c[10]=f*i-o*s,c[11]=d*i-l*s,c}function Ve(c,t,e){var s=Math.sin(e),i=Math.cos(e),n=t[0],r=t[1],o=t[2],l=t[3],a=t[4],h=t[5],f=t[6],d=t[7];return t!==c&&(c[8]=t[8],c[9]=t[9],c[10]=t[10],c[11]=t[11],c[12]=t[12],c[13]=t[13],c[14]=t[14],c[15]=t[15]),c[0]=n*i+a*s,c[1]=r*i+h*s,c[2]=o*i+f*s,c[3]=l*i+d*s,c[4]=a*i-n*s,c[5]=h*i-r*s,c[6]=f*i-o*s,c[7]=d*i-l*s,c}function Fs(c,t,e){var s=e[0],i=e[1],n=e[2],r=Math.hypot(s,i,n),o,l,a;return r<it?null:(r=1/r,s*=r,i*=r,n*=r,o=Math.sin(t),l=Math.cos(t),a=1-l,c[0]=s*s*a+l,c[1]=i*s*a+n*o,c[2]=n*s*a-i*o,c[3]=0,c[4]=s*i*a-n*o,c[5]=i*i*a+l,c[6]=n*i*a+s*o,c[7]=0,c[8]=s*n*a+i*o,c[9]=i*n*a-s*o,c[10]=n*n*a+l,c[11]=0,c[12]=0,c[13]=0,c[14]=0,c[15]=1,c)}function Ss(c,t,e,s,i,n,r){var o=1/(t-e),l=1/(s-i),a=1/(n-r);return c[0]=-2*o,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*a,c[11]=0,c[12]=(t+e)*o,c[13]=(i+s)*l,c[14]=(r+n)*a,c[15]=1,c}var Re=Ss;function Bs(c,t,e){return c[0]=t[0]*e,c[1]=t[1]*e,c[2]=t[2]*e,c[3]=t[3]*e,c[4]=t[4]*e,c[5]=t[5]*e,c[6]=t[6]*e,c[7]=t[7]*e,c[8]=t[8]*e,c[9]=t[9]*e,c[10]=t[10]*e,c[11]=t[11]*e,c[12]=t[12]*e,c[13]=t[13]*e,c[14]=t[14]*e,c[15]=t[15]*e,c}var Ii=Wt;function rt(){var c=new _(3);return _!=Float32Array&&(c[0]=0,c[1]=0,c[2]=0),c}function Ae(c){var t=new _(3);return t[0]=c[0],t[1]=c[1],t[2]=c[2],t}function Ht(c){var t=c[0],e=c[1],s=c[2];return Math.hypot(t,e,s)}function G(c,t,e){var s=new _(3);return s[0]=c,s[1]=t,s[2]=e,s}function Di(c,t){return c[0]=t[0],c[1]=t[1],c[2]=t[2],c}function _t(c,t,e){return c[0]=t[0]+e[0],c[1]=t[1]+e[1],c[2]=t[2]+e[2],c}function lt(c,t,e){return c[0]=t[0]-e[0],c[1]=t[1]-e[1],c[2]=t[2]-e[2],c}function Ze(c,t,e){return c[0]=Math.min(t[0],e[0]),c[1]=Math.min(t[1],e[1]),c[2]=Math.min(t[2],e[2]),c}function Je(c,t,e){return c[0]=Math.max(t[0],e[0]),c[1]=Math.max(t[1],e[1]),c[2]=Math.max(t[2],e[2]),c}function Vs(c,t,e){return c[0]=t[0]*e,c[1]=t[1]*e,c[2]=t[2]*e,c}function Rs(c,t){return c[0]=-t[0],c[1]=-t[1],c[2]=-t[2],c}function Ft(c,t){var e=t[0],s=t[1],i=t[2],n=e*e+s*s+i*i;return n>0&&(n=1/Math.sqrt(n)),c[0]=t[0]*n,c[1]=t[1]*n,c[2]=t[2]*n,c}function Us(c,t){return c[0]*t[0]+c[1]*t[1]+c[2]*t[2]}function Ue(c,t,e){var s=t[0],i=t[1],n=t[2],r=e[0],o=e[1],l=e[2];return c[0]=i*l-n*o,c[1]=n*r-s*l,c[2]=s*o-i*r,c}function Ns(c,t,e,s){var i=t[0],n=t[1],r=t[2];return c[0]=i+s*(e[0]-i),c[1]=n+s*(e[1]-n),c[2]=r+s*(e[2]-r),c}function _e(c,t){var e=c[0],s=c[1],i=c[2],n=t[0],r=t[1],o=t[2],l=Math.sqrt(e*e+s*s+i*i),a=Math.sqrt(n*n+r*r+o*o),h=l*a,f=h&&Us(c,t)/h;return Math.acos(Math.min(Math.max(f,-1),1))}var Ti=lt,ge=Ht;(function(){var c=rt();return function(t,e,s,i,n,r){var o,l;for(e||(e=3),s||(s=0),i?l=Math.min(i*e+s,t.length):l=t.length,o=s;o<l;o+=e)c[0]=t[o],c[1]=t[o+1],c[2]=t[o+2],n(c,c,r),t[o]=c[0],t[o+1]=c[1],t[o+2]=c[2];return t}})();function Ut(){var c=new _(4);return _!=Float32Array&&(c[0]=0,c[1]=0,c[2]=0,c[3]=0),c}function Ot(c){var t=new _(4);return t[0]=c[0],t[1]=c[1],t[2]=c[2],t[3]=c[3],t}function nt(c,t,e,s){var i=new _(4);return i[0]=c,i[1]=t,i[2]=e,i[3]=s,i}function Mi(c,t,e){return c[0]=t[0]+e[0],c[1]=t[1]+e[1],c[2]=t[2]+e[2],c[3]=t[3]+e[3],c}function Ps(c,t,e){return c[0]=t[0]-e[0],c[1]=t[1]-e[1],c[2]=t[2]-e[2],c[3]=t[3]-e[3],c}function Ls(c,t,e){return c[0]=t[0]*e,c[1]=t[1]*e,c[2]=t[2]*e,c[3]=t[3]*e,c}function mt(c,t,e){var s=t[0],i=t[1],n=t[2],r=t[3];return c[0]=e[0]*s+e[4]*i+e[8]*n+e[12]*r,c[1]=e[1]*s+e[5]*i+e[9]*n+e[13]*r,c[2]=e[2]*s+e[6]*i+e[10]*n+e[14]*r,c[3]=e[3]*s+e[7]*i+e[11]*n+e[15]*r,c}var ks=Ps;(function(){var c=Ut();return function(t,e,s,i,n,r){var o,l;for(e||(e=4),s||(s=0),i?l=Math.min(i*e+s,t.length):l=t.length,o=s;o<l;o+=e)c[0]=t[o],c[1]=t[o+1],c[2]=t[o+2],c[3]=t[o+3],n(c,c,r),t[o]=c[0],t[o+1]=c[1],t[o+2]=c[2],t[o+3]=c[3];return t}})();function Os(){var c=new _(2);return _!=Float32Array&&(c[0]=0,c[1]=0),c}function Ei(c,t){var e=new _(2);return e[0]=c,e[1]=t,e}function Gs(c,t,e){return c[0]=t[0]*e,c[1]=t[1]*e,c}function Fi(c){var t=c[0],e=c[1];return Math.hypot(t,e)}function Ys(c,t){var e=t[0],s=t[1],i=e*e+s*s;return i>0&&(i=1/Math.sqrt(i)),c[0]=t[0]*i,c[1]=t[1]*i,c}(function(){var c=Os();return function(t,e,s,i,n,r){var o,l;for(e||(e=2),s||(s=0),i?l=Math.min(i*e+s,t.length):l=t.length,o=s;o<l;o+=e)c[0]=t[o],c[1]=t[o+1],n(c,c,r),t[o]=c[0],t[o+1]=c[1];return t}})();const Si="0.53.1",Et=class Et{constructor({name:t="niivue",level:e="info"}={}){b(this,"level");b(this,"name");this.name=`${t}`,this.level=e}debug(...t){Et.levels[this.level]>Et.levels.debug||console.debug(`${this.name}-debug`,...t)}info(...t){Et.levels[this.level]>Et.levels.info||console.info(`${this.name}-info`,...t)}warn(...t){Et.levels[this.level]>Et.levels.warn||console.warn(`${this.name}-warn`,...t)}error(...t){Et.levels[this.level]>Et.levels.error||console.error(`${this.name}-error`,...t)}fatal(...t){Et.levels[this.level]>Et.levels.fatal||console.error(`${this.name}-fatal`,...t)}setLogLevel(t){this.level=t}setName(t){this.name=t}};b(Et,"levels",{debug:0,info:1,warn:2,error:3,fatal:4,silent:1/0});let $e=Et;const S=new $e({name:"niivue",level:"info"}),zs=function(c,t,e){const s=c.createShader(c.VERTEX_SHADER);c.shaderSource(s,t),c.compileShader(s);const i=c.createShader(c.FRAGMENT_SHADER);c.shaderSource(i,e),c.compileShader(i);const n=c.createProgram();if(c.attachShader(n,s),c.attachShader(n,i),c.linkProgram(n),!c.getProgramParameter(n,c.LINK_STATUS))throw console.log(c.getProgramInfoLog(n)),c.getShaderParameter(s,c.COMPILE_STATUS)||console.log("Vertex shader compilation error:",c.getShaderInfoLog(s)),c.getShaderParameter(i,c.COMPILE_STATUS)||console.log("Fragment shader compilation error:",c.getShaderInfoLog(i)),S.error(c.getProgramInfoLog(n)),new Error("Shader failed to link, see console for log");return n};class ${constructor(t,e,s){b(this,"program");b(this,"uniforms",{});b(this,"isMatcap");this.program=zs(t,e,s);const i=/uniform[^;]+[ ](\w+);/g,n=/uniform[^;]+[ ](\w+);/,r=e.match(i),o=s.match(i);r&&r.forEach(l=>{const a=l.match(n);this.uniforms[a[1]]=-1}),o&&o.forEach(l=>{const a=l.match(n);this.uniforms[a[1]]=-1});for(const l in this.uniforms)this.uniforms[l]=t.getUniformLocation(this.program,l)}use(t){t.useProgram(this.program)}}const pe=`#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;
}`,Bi=`
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;
}`,xe=`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;
}`+Bi,ti=`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
`,Ne=`
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);
}`,Ks=`#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;
`+xe+`
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
`+Ne,Ws=`#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;
`+xe+ti+`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;
}
}
`+Ne,Vi=`#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;
uniform float gradientOpacity[256];
in vec3 vColor;
out vec4 fColor;
`,Hs=Vi+xe+ti+`
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);
int gradIdx = int(grad.a * 255.0);
colorSample.a *= gradientOpacity[gradIdx];
colorSample.rgb *= colorSample.a;
colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
if ( colAcc.a > earlyTermination )
break;
}
samplePos += deltaDir; //advance ray position
}
`+Ne,Qs=Vi+xe+ti+`
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);
colorSample.rgb = abs(normalize(grad.rgb*2.0 - 1.0));
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
}
`+Ne,Ri=`#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;
}`,Ui=`#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;`+Bi+`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
}
`,Ni=` 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;
}`,qs=Ui+Ni,Xs=Ui+` 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;
}
`+Ni,ei=`#version 300 es
#line 480
precision highp int;
precision highp float;
uniform vec4 lineColor;
out vec4 color;
void main() {
color = lineColor;
}`,js=`#version 300 es
#line 723
precision highp int;
precision highp float;
uniform vec4 lineColor;
uniform vec4 leftTopWidthHeight;
uniform float thickness; // line thickness in pixels
uniform vec2 canvasWidthHeight;
out vec4 color;
void main() {
// fragment position in screen coordinates
vec2 fragCoord = gl_FragCoord.xy;
// canvas height
float canvasHeight = canvasWidthHeight.y;
// 'top' and 'bottom' to match gl_FragCoord.y coordinate system
float top = canvasHeight - leftTopWidthHeight.y;
float bottom = top - leftTopWidthHeight.w;
// left and right edges
float left = leftTopWidthHeight.x;
float right = left + leftTopWidthHeight.z;
bool withinLeft = fragCoord.x >= left && fragCoord.x <= left + thickness;
bool withinRight = fragCoord.x <= right && fragCoord.x >= right - thickness;
bool withinTop = fragCoord.y <= top && fragCoord.y >= top - thickness;
bool withinBottom = fragCoord.y >= bottom && fragCoord.y <= bottom + thickness;
bool isOutline = withinLeft || withinRight || withinTop || withinBottom;
if (isOutline) {
color = lineColor;
} else {
discard;
}
}`,Zs=`#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;
}`,Js=`#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);
}`,Pi=`#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);
}`,_s=`#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);
}`,$s=`#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);
}`,tr=`#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);
}`,er=`#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);
}`,ir=`#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) );
}`,sr=`#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);
}`,rr=`#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;
}`,nr=`#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);
}
}
`,ne=`#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);
}`,ii=`#version 300 es
uniform highp usampler3D intensityVol;
`,Li=`#version 300 es
uniform highp isampler3D intensityVol;
`,or=`#version 300 es
uniform highp sampler3D intensityVol;
`,ki=`#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;
}
}`,si=`#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 isColorbarFromZero;
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) || (isColorbarFromZero))
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);
if (f > mn) { //issue1139: survives threshold, so round up to opaque voxel
txl = max(txl, 2.0/256.0);
}
//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 = positive
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) || (isColorbarFromZero))
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);
//issue1139: survives threshold, so round up to opaque voxel
txl = max(txl, 2.0/256.0);
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;
} else if (isColorbarFromZero) {
if ((cal_minNeg != cal_maxNeg) && ( f < 0.0) && (f > cal_maxNeg))
FragColor.a = 0.0;
else if ((f > 0.0) && (cal_min > 0.0) && (f < cal_min))
FragColor.a *= 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;
}`,ar=`#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 bin