Merge pull request #8070 from AndrewMeadows/triangle-soup-4

support btBvhTriangleMeshShape for static mesh entities
This commit is contained in:
Brad Hefta-Gaub 2016-06-23 12:17:32 -07:00 committed by GitHub
commit da74114b32
25 changed files with 468 additions and 177 deletions

View file

@ -593,23 +593,25 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
// the model is still being downloaded.
return false;
} else if (type == SHAPE_TYPE_STATIC_MESH) {
return (_model && _model->isLoaded());
}
return true;
}
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
ShapeType type = getShapeType();
glm::vec3 dimensions = getDimensions();
if (type == SHAPE_TYPE_COMPOUND) {
updateModelBounds();
// should never fall in here when collision model not fully loaded
// hence we assert that all geometries exist and are loaded
assert(_model->isLoaded() && _model->isCollisionLoaded());
const FBXGeometry& renderGeometry = _model->getFBXGeometry();
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry();
QVector<QVector<glm::vec3>>& points = info.getPoints();
points.clear();
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
pointCollection.clear();
uint32_t i = 0;
// the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect
@ -619,8 +621,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
foreach (const FBXMesh& mesh, collisionGeometry.meshes) {
// each meshPart is a convex hull
foreach (const FBXMeshPart &meshPart, mesh.parts) {
points.push_back(QVector<glm::vec3>());
QVector<glm::vec3>& pointsInPart = points[i];
pointCollection.push_back(QVector<glm::vec3>());
ShapeInfo::PointList& pointsInPart = pointCollection[i];
// run through all the triangles and (uniquely) add each point to the hull
uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size();
@ -664,7 +666,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
if (pointsInPart.size() == 0) {
qCDebug(entitiesrenderer) << "Warning -- meshPart has no faces";
points.pop_back();
pointCollection.pop_back();
continue;
}
++i;
@ -677,29 +679,136 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
// to the visual model and apply them to the collision model (without regard for the
// collision model's extents).
glm::vec3 scale = getDimensions() / renderGeometry.getUnscaledMeshExtents().size();
glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size();
// multiply each point by scale before handing the point-set off to the physics engine.
// also determine the extents of the collision model.
AABox box;
for (int i = 0; i < points.size(); i++) {
for (int j = 0; j < points[i].size(); j++) {
for (int i = 0; i < pointCollection.size(); i++) {
for (int j = 0; j < pointCollection[i].size(); j++) {
// compensate for registration
points[i][j] += _model->getOffset();
pointCollection[i][j] += _model->getOffset();
// scale so the collision points match the model points
points[i][j] *= scale;
// this next subtraction is done so we can give info the offset, which will cause
// the shape-key to change.
points[i][j] -= _model->getOffset();
box += points[i][j];
pointCollection[i][j] *= scaleToFit;
}
}
info.setParams(type, dimensions, _compoundShapeURL);
} else if (type == SHAPE_TYPE_STATIC_MESH) {
// compute meshPart local transforms
QVector<glm::mat4> localTransforms;
const FBXGeometry& geometry = _model->getFBXGeometry();
int numberOfMeshes = geometry.meshes.size();
for (int i = 0; i < numberOfMeshes; i++) {
const FBXMesh& mesh = geometry.meshes.at(i);
if (mesh.clusters.size() > 0) {
const FBXCluster& cluster = mesh.clusters.at(0);
auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex);
localTransforms.push_back(jointMatrix * cluster.inverseBindMatrix);
} else {
glm::mat4 identity;
localTransforms.push_back(identity);
}
}
glm::vec3 collisionModelDimensions = box.getDimensions();
info.setParams(type, collisionModelDimensions, _compoundShapeURL);
info.setOffset(_model->getOffset());
updateModelBounds();
// should never fall in here when collision model not fully loaded
assert(_model->isLoaded());
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
pointCollection.clear();
ShapeInfo::PointList points;
ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
auto& meshes = _model->getGeometry()->getGeometry()->getMeshes();
Extents extents;
int meshCount = 0;
for (auto& mesh : meshes) {
const gpu::BufferView& vertices = mesh->getVertexBuffer();
const gpu::BufferView& indices = mesh->getIndexBuffer();
const gpu::BufferView& parts = mesh->getPartBuffer();
// copy points
const glm::mat4& localTransform = localTransforms[meshCount];
uint32_t meshIndexOffset = (uint32_t)points.size();
gpu::BufferView::Iterator<const glm::vec3> vertexItr = vertices.cbegin<const glm::vec3>();
points.reserve((int32_t)((gpu::Size)points.size() + vertices.getNumElements()));
while (vertexItr != vertices.cend<const glm::vec3>()) {
glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr));
points.push_back(point);
extents.addPoint(point);
++vertexItr;
}
// copy triangleIndices
triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements()));
gpu::BufferView::Iterator<const model::Mesh::Part> partItr = parts.cbegin<const model::Mesh::Part>();
while (partItr != parts.cend<const model::Mesh::Part>()) {
if (partItr->_topology == model::Mesh::TRIANGLES) {
assert(partItr->_numIndices % 3 == 0);
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
auto indexEnd = indexItr + partItr->_numIndices;
while (indexItr != indexEnd) {
triangleIndices.push_back(*indexItr + meshIndexOffset);
++indexItr;
}
} else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) {
assert(partItr->_numIndices > 2);
uint32_t approxNumIndices = 3 * partItr->_numIndices;
if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) {
// we underestimated the final size of triangleIndices so we pre-emptively expand it
triangleIndices.reserve(triangleIndices.size() + approxNumIndices);
}
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
auto indexEnd = indexItr + (partItr->_numIndices - 2);
// first triangle uses the first three indices
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
// the rest use previous and next index
uint32_t triangleCount = 1;
while (indexItr != indexEnd) {
if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) {
if (triangleCount % 2 == 0) {
// even triangles use first two indices in order
triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset);
triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset);
} else {
// odd triangles swap order of first two indices
triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset);
triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset);
}
triangleIndices.push_back(*indexItr + meshIndexOffset);
++triangleCount;
}
++indexItr;
}
}
++partItr;
}
++meshCount;
}
// scale and shift
glm::vec3 extentsSize = extents.size();
glm::vec3 scaleToFit = dimensions / extentsSize;
for (int i = 0; i < 3; ++i) {
if (extentsSize[i] < 1.0e-6f) {
scaleToFit[i] = 1.0f;
}
}
for (int i = 0; i < points.size(); ++i) {
points[i] = (points[i] * scaleToFit);
}
pointCollection.push_back(points);
info.setParams(SHAPE_TYPE_STATIC_MESH, 0.5f * dimensions, _modelURL);
} else {
ModelEntityItem::computeShapeInfo(info);
info.setParams(type, 0.5f * getDimensions());
info.setParams(type, 0.5f * dimensions);
adjustShapeInfoByRegistration(info);
}
}

