diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index eb62dacf79..e65f3968e0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -449,7 +449,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif if (nodeInterestList.size() > 0) { DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL; - unsigned int dataMTU = dtlsSession ? gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE; + int dataMTU = dtlsSession ? (int)gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE; // if the node has any interest types, send back those nodes as well foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) { diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index ace7ae5b62..c182f6e842 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -480,7 +480,7 @@ void Audio::handleAudioInput() { float thisSample = 0; int samplesOverNoiseGate = 0; - const float NOISE_GATE_HEIGHT = 7.f; + const float NOISE_GATE_HEIGHT = 7.0f; const int NOISE_GATE_WIDTH = 5; const int NOISE_GATE_CLOSE_FRAME_DELAY = 5; const int NOISE_GATE_FRAMES_TO_AVERAGE = 5; @@ -490,7 +490,7 @@ void Audio::handleAudioInput() { // // Check clipping, adjust DC offset, and check if should open noise gate // - float measuredDcOffset = 0.f; + float measuredDcOffset = 0.0f; // Increment the time since the last clip if (_timeSinceLastClip >= 0.0f) { _timeSinceLastClip += (float) NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / (float) SAMPLE_RATE; @@ -500,7 +500,7 @@ void Audio::handleAudioInput() { measuredDcOffset += monoAudioSamples[i]; monoAudioSamples[i] -= (int16_t) _dcOffset; thisSample = fabsf(monoAudioSamples[i]); - if (thisSample >= (32767.f * CLIPPING_THRESHOLD)) { + if (thisSample >= (32767.0f * CLIPPING_THRESHOLD)) { _timeSinceLastClip = 0.0f; } loudness += thisSample; @@ -511,18 +511,18 @@ void Audio::handleAudioInput() { } measuredDcOffset /= NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; - if (_dcOffset == 0.f) { + if (_dcOffset == 0.0f) { // On first frame, copy over measured offset _dcOffset = measuredDcOffset; } else { - _dcOffset = DC_OFFSET_AVERAGING * _dcOffset + (1.f - DC_OFFSET_AVERAGING) * measuredDcOffset; + _dcOffset = DC_OFFSET_AVERAGING * _dcOffset + (1.0f - DC_OFFSET_AVERAGING) * measuredDcOffset; } // Add tone injection if enabled - const float TONE_FREQ = 220.f / SAMPLE_RATE * TWO_PI; - const float QUARTER_VOLUME = 8192.f; + const float TONE_FREQ = 220.0f / SAMPLE_RATE * TWO_PI; + const float QUARTER_VOLUME = 8192.0f; if (_toneInjectionEnabled) { - loudness = 0.f; + loudness = 0.0f; for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) { monoAudioSamples[i] = QUARTER_VOLUME * sinf(TONE_FREQ * (float)(i + _proceduralEffectSample)); loudness += fabsf(monoAudioSamples[i]); @@ -532,7 +532,7 @@ void Audio::handleAudioInput() { // If Noise Gate is enabled, check and turn the gate on and off if (!_toneInjectionEnabled && _noiseGateEnabled) { - float averageOfAllSampleFrames = 0.f; + float averageOfAllSampleFrames = 0.0f; _noiseSampleFrames[_noiseGateSampleCounter++] = _lastInputLoudness; if (_noiseGateSampleCounter == NUMBER_OF_NOISE_SAMPLE_FRAMES) { float smallestSample = FLT_MAX; @@ -659,9 +659,9 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { _stdev.reset(); // Set jitter buffer to be a multiple of the measured standard deviation const int MAX_JITTER_BUFFER_SAMPLES = _ringBuffer.getSampleCapacity() / 2; - const float NUM_STANDARD_DEVIATIONS = 3.f; + const float NUM_STANDARD_DEVIATIONS = 3.0f; if (Menu::getInstance()->getAudioJitterBufferSamples() == 0) { - float newJitterBufferSamples = (NUM_STANDARD_DEVIATIONS * _measuredJitter) / 1000.f * SAMPLE_RATE; + float newJitterBufferSamples = (NUM_STANDARD_DEVIATIONS * _measuredJitter) / 1000.0f * SAMPLE_RATE; setJitterBufferSamples(glm::clamp((int)newJitterBufferSamples, 0, MAX_JITTER_BUFFER_SAMPLES)); } } @@ -900,10 +900,10 @@ void Audio::toggleAudioSpatialProcessing() { void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) { float sample; const float COLLISION_SOUND_CUTOFF_LEVEL = 0.01f; - const float COLLISION_SOUND_MAX_VOLUME = 1000.f; + const float COLLISION_SOUND_MAX_VOLUME = 1000.0f; const float UP_MAJOR_FIFTH = powf(1.5f, 4.0f); - const float DOWN_TWO_OCTAVES = 4.f; - const float DOWN_FOUR_OCTAVES = 16.f; + const float DOWN_TWO_OCTAVES = 4.0f; + const float DOWN_FOUR_OCTAVES = 16.0f; float t; if (_collisionSoundMagnitude > COLLISION_SOUND_CUTOFF_LEVEL) { for (int i = 0; i < numSamples; i++) { @@ -933,12 +933,12 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) { _proceduralEffectSample += numSamples; // Add a drum sound - const float MAX_VOLUME = 32000.f; - const float MAX_DURATION = 2.f; + const float MAX_VOLUME = 32000.0f; + const float MAX_DURATION = 2.0f; const float MIN_AUDIBLE_VOLUME = 0.001f; const float NOISE_MAGNITUDE = 0.02f; float frequency = (_drumSoundFrequency / SAMPLE_RATE) * TWO_PI; - if (_drumSoundVolume > 0.f) { + if (_drumSoundVolume > 0.0f) { for (int i = 0; i < numSamples; i++) { t = (float) _drumSoundSample + (float) i; sample = sinf(t * frequency); @@ -958,12 +958,12 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) { _localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); - _drumSoundVolume *= (1.f - _drumSoundDecay); + _drumSoundVolume *= (1.0f - _drumSoundDecay); } _drumSoundSample += numSamples; - _drumSoundDuration = glm::clamp(_drumSoundDuration - (AUDIO_CALLBACK_MSECS / 1000.f), 0.f, MAX_DURATION); - if (_drumSoundDuration == 0.f || (_drumSoundVolume < MIN_AUDIBLE_VOLUME)) { - _drumSoundVolume = 0.f; + _drumSoundDuration = glm::clamp(_drumSoundDuration - (AUDIO_CALLBACK_MSECS / 1000.0f), 0.0f, MAX_DURATION); + if (_drumSoundDuration == 0.0f || (_drumSoundVolume < MIN_AUDIBLE_VOLUME)) { + _drumSoundVolume = 0.0f; } } } @@ -996,7 +996,7 @@ void Audio::renderToolBox(int x, int y, bool boxed) { if (boxed) { - bool isClipping = ((getTimeSinceLastClip() > 0.f) && (getTimeSinceLastClip() < 1.f)); + bool isClipping = ((getTimeSinceLastClip() > 0.0f) && (getTimeSinceLastClip() < 1.0f)); const int BOX_LEFT_PADDING = 5; const int BOX_TOP_PADDING = 10; const int BOX_WIDTH = 266; @@ -1007,9 +1007,9 @@ void Audio::renderToolBox(int x, int y, bool boxed) { glBindTexture(GL_TEXTURE_2D, _boxTextureId); if (isClipping) { - glColor3f(1.f,0.f,0.f); + glColor3f(1.0f, 0.0f, 0.0f); } else { - glColor3f(.41f,.41f,.41f); + glColor3f(0.41f, 0.41f, 0.41f); } glBegin(GL_QUADS); @@ -1086,10 +1086,8 @@ void Audio::addBufferToScope( // Short int pointer to mapped samples in byte array int16_t* destination = (int16_t*) byteArray.data(); - for (int i = 0; i < NETWORK_SAMPLES_PER_FRAME; i++) { - + for (unsigned int i = 0; i < NETWORK_SAMPLES_PER_FRAME; i++) { sample = (float)source[i * sourceNumberOfChannels + sourceChannel]; - if (sample > 0) { value = (int16_t)(multiplier * logf(sample)); } else if (sample < 0) { @@ -1097,7 +1095,6 @@ void Audio::addBufferToScope( } else { value = 0; } - destination[i + frameOffset] = value; } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b159593df0..fb547da13e 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -335,15 +335,17 @@ void MyAvatar::simulate(float deltaTime) { radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f)); radius *= COLLISION_RADIUS_SCALAR; } - - if (_collisionFlags & COLLISION_GROUP_ENVIRONMENT) { - updateCollisionWithEnvironment(deltaTime, radius); - } - if (_collisionFlags & COLLISION_GROUP_VOXELS) { - updateCollisionWithVoxels(deltaTime, radius); - } - if (_collisionFlags & COLLISION_GROUP_AVATARS) { - updateCollisionWithAvatars(deltaTime); + if (_collisionFlags) { + updateShapePositions(); + if (_collisionFlags & COLLISION_GROUP_ENVIRONMENT) { + updateCollisionWithEnvironment(deltaTime, radius); + } + if (_collisionFlags & COLLISION_GROUP_VOXELS) { + updateCollisionWithVoxels(deltaTime, radius); + } + if (_collisionFlags & COLLISION_GROUP_AVATARS) { + updateCollisionWithAvatars(deltaTime); + } } } @@ -798,19 +800,21 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) { } } +static CollisionList myCollisions(64); + void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { - const float VOXEL_ELASTICITY = 0.4f; - const float VOXEL_DAMPING = 0.0f; - const float VOXEL_COLLISION_FREQUENCY = 0.5f; - glm::vec3 penetration; - float pelvisFloatingHeight = getPelvisFloatingHeight(); - if (Application::getInstance()->getVoxelTree()->findCapsulePenetration( - _position - glm::vec3(0.0f, pelvisFloatingHeight - radius, 0.0f), - _position + glm::vec3(0.0f, getSkeletonHeight() - pelvisFloatingHeight + radius, 0.0f), radius, penetration)) { - _lastCollisionPosition = _position; - updateCollisionSound(penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); - applyHardCollision(penetration, VOXEL_ELASTICITY, VOXEL_DAMPING); - } + myCollisions.clear(); + const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); + if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions)) { + const float VOXEL_ELASTICITY = 0.4f; + const float VOXEL_DAMPING = 0.0f; + for (int i = 0; i < myCollisions.size(); ++i) { + CollisionInfo* collision = myCollisions[i]; + applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING); + } + const float VOXEL_COLLISION_FREQUENCY = 0.5f; + updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); + } } void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity, float damping) { @@ -915,7 +919,6 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { // no need to compute a bunch of stuff if we have one or fewer avatars return; } - updateShapePositions(); float myBoundingRadius = getBoundingRadius(); const float BODY_COLLISION_RESOLUTION_FACTOR = glm::max(1.0f, deltaTime / BODY_COLLISION_RESOLUTION_TIMESCALE); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 335071d669..64295cb915 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -193,6 +193,8 @@ public: /// Sets blended vertices computed in a separate thread. void setBlendedVertices(const QVector& vertices, const QVector& normals); + const CapsuleShape& getBoundingShape() const { return _boundingShape; } + protected: QSharedPointer _geometry; diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index 1765a5ea1a..3f9b0137ef 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -39,7 +39,8 @@ ScriptEditorWidget::ScriptEditorWidget() : setTitleBarWidget(new QWidget()); QFontMetrics fm(_scriptEditorWidgetUI->scriptEdit->font()); _scriptEditorWidgetUI->scriptEdit->setTabStopWidth(fm.width('0') * 4); - ScriptHighlighting* highlighting = new ScriptHighlighting(_scriptEditorWidgetUI->scriptEdit->document()); + // We create a new ScriptHighligting QObject and provide it with a parent so this is NOT a memory leak. + new ScriptHighlighting(_scriptEditorWidgetUI->scriptEdit->document()); QTimer::singleShot(0, _scriptEditorWidgetUI->scriptEdit, SLOT(setFocus())); } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index d7fc7713cf..ebfb954bd8 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -20,18 +20,20 @@ #include -#include "CoverageMap.h" #include -#include "OctalCode.h" +#include #include #include +#include +#include //#include "Tags.h" -#include "ViewFrustum.h" +#include "CoverageMap.h" #include "OctreeConstants.h" #include "OctreeElementBag.h" #include "Octree.h" +#include "ViewFrustum.h" float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) { return voxelSizeScale / powf(2, renderLevel); @@ -676,6 +678,13 @@ public: bool found; }; +class ShapeArgs { +public: + const Shape* shape; + CollisionList& collisions; + bool found; +}; + bool findCapsulePenetrationOp(OctreeElement* node, void* extraData) { CapsuleArgs* args = static_cast(extraData); @@ -697,6 +706,27 @@ bool findCapsulePenetrationOp(OctreeElement* node, void* extraData) { return false; } +bool findShapeCollisionsOp(OctreeElement* node, void* extraData) { + ShapeArgs* args = static_cast(extraData); + + // coarse check against bounds + AABox cube = node->getAABox(); + cube.scale(TREE_SCALE); + if (!cube.expandedContains(args->shape->getPosition(), args->shape->getBoundingRadius())) { + return false; + } + if (!node->isLeaf()) { + return true; // recurse on children + } + if (node->hasContent()) { + if (ShapeCollider::collideShapeWithAACube(args->shape, cube.calcCenter(), cube.getScale(), args->collisions)) { + args->found = true; + return true; + } + } + return false; +} + bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration, Octree::lockType lockType) { @@ -727,6 +757,29 @@ bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end return args.found; } +bool Octree::findShapeCollisions(const Shape* shape, CollisionList& collisions, Octree::lockType lockType) { + + ShapeArgs args = { shape, collisions, false }; + + bool gotLock = false; + if (lockType == Octree::Lock) { + lockForRead(); + gotLock = true; + } else if (lockType == Octree::TryLock) { + gotLock = tryLockForRead(); + if (!gotLock) { + return args.found; // if we wanted to tryLock, and we couldn't then just bail... + } + } + + recurseTreeWithOperation(findShapeCollisionsOp, &args); + + if (gotLock) { + unlock(); + } + return args.found; +} + class GetElementEnclosingArgs { public: OctreeElement* element; diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 3c868ab172..0b9f1657ee 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -21,6 +21,7 @@ class Octree; class OctreeElement; class OctreeElementBag; class OctreePacketData; +class Shape; #include "JurisdictionMap.h" @@ -30,6 +31,8 @@ class OctreePacketData; #include "OctreePacketData.h" #include "OctreeSceneStats.h" +#include + #include #include @@ -246,6 +249,8 @@ public: bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration, Octree::lockType lockType = Octree::TryLock); + bool findShapeCollisions(const Shape* shape, CollisionList& collisions, Octree::lockType = Octree::TryLock); + OctreeElement* getElementEnclosingPoint(const glm::vec3& point, Octree::lockType lockType = Octree::TryLock); // Note: this assumes the fileFormat is the HIO individual voxels code files diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 96311ceabd..846cf564c2 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -21,10 +21,10 @@ #include "AABox.h" #include "OctalCode.h" -#include "SharedUtil.h" #include "OctreeConstants.h" #include "OctreeElement.h" #include "Octree.h" +#include "SharedUtil.h" quint64 OctreeElement::_voxelMemoryUsage = 0; quint64 OctreeElement::_octcodeMemoryUsage = 0; diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 682516cf0a..c5eec1c9e2 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -19,6 +19,7 @@ #include #include + #include "AABox.h" #include "ViewFrustum.h" #include "OctreeConstants.h" diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index 5c9d7f06c2..acd5c639f7 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -19,7 +19,6 @@ #include "AABox.h" #include "Plane.h" - #include "OctreeConstants.h" #include "OctreeProjectedPolygon.h" diff --git a/libraries/shared/src/CollisionInfo.cpp b/libraries/shared/src/CollisionInfo.cpp index 5a4188a1ef..5d97842530 100644 --- a/libraries/shared/src/CollisionInfo.cpp +++ b/libraries/shared/src/CollisionInfo.cpp @@ -48,3 +48,6 @@ void CollisionList::clear() { _size = 0; } +CollisionInfo* CollisionList::operator[](int index) { + return (index > -1 && index < _size) ? &(_collisions[index]) : NULL; +} diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h index f575dd8595..7db965fe64 100644 --- a/libraries/shared/src/CollisionInfo.h +++ b/libraries/shared/src/CollisionInfo.h @@ -95,6 +95,8 @@ public: /// Clear valid collisions. void clear(); + CollisionInfo* operator[](int index); + private: int _maxSize; // the container cannot get larger than this int _size; // the current number of valid collisions in the list diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index c53c7fab7d..31d57f14ad 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -92,6 +92,29 @@ bool collideShapesCoarse(const QVector& shapesA, const QVectorgetType(); + if (typeA == Shape::SPHERE_SHAPE) { + return sphereAACube(static_cast(shapeA), cubeCenter, cubeSide, collisions); + } else if (typeA == Shape::CAPSULE_SHAPE) { + return capsuleAACube(static_cast(shapeA), cubeCenter, cubeSide, collisions); + } else if (typeA == Shape::LIST_SHAPE) { + const ListShape* listA = static_cast(shapeA); + 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 = sphereAACube(static_cast(subShape), cubeCenter, cubeSide, collisions) || touching; + } else if (subType == Shape::CAPSULE_SHAPE) { + touching = capsuleAACube(static_cast(subShape), cubeCenter, cubeSide, collisions) || touching; + } + } + return touching; + } + return false; +} + bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions) { glm::vec3 BA = sphereB->getPosition() - sphereA->getPosition(); float distanceSquared = glm::dot(BA, BA); @@ -567,4 +590,103 @@ bool listList(const ListShape* listA, const ListShape* listB, CollisionList& col return touching; } +// helper function +bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { + glm::vec3 BA = cubeCenter - sphereCenter; + float distance = glm::length(BA); + if (distance > EPSILON) { + BA /= distance; // BA is now normalized + // compute the nearest point on sphere + glm::vec3 surfaceA = sphereCenter + sphereRadius * BA; + // compute the nearest point on cube + float maxBA = glm::max(glm::max(fabs(BA.x), fabs(BA.y)), fabs(BA.z)); + glm::vec3 surfaceB = cubeCenter - (0.5f * cubeSide / maxBA) * BA; + // collision happens when "vector to surfaceA from surfaceB" dots with BA to produce a positive value + glm::vec3 surfaceAB = surfaceA - surfaceB; + if (glm::dot(surfaceAB, BA) > 0.f) { + CollisionInfo* collision = collisions.getNewCollision(); + if (collision) { + /* KEEP THIS CODE -- this is how to collide the cube with stark face normals (no rounding). + * We might want to use this code later for sealing boundaries between adjacent voxels. + // penetration is parallel to box side direction + BA /= maxBA; + glm::vec3 direction; + glm::modf(BA, direction); + direction = glm::normalize(direction); + */ + + // For rounded normals at edges and corners: + // At this point imagine that sphereCenter touches a "normalized" cube with rounded edges. + // This cube has a sidelength of 2 and its smoothing radius is sphereRadius/maxBA. + // We're going to try to compute the "negative normal" (and hence direction of penetration) + // of this surface. + + float radius = sphereRadius / (distance * maxBA); // normalized radius + float shortLength = maxBA - radius; + glm::vec3 direction = BA; + if (shortLength > 0.0f) { + direction = glm::abs(BA) - glm::vec3(shortLength); + // Set any negative components to zero, and adopt the sign of the original BA component. + // Unfortunately there isn't an easy way to make this fast. + if (direction.x < 0.0f) { + direction.x = 0.f; + } else if (BA.x < 0.f) { + direction.x = -direction.x; + } + if (direction.y < 0.0f) { + direction.y = 0.f; + } else if (BA.y < 0.f) { + direction.y = -direction.y; + } + if (direction.z < 0.0f) { + direction.z = 0.f; + } else if (BA.z < 0.f) { + direction.z = -direction.z; + } + } + direction = glm::normalize(direction); + + // penetration is the projection of surfaceAB on direction + collision->_penetration = glm::dot(surfaceAB, direction) * direction; + // contactPoint is on surface of A + collision->_contactPoint = sphereCenter - sphereRadius * direction; + return true; + } + } + } else if (sphereRadius + 0.5f * cubeSide > distance) { + // NOTE: for cocentric approximation we collide sphere and cube as two spheres which means + // this algorithm will probably be wrong when both sphere and cube are very small (both ~EPSILON) + CollisionInfo* collision = collisions.getNewCollision(); + if (collision) { + // the penetration and contactPoint are undefined, so we pick a penetration direction (-yAxis) + collision->_penetration = (sphereRadius + 0.5f * cubeSide) * glm::vec3(0.0f, -1.0f, 0.0f); + // contactPoint is on surface of A + collision->_contactPoint = sphereCenter + collision->_penetration; + return true; + } + } + return false; +} + +bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { + return sphereAACube(sphereA->getPosition(), sphereA->getRadius(), cubeCenter, cubeSide, collisions); +} + +bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { + // find nerest approach of capsule line segment to cube + glm::vec3 capsuleAxis; + capsuleA->computeNormalizedAxis(capsuleAxis); + float offset = glm::dot(cubeCenter - capsuleA->getPosition(), capsuleAxis); + float halfHeight = capsuleA->getHalfHeight(); + if (offset > halfHeight) { + offset = halfHeight; + } else if (offset < -halfHeight) { + offset = -halfHeight; + } + glm::vec3 nearestApproach = capsuleA->getPosition() + offset * capsuleAxis; + // collide nearest approach like a sphere at that point + return sphereAACube(nearestApproach, capsuleA->getRadius(), cubeCenter, cubeSide, collisions); +} + + } // namespace ShapeCollider diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h index d554775e7b..9e83e31571 100644 --- a/libraries/shared/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -33,6 +33,13 @@ namespace ShapeCollider { /// \return true if any shapes collide bool collideShapesCoarse(const QVector& shapesA, const QVector& shapesB, CollisionInfo& collision); + /// \param shapeA a pointer to a shape + /// \param cubeCenter center of cube + /// \param cubeSide lenght of side of cube + /// \param collisions[out] average collision details + /// \return true if shapeA collides with axis aligned cube + bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); + /// \param sphereA pointer to first shape /// \param sphereB pointer to second shape /// \param[out] collisions where to append collision details @@ -129,6 +136,20 @@ namespace ShapeCollider { /// \return true if shapes collide bool listList(const ListShape* listA, const ListShape* listB, CollisionList& collisions); + /// \param sphereA pointer to sphere + /// \param cubeCenter center of cube + /// \param cubeSide lenght of side of cube + /// \param[out] collisions where to append collision details + /// \return true if sphereA collides with axis aligned cube + bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); + + /// \param capsuleA pointer to capsule + /// \param cubeCenter center of cube + /// \param cubeSide lenght of side of cube + /// \param[out] collisions where to append collision details + /// \return true if capsuleA collides with axis aligned cube + bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); + } // namespace ShapeCollider #endif // hifi_ShapeCollider_h diff --git a/libraries/voxels/src/VoxelTreeElement.h b/libraries/voxels/src/VoxelTreeElement.h index 140744afb0..8733987df4 100644 --- a/libraries/voxels/src/VoxelTreeElement.h +++ b/libraries/voxels/src/VoxelTreeElement.h @@ -18,10 +18,10 @@ #include +#include #include #include -#include "AABox.h" #include "ViewFrustum.h" #include "VoxelConstants.h" diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp index f9e76bac0b..3f952236e2 100644 --- a/tests/physics/src/ShapeColliderTests.cpp +++ b/tests/physics/src/ShapeColliderTests.cpp @@ -23,18 +23,18 @@ #include "ShapeColliderTests.h" -const glm::vec3 origin(0.f); -static const glm::vec3 xAxis(1.f, 0.f, 0.f); -static const glm::vec3 yAxis(0.f, 1.f, 0.f); -static const glm::vec3 zAxis(0.f, 0.f, 1.f); +const glm::vec3 origin(0.0f); +static const glm::vec3 xAxis(1.0f, 0.0f, 0.0f); +static const glm::vec3 yAxis(0.0f, 1.0f, 0.0f); +static const glm::vec3 zAxis(0.0f, 0.0f, 1.0f); void ShapeColliderTests::sphereMissesSphere() { // non-overlapping spheres of unequal size - float radiusA = 7.f; - float radiusB = 3.f; + float radiusA = 7.0f; + float radiusB = 3.0f; float alpha = 1.2f; float beta = 1.3f; - glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.f, 2.f, 3.f)); + glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); float offsetDistance = alpha * radiusA + beta * radiusB; SphereShape sphereA(radiusA, origin); @@ -77,13 +77,13 @@ void ShapeColliderTests::sphereMissesSphere() { void ShapeColliderTests::sphereTouchesSphere() { // overlapping spheres of unequal size - float radiusA = 7.f; - float radiusB = 3.f; + float radiusA = 7.0f; + float radiusB = 3.0f; float alpha = 0.2f; float beta = 0.3f; - glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.f, 2.f, 3.f)); + glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); float offsetDistance = alpha * radiusA + beta * radiusB; - float expectedPenetrationDistance = (1.f - alpha) * radiusA + (1.f - beta) * radiusB; + float expectedPenetrationDistance = (1.0f - alpha) * radiusA + (1.0f - beta) * radiusB; glm::vec3 expectedPenetration = expectedPenetrationDistance * offsetDirection; SphereShape sphereA(radiusA, origin); @@ -118,8 +118,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of sphereA @@ -129,8 +128,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } } @@ -150,8 +148,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of sphereA @@ -161,8 +158,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } } } @@ -181,7 +177,7 @@ void ShapeColliderTests::sphereMissesCapsule() { // give the capsule some arbirary transform float angle = 37.8f; - glm::vec3 axis = glm::normalize( glm::vec3(-7.f, 2.8f, 9.3f) ); + glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) ); glm::quat rotation = glm::angleAxis(angle, axis); glm::vec3 translation(15.1f, -27.1f, -38.6f); capsuleB.setRotation(rotation); @@ -190,7 +186,7 @@ void ShapeColliderTests::sphereMissesCapsule() { CollisionList collisions(16); // walk sphereA along the local yAxis next to, but not touching, capsuleB - glm::vec3 localStartPosition(radialOffset, axialOffset, 0.f); + glm::vec3 localStartPosition(radialOffset, axialOffset, 0.0f); int numberOfSteps = 10; float delta = 1.3f * (totalRadius + halfHeightB) / (numberOfSteps - 1); for (int i = 0; i < numberOfSteps; ++i) { @@ -224,10 +220,10 @@ void ShapeColliderTests::sphereMissesCapsule() { void ShapeColliderTests::sphereTouchesCapsule() { // overlapping sphere and capsule - float radiusA = 2.f; - float radiusB = 1.f; + float radiusA = 2.0f; + float radiusB = 1.0f; float totalRadius = radiusA + radiusB; - float halfHeightB = 2.f; + float halfHeightB = 2.0f; float alpha = 0.5f; float beta = 0.5f; float radialOffset = alpha * radiusA + beta * radiusB; @@ -257,8 +253,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of sphereA @@ -267,8 +262,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } // capsuleB collides with sphereA @@ -288,8 +282,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of capsuleB @@ -300,8 +293,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } } { // sphereA hits end cap at axis @@ -319,13 +311,12 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB CollisionInfo* collision = collisions.getCollision(numCollisions - 1); - glm::vec3 expectedPenetration = - ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis; + glm::vec3 expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; float inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of sphereA @@ -334,8 +325,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } // capsuleB collides with sphereA @@ -350,13 +340,12 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB collision = collisions.getCollision(numCollisions - 1); - expectedPenetration = ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis; + expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of capsuleB @@ -367,8 +356,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } } { // sphereA hits start cap at axis @@ -386,13 +374,12 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB CollisionInfo* collision = collisions.getCollision(numCollisions - 1); - glm::vec3 expectedPenetration = ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis; + glm::vec3 expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; float inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of sphereA @@ -401,8 +388,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } // capsuleB collides with sphereA @@ -417,13 +403,12 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB collision = collisions.getCollision(numCollisions - 1); - expectedPenetration = - ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis; + expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of capsuleB @@ -434,8 +419,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } } if (collisions.size() != numCollisions) { @@ -447,10 +431,10 @@ void ShapeColliderTests::sphereTouchesCapsule() { void ShapeColliderTests::capsuleMissesCapsule() { // non-overlapping capsules - float radiusA = 2.f; - float halfHeightA = 3.f; - float radiusB = 3.f; - float halfHeightB = 4.f; + float radiusA = 2.0f; + float halfHeightA = 3.0f; + float radiusB = 3.0f; + float halfHeightB = 4.0f; float totalRadius = radiusA + radiusB; float totalHalfLength = totalRadius + halfHeightA + halfHeightB; @@ -516,10 +500,10 @@ void ShapeColliderTests::capsuleMissesCapsule() { void ShapeColliderTests::capsuleTouchesCapsule() { // overlapping capsules - float radiusA = 2.f; - float halfHeightA = 3.f; - float radiusB = 3.f; - float halfHeightB = 4.f; + float radiusA = 2.0f; + float halfHeightA = 3.0f; + float radiusB = 3.0f; + float halfHeightB = 4.0f; float totalRadius = radiusA + radiusB; float totalHalfLength = totalRadius + halfHeightA + halfHeightB; @@ -617,8 +601,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } glm::vec3 expectedContactPoint = capsuleA.getPosition() + radiusA * xAxis; @@ -626,8 +609,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } // capsuleB vs capsuleA @@ -699,6 +681,116 @@ void ShapeColliderTests::capsuleTouchesCapsule() { } } +void ShapeColliderTests::sphereTouchesAACube() { + CollisionList collisions(16); + + glm::vec3 cubeCenter(1.23f, 4.56f, 7.89f); + float cubeSide = 2.0f; + + float sphereRadius = 1.0f; + glm::vec3 sphereCenter(0.0f); + SphereShape sphere(sphereRadius, sphereCenter); + + float sphereOffset = (0.5f * cubeSide + sphereRadius - 0.25f); + + // top + sphereCenter = cubeCenter + sphereOffset * yAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } + + // bottom + sphereCenter = cubeCenter - sphereOffset * yAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } + + // left + sphereCenter = cubeCenter + sphereOffset * xAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } + + // right + sphereCenter = cubeCenter - sphereOffset * xAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } + + // forward + sphereCenter = cubeCenter + sphereOffset * zAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } + + // back + sphereCenter = cubeCenter - sphereOffset * zAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } +} + +void ShapeColliderTests::sphereMissesAACube() { + CollisionList collisions(16); + + glm::vec3 cubeCenter(1.23f, 4.56f, 7.89f); + float cubeSide = 2.0f; + + float sphereRadius = 1.0f; + glm::vec3 sphereCenter(0.0f); + SphereShape sphere(sphereRadius, sphereCenter); + + float sphereOffset = (0.5f * cubeSide + sphereRadius + 0.25f); + + // top + sphereCenter = cubeCenter + sphereOffset * yAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } + + // bottom + sphereCenter = cubeCenter - sphereOffset * yAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } + + // left + sphereCenter = cubeCenter + sphereOffset * xAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } + + // right + sphereCenter = cubeCenter - sphereOffset * xAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } + + // forward + sphereCenter = cubeCenter + sphereOffset * zAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } + + // back + sphereCenter = cubeCenter - sphereOffset * zAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } +} + void ShapeColliderTests::runAllTests() { sphereMissesSphere(); @@ -709,4 +801,7 @@ void ShapeColliderTests::runAllTests() { capsuleMissesCapsule(); capsuleTouchesCapsule(); + + sphereTouchesAACube(); + sphereMissesAACube(); } diff --git a/tests/physics/src/ShapeColliderTests.h b/tests/physics/src/ShapeColliderTests.h index 1d468a65d2..a94f5050ff 100644 --- a/tests/physics/src/ShapeColliderTests.h +++ b/tests/physics/src/ShapeColliderTests.h @@ -23,6 +23,9 @@ namespace ShapeColliderTests { void capsuleMissesCapsule(); void capsuleTouchesCapsule(); + void sphereTouchesAACube(); + void sphereMissesAACube(); + void runAllTests(); }