diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ad216458bd..84ce1f7efe 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -531,6 +531,42 @@ void Application::paintGL() { _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation()); + glm::vec3 planeNormal = _myCamera.getTargetRotation() * IDENTITY_FRONT; + const float BASE_PUSHBACK_RADIUS = 0.25f; + float pushbackRadius = _myCamera.getNearClip() + _myAvatar->getScale() * BASE_PUSHBACK_RADIUS; + glm::vec4 plane(planeNormal, -glm::dot(planeNormal, _myCamera.getTargetPosition()) - pushbackRadius); + + // push camera out of any intersecting avatars + float pushback = 0.0f; + foreach (const AvatarSharedPointer& avatarData, _avatarManager.getAvatarHash()) { + Avatar* avatar = static_cast(avatarData.data()); + if (avatar->isMyAvatar()) { + continue; + } + if (glm::distance(avatar->getPosition(), _myCamera.getTargetPosition()) > + avatar->getBoundingRadius() + pushbackRadius) { + continue; + } + float angle = angleBetween(avatar->getPosition() - _myCamera.getTargetPosition(), planeNormal); + if (angle > PI_OVER_TWO) { + continue; + } + float scale = 1.0f - angle / PI_OVER_TWO; + scale = qMin(1.0f, scale * 2.5f); + static CollisionList collisions(64); + collisions.clear(); + if (!avatar->findPlaneCollisions(plane, collisions)) { + continue; + } + for (int i = 0; i < collisions.size(); i++) { + pushback = qMax(pushback, glm::length(collisions.getCollision(i)->_penetration) * scale); + } + } + const float MAX_PUSHBACK = 0.35f; + const float PUSHBACK_DECAY = 0.5f; + _myCamera.setDistance(qMax(qMin(pushback, MAX_PUSHBACK * _myAvatar->getScale()), + _myCamera.getDistance() * PUSHBACK_DECAY)); + } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition()); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 4a6d0fbcf2..475e7a1abc 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -529,6 +529,11 @@ bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penet //return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions); } +bool Avatar::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) { + return _skeletonModel.findPlaneCollisions(plane, collisions) || + getHead()->getFaceModel().findPlaneCollisions(plane, collisions); +} + void Avatar::updateShapePositions() { _skeletonModel.updateShapePositions(); Model& headModel = getHead()->getFaceModel(); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index cad0ddcdf0..ca05e5dbbf 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -119,6 +119,12 @@ public: bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions, int skeletonSkipIndex = -1); + /// Checks for penetration between the described plane and the avatar. + /// \param plane the penetration plane + /// \param collisions[out] a list to which collisions get appended + /// \return whether or not the plane penetrated + bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions); + /// Checks for collision between the a spherical particle and the avatar (including paddle hands) /// \param collisionCenter the center of particle's bounding sphere /// \param collisionRadius the radius of particle's bounding sphere diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7a05c593db..a4f4cce79d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -647,7 +647,7 @@ void MyAvatar::renderBody(RenderMode renderMode) { _skeletonModel.render(1.0f, modelRenderMode); // Render head so long as the camera isn't inside it - const float RENDER_HEAD_CUTOFF_DISTANCE = 0.40f; + const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; Camera* myCamera = Application::getInstance()->getCamera(); if (renderMode != NORMAL_RENDER_MODE || (glm::length(myCamera->getPosition() - getHead()->calculateAverageEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale)) { diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 29a663ef25..2d47a077b7 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -634,6 +634,21 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi return collided; } +bool Model::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) { + bool collided = false; + PlaneShape planeShape(plane); + for (int i = 0; i < _jointShapes.size(); i++) { + if (ShapeCollider::collideShapes(&planeShape, _jointShapes[i], collisions)) { + CollisionInfo* collision = collisions.getLastCollision(); + collision->_type = MODEL_COLLISION; + collision->_data = (void*)(this); + collision->_flags = i; + collided = true; + } + } + return collided; +} + class Blender : public QRunnable { public: diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 30625cc16f..65b79fffdd 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -176,6 +176,8 @@ public: bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions, int skipIndex = -1); + + bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions); /// \param collision details about the collisions /// \return true if the collision is against a moveable joint diff --git a/libraries/shared/src/PlaneShape.cpp b/libraries/shared/src/PlaneShape.cpp new file mode 100644 index 0000000000..a8b4468c93 --- /dev/null +++ b/libraries/shared/src/PlaneShape.cpp @@ -0,0 +1,36 @@ +// +// PlaneShape.cpp +// libraries/shared/src +// +// Created by Andrzej Kapolka on 4/10/2014. +// 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 "PlaneShape.h" +#include "SharedUtil.h" + +const glm::vec3 UNROTATED_NORMAL(0.0f, 1.0f, 0.0f); + +PlaneShape::PlaneShape(const glm::vec4& coefficients) : + Shape(Shape::PLANE_SHAPE) { + + glm::vec3 normal = glm::vec3(coefficients); + _position = -normal * coefficients.w; + + float angle = acosf(glm::dot(normal, UNROTATED_NORMAL)); + if (angle > EPSILON) { + if (angle > PI - EPSILON) { + _rotation = glm::angleAxis(PI, glm::vec3(1.0f, 0.0f, 0.0f)); + } else { + _rotation = glm::angleAxis(angle, glm::normalize(glm::cross(UNROTATED_NORMAL, normal))); + } + } +} + +glm::vec4 PlaneShape::getCoefficients() const { + glm::vec3 normal = _rotation * UNROTATED_NORMAL; + return glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _position)); +} diff --git a/libraries/shared/src/PlaneShape.h b/libraries/shared/src/PlaneShape.h new file mode 100644 index 0000000000..524d53ec73 --- /dev/null +++ b/libraries/shared/src/PlaneShape.h @@ -0,0 +1,24 @@ +// +// PlaneShape.h +// libraries/shared/src +// +// Created by Andrzej Kapolka on 4/9/2014. +// 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 +// + +#ifndef hifi_PlaneShape_h +#define hifi_PlaneShape_h + +#include "Shape.h" + +class PlaneShape : public Shape { +public: + PlaneShape(const glm::vec4& coefficients = glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); + + glm::vec4 getCoefficients() const; +}; + +#endif // hifi_PlaneShape_h diff --git a/libraries/shared/src/Shape.h b/libraries/shared/src/Shape.h index fd16eafeae..87b84ea73b 100644 --- a/libraries/shared/src/Shape.h +++ b/libraries/shared/src/Shape.h @@ -22,6 +22,7 @@ public: UNKNOWN_SHAPE = 0, SPHERE_SHAPE, CAPSULE_SHAPE, + PLANE_SHAPE, BOX_SHAPE, LIST_SHAPE }; diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index 32789aa388..c53c7fab7d 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -34,6 +34,8 @@ bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& coll return sphereSphere(sphereA, static_cast(shapeB), collisions); } else if (typeB == Shape::CAPSULE_SHAPE) { return sphereCapsule(sphereA, static_cast(shapeB), collisions); + } else if (typeB == Shape::PLANE_SHAPE) { + return spherePlane(sphereA, static_cast(shapeB), collisions); } } else if (typeA == Shape::CAPSULE_SHAPE) { const CapsuleShape* capsuleA = static_cast(shapeA); @@ -41,6 +43,17 @@ bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& coll return capsuleSphere(capsuleA, static_cast(shapeB), collisions); } else if (typeB == Shape::CAPSULE_SHAPE) { return capsuleCapsule(capsuleA, static_cast(shapeB), collisions); + } else if (typeB == Shape::PLANE_SHAPE) { + return capsulePlane(capsuleA, static_cast(shapeB), collisions); + } + } else if (typeA == Shape::PLANE_SHAPE) { + const PlaneShape* planeA = static_cast(shapeA); + if (typeB == Shape::SPHERE_SHAPE) { + return planeSphere(planeA, static_cast(shapeB), collisions); + } else if (typeB == Shape::CAPSULE_SHAPE) { + return planeCapsule(planeA, static_cast(shapeB), collisions); + } else if (typeB == Shape::PLANE_SHAPE) { + return planePlane(planeA, static_cast(shapeB), collisions); } } else if (typeA == Shape::LIST_SHAPE) { const ListShape* listA = static_cast(shapeA); @@ -48,6 +61,8 @@ bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& coll return listSphere(listA, static_cast(shapeB), collisions); } else if (typeB == Shape::CAPSULE_SHAPE) { return listCapsule(listA, static_cast(shapeB), collisions); + } else if (typeB == Shape::PLANE_SHAPE) { + return listPlane(listA, static_cast(shapeB), collisions); } } return false; @@ -168,6 +183,20 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col return false; } +bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions) { + glm::vec3 penetration; + if (findSpherePlanePenetration(sphereA->getPosition(), sphereA->getRadius(), planeB->getCoefficients(), penetration)) { + CollisionInfo* collision = collisions.getNewCollision(); + if (!collision) { + return false; // collision list is full + } + collision->_penetration = penetration; + collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * glm::normalize(penetration); + return true; + } + return false; +} + bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions) { // find sphereB's closest approach to axis of capsuleA glm::vec3 AB = capsuleA->getPosition() - sphereB->getPosition(); @@ -374,6 +403,63 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, return false; } +bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, CollisionList& collisions) { + glm::vec3 start, end, penetration; + capsuleA->getStartPoint(start); + capsuleA->getEndPoint(end); + glm::vec4 plane = planeB->getCoefficients(); + if (findCapsulePlanePenetration(start, end, capsuleA->getRadius(), plane, penetration)) { + CollisionInfo* collision = collisions.getNewCollision(); + if (!collision) { + return false; // collision list is full + } + collision->_penetration = penetration; + glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end; + collision->_contactPoint = deepestEnd + capsuleA->getRadius() * glm::normalize(penetration); + return true; + } + return false; +} + +bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions) { + glm::vec3 penetration; + if (findSpherePlanePenetration(sphereB->getPosition(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) { + CollisionInfo* collision = collisions.getNewCollision(); + if (!collision) { + return false; // collision list is full + } + collision->_penetration = -penetration; + collision->_contactPoint = sphereB->getPosition() + + (sphereB->getRadius() / glm::length(penetration) - 1.0f) * penetration; + return true; + } + return false; +} + +bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, CollisionList& collisions) { + glm::vec3 start, end, penetration; + capsuleB->getStartPoint(start); + capsuleB->getEndPoint(end); + glm::vec4 plane = planeA->getCoefficients(); + if (findCapsulePlanePenetration(start, end, capsuleB->getRadius(), plane, penetration)) { + CollisionInfo* collision = collisions.getNewCollision(); + if (!collision) { + return false; // collision list is full + } + collision->_penetration = -penetration; + glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end; + collision->_contactPoint = deepestEnd + (capsuleB->getRadius() / glm::length(penetration) - 1.0f) * penetration; + return true; + } + return false; +} + +bool planePlane(const PlaneShape* planeA, const PlaneShape* planeB, CollisionList& collisions) { + // technically, planes always collide unless they're parallel and not coincident; however, that's + // not going to give us any useful information + return false; +} + bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionList& collisions) { bool touching = false; for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) { @@ -383,6 +469,8 @@ bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionLis touching = sphereSphere(sphereA, static_cast(subShape), collisions) || touching; } else if (subType == Shape::CAPSULE_SHAPE) { touching = sphereCapsule(sphereA, static_cast(subShape), collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = spherePlane(sphereA, static_cast(subShape), collisions) || touching; } } return touching; @@ -397,6 +485,24 @@ bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, Collision touching = capsuleSphere(capsuleA, static_cast(subShape), collisions) || touching; } else if (subType == Shape::CAPSULE_SHAPE) { touching = capsuleCapsule(capsuleA, static_cast(subShape), collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = capsulePlane(capsuleA, static_cast(subShape), collisions) || touching; + } + } + return touching; +} + +bool planeList(const PlaneShape* planeA, const ListShape* listB, CollisionList& collisions) { + bool touching = false; + for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) { + const Shape* subShape = listB->getSubShape(i); + int subType = subShape->getType(); + if (subType == Shape::SPHERE_SHAPE) { + touching = planeSphere(planeA, static_cast(subShape), collisions) || touching; + } else if (subType == Shape::CAPSULE_SHAPE) { + touching = planeCapsule(planeA, static_cast(subShape), collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = planePlane(planeA, static_cast(subShape), collisions) || touching; } } return touching; @@ -411,6 +517,8 @@ bool listSphere(const ListShape* listA, const SphereShape* sphereB, CollisionLis touching = sphereSphere(static_cast(subShape), sphereB, collisions) || touching; } else if (subType == Shape::CAPSULE_SHAPE) { touching = capsuleSphere(static_cast(subShape), sphereB, collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = planeSphere(static_cast(subShape), sphereB, collisions) || touching; } } return touching; @@ -425,6 +533,24 @@ bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, Collision touching = sphereCapsule(static_cast(subShape), capsuleB, collisions) || touching; } else if (subType == Shape::CAPSULE_SHAPE) { touching = capsuleCapsule(static_cast(subShape), capsuleB, collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = planeCapsule(static_cast(subShape), capsuleB, collisions) || touching; + } + } + return touching; +} + +bool listPlane(const ListShape* listA, const PlaneShape* planeB, CollisionList& collisions) { + bool touching = false; + for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { + const Shape* subShape = listA->getSubShape(i); + int subType = subShape->getType(); + if (subType == Shape::SPHERE_SHAPE) { + touching = spherePlane(static_cast(subShape), planeB, collisions) || touching; + } else if (subType == Shape::CAPSULE_SHAPE) { + touching = capsulePlane(static_cast(subShape), planeB, collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = planePlane(static_cast(subShape), planeB, collisions) || touching; } } return touching; diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h index 0b06b95029..d554775e7b 100644 --- a/libraries/shared/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -15,6 +15,7 @@ #include "CapsuleShape.h" #include "CollisionInfo.h" #include "ListShape.h" +#include "PlaneShape.h" #include "SharedUtil.h" #include "SphereShape.h" @@ -44,6 +45,12 @@ namespace ShapeCollider { /// \return true if shapes collide bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions); + /// \param sphereA pointer to first shape + /// \param planeB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions); + /// \param capsuleA pointer to first shape /// \param sphereB pointer to second shape /// \param[out] collisions where to append collision details @@ -56,6 +63,30 @@ namespace ShapeCollider { /// \return true if shapes collide bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionList& collisions); + /// \param capsuleA pointer to first shape + /// \param planeB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, CollisionList& collisions); + + /// \param planeA pointer to first shape + /// \param sphereB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions); + + /// \param planeA pointer to first shape + /// \param capsuleB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, CollisionList& collisions); + + /// \param planeA pointer to first shape + /// \param planeB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool planePlane(const PlaneShape* planeA, const PlaneShape* planeB, CollisionList& collisions); + /// \param sphereA pointer to first shape /// \param listB pointer to second shape /// \param[out] collisions where to append collision details @@ -68,6 +99,12 @@ namespace ShapeCollider { /// \return true if shapes collide bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, CollisionList& collisions); + /// \param planeA pointer to first shape + /// \param listB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool planeList(const PlaneShape* planeA, const ListShape* listB, CollisionList& collisions); + /// \param listA pointer to first shape /// \param sphereB pointer to second shape /// \param[out] collisions where to append collision details @@ -80,6 +117,12 @@ namespace ShapeCollider { /// \return true if shapes collide bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, CollisionList& collisions); + /// \param listA pointer to first shape + /// \param planeB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool listPlane(const ListShape* listA, const PlaneShape* planeB, CollisionList& collisions); + /// \param listA pointer to first shape /// \param capsuleB pointer to second shape /// \param[out] collisions where to append collision details