View file

@ -1198,7 +1198,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] {
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
QVector<QVector<glm::vec3>> points;
QVector<QVector<glm::vec3>> pointCollection;
AABox box;
glm::mat4 vtoM = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->voxelToLocalMatrix();
@ -1207,7 +1207,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
// pull each triangle in the mesh into a polyhedron which can be collided with
unsigned int i = 0;
const gpu::BufferView vertexBufferView = mesh->getVertexBuffer();
const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer();
const gpu::BufferView& indexBufferView = mesh->getIndexBuffer();
gpu::BufferView::Iterator<const uint32_t> it = indexBufferView.cbegin<uint32_t>();
@ -1241,9 +1241,9 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
pointsInPart << p3Model;
// add next convex hull
QVector<glm::vec3> newMeshPoints;
points << newMeshPoints;
pointCollection << newMeshPoints;
// add points to the new convex hull
points[i++] << pointsInPart;
pointCollection[i++] << pointsInPart;
}
} else {
unsigned int i = 0;
@ -1299,19 +1299,19 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
// add next convex hull
QVector<glm::vec3> newMeshPoints;
points << newMeshPoints;
pointCollection << newMeshPoints;
// add points to the new convex hull
points[i++] << pointsInPart;
pointCollection[i++] << pointsInPart;
}
});
}
polyVoxEntity->setCollisionPoints(points, box);
polyVoxEntity->setCollisionPoints(pointCollection, box);
});
}
void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector<QVector<glm::vec3>> points, AABox box) {
void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection pointCollection, AABox box) {
// this catches the payload from computeShapeInfoWorker
if (points.isEmpty()) {
if (pointCollection.isEmpty()) {
EntityItem::computeShapeInfo(_shapeInfo);
return;
}
@ -1325,7 +1325,7 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector<QVector<glm::
QString::number(_registrationPoint.y) + "," +
QString::number(_registrationPoint.z);
_shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey);
_shapeInfo.setConvexHulls(points);
_shapeInfo.setPointCollection(pointCollection);
_meshDirty = false;
});
}

