overte/libraries/physics/src/ShapeFactory.cpp
LaShonda Hopper 5b50b362f1 [WL21389] WL21389 PR2: Representation of collision shapes need updating (details below).
This commit adds support for the polyhedrons and polygons sans
Torus and Quad which aren't currently supported within GeometryCache.

* Moves GeometryCache::_shapes from public to private scope
    * Nothing aside from the class should be directly altering this, only querying
    * Updated instances of direct referencing which looks to have been limited to prior
      testing of instancing and shapes.
* Adds an accessor function for ShapeData to GeometryCache
* Consolidates point list generation to helper function
    * GeometryCache::computeSimpleHullPointListForShape
* Moves GeometryCache::Shape to entity::Shape mapping to GeometryCache from
  RenderableShapeEntityItem
    * Adds conversion accessor to GeometryCache, GeometryCache::Shape getShapeForEntityShape
* Sets ShapeEntityItem::ShapeInfoCalculator callback committed earlier.
    * This helps circumvent the issue with library inclusion.  entity-render knows about
      entity; however, entity doesn't know about entity-renderer; however, GeometryCache
      data is needed within entity::ShapeEntityItem to compose the ShapeInfo point list data.
    * This callback is set up within Application::init of the Interface as it knows about
      both halves of the equation, and the callback needs to be setup prior to any entities
      collision data getting generated.
* Removes _type reset within ShapeInfo::setPointCollection
    * This should avoid any issues due to subversively setting the type or
      incorrectly setting the type as a tangential operation.
        * Audited instances of ShapeInfo::setPointCollection and all seemed to be
          calling the function immediately after having set the type via
          ShapeInfo::setParams
* Adds new ShapeType: SHAPE_TYPE_CIRCLE
    * This type is reserved for the circle which is now treated as a special
      type of Cylinder_Y with regard to collision as opposed to a simple hull.
    * Fixes the issue where jumping on a circle, at times, would
      result in the avatar sliding off towards an edge as if atop a
      squished cone.
* Also updates ShapeInfo::getType() to return ShapeType as opposed to int
    * Auditing calls showed that majority of places were comparing against ShapeType
    * ShapeType::_type is a ShapeType so returning the type explicitly
      is more consistent.
        * ShapeInfo file houses ShapeType enum so any file aware of ShapeInfo is aware of
          ShapeType enumeration.
* entity::Quad defaults to SHAPE_TYPE_ELLIPSOID
    * Like entity::Shape::Torus, entity::Shape::Quad is currently unsupported within
      GeometryCache::buildShapes.
    * Also it appears that a Quad shape can't be created within world via the creation menu.
        * There's no explicit option at present to create one.
        * Trying subvert the Cube/Box creation option to generate one results in an enforced
          stubby box as opposed to a quad.
    * Given the aforementioned points, entity::Shape::Quad will default to
      SHAPE_TYPE_ELLIPSOID as opposed to SHAPE_TYPE_BOX.
        * Added Todo regarding the shape being unsupported along with a notation to create
          a special ShapeType, SHAPE_TYPE_QUAD, for it should it be added in the future.
* Adds some comments and has some minor clean up.

Reviewed-by: Leander Hasty <leander@1stplayable.com>

Changes Committed:
	modified:   interface/src/Application.cpp
	modified:   interface/src/Util.cpp
	modified:   interface/src/Util.h
	modified:   libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
	modified:   libraries/entities/src/ShapeEntityItem.cpp
	modified:   libraries/entities/src/ShapeEntityItem.h
	modified:   libraries/physics/src/ShapeFactory.cpp
	modified:   libraries/render-utils/src/GeometryCache.cpp
	modified:   libraries/render-utils/src/GeometryCache.h
	modified:   libraries/shared/src/ShapeInfo.cpp
	modified:   libraries/shared/src/ShapeInfo.h
	modified:   tests/gpu-test/src/TestInstancedShapes.cpp
2017-10-24 14:16:17 -04:00

461 lines
20 KiB
C++

