This commit is contained in:
Philip Rosedale 2014-09-15 16:32:59 -07:00
commit 4cfbc2b11b
53 changed files with 1271 additions and 598 deletions

View file

@ -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;

View file

@ -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();

View file

@ -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());

79
examples/radio.js Normal file
View file

@ -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);

View file

@ -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) {

View file

@ -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);

View file

@ -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();

View file

@ -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) {

View file

@ -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:

View file

@ -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);
}

View file

@ -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) {

View file

@ -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

View file

@ -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);

View file

@ -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<AvatarData> _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);

View file

@ -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;

View file

@ -28,7 +28,7 @@ public:
AACubeShape* _shape;
};
typedef QHash<quint64, VoxelInfo> VoxelPool;
typedef QHash<uint, VoxelInfo> 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;
};

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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() {

View file

@ -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());
}
}
#endif // HAVE_QXMPP

View file

@ -24,7 +24,7 @@
#include <QXmppClient.h>
#include <QXmppMessage.h>
#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();

View file

@ -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();

View file

@ -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);

View file

@ -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)));
}

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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<const unsigned char*>(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;

View file

@ -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

View file

@ -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;

View file

@ -48,7 +48,7 @@ public:
// Callback function, for recuseTreeWithOperation
typedef bool (*RecurseOctreeOperation)(OctreeElement* element, void* extraData);
typedef enum {GRADIENT, RANDOM, NATURAL} creationMode;
typedef QHash<quint64, AACube> CubeList;
typedef QHash<uint, AACube> CubeList;
const bool NO_EXISTS_BITS = false;
const bool WANT_EXISTS_BITS = true;

View file

@ -9,8 +9,68 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AACubeShape.h"
#include <glm/glm.hpp>
#include <glm/gtx/norm.hpp>
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<AACubeShape*>(this);
hit = true;
}
}
}
}
}
return hit;
}

View file

@ -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; }

View file

@ -59,23 +59,15 @@ template<typename T> inline QByteArray& operator>>(QByteArray& in, ByteCountCode
template<typename T> inline QByteArray ByteCountCoded<T>::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<typename T> inline QByteArray ByteCountCoded<T>::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

View file

@ -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<CapsuleShape*>(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<CapsuleShape*>(this);
return true;
}
// ray still might hit the caps
return findRayIntersectionWithCaps(centerV, intersection);
}
// static

View file

@ -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);

View file

@ -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<uint16_t>::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);

View file

@ -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<const Shape*> shapes, CollisionList& collisions) {

View file

@ -19,6 +19,7 @@
#include <glm/gtc/quaternion.hpp>
#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<const Shape*> shapes, CollisionList& collisions);
bool findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions);
bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);

View file

@ -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<Shape*> 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<Shape*> 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);

View file

@ -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 <QtGlobal>
#include <QMap>
@ -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<quint64, ContactPoint> _contacts;
};
#endif // hifi_PhysicsSimulation
#endif // hifi_PhysicsSimulation_h

View file

@ -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<PlaneShape*>(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<PlaneShape*>(this);
return true;
}
}

View file

@ -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

View file

@ -419,7 +419,7 @@ template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator
}
template<typename Enum> inline void PropertyFlags<Enum>::shinkIfNeeded() {
bool maxFlagWas = _maxFlag;
int maxFlagWas = _maxFlag;
while (_maxFlag >= 0) {
if (_flags.testBit(_maxFlag)) {
break;

View file

@ -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 <glm/glm.hpp>
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

View file

@ -17,6 +17,8 @@
#include <QtGlobal>
#include <QVector>
#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

View file

@ -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<Shape*> shapes, const glm::vec3& rayStart, const glm::vec3& rayDirection, float& minDistance) {
float hitDistance = FLT_MAX;
bool findRayIntersection(const QVector<Shape*>& 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

View file

@ -15,6 +15,7 @@
#include <QVector>
#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<Shape*> shapes, const glm::vec3& startPoint, const glm::vec3& direction, float& minDistance);
bool findRayIntersection(const QVector<Shape*>& shapes, RayIntersectionInfo& intersection);
} // namespace ShapeCollider

View file

@ -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<SphereShape*>(this);
return true;
}
return false;
}

View file

@ -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; }
};

View file

@ -254,6 +254,9 @@ void SequenceNumberStatsTests::pruneTest() {
const QSet<quint16>& 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));

View file

@ -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();
}

View file

@ -38,6 +38,8 @@ namespace ShapeColliderTests {
void rayMissesCapsule();
void rayHitsPlane();
void rayMissesPlane();
void rayHitsAACube();
void rayMissesAACube();
void measureTimeOfCollisionDispatch();