-
Notifications
You must be signed in to change notification settings - Fork 0
/
create_vec.lua
266 lines (227 loc) · 6.19 KB
/
create_vec.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
-- TODO match with cpp/Tensor and call these int2/3/4 float2/3/4 double2/3/4 instead of vec2/3/4i/f/d ...
--[[
TODO find who uses this
rename .typeCode to .code
rename .type to .name
get rid of sizeof ?
--]]
local ffi = require 'ffi'
local table = require 'ext.table'
local template = require 'template'
local showcode = require 'template.showcode'
local suffix = require 'vec-ffi.suffix'
--[[
args:
dim = vector dimension
ctype = vector element type
vectype = (optional) vector class name. default = vec<dim><suffix>_t
fields = (optional) list of fields to use. default = xyzw.
suffix = (optional) suffix of classname. defaults are above.
classCode = (optional) additional functions to put in the metatable
--]]
return function(args)
assert(args)
args = table(args)
assert(args.dim)
assert(args.ctype)
args.suffix = args.suffix or suffix[args.ctype]
args.classCode = args.classCode or ''
args.vectype = args.vectype or 'vec'..args.dim..args.suffix..'_t'
args.fields = (args.fields or table{'x', 'y', 'z', 'w'}):sub(1, args.dim)
local code = template([=[
local ffi = require 'ffi'
local math = require 'ext.math'
local args = ...
local cl = args.cl
local dim = args.dim
local vectype = args.vectype
local ctype = args.ctype
local metatype
local function modifyMetatable(cl)
cl.elemType = ctype
cl.dim = dim
-- TODO get rid of this one? just use ffi.sizeof ?
-- or move this back to struct?
cl.sizeof = ffi.sizeof(cl.name)
-- TODO no more :set on cdata or table, just on raw values
-- use separate methods for the others
-- prevent some if conditions
cl.set = function(self, v, v2, ...)
if type(v) == 'cdata' then
<?=fields:mapi(function(x) return 'self.'..x..' = v.'..x end):concat(' ')?>
elseif type(v) == 'table' then
<?=fields:mapi(function(x,key) return 'self.'..x..' = v['..key..']' end):concat(' ')?>
else
if v2 == nil then
<?=fields:mapi(function(x,key) return 'self.'..x..' = v' end):concat(' ')?>
else
local args = {v, v2, ...}
assert(#args >= <?=dim?>)
<?=fields:mapi(function(x,key) return 'self.'..x..' = args['..key..']' end):concat(' ')?>
end
end
return self
end
-- from here on our, vec-specific functions:
<? -- operations that are per-component or scalar
local table = require 'ext.table'
local op = require 'ext.op'
local opnames = table{
'add', 'sub', 'mul', 'div', 'mod', 'pow',
-- >= 5.3
'idiv', 'band', 'bor', 'bxnot', 'shl', 'shr',
}
local opinfos = opnames:mapi(function(name,_,t)
local symbol = op.symbols[name]
if symbol then
return {name=name, symbol=symbol}, #t+1
end
end)
-- TODO unary operators
for _,info in ipairs(opinfos) do
?> cl.__<?=info.name?> = function(a,b)
if type(b) == 'cdata' then
if type(a) == 'number' then
return metatype(<?=fields:mapi(function(x)
return 'a'..info.symbol..'b.'..x
end):concat(', ')?>)
end
return metatype(<?=fields:mapi(function(x)
return 'a.'..x..info.symbol..'b.'..x
end):concat(', ')?>)
end
b = tonumber(b)
if b == nil then
error("can't handle "..tostring(b).." (type "..type(b)..")")
end
return metatype(<?=fields:mapi(function(x)
return 'a.'..x..info.symbol..'b'
end):concat(', ')?>)
end
<? end
?>
cl.__unm = function(v)
return v * -1
end
cl.map = function(v, m)
return metatype(<?=
fields:mapi(function(x,i)
return 'm(v.'..x..', '..(i-1)..')'
end):concat', '
?>)
end
cl.lenSq = function(a)
return a:dot(a)
end
cl.length = function(a)
return math.sqrt(a:lenSq())
end
cl.dot = function(a,b)
return <?=
fields:mapi(function(x) return 'a.'..x..' * b.'..x end):concat(' + ')
?> end
cl.normalize = function(v)
return v / v:length()
end
-- naming compat with Matlab/matrix
cl.normSq = function(a)
return a:dot(a)
end
cl.norm = function(a)
return math.sqrt(a:lenSq())
end
cl.unit = function(v)
return v / v:length()
end
cl.unitOrZeroEpsilon = 1e-7
-- useful for surface normal / quaternion angle/axis
cl.unitOrZero = function(v, eps)
eps = eps or metatype.unitOrZeroEpsilon
local vlen = v:norm()
if vlen <= eps or not math.isfinite(vlen) then
return metatype(), vlen
end
return v / vlen, vlen
end
cl.lInfLength = function(v) -- L-infinite length
local fp = v.s
local dist = math.abs(fp[0])
for i=1,<?=dim?>-1 do
dist = math.max(dist, math.abs(fp[i]))
end
return dist
end
cl.l1Length = function(v) --L-1 length
local fp = v.s
local dist = math.abs(fp[0])
for i=1,<?=dim?>-1 do
dist = dist + math.abs(fp[i])
end
return dist
end
-- TODO call this 'product'
cl.volume = function(v)
return <?=fields:mapi(function(x) return 'v.'..x end):concat(' * ')?>
end
-- normal first so the function arguments can be 1:1 with plane project
cl.project = function(n, v)
return v - n * (n:dot(v) / n:dot(n))
end
-- allow the caller to override/add any functions
]=]
-- concat so the classCode can contain templated code
..args.classCode
..[=[
end
-- do this declartion inside the templated code
-- so that we can also use the same scope for storing `metatype`
-- and also reference that within the subclass-provided `classCode`
local range = require 'ext.range'
local struct = require 'struct'
metatype = struct{
name = args.vectype,
union = true,
fields = {
-- struct has to come first for the ffi api to allow component initialization
{
type = struct{
anonymous = true,
fields = args.fields:mapi(function(fieldname)
return {name=fieldname, type=args.ctype}
end),
},
},
{
type = struct{
anonymous = true,
fields = range(0, dim-1):mapi(function(i)
return {name='s'..i, type=args.ctype}
end),
},
no_iter = true,
},
{
name = 's',
type = args.ctype..'['..#args.fields..']',
no_iter = true,
},
},
metatable = modifyMetatable,
}
return metatype
]=], args)
local func, msg = load(code)
if not func then
error('\n'..showcode(code)..'\n'..msg)
end
local metatype = func(args)
do
local vecsize = ffi.sizeof(args.vectype)
local elemsize = ffi.sizeof(args.ctype)
if vecsize ~= args.dim * elemsize then
print(metatype.code)
error("struct sizes mismatch, expected ffi.sizeof("..args.vectype..") = "..vecsize.." to equal args.dim = "..args.dim.." * ffi.sizeof("..args.ctype..") = "..elemsize)
end
end
return metatype
end