-
Notifications
You must be signed in to change notification settings - Fork 22
/
affine2d.lua
232 lines (195 loc) · 5.12 KB
/
affine2d.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
--2D affine transforms.
--Transcribed from cairo by Cosmin Apreutesei. Public Domain.
if not ... then require'affine2d_test'; return end
local min, max, abs, sin, cos, tan, floor =
math.min, math.max, math.abs, math.sin, math.cos, math.tan, math.floor
local epsilon = 1e-15
local function snap(x)
return
(abs(x) < epsilon and 0) or
(abs(x-1) < epsilon and 1) or
(abs(x+1) < epsilon and -1) or x
end
local function new(xx, yx, xy, yy, x0, y0)
if not xx then
xx, yx, xy, yy, x0, y0 = 1, 0, 0, 1, 0, 0
end
local mt = {}
function mt:set(xx1, yx1, xy1, yy1, x01, y01)
xx, yx, xy, yy, x0, y0 = xx1, yx1, xy1, yy1, x01, y01
return mt
end
function mt:reset()
xx, yx, xy, yy, x0, y0 = 1, 0, 0, 1, 0, 0
return mt
end
function mt:unpack()
return xx, yx, xy, yy, x0, y0
end
function mt:copy()
return new(xx, yx, xy, yy, x0, y0)
end
function mt:transform_point(x, y)
return xx * x + xy * y + x0,
yx * x + yy * y + y0
end
function mt:transform_distance(x, y)
return xx * x + xy * y,
yx * x + yy * y
end
--multiply mt * b and store result in mt
function mt:multiply(bxx, byx, bxy, byy, bx0, by0)
xx, yx, xy, yy, x0, y0 =
xx * bxx + yx * bxy,
xx * byx + yx * byy,
xy * bxx + yy * bxy,
xy * byx + yy * byy,
x0 * bxx + y0 * bxy + bx0,
x0 * byx + y0 * byy + by0
return mt
end
--multiply b * mt and store result in mt
function mt:transform(bxx, byx, bxy, byy, bx0, by0)
xx, yx, xy, yy, x0, y0 =
bxx * xx + byx * xy,
bxx * yx + byx * yy,
bxy * xx + byy * xy,
bxy * yx + byy * yy,
bx0 * xx + by0 * xy + x0,
bx0 * yx + by0 * yy + y0
return mt
end
function mt:determinant()
return xx * yy - yx * xy
end
local determinant = mt.determinant
function mt:is_invertible()
local det = determinant()
return det ~= 0 and det ~= 1/0 and det ~= -1/0
end
function mt:scalar_multiply(t)
xx = xx * t
yx = yx * t
xy = xy * t
yy = yy * t
x0 = x0 * t
y0 = y0 * t
return mt
end
function mt:inverse()
--scaling/translation-only matrices are easier to invert
if xy == 0 and yx == 0 then
local xx, yx, xy, yy, x0, y0 =
xx, yx, xy, yy, x0, y0
x0 = -x0
y0 = -y0
if xx ~= 1 then
if xx == 0 then return end
xx = 1 / xx
x0 = x0 * xx
end
if yy ~= 1 then
if yy == 0 then return end
yy = 1 / yy
y0 = y0 * yy
end
return new(xx, yx, xy, yy, x0, y0)
end
--inv (A) = 1/det (A) * adj (A)
local det = determinant()
if det == 0 or det == 1/0 or det == -1/0 then return end
--adj (A) = transpose (C:cofactor (A,i,j))
local a, b, c, d, tx, ty = xx, yx, xy, yy, x0, y0
return new(d, -b, -c, a, c*ty - d*tx, b*tx - a*ty):scalar_multiply(1 / det)
end
function mt:translate(x, y)
x0 = x0 + x * xx + y * xy
y0 = y0 + x * yx + y * yy
return mt
end
function mt:scale(sx, sy)
sy = sy or sx
xx = sx * xx
yx = sx * yx
xy = sy * xy
yy = sy * yy
return mt
end
function mt:skew(ax, ay)
ax, ay = tan(ax), tan(ay)
xx, yx, xy, yy =
xx + ay * xy,
yx + ay * yy,
ax * xx + xy,
ax * yx + yy
return mt
end
function mt:rotate(a)
local s, c = snap(sin(a)), snap(cos(a))
xx, yx, xy, yy =
c * xx + s * xy,
c * yx + s * yy,
-s * xx + c * xy,
-s * yx + c * yy
return mt
end
function mt:rotate_around(cx, cy, a)
return mt:translate(cx, cy):rotate(a):translate(-cx, -cy)
end
function mt:scale_around(cx, cy, sx, sxy)
return mt:translate(cx, cy):scale(sx, sy):translate(-cx, -cy)
end
--check that the matrix is the identity matrix, thus having no effect.
function mt:is_identity()
return xx == 1 and yy == 1 and yx == 0 and xy == 0 and x0 == 0 and y0 == 0
end
--check that no scaling is done with this transform, only flipping and
--multiple-of-90deg rotation.
function mt:has_unity_scale()
return
((xy == 0 and yx == 0) and (xx == 1 or xx == -1) and (yy == 1 or yy == -1)) or
((xx == 0 and yy == 0) and (xy == 1 or xy == -1) and (yx == 1 or yx == -1))
end
--check that scaling with this transform is uniform on both axes.
function mt:has_uniform_scale()
return abs(xx) == abs(yy) and xy == yx
end
--the scale factor is the largest dimension of the bounding box of the
--transformed unit square.
function mt:scale_factor()
local w = max(0, xx, xy, xx + xy) - min(0, xx, xy, xx + xy)
local h = max(0, yx, yy, yx + yy) - min(0, yx, yy, yx + yy)
return max(w,h)
end
--check that pixels map 1:1 with this transform so that no filtering
--is necessary.
local has_unity_scale = mt.has_unity_scale
function mt:is_pixel_exact()
return has_unity_scale() and floor(x0) == x0 and floor(y0) == y0
end
--check that there's no skew and that there's no rotation other than
--multiple-of-90-deg. rotation.
function mt:is_straight()
return (xy == 0 and yx == 0) or (xx == 0 and yy == 0)
end
local function __mul(a, b)
return a:copy():multiply(b:unpack())
end
local function __eq(a, b)
local a1, b1, c1, d1, e1, f1 = a:unpack()
local a2, b2, c2, d2, e2, f2 = b:unpack()
return a1 == a2
and b1 == b2
and c1 == c2
and d1 == d2
and e1 == e2
and f1 == f2
end
setmetatable(mt, {
__mul = __mul,
__eq = __eq,
__call = mt.transform_point,
})
return mt
end
return new