diff options
Diffstat (limited to 'o3d/core/cross/transform_test.cc')
-rw-r--r-- | o3d/core/cross/transform_test.cc | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/o3d/core/cross/transform_test.cc b/o3d/core/cross/transform_test.cc new file mode 100644 index 0000000..c7d9869 --- /dev/null +++ b/o3d/core/cross/transform_test.cc @@ -0,0 +1,605 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// Tests functionality defined in transform.cc/h + +#include <algorithm> + +#include "core/cross/client.h" +#include "core/cross/shape.h" +#include "core/cross/primitive.h" +#include "core/cross/material.h" +#include "tests/common/win/testing_common.h" + +namespace o3d { + +// Use a looser bound on floating point compares - 8 units in the last +// place, allowing a loss of 3 bits of accuracy on a 23-bit mantissa. +#define EXPECT_FLOAT_EQ_O3D(expected, actual) \ + EXPECT_TRUE(AlmostEqual32BitFloat((expected), (actual), 8)) + +// ---------------------------------------------------------------------------- + +namespace { + +union FloatAndInt { + float float_value; + int int_value; +}; + +// Bit casts a float into an int. +inline int FloatAsInt(float value) { + FloatAndInt float_and_int; + float_and_int.float_value = value; + return float_and_int.int_value; +} + +// Compares 32-bit floating point values by converting them into integers +// using a "type pun" and subtracting them to see how different they are as +// bit patterns, with a necessary fudge for the IEEE754 +0/-0 area. +// For more see: +// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm +bool AlmostEqual32BitFloat(float first, float second, int max_ulps) { + // Make sure max_ulps is non-negative and small enough to force NaN to not + // compare as ordered. + LOG_ASSERT(max_ulps > 0 && max_ulps < 4*1024*1024); + int first_int = FloatAsInt(first); + // Make first_int lexicographically ordered in twos-compliment. + if (first_int < 0) first_int = 0x80000000 - first_int; + // Make second_int lexicographically ordered in twos-complement + int second_int = FloatAsInt(second); + if (second_int < 0) second_int = 0x80000000 - second_int; + int difference = abs(first_int - second_int); + if (difference <= max_ulps) return true; + return false; +} + +// Compares two Vector3's for equality +void CompareVector3s(const Vector3& v1, const Vector3& v2) { + EXPECT_FLOAT_EQ_O3D(v1.getX(), v2.getX()); + EXPECT_FLOAT_EQ_O3D(v1.getY(), v2.getY()); + EXPECT_FLOAT_EQ_O3D(v1.getZ(), v2.getZ()); +} + +// Compares two Vector4's for equality +void CompareVector4s(const Vector4& v1, const Vector4& v2) { + EXPECT_FLOAT_EQ_O3D(v1.getX(), v2.getX()); + EXPECT_FLOAT_EQ_O3D(v1.getY(), v2.getY()); + EXPECT_FLOAT_EQ_O3D(v1.getZ(), v2.getZ()); + EXPECT_FLOAT_EQ_O3D(v1.getW(), v2.getW()); +} + +// Compares two Matrix4's for equality +void CompareMatrix4s(const Matrix4& m1, const Matrix4& m2) { + CompareVector4s(m1.getCol0(), m2.getCol0()); + CompareVector4s(m1.getCol1(), m2.getCol1()); + CompareVector4s(m1.getCol2(), m2.getCol2()); + CompareVector4s(m1.getCol3(), m2.getCol3()); +} + +bool MatricesAreSame(const Matrix4& m1, const Matrix4& m2) { + for (int ii = 0; ii < 4; ++ii) { + const Vector4& vec1 = m1[ii]; + const Vector4& vec2 = m2[ii]; + for (int jj = 0; jj < 4; ++jj) { + if (!AlmostEqual32BitFloat(vec1[jj], vec2[jj], 8)) { + return false; + } + } + } + return true; +} + +// Check if a param is in the given param array +// Parameters: +// param: param to search for. +// params: ParamVector to search. +// Returns: +// true if param is in params. +bool ParamInParams(Param* param, const ParamVector& params) { + return std::find(params.begin(), params.end(), param) != params.end(); +} + +// A non-cachable Param. +class ParamCounter : public ParamMatrix4 { + public: + explicit ParamCounter(ServiceLocator* service_locator) + : ParamMatrix4(service_locator, true, true), + count_(0) { + SetNotCachable(); + } + private: + virtual void ComputeValue() { + count_ += 1.0f; + set_read_only_value(Matrix4(Vector4(count_, count_, count_, count_), + Vector4(count_, count_, count_, count_), + Vector4(count_, count_, count_, count_), + Vector4(count_, count_, count_, count_))); + } + + float count_; +}; + +} // end unnamed namespace + +// Basic test fixture. Simply creates a Client object +// before each test and deletes it after +class TransformBasic : public testing::Test { + protected: + TransformBasic() + : object_manager_(g_service_locator) {} + + virtual void SetUp(); + + virtual void TearDown(); + + // Creates a simple Transform hierarchy for testing world matrix updates + void SetupSimpleTree(); + + Pack* pack() { return pack_; } + + Transform* transform_; + Transform* transform2_; + + private: + ServiceDependency<ObjectManager> object_manager_; + Pack* pack_; +}; + +void TransformBasic::SetUp() { + pack_ = object_manager_->CreatePack(); + transform_ = pack()->Create<Transform>(); + transform2_ = pack()->Create<Transform>(); +} + +void TransformBasic::TearDown() { + pack_->Destroy(); +} + +// Make sure the Transform is initialized properly +TEST_F(TransformBasic, Type) { + // Check the type + EXPECT_EQ(Transform::GetApparentClass(), transform_->GetClass()); + + // Check that the local_matrix is identity + CompareMatrix4s(Matrix4::identity(), transform_->local_matrix()); + + // Check that the default params got created + EXPECT_TRUE( + transform_->GetParam<ParamBoolean>(Transform::kVisibleParamName) != NULL); + EXPECT_TRUE( + transform_->GetParam<ParamBoolean>(Transform::kCullParamName) != NULL); + EXPECT_TRUE( + transform_->GetParam<ParamBoundingBox>( + Transform::kBoundingBoxParamName) != NULL); + + // Check that visibility defaults to true + EXPECT_EQ(transform_->visible(), true); +} + +// Test set_local_matrix() / local_matrix() { +TEST_F(TransformBasic, SetLocalMatrix) { + Matrix4 t(Vector4(0, 1, 2, 3), + Vector4(4, 5, 6, 7), + Vector4(8, 9, 8, 7), + Vector4(6, 5, 4, 3)); + transform_->set_local_matrix(t); + + CompareMatrix4s(t, transform_->local_matrix()); +} + +// Create an additional Transform and parent transform_ under it +void TransformBasic::SetupSimpleTree() { + transform2_->set_name("t2"); + + // Make the transform_ a child of t2 + transform_->SetParent(transform2_); + + // Set t2's local matrix + Vector3 translate(10, 20, 30); + Vector3 rotate(1, 2, 3); + Vector3 scale(5, 6, 7); + + Matrix4 mat(Matrix4::identity()); + mat *= Matrix4::translation(translate); + mat *= Matrix4::rotationZYX(rotate); + mat *= Matrix4::scale(scale); + + transform2_->set_local_matrix(mat); + + // Set transform_'s local matrix + Vector3 translate2(30, 40, 50); + Vector3 rotate2(3, 4, 5); + Vector3 scale2(4, 5, 12); + + mat = Matrix4::identity(); + mat *= Matrix4::translation(translate2); + mat *= Matrix4::rotationZYX(rotate2); + mat *= Matrix4::scale(scale2); + + transform_->set_local_matrix(mat); +} + + +// Tests GetUpdatedWorldMatrix() +TEST_F(TransformBasic, GetUpdatedWorldMatrix) { + // Create a basic hierarchy + SetupSimpleTree(); + + Matrix4 t_transform = transform_->local_matrix(); + Matrix4 t2_transform = transform2_->local_matrix(); + + // Expected world matrix for transform_ + Matrix4 expected_transform = t2_transform * t_transform; + + CompareMatrix4s(expected_transform, transform_->GetUpdatedWorldMatrix()); + + // Now bind something to the world_matrix of t2 and make sure that the + // value we get is the value from the bind and not from the product + // of the local matrix with the parent matrix. + ParamMatrix4* matrix_param = + transform_->CreateParam<ParamMatrix4>("matrixParam"); + Matrix4 matrix = Matrix4::translation(Vector3(10, 0, 10)) * + Matrix4::rotationZYX(Vector3(2, 1, 2)) * + Matrix4::scale(Vector3(1, 2, 3)); + matrix_param->set_value(matrix); + + ParamMatrix4* t_world_matrix = + transform_->GetParam<ParamMatrix4>(Transform::kWorldMatrixParamName); + ASSERT_TRUE(t_world_matrix != NULL); + ASSERT_TRUE(t_world_matrix->Bind(matrix_param)); + + CompareMatrix4s(matrix, transform_->GetUpdatedWorldMatrix()); +} + +// Tests GetChildren() +TEST_F(TransformBasic, GetChildren) { + Transform* t2 = pack()->Create<Transform>(); + Transform* t3 = pack()->Create<Transform>(); + + EXPECT_EQ(0, transform_->GetChildren().size()); + + t2->SetParent(transform_); + EXPECT_EQ(1, transform_->GetChildren().size()); + + t3->SetParent(transform_); + EXPECT_EQ(2, transform_->GetChildren().size()); + + TransformArray children = transform_->GetChildren(); + TransformArrayConstIterator pos = find(children.begin(), children.end(), t2); + EXPECT_TRUE(pos != children.end()); + + pos = find(children.begin(), children.end(), t3); + EXPECT_TRUE(pos != children.end()); +} + +// Tests WorldMatrix on Transform +TEST_F(TransformBasic, WorldMatrix) { + // Setup a basic hierarchy + SetupSimpleTree(); + + // Compute expected world matrix for transform_ + Matrix4 t_transform = transform_->local_matrix(); + Matrix4 t2_transform = transform2_->local_matrix(); + Matrix4 expected_world_matrix = t2_transform * t_transform; + + // Force an update of the world_matrix for transform_ + transform_->GetUpdatedWorldMatrix(); + + // Get current worldMatrix + Matrix4 world_matrix_val(transform_->world_matrix()); + + CompareMatrix4s(Matrix4(expected_world_matrix), world_matrix_val); +} + +// Tests GetTransformsInTree +TEST_F(TransformBasic, GetTransformsInTree) { + // Setup a basic hierarchy + SetupSimpleTree(); + + Transform* t3 = pack()->Create<Transform>(); + Transform* t4 = pack()->Create<Transform>(); + t3->SetParent(transform_); + t4->SetParent(t3); + + TransformArray transforms_in_tree(transform2_->GetTransformsInTree()); + + // Check that all of them are in the tree. + EXPECT_EQ(transforms_in_tree.size(), 4); + EXPECT_TRUE(std::find(transforms_in_tree.begin(), + transforms_in_tree.end(), + transform_) != transforms_in_tree.end()); + EXPECT_TRUE(std::find(transforms_in_tree.begin(), + transforms_in_tree.end(), + transform2_) != transforms_in_tree.end()); + EXPECT_TRUE(std::find(transforms_in_tree.begin(), + transforms_in_tree.end(), + t3) != transforms_in_tree.end()); + EXPECT_TRUE(std::find(transforms_in_tree.begin(), + transforms_in_tree.end(), + t4) != transforms_in_tree.end()); + + // Check if we remove one it it's still correct. + t3->SetParent(NULL); + + transforms_in_tree = transform2_->GetTransformsInTree(); + EXPECT_EQ(transforms_in_tree.size(), 2); + EXPECT_TRUE(std::find(transforms_in_tree.begin(), + transforms_in_tree.end(), + transform_) != transforms_in_tree.end()); + EXPECT_TRUE(std::find(transforms_in_tree.begin(), + transforms_in_tree.end(), + transform2_) != transforms_in_tree.end()); + EXPECT_FALSE(std::find(transforms_in_tree.begin(), + transforms_in_tree.end(), + t3) != transforms_in_tree.end()); + EXPECT_FALSE(std::find(transforms_in_tree.begin(), + transforms_in_tree.end(), + t4) != transforms_in_tree.end()); +} + +// Tests GetTransformsByNameInTree +TEST_F(TransformBasic, GetTransformsByNameInTree) { + // Setup a basic hierarchy + SetupSimpleTree(); + + // Check that a transform is in there. + EXPECT_EQ(transform2_->GetTransformsByNameInTree("t2").size(), 1); + // Check that another transform is not in there. + EXPECT_EQ(transform2_->GetTransformsByNameInTree("t3").size(), 0); +} + +// Tests AddShape, RemoveShape, GetShapes. +TEST_F(TransformBasic, AddShapeRemoveShapeGetShapes) { + Shape* shape1 = pack()->Create<Shape>(); + Shape* shape2 = pack()->Create<Shape>(); + ASSERT_TRUE(shape1 != NULL); + ASSERT_TRUE(shape2 != NULL); + + transform_->AddShape(shape1); + transform_->AddShape(shape2); + + // Check that they actually got added. + { + const ShapeRefArray& shapes = transform_->GetShapeRefs(); + EXPECT_EQ(shapes.size(), 2); + EXPECT_TRUE(std::find(shapes.begin(), + shapes.end(), + Shape::Ref(shape1)) != shapes.end()); + EXPECT_TRUE(std::find(shapes.begin(), + shapes.end(), + Shape::Ref(shape2)) != shapes.end()); + } + + // Add a second copy of shape1. + transform_->AddShape(shape1); + + { + // check it got added. + ShapeArray shapes = transform_->GetShapes(); + EXPECT_EQ(shapes.size(), 3); + EXPECT_TRUE(std::find(shapes.begin(), + shapes.end(), + Shape::Ref(shape1)) != shapes.end()); + EXPECT_TRUE(std::find(shapes.begin(), + shapes.end(), + Shape::Ref(shape2)) != shapes.end()); + } + + // Check that they can be removed. + EXPECT_TRUE(transform_->RemoveShape(shape1)); + EXPECT_TRUE(transform_->RemoveShape(shape1)); + EXPECT_FALSE(transform_->RemoveShape(shape1)); + EXPECT_TRUE(transform_->RemoveShape(shape2)); + EXPECT_FALSE(transform_->RemoveShape(shape1)); +} + +TEST_F(TransformBasic, + ShouldReplaceShapeArrayWithThoseInArrayPassedToSetShapes) { + Shape* shape1 = pack()->Create<Shape>(); + Shape* shape2 = pack()->Create<Shape>(); + transform_->AddShape(shape1); + + ShapeArray shape_array; + shape_array.push_back(shape2); + + transform_->SetShapes(shape_array); + shape_array = transform_->GetShapes(); + + EXPECT_EQ(1, shape_array.size()); + EXPECT_EQ(shape2, shape_array[0]); +} + +TEST_F(TransformBasic, CreateGroupDrawElements) { + // Setup a basic hierarchy + SetupSimpleTree(); + + Shape* shape1 = pack()->Create<Shape>(); + Shape* shape2 = pack()->Create<Shape>(); + Primitive* primitive1 = pack()->Create<Primitive>(); + Primitive* primitive2 = pack()->Create<Primitive>(); + Material* material = pack()->Create<Material>(); + ASSERT_TRUE(shape1 != NULL); + ASSERT_TRUE(shape2 != NULL); + ASSERT_TRUE(material != NULL); + ASSERT_TRUE(primitive1 != NULL); + ASSERT_TRUE(primitive2 != NULL); + + transform_->AddShape(shape1); + transform2_->AddShape(shape2); + primitive1->SetOwner(shape1); + primitive2->SetOwner(shape2); + + transform2_->CreateDrawElements(pack(), NULL); + transform2_->CreateDrawElements(pack(), material); + + // Check that they got created correctly. + EXPECT_EQ(primitive1->GetDrawElementRefs().size(), 2); + EXPECT_EQ(primitive2->GetDrawElementRefs().size(), 2); +} + +TEST_F(TransformBasic, GetConcreteInputsForParamGetConcreteOutputsForParam) { + // Setup a basic hierarchy + SetupSimpleTree(); + + Transform* t3 = pack()->Create<Transform>(); + Transform* t4 = pack()->Create<Transform>(); + transform2_->SetParent(t3); + t3->SetParent(t4); + + // t4->t3->transform2_->transform_ + + Param* t1_local_matrix = transform_->GetUntypedParam( + Transform::kLocalMatrixParamName); + Param* t2_local_matrix = transform2_->GetUntypedParam( + Transform::kLocalMatrixParamName); + Param* t3_local_matrix = t3->GetUntypedParam( + Transform::kLocalMatrixParamName); + Param* t4_local_matrix = t4->GetUntypedParam( + Transform::kLocalMatrixParamName); + Param* t1_world_matrix = transform_->GetUntypedParam( + Transform::kWorldMatrixParamName); + Param* t2_world_matrix = transform2_->GetUntypedParam( + Transform::kWorldMatrixParamName); + Param* t3_world_matrix = t3->GetUntypedParam( + Transform::kWorldMatrixParamName); + Param* t4_world_matrix = t4->GetUntypedParam( + Transform::kWorldMatrixParamName); + + ParamVector params; + + // Tests GetConcreteInputsForParam (because GetInputsForParam calles + // GetConcreteInputsForParam) + t1_world_matrix->GetInputs(¶ms); + EXPECT_EQ(params.size(), 7); + EXPECT_TRUE(ParamInParams(t1_local_matrix, params)); + EXPECT_TRUE(ParamInParams(t2_world_matrix, params)); + EXPECT_TRUE(ParamInParams(t2_local_matrix, params)); + EXPECT_TRUE(ParamInParams(t3_world_matrix, params)); + EXPECT_TRUE(ParamInParams(t3_local_matrix, params)); + EXPECT_TRUE(ParamInParams(t4_world_matrix, params)); + EXPECT_TRUE(ParamInParams(t4_local_matrix, params)); + + t1_local_matrix->GetInputs(¶ms); + EXPECT_EQ(params.size(), 0); + + // Tests GetConcreteOutputsForParam (because GetOutputsForParam calles + // GetConcreteOutputsForParam) + t4_local_matrix->GetOutputs(¶ms); + EXPECT_EQ(params.size(), 4); + EXPECT_TRUE(ParamInParams(t1_world_matrix, params)); + EXPECT_TRUE(ParamInParams(t2_world_matrix, params)); + EXPECT_TRUE(ParamInParams(t3_world_matrix, params)); + EXPECT_TRUE(ParamInParams(t4_world_matrix, params)); + + t4_world_matrix->GetOutputs(¶ms); + EXPECT_EQ(params.size(), 3); + EXPECT_TRUE(ParamInParams(t1_world_matrix, params)); + EXPECT_TRUE(ParamInParams(t2_world_matrix, params)); + EXPECT_TRUE(ParamInParams(t3_world_matrix, params)); +} + +// Tests the param system handles implicit parameter relationships. +TEST_F(TransformBasic, ImplicitInputs) { + // Setup a basic hierarchy + SetupSimpleTree(); + + Transform* t3 = pack()->Create<Transform>(); + Transform* t4 = pack()->Create<Transform>(); + transform2_->SetParent(t3); + t3->SetParent(t4); + + Param::Ref source_param = Param::Ref(new ParamCounter(g_service_locator)); + ASSERT_FALSE(source_param.IsNull()); + + // t4->t3->transform2_->transform_ + + Param* t1_local_matrix = transform_->GetUntypedParam( + Transform::kLocalMatrixParamName); + Param* t2_local_matrix = transform2_->GetUntypedParam( + Transform::kLocalMatrixParamName); + Param* t3_local_matrix = t3->GetUntypedParam( + Transform::kLocalMatrixParamName); + Param* t4_local_matrix = t4->GetUntypedParam( + Transform::kLocalMatrixParamName); + ParamMatrix4* t1_world_matrix = transform_->GetParam<ParamMatrix4>( + Transform::kWorldMatrixParamName); + Param* t2_world_matrix = transform2_->GetUntypedParam( + Transform::kWorldMatrixParamName); + Param* t3_world_matrix = t3->GetUntypedParam( + Transform::kWorldMatrixParamName); + Param* t4_world_matrix = t4->GetUntypedParam( + Transform::kWorldMatrixParamName); + + // Check that they start as cachable. + EXPECT_TRUE(t4_world_matrix->cachable()); + EXPECT_TRUE(t3_world_matrix->cachable()); + EXPECT_TRUE(t2_world_matrix->cachable()); + EXPECT_TRUE(t1_world_matrix->cachable()); + + // check that all implicitly related params get marked as non-cachable if + // the source is not cachacble. + EXPECT_TRUE(t4_world_matrix->Bind(source_param)); + EXPECT_FALSE(t4_world_matrix->cachable()); + EXPECT_FALSE(t3_world_matrix->cachable()); + EXPECT_FALSE(t2_world_matrix->cachable()); + EXPECT_FALSE(t1_world_matrix->cachable()); + + // Check that each time we ask for the value it changes. + Matrix4 value1(t1_world_matrix->value()); + Matrix4 value2(t1_world_matrix->value()); + EXPECT_FALSE(MatricesAreSame(value1, value2)); + + // Check that if we disconnect in the middle some of them become cachable. + transform2_->SetParent(NULL); + EXPECT_FALSE(t4_world_matrix->cachable()); + EXPECT_FALSE(t3_world_matrix->cachable()); + EXPECT_TRUE(t2_world_matrix->cachable()); + EXPECT_TRUE(t1_world_matrix->cachable()); + + // Check if we disconnect the bottom the rest become cachable. + source_param->UnbindOutputs(); + EXPECT_TRUE(t4_world_matrix->cachable()); + EXPECT_TRUE(t3_world_matrix->cachable()); + EXPECT_TRUE(t2_world_matrix->cachable()); + EXPECT_TRUE(t1_world_matrix->cachable()); + + // Check if we connect to a middle one the correct ones become cachable. + transform2_->SetParent(t3); + EXPECT_TRUE(t3_local_matrix->Bind(source_param)); + EXPECT_TRUE(t4_world_matrix->cachable()); + EXPECT_FALSE(t3_world_matrix->cachable()); + EXPECT_FALSE(t2_world_matrix->cachable()); + EXPECT_FALSE(t1_world_matrix->cachable()); +} + +} // namespace o3d |