Merge branch 'master' into tweak_sit_pose
|
@ -83,7 +83,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
|
||||
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||
this, "handleOctreePacket");
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnership");
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "queueIncomingPacket");
|
||||
|
||||
packetReceiver.registerListenerForTypes({
|
||||
PacketType::ReplicatedAvatarIdentity,
|
||||
|
@ -1136,16 +1136,6 @@ void AvatarMixer::entityChange() {
|
|||
_dirtyHeroStatus = true;
|
||||
}
|
||||
|
||||
void AvatarMixer::handleChallengeOwnership(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
if (senderNode->getType() == NodeType::Agent && senderNode->getLinkedData()) {
|
||||
auto clientData = static_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||
auto avatar = clientData->getAvatarSharedPointer();
|
||||
if (avatar) {
|
||||
avatar->handleChallengeResponse(message.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::aboutToFinish() {
|
||||
DependencyManager::destroy<ResourceManager>();
|
||||
DependencyManager::destroy<ResourceCacheSharedItems>();
|
||||
|
|
|
@ -65,7 +65,6 @@ private slots:
|
|||
void domainSettingsRequestComplete();
|
||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleChallengeOwnership(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void start();
|
||||
|
||||
private:
|
||||
|
|
|
@ -74,6 +74,9 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
|
|||
case PacketType::BulkAvatarTraitsAck:
|
||||
processBulkAvatarTraitsAckMessage(*packet);
|
||||
break;
|
||||
case PacketType::ChallengeOwnership:
|
||||
_avatar->processChallengeResponse(*packet);
|
||||
break;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
|
|
@ -27,13 +27,33 @@
|
|||
#include "ClientTraitsHandler.h"
|
||||
#include "AvatarLogging.h"
|
||||
|
||||
MixerAvatar::~MixerAvatar() {
|
||||
if (_challengeTimeout) {
|
||||
_challengeTimeout->deleteLater();
|
||||
}
|
||||
MixerAvatar::MixerAvatar() {
|
||||
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
|
||||
|
||||
_challengeTimer.setSingleShot(true);
|
||||
_challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS);
|
||||
|
||||
_challengeTimer.callOnTimeout([this]() {
|
||||
if (_verifyState == challengeClient) {
|
||||
_pendingEvent = false;
|
||||
_verifyState = verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID();
|
||||
} else {
|
||||
qCDebug(avatars) << "Ignoring timeout of avatar challenge";
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const char* MixerAvatar::stateToName(VerifyState state) {
|
||||
return QMetaEnum::fromType<VerifyState>().valueToKey(state);
|
||||
}
|
||||
|
||||
void MixerAvatar::fetchAvatarFST() {
|
||||
if (_verifyState >= requestingFST && _verifyState <= challengeClient) {
|
||||
qCDebug(avatars) << "WARNING: Avatar verification restarted; old state:" << stateToName(_verifyState);
|
||||
}
|
||||
_verifyState = nonCertified;
|
||||
|
||||
_pendingEvent = false;
|
||||
|
@ -80,7 +100,7 @@ void MixerAvatar::fetchAvatarFST() {
|
|||
void MixerAvatar::fstRequestComplete() {
|
||||
ResourceRequest* fstRequest = static_cast<ResourceRequest*>(QObject::sender());
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
if (fstRequest == _avatarRequest) {
|
||||
if (_verifyState == requestingFST && fstRequest == _avatarRequest) {
|
||||
auto result = fstRequest->getResult();
|
||||
if (result != ResourceRequest::Success) {
|
||||
_verifyState = error;
|
||||
|
@ -90,11 +110,11 @@ void MixerAvatar::fstRequestComplete() {
|
|||
_verifyState = receivedFST;
|
||||
_pendingEvent = true;
|
||||
}
|
||||
_avatarRequest->deleteLater();
|
||||
_avatarRequest = nullptr;
|
||||
} else {
|
||||
qCDebug(avatars) << "Incorrect request for" << getDisplayName();
|
||||
qCDebug(avatars) << "Incorrect or outdated FST request for" << getDisplayName();
|
||||
}
|
||||
fstRequest->deleteLater();
|
||||
}
|
||||
|
||||
bool MixerAvatar::generateFSTHash() {
|
||||
|
@ -108,7 +128,7 @@ bool MixerAvatar::generateFSTHash() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MixerAvatar::validateFSTHash(const QString& publicKey) {
|
||||
bool MixerAvatar::validateFSTHash(const QString& publicKey) const {
|
||||
// Guess we should refactor this stuff into a Authorization namespace ...
|
||||
return EntityItemProperties::verifySignature(publicKey, _certificateHash,
|
||||
QByteArray::fromBase64(_certificateIdFromFST.toUtf8()));
|
||||
|
@ -171,7 +191,9 @@ void MixerAvatar::ownerRequestComplete() {
|
|||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(QObject::sender());
|
||||
|
||||
if (networkReply->error() == QNetworkReply::NoError) {
|
||||
if (_verifyState != requestingOwner) {
|
||||
qCDebug(avatars) << "WARNING: outdated avatar-owner information received in state" << stateToName(_verifyState);
|
||||
} else if (networkReply->error() == QNetworkReply::NoError) {
|
||||
_dynamicMarketResponse = networkReply->readAll();
|
||||
_verifyState = ownerResponse;
|
||||
_pendingEvent = true;
|
||||
|
@ -259,7 +281,6 @@ void MixerAvatar::processCertifyEvents() {
|
|||
}
|
||||
sendOwnerChallenge();
|
||||
_verifyState = challengeClient;
|
||||
_pendingEvent = true;
|
||||
} else {
|
||||
_verifyState = error;
|
||||
qCDebug(avatars) << "Get owner status - couldn't parse response for" << getSessionUUID()
|
||||
|
@ -273,46 +294,14 @@ void MixerAvatar::processCertifyEvents() {
|
|||
break;
|
||||
}
|
||||
|
||||
case challengeResponse:
|
||||
{
|
||||
if (_challengeResponse.length() < 8) {
|
||||
_verifyState = error;
|
||||
_pendingEvent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
int avatarIDLength;
|
||||
int signedNonceLength;
|
||||
{
|
||||
QDataStream responseStream(_challengeResponse);
|
||||
responseStream.setByteOrder(QDataStream::LittleEndian);
|
||||
responseStream >> avatarIDLength >> signedNonceLength;
|
||||
}
|
||||
QByteArray avatarID(_challengeResponse.data() + 2 * sizeof(int), avatarIDLength);
|
||||
QByteArray signedNonce(_challengeResponse.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength);
|
||||
|
||||
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
|
||||
QByteArray::fromBase64(signedNonce));
|
||||
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
if (_verifyState == verificationFailed) {
|
||||
qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID();
|
||||
} else {
|
||||
qCDebug(avatars) << "Dynamic verification SUCCEEDED for " << getDisplayName() << getSessionUUID();
|
||||
}
|
||||
_pendingEvent = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case requestingOwner:
|
||||
case challengeClient:
|
||||
{ // Qt networking done on this thread:
|
||||
QCoreApplication::processEvents();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qCDebug(avatars) << "Unexpected verify state" << _verifyState;
|
||||
qCDebug(avatars) << "Unexpected verify state" << stateToName(_verifyState);
|
||||
break;
|
||||
|
||||
} // close switch
|
||||
|
@ -334,32 +323,45 @@ void MixerAvatar::sendOwnerChallenge() {
|
|||
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
||||
nonceHash.addData(nonce);
|
||||
_challengeNonceHash = nonceHash.result();
|
||||
|
||||
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
|
||||
if (_challengeTimeout) {
|
||||
_challengeTimeout->deleteLater();
|
||||
}
|
||||
_challengeTimeout = new QTimer();
|
||||
_challengeTimeout->setInterval(CHALLENGE_TIMEOUT_MS);
|
||||
_challengeTimeout->setSingleShot(true);
|
||||
_challengeTimeout->connect(_challengeTimeout, &QTimer::timeout, this, [this]() {
|
||||
if (_verifyState == challengeClient) {
|
||||
_pendingEvent = false;
|
||||
_verifyState = verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID();
|
||||
}
|
||||
});
|
||||
_challengeTimeout->start();
|
||||
_pendingEvent = false;
|
||||
|
||||
// QTimer::start is a set of overloaded functions.
|
||||
QMetaObject::invokeMethod(&_challengeTimer, static_cast<void(QTimer::*)()>(&QTimer::start));
|
||||
}
|
||||
|
||||
void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) {
|
||||
void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
||||
QByteArray avatarID;
|
||||
QByteArray encryptedNonce;
|
||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||
QMetaObject::invokeMethod(&_challengeTimer, &QTimer::stop);
|
||||
if (_verifyState == challengeClient) {
|
||||
_challengeResponse = response->readAll();
|
||||
_verifyState = challengeResponse;
|
||||
_pendingEvent = true;
|
||||
QByteArray responseData = response.readAll();
|
||||
if (responseData.length() < 8) {
|
||||
_verifyState = error;
|
||||
qCDebug(avatars) << "Avatar challenge response packet too small, length:" << responseData.length();
|
||||
return;
|
||||
}
|
||||
|
||||
int avatarIDLength;
|
||||
int signedNonceLength;
|
||||
{
|
||||
QDataStream responseStream(responseData);
|
||||
responseStream.setByteOrder(QDataStream::LittleEndian);
|
||||
responseStream >> avatarIDLength >> signedNonceLength;
|
||||
}
|
||||
QByteArray avatarID(responseData.data() + 2 * sizeof(int), avatarIDLength);
|
||||
QByteArray signedNonce(responseData.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength);
|
||||
|
||||
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
|
||||
QByteArray::fromBase64(signedNonce));
|
||||
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
if (_verifyState == verificationFailed) {
|
||||
qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID();
|
||||
} else {
|
||||
qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID();
|
||||
}
|
||||
|
||||
} else {
|
||||
qCDebug(avatars) << "WARNING: Unexpected avatar challenge-response in state" << stateToName(_verifyState);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// Avatar class for use within the avatar mixer - encapsulates data required only for
|
||||
// sorting priorities within the mixer.
|
||||
// Avatar class for use within the avatar mixer - includes avatar-verification state.
|
||||
|
||||
#ifndef hifi_MixerAvatar_h
|
||||
#define hifi_MixerAvatar_h
|
||||
|
@ -20,8 +19,10 @@
|
|||
class ResourceRequest;
|
||||
|
||||
class MixerAvatar : public AvatarData {
|
||||
Q_OBJECT
|
||||
public:
|
||||
~MixerAvatar();
|
||||
MixerAvatar();
|
||||
|
||||
bool getNeedsHeroCheck() const { return _needsHeroCheck; }
|
||||
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
||||
|
||||
|
@ -31,15 +32,18 @@ public:
|
|||
void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; }
|
||||
|
||||
void processCertifyEvents();
|
||||
void handleChallengeResponse(ReceivedMessage* response);
|
||||
void processChallengeResponse(ReceivedMessage& response);
|
||||
|
||||
// Avatar certification/verification:
|
||||
enum VerifyState {
|
||||
nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse,
|
||||
challengeClient, verified, verificationFailed, verificationSucceeded, error
|
||||
};
|
||||
Q_ENUM(VerifyState)
|
||||
|
||||
private:
|
||||
bool _needsHeroCheck { false };
|
||||
|
||||
// Avatar certification/verification:
|
||||
enum VerifyState { nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse,
|
||||
challengeClient, challengeResponse, verified, verificationFailed, verificationSucceeded, error };
|
||||
Q_ENUM(VerifyState);
|
||||
static const char* stateToName(VerifyState state);
|
||||
VerifyState _verifyState { nonCertified };
|
||||
std::atomic<bool> _pendingEvent { false };
|
||||
QMutex _avatarCertifyLock;
|
||||
|
@ -53,12 +57,11 @@ private:
|
|||
QString _dynamicMarketResponse;
|
||||
QString _ownerPublicKey;
|
||||
QByteArray _challengeNonceHash;
|
||||
QByteArray _challengeResponse;
|
||||
QTimer* _challengeTimeout { nullptr };
|
||||
QTimer _challengeTimer;
|
||||
bool _needsIdentityUpdate { false };
|
||||
|
||||
bool generateFSTHash();
|
||||
bool validateFSTHash(const QString& publicKey);
|
||||
bool validateFSTHash(const QString& publicKey) const;
|
||||
QByteArray canonicalJson(const QString fstFile);
|
||||
void sendOwnerChallenge();
|
||||
|
||||
|
|
|
@ -91,6 +91,7 @@ Item {
|
|||
|
||||
SimplifiedControls.TextField {
|
||||
id: myDisplayNameText
|
||||
rightGlyph: simplifiedUI.glyphs.pencil
|
||||
text: MyAvatar.displayName
|
||||
maximumLength: 256
|
||||
clip: true
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
//
|
||||
// EmoteAppBar.qml
|
||||
//
|
||||
// Created by Zach Fox on 2019-07-08
|
||||
// Copyright 2019 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
|
||||
//
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import "../../simplifiedConstants" as SimplifiedConstants
|
||||
import "../../simplifiedControls" as SimplifiedControls
|
||||
import stylesUit 1.0 as HifiStylesUit
|
||||
import TabletScriptingInterface 1.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
color: simplifiedUI.colors.white
|
||||
anchors.fill: parent
|
||||
|
||||
property int originalWidth: 48
|
||||
property int hoveredWidth: 480
|
||||
property int requestedWidth
|
||||
|
||||
onRequestedWidthChanged: {
|
||||
root.requestNewWidth(root.requestedWidth);
|
||||
}
|
||||
|
||||
Behavior on requestedWidth {
|
||||
enabled: true
|
||||
SmoothedAnimation { duration: 220 }
|
||||
}
|
||||
|
||||
SimplifiedConstants.SimplifiedConstants {
|
||||
id: simplifiedUI
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: enabled
|
||||
onEntered: {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
root.requestedWidth = root.hoveredWidth;
|
||||
}
|
||||
onExited: {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
root.requestedWidth = root.originalWidth;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: mainEmojiContainer
|
||||
z: 2
|
||||
color: simplifiedUI.colors.darkBackground
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
height: parent.height
|
||||
width: root.originalWidth
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "😊"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenterOffset: -2
|
||||
anchors.verticalCenterOffset: -2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
size: 26
|
||||
color: simplifiedUI.colors.text.almostWhite
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: drawerContainer
|
||||
z: 1
|
||||
color: simplifiedUI.colors.white
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
height: parent.height
|
||||
width: root.hoveredWidth
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Emotes go here."
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
size: 20
|
||||
color: simplifiedUI.colors.text.black
|
||||
}
|
||||
}
|
||||
|
||||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
default:
|
||||
console.log('EmoteAppBar.qml: Unrecognized message from JS');
|
||||
break;
|
||||
}
|
||||
}
|
||||
signal sendToScript(var message);
|
||||
signal requestNewWidth(int newWidth);
|
||||
}
|
|
@ -33,6 +33,7 @@ Rectangle {
|
|||
readonly property string unmutedIcon: "images/mic-unmute-i.svg"
|
||||
readonly property string mutedIcon: "images/mic-mute-i.svg"
|
||||
readonly property string pushToTalkIcon: "images/mic-ptt-i.svg"
|
||||
readonly property string pushToTalkClippingIcon: "images/mic-ptt-clip-i.svg"
|
||||
readonly property string pushToTalkMutedIcon: "images/mic-ptt-mute-i.svg"
|
||||
readonly property string clippingIcon: "images/mic-clip-i.svg"
|
||||
readonly property string gatedIcon: "images/mic-gate-i.svg"
|
||||
|
@ -107,7 +108,7 @@ Rectangle {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.rightMargin: 2
|
||||
width: pushToTalk ? 16 : (muted ? 20 : 16)
|
||||
width: pushToTalk ? (clipping && pushingToTalk ? 4 : 16) : (muted ? 20 : 16)
|
||||
height: 22
|
||||
|
||||
Item {
|
||||
|
@ -115,7 +116,7 @@ Rectangle {
|
|||
Image {
|
||||
id: image
|
||||
visible: false
|
||||
source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon :
|
||||
source: pushToTalk ? (clipping && pushingToTalk ? pushToTalkClippingIcon : pushToTalkIcon) : muted ? mutedIcon :
|
||||
clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
@ -170,8 +171,7 @@ Rectangle {
|
|||
Image {
|
||||
id: maskImage
|
||||
visible: false
|
||||
source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon :
|
||||
clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon
|
||||
source: image.source
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: parent.width
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.6, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 3.3 19.6" style="enable-background:new 0 0 3.3 19.6;" xml:space="preserve">
|
||||
<path d="M1.6,16.3C0.7,16.3,0,17.1,0,18c0,0.9,0.7,1.6,1.6,1.6c0.9,0,1.6-0.7,1.6-1.6C3.3,17.1,2.5,16.3,1.6,16.3z M1.5,14.6
|
||||
c0.5,0,0.9-0.1,1.2-0.4c0.3-0.3,0.5-0.8,0.5-1.2V9.8v-5V1.7c0-0.9-0.6-1.6-1.5-1.7C1.3,0,0.9,0.1,0.5,0.4C0.2,0.7,0,1.2,0,1.6v3.1v5
|
||||
v3.1C0,13.8,0.6,14.5,1.5,14.6z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 652 B |
|
@ -148,6 +148,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: tabViewContainers
|
||||
anchors.top: tabContainer.bottom
|
||||
|
@ -155,7 +156,6 @@ Rectangle {
|
|||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
|
||||
GeneralSettings.General {
|
||||
id: generalTabViewContainer
|
||||
visible: activeTabView === "generalTabView"
|
||||
|
@ -163,8 +163,10 @@ Rectangle {
|
|||
onSendNameTagInfo: {
|
||||
sendToScript(message);
|
||||
}
|
||||
onSendEmoteVisible: {
|
||||
sendToScript(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AudioSettings.Audio {
|
||||
id: audioTabViewContainer
|
||||
|
|
|
@ -112,6 +112,49 @@ Flickable {
|
|||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: emoteContainer
|
||||
Layout.preferredWidth: parent.width
|
||||
spacing: 0
|
||||
|
||||
HifiStylesUit.GraphikSemiBold {
|
||||
id: emoteTitle
|
||||
text: "Emote UI"
|
||||
Layout.maximumWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 22
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: emoteSwitchGroup
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||
|
||||
SimplifiedControls.Switch {
|
||||
id: emoteSwitch
|
||||
Layout.preferredHeight: 18
|
||||
Layout.preferredWidth: parent.width
|
||||
labelTextOn: "Show Emote UI"
|
||||
checked: Settings.getValue("simplifiedUI/emoteIndicatorVisible", true)
|
||||
onClicked: {
|
||||
var currentSetting = Settings.getValue("simplifiedUI/emoteIndicatorVisible", true);
|
||||
Settings.setValue("simplifiedUI/emoteIndicatorVisible", !currentSetting);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings
|
||||
|
||||
onValueChanged: {
|
||||
if (setting === "simplifiedUI/emoteIndicatorVisible") {
|
||||
emoteSwitch.checked = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: performanceContainer
|
||||
Layout.preferredWidth: parent.width
|
||||
|
@ -197,7 +240,7 @@ Flickable {
|
|||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
Connections {
|
||||
target: Camera
|
||||
|
||||
onModeUpdated: {
|
||||
|
@ -207,7 +250,7 @@ Flickable {
|
|||
thirdPerson.checked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,4 +291,5 @@ Flickable {
|
|||
}
|
||||
|
||||
signal sendNameTagInfo(var message);
|
||||
signal sendEmoteVisible(var message);
|
||||
}
|
||||
|
|
|
@ -230,7 +230,7 @@ QtObject {
|
|||
readonly property int textSize: 14
|
||||
}
|
||||
readonly property QtObject textField: QtObject {
|
||||
readonly property int editPencilPadding: 6
|
||||
readonly property int rightGlyphPadding: 6
|
||||
}
|
||||
readonly property QtObject scrollBar: QtObject {
|
||||
readonly property int backgroundWidth: 9
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
module hifi.simplifiedUI.simplifiedConstants
|
||||
SimplifiedConstants 1.0 SimplifiedConstants.qml
|
|
@ -0,0 +1,97 @@
|
|||
// From http://www.bytebau.com/progress-circle-with-qml-and-javascript/
|
||||
// ByteBau (Jörn Buchholz) @bytebau.com
|
||||
|
||||
import QtQuick 2.0
|
||||
import QtQml 2.2
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
width: size
|
||||
height: size
|
||||
|
||||
property int size: 200 // The size of the circle in pixel
|
||||
property real arcBegin: 0 // start arc angle in degree
|
||||
property real arcEnd: 360 // end arc angle in degree
|
||||
property real arcOffset: 0 // rotation
|
||||
property bool isPie: false // paint a pie instead of an arc
|
||||
property bool showBackground: false // a full circle as a background of the arc
|
||||
property real lineWidth: 20 // width of the line
|
||||
property string colorCircle: "#CC3333"
|
||||
property string colorBackground: "#779933"
|
||||
|
||||
property alias beginAnimation: animationArcBegin.enabled
|
||||
property alias endAnimation: animationArcEnd.enabled
|
||||
|
||||
property int animationDuration: 200
|
||||
|
||||
onArcBeginChanged: canvas.requestPaint()
|
||||
onArcEndChanged: canvas.requestPaint()
|
||||
|
||||
Behavior on arcBegin {
|
||||
id: animationArcBegin
|
||||
enabled: true
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on arcEnd {
|
||||
id: animationArcEnd
|
||||
enabled: true
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.fill: parent
|
||||
rotation: -90 + parent.arcOffset
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
var x = width / 2
|
||||
var y = height / 2
|
||||
var start = Math.PI * (parent.arcBegin / 180)
|
||||
var end = Math.PI * (parent.arcEnd / 180)
|
||||
ctx.reset()
|
||||
|
||||
if (root.isPie) {
|
||||
if (root.showBackground) {
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = root.colorBackground
|
||||
ctx.moveTo(x, y)
|
||||
ctx.arc(x, y, width / 2, 0, Math.PI * 2, false)
|
||||
ctx.lineTo(x, y)
|
||||
ctx.fill()
|
||||
}
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = root.colorCircle
|
||||
ctx.moveTo(x, y)
|
||||
// Using `width` instead of `width/2` as the argument here ensures
|
||||
// that the ProgressCircle mask will cover the entirety of non-circular emoji.
|
||||
ctx.arc(x, y, width, start, end, false)
|
||||
ctx.lineTo(x, y)
|
||||
ctx.fill()
|
||||
} else {
|
||||
if (root.showBackground) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, (width / 2) - parent.lineWidth / 2, 0, Math.PI * 2, false)
|
||||
ctx.lineWidth = root.lineWidth
|
||||
ctx.strokeStyle = root.colorBackground
|
||||
ctx.stroke()
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, (width / 2) - parent.lineWidth / 2, start, end, false)
|
||||
ctx.lineWidth = root.lineWidth
|
||||
ctx.strokeStyle = root.colorCircle
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
ctx.scale(0.1, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,8 @@ TextField {
|
|||
id: simplifiedUI
|
||||
}
|
||||
|
||||
property string rightGlyph: ""
|
||||
|
||||
color: simplifiedUI.colors.text.white
|
||||
font.family: "Graphik Medium"
|
||||
font.pixelSize: 22
|
||||
|
@ -31,7 +33,7 @@ TextField {
|
|||
autoScroll: false
|
||||
hoverEnabled: true
|
||||
leftPadding: 0
|
||||
rightPadding: editPencil.implicitWidth + simplifiedUI.sizes.controls.textField.editPencilPadding
|
||||
rightPadding: root.rightGlyph === "" ? 0 : rightGlyphItem.implicitWidth + simplifiedUI.sizes.controls.textField.rightGlyphPadding
|
||||
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
|
@ -59,8 +61,9 @@ TextField {
|
|||
}
|
||||
|
||||
HifiStylesUit.HiFiGlyphs {
|
||||
id: editPencil
|
||||
text: simplifiedUI.glyphs.pencil
|
||||
id: rightGlyphItem
|
||||
text: root.rightGlyph
|
||||
visible: rightGlyphItem.text !== ""
|
||||
// Text Size
|
||||
size: root.font.pixelSize * 1.5
|
||||
// Anchors
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
module hifi.simplifiedUI.simplifiedControls
|
||||
Button 1.0 Button.qml
|
||||
CheckBox 1.0 CheckBox.qml
|
||||
InputPeak 1.0 InputPeak.qml
|
||||
ProgressCircle 1.0 ProgressCircle.qml
|
||||
RadioButton 1.0 RadioButton.qml
|
||||
Slider 1.0 Slider.qml
|
||||
Switch 1.0 Switch.qml
|
||||
TextField 1.0 TextField.qml
|
||||
VerticalScrollBar 1.0 VerticalScrollBar.qml
|
|
@ -6282,11 +6282,13 @@ void Application::tryToEnablePhysics() {
|
|||
// process octree stats packets are sent in between full sends of a scene (this isn't currently true).
|
||||
// We keep physics disabled until we've received a full scene and everything near the avatar in that
|
||||
// scene is ready to compute its collision shape.
|
||||
if (getMyAvatar()->isReadyForPhysics()) {
|
||||
auto myAvatar = getMyAvatar();
|
||||
if (myAvatar->isReadyForPhysics()) {
|
||||
myAvatar->getCharacterController()->setPhysicsEngine(_physicsEngine);
|
||||
_octreeProcessor.resetSafeLanding();
|
||||
_physicsEnabled = true;
|
||||
setIsInterstitialMode(false);
|
||||
getMyAvatar()->updateMotionBehaviorFromMenu();
|
||||
myAvatar->updateMotionBehaviorFromMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6577,7 +6579,7 @@ void Application::update(float deltaTime) {
|
|||
avatarManager->handleProcessedPhysicsTransaction(transaction);
|
||||
|
||||
myAvatar->prepareForPhysicsSimulation();
|
||||
_physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying());
|
||||
myAvatar->getCharacterController()->preSimulation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6630,6 +6632,7 @@ void Application::update(float deltaTime) {
|
|||
|
||||
{
|
||||
PROFILE_RANGE(simulation_physics, "MyAvatar");
|
||||
myAvatar->getCharacterController()->postSimulation();
|
||||
myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
|
||||
}
|
||||
|
||||
|
|
|
@ -733,7 +733,7 @@ void MyAvatar::update(float deltaTime) {
|
|||
// When needed and ready, arrange to check and fix.
|
||||
_physicsSafetyPending = false;
|
||||
if (_goToSafe) {
|
||||
safeLanding(_goToPosition); // no-op if already safe
|
||||
safeLanding(_goToPosition); // no-op if safeLanding logic determines already safe
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2733,24 +2733,22 @@ void MyAvatar::nextAttitude(glm::vec3 position, glm::quat orientation) {
|
|||
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
||||
glm::vec3 position;
|
||||
glm::quat orientation;
|
||||
if (_characterController.isEnabledAndReady()) {
|
||||
if (_characterController.isEnabledAndReady() && !(_characterController.needsSafeLandingSupport() || _goToPending)) {
|
||||
_characterController.getPositionAndOrientation(position, orientation);
|
||||
setWorldVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
|
||||
} else {
|
||||
position = getWorldPosition();
|
||||
orientation = getWorldOrientation();
|
||||
if (_characterController.needsSafeLandingSupport() && !_goToPending) {
|
||||
_characterController.resetStuckCounter();
|
||||
_physicsSafetyPending = true;
|
||||
_goToSafe = true;
|
||||
_goToPosition = position;
|
||||
}
|
||||
setWorldVelocity(getWorldVelocity() + _characterController.getFollowVelocity());
|
||||
}
|
||||
nextAttitude(position, orientation);
|
||||
_bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix);
|
||||
|
||||
if (_characterController.isEnabledAndReady()) {
|
||||
setWorldVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
|
||||
if (_characterController.isStuck()) {
|
||||
_physicsSafetyPending = true;
|
||||
_goToPosition = getWorldPosition();
|
||||
}
|
||||
} else {
|
||||
setWorldVelocity(getWorldVelocity() + _characterController.getFollowVelocity());
|
||||
}
|
||||
}
|
||||
|
||||
QString MyAvatar::getScriptedMotorFrame() const {
|
||||
|
|
|
@ -36,10 +36,10 @@ MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar) {
|
|||
MyCharacterController::~MyCharacterController() {
|
||||
}
|
||||
|
||||
void MyCharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
||||
CharacterController::setDynamicsWorld(world);
|
||||
if (world && _rigidBody) {
|
||||
initRayShotgun(world);
|
||||
void MyCharacterController::addToWorld() {
|
||||
CharacterController::addToWorld();
|
||||
if (_rigidBody) {
|
||||
initRayShotgun(_physicsEngine->getDynamicsWorld());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,7 +204,7 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm:
|
|||
}
|
||||
|
||||
int32_t MyCharacterController::computeCollisionMask() const {
|
||||
int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR;
|
||||
int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR;
|
||||
if (_collisionless && _collisionlessAllowed) {
|
||||
collisionMask = BULLET_COLLISION_MASK_COLLISIONLESS;
|
||||
} else if (!_collideWithOtherAvatars) {
|
||||
|
@ -216,16 +216,17 @@ int32_t MyCharacterController::computeCollisionMask() const {
|
|||
void MyCharacterController::handleChangedCollisionMask() {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_MASK) {
|
||||
// ATM the easiest way to update collision groups/masks is to remove/re-add the RigidBody
|
||||
if (_dynamicsWorld) {
|
||||
_dynamicsWorld->removeRigidBody(_rigidBody);
|
||||
int32_t collisionMask = computeCollisionMask();
|
||||
_dynamicsWorld->addRigidBody(_rigidBody, BULLET_COLLISION_GROUP_MY_AVATAR, collisionMask);
|
||||
}
|
||||
// but we don't do it here. Instead we set some flags to remind us to do it later.
|
||||
_pendingFlags |= (PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_ADD_TO_SIMULATION);
|
||||
_pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_MASK;
|
||||
updateCurrentGravity();
|
||||
}
|
||||
}
|
||||
|
||||
bool MyCharacterController::needsSafeLandingSupport() const {
|
||||
return _isStuck && _numStuckSubsteps >= NUM_SUBSTEPS_FOR_SAFE_LANDING_RETRY;
|
||||
}
|
||||
|
||||
btConvexHullShape* MyCharacterController::computeShape() const {
|
||||
// HACK: the avatar collides using convex hull with a collision margin equal to
|
||||
// the old capsule radius. Two points define a capsule and additional points are
|
||||
|
@ -447,12 +448,12 @@ public:
|
|||
std::vector<MyCharacterController::RayAvatarResult> MyCharacterController::rayTest(const btVector3& origin, const btVector3& direction,
|
||||
const btScalar& length, const QVector<uint>& jointsToExclude) const {
|
||||
std::vector<RayAvatarResult> foundAvatars;
|
||||
if (_dynamicsWorld) {
|
||||
if (_physicsEngine) {
|
||||
btVector3 end = origin + length * direction;
|
||||
DetailedRayResultCallback rayCallback = DetailedRayResultCallback();
|
||||
rayCallback.m_flags |= btTriangleRaycastCallback::kF_KeepUnflippedNormal;
|
||||
rayCallback.m_flags |= btTriangleRaycastCallback::kF_UseSubSimplexConvexCastRaytest;
|
||||
_dynamicsWorld->rayTest(origin, end, rayCallback);
|
||||
_physicsEngine->getDynamicsWorld()->rayTest(origin, end, rayCallback);
|
||||
if (rayCallback.m_hitFractions.size() > 0) {
|
||||
foundAvatars.reserve(rayCallback.m_hitFractions.size());
|
||||
for (int32_t i = 0; i < rayCallback.m_hitFractions.size(); i++) {
|
||||
|
|
|
@ -26,7 +26,7 @@ public:
|
|||
explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar);
|
||||
~MyCharacterController ();
|
||||
|
||||
void setDynamicsWorld(btDynamicsWorld* world) override;
|
||||
void addToWorld() override;
|
||||
void updateShapeIfNecessary() override;
|
||||
|
||||
// Sweeping a convex shape through the physics simulation can be expensive when the obstacles are too
|
||||
|
@ -69,9 +69,10 @@ public:
|
|||
int32_t computeCollisionMask() const override;
|
||||
void handleChangedCollisionMask() override;
|
||||
|
||||
bool _collideWithOtherAvatars{ true };
|
||||
void setCollideWithOtherAvatars(bool collideWithOtherAvatars) { _collideWithOtherAvatars = collideWithOtherAvatars; }
|
||||
|
||||
bool needsSafeLandingSupport() const;
|
||||
|
||||
protected:
|
||||
void initRayShotgun(const btCollisionWorld* world);
|
||||
void updateMassProperties() override;
|
||||
|
@ -88,6 +89,7 @@ protected:
|
|||
btScalar _density { 1.0f };
|
||||
|
||||
std::vector<DetailedMotionState*> _detailedMotionStates;
|
||||
bool _collideWithOtherAvatars { true };
|
||||
};
|
||||
|
||||
#endif // hifi_MyCharacterController_h
|
||||
|
|
|
@ -35,8 +35,12 @@ QVariant SettingsScriptingInterface::getValue(const QString& setting, const QVar
|
|||
}
|
||||
|
||||
void SettingsScriptingInterface::setValue(const QString& setting, const QVariant& value) {
|
||||
if (getValue(setting) == value) {
|
||||
return;
|
||||
}
|
||||
// Make a deep-copy of the string.
|
||||
// Dangling pointers can occur with QStrings that are implicitly shared from a QScriptEngine.
|
||||
QString deepCopy = QString::fromUtf16(setting.utf16());
|
||||
Setting::Handle<QVariant>(deepCopy).set(value);
|
||||
emit valueChanged(setting, value);
|
||||
}
|
||||
|
|
|
@ -64,6 +64,9 @@ public slots:
|
|||
* print("Value: " + (typeof value) + " " + JSON.stringify(value)); // object {"x":0,"y":10,"z":0}
|
||||
*/
|
||||
void setValue(const QString& setting, const QVariant& value);
|
||||
|
||||
signals:
|
||||
void valueChanged(const QString& setting, const QVariant& value);
|
||||
};
|
||||
|
||||
#endif // hifi_SettingsScriptingInterface_h
|
||||
|
|
|
@ -56,6 +56,7 @@ WindowScriptingInterface::WindowScriptingInterface() {
|
|||
});
|
||||
|
||||
connect(qApp->getWindow(), &MainWindow::windowGeometryChanged, this, &WindowScriptingInterface::onWindowGeometryChanged);
|
||||
connect(qApp->getWindow(), &MainWindow::windowMinimizedChanged, this, &WindowScriptingInterface::minimizedChanged);
|
||||
connect(qApp, &Application::interstitialModeChanged, [this] (bool interstitialMode) {
|
||||
emit interstitialModeChanged(interstitialMode);
|
||||
});
|
||||
|
|
|
@ -814,6 +814,17 @@ signals:
|
|||
*/
|
||||
void geometryChanged(QRect geometry);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when "minimized" state of the Interface window changes.
|
||||
* @function Window.minimizedChanged
|
||||
* @param {bool} isMinimized - true if the Interface window is now minimized; false otherwise.
|
||||
* @returns {Signal}
|
||||
*
|
||||
* Window.minimizedChanged.connect(onWindowMinimizedChanged);
|
||||
*/
|
||||
void minimizedChanged(bool isMinimized);
|
||||
|
||||
private:
|
||||
QString getPreviousBrowseLocation() const;
|
||||
void setPreviousBrowseLocation(const QString& location);
|
||||
|
|
|
@ -109,6 +109,10 @@ void InteractiveWindow::forwardKeyReleaseEvent(int key, int modifiers) {
|
|||
QCoreApplication::postEvent(QCoreApplication::instance(), event);
|
||||
}
|
||||
|
||||
void InteractiveWindow::emitMainWindowResizeEvent() {
|
||||
emit qApp->getWindow()->windowGeometryChanged(qApp->getWindow()->geometry());
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* A set of properties used when creating an <code>InteractiveWindow</code>.
|
||||
* @typedef {object} InteractiveWindow.Properties
|
||||
|
@ -215,10 +219,11 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
|
|||
Qt::QueuedConnection);
|
||||
QObject::connect(rootItem, SIGNAL(keyReleaseEvent(int, int)), this, SLOT(forwardKeyReleaseEvent(int, int)),
|
||||
Qt::QueuedConnection);
|
||||
emit mainWindow->windowGeometryChanged(qApp->getWindow()->geometry());
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(_dockWidget.get(), SIGNAL(onResizeEvent()), this, SLOT(emitMainWindowResizeEvent()));
|
||||
|
||||
_dockWidget->setSource(QUrl(sourceUrl));
|
||||
mainWindow->addDockWidget(dockArea, _dockWidget.get());
|
||||
} else {
|
||||
|
@ -296,7 +301,9 @@ void InteractiveWindow::sendToQml(const QVariant& message) {
|
|||
QMetaObject::invokeMethod(rootItem, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
}
|
||||
} else {
|
||||
QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
if (_qmlWindowProxy) {
|
||||
QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -319,6 +319,7 @@ protected slots:
|
|||
|
||||
void forwardKeyPressEvent(int key, int modifiers);
|
||||
void forwardKeyReleaseEvent(int key, int modifiers);
|
||||
void emitMainWindowResizeEvent();
|
||||
|
||||
private:
|
||||
std::shared_ptr<QmlWindowProxy> _qmlWindowProxy;
|
||||
|
|
|
@ -69,7 +69,7 @@ set_target_properties(${this_target} PROPERTIES
|
|||
set(MACOSX_BUNDLE_ICON_FILE "interface.icns")
|
||||
|
||||
function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE)
|
||||
if (NOT DEFINED ${_RESULT_NAME})
|
||||
if (NOT DEFINED ${_RESULT_NAME})
|
||||
if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "")
|
||||
set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE)
|
||||
else()
|
||||
|
@ -89,8 +89,14 @@ if ("${LAUNCHER_HMAC_SECRET}" STREQUAL "")
|
|||
message(FATAL_ERROR "LAUNCHER_HMAC_SECRET is not set")
|
||||
endif()
|
||||
|
||||
# Development environments don't set BUILD_VERSION. Let 0 mean a development version.
|
||||
if(NOT BUILD_VERSION)
|
||||
set(BUILD_VERSION 0)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_HMAC_SECRET="${LAUNCHER_HMAC_SECRET}")
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}")
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE USER_AGENT_STRING="HQLauncher/${BUILD_VERSION} \(macOS\)")
|
||||
|
||||
file(GLOB NIB_FILES "nib/*.xib")
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
NSMutableURLRequest *request = [NSMutableURLRequest new];
|
||||
[request setURL:[NSURL URLWithString:@"https://metaverse.highfidelity.com/oauth/token"]];
|
||||
[request setHTTPMethod:@"POST"];
|
||||
[request setValue:@USER_AGENT_STRING forHTTPHeaderField:@"User-Agent"];
|
||||
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
|
||||
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
|
||||
[request setHTTPBody:postData];
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
{
|
||||
self.progressPercentage = 0.0;
|
||||
self.taskProgressPercentage = 0.0;
|
||||
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:domainContentUrl]
|
||||
cachePolicy:NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval:60.0];
|
||||
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:domainContentUrl]
|
||||
cachePolicy:NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval:60.0];
|
||||
[request setValue:@USER_AGENT_STRING forHTTPHeaderField:@"User-Agent"];
|
||||
|
||||
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
{
|
||||
self.progressPercentage = 0.0;
|
||||
self.taskProgressPercentage = 0.0;
|
||||
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:downloadUrl]
|
||||
cachePolicy:NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval:60.0];
|
||||
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:downloadUrl]
|
||||
cachePolicy:NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval:60.0];
|
||||
[request setValue:@USER_AGENT_STRING forHTTPHeaderField:@"User-Agent"];
|
||||
|
||||
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
@implementation DownloadLauncher
|
||||
|
||||
- (void) downloadLauncher:(NSString*)launcherUrl {
|
||||
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:launcherUrl]
|
||||
cachePolicy:NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval:60.0];
|
||||
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:launcherUrl]
|
||||
cachePolicy:NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval:60.0];
|
||||
[request setValue:@USER_AGENT_STRING forHTTPHeaderField:@"User-Agent"];
|
||||
|
||||
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
|
||||
- (void) downloadScripts:(NSString*) scriptsUrl
|
||||
{
|
||||
/*NSURLRequest* theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:scriptsUrl]
|
||||
/*NSMutableURLRequest* theRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:scriptsUrl]
|
||||
cachePolicy:NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval:6000.0];
|
||||
[theRequest setValue:@USER_AGENT_STRING forHTTPHeaderField:@"User-Agent"];
|
||||
|
||||
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest delegate:self];
|
||||
|
||||
if (!theDownload) {
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
NSMutableURLRequest* request = [NSMutableURLRequest new];
|
||||
[request setURL:[NSURL URLWithString:@"https://thunder.highfidelity.com/builds/api/tags/latest?format=json"]];
|
||||
[request setHTTPMethod:@"GET"];
|
||||
[request setValue:@USER_AGENT_STRING forHTTPHeaderField:@"User-Agent"];
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
// We're using an ephermeral session here to ensure the tags api response is never cached.
|
||||
|
|
|
@ -32,6 +32,7 @@ static NSString* const organizationURL = @"https://orgs.highfidelity.com/organiz
|
|||
NSMutableURLRequest *request = [NSMutableURLRequest new];
|
||||
[request setURL:[NSURL URLWithString:[organizationURL stringByAppendingString:jsonFile]]];
|
||||
[request setHTTPMethod:@"GET"];
|
||||
[request setValue:@USER_AGENT_STRING forHTTPHeaderField:@"User-Agent"];
|
||||
[request setValue:@"" forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
NSURLSession * session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.ephemeralSessionConfiguration delegate: self delegateQueue: [NSOperationQueue mainQueue]];
|
||||
|
|
|
@ -405,7 +405,7 @@ LauncherUtils::ResponseError LauncherManager::readOrganizationJSON(const CString
|
|||
CString contentTypeJson = L"content-type:application/json";
|
||||
CString response;
|
||||
CString url = _T("/organizations/") + hash + _T(".json");
|
||||
LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher",
|
||||
LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(getHttpUserAgent(),
|
||||
L"orgs.highfidelity.com", url,
|
||||
contentTypeJson, CStringA(),
|
||||
response, false);
|
||||
|
@ -464,7 +464,7 @@ void LauncherManager::getMostRecentBuilds(CString& launcherUrlOut, CString& laun
|
|||
}
|
||||
onMostRecentBuildsReceived(response, error);
|
||||
};
|
||||
LauncherUtils::httpCallOnThread(L"HQ Launcher",
|
||||
LauncherUtils::httpCallOnThread(getHttpUserAgent(),
|
||||
L"thunder.highfidelity.com",
|
||||
L"/builds/api/tags/latest?format=json",
|
||||
contentTypeJson, CStringA(), false, httpCallback);
|
||||
|
@ -530,7 +530,7 @@ LauncherUtils::ResponseError LauncherManager::getAccessTokenForCredentials(const
|
|||
|
||||
CString contentTypeText = L"content-type:application/x-www-form-urlencoded";
|
||||
CString response;
|
||||
LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher",
|
||||
LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(getHttpUserAgent(),
|
||||
L"metaverse.highfidelity.com",
|
||||
L"/oauth/token",
|
||||
contentTypeText, post,
|
||||
|
|
|
@ -134,6 +134,7 @@ public:
|
|||
void updateProgress(ProcessType processType, float progress);
|
||||
void onCancel();
|
||||
const CString& getLauncherVersion() const { return _launcherVersion; }
|
||||
CString getHttpUserAgent() const { return L"HQLauncher/" + _launcherVersion + L" (Windows)"; }
|
||||
|
||||
private:
|
||||
ProcessType _currentProcess { ProcessType::DownloadApplication };
|
||||
|
|
|
@ -425,7 +425,15 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
packetStream << FingerprintUtils::getMachineFingerprint();
|
||||
|
||||
auto desc = platform::getAll();
|
||||
platform::json all = platform::getAll();
|
||||
platform::json desc;
|
||||
// only pull out those items that will fit within a packet
|
||||
desc[platform::keys::COMPUTER] = all[platform::keys::COMPUTER];
|
||||
desc[platform::keys::MEMORY] = all[platform::keys::MEMORY];
|
||||
desc[platform::keys::CPUS] = all[platform::keys::CPUS];
|
||||
desc[platform::keys::GPUS] = all[platform::keys::GPUS];
|
||||
desc[platform::keys::DISPLAYS] = all[platform::keys::DISPLAYS];
|
||||
desc[platform::keys::NICS] = all[platform::keys::NICS];
|
||||
|
||||
QByteArray systemInfo(desc.dump().c_str());
|
||||
QByteArray compressedSystemInfo = qCompress(systemInfo);
|
||||
|
|
|
@ -11,14 +11,59 @@
|
|||
|
||||
#include "CharacterController.h"
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
#include <AvatarConstants.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <PhysicsCollisionGroups.h>
|
||||
|
||||
#include "ObjectMotionState.h"
|
||||
#include "PhysicsHelpers.h"
|
||||
#include "PhysicsLogging.h"
|
||||
#include "TemporaryPairwiseCollisionFilter.h"
|
||||
|
||||
const float STUCK_PENETRATION = -0.05f; // always negative into the object.
|
||||
const float STUCK_IMPULSE = 500.0f;
|
||||
|
||||
|
||||
const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
|
||||
static bool _appliedStuckRecoveryStrategy = false;
|
||||
|
||||
static TemporaryPairwiseCollisionFilter _pairwiseFilter;
|
||||
|
||||
// Note: applyPairwiseFilter is registered as a sub-callback to Bullet's gContactAddedCallback feature
|
||||
// when we detect MyAvatar is "stuck". It will disable new ManifoldPoints between MyAvatar and mesh objects with
|
||||
// which it has deep penetration, and will continue disabling new contact until new contacts stop happening
|
||||
// (no overlap). If MyAvatar is not trying to move its velocity is defaulted to "up", to help it escape overlap.
|
||||
bool applyPairwiseFilter(btManifoldPoint& cp,
|
||||
const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0,
|
||||
const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) {
|
||||
static int32_t numCalls = 0;
|
||||
++numCalls;
|
||||
// This callback is ONLY called on objects with btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK flag
|
||||
// and the flagged object will always be sorted to Obj0. Hence the "other" is always Obj1.
|
||||
const btCollisionObject* other = colObj1Wrap->m_collisionObject;
|
||||
|
||||
if (_pairwiseFilter.isFiltered(other)) {
|
||||
_pairwiseFilter.incrementEntry(other);
|
||||
// disable contact point by setting distance too large and normal to zero
|
||||
cp.setDistance(1.0e6f);
|
||||
cp.m_normalWorldOnB.setValue(0.0f, 0.0f, 0.0f);
|
||||
_appliedStuckRecoveryStrategy = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (other->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) {
|
||||
if ( cp.getDistance() < 2.0f * STUCK_PENETRATION ||
|
||||
cp.getAppliedImpulse() > 2.0f * STUCK_IMPULSE ||
|
||||
(cp.getDistance() < STUCK_PENETRATION && cp.getAppliedImpulse() > STUCK_IMPULSE)) {
|
||||
_pairwiseFilter.incrementEntry(other);
|
||||
// disable contact point by setting distance too large and normal to zero
|
||||
cp.setDistance(1.0e6f);
|
||||
cp.m_normalWorldOnB.setValue(0.0f, 0.0f, 0.0f);
|
||||
_appliedStuckRecoveryStrategy = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_STATE_CHANGE
|
||||
#define SET_STATE(desiredState, reason) setState(desiredState, reason)
|
||||
|
@ -60,6 +105,8 @@ CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const
|
|||
}
|
||||
}
|
||||
|
||||
static uint32_t _numCharacterControllers { 0 };
|
||||
|
||||
CharacterController::CharacterController() {
|
||||
_floorDistance = _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
|
||||
|
||||
|
@ -78,6 +125,11 @@ CharacterController::CharacterController() {
|
|||
_hasSupport = false;
|
||||
|
||||
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
|
||||
|
||||
// ATM CharacterController is a singleton. When we want more we'll have to
|
||||
// overhaul the applyPairwiseFilter() logic to handle multiple instances.
|
||||
++_numCharacterControllers;
|
||||
assert(numCharacterControllers == 1);
|
||||
}
|
||||
|
||||
CharacterController::~CharacterController() {
|
||||
|
@ -92,64 +144,70 @@ CharacterController::~CharacterController() {
|
|||
}
|
||||
|
||||
bool CharacterController::needsRemoval() const {
|
||||
return ((_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION) == PENDING_FLAG_REMOVE_FROM_SIMULATION);
|
||||
return (_physicsEngine && (_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION) == PENDING_FLAG_REMOVE_FROM_SIMULATION);
|
||||
}
|
||||
|
||||
bool CharacterController::needsAddition() const {
|
||||
return ((_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION) == PENDING_FLAG_ADD_TO_SIMULATION);
|
||||
return (_physicsEngine && (_pendingFlags & PENDING_FLAG_ADD_TO_SIMULATION) == PENDING_FLAG_ADD_TO_SIMULATION);
|
||||
}
|
||||
|
||||
void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
||||
if (_dynamicsWorld != world) {
|
||||
// remove from old world
|
||||
if (_dynamicsWorld) {
|
||||
if (_rigidBody) {
|
||||
_dynamicsWorld->removeRigidBody(_rigidBody);
|
||||
_dynamicsWorld->removeAction(this);
|
||||
}
|
||||
_dynamicsWorld = nullptr;
|
||||
}
|
||||
int32_t collisionMask = computeCollisionMask();
|
||||
int32_t collisionGroup = BULLET_COLLISION_GROUP_MY_AVATAR;
|
||||
void CharacterController::removeFromWorld() {
|
||||
if (_inWorld) {
|
||||
if (_rigidBody) {
|
||||
updateMassProperties();
|
||||
}
|
||||
if (world && _rigidBody) {
|
||||
// add to new world
|
||||
_dynamicsWorld = world;
|
||||
_pendingFlags &= ~PENDING_FLAG_JUMP;
|
||||
_dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, collisionMask);
|
||||
_dynamicsWorld->addAction(this);
|
||||
// restore gravity settings because adding an object to the world overwrites its gravity setting
|
||||
_rigidBody->setGravity(_currentGravity * _currentUp);
|
||||
// set flag to enable custom contactAddedCallback
|
||||
_rigidBody->setCollisionFlags(btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
|
||||
|
||||
// enable CCD
|
||||
_rigidBody->setCcdSweptSphereRadius(_radius);
|
||||
_rigidBody->setCcdMotionThreshold(_radius);
|
||||
|
||||
btCollisionShape* shape = _rigidBody->getCollisionShape();
|
||||
assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE);
|
||||
_ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape));
|
||||
}
|
||||
_ghost.setCollisionGroupAndMask(collisionGroup, collisionMask & (~ collisionGroup));
|
||||
_ghost.setCollisionWorld(_dynamicsWorld);
|
||||
_ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
|
||||
if (_rigidBody) {
|
||||
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
||||
_physicsEngine->getDynamicsWorld()->removeRigidBody(_rigidBody);
|
||||
_physicsEngine->getDynamicsWorld()->removeAction(this);
|
||||
}
|
||||
_inWorld = false;
|
||||
}
|
||||
if (_dynamicsWorld) {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
// shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION |
|
||||
PENDING_FLAG_ADD_DETAILED_TO_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
|
||||
} else {
|
||||
_pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
}
|
||||
_pendingFlags &= ~PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
|
||||
void CharacterController::addToWorld() {
|
||||
if (!_rigidBody) {
|
||||
return;
|
||||
}
|
||||
if (_inWorld) {
|
||||
_pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
|
||||
return;
|
||||
}
|
||||
btDiscreteDynamicsWorld* world = _physicsEngine->getDynamicsWorld();
|
||||
int32_t collisionMask = computeCollisionMask();
|
||||
int32_t collisionGroup = BULLET_COLLISION_GROUP_MY_AVATAR;
|
||||
|
||||
updateMassProperties();
|
||||
_pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
|
||||
|
||||
// add to new world
|
||||
_pendingFlags &= ~PENDING_FLAG_JUMP;
|
||||
world->addRigidBody(_rigidBody, collisionGroup, collisionMask);
|
||||
world->addAction(this);
|
||||
_inWorld = true;
|
||||
|
||||
// restore gravity settings because adding an object to the world overwrites its gravity setting
|
||||
_rigidBody->setGravity(_currentGravity * _currentUp);
|
||||
// set flag to enable custom contactAddedCallback
|
||||
_rigidBody->setCollisionFlags(btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
|
||||
|
||||
// enable CCD
|
||||
_rigidBody->setCcdSweptSphereRadius(_radius);
|
||||
_rigidBody->setCcdMotionThreshold(_radius);
|
||||
|
||||
btCollisionShape* shape = _rigidBody->getCollisionShape();
|
||||
assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE);
|
||||
_ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape));
|
||||
|
||||
_ghost.setCollisionGroupAndMask(collisionGroup, collisionMask & (~ collisionGroup));
|
||||
_ghost.setCollisionWorld(world);
|
||||
_ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
|
||||
if (_rigidBody) {
|
||||
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
||||
}
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
// shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION |
|
||||
PENDING_FLAG_ADD_DETAILED_TO_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
|
||||
} else {
|
||||
_pendingFlags &= ~PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
_pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,17 +217,21 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) {
|
|||
btDispatcher* dispatcher = collisionWorld->getDispatcher();
|
||||
int numManifolds = dispatcher->getNumManifolds();
|
||||
bool hasFloor = false;
|
||||
bool isStuck = false;
|
||||
bool probablyStuck = _isStuck && _appliedStuckRecoveryStrategy;
|
||||
|
||||
btTransform rotation = _rigidBody->getWorldTransform();
|
||||
rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part
|
||||
|
||||
float deepestDistance = 0.0f;
|
||||
float strongestImpulse = 0.0f;
|
||||
|
||||
for (int i = 0; i < numManifolds; i++) {
|
||||
btPersistentManifold* contactManifold = dispatcher->getManifoldByIndexInternal(i);
|
||||
if (_rigidBody == contactManifold->getBody1() || _rigidBody == contactManifold->getBody0()) {
|
||||
bool characterIsFirst = _rigidBody == contactManifold->getBody0();
|
||||
int numContacts = contactManifold->getNumContacts();
|
||||
int stepContactIndex = -1;
|
||||
bool stepValid = true;
|
||||
float highestStep = _minStepHeight;
|
||||
for (int j = 0; j < numContacts; j++) {
|
||||
// check for "floor"
|
||||
|
@ -177,28 +239,24 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) {
|
|||
btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame
|
||||
btVector3 normal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character
|
||||
btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp);
|
||||
// If there's non-trivial penetration with a big impulse for several steps, we're probably stuck.
|
||||
// Note it here in the controller, and let MyAvatar figure out what to do about it.
|
||||
const float STUCK_PENETRATION = -0.05f; // always negative into the object.
|
||||
const float STUCK_IMPULSE = 500.0f;
|
||||
const int STUCK_LIFETIME = 3;
|
||||
if ((contact.getDistance() < STUCK_PENETRATION) && (contact.getAppliedImpulse() > STUCK_IMPULSE) && (contact.getLifeTime() > STUCK_LIFETIME)) {
|
||||
isStuck = true; // latch on
|
||||
|
||||
float distance = contact.getDistance();
|
||||
if (distance < deepestDistance) {
|
||||
deepestDistance = distance;
|
||||
}
|
||||
float impulse = contact.getAppliedImpulse();
|
||||
if (impulse > strongestImpulse) {
|
||||
strongestImpulse = impulse;
|
||||
}
|
||||
|
||||
if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) {
|
||||
hasFloor = true;
|
||||
if (!pushing && isStuck) {
|
||||
// we're not pushing against anything and we're stuck so we can early exit
|
||||
// (all we need to know is that there is a floor)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pushing && _targetVelocity.dot(normal) < 0.0f) {
|
||||
if (stepValid && pushing && _targetVelocity.dot(normal) < 0.0f) {
|
||||
// remember highest step obstacle
|
||||
if (!_stepUpEnabled || hitHeight > _maxStepHeight) {
|
||||
// this manifold is invalidated by point that is too high
|
||||
stepContactIndex = -1;
|
||||
break;
|
||||
stepValid = false;
|
||||
} else if (hitHeight > highestStep && normal.dot(_targetVelocity) < 0.0f ) {
|
||||
highestStep = hitHeight;
|
||||
stepContactIndex = j;
|
||||
|
@ -206,7 +264,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (stepContactIndex > -1 && highestStep > _stepHeight) {
|
||||
if (stepValid && stepContactIndex > -1 && highestStep > _stepHeight) {
|
||||
// remember step info for later
|
||||
btManifoldPoint& contact = contactManifold->getContactPoint(stepContactIndex);
|
||||
btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame
|
||||
|
@ -214,13 +272,44 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) {
|
|||
_stepHeight = highestStep;
|
||||
_stepPoint = rotation * pointOnCharacter; // rotate into world-frame
|
||||
}
|
||||
if (hasFloor && isStuck && !(pushing && _stepUpEnabled)) {
|
||||
// early exit since all we need to know is that we're on a floor
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_isStuck = isStuck;
|
||||
|
||||
// If there's deep penetration and big impulse we're probably stuck.
|
||||
probablyStuck = probablyStuck
|
||||
|| deepestDistance < 2.0f * STUCK_PENETRATION
|
||||
|| strongestImpulse > 2.0f * STUCK_IMPULSE
|
||||
|| (deepestDistance < STUCK_PENETRATION && strongestImpulse > STUCK_IMPULSE);
|
||||
|
||||
if (_isStuck != probablyStuck) {
|
||||
++_stuckTransitionCount;
|
||||
if (_stuckTransitionCount > NUM_SUBSTEPS_FOR_STUCK_TRANSITION) {
|
||||
// we've been in this "probablyStuck" state for several consecutive substeps
|
||||
// --> make it official by changing state
|
||||
qCDebug(physics) << "CharacterController::_isStuck :" << _isStuck << "-->" << probablyStuck;
|
||||
_isStuck = probablyStuck;
|
||||
// init _numStuckSubsteps at NUM_SUBSTEPS_FOR_SAFE_LANDING_RETRY so SafeLanding tries to help immediately
|
||||
_numStuckSubsteps = NUM_SUBSTEPS_FOR_SAFE_LANDING_RETRY;
|
||||
_stuckTransitionCount = 0;
|
||||
if (_isStuck) {
|
||||
// enable pairwise filter
|
||||
_physicsEngine->setContactAddedCallback(applyPairwiseFilter);
|
||||
_pairwiseFilter.incrementStepCount();
|
||||
} else {
|
||||
// disable pairwise filter
|
||||
_physicsEngine->setContactAddedCallback(nullptr);
|
||||
_appliedStuckRecoveryStrategy = false;
|
||||
_pairwiseFilter.clearAllEntries();
|
||||
}
|
||||
updateCurrentGravity();
|
||||
}
|
||||
} else {
|
||||
_stuckTransitionCount = 0;
|
||||
if (_isStuck) {
|
||||
++_numStuckSubsteps;
|
||||
_appliedStuckRecoveryStrategy = false;
|
||||
}
|
||||
}
|
||||
return hasFloor;
|
||||
}
|
||||
|
||||
|
@ -394,7 +483,7 @@ static const char* stateToStr(CharacterController::State state) {
|
|||
|
||||
void CharacterController::updateCurrentGravity() {
|
||||
int32_t collisionMask = computeCollisionMask();
|
||||
if (_state == State::Hover || collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) {
|
||||
if (_state == State::Hover || collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS || _isStuck) {
|
||||
_currentGravity = 0.0f;
|
||||
} else {
|
||||
_currentGravity = _gravity;
|
||||
|
@ -452,7 +541,7 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const
|
|||
_minStepHeight = DEFAULT_MIN_STEP_HEIGHT_FACTOR * (_halfHeight + _radius);
|
||||
_maxStepHeight = DEFAULT_MAX_STEP_HEIGHT_FACTOR * (_halfHeight + _radius);
|
||||
|
||||
if (_dynamicsWorld) {
|
||||
if (_physicsEngine) {
|
||||
// must REMOVE from world prior to shape update
|
||||
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
|
||||
}
|
||||
|
@ -470,6 +559,15 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const
|
|||
}
|
||||
}
|
||||
|
||||
void CharacterController::setPhysicsEngine(const PhysicsEnginePointer& engine) {
|
||||
if (!_physicsEngine && engine) {
|
||||
// ATM there is only one PhysicsEngine: it is a singleton, and we are taking advantage
|
||||
// of that assumption here. If we ever introduce more and allow for this backpointer
|
||||
// to change then we'll have to overhaul this method.
|
||||
_physicsEngine = engine;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::setCollisionless(bool collisionless) {
|
||||
if (collisionless != _collisionless) {
|
||||
_collisionless = collisionless;
|
||||
|
@ -634,6 +732,8 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
|||
}
|
||||
|
||||
void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) {
|
||||
btVector3 currentVelocity = velocity;
|
||||
|
||||
if (velocity.length2() < MIN_TARGET_SPEED_SQUARED) {
|
||||
velocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
@ -665,6 +765,12 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) {
|
|||
if (velocity.length2() < MIN_TARGET_SPEED_SQUARED) {
|
||||
velocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
if (_isStuck && _targetVelocity.length2() < MIN_TARGET_SPEED_SQUARED) {
|
||||
// we're stuck, but not trying to move --> move UP by default
|
||||
// in the hopes we'll get unstuck
|
||||
const float STUCK_EXTRACTION_SPEED = 1.0f;
|
||||
velocity = STUCK_EXTRACTION_SPEED * _currentUp;
|
||||
}
|
||||
|
||||
// 'thrust' is applied at the very end
|
||||
_targetVelocity += dt * _linearAcceleration;
|
||||
|
@ -672,6 +778,14 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) {
|
|||
// Note the differences between these two variables:
|
||||
// _targetVelocity = ideal final velocity according to input
|
||||
// velocity = real final velocity after motors are applied to current velocity
|
||||
|
||||
bool gettingStuck = !_isStuck && _stuckTransitionCount > 1 && _state == State::Hover;
|
||||
if (gettingStuck && velocity.length2() > currentVelocity.length2()) {
|
||||
// we are probably trying to fly fast into a mesh obstacle
|
||||
// which is causing us to tickle the "stuck" detection code
|
||||
// so we average our new velocity with currentVeocity to prevent a "safe landing" response
|
||||
velocity = 0.5f * (velocity + currentVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) {
|
||||
|
@ -681,7 +795,7 @@ void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) {
|
|||
}
|
||||
|
||||
void CharacterController::updateState() {
|
||||
if (!_dynamicsWorld) {
|
||||
if (!_physicsEngine) {
|
||||
return;
|
||||
}
|
||||
if (_pendingFlags & PENDING_FLAG_RECOMPUTE_FLYING) {
|
||||
|
@ -712,7 +826,7 @@ void CharacterController::updateState() {
|
|||
|
||||
ClosestNotMe rayCallback(_rigidBody);
|
||||
rayCallback.m_closestHitFraction = 1.0f;
|
||||
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
|
||||
_physicsEngine->getDynamicsWorld()->rayTest(rayStart, rayEnd, rayCallback);
|
||||
bool rayHasHit = rayCallback.hasHit();
|
||||
quint64 now = usecTimestampNow();
|
||||
if (rayHasHit) {
|
||||
|
@ -829,6 +943,21 @@ void CharacterController::updateState() {
|
|||
}
|
||||
|
||||
void CharacterController::preSimulation() {
|
||||
if (needsRemoval()) {
|
||||
removeFromWorld();
|
||||
|
||||
// We must remove any existing contacts for the avatar so that any new contacts will have
|
||||
// valid data. MyAvatar's RigidBody is the ONLY one in the simulation that does not yet
|
||||
// have a MotionState so we pass nullptr to removeContacts().
|
||||
if (_physicsEngine) {
|
||||
_physicsEngine->removeContacts(nullptr);
|
||||
}
|
||||
}
|
||||
updateShapeIfNecessary();
|
||||
if (needsAddition()) {
|
||||
addToWorld();
|
||||
}
|
||||
|
||||
if (_rigidBody) {
|
||||
// slam body transform and remember velocity
|
||||
_rigidBody->setWorldTransform(btTransform(btTransform(_rotation, _position)));
|
||||
|
@ -843,6 +972,11 @@ void CharacterController::preSimulation() {
|
|||
_followTime = 0.0f;
|
||||
_followLinearDisplacement = btVector3(0, 0, 0);
|
||||
_followAngularDisplacement = btQuaternion::getIdentity();
|
||||
|
||||
if (_isStuck) {
|
||||
_pairwiseFilter.expireOldEntries();
|
||||
_pairwiseFilter.incrementStepCount();
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::postSimulation() {
|
||||
|
|
|
@ -20,11 +20,12 @@
|
|||
|
||||
#include <GLMHelpers.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <PhysicsCollisionGroups.h>
|
||||
|
||||
#include "AvatarConstants.h"
|
||||
#include "BulletUtil.h"
|
||||
#include "CharacterGhostObject.h"
|
||||
#include "AvatarConstants.h"
|
||||
#include "PhysicsEngine.h"
|
||||
#include "PhysicsHelpers.h"
|
||||
|
||||
const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0;
|
||||
const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1;
|
||||
|
@ -37,6 +38,9 @@ const uint32_t PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION = 1U << 7;
|
|||
|
||||
const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f);
|
||||
|
||||
const uint32_t NUM_SUBSTEPS_FOR_STUCK_TRANSITION = 6; // physics substeps
|
||||
const uint32_t NUM_SUBSTEPS_FOR_SAFE_LANDING_RETRY = NUM_SUBSTEPS_PER_SECOND / 2; // retry every half second
|
||||
|
||||
class btRigidBody;
|
||||
class btCollisionWorld;
|
||||
class btDynamicsWorld;
|
||||
|
@ -53,7 +57,8 @@ public:
|
|||
virtual ~CharacterController();
|
||||
bool needsRemoval() const;
|
||||
bool needsAddition() const;
|
||||
virtual void setDynamicsWorld(btDynamicsWorld* world);
|
||||
virtual void addToWorld();
|
||||
void removeFromWorld();
|
||||
btCollisionObject* getCollisionObject() { return _rigidBody; }
|
||||
|
||||
void setGravity(float gravity);
|
||||
|
@ -120,7 +125,8 @@ public:
|
|||
|
||||
void setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale);
|
||||
|
||||
bool isEnabledAndReady() const { return _dynamicsWorld; }
|
||||
void setPhysicsEngine(const PhysicsEnginePointer& engine);
|
||||
bool isEnabledAndReady() const { return (bool)_physicsEngine; }
|
||||
bool isStuck() const { return _isStuck; }
|
||||
|
||||
void setCollisionless(bool collisionless);
|
||||
|
@ -139,6 +145,8 @@ public:
|
|||
void setSeated(bool isSeated) { _isSeated = isSeated; }
|
||||
bool getSeated() { return _isSeated; }
|
||||
|
||||
void resetStuckCounter() { _numStuckSubsteps = 0; }
|
||||
|
||||
protected:
|
||||
#ifdef DEBUG_STATE_CHANGE
|
||||
void setState(State state, const char* reason);
|
||||
|
@ -187,7 +195,6 @@ protected:
|
|||
// data for walking up steps
|
||||
btVector3 _stepPoint { 0.0f, 0.0f, 0.0f };
|
||||
btVector3 _stepNormal { 0.0f, 0.0f, 0.0f };
|
||||
bool _steppingUp { false };
|
||||
btScalar _stepHeight { 0.0f };
|
||||
btScalar _minStepHeight { 0.0f };
|
||||
btScalar _maxStepHeight { 0.0f };
|
||||
|
@ -197,6 +204,7 @@ protected:
|
|||
btScalar _radius { 0.0f };
|
||||
|
||||
btScalar _floorDistance;
|
||||
bool _steppingUp { false };
|
||||
bool _stepUpEnabled { true };
|
||||
bool _hasSupport;
|
||||
|
||||
|
@ -213,11 +221,14 @@ protected:
|
|||
bool _isStuck { false };
|
||||
bool _isSeated { false };
|
||||
|
||||
btDynamicsWorld* _dynamicsWorld { nullptr };
|
||||
PhysicsEnginePointer _physicsEngine { nullptr };
|
||||
btRigidBody* _rigidBody { nullptr };
|
||||
uint32_t _pendingFlags { 0 };
|
||||
uint32_t _previousFlags { 0 };
|
||||
uint32_t _stuckTransitionCount { 0 };
|
||||
uint32_t _numStuckSubsteps { 0 };
|
||||
|
||||
bool _inWorld { false };
|
||||
bool _zoneFlyingAllowed { true };
|
||||
bool _comfortFlyingAllowed { true };
|
||||
bool _hoverWhenUnsupported{ true };
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include "MultiSphereShape.h"
|
||||
#include "PhysicsLogging.h"
|
||||
|
||||
void SphereRegion::translate(const glm::vec3& translation) {
|
||||
for (auto &line : _lines) {
|
||||
|
@ -90,8 +91,8 @@ void SphereRegion::extractSphereRegion(std::vector<std::pair<glm::vec3, glm::vec
|
|||
}
|
||||
}
|
||||
|
||||
CollisionShapeExtractionMode MultiSphereShape::getExtractionModeByName(const QString& name) {
|
||||
CollisionShapeExtractionMode mode = CollisionShapeExtractionMode::Automatic;
|
||||
MultiSphereShape::ExtractionMode MultiSphereShape::getExtractionModeByJointName(const QString& name) {
|
||||
ExtractionMode mode = ExtractionMode::Automatic;
|
||||
bool isSim = name.indexOf("SIM") == 0;
|
||||
bool isFlow = name.indexOf("FLOW") == 0;
|
||||
bool isEye = name.indexOf("EYE") > -1;
|
||||
|
@ -105,13 +106,13 @@ CollisionShapeExtractionMode MultiSphereShape::getExtractionModeByName(const QSt
|
|||
|
||||
//bool isFinger =
|
||||
if (isNeck || isLeftFinger || isRightFinger) {
|
||||
mode = CollisionShapeExtractionMode::SpheresY;
|
||||
mode = ExtractionMode::SpheresY;
|
||||
} else if (isShoulder) {
|
||||
mode = CollisionShapeExtractionMode::SphereCollapse;
|
||||
mode = ExtractionMode::SphereCollapse;
|
||||
} else if (isRightHand || isLeftHand) {
|
||||
mode = CollisionShapeExtractionMode::SpheresXY;
|
||||
mode = ExtractionMode::SpheresXY;
|
||||
} else if (isSim || isFlow || isEye || isToe) {
|
||||
mode = CollisionShapeExtractionMode::None;
|
||||
mode = ExtractionMode::None;
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
@ -130,19 +131,33 @@ void MultiSphereShape::filterUniquePoints(const std::vector<btVector3>& kdop, st
|
|||
}
|
||||
}
|
||||
|
||||
bool MultiSphereShape::computeMultiSphereShape(int jointIndex, const QString& name, const std::vector<btVector3>& kdop, float scale) {
|
||||
bool MultiSphereShape::computeMultiSphereShape(int jointIndex, const QString& jointName, const std::vector<btVector3>& kdop, float scale) {
|
||||
_scale = scale;
|
||||
_jointIndex = jointIndex;
|
||||
_name = name;
|
||||
_mode = getExtractionModeByName(_name);
|
||||
if (_mode == CollisionShapeExtractionMode::None || kdop.size() < 4) {
|
||||
_jointName = jointName;
|
||||
auto mode = getExtractionModeByJointName(_jointName);
|
||||
KdopData kdopData = getKdopData(kdop);
|
||||
if (kdop.size() < 4 || mode == ExtractionMode::None || !kdopData._isValidShape) {
|
||||
return false;
|
||||
}
|
||||
bool needRecompute = true;
|
||||
while (needRecompute) {
|
||||
CollapsingMode collapsingMode = computeSpheres(mode, kdopData);
|
||||
needRecompute = collapsingMode != CollapsingMode::None;
|
||||
if (needRecompute) {
|
||||
mode = (CollapsingMode)collapsingMode;
|
||||
}
|
||||
}
|
||||
return mode != ExtractionMode::None;
|
||||
}
|
||||
|
||||
MultiSphereShape::KdopData MultiSphereShape::getKdopData(const std::vector<btVector3>& kdop) {
|
||||
KdopData data;
|
||||
std::vector<glm::vec3> points;
|
||||
filterUniquePoints(kdop, points);
|
||||
glm::vec3 min = glm::vec3(100.0f, 100.0f, 100.0f);
|
||||
glm::vec3 max = glm::vec3(-100.0f, -100.0f, -100.0f);
|
||||
_midPoint = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
data._origin = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
std::vector<glm::vec3> relPoints;
|
||||
for (size_t i = 0; i < points.size(); i++) {
|
||||
|
||||
|
@ -154,97 +169,111 @@ bool MultiSphereShape::computeMultiSphereShape(int jointIndex, const QString& na
|
|||
max.y = points[i].y > max.y ? points[i].y : max.y;
|
||||
max.z = points[i].z > max.z ? points[i].z : max.z;
|
||||
|
||||
_midPoint += points[i];
|
||||
data._origin += points[i];
|
||||
}
|
||||
|
||||
_midPoint /= (int)points.size();
|
||||
glm::vec3 dimensions = max - min;
|
||||
data._origin /= (int)points.size();
|
||||
glm::vec3& dimensions = data._dimensions;
|
||||
dimensions = max - min;
|
||||
if (glm::length(dimensions) == 0.0f) {
|
||||
return false;
|
||||
data._isValidShape = false;
|
||||
return data;
|
||||
}
|
||||
for (size_t i = 0; i < points.size(); i++) {
|
||||
glm::vec3 relPoint = points[i] - _midPoint;
|
||||
relPoints.push_back(relPoint);
|
||||
glm::vec3 relPoint = points[i] - data._origin;
|
||||
data._relativePoints.push_back(relPoint);
|
||||
}
|
||||
CollisionShapeExtractionMode applyMode = _mode;
|
||||
float xCorrector = dimensions.x > dimensions.y && dimensions.x > dimensions.z ? -1.0f + (dimensions.x / (0.5f * (dimensions.y + dimensions.z))) : 0.0f;
|
||||
float yCorrector = dimensions.y > dimensions.x && dimensions.y > dimensions.z ? -1.0f + (dimensions.y / (0.5f * (dimensions.x + dimensions.z))) : 0.0f;
|
||||
float zCorrector = dimensions.z > dimensions.x && dimensions.z > dimensions.y ? -1.0f + (dimensions.z / (0.5f * (dimensions.x + dimensions.y))) : 0.0f;
|
||||
glm::vec3 corrector;
|
||||
|
||||
corrector.x = dimensions.x > dimensions.y && dimensions.x > dimensions.z ? -1.0f + (dimensions.x / (0.5f * (dimensions.y + dimensions.z))) : 0.0f;
|
||||
corrector.y = dimensions.y > dimensions.x && dimensions.y > dimensions.z ? -1.0f + (dimensions.y / (0.5f * (dimensions.x + dimensions.z))) : 0.0f;
|
||||
corrector.z = dimensions.z > dimensions.x && dimensions.z > dimensions.y ? -1.0f + (dimensions.z / (0.5f * (dimensions.x + dimensions.y))) : 0.0f;
|
||||
|
||||
float xyDif = glm::abs(dimensions.x - dimensions.y);
|
||||
float xzDif = glm::abs(dimensions.x - dimensions.z);
|
||||
float yzDif = glm::abs(dimensions.y - dimensions.z);
|
||||
KdopCoefficient& diff = data._diff;
|
||||
diff.xy = glm::abs(dimensions.x - dimensions.y);
|
||||
diff.xz = glm::abs(dimensions.x - dimensions.z);
|
||||
diff.yz = glm::abs(dimensions.y - dimensions.z);
|
||||
|
||||
float xyEpsilon = (0.05f + zCorrector) * glm::max(dimensions.x, dimensions.y);
|
||||
float xzEpsilon = (0.05f + yCorrector) * glm::max(dimensions.x, dimensions.z);
|
||||
float yzEpsilon = (0.05f + xCorrector) * glm::max(dimensions.y, dimensions.z);
|
||||
KdopCoefficient& epsilon = data._epsilon;
|
||||
epsilon.xy = (0.05f + corrector.z) * glm::max(dimensions.x, dimensions.y);
|
||||
epsilon.xz = (0.05f + corrector.y) * glm::max(dimensions.x, dimensions.z);
|
||||
epsilon.yz = (0.05f + corrector.x) * glm::max(dimensions.y, dimensions.z);
|
||||
|
||||
if (xyDif < 0.5f * xyEpsilon && xzDif < 0.5f * xzEpsilon && yzDif < 0.5f * yzEpsilon) {
|
||||
applyMode = CollisionShapeExtractionMode::Sphere;
|
||||
} else if (xzDif < xzEpsilon) {
|
||||
applyMode = dimensions.y > dimensions.z ? CollisionShapeExtractionMode::SpheresY : CollisionShapeExtractionMode::SpheresXZ;
|
||||
} else if (xyDif < xyEpsilon) {
|
||||
applyMode = dimensions.z > dimensions.y ? CollisionShapeExtractionMode::SpheresZ : CollisionShapeExtractionMode::SpheresXY;
|
||||
} else if (yzDif < yzEpsilon) {
|
||||
applyMode = dimensions.x > dimensions.y ? CollisionShapeExtractionMode::SpheresX : CollisionShapeExtractionMode::SpheresYZ;
|
||||
return data;
|
||||
}
|
||||
|
||||
MultiSphereShape::CollapsingMode MultiSphereShape::computeSpheres(ExtractionMode mode, const KdopData& data) {
|
||||
_mode = mode;
|
||||
_midPoint = data._origin;
|
||||
ExtractionMode applyMode = mode;
|
||||
_spheres.clear();
|
||||
auto& diff = data._diff;
|
||||
auto& epsilon = data._epsilon;
|
||||
auto& dimensions = data._dimensions;
|
||||
|
||||
if (_mode == ExtractionMode::Automatic) {
|
||||
if (diff.xy < 0.5f * epsilon.xy && diff.xz < 0.5f * epsilon.xz && diff.yz < 0.5f * epsilon.yz) {
|
||||
applyMode =ExtractionMode::Sphere;
|
||||
} else if (diff.xz < epsilon.xz) {
|
||||
applyMode = dimensions.y > dimensions.z ? ExtractionMode::SpheresY : ExtractionMode::SpheresXZ;
|
||||
} else if (diff.xy < epsilon.xy) {
|
||||
applyMode = dimensions.z > dimensions.y ? ExtractionMode::SpheresZ : ExtractionMode::SpheresXY;
|
||||
} else if (diff.yz < epsilon.yz) {
|
||||
applyMode = dimensions.x > dimensions.y ? ExtractionMode::SpheresX : ExtractionMode::SpheresYZ;
|
||||
} else {
|
||||
applyMode = ExtractionMode::SpheresXYZ;
|
||||
}
|
||||
} else {
|
||||
applyMode = CollisionShapeExtractionMode::SpheresXYZ;
|
||||
applyMode = _mode;
|
||||
}
|
||||
|
||||
if (_mode != CollisionShapeExtractionMode::Automatic && applyMode != _mode) {
|
||||
bool isModeSphereAxis = (_mode >= CollisionShapeExtractionMode::SpheresX && _mode <= CollisionShapeExtractionMode::SpheresZ);
|
||||
bool isApplyModeComplex = (applyMode >= CollisionShapeExtractionMode::SpheresXY && applyMode <= CollisionShapeExtractionMode::SpheresXYZ);
|
||||
applyMode = (isModeSphereAxis && isApplyModeComplex) ? CollisionShapeExtractionMode::Sphere : _mode;
|
||||
}
|
||||
|
||||
std::vector<glm::vec3> axes;
|
||||
glm::vec3 axis, axis1, axis2;
|
||||
SphereShapeData sphere;
|
||||
SphereData sphere;
|
||||
switch (applyMode) {
|
||||
case CollisionShapeExtractionMode::None:
|
||||
case ExtractionMode::None:
|
||||
break;
|
||||
case CollisionShapeExtractionMode::Automatic:
|
||||
case ExtractionMode::Automatic:
|
||||
break;
|
||||
case CollisionShapeExtractionMode::Box:
|
||||
case ExtractionMode::Box:
|
||||
break;
|
||||
case CollisionShapeExtractionMode::Sphere:
|
||||
case ExtractionMode::Sphere:
|
||||
sphere._radius = 0.5f * (dimensions.x + dimensions.y + dimensions.z) / 3.0f;
|
||||
sphere._position = glm::vec3(0.0f);
|
||||
_spheres.push_back(sphere);
|
||||
break;
|
||||
case CollisionShapeExtractionMode::SphereCollapse:
|
||||
case ExtractionMode::SphereCollapse:
|
||||
sphere._radius = 0.5f * glm::min(glm::min(dimensions.x, dimensions.y), dimensions.z);
|
||||
sphere._position = glm::vec3(0.0f);
|
||||
_spheres.push_back(sphere);
|
||||
break;
|
||||
case CollisionShapeExtractionMode::SpheresX:
|
||||
case ExtractionMode::SpheresX:
|
||||
axis = 0.5f* dimensions.x * Vectors::UNIT_NEG_X;
|
||||
axes = { axis, -axis };
|
||||
break;
|
||||
case CollisionShapeExtractionMode::SpheresY:
|
||||
case ExtractionMode::SpheresY:
|
||||
axis = 0.5f* dimensions.y * Vectors::UNIT_NEG_Y;
|
||||
axes = { axis, -axis };
|
||||
break;
|
||||
case CollisionShapeExtractionMode::SpheresZ:
|
||||
case ExtractionMode::SpheresZ:
|
||||
axis = 0.5f* dimensions.z * Vectors::UNIT_NEG_Z;
|
||||
axes = { axis, -axis };
|
||||
break;
|
||||
case CollisionShapeExtractionMode::SpheresXY:
|
||||
case ExtractionMode::SpheresXY:
|
||||
axis1 = glm::vec3(0.5f * dimensions.x, 0.5f * dimensions.y, 0.0f);
|
||||
axis2 = glm::vec3(0.5f * dimensions.x, -0.5f * dimensions.y, 0.0f);
|
||||
axes = { axis1, axis2, -axis1, -axis2 };
|
||||
break;
|
||||
case CollisionShapeExtractionMode::SpheresYZ:
|
||||
case ExtractionMode::SpheresYZ:
|
||||
axis1 = glm::vec3(0.0f, 0.5f * dimensions.y, 0.5f * dimensions.z);
|
||||
axis2 = glm::vec3(0.0f, 0.5f * dimensions.y, -0.5f * dimensions.z);
|
||||
axes = { axis1, axis2, -axis1, -axis2 };
|
||||
break;
|
||||
case CollisionShapeExtractionMode::SpheresXZ:
|
||||
case ExtractionMode::SpheresXZ:
|
||||
axis1 = glm::vec3(0.5f * dimensions.x, 0.0f, 0.5f * dimensions.z);
|
||||
axis2 = glm::vec3(-0.5f * dimensions.x, 0.0f, 0.5f * dimensions.z);
|
||||
axes = { axis1, axis2, -axis1, -axis2 };
|
||||
break;
|
||||
case CollisionShapeExtractionMode::SpheresXYZ:
|
||||
case ExtractionMode::SpheresXYZ:
|
||||
for (size_t i = 0; i < CORNER_SIGNS.size(); i++) {
|
||||
axes.push_back(0.5f * (dimensions * CORNER_SIGNS[i]));
|
||||
}
|
||||
|
@ -252,24 +281,113 @@ bool MultiSphereShape::computeMultiSphereShape(int jointIndex, const QString& na
|
|||
default:
|
||||
break;
|
||||
}
|
||||
CollapsingMode collapsingMode = CollapsingMode::None;
|
||||
if (axes.size() > 0) {
|
||||
spheresFromAxes(relPoints, axes, _spheres);
|
||||
collapsingMode = spheresFromAxes(data._relativePoints, axes, _spheres);
|
||||
}
|
||||
for (size_t i = 0; i < _spheres.size(); i++) {
|
||||
_spheres[i]._position += _midPoint;
|
||||
}
|
||||
|
||||
return _mode != CollisionShapeExtractionMode::None;
|
||||
// computing fails if the shape needs to be collapsed
|
||||
return collapsingMode;
|
||||
}
|
||||
|
||||
void MultiSphereShape::spheresFromAxes(const std::vector<glm::vec3>& points, const std::vector<glm::vec3>& axes, std::vector<SphereShapeData>& spheres) {
|
||||
MultiSphereShape::CollapsingMode MultiSphereShape::getNextCollapsingMode(ExtractionMode mode, const std::vector<SphereData>& spheres) {
|
||||
auto collapsingMode = CollapsingMode::None;
|
||||
int collapseCount = 0;
|
||||
glm::vec3 collapseVector;
|
||||
for (size_t i = 0; i < spheres.size() - 1; i++) {
|
||||
for (size_t j = i + 1; j < spheres.size(); j++) {
|
||||
size_t maxRadiusIndex = spheres[i]._radius > spheres[j]._radius ? i : j;
|
||||
auto pairVector = spheres[i]._position - spheres[j]._position;
|
||||
if (glm::length(pairVector) < 0.2f * spheres[maxRadiusIndex]._radius) {
|
||||
collapseCount++;
|
||||
collapseVector += spheres[i]._axis - spheres[j]._axis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collapseCount > 0) {
|
||||
float collapseDistance = glm::length(collapseVector);
|
||||
bool allSpheresCollapse = collapseDistance < EPSILON;
|
||||
if (allSpheresCollapse) {
|
||||
collapsingMode = CollapsingMode::Sphere;
|
||||
} else {
|
||||
collapseVector = glm::normalize(collapseVector);
|
||||
bool alongAxis = collapseVector.x == 1.0f || collapseVector.y == 1.0f || collapseVector.z == 1.0f;
|
||||
bool alongPlane = collapseVector.x == 0.0f || collapseVector.y == 0.0f || collapseVector.z == 0.0f;
|
||||
int halfSphere3DCount = 4;
|
||||
int halfSphere2DCount = 2;
|
||||
bool modeSpheres3D = mode == ExtractionMode::SpheresXYZ;
|
||||
bool modeSpheres2D = mode == ExtractionMode::SpheresXY ||
|
||||
mode == ExtractionMode::SpheresYZ ||
|
||||
mode == ExtractionMode::SpheresXZ;
|
||||
bool modeSpheres1D = mode == ExtractionMode::SpheresX ||
|
||||
mode == ExtractionMode::SpheresY ||
|
||||
mode == ExtractionMode::SpheresZ;
|
||||
// SpheresXYZ will collapse along XY YZ XZ planes or X Y Z axes.
|
||||
// SpheresXY, SpheresYZ and Spheres XZ will collapse only along X Y Z axes.
|
||||
// Other occurences will be collapsed to a single sphere.
|
||||
bool isCollapseValid = (modeSpheres3D && (alongAxis || alongPlane)) ||
|
||||
(modeSpheres2D && (alongAxis));
|
||||
bool collapseToSphere = !isCollapseValid || (modeSpheres3D && collapseCount > halfSphere3DCount) ||
|
||||
(modeSpheres2D && collapseCount > halfSphere2DCount) ||
|
||||
modeSpheres1D;
|
||||
if (collapseToSphere) {
|
||||
collapsingMode = CollapsingMode::Sphere;
|
||||
} else if (modeSpheres3D) {
|
||||
if (alongAxis) {
|
||||
if (collapseVector.x == 1.0f) {
|
||||
collapsingMode = CollapsingMode::SpheresYZ;
|
||||
} else if (collapseVector.y == 1.0f) {
|
||||
collapsingMode = CollapsingMode::SpheresXZ;
|
||||
} else if (collapseVector.z == 1.0f) {
|
||||
collapsingMode = CollapsingMode::SpheresXY;
|
||||
}
|
||||
} else if (alongPlane) {
|
||||
if (collapseVector.x == 0.0f) {
|
||||
collapsingMode = CollapsingMode::SpheresX;
|
||||
} else if (collapseVector.y == 0.0f) {
|
||||
collapsingMode = CollapsingMode::SpheresY;
|
||||
} else if (collapseVector.z == 0.0f) {
|
||||
collapsingMode = CollapsingMode::SpheresZ;
|
||||
}
|
||||
}
|
||||
} else if (modeSpheres2D) {
|
||||
if (collapseVector.x == 1.0f) {
|
||||
if (mode == ExtractionMode::SpheresXY) {
|
||||
collapsingMode = CollapsingMode::SpheresY;
|
||||
} else if (mode == ExtractionMode::SpheresXZ) {
|
||||
collapsingMode = CollapsingMode::SpheresZ;
|
||||
}
|
||||
} else if (collapseVector.y == 1.0f) {
|
||||
if (mode == ExtractionMode::SpheresXY) {
|
||||
collapsingMode = CollapsingMode::SpheresX;
|
||||
} else if (mode == ExtractionMode::SpheresYZ) {
|
||||
collapsingMode = CollapsingMode::SpheresZ;
|
||||
}
|
||||
} else if (collapseVector.z == 1.0f) {
|
||||
if (mode == ExtractionMode::SpheresXZ) {
|
||||
collapsingMode = CollapsingMode::SpheresX;
|
||||
} else if (mode == ExtractionMode::SpheresYZ) {
|
||||
collapsingMode = CollapsingMode::SpheresY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return collapsingMode;
|
||||
}
|
||||
|
||||
MultiSphereShape::CollapsingMode MultiSphereShape::spheresFromAxes(const std::vector<glm::vec3>& points,
|
||||
const std::vector<glm::vec3>& axes, std::vector<SphereData>& spheres) {
|
||||
float maxRadius = 0.0f;
|
||||
float maxAverageRadius = 0.0f;
|
||||
float minAverageRadius = glm::length(points[0]);
|
||||
size_t sphereCount = axes.size();
|
||||
spheres.clear();
|
||||
for (size_t j = 0; j < sphereCount; j++) {
|
||||
SphereShapeData sphere = SphereShapeData();
|
||||
SphereData sphere = SphereData();
|
||||
sphere._axis = axes[j];
|
||||
spheres.push_back(sphere);
|
||||
}
|
||||
|
@ -318,29 +436,11 @@ void MultiSphereShape::spheresFromAxes(const std::vector<glm::vec3>& points, con
|
|||
}
|
||||
}
|
||||
// Collapse spheres if too close
|
||||
CollapsingMode collapsingMode = ExtractionMode::None;
|
||||
if (sphereCount > 1) {
|
||||
bool collapsed = false;
|
||||
for (size_t i = 0; i < spheres.size() - 1; i++) {
|
||||
for (size_t j = i + 1; j < spheres.size(); j++) {
|
||||
if (i != j) {
|
||||
size_t maxRadiusIndex = spheres[i]._radius > spheres[j]._radius ? i : j;
|
||||
if (glm::length(spheres[i]._position - spheres[j]._position) < 0.2f * spheres[maxRadiusIndex]._radius) {
|
||||
SphereShapeData newSphere;
|
||||
newSphere._position = _midPoint;
|
||||
newSphere._radius = maxRadius;
|
||||
spheres.clear();
|
||||
spheres.push_back(newSphere);
|
||||
collapsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (collapsed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
collapsingMode = getNextCollapsingMode(_mode, spheres);
|
||||
}
|
||||
return collapsingMode;
|
||||
}
|
||||
|
||||
void MultiSphereShape::connectSpheres(int index1, int index2, bool onlyEdges) {
|
||||
|
|
|
@ -19,28 +19,6 @@
|
|||
#include "BulletUtil.h"
|
||||
|
||||
|
||||
enum CollisionShapeExtractionMode {
|
||||
None = 0,
|
||||
Automatic,
|
||||
Box,
|
||||
Sphere,
|
||||
SphereCollapse,
|
||||
SpheresX,
|
||||
SpheresY,
|
||||
SpheresZ,
|
||||
SpheresXY,
|
||||
SpheresYZ,
|
||||
SpheresXZ,
|
||||
SpheresXYZ
|
||||
};
|
||||
|
||||
struct SphereShapeData {
|
||||
SphereShapeData() {}
|
||||
glm::vec3 _position;
|
||||
glm::vec3 _axis;
|
||||
float _radius;
|
||||
};
|
||||
|
||||
class SphereRegion {
|
||||
public:
|
||||
SphereRegion() {}
|
||||
|
@ -74,23 +52,79 @@ const std::vector<glm::vec3> CORNER_SIGNS = {
|
|||
|
||||
class MultiSphereShape {
|
||||
public:
|
||||
enum ExtractionMode {
|
||||
None = 0,
|
||||
Automatic,
|
||||
Box,
|
||||
Sphere,
|
||||
SphereCollapse,
|
||||
SpheresX,
|
||||
SpheresY,
|
||||
SpheresZ,
|
||||
SpheresXY,
|
||||
SpheresYZ,
|
||||
SpheresXZ,
|
||||
SpheresXYZ
|
||||
};
|
||||
|
||||
using CollapsingMode = ExtractionMode;
|
||||
const std::vector<QString> ExtractionModeNames = {
|
||||
"None",
|
||||
"Automatic",
|
||||
"Box",
|
||||
"Sphere",
|
||||
"SphereCollapse",
|
||||
"SpheresX",
|
||||
"SpheresY",
|
||||
"SpheresZ",
|
||||
"SpheresXY",
|
||||
"SpheresYZ",
|
||||
"SpheresXZ",
|
||||
"SpheresXYZ"
|
||||
};
|
||||
|
||||
struct SphereData {
|
||||
glm::vec3 _position;
|
||||
glm::vec3 _axis;
|
||||
float _radius;
|
||||
};
|
||||
|
||||
struct KdopCoefficient {
|
||||
float xy = 0.0f;
|
||||
float yz = 0.0f;
|
||||
float xz = 0.0f;
|
||||
};
|
||||
|
||||
struct KdopData {
|
||||
std::vector<glm::vec3> _relativePoints;
|
||||
bool _isValidShape{ true };
|
||||
glm::vec3 _origin;
|
||||
glm::vec3 _dimensions;
|
||||
KdopCoefficient _epsilon;
|
||||
KdopCoefficient _diff;
|
||||
};
|
||||
|
||||
MultiSphereShape() {};
|
||||
bool computeMultiSphereShape(int jointIndex, const QString& name, const std::vector<btVector3>& points, float scale = 1.0f);
|
||||
void calculateDebugLines();
|
||||
const std::vector<SphereShapeData>& getSpheresData() const { return _spheres; }
|
||||
const std::vector<SphereData>& getSpheresData() const { return _spheres; }
|
||||
const std::vector<std::pair<glm::vec3, glm::vec3>>& getDebugLines() const { return _debugLines; }
|
||||
void setScale(float scale);
|
||||
AABox& updateBoundingBox(const glm::vec3& position, const glm::quat& rotation);
|
||||
const AABox& getBoundingBox() const { return _boundingBox; }
|
||||
int getJointIndex() const { return _jointIndex; }
|
||||
QString getJointName() const { return _name; }
|
||||
QString getJointName() const { return _jointName; }
|
||||
bool isValid() const { return _spheres.size() > 0; }
|
||||
|
||||
private:
|
||||
CollisionShapeExtractionMode getExtractionModeByName(const QString& name);
|
||||
KdopData getKdopData(const std::vector<btVector3>& kdop);
|
||||
CollapsingMode computeSpheres(ExtractionMode mode, const KdopData& kdopData);
|
||||
ExtractionMode getExtractionModeByJointName(const QString& jointName);
|
||||
CollapsingMode getNextCollapsingMode(ExtractionMode mode, const std::vector<SphereData>& spheres);
|
||||
QString modeToString(CollapsingMode type) { return ExtractionModeNames[(int)type]; }
|
||||
void filterUniquePoints(const std::vector<btVector3>& kdop, std::vector<glm::vec3>& uniquePoints);
|
||||
void spheresFromAxes(const std::vector<glm::vec3>& points, const std::vector<glm::vec3>& axes,
|
||||
std::vector<SphereShapeData>& spheres);
|
||||
CollapsingMode spheresFromAxes(const std::vector<glm::vec3>& points, const std::vector<glm::vec3>& axes,
|
||||
std::vector<SphereData>& spheres);
|
||||
|
||||
void calculateSphereLines(std::vector<std::pair<glm::vec3, glm::vec3>>& outLines, const glm::vec3& center, const float& radius,
|
||||
const int& subdivisions = DEFAULT_SPHERE_SUBDIVISIONS, const glm::vec3& direction = Vectors::UNIT_Y,
|
||||
|
@ -101,10 +135,10 @@ private:
|
|||
void connectSpheres(int index1, int index2, bool onlyEdges = false);
|
||||
|
||||
int _jointIndex { -1 };
|
||||
QString _name;
|
||||
std::vector<SphereShapeData> _spheres;
|
||||
QString _jointName;
|
||||
std::vector<SphereData> _spheres;
|
||||
std::vector<std::pair<glm::vec3, glm::vec3>> _debugLines;
|
||||
CollisionShapeExtractionMode _mode;
|
||||
ExtractionMode _mode { ExtractionMode::None };
|
||||
glm::vec3 _midPoint;
|
||||
float _scale { 1.0f };
|
||||
AABox _boundingBox;
|
||||
|
|
|
@ -27,33 +27,13 @@
|
|||
#include "ThreadSafeDynamicsWorld.h"
|
||||
#include "PhysicsLogging.h"
|
||||
|
||||
static bool flipNormalsMyAvatarVsBackfacingTriangles(btManifoldPoint& cp,
|
||||
const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0,
|
||||
const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) {
|
||||
if (colObj1Wrap->getCollisionShape()->getShapeType() == TRIANGLE_SHAPE_PROXYTYPE) {
|
||||
auto triShape = static_cast<const btTriangleShape*>(colObj1Wrap->getCollisionShape());
|
||||
const btVector3* v = triShape->m_vertices1;
|
||||
btVector3 faceNormal = colObj1Wrap->getWorldTransform().getBasis() * btCross(v[1] - v[0], v[2] - v[0]);
|
||||
float nDotF = btDot(faceNormal, cp.m_normalWorldOnB);
|
||||
if (nDotF <= 0.0f && faceNormal.length2() > EPSILON) {
|
||||
faceNormal.normalize();
|
||||
// flip the contact normal to be aligned with the face normal
|
||||
cp.m_normalWorldOnB += -2.0f * nDotF * faceNormal;
|
||||
}
|
||||
}
|
||||
// return value is currently ignored but to be future-proof: return false when not modifying friction
|
||||
return false;
|
||||
}
|
||||
|
||||
PhysicsEngine::PhysicsEngine(const glm::vec3& offset) :
|
||||
_originOffset(offset),
|
||||
_myAvatarController(nullptr) {
|
||||
}
|
||||
|
||||
PhysicsEngine::~PhysicsEngine() {
|
||||
if (_myAvatarController) {
|
||||
_myAvatarController->setDynamicsWorld(nullptr);
|
||||
}
|
||||
_myAvatarController = nullptr;
|
||||
delete _collisionConfig;
|
||||
delete _collisionDispatcher;
|
||||
delete _broadphaseFilter;
|
||||
|
@ -335,27 +315,6 @@ void PhysicsEngine::stepSimulation() {
|
|||
_clock.reset();
|
||||
float timeStep = btMin(dt, MAX_TIMESTEP);
|
||||
|
||||
if (_myAvatarController) {
|
||||
DETAILED_PROFILE_RANGE(simulation_physics, "avatarController");
|
||||
BT_PROFILE("avatarController");
|
||||
// TODO: move this stuff outside and in front of stepSimulation, because
|
||||
// the updateShapeIfNecessary() call needs info from MyAvatar and should
|
||||
// be done on the main thread during the pre-simulation stuff
|
||||
if (_myAvatarController->needsRemoval()) {
|
||||
_myAvatarController->setDynamicsWorld(nullptr);
|
||||
|
||||
// We must remove any existing contacts for the avatar so that any new contacts will have
|
||||
// valid data. MyAvatar's RigidBody is the ONLY one in the simulation that does not yet
|
||||
// have a MotionState so we pass nullptr to removeContacts().
|
||||
removeContacts(nullptr);
|
||||
}
|
||||
_myAvatarController->updateShapeIfNecessary();
|
||||
if (_myAvatarController->needsAddition()) {
|
||||
_myAvatarController->setDynamicsWorld(_dynamicsWorld);
|
||||
}
|
||||
_myAvatarController->preSimulation();
|
||||
}
|
||||
|
||||
auto onSubStep = [this]() {
|
||||
this->updateContactMap();
|
||||
this->doOwnershipInfectionForConstraints();
|
||||
|
@ -364,15 +323,11 @@ void PhysicsEngine::stepSimulation() {
|
|||
int numSubsteps = _dynamicsWorld->stepSimulationWithSubstepCallback(timeStep, PHYSICS_ENGINE_MAX_NUM_SUBSTEPS,
|
||||
PHYSICS_ENGINE_FIXED_SUBSTEP, onSubStep);
|
||||
if (numSubsteps > 0) {
|
||||
BT_PROFILE("postSimulation");
|
||||
if (_myAvatarController) {
|
||||
_myAvatarController->postSimulation();
|
||||
}
|
||||
_hasOutgoingChanges = true;
|
||||
}
|
||||
|
||||
if (_physicsDebugDraw->getDebugMode()) {
|
||||
_dynamicsWorld->debugDrawWorld();
|
||||
if (_physicsDebugDraw->getDebugMode()) {
|
||||
BT_PROFILE("debugDrawWorld");
|
||||
_dynamicsWorld->debugDrawWorld();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -734,15 +689,7 @@ void PhysicsEngine::bumpAndPruneContacts(ObjectMotionState* motionState) {
|
|||
}
|
||||
|
||||
void PhysicsEngine::setCharacterController(CharacterController* character) {
|
||||
if (_myAvatarController != character) {
|
||||
if (_myAvatarController) {
|
||||
// remove the character from the DynamicsWorld immediately
|
||||
_myAvatarController->setDynamicsWorld(nullptr);
|
||||
_myAvatarController = nullptr;
|
||||
}
|
||||
// the character will be added to the DynamicsWorld later
|
||||
_myAvatarController = character;
|
||||
}
|
||||
_myAvatarController = character;
|
||||
}
|
||||
|
||||
EntityDynamicPointer PhysicsEngine::getDynamicByID(const QUuid& dynamicID) const {
|
||||
|
@ -872,14 +819,11 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) {
|
|||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::enableGlobalContactAddedCallback(bool enabled) {
|
||||
if (enabled) {
|
||||
// register contact filter to help MyAvatar pass through backfacing triangles
|
||||
gContactAddedCallback = flipNormalsMyAvatarVsBackfacingTriangles;
|
||||
} else {
|
||||
// deregister contact filter
|
||||
gContactAddedCallback = nullptr;
|
||||
}
|
||||
void PhysicsEngine::setContactAddedCallback(PhysicsEngine::ContactAddedCallback newCb) {
|
||||
// gContactAddedCallback is a special feature hook in Bullet
|
||||
// if non-null AND one of the colliding objects has btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK flag set
|
||||
// then it is called whenever a new candidate contact point is created
|
||||
gContactAddedCallback = newCb;
|
||||
}
|
||||
|
||||
struct AllContactsCallback : public btCollisionWorld::ContactResultCallback {
|
||||
|
|
|
@ -74,6 +74,10 @@ using CollisionEvents = std::vector<Collision>;
|
|||
|
||||
class PhysicsEngine {
|
||||
public:
|
||||
using ContactAddedCallback = bool (*)(btManifoldPoint& cp,
|
||||
const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0,
|
||||
const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1);
|
||||
|
||||
class Transaction {
|
||||
public:
|
||||
void clear() {
|
||||
|
@ -150,7 +154,10 @@ public:
|
|||
// See PhysicsCollisionGroups.h for mask flags.
|
||||
std::vector<ContactTestResult> contactTest(uint16_t mask, const ShapeInfo& regionShapeInfo, const Transform& regionTransform, uint16_t group = USER_COLLISION_GROUP_DYNAMIC, float threshold = 0.0f) const;
|
||||
|
||||
void enableGlobalContactAddedCallback(bool enabled);
|
||||
void setContactAddedCallback(ContactAddedCallback cb);
|
||||
|
||||
btDiscreteDynamicsWorld* getDynamicsWorld() const { return _dynamicsWorld; }
|
||||
void removeContacts(ObjectMotionState* motionState);
|
||||
|
||||
private:
|
||||
QList<EntityDynamicPointer> removeDynamicsForBody(btRigidBody* body);
|
||||
|
@ -159,8 +166,6 @@ private:
|
|||
/// \brief bump any objects that touch this one, then remove contact info
|
||||
void bumpAndPruneContacts(ObjectMotionState* motionState);
|
||||
|
||||
void removeContacts(ObjectMotionState* motionState);
|
||||
|
||||
void doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB);
|
||||
|
||||
btClock _clock;
|
||||
|
|
36
libraries/physics/src/TemporaryPairwiseCollisionFilter.cpp
Executable file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// TemporaryPairwiseCollisionFilter.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2019.08.12
|
||||
// Copyright 2018 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 "TemporaryPairwiseCollisionFilter.h"
|
||||
|
||||
bool TemporaryPairwiseCollisionFilter::isFiltered(const btCollisionObject* object) const {
|
||||
return _filteredContacts.find(object) != _filteredContacts.end();
|
||||
}
|
||||
|
||||
void TemporaryPairwiseCollisionFilter::incrementEntry(const btCollisionObject* object) {
|
||||
LastContactMap::iterator itr = _filteredContacts.find(object);
|
||||
if (itr == _filteredContacts.end()) {
|
||||
_filteredContacts.emplace(std::make_pair(object, _stepCount));
|
||||
} else {
|
||||
itr->second = _stepCount;
|
||||
}
|
||||
}
|
||||
|
||||
void TemporaryPairwiseCollisionFilter::expireOldEntries() {
|
||||
LastContactMap::iterator itr = _filteredContacts.begin();
|
||||
while (itr != _filteredContacts.end()) {
|
||||
if (itr->second < _stepCount) {
|
||||
itr = _filteredContacts.erase(itr);
|
||||
} else {
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
}
|
35
libraries/physics/src/TemporaryPairwiseCollisionFilter.h
Executable file
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// TemporaryPairwiseCollisionFilter.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2019.08.12
|
||||
// Copyright 2018 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_TemporaryPairwiseCollisionFilter_h
|
||||
#define hifi_TemporaryPairwiseCollisionFilter_h
|
||||
|
||||
#include <unordered_map>
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
|
||||
class TemporaryPairwiseCollisionFilter {
|
||||
public:
|
||||
using LastContactMap = std::unordered_map<const btCollisionObject*, uint32_t>;
|
||||
|
||||
TemporaryPairwiseCollisionFilter() { }
|
||||
|
||||
bool isFiltered(const btCollisionObject* object) const;
|
||||
void incrementEntry(const btCollisionObject* object);
|
||||
void expireOldEntries();
|
||||
void clearAllEntries() { _filteredContacts.clear(); _stepCount = 0; }
|
||||
void incrementStepCount() { ++_stepCount; }
|
||||
|
||||
protected:
|
||||
LastContactMap _filteredContacts;
|
||||
uint32_t _stepCount { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_TemporaryPairwiseCollisionFilter_h
|
|
@ -19,8 +19,9 @@
|
|||
// TODO: move everything in here to the physics library after the physics/entities library
|
||||
// dependency order is swapped.
|
||||
|
||||
const int PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 6; // Bullet will start to "lose time" at 10 FPS.
|
||||
const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 90.0f;
|
||||
const int32_t PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 6; // Bullet will start to "lose time" at 10 FPS.
|
||||
const uint32_t NUM_SUBSTEPS_PER_SECOND = 90;
|
||||
const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / (float)NUM_SUBSTEPS_PER_SECOND;
|
||||
|
||||
const float DYNAMIC_LINEAR_SPEED_THRESHOLD = 0.05f; // 5 cm/sec
|
||||
const float DYNAMIC_ANGULAR_SPEED_THRESHOLD = 0.087266f; // ~5 deg/sec
|
||||
|
|
|
@ -47,3 +47,10 @@ QQuickItem* DockWidget::getRootItem() const {
|
|||
std::shared_ptr<QQuickView> DockWidget::getQuickView() const {
|
||||
return _quickView;
|
||||
}
|
||||
|
||||
void DockWidget::resizeEvent(QResizeEvent* event) {
|
||||
// This signal is currently handled in `InteractiveWindow.cpp`. There, it's used to
|
||||
// emit a `windowGeometryChanged()` signal, which is handled by scripts
|
||||
// that need to know when to change the position of their overlay UI elements.
|
||||
emit onResizeEvent();
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
class QQuickView;
|
||||
class QQuickItem;
|
||||
class DockWidget : public QDockWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DockWidget(const QString& title, QWidget* parent = nullptr);
|
||||
~DockWidget() = default;
|
||||
|
@ -26,6 +27,13 @@ public:
|
|||
void setSource(const QUrl& url);
|
||||
QQuickItem* getRootItem() const;
|
||||
std::shared_ptr<QQuickView> getQuickView() const;
|
||||
|
||||
signals:
|
||||
void onResizeEvent();
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<QQuickView> _quickView;
|
||||
};
|
||||
|
|
After Width: | Height: | Size: 161 KiB |
After Width: | Height: | Size: 201 KiB |
After Width: | Height: | Size: 236 KiB |
After Width: | Height: | Size: 220 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 168 KiB |
After Width: | Height: | Size: 155 KiB |
After Width: | Height: | Size: 181 KiB |
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 201 KiB |
After Width: | Height: | Size: 107 KiB |
After Width: | Height: | Size: 185 KiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 150 KiB |
After Width: | Height: | Size: 182 KiB |
After Width: | Height: | Size: 171 KiB |
After Width: | Height: | Size: 211 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 230 KiB |
After Width: | Height: | Size: 221 KiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 135 KiB |
After Width: | Height: | Size: 175 KiB |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 100 KiB |
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 173 KiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 156 KiB |
After Width: | Height: | Size: 208 KiB |
After Width: | Height: | Size: 147 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 164 KiB |
After Width: | Height: | Size: 179 KiB |
After Width: | Height: | Size: 305 KiB |
After Width: | Height: | Size: 299 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 173 KiB |
After Width: | Height: | Size: 104 KiB |