mirror of
https://github.com/lubosz/overte.git
synced 2025-04-16 06:16:18 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into experimentalStoreModel
Conflicts: tests/octree/CMakeLists.txt tests/octree/src/ModelTests.cpp tests/octree/src/main.cpp
This commit is contained in:
commit
a019b70e58
74 changed files with 3600 additions and 1268 deletions
|
@ -54,9 +54,6 @@
|
|||
|
||||
#include "AudioMixer.h"
|
||||
|
||||
const short JITTER_BUFFER_MSECS = 12;
|
||||
const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_MSECS * (SAMPLE_RATE / 1000.0);
|
||||
|
||||
const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f;
|
||||
|
||||
const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
|
||||
|
@ -67,6 +64,8 @@ void attachNewBufferToNode(Node *newNode) {
|
|||
}
|
||||
}
|
||||
|
||||
bool AudioMixer::_useDynamicJitterBuffers = false;
|
||||
|
||||
AudioMixer::AudioMixer(const QByteArray& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
_trailingSleepRatio(1.0f),
|
||||
|
@ -427,12 +426,46 @@ void AudioMixer::sendStatsPacket() {
|
|||
} else {
|
||||
statsObject["average_mixes_per_listener"] = 0.0;
|
||||
}
|
||||
|
||||
|
||||
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
||||
|
||||
_sumListeners = 0;
|
||||
_sumMixes = 0;
|
||||
_numStatFrames = 0;
|
||||
|
||||
|
||||
// NOTE: These stats can be too large to fit in an MTU, so we break it up into multiple packts...
|
||||
QJsonObject statsObject2;
|
||||
|
||||
// add stats for each listerner
|
||||
bool somethingToSend = false;
|
||||
int sizeOfStats = 0;
|
||||
int TOO_BIG_FOR_MTU = 1200; // some extra space for JSONification
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
int clientNumber = 0;
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
clientNumber++;
|
||||
AudioMixerClientData* clientData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
if (clientData) {
|
||||
QString property = "jitterStats." + node->getUUID().toString();
|
||||
QString value = clientData->getJitterBufferStats();
|
||||
statsObject2[qPrintable(property)] = value;
|
||||
somethingToSend = true;
|
||||
sizeOfStats += property.size() + value.size();
|
||||
}
|
||||
|
||||
// if we're too large, send the packet
|
||||
if (sizeOfStats > TOO_BIG_FOR_MTU) {
|
||||
nodeList->sendStatsToDomainServer(statsObject2);
|
||||
sizeOfStats = 0;
|
||||
statsObject2 = QJsonObject(); // clear it
|
||||
somethingToSend = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (somethingToSend) {
|
||||
nodeList->sendStatsToDomainServer(statsObject2);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixer::run() {
|
||||
|
@ -471,6 +504,16 @@ void AudioMixer::run() {
|
|||
<< QString("%1, %2, %3").arg(destinationCenter.x).arg(destinationCenter.y).arg(destinationCenter.z);
|
||||
}
|
||||
|
||||
// check the payload to see if we have asked for dynamicJitterBuffer support
|
||||
const QString DYNAMIC_JITTER_BUFFER_REGEX_STRING = "--dynamicJitterBuffer";
|
||||
QRegExp dynamicJitterBufferMatch(DYNAMIC_JITTER_BUFFER_REGEX_STRING);
|
||||
if (dynamicJitterBufferMatch.indexIn(_payload) != -1) {
|
||||
qDebug() << "Enable dynamic jitter buffers.";
|
||||
_useDynamicJitterBuffers = true;
|
||||
} else {
|
||||
qDebug() << "Dynamic jitter buffers disabled, using old behavior.";
|
||||
}
|
||||
|
||||
int nextFrame = 0;
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
@ -487,8 +530,7 @@ void AudioMixer::run() {
|
|||
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
if (node->getLinkedData()) {
|
||||
((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES,
|
||||
_sourceUnattenuatedZone,
|
||||
((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(_sourceUnattenuatedZone,
|
||||
_listenerUnattenuatedZone);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,9 @@ public slots:
|
|||
void readPendingDatagrams();
|
||||
|
||||
void sendStatsPacket();
|
||||
|
||||
static bool getUseDynamicJitterBuffers() { return _useDynamicJitterBuffers; }
|
||||
|
||||
private:
|
||||
/// adds one buffer to the mix for a listening node
|
||||
void addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuffer* bufferToAdd,
|
||||
|
@ -54,6 +57,7 @@ private:
|
|||
int _sumMixes;
|
||||
AABox* _sourceUnattenuatedZone;
|
||||
AABox* _listenerUnattenuatedZone;
|
||||
static bool _useDynamicJitterBuffers;
|
||||
};
|
||||
|
||||
#endif // hifi_AudioMixer_h
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "InjectedAudioRingBuffer.h"
|
||||
|
||||
#include "AudioMixer.h"
|
||||
#include "AudioMixerClientData.h"
|
||||
|
||||
AudioMixerClientData::AudioMixerClientData() :
|
||||
|
@ -65,7 +66,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
|
|||
|
||||
if (!avatarRingBuffer) {
|
||||
// we don't have an AvatarAudioRingBuffer yet, so add it
|
||||
avatarRingBuffer = new AvatarAudioRingBuffer(isStereo);
|
||||
avatarRingBuffer = new AvatarAudioRingBuffer(isStereo, AudioMixer::getUseDynamicJitterBuffers());
|
||||
_ringBuffers.push_back(avatarRingBuffer);
|
||||
}
|
||||
|
||||
|
@ -88,7 +89,8 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
|
|||
|
||||
if (!matchingInjectedRingBuffer) {
|
||||
// we don't have a matching injected audio ring buffer, so add it
|
||||
matchingInjectedRingBuffer = new InjectedAudioRingBuffer(streamIdentifier);
|
||||
matchingInjectedRingBuffer = new InjectedAudioRingBuffer(streamIdentifier,
|
||||
AudioMixer::getUseDynamicJitterBuffers());
|
||||
_ringBuffers.push_back(matchingInjectedRingBuffer);
|
||||
}
|
||||
|
||||
|
@ -98,10 +100,9 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples,
|
||||
AABox* checkSourceZone, AABox* listenerZone) {
|
||||
void AudioMixerClientData::checkBuffersBeforeFrameSend(AABox* checkSourceZone, AABox* listenerZone) {
|
||||
for (int i = 0; i < _ringBuffers.size(); i++) {
|
||||
if (_ringBuffers[i]->shouldBeAddedToMix(jitterBufferLengthSamples)) {
|
||||
if (_ringBuffers[i]->shouldBeAddedToMix()) {
|
||||
// this is a ring buffer that is ready to go
|
||||
// set its flag so we know to push its buffer when all is said and done
|
||||
_ringBuffers[i]->setWillBeAddedToMix(true);
|
||||
|
@ -120,20 +121,62 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSam
|
|||
}
|
||||
|
||||
void AudioMixerClientData::pushBuffersAfterFrameSend() {
|
||||
for (int i = 0; i < _ringBuffers.size(); i++) {
|
||||
|
||||
QList<PositionalAudioRingBuffer*>::iterator i = _ringBuffers.begin();
|
||||
while (i != _ringBuffers.end()) {
|
||||
// this was a used buffer, push the output pointer forwards
|
||||
PositionalAudioRingBuffer* audioBuffer = _ringBuffers[i];
|
||||
PositionalAudioRingBuffer* audioBuffer = *i;
|
||||
|
||||
if (audioBuffer->willBeAddedToMix()) {
|
||||
audioBuffer->shiftReadPosition(audioBuffer->isStereo()
|
||||
? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
|
||||
audioBuffer->shiftReadPosition(audioBuffer->getSamplesPerFrame());
|
||||
audioBuffer->setWillBeAddedToMix(false);
|
||||
} else if (audioBuffer->getType() == PositionalAudioRingBuffer::Injector
|
||||
&& audioBuffer->hasStarted() && audioBuffer->isStarved()) {
|
||||
// this is an empty audio buffer that has starved, safe to delete
|
||||
delete audioBuffer;
|
||||
_ringBuffers.erase(_ringBuffers.begin() + i);
|
||||
i = _ringBuffers.erase(i);
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
QString AudioMixerClientData::getJitterBufferStats() const {
|
||||
QString result;
|
||||
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
|
||||
if (avatarRingBuffer) {
|
||||
int desiredJitterBuffer = avatarRingBuffer->getDesiredJitterBufferFrames();
|
||||
int calculatedJitterBuffer = avatarRingBuffer->getCalculatedDesiredJitterBufferFrames();
|
||||
int currentJitterBuffer = avatarRingBuffer->getCurrentJitterBufferFrames();
|
||||
int resetCount = avatarRingBuffer->getResetCount();
|
||||
int samplesAvailable = avatarRingBuffer->samplesAvailable();
|
||||
int framesAvailable = (samplesAvailable / avatarRingBuffer->getSamplesPerFrame());
|
||||
result += "mic.desired:" + QString::number(desiredJitterBuffer)
|
||||
+ " calculated:" + QString::number(calculatedJitterBuffer)
|
||||
+ " current:" + QString::number(currentJitterBuffer)
|
||||
+ " available:" + QString::number(framesAvailable)
|
||||
+ " samples:" + QString::number(samplesAvailable)
|
||||
+ " resets:" + QString::number(resetCount);
|
||||
} else {
|
||||
result = "mic unknown";
|
||||
}
|
||||
|
||||
for (int i = 0; i < _ringBuffers.size(); i++) {
|
||||
if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) {
|
||||
int desiredJitterBuffer = _ringBuffers[i]->getDesiredJitterBufferFrames();
|
||||
int calculatedJitterBuffer = _ringBuffers[i]->getCalculatedDesiredJitterBufferFrames();
|
||||
int currentJitterBuffer = _ringBuffers[i]->getCurrentJitterBufferFrames();
|
||||
int resetCount = _ringBuffers[i]->getResetCount();
|
||||
int samplesAvailable = _ringBuffers[i]->samplesAvailable();
|
||||
int framesAvailable = (samplesAvailable / _ringBuffers[i]->getSamplesPerFrame());
|
||||
result += "| injected["+QString::number(i)+"].desired:" + QString::number(desiredJitterBuffer)
|
||||
+ " calculated:" + QString::number(calculatedJitterBuffer)
|
||||
+ " current:" + QString::number(currentJitterBuffer)
|
||||
+ " available:" + QString::number(framesAvailable)
|
||||
+ " samples:" + QString::number(samplesAvailable)
|
||||
+ " resets:" + QString::number(resetCount);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -27,9 +27,11 @@ public:
|
|||
AvatarAudioRingBuffer* getAvatarAudioRingBuffer() const;
|
||||
|
||||
int parseData(const QByteArray& packet);
|
||||
void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples,
|
||||
AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
|
||||
void checkBuffersBeforeFrameSend(AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
|
||||
void pushBuffersAfterFrameSend();
|
||||
|
||||
QString getJitterBufferStats() const;
|
||||
|
||||
private:
|
||||
QList<PositionalAudioRingBuffer*> _ringBuffers;
|
||||
};
|
||||
|
|
|
@ -13,12 +13,15 @@
|
|||
|
||||
#include "AvatarAudioRingBuffer.h"
|
||||
|
||||
AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo) :
|
||||
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Microphone, isStereo) {
|
||||
AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo, bool dynamicJitterBuffer) :
|
||||
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Microphone, isStereo, dynamicJitterBuffer) {
|
||||
|
||||
}
|
||||
|
||||
int AvatarAudioRingBuffer::parseData(const QByteArray& packet) {
|
||||
_interframeTimeGapStats.frameReceived();
|
||||
updateDesiredJitterBufferFrames();
|
||||
|
||||
_shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho);
|
||||
return PositionalAudioRingBuffer::parseData(packet);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
class AvatarAudioRingBuffer : public PositionalAudioRingBuffer {
|
||||
public:
|
||||
AvatarAudioRingBuffer(bool isStereo = false);
|
||||
AvatarAudioRingBuffer(bool isStereo = false, bool dynamicJitterBuffer = false);
|
||||
|
||||
int parseData(const QByteArray& packet);
|
||||
private:
|
||||
|
|
|
@ -34,6 +34,7 @@ var LASER_COLOR = { red: 255, green: 0, blue: 0 };
|
|||
var LASER_LENGTH_FACTOR = 500
|
||||
;
|
||||
|
||||
var MIN_ANGULAR_SIZE = 2;
|
||||
var MAX_ANGULAR_SIZE = 45;
|
||||
|
||||
var LEFT = 0;
|
||||
|
@ -277,8 +278,9 @@ function controller(wichSide) {
|
|||
var X = Vec3.sum(A, Vec3.multiply(B, x));
|
||||
var d = Vec3.length(Vec3.subtract(P, X));
|
||||
|
||||
if (0 < x && x < LASER_LENGTH_FACTOR) {
|
||||
if (2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14 > MAX_ANGULAR_SIZE) {
|
||||
var angularSize = 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
|
||||
if (0 < x && angularSize > MIN_ANGULAR_SIZE) {
|
||||
if (angularSize > MAX_ANGULAR_SIZE) {
|
||||
print("Angular size too big: " + 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14);
|
||||
return { valid: false };
|
||||
}
|
||||
|
@ -326,7 +328,8 @@ function controller(wichSide) {
|
|||
origin: this.palmPosition,
|
||||
direction: this.front
|
||||
});
|
||||
if (intersection.accurate && intersection.modelID.isKnownID) {
|
||||
var angularSize = 2 * Math.atan(intersection.modelProperties.radius / Vec3.distance(Camera.getPosition(), intersection.modelProperties.position)) * 180 / 3.14;
|
||||
if (intersection.accurate && intersection.modelID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
|
||||
this.glowedIntersectingModel = intersection.modelID;
|
||||
Models.editModel(this.glowedIntersectingModel, { glowLevel: 0.25 });
|
||||
}
|
||||
|
@ -828,8 +831,9 @@ function mousePressEvent(event) {
|
|||
var X = Vec3.sum(A, Vec3.multiply(B, x));
|
||||
var d = Vec3.length(Vec3.subtract(P, X));
|
||||
|
||||
if (0 < x && x < LASER_LENGTH_FACTOR) {
|
||||
if (2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14 < MAX_ANGULAR_SIZE) {
|
||||
var angularSize = 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
|
||||
if (0 < x && angularSize > MIN_ANGULAR_SIZE) {
|
||||
if (angularSize < MAX_ANGULAR_SIZE) {
|
||||
modelSelected = true;
|
||||
selectedModelID = foundModel;
|
||||
selectedModelProperties = properties;
|
||||
|
@ -884,7 +888,8 @@ function mouseMoveEvent(event) {
|
|||
glowedModelID.isKnownID = false;
|
||||
}
|
||||
|
||||
if (modelIntersection.modelID.isKnownID) {
|
||||
var angularSize = 2 * Math.atan(modelIntersection.modelProperties.radius / Vec3.distance(Camera.getPosition(), modelIntersection.modelProperties.position)) * 180 / 3.14;
|
||||
if (modelIntersection.modelID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
|
||||
Models.editModel(modelIntersection.modelID, { glowLevel: 0.25 });
|
||||
glowedModelID = modelIntersection.modelID;
|
||||
}
|
||||
|
@ -1125,8 +1130,8 @@ function handeMenuEvent(menuItem){
|
|||
Models.editModel(editModelID, properties);
|
||||
}
|
||||
}
|
||||
tooltip.show(false);
|
||||
}
|
||||
tooltip.show(false);
|
||||
}
|
||||
Menu.menuItemEvent.connect(handeMenuEvent);
|
||||
|
||||
|
|
|
@ -1196,7 +1196,7 @@ function menuItemEvent(menuItem) {
|
|||
print("deleting...");
|
||||
if (isImporting) {
|
||||
cancelImport();
|
||||
} else {
|
||||
} else if (voxelToolSelected) {
|
||||
Clipboard.deleteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3189,7 +3189,7 @@ void Application::updateWindowTitle(){
|
|||
float creditBalance = accountManager.getAccountInfo().getBalance() / SATOSHIS_PER_CREDIT;
|
||||
|
||||
QString creditBalanceString;
|
||||
creditBalanceString.sprintf("%.8f", creditBalance);
|
||||
creditBalanceString.sprintf("%.8f", floor(creditBalance + 0.5));
|
||||
|
||||
title += " - ₵" + creditBalanceString;
|
||||
}
|
||||
|
|
|
@ -218,9 +218,14 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
|
|||
hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
|
||||
pPropertyStore->Release();
|
||||
pPropertyStore = NULL;
|
||||
//QAudio devices seems to only take the 31 first characters of the Friendly Device Name.
|
||||
const DWORD QT_WIN_MAX_AUDIO_DEVICENAME_LEN = 31;
|
||||
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal).left(QT_WIN_MAX_AUDIO_DEVICENAME_LEN);
|
||||
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal);
|
||||
const DWORD WINDOWS7_MAJOR_VERSION = 6;
|
||||
const DWORD WINDOWS7_MINOR_VERSION = 1;
|
||||
if (osvi.dwMajorVersion <= WINDOWS7_MAJOR_VERSION && osvi.dwMinorVersion <= WINDOWS7_MINOR_VERSION) {
|
||||
// Windows 7 provides only the 31 first characters of the device name.
|
||||
const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31;
|
||||
deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN);
|
||||
}
|
||||
qDebug() << (mode == QAudio::AudioOutput ? "output" : "input") << " device:" << deviceName;
|
||||
PropVariantClear(&pv);
|
||||
}
|
||||
|
@ -461,8 +466,8 @@ void Audio::handleAudioInput() {
|
|||
int16_t* inputAudioSamples = new int16_t[inputSamplesRequired];
|
||||
_inputRingBuffer.readSamples(inputAudioSamples, inputSamplesRequired);
|
||||
|
||||
int numNetworkBytes = _isStereoInput ? NETWORK_BUFFER_LENGTH_BYTES_STEREO : NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
|
||||
int numNetworkSamples = _isStereoInput ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
|
||||
const int numNetworkBytes = _isStereoInput ? NETWORK_BUFFER_LENGTH_BYTES_STEREO : NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
|
||||
const int numNetworkSamples = _isStereoInput ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
|
||||
|
||||
// zero out the monoAudioSamples array and the locally injected audio
|
||||
memset(networkAudioSamples, 0, numNetworkBytes);
|
||||
|
@ -634,12 +639,10 @@ void Audio::handleAudioInput() {
|
|||
packetType = PacketTypeSilentAudioFrame;
|
||||
|
||||
// we need to indicate how many silent samples this is to the audio mixer
|
||||
audioDataPacket[0] = _isStereoInput
|
||||
? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO
|
||||
: NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
|
||||
networkAudioSamples[0] = numNetworkSamples;
|
||||
numAudioBytes = sizeof(int16_t);
|
||||
} else {
|
||||
numAudioBytes = _isStereoInput ? NETWORK_BUFFER_LENGTH_BYTES_STEREO : NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
|
||||
numAudioBytes = numNetworkBytes;
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio)) {
|
||||
packetType = PacketTypeMicrophoneAudioWithEcho;
|
||||
|
|
|
@ -249,12 +249,21 @@ Menu::Menu() :
|
|||
|
||||
QMenu* viewMenu = addMenu("View");
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu,
|
||||
MenuOption::Fullscreen,
|
||||
Qt::CTRL | Qt::META | Qt::Key_F,
|
||||
false,
|
||||
appInstance,
|
||||
SLOT(setFullscreen(bool)));
|
||||
#else
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu,
|
||||
MenuOption::Fullscreen,
|
||||
Qt::CTRL | Qt::Key_F,
|
||||
false,
|
||||
appInstance,
|
||||
SLOT(setFullscreen(bool)));
|
||||
#endif
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true,
|
||||
appInstance,SLOT(cameraMenuChanged()));
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true);
|
||||
|
@ -366,7 +375,7 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagDoll);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagdoll);
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
|
||||
|
@ -1293,6 +1302,7 @@ void Menu::showChat() {
|
|||
if (_chatWindow->isHidden()) {
|
||||
_chatWindow->show();
|
||||
}
|
||||
_chatWindow->activateWindow();
|
||||
} else {
|
||||
Application::getInstance()->getTrayIcon()->showMessage("Interface", "You need to login to be able to chat with others on this domain.");
|
||||
}
|
||||
|
|
|
@ -322,7 +322,7 @@ namespace MenuOption {
|
|||
const QString CascadedShadows = "Cascaded";
|
||||
const QString Chat = "Chat...";
|
||||
const QString ChatCircling = "Chat Circling";
|
||||
const QString CollideAsRagDoll = "Collide As RagDoll";
|
||||
const QString CollideAsRagdoll = "Collide As Ragdoll";
|
||||
const QString CollideWithAvatars = "Collide With Avatars";
|
||||
const QString CollideWithEnvironment = "Collide With World Boundaries";
|
||||
const QString CollideWithParticles = "Collide With Particles";
|
||||
|
|
|
@ -218,9 +218,6 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes);
|
||||
bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes);
|
||||
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
|
||||
if (renderSkeleton || renderHead || renderBounding) {
|
||||
updateShapePositions();
|
||||
}
|
||||
|
||||
if (renderSkeleton) {
|
||||
_skeletonModel.renderJointCollisionShapes(0.7f);
|
||||
|
@ -230,7 +227,6 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
|
||||
}
|
||||
if (renderBounding && shouldRenderHead(cameraPosition, renderMode)) {
|
||||
getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f);
|
||||
_skeletonModel.renderBoundingCollisionShapes(0.7f);
|
||||
}
|
||||
|
||||
|
@ -579,10 +575,9 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
CollisionList& collisions, int skeletonSkipIndex) {
|
||||
return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, skeletonSkipIndex);
|
||||
// Temporarily disabling collisions against the head because most of its collision proxies are bad.
|
||||
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions) {
|
||||
return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
|
||||
// TODO: Andrew to fix: Temporarily disabling collisions against the head
|
||||
//return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
|
||||
}
|
||||
|
||||
|
@ -591,18 +586,6 @@ bool Avatar::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisio
|
|||
getHead()->getFaceModel().findPlaneCollisions(plane, collisions);
|
||||
}
|
||||
|
||||
void Avatar::updateShapePositions() {
|
||||
_skeletonModel.updateShapePositions();
|
||||
Model& headModel = getHead()->getFaceModel();
|
||||
headModel.updateShapePositions();
|
||||
/* KEEP FOR DEBUG: use this in rather than code above to see shapes
|
||||
* in their default positions where the bounding shape is computed.
|
||||
_skeletonModel.resetShapePositions();
|
||||
Model& headModel = getHead()->getFaceModel();
|
||||
headModel.resetShapePositions();
|
||||
*/
|
||||
}
|
||||
|
||||
bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions) {
|
||||
// TODO: Andrew to fix: also collide against _skeleton
|
||||
//bool collided = _skeletonModel.findCollisions(shapes, collisions);
|
||||
|
@ -613,69 +596,6 @@ bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList&
|
|||
return collided;
|
||||
}
|
||||
|
||||
bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
|
||||
if (_collisionGroups & COLLISION_GROUP_PARTICLES) {
|
||||
return false;
|
||||
}
|
||||
bool collided = false;
|
||||
// first do the hand collisions
|
||||
const HandData* handData = getHandData();
|
||||
if (handData) {
|
||||
for (int i = 0; i < NUM_HANDS; i++) {
|
||||
const PalmData* palm = handData->getPalm(i);
|
||||
if (palm && palm->hasPaddle()) {
|
||||
// create a disk collision proxy where the hand is
|
||||
int jointIndex = -1;
|
||||
glm::vec3 handPosition;
|
||||
if (i == 0) {
|
||||
_skeletonModel.getLeftHandPosition(handPosition);
|
||||
jointIndex = _skeletonModel.getLeftHandJointIndex();
|
||||
}
|
||||
else {
|
||||
_skeletonModel.getRightHandPosition(handPosition);
|
||||
jointIndex = _skeletonModel.getRightHandJointIndex();
|
||||
}
|
||||
|
||||
glm::vec3 fingerAxis = palm->getFingerDirection();
|
||||
glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
|
||||
glm::vec3 diskNormal = palm->getNormal();
|
||||
const float DISK_THICKNESS = 0.08f;
|
||||
|
||||
// collide against the disk
|
||||
glm::vec3 penetration;
|
||||
if (findSphereDiskPenetration(particleCenter, particleRadius,
|
||||
diskCenter, HAND_PADDLE_RADIUS, DISK_THICKNESS, diskNormal,
|
||||
penetration)) {
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (collision) {
|
||||
collision->_type = COLLISION_TYPE_PADDLE_HAND;
|
||||
collision->_intData = jointIndex;
|
||||
collision->_penetration = penetration;
|
||||
collision->_addedVelocity = palm->getVelocity();
|
||||
collided = true;
|
||||
} else {
|
||||
// collisions are full, so we might as well bail now
|
||||
return collided;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// then collide against the models
|
||||
int preNumCollisions = collisions.size();
|
||||
if (_skeletonModel.findSphereCollisions(particleCenter, particleRadius, collisions)) {
|
||||
// the Model doesn't have velocity info, so we have to set it for each new collision
|
||||
int postNumCollisions = collisions.size();
|
||||
for (int i = preNumCollisions; i < postNumCollisions; ++i) {
|
||||
CollisionInfo* collision = collisions.getCollision(i);
|
||||
collision->_penetration /= (float)(TREE_SCALE);
|
||||
collision->_addedVelocity = getVelocity();
|
||||
}
|
||||
collided = true;
|
||||
}
|
||||
return collided;
|
||||
}
|
||||
|
||||
glm::quat Avatar::getJointRotation(int index) const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
return AvatarData::getJointRotation(index);
|
||||
|
@ -909,25 +829,6 @@ float Avatar::getHeadHeight() const {
|
|||
return DEFAULT_HEAD_HEIGHT;
|
||||
}
|
||||
|
||||
bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
|
||||
if (!collision._data || collision._type != COLLISION_TYPE_MODEL) {
|
||||
return false;
|
||||
}
|
||||
Model* model = static_cast<Model*>(collision._data);
|
||||
int jointIndex = collision._intData;
|
||||
|
||||
if (model == &(_skeletonModel) && jointIndex != -1) {
|
||||
// collision response of skeleton is temporarily disabled
|
||||
return false;
|
||||
//return _skeletonModel.collisionHitsMoveableJoint(collision);
|
||||
}
|
||||
if (model == &(getHead()->getFaceModel())) {
|
||||
// ATM we always handle COLLISION_TYPE_MODEL against the face.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float Avatar::getBoundingRadius() const {
|
||||
// TODO: also use head model when computing the avatar's bounding radius
|
||||
return _skeletonModel.getBoundingRadius();
|
||||
|
|
|
@ -101,14 +101,12 @@ public:
|
|||
/// \return true if at least one shape collided with avatar
|
||||
bool findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions);
|
||||
|
||||
/// Checks for penetration between the described sphere and the avatar.
|
||||
/// Checks for penetration between the a sphere and the avatar's models.
|
||||
/// \param penetratorCenter the center of the penetration test sphere
|
||||
/// \param penetratorRadius the radius of the penetration test sphere
|
||||
/// \param collisions[out] a list to which collisions get appended
|
||||
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
|
||||
/// \return whether or not the sphere penetrated
|
||||
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
CollisionList& collisions, int skeletonSkipIndex = -1);
|
||||
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions);
|
||||
|
||||
/// Checks for penetration between the described plane and the avatar.
|
||||
/// \param plane the penetration plane
|
||||
|
@ -116,13 +114,6 @@ public:
|
|||
/// \return whether or not the plane penetrated
|
||||
bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
|
||||
|
||||
/// Checks for collision between the a spherical particle and the avatar (including paddle hands)
|
||||
/// \param collisionCenter the center of particle's bounding sphere
|
||||
/// \param collisionRadius the radius of particle's bounding sphere
|
||||
/// \param collisions[out] a list to which collisions get appended
|
||||
/// \return whether or not the particle collided
|
||||
bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions);
|
||||
|
||||
virtual bool isMyAvatar() { return false; }
|
||||
|
||||
virtual glm::quat getJointRotation(int index) const;
|
||||
|
@ -141,14 +132,10 @@ public:
|
|||
|
||||
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
|
||||
|
||||
/// \return true if we expect the avatar would move as a result of the collision
|
||||
bool collisionWouldMoveAvatar(CollisionInfo& collision) const;
|
||||
|
||||
virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { }
|
||||
|
||||
/// \return bounding radius of avatar
|
||||
virtual float getBoundingRadius() const;
|
||||
void updateShapePositions();
|
||||
|
||||
quint32 getCollisionGroups() const { return _collisionGroups; }
|
||||
virtual void setCollisionGroups(quint32 collisionGroups) { _collisionGroups = (collisionGroups & VALID_COLLISION_GROUPS); }
|
||||
|
|
|
@ -94,39 +94,6 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
|||
}
|
||||
}
|
||||
|
||||
void Hand::collideAgainstOurself() {
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::HandsCollideWithSelf)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int leftPalmIndex, rightPalmIndex;
|
||||
getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
|
||||
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
|
||||
|
||||
const SkeletonModel& skeletonModel = _owningAvatar->getSkeletonModel();
|
||||
for (int i = 0; i < int(getNumPalms()); i++) {
|
||||
PalmData& palm = getPalms()[i];
|
||||
if (!palm.isActive()) {
|
||||
continue;
|
||||
}
|
||||
// ignoring everything below the parent of the parent of the last free joint
|
||||
int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
|
||||
skeletonModel.getLastFreeJointIndex((int(i) == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
|
||||
(int(i) == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
|
||||
|
||||
handCollisions.clear();
|
||||
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions, skipIndex)) {
|
||||
glm::vec3 totalPenetration;
|
||||
for (int j = 0; j < handCollisions.size(); ++j) {
|
||||
CollisionInfo* collision = handCollisions.getCollision(j);
|
||||
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
|
||||
}
|
||||
// resolve penetration
|
||||
palm.addToPenetration(totalPenetration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hand::resolvePenetrations() {
|
||||
for (size_t i = 0; i < getNumPalms(); ++i) {
|
||||
PalmData& palm = getPalms()[i];
|
||||
|
|
|
@ -54,7 +54,6 @@ public:
|
|||
void render(bool isMine, Model::RenderMode renderMode = Model::DEFAULT_RENDER_MODE);
|
||||
|
||||
void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
|
||||
void collideAgainstOurself();
|
||||
|
||||
void resolvePenetrations();
|
||||
|
||||
|
|
|
@ -76,14 +76,21 @@ MyAvatar::MyAvatar() :
|
|||
_lastFloorContactPoint(0.0f),
|
||||
_lookAtTargetAvatar(),
|
||||
_shouldRender(true),
|
||||
_billboardValid(false)
|
||||
_billboardValid(false),
|
||||
_physicsSimulation()
|
||||
{
|
||||
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
|
||||
_driveKeys[i] = 0.0f;
|
||||
}
|
||||
_skeletonModel.setEnableShapes(true);
|
||||
// The skeleton is both a PhysicsEntity and Ragdoll, so we add it to the simulation once for each type.
|
||||
_physicsSimulation.addEntity(&_skeletonModel);
|
||||
_physicsSimulation.addRagdoll(&_skeletonModel);
|
||||
}
|
||||
|
||||
MyAvatar::~MyAvatar() {
|
||||
_physicsSimulation.removeEntity(&_skeletonModel);
|
||||
_physicsSimulation.removeRagdoll(&_skeletonModel);
|
||||
_lookAtTargetAvatar.clear();
|
||||
}
|
||||
|
||||
|
@ -154,7 +161,6 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/hand Collision,simulate");
|
||||
// update avatar skeleton and simulate hand and head
|
||||
getHand()->collideAgainstOurself();
|
||||
getHand()->simulate(deltaTime, true);
|
||||
}
|
||||
|
||||
|
@ -189,6 +195,18 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
head->simulate(deltaTime, true);
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/ragdoll");
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
|
||||
const int minError = 0.01f;
|
||||
const float maxIterations = 10;
|
||||
const quint64 maxUsec = 2000;
|
||||
_physicsSimulation.stepForward(deltaTime, minError, maxIterations, maxUsec);
|
||||
} else {
|
||||
_skeletonModel.moveShapesTowardJoints(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// now that we're done stepping the avatar forward in time, compute new collisions
|
||||
if (_collisionGroups != 0) {
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/_collisionGroups");
|
||||
|
@ -199,7 +217,6 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f));
|
||||
radius *= COLLISION_RADIUS_SCALAR;
|
||||
}
|
||||
updateShapePositions();
|
||||
if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) {
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithEnvironment");
|
||||
updateCollisionWithEnvironment(deltaTime, radius);
|
||||
|
@ -210,10 +227,12 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
} else {
|
||||
_trapDuration = 0.0f;
|
||||
}
|
||||
/* TODO: Andrew to make this work
|
||||
if (_collisionGroups & COLLISION_GROUP_AVATARS) {
|
||||
PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithAvatars");
|
||||
updateCollisionWithAvatars(deltaTime);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// consider updating our billboard
|
||||
|
@ -1233,7 +1252,7 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
|
|||
float capsuleHalfHeight = boundingShape.getHalfHeight();
|
||||
const float MAX_STEP_HEIGHT = capsuleRadius + capsuleHalfHeight;
|
||||
const float MIN_STEP_HEIGHT = 0.0f;
|
||||
glm::vec3 footBase = boundingShape.getPosition() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection;
|
||||
glm::vec3 footBase = boundingShape.getTranslation() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection;
|
||||
float highestStep = 0.0f;
|
||||
float lowestStep = MAX_STEP_HEIGHT;
|
||||
glm::vec3 floorPoint;
|
||||
|
@ -1250,7 +1269,7 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
|
|||
if (horizontalDepth > capsuleRadius || fabsf(verticalDepth) > MAX_STEP_HEIGHT) {
|
||||
isTrapped = true;
|
||||
if (_trapDuration > MAX_TRAP_PERIOD) {
|
||||
float distance = glm::dot(boundingShape.getPosition() - cubeCenter, _worldUpDirection);
|
||||
float distance = glm::dot(boundingShape.getTranslation() - cubeCenter, _worldUpDirection);
|
||||
if (distance < 0.0f) {
|
||||
distance = fabsf(distance) + 0.5f * cubeSide;
|
||||
}
|
||||
|
@ -1435,7 +1454,6 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
|||
// don't collide with ourselves
|
||||
continue;
|
||||
}
|
||||
avatar->updateShapePositions();
|
||||
float distance = glm::length(_position - avatar->getPosition());
|
||||
if (_distanceToNearestAvatar > distance) {
|
||||
_distanceToNearestAvatar = distance;
|
||||
|
@ -1461,17 +1479,10 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
// collide our hands against them
|
||||
// TODO: make this work when we can figure out when the other avatar won't yeild
|
||||
// (for example, we're colliding against their chest or leg)
|
||||
//getHand()->collideAgainstAvatar(avatar, true);
|
||||
|
||||
// collide their hands against us
|
||||
avatar->getHand()->collideAgainstAvatar(this, false);
|
||||
}
|
||||
}
|
||||
// TODO: uncomment this when we handle collisions that won't affect other avatar
|
||||
//getHand()->resolvePenetrations();
|
||||
}
|
||||
|
||||
class SortedAvatar {
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include <QSettings>
|
||||
|
||||
#include <PhysicsSimulation.h>
|
||||
|
||||
#include "Avatar.h"
|
||||
|
||||
enum AvatarHandState
|
||||
|
@ -173,6 +175,7 @@ private:
|
|||
float _oculusYawOffset;
|
||||
|
||||
QList<AnimationHandlePointer> _animationHandles;
|
||||
PhysicsSimulation _physicsSimulation;
|
||||
|
||||
// private methods
|
||||
float computeDistanceToFloor(const glm::vec3& startPoint);
|
||||
|
|
|
@ -11,21 +11,37 @@
|
|||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
#include <VerletCapsuleShape.h>
|
||||
#include <VerletSphereShape.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Avatar.h"
|
||||
#include "Hand.h"
|
||||
#include "Menu.h"
|
||||
#include "SkeletonModel.h"
|
||||
|
||||
SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
|
||||
_owningAvatar(owningAvatar) {
|
||||
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
|
||||
Model(parent),
|
||||
Ragdoll(),
|
||||
_owningAvatar(owningAvatar),
|
||||
_boundingShape(),
|
||||
_boundingShapeLocalOffset(0.0f) {
|
||||
}
|
||||
|
||||
void SkeletonModel::setJointStates(QVector<JointState> states) {
|
||||
Model::setJointStates(states);
|
||||
|
||||
if (isActive() && _owningAvatar->isMyAvatar()) {
|
||||
_ragDoll.init(_jointStates);
|
||||
// the SkeletonModel override of updateJointState() will clear the translation part
|
||||
// of its root joint and we need that done before we try to build shapes hence we
|
||||
// recompute all joint transforms at this time.
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
updateJointState(i);
|
||||
}
|
||||
|
||||
clearShapes();
|
||||
clearRagdollConstraintsAndPoints();
|
||||
if (_enableShapes) {
|
||||
buildShapes();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,25 +102,13 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
applyPalmData(geometry.leftHandJointIndex, hand->getPalms()[leftPalmIndex]);
|
||||
applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[rightPalmIndex]);
|
||||
}
|
||||
|
||||
simulateRagDoll(deltaTime);
|
||||
}
|
||||
|
||||
void SkeletonModel::simulateRagDoll(float deltaTime) {
|
||||
_ragDoll.slaveToSkeleton(_jointStates, 0.1f); // fraction = 0.1f left intentionally low for demo purposes
|
||||
|
||||
float MIN_CONSTRAINT_ERROR = 0.005f; // 5mm
|
||||
int MAX_ITERATIONS = 4;
|
||||
int iterations = 0;
|
||||
float delta = 0.0f;
|
||||
do {
|
||||
delta = _ragDoll.enforceConstraints();
|
||||
++iterations;
|
||||
} while (delta > MIN_CONSTRAINT_ERROR && iterations < MAX_ITERATIONS);
|
||||
_boundingShape.setTranslation(_translation + _rotation * _boundingShapeLocalOffset);
|
||||
_boundingShape.setRotation(_rotation);
|
||||
}
|
||||
|
||||
void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const {
|
||||
if (jointIndex < 0 || jointIndex >= int(_jointShapes.size())) {
|
||||
if (jointIndex < 0 || jointIndex >= int(_shapes.size())) {
|
||||
return;
|
||||
}
|
||||
if (jointIndex == getLeftHandJointIndex()
|
||||
|
@ -114,18 +118,21 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes)
|
|||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
int parentIndex = joint.parentIndex;
|
||||
Shape* shape = _shapes[i];
|
||||
if (i == jointIndex) {
|
||||
// this shape is the hand
|
||||
shapes.push_back(_jointShapes[i]);
|
||||
if (parentIndex != -1) {
|
||||
// also add the forearm
|
||||
shapes.push_back(_jointShapes[parentIndex]);
|
||||
if (shape) {
|
||||
shapes.push_back(shape);
|
||||
}
|
||||
} else {
|
||||
if (parentIndex != -1 && _shapes[parentIndex]) {
|
||||
// also add the forearm
|
||||
shapes.push_back(_shapes[parentIndex]);
|
||||
}
|
||||
} else if (shape) {
|
||||
while (parentIndex != -1) {
|
||||
if (parentIndex == jointIndex) {
|
||||
// this shape is a child of the hand
|
||||
shapes.push_back(_jointShapes[i]);
|
||||
shapes.push_back(shape);
|
||||
break;
|
||||
}
|
||||
parentIndex = geometry.joints[parentIndex].parentIndex;
|
||||
|
@ -145,7 +152,7 @@ void SkeletonModel::renderIKConstraints() {
|
|||
renderJointConstraints(getRightHandJointIndex());
|
||||
renderJointConstraints(getLeftHandJointIndex());
|
||||
//if (isActive() && _owningAvatar->isMyAvatar()) {
|
||||
// renderRagDoll();
|
||||
// renderRagdoll();
|
||||
//}
|
||||
}
|
||||
|
||||
|
@ -244,15 +251,6 @@ void SkeletonModel::updateJointState(int index) {
|
|||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::updateShapePositions() {
|
||||
if (isActive() && _owningAvatar->isMyAvatar() &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagDoll)) {
|
||||
_ragDoll.updateShapes(_jointShapes, _rotation, _translation);
|
||||
} else {
|
||||
Model::updateShapePositions();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
||||
if (!_owningAvatar->isMyAvatar() || Application::getInstance()->getPrioVR()->isActive()) {
|
||||
return;
|
||||
|
@ -478,22 +476,21 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco
|
|||
return false;
|
||||
}
|
||||
|
||||
void SkeletonModel::renderRagDoll() {
|
||||
void SkeletonModel::renderRagdoll() {
|
||||
const int BALL_SUBDIVISIONS = 6;
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_LIGHTING);
|
||||
glPushMatrix();
|
||||
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
QVector<glm::vec3> points = _ragDoll.getPoints();
|
||||
int numPoints = points.size();
|
||||
int numPoints = _ragdollPoints.size();
|
||||
float alpha = 0.3f;
|
||||
float radius1 = 0.008f;
|
||||
float radius2 = 0.01f;
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
glPushMatrix();
|
||||
// draw each point as a yellow hexagon with black border
|
||||
glm::vec3 position = _rotation * points[i];
|
||||
glm::vec3 position = _rotation * _ragdollPoints[i]._position;
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
glColor4f(0.0f, 0.0f, 0.0f, alpha);
|
||||
glutSolidSphere(radius2, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
@ -505,3 +502,266 @@ void SkeletonModel::renderRagDoll() {
|
|||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_LIGHTING);
|
||||
}
|
||||
|
||||
// virtual
|
||||
void SkeletonModel::initRagdollPoints() {
|
||||
assert(_ragdollPoints.size() == 0);
|
||||
assert(_ragdollConstraints.size() == 0);
|
||||
|
||||
// one point for each joint
|
||||
int numJoints = _jointStates.size();
|
||||
_ragdollPoints.fill(VerletPoint(), numJoints);
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
const JointState& state = _jointStates.at(i);
|
||||
glm::vec3 position = state.getPosition();
|
||||
_ragdollPoints[i]._position = position;
|
||||
_ragdollPoints[i]._lastPosition = position;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::buildRagdollConstraints() {
|
||||
// NOTE: the length of DistanceConstraints is computed and locked in at this time
|
||||
// so make sure the ragdoll positions are in a normal configuration before here.
|
||||
const int numPoints = _ragdollPoints.size();
|
||||
assert(numPoints == _jointStates.size());
|
||||
|
||||
for (int i = 0; i < numPoints; ++i) {
|
||||
const JointState& state = _jointStates.at(i);
|
||||
const FBXJoint& joint = state.getFBXJoint();
|
||||
int parentIndex = joint.parentIndex;
|
||||
if (parentIndex == -1) {
|
||||
FixedConstraint* anchor = new FixedConstraint(&(_ragdollPoints[i]), glm::vec3(0.0f));
|
||||
_ragdollConstraints.push_back(anchor);
|
||||
} else {
|
||||
DistanceConstraint* bone = new DistanceConstraint(&(_ragdollPoints[i]), &(_ragdollPoints[parentIndex]));
|
||||
_ragdollConstraints.push_back(bone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void SkeletonModel::stepRagdollForward(float deltaTime) {
|
||||
const float RAGDOLL_FOLLOWS_JOINTS_TIMESCALE = 0.03f;
|
||||
float fraction = glm::clamp(deltaTime / RAGDOLL_FOLLOWS_JOINTS_TIMESCALE, 0.0f, 1.0f);
|
||||
moveShapesTowardJoints(fraction);
|
||||
}
|
||||
|
||||
float DENSITY_OF_WATER = 1000.0f; // kg/m^3
|
||||
float MIN_JOINT_MASS = 1.0f;
|
||||
float VERY_BIG_MASS = 1.0e6f;
|
||||
|
||||
// virtual
|
||||
void SkeletonModel::buildShapes() {
|
||||
if (!_geometry || _rootIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
if (geometry.joints.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
initRagdollPoints();
|
||||
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
const int numStates = _jointStates.size();
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
JointState& state = _jointStates[i];
|
||||
const FBXJoint& joint = state.getFBXJoint();
|
||||
float radius = uniformScale * joint.boneRadius;
|
||||
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
|
||||
Shape::Type type = joint.shapeType;
|
||||
if (i == 0 || (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON)) {
|
||||
// this shape is forced to be a sphere
|
||||
type = Shape::SPHERE_SHAPE;
|
||||
}
|
||||
Shape* shape = NULL;
|
||||
int parentIndex = joint.parentIndex;
|
||||
if (type == Shape::SPHERE_SHAPE) {
|
||||
shape = new VerletSphereShape(radius, &(_ragdollPoints[i]));
|
||||
shape->setEntity(this);
|
||||
_ragdollPoints[i]._mass = glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume());
|
||||
} else if (type == Shape::CAPSULE_SHAPE) {
|
||||
assert(parentIndex != -1);
|
||||
shape = new VerletCapsuleShape(radius, &(_ragdollPoints[parentIndex]), &(_ragdollPoints[i]));
|
||||
shape->setEntity(this);
|
||||
_ragdollPoints[i]._mass = glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume());
|
||||
}
|
||||
if (parentIndex != -1) {
|
||||
// always disable collisions between joint and its parent
|
||||
disableCollisions(i, parentIndex);
|
||||
} else {
|
||||
// give the base joint a very large mass since it doesn't actually move
|
||||
// in the local-frame simulation (it defines the origin)
|
||||
_ragdollPoints[i]._mass = VERY_BIG_MASS;
|
||||
}
|
||||
_shapes.push_back(shape);
|
||||
}
|
||||
|
||||
// This method moves the shapes to their default positions in Model frame.
|
||||
computeBoundingShape(geometry);
|
||||
|
||||
// While the shapes are in their default position we disable collisions between
|
||||
// joints that are currently colliding.
|
||||
disableCurrentSelfCollisions();
|
||||
|
||||
buildRagdollConstraints();
|
||||
|
||||
// ... then move shapes back to current joint positions
|
||||
moveShapesTowardJoints(1.0f);
|
||||
enforceRagdollConstraints();
|
||||
}
|
||||
|
||||
void SkeletonModel::moveShapesTowardJoints(float fraction) {
|
||||
const int numStates = _jointStates.size();
|
||||
assert(_jointStates.size() == _ragdollPoints.size());
|
||||
assert(fraction >= 0.0f && fraction <= 1.0f);
|
||||
if (_ragdollPoints.size() == numStates) {
|
||||
float oneMinusFraction = 1.0f - fraction;
|
||||
int numJoints = _jointStates.size();
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
_ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position;
|
||||
_ragdollPoints[i]._position = oneMinusFraction * _ragdollPoints[i]._position + fraction * _jointStates.at(i).getPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
|
||||
// compute default joint transforms
|
||||
int numJoints = geometry.joints.size();
|
||||
if (numJoints != _ragdollPoints.size()) {
|
||||
return;
|
||||
}
|
||||
QVector<glm::mat4> transforms;
|
||||
transforms.fill(glm::mat4(), numJoints);
|
||||
|
||||
// compute the default transforms and slam the ragdoll positions accordingly
|
||||
// (which puts the shapes where we want them)
|
||||
transforms[0] = _jointStates[0].getTransform();
|
||||
_ragdollPoints[0]._position = extractTranslation(transforms[0]);
|
||||
_ragdollPoints[0]._lastPosition = _ragdollPoints[0]._position;
|
||||
for (int i = 1; i < numJoints; i++) {
|
||||
const FBXJoint& joint = geometry.joints.at(i);
|
||||
int parentIndex = joint.parentIndex;
|
||||
assert(parentIndex != -1);
|
||||
|
||||
glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation;
|
||||
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
|
||||
* joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform;
|
||||
// setting the ragdollPoints here slams the VerletShapes into their default positions
|
||||
_ragdollPoints[i]._position = extractTranslation(transforms[i]);
|
||||
_ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position;
|
||||
}
|
||||
|
||||
// compute bounding box that encloses all shapes
|
||||
Extents totalExtents;
|
||||
totalExtents.reset();
|
||||
totalExtents.addPoint(glm::vec3(0.0f));
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
Shape* shape = _shapes[i];
|
||||
if (!shape) {
|
||||
continue;
|
||||
}
|
||||
// TODO: skip hand and arm shapes for bounding box calculation
|
||||
Extents shapeExtents;
|
||||
shapeExtents.reset();
|
||||
glm::vec3 localPosition = shape->getTranslation();
|
||||
int type = shape->getType();
|
||||
if (type == Shape::CAPSULE_SHAPE) {
|
||||
// add the two furthest surface points of the capsule
|
||||
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
|
||||
glm::vec3 axis;
|
||||
capsule->computeNormalizedAxis(axis);
|
||||
float radius = capsule->getRadius();
|
||||
float halfHeight = capsule->getHalfHeight();
|
||||
axis = halfHeight * axis + glm::vec3(radius);
|
||||
|
||||
shapeExtents.addPoint(localPosition + axis);
|
||||
shapeExtents.addPoint(localPosition - axis);
|
||||
totalExtents.addExtents(shapeExtents);
|
||||
} else if (type == Shape::SPHERE_SHAPE) {
|
||||
float radius = shape->getBoundingRadius();
|
||||
glm::vec3 axis = glm::vec3(radius);
|
||||
shapeExtents.addPoint(localPosition + axis);
|
||||
shapeExtents.addPoint(localPosition - axis);
|
||||
totalExtents.addExtents(shapeExtents);
|
||||
}
|
||||
}
|
||||
|
||||
// compute bounding shape parameters
|
||||
// NOTE: we assume that the longest side of totalExtents is the yAxis...
|
||||
glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum;
|
||||
// ... and assume the radius is half the RMS of the X and Z sides:
|
||||
float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
|
||||
_boundingShape.setRadius(capsuleRadius);
|
||||
_boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius);
|
||||
_boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum);
|
||||
_boundingRadius = 0.5f * glm::length(diagonal);
|
||||
}
|
||||
|
||||
void SkeletonModel::resetShapePositions() {
|
||||
// DEBUG method.
|
||||
// Moves shapes to the joint default locations for debug visibility into
|
||||
// how the bounding shape is computed.
|
||||
|
||||
if (!_geometry || _rootIndex == -1 || _shapes.isEmpty()) {
|
||||
// geometry or joints have not yet been created
|
||||
return;
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
if (geometry.joints.isEmpty() || _shapes.size() != geometry.joints.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The shapes are moved to their default positions in computeBoundingShape().
|
||||
computeBoundingShape(geometry);
|
||||
|
||||
// Then we move them into world frame for rendering at the Model's location.
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
Shape* shape = _shapes[i];
|
||||
if (shape) {
|
||||
shape->setTranslation(_translation + _rotation * shape->getTranslation());
|
||||
shape->setRotation(_rotation * shape->getRotation());
|
||||
}
|
||||
}
|
||||
_boundingShape.setTranslation(_translation + _rotation * _boundingShapeLocalOffset);
|
||||
_boundingShape.setRotation(_rotation);
|
||||
}
|
||||
|
||||
void SkeletonModel::renderBoundingCollisionShapes(float alpha) {
|
||||
const int BALL_SUBDIVISIONS = 10;
|
||||
if (_shapes.isEmpty()) {
|
||||
// the bounding shape has not been propery computed
|
||||
// so no need to render it
|
||||
return;
|
||||
}
|
||||
glPushMatrix();
|
||||
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
|
||||
// draw a blue sphere at the capsule endpoint
|
||||
glm::vec3 endPoint;
|
||||
_boundingShape.getEndPoint(endPoint);
|
||||
endPoint = endPoint - _translation;
|
||||
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
|
||||
glColor4f(0.6f, 0.6f, 0.8f, alpha);
|
||||
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
// draw a yellow sphere at the capsule startpoint
|
||||
glm::vec3 startPoint;
|
||||
_boundingShape.getStartPoint(startPoint);
|
||||
startPoint = startPoint - _translation;
|
||||
glm::vec3 axis = endPoint - startPoint;
|
||||
glTranslatef(-axis.x, -axis.y, -axis.z);
|
||||
glColor4f(0.8f, 0.8f, 0.6f, alpha);
|
||||
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
// draw a green cylinder between the two points
|
||||
glm::vec3 origin(0.0f);
|
||||
glColor4f(0.6f, 0.8f, 0.6f, alpha);
|
||||
Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius());
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,23 +13,23 @@
|
|||
#define hifi_SkeletonModel_h
|
||||
|
||||
#include "renderer/Model.h"
|
||||
#include "renderer/RagDoll.h"
|
||||
|
||||
#include <CapsuleShape.h>
|
||||
#include <Ragdoll.h>
|
||||
|
||||
class Avatar;
|
||||
|
||||
/// A skeleton loaded from a model.
|
||||
class SkeletonModel : public Model {
|
||||
class SkeletonModel : public Model, public Ragdoll {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
SkeletonModel(Avatar* owningAvatar);
|
||||
SkeletonModel(Avatar* owningAvatar, QObject* parent = NULL);
|
||||
|
||||
void setJointStates(QVector<JointState> states);
|
||||
|
||||
void simulate(float deltaTime, bool fullUpdate = true);
|
||||
void simulateRagDoll(float deltaTime);
|
||||
void updateShapePositions();
|
||||
|
||||
/// \param jointIndex index of hand joint
|
||||
/// \param shapes[out] list in which is stored pointers to hand shapes
|
||||
|
@ -94,9 +94,27 @@ public:
|
|||
/// \return whether or not both eye meshes were found
|
||||
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
|
||||
|
||||
void renderRagDoll();
|
||||
// virtual overrride from Ragdoll
|
||||
virtual void stepRagdollForward(float deltaTime);
|
||||
|
||||
void moveShapesTowardJoints(float fraction);
|
||||
|
||||
void computeBoundingShape(const FBXGeometry& geometry);
|
||||
void renderBoundingCollisionShapes(float alpha);
|
||||
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
|
||||
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
|
||||
|
||||
void resetShapePositions(); // DEBUG method
|
||||
|
||||
void renderRagdoll();
|
||||
protected:
|
||||
|
||||
// virtual overrrides from Ragdoll
|
||||
void initRagdollPoints();
|
||||
void buildRagdollConstraints();
|
||||
|
||||
void buildShapes();
|
||||
|
||||
/// \param jointIndex index of joint in model
|
||||
/// \param position position of joint in model-frame
|
||||
void applyHandPosition(int jointIndex, const glm::vec3& position);
|
||||
|
@ -120,7 +138,9 @@ private:
|
|||
void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation);
|
||||
|
||||
Avatar* _owningAvatar;
|
||||
RagDoll _ragDoll;
|
||||
|
||||
CapsuleShape _boundingShape;
|
||||
glm::vec3 _boundingShapeLocalOffset;
|
||||
};
|
||||
|
||||
#endif // hifi_SkeletonModel_h
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
#include <glm/gtx/transform.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
||||
#include <CapsuleShape.h>
|
||||
#include <GeometryUtil.h>
|
||||
#include <PhysicsEntity.h>
|
||||
#include <ShapeCollider.h>
|
||||
#include <SphereShape.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Model.h"
|
||||
|
||||
#include <SphereShape.h>
|
||||
#include <CapsuleShape.h>
|
||||
#include <ShapeCollider.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static int modelPointerTypeId = qRegisterMetaType<QPointer<Model> >();
|
||||
|
@ -40,10 +40,7 @@ Model::Model(QObject* parent) :
|
|||
_snapModelToCenter(false),
|
||||
_snappedToCenter(false),
|
||||
_rootIndex(-1),
|
||||
_shapesAreDirty(true),
|
||||
_boundingRadius(0.0f),
|
||||
_boundingShape(),
|
||||
_boundingShapeLocalOffset(0.0f),
|
||||
//_enableCollisionShapes(false),
|
||||
_lodDistance(0.0f),
|
||||
_pupilDilation(0.0f),
|
||||
_url("http://invalid.com") {
|
||||
|
@ -129,7 +126,10 @@ void Model::setScaleInternal(const glm::vec3& scale) {
|
|||
const float ONE_PERCENT = 0.01f;
|
||||
if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) {
|
||||
_scale = scale;
|
||||
rebuildShapes();
|
||||
if (_shapes.size() > 0) {
|
||||
clearShapes();
|
||||
buildShapes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,6 +174,7 @@ QVector<JointState> Model::createJointStates(const FBXGeometry& geometry) {
|
|||
int parentIndex = joint.parentIndex;
|
||||
if (parentIndex == -1) {
|
||||
_rootIndex = i;
|
||||
// NOTE: in practice geometry.offset has a non-unity scale (rather than a translation)
|
||||
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
|
||||
state.computeTransform(parentTransform);
|
||||
} else {
|
||||
|
@ -551,7 +552,6 @@ bool Model::updateGeometry() {
|
|||
model->setURL(attachment.url);
|
||||
_attachments.append(model);
|
||||
}
|
||||
rebuildShapes();
|
||||
needFullUpdate = true;
|
||||
}
|
||||
return needFullUpdate;
|
||||
|
@ -560,6 +560,18 @@ bool Model::updateGeometry() {
|
|||
// virtual
|
||||
void Model::setJointStates(QVector<JointState> states) {
|
||||
_jointStates = states;
|
||||
|
||||
// compute an approximate bounding radius for broadphase collision queries
|
||||
// against PhysicsSimulation boundaries
|
||||
int numJoints = _jointStates.size();
|
||||
float radius = 0.0f;
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
float distance = glm::length(_jointStates[i].getPosition());
|
||||
if (distance > radius) {
|
||||
radius = distance;
|
||||
}
|
||||
}
|
||||
_boundingRadius = radius;
|
||||
}
|
||||
|
||||
bool Model::render(float alpha, RenderMode mode, bool receiveShadows) {
|
||||
|
@ -774,304 +786,13 @@ AnimationHandlePointer Model::createAnimationHandle() {
|
|||
return handle;
|
||||
}
|
||||
|
||||
void Model::clearShapes() {
|
||||
for (int i = 0; i < _jointShapes.size(); ++i) {
|
||||
delete _jointShapes[i];
|
||||
}
|
||||
_jointShapes.clear();
|
||||
}
|
||||
|
||||
void Model::rebuildShapes() {
|
||||
clearShapes();
|
||||
|
||||
if (!_geometry || _rootIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
if (geometry.joints.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We create the shapes with proper dimensions, but we set their transforms later.
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
|
||||
float radius = uniformScale * joint.boneRadius;
|
||||
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
|
||||
Shape::Type type = joint.shapeType;
|
||||
if (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON) {
|
||||
// this capsule is effectively a sphere
|
||||
type = Shape::SPHERE_SHAPE;
|
||||
}
|
||||
if (type == Shape::CAPSULE_SHAPE) {
|
||||
CapsuleShape* capsule = new CapsuleShape(radius, halfHeight);
|
||||
_jointShapes.push_back(capsule);
|
||||
} else if (type == Shape::SPHERE_SHAPE) {
|
||||
SphereShape* sphere = new SphereShape(radius, glm::vec3(0.0f));
|
||||
_jointShapes.push_back(sphere);
|
||||
} else {
|
||||
// this shape type is not handled and the joint shouldn't collide,
|
||||
// however we must have a shape for each joint,
|
||||
// so we make a bogus sphere with zero radius.
|
||||
// TODO: implement collision groups for more control over what collides with what
|
||||
SphereShape* sphere = new SphereShape(0.0f, glm::vec3(0.0f));
|
||||
_jointShapes.push_back(sphere);
|
||||
}
|
||||
}
|
||||
|
||||
// This method moves the shapes to their default positions in Model frame
|
||||
// which is where we compute the bounding shape's parameters.
|
||||
computeBoundingShape(geometry);
|
||||
|
||||
// finally sync shapes to joint positions
|
||||
_shapesAreDirty = true;
|
||||
updateShapePositions();
|
||||
}
|
||||
|
||||
void Model::computeBoundingShape(const FBXGeometry& geometry) {
|
||||
// compute default joint transforms and rotations
|
||||
// (in local frame, ignoring Model translation and rotation)
|
||||
int numJoints = geometry.joints.size();
|
||||
QVector<glm::mat4> transforms;
|
||||
transforms.fill(glm::mat4(), numJoints);
|
||||
QVector<glm::quat> finalRotations;
|
||||
finalRotations.fill(glm::quat(), numJoints);
|
||||
|
||||
QVector<bool> shapeIsSet;
|
||||
shapeIsSet.fill(false, numJoints);
|
||||
int numShapesSet = 0;
|
||||
int lastNumShapesSet = -1;
|
||||
while (numShapesSet < numJoints && numShapesSet != lastNumShapesSet) {
|
||||
lastNumShapesSet = numShapesSet;
|
||||
for (int i = 0; i < numJoints; i++) {
|
||||
const FBXJoint& joint = geometry.joints.at(i);
|
||||
int parentIndex = joint.parentIndex;
|
||||
|
||||
if (parentIndex == -1) {
|
||||
glm::mat4 baseTransform = glm::scale(_scale) * glm::translate(_offset);
|
||||
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
|
||||
glm::mat4 rootTransform = baseTransform * geometry.offset * glm::translate(joint.translation)
|
||||
* joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
// remove the tranlsation part before we save the root transform
|
||||
transforms[i] = glm::translate(- extractTranslation(rootTransform)) * rootTransform;
|
||||
|
||||
finalRotations[i] = combinedRotation;
|
||||
++numShapesSet;
|
||||
shapeIsSet[i] = true;
|
||||
} else if (shapeIsSet[parentIndex]) {
|
||||
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
|
||||
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
|
||||
* joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
finalRotations[i] = finalRotations[parentIndex] * combinedRotation;
|
||||
++numShapesSet;
|
||||
shapeIsSet[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sync shapes to joints
|
||||
_boundingRadius = 0.0f;
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
glm::vec3 jointToShapeOffset = uniformScale * (finalRotations[i] * joint.shapePosition);
|
||||
glm::vec3 localPosition = extractTranslation(transforms[i]) + jointToShapeOffset;
|
||||
Shape* shape = _jointShapes[i];
|
||||
shape->setPosition(localPosition);
|
||||
shape->setRotation(finalRotations[i] * joint.shapeRotation);
|
||||
float distance = glm::length(localPosition) + shape->getBoundingRadius();
|
||||
if (distance > _boundingRadius) {
|
||||
_boundingRadius = distance;
|
||||
}
|
||||
}
|
||||
|
||||
// compute bounding box
|
||||
Extents totalExtents;
|
||||
totalExtents.reset();
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
Extents shapeExtents;
|
||||
shapeExtents.reset();
|
||||
|
||||
Shape* shape = _jointShapes[i];
|
||||
glm::vec3 localPosition = shape->getPosition();
|
||||
int type = shape->getType();
|
||||
if (type == Shape::CAPSULE_SHAPE) {
|
||||
// add the two furthest surface points of the capsule
|
||||
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
|
||||
glm::vec3 axis;
|
||||
capsule->computeNormalizedAxis(axis);
|
||||
float radius = capsule->getRadius();
|
||||
float halfHeight = capsule->getHalfHeight();
|
||||
axis = halfHeight * axis + glm::vec3(radius);
|
||||
|
||||
shapeExtents.addPoint(localPosition + axis);
|
||||
shapeExtents.addPoint(localPosition - axis);
|
||||
totalExtents.addExtents(shapeExtents);
|
||||
} else if (type == Shape::SPHERE_SHAPE) {
|
||||
float radius = shape->getBoundingRadius();
|
||||
glm::vec3 axis = glm::vec3(radius);
|
||||
shapeExtents.addPoint(localPosition + axis);
|
||||
shapeExtents.addPoint(localPosition - axis);
|
||||
totalExtents.addExtents(shapeExtents);
|
||||
}
|
||||
}
|
||||
|
||||
// compute bounding shape parameters
|
||||
// NOTE: we assume that the longest side of totalExtents is the yAxis...
|
||||
glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum;
|
||||
// ... and assume the radius is half the RMS of the X and Z sides:
|
||||
float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
|
||||
_boundingShape.setRadius(capsuleRadius);
|
||||
_boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius);
|
||||
_boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum);
|
||||
}
|
||||
|
||||
void Model::resetShapePositions() {
|
||||
// DEBUG method.
|
||||
// Moves shapes to the joint default locations for debug visibility into
|
||||
// how the bounding shape is computed.
|
||||
|
||||
if (!_geometry || _rootIndex == -1) {
|
||||
// geometry or joints have not yet been created
|
||||
return;
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
if (geometry.joints.isEmpty() || _jointShapes.size() != geometry.joints.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The shapes are moved to their default positions in computeBoundingShape().
|
||||
computeBoundingShape(geometry);
|
||||
|
||||
// Then we move them into world frame for rendering at the Model's location.
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
Shape* shape = _jointShapes[i];
|
||||
shape->setPosition(_translation + _rotation * shape->getPosition());
|
||||
shape->setRotation(_rotation * shape->getRotation());
|
||||
}
|
||||
_boundingShape.setPosition(_translation + _rotation * _boundingShapeLocalOffset);
|
||||
_boundingShape.setRotation(_rotation);
|
||||
// virtual override from PhysicsEntity
|
||||
void Model::buildShapes() {
|
||||
// TODO: figure out how to load/build collision shapes for general models
|
||||
}
|
||||
|
||||
void Model::updateShapePositions() {
|
||||
if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) {
|
||||
glm::vec3 rootPosition(0.0f);
|
||||
_boundingRadius = 0.0f;
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const JointState& state = _jointStates[i];
|
||||
const FBXJoint& joint = state.getFBXJoint();
|
||||
// shape position and rotation need to be in world-frame
|
||||
glm::quat stateRotation = state.getRotation();
|
||||
glm::vec3 shapeOffset = uniformScale * (stateRotation * joint.shapePosition);
|
||||
glm::vec3 worldPosition = _translation + _rotation * (state.getPosition() + shapeOffset);
|
||||
Shape* shape = _jointShapes[i];
|
||||
shape->setPosition(worldPosition);
|
||||
shape->setRotation(_rotation * stateRotation * joint.shapeRotation);
|
||||
float distance = glm::distance(worldPosition, _translation) + shape->getBoundingRadius();
|
||||
if (distance > _boundingRadius) {
|
||||
_boundingRadius = distance;
|
||||
}
|
||||
if (joint.parentIndex == -1) {
|
||||
rootPosition = worldPosition;
|
||||
}
|
||||
}
|
||||
_shapesAreDirty = false;
|
||||
_boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset);
|
||||
_boundingShape.setRotation(_rotation);
|
||||
}
|
||||
}
|
||||
|
||||
bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
|
||||
const glm::vec3 relativeOrigin = origin - _translation;
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
float minDistance = FLT_MAX;
|
||||
float radiusScale = extractUniformScale(_scale);
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
glm::vec3 end = _translation + _rotation * _jointStates[i].getPosition();
|
||||
float endRadius = joint.boneRadius * radiusScale;
|
||||
glm::vec3 start = end;
|
||||
float startRadius = joint.boneRadius * radiusScale;
|
||||
if (joint.parentIndex != -1) {
|
||||
start = _translation + _rotation * _jointStates[joint.parentIndex].getPosition();
|
||||
startRadius = geometry.joints[joint.parentIndex].boneRadius * radiusScale;
|
||||
}
|
||||
// for now, use average of start and end radii
|
||||
float capsuleDistance;
|
||||
if (findRayCapsuleIntersection(relativeOrigin, direction, start, end,
|
||||
(startRadius + endRadius) / 2.0f, capsuleDistance)) {
|
||||
minDistance = qMin(minDistance, capsuleDistance);
|
||||
}
|
||||
}
|
||||
if (minDistance < FLT_MAX) {
|
||||
distance = minDistance;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Model::findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions) {
|
||||
bool collided = false;
|
||||
for (int i = 0; i < shapes.size(); ++i) {
|
||||
const Shape* theirShape = shapes[i];
|
||||
for (int j = 0; j < _jointShapes.size(); ++j) {
|
||||
const Shape* ourShape = _jointShapes[j];
|
||||
if (ShapeCollider::collideShapes(theirShape, ourShape, collisions)) {
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return collided;
|
||||
}
|
||||
|
||||
bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius,
|
||||
CollisionList& collisions, int skipIndex) {
|
||||
bool collided = false;
|
||||
SphereShape sphere(sphereRadius, sphereCenter);
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
if (joint.parentIndex != -1) {
|
||||
if (skipIndex != -1) {
|
||||
int ancestorIndex = joint.parentIndex;
|
||||
do {
|
||||
if (ancestorIndex == skipIndex) {
|
||||
goto outerContinue;
|
||||
}
|
||||
ancestorIndex = geometry.joints[ancestorIndex].parentIndex;
|
||||
|
||||
} while (ancestorIndex != -1);
|
||||
}
|
||||
}
|
||||
if (ShapeCollider::collideShapes(&sphere, _jointShapes[i], collisions)) {
|
||||
CollisionInfo* collision = collisions.getLastCollision();
|
||||
collision->_type = COLLISION_TYPE_MODEL;
|
||||
collision->_data = (void*)(this);
|
||||
collision->_intData = i;
|
||||
collided = true;
|
||||
}
|
||||
outerContinue: ;
|
||||
}
|
||||
return collided;
|
||||
}
|
||||
|
||||
bool Model::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
|
||||
bool collided = false;
|
||||
PlaneShape planeShape(plane);
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
if (ShapeCollider::collideShapes(&planeShape, _jointShapes[i], collisions)) {
|
||||
CollisionInfo* collision = collisions.getLastCollision();
|
||||
collision->_type = COLLISION_TYPE_MODEL;
|
||||
collision->_data = (void*)(this);
|
||||
collision->_intData = i;
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
return collided;
|
||||
// TODO: implement this when we know how to build shapes for regular Models
|
||||
}
|
||||
|
||||
class Blender : public QRunnable {
|
||||
|
@ -1197,7 +918,7 @@ void Model::simulateInternal(float deltaTime) {
|
|||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
updateJointState(i);
|
||||
}
|
||||
_shapesAreDirty = true;
|
||||
_shapesAreDirty = ! _shapes.isEmpty();
|
||||
|
||||
// update the attachment transforms and simulate them
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
@ -1332,7 +1053,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, const gl
|
|||
for (int j = freeLineage.size() - 1; j >= 0; j--) {
|
||||
updateJointState(freeLineage.at(j));
|
||||
}
|
||||
_shapesAreDirty = true;
|
||||
_shapesAreDirty = !_shapes.isEmpty();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1370,14 +1091,17 @@ const int BALL_SUBDIVISIONS = 10;
|
|||
void Model::renderJointCollisionShapes(float alpha) {
|
||||
glPushMatrix();
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
glPushMatrix();
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
Shape* shape = _shapes[i];
|
||||
if (!shape) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Shape* shape = _jointShapes[i];
|
||||
|
||||
glPushMatrix();
|
||||
// NOTE: the shapes are in the avatar local-frame
|
||||
if (shape->getType() == Shape::SPHERE_SHAPE) {
|
||||
// shapes are stored in world-frame, so we have to transform into model frame
|
||||
glm::vec3 position = shape->getPosition() - _translation;
|
||||
glm::vec3 position = _rotation * shape->getTranslation();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
const glm::quat& rotation = shape->getRotation();
|
||||
glm::vec3 axis = glm::axis(rotation);
|
||||
|
@ -1392,7 +1116,7 @@ void Model::renderJointCollisionShapes(float alpha) {
|
|||
// draw a blue sphere at the capsule endpoint
|
||||
glm::vec3 endPoint;
|
||||
capsule->getEndPoint(endPoint);
|
||||
endPoint = endPoint - _translation;
|
||||
endPoint = _rotation * endPoint;
|
||||
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
|
||||
glColor4f(0.6f, 0.6f, 0.8f, alpha);
|
||||
glutSolidSphere(capsule->getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
@ -1400,7 +1124,7 @@ void Model::renderJointCollisionShapes(float alpha) {
|
|||
// draw a yellow sphere at the capsule startpoint
|
||||
glm::vec3 startPoint;
|
||||
capsule->getStartPoint(startPoint);
|
||||
startPoint = startPoint - _translation;
|
||||
startPoint = _rotation * startPoint;
|
||||
glm::vec3 axis = endPoint - startPoint;
|
||||
glTranslatef(-axis.x, -axis.y, -axis.z);
|
||||
glColor4f(0.8f, 0.8f, 0.6f, alpha);
|
||||
|
@ -1416,85 +1140,6 @@ void Model::renderJointCollisionShapes(float alpha) {
|
|||
glPopMatrix();
|
||||
}
|
||||
|
||||
void Model::renderBoundingCollisionShapes(float alpha) {
|
||||
glPushMatrix();
|
||||
|
||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||
|
||||
// draw a blue sphere at the capsule endpoint
|
||||
glm::vec3 endPoint;
|
||||
_boundingShape.getEndPoint(endPoint);
|
||||
endPoint = endPoint - _translation;
|
||||
glTranslatef(endPoint.x, endPoint.y, endPoint.z);
|
||||
glColor4f(0.6f, 0.6f, 0.8f, alpha);
|
||||
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
// draw a yellow sphere at the capsule startpoint
|
||||
glm::vec3 startPoint;
|
||||
_boundingShape.getStartPoint(startPoint);
|
||||
startPoint = startPoint - _translation;
|
||||
glm::vec3 axis = endPoint - startPoint;
|
||||
glTranslatef(-axis.x, -axis.y, -axis.z);
|
||||
glColor4f(0.8f, 0.8f, 0.6f, alpha);
|
||||
glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
|
||||
|
||||
// draw a green cylinder between the two points
|
||||
glm::vec3 origin(0.0f);
|
||||
glColor4f(0.6f, 0.8f, 0.6f, alpha);
|
||||
Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius());
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
|
||||
if (collision._type == COLLISION_TYPE_MODEL) {
|
||||
// the joint is pokable by a collision if it exists and is free to move
|
||||
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._intData];
|
||||
if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// an empty freeLineage means the joint can't move
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
int jointIndex = collision._intData;
|
||||
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
|
||||
return !freeLineage.isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Model::applyCollision(CollisionInfo& collision) {
|
||||
if (collision._type != COLLISION_TYPE_MODEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 jointPosition(0.0f);
|
||||
int jointIndex = collision._intData;
|
||||
if (getJointPositionInWorldFrame(jointIndex, jointPosition)) {
|
||||
const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex];
|
||||
if (joint.parentIndex != -1) {
|
||||
// compute the approximate distance (travel) that the joint needs to move
|
||||
glm::vec3 start;
|
||||
getJointPositionInWorldFrame(joint.parentIndex, start);
|
||||
glm::vec3 contactPoint = collision._contactPoint - start;
|
||||
glm::vec3 penetrationEnd = contactPoint + collision._penetration;
|
||||
glm::vec3 axis = glm::cross(contactPoint, penetrationEnd);
|
||||
float travel = glm::length(axis);
|
||||
const float MIN_TRAVEL = 1.0e-8f;
|
||||
if (travel > MIN_TRAVEL) {
|
||||
// compute the new position of the joint
|
||||
float angle = asinf(travel / (glm::length(contactPoint) * glm::length(penetrationEnd)));
|
||||
axis = glm::normalize(axis);
|
||||
glm::vec3 end;
|
||||
getJointPositionInWorldFrame(jointIndex, end);
|
||||
// transform into model-frame
|
||||
glm::vec3 newEnd = glm::inverse(_rotation) * (start + glm::angleAxis(angle, axis) * (end - start) - _translation);
|
||||
// try to move it
|
||||
setJointPosition(jointIndex, newEnd, glm::quat(), false, -1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Model::setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
|
||||
if (_blendedVertexBuffers.isEmpty()) {
|
||||
return;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
#include <CapsuleShape.h>
|
||||
#include <PhysicsEntity.h>
|
||||
|
||||
#include <AnimationCache.h>
|
||||
|
||||
|
@ -33,7 +33,7 @@ typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
|
|||
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
|
||||
|
||||
/// A generic 3D model displaying geometry loaded from a URL.
|
||||
class Model : public QObject {
|
||||
class Model : public QObject, public PhysicsEntity {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
@ -41,12 +41,6 @@ public:
|
|||
Model(QObject* parent = NULL);
|
||||
virtual ~Model();
|
||||
|
||||
void setTranslation(const glm::vec3& translation) { _translation = translation; }
|
||||
const glm::vec3& getTranslation() const { return _translation; }
|
||||
|
||||
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
|
||||
const glm::quat& getRotation() const { return _rotation; }
|
||||
|
||||
/// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension
|
||||
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f);
|
||||
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
|
||||
|
@ -67,7 +61,7 @@ public:
|
|||
|
||||
void setBlendshapeCoefficients(const QVector<float>& coefficients) { _blendshapeCoefficients = coefficients; }
|
||||
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||
|
||||
|
||||
bool isActive() const { return _geometry && _geometry->isLoaded(); }
|
||||
|
||||
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); }
|
||||
|
@ -134,69 +128,34 @@ public:
|
|||
QStringList getJointNames() const;
|
||||
|
||||
AnimationHandlePointer createAnimationHandle();
|
||||
|
||||
|
||||
const QList<AnimationHandlePointer>& getRunningAnimations() const { return _runningAnimations; }
|
||||
|
||||
void clearShapes();
|
||||
void rebuildShapes();
|
||||
void resetShapePositions();
|
||||
|
||||
// virtual overrides from PhysicsEntity
|
||||
virtual void buildShapes();
|
||||
virtual void updateShapePositions();
|
||||
|
||||
void renderJointCollisionShapes(float alpha);
|
||||
void renderBoundingCollisionShapes(float alpha);
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||
|
||||
/// \param shapes list of pointers shapes to test against Model
|
||||
/// \param collisions list to store collision results
|
||||
/// \return true if at least one shape collided agains Model
|
||||
bool findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions);
|
||||
|
||||
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||
CollisionList& collisions, int skipIndex = -1);
|
||||
|
||||
bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
|
||||
|
||||
/// \param collision details about the collisions
|
||||
/// \return true if the collision is against a moveable joint
|
||||
bool collisionHitsMoveableJoint(CollisionInfo& collision) const;
|
||||
|
||||
/// \param collision details about the collision
|
||||
/// Use the collision to affect the model
|
||||
void applyCollision(CollisionInfo& collision);
|
||||
|
||||
float getBoundingRadius() const { return _boundingRadius; }
|
||||
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
|
||||
|
||||
/// Sets blended vertices computed in a separate thread.
|
||||
void setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||
|
||||
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
|
||||
|
||||
protected:
|
||||
|
||||
QSharedPointer<NetworkGeometry> _geometry;
|
||||
|
||||
glm::vec3 _translation;
|
||||
glm::quat _rotation;
|
||||
glm::vec3 _scale;
|
||||
glm::vec3 _offset;
|
||||
|
||||
bool _scaleToFit; /// If you set scaleToFit, we will calculate scale based on MeshExtents
|
||||
float _scaleToFitLargestDimension; /// this is the dimension that scale to fit will use
|
||||
bool _scaledToFit; /// have we scaled to fit
|
||||
|
||||
|
||||
bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space
|
||||
bool _snappedToCenter; /// are we currently snapped to center
|
||||
int _rootIndex;
|
||||
|
||||
bool _shapesAreDirty;
|
||||
QVector<JointState> _jointStates;
|
||||
QVector<Shape*> _jointShapes;
|
||||
|
||||
float _boundingRadius;
|
||||
CapsuleShape _boundingShape;
|
||||
glm::vec3 _boundingShapeLocalOffset;
|
||||
|
||||
|
||||
class MeshState {
|
||||
public:
|
||||
QVector<glm::mat4> clusterMatrices;
|
||||
|
@ -240,8 +199,6 @@ protected:
|
|||
/// first free ancestor.
|
||||
float getLimbLength(int jointIndex) const;
|
||||
|
||||
void computeBoundingShape(const FBXGeometry& geometry);
|
||||
|
||||
private:
|
||||
|
||||
friend class AnimationHandle;
|
||||
|
|
|
@ -1,167 +0,0 @@
|
|||
//
|
||||
// RagDoll.cpp
|
||||
// interface/src/avatar
|
||||
//
|
||||
// Created by Andrew Meadows 2014.05.30
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
#include <CollisionInfo.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <CapsuleShape.h>
|
||||
#include <SphereShape.h>
|
||||
|
||||
#include "RagDoll.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// FixedConstraint
|
||||
// ----------------------------------------------------------------------------
|
||||
FixedConstraint::FixedConstraint(glm::vec3* point, const glm::vec3& anchor) : _point(point), _anchor(anchor) {
|
||||
}
|
||||
|
||||
float FixedConstraint::enforce() {
|
||||
assert(_point != NULL);
|
||||
float distance = glm::distance(_anchor, *_point);
|
||||
*_point = _anchor;
|
||||
return distance;
|
||||
}
|
||||
|
||||
void FixedConstraint::setPoint(glm::vec3* point) {
|
||||
_point = point;
|
||||
}
|
||||
|
||||
void FixedConstraint::setAnchor(const glm::vec3& anchor) {
|
||||
_anchor = anchor;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DistanceConstraint
|
||||
// ----------------------------------------------------------------------------
|
||||
DistanceConstraint::DistanceConstraint(glm::vec3* startPoint, glm::vec3* endPoint) : _distance(-1.0f) {
|
||||
_points[0] = startPoint;
|
||||
_points[1] = endPoint;
|
||||
_distance = glm::distance(*(_points[0]), *(_points[1]));
|
||||
}
|
||||
|
||||
DistanceConstraint::DistanceConstraint(const DistanceConstraint& other) {
|
||||
_distance = other._distance;
|
||||
_points[0] = other._points[0];
|
||||
_points[1] = other._points[1];
|
||||
}
|
||||
|
||||
void DistanceConstraint::setDistance(float distance) {
|
||||
_distance = fabsf(distance);
|
||||
}
|
||||
|
||||
float DistanceConstraint::enforce() {
|
||||
float newDistance = glm::distance(*(_points[0]), *(_points[1]));
|
||||
glm::vec3 direction(0.0f, 1.0f, 0.0f);
|
||||
if (newDistance > EPSILON) {
|
||||
direction = (*(_points[0]) - *(_points[1])) / newDistance;
|
||||
}
|
||||
glm::vec3 center = 0.5f * (*(_points[0]) + *(_points[1]));
|
||||
*(_points[0]) = center + (0.5f * _distance) * direction;
|
||||
*(_points[1]) = center - (0.5f * _distance) * direction;
|
||||
return glm::abs(newDistance - _distance);
|
||||
}
|
||||
|
||||
void DistanceConstraint::updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const {
|
||||
if (!shape) {
|
||||
return;
|
||||
}
|
||||
switch (shape->getType()) {
|
||||
case Shape::SPHERE_SHAPE: {
|
||||
// sphere collides at endPoint
|
||||
SphereShape* sphere = static_cast<SphereShape*>(shape);
|
||||
sphere->setPosition(translation + rotation * (*_points[1]));
|
||||
}
|
||||
break;
|
||||
case Shape::CAPSULE_SHAPE: {
|
||||
// capsule collides from startPoint to endPoint
|
||||
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
|
||||
capsule->setEndPoints(translation + rotation * (*_points[0]), translation + rotation * (*_points[1]));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// RagDoll
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
RagDoll::RagDoll() {
|
||||
}
|
||||
|
||||
RagDoll::~RagDoll() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void RagDoll::init(const QVector<JointState>& states) {
|
||||
clear();
|
||||
const int numStates = states.size();
|
||||
_points.reserve(numStates);
|
||||
for (int i = 0; i < numStates; ++i) {
|
||||
const JointState& state = states[i];
|
||||
_points.push_back(state.getPosition());
|
||||
int parentIndex = state.getFBXJoint().parentIndex;
|
||||
assert(parentIndex < i);
|
||||
if (parentIndex == -1) {
|
||||
FixedConstraint* anchor = new FixedConstraint(&(_points[i]), glm::vec3(0.0f));
|
||||
_constraints.push_back(anchor);
|
||||
} else {
|
||||
DistanceConstraint* stick = new DistanceConstraint(&(_points[i]), &(_points[parentIndex]));
|
||||
_constraints.push_back(stick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete all data.
|
||||
void RagDoll::clear() {
|
||||
int numConstraints = _constraints.size();
|
||||
for (int i = 0; i < numConstraints; ++i) {
|
||||
delete _constraints[i];
|
||||
}
|
||||
_constraints.clear();
|
||||
_points.clear();
|
||||
}
|
||||
|
||||
float RagDoll::slaveToSkeleton(const QVector<JointState>& states, float fraction) {
|
||||
const int numStates = states.size();
|
||||
assert(numStates == _points.size());
|
||||
fraction = glm::clamp(fraction, 0.0f, 1.0f);
|
||||
float maxDistance = 0.0f;
|
||||
for (int i = 0; i < numStates; ++i) {
|
||||
glm::vec3 oldPoint = _points[i];
|
||||
_points[i] = (1.0f - fraction) * _points[i] + fraction * states[i].getPosition();
|
||||
maxDistance = glm::max(maxDistance, glm::distance(oldPoint, _points[i]));
|
||||
}
|
||||
return maxDistance;
|
||||
}
|
||||
|
||||
float RagDoll::enforceConstraints() {
|
||||
float maxDistance = 0.0f;
|
||||
const int numConstraints = _constraints.size();
|
||||
for (int i = 0; i < numConstraints; ++i) {
|
||||
DistanceConstraint* c = static_cast<DistanceConstraint*>(_constraints[i]);
|
||||
//maxDistance = glm::max(maxDistance, _constraints[i]->enforce());
|
||||
maxDistance = glm::max(maxDistance, c->enforce());
|
||||
}
|
||||
return maxDistance;
|
||||
}
|
||||
|
||||
void RagDoll::updateShapes(const QVector<Shape*>& shapes, const glm::quat& rotation, const glm::vec3& translation) const {
|
||||
int numShapes = shapes.size();
|
||||
int numConstraints = _constraints.size();
|
||||
for (int i = 0; i < numShapes && i < numConstraints; ++i) {
|
||||
_constraints[i]->updateProxyShape(shapes[i], rotation, translation);
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
//
|
||||
// RagDoll.h
|
||||
// interface/src/avatar
|
||||
//
|
||||
// Created by Andrew Meadows 2014.05.30
|
||||
// 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_RagDoll_h
|
||||
#define hifi_RagDoll_h
|
||||
|
||||
#include "renderer/Model.h"
|
||||
|
||||
class Shape;
|
||||
|
||||
class Constraint {
|
||||
public:
|
||||
Constraint() {}
|
||||
virtual ~Constraint() {}
|
||||
|
||||
/// Enforce contraint by moving relevant points.
|
||||
/// \return max distance of point movement
|
||||
virtual float enforce() = 0;
|
||||
|
||||
/// \param shape pointer to shape that will be this Constraint's collision proxy
|
||||
/// \param rotation rotation into shape's collision frame
|
||||
/// \param translation translation into shape's collision frame
|
||||
/// Moves the shape such that it will collide at this constraint's position
|
||||
virtual void updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const {}
|
||||
|
||||
protected:
|
||||
int _type;
|
||||
};
|
||||
|
||||
class FixedConstraint : public Constraint {
|
||||
public:
|
||||
FixedConstraint(glm::vec3* point, const glm::vec3& anchor);
|
||||
float enforce();
|
||||
void setPoint(glm::vec3* point);
|
||||
void setAnchor(const glm::vec3& anchor);
|
||||
private:
|
||||
glm::vec3* _point;
|
||||
glm::vec3 _anchor;
|
||||
};
|
||||
|
||||
class DistanceConstraint : public Constraint {
|
||||
public:
|
||||
DistanceConstraint(glm::vec3* startPoint, glm::vec3* endPoint);
|
||||
DistanceConstraint(const DistanceConstraint& other);
|
||||
float enforce();
|
||||
void setDistance(float distance);
|
||||
void updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const;
|
||||
private:
|
||||
float _distance;
|
||||
glm::vec3* _points[2];
|
||||
};
|
||||
|
||||
class RagDoll {
|
||||
public:
|
||||
|
||||
RagDoll();
|
||||
virtual ~RagDoll();
|
||||
|
||||
/// Create points and constraints based on topology of collection of joints
|
||||
/// \param joints list of connected joint states
|
||||
void init(const QVector<JointState>& states);
|
||||
|
||||
/// Delete all data.
|
||||
void clear();
|
||||
|
||||
/// \param states list of joint states
|
||||
/// \param fraction range from 0.0 (no movement) to 1.0 (use joint locations)
|
||||
/// \return max distance of point movement
|
||||
float slaveToSkeleton(const QVector<JointState>& states, float fraction);
|
||||
|
||||
/// Enforce contraints.
|
||||
/// \return max distance of point movement
|
||||
float enforceConstraints();
|
||||
|
||||
const QVector<glm::vec3>& getPoints() const { return _points; }
|
||||
|
||||
/// \param shapes list of shapes to be updated with new positions
|
||||
/// \param rotation rotation into shapes' collision frame
|
||||
/// \param translation translation into shapes' collision frame
|
||||
void updateShapes(const QVector<Shape*>& shapes, const glm::quat& rotation, const glm::vec3& translation) const;
|
||||
|
||||
private:
|
||||
QVector<Constraint*> _constraints;
|
||||
QVector<glm::vec3> _points;
|
||||
};
|
||||
|
||||
#endif // hifi_RagDoll_h
|
|
@ -30,6 +30,9 @@
|
|||
|
||||
const int NUM_MESSAGES_TO_TIME_STAMP = 20;
|
||||
|
||||
const float OPACITY_ACTIVE = 1.0;
|
||||
const float OPACITY_INACTIVE = 0.8;
|
||||
|
||||
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?)|(?:hifi))://\\S+)");
|
||||
const QRegularExpression regexHifiLinks("([#@]\\S+)");
|
||||
const QString mentionSoundsPath("/mention-sounds/");
|
||||
|
@ -108,7 +111,7 @@ ChatWindow::~ChatWindow() {
|
|||
|
||||
void ChatWindow::keyPressEvent(QKeyEvent* event) {
|
||||
if (event->key() == Qt::Key_Escape) {
|
||||
hide();
|
||||
Application::getInstance()->getWindow()->activateWindow();
|
||||
} else {
|
||||
FramelessDialog::keyPressEvent(event);
|
||||
}
|
||||
|
@ -383,3 +386,12 @@ void ChatWindow::scrollToBottom() {
|
|||
QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar();
|
||||
verticalScrollBar->setValue(verticalScrollBar->maximum());
|
||||
}
|
||||
|
||||
bool ChatWindow::event(QEvent* event) {
|
||||
if (event->type() == QEvent::WindowActivate) {
|
||||
setWindowOpacity(OPACITY_ACTIVE);
|
||||
} else if (event->type() == QEvent::WindowDeactivate) {
|
||||
setWindowOpacity(OPACITY_INACTIVE);
|
||||
}
|
||||
return FramelessDialog::event(event);
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ protected:
|
|||
|
||||
virtual void keyPressEvent(QKeyEvent *event);
|
||||
virtual void showEvent(QShowEvent* event);
|
||||
virtual bool event(QEvent* event);
|
||||
|
||||
private:
|
||||
#ifdef HAVE_QXMPP
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode) :
|
||||
NodeData(),
|
||||
_resetCount(0),
|
||||
_sampleCapacity(numFrameSamples * RING_BUFFER_LENGTH_FRAMES),
|
||||
_numFrameSamples(numFrameSamples),
|
||||
_isStarved(true),
|
||||
|
@ -122,19 +123,15 @@ qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
|
|||
|
||||
int samplesToCopy = std::min((quint64)(maxSize / sizeof(int16_t)), (quint64)_sampleCapacity);
|
||||
|
||||
std::less<int16_t*> less;
|
||||
std::less_equal<int16_t*> lessEqual;
|
||||
|
||||
if (_hasStarted
|
||||
&& (less(_endOfLastWrite, _nextOutput)
|
||||
&& lessEqual(_nextOutput, shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy)))) {
|
||||
if (_hasStarted && samplesToCopy > _sampleCapacity - samplesAvailable()) {
|
||||
// this read will cross the next output, so call us starved and reset the buffer
|
||||
qDebug() << "Filled the ring buffer. Resetting.";
|
||||
_endOfLastWrite = _buffer;
|
||||
_nextOutput = _buffer;
|
||||
_isStarved = true;
|
||||
_resetCount++;
|
||||
}
|
||||
|
||||
|
||||
if (_endOfLastWrite + samplesToCopy <= _buffer + _sampleCapacity) {
|
||||
memcpy(_endOfLastWrite, data, samplesToCopy * sizeof(int16_t));
|
||||
} else {
|
||||
|
@ -144,7 +141,7 @@ qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
|
|||
}
|
||||
|
||||
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy);
|
||||
|
||||
|
||||
return samplesToCopy * sizeof(int16_t);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ const int NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL = NETWORK_BUFFER_LENGTH_BYTE
|
|||
const unsigned int BUFFER_SEND_INTERVAL_USECS = floorf((NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL
|
||||
/ (float) SAMPLE_RATE) * 1000 * 1000);
|
||||
|
||||
const short RING_BUFFER_LENGTH_FRAMES = 10;
|
||||
const short RING_BUFFER_LENGTH_FRAMES = 100;
|
||||
|
||||
const int MAX_SAMPLE_VALUE = std::numeric_limits<int16_t>::max();
|
||||
const int MIN_SAMPLE_VALUE = std::numeric_limits<int16_t>::min();
|
||||
|
@ -71,6 +71,7 @@ public:
|
|||
bool isStarved() const { return _isStarved; }
|
||||
void setIsStarved(bool isStarved) { _isStarved = isStarved; }
|
||||
|
||||
int getResetCount() const { return _resetCount; } /// how many times has the ring buffer written past the end and reset
|
||||
bool hasStarted() const { return _hasStarted; }
|
||||
|
||||
void addSilentFrame(int numSilentSamples);
|
||||
|
@ -80,6 +81,8 @@ protected:
|
|||
AudioRingBuffer& operator= (const AudioRingBuffer&);
|
||||
|
||||
int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const;
|
||||
|
||||
int _resetCount; /// how many times has the ring buffer written past the end and done a reset
|
||||
|
||||
int _sampleCapacity;
|
||||
int _numFrameSamples;
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
#include "InjectedAudioRingBuffer.h"
|
||||
|
||||
InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier) :
|
||||
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Injector),
|
||||
InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier, bool dynamicJitterBuffer) :
|
||||
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Injector, /* isStereo=*/ false , dynamicJitterBuffer),
|
||||
_streamIdentifier(streamIdentifier),
|
||||
_radius(0.0f),
|
||||
_attenuationRatio(0)
|
||||
|
@ -31,6 +31,9 @@ InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier)
|
|||
const uchar MAX_INJECTOR_VOLUME = 255;
|
||||
|
||||
int InjectedAudioRingBuffer::parseData(const QByteArray& packet) {
|
||||
_interframeTimeGapStats.frameReceived();
|
||||
updateDesiredJitterBufferFrames();
|
||||
|
||||
// setup a data stream to read from this packet
|
||||
QDataStream packetStream(packet);
|
||||
packetStream.skipRawData(numBytesForPacketHeader(packet));
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
class InjectedAudioRingBuffer : public PositionalAudioRingBuffer {
|
||||
public:
|
||||
InjectedAudioRingBuffer(const QUuid& streamIdentifier = QUuid());
|
||||
InjectedAudioRingBuffer(const QUuid& streamIdentifier = QUuid(), bool dynamicJitterBuffer = false);
|
||||
|
||||
int parseData(const QByteArray& packet);
|
||||
|
||||
|
|
|
@ -19,8 +19,75 @@
|
|||
#include <UUID.h>
|
||||
|
||||
#include "PositionalAudioRingBuffer.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo) :
|
||||
InterframeTimeGapStats::InterframeTimeGapStats()
|
||||
: _lastFrameReceivedTime(0),
|
||||
_numSamplesInCurrentInterval(0),
|
||||
_currentIntervalMaxGap(0),
|
||||
_newestIntervalMaxGapAt(0),
|
||||
_windowMaxGap(0),
|
||||
_newWindowMaxGapAvailable(false)
|
||||
{
|
||||
memset(_intervalMaxGaps, 0, TIME_GAP_NUM_INTERVALS_IN_WINDOW * sizeof(quint64));
|
||||
}
|
||||
|
||||
void InterframeTimeGapStats::frameReceived() {
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
// make sure this isn't the first time frameReceived() is called so can actually calculate a gap.
|
||||
if (_lastFrameReceivedTime != 0) {
|
||||
quint64 gap = now - _lastFrameReceivedTime;
|
||||
|
||||
// update the current interval max
|
||||
if (gap > _currentIntervalMaxGap) {
|
||||
_currentIntervalMaxGap = gap;
|
||||
|
||||
// keep the window max gap at least as large as the current interval max
|
||||
// this allows the window max gap to respond immediately to a sudden spike in gap times
|
||||
// also, this prevents the window max gap from staying at 0 until the first interval of samples filled up
|
||||
if (_currentIntervalMaxGap > _windowMaxGap) {
|
||||
_windowMaxGap = _currentIntervalMaxGap;
|
||||
_newWindowMaxGapAvailable = true;
|
||||
}
|
||||
}
|
||||
_numSamplesInCurrentInterval++;
|
||||
|
||||
// if the current interval of samples is now full, record it in our interval maxes
|
||||
if (_numSamplesInCurrentInterval == TIME_GAP_NUM_SAMPLES_IN_INTERVAL) {
|
||||
|
||||
// find location to insert this interval's max (increment index cyclically)
|
||||
_newestIntervalMaxGapAt = _newestIntervalMaxGapAt == TIME_GAP_NUM_INTERVALS_IN_WINDOW - 1 ? 0 : _newestIntervalMaxGapAt + 1;
|
||||
|
||||
// record the current interval's max gap as the newest
|
||||
_intervalMaxGaps[_newestIntervalMaxGapAt] = _currentIntervalMaxGap;
|
||||
|
||||
// update the window max gap, which is the max out of all the past intervals' max gaps
|
||||
_windowMaxGap = 0;
|
||||
for (int i = 0; i < TIME_GAP_NUM_INTERVALS_IN_WINDOW; i++) {
|
||||
if (_intervalMaxGaps[i] > _windowMaxGap) {
|
||||
_windowMaxGap = _intervalMaxGaps[i];
|
||||
}
|
||||
}
|
||||
_newWindowMaxGapAvailable = true;
|
||||
|
||||
// reset the current interval
|
||||
_numSamplesInCurrentInterval = 0;
|
||||
_currentIntervalMaxGap = 0;
|
||||
}
|
||||
}
|
||||
_lastFrameReceivedTime = now;
|
||||
}
|
||||
|
||||
quint64 InterframeTimeGapStats::getWindowMaxGap() {
|
||||
_newWindowMaxGapAvailable = false;
|
||||
return _windowMaxGap;
|
||||
}
|
||||
|
||||
|
||||
PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type,
|
||||
bool isStereo, bool dynamicJitterBuffers) :
|
||||
|
||||
AudioRingBuffer(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL),
|
||||
_type(type),
|
||||
_position(0.0f, 0.0f, 0.0f),
|
||||
|
@ -29,9 +96,11 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::
|
|||
_shouldLoopbackForNode(false),
|
||||
_shouldOutputStarveDebug(true),
|
||||
_isStereo(isStereo),
|
||||
_listenerUnattenuatedZone(NULL)
|
||||
_listenerUnattenuatedZone(NULL),
|
||||
_desiredJitterBufferFrames(1),
|
||||
_currentJitterBufferFrames(0),
|
||||
_dynamicJitterBuffers(dynamicJitterBuffers)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
|
||||
|
@ -53,14 +122,35 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
|
|||
|
||||
readBytes += sizeof(int16_t);
|
||||
|
||||
// NOTE: fixes a bug in old clients that would send garbage for their number of silentSamples
|
||||
numSilentSamples = getSamplesPerFrame();
|
||||
|
||||
if (numSilentSamples > 0) {
|
||||
addSilentFrame(numSilentSamples);
|
||||
if (_currentJitterBufferFrames > _desiredJitterBufferFrames) {
|
||||
// our current jitter buffer size exceeds its desired value, so ignore some silent
|
||||
// frames to get that size as close to desired as possible
|
||||
int samplesPerFrame = getSamplesPerFrame();
|
||||
int numSilentFrames = numSilentSamples / samplesPerFrame;
|
||||
int numFramesToDropDesired = _currentJitterBufferFrames - _desiredJitterBufferFrames;
|
||||
|
||||
if (numSilentFrames > numFramesToDropDesired) {
|
||||
// we have more than enough frames to drop to get the jitter buffer to its desired length
|
||||
int numSilentFramesToAdd = numSilentFrames - numFramesToDropDesired;
|
||||
addSilentFrame(numSilentFramesToAdd * samplesPerFrame);
|
||||
_currentJitterBufferFrames = _desiredJitterBufferFrames;
|
||||
|
||||
} else {
|
||||
// we need to drop all frames to get the jitter buffer close as possible to its desired length
|
||||
_currentJitterBufferFrames -= numSilentFrames;
|
||||
}
|
||||
} else {
|
||||
addSilentFrame(numSilentSamples);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// there is audio data to read
|
||||
readBytes += writeData(packet.data() + readBytes, packet.size() - readBytes);
|
||||
}
|
||||
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
|
@ -106,29 +196,72 @@ void PositionalAudioRingBuffer::updateNextOutputTrailingLoudness() {
|
|||
}
|
||||
}
|
||||
|
||||
bool PositionalAudioRingBuffer::shouldBeAddedToMix(int numJitterBufferSamples) {
|
||||
if (!isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL + numJitterBufferSamples)) {
|
||||
bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
|
||||
int samplesPerFrame = getSamplesPerFrame();
|
||||
int desiredJitterBufferSamples = _desiredJitterBufferFrames * samplesPerFrame;
|
||||
|
||||
if (!isNotStarvedOrHasMinimumSamples(samplesPerFrame + desiredJitterBufferSamples)) {
|
||||
// if the buffer was starved, allow it to accrue at least the desired number of
|
||||
// jitter buffer frames before we start taking frames from it for mixing
|
||||
|
||||
if (_shouldOutputStarveDebug) {
|
||||
_shouldOutputStarveDebug = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if (samplesAvailable() < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL) {
|
||||
|
||||
return false;
|
||||
} else if (samplesAvailable() < samplesPerFrame) {
|
||||
// if the buffer doesn't have a full frame of samples to take for mixing, it is starved
|
||||
_isStarved = true;
|
||||
|
||||
// set to 0 to indicate the jitter buffer is starved
|
||||
_currentJitterBufferFrames = 0;
|
||||
|
||||
// reset our _shouldOutputStarveDebug to true so the next is printed
|
||||
_shouldOutputStarveDebug = true;
|
||||
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// good buffer, add this to the mix
|
||||
}
|
||||
|
||||
// good buffer, add this to the mix
|
||||
if (_isStarved) {
|
||||
// if this buffer has just finished replenishing after being starved, the number of frames in it now
|
||||
// minus one (since a frame will be read immediately after this) is the length of the jitter buffer
|
||||
_currentJitterBufferFrames = samplesAvailable() / samplesPerFrame - 1;
|
||||
_isStarved = false;
|
||||
|
||||
// since we've read data from ring buffer at least once - we've started
|
||||
_hasStarted = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// since we've read data from ring buffer at least once - we've started
|
||||
_hasStarted = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int PositionalAudioRingBuffer::getCalculatedDesiredJitterBufferFrames() const {
|
||||
int calculatedDesiredJitterBufferFrames = 1;
|
||||
const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
|
||||
|
||||
calculatedDesiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.peekWindowMaxGap() / USECS_PER_FRAME);
|
||||
if (calculatedDesiredJitterBufferFrames < 1) {
|
||||
calculatedDesiredJitterBufferFrames = 1;
|
||||
}
|
||||
return calculatedDesiredJitterBufferFrames;
|
||||
}
|
||||
|
||||
void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() {
|
||||
if (_interframeTimeGapStats.hasNewWindowMaxGapAvailable()) {
|
||||
if (!_dynamicJitterBuffers) {
|
||||
_desiredJitterBufferFrames = 1; // HACK to see if this fixes the audio silence
|
||||
} else {
|
||||
const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
|
||||
|
||||
_desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.getWindowMaxGap() / USECS_PER_FRAME);
|
||||
if (_desiredJitterBufferFrames < 1) {
|
||||
_desiredJitterBufferFrames = 1;
|
||||
}
|
||||
const int maxDesired = RING_BUFFER_LENGTH_FRAMES - 1;
|
||||
if (_desiredJitterBufferFrames > maxDesired) {
|
||||
_desiredJitterBufferFrames = maxDesired;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,31 @@
|
|||
|
||||
#include "AudioRingBuffer.h"
|
||||
|
||||
// this means that every 500 samples, the max for the past 10*500 samples will be calculated
|
||||
const int TIME_GAP_NUM_SAMPLES_IN_INTERVAL = 500;
|
||||
const int TIME_GAP_NUM_INTERVALS_IN_WINDOW = 10;
|
||||
|
||||
// class used to track time between incoming frames for the purpose of varying the jitter buffer length
|
||||
class InterframeTimeGapStats {
|
||||
public:
|
||||
InterframeTimeGapStats();
|
||||
|
||||
void frameReceived();
|
||||
bool hasNewWindowMaxGapAvailable() const { return _newWindowMaxGapAvailable; }
|
||||
quint64 peekWindowMaxGap() const { return _windowMaxGap; }
|
||||
quint64 getWindowMaxGap();
|
||||
|
||||
private:
|
||||
quint64 _lastFrameReceivedTime;
|
||||
|
||||
int _numSamplesInCurrentInterval;
|
||||
quint64 _currentIntervalMaxGap;
|
||||
quint64 _intervalMaxGaps[TIME_GAP_NUM_INTERVALS_IN_WINDOW];
|
||||
int _newestIntervalMaxGapAt;
|
||||
quint64 _windowMaxGap;
|
||||
bool _newWindowMaxGapAvailable;
|
||||
};
|
||||
|
||||
class PositionalAudioRingBuffer : public AudioRingBuffer {
|
||||
public:
|
||||
enum Type {
|
||||
|
@ -25,7 +50,7 @@ public:
|
|||
Injector
|
||||
};
|
||||
|
||||
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo = false);
|
||||
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo = false, bool dynamicJitterBuffers = false);
|
||||
|
||||
int parseData(const QByteArray& packet);
|
||||
int parsePositionalData(const QByteArray& positionalByteArray);
|
||||
|
@ -34,7 +59,7 @@ public:
|
|||
void updateNextOutputTrailingLoudness();
|
||||
float getNextOutputTrailingLoudness() const { return _nextOutputTrailingLoudness; }
|
||||
|
||||
bool shouldBeAddedToMix(int numJitterBufferSamples);
|
||||
bool shouldBeAddedToMix();
|
||||
|
||||
bool willBeAddedToMix() const { return _willBeAddedToMix; }
|
||||
void setWillBeAddedToMix(bool willBeAddedToMix) { _willBeAddedToMix = willBeAddedToMix; }
|
||||
|
@ -50,10 +75,18 @@ public:
|
|||
AABox* getListenerUnattenuatedZone() const { return _listenerUnattenuatedZone; }
|
||||
void setListenerUnattenuatedZone(AABox* listenerUnattenuatedZone) { _listenerUnattenuatedZone = listenerUnattenuatedZone; }
|
||||
|
||||
int getSamplesPerFrame() const { return _isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; }
|
||||
|
||||
int getCalculatedDesiredJitterBufferFrames() const; /// returns what we would calculate our desired as if asked
|
||||
int getDesiredJitterBufferFrames() const { return _desiredJitterBufferFrames; }
|
||||
int getCurrentJitterBufferFrames() const { return _currentJitterBufferFrames; }
|
||||
|
||||
protected:
|
||||
// disallow copying of PositionalAudioRingBuffer objects
|
||||
PositionalAudioRingBuffer(const PositionalAudioRingBuffer&);
|
||||
PositionalAudioRingBuffer& operator= (const PositionalAudioRingBuffer&);
|
||||
|
||||
void updateDesiredJitterBufferFrames();
|
||||
|
||||
PositionalAudioRingBuffer::Type _type;
|
||||
glm::vec3 _position;
|
||||
|
@ -65,6 +98,11 @@ protected:
|
|||
|
||||
float _nextOutputTrailingLoudness;
|
||||
AABox* _listenerUnattenuatedZone;
|
||||
|
||||
InterframeTimeGapStats _interframeTimeGapStats;
|
||||
int _desiredJitterBufferFrames;
|
||||
int _currentJitterBufferFrames;
|
||||
bool _dynamicJitterBuffers;
|
||||
};
|
||||
|
||||
#endif // hifi_PositionalAudioRingBuffer_h
|
||||
|
|
|
@ -223,7 +223,7 @@ public:
|
|||
|
||||
virtual const glm::vec3& getVelocity() const { return vec3Zero; }
|
||||
|
||||
virtual bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
|
||||
virtual bool findSphereCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1717,7 +1717,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
glm::vec3 boneEnd = extractTranslation(transformJointToMesh);
|
||||
glm::vec3 boneBegin = boneEnd;
|
||||
glm::vec3 boneDirection;
|
||||
float boneLength;
|
||||
float boneLength = 0.0f;
|
||||
if (joint.parentIndex != -1) {
|
||||
boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform);
|
||||
boneDirection = boneEnd - boneBegin;
|
||||
|
@ -1779,7 +1779,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
glm::vec3 boneBegin = boneEnd;
|
||||
|
||||
glm::vec3 boneDirection;
|
||||
float boneLength;
|
||||
float boneLength = 0.0f;
|
||||
if (joint.parentIndex != -1) {
|
||||
boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform);
|
||||
boneDirection = boneEnd - boneBegin;
|
||||
|
|
|
@ -211,6 +211,11 @@ void Attribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelSt
|
|||
root.writeSubdivision(state);
|
||||
}
|
||||
|
||||
bool Attribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) {
|
||||
return firstRoot.deepEquals(this, secondRoot, minimum, size, lod);
|
||||
}
|
||||
|
||||
FloatAttribute::FloatAttribute(const QString& name, float defaultValue) :
|
||||
SimpleInlineAttribute<float>(name, defaultValue) {
|
||||
}
|
||||
|
@ -449,6 +454,12 @@ void SharedObjectAttribute::write(Bitstream& out, void* value, bool isLeaf) cons
|
|||
}
|
||||
}
|
||||
|
||||
bool SharedObjectAttribute::deepEqual(void* first, void* second) const {
|
||||
SharedObjectPointer firstObject = decodeInline<SharedObjectPointer>(first);
|
||||
SharedObjectPointer secondObject = decodeInline<SharedObjectPointer>(second);
|
||||
return firstObject ? firstObject->equals(secondObject) : !secondObject;
|
||||
}
|
||||
|
||||
bool SharedObjectAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
SharedObjectPointer firstChild = decodeInline<SharedObjectPointer>(children[0]);
|
||||
for (int i = 1; i < MERGE_COUNT; i++) {
|
||||
|
@ -489,6 +500,35 @@ MetavoxelNode* SharedObjectSetAttribute::createMetavoxelNode(
|
|||
return new MetavoxelNode(value, original);
|
||||
}
|
||||
|
||||
static bool setsEqual(const SharedObjectSet& firstSet, const SharedObjectSet& secondSet) {
|
||||
if (firstSet.size() != secondSet.size()) {
|
||||
return false;
|
||||
}
|
||||
// some hackiness here: we assume that the local ids of the first set correspond to the remote ids of the second,
|
||||
// so that this will work with the tests
|
||||
foreach (const SharedObjectPointer& firstObject, firstSet) {
|
||||
int id = firstObject->getID();
|
||||
bool found = false;
|
||||
foreach (const SharedObjectPointer& secondObject, secondSet) {
|
||||
if (secondObject->getRemoteID() == id) {
|
||||
if (!firstObject->equals(secondObject)) {
|
||||
return false;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SharedObjectSetAttribute::deepEqual(void* first, void* second) const {
|
||||
return setsEqual(decodeInline<SharedObjectSet>(first), decodeInline<SharedObjectSet>(second));
|
||||
}
|
||||
|
||||
bool SharedObjectSetAttribute::merge(void*& parent, void* children[], bool postRead) const {
|
||||
for (int i = 0; i < MERGE_COUNT; i++) {
|
||||
if (!decodeInline<SharedObjectSet>(children[i]).isEmpty()) {
|
||||
|
@ -563,3 +603,12 @@ void SpannerSetAttribute::writeMetavoxelSubdivision(const MetavoxelNode& root, M
|
|||
state.stream << SharedObjectPointer();
|
||||
}
|
||||
|
||||
bool SpannerSetAttribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) {
|
||||
|
||||
SharedObjectSet firstSet;
|
||||
firstRoot.getSpanners(this, minimum, size, lod, firstSet);
|
||||
SharedObjectSet secondSet;
|
||||
secondRoot.getSpanners(this, minimum, size, lod, secondSet);
|
||||
return setsEqual(firstSet, secondSet);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ class QScriptValue;
|
|||
|
||||
class Attribute;
|
||||
class MetavoxelData;
|
||||
class MetavoxelLOD;
|
||||
class MetavoxelNode;
|
||||
class MetavoxelStreamState;
|
||||
|
||||
|
@ -213,6 +214,11 @@ public:
|
|||
|
||||
virtual bool equal(void* first, void* second) const = 0;
|
||||
|
||||
virtual bool deepEqual(void* first, void* second) const { return equal(first, second); }
|
||||
|
||||
virtual bool metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod);
|
||||
|
||||
/// Merges the value of a parent and its children.
|
||||
/// \param postRead whether or not the merge is happening after a read
|
||||
/// \return whether or not the children and parent values are all equal
|
||||
|
@ -406,6 +412,8 @@ public:
|
|||
virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
|
||||
virtual void write(Bitstream& out, void* value, bool isLeaf) const;
|
||||
|
||||
virtual bool deepEqual(void* first, void* second) const;
|
||||
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
|
||||
virtual void* createFromVariant(const QVariant& value) const;
|
||||
|
@ -434,6 +442,8 @@ public:
|
|||
|
||||
virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const;
|
||||
|
||||
virtual bool deepEqual(void* first, void* second) const;
|
||||
|
||||
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
|
||||
|
||||
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
|
||||
|
@ -462,6 +472,9 @@ public:
|
|||
|
||||
virtual void readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state);
|
||||
virtual void writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state);
|
||||
|
||||
virtual bool metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod);
|
||||
};
|
||||
|
||||
#endif // hifi_AttributeRegistry_h
|
||||
|
|
|
@ -110,6 +110,10 @@ const TypeStreamer* Bitstream::getTypeStreamer(int type) {
|
|||
return getTypeStreamers().value(type);
|
||||
}
|
||||
|
||||
const ObjectStreamer* Bitstream::getObjectStreamer(const QMetaObject* metaObject) {
|
||||
return getObjectStreamers().value(metaObject);
|
||||
}
|
||||
|
||||
const QMetaObject* Bitstream::getMetaObject(const QByteArray& className) {
|
||||
return getMetaObjects().value(className);
|
||||
}
|
||||
|
@ -2316,6 +2320,15 @@ QObject* MappedObjectStreamer::putJSONData(JSONReader& reader, const QJsonObject
|
|||
return object;
|
||||
}
|
||||
|
||||
bool MappedObjectStreamer::equal(const QObject* first, const QObject* second) const {
|
||||
foreach (const StreamerPropertyPair& property, _properties) {
|
||||
if (!property.first->equal(property.second.read(first), property.second.read(second))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MappedObjectStreamer::write(Bitstream& out, const QObject* object) const {
|
||||
foreach (const StreamerPropertyPair& property, _properties) {
|
||||
property.first->write(out, property.second.read(object));
|
||||
|
@ -2433,6 +2446,17 @@ QObject* GenericObjectStreamer::putJSONData(JSONReader& reader, const QJsonObjec
|
|||
return object;
|
||||
}
|
||||
|
||||
bool GenericObjectStreamer::equal(const QObject* first, const QObject* second) const {
|
||||
const QVariantList& firstValues = static_cast<const GenericSharedObject*>(first)->getValues();
|
||||
const QVariantList& secondValues = static_cast<const GenericSharedObject*>(second)->getValues();
|
||||
for (int i = 0; i < _properties.size(); i++) {
|
||||
if (!_properties.at(i).first->equal(firstValues.at(i), secondValues.at(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GenericObjectStreamer::write(Bitstream& out, const QObject* object) const {
|
||||
const QVariantList& values = static_cast<const GenericSharedObject*>(object)->getValues();
|
||||
for (int i = 0; i < _properties.size(); i++) {
|
||||
|
|
|
@ -278,6 +278,9 @@ public:
|
|||
/// Returns the streamer registered for the supplied type, if any.
|
||||
static const TypeStreamer* getTypeStreamer(int type);
|
||||
|
||||
/// Returns the streamer registered for the supplied object, if any.
|
||||
static const ObjectStreamer* getObjectStreamer(const QMetaObject* metaObject);
|
||||
|
||||
/// Returns the meta-object registered under the supplied class name, if any.
|
||||
static const QMetaObject* getMetaObject(const QByteArray& className);
|
||||
|
||||
|
@ -1022,6 +1025,7 @@ public:
|
|||
virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const = 0;
|
||||
virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const = 0;
|
||||
|
||||
virtual bool equal(const QObject* first, const QObject* second) const = 0;
|
||||
virtual void write(Bitstream& out, const QObject* object) const = 0;
|
||||
virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const = 0;
|
||||
virtual QObject* read(Bitstream& in, QObject* object = NULL) const = 0;
|
||||
|
@ -1047,6 +1051,7 @@ public:
|
|||
virtual QJsonObject getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const;
|
||||
virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const;
|
||||
virtual bool equal(const QObject* first, const QObject* second) const;
|
||||
virtual void write(Bitstream& out, const QObject* object) const;
|
||||
virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const;
|
||||
virtual QObject* read(Bitstream& in, QObject* object = NULL) const;
|
||||
|
@ -1070,6 +1075,7 @@ public:
|
|||
virtual QJsonObject getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const;
|
||||
virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const;
|
||||
virtual bool equal(const QObject* first, const QObject* second) const;
|
||||
virtual void write(Bitstream& out, const QObject* object) const;
|
||||
virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const;
|
||||
virtual QObject* read(Bitstream& in, QObject* object = NULL) const;
|
||||
|
@ -1104,7 +1110,7 @@ private:
|
|||
Q_DECLARE_METATYPE(const QMetaObject*)
|
||||
|
||||
/// Macro for registering streamable meta-objects. Typically, one would use this macro at the top level of the source file
|
||||
/// associated with the class.
|
||||
/// associated with the class. The class should have a no-argument constructor flagged with Q_INVOKABLE.
|
||||
#define REGISTER_META_OBJECT(x) static int x##Registration = Bitstream::registerMetaObject(#x, &x::staticMetaObject);
|
||||
|
||||
/// Contains a value along with a pointer to its streamer. This is stored in QVariants when using fallback generics and
|
||||
|
@ -1563,8 +1569,8 @@ public:
|
|||
Bitstream::registerTypeStreamer(qMetaTypeId<X>(), new CollectionTypeStreamer<X>());
|
||||
|
||||
/// Declares the metatype and the streaming operators. Typically, one would use this immediately after the definition of a
|
||||
/// type flagged as STREAMABLE in its header file. The last lines ensure that the generated file will be included in the link
|
||||
/// phase.
|
||||
/// type flagged as STREAMABLE in its header file. The type should have a no-argument constructor. The last lines of this
|
||||
/// macro ensure that the generated file will be included in the link phase.
|
||||
#ifdef _WIN32
|
||||
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
|
||||
Bitstream& operator<<(Bitstream& out, const X& obj); \
|
||||
|
|
|
@ -610,6 +610,23 @@ MetavoxelNode* MetavoxelData::createRoot(const AttributePointer& attribute) {
|
|||
return root = new MetavoxelNode(attribute);
|
||||
}
|
||||
|
||||
bool MetavoxelData::deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod) const {
|
||||
if (_size != other._size) {
|
||||
return false;
|
||||
}
|
||||
if (_roots.size() != other._roots.size()) {
|
||||
return false;
|
||||
}
|
||||
glm::vec3 minimum = getMinimum();
|
||||
for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
|
||||
MetavoxelNode* otherNode = other._roots.value(it.key());
|
||||
if (!(otherNode && it.key()->metavoxelRootsEqual(*it.value(), *otherNode, minimum, _size, lod))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetavoxelData::operator==(const MetavoxelData& other) const {
|
||||
return _size == other._size && _roots == other._roots;
|
||||
}
|
||||
|
@ -1006,6 +1023,44 @@ void MetavoxelNode::clearChildren(const AttributePointer& attribute) {
|
|||
}
|
||||
}
|
||||
|
||||
bool MetavoxelNode::deepEquals(const AttributePointer& attribute, const MetavoxelNode& other,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const {
|
||||
if (!attribute->deepEqual(_attributeValue, other._attributeValue)) {
|
||||
return false;
|
||||
}
|
||||
if (!lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) {
|
||||
return true;
|
||||
}
|
||||
bool leaf = isLeaf(), otherLeaf = other.isLeaf();
|
||||
if (leaf && otherLeaf) {
|
||||
return true;
|
||||
}
|
||||
if (leaf || otherLeaf) {
|
||||
return false;
|
||||
}
|
||||
float nextSize = size * 0.5f;
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i);
|
||||
if (!_children[i]->deepEquals(attribute, *(other._children[i]), nextMinimum, nextSize, lod)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetavoxelNode::getSpanners(const AttributePointer& attribute, const glm::vec3& minimum,
|
||||
float size, const MetavoxelLOD& lod, SharedObjectSet& results) const {
|
||||
results.unite(decodeInline<SharedObjectSet>(_attributeValue));
|
||||
if (isLeaf() || !lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) {
|
||||
return;
|
||||
}
|
||||
float nextSize = size * 0.5f;
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i);
|
||||
_children[i]->getSpanners(attribute, nextMinimum, nextSize, lod, results);
|
||||
}
|
||||
}
|
||||
|
||||
int MetavoxelVisitor::encodeOrder(int first, int second, int third, int fourth,
|
||||
int fifth, int sixth, int seventh, int eighth) {
|
||||
return first | (second << 3) | (third << 6) | (fourth << 9) |
|
||||
|
@ -1034,6 +1089,25 @@ int MetavoxelVisitor::encodeOrder(const glm::vec3& direction) {
|
|||
indexDistances.at(6).index, indexDistances.at(7).index);
|
||||
}
|
||||
|
||||
const int ORDER_ELEMENT_BITS = 3;
|
||||
const int ORDER_ELEMENT_MASK = (1 << ORDER_ELEMENT_BITS) - 1;
|
||||
|
||||
int MetavoxelVisitor::encodeRandomOrder() {
|
||||
// see http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_.22inside-out.22_algorithm
|
||||
int order;
|
||||
int randomValues = rand();
|
||||
for (int i = 0, iShift = 0; i < MetavoxelNode::CHILD_COUNT; i++, iShift += ORDER_ELEMENT_BITS) {
|
||||
int j = (randomValues >> iShift) % (i + 1);
|
||||
int jShift = j * ORDER_ELEMENT_BITS;
|
||||
if (j != i) {
|
||||
int jValue = (order >> jShift) & ORDER_ELEMENT_MASK;
|
||||
order = (order & ~(ORDER_ELEMENT_MASK << iShift)) | (jValue << iShift);
|
||||
}
|
||||
order = (order & ~(ORDER_ELEMENT_MASK << jShift)) | (i << jShift);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
const int MetavoxelVisitor::DEFAULT_ORDER = encodeOrder(0, 1, 2, 3, 4, 5, 6, 7);
|
||||
const int MetavoxelVisitor::STOP_RECURSION = 0;
|
||||
const int MetavoxelVisitor::SHORT_CIRCUIT = -1;
|
||||
|
@ -1227,8 +1301,6 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
QVector<OwnedAttributeValue>(visitation.outputNodes.size()) } };
|
||||
for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
|
||||
// the encoded order tells us the child indices for each iteration
|
||||
const int ORDER_ELEMENT_BITS = 3;
|
||||
const int ORDER_ELEMENT_MASK = (1 << ORDER_ELEMENT_BITS) - 1;
|
||||
int index = encodedOrder & ORDER_ELEMENT_MASK;
|
||||
encodedOrder >>= ORDER_ELEMENT_BITS;
|
||||
for (int j = 0; j < visitation.inputNodes.size(); j++) {
|
||||
|
@ -1269,7 +1341,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
|
|||
}
|
||||
}
|
||||
MetavoxelNode* node = visitation.outputNodes.at(j);
|
||||
MetavoxelNode* child = node->getChild(i);
|
||||
MetavoxelNode* child = node->getChild(index);
|
||||
if (child) {
|
||||
child->decrementReferenceCount(value.getAttribute());
|
||||
} else {
|
||||
|
|
|
@ -34,7 +34,8 @@ class NetworkValue;
|
|||
class Spanner;
|
||||
class SpannerRenderer;
|
||||
|
||||
/// Determines whether to subdivide each node when traversing.
|
||||
/// Determines whether to subdivide each node when traversing. Contains the position (presumed to be of the viewer) and a
|
||||
/// threshold value, where lower thresholds cause smaller/more distant voxels to be subdivided.
|
||||
class MetavoxelLOD {
|
||||
STREAMABLE
|
||||
|
||||
|
@ -46,6 +47,7 @@ public:
|
|||
|
||||
bool isValid() const { return threshold > 0.0f; }
|
||||
|
||||
/// Checks whether, according to this LOD, we should subdivide the described voxel.
|
||||
bool shouldSubdivide(const glm::vec3& minimum, float size, float multiplier = 1.0f) const;
|
||||
|
||||
/// Checks whether the node or any of the nodes underneath it have had subdivision enabled as compared to the reference.
|
||||
|
@ -54,7 +56,8 @@ public:
|
|||
|
||||
DECLARE_STREAMABLE_METATYPE(MetavoxelLOD)
|
||||
|
||||
/// The base metavoxel representation shared between server and client.
|
||||
/// The base metavoxel representation shared between server and client. Contains a size (for all dimensions) and a set of
|
||||
/// octrees for different attributes.
|
||||
class MetavoxelData {
|
||||
public:
|
||||
|
||||
|
@ -64,30 +67,38 @@ public:
|
|||
|
||||
MetavoxelData& operator=(const MetavoxelData& other);
|
||||
|
||||
/// Sets the size in all dimensions.
|
||||
void setSize(float size) { _size = size; }
|
||||
float getSize() const { return _size; }
|
||||
|
||||
/// Returns the minimum extent of the octrees (which are centered about the origin).
|
||||
glm::vec3 getMinimum() const { return glm::vec3(_size, _size, _size) * -0.5f; }
|
||||
|
||||
/// Returns the bounds of the octrees.
|
||||
Box getBounds() const;
|
||||
|
||||
/// Applies the specified visitor to the contained voxels.
|
||||
void guide(MetavoxelVisitor& visitor);
|
||||
|
||||
/// Inserts a spanner into the specified attribute layer.
|
||||
void insert(const AttributePointer& attribute, const SharedObjectPointer& object);
|
||||
void insert(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
/// Removes a spanner from the specified attribute layer.
|
||||
void remove(const AttributePointer& attribute, const SharedObjectPointer& object);
|
||||
void remove(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
/// Toggles the existence of a spanner in the specified attribute layer (removes if present, adds if not).
|
||||
void toggle(const AttributePointer& attribute, const SharedObjectPointer& object);
|
||||
void toggle(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
|
||||
|
||||
/// Replaces a spanner in the specified attribute layer.
|
||||
void replace(const AttributePointer& attribute, const SharedObjectPointer& oldObject,
|
||||
const SharedObjectPointer& newObject);
|
||||
void replace(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& oldObject,
|
||||
const SharedObjectPointer& newObject);
|
||||
|
||||
|
||||
/// Clears all data in the specified attribute layer.
|
||||
void clear(const AttributePointer& attribute);
|
||||
|
||||
/// Convenience function that finds the first spanner intersecting the provided ray.
|
||||
|
@ -97,7 +108,7 @@ public:
|
|||
/// Sets part of the data.
|
||||
void set(const glm::vec3& minimum, const MetavoxelData& data, bool blend = false);
|
||||
|
||||
/// Expands the tree, increasing its capacity in all dimensions.
|
||||
/// Expands the tree, doubling its size in all dimensions (that is, increasing its volume eightfold).
|
||||
void expand();
|
||||
|
||||
void read(Bitstream& in, const MetavoxelLOD& lod = MetavoxelLOD());
|
||||
|
@ -110,6 +121,10 @@ public:
|
|||
MetavoxelNode* getRoot(const AttributePointer& attribute) const { return _roots.value(attribute); }
|
||||
MetavoxelNode* createRoot(const AttributePointer& attribute);
|
||||
|
||||
/// Performs a deep comparison between this data and the specified other (as opposed to the == operator, which does a
|
||||
/// shallow comparison).
|
||||
bool deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod = MetavoxelLOD()) const;
|
||||
|
||||
bool operator==(const MetavoxelData& other) const;
|
||||
bool operator!=(const MetavoxelData& other) const;
|
||||
|
||||
|
@ -198,6 +213,14 @@ public:
|
|||
|
||||
void clearChildren(const AttributePointer& attribute);
|
||||
|
||||
/// Performs a deep comparison between this and the specified other node.
|
||||
bool deepEquals(const AttributePointer& attribute, const MetavoxelNode& other,
|
||||
const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const;
|
||||
|
||||
/// Retrieves all spanners satisfying the LOD constraint, placing them in the provided set.
|
||||
void getSpanners(const AttributePointer& attribute, const glm::vec3& minimum,
|
||||
float size, const MetavoxelLOD& lod, SharedObjectSet& results) const;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(MetavoxelNode)
|
||||
|
||||
|
@ -234,6 +257,9 @@ public:
|
|||
/// Encodes a visitation order sequence that visits each child as sorted along the specified direction.
|
||||
static int encodeOrder(const glm::vec3& direction);
|
||||
|
||||
/// Returns a random visitation order sequence.
|
||||
static int encodeRandomOrder();
|
||||
|
||||
/// The default visitation order.
|
||||
static const int DEFAULT_ORDER;
|
||||
|
||||
|
|
|
@ -84,11 +84,19 @@ bool SharedObject::equals(const SharedObject* other, bool sharedAncestry) const
|
|||
if (metaObject != other->metaObject() && !sharedAncestry) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (property.isStored() && property.read(this) != property.read(other)) {
|
||||
// use the streamer, if we have one
|
||||
const ObjectStreamer* streamer = Bitstream::getObjectStreamer(metaObject);
|
||||
if (streamer) {
|
||||
if (!streamer->equal(this, other)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < metaObject->propertyCount(); i++) {
|
||||
QMetaProperty property = metaObject->property(i);
|
||||
if (property.isStored() && property.read(this) != property.read(other)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
QList<QByteArray> dynamicPropertyNames = this->dynamicPropertyNames();
|
||||
if (dynamicPropertyNames.size() != other->dynamicPropertyNames().size()) {
|
||||
|
|
|
@ -766,7 +766,7 @@ bool findShapeCollisionsOp(OctreeElement* element, void* extraData) {
|
|||
// coarse check against bounds
|
||||
AACube cube = element->getAACube();
|
||||
cube.scale(TREE_SCALE);
|
||||
if (!cube.expandedContains(args->shape->getPosition(), args->shape->getBoundingRadius())) {
|
||||
if (!cube.expandedContains(args->shape->getTranslation(), args->shape->getBoundingRadius())) {
|
||||
return false;
|
||||
}
|
||||
if (!element->isLeaf()) {
|
||||
|
|
|
@ -354,7 +354,7 @@ void OctreeEditPacketSender::processNackPacket(const QByteArray& packet) {
|
|||
// read number of sequence numbers
|
||||
uint16_t numSequenceNumbers = (*(uint16_t*)dataAt);
|
||||
dataAt += sizeof(uint16_t);
|
||||
|
||||
|
||||
// read sequence numbers and queue packets for resend
|
||||
for (int i = 0; i < numSequenceNumbers; i++) {
|
||||
unsigned short int sequenceNumber = (*(unsigned short int*)dataAt);
|
||||
|
|
|
@ -202,14 +202,13 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
|
|||
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
|
||||
AvatarData* avatar = avatarPointer.data();
|
||||
|
||||
// use a very generous bounding radius since the arms can stretch
|
||||
float totalRadius = 2.f * avatar->getBoundingRadius() + radius;
|
||||
float totalRadius = avatar->getBoundingRadius() + radius;
|
||||
glm::vec3 relativePosition = center - avatar->getPosition();
|
||||
if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (avatar->findParticleCollisions(center, radius, _collisions)) {
|
||||
if (avatar->findSphereCollisions(center, radius, _collisions)) {
|
||||
int numCollisions = _collisions.size();
|
||||
for (int i = 0; i < numCollisions; ++i) {
|
||||
CollisionInfo* collision = _collisions.getCollision(i);
|
||||
|
@ -222,25 +221,6 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
|
|||
if (glm::dot(relativeVelocity, collision->_penetration) <= 0.f) {
|
||||
// only collide when particle and collision point are moving toward each other
|
||||
// (doing this prevents some "collision snagging" when particle penetrates the object)
|
||||
|
||||
// HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against them.
|
||||
if (collision->_type == COLLISION_TYPE_PADDLE_HAND) {
|
||||
// NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle.
|
||||
// TODO: make this less hacky when we have more per-collision details
|
||||
float elasticity = ELASTICITY;
|
||||
float attenuationFactor = glm::length(collision->_addedVelocity) / HALTING_SPEED;
|
||||
float damping = DAMPING;
|
||||
if (attenuationFactor < 1.f) {
|
||||
collision->_addedVelocity *= attenuationFactor;
|
||||
elasticity *= attenuationFactor;
|
||||
// NOTE: the math below keeps the damping piecewise continuous,
|
||||
// while ramping it up to 1 when attenuationFactor = 0
|
||||
damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
|
||||
}
|
||||
collision->_damping = damping;
|
||||
}
|
||||
// HACK END
|
||||
|
||||
updateCollisionSound(particle, collision->_penetration, COLLISION_FREQUENCY);
|
||||
collision->_penetration /= (float)(TREE_SCALE);
|
||||
particle->applyHardCollision(*collision);
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
#include "SharedUtil.h"
|
||||
|
||||
|
||||
// default axis of CapsuleShape is Y-axis
|
||||
const glm::vec3 localAxis(0.0f, 1.0f, 0.0f);
|
||||
|
||||
CapsuleShape::CapsuleShape() : Shape(Shape::CAPSULE_SHAPE), _radius(0.0f), _halfHeight(0.0f) {}
|
||||
|
||||
CapsuleShape::CapsuleShape(float radius, float halfHeight) : Shape(Shape::CAPSULE_SHAPE),
|
||||
|
@ -40,17 +37,17 @@ CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm:
|
|||
|
||||
/// \param[out] startPoint is the center of start cap
|
||||
void CapsuleShape::getStartPoint(glm::vec3& startPoint) const {
|
||||
startPoint = _position - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
|
||||
startPoint = _translation - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
|
||||
}
|
||||
|
||||
/// \param[out] endPoint is the center of the end cap
|
||||
void CapsuleShape::getEndPoint(glm::vec3& endPoint) const {
|
||||
endPoint = _position + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
|
||||
endPoint = _translation + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
|
||||
}
|
||||
|
||||
void CapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {
|
||||
// default axis of a capsule is along the yAxis
|
||||
axis = _rotation * glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
axis = _rotation * DEFAULT_CAPSULE_AXIS;
|
||||
}
|
||||
|
||||
void CapsuleShape::setRadius(float radius) {
|
||||
|
@ -71,17 +68,12 @@ void CapsuleShape::setRadiusAndHalfHeight(float radius, float halfHeight) {
|
|||
|
||||
void CapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint) {
|
||||
glm::vec3 axis = endPoint - startPoint;
|
||||
_position = 0.5f * (endPoint + startPoint);
|
||||
_translation = 0.5f * (endPoint + startPoint);
|
||||
float height = glm::length(axis);
|
||||
if (height > EPSILON) {
|
||||
_halfHeight = 0.5f * height;
|
||||
axis /= height;
|
||||
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
|
||||
float angle = glm::angle(axis, yAxis);
|
||||
if (angle > EPSILON) {
|
||||
axis = glm::normalize(glm::cross(yAxis, axis));
|
||||
_rotation = glm::angleAxis(angle, axis);
|
||||
}
|
||||
computeNewRotation(axis);
|
||||
}
|
||||
updateBoundingRadius();
|
||||
}
|
||||
|
@ -94,3 +86,13 @@ bool CapsuleShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec
|
|||
// TODO: implement the raycast to return inside surface intersection for the internal rayStart.
|
||||
return findRayCapsuleIntersection(rayStart, rayDirection, capsuleStart, capsuleEnd, _radius, distance);
|
||||
}
|
||||
|
||||
// static
|
||||
glm::quat CapsuleShape::computeNewRotation(const glm::vec3& newAxis) {
|
||||
float angle = glm::angle(newAxis, DEFAULT_CAPSULE_AXIS);
|
||||
if (angle > EPSILON) {
|
||||
glm::vec3 rotationAxis = glm::normalize(glm::cross(DEFAULT_CAPSULE_AXIS, newAxis));
|
||||
return glm::angleAxis(angle, rotationAxis);
|
||||
}
|
||||
return glm::quat();
|
||||
}
|
||||
|
|
|
@ -14,7 +14,11 @@
|
|||
|
||||
#include "Shape.h"
|
||||
|
||||
#include "SharedUtil.h"
|
||||
|
||||
// default axis of CapsuleShape is Y-axis
|
||||
const glm::vec3 DEFAULT_CAPSULE_AXIS(0.0f, 1.0f, 0.0f);
|
||||
|
||||
|
||||
class CapsuleShape : public Shape {
|
||||
public:
|
||||
|
@ -23,26 +27,33 @@ public:
|
|||
CapsuleShape(float radius, float halfHeight, const glm::vec3& position, const glm::quat& rotation);
|
||||
CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint);
|
||||
|
||||
virtual ~CapsuleShape() {}
|
||||
|
||||
float getRadius() const { return _radius; }
|
||||
float getHalfHeight() const { return _halfHeight; }
|
||||
virtual float getHalfHeight() const { return _halfHeight; }
|
||||
|
||||
/// \param[out] startPoint is the center of start cap
|
||||
void getStartPoint(glm::vec3& startPoint) const;
|
||||
virtual void getStartPoint(glm::vec3& startPoint) const;
|
||||
|
||||
/// \param[out] endPoint is the center of the end cap
|
||||
void getEndPoint(glm::vec3& endPoint) const;
|
||||
virtual void getEndPoint(glm::vec3& endPoint) const;
|
||||
|
||||
void computeNormalizedAxis(glm::vec3& axis) const;
|
||||
virtual void computeNormalizedAxis(glm::vec3& axis) const;
|
||||
|
||||
void setRadius(float radius);
|
||||
void setHalfHeight(float height);
|
||||
void setRadiusAndHalfHeight(float radius, float height);
|
||||
void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint);
|
||||
virtual void setHalfHeight(float height);
|
||||
virtual void setRadiusAndHalfHeight(float radius, float height);
|
||||
|
||||
/// 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;
|
||||
|
||||
virtual float getVolume() const { return (PI * _radius * _radius) * (1.3333333333f * _radius + getHalfHeight()); }
|
||||
|
||||
protected:
|
||||
void updateBoundingRadius() { _boundingRadius = _radius + _halfHeight; }
|
||||
virtual void updateBoundingRadius() { _boundingRadius = _radius + getHalfHeight(); }
|
||||
static glm::quat computeNewRotation(const glm::vec3& newAxis);
|
||||
|
||||
float _radius;
|
||||
float _halfHeight;
|
||||
|
|
|
@ -11,6 +11,20 @@
|
|||
|
||||
#include "CollisionInfo.h"
|
||||
|
||||
#include "Shape.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
CollisionInfo::CollisionInfo() :
|
||||
_data(NULL),
|
||||
_intData(0),
|
||||
_shapeA(NULL),
|
||||
_shapeB(NULL),
|
||||
_damping(0.f),
|
||||
_elasticity(1.f),
|
||||
_contactPoint(0.f),
|
||||
_penetration(0.f),
|
||||
_addedVelocity(0.f) {
|
||||
}
|
||||
|
||||
CollisionList::CollisionList(int maxSize) :
|
||||
_maxSize(maxSize),
|
||||
|
@ -18,6 +32,29 @@ CollisionList::CollisionList(int maxSize) :
|
|||
_collisions.resize(_maxSize);
|
||||
}
|
||||
|
||||
void CollisionInfo::apply() {
|
||||
assert(_shapeA);
|
||||
// NOTE: Shape::computeEffectiveMass() has side effects: computes and caches partial Lagrangian coefficients
|
||||
Shape* shapeA = const_cast<Shape*>(_shapeA);
|
||||
float massA = shapeA->computeEffectiveMass(_penetration, _contactPoint);
|
||||
float massB = MAX_SHAPE_MASS;
|
||||
float totalMass = massA + massB;
|
||||
if (_shapeB) {
|
||||
Shape* shapeB = const_cast<Shape*>(_shapeB);
|
||||
massB = shapeB->computeEffectiveMass(-_penetration, _contactPoint - _penetration);
|
||||
totalMass = massA + massB;
|
||||
if (totalMass < EPSILON) {
|
||||
massA = massB = 1.0f;
|
||||
totalMass = 2.0f;
|
||||
}
|
||||
// remember that _penetration points from A into B
|
||||
shapeB->accumulateDelta(massA / totalMass, _penetration);
|
||||
}
|
||||
// NOTE: Shape::accumulateDelta() uses the coefficients from previous call to Shape::computeEffectiveMass()
|
||||
// remember that _penetration points from A into B
|
||||
shapeA->accumulateDelta(massB / totalMass, -_penetration);
|
||||
}
|
||||
|
||||
CollisionInfo* CollisionList::getNewCollision() {
|
||||
// return pointer to existing CollisionInfo, or NULL of list is full
|
||||
return (_size < _maxSize) ? &(_collisions[_size++]) : NULL;
|
||||
|
@ -38,17 +75,17 @@ CollisionInfo* CollisionList::getLastCollision() {
|
|||
}
|
||||
|
||||
void CollisionList::clear() {
|
||||
// we rely on the external context to properly set or clear the data members of a collision
|
||||
// whenever it is used.
|
||||
// we rely on the external context to properly set or clear the data members of CollisionInfos
|
||||
/*
|
||||
for (int i = 0; i < _size; ++i) {
|
||||
// we only clear the important stuff
|
||||
CollisionInfo& collision = _collisions[i];
|
||||
collision._type = COLLISION_TYPE_UNKNOWN;
|
||||
//collision._data = NULL;
|
||||
//collision._intData = 0;
|
||||
//collision._floatDAta = 0.0f;
|
||||
//collision._vecData = glm::vec3(0.0f);
|
||||
//collision._shapeA = NULL;
|
||||
//collision._shapeB = NULL;
|
||||
//collision._damping;
|
||||
//collision._elasticity;
|
||||
//collision._contactPoint;
|
||||
|
|
|
@ -17,16 +17,7 @@
|
|||
|
||||
#include <QVector>
|
||||
|
||||
enum CollisionType {
|
||||
COLLISION_TYPE_UNKNOWN = 0,
|
||||
COLLISION_TYPE_PADDLE_HAND,
|
||||
COLLISION_TYPE_MODEL,
|
||||
// _data = pointer to Model that owns joint
|
||||
// _intData = joint index
|
||||
COLLISION_TYPE_AACUBE,
|
||||
// _floatData = cube side
|
||||
// _vecData = cube center
|
||||
};
|
||||
class Shape;
|
||||
|
||||
const quint32 COLLISION_GROUP_ENVIRONMENT = 1U << 0;
|
||||
const quint32 COLLISION_GROUP_AVATARS = 1U << 1;
|
||||
|
@ -41,38 +32,24 @@ const quint32 VALID_COLLISION_GROUPS = 0x0f;
|
|||
|
||||
class CollisionInfo {
|
||||
public:
|
||||
CollisionInfo()
|
||||
: _type(0),
|
||||
_data(NULL),
|
||||
_intData(0),
|
||||
_damping(0.f),
|
||||
_elasticity(1.f),
|
||||
_contactPoint(0.f),
|
||||
_penetration(0.f),
|
||||
_addedVelocity(0.f) {
|
||||
}
|
||||
|
||||
CollisionInfo(qint32 type)
|
||||
: _type(type),
|
||||
_data(NULL),
|
||||
_intData(0),
|
||||
_damping(0.f),
|
||||
_elasticity(1.f),
|
||||
_contactPoint(0.f),
|
||||
_penetration(0.f),
|
||||
_addedVelocity(0.f) {
|
||||
}
|
||||
|
||||
CollisionInfo();
|
||||
~CollisionInfo() {}
|
||||
|
||||
int _type; // type of Collision
|
||||
|
||||
// the value of the *Data fields depend on the type
|
||||
// TODO: Andrew to get rid of these data members
|
||||
void* _data;
|
||||
int _intData;
|
||||
float _floatData;
|
||||
glm::vec3 _vecData;
|
||||
|
||||
/// accumulates position changes for the shapes in this collision to resolve penetration
|
||||
void apply();
|
||||
|
||||
Shape* getShapeA() const { return const_cast<Shape*>(_shapeA); }
|
||||
Shape* getShapeB() const { return const_cast<Shape*>(_shapeB); }
|
||||
|
||||
const Shape* _shapeA; // pointer to shapeA in this collision
|
||||
const Shape* _shapeB; // pointer to shapeB in this collision
|
||||
|
||||
float _damping; // range [0,1] of friction coeficient
|
||||
float _elasticity; // range [0,1] of energy conservation
|
||||
glm::vec3 _contactPoint; // world-frame point on BodyA that is deepest into BodyB
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
// ListShapeEntry
|
||||
|
||||
void ListShapeEntry::updateTransform(const glm::vec3& rootPosition, const glm::quat& rootRotation) {
|
||||
_shape->setPosition(rootPosition + rootRotation * _localPosition);
|
||||
_shape->setTranslation(rootPosition + rootRotation * _localPosition);
|
||||
_shape->setRotation(_localRotation * rootRotation);
|
||||
}
|
||||
|
||||
|
@ -24,9 +24,9 @@ ListShape::~ListShape() {
|
|||
clear();
|
||||
}
|
||||
|
||||
void ListShape::setPosition(const glm::vec3& position) {
|
||||
void ListShape::setTranslation(const glm::vec3& position) {
|
||||
_subShapeTransformsAreDirty = true;
|
||||
Shape::setPosition(position);
|
||||
Shape::setTranslation(position);
|
||||
}
|
||||
|
||||
void ListShape::setRotation(const glm::quat& rotation) {
|
||||
|
@ -44,7 +44,7 @@ const Shape* ListShape::getSubShape(int index) const {
|
|||
void ListShape::updateSubTransforms() {
|
||||
if (_subShapeTransformsAreDirty) {
|
||||
for (int i = 0; i < _subShapeEntries.size(); ++i) {
|
||||
_subShapeEntries[i].updateTransform(_position, _rotation);
|
||||
_subShapeEntries[i].updateTransform(_translation, _rotation);
|
||||
}
|
||||
_subShapeTransformsAreDirty = false;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public:
|
|||
|
||||
~ListShape();
|
||||
|
||||
void setPosition(const glm::vec3& position);
|
||||
void setTranslation(const glm::vec3& position);
|
||||
void setRotation(const glm::quat& rotation);
|
||||
|
||||
const Shape* getSubShape(int index) const;
|
||||
|
|
211
libraries/shared/src/PhysicsEntity.cpp
Normal file
211
libraries/shared/src/PhysicsEntity.cpp
Normal file
|
@ -0,0 +1,211 @@
|
|||
//
|
||||
// PhysicsEntity.cpp
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.06.11
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "PhysicsEntity.h"
|
||||
|
||||
#include "PhysicsSimulation.h"
|
||||
#include "Shape.h"
|
||||
#include "ShapeCollider.h"
|
||||
|
||||
PhysicsEntity::PhysicsEntity() :
|
||||
_translation(0.0f),
|
||||
_rotation(),
|
||||
_boundingRadius(0.0f),
|
||||
_shapesAreDirty(true),
|
||||
_enableShapes(false),
|
||||
_simulation(NULL) {
|
||||
}
|
||||
|
||||
PhysicsEntity::~PhysicsEntity() {
|
||||
if (_simulation) {
|
||||
_simulation->removeEntity(this);
|
||||
_simulation = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEntity::setTranslation(const glm::vec3& translation) {
|
||||
if (_translation != translation) {
|
||||
_shapesAreDirty = !_shapes.isEmpty();
|
||||
_translation = translation;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEntity::setRotation(const glm::quat& rotation) {
|
||||
if (_rotation != rotation) {
|
||||
_shapesAreDirty = !_shapes.isEmpty();
|
||||
_rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEntity::setShapeBackPointers() {
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
Shape* shape = _shapes[i];
|
||||
if (shape) {
|
||||
shape->setEntity(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEntity::setEnableShapes(bool enable) {
|
||||
if (enable != _enableShapes) {
|
||||
clearShapes();
|
||||
_enableShapes = enable;
|
||||
if (_enableShapes) {
|
||||
buildShapes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEntity::clearShapes() {
|
||||
for (int i = 0; i < _shapes.size(); ++i) {
|
||||
delete _shapes[i];
|
||||
}
|
||||
_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::findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions) {
|
||||
bool collided = false;
|
||||
int numTheirShapes = shapes.size();
|
||||
for (int i = 0; i < numTheirShapes; ++i) {
|
||||
const Shape* theirShape = shapes[i];
|
||||
if (!theirShape) {
|
||||
continue;
|
||||
}
|
||||
int numOurShapes = _shapes.size();
|
||||
for (int j = 0; j < numOurShapes; ++j) {
|
||||
const Shape* ourShape = _shapes.at(j);
|
||||
if (ourShape && ShapeCollider::collideShapes(theirShape, ourShape, collisions)) {
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return collided;
|
||||
}
|
||||
|
||||
bool PhysicsEntity::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions) {
|
||||
bool collided = false;
|
||||
SphereShape sphere(sphereRadius, sphereCenter);
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
Shape* shape = _shapes[i];
|
||||
if (!shape) {
|
||||
continue;
|
||||
}
|
||||
if (ShapeCollider::collideShapes(&sphere, shape, collisions)) {
|
||||
CollisionInfo* collision = collisions.getLastCollision();
|
||||
collision->_data = (void*)(this);
|
||||
collision->_intData = i;
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
return collided;
|
||||
}
|
||||
|
||||
bool PhysicsEntity::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
|
||||
bool collided = false;
|
||||
PlaneShape planeShape(plane);
|
||||
for (int i = 0; i < _shapes.size(); i++) {
|
||||
if (_shapes.at(i) && ShapeCollider::collideShapes(&planeShape, _shapes.at(i), collisions)) {
|
||||
CollisionInfo* collision = collisions.getLastCollision();
|
||||
collision->_data = (void*)(this);
|
||||
collision->_intData = i;
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
return collided;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// TODO: enforce this maximum when shapes are actually built. The gotcha here is
|
||||
// that the Model class (derived from PhysicsEntity) expects numShapes == numJoints,
|
||||
// so we have to modify that code to be safe.
|
||||
const int MAX_SHAPES_PER_ENTITY = 256;
|
||||
|
||||
// the first 256 prime numbers
|
||||
const int primes[256] = {
|
||||
2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
|
||||
31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
|
||||
73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
|
||||
127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
|
||||
179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
|
||||
233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
|
||||
283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
|
||||
353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
|
||||
419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
|
||||
467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
|
||||
547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
|
||||
607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
|
||||
661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
|
||||
739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
|
||||
811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
|
||||
877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
|
||||
947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013,
|
||||
1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
|
||||
1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151,
|
||||
1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
|
||||
1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
|
||||
1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
|
||||
1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451,
|
||||
1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
|
||||
1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583,
|
||||
1597, 1601, 1607, 1609, 1613, 1619 };
|
||||
|
||||
void PhysicsEntity::disableCollisions(int shapeIndexA, int shapeIndexB) {
|
||||
if (shapeIndexA < MAX_SHAPES_PER_ENTITY && shapeIndexB < MAX_SHAPES_PER_ENTITY) {
|
||||
_disabledCollisions.insert(primes[shapeIndexA] * primes[shapeIndexB]);
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicsEntity::collisionsAreEnabled(int shapeIndexA, int shapeIndexB) const {
|
||||
if (shapeIndexA < MAX_SHAPES_PER_ENTITY && shapeIndexB < MAX_SHAPES_PER_ENTITY) {
|
||||
return !_disabledCollisions.contains(primes[shapeIndexA] * primes[shapeIndexB]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PhysicsEntity::disableCurrentSelfCollisions() {
|
||||
CollisionList collisions(10);
|
||||
int numShapes = _shapes.size();
|
||||
for (int i = 0; i < numShapes; ++i) {
|
||||
const Shape* shape = _shapes.at(i);
|
||||
if (!shape) {
|
||||
continue;
|
||||
}
|
||||
for (int j = i+1; j < numShapes; ++j) {
|
||||
if (!collisionsAreEnabled(i, j)) {
|
||||
continue;
|
||||
}
|
||||
const Shape* otherShape = _shapes.at(j);
|
||||
if (otherShape && ShapeCollider::collideShapes(shape, otherShape, collisions)) {
|
||||
disableCollisions(i, j);
|
||||
collisions.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
libraries/shared/src/PhysicsEntity.h
Normal file
78
libraries/shared/src/PhysicsEntity.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// PhysicsEntity.h
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.05.30
|
||||
// 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_PhysicsEntity_h
|
||||
#define hifi_PhysicsEntity_h
|
||||
|
||||
#include <QVector>
|
||||
#include <QSet>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include "CollisionInfo.h"
|
||||
|
||||
class Shape;
|
||||
class PhysicsSimulation;
|
||||
|
||||
// PhysicsEntity is the base class for anything that owns one or more Shapes that collide in a
|
||||
// PhysicsSimulation. Each CollisionInfo generated by a PhysicsSimulation has back pointers to the
|
||||
// two Shapes involved, and those Shapes may (optionally) have valid back pointers to their PhysicsEntity.
|
||||
|
||||
class PhysicsEntity {
|
||||
|
||||
public:
|
||||
PhysicsEntity();
|
||||
virtual ~PhysicsEntity();
|
||||
|
||||
void setTranslation(const glm::vec3& translation);
|
||||
void setRotation(const glm::quat& rotation);
|
||||
|
||||
const glm::vec3& getTranslation() const { return _translation; }
|
||||
const glm::quat& getRotation() const { return _rotation; }
|
||||
float getBoundingRadius() const { return _boundingRadius; }
|
||||
|
||||
void setShapeBackPointers();
|
||||
|
||||
void setEnableShapes(bool enable);
|
||||
|
||||
virtual void buildShapes() = 0;
|
||||
virtual void clearShapes();
|
||||
const QVector<Shape*> getShapes() const { return _shapes; }
|
||||
|
||||
PhysicsSimulation* getSimulation() const { return _simulation; }
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) 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);
|
||||
|
||||
void disableCollisions(int shapeIndexA, int shapeIndexB);
|
||||
bool collisionsAreEnabled(int shapeIndexA, int shapeIndexB) const;
|
||||
|
||||
void disableCurrentSelfCollisions();
|
||||
|
||||
protected:
|
||||
glm::vec3 _translation;
|
||||
glm::quat _rotation;
|
||||
float _boundingRadius;
|
||||
bool _shapesAreDirty;
|
||||
bool _enableShapes;
|
||||
QVector<Shape*> _shapes;
|
||||
QSet<int> _disabledCollisions;
|
||||
|
||||
private:
|
||||
// PhysicsSimulation is a friend so that it can set the protected _simulation backpointer
|
||||
friend class PhysicsSimulation;
|
||||
PhysicsSimulation* _simulation;
|
||||
};
|
||||
|
||||
#endif // hifi_PhysicsEntity_h
|
244
libraries/shared/src/PhysicsSimulation.cpp
Normal file
244
libraries/shared/src/PhysicsSimulation.cpp
Normal file
|
@ -0,0 +1,244 @@
|
|||
//
|
||||
// PhysicsSimulation.cpp
|
||||
// interface/src/avatar
|
||||
//
|
||||
// Created by Andrew Meadows 2014.06.06
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include "PhysicsSimulation.h"
|
||||
|
||||
#include "PhysicsEntity.h"
|
||||
#include "Ragdoll.h"
|
||||
#include "SharedUtil.h"
|
||||
#include "ShapeCollider.h"
|
||||
|
||||
int MAX_DOLLS_PER_SIMULATION = 16;
|
||||
int MAX_ENTITIES_PER_SIMULATION = 64;
|
||||
int MAX_COLLISIONS_PER_SIMULATION = 256;
|
||||
|
||||
|
||||
const int NUM_SHAPE_BITS = 6;
|
||||
const int SHAPE_INDEX_MASK = (1 << (NUM_SHAPE_BITS + 1)) - 1;
|
||||
|
||||
PhysicsSimulation::PhysicsSimulation() : _collisionList(MAX_COLLISIONS_PER_SIMULATION),
|
||||
_numIterations(0), _numCollisions(0), _constraintError(0.0f), _stepTime(0) {
|
||||
}
|
||||
|
||||
PhysicsSimulation::~PhysicsSimulation() {
|
||||
// entities have a backpointer to this simulator that must be cleaned up
|
||||
int numEntities = _entities.size();
|
||||
for (int i = 0; i < numEntities; ++i) {
|
||||
_entities[i]->_simulation = NULL;
|
||||
}
|
||||
_entities.clear();
|
||||
|
||||
// but Ragdolls do not
|
||||
_dolls.clear();
|
||||
}
|
||||
|
||||
bool PhysicsSimulation::addEntity(PhysicsEntity* entity) {
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
if (entity->_simulation == this) {
|
||||
int numEntities = _entities.size();
|
||||
for (int i = 0; i < numEntities; ++i) {
|
||||
if (entity == _entities.at(i)) {
|
||||
// already in list
|
||||
assert(entity->_simulation == this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// belongs to some other simulation
|
||||
return false;
|
||||
}
|
||||
int numEntities = _entities.size();
|
||||
if (numEntities > MAX_ENTITIES_PER_SIMULATION) {
|
||||
// list is full
|
||||
return false;
|
||||
}
|
||||
// add to list
|
||||
entity->_simulation = this;
|
||||
_entities.push_back(entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PhysicsSimulation::removeEntity(PhysicsEntity* entity) {
|
||||
if (!entity || !entity->_simulation || !(entity->_simulation == this)) {
|
||||
return;
|
||||
}
|
||||
int numEntities = _entities.size();
|
||||
for (int i = 0; i < numEntities; ++i) {
|
||||
if (entity == _entities.at(i)) {
|
||||
if (i == numEntities - 1) {
|
||||
// remove it
|
||||
_entities.pop_back();
|
||||
} else {
|
||||
// swap the last for this one
|
||||
PhysicsEntity* lastEntity = _entities[numEntities - 1];
|
||||
_entities.pop_back();
|
||||
_entities[i] = lastEntity;
|
||||
}
|
||||
entity->_simulation = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicsSimulation::addRagdoll(Ragdoll* doll) {
|
||||
if (!doll) {
|
||||
return false;
|
||||
}
|
||||
int numDolls = _dolls.size();
|
||||
if (numDolls > MAX_DOLLS_PER_SIMULATION) {
|
||||
// list is full
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < numDolls; ++i) {
|
||||
if (doll == _dolls[i]) {
|
||||
// already in list
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// add to list
|
||||
_dolls.push_back(doll);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PhysicsSimulation::removeRagdoll(Ragdoll* doll) {
|
||||
int numDolls = _dolls.size();
|
||||
for (int i = 0; i < numDolls; ++i) {
|
||||
if (doll == _dolls[i]) {
|
||||
if (i == numDolls - 1) {
|
||||
// remove it
|
||||
_dolls.pop_back();
|
||||
} else {
|
||||
// swap the last for this one
|
||||
Ragdoll* lastDoll = _dolls[numDolls - 1];
|
||||
_dolls.pop_back();
|
||||
_dolls[i] = lastDoll;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Andrew to implement:
|
||||
// DONE (1) joints pull points (SpecialCapsuleShape would help solve this)
|
||||
// DONE (2) points slam shapes (SpecialCapsuleShape would help solve this)
|
||||
// DONE (3) detect collisions
|
||||
// DONE (4) collisions move points (SpecialCapsuleShape would help solve this)
|
||||
// DONE (5) enforce constraints
|
||||
// DONE (6) make sure MyAvatar creates shapes, adds to simulation with ragdoll support
|
||||
// DONE (7) support for pairwise collision bypass
|
||||
// DONE (8) process collisions
|
||||
// DONE (8a) stubbery
|
||||
// DONE (8b) shapes actually accumulate movement
|
||||
// DONE (9) verify that avatar shapes self collide
|
||||
// (10) slave rendered SkeletonModel to physical shapes
|
||||
// (10a) give SkeletonModel duplicate JointState data
|
||||
// (10b) figure out how to slave dupe JointStates to physical shapes
|
||||
// (11) add and enforce angular contraints for joints
|
||||
void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIterations, quint64 maxUsec) {
|
||||
quint64 now = usecTimestampNow();
|
||||
quint64 startTime = now;
|
||||
quint64 expiry = startTime + maxUsec;
|
||||
|
||||
moveRagdolls(deltaTime);
|
||||
|
||||
int numDolls = _dolls.size();
|
||||
_numCollisions = 0;
|
||||
int iterations = 0;
|
||||
float error = 0.0f;
|
||||
do {
|
||||
computeCollisions();
|
||||
processCollisions();
|
||||
|
||||
// enforce constraints
|
||||
error = 0.0f;
|
||||
for (int i = 0; i < numDolls; ++i) {
|
||||
error = glm::max(error, _dolls[i]->enforceRagdollConstraints());
|
||||
}
|
||||
++iterations;
|
||||
|
||||
now = usecTimestampNow();
|
||||
} while (_numCollisions != 0 && (iterations < maxIterations) && (error > minError) && (now < expiry));
|
||||
|
||||
_numIterations = iterations;
|
||||
_constraintError = error;
|
||||
_stepTime = usecTimestampNow()- startTime;
|
||||
|
||||
#ifdef ANDREW_DEBUG
|
||||
// temporary debug info for watching simulation performance
|
||||
static int adebug = 0; ++adebug;
|
||||
if (0 == (adebug % 100)) {
|
||||
std::cout << "adebug Ni = " << _numIterations << " E = " << error << " t = " << _stepTime << std::endl; // adebug
|
||||
}
|
||||
#endif // ANDREW_DEBUG
|
||||
}
|
||||
|
||||
void PhysicsSimulation::moveRagdolls(float deltaTime) {
|
||||
int numDolls = _dolls.size();
|
||||
for (int i = 0; i < numDolls; ++i) {
|
||||
_dolls.at(i)->stepRagdollForward(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsSimulation::computeCollisions() {
|
||||
_collisionList.clear();
|
||||
// TODO: keep track of QSet<PhysicsEntity*> collidedEntities;
|
||||
int numEntities = _entities.size();
|
||||
for (int i = 0; i < numEntities; ++i) {
|
||||
PhysicsEntity* entity = _entities.at(i);
|
||||
const QVector<Shape*> shapes = entity->getShapes();
|
||||
int numShapes = shapes.size();
|
||||
// collide with self
|
||||
for (int j = 0; j < numShapes; ++j) {
|
||||
const Shape* shape = shapes.at(j);
|
||||
if (!shape) {
|
||||
continue;
|
||||
}
|
||||
for (int k = j+1; k < numShapes; ++k) {
|
||||
const Shape* otherShape = shapes.at(k);
|
||||
if (otherShape && entity->collisionsAreEnabled(j, k)) {
|
||||
ShapeCollider::collideShapes(shape, otherShape, _collisionList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collide with others
|
||||
for (int j = i+1; j < numEntities; ++j) {
|
||||
const QVector<Shape*> otherShapes = _entities.at(j)->getShapes();
|
||||
ShapeCollider::collideShapesWithShapes(shapes, otherShapes, _collisionList);
|
||||
}
|
||||
}
|
||||
_numCollisions = _collisionList.size();
|
||||
}
|
||||
|
||||
void PhysicsSimulation::processCollisions() {
|
||||
// walk all collisions, accumulate movement on shapes, and build a list of affected shapes
|
||||
QSet<Shape*> shapes;
|
||||
int numCollisions = _collisionList.size();
|
||||
for (int i = 0; i < numCollisions; ++i) {
|
||||
CollisionInfo* collision = _collisionList.getCollision(i);
|
||||
collision->apply();
|
||||
// there is always a shapeA
|
||||
shapes.insert(collision->getShapeA());
|
||||
// but need to check for valid shapeB
|
||||
if (collision->_shapeB) {
|
||||
shapes.insert(collision->getShapeB());
|
||||
}
|
||||
}
|
||||
// walk all affected shapes and apply accumulated movement
|
||||
QSet<Shape*>::const_iterator shapeItr = shapes.constBegin();
|
||||
while (shapeItr != shapes.constEnd()) {
|
||||
(*shapeItr)->applyAccumulatedDelta();
|
||||
++shapeItr;
|
||||
}
|
||||
}
|
60
libraries/shared/src/PhysicsSimulation.h
Normal file
60
libraries/shared/src/PhysicsSimulation.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// PhysicsSimulation.h
|
||||
// interface/src/avatar
|
||||
//
|
||||
// Created by Andrew Meadows 2014.06.06
|
||||
// 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_PhysicsSimulation
|
||||
#define hifi_PhysicsSimulation
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include "CollisionInfo.h"
|
||||
|
||||
class PhysicsEntity;
|
||||
class Ragdoll;
|
||||
|
||||
class PhysicsSimulation {
|
||||
public:
|
||||
|
||||
PhysicsSimulation();
|
||||
~PhysicsSimulation();
|
||||
|
||||
/// \return true if entity was added to or is already in the list
|
||||
bool addEntity(PhysicsEntity* entity);
|
||||
|
||||
void removeEntity(PhysicsEntity* entity);
|
||||
|
||||
/// \return true if doll was added to or is already in the list
|
||||
bool addRagdoll(Ragdoll* doll);
|
||||
|
||||
void removeRagdoll(Ragdoll* doll);
|
||||
|
||||
/// \param minError constraint motion below this value is considered "close enough"
|
||||
/// \param maxIterations max number of iterations before giving up
|
||||
/// \param maxUsec max number of usec to spend enforcing constraints
|
||||
/// \return distance of largest movement
|
||||
void stepForward(float deltaTime, float minError, int maxIterations, quint64 maxUsec);
|
||||
|
||||
void moveRagdolls(float deltaTime);
|
||||
void computeCollisions();
|
||||
void processCollisions();
|
||||
|
||||
private:
|
||||
CollisionList _collisionList;
|
||||
QVector<PhysicsEntity*> _entities;
|
||||
QVector<Ragdoll*> _dolls;
|
||||
|
||||
// some stats
|
||||
int _numIterations;
|
||||
int _numCollisions;
|
||||
float _constraintError;
|
||||
quint64 _stepTime;
|
||||
};
|
||||
|
||||
#endif // hifi_PhysicsSimulation
|
|
@ -18,7 +18,7 @@ PlaneShape::PlaneShape(const glm::vec4& coefficients) :
|
|||
Shape(Shape::PLANE_SHAPE) {
|
||||
|
||||
glm::vec3 normal = glm::vec3(coefficients);
|
||||
_position = -normal * coefficients.w;
|
||||
_translation = -normal * coefficients.w;
|
||||
|
||||
float angle = acosf(glm::dot(normal, UNROTATED_NORMAL));
|
||||
if (angle > EPSILON) {
|
||||
|
@ -36,7 +36,7 @@ glm::vec3 PlaneShape::getNormal() const {
|
|||
|
||||
glm::vec4 PlaneShape::getCoefficients() const {
|
||||
glm::vec3 normal = _rotation * UNROTATED_NORMAL;
|
||||
return glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _position));
|
||||
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 {
|
||||
|
@ -44,9 +44,9 @@ bool PlaneShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3&
|
|||
float denominator = glm::dot(n, rayDirection);
|
||||
if (fabsf(denominator) < EPSILON) {
|
||||
// line is parallel to plane
|
||||
return glm::dot(_position - rayStart, n) < EPSILON;
|
||||
return glm::dot(_translation - rayStart, n) < EPSILON;
|
||||
} else {
|
||||
float d = glm::dot(_position - rayStart, n) / denominator;
|
||||
float d = glm::dot(_translation - rayStart, n) / denominator;
|
||||
if (d > 0.0f) {
|
||||
// ray points toward plane
|
||||
distance = d;
|
||||
|
|
121
libraries/shared/src/Ragdoll.cpp
Normal file
121
libraries/shared/src/Ragdoll.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// Ragdoll.cpp
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.05.30
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Ragdoll.h"
|
||||
|
||||
#include "CapsuleShape.h"
|
||||
#include "CollisionInfo.h"
|
||||
#include "SharedUtil.h"
|
||||
#include "SphereShape.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// VerletPoint
|
||||
// ----------------------------------------------------------------------------
|
||||
void VerletPoint::accumulateDelta(const glm::vec3& delta) {
|
||||
_accumulatedDelta += delta;
|
||||
++_numDeltas;
|
||||
}
|
||||
|
||||
void VerletPoint::applyAccumulatedDelta() {
|
||||
if (_numDeltas > 0) {
|
||||
_position += _accumulatedDelta / (float)_numDeltas;
|
||||
_accumulatedDelta = glm::vec3(0.0f);
|
||||
_numDeltas = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// FixedConstraint
|
||||
// ----------------------------------------------------------------------------
|
||||
FixedConstraint::FixedConstraint(VerletPoint* point, const glm::vec3& anchor) : _point(point), _anchor(anchor) {
|
||||
}
|
||||
|
||||
float FixedConstraint::enforce() {
|
||||
assert(_point != NULL);
|
||||
// TODO: use fast approximate sqrt here
|
||||
float distance = glm::distance(_anchor, _point->_position);
|
||||
_point->_position = _anchor;
|
||||
return distance;
|
||||
}
|
||||
|
||||
void FixedConstraint::setPoint(VerletPoint* point) {
|
||||
assert(point);
|
||||
_point = point;
|
||||
_point->_mass = MAX_SHAPE_MASS;
|
||||
}
|
||||
|
||||
void FixedConstraint::setAnchor(const glm::vec3& anchor) {
|
||||
_anchor = anchor;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DistanceConstraint
|
||||
// ----------------------------------------------------------------------------
|
||||
DistanceConstraint::DistanceConstraint(VerletPoint* startPoint, VerletPoint* endPoint) : _distance(-1.0f) {
|
||||
_points[0] = startPoint;
|
||||
_points[1] = endPoint;
|
||||
_distance = glm::distance(_points[0]->_position, _points[1]->_position);
|
||||
}
|
||||
|
||||
DistanceConstraint::DistanceConstraint(const DistanceConstraint& other) {
|
||||
_distance = other._distance;
|
||||
_points[0] = other._points[0];
|
||||
_points[1] = other._points[1];
|
||||
}
|
||||
|
||||
void DistanceConstraint::setDistance(float distance) {
|
||||
_distance = fabsf(distance);
|
||||
}
|
||||
|
||||
float DistanceConstraint::enforce() {
|
||||
// TODO: use a fast distance approximation
|
||||
float newDistance = glm::distance(_points[0]->_position, _points[1]->_position);
|
||||
glm::vec3 direction(0.0f, 1.0f, 0.0f);
|
||||
if (newDistance > EPSILON) {
|
||||
direction = (_points[0]->_position - _points[1]->_position) / newDistance;
|
||||
}
|
||||
glm::vec3 center = 0.5f * (_points[0]->_position + _points[1]->_position);
|
||||
_points[0]->_position = center + (0.5f * _distance) * direction;
|
||||
_points[1]->_position = center - (0.5f * _distance) * direction;
|
||||
return glm::abs(newDistance - _distance);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Ragdoll
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
Ragdoll::Ragdoll() {
|
||||
}
|
||||
|
||||
Ragdoll::~Ragdoll() {
|
||||
clearRagdollConstraintsAndPoints();
|
||||
}
|
||||
|
||||
void Ragdoll::clearRagdollConstraintsAndPoints() {
|
||||
int numConstraints = _ragdollConstraints.size();
|
||||
for (int i = 0; i < numConstraints; ++i) {
|
||||
delete _ragdollConstraints[i];
|
||||
}
|
||||
_ragdollConstraints.clear();
|
||||
_ragdollPoints.clear();
|
||||
}
|
||||
|
||||
float Ragdoll::enforceRagdollConstraints() {
|
||||
float maxDistance = 0.0f;
|
||||
const int numConstraints = _ragdollConstraints.size();
|
||||
for (int i = 0; i < numConstraints; ++i) {
|
||||
DistanceConstraint* c = static_cast<DistanceConstraint*>(_ragdollConstraints[i]);
|
||||
//maxDistance = glm::max(maxDistance, _ragdollConstraints[i]->enforce());
|
||||
maxDistance = glm::max(maxDistance, c->enforce());
|
||||
}
|
||||
return maxDistance;
|
||||
}
|
||||
|
107
libraries/shared/src/Ragdoll.h
Normal file
107
libraries/shared/src/Ragdoll.h
Normal file
|
@ -0,0 +1,107 @@
|
|||
//
|
||||
// Ragdoll.h
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.05.30
|
||||
// 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_Ragdoll_h
|
||||
#define hifi_Ragdoll_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <QVector>
|
||||
|
||||
class Shape;
|
||||
|
||||
// TODO: Andrew to move VerletPoint class to its own file
|
||||
class VerletPoint {
|
||||
public:
|
||||
VerletPoint() : _position(0.0f), _lastPosition(0.0f), _mass(1.0f), _accumulatedDelta(0.0f), _numDeltas(0) {}
|
||||
|
||||
void accumulateDelta(const glm::vec3& delta);
|
||||
void applyAccumulatedDelta();
|
||||
|
||||
glm::vec3 getAccumulatedDelta() const {
|
||||
glm::vec3 foo(0.0f);
|
||||
if (_numDeltas > 0) {
|
||||
foo = _accumulatedDelta / (float)_numDeltas;
|
||||
}
|
||||
return foo;
|
||||
}
|
||||
|
||||
glm::vec3 _position;
|
||||
glm::vec3 _lastPosition;
|
||||
float _mass;
|
||||
|
||||
private:
|
||||
glm::vec3 _accumulatedDelta;
|
||||
int _numDeltas;
|
||||
};
|
||||
|
||||
class Constraint {
|
||||
public:
|
||||
Constraint() {}
|
||||
virtual ~Constraint() {}
|
||||
|
||||
/// Enforce contraint by moving relevant points.
|
||||
/// \return max distance of point movement
|
||||
virtual float enforce() = 0;
|
||||
|
||||
protected:
|
||||
int _type;
|
||||
};
|
||||
|
||||
class FixedConstraint : public Constraint {
|
||||
public:
|
||||
FixedConstraint(VerletPoint* point, const glm::vec3& anchor);
|
||||
float enforce();
|
||||
void setPoint(VerletPoint* point);
|
||||
void setAnchor(const glm::vec3& anchor);
|
||||
private:
|
||||
VerletPoint* _point;
|
||||
glm::vec3 _anchor;
|
||||
};
|
||||
|
||||
class DistanceConstraint : public Constraint {
|
||||
public:
|
||||
DistanceConstraint(VerletPoint* startPoint, VerletPoint* endPoint);
|
||||
DistanceConstraint(const DistanceConstraint& other);
|
||||
float enforce();
|
||||
void setDistance(float distance);
|
||||
float getDistance() const { return _distance; }
|
||||
private:
|
||||
float _distance;
|
||||
VerletPoint* _points[2];
|
||||
};
|
||||
|
||||
class Ragdoll {
|
||||
public:
|
||||
|
||||
Ragdoll();
|
||||
virtual ~Ragdoll();
|
||||
|
||||
virtual void stepRagdollForward(float deltaTime) = 0;
|
||||
|
||||
/// \return max distance of point movement
|
||||
float enforceRagdollConstraints();
|
||||
|
||||
// both const and non-const getPoints()
|
||||
const QVector<VerletPoint>& getRagdollPoints() const { return _ragdollPoints; }
|
||||
QVector<VerletPoint>& getRagdollPoints() { return _ragdollPoints; }
|
||||
|
||||
protected:
|
||||
void clearRagdollConstraintsAndPoints();
|
||||
virtual void initRagdollPoints() = 0;
|
||||
virtual void buildRagdollConstraints() = 0;
|
||||
|
||||
QVector<VerletPoint> _ragdollPoints;
|
||||
QVector<Constraint*> _ragdollConstraints;
|
||||
};
|
||||
|
||||
#endif // hifi_Ragdoll_h
|
|
@ -15,47 +15,76 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
class PhysicsEntity;
|
||||
|
||||
const float MAX_SHAPE_MASS = 1.0e18f; // something less than sqrt(FLT_MAX)
|
||||
|
||||
class Shape {
|
||||
public:
|
||||
|
||||
enum Type{
|
||||
UNKNOWN_SHAPE = 0,
|
||||
SPHERE_SHAPE,
|
||||
CAPSULE_SHAPE,
|
||||
PLANE_SHAPE,
|
||||
BOX_SHAPE,
|
||||
LIST_SHAPE
|
||||
};
|
||||
|
||||
Shape() : _type(UNKNOWN_SHAPE), _boundingRadius(0.f), _position(0.f), _rotation() { }
|
||||
Shape() : _type(UNKNOWN_SHAPE), _owningEntity(NULL), _boundingRadius(0.f), _translation(0.f), _rotation(), _mass(MAX_SHAPE_MASS) { }
|
||||
virtual ~Shape() {}
|
||||
|
||||
int getType() const { return _type; }
|
||||
float getBoundingRadius() const { return _boundingRadius; }
|
||||
const glm::vec3& getPosition() const { return _position; }
|
||||
const glm::quat& getRotation() const { return _rotation; }
|
||||
|
||||
virtual void setPosition(const glm::vec3& position) { _position = position; }
|
||||
void setEntity(PhysicsEntity* entity) { _owningEntity = entity; }
|
||||
PhysicsEntity* getEntity() const { return _owningEntity; }
|
||||
|
||||
float getBoundingRadius() const { return _boundingRadius; }
|
||||
|
||||
virtual const glm::quat& getRotation() const { return _rotation; }
|
||||
virtual void setRotation(const glm::quat& rotation) { _rotation = rotation; }
|
||||
|
||||
virtual void setTranslation(const glm::vec3& translation) { _translation = translation; }
|
||||
virtual const glm::vec3& getTranslation() const { return _translation; }
|
||||
|
||||
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;
|
||||
|
||||
/// \param penetration of collision
|
||||
/// \param contactPoint of collision
|
||||
/// \return the effective mass for the collision
|
||||
/// For most shapes has side effects: computes and caches the partial Lagrangian coefficients which will be
|
||||
/// used in the next accumulateDelta() call.
|
||||
virtual float computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint) { return _mass; }
|
||||
|
||||
/// \param relativeMassFactor the final ingredient for partial Lagrangian coefficients from computeEffectiveMass()
|
||||
/// \param penetration the delta movement
|
||||
virtual void accumulateDelta(float relativeMassFactor, const glm::vec3& penetration) {}
|
||||
|
||||
virtual void applyAccumulatedDelta() {}
|
||||
|
||||
/// \return volume of shape in cubic meters
|
||||
virtual float getVolume() const { return 1.0; }
|
||||
|
||||
protected:
|
||||
// these ctors are protected (used by derived classes only)
|
||||
Shape(Type type) : _type(type), _boundingRadius(0.f), _position(0.f), _rotation() {}
|
||||
Shape(Type type) : _type(type), _owningEntity(NULL), _boundingRadius(0.f), _translation(0.f), _rotation() {}
|
||||
|
||||
Shape(Type type, const glm::vec3& position)
|
||||
: _type(type), _boundingRadius(0.f), _position(position), _rotation() {}
|
||||
: _type(type), _owningEntity(NULL), _boundingRadius(0.f), _translation(position), _rotation() {}
|
||||
|
||||
Shape(Type type, const glm::vec3& position, const glm::quat& rotation)
|
||||
: _type(type), _boundingRadius(0.f), _position(position), _rotation(rotation) {}
|
||||
: _type(type), _owningEntity(NULL), _boundingRadius(0.f), _translation(position), _rotation(rotation) {}
|
||||
|
||||
void setBoundingRadius(float radius) { _boundingRadius = radius; }
|
||||
|
||||
int _type;
|
||||
PhysicsEntity* _owningEntity;
|
||||
float _boundingRadius;
|
||||
glm::vec3 _position;
|
||||
glm::vec3 _translation;
|
||||
glm::quat _rotation;
|
||||
float _mass;
|
||||
};
|
||||
|
||||
#endif // hifi_Shape_h
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
namespace ShapeCollider {
|
||||
|
||||
bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) {
|
||||
// ATM we only have two shape types so we just check every case.
|
||||
// TODO: make a fast lookup for correct method
|
||||
int typeA = shapeA->getType();
|
||||
int typeB = shapeB->getType();
|
||||
|
@ -74,7 +73,7 @@ bool collideShapesCoarse(const QVector<const Shape*>& shapesA, const QVector<con
|
|||
tempCollisions.clear();
|
||||
foreach (const Shape* shapeA, shapesA) {
|
||||
foreach (const Shape* shapeB, shapesB) {
|
||||
ShapeCollider::collideShapes(shapeA, shapeB, tempCollisions);
|
||||
collideShapes(shapeA, shapeB, tempCollisions);
|
||||
}
|
||||
}
|
||||
if (tempCollisions.size() > 0) {
|
||||
|
@ -87,11 +86,52 @@ bool collideShapesCoarse(const QVector<const Shape*>& shapesA, const QVector<con
|
|||
}
|
||||
collision._penetration = totalPenetration;
|
||||
collision._contactPoint = averageContactPoint / (float)(tempCollisions.size());
|
||||
// there are no valid shape pointers for this collision so we set them NULL
|
||||
collision._shapeA = NULL;
|
||||
collision._shapeB = NULL;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool collideShapeWithShapes(const Shape* shapeA, const QVector<Shape*>& shapes, int startIndex, CollisionList& collisions) {
|
||||
bool collided = false;
|
||||
if (shapeA) {
|
||||
int numShapes = shapes.size();
|
||||
for (int i = startIndex; i < numShapes; ++i) {
|
||||
const Shape* shapeB = shapes.at(i);
|
||||
if (!shapeB) {
|
||||
continue;
|
||||
}
|
||||
if (collideShapes(shapeA, shapeB, collisions)) {
|
||||
collided = true;
|
||||
if (collisions.isFull()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return collided;
|
||||
}
|
||||
|
||||
bool collideShapesWithShapes(const QVector<Shape*>& shapesA, const QVector<Shape*>& shapesB, CollisionList& collisions) {
|
||||
bool collided = false;
|
||||
int numShapesA = shapesA.size();
|
||||
for (int i = 0; i < numShapesA; ++i) {
|
||||
Shape* shapeA = shapesA.at(i);
|
||||
if (!shapeA) {
|
||||
continue;
|
||||
}
|
||||
if (collideShapeWithShapes(shapeA, shapesB, 0, collisions)) {
|
||||
collided = true;
|
||||
if (collisions.isFull()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return collided;
|
||||
}
|
||||
|
||||
bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
|
||||
int typeA = shapeA->getType();
|
||||
if (typeA == Shape::SPHERE_SHAPE) {
|
||||
|
@ -116,7 +156,7 @@ bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, fl
|
|||
}
|
||||
|
||||
bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions) {
|
||||
glm::vec3 BA = sphereB->getPosition() - sphereA->getPosition();
|
||||
glm::vec3 BA = sphereB->getTranslation() - sphereA->getTranslation();
|
||||
float distanceSquared = glm::dot(BA, BA);
|
||||
float totalRadius = sphereA->getRadius() + sphereB->getRadius();
|
||||
if (distanceSquared < totalRadius * totalRadius) {
|
||||
|
@ -132,10 +172,11 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis
|
|||
// penetration points from A into B
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (collision) {
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_penetration = BA * (totalRadius - distance);
|
||||
// contactPoint is on surface of A
|
||||
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * BA;
|
||||
collision->_contactPoint = sphereA->getTranslation() + sphereA->getRadius() * BA;
|
||||
collision->_shapeA = sphereA;
|
||||
collision->_shapeB = sphereB;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +185,7 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis
|
|||
|
||||
bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions) {
|
||||
// find sphereA's closest approach to axis of capsuleB
|
||||
glm::vec3 BA = capsuleB->getPosition() - sphereA->getPosition();
|
||||
glm::vec3 BA = capsuleB->getTranslation() - sphereA->getTranslation();
|
||||
glm::vec3 capsuleAxis;
|
||||
capsuleB->computeNormalizedAxis(capsuleAxis);
|
||||
float axialDistance = - glm::dot(BA, capsuleAxis);
|
||||
|
@ -179,8 +220,9 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
|
|||
// penetration points from A into B
|
||||
collision->_penetration = (totalRadius - radialDistance) * radialAxis; // points from A into B
|
||||
// contactPoint is on surface of sphereA
|
||||
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * radialAxis;
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_contactPoint = sphereA->getTranslation() + sphereA->getRadius() * radialAxis;
|
||||
collision->_shapeA = sphereA;
|
||||
collision->_shapeB = capsuleB;
|
||||
} else {
|
||||
// A is on B's axis, so the penetration is undefined...
|
||||
if (absAxialDistance > capsuleB->getHalfHeight()) {
|
||||
|
@ -201,8 +243,9 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
|
|||
float sign = (axialDistance > 0.0f) ? -1.0f : 1.0f;
|
||||
collision->_penetration = (sign * (totalRadius + capsuleB->getHalfHeight() - absAxialDistance)) * capsuleAxis;
|
||||
// contactPoint is on surface of sphereA
|
||||
collision->_contactPoint = sphereA->getPosition() + (sign * sphereA->getRadius()) * capsuleAxis;
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_contactPoint = sphereA->getTranslation() + (sign * sphereA->getRadius()) * capsuleAxis;
|
||||
collision->_shapeA = sphereA;
|
||||
collision->_shapeB = capsuleB;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -211,14 +254,15 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
|
|||
|
||||
bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions) {
|
||||
glm::vec3 penetration;
|
||||
if (findSpherePlanePenetration(sphereA->getPosition(), sphereA->getRadius(), planeB->getCoefficients(), penetration)) {
|
||||
if (findSpherePlanePenetration(sphereA->getTranslation(), sphereA->getRadius(), planeB->getCoefficients(), penetration)) {
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (!collision) {
|
||||
return false; // collision list is full
|
||||
}
|
||||
collision->_penetration = penetration;
|
||||
collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * glm::normalize(penetration);
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_contactPoint = sphereA->getTranslation() + sphereA->getRadius() * glm::normalize(penetration);
|
||||
collision->_shapeA = sphereA;
|
||||
collision->_shapeB = planeB;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -226,7 +270,7 @@ bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, Collision
|
|||
|
||||
bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions) {
|
||||
// find sphereB's closest approach to axis of capsuleA
|
||||
glm::vec3 AB = capsuleA->getPosition() - sphereB->getPosition();
|
||||
glm::vec3 AB = capsuleA->getTranslation() - sphereB->getTranslation();
|
||||
glm::vec3 capsuleAxis;
|
||||
capsuleA->computeNormalizedAxis(capsuleAxis);
|
||||
float axialDistance = - glm::dot(AB, capsuleAxis);
|
||||
|
@ -242,14 +286,14 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
|
|||
}
|
||||
|
||||
// closestApproach = point on capsuleA's axis that is closest to sphereB's center
|
||||
glm::vec3 closestApproach = capsuleA->getPosition() + axialDistance * capsuleAxis;
|
||||
glm::vec3 closestApproach = capsuleA->getTranslation() + axialDistance * capsuleAxis;
|
||||
|
||||
if (absAxialDistance > capsuleA->getHalfHeight()) {
|
||||
// sphere hits capsule on a cap
|
||||
// --> recompute radialAxis and closestApproach
|
||||
float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f;
|
||||
closestApproach = capsuleA->getPosition() + (sign * capsuleA->getHalfHeight()) * capsuleAxis;
|
||||
radialAxis = closestApproach - sphereB->getPosition();
|
||||
closestApproach = capsuleA->getTranslation() + (sign * capsuleA->getHalfHeight()) * capsuleAxis;
|
||||
radialAxis = closestApproach - sphereB->getTranslation();
|
||||
radialDistance2 = glm::length2(radialAxis);
|
||||
if (radialDistance2 > totalRadius2) {
|
||||
return false;
|
||||
|
@ -268,7 +312,8 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
|
|||
collision->_penetration = (radialDistance - totalRadius) * radialAxis; // points from A into B
|
||||
// contactPoint is on surface of capsuleA
|
||||
collision->_contactPoint = closestApproach - capsuleA->getRadius() * radialAxis;
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_shapeA = capsuleA;
|
||||
collision->_shapeB = sphereB;
|
||||
} else {
|
||||
// A is on B's axis, so the penetration is undefined...
|
||||
if (absAxialDistance > capsuleA->getHalfHeight()) {
|
||||
|
@ -289,7 +334,8 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
|
|||
collision->_penetration = (sign * (totalRadius + capsuleA->getHalfHeight() - absAxialDistance)) * capsuleAxis;
|
||||
// contactPoint is on surface of sphereA
|
||||
collision->_contactPoint = closestApproach + (sign * capsuleA->getRadius()) * capsuleAxis;
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_shapeA = capsuleA;
|
||||
collision->_shapeB = sphereB;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -302,8 +348,8 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
|
|||
capsuleA->computeNormalizedAxis(axisA);
|
||||
glm::vec3 axisB;
|
||||
capsuleB->computeNormalizedAxis(axisB);
|
||||
glm::vec3 centerA = capsuleA->getPosition();
|
||||
glm::vec3 centerB = capsuleB->getPosition();
|
||||
glm::vec3 centerA = capsuleA->getTranslation();
|
||||
glm::vec3 centerB = capsuleB->getTranslation();
|
||||
|
||||
// NOTE: The formula for closest approach between two lines is:
|
||||
// d = [(B - A) . (a - (a.b)b)] / (1 - (a.b)^2)
|
||||
|
@ -361,7 +407,8 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
|
|||
collision->_penetration = BA * (totalRadius - distance);
|
||||
// contactPoint is on surface of A
|
||||
collision->_contactPoint = centerA + distanceA * axisA + capsuleA->getRadius() * BA;
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_shapeA = capsuleA;
|
||||
collision->_shapeB = capsuleB;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
|
@ -427,7 +474,8 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
|
|||
// average the internal pair, and then do the math from centerB
|
||||
collision->_contactPoint = centerB + (0.5f * (points[1] + points[2])) * axisB
|
||||
+ (capsuleA->getRadius() - distance) * BA;
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_shapeA = capsuleA;
|
||||
collision->_shapeB = capsuleB;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -447,7 +495,8 @@ bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, Collis
|
|||
collision->_penetration = penetration;
|
||||
glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end;
|
||||
collision->_contactPoint = deepestEnd + capsuleA->getRadius() * glm::normalize(penetration);
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_shapeA = capsuleA;
|
||||
collision->_shapeB = planeB;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -455,15 +504,16 @@ bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, Collis
|
|||
|
||||
bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions) {
|
||||
glm::vec3 penetration;
|
||||
if (findSpherePlanePenetration(sphereB->getPosition(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) {
|
||||
if (findSpherePlanePenetration(sphereB->getTranslation(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) {
|
||||
CollisionInfo* collision = collisions.getNewCollision();
|
||||
if (!collision) {
|
||||
return false; // collision list is full
|
||||
}
|
||||
collision->_penetration = -penetration;
|
||||
collision->_contactPoint = sphereB->getPosition() +
|
||||
collision->_contactPoint = sphereB->getTranslation() +
|
||||
(sphereB->getRadius() / glm::length(penetration) - 1.0f) * penetration;
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_shapeA = planeA;
|
||||
collision->_shapeB = sphereB;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -482,7 +532,8 @@ bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, Collis
|
|||
collision->_penetration = -penetration;
|
||||
glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end;
|
||||
collision->_contactPoint = deepestEnd + (capsuleB->getRadius() / glm::length(penetration) - 1.0f) * penetration;
|
||||
collision->_type = COLLISION_TYPE_UNKNOWN;
|
||||
collision->_shapeA = planeA;
|
||||
collision->_shapeB = capsuleB;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -668,15 +719,15 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
|
|||
direction /= lengthDirection;
|
||||
|
||||
// compute collision details
|
||||
collision->_type = COLLISION_TYPE_AACUBE;
|
||||
collision->_floatData = cubeSide;
|
||||
collision->_vecData = cubeCenter;
|
||||
collision->_penetration = (halfCubeSide * lengthDirection + sphereRadius - maxBA * glm::dot(BA, direction)) * direction;
|
||||
collision->_contactPoint = sphereCenter + sphereRadius * direction;
|
||||
}
|
||||
collision->_type = COLLISION_TYPE_AACUBE;
|
||||
collision->_floatData = cubeSide;
|
||||
collision->_vecData = cubeCenter;
|
||||
collision->_shapeA = NULL;
|
||||
collision->_shapeB = NULL;
|
||||
return true;
|
||||
} else if (sphereRadius + halfCubeSide > distance) {
|
||||
// NOTE: for cocentric approximation we collide sphere and cube as two spheres which means
|
||||
|
@ -688,9 +739,10 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
|
|||
// contactPoint is on surface of A
|
||||
collision->_contactPoint = sphereCenter + collision->_penetration;
|
||||
|
||||
collision->_type = COLLISION_TYPE_AACUBE;
|
||||
collision->_floatData = cubeSide;
|
||||
collision->_vecData = cubeCenter;
|
||||
collision->_shapeA = NULL;
|
||||
collision->_shapeB = NULL;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -726,6 +778,8 @@ bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius,
|
|||
collision->_penetration = glm::dot(surfaceAB, direction) * direction;
|
||||
// contactPoint is on surface of A
|
||||
collision->_contactPoint = sphereCenter + sphereRadius * direction;
|
||||
collision->_shapeA = NULL;
|
||||
collision->_shapeB = NULL;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -738,6 +792,8 @@ bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius,
|
|||
collision->_penetration = (sphereRadius + 0.5f * cubeSide) * glm::vec3(0.0f, -1.0f, 0.0f);
|
||||
// contactPoint is on surface of A
|
||||
collision->_contactPoint = sphereCenter + collision->_penetration;
|
||||
collision->_shapeA = NULL;
|
||||
collision->_shapeB = NULL;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -746,21 +802,21 @@ bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius,
|
|||
*/
|
||||
|
||||
bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
|
||||
return sphereAACube(sphereA->getPosition(), sphereA->getRadius(), cubeCenter, cubeSide, collisions);
|
||||
return sphereAACube(sphereA->getTranslation(), sphereA->getRadius(), cubeCenter, cubeSide, collisions);
|
||||
}
|
||||
|
||||
bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
|
||||
// find nerest approach of capsule line segment to cube
|
||||
glm::vec3 capsuleAxis;
|
||||
capsuleA->computeNormalizedAxis(capsuleAxis);
|
||||
float offset = glm::dot(cubeCenter - capsuleA->getPosition(), capsuleAxis);
|
||||
float offset = glm::dot(cubeCenter - capsuleA->getTranslation(), capsuleAxis);
|
||||
float halfHeight = capsuleA->getHalfHeight();
|
||||
if (offset > halfHeight) {
|
||||
offset = halfHeight;
|
||||
} else if (offset < -halfHeight) {
|
||||
offset = -halfHeight;
|
||||
}
|
||||
glm::vec3 nearestApproach = capsuleA->getPosition() + offset * capsuleAxis;
|
||||
glm::vec3 nearestApproach = capsuleA->getTranslation() + offset * capsuleAxis;
|
||||
// collide nearest approach like a sphere at that point
|
||||
return sphereAACube(nearestApproach, capsuleA->getRadius(), cubeCenter, cubeSide, collisions);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#ifndef hifi_ShapeCollider_h
|
||||
#define hifi_ShapeCollider_h
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include "CapsuleShape.h"
|
||||
#include "CollisionInfo.h"
|
||||
#include "ListShape.h"
|
||||
|
@ -33,6 +35,9 @@ namespace ShapeCollider {
|
|||
/// \return true if any shapes collide
|
||||
bool collideShapesCoarse(const QVector<const Shape*>& shapesA, const QVector<const Shape*>& shapesB, CollisionInfo& collision);
|
||||
|
||||
bool collideShapeWithShapes(const Shape* shapeA, const QVector<Shape*>& shapes, int startIndex, CollisionList& collisions);
|
||||
bool collideShapesWithShapes(const QVector<Shape*>& shapesA, const QVector<Shape*>& shapesB, CollisionList& collisions);
|
||||
|
||||
/// \param shapeA a pointer to a shape (cannot be NULL)
|
||||
/// \param cubeCenter center of cube
|
||||
/// \param cubeSide lenght of side of cube
|
||||
|
|
|
@ -68,7 +68,7 @@ float randFloat();
|
|||
int randIntInRange (int min, int max);
|
||||
float randFloatInRange (float min,float max);
|
||||
float randomSign(); /// \return -1.0 or 1.0
|
||||
unsigned char randomColorValue(int minimum);
|
||||
unsigned char randomColorValue(int minimum = 0);
|
||||
bool randomBoolean();
|
||||
|
||||
glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha);
|
||||
|
|
|
@ -17,14 +17,14 @@ bool SphereShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3
|
|||
float r2 = _boundingRadius * _boundingRadius;
|
||||
|
||||
// compute closest approach (CA)
|
||||
float a = glm::dot(_position - rayStart, rayDirection); // a = distance from ray-start to CA
|
||||
float b2 = glm::distance2(_position, rayStart + a * rayDirection); // b2 = squared distance from sphere-center to 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
|
||||
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, _position); // d2 = squared distance from sphere-center to ray-start
|
||||
float d2 = glm::distance2(rayStart, _translation); // d2 = squared distance from sphere-center to ray-start
|
||||
if (a < 0.0f) {
|
||||
// ray points away from sphere-center
|
||||
if (d2 > r2) {
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include "Shape.h"
|
||||
|
||||
#include "SharedUtil.h"
|
||||
|
||||
class SphereShape : public Shape {
|
||||
public:
|
||||
SphereShape() : Shape(Shape::SPHERE_SHAPE) {}
|
||||
|
@ -26,11 +28,15 @@ public:
|
|||
_boundingRadius = radius;
|
||||
}
|
||||
|
||||
virtual ~SphereShape() {}
|
||||
|
||||
float getRadius() const { return _boundingRadius; }
|
||||
|
||||
void setRadius(float radius) { _boundingRadius = radius; }
|
||||
|
||||
bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const;
|
||||
|
||||
float getVolume() const { return 1.3333333333f * PI * _boundingRadius * _boundingRadius * _boundingRadius; }
|
||||
};
|
||||
|
||||
#endif // hifi_SphereShape_h
|
||||
|
|
166
libraries/shared/src/VerletCapsuleShape.cpp
Normal file
166
libraries/shared/src/VerletCapsuleShape.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
//
|
||||
// VerletCapsuleShape.cpp
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.06.16
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "VerletCapsuleShape.h"
|
||||
|
||||
#include "Ragdoll.h" // for VerletPoint
|
||||
#include "SharedUtil.h"
|
||||
|
||||
VerletCapsuleShape::VerletCapsuleShape(VerletPoint* startPoint, VerletPoint* endPoint) :
|
||||
CapsuleShape(), _startPoint(startPoint), _endPoint(endPoint), _startLagrangeCoef(0.5f), _endLagrangeCoef(0.5f) {
|
||||
assert(startPoint);
|
||||
assert(endPoint);
|
||||
_halfHeight = 0.5f * glm::distance(_startPoint->_position, _endPoint->_position);
|
||||
updateBoundingRadius();
|
||||
}
|
||||
|
||||
VerletCapsuleShape::VerletCapsuleShape(float radius, VerletPoint* startPoint, VerletPoint* endPoint) :
|
||||
CapsuleShape(radius, 1.0f), _startPoint(startPoint), _endPoint(endPoint),
|
||||
_startLagrangeCoef(0.5f), _endLagrangeCoef(0.5f) {
|
||||
assert(startPoint);
|
||||
assert(endPoint);
|
||||
_halfHeight = 0.5f * glm::distance(_startPoint->_position, _endPoint->_position);
|
||||
updateBoundingRadius();
|
||||
}
|
||||
|
||||
const glm::quat& VerletCapsuleShape::getRotation() const {
|
||||
// NOTE: The "rotation" of this shape must be computed on the fly,
|
||||
// which makes this method MUCH more more expensive than you might expect.
|
||||
glm::vec3 axis;
|
||||
computeNormalizedAxis(axis);
|
||||
VerletCapsuleShape* thisCapsule = const_cast<VerletCapsuleShape*>(this);
|
||||
thisCapsule->_rotation = computeNewRotation(axis);
|
||||
return _rotation;
|
||||
}
|
||||
|
||||
void VerletCapsuleShape::setRotation(const glm::quat& rotation) {
|
||||
// NOTE: this method will update the verlet points, which is probably not
|
||||
// what you want to do. Only call this method if you know what you're doing.
|
||||
|
||||
// update points such that they have the same center but a different axis
|
||||
glm::vec3 center = getTranslation();
|
||||
float halfHeight = getHalfHeight();
|
||||
glm::vec3 axis = rotation * DEFAULT_CAPSULE_AXIS;
|
||||
_startPoint->_position = center - halfHeight * axis;
|
||||
_endPoint->_position = center + halfHeight * axis;
|
||||
}
|
||||
|
||||
void VerletCapsuleShape::setTranslation(const glm::vec3& position) {
|
||||
// NOTE: this method will update the verlet points, which is probably not
|
||||
// what you want to do. Only call this method if you know what you're doing.
|
||||
|
||||
// update the points such that their center is at position
|
||||
glm::vec3 movement = position - getTranslation();
|
||||
_startPoint->_position += movement;
|
||||
_endPoint->_position += movement;
|
||||
}
|
||||
|
||||
const glm::vec3& VerletCapsuleShape::getTranslation() const {
|
||||
// the "translation" of this shape must be computed on the fly
|
||||
VerletCapsuleShape* thisCapsule = const_cast<VerletCapsuleShape*>(this);
|
||||
thisCapsule->_translation = 0.5f * (_startPoint->_position + _endPoint->_position);
|
||||
return _translation;
|
||||
}
|
||||
|
||||
float VerletCapsuleShape::computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint) {
|
||||
glm::vec3 startLeg = _startPoint->_position - contactPoint;
|
||||
glm::vec3 endLeg = _endPoint->_position - contactPoint;
|
||||
|
||||
// TODO: use fast approximate distance calculations here
|
||||
float startLength = glm::length(startLeg);
|
||||
float endlength = glm::length(endLeg);
|
||||
|
||||
// The raw coefficient is proportional to the other leg's length multiplied by the dot-product
|
||||
// of the penetration and this leg direction. We don't worry about the common penetration length
|
||||
// because it is normalized out later.
|
||||
float startCoef = glm::abs(glm::dot(startLeg, penetration)) * endlength / (startLength + EPSILON);
|
||||
float endCoef = glm::abs(glm::dot(endLeg, penetration)) * startLength / (endlength + EPSILON);
|
||||
|
||||
float maxCoef = glm::max(startCoef, endCoef);
|
||||
if (maxCoef > EPSILON) {
|
||||
// One of these coeficients will be 1.0, the other will be less -->
|
||||
// one endpoint will move the full amount while the other will move less.
|
||||
_startLagrangeCoef = startCoef / maxCoef;
|
||||
_endLagrangeCoef = endCoef / maxCoef;
|
||||
assert(!glm::isnan(_startLagrangeCoef));
|
||||
assert(!glm::isnan(_startLagrangeCoef));
|
||||
} else {
|
||||
// The coefficients are the same --> the collision will move both equally
|
||||
// as if the object were solid.
|
||||
_startLagrangeCoef = 1.0f;
|
||||
_endLagrangeCoef = 1.0f;
|
||||
}
|
||||
// the effective mass is the weighted sum of the two endpoints
|
||||
return _startLagrangeCoef * _startPoint->_mass + _endLagrangeCoef * _endPoint->_mass;
|
||||
}
|
||||
|
||||
void VerletCapsuleShape::accumulateDelta(float relativeMassFactor, const glm::vec3& penetration) {
|
||||
assert(!glm::isnan(relativeMassFactor));
|
||||
_startPoint->accumulateDelta(relativeMassFactor * _startLagrangeCoef * penetration);
|
||||
_endPoint->accumulateDelta(relativeMassFactor * _endLagrangeCoef * penetration);
|
||||
}
|
||||
|
||||
void VerletCapsuleShape::applyAccumulatedDelta() {
|
||||
_startPoint->applyAccumulatedDelta();
|
||||
_endPoint->applyAccumulatedDelta();
|
||||
}
|
||||
|
||||
// virtual
|
||||
float VerletCapsuleShape::getHalfHeight() const {
|
||||
return 0.5f * glm::distance(_startPoint->_position, _endPoint->_position);
|
||||
}
|
||||
|
||||
// virtual
|
||||
void VerletCapsuleShape::getStartPoint(glm::vec3& startPoint) const {
|
||||
startPoint = _startPoint->_position;
|
||||
}
|
||||
|
||||
// virtual
|
||||
void VerletCapsuleShape::getEndPoint(glm::vec3& endPoint) const {
|
||||
endPoint = _endPoint->_position;
|
||||
}
|
||||
|
||||
// virtual
|
||||
void VerletCapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {
|
||||
glm::vec3 unormalizedAxis = _endPoint->_position - _startPoint->_position;
|
||||
float fullLength = glm::length(unormalizedAxis);
|
||||
if (fullLength > EPSILON) {
|
||||
axis = unormalizedAxis / fullLength;
|
||||
} else {
|
||||
// the axis is meaningless, but we fill it with a normalized direction
|
||||
// just in case the calling context assumes it really is normalized.
|
||||
axis = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
void VerletCapsuleShape::setHalfHeight(float halfHeight) {
|
||||
// push points along axis so they are 2*halfHeight apart
|
||||
glm::vec3 center = getTranslation();
|
||||
glm::vec3 axis;
|
||||
computeNormalizedAxis(axis);
|
||||
_startPoint->_position = center - halfHeight * axis;
|
||||
_endPoint->_position = center + halfHeight * axis;
|
||||
_boundingRadius = _radius + halfHeight;
|
||||
}
|
||||
|
||||
// virtual
|
||||
void VerletCapsuleShape::setRadiusAndHalfHeight(float radius, float halfHeight) {
|
||||
_radius = radius;
|
||||
setHalfHeight(halfHeight);
|
||||
}
|
||||
|
||||
// virtual
|
||||
void VerletCapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint) {
|
||||
_startPoint->_position = startPoint;
|
||||
_endPoint->_position = endPoint;
|
||||
updateBoundingRadius();
|
||||
}
|
83
libraries/shared/src/VerletCapsuleShape.h
Normal file
83
libraries/shared/src/VerletCapsuleShape.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// VerletCapsuleShape.h
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.06.16
|
||||
// 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_VerletCapsuleShape_h
|
||||
#define hifi_VerletCapsuleShape_h
|
||||
|
||||
#include "CapsuleShape.h"
|
||||
|
||||
|
||||
// The VerletCapsuleShape is similar to a regular CapsuleShape, except it keeps a pointer
|
||||
// to its endpoints which are owned by some other data structure (a verlet simulation system).
|
||||
// This makes it easier for the points to be moved around by constraints in the system
|
||||
// as well as collisions with the shape, however it has some drawbacks:
|
||||
//
|
||||
// (1) The Shape::_translation and ::_rotation data members are not used (wasted)
|
||||
//
|
||||
// (2) A VerletShape doesn't own the points that it uses, so you must be careful not to
|
||||
// leave dangling pointers around.
|
||||
//
|
||||
// (3) Some const methods of VerletCapsuleShape are much more expensive than you might think.
|
||||
// For example getHalfHeight() and setHalfHeight() methods must do extra computation. In
|
||||
// particular setRotation() is significantly more expensive than for the CapsuleShape.
|
||||
// Not too expensive to use when setting up shapes, but you woudln't want to use it deep
|
||||
// down in a hot simulation loop, such as when processing collision results. Best to
|
||||
// just let the verlet simulation do its thing and not try to constantly force a rotation.
|
||||
|
||||
class VerletPoint;
|
||||
|
||||
class VerletCapsuleShape : public CapsuleShape {
|
||||
public:
|
||||
VerletCapsuleShape(VerletPoint* startPoint, VerletPoint* endPoint);
|
||||
VerletCapsuleShape(float radius, VerletPoint* startPoint, VerletPoint* endPoint);
|
||||
|
||||
// virtual overrides from Shape
|
||||
const glm::quat& getRotation() const;
|
||||
void setRotation(const glm::quat& rotation);
|
||||
void setTranslation(const glm::vec3& position);
|
||||
const glm::vec3& getTranslation() const;
|
||||
float computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint);
|
||||
void accumulateDelta(float relativeMassFactor, const glm::vec3& penetration);
|
||||
void applyAccumulatedDelta();
|
||||
|
||||
//float getRadius() const { return _radius; }
|
||||
virtual float getHalfHeight() const;
|
||||
|
||||
/// \param[out] startPoint is the center of start cap
|
||||
void getStartPoint(glm::vec3& startPoint) const;
|
||||
|
||||
/// \param[out] endPoint is the center of the end cap
|
||||
void getEndPoint(glm::vec3& endPoint) const;
|
||||
|
||||
/// \param[out] axis is a normalized vector that points from start to end
|
||||
void computeNormalizedAxis(glm::vec3& axis) const;
|
||||
|
||||
//void setRadius(float radius);
|
||||
void setHalfHeight(float halfHeight);
|
||||
void setRadiusAndHalfHeight(float radius, float halfHeight);
|
||||
void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint);
|
||||
|
||||
//void assignEndPoints(glm::vec3* startPoint, glm::vec3* endPoint);
|
||||
|
||||
protected:
|
||||
// NOTE: VerletCapsuleShape does NOT own the data in its points.
|
||||
VerletPoint* _startPoint;
|
||||
VerletPoint* _endPoint;
|
||||
|
||||
// The LagrangeCoef's are numerical weights for distributing collision movement
|
||||
// between the relevant VerletPoints associated with this shape. They are functions
|
||||
// of the movement parameters and are computed (and cached) in computeEffectiveMass()
|
||||
// and then used in the subsequent accumulateDelta().
|
||||
float _startLagrangeCoef;
|
||||
float _endLagrangeCoef;
|
||||
};
|
||||
|
||||
#endif // hifi_VerletCapsuleShape_h
|
50
libraries/shared/src/VerletSphereShape.cpp
Normal file
50
libraries/shared/src/VerletSphereShape.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// VerletSphereShape.cpp
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.06.16
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "VerletSphereShape.h"
|
||||
|
||||
#include "Ragdoll.h" // for VerletPoint
|
||||
|
||||
VerletSphereShape::VerletSphereShape(VerletPoint* centerPoint) : SphereShape() {
|
||||
assert(centerPoint);
|
||||
_point = centerPoint;
|
||||
}
|
||||
|
||||
VerletSphereShape::VerletSphereShape(float radius, VerletPoint* centerPoint) : SphereShape(radius) {
|
||||
assert(centerPoint);
|
||||
_point = centerPoint;
|
||||
}
|
||||
|
||||
// virtual from Shape class
|
||||
void VerletSphereShape::setTranslation(const glm::vec3& position) {
|
||||
_point->_position = position;
|
||||
_point->_lastPosition = position;
|
||||
}
|
||||
|
||||
// virtual from Shape class
|
||||
const glm::vec3& VerletSphereShape::getTranslation() const {
|
||||
return _point->_position;
|
||||
}
|
||||
|
||||
// virtual
|
||||
float VerletSphereShape::computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint) {
|
||||
return _point->_mass;
|
||||
}
|
||||
|
||||
// virtual
|
||||
void VerletSphereShape::accumulateDelta(float relativeMassFactor, const glm::vec3& penetration) {
|
||||
_point->accumulateDelta(relativeMassFactor * penetration);
|
||||
}
|
||||
|
||||
// virtual
|
||||
void VerletSphereShape::applyAccumulatedDelta() {
|
||||
_point->applyAccumulatedDelta();
|
||||
}
|
47
libraries/shared/src/VerletSphereShape.h
Normal file
47
libraries/shared/src/VerletSphereShape.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// VerletSphereShape.h
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.06.16
|
||||
// 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_VerletSphereShape_h
|
||||
#define hifi_VerletSphereShape_h
|
||||
|
||||
#include "SphereShape.h"
|
||||
|
||||
// The VerletSphereShape is similar to a regular SphereShape, except it keeps a pointer
|
||||
// to its center which is owned by some other data structure (a verlet simulation system).
|
||||
// This makes it easier for the points to be moved around by constraints in the system
|
||||
// as well as collisions with the shape, however it has some drawbacks:
|
||||
//
|
||||
// (1) The Shape::_translation data member is not used (wasted)
|
||||
//
|
||||
// (2) A VerletShape doesn't own the points that it uses, so you must be careful not to
|
||||
// leave dangling pointers around.
|
||||
|
||||
class VerletPoint;
|
||||
|
||||
class VerletSphereShape : public SphereShape {
|
||||
public:
|
||||
VerletSphereShape(VerletPoint* point);
|
||||
|
||||
VerletSphereShape(float radius, VerletPoint* centerPoint);
|
||||
|
||||
// virtual overrides from Shape
|
||||
void setTranslation(const glm::vec3& position);
|
||||
const glm::vec3& getTranslation() const;
|
||||
float computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint);
|
||||
void accumulateDelta(float relativeMassFactor, const glm::vec3& penetration);
|
||||
void applyAccumulatedDelta();
|
||||
|
||||
protected:
|
||||
// NOTE: VerletSphereShape does NOT own its _point
|
||||
VerletPoint* _point;
|
||||
};
|
||||
|
||||
#endif // hifi_VerletSphereShape_h
|
|
@ -32,6 +32,8 @@ static int datagramsSent = 0;
|
|||
static int datagramsReceived = 0;
|
||||
static int bytesSent = 0;
|
||||
static int bytesReceived = 0;
|
||||
static int maxDatagramsPerPacket = 0;
|
||||
static int maxBytesPerPacket = 0;
|
||||
static int highPriorityMessagesSent = 0;
|
||||
static int highPriorityMessagesReceived = 0;
|
||||
static int unreliableMessagesSent = 0;
|
||||
|
@ -45,6 +47,8 @@ static int sharedObjectsDestroyed = 0;
|
|||
static int objectMutationsPerformed = 0;
|
||||
static int scriptObjectsCreated = 0;
|
||||
static int scriptMutationsPerformed = 0;
|
||||
static int metavoxelMutationsPerformed = 0;
|
||||
static int spannerMutationsPerformed = 0;
|
||||
|
||||
static QByteArray createRandomBytes(int minimumSize, int maximumSize) {
|
||||
QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0);
|
||||
|
@ -321,44 +325,80 @@ static bool testSerialization(Bitstream::MetadataType metadataType) {
|
|||
}
|
||||
|
||||
bool MetavoxelTests::run() {
|
||||
|
||||
qDebug() << "Running transmission tests...";
|
||||
qDebug();
|
||||
|
||||
// seed the random number generator so that our tests are reproducible
|
||||
srand(0xBAAAAABE);
|
||||
|
||||
// create two endpoints with the same header
|
||||
// check for an optional command line argument specifying a single test
|
||||
QStringList arguments = this->arguments();
|
||||
int test = (arguments.size() > 1) ? arguments.at(1).toInt() : 0;
|
||||
|
||||
QByteArray datagramHeader("testheader");
|
||||
Endpoint alice(datagramHeader), bob(datagramHeader);
|
||||
|
||||
alice.setOther(&bob);
|
||||
bob.setOther(&alice);
|
||||
|
||||
// perform a large number of simulation iterations
|
||||
const int SIMULATION_ITERATIONS = 10000;
|
||||
for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
|
||||
if (alice.simulate(i) || bob.simulate(i)) {
|
||||
if (test == 0 || test == 1) {
|
||||
qDebug() << "Running transmission tests...";
|
||||
qDebug();
|
||||
|
||||
// create two endpoints with the same header
|
||||
Endpoint alice(datagramHeader), bob(datagramHeader);
|
||||
|
||||
alice.setOther(&bob);
|
||||
bob.setOther(&alice);
|
||||
|
||||
// perform a large number of simulation iterations
|
||||
for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
|
||||
if (alice.simulate(i) || bob.simulate(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Sent" << highPriorityMessagesSent << "high priority messages, received" << highPriorityMessagesReceived;
|
||||
qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived;
|
||||
qDebug() << "Sent" << reliableMessagesSent << "reliable messages, received" << reliableMessagesReceived;
|
||||
qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
|
||||
qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" <<
|
||||
datagramsReceived << "with" << bytesReceived << "bytes";
|
||||
qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet";
|
||||
qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed;
|
||||
qDebug() << "Performed" << objectMutationsPerformed << "object mutations";
|
||||
qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed;
|
||||
qDebug();
|
||||
}
|
||||
|
||||
if (test == 0 || test == 2) {
|
||||
qDebug() << "Running serialization tests...";
|
||||
qDebug();
|
||||
|
||||
if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Sent" << highPriorityMessagesSent << "high priority messages, received" << highPriorityMessagesReceived;
|
||||
qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived;
|
||||
qDebug() << "Sent" << reliableMessagesSent << "reliable messages, received" << reliableMessagesReceived;
|
||||
qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
|
||||
qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" <<
|
||||
datagramsReceived << "with" << bytesReceived << "bytes";
|
||||
qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed;
|
||||
qDebug() << "Performed" << objectMutationsPerformed << "object mutations";
|
||||
qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed;
|
||||
qDebug();
|
||||
if (test == 0 || test == 3) {
|
||||
qDebug() << "Running metavoxel data tests...";
|
||||
qDebug();
|
||||
|
||||
qDebug() << "Running serialization tests...";
|
||||
qDebug();
|
||||
// clear the stats
|
||||
datagramsSent = bytesSent = datagramsReceived = bytesReceived = maxDatagramsPerPacket = maxBytesPerPacket = 0;
|
||||
|
||||
if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) {
|
||||
return true;
|
||||
// create client and server endpoints
|
||||
Endpoint client(datagramHeader, Endpoint::METAVOXEL_CLIENT_MODE);
|
||||
Endpoint server(datagramHeader, Endpoint::METAVOXEL_SERVER_MODE);
|
||||
|
||||
client.setOther(&server);
|
||||
server.setOther(&client);
|
||||
|
||||
// simulate
|
||||
for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
|
||||
if (client.simulate(i) || server.simulate(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" <<
|
||||
datagramsReceived << "with" << bytesReceived << "bytes";
|
||||
qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet";
|
||||
qDebug() << "Performed" << metavoxelMutationsPerformed << "metavoxel mutations," << spannerMutationsPerformed <<
|
||||
"spanner mutations";
|
||||
}
|
||||
|
||||
qDebug() << "All tests passed!";
|
||||
|
@ -375,7 +415,36 @@ static SharedObjectPointer createRandomSharedObject() {
|
|||
}
|
||||
}
|
||||
|
||||
Endpoint::Endpoint(const QByteArray& datagramHeader) :
|
||||
class RandomVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
int leafCount;
|
||||
|
||||
RandomVisitor();
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
};
|
||||
|
||||
RandomVisitor::RandomVisitor() :
|
||||
MetavoxelVisitor(QVector<AttributePointer>(),
|
||||
QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute()),
|
||||
leafCount(0) {
|
||||
}
|
||||
|
||||
const float MAXIMUM_LEAF_SIZE = 0.5f;
|
||||
const float MINIMUM_LEAF_SIZE = 0.25f;
|
||||
|
||||
int RandomVisitor::visit(MetavoxelInfo& info) {
|
||||
if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) {
|
||||
return DEFAULT_ORDER;
|
||||
}
|
||||
info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline<QRgb>(qRgb(randomColorValue(),
|
||||
randomColorValue(), randomColorValue())));
|
||||
leafCount++;
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) :
|
||||
_mode(mode),
|
||||
_sequencer(new DatagramSequencer(datagramHeader, this)),
|
||||
_highPriorityMessagesToSend(0.0f),
|
||||
_reliableMessagesToSend(0.0f) {
|
||||
|
@ -396,6 +465,25 @@ Endpoint::Endpoint(const QByteArray& datagramHeader) :
|
|||
ReceiveRecord receiveRecord = { 0 };
|
||||
_receiveRecords.append(receiveRecord);
|
||||
|
||||
if (mode == METAVOXEL_CLIENT_MODE) {
|
||||
_lod = MetavoxelLOD(glm::vec3(), 0.01f);
|
||||
return;
|
||||
}
|
||||
if (mode == METAVOXEL_SERVER_MODE) {
|
||||
_data.expand();
|
||||
_data.expand();
|
||||
|
||||
RandomVisitor visitor;
|
||||
_data.guide(visitor);
|
||||
qDebug() << "Created" << visitor.leafCount << "base leaves";
|
||||
|
||||
_data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), new Sphere());
|
||||
|
||||
_sphere = new Sphere();
|
||||
static_cast<Transformable*>(_sphere.data())->setScale(0.01f);
|
||||
_data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), _sphere);
|
||||
return;
|
||||
}
|
||||
// create the object that represents out delta-encoded state
|
||||
_localState = new TestSharedObjectA();
|
||||
|
||||
|
@ -415,7 +503,7 @@ Endpoint::Endpoint(const QByteArray& datagramHeader) :
|
|||
QByteArray bytes = createRandomBytes(MIN_STREAM_BYTES, MAX_STREAM_BYTES);
|
||||
_dataStreamed.append(bytes);
|
||||
output->getBuffer().write(bytes);
|
||||
streamedBytesSent += bytes.size();
|
||||
streamedBytesSent += bytes.size();
|
||||
}
|
||||
|
||||
static QVariant createRandomMessage() {
|
||||
|
@ -512,6 +600,37 @@ static bool messagesEqual(const QVariant& firstMessage, const QVariant& secondMe
|
|||
}
|
||||
}
|
||||
|
||||
class MutateVisitor : public MetavoxelVisitor {
|
||||
public:
|
||||
|
||||
MutateVisitor();
|
||||
virtual int visit(MetavoxelInfo& info);
|
||||
|
||||
private:
|
||||
|
||||
int _mutationsRemaining;
|
||||
};
|
||||
|
||||
MutateVisitor::MutateVisitor() :
|
||||
MetavoxelVisitor(QVector<AttributePointer>(),
|
||||
QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute()),
|
||||
_mutationsRemaining(randIntInRange(2, 4)) {
|
||||
}
|
||||
|
||||
int MutateVisitor::visit(MetavoxelInfo& info) {
|
||||
if (_mutationsRemaining <= 0) {
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) {
|
||||
return encodeRandomOrder();
|
||||
}
|
||||
info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline<QRgb>(qRgb(randomColorValue(),
|
||||
randomColorValue(), randomColorValue())));
|
||||
_mutationsRemaining--;
|
||||
metavoxelMutationsPerformed++;
|
||||
return STOP_RECURSION;
|
||||
}
|
||||
|
||||
bool Endpoint::simulate(int iterationNumber) {
|
||||
// update/send our delayed datagrams
|
||||
for (QList<QPair<QByteArray, int> >::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) {
|
||||
|
@ -525,51 +644,101 @@ bool Endpoint::simulate(int iterationNumber) {
|
|||
}
|
||||
}
|
||||
|
||||
// enqueue some number of high priority messages
|
||||
const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f;
|
||||
const float MAX_HIGH_PRIORITY_MESSAGES = 2.0f;
|
||||
_highPriorityMessagesToSend += randFloatInRange(MIN_HIGH_PRIORITY_MESSAGES, MAX_HIGH_PRIORITY_MESSAGES);
|
||||
while (_highPriorityMessagesToSend >= 1.0f) {
|
||||
QVariant message = createRandomMessage();
|
||||
_highPriorityMessagesSent.append(message);
|
||||
_sequencer->sendHighPriorityMessage(message);
|
||||
highPriorityMessagesSent++;
|
||||
_highPriorityMessagesToSend -= 1.0f;
|
||||
}
|
||||
|
||||
// and some number of reliable messages
|
||||
const float MIN_RELIABLE_MESSAGES = 0.0f;
|
||||
const float MAX_RELIABLE_MESSAGES = 4.0f;
|
||||
_reliableMessagesToSend += randFloatInRange(MIN_RELIABLE_MESSAGES, MAX_RELIABLE_MESSAGES);
|
||||
while (_reliableMessagesToSend >= 1.0f) {
|
||||
QVariant message = createRandomMessage();
|
||||
_reliableMessagesSent.append(message);
|
||||
_sequencer->getReliableOutputChannel()->sendMessage(message);
|
||||
reliableMessagesSent++;
|
||||
_reliableMessagesToSend -= 1.0f;
|
||||
}
|
||||
|
||||
// tweak the local state
|
||||
_localState = mutate(_localState);
|
||||
|
||||
// send a packet
|
||||
try {
|
||||
int oldDatagramsSent = datagramsSent;
|
||||
int oldBytesSent = bytesSent;
|
||||
if (_mode == METAVOXEL_CLIENT_MODE) {
|
||||
Bitstream& out = _sequencer->startPacket();
|
||||
SequencedTestMessage message = { iterationNumber, createRandomMessage(), _localState };
|
||||
_unreliableMessagesSent.append(message);
|
||||
unreliableMessagesSent++;
|
||||
out << message;
|
||||
|
||||
ClientStateMessage state = { _lod };
|
||||
out << QVariant::fromValue(state);
|
||||
_sequencer->endPacket();
|
||||
|
||||
// record the send
|
||||
SendRecord record = { _sequencer->getOutgoingPacketNumber(), SharedObjectPointer(), MetavoxelData(), _lod };
|
||||
_sendRecords.append(record);
|
||||
|
||||
} else if (_mode == METAVOXEL_SERVER_MODE) {
|
||||
// make a random change
|
||||
MutateVisitor visitor;
|
||||
_data.guide(visitor);
|
||||
|
||||
// perhaps mutate the spanner
|
||||
if (randomBoolean()) {
|
||||
SharedObjectPointer oldSphere = _sphere;
|
||||
_sphere = _sphere->clone(true);
|
||||
Sphere* newSphere = static_cast<Sphere*>(_sphere.data());
|
||||
if (randomBoolean()) {
|
||||
newSphere->setColor(QColor(randomColorValue(), randomColorValue(), randomColorValue()));
|
||||
} else {
|
||||
newSphere->setTranslation(newSphere->getTranslation() + glm::vec3(randFloatInRange(-0.01f, 0.01f),
|
||||
randFloatInRange(-0.01f, 0.01f), randFloatInRange(-0.01f, 0.01f)));
|
||||
}
|
||||
_data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), oldSphere, _sphere);
|
||||
spannerMutationsPerformed++;
|
||||
}
|
||||
|
||||
// wait until we have a valid lod before sending
|
||||
if (!_lod.isValid()) {
|
||||
return false;
|
||||
}
|
||||
Bitstream& out = _sequencer->startPacket();
|
||||
out << QVariant::fromValue(MetavoxelDeltaMessage());
|
||||
_data.writeDelta(_sendRecords.first().data, _sendRecords.first().lod, out, _lod);
|
||||
|
||||
// record the send
|
||||
SendRecord record = { _sequencer->getOutgoingPacketNumber() + 1, SharedObjectPointer(), _data, _lod };
|
||||
_sendRecords.append(record);
|
||||
|
||||
_sequencer->endPacket();
|
||||
|
||||
} else {
|
||||
// enqueue some number of high priority messages
|
||||
const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f;
|
||||
const float MAX_HIGH_PRIORITY_MESSAGES = 2.0f;
|
||||
_highPriorityMessagesToSend += randFloatInRange(MIN_HIGH_PRIORITY_MESSAGES, MAX_HIGH_PRIORITY_MESSAGES);
|
||||
while (_highPriorityMessagesToSend >= 1.0f) {
|
||||
QVariant message = createRandomMessage();
|
||||
_highPriorityMessagesSent.append(message);
|
||||
_sequencer->sendHighPriorityMessage(message);
|
||||
highPriorityMessagesSent++;
|
||||
_highPriorityMessagesToSend -= 1.0f;
|
||||
}
|
||||
|
||||
// and some number of reliable messages
|
||||
const float MIN_RELIABLE_MESSAGES = 0.0f;
|
||||
const float MAX_RELIABLE_MESSAGES = 4.0f;
|
||||
_reliableMessagesToSend += randFloatInRange(MIN_RELIABLE_MESSAGES, MAX_RELIABLE_MESSAGES);
|
||||
while (_reliableMessagesToSend >= 1.0f) {
|
||||
QVariant message = createRandomMessage();
|
||||
_reliableMessagesSent.append(message);
|
||||
_sequencer->getReliableOutputChannel()->sendMessage(message);
|
||||
reliableMessagesSent++;
|
||||
_reliableMessagesToSend -= 1.0f;
|
||||
}
|
||||
|
||||
// tweak the local state
|
||||
_localState = mutate(_localState);
|
||||
|
||||
// send a packet
|
||||
try {
|
||||
Bitstream& out = _sequencer->startPacket();
|
||||
SequencedTestMessage message = { iterationNumber, createRandomMessage(), _localState };
|
||||
_unreliableMessagesSent.append(message);
|
||||
unreliableMessagesSent++;
|
||||
out << message;
|
||||
_sequencer->endPacket();
|
||||
|
||||
} catch (const QString& message) {
|
||||
qDebug() << message;
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (const QString& message) {
|
||||
qDebug() << message;
|
||||
return true;
|
||||
// record the send
|
||||
SendRecord record = { _sequencer->getOutgoingPacketNumber(), _localState };
|
||||
_sendRecords.append(record);
|
||||
}
|
||||
|
||||
// record the send
|
||||
SendRecord record = { _sequencer->getOutgoingPacketNumber(), _localState };
|
||||
_sendRecords.append(record);
|
||||
|
||||
maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent);
|
||||
maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -619,6 +788,39 @@ void Endpoint::handleHighPriorityMessage(const QVariant& message) {
|
|||
}
|
||||
|
||||
void Endpoint::readMessage(Bitstream& in) {
|
||||
if (_mode == METAVOXEL_CLIENT_MODE) {
|
||||
QVariant message;
|
||||
in >> message;
|
||||
handleMessage(message, in);
|
||||
|
||||
// deep-compare data to sent version
|
||||
int packetNumber = _sequencer->getIncomingPacketNumber();
|
||||
foreach (const SendRecord& sendRecord, _other->_sendRecords) {
|
||||
if (sendRecord.packetNumber == packetNumber) {
|
||||
if (!sendRecord.data.deepEquals(_data, _sendRecords.first().lod)) {
|
||||
qDebug() << "Sent/received metavoxel data mismatch.";
|
||||
exit(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// record the receipt
|
||||
ReceiveRecord record = { packetNumber, SharedObjectPointer(), _data, _sendRecords.first().lod };
|
||||
_receiveRecords.append(record);
|
||||
return;
|
||||
}
|
||||
if (_mode == METAVOXEL_SERVER_MODE) {
|
||||
QVariant message;
|
||||
in >> message;
|
||||
handleMessage(message, in);
|
||||
|
||||
// record the receipt
|
||||
ReceiveRecord record = { _sequencer->getIncomingPacketNumber() };
|
||||
_receiveRecords.append(record);
|
||||
return;
|
||||
}
|
||||
|
||||
SequencedTestMessage message;
|
||||
in >> message;
|
||||
|
||||
|
@ -632,17 +834,20 @@ void Endpoint::readMessage(Bitstream& in) {
|
|||
it != _other->_unreliableMessagesSent.end(); it++) {
|
||||
if (it->sequenceNumber == message.sequenceNumber) {
|
||||
if (!messagesEqual(it->submessage, message.submessage)) {
|
||||
throw QString("Sent/received unreliable message mismatch.");
|
||||
qDebug() << "Sent/received unreliable message mismatch.";
|
||||
exit(true);
|
||||
}
|
||||
if (!it->state->equals(message.state)) {
|
||||
throw QString("Delta-encoded object mismatch.");
|
||||
qDebug() << "Delta-encoded object mismatch.";
|
||||
exit(true);
|
||||
}
|
||||
_other->_unreliableMessagesSent.erase(_other->_unreliableMessagesSent.begin(), it + 1);
|
||||
unreliableMessagesReceived++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw QString("Received unsent/already sent unreliable message.");
|
||||
qDebug() << "Received unsent/already sent unreliable message.";
|
||||
exit(true);
|
||||
}
|
||||
|
||||
void Endpoint::handleReliableMessage(const QVariant& message) {
|
||||
|
@ -682,6 +887,22 @@ void Endpoint::clearReceiveRecordsBefore(int index) {
|
|||
_receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1);
|
||||
}
|
||||
|
||||
void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
|
||||
int userType = message.userType();
|
||||
if (userType == ClientStateMessage::Type) {
|
||||
ClientStateMessage state = message.value<ClientStateMessage>();
|
||||
_lod = state.lod;
|
||||
|
||||
} else if (userType == MetavoxelDeltaMessage::Type) {
|
||||
_data.readDelta(_receiveRecords.first().data, _receiveRecords.first().lod, in, _sendRecords.first().lod);
|
||||
|
||||
} else if (userType == QMetaType::QVariantList) {
|
||||
foreach (const QVariant& element, message.toList()) {
|
||||
handleMessage(element, in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TestSharedObjectA::TestSharedObjectA(float foo, TestEnum baz, TestFlags bong) :
|
||||
_foo(foo),
|
||||
_baz(baz),
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QVariantList>
|
||||
|
||||
#include <DatagramSequencer.h>
|
||||
#include <MetavoxelData.h>
|
||||
#include <ScriptCache.h>
|
||||
|
||||
class SequencedTestMessage;
|
||||
|
@ -39,7 +40,9 @@ class Endpoint : public QObject {
|
|||
|
||||
public:
|
||||
|
||||
Endpoint(const QByteArray& datagramHeader);
|
||||
enum Mode { BASIC_PEER_MODE, METAVOXEL_SERVER_MODE, METAVOXEL_CLIENT_MODE };
|
||||
|
||||
Endpoint(const QByteArray& datagramHeader, Mode mode = BASIC_PEER_MODE);
|
||||
|
||||
void setOther(Endpoint* other) { _other = other; }
|
||||
|
||||
|
@ -60,18 +63,26 @@ private slots:
|
|||
|
||||
private:
|
||||
|
||||
void handleMessage(const QVariant& message, Bitstream& in);
|
||||
|
||||
class SendRecord {
|
||||
public:
|
||||
int packetNumber;
|
||||
SharedObjectPointer localState;
|
||||
MetavoxelData data;
|
||||
MetavoxelLOD lod;
|
||||
};
|
||||
|
||||
class ReceiveRecord {
|
||||
public:
|
||||
int packetNumber;
|
||||
SharedObjectPointer remoteState;
|
||||
MetavoxelData data;
|
||||
MetavoxelLOD lod;
|
||||
};
|
||||
|
||||
Mode _mode;
|
||||
|
||||
DatagramSequencer* _sequencer;
|
||||
QList<SendRecord> _sendRecords;
|
||||
QList<ReceiveRecord> _receiveRecords;
|
||||
|
@ -79,6 +90,11 @@ private:
|
|||
SharedObjectPointer _localState;
|
||||
SharedObjectPointer _remoteState;
|
||||
|
||||
MetavoxelData _data;
|
||||
MetavoxelLOD _lod;
|
||||
|
||||
SharedObjectPointer _sphere;
|
||||
|
||||
Endpoint* _other;
|
||||
QList<QPair<QByteArray, int> > _delayedDatagrams;
|
||||
float _highPriorityMessagesToSend;
|
||||
|
|
|
@ -236,6 +236,45 @@ void ModelTests::modelTreeTests(bool verbose) {
|
|||
qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs";
|
||||
}
|
||||
|
||||
{
|
||||
testsTaken++;
|
||||
QString testName = "Performance - add model to tree 10,000 times";
|
||||
if (verbose) {
|
||||
qDebug() << "Test" << testsTaken <<":" << qPrintable(testName);
|
||||
}
|
||||
|
||||
const int TEST_ITERATIONS = 10000;
|
||||
quint64 start = usecTimestampNow();
|
||||
for (int i = 0; i < TEST_ITERATIONS; i++) {
|
||||
uint32_t id = i + 2; // make sure it doesn't collide with previous model ids
|
||||
ModelItemID modelID(id);
|
||||
modelID.isKnownID = false; // this is a temporary workaround to allow local tree models to be added with known IDs
|
||||
|
||||
float randomX = randFloatInRange(0.0f ,(float)TREE_SCALE);
|
||||
float randomY = randFloatInRange(0.0f ,(float)TREE_SCALE);
|
||||
float randomZ = randFloatInRange(0.0f ,(float)TREE_SCALE);
|
||||
glm::vec3 randomPositionInMeters(randomX,randomY,randomZ);
|
||||
|
||||
properties.setPosition(randomPositionInMeters);
|
||||
properties.setRadius(halfMeter);
|
||||
properties.setModelURL("https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/theater.fbx");
|
||||
|
||||
tree.addModel(modelID, properties);
|
||||
}
|
||||
quint64 end = usecTimestampNow();
|
||||
|
||||
bool passed = true;
|
||||
if (passed) {
|
||||
testsPassed++;
|
||||
} else {
|
||||
testsFailed++;
|
||||
qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName);
|
||||
}
|
||||
float USECS_PER_MSECS = 1000.0f;
|
||||
float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS;
|
||||
qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs";
|
||||
}
|
||||
|
||||
qDebug() << " tests passed:" << testsPassed << "out of" << testsTaken;
|
||||
if (verbose) {
|
||||
qDebug() << "******************************************************************************************";
|
||||
|
|
|
@ -123,8 +123,8 @@ void ShapeColliderTests::sphereTouchesSphere() {
|
|||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 AtoB = sphereB.getPosition() - sphereA.getPosition();
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() + radiusA * glm::normalize(AtoB);
|
||||
glm::vec3 AtoB = sphereB.getTranslation() - sphereA.getTranslation();
|
||||
glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * glm::normalize(AtoB);
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -153,8 +153,8 @@ void ShapeColliderTests::sphereTouchesSphere() {
|
|||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 BtoA = sphereA.getPosition() - sphereB.getPosition();
|
||||
glm::vec3 expectedContactPoint = sphereB.getPosition() + radiusB * glm::normalize(BtoA);
|
||||
glm::vec3 BtoA = sphereA.getTranslation() - sphereB.getTranslation();
|
||||
glm::vec3 expectedContactPoint = sphereB.getTranslation() + radiusB * glm::normalize(BtoA);
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -182,7 +182,7 @@ void ShapeColliderTests::sphereMissesCapsule() {
|
|||
glm::quat rotation = glm::angleAxis(angle, axis);
|
||||
glm::vec3 translation(15.1f, -27.1f, -38.6f);
|
||||
capsuleB.setRotation(rotation);
|
||||
capsuleB.setPosition(translation);
|
||||
capsuleB.setTranslation(translation);
|
||||
|
||||
CollisionList collisions(16);
|
||||
|
||||
|
@ -193,7 +193,7 @@ void ShapeColliderTests::sphereMissesCapsule() {
|
|||
for (int i = 0; i < numberOfSteps; ++i) {
|
||||
// translate sphereA into world-frame
|
||||
glm::vec3 localPosition = localStartPosition + ((float)i * delta) * yAxis;
|
||||
sphereA.setPosition(rotation * localPosition + translation);
|
||||
sphereA.setTranslation(rotation * localPosition + translation);
|
||||
|
||||
// sphereA agains capsuleB
|
||||
if (ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
|
||||
|
@ -236,7 +236,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
|
|||
int numCollisions = 0;
|
||||
|
||||
{ // sphereA collides with capsuleB's cylindrical wall
|
||||
sphereA.setPosition(radialOffset * xAxis);
|
||||
sphereA.setTranslation(radialOffset * xAxis);
|
||||
|
||||
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
|
@ -258,7 +258,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
|
|||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() - radiusA * xAxis;
|
||||
glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * xAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -287,8 +287,8 @@ void ShapeColliderTests::sphereTouchesCapsule() {
|
|||
}
|
||||
|
||||
// contactPoint is on surface of capsuleB
|
||||
glm::vec3 BtoA = sphereA.getPosition() - capsuleB.getPosition();
|
||||
glm::vec3 closestApproach = capsuleB.getPosition() + glm::dot(BtoA, yAxis) * yAxis;
|
||||
glm::vec3 BtoA = sphereA.getTranslation() - capsuleB.getTranslation();
|
||||
glm::vec3 closestApproach = capsuleB.getTranslation() + glm::dot(BtoA, yAxis) * yAxis;
|
||||
expectedContactPoint = closestApproach + radiusB * glm::normalize(BtoA - closestApproach);
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
|
@ -299,7 +299,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
|
|||
}
|
||||
{ // sphereA hits end cap at axis
|
||||
glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
|
||||
sphereA.setPosition(axialOffset * yAxis);
|
||||
sphereA.setTranslation(axialOffset * yAxis);
|
||||
|
||||
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
|
@ -321,7 +321,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
|
|||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() - radiusA * yAxis;
|
||||
glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -362,7 +362,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
|
|||
}
|
||||
{ // sphereA hits start cap at axis
|
||||
glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
|
||||
sphereA.setPosition(axialOffset * yAxis);
|
||||
sphereA.setTranslation(axialOffset * yAxis);
|
||||
|
||||
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
|
@ -384,7 +384,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
|
|||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getPosition() + radiusA * yAxis;
|
||||
glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -441,12 +441,12 @@ void ShapeColliderTests::capsuleMissesCapsule() {
|
|||
float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
|
||||
|
||||
CapsuleShape capsuleA(radiusA, halfHeightA);
|
||||
CapsuleShape capsuleB(radiusA, halfHeightA);
|
||||
CapsuleShape capsuleB(radiusB, halfHeightB);
|
||||
|
||||
CollisionList collisions(16);
|
||||
|
||||
// side by side
|
||||
capsuleB.setPosition((1.01f * totalRadius) * xAxis);
|
||||
capsuleB.setTranslation((1.01f * totalRadius) * xAxis);
|
||||
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -461,7 +461,7 @@ void ShapeColliderTests::capsuleMissesCapsule() {
|
|||
}
|
||||
|
||||
// end to end
|
||||
capsuleB.setPosition((1.01f * totalHalfLength) * xAxis);
|
||||
capsuleB.setTranslation((1.01f * totalHalfLength) * xAxis);
|
||||
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -478,7 +478,7 @@ void ShapeColliderTests::capsuleMissesCapsule() {
|
|||
// rotate B and move it to the side
|
||||
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
capsuleB.setPosition((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
|
||||
capsuleB.setTranslation((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
|
||||
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -516,7 +516,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
|
|||
int numCollisions = 0;
|
||||
|
||||
{ // side by side
|
||||
capsuleB.setPosition((0.99f * totalRadius) * xAxis);
|
||||
capsuleB.setTranslation((0.99f * totalRadius) * xAxis);
|
||||
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -536,7 +536,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
|
|||
}
|
||||
|
||||
{ // end to end
|
||||
capsuleB.setPosition((0.99f * totalHalfLength) * yAxis);
|
||||
capsuleB.setTranslation((0.99f * totalHalfLength) * yAxis);
|
||||
|
||||
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
|
@ -559,7 +559,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
|
|||
{ // rotate B and move it to the side
|
||||
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
capsuleB.setPosition((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
|
||||
capsuleB.setTranslation((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
|
||||
|
||||
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
|
@ -584,7 +584,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
|
|||
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
glm::vec3 positionB = ((totalRadius + capsuleB.getHalfHeight()) - overlap) * xAxis;
|
||||
capsuleB.setPosition(positionB);
|
||||
capsuleB.setTranslation(positionB);
|
||||
|
||||
// capsuleA vs capsuleB
|
||||
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
|
@ -605,7 +605,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
|
|||
<< " actual = " << collision->_penetration;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContactPoint = capsuleA.getPosition() + radiusA * xAxis;
|
||||
glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * xAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -633,7 +633,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
|
|||
<< std::endl;
|
||||
}
|
||||
|
||||
expectedContactPoint = capsuleB.getPosition() - (radiusB + halfHeightB) * xAxis;
|
||||
expectedContactPoint = capsuleB.getTranslation() - (radiusB + halfHeightB) * xAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -649,7 +649,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
|
|||
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
glm::vec3 positionB = (totalRadius - overlap) * zAxis + shift * yAxis;
|
||||
capsuleB.setPosition(positionB);
|
||||
capsuleB.setTranslation(positionB);
|
||||
|
||||
// capsuleA vs capsuleB
|
||||
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
|
@ -671,7 +671,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
|
|||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContactPoint = capsuleA.getPosition() + radiusA * zAxis + shift * yAxis;
|
||||
glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * zAxis + shift * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
|
@ -708,7 +708,7 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() {
|
|||
float overlap = 0.25f;
|
||||
float sphereOffset = 0.5f * cubeSide + sphereRadius - overlap;
|
||||
sphereCenter = cubeCenter + sphereOffset * axis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
sphere.setTranslation(sphereCenter);
|
||||
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl;
|
||||
|
@ -741,7 +741,7 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() {
|
|||
float overlap = 1.25f * sphereRadius;
|
||||
float sphereOffset = 0.5f * cubeSide + sphereRadius - overlap;
|
||||
sphereCenter = cubeCenter + sphereOffset * axis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
sphere.setTranslation(sphereCenter);
|
||||
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube."
|
||||
|
@ -815,7 +815,7 @@ void ShapeColliderTests::sphereTouchesAACubeEdges() {
|
|||
float overlap = 0.25f;
|
||||
|
||||
sphereCenter = cubeCenter + (lengthAxis * 0.5f * cubeSide + sphereRadius - overlap) * axis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
sphere.setTranslation(sphereCenter);
|
||||
|
||||
if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl;
|
||||
|
@ -857,42 +857,42 @@ void ShapeColliderTests::sphereMissesAACube() {
|
|||
|
||||
// top
|
||||
sphereCenter = cubeCenter + sphereOffset * yAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
sphere.setTranslation(sphereCenter);
|
||||
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
|
||||
}
|
||||
|
||||
// bottom
|
||||
sphereCenter = cubeCenter - sphereOffset * yAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
sphere.setTranslation(sphereCenter);
|
||||
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
|
||||
}
|
||||
|
||||
// left
|
||||
sphereCenter = cubeCenter + sphereOffset * xAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
sphere.setTranslation(sphereCenter);
|
||||
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
|
||||
}
|
||||
|
||||
// right
|
||||
sphereCenter = cubeCenter - sphereOffset * xAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
sphere.setTranslation(sphereCenter);
|
||||
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
|
||||
}
|
||||
|
||||
// forward
|
||||
sphereCenter = cubeCenter + sphereOffset * zAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
sphere.setTranslation(sphereCenter);
|
||||
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
|
||||
}
|
||||
|
||||
// back
|
||||
sphereCenter = cubeCenter - sphereOffset * zAxis;
|
||||
sphere.setPosition(sphereCenter);
|
||||
sphere.setTranslation(sphereCenter);
|
||||
if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
|
||||
}
|
||||
|
@ -955,7 +955,7 @@ void ShapeColliderTests::rayHitsSphere() {
|
|||
rayDirection = rotation * unrotatedRayDirection;
|
||||
|
||||
sphere.setRadius(radius);
|
||||
sphere.setPosition(rotation * translation);
|
||||
sphere.setTranslation(rotation * translation);
|
||||
|
||||
float distance = FLT_MAX;
|
||||
if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) {
|
||||
|
@ -994,7 +994,7 @@ void ShapeColliderTests::rayBarelyHitsSphere() {
|
|||
|
||||
rayStart = rotation * (rayStart + translation);
|
||||
rayDirection = rotation * rayDirection;
|
||||
sphere.setPosition(rotation * translation);
|
||||
sphere.setTranslation(rotation * translation);
|
||||
|
||||
// ...and test again
|
||||
distance = FLT_MAX;
|
||||
|
@ -1032,7 +1032,7 @@ void ShapeColliderTests::rayBarelyMissesSphere() {
|
|||
|
||||
rayStart = rotation * (rayStart + translation);
|
||||
rayDirection = rotation * rayDirection;
|
||||
sphere.setPosition(rotation * translation);
|
||||
sphere.setTranslation(rotation * translation);
|
||||
|
||||
// ...and test again
|
||||
distance = FLT_MAX;
|
||||
|
@ -1186,7 +1186,7 @@ void ShapeColliderTests::rayHitsPlane() {
|
|||
float planeDistanceFromOrigin = 3.579;
|
||||
glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f);
|
||||
PlaneShape plane;
|
||||
plane.setPosition(planePosition);
|
||||
plane.setTranslation(planePosition);
|
||||
|
||||
// make a simple ray
|
||||
float startDistance = 1.234f;
|
||||
|
@ -1209,7 +1209,7 @@ void ShapeColliderTests::rayHitsPlane() {
|
|||
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
|
||||
glm::quat rotation = glm::angleAxis(angle, axis);
|
||||
|
||||
plane.setPosition(rotation * planePosition);
|
||||
plane.setTranslation(rotation * planePosition);
|
||||
plane.setRotation(rotation);
|
||||
rayStart = rotation * rayStart;
|
||||
rayDirection = rotation * rayDirection;
|
||||
|
@ -1231,7 +1231,7 @@ void ShapeColliderTests::rayMissesPlane() {
|
|||
float planeDistanceFromOrigin = 3.579;
|
||||
glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f);
|
||||
PlaneShape plane;
|
||||
plane.setPosition(planePosition);
|
||||
plane.setTranslation(planePosition);
|
||||
|
||||
{ // parallel rays should miss
|
||||
float startDistance = 1.234f;
|
||||
|
@ -1251,7 +1251,7 @@ void ShapeColliderTests::rayMissesPlane() {
|
|||
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
|
||||
glm::quat rotation = glm::angleAxis(angle, axis);
|
||||
|
||||
plane.setPosition(rotation * planePosition);
|
||||
plane.setTranslation(rotation * planePosition);
|
||||
plane.setRotation(rotation);
|
||||
rayStart = rotation * rayStart;
|
||||
rayDirection = rotation * rayDirection;
|
||||
|
@ -1283,7 +1283,7 @@ void ShapeColliderTests::rayMissesPlane() {
|
|||
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
|
||||
glm::quat rotation = glm::angleAxis(angle, axis);
|
||||
|
||||
plane.setPosition(rotation * planePosition);
|
||||
plane.setTranslation(rotation * planePosition);
|
||||
plane.setRotation(rotation);
|
||||
rayStart = rotation * rayStart;
|
||||
rayDirection = rotation * rayDirection;
|
||||
|
|
769
tests/physics/src/VerletShapeTests.cpp
Normal file
769
tests/physics/src/VerletShapeTests.cpp
Normal file
|
@ -0,0 +1,769 @@
|
|||
//
|
||||
// VerletShapeTests.cpp
|
||||
// tests/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows on 02/21/2014.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
//#include <stdio.h>
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <CollisionInfo.h>
|
||||
#include <Ragdoll.h> // for VerletPoint
|
||||
#include <ShapeCollider.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <VerletCapsuleShape.h>
|
||||
#include <VerletSphereShape.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include "VerletShapeTests.h"
|
||||
|
||||
const glm::vec3 origin(0.0f);
|
||||
static const glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
|
||||
static const glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
|
||||
static const glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
|
||||
|
||||
void VerletShapeTests::setSpherePosition() {
|
||||
float radius = 1.0f;
|
||||
glm::vec3 offset(1.23f, 4.56f, 7.89f);
|
||||
VerletPoint point;
|
||||
VerletSphereShape sphere(radius, &point);
|
||||
|
||||
point._position = glm::vec3(0.f);
|
||||
float d = glm::distance(glm::vec3(0.0f), sphere.getTranslation());
|
||||
if (d != 0.0f) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should be at origin" << std::endl;
|
||||
}
|
||||
|
||||
point._position = offset;
|
||||
d = glm::distance(glm::vec3(0.0f), sphere.getTranslation());
|
||||
if (d != glm::length(offset)) {
|
||||
std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should be at offset" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void VerletShapeTests::sphereMissesSphere() {
|
||||
// non-overlapping spheres of unequal size
|
||||
|
||||
float radiusA = 7.0f;
|
||||
float radiusB = 3.0f;
|
||||
float alpha = 1.2f;
|
||||
float beta = 1.3f;
|
||||
glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
|
||||
float offsetDistance = alpha * radiusA + beta * radiusB;
|
||||
|
||||
// create points for the sphere centers
|
||||
VerletPoint points[2];
|
||||
|
||||
// give pointers to the spheres
|
||||
VerletSphereShape sphereA(radiusA, (points + 0));
|
||||
VerletSphereShape sphereB(radiusB, (points + 1));
|
||||
|
||||
// set the positions of the spheres by slamming the points directly
|
||||
points[0]._position = origin;
|
||||
points[1]._position = offsetDistance * offsetDirection;
|
||||
|
||||
CollisionList collisions(16);
|
||||
|
||||
// collide A to B...
|
||||
{
|
||||
bool touching = ShapeCollider::collideShapes(&sphereA, &sphereB, collisions);
|
||||
if (touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// collide B to A...
|
||||
{
|
||||
bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
|
||||
if (touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// also test shapeShape
|
||||
{
|
||||
bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
|
||||
if (touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should NOT touch" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (collisions.size() > 0) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected empty collision list but size is " << collisions.size()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void VerletShapeTests::sphereTouchesSphere() {
|
||||
// overlapping spheres of unequal size
|
||||
float radiusA = 7.0f;
|
||||
float radiusB = 3.0f;
|
||||
float alpha = 0.2f;
|
||||
float beta = 0.3f;
|
||||
glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
|
||||
float offsetDistance = alpha * radiusA + beta * radiusB;
|
||||
float expectedPenetrationDistance = (1.0f - alpha) * radiusA + (1.0f - beta) * radiusB;
|
||||
glm::vec3 expectedPenetration = expectedPenetrationDistance * offsetDirection;
|
||||
|
||||
// create two points for the sphere centers
|
||||
VerletPoint points[2];
|
||||
|
||||
// give pointers to the spheres
|
||||
VerletSphereShape sphereA(radiusA, points+0);
|
||||
VerletSphereShape sphereB(radiusB, points+1);
|
||||
|
||||
// set the positions of the spheres by slamming the points directly
|
||||
points[0]._position = origin;
|
||||
points[1]._position = offsetDistance * offsetDirection;
|
||||
|
||||
CollisionList collisions(16);
|
||||
int numCollisions = 0;
|
||||
|
||||
// collide A to B...
|
||||
{
|
||||
bool touching = ShapeCollider::collideShapes(&sphereA, &sphereB, collisions);
|
||||
if (!touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should touch" << std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// verify state of collisions
|
||||
if (numCollisions != collisions.size()) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected collisions size of " << numCollisions << " but actual size is " << collisions.size()
|
||||
<< std::endl;
|
||||
}
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
if (!collision) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: null collision" << std::endl;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into sphereB
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 AtoB = sphereB.getTranslation() - sphereA.getTranslation();
|
||||
glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * glm::normalize(AtoB);
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint;
|
||||
}
|
||||
}
|
||||
|
||||
// collide B to A...
|
||||
{
|
||||
bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
|
||||
if (!touching) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphereA and sphereB should touch" << std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into sphereB
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
float inaccuracy = glm::length(collision->_penetration + expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 BtoA = sphereA.getTranslation() - sphereB.getTranslation();
|
||||
glm::vec3 expectedContactPoint = sphereB.getTranslation() + radiusB * glm::normalize(BtoA);
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VerletShapeTests::sphereMissesCapsule() {
|
||||
// non-overlapping sphere and capsule
|
||||
float radiusA = 1.5f;
|
||||
float radiusB = 2.3f;
|
||||
float totalRadius = radiusA + radiusB;
|
||||
float halfHeightB = 1.7f;
|
||||
float axialOffset = totalRadius + 1.1f * halfHeightB;
|
||||
float radialOffset = 1.2f * radiusA + 1.3f * radiusB;
|
||||
|
||||
// create points for the sphere + capsule
|
||||
VerletPoint points[3];
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
points[i]._position = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
// give the points to the shapes
|
||||
VerletSphereShape sphereA(radiusA, points);
|
||||
VerletCapsuleShape capsuleB(radiusB, points+1, points+2);
|
||||
capsuleB.setHalfHeight(halfHeightB);
|
||||
|
||||
// give the capsule some arbitrary transform
|
||||
float angle = 37.8f;
|
||||
glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
|
||||
glm::quat rotation = glm::angleAxis(angle, axis);
|
||||
glm::vec3 translation(15.1f, -27.1f, -38.6f);
|
||||
capsuleB.setRotation(rotation);
|
||||
capsuleB.setTranslation(translation);
|
||||
|
||||
CollisionList collisions(16);
|
||||
|
||||
// walk sphereA along the local yAxis next to, but not touching, capsuleB
|
||||
glm::vec3 localStartPosition(radialOffset, axialOffset, 0.0f);
|
||||
int numberOfSteps = 10;
|
||||
float delta = 1.3f * (totalRadius + halfHeightB) / (numberOfSteps - 1);
|
||||
for (int i = 0; i < numberOfSteps; ++i) {
|
||||
// translate sphereA into world-frame
|
||||
glm::vec3 localPosition = localStartPosition + ((float)i * delta) * yAxis;
|
||||
sphereA.setTranslation(rotation * localPosition + translation);
|
||||
|
||||
// sphereA agains capsuleB
|
||||
if (ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// capsuleB against sphereA
|
||||
if (ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (collisions.size() > 0) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected empty collision list but size is " << collisions.size()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void VerletShapeTests::sphereTouchesCapsule() {
|
||||
// overlapping sphere and capsule
|
||||
float radiusA = 2.0f;
|
||||
float radiusB = 1.0f;
|
||||
float totalRadius = radiusA + radiusB;
|
||||
float halfHeightB = 2.0f;
|
||||
float alpha = 0.5f;
|
||||
float beta = 0.5f;
|
||||
float radialOffset = alpha * radiusA + beta * radiusB;
|
||||
|
||||
// create points for the sphere + capsule
|
||||
VerletPoint points[3];
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
points[i]._position = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
// give the points to the shapes
|
||||
VerletSphereShape sphereA(radiusA, points);
|
||||
VerletCapsuleShape capsuleB(radiusB, points+1, points+2);
|
||||
capsuleB.setHalfHeight(halfHeightB);
|
||||
|
||||
CollisionList collisions(16);
|
||||
int numCollisions = 0;
|
||||
|
||||
{ // sphereA collides with capsuleB's cylindrical wall
|
||||
sphereA.setTranslation(radialOffset * xAxis);
|
||||
|
||||
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
glm::vec3 expectedPenetration = (radialOffset - totalRadius) * xAxis;
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * xAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint;
|
||||
}
|
||||
|
||||
// capsuleB collides with sphereA
|
||||
if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and sphere should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
collision = collisions.getCollision(numCollisions - 1);
|
||||
expectedPenetration = - (radialOffset - totalRadius) * xAxis;
|
||||
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of capsuleB
|
||||
glm::vec3 BtoA = sphereA.getTranslation() - capsuleB.getTranslation();
|
||||
glm::vec3 closestApproach = capsuleB.getTranslation() + glm::dot(BtoA, yAxis) * yAxis;
|
||||
expectedContactPoint = closestApproach + radiusB * glm::normalize(BtoA - closestApproach);
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint;
|
||||
}
|
||||
}
|
||||
{ // sphereA hits end cap at axis
|
||||
glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
|
||||
sphereA.setTranslation(axialOffset * yAxis);
|
||||
|
||||
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
glm::vec3 expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint;
|
||||
}
|
||||
|
||||
// capsuleB collides with sphereA
|
||||
if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and sphere should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
collision = collisions.getCollision(numCollisions - 1);
|
||||
expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
|
||||
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of capsuleB
|
||||
glm::vec3 endPoint;
|
||||
capsuleB.getEndPoint(endPoint);
|
||||
expectedContactPoint = endPoint + radiusB * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint;
|
||||
}
|
||||
}
|
||||
{ // sphereA hits start cap at axis
|
||||
glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
|
||||
sphereA.setTranslation(axialOffset * yAxis);
|
||||
|
||||
if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: sphere and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
glm::vec3 expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of sphereA
|
||||
glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint;
|
||||
}
|
||||
|
||||
// capsuleB collides with sphereA
|
||||
if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and sphere should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
// penetration points from sphereA into capsuleB
|
||||
collision = collisions.getCollision(numCollisions - 1);
|
||||
expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
|
||||
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration;
|
||||
}
|
||||
|
||||
// contactPoint is on surface of capsuleB
|
||||
glm::vec3 startPoint;
|
||||
capsuleB.getStartPoint(startPoint);
|
||||
expectedContactPoint = startPoint - radiusB * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint;
|
||||
}
|
||||
}
|
||||
if (collisions.size() != numCollisions) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected " << numCollisions << " collisions but actual number is " << collisions.size()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void VerletShapeTests::capsuleMissesCapsule() {
|
||||
// non-overlapping capsules
|
||||
float radiusA = 2.0f;
|
||||
float halfHeightA = 3.0f;
|
||||
float radiusB = 3.0f;
|
||||
float halfHeightB = 4.0f;
|
||||
|
||||
float totalRadius = radiusA + radiusB;
|
||||
float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
|
||||
|
||||
// create points for the shapes
|
||||
VerletPoint points[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
points[i]._position = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
// give the points to the shapes
|
||||
VerletCapsuleShape capsuleA(radiusA, points+0, points+1);
|
||||
VerletCapsuleShape capsuleB(radiusB, points+2, points+3);
|
||||
capsuleA.setHalfHeight(halfHeightA);
|
||||
capsuleA.setHalfHeight(halfHeightB);
|
||||
|
||||
CollisionList collisions(16);
|
||||
|
||||
// side by side
|
||||
capsuleB.setTranslation((1.01f * totalRadius) * xAxis);
|
||||
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// end to end
|
||||
capsuleB.setTranslation((1.01f * totalHalfLength) * xAxis);
|
||||
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// rotate B and move it to the side
|
||||
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
capsuleB.setTranslation((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
|
||||
if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should NOT touch"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
if (collisions.size() > 0) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: expected empty collision list but size is " << collisions.size()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void VerletShapeTests::capsuleTouchesCapsule() {
|
||||
// overlapping capsules
|
||||
float radiusA = 2.0f;
|
||||
float halfHeightA = 3.0f;
|
||||
float radiusB = 3.0f;
|
||||
float halfHeightB = 4.0f;
|
||||
|
||||
float totalRadius = radiusA + radiusB;
|
||||
float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
|
||||
|
||||
// create points for the shapes
|
||||
VerletPoint points[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
points[i]._position = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
// give the points to the shapes
|
||||
VerletCapsuleShape capsuleA(radiusA, points+0, points+1);
|
||||
VerletCapsuleShape capsuleB(radiusB, points+2, points+3);
|
||||
capsuleA.setHalfHeight(halfHeightA);
|
||||
capsuleB.setHalfHeight(halfHeightB);
|
||||
|
||||
CollisionList collisions(16);
|
||||
int numCollisions = 0;
|
||||
|
||||
{ // side by side
|
||||
capsuleB.setTranslation((0.99f * totalRadius) * xAxis);
|
||||
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
}
|
||||
|
||||
{ // end to end
|
||||
capsuleB.setTranslation((0.99f * totalHalfLength) * yAxis);
|
||||
|
||||
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
}
|
||||
|
||||
{ // rotate B and move it to the side
|
||||
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
capsuleB.setTranslation((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
|
||||
|
||||
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
}
|
||||
|
||||
{ // again, but this time check collision details
|
||||
float overlap = 0.1f;
|
||||
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
glm::vec3 positionB = ((totalRadius + capsuleB.getHalfHeight()) - overlap) * xAxis;
|
||||
capsuleB.setTranslation(positionB);
|
||||
|
||||
// capsuleA vs capsuleB
|
||||
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
glm::vec3 expectedPenetration = overlap * xAxis;
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * xAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint;
|
||||
}
|
||||
|
||||
// capsuleB vs capsuleA
|
||||
if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
collision = collisions.getCollision(numCollisions - 1);
|
||||
expectedPenetration = - overlap * xAxis;
|
||||
inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
expectedContactPoint = capsuleB.getTranslation() - (radiusB + halfHeightB) * xAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
{ // collide cylinder wall against cylinder wall
|
||||
float overlap = 0.137f;
|
||||
float shift = 0.317f * halfHeightA;
|
||||
glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
|
||||
capsuleB.setRotation(rotation);
|
||||
glm::vec3 positionB = (totalRadius - overlap) * zAxis + shift * yAxis;
|
||||
capsuleB.setTranslation(positionB);
|
||||
|
||||
// capsuleA vs capsuleB
|
||||
if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
|
||||
{
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: capsule and capsule should touch"
|
||||
<< std::endl;
|
||||
} else {
|
||||
++numCollisions;
|
||||
}
|
||||
|
||||
CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
|
||||
glm::vec3 expectedPenetration = overlap * zAxis;
|
||||
float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad penetration: expected = " << expectedPenetration
|
||||
<< " actual = " << collision->_penetration
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * zAxis + shift * yAxis;
|
||||
inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
|
||||
if (fabs(inaccuracy) > EPSILON) {
|
||||
std::cout << __FILE__ << ":" << __LINE__
|
||||
<< " ERROR: bad contactPoint: expected = " << expectedContactPoint
|
||||
<< " actual = " << collision->_contactPoint
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VerletShapeTests::runAllTests() {
|
||||
setSpherePosition();
|
||||
sphereMissesSphere();
|
||||
sphereTouchesSphere();
|
||||
|
||||
sphereMissesCapsule();
|
||||
sphereTouchesCapsule();
|
||||
|
||||
capsuleMissesCapsule();
|
||||
capsuleTouchesCapsule();
|
||||
}
|
30
tests/physics/src/VerletShapeTests.h
Normal file
30
tests/physics/src/VerletShapeTests.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// VerletShapeTests.h
|
||||
// tests/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.06.18
|
||||
// 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_VerletShapeTests_h
|
||||
#define hifi_VerletShapeTests_h
|
||||
|
||||
namespace VerletShapeTests {
|
||||
void setSpherePosition();
|
||||
|
||||
void sphereMissesSphere();
|
||||
void sphereTouchesSphere();
|
||||
|
||||
void sphereMissesCapsule();
|
||||
void sphereTouchesCapsule();
|
||||
|
||||
void capsuleMissesCapsule();
|
||||
void capsuleTouchesCapsule();
|
||||
|
||||
void runAllTests();
|
||||
}
|
||||
|
||||
#endif // hifi_VerletShapeTests_h
|
|
@ -9,8 +9,10 @@
|
|||
//
|
||||
|
||||
#include "ShapeColliderTests.h"
|
||||
#include "VerletShapeTests.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ShapeColliderTests::runAllTests();
|
||||
VerletShapeTests::runAllTests();
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue