-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathModelExt.lua
1583 lines (1501 loc) · 48.4 KB
/
ModelExt.lua
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
-- MeshExt
local __doJewel, __doSuspension, __doPyramid, __doBlock, __addTriangle, __doSphere, __threeFromTwo, __orthogonalTo, __doCylinder, __discreteNormal, __doCone, __doPoly, __doFacetedClosedCone, __doFacetedOpenCone, __doSmoothClosedCone, __doSmoothOpenCone, __doFacetedClosedCylinder, __doFacetedOpenCylinder, __doSmoothClosedCylinder, __doSmoothOpenCylinder, __initmesh
--[[
| Option | Default | Description |
|:-------------|:-------------------------|:------------|
| `mesh` | new mesh | The mesh to add the shape to. |
| `position` | end of mesh | The position in the mesh at which to start the shape. |
| `origin` | `vec3(0,0,0)` | The origin (or centre) of the shape. |
| `axis` | `vec3(0,1,0)` | The axis specifies the direction of the jewel. |
| `aspect` | 1 | The ratio of the height to the diameter of the gem. |
| `size` | the length of the axis | The size of the jewel; specifically the distance from the centre to the apex of the jewel. |
| `colour`/`color` | white | The colour of the jewel. |
| `texOrigin` | `vec2(0,0)` | If using a sprite sheet, this is the lower left corner of the rectangle associated with this gem. |
| `texSize` | `vec2(1,1)` | This is the width and height of the rectangle of the texture associated to this gem.
--]]
function addJewel(t)
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting)
local p = t.position or (m.size + 1)
p = p + (1-p)%3
local o = t.origin or vec3(0,0,0)
local c = t.colour or t.color or color(255, 255, 255, 255)
local as = t.aspect or 1
local to = t.texOrigin or vec2(0,0)
local ts = t.texSize or vec2(1,1)
local a = {}
a[1] = t.axis or vec3(0,1,0)
if t.size then
a[1] = a[1]:normalize()*t.size
end
a[2] = __orthogonalTo(a[1])
a[3] = a[1]:cross(a[2])
local la = a[1]:len()
for i = 2,3 do
a[i] = as*la*a[i]:normalize()
end
local n = t.sides or 12
if p > m.size - 12*n then
m:resize(p + 12*n-1)
end
local l,am
if rl then
l = vec3(0,0,0)
am = 1
else
l = t.light or vec3(0,0,0)
if t.intensity then
l = l:normalize()*t.intensity
elseif l:lenSqr() > 1 then
l = l:normalize()
end
am = t.ambience or (1 - l:len())
end
local np = __doJewel(m,p,n,o,a,c,to,ts,l,am)
if ret then
return m,p,np
else
return m
end
end
--[[
A jewel is a special case of a "suspension".
m mesh to add shape to
p position of first vertex of shape
n number of sides
o centre of shape (vec3)
a axes (table of vec3s)
col colour
to offset of texture region (vec2)
ts size of texture region (vec2)
l light vector
--]]
function __doJewel(m,p,n,o,a,col,to,ts,l,am)
local th = math.pi/n
local cs = math.cos(th)
local sn = math.sin(th)
local h = (1 - cs)/(1 + cs)
local k,b,c,d,tb,tc,td,tex,pol
tex,pol={},{}
c = cs*a[2] + sn*a[3]
d = -sn*a[2] + cs*a[3]
tc = cs*vec2(ts.x*.25,0) + sn*vec2(0,ts.y*.5)
td = -sn*vec2(ts.x*.25,0) + cs*vec2(0,ts.y*.5)
for i = 1,2*n do
k = 2*(i%2) - 1
table.insert(pol,o+h*k*a[1]+c)
table.insert(tex,tc)
c,d = cs*c + sn*d,-sn*c + cs*d
tc,td = cs*tc + sn*td,-sn*tc + cs*td
end
return __doSuspension(m,p,2*n,o,{o+a[1],o-a[1]},pol,tex,col,to,ts,true,true,l,am)
end
--[[
A "suspension" is a double cone on a curve.
m mesh to add shape to
p position of first vertex of shape
n number of points
o centre of shape (vec3)
a apexes (table of 2 vec3s)
v vertices (table of vec3s in cyclic order)
t texture coordinates corresponding to vertices (relative to centre)
col colour
to offset of texture region (vec2)
ts size of texture region (vec2)
f faceted
cl closed curve or not
l light vector
--]]
function __doSuspension(m,p,n,o,a,v,t,col,to,ts,f,cl,l,am)
local tu
for i=1,2 do
tu = to+vec2(ts.x*(i*.5-.25),ts.y*.5)
p = __doCone(m,p,n,o,a[i],v,t,col,tu,ts,f,cl,l,am)
end
return p
end
--[[
A "cone" is formed by taking a curve in space and joining each of its points to an apex.
If the original curve is made from line segments, the resulting cone has a natural triangulation which can be used to construct it as a mesh.
m mesh
p position in mesh
n number of points
o "internal" point (to ensure that normals point outwards)
a apex of cone
v table of base points
t table of texture points
col colour
to texture offset
ts not used
f faceted or not
cl closed curve or not
l light vector
--]]
function __doCone(m,p,n,o,a,v,t,col,to,ts,f,cl,l,am)
if f then
if cl then
return __doFacetedClosedCone(m,p,n,o,a,v,t,col,to,ts,l,am)
else
return __doFacetedOpenCone(m,p,n,o,a,v,t,col,to,ts,l,am)
end
else
if cl then
return __doSmoothClosedCone(m,p,n,o,a,v,t,col,to,ts,l,am)
else
return __doSmoothOpenCone(m,p,n,o,a,v,t,col,to,ts,l,am)
end
end
end
function __doFacetedClosedCone(m,p,n,o,a,v,t,col,to,ts,l,am)
local j,nml,c
for k=1,n do
j = k%n + 1
nml = (v[k] - a):cross(v[j] - a)
if nml:dot(a - o) < 0 then
nml = -nml
end
nml = nml:normalize()
c = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nml)))
__addTriangle(m,p,v[j],v[k],a,c,c,c,nml,nml,nml,to+t[j],to+t[k],to)
p = p + 3
end
return p
end
function __doFacetedOpenCone(m,p,n,o,a,v,t,col,to,ts,l,am)
local j,nml,c
for k=1,n-1 do
j = k + 1
nml = (v[k] - a):cross(v[j] - a)
if nml:dot(a - o) < 0 then
nml = -nml
end
nml = nml:normalize()
c = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nml)))
__addTriangle(m,p,v[j],v[k],a,c,c,c,nml,nml,nml,to+t[j],to+t[k],to)
p = p + 3
end
return p
end
function __doSmoothClosedCone(m,p,n,o,a,v,t,col,to,ts,l,am)
local j,nmla,nmlb,nmlc,cc,ca,nb,cb
nmlb = vec3(0,0,0)
nmlc = __discreteNormal(v[1],o,v[n],a,v[2])
cc = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nmlc)))
nb = vec3(0,0,0)
for k=1,n do
j = k%n + 1
nb = nb + __discreteNormal(v[j],o,v[k],a,v[j%n+1])
end
nb = nb:normalize()
cb = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nb)))
for k=1,n do
j = k%n + 1
nmla = nmlc
ca = cc
nmlc = __discreteNormal(v[j],o,v[k],a,v[j%n+1])
cc = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nmlc)))
__addTriangle(m,p,v[j],v[k],a,cc,ca,cb,nmlc,nmla,nmlb,to+t[j],to+t[k],to)
p = p + 3
end
return p
end
function __doSmoothOpenCone(m,p,n,o,a,v,t,col,to,ts,l,am)
local j,nmla,nmlb,nmlc,cc,ca,ll,nb,cb
ll = l:len()
nmlb = vec3(0,0,0)
nmlc = __discreteNormal(v[1],o,a,v[2])
cc = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nmlc)))
nb = vec3(0,0,0)
for k=1,n-2 do
j = k + 1
nb = nb + __discreteNormal(v[j],o,v[k],a,v[j%n+1])
end
nb = nb + __discreteNormal(v[n],o,v[n-1],a)
nb = nb:normalize()
cb = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nb)))
for k=1,n-2 do
j = k + 1
nmla = nmlc
ca = cc
nmlc = __discreteNormal(v[j],o,v[k],a,v[j%n+1])
cc = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nmlc)))
__addTriangle(m,p,v[j],v[k],a,cc,ca,cb,nmlc,nmla,nmlb,to+t[j],to+t[k],to)
p = p + 3
end
nmla = nmlc
ca = cc
nmlc = __discreteNormal(v[n],o,v[n-1],a)
cc = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nmlc)))
__addTriangle(m,p,v[n],v[n-1],a,cc,ca,cb,nmlc,nmla,nmlb,to+t[n],to+t[n-1],to)
return p + 3
end
function addPolygon(t)
t = t or {}
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting)
local p = t.position or (m.size + 1)
p = p + (1-p)%3
local ip = p
local col = t.colour or t.color or color(255, 255, 255, 255)
local f = true
local to = t.texOrigin or vec2(0,0)
local ts = t.texSize or vec2(1,1)
if t.faceted ~= nil then
f = t.faceted
end
local cl = true
if t.closed ~= nil then
cl = t.closed
end
local l,am
if rl then
l = vec3(0,0,0)
am = 1
else
l = t.light or vec3(0,0,0)
if t.intensity then
l = l:normalize()*t.intensity
elseif l:lenSqr() > 1 then
l = l:normalize()
end
am = t.ambience or (1 - l:len())
end
local v = t.vertices or {}
local c = vec3(0,0,0)
local n = 0
for k,u in ipairs(v) do
c = c + u
n = n + 1
end
local size = p-1+3*n+3
if not closed then
size = size - 3
end
if m.size<size then
m:resize(size)
end
c = c/n
local cv = {}
for k,u in ipairs(v) do
table.insert(cv,u-c)
end
local nml = vec3(0,0,0)
for k=2,n do
nml = nml + cv[k]:cross(cv[k-1])
end
if cl then
nml = nml + cv[1]:cross(cv[n])
end
nml = nml:normalize()
local o = t.viewFrom or 1
if type(o) == "number" then
o = o * nml + c
end
local tx
if t.texCoords then
tx = t.texCoords
else
tx = {}
local mx = (cv[1] - cv[1]:dot(nml)*nml):normalize()
local my = nml:cross(mx):normalize()
for k,u in ipairs(cv) do
table.insert(tx,vec2(u:dot(mx),u:dot(my)))
end
mx,my = tx[1],tx[1]
for k,u in ipairs(tx) do
mx.x = math.min(mx.x,u.x)
mx.y = math.min(mx.y,u.y)
my.x = math.max(my.x,u.x)
my.y = math.max(my.y,u.y)
end
for k,u in ipairs(tx) do
tx[k] = vec2((u.x-mx.x)/(my.x-mx.x),(u.y-mx.y)/(my.y-mx.y))
end
end
p = __doPoly(m,p,n,o,v,tx,col,to,ts,f,cl,l,am)
if rt then
return m,ip,p
else
return m
end
end
--[[
This forms a surface which has boundary a given curve by forming a cone with the barycentre of the curve as its apex.
m mesh
p position in mesh
n number of points
o "internal" point (for normals)
v table of base points
t table of texture points
col colour
to texture offset
ts not used
f faceted or not
cl closed curve or not
l light vector
--]]
function __doPoly(m,p,n,o,v,t,col,to,ts,f,cl,l,am)
local a,b,r = vec3(0,0,0),vec2(0,0),0
for k,u in ipairs(v) do
a = a + u
r = r + 1
end
a = a / r
for k,u in ipairs(t) do
b = b + u
end
b = b / r
for k=1,r do
t[k] = t[k] - b
end
return __doCone(m,p,n,o,a,v,t,col,to+b,ts,f,cl,l,am)
end
--[[
| Option | Default | Description |
|:-------|:--------|:------------|
| `mesh` | new mesh | The mesh to add the shape to. |
| `position` | end of mesh | The place in the mesh at which to add the shape. |
| `colour`/`color` | white | The colour of the shape. |
| `faceted` | true | Whether to make it faceted or smooth. |
| `ends` | `0` | Which ends to fill in (`0` for none, `1` for start, `2` for end, `3` for both) |
| `texOrigin` | `vec2(0,0)` | If using a sprite sheet, this is the lower left corner of the rectangle associated with this shape. |
| `texSize` | `vec2(1,1)` | This is the width and height of the rectangle of the texture associated to this shape. |
There are various ways to specify the dimensions of the cylinder.
If given together, the more specific overrides the more general.
`radius` and `height` (`number`s) can be combined with `axes` (table of three `vec3`s) to specify the dimensions, where the first axis vector lies along the cylinder. The vector `origin` then locates the cylinder in space.
`startCentre`/`startCenter` (a `vec3`), `startWidth` (`number` or `vec3`), `startHeight` (`number` or `vec3`), `startRadius` (`number`) specify the dimensions at the start of the cylinder (if numbers, they are taken with respect to certain axes).
Similarly named options control the other end.
If axes are needed, these can be supplied via the `axes` option.
If just the `axis` option is given (a single `vec3`), this is the direction along the cylinder.
Other directions (if needed) are found by taking orthogonal vectors to this axis.
--]]
function addCylinder(t)
t = t or {}
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting)
local p = t.position or (m.size + 1)
p = p + (1-p)%3
local ip = p
local col = t.colour or t.color or color(255, 255, 255, 255)
local f = true
local ends
local solid = t.solid
if solid then
ends = t.ends or 3
else
ends = t.ends or 0
end
if t.faceted ~= nil then
f = t.faceted
end
local l,am
if rl then
l = vec3(0,0,0)
am = 1
else
l = t.light or vec3(0,0,0)
if t.intensity then
l = l:normalize()*t.intensity
elseif l:lenSqr() > 1 then
l = l:normalize()
end
am = t.ambience or (1 - l:len())
end
local r = t.radius or 1
local h = t.height or 1
local to = t.texOrigin or vec2(0,0)
local ts = t.texSize or vec2(1,1)
local sc,si,sj,ec,ei,ej,a,o
if t.axis or t.axes or t.origin or t.centre or t.center then
if t.axis then
a = t.axis
elseif t.axes then
a = t.axes[1]
else
a = vec3(0,1,0)
end
if t.height then
a = h*a:normalize()
end
if t.origin or t.centre or t.center then
local o = t.origin or t.centre or t.center
sc,ec = o - a/2,o + a/2
end
end
sc = t.startCentre or t.startCenter or sc
ec = t.endCentre or t.endCenter or ec
sc,ec,a = __threeFromTwo(sc,ec,a,vec3(0,-h/2,0),vec3(0,h/2,0),vec3(0,h,0))
si = t.startWidth or t.startRadius or t.radius or 1
sj = t.startHeight or t.startRadius or t.radius or 1
ei = t.endWidth or t.endRadius or t.radius or 1
ej = t.endHeight or t.endRadius or t.radius or 1
local c,d
if t.axes then
a,c,d = unpack(t.axes)
end
if type(si) == "number" then
if type(sj) == "number" then
if not c then
c = __orthogonalTo(a)
end
si = si*c:normalize()
if not d then
sj = sj*a:cross(c):normalize()
else
sj = sj*d:normalize()
end
else
si = si*sj:cross(a):normalize()
end
elseif type(sj) == "number" then
sj = sj*a:cross(si):normalize()
end
if type(ei) == "number" then
if type(ej) == "number" then
if not c then
c = __orthogonalTo(a)
end
ei = ei*c:normalize()
if not d then
ej = ej*a:cross(c):normalize()
else
ej = ej*d:normalize()
end
else
ei = ei*ej:cross(a):normalize()
end
elseif type(ej) == "number" then
ej = ej*a:cross(ei):normalize()
end
local n = t.size or 12
local sa,ea,da = __threeFromTwo(t.startAngle,t.endAngle,t.deltaAngle,0,360,360)
local closed
if da == 360 then
closed = true
solid = false
else
closed = false
end
sa = math.rad(sa)
ea = math.rad(ea)
da = math.rad(da)/n
o = (sc + math.cos((sa+ea)/2)*si/2 + math.sin((sa+ea)/2)*sj/2 + ec + math.cos((sa+ea)/2)*ei/2 + math.sin((sa+ea)/2)*ej/2)/2
local ss = 1 + math.floor((ends+1)/2)
if solid then
ss = ss + 2
end
ts.x = ts.x / ss
local cs,sn,ti,tj
ti,tj = vec2(ts.x/2,0),vec2(0,ts.y/2)
cs = math.cos(sa)
sn = math.sin(sa)
si,sj = cs*si + sn*sj, -sn*si + cs*sj
ei,ej = cs*ei + sn*ej, -sn*ei + cs*ej
ti,tj = cs*ti + sn*tj, -sn*ti + cs*tj
local u,v,tu,tv,tw,cnrs = {},{},{},{},{},{}
cnrs[1] = {sc,sc+si,ec+ei,ec}
cs = math.cos(da)
sn = math.sin(da)
u[0] = sc+cs*si - sn*sj
v[0] = ec+cs*ei - sn*ej
for k=0,n do
table.insert(u,sc+si)
table.insert(v,ec+ei)
table.insert(tu,to + vec2(ts.x*k/n,0))
table.insert(tv,to + vec2(ts.x*k/n,ts.y))
table.insert(tw,ti)
si,sj = cs*si + sn*sj, -sn*si + cs*sj
ei,ej = cs*ei + sn*ej, -sn*ei + cs*ej
ti,tj = cs*ti + sn*tj, -sn*ti + cs*tj
end
u[n+2] = sc+cs*si + sn*sj
v[n+2] = ec+cs*ei + sn*ej
cnrs[2] = {sc, sc+cs*si - sn*sj, ec+cs*ei - sn*ej, ec}
local size = 6*n + math.floor((ends+1)/2)*3*n
if closed then
size = size + math.floor((ends+1)/2)*3
elseif solid then
size = size + 24
end
if p - 1 + size > m.size then
m:resize(p-1+size)
end
n = n + 1
p = __doCylinder(m,p,n,o,u,v,tu,tv,col,f,closed,l,am)
to = to + ts/2
if solid and not closed then
local tex = {-ts/2,vec2(ts.x/2,-ts.y/2),ts/2,vec2(-ts.x/2,ts.y/2)}
for i=1,2 do
to.x = to.x + ts.x
p = __doPoly(m,p,4,o,cnrs[i],tex,col,to,ts,f,true,l,am)
end
end
if ends%2 == 1 then
to.x = to.x + ts.x
p = __doCone(m,p,n,o,sc,u,tw,col,to,ts,f,closed,l,am)
end
if ends >= 2 then
to.x = to.x + ts.x
p = __doCone(m,p,n,o,ec,v,tw,col,to,ts,f,closed,l,am)
end
if ret then
return m,ip, p
else
return m
end
end
--[[
This adds a cylinder to the mesh.
m mesh to add shape to
p position of first vertex of shape
n number of points
o centre of shape (vec3)
a apexes (table of 2 vec3s)
v vertices (table of vec3s in cyclic order)
t texture coordinates corresponding to vertices (relative to centre)
col colour
to offset of texture region (vec2)
ts size of texture region (vec2)
f faceted
cl closed
l light vector
--]]
function __doCylinder(m,p,n,o,u,v,ut,vt,col,f,cl,l,am)
if f then
if cl then
return __doFacetedClosedCylinder(m,p,n-1,o,u,v,ut,vt,col,l,am)
else
return __doFacetedOpenCylinder(m,p,n,o,u,v,ut,vt,col,l,am)
end
else
if cl then
return __doSmoothClosedCylinder(m,p,n-1,o,u,v,ut,vt,col,l,am)
else
return __doSmoothOpenCylinder(m,p,n,o,u,v,ut,vt,col,l,am)
end
end
end
function __doFacetedClosedCylinder(m,p,n,o,u,v,ut,vt,col,l,am)
local i,j,nv,nu,cu,cv,ll
ll = l:len()
for k=1,n do
j = k%n + 1
nu = (u[j] - u[k]):cross(v[k] - u[k]):normalize()
nv = (v[j] - v[k]):cross(u[k] - v[k]):normalize()
if nu:dot(u[k]-o) < 0 then
nu = -nu
end
if nv:dot(v[k]-o) < 0 then
nv = -nv
end
cv = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv)))
cu = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu)))
__addTriangle(m,p,v[j],v[k],u[j],cv,cv,cu,nv,nv,nu,vt[j],vt[k],ut[j])
p = p + 3
__addTriangle(m,p,v[k],u[j],u[k],cv,cu,cu,nv,nu,nu,vt[k],ut[j],ut[k])
p = p + 3
end
return p
end
function __doFacetedOpenCylinder(m,p,n,o,u,v,ut,vt,col,l,am)
local i,j,nv,nu,cu,cv,ll
ll = l:len()
for k=1,n-1 do
j = k + 1
nu = (u[j] - u[k]):cross(v[k] - u[k]):normalize()
nv = (v[j] - v[k]):cross(u[k] - v[k]):normalize()
if nu:dot(u[k]-o) < 0 then
nu = -nu
end
if nv:dot(v[k]-o) < 0 then
nv = -nv
end
cv = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv)))
cu = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu)))
__addTriangle(m,p,v[j],v[k],u[j],cv,cv,cu,nv,nv,nu,vt[j],vt[k],ut[j])
p = p + 3
__addTriangle(m,p,v[k],u[j],u[k],cv,cu,cu,nv,nu,nu,vt[k],ut[j],ut[k])
p = p + 3
end
return p
end
function __doSmoothClosedCylinder(m,p,n,o,u,v,ut,vt,col,l,am)
local i,j,nv,nu,cu,cv
nv,nu,cv,cu = {},{},{},{}
nv[1] = __discreteNormal(v[1],o,v[n],u[1],v[2])
nu[1] = __discreteNormal(u[1],o,u[n],v[1],u[2])
cv[1] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv[1])))
cu[1] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu[1])))
for k=1,n do
j = k%n + 1
i = j%n + 1
nv[j] = __discreteNormal(v[j],o,v[k],u[j],v[i])
nu[j] = __discreteNormal(u[j],o,u[k],v[j],u[i])
cv[j] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv[j])))
cu[j] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu[j])))
__addTriangle(m,p,v[j],v[k],u[j],cv[j],cv[k],cu[j],nv[j],nv[k],nu[j],vt[j],vt[k],ut[j])
p = p + 3
__addTriangle(m,p,v[k],u[j],u[k],cv[k],cu[j],cu[k],nv[k],nu[j],nu[k],vt[k],ut[j],ut[k])
p = p + 3
end
return p
end
function __doSmoothOpenCylinder(m,p,n,o,u,v,ut,vt,col,l,am)
local i,j,nv,nu,cu,cv
nv,nu,cv,cu = {},{},{},{}
nv[1] = __discreteNormal(v[1],o,v[0],u[1],v[2])
nu[1] = __discreteNormal(u[1],o,v[0],v[1],u[2])
cv[1] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv[1])))
cu[1] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu[1])))
for k=1,n-2 do
j = k + 1
i = j + 1
nv[j] = __discreteNormal(v[j],o,v[k],u[j],v[i])
nu[j] = __discreteNormal(u[j],o,u[k],v[j],u[i])
cv[j] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv[j])))
cu[j] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu[j])))
__addTriangle(m,p,v[j],v[k],u[j],cv[j],cv[k],cu[j],nv[j],nv[k],nu[j],vt[j],vt[k],ut[j])
p = p + 3
__addTriangle(m,p,v[k],u[j],u[k],cv[k],cu[j],cu[k],nv[k],nu[j],nu[k],vt[k],ut[j],ut[k])
p = p + 3
end
nv[n] = __discreteNormal(v[n],o,v[n-1],u[n],v[n+1])
nu[n] = __discreteNormal(u[n],o,u[n-1],v[n],u[n+1])
cv[n] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nv[n])))
cu[n] = col:mix(color(0,0,0,col.a),am+(1-am)*math.max(0,l:dot(nu[n])))
__addTriangle(m,p,v[n],v[n-1],u[n],cv[n],cv[n-1],cu[n],nv[n],nv[n-1],nu[n],vt[n],vt[n-1],ut[n])
p = p + 3
__addTriangle(m,p,v[n-1],u[n],u[n-1],cv[n-1],cu[n],cu[n-1],nv[n-1],nu[n],nu[n-1],vt[n-1],ut[n],ut[n-1])
return p+3
end
--[[
This works out a normal vector for a vertex in a triangulated surface by taking an average of the triangles in which it appears.
The normals are weighted by the reciprocal of the size of the corresponding triangle.
a vertex under consideration
o a point to determine which side the normals lie
... a cyclic list of vertices, successive pairs of which make up the triangles
--]]
function __discreteNormal(a,o,...)
local arg = {}
local n = 0
for k,v in ipairs({...}) do
if v then
table.insert(arg,v)
n = n + 1
end
end
local na,nb
na = vec3(0,0,0)
for k=2,n do
nb = (arg[k] - a):cross(arg[k-1] - a)
na = na + nb/nb:lenSqr()
end
na = na:normalize()
if na:dot(a-o) < 0 then
na = -na
end
return na
end
--[[
Adds a pyramid to a mesh.
| Option | Default | Description |
|:-------------|:-------------------------|:------------|
| `mesh` | new mesh | The mesh to add the shape to. |
| `position` | end of mesh | The position in the mesh at which to start the shape. |
| `origin` | `vec3(0,0,0)` | The origin (or centre) of the shape. |
| `axis` | `vec3(0,1,0)` | The axis specifies the direction of the jewel. |
| `aspect` | 1 | The ratio of the height to the diameter of the gem. |
| `size` | the length of the axis | The size of the jewel; specifically the distance from the centre to the apex of the jewel. |
| `colour`/`color` | `color(255, 255, 255, 255)` | The colour of the jewel. |
| `texOrigin` | `vwc2(0,0)` | If using a sprite sheet, this is the lower left corner of the rectangle associated with this gem. |
| `texSize` | `vec2(1,1)` | This is the width and height of the rectangle of the texture associated to this gem.
--]]
function addPyramid(t)
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting)
local p = t.position or (m.size + 1)
p = p + (1-p)%3
local o = t.origin or vec3(0,0,0)
local c = t.colour or t.color or color(255, 255, 255, 255)
local f = true
if t.faceted ~= nil then
f = t.faceted
end
local l,am
if rl then
l = vec3(0,0,0)
am = 1
else
l = t.light or vec3(0,0,0)
if t.intensity then
l = l:normalize()*t.intensity
elseif l:lenSqr() > 1 then
l = l:normalize()
end
am = t.ambience or (1 - l:len())
end
local as = t.aspect or 1
local to = t.texOrigin or vec2(0,0)
local ts = t.texSize or vec2(1,1)
local a = t.axes or {}
a[1] = t.apex or a[1] or vec3(0,1,0)
if t.size then
a[1] = a[1]:normalize()*t.size
end
if not a[2] then
local ax,ay,az = math.abs(a[1].x),math.abs(a[1].y),math.abs(a[1].z)
if ax < ay and ax < az then
a[2] = vec3(0,a[1].z,-a[1].y)
elseif ay < az then
a[2] = vec3(a[1].z,0,-a[1].x)
else
a[2] = vec3(a[1].y,-a[1].x,0)
end
a[3] = a[1]:cross(a[2])
end
local la = a[1]:len()
for i = 2,3 do
a[i] = as*la*a[i]:normalize()
end
local n = t.sides or 12
if p > m.size - 6*n then
m:resize(p + 6*n-1)
end
local np = __doPyramid(m,p,n,o,a,c,to,ts,f,l,am)
if ret then
return m,p,np
else
return m
end
end
--[[
A pyramid is a special case of a suspension.
m mesh
p position
n number of points
o origin
a apex
col colour
to texture offset
ts texture size
f faceted
l light vector
--]]
function __doPyramid(m,p,n,o,a,col,to,ts,f,l,am)
local th = 2*math.pi/n
local cs = math.cos(th)
local sn = math.sin(th)
local b,c,d,tb,tc,td,tex,pol
tex,pol={},{}
c = cs*a[2] + sn*a[3]
d = -sn*a[2] + cs*a[3]
tc = cs*vec2(ts.x*.25,0) + sn*vec2(0,ts.y*.5)
td = -sn*vec2(ts.x*.25,0) + cs*vec2(0,ts.y*.5)
for i = 1,n do
table.insert(pol,o+c)
table.insert(tex,tc)
c,d = cs*c + sn*d, -sn*c + cs*d
tc,td = cs*tc + sn*td, -sn*tc + cs*td
end
return __doSuspension(m,p,n,o+a[1]/2,{o+a[1],o},pol,tex,col,to,ts,f,true,l,am)
end
-- block faces are in binary order: 000, 001, 010, 011 etc
local BlockFaces = {
{1,2,3,4},
{5,7,6,8},
{1,5,2,6},
{3,4,7,8},
{2,6,4,8},
{1,3,5,7}
}
local BlockTex = {
vec2(0,0),vec2(1/6,0),vec2(0,1),vec2(1/6,1)
}
--[[
Adds a block to a mesh.
| Option | Default | Description |
|:-------|:--------|:------------|
| `mesh` | new mesh | Mesh to use to add shape to. |
| `position` | end of mesh | Position in mesh to add shape at. |
| `colour`/`color` | color(255, 255, 255, 255) | Colour or colours to use. Can be a table of colours, one for each vertex of the block. |
| `faces` | all | Which faces to render |
| `texOrigin` | `vec2(0,0)` | Lower left corner of region on texture. |
| `texSize` | `vec2(1,1)` | Width and height of region on texture. |
| `singleImage` | `false` | Uses the same image for all sides. |
There are a few ways of specifying the dimensions of the "block".
`centre`/`center`, `width`, `height`, `depth`, `size`. This defines the "block" by specifying a centre followed by the width, height, and depth of the cube (`size` sets all three). These can be `vec3`s or numbers. If numbers, they correspond to the dimensions of the "block" in the `x`, `y`, and `z` directions respectively. If `vec3`s, then are used to construct the vertices by adding them to the centre so that the edges of the "block" end up parallel to the given vectors.
`startCentre`/`startCenter`, `startWidth`, `startHeight`, `endCentre`/`endCenter`, `endWidth`, `endHeight`. This defined the "block" by defining two opposite faces of the cube and then filling in the region in between. The two faces are defined by their centres, widths, and heights. The widths and heights can be numbers or `vec3`s exactly as above.
`block`. This is a table of eight vertices defining the block. The vertices are listed in binary order, in that if you picture the vertices of the standard cube of side length `1` with one vertex at the origin, the vertex with coordinates `(a,b,c)` is number a + 2b + 4c + 1 in the table (the `+1` is because lua tables are 1-based).
--]]
function addBlock(t)
local m,ret,rl = __initmesh(t.mesh, t.light, t.ambience, t.intensity, t.texture, t.basicLighting)
local p = t.position or (m.size + 1)
p = p + (1-p)%3
local c = t.colour or t.color or color(255, 255, 255, 255)
if type(c) == "userdata" then
c = {c,c,c,c,c,c,c,c}
end
local f = t.faces or BlockFaces
local to = t.texOrigin or vec2(0,0)
local ts = t.texSize or vec2(1,1)
local dt = 1
if t.singleImage then
dt = 0
ts.x = ts.x * 6
end
local l,am
if rl then
l = vec3(0,0,0)
am = 1
else
l = t.light or vec3(0,0,0)
if t.intensity then
l = l:normalize()*t.intensity
elseif l:lenSqr() > 1 then
l = l:normalize()
end
am = t.ambience or (1 - l:len())
end
local v
if t.block then
v = t.block
elseif t.center or t.centre then
local o = t.center or t.centre
local w,h,d = t.width or t.size or 1, t.height or t.size or 1, t.depth or t.size or 1
w,h,d=w/2,h/2,d/2
if type(w) == "number" then
w = vec3(w,0,0)
end
if type(h) == "number" then
h = vec3(0,h,0)
end
if type(d) == "number" then
d = vec3(0,0,d)
end
v = {
o - w - h - d,
o + w - h - d,
o - w + h - d,
o + w + h - d,
o - w - h + d,
o + w - h + d,
o - w + h + d,
o + w + h + d
}
elseif t.startCentre or t.startCenter then
local sc = t.startCentre or t.startCenter
local ec = t.endCentre or t.endCenter
local sw = t.startWidth
local sh = t.startHeight
local ew = t.endWidth
local eh = t.endHeight
if type(sw) == "number" then
sw = vec3(sw,0,0)
end
if type(sh) == "number" then
sh = vec3(0,sh,0)
end
if type(sc) == "number" then
sc = vec3(0,0,sc)
end
if type(ew) == "number" then
ew = vec3(ew,0,0)
end
if type(eh) == "number" then
eh = vec3(0,eh,0)
end
if type(ec) == "number" then
ec = vec3(0,0,ec)
end
v = {
sc - sw - sh,
sc + sw - sh,
sc - sw + sh,
sc + sw + sh,
ec - ew - eh,
ec + ew - eh,
ec - ew + eh,
ec + ew + eh
}
else
v = {
vec3(-1,-1,-1)/2,
vec3(1,-1,-1)/2,
vec3(-1,1,-1)/2,
vec3(1,1,-1)/2,
vec3(-1,-1,1)/2,
vec3(1,-1,1)/2,
vec3(-1,1,1)/2,
vec3(1,1,1)/2
}
end
if p > m.size - 36 then
m:resize(p + 35)
end
local np = __doBlock(m,p,f,v,c,to,ts,dt,l,am)
if ret then
return m,p,np
else
return m
end
end
--[[
m mesh
p index of first vertex to be used
f table of faces of block
v table of vertices of block
c table of colours of block (per vertex of block)
to offset for this shape's segment of the texture
ts size of this shape's segment of the texture
dt step size for the texture tiling
l light vector
--]]
function __doBlock(m,p,f,v,c,to,ts,dt,l,am)
local n,t,tv
t = 0
l = l / 2
for k,w in ipairs(f) do
n = (v[w[3]] - v[w[1]]):cross(v[w[2]] - v[w[1]]):normalize()
for i,u in ipairs({1,2,3,2,3,4}) do