View file

@ -123,7 +123,7 @@ public:
std::function<void(int, int, int, uint8_t)> thunk);
void setMesh(model::MeshPointer mesh);
void setCollisionPoints(const QVector<QVector<glm::vec3>> points, AABox box);
void setCollisionPoints(ShapeInfo::PointCollection points, AABox box);
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
uint8_t getVoxelInternal(int x, int y, int z);

View file

@ -1602,14 +1602,20 @@ void EntityItem::updateMass(float mass) {
void EntityItem::updateVelocity(const glm::vec3& value) {
glm::vec3 velocity = getLocalVelocity();
if (velocity != value) {
const float MIN_LINEAR_SPEED = 0.001f;
if (glm::length(value) < MIN_LINEAR_SPEED) {
velocity = ENTITY_ITEM_ZERO_VEC3;
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
if (velocity != Vectors::ZERO) {
setLocalVelocity(Vectors::ZERO);
}
} else {
velocity = value;
const float MIN_LINEAR_SPEED = 0.001f;
if (glm::length(value) < MIN_LINEAR_SPEED) {
velocity = ENTITY_ITEM_ZERO_VEC3;
} else {
velocity = value;
}
setLocalVelocity(velocity);
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
}
setLocalVelocity(velocity);
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
}
}
@ -1630,22 +1636,30 @@ void EntityItem::updateDamping(float value) {
void EntityItem::updateGravity(const glm::vec3& value) {
if (_gravity != value) {
_gravity = value;
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
_gravity = Vectors::ZERO;
} else {
_gravity = value;
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
}
}
}
void EntityItem::updateAngularVelocity(const glm::vec3& value) {
glm::vec3 angularVelocity = getLocalAngularVelocity();
if (angularVelocity != value) {
const float MIN_ANGULAR_SPEED = 0.0002f;
if (glm::length(value) < MIN_ANGULAR_SPEED) {
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
setLocalAngularVelocity(Vectors::ZERO);
} else {
angularVelocity = value;
const float MIN_ANGULAR_SPEED = 0.0002f;
if (glm::length(value) < MIN_ANGULAR_SPEED) {
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
} else {
angularVelocity = value;
}
setLocalAngularVelocity(angularVelocity);
_dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY;
}
setLocalAngularVelocity(angularVelocity);
_dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY;
}
}
@ -1679,9 +1693,17 @@ void EntityItem::updateCollisionMask(uint8_t value) {
}
void EntityItem::updateDynamic(bool value) {
if (_dynamic != value) {
_dynamic = value;
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
if (getDynamic() != value) {
// dynamic and STATIC_MESH are incompatible so we check for that case
if (value && getShapeType() == SHAPE_TYPE_STATIC_MESH) {
if (_dynamic) {
_dynamic = false;
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
}
} else {
_dynamic = value;
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
}
}
}
@ -1731,7 +1753,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
group = BULLET_COLLISION_GROUP_COLLISIONLESS;
mask = 0;
} else {
if (_dynamic) {
if (getDynamic()) {
group = BULLET_COLLISION_GROUP_DYNAMIC;
} else if (isMovingRelativeToParent() || hasActions()) {
group = BULLET_COLLISION_GROUP_KINEMATIC;

View file

@ -283,7 +283,7 @@ public:
void computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask) const;
bool getDynamic() const { return _dynamic; }
bool getDynamic() const { return SHAPE_TYPE_STATIC_MESH == getShapeType() ? false : _dynamic; }
void setDynamic(bool value) { _dynamic = value; }
virtual bool shouldBePhysical() const { return false; }
@ -348,7 +348,7 @@ public:
void updateDynamic(bool value);
void updateLifetime(float value);
void updateCreated(uint64_t value);
virtual void updateShapeType(ShapeType type) { /* do nothing */ }
virtual void setShapeType(ShapeType type) { /* do nothing */ }
uint32_t getDirtyFlags() const { return _dirtyFlags; }
void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; }

View file

@ -88,8 +88,21 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) {
_lastEdited = usecTime > _created ? usecTime : _created;
}
const char* shapeTypeNames[] = {"none", "box", "sphere", "plane", "compound", "capsule-x",
"capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"};
const char* shapeTypeNames[] = {
"none",
"box",
"sphere",
"capsule-x",
"capsule-y",
"capsule-z",
"cylinder-x",
"cylinder-y",
"cylinder-z",
"hull",
"plane",
"compound",
"static-mesh"
};
QHash<QString, ShapeType> stringToShapeTypeLookup;
@ -101,14 +114,16 @@ void buildStringToShapeTypeLookup() {
addShapeType(SHAPE_TYPE_NONE);
addShapeType(SHAPE_TYPE_BOX);
addShapeType(SHAPE_TYPE_SPHERE);
addShapeType(SHAPE_TYPE_PLANE);
addShapeType(SHAPE_TYPE_COMPOUND);
addShapeType(SHAPE_TYPE_CAPSULE_X);
addShapeType(SHAPE_TYPE_CAPSULE_Y);
addShapeType(SHAPE_TYPE_CAPSULE_Z);
addShapeType(SHAPE_TYPE_CYLINDER_X);
addShapeType(SHAPE_TYPE_CYLINDER_Y);
addShapeType(SHAPE_TYPE_CYLINDER_Z);
addShapeType(SHAPE_TYPE_HULL);
addShapeType(SHAPE_TYPE_PLANE);
addShapeType(SHAPE_TYPE_COMPOUND);
addShapeType(SHAPE_TYPE_STATIC_MESH);
}
QString getCollisionGroupAsString(uint8_t group) {

View file

@ -77,7 +77,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotationsSet, setJointRotationsSet);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet);
@ -145,7 +145,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
dataAt += bytesFromAnimation;
}
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
if (animationPropertiesChanged) {
_dirtyFlags |= Simulation::DIRTY_UPDATEABLE;
@ -257,37 +257,54 @@ void ModelEntityItem::debugDump() const {
qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL();
}
void ModelEntityItem::updateShapeType(ShapeType type) {
// BEGIN_TEMPORARY_WORKAROUND
// we have allowed inconsistent ShapeType's to be stored in SVO files in the past (this was a bug)
// but we are now enforcing the entity properties to be consistent. To make the possible we're
// introducing a temporary workaround: we will ignore ShapeType updates that conflict with the
// _compoundShapeURL.
if (hasCompoundShapeURL()) {
type = SHAPE_TYPE_COMPOUND;
}
// END_TEMPORARY_WORKAROUND
void ModelEntityItem::setShapeType(ShapeType type) {
if (type != _shapeType) {
if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
// dynamic and STATIC_MESH are incompatible
// since the shape is being set here we clear the dynamic bit
_dynamic = false;
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
}
_shapeType = type;
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
}
// virtual
ShapeType ModelEntityItem::getShapeType() const {
if (_shapeType == SHAPE_TYPE_COMPOUND) {
return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
} else {
return _shapeType;
return computeTrueShapeType();
}
ShapeType ModelEntityItem::computeTrueShapeType() const {
ShapeType type = _shapeType;
if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
// dynamic is incompatible with STATIC_MESH
// shouldn't fall in here but just in case --> fall back to COMPOUND
type = SHAPE_TYPE_COMPOUND;
}
if (type == SHAPE_TYPE_COMPOUND && !hasCompoundShapeURL()) {
// no compoundURL set --> fall back to NONE
type = SHAPE_TYPE_NONE;
}
return type;
}
void ModelEntityItem::setModelURL(const QString& url) {
if (_modelURL != url) {
_modelURL = url;
_parsedModelURL = QUrl(url);
if (_shapeType == SHAPE_TYPE_STATIC_MESH) {
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
}
}
void ModelEntityItem::setCompoundShapeURL(const QString& url) {
if (_compoundShapeURL != url) {
ShapeType oldType = computeTrueShapeType();
_compoundShapeURL = url;
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
_shapeType = _compoundShapeURL.isEmpty() ? SHAPE_TYPE_NONE : SHAPE_TYPE_COMPOUND;
if (oldType != computeTrueShapeType()) {
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
}
}

View file

@ -50,9 +50,10 @@ public:
virtual bool needsToCallUpdate() const;
virtual void debugDump() const;
void updateShapeType(ShapeType type);
void setShapeType(ShapeType type);
virtual ShapeType getShapeType() const;
// TODO: Move these to subclasses, or other appropriate abstraction
// getters/setters applicable to models and particles
@ -76,7 +77,7 @@ public:
}
// model related properties
virtual void setModelURL(const QString& url) { _modelURL = url; _parsedModelURL = QUrl(url); }
virtual void setModelURL(const QString& url);
virtual void setCompoundShapeURL(const QString& url);
// Animation related items...
@ -130,6 +131,7 @@ public:
private:
void setAnimationSettings(const QString& value); // only called for old bitstream format
ShapeType computeTrueShapeType() const;
protected:
// these are used:

View file

@ -342,7 +342,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(isEmitting, setIsEmitting);
@ -406,7 +406,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
READ_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, bool, setIsEmitting);
}
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles);
READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan);
READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate);
@ -584,7 +584,7 @@ void ParticleEffectEntityItem::debugDump() const {
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
}
void ParticleEffectEntityItem::updateShapeType(ShapeType type) {
void ParticleEffectEntityItem::setShapeType(ShapeType type) {
if (type != _shapeType) {
_shapeType = type;
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;

View file

@ -95,7 +95,7 @@ public:
void setAlphaSpread(float alphaSpread);
float getAlphaSpread() const { return _alphaSpread; }
void updateShapeType(ShapeType type);
void setShapeType(ShapeType type);
virtual ShapeType getShapeType() const { return _shapeType; }
virtual void debugDump() const;

View file

@ -73,7 +73,7 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChangedInStage = _stageProperties.setProperties(properties);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode);
@ -117,7 +117,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
bytesRead += bytesFromStage;
dataAt += bytesFromStage;
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode);

View file

@ -55,7 +55,7 @@ public:
static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; }
virtual bool isReadyToComputeShape() { return false; }
void updateShapeType(ShapeType type) { _shapeType = type; }
void setShapeType(ShapeType type) { _shapeType = type; }
virtual ShapeType getShapeType() const;
virtual bool hasCompoundShapeURL() const { return !_compoundShapeURL.isEmpty(); }

