Commit 4590cc04 authored by vollick@chromium.org's avatar vollick@chromium.org

Allow the computation of inflated bounds for non matrix transform operations.

This CL expands the list of transform operations for which we may compute inflated
bounds for animation.

R=hartmanng@chromium.org,ajuma@chromium.org
BUG=

Review URL: https://codereview.chromium.org/55763004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@232889 0039d316-1c4b-4281-b951-d872f2087c98
parent 61a035ae
...@@ -1260,9 +1260,11 @@ TEST(LayerAnimationControllerTest, AnimatedBounds) { ...@@ -1260,9 +1260,11 @@ TEST(LayerAnimationControllerTest, AnimatedBounds) {
scoped_ptr<KeyframedTransformAnimationCurve> curve3( scoped_ptr<KeyframedTransformAnimationCurve> curve3(
KeyframedTransformAnimationCurve::Create()); KeyframedTransformAnimationCurve::Create());
TransformOperations operations3; TransformOperations operations3;
gfx::Transform transform3;
transform3.Scale3d(1.0, 2.0, 3.0);
curve3->AddKeyframe(TransformKeyframe::Create( curve3->AddKeyframe(TransformKeyframe::Create(
0.0, operations3, scoped_ptr<TimingFunction>())); 0.0, operations3, scoped_ptr<TimingFunction>()));
operations3.AppendSkew(1.0, 2.0); operations3.AppendMatrix(transform3);
curve3->AddKeyframe(TransformKeyframe::Create( curve3->AddKeyframe(TransformKeyframe::Create(
1.0, operations3, scoped_ptr<TimingFunction>())); 1.0, operations3, scoped_ptr<TimingFunction>()));
animation = Animation::Create( animation = Animation::Create(
......
...@@ -13,7 +13,9 @@ ...@@ -13,7 +13,9 @@
#include "base/logging.h" #include "base/logging.h"
#include "cc/animation/transform_operation.h" #include "cc/animation/transform_operation.h"
#include "cc/animation/transform_operations.h"
#include "ui/gfx/box_f.h" #include "ui/gfx/box_f.h"
#include "ui/gfx/transform_util.h"
#include "ui/gfx/vector3d_f.h" #include "ui/gfx/vector3d_f.h"
namespace { namespace {
...@@ -198,30 +200,6 @@ bool TransformOperation::BlendTransformOperations( ...@@ -198,30 +200,6 @@ bool TransformOperation::BlendTransformOperations(
return true; return true;
} }
static void ApplyScaleToBox(float x_scale,
float y_scale,
float z_scale,
gfx::BoxF* box) {
if (x_scale < 0)
box->set_x(-box->right());
if (y_scale < 0)
box->set_y(-box->bottom());
if (z_scale < 0)
box->set_z(-box->front());
box->Scale(std::abs(x_scale), std::abs(y_scale), std::abs(z_scale));
}
static void UnionBoxWithZeroScale(gfx::BoxF* box) {
float min_x = std::min(box->x(), 0.f);
float min_y = std::min(box->y(), 0.f);
float min_z = std::min(box->z(), 0.f);
float max_x = std::max(box->right(), 0.f);
float max_y = std::max(box->bottom(), 0.f);
float max_z = std::max(box->front(), 0.f);
*box = gfx::BoxF(
min_x, min_y, min_z, max_x - min_x, max_y - min_y, max_z - min_z);
}
// If p = (px, py) is a point in the plane being rotated about (0, 0, nz), this // If p = (px, py) is a point in the plane being rotated about (0, 0, nz), this
// function computes the angles we would have to rotate from p to get to // function computes the angles we would have to rotate from p to get to
// (length(p), 0), (-length(p), 0), (0, length(p)), (0, -length(p)). If nz is // (length(p), 0), (-length(p), 0), (0, length(p)), (0, -length(p)). If nz is
...@@ -250,20 +228,6 @@ static float DegreesToRadians(float degrees) { ...@@ -250,20 +228,6 @@ static float DegreesToRadians(float degrees) {
return (M_PI * degrees) / 180.f; return (M_PI * degrees) / 180.f;
} }
// Div by zero doesn't always result in Inf as you might hope, so we'll do this
// explicitly here.
static float SafeDivide(float numerator, float denominator) {
if (numerator == 0.f)
return 0.f;
if (denominator == 0.f) {
return numerator > 0.f ? std::numeric_limits<float>::infinity()
: -std::numeric_limits<float>::infinity();
}
return numerator / denominator;
}
static void BoundingBoxForArc(const gfx::Point3F& point, static void BoundingBoxForArc(const gfx::Point3F& point,
const TransformOperation* from, const TransformOperation* from,
const TransformOperation* to, const TransformOperation* to,
...@@ -291,6 +255,16 @@ static void BoundingBoxForArc(const gfx::Point3F& point, ...@@ -291,6 +255,16 @@ static void BoundingBoxForArc(const gfx::Point3F& point,
SkMScalar from_angle = from ? from->rotate.angle : 0.f; SkMScalar from_angle = from ? from->rotate.angle : 0.f;
SkMScalar to_angle = to ? to->rotate.angle : 0.f; SkMScalar to_angle = to ? to->rotate.angle : 0.f;
// If the axes of rotation are pointing in opposite directions, we need to
// flip one of the angles. Note, if both |from| and |to| exist, then axis will
// correspond to |from|.
if (from && to) {
gfx::Vector3dF other_axis(
to->rotate.axis.x, to->rotate.axis.y, to->rotate.axis.z);
if (gfx::DotProduct(axis, other_axis) < 0.f)
to_angle *= -1.f;
}
float min_degrees = float min_degrees =
SkMScalarToFloat(BlendSkMScalars(from_angle, to_angle, min_progress)); SkMScalarToFloat(BlendSkMScalars(from_angle, to_angle, min_progress));
float max_degrees = float max_degrees =
...@@ -360,16 +334,11 @@ static void BoundingBoxForArc(const gfx::Point3F& point, ...@@ -360,16 +334,11 @@ static void BoundingBoxForArc(const gfx::Point3F& point,
double phi_x = atan2(gfx::DotProduct(v2, vx), gfx::DotProduct(v1, vx)); double phi_x = atan2(gfx::DotProduct(v2, vx), gfx::DotProduct(v1, vx));
double phi_z = atan2(gfx::DotProduct(v2, vz), gfx::DotProduct(v1, vz)); double phi_z = atan2(gfx::DotProduct(v2, vz), gfx::DotProduct(v1, vz));
// NB: it is fine if the denominators here are zero and these values go to candidates[0] = atan2(normal.y(), normal.x() * normal.z()) + phi_x;
// infinity; atan can handle it.
double tan_theta1 = SafeDivide(normal.y(), (normal.x() * normal.z()));
double tan_theta2 = SafeDivide(-normal.z(), (normal.x() * normal.y()));
candidates[0] = atan(tan_theta1) + phi_x;
candidates[1] = candidates[0] + M_PI; candidates[1] = candidates[0] + M_PI;
candidates[2] = atan(tan_theta2) + phi_x; candidates[2] = atan2(-normal.z(), normal.x() * normal.y()) + phi_x;
candidates[3] = candidates[2] + M_PI; candidates[3] = candidates[2] + M_PI;
candidates[4] = atan(-tan_theta1) + phi_z; candidates[4] = atan2(normal.y(), -normal.x() * normal.z()) + phi_z;
candidates[5] = candidates[4] + M_PI; candidates[5] = candidates[4] + M_PI;
} }
...@@ -415,78 +384,36 @@ bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box, ...@@ -415,78 +384,36 @@ bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box,
interpolation_type = to->type; interpolation_type = to->type;
switch (interpolation_type) { switch (interpolation_type) {
case TransformOperation::TransformOperationTranslate: { case TransformOperation::TransformOperationIdentity:
SkMScalar from_x, from_y, from_z;
if (is_identity_from) {
from_x = from_y = from_z = 0.0;
} else {
from_x = from->translate.x;
from_y = from->translate.y;
from_z = from->translate.z;
}
SkMScalar to_x, to_y, to_z;
if (is_identity_to) {
to_x = to_y = to_z = 0.0;
} else {
to_x = to->translate.x;
to_y = to->translate.y;
to_z = to->translate.z;
}
*bounds = box; *bounds = box;
*bounds += gfx::Vector3dF(BlendSkMScalars(from_x, to_x, min_progress),
BlendSkMScalars(from_y, to_y, min_progress),
BlendSkMScalars(from_z, to_z, min_progress));
gfx::BoxF bounds_max = box;
bounds_max += gfx::Vector3dF(BlendSkMScalars(from_x, to_x, max_progress),
BlendSkMScalars(from_y, to_y, max_progress),
BlendSkMScalars(from_z, to_z, max_progress));
bounds->Union(bounds_max);
return true; return true;
} case TransformOperation::TransformOperationTranslate:
case TransformOperation::TransformOperationSkew:
case TransformOperation::TransformOperationPerspective:
case TransformOperation::TransformOperationScale: { case TransformOperation::TransformOperationScale: {
SkMScalar from_x, from_y, from_z; gfx::Transform from_transform;
if (is_identity_from) { gfx::Transform to_transform;
from_x = from_y = from_z = 1.0; if (!BlendTransformOperations(from, to, min_progress, &from_transform) ||
} else { !BlendTransformOperations(from, to, max_progress, &to_transform))
from_x = from->scale.x; return false;
from_y = from->scale.y;
from_z = from->scale.z;
}
SkMScalar to_x, to_y, to_z;
if (is_identity_to) {
to_x = to_y = to_z = 1.0;
} else {
to_x = to->scale.x;
to_y = to->scale.y;
to_z = to->scale.z;
}
*bounds = box; *bounds = box;
ApplyScaleToBox( from_transform.TransformBox(bounds);
SkMScalarToFloat(BlendSkMScalars(from_x, to_x, min_progress)),
SkMScalarToFloat(BlendSkMScalars(from_y, to_y, min_progress)), gfx::BoxF to_box = box;
SkMScalarToFloat(BlendSkMScalars(from_z, to_z, min_progress)), to_transform.TransformBox(&to_box);
bounds); bounds->ExpandTo(to_box);
gfx::BoxF bounds_max = box;
ApplyScaleToBox(
SkMScalarToFloat(BlendSkMScalars(from_x, to_x, max_progress)),
SkMScalarToFloat(BlendSkMScalars(from_y, to_y, max_progress)),
SkMScalarToFloat(BlendSkMScalars(from_z, to_z, max_progress)),
&bounds_max);
if (!bounds->IsEmpty() && !bounds_max.IsEmpty()) {
bounds->Union(bounds_max);
} else if (!bounds->IsEmpty()) {
UnionBoxWithZeroScale(bounds);
} else if (!bounds_max.IsEmpty()) {
UnionBoxWithZeroScale(&bounds_max);
*bounds = bounds_max;
}
return true; return true;
} }
case TransformOperation::TransformOperationIdentity:
*bounds = box;
return true;
case TransformOperation::TransformOperationRotate: { case TransformOperation::TransformOperationRotate: {
SkMScalar axis_x = 0;
SkMScalar axis_y = 0;
SkMScalar axis_z = 1;
SkMScalar from_angle = 0;
if (!ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle))
return false;
bool first_point = true; bool first_point = true;
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
gfx::Point3F corner = box.origin(); gfx::Point3F corner = box.origin();
...@@ -504,8 +431,6 @@ bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box, ...@@ -504,8 +431,6 @@ bool TransformOperation::BlendedBoundsForBox(const gfx::BoxF& box,
} }
return true; return true;
} }
case TransformOperation::TransformOperationSkew:
case TransformOperation::TransformOperationPerspective:
case TransformOperation::TransformOperationMatrix: case TransformOperation::TransformOperationMatrix:
return false; return false;
} }
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/animation/tween.h" #include "ui/gfx/animation/tween.h"
#include "ui/gfx/box_f.h" #include "ui/gfx/box_f.h"
#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/vector3d_f.h" #include "ui/gfx/vector3d_f.h"
namespace cc { namespace cc {
...@@ -886,6 +887,30 @@ TEST(TransformOperationTest, BlendedBoundsForRotationAllExtrema) { ...@@ -886,6 +887,30 @@ TEST(TransformOperationTest, BlendedBoundsForRotationAllExtrema) {
bounds.ToString()); bounds.ToString());
} }
TEST(TransformOperationTest, BlendedBoundsForRotationDifferentAxes) {
// We can handle rotations about a single axis. If the axes are different,
// we revert to matrix interpolation for which inflated bounds cannot be
// computed.
TransformOperations operations_from;
operations_from.AppendRotate(1.f, 1.f, 1.f, 30.f);
TransformOperations operations_to_same;
operations_to_same.AppendRotate(1.f, 1.f, 1.f, 390.f);
TransformOperations operations_to_opposite;
operations_to_opposite.AppendRotate(-1.f, -1.f, -1.f, 390.f);
TransformOperations operations_to_different;
operations_to_different.AppendRotate(1.f, 3.f, 1.f, 390.f);
gfx::BoxF box(1.f, 0.f, 0.f, 0.f, 0.f, 0.f);
gfx::BoxF bounds;
EXPECT_TRUE(operations_to_same.BlendedBoundsForBox(
box, operations_from, 0.f, 1.f, &bounds));
EXPECT_TRUE(operations_to_opposite.BlendedBoundsForBox(
box, operations_from, 0.f, 1.f, &bounds));
EXPECT_FALSE(operations_to_different.BlendedBoundsForBox(
box, operations_from, 0.f, 1.f, &bounds));
}
TEST(TransformOperationTest, BlendedBoundsForRotationPointOnAxis) { TEST(TransformOperationTest, BlendedBoundsForRotationPointOnAxis) {
// Checks that if the point to rotate is sitting on the axis of rotation, that // Checks that if the point to rotate is sitting on the axis of rotation, that
// it does not get affected. // it does not get affected.
...@@ -968,7 +993,80 @@ struct TestProgress { ...@@ -968,7 +993,80 @@ struct TestProgress {
float max_progress; float max_progress;
}; };
TEST(TransformOperationsTest, BlendedBoundsForRotationEmpiricalTests) { static void ExpectBoxesApproximatelyEqual(const gfx::BoxF& lhs,
const gfx::BoxF& rhs,
float tolerance) {
EXPECT_NEAR(lhs.x(), rhs.x(), tolerance);
EXPECT_NEAR(lhs.y(), rhs.y(), tolerance);
EXPECT_NEAR(lhs.z(), rhs.z(), tolerance);
EXPECT_NEAR(lhs.width(), rhs.width(), tolerance);
EXPECT_NEAR(lhs.height(), rhs.height(), tolerance);
EXPECT_NEAR(lhs.depth(), rhs.depth(), tolerance);
}
static void EmpiricallyTestBounds(const TransformOperations& from,
const TransformOperations& to,
SkMScalar min_progress,
SkMScalar max_progress,
bool test_containment_only) {
gfx::BoxF box(200.f, 500.f, 100.f, 100.f, 300.f, 200.f);
gfx::BoxF bounds;
EXPECT_TRUE(
to.BlendedBoundsForBox(box, from, min_progress, max_progress, &bounds));
bool first_time = true;
gfx::BoxF empirical_bounds;
static const size_t kNumSteps = 10;
for (size_t step = 0; step < kNumSteps; ++step) {
float t = step / (kNumSteps - 1.f);
t = gfx::Tween::FloatValueBetween(t, min_progress, max_progress);
gfx::Transform partial_transform = to.Blend(from, t);
gfx::BoxF transformed = box;
partial_transform.TransformBox(&transformed);
if (first_time) {
empirical_bounds = transformed;
first_time = false;
} else {
empirical_bounds.Union(transformed);
}
}
if (test_containment_only) {
gfx::BoxF unified_bounds = bounds;
unified_bounds.Union(empirical_bounds);
// Convert to the screen space rects these boxes represent.
gfx::Rect bounds_rect = ToEnclosingRect(
gfx::RectF(bounds.x(), bounds.y(), bounds.width(), bounds.height()));
gfx::Rect unified_bounds_rect =
ToEnclosingRect(gfx::RectF(unified_bounds.x(),
unified_bounds.y(),
unified_bounds.width(),
unified_bounds.height()));
EXPECT_EQ(bounds_rect.ToString(), unified_bounds_rect.ToString());
} else {
// Our empirical estimate will be a little rough since we're only doing
// 100 samples.
static const float kTolerance = 1e-2f;
ExpectBoxesApproximatelyEqual(empirical_bounds, bounds, kTolerance);
}
}
static void EmpiricallyTestBoundsEquality(const TransformOperations& from,
const TransformOperations& to,
SkMScalar min_progress,
SkMScalar max_progress) {
EmpiricallyTestBounds(from, to, min_progress, max_progress, false);
}
static void EmpiricallyTestBoundsContainment(const TransformOperations& from,
const TransformOperations& to,
SkMScalar min_progress,
SkMScalar max_progress) {
EmpiricallyTestBounds(from, to, min_progress, max_progress, true);
}
TEST(TransformOperationTest, BlendedBoundsForRotationEmpiricalTests) {
// Sets up various axis angle combinations, computes the bounding box and // Sets up various axis angle combinations, computes the bounding box and
// empirically tests that the transformed bounds are indeed contained by the // empirically tests that the transformed bounds are indeed contained by the
// computed bounding box. // computed bounding box.
...@@ -1012,7 +1110,6 @@ TEST(TransformOperationsTest, BlendedBoundsForRotationEmpiricalTests) { ...@@ -1012,7 +1110,6 @@ TEST(TransformOperationsTest, BlendedBoundsForRotationEmpiricalTests) {
{ -.25f, 1.25f }, { -.25f, 1.25f },
}; };
size_t num_steps = 150;
for (size_t i = 0; i < arraysize(axes); ++i) { for (size_t i = 0; i < arraysize(axes); ++i) {
for (size_t j = 0; j < arraysize(angles); ++j) { for (size_t j = 0; j < arraysize(angles); ++j) {
for (size_t k = 0; k < arraysize(progress); ++k) { for (size_t k = 0; k < arraysize(progress); ++k) {
...@@ -1023,48 +1120,10 @@ TEST(TransformOperationsTest, BlendedBoundsForRotationEmpiricalTests) { ...@@ -1023,48 +1120,10 @@ TEST(TransformOperationsTest, BlendedBoundsForRotationEmpiricalTests) {
operations_from.AppendRotate(x, y, z, angles[j].theta_from); operations_from.AppendRotate(x, y, z, angles[j].theta_from);
TransformOperations operations_to; TransformOperations operations_to;
operations_to.AppendRotate(x, y, z, angles[j].theta_to); operations_to.AppendRotate(x, y, z, angles[j].theta_to);
EmpiricallyTestBoundsContainment(operations_from,
gfx::BoxF box(2.f, 5.f, 6.f, 1.f, 3.f, 2.f); operations_to,
gfx::BoxF bounds; progress[k].min_progress,
progress[k].max_progress);
EXPECT_TRUE(operations_to.BlendedBoundsForBox(box,
operations_from,
progress[k].min_progress,
progress[k].max_progress,
&bounds));
bool first_point = true;
gfx::BoxF empirical_bounds;
for (size_t step = 0; step < num_steps; ++step) {
float t = step / (num_steps - 1.f);
t = gfx::Tween::FloatValueBetween(
t, progress[k].min_progress, progress[k].max_progress);
gfx::Transform partial_rotation =
operations_to.Blend(operations_from, t);
for (int corner = 0; corner < 8; ++corner) {
gfx::Point3F point = box.origin();
point += gfx::Vector3dF(corner & 1 ? box.width() : 0.f,
corner & 2 ? box.height() : 0.f,
corner & 4 ? box.depth() : 0.f);
partial_rotation.TransformPoint(&point);
if (first_point) {
empirical_bounds.set_origin(point);
first_point = false;
} else {
empirical_bounds.ExpandTo(point);
}
}
}
// Our empirical estimate will be a little rough since we're only doing
// 100 samples.
static const float kTolerance = 1e-2f;
EXPECT_NEAR(empirical_bounds.x(), bounds.x(), kTolerance);
EXPECT_NEAR(empirical_bounds.y(), bounds.y(), kTolerance);
EXPECT_NEAR(empirical_bounds.z(), bounds.z(), kTolerance);
EXPECT_NEAR(empirical_bounds.width(), bounds.width(), kTolerance);
EXPECT_NEAR(empirical_bounds.height(), bounds.height(), kTolerance);
EXPECT_NEAR(empirical_bounds.depth(), bounds.depth(), kTolerance);
} }
} }
} }
...@@ -1097,6 +1156,69 @@ TEST(TransformOperationTest, PerspectiveMatrixAndTransformBlendingEquivalency) { ...@@ -1097,6 +1156,69 @@ TEST(TransformOperationTest, PerspectiveMatrixAndTransformBlendingEquivalency) {
} }
} }
struct TestPerspectiveDepths {
float from_depth;
float to_depth;
};
TEST(TransformOperationTest, BlendedBoundsForPerspective) {
TestPerspectiveDepths perspective_depths[] = {
{ 600.f, 400.f },
{ 800.f, 1000.f },
{ 800.f, std::numeric_limits<float>::infinity() },
};
TestProgress progress[] = {
{ 0.f, 1.f },
{ -0.1f, 1.1f },
};
for (size_t i = 0; i < arraysize(perspective_depths); ++i) {
for (size_t j = 0; j < arraysize(progress); ++j) {
TransformOperations operations_from;
operations_from.AppendPerspective(perspective_depths[i].from_depth);
TransformOperations operations_to;
operations_to.AppendPerspective(perspective_depths[i].to_depth);
EmpiricallyTestBoundsEquality(operations_from,
operations_to,
progress[j].min_progress,
progress[j].max_progress);
}
}
}
struct TestSkews {
float from_x;
float from_y;
float to_x;
float to_y;
};
TEST(TransformOperationTest, BlendedBoundsForSkew) {
TestSkews skews[] = {
{ 1.f, 0.5f, 0.5f, 1.f },
{ 2.f, 1.f, 0.5f, 0.5f },
};
TestProgress progress[] = {
{ 0.f, 1.f },
{ -0.1f, 1.1f },
};
for (size_t i = 0; i < arraysize(skews); ++i) {
for (size_t j = 0; j < arraysize(progress); ++j) {
TransformOperations operations_from;
operations_from.AppendSkew(skews[i].from_x, skews[i].from_y);
TransformOperations operations_to;
operations_to.AppendSkew(skews[i].to_x, skews[i].to_y);
EmpiricallyTestBoundsEquality(operations_from,
operations_to,
progress[j].min_progress,
progress[j].max_progress);
}
}
}
TEST(TransformOperationTest, BlendedBoundsForSequence) { TEST(TransformOperationTest, BlendedBoundsForSequence) {
TransformOperations operations_from; TransformOperations operations_from;
operations_from.AppendTranslate(2.0, 4.0, -1.0); operations_from.AppendTranslate(2.0, 4.0, -1.0);
......
...@@ -50,14 +50,17 @@ void BoxF::Union(const BoxF& box) { ...@@ -50,14 +50,17 @@ void BoxF::Union(const BoxF& box) {
} }
if (box.IsEmpty()) if (box.IsEmpty())
return; return;
ExpandTo(box);
ExpandTo(box.origin(), gfx::Point3F(box.right(), box.bottom(), box.front()));
} }
void BoxF::ExpandTo(const Point3F& point) { void BoxF::ExpandTo(const Point3F& point) {
ExpandTo(point, 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 UnionBoxes(const BoxF& a, const BoxF& b) {
BoxF result = a; BoxF result = a;
result.Union(b); result.Union(b);
......
...@@ -99,6 +99,11 @@ class GFX_EXPORT BoxF { ...@@ -99,6 +99,11 @@ class GFX_EXPORT BoxF {
// |origin_|. // |origin_|.
void ExpandTo(const Point3F& point); 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: private:
// Expands the box to contain the two given points. It is required that each // 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 // component of |min| is less than or equal to the corresponding component in
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment