@niivue/niivue 0.24.3 → 0.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/niivue.es.js CHANGED
@@ -91,7 +91,7 @@ function fromValues$3(m00, m01, m02, m10, m11, m12, m20, m21, m22) {
91
91
  out[8] = m22;
92
92
  return out;
93
93
  }
94
- function multiply$2(out, a, b) {
94
+ function multiply$1(out, a, b) {
95
95
  var a00 = a[0], a01 = a[1], a02 = a[2];
96
96
  var a10 = a[3], a11 = a[4], a12 = a[5];
97
97
  var a20 = a[6], a21 = a[7], a22 = a[8];
@@ -286,7 +286,7 @@ function invert(out, a) {
286
286
  out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
287
287
  return out;
288
288
  }
289
- function multiply$1(out, a, b) {
289
+ function multiply(out, a, b) {
290
290
  var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
291
291
  var a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
292
292
  var a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
@@ -364,6 +364,26 @@ function translate(out, a, v) {
364
364
  }
365
365
  return out;
366
366
  }
367
+ function scale$1(out, a, v) {
368
+ var x2 = v[0], y = v[1], z = v[2];
369
+ out[0] = a[0] * x2;
370
+ out[1] = a[1] * x2;
371
+ out[2] = a[2] * x2;
372
+ out[3] = a[3] * x2;
373
+ out[4] = a[4] * y;
374
+ out[5] = a[5] * y;
375
+ out[6] = a[6] * y;
376
+ out[7] = a[7] * y;
377
+ out[8] = a[8] * z;
378
+ out[9] = a[9] * z;
379
+ out[10] = a[10] * z;
380
+ out[11] = a[11] * z;
381
+ out[12] = a[12];
382
+ out[13] = a[13];
383
+ out[14] = a[14];
384
+ out[15] = a[15];
385
+ return out;
386
+ }
367
387
  function rotateX(out, a, rad) {
368
388
  var s = Math.sin(rad);
369
389
  var c2 = Math.cos(rad);
@@ -500,7 +520,7 @@ function multiplyScalar(out, a, b) {
500
520
  out[15] = a[15] * b;
501
521
  return out;
502
522
  }
503
- var mul$1 = multiply$1;
523
+ var mul = multiply;
504
524
  function create$1() {
505
525
  var out = new ARRAY_TYPE(3);
506
526
  if (ARRAY_TYPE != Float32Array) {
@@ -609,6 +629,7 @@ function angle(a, b) {
609
629
  var ax = a[0], ay = a[1], az = a[2], bx = b[0], by = b[1], bz = b[2], mag1 = Math.sqrt(ax * ax + ay * ay + az * az), mag2 = Math.sqrt(bx * bx + by * by + bz * bz), mag = mag1 * mag2, cosine = mag && dot(a, b) / mag;
610
630
  return Math.acos(Math.min(Math.max(cosine, -1), 1));
611
631
  }
632
+ var sub = subtract;
612
633
  var len = length$1;
613
634
  (function() {
614
635
  var vec = create$1();
@@ -662,13 +683,6 @@ function add(out, a, b) {
662
683
  out[3] = a[3] + b[3];
663
684
  return out;
664
685
  }
665
- function multiply(out, a, b) {
666
- out[0] = a[0] * b[0];
667
- out[1] = a[1] * b[1];
668
- out[2] = a[2] * b[2];
669
- out[3] = a[3] * b[3];
670
- return out;
671
- }
672
686
  function transformMat4(out, a, m) {
673
687
  var x2 = a[0], y = a[1], z = a[2], w = a[3];
674
688
  out[0] = m[0] * x2 + m[4] * y + m[8] * z + m[12] * w;
@@ -677,7 +691,6 @@ function transformMat4(out, a, m) {
677
691
  out[3] = m[3] * x2 + m[7] * y + m[11] * z + m[15] * w;
678
692
  return out;
679
693
  }
680
- var mul = multiply;
681
694
  (function() {
682
695
  var vec = create();
683
696
  return function(a, stride, offset, count, fn, arg2) {
@@ -754,7 +767,20 @@ vec4 applyClip (vec3 dir, inout vec4 samplePos, inout float len, inout bool isCl
754
767
  samplePos = vec4(samplePos.xyz+dir * disBackFace, disBackFace);
755
768
  }
756
769
  return samplePos;
757
- }`;
770
+ }
771
+ float frac2ndc(vec3 frac) {
772
+ //https://stackoverflow.com/questions/7777913/how-to-render-depth-linearly-in-modern-opengl-with-gl-fragcoord-z-in-fragment-sh
773
+ vec4 pos = vec4(frac.xyz, 1.0); //fraction
774
+ vec4 dim = vec4(vec3(textureSize(volume, 0)), 1.0);
775
+ pos = pos * dim;
776
+ vec4 shim = vec4(-0.5, -0.5, -0.5, 0.0);
777
+ pos += shim;
778
+ vec4 mm = transpose(matRAS) * pos;
779
+ float z_ndc = (mvpMtx * vec4(mm.xyz, 1.0)).z;
780
+ return (z_ndc + 1.0) / 2.0;
781
+
782
+ }
783
+ `;
758
784
  var fragRenderShader = `#version 300 es
759
785
  #line 14
760
786
  precision highp int;
@@ -772,20 +798,11 @@ uniform mat4 matRAS;
772
798
  uniform vec4 clipPlaneColor;
773
799
  in vec3 vColor;
774
800
  out vec4 fColor;
775
- ` + kRenderFunc + `
776
- float frac2ndc(vec3 frac) {
777
- //https://stackoverflow.com/questions/7777913/how-to-render-depth-linearly-in-modern-opengl-with-gl-fragcoord-z-in-fragment-sh
778
- vec4 pos = vec4(frac.xyz, 1.0); //fraction
779
- vec4 dim = vec4(vec3(textureSize(volume, 0)), 1.0);
780
- pos = pos * dim;
781
- vec4 shim = vec4(-0.5, -0.5, -0.5, 0.0);
782
- pos += shim;
783
- vec4 mm = transpose(matRAS) * pos;
784
- float z_ndc = (mvpMtx * vec4(mm.xyz, 1.0)).z;
785
- return (z_ndc + 1.0) / 2.0;
786
-
787
- }
788
- void main() {
801
+ ` + kRenderFunc + `void main() {
802
+ if (fColor.x > 2.0) {
803
+ fColor = vec4(1.0, 0.0, 0.0, 0.5);
804
+ return;
805
+ }
789
806
  fColor = vec4(0.0,0.0,0.0,0.0);
790
807
  vec4 clipPlaneColorX = clipPlaneColor;
791
808
  //if (clipPlaneColor.a < 0.0)
@@ -821,19 +838,23 @@ void main() {
821
838
  vec4 deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast);
822
839
  while (samplePos.a <= len) {
823
840
  float val = texture(volume, samplePos.xyz).a;
824
- if (val > 0.01) break;
841
+ if (val > 0.01) {
842
+ break;
843
+ }
825
844
  samplePos += deltaDirFast; //advance ray position
826
845
  }
827
846
  if ((samplePos.a >= len) && ((overlays < 1.0) || (backgroundMasksOverlays > 0))) {
828
- if (isClip)
847
+ if (isClip){
829
848
  fColor += clipPlaneColorX;
849
+ }
830
850
  return;
831
851
  }
832
852
  fColor = vec4(1.0, 1.0, 1.0, 1.0);
833
853
  //gl_FragDepth = frac2ndc(samplePos.xyz); //crude due to fast pass resolution
834
854
  samplePos -= deltaDirFast;
835
- if (samplePos.a < 0.0)
855
+ if (samplePos.a < 0.0){
836
856
  vec4 samplePos = vec4(start.xyz, 0.0); //ray position
857
+ }
837
858
  //end: fast pass
838
859
  vec4 colAcc = vec4(0.0,0.0,0.0,0.0);
839
860
  vec4 firstHit = vec4(0.0,0.0,0.0,2.0 * lenNoClip);
@@ -844,25 +865,29 @@ void main() {
844
865
  while (samplePos.a <= len) {
845
866
  vec4 colorSample = texture(volume, samplePos.xyz);
846
867
  samplePos += deltaDir; //advance ray position
847
- if (colorSample.a < 0.01) continue;
848
- if (firstHit.a > lenNoClip)
849
- firstHit = samplePos;
850
- backNearest = min(backNearest, samplePos.a);
851
- colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
852
- colorSample.rgb *= colorSample.a;
853
- colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
854
- if ( colAcc.a > earlyTermination )
855
- break;
868
+ if (colorSample.a >= 0.01) {
869
+ if (firstHit.a > lenNoClip)
870
+ firstHit = samplePos;
871
+ backNearest = min(backNearest, samplePos.a);
872
+ colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
873
+ colorSample.rgb *= colorSample.a;
874
+ colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
875
+ if ( colAcc.a > earlyTermination )
876
+ break;
877
+ }
856
878
  }
857
- if (firstHit.a < len)
879
+ if (firstHit.a < len){
858
880
  gl_FragDepth = frac2ndc(firstHit.xyz);
881
+ }
859
882
  colAcc.a = (colAcc.a / earlyTermination) * backOpacity;
860
883
  fColor = colAcc;
861
884
  //if (isClip) //CR
862
885
  if ((isColorPlaneInVolume) && (clipPos.a != samplePos.a) && (abs(firstHit.a - clipPos.a) < deltaDir.a))
863
886
  fColor.rgb = mix(fColor.rgb, clipPlaneColorX.rgb, abs(clipPlaneColor.a));
864
887
  //fColor.rgb = mix(fColor.rgb, clipPlaneColorX.rgb, clipPlaneColorX.a * 0.65);
865
- if (overlays < 1.0) return;
888
+ if (overlays < 1.0) {
889
+ return;
890
+ }
866
891
  //overlay pass
867
892
  len = lenNoClip;
868
893
  samplePos = vec4(start.xyz, 0.0); //ray position
@@ -871,48 +896,56 @@ void main() {
871
896
  deltaDirFast = vec4(dir.xyz * stepSizeFast, stepSizeFast);
872
897
  while (samplePos.a <= len) {
873
898
  float val = texture(overlay, samplePos.xyz).a;
874
- if (val > 0.01) break;
899
+ if (val > 0.01) {
900
+ break;
901
+ }
875
902
  samplePos += deltaDirFast; //advance ray position
876
903
  }
877
904
  if (samplePos.a >= len) {
878
- if (isClip && (fColor.a == 0.0))
905
+ if (isClip && (fColor.a == 0.0)) {
879
906
  fColor += clipPlaneColorX;
907
+ }
880
908
  return;
881
909
  }
882
910
  samplePos -= deltaDirFast;
883
- if (samplePos.a < 0.0)
911
+ if (samplePos.a < 0.0){
884
912
  vec4 samplePos = vec4(start.xyz, 0.0); //ray position
913
+ }
885
914
  //end: fast pass
886
915
  float overFarthest = len;
887
916
  colAcc = vec4(0.0, 0.0, 0.0, 0.0);
888
917
  samplePos += deltaDir * ran; //jitter ray
889
918
  vec4 overFirstHit = vec4(0.0,0.0,0.0,2.0 * len);
890
- if (backgroundMasksOverlays > 0)
919
+ if (backgroundMasksOverlays > 0){
891
920
  samplePos = firstHit;
921
+ }
892
922
  while (samplePos.a <= len) {
893
923
  vec4 colorSample = texture(overlay, samplePos.xyz);
894
924
  samplePos += deltaDir; //advance ray position
895
- if (colorSample.a < 0.01) continue;
896
- if (overFirstHit.a > len)
897
- overFirstHit = samplePos;
898
- colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
899
- colorSample.rgb *= colorSample.a;
900
- colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
901
- overFarthest = samplePos.a;
902
- if ( colAcc.a > earlyTermination )
903
- break;
925
+ if (colorSample.a >= 0.01) {
926
+ if (overFirstHit.a > len)
927
+ overFirstHit = samplePos;
928
+ colorSample.a = 1.0-pow((1.0 - colorSample.a), opacityCorrection);
929
+ colorSample.rgb *= colorSample.a;
930
+ colAcc= (1.0 - colAcc.a) * colorSample + colAcc;
931
+ overFarthest = samplePos.a;
932
+ if ( colAcc.a > earlyTermination )
933
+ break;
934
+ }
904
935
  }
905
936
  if (samplePos.a >= len) {
906
- if (isClip && (fColor.a == 0.0))
937
+ if (isClip && (fColor.a == 0.0)){
907
938
  fColor += clipPlaneColorX;
939
+ }
908
940
  return;
909
941
  }
910
942
  //if (overFirstHit.a < len)
911
- gl_FragDepth = frac2ndc(overFirstHit.xyz);
943
+ gl_FragDepth = frac2ndc(overFirstHit.xyz);
912
944
  float overMix = colAcc.a;
913
945
  float overlayDepth = 0.3;
914
- if (fColor.a <= 0.0)
946
+ if (fColor.a <= 0.0){
915
947
  overMix = 1.0;
948
+ }
916
949
  else if (((overFarthest) > backNearest)) {
917
950
  float dx = (overFarthest - backNearest)/1.73;
918
951
  dx = fColor.a * pow(dx, overlayDepth);
@@ -927,15 +960,22 @@ layout(location=0) in vec3 pos;
927
960
  uniform int axCorSag;
928
961
  uniform float slice;
929
962
  uniform vec2 canvasWidthHeight;
963
+ uniform vec3 panXYscale;
930
964
  uniform vec4 leftTopWidthHeight;
931
965
  out vec3 texPos;
932
966
  void main(void) {
933
967
  //convert pixel x,y space 1..canvasWidth,1..canvasHeight to WebGL 1..-1,-1..1
934
968
  vec2 frac;
935
- frac.x = (leftTopWidthHeight.x + (pos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1
936
- frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - pos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0
969
+ vec3 vpos = pos;
970
+ frac.x = (leftTopWidthHeight.x + (vpos.x * leftTopWidthHeight.z)) / canvasWidthHeight.x; //0..1
971
+ frac.y = 1.0 - ((leftTopWidthHeight.y + ((1.0 - vpos.y) * leftTopWidthHeight.w)) / canvasWidthHeight.y); //1..0
972
+ //frac.x = pos.x; //0..1
973
+ //frac.y = pos.y; //1..0
974
+
937
975
  frac = (frac * 2.0) - 1.0;
938
976
  gl_Position = vec4(frac, 0.0, 1.0);
977
+ gl_Position.x += panXYscale.x;
978
+ gl_Position.y += panXYscale.y;
939
979
  if (axCorSag == 1)
940
980
  texPos = vec3(pos.x, slice, pos.y);
941
981
  else if (axCorSag == 2)
@@ -984,7 +1024,68 @@ void main() {
984
1024
  color.rgb = mix(color.rgb, ocolor.rgb, ocolor.a);
985
1025
  color.a = aout;
986
1026
  }`;
987
- var fragLineShader = `#version 300 es
1027
+ var vertSliceMMShader = `#version 300 es
1028
+ #line 4
1029
+ layout(location=0) in vec3 pos;
1030
+ uniform int axCorSag;
1031
+ uniform mat4 mvpMtx;
1032
+ uniform mat4 frac2mm;
1033
+ uniform float slice;
1034
+ out vec3 texPos;
1035
+ void main(void) {
1036
+ texPos = vec3(pos.x, pos.y, slice);
1037
+ if (axCorSag > 1)
1038
+ texPos = vec3(slice, pos.x, pos.y);
1039
+ else if (axCorSag > 0)
1040
+ texPos = vec3(pos.x, slice, pos.y);
1041
+ vec4 mm = frac2mm * vec4(texPos, 1.0);
1042
+ //vec4 mm = vec4(texPos, 1.0) * frac2mm;
1043
+ gl_Position = mvpMtx * mm;
1044
+ //gl_Position = mm;
1045
+ }`;
1046
+ var fragSliceMMShader = `#version 300 es
1047
+ #line 228
1048
+ precision highp int;
1049
+ precision highp float;
1050
+ uniform highp sampler3D volume, overlay;
1051
+ uniform int backgroundMasksOverlays;
1052
+ uniform float overlays;
1053
+ uniform float opacity;
1054
+ uniform float drawOpacity;
1055
+ uniform highp sampler3D drawing;
1056
+ in vec3 texPos;
1057
+ out vec4 color;
1058
+ void main() {
1059
+ //color = vec4(1.0, 0.0, 1.0, 1.0);return;
1060
+ vec4 background = texture(volume, texPos);
1061
+ color = vec4(background.rgb, opacity);
1062
+ vec4 ocolor = vec4(0.0);
1063
+ if (overlays > 0.0) {
1064
+ ocolor = texture(overlay, texPos);
1065
+ }
1066
+ float draw = texture(drawing, texPos).r;
1067
+ if (draw > 0.0) {
1068
+ vec3 dcolor = vec3(0.0, 0.0, 0.0);
1069
+ if (draw >= (4.0/255.0))
1070
+ dcolor.rgb = vec3(draw,0.0,draw);
1071
+ else if (draw >= (3.0/255.0))
1072
+ dcolor.b = 1.0;
1073
+ else if (draw >= (2.0/255.0))
1074
+ dcolor.g = 1.0;
1075
+ else
1076
+ dcolor.r = 1.0;
1077
+ color.rgb = mix(color.rgb, dcolor, drawOpacity);
1078
+ color.a = max(drawOpacity, color.a);
1079
+ }
1080
+ if ((backgroundMasksOverlays > 0) && (background.a == 0.0))
1081
+ return;
1082
+ //float aout = ocolor.a + (1.0 - ocolor.a) * color.a;
1083
+ //if (aout <= 0.0) return;
1084
+ //color.rgb = ((ocolor.rgb * ocolor.a) + (color.rgb * color.a * (1.0 - ocolor.a))) / aout;
1085
+ color.rgb = mix(color.rgb, ocolor.rgb, ocolor.a);
1086
+ color.a = 1.0;
1087
+ }`;
1088
+ var fragRectShader = `#version 300 es
988
1089
  #line 189
989
1090
  precision highp int;
990
1091
  precision highp float;
@@ -1013,10 +1114,13 @@ var fragColorbarShader = `#version 300 es
1013
1114
  precision highp int;
1014
1115
  precision highp float;
1015
1116
  uniform highp sampler2D colormap;
1117
+ uniform float layer;
1016
1118
  in vec2 vColor;
1017
1119
  out vec4 color;
1018
1120
  void main() {
1019
- color = vec4(texture(colormap, vColor).rgb, 1.0);
1121
+ float nlayer = float(textureSize(colormap, 0).y);
1122
+ float fmap = (0.5 + layer) / nlayer;
1123
+ color = vec4(texture(colormap, vec2(vColor.x, fmap)).rgb, 1.0);
1020
1124
  }`;
1021
1125
  var vertGraphShader = `#version 300 es
1022
1126
  #line 229
@@ -1057,7 +1161,7 @@ void main(void) {
1057
1161
  frac = (frac * 2.0) - 1.0;
1058
1162
  gl_Position = vec4(frac, 0.0, 1.0);
1059
1163
  }`;
1060
- var vertLineShader = `#version 300 es
1164
+ var vertRectShader = `#version 300 es
1061
1165
  #line 229
1062
1166
  layout(location=0) in vec3 pos;
1063
1167
  uniform vec2 canvasWidthHeight;
@@ -1070,6 +1174,20 @@ void main(void) {
1070
1174
  frac = (frac * 2.0) - 1.0;
1071
1175
  gl_Position = vec4(frac, 0.0, 1.0);
1072
1176
  }`;
1177
+ var vertLineShader = `#version 300 es
1178
+ #line 229
1179
+ layout(location=0) in vec3 pos;
1180
+ uniform vec2 canvasWidthHeight;
1181
+ uniform float thickness;
1182
+ uniform vec4 startXYendXY;
1183
+ void main(void) {
1184
+ vec2 posXY = mix(startXYendXY.xy, startXYendXY.zw, pos.x);
1185
+ vec2 dir = normalize(startXYendXY.xy - startXYendXY.zw);
1186
+ posXY += vec2(-dir.y, dir.x) * thickness * (pos.y - 0.5);
1187
+ posXY.x = (posXY.x) / canvasWidthHeight.x; //0..1
1188
+ posXY.y = 1.0 - (posXY.y / canvasWidthHeight.y); //1..0
1189
+ gl_Position = vec4((posXY * 2.0) - 1.0, 0.0, 1.0);
1190
+ }`;
1073
1191
  var vertBmpShader = `#version 300 es
1074
1192
  #line 229
1075
1193
  layout(location=0) in vec3 pos;
@@ -1156,7 +1274,7 @@ in vec2 TexCoord;
1156
1274
  out vec4 FragColor;
1157
1275
  uniform float coordZ;
1158
1276
  uniform float layer;
1159
- uniform float numLayers;
1277
+ //uniform float numLayers;
1160
1278
  uniform highp sampler2D colormap;
1161
1279
  uniform lowp sampler3D blend3D;
1162
1280
  uniform float opacity;
@@ -1186,7 +1304,9 @@ void main(void) {
1186
1304
  }
1187
1305
  idx = ((idx - uint(1)) % uint(100))+uint(1);
1188
1306
  float fx = (float(idx)+0.5) / 256.0;
1189
- float y = (2.0 * layer + 1.0)/(2.0 * numLayers);
1307
+ float nlayer = float(textureSize(colormap, 0).y) * 0.5; //0.5 as both each layer has positive and negative color slot
1308
+ float y = (2.0 * layer + 1.0)/(4.0 * nlayer);
1309
+ //float y = (2.0 * layer + 1.0)/(4.0 * numLayers);
1190
1310
  FragColor = texture(colormap, vec2(fx, y)).rgba;
1191
1311
  FragColor.a *= opacity;
1192
1312
  if (layer < 2.0) return;
@@ -1205,28 +1325,43 @@ in vec2 TexCoord;
1205
1325
  out vec4 FragColor;
1206
1326
  uniform float coordZ;
1207
1327
  uniform float layer;
1208
- uniform float numLayers;
1328
+ //uniform float numLayers;
1209
1329
  uniform float scl_slope;
1210
1330
  uniform float scl_inter;
1211
1331
  uniform float cal_max;
1212
1332
  uniform float cal_min;
1213
1333
  uniform highp sampler2D colormap;
1214
1334
  uniform lowp sampler3D blend3D;
1335
+ uniform int modulation;
1336
+ uniform highp sampler3D modulationVol;
1215
1337
  uniform float opacity;
1216
1338
  uniform mat4 mtx;
1217
1339
  void main(void) {
1218
1340
  vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx;
1219
1341
  float f = (scl_slope * float(texture(intensityVol, vx.xyz).r)) + scl_inter;
1342
+ bool isNegative = (f < 0.0);
1220
1343
  float r = max(0.00001, abs(cal_max - cal_min));
1221
1344
  float mn = min(cal_min, cal_max);
1222
- f = mix(0.0, 1.0, (f - mn) / r);
1223
- //float y = 1.0 / numLayers;
1224
- //y = ((layer + 0.5) * y);
1345
+ float txl = mix(0.0, 1.0, (f - mn) / r);
1225
1346
  //https://stackoverflow.com/questions/5879403/opengl-texture-coordinates-in-pixel-space
1226
- float y = (2.0 * layer + 1.0)/(2.0 * numLayers);
1227
- FragColor = texture(colormap, vec2(f, y)).rgba;
1347
+ float nlayer = float(textureSize(colormap, 0).y) * 0.5; //0.5 as both each layer has positive and negative color slot
1348
+ float y = (2.0 * layer + 1.0)/(4.0 * nlayer);
1349
+ FragColor = texture(colormap, vec2(txl, y)).rgba;
1350
+ if (isNegative) {
1351
+ y = (2.0 * layer + nlayer + nlayer + 1.0)/(4.0 * nlayer);
1352
+ //select texels at positions 0 and 1 of lookup table:
1353
+ vec4 v0 = texture(colormap, vec2(0.5/256.0, y));
1354
+ vec4 v1 = texture(colormap, vec2(1.5/256.0, y));
1355
+ txl = mix(0.0, 1.0, (- f - mn) / r);
1356
+ //detect bogus color: negative color slot not used than
1357
+ // v0 = 1,1,1,0 and v1 = 0,0,0,1
1358
+ if ((v0.r != 1.0) || (v0.a != 0.0) || (v1.r != 0.0) || (v1.a != 1.0))
1359
+ FragColor = texture(colormap, vec2(txl, y));
1360
+ }
1228
1361
  if (layer > 0.7)
1229
1362
  FragColor.a = step(0.00001, FragColor.a);
1363
+ if (modulation > 0)
1364
+ FragColor.rgb *= texture(modulationVol, vx.xyz).r;
1230
1365
  FragColor.a *= opacity;
1231
1366
  if (layer < 1.0) return;
1232
1367
  vec2 texXY = TexCoord.xy*0.5 +vec2(0.5,0.5);
@@ -1244,7 +1379,7 @@ in vec2 TexCoord;
1244
1379
  out vec4 FragColor;
1245
1380
  uniform float coordZ;
1246
1381
  uniform float layer;
1247
- uniform float numLayers;
1382
+ //uniform float numLayers;
1248
1383
  uniform float scl_slope;
1249
1384
  uniform float scl_inter;
1250
1385
  uniform float cal_max;
@@ -1254,10 +1389,14 @@ uniform lowp sampler3D blend3D;
1254
1389
  uniform float opacity;
1255
1390
  uniform mat4 mtx;
1256
1391
  uniform bool hasAlpha;
1392
+ uniform int modulation;
1393
+ uniform highp sampler3D modulationVol;
1257
1394
  void main(void) {
1258
1395
  vec4 vx = vec4(TexCoord.xy, coordZ, 1.0) * mtx;
1259
1396
  uvec4 aColor = texture(intensityVol, vx.xyz);
1260
1397
  FragColor = vec4(float(aColor.r) / 255.0, float(aColor.g) / 255.0, float(aColor.b) / 255.0, float(aColor.a) / 255.0);
1398
+ if (modulation > 0)
1399
+ FragColor.rgb *= texture(modulationVol, vx.xyz).r;
1261
1400
  if (!hasAlpha)
1262
1401
  FragColor.a = (FragColor.r * 0.21 + FragColor.g * 0.72 + FragColor.b * 0.07);
1263
1402
  FragColor.a *= opacity;
@@ -1404,6 +1543,21 @@ void main(void) {
1404
1543
  vV = -vec3(modelMtx*vec4(pos,1.0));
1405
1544
  vClr = clr;
1406
1545
  }`;
1546
+ var fragMeshDepthShader = `#version 300 es
1547
+ precision highp int;
1548
+ precision highp float;
1549
+ uniform float opacity;
1550
+ out vec4 color;
1551
+ vec4 packFloatToVec4i(const float value) {
1552
+ const vec4 bitSh = vec4(256.0*256.0*256.0, 256.0*256.0, 256.0, 1.0);
1553
+ const vec4 bitMsk = vec4(0.0, 1.0/256.0, 1.0/256.0, 1.0/256.0);
1554
+ vec4 res = fract(value * bitSh);
1555
+ res -= res.xxyz * bitMsk;
1556
+ return res;
1557
+ }
1558
+ void main() {
1559
+ color = packFloatToVec4i(gl_FragCoord.z);
1560
+ }`;
1407
1561
  var fragMeshToonShader = `#version 300 es
1408
1562
  precision highp int;
1409
1563
  precision highp float;
@@ -1599,15 +1753,6 @@ void main() {
1599
1753
  vec3 d = vClr.rgb * diffuse * SH(-reflect(n, vec3(l.x, l.y, -l.z)) );
1600
1754
  color = vec4(a + d + s, opacity);
1601
1755
  }`;
1602
- var fragDepthPickingShader = `#version 300 es
1603
- precision highp int;
1604
- precision highp float;
1605
- uniform int id;
1606
- in vec3 vColor;
1607
- out vec4 color;
1608
- void main() {
1609
- color = vec4(vColor, float(id & 255) / 255.0);
1610
- }`;
1611
1756
  var fragVolumePickingShader = `#version 300 es
1612
1757
  #line 506
1613
1758
  //precision highp int;
@@ -1618,12 +1763,15 @@ uniform vec3 texVox;
1618
1763
  uniform vec4 clipPlane;
1619
1764
  uniform highp sampler3D volume, overlay;
1620
1765
  uniform float overlays;
1621
- uniform int id;
1766
+ uniform mat4 matRAS;
1767
+ uniform mat4 mvpMtx;
1622
1768
  in vec3 vColor;
1623
1769
  out vec4 fColor;
1624
1770
  ` + kRenderFunc + `
1625
1771
  void main() {
1772
+ int id = 254;
1626
1773
  vec3 start = vColor;
1774
+ gl_FragDepth = 0.0;
1627
1775
  fColor = vec4(0.0, 0.0, 0.0, 0.0); //assume no hit: ID = 0
1628
1776
  float fid = float(id & 255)/ 255.0;
1629
1777
  vec3 backPosition = GetBackPosition(start);
@@ -1647,6 +1795,7 @@ void main() {
1647
1795
  float val = texture(volume, samplePos.xyz).a;
1648
1796
  if (val > 0.01) {
1649
1797
  fColor = vec4(samplePos.rgb, fid);
1798
+ gl_FragDepth = frac2ndc(samplePos.xyz);
1650
1799
  break;
1651
1800
  }
1652
1801
  samplePos += deltaDirFast; //advance ray position
@@ -1663,6 +1812,7 @@ void main() {
1663
1812
  float val = texture(overlay, samplePos.xyz).a;
1664
1813
  if (val > 0.01) {
1665
1814
  fColor = vec4(samplePos.rgb, fid);
1815
+ gl_FragDepth = frac2ndc(samplePos.xyz);
1666
1816
  return;
1667
1817
  }
1668
1818
  samplePos += deltaDirFast; //advance ray position
@@ -1670,6 +1820,34 @@ void main() {
1670
1820
  //if (fColor.a == 0.0) discard; //no hit in either background or overlays
1671
1821
  //you only get here if there is a hit with the background that is closer than any overlay
1672
1822
  }`;
1823
+ var vertOrientCubeShader = `#version 300 es
1824
+ // an attribute is an input (in) to a vertex shader.
1825
+ // It will receive data from a buffer
1826
+ layout(location=0) in vec3 a_position;
1827
+ layout(location=1) in vec3 a_color;
1828
+ // A matrix to transform the positions by
1829
+ uniform mat4 u_matrix;
1830
+ out vec3 vColor;
1831
+ // all shaders have a main function
1832
+ void main() {
1833
+ // Multiply the position by the matrix.
1834
+ vec4 pos = vec4(a_position, 1.0);
1835
+ gl_Position = u_matrix * vec4(pos);
1836
+ vColor = a_color;
1837
+ }
1838
+ `;
1839
+ var fragOrientCubeShader = `#version 300 es
1840
+ precision highp float;
1841
+
1842
+ uniform vec4 u_color;
1843
+ in vec3 vColor;
1844
+ // we need to declare an output for the fragment shader
1845
+ out vec4 outColor;
1846
+
1847
+ void main() {
1848
+ outColor = vec4(vColor, 1.0);
1849
+ }
1850
+ `;
1673
1851
  /*! *****************************************************************************
1674
1852
  Copyright (c) Microsoft Corporation.
1675
1853
 
@@ -2709,6 +2887,1016 @@ function interval(period, scheduler) {
2709
2887
  }
2710
2888
  return timer(period, period, scheduler);
2711
2889
  }
2890
+ const orientCube = new Float32Array([
2891
+ -1,
2892
+ -1,
2893
+ -1,
2894
+ 0.28,
2895
+ 0.28,
2896
+ 0.28,
2897
+ -1,
2898
+ -1,
2899
+ -1,
2900
+ 0.28,
2901
+ 0.28,
2902
+ 0.28,
2903
+ -1,
2904
+ 1,
2905
+ -1,
2906
+ 0.28,
2907
+ 0.28,
2908
+ 0.28,
2909
+ 1,
2910
+ -1,
2911
+ -1,
2912
+ 0.28,
2913
+ 0.28,
2914
+ 0.28,
2915
+ 1,
2916
+ 1,
2917
+ -1,
2918
+ 0.28,
2919
+ 0.28,
2920
+ 0.28,
2921
+ 1,
2922
+ 1,
2923
+ -1,
2924
+ 0.28,
2925
+ 0.28,
2926
+ 0.28,
2927
+ -1,
2928
+ -1,
2929
+ 1,
2930
+ 0.8,
2931
+ 0.8,
2932
+ 0.8,
2933
+ -1,
2934
+ -1,
2935
+ 1,
2936
+ 0.8,
2937
+ 0.8,
2938
+ 0.8,
2939
+ 1,
2940
+ -1,
2941
+ 1,
2942
+ 0.8,
2943
+ 0.8,
2944
+ 0.8,
2945
+ -1,
2946
+ 1,
2947
+ 1,
2948
+ 0.8,
2949
+ 0.8,
2950
+ 0.8,
2951
+ 1,
2952
+ 1,
2953
+ 1,
2954
+ 0.8,
2955
+ 0.8,
2956
+ 0.8,
2957
+ 1,
2958
+ 1,
2959
+ 1,
2960
+ 0.8,
2961
+ 0.8,
2962
+ 0.8,
2963
+ -1,
2964
+ 1,
2965
+ -1,
2966
+ 0,
2967
+ 0,
2968
+ 0.74,
2969
+ -1,
2970
+ 1,
2971
+ -1,
2972
+ 0,
2973
+ 0,
2974
+ 0.74,
2975
+ -1,
2976
+ 1,
2977
+ 1,
2978
+ 0,
2979
+ 0,
2980
+ 0.74,
2981
+ 1,
2982
+ 1,
2983
+ -1,
2984
+ 0,
2985
+ 0,
2986
+ 0.74,
2987
+ 1,
2988
+ 1,
2989
+ 1,
2990
+ 0,
2991
+ 0,
2992
+ 0.74,
2993
+ 1,
2994
+ 1,
2995
+ 1,
2996
+ 0,
2997
+ 0,
2998
+ 0.74,
2999
+ -1,
3000
+ -1,
3001
+ -1,
3002
+ 0.42,
3003
+ 0,
3004
+ 0.42,
3005
+ -1,
3006
+ -1,
3007
+ -1,
3008
+ 0.42,
3009
+ 0,
3010
+ 0.42,
3011
+ 1,
3012
+ -1,
3013
+ -1,
3014
+ 0.42,
3015
+ 0,
3016
+ 0.42,
3017
+ -1,
3018
+ -1,
3019
+ 1,
3020
+ 0.42,
3021
+ 0,
3022
+ 0.42,
3023
+ 1,
3024
+ -1,
3025
+ 1,
3026
+ 0.42,
3027
+ 0,
3028
+ 0.42,
3029
+ 1,
3030
+ -1,
3031
+ 1,
3032
+ 0.42,
3033
+ 0,
3034
+ 0.42,
3035
+ -1,
3036
+ -1,
3037
+ -1,
3038
+ 0.64,
3039
+ 0,
3040
+ 0,
3041
+ -1,
3042
+ -1,
3043
+ -1,
3044
+ 0.64,
3045
+ 0,
3046
+ 0,
3047
+ -1,
3048
+ -1,
3049
+ 1,
3050
+ 0.64,
3051
+ 0,
3052
+ 0,
3053
+ -1,
3054
+ 1,
3055
+ -1,
3056
+ 0.64,
3057
+ 0,
3058
+ 0,
3059
+ -1,
3060
+ 1,
3061
+ 1,
3062
+ 0.64,
3063
+ 0,
3064
+ 0,
3065
+ -1,
3066
+ 1,
3067
+ 1,
3068
+ 0.64,
3069
+ 0,
3070
+ 0,
3071
+ 1,
3072
+ -1,
3073
+ -1,
3074
+ 0,
3075
+ 0.5,
3076
+ 0,
3077
+ 1,
3078
+ -1,
3079
+ -1,
3080
+ 0,
3081
+ 0.5,
3082
+ 0,
3083
+ 1,
3084
+ 1,
3085
+ -1,
3086
+ 0,
3087
+ 0.5,
3088
+ 0,
3089
+ 1,
3090
+ -1,
3091
+ 1,
3092
+ 0,
3093
+ 0.5,
3094
+ 0,
3095
+ 1,
3096
+ 1,
3097
+ 1,
3098
+ 0,
3099
+ 0.5,
3100
+ 0,
3101
+ 1,
3102
+ 1,
3103
+ 1,
3104
+ 0,
3105
+ 0.5,
3106
+ 0,
3107
+ -0.45,
3108
+ 1,
3109
+ -0.8,
3110
+ 0,
3111
+ 0,
3112
+ 0,
3113
+ -0.45,
3114
+ 1,
3115
+ -0.8,
3116
+ 0,
3117
+ 0,
3118
+ 0,
3119
+ -0.45,
3120
+ 1,
3121
+ 0.8,
3122
+ 0,
3123
+ 0,
3124
+ 0,
3125
+ -0.25,
3126
+ 1,
3127
+ -0.8,
3128
+ 0,
3129
+ 0,
3130
+ 0,
3131
+ -0.25,
3132
+ 1,
3133
+ 0.8,
3134
+ 0,
3135
+ 0,
3136
+ 0,
3137
+ -0.25,
3138
+ 1,
3139
+ 0.8,
3140
+ 0,
3141
+ 0,
3142
+ 0,
3143
+ -0.25,
3144
+ 1,
3145
+ 0.6,
3146
+ 0,
3147
+ 0,
3148
+ 0,
3149
+ -0.25,
3150
+ 1,
3151
+ 0.6,
3152
+ 0,
3153
+ 0,
3154
+ 0,
3155
+ -0.25,
3156
+ 1,
3157
+ 0.8,
3158
+ 0,
3159
+ 0,
3160
+ 0,
3161
+ 0.45,
3162
+ 1,
3163
+ 0.6,
3164
+ 0,
3165
+ 0,
3166
+ 0,
3167
+ 0.25,
3168
+ 1,
3169
+ 0.8,
3170
+ 0,
3171
+ 0,
3172
+ 0,
3173
+ 0.25,
3174
+ 1,
3175
+ 0.8,
3176
+ 0,
3177
+ 0,
3178
+ 0,
3179
+ 0.25,
3180
+ 1,
3181
+ 0.1,
3182
+ 0,
3183
+ 0,
3184
+ 0,
3185
+ 0.25,
3186
+ 1,
3187
+ 0.1,
3188
+ 0,
3189
+ 0,
3190
+ 0,
3191
+ 0.25,
3192
+ 1,
3193
+ 0.6,
3194
+ 0,
3195
+ 0,
3196
+ 0,
3197
+ 0.45,
3198
+ 1,
3199
+ 0.1,
3200
+ 0,
3201
+ 0,
3202
+ 0,
3203
+ 0.45,
3204
+ 1,
3205
+ 0.6,
3206
+ 0,
3207
+ 0,
3208
+ 0,
3209
+ 0.45,
3210
+ 1,
3211
+ 0.6,
3212
+ 0,
3213
+ 0,
3214
+ 0,
3215
+ -0.25,
3216
+ 1,
3217
+ -0.1,
3218
+ 0,
3219
+ 0,
3220
+ 0,
3221
+ -0.25,
3222
+ 1,
3223
+ -0.1,
3224
+ 0,
3225
+ 0,
3226
+ 0,
3227
+ -0.25,
3228
+ 1,
3229
+ 0.1,
3230
+ 0,
3231
+ 0,
3232
+ 0,
3233
+ 0.25,
3234
+ 1,
3235
+ -0.1,
3236
+ 0,
3237
+ 0,
3238
+ 0,
3239
+ 0.45,
3240
+ 1,
3241
+ 0.1,
3242
+ 0,
3243
+ 0,
3244
+ 0,
3245
+ 0.45,
3246
+ 1,
3247
+ 0.1,
3248
+ 0,
3249
+ 0,
3250
+ 0,
3251
+ 0.45,
3252
+ -1,
3253
+ -0.8,
3254
+ 0,
3255
+ 0,
3256
+ 0,
3257
+ 0.45,
3258
+ -1,
3259
+ -0.8,
3260
+ 0,
3261
+ 0,
3262
+ 0,
3263
+ 0.05,
3264
+ -1,
3265
+ 0.8,
3266
+ 0,
3267
+ 0,
3268
+ 0,
3269
+ 0.25,
3270
+ -1,
3271
+ -0.8,
3272
+ 0,
3273
+ 0,
3274
+ 0,
3275
+ -0.15,
3276
+ -1,
3277
+ 0.8,
3278
+ 0,
3279
+ 0,
3280
+ 0,
3281
+ -0.15,
3282
+ -1,
3283
+ 0.8,
3284
+ 0,
3285
+ 0,
3286
+ 0,
3287
+ -0.25,
3288
+ -1,
3289
+ -0.8,
3290
+ 0,
3291
+ 0,
3292
+ 0,
3293
+ -0.25,
3294
+ -1,
3295
+ -0.8,
3296
+ 0,
3297
+ 0,
3298
+ 0,
3299
+ 0.05,
3300
+ -1,
3301
+ 0.8,
3302
+ 0,
3303
+ 0,
3304
+ 0,
3305
+ -0.45,
3306
+ -1,
3307
+ -0.8,
3308
+ 0,
3309
+ 0,
3310
+ 0,
3311
+ -0.15,
3312
+ -1,
3313
+ 0.8,
3314
+ 0,
3315
+ 0,
3316
+ 0,
3317
+ -0.15,
3318
+ -1,
3319
+ 0.8,
3320
+ 0,
3321
+ 0,
3322
+ 0,
3323
+ 0.13,
3324
+ -1,
3325
+ -0.3,
3326
+ 0,
3327
+ 0,
3328
+ 0,
3329
+ 0.13,
3330
+ -1,
3331
+ -0.3,
3332
+ 0,
3333
+ 0,
3334
+ 0,
3335
+ 0.07,
3336
+ -1,
3337
+ -0.1,
3338
+ 0,
3339
+ 0,
3340
+ 0,
3341
+ -0.33,
3342
+ -1,
3343
+ -0.3,
3344
+ 0,
3345
+ 0,
3346
+ 0,
3347
+ -0.27,
3348
+ -1,
3349
+ -0.1,
3350
+ 0,
3351
+ 0,
3352
+ 0,
3353
+ -0.27,
3354
+ -1,
3355
+ -0.1,
3356
+ 0,
3357
+ 0,
3358
+ 0,
3359
+ -0.45,
3360
+ 0.6,
3361
+ 1,
3362
+ 0,
3363
+ 0,
3364
+ 0,
3365
+ -0.45,
3366
+ 0.6,
3367
+ 1,
3368
+ 0,
3369
+ 0,
3370
+ 0,
3371
+ -0.45,
3372
+ 0.4,
3373
+ 1,
3374
+ 0,
3375
+ 0,
3376
+ 0,
3377
+ -0.25,
3378
+ 0.8,
3379
+ 1,
3380
+ 0,
3381
+ 0,
3382
+ 0,
3383
+ -0.25,
3384
+ 0.4,
3385
+ 1,
3386
+ 0,
3387
+ 0,
3388
+ 0,
3389
+ -0.25,
3390
+ 0.4,
3391
+ 1,
3392
+ 0,
3393
+ 0,
3394
+ 0,
3395
+ -0.25,
3396
+ 0.8,
3397
+ 1,
3398
+ 0,
3399
+ 0,
3400
+ 0,
3401
+ -0.25,
3402
+ 0.8,
3403
+ 1,
3404
+ 0,
3405
+ 0,
3406
+ 0,
3407
+ -0.25,
3408
+ 0.6,
3409
+ 1,
3410
+ 0,
3411
+ 0,
3412
+ 0,
3413
+ 0.25,
3414
+ 0.8,
3415
+ 1,
3416
+ 0,
3417
+ 0,
3418
+ 0,
3419
+ 0.45,
3420
+ 0.6,
3421
+ 1,
3422
+ 0,
3423
+ 0,
3424
+ 0,
3425
+ 0.45,
3426
+ 0.6,
3427
+ 1,
3428
+ 0,
3429
+ 0,
3430
+ 0,
3431
+ 0.25,
3432
+ 0.8,
3433
+ 1,
3434
+ 0,
3435
+ 0,
3436
+ 0,
3437
+ 0.25,
3438
+ 0.8,
3439
+ 1,
3440
+ 0,
3441
+ 0,
3442
+ 0,
3443
+ 0.25,
3444
+ -0.1,
3445
+ 1,
3446
+ 0,
3447
+ 0,
3448
+ 0,
3449
+ 0.45,
3450
+ 0.6,
3451
+ 1,
3452
+ 0,
3453
+ 0,
3454
+ 0,
3455
+ 0.45,
3456
+ 0.1,
3457
+ 1,
3458
+ 0,
3459
+ 0,
3460
+ 0,
3461
+ 0.45,
3462
+ 0.1,
3463
+ 1,
3464
+ 0,
3465
+ 0,
3466
+ 0,
3467
+ -0.25,
3468
+ 0.1,
3469
+ 1,
3470
+ 0,
3471
+ 0,
3472
+ 0,
3473
+ -0.25,
3474
+ 0.1,
3475
+ 1,
3476
+ 0,
3477
+ 0,
3478
+ 0,
3479
+ -0.45,
3480
+ -0.1,
3481
+ 1,
3482
+ 0,
3483
+ 0,
3484
+ 0,
3485
+ 0.25,
3486
+ 0.1,
3487
+ 1,
3488
+ 0,
3489
+ 0,
3490
+ 0,
3491
+ 0.25,
3492
+ -0.1,
3493
+ 1,
3494
+ 0,
3495
+ 0,
3496
+ 0,
3497
+ 0.25,
3498
+ -0.1,
3499
+ 1,
3500
+ 0,
3501
+ 0,
3502
+ 0,
3503
+ -0.45,
3504
+ -0.1,
3505
+ 1,
3506
+ 0,
3507
+ 0,
3508
+ 0,
3509
+ -0.45,
3510
+ -0.1,
3511
+ 1,
3512
+ 0,
3513
+ 0,
3514
+ 0,
3515
+ -0.45,
3516
+ -0.6,
3517
+ 1,
3518
+ 0,
3519
+ 0,
3520
+ 0,
3521
+ -0.25,
3522
+ -0.1,
3523
+ 1,
3524
+ 0,
3525
+ 0,
3526
+ 0,
3527
+ -0.25,
3528
+ -0.8,
3529
+ 1,
3530
+ 0,
3531
+ 0,
3532
+ 0,
3533
+ -0.25,
3534
+ -0.8,
3535
+ 1,
3536
+ 0,
3537
+ 0,
3538
+ 0,
3539
+ -0.25,
3540
+ -0.6,
3541
+ 1,
3542
+ 0,
3543
+ 0,
3544
+ 0,
3545
+ -0.25,
3546
+ -0.6,
3547
+ 1,
3548
+ 0,
3549
+ 0,
3550
+ 0,
3551
+ -0.25,
3552
+ -0.8,
3553
+ 1,
3554
+ 0,
3555
+ 0,
3556
+ 0,
3557
+ 0.45,
3558
+ -0.6,
3559
+ 1,
3560
+ 0,
3561
+ 0,
3562
+ 0,
3563
+ 0.25,
3564
+ -0.8,
3565
+ 1,
3566
+ 0,
3567
+ 0,
3568
+ 0,
3569
+ 0.25,
3570
+ -0.8,
3571
+ 1,
3572
+ 0,
3573
+ 0,
3574
+ 0,
3575
+ 0.25,
3576
+ -0.4,
3577
+ 1,
3578
+ 0,
3579
+ 0,
3580
+ 0,
3581
+ 0.25,
3582
+ -0.4,
3583
+ 1,
3584
+ 0,
3585
+ 0,
3586
+ 0,
3587
+ 0.25,
3588
+ -0.6,
3589
+ 1,
3590
+ 0,
3591
+ 0,
3592
+ 0,
3593
+ 0.45,
3594
+ -0.4,
3595
+ 1,
3596
+ 0,
3597
+ 0,
3598
+ 0,
3599
+ 0.45,
3600
+ -0.6,
3601
+ 1,
3602
+ 0,
3603
+ 0,
3604
+ 0,
3605
+ 0.45,
3606
+ -0.6,
3607
+ 1,
3608
+ 0,
3609
+ 0,
3610
+ 0,
3611
+ -0.1,
3612
+ -0.8,
3613
+ -1,
3614
+ 0,
3615
+ 0,
3616
+ 0,
3617
+ -0.1,
3618
+ -0.8,
3619
+ -1,
3620
+ 0,
3621
+ 0,
3622
+ 0,
3623
+ -0.1,
3624
+ 0.8,
3625
+ -1,
3626
+ 0,
3627
+ 0,
3628
+ 0,
3629
+ 0.1,
3630
+ -0.8,
3631
+ -1,
3632
+ 0,
3633
+ 0,
3634
+ 0,
3635
+ 0.1,
3636
+ 0.8,
3637
+ -1,
3638
+ 0,
3639
+ 0,
3640
+ 0,
3641
+ 0.1,
3642
+ 0.8,
3643
+ -1,
3644
+ 0,
3645
+ 0,
3646
+ 0,
3647
+ -1,
3648
+ -0.45,
3649
+ -0.8,
3650
+ 0,
3651
+ 0,
3652
+ 0,
3653
+ -1,
3654
+ -0.45,
3655
+ -0.8,
3656
+ 0,
3657
+ 0,
3658
+ 0,
3659
+ -1,
3660
+ -0.45,
3661
+ 0.8,
3662
+ 0,
3663
+ 0,
3664
+ 0,
3665
+ -1,
3666
+ -0.25,
3667
+ -0.8,
3668
+ 0,
3669
+ 0,
3670
+ 0,
3671
+ -1,
3672
+ -0.25,
3673
+ 0.8,
3674
+ 0,
3675
+ 0,
3676
+ 0,
3677
+ -1,
3678
+ -0.25,
3679
+ 0.8,
3680
+ 0,
3681
+ 0,
3682
+ 0,
3683
+ -1,
3684
+ -0.25,
3685
+ -0.8,
3686
+ 0,
3687
+ 0,
3688
+ 0,
3689
+ -1,
3690
+ -0.25,
3691
+ -0.8,
3692
+ 0,
3693
+ 0,
3694
+ 0,
3695
+ -1,
3696
+ -0.25,
3697
+ -0.6,
3698
+ 0,
3699
+ 0,
3700
+ 0,
3701
+ -1,
3702
+ 0.45,
3703
+ -0.8,
3704
+ 0,
3705
+ 0,
3706
+ 0,
3707
+ -1,
3708
+ 0.45,
3709
+ -0.6,
3710
+ 0,
3711
+ 0,
3712
+ 0,
3713
+ -1,
3714
+ 0.45,
3715
+ -0.6,
3716
+ 0,
3717
+ 0,
3718
+ 0,
3719
+ 1,
3720
+ 0.45,
3721
+ -0.8,
3722
+ 0,
3723
+ 0,
3724
+ 0,
3725
+ 1,
3726
+ 0.45,
3727
+ -0.8,
3728
+ 0,
3729
+ 0,
3730
+ 0,
3731
+ 1,
3732
+ 0.45,
3733
+ 0.8,
3734
+ 0,
3735
+ 0,
3736
+ 0,
3737
+ 1,
3738
+ 0.25,
3739
+ -0.8,
3740
+ 0,
3741
+ 0,
3742
+ 0,
3743
+ 1,
3744
+ 0.25,
3745
+ 0.8,
3746
+ 0,
3747
+ 0,
3748
+ 0,
3749
+ 1,
3750
+ 0.25,
3751
+ 0.8,
3752
+ 0,
3753
+ 0,
3754
+ 0,
3755
+ 1,
3756
+ 0.25,
3757
+ 0.6,
3758
+ 0,
3759
+ 0,
3760
+ 0,
3761
+ 1,
3762
+ 0.25,
3763
+ 0.6,
3764
+ 0,
3765
+ 0,
3766
+ 0,
3767
+ 1,
3768
+ 0.25,
3769
+ 0.8,
3770
+ 0,
3771
+ 0,
3772
+ 0,
3773
+ 1,
3774
+ -0.45,
3775
+ 0.6,
3776
+ 0,
3777
+ 0,
3778
+ 0,
3779
+ 1,
3780
+ -0.25,
3781
+ 0.8,
3782
+ 0,
3783
+ 0,
3784
+ 0,
3785
+ 1,
3786
+ -0.25,
3787
+ 0.8,
3788
+ 0,
3789
+ 0,
3790
+ 0,
3791
+ 1,
3792
+ -0.25,
3793
+ 0.1,
3794
+ 0,
3795
+ 0,
3796
+ 0,
3797
+ 1,
3798
+ -0.25,
3799
+ 0.1,
3800
+ 0,
3801
+ 0,
3802
+ 0,
3803
+ 1,
3804
+ -0.25,
3805
+ 0.6,
3806
+ 0,
3807
+ 0,
3808
+ 0,
3809
+ 1,
3810
+ -0.45,
3811
+ 0.1,
3812
+ 0,
3813
+ 0,
3814
+ 0,
3815
+ 1,
3816
+ -0.45,
3817
+ 0.6,
3818
+ 0,
3819
+ 0,
3820
+ 0,
3821
+ 1,
3822
+ -0.45,
3823
+ 0.6,
3824
+ 0,
3825
+ 0,
3826
+ 0,
3827
+ 1,
3828
+ 0.25,
3829
+ -0.1,
3830
+ 0,
3831
+ 0,
3832
+ 0,
3833
+ 1,
3834
+ 0.25,
3835
+ -0.1,
3836
+ 0,
3837
+ 0,
3838
+ 0,
3839
+ 1,
3840
+ 0.25,
3841
+ 0.1,
3842
+ 0,
3843
+ 0,
3844
+ 0,
3845
+ 1,
3846
+ -0.25,
3847
+ -0.1,
3848
+ 0,
3849
+ 0,
3850
+ 0,
3851
+ 1,
3852
+ -0.45,
3853
+ 0.1,
3854
+ 0,
3855
+ 0,
3856
+ 0,
3857
+ 1,
3858
+ -0.45,
3859
+ 0.1,
3860
+ 0,
3861
+ 0,
3862
+ 0,
3863
+ 1,
3864
+ -0.25,
3865
+ -0.8,
3866
+ 0,
3867
+ 0,
3868
+ 0,
3869
+ 1,
3870
+ -0.25,
3871
+ -0.8,
3872
+ 0,
3873
+ 0,
3874
+ 0,
3875
+ 1,
3876
+ -0.05,
3877
+ -0.1,
3878
+ 0,
3879
+ 0,
3880
+ 0,
3881
+ 1,
3882
+ -0.45,
3883
+ -0.8,
3884
+ 0,
3885
+ 0,
3886
+ 0,
3887
+ 1,
3888
+ -0.25,
3889
+ -0.1,
3890
+ 0,
3891
+ 0,
3892
+ 0,
3893
+ 1,
3894
+ -0.25,
3895
+ -0.1,
3896
+ 0,
3897
+ 0,
3898
+ 0
3899
+ ]);
2712
3900
  var NiivueObject3D = function(id, vertexBuffer, mode, indexCount, indexBuffer = null, vao = null) {
2713
3901
  this.BLEND = 1;
2714
3902
  this.CULL_FACE = 2;
@@ -17201,11 +18389,11 @@ var jpx = { exports: {} };
17201
18389
  }
17202
18390
  return dict.get(name);
17203
18391
  };
17204
- Util2.inherit = function Util_inherit(sub, base, prototype) {
17205
- sub.prototype = Object.create(base.prototype);
17206
- sub.prototype.constructor = sub;
18392
+ Util2.inherit = function Util_inherit(sub2, base, prototype) {
18393
+ sub2.prototype = Object.create(base.prototype);
18394
+ sub2.prototype.constructor = sub2;
17207
18395
  for (var prop in prototype) {
17208
- sub.prototype[prop] = prototype[prop];
18396
+ sub2.prototype[prop] = prototype[prop];
17209
18397
  }
17210
18398
  };
17211
18399
  Util2.loadScript = function Util_loadScript(src, callback) {
@@ -103407,7 +104595,7 @@ function isPlatformLittleEndian() {
103407
104595
  new DataView(buffer2).setInt16(0, 256, true);
103408
104596
  return new Int16Array(buffer2)[0] === 256;
103409
104597
  }
103410
- function NVImage(dataBuffer, name = "", colorMap = "gray", opacity = 1, pairedImgData = null, cal_min = NaN, cal_max = NaN, trustCalMinMax = true, percentileFrac = 0.02, ignoreZeroVoxels = false, visible = true, isDICOMDIR = false, useQFormNotSForm = false) {
104598
+ function NVImage(dataBuffer, name = "", colorMap = "gray", opacity = 1, pairedImgData = null, cal_min = NaN, cal_max = NaN, trustCalMinMax = true, percentileFrac = 0.02, ignoreZeroVoxels = false, visible = true, isDICOMDIR = false, useQFormNotSForm = false, colorMapNegative = "") {
103411
104599
  this.DT_NONE = 0;
103412
104600
  this.DT_UNKNOWN = 0;
103413
104601
  this.DT_BINARY = 1;
@@ -103436,7 +104624,9 @@ function NVImage(dataBuffer, name = "", colorMap = "gray", opacity = 1, pairedIm
103436
104624
  this.percentileFrac = percentileFrac;
103437
104625
  this.ignoreZeroVoxels = ignoreZeroVoxels;
103438
104626
  this.trustCalMinMax = trustCalMinMax;
104627
+ this.colorMapNegative = colorMapNegative;
103439
104628
  this.visible = visible;
104629
+ this.modulationImage = null;
103440
104630
  this.series = [];
103441
104631
  if (!dataBuffer) {
103442
104632
  return;
@@ -103700,7 +104890,25 @@ function NVImage(dataBuffer, name = "", colorMap = "gray", opacity = 1, pairedIm
103700
104890
  this.hdr.cal_max = cal_max;
103701
104891
  this.calMinMax();
103702
104892
  }
104893
+ NVImage.prototype.computeObliqueAngle = function(mtx44) {
104894
+ let mtx = clone$1(mtx44);
104895
+ transpose(mtx, mtx44);
104896
+ let dxtmp = Math.sqrt(mtx[0] * mtx[0] + mtx[1] * mtx[1] + mtx[2] * mtx[2]);
104897
+ let xmax = Math.max(Math.max(Math.abs(mtx[0]), Math.abs(mtx[1])), Math.abs(mtx[2])) / dxtmp;
104898
+ let dytmp = Math.sqrt(mtx[4] * mtx[4] + mtx[5] * mtx[5] + mtx[6] * mtx[6]);
104899
+ let ymax = Math.max(Math.max(Math.abs(mtx[4]), Math.abs(mtx[5])), Math.abs(mtx[6])) / dytmp;
104900
+ let dztmp = Math.sqrt(mtx[8] * mtx[8] + mtx[9] * mtx[9] + mtx[10] * mtx[10]);
104901
+ let zmax = Math.max(Math.max(Math.abs(mtx[8]), Math.abs(mtx[9])), Math.abs(mtx[10])) / dztmp;
104902
+ let fig_merit = Math.min(Math.min(xmax, ymax), zmax);
104903
+ let oblique_angle = Math.abs(Math.acos(fig_merit) * 180 / 3.141592653);
104904
+ if (oblique_angle > 0.01)
104905
+ console.log("Warning voxels not aligned with world space: " + oblique_angle + " degrees from plumb.\n");
104906
+ else
104907
+ oblique_angle = 0;
104908
+ return oblique_angle;
104909
+ };
103703
104910
  NVImage.prototype.calculateOblique = function() {
104911
+ this.oblique_angle = this.computeObliqueAngle(this.matRAS);
103704
104912
  let LPI = this.vox2mm([0, 0, 0], this.matRAS);
103705
104913
  let X1mm = this.vox2mm([1 / this.pixDimsRAS[1], 0, 0], this.matRAS);
103706
104914
  let Y1mm = this.vox2mm([0, 1 / this.pixDimsRAS[2], 0], this.matRAS);
@@ -103716,6 +104924,21 @@ NVImage.prototype.calculateOblique = function() {
103716
104924
  let maxShear = Math.max(Math.max(XY, XZ), YZ);
103717
104925
  if (maxShear > 0.1)
103718
104926
  log$2.debug("Warning: shear detected (gantry tilt) of %f degrees", maxShear);
104927
+ let dim = fromValues(this.dimsRAS[1], this.dimsRAS[2], this.dimsRAS[3], 1);
104928
+ let sform = clone$1(this.matRAS);
104929
+ transpose(sform, sform);
104930
+ let shim = fromValues(-0.5, -0.5, -0.5, 0);
104931
+ translate(sform, sform, shim);
104932
+ sform[0] *= dim[0];
104933
+ sform[1] *= dim[0];
104934
+ sform[2] *= dim[0];
104935
+ sform[4] *= dim[1];
104936
+ sform[5] *= dim[1];
104937
+ sform[6] *= dim[1];
104938
+ sform[8] *= dim[2];
104939
+ sform[9] *= dim[2];
104940
+ sform[10] *= dim[2];
104941
+ this.frac2mm = clone$1(sform);
103719
104942
  };
103720
104943
  NVImage.prototype.THD_daxes_to_NIFTI = function(xyzDelta, xyzOrigin, orientSpecific) {
103721
104944
  let hdr = this.hdr;
@@ -104411,7 +105634,7 @@ NVImage.prototype.readMHA = function(buffer2, pairedImgData) {
104411
105634
  line = readStr();
104412
105635
  }
104413
105636
  let mmMat = fromValues$3(hdr.pixDims[1], 0, 0, 0, hdr.pixDims[2], 0, 0, 0, hdr.pixDims[3]);
104414
- multiply$2(mat33, mmMat, mat33);
105637
+ multiply$1(mat33, mmMat, mat33);
104415
105638
  hdr.affine = [
104416
105639
  [-mat33[0], -mat33[3], -mat33[6], -offset[0]],
104417
105640
  [-mat33[1], -mat33[4], -mat33[7], -offset[1]],
@@ -104812,7 +106035,7 @@ NVImage.prototype.readNRRD = function(dataBuffer, pairedImgData) {
104812
106035
  offset[1] = -offset[1];
104813
106036
  if (rot33[8] < 0)
104814
106037
  offset[2] = -offset[2];
104815
- multiply$2(mat33, rot33, mat33);
106038
+ multiply$1(mat33, rot33, mat33);
104816
106039
  let mat = fromValues$2(mat33[0], mat33[3], mat33[6], offset[0], mat33[1], mat33[4], mat33[7], offset[1], mat33[2], mat33[5], mat33[8], offset[2], 0, 0, 0, 1);
104817
106040
  let mm000 = this.vox2mm([0, 0, 0], mat);
104818
106041
  let mm100 = this.vox2mm([1, 0, 0], mat);
@@ -104928,7 +106151,7 @@ NVImage.prototype.calculateRAS = function() {
104928
106151
  rotM[3 + 2 * 4] = (header.dims[perm[2]] - 1) * flip[2];
104929
106152
  let residualR = create$2();
104930
106153
  invert(residualR, rotM);
104931
- multiply$1(residualR, residualR, R2);
106154
+ multiply(residualR, residualR, R2);
104932
106155
  this.matRAS = clone$1(residualR);
104933
106156
  rotM = fromValues$2(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
104934
106157
  rotM[perm[0] - 1 + 0 * 4] = -flip[0] * 2 + 1;
@@ -105230,7 +106453,8 @@ NVImage.loadFromUrl = async function({
105230
106453
  trustCalMinMax = true,
105231
106454
  percentileFrac = 0.02,
105232
106455
  ignoreZeroVoxels = false,
105233
- visible = true
106456
+ visible = true,
106457
+ colorMapNegative = ""
105234
106458
  } = {}) {
105235
106459
  if (url === "") {
105236
106460
  throw Error("url must not be empty");
@@ -105263,7 +106487,7 @@ NVImage.loadFromUrl = async function({
105263
106487
  pairedImgData = await resp.arrayBuffer();
105264
106488
  }
105265
106489
  if (dataBuffer) {
105266
- nvimage = new NVImage(dataBuffer, name, colorMap, opacity, pairedImgData, cal_min, cal_max, trustCalMinMax, percentileFrac, ignoreZeroVoxels, visible);
106490
+ nvimage = new NVImage(dataBuffer, name, colorMap, opacity, pairedImgData, cal_min, cal_max, trustCalMinMax, percentileFrac, ignoreZeroVoxels, visible, false, false, colorMapNegative);
105267
106491
  } else {
105268
106492
  alert("Unable to load buffer properly from volume");
105269
106493
  }
@@ -105463,14 +106687,20 @@ function getExtents$1(positions, forceOriginInVolume = true) {
105463
106687
  return { min: min2, max: max2, furthestVertexFromOrigin, origin };
105464
106688
  }
105465
106689
  NVImage.prototype.toNiivueObject3D = function(id, gl) {
105466
- let LPI = this.vox2mm([0, 0, 0], this.matRAS);
105467
- let LAI = this.vox2mm([0, this.dimsRAS[2] - 1, 0], this.matRAS);
105468
- let LPS = this.vox2mm([0, 0, this.dimsRAS[3] - 1], this.matRAS);
105469
- let LAS = this.vox2mm([0, this.dimsRAS[2] - 1, this.dimsRAS[3] - 1], this.matRAS);
105470
- let RPI = this.vox2mm([this.dimsRAS[1] - 1, 0, 0], this.matRAS);
105471
- let RAI = this.vox2mm([this.dimsRAS[1] - 1, this.dimsRAS[2] - 1, 0], this.matRAS);
105472
- let RPS = this.vox2mm([this.dimsRAS[1] - 1, 0, this.dimsRAS[3] - 1], this.matRAS);
105473
- let RAS = this.vox2mm([this.dimsRAS[1] - 1, this.dimsRAS[2] - 1, this.dimsRAS[3] - 1], this.matRAS);
106690
+ let L = -0.5;
106691
+ let P = -0.5;
106692
+ let I2 = -0.5;
106693
+ let R2 = this.dimsRAS[1] - 1 + 0.5;
106694
+ let A2 = this.dimsRAS[2] - 1 + 0.5;
106695
+ let S = this.dimsRAS[3] - 1 + 0.5;
106696
+ let LPI = this.vox2mm([L, P, I2], this.matRAS);
106697
+ let LAI = this.vox2mm([L, A2, I2], this.matRAS);
106698
+ let LPS = this.vox2mm([L, P, S], this.matRAS);
106699
+ let LAS = this.vox2mm([L, A2, S], this.matRAS);
106700
+ let RPI = this.vox2mm([R2, P, I2], this.matRAS);
106701
+ let RAI = this.vox2mm([R2, A2, I2], this.matRAS);
106702
+ let RPS = this.vox2mm([R2, P, S], this.matRAS);
106703
+ let RAS = this.vox2mm([R2, A2, S], this.matRAS);
105474
106704
  let posTex = [
105475
106705
  ...LPS,
105476
106706
  ...[0, 0, 1],
@@ -105553,11 +106783,16 @@ NVImage.prototype.toNiivueObject3D = function(id, gl) {
105553
106783
  ...RAI,
105554
106784
  ...RPI
105555
106785
  ]);
105556
- obj3D.extentsMin = extents.min;
105557
- obj3D.extentsMax = extents.max;
106786
+ obj3D.extentsMin = extents.min.slice();
106787
+ obj3D.extentsMax = extents.max.slice();
105558
106788
  obj3D.furthestVertexFromOrigin = extents.furthestVertexFromOrigin;
105559
106789
  obj3D.originNegate = clone(extents.origin);
105560
106790
  negate(obj3D.originNegate, obj3D.originNegate);
106791
+ obj3D.fieldOfViewDeObliqueMM = [
106792
+ this.dimsRAS[1] * this.pixDimsRAS[1],
106793
+ this.dimsRAS[2] * this.pixDimsRAS[2],
106794
+ this.dimsRAS[3] * this.pixDimsRAS[3]
106795
+ ];
105561
106796
  return obj3D;
105562
106797
  };
105563
106798
  const colortables = function() {
@@ -105609,6 +106844,8 @@ colortables.prototype.makeLut = function(Rs, Gs, Bs, As, Is) {
105609
106844
  if (this.gamma === 1)
105610
106845
  return lut;
105611
106846
  for (var i2 = 0; i2 < 255 * 4; i2++) {
106847
+ if (i2 % 4 === 3)
106848
+ continue;
105612
106849
  lut[i2] = Math.pow(lut[i2] / 255, 1 / this.gamma) * 255;
105613
106850
  }
105614
106851
  return lut;
@@ -106282,7 +107519,7 @@ NVMesh.readTRK = function(buffer2) {
106282
107519
  identity$1(mat);
106283
107520
  }
106284
107521
  var vox2mmMat = create$2();
106285
- mul$1(vox2mmMat, mat, zoomMat);
107522
+ mul(vox2mmMat, mat, zoomMat);
106286
107523
  let i32 = null;
106287
107524
  let f32 = null;
106288
107525
  i32 = new Int32Array(buffer2.slice(hdr_sz));
@@ -110233,11 +111470,13 @@ function Niivue(options = {}) {
110233
111470
  textHeight: 0.06,
110234
111471
  colorbarHeight: 0.05,
110235
111472
  crosshairWidth: 1,
111473
+ rulerWidth: 4,
110236
111474
  show3Dcrosshair: false,
110237
111475
  backColor: [0, 0, 0, 1],
110238
111476
  crosshairColor: [1, 0, 0, 1],
110239
111477
  selectionBoxColor: [1, 1, 1, 0.5],
110240
111478
  clipPlaneColor: [0.7, 0, 0.7, 0.5],
111479
+ rulerColor: [1, 0, 0, 0.8],
110241
111480
  colorBarMargin: 0.05,
110242
111481
  trustCalMinMax: true,
110243
111482
  clipPlaneHotKey: "KeyC",
@@ -110245,7 +111484,17 @@ function Niivue(options = {}) {
110245
111484
  keyDebounceTime: 50,
110246
111485
  isNearestInterpolation: false,
110247
111486
  isAtlasOutline: false,
111487
+ isRuler: false,
111488
+ isColorbar: false,
111489
+ isOrientCube: false,
111490
+ multiplanarPadPixels: 0,
110248
111491
  isRadiologicalConvention: false,
111492
+ meshThicknessOn2D: Infinity,
111493
+ isDragShowsMeasurementTool: false,
111494
+ isDepthPickMesh: false,
111495
+ isCornerOrientationText: false,
111496
+ sagittalNoseLeft: false,
111497
+ isSliceMM: false,
110249
111498
  logging: false,
110250
111499
  loadingText: "waiting for images...",
110251
111500
  dragAndDropEnabled: true,
@@ -110264,6 +111513,7 @@ function Niivue(options = {}) {
110264
111513
  this.currentDrawUndoBitmap = this.maxDrawUndoBitmaps;
110265
111514
  this.drawUndoBitmaps = [];
110266
111515
  this.drawOpacity = 0.8;
111516
+ this.colorbarHeight = 0;
110267
111517
  this.drawPenLocation = [NaN, NaN, NaN];
110268
111518
  this.drawPenAxCorSag = -1;
110269
111519
  this.drawFillOverwrites = true;
@@ -110271,10 +111521,14 @@ function Niivue(options = {}) {
110271
111521
  this.overlayTexture = null;
110272
111522
  this.overlayTextureID = [];
110273
111523
  this.sliceShader = null;
110274
- this.lineShader = null;
111524
+ this.sliceMMShader = null;
111525
+ this.orientCubeShader = null;
111526
+ this.orientCubeShaderVAO = null;
111527
+ this.rectShader = null;
110275
111528
  this.graphShader = null;
110276
111529
  this.renderShader = null;
110277
- this.pickingShader = null;
111530
+ this.pickingMeshShader = null;
111531
+ this.pickingImageShader = null;
110278
111532
  this.colorbarShader = null;
110279
111533
  this.fontShader = null;
110280
111534
  this.fontTexture = null;
@@ -110294,7 +111548,6 @@ function Niivue(options = {}) {
110294
111548
  this.genericVAO = null;
110295
111549
  this.unusedVAO = null;
110296
111550
  this.crosshairs3D = null;
110297
- this.pickingSurfaceShader = null;
110298
111551
  this.DEFAULT_FONT_GLYPH_SHEET = defaultFontPNG;
110299
111552
  this.DEFAULT_FONT_METRICS = defaultFontMetrics;
110300
111553
  this.fontMets = null;
@@ -110304,6 +111557,7 @@ function Niivue(options = {}) {
110304
111557
  this.sliceTypeSagittal = 2;
110305
111558
  this.sliceTypeMultiplanar = 3;
110306
111559
  this.sliceTypeRender = 4;
111560
+ this.sliceMosaicString = "";
110307
111561
  this.sliceType = this.sliceTypeMultiplanar;
110308
111562
  this.scene = {};
110309
111563
  this.syncOpts = {};
@@ -110320,6 +111574,7 @@ function Niivue(options = {}) {
110320
111574
  this.scene.mouseButtonLeftDown = false;
110321
111575
  this.scene.mouseButtonRightDown = false;
110322
111576
  this.scene.mouseDepthPicker = false;
111577
+ this.scene.pan2Dxyzmm = [0, 0, 0, 1];
110323
111578
  this.scene.scale2D = 1;
110324
111579
  this.scene.prevX = 0;
110325
111580
  this.scene.prevY = 0;
@@ -110336,13 +111591,7 @@ function Niivue(options = {}) {
110336
111591
  this.volScale = [];
110337
111592
  this.vox = [];
110338
111593
  this.mousePos = [0, 0];
110339
- this.numScreenSlices = 0;
110340
- this.screenSlices = [
110341
- { leftTopWidthHeight: [1, 0, 0, 1], axCorSag: this.sliceTypeAxial },
110342
- { leftTopWidthHeight: [1, 0, 0, 1], axCorSag: this.sliceTypeAxial },
110343
- { leftTopWidthHeight: [1, 0, 0, 1], axCorSag: this.sliceTypeAxial },
110344
- { leftTopWidthHeight: [1, 0, 0, 1], axCorSag: this.sliceTypeAxial }
110345
- ];
111594
+ this.screenSlices = [];
110346
111595
  this.isDragging = false;
110347
111596
  this.dragStart = [0, 0];
110348
111597
  this.dragEnd = [0, 0];
@@ -110394,6 +111643,10 @@ function Niivue(options = {}) {
110394
111643
  {
110395
111644
  Name: "Toon",
110396
111645
  Frag: fragMeshToonShader
111646
+ },
111647
+ {
111648
+ Name: "Depth",
111649
+ Frag: fragMeshDepthShader
110397
111650
  }
110398
111651
  ];
110399
111652
  this.isController = false;
@@ -110422,6 +111675,28 @@ function Niivue(options = {}) {
110422
111675
  };
110423
111676
  this.subscriptions = [];
110424
111677
  }
111678
+ Niivue.prototype.saveScene = function(filename = "") {
111679
+ function saveBlob(blob, name) {
111680
+ const a = document.createElement("a");
111681
+ document.body.appendChild(a);
111682
+ a.style.display = "none";
111683
+ const url = window.URL.createObjectURL(blob);
111684
+ a.href = url;
111685
+ a.download = name;
111686
+ a.click();
111687
+ a.remove();
111688
+ }
111689
+ let canvas = this.canvas;
111690
+ this.drawScene();
111691
+ canvas.toBlob((blob) => {
111692
+ if (filename === "") {
111693
+ filename = `niivue-screenshot-${new Date().toString()}.png`;
111694
+ filename = filename.replace(/\s/g, "_");
111695
+ }
111696
+ console.log(filename);
111697
+ saveBlob(blob, filename);
111698
+ });
111699
+ };
110425
111700
  Niivue.prototype.attachTo = async function(id) {
110426
111701
  await this.attachToCanvas(document.getElementById(id));
110427
111702
  log.debug("attached to element with id: ", id);
@@ -110640,14 +111915,18 @@ function intensityRaw2Scaled(hdr, raw) {
110640
111915
  return raw * hdr.scl_slope + hdr.scl_inter;
110641
111916
  }
110642
111917
  Niivue.prototype.calculateNewRange = function(volIdx = 0) {
110643
- if (this.sliceType === this.sliceTypeRender) {
111918
+ if (this.sliceType === this.sliceTypeRender && this.sliceMosaicString.length < 1) {
110644
111919
  return;
110645
111920
  }
110646
111921
  if (this.dragStart[0] === this.dragEnd[0] && this.dragStart[1] === this.dragEnd[1])
110647
111922
  return;
110648
111923
  let frac = this.canvasPos2frac([this.dragStart[0], this.dragStart[1]]);
111924
+ if (frac[0] < 0)
111925
+ return;
110649
111926
  let startVox = this.frac2vox(frac, volIdx);
110650
111927
  frac = this.canvasPos2frac([this.dragEnd[0], this.dragEnd[1]]);
111928
+ if (frac[0] < 0)
111929
+ return;
110651
111930
  let endVox = this.frac2vox(frac, volIdx);
110652
111931
  let hi = -Number.MAX_VALUE, lo = Number.MAX_VALUE;
110653
111932
  let xrange;
@@ -110702,6 +111981,8 @@ Niivue.prototype.mouseUpListener = function() {
110702
111981
  this.drawPenAxCorSag = -1;
110703
111982
  if (this.isDragging) {
110704
111983
  this.isDragging = false;
111984
+ if (this.opts.isDragShowsMeasurementTool)
111985
+ return;
110705
111986
  this.calculateNewRange();
110706
111987
  this.refreshLayers(this.volumes[0], 0, this.volumes.length);
110707
111988
  }
@@ -110744,10 +112025,18 @@ Niivue.prototype.mouseMoveListener = function(e) {
110744
112025
  this.scene.prevY = this.scene.currY;
110745
112026
  }
110746
112027
  };
110747
- Niivue.prototype.resetBriCon = function() {
110748
- if (this.sliceType === this.sliceTypeRender) {
112028
+ Niivue.prototype.resetBriCon = function(msg2 = null) {
112029
+ let isRender = false;
112030
+ if (this.sliceType === this.sliceTypeRender)
112031
+ isRender = true;
112032
+ if (msg2 !== null) {
112033
+ if (this.inRenderTile(msg2.offsetX, msg2.offsetY) >= 0)
112034
+ isRender = true;
112035
+ }
112036
+ if (isRender) {
110749
112037
  this.scene.mouseDepthPicker = true;
110750
112038
  this.drawScene();
112039
+ this.drawScene();
110751
112040
  return;
110752
112041
  }
110753
112042
  this.volumes[0].cal_min = this.volumes[0].robust_min;
@@ -110783,9 +112072,6 @@ Niivue.prototype.handlePinchZoom = function(e) {
110783
112072
  };
110784
112073
  Niivue.prototype.keyUpListener = function(e) {
110785
112074
  if (e.code === this.opts.clipPlaneHotKey) {
110786
- if (this.sliceType != this.sliceTypeRender) {
110787
- return;
110788
- }
110789
112075
  let now = new Date().getTime();
110790
112076
  let elapsed = now - this.lastCalled;
110791
112077
  if (elapsed > this.opts.keyDebounceTime) {
@@ -110983,8 +112269,26 @@ Niivue.prototype.dropListener = async function(e) {
110983
112269
  }
110984
112270
  this.drawScene();
110985
112271
  };
112272
+ Niivue.prototype.setCornerOrientationText = function(isCornerOrientationText) {
112273
+ this.opts.isCornerOrientationText = isCornerOrientationText;
112274
+ this.updateGLVolume();
112275
+ };
110986
112276
  Niivue.prototype.setRadiologicalConvention = function(isRadiologicalConvention) {
110987
112277
  this.opts.isRadiologicalConvention = isRadiologicalConvention;
112278
+ this.updateGLVolume();
112279
+ };
112280
+ Niivue.prototype.setMeshThicknessOn2D = function(meshThicknessOn2D) {
112281
+ this.opts.meshThicknessOn2D = meshThicknessOn2D;
112282
+ this.updateGLVolume();
112283
+ };
112284
+ Niivue.prototype.setSliceMosaicString = function(str) {
112285
+ this.sliceMosaicString = str;
112286
+ this.updateGLVolume();
112287
+ };
112288
+ Niivue.prototype.setSliceMM = function(isSliceMM) {
112289
+ this.opts.isSliceMM = isSliceMM;
112290
+ console.log("SliceMM feature is experimental");
112291
+ this.updateGLVolume();
110988
112292
  };
110989
112293
  Niivue.prototype.getRadiologicalConvention = function() {
110990
112294
  return this.opts.isRadiologicalConvention;
@@ -111291,9 +112595,12 @@ Niivue.prototype.setMeshLayerProperty = function(mesh, layer, key2, val) {
111291
112595
  this.meshes[idx].setLayerProperty(layer, key2, val, this.gl);
111292
112596
  this.updateGLVolume();
111293
112597
  };
112598
+ Niivue.prototype.setPan2Dxyzmm = function(xyzmmZoom) {
112599
+ this.scene.pan2Dxyzmm = xyzmmZoom;
112600
+ this.drawScene();
112601
+ };
111294
112602
  Niivue.prototype.setScale2D = function(zoomFactor) {
111295
112603
  this.scene.scale2D = zoomFactor;
111296
- console.log("bingo");
111297
112604
  this.drawScene();
111298
112605
  };
111299
112606
  Niivue.prototype.setRenderAzimuthElevation = function(a, e) {
@@ -111388,12 +112695,12 @@ Niivue.prototype.moveVolumeToTop = function(volume) {
111388
112695
  this.setVolume(volume, this.volumes.length - 1);
111389
112696
  };
111390
112697
  Niivue.prototype.mouseDown = function mouseDown(x2, y) {
111391
- if (this.sliceType != this.sliceTypeRender)
112698
+ if (this.inRenderTile(x2, y) < 0)
111392
112699
  return;
111393
112700
  this.mousePos = [x2, y];
111394
112701
  };
111395
112702
  Niivue.prototype.mouseMove = function mouseMove(x2, y) {
111396
- if (this.sliceType != this.sliceTypeRender)
112703
+ if (this.inRenderTile(x2, y) < 0)
111397
112704
  return;
111398
112705
  this.scene.renderAzimuth += x2 - this.mousePos[0];
111399
112706
  this.scene.renderElevation += y - this.mousePos[1];
@@ -111420,8 +112727,6 @@ Niivue.prototype.setClipPlane = function(depthAzimuthElevation) {
111420
112727
  let v = this.sph2cartDeg(depthAzimuthElevation[1] + 180, depthAzimuthElevation[2]);
111421
112728
  this.scene.clipPlane = [v[0], v[1], v[2], depthAzimuthElevation[0]];
111422
112729
  this.scene.clipPlaneDepthAziElev = depthAzimuthElevation;
111423
- if (this.sliceType != this.sliceTypeRender)
111424
- return;
111425
112730
  this.drawScene();
111426
112731
  };
111427
112732
  Niivue.prototype.setCrosshairColor = function(color) {
@@ -111538,6 +112843,7 @@ Niivue.prototype.loadVolumes = async function(volumeList) {
111538
112843
  url: volumeList[i2].url,
111539
112844
  name: volumeList[i2].name,
111540
112845
  colorMap: volumeList[i2].colorMap,
112846
+ colorMapNegative: volumeList[i2].colorMapNegative,
111541
112847
  opacity: volumeList[i2].opacity,
111542
112848
  urlImgData: volumeList[i2].urlImgData,
111543
112849
  cal_min: volumeList[i2].cal_min,
@@ -111806,7 +113112,7 @@ Niivue.prototype.drawPt = function(x2, y, z, penValue) {
111806
113112
  z = Math.min(Math.max(z, 0), dz - 1);
111807
113113
  this.drawBitmap[x2 + y * dx + z * dx * dy] = penValue;
111808
113114
  };
111809
- Niivue.prototype.drawLine = function(ptA, ptB, penValue) {
113115
+ Niivue.prototype.drawPenLine = function(ptA, ptB, penValue) {
111810
113116
  let dx = Math.abs(ptA[0] - ptB[0]);
111811
113117
  let dy = Math.abs(ptA[1] - ptB[1]);
111812
113118
  let dz = Math.abs(ptA[2] - ptB[2]);
@@ -112302,21 +113608,44 @@ Niivue.prototype.init = async function() {
112302
113608
  this.gl.enableVertexAttribArray(0);
112303
113609
  this.gl.vertexAttribPointer(0, 3, this.gl.FLOAT, false, 0, 0);
112304
113610
  this.gl.bindVertexArray(this.unusedVAO);
112305
- this.pickingShader = new Shader(this.gl, vertRenderShader, fragVolumePickingShader);
112306
- this.pickingShader.use(this.gl);
112307
- this.gl.uniform1i(this.pickingShader.uniforms["volume"], 0);
112308
- this.gl.uniform1i(this.pickingShader.uniforms["overlay"], 2);
112309
- this.pickingShader.mvpUniformLoc = this.pickingShader.uniforms["mvpMtx"];
112310
- this.pickingShader.rayDirUniformLoc = this.pickingShader.uniforms["rayDir"];
112311
- this.pickingShader.clipPlaneUniformLoc = this.pickingShader.uniforms["clipPlane"];
113611
+ this.pickingMeshShader = new Shader(this.gl, vertMeshShader, fragMeshDepthShader);
113612
+ this.pickingImageShader = new Shader(this.gl, vertRenderShader, fragVolumePickingShader);
113613
+ this.pickingImageShader.use(this.gl);
113614
+ this.gl.uniform1i(this.pickingImageShader.uniforms["volume"], 0);
113615
+ this.gl.uniform1i(this.pickingImageShader.uniforms["overlay"], 2);
113616
+ this.pickingImageShader.mvpUniformLoc = this.pickingImageShader.uniforms["mvpMtx"];
113617
+ this.pickingImageShader.rayDirUniformLoc = this.pickingImageShader.uniforms["rayDir"];
113618
+ this.pickingImageShader.clipPlaneUniformLoc = this.pickingImageShader.uniforms["clipPlane"];
112312
113619
  this.sliceShader = new Shader(this.gl, vertSliceShader, fragSliceShader);
112313
113620
  this.sliceShader.use(this.gl);
112314
113621
  this.gl.uniform1i(this.sliceShader.uniforms["volume"], 0);
112315
113622
  this.gl.uniform1i(this.sliceShader.uniforms["overlay"], 2);
112316
113623
  this.gl.uniform1i(this.sliceShader.uniforms["drawing"], 7);
112317
113624
  this.gl.uniform1f(this.sliceShader.uniforms["drawOpacity"], this.drawOpacity);
112318
- this.lineShader = new Shader(this.gl, vertLineShader, fragLineShader);
112319
- this.graphShader = new Shader(this.gl, vertGraphShader, fragLineShader);
113625
+ this.sliceMMShader = new Shader(this.gl, vertSliceMMShader, fragSliceMMShader);
113626
+ this.sliceMMShader.use(this.gl);
113627
+ this.gl.uniform1i(this.sliceMMShader.uniforms["volume"], 0);
113628
+ this.gl.uniform1i(this.sliceMMShader.uniforms["overlay"], 2);
113629
+ this.gl.uniform1i(this.sliceMMShader.uniforms["drawing"], 7);
113630
+ this.gl.uniform1f(this.sliceMMShader.uniforms["drawOpacity"], this.drawOpacity);
113631
+ this.orientCubeShader = new Shader(this.gl, vertOrientCubeShader, fragOrientCubeShader);
113632
+ let gl = this.gl;
113633
+ this.orientCubeShaderVAO = gl.createVertexArray();
113634
+ gl.bindVertexArray(this.orientCubeShaderVAO);
113635
+ let program = this.orientCubeShader.program;
113636
+ this.orientCubeMtxLoc = gl.getUniformLocation(program, "u_matrix");
113637
+ var positionBuffer = gl.createBuffer();
113638
+ gl.enableVertexAttribArray(0);
113639
+ gl.enableVertexAttribArray(1);
113640
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
113641
+ gl.bufferData(gl.ARRAY_BUFFER, orientCube, gl.STATIC_DRAW);
113642
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 24, 0);
113643
+ gl.enableVertexAttribArray(1);
113644
+ gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 24, 12);
113645
+ gl.bindVertexArray(this.unusedVAO);
113646
+ this.rectShader = new Shader(this.gl, vertRectShader, fragRectShader);
113647
+ this.lineShader = new Shader(this.gl, vertLineShader, fragRectShader);
113648
+ this.graphShader = new Shader(this.gl, vertGraphShader, fragRectShader);
112320
113649
  this.renderShader = new Shader(this.gl, vertRenderShader, fragRenderShader);
112321
113650
  this.renderShader.use(this.gl);
112322
113651
  this.gl.uniform1i(this.renderShader.uniforms["volume"], 0);
@@ -112332,7 +113661,6 @@ Niivue.prototype.init = async function() {
112332
113661
  this.orientShaderI = new Shader(this.gl, vertOrientShader, fragOrientShaderI.concat(fragOrientShader));
112333
113662
  this.orientShaderF = new Shader(this.gl, vertOrientShader, fragOrientShaderF.concat(fragOrientShader));
112334
113663
  this.orientShaderRGBU = new Shader(this.gl, vertOrientShader, fragOrientShaderU.concat(fragRGBOrientShader));
112335
- this.pickingSurfaceShader = new Shader(this.gl, vertRenderShader, fragDepthPickingShader);
112336
113664
  this.surfaceShader = new Shader(this.gl, vertSurfaceShader, fragSurfaceShader);
112337
113665
  this.fiberShader = new Shader(this.gl, vertFiberShader, fragFiberShader);
112338
113666
  this.meshShader = new Shader(this.gl, vertMeshShader, this.meshShaders[0].Frag);
@@ -112444,7 +113772,6 @@ Niivue.prototype.refreshLayers = function(overlayItem, layer, numLayers) {
112444
113772
  if (layer === 0) {
112445
113773
  this.volumeObject3D = overlayItem.toNiivueObject3D(this.VOLUME_ID, this.gl);
112446
113774
  invert(mtx, mtx);
112447
- log.debug(`mtx layer ${layer}`, mtx);
112448
113775
  this.back.matRAS = overlayItem.matRAS;
112449
113776
  this.back.dims = overlayItem.dimsRAS;
112450
113777
  this.back.pixDims = overlayItem.pixDimsRAS;
@@ -112456,8 +113783,7 @@ Niivue.prototype.refreshLayers = function(overlayItem, layer, numLayers) {
112456
113783
  this.renderShader.use(this.gl);
112457
113784
  this.gl.uniform3fv(this.renderShader.uniforms["texVox"], vox2);
112458
113785
  this.gl.uniform3fv(this.renderShader.uniforms["volScale"], volScale2);
112459
- this.renderShader;
112460
- let pickingShader = this.pickingShader;
113786
+ let pickingShader = this.pickingImageShader;
112461
113787
  pickingShader.use(this.gl);
112462
113788
  this.gl.uniform1i(pickingShader.uniforms["volume"], 0);
112463
113789
  this.gl.uniform1i(pickingShader.uniforms["colormap"], 1);
@@ -112561,10 +113887,54 @@ Niivue.prototype.refreshLayers = function(overlayItem, layer, numLayers) {
112561
113887
  this.gl.uniform1i(orientShader.uniforms["blend3D"], 5);
112562
113888
  this.gl.uniform1i(orientShader.uniforms["colormap"], 1);
112563
113889
  this.gl.uniform1f(orientShader.uniforms["layer"], layer);
112564
- this.gl.uniform1f(orientShader.uniforms["numLayers"], numLayers);
112565
113890
  this.gl.uniform1f(orientShader.uniforms["scl_inter"], hdr.scl_inter);
112566
113891
  this.gl.uniform1f(orientShader.uniforms["scl_slope"], hdr.scl_slope);
112567
113892
  this.gl.uniform1f(orientShader.uniforms["opacity"], opacity);
113893
+ this.gl.uniform1i(orientShader.uniforms["modulationVol"], 7);
113894
+ let modulateTexture = null;
113895
+ if (overlayItem.modulationImage && overlayItem.modulationImage >= 0 && overlayItem.modulationImage < this.volumes.length) {
113896
+ console.log(this.volumes);
113897
+ let mhdr = this.volumes[overlayItem.modulationImage].hdr;
113898
+ if (mhdr.dims[1] === hdr.dims[1] && mhdr.dims[2] === hdr.dims[2] && mhdr.dims[3] === hdr.dims[3]) {
113899
+ this.gl.uniform1i(orientShader.uniforms["modulation"], 1);
113900
+ modulateTexture = this.r8Tex(modulateTexture, this.gl.TEXTURE7, hdr.dims, true);
113901
+ this.gl.activeTexture(this.gl.TEXTURE7);
113902
+ this.gl.bindTexture(this.gl.TEXTURE_3D, modulateTexture);
113903
+ let vx = hdr.dims[1] * hdr.dims[2] * hdr.dims[3];
113904
+ let modulateVolume = new Uint8Array(vx);
113905
+ let mn = mhdr.cal_min;
113906
+ let scale2 = 255 / (mhdr.cal_max - mhdr.cal_min);
113907
+ let imgRaw = this.volumes[overlayItem.modulationImage].img.buffer;
113908
+ let img2 = new Uint8Array(imgRaw);
113909
+ switch (mhdr.datatypeCode) {
113910
+ case overlayItem.DT_SIGNED_SHORT:
113911
+ img2 = new Int16Array(imgRaw);
113912
+ break;
113913
+ case overlayItem.DT_FLOAT:
113914
+ img2 = new Float32Array(imgRaw);
113915
+ break;
113916
+ case overlayItem.DT_DOUBLE:
113917
+ img2 = new Float64Array(imgRaw);
113918
+ break;
113919
+ case overlayItem.DT_RGB:
113920
+ img2 = new Uint8Array(imgRaw);
113921
+ break;
113922
+ case overlayItem.DT_UINT16:
113923
+ img2 = new Uint16Array(imgRaw);
113924
+ break;
113925
+ }
113926
+ console.log(this.volumes[overlayItem.modulationImage]);
113927
+ for (let i2 = 0; i2 < vx; i2++) {
113928
+ let v = img2[i2] * mhdr.scl_slope + mhdr.scl_inter;
113929
+ v = (v - mn) * scale2;
113930
+ v = Math.min(Math.max(v, 0), 255);
113931
+ modulateVolume[i2] = v;
113932
+ }
113933
+ this.gl.texSubImage3D(this.gl.TEXTURE_3D, 0, 0, 0, 0, hdr.dims[1], hdr.dims[2], hdr.dims[3], this.gl.RED, this.gl.UNSIGNED_BYTE, modulateVolume);
113934
+ } else
113935
+ console.log("Modulation image dimensions do not match target");
113936
+ } else
113937
+ this.gl.uniform1i(orientShader.uniforms["modulation"], 0);
112568
113938
  this.gl.uniformMatrix4fv(orientShader.uniforms["mtx"], false, mtx);
112569
113939
  if (hdr.intent_code === 1002) {
112570
113940
  let x2 = 1 / this.back.dims[1];
@@ -112585,6 +113955,7 @@ Niivue.prototype.refreshLayers = function(overlayItem, layer, numLayers) {
112585
113955
  }
112586
113956
  this.gl.bindVertexArray(this.unusedVAO);
112587
113957
  this.gl.deleteTexture(tempTex3D);
113958
+ this.gl.deleteTexture(modulateTexture);
112588
113959
  this.gl.deleteTexture(blendTexture);
112589
113960
  this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
112590
113961
  this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
@@ -112599,13 +113970,19 @@ Niivue.prototype.refreshLayers = function(overlayItem, layer, numLayers) {
112599
113970
  this.gl.uniform4fv(this.renderShader.uniforms["clipPlane"], this.scene.clipPlane);
112600
113971
  this.gl.uniform3fv(this.renderShader.uniforms["texVox"], vox);
112601
113972
  this.gl.uniform3fv(this.renderShader.uniforms["volScale"], volScale);
112602
- this.pickingShader.use(this.gl);
112603
- this.gl.uniform1f(this.pickingShader.uniforms["overlays"], this.overlays.length);
112604
- this.gl.uniform3fv(this.pickingShader.uniforms["texVox"], vox);
113973
+ this.pickingImageShader.use(this.gl);
113974
+ this.gl.uniform1f(this.pickingImageShader.uniforms["overlays"], this.overlays.length);
113975
+ this.gl.uniform3fv(this.pickingImageShader.uniforms["texVox"], vox);
113976
+ this.sliceMMShader.use(this.gl);
113977
+ this.gl.uniform1f(this.sliceMMShader.uniforms["overlays"], this.overlays.length);
113978
+ this.gl.uniform1f(this.sliceMMShader.uniforms["drawOpacity"], this.drawOpacity);
113979
+ this.gl.uniform1i(this.sliceMMShader.uniforms["drawing"], 7);
112605
113980
  this.sliceShader.use(this.gl);
112606
113981
  this.gl.uniform1f(this.sliceShader.uniforms["overlays"], this.overlays.length);
112607
113982
  this.gl.uniform1f(this.sliceShader.uniforms["drawOpacity"], this.drawOpacity);
112608
113983
  this.gl.uniform1i(this.sliceShader.uniforms["drawing"], 7);
113984
+ this.gl.activeTexture(this.gl.TEXTURE7);
113985
+ this.gl.bindTexture(this.gl.TEXTURE_3D, this.drawTexture);
112609
113986
  this.updateInterpolation(layer);
112610
113987
  };
112611
113988
  Niivue.prototype.colorMaps = function(sort = true) {
@@ -112616,6 +113993,18 @@ Niivue.prototype.setColorMap = function(id, colorMap) {
112616
113993
  this.volumes[idx].colorMap = colorMap;
112617
113994
  this.updateGLVolume();
112618
113995
  };
113996
+ Niivue.prototype.setColorMapNegative = function(id, colorMapNegative) {
113997
+ let idx = this.getVolumeIndexByID(id);
113998
+ this.volumes[idx].colorMapNegative = colorMapNegative;
113999
+ this.updateGLVolume();
114000
+ };
114001
+ Niivue.prototype.setModulationImage = function(idTarget, idModulation) {
114002
+ let idxTarget = this.getVolumeIndexByID(idTarget);
114003
+ let idxModulation = null;
114004
+ idxModulation = this.getVolumeIndexByID(idModulation);
114005
+ this.volumes[idxTarget].modulationImage = idxModulation;
114006
+ this.updateGLVolume();
114007
+ };
112619
114008
  Niivue.prototype.setGamma = function(gamma = 1) {
112620
114009
  cmapper.gamma = gamma;
112621
114010
  this.updateGLVolume();
@@ -112650,40 +114039,87 @@ Niivue.prototype.refreshColormaps = function() {
112650
114039
  this.colormapTexture = this.gl.createTexture();
112651
114040
  this.gl.activeTexture(this.gl.TEXTURE1);
112652
114041
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.colormapTexture);
112653
- this.gl.texStorage2D(this.gl.TEXTURE_2D, 1, this.gl.RGBA8, 256, nLayer);
114042
+ this.gl.texStorage2D(this.gl.TEXTURE_2D, 1, this.gl.RGBA8, 256, nLayer * 2);
112654
114043
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
112655
114044
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
112656
114045
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_R, this.gl.CLAMP_TO_EDGE);
112657
114046
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
112658
114047
  this.gl.pixelStorei(this.gl.UNPACK_ALIGNMENT, 1);
112659
114048
  let luts2 = this.colormap(this.volumes[0].colorMap);
112660
- if (nLayer > 1) {
112661
- for (let i2 = 1; i2 < nLayer; i2++) {
112662
- let lut = this.colormap(this.volumes[i2].colorMap);
112663
- let c2 = new Uint8ClampedArray(luts2.length + lut.length);
112664
- c2.set(luts2);
112665
- c2.set(lut, luts2.length);
112666
- luts2 = c2;
112667
- }
112668
- }
112669
- this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, 0, 0, 256, nLayer, this.gl.RGBA, this.gl.UNSIGNED_BYTE, luts2);
114049
+ function addColormap(lut) {
114050
+ let c2 = new Uint8ClampedArray(luts2.length + lut.length);
114051
+ c2.set(luts2);
114052
+ c2.set(lut, luts2.length);
114053
+ luts2 = c2;
114054
+ }
114055
+ for (let i2 = 1; i2 < nLayer; i2++)
114056
+ addColormap(this.colormap(this.volumes[i2].colorMap));
114057
+ let bogusLut = new Uint8ClampedArray(1024);
114058
+ bogusLut[0] = 255;
114059
+ bogusLut[1] = 255;
114060
+ bogusLut[2] = 255;
114061
+ bogusLut[3] = 0;
114062
+ bogusLut[4] = 0;
114063
+ bogusLut[5] = 0;
114064
+ bogusLut[6] = 0;
114065
+ bogusLut[7] = 255;
114066
+ for (let i2 = 0; i2 < nLayer; i2++) {
114067
+ let lut = bogusLut.slice();
114068
+ if (this.volumes[i2].colorMapNegative.length > 0)
114069
+ lut = this.colormap(this.volumes[i2].colorMapNegative);
114070
+ addColormap(lut);
114071
+ }
114072
+ this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, 0, 0, 256, nLayer * 2, this.gl.RGBA, this.gl.UNSIGNED_BYTE, luts2);
112670
114073
  return this;
112671
114074
  };
112672
114075
  Niivue.prototype.sliceScale = function() {
112673
- var dims = [
112674
- 1,
112675
- this.back.dims[1] * this.back.pixDims[1],
112676
- this.back.dims[2] * this.back.pixDims[2],
112677
- this.back.dims[3] * this.back.pixDims[3]
112678
- ];
112679
- var longestAxis = Math.max(dims[1], Math.max(dims[2], dims[3]));
114076
+ let dimsMM = this.screenFieldOfViewMM(this.sliceTypeAxial);
114077
+ var longestAxis = Math.max(dimsMM[0], Math.max(dimsMM[1], dimsMM[2]));
112680
114078
  var volScale = [
112681
- dims[1] / longestAxis,
112682
- dims[2] / longestAxis,
112683
- dims[3] / longestAxis
114079
+ dimsMM[0] / longestAxis,
114080
+ dimsMM[1] / longestAxis,
114081
+ dimsMM[2] / longestAxis
112684
114082
  ];
112685
114083
  var vox = [this.back.dims[1], this.back.dims[2], this.back.dims[3]];
112686
- return { volScale, vox };
114084
+ return { volScale, vox, longestAxis, dimsMM };
114085
+ };
114086
+ function swizzleVec3(vec, order = [0, 1, 2]) {
114087
+ let vout = create$1();
114088
+ vout[0] = vec[order[0]];
114089
+ vout[1] = vec[order[1]];
114090
+ vout[2] = vec[order[2]];
114091
+ return vout;
114092
+ }
114093
+ Niivue.prototype.inRenderTile = function(x2, y) {
114094
+ for (let i2 = 0; i2 < this.screenSlices.length; i2++) {
114095
+ if (this.screenSlices[i2].axCorSag !== this.sliceTypeRender)
114096
+ continue;
114097
+ let ltwh = this.screenSlices[i2].leftTopWidthHeight;
114098
+ if (x2 > ltwh[0] && y > ltwh[1] && x2 < ltwh[0] + ltwh[2] && y < ltwh[1] + ltwh[3])
114099
+ return i2;
114100
+ }
114101
+ return -1;
114102
+ };
114103
+ Niivue.prototype.sliceScroll3D = function(posChange = 0) {
114104
+ if (posChange === 0)
114105
+ return;
114106
+ if (this.volumes.length > 0 && this.scene.clipPlaneDepthAziElev[0] < 1.8) {
114107
+ let depthAziElev = this.scene.clipPlaneDepthAziElev.slice();
114108
+ if (posChange > 0)
114109
+ depthAziElev[0] = Math.min(1.5, depthAziElev[0] + 0.025);
114110
+ if (posChange < 0)
114111
+ depthAziElev[0] = Math.max(-1.5, depthAziElev[0] - 0.025);
114112
+ if (depthAziElev[0] !== this.scene.clipPlaneDepthAziElev[0]) {
114113
+ this.scene.clipPlaneDepthAziElev = depthAziElev;
114114
+ return this.setClipPlane(this.scene.clipPlaneDepthAziElev);
114115
+ }
114116
+ return;
114117
+ }
114118
+ if (posChange > 0)
114119
+ this.volScaleMultiplier = Math.min(2, this.volScaleMultiplier * 1.1);
114120
+ if (posChange < 0)
114121
+ this.volScaleMultiplier = Math.max(0.5, this.volScaleMultiplier * 0.9);
114122
+ this.drawScene();
112687
114123
  };
112688
114124
  Niivue.prototype.mouseClick = function(x2, y, posChange = 0, isDelta = true) {
112689
114125
  var posNow;
@@ -112706,48 +114142,23 @@ Niivue.prototype.mouseClick = function(x2, y, posChange = 0, isDelta = true) {
112706
114142
  return;
112707
114143
  }
112708
114144
  }
112709
- if (this.sliceType === this.sliceTypeRender) {
112710
- if (posChange === 0)
112711
- return;
112712
- if (this.volumes.length > 0 && this.scene.clipPlaneDepthAziElev[0] < 1.8) {
112713
- let depthAziElev = this.scene.clipPlaneDepthAziElev.slice();
112714
- if (posChange > 0)
112715
- depthAziElev[0] = Math.min(1.5, depthAziElev[0] + 0.025);
112716
- if (posChange < 0)
112717
- depthAziElev[0] = Math.max(-1.5, depthAziElev[0] - 0.025);
112718
- if (depthAziElev[0] !== this.scene.clipPlaneDepthAziElev[0]) {
112719
- this.scene.clipPlaneDepthAziElev = depthAziElev;
112720
- return this.setClipPlane(this.scene.clipPlaneDepthAziElev);
112721
- }
112722
- return;
112723
- }
112724
- if (posChange > 0)
112725
- this.volScaleMultiplier = Math.min(2, this.volScaleMultiplier * 1.1);
112726
- if (posChange < 0)
112727
- this.volScaleMultiplier = Math.max(0.5, this.volScaleMultiplier * 0.9);
114145
+ if (this.inRenderTile(x2, y) >= 0) {
114146
+ this.sliceScroll3D(posChange);
112728
114147
  this.drawScene();
112729
114148
  return;
112730
114149
  }
112731
- if (this.numScreenSlices < 1 || this.gl.canvas.height < 1 || this.gl.canvas.width < 1)
114150
+ if (this.screenSlices.length < 1 || this.gl.canvas.height < 1 || this.gl.canvas.width < 1)
112732
114151
  return;
112733
- for (let i2 = 0; i2 < this.numScreenSlices; i2++) {
114152
+ for (let i2 = 0; i2 < this.screenSlices.length; i2++) {
112734
114153
  var axCorSag = this.screenSlices[i2].axCorSag;
112735
114154
  if (this.drawPenAxCorSag >= 0 && this.drawPenAxCorSag !== axCorSag)
112736
114155
  continue;
112737
114156
  if (axCorSag > this.sliceTypeSagittal)
112738
114157
  continue;
112739
- var ltwh = this.screenSlices[i2].leftTopWidthHeight;
112740
- let isMirror = false;
112741
- if (ltwh[2] < 0) {
112742
- isMirror = true;
112743
- ltwh[0] += ltwh[2];
112744
- ltwh[2] = -ltwh[2];
112745
- }
112746
- var fracX = (x2 - ltwh[0]) / ltwh[2];
112747
- if (isMirror)
112748
- fracX = 1 - fracX;
112749
- var fracY = 1 - (y - ltwh[1]) / ltwh[3];
112750
- if (fracX >= 0 && fracX < 1 && fracY >= 0 && fracY < 1) {
114158
+ let texFrac = this.screenXY2TextureFrac(x2, y, i2);
114159
+ if (texFrac[0] < 0)
114160
+ continue;
114161
+ {
112751
114162
  if (!isDelta) {
112752
114163
  this.scene.crosshairPos[2 - axCorSag] = posChange;
112753
114164
  this.drawScene();
@@ -112775,18 +114186,7 @@ Niivue.prototype.mouseClick = function(x2, y, posChange = 0, isDelta = true) {
112775
114186
  });
112776
114187
  return;
112777
114188
  }
112778
- if (axCorSag === this.sliceTypeAxial) {
112779
- this.scene.crosshairPos[0] = fracX;
112780
- this.scene.crosshairPos[1] = fracY;
112781
- }
112782
- if (axCorSag === this.sliceTypeCoronal) {
112783
- this.scene.crosshairPos[0] = fracX;
112784
- this.scene.crosshairPos[2] = fracY;
112785
- }
112786
- if (axCorSag === this.sliceTypeSagittal) {
112787
- this.scene.crosshairPos[1] = fracX;
112788
- this.scene.crosshairPos[2] = fracY;
112789
- }
114189
+ this.scene.crosshairPos = texFrac.slice();
112790
114190
  if (this.opts.drawingEnabled) {
112791
114191
  let pt = this.frac2vox(this.scene.crosshairPos);
112792
114192
  if (this.opts.penValue < 0 || Object.is(this.opts.penValue, -0)) {
@@ -112800,7 +114200,7 @@ Niivue.prototype.mouseClick = function(x2, y, posChange = 0, isDelta = true) {
112800
114200
  } else {
112801
114201
  if (pt[0] === this.drawPenLocation[0] && pt[1] === this.drawPenLocation[1] && pt[2] === this.drawPenLocation[2])
112802
114202
  return;
112803
- this.drawLine(pt, this.drawPenLocation, this.opts.penValue);
114203
+ this.drawPenLine(pt, this.drawPenLocation, this.opts.penValue);
112804
114204
  }
112805
114205
  this.drawPenLocation = pt;
112806
114206
  if (this.opts.isFilledPen)
@@ -112820,45 +114220,274 @@ Niivue.prototype.mouseClick = function(x2, y, posChange = 0, isDelta = true) {
112820
114220
  })
112821
114221
  });
112822
114222
  return;
112823
- } else {
112824
- if (x2 === null && y === null) {
112825
- this.scene.crosshairPos[2 - axCorSag] = posChange;
112826
- this.drawScene();
112827
- return;
112828
- }
112829
114223
  }
112830
114224
  }
112831
114225
  };
112832
- Niivue.prototype.drawSelectionBox = function(leftTopWidthHeight) {
114226
+ Niivue.prototype.drawRuler = function() {
114227
+ let fovMM = [];
114228
+ let ltwh = [];
114229
+ for (let i2 = 0; i2 < this.screenSlices.length; i2++) {
114230
+ if (this.screenSlices[i2].axCorSag === this.sliceTypeRender)
114231
+ continue;
114232
+ if (this.screenSlices[i2].fovMM.length > 1) {
114233
+ ltwh = this.screenSlices[i2].leftTopWidthHeight;
114234
+ fovMM = this.screenSlices[i2].fovMM;
114235
+ break;
114236
+ }
114237
+ }
114238
+ if (ltwh.length < 4)
114239
+ return;
114240
+ let frac10cm = 100 / fovMM[0];
114241
+ let pix10cm = frac10cm * ltwh[2];
114242
+ let pixLeft = ltwh[0] + 0.5 * ltwh[2] - 0.5 * pix10cm;
114243
+ let pixTop = ltwh[1] + ltwh[3] - 2 * this.opts.rulerWidth;
114244
+ let startXYendXY = [pixLeft, pixTop, pixLeft + pix10cm, pixTop];
114245
+ this.drawRuler10cm(startXYendXY);
114246
+ };
114247
+ Niivue.prototype.drawRuler10cm = function(startXYendXY) {
114248
+ this.gl.bindVertexArray(this.genericVAO);
112833
114249
  this.lineShader.use(this.gl);
112834
- this.gl.uniform4fv(this.lineShader.uniforms["lineColor"], this.opts.selectionBoxColor);
114250
+ this.gl.uniform4fv(this.lineShader.uniforms["lineColor"], this.opts.rulerColor);
112835
114251
  this.gl.uniform2fv(this.lineShader.uniforms["canvasWidthHeight"], [
112836
114252
  this.gl.canvas.width,
112837
114253
  this.gl.canvas.height
112838
114254
  ]);
112839
- this.gl.uniform4f(this.lineShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
114255
+ this.gl.uniform1f(this.lineShader.uniforms["thickness"], this.opts.rulerWidth);
114256
+ this.gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], startXYendXY);
114257
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
114258
+ let w1cm = -0.1 * (startXYendXY[0] - startXYendXY[2]);
114259
+ let b = startXYendXY[1];
114260
+ let t = b - 2 * this.opts.rulerWidth;
114261
+ let t2 = b - 4 * this.opts.rulerWidth;
114262
+ for (let i2 = 0; i2 < 11; i2++) {
114263
+ let l = startXYendXY[0] + i2 * w1cm;
114264
+ let xyxy = [l, b, l, t];
114265
+ if (i2 % 5 === 0)
114266
+ xyxy[3] = t2;
114267
+ this.gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], xyxy);
114268
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
114269
+ }
114270
+ this.gl.bindVertexArray(this.unusedVAO);
114271
+ };
114272
+ Niivue.prototype.drawMeasurementTool = function(startXYendXY) {
114273
+ let gl = this.gl;
114274
+ gl.bindVertexArray(this.genericVAO);
114275
+ gl.depthFunc(gl.ALWAYS);
114276
+ gl.enable(gl.BLEND);
114277
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
114278
+ this.lineShader.use(this.gl);
114279
+ gl.uniform4fv(this.lineShader.uniforms["lineColor"], this.opts.rulerColor);
114280
+ gl.uniform2fv(this.lineShader.uniforms["canvasWidthHeight"], [
114281
+ gl.canvas.width,
114282
+ gl.canvas.height
114283
+ ]);
114284
+ gl.uniform1f(this.lineShader.uniforms["thickness"], this.opts.rulerWidth);
114285
+ gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], startXYendXY);
114286
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
114287
+ let c2 = this.opts.rulerColor;
114288
+ c2[3] = 1;
114289
+ gl.uniform4fv(this.lineShader.uniforms["lineColor"], c2);
114290
+ let w = this.opts.rulerWidth;
114291
+ gl.uniform1f(this.lineShader.uniforms["thickness"], w * 2);
114292
+ let sXYeXY = [
114293
+ startXYendXY[0],
114294
+ startXYendXY[1] - w,
114295
+ startXYendXY[0],
114296
+ startXYendXY[1] + w
114297
+ ];
114298
+ gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], sXYeXY);
114299
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
114300
+ sXYeXY = [
114301
+ startXYendXY[2],
114302
+ startXYendXY[3] - w,
114303
+ startXYendXY[2],
114304
+ startXYendXY[3] + w
114305
+ ];
114306
+ gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], sXYeXY);
114307
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
114308
+ let startXY = this.canvasPos2frac([startXYendXY[0], startXYendXY[1]]);
114309
+ let endXY = this.canvasPos2frac([startXYendXY[2], startXYendXY[3]]);
114310
+ if (startXY[0] >= 0 && endXY[0] >= 0) {
114311
+ startXY = this.frac2mm(startXY);
114312
+ endXY = this.frac2mm(endXY);
114313
+ let v = create$1();
114314
+ sub(v, startXY, endXY);
114315
+ let lenMM = len(v);
114316
+ let decimals = 2;
114317
+ if (lenMM > 9)
114318
+ decimals = 1;
114319
+ if (lenMM > 99)
114320
+ decimals = 0;
114321
+ let stringMM = lenMM.toFixed(decimals);
114322
+ this.drawTextBetween(startXYendXY, stringMM);
114323
+ }
114324
+ gl.bindVertexArray(this.unusedVAO);
114325
+ };
114326
+ Niivue.prototype.drawBox = function(leftTopWidthHeight, lineColor = [1, 0, 0, 1]) {
114327
+ this.rectShader.use(this.gl);
114328
+ this.gl.enable(this.gl.BLEND);
114329
+ this.gl.uniform4fv(this.rectShader.uniforms["lineColor"], lineColor);
114330
+ this.gl.uniform2fv(this.rectShader.uniforms["canvasWidthHeight"], [
114331
+ this.gl.canvas.width,
114332
+ this.gl.canvas.height
114333
+ ]);
114334
+ this.gl.uniform4f(this.rectShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
112840
114335
  this.gl.bindVertexArray(this.genericVAO);
112841
114336
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
112842
114337
  this.gl.bindVertexArray(this.unusedVAO);
112843
114338
  };
112844
- Niivue.prototype.drawColorbar = function(leftTopWidthHeight) {
114339
+ Niivue.prototype.drawSelectionBox = function(leftTopWidthHeight, lineColor = [1, 0, 0, 1]) {
114340
+ this.drawBox(leftTopWidthHeight, this.opts.selectionBoxColor);
114341
+ };
114342
+ function tickSpacingX(tickCount, mn, mx) {
114343
+ let range = Math.abs(mx - mn);
114344
+ if (range === 0)
114345
+ return [0, 0];
114346
+ let unroundedTickSize = range / (tickCount - 1);
114347
+ let x2 = Math.ceil(Math.log10(unroundedTickSize) - 1);
114348
+ let pow10x = Math.pow(10, x2);
114349
+ let spacing = Math.ceil(unroundedTickSize / pow10x) * pow10x;
114350
+ let ticMin = mn;
114351
+ if (ticMin % spacing !== 0 && range % spacing !== 0)
114352
+ ticMin = Math.floor((mn + spacing) / spacing) * spacing;
114353
+ if (mn / spacing % 1 !== 0)
114354
+ ticMin = Math.sign(ticMin) * Math.round(Math.abs(ticMin));
114355
+ return [spacing, ticMin];
114356
+ }
114357
+ function tickSpacing(mn, mx) {
114358
+ let range = Math.abs(mx - mn);
114359
+ let [spacing, ticMin] = tickSpacingX(5, mn, mx);
114360
+ if (range % spacing === 0)
114361
+ return [spacing, ticMin];
114362
+ [spacing, ticMin] = tickSpacingX(4, mn, mx);
114363
+ if (range % spacing === 0)
114364
+ return [spacing, ticMin];
114365
+ [spacing, ticMin] = tickSpacingX(5, mn, mx);
114366
+ return [spacing, ticMin];
114367
+ }
114368
+ Niivue.prototype.effectiveCanvasHeight = function() {
114369
+ return this.gl.canvas.height - this.colorbarHeight;
114370
+ };
114371
+ Niivue.prototype.reserveColorbarPanel = function() {
114372
+ let txtHt = Math.max(this.opts.textHeight, 0.01);
114373
+ txtHt = txtHt * Math.min(this.gl.canvas.height, this.gl.canvas.width);
114374
+ let fullHt = 3 * txtHt;
114375
+ let leftTopWidthHeight = [
114376
+ 0,
114377
+ this.gl.canvas.height - fullHt,
114378
+ this.gl.canvas.width,
114379
+ fullHt
114380
+ ];
114381
+ this.colorbarHeight = leftTopWidthHeight[3] + 1;
114382
+ return leftTopWidthHeight;
114383
+ };
114384
+ Niivue.prototype.drawColorbarCore = function(layer = 0, leftTopWidthHeight = [0, 0, 0, 0], isNegativeColor = false) {
114385
+ if (this.volumes.length < 1)
114386
+ return;
114387
+ if (layer >= this.volumes.length)
114388
+ layer = 0;
112845
114389
  if (leftTopWidthHeight[2] <= 0 || leftTopWidthHeight[3] <= 0)
112846
114390
  return;
114391
+ let txtHt = Math.max(this.opts.textHeight, 0.01);
114392
+ txtHt = txtHt * Math.min(this.gl.canvas.height, this.gl.canvas.width);
114393
+ let margin = txtHt;
114394
+ let fullHt = 3 * txtHt;
114395
+ let barHt = txtHt;
114396
+ if (leftTopWidthHeight[3] < fullHt) {
114397
+ if (leftTopWidthHeight[3] < 3)
114398
+ return;
114399
+ margin = 1;
114400
+ barHt = leftTopWidthHeight[3] - 2;
114401
+ }
114402
+ this.gl.disable(this.gl.DEPTH_TEST);
114403
+ this.colorbarHeight = leftTopWidthHeight[3] + 1;
114404
+ [
114405
+ this.opts.backColor[0],
114406
+ this.opts.backColor[1],
114407
+ this.opts.backColor[2],
114408
+ 0.5
114409
+ ];
114410
+ leftTopWidthHeight[1];
114411
+ let barLTWH = [
114412
+ leftTopWidthHeight[0] + margin,
114413
+ leftTopWidthHeight[1],
114414
+ leftTopWidthHeight[2] - 2 * margin,
114415
+ barHt
114416
+ ];
114417
+ let rimLTWH = [
114418
+ barLTWH[0] - 1,
114419
+ barLTWH[1] - 1,
114420
+ barLTWH[2] + 2,
114421
+ barLTWH[3] + 2
114422
+ ];
114423
+ this.drawBox(rimLTWH, this.opts.crosshairColor);
112847
114424
  this.colorbarShader.use(this.gl);
112848
114425
  this.gl.activeTexture(this.gl.TEXTURE1);
112849
114426
  this.gl.bindTexture(this.gl.TEXTURE_2D, this.colormapTexture);
112850
114427
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
112851
114428
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
114429
+ let lx = layer;
114430
+ if (isNegativeColor)
114431
+ lx += this.volumes.length;
114432
+ this.gl.uniform1f(this.colorbarShader.uniforms["layer"], lx);
112852
114433
  this.gl.uniform2fv(this.colorbarShader.uniforms["canvasWidthHeight"], [
112853
114434
  this.gl.canvas.width,
112854
114435
  this.gl.canvas.height
112855
114436
  ]);
112856
- this.gl.uniform4f(this.colorbarShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
114437
+ this.gl.uniform4fv(this.colorbarShader.uniforms["leftTopWidthHeight"], barLTWH);
112857
114438
  this.gl.bindVertexArray(this.genericVAO);
112858
114439
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
112859
114440
  this.gl.bindVertexArray(this.unusedVAO);
112860
114441
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
112861
114442
  this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
114443
+ let min2 = this.volumes[layer].cal_min;
114444
+ let max2 = this.volumes[layer].cal_max;
114445
+ if (min2 >= max2 || txtHt < 1)
114446
+ return;
114447
+ let range = max2 - min2;
114448
+ let [spacing, ticMin] = tickSpacing(min2, max2);
114449
+ function humanize(x2) {
114450
+ return x2.toFixed(6).replace(/\.?0*$/, "");
114451
+ }
114452
+ let tic = ticMin;
114453
+ let ticLTWH = [0, barLTWH[1] + barLTWH[3] - txtHt * 0.5, 2, txtHt * 0.75];
114454
+ let txtTop = ticLTWH[1] + ticLTWH[3];
114455
+ while (tic <= max2) {
114456
+ ticLTWH[0] = barLTWH[0] + (tic - min2) / range * barLTWH[2];
114457
+ this.drawBox(ticLTWH);
114458
+ let str = humanize(tic);
114459
+ if (isNegativeColor)
114460
+ str = "-" + str;
114461
+ this.drawTextBelow([ticLTWH[0], txtTop], str);
114462
+ tic += spacing;
114463
+ }
114464
+ };
114465
+ Niivue.prototype.drawColorbar = function() {
114466
+ let nlayers = this.volumes.length;
114467
+ if (nlayers < 1)
114468
+ return;
114469
+ let ncolorMapNegative = 0;
114470
+ for (let i2 = 0; i2 < nlayers; i2++)
114471
+ if (this.volumes[i2].colorMapNegative.length > 0)
114472
+ ncolorMapNegative++;
114473
+ let leftTopWidthHeight = this.reserveColorbarPanel();
114474
+ let txtHt = Math.max(this.opts.textHeight, 0.01);
114475
+ txtHt = txtHt * Math.min(this.gl.canvas.height, this.gl.canvas.width);
114476
+ let fullHt = 3 * txtHt;
114477
+ let wid = leftTopWidthHeight[2] / (nlayers + ncolorMapNegative);
114478
+ if (leftTopWidthHeight[2] <= 0 || leftTopWidthHeight[3] <= 0) {
114479
+ wid = this.gl.canvas.width / nlayers;
114480
+ leftTopWidthHeight = [0, this.gl.canvas.height - fullHt, wid, fullHt];
114481
+ }
114482
+ leftTopWidthHeight[2] = wid;
114483
+ for (let i2 = 0; i2 < nlayers; i2++) {
114484
+ this.drawColorbarCore(i2, leftTopWidthHeight);
114485
+ leftTopWidthHeight[0] += wid;
114486
+ if (this.volumes[i2].colorMapNegative.length === 0)
114487
+ continue;
114488
+ this.drawColorbarCore(i2, leftTopWidthHeight, true);
114489
+ leftTopWidthHeight[0] += wid;
114490
+ }
112862
114491
  };
112863
114492
  Niivue.prototype.textWidth = function(scale2, str) {
112864
114493
  let w = 0;
@@ -112919,6 +114548,34 @@ Niivue.prototype.drawTextLeft = function(xy, str, scale2 = 1, color = null) {
112919
114548
  xy[1] -= 0.5 * size;
112920
114549
  this.drawText(xy, str, scale2, color);
112921
114550
  };
114551
+ Niivue.prototype.drawTextRightBelow = function(xy, str, scale2 = 1, color = null) {
114552
+ if (this.opts.textHeight <= 0)
114553
+ return;
114554
+ this.opts.textHeight * this.gl.canvas.height * scale2;
114555
+ this.drawText(xy, str, scale2, color);
114556
+ };
114557
+ Niivue.prototype.drawTextBetween = function(startXYendXY, str, scale2 = 1, color = null) {
114558
+ if (this.opts.textHeight <= 0)
114559
+ return;
114560
+ let xy = [
114561
+ (startXYendXY[0] + startXYendXY[2]) * 0.5,
114562
+ (startXYendXY[1] + startXYendXY[3]) * 0.5
114563
+ ];
114564
+ let size = this.opts.textHeight * this.gl.canvas.height * scale2;
114565
+ let w = this.textWidth(size, str);
114566
+ xy[0] -= 0.5 * w;
114567
+ xy[1] -= 0.5 * size;
114568
+ let LTWH = [xy[0] - 1, xy[1] - 1, w + 2, size + 2];
114569
+ let clr = color;
114570
+ if (clr === null)
114571
+ clr = this.opts.crosshairColor;
114572
+ if (clr[0] + clr[1] + clr[2] > 0.8)
114573
+ clr = [0, 0, 0, 0.5];
114574
+ else
114575
+ clr = [1, 1, 1, 0.5];
114576
+ this.drawRect(LTWH, clr);
114577
+ this.drawText(xy, str, scale2, color);
114578
+ };
112922
114579
  Niivue.prototype.drawTextBelow = function(xy, str, scale2 = 1, color = null) {
112923
114580
  if (this.opts.textHeight <= 0)
112924
114581
  return;
@@ -112952,117 +114609,271 @@ Niivue.prototype.setInterpolation = function(isNearest) {
112952
114609
  this.updateInterpolation(1);
112953
114610
  this.drawScene();
112954
114611
  };
112955
- Niivue.prototype.calculateMvpMatrixX = function(leftTopWidthHeight) {
114612
+ function deg2rad(deg) {
114613
+ return deg * (Math.PI / 180);
114614
+ }
114615
+ Niivue.prototype.calculateMvpMatrix2D = function(leftTopWidthHeight, mn, mx, clipTolerance = Infinity, clipDepth = 0, azimuth = null, elevation = null, isRadiolgical) {
112956
114616
  let gl = this.gl;
112957
- gl.viewport(leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
112958
- let whratio = leftTopWidthHeight[2] / leftTopWidthHeight[3];
112959
- console.log(">>", this.back.dimsRAS, this.back.matRAS);
112960
- let scale2 = this.furthestFromPivot * 0.5;
112961
- let origin = [0, 0, 0];
114617
+ gl.viewport(leftTopWidthHeight[0], this.gl.canvas.clientHeight - (leftTopWidthHeight[1] + leftTopWidthHeight[3]), leftTopWidthHeight[2], leftTopWidthHeight[3]);
114618
+ let left = mn[0];
114619
+ let right = mx[0];
114620
+ let leftTopMM = [left, mn[1]];
114621
+ let fovMM = [right - left, mx[1] - mn[1]];
114622
+ if (isRadiolgical) {
114623
+ leftTopMM = [mx[0], mn[1]];
114624
+ fovMM = [mn[0] - mx[0], mx[1] - mn[1]];
114625
+ left = -mx[0];
114626
+ right = -mn[0];
114627
+ }
114628
+ let scale2 = 2 * Math.max(Math.abs(mn[2]), Math.abs(mx[2]));
112962
114629
  let projectionMatrix = create$2();
112963
- if (whratio < 1)
112964
- ortho(projectionMatrix, -scale2, scale2, -scale2 / whratio, scale2 / whratio, 0.01, scale2 * 8);
112965
- else
112966
- ortho(projectionMatrix, -scale2 * whratio, scale2 * whratio, -scale2, scale2, 0.01, scale2 * 8);
114630
+ let near = 0.01;
114631
+ let far = scale2 * 8;
114632
+ if (clipTolerance !== Infinity) {
114633
+ let r = isRadiolgical;
114634
+ if (elevation === 0 && (azimuth === 0 || azimuth === 180))
114635
+ r = !r;
114636
+ let dx = scale2 * 1.8 - clipDepth;
114637
+ if (!r)
114638
+ dx = scale2 * 1.8 + clipDepth;
114639
+ near = dx - clipTolerance;
114640
+ far = dx + clipTolerance;
114641
+ }
114642
+ ortho(projectionMatrix, left, right, mn[1], mx[1], near, far);
112967
114643
  const modelMatrix = create$2();
112968
114644
  modelMatrix[0] = -1;
112969
114645
  let translateVec3 = fromValues$1(0, 0, -scale2 * 1.8);
112970
114646
  translate(modelMatrix, modelMatrix, translateVec3);
112971
- if (this.position)
112972
- translate(modelMatrix, modelMatrix, this.position);
112973
- translate(modelMatrix, modelMatrix, [
112974
- -origin[0],
112975
- -origin[1],
112976
- -origin[2]
112977
- ]);
114647
+ rotateX(modelMatrix, modelMatrix, deg2rad(270 - elevation));
114648
+ rotateZ(modelMatrix, modelMatrix, deg2rad(azimuth - 180));
112978
114649
  let iModelMatrix = create$2();
112979
114650
  invert(iModelMatrix, modelMatrix);
112980
114651
  let normalMatrix = create$2();
112981
114652
  transpose(normalMatrix, iModelMatrix);
112982
114653
  let modelViewProjectionMatrix = create$2();
112983
- multiply$1(modelViewProjectionMatrix, projectionMatrix, modelMatrix);
112984
- return [modelViewProjectionMatrix, modelMatrix, normalMatrix];
114654
+ multiply(modelViewProjectionMatrix, projectionMatrix, modelMatrix);
114655
+ return {
114656
+ modelViewProjectionMatrix,
114657
+ modelMatrix,
114658
+ normalMatrix,
114659
+ leftTopMM,
114660
+ fovMM
114661
+ };
112985
114662
  };
112986
- Niivue.prototype.drawMesh3DX = function(isDepthTest = true, alpha = 1, leftTopWidthHeight) {
112987
- if (this.meshes.length < 1)
114663
+ Niivue.prototype.swizzleVec3MM = function(v3, axCorSag) {
114664
+ if (axCorSag === this.sliceTypeCoronal) {
114665
+ v3 = swizzleVec3(v3, [0, 2, 1]);
114666
+ } else if (axCorSag === this.sliceTypeSagittal) {
114667
+ v3 = swizzleVec3(v3, [1, 2, 0]);
114668
+ }
114669
+ return v3;
114670
+ };
114671
+ Niivue.prototype.screenFieldOfViewMM = function(axCorSag = 0, forceSliceMM = false) {
114672
+ if (!forceSliceMM && !this.opts.isSliceMM) {
114673
+ let fov = this.volumeObject3D.fieldOfViewDeObliqueMM.slice();
114674
+ return this.swizzleVec3MM(fov, axCorSag);
114675
+ }
114676
+ let mnMM = this.volumeObject3D.extentsMin.slice();
114677
+ let mxMM = this.volumeObject3D.extentsMax.slice();
114678
+ create$2();
114679
+ mnMM = this.swizzleVec3MM(mnMM, axCorSag);
114680
+ mxMM = this.swizzleVec3MM(mxMM, axCorSag);
114681
+ let fovMM = create$1();
114682
+ subtract(fovMM, mxMM, mnMM);
114683
+ return fovMM;
114684
+ };
114685
+ Niivue.prototype.screenFieldOfViewExtendedMM = function(axCorSag = 0) {
114686
+ let mnMM = this.volumeObject3D.extentsMin.slice();
114687
+ let mxMM = this.volumeObject3D.extentsMax.slice();
114688
+ let rotation = create$2();
114689
+ mnMM = this.swizzleVec3MM(mnMM, axCorSag);
114690
+ mxMM = this.swizzleVec3MM(mxMM, axCorSag);
114691
+ let fovMM = create$1();
114692
+ subtract(fovMM, mxMM, mnMM);
114693
+ return { mnMM, mxMM, rotation, fovMM };
114694
+ };
114695
+ Niivue.prototype.drawSliceOrientationText = function(leftTopWidthHeight, axCorSag) {
114696
+ this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
114697
+ let topText = "S";
114698
+ if (axCorSag === this.sliceTypeAxial)
114699
+ topText = "A";
114700
+ let leftText = this.opts.isRadiologicalConvention ? "R" : "L";
114701
+ if (axCorSag === this.sliceTypeSagittal)
114702
+ leftText = this.opts.sagittalNoseLeft ? "A" : "P";
114703
+ if (this.opts.isCornerOrientationText) {
114704
+ this.drawTextRightBelow([leftTopWidthHeight[0], leftTopWidthHeight[1]], leftText + topText);
112988
114705
  return;
114706
+ }
114707
+ this.drawTextBelow([
114708
+ leftTopWidthHeight[0] + leftTopWidthHeight[2] * 0.5,
114709
+ leftTopWidthHeight[1]
114710
+ ], topText);
114711
+ this.drawTextRight([
114712
+ leftTopWidthHeight[0],
114713
+ leftTopWidthHeight[1] + leftTopWidthHeight[3] * 0.5
114714
+ ], leftText);
114715
+ };
114716
+ Niivue.prototype.xyMM2xyzMM = function(axCorSag, sliceFrac) {
114717
+ let sliceDim = 2;
114718
+ if (axCorSag === this.sliceTypeCoronal)
114719
+ sliceDim = 1;
114720
+ if (axCorSag === this.sliceTypeSagittal)
114721
+ sliceDim = 0;
114722
+ let a = [0, 0, 0];
114723
+ let b = [1, 1, 0];
114724
+ let c2 = [1, 0, 1];
114725
+ a[sliceDim] = sliceFrac;
114726
+ b[sliceDim] = sliceFrac;
114727
+ c2[sliceDim] = sliceFrac;
114728
+ a = this.frac2mm(a);
114729
+ b = this.frac2mm(b);
114730
+ c2 = this.frac2mm(c2);
114731
+ a = this.swizzleVec3MM(a, axCorSag);
114732
+ b = this.swizzleVec3MM(b, axCorSag);
114733
+ c2 = this.swizzleVec3MM(c2, axCorSag);
114734
+ let denom = (b[0] - a[0]) * (c2[1] - a[1]) - (c2[0] - a[0]) * (b[1] - a[1]);
114735
+ let yMult = (b[0] - a[0]) * (c2[2] - a[2]) - (c2[0] - a[0]) * (b[2] - a[2]);
114736
+ yMult /= denom;
114737
+ let xMult = (b[1] - a[1]) * (c2[2] - a[2]) - (c2[1] - a[1]) * (b[2] - a[2]);
114738
+ xMult /= denom;
114739
+ let AxyzMxy = [0, 0, 0, 0, 0];
114740
+ AxyzMxy[0] = a[0];
114741
+ AxyzMxy[1] = a[1];
114742
+ AxyzMxy[2] = a[2];
114743
+ AxyzMxy[3] = xMult;
114744
+ AxyzMxy[4] = yMult;
114745
+ return AxyzMxy;
114746
+ };
114747
+ Niivue.prototype.draw2DMM = function(leftTopWidthHeight, axCorSag, customMM = NaN) {
114748
+ let screen2 = this.screenFieldOfViewExtendedMM(axCorSag);
114749
+ let isRadiolgical = this.opts.isRadiologicalConvention && axCorSag < this.sliceTypeSagittal;
114750
+ if (customMM === Infinity || customMM === -Infinity) {
114751
+ isRadiolgical = customMM !== Infinity;
114752
+ if (axCorSag === this.sliceTypeCoronal)
114753
+ isRadiolgical = !isRadiolgical;
114754
+ } else if (this.opts.sagittalNoseLeft && axCorSag === this.sliceTypeSagittal)
114755
+ isRadiolgical = !isRadiolgical;
114756
+ let elevation = 0;
114757
+ let azimuth = 0;
114758
+ if (axCorSag === this.sliceTypeSagittal) {
114759
+ azimuth = isRadiolgical ? 90 : -90;
114760
+ } else if (axCorSag === this.sliceTypeCoronal) {
114761
+ azimuth = isRadiolgical ? 180 : 0;
114762
+ } else {
114763
+ azimuth = isRadiolgical ? 180 : 0;
114764
+ elevation = isRadiolgical ? -90 : 90;
114765
+ }
114766
+ if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0)
114767
+ leftTopWidthHeight = this.scaleSlice(screen2.fovMM[0], screen2.fovMM[1]);
114768
+ if (isNaN(customMM)) {
114769
+ let panXY = this.swizzleVec3MM(this.scene.pan2Dxyzmm, axCorSag);
114770
+ screen2.mnMM[0] -= panXY[0];
114771
+ screen2.mxMM[0] -= panXY[0];
114772
+ screen2.mnMM[1] -= panXY[1];
114773
+ screen2.mxMM[1] -= panXY[1];
114774
+ screen2.mnMM[0] /= this.scene.pan2Dxyzmm[3];
114775
+ screen2.mxMM[0] /= this.scene.pan2Dxyzmm[3];
114776
+ screen2.mnMM[1] /= this.scene.pan2Dxyzmm[3];
114777
+ screen2.mxMM[1] /= this.scene.pan2Dxyzmm[3];
114778
+ }
114779
+ let sliceDim = 2;
114780
+ if (axCorSag === this.sliceTypeCoronal)
114781
+ sliceDim = 1;
114782
+ if (axCorSag === this.sliceTypeSagittal)
114783
+ sliceDim = 0;
114784
+ let sliceFrac = this.scene.crosshairPos[sliceDim];
114785
+ let mm = this.frac2mm(this.scene.crosshairPos);
114786
+ if (!isNaN(customMM) && customMM !== Infinity && customMM !== -Infinity) {
114787
+ mm = this.frac2mm([0.5, 0.5, 0.5]);
114788
+ mm[sliceDim] = customMM;
114789
+ let frac = this.mm2frac(mm);
114790
+ sliceFrac = frac[sliceDim];
114791
+ }
114792
+ let sliceMM = mm[sliceDim];
112989
114793
  let gl = this.gl;
112990
- let m, modelMtx, normMtx;
112991
- [m, modelMtx, normMtx] = this.calculateMvpMatrixX(leftTopWidthHeight);
114794
+ gl.clear(gl.DEPTH_BUFFER_BIT);
114795
+ let obj = this.calculateMvpMatrix2D(leftTopWidthHeight, screen2.mnMM, screen2.mxMM, Infinity, 0, azimuth, elevation, isRadiolgical);
114796
+ if (customMM === Infinity || customMM === -Infinity) {
114797
+ this.draw3D(leftTopWidthHeight, obj.modelViewProjectionMatrix, obj.modelMatrix, obj.normalMatrix, azimuth, elevation);
114798
+ return;
114799
+ }
112992
114800
  gl.enable(gl.DEPTH_TEST);
112993
114801
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
112994
114802
  gl.disable(gl.BLEND);
112995
114803
  gl.depthFunc(gl.GREATER);
112996
- if (isDepthTest) {
112997
- gl.disable(gl.BLEND);
112998
- gl.depthFunc(gl.GREATER);
112999
- } else {
113000
- gl.enable(gl.BLEND);
113001
- gl.depthFunc(gl.ALWAYS);
113002
- }
113003
114804
  gl.disable(gl.CULL_FACE);
113004
- this.meshShader.use(this.gl);
113005
- gl.uniformMatrix4fv(this.meshShader.uniforms["mvpMtx"], false, m);
113006
- gl.uniformMatrix4fv(this.meshShader.uniforms["modelMtx"], false, modelMtx);
113007
- gl.uniformMatrix4fv(this.meshShader.uniforms["normMtx"], false, normMtx);
113008
- gl.uniform1f(this.meshShader.uniforms["opacity"], alpha);
113009
- let hasFibers = false;
113010
- for (let i2 = 0; i2 < this.meshes.length; i2++) {
113011
- if (this.meshes[i2].indexCount < 3)
113012
- continue;
113013
- if (this.meshes[i2].offsetPt0) {
113014
- hasFibers = true;
113015
- continue;
113016
- }
113017
- gl.bindVertexArray(this.meshes[i2].vao);
113018
- gl.drawElements(gl.TRIANGLES, this.meshes[i2].indexCount, gl.UNSIGNED_INT, 0);
113019
- gl.bindVertexArray(this.unusedVAO);
113020
- }
113021
- if (!hasFibers) {
113022
- gl.enable(gl.BLEND);
113023
- gl.depthFunc(gl.ALWAYS);
113024
- return;
114805
+ this.sliceMMShader.use(this.gl);
114806
+ this.gl.uniform1i(this.sliceMMShader.uniforms["backgroundMasksOverlays"], this.backgroundMasksOverlays);
114807
+ this.gl.uniform1f(this.sliceMMShader.uniforms["opacity"], this.volumes[0].opacity);
114808
+ this.gl.uniform1i(this.sliceMMShader.uniforms["axCorSag"], axCorSag);
114809
+ this.gl.uniform1f(this.sliceMMShader.uniforms["slice"], sliceFrac);
114810
+ this.gl.uniformMatrix4fv(this.sliceMMShader.uniforms["frac2mm"], false, this.volumes[0].frac2mm);
114811
+ this.gl.uniformMatrix4fv(this.sliceMMShader.uniforms["mvpMtx"], false, obj.modelViewProjectionMatrix.slice());
114812
+ this.gl.bindVertexArray(this.genericVAO);
114813
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
114814
+ this.gl.bindVertexArray(this.unusedVAO);
114815
+ this.screenSlices.push({
114816
+ leftTopWidthHeight,
114817
+ axCorSag,
114818
+ sliceFrac,
114819
+ AxyzMxy: this.xyMM2xyzMM(axCorSag, sliceFrac),
114820
+ leftTopMM: obj.leftTopMM,
114821
+ fovMM: obj.fovMM
114822
+ });
114823
+ if (isNaN(customMM)) {
114824
+ this.drawCrosshairs3D(true, 1, obj.modelViewProjectionMatrix);
113025
114825
  }
113026
- let shader = this.fiberShader;
113027
- shader.use(this.gl);
113028
- gl.uniformMatrix4fv(shader.uniforms["mvpMtx"], false, m);
113029
- gl.uniform1f(shader.uniforms["opacity"], alpha);
113030
- for (let i2 = 0; i2 < this.meshes.length; i2++) {
113031
- if (this.meshes[i2].indexCount < 3)
113032
- continue;
113033
- if (!this.meshes[i2].offsetPt0)
113034
- continue;
113035
- gl.bindVertexArray(this.meshes[i2].vao);
113036
- gl.drawElements(gl.LINE_STRIP, this.meshes[i2].indexCount, gl.UNSIGNED_INT, 0);
113037
- gl.bindVertexArray(this.unusedVAO);
114826
+ if (this.opts.meshThicknessOn2D > 0) {
114827
+ if (this.opts.meshThicknessOn2D !== Infinity)
114828
+ obj = this.calculateMvpMatrix2D(leftTopWidthHeight, screen2.mnMM, screen2.mxMM, this.opts.meshThicknessOn2D, sliceMM, azimuth, elevation, isRadiolgical);
114829
+ this.drawMesh3D(true, 1, obj.modelViewProjectionMatrix, obj.modelMatrix, obj.normalMatrix);
113038
114830
  }
113039
- gl.enable(gl.BLEND);
113040
- gl.depthFunc(gl.ALWAYS);
114831
+ if (isNaN(customMM))
114832
+ this.drawCrosshairs3D(false, 0.15, obj.modelViewProjectionMatrix);
114833
+ this.drawSliceOrientationText(leftTopWidthHeight, axCorSag);
113041
114834
  this.readyForSync = true;
113042
114835
  };
113043
- Niivue.prototype.draw2D = function(leftTopWidthHeight, axCorSag) {
114836
+ Niivue.prototype.draw2DVox = function(leftTopWidthHeight, axCorSag, customMM = NaN) {
114837
+ let fovMM = this.screenFieldOfViewMM(axCorSag);
114838
+ if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0) {
114839
+ leftTopWidthHeight = this.scaleSlice(fovMM[0], fovMM[1]);
114840
+ }
113044
114841
  this.gl.cullFace(this.gl.FRONT);
114842
+ let ltwh = leftTopWidthHeight.slice();
113045
114843
  let gl = this.gl;
113046
114844
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
113047
114845
  gl.disable(gl.DEPTH_TEST);
113048
- var crossXYZ = [
114846
+ let sliceDim = 2;
114847
+ let crossXYZ = [
113049
114848
  this.scene.crosshairPos[0],
113050
114849
  this.scene.crosshairPos[1],
113051
114850
  this.scene.crosshairPos[2]
113052
114851
  ];
113053
- if (axCorSag === this.sliceTypeCoronal)
114852
+ if (axCorSag === this.sliceTypeCoronal) {
114853
+ sliceDim = 1;
113054
114854
  crossXYZ = [
113055
114855
  this.scene.crosshairPos[0],
113056
114856
  this.scene.crosshairPos[2],
113057
114857
  this.scene.crosshairPos[1]
113058
114858
  ];
113059
- if (axCorSag === this.sliceTypeSagittal)
114859
+ }
114860
+ if (axCorSag === this.sliceTypeSagittal) {
114861
+ sliceDim = 0;
113060
114862
  crossXYZ = [
113061
114863
  this.scene.crosshairPos[1],
113062
114864
  this.scene.crosshairPos[2],
113063
114865
  this.scene.crosshairPos[0]
113064
114866
  ];
114867
+ }
114868
+ if (!isNaN(customMM)) {
114869
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
114870
+ mm[sliceDim] = customMM;
114871
+ let frac = this.mm2frac(mm);
114872
+ crossXYZ[2] = frac[sliceDim];
114873
+ }
113065
114874
  let isMirrorLR = this.opts.isRadiologicalConvention && axCorSag < this.sliceTypeSagittal;
114875
+ if (this.opts.sagittalNoseLeft && axCorSag === this.sliceTypeSagittal)
114876
+ isMirrorLR = !isMirrorLR;
113066
114877
  this.sliceShader.use(this.gl);
113067
114878
  this.gl.uniform1f(this.sliceShader.uniforms["opacity"], this.volumes[0].opacity);
113068
114879
  this.gl.uniform1i(this.sliceShader.uniforms["backgroundMasksOverlays"], this.backgroundMasksOverlays);
@@ -113080,58 +114891,50 @@ Niivue.prototype.draw2D = function(leftTopWidthHeight, axCorSag) {
113080
114891
  this.gl.uniform4f(this.sliceShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
113081
114892
  this.gl.bindVertexArray(this.genericVAO);
113082
114893
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
113083
- this.screenSlices[this.numScreenSlices].leftTopWidthHeight = leftTopWidthHeight;
113084
- this.screenSlices[this.numScreenSlices].axCorSag = axCorSag;
113085
- this.numScreenSlices += 1;
114894
+ this.screenSlices.push({
114895
+ leftTopWidthHeight,
114896
+ axCorSag,
114897
+ sliceFrac: crossXYZ[2],
114898
+ AxyzMxy: [],
114899
+ leftTopMM: [],
114900
+ fovMM: [fovMM[0], fovMM[1]]
114901
+ });
114902
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
114903
+ this.drawSliceOrientationText(ltwh, axCorSag);
114904
+ if (!isNaN(customMM))
114905
+ return;
113086
114906
  if (this.opts.crosshairWidth <= 0)
113087
114907
  return;
113088
- this.lineShader.use(this.gl);
113089
- this.gl.uniform4fv(this.lineShader.uniforms["lineColor"], this.opts.crosshairColor);
113090
- this.gl.uniform2fv(this.lineShader.uniforms["canvasWidthHeight"], [
114908
+ this.gl.bindVertexArray(this.genericVAO);
114909
+ this.rectShader.use(this.gl);
114910
+ this.gl.uniform4fv(this.rectShader.uniforms["lineColor"], this.opts.crosshairColor);
114911
+ this.gl.uniform2fv(this.rectShader.uniforms["canvasWidthHeight"], [
113091
114912
  this.gl.canvas.width,
113092
114913
  this.gl.canvas.height
113093
114914
  ]);
113094
114915
  var xleft = leftTopWidthHeight[0] + leftTopWidthHeight[2] * crossXYZ[0];
113095
- this.gl.uniform4f(this.lineShader.uniforms["leftTopWidthHeight"], xleft - 0.5 * this.opts.crosshairWidth, leftTopWidthHeight[1], this.opts.crosshairWidth, leftTopWidthHeight[3]);
114916
+ this.gl.uniform4f(this.rectShader.uniforms["leftTopWidthHeight"], xleft - 0.5 * this.opts.crosshairWidth, leftTopWidthHeight[1], this.opts.crosshairWidth, leftTopWidthHeight[3]);
113096
114917
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
113097
114918
  var xtop = leftTopWidthHeight[1] + leftTopWidthHeight[3] * (1 - crossXYZ[1]);
113098
- this.gl.uniform4f(this.lineShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], xtop - 0.5 * this.opts.crosshairWidth, leftTopWidthHeight[2], this.opts.crosshairWidth);
114919
+ this.gl.uniform4f(this.rectShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], xtop - 0.5 * this.opts.crosshairWidth, leftTopWidthHeight[2], this.opts.crosshairWidth);
113099
114920
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
113100
114921
  this.gl.bindVertexArray(this.unusedVAO);
113101
114922
  this.gl.enable(this.gl.CULL_FACE);
113102
- if (isMirrorLR)
113103
- this.drawTextRight([
113104
- leftTopWidthHeight[0] + leftTopWidthHeight[2] + 1,
113105
- leftTopWidthHeight[1] + 0.5 * leftTopWidthHeight[3]
113106
- ], "R");
113107
- else if (axCorSag < this.sliceTypeSagittal)
113108
- this.drawTextRight([
113109
- leftTopWidthHeight[0] + 1,
113110
- leftTopWidthHeight[1] + 0.5 * leftTopWidthHeight[3]
113111
- ], "L");
113112
- if (axCorSag === this.sliceTypeAxial)
113113
- this.drawTextBelow([
113114
- leftTopWidthHeight[0] + 0.5 * leftTopWidthHeight[2],
113115
- leftTopWidthHeight[1] + 1
113116
- ], "A");
113117
- if (axCorSag > this.sliceTypeAxial)
113118
- this.drawTextBelow([
113119
- leftTopWidthHeight[0] + 0.5 * leftTopWidthHeight[2],
113120
- leftTopWidthHeight[1] + 1
113121
- ], "S");
113122
- if (axCorSag !== this.sliceTypeAxial)
113123
- return;
113124
- gl.viewport(leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
113125
114923
  gl.enable(gl.DEPTH_TEST);
113126
114924
  gl.depthFunc(gl.ALWAYS);
113127
114925
  gl.clearDepth(0);
113128
114926
  this.sync();
113129
114927
  };
113130
- Niivue.prototype.calculateMvpMatrix = function() {
113131
- function deg2rad(deg) {
113132
- return deg * (Math.PI / 180);
113133
- }
113134
- let whratio = this.gl.canvas.clientWidth / this.gl.canvas.clientHeight;
114928
+ Niivue.prototype.draw2D = function(leftTopWidthHeight, axCorSag, customMM = NaN) {
114929
+ if (this.opts.isSliceMM)
114930
+ this.draw2DMM(leftTopWidthHeight, axCorSag, customMM);
114931
+ else
114932
+ this.draw2DVox(leftTopWidthHeight, axCorSag, customMM);
114933
+ };
114934
+ Niivue.prototype.calculateMvpMatrix = function(unused, leftTopWidthHeight = [0, 0, 0, 0], azimuth, elevation) {
114935
+ if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0)
114936
+ leftTopWidthHeight = [0, 0, this.gl.canvas.width, this.gl.canvas.height];
114937
+ let whratio = leftTopWidthHeight[2] / leftTopWidthHeight[3];
113135
114938
  let scale2 = this.furthestFromPivot;
113136
114939
  let origin = this.pivot3D;
113137
114940
  let projectionMatrix = create$2();
@@ -113146,8 +114949,8 @@ Niivue.prototype.calculateMvpMatrix = function() {
113146
114949
  translate(modelMatrix, modelMatrix, translateVec3);
113147
114950
  if (this.position)
113148
114951
  translate(modelMatrix, modelMatrix, this.position);
113149
- rotateX(modelMatrix, modelMatrix, deg2rad(270 - this.scene.renderElevation));
113150
- rotateZ(modelMatrix, modelMatrix, deg2rad(this.scene.renderAzimuth - 180));
114952
+ rotateX(modelMatrix, modelMatrix, deg2rad(270 - elevation));
114953
+ rotateZ(modelMatrix, modelMatrix, deg2rad(azimuth - 180));
113151
114954
  translate(modelMatrix, modelMatrix, [
113152
114955
  -origin[0],
113153
114956
  -origin[1],
@@ -113158,24 +114961,24 @@ Niivue.prototype.calculateMvpMatrix = function() {
113158
114961
  let normalMatrix = create$2();
113159
114962
  transpose(normalMatrix, iModelMatrix);
113160
114963
  let modelViewProjectionMatrix = create$2();
113161
- multiply$1(modelViewProjectionMatrix, projectionMatrix, modelMatrix);
114964
+ multiply(modelViewProjectionMatrix, projectionMatrix, modelMatrix);
113162
114965
  return [modelViewProjectionMatrix, modelMatrix, normalMatrix];
113163
114966
  };
113164
- Niivue.prototype.calculateRayDirection = function() {
113165
- function deg2rad(deg) {
114967
+ Niivue.prototype.calculateRayDirection = function(azimuth, elevation) {
114968
+ function deg2rad2(deg) {
113166
114969
  return deg * (Math.PI / 180);
113167
114970
  }
113168
114971
  const modelMatrix = create$2();
113169
114972
  modelMatrix[0] = -1;
113170
- rotateX(modelMatrix, modelMatrix, deg2rad(270 - this.scene.renderElevation));
113171
- rotateZ(modelMatrix, modelMatrix, deg2rad(this.scene.renderAzimuth - 180));
114973
+ rotateX(modelMatrix, modelMatrix, deg2rad2(270 - elevation));
114974
+ rotateZ(modelMatrix, modelMatrix, deg2rad2(azimuth - 180));
113172
114975
  if (this.back.obliqueRAS) {
113173
114976
  let oblique = clone$1(this.back.obliqueRAS);
113174
- multiply$1(modelMatrix, modelMatrix, oblique);
114977
+ multiply(modelMatrix, modelMatrix, oblique);
113175
114978
  }
113176
114979
  let projectionMatrix = fromValues$2(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1);
113177
114980
  let mvpMatrix = create$2();
113178
- multiply$1(mvpMatrix, projectionMatrix, modelMatrix);
114981
+ multiply(mvpMatrix, projectionMatrix, modelMatrix);
113179
114982
  var inv = create$2();
113180
114983
  invert(inv, mvpMatrix);
113181
114984
  var rayDir4 = fromValues(0, 0, -1, 1);
@@ -113191,7 +114994,7 @@ Niivue.prototype.calculateRayDirection = function() {
113191
114994
  rayDir[2] = tiny;
113192
114995
  return rayDir;
113193
114996
  };
113194
- Niivue.prototype.setPivot3D = function() {
114997
+ Niivue.prototype.sceneExtentsMinMax = function() {
113195
114998
  let mn = fromValues$1(0, 0, 0);
113196
114999
  let mx = fromValues$1(0, 0, 0);
113197
115000
  if (this.volumes.length > 0) {
@@ -113209,11 +115012,19 @@ Niivue.prototype.setPivot3D = function() {
113209
115012
  max$T(mx, mx, v);
113210
115013
  }
113211
115014
  }
115015
+ let range = create$1();
115016
+ subtract(range, mx, mn);
115017
+ return [mn, mx, range];
115018
+ };
115019
+ Niivue.prototype.setPivot3D = function() {
115020
+ let [mn, mx] = this.sceneExtentsMinMax();
113212
115021
  let pivot = create$1();
113213
115022
  add$1(pivot, mn, mx);
113214
115023
  scale(pivot, pivot, 0.5);
113215
115024
  this.pivot3D = [pivot[0], pivot[1], pivot[2]];
113216
115025
  subtract(pivot, mx, mn);
115026
+ this.extentsMin = mn;
115027
+ this.extentsMax = mx;
113217
115028
  this.furthestFromPivot = length$1(pivot) * 0.5;
113218
115029
  };
113219
115030
  Niivue.prototype.drawGraphLine = function(LTRB, color = [1, 0, 0, 0.5], thickness = 2) {
@@ -113230,35 +115041,22 @@ Niivue.prototype.drawGraphLine = function(LTRB, color = [1, 0, 0, 0.5], thicknes
113230
115041
  this.gl.bindVertexArray(this.unusedVAO);
113231
115042
  };
113232
115043
  Niivue.prototype.drawRect = function(LTWH, color = [1, 0, 0, 0.5]) {
113233
- this.lineShader.use(this.gl);
113234
- this.gl.uniform4fv(this.lineShader.uniforms["lineColor"], color);
113235
- this.gl.uniform2fv(this.lineShader.uniforms["canvasWidthHeight"], [
115044
+ this.rectShader.use(this.gl);
115045
+ this.gl.uniform4fv(this.rectShader.uniforms["lineColor"], color);
115046
+ this.gl.uniform2fv(this.rectShader.uniforms["canvasWidthHeight"], [
113236
115047
  this.gl.canvas.width,
113237
115048
  this.gl.canvas.height
113238
115049
  ]);
113239
- this.gl.uniform4f(this.lineShader.uniforms["leftTopWidthHeight"], LTWH[0], LTWH[1], LTWH[2], LTWH[3]);
115050
+ this.gl.uniform4f(this.rectShader.uniforms["leftTopWidthHeight"], LTWH[0], LTWH[1], LTWH[2], LTWH[3]);
113240
115051
  this.gl.bindVertexArray(this.genericVAO);
113241
115052
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
113242
115053
  this.gl.bindVertexArray(this.unusedVAO);
113243
115054
  };
113244
- function tickSpacing(tickCount, mn, mx) {
113245
- let range = Math.abs(mx - mn);
113246
- if (range === 0)
113247
- return [0, 0];
113248
- let unroundedTickSize = range / (tickCount - 1);
113249
- let x2 = Math.ceil(Math.log10(unroundedTickSize) - 1);
113250
- let pow10x = Math.pow(10, x2);
113251
- let spacing = Math.ceil(unroundedTickSize / pow10x) * pow10x;
113252
- let ticMin = mn;
113253
- if (mn / spacing % 1 !== 0)
113254
- ticMin = Math.sign(ticMin) * Math.round(Math.abs(ticMin));
113255
- return [spacing, ticMin];
113256
- }
113257
115055
  Niivue.prototype.drawGraph = function() {
113258
115056
  this.gl;
113259
115057
  let graph = this.graph;
113260
115058
  if (this.graph.autoSizeMultiplanar && this.sliceType === this.sliceTypeMultiplanar) {
113261
- for (let i2 = 0; i2 < this.numScreenSlices; i2++) {
115059
+ for (let i2 = 0; i2 < this.screenSlices.length; i2++) {
113262
115060
  var axCorSag = this.screenSlices[i2].axCorSag;
113263
115061
  if (axCorSag !== this.sliceTypeSagittal)
113264
115062
  continue;
@@ -113351,7 +115149,7 @@ Niivue.prototype.drawGraph = function() {
113351
115149
  graph.backColor[3]
113352
115150
  ];
113353
115151
  this.drawRect(graph.LTWH, borderColor);
113354
- let [spacing, ticMin] = tickSpacing(5, mn, mx);
115152
+ let [spacing, ticMin] = tickSpacing(mn, mx);
113355
115153
  function humanize(x2) {
113356
115154
  return x2.toFixed(6).replace(/\.?0*$/, "");
113357
115155
  }
@@ -113415,7 +115213,7 @@ Niivue.prototype.drawGraph = function() {
113415
115213
  if (i2 % 2 === 1) {
113416
115214
  thick *= 0.5;
113417
115215
  this.drawGraphLine([x2, plotLTWH[1], x2, plotLTWH[1] + plotLTWH[3]], thinColor, thick);
113418
- } else if (i2 > 0) {
115216
+ } else {
113419
115217
  let str = humanize(i2);
113420
115218
  if (fntSize > 0)
113421
115219
  this.drawTextBelow([x2, 2 + plotLTWH[1] + plotLTWH[3]], str, fntScale, graph.textColor);
@@ -113452,53 +115250,77 @@ Niivue.prototype.drawGraph = function() {
113452
115250
  this.drawGraphLine([x2, plotLTWH[1], x2, plotLTWH[1] + plotLTWH[3]], [graph.lineRGB[3][0], graph.lineRGB[3][1], graph.lineRGB[3][2], 1], graph.lineThickness);
113453
115251
  }
113454
115252
  };
113455
- Niivue.prototype.draw3D = function() {
113456
- this.setPivot3D();
115253
+ function isRadiological(mtx) {
115254
+ let vRight = fromValues(1, 0, 0, 0);
115255
+ let vRotated = create();
115256
+ transformMat4(vRotated, vRight, mtx);
115257
+ return vRotated[0];
115258
+ }
115259
+ function unProject(winX, winY, winZ, mvpMatrix) {
115260
+ let inp = fromValues(winX, winY, winZ, 1);
115261
+ let finalMatrix = clone$1(mvpMatrix);
115262
+ invert(finalMatrix, finalMatrix);
115263
+ inp[0] = inp[0] * 2 - 1;
115264
+ inp[1] = inp[1] * 2 - 1;
115265
+ inp[2] = inp[2] * 2 - 1;
115266
+ let out = create();
115267
+ transformMat4(out, inp, finalMatrix);
115268
+ if (out[3] === 0)
115269
+ return out;
115270
+ out[0] /= out[3];
115271
+ out[1] /= out[3];
115272
+ out[2] /= out[3];
115273
+ return out;
115274
+ }
115275
+ function unpackFloatFromVec4i(val) {
115276
+ var bitSh = [
115277
+ 1 / (256 * 256 * 256),
115278
+ 1 / (256 * 256),
115279
+ 1 / 256,
115280
+ 1
115281
+ ];
115282
+ return (val[0] * bitSh[0] + val[1] * bitSh[1] + val[2] * bitSh[2] + val[3] * bitSh[3]) / 255;
115283
+ }
115284
+ Niivue.prototype.depthPicker = function(leftTopWidthHeight, mvpMatrix) {
115285
+ if (!this.scene.mouseDepthPicker)
115286
+ return;
115287
+ this.scene.mouseDepthPicker = false;
113457
115288
  let gl = this.gl;
113458
- gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
113459
- gl.enable(gl.DEPTH_TEST);
113460
- gl.depthFunc(gl.ALWAYS);
113461
- gl.clearDepth(0);
113462
- if (this.volumes.length === 0) {
113463
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
113464
- this.drawMesh3D(true, 1);
115289
+ const pixelX = this.mousePos[0] * leftTopWidthHeight[2] / leftTopWidthHeight[2];
115290
+ const pixelY = gl.canvas.height - this.mousePos[1] * leftTopWidthHeight[3] / leftTopWidthHeight[3] - 1;
115291
+ console.log(pixelX + "/" + pixelY);
115292
+ const rgbaPixel = new Uint8Array(4);
115293
+ gl.readPixels(pixelX, pixelY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, rgbaPixel);
115294
+ this.selectedObjectId = rgbaPixel[3];
115295
+ if (this.selectedObjectId === this.VOLUME_ID) {
115296
+ this.scene.crosshairPos = new Float32Array(rgbaPixel.slice(0, 3)).map((x2) => x2 / 255);
113465
115297
  return;
113466
115298
  }
113467
- let mvpMatrix, modelMatrix, normalMatrix;
113468
- [mvpMatrix, modelMatrix, normalMatrix] = this.calculateMvpMatrix(this.volumeObject3D);
113469
- const rayDir = this.calculateRayDirection();
115299
+ let depthZ = unpackFloatFromVec4i(rgbaPixel);
115300
+ if (depthZ > 1)
115301
+ return;
115302
+ let fracX = (this.mousePos[0] - leftTopWidthHeight[0]) / leftTopWidthHeight[2];
115303
+ let fracY = (gl.canvas.height - this.mousePos[1] - leftTopWidthHeight[1]) / leftTopWidthHeight[3];
115304
+ let mm = unProject(fracX, fracY, depthZ, mvpMatrix);
115305
+ let frac = this.mm2frac(mm);
115306
+ if (frac[0] < 0 || frac[0] > 1 || frac[1] < 0 || frac[1] > 1 || frac[2] < 0 || frac[2] > 1)
115307
+ return;
115308
+ this.scene.crosshairPos = this.mm2frac(mm);
115309
+ };
115310
+ Niivue.prototype.drawImage3D = function(mvpMatrix, azimuth, elevation) {
115311
+ if (this.volumes.length === 0)
115312
+ return;
115313
+ let gl = this.gl;
115314
+ const rayDir = this.calculateRayDirection(azimuth, elevation);
113470
115315
  let object3D = this.volumeObject3D;
113471
- if (this.scene.mouseDepthPicker) {
113472
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
113473
- this.scene.mouseDepthPicker = false;
113474
- if (object3D.isVisible && object3D.isPickable) {
113475
- let shader = this.pickingShader;
113476
- shader.use(this.gl);
113477
- gl.enable(gl.CULL_FACE);
113478
- gl.cullFace(gl.FRONT);
113479
- gl.uniformMatrix4fv(shader.mvpUniformLoc, false, mvpMatrix);
113480
- gl.uniform3fv(shader.rayDirUniformLoc, rayDir);
113481
- gl.uniform4fv(shader.clipPlaneUniformLoc, this.scene.clipPlane);
113482
- gl.uniform1i(shader.uniforms["id"], object3D.id);
113483
- gl.bindVertexArray(object3D.vao);
113484
- gl.drawElements(object3D.mode, object3D.indexCount, gl.UNSIGNED_SHORT, 0);
113485
- gl.bindVertexArray(this.unusedVAO);
113486
- }
113487
- const pixelX = this.mousePos[0] * gl.canvas.width / gl.canvas.clientWidth;
113488
- const pixelY = gl.canvas.height - this.mousePos[1] * gl.canvas.height / gl.canvas.clientHeight - 1;
113489
- const rgbaPixel = new Uint8Array(4);
113490
- gl.readPixels(pixelX, pixelY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, rgbaPixel);
113491
- this.selectedObjectId = rgbaPixel[3];
113492
- if (this.selectedObjectId === this.VOLUME_ID) {
113493
- this.scene.crosshairPos = new Float32Array(rgbaPixel.slice(0, 3)).map((x2) => x2 / 255);
113494
- }
113495
- }
113496
- if (this.volumeObject3D.isVisible) {
115316
+ if (object3D) {
113497
115317
  gl.enable(gl.BLEND);
113498
115318
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
113499
115319
  gl.enable(gl.CULL_FACE);
113500
115320
  gl.cullFace(gl.FRONT);
113501
115321
  let shader = this.renderShader;
115322
+ if (this.scene.mouseDepthPicker)
115323
+ shader = this.pickingImageShader;
113502
115324
  shader.use(this.gl);
113503
115325
  gl.uniform1i(shader.uniforms["backgroundMasksOverlays"], this.backgroundMasksOverlays);
113504
115326
  gl.uniformMatrix4fv(shader.mvpUniformLoc, false, mvpMatrix);
@@ -113509,22 +115331,99 @@ Niivue.prototype.draw3D = function() {
113509
115331
  gl.drawElements(object3D.mode, object3D.indexCount, gl.UNSIGNED_SHORT, 0);
113510
115332
  gl.bindVertexArray(this.unusedVAO);
113511
115333
  }
113512
- this.drawCrosshairs3D(true, 1);
113513
- this.drawMesh3D(true, 1);
113514
- this.drawMesh3D(false, 0.02);
113515
- this.drawCrosshairs3D(false, 0.15);
115334
+ };
115335
+ Niivue.prototype.drawOrientationCube = function(leftTopWidthHeight, azimuth = 0, elevation = 0) {
115336
+ if (!this.opts.isOrientCube)
115337
+ return;
115338
+ let sz = 0.05 * Math.min(leftTopWidthHeight[2], leftTopWidthHeight[3]);
115339
+ if (sz < 5)
115340
+ return;
115341
+ let gl = this.gl;
115342
+ gl.enable(gl.CULL_FACE);
115343
+ gl.cullFace(gl.BACK);
115344
+ this.orientCubeShader.use(this.gl);
115345
+ gl.bindVertexArray(this.orientCubeShaderVAO);
115346
+ function deg2rad2(deg) {
115347
+ return deg * (Math.PI / 180);
115348
+ }
115349
+ let modelMatrix = create$2();
115350
+ let projectionMatrix = create$2();
115351
+ ortho(projectionMatrix, 0, gl.canvas.width, 0, gl.canvas.height, -10 * sz, 10 * sz);
115352
+ translate(modelMatrix, modelMatrix, [
115353
+ 1.8 * sz + leftTopWidthHeight[0],
115354
+ 1.8 * sz + leftTopWidthHeight[1],
115355
+ 0
115356
+ ]);
115357
+ scale$1(modelMatrix, modelMatrix, [sz, sz, sz]);
115358
+ rotateX(modelMatrix, modelMatrix, deg2rad2(270 - elevation));
115359
+ rotateZ(modelMatrix, modelMatrix, deg2rad2(-azimuth));
115360
+ let modelViewProjectionMatrix = create$2();
115361
+ multiply(modelViewProjectionMatrix, projectionMatrix, modelMatrix);
115362
+ gl.uniformMatrix4fv(this.orientCubeMtxLoc, false, modelViewProjectionMatrix);
115363
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 168);
115364
+ gl.bindVertexArray(this.unusedVAO);
115365
+ };
115366
+ Niivue.prototype.draw3D = function(leftTopWidthHeight = [0, 0, 0, 0], mvpMatrix = null, modelMatrix, normalMatrix, azimuth = null, elevation) {
115367
+ let isMosaic = azimuth !== null;
115368
+ this.setPivot3D();
115369
+ if (!isMosaic) {
115370
+ azimuth = this.scene.renderAzimuth;
115371
+ elevation = this.scene.renderElevation;
115372
+ }
115373
+ let gl = this.gl;
115374
+ if (mvpMatrix === null)
115375
+ [mvpMatrix, modelMatrix, normalMatrix] = this.calculateMvpMatrix(null, leftTopWidthHeight, azimuth, elevation);
115376
+ if (leftTopWidthHeight[2] === 0 || leftTopWidthHeight[3] === 0) {
115377
+ leftTopWidthHeight = [0, 0, gl.canvas.width, gl.canvas.height];
115378
+ this.screenSlices.push({
115379
+ leftTopWidthHeight,
115380
+ axCorSag: this.sliceTypeRender,
115381
+ sliceFrac: 0,
115382
+ AxyzMxy: [],
115383
+ leftTopMM: [],
115384
+ fovMM: [isRadiological(modelMatrix), 0]
115385
+ });
115386
+ } else {
115387
+ this.screenSlices.push({
115388
+ leftTopWidthHeight: leftTopWidthHeight.slice(),
115389
+ axCorSag: this.sliceTypeRender,
115390
+ sliceFrac: 0,
115391
+ AxyzMxy: [],
115392
+ leftTopMM: [],
115393
+ fovMM: [isRadiological(modelMatrix), 0]
115394
+ });
115395
+ leftTopWidthHeight[1] = gl.canvas.height - leftTopWidthHeight[3] - leftTopWidthHeight[1];
115396
+ }
115397
+ gl.viewport(leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
115398
+ gl.enable(gl.DEPTH_TEST);
115399
+ gl.depthFunc(gl.ALWAYS);
115400
+ gl.clearDepth(0);
115401
+ if (this.volumes.length > 0)
115402
+ this.drawImage3D(mvpMatrix, azimuth, elevation);
115403
+ if (!isMosaic)
115404
+ this.drawCrosshairs3D(true, 1, mvpMatrix);
115405
+ this.drawMesh3D(true, 1, mvpMatrix, modelMatrix, normalMatrix);
115406
+ if (this.scene.mouseDepthPicker) {
115407
+ this.depthPicker(leftTopWidthHeight, mvpMatrix, true);
115408
+ return;
115409
+ }
115410
+ this.drawMesh3D(false, 0.02, mvpMatrix, modelMatrix, normalMatrix);
115411
+ if (!isMosaic)
115412
+ this.drawCrosshairs3D(false, 0.15, mvpMatrix);
115413
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
115414
+ this.drawOrientationCube(leftTopWidthHeight, azimuth, elevation);
113516
115415
  let posString = "azimuth: " + this.scene.renderAzimuth.toFixed(0) + " elevation: " + this.scene.renderElevation.toFixed(0);
113517
115416
  this.drawGraph();
113518
115417
  this.readyForSync = true;
113519
115418
  this.sync();
113520
115419
  return posString;
113521
115420
  };
113522
- Niivue.prototype.drawMesh3D = function(isDepthTest = true, alpha = 1) {
115421
+ Niivue.prototype.drawMesh3D = function(isDepthTest = true, alpha = 1, m, modelMtx, normMtx) {
113523
115422
  if (this.meshes.length < 1)
113524
115423
  return;
113525
115424
  let gl = this.gl;
113526
- let m, modelMtx, normMtx;
113527
- [m, modelMtx, normMtx] = this.calculateMvpMatrix(this.volumeObject3D);
115425
+ if (!m)
115426
+ [m, modelMtx, normMtx] = this.calculateMvpMatrix(this.volumeObject3D, this.scene.renderAzimuth, this.scene.renderElevation);
113528
115427
  gl.enable(gl.DEPTH_TEST);
113529
115428
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
113530
115429
  gl.disable(gl.BLEND);
@@ -113537,11 +115436,14 @@ Niivue.prototype.drawMesh3D = function(isDepthTest = true, alpha = 1) {
113537
115436
  gl.depthFunc(gl.ALWAYS);
113538
115437
  }
113539
115438
  gl.disable(gl.CULL_FACE);
113540
- this.meshShader.use(this.gl);
113541
- gl.uniformMatrix4fv(this.meshShader.uniforms["mvpMtx"], false, m);
113542
- gl.uniformMatrix4fv(this.meshShader.uniforms["modelMtx"], false, modelMtx);
113543
- gl.uniformMatrix4fv(this.meshShader.uniforms["normMtx"], false, normMtx);
113544
- gl.uniform1f(this.meshShader.uniforms["opacity"], alpha);
115439
+ let shader = this.meshShader;
115440
+ if (this.scene.mouseDepthPicker)
115441
+ shader = this.pickingMeshShader;
115442
+ shader.use(this.gl);
115443
+ gl.uniformMatrix4fv(shader.uniforms["mvpMtx"], false, m);
115444
+ gl.uniformMatrix4fv(shader.uniforms["modelMtx"], false, modelMtx);
115445
+ gl.uniformMatrix4fv(shader.uniforms["normMtx"], false, normMtx);
115446
+ gl.uniform1f(shader.uniforms["opacity"], alpha);
113545
115447
  let hasFibers = false;
113546
115448
  for (let i2 = 0; i2 < this.meshes.length; i2++) {
113547
115449
  if (this.meshes[i2].indexCount < 3)
@@ -113559,7 +115461,7 @@ Niivue.prototype.drawMesh3D = function(isDepthTest = true, alpha = 1) {
113559
115461
  gl.depthFunc(gl.ALWAYS);
113560
115462
  return;
113561
115463
  }
113562
- let shader = this.fiberShader;
115464
+ shader = this.fiberShader;
113563
115465
  shader.use(this.gl);
113564
115466
  gl.uniformMatrix4fv(shader.uniforms["mvpMtx"], false, m);
113565
115467
  gl.uniform1f(shader.uniforms["opacity"], alpha);
@@ -113576,7 +115478,7 @@ Niivue.prototype.drawMesh3D = function(isDepthTest = true, alpha = 1) {
113576
115478
  gl.depthFunc(gl.ALWAYS);
113577
115479
  this.readyForSync = true;
113578
115480
  };
113579
- Niivue.prototype.drawCrosshairs3D = function(isDepthTest = true, alpha = 1) {
115481
+ Niivue.prototype.drawCrosshairs3D = function(isDepthTest = true, alpha = 1, mvpMtx = null) {
113580
115482
  if (!this.opts.show3Dcrosshair)
113581
115483
  return;
113582
115484
  let gl = this.gl;
@@ -113586,15 +115488,21 @@ Niivue.prototype.drawCrosshairs3D = function(isDepthTest = true, alpha = 1) {
113586
115488
  gl.deleteBuffer(this.crosshairs3D.indexBuffer);
113587
115489
  gl.deleteBuffer(this.crosshairs3D.vertexBuffer);
113588
115490
  }
113589
- let radius = Math.min(Math.min(this.back.pixDims[1], this.back.pixDims[2]), this.back.pixDims[3]);
113590
- this.crosshairs3D = NiivueObject3D.generateCrosshairs(this.gl, 1, mm, this.volumeObject3D.extentsMin, this.volumeObject3D.extentsMax, radius);
115491
+ let [mn, mx, range] = this.sceneExtentsMinMax();
115492
+ let radius = 1;
115493
+ if (this.volumes.length > 0)
115494
+ radius = 0.5 * Math.min(Math.min(this.back.pixDims[1], this.back.pixDims[2]), this.back.pixDims[3]);
115495
+ else if (range[0] < 50 || range[0] > 1e3)
115496
+ radius = range[0] * 0.02;
115497
+ this.crosshairs3D = NiivueObject3D.generateCrosshairs(this.gl, 1, mm, mn, mx, radius);
113591
115498
  this.crosshairs3D.mm = mm;
113592
115499
  }
113593
115500
  let crosshairsShader = this.surfaceShader;
113594
115501
  crosshairsShader.use(this.gl);
113595
- let m, modelMtx, normMtx;
113596
- [m, modelMtx, normMtx] = this.calculateMvpMatrix(this.crosshairs3D);
113597
- gl.uniformMatrix4fv(crosshairsShader.uniforms["mvpMtx"], false, m);
115502
+ let modelMtx, normMtx;
115503
+ if (mvpMtx == null)
115504
+ [mvpMtx, modelMtx, normMtx] = this.calculateMvpMatrix(this.crosshairs3D, this.scene.renderAzimuth, this.scene.renderElevation);
115505
+ gl.uniformMatrix4fv(crosshairsShader.uniforms["mvpMtx"], false, mvpMtx);
113598
115506
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.crosshairs3D.indexBuffer);
113599
115507
  gl.enable(gl.DEPTH_TEST);
113600
115508
  let color = [...this.opts.crosshairColor];
@@ -113613,6 +115521,14 @@ Niivue.prototype.drawCrosshairs3D = function(isDepthTest = true, alpha = 1) {
113613
115521
  gl.bindVertexArray(this.unusedVAO);
113614
115522
  };
113615
115523
  Niivue.prototype.mm2frac = function(mm, volIdx = 0) {
115524
+ if (this.volumes.length < 1) {
115525
+ let frac2 = [0.1, 0.5, 0.5];
115526
+ let [mn, mx, range] = this.sceneExtentsMinMax();
115527
+ frac2[0] = (mm[0] - mn[0]) / range[0];
115528
+ frac2[1] = (mm[1] - mn[1]) / range[1];
115529
+ frac2[2] = (mm[2] - mn[2]) / range[2];
115530
+ return frac2;
115531
+ }
113616
115532
  let mm4 = fromValues(mm[0], mm[1], mm[2], 1);
113617
115533
  let d = this.volumes[volIdx].dimsRAS;
113618
115534
  let frac = [0, 0, 0];
@@ -113656,69 +115572,87 @@ Niivue.prototype.moveCrosshairInVox = function(x2, y, z) {
113656
115572
  };
113657
115573
  Niivue.prototype.frac2mm = function(frac, volIdx = 0) {
113658
115574
  let pos = fromValues(frac[0], frac[1], frac[2], 1);
113659
- let dim = fromValues(this.volumes[volIdx].dimsRAS[1], this.volumes[volIdx].dimsRAS[2], this.volumes[volIdx].dimsRAS[3], 1);
113660
- let sform = clone$1(this.volumes[volIdx].matRAS);
113661
- transpose(sform, sform);
113662
- mul(pos, pos, dim);
113663
- let shim = fromValues(-0.5, -0.5, -0.5, 0);
113664
- add(pos, pos, shim);
113665
- transformMat4(pos, pos, sform);
115575
+ if (this.volumes.length > 0)
115576
+ transformMat4(pos, pos, this.volumes[volIdx].frac2mm);
115577
+ else {
115578
+ let [mn, mx] = this.sceneExtentsMinMax();
115579
+ const lerp2 = (x2, y, a) => x2 * (1 - a) + y * a;
115580
+ pos[0] = lerp2(mn[0], mx[0], frac[0]);
115581
+ pos[1] = lerp2(mn[1], mx[1], frac[1]);
115582
+ pos[2] = lerp2(mn[2], mx[2], frac[2]);
115583
+ }
113666
115584
  return pos;
113667
115585
  };
113668
- Niivue.prototype.canvasPos2frac = function(canvasPos) {
113669
- let x2, y, z;
113670
- for (let i2 = 0; i2 < this.numScreenSlices; i2++) {
113671
- var axCorSag = this.screenSlices[i2].axCorSag;
113672
- if (axCorSag > this.sliceTypeSagittal)
113673
- continue;
113674
- var ltwh = this.screenSlices[i2].leftTopWidthHeight;
113675
- let isMirror = false;
113676
- if (ltwh[2] < 0) {
113677
- isMirror = true;
113678
- ltwh[0] += ltwh[2];
113679
- ltwh[2] = -ltwh[2];
113680
- }
113681
- var fracX = (canvasPos[0] - ltwh[0]) / ltwh[2];
113682
- if (isMirror) {
113683
- fracX = 1 - fracX;
113684
- }
113685
- var fracY = 1 - (canvasPos[1] - ltwh[1]) / ltwh[3];
113686
- if (fracX >= 0 && fracX < 1 && fracY >= 0 && fracY < 1) {
113687
- switch (axCorSag) {
113688
- case this.sliceTypeAxial:
113689
- break;
113690
- case this.sliceTypeCoronal:
113691
- break;
113692
- }
113693
- if (axCorSag === this.sliceTypeAxial) {
113694
- x2 = fracX;
113695
- y = fracY;
113696
- z = this.scene.crosshairPos[2];
113697
- }
113698
- if (axCorSag === this.sliceTypeCoronal) {
113699
- x2 = fracX;
113700
- y = this.scene.crosshairPos[1];
113701
- z = fracY;
113702
- }
113703
- if (axCorSag === this.sliceTypeSagittal) {
113704
- x2 = this.scene.crosshairPos[0];
113705
- y = fracX;
113706
- z = fracY;
113707
- }
113708
- break;
115586
+ Niivue.prototype.screenXY2TextureFrac = function(x2, y, i2) {
115587
+ let texFrac = [-1, -1, -1];
115588
+ var axCorSag = this.screenSlices[i2].axCorSag;
115589
+ if (axCorSag > this.sliceTypeSagittal)
115590
+ return texFrac;
115591
+ var ltwh = this.screenSlices[i2].leftTopWidthHeight.slice();
115592
+ let isMirror = false;
115593
+ if (ltwh[2] < 0) {
115594
+ isMirror = true;
115595
+ ltwh[0] += ltwh[2];
115596
+ ltwh[2] = -ltwh[2];
115597
+ }
115598
+ var fracX = (x2 - ltwh[0]) / ltwh[2];
115599
+ if (isMirror)
115600
+ fracX = 1 - fracX;
115601
+ var fracY = 1 - (y - ltwh[1]) / ltwh[3];
115602
+ if (fracX < 0 || fracX > 1 || fracY < 0 || fracY > 1)
115603
+ return texFrac;
115604
+ if (this.screenSlices[i2].AxyzMxy.length > 4) {
115605
+ let xyzMM = [0, 0, 0];
115606
+ xyzMM[0] = this.screenSlices[i2].leftTopMM[0] + fracX * this.screenSlices[i2].fovMM[0];
115607
+ xyzMM[1] = this.screenSlices[i2].leftTopMM[1] + fracY * this.screenSlices[i2].fovMM[1];
115608
+ let v = this.screenSlices[i2].AxyzMxy;
115609
+ xyzMM[2] = v[2] + v[4] * (xyzMM[1] - v[1]) - v[3] * (xyzMM[0] - v[0]);
115610
+ if (axCorSag === this.sliceTypeCoronal)
115611
+ xyzMM = swizzleVec3(xyzMM, [0, 2, 1]);
115612
+ if (axCorSag === this.sliceTypeSagittal)
115613
+ xyzMM = swizzleVec3(xyzMM, [2, 0, 1]);
115614
+ let xyz = this.mm2frac(xyzMM);
115615
+ if (xyz[0] < 0 || xyz[0] > 1 || xyz[1] < 0 || xyz[1] > 1 || xyz[2] < 0 || xyz[2] > 1)
115616
+ return texFrac;
115617
+ texFrac[0] = xyz[0];
115618
+ texFrac[1] = xyz[1];
115619
+ texFrac[2] = xyz[2];
115620
+ } else {
115621
+ texFrac = this.scene.crosshairPos.slice();
115622
+ if (axCorSag === this.sliceTypeAxial) {
115623
+ texFrac[0] = fracX;
115624
+ texFrac[1] = fracY;
115625
+ }
115626
+ if (axCorSag === this.sliceTypeCoronal) {
115627
+ texFrac[0] = fracX;
115628
+ texFrac[2] = fracY;
113709
115629
  }
115630
+ if (axCorSag === this.sliceTypeSagittal) {
115631
+ texFrac[1] = fracX;
115632
+ texFrac[2] = fracY;
115633
+ }
115634
+ }
115635
+ return texFrac;
115636
+ };
115637
+ Niivue.prototype.canvasPos2frac = function(canvasPos) {
115638
+ for (let i2 = 0; i2 < this.screenSlices.length; i2++) {
115639
+ let texFrac = this.screenXY2TextureFrac(canvasPos[0], canvasPos[1], i2);
115640
+ if (texFrac[0] >= 0)
115641
+ return texFrac;
113710
115642
  }
113711
- return [x2, y, z];
115643
+ return [-1, -1, -1];
113712
115644
  };
113713
- Niivue.prototype.scaleSlice = function(w, h) {
113714
- let scalePix = this.gl.canvas.clientWidth / w;
113715
- if (h * scalePix > this.gl.canvas.clientHeight)
113716
- scalePix = this.gl.canvas.clientHeight / h;
115645
+ Niivue.prototype.scaleSlice = function(w, h, widthPadPixels = 0, heightPadPixels = 0) {
115646
+ let canvasW = this.gl.canvas.clientWidth - widthPadPixels;
115647
+ let canvasH = this.effectiveCanvasHeight() - heightPadPixels;
115648
+ let scalePix = canvasW / w;
115649
+ if (h * scalePix > canvasH)
115650
+ scalePix = canvasH / h;
113717
115651
  let wPix = w * scalePix;
113718
115652
  let hPix = h * scalePix;
113719
115653
  let leftTopWidthHeight = [
113720
- (this.gl.canvas.clientWidth - wPix) * 0.5,
113721
- (this.gl.canvas.clientHeight - hPix) * 0.5,
115654
+ (canvasW - wPix) * 0.5,
115655
+ (canvasH - hPix) * 0.5,
113722
115656
  wPix,
113723
115657
  hPix
113724
115658
  ];
@@ -113738,12 +115672,326 @@ Niivue.prototype.drawThumbnail = function() {
113738
115672
  this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
113739
115673
  this.gl.bindVertexArray(this.unusedVAO);
113740
115674
  };
115675
+ Niivue.prototype.drawLine = function(leftTopWidthHeight) {
115676
+ this.gl.enable(this.gl.BLEND);
115677
+ this.rectShader.use(this.gl);
115678
+ this.gl.uniform4fv(this.rectShader.uniforms["lineColor"], this.opts.crosshairColor);
115679
+ this.gl.uniform2fv(this.rectShader.uniforms["canvasWidthHeight"], [
115680
+ this.gl.canvas.width,
115681
+ this.gl.canvas.height
115682
+ ]);
115683
+ this.gl.uniform4f(this.rectShader.uniforms["leftTopWidthHeight"], leftTopWidthHeight[0], leftTopWidthHeight[1], leftTopWidthHeight[2], leftTopWidthHeight[3]);
115684
+ this.gl.bindVertexArray(this.genericVAO);
115685
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
115686
+ this.gl.bindVertexArray(this.unusedVAO);
115687
+ };
115688
+ Niivue.prototype.drawLine2DX = function(startXYendXY, thickness = 1) {
115689
+ this.gl.bindVertexArray(this.genericVAO);
115690
+ this.lineShader.use(this.gl);
115691
+ this.gl.uniform4fv(this.lineShader.uniforms["lineColor"], this.opts.crosshairColor);
115692
+ this.gl.uniform2fv(this.lineShader.uniforms["canvasWidthHeight"], [
115693
+ this.gl.canvas.width,
115694
+ this.gl.canvas.height
115695
+ ]);
115696
+ this.gl.uniform1f(this.lineShader.uniforms["thickness"], thickness);
115697
+ this.gl.uniform4fv(this.lineShader.uniforms["startXYendXY"], startXYendXY);
115698
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
115699
+ this.gl.bindVertexArray(this.unusedVAO);
115700
+ };
115701
+ Niivue.prototype.drawCrossLinesMM = function(sliceIndex, axCorSag, axiMM, corMM, sagMM) {
115702
+ if (sliceIndex < 0 || this.screenSlices.length <= sliceIndex)
115703
+ return;
115704
+ let tile = this.screenSlices[sliceIndex];
115705
+ let linesH = corMM.slice();
115706
+ let linesV = sagMM.slice();
115707
+ let thick = Math.max(1, this.opts.crosshairWidth);
115708
+ if (axCorSag === this.sliceTypeCoronal)
115709
+ linesH = axiMM.slice();
115710
+ if (axCorSag === this.sliceTypeSagittal) {
115711
+ linesH = axiMM.slice();
115712
+ linesV = corMM.slice();
115713
+ }
115714
+ function mm2screen(mm) {
115715
+ let screenXY = [0, 0];
115716
+ screenXY[0] = tile.leftTopWidthHeight[0] + (mm[0] - tile.leftTopMM[0]) / tile.fovMM[0] * tile.leftTopWidthHeight[2];
115717
+ screenXY[1] = tile.leftTopWidthHeight[1] + tile.leftTopWidthHeight[3] - (mm[1] - tile.leftTopMM[1]) / tile.fovMM[1] * tile.leftTopWidthHeight[3];
115718
+ return screenXY;
115719
+ }
115720
+ if (linesH.length > 0 && axCorSag === 0) {
115721
+ let fracZ = tile.sliceFrac;
115722
+ let dimV = 1;
115723
+ for (let i2 = 0; i2 < linesH.length; i2++) {
115724
+ let mmV = this.frac2mm([0.5, 0.5, 0.5]);
115725
+ mmV[dimV] = linesH[i2];
115726
+ let fracY = this.mm2frac(mmV);
115727
+ fracY = fracY[dimV];
115728
+ let left = this.frac2mm([0, fracY, fracZ]);
115729
+ left = swizzleVec3(left, [0, 1, 2]);
115730
+ let right = this.frac2mm([1, fracY, fracZ]);
115731
+ right = swizzleVec3(right, [0, 1, 2]);
115732
+ left = mm2screen(left);
115733
+ right = mm2screen(right);
115734
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
115735
+ }
115736
+ }
115737
+ if (linesH.length > 0 && axCorSag === 1) {
115738
+ let fracH = tile.sliceFrac;
115739
+ this.frac2mm([0.5, fracH, 0.5]);
115740
+ let dimV = 2;
115741
+ for (let i2 = 0; i2 < linesH.length; i2++) {
115742
+ let mmV = this.frac2mm([0.5, 0.5, 0.5]);
115743
+ mmV[dimV] = linesH[i2];
115744
+ let fracV = this.mm2frac(mmV);
115745
+ fracV = fracV[dimV];
115746
+ let left = this.frac2mm([0, fracH, fracV]);
115747
+ left = swizzleVec3(left, [0, 2, 1]);
115748
+ let right = this.frac2mm([1, fracH, fracV]);
115749
+ right = swizzleVec3(right, [0, 2, 1]);
115750
+ left = mm2screen(left);
115751
+ right = mm2screen(right);
115752
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
115753
+ }
115754
+ }
115755
+ if (linesH.length > 0 && axCorSag === 2) {
115756
+ let fracX = tile.sliceFrac;
115757
+ let dimV = 2;
115758
+ for (let i2 = 0; i2 < linesH.length; i2++) {
115759
+ let mmV = this.frac2mm([0.5, 0.5, 0.5]);
115760
+ mmV[dimV] = linesH[i2];
115761
+ let fracZ = this.mm2frac(mmV);
115762
+ fracZ = fracZ[dimV];
115763
+ let left = this.frac2mm([fracX, 0, fracZ]);
115764
+ left = swizzleVec3(left, [1, 2, 0]);
115765
+ let right = this.frac2mm([fracX, 1, fracZ]);
115766
+ right = swizzleVec3(right, [1, 2, 0]);
115767
+ left = mm2screen(left);
115768
+ right = mm2screen(right);
115769
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
115770
+ }
115771
+ }
115772
+ if (linesV.length > 0 && axCorSag === 0) {
115773
+ let fracZ = tile.sliceFrac;
115774
+ let dimH = 0;
115775
+ for (let i2 = 0; i2 < linesV.length; i2++) {
115776
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
115777
+ mm[dimH] = linesV[i2];
115778
+ let frac = this.mm2frac(mm);
115779
+ frac = frac[dimH];
115780
+ let left = this.frac2mm([frac, 0, fracZ]);
115781
+ left = swizzleVec3(left, [0, 1, 2]);
115782
+ let right = this.frac2mm([frac, 1, fracZ]);
115783
+ right = swizzleVec3(right, [0, 1, 2]);
115784
+ left = mm2screen(left);
115785
+ right = mm2screen(right);
115786
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
115787
+ }
115788
+ }
115789
+ if (linesV.length > 0 && axCorSag === 1) {
115790
+ let fracY = tile.sliceFrac;
115791
+ let dimH = 0;
115792
+ for (let i2 = 0; i2 < linesV.length; i2++) {
115793
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
115794
+ mm[dimH] = linesV[i2];
115795
+ let frac = this.mm2frac(mm);
115796
+ frac = frac[dimH];
115797
+ let left = this.frac2mm([frac, fracY, 0]);
115798
+ left = swizzleVec3(left, [0, 2, 1]);
115799
+ let right = this.frac2mm([frac, fracY, 1]);
115800
+ right = swizzleVec3(right, [0, 2, 1]);
115801
+ left = mm2screen(left);
115802
+ right = mm2screen(right);
115803
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
115804
+ }
115805
+ }
115806
+ if (linesV.length > 0 && axCorSag === 2) {
115807
+ let fracX = tile.sliceFrac;
115808
+ let dimH = 1;
115809
+ for (let i2 = 0; i2 < linesV.length; i2++) {
115810
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
115811
+ mm[dimH] = linesV[i2];
115812
+ let frac = this.mm2frac(mm);
115813
+ frac = frac[dimH];
115814
+ let left = this.frac2mm([fracX, frac, 0]);
115815
+ left = swizzleVec3(left, [1, 2, 0]);
115816
+ let right = this.frac2mm([fracX, frac, 1]);
115817
+ right = swizzleVec3(right, [1, 2, 0]);
115818
+ left = mm2screen(left);
115819
+ right = mm2screen(right);
115820
+ this.drawLine2DX([left[0], left[1], right[0], right[1]], thick);
115821
+ }
115822
+ }
115823
+ };
115824
+ Niivue.prototype.drawCrossLines = function(sliceIndex, axCorSag, axiMM, corMM, sagMM) {
115825
+ if (sliceIndex < 0 || this.screenSlices.length <= sliceIndex)
115826
+ return;
115827
+ if (this.opts.isSliceMM)
115828
+ return this.drawCrossLinesMM(sliceIndex, axCorSag, axiMM, corMM, sagMM);
115829
+ let tile = this.screenSlices[sliceIndex];
115830
+ let linesH = corMM.slice();
115831
+ let linesV = sagMM.slice();
115832
+ Math.max(1, this.opts.crosshairWidth);
115833
+ if (axCorSag === this.sliceTypeCoronal)
115834
+ linesH = axiMM.slice();
115835
+ if (axCorSag === this.sliceTypeSagittal) {
115836
+ linesH = axiMM.slice();
115837
+ linesV = corMM.slice();
115838
+ }
115839
+ if (linesH.length > 0) {
115840
+ let LTWH = tile.leftTopWidthHeight.slice();
115841
+ let sliceDim = 2;
115842
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
115843
+ for (let i2 = 0; i2 < linesH.length; i2++) {
115844
+ mm[sliceDim] = linesH[i2];
115845
+ let frac = this.mm2frac(mm);
115846
+ this.drawLine([
115847
+ LTWH[0],
115848
+ LTWH[1] + LTWH[3] - frac[sliceDim] * LTWH[3],
115849
+ LTWH[2],
115850
+ 1
115851
+ ]);
115852
+ }
115853
+ }
115854
+ if (linesV.length > 0) {
115855
+ let LTWH = tile.leftTopWidthHeight.slice();
115856
+ let isRadiolgical = tile.fovMM[0] < 0;
115857
+ let sliceDim = 0;
115858
+ if (axCorSag === this.sliceTypeSagittal)
115859
+ sliceDim = 1;
115860
+ let mm = this.frac2mm([0.5, 0.5, 0.5]);
115861
+ for (let i2 = 0; i2 < linesV.length; i2++) {
115862
+ mm[sliceDim] = linesV[i2];
115863
+ let frac = this.mm2frac(mm);
115864
+ if (isRadiolgical)
115865
+ this.drawLine([
115866
+ LTWH[0] + (LTWH[2] - frac[sliceDim] * LTWH[2]),
115867
+ LTWH[1],
115868
+ 1,
115869
+ LTWH[3]
115870
+ ]);
115871
+ else
115872
+ this.drawLine([
115873
+ LTWH[0] + frac[sliceDim] * LTWH[2],
115874
+ LTWH[1],
115875
+ 1,
115876
+ LTWH[3]
115877
+ ]);
115878
+ }
115879
+ }
115880
+ };
115881
+ Niivue.prototype.drawMosaic = function(mosaicStr) {
115882
+ if (this.volumes.length === 0) {
115883
+ console.log("Unable to draw mosaic until voxel-based image is loaded");
115884
+ return;
115885
+ }
115886
+ this.screenSlices = [];
115887
+ let fovRenderMM = this.screenFieldOfViewMM(this.sliceTypeAxial, true);
115888
+ let fovSliceMM = this.screenFieldOfViewMM(this.sliceTypeAxial);
115889
+ mosaicStr = mosaicStr.replaceAll(";", " ;").trim();
115890
+ let axiMM = [];
115891
+ let corMM = [];
115892
+ let sagMM = [];
115893
+ let items = mosaicStr.split(/\s+/);
115894
+ let scale2 = 1;
115895
+ let labelSize = this.opts.textHeight;
115896
+ for (let pass = 0; pass < 2; pass++) {
115897
+ let isRender = false;
115898
+ let isCrossLines = false;
115899
+ isRender = false;
115900
+ let rowHt = 0;
115901
+ let left = 0;
115902
+ let top = 0;
115903
+ let mxRowWid = 0;
115904
+ let isLabel = false;
115905
+ let axCorSag = this.sliceTypeAxial;
115906
+ for (let i2 = 0; i2 < items.length; i2++) {
115907
+ let item = items[i2];
115908
+ if (item.includes("X")) {
115909
+ isCrossLines = true;
115910
+ continue;
115911
+ }
115912
+ if (item.includes("L")) {
115913
+ isLabel = !item.includes("-");
115914
+ continue;
115915
+ }
115916
+ if (item.includes("V") || item.includes("H")) {
115917
+ i2++;
115918
+ continue;
115919
+ }
115920
+ if (item.includes("A"))
115921
+ axCorSag = this.sliceTypeAxial;
115922
+ if (item.includes("C"))
115923
+ axCorSag = this.sliceTypeCoronal;
115924
+ if (item.includes("S"))
115925
+ axCorSag = this.sliceTypeSagittal;
115926
+ if (item.includes("R"))
115927
+ isRender = true;
115928
+ if (item.includes(";")) {
115929
+ top += rowHt;
115930
+ mxRowWid = Math.max(mxRowWid, left);
115931
+ rowHt = 0;
115932
+ left = 0;
115933
+ }
115934
+ let sliceMM = parseFloat(item, NaN);
115935
+ if (isNaN(sliceMM))
115936
+ continue;
115937
+ let w = 0;
115938
+ let h = 0;
115939
+ let fov = fovSliceMM;
115940
+ if (isRender)
115941
+ fov = fovRenderMM;
115942
+ if (axCorSag === this.sliceTypeSagittal)
115943
+ w = fov[1];
115944
+ else
115945
+ w = fov[0];
115946
+ if (axCorSag === this.sliceTypeAxial)
115947
+ h = fov[1];
115948
+ else
115949
+ h = fov[2];
115950
+ if (pass === 0) {
115951
+ if (!isRender) {
115952
+ if (axCorSag === this.sliceTypeAxial)
115953
+ axiMM.push(sliceMM);
115954
+ if (axCorSag === this.sliceTypeCoronal)
115955
+ corMM.push(sliceMM);
115956
+ if (axCorSag === this.sliceTypeSagittal)
115957
+ sagMM.push(sliceMM);
115958
+ }
115959
+ } else {
115960
+ let ltwh = [scale2 * left, scale2 * top, scale2 * w, scale2 * h];
115961
+ this.opts.textHeight = isLabel ? labelSize : 0;
115962
+ if (isRender) {
115963
+ let inf = sliceMM < 0 ? -Infinity : Infinity;
115964
+ if (Object.is(sliceMM, -0))
115965
+ inf = -Infinity;
115966
+ this.draw2DMM(ltwh, axCorSag, inf, isLabel);
115967
+ } else
115968
+ this.draw2D(ltwh, axCorSag, sliceMM, isLabel);
115969
+ if (isCrossLines) {
115970
+ this.drawCrossLines(this.screenSlices.length - 1, axCorSag, axiMM, corMM, sagMM);
115971
+ }
115972
+ isRender = false;
115973
+ isCrossLines = false;
115974
+ }
115975
+ left += w;
115976
+ rowHt = Math.max(rowHt, h);
115977
+ }
115978
+ top += rowHt;
115979
+ mxRowWid = Math.max(mxRowWid, left);
115980
+ if (mxRowWid <= 0 || top <= 0)
115981
+ break;
115982
+ let scaleW = this.gl.canvas.width / mxRowWid;
115983
+ let scaleH = this.effectiveCanvasHeight() / top;
115984
+ scale2 = Math.min(scaleW, scaleH);
115985
+ }
115986
+ this.opts.textHeight = labelSize;
115987
+ };
113741
115988
  Niivue.prototype.drawScene = function() {
113742
115989
  if (!this.initialized) {
113743
115990
  return;
113744
115991
  }
115992
+ this.colorbarHeight = 0;
113745
115993
  this.gl.clearColor(this.opts.backColor[0], this.opts.backColor[1], this.opts.backColor[2], this.opts.backColor[3]);
113746
- this.gl.clear(this.gl.COLOR_BUFFER_BIT);
115994
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
113747
115995
  if (this.bmpTexture) {
113748
115996
  this.drawThumbnail();
113749
115997
  return;
@@ -113751,6 +115999,7 @@ Niivue.prototype.drawScene = function() {
113751
115999
  let posString = "";
113752
116000
  if (this.volumes.length === 0 || typeof this.volumes[0].dims === "undefined") {
113753
116001
  if (this.meshes.length > 0) {
116002
+ this.screenSlices = [];
113754
116003
  this.sliceType = this.sliceTypeRender;
113755
116004
  return this.draw3D();
113756
116005
  }
@@ -113759,64 +116008,91 @@ Niivue.prototype.drawScene = function() {
113759
116008
  }
113760
116009
  if (!this.back.hasOwnProperty("dims"))
113761
116010
  return;
113762
- if (this.sliceType === this.sliceTypeRender) {
113763
- if (this.isDragging && this.scene.clipPlaneDepthAziElev[0] < 1.8) {
113764
- let x2 = this.dragStart[0] - this.dragEnd[0];
113765
- let y = this.dragStart[1] - this.dragEnd[1];
113766
- let depthAziElev = this.dragClipPlaneStartDepthAziElev.slice();
113767
- depthAziElev[1] -= x2;
113768
- depthAziElev[1] = depthAziElev[1] % 360;
113769
- depthAziElev[2] += y;
113770
- if (depthAziElev[1] !== this.scene.clipPlaneDepthAziElev[1] || depthAziElev[2] !== this.scene.clipPlaneDepthAziElev[2]) {
113771
- this.scene.clipPlaneDepthAziElev = depthAziElev;
113772
- return this.setClipPlane(this.scene.clipPlaneDepthAziElev);
113773
- }
113774
- }
113775
- return this.draw3D();
113776
- }
113777
- let { volScale } = this.sliceScale();
113778
- this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
113779
- this.numScreenSlices = 0;
113780
- if (this.sliceType === this.sliceTypeAxial) {
113781
- let leftTopWidthHeight = this.scaleSlice(volScale[0], volScale[1]);
113782
- this.draw2D(leftTopWidthHeight, 0);
113783
- } else if (this.sliceType === this.sliceTypeCoronal) {
113784
- let leftTopWidthHeight = this.scaleSlice(volScale[0], volScale[2]);
113785
- this.draw2D(leftTopWidthHeight, 1);
113786
- } else if (this.sliceType === this.sliceTypeSagittal) {
113787
- let leftTopWidthHeight = this.scaleSlice(volScale[1], volScale[2]);
113788
- this.draw2D(leftTopWidthHeight, 2);
116011
+ if (this.isDragging && this.scene.clipPlaneDepthAziElev[0] < 1.8 && this.inRenderTile(this.dragStart[0], this.dragStart[1]) >= 0) {
116012
+ let x2 = this.dragStart[0] - this.dragEnd[0];
116013
+ let y = this.dragStart[1] - this.dragEnd[1];
116014
+ let depthAziElev = this.dragClipPlaneStartDepthAziElev.slice();
116015
+ depthAziElev[1] -= x2;
116016
+ depthAziElev[1] = depthAziElev[1] % 360;
116017
+ depthAziElev[2] += y;
116018
+ if (depthAziElev[1] !== this.scene.clipPlaneDepthAziElev[1] || depthAziElev[2] !== this.scene.clipPlaneDepthAziElev[2]) {
116019
+ this.scene.clipPlaneDepthAziElev = depthAziElev;
116020
+ return this.setClipPlane(this.scene.clipPlaneDepthAziElev);
116021
+ }
116022
+ }
116023
+ if (this.sliceMosaicString.length < 1 && this.sliceType === this.sliceTypeRender) {
116024
+ this.screenSlices = [];
116025
+ this.draw3D();
116026
+ if (this.opts.isColorbar)
116027
+ this.drawColorbar();
116028
+ return;
116029
+ }
116030
+ if (this.opts.isColorbar)
116031
+ this.reserveColorbarPanel();
116032
+ if (this.sliceMosaicString.length > 0) {
116033
+ this.drawMosaic(this.sliceMosaicString);
113789
116034
  } else {
113790
- let ltwh = this.scaleSlice(volScale[0] + volScale[1], volScale[1] + volScale[2]);
113791
- let wX = ltwh[2] * volScale[0] / (volScale[0] + volScale[1]);
113792
- let ltwh3x1 = this.scaleSlice(volScale[0] + volScale[0] + volScale[1], Math.max(volScale[1], volScale[2]));
113793
- let wX1 = ltwh3x1[2] * volScale[0] / (volScale[0] + volScale[0] + volScale[1]);
113794
- if (wX1 > wX && !this.graph.autoSizeMultiplanar) {
113795
- let pixScale = wX1 / volScale[0];
113796
- let hY1 = volScale[1] * pixScale;
113797
- let hZ1 = volScale[2] * pixScale;
113798
- this.draw2D([ltwh3x1[0], ltwh3x1[1], wX1, hY1], 0);
113799
- this.draw2D([ltwh3x1[0] + wX1, ltwh3x1[1], wX1, hZ1], 1);
113800
- this.draw2D([ltwh3x1[0] + wX1 + wX1, ltwh3x1[1], hY1, hZ1], 2);
116035
+ this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
116036
+ this.screenSlices = [];
116037
+ if (this.sliceType === this.sliceTypeAxial) {
116038
+ this.draw2D([0, 0, 0, 0], 0);
116039
+ } else if (this.sliceType === this.sliceTypeCoronal) {
116040
+ this.draw2D([0, 0, 0, 0], 1);
116041
+ } else if (this.sliceType === this.sliceTypeSagittal) {
116042
+ this.draw2D([0, 0, 0, 0], 2);
113801
116043
  } else {
113802
- let wY = ltwh[2] - wX;
113803
- let hY = ltwh[3] * volScale[1] / (volScale[1] + volScale[2]);
113804
- let hZ = ltwh[3] - hY;
113805
- this.draw2D([ltwh[0], ltwh[1] + hZ, wX, hY], 0);
113806
- this.draw2D([ltwh[0], ltwh[1], wX, hZ], 1);
113807
- this.draw2D([ltwh[0] + wX, ltwh[1], wY, hZ], 2);
113808
- var margin = this.opts.colorBarMargin * hY;
113809
- if (!this.graph.autoSizeMultiplanar) {
113810
- this.drawColorbar([
113811
- ltwh[0] + wX + margin,
113812
- ltwh[1] + hZ + margin,
113813
- wY - margin - margin,
113814
- hY * this.opts.colorbarHeight
113815
- ]);
116044
+ let { volScale, vox, longestAxis } = this.sliceScale();
116045
+ let pad = this.opts.multiplanarPadPixels;
116046
+ let ltwh = this.scaleSlice(volScale[0] + volScale[1], volScale[1] + volScale[2], pad * 1, pad * 1);
116047
+ let wX = ltwh[2] * volScale[0] / (volScale[0] + volScale[1]);
116048
+ let ltwh3x1 = this.scaleSlice(volScale[0] + volScale[0] + volScale[1], Math.max(volScale[1], volScale[2]), pad * 2);
116049
+ let mx = Math.max(Math.max(volScale[1], volScale[2]), volScale[0]);
116050
+ let ltwh4x1 = this.scaleSlice(volScale[0] + volScale[0] + volScale[1] + mx, mx, pad * 3);
116051
+ let wX1 = ltwh3x1[2] * volScale[0] / (volScale[0] + volScale[0] + volScale[1]);
116052
+ if (wX1 > wX && !this.graph.autoSizeMultiplanar) {
116053
+ let pixScale = wX1 / volScale[0];
116054
+ let hY1 = volScale[1] * pixScale;
116055
+ let hZ1 = volScale[2] * pixScale;
116056
+ if (ltwh3x1[3] === ltwh4x1[3]) {
116057
+ ltwh3x1 = ltwh4x1;
116058
+ this.draw3D([
116059
+ ltwh3x1[0] + wX1 + wX1 + hY1 + pad * 3,
116060
+ ltwh3x1[1],
116061
+ ltwh4x1[3],
116062
+ ltwh4x1[3]
116063
+ ]);
116064
+ }
116065
+ this.draw2D([ltwh3x1[0], ltwh3x1[1], wX1, hY1], 0);
116066
+ this.draw2D([ltwh3x1[0] + wX1 + pad, ltwh3x1[1], wX1, hZ1], 1);
116067
+ this.draw2D([ltwh3x1[0] + wX1 + wX1 + pad * 2, ltwh3x1[1], hY1, hZ1], 2);
116068
+ } else {
116069
+ let wY = ltwh[2] - wX;
116070
+ let hY = ltwh[3] * volScale[1] / (volScale[1] + volScale[2]);
116071
+ let hZ = ltwh[3] - hY;
116072
+ this.draw2D([ltwh[0], ltwh[1] + hZ + pad, wX, hY], 0);
116073
+ this.draw2D([ltwh[0], ltwh[1], wX, hZ], 1);
116074
+ this.draw2D([ltwh[0] + wX + pad, ltwh[1], wY, hZ], 2);
116075
+ if (!this.graph.autoSizeMultiplanar)
116076
+ this.draw3D([ltwh[0] + wX + pad, ltwh[1] + hZ + pad, wY, hY]);
113816
116077
  }
113817
116078
  }
113818
116079
  }
113819
- if (this.isDragging && this.sliceType !== this.sliceTypeRender) {
116080
+ if (this.opts.isRuler)
116081
+ this.drawRuler();
116082
+ if (this.opts.isColorbar)
116083
+ this.drawColorbar();
116084
+ if (this.isDragging) {
116085
+ if (this.opts.isDragShowsMeasurementTool) {
116086
+ this.drawMeasurementTool([
116087
+ this.dragStart[0],
116088
+ this.dragStart[1],
116089
+ this.dragEnd[0],
116090
+ this.dragEnd[1]
116091
+ ]);
116092
+ return;
116093
+ }
116094
+ if (this.inRenderTile(this.dragStart[0], this.dragStart[1]) >= 0)
116095
+ return;
113820
116096
  let width = Math.abs(this.dragStart[0] - this.dragEnd[0]);
113821
116097
  let height = Math.abs(this.dragStart[1] - this.dragEnd[1]);
113822
116098
  this.drawSelectionBox([