1020 lines
31 KiB
C++
1020 lines
31 KiB
C++
/**
|
|
* @file mass_properties.cpp
|
|
* @author andrew@lindenlab.com
|
|
* @date 2007-12-20
|
|
* @brief Tests for the LLPrimMassProperties and LLObjectMassProperties classes
|
|
*
|
|
* $LicenseInfo:firstyear=2007&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2007-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
#include "lltut.h"
|
|
#include <llmath/v3math.h>
|
|
#include <llmath/llquaternion.h>
|
|
#include <llphysics/abstract/utils/llinertiatensorutils.h>
|
|
#include <llphysics/abstract/utils/llobjectmassproperties.h>
|
|
#include <llphysics/abstract/utils/llprimmassproperties.h>
|
|
#include <llphysics/abstract/utils/llphysicsvolumemanager.h>
|
|
#include <llprimitive/llmaterialtable.h>
|
|
#include <llprimitive/llprimitive.h>
|
|
|
|
|
|
const F32 SMALL_RELATIVE_ERROR = 0.001f; // 0.1%
|
|
const F32 SQRT_THREE = 1.732050808f;
|
|
const F32 SQRT_SIX = 2.449489743f;
|
|
|
|
namespace tut
|
|
{
|
|
struct mass_properties_data
|
|
{
|
|
LLPrimMassProperties prim_properties;
|
|
LLObjectMassProperties object_properties;
|
|
};
|
|
|
|
typedef test_group<mass_properties_data> mass_properties_group;
|
|
typedef mass_properties_group::object mass_properties;
|
|
tut::mass_properties_group mp_test_group("mass properties");
|
|
|
|
template<> template<>
|
|
void mass_properties::test<1>()
|
|
{
|
|
// test SPHERE mass properties
|
|
LLPrimMassProperties prim_sphere;
|
|
prim_sphere.setUnitSphere();
|
|
|
|
F32 density = 1000.f;
|
|
F32 radius = 5.f;
|
|
F32 diameter = 2.f * radius;
|
|
LLVector3 scale(diameter, diameter, diameter);
|
|
LLObjectMassProperties obj_sphere(prim_sphere, scale, density);
|
|
|
|
F32 computed_mass = obj_sphere.getMass();
|
|
//LLVector3 center_of_mass
|
|
//obj_sphere.getCenterOfMass(center_of_mass);
|
|
LLMatrix3 inertia;
|
|
obj_sphere.getInertiaLocal(inertia);
|
|
F32 computed_inertia_eigenvalue = inertia.mMatrix[0][0];
|
|
|
|
|
|
// volume is normalized for scale = <1,1,1>
|
|
// V = 4/3 * PI * r^3
|
|
// inertia_eigenvalue = (2/5) * M * r^2
|
|
F32 volume = ( 4.f / 3.f ) * radius * radius * radius * F_PI;
|
|
F32 expected_mass = density * volume;
|
|
F32 expected_inertia_eigenvalue = ( 2.f / 5.f ) * expected_mass * radius * radius;
|
|
|
|
F32 error = fabs(computed_mass - expected_mass) / expected_mass;
|
|
ensure("expected sphere mass should match computed", error < SMALL_RELATIVE_ERROR);
|
|
|
|
error = fabs(computed_inertia_eigenvalue - expected_inertia_eigenvalue) / expected_inertia_eigenvalue;
|
|
ensure("expected sphere inertia should match computed", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
|
|
template<> template<>
|
|
void mass_properties::test<2>()
|
|
{
|
|
// test LLInertiaTensorUtils
|
|
|
|
// define a known inertia tensor in the center of mass frame
|
|
// from the numerical example in this paper:
|
|
// http://www.journaldatabase.org/articles/87064/Explicit_Exact_Formulas_f.html
|
|
F32 known_mass = 1873.23f;
|
|
LLVector3 known_center( 0.f, 0.f, 0.f );
|
|
LLMatrix3 known_inertia;
|
|
known_inertia.mMatrix[0][0] = 43520.33257f;
|
|
known_inertia.mMatrix[1][1] = 194711.28938f;
|
|
known_inertia.mMatrix[2][2] = 191168.76173f;
|
|
|
|
known_inertia.mMatrix[0][1] = -11996.20119f;
|
|
known_inertia.mMatrix[1][0] = -11996.20119f;
|
|
|
|
known_inertia.mMatrix[0][2] = 46343.16662f;
|
|
known_inertia.mMatrix[2][0] = 46343.16662f;
|
|
|
|
known_inertia.mMatrix[2][1] = -4417.66150f;
|
|
known_inertia.mMatrix[1][2] = -4417.66150f;
|
|
|
|
// the following two shifts should have null effect
|
|
{
|
|
LLVector3 first_shift(2.f, 3.f, 4.f);
|
|
LLVector3 second_shift = - first_shift;
|
|
LLMatrix3 inertia = known_inertia;
|
|
|
|
LLInertiaTensorUtils::shiftCenteredInertiaTensor(inertia, first_shift, known_mass);
|
|
LLInertiaTensorUtils::centerInertiaTensor(inertia, second_shift, known_mass);
|
|
|
|
// we should now have the same inertia with which we started
|
|
for (S32 i=0; i<3; ++i)
|
|
{
|
|
for (S32 j=0; j<3; ++j)
|
|
{
|
|
F32 error = fabs(1.f - inertia.mMatrix[i][j] / known_inertia.mMatrix[i][j]);
|
|
ensure("LLInertiaTensorUtils shift+sclae-shift-scale should be no-op", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
// the following series operations should have null effect
|
|
{
|
|
LLVector3 first_shift(1.f, 5.f, 10.f);
|
|
LLVector3 second_scale(2.f, 3.f, 4.f);
|
|
LLVector3 third_shift;
|
|
LLVector3 fourth_scale;
|
|
for (S32 i = 0; i < 3; ++i)
|
|
{
|
|
third_shift.mV[i] = -first_shift.mV[i] * second_scale.mV[i];
|
|
fourth_scale.mV[i] = 1.f / second_scale.mV[i];
|
|
}
|
|
|
|
F32 mass = known_mass;
|
|
LLVector3 center = known_center;
|
|
LLMatrix3 inertia = known_inertia;
|
|
|
|
// first
|
|
LLInertiaTensorUtils::shiftCenteredInertiaTensor(inertia, first_shift, mass);
|
|
center += first_shift;
|
|
|
|
// second
|
|
LLInertiaTensorUtils::scaleInertiaTensor(inertia, second_scale);
|
|
mass *= second_scale.mV[VX] * second_scale.mV[VY] * second_scale.mV[VZ];
|
|
for (S32 i = 0; i < 3; ++i)
|
|
{
|
|
center.mV[i] *= second_scale.mV[i];
|
|
}
|
|
|
|
// third
|
|
LLInertiaTensorUtils::centerInertiaTensor(inertia, third_shift, mass);
|
|
center -= third_shift;
|
|
|
|
// foruth
|
|
LLInertiaTensorUtils::scaleInertiaTensor(inertia, fourth_scale);
|
|
|
|
// we should now have the same inertia with which we started
|
|
for (S32 i=0; i<3; ++i)
|
|
{
|
|
for (S32 j=0; j<3; ++j)
|
|
{
|
|
F32 error = fabs(1.f - inertia.mMatrix[i][j] / known_inertia.mMatrix[i][j]);
|
|
ensure("LLInertiaTensorUtils shift+sclae-shift-scale should be no-op", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<> template<>
|
|
void mass_properties::test<3>()
|
|
{
|
|
// test tetrahedral decomposition of unit tetrahedron centered on origin
|
|
std::vector< LLVector3 > points;
|
|
points.push_back( LLVector3( 0.f, 0.f, 0.f ) );
|
|
points.push_back( LLVector3( 1.f, 0.f, 0.f ) );
|
|
points.push_back( LLVector3( 0.5f, 0.5f * SQRT_THREE, 0.f) );
|
|
points.push_back( LLVector3( 0.5f, SQRT_THREE / 6.f, SQRT_SIX / 3.f) );
|
|
|
|
// compute the center
|
|
LLVector3 center;
|
|
for (S32 i = 0; i < (S32)points.size(); ++i)
|
|
{
|
|
center += points[i];
|
|
}
|
|
center *= ( 1.f / F32(points.size()) );
|
|
|
|
// shift all points to center of mass frame
|
|
for (S32 i = 0; i < (S32)points.size(); ++i)
|
|
{
|
|
points[i] -= center;
|
|
}
|
|
|
|
LLPrimMassProperties tetrahedron;
|
|
tetrahedron.addSignedTetrahedron(1.0, points[0], points[1], points[2], points[3]);
|
|
// we must manually center the inertia tensor here
|
|
// since addSignedTetrahedron() does not do it automatically
|
|
tetrahedron.centerInertiaTensor();
|
|
|
|
F32 density = 1.0f;
|
|
LLVector3 scale(1.f, 1.f, 1.f);
|
|
LLMatrix3 analytic_inertia;
|
|
tetrahedron.getScaledInertiaTensor(analytic_inertia, scale, density);
|
|
|
|
// compute the mesh
|
|
std::vector< S32 > triangle_indices;
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(1);
|
|
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(3);
|
|
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(3);
|
|
triangle_indices.push_back(2);
|
|
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(3);
|
|
|
|
// compute the same inertia using a mesh
|
|
{
|
|
LLPrimMassProperties mesh;
|
|
mesh.setUnitMesh(points, triangle_indices);
|
|
|
|
// the two properties should agree
|
|
F32 error = ( tetrahedron.getVolume() - mesh.getVolume() ) / tetrahedron.getVolume();
|
|
ensure("tetrahedron and mesh volume should match", error < SMALL_RELATIVE_ERROR);
|
|
|
|
error = ( tetrahedron.getCenterOfMass() - mesh.getCenterOfMass() ).length();
|
|
ensure("tetrahedron and mesh centers should match", error < SMALL_RELATIVE_ERROR);
|
|
|
|
LLMatrix3 mesh_inertia;
|
|
mesh.getScaledInertiaTensor(mesh_inertia, scale, density);
|
|
|
|
for (S32 i=0; i<3; ++i)
|
|
{
|
|
for (S32 j=0; j<3; ++j)
|
|
{
|
|
// only verify the non-small elements
|
|
if (analytic_inertia.mMatrix[i][j] > SMALL_RELATIVE_ERROR)
|
|
{
|
|
error = fabs(1.f - mesh_inertia.mMatrix[i][j] / analytic_inertia.mMatrix[i][j]);
|
|
ensure("LLPrimMassProperties::setUnitMesh() inertia ", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// shift the whole tetrahedron away from the center of mass and recompute the mesh
|
|
{
|
|
LLVector3 shift(11.f, 7.f, 3.f);
|
|
for (S32 i = 0; i < (S32)points.size(); ++i)
|
|
{
|
|
points[i] += shift;
|
|
}
|
|
LLPrimMassProperties mesh;
|
|
mesh.setUnitMesh(points, triangle_indices);
|
|
|
|
// the two properties should agree
|
|
F32 error = ( tetrahedron.getVolume() - mesh.getVolume() ) / tetrahedron.getVolume();
|
|
ensure("tetrahedron and mesh volume should match", error < SMALL_RELATIVE_ERROR);
|
|
|
|
LLMatrix3 mesh_inertia;
|
|
mesh.getScaledInertiaTensor(mesh_inertia, scale, density);
|
|
|
|
for (S32 i=0; i<3; ++i)
|
|
{
|
|
for (S32 j=0; j<3; ++j)
|
|
{
|
|
// only verify the non-small elements
|
|
if (analytic_inertia.mMatrix[i][j] > SMALL_RELATIVE_ERROR)
|
|
{
|
|
error = fabs(1.f - mesh_inertia.mMatrix[i][j] / analytic_inertia.mMatrix[i][j]);
|
|
ensure("LLPrimMassProperties::setUnitMesh() inertia ", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<> template<>
|
|
void mass_properties::test<4>()
|
|
{
|
|
// test tetrahedron utilities
|
|
|
|
// from the paper described here:
|
|
// from the numerical example in this paper:
|
|
// http://www.journaldatabase.org/articles/87064/Explicit_Exact_Formulas_f.html
|
|
|
|
// initialize info about the tetrahedron
|
|
std::vector< LLVector3 > points;
|
|
points.push_back( LLVector3( 8.33220f, -11.86875f, 0.93355f) );
|
|
points.push_back( LLVector3( 0.75523f, 5.00000f, 16.37072f) );
|
|
points.push_back( LLVector3( 52.61236f, 5.00000f, - 5.38580f) );
|
|
points.push_back( LLVector3( 2.00000f, 5.00000f, 3.00000f) );
|
|
|
|
LLVector3 expected_center( 15.92492f, 0.78281f, 3.732962f);
|
|
|
|
LLMatrix3 expected_inertia;
|
|
expected_inertia.mMatrix[0][0] = 43520.33257f;
|
|
expected_inertia.mMatrix[1][1] = 194711.28938f;
|
|
expected_inertia.mMatrix[2][2] = 191168.76173f;
|
|
|
|
expected_inertia.mMatrix[0][1] = -11996.20119f;
|
|
expected_inertia.mMatrix[1][0] = -11996.20119f;
|
|
|
|
expected_inertia.mMatrix[0][2] = 46343.16662f;
|
|
expected_inertia.mMatrix[2][0] = 46343.16662f;
|
|
|
|
expected_inertia.mMatrix[2][1] = -4417.66150f;
|
|
expected_inertia.mMatrix[1][2] = -4417.66150f;
|
|
|
|
// measure tetrahedron bounding box max dimension
|
|
// for relative error estimates
|
|
LLVector3 box_min(FLT_MAX, FLT_MAX, FLT_MAX);
|
|
LLVector3 box_max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
for (S32 point_index = 0; point_index < (S32)points.size(); ++point_index)
|
|
{
|
|
for (S32 i = 0; i < 3; ++i)
|
|
{
|
|
if (points[point_index].mV[i] < box_min.mV[i])
|
|
{
|
|
box_min.mV[i] = points[point_index].mV[i];
|
|
}
|
|
if (points[point_index].mV[i] > box_max.mV[i])
|
|
{
|
|
box_max.mV[i] = points[point_index].mV[i];
|
|
}
|
|
}
|
|
}
|
|
F32 tetrahedron_max_dimension = (box_max - box_min).length();
|
|
|
|
|
|
// test LLPrimMassProperties::addSignedTetrahedron()
|
|
{
|
|
LLPrimMassProperties tetrahedron;
|
|
tetrahedron.addSignedTetrahedron(1.f, points[0], points[1], points[2], points[3]);
|
|
// we must manually center the inertia tensor here
|
|
// since addSignedTetrahedron() does not do it automatically
|
|
tetrahedron.centerInertiaTensor();
|
|
|
|
// check the center of mass
|
|
LLVector3 center = tetrahedron.getCenterOfMass();
|
|
F32 error = (center - expected_center).length() / tetrahedron_max_dimension;
|
|
|
|
ensure("LLPrimMassProperties::addSignedTetrahedron() center of mass ", error < SMALL_RELATIVE_ERROR);
|
|
|
|
// check the inertia tensor
|
|
LLMatrix3 computed_inertia;
|
|
LLVector3 scale(1.f, 1.f, 1.f);
|
|
F32 density = 1.f;
|
|
tetrahedron.getScaledInertiaTensor(computed_inertia, scale, density);
|
|
|
|
for (S32 i=0; i<3; ++i)
|
|
{
|
|
for (S32 j=0; j<3; ++j)
|
|
{
|
|
error = fabs(1.f - computed_inertia.mMatrix[i][j] / expected_inertia.mMatrix[i][j]);
|
|
ensure("LLPrimMassProperties::addSignedTetrahedron inertia ", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
// test LLPrimMassProperties::addUnitMesh()
|
|
{
|
|
std::vector< S32 > triangle_indices;
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(1);
|
|
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(3);
|
|
triangle_indices.push_back(0);
|
|
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(3);
|
|
|
|
triangle_indices.push_back(3);
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(2);
|
|
|
|
LLPrimMassProperties mesh;
|
|
mesh.setUnitMesh(points, triangle_indices);
|
|
|
|
// check the center of mass
|
|
LLVector3 center = mesh.getCenterOfMass();
|
|
F32 error = (center - expected_center).length() / tetrahedron_max_dimension;
|
|
|
|
ensure("LLPrimMassProperties::setUnitMesh() center of mass ", error < SMALL_RELATIVE_ERROR);
|
|
|
|
// check the inertia tensor
|
|
LLMatrix3 computed_inertia;
|
|
LLVector3 scale(1.f, 1.f, 1.f);
|
|
F32 density = 1.f;
|
|
mesh.getScaledInertiaTensor(computed_inertia, scale, density);
|
|
|
|
for (S32 i=0; i<3; ++i)
|
|
{
|
|
for (S32 j=0; j<3; ++j)
|
|
{
|
|
error = fabs(1.f - computed_inertia.mMatrix[i][j] / expected_inertia.mMatrix[i][j]);
|
|
ensure("LLPrimMassProperties::setUnitMesh() inertia diagonal elements mismatch", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<> template<>
|
|
void mass_properties::test<5>()
|
|
{
|
|
// test LLPrimMassProperties
|
|
|
|
// unit shape box
|
|
LLPrimMassProperties box;
|
|
box.setUnitBox();
|
|
|
|
// unit shape mesh -- box
|
|
|
|
//
|
|
// 4-----------0
|
|
// z /| /|
|
|
// | / | / |
|
|
// | / | / |
|
|
// | 6-----------2 |
|
|
// | | | | |
|
|
// | | 5-------|---1
|
|
// | | / | /
|
|
// | | / | /
|
|
// | y |/ |/
|
|
// |/ 7-----------3
|
|
// +------------------------ x
|
|
|
|
std::vector< LLVector3 > points;
|
|
points.push_back( LLVector3( 0.5f, 0.5f, 0.5f) );
|
|
points.push_back( LLVector3( 0.5f, 0.5f, -0.5f) );
|
|
points.push_back( LLVector3( 0.5f, -0.5f, 0.5f) );
|
|
points.push_back( LLVector3( 0.5f, -0.5f, -0.5f) );
|
|
points.push_back( LLVector3(-0.5f, 0.5f, 0.5f) );
|
|
points.push_back( LLVector3(-0.5f, 0.5f, -0.5f) );
|
|
points.push_back( LLVector3(-0.5f, -0.5f, 0.5f) );
|
|
points.push_back( LLVector3(-0.5f, -0.5f, -0.5f) );
|
|
|
|
std::vector< S32 > triangle_indices;
|
|
// +x
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(2);
|
|
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(3);
|
|
|
|
// -y
|
|
triangle_indices.push_back(3);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(7);
|
|
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(6);
|
|
|
|
// -x
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(6);
|
|
triangle_indices.push_back(4);
|
|
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(5);
|
|
|
|
// +y
|
|
triangle_indices.push_back(5);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(1);
|
|
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(0);
|
|
|
|
// +z
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(6);
|
|
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(6);
|
|
triangle_indices.push_back(2);
|
|
|
|
// -z
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(5);
|
|
triangle_indices.push_back(3);
|
|
|
|
triangle_indices.push_back(3);
|
|
triangle_indices.push_back(5);
|
|
triangle_indices.push_back(1);
|
|
|
|
LLPrimMassProperties mesh;
|
|
mesh.setUnitMesh(points, triangle_indices);
|
|
|
|
// the unit box and unit mesh mass properties should be nearly the same
|
|
|
|
// volume should agree
|
|
F32 error = fabs(box.getVolume() - mesh.getVolume()) / box.getVolume();
|
|
ensure("UnitBox and UnitMesh(box) should have same volume", error < SMALL_RELATIVE_ERROR);
|
|
|
|
// center of mass should agree
|
|
LLVector3 box_center = box.getCenterOfMass();
|
|
LLVector3 mesh_center = mesh.getCenterOfMass();
|
|
error = fabs( (box_center - mesh_center).length() );
|
|
ensure("UnitBox and UnitMesh(box) centers of mass should agree", error < SMALL_RELATIVE_ERROR );
|
|
|
|
LLVector3 scale(1.f, 1.f, 1.f);
|
|
F32 density = 1.f;
|
|
LLMatrix3 box_inertia, mesh_inertia;
|
|
box.getScaledInertiaTensor(box_inertia, scale, density);
|
|
mesh.getScaledInertiaTensor(mesh_inertia, scale, density);
|
|
|
|
// mesh eigenvalues should be uniform
|
|
for (S32 i = 0; i < 2; ++i)
|
|
{
|
|
error = fabs(mesh_inertia.mMatrix[i][i] - mesh_inertia.mMatrix[i+1][i+1]) / mesh_inertia.mMatrix[i][i];
|
|
ensure("UnitMesh(box) should have uniform eigenvalues", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
// inertias should agree
|
|
for (S32 i = 0; i < 3; ++i)
|
|
{
|
|
for (S32 j = 0; j < 3; ++j)
|
|
{
|
|
error = fabs(box_inertia.mMatrix[i][j] - mesh_inertia.mMatrix[i][j]);
|
|
if (error > 0.f
|
|
&& box_inertia.mMatrix[i][j] != 0.f)
|
|
{
|
|
error /= box_inertia.mMatrix[i][j];
|
|
}
|
|
ensure("UnitBox and UnitMesh(box) should have same inertia", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
}
|
|
|
|
// Here we test the boundary of the LLPrimLinkInfo::canLink() method
|
|
// between semi-random middle-sized objects.
|
|
}
|
|
|
|
template<> template<>
|
|
void mass_properties::test<6>()
|
|
{
|
|
// test LLObjectMassProperties
|
|
|
|
// we make a large single-prim box, then a similarly shaped object
|
|
// that is multiple prims, and compare their mass properties
|
|
|
|
LLPrimMassProperties box;
|
|
box.setUnitBox();
|
|
|
|
F32 density = 3.7f;
|
|
LLVector3 big_scale(1.f, 2.f, 3.f);
|
|
LLObjectMassProperties big_box(box, big_scale, density);
|
|
|
|
LLObjectMassProperties multiple_box;
|
|
LLVector3 position;
|
|
LLQuaternion rotation;
|
|
rotation.loadIdentity();
|
|
|
|
F32 small_box_size = 0.5f;
|
|
LLVector3 small_scale( small_box_size, small_box_size, small_box_size);
|
|
S32 num_boxes_x = S32(big_scale.mV[VX] / small_box_size);
|
|
S32 num_boxes_y = S32(big_scale.mV[VY] / small_box_size);
|
|
S32 num_boxes_z = S32(big_scale.mV[VZ] / small_box_size);
|
|
LLVector3 start_pos = 0.5f * (small_scale - big_scale);
|
|
for (S32 x = 0; x < num_boxes_x; ++x)
|
|
{
|
|
for (S32 y = 0; y < num_boxes_y; ++y)
|
|
{
|
|
for (S32 z = 0; z < num_boxes_z; ++z)
|
|
{
|
|
position.set( F32(x) * small_box_size, F32(y) * small_box_size, F32(z) * small_box_size );
|
|
position += start_pos;
|
|
|
|
multiple_box.add(box, small_scale, density, position, rotation);
|
|
}
|
|
}
|
|
}
|
|
|
|
// the mass properties of the two boxes should match
|
|
|
|
// mass
|
|
F32 big_mass = big_box.getMass();
|
|
F32 multiple_mass = multiple_box.getMass();
|
|
F32 error = (big_mass - multiple_mass) / big_mass;
|
|
ensure("Big box and equivalent multi-prim box should have same mass", error < SMALL_RELATIVE_ERROR);
|
|
|
|
// center of mass
|
|
LLVector3 big_center, multiple_center;
|
|
big_box.getCenterOfMass(big_center);
|
|
multiple_box.getCenterOfMass(multiple_center);
|
|
error = (big_center - multiple_center).length();
|
|
ensure("Big box and equivalent multi-prim box should have same center", error < SMALL_RELATIVE_ERROR);
|
|
|
|
// inertia
|
|
LLMatrix3 big_inertia, multiple_inertia;
|
|
big_box.getInertiaLocal(big_inertia);
|
|
multiple_box.getInertiaLocal(multiple_inertia);
|
|
for (S32 i = 0; i < 3; ++i)
|
|
{
|
|
for (S32 j = 0; j < 3; ++j)
|
|
{
|
|
error = fabs(big_inertia.mMatrix[i][j] - multiple_inertia.mMatrix[i][j]);
|
|
if (error > 0.f
|
|
&& big_inertia.mMatrix[i][j] != 0.f)
|
|
{
|
|
error /= big_inertia.mMatrix[i][j];
|
|
}
|
|
ensure("UnitBox and UnitMesh(box) should have same inertia", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<> template<>
|
|
void mass_properties::test<7>()
|
|
{
|
|
// test LLObjectMassProperties with rotations
|
|
|
|
// we make a large single-prim box via mesh, then a similarly shaped
|
|
// object that is multiple prims (implicit boxes), and compare their
|
|
// mass properties
|
|
|
|
//
|
|
// 4-----------0
|
|
// z /| /|
|
|
// | / | / |
|
|
// | / | / |
|
|
// | 6-----------2 |
|
|
// | | | | |
|
|
// | | 5-------|---1
|
|
// | | / | /
|
|
// | | / | /
|
|
// | y |/ |/
|
|
// |/ 7-----------3
|
|
// +------------------------ x
|
|
|
|
std::vector< LLVector3 > points;
|
|
points.push_back( LLVector3( 0.5f, 0.5f, 0.5f) );
|
|
points.push_back( LLVector3( 0.5f, 0.5f, -0.5f) );
|
|
points.push_back( LLVector3( 0.5f, -0.5f, 0.5f) );
|
|
points.push_back( LLVector3( 0.5f, -0.5f, -0.5f) );
|
|
points.push_back( LLVector3(-0.5f, 0.5f, 0.5f) );
|
|
points.push_back( LLVector3(-0.5f, 0.5f, -0.5f) );
|
|
points.push_back( LLVector3(-0.5f, -0.5f, 0.5f) );
|
|
points.push_back( LLVector3(-0.5f, -0.5f, -0.5f) );
|
|
|
|
std::vector< S32 > triangle_indices;
|
|
// +x
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(2);
|
|
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(3);
|
|
|
|
// -y
|
|
triangle_indices.push_back(3);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(7);
|
|
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(6);
|
|
|
|
// -x
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(6);
|
|
triangle_indices.push_back(4);
|
|
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(5);
|
|
|
|
// +y
|
|
triangle_indices.push_back(5);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(1);
|
|
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(0);
|
|
|
|
// +z
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(6);
|
|
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(6);
|
|
triangle_indices.push_back(2);
|
|
|
|
// -z
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(5);
|
|
triangle_indices.push_back(3);
|
|
|
|
triangle_indices.push_back(3);
|
|
triangle_indices.push_back(5);
|
|
triangle_indices.push_back(1);
|
|
|
|
F32 angle_step = F_PI / (2.f * 3.f);
|
|
for (F32 angle = 0.f; angle < 0.51f * F_PI; angle += angle_step)
|
|
{
|
|
// scale and rotate mesh points
|
|
LLVector3 axis(0.f, 0.f, angle);
|
|
LLQuaternion mesh_rotation(angle, axis);
|
|
LLVector3 big_scale(3.f, 5.f, 7.f);
|
|
std::vector< LLVector3 > new_points;
|
|
for (S32 p = 0; p < (S32)points.size(); ++p)
|
|
{
|
|
LLVector3 new_point = points[p];
|
|
for (S32 i = 0; i < 3; ++i)
|
|
{
|
|
new_point.mV[i] *= big_scale.mV[i];
|
|
}
|
|
new_points.push_back( new_point * mesh_rotation );
|
|
}
|
|
|
|
// build the big mesh box
|
|
LLPrimMassProperties mesh_box;
|
|
mesh_box.setUnitMesh(new_points, triangle_indices);
|
|
|
|
F32 density = 3.7f;
|
|
LLVector3 unit_scale(1.f, 1.f, 1.f);
|
|
LLObjectMassProperties big_box(mesh_box, unit_scale, density);
|
|
|
|
// build the multiple_box
|
|
LLPrimMassProperties box;
|
|
box.setUnitBox();
|
|
|
|
LLObjectMassProperties multiple_box;
|
|
LLVector3 position;
|
|
|
|
F32 small_box_size = 0.5f;
|
|
LLVector3 small_scale( small_box_size, small_box_size, small_box_size);
|
|
S32 num_boxes_x = S32(big_scale.mV[VX] / small_box_size);
|
|
S32 num_boxes_y = S32(big_scale.mV[VY] / small_box_size);
|
|
S32 num_boxes_z = S32(big_scale.mV[VZ] / small_box_size);
|
|
LLVector3 start_pos = (0.5f * (small_scale - big_scale)) * mesh_rotation;
|
|
for (S32 x = 0; x < num_boxes_x; ++x)
|
|
{
|
|
for (S32 y = 0; y < num_boxes_y; ++y)
|
|
{
|
|
for (S32 z = 0; z < num_boxes_z; ++z)
|
|
{
|
|
position.set( F32(x) * small_box_size, F32(y) * small_box_size, F32(z) * small_box_size );
|
|
position *= mesh_rotation;
|
|
position += start_pos;
|
|
multiple_box.add(box, small_scale, density, position, mesh_rotation);
|
|
}
|
|
}
|
|
}
|
|
|
|
// the mass properties of the two boxes should match
|
|
|
|
// mass
|
|
F32 big_mass = big_box.getMass();
|
|
F32 multiple_mass = multiple_box.getMass();
|
|
F32 error = (big_mass - multiple_mass) / big_mass;
|
|
ensure("Big box and equivalent multi-prim box should have same mass", error < SMALL_RELATIVE_ERROR);
|
|
|
|
// center of mass
|
|
LLVector3 big_center, multiple_center;
|
|
big_box.getCenterOfMass(big_center);
|
|
multiple_box.getCenterOfMass(multiple_center);
|
|
error = (big_center - multiple_center).length();
|
|
ensure("Big box and equivalent multi-prim box should have same center", error < SMALL_RELATIVE_ERROR);
|
|
|
|
LLMatrix3 big_inertia, multiple_inertia;
|
|
big_box.getInertiaLocal(big_inertia);
|
|
multiple_box.getInertiaLocal(multiple_inertia);
|
|
|
|
for (S32 i = 0; i < 3; ++i)
|
|
{
|
|
for (S32 j = 0; j < 3; ++j)
|
|
{
|
|
error = fabs(big_inertia.mMatrix[i][j] - multiple_inertia.mMatrix[i][j]);
|
|
if (error > 0.f
|
|
&& big_inertia.mMatrix[i][j] > SMALL_RELATIVE_ERROR)
|
|
{
|
|
error /= big_inertia.mMatrix[i][j];
|
|
}
|
|
ensure("UnitBox and UnitMesh(box) should have same inertia", error < SMALL_RELATIVE_ERROR);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<> template<>
|
|
void mass_properties::test<8>()
|
|
{
|
|
// test LLPhysicsVolumeManager
|
|
|
|
// we make a large single-prim box, then a similarly shaped object
|
|
// that is multiple prims, and compare their mass properties
|
|
|
|
// first we make the single-prim giant
|
|
//
|
|
// 4-----------0
|
|
// z /| /|
|
|
// | / | / |
|
|
// | / | / |
|
|
// | 6-----------2 |
|
|
// | | | | |
|
|
// | | 5-------|---1
|
|
// | | / | /
|
|
// | | / | /
|
|
// | y |/ |/
|
|
// |/ 7-----------3
|
|
// +------------------------ x
|
|
|
|
std::vector< LLVector3 > points;
|
|
points.push_back( LLVector3( 0.5f, 0.5f, 0.5f) );
|
|
points.push_back( LLVector3( 0.5f, 0.5f, -0.5f) );
|
|
points.push_back( LLVector3( 0.5f, -0.5f, 0.5f) );
|
|
points.push_back( LLVector3( 0.5f, -0.5f, -0.5f) );
|
|
points.push_back( LLVector3(-0.5f, 0.5f, 0.5f) );
|
|
points.push_back( LLVector3(-0.5f, 0.5f, -0.5f) );
|
|
points.push_back( LLVector3(-0.5f, -0.5f, 0.5f) );
|
|
points.push_back( LLVector3(-0.5f, -0.5f, -0.5f) );
|
|
|
|
std::vector< S32 > triangle_indices;
|
|
// +x
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(2);
|
|
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(3);
|
|
|
|
// -y
|
|
triangle_indices.push_back(3);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(7);
|
|
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(2);
|
|
triangle_indices.push_back(6);
|
|
|
|
// -x
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(6);
|
|
triangle_indices.push_back(4);
|
|
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(5);
|
|
|
|
// +y
|
|
triangle_indices.push_back(5);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(1);
|
|
|
|
triangle_indices.push_back(1);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(0);
|
|
|
|
// +z
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(4);
|
|
triangle_indices.push_back(6);
|
|
|
|
triangle_indices.push_back(0);
|
|
triangle_indices.push_back(6);
|
|
triangle_indices.push_back(2);
|
|
|
|
// -z
|
|
triangle_indices.push_back(7);
|
|
triangle_indices.push_back(5);
|
|
triangle_indices.push_back(3);
|
|
|
|
triangle_indices.push_back(3);
|
|
triangle_indices.push_back(5);
|
|
triangle_indices.push_back(1);
|
|
|
|
// scale the mesh points
|
|
LLVector3 big_scale(1.f, 2.f, 3.f);
|
|
std::vector< LLVector3 > new_points;
|
|
for (S32 p = 0; p < (S32)points.size(); ++p)
|
|
{
|
|
LLVector3 new_point = points[p];
|
|
for (S32 i = 0; i < 3; ++i)
|
|
{
|
|
new_point.mV[i] *= big_scale.mV[i];
|
|
}
|
|
new_points.push_back( new_point );
|
|
}
|
|
|
|
// build the big mesh box (primitive)
|
|
LLPrimMassProperties mesh_box;
|
|
mesh_box.setUnitMesh(new_points, triangle_indices);
|
|
|
|
F32 density = DEFAULT_OBJECT_DENSITY;
|
|
LLVector3 unit_scale(1.f, 1.f, 1.f);
|
|
LLObjectMassProperties big_box(mesh_box, unit_scale, density);
|
|
|
|
// build the multi-prim box (object)
|
|
S32 TEST_VOLUME_DETAIL = 1;
|
|
|
|
LLVolumeParams volume_params;
|
|
volume_params.setCube();
|
|
|
|
LLObjectMassProperties multiple_box;
|
|
F32 small_box_size = 0.5f;
|
|
LLVector3 small_scale( small_box_size, small_box_size, small_box_size);
|
|
{
|
|
// hijack the volume manager used by LLPrimitive
|
|
LLPhysicsVolumeManager* volume_manager = new LLPhysicsVolumeManager();
|
|
//volume_manager->setThreadSafe(false);
|
|
LLPrimitive::setVolumeManager(volume_manager);
|
|
|
|
std::vector< const LLPrimitive* > prim_list;
|
|
|
|
F32 angle = 0.f;
|
|
LLVector3 axis(0.f, 0.f, angle);
|
|
LLVector3 position;
|
|
LLQuaternion rotation(angle, axis);
|
|
S32 num_boxes_x = S32(big_scale.mV[VX] / small_box_size);
|
|
S32 num_boxes_y = S32(big_scale.mV[VY] / small_box_size);
|
|
S32 num_boxes_z = S32(big_scale.mV[VZ] / small_box_size);
|
|
|
|
for (S32 x = 0; x < num_boxes_x; ++x)
|
|
{
|
|
for (S32 y = 0; y < num_boxes_y; ++y)
|
|
{
|
|
for (S32 z = 0; z < num_boxes_z; ++z)
|
|
{
|
|
LLPrimitive* primp = new LLPrimitive();
|
|
primp->setVolume( volume_params, TEST_VOLUME_DETAIL);
|
|
|
|
position.set( F32(x) * small_box_size, F32(y) * small_box_size, F32(z) * small_box_size );
|
|
position *= rotation;
|
|
|
|
primp->setPosition(position);
|
|
primp->setRotation(rotation);
|
|
primp->setScale(small_scale);
|
|
|
|
prim_list.push_back(primp);
|
|
}
|
|
}
|
|
}
|
|
|
|
volume_manager->getObjectMassProperties(multiple_box, prim_list);
|
|
|
|
for (S32 i = 0; i < (S32)prim_list.size(); ++i)
|
|
{
|
|
delete prim_list[i];
|
|
prim_list[i] = NULL;
|
|
}
|
|
LLPrimitive::cleanupVolumeManager();
|
|
}
|
|
|
|
// mass
|
|
F32 big_mass = big_box.getMass();
|
|
F32 multiple_mass = multiple_box.getMass();
|
|
F32 error = (big_mass - multiple_mass) / big_mass;
|
|
ensure("Big box and equivalent multi-prim box should have same mass", error < SMALL_RELATIVE_ERROR);
|
|
|
|
// center of mass
|
|
LLVector3 big_center, multiple_center;
|
|
big_box.getCenterOfMass(big_center);
|
|
multiple_box.getCenterOfMass(multiple_center);
|
|
LLVector3 expected_shift = 0.5f * ( big_scale - small_scale );
|
|
error = ( big_center - (multiple_center - expected_shift) ).length();
|
|
ensure("Big box and equivalent multi-prim box should have same center", error < SMALL_RELATIVE_ERROR);
|
|
|
|
// inertia
|
|
LLMatrix3 big_inertia, multiple_inertia;
|
|
big_box.getInertiaLocal(big_inertia);
|
|
multiple_box.getInertiaLocal(multiple_inertia);
|
|
|
|
for (S32 i = 0; i < 3; ++i)
|
|
{
|
|
for (S32 j = 0; j < 3; ++j)
|
|
{
|
|
error = fabs(big_inertia.mMatrix[i][j] - multiple_inertia.mMatrix[i][j]);
|
|
if (error > 0.f
|
|
&& big_inertia.mMatrix[i][j] > SMALL_RELATIVE_ERROR)
|
|
{
|
|
error /= big_inertia.mMatrix[i][j];
|
|
}
|
|
bool ok = error < SMALL_RELATIVE_ERROR
|
|
|| (i != j
|
|
&& error < SMALL_RELATIVE_ERROR);
|
|
ensure("UnitBox and UnitMesh(box) should have same inertia", ok );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|