summaryrefslogtreecommitdiff
path: root/ui/gfx/geometry
diff options
context:
space:
mode:
Diffstat (limited to 'ui/gfx/geometry')
-rw-r--r--ui/gfx/geometry/angle_conversions.h29
-rw-r--r--ui/gfx/geometry/axis_transform2d.cc16
-rw-r--r--ui/gfx/geometry/axis_transform2d.h138
-rw-r--r--ui/gfx/geometry/axis_transform2d_unittest.cc91
-rw-r--r--ui/gfx/geometry/box_f.cc70
-rw-r--r--ui/gfx/geometry/box_f.h160
-rw-r--r--ui/gfx/geometry/box_unittest.cc175
-rw-r--r--ui/gfx/geometry/cubic_bezier.cc213
-rw-r--r--ui/gfx/geometry/cubic_bezier.h96
-rw-r--r--ui/gfx/geometry/cubic_bezier_unittest.cc230
-rw-r--r--ui/gfx/geometry/dip_util.cc88
-rw-r--r--ui/gfx/geometry/dip_util.h41
-rw-r--r--ui/gfx/geometry/insets_unittest.cc139
-rw-r--r--ui/gfx/geometry/matrix3_f.cc291
-rw-r--r--ui/gfx/geometry/matrix3_f.h139
-rw-r--r--ui/gfx/geometry/matrix3_unittest.cc182
-rw-r--r--ui/gfx/geometry/mojo/BUILD.gn51
-rw-r--r--ui/gfx/geometry/point3_f.cc40
-rw-r--r--ui/gfx/geometry/point3_f.h128
-rw-r--r--ui/gfx/geometry/point3_unittest.cc71
-rw-r--r--ui/gfx/geometry/point_unittest.cc236
-rw-r--r--ui/gfx/geometry/quad_f.cc134
-rw-r--r--ui/gfx/geometry/quad_f.h130
-rw-r--r--ui/gfx/geometry/quad_unittest.cc361
-rw-r--r--ui/gfx/geometry/quaternion.cc106
-rw-r--r--ui/gfx/geometry/quaternion.h93
-rw-r--r--ui/gfx/geometry/quaternion_unittest.cc169
-rw-r--r--ui/gfx/geometry/rect_conversions.cc82
-rw-r--r--ui/gfx/geometry/rect_conversions.h36
-rw-r--r--ui/gfx/geometry/rect_unittest.cc1141
-rw-r--r--ui/gfx/geometry/safe_integer_conversions_unittest.cc80
-rw-r--r--ui/gfx/geometry/scroll_offset_unittest.cc124
-rw-r--r--ui/gfx/geometry/size_unittest.cc251
-rw-r--r--ui/gfx/geometry/test/rect_test_util.cc23
-rw-r--r--ui/gfx/geometry/test/rect_test_util.h21
-rw-r--r--ui/gfx/geometry/vector2d_conversions.cc30
-rw-r--r--ui/gfx/geometry/vector2d_conversions.h24
-rw-r--r--ui/gfx/geometry/vector2d_unittest.cc293
-rw-r--r--ui/gfx/geometry/vector3d_f.cc108
-rw-r--r--ui/gfx/geometry/vector3d_f.h151
-rw-r--r--ui/gfx/geometry/vector3d_unittest.cc347
41 files changed, 6277 insertions, 51 deletions
diff --git a/ui/gfx/geometry/angle_conversions.h b/ui/gfx/geometry/angle_conversions.h
new file mode 100644
index 0000000000..68e9209f72
--- /dev/null
+++ b/ui/gfx/geometry/angle_conversions.h
@@ -0,0 +1,29 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_ANGLE_CONVERSIONS_H_
+#define UI_GFX_GEOMETRY_ANGLE_CONVERSIONS_H_
+
+#include "base/numerics/math_constants.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+GFX_EXPORT constexpr double DegToRad(double deg) {
+ return deg * base::kPiDouble / 180.0;
+}
+GFX_EXPORT constexpr float DegToRad(float deg) {
+ return deg * base::kPiFloat / 180.0f;
+}
+
+GFX_EXPORT constexpr double RadToDeg(double rad) {
+ return rad * 180.0 / base::kPiDouble;
+}
+GFX_EXPORT constexpr float RadToDeg(float rad) {
+ return rad * 180.0f / base::kPiFloat;
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_ANGLE_CONVERSIONS_H_
diff --git a/ui/gfx/geometry/axis_transform2d.cc b/ui/gfx/geometry/axis_transform2d.cc
new file mode 100644
index 0000000000..5b7d86a450
--- /dev/null
+++ b/ui/gfx/geometry/axis_transform2d.cc
@@ -0,0 +1,16 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/axis_transform2d.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string AxisTransform2d::ToString() const {
+ return base::StringPrintf("[%f, %s]", scale_,
+ translation_.ToString().c_str());
+}
+
+} // namespace gfx \ No newline at end of file
diff --git a/ui/gfx/geometry/axis_transform2d.h b/ui/gfx/geometry/axis_transform2d.h
new file mode 100644
index 0000000000..1829bf60fc
--- /dev/null
+++ b/ui/gfx/geometry/axis_transform2d.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
+#define UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
+
+#include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+// This class implements the subset of 2D linear transforms that only
+// translation and uniform scaling are allowed.
+// Internally this is stored as a scalar pre-scale factor, and a vector
+// for post-translation. The class constructor and member accessor follows
+// the same convention.
+class GFX_EXPORT AxisTransform2d {
+ public:
+ constexpr AxisTransform2d() = default;
+ constexpr AxisTransform2d(float scale, const Vector2dF& translation)
+ : scale_(scale), translation_(translation) {}
+
+ bool operator==(const AxisTransform2d& other) const {
+ return scale_ == other.scale_ && translation_ == other.translation_;
+ }
+ bool operator!=(const AxisTransform2d& other) const {
+ return !(*this == other);
+ }
+
+ void PreScale(float scale) { scale_ *= scale; }
+ void PostScale(float scale) {
+ scale_ *= scale;
+ translation_.Scale(scale);
+ }
+ void PreTranslate(const Vector2dF& translation) {
+ translation_ += ScaleVector2d(translation, scale_);
+ }
+ void PostTranslate(const Vector2dF& translation) {
+ translation_ += translation;
+ }
+
+ void PreConcat(const AxisTransform2d& pre) {
+ PreTranslate(pre.translation_);
+ PreScale(pre.scale_);
+ }
+ void PostConcat(const AxisTransform2d& post) {
+ PostScale(post.scale_);
+ PostTranslate(post.translation_);
+ }
+
+ void Invert() {
+ DCHECK(scale_);
+ scale_ = 1.f / scale_;
+ translation_.Scale(-scale_);
+ }
+
+ PointF MapPoint(const PointF& p) const {
+ return ScalePoint(p, scale_) + translation_;
+ }
+ PointF InverseMapPoint(const PointF& p) const {
+ return ScalePoint(p - translation_, 1.f / scale_);
+ }
+
+ RectF MapRect(const RectF& r) const {
+ DCHECK(scale_ >= 0.f);
+ return ScaleRect(r, scale_) + translation_;
+ }
+ RectF InverseMapRect(const RectF& r) const {
+ DCHECK(scale_ > 0.f);
+ return ScaleRect(r - translation_, 1.f / scale_);
+ }
+
+ float scale() const { return scale_; }
+ const Vector2dF& translation() const { return translation_; }
+
+ std::string ToString() const;
+
+ private:
+ // Scale is applied before translation, i.e.
+ // this->Transform(p) == scale_ * p + translation_
+ float scale_ = 1.f;
+ Vector2dF translation_;
+};
+
+static inline AxisTransform2d PreScaleAxisTransform2d(const AxisTransform2d& t,
+ float scale) {
+ AxisTransform2d result(t);
+ result.PreScale(scale);
+ return result;
+}
+
+static inline AxisTransform2d PostScaleAxisTransform2d(const AxisTransform2d& t,
+ float scale) {
+ AxisTransform2d result(t);
+ result.PostScale(scale);
+ return result;
+}
+
+static inline AxisTransform2d PreTranslateAxisTransform2d(
+ const AxisTransform2d& t,
+ const Vector2dF& translation) {
+ AxisTransform2d result(t);
+ result.PreTranslate(translation);
+ return result;
+}
+
+static inline AxisTransform2d PostTranslateAxisTransform2d(
+ const AxisTransform2d& t,
+ const Vector2dF& translation) {
+ AxisTransform2d result(t);
+ result.PostTranslate(translation);
+ return result;
+}
+
+static inline AxisTransform2d ConcatAxisTransform2d(
+ const AxisTransform2d& post,
+ const AxisTransform2d& pre) {
+ AxisTransform2d result(post);
+ result.PreConcat(pre);
+ return result;
+}
+
+static inline AxisTransform2d InvertAxisTransform2d(const AxisTransform2d& t) {
+ AxisTransform2d result = t;
+ result.Invert();
+ return result;
+}
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const AxisTransform2d&, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_AXIS_TRANSFORM2D_H_
diff --git a/ui/gfx/geometry/axis_transform2d_unittest.cc b/ui/gfx/geometry/axis_transform2d_unittest.cc
new file mode 100644
index 0000000000..b132c69635
--- /dev/null
+++ b/ui/gfx/geometry/axis_transform2d_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/axis_transform2d.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/test/gfx_util.h"
+
+namespace gfx {
+namespace {
+
+TEST(AxisTransform2dTest, Mapping) {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+
+ PointF p(150.f, 100.f);
+ EXPECT_EQ(PointF(191.25f, 180.f), t.MapPoint(p));
+ EXPECT_POINTF_EQ(PointF(117.f, 36.f), t.InverseMapPoint(p));
+
+ RectF r(150.f, 100.f, 22.5f, 37.5f);
+ EXPECT_EQ(RectF(191.25f, 180.f, 28.125f, 46.875f), t.MapRect(r));
+ EXPECT_RECTF_EQ(RectF(117.f, 36.f, 18.f, 30.f), t.InverseMapRect(r));
+}
+
+TEST(AxisTransform2dTest, Scaling) {
+ {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+ EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(3.75f, 55.f)),
+ PreScaleAxisTransform2d(t, 1.25));
+ t.PreScale(1.25);
+ EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(3.75f, 55.f)), t);
+ }
+
+ {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+ EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(4.6875f, 68.75f)),
+ PostScaleAxisTransform2d(t, 1.25));
+ t.PostScale(1.25);
+ EXPECT_EQ(AxisTransform2d(1.5625f, Vector2dF(4.6875f, 68.75f)), t);
+ }
+}
+
+TEST(AxisTransform2dTest, Translating) {
+ Vector2dF tr(3.f, -5.f);
+ {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+ EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(7.5f, 48.75f)),
+ PreTranslateAxisTransform2d(t, tr));
+ t.PreTranslate(tr);
+ EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(7.5f, 48.75f)), t);
+ }
+
+ {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+ EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(6.75f, 50.f)),
+ PostTranslateAxisTransform2d(t, tr));
+ t.PostTranslate(tr);
+ EXPECT_EQ(AxisTransform2d(1.25f, Vector2dF(6.75f, 50.f)), t);
+ }
+}
+
+TEST(AxisTransform2dTest, Concat) {
+ AxisTransform2d pre(1.25f, Vector2dF(3.75f, 55.f));
+ AxisTransform2d post(0.5f, Vector2dF(10.f, 5.f));
+ AxisTransform2d expectation(0.625f, Vector2dF(11.875f, 32.5f));
+ EXPECT_EQ(expectation, ConcatAxisTransform2d(post, pre));
+
+ AxisTransform2d post_concat = pre;
+ post_concat.PostConcat(post);
+ EXPECT_EQ(expectation, post_concat);
+
+ AxisTransform2d pre_concat = post;
+ pre_concat.PreConcat(pre);
+ EXPECT_EQ(expectation, pre_concat);
+}
+
+TEST(AxisTransform2dTest, Inverse) {
+ AxisTransform2d t(1.25f, Vector2dF(3.75f, 55.f));
+ AxisTransform2d inv_inplace = t;
+ inv_inplace.Invert();
+ AxisTransform2d inv_out_of_place = InvertAxisTransform2d(t);
+
+ EXPECT_AXIS_TRANSFORM2D_EQ(inv_inplace, inv_out_of_place);
+ EXPECT_AXIS_TRANSFORM2D_EQ(AxisTransform2d(),
+ ConcatAxisTransform2d(t, inv_inplace));
+ EXPECT_AXIS_TRANSFORM2D_EQ(AxisTransform2d(),
+ ConcatAxisTransform2d(inv_inplace, t));
+}
+
+} // namespace
+} // namespace gfx
diff --git a/ui/gfx/geometry/box_f.cc b/ui/gfx/geometry/box_f.cc
new file mode 100644
index 0000000000..674bb509c8
--- /dev/null
+++ b/ui/gfx/geometry/box_f.cc
@@ -0,0 +1,70 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/box_f.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string BoxF::ToString() const {
+ return base::StringPrintf("%s %fx%fx%f",
+ origin().ToString().c_str(),
+ width_,
+ height_,
+ depth_);
+}
+
+bool BoxF::IsEmpty() const {
+ return (width_ == 0 && height_ == 0) ||
+ (width_ == 0 && depth_ == 0) ||
+ (height_ == 0 && depth_ == 0);
+}
+
+void BoxF::ExpandTo(const Point3F& min, const Point3F& max) {
+ DCHECK_LE(min.x(), max.x());
+ DCHECK_LE(min.y(), max.y());
+ DCHECK_LE(min.z(), max.z());
+
+ float min_x = std::min(x(), min.x());
+ float min_y = std::min(y(), min.y());
+ float min_z = std::min(z(), min.z());
+ float max_x = std::max(right(), max.x());
+ float max_y = std::max(bottom(), max.y());
+ float max_z = std::max(front(), max.z());
+
+ origin_.SetPoint(min_x, min_y, min_z);
+ width_ = max_x - min_x;
+ height_ = max_y - min_y;
+ depth_ = max_z - min_z;
+}
+
+void BoxF::Union(const BoxF& box) {
+ if (IsEmpty()) {
+ *this = box;
+ return;
+ }
+ if (box.IsEmpty())
+ return;
+ ExpandTo(box);
+}
+
+void BoxF::ExpandTo(const Point3F& point) {
+ ExpandTo(point, point);
+}
+
+void BoxF::ExpandTo(const BoxF& box) {
+ ExpandTo(box.origin(), gfx::Point3F(box.right(), box.bottom(), box.front()));
+}
+
+BoxF UnionBoxes(const BoxF& a, const BoxF& b) {
+ BoxF result = a;
+ result.Union(b);
+ return result;
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/box_f.h b/ui/gfx/geometry/box_f.h
new file mode 100644
index 0000000000..b4d9eff1c0
--- /dev/null
+++ b/ui/gfx/geometry/box_f.h
@@ -0,0 +1,160 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_BOX_F_H_
+#define UI_GFX_GEOMETRY_BOX_F_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "ui/gfx/geometry/point3_f.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+
+namespace gfx {
+
+// A 3d version of gfx::RectF, with the positive z-axis pointed towards
+// the camera.
+class GFX_EXPORT BoxF {
+ public:
+ constexpr BoxF() : BoxF(0, 0, 0) {}
+ constexpr BoxF(float width, float height, float depth)
+ : BoxF(0, 0, 0, width, height, depth) {}
+ constexpr BoxF(float x,
+ float y,
+ float z,
+ float width,
+ float height,
+ float depth)
+ : BoxF(Point3F(x, y, z), width, height, depth) {}
+ constexpr BoxF(const Point3F& origin, float width, float height, float depth)
+ : origin_(origin),
+ width_(width >= 0 ? width : 0),
+ height_(height >= 0 ? height : 0),
+ depth_(depth >= 0 ? depth : 0) {}
+
+ // Scales all three axes by the given scale.
+ void Scale(float scale) {
+ Scale(scale, scale, scale);
+ }
+
+ // Scales each axis by the corresponding given scale.
+ void Scale(float x_scale, float y_scale, float z_scale) {
+ origin_.Scale(x_scale, y_scale, z_scale);
+ set_size(width_ * x_scale, height_ * y_scale, depth_ * z_scale);
+ }
+
+ // Moves the box by the specified distance in each dimension.
+ void operator+=(const Vector3dF& offset) {
+ origin_ += offset;
+ }
+
+ // Returns true if the box has no interior points.
+ bool IsEmpty() const;
+
+ // Computes the union of this box with the given box. The union is the
+ // smallest box that contains both boxes.
+ void Union(const BoxF& box);
+
+ std::string ToString() const;
+
+ constexpr float x() const { return origin_.x(); }
+ void set_x(float x) { origin_.set_x(x); }
+
+ constexpr float y() const { return origin_.y(); }
+ void set_y(float y) { origin_.set_y(y); }
+
+ constexpr float z() const { return origin_.z(); }
+ void set_z(float z) { origin_.set_z(z); }
+
+ constexpr float width() const { return width_; }
+ void set_width(float width) { width_ = width < 0 ? 0 : width; }
+
+ constexpr float height() const { return height_; }
+ void set_height(float height) { height_ = height < 0 ? 0 : height; }
+
+ constexpr float depth() const { return depth_; }
+ void set_depth(float depth) { depth_ = depth < 0 ? 0 : depth; }
+
+ constexpr float right() const { return x() + width(); }
+ constexpr float bottom() const { return y() + height(); }
+ constexpr float front() const { return z() + depth(); }
+
+ void set_size(float width, float height, float depth) {
+ width_ = width < 0 ? 0 : width;
+ height_ = height < 0 ? 0 : height;
+ depth_ = depth < 0 ? 0 : depth;
+ }
+
+ constexpr const Point3F& origin() const { return origin_; }
+ void set_origin(const Point3F& origin) { origin_ = origin; }
+
+ // Expands |this| to contain the given point, if necessary. Please note, even
+ // if |this| is empty, after the function |this| will continue to contain its
+ // |origin_|.
+ void ExpandTo(const Point3F& point);
+
+ // Expands |this| to contain the given box, if necessary. Please note, even
+ // if |this| is empty, after the function |this| will continue to contain its
+ // |origin_|.
+ void ExpandTo(const BoxF& box);
+
+ private:
+ // Expands the box to contain the two given points. It is required that each
+ // component of |min| is less than or equal to the corresponding component in
+ // |max|. Precisely, what this function does is ensure that after the function
+ // completes, |this| contains origin_, min, max, and origin_ + (width_,
+ // height_, depth_), even if the box is empty. Emptiness checks are handled in
+ // the public function Union.
+ void ExpandTo(const Point3F& min, const Point3F& max);
+
+ Point3F origin_;
+ float width_;
+ float height_;
+ float depth_;
+};
+
+GFX_EXPORT BoxF UnionBoxes(const BoxF& a, const BoxF& b);
+
+inline BoxF ScaleBox(const BoxF& b,
+ float x_scale,
+ float y_scale,
+ float z_scale) {
+ return BoxF(b.x() * x_scale,
+ b.y() * y_scale,
+ b.z() * z_scale,
+ b.width() * x_scale,
+ b.height() * y_scale,
+ b.depth() * z_scale);
+}
+
+inline BoxF ScaleBox(const BoxF& b, float scale) {
+ return ScaleBox(b, scale, scale, scale);
+}
+
+inline bool operator==(const BoxF& a, const BoxF& b) {
+ return a.origin() == b.origin() && a.width() == b.width() &&
+ a.height() == b.height() && a.depth() == b.depth();
+}
+
+inline bool operator!=(const BoxF& a, const BoxF& b) {
+ return !(a == b);
+}
+
+inline BoxF operator+(const BoxF& b, const Vector3dF& v) {
+ return BoxF(b.x() + v.x(),
+ b.y() + v.y(),
+ b.z() + v.z(),
+ b.width(),
+ b.height(),
+ b.depth());
+}
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const BoxF& box, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_BOX_F_H_
diff --git a/ui/gfx/geometry/box_unittest.cc b/ui/gfx/geometry/box_unittest.cc
new file mode 100644
index 0000000000..990fccc249
--- /dev/null
+++ b/ui/gfx/geometry/box_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/box_f.h"
+
+namespace gfx {
+
+TEST(BoxTest, Constructors) {
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).ToString(),
+ BoxF().ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, -3.f, -5.f, -7.f).ToString(),
+ BoxF().ToString());
+
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 3.f, 5.f, 7.f).ToString(),
+ BoxF(3.f, 5.f, 7.f).ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).ToString(),
+ BoxF(-3.f, -5.f, -7.f).ToString());
+
+ EXPECT_EQ(BoxF(2.f, 4.f, 6.f, 3.f, 5.f, 7.f).ToString(),
+ BoxF(Point3F(2.f, 4.f, 6.f), 3.f, 5.f, 7.f).ToString());
+ EXPECT_EQ(BoxF(2.f, 4.f, 6.f, 0.f, 0.f, 0.f).ToString(),
+ BoxF(Point3F(2.f, 4.f, 6.f), -3.f, -5.f, -7.f).ToString());
+}
+
+TEST(BoxTest, IsEmpty) {
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 0.f, 0.f).IsEmpty());
+
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 2.f, 0.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 2.f, 0.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 2.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 2.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 2.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 0.f, 2.f).IsEmpty());
+
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 0.f, 2.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 0.f, 2.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 0.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 0.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 2.f, 0.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 2.f, 0.f).IsEmpty());
+
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 2.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 2.f, 2.f).IsEmpty());
+}
+
+TEST(BoxTest, Union) {
+ BoxF empty_box;
+ BoxF box1(0.f, 0.f, 0.f, 1.f, 1.f, 1.f);
+ BoxF box2(0.f, 0.f, 0.f, 4.f, 6.f, 8.f);
+ BoxF box3(3.f, 4.f, 5.f, 6.f, 4.f, 0.f);
+
+ EXPECT_EQ(empty_box.ToString(), UnionBoxes(empty_box, empty_box).ToString());
+ EXPECT_EQ(box1.ToString(), UnionBoxes(empty_box, box1).ToString());
+ EXPECT_EQ(box1.ToString(), UnionBoxes(box1, empty_box).ToString());
+ EXPECT_EQ(box2.ToString(), UnionBoxes(empty_box, box2).ToString());
+ EXPECT_EQ(box2.ToString(), UnionBoxes(box2, empty_box).ToString());
+ EXPECT_EQ(box3.ToString(), UnionBoxes(empty_box, box3).ToString());
+ EXPECT_EQ(box3.ToString(), UnionBoxes(box3, empty_box).ToString());
+
+ // box_1 is contained in box_2
+ EXPECT_EQ(box2.ToString(), UnionBoxes(box1, box2).ToString());
+ EXPECT_EQ(box2.ToString(), UnionBoxes(box2, box1).ToString());
+
+ // box_1 and box_3 are disjoint
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 5.f).ToString(),
+ UnionBoxes(box1, box3).ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 5.f).ToString(),
+ UnionBoxes(box3, box1).ToString());
+
+ // box_2 and box_3 intersect, but neither contains the other
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 8.f).ToString(),
+ UnionBoxes(box2, box3).ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 8.f).ToString(),
+ UnionBoxes(box3, box2).ToString());
+}
+
+TEST(BoxTest, ExpandTo) {
+ BoxF box1;
+ BoxF box2(0.f, 0.f, 0.f, 1.f, 1.f, 1.f);
+ BoxF box3(1.f, 1.f, 1.f, 0.f, 0.f, 0.f);
+
+ Point3F point1(0.5f, 0.5f, 0.5f);
+ Point3F point2(-0.5f, -0.5f, -0.5f);
+
+ BoxF expected1_1(0.f, 0.f, 0.f, 0.5f, 0.5f, 0.5f);
+ BoxF expected1_2(-0.5f, -0.5f, -0.5f, 1.f, 1.f, 1.f);
+
+ BoxF expected2_1 = box2;
+ BoxF expected2_2(-0.5f, -0.5f, -0.5f, 1.5f, 1.5f, 1.5f);
+
+ BoxF expected3_1(0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f);
+ BoxF expected3_2(-0.5f, -0.5f, -0.5f, 1.5f, 1.5f, 1.5f);
+
+ box1.ExpandTo(point1);
+ EXPECT_EQ(expected1_1.ToString(), box1.ToString());
+ box1.ExpandTo(point2);
+ EXPECT_EQ(expected1_2.ToString(), box1.ToString());
+
+ box2.ExpandTo(point1);
+ EXPECT_EQ(expected2_1.ToString(), box2.ToString());
+ box2.ExpandTo(point2);
+ EXPECT_EQ(expected2_2.ToString(), box2.ToString());
+
+ box3.ExpandTo(point1);
+ EXPECT_EQ(expected3_1.ToString(), box3.ToString());
+ box3.ExpandTo(point2);
+ EXPECT_EQ(expected3_2.ToString(), box3.ToString());
+}
+
+TEST(BoxTest, Scale) {
+ BoxF box1(2.f, 3.f, 4.f, 5.f, 6.f, 7.f);
+
+ EXPECT_EQ(BoxF().ToString(), ScaleBox(box1, 0.f).ToString());
+ EXPECT_EQ(box1.ToString(), ScaleBox(box1, 1.f).ToString());
+ EXPECT_EQ(BoxF(4.f, 12.f, 24.f, 10.f, 24.f, 42.f).ToString(),
+ ScaleBox(box1, 2.f, 4.f, 6.f).ToString());
+
+ BoxF box2 = box1;
+ box2.Scale(0.f);
+ EXPECT_EQ(BoxF().ToString(), box2.ToString());
+
+ box2 = box1;
+ box2.Scale(1.f);
+ EXPECT_EQ(box1.ToString(), box2.ToString());
+
+ box2.Scale(2.f, 4.f, 6.f);
+ EXPECT_EQ(BoxF(4.f, 12.f, 24.f, 10.f, 24.f, 42.f).ToString(),
+ box2.ToString());
+}
+
+TEST(BoxTest, Equals) {
+ EXPECT_TRUE(BoxF() == BoxF());
+ EXPECT_TRUE(BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f) ==
+ BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 1.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 0.f, 1.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 1.f, 0.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 1.f, 0.f, 0.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 1.f, 0.f, 0.f, 0.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(1.f, 0.f, 0.f, 0.f, 0.f, 0.f));
+}
+
+TEST(BoxTest, NotEquals) {
+ EXPECT_FALSE(BoxF() != BoxF());
+ EXPECT_FALSE(BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f) !=
+ BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 1.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 0.f, 1.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 1.f, 0.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 1.f, 0.f, 0.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 1.f, 0.f, 0.f, 0.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(1.f, 0.f, 0.f, 0.f, 0.f, 0.f));
+}
+
+
+TEST(BoxTest, Offset) {
+ BoxF box1(2.f, 3.f, 4.f, 5.f, 6.f, 7.f);
+
+ EXPECT_EQ(box1.ToString(), (box1 + Vector3dF(0.f, 0.f, 0.f)).ToString());
+ EXPECT_EQ(BoxF(3.f, 1.f, 0.f, 5.f, 6.f, 7.f).ToString(),
+ (box1 + Vector3dF(1.f, -2.f, -4.f)).ToString());
+
+ BoxF box2 = box1;
+ box2 += Vector3dF(0.f, 0.f, 0.f);
+ EXPECT_EQ(box1.ToString(), box2.ToString());
+
+ box2 += Vector3dF(1.f, -2.f, -4.f);
+ EXPECT_EQ(BoxF(3.f, 1.f, 0.f, 5.f, 6.f, 7.f).ToString(),
+ box2.ToString());
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/cubic_bezier.cc b/ui/gfx/geometry/cubic_bezier.cc
new file mode 100644
index 0000000000..e42d893d7f
--- /dev/null
+++ b/ui/gfx/geometry/cubic_bezier.cc
@@ -0,0 +1,213 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/cubic_bezier.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/logging.h"
+
+namespace gfx {
+
+static const double kBezierEpsilon = 1e-7;
+
+CubicBezier::CubicBezier(double p1x, double p1y, double p2x, double p2y) {
+ InitCoefficients(p1x, p1y, p2x, p2y);
+ InitGradients(p1x, p1y, p2x, p2y);
+ InitRange(p1y, p2y);
+}
+
+CubicBezier::CubicBezier(const CubicBezier& other) = default;
+
+void CubicBezier::InitCoefficients(double p1x,
+ double p1y,
+ double p2x,
+ double p2y) {
+ // Calculate the polynomial coefficients, implicit first and last control
+ // points are (0,0) and (1,1).
+ cx_ = 3.0 * p1x;
+ bx_ = 3.0 * (p2x - p1x) - cx_;
+ ax_ = 1.0 - cx_ - bx_;
+
+ cy_ = 3.0 * p1y;
+ by_ = 3.0 * (p2y - p1y) - cy_;
+ ay_ = 1.0 - cy_ - by_;
+}
+
+void CubicBezier::InitGradients(double p1x,
+ double p1y,
+ double p2x,
+ double p2y) {
+ // End-point gradients are used to calculate timing function results
+ // outside the range [0, 1].
+ //
+ // There are three possibilities for the gradient at each end:
+ // (1) the closest control point is not horizontally coincident with regard to
+ // (0, 0) or (1, 1). In this case the line between the end point and
+ // the control point is tangent to the bezier at the end point.
+ // (2) the closest control point is coincident with the end point. In
+ // this case the line between the end point and the far control
+ // point is tangent to the bezier at the end point.
+ // (3) the closest control point is horizontally coincident with the end
+ // point, but vertically distinct. In this case the gradient at the
+ // end point is Infinite. However, this causes issues when
+ // interpolating. As a result, we break down to a simple case of
+ // 0 gradient under these conditions.
+
+ if (p1x > 0)
+ start_gradient_ = p1y / p1x;
+ else if (!p1y && p2x > 0)
+ start_gradient_ = p2y / p2x;
+ else
+ start_gradient_ = 0;
+
+ if (p2x < 1)
+ end_gradient_ = (p2y - 1) / (p2x - 1);
+ else if (p2x == 1 && p1x < 1)
+ end_gradient_ = (p1y - 1) / (p1x - 1);
+ else
+ end_gradient_ = 0;
+}
+
+// This works by taking taking the derivative of the cubic bezier, on the y
+// axis. We can then solve for where the derivative is zero to find the min
+// and max distance along the line. We the have to solve those in terms of time
+// rather than distance on the x-axis
+void CubicBezier::InitRange(double p1y, double p2y) {
+ range_min_ = 0;
+ range_max_ = 1;
+ if (0 <= p1y && p1y < 1 && 0 <= p2y && p2y <= 1)
+ return;
+
+ const double epsilon = kBezierEpsilon;
+
+ // Represent the function's derivative in the form at^2 + bt + c
+ // as in sampleCurveDerivativeY.
+ // (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros
+ // but does not actually give the slope of the curve.)
+ const double a = 3.0 * ay_;
+ const double b = 2.0 * by_;
+ const double c = cy_;
+
+ // Check if the derivative is constant.
+ if (std::abs(a) < epsilon && std::abs(b) < epsilon)
+ return;
+
+ // Zeros of the function's derivative.
+ double t1 = 0;
+ double t2 = 0;
+
+ if (std::abs(a) < epsilon) {
+ // The function's derivative is linear.
+ t1 = -c / b;
+ } else {
+ // The function's derivative is a quadratic. We find the zeros of this
+ // quadratic using the quadratic formula.
+ double discriminant = b * b - 4 * a * c;
+ if (discriminant < 0)
+ return;
+ double discriminant_sqrt = sqrt(discriminant);
+ t1 = (-b + discriminant_sqrt) / (2 * a);
+ t2 = (-b - discriminant_sqrt) / (2 * a);
+ }
+
+ double sol1 = 0;
+ double sol2 = 0;
+
+ // If the solution is in the range [0,1] then we include it, otherwise we
+ // ignore it.
+
+ // An interesting fact about these beziers is that they are only
+ // actually evaluated in [0,1]. After that we take the tangent at that point
+ // and linearly project it out.
+ if (0 < t1 && t1 < 1)
+ sol1 = SampleCurveY(t1);
+
+ if (0 < t2 && t2 < 1)
+ sol2 = SampleCurveY(t2);
+
+ range_min_ = std::min(std::min(range_min_, sol1), sol2);
+ range_max_ = std::max(std::max(range_max_, sol1), sol2);
+}
+
+double CubicBezier::GetDefaultEpsilon() {
+ return kBezierEpsilon;
+}
+
+double CubicBezier::SolveCurveX(double x, double epsilon) const {
+ DCHECK_GE(x, 0.0);
+ DCHECK_LE(x, 1.0);
+
+ double t0;
+ double t1;
+ double t2;
+ double x2;
+ double d2;
+ int i;
+
+ // First try a few iterations of Newton's method -- normally very fast.
+ for (t2 = x, i = 0; i < 8; i++) {
+ x2 = SampleCurveX(t2) - x;
+ if (fabs(x2) < epsilon)
+ return t2;
+ d2 = SampleCurveDerivativeX(t2);
+ if (fabs(d2) < 1e-6)
+ break;
+ t2 = t2 - x2 / d2;
+ }
+
+ // Fall back to the bisection method for reliability.
+ t0 = 0.0;
+ t1 = 1.0;
+ t2 = x;
+
+ while (t0 < t1) {
+ x2 = SampleCurveX(t2);
+ if (fabs(x2 - x) < epsilon)
+ return t2;
+ if (x > x2)
+ t0 = t2;
+ else
+ t1 = t2;
+ t2 = (t1 - t0) * .5 + t0;
+ }
+
+ // Failure.
+ return t2;
+}
+
+double CubicBezier::Solve(double x) const {
+ return SolveWithEpsilon(x, kBezierEpsilon);
+}
+
+double CubicBezier::SlopeWithEpsilon(double x, double epsilon) const {
+ x = std::min(std::max(x, 0.0), 1.0);
+ double t = SolveCurveX(x, epsilon);
+ double dx = SampleCurveDerivativeX(t);
+ double dy = SampleCurveDerivativeY(t);
+ return dy / dx;
+}
+
+double CubicBezier::Slope(double x) const {
+ return SlopeWithEpsilon(x, kBezierEpsilon);
+}
+
+double CubicBezier::GetX1() const {
+ return cx_ / 3.0;
+}
+
+double CubicBezier::GetY1() const {
+ return cy_ / 3.0;
+}
+
+double CubicBezier::GetX2() const {
+ return (bx_ + cx_) / 3.0 + GetX1();
+}
+
+double CubicBezier::GetY2() const {
+ return (by_ + cy_) / 3.0 + GetY1();
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/cubic_bezier.h b/ui/gfx/geometry/cubic_bezier.h
new file mode 100644
index 0000000000..013123999c
--- /dev/null
+++ b/ui/gfx/geometry/cubic_bezier.h
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_CUBIC_BEZIER_H_
+#define UI_GFX_GEOMETRY_CUBIC_BEZIER_H_
+
+#include "base/macros.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+class GFX_EXPORT CubicBezier {
+ public:
+ CubicBezier(double p1x, double p1y, double p2x, double p2y);
+ CubicBezier(const CubicBezier& other);
+
+ double SampleCurveX(double t) const {
+ // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
+ return ((ax_ * t + bx_) * t + cx_) * t;
+ }
+
+ double SampleCurveY(double t) const {
+ return ((ay_ * t + by_) * t + cy_) * t;
+ }
+
+ double SampleCurveDerivativeX(double t) const {
+ return (3.0 * ax_ * t + 2.0 * bx_) * t + cx_;
+ }
+
+ double SampleCurveDerivativeY(double t) const {
+ return (3.0 * ay_ * t + 2.0 * by_) * t + cy_;
+ }
+
+ static double GetDefaultEpsilon();
+
+ // Given an x value, find a parametric value it came from.
+ // x must be in [0, 1] range. Doesn't use gradients.
+ double SolveCurveX(double x, double epsilon) const;
+
+ // Evaluates y at the given x with default epsilon.
+ double Solve(double x) const;
+ // Evaluates y at the given x. The epsilon parameter provides a hint as to the
+ // required accuracy and is not guaranteed. Uses gradients if x is
+ // out of [0, 1] range.
+ double SolveWithEpsilon(double x, double epsilon) const {
+ if (x < 0.0)
+ return 0.0 + start_gradient_ * x;
+ if (x > 1.0)
+ return 1.0 + end_gradient_ * (x - 1.0);
+ return SampleCurveY(SolveCurveX(x, epsilon));
+ }
+
+ // Returns an approximation of dy/dx at the given x with default epsilon.
+ double Slope(double x) const;
+ // Returns an approximation of dy/dx at the given x.
+ // Clamps x to range [0, 1].
+ double SlopeWithEpsilon(double x, double epsilon) const;
+
+ // These getters are used rarely. We reverse compute them from coefficients.
+ // See CubicBezier::InitCoefficients. The speed has been traded for memory.
+ double GetX1() const;
+ double GetY1() const;
+ double GetX2() const;
+ double GetY2() const;
+
+ // Gets the bezier's minimum y value in the interval [0, 1].
+ double range_min() const { return range_min_; }
+ // Gets the bezier's maximum y value in the interval [0, 1].
+ double range_max() const { return range_max_; }
+
+ private:
+ void InitCoefficients(double p1x, double p1y, double p2x, double p2y);
+ void InitGradients(double p1x, double p1y, double p2x, double p2y);
+ void InitRange(double p1y, double p2y);
+
+ double ax_;
+ double bx_;
+ double cx_;
+
+ double ay_;
+ double by_;
+ double cy_;
+
+ double start_gradient_;
+ double end_gradient_;
+
+ double range_min_;
+ double range_max_;
+
+ DISALLOW_ASSIGN(CubicBezier);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_CUBIC_BEZIER_H_
diff --git a/ui/gfx/geometry/cubic_bezier_unittest.cc b/ui/gfx/geometry/cubic_bezier_unittest.cc
new file mode 100644
index 0000000000..74ccb6d414
--- /dev/null
+++ b/ui/gfx/geometry/cubic_bezier_unittest.cc
@@ -0,0 +1,230 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/cubic_bezier.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+namespace {
+
+TEST(CubicBezierTest, Basic) {
+ CubicBezier function(0.25, 0.0, 0.75, 1.0);
+
+ double epsilon = 0.00015;
+
+ EXPECT_NEAR(function.Solve(0), 0, epsilon);
+ EXPECT_NEAR(function.Solve(0.05), 0.01136, epsilon);
+ EXPECT_NEAR(function.Solve(0.1), 0.03978, epsilon);
+ EXPECT_NEAR(function.Solve(0.15), 0.079780, epsilon);
+ EXPECT_NEAR(function.Solve(0.2), 0.12803, epsilon);
+ EXPECT_NEAR(function.Solve(0.25), 0.18235, epsilon);
+ EXPECT_NEAR(function.Solve(0.3), 0.24115, epsilon);
+ EXPECT_NEAR(function.Solve(0.35), 0.30323, epsilon);
+ EXPECT_NEAR(function.Solve(0.4), 0.36761, epsilon);
+ EXPECT_NEAR(function.Solve(0.45), 0.43345, epsilon);
+ EXPECT_NEAR(function.Solve(0.5), 0.5, epsilon);
+ EXPECT_NEAR(function.Solve(0.6), 0.63238, epsilon);
+ EXPECT_NEAR(function.Solve(0.65), 0.69676, epsilon);
+ EXPECT_NEAR(function.Solve(0.7), 0.75884, epsilon);
+ EXPECT_NEAR(function.Solve(0.75), 0.81764, epsilon);
+ EXPECT_NEAR(function.Solve(0.8), 0.87196, epsilon);
+ EXPECT_NEAR(function.Solve(0.85), 0.92021, epsilon);
+ EXPECT_NEAR(function.Solve(0.9), 0.96021, epsilon);
+ EXPECT_NEAR(function.Solve(0.95), 0.98863, epsilon);
+ EXPECT_NEAR(function.Solve(1), 1, epsilon);
+
+ CubicBezier basic_use(0.5, 1.0, 0.5, 1.0);
+ EXPECT_EQ(0.875, basic_use.Solve(0.5));
+
+ CubicBezier overshoot(0.5, 2.0, 0.5, 2.0);
+ EXPECT_EQ(1.625, overshoot.Solve(0.5));
+
+ CubicBezier undershoot(0.5, -1.0, 0.5, -1.0);
+ EXPECT_EQ(-0.625, undershoot.Solve(0.5));
+}
+
+// Tests that solving the bezier works with knots with y not in (0, 1).
+TEST(CubicBezierTest, UnclampedYValues) {
+ CubicBezier function(0.5, -1.0, 0.5, 2.0);
+
+ double epsilon = 0.00015;
+
+ EXPECT_NEAR(function.Solve(0.0), 0.0, epsilon);
+ EXPECT_NEAR(function.Solve(0.05), -0.08954, epsilon);
+ EXPECT_NEAR(function.Solve(0.1), -0.15613, epsilon);
+ EXPECT_NEAR(function.Solve(0.15), -0.19641, epsilon);
+ EXPECT_NEAR(function.Solve(0.2), -0.20651, epsilon);
+ EXPECT_NEAR(function.Solve(0.25), -0.18232, epsilon);
+ EXPECT_NEAR(function.Solve(0.3), -0.11992, epsilon);
+ EXPECT_NEAR(function.Solve(0.35), -0.01672, epsilon);
+ EXPECT_NEAR(function.Solve(0.4), 0.12660, epsilon);
+ EXPECT_NEAR(function.Solve(0.45), 0.30349, epsilon);
+ EXPECT_NEAR(function.Solve(0.5), 0.50000, epsilon);
+ EXPECT_NEAR(function.Solve(0.55), 0.69651, epsilon);
+ EXPECT_NEAR(function.Solve(0.6), 0.87340, epsilon);
+ EXPECT_NEAR(function.Solve(0.65), 1.01672, epsilon);
+ EXPECT_NEAR(function.Solve(0.7), 1.11992, epsilon);
+ EXPECT_NEAR(function.Solve(0.75), 1.18232, epsilon);
+ EXPECT_NEAR(function.Solve(0.8), 1.20651, epsilon);
+ EXPECT_NEAR(function.Solve(0.85), 1.19641, epsilon);
+ EXPECT_NEAR(function.Solve(0.9), 1.15613, epsilon);
+ EXPECT_NEAR(function.Solve(0.95), 1.08954, epsilon);
+ EXPECT_NEAR(function.Solve(1.0), 1.0, epsilon);
+}
+
+TEST(CubicBezierTest, Range) {
+ double epsilon = 0.00015;
+
+ // Derivative is a constant.
+ std::unique_ptr<CubicBezier> function(
+ new CubicBezier(0.25, (1.0 / 3.0), 0.75, (2.0 / 3.0)));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative is linear.
+ function.reset(new CubicBezier(0.25, -0.5, 0.75, (-1.0 / 6.0)));
+ EXPECT_NEAR(function->range_min(), -0.225, epsilon);
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has no real roots.
+ function.reset(new CubicBezier(0.25, 0.25, 0.75, 0.5));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has exactly one real root.
+ function.reset(new CubicBezier(0.0, 1.0, 1.0, 0.0));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has one root < 0 and one root > 1.
+ function.reset(new CubicBezier(0.25, 0.1, 0.75, 0.9));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has two roots in [0,1].
+ function.reset(new CubicBezier(0.25, 2.5, 0.75, 0.5));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_NEAR(function->range_max(), 1.28818, epsilon);
+ function.reset(new CubicBezier(0.25, 0.5, 0.75, -1.5));
+ EXPECT_NEAR(function->range_min(), -0.28818, epsilon);
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has one root < 0 and one root in [0,1].
+ function.reset(new CubicBezier(0.25, 0.1, 0.75, 1.5));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_NEAR(function->range_max(), 1.10755, epsilon);
+
+ // Derivative has one root in [0,1] and one root > 1.
+ function.reset(new CubicBezier(0.25, -0.5, 0.75, 0.9));
+ EXPECT_NEAR(function->range_min(), -0.10755, epsilon);
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has two roots < 0.
+ function.reset(new CubicBezier(0.25, 0.3, 0.75, 0.633));
+ EXPECT_EQ(0, function->range_min());
+ EXPECT_EQ(1, function->range_max());
+
+ // Derivative has two roots > 1.
+ function.reset(new CubicBezier(0.25, 0.367, 0.75, 0.7));
+ EXPECT_EQ(0.f, function->range_min());
+ EXPECT_EQ(1.f, function->range_max());
+}
+
+TEST(CubicBezierTest, Slope) {
+ CubicBezier function(0.25, 0.0, 0.75, 1.0);
+
+ double epsilon = 0.00015;
+
+ EXPECT_NEAR(function.Slope(-0.1), 0, epsilon);
+ EXPECT_NEAR(function.Slope(0), 0, epsilon);
+ EXPECT_NEAR(function.Slope(0.05), 0.42170, epsilon);
+ EXPECT_NEAR(function.Slope(0.1), 0.69778, epsilon);
+ EXPECT_NEAR(function.Slope(0.15), 0.89121, epsilon);
+ EXPECT_NEAR(function.Slope(0.2), 1.03184, epsilon);
+ EXPECT_NEAR(function.Slope(0.25), 1.13576, epsilon);
+ EXPECT_NEAR(function.Slope(0.3), 1.21239, epsilon);
+ EXPECT_NEAR(function.Slope(0.35), 1.26751, epsilon);
+ EXPECT_NEAR(function.Slope(0.4), 1.30474, epsilon);
+ EXPECT_NEAR(function.Slope(0.45), 1.32628, epsilon);
+ EXPECT_NEAR(function.Slope(0.5), 1.33333, epsilon);
+ EXPECT_NEAR(function.Slope(0.55), 1.32628, epsilon);
+ EXPECT_NEAR(function.Slope(0.6), 1.30474, epsilon);
+ EXPECT_NEAR(function.Slope(0.65), 1.26751, epsilon);
+ EXPECT_NEAR(function.Slope(0.7), 1.21239, epsilon);
+ EXPECT_NEAR(function.Slope(0.75), 1.13576, epsilon);
+ EXPECT_NEAR(function.Slope(0.8), 1.03184, epsilon);
+ EXPECT_NEAR(function.Slope(0.85), 0.89121, epsilon);
+ EXPECT_NEAR(function.Slope(0.9), 0.69778, epsilon);
+ EXPECT_NEAR(function.Slope(0.95), 0.42170, epsilon);
+ EXPECT_NEAR(function.Slope(1), 0, epsilon);
+ EXPECT_NEAR(function.Slope(1.1), 0, epsilon);
+}
+
+TEST(CubicBezierTest, InputOutOfRange) {
+ CubicBezier simple(0.5, 1.0, 0.5, 1.0);
+ EXPECT_EQ(-2.0, simple.Solve(-1.0));
+ EXPECT_EQ(1.0, simple.Solve(2.0));
+
+ CubicBezier at_edge_of_range(0.5, 1.0, 0.5, 1.0);
+ EXPECT_EQ(0.0, at_edge_of_range.Solve(0.0));
+ EXPECT_EQ(1.0, at_edge_of_range.Solve(1.0));
+
+ CubicBezier large_epsilon(0.5, 1.0, 0.5, 1.0);
+ EXPECT_EQ(-2.0, large_epsilon.SolveWithEpsilon(-1.0, 1.0));
+ EXPECT_EQ(1.0, large_epsilon.SolveWithEpsilon(2.0, 1.0));
+
+ CubicBezier coincident_endpoints(0.0, 0.0, 1.0, 1.0);
+ EXPECT_EQ(-1.0, coincident_endpoints.Solve(-1.0));
+ EXPECT_EQ(2.0, coincident_endpoints.Solve(2.0));
+
+ CubicBezier vertical_gradient(0.0, 1.0, 1.0, 0.0);
+ EXPECT_EQ(0.0, vertical_gradient.Solve(-1.0));
+ EXPECT_EQ(1.0, vertical_gradient.Solve(2.0));
+
+ CubicBezier distinct_endpoints(0.1, 0.2, 0.8, 0.8);
+ EXPECT_EQ(-2.0, distinct_endpoints.Solve(-1.0));
+ EXPECT_EQ(2.0, distinct_endpoints.Solve(2.0));
+
+ CubicBezier coincident_endpoint(0.0, 0.0, 0.8, 0.8);
+ EXPECT_EQ(-1.0, coincident_endpoint.Solve(-1.0));
+ EXPECT_EQ(2.0, coincident_endpoint.Solve(2.0));
+
+ CubicBezier three_coincident_points(0.0, 0.0, 0.0, 0.0);
+ EXPECT_EQ(0, three_coincident_points.Solve(-1.0));
+ EXPECT_EQ(2.0, three_coincident_points.Solve(2.0));
+}
+
+TEST(CubicBezierTest, GetPoints) {
+ double epsilon = 0.00015;
+
+ CubicBezier cubic1(0.1, 0.2, 0.8, 0.9);
+ EXPECT_NEAR(0.1, cubic1.GetX1(), epsilon);
+ EXPECT_NEAR(0.2, cubic1.GetY1(), epsilon);
+ EXPECT_NEAR(0.8, cubic1.GetX2(), epsilon);
+ EXPECT_NEAR(0.9, cubic1.GetY2(), epsilon);
+
+ CubicBezier cubic_zero(0, 0, 0, 0);
+ EXPECT_NEAR(0, cubic_zero.GetX1(), epsilon);
+ EXPECT_NEAR(0, cubic_zero.GetY1(), epsilon);
+ EXPECT_NEAR(0, cubic_zero.GetX2(), epsilon);
+ EXPECT_NEAR(0, cubic_zero.GetY2(), epsilon);
+
+ CubicBezier cubic_one(1, 1, 1, 1);
+ EXPECT_NEAR(1, cubic_one.GetX1(), epsilon);
+ EXPECT_NEAR(1, cubic_one.GetY1(), epsilon);
+ EXPECT_NEAR(1, cubic_one.GetX2(), epsilon);
+ EXPECT_NEAR(1, cubic_one.GetY2(), epsilon);
+
+ CubicBezier cubic_oor(-0.5, -1.5, 1.5, -1.6);
+ EXPECT_NEAR(-0.5, cubic_oor.GetX1(), epsilon);
+ EXPECT_NEAR(-1.5, cubic_oor.GetY1(), epsilon);
+ EXPECT_NEAR(1.5, cubic_oor.GetX2(), epsilon);
+ EXPECT_NEAR(-1.6, cubic_oor.GetY2(), epsilon);
+}
+
+} // namespace
+} // namespace gfx
diff --git a/ui/gfx/geometry/dip_util.cc b/ui/gfx/geometry/dip_util.cc
new file mode 100644
index 0000000000..db8e1be5d5
--- /dev/null
+++ b/ui/gfx/geometry/dip_util.cc
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/dip_util.h"
+
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/point_conversions.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/geometry/size_conversions.h"
+
+namespace gfx {
+
+Insets ConvertInsetsToDIP(float scale_factor,
+ const gfx::Insets& insets_in_pixel) {
+ if (scale_factor == 1.f)
+ return insets_in_pixel;
+ return insets_in_pixel.Scale(1.f / scale_factor);
+}
+
+Point ConvertPointToDIP(float scale_factor, const Point& point_in_pixel) {
+ if (scale_factor == 1.f)
+ return point_in_pixel;
+ return ScaleToFlooredPoint(point_in_pixel, 1.f / scale_factor);
+}
+
+PointF ConvertPointToDIP(float scale_factor, const PointF& point_in_pixel) {
+ if (scale_factor == 1.f)
+ return point_in_pixel;
+ return ScalePoint(point_in_pixel, 1.f / scale_factor);
+}
+
+Size ConvertSizeToDIP(float scale_factor, const Size& size_in_pixel) {
+ if (scale_factor == 1.f)
+ return size_in_pixel;
+ return ScaleToFlooredSize(size_in_pixel, 1.f / scale_factor);
+}
+
+Rect ConvertRectToDIP(float scale_factor, const Rect& rect_in_pixel) {
+ if (scale_factor == 1.f)
+ return rect_in_pixel;
+ return ToFlooredRectDeprecated(
+ ScaleRect(RectF(rect_in_pixel), 1.f / scale_factor));
+}
+
+Insets ConvertInsetsToPixel(float scale_factor,
+ const gfx::Insets& insets_in_dip) {
+ if (scale_factor == 1.f)
+ return insets_in_dip;
+ return insets_in_dip.Scale(scale_factor);
+}
+
+Point ConvertPointToPixel(float scale_factor, const Point& point_in_dip) {
+ if (scale_factor == 1.f)
+ return point_in_dip;
+ return ScaleToFlooredPoint(point_in_dip, scale_factor);
+}
+
+PointF ConvertPointToPixel(float scale_factor, const PointF& point_in_dip) {
+ if (scale_factor == 1.f)
+ return point_in_dip;
+ return ScalePoint(point_in_dip, scale_factor);
+}
+
+Size ConvertSizeToPixel(float scale_factor, const Size& size_in_dip) {
+ if (scale_factor == 1.f)
+ return size_in_dip;
+ return ScaleToFlooredSize(size_in_dip, scale_factor);
+}
+
+Rect ConvertRectToPixel(float scale_factor, const Rect& rect_in_dip) {
+ // Use ToEnclosingRect() to ensure we paint all the possible pixels
+ // touched. ToEnclosingRect() floors the origin, and ceils the max
+ // coordinate. To do otherwise (such as flooring the size) potentially
+ // results in rounding down and not drawing all the pixels that are
+ // touched.
+ if (scale_factor == 1.f)
+ return rect_in_dip;
+ return ToEnclosingRect(
+ RectF(ScalePoint(gfx::PointF(rect_in_dip.origin()), scale_factor),
+ ScaleSize(gfx::SizeF(rect_in_dip.size()), scale_factor)));
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/dip_util.h b/ui/gfx/geometry/dip_util.h
new file mode 100644
index 0000000000..e88d49b007
--- /dev/null
+++ b/ui/gfx/geometry/dip_util.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_DIP_UTIL_H_
+#define UI_GFX_GEOMETRY_DIP_UTIL_H_
+
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+class Insets;
+class Point;
+class PointF;
+class Rect;
+class Size;
+
+GFX_EXPORT gfx::Insets ConvertInsetsToDIP(float scale_factor,
+ const gfx::Insets& insets_in_pixel);
+GFX_EXPORT gfx::Point ConvertPointToDIP(float scale_factor,
+ const gfx::Point& point_in_pixel);
+GFX_EXPORT gfx::PointF ConvertPointToDIP(float scale_factor,
+ const gfx::PointF& point_in_pixel);
+GFX_EXPORT gfx::Size ConvertSizeToDIP(float scale_factor,
+ const gfx::Size& size_in_pixel);
+GFX_EXPORT gfx::Rect ConvertRectToDIP(float scale_factor,
+ const gfx::Rect& rect_in_pixel);
+
+GFX_EXPORT gfx::Insets ConvertInsetsToPixel(float scale_factor,
+ const gfx::Insets& insets_in_dip);
+GFX_EXPORT gfx::Point ConvertPointToPixel(float scale_factor,
+ const gfx::Point& point_in_dip);
+GFX_EXPORT gfx::PointF ConvertPointToPixel(float scale_factor,
+ const gfx::PointF& point_in_dip);
+GFX_EXPORT gfx::Size ConvertSizeToPixel(float scale_factor,
+ const gfx::Size& size_in_dip);
+GFX_EXPORT gfx::Rect ConvertRectToPixel(float scale_factor,
+ const gfx::Rect& rect_in_dip);
+} // gfx
+
+#endif // UI_GFX_GEOMETRY_DIP_UTIL_H_
diff --git a/ui/gfx/geometry/insets_unittest.cc b/ui/gfx/geometry/insets_unittest.cc
new file mode 100644
index 0000000000..6f607d9173
--- /dev/null
+++ b/ui/gfx/geometry/insets_unittest.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/insets.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/insets_f.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d.h"
+
+TEST(InsetsTest, InsetsDefault) {
+ gfx::Insets insets;
+ EXPECT_EQ(0, insets.top());
+ EXPECT_EQ(0, insets.left());
+ EXPECT_EQ(0, insets.bottom());
+ EXPECT_EQ(0, insets.right());
+ EXPECT_EQ(0, insets.width());
+ EXPECT_EQ(0, insets.height());
+ EXPECT_TRUE(insets.IsEmpty());
+}
+
+TEST(InsetsTest, Insets) {
+ gfx::Insets insets(1, 2, 3, 4);
+ EXPECT_EQ(1, insets.top());
+ EXPECT_EQ(2, insets.left());
+ EXPECT_EQ(3, insets.bottom());
+ EXPECT_EQ(4, insets.right());
+ EXPECT_EQ(6, insets.width()); // Left + right.
+ EXPECT_EQ(4, insets.height()); // Top + bottom.
+ EXPECT_FALSE(insets.IsEmpty());
+}
+
+TEST(InsetsTest, Set) {
+ gfx::Insets insets;
+ insets.Set(1, 2, 3, 4);
+ EXPECT_EQ(1, insets.top());
+ EXPECT_EQ(2, insets.left());
+ EXPECT_EQ(3, insets.bottom());
+ EXPECT_EQ(4, insets.right());
+}
+
+TEST(InsetsTest, Operators) {
+ gfx::Insets insets;
+ insets.Set(1, 2, 3, 4);
+ insets += gfx::Insets(5, 6, 7, 8);
+ EXPECT_EQ(6, insets.top());
+ EXPECT_EQ(8, insets.left());
+ EXPECT_EQ(10, insets.bottom());
+ EXPECT_EQ(12, insets.right());
+
+ insets -= gfx::Insets(-1, 0, 1, 2);
+ EXPECT_EQ(7, insets.top());
+ EXPECT_EQ(8, insets.left());
+ EXPECT_EQ(9, insets.bottom());
+ EXPECT_EQ(10, insets.right());
+
+ insets = gfx::Insets(10, 10, 10, 10) + gfx::Insets(5, 5, 0, -20);
+ EXPECT_EQ(15, insets.top());
+ EXPECT_EQ(15, insets.left());
+ EXPECT_EQ(10, insets.bottom());
+ EXPECT_EQ(-10, insets.right());
+
+ insets = gfx::Insets(10, 10, 10, 10) - gfx::Insets(5, 5, 0, -20);
+ EXPECT_EQ(5, insets.top());
+ EXPECT_EQ(5, insets.left());
+ EXPECT_EQ(10, insets.bottom());
+ EXPECT_EQ(30, insets.right());
+}
+
+TEST(InsetsFTest, Operators) {
+ gfx::InsetsF insets;
+ insets.Set(1.f, 2.5f, 3.3f, 4.1f);
+ insets += gfx::InsetsF(5.8f, 6.7f, 7.6f, 8.5f);
+ EXPECT_FLOAT_EQ(6.8f, insets.top());
+ EXPECT_FLOAT_EQ(9.2f, insets.left());
+ EXPECT_FLOAT_EQ(10.9f, insets.bottom());
+ EXPECT_FLOAT_EQ(12.6f, insets.right());
+
+ insets -= gfx::InsetsF(-1.f, 0, 1.1f, 2.2f);
+ EXPECT_FLOAT_EQ(7.8f, insets.top());
+ EXPECT_FLOAT_EQ(9.2f, insets.left());
+ EXPECT_FLOAT_EQ(9.8f, insets.bottom());
+ EXPECT_FLOAT_EQ(10.4f, insets.right());
+
+ insets = gfx::InsetsF(10, 10.1f, 10.01f, 10.001f) +
+ gfx::InsetsF(5.5f, 5.f, 0, -20.2f);
+ EXPECT_FLOAT_EQ(15.5f, insets.top());
+ EXPECT_FLOAT_EQ(15.1f, insets.left());
+ EXPECT_FLOAT_EQ(10.01f, insets.bottom());
+ EXPECT_FLOAT_EQ(-10.199f, insets.right());
+
+ insets = gfx::InsetsF(10, 10.1f, 10.01f, 10.001f) -
+ gfx::InsetsF(5.5f, 5.f, 0, -20.2f);
+ EXPECT_FLOAT_EQ(4.5f, insets.top());
+ EXPECT_FLOAT_EQ(5.1f, insets.left());
+ EXPECT_FLOAT_EQ(10.01f, insets.bottom());
+ EXPECT_FLOAT_EQ(30.201f, insets.right());
+}
+
+TEST(InsetsTest, Equality) {
+ gfx::Insets insets1;
+ insets1.Set(1, 2, 3, 4);
+ gfx::Insets insets2;
+ // Test operator== and operator!=.
+ EXPECT_FALSE(insets1 == insets2);
+ EXPECT_TRUE(insets1 != insets2);
+
+ insets2.Set(1, 2, 3, 4);
+ EXPECT_TRUE(insets1 == insets2);
+ EXPECT_FALSE(insets1 != insets2);
+}
+
+TEST(InsetsTest, ToString) {
+ gfx::Insets insets(1, 2, 3, 4);
+ EXPECT_EQ("1,2,3,4", insets.ToString());
+}
+
+TEST(InsetsTest, Offset) {
+ const gfx::Insets insets(1, 2, 3, 4);
+ const gfx::Rect rect(5, 6, 7, 8);
+ const gfx::Vector2d vector(9, 10);
+
+ // Whether you inset then offset the rect, offset then inset the rect, or
+ // offset the insets then apply to the rect, the outcome should be the same.
+ gfx::Rect inset_first = rect;
+ inset_first.Inset(insets);
+ inset_first.Offset(vector);
+
+ gfx::Rect offset_first = rect;
+ offset_first.Offset(vector);
+ offset_first.Inset(insets);
+
+ gfx::Rect inset_by_offset = rect;
+ inset_by_offset.Inset(insets.Offset(vector));
+
+ EXPECT_EQ(inset_first, offset_first);
+ EXPECT_EQ(inset_by_offset, inset_first);
+}
diff --git a/ui/gfx/geometry/matrix3_f.cc b/ui/gfx/geometry/matrix3_f.cc
new file mode 100644
index 0000000000..1420ee539d
--- /dev/null
+++ b/ui/gfx/geometry/matrix3_f.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/matrix3_f.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include "base/numerics/math_constants.h"
+#include "base/strings/stringprintf.h"
+
+namespace {
+
+// This is only to make accessing indices self-explanatory.
+enum MatrixCoordinates {
+ M00,
+ M01,
+ M02,
+ M10,
+ M11,
+ M12,
+ M20,
+ M21,
+ M22,
+ M_END
+};
+
+template<typename T>
+double Determinant3x3(T data[M_END]) {
+ // This routine is separated from the Matrix3F::Determinant because in
+ // computing inverse we do want higher precision afforded by the explicit
+ // use of 'double'.
+ return
+ static_cast<double>(data[M00]) * (
+ static_cast<double>(data[M11]) * data[M22] -
+ static_cast<double>(data[M12]) * data[M21]) +
+ static_cast<double>(data[M01]) * (
+ static_cast<double>(data[M12]) * data[M20] -
+ static_cast<double>(data[M10]) * data[M22]) +
+ static_cast<double>(data[M02]) * (
+ static_cast<double>(data[M10]) * data[M21] -
+ static_cast<double>(data[M11]) * data[M20]);
+}
+
+} // namespace
+
+namespace gfx {
+
+Matrix3F::Matrix3F() {
+}
+
+Matrix3F::~Matrix3F() {
+}
+
+// static
+Matrix3F Matrix3F::Zeros() {
+ Matrix3F matrix;
+ matrix.set(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+ return matrix;
+}
+
+// static
+Matrix3F Matrix3F::Ones() {
+ Matrix3F matrix;
+ matrix.set(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
+ return matrix;
+}
+
+// static
+Matrix3F Matrix3F::Identity() {
+ Matrix3F matrix;
+ matrix.set(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+ return matrix;
+}
+
+// static
+Matrix3F Matrix3F::FromOuterProduct(const Vector3dF& a, const Vector3dF& bt) {
+ Matrix3F matrix;
+ matrix.set(a.x() * bt.x(), a.x() * bt.y(), a.x() * bt.z(),
+ a.y() * bt.x(), a.y() * bt.y(), a.y() * bt.z(),
+ a.z() * bt.x(), a.z() * bt.y(), a.z() * bt.z());
+ return matrix;
+}
+
+bool Matrix3F::IsEqual(const Matrix3F& rhs) const {
+ return 0 == memcmp(data_, rhs.data_, sizeof(data_));
+}
+
+bool Matrix3F::IsNear(const Matrix3F& rhs, float precision) const {
+ DCHECK(precision >= 0);
+ for (int i = 0; i < M_END; ++i) {
+ if (std::abs(data_[i] - rhs.data_[i]) > precision)
+ return false;
+ }
+ return true;
+}
+
+Matrix3F Matrix3F::Add(const Matrix3F& rhs) const {
+ Matrix3F result;
+ for (int i = 0; i < M_END; ++i)
+ result.data_[i] = data_[i] + rhs.data_[i];
+ return result;
+}
+
+Matrix3F Matrix3F::Subtract(const Matrix3F& rhs) const {
+ Matrix3F result;
+ for (int i = 0; i < M_END; ++i)
+ result.data_[i] = data_[i] - rhs.data_[i];
+ return result;
+}
+
+Matrix3F Matrix3F::Inverse() const {
+ Matrix3F inverse = Matrix3F::Zeros();
+ double determinant = Determinant3x3(data_);
+ if (std::numeric_limits<float>::epsilon() > std::abs(determinant))
+ return inverse; // Singular matrix. Return Zeros().
+
+ inverse.set(
+ static_cast<float>((data_[M11] * data_[M22] - data_[M12] * data_[M21]) /
+ determinant),
+ static_cast<float>((data_[M02] * data_[M21] - data_[M01] * data_[M22]) /
+ determinant),
+ static_cast<float>((data_[M01] * data_[M12] - data_[M02] * data_[M11]) /
+ determinant),
+ static_cast<float>((data_[M12] * data_[M20] - data_[M10] * data_[M22]) /
+ determinant),
+ static_cast<float>((data_[M00] * data_[M22] - data_[M02] * data_[M20]) /
+ determinant),
+ static_cast<float>((data_[M02] * data_[M10] - data_[M00] * data_[M12]) /
+ determinant),
+ static_cast<float>((data_[M10] * data_[M21] - data_[M11] * data_[M20]) /
+ determinant),
+ static_cast<float>((data_[M01] * data_[M20] - data_[M00] * data_[M21]) /
+ determinant),
+ static_cast<float>((data_[M00] * data_[M11] - data_[M01] * data_[M10]) /
+ determinant));
+ return inverse;
+}
+
+Matrix3F Matrix3F::Transpose() const {
+ Matrix3F transpose;
+ transpose.set(data_[M00], data_[M10], data_[M20], data_[M01], data_[M11],
+ data_[M21], data_[M02], data_[M12], data_[M22]);
+ return transpose;
+}
+
+float Matrix3F::Determinant() const {
+ return static_cast<float>(Determinant3x3(data_));
+}
+
+Vector3dF Matrix3F::SolveEigenproblem(Matrix3F* eigenvectors) const {
+ // The matrix must be symmetric.
+ const float epsilon = std::numeric_limits<float>::epsilon();
+ if (std::abs(data_[M01] - data_[M10]) > epsilon ||
+ std::abs(data_[M02] - data_[M20]) > epsilon ||
+ std::abs(data_[M12] - data_[M21]) > epsilon) {
+ NOTREACHED();
+ return Vector3dF();
+ }
+
+ float eigenvalues[3];
+ float p =
+ data_[M01] * data_[M01] +
+ data_[M02] * data_[M02] +
+ data_[M12] * data_[M12];
+
+ bool diagonal = std::abs(p) < epsilon;
+ if (diagonal) {
+ eigenvalues[0] = data_[M00];
+ eigenvalues[1] = data_[M11];
+ eigenvalues[2] = data_[M22];
+ } else {
+ float q = Trace() / 3.0f;
+ p = (data_[M00] - q) * (data_[M00] - q) +
+ (data_[M11] - q) * (data_[M11] - q) +
+ (data_[M22] - q) * (data_[M22] - q) +
+ 2 * p;
+ p = std::sqrt(p / 6);
+
+ // The computation below puts B as (A - qI) / p, where A is *this.
+ Matrix3F matrix_b(*this);
+ matrix_b.data_[M00] -= q;
+ matrix_b.data_[M11] -= q;
+ matrix_b.data_[M22] -= q;
+ for (int i = 0; i < M_END; ++i)
+ matrix_b.data_[i] /= p;
+
+ double half_det_b = Determinant3x3(matrix_b.data_) / 2.0;
+ // half_det_b should be in <-1, 1>, but beware of rounding error.
+ double phi = 0.0f;
+ if (half_det_b <= -1.0)
+ phi = base::kPiDouble / 3;
+ else if (half_det_b < 1.0)
+ phi = acos(half_det_b) / 3;
+
+ eigenvalues[0] = q + 2 * p * static_cast<float>(cos(phi));
+ eigenvalues[2] =
+ q + 2 * p * static_cast<float>(cos(phi + 2.0 * base::kPiDouble / 3.0));
+ eigenvalues[1] = 3 * q - eigenvalues[0] - eigenvalues[2];
+ }
+
+ // Put eigenvalues in the descending order.
+ int indices[3] = {0, 1, 2};
+ if (eigenvalues[2] > eigenvalues[1]) {
+ std::swap(eigenvalues[2], eigenvalues[1]);
+ std::swap(indices[2], indices[1]);
+ }
+
+ if (eigenvalues[1] > eigenvalues[0]) {
+ std::swap(eigenvalues[1], eigenvalues[0]);
+ std::swap(indices[1], indices[0]);
+ }
+
+ if (eigenvalues[2] > eigenvalues[1]) {
+ std::swap(eigenvalues[2], eigenvalues[1]);
+ std::swap(indices[2], indices[1]);
+ }
+
+ if (eigenvectors != NULL && diagonal) {
+ // Eigenvectors are e-vectors, just need to be sorted accordingly.
+ *eigenvectors = Zeros();
+ for (int i = 0; i < 3; ++i)
+ eigenvectors->set(indices[i], i, 1.0f);
+ } else if (eigenvectors != NULL) {
+ // Consult the following for a detailed discussion:
+ // Joachim Kopp
+ // Numerical diagonalization of hermitian 3x3 matrices
+ // arXiv.org preprint: physics/0610206
+ // Int. J. Mod. Phys. C19 (2008) 523-548
+
+ // TODO(motek): expand to handle correctly negative and multiple
+ // eigenvalues.
+ for (int i = 0; i < 3; ++i) {
+ float l = eigenvalues[i];
+ // B = A - l * I
+ Matrix3F matrix_b(*this);
+ matrix_b.data_[M00] -= l;
+ matrix_b.data_[M11] -= l;
+ matrix_b.data_[M22] -= l;
+ Vector3dF e1 = CrossProduct(matrix_b.get_column(0),
+ matrix_b.get_column(1));
+ Vector3dF e2 = CrossProduct(matrix_b.get_column(1),
+ matrix_b.get_column(2));
+ Vector3dF e3 = CrossProduct(matrix_b.get_column(2),
+ matrix_b.get_column(0));
+
+ // e1, e2 and e3 should point in the same direction.
+ if (DotProduct(e1, e2) < 0)
+ e2 = -e2;
+
+ if (DotProduct(e1, e3) < 0)
+ e3 = -e3;
+
+ Vector3dF eigvec = e1 + e2 + e3;
+ // Normalize.
+ eigvec.Scale(1.0f / eigvec.Length());
+ eigenvectors->set_column(i, eigvec);
+ }
+ }
+
+ return Vector3dF(eigenvalues[0], eigenvalues[1], eigenvalues[2]);
+}
+
+Matrix3F MatrixProduct(const Matrix3F& lhs, const Matrix3F& rhs) {
+ Matrix3F result = Matrix3F::Zeros();
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ result.set(i, j, DotProduct(lhs.get_row(i), rhs.get_column(j)));
+ }
+ }
+ return result;
+}
+
+Vector3dF MatrixProduct(const Matrix3F& lhs, const Vector3dF& rhs) {
+ return Vector3dF(DotProduct(lhs.get_row(0), rhs),
+ DotProduct(lhs.get_row(1), rhs),
+ DotProduct(lhs.get_row(2), rhs));
+}
+
+std::string Matrix3F::ToString() const {
+ return base::StringPrintf(
+ "[[%+0.4f, %+0.4f, %+0.4f],"
+ " [%+0.4f, %+0.4f, %+0.4f],"
+ " [%+0.4f, %+0.4f, %+0.4f]]",
+ data_[M00], data_[M01], data_[M02], data_[M10], data_[M11], data_[M12],
+ data_[M20], data_[M21], data_[M22]);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/matrix3_f.h b/ui/gfx/geometry/matrix3_f.h
new file mode 100644
index 0000000000..7fab2e3e7c
--- /dev/null
+++ b/ui/gfx/geometry/matrix3_f.h
@@ -0,0 +1,139 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_MATRIX3_F_H_
+#define UI_GFX_GEOMETRY_MATRIX3_F_H_
+
+#include "base/logging.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+
+namespace gfx {
+
+class GFX_EXPORT Matrix3F {
+ public:
+ ~Matrix3F();
+
+ static Matrix3F Zeros();
+ static Matrix3F Ones();
+ static Matrix3F Identity();
+ static Matrix3F FromOuterProduct(const Vector3dF& a, const Vector3dF& bt);
+
+ bool IsEqual(const Matrix3F& rhs) const;
+
+ // Element-wise comparison with given precision.
+ bool IsNear(const Matrix3F& rhs, float precision) const;
+
+ float get(int i, int j) const {
+ return data_[MatrixToArrayCoords(i, j)];
+ }
+
+ void set(int i, int j, float v) {
+ data_[MatrixToArrayCoords(i, j)] = v;
+ }
+
+ void set(float m00, float m01, float m02,
+ float m10, float m11, float m12,
+ float m20, float m21, float m22) {
+ data_[0] = m00;
+ data_[1] = m01;
+ data_[2] = m02;
+ data_[3] = m10;
+ data_[4] = m11;
+ data_[5] = m12;
+ data_[6] = m20;
+ data_[7] = m21;
+ data_[8] = m22;
+ }
+
+ Vector3dF get_row(int i) const {
+ return Vector3dF(data_[MatrixToArrayCoords(i, 0)],
+ data_[MatrixToArrayCoords(i, 1)],
+ data_[MatrixToArrayCoords(i, 2)]);
+ }
+
+ Vector3dF get_column(int i) const {
+ return Vector3dF(
+ data_[MatrixToArrayCoords(0, i)],
+ data_[MatrixToArrayCoords(1, i)],
+ data_[MatrixToArrayCoords(2, i)]);
+ }
+
+ void set_column(int i, const Vector3dF& c) {
+ data_[MatrixToArrayCoords(0, i)] = c.x();
+ data_[MatrixToArrayCoords(1, i)] = c.y();
+ data_[MatrixToArrayCoords(2, i)] = c.z();
+ }
+
+ // Produces a new matrix by adding the elements of |rhs| to this matrix
+ Matrix3F Add(const Matrix3F& rhs) const;
+ // Produces a new matrix by subtracting elements of |rhs| from this matrix.
+ Matrix3F Subtract(const Matrix3F& rhs) const;
+
+ // Returns an inverse of this if the matrix is non-singular, zero (== Zero())
+ // otherwise.
+ Matrix3F Inverse() const;
+
+ // Returns a transpose of this matrix.
+ Matrix3F Transpose() const;
+
+ // Value of the determinant of the matrix.
+ float Determinant() const;
+
+ // Trace (sum of diagonal elements) of the matrix.
+ float Trace() const {
+ return data_[MatrixToArrayCoords(0, 0)] +
+ data_[MatrixToArrayCoords(1, 1)] +
+ data_[MatrixToArrayCoords(2, 2)];
+ }
+
+ // Compute eigenvalues and (optionally) normalized eigenvectors of
+ // a positive defnite matrix *this. Eigenvectors are computed only if
+ // non-null |eigenvectors| matrix is passed. If it is NULL, the routine
+ // will not attempt to compute eigenvectors but will still return eigenvalues
+ // if they can be computed.
+ // If eigenvalues cannot be computed (the matrix does not meet constraints)
+ // the 0-vector is returned. Note that to retrieve eigenvalues, the matrix
+ // only needs to be symmetric while eigenvectors require it to be
+ // positive-definite. Passing a non-positive definite matrix will result in
+ // NaNs in vectors which cannot be computed.
+ // Eigenvectors are placed as column in |eigenvectors| in order corresponding
+ // to eigenvalues.
+ Vector3dF SolveEigenproblem(Matrix3F* eigenvectors) const;
+
+ std::string ToString() const;
+
+ private:
+ Matrix3F(); // Uninitialized default.
+
+ static int MatrixToArrayCoords(int i, int j) {
+ DCHECK(i >= 0 && i < 3);
+ DCHECK(j >= 0 && j < 3);
+ return i * 3 + j;
+ }
+
+ float data_[9];
+};
+
+inline bool operator==(const Matrix3F& lhs, const Matrix3F& rhs) {
+ return lhs.IsEqual(rhs);
+}
+
+// Matrix addition. Produces a new matrix by adding the corresponding elements
+// together.
+inline Matrix3F operator+(const Matrix3F& lhs, const Matrix3F& rhs) {
+ return lhs.Add(rhs);
+}
+
+// Matrix subtraction. Produces a new matrix by subtracting elements of rhs
+// from corresponding elements of lhs.
+inline Matrix3F operator-(const Matrix3F& lhs, const Matrix3F& rhs) {
+ return lhs.Subtract(rhs);
+}
+
+GFX_EXPORT Matrix3F MatrixProduct(const Matrix3F& lhs, const Matrix3F& rhs);
+GFX_EXPORT Vector3dF MatrixProduct(const Matrix3F& lhs, const Vector3dF& rhs);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_MATRIX3_F_H_
diff --git a/ui/gfx/geometry/matrix3_unittest.cc b/ui/gfx/geometry/matrix3_unittest.cc
new file mode 100644
index 0000000000..27e9182f94
--- /dev/null
+++ b/ui/gfx/geometry/matrix3_unittest.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cmath>
+#include <limits>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/matrix3_f.h"
+
+namespace gfx {
+namespace {
+
+TEST(Matrix3fTest, Constructors) {
+ Matrix3F zeros = Matrix3F::Zeros();
+ Matrix3F ones = Matrix3F::Ones();
+ Matrix3F identity = Matrix3F::Identity();
+
+ Matrix3F product_ones = Matrix3F::FromOuterProduct(
+ Vector3dF(1.0f, 1.0f, 1.0f), Vector3dF(1.0f, 1.0f, 1.0f));
+ Matrix3F product_zeros = Matrix3F::FromOuterProduct(
+ Vector3dF(1.0f, 1.0f, 1.0f), Vector3dF(0.0f, 0.0f, 0.0f));
+ EXPECT_EQ(ones, product_ones);
+ EXPECT_EQ(zeros, product_zeros);
+
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j)
+ EXPECT_EQ(i == j ? 1.0f : 0.0f, identity.get(i, j));
+ }
+}
+
+TEST(Matrix3fTest, DataAccess) {
+ Matrix3F matrix = Matrix3F::Ones();
+ Matrix3F identity = Matrix3F::Identity();
+
+ EXPECT_EQ(Vector3dF(0.0f, 1.0f, 0.0f), identity.get_column(1));
+ EXPECT_EQ(Vector3dF(0.0f, 1.0f, 0.0f), identity.get_row(1));
+ matrix.set(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f);
+ EXPECT_EQ(Vector3dF(2.0f, 5.0f, 8.0f), matrix.get_column(2));
+ EXPECT_EQ(Vector3dF(6.0f, 7.0f, 8.0f), matrix.get_row(2));
+ matrix.set_column(0, Vector3dF(0.1f, 0.2f, 0.3f));
+ matrix.set_column(0, Vector3dF(0.1f, 0.2f, 0.3f));
+ EXPECT_EQ(Vector3dF(0.1f, 0.2f, 0.3f), matrix.get_column(0));
+ EXPECT_EQ(Vector3dF(0.1f, 1.0f, 2.0f), matrix.get_row(0));
+
+ EXPECT_EQ(0.1f, matrix.get(0, 0));
+ EXPECT_EQ(5.0f, matrix.get(1, 2));
+}
+
+TEST(Matrix3fTest, Determinant) {
+ EXPECT_EQ(1.0f, Matrix3F::Identity().Determinant());
+ EXPECT_EQ(0.0f, Matrix3F::Zeros().Determinant());
+ EXPECT_EQ(0.0f, Matrix3F::Ones().Determinant());
+
+ // Now for something non-trivial...
+ Matrix3F matrix = Matrix3F::Zeros();
+ matrix.set(0, 5, 6, 8, 7, 0, 1, 9, 0);
+ EXPECT_EQ(390.0f, matrix.Determinant());
+ matrix.set(2, 0, 3 * matrix.get(0, 0));
+ matrix.set(2, 1, 3 * matrix.get(0, 1));
+ matrix.set(2, 2, 3 * matrix.get(0, 2));
+ EXPECT_EQ(0, matrix.Determinant());
+
+ matrix.set(0.57f, 0.205f, 0.942f,
+ 0.314f, 0.845f, 0.826f,
+ 0.131f, 0.025f, 0.962f);
+ EXPECT_NEAR(0.3149f, matrix.Determinant(), 0.0001f);
+}
+
+TEST(Matrix3fTest, Inverse) {
+ Matrix3F identity = Matrix3F::Identity();
+ Matrix3F inv_identity = identity.Inverse();
+ EXPECT_EQ(identity, inv_identity);
+
+ Matrix3F singular = Matrix3F::Zeros();
+ singular.set(1.0f, 3.0f, 4.0f,
+ 2.0f, 11.0f, 5.0f,
+ 0.5f, 1.5f, 2.0f);
+ EXPECT_EQ(0, singular.Determinant());
+ EXPECT_EQ(Matrix3F::Zeros(), singular.Inverse());
+
+ Matrix3F regular = Matrix3F::Zeros();
+ regular.set(0.57f, 0.205f, 0.942f,
+ 0.314f, 0.845f, 0.826f,
+ 0.131f, 0.025f, 0.962f);
+ Matrix3F inv_regular = regular.Inverse();
+ regular.set(2.51540616f, -0.55138018f, -1.98968043f,
+ -0.61552266f, 1.34920184f, -0.55573636f,
+ -0.32653861f, 0.04002158f, 1.32488726f);
+ EXPECT_TRUE(regular.IsNear(inv_regular, 0.00001f));
+}
+
+TEST(Matrix3fTest, Transpose) {
+ Matrix3F matrix = Matrix3F::Zeros();
+
+ matrix.set(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f);
+
+ Matrix3F transpose = matrix.Transpose();
+ EXPECT_EQ(Vector3dF(0.0f, 1.0f, 2.0f), transpose.get_column(0));
+ EXPECT_EQ(Vector3dF(3.0f, 4.0f, 5.0f), transpose.get_column(1));
+ EXPECT_EQ(Vector3dF(6.0f, 7.0f, 8.0f), transpose.get_column(2));
+
+ EXPECT_TRUE(matrix.IsEqual(transpose.Transpose()));
+}
+
+TEST(Matrix3fTest, EigenvectorsIdentity) {
+ // This block tests the trivial case of eigenvalues of the identity matrix.
+ Matrix3F identity = Matrix3F::Identity();
+ Vector3dF eigenvals = identity.SolveEigenproblem(NULL);
+ EXPECT_EQ(Vector3dF(1.0f, 1.0f, 1.0f), eigenvals);
+}
+
+TEST(Matrix3fTest, EigenvectorsDiagonal) {
+ // This block tests the another trivial case of eigenvalues of a diagonal
+ // matrix. Here we expect values to be sorted.
+ Matrix3F matrix = Matrix3F::Zeros();
+ matrix.set(0, 0, 1.0f);
+ matrix.set(1, 1, -2.5f);
+ matrix.set(2, 2, 3.14f);
+ Matrix3F eigenvectors = Matrix3F::Zeros();
+ Vector3dF eigenvals = matrix.SolveEigenproblem(&eigenvectors);
+ EXPECT_EQ(Vector3dF(3.14f, 1.0f, -2.5f), eigenvals);
+
+ EXPECT_EQ(Vector3dF(0.0f, 0.0f, 1.0f), eigenvectors.get_column(0));
+ EXPECT_EQ(Vector3dF(1.0f, 0.0f, 0.0f), eigenvectors.get_column(1));
+ EXPECT_EQ(Vector3dF(0.0f, 1.0f, 0.0f), eigenvectors.get_column(2));
+}
+
+TEST(Matrix3fTest, EigenvectorsNiceNotPositive) {
+ // This block tests computation of eigenvectors of a matrix where nice
+ // round values are expected.
+ Matrix3F matrix = Matrix3F::Zeros();
+ // This is not a positive-definite matrix but eigenvalues and the first
+ // eigenvector should nonetheless be computed correctly.
+ matrix.set(3, 2, 4, 2, 0, 2, 4, 2, 3);
+ Matrix3F eigenvectors = Matrix3F::Zeros();
+ Vector3dF eigenvals = matrix.SolveEigenproblem(&eigenvectors);
+ EXPECT_EQ(Vector3dF(8.0f, -1.0f, -1.0f), eigenvals);
+
+ Vector3dF expected_principal(0.66666667f, 0.33333333f, 0.66666667f);
+ EXPECT_NEAR(0.0f,
+ (expected_principal - eigenvectors.get_column(0)).Length(),
+ 0.000001f);
+}
+
+TEST(Matrix3fTest, EigenvectorsPositiveDefinite) {
+ // This block tests computation of eigenvectors of a matrix where output
+ // is not as nice as above, but it actually meets the definition.
+ Matrix3F matrix = Matrix3F::Zeros();
+ Matrix3F eigenvectors = Matrix3F::Zeros();
+ Matrix3F expected_eigenvectors = Matrix3F::Zeros();
+ matrix.set(1, -1, 2, -1, 4, 5, 2, 5, 0);
+ Vector3dF eigenvals = matrix.SolveEigenproblem(&eigenvectors);
+ Vector3dF expected_eigv(7.3996266f, 1.91197255f, -4.31159915f);
+ expected_eigv -= eigenvals;
+ EXPECT_NEAR(0, expected_eigv.LengthSquared(), 0.00001f);
+ expected_eigenvectors.set(0.04926317f, -0.92135662f, -0.38558414f,
+ 0.82134249f, 0.25703273f, -0.50924521f,
+ 0.56830419f, -0.2916096f, 0.76941158f);
+ EXPECT_TRUE(expected_eigenvectors.IsNear(eigenvectors, 0.00001f));
+}
+
+TEST(Matrix3fTest, Operators) {
+ Matrix3F matrix1 = Matrix3F::Zeros();
+ matrix1.set(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ EXPECT_EQ(matrix1 + Matrix3F::Zeros(), matrix1);
+
+ Matrix3F matrix2 = Matrix3F::Zeros();
+ matrix2.set(-1, -2, -3, -4, -5, -6, -7, -8, -9);
+ EXPECT_EQ(matrix1 + matrix2, Matrix3F::Zeros());
+
+ EXPECT_EQ(Matrix3F::Zeros() - matrix1, matrix2);
+
+ Matrix3F result = Matrix3F::Zeros();
+ result.set(2, 4, 6, 8, 10, 12, 14, 16, 18);
+ EXPECT_EQ(matrix1 - matrix2, result);
+ result.set(-2, -4, -6, -8, -10, -12, -14, -16, -18);
+ EXPECT_EQ(matrix2 - matrix1, result);
+}
+
+} // namespace
+} // namespace gfx
diff --git a/ui/gfx/geometry/mojo/BUILD.gn b/ui/gfx/geometry/mojo/BUILD.gn
deleted file mode 100644
index 2d0e1efbcf..0000000000
--- a/ui/gfx/geometry/mojo/BUILD.gn
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//mojo/public/tools/bindings/mojom.gni")
-
-# This target does NOT depend on skia. One can depend on this target to avoid
-# picking up a dependency on skia.
-mojom("mojo") {
- sources = [
- "geometry.mojom",
- ]
-
- check_includes_blink = false
-}
-
-mojom("test_interfaces") {
- sources = [
- "geometry_traits_test_service.mojom",
- ]
-
- public_deps = [
- ":mojo",
- ]
-}
-
-source_set("unit_test") {
- testonly = true
-
- sources = [
- "geometry_struct_traits_unittest.cc",
- ]
-
- deps = [
- ":test_interfaces",
- "//base",
- "//mojo/public/cpp/bindings",
- "//testing/gtest",
- "//ui/gfx/geometry",
- ]
-}
-
-source_set("struct_traits") {
- sources = [
- "geometry_struct_traits.h",
- ]
- public_deps = [
- ":mojo_shared_cpp_sources",
- "//ui/gfx/geometry",
- ]
-}
diff --git a/ui/gfx/geometry/point3_f.cc b/ui/gfx/geometry/point3_f.cc
new file mode 100644
index 0000000000..465376e55e
--- /dev/null
+++ b/ui/gfx/geometry/point3_f.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/point3_f.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string Point3F::ToString() const {
+ return base::StringPrintf("%f,%f,%f", x_, y_, z_);
+}
+
+Point3F operator+(const Point3F& lhs, const Vector3dF& rhs) {
+ float x = lhs.x() + rhs.x();
+ float y = lhs.y() + rhs.y();
+ float z = lhs.z() + rhs.z();
+ return Point3F(x, y, z);
+}
+
+// Subtract a vector from a point, producing a new point offset by the vector's
+// inverse.
+Point3F operator-(const Point3F& lhs, const Vector3dF& rhs) {
+ float x = lhs.x() - rhs.x();
+ float y = lhs.y() - rhs.y();
+ float z = lhs.z() - rhs.z();
+ return Point3F(x, y, z);
+}
+
+// Subtract one point from another, producing a vector that represents the
+// distances between the two points along each axis.
+Vector3dF operator-(const Point3F& lhs, const Point3F& rhs) {
+ float x = lhs.x() - rhs.x();
+ float y = lhs.y() - rhs.y();
+ float z = lhs.z() - rhs.z();
+ return Vector3dF(x, y, z);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/point3_f.h b/ui/gfx/geometry/point3_f.h
new file mode 100644
index 0000000000..15a560c497
--- /dev/null
+++ b/ui/gfx/geometry/point3_f.h
@@ -0,0 +1,128 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_POINT3_F_H_
+#define UI_GFX_GEOMETRY_POINT3_F_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+// A point has an x, y and z coordinate.
+class GFX_EXPORT Point3F {
+ public:
+ constexpr Point3F() : x_(0), y_(0), z_(0) {}
+ constexpr Point3F(float x, float y, float z) : x_(x), y_(y), z_(z) {}
+
+ constexpr explicit Point3F(const PointF& point)
+ : x_(point.x()), y_(point.y()), z_(0) {}
+
+ void Scale(float scale) {
+ Scale(scale, scale, scale);
+ }
+
+ void Scale(float x_scale, float y_scale, float z_scale) {
+ SetPoint(x() * x_scale, y() * y_scale, z() * z_scale);
+ }
+
+ constexpr float x() const { return x_; }
+ constexpr float y() const { return y_; }
+ constexpr float z() const { return z_; }
+
+ void set_x(float x) { x_ = x; }
+ void set_y(float y) { y_ = y; }
+ void set_z(float z) { z_ = z; }
+
+ void SetPoint(float x, float y, float z) {
+ x_ = x;
+ y_ = y;
+ z_ = z;
+ }
+
+ // Offset the point by the given vector.
+ void operator+=(const Vector3dF& v) {
+ x_ += v.x();
+ y_ += v.y();
+ z_ += v.z();
+ }
+
+ // Offset the point by the given vector's inverse.
+ void operator-=(const Vector3dF& v) {
+ x_ -= v.x();
+ y_ -= v.y();
+ z_ -= v.z();
+ }
+
+ // Returns the squared euclidean distance between two points.
+ float SquaredDistanceTo(const Point3F& other) const {
+ float dx = x_ - other.x_;
+ float dy = y_ - other.y_;
+ float dz = z_ - other.z_;
+ return dx * dx + dy * dy + dz * dz;
+ }
+
+ PointF AsPointF() const { return PointF(x_, y_); }
+
+ // Returns a string representation of 3d point.
+ std::string ToString() const;
+
+ private:
+ float x_;
+ float y_;
+ float z_;
+
+ // copy/assign are allowed.
+};
+
+inline bool operator==(const Point3F& lhs, const Point3F& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z();
+}
+
+inline bool operator!=(const Point3F& lhs, const Point3F& rhs) {
+ return !(lhs == rhs);
+}
+
+// Add a vector to a point, producing a new point offset by the vector.
+GFX_EXPORT Point3F operator+(const Point3F& lhs, const Vector3dF& rhs);
+
+// Subtract a vector from a point, producing a new point offset by the vector's
+// inverse.
+GFX_EXPORT Point3F operator-(const Point3F& lhs, const Vector3dF& rhs);
+
+// Subtract one point from another, producing a vector that represents the
+// distances between the two points along each axis.
+GFX_EXPORT Vector3dF operator-(const Point3F& lhs, const Point3F& rhs);
+
+inline Point3F PointAtOffsetFromOrigin(const Vector3dF& offset) {
+ return Point3F(offset.x(), offset.y(), offset.z());
+}
+
+inline Point3F ScalePoint(const Point3F& p,
+ float x_scale,
+ float y_scale,
+ float z_scale) {
+ return Point3F(p.x() * x_scale, p.y() * y_scale, p.z() * z_scale);
+}
+
+inline Point3F ScalePoint(const Point3F& p, const Vector3dF& v) {
+ return Point3F(p.x() * v.x(), p.y() * v.y(), p.z() * v.z());
+}
+
+inline Point3F ScalePoint(const Point3F& p, float scale) {
+ return ScalePoint(p, scale, scale, scale);
+}
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const Point3F& point, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_POINT3_F_H_
diff --git a/ui/gfx/geometry/point3_unittest.cc b/ui/gfx/geometry/point3_unittest.cc
new file mode 100644
index 0000000000..999b1f43c8
--- /dev/null
+++ b/ui/gfx/geometry/point3_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/point3_f.h"
+
+namespace gfx {
+
+TEST(Point3Test, VectorArithmetic) {
+ gfx::Point3F a(1.6f, 5.1f, 3.2f);
+ gfx::Vector3dF v1(3.1f, -3.2f, 9.3f);
+ gfx::Vector3dF v2(-8.1f, 1.2f, 3.3f);
+
+ static const struct {
+ gfx::Point3F expected;
+ gfx::Point3F actual;
+ } tests[] = {
+ { gfx::Point3F(4.7f, 1.9f, 12.5f), a + v1 },
+ { gfx::Point3F(-1.5f, 8.3f, -6.1f), a - v1 },
+ { a, a - v1 + v1 },
+ { a, a + v1 - v1 },
+ { a, a + gfx::Vector3dF() },
+ { gfx::Point3F(12.8f, 0.7f, 9.2f), a + v1 - v2 },
+ { gfx::Point3F(-9.6f, 9.5f, -2.8f), a - v1 + v2 }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i)
+ EXPECT_EQ(tests[i].expected.ToString(),
+ tests[i].actual.ToString());
+
+ a += v1;
+ EXPECT_EQ(Point3F(4.7f, 1.9f, 12.5f).ToString(), a.ToString());
+
+ a -= v2;
+ EXPECT_EQ(Point3F(12.8f, 0.7f, 9.2f).ToString(), a.ToString());
+}
+
+TEST(Point3Test, VectorFromPoints) {
+ gfx::Point3F a(1.6f, 5.2f, 3.2f);
+ gfx::Vector3dF v1(3.1f, -3.2f, 9.3f);
+
+ gfx::Point3F b(a + v1);
+ EXPECT_EQ((b - a).ToString(), v1.ToString());
+}
+
+TEST(Point3Test, Scale) {
+ EXPECT_EQ(Point3F().ToString(), ScalePoint(Point3F(), 2.f).ToString());
+ EXPECT_EQ(Point3F().ToString(),
+ ScalePoint(Point3F(), 2.f, 2.f, 2.f).ToString());
+
+ EXPECT_EQ(Point3F(2.f, -2.f, 4.f).ToString(),
+ ScalePoint(Point3F(1.f, -1.f, 2.f), 2.f).ToString());
+ EXPECT_EQ(Point3F(2.f, -3.f, 8.f).ToString(),
+ ScalePoint(Point3F(1.f, -1.f, 2.f), 2.f, 3.f, 4.f).ToString());
+
+ Point3F zero;
+ zero.Scale(2.f);
+ zero.Scale(6.f, 3.f, 1.5f);
+ EXPECT_EQ(Point3F().ToString(), zero.ToString());
+
+ Point3F point(1.f, -1.f, 2.f);
+ point.Scale(2.f);
+ point.Scale(6.f, 3.f, 1.5f);
+ EXPECT_EQ(Point3F(12.f, -6.f, 6.f).ToString(), point.ToString());
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/point_unittest.cc b/ui/gfx/geometry/point_unittest.cc
new file mode 100644
index 0000000000..b453dd7684
--- /dev/null
+++ b/ui/gfx/geometry/point_unittest.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/point_conversions.h"
+#include "ui/gfx/geometry/point_f.h"
+
+namespace gfx {
+
+TEST(PointTest, ToPointF) {
+ // Check that explicit conversion from integer to float compiles.
+ Point a(10, 20);
+ PointF b = PointF(a);
+
+ EXPECT_EQ(static_cast<float>(a.x()), b.x());
+ EXPECT_EQ(static_cast<float>(a.y()), b.y());
+}
+
+TEST(PointTest, IsOrigin) {
+ EXPECT_FALSE(Point(1, 0).IsOrigin());
+ EXPECT_FALSE(Point(0, 1).IsOrigin());
+ EXPECT_FALSE(Point(1, 2).IsOrigin());
+ EXPECT_FALSE(Point(-1, 0).IsOrigin());
+ EXPECT_FALSE(Point(0, -1).IsOrigin());
+ EXPECT_FALSE(Point(-1, -2).IsOrigin());
+ EXPECT_TRUE(Point(0, 0).IsOrigin());
+
+ EXPECT_FALSE(PointF(0.1f, 0).IsOrigin());
+ EXPECT_FALSE(PointF(0, 0.1f).IsOrigin());
+ EXPECT_FALSE(PointF(0.1f, 2).IsOrigin());
+ EXPECT_FALSE(PointF(-0.1f, 0).IsOrigin());
+ EXPECT_FALSE(PointF(0, -0.1f).IsOrigin());
+ EXPECT_FALSE(PointF(-0.1f, -2).IsOrigin());
+ EXPECT_TRUE(PointF(0, 0).IsOrigin());
+}
+
+TEST(PointTest, VectorArithmetic) {
+ Point a(1, 5);
+ Vector2d v1(3, -3);
+ Vector2d v2(-8, 1);
+
+ static const struct {
+ Point expected;
+ Point actual;
+ } tests[] = {
+ { Point(4, 2), a + v1 },
+ { Point(-2, 8), a - v1 },
+ { a, a - v1 + v1 },
+ { a, a + v1 - v1 },
+ { a, a + Vector2d() },
+ { Point(12, 1), a + v1 - v2 },
+ { Point(-10, 9), a - v1 + v2 }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i)
+ EXPECT_EQ(tests[i].expected.ToString(), tests[i].actual.ToString());
+}
+
+TEST(PointTest, OffsetFromPoint) {
+ Point a(1, 5);
+ Point b(-20, 8);
+ EXPECT_EQ(Vector2d(-20 - 1, 8 - 5).ToString(), (b - a).ToString());
+}
+
+TEST(PointTest, ToRoundedPoint) {
+ EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0, 0)));
+ EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Point(1, 1), ToRoundedPoint(PointF(0.5f, 0.5f)));
+ EXPECT_EQ(Point(1, 1), ToRoundedPoint(PointF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10, 10)));
+ EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Point(11, 11), ToRoundedPoint(PointF(10.5f, 10.5f)));
+ EXPECT_EQ(Point(11, 11), ToRoundedPoint(PointF(10.9999f, 10.9999f)));
+
+ EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10, -10)));
+ EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10.0001f, -10.0001f)));
+ EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10.4999f, -10.4999f)));
+ EXPECT_EQ(Point(-11, -11), ToRoundedPoint(PointF(-10.5f, -10.5f)));
+ EXPECT_EQ(Point(-11, -11), ToRoundedPoint(PointF(-10.9999f, -10.9999f)));
+}
+
+TEST(PointTest, Scale) {
+ EXPECT_EQ(PointF().ToString(), ScalePoint(PointF(), 2).ToString());
+ EXPECT_EQ(PointF().ToString(), ScalePoint(PointF(), 2, 2).ToString());
+
+ EXPECT_EQ(PointF(2, -2).ToString(), ScalePoint(PointF(1, -1), 2).ToString());
+ EXPECT_EQ(PointF(2, -2).ToString(),
+ ScalePoint(PointF(1, -1), 2, 2).ToString());
+
+ PointF zero;
+ PointF one(1, -1);
+
+ zero.Scale(2);
+ zero.Scale(3, 1.5);
+
+ one.Scale(2);
+ one.Scale(3, 1.5);
+
+ EXPECT_EQ(PointF().ToString(), zero.ToString());
+ EXPECT_EQ(PointF(6, -3).ToString(), one.ToString());
+}
+
+TEST(PointTest, ClampPoint) {
+ Point a;
+
+ a = Point(3, 5);
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+ a.SetToMax(Point(2, 4));
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+ a.SetToMax(Point(3, 5));
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+ a.SetToMax(Point(4, 2));
+ EXPECT_EQ(Point(4, 5).ToString(), a.ToString());
+ a.SetToMax(Point(8, 10));
+ EXPECT_EQ(Point(8, 10).ToString(), a.ToString());
+
+ a.SetToMin(Point(9, 11));
+ EXPECT_EQ(Point(8, 10).ToString(), a.ToString());
+ a.SetToMin(Point(8, 10));
+ EXPECT_EQ(Point(8, 10).ToString(), a.ToString());
+ a.SetToMin(Point(11, 9));
+ EXPECT_EQ(Point(8, 9).ToString(), a.ToString());
+ a.SetToMin(Point(7, 11));
+ EXPECT_EQ(Point(7, 9).ToString(), a.ToString());
+ a.SetToMin(Point(3, 5));
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+}
+
+TEST(PointTest, ClampPointF) {
+ PointF a;
+
+ a = PointF(3.5f, 5.5f);
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(2.5f, 4.5f));
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(3.5f, 5.5f));
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(4.5f, 2.5f));
+ EXPECT_EQ(PointF(4.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(8.5f, 10.5f));
+ EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString());
+
+ a.SetToMin(PointF(9.5f, 11.5f));
+ EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(8.5f, 10.5f));
+ EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(11.5f, 9.5f));
+ EXPECT_EQ(PointF(8.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(7.5f, 11.5f));
+ EXPECT_EQ(PointF(7.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(3.5f, 5.5f));
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+}
+
+TEST(PointTest, Offset) {
+ Point test(3, 4);
+ test.Offset(5, -8);
+ EXPECT_EQ(test, Point(8, -4));
+}
+
+TEST(PointTest, VectorMath) {
+ Point test = Point(3, 4);
+ test += Vector2d(5, -8);
+ EXPECT_EQ(test, Point(8, -4));
+
+ Point test2 = Point(3, 4);
+ test2 -= Vector2d(5, -8);
+ EXPECT_EQ(test2, Point(-2, 12));
+}
+
+TEST(PointTest, IntegerOverflow) {
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ Point max_point(int_max, int_max);
+ Point min_point(int_min, int_min);
+ Point test;
+
+ test = Point();
+ test.Offset(int_max, int_max);
+ EXPECT_EQ(test, max_point);
+
+ test = Point();
+ test.Offset(int_min, int_min);
+ EXPECT_EQ(test, min_point);
+
+ test = Point(10, 20);
+ test.Offset(int_max, int_max);
+ EXPECT_EQ(test, max_point);
+
+ test = Point(-10, -20);
+ test.Offset(int_min, int_min);
+ EXPECT_EQ(test, min_point);
+
+ test = Point();
+ test += Vector2d(int_max, int_max);
+ EXPECT_EQ(test, max_point);
+
+ test = Point();
+ test += Vector2d(int_min, int_min);
+ EXPECT_EQ(test, min_point);
+
+ test = Point(10, 20);
+ test += Vector2d(int_max, int_max);
+ EXPECT_EQ(test, max_point);
+
+ test = Point(-10, -20);
+ test += Vector2d(int_min, int_min);
+ EXPECT_EQ(test, min_point);
+
+ test = Point();
+ test -= Vector2d(int_max, int_max);
+ EXPECT_EQ(test, Point(-int_max, -int_max));
+
+ test = Point();
+ test -= Vector2d(int_min, int_min);
+ EXPECT_EQ(test, max_point);
+
+ test = Point(10, 20);
+ test -= Vector2d(int_min, int_min);
+ EXPECT_EQ(test, max_point);
+
+ test = Point(-10, -20);
+ test -= Vector2d(int_max, int_max);
+ EXPECT_EQ(test, min_point);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/quad_f.cc b/ui/gfx/geometry/quad_f.cc
new file mode 100644
index 0000000000..8ed8b91700
--- /dev/null
+++ b/ui/gfx/geometry/quad_f.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/quad_f.h"
+
+#include <limits>
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+void QuadF::operator=(const RectF& rect) {
+ p1_ = PointF(rect.x(), rect.y());
+ p2_ = PointF(rect.right(), rect.y());
+ p3_ = PointF(rect.right(), rect.bottom());
+ p4_ = PointF(rect.x(), rect.bottom());
+}
+
+std::string QuadF::ToString() const {
+ return base::StringPrintf("%s;%s;%s;%s",
+ p1_.ToString().c_str(),
+ p2_.ToString().c_str(),
+ p3_.ToString().c_str(),
+ p4_.ToString().c_str());
+}
+
+static inline bool WithinEpsilon(float a, float b) {
+ return std::abs(a - b) < std::numeric_limits<float>::epsilon();
+}
+
+bool QuadF::IsRectilinear() const {
+ return
+ (WithinEpsilon(p1_.x(), p2_.x()) && WithinEpsilon(p2_.y(), p3_.y()) &&
+ WithinEpsilon(p3_.x(), p4_.x()) && WithinEpsilon(p4_.y(), p1_.y())) ||
+ (WithinEpsilon(p1_.y(), p2_.y()) && WithinEpsilon(p2_.x(), p3_.x()) &&
+ WithinEpsilon(p3_.y(), p4_.y()) && WithinEpsilon(p4_.x(), p1_.x()));
+}
+
+bool QuadF::IsCounterClockwise() const {
+ // This math computes the signed area of the quad. Positive area
+ // indicates the quad is clockwise; negative area indicates the quad is
+ // counter-clockwise. Note carefully: this is backwards from conventional
+ // math because our geometric space uses screen coordiantes with y-axis
+ // pointing downards.
+ // Reference: http://mathworld.wolfram.com/PolygonArea.html.
+ // The equation can be written:
+ // Signed area = determinant1 + determinant2 + determinant3 + determinant4
+ // In practise, Refactoring the computation of adding determinants so that
+ // reducing the number of operations. The equation is:
+ // Signed area = element1 + element2 - element3 - element4
+
+ float p24 = p2_.y() - p4_.y();
+ float p31 = p3_.y() - p1_.y();
+
+ // Up-cast to double so this cannot overflow.
+ double element1 = static_cast<double>(p1_.x()) * p24;
+ double element2 = static_cast<double>(p2_.x()) * p31;
+ double element3 = static_cast<double>(p3_.x()) * p24;
+ double element4 = static_cast<double>(p4_.x()) * p31;
+
+ return element1 + element2 < element3 + element4;
+}
+
+static inline bool PointIsInTriangle(const PointF& point,
+ const PointF& r1,
+ const PointF& r2,
+ const PointF& r3) {
+ // Compute the barycentric coordinates (u, v, w) of |point| relative to the
+ // triangle (r1, r2, r3) by the solving the system of equations:
+ // 1) point = u * r1 + v * r2 + w * r3
+ // 2) u + v + w = 1
+ // This algorithm comes from Christer Ericson's Real-Time Collision Detection.
+
+ Vector2dF r31 = r1 - r3;
+ Vector2dF r32 = r2 - r3;
+ Vector2dF r3p = point - r3;
+
+ // Promote to doubles so all the math below is done with doubles, because
+ // otherwise it gets incorrect results on arm64.
+ double r31x = r31.x();
+ double r31y = r31.y();
+ double r32x = r32.x();
+ double r32y = r32.y();
+
+ double denom = r32y * r31x - r32x * r31y;
+ double u = (r32y * r3p.x() - r32x * r3p.y()) / denom;
+ double v = (r31x * r3p.y() - r31y * r3p.x()) / denom;
+ double w = 1.0 - u - v;
+
+ // Use the barycentric coordinates to test if |point| is inside the
+ // triangle (r1, r2, r2).
+ return (u >= 0) && (v >= 0) && (w >= 0);
+}
+
+bool QuadF::Contains(const PointF& point) const {
+ return PointIsInTriangle(point, p1_, p2_, p3_)
+ || PointIsInTriangle(point, p1_, p3_, p4_);
+}
+
+void QuadF::Scale(float x_scale, float y_scale) {
+ p1_.Scale(x_scale, y_scale);
+ p2_.Scale(x_scale, y_scale);
+ p3_.Scale(x_scale, y_scale);
+ p4_.Scale(x_scale, y_scale);
+}
+
+void QuadF::operator+=(const Vector2dF& rhs) {
+ p1_ += rhs;
+ p2_ += rhs;
+ p3_ += rhs;
+ p4_ += rhs;
+}
+
+void QuadF::operator-=(const Vector2dF& rhs) {
+ p1_ -= rhs;
+ p2_ -= rhs;
+ p3_ -= rhs;
+ p4_ -= rhs;
+}
+
+QuadF operator+(const QuadF& lhs, const Vector2dF& rhs) {
+ QuadF result = lhs;
+ result += rhs;
+ return result;
+}
+
+QuadF operator-(const QuadF& lhs, const Vector2dF& rhs) {
+ QuadF result = lhs;
+ result -= rhs;
+ return result;
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/quad_f.h b/ui/gfx/geometry/quad_f.h
new file mode 100644
index 0000000000..8e45b3e2de
--- /dev/null
+++ b/ui/gfx/geometry/quad_f.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_QUAD_F_H_
+#define UI_GFX_GEOMETRY_QUAD_F_H_
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <cmath>
+#include <iosfwd>
+#include <string>
+
+#include "base/logging.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+// A Quad is defined by four corners, allowing it to have edges that are not
+// axis-aligned, unlike a Rect.
+class GFX_EXPORT QuadF {
+ public:
+ constexpr QuadF() = default;
+ constexpr QuadF(const PointF& p1,
+ const PointF& p2,
+ const PointF& p3,
+ const PointF& p4)
+ : p1_(p1), p2_(p2), p3_(p3), p4_(p4) {}
+
+ constexpr explicit QuadF(const RectF& rect)
+ : p1_(rect.x(), rect.y()),
+ p2_(rect.right(), rect.y()),
+ p3_(rect.right(), rect.bottom()),
+ p4_(rect.x(), rect.bottom()) {}
+
+ void operator=(const RectF& rect);
+
+ void set_p1(const PointF& p) { p1_ = p; }
+ void set_p2(const PointF& p) { p2_ = p; }
+ void set_p3(const PointF& p) { p3_ = p; }
+ void set_p4(const PointF& p) { p4_ = p; }
+
+ constexpr const PointF& p1() const { return p1_; }
+ constexpr const PointF& p2() const { return p2_; }
+ constexpr const PointF& p3() const { return p3_; }
+ constexpr const PointF& p4() const { return p4_; }
+
+ // Returns true if the quad is an axis-aligned rectangle.
+ bool IsRectilinear() const;
+
+ // Returns true if the points of the quad are in counter-clockwise order. This
+ // assumes that the quad is convex, and that no three points are collinear.
+ bool IsCounterClockwise() const;
+
+ // Returns true if the |point| is contained within the quad, or lies on on
+ // edge of the quad. This assumes that the quad is convex.
+ bool Contains(const gfx::PointF& point) const;
+
+ // Returns a rectangle that bounds the four points of the quad. The points of
+ // the quad may lie on the right/bottom edge of the resulting rectangle,
+ // rather than being strictly inside it.
+ RectF BoundingBox() const {
+ float rl = std::min(std::min(p1_.x(), p2_.x()), std::min(p3_.x(), p4_.x()));
+ float rr = std::max(std::max(p1_.x(), p2_.x()), std::max(p3_.x(), p4_.x()));
+ float rt = std::min(std::min(p1_.y(), p2_.y()), std::min(p3_.y(), p4_.y()));
+ float rb = std::max(std::max(p1_.y(), p2_.y()), std::max(p3_.y(), p4_.y()));
+ return RectF(rl, rt, rr - rl, rb - rt);
+ }
+
+ // Realigns the corners in the quad by rotating them n corners to the right.
+ void Realign(size_t times) {
+ DCHECK_LE(times, 4u);
+ for (size_t i = 0; i < times; ++i) {
+ PointF temp = p1_;
+ p1_ = p2_;
+ p2_ = p3_;
+ p3_ = p4_;
+ p4_ = temp;
+ }
+ }
+
+ // Add a vector to the quad, offseting each point in the quad by the vector.
+ void operator+=(const Vector2dF& rhs);
+ // Subtract a vector from the quad, offseting each point in the quad by the
+ // inverse of the vector.
+ void operator-=(const Vector2dF& rhs);
+
+ // Scale each point in the quad by the |scale| factor.
+ void Scale(float scale) { Scale(scale, scale); }
+
+ // Scale each point in the quad by the scale factors along each axis.
+ void Scale(float x_scale, float y_scale);
+
+ // Returns a string representation of quad.
+ std::string ToString() const;
+
+ private:
+ PointF p1_;
+ PointF p2_;
+ PointF p3_;
+ PointF p4_;
+};
+
+inline bool operator==(const QuadF& lhs, const QuadF& rhs) {
+ return
+ lhs.p1() == rhs.p1() && lhs.p2() == rhs.p2() &&
+ lhs.p3() == rhs.p3() && lhs.p4() == rhs.p4();
+}
+
+inline bool operator!=(const QuadF& lhs, const QuadF& rhs) {
+ return !(lhs == rhs);
+}
+
+// Add a vector to a quad, offseting each point in the quad by the vector.
+GFX_EXPORT QuadF operator+(const QuadF& lhs, const Vector2dF& rhs);
+// Subtract a vector from a quad, offseting each point in the quad by the
+// inverse of the vector.
+GFX_EXPORT QuadF operator-(const QuadF& lhs, const Vector2dF& rhs);
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const QuadF& quad, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_QUAD_F_H_
diff --git a/ui/gfx/geometry/quad_unittest.cc b/ui/gfx/geometry/quad_unittest.cc
new file mode 100644
index 0000000000..2731583aab
--- /dev/null
+++ b/ui/gfx/geometry/quad_unittest.cc
@@ -0,0 +1,361 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/quad_f.h"
+#include "ui/gfx/geometry/rect_f.h"
+
+namespace gfx {
+
+TEST(QuadTest, Construction) {
+ // Verify constructors.
+ PointF a(1, 1);
+ PointF b(2, 1);
+ PointF c(2, 2);
+ PointF d(1, 2);
+ PointF e;
+ QuadF q1;
+ QuadF q2(e, e, e, e);
+ QuadF q3(a, b, c, d);
+ QuadF q4(BoundingRect(a, c));
+ EXPECT_EQ(q1, q2);
+ EXPECT_EQ(q3, q4);
+
+ // Verify getters.
+ EXPECT_EQ(q3.p1(), a);
+ EXPECT_EQ(q3.p2(), b);
+ EXPECT_EQ(q3.p3(), c);
+ EXPECT_EQ(q3.p4(), d);
+
+ // Verify setters.
+ q3.set_p1(b);
+ q3.set_p2(c);
+ q3.set_p3(d);
+ q3.set_p4(a);
+ EXPECT_EQ(q3.p1(), b);
+ EXPECT_EQ(q3.p2(), c);
+ EXPECT_EQ(q3.p3(), d);
+ EXPECT_EQ(q3.p4(), a);
+
+ // Verify operator=(Rect)
+ EXPECT_NE(q1, q4);
+ q1 = BoundingRect(a, c);
+ EXPECT_EQ(q1, q4);
+
+ // Verify operator=(Quad)
+ EXPECT_NE(q1, q3);
+ q1 = q3;
+ EXPECT_EQ(q1, q3);
+}
+
+TEST(QuadTest, AddingVectors) {
+ PointF a(1, 1);
+ PointF b(2, 1);
+ PointF c(2, 2);
+ PointF d(1, 2);
+ Vector2dF v(3.5f, -2.5f);
+
+ QuadF q1(a, b, c, d);
+ QuadF added = q1 + v;
+ q1 += v;
+ QuadF expected1(PointF(4.5f, -1.5f),
+ PointF(5.5f, -1.5f),
+ PointF(5.5f, -0.5f),
+ PointF(4.5f, -0.5f));
+ EXPECT_EQ(expected1, added);
+ EXPECT_EQ(expected1, q1);
+
+ QuadF q2(a, b, c, d);
+ QuadF subtracted = q2 - v;
+ q2 -= v;
+ QuadF expected2(PointF(-2.5f, 3.5f),
+ PointF(-1.5f, 3.5f),
+ PointF(-1.5f, 4.5f),
+ PointF(-2.5f, 4.5f));
+ EXPECT_EQ(expected2, subtracted);
+ EXPECT_EQ(expected2, q2);
+
+ QuadF q3(a, b, c, d);
+ q3 += v;
+ q3 -= v;
+ EXPECT_EQ(QuadF(a, b, c, d), q3);
+ EXPECT_EQ(q3, (q3 + v - v));
+}
+
+TEST(QuadTest, IsRectilinear) {
+ PointF a(1, 1);
+ PointF b(2, 1);
+ PointF c(2, 2);
+ PointF d(1, 2);
+ Vector2dF v(3.5f, -2.5f);
+
+ EXPECT_TRUE(QuadF().IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b, c, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b, c, d) + v).IsRectilinear());
+
+ float epsilon = std::numeric_limits<float>::epsilon();
+ PointF a2(1 + epsilon / 2, 1 + epsilon / 2);
+ PointF b2(2 + epsilon / 2, 1 + epsilon / 2);
+ PointF c2(2 + epsilon / 2, 2 + epsilon / 2);
+ PointF d2(1 + epsilon / 2, 2 + epsilon / 2);
+ EXPECT_TRUE(QuadF(a2, b, c, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a2, b, c, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b2, c, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b2, c, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b, c2, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b, c2, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b, c, d2).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b, c, d2) + v).IsRectilinear());
+
+ struct {
+ PointF a_off, b_off, c_off, d_off;
+ } tests[] = {
+ {
+ PointF(1, 1.00001f),
+ PointF(2, 1.00001f),
+ PointF(2, 2.00001f),
+ PointF(1, 2.00001f)
+ },
+ {
+ PointF(1.00001f, 1),
+ PointF(2.00001f, 1),
+ PointF(2.00001f, 2),
+ PointF(1.00001f, 2)
+ },
+ {
+ PointF(1.00001f, 1.00001f),
+ PointF(2.00001f, 1.00001f),
+ PointF(2.00001f, 2.00001f),
+ PointF(1.00001f, 2.00001f)
+ },
+ {
+ PointF(1, 0.99999f),
+ PointF(2, 0.99999f),
+ PointF(2, 1.99999f),
+ PointF(1, 1.99999f)
+ },
+ {
+ PointF(0.99999f, 1),
+ PointF(1.99999f, 1),
+ PointF(1.99999f, 2),
+ PointF(0.99999f, 2)
+ },
+ {
+ PointF(0.99999f, 0.99999f),
+ PointF(1.99999f, 0.99999f),
+ PointF(1.99999f, 1.99999f),
+ PointF(0.99999f, 1.99999f)
+ }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ PointF a_off = tests[i].a_off;
+ PointF b_off = tests[i].b_off;
+ PointF c_off = tests[i].c_off;
+ PointF d_off = tests[i].d_off;
+
+ EXPECT_FALSE(QuadF(a_off, b, c, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b, c, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b_off, c, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b_off, c, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b, c_off, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b, c_off, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b, c, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b, c, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b, c_off, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b, c_off, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b_off, c, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b_off, c, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b_off, c_off, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b_off, c_off, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b, c_off, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b, c_off, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b_off, c, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b_off, c, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b_off, c_off, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b_off, c_off, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a_off, b_off, c_off, d_off).IsRectilinear());
+ EXPECT_TRUE((QuadF(a_off, b_off, c_off, d_off) + v).IsRectilinear());
+ }
+}
+
+TEST(QuadTest, IsCounterClockwise) {
+ PointF a1(1, 1);
+ PointF b1(2, 1);
+ PointF c1(2, 2);
+ PointF d1(1, 2);
+ EXPECT_FALSE(QuadF(a1, b1, c1, d1).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b1, c1, d1, a1).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a1, d1, c1, b1).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(c1, b1, a1, d1).IsCounterClockwise());
+
+ // Slightly more complicated quads should work just as easily.
+ PointF a2(1.3f, 1.4f);
+ PointF b2(-0.7f, 4.9f);
+ PointF c2(1.8f, 6.2f);
+ PointF d2(2.1f, 1.6f);
+ EXPECT_TRUE(QuadF(a2, b2, c2, d2).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(b2, c2, d2, a2).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(a2, d2, c2, b2).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(c2, b2, a2, d2).IsCounterClockwise());
+
+ // Quads with 3 collinear points should work correctly, too.
+ PointF a3(0, 0);
+ PointF b3(1, 0);
+ PointF c3(2, 0);
+ PointF d3(1, 1);
+ EXPECT_FALSE(QuadF(a3, b3, c3, d3).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b3, c3, d3, a3).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a3, d3, c3, b3).IsCounterClockwise());
+ // The next expectation in particular would fail for an implementation
+ // that incorrectly uses only a cross product of the first 3 vertices.
+ EXPECT_TRUE(QuadF(c3, b3, a3, d3).IsCounterClockwise());
+
+ // Non-convex quads should work correctly, too.
+ PointF a4(0, 0);
+ PointF b4(1, 1);
+ PointF c4(2, 0);
+ PointF d4(1, 3);
+ EXPECT_FALSE(QuadF(a4, b4, c4, d4).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b4, c4, d4, a4).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a4, d4, c4, b4).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(c4, b4, a4, d4).IsCounterClockwise());
+
+ // A quad with huge coordinates should not fail this check due to
+ // single-precision overflow.
+ PointF a5(1e30f, 1e30f);
+ PointF b5(1e35f, 1e30f);
+ PointF c5(1e35f, 1e35f);
+ PointF d5(1e30f, 1e35f);
+ EXPECT_FALSE(QuadF(a5, b5, c5, d5).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b5, c5, d5, a5).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a5, d5, c5, b5).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(c5, b5, a5, d5).IsCounterClockwise());
+}
+
+TEST(QuadTest, BoundingBox) {
+ RectF r(3.2f, 5.4f, 7.007f, 12.01f);
+ EXPECT_EQ(r, QuadF(r).BoundingBox());
+
+ PointF a(1.3f, 1.4f);
+ PointF b(-0.7f, 4.9f);
+ PointF c(1.8f, 6.2f);
+ PointF d(2.1f, 1.6f);
+ float left = -0.7f;
+ float top = 1.4f;
+ float right = 2.1f;
+ float bottom = 6.2f;
+ EXPECT_EQ(RectF(left, top, right - left, bottom - top),
+ QuadF(a, b, c, d).BoundingBox());
+}
+
+TEST(QuadTest, ContainsPoint) {
+ PointF a(1.3f, 1.4f);
+ PointF b(-0.8f, 4.4f);
+ PointF c(1.8f, 6.1f);
+ PointF d(2.1f, 1.6f);
+
+ Vector2dF epsilon_x(2 * std::numeric_limits<float>::epsilon(), 0);
+ Vector2dF epsilon_y(0, 2 * std::numeric_limits<float>::epsilon());
+
+ Vector2dF ac_center = c - a;
+ ac_center.Scale(0.5f);
+ Vector2dF bd_center = d - b;
+ bd_center.Scale(0.5f);
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(a + ac_center));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(b + bd_center));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(c - ac_center));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(d - bd_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - ac_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - bd_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + ac_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + bd_center));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(a));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - epsilon_y));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a + epsilon_x));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(a + epsilon_y));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(b));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - epsilon_y));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(b + epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b + epsilon_y));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(c));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c - epsilon_x));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(c - epsilon_y));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + epsilon_y));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(d));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(d - epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d - epsilon_y));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + epsilon_y));
+
+ // Test a simple square.
+ PointF s1(-1, -1);
+ PointF s2(1, -1);
+ PointF s3(1, 1);
+ PointF s4(-1, 1);
+ // Top edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, -1.0f)));
+ // Bottom edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, 1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0.0f, 1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, 1.0f)));
+ // Left edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.1f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 0.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.1f)));
+ // Right edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.1f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 0.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.1f)));
+ // Centered inside.
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0, 0)));
+ // Centered outside.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, 0)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, 0)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(0, -1.1f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(0, 1.1f)));
+}
+
+TEST(QuadTest, Scale) {
+ PointF a(1.3f, 1.4f);
+ PointF b(-0.8f, 4.4f);
+ PointF c(1.8f, 6.1f);
+ PointF d(2.1f, 1.6f);
+ QuadF q1(a, b, c, d);
+ q1.Scale(1.5f);
+
+ PointF a_scaled = ScalePoint(a, 1.5f);
+ PointF b_scaled = ScalePoint(b, 1.5f);
+ PointF c_scaled = ScalePoint(c, 1.5f);
+ PointF d_scaled = ScalePoint(d, 1.5f);
+ EXPECT_EQ(q1, QuadF(a_scaled, b_scaled, c_scaled, d_scaled));
+
+ QuadF q2;
+ q2.Scale(1.5f);
+ EXPECT_EQ(q2, q2);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/quaternion.cc b/ui/gfx/geometry/quaternion.cc
new file mode 100644
index 0000000000..f2be00b857
--- /dev/null
+++ b/ui/gfx/geometry/quaternion.cc
@@ -0,0 +1,106 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/quaternion.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/numerics/math_constants.h"
+#include "base/strings/stringprintf.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+
+namespace gfx {
+
+namespace {
+
+const double kEpsilon = 1e-5;
+
+} // namespace
+
+Quaternion::Quaternion(const Vector3dF& axis, double theta) {
+ // Rotation angle is the product of |angle| and the magnitude of |axis|.
+ double length = axis.Length();
+ if (std::abs(length) < kEpsilon)
+ return;
+
+ Vector3dF normalized = axis;
+ normalized.Scale(1.0 / length);
+
+ theta *= 0.5;
+ double s = sin(theta);
+ x_ = normalized.x() * s;
+ y_ = normalized.y() * s;
+ z_ = normalized.z() * s;
+ w_ = cos(theta);
+}
+
+Quaternion::Quaternion(const Vector3dF& from, const Vector3dF& to) {
+ double dot = gfx::DotProduct(from, to);
+ double norm = sqrt(from.LengthSquared() * to.LengthSquared());
+ double real = norm + dot;
+ gfx::Vector3dF axis;
+ if (real < kEpsilon * norm) {
+ real = 0.0f;
+ axis = std::abs(from.x()) > std::abs(from.z())
+ ? gfx::Vector3dF{-from.y(), from.x(), 0.0}
+ : gfx::Vector3dF{0.0, -from.z(), from.y()};
+ } else {
+ axis = gfx::CrossProduct(from, to);
+ }
+ x_ = axis.x();
+ y_ = axis.y();
+ z_ = axis.z();
+ w_ = real;
+ *this = this->Normalized();
+}
+
+// Taken from http://www.w3.org/TR/css3-transforms/.
+Quaternion Quaternion::Slerp(const Quaternion& q, double t) const {
+ double dot = x_ * q.x_ + y_ * q.y_ + z_ * q.z_ + w_ * q.w_;
+
+ // Clamp dot to -1.0 <= dot <= 1.0.
+ dot = std::min(std::max(dot, -1.0), 1.0);
+
+ // Quaternions are facing the same direction.
+ if (std::abs(dot - 1.0) < kEpsilon || std::abs(dot + 1.0) < kEpsilon)
+ return *this;
+
+ double denom = std::sqrt(1.0 - dot * dot);
+ double theta = std::acos(dot);
+ double w = std::sin(t * theta) * (1.0 / denom);
+
+ double s1 = std::cos(t * theta) - dot * w;
+ double s2 = w;
+
+ return (s1 * *this) + (s2 * q);
+}
+
+Quaternion Quaternion::Lerp(const Quaternion& q, double t) const {
+ return (((1.0 - t) * *this) + (t * q)).Normalized();
+}
+
+double Quaternion::Length() const {
+ return x_ * x_ + y_ * y_ + z_ * z_ + w_ * w_;
+}
+
+Quaternion Quaternion::Normalized() const {
+ double length = Length();
+ if (length < kEpsilon)
+ return *this;
+ return *this / sqrt(length);
+}
+
+std::string Quaternion::ToString() const {
+ // q = (con(abs(v_theta)/2), v_theta/abs(v_theta) * sin(abs(v_theta)/2))
+ float abs_theta = acos(w_) * 2;
+ float scale = 1. / sin(abs_theta * .5);
+ gfx::Vector3dF v(x_, y_, z_);
+ v.Scale(scale);
+ return base::StringPrintf("[%f %f %f %f], v:", x_, y_, z_, w_) +
+ v.ToString() +
+ base::StringPrintf(", θ:%fπ", abs_theta / base::kPiFloat);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/quaternion.h b/ui/gfx/geometry/quaternion.h
new file mode 100644
index 0000000000..7f65b796e6
--- /dev/null
+++ b/ui/gfx/geometry/quaternion.h
@@ -0,0 +1,93 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_QUATERNION_
+#define UI_GFX_GEOMETRY_QUATERNION_
+
+#include <string>
+
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+class Vector3dF;
+
+class GFX_EXPORT Quaternion {
+ public:
+ constexpr Quaternion() = default;
+ constexpr Quaternion(double x, double y, double z, double w)
+ : x_(x), y_(y), z_(z), w_(w) {}
+ Quaternion(const Vector3dF& axis, double angle);
+
+ // Constructs a quaternion representing a rotation between |from| and |to|.
+ Quaternion(const Vector3dF& from, const Vector3dF& to);
+
+ constexpr double x() const { return x_; }
+ void set_x(double x) { x_ = x; }
+
+ constexpr double y() const { return y_; }
+ void set_y(double y) { y_ = y; }
+
+ constexpr double z() const { return z_; }
+ void set_z(double z) { z_ = z; }
+
+ constexpr double w() const { return w_; }
+ void set_w(double w) { w_ = w; }
+
+ Quaternion operator+(const Quaternion& q) const {
+ return {q.x_ + x_, q.y_ + y_, q.z_ + z_, q.w_ + w_};
+ }
+
+ Quaternion operator*(const Quaternion& q) const {
+ return {w_ * q.x_ + x_ * q.w_ + y_ * q.z_ - z_ * q.y_,
+ w_ * q.y_ - x_ * q.z_ + y_ * q.w_ + z_ * q.x_,
+ w_ * q.z_ + x_ * q.y_ - y_ * q.x_ + z_ * q.w_,
+ w_ * q.w_ - x_ * q.x_ - y_ * q.y_ - z_ * q.z_};
+ }
+
+ Quaternion inverse() const { return {-x_, -y_, -z_, w_}; }
+
+ // Blends with the given quaternion, |q|, via spherical linear interpolation.
+ // Values of |t| in the range [0, 1] will interpolate between |this| and |q|,
+ // and values outside that range will extrapolate beyond in either direction.
+ Quaternion Slerp(const Quaternion& q, double t) const;
+
+ // Blends with the given quaternion, |q|, via linear interpolation. This is
+ // rarely what you want. Use only if you know what you're doing.
+ // Values of |t| in the range [0, 1] will interpolate between |this| and |q|,
+ // and values outside that range will extrapolate beyond in either direction.
+ Quaternion Lerp(const Quaternion& q, double t) const;
+
+ double Length() const;
+
+ Quaternion Normalized() const;
+
+ std::string ToString() const;
+
+ private:
+ double x_ = 0.0;
+ double y_ = 0.0;
+ double z_ = 0.0;
+ double w_ = 1.0;
+};
+
+// |s| is an arbitrary, real constant.
+inline Quaternion operator*(const Quaternion& q, double s) {
+ return Quaternion(q.x() * s, q.y() * s, q.z() * s, q.w() * s);
+}
+
+// |s| is an arbitrary, real constant.
+inline Quaternion operator*(double s, const Quaternion& q) {
+ return Quaternion(q.x() * s, q.y() * s, q.z() * s, q.w() * s);
+}
+
+// |s| is an arbitrary, real constant.
+inline Quaternion operator/(const Quaternion& q, double s) {
+ double inv = 1.0 / s;
+ return q * inv;
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_QUATERNION_
diff --git a/ui/gfx/geometry/quaternion_unittest.cc b/ui/gfx/geometry/quaternion_unittest.cc
new file mode 100644
index 0000000000..5c8fa9b8ae
--- /dev/null
+++ b/ui/gfx/geometry/quaternion_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#define _USE_MATH_DEFINES // For VC++ to get M_PI. This has to be first.
+
+#include <cmath>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/quaternion.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+
+namespace gfx {
+
+namespace {
+
+const double kEpsilon = 1e-7;
+
+void CompareQuaternions(const Quaternion& a, const Quaternion& b) {
+ EXPECT_FLOAT_EQ(a.x(), b.x());
+ EXPECT_FLOAT_EQ(a.y(), b.y());
+ EXPECT_FLOAT_EQ(a.z(), b.z());
+ EXPECT_FLOAT_EQ(a.w(), b.w());
+}
+
+} // namespace
+
+TEST(QuatTest, DefaultConstruction) {
+ CompareQuaternions(Quaternion(0, 0, 0, 1), Quaternion());
+}
+
+TEST(QuatTest, AxisAngleCommon) {
+ double radians = 0.5;
+ Quaternion q(Vector3dF(1, 0, 0), radians);
+ CompareQuaternions(
+ Quaternion(std::sin(radians / 2), 0, 0, std::cos(radians / 2)), q);
+}
+
+TEST(QuatTest, VectorToVectorRotation) {
+ Quaternion q(Vector3dF(1.0f, 0.0f, 0.0f), Vector3dF(0.0f, 1.0f, 0.0f));
+ Quaternion r(Vector3dF(0.0f, 0.0f, 1.0f), M_PI_2);
+
+ EXPECT_FLOAT_EQ(r.x(), q.x());
+ EXPECT_FLOAT_EQ(r.y(), q.y());
+ EXPECT_FLOAT_EQ(r.z(), q.z());
+ EXPECT_FLOAT_EQ(r.w(), q.w());
+}
+
+TEST(QuatTest, AxisAngleWithZeroLengthAxis) {
+ Quaternion q(Vector3dF(0, 0, 0), 0.5);
+ // If the axis of zero length, we should assume the default values.
+ CompareQuaternions(q, Quaternion());
+}
+
+TEST(QuatTest, Addition) {
+ double values[] = {0, 1, 100};
+ for (size_t i = 0; i < arraysize(values); ++i) {
+ float t = values[i];
+ Quaternion a(t, 2 * t, 3 * t, 4 * t);
+ Quaternion b(5 * t, 4 * t, 3 * t, 2 * t);
+ Quaternion sum = a + b;
+ CompareQuaternions(Quaternion(t, t, t, t) * 6, sum);
+ }
+}
+
+TEST(QuatTest, Multiplication) {
+ struct {
+ Quaternion a;
+ Quaternion b;
+ Quaternion expected;
+ } cases[] = {
+ {Quaternion(1, 0, 0, 0), Quaternion(1, 0, 0, 0), Quaternion(0, 0, 0, -1)},
+ {Quaternion(0, 1, 0, 0), Quaternion(0, 1, 0, 0), Quaternion(0, 0, 0, -1)},
+ {Quaternion(0, 0, 1, 0), Quaternion(0, 0, 1, 0), Quaternion(0, 0, 0, -1)},
+ {Quaternion(0, 0, 0, 1), Quaternion(0, 0, 0, 1), Quaternion(0, 0, 0, 1)},
+ {Quaternion(1, 2, 3, 4), Quaternion(5, 6, 7, 8),
+ Quaternion(24, 48, 48, -6)},
+ {Quaternion(5, 6, 7, 8), Quaternion(1, 2, 3, 4),
+ Quaternion(32, 32, 56, -6)},
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ Quaternion product = cases[i].a * cases[i].b;
+ CompareQuaternions(cases[i].expected, product);
+ }
+}
+
+TEST(QuatTest, Scaling) {
+ double values[] = {0, 10, 100};
+ for (size_t i = 0; i < arraysize(values); ++i) {
+ double s = values[i];
+ Quaternion q(1, 2, 3, 4);
+ Quaternion expected(s, 2 * s, 3 * s, 4 * s);
+ CompareQuaternions(expected, q * s);
+ CompareQuaternions(expected, s * q);
+ if (s > 0)
+ CompareQuaternions(expected, q / (1 / s));
+ }
+}
+
+TEST(QuatTest, Normalization) {
+ Quaternion q(1, -1, 1, -1);
+ EXPECT_NEAR(q.Length(), 4, kEpsilon);
+
+ q = q.Normalized();
+
+ EXPECT_NEAR(q.Length(), 1, kEpsilon);
+ EXPECT_NEAR(q.x(), 0.5, kEpsilon);
+ EXPECT_NEAR(q.y(), -0.5, kEpsilon);
+ EXPECT_NEAR(q.z(), 0.5, kEpsilon);
+ EXPECT_NEAR(q.w(), -0.5, kEpsilon);
+}
+
+TEST(QuatTest, Lerp) {
+ for (size_t i = 1; i < 100; ++i) {
+ Quaternion a(0, 0, 0, 0);
+ Quaternion b(1, 2, 3, 4);
+ float t = static_cast<float>(i) / 100.0f;
+ Quaternion interpolated = a.Lerp(b, t);
+ double s = 1.0 / sqrt(30.0);
+ CompareQuaternions(Quaternion(1, 2, 3, 4) * s, interpolated);
+ }
+
+ Quaternion a(4, 3, 2, 1);
+ Quaternion b(1, 2, 3, 4);
+ CompareQuaternions(a.Normalized(), a.Lerp(b, 0));
+ CompareQuaternions(b.Normalized(), a.Lerp(b, 1));
+ CompareQuaternions(Quaternion(1, 1, 1, 1).Normalized(), a.Lerp(b, 0.5));
+}
+
+TEST(QuatTest, Slerp) {
+ Vector3dF axis(1, 1, 1);
+ double start_radians = -0.5;
+ double stop_radians = 0.5;
+ Quaternion start(axis, start_radians);
+ Quaternion stop(axis, stop_radians);
+
+ for (size_t i = 0; i < 100; ++i) {
+ float t = static_cast<float>(i) / 100.0f;
+ double radians = (1.0 - t) * start_radians + t * stop_radians;
+ Quaternion expected(axis, radians);
+ Quaternion interpolated = start.Slerp(stop, t);
+ EXPECT_NEAR(expected.x(), interpolated.x(), kEpsilon);
+ EXPECT_NEAR(expected.y(), interpolated.y(), kEpsilon);
+ EXPECT_NEAR(expected.z(), interpolated.z(), kEpsilon);
+ EXPECT_NEAR(expected.w(), interpolated.w(), kEpsilon);
+ }
+}
+
+TEST(QuatTest, SlerpOppositeAngles) {
+ Vector3dF axis(1, 1, 1);
+ double start_radians = -M_PI_2;
+ double stop_radians = M_PI_2;
+ Quaternion start(axis, start_radians);
+ Quaternion stop(axis, stop_radians);
+
+ // When quaternions are pointed in the fully opposite direction, this is
+ // ambiguous, so we rotate as per https://www.w3.org/TR/css-transforms-1/
+ Quaternion expected(axis, 0);
+
+ Quaternion interpolated = start.Slerp(stop, 0.5f);
+ EXPECT_NEAR(expected.x(), interpolated.x(), kEpsilon);
+ EXPECT_NEAR(expected.y(), interpolated.y(), kEpsilon);
+ EXPECT_NEAR(expected.z(), interpolated.z(), kEpsilon);
+ EXPECT_NEAR(expected.w(), interpolated.w(), kEpsilon);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/rect_conversions.cc b/ui/gfx/geometry/rect_conversions.cc
new file mode 100644
index 0000000000..3a5b2dcead
--- /dev/null
+++ b/ui/gfx/geometry/rect_conversions.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/rect_conversions.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/logging.h"
+#include "ui/gfx/geometry/safe_integer_conversions.h"
+
+namespace gfx {
+
+Rect ToEnclosingRect(const RectF& r) {
+ int left = ToFlooredInt(r.x());
+ int right = r.width() ? ToCeiledInt(r.right()) : left;
+ int top = ToFlooredInt(r.y());
+ int bottom = r.height() ? ToCeiledInt(r.bottom()) : top;
+
+ Rect result;
+ result.SetByBounds(left, top, right, bottom);
+ return result;
+}
+
+Rect ToEnclosedRect(const RectF& rect) {
+ Rect result;
+ result.SetByBounds(ToCeiledInt(rect.x()), ToCeiledInt(rect.y()),
+ ToFlooredInt(rect.right()), ToFlooredInt(rect.bottom()));
+ return result;
+}
+
+Rect ToNearestRect(const RectF& rect) {
+ float float_min_x = rect.x();
+ float float_min_y = rect.y();
+ float float_max_x = rect.right();
+ float float_max_y = rect.bottom();
+
+ int min_x = ToRoundedInt(float_min_x);
+ int min_y = ToRoundedInt(float_min_y);
+ int max_x = ToRoundedInt(float_max_x);
+ int max_y = ToRoundedInt(float_max_y);
+
+ // If these DCHECKs fail, you're using the wrong method, consider using
+ // ToEnclosingRect or ToEnclosedRect instead.
+ DCHECK(std::abs(min_x - float_min_x) < 0.01f);
+ DCHECK(std::abs(min_y - float_min_y) < 0.01f);
+ DCHECK(std::abs(max_x - float_max_x) < 0.01f);
+ DCHECK(std::abs(max_y - float_max_y) < 0.01f);
+
+ Rect result;
+ result.SetByBounds(min_x, min_y, max_x, max_y);
+
+ return result;
+}
+
+bool IsNearestRectWithinDistance(const gfx::RectF& rect, float distance) {
+ float float_min_x = rect.x();
+ float float_min_y = rect.y();
+ float float_max_x = rect.right();
+ float float_max_y = rect.bottom();
+
+ int min_x = ToRoundedInt(float_min_x);
+ int min_y = ToRoundedInt(float_min_y);
+ int max_x = ToRoundedInt(float_max_x);
+ int max_y = ToRoundedInt(float_max_y);
+
+ return
+ (std::abs(min_x - float_min_x) < distance) &&
+ (std::abs(min_y - float_min_y) < distance) &&
+ (std::abs(max_x - float_max_x) < distance) &&
+ (std::abs(max_y - float_max_y) < distance);
+}
+
+Rect ToFlooredRectDeprecated(const RectF& rect) {
+ return Rect(ToFlooredInt(rect.x()),
+ ToFlooredInt(rect.y()),
+ ToFlooredInt(rect.width()),
+ ToFlooredInt(rect.height()));
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/rect_conversions.h b/ui/gfx/geometry/rect_conversions.h
new file mode 100644
index 0000000000..617074abee
--- /dev/null
+++ b/ui/gfx/geometry/rect_conversions.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_RECT_CONVERSIONS_H_
+#define UI_GFX_GEOMETRY_RECT_CONVERSIONS_H_
+
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_f.h"
+
+namespace gfx {
+
+// Returns the smallest Rect that encloses the given RectF.
+GFX_EXPORT Rect ToEnclosingRect(const RectF& rect);
+
+// Returns the largest Rect that is enclosed by the given RectF.
+GFX_EXPORT Rect ToEnclosedRect(const RectF& rect);
+
+// Returns the Rect after snapping the corners of the RectF to an integer grid.
+// This should only be used when the RectF you provide is expected to be an
+// integer rect with floating point error. If it is an arbitrary RectF, then
+// you should use a different method.
+GFX_EXPORT Rect ToNearestRect(const RectF& rect);
+
+// Returns true if the Rect produced after snapping the corners of the RectF
+// to an integer grid is withing |distance|.
+GFX_EXPORT bool IsNearestRectWithinDistance(
+ const gfx::RectF& rect, float distance);
+
+// Returns a Rect obtained by flooring the values of the given RectF.
+// Please prefer the previous two functions in new code.
+GFX_EXPORT Rect ToFlooredRectDeprecated(const RectF& rect);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_RECT_CONVERSIONS_H_
diff --git a/ui/gfx/geometry/rect_unittest.cc b/ui/gfx/geometry/rect_unittest.cc
new file mode 100644
index 0000000000..aaa533bfcd
--- /dev/null
+++ b/ui/gfx/geometry/rect_unittest.cc
@@ -0,0 +1,1141 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <limits>
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gfx/test/gfx_util.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace gfx {
+
+TEST(RectTest, Contains) {
+ static const struct ContainsCase {
+ int rect_x;
+ int rect_y;
+ int rect_width;
+ int rect_height;
+ int point_x;
+ int point_y;
+ bool contained;
+ } contains_cases[] = {
+ {0, 0, 10, 10, 0, 0, true},
+ {0, 0, 10, 10, 5, 5, true},
+ {0, 0, 10, 10, 9, 9, true},
+ {0, 0, 10, 10, 5, 10, false},
+ {0, 0, 10, 10, 10, 5, false},
+ {0, 0, 10, 10, -1, -1, false},
+ {0, 0, 10, 10, 50, 50, false},
+ #if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+ {0, 0, -10, -10, 0, 0, false},
+ #endif
+ };
+ for (size_t i = 0; i < arraysize(contains_cases); ++i) {
+ const ContainsCase& value = contains_cases[i];
+ Rect rect(value.rect_x, value.rect_y, value.rect_width, value.rect_height);
+ EXPECT_EQ(value.contained, rect.Contains(value.point_x, value.point_y));
+ }
+}
+
+TEST(RectTest, Intersects) {
+ static const struct {
+ int x1; // rect 1
+ int y1;
+ int w1;
+ int h1;
+ int x2; // rect 2
+ int y2;
+ int w2;
+ int h2;
+ bool intersects;
+ } tests[] = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, false },
+ { 0, 0, 0, 0, -10, -10, 20, 20, false },
+ { -10, 0, 0, 20, 0, -10, 20, 0, false },
+ { 0, 0, 10, 10, 0, 0, 10, 10, true },
+ { 0, 0, 10, 10, 10, 10, 10, 10, false },
+ { 10, 10, 10, 10, 0, 0, 10, 10, false },
+ { 10, 10, 10, 10, 5, 5, 10, 10, true },
+ { 10, 10, 10, 10, 15, 15, 10, 10, true },
+ { 10, 10, 10, 10, 20, 15, 10, 10, false },
+ { 10, 10, 10, 10, 21, 15, 10, 10, false }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ EXPECT_EQ(tests[i].intersects, r1.Intersects(r2));
+ EXPECT_EQ(tests[i].intersects, r2.Intersects(r1));
+ }
+}
+
+TEST(RectTest, Intersect) {
+ static const struct {
+ int x1; // rect 1
+ int y1;
+ int w1;
+ int h1;
+ int x2; // rect 2
+ int y2;
+ int w2;
+ int h2;
+ int x3; // rect 3: the union of rects 1 and 2
+ int y3;
+ int w3;
+ int h3;
+ } tests[] = {
+ { 0, 0, 0, 0, // zeros
+ 0, 0, 0, 0,
+ 0, 0, 0, 0 },
+ { 0, 0, 4, 4, // equal
+ 0, 0, 4, 4,
+ 0, 0, 4, 4 },
+ { 0, 0, 4, 4, // neighboring
+ 4, 4, 4, 4,
+ 0, 0, 0, 0 },
+ { 0, 0, 4, 4, // overlapping corners
+ 2, 2, 4, 4,
+ 2, 2, 2, 2 },
+ { 0, 0, 4, 4, // T junction
+ 3, 1, 4, 2,
+ 3, 1, 1, 2 },
+ { 3, 0, 2, 2, // gap
+ 0, 0, 2, 2,
+ 0, 0, 0, 0 }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ Rect ir = IntersectRects(r1, r2);
+ EXPECT_EQ(r3.x(), ir.x());
+ EXPECT_EQ(r3.y(), ir.y());
+ EXPECT_EQ(r3.width(), ir.width());
+ EXPECT_EQ(r3.height(), ir.height());
+ }
+}
+
+TEST(RectTest, Union) {
+ static const struct Test {
+ int x1; // rect 1
+ int y1;
+ int w1;
+ int h1;
+ int x2; // rect 2
+ int y2;
+ int w2;
+ int h2;
+ int x3; // rect 3: the union of rects 1 and 2
+ int y3;
+ int w3;
+ int h3;
+ } tests[] = {
+ { 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0 },
+ { 0, 0, 4, 4,
+ 0, 0, 4, 4,
+ 0, 0, 4, 4 },
+ { 0, 0, 4, 4,
+ 4, 4, 4, 4,
+ 0, 0, 8, 8 },
+ { 0, 0, 4, 4,
+ 0, 5, 4, 4,
+ 0, 0, 4, 9 },
+ { 0, 0, 2, 2,
+ 3, 3, 2, 2,
+ 0, 0, 5, 5 },
+ { 3, 3, 2, 2, // reverse r1 and r2 from previous test
+ 0, 0, 2, 2,
+ 0, 0, 5, 5 },
+ { 0, 0, 0, 0, // union with empty rect
+ 2, 2, 2, 2,
+ 2, 2, 2, 2 }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ Rect u = UnionRects(r1, r2);
+ EXPECT_EQ(r3.x(), u.x());
+ EXPECT_EQ(r3.y(), u.y());
+ EXPECT_EQ(r3.width(), u.width());
+ EXPECT_EQ(r3.height(), u.height());
+ }
+}
+
+TEST(RectTest, Equals) {
+ ASSERT_TRUE(Rect(0, 0, 0, 0) == Rect(0, 0, 0, 0));
+ ASSERT_TRUE(Rect(1, 2, 3, 4) == Rect(1, 2, 3, 4));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 0, 0, 1));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 0, 1, 0));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 1, 0, 0));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(1, 0, 0, 0));
+}
+
+TEST(RectTest, AdjustToFit) {
+ static const struct Test {
+ int x1; // source
+ int y1;
+ int w1;
+ int h1;
+ int x2; // target
+ int y2;
+ int w2;
+ int h2;
+ int x3; // rect 3: results of invoking AdjustToFit
+ int y3;
+ int w3;
+ int h3;
+ } tests[] = {
+ { 0, 0, 2, 2,
+ 0, 0, 2, 2,
+ 0, 0, 2, 2 },
+ { 2, 2, 3, 3,
+ 0, 0, 4, 4,
+ 1, 1, 3, 3 },
+ { -1, -1, 5, 5,
+ 0, 0, 4, 4,
+ 0, 0, 4, 4 },
+ { 2, 2, 4, 4,
+ 0, 0, 3, 3,
+ 0, 0, 3, 3 },
+ { 2, 2, 1, 1,
+ 0, 0, 3, 3,
+ 2, 2, 1, 1 }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ Rect u = r1;
+ u.AdjustToFit(r2);
+ EXPECT_EQ(r3.x(), u.x());
+ EXPECT_EQ(r3.y(), u.y());
+ EXPECT_EQ(r3.width(), u.width());
+ EXPECT_EQ(r3.height(), u.height());
+ }
+}
+
+TEST(RectTest, Subtract) {
+ Rect result;
+
+ // Matching
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(10, 10, 20, 20));
+ EXPECT_EQ(Rect(0, 0, 0, 0), result);
+
+ // Contains
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 5, 30, 30));
+ EXPECT_EQ(Rect(0, 0, 0, 0), result);
+
+ // No intersection
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(30, 30, 30, 30));
+ EXPECT_EQ(Rect(10, 10, 20, 20), result);
+
+ // Not a complete intersection in either direction
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(15, 15, 20, 20));
+ EXPECT_EQ(Rect(10, 10, 20, 20), result);
+
+ // Complete intersection in the x-direction, top edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(10, 15, 20, 20));
+ EXPECT_EQ(Rect(10, 10, 20, 5), result);
+
+ // Complete intersection in the x-direction, top edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 15, 30, 20));
+ EXPECT_EQ(Rect(10, 10, 20, 5), result);
+
+ // Complete intersection in the x-direction, bottom edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 5, 30, 20));
+ EXPECT_EQ(Rect(10, 25, 20, 5), result);
+
+ // Complete intersection in the x-direction, none of the edges is fully
+ // covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 15, 30, 1));
+ EXPECT_EQ(Rect(10, 10, 20, 20), result);
+
+ // Complete intersection in the y-direction, left edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(10, 10, 10, 30));
+ EXPECT_EQ(Rect(20, 10, 10, 20), result);
+
+ // Complete intersection in the y-direction, left edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 5, 20, 30));
+ EXPECT_EQ(Rect(25, 10, 5, 20), result);
+
+ // Complete intersection in the y-direction, right edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(20, 5, 20, 30));
+ EXPECT_EQ(Rect(10, 10, 10, 20), result);
+
+ // Complete intersection in the y-direction, none of the edges is fully
+ // covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(15, 5, 1, 30));
+ EXPECT_EQ(Rect(10, 10, 20, 20), result);
+}
+
+TEST(RectTest, IsEmpty) {
+ EXPECT_TRUE(Rect(0, 0, 0, 0).IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 0, 0).size().IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 10, 0).IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 10, 0).size().IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 0, 10).IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 0, 10).size().IsEmpty());
+ EXPECT_FALSE(Rect(0, 0, 10, 10).IsEmpty());
+ EXPECT_FALSE(Rect(0, 0, 10, 10).size().IsEmpty());
+}
+
+TEST(RectTest, SplitVertically) {
+ Rect left_half, right_half;
+
+ // Splitting when origin is (0, 0).
+ Rect(0, 0, 20, 20).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(0, 0, 10, 20));
+ EXPECT_TRUE(right_half == Rect(10, 0, 10, 20));
+
+ // Splitting when origin is arbitrary.
+ Rect(10, 10, 20, 10).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(10, 10, 10, 10));
+ EXPECT_TRUE(right_half == Rect(20, 10, 10, 10));
+
+ // Splitting a rectangle of zero width.
+ Rect(10, 10, 0, 10).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(10, 10, 0, 10));
+ EXPECT_TRUE(right_half == Rect(10, 10, 0, 10));
+
+ // Splitting a rectangle of odd width.
+ Rect(10, 10, 5, 10).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(10, 10, 2, 10));
+ EXPECT_TRUE(right_half == Rect(12, 10, 3, 10));
+}
+
+TEST(RectTest, CenterPoint) {
+ Point center;
+
+ // When origin is (0, 0).
+ center = Rect(0, 0, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(10, 10));
+
+ // When origin is even.
+ center = Rect(10, 10, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(20, 20));
+
+ // When origin is odd.
+ center = Rect(11, 11, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(21, 21));
+
+ // When 0 width or height.
+ center = Rect(10, 10, 0, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(10, 20));
+ center = Rect(10, 10, 20, 0).CenterPoint();
+ EXPECT_TRUE(center == Point(20, 10));
+
+ // When an odd size.
+ center = Rect(10, 10, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == Point(20, 20));
+
+ // When an odd size and position.
+ center = Rect(11, 11, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == Point(21, 21));
+}
+
+TEST(RectTest, CenterPointF) {
+ PointF center;
+
+ // When origin is (0, 0).
+ center = RectF(0, 0, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(10, 10));
+
+ // When origin is even.
+ center = RectF(10, 10, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(20, 20));
+
+ // When origin is odd.
+ center = RectF(11, 11, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(21, 21));
+
+ // When 0 width or height.
+ center = RectF(10, 10, 0, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(10, 20));
+ center = RectF(10, 10, 20, 0).CenterPoint();
+ EXPECT_TRUE(center == PointF(20, 10));
+
+ // When an odd size.
+ center = RectF(10, 10, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == PointF(20.5f, 20.5f));
+
+ // When an odd size and position.
+ center = RectF(11, 11, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == PointF(21.5f, 21.5f));
+}
+
+TEST(RectTest, SharesEdgeWith) {
+ Rect r(2, 3, 4, 5);
+
+ // Must be non-overlapping
+ EXPECT_FALSE(r.SharesEdgeWith(r));
+
+ Rect just_above(2, 1, 4, 2);
+ Rect just_below(2, 8, 4, 2);
+ Rect just_left(0, 3, 2, 5);
+ Rect just_right(6, 3, 2, 5);
+
+ EXPECT_TRUE(r.SharesEdgeWith(just_above));
+ EXPECT_TRUE(r.SharesEdgeWith(just_below));
+ EXPECT_TRUE(r.SharesEdgeWith(just_left));
+ EXPECT_TRUE(r.SharesEdgeWith(just_right));
+
+ // Wrong placement
+ Rect same_height_no_edge(0, 0, 1, 5);
+ Rect same_width_no_edge(0, 0, 4, 1);
+
+ EXPECT_FALSE(r.SharesEdgeWith(same_height_no_edge));
+ EXPECT_FALSE(r.SharesEdgeWith(same_width_no_edge));
+
+ Rect just_above_no_edge(2, 1, 5, 2); // too wide
+ Rect just_below_no_edge(2, 8, 3, 2); // too narrow
+ Rect just_left_no_edge(0, 3, 2, 6); // too tall
+ Rect just_right_no_edge(6, 3, 2, 4); // too short
+
+ EXPECT_FALSE(r.SharesEdgeWith(just_above_no_edge));
+ EXPECT_FALSE(r.SharesEdgeWith(just_below_no_edge));
+ EXPECT_FALSE(r.SharesEdgeWith(just_left_no_edge));
+ EXPECT_FALSE(r.SharesEdgeWith(just_right_no_edge));
+}
+
+// Similar to EXPECT_FLOAT_EQ, but lets NaN equal NaN
+#define EXPECT_FLOAT_AND_NAN_EQ(a, b) \
+ { if (a == a || b == b) { EXPECT_FLOAT_EQ(a, b); } }
+
+TEST(RectTest, ScaleRect) {
+ static const struct Test {
+ int x1; // source
+ int y1;
+ int w1;
+ int h1;
+ float scale;
+ float x2; // target
+ float y2;
+ float w2;
+ float h2;
+ } tests[] = {
+ { 3, 3, 3, 3,
+ 1.5f,
+ 4.5f, 4.5f, 4.5f, 4.5f },
+ { 3, 3, 3, 3,
+ 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f },
+ { 3, 3, 3, 3,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN() },
+ { 3, 3, 3, 3,
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max() }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ RectF r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ RectF r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+
+ RectF scaled = ScaleRect(r1, tests[i].scale);
+ EXPECT_FLOAT_AND_NAN_EQ(r2.x(), scaled.x());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.y(), scaled.y());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.width(), scaled.width());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.height(), scaled.height());
+ }
+}
+
+TEST(RectTest, ToEnclosedRect) {
+ static const int max_int = std::numeric_limits<int>::max();
+ static const int min_int = std::numeric_limits<int>::min();
+ static const float max_float = std::numeric_limits<float>::max();
+ static const float max_int_f = static_cast<float>(max_int);
+ static const float min_int_f = static_cast<float>(min_int);
+
+ static const struct Test {
+ struct {
+ float x;
+ float y;
+ float width;
+ float height;
+ } in;
+ struct {
+ int x;
+ int y;
+ int width;
+ int height;
+ } expected;
+ } tests[] = {
+ {{0.0f, 0.0f, 0.0f, 0.0f}, {0, 0, 0, 0}},
+ {{-1.5f, -1.5f, 3.0f, 3.0f}, {-1, -1, 2, 2}},
+ {{-1.5f, -1.5f, 3.5f, 3.5f}, {-1, -1, 3, 3}},
+ {{max_float, max_float, 2.0f, 2.0f}, {max_int, max_int, 0, 0}},
+ {{0.0f, 0.0f, max_float, max_float}, {0, 0, max_int, max_int}},
+ {{20000.5f, 20000.5f, 0.5f, 0.5f}, {20001, 20001, 0, 0}},
+ {{max_int_f, max_int_f, max_int_f, max_int_f}, {max_int, max_int, 0, 0}}};
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ RectF source(tests[i].in.x, tests[i].in.y, tests[i].in.width,
+ tests[i].in.height);
+ Rect enclosed = ToEnclosedRect(source);
+
+ EXPECT_EQ(tests[i].expected.x, enclosed.x());
+ EXPECT_EQ(tests[i].expected.y, enclosed.y());
+ EXPECT_EQ(tests[i].expected.width, enclosed.width());
+ EXPECT_EQ(tests[i].expected.height, enclosed.height());
+ }
+
+ {
+ RectF source(min_int_f, min_int_f, max_int_f * 3.f, max_int_f * 3.f);
+ Rect enclosed = ToEnclosedRect(source);
+
+ // That rect can't be represented, but it should be big.
+ EXPECT_EQ(max_int, enclosed.width());
+ EXPECT_EQ(max_int, enclosed.height());
+ // It should include some axis near the global origin.
+ EXPECT_GT(1, enclosed.x());
+ EXPECT_GT(1, enclosed.y());
+ // And it should not cause computation issues for itself.
+ EXPECT_LT(0, enclosed.right());
+ EXPECT_LT(0, enclosed.bottom());
+ }
+}
+
+TEST(RectTest, ToEnclosingRect) {
+ static const int max_int = std::numeric_limits<int>::max();
+ static const int min_int = std::numeric_limits<int>::min();
+ static const float max_float = std::numeric_limits<float>::max();
+ static const float epsilon_float = std::numeric_limits<float>::epsilon();
+ static const float max_int_f = static_cast<float>(max_int);
+ static const float min_int_f = static_cast<float>(min_int);
+ static const struct Test {
+ struct {
+ float x;
+ float y;
+ float width;
+ float height;
+ } in;
+ struct {
+ int x;
+ int y;
+ int width;
+ int height;
+ } expected;
+ } tests[] = {
+ {{0.0f, 0.0f, 0.0f, 0.0f}, {0, 0, 0, 0}},
+ {{5.5f, 5.5f, 0.0f, 0.0f}, {5, 5, 0, 0}},
+ {{3.5f, 2.5f, epsilon_float, -0.0f}, {3, 2, 0, 0}},
+ {{3.5f, 2.5f, 0.f, 0.001f}, {3, 2, 0, 1}},
+ {{-1.5f, -1.5f, 3.0f, 3.0f}, {-2, -2, 4, 4}},
+ {{-1.5f, -1.5f, 3.5f, 3.5f}, {-2, -2, 4, 4}},
+ {{max_float, max_float, 2.0f, 2.0f}, {max_int, max_int, 0, 0}},
+ {{0.0f, 0.0f, max_float, max_float}, {0, 0, max_int, max_int}},
+ {{20000.5f, 20000.5f, 0.5f, 0.5f}, {20000, 20000, 1, 1}},
+ {{max_int_f, max_int_f, max_int_f, max_int_f}, {max_int, max_int, 0, 0}},
+ {{-0.5f, -0.5f, 22777712.f, 1.f}, {-1, -1, 22777713, 2}}};
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ RectF source(tests[i].in.x, tests[i].in.y, tests[i].in.width,
+ tests[i].in.height);
+
+ Rect enclosing = ToEnclosingRect(source);
+ EXPECT_EQ(tests[i].expected.x, enclosing.x());
+ EXPECT_EQ(tests[i].expected.y, enclosing.y());
+ EXPECT_EQ(tests[i].expected.width, enclosing.width());
+ EXPECT_EQ(tests[i].expected.height, enclosing.height());
+ }
+
+ {
+ RectF source(min_int_f, min_int_f, max_int_f * 3.f, max_int_f * 3.f);
+ Rect enclosing = ToEnclosingRect(source);
+
+ // That rect can't be represented, but it should be big.
+ EXPECT_EQ(max_int, enclosing.width());
+ EXPECT_EQ(max_int, enclosing.height());
+ // It should include some axis near the global origin.
+ EXPECT_GT(1, enclosing.x());
+ EXPECT_GT(1, enclosing.y());
+ // And it should cause computation issues for itself.
+ EXPECT_LT(0, enclosing.right());
+ EXPECT_LT(0, enclosing.bottom());
+ }
+}
+
+TEST(RectTest, ToNearestRect) {
+ Rect rect;
+ EXPECT_EQ(rect, ToNearestRect(RectF(rect)));
+
+ rect = Rect(-1, -1, 3, 3);
+ EXPECT_EQ(rect, ToNearestRect(RectF(rect)));
+
+ RectF rectf(-1.00001f, -0.999999f, 3.0000001f, 2.999999f);
+ EXPECT_EQ(rect, ToNearestRect(rectf));
+}
+
+TEST(RectTest, ToFlooredRect) {
+ static const struct Test {
+ float x1; // source
+ float y1;
+ float w1;
+ float h1;
+ int x2; // target
+ int y2;
+ int w2;
+ int h2;
+ } tests [] = {
+ { 0.0f, 0.0f, 0.0f, 0.0f,
+ 0, 0, 0, 0 },
+ { -1.5f, -1.5f, 3.0f, 3.0f,
+ -2, -2, 3, 3 },
+ { -1.5f, -1.5f, 3.5f, 3.5f,
+ -2, -2, 3, 3 },
+ { 20000.5f, 20000.5f, 0.5f, 0.5f,
+ 20000, 20000, 0, 0 },
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ RectF r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+
+ Rect floored = ToFlooredRectDeprecated(r1);
+ EXPECT_FLOAT_EQ(r2.x(), floored.x());
+ EXPECT_FLOAT_EQ(r2.y(), floored.y());
+ EXPECT_FLOAT_EQ(r2.width(), floored.width());
+ EXPECT_FLOAT_EQ(r2.height(), floored.height());
+ }
+}
+
+TEST(RectTest, ScaleToEnclosedRect) {
+ static const struct Test {
+ Rect input_rect;
+ float input_scale;
+ Rect expected_rect;
+ } tests[] = {
+ {
+ Rect(),
+ 5.f,
+ Rect(),
+ }, {
+ Rect(1, 1, 1, 1),
+ 5.f,
+ Rect(5, 5, 5, 5),
+ }, {
+ Rect(-1, -1, 0, 0),
+ 5.f,
+ Rect(-5, -5, 0, 0),
+ }, {
+ Rect(1, -1, 0, 1),
+ 5.f,
+ Rect(5, -5, 0, 5),
+ }, {
+ Rect(-1, 1, 1, 0),
+ 5.f,
+ Rect(-5, 5, 5, 0),
+ }, {
+ Rect(1, 2, 3, 4),
+ 1.5f,
+ Rect(2, 3, 4, 6),
+ }, {
+ Rect(-1, -2, 0, 0),
+ 1.5f,
+ Rect(-1, -3, 0, 0),
+ }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect result = ScaleToEnclosedRect(tests[i].input_rect,
+ tests[i].input_scale);
+ EXPECT_EQ(tests[i].expected_rect, result);
+ }
+}
+
+TEST(RectTest, ScaleToEnclosingRect) {
+ static const struct Test {
+ Rect input_rect;
+ float input_scale;
+ Rect expected_rect;
+ } tests[] = {
+ {
+ Rect(),
+ 5.f,
+ Rect(),
+ }, {
+ Rect(1, 1, 1, 1),
+ 5.f,
+ Rect(5, 5, 5, 5),
+ }, {
+ Rect(-1, -1, 0, 0),
+ 5.f,
+ Rect(-5, -5, 0, 0),
+ }, {
+ Rect(1, -1, 0, 1),
+ 5.f,
+ Rect(5, -5, 0, 5),
+ }, {
+ Rect(-1, 1, 1, 0),
+ 5.f,
+ Rect(-5, 5, 5, 0),
+ }, {
+ Rect(1, 2, 3, 4),
+ 1.5f,
+ Rect(1, 3, 5, 6),
+ }, {
+ Rect(-1, -2, 0, 0),
+ 1.5f,
+ Rect(-2, -3, 0, 0),
+ }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ Rect result =
+ ScaleToEnclosingRect(tests[i].input_rect, tests[i].input_scale);
+ EXPECT_EQ(tests[i].expected_rect, result);
+ Rect result_safe =
+ ScaleToEnclosingRectSafe(tests[i].input_rect, tests[i].input_scale);
+ EXPECT_EQ(tests[i].expected_rect, result_safe);
+ }
+}
+
+#if defined(OS_WIN)
+TEST(RectTest, ConstructAndAssign) {
+ const RECT rect_1 = { 0, 0, 10, 10 };
+ const RECT rect_2 = { 0, 0, -10, -10 };
+ Rect test1(rect_1);
+ Rect test2(rect_2);
+}
+#endif
+
+TEST(RectTest, ToRectF) {
+ // Check that explicit conversion from integer to float compiles.
+ Rect a(10, 20, 30, 40);
+ RectF b(10, 20, 30, 40);
+
+ RectF c = RectF(a);
+ EXPECT_EQ(b, c);
+}
+
+TEST(RectTest, BoundingRect) {
+ struct {
+ Point a;
+ Point b;
+ Rect expected;
+ } int_tests[] = {
+ // If point B dominates A, then A should be the origin.
+ { Point(4, 6), Point(4, 6), Rect(4, 6, 0, 0) },
+ { Point(4, 6), Point(8, 6), Rect(4, 6, 4, 0) },
+ { Point(4, 6), Point(4, 9), Rect(4, 6, 0, 3) },
+ { Point(4, 6), Point(8, 9), Rect(4, 6, 4, 3) },
+ // If point A dominates B, then B should be the origin.
+ { Point(4, 6), Point(4, 6), Rect(4, 6, 0, 0) },
+ { Point(8, 6), Point(4, 6), Rect(4, 6, 4, 0) },
+ { Point(4, 9), Point(4, 6), Rect(4, 6, 0, 3) },
+ { Point(8, 9), Point(4, 6), Rect(4, 6, 4, 3) },
+ // If neither point dominates, then the origin is a combination of the two.
+ { Point(4, 6), Point(6, 4), Rect(4, 4, 2, 2) },
+ { Point(-4, -6), Point(-6, -4), Rect(-6, -6, 2, 2) },
+ { Point(-4, 6), Point(6, -4), Rect(-4, -4, 10, 10) },
+ };
+
+ for (size_t i = 0; i < arraysize(int_tests); ++i) {
+ Rect actual = BoundingRect(int_tests[i].a, int_tests[i].b);
+ EXPECT_EQ(int_tests[i].expected, actual);
+ }
+
+ struct {
+ PointF a;
+ PointF b;
+ RectF expected;
+ } float_tests[] = {
+ // If point B dominates A, then A should be the origin.
+ { PointF(4.2f, 6.8f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 0, 0) },
+ { PointF(4.2f, 6.8f), PointF(8.5f, 6.8f),
+ RectF(4.2f, 6.8f, 4.3f, 0) },
+ { PointF(4.2f, 6.8f), PointF(4.2f, 9.3f),
+ RectF(4.2f, 6.8f, 0, 2.5f) },
+ { PointF(4.2f, 6.8f), PointF(8.5f, 9.3f),
+ RectF(4.2f, 6.8f, 4.3f, 2.5f) },
+ // If point A dominates B, then B should be the origin.
+ { PointF(4.2f, 6.8f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 0, 0) },
+ { PointF(8.5f, 6.8f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 4.3f, 0) },
+ { PointF(4.2f, 9.3f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 0, 2.5f) },
+ { PointF(8.5f, 9.3f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 4.3f, 2.5f) },
+ // If neither point dominates, then the origin is a combination of the two.
+ { PointF(4.2f, 6.8f), PointF(6.8f, 4.2f),
+ RectF(4.2f, 4.2f, 2.6f, 2.6f) },
+ { PointF(-4.2f, -6.8f), PointF(-6.8f, -4.2f),
+ RectF(-6.8f, -6.8f, 2.6f, 2.6f) },
+ { PointF(-4.2f, 6.8f), PointF(6.8f, -4.2f),
+ RectF(-4.2f, -4.2f, 11.0f, 11.0f) }
+ };
+
+ for (size_t i = 0; i < arraysize(float_tests); ++i) {
+ RectF actual = BoundingRect(float_tests[i].a, float_tests[i].b);
+ EXPECT_RECTF_EQ(float_tests[i].expected, actual);
+ }
+}
+
+TEST(RectTest, IsExpressibleAsRect) {
+ EXPECT_TRUE(RectF().IsExpressibleAsRect());
+
+ float min = std::numeric_limits<int>::min();
+ float max = std::numeric_limits<int>::max();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ EXPECT_TRUE(RectF(
+ min + 200, min + 200, max - 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min - 200, min + 200, max + 200, max + 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min + 200 , min - 200, max + 200, max + 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min + 200, min + 200, max + 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min + 200, min + 200, max - 200, max + 200).IsExpressibleAsRect());
+
+ EXPECT_TRUE(RectF(0, 0, max - 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(200, 0, max + 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 200, max - 200, max + 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, max + 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, max - 200, max + 200).IsExpressibleAsRect());
+
+ EXPECT_FALSE(RectF(infinity, 0, 1, 1).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, infinity, 1, 1).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, infinity, 1).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, 1, infinity).IsExpressibleAsRect());
+}
+
+TEST(RectTest, Offset) {
+ Rect i(1, 2, 3, 4);
+
+ EXPECT_EQ(Rect(2, 1, 3, 4), (i + Vector2d(1, -1)));
+ EXPECT_EQ(Rect(2, 1, 3, 4), (Vector2d(1, -1) + i));
+ i += Vector2d(1, -1);
+ EXPECT_EQ(Rect(2, 1, 3, 4), i);
+ EXPECT_EQ(Rect(1, 2, 3, 4), (i - Vector2d(1, -1)));
+ i -= Vector2d(1, -1);
+ EXPECT_EQ(Rect(1, 2, 3, 4), i);
+
+ RectF f(1.1f, 2.2f, 3.3f, 4.4f);
+ EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f), (f + Vector2dF(1.1f, -1.1f)));
+ EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f), (Vector2dF(1.1f, -1.1f) + f));
+ f += Vector2dF(1.1f, -1.1f);
+ EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f), f);
+ EXPECT_EQ(RectF(1.1f, 2.2f, 3.3f, 4.4f), (f - Vector2dF(1.1f, -1.1f)));
+ f -= Vector2dF(1.1f, -1.1f);
+ EXPECT_EQ(RectF(1.1f, 2.2f, 3.3f, 4.4f), f);
+}
+
+TEST(RectTest, Corners) {
+ Rect i(1, 2, 3, 4);
+ RectF f(1.1f, 2.1f, 3.1f, 4.1f);
+
+ EXPECT_EQ(Point(1, 2), i.origin());
+ EXPECT_EQ(Point(4, 2), i.top_right());
+ EXPECT_EQ(Point(1, 6), i.bottom_left());
+ EXPECT_EQ(Point(4, 6), i.bottom_right());
+
+ EXPECT_EQ(PointF(1.1f, 2.1f), f.origin());
+ EXPECT_EQ(PointF(4.2f, 2.1f), f.top_right());
+ EXPECT_EQ(PointF(1.1f, 6.2f), f.bottom_left());
+ EXPECT_EQ(PointF(4.2f, 6.2f), f.bottom_right());
+}
+
+TEST(RectTest, ManhattanDistanceToPoint) {
+ Rect i(1, 2, 3, 4);
+ EXPECT_EQ(0, i.ManhattanDistanceToPoint(Point(1, 2)));
+ EXPECT_EQ(0, i.ManhattanDistanceToPoint(Point(4, 6)));
+ EXPECT_EQ(0, i.ManhattanDistanceToPoint(Point(2, 4)));
+ EXPECT_EQ(3, i.ManhattanDistanceToPoint(Point(0, 0)));
+ EXPECT_EQ(2, i.ManhattanDistanceToPoint(Point(2, 0)));
+ EXPECT_EQ(3, i.ManhattanDistanceToPoint(Point(5, 0)));
+ EXPECT_EQ(1, i.ManhattanDistanceToPoint(Point(5, 4)));
+ EXPECT_EQ(3, i.ManhattanDistanceToPoint(Point(5, 8)));
+ EXPECT_EQ(2, i.ManhattanDistanceToPoint(Point(3, 8)));
+ EXPECT_EQ(2, i.ManhattanDistanceToPoint(Point(0, 7)));
+ EXPECT_EQ(1, i.ManhattanDistanceToPoint(Point(0, 3)));
+
+ RectF f(1.1f, 2.1f, 3.1f, 4.1f);
+ EXPECT_FLOAT_EQ(0.f, f.ManhattanDistanceToPoint(PointF(1.1f, 2.1f)));
+ EXPECT_FLOAT_EQ(0.f, f.ManhattanDistanceToPoint(PointF(4.2f, 6.f)));
+ EXPECT_FLOAT_EQ(0.f, f.ManhattanDistanceToPoint(PointF(2.f, 4.f)));
+ EXPECT_FLOAT_EQ(3.2f, f.ManhattanDistanceToPoint(PointF(0.f, 0.f)));
+ EXPECT_FLOAT_EQ(2.1f, f.ManhattanDistanceToPoint(PointF(2.f, 0.f)));
+ EXPECT_FLOAT_EQ(2.9f, f.ManhattanDistanceToPoint(PointF(5.f, 0.f)));
+ EXPECT_FLOAT_EQ(.8f, f.ManhattanDistanceToPoint(PointF(5.f, 4.f)));
+ EXPECT_FLOAT_EQ(2.6f, f.ManhattanDistanceToPoint(PointF(5.f, 8.f)));
+ EXPECT_FLOAT_EQ(1.8f, f.ManhattanDistanceToPoint(PointF(3.f, 8.f)));
+ EXPECT_FLOAT_EQ(1.9f, f.ManhattanDistanceToPoint(PointF(0.f, 7.f)));
+ EXPECT_FLOAT_EQ(1.1f, f.ManhattanDistanceToPoint(PointF(0.f, 3.f)));
+}
+
+TEST(RectTest, ManhattanInternalDistance) {
+ Rect i(0, 0, 400, 400);
+ EXPECT_EQ(0, i.ManhattanInternalDistance(gfx::Rect(-1, 0, 2, 1)));
+ EXPECT_EQ(1, i.ManhattanInternalDistance(gfx::Rect(400, 0, 1, 400)));
+ EXPECT_EQ(2, i.ManhattanInternalDistance(gfx::Rect(-100, -100, 100, 100)));
+ EXPECT_EQ(2, i.ManhattanInternalDistance(gfx::Rect(-101, 100, 100, 100)));
+ EXPECT_EQ(4, i.ManhattanInternalDistance(gfx::Rect(-101, -101, 100, 100)));
+ EXPECT_EQ(435, i.ManhattanInternalDistance(gfx::Rect(630, 603, 100, 100)));
+
+ RectF f(0.0f, 0.0f, 400.0f, 400.0f);
+ static const float kEpsilon = std::numeric_limits<float>::epsilon();
+
+ EXPECT_FLOAT_EQ(
+ 0.0f, f.ManhattanInternalDistance(gfx::RectF(-1.0f, 0.0f, 2.0f, 1.0f)));
+ EXPECT_FLOAT_EQ(
+ kEpsilon,
+ f.ManhattanInternalDistance(gfx::RectF(400.0f, 0.0f, 1.0f, 400.0f)));
+ EXPECT_FLOAT_EQ(2.0f * kEpsilon,
+ f.ManhattanInternalDistance(
+ gfx::RectF(-100.0f, -100.0f, 100.0f, 100.0f)));
+ EXPECT_FLOAT_EQ(
+ 1.0f + kEpsilon,
+ f.ManhattanInternalDistance(gfx::RectF(-101.0f, 100.0f, 100.0f, 100.0f)));
+ EXPECT_FLOAT_EQ(2.0f + 2.0f * kEpsilon,
+ f.ManhattanInternalDistance(
+ gfx::RectF(-101.0f, -101.0f, 100.0f, 100.0f)));
+ EXPECT_FLOAT_EQ(
+ 433.0f + 2.0f * kEpsilon,
+ f.ManhattanInternalDistance(gfx::RectF(630.0f, 603.0f, 100.0f, 100.0f)));
+
+ EXPECT_FLOAT_EQ(
+ 0.0f, f.ManhattanInternalDistance(gfx::RectF(-1.0f, 0.0f, 1.1f, 1.0f)));
+ EXPECT_FLOAT_EQ(
+ 0.1f + kEpsilon,
+ f.ManhattanInternalDistance(gfx::RectF(-1.5f, 0.0f, 1.4f, 1.0f)));
+ EXPECT_FLOAT_EQ(
+ kEpsilon,
+ f.ManhattanInternalDistance(gfx::RectF(-1.5f, 0.0f, 1.5f, 1.0f)));
+}
+
+TEST(RectTest, IntegerOverflow) {
+ int limit = std::numeric_limits<int>::max();
+ int min_limit = std::numeric_limits<int>::min();
+ int expected = 10;
+ int large_number = limit - expected;
+
+ Rect height_overflow(0, large_number, 100, 100);
+ EXPECT_EQ(large_number, height_overflow.y());
+ EXPECT_EQ(expected, height_overflow.height());
+
+ Rect width_overflow(large_number, 0, 100, 100);
+ EXPECT_EQ(large_number, width_overflow.x());
+ EXPECT_EQ(expected, width_overflow.width());
+
+ Rect size_height_overflow(Point(0, large_number), Size(100, 100));
+ EXPECT_EQ(large_number, size_height_overflow.y());
+ EXPECT_EQ(expected, size_height_overflow.height());
+
+ Rect size_width_overflow(Point(large_number, 0), Size(100, 100));
+ EXPECT_EQ(large_number, size_width_overflow.x());
+ EXPECT_EQ(expected, size_width_overflow.width());
+
+ Rect set_height_overflow(0, large_number, 100, 5);
+ EXPECT_EQ(5, set_height_overflow.height());
+ set_height_overflow.set_height(100);
+ EXPECT_EQ(expected, set_height_overflow.height());
+
+ Rect set_y_overflow(100, 100, 100, 100);
+ EXPECT_EQ(100, set_y_overflow.height());
+ set_y_overflow.set_y(large_number);
+ EXPECT_EQ(expected, set_y_overflow.height());
+
+ Rect set_width_overflow(large_number, 0, 5, 100);
+ EXPECT_EQ(5, set_width_overflow.width());
+ set_width_overflow.set_width(100);
+ EXPECT_EQ(expected, set_width_overflow.width());
+
+ Rect set_x_overflow(100, 100, 100, 100);
+ EXPECT_EQ(100, set_x_overflow.width());
+ set_x_overflow.set_x(large_number);
+ EXPECT_EQ(expected, set_x_overflow.width());
+
+ Point large_offset(large_number, large_number);
+ Size size(100, 100);
+ Size expected_size(10, 10);
+
+ Rect set_origin_overflow(100, 100, 100, 100);
+ EXPECT_EQ(size, set_origin_overflow.size());
+ set_origin_overflow.set_origin(large_offset);
+ EXPECT_EQ(large_offset, set_origin_overflow.origin());
+ EXPECT_EQ(expected_size, set_origin_overflow.size());
+
+ Rect set_size_overflow(large_number, large_number, 5, 5);
+ EXPECT_EQ(Size(5, 5), set_size_overflow.size());
+ set_size_overflow.set_size(size);
+ EXPECT_EQ(large_offset, set_size_overflow.origin());
+ EXPECT_EQ(expected_size, set_size_overflow.size());
+
+ Rect set_rect_overflow;
+ set_rect_overflow.SetRect(large_number, large_number, 100, 100);
+ EXPECT_EQ(large_offset, set_rect_overflow.origin());
+ EXPECT_EQ(expected_size, set_rect_overflow.size());
+
+ // Insetting an empty rect, but the total inset (left + right) could overflow.
+ Rect inset_overflow;
+ inset_overflow.Inset(large_number, large_number, 100, 100);
+ EXPECT_EQ(large_offset, inset_overflow.origin());
+ EXPECT_EQ(gfx::Size(), inset_overflow.size());
+
+ // Insetting where the total inset (width - left - right) could overflow.
+ // Also, this insetting by the min limit in all directions cannot
+ // represent width() without overflow, so that will also clamp.
+ Rect inset_overflow2;
+ inset_overflow2.Inset(min_limit, min_limit, min_limit, min_limit);
+ EXPECT_EQ(inset_overflow2, gfx::Rect(min_limit, min_limit, limit, limit));
+
+ // Insetting where the width shouldn't change, but if the insets operations
+ // clamped in the wrong order, e.g. ((width - left) - right) vs (width - (left
+ // + right)) then this will not work properly. This is the proper order,
+ // as if left + right overflows, the width cannot be decreased by more than
+ // max int anyway. Additionally, if left + right underflows, it cannot be
+ // increased by more then max int.
+ Rect inset_overflow3(0, 0, limit, limit);
+ inset_overflow3.Inset(-100, -100, 100, 100);
+ EXPECT_EQ(inset_overflow3, gfx::Rect(-100, -100, limit, limit));
+
+ Rect inset_overflow4(-1000, -1000, limit, limit);
+ inset_overflow4.Inset(100, 100, -100, -100);
+ EXPECT_EQ(inset_overflow4, gfx::Rect(-900, -900, limit, limit));
+
+ Rect offset_overflow(0, 0, 100, 100);
+ offset_overflow.Offset(large_number, large_number);
+ EXPECT_EQ(large_offset, offset_overflow.origin());
+ EXPECT_EQ(expected_size, offset_overflow.size());
+
+ Rect operator_overflow(0, 0, 100, 100);
+ operator_overflow += Vector2d(large_number, large_number);
+ EXPECT_EQ(large_offset, operator_overflow.origin());
+ EXPECT_EQ(expected_size, operator_overflow.size());
+
+ Rect origin_maxint(limit, limit, limit, limit);
+ EXPECT_EQ(origin_maxint, Rect(gfx::Point(limit, limit), gfx::Size()));
+
+ // Expect a rect at the origin and a rect whose right/bottom is maxint
+ // create a rect that extends from 0..maxint in both extents.
+ {
+ Rect origin_small(0, 0, 100, 100);
+ Rect big_clamped(50, 50, limit, limit);
+ EXPECT_EQ(big_clamped.right(), limit);
+
+ Rect unioned = UnionRects(origin_small, big_clamped);
+ Rect rect_limit(0, 0, limit, limit);
+ EXPECT_EQ(unioned, rect_limit);
+ }
+
+ // Expect a rect that would overflow width (but not right) to be clamped
+ // and to have maxint extents after unioning.
+ {
+ Rect small(-500, -400, 100, 100);
+ Rect big(-400, -500, limit, limit);
+ // Technically, this should be limit + 100 width, but will clamp to maxint.
+ EXPECT_EQ(UnionRects(small, big), Rect(-500, -500, limit, limit));
+ }
+
+ // Expect a rect that would overflow right *and* width to be clamped.
+ {
+ Rect clamped(500, 500, limit, limit);
+ Rect positive_origin(100, 100, 500, 500);
+
+ // Ideally, this should be (100, 100, limit + 400, limit + 400).
+ // However, width overflows and would be clamped to limit, but right
+ // overflows too and so will be clamped to limit - 100.
+ Rect expected(100, 100, limit - 100, limit - 100);
+ EXPECT_EQ(UnionRects(clamped, positive_origin), expected);
+ }
+
+ // Unioning a left=minint rect with a right=maxint rect.
+ // We can't represent both ends of the spectrum in the same rect.
+ // Make sure we keep the most useful area.
+ {
+ int part_limit = min_limit / 3;
+ Rect left_minint(min_limit, min_limit, 1, 1);
+ Rect right_maxint(limit - 1, limit - 1, limit, limit);
+ Rect expected(part_limit, part_limit, 2 * part_limit, 2 * part_limit);
+ Rect result = UnionRects(left_minint, right_maxint);
+
+ // The result should be maximally big.
+ EXPECT_EQ(limit, result.height());
+ EXPECT_EQ(limit, result.width());
+
+ // The result should include the area near the origin.
+ EXPECT_GT(-part_limit, result.x());
+ EXPECT_LT(part_limit, result.right());
+ EXPECT_GT(-part_limit, result.y());
+ EXPECT_LT(part_limit, result.bottom());
+
+ // More succinctly, but harder to read in the results.
+ EXPECT_TRUE(UnionRects(left_minint, right_maxint).Contains(expected));
+ }
+}
+
+TEST(RectTest, ScaleToEnclosingRectSafe) {
+ const int max_int = std::numeric_limits<int>::max();
+ const int min_int = std::numeric_limits<int>::min();
+
+ Rect xy_underflow(-100000, -123456, 10, 20);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(xy_underflow, 100000, 100000),
+ Rect(min_int, min_int, 1000000, 2000000));
+
+ // A location overflow means that width/right and bottom/top also
+ // overflow so need to be clamped.
+ Rect xy_overflow(100000, 123456, 10, 20);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(xy_overflow, 100000, 100000),
+ Rect(max_int, max_int, 0, 0));
+
+ // In practice all rects are clamped to 0 width / 0 height so
+ // negative sizes don't matter, but try this for the sake of testing.
+ Rect size_underflow(-1, -2, 100000, 100000);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(size_underflow, -100000, -100000),
+ Rect(100000, 200000, 0, 0));
+
+ Rect size_overflow(-1, -2, 123456, 234567);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(size_overflow, 100000, 100000),
+ Rect(-100000, -200000, max_int, max_int));
+ // Verify width/right gets clamped properly too if x/y positive.
+ Rect size_overflow2(1, 2, 123456, 234567);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(size_overflow2, 100000, 100000),
+ Rect(100000, 200000, max_int - 100000, max_int - 200000));
+
+ Rect max_rect(max_int, max_int, max_int, max_int);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(max_rect, max_int, max_int),
+ Rect(max_int, max_int, 0, 0));
+
+ Rect min_rect(min_int, min_int, max_int, max_int);
+ // Min rect can't be scaled up any further in any dimension.
+ EXPECT_EQ(ScaleToEnclosingRectSafe(min_rect, 2, 3.5), min_rect);
+ EXPECT_EQ(ScaleToEnclosingRectSafe(min_rect, max_int, max_int), min_rect);
+ // Min rect scaled by min is an empty rect at (max, max)
+ EXPECT_EQ(ScaleToEnclosingRectSafe(min_rect, min_int, min_int), max_rect);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/safe_integer_conversions_unittest.cc b/ui/gfx/geometry/safe_integer_conversions_unittest.cc
new file mode 100644
index 0000000000..91bdbb8d35
--- /dev/null
+++ b/ui/gfx/geometry/safe_integer_conversions_unittest.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/safe_integer_conversions.h"
+
+#include <limits>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+
+TEST(SafeIntegerConversions, ToFlooredInt) {
+ float max = std::numeric_limits<int>::max();
+ float min = std::numeric_limits<int>::min();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ EXPECT_EQ(int_max, ToFlooredInt(infinity));
+ EXPECT_EQ(int_max, ToFlooredInt(max));
+ EXPECT_EQ(int_max, ToFlooredInt(max + 100));
+
+ EXPECT_EQ(-101, ToFlooredInt(-100.5f));
+ EXPECT_EQ(0, ToFlooredInt(0.f));
+ EXPECT_EQ(100, ToFlooredInt(100.5f));
+
+ EXPECT_EQ(int_min, ToFlooredInt(-infinity));
+ EXPECT_EQ(int_min, ToFlooredInt(min));
+ EXPECT_EQ(int_min, ToFlooredInt(min - 100));
+}
+
+TEST(SafeIntegerConversions, ToCeiledInt) {
+ float max = std::numeric_limits<int>::max();
+ float min = std::numeric_limits<int>::min();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ EXPECT_EQ(int_max, ToCeiledInt(infinity));
+ EXPECT_EQ(int_max, ToCeiledInt(max));
+ EXPECT_EQ(int_max, ToCeiledInt(max + 100));
+
+ EXPECT_EQ(-100, ToCeiledInt(-100.5f));
+ EXPECT_EQ(0, ToCeiledInt(0.f));
+ EXPECT_EQ(101, ToCeiledInt(100.5f));
+
+ EXPECT_EQ(int_min, ToCeiledInt(-infinity));
+ EXPECT_EQ(int_min, ToCeiledInt(min));
+ EXPECT_EQ(int_min, ToCeiledInt(min - 100));
+}
+
+TEST(SafeIntegerConversions, ToRoundedInt) {
+ float max = std::numeric_limits<int>::max();
+ float min = std::numeric_limits<int>::min();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ EXPECT_EQ(int_max, ToRoundedInt(infinity));
+ EXPECT_EQ(int_max, ToRoundedInt(max));
+ EXPECT_EQ(int_max, ToRoundedInt(max + 100));
+
+ EXPECT_EQ(-100, ToRoundedInt(-100.1f));
+ EXPECT_EQ(-101, ToRoundedInt(-100.5f));
+ EXPECT_EQ(-101, ToRoundedInt(-100.9f));
+ EXPECT_EQ(0, ToRoundedInt(0.f));
+ EXPECT_EQ(100, ToRoundedInt(100.1f));
+ EXPECT_EQ(101, ToRoundedInt(100.5f));
+ EXPECT_EQ(101, ToRoundedInt(100.9f));
+
+ EXPECT_EQ(int_min, ToRoundedInt(-infinity));
+ EXPECT_EQ(int_min, ToRoundedInt(min));
+ EXPECT_EQ(int_min, ToRoundedInt(min - 100));
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/scroll_offset_unittest.cc b/ui/gfx/geometry/scroll_offset_unittest.cc
new file mode 100644
index 0000000000..782fdf4c10
--- /dev/null
+++ b/ui/gfx/geometry/scroll_offset_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <cmath>
+#include <limits>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/scroll_offset.h"
+
+namespace gfx {
+
+TEST(ScrollOffsetTest, IsZero) {
+ ScrollOffset zero(0, 0);
+ ScrollOffset nonzero(0.1f, -0.1f);
+
+ EXPECT_TRUE(zero.IsZero());
+ EXPECT_FALSE(nonzero.IsZero());
+}
+
+TEST(ScrollOffsetTest, Add) {
+ ScrollOffset f1(3.1f, 5.1f);
+ ScrollOffset f2(4.3f, -1.3f);
+
+ const struct {
+ ScrollOffset expected;
+ ScrollOffset actual;
+ } scroll_offset_tests[] = {
+ { ScrollOffset(3.1f, 5.1f), f1 + ScrollOffset() },
+ { ScrollOffset(3.1f + 4.3f, 5.1f - 1.3f), f1 + f2 },
+ { ScrollOffset(3.1f - 4.3f, 5.1f + 1.3f), f1 - f2 }
+ };
+
+ for (size_t i = 0; i < arraysize(scroll_offset_tests); ++i)
+ EXPECT_EQ(scroll_offset_tests[i].expected.ToString(),
+ scroll_offset_tests[i].actual.ToString());
+}
+
+TEST(ScrollOffsetTest, Negative) {
+ const struct {
+ ScrollOffset expected;
+ ScrollOffset actual;
+ } scroll_offset_tests[] = {
+ { ScrollOffset(-0.3f, -0.3f), -ScrollOffset(0.3f, 0.3f) },
+ { ScrollOffset(0.3f, 0.3f), -ScrollOffset(-0.3f, -0.3f) },
+ { ScrollOffset(-0.3f, 0.3f), -ScrollOffset(0.3f, -0.3f) },
+ { ScrollOffset(0.3f, -0.3f), -ScrollOffset(-0.3f, 0.3f) }
+ };
+
+ for (size_t i = 0; i < arraysize(scroll_offset_tests); ++i)
+ EXPECT_EQ(scroll_offset_tests[i].expected.ToString(),
+ scroll_offset_tests[i].actual.ToString());
+}
+
+TEST(ScrollOffsetTest, Scale) {
+ float float_values[][4] = {
+ { 4.5f, 1.2f, 3.3f, 5.6f },
+ { 4.5f, -1.2f, 3.3f, 5.6f },
+ { 4.5f, 1.2f, 3.3f, -5.6f },
+ { 4.5f, 1.2f, -3.3f, -5.6f },
+ { -4.5f, 1.2f, 3.3f, 5.6f },
+ { -4.5f, 1.2f, 0, 5.6f },
+ { -4.5f, 1.2f, 3.3f, 0 },
+ { 4.5f, 0, 3.3f, 5.6f },
+ { 0, 1.2f, 3.3f, 5.6f }
+ };
+
+ for (size_t i = 0; i < arraysize(float_values); ++i) {
+ ScrollOffset v(float_values[i][0], float_values[i][1]);
+ v.Scale(float_values[i][2], float_values[i][3]);
+ EXPECT_EQ(v.x(), float_values[i][0] * float_values[i][2]);
+ EXPECT_EQ(v.y(), float_values[i][1] * float_values[i][3]);
+ }
+
+ float single_values[][3] = {
+ { 4.5f, 1.2f, 3.3f },
+ { 4.5f, -1.2f, 3.3f },
+ { 4.5f, 1.2f, 3.3f },
+ { 4.5f, 1.2f, -3.3f },
+ { -4.5f, 1.2f, 3.3f },
+ { -4.5f, 1.2f, 0 },
+ { -4.5f, 1.2f, 3.3f },
+ { 4.5f, 0, 3.3f },
+ { 0, 1.2f, 3.3f }
+ };
+
+ for (size_t i = 0; i < arraysize(single_values); ++i) {
+ ScrollOffset v(single_values[i][0], single_values[i][1]);
+ v.Scale(single_values[i][2]);
+ EXPECT_EQ(v.x(), single_values[i][0] * single_values[i][2]);
+ EXPECT_EQ(v.y(), single_values[i][1] * single_values[i][2]);
+ }
+}
+
+TEST(ScrollOffsetTest, ClampScrollOffset) {
+ ScrollOffset a;
+
+ a = ScrollOffset(3.5, 5.5);
+ EXPECT_EQ(ScrollOffset(3.5, 5.5).ToString(), a.ToString());
+ a.SetToMax(ScrollOffset(2.5, 4.5));
+ EXPECT_EQ(ScrollOffset(3.5, 5.5).ToString(), a.ToString());
+ a.SetToMax(ScrollOffset(3.5, 5.5));
+ EXPECT_EQ(ScrollOffset(3.5, 5.5).ToString(), a.ToString());
+ a.SetToMax(ScrollOffset(4.5, 2.5));
+ EXPECT_EQ(ScrollOffset(4.5, 5.5).ToString(), a.ToString());
+ a.SetToMax(ScrollOffset(8.5, 10.5));
+ EXPECT_EQ(ScrollOffset(8.5, 10.5).ToString(), a.ToString());
+
+ a.SetToMin(ScrollOffset(9.5, 11.5));
+ EXPECT_EQ(ScrollOffset(8.5, 10.5).ToString(), a.ToString());
+ a.SetToMin(ScrollOffset(8.5, 10.5));
+ EXPECT_EQ(ScrollOffset(8.5, 10.5).ToString(), a.ToString());
+ a.SetToMin(ScrollOffset(11.5, 9.5));
+ EXPECT_EQ(ScrollOffset(8.5, 9.5).ToString(), a.ToString());
+ a.SetToMin(ScrollOffset(7.5, 11.5));
+ EXPECT_EQ(ScrollOffset(7.5, 9.5).ToString(), a.ToString());
+ a.SetToMin(ScrollOffset(3.5, 5.5));
+ EXPECT_EQ(ScrollOffset(3.5, 5.5).ToString(), a.ToString());
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/size_unittest.cc b/ui/gfx/geometry/size_unittest.cc
new file mode 100644
index 0000000000..ab4a831768
--- /dev/null
+++ b/ui/gfx/geometry/size_unittest.cc
@@ -0,0 +1,251 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/geometry/size_conversions.h"
+#include "ui/gfx/geometry/size_f.h"
+
+namespace gfx {
+
+namespace {
+
+int TestSizeF(const SizeF& s) {
+ return s.width();
+}
+
+} // namespace
+
+TEST(SizeTest, ToSizeF) {
+ // Check that explicit conversion from integer to float compiles.
+ Size a(10, 20);
+ float width = TestSizeF(gfx::SizeF(a));
+ EXPECT_EQ(width, a.width());
+
+ SizeF b(10, 20);
+
+ EXPECT_EQ(b, gfx::SizeF(a));
+}
+
+TEST(SizeTest, ToFlooredSize) {
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0, 0)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.5f, 0.5f)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10, 10)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.5f, 10.5f)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.9999f, 10.9999f)));
+}
+
+TEST(SizeTest, ToCeiledSize) {
+ EXPECT_EQ(Size(0, 0), ToCeiledSize(SizeF(0, 0)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.5f, 0.5f)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Size(10, 10), ToCeiledSize(SizeF(10, 10)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.5f, 10.5f)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.9999f, 10.9999f)));
+}
+
+TEST(SizeTest, ToRoundedSize) {
+ EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0, 0)));
+ EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Size(1, 1), ToRoundedSize(SizeF(0.5f, 0.5f)));
+ EXPECT_EQ(Size(1, 1), ToRoundedSize(SizeF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10, 10)));
+ EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Size(11, 11), ToRoundedSize(SizeF(10.5f, 10.5f)));
+ EXPECT_EQ(Size(11, 11), ToRoundedSize(SizeF(10.9999f, 10.9999f)));
+}
+
+TEST(SizeTest, ClampSize) {
+ Size a;
+
+ a = Size(3, 5);
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+ a.SetToMax(Size(2, 4));
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+ a.SetToMax(Size(3, 5));
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+ a.SetToMax(Size(4, 2));
+ EXPECT_EQ(Size(4, 5).ToString(), a.ToString());
+ a.SetToMax(Size(8, 10));
+ EXPECT_EQ(Size(8, 10).ToString(), a.ToString());
+
+ a.SetToMin(Size(9, 11));
+ EXPECT_EQ(Size(8, 10).ToString(), a.ToString());
+ a.SetToMin(Size(8, 10));
+ EXPECT_EQ(Size(8, 10).ToString(), a.ToString());
+ a.SetToMin(Size(11, 9));
+ EXPECT_EQ(Size(8, 9).ToString(), a.ToString());
+ a.SetToMin(Size(7, 11));
+ EXPECT_EQ(Size(7, 9).ToString(), a.ToString());
+ a.SetToMin(Size(3, 5));
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+}
+
+TEST(SizeTest, ClampSizeF) {
+ SizeF a;
+
+ a = SizeF(3.5f, 5.5f);
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(2.5f, 4.5f));
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(3.5f, 5.5f));
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(4.5f, 2.5f));
+ EXPECT_EQ(SizeF(4.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(8.5f, 10.5f));
+ EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString());
+
+ a.SetToMin(SizeF(9.5f, 11.5f));
+ EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(8.5f, 10.5f));
+ EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(11.5f, 9.5f));
+ EXPECT_EQ(SizeF(8.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(7.5f, 11.5f));
+ EXPECT_EQ(SizeF(7.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(3.5f, 5.5f));
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+}
+
+TEST(SizeTest, Enlarge) {
+ Size test(3, 4);
+ test.Enlarge(5, -8);
+ EXPECT_EQ(test, Size(8, -4));
+}
+
+TEST(SizeTest, IntegerOverflow) {
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ Size max_size(int_max, int_max);
+ Size min_size(int_min, int_min);
+ Size test;
+
+ test = Size();
+ test.Enlarge(int_max, int_max);
+ EXPECT_EQ(test, max_size);
+
+ test = Size();
+ test.Enlarge(int_min, int_min);
+ EXPECT_EQ(test, min_size);
+
+ test = Size(10, 20);
+ test.Enlarge(int_max, int_max);
+ EXPECT_EQ(test, max_size);
+
+ test = Size(-10, -20);
+ test.Enlarge(int_min, int_min);
+ EXPECT_EQ(test, min_size);
+}
+
+// This checks that we set IsEmpty appropriately.
+TEST(SizeTest, TrivialDimensionTests) {
+ const float clearly_trivial = SizeF::kTrivial / 2.f;
+ const float massize_dimension = 4e13f;
+
+ // First, using the constructor.
+ EXPECT_TRUE(SizeF(clearly_trivial, 1.f).IsEmpty());
+ EXPECT_TRUE(SizeF(.01f, clearly_trivial).IsEmpty());
+ EXPECT_TRUE(SizeF(0.f, 0.f).IsEmpty());
+ EXPECT_FALSE(SizeF(.01f, .01f).IsEmpty());
+
+ // Then use the setter.
+ SizeF test(2.f, 1.f);
+ EXPECT_FALSE(test.IsEmpty());
+
+ test.SetSize(clearly_trivial, 1.f);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.SetSize(.01f, clearly_trivial);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.SetSize(0.f, 0.f);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.SetSize(.01f, .01f);
+ EXPECT_FALSE(test.IsEmpty());
+
+ // Now just one dimension at a time.
+ test.set_width(clearly_trivial);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.set_width(massize_dimension);
+ test.set_height(clearly_trivial);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.set_width(clearly_trivial);
+ test.set_height(massize_dimension);
+ EXPECT_TRUE(test.IsEmpty());
+
+ test.set_width(2.f);
+ EXPECT_FALSE(test.IsEmpty());
+}
+
+// These are the ramifications of the decision to keep the recorded size
+// at zero for trivial sizes.
+TEST(SizeTest, ClampsToZero) {
+ const float clearly_trivial = SizeF::kTrivial / 2.f;
+ const float nearly_trivial = SizeF::kTrivial * 1.5f;
+
+ SizeF test(clearly_trivial, 1.f);
+
+ EXPECT_FLOAT_EQ(0.f, test.width());
+ EXPECT_FLOAT_EQ(1.f, test.height());
+
+ test.SetSize(.01f, clearly_trivial);
+
+ EXPECT_FLOAT_EQ(.01f, test.width());
+ EXPECT_FLOAT_EQ(0.f, test.height());
+
+ test.SetSize(nearly_trivial, nearly_trivial);
+
+ EXPECT_FLOAT_EQ(nearly_trivial, test.width());
+ EXPECT_FLOAT_EQ(nearly_trivial, test.height());
+
+ test.Scale(0.5f);
+
+ EXPECT_FLOAT_EQ(0.f, test.width());
+ EXPECT_FLOAT_EQ(0.f, test.height());
+
+ test.SetSize(0.f, 0.f);
+ test.Enlarge(clearly_trivial, clearly_trivial);
+ test.Enlarge(clearly_trivial, clearly_trivial);
+ test.Enlarge(clearly_trivial, clearly_trivial);
+
+ EXPECT_EQ(SizeF(0.f, 0.f), test);
+}
+
+// These make sure the constructor and setter have the same effect on the
+// boundary case. This claims to know the boundary, but not which way it goes.
+TEST(SizeTest, ConsistentClamping) {
+ SizeF resized;
+
+ resized.SetSize(SizeF::kTrivial, 0.f);
+ EXPECT_EQ(SizeF(SizeF::kTrivial, 0.f), resized);
+
+ resized.SetSize(0.f, SizeF::kTrivial);
+ EXPECT_EQ(SizeF(0.f, SizeF::kTrivial), resized);
+}
+
+// Let's make sure we don't unexpectedly grow the struct by adding constants.
+// Also, if some platform packs floats inefficiently, it would be worth noting.
+TEST(SizeTest, StaysSmall) {
+ EXPECT_EQ(2 * sizeof(float), sizeof(SizeF));
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/test/rect_test_util.cc b/ui/gfx/geometry/test/rect_test_util.cc
new file mode 100644
index 0000000000..bcc943b7fc
--- /dev/null
+++ b/ui/gfx/geometry/test/rect_test_util.cc
@@ -0,0 +1,23 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/test/rect_test_util.h"
+
+namespace gfx {
+namespace test {
+
+testing::AssertionResult RectContains(const gfx::Rect& outer_rect,
+ const gfx::Rect& inner_rect) {
+ if (outer_rect.Contains(inner_rect)) {
+ return testing::AssertionSuccess()
+ << "outer_rect (" << outer_rect.ToString()
+ << ") does contain inner_rect (" << inner_rect.ToString() << ")";
+ }
+ return testing::AssertionFailure() << "outer_rect (" << outer_rect.ToString()
+ << ") does not contain inner_rect ("
+ << inner_rect.ToString() << ")";
+}
+
+} // namespace test
+} // namespace gfx
diff --git a/ui/gfx/geometry/test/rect_test_util.h b/ui/gfx/geometry/test/rect_test_util.h
new file mode 100644
index 0000000000..857dd210b1
--- /dev/null
+++ b/ui/gfx/geometry/test/rect_test_util.h
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_TEST_RECT_TEST_UTIL_H_
+#define UI_GFX_GEOMETRY_TEST_RECT_TEST_UTIL_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+namespace test {
+
+testing::AssertionResult RectContains(const gfx::Rect& outer_rect,
+ const gfx::Rect& inner_rect);
+
+} // namespace test
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_TEST_RECT_TEST_UTIL_H_
diff --git a/ui/gfx/geometry/vector2d_conversions.cc b/ui/gfx/geometry/vector2d_conversions.cc
new file mode 100644
index 0000000000..dceb69e2ff
--- /dev/null
+++ b/ui/gfx/geometry/vector2d_conversions.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/vector2d_conversions.h"
+
+#include "ui/gfx/geometry/safe_integer_conversions.h"
+
+namespace gfx {
+
+Vector2d ToFlooredVector2d(const Vector2dF& vector2d) {
+ int x = ToFlooredInt(vector2d.x());
+ int y = ToFlooredInt(vector2d.y());
+ return Vector2d(x, y);
+}
+
+Vector2d ToCeiledVector2d(const Vector2dF& vector2d) {
+ int x = ToCeiledInt(vector2d.x());
+ int y = ToCeiledInt(vector2d.y());
+ return Vector2d(x, y);
+}
+
+Vector2d ToRoundedVector2d(const Vector2dF& vector2d) {
+ int x = ToRoundedInt(vector2d.x());
+ int y = ToRoundedInt(vector2d.y());
+ return Vector2d(x, y);
+}
+
+} // namespace gfx
+
diff --git a/ui/gfx/geometry/vector2d_conversions.h b/ui/gfx/geometry/vector2d_conversions.h
new file mode 100644
index 0000000000..f4e16ae4be
--- /dev/null
+++ b/ui/gfx/geometry/vector2d_conversions.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_GFX_GEOMETRY_VECTOR2D_CONVERSIONS_H_
+#define UI_GFX_GEOMETRY_VECTOR2D_CONVERSIONS_H_
+
+#include "ui/gfx/geometry/vector2d.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace gfx {
+
+// Returns a Vector2d with each component from the input Vector2dF floored.
+GFX_EXPORT Vector2d ToFlooredVector2d(const Vector2dF& vector2d);
+
+// Returns a Vector2d with each component from the input Vector2dF ceiled.
+GFX_EXPORT Vector2d ToCeiledVector2d(const Vector2dF& vector2d);
+
+// Returns a Vector2d with each component from the input Vector2dF rounded.
+GFX_EXPORT Vector2d ToRoundedVector2d(const Vector2dF& vector2d);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_VECTOR2D_CONVERSIONS_H_
diff --git a/ui/gfx/geometry/vector2d_unittest.cc b/ui/gfx/geometry/vector2d_unittest.cc
new file mode 100644
index 0000000000..4bdf24cba4
--- /dev/null
+++ b/ui/gfx/geometry/vector2d_unittest.cc
@@ -0,0 +1,293 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <cmath>
+#include <limits>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/vector2d.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace gfx {
+
+TEST(Vector2dTest, ConversionToFloat) {
+ Vector2d i(3, 4);
+ Vector2dF f = i;
+ EXPECT_EQ(i, f);
+}
+
+TEST(Vector2dTest, IsZero) {
+ Vector2d int_zero(0, 0);
+ Vector2d int_nonzero(2, -2);
+ Vector2dF float_zero(0, 0);
+ Vector2dF float_nonzero(0.1f, -0.1f);
+
+ EXPECT_TRUE(int_zero.IsZero());
+ EXPECT_FALSE(int_nonzero.IsZero());
+ EXPECT_TRUE(float_zero.IsZero());
+ EXPECT_FALSE(float_nonzero.IsZero());
+}
+
+TEST(Vector2dTest, Add) {
+ Vector2d i1(3, 5);
+ Vector2d i2(4, -1);
+
+ const struct {
+ Vector2d expected;
+ Vector2d actual;
+ } int_tests[] = {
+ { Vector2d(3, 5), i1 + Vector2d() },
+ { Vector2d(3 + 4, 5 - 1), i1 + i2 },
+ { Vector2d(3 - 4, 5 + 1), i1 - i2 }
+ };
+
+ for (size_t i = 0; i < arraysize(int_tests); ++i)
+ EXPECT_EQ(int_tests[i].expected.ToString(),
+ int_tests[i].actual.ToString());
+
+ Vector2dF f1(3.1f, 5.1f);
+ Vector2dF f2(4.3f, -1.3f);
+
+ const struct {
+ Vector2dF expected;
+ Vector2dF actual;
+ } float_tests[] = {
+ { Vector2dF(3.1F, 5.1F), f1 + Vector2d() },
+ { Vector2dF(3.1F, 5.1F), f1 + Vector2dF() },
+ { Vector2dF(3.1f + 4.3f, 5.1f - 1.3f), f1 + f2 },
+ { Vector2dF(3.1f - 4.3f, 5.1f + 1.3f), f1 - f2 }
+ };
+
+ for (size_t i = 0; i < arraysize(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector2dTest, Negative) {
+ const struct {
+ Vector2d expected;
+ Vector2d actual;
+ } int_tests[] = {
+ { Vector2d(0, 0), -Vector2d(0, 0) },
+ { Vector2d(-3, -3), -Vector2d(3, 3) },
+ { Vector2d(3, 3), -Vector2d(-3, -3) },
+ { Vector2d(-3, 3), -Vector2d(3, -3) },
+ { Vector2d(3, -3), -Vector2d(-3, 3) }
+ };
+
+ for (size_t i = 0; i < arraysize(int_tests); ++i)
+ EXPECT_EQ(int_tests[i].expected.ToString(),
+ int_tests[i].actual.ToString());
+
+ const struct {
+ Vector2dF expected;
+ Vector2dF actual;
+ } float_tests[] = {
+ { Vector2dF(0, 0), -Vector2d(0, 0) },
+ { Vector2dF(-0.3f, -0.3f), -Vector2dF(0.3f, 0.3f) },
+ { Vector2dF(0.3f, 0.3f), -Vector2dF(-0.3f, -0.3f) },
+ { Vector2dF(-0.3f, 0.3f), -Vector2dF(0.3f, -0.3f) },
+ { Vector2dF(0.3f, -0.3f), -Vector2dF(-0.3f, 0.3f) }
+ };
+
+ for (size_t i = 0; i < arraysize(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector2dTest, Scale) {
+ float double_values[][4] = {
+ { 4.5f, 1.2f, 3.3f, 5.6f },
+ { 4.5f, -1.2f, 3.3f, 5.6f },
+ { 4.5f, 1.2f, 3.3f, -5.6f },
+ { 4.5f, 1.2f, -3.3f, -5.6f },
+ { -4.5f, 1.2f, 3.3f, 5.6f },
+ { -4.5f, 1.2f, 0, 5.6f },
+ { -4.5f, 1.2f, 3.3f, 0 },
+ { 4.5f, 0, 3.3f, 5.6f },
+ { 0, 1.2f, 3.3f, 5.6f }
+ };
+
+ for (size_t i = 0; i < arraysize(double_values); ++i) {
+ Vector2dF v(double_values[i][0], double_values[i][1]);
+ v.Scale(double_values[i][2], double_values[i][3]);
+ EXPECT_EQ(v.x(), double_values[i][0] * double_values[i][2]);
+ EXPECT_EQ(v.y(), double_values[i][1] * double_values[i][3]);
+
+ Vector2dF v2 = ScaleVector2d(
+ gfx::Vector2dF(double_values[i][0], double_values[i][1]),
+ double_values[i][2], double_values[i][3]);
+ EXPECT_EQ(double_values[i][0] * double_values[i][2], v2.x());
+ EXPECT_EQ(double_values[i][1] * double_values[i][3], v2.y());
+ }
+
+ float single_values[][3] = {
+ { 4.5f, 1.2f, 3.3f },
+ { 4.5f, -1.2f, 3.3f },
+ { 4.5f, 1.2f, 3.3f },
+ { 4.5f, 1.2f, -3.3f },
+ { -4.5f, 1.2f, 3.3f },
+ { -4.5f, 1.2f, 0 },
+ { -4.5f, 1.2f, 3.3f },
+ { 4.5f, 0, 3.3f },
+ { 0, 1.2f, 3.3f }
+ };
+
+ for (size_t i = 0; i < arraysize(single_values); ++i) {
+ Vector2dF v(single_values[i][0], single_values[i][1]);
+ v.Scale(single_values[i][2]);
+ EXPECT_EQ(v.x(), single_values[i][0] * single_values[i][2]);
+ EXPECT_EQ(v.y(), single_values[i][1] * single_values[i][2]);
+
+ Vector2dF v2 = ScaleVector2d(
+ gfx::Vector2dF(double_values[i][0], double_values[i][1]),
+ double_values[i][2]);
+ EXPECT_EQ(single_values[i][0] * single_values[i][2], v2.x());
+ EXPECT_EQ(single_values[i][1] * single_values[i][2], v2.y());
+ }
+}
+
+TEST(Vector2dTest, Length) {
+ int int_values[][2] = {
+ { 0, 0 },
+ { 10, 20 },
+ { 20, 10 },
+ { -10, -20 },
+ { -20, 10 },
+ { 10, -20 },
+ };
+
+ for (size_t i = 0; i < arraysize(int_values); ++i) {
+ int v0 = int_values[i][0];
+ int v1 = int_values[i][1];
+ double length_squared =
+ static_cast<double>(v0) * v0 + static_cast<double>(v1) * v1;
+ double length = std::sqrt(length_squared);
+ Vector2d vector(v0, v1);
+ EXPECT_EQ(static_cast<float>(length_squared), vector.LengthSquared());
+ EXPECT_EQ(static_cast<float>(length), vector.Length());
+ }
+
+ float float_values[][2] = {
+ { 0, 0 },
+ { 10.5f, 20.5f },
+ { 20.5f, 10.5f },
+ { -10.5f, -20.5f },
+ { -20.5f, 10.5f },
+ { 10.5f, -20.5f },
+ // A large vector that fails if the Length function doesn't use
+ // double precision internally.
+ { 1236278317862780234892374893213178027.12122348904204230f,
+ 335890352589839028212313231225425134332.38123f },
+ };
+
+ for (size_t i = 0; i < arraysize(float_values); ++i) {
+ double v0 = float_values[i][0];
+ double v1 = float_values[i][1];
+ double length_squared =
+ static_cast<double>(v0) * v0 + static_cast<double>(v1) * v1;
+ double length = std::sqrt(length_squared);
+ Vector2dF vector(v0, v1);
+ EXPECT_DOUBLE_EQ(length_squared, vector.LengthSquared());
+ EXPECT_FLOAT_EQ(static_cast<float>(length), vector.Length());
+ }
+}
+
+TEST(Vector2dTest, ClampVector2d) {
+ Vector2d a;
+
+ a = Vector2d(3, 5);
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(2, 4));
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(3, 5));
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(4, 2));
+ EXPECT_EQ(Vector2d(4, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(8, 10));
+ EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString());
+
+ a.SetToMin(Vector2d(9, 11));
+ EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString());
+ a.SetToMin(Vector2d(8, 10));
+ EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString());
+ a.SetToMin(Vector2d(11, 9));
+ EXPECT_EQ(Vector2d(8, 9).ToString(), a.ToString());
+ a.SetToMin(Vector2d(7, 11));
+ EXPECT_EQ(Vector2d(7, 9).ToString(), a.ToString());
+ a.SetToMin(Vector2d(3, 5));
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+}
+
+TEST(Vector2dTest, ClampVector2dF) {
+ Vector2dF a;
+
+ a = Vector2dF(3.5f, 5.5f);
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(2.5f, 4.5f));
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(3.5f, 5.5f));
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(4.5f, 2.5f));
+ EXPECT_EQ(Vector2dF(4.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(8.5f, 10.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString());
+
+ a.SetToMin(Vector2dF(9.5f, 11.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(8.5f, 10.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(11.5f, 9.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(7.5f, 11.5f));
+ EXPECT_EQ(Vector2dF(7.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(3.5f, 5.5f));
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+}
+
+TEST(Vector2dTest, IntegerOverflow) {
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ Vector2d max_vector(int_max, int_max);
+ Vector2d min_vector(int_min, int_min);
+ Vector2d test;
+
+ test = Vector2d();
+ test += Vector2d(int_max, int_max);
+ EXPECT_EQ(test, max_vector);
+
+ test = Vector2d();
+ test += Vector2d(int_min, int_min);
+ EXPECT_EQ(test, min_vector);
+
+ test = Vector2d(10, 20);
+ test += Vector2d(int_max, int_max);
+ EXPECT_EQ(test, max_vector);
+
+ test = Vector2d(-10, -20);
+ test += Vector2d(int_min, int_min);
+ EXPECT_EQ(test, min_vector);
+
+ test = Vector2d();
+ test -= Vector2d(int_max, int_max);
+ EXPECT_EQ(test, Vector2d(-int_max, -int_max));
+
+ test = Vector2d();
+ test -= Vector2d(int_min, int_min);
+ EXPECT_EQ(test, max_vector);
+
+ test = Vector2d(10, 20);
+ test -= Vector2d(int_min, int_min);
+ EXPECT_EQ(test, max_vector);
+
+ test = Vector2d(-10, -20);
+ test -= Vector2d(int_max, int_max);
+ EXPECT_EQ(test, min_vector);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/vector3d_f.cc b/ui/gfx/geometry/vector3d_f.cc
new file mode 100644
index 0000000000..749e330f27
--- /dev/null
+++ b/ui/gfx/geometry/vector3d_f.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/gfx/geometry/vector3d_f.h"
+
+#include <cmath>
+
+#include "base/strings/stringprintf.h"
+#include "ui/gfx/geometry/angle_conversions.h"
+
+namespace {
+const double kEpsilon = 1.0e-6;
+}
+
+namespace gfx {
+
+std::string Vector3dF::ToString() const {
+ return base::StringPrintf("[%f %f %f]", x_, y_, z_);
+}
+
+bool Vector3dF::IsZero() const {
+ return x_ == 0 && y_ == 0 && z_ == 0;
+}
+
+void Vector3dF::Add(const Vector3dF& other) {
+ x_ += other.x_;
+ y_ += other.y_;
+ z_ += other.z_;
+}
+
+void Vector3dF::Subtract(const Vector3dF& other) {
+ x_ -= other.x_;
+ y_ -= other.y_;
+ z_ -= other.z_;
+}
+
+double Vector3dF::LengthSquared() const {
+ return static_cast<double>(x_) * x_ + static_cast<double>(y_) * y_ +
+ static_cast<double>(z_) * z_;
+}
+
+float Vector3dF::Length() const {
+ return static_cast<float>(std::sqrt(LengthSquared()));
+}
+
+void Vector3dF::Scale(float x_scale, float y_scale, float z_scale) {
+ x_ *= x_scale;
+ y_ *= y_scale;
+ z_ *= z_scale;
+}
+
+void Vector3dF::Cross(const Vector3dF& other) {
+ double dx = x_;
+ double dy = y_;
+ double dz = z_;
+ float x = static_cast<float>(dy * other.z() - dz * other.y());
+ float y = static_cast<float>(dz * other.x() - dx * other.z());
+ float z = static_cast<float>(dx * other.y() - dy * other.x());
+ x_ = x;
+ y_ = y;
+ z_ = z;
+}
+
+bool Vector3dF::GetNormalized(Vector3dF* out) const {
+ double length_squared = LengthSquared();
+ *out = *this;
+ if (length_squared < kEpsilon * kEpsilon)
+ return false;
+ out->Scale(1 / sqrt(length_squared));
+ return true;
+}
+
+float DotProduct(const Vector3dF& lhs, const Vector3dF& rhs) {
+ return lhs.x() * rhs.x() + lhs.y() * rhs.y() + lhs.z() * rhs.z();
+}
+
+Vector3dF ScaleVector3d(const Vector3dF& v,
+ float x_scale,
+ float y_scale,
+ float z_scale) {
+ Vector3dF scaled_v(v);
+ scaled_v.Scale(x_scale, y_scale, z_scale);
+ return scaled_v;
+}
+
+float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
+ const gfx::Vector3dF& other) {
+ return gfx::RadToDeg(
+ std::acos(gfx::DotProduct(base, other) / base.Length() / other.Length()));
+}
+
+float ClockwiseAngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
+ const gfx::Vector3dF& other,
+ const gfx::Vector3dF& normal) {
+ float angle = AngleBetweenVectorsInDegrees(base, other);
+ gfx::Vector3dF cross(base);
+ cross.Cross(other);
+
+ // If the dot product of this cross product is normal, it means that the
+ // shortest angle between |base| and |other| was counterclockwise with respect
+ // to the surface represented by |normal| and this angle must be reversed.
+ if (gfx::DotProduct(cross, normal) > 0.0f)
+ angle = 360.0f - angle;
+ return angle;
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/vector3d_f.h b/ui/gfx/geometry/vector3d_f.h
new file mode 100644
index 0000000000..0e5e43713a
--- /dev/null
+++ b/ui/gfx/geometry/vector3d_f.h
@@ -0,0 +1,151 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Defines a simple float vector class. This class is used to indicate a
+// distance in two dimensions between two points. Subtracting two points should
+// produce a vector, and adding a vector to a point produces the point at the
+// vector's distance from the original point.
+
+#ifndef UI_GFX_GEOMETRY_VECTOR3D_F_H_
+#define UI_GFX_GEOMETRY_VECTOR3D_F_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "ui/gfx/geometry/vector2d_f.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+class GFX_EXPORT Vector3dF {
+ public:
+ constexpr Vector3dF() : x_(0), y_(0), z_(0) {}
+ constexpr Vector3dF(float x, float y, float z) : x_(x), y_(y), z_(z) {}
+
+ constexpr explicit Vector3dF(const Vector2dF& other)
+ : x_(other.x()), y_(other.y()), z_(0) {}
+
+ constexpr float x() const { return x_; }
+ void set_x(float x) { x_ = x; }
+
+ constexpr float y() const { return y_; }
+ void set_y(float y) { y_ = y; }
+
+ constexpr float z() const { return z_; }
+ void set_z(float z) { z_ = z; }
+
+ // True if all components of the vector are 0.
+ bool IsZero() const;
+
+ // Add the components of the |other| vector to the current vector.
+ void Add(const Vector3dF& other);
+ // Subtract the components of the |other| vector from the current vector.
+ void Subtract(const Vector3dF& other);
+
+ void operator+=(const Vector3dF& other) { Add(other); }
+ void operator-=(const Vector3dF& other) { Subtract(other); }
+
+ void SetToMin(const Vector3dF& other) {
+ x_ = x_ <= other.x_ ? x_ : other.x_;
+ y_ = y_ <= other.y_ ? y_ : other.y_;
+ z_ = z_ <= other.z_ ? z_ : other.z_;
+ }
+
+ void SetToMax(const Vector3dF& other) {
+ x_ = x_ >= other.x_ ? x_ : other.x_;
+ y_ = y_ >= other.y_ ? y_ : other.y_;
+ z_ = z_ >= other.z_ ? z_ : other.z_;
+ }
+
+ // Gives the square of the diagonal length of the vector.
+ double LengthSquared() const;
+ // Gives the diagonal length of the vector.
+ float Length() const;
+
+ // Scale all components of the vector by |scale|.
+ void Scale(float scale) { Scale(scale, scale, scale); }
+ // Scale the each component of the vector by the given scale factors.
+ void Scale(float x_scale, float y_scale, float z_scale);
+
+ // Take the cross product of this vector with |other| and become the result.
+ void Cross(const Vector3dF& other);
+
+ // |out| is assigned a unit-length vector in the direction of |this| iff
+ // this function returns true. It can return false if |this| is too short.
+ bool GetNormalized(Vector3dF* out) const;
+
+ std::string ToString() const;
+
+ private:
+ float x_;
+ float y_;
+ float z_;
+};
+
+inline bool operator==(const Vector3dF& lhs, const Vector3dF& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z();
+}
+
+inline Vector3dF operator-(const Vector3dF& v) {
+ return Vector3dF(-v.x(), -v.y(), -v.z());
+}
+
+inline Vector3dF operator+(const Vector3dF& lhs, const Vector3dF& rhs) {
+ Vector3dF result = lhs;
+ result.Add(rhs);
+ return result;
+}
+
+inline Vector3dF operator-(const Vector3dF& lhs, const Vector3dF& rhs) {
+ Vector3dF result = lhs;
+ result.Add(-rhs);
+ return result;
+}
+
+// Return the cross product of two vectors.
+inline Vector3dF CrossProduct(const Vector3dF& lhs, const Vector3dF& rhs) {
+ Vector3dF result = lhs;
+ result.Cross(rhs);
+ return result;
+}
+
+// Return the dot product of two vectors.
+GFX_EXPORT float DotProduct(const Vector3dF& lhs, const Vector3dF& rhs);
+
+// Return a vector that is |v| scaled by the given scale factors along each
+// axis.
+GFX_EXPORT Vector3dF ScaleVector3d(const Vector3dF& v,
+ float x_scale,
+ float y_scale,
+ float z_scale);
+
+// Return a vector that is |v| scaled by the components of |s|
+inline Vector3dF ScaleVector3d(const Vector3dF& v, const Vector3dF& s) {
+ return ScaleVector3d(v, s.x(), s.y(), s.z());
+}
+
+// Return a vector that is |v| scaled by the given scale factor.
+inline Vector3dF ScaleVector3d(const Vector3dF& v, float scale) {
+ return ScaleVector3d(v, scale, scale, scale);
+}
+
+// Returns the angle between |base| and |other| in degrees.
+GFX_EXPORT float AngleBetweenVectorsInDegrees(const gfx::Vector3dF& base,
+ const gfx::Vector3dF& other);
+
+// Returns the clockwise angle between |base| and |other| where |normal| is the
+// normal of the virtual surface to measure clockwise according to.
+GFX_EXPORT float ClockwiseAngleBetweenVectorsInDegrees(
+ const gfx::Vector3dF& base,
+ const gfx::Vector3dF& other,
+ const gfx::Vector3dF& normal);
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const Vector3dF& vector, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_VECTOR3D_F_H_
diff --git a/ui/gfx/geometry/vector3d_unittest.cc b/ui/gfx/geometry/vector3d_unittest.cc
new file mode 100644
index 0000000000..12ee757346
--- /dev/null
+++ b/ui/gfx/geometry/vector3d_unittest.cc
@@ -0,0 +1,347 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include <cmath>
+#include <limits>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/vector3d_f.h"
+
+namespace gfx {
+
+TEST(Vector3dTest, IsZero) {
+ gfx::Vector3dF float_zero(0, 0, 0);
+ gfx::Vector3dF float_nonzero(0.1f, -0.1f, 0.1f);
+
+ EXPECT_TRUE(float_zero.IsZero());
+ EXPECT_FALSE(float_nonzero.IsZero());
+}
+
+TEST(Vector3dTest, Add) {
+ gfx::Vector3dF f1(3.1f, 5.1f, 2.7f);
+ gfx::Vector3dF f2(4.3f, -1.3f, 8.1f);
+
+ const struct {
+ gfx::Vector3dF expected;
+ gfx::Vector3dF actual;
+ } float_tests[] = {
+ { gfx::Vector3dF(3.1F, 5.1F, 2.7f), f1 + gfx::Vector3dF() },
+ { gfx::Vector3dF(3.1f + 4.3f, 5.1f - 1.3f, 2.7f + 8.1f), f1 + f2 },
+ { gfx::Vector3dF(3.1f - 4.3f, 5.1f + 1.3f, 2.7f - 8.1f), f1 - f2 }
+ };
+
+ for (size_t i = 0; i < arraysize(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector3dTest, Negative) {
+ const struct {
+ gfx::Vector3dF expected;
+ gfx::Vector3dF actual;
+ } float_tests[] = {
+ { gfx::Vector3dF(-0.0f, -0.0f, -0.0f), -gfx::Vector3dF(0, 0, 0) },
+ { gfx::Vector3dF(-0.3f, -0.3f, -0.3f), -gfx::Vector3dF(0.3f, 0.3f, 0.3f) },
+ { gfx::Vector3dF(0.3f, 0.3f, 0.3f), -gfx::Vector3dF(-0.3f, -0.3f, -0.3f) },
+ { gfx::Vector3dF(-0.3f, 0.3f, -0.3f), -gfx::Vector3dF(0.3f, -0.3f, 0.3f) },
+ { gfx::Vector3dF(0.3f, -0.3f, -0.3f), -gfx::Vector3dF(-0.3f, 0.3f, 0.3f) },
+ { gfx::Vector3dF(-0.3f, -0.3f, 0.3f), -gfx::Vector3dF(0.3f, 0.3f, -0.3f) }
+ };
+
+ for (size_t i = 0; i < arraysize(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector3dTest, Scale) {
+ float triple_values[][6] = {
+ { 4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f },
+ { 4.5f, -1.2f, -1.8f, 3.3f, 5.6f, 4.2f },
+ { 4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 4.2f },
+ { 4.5f, -1.2f -1.8f, 3.3f, 5.6f, 4.2f },
+
+ { 4.5f, 1.2f, 1.8f, 3.3f, -5.6f, -4.2f },
+ { 4.5f, 1.2f, 1.8f, -3.3f, -5.6f, -4.2f },
+ { 4.5f, 1.2f, -1.8f, 3.3f, -5.6f, -4.2f },
+ { 4.5f, 1.2f, -1.8f, -3.3f, -5.6f, -4.2f },
+
+ { -4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, 1.8f, 0, 5.6f, 4.2f },
+ { -4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, -1.8f, 0, 5.6f, 4.2f },
+
+ { -4.5f, 1.2f, 1.8f, 3.3f, 0, 4.2f },
+ { 4.5f, 0, 1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, -1.8f, 3.3f, 0, 4.2f },
+ { 4.5f, 0, -1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 0 },
+ { -4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 0 },
+
+ { 0, 1.2f, 0, 3.3f, 5.6f, 4.2f },
+ { 0, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f }
+ };
+
+ for (size_t i = 0; i < arraysize(triple_values); ++i) {
+ gfx::Vector3dF v(triple_values[i][0],
+ triple_values[i][1],
+ triple_values[i][2]);
+ v.Scale(triple_values[i][3], triple_values[i][4], triple_values[i][5]);
+ EXPECT_EQ(triple_values[i][0] * triple_values[i][3], v.x());
+ EXPECT_EQ(triple_values[i][1] * triple_values[i][4], v.y());
+ EXPECT_EQ(triple_values[i][2] * triple_values[i][5], v.z());
+
+ Vector3dF v2 = ScaleVector3d(
+ gfx::Vector3dF(triple_values[i][0],
+ triple_values[i][1],
+ triple_values[i][2]),
+ triple_values[i][3], triple_values[i][4], triple_values[i][5]);
+ EXPECT_EQ(triple_values[i][0] * triple_values[i][3], v2.x());
+ EXPECT_EQ(triple_values[i][1] * triple_values[i][4], v2.y());
+ EXPECT_EQ(triple_values[i][2] * triple_values[i][5], v2.z());
+ }
+
+ float single_values[][4] = {
+ { 4.5f, 1.2f, 1.8f, 3.3f },
+ { 4.5f, -1.2f, 1.8f, 3.3f },
+ { 4.5f, 1.2f, -1.8f, 3.3f },
+ { 4.5f, -1.2f, -1.8f, 3.3f },
+ { -4.5f, 1.2f, 3.3f },
+ { -4.5f, 1.2f, 0 },
+ { -4.5f, 1.2f, 1.8f, 3.3f },
+ { -4.5f, 1.2f, 1.8f, 0 },
+ { 4.5f, 0, 1.8f, 3.3f },
+ { 0, 1.2f, 1.8f, 3.3f },
+ { 4.5f, 0, 1.8f, 3.3f },
+ { 0, 1.2f, 1.8f, 3.3f },
+ { 4.5f, 1.2f, 0, 3.3f },
+ { 4.5f, 1.2f, 0, 3.3f }
+ };
+
+ for (size_t i = 0; i < arraysize(single_values); ++i) {
+ gfx::Vector3dF v(single_values[i][0],
+ single_values[i][1],
+ single_values[i][2]);
+ v.Scale(single_values[i][3]);
+ EXPECT_EQ(single_values[i][0] * single_values[i][3], v.x());
+ EXPECT_EQ(single_values[i][1] * single_values[i][3], v.y());
+ EXPECT_EQ(single_values[i][2] * single_values[i][3], v.z());
+
+ Vector3dF v2 = ScaleVector3d(
+ gfx::Vector3dF(single_values[i][0],
+ single_values[i][1],
+ single_values[i][2]),
+ single_values[i][3]);
+ EXPECT_EQ(single_values[i][0] * single_values[i][3], v2.x());
+ EXPECT_EQ(single_values[i][1] * single_values[i][3], v2.y());
+ EXPECT_EQ(single_values[i][2] * single_values[i][3], v2.z());
+ }
+}
+
+TEST(Vector3dTest, Length) {
+ float float_values[][3] = {
+ { 0, 0, 0 },
+ { 10.5f, 20.5f, 8.5f },
+ { 20.5f, 10.5f, 8.5f },
+ { 8.5f, 20.5f, 10.5f },
+ { 10.5f, 8.5f, 20.5f },
+ { -10.5f, -20.5f, -8.5f },
+ { -20.5f, 10.5f, -8.5f },
+ { -8.5f, -20.5f, -10.5f },
+ { -10.5f, -8.5f, -20.5f },
+ { 10.5f, -20.5f, 8.5f },
+ { -10.5f, 20.5f, 8.5f },
+ { 10.5f, -20.5f, -8.5f },
+ { -10.5f, 20.5f, -8.5f },
+ // A large vector that fails if the Length function doesn't use
+ // double precision internally.
+ { 1236278317862780234892374893213178027.12122348904204230f,
+ 335890352589839028212313231225425134332.38123f,
+ 27861786423846742743236423478236784678.236713617231f }
+ };
+
+ for (size_t i = 0; i < arraysize(float_values); ++i) {
+ double v0 = float_values[i][0];
+ double v1 = float_values[i][1];
+ double v2 = float_values[i][2];
+ double length_squared =
+ static_cast<double>(v0) * v0 +
+ static_cast<double>(v1) * v1 +
+ static_cast<double>(v2) * v2;
+ double length = std::sqrt(length_squared);
+ gfx::Vector3dF vector(v0, v1, v2);
+ EXPECT_DOUBLE_EQ(length_squared, vector.LengthSquared());
+ EXPECT_FLOAT_EQ(static_cast<float>(length), vector.Length());
+ }
+}
+
+TEST(Vector3dTest, DotProduct) {
+ const struct {
+ float expected;
+ gfx::Vector3dF input1;
+ gfx::Vector3dF input2;
+ } tests[] = {
+ { 0, gfx::Vector3dF(1, 0, 0), gfx::Vector3dF(0, 1, 1) },
+ { 0, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(1, 0, 1) },
+ { 0, gfx::Vector3dF(0, 0, 1), gfx::Vector3dF(1, 1, 0) },
+
+ { 3, gfx::Vector3dF(1, 1, 1), gfx::Vector3dF(1, 1, 1) },
+
+ { 1.2f, gfx::Vector3dF(1.2f, -1.2f, 1.2f), gfx::Vector3dF(1, 1, 1) },
+ { 1.2f, gfx::Vector3dF(1, 1, 1), gfx::Vector3dF(1.2f, -1.2f, 1.2f) },
+
+ { 38.72f,
+ gfx::Vector3dF(1.1f, 2.2f, 3.3f), gfx::Vector3dF(4.4f, 5.5f, 6.6f) }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ float actual = gfx::DotProduct(tests[i].input1, tests[i].input2);
+ EXPECT_EQ(tests[i].expected, actual);
+ }
+}
+
+TEST(Vector3dTest, CrossProduct) {
+ const struct {
+ gfx::Vector3dF expected;
+ gfx::Vector3dF input1;
+ gfx::Vector3dF input2;
+ } tests[] = {
+ { Vector3dF(), Vector3dF(), Vector3dF(1, 1, 1) },
+ { Vector3dF(), Vector3dF(1, 1, 1), Vector3dF() },
+ { Vector3dF(), Vector3dF(1, 1, 1), Vector3dF(1, 1, 1) },
+ { Vector3dF(),
+ Vector3dF(1.6f, 10.6f, -10.6f),
+ Vector3dF(1.6f, 10.6f, -10.6f) },
+
+ { Vector3dF(1, -1, 0), Vector3dF(1, 1, 1), Vector3dF(0, 0, 1) },
+ { Vector3dF(-1, 0, 1), Vector3dF(1, 1, 1), Vector3dF(0, 1, 0) },
+ { Vector3dF(0, 1, -1), Vector3dF(1, 1, 1), Vector3dF(1, 0, 0) },
+
+ { Vector3dF(-1, 1, 0), Vector3dF(0, 0, 1), Vector3dF(1, 1, 1) },
+ { Vector3dF(1, 0, -1), Vector3dF(0, 1, 0), Vector3dF(1, 1, 1) },
+ { Vector3dF(0, -1, 1), Vector3dF(1, 0, 0), Vector3dF(1, 1, 1) }
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ SCOPED_TRACE(i);
+ Vector3dF actual = gfx::CrossProduct(tests[i].input1, tests[i].input2);
+ EXPECT_EQ(tests[i].expected.ToString(), actual.ToString());
+ }
+}
+
+TEST(Vector3dFTest, ClampVector3dF) {
+ Vector3dF a;
+
+ a = Vector3dF(3.5f, 5.5f, 7.5f);
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(2, 4.5f, 6.5f));
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(3.5f, 5.5f, 7.5f));
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(4.5f, 2, 6.5f));
+ EXPECT_EQ(Vector3dF(4.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(3.5f, 6.5f, 6.5f));
+ EXPECT_EQ(Vector3dF(4.5f, 6.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(3.5f, 5.5f, 8.5f));
+ EXPECT_EQ(Vector3dF(4.5f, 6.5f, 8.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(8.5f, 10.5f, 12.5f));
+ EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString());
+
+ a.SetToMin(Vector3dF(9.5f, 11.5f, 13.5f));
+ EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(8.5f, 10.5f, 12.5f));
+ EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(7.5f, 11.5f, 13.5f));
+ EXPECT_EQ(Vector3dF(7.5f, 10.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(9.5f, 9.5f, 13.5f));
+ EXPECT_EQ(Vector3dF(7.5f, 9.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(9.5f, 11.5f, 11.5f));
+ EXPECT_EQ(Vector3dF(7.5f, 9.5f, 11.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(3.5f, 5.5f, 7.5f));
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+}
+
+TEST(Vector3dTest, AngleBetweenVectorsInDegress) {
+ const struct {
+ float expected;
+ gfx::Vector3dF input1;
+ gfx::Vector3dF input2;
+ } tests[] = {
+ {0, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 1, 0)},
+ {90, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 0, 1)},
+ {45,
+ gfx::Vector3dF(0, 1, 0),
+ gfx::Vector3dF(0, 0.70710678188f, 0.70710678188f)},
+ {180, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, -1, 0)},
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ float actual =
+ gfx::AngleBetweenVectorsInDegrees(tests[i].input1, tests[i].input2);
+ EXPECT_FLOAT_EQ(tests[i].expected, actual);
+ actual =
+ gfx::AngleBetweenVectorsInDegrees(tests[i].input2, tests[i].input1);
+ EXPECT_FLOAT_EQ(tests[i].expected, actual);
+ }
+}
+
+TEST(Vector3dTest, ClockwiseAngleBetweenVectorsInDegress) {
+ const struct {
+ float expected;
+ gfx::Vector3dF input1;
+ gfx::Vector3dF input2;
+ } tests[] = {
+ {0, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 1, 0)},
+ {90, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 0, -1)},
+ {45,
+ gfx::Vector3dF(0, -1, 0),
+ gfx::Vector3dF(0, -0.70710678188f, 0.70710678188f)},
+ {180, gfx::Vector3dF(0, -1, 0), gfx::Vector3dF(0, 1, 0)},
+ {270, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(0, 0, 1)},
+ };
+
+ const gfx::Vector3dF normal_vector(1.0f, 0.0f, 0.0f);
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ float actual = gfx::ClockwiseAngleBetweenVectorsInDegrees(
+ tests[i].input1, tests[i].input2, normal_vector);
+ EXPECT_FLOAT_EQ(tests[i].expected, actual);
+ actual = -gfx::ClockwiseAngleBetweenVectorsInDegrees(
+ tests[i].input2, tests[i].input1, normal_vector);
+ if (actual < 0.0f)
+ actual += 360.0f;
+ EXPECT_FLOAT_EQ(tests[i].expected, actual);
+ }
+}
+
+TEST(Vector3dTest, GetNormalized) {
+ const struct {
+ bool expected;
+ gfx::Vector3dF v;
+ gfx::Vector3dF normalized;
+ } tests[] = {
+ {false, gfx::Vector3dF(0, 0, 0), gfx::Vector3dF(0, 0, 0)},
+ {false,
+ gfx::Vector3dF(std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::min()),
+ gfx::Vector3dF(std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::min())},
+ {true, gfx::Vector3dF(1, 0, 0), gfx::Vector3dF(1, 0, 0)},
+ {true, gfx::Vector3dF(std::numeric_limits<float>::max(), 0, 0),
+ gfx::Vector3dF(1, 0, 0)},
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ gfx::Vector3dF n;
+ EXPECT_EQ(tests[i].expected, tests[i].v.GetNormalized(&n));
+ EXPECT_EQ(tests[i].normalized.ToString(), n.ToString());
+ }
+}
+
+} // namespace gfx