Merge branch 'master' into tweak_sit_pose

This commit is contained in:
dooglife@gmail.com 2019-08-23 09:34:45 -07:00
commit fed14ac588
1041 changed files with 41916 additions and 604 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -91,6 +91,7 @@ Item {
SimplifiedControls.TextField {
id: myDisplayNameText
rightGlyph: simplifiedUI.glyphs.pencil
text: MyAvatar.displayName
maximumLength: 256
clip: true

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,2 @@
module hifi.simplifiedUI.simplifiedConstants
SimplifiedConstants 1.0 SimplifiedConstants.qml

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View 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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Some files were not shown because too many files have changed in this diff Show more