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"
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"
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);
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
)
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.
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
)
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
)
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)));
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
));
280 return h
.cartesianPoint2d();
283 // The cartesian coordinates will be invalid after dividing by w.
286 // Avoid dividing by w if w == 0.
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
);
303 return h
.cartesianPoint3d();
306 // The cartesian coordinates will be invalid after dividing by w.
309 // Avoid dividing by w if w == 0.
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
;
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
);
341 // The cartesian coordinates will be valid in this case.
343 return h
.cartesianPoint2d();
346 // The cartesian coordinates will be invalid after dividing by w.
349 // Avoid dividing by w if w == 0.
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);
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
);
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
);