1 /*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/core/SkM44.h"
9 #include "include/core/SkMatrix.h"
10 #include "include/private/SkVx.h"
11
12 #include "src/core/SkMatrixInvert.h"
13 #include "src/core/SkMatrixPriv.h"
14 #include "src/core/SkPathPriv.h"
15
16 using sk4f = skvx::Vec<4, float>;
17 using sk2f = skvx::Vec<2, float>;
18
operator ==(const SkM44 & other) const19 bool SkM44::operator==(const SkM44& other) const {
20 if (this == &other) {
21 return true;
22 }
23
24 sk4f a0 = sk4f::Load(fMat + 0);
25 sk4f a1 = sk4f::Load(fMat + 4);
26 sk4f a2 = sk4f::Load(fMat + 8);
27 sk4f a3 = sk4f::Load(fMat + 12);
28
29 sk4f b0 = sk4f::Load(other.fMat + 0);
30 sk4f b1 = sk4f::Load(other.fMat + 4);
31 sk4f b2 = sk4f::Load(other.fMat + 8);
32 sk4f b3 = sk4f::Load(other.fMat + 12);
33
34 auto eq = (a0 == b0) & (a1 == b1) & (a2 == b2) & (a3 == b3);
35 return (eq[0] & eq[1] & eq[2] & eq[3]) == ~0;
36 }
37
transpose_arrays(SkScalar dst[],const SkScalar src[])38 static void transpose_arrays(SkScalar dst[], const SkScalar src[]) {
39 dst[0] = src[0]; dst[1] = src[4]; dst[2] = src[8]; dst[3] = src[12];
40 dst[4] = src[1]; dst[5] = src[5]; dst[6] = src[9]; dst[7] = src[13];
41 dst[8] = src[2]; dst[9] = src[6]; dst[10] = src[10]; dst[11] = src[14];
42 dst[12] = src[3]; dst[13] = src[7]; dst[14] = src[11]; dst[15] = src[15];
43 }
44
getRowMajor(SkScalar v[]) const45 void SkM44::getRowMajor(SkScalar v[]) const {
46 transpose_arrays(v, fMat);
47 }
48
setConcat(const SkM44 & a,const SkM44 & b)49 SkM44& SkM44::setConcat(const SkM44& a, const SkM44& b) {
50 sk4f c0 = sk4f::Load(a.fMat + 0);
51 sk4f c1 = sk4f::Load(a.fMat + 4);
52 sk4f c2 = sk4f::Load(a.fMat + 8);
53 sk4f c3 = sk4f::Load(a.fMat + 12);
54
55 auto compute = [&](sk4f r) {
56 return c0*r[0] + (c1*r[1] + (c2*r[2] + c3*r[3]));
57 };
58
59 sk4f m0 = compute(sk4f::Load(b.fMat + 0));
60 sk4f m1 = compute(sk4f::Load(b.fMat + 4));
61 sk4f m2 = compute(sk4f::Load(b.fMat + 8));
62 sk4f m3 = compute(sk4f::Load(b.fMat + 12));
63
64 m0.store(fMat + 0);
65 m1.store(fMat + 4);
66 m2.store(fMat + 8);
67 m3.store(fMat + 12);
68 return *this;
69 }
70
preConcat(const SkMatrix & b)71 SkM44& SkM44::preConcat(const SkMatrix& b) {
72 sk4f c0 = sk4f::Load(fMat + 0);
73 sk4f c1 = sk4f::Load(fMat + 4);
74 sk4f c3 = sk4f::Load(fMat + 12);
75
76 auto compute = [&](float r0, float r1, float r3) {
77 return (c0*r0 + (c1*r1 + c3*r3));
78 };
79
80 sk4f m0 = compute(b[0], b[3], b[6]);
81 sk4f m1 = compute(b[1], b[4], b[7]);
82 sk4f m3 = compute(b[2], b[5], b[8]);
83
84 m0.store(fMat + 0);
85 m1.store(fMat + 4);
86 m3.store(fMat + 12);
87 return *this;
88 }
89
preTranslate(SkScalar x,SkScalar y,SkScalar z)90 SkM44& SkM44::preTranslate(SkScalar x, SkScalar y, SkScalar z) {
91 sk4f c0 = sk4f::Load(fMat + 0);
92 sk4f c1 = sk4f::Load(fMat + 4);
93 sk4f c2 = sk4f::Load(fMat + 8);
94 sk4f c3 = sk4f::Load(fMat + 12);
95
96 // only need to update the last column
97 (c0*x + (c1*y + (c2*z + c3))).store(fMat + 12);
98 return *this;
99 }
100
postTranslate(SkScalar x,SkScalar y,SkScalar z)101 SkM44& SkM44::postTranslate(SkScalar x, SkScalar y, SkScalar z) {
102 sk4f t = { x, y, z, 0 };
103 (t * fMat[ 3] + sk4f::Load(fMat + 0)).store(fMat + 0);
104 (t * fMat[ 7] + sk4f::Load(fMat + 4)).store(fMat + 4);
105 (t * fMat[11] + sk4f::Load(fMat + 8)).store(fMat + 8);
106 (t * fMat[15] + sk4f::Load(fMat + 12)).store(fMat + 12);
107 return *this;
108 }
109
preScale(SkScalar x,SkScalar y)110 SkM44& SkM44::preScale(SkScalar x, SkScalar y) {
111 sk4f c0 = sk4f::Load(fMat + 0);
112 sk4f c1 = sk4f::Load(fMat + 4);
113
114 (c0 * x).store(fMat + 0);
115 (c1 * y).store(fMat + 4);
116 return *this;
117 }
118
preScale(SkScalar x,SkScalar y,SkScalar z)119 SkM44& SkM44::preScale(SkScalar x, SkScalar y, SkScalar z) {
120 sk4f c0 = sk4f::Load(fMat + 0);
121 sk4f c1 = sk4f::Load(fMat + 4);
122 sk4f c2 = sk4f::Load(fMat + 8);
123
124 (c0 * x).store(fMat + 0);
125 (c1 * y).store(fMat + 4);
126 (c2 * z).store(fMat + 8);
127 return *this;
128 }
129
map(float x,float y,float z,float w) const130 SkV4 SkM44::map(float x, float y, float z, float w) const {
131 sk4f c0 = sk4f::Load(fMat + 0);
132 sk4f c1 = sk4f::Load(fMat + 4);
133 sk4f c2 = sk4f::Load(fMat + 8);
134 sk4f c3 = sk4f::Load(fMat + 12);
135
136 SkV4 v;
137 (c0*x + (c1*y + (c2*z + c3*w))).store(&v.x);
138 return v;
139 }
140
map_rect_affine(const SkRect & src,const float mat[16])141 static SkRect map_rect_affine(const SkRect& src, const float mat[16]) {
142 // When multiplied against vectors of the form <x,y,x,y>, 'flip' allows a single min(sk4f, sk4f)
143 // to compute both the min and "negated" max between the xy coordinates. Once finished, another
144 // multiplication produces the original max.
145 const sk4f flip{1.f, 1.f, -1.f, -1.f};
146
147 // Since z = 0 and it's assumed ther's no perspective, only load the upper 2x2 and (tx,ty) in c3
148 sk4f c0 = skvx::shuffle<0,1,0,1>(sk2f::Load(mat + 0)) * flip;
149 sk4f c1 = skvx::shuffle<0,1,0,1>(sk2f::Load(mat + 4)) * flip;
150 sk4f c3 = skvx::shuffle<0,1,0,1>(sk2f::Load(mat + 12));
151
152 // Compute the min and max of the four transformed corners pre-translation; then translate once
153 // at the end.
154 sk4f minMax = c3 + flip * min(min(c0 * src.fLeft + c1 * src.fTop,
155 c0 * src.fRight + c1 * src.fTop),
156 min(c0 * src.fLeft + c1 * src.fBottom,
157 c0 * src.fRight + c1 * src.fBottom));
158
159 // minMax holds (min x, min y, max x, max y) so can be copied into an SkRect expecting l,t,r,b
160 SkRect r;
161 minMax.store(&r);
162 return r;
163 }
164
map_rect_perspective(const SkRect & src,const float mat[16])165 static SkRect map_rect_perspective(const SkRect& src, const float mat[16]) {
166 // Like map_rect_affine, z = 0 so we can skip the 3rd column, but we do need to compute w's
167 // for each corner of the src rect.
168 sk4f c0 = sk4f::Load(mat + 0);
169 sk4f c1 = sk4f::Load(mat + 4);
170 sk4f c3 = sk4f::Load(mat + 12);
171
172 // Unlike map_rect_affine, we do not defer the 4th column since we may need to homogeneous
173 // coordinates to clip against the w=0 plane
174 sk4f tl = c0 * src.fLeft + c1 * src.fTop + c3;
175 sk4f tr = c0 * src.fRight + c1 * src.fTop + c3;
176 sk4f bl = c0 * src.fLeft + c1 * src.fBottom + c3;
177 sk4f br = c0 * src.fRight + c1 * src.fBottom + c3;
178
179 // After clipping to w>0 and projecting to 2d, 'project' employs the same negation trick to
180 // compute min and max at the same time.
181 const sk4f flip{1.f, 1.f, -1.f, -1.f};
182 auto project = [&flip](const sk4f& p0, const sk4f& p1, const sk4f& p2) {
183 float w0 = p0[3];
184 if (w0 >= SkPathPriv::kW0PlaneDistance) {
185 // Unclipped, just divide by w
186 return flip * skvx::shuffle<0,1,0,1>(p0) / w0;
187 } else {
188 auto clip = [&](const sk4f& p) {
189 float w = p[3];
190 if (w >= SkPathPriv::kW0PlaneDistance) {
191 float t = (SkPathPriv::kW0PlaneDistance - w0) / (w - w0);
192 sk2f c = (t * skvx::shuffle<0,1>(p) + (1.f - t) * skvx::shuffle<0,1>(p0)) /
193 SkPathPriv::kW0PlaneDistance;
194
195 return flip * skvx::shuffle<0,1,0,1>(c);
196 } else {
197 return sk4f(SK_ScalarInfinity);
198 }
199 };
200 // Clip both edges leaving p0, and return the min/max of the two clipped points
201 // (since clip returns infinity when both p0 and 2nd vertex have w<0, it'll
202 // automatically be ignored).
203 return min(clip(p1), clip(p2));
204 }
205 };
206
207 // Project all 4 corners, and pass in their adjacent vertices for clipping if it has w < 0,
208 // then accumulate the min and max xy's.
209 sk4f minMax = flip * min(min(project(tl, tr, bl), project(tr, br, tl)),
210 min(project(br, bl, tr), project(bl, tl, br)));
211
212 SkRect r;
213 minMax.store(&r);
214 return r;
215 }
216
MapRect(const SkM44 & m,const SkRect & src)217 SkRect SkMatrixPriv::MapRect(const SkM44& m, const SkRect& src) {
218 const bool hasPerspective =
219 m.fMat[3] != 0 || m.fMat[7] != 0 || m.fMat[11] != 0 || m.fMat[15] != 1;
220 if (hasPerspective) {
221 return map_rect_perspective(src, m.fMat);
222 } else {
223 return map_rect_affine(src, m.fMat);
224 }
225 }
226
normalizePerspective()227 void SkM44::normalizePerspective() {
228 // If the bottom row of the matrix is [0, 0, 0, not_one], we will treat the matrix as if it
229 // is in perspective, even though it stills behaves like its affine. If we divide everything
230 // by the not_one value, then it will behave the same, but will be treated as affine,
231 // and therefore faster (e.g. clients can forward-difference calculations).
232 if (fMat[15] != 1 && fMat[15] != 0 && fMat[3] == 0 && fMat[7] == 0 && fMat[11] == 0) {
233 double inv = 1.0 / fMat[15];
234 (sk4f::Load(fMat + 0) * inv).store(fMat + 0);
235 (sk4f::Load(fMat + 4) * inv).store(fMat + 4);
236 (sk4f::Load(fMat + 8) * inv).store(fMat + 8);
237 (sk4f::Load(fMat + 12) * inv).store(fMat + 12);
238 fMat[15] = 1.0f;
239 }
240 }
241
242 ///////////////////////////////////////////////////////////////////////////////
243
244 /** We always perform the calculation in doubles, to avoid prematurely losing
245 precision along the way. This relies on the compiler automatically
246 promoting our SkScalar values to double (if needed).
247 */
invert(SkM44 * inverse) const248 bool SkM44::invert(SkM44* inverse) const {
249 SkScalar tmp[16];
250 if (SkInvert4x4Matrix(fMat, tmp) == 0.0f) {
251 return false;
252 }
253 memcpy(inverse->fMat, tmp, sizeof(tmp));
254 return true;
255 }
256
transpose() const257 SkM44 SkM44::transpose() const {
258 SkM44 trans(SkM44::kUninitialized_Constructor);
259 transpose_arrays(trans.fMat, fMat);
260 return trans;
261 }
262
setRotateUnitSinCos(SkV3 axis,SkScalar sinAngle,SkScalar cosAngle)263 SkM44& SkM44::setRotateUnitSinCos(SkV3 axis, SkScalar sinAngle, SkScalar cosAngle) {
264 // Taken from "Essential Mathematics for Games and Interactive Applications"
265 // James M. Van Verth and Lars M. Bishop -- third edition
266 SkScalar x = axis.x;
267 SkScalar y = axis.y;
268 SkScalar z = axis.z;
269 SkScalar c = cosAngle;
270 SkScalar s = sinAngle;
271 SkScalar t = 1 - c;
272
273 *this = { t*x*x + c, t*x*y - s*z, t*x*z + s*y, 0,
274 t*x*y + s*z, t*y*y + c, t*y*z - s*x, 0,
275 t*x*z - s*y, t*y*z + s*x, t*z*z + c, 0,
276 0, 0, 0, 1 };
277 return *this;
278 }
279
setRotate(SkV3 axis,SkScalar radians)280 SkM44& SkM44::setRotate(SkV3 axis, SkScalar radians) {
281 SkScalar len = axis.length();
282 if (len > 0 && SkScalarIsFinite(len)) {
283 this->setRotateUnit(axis * (SK_Scalar1 / len), radians);
284 } else {
285 this->setIdentity();
286 }
287 return *this;
288 }
289
290 ///////////////////////////////////////////////////////////////////////////////
291
dump() const292 void SkM44::dump() const {
293 static const char* format = "|%g %g %g %g|\n"
294 "|%g %g %g %g|\n"
295 "|%g %g %g %g|\n"
296 "|%g %g %g %g|\n";
297 SkDebugf(format,
298 fMat[0], fMat[4], fMat[8], fMat[12],
299 fMat[1], fMat[5], fMat[9], fMat[13],
300 fMat[2], fMat[6], fMat[10], fMat[14],
301 fMat[3], fMat[7], fMat[11], fMat[15]);
302 }
303
304 ///////////////////////////////////////////////////////////////////////////////
305
RectToRect(const SkRect & src,const SkRect & dst)306 SkM44 SkM44::RectToRect(const SkRect& src, const SkRect& dst) {
307 if (src.isEmpty()) {
308 return SkM44();
309 } else if (dst.isEmpty()) {
310 return SkM44::Scale(0.f, 0.f, 0.f);
311 }
312
313 float sx = dst.width() / src.width();
314 float sy = dst.height() / src.height();
315
316 float tx = dst.fLeft - sx * src.fLeft;
317 float ty = dst.fTop - sy * src.fTop;
318
319 return SkM44{sx, 0.f, 0.f, tx,
320 0.f, sy, 0.f, ty,
321 0.f, 0.f, 1.f, 0.f,
322 0.f, 0.f, 0.f, 1.f};
323 }
324
normalize(SkV3 v)325 static SkV3 normalize(SkV3 v) { return v * (1.0f / v.length()); }
326
v4(SkV3 v,SkScalar w)327 static SkV4 v4(SkV3 v, SkScalar w) { return {v.x, v.y, v.z, w}; }
328
LookAt(const SkV3 & eye,const SkV3 & center,const SkV3 & up)329 SkM44 SkM44::LookAt(const SkV3& eye, const SkV3& center, const SkV3& up) {
330 SkV3 f = normalize(center - eye);
331 SkV3 u = normalize(up);
332 SkV3 s = normalize(f.cross(u));
333
334 SkM44 m(SkM44::kUninitialized_Constructor);
335 if (!SkM44::Cols(v4(s, 0), v4(s.cross(f), 0), v4(-f, 0), v4(eye, 1)).invert(&m)) {
336 m.setIdentity();
337 }
338 return m;
339 }
340
Perspective(float near,float far,float angle)341 SkM44 SkM44::Perspective(float near, float far, float angle) {
342 SkASSERT(far > near);
343
344 float denomInv = sk_ieee_float_divide(1, far - near);
345 float halfAngle = angle * 0.5f;
346 float cot = sk_float_cos(halfAngle) / sk_float_sin(halfAngle);
347
348 SkM44 m;
349 m.setRC(0, 0, cot);
350 m.setRC(1, 1, cot);
351 m.setRC(2, 2, (far + near) * denomInv);
352 m.setRC(2, 3, 2 * far * near * denomInv);
353 m.setRC(3, 2, -1);
354 return m;
355 }
356