View file

@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityAdd:
case PacketType::EntityEdit:
case PacketType::EntityData:
return VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS;
return VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH;
case PacketType::AvatarIdentity:
case PacketType::AvatarData:
case PacketType::BulkAvatarData:

View file

@ -180,6 +180,7 @@ const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57;
const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58;
const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59;
const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60;
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61;
enum class AvatarMixerPacketVersion : PacketVersion {
TranslationSupport = 17,

View file

@ -159,6 +159,11 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const {
}
assert(entityTreeIsLocked());
if (_entity->getShapeType() == SHAPE_TYPE_STATIC_MESH
|| (_body && _body->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)) {
return MOTION_TYPE_STATIC;
}
if (_entity->getDynamic()) {
if (!_entity->getParentID().isNull()) {
// if something would have been dynamic but is a child of something else, force it to be kinematic, instead.

View file

@ -203,35 +203,37 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
}
}
if (flags & Simulation::DIRTY_LINEAR_VELOCITY) {
btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newLinearVelocity - _body->getLinearVelocity()).length();
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
if (_body->getCollisionShape()->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) {
if (flags & Simulation::DIRTY_LINEAR_VELOCITY) {
btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newLinearVelocity - _body->getLinearVelocity()).length();
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
}
}
}
_body->setLinearVelocity(newLinearVelocity);
_body->setLinearVelocity(newLinearVelocity);
btVector3 newGravity = glmToBullet(getObjectGravity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newGravity - _body->getGravity()).length();
if (delta > ACTIVATION_GRAVITY_DELTA ||
(delta > 0.0f && _body->getGravity().length2() == 0.0f)) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
btVector3 newGravity = glmToBullet(getObjectGravity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newGravity - _body->getGravity()).length();
if (delta > ACTIVATION_GRAVITY_DELTA ||
(delta > 0.0f && _body->getGravity().length2() == 0.0f)) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
}
}
_body->setGravity(newGravity);
}
_body->setGravity(newGravity);
}
if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) {
btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newAngularVelocity - _body->getAngularVelocity()).length();
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) {
btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity());
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
float delta = (newAngularVelocity - _body->getAngularVelocity()).length();
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
}
}
_body->setAngularVelocity(newAngularVelocity);
}
_body->setAngularVelocity(newAngularVelocity);
}
if (flags & Simulation::DIRTY_MATERIAL) {

View file

@ -217,7 +217,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
} else if (entity->isReadyToComputeShape()) {
ShapeInfo shapeInfo;
entity->computeShapeInfo(shapeInfo);
int numPoints = shapeInfo.getMaxNumPoints();
int numPoints = shapeInfo.getLargestSubshapePointCount();
if (numPoints > MAX_HULL_POINTS) {
qWarning() << "convex hull with" << numPoints
<< "points for entity" << entity->getName()
@ -231,7 +231,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
result.push_back(motionState);
entityItr = _entitiesToAddToPhysics.erase(entityItr);
} else {
//qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName();
//qWarning() << "Failed to generate new shape for entity." << entity->getName();
++entityItr;
}
} else {

View file

@ -67,7 +67,8 @@ static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = {
};
btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& points) {
// util method
btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) {
assert(points.size() > 0);
btConvexHullShape* hull = new btConvexHullShape();
@ -158,6 +159,84 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& poin
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();
assert(pointCollection.size() == 1); // should only have one mesh
const ShapeInfo::PointList& pointList = pointCollection[0];
assert(pointList.size() > 2); // should have at least one triangle's worth of points
const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
assert(triangleIndices.size() > 2); // should have at least one triangle's worth of indices
// allocate mesh buffers
btIndexedMesh mesh;
int32_t numIndices = triangleIndices.size();
const int32_t VERTICES_PER_TRIANGLE = 3;
mesh.m_numTriangles = numIndices / VERTICES_PER_TRIANGLE;
if (numIndices < INT16_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 < INT16_MAX) {
int16_t* indices = static_cast<int16_t*>((void*)(mesh.m_triangleIndexBase));
for (int32_t i = 0; i < numIndices; ++i) {
indices[i] = 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;
}
btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
btCollisionShape* shape = NULL;
int type = info.getType();
@ -179,15 +258,15 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
}
break;
case SHAPE_TYPE_COMPOUND: {
const QVector<QVector<glm::vec3>>& points = info.getPoints();
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
uint32_t numSubShapes = info.getNumSubShapes();
if (numSubShapes == 1) {
shape = createConvexHull(info.getPoints()[0]);
shape = createConvexHull(pointCollection[0]);
} else {
auto compound = new btCompoundShape();
btTransform trans;
trans.setIdentity();
foreach (QVector<glm::vec3> hullPoints, points) {
foreach (const ShapeInfo::PointList& hullPoints, pointCollection) {
btConvexHullShape* hull = createConvexHull(hullPoints);
compound->addChildShape (trans, hull);
}
@ -195,6 +274,11 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
}
}
break;
case SHAPE_TYPE_STATIC_MESH: {
btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info);
shape = new StaticMeshShape(dataArray);
}
break;
}
if (shape) {
if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) {
@ -228,3 +312,14 @@ void ShapeFactory::deleteShape(btCollisionShape* shape) {
}
delete shape;
}
// 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;
}