//
// ShapeFactory.cpp
// libraries/physics/src
//
// Created by Andrew Meadows 2014.12.01
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <glm/gtx/norm.hpp>
#include <SharedUtil.h> // for MILLIMETERS_PER_METER
#include "ShapeFactory.h"
#include "BulletUtil.h"
// These are the same normalized directions used by the btShapeHull class.
// 12 points for the face centers of a dodecahedron plus another 30 points
// for the midpoints the edges, for a total of 42.
const uint32_t NUM_UNIT_SPHERE_DIRECTIONS = 42;
static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = {
btVector3(btScalar(0.000000) , btScalar(-0.000000),btScalar(-1.000000)),
btVector3(btScalar(0.723608) , btScalar(-0.525725),btScalar(-0.447219)),
btVector3(btScalar(-0.276388) , btScalar(-0.850649),btScalar(-0.447219)),
btVector3(btScalar(-0.894426) , btScalar(-0.000000),btScalar(-0.447216)),
btVector3(btScalar(-0.276388) , btScalar(0.850649),btScalar(-0.447220)),
btVector3(btScalar(0.723608) , btScalar(0.525725),btScalar(-0.447219)),
btVector3(btScalar(0.276388) , btScalar(-0.850649),btScalar(0.447220)),
btVector3(btScalar(-0.723608) , btScalar(-0.525725),btScalar(0.447219)),
btVector3(btScalar(-0.723608) , btScalar(0.525725),btScalar(0.447219)),
btVector3(btScalar(0.276388) , btScalar(0.850649),btScalar(0.447219)),
btVector3(btScalar(0.894426) , btScalar(0.000000),btScalar(0.447216)),
btVector3(btScalar(-0.000000) , btScalar(0.000000),btScalar(1.000000)),
btVector3(btScalar(0.425323) , btScalar(-0.309011),btScalar(-0.850654)),
btVector3(btScalar(-0.162456) , btScalar(-0.499995),btScalar(-0.850654)),
btVector3(btScalar(0.262869) , btScalar(-0.809012),btScalar(-0.525738)),
btVector3(btScalar(0.425323) , btScalar(0.309011),btScalar(-0.850654)),
btVector3(btScalar(0.850648) , btScalar(-0.000000),btScalar(-0.525736)),
btVector3(btScalar(-0.525730) , btScalar(-0.000000),btScalar(-0.850652)),
btVector3(btScalar(-0.688190) , btScalar(-0.499997),btScalar(-0.525736)),
btVector3(btScalar(-0.162456) , btScalar(0.499995),btScalar(-0.850654)),
btVector3(btScalar(-0.688190) , btScalar(0.499997),btScalar(-0.525736)),
btVector3(btScalar(0.262869) , btScalar(0.809012),btScalar(-0.525738)),
btVector3(btScalar(0.951058) , btScalar(0.309013),btScalar(0.000000)),
btVector3(btScalar(0.951058) , btScalar(-0.309013),btScalar(0.000000)),
btVector3(btScalar(0.587786) , btScalar(-0.809017),btScalar(0.000000)),
btVector3(btScalar(0.000000) , btScalar(-1.000000),btScalar(0.000000)),
btVector3(btScalar(-0.587786) , btScalar(-0.809017),btScalar(0.000000)),
btVector3(btScalar(-0.951058) , btScalar(-0.309013),btScalar(-0.000000)),
btVector3(btScalar(-0.951058) , btScalar(0.309013),btScalar(-0.000000)),
btVector3(btScalar(-0.587786) , btScalar(0.809017),btScalar(-0.000000)),
btVector3(btScalar(-0.000000) , btScalar(1.000000),btScalar(-0.000000)),
btVector3(btScalar(0.587786) , btScalar(0.809017),btScalar(-0.000000)),
btVector3(btScalar(0.688190) , btScalar(-0.499997),btScalar(0.525736)),
btVector3(btScalar(-0.262869) , btScalar(-0.809012),btScalar(0.525738)),
btVector3(btScalar(-0.850648) , btScalar(0.000000),btScalar(0.525736)),
btVector3(btScalar(-0.262869) , btScalar(0.809012),btScalar(0.525738)),
btVector3(btScalar(0.688190) , btScalar(0.499997),btScalar(0.525736)),
btVector3(btScalar(0.525730) , btScalar(0.000000),btScalar(0.850652)),
btVector3(btScalar(0.162456) , btScalar(-0.499995),btScalar(0.850654)),
btVector3(btScalar(-0.425323) , btScalar(-0.309011),btScalar(0.850654)),
btVector3(btScalar(-0.425323) , btScalar(0.309011),btScalar(0.850654)),
btVector3(btScalar(0.162456) , btScalar(0.499995),btScalar(0.850654))
};
// util method
btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) {
assert(points.size() > 0);
btConvexHullShape* hull = new btConvexHullShape();
glm::vec3 center = points[0];
glm::vec3 maxCorner = center;
glm::vec3 minCorner = center;
for (int i = 1; i < points.size(); i++) {
center += points[i];
maxCorner = glm::max(maxCorner, points[i]);
minCorner = glm::min(minCorner, points[i]);
}
center /= (float)(points.size());
float margin = hull->getMargin();
// Bullet puts "margins" around all the collision shapes. This can cause objects that use ConvexHull shapes
// to have visible gaps between them and the surface they touch. One option is to reduce the size of the margin
// but this can reduce the performance and stability of the simulation (e.g. the GJK algorithm will fail to provide
// nearest contact points and narrow-phase collisions will fall into more expensive code paths). Alternatively
// one can shift the geometry of the shape to make the margin surface approximately close to the visible surface.
// This is the strategy we try, but if the object is too small then we start to reduce the margin down to some minimum.
const float MIN_MARGIN = 0.01f;
glm::vec3 diagonal = maxCorner - minCorner;
float smallestDimension = glm::min(diagonal[0], diagonal[1]);
smallestDimension = glm::min(smallestDimension, diagonal[2]);
const float MIN_DIMENSION = 2.0f * MIN_MARGIN + 0.001f;
if (smallestDimension < MIN_DIMENSION) {
for (int i = 0; i < 3; ++i) {
if (diagonal[i] < MIN_DIMENSION) {
diagonal[i] = MIN_DIMENSION;
}
}
smallestDimension = MIN_DIMENSION;
}
margin = glm::min(glm::max(0.5f * smallestDimension, MIN_MARGIN), margin);
hull->setMargin(margin);
// add the points, correcting for margin
glm::vec3 relativeScale = (diagonal - glm::vec3(2.0f * margin)) / diagonal;
glm::vec3 correctedPoint;
for (int i = 0; i < points.size(); ++i) {
correctedPoint = (points[i] - center) * relativeScale + center;
hull->addPoint(btVector3(correctedPoint[0], correctedPoint[1], correctedPoint[2]), false);
}
uint32_t numPoints = (uint32_t)hull->getNumPoints();
if (numPoints > MAX_HULL_POINTS) {
// we have too many points, so we compute point projections along canonical unit vectors
// and keep the those that project the farthest
btVector3 btCenter = glmToBullet(center);
btVector3* shapePoints = hull->getUnscaledPoints();
std::vector<uint32_t> finalIndices;
finalIndices.reserve(NUM_UNIT_SPHERE_DIRECTIONS);
for (uint32_t i = 0; i < NUM_UNIT_SPHERE_DIRECTIONS; ++i) {
uint32_t bestIndex = 0;
btScalar maxDistance = _unitSphereDirections[i].dot(shapePoints[0] - btCenter);
for (uint32_t j = 1; j < numPoints; ++j) {
btScalar distance = _unitSphereDirections[i].dot(shapePoints[j] - btCenter);
if (distance > maxDistance) {
maxDistance = distance;
bestIndex = j;
}
}
bool keep = true;
for (uint32_t j = 0; j < finalIndices.size(); ++j) {
if (finalIndices[j] == bestIndex) {
keep = false;
break;
}
}
if (keep) {
finalIndices.push_back(bestIndex);
}
}
// we cannot copy Bullet shapes so we must create a new one...
btConvexHullShape* newHull = new btConvexHullShape();
for (uint32_t i = 0; i < finalIndices.size(); ++i) {
newHull->addPoint(shapePoints[finalIndices[i]], false);
}
// ...and delete the old one
delete hull;
hull = newHull;
}
hull->recalcLocalAabb();
return hull;
}
// util method
btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) {
assert(info.getType() == SHAPE_TYPE_STATIC_MESH); // should only get here for mesh shapes
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
if (pointCollection.size() < 1) {
// no lists of points to work with
return nullptr;
}
// we only use the first point collection
const ShapeInfo::PointList& pointList = pointCollection[0];
if (pointList.size() < 3) {
// not enough distinct points to make a non-degenerate triangle
return nullptr;
}
const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
int32_t numIndices = triangleIndices.size();
if (numIndices < 3) {
// not enough indices to make a single triangle
return nullptr;
}
// allocate mesh buffers
btIndexedMesh mesh;
const int32_t VERTICES_PER_TRIANGLE = 3;
mesh.m_numTriangles = numIndices / VERTICES_PER_TRIANGLE;
if (numIndices < std::numeric_limits<int16_t>::max()) {
// small number of points so we can use 16-bit indices
mesh.m_triangleIndexBase = new unsigned char[sizeof(int16_t) * (size_t)numIndices];
mesh.m_indexType = PHY_SHORT;
mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int16_t);
} else {
mesh.m_triangleIndexBase = new unsigned char[sizeof(int32_t) * (size_t)numIndices];
mesh.m_indexType = PHY_INTEGER;
mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int32_t);
}
mesh.m_numVertices = pointList.size();
mesh.m_vertexBase = new unsigned char[VERTICES_PER_TRIANGLE * sizeof(btScalar) * (size_t)mesh.m_numVertices];
mesh.m_vertexStride = VERTICES_PER_TRIANGLE * sizeof(btScalar);
mesh.m_vertexType = PHY_FLOAT;
// copy data into buffers
btScalar* vertexData = static_cast<btScalar*>((void*)(mesh.m_vertexBase));
for (int32_t i = 0; i < mesh.m_numVertices; ++i) {
int32_t j = i * VERTICES_PER_TRIANGLE;
const glm::vec3& point = pointList[i];
vertexData[j] = point.x;
vertexData[j + 1] = point.y;
vertexData[j + 2] = point.z;
}
if (numIndices < std::numeric_limits<int16_t>::max()) {
int16_t* indices = static_cast<int16_t*>((void*)(mesh.m_triangleIndexBase));
for (int32_t i = 0; i < numIndices; ++i) {
indices[i] = (int16_t)triangleIndices[i];
}
} else {
int32_t* indices = static_cast<int32_t*>((void*)(mesh.m_triangleIndexBase));
for (int32_t i = 0; i < numIndices; ++i) {
indices[i] = triangleIndices[i];
}
}
// store buffers in a new dataArray and return the pointer
// (external StaticMeshShape will own all of the data that was allocated here)
btTriangleIndexVertexArray* dataArray = new btTriangleIndexVertexArray;
dataArray->addIndexedMesh(mesh, mesh.m_indexType);
return dataArray;
}
// util method
void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) {
assert(dataArray);
IndexedMeshArray& meshes = dataArray->getIndexedMeshArray();
for (int32_t i = 0; i < meshes.size(); ++i) {
btIndexedMesh mesh = meshes[i];
mesh.m_numTriangles = 0;
delete [] mesh.m_triangleIndexBase;
mesh.m_triangleIndexBase = nullptr;
mesh.m_numVertices = 0;
delete [] mesh.m_vertexBase;
mesh.m_vertexBase = nullptr;
}
meshes.clear();
delete dataArray;
}
const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
btCollisionShape* shape = NULL;
int type = info.getType();
switch(type) {
case SHAPE_TYPE_BOX: {
shape = new btBoxShape(glmToBullet(info.getHalfExtents()));
}
break;
case SHAPE_TYPE_SPHERE: {
glm::vec3 halfExtents = info.getHalfExtents();
float radius = glm::max(halfExtents.x, glm::max(halfExtents.y, halfExtents.z));
shape = new btSphereShape(radius);
}
break;
case SHAPE_TYPE_ELLIPSOID: {
glm::vec3 halfExtents = info.getHalfExtents();
float radius = halfExtents.x;
const float MIN_RADIUS = 0.001f;
const float MIN_RELATIVE_SPHERICAL_ERROR = 0.001f;
if (radius > MIN_RADIUS
&& fabsf(radius - halfExtents.y) / radius < MIN_RELATIVE_SPHERICAL_ERROR
&& fabsf(radius - halfExtents.z) / radius < MIN_RELATIVE_SPHERICAL_ERROR) {
// close enough to true sphere
shape = new btSphereShape(radius);
} else {
ShapeInfo::PointList points;
points.reserve(NUM_UNIT_SPHERE_DIRECTIONS);
for (uint32_t i = 0; i < NUM_UNIT_SPHERE_DIRECTIONS; ++i) {
points.push_back(bulletToGLM(_unitSphereDirections[i]) * halfExtents);
}
shape = createConvexHull(points);
}
}
break;
case SHAPE_TYPE_CAPSULE_Y: {
glm::vec3 halfExtents = info.getHalfExtents();
float radius = halfExtents.x;
float height = 2.0f * halfExtents.y;
shape = new btCapsuleShape(radius, height);
}
break;
case SHAPE_TYPE_CAPSULE_X: {
glm::vec3 halfExtents = info.getHalfExtents();
float radius = halfExtents.y;
float height = 2.0f * halfExtents.x;
shape = new btCapsuleShapeX(radius, height);
}
break;
case SHAPE_TYPE_CAPSULE_Z: {
glm::vec3 halfExtents = info.getHalfExtents();
float radius = halfExtents.x;
float height = 2.0f * halfExtents.z;
shape = new btCapsuleShapeZ(radius, height);
}
break;
case SHAPE_TYPE_CYLINDER_X: {
const glm::vec3 halfExtents = info.getHalfExtents();
const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z);
shape = new btCylinderShapeX(btHalfExtents);
}
break;
case SHAPE_TYPE_CYLINDER_Z: {
const glm::vec3 halfExtents = info.getHalfExtents();
const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z);
shape = new btCylinderShapeZ(btHalfExtents);
}
break;
case SHAPE_TYPE_CIRCLE:
case SHAPE_TYPE_CYLINDER_Y: {
const glm::vec3 halfExtents = info.getHalfExtents();
const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z);
shape = new btCylinderShape(btHalfExtents);
}
break;
case SHAPE_TYPE_COMPOUND:
case SHAPE_TYPE_SIMPLE_HULL: {
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
uint32_t numSubShapes = info.getNumSubShapes();
if (numSubShapes == 1) {
if (!pointCollection.isEmpty()) {
shape = createConvexHull(pointCollection[0]);
}
} else {
auto compound = new btCompoundShape();
btTransform trans;
trans.setIdentity();
foreach (const ShapeInfo::PointList& hullPoints, pointCollection) {
btConvexHullShape* hull = createConvexHull(hullPoints);
compound->addChildShape(trans, hull);
}
shape = compound;
}
}
break;
case SHAPE_TYPE_SIMPLE_COMPOUND: {
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
uint32_t numIndices = triangleIndices.size();
uint32_t numMeshes = info.getNumSubShapes();
const uint32_t MIN_NUM_SIMPLE_COMPOUND_INDICES = 2; // END_OF_MESH_PART + END_OF_MESH
if (numMeshes > 0 && numIndices > MIN_NUM_SIMPLE_COMPOUND_INDICES) {
uint32_t i = 0;
std::vector<btConvexHullShape*> hulls;
for (auto& points : pointCollection) {
// build a hull around each part
while (i < numIndices) {
ShapeInfo::PointList hullPoints;
hullPoints.reserve(points.size());
while (i < numIndices) {
int32_t j = triangleIndices[i];
++i;
if (j == END_OF_MESH_PART) {
// end of part
break;
}
hullPoints.push_back(points[j]);
}
if (hullPoints.size() > 0) {
btConvexHullShape* hull = createConvexHull(hullPoints);
hulls.push_back(hull);
}
assert(i < numIndices);
if (triangleIndices[i] == END_OF_MESH) {
// end of mesh
++i;
break;
}
}
}
uint32_t numHulls = (uint32_t)hulls.size();
if (numHulls == 1) {
shape = hulls[0];
} else {
auto compound = new btCompoundShape();
btTransform trans;
trans.setIdentity();
for (auto hull : hulls) {
compound->addChildShape(trans, hull);
}
shape = compound;
}
}
}
break;
case SHAPE_TYPE_STATIC_MESH: {
btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info);
if (dataArray) {
shape = new StaticMeshShape(dataArray);
}
}
break;
}
if (shape) {
if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) {
// we need to apply an offset
btTransform offset;
offset.setIdentity();
offset.setOrigin(glmToBullet(info.getOffset()));
if (shape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
// this shape is already compound
// walk through the child shapes and adjust their transforms
btCompoundShape* compound = static_cast<btCompoundShape*>(shape);
int32_t numSubShapes = compound->getNumChildShapes();
for (int32_t i = 0; i < numSubShapes; ++i) {
compound->updateChildTransform(i, offset * compound->getChildTransform(i), false);
}
compound->recalculateLocalAabb();
} else {
// wrap this shape in a compound
auto compound = new btCompoundShape();
compound->addChildShape(offset, shape);
shape = compound;
}
}
}
return shape;
}
void ShapeFactory::deleteShape(const btCollisionShape* shape) {
assert(shape);
// ShapeFactory is responsible for deleting all shapes, even the const ones that are stored
// in the ShapeManager, so we must cast to non-const here when deleting.
// so we cast to non-const here when deleting memory.
btCollisionShape* nonConstShape = const_cast<btCollisionShape*>(shape);
if (nonConstShape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
btCompoundShape* compoundShape = static_cast<btCompoundShape*>(nonConstShape);
const int numChildShapes = compoundShape->getNumChildShapes();
for (int i = 0; i < numChildShapes; i ++) {
btCollisionShape* childShape = compoundShape->getChildShape(i);
if (childShape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
// recurse
ShapeFactory::deleteShape(childShape);
} else {
delete childShape;
}
}
}
delete nonConstShape;
}
// the dataArray must be created before we create the StaticMeshShape
ShapeFactory::StaticMeshShape::StaticMeshShape(btTriangleIndexVertexArray* dataArray)
: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) {
assert(dataArray);
}
ShapeFactory::StaticMeshShape::~StaticMeshShape() {
deleteStaticMeshArray(_dataArray);
_dataArray = nullptr;
}