diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 62ab3377a8..b197e0b9e6 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -48,18 +48,22 @@ RenderableShapeEntityItem::Pointer RenderableShapeEntityItem::baseFactory(const } EntityItemPointer RenderableShapeEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return baseFactory(entityID, properties); + auto result = baseFactory(entityID, properties); + + qCDebug(entities) << "Creating RenderableShapeEntityItem( " << result->_name << " ): " << result.get() << " ID: " << result->_id; + + return result; } EntityItemPointer RenderableShapeEntityItem::boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { auto result = baseFactory(entityID, properties); - result->setShape(entity::Cube); + result->setShape(entity::Shape::Cube); return result; } EntityItemPointer RenderableShapeEntityItem::sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { auto result = baseFactory(entityID, properties); - result->setShape(entity::Sphere); + result->setShape(entity::Shape::Sphere); return result; } diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index 19341ec3e2..90de43b5f8 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,3 +1,4 @@ set(TARGET_NAME entities) setup_hifi_library(Network Script) -link_hifi_libraries(shared networking octree avatars) +link_hifi_libraries(shared networking octree avatars physics) + diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 018d8c568a..d9c48f8593 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -12,6 +12,7 @@ #include #include +#include #include "EntitiesLogging.h" #include "EntityItemProperties.h" @@ -58,7 +59,11 @@ ShapeEntityItem::Pointer ShapeEntityItem::baseFactory(const EntityItemID& entity } EntityItemPointer ShapeEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return baseFactory(entityID, properties); + auto result = baseFactory(entityID, properties); + + qCDebug(entities) << "Creating ShapeEntityItem( " << result->_name << " ): " << result.get() << " ID: " << result->_id; + + return result; } EntityItemPointer ShapeEntityItem::boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { @@ -101,6 +106,11 @@ void ShapeEntityItem::setShape(const entity::Shape& shape) { } } +//TODO_CUSACK: Move back to header prior to PN +void ShapeEntityItem::setShape( const QString &shape ) { + setShape(entity::shapeFromString(shape)); +} + bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class @@ -160,10 +170,46 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); } +void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { + + if ( _collisionShapeType == ShapeType::SHAPE_TYPE_NONE ) { + if (_shape == entity::Shape::NUM_SHAPES) + { + EntityItem::computeShapeInfo(info); + + //--EARLY EXIT--( allow default handling to process ) + return; + } + + _collisionShapeType = ShapeFactory::computeShapeType(getShape(), getDimensions()); + } + + return EntityItem::computeShapeInfo(info); +} + // This value specifes how the shape should be treated by physics calculations. // For now, all polys will act as spheres ShapeType ShapeEntityItem::getShapeType() const { - return (_shape == entity::Shape::Cube) ? SHAPE_TYPE_BOX : SHAPE_TYPE_ELLIPSOID; + //TODO_CUSACK: This needs to be retrieved from properties if possible + // or stored within a new member and set during parsing of + // the properties like setShape via set/get/readEntityProperties. + // Perhaps if the _actual_ collisionShapeType is needed (the version that's in use + // based on analysis of the shape's halfExtents when BulletLibrary collision shape was + // created as opposed to the desired ShapeType is it possible to retrieve that information)? + //if (_shape == entity::Shape::Cylinder) { + // return SHAPE_TYPE_CYLINDER_Y; + //} + + //// Original functionality: Everything not a cube, is treated like an ellipsoid/sphere + //return (_shape == entity::Shape::Cube) ? SHAPE_TYPE_BOX : SHAPE_TYPE_ELLIPSOID; + + if (_collisionShapeType == ShapeType::SHAPE_TYPE_NONE) + { + //--EARLY EXIT--( Maintain previous behavior of treating invalid as Ellipsoid/Sphere ) + return SHAPE_TYPE_ELLIPSOID; + } + + return _collisionShapeType; } void ShapeEntityItem::setColor(const rgbColor& value) { @@ -223,10 +269,12 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const void ShapeEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; - qCDebug(entities) << " shape:" << stringFromShape(_shape); + qCDebug(entities) << " name:" << _name; + qCDebug(entities) << " shape:" << stringFromShape(_shape) << " (EnumId: " << _shape << " )"; qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; qCDebug(entities) << " position:" << debugTreeVector(getPosition()); qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << "SHAPE EntityItem Ptr:" << this; } diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 96f69deb0c..f021fcf957 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -70,7 +70,7 @@ public: entity::Shape getShape() const { return _shape; } void setShape(const entity::Shape& shape); - void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); } + void setShape(const QString& shape); float getAlpha() const { return _alpha; }; void setAlpha(float alpha) { _alpha = alpha; } @@ -84,6 +84,7 @@ public: QColor getQColor() const; void setColor(const QColor& value); + void computeShapeInfo(ShapeInfo& info); ShapeType getShapeType() const override; bool shouldBePhysical() const override { return !isDead(); } @@ -100,6 +101,7 @@ protected: float _alpha { 1 }; rgbColor _color; entity::Shape _shape { entity::Shape::Sphere }; + ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_NONE }; }; #endif // hifi_ShapeEntityItem_h diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index d209667966..a9bc8b2e05 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -17,7 +17,7 @@ #include "BulletUtil.h" // These are the same normalized directions used by the btShapeHull class. -// 12 points for the face centers of a duodecohedron plus another 30 points +// 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] = { @@ -247,6 +247,124 @@ void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) { delete dataArray; } +ShapeType validateShapeType(ShapeType type, const glm::vec3 &halfExtents, btCollisionShape *outCollisionShape = nullptr) +{ + if ((type == SHAPE_TYPE_SPHERE) || (type == SHAPE_TYPE_ELLIPSOID)) + { + 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 + if (outCollisionShape) { + outCollisionShape = new btSphereShape(radius); + } + + return SHAPE_TYPE_SPHERE; + } + 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); + } + if (outCollisionShape) { + outCollisionShape = createConvexHull(points); + } + + return SHAPE_TYPE_ELLIPSOID; + } + } + else if ((type == SHAPE_TYPE_CYLINDER_X) || (type == SHAPE_TYPE_CYLINDER_Y) || (type == SHAPE_TYPE_CYLINDER_Z)) + { + const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z); + if ((halfExtents.y > halfExtents.x) && (halfExtents.y > halfExtents.z)) { + if (outCollisionShape) { + outCollisionShape = new btCylinderShape(btHalfExtents); + } + + return SHAPE_TYPE_CYLINDER_Y; + } + else if (halfExtents.x > halfExtents.z) { + if (outCollisionShape) { + outCollisionShape = new btCylinderShapeX(btHalfExtents); + } + + return SHAPE_TYPE_CYLINDER_X; + } + else if (halfExtents.z > halfExtents.x) { + if (outCollisionShape) { + outCollisionShape = new btCylinderShapeZ(btHalfExtents); + } + + return SHAPE_TYPE_CYLINDER_Z; + } + else //...there was no major axis, treat as a sphere + { + ShapeType cylinderFallback = validateShapeType(SHAPE_TYPE_ELLIPSOID, halfExtents, outCollisionShape); + return cylinderFallback; + } + } + + //Got here, then you are what you are along with outCollisionShape + return type; +} + +ShapeType ShapeFactory::computeShapeType(entity::Shape shape, const glm::vec3 &entityDimensions) { + if ( shape == entity::Shape::NUM_SHAPES ) { + //--EARLY EXIT-- + return SHAPE_TYPE_NONE; + } + + const glm::vec3 halfExtents = entityDimensions * 0.5f; + switch (shape){ + case entity::Shape::Triangle: { + //TODO_CUSACK: Implement this + return validateShapeType(SHAPE_TYPE_ELLIPSOID, halfExtents); + } + + //Note: Intentional Fallthrough from Quad to Cube + case entity::Shape::Quad: + case entity::Shape::Cube: { + return SHAPE_TYPE_BOX; + } + + //Note: Intentional Fallthrough from Hexagon to Sphere + case entity::Shape::Hexagon: + case entity::Shape::Octagon: + case entity::Shape::Circle: + case entity::Shape::Sphere: { + return validateShapeType(SHAPE_TYPE_SPHERE, halfExtents); + } + + case entity::Shape::Cylinder: { + return validateShapeType(SHAPE_TYPE_CYLINDER_Y, halfExtents); + } + + //Note: Intentional Fallthrough from Tetrahedron to Icosahedron + case entity::Shape::Tetrahedron: + case entity::Shape::Octahedron: + case entity::Shape::Dodecahedron: + case entity::Shape::Icosahedron: { + + //TODO_CUSACK: Implement the hedrons + return validateShapeType( SHAPE_TYPE_ELLIPSOID, halfExtents ); + } + + //Note: Intentional Fallthrough from Torus to default. + case entity::Shape::Torus: + case entity::Shape::Cone: { + + // These types are currently unsupported + } + default: + return SHAPE_TYPE_NONE; + } + +} + const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { btCollisionShape* shape = NULL; int type = info.getType(); @@ -255,30 +373,33 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) 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_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_SPHERE: 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); - } + //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); + //} + + validateShapeType(SHAPE_TYPE_ELLIPSOID, halfExtents, shape); } break; case SHAPE_TYPE_CAPSULE_Y: { @@ -288,6 +409,42 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) 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: + case SHAPE_TYPE_CYLINDER_Z: + case SHAPE_TYPE_CYLINDER_Y: { + // TODO_CUSACK: Should allow for minor variance along axes. + const glm::vec3 halfExtents = info.getHalfExtents(); + //const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z); + //if ((halfExtents.y > halfExtents.x) && (halfExtents.y > halfExtents.z)) { + // shape = new btCylinderShape(btHalfExtents); + //} + //else if (halfExtents.x > halfExtents.z) { + // shape = new btCylinderShapeX(btHalfExtents); + //} + //else if (halfExtents.z > halfExtents.x) { + // shape = new btCylinderShapeZ(btHalfExtents); + //} + //else //...there was no major axis, treat as a sphere + //{ + // //TODO_CUSACK: Shunt to ELLIPSOID handling + //} + validateShapeType(SHAPE_TYPE_CYLINDER_Y, halfExtents, shape); + } + break; case SHAPE_TYPE_COMPOUND: case SHAPE_TYPE_SIMPLE_HULL: { const ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); diff --git a/libraries/physics/src/ShapeFactory.h b/libraries/physics/src/ShapeFactory.h index 2bf79f390c..52b448ee1d 100644 --- a/libraries/physics/src/ShapeFactory.h +++ b/libraries/physics/src/ShapeFactory.h @@ -15,11 +15,14 @@ #include #include +#include //< Needed for entity::Shape #include // translates between ShapeInfo and btShape namespace ShapeFactory { + + ShapeType computeShapeType( entity::Shape shape, const glm::vec3 &entityDimensions); const btCollisionShape* createShapeFromInfo(const ShapeInfo& info); void deleteShape(const btCollisionShape* shape); diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 496e94f8bd..e40d91379c 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -136,7 +136,10 @@ float ShapeInfo::computeVolume() const { } case SHAPE_TYPE_CAPSULE_Y: { float radius = _halfExtents.x; - volume = PI * radius * radius * (2.0f * (_halfExtents.y - _halfExtents.x) + 4.0f * radius / 3.0f); + // Need to offset halfExtents.y by x to account for the system treating + // the y extent of the capsule as the cylindrical height + spherical radius. + float cylinderHeight = 2.0f * (_halfExtents.y - _halfExtents.x); + volume = PI * radius * radius * (cylinderHeight + 4.0f * radius / 3.0f); break; } default: diff --git a/scripts/developer/tests/basicEntityTest/entitySpawner.js b/scripts/developer/tests/basicEntityTest/entitySpawner.js index 538e9145f5..fa5c9291cb 100644 --- a/scripts/developer/tests/basicEntityTest/entitySpawner.js +++ b/scripts/developer/tests/basicEntityTest/entitySpawner.js @@ -8,7 +8,9 @@ var SCRIPT_URL = Script.resolvePath("myEntityScript.js") var myEntity = Entities.addEntity({ - type: "Sphere", + name: "Cusack_Testing", + type: "Shape", + shapeType: "Cylinder", color: { red: 200, green: 10, diff --git a/scripts/developer/tests/basicEntityTest/shapeSpawner.js b/scripts/developer/tests/basicEntityTest/shapeSpawner.js new file mode 100644 index 0000000000..f93072a582 --- /dev/null +++ b/scripts/developer/tests/basicEntityTest/shapeSpawner.js @@ -0,0 +1,33 @@ + var orientation = Camera.getOrientation(); + orientation = Quat.safeEulerAngles(orientation); + orientation.x = 0; + orientation = Quat.fromVec3Degrees(orientation); + var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getForward(orientation))); + + // Math.random ensures no caching of script + var SCRIPT_URL = Script.resolvePath("myEntityScript.js") + + var myEntity = Entities.addEntity({ + name: "ShapeSpawnTest", + type: "Shape", + shape: "Cylinder", + color: { + red: 200, + green: 10, + blue: 200 + }, + position: center, + dimensions: { + x: 1, + y: 1, + z: 1 + }, + script: SCRIPT_URL + }) + + + function cleanup() { + // Entities.deleteEntity(myEntity); + } + + Script.scriptEnding.connect(cleanup);