-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmarching.js
2095 lines (1872 loc) · 85.9 KB
/
marching.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* eslint-disable no-use-before-define */
/**
* now at https://github.com/sjpt/metaballsWebgl
* Derived from MarchingCubes.js
* This version modified to do complete work on GPU sjpt 8 May 2020 to 19 May 2020
*
* works in four passes
* 1: spat: use a course grid and deciding which spheres are 'active' within each course voxel (output spatrt, activeKey)
* 2: fill: compute the potential function at every point on the full grid (using spatrt for optimization)
* 3: box: for each voxel compute its marching cubes 'key', and if useboxnorm set the normal for the 'low' corner
* 4: march: run marching cubes on the voxels, up to 5 triangles, 15 vertices per voxel
*
* This algorithm does not intrinsically need vertex or position attributes for the main marching cubes phase;
* they are provided by gl_VertexID and gl_InstanceID.
* However, these are not supported in WebGL1, and three.js (up to 117)
* does not support rendering without some 'dummy' attributes.
* I hope three.js will accept these min later revisions.
*
* This revision does not allow geometry with no vertex or instance attributes
*
* Knows things to look at:
* edges poor with instance tracking, probably not completely fixable
* would improve tracking 2 objects
* Always tracking position withing triangle (whichVert), even though generally not used. Slight inefficiency.
*
* I haven't tried marching tetrahedra. Partly because Mikola Lysenko's javascript implementation showed it as much slower,
* but Andrew Gotow discusses it as 16 times faster than marching cubes, so todo???
* http://mikolalysenko.github.io/Isosurface/
* https://andrewgotow.com/2018/08/21/gpu-isosurface-polygonalization/
***/
var THREE, Stats, queryloadpromise, trywebgl2=true, marchtexture, TextureMaterial;
Marching.marchmatver = 0;
function Marching(isWebGL2) {
var me = this;
var THREESingleChannelFormat = (isWebGL2) ? THREE.RedFormat : THREE.LuminanceFormat;
// control structure of options (could just be me. ?)
const X = me.X = window.X = {
rad: 0.1, // radius of spheres
radInfluence: 1.5, // how far infludence spreads
spherePosScale: new THREE.Vector4(0,0,0,1), // scale of sphere positions (TODO; something cheap but more complete)
sphereYin: false, // set to true for y based input texture of sphere points
npart: 1000, // number of particles
ntexsize: 1000, // size of texture holding particles
spatdiv: 25, // numnber of spat phase subdivisions if equal for x,y,z
doshade: true, // control final shading phase
dowire: false, // control final wireframe phase
dopoints: false, // control final points phase
funtype: 2, // 0 cubic, 1 quadratic, 2 exp
useboxnorm: true, // true to compute normals in box phase
surfnet: false, // true to test surfnet
lego: false, // use for lego normals and points on surfnet
instancing: true, // do we use instancing
loops: 1, // number of repititions of loop to apply each render cycle
trivmarch: false, // true for trivial version of marching cubes pass, performance test to estimate overheads
xnum: 100, ynum: undefined, znum: undefined, // numbers for voxel boundary count (ynum and znum default to xnum)
yinz: 0, // manual override value used to split y in 3d/2d lookup, needed where xnum*ynum > maxtexsize
threeShader: true, // true to use customized three shader, false for trivial shading
trackStyle: 'trackColor', // trackNone, trackColor, trackId1, trackId2, trackMedial
showTriangles: 0, // size to show trianlges (? id1 mode only for now) 0 or -ve for none
doubleSide: false, // true for double-sided
rotateInside: false,// true to rotate rgb for back-facing surface
isol: 1, // marching cubes isolation level
useFun: false, // true to use function rather than spheres
funcode: '', // function code
funRange: 1, // range for function xyz values (eg -1..1)
medialNeg: 1e20, // particles above this to be -ve
spatoff: 1, // offset in test for -ve,+ve regions
medialThresh: -999, // threshold for medial surface
medialColMax: 0.3, // scale for medial colouring, 0 for plain
medialStyle: 'frag', // where to apply medialThresh: box, marchvert, marchfrag. Mix of keywords box, vert, frag
medialColorStyle: 'grade', // style for medial colouring, none or more of mix, grade, left, right, tri, other, this
// note, combined values are valid but not necessarily sensible
medialColorBalance: 0.5, // balance of left/right for some medial colour settings
transparent: false, // transparency
bedtexture: undefined, // if set, use for looking up particle colours
marchtexture: !!window.TextureMaterial // use the marchtexture override code
}
var
expradinf = 2, // radinf for exp code
// edgeTable, // unused table for edges
triTable, // table to drive mc
legonorms, // table of normals for lego style marching
span, // span of items for spatial check
spatdivs, // number of spat phase subdivisions, separate vec3 for x,y,z
maxt = 5, // max triangles per voxel, 5 for all possible
A = 24; // number of sphere mask bits to pack into each float element
const VEC3 = (x,y,z) => new THREE.Vector3(x, y, z);
X.ynum = X.ynum || X.xnum;
X.znum = X.znum || X.xnum;
spatdivs = spatdivs || VEC3(X.spatdiv, X.spatdiv, X.spatdiv);
const setspatdivs = () => {if (X.spatdiv) spatdivs = VEC3(X.spatdiv,X.spatdiv,X.spatdiv);};
const orderedTodos = {setspatdivs, setfun, spatinit, fillinit, boxinit, boxmat, marchinit, marchmatgen};
let lastCheck = [];
// Check that the various settings are unchanged, or rebuild as needed if not
// Could be done with get/set properties but I think this is a bit easier.
// This will do almost all preparation work at first call.
//
// Work in two passes, to decide what needs to be done, then do it.
// TODO Check overlaps such as marchinit, marchmatgen
function beforeRender(renderer, scene, camera) {
if (!maxtext) { const gl = renderer.getContext(); maxtext = gl.getParameter( gl.MAX_TEXTURE_SIZE ); }
// dynamic recompile etc if needed
let cn=0; // check number, increased on each call
let todo = {};
// check what needs doing
function check(key, list) {
key = key.toString();
if (key !== lastCheck[cn]) {
lastCheck[cn] = key;
if (typeof list === 'function')
list();
else
for (let l of list.split(' '))
todo[l] = true;
}
cn++;
}
check([X.funtype, X.spatdiv, X.npart, X.ntexsize, X.sphereYin], 'setspatdivs setfun spatinit fillinit');
check(!!X.bedtexture, 'fillinit');
check([X.marchtexture, X.medialColorStyle, X.transparent], 'marchmatgen');
check(X.trackStyle, 'fillinit marchinit');
check([X.xnum, X.ynum, X.znum, X.yinz, X.threeShader], 'fillinit marchinit boxinit');
check(X.doubleSide, () => marchmesh.material.needsUpdate = true);
check(X.trivmarch, 'marchmatgen');
check(X.instancing, 'marchinit');
check([X.useboxnorm, X.surfnet, X.lego, X.useFun, X.funcode, X.medialStyle], 'boxmat marchinit');
for (let k in orderedTodos) {
if (todo[k]) orderedTodos[k]();
}
if (X.marchtexture && X.marchtexture.onframe)
X.marchtexture.onframe({uniforms: boxMarchUniforms});
boxMarchUniforms.projectionMatrix.value = camera.projectionMatrix;
boxMarchUniforms.showTriangles.value = X.showTriangles;
boxMarchUniforms.medialColorBalance.value = X.medialColorBalance;
boxMarchUniforms.rotateInside.value = +X.rotateInside;
marchmesh.material.wireframe = X.dowire;
marchmesh.material.side = X.doubleSide ? THREE.DoubleSide : THREE.FrontSide;
setinfluence();
renderPreobjects(renderer, camera);
}
function renderPreobjects(renderer, camera) {
// TODO optimize, some of these not needed if sphereData hasn't changed
if (!X.useFun) rrender('spat', renderer, spatscene, camera, spatrt); // camera not used here
if (!X.useFun) rrender('fill', renderer, fillscene, camera, fillrt); // camera not used here
rrender('box', renderer, boxscene, camera, boxrt); // camera not used here
}
// render not using THREE scene, but with more control.
me.testRender = function(renderer, scene, camera) {
marchmesh.onBeforeRender = ()=>{};
beforeRender(renderer, scene, camera);
const isol = X.isol;
if (X.dowire) {
marchmat.wireframe=true; boxMarchUniforms.isol.value = isol-0.1; boxMarchUniforms.color.value.y = 0;
rrender('march', renderer, marchmesh, camera, null);
marchmat.wireframe=false; boxMarchUniforms.isol.value = isol; boxMarchUniforms.color.value.y = 1;
}
if (X.doshade) {
// scene.children = [marchmesh];
marchmat.wireframe=false; boxMarchUniforms.isol.value = isol; boxMarchUniforms.color.value.y = 1;
rrender('march', renderer, marchmesh, camera, null);
}
if (X.dopoints) {
boxMarchUniforms.isol.value = isol-0.15; boxMarchUniforms.color.value.y = 1;
rrender('march', renderer, marchpoints, camera, null);
}
marchmesh.onBeforeRender = beforeRender;
}
me.updateData = function (datatexture, spherePosScale=X.spherePosScale) {
if (spatFillUniforms && spatFillUniforms.sphereData) {
spatFillUniforms.sphereData.value = datatexture;
spatFillUniforms.bedtexture.value = X.bedtexture;
spatFillUniforms.spherePosScale.value.copy(spherePosScale);
boxMarchUniforms.spherePosScale.value.copy(spherePosScale);
}
}
var renders = X.renders = {}; // for performance tests
// rrender is a convenient funnel/filter for render calls for debug, performance test, etc
function rrender(name, renderer, _scene, xcamera, rtout) {
const oldrt = renderer.getRenderTarget();
renderer.setRenderTarget(rtout);
let _nn = renders[name];
if (_nn === undefined) _nn = 1;
for (let i = 0; i < _nn; i++) {
renderer.render(_scene, xcamera);
}
renderer.setRenderTarget(oldrt);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// bits of shader code that are or might be/might have been) shared between different passes
// Most are constant, some are provided as functions to allow for dynamic changes in settings such as xnum
var codebits = {};
function codebitsinit() {
codebits.getmu = ()=> /*glsl*/`// codebits.getmu get mu given isol level and two potential values
const float tol = 0.000; // tolerance
// get mu value and check in range
float getmu(float isol, float valp1, float valp2) {
float mu = (isol - valp1) / (valp2 - valp1);
#if ${+X.lego} // need to force normals as well for this to work well
mu = 0.5;
#endif
// if (mu < -tol || mu > 1.+tol) {
// mu = 770.5; // exaggerate so it really shows the error
// } // till working
return mu;
}
`;
codebits.sphereInput = () => /*glsl*/`
${codebits.getpart}
uniform vec4 spherePosScale;
uniform sampler2D sphereData;
uniform sampler2D bedtexture;
vec4 sphereInput(float iin) { // iin relative to full power2 texture
#if ${+X.sphereYin}
vec4 r = texture2D(sphereData, vec2(0.5, iin));
#else
vec4 r = texture2D(sphereData, vec2(iin, 0.5));
#endif
r.xyz -= spherePosScale.xyz;
r.xyz *= spherePosScale.w;
#if (${+!!X.bedtexture}) // ?? extra lookup even on spat pass where it is not used ??
float xin = iin * float(${X.ntexsize}) / float(${X.npart}); // xin relative to particle length texture
vec4 c = texture2D(bedtexture, vec2(xin, 0.25)); // 0.25 assumes 2 row texture, but OK for 1 row as well
r.w = pack256(c.rgb);
#endif
// if not, then bedtexture r.w may have colour. pass on unchanged
return r;
}
`;
codebits.vintcore = () => /*glsl*/`// codebits.vintcore()
uniform float medialThresh, medialColMax;
// this decides what final colours will be, then passed via vmarchCol etc and applied in marchTrackColFrag
// It does the interpolation work on corners pre-determined by the active edge marchvert
void VIntCoreVert(vec3 up1, vec3 up2, vec4 trackfa, vec4 trackfb, out vec3 pos, out vec3 norm, out vec4 marchCol, out vec4 marchColB ) {
#if trackStyle == trackMedial && ${+contains(X.medialStyle, 'vert')} != 0
if (trackfa.x < medialThresh || trackfb.x < medialThresh) {
pos = vec3(sqrt(medialThresh-1e10)); norm=vec3(0,0,1); marchCol=vec4(1,1,0,1); return;
}
#endif
float mu = getmu(isol, trackfa.w, trackfb.w);
pos = up1 + (up2 - up1) * mu; // box coords
vec3 na = compNorm(up1), nb = compNorm(up2);
norm = na * (1.-mu) + nb * mu;
#if trackStyle == trackColor
marchCol.rgb = trackfa.rgb * (1.-mu) + trackfb.rgb * mu;
#elif trackStyle == trackMedial
float p = trackfa.x * (1.-mu) + trackfb.x * mu;
// p *= medialColMax;
marchCol = vec4(unpack256(trackfa.y), p);
marchColB = vec4(unpack256(trackfb.z), p);
#elif trackStyle == trackId1
marchCol.rgb = (trackfa.y * (1.-mu) > trackfb.y * mu ? trackfa : trackfb).rgb;
#else
marchCol.rgb = vec3(1);
#endif
}
// end codebits.vintcore()`
codebits.compnorm = /*glsl*/`// codebits.compnorm compute normal at 0,0,0 corner of box, xi integer
vec3 compNormi(float xi, float yi, float zi) {
float dx = fillLook(xi+1., yi, zi).w - fillLook(xi-1., yi, zi).w;
float dy = fillLook(xi, yi+1., zi).w - fillLook(xi, yi-1., zi).w;
float dz = fillLook(xi, yi, zi+1.).w - fillLook(xi, yi, zi-1.).w;
// if we allow (common) 0,0,0 case through then NaN can spread
// eg r = vec4(compNorm i(???), 1.) will pollute r.w
if (dx == 0. && dy == 0. && dz == 0.) return vec3(0.199,0.299,0.399);
return normalize(vec3(dx, dy, dz));
}
// compute normal, box coord inputs
// ff is integer box numbers
vec3 compNorm(vec3 ff) {
#if ! ${+X.useboxnorm}
return boxf(ff.x, ff.y, ff.z).xyz; // assumes already computed in box pass, and so just lookup
#else
return compNormi(ff.x, ff.y, ff.z); // perform normal computation
#endif
}
// codebits.compnorm`
codebits.getxyzi = () => /*glsl*/`// codebits.getxyzi() find xi,yi,zi
// this code must complement lookup
float xi, yi, zi;
float xy = gl_FragCoord.x - 0.5; // 0 .. nump.x*nump.y-1
if (${yinz} == 1) { // pre-split code, minor optimization
xi = mod(xy, nump.x);
yi = floor(xy / nump.x);
zi = gl_FragCoord.y - 0.5;
} else {
const float yinx = float(${yinx});
const float yinz = float(${yinz});
float yz = gl_FragCoord.y - 0.5; // 0 .. nump.x*nump.y-1
xi = getpart(xy, nump.x);
float yihi = getpart(yz, yinz);
yi = xy + yihi * yinx;
zi = yz;
}
`
codebits.lookup = () => /*glsl*/`// codebits.lookup lookup field/box values
${codebits.getpart}
uniform sampler2D fillrt;
// box or marching cube lookup value in value texture, integer box coord inputs
// this code must complement codebits.getxyzi
vec4 look(float xi, float yi, float zi, sampler2D rt) { // range 0 .. numv etc
//xi = clamp(xi, 0., numv.x); // to check if needed todo
//yi = clamp(yi, 0., numv.y);
//zi = clamp(zi, 0., numv.z);
vec2 ll;
if (${yinz} == 1) { // pre-split code, minor optimization
float xy = (xi + yi * nump.x + 0.5) / (nump.x * nump.y);
ll = vec2(xy, (zi+0.5) / nump.z);
} else {
const float yinx = float(${yinx});
const float yinz = float(${yinz});
float yilo = getpart(yi, yinx);
float xy = (xi + yilo * nump.x + 0.5) / (nump.x * yinx);
float yz = (yi + zi * yinz + 0.5) / (yinz * nump.z);
ll = vec2(xy, yz);
}
return texture2D(rt, ll);
}
#define useFun ${+X.useFun}
#if useFun
uniform float funRange;
vec4 fillLook(float xi, float yi, float zi) { // range 0 .. numv etc
vec3 p = vec3(xi, yi, zi) / nump * 2. - 1.;
p *= funRange;
float x = p.x, y = p.y, z = p.z;
float v;
${X.funcode}
vec3 fcol = vec3(0.5, 0.7, 0.9);
return vec4(fcol, v);
}
#else
vec4 fillLook(float xi, float yi, float zi) { // range 0 .. numv etc
return look(xi, yi, zi, fillrt);
}
#endif
uniform sampler2D boxrt;
// look up value in boxrt, includes normal (in xyz) and key (in w)
vec4 boxf(float xi, float yi, float zi) { // integer range 0 .. numv etc
return look(xi, yi, zi, boxrt);
}
// codebits.lookup`
codebits.setfxxx = /*glsl*/`// codebits.setfxxx: compute values (by lookup)
vec4
f000 = fillLook(xi, yi, zi),
f100 = fillLook(xi+1., yi, zi),
f010 = fillLook(xi, yi+1., zi),
f110 = fillLook(xi+1., yi+1., zi),
f001 = fillLook(xi, yi, zi+1.),
f101 = fillLook(xi+1., yi, zi+1.),
f011 = fillLook(xi, yi+1., zi+1.),
f111 = fillLook(xi+1., yi+1., zi+1.);
//codebits.setfxxx`
codebits.defpxxx = /*glsl*/`// codebits.defpxxx: define vertices of voxel
#define p000 vec3(xi, yi, zi)
#define p100 vec3(xi+1., yi, zi)
#define p010 vec3(xi, yi+1., zi)
#define p110 vec3(xi+1., yi+1., zi)
#define p001 vec3(xi, yi, zi+1.)
#define p101 vec3(xi+1., yi, zi+1.)
#define p011 vec3(xi, yi+1., zi+1.)
#define p111 vec3(xi+1., yi+1., zi+1.)
// codebits.defpxxx`
codebits.keyi = /*glsl*/`// codebits.keyi return the signature key for the setup; 256 values in range 0..255
float keyi(float f000, float f100, float f010, float f110, float f001, float f101, float f011, float f111, float isol) {
float cubeindex = 0.;
if (f000 < isol) cubeindex += 1.;
if (f100 < isol) cubeindex += 2.;
if (f010 < isol) cubeindex += 8.;
if (f110 < isol) cubeindex += 4.;
if (f001 < isol) cubeindex += 16.;
if (f101 < isol) cubeindex += 32.;
if (f011 < isol) cubeindex += 128.;
if (f111 < isol) cubeindex += 64.;
return cubeindex;
}
// codebits.keyi`
codebits.avghit = /*glsl*/`// codebits.VintX
vec3 cpos, cnorm, cvmarchCol; float ccn;
void VIntX(vec3 p1, vec3 p2, vec4 f1, vec4 f2) {
vec3 pos, norm; vec4 col, colb;
if ((isol - f1.w) * (isol - f2.w) >= 0.) return;
VIntCoreVert(p1, p2, f1, f2, pos, norm, col, colb);
cpos += pos;
cnorm += norm;
cvmarchCol += (col.rgb + colb.rgb) * 0.5;
ccn++;
}
// compute details ready for average, output in cpos, cnorm, cvmarchCol, ccn
void avghit(float xi, float yi, float zi) {
${codebits.setfxxx} // set f000 etc
ccn = 0.;
cpos = cnorm = cvmarchCol = vec3(0,0,0);
VIntX(p000, p100, f000, f100);
VIntX(p100, p110, f100, f110);
VIntX(p010, p110, f010, f110);
VIntX(p001, p101, f001, f101);
VIntX(p101, p111, f101, f111);
VIntX(p011, p111, f011, f111);
VIntX(p001, p011, f001, f011);
VIntX(p000, p001, f000, f001);
VIntX(p100, p101, f100, f101);
VIntX(p110, p111, f110, f111);
VIntX(p010, p011, f010, f011);
}
`
codebits.vertpre = '';
if (isWebGL2) codebits.vertpre =
/*glsl*/`#version 300 es
// codebits.vertpre: patch to use webgl-like shader work with later shader version
#define attribute in
#define varying out
#define texture2D texture
`;
codebits.fragpre = '';
if (isWebGL2) codebits.fragpre =
/*glsl*/`#version 300 es
// codebits.fragpre: patch to use webgl-like shader work with later shader version
#define varying in
out highp vec4 pc_fragColor;
#define gl_FragColor pc_fragColor
`;
/* track:
color: r g b v
id1: id val -99 v
none: 1 1 1 v
medial: min va vb v
*/
codebits.track = () => /*glsl*/`// codbits.track
#ifndef trackStyle
#define trackNone 0
#define trackColor 10
#define trackId1 1
#define trackId2 2
#define trackMedial 3
#define trackStyle ${X.trackStyle}
#if trackStyle == trackColor
vec3 trackC = vec3(0,0,0);
#elif trackStyle == trackMedial
vec3 trackC1 = vec3(0,0,0);
vec3 trackC2 = vec3(0,0,0);
#elif trackStyle == trackId1
vec2 trackI1 = vec2(-1,0); // id, val
// #define trackId track.x
#define trackV trackI1.y
#else
float trackF = 0.;
#endif
uniform float showTriangles, rotateInside, medialColorBalance;
varying vec4 vmarchCol, vmarchColB; // collect output colour(s)
varying vec3 norm; // collect normal
varying vec3 whichVert; // used to track where in triangle, help reconstruct whichId and whichF
#if trackStyle == trackId1
varying vec3 whichId; // holds the id for each of verts 1,2,3
varying vec3 whichF; // holds the strenght for each of verts 1,2,3
#endif
float trackId = -1.; // shared with texture for now TODO
#endif
//codebits.track`
codebits.marchTrackColFragc = () => /*glsl*/`// codebits.marchTrackColFragc
uniform float medialThresh, medialColMax;
void marchTrackColFrag(inout vec4 inoutcol) {
// collect rgb according to tracking rules.
// data in vmarchCol(B) was established in vertex phase by VIntCoreVert
#if trackStyle == trackColor // track colour as set in original sphere array w field, or bedbuffer
inoutcol.xyz *= vmarchCol.xyz;
// #elif trackStyle == trackNone // DEBUG TEST vmarchCol should be 1,1,1
// inoutcol *= vmarchCol * ;
#elif trackStyle == trackMedial // track colour derived from medial closeness
float p = clamp(vmarchCol.a * medialColMax, 0., 1.);
float b = medialColorBalance;
float b1 = 1. - b;
#if ${+contains(X.medialStyle, 'frag')}
if (vmarchCol.w < medialThresh) discard;
#endif
#if ${+X.transparent}
inoutcol.a *= p;
#endif
#if ${+contains(X.medialColorStyle, 'mix')}
inoutcol.rgb *= (vmarchCol.rgb * b + vmarchColB.rgb * b1);
#endif
#if ${+contains(X.medialColorStyle, 'tri')}
inoutcol.rgb *= whichVert.x * b < whichVert.y * b1 ? vmarchCol.rgb : vmarchColB.rgb;
#endif
#if ${+contains(X.medialColorStyle, 'cen')}
inoutcol.rgb *= max(whichVert.x, max(whichVert.y, whichVert.z)) < b ? vmarchCol.rgb : vmarchColB.rgb;
#endif
#if ${+contains(X.medialColorStyle, 'other')}
inoutcol.rgb *= gl_FrontFacing ? vmarchCol.rgb : vmarchColB.rgb;
#endif
#if ${+contains(X.medialColorStyle, 'this')}
inoutcol.rgb *= !gl_FrontFacing ? vmarchCol.rgb : vmarchColB.rgb;
#endif
#if ${+contains(X.medialColorStyle, 'left')}
inoutcol.rgb *= vmarchCol.rgb;
#endif
#if ${+contains(X.medialColorStyle, 'right')}
inoutcol.rgb *= vmarchColB.rgb;
#endif
#if ${+contains(X.medialColorStyle, 'grade')}
inoutcol.rgb *= vec3(p, p, 1.-p);
#endif
#elif trackStyle == trackId1
// find best trackId, reconstruct ids and consider different cases
// all equal whole triangle is same id
// two equal, divide triangle into 2 by single line between edge midpoints
// all different, divide triangle into 3 by lines from midpoints to centre
vec3 ids = floor(whichId / whichVert + 0.5); // vertex ids restored from 'distortion' in filling
vec3 fs = whichF / whichVert; // force strengths at the corners
vec3 f = whichVert * max(fs - 0.0, 0.); // force here
trackId =
ids.y == ids.z ? // 2-way xy equal, also covers 1-way xyz equal
f.x > f.y + f.z ? ids.x : ids.y :
ids.z == ids.x ? // 2-way zx equal
f.y > f.z + f.x ? ids.y : ids.z :
ids.x == ids.y ? // 2-way xy equal
f.z > f.x + f.y ? ids.z : ids.x :
f.x > f.y && f.x > f.z ? ids.x : // 3-way x strongest
f.y > f.x && f.y > f.z ? ids.y : // 3-way y strongest
ids.z; // 3-way z strongest (or equal)
inoutcol.rgb *= mod((trackId + 1.) * vec3(234.66, 77.8, 9968.7), 255.)/255.; // randomish colour for each id
// trackNone leave to standard three.js material colour
#endif
if (!gl_FrontFacing && rotateInside != 0.) inoutcol.rgb = inoutcol.brg;
if (showTriangles > 0. && min(whichVert.x, min(whichVert.y, whichVert.z)) < showTriangles) inoutcol.rgb = 1. - inoutcol.rgb;
}
// end codebits.marchTrackColFragc`
codebits.getpart = /*glsl*/`//codebits.getpart unwind integer value 0..b-1 from a packed integer a; reduce a
#ifndef _GETPART
#define _GETPART
float getpart(inout float a, float b) {
float t = floor(a/b);
float r = a - t*b;
a = t;
return r;
}
float getpart(inout int a, int b) {
int t = a/b;
int r = a - t*b;
a = t;
return float(r);
}
float getpart(inout int a, float b) {
return getpart(a, int(b));
}
vec3 unpack256(float w) {
vec3 r; r.x = getpart(w, 256.); r.y = getpart(w, 256.); r.z = w;
return r / 255.;
}
float pack256(vec3 v) {
v = floor(v * 255.);
return v.x + v.y * 256. + v.z * 256. * 256.;
}
#endif
// codebits.getpart`
} // end codebitsinit
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// code for marching cubes pass
var marchgeom, marchmat, marchvert, marchfrag, boxMarchUniforms, marchmesh, marchpoints, triTexture, legonormTexture;
me.three = marchmesh = new THREE.Mesh(); // create on construction so available at once
marchmesh.frustumCulled = false;
marchpoints = new THREE.Points();
marchpoints.frustumCulled = false;
marchmesh.name = 'marchmesh';
// NOTE: three.js bug where onBeforeRender on mesh is performed twice
// renderer, scene, camera, geometry, material, group
marchmesh.onBeforeRender = beforeRender;
// const edgeTexture = new THREE.DataTexture(edgeTable, 1, 256, THREESingleChannelFormat, THREE.FloatType);
// edgeTexture.needsUpdate = true;
tables();
triTexture = new THREE.DataTexture(triTable, 16, 256, THREESingleChannelFormat, THREE.FloatType);
triTexture.needsUpdate = true;
triTexture.name = 'triTexture';
boxMarchUniforms = {
// edgeTable: {value: edgeTexture},
triTable: {value: triTexture},
color: {value: new THREE.Vector3(1,1,1)},
ambcol: {value: new THREE.Vector3(0.03,0.03,0.07)},
isol: {value: X.isol},
spherePosScale: {value: new THREE.Vector4(0,0,0,1)},
funRange: {value: X.funRange},
showTriangles: {value: 0},
medialColorBalance: {value: 0.5},
rotateInside: {value: 0},
projectionMatrix: {value: undefined},
modelViewMatrix: {value: new THREE.Matrix4()}, // contents set by three.js
fillrt: {value: undefined},
medialThresh: {value: -999},
medialColMax: {value: 0.4},
boxrt: {value: undefined}
}
function marchinit() {
if (X.surfnet) return surfinit();
marchgeomgen();
marchmatgen();
// marchmat.side = THREE.DoubleSide;
}
function marchgeomgen() {
marchgeom = new THREE.BufferGeometry(); marchgeom.name = 'marchgeom';
const {xnum, ynum, znum, instancing} = X;
const voxs = (xnum-1) * (ynum-1) * (znum-1); // voxel count
const tris = voxs * 3 * maxt
let q = 0;
let posb, posatt;
if (instancing) { // assume webgl2 for now
marchgeom = new THREE.InstancedBufferGeometry(); marchgeom.name = 'marchgeom';
//if (!marchgeom.setAttribute) marchgeom.setAttribute = marchgeom.setAttribute; // older three.js
if (isWebGL2) {
marchgeom.drawRange.count = maxt*3;
posb = new Uint8Array(maxt*3); // data not used, but size used inside three.js for wireframe << needed till three.js fix
posatt = new THREE.BufferAttribute(posb, 1);
posatt.count = maxt*3; // this was for when we had a 'dummy' posb, not we have real one for wireframe
} else {
posb = new Uint16Array(maxt*3*3);
for (let r = 0; r < 3 * maxt; r++) {
posb[q++] = 0;
posb[q++] = 0;
posb[q++] = r;
}
posatt = new THREE.BufferAttribute(posb, 3);
const instanceID = new Float32Array(voxs); // nb webgl1 does not allow Uint32Array
for (let i = 0; i < voxs; i++) instanceID[i] = i;
const instatt = new THREE.InstancedBufferAttribute(instanceID, 1, false);
// instatt.setDynamic(true); // old syntax and inapproprite anyway
marchgeom.setAttribute('instanceID', instatt);
}
marchgeom._maxInstanceCount = Infinity; // needed until three.js fix <<<
marchgeom.instanceCount = voxs;
} else if (isWebGL2) { // for webgl2 the buffer is only there to let three.js know #vertices
posb = new Int8Array(tris*3); // data not used, but size used inside three.js for wireframe
posatt = new THREE.BufferAttribute(posb, 1);
posatt.count = tris;
} else { // !instancing, !webgl2
posb = new Int16Array(tris*3);
for (let i = 0; i < xnum - 1; i++) {
for (let j = 0; j < ynum - 1; j++) {
for (let k = 0; k < znum - 1; k++) {
for (let r = 0; r < 3 * maxt; r++) {
posb[q++] = i;
posb[q++] = j;
posb[q++] = k * 16 + r;
}
}
}
}
posatt = new THREE.BufferAttribute(posb, 3);
}
// if (instancing && isWebGL2) { // this is needed till three.js fix
// console.log('no attribute')
//} else {
marchgeom.setAttribute('position', posatt);
//}
marchmesh.geometry = marchgeom;
marchpoints.geometry = marchgeom;
}
function marchmatgen() {
const {xnum, ynum, znum, instancing} = X;
var marchvertPre =
/*glsl*/`${codebits.vertpre}
// marchvert
// marching cubes vertex shader
precision highp float;
attribute vec3 position;
uniform mat4 projectionMatrix, modelViewMatrix; // , normalMatrix;
const vec3 nump = vec3(${xnum}., ${ynum}., ${znum}.);
const vec3 numv = vec3(${xnum-1}., ${ynum-1}., ${znum-1}.);
uniform float isol;
// uniform sampler2D edgeTable; // 256 x 1
uniform sampler2D triTable; // 256 x 16
uniform sampler2D legonorms; // 254 x 5
${codebits.track()}
#define NaN -9999999.9 // sqrt for 'real' NaN, no noticable performance difference
vec3 NaN3 = vec3(NaN, NaN, NaN);
vec4 NaN4 = vec4(NaN, NaN, NaN, NaN);
vec4 BAD4 = vec4(999, 999, 999, 1); // doesn't seem to make much difference what this is
${codebits.lookup()}
${codebits.getmu()}
${codebits.compnorm}
uniform vec4 spherePosScale;
${codebits.vintcore()}
vec3 up1, up2; // grid coordinates for ends and step between them, set by VIntG etc for use by VIntReal,
// Saving just one detail set up1/up2 for VIntG and then computing once in VIntReal
// forces only one lookup.
// When VIntG actually did the work (even under conditional)
// it seems the compiler forced uniform flow and performed unnecessary lookups.
// compute the intersection point on general line, step gives line direction
// varying vec4 vmarchCol;
void VIntReal(out vec3 pos) {
vec4 trackfa = fillLook(up1.x, up1.y, up1.z), trackfb = fillLook(up2.x, up2.y, up2.z);
VIntCoreVert(up1, up2, trackfa, trackfb, pos, norm, vmarchCol, vmarchColB);
}
// save details of ends of edge, ready to compute intersection point
void VIntG(vec3 p1, vec3 p2) {
up1 = p1;
up2 = p2;
}
float vk;
float modif(int a, int b) { return float(a - (a/b)*b);}
attribute float instanceID;
// end of marchvertPre
`;
// marchvertCore outputs transformed (position), norm ()
var marchvertCore = () => /*glsl*/` // marchvertCore, core work of computing positions
// ${X.trivmarch ? '' : '// '} gl_Position = vec4(9999, 9999, 9999, 1); return; // trivial version
// ${X.trivmarch ? '' : '// '} gl_Position = vec4(0); return; // trivial version
${X.trivmarch ? '' : '// '} gl_Position = vec4(sqrt(-1.)); return; // trivial version
gl_Position = BAD4; // till proved otherwise
// ~~~~~~~~~~~~~~~~~~~~~
// first stage, sort out exactly which voxel and voxel index is being worked on
// varies depending on use of instancing and vertexid
float xi, yi, zi, ik; // ik is 0..14, integer key to vertex
#if (${isWebGL2 ? '1==1' : '1==0'})
#if (${instancing ? '1==1' : '1==0'})
int q = gl_InstanceID;
ik = float(gl_VertexID);
#else
int q = gl_VertexID;
ik = getpart(q, ${3*maxt});
#endif
zi = getpart(q, ${znum-1});
yi = getpart(q, ${ynum-1});
xi = getpart(q, ${xnum-1});
#else
#if (${instancing ? '1==1' : '1==0'})
int q = int(instanceID);
zi = getpart(q, ${znum-1});
yi = getpart(q, ${ynum-1});
xi = getpart(q, ${xnum-1});
ik = position.z;
#else
xi = position.x;
yi = position.y;
float fpx = position.z / 16.;
zi = floor(fpx);
ik = (fpx - zi) * 16.; // todo, simplify this bit
#endif
#endif
float vk = ik / 16. + 1./32.; // vk is key into which vertex to compute, 0/16..14/16 + 1/32.
// ~~~~~~~~
// now we know the vertex find the key and other information precomputed by the box phase
// Basic active box optimization.
vec4 box = boxf(xi, yi, zi);
if (box.x == -1.) return;
#if ${+X.surfnet}
#error wrong X.surfnet
#endif
// key on of 256 values in range 0..1
float key = box.w;
key = (key + 0.5) / 256.; // to match texture lookup
// bits from edgeTable says which of the twelve edges are actively involved
// but doesn't help in this variant of the algorithm
//float bits = texture2D(edgeTable, vec2(0.5, key)).x;
// find which edge we are working on, and this the two ends of the edte
float edgen = texture2D(triTable, vec2(vk, key)).x; // get the edge number, 0..11
if (edgen < 0.) return; // optimization, presumably -1 in table
// exactly one of the below will execute and save details up1 and up2
// depending which edge
// and then VIntReal() will do the actual computation
${codebits.defpxxx} // set defines for voxel corner points
// front lines of the cube
if (edgen < 3.5) {
if (edgen == 0.) VIntG(p000, p100);
else if (edgen == 1.) VIntG(p100, p110);
else if (edgen == 2.) VIntG(p010, p110);
else VIntG(p000, p010);
}
// back lines of the cube
else if (edgen < 7.5) {
if (edgen == 4.) VIntG(p001, p101);
else if (edgen == 5.) VIntG(p101, p111);
else if (edgen == 6.) VIntG(p011, p111);
else VIntG(p001, p011);
}
// front to back lines of the cube
else {
if (edgen == 8.) VIntG(p000, p001);
else if (edgen == 9.) VIntG(p100, p101);
else if (edgen == 10.) VIntG(p110, p111);
else VIntG(p010, p011);
}
// now we have identified edge ends find transformed position and norm
VIntReal(transformed) ;
transformed = transformed/numv * 2. - 1.; // -1..1 coords
transformed /= spherePosScale.w; // return to user coords
transformed += spherePosScale.xyz;
vec3 bnorm = normalize(norm);
#if ${+X.lego}
bnorm = texture2D(legonorms, vec2((floor(ik/3.)+0.5)/5.0, key)).xyz;
#endif
norm = -mat3(modelViewMatrix) * bnorm;
// _worldPosition = (modelMatrix * vec4( pos, 1.0 )).xyz;
// gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.);
gl_PointSize = 1.;
// so fragement shader can find where in triangle, and reconstruct corner ids
float en = mod(ik, 3.); // which vertex, 0,1,2
if (en < 0.5) {
whichVert = vec3(1,0,0);
} else if (en < 1.5) {
whichVert = vec3(0,1,0);
} else {
whichVert = vec3(0,0,1);
}
#if trackStyle == trackId1
whichId = whichVert * vmarchCol.x;
whichF = whichVert * vmarchCol.y;
#endif
// end of marchvertCore
`
marchfrag = () =>
/*glsl*/`${codebits.fragpre}
// marchfrag
precision highp float;
uniform vec3 color;
uniform vec3 ambcol;
const vec3 light1 = normalize(vec3(1,1,1)) * 0.7;
const vec3 light2 = normalize(vec3(-1,0,1)) * 0.1;
const vec3 eye = vec3(0,0,3);
${codebits.track()}
${codebits.marchTrackColFragc()}
void main() { // marchfragmain ... simple version
vec3 nn = normalize(norm);
vec4 ucol = vec4(color, 1);
marchTrackColFrag(ucol);
float k = max(dot(nn, light1), 0.) + max(dot(nn, light2), 0.);
ucol.rgb *= k;
gl_FragColor = ucol;
gl_FragColor.xyz += ambcol;
// gl_FragColor.xyz = (gl_FragColor.xyz);
}
// end marchfrag`;
// X.threeShader = false;
if (!X.threeShader) {
marchvert =
`${marchvertPre}
void main() { // marchvertmain trivial shader
vec3 transformed; // transformed is output of marching transformation, to fit in with three.js convenetions
${marchvertCore()}
gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.);
}`;
marchmat = new THREE.RawShaderMaterial({
fragmentShader: marchfrag(),
vertexShader: marchvert,
uniforms: boxMarchUniforms
});
// marchmat.defines = {qq: 99}; // defines breaks shader in webgl2 as it gets before #version 300 es
} else {
const onBeforeCompile = shader => {
marchmat.xshader = shader; // for debug
// // marchvertPre set up for standalone not three material
// // needs slight patching (may replace with cleaner mechanism?)
marchvertPre = marchvertPre.replace('#version', '//PATCH #version');
marchvertPre = marchvertPre.replace('uniform mat4 pro', '//PATCH uniform mat4 pro');
marchvertPre = marchvertPre.replace('attribute vec3 position', '//PATCH attribute vec3 position');
shader.vertexShader = marchvertPre + '\n' + shader.vertexShader;
// marchvertCore tailored for fitting in with three shader
const marchvertCoreX = `${marchvertCore()}
// transformed = (r * scaleFactor) * rotpos + ppos; // transformed has modelMatrix
// vec4 mvPosition = viewMatrix * vec4( transformed, 1.0 ); // needed if no light?
vNormal = norm;
`
const patch = (s, k, toadd) => s.replace(k, toadd);
const patchBefore = (s, k, toadd) => s.replace(k, toadd + '\n' + k );
const patchAfter = (s, k, toadd) => s.replace(k, k + '\n' + toadd);
shader.vertexShader = patchBefore(shader.vertexShader, '#include <project_vertex>', marchvertCoreX);
shader.fragmentShader = patchBefore(shader.fragmentShader, '#include <common>', codebits.track());
shader.fragmentShader = patchBefore(shader.fragmentShader, 'void main() {', `
${codebits.marchTrackColFragc()}
`);
//shader.fragmentShader = patchBefore(shader.fragmentShader, '#include <lights_physical_fragment>', marchfragcol);
shader.fragmentShader = patchAfter(shader.fragmentShader, '#include <emissivemap_fragment>', `
marchTrackColFrag(diffuseColor);
#if ${X.marchtexture ? 1 : 0}
colourid = trackId; // make trackId available to tetxure code
#endif
`);
Object.assign(shader.uniforms, boxMarchUniforms);
}
if (X.marchtexture) {
marchmat = new TextureMaterial(boxMarchUniforms);
marchmat.onBeforeCompileX = onBeforeCompile;
} else {
marchmat = new THREE.MeshStandardMaterial();
marchmat.onBeforeCompile = onBeforeCompile;
}
// defines needed here to resolve three.js cache conflict
// https://github.com/mrdoob/three.js/issues/19377
// defines could be used to replace ${xnum} etc with plain xnum
// but for the moment at least using .define breaks our raw shader in webgl2
marchmat.defines = {xnum, ynum, znum, XtrackStyle: X.trackStyle, ver: Marching.marchmatver++};
me.material = marchmat;
}
marchmat.name = 'marchmat';
marchmesh.material = marchmat;
marchpoints.material = marchmat;
console.log('using march material');
return marchmat;