diff options
Diffstat (limited to 'ui/gfx')
43 files changed, 6277 insertions, 135 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 diff --git a/ui/gfx/range/BUILD.gn b/ui/gfx/range/BUILD.gn deleted file mode 100644 index 2a2568a287..0000000000 --- a/ui/gfx/range/BUILD.gn +++ /dev/null @@ -1,35 +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("//build/config/jumbo.gni") - -jumbo_component("range") { - sources = [ - "gfx_range_export.h", - "range.cc", - "range.h", - "range_f.cc", - "range_f.h", - "range_mac.mm", - "range_win.cc", - ] - - if (is_ios) { - set_sources_assignment_filter([]) - sources += [ "range_mac.mm" ] - set_sources_assignment_filter(sources_assignment_filter) - } - - configs += [ - # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. - "//build/config/compiler:no_size_t_to_int_warning", - ] - - defines = [ "GFX_RANGE_IMPLEMENTATION" ] - - deps = [ - "//base", - "//ui/gfx:gfx_export", - ] -} diff --git a/ui/gfx/range/mojo/BUILD.gn b/ui/gfx/range/mojo/BUILD.gn deleted file mode 100644 index b6d458dbc9..0000000000 --- a/ui/gfx/range/mojo/BUILD.gn +++ /dev/null @@ -1,49 +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 = [ - "range.mojom", - ] -} - -mojom("test_interfaces") { - sources = [ - "range_traits_test_service.mojom", - ] - - public_deps = [ - ":mojo", - ] -} - -source_set("unit_test") { - testonly = true - - sources = [ - "range_struct_traits_unittest.cc", - ] - - deps = [ - ":test_interfaces", - "//base", - "//mojo/public/cpp/bindings", - "//testing/gtest", - "//ui/gfx/range", - ] -} - -source_set("struct_traits") { - sources = [ - "range_struct_traits.h", - ] - public_deps = [ - ":mojo_shared_cpp_sources", - "//ui/gfx/range", - ] -} |