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.
7 #include "CCMathUtil.h"
9 #include "FloatPoint.h"
10 #include "FloatQuad.h"
12 #include <public/WebTransformationMatrix.h>
14 using WebKit::WebTransformationMatrix
;
18 static HomogeneousCoordinate
projectHomogeneousPoint(const WebTransformationMatrix
& transform
, const FloatPoint
& p
)
20 // In this case, the layer we are trying to project onto is perpendicular to ray
21 // (point p and z-axis direction) that we are trying to project. This happens when the
22 // layer is rotated so that it is infinitesimally thin, or when it is co-planar with
23 // the camera origin -- i.e. when the layer is invisible anyway.
25 return HomogeneousCoordinate(0, 0, 0, 1);
29 double z
= -(transform
.m13() * x
+ transform
.m23() * y
+ transform
.m43()) / transform
.m33();
30 // implicit definition of w = 1;
32 double outX
= x
* transform
.m11() + y
* transform
.m21() + z
* transform
.m31() + transform
.m41();
33 double outY
= x
* transform
.m12() + y
* transform
.m22() + z
* transform
.m32() + transform
.m42();
34 double outZ
= x
* transform
.m13() + y
* transform
.m23() + z
* transform
.m33() + transform
.m43();
35 double outW
= x
* transform
.m14() + y
* transform
.m24() + z
* transform
.m34() + transform
.m44();
37 return HomogeneousCoordinate(outX
, outY
, outZ
, outW
);
40 static HomogeneousCoordinate
mapHomogeneousPoint(const WebTransformationMatrix
& transform
, const FloatPoint3D
& p
)
45 // implicit definition of w = 1;
47 double outX
= x
* transform
.m11() + y
* transform
.m21() + z
* transform
.m31() + transform
.m41();
48 double outY
= x
* transform
.m12() + y
* transform
.m22() + z
* transform
.m32() + transform
.m42();
49 double outZ
= x
* transform
.m13() + y
* transform
.m23() + z
* transform
.m33() + transform
.m43();
50 double outW
= x
* transform
.m14() + y
* transform
.m24() + z
* transform
.m34() + transform
.m44();
52 return HomogeneousCoordinate(outX
, outY
, outZ
, outW
);
55 static HomogeneousCoordinate
computeClippedPointForEdge(const HomogeneousCoordinate
& h1
, const HomogeneousCoordinate
& h2
)
57 // Points h1 and h2 form a line in 4d, and any point on that line can be represented
58 // as an interpolation between h1 and h2:
59 // p = (1-t) h1 + (t) h2
61 // We want to compute point p such that p.w == epsilon, where epsilon is a small
62 // non-zero number. (but the smaller the number is, the higher the risk of overflow)
63 // To do this, we solve for t in the following equation:
64 // p.w = epsilon = (1-t) * h1.w + (t) * h2.w
66 // Once paramter t is known, the rest of p can be computed via p = (1-t) h1 + (t) h2.
68 // Technically this is a special case of the following assertion, but its a good idea to keep it an explicit sanity check here.
70 // Exactly one of h1 or h2 (but not both) must be on the negative side of the w plane when this is called.
71 ASSERT(h1
.shouldBeClipped() ^ h2
.shouldBeClipped());
73 double w
= 0.00001; // or any positive non-zero small epsilon
75 double t
= (w
- h1
.w
) / (h2
.w
- h1
.w
);
77 double x
= (1-t
) * h1
.x
+ t
* h2
.x
;
78 double y
= (1-t
) * h1
.y
+ t
* h2
.y
;
79 double z
= (1-t
) * h1
.z
+ t
* h2
.z
;
81 return HomogeneousCoordinate(x
, y
, z
, w
);
84 static inline void expandBoundsToIncludePoint(float& xmin
, float& xmax
, float& ymin
, float& ymax
, const FloatPoint
& p
)
86 xmin
= std::min(p
.x(), xmin
);
87 xmax
= std::max(p
.x(), xmax
);
88 ymin
= std::min(p
.y(), ymin
);
89 ymax
= std::max(p
.y(), ymax
);
92 static inline void addVertexToClippedQuad(const FloatPoint
& newVertex
, FloatPoint clippedQuad
[8], int& numVerticesInClippedQuad
)
94 clippedQuad
[numVerticesInClippedQuad
] = newVertex
;
95 numVerticesInClippedQuad
++;
98 IntRect
CCMathUtil::mapClippedRect(const WebTransformationMatrix
& transform
, const IntRect
& srcRect
)
100 return enclosingIntRect(mapClippedRect(transform
, FloatRect(srcRect
)));
103 FloatRect
CCMathUtil::mapClippedRect(const WebTransformationMatrix
& transform
, const FloatRect
& srcRect
)
105 if (transform
.isIdentityOrTranslation()) {
106 FloatRect
mappedRect(srcRect
);
107 mappedRect
.move(static_cast<float>(transform
.m41()), static_cast<float>(transform
.m42()));
111 // Apply the transform, but retain the result in homogeneous coordinates.
112 FloatQuad q
= FloatQuad(FloatRect(srcRect
));
113 HomogeneousCoordinate h1
= mapHomogeneousPoint(transform
, q
.p1());
114 HomogeneousCoordinate h2
= mapHomogeneousPoint(transform
, q
.p2());
115 HomogeneousCoordinate h3
= mapHomogeneousPoint(transform
, q
.p3());
116 HomogeneousCoordinate h4
= mapHomogeneousPoint(transform
, q
.p4());
118 return computeEnclosingClippedRect(h1
, h2
, h3
, h4
);
121 FloatRect
CCMathUtil::projectClippedRect(const WebTransformationMatrix
& transform
, const FloatRect
& srcRect
)
123 // Perform the projection, but retain the result in homogeneous coordinates.
124 FloatQuad q
= FloatQuad(FloatRect(srcRect
));
125 HomogeneousCoordinate h1
= projectHomogeneousPoint(transform
, q
.p1());
126 HomogeneousCoordinate h2
= projectHomogeneousPoint(transform
, q
.p2());
127 HomogeneousCoordinate h3
= projectHomogeneousPoint(transform
, q
.p3());
128 HomogeneousCoordinate h4
= projectHomogeneousPoint(transform
, q
.p4());
130 return computeEnclosingClippedRect(h1
, h2
, h3
, h4
);
133 void CCMathUtil::mapClippedQuad(const WebTransformationMatrix
& transform
, const FloatQuad
& srcQuad
, FloatPoint clippedQuad
[8], int& numVerticesInClippedQuad
)
135 HomogeneousCoordinate h1
= mapHomogeneousPoint(transform
, srcQuad
.p1());
136 HomogeneousCoordinate h2
= mapHomogeneousPoint(transform
, srcQuad
.p2());
137 HomogeneousCoordinate h3
= mapHomogeneousPoint(transform
, srcQuad
.p3());
138 HomogeneousCoordinate h4
= mapHomogeneousPoint(transform
, srcQuad
.p4());
140 // The order of adding the vertices to the array is chosen so that clockwise / counter-clockwise orientation is retained.
142 numVerticesInClippedQuad
= 0;
144 if (!h1
.shouldBeClipped())
145 addVertexToClippedQuad(h1
.cartesianPoint2d(), clippedQuad
, numVerticesInClippedQuad
);
147 if (h1
.shouldBeClipped() ^ h2
.shouldBeClipped())
148 addVertexToClippedQuad(computeClippedPointForEdge(h1
, h2
).cartesianPoint2d(), clippedQuad
, numVerticesInClippedQuad
);
150 if (!h2
.shouldBeClipped())
151 addVertexToClippedQuad(h2
.cartesianPoint2d(), clippedQuad
, numVerticesInClippedQuad
);
153 if (h2
.shouldBeClipped() ^ h3
.shouldBeClipped())
154 addVertexToClippedQuad(computeClippedPointForEdge(h2
, h3
).cartesianPoint2d(), clippedQuad
, numVerticesInClippedQuad
);
156 if (!h3
.shouldBeClipped())
157 addVertexToClippedQuad(h3
.cartesianPoint2d(), clippedQuad
, numVerticesInClippedQuad
);
159 if (h3
.shouldBeClipped() ^ h4
.shouldBeClipped())
160 addVertexToClippedQuad(computeClippedPointForEdge(h3
, h4
).cartesianPoint2d(), clippedQuad
, numVerticesInClippedQuad
);
162 if (!h4
.shouldBeClipped())
163 addVertexToClippedQuad(h4
.cartesianPoint2d(), clippedQuad
, numVerticesInClippedQuad
);
165 if (h4
.shouldBeClipped() ^ h1
.shouldBeClipped())
166 addVertexToClippedQuad(computeClippedPointForEdge(h4
, h1
).cartesianPoint2d(), clippedQuad
, numVerticesInClippedQuad
);
168 ASSERT(numVerticesInClippedQuad
<= 8);
171 FloatRect
CCMathUtil::computeEnclosingRectOfVertices(FloatPoint vertices
[], int numVertices
)
176 float xmin
= std::numeric_limits
<float>::max();
177 float xmax
= -std::numeric_limits
<float>::max();
178 float ymin
= std::numeric_limits
<float>::max();
179 float ymax
= -std::numeric_limits
<float>::max();
181 for (int i
= 0; i
< numVertices
; ++i
)
182 expandBoundsToIncludePoint(xmin
, xmax
, ymin
, ymax
, vertices
[i
]);
184 return FloatRect(FloatPoint(xmin
, ymin
), FloatSize(xmax
- xmin
, ymax
- ymin
));
187 FloatRect
CCMathUtil::computeEnclosingClippedRect(const HomogeneousCoordinate
& h1
, const HomogeneousCoordinate
& h2
, const HomogeneousCoordinate
& h3
, const HomogeneousCoordinate
& h4
)
189 // This function performs clipping as necessary and computes the enclosing 2d
190 // FloatRect of the vertices. Doing these two steps simultaneously allows us to avoid
191 // the overhead of storing an unknown number of clipped vertices.
193 // If no vertices on the quad are clipped, then we can simply return the enclosing rect directly.
194 bool somethingClipped
= h1
.shouldBeClipped() || h2
.shouldBeClipped() || h3
.shouldBeClipped() || h4
.shouldBeClipped();
195 if (!somethingClipped
) {
196 FloatQuad mappedQuad
= FloatQuad(h1
.cartesianPoint2d(), h2
.cartesianPoint2d(), h3
.cartesianPoint2d(), h4
.cartesianPoint2d());
197 return mappedQuad
.boundingBox();
200 bool everythingClipped
= h1
.shouldBeClipped() && h2
.shouldBeClipped() && h3
.shouldBeClipped() && h4
.shouldBeClipped();
201 if (everythingClipped
)
205 float xmin
= std::numeric_limits
<float>::max();
206 float xmax
= -std::numeric_limits
<float>::max();
207 float ymin
= std::numeric_limits
<float>::max();
208 float ymax
= -std::numeric_limits
<float>::max();
210 if (!h1
.shouldBeClipped())
211 expandBoundsToIncludePoint(xmin
, xmax
, ymin
, ymax
, h1
.cartesianPoint2d());
213 if (h1
.shouldBeClipped() ^ h2
.shouldBeClipped())
214 expandBoundsToIncludePoint(xmin
, xmax
, ymin
, ymax
, computeClippedPointForEdge(h1
, h2
).cartesianPoint2d());
216 if (!h2
.shouldBeClipped())
217 expandBoundsToIncludePoint(xmin
, xmax
, ymin
, ymax
, h2
.cartesianPoint2d());
219 if (h2
.shouldBeClipped() ^ h3
.shouldBeClipped())
220 expandBoundsToIncludePoint(xmin
, xmax
, ymin
, ymax
, computeClippedPointForEdge(h2
, h3
).cartesianPoint2d());
222 if (!h3
.shouldBeClipped())
223 expandBoundsToIncludePoint(xmin
, xmax
, ymin
, ymax
, h3
.cartesianPoint2d());
225 if (h3
.shouldBeClipped() ^ h4
.shouldBeClipped())
226 expandBoundsToIncludePoint(xmin
, xmax
, ymin
, ymax
, computeClippedPointForEdge(h3
, h4
).cartesianPoint2d());
228 if (!h4
.shouldBeClipped())
229 expandBoundsToIncludePoint(xmin
, xmax
, ymin
, ymax
, h4
.cartesianPoint2d());
231 if (h4
.shouldBeClipped() ^ h1
.shouldBeClipped())
232 expandBoundsToIncludePoint(xmin
, xmax
, ymin
, ymax
, computeClippedPointForEdge(h4
, h1
).cartesianPoint2d());
234 return FloatRect(FloatPoint(xmin
, ymin
), FloatSize(xmax
- xmin
, ymax
- ymin
));
237 FloatQuad
CCMathUtil::mapQuad(const WebTransformationMatrix
& transform
, const FloatQuad
& q
, bool& clipped
)
239 if (transform
.isIdentityOrTranslation()) {
240 FloatQuad
mappedQuad(q
);
241 mappedQuad
.move(static_cast<float>(transform
.m41()), static_cast<float>(transform
.m42()));
246 HomogeneousCoordinate h1
= mapHomogeneousPoint(transform
, q
.p1());
247 HomogeneousCoordinate h2
= mapHomogeneousPoint(transform
, q
.p2());
248 HomogeneousCoordinate h3
= mapHomogeneousPoint(transform
, q
.p3());
249 HomogeneousCoordinate h4
= mapHomogeneousPoint(transform
, q
.p4());
251 clipped
= h1
.shouldBeClipped() || h2
.shouldBeClipped() || h3
.shouldBeClipped() || h4
.shouldBeClipped();
253 // Result will be invalid if clipped == true. But, compute it anyway just in case, to emulate existing behavior.
254 return FloatQuad(h1
.cartesianPoint2d(), h2
.cartesianPoint2d(), h3
.cartesianPoint2d(), h4
.cartesianPoint2d());
257 FloatPoint
CCMathUtil::mapPoint(const WebTransformationMatrix
& transform
, const FloatPoint
& p
, bool& clipped
)
259 HomogeneousCoordinate h
= mapHomogeneousPoint(transform
, p
);
263 return h
.cartesianPoint2d();
266 // The cartesian coordinates will be invalid after dividing by w.
269 // Avoid dividing by w if w == 0.
273 // This return value will be invalid because clipped == true, but (1) users of this
274 // code should be ignoring the return value when clipped == true anyway, and (2) this
275 // behavior is more consistent with existing behavior of WebKit transforms if the user
276 // really does not ignore the return value.
277 return h
.cartesianPoint2d();
280 FloatPoint3D
CCMathUtil::mapPoint(const WebTransformationMatrix
& transform
, const FloatPoint3D
& p
, bool& clipped
)
282 HomogeneousCoordinate h
= mapHomogeneousPoint(transform
, p
);
286 return h
.cartesianPoint3d();
289 // The cartesian coordinates will be invalid after dividing by w.
292 // Avoid dividing by w if w == 0.
294 return FloatPoint3D();
296 // This return value will be invalid because clipped == true, but (1) users of this
297 // code should be ignoring the return value when clipped == true anyway, and (2) this
298 // behavior is more consistent with existing behavior of WebKit transforms if the user
299 // really does not ignore the return value.
300 return h
.cartesianPoint3d();
303 FloatQuad
CCMathUtil::projectQuad(const WebTransformationMatrix
& transform
, const FloatQuad
& q
, bool& clipped
)
305 FloatQuad projectedQuad
;
307 projectedQuad
.setP1(projectPoint(transform
, q
.p1(), clippedPoint
));
308 clipped
= clippedPoint
;
309 projectedQuad
.setP2(projectPoint(transform
, q
.p2(), clippedPoint
));
310 clipped
|= clippedPoint
;
311 projectedQuad
.setP3(projectPoint(transform
, q
.p3(), clippedPoint
));
312 clipped
|= clippedPoint
;
313 projectedQuad
.setP4(projectPoint(transform
, q
.p4(), clippedPoint
));
314 clipped
|= clippedPoint
;
316 return projectedQuad
;
319 FloatPoint
CCMathUtil::projectPoint(const WebTransformationMatrix
& transform
, const FloatPoint
& p
, bool& clipped
)
321 HomogeneousCoordinate h
= projectHomogeneousPoint(transform
, p
);
324 // The cartesian coordinates will be valid in this case.
326 return h
.cartesianPoint2d();
329 // The cartesian coordinates will be invalid after dividing by w.
332 // Avoid dividing by w if w == 0.
336 // This return value will be invalid because clipped == true, but (1) users of this
337 // code should be ignoring the return value when clipped == true anyway, and (2) this
338 // behavior is more consistent with existing behavior of WebKit transforms if the user
339 // really does not ignore the return value.
340 return h
.cartesianPoint2d();
343 void CCMathUtil::flattenTransformTo2d(WebTransformationMatrix
& transform
)
345 // Set both the 3rd row and 3rd column to (0, 0, 1, 0).
347 // One useful interpretation of doing this operation:
348 // - For x and y values, the new transform behaves effectively like an orthographic
349 // projection was added to the matrix sequence.
350 // - For z values, the new transform overrides any effect that the transform had on
351 // z, and instead it preserves the z value for any points that are transformed.
352 // - Because of linearity of transforms, this flattened transform also preserves the
353 // effect that any subsequent (post-multiplied) transforms would have on z values.
364 float CCMathUtil::smallestAngleBetweenVectors(const FloatSize
& v1
, const FloatSize
& v2
)
366 float dotProduct
= (v1
.width() * v2
.width() + v1
.height() * v2
.height()) / (v1
.diagonalLength() * v2
.diagonalLength());
367 // Clamp to compensate for rounding errors.
368 dotProduct
= std::max(-1.f
, std::min(1.f
, dotProduct
));
369 return rad2deg(acosf(dotProduct
));
372 FloatSize
CCMathUtil::projectVector(const FloatSize
& source
, const FloatSize
& destination
)
374 float sourceDotDestination
= source
.width() * destination
.width() + source
.height() * destination
.height();
375 float projectedLength
= sourceDotDestination
/ destination
.diagonalLengthSquared();
376 return FloatSize(projectedLength
* destination
.width(), projectedLength
* destination
.height());