Mailbox support for texture layers.
[chromium-blink-merge.git] / cc / math_util.cc
blob8796504e2465387c2e0e8fa74d203ba5324fb6b9
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "cc/math_util.h"
7 #include <cmath>
8 #include <limits>
10 #include "ui/gfx/quad_f.h"
11 #include "ui/gfx/rect.h"
12 #include "ui/gfx/rect_conversions.h"
13 #include "ui/gfx/rect_f.h"
14 #include "ui/gfx/transform.h"
15 #include "ui/gfx/vector2d_f.h"
17 namespace cc {
19 const double MathUtil::PI_DOUBLE = 3.14159265358979323846;
20 const float MathUtil::PI_FLOAT = 3.14159265358979323846f;
21 const double MathUtil::EPSILON = 1e-9;
23 static HomogeneousCoordinate projectHomogeneousPoint(const gfx::Transform& transform, const gfx::PointF& p)
25 // In this case, the layer we are trying to project onto is perpendicular to ray
26 // (point p and z-axis direction) that we are trying to project. This happens when the
27 // layer is rotated so that it is infinitesimally thin, or when it is co-planar with
28 // the camera origin -- i.e. when the layer is invisible anyway.
29 if (!transform.matrix().getDouble(2, 2))
30 return HomogeneousCoordinate(0, 0, 0, 1);
32 double x = p.x();
33 double y = p.y();
34 double z = -(transform.matrix().getDouble(2, 0) * x + transform.matrix().getDouble(2, 1) * y + transform.matrix().getDouble(2, 3)) / transform.matrix().getDouble(2, 2);
35 // implicit definition of w = 1;
37 double outX = x * transform.matrix().getDouble(0, 0) + y * transform.matrix().getDouble(0, 1) + z * transform.matrix().getDouble(0, 2) + transform.matrix().getDouble(0, 3);
38 double outY = x * transform.matrix().getDouble(1, 0) + y * transform.matrix().getDouble(1, 1) + z * transform.matrix().getDouble(1, 2) + transform.matrix().getDouble(1, 3);
39 double outZ = x * transform.matrix().getDouble(2, 0) + y * transform.matrix().getDouble(2, 1) + z * transform.matrix().getDouble(2, 2) + transform.matrix().getDouble(2, 3);
40 double outW = x * transform.matrix().getDouble(3, 0) + y * transform.matrix().getDouble(3, 1) + z * transform.matrix().getDouble(3, 2) + transform.matrix().getDouble(3, 3);
42 return HomogeneousCoordinate(outX, outY, outZ, outW);
45 static HomogeneousCoordinate mapHomogeneousPoint(const gfx::Transform& transform, const gfx::Point3F& p)
47 double x = p.x();
48 double y = p.y();
49 double z = p.z();
50 // implicit definition of w = 1;
52 double outX = x * transform.matrix().getDouble(0, 0) + y * transform.matrix().getDouble(0, 1) + z * transform.matrix().getDouble(0, 2) + transform.matrix().getDouble(0, 3);
53 double outY = x * transform.matrix().getDouble(1, 0) + y * transform.matrix().getDouble(1, 1) + z * transform.matrix().getDouble(1, 2) + transform.matrix().getDouble(1, 3);
54 double outZ = x * transform.matrix().getDouble(2, 0) + y * transform.matrix().getDouble(2, 1) + z * transform.matrix().getDouble(2, 2) + transform.matrix().getDouble(2, 3);
55 double outW = x * transform.matrix().getDouble(3, 0) + y * transform.matrix().getDouble(3, 1) + z * transform.matrix().getDouble(3, 2) + transform.matrix().getDouble(3, 3);
57 return HomogeneousCoordinate(outX, outY, outZ, outW);
60 static HomogeneousCoordinate computeClippedPointForEdge(const HomogeneousCoordinate& h1, const HomogeneousCoordinate& h2)
62 // Points h1 and h2 form a line in 4d, and any point on that line can be represented
63 // as an interpolation between h1 and h2:
64 // p = (1-t) h1 + (t) h2
66 // We want to compute point p such that p.w == epsilon, where epsilon is a small
67 // non-zero number. (but the smaller the number is, the higher the risk of overflow)
68 // To do this, we solve for t in the following equation:
69 // p.w = epsilon = (1-t) * h1.w + (t) * h2.w
71 // Once paramter t is known, the rest of p can be computed via p = (1-t) h1 + (t) h2.
73 // Technically this is a special case of the following assertion, but its a good idea to keep it an explicit sanity check here.
74 DCHECK(h2.w != h1.w);
75 // Exactly one of h1 or h2 (but not both) must be on the negative side of the w plane when this is called.
76 DCHECK(h1.shouldBeClipped() ^ h2.shouldBeClipped());
78 double w = 0.00001; // or any positive non-zero small epsilon
80 double t = (w - h1.w) / (h2.w - h1.w);
82 double x = (1-t) * h1.x + t * h2.x;
83 double y = (1-t) * h1.y + t * h2.y;
84 double z = (1-t) * h1.z + t * h2.z;
86 return HomogeneousCoordinate(x, y, z, w);
89 static inline void expandBoundsToIncludePoint(float& xmin, float& xmax, float& ymin, float& ymax, const gfx::PointF& p)
91 xmin = std::min(p.x(), xmin);
92 xmax = std::max(p.x(), xmax);
93 ymin = std::min(p.y(), ymin);
94 ymax = std::max(p.y(), ymax);
97 static inline void addVertexToClippedQuad(const gfx::PointF& newVertex, gfx::PointF clippedQuad[8], int& numVerticesInClippedQuad)
99 clippedQuad[numVerticesInClippedQuad] = newVertex;
100 numVerticesInClippedQuad++;
103 gfx::Rect MathUtil::mapClippedRect(const gfx::Transform& transform, const gfx::Rect& srcRect)
105 return gfx::ToEnclosingRect(mapClippedRect(transform, gfx::RectF(srcRect)));
108 gfx::RectF MathUtil::mapClippedRect(const gfx::Transform& transform, const gfx::RectF& srcRect)
110 if (transform.IsIdentityOrTranslation())
111 return srcRect + gfx::Vector2dF(static_cast<float>(transform.matrix().getDouble(0, 3)), static_cast<float>(transform.matrix().getDouble(1, 3)));
113 // Apply the transform, but retain the result in homogeneous coordinates.
115 double quad[4 * 2]; // input: 4 x 2D points
116 quad[0] = srcRect.x();
117 quad[1] = srcRect.y();
118 quad[2] = srcRect.right();
119 quad[3] = srcRect.y();
120 quad[4] = srcRect.right();
121 quad[5] = srcRect.bottom();
122 quad[6] = srcRect.x();
123 quad[7] = srcRect.bottom();
125 double result[4 * 4]; // output: 4 x 4D homogeneous points
126 transform.matrix().map2(quad, 4, result);
128 HomogeneousCoordinate hc0(result[0], result[1], result[2], result[3]);
129 HomogeneousCoordinate hc1(result[4], result[5], result[6], result[7]);
130 HomogeneousCoordinate hc2(result[8], result[9], result[10], result[11]);
131 HomogeneousCoordinate hc3(result[12], result[13], result[14], result[15]);
132 return computeEnclosingClippedRect(hc0, hc1, hc2, hc3);
135 gfx::RectF MathUtil::projectClippedRect(const gfx::Transform& transform, const gfx::RectF& srcRect)
137 if (transform.IsIdentityOrTranslation())
138 return srcRect + gfx::Vector2dF(static_cast<float>(transform.matrix().getDouble(0, 3)), static_cast<float>(transform.matrix().getDouble(1, 3)));
140 // Perform the projection, but retain the result in homogeneous coordinates.
141 gfx::QuadF q = gfx::QuadF(srcRect);
142 HomogeneousCoordinate h1 = projectHomogeneousPoint(transform, q.p1());
143 HomogeneousCoordinate h2 = projectHomogeneousPoint(transform, q.p2());
144 HomogeneousCoordinate h3 = projectHomogeneousPoint(transform, q.p3());
145 HomogeneousCoordinate h4 = projectHomogeneousPoint(transform, q.p4());
147 return computeEnclosingClippedRect(h1, h2, h3, h4);
150 void MathUtil::mapClippedQuad(const gfx::Transform& transform, const gfx::QuadF& srcQuad, gfx::PointF clippedQuad[8], int& numVerticesInClippedQuad)
152 HomogeneousCoordinate h1 = mapHomogeneousPoint(transform, gfx::Point3F(srcQuad.p1()));
153 HomogeneousCoordinate h2 = mapHomogeneousPoint(transform, gfx::Point3F(srcQuad.p2()));
154 HomogeneousCoordinate h3 = mapHomogeneousPoint(transform, gfx::Point3F(srcQuad.p3()));
155 HomogeneousCoordinate h4 = mapHomogeneousPoint(transform, gfx::Point3F(srcQuad.p4()));
157 // The order of adding the vertices to the array is chosen so that clockwise / counter-clockwise orientation is retained.
159 numVerticesInClippedQuad = 0;
161 if (!h1.shouldBeClipped())
162 addVertexToClippedQuad(h1.cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad);
164 if (h1.shouldBeClipped() ^ h2.shouldBeClipped())
165 addVertexToClippedQuad(computeClippedPointForEdge(h1, h2).cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad);
167 if (!h2.shouldBeClipped())
168 addVertexToClippedQuad(h2.cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad);
170 if (h2.shouldBeClipped() ^ h3.shouldBeClipped())
171 addVertexToClippedQuad(computeClippedPointForEdge(h2, h3).cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad);
173 if (!h3.shouldBeClipped())
174 addVertexToClippedQuad(h3.cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad);
176 if (h3.shouldBeClipped() ^ h4.shouldBeClipped())
177 addVertexToClippedQuad(computeClippedPointForEdge(h3, h4).cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad);
179 if (!h4.shouldBeClipped())
180 addVertexToClippedQuad(h4.cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad);
182 if (h4.shouldBeClipped() ^ h1.shouldBeClipped())
183 addVertexToClippedQuad(computeClippedPointForEdge(h4, h1).cartesianPoint2d(), clippedQuad, numVerticesInClippedQuad);
185 DCHECK(numVerticesInClippedQuad <= 8);
188 gfx::RectF MathUtil::computeEnclosingRectOfVertices(gfx::PointF vertices[], int numVertices)
190 if (numVertices < 2)
191 return gfx::RectF();
193 float xmin = std::numeric_limits<float>::max();
194 float xmax = -std::numeric_limits<float>::max();
195 float ymin = std::numeric_limits<float>::max();
196 float ymax = -std::numeric_limits<float>::max();
198 for (int i = 0; i < numVertices; ++i)
199 expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, vertices[i]);
201 return gfx::RectF(gfx::PointF(xmin, ymin), gfx::SizeF(xmax - xmin, ymax - ymin));
204 gfx::RectF MathUtil::computeEnclosingClippedRect(const HomogeneousCoordinate& h1, const HomogeneousCoordinate& h2, const HomogeneousCoordinate& h3, const HomogeneousCoordinate& h4)
206 // This function performs clipping as necessary and computes the enclosing 2d
207 // gfx::RectF of the vertices. Doing these two steps simultaneously allows us to avoid
208 // the overhead of storing an unknown number of clipped vertices.
210 // If no vertices on the quad are clipped, then we can simply return the enclosing rect directly.
211 bool somethingClipped = h1.shouldBeClipped() || h2.shouldBeClipped() || h3.shouldBeClipped() || h4.shouldBeClipped();
212 if (!somethingClipped) {
213 gfx::QuadF mappedQuad = gfx::QuadF(h1.cartesianPoint2d(), h2.cartesianPoint2d(), h3.cartesianPoint2d(), h4.cartesianPoint2d());
214 return mappedQuad.BoundingBox();
217 bool everythingClipped = h1.shouldBeClipped() && h2.shouldBeClipped() && h3.shouldBeClipped() && h4.shouldBeClipped();
218 if (everythingClipped)
219 return gfx::RectF();
222 float xmin = std::numeric_limits<float>::max();
223 float xmax = -std::numeric_limits<float>::max();
224 float ymin = std::numeric_limits<float>::max();
225 float ymax = -std::numeric_limits<float>::max();
227 if (!h1.shouldBeClipped())
228 expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h1.cartesianPoint2d());
230 if (h1.shouldBeClipped() ^ h2.shouldBeClipped())
231 expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h1, h2).cartesianPoint2d());
233 if (!h2.shouldBeClipped())
234 expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h2.cartesianPoint2d());
236 if (h2.shouldBeClipped() ^ h3.shouldBeClipped())
237 expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h2, h3).cartesianPoint2d());
239 if (!h3.shouldBeClipped())
240 expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h3.cartesianPoint2d());
242 if (h3.shouldBeClipped() ^ h4.shouldBeClipped())
243 expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h3, h4).cartesianPoint2d());
245 if (!h4.shouldBeClipped())
246 expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h4.cartesianPoint2d());
248 if (h4.shouldBeClipped() ^ h1.shouldBeClipped())
249 expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h4, h1).cartesianPoint2d());
251 return gfx::RectF(gfx::PointF(xmin, ymin), gfx::SizeF(xmax - xmin, ymax - ymin));
254 gfx::QuadF MathUtil::mapQuad(const gfx::Transform& transform, const gfx::QuadF& q, bool& clipped)
256 if (transform.IsIdentityOrTranslation()) {
257 gfx::QuadF mappedQuad(q);
258 mappedQuad += gfx::Vector2dF(static_cast<float>(transform.matrix().getDouble(0, 3)), static_cast<float>(transform.matrix().getDouble(1, 3)));
259 clipped = false;
260 return mappedQuad;
263 HomogeneousCoordinate h1 = mapHomogeneousPoint(transform, gfx::Point3F(q.p1()));
264 HomogeneousCoordinate h2 = mapHomogeneousPoint(transform, gfx::Point3F(q.p2()));
265 HomogeneousCoordinate h3 = mapHomogeneousPoint(transform, gfx::Point3F(q.p3()));
266 HomogeneousCoordinate h4 = mapHomogeneousPoint(transform, gfx::Point3F(q.p4()));
268 clipped = h1.shouldBeClipped() || h2.shouldBeClipped() || h3.shouldBeClipped() || h4.shouldBeClipped();
270 // Result will be invalid if clipped == true. But, compute it anyway just in case, to emulate existing behavior.
271 return gfx::QuadF(h1.cartesianPoint2d(), h2.cartesianPoint2d(), h3.cartesianPoint2d(), h4.cartesianPoint2d());
274 gfx::PointF MathUtil::mapPoint(const gfx::Transform& transform, const gfx::PointF& p, bool& clipped)
276 HomogeneousCoordinate h = mapHomogeneousPoint(transform, gfx::Point3F(p));
278 if (h.w > 0) {
279 clipped = false;
280 return h.cartesianPoint2d();
283 // The cartesian coordinates will be invalid after dividing by w.
284 clipped = true;
286 // Avoid dividing by w if w == 0.
287 if (!h.w)
288 return gfx::PointF();
290 // This return value will be invalid because clipped == true, but (1) users of this
291 // code should be ignoring the return value when clipped == true anyway, and (2) this
292 // behavior is more consistent with existing behavior of WebKit transforms if the user
293 // really does not ignore the return value.
294 return h.cartesianPoint2d();
297 gfx::Point3F MathUtil::mapPoint(const gfx::Transform& transform, const gfx::Point3F& p, bool& clipped)
299 HomogeneousCoordinate h = mapHomogeneousPoint(transform, p);
301 if (h.w > 0) {
302 clipped = false;
303 return h.cartesianPoint3d();
306 // The cartesian coordinates will be invalid after dividing by w.
307 clipped = true;
309 // Avoid dividing by w if w == 0.
310 if (!h.w)
311 return gfx::Point3F();
313 // This return value will be invalid because clipped == true, but (1) users of this
314 // code should be ignoring the return value when clipped == true anyway, and (2) this
315 // behavior is more consistent with existing behavior of WebKit transforms if the user
316 // really does not ignore the return value.
317 return h.cartesianPoint3d();
320 gfx::QuadF MathUtil::projectQuad(const gfx::Transform& transform, const gfx::QuadF& q, bool& clipped)
322 gfx::QuadF projectedQuad;
323 bool clippedPoint;
324 projectedQuad.set_p1(projectPoint(transform, q.p1(), clippedPoint));
325 clipped = clippedPoint;
326 projectedQuad.set_p2(projectPoint(transform, q.p2(), clippedPoint));
327 clipped |= clippedPoint;
328 projectedQuad.set_p3(projectPoint(transform, q.p3(), clippedPoint));
329 clipped |= clippedPoint;
330 projectedQuad.set_p4(projectPoint(transform, q.p4(), clippedPoint));
331 clipped |= clippedPoint;
333 return projectedQuad;
336 gfx::PointF MathUtil::projectPoint(const gfx::Transform& transform, const gfx::PointF& p, bool& clipped)
338 HomogeneousCoordinate h = projectHomogeneousPoint(transform, p);
340 if (h.w > 0) {
341 // The cartesian coordinates will be valid in this case.
342 clipped = false;
343 return h.cartesianPoint2d();
346 // The cartesian coordinates will be invalid after dividing by w.
347 clipped = true;
349 // Avoid dividing by w if w == 0.
350 if (!h.w)
351 return gfx::PointF();
353 // This return value will be invalid because clipped == true, but (1) users of this
354 // code should be ignoring the return value when clipped == true anyway, and (2) this
355 // behavior is more consistent with existing behavior of WebKit transforms if the user
356 // really does not ignore the return value.
357 return h.cartesianPoint2d();
360 void MathUtil::flattenTransformTo2d(gfx::Transform& transform)
362 // Set both the 3rd row and 3rd column to (0, 0, 1, 0).
364 // One useful interpretation of doing this operation:
365 // - For x and y values, the new transform behaves effectively like an orthographic
366 // projection was added to the matrix sequence.
367 // - For z values, the new transform overrides any effect that the transform had on
368 // z, and instead it preserves the z value for any points that are transformed.
369 // - Because of linearity of transforms, this flattened transform also preserves the
370 // effect that any subsequent (post-multiplied) transforms would have on z values.
372 transform.matrix().setDouble(2, 0, 0);
373 transform.matrix().setDouble(2, 1, 0);
374 transform.matrix().setDouble(0, 2, 0);
375 transform.matrix().setDouble(1, 2, 0);
376 transform.matrix().setDouble(2, 2, 1);
377 transform.matrix().setDouble(3, 2, 0);
378 transform.matrix().setDouble(2, 3, 0);
381 static inline float scaleOnAxis(double a, double b, double c)
383 return std::sqrt(a * a + b * b + c * c);
386 gfx::Vector2dF MathUtil::computeTransform2dScaleComponents(const gfx::Transform& transform, float fallbackValue)
388 if (transform.HasPerspective())
389 return gfx::Vector2dF(fallbackValue, fallbackValue);
390 float xScale = scaleOnAxis(transform.matrix().getDouble(0, 0), transform.matrix().getDouble(1, 0), transform.matrix().getDouble(2, 0));
391 float yScale = scaleOnAxis(transform.matrix().getDouble(0, 1), transform.matrix().getDouble(1, 1), transform.matrix().getDouble(2, 1));
392 return gfx::Vector2dF(xScale, yScale);
395 float MathUtil::smallestAngleBetweenVectors(gfx::Vector2dF v1, gfx::Vector2dF v2)
397 double dotProduct = gfx::DotProduct(v1, v2) / v1.Length() / v2.Length();
398 // Clamp to compensate for rounding errors.
399 dotProduct = std::max(-1.0, std::min(1.0, dotProduct));
400 return static_cast<float>(Rad2Deg(std::acos(dotProduct)));
403 gfx::Vector2dF MathUtil::projectVector(gfx::Vector2dF source, gfx::Vector2dF destination)
405 float projectedLength = gfx::DotProduct(source, destination) / destination.LengthSquared();
406 return gfx::Vector2dF(projectedLength * destination.x(), projectedLength * destination.y());
409 void MathUtil::rotateEulerAngles(gfx::Transform* transform, double eulerX, double eulerY, double eulerZ)
411 // TODO (shawnsingh): make this implementation faster and more accurate by
412 // hard-coding each matrix instead of calling RotateAbout().
413 gfx::Transform rotationAboutX;
414 gfx::Transform rotationAboutY;
415 gfx::Transform rotationAboutZ;
417 rotationAboutX.RotateAboutXAxis(eulerX);
418 rotationAboutY.RotateAboutYAxis(eulerY);
419 rotationAboutZ.RotateAboutZAxis(eulerZ);
421 gfx::Transform composite = rotationAboutZ * rotationAboutY * rotationAboutX;
422 transform->PreconcatTransform(composite);
425 gfx::Transform MathUtil::to2dTransform(const gfx::Transform& transform)
427 gfx::Transform result = transform;
428 SkMatrix44& matrix = result.matrix();
429 matrix.setDouble(0, 2, 0);
430 matrix.setDouble(1, 2, 0);
431 matrix.setDouble(2, 2, 1);
432 matrix.setDouble(3, 2, 0);
434 matrix.setDouble(2, 0, 0);
435 matrix.setDouble(2, 1, 0);
436 matrix.setDouble(2, 3, 0);
438 return result;
441 gfx::Transform MathUtil::createGfxTransform(double m11, double m12, double m13, double m14,
442 double m21, double m22, double m23, double m24,
443 double m31, double m32, double m33, double m34,
444 double m41, double m42, double m43, double m44)
446 gfx::Transform result;
447 SkMatrix44& matrix = result.matrix();
449 // Initialize column 1
450 matrix.setDouble(0, 0, m11);
451 matrix.setDouble(1, 0, m12);
452 matrix.setDouble(2, 0, m13);
453 matrix.setDouble(3, 0, m14);
455 // Initialize column 2
456 matrix.setDouble(0, 1, m21);
457 matrix.setDouble(1, 1, m22);
458 matrix.setDouble(2, 1, m23);
459 matrix.setDouble(3, 1, m24);
461 // Initialize column 3
462 matrix.setDouble(0, 2, m31);
463 matrix.setDouble(1, 2, m32);
464 matrix.setDouble(2, 2, m33);
465 matrix.setDouble(3, 2, m34);
467 // Initialize column 4
468 matrix.setDouble(0, 3, m41);
469 matrix.setDouble(1, 3, m42);
470 matrix.setDouble(2, 3, m43);
471 matrix.setDouble(3, 3, m44);
473 return result;
476 gfx::Transform MathUtil::createGfxTransform(double a, double b, double c,
477 double d, double e, double f)
479 gfx::Transform result;
480 SkMatrix44& matrix = result.matrix();
481 matrix.setDouble(0, 0, a);
482 matrix.setDouble(1, 0, b);
483 matrix.setDouble(0, 1, c);
484 matrix.setDouble(1, 1, d);
485 matrix.setDouble(0, 3, e);
486 matrix.setDouble(1, 3, f);
488 return result;
491 } // namespace cc