lavavu-osmesa 1.9.9__cp313-cp313-manylinux_2_28_x86_64.whl

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.
Files changed (65) hide show
  1. lavavu/LavaVuPython.py +561 -0
  2. lavavu/_LavaVuPython.cpython-313-x86_64-linux-gnu.so +0 -0
  3. lavavu/__init__.py +15 -0
  4. lavavu/__main__.py +12 -0
  5. lavavu/amalgamate.py +15 -0
  6. lavavu/aserver.py +359 -0
  7. lavavu/control.py +1731 -0
  8. lavavu/convert.py +888 -0
  9. lavavu/dict.json +2528 -0
  10. lavavu/font.bin +0 -0
  11. lavavu/html/LavaVu-amalgamated.css +282 -0
  12. lavavu/html/OK-min.js +99 -0
  13. lavavu/html/baseviewer.js +307 -0
  14. lavavu/html/control.css +104 -0
  15. lavavu/html/control.js +340 -0
  16. lavavu/html/dat-gui-light-theme.css +68 -0
  17. lavavu/html/dat.gui.min.js +2 -0
  18. lavavu/html/draw.js +2259 -0
  19. lavavu/html/drawbox.js +1039 -0
  20. lavavu/html/emscripten-template.js +184 -0
  21. lavavu/html/emscripten.css +92 -0
  22. lavavu/html/favicon.ico +0 -0
  23. lavavu/html/gl-matrix-min.js +47 -0
  24. lavavu/html/gui.css +25 -0
  25. lavavu/html/menu.js +615 -0
  26. lavavu/html/server.js +226 -0
  27. lavavu/html/stats.min.js +5 -0
  28. lavavu/html/styles.css +58 -0
  29. lavavu/html/webview-template.html +43 -0
  30. lavavu/html/webview.html +43 -0
  31. lavavu/lavavu.py +6200 -0
  32. lavavu/osmesa/LavaVuPython.py +561 -0
  33. lavavu/osmesa/_LavaVuPython.cpython-313-x86_64-linux-gnu.so +0 -0
  34. lavavu/osmesa/__init__.py +0 -0
  35. lavavu/points.py +191 -0
  36. lavavu/server.py +343 -0
  37. lavavu/shaders/default.frag +14 -0
  38. lavavu/shaders/default.vert +17 -0
  39. lavavu/shaders/fontShader.frag +20 -0
  40. lavavu/shaders/fontShader.vert +18 -0
  41. lavavu/shaders/lineShader.frag +39 -0
  42. lavavu/shaders/lineShader.vert +26 -0
  43. lavavu/shaders/pointShader.frag +127 -0
  44. lavavu/shaders/pointShader.vert +53 -0
  45. lavavu/shaders/triShader.frag +153 -0
  46. lavavu/shaders/triShader.vert +49 -0
  47. lavavu/shaders/volumeShader.frag +400 -0
  48. lavavu/shaders/volumeShader.vert +5 -0
  49. lavavu/tracers.py +207 -0
  50. lavavu/vutils.py +211 -0
  51. lavavu_osmesa-1.9.9.dist-info/METADATA +323 -0
  52. lavavu_osmesa-1.9.9.dist-info/RECORD +65 -0
  53. lavavu_osmesa-1.9.9.dist-info/WHEEL +5 -0
  54. lavavu_osmesa-1.9.9.dist-info/entry_points.txt +2 -0
  55. lavavu_osmesa-1.9.9.dist-info/licenses/LICENSE.md +179 -0
  56. lavavu_osmesa-1.9.9.dist-info/top_level.txt +1 -0
  57. lavavu_osmesa.libs/libLLVM-17-51492e70.so +0 -0
  58. lavavu_osmesa.libs/libOSMesa-f6a8f160.so.8.0.0 +0 -0
  59. lavavu_osmesa.libs/libdrm-b0291a67.so.2.4.0 +0 -0
  60. lavavu_osmesa.libs/libffi-3a37023a.so.6.0.2 +0 -0
  61. lavavu_osmesa.libs/libglapi-520b284c.so.0.0.0 +0 -0
  62. lavavu_osmesa.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  63. lavavu_osmesa.libs/libselinux-d0805dcb.so.1 +0 -0
  64. lavavu_osmesa.libs/libtinfo-3a2cb85b.so.6.1 +0 -0
  65. lavavu_osmesa.libs/libzstd-76b78bac.so.1.4.4 +0 -0
