diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 044396f844..bfe2f7e249 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -315,53 +315,69 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* rotatedSourcePosition.y = 0.0f; // produce an oriented angle about the y-axis - bearingRelativeAngleToSource = glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), - glm::normalize(rotatedSourcePosition), - glm::vec3(0.0f, 1.0f, 0.0f)); + float bearingAngleToSource = glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), + glm::normalize(rotatedSourcePosition), + glm::vec3(0.0f, -1.0f, 0.0f)); + const float TWO_OVER_PI = 2.0f / PI; - // if the source is in the range (-pi/2,+pi/2) (e.g, -Z from the listener's perspective - if (bearingRelativeAngleToSource < -PI_OVER_TWO || bearingRelativeAngleToSource > PI_OVER_TWO) - { - AudioFilterHSF1s& penumbraFilter = streamToAdd->getFilter(); + const float ZERO_DB = 1.0f; + const float NEGATIVE_ONE_DB = 0.891f; + const float NEGATIVE_THREE_DB = 0.708f; - const float FULL_POWER = 1.0f; - const float SQUARE_ROOT_OF_TWO_OVER_TWO = 0.71f; - const float HALF_POWER = SQUARE_ROOT_OF_TWO_OVER_TWO; - - const float ONE_OVER_TWO_PI = 1.0f / TWO_PI; - const float FILTER_CUTOFF_FREQUENCY_HZ = 1000.0f; - - // calculate the updated gain, frequency and slope. - const float penumbraFilterFrequency = FILTER_CUTOFF_FREQUENCY_HZ; // constant frequency - const float penumbraFilterSlope = SQUARE_ROOT_OF_TWO_OVER_TWO; // constant slope + const float FILTER_CUTOFF_FREQUENCY_HZ = 1000.0f; + + const float penumbraFilterFrequency = FILTER_CUTOFF_FREQUENCY_HZ; // constant frequency + const float penumbraFilterSlope = NEGATIVE_THREE_DB; // constant slope + + float penumbraFilterGainL; + float penumbraFilterGainR; - const float penumbraFilterGainL = (bearingRelativeAngleToSource <= -PI_OVER_TWO) ? - ((+1.0 * ONE_OVER_TWO_PI * (bearingRelativeAngleToSource + PI_OVER_TWO)) + FULL_POWER) : - ((+1.0 * ONE_OVER_TWO_PI * (bearingRelativeAngleToSource - PI)) + HALF_POWER); - - const float penumbraFilterGainR = (bearingRelativeAngleToSource <= -PI_OVER_TWO) ? - ((-1.0 * ONE_OVER_TWO_PI * (bearingRelativeAngleToSource + PI_OVER_TWO)) + HALF_POWER) : - ((-1.0 * ONE_OVER_TWO_PI * (bearingRelativeAngleToSource - PI)) + HALF_POWER); - + // variable gain calculation broken down by quadrent + if (bearingAngleToSource < -PI_OVER_TWO && bearingAngleToSource > -PI) { + // gainL(-pi/2,0b)->(-pi,-1db) + penumbraFilterGainL = TWO_OVER_PI * + (ZERO_DB - NEGATIVE_ONE_DB) * (bearingAngleToSource + PI_OVER_TWO) + ZERO_DB; + // gainR(-pi/2,-3db)->(-pi,-1db) + penumbraFilterGainR = TWO_OVER_PI * + (NEGATIVE_THREE_DB - NEGATIVE_ONE_DB) * (bearingAngleToSource + PI_OVER_TWO) + NEGATIVE_THREE_DB; + } else if (bearingAngleToSource <= PI && bearingAngleToSource > PI_OVER_TWO) { + // gainL(+pi,-1db)->(pi/2,-3db) + penumbraFilterGainL = TWO_OVER_PI * + (NEGATIVE_ONE_DB - NEGATIVE_THREE_DB) * (bearingAngleToSource - PI) + NEGATIVE_ONE_DB; + // gainR(+pi,-1db)->(pi/2,0db) + penumbraFilterGainR = TWO_OVER_PI * + (NEGATIVE_ONE_DB - ZERO_DB) * (bearingAngleToSource - PI) + NEGATIVE_ONE_DB; + } else if (bearingAngleToSource <= PI_OVER_TWO && bearingAngleToSource > 0) { + // gainL(+pi/2,-3db)->(0,0db) + penumbraFilterGainL = TWO_OVER_PI * + (NEGATIVE_THREE_DB - ZERO_DB) * (bearingAngleToSource - PI_OVER_TWO) + NEGATIVE_THREE_DB; + // gainR(+p1/2,0db)->(0,0db) + penumbraFilterGainR = ZERO_DB; + } else { + // gainL(0,0db)->(-pi/2,0db) + penumbraFilterGainL = ZERO_DB; + // gainR(0,0db)->(-pi/2,-3db) + penumbraFilterGainR = TWO_OVER_PI * + (ZERO_DB - NEGATIVE_THREE_DB) * (bearingAngleToSource) + ZERO_DB; + } #if 0 - float distanceBetween = glm::length(relativePosition); - qDebug() << "avatar=" - << listeningNodeStream + qDebug() << "avatar=" + << listeningNodeStream << "gainL=" << penumbraFilterGainL << "gainR=" << penumbraFilterGainR << "angle=" - << bearingRelativeAngleToSource - << "dist=" - << distanceBetween; + << bearingAngleToSource; #endif - // set the gain on both filter channels - penumbraFilter.setParameters(0, 0, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainL, penumbraFilterSlope); - penumbraFilter.setParameters(0, 1, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainR, penumbraFilterSlope); + + // set the gain on both filter channels + AudioFilterHSF1s& penumbraFilter = streamToAdd->getFilter(); + + penumbraFilter.setParameters(0, 0, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainL, penumbraFilterSlope); + penumbraFilter.setParameters(0, 1, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainR, penumbraFilterSlope); - penumbraFilter.render(_clientSamples, _clientSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2); - } + penumbraFilter.render(_clientSamples, _clientSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2); } return 1; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 116a1dcc84..f905ae652f 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -394,8 +394,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus int extraPackingAttempts = 0; bool completedScene = false; - OctreeElement* lastAttemptedSubTree = NULL; - while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) { float lockWaitElapsedUsec = OctreeServer::SKIP_TIME; float encodeElapsedUsec = OctreeServer::SKIP_TIME; @@ -408,9 +406,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus if (!nodeData->elementBag.isEmpty()) { OctreeElement* subTree = nodeData->elementBag.extract(); - // TODO: look into breaking early if the same subtree keeps repeating for inclusion... - lastAttemptedSubTree = subTree; - /* TODO: Looking for a way to prevent locking and encoding a tree that is not // going to result in any packets being sent... // @@ -516,8 +511,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent); } - lastAttemptedSubTree = NULL; // reset this - nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize()); extraPackingAttempts = 0; quint64 compressAndWriteEnd = usecTimestampNow(); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 9c741b2a3b..6dff445fca 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -22,7 +22,7 @@ #include "DomainServerSettingsManager.h" const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/web/settings/describe.json"; -const QString SETTINGS_CONFIG_FILE_RELATIVE_PATH = "/resources/config.json"; +const QString SETTINGS_JSON_FILE_RELATIVE_PATH = "/resources/settings.json"; DomainServerSettingsManager::DomainServerSettingsManager() : _descriptionObject(), @@ -35,7 +35,7 @@ DomainServerSettingsManager::DomainServerSettingsManager() : _descriptionObject = QJsonDocument::fromJson(descriptionFile.readAll()).object(); // load the existing config file to get the current values - QFile configFile(QCoreApplication::applicationDirPath() + SETTINGS_CONFIG_FILE_RELATIVE_PATH); + QFile configFile(QCoreApplication::applicationDirPath() + SETTINGS_JSON_FILE_RELATIVE_PATH); if (configFile.exists()) { configFile.open(QIODevice::ReadOnly); @@ -197,7 +197,7 @@ QByteArray DomainServerSettingsManager::getJSONSettingsMap() const { } void DomainServerSettingsManager::persistToFile() { - QFile settingsFile(QCoreApplication::applicationDirPath() + SETTINGS_CONFIG_FILE_RELATIVE_PATH); + QFile settingsFile(QCoreApplication::applicationDirPath() + SETTINGS_JSON_FILE_RELATIVE_PATH); if (settingsFile.open(QIODevice::WriteOnly)) { settingsFile.write(getJSONSettingsMap()); diff --git a/examples/radio.js b/examples/radio.js new file mode 100644 index 0000000000..6ffc7deb84 --- /dev/null +++ b/examples/radio.js @@ -0,0 +1,79 @@ +// +// Radio.js +// examples +// +// Created by Clément Brisset on 8/20/14. +// 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 +// + +var position = { x:1, y: 1, z: 10 }; +var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0); +var scale = 1.0; + +var modelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/entities/radio/Speakers2Finished.fbx"; +var soundURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/FamilyStereo.raw"; + +var AudioRotationOffset = Quat.fromPitchYawRollDegrees(0, -90, 0); +var audioOptions = new AudioInjectionOptions(); +audioOptions.volume = 0.7; +audioOptions.position = position; +audioOptions.orientation = Quat.multiply(AudioRotationOffset, rotation); +audioOptions.loop = true; +audioOptions.isStereo = true; +var injector = null; + +var sound = new Sound(soundURL); + +var entity = null; +var properties = null; + +function update() { + if (entity === null) { + if (sound.downloaded) { + print("Sound file downloaded"); + entity = Entities.addEntity({ + type: "Model", + position: position, + rotation: rotation, + radius: scale / 2.0, + modelURL: modelURL + }); + properties = Entities.getEntityProperties(entity); + + injector = Audio.playSound(sound, audioOptions); + } + } else { + var newProperties = Entities.getEntityProperties(entity); + if (newProperties.type === "Model") { + if (newProperties.position != properties.position) { + audioOptions.position = newProperties.position; + } + if (newProperties.orientation != properties.orientation) { + audioOptions.orientation = newProperties.orientation; + } + + properties = newProperties; + } else { + entity = null; + Script.update.disconnect(update); + Script.scriptEnding.connect(scriptEnding); + scriptEnding(); + } + } +} + +function scriptEnding() { + if (entity != null) { + Entities.deleteEntity(entity); + } + if (injector != null) { + injector.stop(); + } +} + +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); + diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 885df74d2b..80500d392d 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -511,7 +511,7 @@ void Audio::handleAudioInput() { _inputFrameBuffer.copyFrames(1, inputFrameCount, inputFrameData, false /*copy in*/); - _inputGain.render(_inputFrameBuffer); // input/mic gain+mute + // _inputGain.render(_inputFrameBuffer); // input/mic gain+mute // Add audio source injection if enabled if (_audioSourceInjectEnabled && !_muted) { diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index 62f5b6453a..0889007c76 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -45,17 +45,31 @@ void DatagramProcessor::processDatagrams() { _byteCount += incomingPacket.size(); if (nodeList->packetVersionAndHashMatch(incomingPacket)) { + + PacketType incomingType = packetTypeForPacket(incomingPacket); // only process this packet if we have a match on the packet version - switch (packetTypeForPacket(incomingPacket)) { + switch (incomingType) { case PacketTypeMixedAudio: case PacketTypeSilentAudioFrame: - QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToStream", Qt::QueuedConnection, - Q_ARG(QByteArray, incomingPacket)); - break; - case PacketTypeAudioStreamStats: - QMetaObject::invokeMethod(&application->_audio, "parseAudioStreamStatsPacket", Qt::QueuedConnection, - Q_ARG(QByteArray, incomingPacket)); + case PacketTypeAudioStreamStats: { + if (incomingType != PacketTypeAudioStreamStats) { + QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToStream", Qt::QueuedConnection, + Q_ARG(QByteArray, incomingPacket)); + } else { + QMetaObject::invokeMethod(&application->_audio, "parseAudioStreamStatsPacket", Qt::QueuedConnection, + Q_ARG(QByteArray, incomingPacket)); + } + + // update having heard from the audio-mixer and record the bytes received + SharedNodePointer audioMixer = nodeList->sendingNodeForPacket(incomingPacket); + + if (audioMixer) { + audioMixer->setLastHeardMicrostamp(usecTimestampNow()); + audioMixer->recordBytesReceived(incomingPacket.size()); + } + break; + } case PacketTypeParticleAddResponse: // this will keep creatorTokenIDs to IDs mapped correctly Particle::handleAddParticleResponse(incomingPacket); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 87290a3e64..d3d1c8c0d0 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -277,7 +277,8 @@ Menu::Menu() : avatar, SLOT(updateMotionBehaviorsFromMenu())); QMenu* collisionsMenu = avatarMenu->addMenu("Collide With..."); - addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideAsRagdoll); + addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideAsRagdoll, 0, false, + avatar, SLOT(onToggleRagdoll())); addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideWithAvatars, 0, true, avatar, SLOT(updateCollisionGroups())); addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideWithVoxels, @@ -745,6 +746,7 @@ void Menu::loadSettings(QSettings* settings) { // TODO: cache more settings in MyAvatar that are checked with very high frequency. MyAvatar* myAvatar = Application::getInstance()->getAvatar(); myAvatar->updateCollisionGroups(); + myAvatar->onToggleRagdoll(); if (lockedSettings) { Application::getInstance()->unlockSettings(); diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 903e443cda..c7c074941e 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -1743,7 +1743,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) { for (int z = 0; z < expanded; z++) { const QRgb* colorY = colorZ; for (int y = 0; y < expanded; y++) { - int lastIndex; + int lastIndex = 0; const QRgb* colorX = colorY; for (int x = 0; x < expanded; x++) { int alpha0 = colorX[0] >> ALPHA_OFFSET; @@ -2479,9 +2479,9 @@ void StaticModelRenderer::renderUnclipped(float alpha, Mode mode) { _model->render(alpha); } -bool StaticModelRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - const glm::vec3& clipMinimum, float clipSize, float& distance) const { - return _model->findRayIntersection(origin, direction, distance); +bool StaticModelRenderer::findRayIntersection(RayIntersectionInfo& intersection, + const glm::vec3& clipMinimum, float clipSize) const { + return _model->findRayIntersection(intersection); } void StaticModelRenderer::applyTranslation(const glm::vec3& translation) { diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index f3b9a02117..ab1f8b32fc 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -392,8 +392,8 @@ public: virtual void init(Spanner* spanner); virtual void simulate(float deltaTime); - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - const glm::vec3& clipMinimum, float clipSize, float& distance) const; + virtual bool findRayIntersection(RayIntersectionInfo& intersection, + const glm::vec3& clipMinimum, float clipSize) const; protected: diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 91c1770e6f..f11d035ebe 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -196,39 +196,56 @@ bool ModelUploader::zip() { // mixamo blendshapes if (!mapping.contains(BLENDSHAPE_FIELD) && geometry.applicationName == "mixamo.com") { QVariantHash blendshapes; - blendshapes.insert("EyeBlink_L", QVariantList() << "Blink_Left" << 1.0); - blendshapes.insert("EyeBlink_R", QVariantList() << "Blink_Right" << 1.0); - blendshapes.insert("EyeSquint_L", QVariantList() << "Squint_Left" << 1.0); - blendshapes.insert("EyeSquint_R", QVariantList() << "Squint_Right" << 1.0); - blendshapes.insert("EyeOpen_L", QVariantList() << "EyesWide_Left" << 1.0); - blendshapes.insert("EyeOpen_R", QVariantList() << "EyesWide_Right" << 1.0); - blendshapes.insert("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0); - blendshapes.insert("BrowsD_R", QVariantList() << "BrowsDown_Right" << 1.0); - blendshapes.insert("BrowsU_L", QVariantList() << "BrowsUp_Left" << 1.0); - blendshapes.insert("BrowsU_R", QVariantList() << "BrowsUp_Right" << 1.0); - blendshapes.insert("JawFwd", QVariantList() << "JawForeward" << 1.0); - blendshapes.insert("JawOpen", QVariantList() << "Jaw_Down" << 1.0); - blendshapes.insert("JawLeft", QVariantList() << "Jaw_Left" << 1.0); - blendshapes.insert("JawRight", QVariantList() << "Jaw_Right" << 1.0); - blendshapes.insert("JawChew", QVariantList() << "Jaw_Up" << 1.0); - blendshapes.insert("MouthLeft", QVariantList() << "Midmouth_Left" << 1.0); - blendshapes.insert("MouthRight", QVariantList() << "Midmouth_Right" << 1.0); - blendshapes.insert("MouthFrown_L", QVariantList() << "Frown_Left" << 1.0); - blendshapes.insert("MouthFrown_R", QVariantList() << "Frown_Right" << 1.0); - blendshapes.insert("MouthSmile_L", QVariantList() << "Smile_Left" << 1.0); - blendshapes.insert("MouthSmile_R", QVariantList() << "Smile_Right" << 1.0); - blendshapes.insert("LipsUpperUp", QVariantList() << "UpperLipUp_Left" << 0.5); - blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Right" << 0.5); - blendshapes.insert("Puff", QVariantList() << "CheekPuff_Left" << 0.5); - blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Right" << 0.5); - blendshapes.insert("Sneer", QVariantList() << "NoseScrunch_Left" << 0.5); - blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Right" << 0.5); - blendshapes.insert("CheekSquint_L", QVariantList() << "Squint_Left" << 1.0); - blendshapes.insert("CheekSquint_R", QVariantList() << "Squint_Right" << 1.0); - blendshapes.insert("LipsPucker", QVariantList() << "MouthNarrow_Left" << 0.5); - blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Right" << 0.5); - blendshapes.insert("LipsLowerDown", QVariantList() << "LowerLipDown_Left" << 0.5); - blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Right" << 0.5); + blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0); + blendshapes.insertMulti("BrowsD_R", QVariantList() << "BrowsDown_Right" << 1.0); + blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Left" << 1.0); + blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Right" << 1.0); + blendshapes.insertMulti("BrowsU_L", QVariantList() << "BrowsUp_Left" << 1.0); + blendshapes.insertMulti("BrowsU_R", QVariantList() << "BrowsUp_Right" << 1.0); + blendshapes.insertMulti("ChinLowerRaise", QVariantList() << "Jaw_Up" << 1.0); + blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Left" << 0.5); + blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Right" << 0.5); + blendshapes.insertMulti("EyeBlink_L", QVariantList() << "Blink_Left" << 1.0); + blendshapes.insertMulti("EyeBlink_R", QVariantList() << "Blink_Right" << 1.0); + blendshapes.insertMulti("EyeOpen_L", QVariantList() << "EyesWide_Left" << 1.0); + blendshapes.insertMulti("EyeOpen_R", QVariantList() << "EyesWide_Right" << 1.0); + blendshapes.insertMulti("EyeSquint_L", QVariantList() << "Squint_Left" << 1.0); + blendshapes.insertMulti("EyeSquint_R", QVariantList() << "Squint_Right" << 1.0); + blendshapes.insertMulti("JawFwd", QVariantList() << "JawForeward" << 1.0); + blendshapes.insertMulti("JawLeft", QVariantList() << "JawRotateY_Left" << 0.5); + blendshapes.insertMulti("JawOpen", QVariantList() << "MouthOpen" << 0.7); + blendshapes.insertMulti("JawRight", QVariantList() << "Jaw_Right" << 1.0); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "JawForeward" << 0.39); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "Jaw_Down" << 0.36); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Left" << 1.0); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Right" << 1.0); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Left" << 0.5); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Right" << 0.5); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "TongueUp" << 1.0); + blendshapes.insertMulti("LipsLowerClose", QVariantList() << "LowerLipIn" << 1.0); + blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Left" << 0.7); + blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Right" << 0.7); + blendshapes.insertMulti("LipsLowerOpen", QVariantList() << "LowerLipOut" << 1.0); + blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Left" << 1.0); + blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Right" << 1.0); + blendshapes.insertMulti("LipsUpperClose", QVariantList() << "UpperLipIn" << 1.0); + blendshapes.insertMulti("LipsUpperOpen", QVariantList() << "UpperLipOut" << 1.0); + blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Left" << 0.7); + blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Right" << 0.7); + blendshapes.insertMulti("MouthDimple_L", QVariantList() << "Smile_Left" << 0.25); + blendshapes.insertMulti("MouthDimple_R", QVariantList() << "Smile_Right" << 0.25); + blendshapes.insertMulti("MouthFrown_L", QVariantList() << "Frown_Left" << 1.0); + blendshapes.insertMulti("MouthFrown_R", QVariantList() << "Frown_Right" << 1.0); + blendshapes.insertMulti("MouthLeft", QVariantList() << "Midmouth_Left" << 1.0); + blendshapes.insertMulti("MouthRight", QVariantList() << "Midmouth_Right" << 1.0); + blendshapes.insertMulti("MouthSmile_L", QVariantList() << "Smile_Left" << 1.0); + blendshapes.insertMulti("MouthSmile_R", QVariantList() << "Smile_Right" << 1.0); + blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Left" << 1.0); + blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Right" << 1.0); + blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Left" << 0.75); + blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Right" << 0.75); + blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Left" << 0.5); + blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Right" << 0.5); mapping.insert(BLENDSHAPE_FIELD, blendshapes); } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index c7d69cff7a..293aa8595f 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -715,20 +715,10 @@ void Avatar::renderDisplayName() { glEnable(GL_LIGHTING); } -bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { - float minDistance = FLT_MAX; - float modelDistance; - if (_skeletonModel.findRayIntersection(origin, direction, modelDistance)) { - minDistance = qMin(minDistance, modelDistance); - } - if (getHead()->getFaceModel().findRayIntersection(origin, direction, modelDistance)) { - minDistance = qMin(minDistance, modelDistance); - } - if (minDistance < FLT_MAX) { - distance = minDistance; - return true; - } - return false; +bool Avatar::findRayIntersection(RayIntersectionInfo& intersection) const { + bool hit = _skeletonModel.findRayIntersection(intersection); + hit = getHead()->getFaceModel().findRayIntersection(intersection) || hit; + return hit; } bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions) { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index cf51ffb8b8..2ec1ce661a 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -99,7 +99,7 @@ public: /// Returns the distance to use as a LOD parameter. float getLODDistance() const; - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + bool findRayIntersection(RayIntersectionInfo& intersection) const; /// \param shapes list of shapes to collide against avatar /// \param collisions list to store collision results diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 48d58fb02c..6cad3d4296 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -49,7 +49,7 @@ const float PITCH_SPEED = 100.0f; // degrees/sec const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions const float COLLISION_RADIUS_SCALE = 0.125f; -const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f; +const float MIN_KEYBOARD_CONTROL_SPEED = 1.5f; const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED; // TODO: normalize avatar speed for standard avatar size, then scale all motion logic @@ -75,7 +75,6 @@ MyAvatar::MyAvatar() : _motorTimescale(DEFAULT_MOTOR_TIMESCALE), _maxMotorSpeed(MAX_MOTOR_SPEED), _motionBehaviors(AVATAR_MOTION_DEFAULTS), - _lastFloorContactPoint(0.0f), _lookAtTargetAvatar(), _shouldRender(true), _billboardValid(false), @@ -87,11 +86,10 @@ MyAvatar::MyAvatar() : _driveKeys[i] = 0.0f; } _physicsSimulation.setEntity(&_skeletonModel); + _physicsSimulation.addEntity(&_voxelShapeManager); _skeletonModel.setEnableShapes(true); - Ragdoll* ragdoll = _skeletonModel.buildRagdoll(); - _physicsSimulation.setRagdoll(ragdoll); - _physicsSimulation.addEntity(&_voxelShapeManager); + _skeletonModel.buildRagdoll(); // connect to AddressManager signal for location jumps connect(&AddressManager::getInstance(), &AddressManager::locationChangeRequired, this, &MyAvatar::goToLocation); @@ -217,15 +215,15 @@ void MyAvatar::simulate(float deltaTime) { } { - PerformanceTimer perfTimer("ragdoll"); + PerformanceTimer perfTimer("physics"); + const float minError = 0.00001f; + const float maxIterations = 3; + const quint64 maxUsec = 4000; + _physicsSimulation.setTranslation(_position); + _physicsSimulation.stepForward(deltaTime, minError, maxIterations, maxUsec); + Ragdoll* ragdoll = _skeletonModel.getRagdoll(); if (ragdoll && Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) { - const float minError = 0.00001f; - const float maxIterations = 3; - const quint64 maxUsec = 4000; - _physicsSimulation.setTranslation(_position); - _physicsSimulation.stepForward(deltaTime, minError, maxIterations, maxUsec); - // harvest any displacement of the Ragdoll that is a result of collisions glm::vec3 ragdollDisplacement = ragdoll->getAndClearAccumulatedMovement(); const float MAX_RAGDOLL_DISPLACEMENT_2 = 1.0f; @@ -1086,15 +1084,6 @@ bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode rend (glm::length(cameraPosition - head->getEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale); } -float MyAvatar::computeDistanceToFloor(const glm::vec3& startPoint) { - glm::vec3 direction = -_worldUpDirection; - OctreeElement* elementHit; // output from findRayIntersection - float distance = FLT_MAX; // output from findRayIntersection - BoxFace face; // output from findRayIntersection - Application::getInstance()->getVoxelTree()->findRayIntersection(startPoint, direction, elementHit, distance, face); - return distance; -} - void MyAvatar::updateOrientation(float deltaTime) { // Gather rotation information from keyboard _bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime; @@ -1152,86 +1141,69 @@ void MyAvatar::updateOrientation(float deltaTime) { const float NEARBY_FLOOR_THRESHOLD = 5.0f; void MyAvatar::updatePosition(float deltaTime) { - float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + - fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) + - fabsf(_driveKeys[UP] - _driveKeys[DOWN]); - - bool walkingOnFloor = false; - float gravityLength = glm::length(_gravity) * GRAVITY_EARTH; + // check for floor by casting a ray straight down from avatar's position + float heightAboveFloor = FLT_MAX; const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); - glm::vec3 startCap; - boundingShape.getStartPoint(startCap); - glm::vec3 bottom = startCap - boundingShape.getRadius() * _worldUpDirection; + RayIntersectionInfo intersection; + // NOTE: avatar is center of PhysicsSimulation, so rayStart is the origin for the purposes of the raycast + intersection._rayStart = glm::vec3(0.0f); + intersection._rayDirection = - _worldUpDirection; + intersection._rayLength = 5.0f * boundingShape.getBoundingRadius(); + if (_physicsSimulation.findFloorRayIntersection(intersection)) { + // NOTE: heightAboveFloor is the distance between the bottom of the avatar and the floor + heightAboveFloor = intersection._hitDistance - boundingShape.getBoundingRadius(); + } - // velocity is initialized to the measured _velocity but will be modified - // by friction, external thrust, etc + // velocity is initialized to the measured _velocity but will be modified by friction, external thrust, etc glm::vec3 velocity = _velocity; - // apply friction - if (gravityLength > EPSILON) { - float speedFromGravity = _scale * deltaTime * gravityLength; - float distanceToFall = glm::distance(bottom, _lastFloorContactPoint); - walkingOnFloor = (distanceToFall < 2.0f * deltaTime * speedFromGravity); - - if (walkingOnFloor) { - // BEGIN HACK: to prevent the avatar from bouncing on a floor surface - if (distanceToFall < deltaTime * speedFromGravity) { - float verticalSpeed = glm::dot(velocity, _worldUpDirection); - if (fabs(verticalSpeed) < speedFromGravity) { - // we're standing on a floor, and nearly at rest so we zero the vertical velocity component - velocity -= verticalSpeed * _worldUpDirection; - } - } else { - // fall with gravity against floor - velocity -= speedFromGravity * _worldUpDirection; - } - // END HACK + bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f); + bool walkingOnFloor = false; + if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { + const float MAX_SPEED_UNDER_GRAVITY = 2.0f * _scale * MAX_WALKING_SPEED; + if (pushingUp || glm::length2(velocity) > MAX_SPEED_UNDER_GRAVITY * MAX_SPEED_UNDER_GRAVITY) { + // we're pushing up or moving quickly, so disable gravity + setLocalGravity(glm::vec3(0.0f)); } else { - if (!_isBraking) { - // fall with gravity toward floor - velocity -= speedFromGravity * _worldUpDirection; - } - - if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { - const float MAX_VERTICAL_FLOOR_DETECTION_SPEED = _scale * MAX_WALKING_SPEED; - if (keyboardInput && glm::dot(_motorVelocity, _worldUpDirection) > 0.0f && - glm::dot(velocity, _worldUpDirection) > MAX_VERTICAL_FLOOR_DETECTION_SPEED) { - // disable local gravity when flying up - setLocalGravity(glm::vec3(0.0f)); - } else { - const float maxFloorDistance = _scale * NEARBY_FLOOR_THRESHOLD; - if (computeDistanceToFloor(bottom) > maxFloorDistance) { - // disable local gravity when floor is too far - setLocalGravity(glm::vec3(0.0f)); - } - } - } - } - } else if ((_collisionGroups & COLLISION_GROUP_VOXELS) && - _motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { - const float MIN_FLOOR_DETECTION_SPEED = _scale * 1.0f; - if (glm::length(_velocity) < MIN_FLOOR_DETECTION_SPEED ) { - // scan for floor under avatar - const float maxFloorDistance = _scale * NEARBY_FLOOR_THRESHOLD; - if (computeDistanceToFloor(bottom) < maxFloorDistance) { - // enable local gravity + const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD; + if (heightAboveFloor > maxFloorDistance) { + // disable local gravity when floor is too far away + setLocalGravity(glm::vec3(0.0f)); + } else { + // enable gravity + walkingOnFloor = true; setLocalGravity(-_worldUpDirection); } } } - float speed = glm::length(velocity); - if (keyboardInput > 0.0f || speed > 0.0f || glm::length2(_thrust) > 0.0f || ! walkingOnFloor) { - // update motor - if (_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED) { - // Increase motor velocity until its length is equal to _maxMotorSpeed. - glm::vec3 localVelocity = velocity; - if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) { - glm::quat orientation = getHead()->getCameraOrientation(); - localVelocity = glm::inverse(orientation) * velocity; - } - + bool zeroDownwardVelocity = false; + bool gravityEnabled = (glm::length2(_gravity) > EPSILON); + if (gravityEnabled) { + if (heightAboveFloor < 0.0f) { + // Gravity is in effect so we assume that the avatar is colliding against the world and we need + // to lift avatar out of floor, but we don't want to do it too fast (keep it smooth). + float distanceToLift = glm::min(-heightAboveFloor, MAX_WALKING_SPEED * deltaTime); + + // We don't use applyPositionDelta() for this lift distance because we don't want the avatar + // to come flying out of the floor. Instead we update position directly, and set a boolean + // that will remind us later to zero any downward component of the velocity. + _position += (distanceToLift - EPSILON) * _worldUpDirection; + zeroDownwardVelocity = true; + } + velocity += (deltaTime * GRAVITY_EARTH) * _gravity; + } + + float motorEfficiency = glm::clamp(deltaTime / computeMotorTimescale(velocity), 0.0f, 1.0f); + + // compute targetVelocity + glm::vec3 targetVelocity(0.0f); + if (_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED) { + float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + + (fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) + + fabsf(_driveKeys[UP] - _driveKeys[DOWN]); + if (keyboardInput) { // Compute keyboard input glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT; glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT; @@ -1243,76 +1215,69 @@ void MyAvatar::updatePosition(float deltaTime) { // Compute motor magnitude if (directionLength > EPSILON) { direction /= directionLength; - // the finalMotorSpeed depends on whether we are walking or not + + // Compute the target keyboard velocity (which ramps up slowly, and damps very quickly) + // the max magnitude of which depends on what we're doing: float finalMaxMotorSpeed = walkingOnFloor ? _scale * MAX_WALKING_SPEED : _scale * _maxMotorSpeed; - float motorLength = glm::length(_motorVelocity); if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) { // an active keyboard motor should never be slower than this _motorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction; + motorEfficiency = 1.0f; } else { - float MOTOR_LENGTH_TIMESCALE = 1.5f; - float tau = glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f); - float INCREASE_FACTOR = 2.0f; - //_motorVelocity *= 1.0f + tau * INCREASE_FACTOR; - motorLength *= 1.0f + tau * INCREASE_FACTOR; + float MOTOR_LENGTH_TIMESCALE = 2.0f; + float INCREASE_FACTOR = 1.8f; + motorLength *= 1.0f + glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f) * INCREASE_FACTOR; if (motorLength > finalMaxMotorSpeed) { motorLength = finalMaxMotorSpeed; } _motorVelocity = motorLength * direction; } _isPushing = true; - } else { - // motor opposes motion (wants to be at rest) - _motorVelocity = - localVelocity; - } + } + targetVelocity = _motorVelocity; + } else { + _motorVelocity = glm::vec3(0.0f); } + } + targetVelocity = getHead()->getCameraOrientation() * targetVelocity; - // apply motor - if (_motionBehaviors & AVATAR_MOTION_MOTOR_ENABLED) { - glm::vec3 targetVelocity = _motorVelocity; - if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) { - // rotate targetVelocity into world frame - glm::quat rotation = getHead()->getCameraOrientation(); - targetVelocity = rotation * _motorVelocity; - } - - glm::vec3 deltaVelocity = targetVelocity - velocity; - - if (_motionBehaviors & AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY && glm::length2(_gravity) > EPSILON) { - // For now we subtract the component parallel to gravity but what we need to do is: - // TODO: subtract the component perp to the local surface normal (motor only pushes in surface plane). - glm::vec3 gravityDirection = glm::normalize(_gravity); - glm::vec3 parallelDelta = glm::dot(deltaVelocity, gravityDirection) * gravityDirection; - if (glm::dot(targetVelocity, velocity) > 0.0f) { - // remove parallel part from deltaVelocity - deltaVelocity -= parallelDelta; - } - } - - // simple critical damping - float timescale = computeMotorTimescale(velocity); - float tau = glm::clamp(deltaTime / timescale, 0.0f, 1.0f); - velocity += tau * deltaVelocity; - } + glm::vec3 deltaVelocity = targetVelocity - velocity; - // apply thrust - velocity += _thrust * deltaTime; - speed = glm::length(velocity); - if (speed > MAX_AVATAR_SPEED) { - velocity *= MAX_AVATAR_SPEED / speed; - speed = MAX_AVATAR_SPEED; - } - _thrust = glm::vec3(0.0f); + if (walkingOnFloor && !pushingUp) { + // remove vertical component of deltaVelocity + deltaVelocity -= glm::dot(deltaVelocity, _worldUpDirection) * _worldUpDirection; + } - // update position - const float MIN_AVATAR_SPEED = 0.075f; - if (speed > MIN_AVATAR_SPEED) { - applyPositionDelta(deltaTime * velocity); + // apply motor + velocity += motorEfficiency * deltaVelocity; + + // apply thrust + velocity += _thrust * deltaTime; + _thrust = glm::vec3(0.0f); + + // remove downward velocity so we don't push into floor + if (zeroDownwardVelocity) { + float verticalSpeed = glm::dot(velocity, _worldUpDirection); + if (verticalSpeed < 0.0f) { + velocity += verticalSpeed * _worldUpDirection; } } - // update moving flag based on speed + // cap avatar speed + float speed = glm::length(velocity); + if (speed > MAX_AVATAR_SPEED) { + velocity *= MAX_AVATAR_SPEED / speed; + speed = MAX_AVATAR_SPEED; + } + + // update position + const float MIN_AVATAR_SPEED = 0.075f; + if (speed > MIN_AVATAR_SPEED) { + applyPositionDelta(deltaTime * velocity); + } + + // update _moving flag based on speed const float MOVING_SPEED_THRESHOLD = 0.01f; _moving = speed > MOVING_SPEED_THRESHOLD; @@ -1331,8 +1296,8 @@ float MyAvatar::computeMotorTimescale(const glm::vec3& velocity) { // (3) inactive --> long timescale (gentle friction for low speeds) float MIN_MOTOR_TIMESCALE = 0.125f; - float MAX_MOTOR_TIMESCALE = 0.5f; - float MIN_BRAKE_SPEED = 0.4f; + float MAX_MOTOR_TIMESCALE = 0.4f; + float MIN_BRAKE_SPEED = 0.3f; float timescale = MAX_MOTOR_TIMESCALE; bool isThrust = (glm::length2(_thrust) > EPSILON); @@ -1369,18 +1334,23 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) { static CollisionList myCollisions(64); void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { - if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) { + + quint64 now = usecTimestampNow(); + if (_voxelShapeManager.needsUpdate(now)) { // We use a multiple of the avatar's boundingRadius as the size of the cube of interest. - float cubeScale = 4.0f * getBoundingRadius(); + float cubeScale = 6.0f * getBoundingRadius(); glm::vec3 corner = getPosition() - glm::vec3(0.5f * cubeScale); AACube boundingCube(corner, cubeScale); // query the VoxelTree for cubes that touch avatar's boundingCube CubeList cubes; if (Application::getInstance()->getVoxelTree()->findContentInCube(boundingCube, cubes)) { - _voxelShapeManager.updateVoxels(cubes); + _voxelShapeManager.updateVoxels(now, cubes); } - } else { + } + + // TODO: Andrew to do ground/walking detection in ragdoll mode + if (!Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) { const float MAX_VOXEL_COLLISION_SPEED = 100.0f; float speed = glm::length(_velocity); if (speed > MAX_VOXEL_COLLISION_SPEED) { @@ -1390,15 +1360,18 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { } bool isTrapped = false; myCollisions.clear(); - const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); - if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions, Octree::TryLock)) { + // copy the boundingShape and tranform into physicsSimulation frame + CapsuleShape boundingShape = _skeletonModel.getBoundingShape(); + boundingShape.setTranslation(boundingShape.getTranslation() - _position); + + if (_physicsSimulation.getShapeCollisions(&boundingShape, myCollisions)) { + // we temporarily move b const float VOXEL_ELASTICITY = 0.0f; const float VOXEL_DAMPING = 0.0f; - float capsuleRadius = boundingShape.getRadius(); - float capsuleHalfHeight = boundingShape.getHalfHeight(); + const float capsuleRadius = boundingShape.getRadius(); + const float capsuleHalfHeight = boundingShape.getHalfHeight(); const float MAX_STEP_HEIGHT = capsuleRadius + capsuleHalfHeight; const float MIN_STEP_HEIGHT = 0.0f; - glm::vec3 footBase = boundingShape.getTranslation() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection; float highestStep = 0.0f; float lowestStep = MAX_STEP_HEIGHT; glm::vec3 floorPoint; @@ -1407,43 +1380,51 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { for (int i = 0; i < myCollisions.size(); ++i) { CollisionInfo* collision = myCollisions[i]; - glm::vec3 cubeCenter = collision->_vecData; - float cubeSide = collision->_floatData; + float verticalDepth = glm::dot(collision->_penetration, _worldUpDirection); float horizontalDepth = glm::length(collision->_penetration - verticalDepth * _worldUpDirection); const float MAX_TRAP_PERIOD = 0.125f; if (horizontalDepth > capsuleRadius || fabsf(verticalDepth) > MAX_STEP_HEIGHT) { isTrapped = true; if (_trapDuration > MAX_TRAP_PERIOD) { - float distance = glm::dot(boundingShape.getTranslation() - cubeCenter, _worldUpDirection); - if (distance < 0.0f) { - distance = fabsf(distance) + 0.5f * cubeSide; + RayIntersectionInfo intersection; + // we pick a rayStart that we expect to be inside the boundingShape (aka shapeA) + intersection._rayStart = collision->_contactPoint - MAX_STEP_HEIGHT * glm::normalize(collision->_penetration); + intersection._rayDirection = -_worldUpDirection; + // cast the ray down against shapeA + if (collision->_shapeA->findRayIntersection(intersection)) { + float firstDepth = - intersection._hitDistance; + // recycle intersection and cast again in up against shapeB + intersection._rayDirection = _worldUpDirection; + intersection._hitDistance = FLT_MAX; + if (collision->_shapeB->findRayIntersection(intersection)) { + // now we know how much we need to move UP to get out + totalPenetration = addPenetrations(totalPenetration, + (firstDepth + intersection._hitDistance) * _worldUpDirection); + } } - distance += capsuleRadius + capsuleHalfHeight; - totalPenetration = addPenetrations(totalPenetration, - distance * _worldUpDirection); continue; } } else if (_trapDuration > MAX_TRAP_PERIOD) { - // we're trapped, ignore this collision + // we're trapped, ignore this shallow collision continue; } totalPenetration = addPenetrations(totalPenetration, collision->_penetration); + + // some logic to help us walk up steps if (glm::dot(collision->_penetration, _velocity) >= 0.0f) { - glm::vec3 cubeTop = cubeCenter + (0.5f * cubeSide) * _worldUpDirection; - float stepHeight = glm::dot(_worldUpDirection, cubeTop - footBase); + float stepHeight = - glm::dot(_worldUpDirection, collision->_penetration); if (stepHeight > highestStep) { highestStep = stepHeight; stepPenetration = collision->_penetration; } if (stepHeight < lowestStep) { lowestStep = stepHeight; - floorPoint = collision->_contactPoint - collision->_penetration; + // remember that collision is in _physicsSimulation frame so we must add _position + floorPoint = _position + collision->_contactPoint - collision->_penetration; } } } - if (lowestStep < MAX_STEP_HEIGHT) { - _lastFloorContactPoint = floorPoint; - } float penetrationLength = glm::length(totalPenetration); if (penetrationLength < EPSILON) { @@ -1453,12 +1434,11 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { float verticalPenetration = glm::dot(totalPenetration, _worldUpDirection); if (highestStep > MIN_STEP_HEIGHT && highestStep < MAX_STEP_HEIGHT && verticalPenetration <= 0.0f) { // we're colliding against an edge + + // rotate _motorVelocity into world frame glm::vec3 targetVelocity = _motorVelocity; - if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) { - // rotate _motorVelocity into world frame - glm::quat rotation = getHead()->getCameraOrientation(); - targetVelocity = rotation * _motorVelocity; - } + glm::quat rotation = getHead()->getCameraOrientation(); + targetVelocity = rotation * _motorVelocity; if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) { // we're puhing into the edge, so we want to lift @@ -1834,6 +1814,17 @@ void MyAvatar::updateMotionBehaviorsFromMenu() { } } +void MyAvatar::onToggleRagdoll() { + Ragdoll* ragdoll = _skeletonModel.getRagdoll(); + if (ragdoll) { + if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) { + _physicsSimulation.setRagdoll(ragdoll); + } else { + _physicsSimulation.setRagdoll(NULL); + } + } +} + void MyAvatar::renderAttachments(RenderMode renderMode) { if (Application::getInstance()->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON || renderMode == MIRROR_RENDER_MODE) { Avatar::renderAttachments(renderMode); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 758b1f92bb..d86829ea91 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -164,6 +164,7 @@ public slots: void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } void updateMotionBehaviorsFromMenu(); + void onToggleRagdoll(); glm::vec3 getLeftPalmPosition(); glm::vec3 getRightPalmPosition(); @@ -206,7 +207,6 @@ private: float _maxMotorSpeed; quint32 _motionBehaviors; - glm::vec3 _lastFloorContactPoint; QWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; bool _shouldRender; @@ -220,7 +220,6 @@ private: RecorderPointer _recorder; // private methods - float computeDistanceToFloor(const glm::vec3& startPoint); void updateOrientation(float deltaTime); void updatePosition(float deltaTime); float computeMotorTimescale(const glm::vec3& velocity); diff --git a/interface/src/avatar/VoxelShapeManager.cpp b/interface/src/avatar/VoxelShapeManager.cpp index a73679a2c4..f59077eb91 100644 --- a/interface/src/avatar/VoxelShapeManager.cpp +++ b/interface/src/avatar/VoxelShapeManager.cpp @@ -17,7 +17,7 @@ #include "VoxelShapeManager.h" -VoxelShapeManager::VoxelShapeManager() : PhysicsEntity(), _lastSimulationTranslation(0.0f) { +VoxelShapeManager::VoxelShapeManager() : PhysicsEntity(), _updateExpiry(0), _lastSimulationTranslation(0.0f) { } VoxelShapeManager::~VoxelShapeManager() { @@ -57,7 +57,9 @@ void VoxelShapeManager::clearShapes() { _voxels.clear(); } -void VoxelShapeManager::updateVoxels(CubeList& cubes) { +void VoxelShapeManager::updateVoxels(const quint64& now, CubeList& cubes) { + const quint64 VOXEL_UPDATE_PERIOD = 100000; // usec + _updateExpiry = now + VOXEL_UPDATE_PERIOD; PhysicsSimulation* simulation = getSimulation(); if (!simulation) { return; diff --git a/interface/src/avatar/VoxelShapeManager.h b/interface/src/avatar/VoxelShapeManager.h index 1b7179788d..fe0024b321 100644 --- a/interface/src/avatar/VoxelShapeManager.h +++ b/interface/src/avatar/VoxelShapeManager.h @@ -28,7 +28,7 @@ public: AACubeShape* _shape; }; -typedef QHash VoxelPool; +typedef QHash VoxelPool; class VoxelShapeManager : public PhysicsEntity { public: @@ -39,11 +39,14 @@ public: void buildShapes(); void clearShapes(); + bool needsUpdate(const quint64& now) const { return _updateExpiry < now; } + /// \param cubes list of AACubes representing all of the voxels that should be in this VoxelShapeManager - void updateVoxels(CubeList& cubes); + void updateVoxels(const quint64& now, CubeList& cubes); private: + quint64 _updateExpiry; glm::vec3 _lastSimulationTranslation; VoxelPool _voxels; }; diff --git a/interface/src/devices/CaraFaceTracker.cpp b/interface/src/devices/CaraFaceTracker.cpp index 9f056fab9b..bc2c4bb2d1 100644 --- a/interface/src/devices/CaraFaceTracker.cpp +++ b/interface/src/devices/CaraFaceTracker.cpp @@ -309,7 +309,7 @@ void CaraFaceTracker::bindTo(const QHostAddress& host, quint16 port) { } bool CaraFaceTracker::isActive() const { - static const int ACTIVE_TIMEOUT_USECS = 3000000; //3 secs + static const quint64 ACTIVE_TIMEOUT_USECS = 3000000; //3 secs return (usecTimestampNow() - _lastReceiveTimestamp < ACTIVE_TIMEOUT_USECS); } diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index aab3e1deb4..b9f7e338ca 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -122,7 +122,7 @@ void DdeFaceTracker::bindTo(const QHostAddress& host, quint16 port) { } bool DdeFaceTracker::isActive() const { - static const int ACTIVE_TIMEOUT_USECS = 3000000; //3 secs + static const quint64 ACTIVE_TIMEOUT_USECS = 3000000; //3 secs return (usecTimestampNow() - _lastReceiveTimestamp < ACTIVE_TIMEOUT_USECS); } @@ -172,8 +172,8 @@ float DdeFaceTracker::getBlendshapeCoefficient(int index) const { return (index >= 0 && index < (int)_blendshapeCoefficients.size()) ? _blendshapeCoefficients[index] : 0.0f; } -static const float DDE_MIN_RANGE = -0.2; -static const float DDE_MAX_RANGE = 1.5; +static const float DDE_MIN_RANGE = -0.2f; +static const float DDE_MAX_RANGE = 1.5f; float rescaleCoef(float ddeCoef) { return (ddeCoef - DDE_MIN_RANGE) / (DDE_MAX_RANGE - DDE_MIN_RANGE); } diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 85e4e9c94f..51e1249bd9 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -894,7 +894,21 @@ void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) { } void Model::setScaleToFit(bool scaleToFit, float largestDimension) { - setScaleToFit(scaleToFit, glm::vec3(largestDimension, largestDimension, largestDimension)); + if (_scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) { + _scaleToFit = scaleToFit; + + // we only need to do this work if we're "turning on" scale to fit. + if (scaleToFit) { + Extents modelMeshExtents = getUnscaledMeshExtents(); + float maxDimension = glm::distance(modelMeshExtents.maximum, modelMeshExtents.minimum); + float maxScale = largestDimension / maxDimension; + glm::vec3 modelMeshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum; + glm::vec3 dimensions = modelMeshDimensions * maxScale; + + _scaleToFitDimensions = dimensions; + _scaledToFit = false; // force rescaling + } + } } void Model::scaleToFit() { diff --git a/interface/src/scripting/GlobalServicesScriptingInterface.cpp b/interface/src/scripting/GlobalServicesScriptingInterface.cpp index 8c117a4a11..26fa7c564b 100644 --- a/interface/src/scripting/GlobalServicesScriptingInterface.cpp +++ b/interface/src/scripting/GlobalServicesScriptingInterface.cpp @@ -104,10 +104,12 @@ void GlobalServicesScriptingInterface::loggedOut() { emit GlobalServicesScriptingInterface::disconnected(QString("logout")); } +#ifdef HAVE_QXMPP void GlobalServicesScriptingInterface::messageReceived(const QXmppMessage& message) { if (message.type() != QXmppMessage::GroupChat) { return; } const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom(); emit GlobalServicesScriptingInterface::incomingMessage(message.from().right(message.from().count() - 1 - publicChatRoom->jid().count()), message.body()); -} \ No newline at end of file +} +#endif // HAVE_QXMPP diff --git a/interface/src/scripting/GlobalServicesScriptingInterface.h b/interface/src/scripting/GlobalServicesScriptingInterface.h index 370ba1ade9..44b2f7c1bf 100644 --- a/interface/src/scripting/GlobalServicesScriptingInterface.h +++ b/interface/src/scripting/GlobalServicesScriptingInterface.h @@ -24,7 +24,7 @@ #include #include -#endif +#endif // HAVE_QXMPP class GlobalServicesScriptingInterface : public QObject { Q_OBJECT @@ -47,7 +47,9 @@ private slots: void loggedOut(); void onConnected(); void participantsChanged(); +#ifdef HAVE_QXMPP void messageReceived(const QXmppMessage& message); +#endif // HAVE_QXMPP signals: void connected(); diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index cf839f6800..64cf12373d 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -1022,7 +1022,7 @@ void ImportHeightfieldTool::apply() { QByteArray color; if (buffer->getColor().isEmpty()) { - const int WHITE_VALUE = 0xFF; + const unsigned char WHITE_VALUE = 0xFF; color = QByteArray(height.size() * DataBlock::COLOR_BYTES, WHITE_VALUE); } else { color = buffer->getUnextendedColor(); diff --git a/libraries/audio/src/AudioInjectorOptions.h b/libraries/audio/src/AudioInjectorOptions.h index e1beb3e689..66e8f1550b 100644 --- a/libraries/audio/src/AudioInjectorOptions.h +++ b/libraries/audio/src/AudioInjectorOptions.h @@ -24,9 +24,11 @@ class AudioInjectorOptions : public QObject { Q_OBJECT + Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) Q_PROPERTY(float volume READ getVolume WRITE setVolume) Q_PROPERTY(bool loop READ getLoop WRITE setLoop) + Q_PROPERTY(bool isStereo READ isStereo WRITE setIsStereo) public: AudioInjectorOptions(QObject* parent = 0); AudioInjectorOptions(const AudioInjectorOptions& other); diff --git a/libraries/audio/src/AudioSourceNoise.h b/libraries/audio/src/AudioSourceNoise.h index 26cb21a065..3e71703893 100644 --- a/libraries/audio/src/AudioSourceNoise.h +++ b/libraries/audio/src/AudioSourceNoise.h @@ -48,7 +48,7 @@ public: _runningSum = 0; _index = 0; - _indexMask = (1 << _randomRows) - 1; + _indexMask = (uint16_t)((1 << _randomRows) - 1); _scale = 1.0f / ((_randomRows + 1) * (1 << (_randomBits - 1))); } diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index e12dbb42a9..cc474ea491 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -377,7 +377,7 @@ void InboundAudioStream::packetReceivedUpdateTimingStats() { // update our timegap stats and desired jitter buffer frames if necessary // discard the first few packets we receive since they usually have gaps that aren't represensative of normal jitter - const int NUM_INITIAL_PACKETS_DISCARD = 3; + const quint32 NUM_INITIAL_PACKETS_DISCARD = 3; quint64 now = usecTimestampNow(); if (_incomingSequenceNumberStats.getReceived() > NUM_INITIAL_PACKETS_DISCARD) { quint64 gap = now - _lastPacketReceivedTime; diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index ca9591a746..a395b1c6c8 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -33,7 +33,7 @@ const int STATS_FOR_STATS_PACKET_WINDOW_SECONDS = 30; // this controls the window size of the time-weighted avg of frames available. Every time the window fills up, // _currentJitterBufferFrames is updated with the time-weighted avg and the running time-weighted avg is reset. -const int FRAMES_AVAILABLE_STAT_WINDOW_USECS = 10 * USECS_PER_SECOND; +const quint64 FRAMES_AVAILABLE_STAT_WINDOW_USECS = 10 * USECS_PER_SECOND; // default values for members of the Settings struct const int DEFAULT_MAX_FRAMES_OVER_DESIRED = 10; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index d70876722b..5b0c6b97dd 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -55,20 +55,14 @@ typedef unsigned long long quint64; #include "HandData.h" // avatar motion behaviors -const quint32 AVATAR_MOTION_MOTOR_ENABLED = 1U << 0; -const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 1; -const quint32 AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME = 1U << 2; -const quint32 AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY = 1U << 3; +const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 0; -const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 4; -const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 5; - -const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 6; +const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 1; +const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 2; +const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 3; const quint32 AVATAR_MOTION_DEFAULTS = - AVATAR_MOTION_MOTOR_ENABLED | AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED | - AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME | AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; // these bits will be expanded as features are exposed diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8cec19eddd..9a6e53b17a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -79,7 +79,10 @@ void EntityTree::addEntityItem(EntityItem* entityItem) { // You should not call this on existing entities that are already part of the tree! Call updateEntity() EntityItemID entityID = entityItem->getEntityItemID(); EntityTreeElement* containingElement = getContainingElement(entityID); - assert(containingElement == NULL); // don't call addEntityItem() on existing entity items + if (containingElement) { + qDebug() << "UNEXPECTED!!!! don't call addEntityItem() on existing entity items. entityID=" << entityID; + return; + } // Recurse the tree and store the entity in the correct tree element AddEntityOperator theOperator(this, entityItem); @@ -95,14 +98,13 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp // You should not call this on existing entities that are already part of the tree! Call updateEntity() EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { - //assert(containingElement); // don't call updateEntity() on entity items that don't exist qDebug() << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID; return false; } EntityItem* existingEntity = containingElement->getEntityWithEntityItemID(entityID); if (!existingEntity) { - assert(existingEntity); // don't call updateEntity() on entity items that don't exist + qDebug() << "UNEXPECTED!!!! don't call updateEntity() on entity items that don't exist. entityID=" << entityID; return false; } @@ -118,8 +120,8 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp containingElement = getContainingElement(entityID); if (!containingElement) { - qDebug() << "after updateEntity() we no longer have a containing element???"; - assert(containingElement); // don't call updateEntity() on entity items that don't exist + qDebug() << "UNEXPECTED!!!! after updateEntity() we no longer have a containing element??? entityID=" << entityID; + return false; } return true; @@ -127,19 +129,20 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp EntityItem* EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { + EntityItem* result = NULL; + // NOTE: This method is used in the client and the server tree. In the client, it's possible to create EntityItems // that do not yet have known IDs. In the server tree however we don't want to have entities without known IDs. if (getIsServer() && !entityID.isKnownID) { - //assert(entityID.isKnownID); qDebug() << "UNEXPECTED!!! ----- EntityTree::addEntity()... (getIsSever() && !entityID.isKnownID)"; + return result; } - EntityItem* result = NULL; // You should not call this on existing entities that are already part of the tree! Call updateEntity() EntityTreeElement* containingElement = getContainingElement(entityID); if (containingElement) { - qDebug() << "UNEXPECTED!!! ----- EntityTree::addEntity()... entityID=" << entityID << "containingElement=" << containingElement; - assert(containingElement == NULL); // don't call addEntity() on existing entity items + qDebug() << "UNEXPECTED!!! ----- don't call addEntity() on existing entity items. entityID=" << entityID + << "containingElement=" << containingElement; return result; } @@ -239,9 +242,9 @@ void EntityTree::removeEntityFromSimulationLists(const EntityItemID& entityID) { /// based to known IDs. This means we don't have to recurse the tree to mark the changed path as dirty. void EntityTree::handleAddEntityResponse(const QByteArray& packet) { - //assert(getIsClient()); // we should only call this on client trees if (!getIsClient()) { qDebug() << "UNEXPECTED!!! EntityTree::handleAddEntityResponse() with !getIsClient() ***"; + return; } const unsigned char* dataAt = reinterpret_cast(packet.data()); @@ -430,8 +433,15 @@ EntityItem* EntityTree::findEntityByEntityItemID(const EntityItemID& entityID) / } EntityItemID EntityTree::assignEntityID(const EntityItemID& entityItemID) { - assert(getIsServer()); // NOTE: this only operates on an server tree. - assert(!getContainingElement(entityItemID)); // NOTE: don't call this for existing entityIDs + if (!getIsServer()) { + qDebug() << "UNEXPECTED!!! assignEntityID should only be called on a server tree. entityItemID:" << entityItemID; + return entityItemID; + } + + if (getContainingElement(entityItemID)) { + qDebug() << "UNEXPECTED!!! don't call assignEntityID() for existing entityIDs. entityItemID:" << entityItemID; + return entityItemID; + } // The EntityItemID is responsible for assigning actual IDs and keeping track of them. return entityItemID.assignActualIDForToken(); @@ -440,7 +450,10 @@ EntityItemID EntityTree::assignEntityID(const EntityItemID& entityItemID) { int EntityTree::processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) { - assert(getIsServer()); // NOTE: this only operates on an server tree. + if (!getIsServer()) { + qDebug() << "UNEXPECTED!!! processEditPacketData() should only be called on a server tree."; + return 0; + } int processedBytes = 0; // we handle these types of "edit" packets @@ -969,9 +982,21 @@ EntityTreeElement* EntityTree::getContainingElement(const EntityItemID& entityIt // TODO: do we need to make this thread safe? Or is it acceptable as is void EntityTree::resetContainingElement(const EntityItemID& entityItemID, EntityTreeElement* element) { - assert(entityItemID.id != UNKNOWN_ENTITY_ID); - assert(entityItemID.creatorTokenID != UNKNOWN_ENTITY_TOKEN); - assert(element); + if (entityItemID.id == UNKNOWN_ENTITY_ID) { + //assert(entityItemID.id != UNKNOWN_ENTITY_ID); + qDebug() << "UNEXPECTED! resetContainingElement() called with UNKNOWN_ENTITY_ID. entityItemID:" << entityItemID; + return; + } + if (entityItemID.creatorTokenID == UNKNOWN_ENTITY_TOKEN) { + //assert(entityItemID.creatorTokenID != UNKNOWN_ENTITY_TOKEN); + qDebug() << "UNEXPECTED! resetContainingElement() called with UNKNOWN_ENTITY_TOKEN. entityItemID:" << entityItemID; + return; + } + if (!element) { + //assert(element); + qDebug() << "UNEXPECTED! resetContainingElement() called with NULL element. entityItemID:" << entityItemID; + return; + } // remove the old version with the creatorTokenID EntityItemID creatorTokenVersion; diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index f6e78275d3..8851b04b7e 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -64,7 +64,9 @@ private: // static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); #define REGISTER_ENTITY_TYPE_WITH_FACTORY(x,y) static bool x##Registration = \ EntityTypes::registerEntityType(EntityTypes::x, #x, y); \ - assert(x##Registration); + if (!x##Registration) { \ + qDebug() << "UNEXPECTED: REGISTER_ENTITY_TYPE_WITH_FACTORY(" #x "," #y ") FAILED.!"; \ + } #endif // hifi_EntityTypes_h diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 5d83d4034e..1a1412d305 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -851,7 +851,7 @@ bool findShapeCollisionsOp(OctreeElement* element, void* extraData) { return false; } -quint64 cubeListHashKey(const glm::vec3& point) { +uint qHash(const glm::vec3& point) { // NOTE: TREE_SCALE = 16384 (15 bits) and multiplier is 1024 (11 bits), // so each component (26 bits) uses more than its alloted 21 bits. // however we don't expect to span huge cubes so it is ok if we wrap @@ -859,9 +859,9 @@ quint64 cubeListHashKey(const glm::vec3& point) { const uint BITS_PER_COMPONENT = 21; const quint64 MAX_SCALED_COMPONENT = 2097152; // 2^21 const float RESOLUTION_PER_METER = 1024.0f; // 2^10 - return (quint64)(point.x * RESOLUTION_PER_METER) % MAX_SCALED_COMPONENT + + return qHash((quint64)(point.x * RESOLUTION_PER_METER) % MAX_SCALED_COMPONENT + (((quint64)(point.y * RESOLUTION_PER_METER)) % MAX_SCALED_COMPONENT << BITS_PER_COMPONENT) + - (((quint64)(point.z * RESOLUTION_PER_METER)) % MAX_SCALED_COMPONENT << 2 * BITS_PER_COMPONENT); + (((quint64)(point.z * RESOLUTION_PER_METER)) % MAX_SCALED_COMPONENT << 2 * BITS_PER_COMPONENT)); } bool findContentInCubeOp(OctreeElement* element, void* extraData) { @@ -877,8 +877,9 @@ bool findContentInCubeOp(OctreeElement* element, void* extraData) { return true; // recurse on children } if (element->hasContent()) { - // NOTE: the voxel's center is unique so we use it as the input for the key - args->cubes->insert(cubeListHashKey(cube.calcCenter()), cube); + // NOTE: the voxel's center is unique so we use it as the input for the key. + // We use the qHash(glm::vec()) as the key as an optimization for the code that uses CubeLists. + args->cubes->insert(qHash(cube.calcCenter()), cube); return true; } return false; diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 5bb33d8c75..e07d2e2688 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -48,7 +48,7 @@ public: // Callback function, for recuseTreeWithOperation typedef bool (*RecurseOctreeOperation)(OctreeElement* element, void* extraData); typedef enum {GRADIENT, RANDOM, NATURAL} creationMode; -typedef QHash CubeList; +typedef QHash CubeList; const bool NO_EXISTS_BITS = false; const bool WANT_EXISTS_BITS = true; diff --git a/libraries/shared/src/AACubeShape.cpp b/libraries/shared/src/AACubeShape.cpp index 70477d5682..30197d6bfd 100644 --- a/libraries/shared/src/AACubeShape.cpp +++ b/libraries/shared/src/AACubeShape.cpp @@ -9,8 +9,68 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "AACubeShape.h" +#include +#include -bool AACubeShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const { - return false; +#include "AACubeShape.h" +#include "SharedUtil.h" // for SQUARE_ROOT_OF_3 + +glm::vec3 faceNormals[3] = { glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f) }; + +bool AACubeShape::findRayIntersection(RayIntersectionInfo& intersection) const { + // A = ray point + // B = cube center + glm::vec3 BA = _translation - intersection._rayStart; + + // check for ray intersection with cube's bounding sphere + // a = distance along line to closest approach to B + float a = glm::dot(intersection._rayDirection, BA); + // b2 = squared distance from cube center to point of closest approach + float b2 = glm::length2(a * intersection._rayDirection - BA); + // r = bounding radius of cube + float halfSide = 0.5f * _scale; + const float r = SQUARE_ROOT_OF_3 * halfSide; + if (b2 > r * r) { + // line doesn't hit cube's bounding sphere + return false; + } + + // check for tuncated/short ray + // maxLength = maximum possible distance between rayStart and center of cube + const float maxLength = glm::min(intersection._rayLength, intersection._hitDistance) + r; + if (a * a + b2 > maxLength * maxLength) { + // ray is not long enough to reach cube's bounding sphere + // NOTE: we don't fall in here when ray's length if FLT_MAX because maxLength^2 will be inf or nan + return false; + } + + // the trivial checks have been exhausted, so must trace to each face + bool hit = false; + for (int i = 0; i < 3; ++i) { + for (float sign = -1.0f; sign < 2.0f; sign += 2.0f) { + glm::vec3 faceNormal = sign * faceNormals[i]; + float rayDotPlane = glm::dot(intersection._rayDirection, faceNormal); + if (glm::abs(rayDotPlane) > EPSILON) { + float distanceToFace = (halfSide + glm::dot(BA, faceNormal)) / rayDotPlane; + if (distanceToFace >= 0.0f) { + glm::vec3 point = distanceToFace * intersection._rayDirection - BA; + int j = (i + 1) % 3; + int k = (i + 2) % 3; + glm::vec3 secondNormal = faceNormals[j]; + glm::vec3 thirdNormal = faceNormals[k]; + if (glm::abs(glm::dot(point, secondNormal)) > halfSide || + glm::abs(glm::dot(point, thirdNormal)) > halfSide) { + continue; + } + if (distanceToFace < intersection._hitDistance && distanceToFace < intersection._rayLength) { + intersection._hitDistance = distanceToFace; + intersection._hitNormal = faceNormal; + intersection._hitShape = const_cast(this); + hit = true; + } + } + } + } + } + return hit; } diff --git a/libraries/shared/src/AACubeShape.h b/libraries/shared/src/AACubeShape.h index 96010926c7..4b834aa1bf 100644 --- a/libraries/shared/src/AACubeShape.h +++ b/libraries/shared/src/AACubeShape.h @@ -25,7 +25,7 @@ public: float getScale() const { return _scale; } void setScale(float scale) { _scale = scale; } - bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const; + bool findRayIntersection(RayIntersectionInfo& intersection) const; float getVolume() const { return _scale * _scale * _scale; } diff --git a/libraries/shared/src/ByteCountCoding.h b/libraries/shared/src/ByteCountCoding.h index ada7e7a4cb..ddaa750e86 100644 --- a/libraries/shared/src/ByteCountCoding.h +++ b/libraries/shared/src/ByteCountCoding.h @@ -59,23 +59,15 @@ template inline QByteArray& operator>>(QByteArray& in, ByteCountCode template inline QByteArray ByteCountCoded::encode() const { QByteArray output; - //qDebug() << "data="; - //outputBufferBits((const unsigned char*)&data, sizeof(data)); - - T totalBits = sizeof(data) * BITS_IN_BYTE; - //qDebug() << "totalBits=" << totalBits; - T valueBits = totalBits; + int totalBits = sizeof(data) * BITS_IN_BYTE; + int valueBits = totalBits; bool firstValueFound = false; T temp = data; T lastBitMask = (T)(1) << (totalBits - 1); - //qDebug() << "lastBitMask="; - //outputBufferBits((const unsigned char*)&lastBitMask, sizeof(lastBitMask)); - // determine the number of bits that the value takes for (int bitAt = 0; bitAt < totalBits; bitAt++) { T bitValue = (temp & lastBitMask) == lastBitMask; - //qDebug() << "bitValue[" << bitAt <<"]=" << bitValue; if (!firstValueFound) { if (bitValue == 0) { valueBits--; @@ -85,17 +77,12 @@ template inline QByteArray ByteCountCoded::encode() const { } temp = temp << 1; } - //qDebug() << "valueBits=" << valueBits; // calculate the number of total bytes, including our header // BITS_IN_BYTE-1 because we need to code the number of bytes in the header // + 1 because we always take at least 1 byte, even if number of bits is less than a bytes worth int numberOfBytes = (valueBits / (BITS_IN_BYTE - 1)) + 1; - //qDebug() << "numberOfBytes=" << numberOfBytes; - //int numberOfBits = numberOfBytes + valueBits; - //qDebug() << "numberOfBits=" << numberOfBits; - output.fill(0, numberOfBytes); // next pack the number of header bits in, the first N-1 to be set to 1, the last to be set to 0 diff --git a/libraries/shared/src/CapsuleShape.cpp b/libraries/shared/src/CapsuleShape.cpp index 09776a233f..5bb118d36e 100644 --- a/libraries/shared/src/CapsuleShape.cpp +++ b/libraries/shared/src/CapsuleShape.cpp @@ -78,13 +78,135 @@ void CapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& en updateBoundingRadius(); } -bool CapsuleShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const { - glm::vec3 capsuleStart, capsuleEnd; - getStartPoint(capsuleStart); - getEndPoint(capsuleEnd); - // NOTE: findRayCapsuleIntersection returns 'true' with distance = 0 when rayStart is inside capsule. - // TODO: implement the raycast to return inside surface intersection for the internal rayStart. - return findRayCapsuleIntersection(rayStart, rayDirection, capsuleStart, capsuleEnd, _radius, distance); +// helper +bool findRayIntersectionWithCap(const glm::vec3& sphereCenter, float sphereRadius, + const glm::vec3& capsuleCenter, RayIntersectionInfo& intersection) { + float r2 = sphereRadius * sphereRadius; + + // compute closest approach (CA) + float a = glm::dot(sphereCenter - intersection._rayStart, intersection._rayDirection); // a = distance from ray-start to CA + float b2 = glm::distance2(sphereCenter, intersection._rayStart + a * intersection._rayDirection); // b2 = squared distance from sphere-center to CA + if (b2 > r2) { + // ray does not hit sphere + return false; + } + float c = sqrtf(r2 - b2); // c = distance from CA to sphere surface along intersection._rayDirection + float d2 = glm::distance2(intersection._rayStart, sphereCenter); // d2 = squared distance from sphere-center to ray-start + float distance = FLT_MAX; + if (a < 0.0f) { + // ray points away from sphere-center + if (d2 > r2) { + // ray starts outside sphere + return false; + } + // ray starts inside sphere + distance = c + a; + } else if (d2 > r2) { + // ray starts outside sphere + distance = a - c; + } else { + // ray starts inside sphere + distance = a + c; + } + if (distance > 0.0f && distance < intersection._rayLength && distance < intersection._hitDistance) { + glm::vec3 sphereCenterToHitPoint = intersection._rayStart + distance * intersection._rayDirection - sphereCenter; + if (glm::dot(sphereCenterToHitPoint, sphereCenter - capsuleCenter) >= 0.0f) { + intersection._hitDistance = distance; + intersection._hitNormal = glm::normalize(sphereCenterToHitPoint); + return true; + } + } + return false; +} + +bool CapsuleShape::findRayIntersectionWithCaps(const glm::vec3& capsuleCenter, RayIntersectionInfo& intersection) const { + glm::vec3 capCenter; + getStartPoint(capCenter); + bool hit = findRayIntersectionWithCap(capCenter, _radius, capsuleCenter, intersection); + getEndPoint(capCenter); + hit = findRayIntersectionWithCap(capCenter, _radius, capsuleCenter, intersection) || hit; + if (hit) { + intersection._hitShape = const_cast(this); + } + return hit; +} + +bool CapsuleShape::findRayIntersection(RayIntersectionInfo& intersection) const { + // ray is U, capsule is V + glm::vec3 axisV; + computeNormalizedAxis(axisV); + glm::vec3 centerV = getTranslation(); + + // first handle parallel case + float uDotV = glm::dot(axisV, intersection._rayDirection); + glm::vec3 UV = intersection._rayStart - centerV; + if (glm::abs(1.0f - glm::abs(uDotV)) < EPSILON) { + // line and cylinder are parallel + float distanceV = glm::dot(UV, intersection._rayDirection); + if (glm::length2(UV - distanceV * intersection._rayDirection) <= _radius * _radius) { + // ray is inside cylinder's radius and might intersect caps + return findRayIntersectionWithCaps(centerV, intersection); + } + return false; + } + + // Given a line with point 'U' and normalized direction 'u' and + // a cylinder with axial point 'V', radius 'r', and normalized direction 'v' + // the intersection of the two is on the line at distance 't' from 'U'. + // + // Determining the values of t reduces to solving a quadratic equation: At^2 + Bt + C = 0 + // + // where: + // + // UV = U-V + // w = u-(u.v)v + // Q = UV-(UV.v)v + // + // A = w^2 + // B = 2(w.Q) + // C = Q^2 - r^2 + + glm::vec3 w = intersection._rayDirection - uDotV * axisV; + glm::vec3 Q = UV - glm::dot(UV, axisV) * axisV; + + // we save a few multiplies by storing 2*A rather than just A + float A2 = 2.0f * glm::dot(w, w); + float B = 2.0f * glm::dot(w, Q); + + // since C is only ever used once (in the determinant) we compute it inline + float determinant = B * B - 2.0f * A2 * (glm::dot(Q, Q) - _radius * _radius); + if (determinant < 0.0f) { + return false; + } + float hitLow = (-B - sqrtf(determinant)) / A2; + float hitHigh = -(hitLow + 2.0f * B / A2); + + if (hitLow > hitHigh) { + // re-arrange so hitLow is always the smaller value + float temp = hitHigh; + hitHigh = hitLow; + hitLow = temp; + } + if (hitLow < 0.0f) { + if (hitHigh < 0.0f) { + // capsule is completely behind rayStart + return false; + } + hitLow = hitHigh; + } + + glm::vec3 p = intersection._rayStart + hitLow * intersection._rayDirection; + float d = glm::dot(p - centerV, axisV); + if (glm::abs(d) <= getHalfHeight()) { + // we definitely hit the cylinder wall + intersection._hitDistance = hitLow; + intersection._hitNormal = glm::normalize(p - centerV - d * axisV); + intersection._hitShape = const_cast(this); + return true; + } + + // ray still might hit the caps + return findRayIntersectionWithCaps(centerV, intersection); } // static diff --git a/libraries/shared/src/CapsuleShape.h b/libraries/shared/src/CapsuleShape.h index 8d84e32a97..6e889f6566 100644 --- a/libraries/shared/src/CapsuleShape.h +++ b/libraries/shared/src/CapsuleShape.h @@ -47,11 +47,12 @@ public: /// Sets the endpoints and updates center, rotation, and halfHeight to agree. virtual void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint); - bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const; + bool findRayIntersection(RayIntersectionInfo& intersection) const; virtual float getVolume() const { return (PI * _radius * _radius) * (1.3333333333f * _radius + getHalfHeight()); } protected: + bool findRayIntersectionWithCaps(const glm::vec3& capsuleCenter, RayIntersectionInfo& intersection) const; virtual void updateBoundingRadius() { _boundingRadius = _radius + getHalfHeight(); } static glm::quat computeNewRotation(const glm::vec3& newAxis); diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 566983679b..0c7f126893 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -86,12 +86,13 @@ int unpackFloatAngleFromTwoByte(const uint16_t* byteAnglePointer, float* destina } int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput) { + glm::quat quatNormalized = glm::normalize(quatInput); const float QUAT_PART_CONVERSION_RATIO = (std::numeric_limits::max() / 2.f); uint16_t quatParts[4]; - quatParts[0] = floorf((quatInput.x + 1.f) * QUAT_PART_CONVERSION_RATIO); - quatParts[1] = floorf((quatInput.y + 1.f) * QUAT_PART_CONVERSION_RATIO); - quatParts[2] = floorf((quatInput.z + 1.f) * QUAT_PART_CONVERSION_RATIO); - quatParts[3] = floorf((quatInput.w + 1.f) * QUAT_PART_CONVERSION_RATIO); + quatParts[0] = floorf((quatNormalized.x + 1.f) * QUAT_PART_CONVERSION_RATIO); + quatParts[1] = floorf((quatNormalized.y + 1.f) * QUAT_PART_CONVERSION_RATIO); + quatParts[2] = floorf((quatNormalized.z + 1.f) * QUAT_PART_CONVERSION_RATIO); + quatParts[3] = floorf((quatNormalized.w + 1.f) * QUAT_PART_CONVERSION_RATIO); memcpy(buffer, &quatParts, sizeof(quatParts)); return sizeof(quatParts); diff --git a/libraries/shared/src/PhysicsEntity.cpp b/libraries/shared/src/PhysicsEntity.cpp index 6be37a7528..a01706f539 100644 --- a/libraries/shared/src/PhysicsEntity.cpp +++ b/libraries/shared/src/PhysicsEntity.cpp @@ -76,23 +76,8 @@ void PhysicsEntity::clearShapes() { _shapes.clear(); } -bool PhysicsEntity::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { - int numShapes = _shapes.size(); - float minDistance = FLT_MAX; - for (int j = 0; j < numShapes; ++j) { - const Shape* shape = _shapes[j]; - float thisDistance = FLT_MAX; - if (shape && shape->findRayIntersection(origin, direction, thisDistance)) { - if (thisDistance < minDistance) { - minDistance = thisDistance; - } - } - } - if (minDistance < FLT_MAX) { - distance = minDistance; - return true; - } - return false; +bool PhysicsEntity::findRayIntersection(RayIntersectionInfo& intersection) const { + return ShapeCollider::findRayIntersection(_shapes, intersection); } bool PhysicsEntity::findCollisions(const QVector shapes, CollisionList& collisions) { diff --git a/libraries/shared/src/PhysicsEntity.h b/libraries/shared/src/PhysicsEntity.h index 9f98cc96ca..a96754b75c 100644 --- a/libraries/shared/src/PhysicsEntity.h +++ b/libraries/shared/src/PhysicsEntity.h @@ -19,6 +19,7 @@ #include #include "CollisionInfo.h" +#include "RayIntersectionInfo.h" class Shape; class PhysicsSimulation; @@ -52,7 +53,7 @@ public: PhysicsSimulation* getSimulation() const { return _simulation; } - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + bool findRayIntersection(RayIntersectionInfo& intersection) const; bool findCollisions(const QVector shapes, CollisionList& collisions); bool findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions); bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions); diff --git a/libraries/shared/src/PhysicsSimulation.cpp b/libraries/shared/src/PhysicsSimulation.cpp index 93f0797c94..ee5ea9b2b8 100644 --- a/libraries/shared/src/PhysicsSimulation.cpp +++ b/libraries/shared/src/PhysicsSimulation.cpp @@ -207,9 +207,6 @@ void PhysicsSimulation::removeRagdoll(Ragdoll* doll) { void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIterations, quint64 maxUsec) { ++_frameCount; - if (!_ragdoll) { - return; - } quint64 now = usecTimestampNow(); quint64 startTime = now; quint64 expiry = startTime + maxUsec; @@ -219,7 +216,9 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter int numDolls = _otherRagdolls.size(); { PerformanceTimer perfTimer("enforce"); - _ragdoll->enforceConstraints(); + if (_ragdoll) { + _ragdoll->enforceConstraints(); + } for (int i = 0; i < numDolls; ++i) { _otherRagdolls[i]->enforceConstraints(); } @@ -235,7 +234,9 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter { // enforce constraints PerformanceTimer perfTimer("enforce"); - error = _ragdoll->enforceConstraints(); + if (_ragdoll) { + error = _ragdoll->enforceConstraints(); + } for (int i = 0; i < numDolls; ++i) { error = glm::max(error, _otherRagdolls[i]->enforceConstraints()); } @@ -246,9 +247,12 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter now = usecTimestampNow(); } while (_collisions.size() != 0 && (iterations < maxIterations) && (error > minError) && (now < expiry)); - // the collisions may have moved the main ragdoll from the simulation center - // so we remove this offset (potentially storing it as movement of the Ragdoll owner) - _ragdoll->removeRootOffset(collidedWithOtherRagdoll); + if (_ragdoll) { + // This is why _ragdoll is special and is not in the list of other ragdolls: + // The collisions may have moved the main ragdoll from the simulation center + // so we remove this offset (potentially storing it as movement of the Ragdoll owner) + _ragdoll->removeRootOffset(collidedWithOtherRagdoll); + } // also remove any offsets from the other ragdolls for (int i = 0; i < numDolls; ++i) { @@ -257,13 +261,41 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter pruneContacts(); } +bool PhysicsSimulation::findFloorRayIntersection(RayIntersectionInfo& intersection) const { + // only casts against otherEntities + bool hit = false; + int numEntities = _otherEntities.size(); + for (int i = 0; i < numEntities; ++i) { + const QVector otherShapes = _otherEntities.at(i)->getShapes(); + if (ShapeCollider::findRayIntersection(otherShapes, intersection)) { + hit = true; + } + } + return hit; +} + + +bool PhysicsSimulation::getShapeCollisions(const Shape* shape, CollisionList& collisions) const { + bool hit = false; + int numEntities = _otherEntities.size(); + for (int i = 0; i < numEntities; ++i) { + const QVector otherShapes = _otherEntities.at(i)->getShapes(); + if (ShapeCollider::collideShapeWithShapes(shape, otherShapes, 0, collisions)) { + hit = true; + } + } + return hit; +} + void PhysicsSimulation::integrate(float deltaTime) { PerformanceTimer perfTimer("integrate"); int numEntities = _otherEntities.size(); for (int i = 0; i < numEntities; ++i) { _otherEntities[i]->stepForward(deltaTime); } - _ragdoll->stepForward(deltaTime); + if (_ragdoll) { + _ragdoll->stepForward(deltaTime); + } int numDolls = _otherRagdolls.size(); for (int i = 0; i < numDolls; ++i) { _otherRagdolls[i]->stepForward(deltaTime); diff --git a/libraries/shared/src/PhysicsSimulation.h b/libraries/shared/src/PhysicsSimulation.h index 955029c174..12506e23d0 100644 --- a/libraries/shared/src/PhysicsSimulation.h +++ b/libraries/shared/src/PhysicsSimulation.h @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_PhysicsSimulation -#define hifi_PhysicsSimulation +#ifndef hifi_PhysicsSimulation_h +#define hifi_PhysicsSimulation_h #include #include @@ -18,6 +18,7 @@ #include "CollisionInfo.h" #include "ContactPoint.h" +#include "RayIntersectionInfo.h" class PhysicsEntity; class Ragdoll; @@ -54,6 +55,12 @@ public: /// \return distance of largest movement void stepForward(float deltaTime, float minError, int maxIterations, quint64 maxUsec); + /// \param intersection collision info about ray hit + /// \return true if ray hits any shape that doesn't belong to the main ragdoll/entity + bool findFloorRayIntersection(RayIntersectionInfo& hit) const; + + bool getShapeCollisions(const Shape* shape, CollisionList& collisions) const; + protected: void integrate(float deltaTime); @@ -80,4 +87,4 @@ private: QMap _contacts; }; -#endif // hifi_PhysicsSimulation +#endif // hifi_PhysicsSimulation_h diff --git a/libraries/shared/src/PlaneShape.cpp b/libraries/shared/src/PlaneShape.cpp index 72704c3116..845b58728a 100644 --- a/libraries/shared/src/PlaneShape.cpp +++ b/libraries/shared/src/PlaneShape.cpp @@ -11,6 +11,7 @@ #include "PlaneShape.h" #include "SharedUtil.h" +#include "GLMHelpers.h" const glm::vec3 UNROTATED_NORMAL(0.0f, 1.0f, 0.0f); @@ -34,22 +35,42 @@ glm::vec3 PlaneShape::getNormal() const { return _rotation * UNROTATED_NORMAL; } +void PlaneShape::setNormal(const glm::vec3& direction) { + glm::vec3 oldTranslation = _translation; + _rotation = rotationBetween(UNROTATED_NORMAL, direction); + glm::vec3 normal = getNormal(); + _translation = glm::dot(oldTranslation, normal) * normal; +} + +void PlaneShape::setPoint(const glm::vec3& point) { + glm::vec3 normal = getNormal(); + _translation = glm::dot(point, 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, _translation)); } -bool PlaneShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const { +bool PlaneShape::findRayIntersection(RayIntersectionInfo& intersection) const { glm::vec3 n = getNormal(); - float denominator = glm::dot(n, rayDirection); + float denominator = glm::dot(n, intersection._rayDirection); if (fabsf(denominator) < EPSILON) { // line is parallel to plane - return glm::dot(_translation - rayStart, n) < EPSILON; + if (glm::dot(_translation - intersection._rayStart, n) < EPSILON) { + // ray starts on the plane + intersection._hitDistance = 0.0f; + intersection._hitNormal = n; + intersection._hitShape = const_cast(this); + return true; + } } else { - float d = glm::dot(_translation - rayStart, n) / denominator; - if (d > 0.0f) { + float d = glm::dot(_translation - intersection._rayStart, n) / denominator; + if (d > 0.0f && d < intersection._rayLength && d < intersection._hitDistance) { // ray points toward plane - distance = d; + intersection._hitDistance = d; + intersection._hitNormal = n; + intersection._hitShape = const_cast(this); return true; } } diff --git a/libraries/shared/src/PlaneShape.h b/libraries/shared/src/PlaneShape.h index b8a93324b7..8d6de326af 100644 --- a/libraries/shared/src/PlaneShape.h +++ b/libraries/shared/src/PlaneShape.h @@ -21,7 +21,10 @@ public: glm::vec3 getNormal() const; glm::vec4 getCoefficients() const; - bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const; + void setNormal(const glm::vec3& normal); + void setPoint(const glm::vec3& point); + + bool findRayIntersection(RayIntersectionInfo& intersection) const; }; #endif // hifi_PlaneShape_h diff --git a/libraries/shared/src/PropertyFlags.h b/libraries/shared/src/PropertyFlags.h index 846c4886b0..04fe1475f7 100644 --- a/libraries/shared/src/PropertyFlags.h +++ b/libraries/shared/src/PropertyFlags.h @@ -419,7 +419,7 @@ template inline PropertyFlags PropertyFlags::operator } template inline void PropertyFlags::shinkIfNeeded() { - bool maxFlagWas = _maxFlag; + int maxFlagWas = _maxFlag; while (_maxFlag >= 0) { if (_flags.testBit(_maxFlag)) { break; diff --git a/libraries/shared/src/RayIntersectionInfo.h b/libraries/shared/src/RayIntersectionInfo.h new file mode 100644 index 0000000000..6c4eb3f8dd --- /dev/null +++ b/libraries/shared/src/RayIntersectionInfo.h @@ -0,0 +1,37 @@ +// +// RayIntersectionInfo.h +// interface/src/avatar +// +// Created by Andrew Meadows 2014.09.09 +// 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_RayIntersectionInfo_h +#define hifi_RayIntersectionInfo_h + +#include + +class Shape; + +class RayIntersectionInfo { +public: + RayIntersectionInfo() : _rayStart(0.0f), _rayDirection(1.0f, 0.0f, 0.0f), _rayLength(FLT_MAX), + _hitDistance(FLT_MAX), _hitNormal(1.0f, 0.0f, 0.0f), _hitShape(NULL) { } + + glm::vec3 getIntersectionPoint() const { return _rayStart + _hitDistance * _rayDirection; } + + // input + glm::vec3 _rayStart; + glm::vec3 _rayDirection; + float _rayLength; + + // output + float _hitDistance; + glm::vec3 _hitNormal; + Shape* _hitShape; +}; + +#endif // hifi_RayIntersectionInfo_h diff --git a/libraries/shared/src/Shape.h b/libraries/shared/src/Shape.h index 345d69d8e4..4b85234eb3 100644 --- a/libraries/shared/src/Shape.h +++ b/libraries/shared/src/Shape.h @@ -17,6 +17,8 @@ #include #include +#include "RayIntersectionInfo.h" + class PhysicsEntity; class VerletPoint; @@ -59,7 +61,7 @@ public: virtual void setMass(float mass) { _mass = mass; } virtual float getMass() const { return _mass; } - virtual bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const = 0; + virtual bool findRayIntersection(RayIntersectionInfo& intersection) const = 0; /// \param penetration of collision /// \param contactPoint of collision diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index 259b7c9118..be3b086776 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -1087,24 +1087,18 @@ bool capsuleVsAACubeLegacy(const CapsuleShape* capsuleA, const glm::vec3& cubeCe return sphereVsAACubeLegacy(nearestApproach, capsuleA->getRadius(), cubeCenter, cubeSide, collisions); } -bool findRayIntersectionWithShapes(const QVector shapes, const glm::vec3& rayStart, const glm::vec3& rayDirection, float& minDistance) { - float hitDistance = FLT_MAX; +bool findRayIntersection(const QVector& shapes, RayIntersectionInfo& intersection) { int numShapes = shapes.size(); + bool hit = false; for (int i = 0; i < numShapes; ++i) { Shape* shape = shapes.at(i); if (shape) { - float distance; - if (shape->findRayIntersection(rayStart, rayDirection, distance)) { - if (distance < hitDistance) { - hitDistance = distance; - } + if (shape->findRayIntersection(intersection)) { + hit = true; } } } - if (hitDistance < FLT_MAX) { - minDistance = hitDistance; - } - return false; + return hit; } } // namespace ShapeCollider diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h index 3cfec4c8a2..618a5ba115 100644 --- a/libraries/shared/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -15,6 +15,7 @@ #include #include "CollisionInfo.h" +#include "RayIntersectionInfo.h" #include "SharedUtil.h" class Shape; @@ -145,11 +146,9 @@ namespace ShapeCollider { bool capsuleVsAACubeLegacy(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); /// \param shapes list of pointers to shapes (shape pointers may be NULL) - /// \param startPoint beginning of ray - /// \param direction direction of ray - /// \param minDistance[out] shortest distance to intersection of ray with a shapes + /// \param intersection[out] struct with info about Ray and hit /// \return true if ray hits any shape in shapes - bool findRayIntersectionWithShapes(const QVector shapes, const glm::vec3& startPoint, const glm::vec3& direction, float& minDistance); + bool findRayIntersection(const QVector& shapes, RayIntersectionInfo& intersection); } // namespace ShapeCollider diff --git a/libraries/shared/src/SphereShape.cpp b/libraries/shared/src/SphereShape.cpp index c77b0c97fb..4c47ae91c0 100644 --- a/libraries/shared/src/SphereShape.cpp +++ b/libraries/shared/src/SphereShape.cpp @@ -13,18 +13,19 @@ #include "SphereShape.h" -bool SphereShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const { +bool SphereShape::findRayIntersection(RayIntersectionInfo& intersection) const { float r2 = _boundingRadius * _boundingRadius; // compute closest approach (CA) - float a = glm::dot(_translation - rayStart, rayDirection); // a = distance from ray-start to CA - float b2 = glm::distance2(_translation, rayStart + a * rayDirection); // b2 = squared distance from sphere-center to CA + float a = glm::dot(_translation - intersection._rayStart, intersection._rayDirection); // a = distance from ray-start to CA + float b2 = glm::distance2(_translation, intersection._rayStart + a * intersection._rayDirection); // b2 = squared distance from sphere-center to CA if (b2 > r2) { // ray does not hit sphere return false; } - float c = sqrtf(r2 - b2); // c = distance from CA to sphere surface along rayDirection - float d2 = glm::distance2(rayStart, _translation); // d2 = squared distance from sphere-center to ray-start + float c = sqrtf(r2 - b2); // c = distance from CA to sphere surface along intersection._rayDirection + float d2 = glm::distance2(intersection._rayStart, _translation); // d2 = squared distance from sphere-center to ray-start + float distance = FLT_MAX; if (a < 0.0f) { // ray points away from sphere-center if (d2 > r2) { @@ -40,5 +41,11 @@ bool SphereShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3 // ray starts inside sphere distance = a + c; } - return true; + if (distance > 0.0f && distance < intersection._rayLength && distance < intersection._hitDistance) { + intersection._hitDistance = distance; + intersection._hitNormal = glm::normalize(intersection._rayStart + distance * intersection._rayDirection - _translation); + intersection._hitShape = const_cast(this); + return true; + } + return false; } diff --git a/libraries/shared/src/SphereShape.h b/libraries/shared/src/SphereShape.h index 0626927453..b5f2c50d8f 100644 --- a/libraries/shared/src/SphereShape.h +++ b/libraries/shared/src/SphereShape.h @@ -34,7 +34,7 @@ public: void setRadius(float radius) { _boundingRadius = radius; } - bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const; + bool findRayIntersection(RayIntersectionInfo& intersection) const; float getVolume() const { return 1.3333333333f * PI * _boundingRadius * _boundingRadius * _boundingRadius; } }; diff --git a/tests/networking/src/SequenceNumberStatsTests.cpp b/tests/networking/src/SequenceNumberStatsTests.cpp index ded67b1ab6..204c77eeb3 100644 --- a/tests/networking/src/SequenceNumberStatsTests.cpp +++ b/tests/networking/src/SequenceNumberStatsTests.cpp @@ -254,6 +254,9 @@ void SequenceNumberStatsTests::pruneTest() { const QSet& missingSet = stats.getMissingSet(); assert(missingSet.size() <= 1000); + if (missingSet.size() > 1000) { + qDebug() << "FAIL: missingSet larger than 1000."; + } for (int i = 0; i < 10; i++) { assert(missingSet.contains(highestSkipped2)); diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp index 88cb9fa548..84335901bf 100644 --- a/tests/physics/src/ShapeColliderTests.cpp +++ b/tests/physics/src/ShapeColliderTests.cpp @@ -1803,40 +1803,44 @@ void ShapeColliderTests::capsuleTouchesAACube() { void ShapeColliderTests::rayHitsSphere() { float startDistance = 3.0f; - glm::vec3 rayStart(-startDistance, 0.0f, 0.0f); - glm::vec3 rayDirection(1.0f, 0.0f, 0.0f); float radius = 1.0f; glm::vec3 center(0.0f); - SphereShape sphere(radius, center); // very simple ray along xAxis { - float distance = FLT_MAX; - if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) { + RayIntersectionInfo intersection; + intersection._rayStart = -startDistance * xAxis; + intersection._rayDirection = xAxis; + + if (!sphere.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl; } float expectedDistance = startDistance - radius; - float relativeError = fabsf(distance - expectedDistance) / startDistance; + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance; if (relativeError > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " << relativeError << std::endl; } + if (intersection._hitShape != &sphere) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray intersection._hitShape should point at sphere" + << std::endl; + } } // ray along a diagonal axis { - rayStart = glm::vec3(startDistance, startDistance, 0.0f); - rayDirection = - glm::normalize(rayStart); + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(startDistance, startDistance, 0.0f); + intersection._rayDirection = - glm::normalize(intersection._rayStart); - float distance = FLT_MAX; - if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) { + if (!sphere.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl; } float expectedDistance = SQUARE_ROOT_OF_2 * startDistance - radius; - float relativeError = fabsf(distance - expectedDistance) / startDistance; + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance; if (relativeError > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " << relativeError << std::endl; } @@ -1851,22 +1855,22 @@ void ShapeColliderTests::rayHitsSphere() { glm::quat rotation = glm::angleAxis(0.987654321f, axis); glm::vec3 translation(35.7f, 2.46f, -1.97f); - glm::vec3 unrotatedRayDirection(-1.0f, 0.0f, 0.0f); - glm::vec3 untransformedRayStart(startDistance, 0.0f, 0.0f); + glm::vec3 unrotatedRayDirection = -xAxis; + glm::vec3 untransformedRayStart = startDistance * xAxis; - rayStart = rotation * (untransformedRayStart + translation); - rayDirection = rotation * unrotatedRayDirection; + RayIntersectionInfo intersection; + intersection._rayStart = rotation * (untransformedRayStart + translation); + intersection._rayDirection = rotation * unrotatedRayDirection; sphere.setRadius(radius); sphere.setTranslation(rotation * translation); - float distance = FLT_MAX; - if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) { + if (!sphere.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should intersect sphere" << std::endl; } float expectedDistance = startDistance - radius; - float relativeError = fabsf(distance - expectedDistance) / startDistance; + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance; if (relativeError > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " << relativeError << std::endl; @@ -1879,31 +1883,40 @@ void ShapeColliderTests::rayBarelyHitsSphere() { glm::vec3 center(0.0f); float delta = 2.0f * EPSILON; - float startDistance = 3.0f; - glm::vec3 rayStart(-startDistance, radius - delta, 0.0f); - glm::vec3 rayDirection(1.0f, 0.0f, 0.0f); - SphereShape sphere(radius, center); + float startDistance = 3.0f; - // very simple ray along xAxis - float distance = FLT_MAX; - if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely hit sphere" << std::endl; + { + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(-startDistance, radius - delta, 0.0f); + intersection._rayDirection = xAxis; + + // very simple ray along xAxis + if (!sphere.findRayIntersection(intersection)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely hit sphere" << std::endl; + } + if (intersection._hitShape != &sphere) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray intersection._hitShape should point at sphere" + << std::endl; + } } - // translate and rotate the whole system... - glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); - glm::quat rotation = glm::angleAxis(0.987654321f, axis); - glm::vec3 translation(35.7f, 2.46f, -1.97f); - - rayStart = rotation * (rayStart + translation); - rayDirection = rotation * rayDirection; - sphere.setTranslation(rotation * translation); - - // ...and test again - distance = FLT_MAX; - if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely hit sphere" << std::endl; + { + // translate and rotate the whole system... + glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); + glm::quat rotation = glm::angleAxis(0.987654321f, axis); + glm::vec3 translation(35.7f, 0.46f, -1.97f); + + RayIntersectionInfo intersection; + intersection._rayStart = rotation * (intersection._rayStart + translation); + intersection._rayDirection = rotation * intersection._rayDirection; + + sphere.setTranslation(rotation * translation); + + // ...and test again + if (!sphere.findRayIntersection(intersection)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely hit sphere" << std::endl; + } } } @@ -1914,39 +1927,47 @@ void ShapeColliderTests::rayBarelyMissesSphere() { glm::vec3 center(0.0f); float delta = 2.0f * EPSILON; - float startDistance = 3.0f; - glm::vec3 rayStart(-startDistance, radius + delta, 0.0f); - glm::vec3 rayDirection(1.0f, 0.0f, 0.0f); - SphereShape sphere(radius, center); + float startDistance = 3.0f; - // very simple ray along xAxis - float distance = FLT_MAX; - if (sphere.findRayIntersection(rayStart, rayDirection, distance)) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl; - } - if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" - << std::endl; + { + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(-startDistance, radius + delta, 0.0f); + intersection._rayDirection = xAxis; + + // very simple ray along xAxis + if (sphere.findRayIntersection(intersection)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl; + } + if (intersection._hitDistance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; + } } - // translate and rotate the whole system... - glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); - glm::quat rotation = glm::angleAxis(0.987654321f, axis); - glm::vec3 translation(35.7f, 2.46f, -1.97f); + { + // translate and rotate the whole system... + float angle = 0.987654321f; + glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); + glm::quat rotation = glm::angleAxis(angle, axis); + glm::vec3 translation(35.7f, 2.46f, -1.97f); - rayStart = rotation * (rayStart + translation); - rayDirection = rotation * rayDirection; - sphere.setTranslation(rotation * translation); - - // ...and test again - distance = FLT_MAX; - if (sphere.findRayIntersection(rayStart, rayDirection, distance)) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl; - } - if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" - << std::endl; + RayIntersectionInfo intersection; + intersection._rayStart = rotation * (glm::vec3(-startDistance, radius + delta, 0.0f) + translation); + intersection._rayDirection = rotation * xAxis; + sphere.setTranslation(rotation * translation); + + // ...and test again + if (sphere.findRayIntersection(intersection)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl; + } + if (intersection._hitDistance != FLT_MAX) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; + } + if (intersection._hitShape != NULL) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray intersection._hitShape should be NULL" << std::endl; + } } } @@ -1957,85 +1978,99 @@ void ShapeColliderTests::rayHitsCapsule() { glm::vec3 center(0.0f); CapsuleShape capsule(radius, halfHeight); - { // simple test along xAxis - // toward capsule center - glm::vec3 rayStart(startDistance, 0.0f, 0.0f); - glm::vec3 rayDirection(-1.0f, 0.0f, 0.0f); - float distance = FLT_MAX; - if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { + // simple tests along xAxis + { // toward capsule center + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(startDistance, 0.0f, 0.0f); + intersection._rayDirection = - xAxis; + if (!capsule.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } float expectedDistance = startDistance - radius; - float relativeError = fabsf(distance - expectedDistance) / startDistance; + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance; if (relativeError > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; } + if (intersection._hitShape != &capsule) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray intersection._hitShape should point at capsule" + << std::endl; + } + } - // toward top of cylindrical wall - rayStart.y = halfHeight; - distance = FLT_MAX; - if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { + { // toward top of cylindrical wall + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(startDistance, halfHeight, 0.0f); + intersection._rayDirection = - xAxis; + if (!capsule.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } - relativeError = fabsf(distance - expectedDistance) / startDistance; + float expectedDistance = startDistance - radius; + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance; if (relativeError > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; } + } - // toward top cap - float delta = 2.0f * EPSILON; - rayStart.y = halfHeight + delta; - distance = FLT_MAX; - if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { + float delta = 2.0f * EPSILON; + { // toward top cap + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(startDistance, halfHeight + delta, 0.0f); + intersection._rayDirection = - xAxis; + if (!capsule.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } - relativeError = fabsf(distance - expectedDistance) / startDistance; + float expectedDistance = startDistance - radius; + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance; if (relativeError > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; } + } - const float EDGE_CASE_SLOP_FACTOR = 20.0f; - - // toward tip of top cap - rayStart.y = halfHeight + radius - delta; - distance = FLT_MAX; - if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { + const float EDGE_CASE_SLOP_FACTOR = 20.0f; + { // toward tip of top cap + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(startDistance, halfHeight + radius - delta, 0.0f); + intersection._rayDirection = - xAxis; + if (!capsule.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } - expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine - relativeError = fabsf(distance - expectedDistance) / startDistance; + float expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance; // for edge cases we allow a LOT of error if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; } + } - // toward tip of bottom cap - rayStart.y = - halfHeight - radius + delta; - distance = FLT_MAX; - if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { + { // toward tip of bottom cap + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(startDistance, - halfHeight - radius + delta, 0.0f); + intersection._rayDirection = - xAxis; + if (!capsule.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } - expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine - relativeError = fabsf(distance - expectedDistance) / startDistance; + float expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance; // for edge cases we allow a LOT of error if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; } + } - // toward edge of capsule cylindrical face - rayStart.y = 0.0f; - rayStart.z = radius - delta; - distance = FLT_MAX; - if (!capsule.findRayIntersection(rayStart, rayDirection, distance)) { + { // toward edge of capsule cylindrical face + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(startDistance, 0.0f, radius - delta); + intersection._rayDirection = - xAxis; + if (!capsule.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit capsule" << std::endl; } - expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine - relativeError = fabsf(distance - expectedDistance) / startDistance; + float expectedDistance = startDistance - radius * sqrtf(2.0f * delta); // using small angle approximation of cosine + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / startDistance; // for edge cases we allow a LOT of error if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " @@ -2055,43 +2090,47 @@ void ShapeColliderTests::rayMissesCapsule() { { // simple test along xAxis // toward capsule center - glm::vec3 rayStart(startDistance, 0.0f, 0.0f); - glm::vec3 rayDirection(-1.0f, 0.0f, 0.0f); + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(startDistance, 0.0f, 0.0f); + intersection._rayDirection = -xAxis; float delta = 2.0f * EPSILON; // over top cap - rayStart.y = halfHeight + radius + delta; - float distance = FLT_MAX; - if (capsule.findRayIntersection(rayStart, rayDirection, distance)) { + intersection._rayStart.y = halfHeight + radius + delta; + intersection._hitDistance = FLT_MAX; + if (capsule.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; } - if (distance != FLT_MAX) { + if (intersection._hitDistance != FLT_MAX) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; } // below bottom cap - rayStart.y = - halfHeight - radius - delta; - distance = FLT_MAX; - if (capsule.findRayIntersection(rayStart, rayDirection, distance)) { + intersection._rayStart.y = - halfHeight - radius - delta; + intersection._hitDistance = FLT_MAX; + if (capsule.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; } - if (distance != FLT_MAX) { + if (intersection._hitDistance != FLT_MAX) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; } // past edge of capsule cylindrical face - rayStart.y = 0.0f; - rayStart.z = radius + delta; - distance = FLT_MAX; - if (capsule.findRayIntersection(rayStart, rayDirection, distance)) { + intersection._rayStart.y = 0.0f; + intersection._rayStart.z = radius + delta; + intersection._hitDistance = FLT_MAX; + if (capsule.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; } - if (distance != FLT_MAX) { + if (intersection._hitDistance != FLT_MAX) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; } + if (intersection._hitShape != NULL) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray intersection._hitShape should be NULL" << std::endl; + } } // TODO: test at steep angles near edge } @@ -2101,45 +2140,53 @@ void ShapeColliderTests::rayHitsPlane() { float planeDistanceFromOrigin = 3.579f; glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f); PlaneShape plane; - plane.setTranslation(planePosition); + plane.setPoint(planePosition); + plane.setNormal(yAxis); // make a simple ray float startDistance = 1.234f; - glm::vec3 rayStart(-startDistance, 0.0f, 0.0f); - glm::vec3 rayDirection = glm::normalize(glm::vec3(1.0f, 1.0f, 1.0f)); - - float distance = FLT_MAX; - if (!plane.findRayIntersection(rayStart, rayDirection, distance)) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit plane" << std::endl; + { + RayIntersectionInfo intersection; + intersection._rayStart = -startDistance * xAxis; + intersection._rayDirection = glm::normalize(glm::vec3(1.0f, 1.0f, 1.0f)); + + if (!plane.findRayIntersection(intersection)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit plane" << std::endl; + } + + float expectedDistance = SQUARE_ROOT_OF_3 * planeDistanceFromOrigin; + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / planeDistanceFromOrigin; + if (relativeError > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " + << relativeError << std::endl; + } + if (intersection._hitShape != &plane) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray intersection._hitShape should point at plane" + << std::endl; + } } - float expectedDistance = SQUARE_ROOT_OF_3 * planeDistanceFromOrigin; - float relativeError = fabsf(distance - expectedDistance) / planeDistanceFromOrigin; - if (relativeError > EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " - << relativeError << std::endl; - } - - // rotate the whole system and try again - float angle = 37.8f; - glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) ); - glm::quat rotation = glm::angleAxis(angle, axis); - - plane.setTranslation(rotation * planePosition); - plane.setRotation(rotation); - rayStart = rotation * rayStart; - rayDirection = rotation * rayDirection; - - distance = FLT_MAX; - if (!plane.findRayIntersection(rayStart, rayDirection, distance)) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit plane" << std::endl; - } - - expectedDistance = SQUARE_ROOT_OF_3 * planeDistanceFromOrigin; - relativeError = fabsf(distance - expectedDistance) / planeDistanceFromOrigin; - if (relativeError > EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " - << relativeError << std::endl; + { // rotate the whole system and try again + float angle = 37.8f; + glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) ); + glm::quat rotation = glm::angleAxis(angle, axis); + + plane.setNormal(rotation * yAxis); + plane.setPoint(rotation * planePosition); + RayIntersectionInfo intersection; + intersection._rayStart = rotation * (-startDistance * xAxis); + intersection._rayDirection = rotation * glm::normalize(glm::vec3(1.0f, 1.0f, 1.0f)); + + if (!plane.findRayIntersection(intersection)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit plane" << std::endl; + } + + float expectedDistance = SQUARE_ROOT_OF_3 * planeDistanceFromOrigin; + float relativeError = fabsf(intersection._hitDistance - expectedDistance) / planeDistanceFromOrigin; + if (relativeError > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " + << relativeError << std::endl; + } } } @@ -2152,14 +2199,14 @@ void ShapeColliderTests::rayMissesPlane() { { // parallel rays should miss float startDistance = 1.234f; - glm::vec3 rayStart(-startDistance, 0.0f, 0.0f); - glm::vec3 rayDirection = glm::normalize(glm::vec3(-1.0f, 0.0f, -1.0f)); + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(-startDistance, 0.0f, 0.0f); + intersection._rayDirection = glm::normalize(glm::vec3(-1.0f, 0.0f, -1.0f)); - float distance = FLT_MAX; - if (plane.findRayIntersection(rayStart, rayDirection, distance)) { + if (plane.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } - if (distance != FLT_MAX) { + if (intersection._hitDistance != FLT_MAX) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; } @@ -2171,29 +2218,35 @@ void ShapeColliderTests::rayMissesPlane() { plane.setTranslation(rotation * planePosition); plane.setRotation(rotation); - rayStart = rotation * rayStart; - rayDirection = rotation * rayDirection; - - distance = FLT_MAX; - if (plane.findRayIntersection(rayStart, rayDirection, distance)) { + + intersection._rayStart = rotation * intersection._rayStart; + intersection._rayDirection = rotation * intersection._rayDirection; + intersection._hitDistance = FLT_MAX; + + if (plane.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } - if (distance != FLT_MAX) { + if (intersection._hitDistance != FLT_MAX) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; } + if (intersection._hitShape != NULL) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray intersection._hitShape should be NULL" << std::endl; + } } { // make a simple ray that points away from plane float startDistance = 1.234f; - glm::vec3 rayStart(-startDistance, 0.0f, 0.0f); - glm::vec3 rayDirection = glm::normalize(glm::vec3(-1.0f, -1.0f, -1.0f)); + + RayIntersectionInfo intersection; + intersection._rayStart = glm::vec3(-startDistance, 0.0f, 0.0f); + intersection._rayDirection = glm::normalize(glm::vec3(-1.0f, -1.0f, -1.0f)); + intersection._hitDistance = FLT_MAX; - float distance = FLT_MAX; - if (plane.findRayIntersection(rayStart, rayDirection, distance)) { + if (plane.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } - if (distance != FLT_MAX) { + if (intersection._hitDistance != FLT_MAX) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; } @@ -2205,20 +2258,225 @@ void ShapeColliderTests::rayMissesPlane() { plane.setTranslation(rotation * planePosition); plane.setRotation(rotation); - rayStart = rotation * rayStart; - rayDirection = rotation * rayDirection; + + intersection._rayStart = rotation * intersection._rayStart; + intersection._rayDirection = rotation * intersection._rayDirection; + intersection._hitDistance = FLT_MAX; - distance = FLT_MAX; - if (plane.findRayIntersection(rayStart, rayDirection, distance)) { + if (plane.findRayIntersection(intersection)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } - if (distance != FLT_MAX) { + if (intersection._hitDistance != FLT_MAX) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; } } } +void ShapeColliderTests::rayHitsAACube() { + glm::vec3 cubeCenter(1.23f, 4.56f, 7.89f); + float cubeSide = 2.127f; + AACubeShape cube(cubeSide, cubeCenter); + + float rayOffset = 3.796f; + + glm::vec3 faceNormals[] = {xAxis, yAxis, zAxis}; + int numDirections = 3; + int numRayCasts = 5; + + for (int i = 0; i < numDirections; ++i) { + for (float sign = -1.0f; sign < 2.0f; sign += 2.0f) { + glm::vec3 faceNormal = sign * faceNormals[i]; + glm::vec3 secondNormal = faceNormals[(i + 1) % numDirections]; + glm::vec3 thirdNormal = faceNormals[(i + 2) % numDirections]; + + // pick a random point somewhere above the face + glm::vec3 rayStart = cubeCenter + + (cubeSide + rayOffset) * faceNormal + + (cubeSide * (randFloat() - 0.5f)) * secondNormal + + (cubeSide * (randFloat() - 0.5f)) * thirdNormal; + + // cast multiple rays toward the face + for (int j = 0; j < numRayCasts; ++j) { + // pick a random point on the face + glm::vec3 facePoint = cubeCenter + + 0.5f * cubeSide * faceNormal + + (cubeSide * (randFloat() - 0.5f)) * secondNormal + + (cubeSide * (randFloat() - 0.5f)) * thirdNormal; + + // construct a ray from first point through second point + RayIntersectionInfo intersection; + intersection._rayStart = rayStart; + intersection._rayDirection = glm::normalize(facePoint - rayStart); + intersection._rayLength = 1.0001f * glm::distance(rayStart, facePoint); + + // cast the ray + bool hit = cube.findRayIntersection(intersection); + + // validate + if (!hit) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should hit cube face" << std::endl; + break; + } + if (glm::abs(1.0f - glm::dot(faceNormal, intersection._hitNormal)) > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ray should hit cube face with normal " << faceNormal + << " but found different normal " << intersection._hitNormal << std::endl; + } + if (glm::distance(facePoint, intersection.getIntersectionPoint()) > EPSILON) { + std::cout << __FILE__ << ":" << __LINE__ + << " ERROR: ray should hit cube face at " << facePoint + << " but actually hit at " << intersection.getIntersectionPoint() + << std::endl; + } + if (intersection._hitShape != &cube) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray intersection._hitShape should point at cube" + << std::endl; + } + } + } + } +} + +void ShapeColliderTests::rayMissesAACube() { + //glm::vec3 cubeCenter(1.23f, 4.56f, 7.89f); + //float cubeSide = 2.127f; + glm::vec3 cubeCenter(0.0f); + float cubeSide = 2.f; + AACubeShape cube(cubeSide, cubeCenter); + + float rayOffset = 3.796f; + + glm::vec3 faceNormals[] = {xAxis, yAxis, zAxis}; + int numDirections = 3; + int numRayCasts = 5; + + const float SOME_SMALL_NUMBER = 0.0001f; + + { // ray misses cube for being too short + for (int i = 0; i < numDirections; ++i) { + for (float sign = -1.0f; sign < 2.0f; sign += 2.0f) { + glm::vec3 faceNormal = sign * faceNormals[i]; + glm::vec3 secondNormal = faceNormals[(i + 1) % numDirections]; + glm::vec3 thirdNormal = faceNormals[(i + 2) % numDirections]; + + // pick a random point somewhere above the face + glm::vec3 rayStart = cubeCenter + + (cubeSide + rayOffset) * faceNormal + + (cubeSide * (randFloat() - 0.5f)) * secondNormal + + (cubeSide * (randFloat() - 0.5f)) * thirdNormal; + + // cast multiple rays toward the face + for (int j = 0; j < numRayCasts; ++j) { + // pick a random point on the face + glm::vec3 facePoint = cubeCenter + + 0.5f * cubeSide * faceNormal + + (cubeSide * (randFloat() - 0.5f)) * secondNormal + + (cubeSide * (randFloat() - 0.5f)) * thirdNormal; + + // construct a ray from first point to almost second point + RayIntersectionInfo intersection; + intersection._rayStart = rayStart; + intersection._rayDirection = glm::normalize(facePoint - rayStart); + intersection._rayLength = (1.0f - SOME_SMALL_NUMBER) * glm::distance(rayStart, facePoint); + + // cast the ray + if (cube.findRayIntersection(intersection)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should NOT hit cube face " + << faceNormal << std::endl; + } + } + } + } + } + { // long ray misses cube + for (int i = 0; i < numDirections; ++i) { + for (float sign = -1.0f; sign < 2.0f; sign += 2.0f) { + glm::vec3 faceNormal = sign * faceNormals[i]; + glm::vec3 secondNormal = faceNormals[(i + 1) % numDirections]; + glm::vec3 thirdNormal = faceNormals[(i + 2) % numDirections]; + + // pick a random point somewhere above the face + glm::vec3 rayStart = cubeCenter + + (cubeSide + rayOffset) * faceNormal + + (cubeSide * (randFloat() - 0.5f)) * secondNormal + + (cubeSide * (randFloat() - 0.5f)) * thirdNormal; + + // cast multiple rays that miss the face + for (int j = 0; j < numRayCasts; ++j) { + // pick a random point just outside of face + float inside = (cubeSide * (randFloat() - 0.5f)); + float outside = 0.5f * cubeSide + SOME_SMALL_NUMBER * randFloat(); + if (randFloat() - 0.5f < 0.0f) { + outside *= -1.0f; + } + glm::vec3 sidePoint = cubeCenter + 0.5f * cubeSide * faceNormal; + if (randFloat() - 0.5f < 0.0f) { + sidePoint += outside * secondNormal + inside * thirdNormal; + } else { + sidePoint += inside * secondNormal + outside * thirdNormal; + } + + // construct a ray from first point through second point + RayIntersectionInfo intersection; + intersection._rayStart = rayStart; + intersection._rayDirection = glm::normalize(sidePoint - rayStart); + + // cast the ray + if (cube.findRayIntersection(intersection)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should NOT hit cube face " + << faceNormal << std::endl; + } + } + } + } + } + { // ray parallel to face barely misses cube + for (int i = 0; i < numDirections; ++i) { + for (float sign = -1.0f; sign < 2.0f; sign += 2.0f) { + glm::vec3 faceNormal = sign * faceNormals[i]; + glm::vec3 secondNormal = faceNormals[(i + 1) % numDirections]; + glm::vec3 thirdNormal = faceNormals[(i + 2) % numDirections]; + + // cast multiple rays that miss the face + for (int j = 0; j < numRayCasts; ++j) { + // rayStart is above the face + glm::vec3 rayStart = cubeCenter + (0.5f + SOME_SMALL_NUMBER) * cubeSide * faceNormal; + + // move rayStart to some random edge and choose the ray direction to point across the face + float inside = (cubeSide * (randFloat() - 0.5f)); + float outside = 0.5f * cubeSide + SOME_SMALL_NUMBER * randFloat(); + if (randFloat() - 0.5f < 0.0f) { + outside *= -1.0f; + } + glm::vec3 rayDirection = secondNormal; + if (randFloat() - 0.5f < 0.0f) { + rayStart += outside * secondNormal + inside * thirdNormal; + } else { + rayStart += inside * secondNormal + outside * thirdNormal; + rayDirection = thirdNormal; + } + if (outside > 0.0f) { + rayDirection *= -1.0f; + } + + // construct a ray from first point through second point + RayIntersectionInfo intersection; + intersection._rayStart = rayStart; + intersection._rayDirection = rayDirection; + + // cast the ray + if (cube.findRayIntersection(intersection)) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should NOT hit cube face " + << faceNormal << std::endl; + } + } + } + } + } + +} + void ShapeColliderTests::measureTimeOfCollisionDispatch() { /* KEEP for future manual testing // create two non-colliding spheres @@ -2278,4 +2536,7 @@ void ShapeColliderTests::runAllTests() { rayMissesCapsule(); rayHitsPlane(); rayMissesPlane(); + + rayHitsAACube(); + rayMissesAACube(); } diff --git a/tests/physics/src/ShapeColliderTests.h b/tests/physics/src/ShapeColliderTests.h index a7495d32bf..fa6887f685 100644 --- a/tests/physics/src/ShapeColliderTests.h +++ b/tests/physics/src/ShapeColliderTests.h @@ -38,6 +38,8 @@ namespace ShapeColliderTests { void rayMissesCapsule(); void rayHitsPlane(); void rayMissesPlane(); + void rayHitsAACube(); + void rayMissesAACube(); void measureTimeOfCollisionDispatch();