View file

@ -20,9 +20,22 @@
// translates between ShapeInfo and btShape
namespace ShapeFactory {
btConvexHullShape* createConvexHull(const QVector<glm::vec3>& points);
btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
void deleteShape(btCollisionShape* shape);
//btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info);
//void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray);
class StaticMeshShape : public btBvhTriangleMeshShape {
public:
StaticMeshShape() = delete;
StaticMeshShape(btTriangleIndexVertexArray* dataArray);
~StaticMeshShape();
private:
// the StaticMeshShape owns its vertex/index data
btTriangleIndexVertexArray* _dataArray;
};
};
#endif // hifi_ShapeFactory_h

View file

@ -32,15 +32,13 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
if (info.getType() == SHAPE_TYPE_NONE) {
return NULL;
}
if (info.getType() != SHAPE_TYPE_COMPOUND) {
// Very small or large non-compound objects are not supported.
float diagonal = 4.0f * glm::length2(info.getHalfExtents());
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED) {
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL;
}
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) {
// tiny shapes are not supported
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL;
}
DoubleHashKey key = info.getHash();
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
@ -66,8 +64,8 @@ bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) {
shapeRef->refCount--;
if (shapeRef->refCount == 0) {
_pendingGarbage.push_back(key);
const int MAX_GARBAGE_CAPACITY = 255;
if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) {
const int MAX_SHAPE_GARBAGE_CAPACITY = 255;
if (_pendingGarbage.size() > MAX_SHAPE_GARBAGE_CAPACITY) {
collectGarbage();
}
}

View file

@ -1002,7 +1002,7 @@ void Model::scaleToFit() {
Extents modelMeshExtents = getUnscaledMeshExtents();
// size is our "target size in world space"
// we need to set our model scale so that the extents of the mesh, fit in a cube that size...
// we need to set our model scale so that the extents of the mesh, fit in a box that size...
glm::vec3 meshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum;
glm::vec3 rescaleDimensions = _scaleToFitDimensions / meshDimensions;
setScaleInternal(rescaleDimensions);

View file

@ -16,19 +16,23 @@
#include "NumericalConstants.h" // for MILLIMETERS_PER_METER
void ShapeInfo::clear() {
_type = SHAPE_TYPE_NONE;
_halfExtents = _offset = glm::vec3(0.0f);
_url.clear();
_pointCollection.clear();
_triangleIndices.clear();
_halfExtents = glm::vec3(0.0f);
_offset = glm::vec3(0.0f);
_doubleHashKey.clear();
_type = SHAPE_TYPE_NONE;
}
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) {
_type = type;
_halfExtents = halfExtents;
switch(type) {
case SHAPE_TYPE_NONE:
_halfExtents = glm::vec3(0.0f);
break;
case SHAPE_TYPE_BOX:
_halfExtents = halfExtents;
break;
case SHAPE_TYPE_SPHERE: {
// sphere radius is max of halfExtents
@ -37,11 +41,10 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
break;
}
case SHAPE_TYPE_COMPOUND:
case SHAPE_TYPE_STATIC_MESH:
_url = QUrl(url);
_halfExtents = halfExtents;
break;
default:
_halfExtents = halfExtents;
break;
}
_doubleHashKey.clear();
@ -61,9 +64,9 @@ void ShapeInfo::setSphere(float radius) {
_doubleHashKey.clear();
}
void ShapeInfo::setConvexHulls(const QVector<QVector<glm::vec3>>& points) {
_points = points;
_type = (_points.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) {
_pointCollection = pointCollection;
_type = (_pointCollection.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
_doubleHashKey.clear();
}
@ -83,15 +86,15 @@ uint32_t ShapeInfo::getNumSubShapes() const {
if (_type == SHAPE_TYPE_NONE) {
return 0;
} else if (_type == SHAPE_TYPE_COMPOUND) {
return _points.size();
return _pointCollection.size();
}
return 1;
}
int ShapeInfo::getMaxNumPoints() const {
int ShapeInfo::getLargestSubshapePointCount() const {
int numPoints = 0;
for (int i = 0; i < _points.size(); ++i) {
int n = _points[i].size();
for (int i = 0; i < _pointCollection.size(); ++i) {
int n = _pointCollection[i].size();
if (n > numPoints) {
numPoints = n;
}
@ -178,34 +181,31 @@ const DoubleHashKey& ShapeInfo::getHash() const {
// NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance.
if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) {
bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET;
// The key is not yet cached therefore we must compute it! To this end we bypass the const-ness
// of this method by grabbing a non-const pointer to "this" and a non-const reference to _doubleHashKey.
ShapeInfo* thisPtr = const_cast<ShapeInfo*>(this);
DoubleHashKey& key = thisPtr->_doubleHashKey;
// The key is not yet cached therefore we must compute it.
// compute hash1
// TODO?: provide lookup table for hash/hash2 of _type rather than recompute?
uint32_t primeIndex = 0;
key.computeHash((uint32_t)_type, primeIndex++);
// compute hash1
uint32_t hash = key.getHash();
_doubleHashKey.computeHash((uint32_t)_type, primeIndex++);
// compute hash1
uint32_t hash = _doubleHashKey.getHash();
for (int j = 0; j < 3; ++j) {
// NOTE: 0.49f is used to bump the float up almost half a millimeter
// so the cast to int produces a round() effect rather than a floor()
hash ^= DoubleHashKey::hashFunction(
(uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f),
(uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f),
primeIndex++);
if (useOffset) {
hash ^= DoubleHashKey::hashFunction(
(uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f),
(uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f),
primeIndex++);
}
}
key.setHash(hash);
_doubleHashKey.setHash(hash);
// compute hash2
hash = key.getHash2();
hash = _doubleHashKey.getHash2();
for (int j = 0; j < 3; ++j) {
// NOTE: 0.49f is used to bump the float up almost half a millimeter
// so the cast to int produces a round() effect rather than a floor()
@ -222,16 +222,18 @@ const DoubleHashKey& ShapeInfo::getHash() const {
hash += ~(floatHash << 10);
hash = (hash << 16) | (hash >> 16);
}
key.setHash2(hash);
_doubleHashKey.setHash2(hash);
QString url = _url.toString();
if (!url.isEmpty()) {
// fold the urlHash into both parts
QByteArray baUrl = url.toLocal8Bit();
const char *cUrl = baUrl.data();
uint32_t urlHash = qChecksum(cUrl, baUrl.count());
key.setHash(key.getHash() ^ urlHash);
key.setHash2(key.getHash2() ^ urlHash);
if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_STATIC_MESH) {
QString url = _url.toString();
if (!url.isEmpty()) {
// fold the urlHash into both parts
QByteArray baUrl = url.toLocal8Bit();
const char *cUrl = baUrl.data();
uint32_t urlHash = qChecksum(cUrl, baUrl.count());
_doubleHashKey.setHash(_doubleHashKey.getHash() ^ urlHash);
_doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ urlHash);
}
}
}
return _doubleHashKey;

View file

@ -30,26 +30,32 @@ enum ShapeType {
SHAPE_TYPE_NONE,
SHAPE_TYPE_BOX,
SHAPE_TYPE_SPHERE,
SHAPE_TYPE_PLANE,
SHAPE_TYPE_COMPOUND,
SHAPE_TYPE_CAPSULE_X,
SHAPE_TYPE_CAPSULE_Y,
SHAPE_TYPE_CAPSULE_Z,
SHAPE_TYPE_CYLINDER_X,
SHAPE_TYPE_CYLINDER_Y,
SHAPE_TYPE_CYLINDER_Z,
SHAPE_TYPE_HULL,
SHAPE_TYPE_PLANE,
SHAPE_TYPE_COMPOUND,
SHAPE_TYPE_STATIC_MESH
};
class ShapeInfo {
public:
using PointList = QVector<glm::vec3>;
using PointCollection = QVector<PointList>;
using TriangleIndices = QVector<uint32_t>;
void clear();
void setParams(ShapeType type, const glm::vec3& halfExtents, QString url="");
void setBox(const glm::vec3& halfExtents);
void setSphere(float radius);
void setConvexHulls(const QVector<QVector<glm::vec3>>& points);
void setPointCollection(const PointCollection& pointCollection);
void setCapsuleY(float radius, float halfHeight);
void setOffset(const glm::vec3& offset);
@ -57,13 +63,15 @@ public:
const glm::vec3& getHalfExtents() const { return _halfExtents; }
const glm::vec3& getOffset() const { return _offset; }
QVector<QVector<glm::vec3>>& getPoints() { return _points; }
const QVector<QVector<glm::vec3>>& getPoints() const { return _points; }
uint32_t getNumSubShapes() const;
void appendToPoints (const QVector<glm::vec3>& newPoints) { _points << newPoints; }
int getMaxNumPoints() const;
PointCollection& getPointCollection() { return _pointCollection; }
const PointCollection& getPointCollection() const { return _pointCollection; }
TriangleIndices& getTriangleIndices() { return _triangleIndices; }
const TriangleIndices& getTriangleIndices() const { return _triangleIndices; }
int getLargestSubshapePointCount() const;
float computeVolume() const;
@ -74,12 +82,13 @@ public:
const DoubleHashKey& getHash() const;
protected:
ShapeType _type = SHAPE_TYPE_NONE;
QUrl _url; // url for model of convex collision hulls
PointCollection _pointCollection;
TriangleIndices _triangleIndices;
glm::vec3 _halfExtents = glm::vec3(0.0f);
glm::vec3 _offset = glm::vec3(0.0f);
DoubleHashKey _doubleHashKey;
QVector<QVector<glm::vec3>> _points; // points for convex collision hulls
QUrl _url; // url for model of convex collision hulls
mutable DoubleHashKey _doubleHashKey;
ShapeType _type = SHAPE_TYPE_NONE;
};
#endif // hifi_ShapeInfo_h

View file

@ -1646,6 +1646,7 @@
<option value="box">Box</option>
<option value="sphere">Sphere</option>
<option value="compound">Compound</option>
<option value="static-mesh">Static Mesh</option>
</select>
</div>
<div class="model-group model-section zone-section property url ">

View file

@ -194,23 +194,23 @@ void ShapeManagerTests::addCompoundShape() {
int numHullPoints = tetrahedron.size();
// compute the points of the hulls
QVector< QVector<glm::vec3> > hulls;
ShapeInfo::PointCollection pointCollection;
int numHulls = 5;
glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f);
for (int i = 0; i < numHulls; ++i) {
glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal;
QVector<glm::vec3> hull;
ShapeInfo::PointList pointList;
float radius = (float)(i + 1);
for (int j = 0; j < numHullPoints; ++j) {
glm::vec3 point = radius * tetrahedron[j] + offset;
hull.push_back(point);
pointList.push_back(point);
}
hulls.push_back(hull);
pointCollection.push_back(pointList);
}
// create the ShapeInfo
ShapeInfo info;
info.setConvexHulls(hulls);
info.setPointCollection(hulls);
// create the shape
ShapeManager shapeManager;