lavavu/html/draw.js ADDED
@@ -0,0 +1,2259 @@
1
+ //WebGL viewer, Owen Kaluza (c) Monash University 2012-18
2
+ // License: LGPLv3 for now
3
+ var server = false;
4
+ var types = ["triangles", "points", "lines", "volume"]
5
+ var MAXIDX = 2047;
6
+ /** @const */
7
+ var DEBUG = false;
8
+
9
+ function initPage(elid, menu, src) {
10
+ //Load from the data tag if available, otherwise URL
11
+ var dtag = document.getElementById('data');
12
+ if (!src && dtag && dtag.innerHTML.length > 100)
13
+ src = dtag.innerHTML;
14
+
15
+ var urlq = decodeURI(window.location.href);
16
+ if (!src && urlq.indexOf("?") > 0) {
17
+ var parts = urlq.split("?"); //whole querystring before and after ?
18
+ var query = parts[1];
19
+
20
+ if (query.indexOf(".json") > 0) {
21
+ //Passed a json(p) file on URL
22
+ if (query.indexOf(".jsonp") > 0) {
23
+ //Load jsonp file as a script, useful when opening page as file://
24
+ //only way to get around security issues in chrome,
25
+ //script calls the loadData function providing the content
26
+ var script = document.createElement('script');
27
+ script.id = 'script';
28
+ script.style.display = 'none';
29
+ script.src = query;
30
+ document.body.appendChild(script);
31
+ } else {
32
+ var el = document.getElementById('fileupload');
33
+ if (el) el.style.display = "none";
34
+ progress("Downloading model data from server...");
35
+ ajaxReadFile(query, initPage, false, updateProgress);
36
+ }
37
+ return;
38
+ }
39
+ } else if (!src && urlq.indexOf("#") > 0) {
40
+ //IPython strips out ? args so have to check for this instead
41
+ var parts = urlq.split("#"); //whole querystring before and after #
42
+ if (parts[1].indexOf(".json") > 0) {
43
+ //Load filename from url
44
+ ajaxReadFile(parts[1], initPage, false);
45
+ return;
46
+ }
47
+
48
+ //Load base64 encoded data from url
49
+ window.location.hash = ""
50
+ src = window.atob(parts[1]);
51
+ }
52
+
53
+ progress();
54
+
55
+ var container;
56
+ if (elid)
57
+ container = document.getElementById(elid);
58
+ else
59
+ container = document.body;
60
+ var canvas = document.createElement("canvas");
61
+ canvas.id = "canvas_" + container.id;
62
+ container.appendChild(canvas);
63
+ var viewer = new Viewer(canvas);
64
+ viewer.menu = menu; //GUI menu flag
65
+ if (canvas) {
66
+ //this.canvas = document.createElement("canvas");
67
+ //this.canvas.style.cssText = "width: 100%; height: 100%; z-index: 0; margin: 0px; padding: 0px; background: black; border: none; display:block;";
68
+ //if (!parentEl) parentEl = document.body;
69
+ //parentEl.appendChild(this.canvas);
70
+
71
+ //Canvas event handling
72
+ var handler = new MouseEventHandler(canvasMouseClick, canvasMouseWheel, canvasMouseMove, canvasMouseDown, null, null, canvasMousePinch);
73
+ canvas.mouse = new Mouse(canvas, handler);
74
+ //Following two settings should probably be defaults?
75
+ canvas.mouse.moveUpdate = true; //Continual update of deltaX/Y
76
+ //canvas.mouse.setDefault();
77
+
78
+ canvas.mouse.wheelTimer = true; //Accumulate wheel scroll (prevents too many events backing up)
79
+ defaultMouse = document.mouse = canvas.mouse;
80
+
81
+ //Reference to viewer object on canvas for event handling
82
+ canvas.viewer = viewer;
83
+ window.viewer = viewer; //Only most recent stored on window
84
+
85
+ //Enable auto resize for full screen view
86
+ if (canvas.parentElement == document.body) {
87
+ window.onresize = function() {viewer.drawTimed();};
88
+ //No border
89
+ canvas.style.cssText = "width: 100%; height: 100%; z-index: 0; margin: 0px; padding: 0px; background: black; border: none; display:block;";
90
+ } else {
91
+ //Light border
92
+ canvas.style.cssText = "width: 100%; height: 100%; z-index: 0; margin: 0px; padding: 0px; background: black; border: 1px solid #aaa; display:block;";
93
+ }
94
+ }
95
+
96
+ //Data dict and colourmap names stored in globals
97
+ viewer.dict = window.dictionary;
98
+ viewer.defaultcolourmaps = window.defaultcolourmaps;
99
+
100
+ if (query && query.indexOf("server") >= 0) {
101
+ //Switch to image frame
102
+ setAll('', 'server');
103
+ setAll('none', 'client');
104
+
105
+ server = true;
106
+
107
+ //if (!viewer.gl) {
108
+ //img = document.getElementById('frame');
109
+ //Image canvas event handling
110
+ //img.mouse = new Mouse(img, new MouseEventHandler(serverMouseClick, serverMouseWheel, serverMouseMove, serverMouseDown));
111
+ //}
112
+
113
+ //Initiate the server update
114
+ requestData('/connect', parseRequest);
115
+ //requestData('/objects', parseObjects);
116
+
117
+ //Enable to forward key presses to server directly
118
+ //document.onkeypress = keyPress;
119
+ window.onbeforeunload = function() {if (client_id >= 0) requestData("/disconnect=" + client_id, null, true); client_id = -1;};
120
+
121
+ //Set viewer window to match ours?
122
+ //resizeToWindow();
123
+
124
+ } else {
125
+ setAll('none', 'server');
126
+ setAll('', 'client');
127
+ var frame = document.getElementById('frame');
128
+ if (frame)
129
+ frame.style.display = 'none';
130
+ }
131
+
132
+ if (src) {
133
+ viewer.loadFile(src);
134
+ } else {
135
+ var source = getSourceFromElement('source');
136
+ if (source) {
137
+ //Preloaded data
138
+ var el = document.getElementById('fileupload');
139
+ if (el) el.style.display = "none";
140
+ viewer.loadFile(source);
141
+ } else {
142
+ //Demo objects
143
+ //demoData();
144
+ viewer.draw();
145
+ }
146
+ }
147
+
148
+ //VR setup
149
+ if (navigator.getVRDisplays)
150
+ setup_VR(viewer);
151
+ }
152
+
153
+ function loadData(data) {
154
+ initPage(data);
155
+ }
156
+
157
+ function progress(text) {
158
+ var el = document.getElementById('progress');
159
+ if (!el) return;
160
+ if (el.style.display == 'block' || text == undefined)
161
+ //el.style.display = 'none';
162
+ setTimeout("document.getElementById('progress').style.display = 'none';", 150);
163
+ else {
164
+ document.getElementById('progressmessage').innerHTML = text;
165
+ document.getElementById('progressstatus').innerHTML = "";
166
+ document.getElementById('progressbar').style.width = 0;
167
+ el.style.display = 'block';
168
+ }
169
+ }
170
+
171
+ function canvasMouseClick(event, mouse) {
172
+ if (server) {
173
+ if (mouse.element.viewer.rotating)
174
+ sendCommand('' + mouse.element.viewer.getRotationString());
175
+ else
176
+ sendCommand('' + mouse.element.viewer.getTranslationString());
177
+ }
178
+
179
+ //if (server) serverMouseClick(event, mouse); //Pass to server handler
180
+ if (mouse.element.viewer.rotating) {
181
+ mouse.element.viewer.rotating = false;
182
+ //mouse.element.viewer.reload = true;
183
+ sortTimer(false, mouse.element.viewer);
184
+ return false;
185
+ }
186
+
187
+ mouse.element.viewer.draw();
188
+ return false;
189
+ }
190
+
191
+ function sortTimer(ifexists, viewer) {
192
+ if (viewer.vis.sortenabled == false) {
193
+ //Sorting disabled
194
+ viewer.rotated = false;
195
+ viewer.draw();
196
+ return;
197
+ }
198
+ if (viewer.vis.immediatesort == true) {
199
+ //No timers
200
+ viewer.rotated = true;
201
+ viewer.draw();
202
+ return;
203
+ }
204
+ //Set a timer to apply the sort function in 2 seconds
205
+ if (viewer.timer) {
206
+ clearTimeout(viewer.timer);
207
+ } else if (ifexists) {
208
+ //No existing timer? Don't start a new one
209
+ return;
210
+ }
211
+
212
+ viewer.timer = setTimeout(function() {viewer.rotated = true; viewer.draw();}, 50);
213
+ }
214
+
215
+ function canvasMouseDown(event, mouse) {
216
+ //if (server) serverMouseDown(event, mouse); //Pass to server handler
217
+ return false;
218
+ }
219
+
220
+ var hideTimer;
221
+
222
+ function canvasMouseMove(event, mouse) {
223
+ //if (server) serverMouseMove(event, mouse); //Pass to server handler
224
+
225
+ //GUI elements to show on mouseover
226
+ if (mouse.element && mouse.element.viewer) {
227
+ var gui = mouse.element.viewer.gui;
228
+ var rect = mouse.element.getBoundingClientRect();
229
+ x = event.clientX-rect.left;
230
+ y = event.clientY-rect.top;
231
+ if (x >= 0 && y >= 0 && x < rect.width && y < rect.height) {
232
+ if (!gui && mouse.element.imgtarget)
233
+ mouse.element.imgtarget.nextElementSibling.style.display = "block";
234
+
235
+ if (gui) {
236
+ if (mouse.element.imgtarget) mouse.element.imgtarget.nextElementSibling.style.display = "none";
237
+ gui.domElement.style.display = "block";
238
+ }
239
+
240
+ if (hideTimer)
241
+ clearTimeout(hideTimer);
242
+
243
+ hideTimer = setTimeout(function () { hideMenu(mouse.element, gui);}, 1000 );
244
+ }
245
+ }
246
+
247
+ if (!mouse.isdown || !mouse.element.viewer) return true;
248
+ mouse.element.viewer.rotating = false;
249
+
250
+ //Switch buttons for translate/rotate
251
+ var button = mouse.button;
252
+ if (mouse.element.viewer.mode == "Translate") {
253
+ //Swap rotate/translate buttons
254
+ if (button == 0)
255
+ button = 2
256
+ else if (button == 2)
257
+ button = 0;
258
+ } else if (button==0 && mouse.element.viewer.mode == "Zoom") {
259
+ button = 100;
260
+ }
261
+
262
+ //console.log(mouse.deltaX + "," + mouse.deltaY);
263
+ switch (button)
264
+ {
265
+ case 0:
266
+ mouse.element.viewer.rotateY(mouse.deltaX/5);
267
+ mouse.element.viewer.rotateX(mouse.deltaY/5);
268
+ mouse.element.viewer.rotating = true;
269
+ sortTimer(true, mouse.element.viewer); //Delay sort if queued
270
+ break;
271
+ case 1:
272
+ mouse.element.viewer.rotateZ(Math.sqrt(mouse.deltaX*mouse.deltaX + mouse.deltaY*mouse.deltaY)/5);
273
+ mouse.element.viewer.rotating = true;
274
+ sortTimer(true, mouse.element.viewer); //Delay sort if queued
275
+ break;
276
+ case 2:
277
+ var adjust = mouse.element.viewer.modelsize / 1000; //1/1000th of size
278
+ mouse.element.viewer.translate[0] += mouse.deltaX * adjust;
279
+ mouse.element.viewer.translate[1] -= mouse.deltaY * adjust;
280
+ break;
281
+ case 100:
282
+ var adjust = mouse.element.viewer.modelsize / 1000; //1/1000th of size
283
+ mouse.element.viewer.translate[2] += mouse.deltaX * adjust;
284
+ break;
285
+ }
286
+
287
+ //Always draw border while interacting in server mode?
288
+ if (server)
289
+ mouse.element.viewer.draw(true);
290
+ //Draw border while interacting (automatically on for models > 500K vertices)
291
+ //Hold shift to switch from default behaviour
292
+ else if (mouse.element.viewer.vis.interactive)
293
+ mouse.element.viewer.draw();
294
+ else
295
+ mouse.element.viewer.draw(!event.shiftKey);
296
+ return false;
297
+ }
298
+
299
+ var zoomTimer;
300
+ var zoomClipTimer;
301
+ var zoomSpin = 0;
302
+
303
+ function canvasMouseWheel(event, mouse) {
304
+ if (event.shiftKey) {
305
+ var factor = event.spin * 0.01;
306
+ if (zoomClipTimer) clearTimeout(zoomClipTimer);
307
+ zoomClipTimer = setTimeout(function () {mouse.element.viewer.zoomClip(factor);}, 100 );
308
+ } else {
309
+ if (zoomTimer)
310
+ clearTimeout(zoomTimer);
311
+ zoomSpin += event.spin;
312
+ zoomTimer = setTimeout(function () {mouse.element.viewer.zoom(zoomSpin*0.01); zoomSpin = 0;}, 100 );
313
+ //Clear the box after a second
314
+ //setTimeout(function() {mouse.element.viewer.clear();}, 1000);
315
+ }
316
+ return false; //Prevent default
317
+ }
318
+
319
+ function canvasMousePinch(event, mouse) {
320
+ if (event.distance != 0) {
321
+ var factor = event.distance * 0.0001;
322
+ mouse.element.viewer.zoom(factor);
323
+ //Clear the box after a second
324
+ //setTimeout(function() {mouse.element.viewer.clear();}, 1000);
325
+ }
326
+ return false; //Prevent default
327
+ }
328
+
329
+ function getImageDataURL(img) {
330
+ var canvas = document.createElement("canvas");
331
+ canvas.width = img.width;
332
+ canvas.height = img.height;
333
+ var ctx = canvas.getContext("2d");
334
+ ctx.drawImage(img, 0, 0);
335
+ var dataURL = canvas.toDataURL("image/png");
336
+ return dataURL;
337
+ }
338
+
339
+ function loadColourMaps(vis) {
340
+ //Load colourmaps
341
+ if (!vis.colourmaps) return;
342
+
343
+ var canvas = document.getElementById('palette');
344
+ for (var i=0; i<vis.colourmaps.length; i++) {
345
+ var palette = new Palette(vis.colourmaps[i].colours);
346
+ vis.colourmaps[i].palette = palette;
347
+ paletteLoad(palette);
348
+ }
349
+
350
+ //if (viewer) viewer.setColourMap(sel);
351
+ }
352
+
353
+ function safeLog10(val) {return val < Number.MIN_VALUE ? Math.log10(Number.MIN_VALUE) : Math.log10(val); }
354
+
355
+ function vertexColour(colour, opacity, colourmap, data, idx) {
356
+ //Default to object colour property
357
+ if (data.values) {
358
+ var colrange = data.vertices.data.length / (3*data.values.data.length);
359
+ idx = Math.floor(idx/colrange);
360
+ if (colourmap) {
361
+ var min = 0;
362
+ var max = 1;
363
+ if (data.values.minimum != undefined) min = data.values.minimum;
364
+ if (data.values.maximum != undefined) max = data.values.maximum;
365
+ //Use a colourmap
366
+ if (colourmap.range != undefined) {
367
+ rmin = parseFloat(colourmap.range[0]);
368
+ rmax = parseFloat(colourmap.range[1]);
369
+ if (rmin < rmax) {
370
+ min = rmin;
371
+ max = rmax;
372
+ }
373
+ }
374
+ //Get nearest pixel on the canvas
375
+ var pos = MAXIDX*0.5; //If rubbish data, return centre
376
+ //Allows single value for entire object
377
+ if (idx >= data.values.data.length) idx = data.values.data.length-1;
378
+ var val = data.values.data[idx];
379
+ if (val < min)
380
+ pos = 0;
381
+ else if (val > max)
382
+ pos = MAXIDX;
383
+ else if (max > min) {
384
+ var scaled;
385
+ if (colourmap.logscale) {
386
+ val = safeLog10(val);
387
+ min = safeLog10(min);
388
+ max = safeLog10(max);
389
+ }
390
+ //Scale to range [0,1]
391
+ scaled = (val - min) / (max - min);
392
+ //Get colour pos [0-2047)
393
+ pos = Math.round(MAXIDX * scaled);
394
+ }
395
+ colour = colourmap.palette.cache[pos];
396
+ //if (idx % 100) console.log(" : " + val + " min " + min + " max " + max + " pos = " + pos + " colour: " + colour);
397
+ } else if (data.values.type == 'integer') {
398
+ //Integer data values, treat as colours
399
+ colour = data.values.data[idx];
400
+ }
401
+ }
402
+ //RGBA colour values
403
+ if (data.colours) {
404
+ var colrange = data.vertices.data.length / (3*data.colours.data.length);
405
+ idx = Math.floor(idx/colrange);
406
+ if (data.colours.data.length == 1) idx = 0; //Single colour only provided
407
+ if (idx >= data.colours.data.length) idx = data.colours.data.length-1;
408
+ colour = data.colours.data[idx];
409
+ }
410
+
411
+ //Apply opacity per object setting
412
+ var C = new Colour(colour);
413
+ if (opacity < 1.0)
414
+ C.alpha *= opacity;
415
+ //Return final integer value
416
+ return C.toInt();
417
+ }
418
+
419
+ //Get eye pos vector z by multiplying vertex by modelview matrix
420
+ function eyeDistance(M2,P) {
421
+ return -(M2[0] * P[0] + M2[1] * P[1] + M2[2] * P[2] + M2[3]);
422
+ }
423
+
424
+ function newFilledArray(length, val) {
425
+ var array = [];
426
+ for (var i = 0; i < length; i++) {
427
+ array[i] = val;
428
+ }
429
+ return array;
430
+ }
431
+
432
+ function radix(nbyte, source, dest, N)
433
+ //void radix(char byte, char size, long N, unsigned char *src, unsigned char *dst)
434
+ {
435
+ // Radix counting sort of 1 byte, 8 bits = 256 bins
436
+ var count = newFilledArray(256,0);
437
+ var index = [];
438
+ var i;
439
+ //unsigned char* dst = (unsigned char*)dest;
440
+
441
+ //Create histogram, count occurences of each possible byte value 0-255
442
+ var mask = 0xff;// << (byte*8);
443
+ for (i=0; i<N; i++) {
444
+ //if (byte > 0) alert(source[i].key + " : " + (source[i].key >> (byte*8) & mask) + " " + mask);
445
+ count[source[i].key >> (nbyte*8) & mask]++;
446
+ }
447
+
448
+ //Calculate number of elements less than each value (running total through array)
449
+ //This becomes the offset index into the sorted array
450
+ //(eg: there are 5 smaller values so place me in 6th position = 5)
451
+ index[0]=0;
452
+ for (i=1; i<256; i++) index[i] = index[i-1] + count[i-1];
453
+
454
+ //Finally, re-arrange data by index positions
455
+ for (i=0; i<N; i++ )
456
+ {
457
+ var val = source[i].key >> (nbyte*8) & mask; //Get value
458
+ //memcpy(&dest[index[val]], &source[i], size);
459
+ dest[index[val]] = source[i];
460
+ //memcpy(&dst[index[val]*size], &src[i*size], size);
461
+ index[val]++; //Increment index to push next element with same value forward one
462
+ }
463
+ }
464
+
465
+
466
+ function radix_sort(source, swap, bytes)
467
+ {
468
+ //assert(bytes % 2 == 0);
469
+ //DEBUG && console.log("Radix X sort: %d items %d bytes. Byte: ", N, size);
470
+ // Sort bytes from least to most significant
471
+ var N = source.length;
472
+ for (var x = 0; x < bytes; x += 2)
473
+ {
474
+ radix(x, source, swap, N);
475
+ radix(x+1, swap, source, N);
476
+ }
477
+ }
478
+
479
+
480
+ //http://stackoverflow.com/questions/7936923/assist-with-implementing-radix-sort-in-javascript
481
+ //arr: array to be sorted
482
+ //begin: 0
483
+ //end: length of array
484
+ //bit: maximum number of bits required to represent numbers in arr
485
+ function msb_radix_sort(arr, begin, end, bit) {
486
+ var i, j, mask, tmp;
487
+ i = begin;
488
+ j = end;
489
+ mask = 1 << bit;
490
+ while(i < j) {
491
+ while(i < j && !(arr[i].key & mask)) ++i;
492
+ while(i < j && (arr[j - 1].key & mask)) --j;
493
+ if(i < j) {
494
+ j--;
495
+ tmp = arr[i]; //Swap
496
+ arr[i] = arr[j];
497
+ arr[j] = tmp;
498
+ i++;
499
+ }
500
+ }
501
+ if(bit && i > begin)
502
+ msb_radix_sort(arr, begin, i, bit - 1);
503
+ if(bit && i < end)
504
+ msb_radix_sort(arr, i, end, bit - 1);
505
+ }
506
+
507
+ //This object encapsulates a vertex buffer and shader set
508
+ function Renderer(viewer, type, colour, border) {
509
+ this.viewer = viewer;
510
+ this.gl = viewer.gl;
511
+ this.type = type;
512
+ this.border = border;
513
+ if (colour) this.colour = new Colour(colour);
514
+
515
+ //Only two options for now, points and triangles
516
+ if (type == "points") {
517
+ //Particle renderer
518
+ //Vertex3, Colour, Size, Type
519
+ this.attribSizes = [3 * Float32Array.BYTES_PER_ELEMENT,
520
+ Int32Array.BYTES_PER_ELEMENT,
521
+ Float32Array.BYTES_PER_ELEMENT,
522
+ Float32Array.BYTES_PER_ELEMENT];
523
+ } else if (type == "triangles") {
524
+ //Triangle renderer
525
+ //Vertex3, Normal3, Colour, TexCoord2
526
+ this.attribSizes = [3 * Float32Array.BYTES_PER_ELEMENT,
527
+ 3 * Float32Array.BYTES_PER_ELEMENT,
528
+ Int32Array.BYTES_PER_ELEMENT,
529
+ 2 * Float32Array.BYTES_PER_ELEMENT];
530
+ } else if (type == "lines") {
531
+ //Line renderer
532
+ //Vertex3, Colour
533
+ this.attribSizes = [3 * Float32Array.BYTES_PER_ELEMENT,
534
+ Int32Array.BYTES_PER_ELEMENT];
535
+ } else if (type == "volume") {
536
+ //Volume renderer
537
+ //Vertex3
538
+ this.attribSizes = [3 * Float32Array.BYTES_PER_ELEMENT];
539
+ }
540
+
541
+ this.elementSize = 0;
542
+ for (var i=0; i<this.attribSizes.length; i++)
543
+ this.elementSize += this.attribSizes[i];
544
+ }
545
+
546
+ Renderer.prototype.init = function() {
547
+ if (this.type == "triangles" && !this.viewer.hasTriangles) return false;
548
+ if (this.type == "points" && !this.viewer.hasPoints) return false;
549
+ var fs = this.type + '-fs';
550
+ var vs = this.type + '-vs';
551
+ var vis = this.viewer.vis;
552
+
553
+ //User defined shaders if provided...
554
+ if (vis.shaders) {
555
+ if (vis.shaders[this.type]) {
556
+ fs = vis.shaders[this.type].fragment || fs;
557
+ vs = vis.shaders[this.type].vertex || vs;
558
+ }
559
+ }
560
+
561
+ var fdefines = "#extension GL_OES_standard_derivatives : enable\nprecision highp float;\n#define WEBGL\n";
562
+ var vdefines = "precision highp float;\n#define WEBGL\n";
563
+
564
+ if (this.type == "volume" && this.id && this.image) {
565
+ //Setup two-triangle rendering
566
+ this.viewer.webgl.init2dBuffers(this.gl.TEXTURE1); //Use 2nd texture unit
567
+
568
+ //Override texture params set in previous call
569
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
570
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
571
+
572
+ //Load the volume texture image
573
+ this.viewer.webgl.loadTexture(this.image, this.gl.LINEAR); //this.gl.LUMINANCE, true
574
+
575
+ //Calculated scaling
576
+ this.res = vis.objects[this.id].volume["res"];
577
+ this.scaling = vis.objects[this.id].volume["scale"];
578
+ /*/Auto compensate for differences in resolution..
579
+ if (vis.objects[this.id].volume.autoscale) {
580
+ //Divide all by the highest res
581
+ var maxn = Math.max.apply(null, this.res);
582
+ this.scaling = [this.res[0] / maxn * vis.objects[this.id].volume["scale"][0],
583
+ this.res[1] / maxn * vis.objects[this.id].volume["scale"][1],
584
+ this.res[2] / maxn * vis.objects[this.id].volume["scale"][2]];
585
+ }*/
586
+ this.tiles = [this.image.width / this.res[0],
587
+ this.image.height / this.res[1]];
588
+ this.iscale = [1.0 / this.scaling[0], 1.0 / this.scaling[1], 1.0 / this.scaling[2]]
589
+
590
+ var maxSamples = 1024; //interactive ? 1024 : 256;
591
+ fdefines += "const highp vec2 slices = vec2(" + this.tiles[0] + "," + this.tiles[1] + ");\n";
592
+ fdefines += "const int maxSamples = " + maxSamples + ";\n";
593
+
594
+ if (vis.objects[this.id].tricubicfilter)
595
+ fdefines += "#define ENABLE_TRICUBIC\n";
596
+ }
597
+
598
+ //vs = vdefines + getSourceFromElement(vs).replace(/^out /gm, 'varying ').replace(/^in /gm, 'attribute ');
599
+ //fs = fdefines + getSourceFromElement(fs).replace(/^in /gm, 'varying ');
600
+ vs = vdefines + shaders[vs].replace(/^in /gm, 'attribute ').replace(/^out /gm, 'varying ');
601
+ fs = fdefines + shaders[fs].replace(/^in /gm, 'varying ');
602
+
603
+ try {
604
+ //Compile the shaders
605
+ this.program = new WebGLProgram(this.gl, vs, fs);
606
+ if (this.program.errors) DEBUG && console.log(this.program.errors);
607
+ //Setup attribs/uniforms (flag set to skip enabling attribs)
608
+ this.program.setup(undefined, undefined, true);
609
+ } catch(e) {
610
+ console.log("FRAGMENT SHADER:\n" + fs);
611
+ console.log("VERTEX SHADER:\n" + vs);
612
+ console.error(e);
613
+ return false;
614
+ }
615
+ return true;
616
+ }
617
+
618
+ function SortIdx(idx, key) {
619
+ this.idx = idx;
620
+ this.key = key;
621
+ }
622
+
623
+ Renderer.prototype.loadElements = function() {
624
+ if (this.border) return;
625
+ DEBUG && console.log("Loading " + this.type + " elements...");
626
+ var start = new Date();
627
+ var distances = [];
628
+ var indices = [];
629
+ var vis = this.viewer.vis;
630
+ //Only update the positions array when sorting due to update
631
+ if (!this.positions || !this.viewer.rotated || this.type == 'lines') {
632
+ this.positions = [];
633
+ //Add visible element positions
634
+ for (var id in vis.objects) {
635
+ var name = vis.objects[id].name;
636
+ var skip = !vis.objects[id].visible;
637
+
638
+ if (this.type == "points") {
639
+ if (vis.objects[id].points) {
640
+ for (var e in vis.objects[id].points) {
641
+ var pdat = vis.objects[id].points[e];
642
+ var count = pdat.vertices.data.length;
643
+ //DEBUG && console.log(name + " " + skip + " : " + count);
644
+ for (var i=0; i<count; i += 3)
645
+ this.positions.push(skip ? null : [pdat.vertices.data[i], pdat.vertices.data[i+1], pdat.vertices.data[i+2]]);
646
+ }
647
+ }
648
+ } else if (this.type == "triangles") {
649
+ if (vis.objects[id].triangles) {
650
+ for (var e in vis.objects[id].triangles) {
651
+ var tdat = vis.objects[id].triangles[e];
652
+ var count = tdat.indices.data.length/3;
653
+
654
+ //console.log(name + " " + skip + " : " + count + " - " + tdat.centroids.length);
655
+ for (var i=0; i<count; i++) {
656
+ //this.positions.push(skip ? null : tdat.centroids[i]);
657
+ if (skip || !tdat.centroids)
658
+ this.positions.push(null);
659
+ else if (tdat.centroids.length == 1)
660
+ this.positions.push(tdat.centroids[0]);
661
+ else
662
+ this.positions.push(tdat.centroids[i]);
663
+ }
664
+ }
665
+ }
666
+ } else if (this.type == "lines") {
667
+ //Write lines directly to indices buffer, no depth sort necessary
668
+ if (skip) continue;
669
+ if (vis.objects[id].lines) {
670
+ for (var e in vis.objects[id].lines) {
671
+ var ldat = vis.objects[id].lines[e];
672
+ var count = ldat.indices.data.length;
673
+ //DEBUG && console.log(name + " " + skip + " : " + count);
674
+ for (var i=0; i<count; i++)
675
+ indices.push(ldat.indices.data[i]);
676
+ }
677
+ }
678
+ }
679
+ }
680
+
681
+ var time = (new Date() - start) / 1000.0;
682
+ DEBUG && console.log(this.type + " : " + time + " seconds to update positions " + this.positions.length);
683
+ start = new Date();
684
+ }
685
+
686
+ //Depth sorting and create index buffer for objects that require it...
687
+ if (indices.length == 0) {
688
+ var distance;
689
+ //Calculate min/max distances from view plane
690
+ var minmax = minMaxDist();
691
+ var mindist = minmax[0];
692
+ var maxdist = minmax[1];
693
+
694
+ //Update eye distances, clamping int distance to integer between 0 and 65535
695
+ var multiplier = 65534.0 / (maxdist - mindist);
696
+ var M2 = [this.viewer.webgl.modelView.matrix[2],
697
+ this.viewer.webgl.modelView.matrix[6],
698
+ this.viewer.webgl.modelView.matrix[10],
699
+ this.viewer.webgl.modelView.matrix[14]];
700
+
701
+ //Add visible element distances to sorting array
702
+ for (var i=0; i<this.positions.length; i++) {
703
+ if (this.positions[i]) {
704
+ if (this.positions[i].length == 0) {
705
+ //No position data, draw last
706
+ distances.push(new SortIdx(i, 65535));
707
+ } else {
708
+ //Distance from viewing plane is -eyeZ
709
+ distance = multiplier * (-(M2[0] * this.positions[i][0] + M2[1] * this.positions[i][1] + M2[2] * this.positions[i][2] + M2[3]) - mindist);
710
+ //distance = (-(M2[0] * this.positions[i][0] + M2[1] * this.positions[i][1] + M2[2] * this.positions[i][2] + M2[3]) - mindist);
711
+ // if (i%100==0 && this.positions[i].length == 4) console.log(distance + " - " + this.positions[i][3]);
712
+ //if (this.positions[i].length == 4) distance -= this.positions[i][3];
713
+ //distance *= multiplier;
714
+ //if (distance < 0) distance = 0;
715
+ if (distance > 65535) distance = 65535;
716
+ distances.push(new SortIdx(i, 65535 - Math.round(distance)));
717
+ }
718
+ }
719
+ }
720
+
721
+ var time = (new Date() - start) / 1000.0;
722
+ DEBUG && console.log(this.type + " : " + time + " seconds to update distances " + distances.length);
723
+
724
+ if (distances.length > 0) {
725
+ //Sort
726
+ start = new Date();
727
+ //distances.sort(function(a,b){return a.key - b.key});
728
+ //This is about 10 times faster than above:
729
+ if (this.viewer.view.is3d)
730
+ msb_radix_sort(distances, 0, distances.length, 16);
731
+ //Pretty sure msb is still fastest...
732
+ //if (!this.swap) this.swap = [];
733
+ //radix_sort(distances, this.swap, 2);
734
+ time = (new Date() - start) / 1000.0;
735
+ DEBUG && console.log(time + " seconds to sort");
736
+
737
+ start = new Date();
738
+ //Reload index buffer
739
+ if (this.type == "points") {
740
+ //Process points
741
+ for (var i = 0; i < distances.length; ++i)
742
+ indices.push(distances[i].idx);
743
+ //if (distances[i].idx > this.elements) alert("ERROR: " + i + " - " + distances[i].idx + " > " + this.elements);
744
+ } else if (this.type == "triangles") {
745
+ //Process triangles
746
+ for (var i = 0; i < distances.length; ++i) {
747
+ var i3 = distances[i].idx*3;
748
+ indices.push(i3);
749
+ indices.push(i3+1);
750
+ indices.push(i3+2);
751
+ //if (i3+2 > this.elements) alert("ERROR: " + i + " - " + (i3+2) + " > " + this.elements);
752
+ }
753
+ }
754
+ time = (new Date() - start) / 1000.0;
755
+ DEBUG && console.log(time + " seconds to load index buffers");
756
+ }
757
+ }
758
+
759
+ start = new Date();
760
+ if (indices.length > 0) {
761
+ //this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indices), this.gl.STATIC_DRAW);
762
+ this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indices), this.gl.DYNAMIC_DRAW);
763
+ //this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), this.gl.DYNAMIC_DRAW);
764
+
765
+ time = (new Date() - start) / 1000.0;
766
+ DEBUG && console.log(this.type + " : " + time + " seconds to update index buffer object " + indices.length);
767
+ }
768
+ //Update count to visible elements...
769
+ this.elements = indices.length;
770
+ }
771
+
772
+ var texcoords = [[[0,0], [0,0], [0,0]], [[0,0], [0,255], [255,0]], [[255,255], [0,0], [0,255]]];
773
+
774
+ function VertexBuffer(elements, size) {
775
+ this.size = size;
776
+ this.vertexSizeInFloats = size / Float32Array.BYTES_PER_ELEMENT;
777
+ this.array = new ArrayBuffer(elements * size);
778
+ // Map this buffer to a Float32Array to access the positions/normals/sizes
779
+ this.floats = new Float32Array(this.array);
780
+ // Map the same buffer to an Int32Array to access the color
781
+ this.ints = new Int32Array(this.array);
782
+ this.bytes = new Uint8Array(this.array);
783
+ this.offset = 0;
784
+ DEBUG && console.log(elements + " - " + size);
785
+ DEBUG && console.log("Created vertex buffer");
786
+ }
787
+
788
+ VertexBuffer.prototype.loadPoints = function(object, viewer) {
789
+ for (var p in object.points) {
790
+ var pdat = object.points[p];
791
+
792
+ /*console.log("loadPoints " + p);
793
+ if (pdat.values)
794
+ console.log(object.name + " : " + pdat.values.minimum + " -> " + pdat.values.maximum);
795
+ if (object.colourmap >= 0)
796
+ console.log(object.name + " :: " + vis.colourmaps[object.colourmap].range[0] + " -> " + vis.colourmaps[object.colourmap].range[1]);
797
+ else
798
+ console.log(object.colourmap);*/
799
+
800
+ var map = viewer.lookupMap(object.colourmap);
801
+
802
+ var psize = object.pointsize ? object.pointsize : viewer.vis.properties.pointsize;
803
+ if (!psize) psize = 1.0;
804
+ psize = psize * (object.scaling || 1.0);
805
+ //console.log("POINTSIZE: " + psize);
806
+
807
+ for (var i=0; i<pdat.vertices.data.length/3; i++) {
808
+ var i3 = i*3;
809
+ var vert = [pdat.vertices.data[i3], pdat.vertices.data[i3+1], pdat.vertices.data[i3+2]];
810
+ this.floats[this.offset] = vert[0];
811
+ this.floats[this.offset+1] = vert[1];
812
+ this.floats[this.offset+2] = vert[2];
813
+ this.ints[this.offset+3] = vertexColour(object.colour, object.opacity, map, pdat, i)
814
+ this.floats[this.offset+4] = pdat.sizes ? pdat.sizes.data[i] * psize : psize;
815
+ this.floats[this.offset+5] = object.pointtype >= 0 ? object.pointtype : -1;
816
+ this.offset += this.vertexSizeInFloats;
817
+ }
818
+ }
819
+ }
820
+
821
+ VertexBuffer.prototype.loadTriangles = function(object, id, viewer) {
822
+ //Process triangles
823
+ if (!this.byteOffset) this.byteOffset = 9 * Float32Array.BYTES_PER_ELEMENT;
824
+ var T = 0;
825
+ if (object.wireframe) T = 1;
826
+ for (var t in object.triangles) {
827
+ var tdat = object.triangles[t];
828
+ var calcCentroids = false;
829
+ if (!tdat.centroids) {
830
+ calcCentroids = true;
831
+ tdat.centroids = [];
832
+ }
833
+
834
+ //if (tdat.values)
835
+ // console.log(object.name + " : " + tdat.values.minimum + " -> " + tdat.values.maximum);
836
+ // console.log("OPACITY: " + object.name + " : " + object.opacity);
837
+ //if (object.colourmap >= 0)
838
+ // console.log(object.name + " :: " + viewer.vis.colourmaps[object.colourmap].range[0] + " -> " + viewer.vis.colourmaps[object.colourmap].range[1]);
839
+
840
+ var map = viewer.lookupMap(object.colourmap);
841
+
842
+ for (var i=0; i<tdat.indices.data.length/3; i++) {
843
+ if (i%10000 == 0) console.log(i);
844
+ //Indices holds references to vertices and other data
845
+ var i3 = i * 3;
846
+ var ids = [tdat.indices.data[i3], tdat.indices.data[i3+1], tdat.indices.data[i3+2]];
847
+ var ids3 = [ids[0]*3, ids[1]*3, ids[2]*3];
848
+
849
+ for (var j=0; j<3; j++) {
850
+ this.floats[this.offset] = tdat.vertices.data[ids3[j]];
851
+ this.floats[this.offset+1] = tdat.vertices.data[ids3[j]+1];
852
+ this.floats[this.offset+2] = tdat.vertices.data[ids3[j]+2];
853
+ if (tdat.normals && tdat.normals.data.length == 3) {
854
+ //Single surface normal
855
+ this.floats[this.offset+3] = tdat.normals.data[0];
856
+ this.floats[this.offset+4] = tdat.normals.data[1];
857
+ this.floats[this.offset+5] = tdat.normals.data[2];
858
+ } else if (tdat.normals) {
859
+ this.floats[this.offset+3] = tdat.normals.data[ids3[j]];
860
+ this.floats[this.offset+4] = tdat.normals.data[ids3[j]+1];
861
+ this.floats[this.offset+5] = tdat.normals.data[ids3[j]+2];
862
+ } else {
863
+ this.floats[this.offset+3] = 0.0;
864
+ this.floats[this.offset+4] = 0.0;
865
+ this.floats[this.offset+5] = 0.0;
866
+ }
867
+ if (!object.tex)
868
+ this.ints[this.offset+6] = vertexColour(object.colour, object.opacity, map, tdat, ids[j])
869
+ else {
870
+ if (tdat.texcoords) {
871
+ this.floats[this.offset+7] = tdat.texcoords.data[ids[j]*2];
872
+ this.floats[this.offset+8] = tdat.texcoords.data[ids[j]*2+1];
873
+ } else {
874
+ this.floats[this.offset+7] = texcoords[(i%2+1)*T][j][0];
875
+ this.floats[this.offset+8] = texcoords[(i%2+1)*T][j][1];
876
+ }
877
+ }
878
+ this.offset += this.vertexSizeInFloats;
879
+ this.byteOffset += this.size;
880
+ }
881
+
882
+ //Calc centroids (only required if vertices changed)
883
+ if (calcCentroids) {
884
+ if (tdat.width) //indices.data.length == 6)
885
+ //Cross-sections, null centroid - always drawn last
886
+ tdat.centroids.push([]);
887
+ else {
888
+ //(x1+x2+x3, y1+y2+y3, z1+z2+z3)
889
+ var verts = tdat.vertices.data;
890
+ /*/Side lengths: A-B, A-C, B-C
891
+ var AB = vec3.createFrom(verts[ids3[0]] - verts[ids3[1]], verts[ids3[0] + 1] - verts[ids3[1] + 1], verts[ids3[0] + 2] - verts[ids3[1] + 2]);
892
+ var AC = vec3.createFrom(verts[ids3[0]] - verts[ids3[2]], verts[ids3[0] + 1] - verts[ids3[2] + 1], verts[ids3[0] + 2] - verts[ids3[2] + 2]);
893
+ var BC = vec3.createFrom(verts[ids3[1]] - verts[ids3[2]], verts[ids3[1] + 1] - verts[ids3[2] + 1], verts[ids3[1] + 2] - verts[ids3[2] + 2]);
894
+ var lengths = [vec3.length(AB), vec3.length(AC), vec3.length(BC)];
895
+ //Size weighting shift
896
+ var adj = (lengths[0] + lengths[1] + lengths[2]) / 9.0;
897
+ //if (i%100==0) console.log(verts[ids3[0]] + "," + verts[ids3[0] + 1] + "," + verts[ids3[0] + 2] + " " + adj);*/
898
+ tdat.centroids.push([(verts[ids3[0]] + verts[ids3[1]] + verts[ids3[2]]) / 3,
899
+ (verts[ids3[0] + 1] + verts[ids3[1] + 1] + verts[ids3[2] + 1]) / 3,
900
+ (verts[ids3[0] + 2] + verts[ids3[1] + 2] + verts[ids3[2] + 2]) / 3]);
901
+ }
902
+ }
903
+ }
904
+ }
905
+ }
906
+
907
+ VertexBuffer.prototype.loadLines = function(object) {
908
+ for (var l in object.lines) {
909
+ var ldat = object.lines[l];
910
+ var map = viewer.lookupMap(object.colourmap);
911
+ for (var i=0; i<ldat.vertices.data.length/3; i++) {
912
+ var i3 = i*3;
913
+ var vert = [ldat.vertices.data[i3], ldat.vertices.data[i3+1], ldat.vertices.data[i3+2]];
914
+ this.floats[this.offset] = vert[0];
915
+ this.floats[this.offset+1] = vert[1];
916
+ this.floats[this.offset+2] = vert[2];
917
+ this.ints[this.offset+3] = vertexColour(object.colour, object.opacity, map, ldat, i)
918
+ this.offset += this.vertexSizeInFloats;
919
+ }
920
+ }
921
+ }
922
+
923
+ VertexBuffer.prototype.update = function(gl) {
924
+ start = new Date();
925
+ //gl.bufferData(gl.ARRAY_BUFFER, this.array, gl.STATIC_DRAW);
926
+ gl.bufferData(gl.ARRAY_BUFFER, this.array, gl.DYNAMIC_DRAW);
927
+ //gl.bufferData(gl.ARRAY_BUFFER, this.vertices * this.elementSize, gl.DYNAMIC_DRAW);
928
+ //gl.bufferSubData(gl.ARRAY_BUFFER, 0, buffer);
929
+
930
+ time = (new Date() - start) / 1000.0;
931
+ DEBUG && console.log(time + " seconds to update vertex buffer object");
932
+ }
933
+
934
+ Renderer.prototype.updateBuffers = function() {
935
+ var vis = viewer.vis;
936
+ if (this.border) {
937
+ this.box(viewer.view.min, viewer.view.max);
938
+ this.elements = 24;
939
+ return;
940
+ }
941
+
942
+ //Count vertices
943
+ this.elements = 0;
944
+ for (var id in vis.objects) {
945
+ if (this.type == "triangles" && vis.objects[id].triangles) {
946
+ for (var t in vis.objects[id].triangles)
947
+ this.elements += vis.objects[id].triangles[t].indices.data.length;
948
+ } else if (this.type == "points" && vis.objects[id].points) {
949
+ for (var t in vis.objects[id].points)
950
+ this.elements += vis.objects[id].points[t].vertices.data.length/3;
951
+ } else if (this.type == "lines" && vis.objects[id].lines) {
952
+ for (var t in vis.objects[id].lines)
953
+ this.elements += vis.objects[id].lines[t].indices.data.length;
954
+ }
955
+ }
956
+
957
+ if (this.elements == 0) return;
958
+ DEBUG && console.log("Updating " + this.type + " data... (" + this.elements + " elements)");
959
+ console.log("Updating " + this.type + " data... (" + this.elements + " elements)");
960
+
961
+ //Load vertices and attributes into buffers
962
+ var start = new Date();
963
+
964
+ //Copy data to VBOs
965
+ var buffer = new VertexBuffer(this.elements, this.elementSize);
966
+
967
+ //Reload vertex buffers
968
+ if (this.type == "points") {
969
+ //Process points
970
+
971
+ /*/Auto 50% subsample when > 1M particles
972
+ var subsample = 1;
973
+ if (document.getElementById("subsample").checked == true && newParticles > 1000000) {
974
+ subsample = Math.round(newParticles/1000000 + 0.5);
975
+ DEBUG && console.log("Subsampling at " + (100/subsample) + "% (" + subsample + ") to " + Math.floor(newParticles / subsample));
976
+ }*/
977
+ //Random subsampling
978
+ //if (subsample > 1 && Math.random() > 1.0/subsample) continue;
979
+
980
+ for (var id in vis.objects)
981
+ if (vis.objects[id].points)
982
+ buffer.loadPoints(vis.objects[id], this.viewer);
983
+
984
+ } else if (this.type == "lines") {
985
+ //Process lines
986
+ for (var id in vis.objects)
987
+ if (vis.objects[id].lines)
988
+ buffer.loadLines(vis.objects[id]);
989
+
990
+ } else if (this.type == "triangles") {
991
+ //Process triangles
992
+ for (var id in vis.objects)
993
+ if (vis.objects[id].triangles)
994
+ buffer.loadTriangles(vis.objects[id], id, this.viewer);
995
+ }
996
+
997
+ var time = (new Date() - start) / 1000.0;
998
+ DEBUG && console.log(time + " seconds to load buffers... (elements: " + this.elements + " bytes: " + buffer.bytes.length + ")");
999
+ console.log(time + " seconds to load buffers... (elements: " + this.elements + " bytes: " + buffer.bytes.length + ")");
1000
+
1001
+ buffer.update(this.gl);
1002
+ }
1003
+
1004
+ Renderer.prototype.box = function(min, max) {
1005
+ var vertices = new Float32Array(
1006
+ [
1007
+ min[0], min[1], max[2],
1008
+ min[0], max[1], max[2],
1009
+ max[0], max[1], max[2],
1010
+ max[0], min[1], max[2],
1011
+ min[0], min[1], min[2],
1012
+ min[0], max[1], min[2],
1013
+ max[0], max[1], min[2],
1014
+ max[0], min[1], min[2]
1015
+ ]);
1016
+
1017
+ var indices = new Uint16Array(
1018
+ [
1019
+ 0, 1, 1, 2, 2, 3, 3, 0,
1020
+ 4, 5, 5, 6, 6, 7, 7, 4,
1021
+ 0, 4, 3, 7, 1, 5, 2, 6
1022
+ ]
1023
+ );
1024
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
1025
+ this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, indices, this.gl.STATIC_DRAW);
1026
+ }
1027
+
1028
+ Renderer.prototype.getglobal = function(prop) {
1029
+ //Lookup global prop, then use default if not found
1030
+ if (this.viewer.vis.properties[prop] != undefined)
1031
+ return this.viewer.vis.properties[prop];
1032
+ return this.viewer.dict[prop].default;
1033
+ }
1034
+
1035
+ Renderer.prototype.getprop = function(prop, id) {
1036
+ //Lookup prop on object (or renderer if not set) first, then global, then use default
1037
+ //console.log(prop + " getprop " + this.id);
1038
+ var object = this;
1039
+ if (id == undefined) id = this.id;
1040
+ if (id != undefined) object = this.viewer.vis.objects[id];
1041
+ if (object[prop] != undefined)
1042
+ return object[prop];
1043
+ return this.getglobal(prop);
1044
+ }
1045
+
1046
+ Renderer.prototype.draw = function() {
1047
+ var vis = this.viewer.vis;
1048
+ if (!vis.objects || !vis.objects.length) return;
1049
+ var start = new Date();
1050
+ var desc = "";
1051
+ //console.log(" ----- " + this.type + " --------------------------------------------------------------------");
1052
+
1053
+ //Create buffer if not yet allocated
1054
+ if (this.vertexBuffer == undefined) {
1055
+ //Init shaders etc...
1056
+ //(This is done here now so we can skip triangle shaders if not required,
1057
+ //due to really slow rendering when doing triangles and points... still to be looked into)
1058
+ if (!this.init()) return;
1059
+ DEBUG && console.log("Creating " + this.type + " buffers...");
1060
+ this.vertexBuffer = this.gl.createBuffer();
1061
+ this.indexBuffer = this.gl.createBuffer();
1062
+ //this.viewer.reload = true;
1063
+ }
1064
+
1065
+ if (this.program.attributes["aVertexPosition"] == undefined) return; //Require vertex buffer
1066
+
1067
+ this.viewer.webgl.use(this.program);
1068
+ this.viewer.webgl.setMatrices();
1069
+
1070
+ //Bind buffers
1071
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
1072
+ this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
1073
+
1074
+ //Reload geometry if required
1075
+ this.viewer.canvas.mouse.disabled = true;
1076
+ if (this.reload) this.updateBuffers();
1077
+ if (this.sort || this.viewer.rotated) this.loadElements();
1078
+ this.reload = this.sort = false;
1079
+ this.viewer.canvas.mouse.disabled = false;
1080
+
1081
+ if (this.elements == 0 && this.type != "volume") return;
1082
+
1083
+ //Enable attributes
1084
+ for (var key in this.program.attributes)
1085
+ this.gl.enableVertexAttribArray(this.program.attributes[key]);
1086
+
1087
+ //General uniform vars
1088
+ //TODO: fix this? can't us getprop() as draw is not per object - support per object renderers as in library?
1089
+ this.gl.uniform1i(this.program.uniforms["uLighting"], this.getglobal('lit'));
1090
+ this.gl.uniform1f(this.program.uniforms["uOpacity"], this.getglobal('opacity')); //Global opacity
1091
+ this.gl.uniform1f(this.program.uniforms["uBrightness"], this.getglobal('brightness'));
1092
+ this.gl.uniform1f(this.program.uniforms["uContrast"], this.getglobal('contrast'));
1093
+ this.gl.uniform1f(this.program.uniforms["uAmbient"], this.getglobal('ambient'));
1094
+ this.gl.uniform1f(this.program.uniforms["uSaturation"], this.getglobal('saturation'));
1095
+ this.gl.uniform1f(this.program.uniforms["uAmbient"], this.getglobal('ambient'));
1096
+ this.gl.uniform1f(this.program.uniforms["uDiffuse"], this.getglobal('diffuse'));
1097
+ this.gl.uniform1f(this.program.uniforms["uSpecular"], this.getglobal('specular'));
1098
+ this.gl.uniform1f(this.program.uniforms["uShininess"], this.getglobal('shininess'));
1099
+ this.gl.uniform4fv(this.program.uniforms["uLightPos"], new Float32Array(this.getglobal('lightpos')));
1100
+ this.gl.uniform4fv(this.program.uniforms["uLight"], new Float32Array(this.getglobal('light')));
1101
+ var colour = this.getglobal('colour');
1102
+ //if (this.colour)
1103
+ if (colour)
1104
+ this.gl.uniform4f(this.program.uniforms["uColour"], colour.red/255.0, colour.green/255.0, colour.blue/255.0, colour.alpha);
1105
+
1106
+ var cmin = [vis.properties.xmin || -Infinity,
1107
+ vis.properties.ymin || -Infinity,
1108
+ this.viewer.view.is3d ? (vis.properties.zmin || -Infinity) : -Infinity];
1109
+ var cmax = [vis.properties.xmax || Infinity,
1110
+ vis.properties.ymax || Infinity,
1111
+ this.viewer.view.is3d ? (vis.properties.zmax || Infinity) : Infinity];
1112
+
1113
+ if (vis.properties.clipmap == undefined || vis.properties.clipmap) {
1114
+ cmin = [this.viewer.view.min[0] + this.viewer.dims[0] * cmin[0],
1115
+ this.viewer.view.min[1] + this.viewer.dims[1] * cmin[1],
1116
+ this.viewer.view.min[2] + this.viewer.dims[2] * cmin[2]];
1117
+ cmax = [this.viewer.view.min[0] + this.viewer.dims[0] * cmax[0],
1118
+ this.viewer.view.min[1] + this.viewer.dims[1] * cmax[1],
1119
+ this.viewer.view.min[2] + this.viewer.dims[2] * cmax[2]];
1120
+ }
1121
+ this.gl.uniform3fv(this.program.uniforms["uClipMin"], new Float32Array(cmin));
1122
+ this.gl.uniform3fv(this.program.uniforms["uClipMax"], new Float32Array(cmax));
1123
+
1124
+ if (this.type == "points") {
1125
+
1126
+ this.gl.vertexAttribPointer(this.program.attributes["aVertexPosition"], 3, this.gl.FLOAT, false, this.elementSize, 0);
1127
+ this.gl.vertexAttribPointer(this.program.attributes["aVertexColour"], 4, this.gl.UNSIGNED_BYTE, true, this.elementSize, this.attribSizes[0]);
1128
+ this.gl.vertexAttribPointer(this.program.attributes["aSize"], 1, this.gl.FLOAT, false, this.elementSize, this.attribSizes[0]+this.attribSizes[1]);
1129
+ this.gl.vertexAttribPointer(this.program.attributes["aPointType"], 1, this.gl.FLOAT, false, this.elementSize, this.attribSizes[0]+this.attribSizes[1]+this.attribSizes[2]);
1130
+
1131
+ //Set uniforms...
1132
+ this.gl.uniform1i(this.program.uniforms["uPointType"], this.viewer.pointType >= 0 ? this.viewer.pointType : 1);
1133
+ this.gl.uniform1f(this.program.uniforms["uPointScale"], this.viewer.pointScale*this.viewer.modelsize);
1134
+
1135
+ //Draw points
1136
+ this.gl.drawElements(this.gl.POINTS, this.elements, this.gl.UNSIGNED_INT, 0);
1137
+ //this.gl.drawElements(this.gl.POINTS, this.positions.length, this.gl.UNSIGNED_SHORT, 0);
1138
+ //this.gl.drawArrays(this.gl.POINTS, 0, this.positions.length);
1139
+ desc = this.elements + " points";
1140
+
1141
+ } else if (this.type == "triangles") {
1142
+
1143
+ this.gl.vertexAttribPointer(this.program.attributes["aVertexPosition"], 3, this.gl.FLOAT, false, this.elementSize, 0);
1144
+ this.gl.vertexAttribPointer(this.program.attributes["aVertexNormal"], 3, this.gl.FLOAT, false, this.elementSize, this.attribSizes[0]);
1145
+ this.gl.vertexAttribPointer(this.program.attributes["aVertexColour"], 4, this.gl.UNSIGNED_BYTE, true, this.elementSize, this.attribSizes[0]+this.attribSizes[1]);
1146
+ this.gl.vertexAttribPointer(this.program.attributes["aVertexTexCoord"], 2, this.gl.FLOAT, true, this.elementSize, this.attribSizes[0]+this.attribSizes[1]+this.attribSizes[2]);
1147
+
1148
+ //Set uniforms...
1149
+ //this.gl.enable(this.gl.CULL_FACE);
1150
+ //this.gl.cullFace(this.gl.BACK_FACE);
1151
+
1152
+ //Texture -- TODO: Switch per object!
1153
+ //this.gl.bindTexture(this.gl.TEXTURE_2D, this.viewer.webgl.textures[0]);
1154
+ if (vis.objects[0].tex) {
1155
+ this.gl.activeTexture(this.gl.TEXTURE0);
1156
+ this.gl.bindTexture(this.gl.TEXTURE_2D, vis.objects[0].tex);
1157
+ this.gl.uniform1i(this.program.uniforms["uTexture"], 0);
1158
+ this.gl.uniform1i(this.program.uniforms["uTextured"], 1); //Disabled unless texture attached!
1159
+ } else {
1160
+ this.gl.uniform1i(this.program.uniforms["uTextured"], 0); //Disabled unless texture attached!
1161
+ }
1162
+
1163
+ this.gl.uniform1i(this.program.uniforms["uCalcNormal"], 0);
1164
+
1165
+ //Draw triangles
1166
+ this.gl.drawElements(this.gl.TRIANGLES, this.elements, this.gl.UNSIGNED_INT, 0);
1167
+ //this.gl.drawElements(this.gl.TRIANGLES, this.positions.length*3, this.gl.UNSIGNED_SHORT, 0);
1168
+ //this.gl.drawArrays(this.gl.TRIANGLES, 0, this.positions.length*3);
1169
+ desc = (this.elements / 3) + " triangles";
1170
+
1171
+ } else if (this.border) {
1172
+ this.gl.lineWidth(vis.properties.border >= 0 ? vis.properties.border : 1.0);
1173
+
1174
+ this.gl.vertexAttribPointer(this.program.attributes["aVertexPosition"], 3, this.gl.FLOAT, false, 0, 0);
1175
+ this.gl.vertexAttribPointer(this.program.attributes["aVertexColour"], 4, this.gl.UNSIGNED_BYTE, true, 0, 0);
1176
+ this.gl.drawElements(this.gl.LINES, this.elements, this.gl.UNSIGNED_SHORT, 0);
1177
+ desc = "border";
1178
+
1179
+ } else if (this.type == "lines") {
1180
+
1181
+ //Default line width (TODO: per object settings, using first object only here)
1182
+ this.gl.lineWidth(vis.objects[0].linewidth || 1.0);
1183
+
1184
+ this.gl.vertexAttribPointer(this.program.attributes["aVertexPosition"], 3, this.gl.FLOAT, false, this.elementSize, 0);
1185
+ this.gl.vertexAttribPointer(this.program.attributes["aVertexColour"], 4, this.gl.UNSIGNED_BYTE, true, this.elementSize, this.attribSizes[0]);
1186
+
1187
+ //Draw lines
1188
+ this.gl.drawElements(this.gl.LINES, this.elements, this.gl.UNSIGNED_INT, 0);
1189
+ desc = (this.elements / 2) + " lines";
1190
+ //this.gl.drawArrays(this.gl.LINES, 0, this.positions.length);
1191
+
1192
+ } else if (this.type == "volume") {
1193
+
1194
+ //Volume render
1195
+ this.viewer.webgl.initDraw2d(); //Prep for two-triangle render
1196
+
1197
+ //Premultiplied alpha blending
1198
+ this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
1199
+
1200
+ if (this.viewer.gradient.mapid != vis.objects[this.id].colourmap) {
1201
+ //Map selected has changed, so update texture (used on initial render only)
1202
+ var obj = {};
1203
+ obj.viewer = this.viewer;
1204
+ paletteUpdate(obj, vis.objects[this.id].colourmap);
1205
+ }
1206
+
1207
+ //Setup volume camera
1208
+ this.viewer.webgl.modelView.push();
1209
+ this.viewer.webgl.projection.push();
1210
+ {
1211
+ var props = vis.objects[this.id];
1212
+ var scaling = vis.objects[this.id].volume["scale"];
1213
+ /*/Apply translation to origin, any rotation and scaling
1214
+ this.viewer.webgl.modelView.identity()
1215
+ this.viewer.webgl.modelView.translate(this.viewer.translate)
1216
+
1217
+ // rotate model
1218
+ var rotmat = quat4.toMat4(this.viewer.rotate);
1219
+ this.viewer.webgl.modelView.mult(rotmat);*/
1220
+
1221
+ //TODO: get object rotation & translation and apply them (pass to apply?)
1222
+ //Apply the default camera
1223
+ //this.viewer.webgl.apply(viewer, rot, trans);
1224
+
1225
+ //console.log("DIMS: " + (scaling) + " TRANS: " + [-scaling[0]*0.5, -scaling[1]*0.5, -scaling[2]*0.5] + " SCALE: " + [1.0/scaling[0], 1.0/scaling[1], 1.0/scaling[2]]);
1226
+ //For a volume cube other than [0,0,0] - [1,1,1], need to translate/scale here...
1227
+ //this.viewer.webgl.modelView.translate([-scaling[0]*0.5, -scaling[1]*0.5, -scaling[2]*0.5]); //Translate to origin
1228
+ //this.viewer.webgl.modelView.translate([-0.5, -0.5, -0.5]); //Translate to origin
1229
+
1230
+ //Apply any corner offset translation here
1231
+ var minv = vis.objects[this.id].min;
1232
+ this.viewer.webgl.modelView.translate(minv); //Translate to origin
1233
+ //this.viewer.webgl.modelView.translate([-minv[0], -minv[1], -minv[2]]); //Translate to origin
1234
+
1235
+ //Get the mvMatrix scaled by volume size
1236
+ //(used for depth calculations)
1237
+ var matrix = mat4.create(this.viewer.webgl.modelView.matrix);
1238
+ mat4.scale(matrix, [scaling[0], scaling[1], scaling[2]]);
1239
+
1240
+ //Inverse of scaling
1241
+ this.viewer.webgl.modelView.scale([this.iscale[0], this.iscale[1], this.iscale[2]]);
1242
+
1243
+ //Send the normal matrix now
1244
+ this.viewer.webgl.setNormalMatrix();
1245
+
1246
+ //Perspective matrix
1247
+ //this.viewer.webgl.setPerspective(this.viewer.fov, this.viewer.webgl.viewport.width / this.viewer.webgl.viewport.height, this.viewer.near_clip, this.viewer.far_clip);
1248
+ //Apply model scaling, inverse squared
1249
+ this.viewer.webgl.modelView.scale([1.0/(this.viewer.scale[0]*this.viewer.scale[0]), 1.0/(this.viewer.scale[1]*this.viewer.scale[1]), 1.0/(this.viewer.scale[2]*this.viewer.scale[2])]);
1250
+
1251
+ //Send default matrices now
1252
+ //this.viewer.webgl.setMatrices(); //(can't use this as will overwrite our normal matrix)
1253
+ //Model view matrix
1254
+ this.gl.uniformMatrix4fv(this.viewer.webgl.program.mvMatrixUniform, false, this.viewer.webgl.modelView.matrix);
1255
+ //Perspective projection matrix
1256
+ this.gl.uniformMatrix4fv(this.viewer.webgl.program.pMatrixUniform, false, this.viewer.webgl.projection.matrix);
1257
+
1258
+ //Combined ModelView * Projection
1259
+ var MVPMatrix = mat4.create(this.viewer.webgl.projection.matrix);
1260
+ mat4.multiply(MVPMatrix, matrix);
1261
+
1262
+ //Transpose of model view
1263
+ var tMVMatrix = mat4.create(this.viewer.webgl.modelView.matrix);
1264
+ mat4.transpose(tMVMatrix);
1265
+
1266
+ //Combined InverseProjection * InverseModelView
1267
+ var invPMatrix = mat4.create(this.viewer.webgl.projection.matrix);
1268
+ mat4.inverse(invPMatrix);
1269
+ var invMVPMatrix = mat4.create(this.viewer.webgl.modelView.matrix);
1270
+ mat4.transpose(invMVPMatrix);
1271
+ mat4.multiply(invMVPMatrix, invPMatrix);
1272
+ }
1273
+ this.viewer.webgl.modelView.pop();
1274
+
1275
+ this.gl.activeTexture(this.gl.TEXTURE0);
1276
+ this.gl.bindTexture(this.gl.TEXTURE_2D, this.viewer.webgl.textures[0]);
1277
+
1278
+ this.gl.activeTexture(this.gl.TEXTURE1);
1279
+ this.gl.bindTexture(this.gl.TEXTURE_2D, this.viewer.webgl.gradientTexture);
1280
+
1281
+ //Only render full quality when not interacting
1282
+ //this.gl.uniform1i(this.program.uniforms["uSamples"], this.samples);
1283
+ //TODO: better default handling here - get default values from the properties dict, needs to be exported to a json file
1284
+ this.gl.uniform1i(this.program.uniforms["uSamples"], props.samples || 256);
1285
+ this.gl.uniform1i(this.program.uniforms["uVolume"], 0);
1286
+ this.gl.uniform1i(this.program.uniforms["uTransferFunction"], 1);
1287
+ this.gl.uniform1i(this.program.uniforms["uEnableColour"], props.usecolourmap || 1);
1288
+ this.gl.uniform1i(this.program.uniforms["uFilter"], props.tricubicfilter || 0);
1289
+ this.gl.uniform4fv(this.program.uniforms["uViewport"], this.viewer.webgl.viewport.array);
1290
+
1291
+ var bbmin = [props.xmin || 0.0, props.ymin || 0.0, props.zmin || 0.0];
1292
+ var bbmax = [props.xmax || 1.0, props.ymax || 1.0, props.zmax || 1.0];
1293
+ this.gl.uniform3fv(this.program.uniforms["uBBMin"], new Float32Array(bbmin));
1294
+ this.gl.uniform3fv(this.program.uniforms["uBBMax"], new Float32Array(bbmax));
1295
+ this.gl.uniform3fv(this.program.uniforms["uResolution"], new Float32Array(vis.objects[this.id].volume["res"]));
1296
+
1297
+ this.gl.uniform1f(this.program.uniforms["uDensityFactor"], (props.density != undefined ? props.density : 5));
1298
+ //Per object settings
1299
+ this.gl.uniform1f(this.program.uniforms["uOpacity"], this.getprop('opacity')); //TODO: fix per object settings
1300
+ // brightness and contrast
1301
+ this.gl.uniform1f(this.program.uniforms["uSaturation"], props.saturation || 1.0);
1302
+ this.gl.uniform1f(this.program.uniforms["uBrightness"], props.brightness || 0.0);
1303
+ this.gl.uniform1f(this.program.uniforms["uContrast"], props.contrast || 1.0);
1304
+ this.gl.uniform1f(this.program.uniforms["uPower"], props.power || 1.0);
1305
+ this.gl.uniform1f(this.program.uniforms["uBloom"], props.bloom || 0.0);
1306
+ //Density clip range
1307
+ var dclip = [vis.objects[this.id].minclip || 0.0, vis.objects[this.id].maxclip || 1.0];
1308
+ this.gl.uniform2fv(this.program.uniforms["uDenMinMax"], new Float32Array(dclip));
1309
+
1310
+ //Data value range for isoValue etc
1311
+ var range = new Float32Array([0.0, 1.0]);
1312
+ var isovalue = props.isovalue;
1313
+ var isoalpha = props.isoalpha;
1314
+ if (isoalpha == undefined) isoalpha = 1.0;
1315
+ if (props.isovalue != undefined) {
1316
+ if (vis.objects[this.id]["volume"].minimum != undefined && vis.objects[this.id]["volume"].maximum != undefined) {
1317
+ var r = [vis.objects[this.id]["volume"].minimum, vis.objects[this.id]["volume"].maximum];
1318
+ //Normalise isovalue to range [0,1] to match data (non-float textures always used in WebGL)
1319
+ isovalue = (isovalue - r[0]) / (r[1] - r[0]);
1320
+ //This is used in shader to normalize data to [0,1] when using float textures
1321
+ //range[0] = r[0];
1322
+ //range[1] = r[1];
1323
+ }
1324
+ } else {
1325
+ isoalpha = 0.0;
1326
+ }
1327
+
1328
+ this.gl.uniform2fv(this.program.uniforms["uRange"], range);
1329
+ this.gl.uniform1f(this.program.uniforms["uIsoValue"], isovalue);
1330
+
1331
+ var colour = new Colour(props.colour);
1332
+ colour.alpha = isoalpha;
1333
+ this.gl.uniform4fv(this.program.uniforms["uIsoColour"], colour.rgbaGL());
1334
+
1335
+ this.gl.uniform1f(this.program.uniforms["uIsoSmooth"], props.isosmooth || 0.1);
1336
+ this.gl.uniform1i(this.program.uniforms["uIsoWalls"], props.isowalls != undefined ? props.isowalls : 1.0);
1337
+
1338
+ //Draw two triangles
1339
+ this.gl.uniformMatrix4fv(this.program.uniforms["uTMVMatrix"], false, tMVMatrix);
1340
+ this.gl.uniformMatrix4fv(this.program.uniforms["uInvMVPMatrix"], false, invMVPMatrix);
1341
+ this.gl.uniformMatrix4fv(this.program.uniforms["uMVPMatrix"], false, MVPMatrix);
1342
+ this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, this.viewer.webgl.vertexPositionBuffer.numItems);
1343
+
1344
+ //Restore default blending
1345
+ this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
1346
+ }
1347
+
1348
+ //Disable attribs
1349
+ for (var key in this.program.attributes)
1350
+ this.gl.disableVertexAttribArray(this.program.attributes[key]);
1351
+
1352
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
1353
+ this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null);
1354
+ this.gl.useProgram(null);
1355
+
1356
+ var time = (new Date() - start) / 1000.0;
1357
+ if (time > 0.01) DEBUG && console.log(time + " seconds to draw " + desc);
1358
+ }
1359
+
1360
+ function minMaxDist()
1361
+ {
1362
+ //Calculate min/max distances from view plane
1363
+ var M2 = [this.viewer.webgl.modelView.matrix[0*4+2],
1364
+ this.viewer.webgl.modelView.matrix[1*4+2],
1365
+ this.viewer.webgl.modelView.matrix[2*4+2],
1366
+ this.viewer.webgl.modelView.matrix[3*4+2]];
1367
+ var maxdist = -Number.MAX_VALUE, mindist = Number.MAX_VALUE;
1368
+ for (i=0; i<2; i++)
1369
+ {
1370
+ var x = i==0 ? this.viewer.view.min[0] : this.viewer.view.max[0];
1371
+ for (var j=0; j<2; j++)
1372
+ {
1373
+ var y = j==0 ? this.viewer.view.min[1] : this.viewer.view.max[1];
1374
+ for (var k=0; k<2; k++)
1375
+ {
1376
+ var z = k==0 ? this.viewer.view.min[2] : this.viewer.view.max[2];
1377
+ var dist = eyeDistance(M2, [x, y, z]);
1378
+ if (dist < mindist) mindist = dist;
1379
+ if (dist > maxdist) maxdist = dist;
1380
+ }
1381
+ }
1382
+ }
1383
+ //alert(mindist + " --> " + maxdist);
1384
+ if (maxdist == mindist) maxdist += 0.0000001;
1385
+ return [mindist, maxdist];
1386
+ }
1387
+
1388
+ //This object holds the viewer details and calls the renderers
1389
+ function Viewer(canvas) {
1390
+ this.vis = {};
1391
+ this.canvas = canvas;
1392
+ if (canvas)
1393
+ {
1394
+ try {
1395
+ this.webgl = new WebGL(this.canvas, {antialias: true, premultipliedAlpha: false});
1396
+ this.gl = this.webgl.gl;
1397
+ this.ext = (
1398
+ this.gl.getExtension('OES_element_index_uint') ||
1399
+ this.gl.getExtension('MOZ_OES_element_index_uint') ||
1400
+ this.gl.getExtension('WEBKIT_OES_element_index_uint')
1401
+ );
1402
+ this.gl.getExtension('OES_standard_derivatives');
1403
+ } catch(e) {
1404
+ //No WebGL
1405
+ DEBUG && console.log(e);
1406
+ if (!this.webgl) document.getElementById('canvas').style.display = 'none';
1407
+ }
1408
+ }
1409
+
1410
+ //Default colour editor
1411
+ this.gradient = new GradientEditor(document.getElementById('palette'), paletteUpdate);
1412
+ this.gradient.viewer = this;
1413
+
1414
+ this.width = 0; //Auto resize
1415
+ this.height = 0;
1416
+
1417
+ this.rotating = false;
1418
+ this.translate = [0,0,0];
1419
+ this.rotate = quat4.create();
1420
+ quat4.identity(this.rotate);
1421
+ this.focus = [0,0,0];
1422
+ this.centre = [0,0,0];
1423
+ this.fov = 45.0;
1424
+ this.near_clip = this.far_clip = 0.0;
1425
+ this.modelsize = 1;
1426
+ this.scale = [1, 1, 1];
1427
+ this.orientation = 1.0; //1.0 for RH, -1.0 for LH
1428
+ this.background = new Colour(0xff404040);
1429
+ this.canvas.parentElement.style.background = this.background.html();
1430
+ this.pointScale = 1.0;
1431
+ this.pointType = 0;
1432
+
1433
+ //Non-persistant settings
1434
+ this.border = 0;
1435
+ this.mode = 'Rotate';
1436
+
1437
+ //Create the renderers
1438
+ this.renderers = [];
1439
+ if (this.gl) {
1440
+ this.points = new Renderer(this, 'points');
1441
+ this.triangles = new Renderer(this, 'triangles');
1442
+ this.lines = new Renderer(this, 'lines');
1443
+ this.border = new Renderer(this, 'lines', 0xffffffff, true);
1444
+
1445
+ //Defines draw order
1446
+ this.renderers.push(this.triangles);
1447
+ this.renderers.push(this.points);
1448
+ this.renderers.push(this.lines);
1449
+ this.renderers.push(this.border);
1450
+
1451
+ this.gl.enable(this.gl.DEPTH_TEST);
1452
+ this.gl.depthFunc(this.gl.LEQUAL);
1453
+ //this.gl.depthMask(this.gl.FALSE);
1454
+ this.gl.enable(this.gl.BLEND);
1455
+ //this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
1456
+ //this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ZERO, this.gl.ONE);
1457
+ this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
1458
+ }
1459
+ }
1460
+
1461
+ Viewer.prototype.loadFile = function(source) {
1462
+ //Skip update to rotate/translate etc if in process of updating
1463
+ //console.log(source);
1464
+ if (document.mouse && document.mouse.isdown) return;
1465
+ var start = new Date();
1466
+ var updated = true;
1467
+ try {
1468
+ if (typeof source != 'object') {
1469
+ if (source.indexOf("{") < 0) {
1470
+ if (server) {
1471
+ //Not a json string, assume a command script
1472
+ var lines = source.split("\n");
1473
+ for (var line in lines) {
1474
+ sendCommand('' + lines[line]);
1475
+ }
1476
+ return;
1477
+ } else {
1478
+ console.log(source);
1479
+ alert("Invalid data");
1480
+ return;
1481
+ }
1482
+ } else if (source.substr(8) == "loadData") {
1483
+ //jsonp, strip function call when loaded from FileReader
1484
+ source = source.substring(10, source.lastIndexOf("\n"));
1485
+ }
1486
+ source = JSON.parse(source);
1487
+ }
1488
+ } catch(e) {
1489
+ console.log(source);
1490
+ alert(e);
1491
+ return;
1492
+ }
1493
+ var time = (new Date() - start) / 1000.0;
1494
+ DEBUG && console.log(time + " seconds to parse data");
1495
+
1496
+ if (source.exported) {
1497
+ if (!this.vis.views && !this.view) {DEBUG && console.log("Exported settings require loaded model"); return;}
1498
+ var old = this.toString();
1499
+ //Copy, overwriting if exists in source
1500
+ if (source.views[0].rotate) this.vis.views[0].rotate = source.views[0].rotate;
1501
+ //Merge keys - preserves original objects for gui access
1502
+ Merge(this.vis, source);
1503
+ if (!source.reload) updated = false; //No reload necessary
1504
+ } else {
1505
+ //Clear geometry
1506
+ for (var r in this.renderers)
1507
+ this.renderers[r].elements = 0;
1508
+
1509
+ //Replace
1510
+ //Merge keys - preserves original objects for gui access
1511
+ Merge(this.vis, source);
1512
+ //this.vis = source;
1513
+
1514
+ if (!this.vis.colourmaps) this.vis.colourmaps = [];
1515
+ }
1516
+
1517
+ var vis = this.vis;
1518
+ if (!vis.properties) vis.properties = {}; //Ensure valid object even if no properties
1519
+
1520
+ //Set active view (always first for now)
1521
+ this.view = vis.views[0];
1522
+
1523
+ //Always set a bounding box, get from objects if not in view
1524
+ var objbb = false;
1525
+ if (!this.view.min || !this.view.max) {
1526
+ this.view.min = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE];
1527
+ this.view.max = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE];
1528
+ objbb = true;
1529
+ }
1530
+ //console.log(JSON.stringify(this.view));
1531
+
1532
+ //Load some user options...
1533
+ loadColourMaps(this.vis);
1534
+
1535
+ if (this.view) {
1536
+ this.fov = this.view.fov || 45;
1537
+ this.near_clip = this.view.near || 0;
1538
+ this.far_clip = this.view.far || 0;
1539
+ this.orientation = this.view.orientation || 1;
1540
+ //this.axes = this.view.axis == undefined ? true : this.view.axis;
1541
+ }
1542
+
1543
+ this.pointScale = vis.properties.scalepoints || 1.0;
1544
+ this.pointType = vis.properties.pointtype; // > 0 ? vis.properties.pointtype : 0;
1545
+
1546
+ this.applyBackground(vis.properties.background);
1547
+
1548
+ if (this.canvas && vis.properties.resolution && vis.properties.resolution[0] && vis.properties.resolution[1]) {
1549
+ this.width = vis.properties.resolution[0];
1550
+ this.height = vis.properties.resolution[1];
1551
+ this.canvas.style.width = "";
1552
+ this.canvas.style.height = "";
1553
+ }
1554
+
1555
+ //Decode into Float buffers and replace original base64 data
1556
+ //-Colour lookups: do in shader with a texture?
1557
+ //-Sizes don't change at per-particle level here, per-object? send id and calc size in shader? (short:id + short:size = 4 bytes)
1558
+
1559
+ //min = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE];
1560
+ //max = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE];
1561
+
1562
+ //Process object data and convert base64 to Float32Array
1563
+ if (!source.exported) this.vertexCount = 0;
1564
+ for (var id in vis.objects) {
1565
+ //Opacity bug fix hack
1566
+ if (!vis.objects[id]['opacity']) vis.objects[id]['opacity'] = 1.0;
1567
+ var name = vis.objects[id].name;
1568
+ //Process points/triangles
1569
+ if (!source.exported) {
1570
+ //Set default colour if not yet set
1571
+ if (!vis.objects[id].colour) {
1572
+ if ("volume" in vis.objects[id]) //Use a different default for volume (isosurface) colour
1573
+ vis.objects[id].colour = [220, 220, 200, 255];
1574
+ else
1575
+ vis.objects[id].colour = this.dict["colour"].default;
1576
+ }
1577
+
1578
+ for (var type in vis.objects[id]) {
1579
+ if (["triangles", "points", "lines", "volume"].indexOf(type) < 0) continue;
1580
+ if (type == "triangles") this.hasTriangles = true;
1581
+ if (type == "points") this.hasPoints = true;
1582
+ if (type == "lines") this.hasLines = true;
1583
+
1584
+ if (type == "volume" && vis.objects[id][type].url) {
1585
+ //Single volume per object only, each volume needs own renderer
1586
+ this.hasVolumes = true;
1587
+ var vren = new Renderer(this, 'volume');
1588
+ vren.id = id;
1589
+ this.renderers.push(vren);
1590
+ vren.image = new Image();
1591
+ vren.image.src = vis.objects[id][type].url;
1592
+ var viewer = this;
1593
+ vren.image.onload = function(){ viewer.drawFrame(); viewer.draw(); };
1594
+ }
1595
+
1596
+ //Apply object bounding box
1597
+ if (objbb && vis.objects[id].min)
1598
+ this.checkPointMinMax(vis.objects[id].min);
1599
+ if (objbb && vis.objects[id].max)
1600
+ this.checkPointMinMax(vis.objects[id].max);
1601
+
1602
+ //Read vertices, values, normals, sizes, etc...
1603
+ for (var idx in vis.objects[id][type]) {
1604
+ //Only support following data types for now
1605
+ decodeBase64(vis.objects[id][type][idx], 'vertices');
1606
+ decodeBase64(vis.objects[id][type][idx], 'values');
1607
+ decodeBase64(vis.objects[id][type][idx], 'normals');
1608
+ decodeBase64(vis.objects[id][type][idx], 'texcoords');
1609
+ decodeBase64(vis.objects[id][type][idx], 'colours', 'integer');
1610
+ decodeBase64(vis.objects[id][type][idx], 'sizes');
1611
+ decodeBase64(vis.objects[id][type][idx], 'indices', 'integer');
1612
+
1613
+ if (vis.objects[id][type][idx].vertices) {
1614
+ DEBUG && console.log("Loaded " + vis.objects[id][type][idx].vertices.data.length/3 + " vertices from " + name);
1615
+ this.vertexCount += vis.objects[id][type][idx].vertices.data.length/3;
1616
+ }
1617
+
1618
+ //Create indices for cross-sections & lines
1619
+ if (type == 'lines' && !vis.objects[id][type][idx].indices) {
1620
+ //Collect indices
1621
+ var verts = vis.objects[id][type][idx].vertices.data;
1622
+ var buf = new Uint32Array(verts.length);
1623
+ for (var j=0; j < verts.length; j++) //Iterate over h-1 strips
1624
+ buf[j] = j;
1625
+ vis.objects[id][type][idx].indices = {"data" : buf, "type" : "integer"};
1626
+ }
1627
+ if (type == 'triangles' && !vis.objects[id][type][idx].indices) {
1628
+ //Collect indices
1629
+ var h = vis.objects[id][type][idx].height;
1630
+ var w = vis.objects[id][type][idx].width;
1631
+ DEBUG && console.log("GRID w : " + w + " , h : " + h);
1632
+ var buf = new Uint32Array((w-1)*(h-1)*6);
1633
+ var i = 0;
1634
+ for (var j=0; j < h-1; j++) //Iterate over h-1 strips
1635
+ {
1636
+ var offset0 = j * w;
1637
+ var offset1 = (j+1) * w;
1638
+ for (var k=0; k < w-1; k++) //Iterate width of strips, w vertices
1639
+ {
1640
+ //Tri 1
1641
+ buf[i++] = offset0 + k;
1642
+ buf[i++] = offset1 + k;
1643
+ buf[i++] = offset0 + k + 1;
1644
+ //Tri 2
1645
+ buf[i++] = offset1 + k;
1646
+ buf[i++] = offset0 + k + 1;
1647
+ buf[i++] = offset1 + k + 1;
1648
+ }
1649
+ }
1650
+
1651
+ vis.objects[id][type][idx].indices = {"data" : buf, "type" : "integer"};
1652
+ /*/Get center point for depth sort...
1653
+ var verts = vis.objects[id][type][idx].vertices.data;
1654
+ vis.objects[id][type][idx].centroids = [null];
1655
+ vis.objects[id][type][idx].centroids =
1656
+ [[
1657
+ (verts[0]+verts[verts.length-3])/2,
1658
+ (verts[1]+verts[verts.length-2])/2,
1659
+ (verts[2]+verts[verts.length-1])/2
1660
+ ]];*/
1661
+ }
1662
+ }
1663
+ }
1664
+ }
1665
+
1666
+ this.updateDims(this.view);
1667
+ }
1668
+ var time = (new Date() - start) / 1000.0;
1669
+ DEBUG && console.log(time + " seconds to import data");
1670
+
1671
+ //Load texture images
1672
+ this.loadTextures();
1673
+
1674
+ //Defaults for interaction flags
1675
+ //(Default to interactive render if vertex count < 0.5 M)
1676
+ if (vis.interactive == undefined) vis.interactive = (this.vertexCount <= 500000);
1677
+ if (vis.immediatesort == undefined) vis.immediatesort = false;
1678
+ if (vis.sortenabled == undefined) vis.sortenabled = true;
1679
+
1680
+ //TODO: loaded custom shader is not replaced by default when new model loaded...
1681
+ for (var r in this.renderers) {
1682
+ var ren = this.renderers[r];
1683
+ //Custom shader load
1684
+ if (vis.shaders && vis.shaders[ren.type])
1685
+ ren.init();
1686
+ //Set update flags
1687
+ ren.sort = ren.reload = updated;
1688
+ }
1689
+
1690
+ //Defer drawing until textures loaded if necessary
1691
+ if (!this.hasVolumes && !this.hasTexture) {
1692
+ this.drawFrame();
1693
+ this.draw();
1694
+ }
1695
+
1696
+ //Create UI - disable by omitting dat.gui.min.js
1697
+ var viewer = this;
1698
+ var changefn = function(value) {
1699
+ viewer.reload();
1700
+ viewer.draw();
1701
+ };
1702
+
1703
+ //Create GUI menu if enabled
1704
+ if (this.menu)
1705
+ createMenu(this, changefn, 1);
1706
+ }
1707
+
1708
+ Viewer.prototype.loadTextures = function() {
1709
+ //Load/reload textures for all objects
1710
+ var vis = this.vis;
1711
+ for (var id in vis.objects) {
1712
+ //Texure loading
1713
+ if (vis.objects[id].texture) {
1714
+ this.hasTexture = true;
1715
+ vis.objects[id].image = new Image();
1716
+ vis.objects[id].image.crossOrigin = "anonymous";
1717
+ vis.objects[id].image.src = vis.objects[id].texture + '?_' + new Date().getTime(); //Prevent caching
1718
+ var viewer = this;
1719
+ var i = id; //Variable for closure
1720
+ vis.objects[id].image.onload = function() {
1721
+ // Flip the image's Y axis to match the WebGL texture coordinate space.
1722
+ viewer.webgl.gl.activeTexture(viewer.webgl.gl.TEXTURE0);
1723
+ //Flip jpegs
1724
+ var flip = vis.objects[i].image.src.slice(0,1024).toLowerCase().indexOf('png') < 0;
1725
+ vis.objects[i].tex = viewer.webgl.loadTexture(vis.objects[i].image, viewer.gl.LINEAR, viewer.gl.RGBA, flip);
1726
+ viewer.drawFrame();
1727
+ viewer.draw();
1728
+ };
1729
+ }
1730
+ }
1731
+ }
1732
+
1733
+ Viewer.prototype.reload = function() {
1734
+ for (var r in this.renderers) {
1735
+ //Set update flags
1736
+ var ren = this.renderers[r];
1737
+ ren.sort = ren.reload = true;
1738
+ }
1739
+
1740
+ this.applyBackground(this.vis.properties.background);
1741
+ }
1742
+
1743
+ Viewer.prototype.checkPointMinMax = function(coord) {
1744
+ for (var i=0; i<3; i++) {
1745
+ this.view.min[i] = Math.min(coord[i], this.view.min[i]);
1746
+ this.view.max[i] = Math.max(coord[i], this.view.max[i]);
1747
+ }
1748
+ //console.log(JSON.stringify(this.view.min) + " -- " + JSON.stringify(this.view.max));
1749
+ }
1750
+
1751
+ function Merge(obj1, obj2) {
1752
+ for (var p in obj2) {
1753
+ try {
1754
+ //alert(p + " ==> " + obj2[p].constructor);
1755
+ // Property in destination object set; update its value.
1756
+ if (obj2[p].constructor == Object || obj2[p].constructor == Array) {
1757
+ obj1[p] = Merge(obj1[p], obj2[p]);
1758
+ } else {
1759
+ obj1[p] = obj2[p];
1760
+ }
1761
+ } catch(e) {
1762
+ // Property in destination object not set; create it and set its value.
1763
+ obj1[p] = obj2[p];
1764
+ }
1765
+ }
1766
+ return obj1;
1767
+ }
1768
+
1769
+ Viewer.prototype.toString = function(nocam, reload) {
1770
+ var exp = {"objects" : this.exportObjects(),
1771
+ "colourmaps" : this.exportColourMaps(),
1772
+ "views" : this.exportView(nocam),
1773
+ "properties" : this.vis.properties};
1774
+
1775
+ exp.exported = true;
1776
+ exp.reload = reload ? true : false;
1777
+
1778
+ if (nocam) return JSON.stringify(exp);
1779
+ //Export with 2 space indentation
1780
+ return JSON.stringify(exp, undefined, 2);
1781
+ }
1782
+
1783
+ Viewer.prototype.exportView = function(nocam) {
1784
+ //Update camera settings of current view
1785
+ if (nocam)
1786
+ this.view = {};
1787
+ else {
1788
+ this.view.rotate = this.getRotation();
1789
+ this.view.focus = this.focus;
1790
+ this.view.translate = this.translate;
1791
+ this.view.scale = this.scale;
1792
+ }
1793
+ this.view.fov = this.fov;
1794
+ this.view.near = this.near_clip;
1795
+ this.view.far = this.far_clip;
1796
+ //this.properties.border = this.border ? 1 : 0;
1797
+ //this.properties.axis = this.axes;
1798
+ //this.view.background = this.background.toString();
1799
+
1800
+ return [this.view];
1801
+ }
1802
+
1803
+ Viewer.prototype.exportObjects = function() {
1804
+ objs = [];
1805
+ for (var id in this.vis.objects) {
1806
+ objs[id] = {};
1807
+ //Skip geometry data
1808
+ for (var type in this.vis.objects[id]) {
1809
+ if (type != "triangles" && type != "points" && type != 'lines' && type != "volume") {
1810
+ objs[id][type] = this.vis.objects[id][type];
1811
+ }
1812
+ }
1813
+ }
1814
+ return objs;
1815
+ }
1816
+
1817
+ Viewer.prototype.exportColourMaps = function() {
1818
+ cmaps = [];
1819
+ if (this.vis.colourmaps) {
1820
+ for (var i=0; i<this.vis.colourmaps.length; i++) {
1821
+ cmaps[i] = this.vis.colourmaps[i].palette.get();
1822
+ //Copy additional properties
1823
+ for (var type in this.vis.colourmaps[i]) {
1824
+ if (type != "palette" && type != "colours")
1825
+ cmaps[i][type] = this.vis.colourmaps[i][type];
1826
+ }
1827
+ }
1828
+ }
1829
+ return cmaps;
1830
+ }
1831
+
1832
+ Viewer.prototype.exportFile = function() {
1833
+ window.open('data:text/plain;base64,' + window.btoa(this.toString(false, true)));
1834
+ }
1835
+
1836
+ Viewer.prototype.applyBackground = function(bg) {
1837
+ if (!bg) return;
1838
+ this.background = new Colour(bg);
1839
+ var hsv = this.background.HSV();
1840
+ if (this.border) this.border.colour = hsv.V > 50 ? new Colour(0xff444444) : new Colour(0xffbbbbbb);
1841
+ this.canvas.parentElement.style.background = this.background.html();
1842
+ }
1843
+
1844
+ Viewer.prototype.addColourMap = function() {
1845
+ var properties = this.vis.properties;
1846
+ if (properties.id == undefined) return;
1847
+ var name = prompt("Enter colourmap name:");
1848
+ //New colourmap on active object
1849
+ if (server)
1850
+ sendCommand("colourmap " + properties.id + " add");
1851
+
1852
+ var newmap = {
1853
+ "name": name,
1854
+ "range": [0, 1], "logscale": 0,
1855
+ "colours": [{"position": 0, "colour": "rgba(0,0,0,0)"},{"position": 1,"colour": "rgba(255,255,255,1)"}
1856
+ ]
1857
+ }
1858
+ this.vis.colourmaps.push(newmap);
1859
+
1860
+ loadColourMaps(this.vis);
1861
+ }
1862
+
1863
+ Viewer.prototype.setColourMap = function(id) {
1864
+ var properties = this.vis.properties;
1865
+ if (properties.id == undefined) return;
1866
+ this.vis.objects[properties.id].colourmap = parseInt(id);
1867
+ if (id === undefined) id = -1;
1868
+ //Set new colourmap on active object
1869
+ if (id < 0) {
1870
+ document.getElementById('palette').style.display = 'none';
1871
+ document.getElementById('log').style.display = 'none';
1872
+ } else {
1873
+ //Draw palette UI
1874
+ document.getElementById('logscale').checked = this.vis.colourmaps[id].logscale;
1875
+ document.getElementById('log').style.display = 'block';
1876
+ document.getElementById('palette').style.display = 'block';
1877
+ this.gradient.palette = this.vis.colourmaps[id].palette;
1878
+ this.gradient.mapid = id; //Save the id
1879
+ this.gradient.update();
1880
+ }
1881
+ }
1882
+
1883
+ function decodeBase64(obj, datatype, format) {
1884
+ if (!format) format = 'float';
1885
+ if (!obj[datatype]) return null;
1886
+ var buf;
1887
+ if (typeof obj[datatype].data == 'string') {
1888
+ //Base64 encoded string
1889
+ var decoded = atob(obj[datatype].data);
1890
+ var buffer = new ArrayBuffer(decoded.length);
1891
+ var bufView = new Uint8Array(buffer);
1892
+ for (var i=0, strLen=decoded.length; i<strLen; i++) {
1893
+ bufView[i] = decoded.charCodeAt(i);
1894
+ }
1895
+ if (format == 'float')
1896
+ buf = new Float32Array(buffer);
1897
+ else
1898
+ buf = new Uint32Array(buffer);
1899
+ } else {
1900
+ //Literal array
1901
+ if (format == 'float')
1902
+ buf = new Float32Array(obj[datatype].data);
1903
+ else
1904
+ buf = new Uint32Array(obj[datatype].data);
1905
+ }
1906
+
1907
+ obj[datatype].data = buf;
1908
+ obj[datatype].type = format;
1909
+
1910
+ if (datatype == 'values') {
1911
+ if (!obj.values.minimum ||
1912
+ !obj.values.maximum) {
1913
+ //Value field max min
1914
+ var minval = Number.MAX_VALUE, maxval = -Number.MAX_VALUE;
1915
+ for (var i=0; i<buf.length; i++) {
1916
+ if (buf[i] > maxval)
1917
+ maxval = buf[i];
1918
+ if (buf[i] < minval)
1919
+ minval = buf[i];
1920
+ }
1921
+
1922
+ //Set from data where none provided...
1923
+ if (!obj.values.minimum)
1924
+ obj.values.minimum = minval;
1925
+ if (!obj.values.maximum)
1926
+ obj.values.maximum = maxval;
1927
+ }
1928
+ }
1929
+ }
1930
+
1931
+ function removeChildren(element) {
1932
+ if (element.hasChildNodes()) {
1933
+ while (element.childNodes.length > 0 )
1934
+ element.removeChild(element.firstChild);
1935
+ }
1936
+ }
1937
+
1938
+ Viewer.prototype.lookupMapId = function(id) {
1939
+ if (typeof(id) == 'string') {
1940
+ for (var i = 0; i < this.vis.colourmaps.length; i++) {
1941
+ if (this.vis.colourmaps[i].name == id) {
1942
+ return i;
1943
+ break;
1944
+ }
1945
+ }
1946
+ }
1947
+ return id;
1948
+ }
1949
+
1950
+ Viewer.prototype.lookupMap = function(id) {
1951
+ var mapid = this.lookupMapId(id);
1952
+ if (mapid >= 0)
1953
+ return this.vis.colourmaps[mapid];
1954
+ return null;
1955
+ }
1956
+
1957
+ paletteUpdate = function(obj, id) {
1958
+ //Convert name to index for now
1959
+ //console.log("paletteUpdate " + id + " : " + obj.name);
1960
+ viewer = obj.viewer;
1961
+ id = viewer.lookupMapId(id);
1962
+ if (id != undefined) viewer.gradient.mapid = id;
1963
+
1964
+ //Load colourmap change
1965
+ if (viewer.gradient.mapid < 0) return;
1966
+ var cmap = viewer.vis.colourmaps[viewer.gradient.mapid];
1967
+ if (!cmap) return;
1968
+
1969
+ paletteLoad(cmap.palette);
1970
+
1971
+ //Update colour data
1972
+ cmap.colours = cmap.palette.colours;
1973
+
1974
+ if (viewer.webgl) {
1975
+ var gradient = document.getElementById('gradient');
1976
+ viewer.webgl.updateTexture(viewer.webgl.gradientTexture, gradient, viewer.gl.TEXTURE1); //Use 2nd texture unit
1977
+ //window.open(gradient.toDataURL());
1978
+ }
1979
+ }
1980
+
1981
+ paletteLoad = function(palette) {
1982
+ //Update colours and cache
1983
+ var canvas = document.getElementById('palette');
1984
+ var gradient = document.getElementById('gradient');
1985
+ var context = gradient.getContext('2d');
1986
+ if (!context) alert("getContext failed");
1987
+
1988
+ //Get colour data and store in array
1989
+ //Redraw without UI elements
1990
+ //palette.draw(canvas, false);
1991
+ palette.draw(gradient, false);
1992
+ //Cache colour values
1993
+ var pixels = context.getImageData(0, 0, MAXIDX+1, 1).data;
1994
+
1995
+ palette.cache = [];
1996
+ for (var c=0; c<=MAXIDX; c++)
1997
+ palette.cache[c] = pixels[c*4] + (pixels[c*4+1] << 8) + (pixels[c*4+2] << 16) + (pixels[c*4+3] << 24);
1998
+
1999
+ //Redraw UI
2000
+ palette.draw(canvas, true);
2001
+ }
2002
+
2003
+ Viewer.prototype.drawTimed = function() {
2004
+ if (this.drawTimer)
2005
+ clearTimeout(this.drawTimer);
2006
+ var viewer = this;
2007
+ this.drawTimer = setTimeout(function () {viewer.drawFrame();}, 100 );
2008
+ }
2009
+
2010
+ Viewer.prototype.draw = function(borderOnly) {
2011
+ //If requested draw border only (used while interacting)
2012
+ //Draw the full model on a timer
2013
+ this.drawFrame(borderOnly);
2014
+ if (borderOnly && !server) this.drawTimed();
2015
+ }
2016
+
2017
+ Viewer.prototype.drawFrame = function(borderOnly) {
2018
+ if (!this.canvas || this.inVR) return;
2019
+
2020
+ if (server && !document.mouse.isdown && this.gl) {
2021
+ //Don't draw in server mode unless interacting (border view)
2022
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
2023
+ return;
2024
+ }
2025
+
2026
+ /*/Show screenshot while interacting or if using server
2027
+ //if (server || borderOnly)
2028
+ var frame = document.getElementById('frame');
2029
+ if (frame) {
2030
+ if (server) {
2031
+ frame.style.display = 'block';
2032
+ this.width = frame.offsetWidth;
2033
+ this.height = frame.offsetHeight;
2034
+ this.canvas.style.width = this.width + "px";
2035
+ this.canvas.style.height = this.height + "px";
2036
+ } else {
2037
+ frame.style.display = 'none';
2038
+ }
2039
+ }*/
2040
+
2041
+ if (!this.gl) return;
2042
+
2043
+ if (this.width == 0 || this.height == 0) {
2044
+ //Get size from window/container
2045
+ if (this.canvas.parentElement != document.body) {
2046
+ //Get from container
2047
+ this.width = this.canvas.parentElement.offsetWidth;
2048
+ this.height = this.canvas.parentElement.offsetHeight;
2049
+ //alert(this.canvas.parentElement.id + " ==> " + this.canvas.parentElement.offsetWidth + " : " + this.canvas.parentElement.clientWidth + " : " + this.canvas.parentElement.style.width);
2050
+ } else {
2051
+ this.width = window.innerWidth;
2052
+ this.height = window.innerHeight;
2053
+ }
2054
+ }
2055
+
2056
+ if (this.width != this.canvas.width || this.height != this.canvas.height) {
2057
+ this.canvas.width = this.canvas.clientWidth = this.width;
2058
+ this.canvas.height = this.canvas.clientHeight = this.height;
2059
+ this.canvas.setAttribute("width", this.width);
2060
+ this.canvas.setAttribute("height", this.height);
2061
+ this.webgl.viewport = new Viewport(0, 0, this.width, this.height);
2062
+ }
2063
+ this.width = this.height = 0;
2064
+
2065
+ var start = new Date();
2066
+
2067
+ this.gl.viewport(this.webgl.viewport.x, this.webgl.viewport.y, this.webgl.viewport.width, this.webgl.viewport.height);
2068
+ // console.log(JSON.stringify(this.webgl.viewport));
2069
+ //this.gl.clearColor(this.background.red/255, this.background.green/255, this.background.blue/255, server || document.mouse.isdown ? 0 : 1);
2070
+ this.gl.clearColor(this.background.red/255, this.background.green/255, this.background.blue/255, server ? 0 : 1);
2071
+ //this.gl.clearColor(this.background.red/255, this.background.green/255, this.background.blue/255, 0);
2072
+ //this.gl.clearColor(1, 1, 1, 0);
2073
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
2074
+
2075
+ //Apply the camera
2076
+ this.webgl.view(this);
2077
+
2078
+ //Render objects
2079
+ for (var r in this.renderers) {
2080
+ if (this.renderers[r].border) {
2081
+ if (!borderOnly && !this.vis.border) continue;
2082
+ } else {
2083
+ if (borderOnly) continue;
2084
+ }
2085
+
2086
+ this.renderers[r].draw();
2087
+ //console.log("Draw: " + r + " : " + this.renderers[r].type);
2088
+ }
2089
+
2090
+ /*/Save canvas image to background for display while interacting
2091
+ if (!borderOnly && !server) {
2092
+ if (document.mouse.isdown || !("frame").src) {
2093
+ //document.getElementById("frame").src = document.getElementById('canvas').toDataURL("image/png");
2094
+ //document.getElementById('canvas').toBlob(function (blob) {document.getElementById("frame").src = URL.createObjectURL(blob);});
2095
+ this.border.draw();
2096
+ }
2097
+ }*/
2098
+
2099
+ this.rotated = false; //Clear rotation flag
2100
+ }
2101
+
2102
+ Viewer.prototype.syncRotation = function() {
2103
+ this.rotated = true;
2104
+ this.draw();
2105
+ //if (this.command)
2106
+ // this.command('' + this.getRotationString());
2107
+ }
2108
+
2109
+ Viewer.prototype.rotateX = function(deg) {
2110
+ this.rotation(deg, [1,0,0]);
2111
+ }
2112
+
2113
+ Viewer.prototype.rotateY = function(deg) {
2114
+ this.rotation(deg, [0,1,0]);
2115
+ }
2116
+
2117
+ Viewer.prototype.rotateZ = function(deg) {
2118
+ this.rotation(deg, [0,0,1]);
2119
+ }
2120
+
2121
+ Viewer.prototype.rotation = function(deg, axis) {
2122
+ //Quaterion rotate
2123
+ var arad = deg * Math.PI / 180.0;
2124
+ var rotation = quat4.fromAngleAxis(arad, axis);
2125
+ rotation = quat4.normalize(rotation);
2126
+ this.rotate = quat4.multiply(rotation, this.rotate);
2127
+ }
2128
+
2129
+ Viewer.prototype.getRotation = function() {
2130
+ return [this.rotate[0], this.rotate[1], this.rotate[2], this.rotate[3]];
2131
+ }
2132
+
2133
+ Viewer.prototype.getRotationString = function() {
2134
+ //Return current rotation quaternion as string
2135
+ var q = this.getRotation();
2136
+ return 'rotation ' + q[0] + ' ' + q[1] + ' ' + q[2] + ' ' + q[3];
2137
+ }
2138
+
2139
+ Viewer.prototype.getTranslationString = function() {
2140
+ return 'translation ' + this.translate[0] + ' ' + this.translate[1] + ' ' + this.translate[2];
2141
+ }
2142
+
2143
+ Viewer.prototype.reset = function() {
2144
+ if (this.gl) {
2145
+ this.updateDims(this.view);
2146
+ this.draw();
2147
+ }
2148
+
2149
+ if (server) {
2150
+ //sendCommand('' + this.getRotationString());
2151
+ //sendCommand('' + this.getTranslationString());
2152
+ sendCommand('reset');
2153
+ }
2154
+ }
2155
+
2156
+ Viewer.prototype.zoom = function(factor) {
2157
+ if (this.gl) {
2158
+ var adj = factor * this.modelsize;
2159
+ this.translate[2] += adj;
2160
+ this.draw();
2161
+ }
2162
+
2163
+ if (server)
2164
+ sendCommand('' + this.getTranslationString());
2165
+ //sendCommand('zoom ' + factor);
2166
+ }
2167
+
2168
+ Viewer.prototype.zoomClip = function(factor) {
2169
+ if (this.gl) {
2170
+ var near_clip = this.near_clip + factor * this.modelsize;
2171
+ if (near_clip >= this.modelsize * 0.001)
2172
+ this.near_clip = near_clip;
2173
+ this.draw();
2174
+ }
2175
+ if (server)
2176
+ sendCommand('zoomclip ' + factor);
2177
+ }
2178
+
2179
+ Viewer.prototype.updateDims = function(view) {
2180
+ if (!view) return;
2181
+ var oldsize = this.modelsize;
2182
+ this.dims = [view.max[0] - view.min[0], view.max[1] - view.min[1], view.max[2] - view.min[2]];
2183
+ this.modelsize = Math.sqrt(this.dims[0]*this.dims[0] + this.dims[1]*this.dims[1] + this.dims[2]*this.dims[2]);
2184
+
2185
+ this.focus = [view.min[0] + 0.5*this.dims[0], view.min[1] + 0.5*this.dims[1], view.min[2] + 0.5*this.dims[2]];
2186
+ this.centre = [this.focus[0],this.focus[1],this.focus[2]];
2187
+
2188
+ this.translate = [0,0,0];
2189
+ if (this.modelsize != oldsize) this.translate[2] = -this.modelsize*1.25;
2190
+
2191
+ if (this.near_clip == 0.0) this.near_clip = this.modelsize / 10.0;
2192
+ if (this.far_clip == 0.0) this.far_clip = this.modelsize * 10.0;
2193
+
2194
+ quat4.identity(this.rotate);
2195
+ this.rotated = true;
2196
+
2197
+ if (view) {
2198
+ //Initial rotation
2199
+ if (view.rotate) {
2200
+ if (view.rotate.length == 3) {
2201
+ this.rotateZ(-view.rotate[2]);
2202
+ this.rotateY(-view.rotate[1]);
2203
+ this.rotateX(-view.rotate[0]);
2204
+ } else if (view.rotate.length == 4) {
2205
+ this.rotate = quat4.create(view.rotate);
2206
+ }
2207
+ }
2208
+
2209
+ //Translate
2210
+ if (view.translate) {
2211
+ this.translate[0] = view.translate[0];
2212
+ this.translate[1] = view.translate[1];
2213
+ this.translate[2] = view.translate[2];
2214
+ }
2215
+
2216
+ //Scale
2217
+ if (view.scale) {
2218
+ this.scale[0] = view.scale[0];
2219
+ this.scale[1] = view.scale[1];
2220
+ this.scale[2] = view.scale[2];
2221
+ }
2222
+
2223
+ //Focal point
2224
+ if (view.focus) {
2225
+ this.focus[0] = this.centre[0] = view.focus[0];
2226
+ this.focus[1] = this.centre[1] = view.focus[1];
2227
+ this.focus[2] = this.centre[2] = view.focus[2];
2228
+ }
2229
+
2230
+ }
2231
+
2232
+ /*
2233
+ console.log("DIMS: " + this.dims[0] + "," + this.dims[1] + "," + this.dims[2]);
2234
+ console.log("New model size: " + this.modelsize + ", Focal point: " + this.focus[0] + "," + this.focus[1] + "," + this.focus[2]);
2235
+ console.log("Translate: " + this.translate[0] + "," + this.translate[1] + "," + this.translate[2]);
2236
+ console.log("Rotate: " + this.rotate[0] + "," + this.rotate[1] + "," + this.rotate[2] + "," + this.rotate[3]);
2237
+ console.log(JSON.stringify(view));
2238
+ */
2239
+ }
2240
+
2241
+ /*
2242
+ function resizeToWindow() {
2243
+ //var canvas = document.getElementById('canvas');
2244
+ //if (canvas.width < window.innerWidth || canvas.height < window.innerHeight)
2245
+ sendCommand('resize ' + window.innerWidth + " " + window.innerHeight);
2246
+ var frame = document.getElementById('frame');
2247
+ if (frame) {
2248
+ canvas.style.width = frame.style.width = "100%";
2249
+ canvas.style.height = frame.style.height = "100%";
2250
+ }
2251
+ }
2252
+
2253
+ function connectWindow() {
2254
+ requestData('/render');
2255
+ window.location.reload();
2256
+ }
2257
+ */
2258
+
2259
+