Merge branch 'master' into M17217-a

This commit is contained in:
David Rowe 2018-08-04 09:44:39 +12:00
commit 11b85e30db
103 changed files with 4596 additions and 1726 deletions

View file

@ -19,6 +19,7 @@
#include <QtNetwork/QNetworkReply>
#include <QThread>
#include <AnimationCacheScriptingInterface.h>
#include <AssetClient.h>
#include <AvatarHashMap.h>
#include <AudioInjectorManager.h>
@ -32,6 +33,7 @@
#include <ResourceCache.h>
#include <ScriptCache.h>
#include <ScriptEngines.h>
#include <SoundCacheScriptingInterface.h>
#include <SoundCache.h>
#include <UsersScriptingInterface.h>
#include <UUID.h>
@ -71,6 +73,7 @@ Agent::Agent(ReceivedMessage& message) :
DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<SoundCache>();
DependencyManager::set<SoundCacheScriptingInterface>();
DependencyManager::set<AudioScriptingInterface>();
DependencyManager::set<AudioInjectorManager>();
@ -453,8 +456,8 @@ void Agent::executeScript() {
// register ourselves to the script engine
_scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this));
_scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
_scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
_scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
_scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor);
_scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
@ -824,10 +827,6 @@ void Agent::processAgentAvatarAudio() {
void Agent::aboutToFinish() {
setIsAvatar(false);// will stop timers for sending identity packets
if (_scriptEngine) {
_scriptEngine->stop();
}
// our entity tree is going to go away so tell that to the EntityScriptingInterface
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
@ -840,9 +839,9 @@ void Agent::aboutToFinish() {
// destroy all other created dependencies
DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<ScriptEngines>();
DependencyManager::destroy<ResourceCacheSharedItems>();
DependencyManager::destroy<SoundCacheScriptingInterface>();
DependencyManager::destroy<SoundCache>();
DependencyManager::destroy<AudioScriptingInterface>();
@ -858,3 +857,11 @@ void Agent::aboutToFinish() {
_encoder = nullptr;
}
}
void Agent::stop() {
if (_scriptEngine) {
_scriptEngine->stop();
} else {
setFinished(true);
}
}

View file

@ -67,6 +67,8 @@ public slots:
void setIsAvatar(bool isAvatar);
bool isAvatar() const { return _isAvatar; }
Q_INVOKABLE virtual void stop() override;
private slots:
void requestScript();
void scriptRequestFinished();

View file

@ -21,6 +21,7 @@
#include <shared/QtHelpers.h>
#include <AccountManager.h>
#include <AddressManager.h>
#include <AnimationCacheScriptingInterface.h>
#include <Assignment.h>
#include <AvatarHashMap.h>
#include <EntityScriptingInterface.h>
@ -63,6 +64,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
auto nodeList = DependencyManager::set<NodeList>(NodeType::Unassigned, listenPort);
auto animationCache = DependencyManager::set<AnimationCache>();
DependencyManager::set<AnimationCacheScriptingInterface>();
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>(false);
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();

View file

@ -25,6 +25,9 @@
#include "AssignmentClientChildData.h"
#include "SharedUtil.h"
#include <QtCore/QJsonDocument>
#ifdef _POSIX_SOURCE
#include <sys/resource.h>
#endif
const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor";
const int WAIT_FOR_CHILD_MSECS = 1000;
@ -71,6 +74,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssignmentClientStatus, this, "handleChildStatusPacket");
adjustOSResources(std::max(_numAssignmentClientForks, _maxAssignmentClientForks));
// use QProcess to fork off a process for each of the child assignment clients
for (unsigned int i = 0; i < _numAssignmentClientForks; i++) {
spawnChildClient();
@ -372,3 +376,27 @@ bool AssignmentClientMonitor::handleHTTPRequest(HTTPConnection* connection, cons
return true;
}
void AssignmentClientMonitor::adjustOSResources(unsigned int numForks) const
{
#ifdef _POSIX_SOURCE
// QProcess on Unix uses six (I think) descriptors, some temporarily, for each child proc.
// Formula based on tests with a Ubuntu 16.04 VM.
unsigned requiredDescriptors = 30 + 6 * numForks;
struct rlimit descLimits;
if (getrlimit(RLIMIT_NOFILE, &descLimits) == 0) {
if (descLimits.rlim_cur < requiredDescriptors) {
descLimits.rlim_cur = requiredDescriptors;
if (setrlimit(RLIMIT_NOFILE, &descLimits) == 0) {
qDebug() << "Resetting descriptor limit to" << requiredDescriptors;
} else {
const char *const errorString = strerror(errno);
qDebug() << "Failed to reset descriptor limit to" << requiredDescriptors << ":" << errorString;
}
}
} else {
const char *const errorString = strerror(errno);
qDebug() << "Failed to read descriptor limit:" << errorString;
}
#endif
}

View file

@ -55,6 +55,7 @@ public slots:
private:
void spawnChildClient();
void simultaneousWaitOnChildren(int waitMsecs);
void adjustOSResources(unsigned int numForks) const;
QTimer _checkSparesTimer; // every few seconds see if it need fewer or more spare children

View file

@ -17,7 +17,6 @@
#include "EntityServer.h"
EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
OctreeSendThread(myServer, node)
{
@ -100,7 +99,7 @@ void EntityTreeSendThread::preDistributionProcessing() {
}
}
void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene) {
if (viewFrustumChanged || _traversal.finished()) {
EntityTreeElementPointer root = std::dynamic_pointer_cast<EntityTreeElement>(_myServer->getOctree()->getRoot());
@ -111,7 +110,7 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O
int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
newView.lodScaleFactor = powf(2.0f, lodLevelOffset);
startNewTraversal(newView, root);
// When the viewFrustum changed the sort order may be incorrect, so we re-sort
@ -156,7 +155,20 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O
OctreeServer::trackTreeTraverseTime((float)(usecTimestampNow() - startTime));
}
OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
bool sendComplete = OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
if (sendComplete && nodeData->wantReportInitialCompletion() && _traversal.finished()) {
// Dealt with all nearby entities.
nodeData->setReportInitialCompletion(false);
// Send EntityQueryInitialResultsComplete reliable packet ...
auto initialCompletion = NLPacket::create(PacketType::EntityQueryInitialResultsComplete,
sizeof(OCTREE_PACKET_SEQUENCE), true);
initialCompletion->writePrimitive(OCTREE_PACKET_SEQUENCE(nodeData->getSequenceNumber() - 1U));
DependencyManager::get<NodeList>()->sendPacket(std::move(initialCompletion), *node);
}
return sendComplete;
}
bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID,
@ -301,6 +313,7 @@ void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, En
bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
if (_sendQueue.empty()) {
params.stopReason = EncodeBitstreamParams::FINISHED;
OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME);
return false;
}

View file

@ -31,7 +31,7 @@ public:
EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node);
protected:
void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene) override;
private slots:

View file

@ -211,10 +211,9 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
//_totalWastedBytes += 0;
_trueBytesSent += numBytes;
numPackets++;
NLPacket& sentPacket = nodeData->getPacket();
if (debug) {
NLPacket& sentPacket = nodeData->getPacket();
sentPacket.seek(sizeof(OCTREE_PACKET_FLAGS));
OCTREE_PACKET_SEQUENCE sequence;
@ -231,9 +230,9 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
// second packet
OctreeServer::didCallWriteDatagram(this);
DependencyManager::get<NodeList>()->sendUnreliablePacket(nodeData->getPacket(), *node);
DependencyManager::get<NodeList>()->sendUnreliablePacket(sentPacket, *node);
numBytes = nodeData->getPacket().getDataSize();
numBytes = sentPacket.getDataSize();
_totalBytes += numBytes;
_totalPackets++;
// we count wasted bytes here because we were unable to fit the stats packet
@ -243,8 +242,6 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
numPackets++;
if (debug) {
NLPacket& sentPacket = nodeData->getPacket();
sentPacket.seek(sizeof(OCTREE_PACKET_FLAGS));
OCTREE_PACKET_SEQUENCE sequence;
@ -265,9 +262,10 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
if (nodeData->isPacketWaiting() && !nodeData->isShuttingDown()) {
// just send the octree packet
OctreeServer::didCallWriteDatagram(this);
DependencyManager::get<NodeList>()->sendUnreliablePacket(nodeData->getPacket(), *node);
NLPacket& sentPacket = nodeData->getPacket();
DependencyManager::get<NodeList>()->sendUnreliablePacket(sentPacket, *node);
int numBytes = nodeData->getPacket().getDataSize();
int numBytes = sentPacket.getDataSize();
_totalBytes += numBytes;
_totalPackets++;
int thisWastedBytes = udt::MAX_PACKET_SIZE - numBytes;
@ -276,8 +274,6 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
_trueBytesSent += numBytes;
if (debug) {
NLPacket& sentPacket = nodeData->getPacket();
sentPacket.seek(sizeof(OCTREE_PACKET_FLAGS));
OCTREE_PACKET_SEQUENCE sequence;
@ -434,7 +430,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
return _truePacketsSent;
}
void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) {
bool OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) {
// calculate max number of packets that can be sent during this interval
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
@ -517,4 +513,6 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
<< " maxPacketsPerInterval = " << maxPacketsPerInterval
<< " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval;
}
return params.stopReason == EncodeBitstreamParams::FINISHED;
}

View file

@ -52,7 +52,7 @@ protected:
/// Implements generic processing behavior for this thread.
virtual bool process() override;
virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
virtual bool traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene);
virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) = 0;

View file

@ -26,7 +26,7 @@
#include <ResourceManager.h>
#include <ScriptCache.h>
#include <ScriptEngines.h>
#include <SoundCache.h>
#include <SoundCacheScriptingInterface.h>
#include <UUID.h>
#include <WebSocketServerClass.h>
@ -66,6 +66,7 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<SoundCache>();
DependencyManager::set<SoundCacheScriptingInterface>();
DependencyManager::set<AudioInjectorManager>();
DependencyManager::set<ScriptCache>();
@ -438,7 +439,7 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
auto webSocketServerConstructorValue = newEngine->newFunction(WebSocketServerClass::constructor);
newEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
newEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
newEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();

View file

@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC69.zip
URL_MD5 e2467b08de069da7e22ec8e032435592
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC70v2.zip
URL_MD5 35fcc8e635e71d0b00a08455a2582448
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -20,6 +20,7 @@ Original.Button {
property int color: 0
property int colorScheme: hifi.colorSchemes.light
property int fontSize: hifi.fontSizes.buttonLabel
property int radius: hifi.buttons.radius
property alias implicitTextWidth: buttonText.implicitWidth
property string buttonGlyph: "";
property int fontCapitalization: Font.AllUppercase
@ -46,7 +47,7 @@ Original.Button {
}
background: Rectangle {
radius: hifi.buttons.radius
radius: control.radius
border.width: (control.color === hifi.buttons.none ||
(control.color === hifi.buttons.noneBorderless && control.hovered) ||

View file

@ -124,6 +124,11 @@ SpinBox {
color: spinBox.up.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray
}
}
up.onPressedChanged: {
if(value) {
spinBox.forceActiveFocus();
}
}
down.indicator: Item {
x: spinBox.width - implicitWidth - 5
@ -138,6 +143,11 @@ SpinBox {
color: spinBox.down.pressed || spinBox.down.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray
}
}
down.onPressedChanged: {
if(value) {
spinBox.forceActiveFocus();
}
}
HifiControls.Label {
id: spinBoxLabel

View file

@ -476,17 +476,13 @@ Rectangle {
anchors.verticalCenter: avatarNameLabel.verticalCenter
glyphText: "."
glyphSize: 22
MouseArea {
anchors.fill: parent
onClicked: {
popup.showSpecifyAvatarUrl(currentAvatar.avatarUrl, function() {
var url = popup.inputText.text;
emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url})
}, function(link) {
Qt.openUrlExternally(link);
});
}
onClicked: {
popup.showSpecifyAvatarUrl(currentAvatar.avatarUrl, function() {
var url = popup.inputText.text;
emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url})
}, function(link) {
Qt.openUrlExternally(link);
});
}
}
@ -496,12 +492,8 @@ Rectangle {
glyphText: "\ue02e"
visible: avatarWearablesCount !== 0
MouseArea {
anchors.fill: parent
onClicked: {
adjustWearables.open(currentAvatar);
}
onClicked: {
adjustWearables.open(currentAvatar);
}
}

View file

@ -326,7 +326,7 @@ Rectangle {
height: 40
anchors.right: parent.right
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.dark;
colorScheme: hifi.colorSchemes.light;
text: "TAKE IT OFF"
onClicked: wearableDeleted(root.avatarName, getCurrentWearable().id);
enabled: wearablesCombobox.model.count !== 0

View file

@ -1,25 +1,42 @@
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import QtQuick 2.9
import QtGraphicalEffects 1.0
ShadowRectangle {
Item {
id: root
width: 44
height: 28
AvatarAppStyle {
id: style
signal clicked();
HifiControlsUit.Button {
id: button
HifiConstants {
id: hifi
}
anchors.fill: parent
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
radius: 3
onClicked: root.clicked();
}
gradient: Gradient {
GradientStop { position: 0.0; color: style.colors.blueHighlight }
GradientStop { position: 1.0; color: style.colors.blueAccent }
DropShadow {
id: shadow
anchors.fill: button
radius: 6
horizontalOffset: 0
verticalOffset: 3
color: Qt.rgba(0, 0, 0, 0.25)
source: button
}
property alias glyphText: glyph.text
property alias glyphRotation: glyph.rotation
property alias glyphSize: glyph.size
radius: 3
HiFiGlyphs {
id: glyph
color: 'white'

View file

@ -876,7 +876,7 @@ Rectangle {
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
onLinkActivated: {
sendToScript({method: 'checkout_goToPurchases'});
sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
}
}

View file

@ -142,7 +142,7 @@ Item {
Timer {
id: refreshTimer;
interval: 4000;
interval: 6000;
onTriggered: {
if (transactionHistory.atYBeginning) {
console.log("Refreshing 1st Page of Recent Activity...");
@ -211,6 +211,7 @@ Item {
HifiModels.PSFListModel {
id: transactionHistoryModel;
property int lastPendingCount: 0;
listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly.
listView: transactionHistory;
itemsPerPage: 6;
@ -221,8 +222,26 @@ Item {
processPage: function (data) {
console.debug('processPage', transactionHistoryModel.listModelName, JSON.stringify(data));
var result, pending; // Set up or get the accumulator for pending.
if (transactionHistoryModel.currentPageToRetrieve == 1) {
pending = {transaction_type: "pendingCount", count: 0};
if (transactionHistoryModel.currentPageToRetrieve === 1) {
// The initial data elements inside the ListModel MUST contain all keys
// that will be used in future data.
pending = {
transaction_type: "pendingCount",
count: 0,
created_at: 0,
hfc_text: "",
id: "",
message: "",
place_name: "",
received_certs: 0,
received_money: 0,
recipient_name: "",
sender_name: "",
sent_certs: 0,
sent_money: 0,
status: "",
transaction_text: ""
};
result = [pending];
} else {
pending = transactionHistoryModel.get(0);
@ -239,6 +258,15 @@ Item {
}
});
if (lastPendingCount === 0) {
lastPendingCount = pending.count;
} else {
if (lastPendingCount !== pending.count) {
transactionHistoryModel.getNextPageIfNotEnoughVerticalResults();
}
lastPendingCount = pending.count;
}
// Only auto-refresh if the user hasn't scrolled
// and there is more data to grab
if (transactionHistory.atYBeginning && data.history.length) {
@ -257,13 +285,13 @@ Item {
ListView {
id: transactionHistory;
ScrollBar.vertical: ScrollBar {
policy: transactionHistory.contentHeight > parent.parent.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded;
parent: transactionHistory.parent;
anchors.top: transactionHistory.top;
anchors.left: transactionHistory.right;
anchors.leftMargin: 4;
anchors.bottom: transactionHistory.bottom;
width: 20;
policy: transactionHistory.contentHeight > parent.parent.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded;
parent: transactionHistory.parent;
anchors.top: transactionHistory.top;
anchors.left: transactionHistory.right;
anchors.leftMargin: 4;
anchors.bottom: transactionHistory.bottom;
width: 20;
}
anchors.centerIn: parent;
width: parent.width - 12;
@ -340,7 +368,7 @@ Item {
}
HifiControlsUit.Separator {
colorScheme: 1;
colorScheme: 1;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;

View file

@ -38,6 +38,16 @@ ListModel {
onSearchFilterChanged: if (initialized) { getFirstPage('delayClear'); }
onTagsFilterChanged: if (initialized) { getFirstPage('delayClear'); }
// When considering a value for `itemsPerPage` in YOUR model, consider the following:
// - If your ListView delegates are of variable width/height, ensure you select
// an `itemsPerPage` value that would be sufficient to show one full page of data
// if all of the delegates were at their minimum heights.
// - If your first ListView delegate contains some special data (as in WalletHome's
// "Recent Activity" view), beware that your `itemsPerPage` value may _never_ reasonably be
// high enough such that the first page of data causes the view to be one-screen in height
// after retrieving the first page. This means data will automatically pop-in (after a short delay)
// until the combined heights of your View's delegates reach one-screen in height OR there is
// no more data to retrieve. See "needsMoreVerticalResults()" below.
property int itemsPerPage: 100;
// State.
@ -81,12 +91,35 @@ ListModel {
function getNextPageIfVerticalScroll() {
if (needsEarlyYFetch()) { getNextPage(); }
}
function needsMoreHorizontalResults() {
return flickable
&& currentPageToRetrieve > 0
&& flickable.contentWidth < flickable.width;
}
function needsMoreVerticalResults() {
return flickable
&& currentPageToRetrieve > 0
&& flickable.contentHeight < flickable.height;
}
function getNextPageIfNotEnoughHorizontalResults() {
if (needsMoreHorizontalResults()) {
getNextPage();
}
}
function getNextPageIfNotEnoughVerticalResults() {
if (needsMoreVerticalResults()) {
getNextPage();
}
}
Component.onCompleted: {
initialized = true;
if (flickable && pageAhead > 0.0) {
// Pun: Scrollers are usually one direction or another, such that only one of the following will actually fire.
flickable.contentXChanged.connect(getNextPageIfHorizontalScroll);
flickable.contentYChanged.connect(getNextPageIfVerticalScroll);
flickable.contentWidthChanged.connect(getNextPageIfNotEnoughHorizontalResults);
flickable.contentHeightChanged.connect(getNextPageIfNotEnoughVerticalResults);
}
}

View file

@ -63,6 +63,7 @@
#include <AddressManager.h>
#include <AnimDebugDraw.h>
#include <BuildInfo.h>
#include <AnimationCacheScriptingInterface.h>
#include <AssetClient.h>
#include <AssetUpload.h>
#include <AutoUpdater.h>
@ -98,6 +99,8 @@
#include <MainWindow.h>
#include <MappingRequest.h>
#include <MessagesClient.h>
#include <model-networking/ModelCacheScriptingInterface.h>
#include <model-networking/TextureCacheScriptingInterface.h>
#include <ModelEntityItem.h>
#include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
@ -127,7 +130,7 @@
#include <ScriptEngines.h>
#include <ScriptCache.h>
#include <ShapeEntityItem.h>
#include <SoundCache.h>
#include <SoundCacheScriptingInterface.h>
#include <ui/TabletScriptingInterface.h>
#include <ui/ToolbarScriptingInterface.h>
#include <InteractiveWindow.h>
@ -143,6 +146,7 @@
#include <QmlFragmentClass.h>
#include <Preferences.h>
#include <display-plugins/CompositorHelper.h>
#include <display-plugins/hmd/HmdDisplayPlugin.h>
#include <trackers/EyeTracker.h>
#include <avatars-renderer/ScriptAvatar.h>
#include <RenderableEntityItem.h>
@ -374,6 +378,7 @@ static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SI
static const uint32_t INVALID_FRAME = UINT32_MAX;
static const float PHYSICS_READY_RANGE = 3.0f; // how far from avatar to check for entities that aren't ready for simulation
static const float INITIAL_QUERY_RADIUS = 10.0f; // priority radius for entities before physics enabled
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
@ -866,16 +871,20 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<recording::ClipCache>();
DependencyManager::set<GeometryCache>();
DependencyManager::set<ModelCache>();
DependencyManager::set<ModelCacheScriptingInterface>();
DependencyManager::set<ScriptCache>();
DependencyManager::set<SoundCache>();
DependencyManager::set<SoundCacheScriptingInterface>();
DependencyManager::set<DdeFaceTracker>();
DependencyManager::set<EyeTracker>();
DependencyManager::set<AudioClient>();
DependencyManager::set<AudioScope>();
DependencyManager::set<DeferredLightingEffect>();
DependencyManager::set<TextureCache>();
DependencyManager::set<TextureCacheScriptingInterface>();
DependencyManager::set<FramebufferCache>();
DependencyManager::set<AnimationCache>();
DependencyManager::set<AnimationCacheScriptingInterface>();
DependencyManager::set<ModelBlender>();
DependencyManager::set<UsersScriptingInterface>();
DependencyManager::set<AvatarManager>();
@ -2562,12 +2571,18 @@ Application::~Application() {
DependencyManager::destroy<CompositorHelper>(); // must be destroyed before the FramebufferCache
DependencyManager::destroy<SoundCacheScriptingInterface>();
DependencyManager::destroy<AvatarManager>();
DependencyManager::destroy<AnimationCacheScriptingInterface>();
DependencyManager::destroy<AnimationCache>();
DependencyManager::destroy<FramebufferCache>();
DependencyManager::destroy<TextureCacheScriptingInterface>();
DependencyManager::destroy<TextureCache>();
DependencyManager::destroy<ModelCacheScriptingInterface>();
DependencyManager::destroy<ModelCache>();
DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<SoundCacheScriptingInterface>();
DependencyManager::destroy<SoundCache>();
DependencyManager::destroy<OctreeStatsProvider>();
DependencyManager::destroy<GeometryCache>();
@ -2717,6 +2732,10 @@ void Application::initializeDisplayPlugins() {
QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged,
[this](const QSize& size) { resizeGL(); });
QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
if (displayPlugin->isHmd()) {
QObject::connect(dynamic_cast<HmdDisplayPlugin*>(displayPlugin.get()), &HmdDisplayPlugin::hmdMountedChanged,
DependencyManager::get<HMDScriptingInterface>().data(), &HMDScriptingInterface::mountedChanged);
}
}
// The default display plugin needs to be activated first, otherwise the display plugin thread
@ -2989,10 +3008,11 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
// Caches
surfaceContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
surfaceContext->setContextProperty("TextureCache", DependencyManager::get<TextureCache>().data());
surfaceContext->setContextProperty("ModelCache", DependencyManager::get<ModelCache>().data());
surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
surfaceContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
surfaceContext->setContextProperty("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data());
surfaceContext->setContextProperty("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data());
surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
@ -5480,12 +5500,14 @@ void Application::update(float deltaTime) {
// we haven't yet enabled physics. we wait until we think we have all the collision information
// for nearby entities before starting bullet up.
quint64 now = usecTimestampNow();
const int PHYSICS_CHECK_TIMEOUT = 2 * USECS_PER_SECOND;
if (now - _lastPhysicsCheckTime > PHYSICS_CHECK_TIMEOUT || _fullSceneReceivedCounter > _fullSceneCounterAtLastPhysicsCheck) {
// Check for flagged EntityData having arrived.
auto entityTreeRenderer = getEntities();
if (isServerlessMode() ||
(entityTreeRenderer && _octreeProcessor.octreeSequenceIsComplete(entityTreeRenderer->getLastOctreeMessageSequence()) )) {
// we've received a new full-scene octree stats packet, or it's been long enough to try again anyway
_lastPhysicsCheckTime = now;
_fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter;
_lastQueriedViews.clear(); // Force new view.
// 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
@ -6134,11 +6156,23 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType) {
return; // bail early if settings are not loaded
}
_octreeQuery.setConicalViews(_conicalViews);
const bool isModifiedQuery = !_physicsEnabled;
if (isModifiedQuery) {
// Create modified view that is a simple sphere.
ConicalViewFrustum sphericalView;
sphericalView.setSimpleRadius(INITIAL_QUERY_RADIUS);
_octreeQuery.setConicalViews({ sphericalView });
_octreeQuery.setOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE);
static constexpr float MIN_LOD_ADJUST = -20.0f;
_octreeQuery.setBoundaryLevelAdjust(MIN_LOD_ADJUST);
} else {
_octreeQuery.setConicalViews(_conicalViews);
auto lodManager = DependencyManager::get<LODManager>();
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
}
_octreeQuery.setReportInitialCompletion(isModifiedQuery);
auto lodManager = DependencyManager::get<LODManager>();
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
auto nodeList = DependencyManager::get<NodeList>();
@ -6286,6 +6320,7 @@ void Application::clearDomainOctreeDetails() {
_octreeServerSceneStats.clear();
});
_octreeProcessor.resetCompletionSequenceNumber();
// reset the model renderer
getEntities()->clear();
@ -6606,10 +6641,10 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("Pointers", DependencyManager::get<PointerScriptingInterface>().data());
// Caches
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get<TextureCache>().data());
scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get<ModelCache>().data());
scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface);

View file

@ -19,6 +19,7 @@
AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
assert(_avatar);
_type = MOTIONSTATE_TYPE_AVATAR;
cacheShapeDiameter();
}
void AvatarMotionState::handleEasyChanges(uint32_t& flags) {
@ -57,9 +58,6 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
const btCollisionShape* AvatarMotionState::computeNewShape() {
ShapeInfo shapeInfo;
std::static_pointer_cast<Avatar>(_avatar)->computeShapeInfo(shapeInfo);
glm::vec3 halfExtents = shapeInfo.getHalfExtents();
halfExtents.y = 0.0f;
_diameter = 2.0f * glm::length(halfExtents);
return getShapeManager()->getShape(shapeInfo);
}
@ -98,6 +96,10 @@ void AvatarMotionState::setWorldTransform(const btTransform& worldTrans) {
btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget;
_body->setLinearVelocity(velocity);
_body->setAngularVelocity(glmToBullet(getObjectAngularVelocity()));
// slam its rotation
btTransform newTransform = worldTrans;
newTransform.setRotation(glmToBullet(getObjectRotation()));
_body->setWorldTransform(newTransform);
}
}
@ -141,7 +143,10 @@ glm::vec3 AvatarMotionState::getObjectLinearVelocity() const {
// virtual
glm::vec3 AvatarMotionState::getObjectAngularVelocity() const {
return _avatar->getWorldAngularVelocity();
// HACK: avatars use a capusle collision shape and their angularVelocity in the local simulation is unimportant.
// Therefore, as optimization toward support for larger crowds we ignore it and return zero.
//return _avatar->getWorldAngularVelocity();
return glm::vec3(0.0f);
}
// virtual
@ -174,3 +179,28 @@ float AvatarMotionState::getMass() const {
return std::static_pointer_cast<Avatar>(_avatar)->computeMass();
}
void AvatarMotionState::cacheShapeDiameter() {
if (_shape) {
// shape is capsuleY
btVector3 aabbMin, aabbMax;
btTransform transform;
transform.setIdentity();
_shape->getAabb(transform, aabbMin, aabbMax);
_diameter = (aabbMax - aabbMin).getX();
} else {
_diameter = 0.0f;
}
}
void AvatarMotionState::setRigidBody(btRigidBody* body) {
ObjectMotionState::setRigidBody(body);
if (_body) {
// remove angular dynamics from this body
_body->setAngularFactor(0.0f);
}
}
void AvatarMotionState::setShape(const btCollisionShape* shape) {
ObjectMotionState::setShape(shape);
cacheShapeDiameter();
}

View file

@ -74,6 +74,10 @@ public:
friend class Avatar;
protected:
void setRigidBody(btRigidBody* body) override;
void setShape(const btCollisionShape* shape) override;
void cacheShapeDiameter();
// the dtor had been made protected to force the compiler to verify that it is only
// ever called by the Avatar class dtor.
~AvatarMotionState();

View file

@ -21,9 +21,9 @@ OctreePacketProcessor::OctreePacketProcessor() {
setObjectName("Octree Packet Processor");
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerDirectListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
const PacketReceiver::PacketTypeList octreePackets =
{ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase, PacketType::EntityQueryInitialResultsComplete };
packetReceiver.registerDirectListenerForTypes(octreePackets, this, "handleOctreePacket");
}
void OctreePacketProcessor::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
@ -111,8 +111,36 @@ void OctreePacketProcessor::processPacket(QSharedPointer<ReceivedMessage> messag
}
} break;
case PacketType::EntityQueryInitialResultsComplete: {
// Read sequence #
OCTREE_PACKET_SEQUENCE completionNumber;
message->readPrimitive(&completionNumber);
_completionSequenceNumber = completionNumber;
} break;
default: {
// nothing to do
} break;
}
}
void OctreePacketProcessor::resetCompletionSequenceNumber() {
_completionSequenceNumber = INVALID_SEQUENCE;
}
namespace {
template<typename T> bool lessThanWraparound(int a, int b) {
constexpr int MAX_T_VALUE = std::numeric_limits<T>::max();
if (b <= a) {
b += MAX_T_VALUE;
}
return (b - a) < (MAX_T_VALUE / 2);
}
}
bool OctreePacketProcessor::octreeSequenceIsComplete(int sequenceNumber) const {
// If we've received the flagged seq # and the current one is >= it.
return _completionSequenceNumber != INVALID_SEQUENCE &&
!lessThanWraparound<OCTREE_PACKET_SEQUENCE>(sequenceNumber, _completionSequenceNumber);
}

View file

@ -22,13 +22,23 @@ class OctreePacketProcessor : public ReceivedPacketProcessor {
public:
OctreePacketProcessor();
bool octreeSequenceIsComplete(int sequenceNumber) const;
signals:
void packetVersionMismatch();
public slots:
void resetCompletionSequenceNumber();
protected:
virtual void processPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) override;
private slots:
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
private:
static constexpr int INVALID_SEQUENCE = -1;
std::atomic<int> _completionSequenceNumber { INVALID_SEQUENCE };
};
#endif // hifi_OctreePacketProcessor_h

View file

@ -351,17 +351,6 @@ signals:
*/
bool shouldShowHandControllersChanged();
/**jsdoc
* Triggered when the <code>HMD.mounted</code> property value changes.
* @function HMD.mountedChanged
* @returns {Signal}
* @example <caption>Report when there's a change in the HMD being worn.</caption>
* HMD.mountedChanged.connect(function () {
* print("Mounted changed. HMD is mounted: " + HMD.mounted);
* });
*/
void mountedChanged();
public:
HMDScriptingInterface();
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);

View file

@ -55,7 +55,7 @@
#include "scripting/AccountServicesScriptingInterface.h"
#include <plugins/InputConfiguration.h>
#include "ui/Snapshot.h"
#include "SoundCache.h"
#include "SoundCacheScriptingInterface.h"
#include "raypick/PointerScriptingInterface.h"
#include <display-plugins/CompositorHelper.h>
#include "AboutUtil.h"
@ -253,7 +253,7 @@ void Web3DOverlay::setupQmlSurface() {
_webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
_webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());

View file

@ -24,7 +24,7 @@ AnimBlendLinear::~AnimBlendLinear() {
}
const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
_alpha = animVars.lookup(_alphaVar, _alpha);
@ -43,6 +43,9 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con
evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, dt);
}
processOutputJoints(triggersOut);
return _poses;
}
@ -51,7 +54,7 @@ const AnimPoseVec& AnimBlendLinear::getPosesInternal() const {
return _poses;
}
void AnimBlendLinear::evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha,
void AnimBlendLinear::evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, AnimVariantMap& triggersOut, float alpha,
size_t prevPoseIndex, size_t nextPoseIndex, float dt) {
if (prevPoseIndex == nextPoseIndex) {
// this can happen if alpha is on an integer boundary

View file

@ -30,7 +30,7 @@ public:
AnimBlendLinear(const QString& id, float alpha);
virtual ~AnimBlendLinear() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
@ -38,7 +38,7 @@ protected:
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;
void evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha,
void evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, AnimVariantMap& triggersOut, float alpha,
size_t prevPoseIndex, size_t nextPoseIndex, float dt);
AnimPoseVec _poses;

View file

@ -26,7 +26,7 @@ AnimBlendLinearMove::~AnimBlendLinearMove() {
}
const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
assert(_children.size() == _characteristicSpeeds.size());
@ -54,6 +54,9 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars,
setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut);
evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime);
}
processOutputJoints(triggersOut);
return _poses;
}
@ -62,7 +65,7 @@ const AnimPoseVec& AnimBlendLinearMove::getPosesInternal() const {
return _poses;
}
void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha,
void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, AnimVariantMap& triggersOut, float alpha,
size_t prevPoseIndex, size_t nextPoseIndex,
float prevDeltaTime, float nextDeltaTime) {
if (prevPoseIndex == nextPoseIndex) {
@ -82,7 +85,7 @@ void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVar
}
void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIndex, int nextPoseIndex,
float* prevDeltaTimeOut, float* nextDeltaTimeOut, Triggers& triggersOut) {
float* prevDeltaTimeOut, float* nextDeltaTimeOut, AnimVariantMap& triggersOut) {
const float FRAMES_PER_SECOND = 30.0f;
auto prevClipNode = std::dynamic_pointer_cast<AnimClip>(_children[prevPoseIndex]);
@ -109,7 +112,7 @@ void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIn
// detect loop trigger events
if (_phase >= 1.0f) {
triggersOut.push_back(_id + "Loop");
triggersOut.setTrigger(_id + "Loop");
_phase = glm::fract(_phase);
}

View file

@ -39,7 +39,7 @@ public:
AnimBlendLinearMove(const QString& id, float alpha, float desiredSpeed, const std::vector<float>& characteristicSpeeds);
virtual ~AnimBlendLinearMove() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
void setDesiredSpeedVar(const QString& desiredSpeedVar) { _desiredSpeedVar = desiredSpeedVar; }
@ -48,12 +48,12 @@ protected:
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;
void evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha,
void evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, AnimVariantMap& triggersOut, float alpha,
size_t prevPoseIndex, size_t nextPoseIndex,
float prevDeltaTime, float nextDeltaTime);
void setFrameAndPhase(float dt, float alpha, int prevPoseIndex, int nextPoseIndex,
float* prevDeltaTimeOut, float* nextDeltaTimeOut, Triggers& triggersOut);
float* prevDeltaTimeOut, float* nextDeltaTimeOut, AnimVariantMap& triggersOut);
virtual void setCurrentFrameInternal(float frame) override;

View file

@ -0,0 +1,159 @@
//
// AnimChain.h
//
// Created by Anthony J. Thibault on 7/16/2018.
// Copyright (c) 2018 High Fidelity, Inc. All rights reserved.
//
// 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_AnimChain
#define hifi_AnimChain
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <DebugDraw.h>
template <int N>
class AnimChainT {
public:
AnimChainT() {}
AnimChainT(const AnimChainT& orig) {
_top = orig._top;
for (int i = 0; i < _top; i++) {
_chain[i] = orig._chain[i];
}
}
AnimChainT& operator=(const AnimChainT& orig) {
_top = orig._top;
for (int i = 0; i < _top; i++) {
_chain[i] = orig._chain[i];
}
return *this;
}
bool buildFromRelativePoses(const AnimSkeleton::ConstPointer& skeleton, const AnimPoseVec& relativePoses, int tipIndex) {
_top = 0;
// iterate through the skeleton parents, from the tip to the base, copying over relativePoses into the chain.
for (int jointIndex = tipIndex; jointIndex != -1; jointIndex = skeleton->getParentIndex(jointIndex)) {
if (_top >= N) {
assert(_top < N);
// stack overflow
return false;
}
_chain[_top].relativePose = relativePoses[jointIndex];
_chain[_top].jointIndex = jointIndex;
_chain[_top].dirty = true;
_top++;
}
buildDirtyAbsolutePoses();
return true;
}
const AnimPose& getAbsolutePoseFromJointIndex(int jointIndex) const {
for (int i = 0; i < _top; i++) {
if (_chain[i].jointIndex == jointIndex) {
return _chain[i].absolutePose;
}
}
return AnimPose::identity;
}
bool setRelativePoseAtJointIndex(int jointIndex, const AnimPose& relativePose) {
bool foundIndex = false;
for (int i = _top - 1; i >= 0; i--) {
if (_chain[i].jointIndex == jointIndex) {
_chain[i].relativePose = relativePose;
foundIndex = true;
}
// all child absolute poses are now dirty
if (foundIndex) {
_chain[i].dirty = true;
}
}
return foundIndex;
}
void buildDirtyAbsolutePoses() {
// the relative and absolute pose is the same for the base of the chain.
_chain[_top - 1].absolutePose = _chain[_top - 1].relativePose;
_chain[_top - 1].dirty = false;
// iterate chain from base to tip, concatinating the relative poses to build the absolute poses.
for (int i = _top - 1; i > 0; i--) {
AnimChainElem& parent = _chain[i];
AnimChainElem& child = _chain[i - 1];
if (child.dirty) {
child.absolutePose = parent.absolutePose * child.relativePose;
child.dirty = false;
}
}
}
void blend(const AnimChainT& srcChain, float alpha) {
// make sure chains have same lengths
assert(srcChain._top == _top);
if (srcChain._top != _top) {
return;
}
// only blend the relative poses
for (int i = 0; i < _top; i++) {
_chain[i].relativePose.blend(srcChain._chain[i].relativePose, alpha);
_chain[i].dirty = true;
}
}
int size() const {
return _top;
}
void outputRelativePoses(AnimPoseVec& relativePoses) {
for (int i = 0; i < _top; i++) {
relativePoses[_chain[i].jointIndex] = _chain[i].relativePose;
}
}
void debugDraw(const glm::mat4& geomToWorldMat, const glm::vec4& color) const {
for (int i = 1; i < _top; i++) {
glm::vec3 start = transformPoint(geomToWorldMat, _chain[i - 1].absolutePose.trans());
glm::vec3 end = transformPoint(geomToWorldMat, _chain[i].absolutePose.trans());
DebugDraw::getInstance().drawRay(start, end, color);
}
}
void dump() const {
for (int i = 0; i < _top; i++) {
qWarning() << "AJT: AnimPoseElem[" << i << "]";
qWarning() << "AJT: relPose =" << _chain[i].relativePose;
qWarning() << "AJT: absPose =" << _chain[i].absolutePose;
qWarning() << "AJT: jointIndex =" << _chain[i].jointIndex;
qWarning() << "AJT: dirty =" << _chain[i].dirty;
}
}
protected:
struct AnimChainElem {
AnimPose relativePose;
AnimPose absolutePose;
int jointIndex { -1 };
bool dirty { true };
};
AnimChainElem _chain[N];
int _top { 0 };
};
using AnimChain = AnimChainT<10>;
#endif

View file

@ -30,7 +30,7 @@ AnimClip::~AnimClip() {
}
const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
// lookup parameters from animVars, using current instance variables as defaults.
_startFrame = animVars.lookup(_startFrameVar, _startFrame);
@ -77,6 +77,8 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const Anim
::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]);
}
processOutputJoints(triggersOut);
return _poses;
}
@ -89,7 +91,7 @@ void AnimClip::loadURL(const QString& url) {
void AnimClip::setCurrentFrameInternal(float frame) {
// because dt is 0, we should not encounter any triggers
const float dt = 0.0f;
Triggers triggers;
AnimVariantMap triggers;
_frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame + _startFrame, dt, _loopFlag, _id, triggers);
}

View file

@ -28,7 +28,7 @@ public:
AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag);
virtual ~AnimClip() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
void setStartFrameVar(const QString& startFrameVar) { _startFrameVar = startFrameVar; }
void setEndFrameVar(const QString& endFrameVar) { _endFrameVar = endFrameVar; }

View file

@ -20,12 +20,15 @@ AnimDefaultPose::~AnimDefaultPose() {
}
const AnimPoseVec& AnimDefaultPose::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
const AnimPoseVec& AnimDefaultPose::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
if (_skeleton) {
_poses = _skeleton->getRelativeDefaultPoses();
} else {
_poses.clear();
}
processOutputJoints(triggersOut);
return _poses;
}

View file

@ -21,7 +21,7 @@ public:
AnimDefaultPose(const QString& id);
virtual ~AnimDefaultPose() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
protected:
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;

View file

@ -259,14 +259,6 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector<
jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha);
jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha);
}
// if joint chain was just disabled, ramp the weight toward zero.
if (_prevJointChainInfoVec[i].target.getType() != IKTarget::Type::Unknown &&
jointChainInfoVec[i].target.getType() == IKTarget::Type::Unknown) {
IKTarget newTarget = _prevJointChainInfoVec[i].target;
newTarget.setWeight((1.0f - alpha) * _prevJointChainInfoVec[i].target.getWeight());
jointChainInfoVec[i].target = newTarget;
}
}
}
}
@ -874,14 +866,14 @@ void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, co
}
//virtual
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) {
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
// don't call this function, call overlay() instead
assert(false);
return _relativePoses;
}
//virtual
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) {
#ifdef Q_OS_ANDROID
// disable IK on android
return underPoses;
@ -961,6 +953,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0);
if (_hipsTargetIndex >= 0) {
assert(_hipsTargetIndex < (int)targets.size());
// slam the hips to match the _hipsTarget
@ -1045,6 +1038,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
setSecondaryTargets(context);
preconditionRelativePosesToAvoidLimbLock(context, targets);
solve(context, targets, dt, jointChainInfoVec);
@ -1056,6 +1050,8 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
}
}
processOutputJoints(triggersOut);
return _relativePoses;
}
@ -1750,7 +1746,7 @@ void AnimInverseKinematics::preconditionRelativePosesToAvoidLimbLock(const AnimC
const float MIN_AXIS_LENGTH = 1.0e-4f;
for (auto& target : targets) {
if (target.getIndex() != -1) {
if (target.getIndex() != -1 && target.getType() == IKTarget::Type::RotationAndPosition) {
for (int i = 0; i < NUM_LIMBS; i++) {
if (limbs[i].first == target.getIndex()) {
int tipIndex = limbs[i].first;
@ -1843,6 +1839,10 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s
default:
case SolutionSource::RelaxToUnderPoses:
blendToPoses(underPoses, underPoses, RELAX_BLEND_FACTOR);
// special case for hips: don't dampen hip motion from underposes
if (_hipsIndex >= 0 && _hipsIndex < (int)_relativePoses.size()) {
_relativePoses[_hipsIndex] = underPoses[_hipsIndex];
}
break;
case SolutionSource::RelaxToLimitCenterPoses:
blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR);

View file

@ -52,8 +52,8 @@ public:
const QString& typeVar, const QString& weightVar, float weight, const std::vector<float>& flexCoefficients,
const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar);
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) override;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) override;
void clearIKJointLimitHistory();

View file

@ -32,11 +32,11 @@ AnimManipulator::~AnimManipulator() {
}
const AnimPoseVec& AnimManipulator::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
const AnimPoseVec& AnimManipulator::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
return overlay(animVars, context, dt, triggersOut, _skeleton->getRelativeDefaultPoses());
}
const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) {
_alpha = animVars.lookup(_alphaVar, _alpha);
_poses = underPoses;
@ -74,6 +74,8 @@ const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, cons
}
}
processOutputJoints(triggersOut);
return _poses;
}

View file

@ -22,8 +22,8 @@ public:
AnimManipulator(const QString& id, float alpha);
virtual ~AnimManipulator() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) override;
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }

View file

@ -59,3 +59,19 @@ void AnimNode::setCurrentFrame(float frame) {
child->setCurrentFrameInternal(frame);
}
}
void AnimNode::processOutputJoints(AnimVariantMap& triggersOut) const {
if (!_skeleton) {
return;
}
for (auto&& jointName : _outputJointNames) {
// TODO: cache the jointIndices
int jointIndex = _skeleton->nameToJointIndex(jointName);
if (jointIndex >= 0) {
AnimPose pose = _skeleton->getAbsolutePose(jointIndex, getPosesInternal());
triggersOut.set(_id + jointName + "Rotation", pose.rot());
triggersOut.set(_id + jointName + "Position", pose.trans());
}
}
}

View file

@ -45,11 +45,12 @@ public:
Manipulator,
InverseKinematics,
DefaultPose,
TwoBoneIK,
PoleVectorConstraint,
NumTypes
};
using Pointer = std::shared_ptr<AnimNode>;
using ConstPointer = std::shared_ptr<const AnimNode>;
using Triggers = std::vector<QString>;
friend class AnimDebugDraw;
friend void buildChildMap(std::map<QString, Pointer>& map, Pointer node);
@ -61,6 +62,8 @@ public:
const QString& getID() const { return _id; }
Type getType() const { return _type; }
void addOutputJoint(const QString& outputJointName) { _outputJointNames.push_back(outputJointName); }
// hierarchy accessors
Pointer getParent();
void addChild(Pointer child);
@ -74,8 +77,8 @@ public:
AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; }
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) = 0;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut,
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) = 0;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut,
const AnimPoseVec& underPoses) {
return evaluate(animVars, context, dt, triggersOut);
}
@ -114,11 +117,14 @@ protected:
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const = 0;
void processOutputJoints(AnimVariantMap& triggersOut) const;
Type _type;
QString _id;
std::vector<AnimNode::Pointer> _children;
AnimSkeleton::ConstPointer _skeleton;
std::weak_ptr<AnimNode> _parent;
std::vector<QString> _outputJointNames;
// no copies
AnimNode(const AnimNode&) = delete;

View file

@ -25,6 +25,8 @@
#include "AnimManipulator.h"
#include "AnimInverseKinematics.h"
#include "AnimDefaultPose.h"
#include "AnimTwoBoneIK.h"
#include "AnimPoleVectorConstraint.h"
using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
@ -38,6 +40,8 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const
static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadPoleVectorConstraintNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f;
@ -56,6 +60,8 @@ static const char* animNodeTypeToString(AnimNode::Type type) {
case AnimNode::Type::Manipulator: return "manipulator";
case AnimNode::Type::InverseKinematics: return "inverseKinematics";
case AnimNode::Type::DefaultPose: return "defaultPose";
case AnimNode::Type::TwoBoneIK: return "twoBoneIK";
case AnimNode::Type::PoleVectorConstraint: return "poleVectorConstraint";
case AnimNode::Type::NumTypes: return nullptr;
};
return nullptr;
@ -116,6 +122,8 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
case AnimNode::Type::Manipulator: return loadManipulatorNode;
case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode;
case AnimNode::Type::DefaultPose: return loadDefaultPoseNode;
case AnimNode::Type::TwoBoneIK: return loadTwoBoneIKNode;
case AnimNode::Type::PoleVectorConstraint: return loadPoleVectorConstraintNode;
case AnimNode::Type::NumTypes: return nullptr;
};
return nullptr;
@ -131,6 +139,8 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
case AnimNode::Type::Manipulator: return processDoNothing;
case AnimNode::Type::InverseKinematics: return processDoNothing;
case AnimNode::Type::DefaultPose: return processDoNothing;
case AnimNode::Type::TwoBoneIK: return processDoNothing;
case AnimNode::Type::PoleVectorConstraint: return processDoNothing;
case AnimNode::Type::NumTypes: return nullptr;
};
return nullptr;
@ -189,6 +199,25 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
} \
do {} while (0)
#define READ_VEC3(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \
auto NAME##_VAL = JSON_OBJ.value(#NAME); \
if (!NAME##_VAL.isArray()) { \
qCCritical(animation) << "AnimNodeLoader, error reading vector" \
<< #NAME << "id =" << ID \
<< ", url =" << URL.toDisplayString(); \
return ERROR_RETURN; \
} \
QJsonArray NAME##_ARRAY = NAME##_VAL.toArray(); \
if (NAME##_ARRAY.size() != 3) { \
qCCritical(animation) << "AnimNodeLoader, vector size != 3" \
<< #NAME << "id =" << ID \
<< ", url =" << URL.toDisplayString(); \
return ERROR_RETURN; \
} \
glm::vec3 NAME((float)NAME##_ARRAY.at(0).toDouble(), \
(float)NAME##_ARRAY.at(1).toDouble(), \
(float)NAME##_ARRAY.at(2).toDouble())
static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) {
auto idVal = jsonObj.value("id");
if (!idVal.isString()) {
@ -216,6 +245,16 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr
}
auto dataObj = dataValue.toObject();
std::vector<QString> outputJoints;
auto outputJoints_VAL = dataObj.value("outputJoints");
if (outputJoints_VAL.isArray()) {
QJsonArray outputJoints_ARRAY = outputJoints_VAL.toArray();
for (int i = 0; i < outputJoints_ARRAY.size(); i++) {
outputJoints.push_back(outputJoints_ARRAY.at(i).toString());
}
}
assert((int)type >= 0 && type < AnimNode::Type::NumTypes);
auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl);
if (!node) {
@ -242,6 +281,9 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr
}
if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) {
for (auto&& outputJoint : outputJoints) {
node->addOutputJoint(outputJoint);
}
return node;
} else {
return nullptr;
@ -531,6 +573,41 @@ static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const Q
return node;
}
static AnimNode::Pointer loadTwoBoneIKNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr);
READ_FLOAT(interpDuration, jsonObj, id, jsonUrl, nullptr);
READ_STRING(baseJointName, jsonObj, id, jsonUrl, nullptr);
READ_STRING(midJointName, jsonObj, id, jsonUrl, nullptr);
READ_STRING(tipJointName, jsonObj, id, jsonUrl, nullptr);
READ_VEC3(midHingeAxis, jsonObj, id, jsonUrl, nullptr);
READ_STRING(alphaVar, jsonObj, id, jsonUrl, nullptr);
READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr);
READ_STRING(endEffectorRotationVarVar, jsonObj, id, jsonUrl, nullptr);
READ_STRING(endEffectorPositionVarVar, jsonObj, id, jsonUrl, nullptr);
auto node = std::make_shared<AnimTwoBoneIK>(id, alpha, enabled, interpDuration,
baseJointName, midJointName, tipJointName, midHingeAxis,
alphaVar, enabledVar,
endEffectorRotationVarVar, endEffectorPositionVarVar);
return node;
}
static AnimNode::Pointer loadPoleVectorConstraintNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
READ_VEC3(referenceVector, jsonObj, id, jsonUrl, nullptr);
READ_BOOL(enabled, jsonObj, id, jsonUrl, nullptr);
READ_STRING(baseJointName, jsonObj, id, jsonUrl, nullptr);
READ_STRING(midJointName, jsonObj, id, jsonUrl, nullptr);
READ_STRING(tipJointName, jsonObj, id, jsonUrl, nullptr);
READ_STRING(enabledVar, jsonObj, id, jsonUrl, nullptr);
READ_STRING(poleVectorVar, jsonObj, id, jsonUrl, nullptr);
auto node = std::make_shared<AnimPoleVectorConstraint>(id, enabled, referenceVector,
baseJointName, midJointName, tipJointName,
enabledVar, poleVectorVar);
return node;
}
void buildChildMap(std::map<QString, int>& map, AnimNode::Pointer node) {
for (int i = 0; i < (int)node->getChildCount(); ++i) {
map.insert(std::pair<QString, int>(node->getChild(i)->getID(), i));
@ -682,7 +759,8 @@ AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& j
QString version = versionVal.toString();
// check version
if (version != "1.0") {
// AJT: TODO version check
if (version != "1.0" && version != "1.1") {
qCCritical(animation) << "AnimNodeLoader, bad version number" << version << "expected \"1.0\", url =" << jsonUrl.toDisplayString();
return nullptr;
}

View file

@ -41,7 +41,7 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) {
}
}
const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
// lookup parameters from animVars, using current instance variables as defaults.
// NOTE: switching bonesets can be an expensive operation, let's try to avoid it.
@ -66,6 +66,9 @@ const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, const A
}
}
}
processOutputJoints(triggersOut);
return _poses;
}

View file

@ -45,7 +45,7 @@ public:
AnimOverlay(const QString& id, BoneSet boneSet, float alpha);
virtual ~AnimOverlay() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
void setBoneSetVar(const QString& boneSetVar) { _boneSetVar = boneSetVar; }
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }

View file

@ -0,0 +1,244 @@
//
// AnimPoleVectorConstraint.cpp
//
// Created by Anthony J. Thibault on 5/12/18.
// Copyright (c) 2018 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AnimPoleVectorConstraint.h"
#include "AnimationLogging.h"
#include "AnimUtil.h"
#include "GLMHelpers.h"
const float FRAMES_PER_SECOND = 30.0f;
const float INTERP_DURATION = 6.0f;
AnimPoleVectorConstraint::AnimPoleVectorConstraint(const QString& id, bool enabled, glm::vec3 referenceVector,
const QString& baseJointName, const QString& midJointName, const QString& tipJointName,
const QString& enabledVar, const QString& poleVectorVar) :
AnimNode(AnimNode::Type::PoleVectorConstraint, id),
_enabled(enabled),
_referenceVector(referenceVector),
_baseJointName(baseJointName),
_midJointName(midJointName),
_tipJointName(tipJointName),
_enabledVar(enabledVar),
_poleVectorVar(poleVectorVar) {
}
AnimPoleVectorConstraint::~AnimPoleVectorConstraint() {
}
const AnimPoseVec& AnimPoleVectorConstraint::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
assert(_children.size() == 1);
if (_children.size() != 1) {
return _poses;
}
// evalute underPoses
AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut);
// if we don't have a skeleton, or jointName lookup failed.
if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || underPoses.size() == 0) {
// pass underPoses through unmodified.
_poses = underPoses;
return _poses;
}
// guard against size changes
if (underPoses.size() != _poses.size()) {
_poses = underPoses;
}
// Look up poleVector from animVars, make sure to convert into geom space.
glm::vec3 poleVector = animVars.lookupRigToGeometryVector(_poleVectorVar, Vectors::UNIT_Z);
// determine if we should interpolate
bool enabled = animVars.lookup(_enabledVar, _enabled);
const float MIN_LENGTH = 1.0e-4f;
if (glm::length(poleVector) < MIN_LENGTH) {
enabled = false;
}
if (enabled != _enabled) {
AnimChain poseChain;
poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex);
if (enabled) {
beginInterp(InterpType::SnapshotToSolve, poseChain);
} else {
beginInterp(InterpType::SnapshotToUnderPoses, poseChain);
}
}
_enabled = enabled;
// don't build chains or do IK if we are disbled & not interping.
if (_interpType == InterpType::None && !enabled) {
_poses = underPoses;
return _poses;
}
// compute chain
AnimChain underChain;
underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex);
AnimChain ikChain = underChain;
AnimPose baseParentPose = ikChain.getAbsolutePoseFromJointIndex(_baseParentJointIndex);
AnimPose basePose = ikChain.getAbsolutePoseFromJointIndex(_baseJointIndex);
AnimPose midPose = ikChain.getAbsolutePoseFromJointIndex(_midJointIndex);
AnimPose tipPose = ikChain.getAbsolutePoseFromJointIndex(_tipJointIndex);
// Look up refVector from animVars, make sure to convert into geom space.
glm::vec3 refVector = midPose.xformVectorFast(_referenceVector);
float refVectorLength = glm::length(refVector);
glm::vec3 axis = basePose.trans() - tipPose.trans();
float axisLength = glm::length(axis);
glm::vec3 unitAxis = axis / axisLength;
glm::vec3 sideVector = glm::cross(unitAxis, refVector);
float sideVectorLength = glm::length(sideVector);
// project refVector onto axis plane
glm::vec3 refVectorProj = refVector - glm::dot(refVector, unitAxis) * unitAxis;
float refVectorProjLength = glm::length(refVectorProj);
// project poleVector on plane formed by axis.
glm::vec3 poleVectorProj = poleVector - glm::dot(poleVector, unitAxis) * unitAxis;
float poleVectorProjLength = glm::length(poleVectorProj);
// double check for zero length vectors or vectors parallel to rotaiton axis.
if (axisLength > MIN_LENGTH && refVectorLength > MIN_LENGTH && sideVectorLength > MIN_LENGTH &&
refVectorProjLength > MIN_LENGTH && poleVectorProjLength > MIN_LENGTH) {
float dot = glm::clamp(glm::dot(refVectorProj / refVectorProjLength, poleVectorProj / poleVectorProjLength), 0.0f, 1.0f);
float sideDot = glm::dot(poleVector, sideVector);
float theta = copysignf(1.0f, sideDot) * acosf(dot);
glm::quat deltaRot = glm::angleAxis(theta, unitAxis);
// transform result back into parent relative frame.
glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * deltaRot * basePose.rot();
ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans()));
glm::quat relTipRot = glm::inverse(midPose.rot()) * glm::inverse(deltaRot) * tipPose.rot();
ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans()));
}
// start off by initializing output poses with the underPoses
_poses = underPoses;
// apply smooth interpolation
if (_interpType != InterpType::None) {
_interpAlpha += _interpAlphaVel * dt;
if (_interpAlpha < 1.0f) {
AnimChain interpChain;
if (_interpType == InterpType::SnapshotToUnderPoses) {
interpChain = underChain;
interpChain.blend(_snapshotChain, _interpAlpha);
} else if (_interpType == InterpType::SnapshotToSolve) {
interpChain = ikChain;
interpChain.blend(_snapshotChain, _interpAlpha);
}
// copy interpChain into _poses
interpChain.outputRelativePoses(_poses);
} else {
// interpolation complete
_interpType = InterpType::None;
}
}
if (_interpType == InterpType::None) {
if (enabled) {
// copy chain into _poses
ikChain.outputRelativePoses(_poses);
} else {
// copy under chain into _poses
underChain.outputRelativePoses(_poses);
}
}
if (context.getEnableDebugDrawIKChains()) {
if (_interpType == InterpType::None && enabled) {
const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);
ikChain.debugDraw(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(), BLUE);
}
}
if (context.getEnableDebugDrawIKChains()) {
if (enabled) {
const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
const glm::vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f);
const glm::vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f);
const glm::vec4 YELLOW(1.0f, 0.0f, 1.0f, 1.0f);
const float VECTOR_LENGTH = 0.5f;
glm::mat4 geomToWorld = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix();
// draw the pole
glm::vec3 start = transformPoint(geomToWorld, basePose.trans());
glm::vec3 end = transformPoint(geomToWorld, tipPose.trans());
DebugDraw::getInstance().drawRay(start, end, CYAN);
// draw the poleVector
glm::vec3 midPoint = 0.5f * (start + end);
glm::vec3 poleVectorEnd = midPoint + VECTOR_LENGTH * glm::normalize(transformVectorFast(geomToWorld, poleVector));
DebugDraw::getInstance().drawRay(midPoint, poleVectorEnd, GREEN);
// draw the refVector
glm::vec3 refVectorEnd = midPoint + VECTOR_LENGTH * glm::normalize(transformVectorFast(geomToWorld, refVector));
DebugDraw::getInstance().drawRay(midPoint, refVectorEnd, RED);
// draw the sideVector
glm::vec3 sideVector = glm::cross(poleVector, refVector);
glm::vec3 sideVectorEnd = midPoint + VECTOR_LENGTH * glm::normalize(transformVectorFast(geomToWorld, sideVector));
DebugDraw::getInstance().drawRay(midPoint, sideVectorEnd, YELLOW);
}
}
processOutputJoints(triggersOut);
return _poses;
}
// for AnimDebugDraw rendering
const AnimPoseVec& AnimPoleVectorConstraint::getPosesInternal() const {
return _poses;
}
void AnimPoleVectorConstraint::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
AnimNode::setSkeletonInternal(skeleton);
lookUpIndices();
}
void AnimPoleVectorConstraint::lookUpIndices() {
assert(_skeleton);
// look up bone indices by name
std::vector<int> indices = _skeleton->lookUpJointIndices({_baseJointName, _midJointName, _tipJointName});
// cache the results
_baseJointIndex = indices[0];
_midJointIndex = indices[1];
_tipJointIndex = indices[2];
if (_baseJointIndex != -1) {
_baseParentJointIndex = _skeleton->getParentIndex(_baseJointIndex);
}
}
void AnimPoleVectorConstraint::beginInterp(InterpType interpType, const AnimChain& chain) {
// capture the current poses in a snapshot.
_snapshotChain = chain;
_interpType = interpType;
_interpAlphaVel = FRAMES_PER_SECOND / INTERP_DURATION;
_interpAlpha = 0.0f;
}

View file

@ -0,0 +1,74 @@
//
// AnimPoleVectorConstraint.h
//
// Created by Anthony J. Thibault on 5/25/18.
// Copyright (c) 2018 High Fidelity, Inc. All rights reserved.
//
// 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_AnimPoleVectorConstraint_h
#define hifi_AnimPoleVectorConstraint_h
#include "AnimNode.h"
#include "AnimChain.h"
// Three bone IK chain
class AnimPoleVectorConstraint : public AnimNode {
public:
friend class AnimTests;
AnimPoleVectorConstraint(const QString& id, bool enabled, glm::vec3 referenceVector,
const QString& baseJointName, const QString& midJointName, const QString& tipJointName,
const QString& enabledVar, const QString& poleVectorVar);
virtual ~AnimPoleVectorConstraint() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
protected:
enum class InterpType {
None = 0,
SnapshotToUnderPoses,
SnapshotToSolve,
NumTypes
};
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
void lookUpIndices();
void beginInterp(InterpType interpType, const AnimChain& chain);
AnimPoseVec _poses;
bool _enabled;
glm::vec3 _referenceVector;
QString _baseJointName;
QString _midJointName;
QString _tipJointName;
QString _enabledVar;
QString _poleVectorVar;
int _baseParentJointIndex { -1 };
int _baseJointIndex { -1 };
int _midJointIndex { -1 };
int _tipJointIndex { -1 };
InterpType _interpType { InterpType::None };
float _interpAlphaVel { 0.0f };
float _interpAlpha { 0.0f };
AnimChain _snapshotChain;
// no copies
AnimPoleVectorConstraint(const AnimPoleVectorConstraint&) = delete;
AnimPoleVectorConstraint& operator=(const AnimPoleVectorConstraint&) = delete;
};
#endif // hifi_AnimPoleVectorConstraint_h

View file

@ -12,6 +12,7 @@
#include <GLMHelpers.h>
#include <algorithm>
#include <glm/gtc/matrix_transform.hpp>
#include "AnimUtil.h"
const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
glm::quat(),
@ -77,4 +78,8 @@ AnimPose::operator glm::mat4() const {
glm::vec4(zAxis, 0.0f), glm::vec4(_trans, 1.0f));
}
void AnimPose::blend(const AnimPose& srcPose, float alpha) {
_scale = lerp(srcPose._scale, _scale, alpha);
_rot = safeLerp(srcPose._rot, _rot, alpha);
_trans = lerp(srcPose._trans, _trans, alpha);
}

View file

@ -46,6 +46,8 @@ public:
const glm::vec3& trans() const { return _trans; }
glm::vec3& trans() { return _trans; }
void blend(const AnimPose& srcPose, float alpha);
private:
friend QDebug operator<<(QDebug debug, const AnimPose& pose);
glm::vec3 _scale { 1.0f };

View file

@ -282,3 +282,17 @@ void AnimSkeleton::dump(const AnimPoseVec& poses) const {
qCDebug(animation) << "]";
}
std::vector<int> AnimSkeleton::lookUpJointIndices(const std::vector<QString>& jointNames) const {
std::vector<int> result;
result.reserve(jointNames.size());
for (auto& name : jointNames) {
int index = nameToJointIndex(name);
if (index == -1) {
qWarning(animation) << "AnimSkeleton::lookUpJointIndices(): could not find bone with named " << name;
}
result.push_back(index);
}
return result;
}

View file

@ -61,6 +61,8 @@ public:
void dump(bool verbose) const;
void dump(const AnimPoseVec& poses) const;
std::vector<int> lookUpJointIndices(const std::vector<QString>& jointNames) const;
protected:
void buildSkeletonFromJoints(const std::vector<FBXJoint>& joints);

View file

@ -21,7 +21,7 @@ AnimStateMachine::~AnimStateMachine() {
}
const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID());
if (_currentState->getID() != desiredStateID) {
@ -81,6 +81,9 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
if (!_duringInterp) {
_poses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
}
processOutputJoints(triggersOut);
return _poses;
}
@ -107,7 +110,7 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, const AnimCon
// because dt is 0, we should not encounter any triggers
const float dt = 0.0f;
Triggers triggers;
AnimVariantMap triggers;
if (_interpType == InterpType::SnapshotBoth) {
// snapshot previous pose.

View file

@ -113,7 +113,7 @@ public:
explicit AnimStateMachine(const QString& id);
virtual ~AnimStateMachine() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
void setCurrentStateVar(QString& currentStateVar) { _currentStateVar = currentStateVar; }

View file

@ -0,0 +1,293 @@
//
// AnimTwoBoneIK.cpp
//
// Created by Anthony J. Thibault on 5/12/18.
// Copyright (c) 2018 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AnimTwoBoneIK.h"
#include <DebugDraw.h>
#include "AnimationLogging.h"
#include "AnimUtil.h"
const float FRAMES_PER_SECOND = 30.0f;
AnimTwoBoneIK::AnimTwoBoneIK(const QString& id, float alpha, bool enabled, float interpDuration,
const QString& baseJointName, const QString& midJointName,
const QString& tipJointName, const glm::vec3& midHingeAxis,
const QString& alphaVar, const QString& enabledVar,
const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar) :
AnimNode(AnimNode::Type::TwoBoneIK, id),
_alpha(alpha),
_enabled(enabled),
_interpDuration(interpDuration),
_baseJointName(baseJointName),
_midJointName(midJointName),
_tipJointName(tipJointName),
_midHingeAxis(glm::normalize(midHingeAxis)),
_alphaVar(alphaVar),
_enabledVar(enabledVar),
_endEffectorRotationVarVar(endEffectorRotationVarVar),
_endEffectorPositionVarVar(endEffectorPositionVarVar),
_prevEndEffectorRotationVar(),
_prevEndEffectorPositionVar()
{
}
AnimTwoBoneIK::~AnimTwoBoneIK() {
}
const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
assert(_children.size() == 1);
if (_children.size() != 1) {
return _poses;
}
// evalute underPoses
AnimPoseVec underPoses = _children[0]->evaluate(animVars, context, dt, triggersOut);
// if we don't have a skeleton, or jointName lookup failed.
if (!_skeleton || _baseJointIndex == -1 || _midJointIndex == -1 || _tipJointIndex == -1 || underPoses.size() == 0) {
// pass underPoses through unmodified.
_poses = underPoses;
return _poses;
}
// guard against size changes
if (underPoses.size() != _poses.size()) {
_poses = underPoses;
}
const float MIN_ALPHA = 0.0f;
const float MAX_ALPHA = 1.0f;
float alpha = glm::clamp(animVars.lookup(_alphaVar, _alpha), MIN_ALPHA, MAX_ALPHA);
// don't perform IK if we have bad indices, or alpha is zero
if (_tipJointIndex == -1 || _midJointIndex == -1 || _baseJointIndex == -1 || alpha == 0.0f) {
_poses = underPoses;
return _poses;
}
// determine if we should interpolate
bool enabled = animVars.lookup(_enabledVar, _enabled);
if (enabled != _enabled) {
AnimChain poseChain;
poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex);
if (enabled) {
beginInterp(InterpType::SnapshotToSolve, poseChain);
} else {
beginInterp(InterpType::SnapshotToUnderPoses, poseChain);
}
}
_enabled = enabled;
// don't build chains or do IK if we are disbled & not interping.
if (_interpType == InterpType::None && !enabled) {
_poses = underPoses;
return _poses;
}
// compute chain
AnimChain underChain;
underChain.buildFromRelativePoses(_skeleton, underPoses, _tipJointIndex);
AnimChain ikChain = underChain;
AnimPose baseParentPose = ikChain.getAbsolutePoseFromJointIndex(_baseParentJointIndex);
AnimPose basePose = ikChain.getAbsolutePoseFromJointIndex(_baseJointIndex);
AnimPose midPose = ikChain.getAbsolutePoseFromJointIndex(_midJointIndex);
AnimPose tipPose = ikChain.getAbsolutePoseFromJointIndex(_tipJointIndex);
QString endEffectorRotationVar = animVars.lookup(_endEffectorRotationVarVar, QString(""));
QString endEffectorPositionVar = animVars.lookup(_endEffectorPositionVarVar, QString(""));
// if either of the endEffectorVars have changed
if ((!_prevEndEffectorRotationVar.isEmpty() && (_prevEndEffectorRotationVar != endEffectorRotationVar)) ||
(!_prevEndEffectorPositionVar.isEmpty() && (_prevEndEffectorPositionVar != endEffectorPositionVar))) {
// begin interp to smooth out transition between prev and new end effector.
AnimChain poseChain;
poseChain.buildFromRelativePoses(_skeleton, _poses, _tipJointIndex);
beginInterp(InterpType::SnapshotToSolve, poseChain);
}
// Look up end effector from animVars, make sure to convert into geom space.
// First look in the triggers then look in the animVars, so we can follow output joints underneath us in the anim graph
AnimPose targetPose(tipPose);
if (triggersOut.hasKey(endEffectorRotationVar)) {
targetPose.rot() = triggersOut.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot());
} else if (animVars.hasKey(endEffectorRotationVar)) {
targetPose.rot() = animVars.lookupRigToGeometry(endEffectorRotationVar, tipPose.rot());
}
if (triggersOut.hasKey(endEffectorPositionVar)) {
targetPose.trans() = triggersOut.lookupRigToGeometry(endEffectorPositionVar, tipPose.trans());
} else if (animVars.hasKey(endEffectorRotationVar)) {
targetPose.trans() = animVars.lookupRigToGeometry(endEffectorPositionVar, tipPose.trans());
}
_prevEndEffectorRotationVar = endEffectorRotationVar;
_prevEndEffectorPositionVar = endEffectorPositionVar;
glm::vec3 bicepVector = midPose.trans() - basePose.trans();
float r0 = glm::length(bicepVector);
bicepVector = bicepVector / r0;
glm::vec3 forearmVector = tipPose.trans() - midPose.trans();
float r1 = glm::length(forearmVector);
forearmVector = forearmVector / r1;
float d = glm::length(targetPose.trans() - basePose.trans());
// http://mathworld.wolfram.com/Circle-CircleIntersection.html
float midAngle = 0.0f;
if (d < r0 + r1) {
float y = sqrtf((-d + r1 - r0) * (-d - r1 + r0) * (-d + r1 + r0) * (d + r1 + r0)) / (2.0f * d);
midAngle = PI - (acosf(y / r0) + acosf(y / r1));
}
// compute midJoint rotation
glm::quat relMidRot = glm::angleAxis(midAngle, _midHingeAxis);
// insert new relative pose into the chain and rebuild it.
ikChain.setRelativePoseAtJointIndex(_midJointIndex, AnimPose(relMidRot, underPoses[_midJointIndex].trans()));
ikChain.buildDirtyAbsolutePoses();
// recompute tip pose after mid joint has been rotated
AnimPose newTipPose = ikChain.getAbsolutePoseFromJointIndex(_tipJointIndex);
glm::vec3 leverArm = newTipPose.trans() - basePose.trans();
glm::vec3 targetLine = targetPose.trans() - basePose.trans();
// compute delta rotation that brings leverArm parallel to targetLine
glm::vec3 axis = glm::cross(leverArm, targetLine);
float axisLength = glm::length(axis);
const float MIN_AXIS_LENGTH = 1.0e-4f;
if (axisLength > MIN_AXIS_LENGTH) {
axis /= axisLength;
float cosAngle = glm::clamp(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)), -1.0f, 1.0f);
float angle = acosf(cosAngle);
glm::quat deltaRot = glm::angleAxis(angle, axis);
// combine deltaRot with basePose.
glm::quat absRot = deltaRot * basePose.rot();
// transform result back into parent relative frame.
glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * absRot;
ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans()));
}
// recompute midJoint pose after base has been rotated.
ikChain.buildDirtyAbsolutePoses();
AnimPose midJointPose = ikChain.getAbsolutePoseFromJointIndex(_midJointIndex);
// transform target rotation in to parent relative frame.
glm::quat relTipRot = glm::inverse(midJointPose.rot()) * targetPose.rot();
ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans()));
// blend with the underChain
ikChain.blend(underChain, alpha);
// start off by initializing output poses with the underPoses
_poses = underPoses;
// apply smooth interpolation
if (_interpType != InterpType::None) {
_interpAlpha += _interpAlphaVel * dt;
if (_interpAlpha < 1.0f) {
AnimChain interpChain;
if (_interpType == InterpType::SnapshotToUnderPoses) {
interpChain = underChain;
interpChain.blend(_snapshotChain, _interpAlpha);
} else if (_interpType == InterpType::SnapshotToSolve) {
interpChain = ikChain;
interpChain.blend(_snapshotChain, _interpAlpha);
}
// copy interpChain into _poses
interpChain.outputRelativePoses(_poses);
} else {
// interpolation complete
_interpType = InterpType::None;
}
}
if (_interpType == InterpType::None) {
if (enabled) {
// copy chain into _poses
ikChain.outputRelativePoses(_poses);
} else {
// copy under chain into _poses
underChain.outputRelativePoses(_poses);
}
}
if (context.getEnableDebugDrawIKTargets()) {
const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f);
glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3());
glm::mat4 geomTargetMat = createMatFromQuatAndPos(targetPose.rot(), targetPose.trans());
glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat;
QString name = QString("%1_target").arg(_id);
DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat),
extractTranslation(avatarTargetMat), _enabled ? GREEN : RED);
} else if (_lastEnableDebugDrawIKTargets) {
QString name = QString("%1_target").arg(_id);
DebugDraw::getInstance().removeMyAvatarMarker(name);
}
_lastEnableDebugDrawIKTargets = context.getEnableDebugDrawIKTargets();
if (context.getEnableDebugDrawIKChains()) {
if (_interpType == InterpType::None && enabled) {
const vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f);
ikChain.debugDraw(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(), CYAN);
}
}
processOutputJoints(triggersOut);
return _poses;
}
// for AnimDebugDraw rendering
const AnimPoseVec& AnimTwoBoneIK::getPosesInternal() const {
return _poses;
}
void AnimTwoBoneIK::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
AnimNode::setSkeletonInternal(skeleton);
lookUpIndices();
}
void AnimTwoBoneIK::lookUpIndices() {
assert(_skeleton);
// look up bone indices by name
std::vector<int> indices = _skeleton->lookUpJointIndices({_baseJointName, _midJointName, _tipJointName});
// cache the results
_baseJointIndex = indices[0];
_midJointIndex = indices[1];
_tipJointIndex = indices[2];
if (_baseJointIndex != -1) {
_baseParentJointIndex = _skeleton->getParentIndex(_baseJointIndex);
}
}
void AnimTwoBoneIK::beginInterp(InterpType interpType, const AnimChain& chain) {
// capture the current poses in a snapshot.
_snapshotChain = chain;
_interpType = interpType;
_interpAlphaVel = FRAMES_PER_SECOND / _interpDuration;
_interpAlpha = 0.0f;
}

View file

@ -0,0 +1,83 @@
//
// AnimTwoBoneIK.h
//
// Created by Anthony J. Thibault on 5/12/18.
// Copyright (c) 2018 High Fidelity, Inc. All rights reserved.
//
// 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_AnimTwoBoneIK_h
#define hifi_AnimTwoBoneIK_h
#include "AnimNode.h"
#include "AnimChain.h"
// Simple two bone IK chain
class AnimTwoBoneIK : public AnimNode {
public:
friend class AnimTests;
AnimTwoBoneIK(const QString& id, float alpha, bool enabled, float interpDuration,
const QString& baseJointName, const QString& midJointName,
const QString& tipJointName, const glm::vec3& midHingeAxis,
const QString& alphaVar, const QString& enabledVar,
const QString& endEffectorRotationVarVar, const QString& endEffectorPositionVarVar);
virtual ~AnimTwoBoneIK() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
protected:
enum class InterpType {
None = 0,
SnapshotToUnderPoses,
SnapshotToSolve,
NumTypes
};
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
void lookUpIndices();
void beginInterp(InterpType interpType, const AnimChain& chain);
AnimPoseVec _poses;
float _alpha;
bool _enabled;
float _interpDuration; // in frames (1/30 sec)
QString _baseJointName;
QString _midJointName;
QString _tipJointName;
glm::vec3 _midHingeAxis; // in baseJoint relative frame, should be normalized
int _baseParentJointIndex { -1 };
int _baseJointIndex { -1 };
int _midJointIndex { -1 };
int _tipJointIndex { -1 };
QString _alphaVar; // float - (0, 1) 0 means underPoses only, 1 means IK only.
QString _enabledVar; // bool
QString _endEffectorRotationVarVar; // string
QString _endEffectorPositionVarVar; // string
QString _prevEndEffectorRotationVar;
QString _prevEndEffectorPositionVar;
InterpType _interpType { InterpType::None };
float _interpAlphaVel { 0.0f };
float _interpAlpha { 0.0f };
AnimChain _snapshotChain;
bool _lastEnableDebugDrawIKTargets { false };
// no copies
AnimTwoBoneIK(const AnimTwoBoneIK&) = delete;
AnimTwoBoneIK& operator=(const AnimTwoBoneIK&) = delete;
};
#endif // hifi_AnimTwoBoneIK_h

View file

@ -21,14 +21,6 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A
const AnimPose& aPose = a[i];
const AnimPose& bPose = b[i];
// adjust signs if necessary
const glm::quat& q1 = aPose.rot();
glm::quat q2 = bPose.rot();
float dot = glm::dot(q1, q2);
if (dot < 0.0f) {
q2 = -q2;
}
result[i].scale() = lerp(aPose.scale(), bPose.scale(), alpha);
result[i].rot() = safeLerp(aPose.rot(), bPose.rot(), alpha);
result[i].trans() = lerp(aPose.trans(), bPose.trans(), alpha);
@ -53,7 +45,7 @@ glm::quat averageQuats(size_t numQuats, const glm::quat* quats) {
}
float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag,
const QString& id, AnimNode::Triggers& triggersOut) {
const QString& id, AnimVariantMap& triggersOut) {
const float EPSILON = 0.0001f;
float frame = currentFrame;
@ -79,12 +71,12 @@ float accumulateTime(float startFrame, float endFrame, float timeScale, float cu
if (framesRemaining >= framesTillEnd) {
if (loopFlag) {
// anim loop
triggersOut.push_back(id + "OnLoop");
triggersOut.setTrigger(id + "OnLoop");
framesRemaining -= framesTillEnd;
frame = clampedStartFrame;
} else {
// anim end
triggersOut.push_back(id + "OnDone");
triggersOut.setTrigger(id + "OnDone");
frame = endFrame;
framesRemaining = 0.0f;
}

View file

@ -19,7 +19,7 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A
glm::quat averageQuats(size_t numQuats, const glm::quat* quats);
float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag,
const QString& id, AnimNode::Triggers& triggersOut);
const QString& id, AnimVariantMap& triggersOut);
inline glm::quat safeLerp(const glm::quat& a, const glm::quat& b, float alpha) {
// adjust signs if necessary

View file

@ -24,71 +24,12 @@ class Animation;
typedef QSharedPointer<Animation> AnimationPointer;
/// Scriptable interface for FBX animation loading.
class AnimationCache : public ResourceCache, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* API to manage animation cache resources.
* @namespace AnimationCache
*
* @hifi-interface
* @hifi-client-entity
* @hifi-assignment-client
*
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*/
// Functions are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* Get the list of all resource URLs.
* @function AnimationCache.getResourceList
* @returns {string[]}
*/
/**jsdoc
* @function AnimationCache.dirty
* @returns {Signal}
*/
/**jsdoc
* @function AnimationCache.updateTotalSize
* @param {number} deltaSize
*/
/**jsdoc
* Prefetches a resource.
* @function AnimationCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {ResourceObject}
*/
/**jsdoc
* Asynchronously loads a resource from the specified URL and returns it.
* @function AnimationCache.getResource
* @param {string} url - URL of the resource to load.
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
* @param {} [extra=null]
* @returns {object}
*/
/**jsdoc
* Returns animation resource for particular animation.
* @function AnimationCache.getAnimation
* @param {string} url - URL to load.
* @returns {AnimationObject} animation
*/
Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); }
Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url);

View file

@ -0,0 +1,20 @@
//
// AnimationCacheScriptingInterface.cpp
// libraries/animation/src
//
// Created by David Rowe on 25 Jul 2018.
// 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 "AnimationCacheScriptingInterface.h"
AnimationCacheScriptingInterface::AnimationCacheScriptingInterface() :
ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get<AnimationCache>())
{ }
AnimationPointer AnimationCacheScriptingInterface::getAnimation(const QString& url) {
return DependencyManager::get<AnimationCache>()->getAnimation(QUrl(url));
}

View file

@ -0,0 +1,58 @@
//
// AnimationCacheScriptingInterface.h
// libraries/animation/src
//
// Created by David Rowe on 25 Jul 2018.
// 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
//
#pragma once
#ifndef hifi_AnimationCacheScriptingInterface_h
#define hifi_AnimationCacheScriptingInterface_h
#include <QObject>
#include <ResourceCache.h>
#include "AnimationCache.h"
class AnimationCacheScriptingInterface : public ScriptableResourceCache, public Dependency {
Q_OBJECT
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* API to manage animation cache resources.
* @namespace AnimationCache
*
* @hifi-interface
* @hifi-client-entity
* @hifi-assignment-client
*
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize
* @borrows ResourceCache.prefetch as prefetch
* @borrows ResourceCache.dirty as dirty
*/
public:
AnimationCacheScriptingInterface();
/**jsdoc
* Returns animation resource for particular animation.
* @function AnimationCache.getAnimation
* @param {string} url - URL to load.
* @returns {AnimationObject} animation
*/
Q_INVOKABLE AnimationPointer getAnimation(const QString& url);
};
#endif // hifi_AnimationCacheScriptingInterface_h

View file

@ -59,6 +59,21 @@ const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 0.9f, 0.0f);
const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 0.9f, 0.0f);
const glm::vec3 DEFAULT_HEAD_POS(0.0f, 0.75f, 0.0f);
static const QString LEFT_FOOT_POSITION("leftFootPosition");
static const QString LEFT_FOOT_ROTATION("leftFootRotation");
static const QString LEFT_FOOT_IK_POSITION_VAR("leftFootIKPositionVar");
static const QString LEFT_FOOT_IK_ROTATION_VAR("leftFootIKRotationVar");
static const QString MAIN_STATE_MACHINE_LEFT_FOOT_POSITION("mainStateMachineLeftFootPosition");
static const QString MAIN_STATE_MACHINE_LEFT_FOOT_ROTATION("mainStateMachineLeftFootRotation");
static const QString RIGHT_FOOT_POSITION("rightFootPosition");
static const QString RIGHT_FOOT_ROTATION("rightFootRotation");
static const QString RIGHT_FOOT_IK_POSITION_VAR("rightFootIKPositionVar");
static const QString RIGHT_FOOT_IK_ROTATION_VAR("rightFootIKRotationVar");
static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION("mainStateMachineRightFootRotation");
static const QString MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION("mainStateMachineRightFootPosition");
Rig::Rig() {
// Ensure thread-safe access to the rigRegistry.
std::lock_guard<std::mutex> guard(rigRegistryMutex);
@ -1049,7 +1064,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
getGeometryToRigTransform(), rigToWorldTransform);
// evaluate the animation
AnimNode::Triggers triggersOut;
AnimVariantMap triggersOut;
_internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut);
if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) {
@ -1057,9 +1072,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
}
_animVars.clearTriggers();
for (auto& trigger : triggersOut) {
_animVars.setTrigger(trigger);
}
_animVars = triggersOut;
}
applyOverridePoses();
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
@ -1241,7 +1254,7 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const FBXJoin
}
void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated,
bool leftArmEnabled, bool rightArmEnabled, float dt,
bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt,
const AnimPose& leftHandPose, const AnimPose& rightHandPose,
const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo,
const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo,
@ -1305,7 +1318,13 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
_animVars.unset("leftHandPosition");
_animVars.unset("leftHandRotation");
_animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
if (headEnabled) {
_animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
} else {
// disable hand IK for desktop mode
_animVars.set("leftHandType", (int)IKTarget::Type::Unknown);
}
}
if (rightHandEnabled) {
@ -1364,21 +1383,41 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
_animVars.unset("rightHandPosition");
_animVars.unset("rightHandRotation");
_animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
if (headEnabled) {
_animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
} else {
// disable hand IK for desktop mode
_animVars.set("rightHandType", (int)IKTarget::Type::Unknown);
}
}
}
void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose,
void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled,
const AnimPose& leftFootPose, const AnimPose& rightFootPose,
const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) {
const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.95f;
int hipsIndex = indexOfJoint("Hips");
const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.85f;
if (headEnabled) {
// always do IK if head is enabled
_animVars.set("leftFootIKEnabled", true);
_animVars.set("rightFootIKEnabled", true);
} else {
// only do IK if we have a valid foot.
_animVars.set("leftFootIKEnabled", leftFootEnabled);
_animVars.set("rightFootIKEnabled", rightFootEnabled);
}
if (leftFootEnabled) {
_animVars.set("leftFootPosition", leftFootPose.trans());
_animVars.set("leftFootRotation", leftFootPose.rot());
_animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition);
_animVars.set(LEFT_FOOT_POSITION, leftFootPose.trans());
_animVars.set(LEFT_FOOT_ROTATION, leftFootPose.rot());
// We want to drive the IK directly from the trackers.
_animVars.set(LEFT_FOOT_IK_POSITION_VAR, LEFT_FOOT_POSITION);
_animVars.set(LEFT_FOOT_IK_ROTATION_VAR, LEFT_FOOT_ROTATION);
int footJointIndex = _animSkeleton->nameToJointIndex("LeftFoot");
int kneeJointIndex = _animSkeleton->nameToJointIndex("LeftLeg");
@ -1396,20 +1435,25 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose
_prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector;
_animVars.set("leftFootPoleVectorEnabled", true);
_animVars.set("leftFootPoleReferenceVector", Vectors::UNIT_Z);
_animVars.set("leftFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftFootPoleVector));
} else {
_animVars.unset("leftFootPosition");
_animVars.unset("leftFootRotation");
_animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition);
// We want to drive the IK from the underlying animation.
// This gives us the ability to squat while in the HMD, without the feet from dipping under the floor.
_animVars.set(LEFT_FOOT_IK_POSITION_VAR, MAIN_STATE_MACHINE_LEFT_FOOT_POSITION);
_animVars.set(LEFT_FOOT_IK_ROTATION_VAR, MAIN_STATE_MACHINE_LEFT_FOOT_ROTATION);
// We want to match the animated knee pose as close as possible, so don't use poleVectors
_animVars.set("leftFootPoleVectorEnabled", false);
_prevLeftFootPoleVectorValid = false;
}
if (rightFootEnabled) {
_animVars.set("rightFootPosition", rightFootPose.trans());
_animVars.set("rightFootRotation", rightFootPose.rot());
_animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
_animVars.set(RIGHT_FOOT_POSITION, rightFootPose.trans());
_animVars.set(RIGHT_FOOT_ROTATION, rightFootPose.rot());
// We want to drive the IK directly from the trackers.
_animVars.set(RIGHT_FOOT_IK_POSITION_VAR, RIGHT_FOOT_POSITION);
_animVars.set(RIGHT_FOOT_IK_ROTATION_VAR, RIGHT_FOOT_ROTATION);
int footJointIndex = _animSkeleton->nameToJointIndex("RightFoot");
int kneeJointIndex = _animSkeleton->nameToJointIndex("RightLeg");
@ -1427,13 +1471,16 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose
_prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector;
_animVars.set("rightFootPoleVectorEnabled", true);
_animVars.set("rightFootPoleReferenceVector", Vectors::UNIT_Z);
_animVars.set("rightFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightFootPoleVector));
} else {
_animVars.unset("rightFootPosition");
_animVars.unset("rightFootRotation");
// We want to drive the IK from the underlying animation.
// This gives us the ability to squat while in the HMD, without the feet from dipping under the floor.
_animVars.set(RIGHT_FOOT_IK_POSITION_VAR, MAIN_STATE_MACHINE_RIGHT_FOOT_POSITION);
_animVars.set(RIGHT_FOOT_IK_ROTATION_VAR, MAIN_STATE_MACHINE_RIGHT_FOOT_ROTATION);
// We want to match the animated knee pose as close as possible, so don't use poleVectors
_animVars.set("rightFootPoleVectorEnabled", false);
_animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
_prevRightFootPoleVectorValid = false;
}
}
@ -1475,23 +1522,12 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
for (int i = 0; i < (int)children.size(); i++) {
int jointIndex = children[i];
int parentIndex = _animSkeleton->getParentIndex(jointIndex);
_internalPoseSet._absolutePoses[jointIndex] =
_internalPoseSet._absolutePoses[jointIndex] =
_internalPoseSet._absolutePoses[parentIndex] * _internalPoseSet._relativePoses[jointIndex];
}
}
}
static glm::quat quatLerp(const glm::quat& q1, const glm::quat& q2, float alpha) {
float dot = glm::dot(q1, q2);
glm::quat temp;
if (dot < 0.0f) {
temp = -q2;
} else {
temp = q2;
}
return glm::normalize(glm::lerp(q1, temp, alpha));
}
bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const {
// The resulting Pole Vector is calculated as the sum of a three vectors.
// The first is the vector with direction shoulder-hand. The module of this vector is inversely proportional to the strength of the resulting Pole Vector.
@ -1510,7 +1546,7 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex,
glm::vec3 backVector = oppositeArmPose.trans() - armPose.trans();
glm::vec3 backCenter = armPose.trans() + 0.5f * backVector;
const float OVER_BACK_HEAD_PERCENTAGE = 0.2f;
glm::vec3 headCenter = backCenter + glm::vec3(0, OVER_BACK_HEAD_PERCENTAGE * backVector.length(), 0);
@ -1522,7 +1558,7 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex,
glm::vec3 headForward = headCenter + horizontalModule * frontVector;
glm::vec3 armToHead = headForward - armPose.trans();
float armToHandDistance = glm::length(armToHand);
float armToElbowDistance = glm::length(armToElbow);
float elbowToHandDistance = glm::length(elbowToHand);
@ -1533,7 +1569,7 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex,
// How much the hand is reaching for the opposite side
float oppositeProjection = glm::dot(armToHandDir, glm::normalize(backVector));
// Don't use pole vector when the hands are behind
if (glm::dot(frontVector, armToHand) < 0 && oppositeProjection < 0.5f * armTotalDistance) {
return false;
@ -1552,7 +1588,7 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex,
const float FORWARD_CORRECTOR_WEIGHT = 3.0f;
float elbowForwardTrigger = FORWARD_TRIGGER_PERCENTAGE * armToHandDistance;
if (oppositeProjection > -elbowForwardTrigger) {
float forwardAmount = FORWARD_CORRECTOR_WEIGHT * (elbowForwardTrigger + oppositeProjection);
correctionVector = forwardAmount * frontVector;
@ -1561,31 +1597,18 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex,
return true;
}
// returns a poleVector for the knees that is a blend of the foot and the hips.
// targetFootPose is in rig space
// result poleVector is also in rig space.
glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const {
const float FOOT_THETA = 0.8969f; // 51.39 degrees
const glm::vec3 localFootForward(0.0f, cosf(FOOT_THETA), sinf(FOOT_THETA));
glm::vec3 footForward = targetFootPose.rot() * localFootForward;
AnimPose hipsPose = _externalPoseSet._absolutePoses[hipsIndex];
AnimPose footPose = targetFootPose;
AnimPose kneePose = _externalPoseSet._absolutePoses[kneeIndex];
AnimPose upLegPose = _externalPoseSet._absolutePoses[upLegIndex];
glm::vec3 hipsForward = hipsPose.rot() * Vectors::UNIT_Z;
// ray from foot to upLeg
glm::vec3 d = glm::normalize(footPose.trans() - upLegPose.trans());
// form a plane normal to the hips x-axis
glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X;
// project d onto this plane
glm::vec3 dProj = d - glm::dot(d, n) * n;
// rotate dProj by 90 degrees to get the poleVector.
glm::vec3 poleVector = glm::angleAxis(-PI / 2.0f, n) * dProj;
// blend the foot oreintation into the pole vector
glm::quat kneeToFootDelta = footPose.rot() * glm::inverse(kneePose.rot());
const float WRIST_POLE_ADJUST_FACTOR = 0.5f;
glm::quat poleAdjust = quatLerp(Quaternions::IDENTITY, kneeToFootDelta, WRIST_POLE_ADJUST_FACTOR);
return glm::normalize(poleAdjust * poleVector);
return glm::normalize(lerp(hipsForward, footForward, 0.75f));
}
void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) {
@ -1610,12 +1633,12 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
updateHead(headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]);
updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, dt,
updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, headEnabled, dt,
params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand],
params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo,
params.rigToSensorMatrix, sensorToRigMatrix);
updateFeet(leftFootEnabled, rightFootEnabled,
updateFeet(leftFootEnabled, rightFootEnabled, headEnabled,
params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot],
params.rigToSensorMatrix, sensorToRigMatrix);

View file

@ -75,6 +75,10 @@ public:
};
struct ControllerParameters {
ControllerParameters() {
memset(primaryControllerFlags, 0, NumPrimaryControllerTypes);
memset(secondaryControllerFlags, 0, NumPrimaryControllerTypes);
}
glm::mat4 rigToSensorMatrix;
AnimPose primaryControllerPoses[NumPrimaryControllerTypes]; // rig space
uint8_t primaryControllerFlags[NumPrimaryControllerTypes];
@ -229,12 +233,13 @@ protected:
void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix);
void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated,
bool leftArmEnabled, bool rightArmEnabled, float dt,
bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt,
const AnimPose& leftHandPose, const AnimPose& rightHandPose,
const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo,
const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo,
const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix);
void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose,
void updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled,
const AnimPose& leftFootPose, const AnimPose& rightFootPose,
const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix);
void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade);

View file

@ -16,73 +16,13 @@
#include "Sound.h"
/// Scriptable interface for sound loading.
class SoundCache : public ResourceCache, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* API to manage sound cache resources.
* @namespace SoundCache
*
* @hifi-interface
* @hifi-client-entity
* @hifi-server-entity
* @hifi-assignment-client
*
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*/
// Functions are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* Get the list of all resource URLs.
* @function SoundCache.getResourceList
* @returns {string[]}
*/
/**jsdoc
* @function SoundCache.dirty
* @returns {Signal}
*/
/**jsdoc
* @function SoundCache.updateTotalSize
* @param {number} deltaSize
*/
/**jsdoc
* Prefetches a resource.
* @function SoundCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {ResourceObject}
*/
/**jsdoc
* Asynchronously loads a resource from the specified URL and returns it.
* @function SoundCache.getResource
* @param {string} url - URL of the resource to load.
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
* @param {} [extra=null]
* @returns {object}
*/
/**jsdoc
* @function SoundCache.getSound
* @param {string} url
* @returns {SoundObject}
*/
Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url);
protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
const void* extra) override;

View file

@ -0,0 +1,20 @@
//
// SoundCacheScriptingInterface.cpp
// libraries/audio/src
//
// Created by David Rowe on 25 Jul 2018.
// 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 "SoundCacheScriptingInterface.h"
SoundCacheScriptingInterface::SoundCacheScriptingInterface() :
ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get<SoundCache>())
{ }
SharedSoundPointer SoundCacheScriptingInterface::getSound(const QUrl& url) {
return DependencyManager::get<SoundCache>()->getSound(url);
}

View file

@ -0,0 +1,58 @@
//
// SoundCacheScriptingInterface.h
// libraries/audio/src
//
// Created by David Rowe on 25 Jul 2018.
// 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
//
#pragma once
#ifndef hifi_SoundCacheScriptingInterface_h
#define hifi_SoundCacheScriptingInterface_h
#include <QObject>
#include <ResourceCache.h>
#include "SoundCache.h"
class SoundCacheScriptingInterface : public ScriptableResourceCache, public Dependency {
Q_OBJECT
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* API to manage sound cache resources.
* @namespace SoundCache
*
* @hifi-interface
* @hifi-client-entity
* @hifi-server-entity
* @hifi-assignment-client
*
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize
* @borrows ResourceCache.prefetch as prefetch
* @borrows ResourceCache.dirty as dirty
*/
public:
SoundCacheScriptingInterface();
/**jsdoc
* @function SoundCache.getSound
* @param {string} url
* @returns {SoundObject}
*/
Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url);
};
#endif // hifi_SoundCacheScriptingInterface_h

View file

@ -54,6 +54,17 @@ signals:
*/
void displayModeChanged(bool isHMDMode);
/**jsdoc
* Triggered when the <code>HMD.mounted</code> property value changes.
* @function HMD.mountedChanged
* @returns {Signal}
* @example <caption>Report when there's a change in the HMD being worn.</caption>
* HMD.mountedChanged.connect(function () {
* print("Mounted changed. HMD is mounted: " + HMD.mounted);
* });
*/
void mountedChanged();
private:
float _IPDScale{ 1.0 };
};

View file

@ -21,6 +21,7 @@
#include "../OpenGLDisplayPlugin.h"
class HmdDisplayPlugin : public OpenGLDisplayPlugin {
Q_OBJECT
using Parent = OpenGLDisplayPlugin;
public:
~HmdDisplayPlugin();
@ -45,6 +46,9 @@ public:
virtual bool onDisplayTextureReset() override { _clearPreviewFlag = true; return true; };
signals:
void hmdMountedChanged();
protected:
virtual void hmdPresent() = 0;
virtual bool isHmdMounted() const = 0;

View file

@ -40,7 +40,6 @@
#include <PointerManager.h>
size_t std::hash<EntityItemID>::operator()(const EntityItemID& id) const { return qHash(id); }
std::function<bool()> EntityTreeRenderer::_entitiesShouldFadeFunction;
QString resolveScriptURL(const QString& scriptUrl) {

View file

@ -40,9 +40,6 @@ namespace render { namespace entities {
} }
// Allow the use of std::unordered_map with QUuid keys
namespace std { template<> struct hash<EntityItemID> { size_t operator()(const EntityItemID& id) const; }; }
using EntityRenderer = render::entities::EntityRenderer;
using EntityRendererPointer = render::entities::EntityRendererPointer;
using EntityRendererWeakPointer = render::entities::EntityRendererWeakPointer;

View file

@ -69,3 +69,4 @@ QVector<EntityItemID> qVectorEntityItemIDFromScriptValue(const QScriptValue& arr
return newVector;
}
size_t std::hash<EntityItemID>::operator()(const EntityItemID& id) const { return qHash(id); }

View file

@ -45,4 +45,7 @@ QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID
void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& properties);
QVector<EntityItemID> qVectorEntityItemIDFromScriptValue(const QScriptValue& array);
// Allow the use of std::unordered_map with QUuid keys
namespace std { template<> struct hash<EntityItemID> { size_t operator()(const EntityItemID& id) const; }; }
#endif // hifi_EntityItemID_h

View file

@ -891,28 +891,30 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency,
* use PNG format.
* @property {number} particleRadius=0.025 - The radius of each particle at the middle of its life.
* @property {number} radiusStart=NAN - The radius of each particle at the start of its life. If NAN, the
* @property {number} radiusStart=NaN - The radius of each particle at the start of its life. If <code>NaN</code>, the
* <code>particleRadius</code> value is used.
* @property {number} radiusFinish=NAN - The radius of each particle at the end of its life. If NAN, the
* @property {number} radiusFinish=NaN - The radius of each particle at the end of its life. If <code>NaN</code>, the
* <code>particleRadius</code> value is used.
* @property {number} radiusSpread=0 - The spread in radius that each particle is given. If <code>particleRadius == 0.5</code>
* and <code>radiusSpread == 0.25</code>, each particle will have a radius in the range <code>0.25</code> &ndash; <code>0.75</code>.
* and <code>radiusSpread == 0.25</code>, each particle will have a radius in the range <code>0.25</code> &ndash;
* <code>0.75</code>.
* @property {Color} color=255,255,255 - The color of each particle at the middle of its life.
* @property {Color} colorStart=NAN,NAN,NAN - The color of each particle at the start of its life. If any of the values are NAN, the
* <code>color</code> value is used.
* @property {Color} colorFinish=NAN,NAN,NAN - The color of each particle at the end of its life. If any of the values are NAN, the
* <code>color</code> value is used.
* @property {Color} colorStart={} - The color of each particle at the start of its life. If any of the component values are
* undefined, the <code>color</code> value is used.
* @property {Color} colorFinish={} - The color of each particle at the end of its life. If any of the component values are
* undefined, the <code>color</code> value is used.
* @property {Color} colorSpread=0,0,0 - The spread in color that each particle is given. If
* <code>color == {red: 100, green: 100, blue: 100}</code> and <code>colorSpread ==
* {red: 10, green: 25, blue: 50}</code>, each particle will have an acceleration in the range <code>{red: 90, green: 75, blue: 50}</code>
* &ndash; <code>{red: 110, green: 125, blue: 150}</code>.
* {red: 10, green: 25, blue: 50}</code>, each particle will have a color in the range
* <code>{red: 90, green: 75, blue: 50}</code> &ndash; <code>{red: 110, green: 125, blue: 150}</code>.
* @property {number} alpha=1 - The alpha of each particle at the middle of its life.
* @property {number} alphaStart=NAN - The alpha of each particle at the start of its life. If NAN, the
* @property {number} alphaStart=NaN - The alpha of each particle at the start of its life. If <code>NaN</code>, the
* <code>alpha</code> value is used.
* @property {number} alphaFinish=NAN - The alpha of each particle at the end of its life. If NAN, the
* @property {number} alphaFinish=NaN - The alpha of each particle at the end of its life. If <code>NaN</code>, the
* <code>alpha</code> value is used.
* @property {number} alphaSpread=0 - The spread in alpha that each particle is given. If <code>alpha == 0.5</code>
* and <code>alphaSpread == 0.25</code>, each particle will have an alpha in the range <code>0.25</code> &ndash; <code>0.75</code>.
* and <code>alphaSpread == 0.25</code>, each particle will have an alpha in the range <code>0.25</code> &ndash;
* <code>0.75</code>.
* @property {number} particleSpin=0 - The spin of each particle at the middle of its life. In the range <code>-2*PI</code> &ndash; <code>2*PI</code>.
* @property {number} spinStart=NaN - The spin of each particle at the start of its life. In the range <code>-2*PI</code> &ndash; <code>2*PI</code>.
* If <code>NaN</code>, the <code>particleSpin</code> value is used.

View file

@ -140,58 +140,6 @@ class ModelCache : public ResourceCache, public Dependency {
public:
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* API to manage model cache resources.
* @namespace ModelCache
*
* @hifi-interface
* @hifi-client-entity
*
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*/
// Functions are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* Get the list of all resource URLs.
* @function ModelCache.getResourceList
* @returns {string[]}
*/
/**jsdoc
* @function ModelCache.dirty
* @returns {Signal}
*/
/**jsdoc
* @function ModelCache.updateTotalSize
* @param {number} deltaSize
*/
/**jsdoc
* Prefetches a resource.
* @function ModelCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {ResourceObject}
*/
/**jsdoc
* Asynchronously loads a resource from the specified URL and returns it.
* @function ModelCache.getResource
* @param {string} url - URL of the resource to load.
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
* @param {} [extra=null]
* @returns {object}
*/
GeometryResource::Pointer getGeometryResource(const QUrl& url,
const QVariantHash& mapping = QVariantHash(),
const QUrl& textureBaseUrl = QUrl());

View file

@ -0,0 +1,16 @@
//
// ModelCacheScriptingInterface.cpp
// libraries/mmodel-networking/src/model-networking
//
// Created by David Rowe on 25 Jul 2018.
// 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 "ModelCacheScriptingInterface.h"
ModelCacheScriptingInterface::ModelCacheScriptingInterface() :
ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get<ModelCache>())
{ }

View file

@ -0,0 +1,49 @@
//
// ModelCacheScriptingInterface.h
// libraries/mmodel-networking/src/model-networking
//
// Created by David Rowe on 25 Jul 2018.
// 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
//
#pragma once
#ifndef hifi_ModelCacheScriptingInterface_h
#define hifi_ModelCacheScriptingInterface_h
#include <QObject>
#include <ResourceCache.h>
#include "ModelCache.h"
class ModelCacheScriptingInterface : public ScriptableResourceCache, public Dependency {
Q_OBJECT
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* API to manage model cache resources.
* @namespace ModelCache
*
* @hifi-interface
* @hifi-client-entity
*
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize
* @borrows ResourceCache.prefetch as prefetch
* @borrows ResourceCache.dirty as dirty
*/
public:
ModelCacheScriptingInterface();
};
#endif // hifi_ModelCacheScriptingInterface_h

View file

@ -156,58 +156,6 @@ class TextureCache : public ResourceCache, public Dependency {
public:
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* API to manage texture cache resources.
* @namespace TextureCache
*
* @hifi-interface
* @hifi-client-entity
*
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*/
// Functions are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* Get the list of all resource URLs.
* @function TextureCache.getResourceList
* @returns {string[]}
*/
/**jsdoc
* @function TextureCache.dirty
* @returns {Signal}
*/
/**jsdoc
* @function TextureCache.updateTotalSize
* @param {number} deltaSize
*/
/**jsdoc
* Prefetches a resource.
* @function TextureCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {ResourceObject}
*/
/**jsdoc
* Asynchronously loads a resource from the specified URL and returns it.
* @function TextureCache.getResource
* @param {string} url - URL of the resource to load.
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
* @param {} [extra=null]
* @returns {object}
*/
/// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture
/// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and
/// the second, a set of random unit vectors to be used as noise gradients.
@ -248,21 +196,10 @@ public:
gpu::ContextPointer getGPUContext() const { return _gpuContext; }
signals:
/**jsdoc
* @function TextureCache.spectatorCameraFramebufferReset
* @returns {Signal}
*/
void spectatorCameraFramebufferReset();
protected:
/**jsdoc
* @function TextureCache.prefetch
* @param {string} url
* @param {number} type
* @param {number} [maxNumPixels=67108864]
* @returns {ResourceObject}
*/
// Overload ResourceCache::prefetch to allow specifying texture type for loads
Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
@ -273,6 +210,7 @@ private:
friend class ImageReader;
friend class NetworkTexture;
friend class DilatableNetworkTexture;
friend class TextureCacheScriptingInterface;
TextureCache();
virtual ~TextureCache();

View file

@ -0,0 +1,23 @@
//
// TextureCacheScriptingInterface.cpp
// libraries/mmodel-networking/src/model-networking
//
// Created by David Rowe on 25 Jul 2018.
// 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 "TextureCacheScriptingInterface.h"
TextureCacheScriptingInterface::TextureCacheScriptingInterface() :
ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get<TextureCache>())
{
connect(DependencyManager::get<TextureCache>().data(), &TextureCache::spectatorCameraFramebufferReset,
this, &TextureCacheScriptingInterface::spectatorCameraFramebufferReset);
}
ScriptableResource* TextureCacheScriptingInterface::prefetch(const QUrl& url, int type, int maxNumPixels) {
return DependencyManager::get<TextureCache>()->prefetch(url, type, maxNumPixels);
}

View file

@ -0,0 +1,65 @@
//
// TextureCacheScriptingInterface.h
// libraries/mmodel-networking/src/model-networking
//
// Created by David Rowe on 25 Jul 2018.
// 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
//
#pragma once
#ifndef hifi_TextureCacheScriptingInterface_h
#define hifi_TextureCacheScriptingInterface_h
#include <QObject>
#include <ResourceCache.h>
#include "TextureCache.h"
class TextureCacheScriptingInterface : public ScriptableResourceCache, public Dependency {
Q_OBJECT
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* API to manage texture cache resources.
* @namespace TextureCache
*
* @hifi-interface
* @hifi-client-entity
*
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize
* @borrows ResourceCache.prefetch as prefetch
* @borrows ResourceCache.dirty as dirty
*/
public:
TextureCacheScriptingInterface();
/**jsdoc
* @function TextureCache.prefetch
* @param {string} url
* @param {number} type
* @param {number} [maxNumPixels=67108864]
* @returns {ResourceObject}
*/
Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
signals:
/**jsdoc
* @function TextureCache.spectatorCameraFramebufferReset
* @returns {Signal}
*/
void spectatorCameraFramebufferReset();
};
#endif // hifi_TextureCacheScriptingInterface_h

View file

@ -131,6 +131,24 @@ QSharedPointer<Resource> ResourceCacheSharedItems::getHighestPendingRequest() {
return highestResource;
}
ScriptableResourceCache::ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache) {
_resourceCache = resourceCache;
}
QVariantList ScriptableResourceCache::getResourceList() {
return _resourceCache->getResourceList();
}
void ScriptableResourceCache::updateTotalSize(const qint64& deltaSize) {
_resourceCache->updateTotalSize(deltaSize);
}
ScriptableResource* ScriptableResourceCache::prefetch(const QUrl& url, void* extra) {
return _resourceCache->prefetch(url, extra);
}
ScriptableResource::ScriptableResource(const QUrl& url) :
QObject(nullptr),
_url(url) { }

View file

@ -124,9 +124,9 @@ public:
virtual ~ScriptableResource() = default;
/**jsdoc
* Release this resource.
* @function ResourceObject#release
*/
* Release this resource.
* @function ResourceObject#release
*/
Q_INVOKABLE void release();
const QUrl& getURL() const { return _url; }
@ -186,15 +186,6 @@ Q_DECLARE_METATYPE(ScriptableResource*);
class ResourceCache : public QObject {
Q_OBJECT
// JSDoc 3.5.5 doesn't augment namespaces with @property or @function definitions.
// The ResourceCache properties and functions are copied to the different exposed cache classes.
/**jsdoc
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*/
Q_PROPERTY(size_t numTotal READ getNumTotalResources NOTIFY dirty)
Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty)
Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty)
@ -207,11 +198,6 @@ public:
size_t getNumCachedResources() const { return _numUnusedResources; }
size_t getSizeCachedResources() const { return _unusedResourcesSize; }
/**jsdoc
* Get the list of all resource URLs.
* @function ResourceCache.getResourceList
* @returns {string[]}
*/
Q_INVOKABLE QVariantList getResourceList();
static void setRequestLimit(int limit);
@ -237,40 +223,17 @@ public:
signals:
/**jsdoc
* @function ResourceCache.dirty
* @returns {Signal}
*/
void dirty();
protected slots:
/**jsdoc
* @function ResourceCache.updateTotalSize
* @param {number} deltaSize
*/
void updateTotalSize(const qint64& deltaSize);
/**jsdoc
* Prefetches a resource.
* @function ResourceCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {ResourceObject}
*/
// Prefetches a resource to be held by the QScriptEngine.
// Left as a protected member so subclasses can overload prefetch
// and delegate to it (see TextureCache::prefetch(const QUrl&, int).
ScriptableResource* prefetch(const QUrl& url, void* extra);
/**jsdoc
* Asynchronously loads a resource from the specified URL and returns it.
* @function ResourceCache.getResource
* @param {string} url - URL of the resource to load.
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
* @param {} [extra=null]
* @returns {object}
*/
// FIXME: The return type is not recognized by JavaScript.
/// Loads a resource from the specified URL and returns it.
/// If the caller is on a different thread than the ResourceCache,
@ -306,6 +269,7 @@ protected:
private:
friend class Resource;
friend class ScriptableResourceCache;
void reserveUnusedResource(qint64 resourceSize);
void resetResourceCounters();
@ -335,6 +299,66 @@ private:
QReadWriteLock _resourcesToBeGottenLock { QReadWriteLock::Recursive };
};
/// Wrapper to expose resource caches to JS/QML
class ScriptableResourceCache : public QObject {
Q_OBJECT
// JSDoc 3.5.5 doesn't augment name spaces with @property definitions so the following properties JSDoc is copied to the
// different exposed cache classes.
/**jsdoc
* @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
*/
Q_PROPERTY(size_t numTotal READ getNumTotalResources NOTIFY dirty)
Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty)
Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty)
Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty)
public:
ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache);
/**jsdoc
* Get the list of all resource URLs.
* @function ResourceCache.getResourceList
* @returns {string[]}
*/
Q_INVOKABLE QVariantList getResourceList();
/**jsdoc
* @function ResourceCache.updateTotalSize
* @param {number} deltaSize
*/
Q_INVOKABLE void updateTotalSize(const qint64& deltaSize);
/**jsdoc
* Prefetches a resource.
* @function ResourceCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {ResourceObject}
*/
Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra = nullptr);
signals:
/**jsdoc
* @function ResourceCache.dirty
* @returns {Signal}
*/
void dirty();
private:
QSharedPointer<ResourceCache> _resourceCache;
size_t getNumTotalResources() const { return _resourceCache->getNumTotalResources(); }
size_t getSizeTotalResources() const { return _resourceCache->getSizeTotalResources(); }
size_t getNumCachedResources() const { return _resourceCache->getNumCachedResources(); }
size_t getSizeCachedResources() const { return _resourceCache->getSizeCachedResources(); }
};
/// Base class for resources.
class Resource : public QObject {
Q_OBJECT

View file

@ -120,7 +120,7 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() {
if (_numQueuedCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
qCDebug(networking) << "At least" << MAX_SILENT_DOMAIN_SERVER_CHECK_INS << "have been queued without a response from domain-server"
<< "Stopping the current assignment";
setFinished(true);
stop();
} else {
auto nodeList = DependencyManager::get<NodeList>();
QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn");
@ -132,5 +132,5 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() {
void ThreadedAssignment::domainSettingsRequestFailed() {
qCDebug(networking) << "Failed to retreive settings object from domain-server. Bailing on assignment.";
setFinished(true);
stop();
}

View file

@ -24,7 +24,6 @@ public:
ThreadedAssignment(ReceivedMessage& message);
~ThreadedAssignment() { stop(); }
void setFinished(bool isFinished);
virtual void aboutToFinish() { };
void addPacketStatsAndSendStatsPacket(QJsonObject statsObject);
@ -43,6 +42,7 @@ signals:
protected:
void commonInit(const QString& targetName, NodeType_t nodeType);
void setFinished(bool isFinished);
bool _isFinished;
QTimer _domainServerTimer;

View file

@ -132,6 +132,7 @@ public:
OctreeDataPersist,
EntityClone,
EntityQueryInitialResultsComplete,
NUM_PACKET_TYPE
};

View file

@ -192,6 +192,8 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe
_elementsInLastWindow = 0;
_entitiesInLastWindow = 0;
}
_lastOctreeMessageSequence = sequence;
}
}

View file

@ -56,6 +56,8 @@ public:
float getAverageUncompressPerPacket() const { return _uncompressPerPacket.getAverage(); }
float getAverageReadBitstreamPerPacket() const { return _readBitstreamPerPacket.getAverage(); }
OCTREE_PACKET_SEQUENCE getLastOctreeMessageSequence() const { return _lastOctreeMessageSequence; }
protected:
virtual OctreePointer createTree() = 0;
@ -77,6 +79,7 @@ protected:
int _packetsInLastWindow = 0;
int _elementsInLastWindow = 0;
int _entitiesInLastWindow = 0;
std::atomic<OCTREE_PACKET_SEQUENCE> _lastOctreeMessageSequence;
};

View file

@ -27,6 +27,10 @@ OctreeQuery::OctreeQuery(bool randomizeConnectionID) {
}
}
OctreeQuery::OctreeQueryFlags operator|=(OctreeQuery::OctreeQueryFlags& lhs, int rhs) {
return lhs = OctreeQuery::OctreeQueryFlags(lhs | rhs);
}
int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
unsigned char* bufferStart = destinationBuffer;
@ -76,7 +80,12 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
memcpy(destinationBuffer, binaryParametersDocument.data(), binaryParametersBytes);
destinationBuffer += binaryParametersBytes;
}
OctreeQueryFlags queryFlags { NoFlags };
queryFlags |= (_reportInitialCompletion ? OctreeQuery::WantInitialCompletion : 0);
memcpy(destinationBuffer, &queryFlags, sizeof(queryFlags));
destinationBuffer += sizeof(queryFlags);
return destinationBuffer - bufferStart;
}
@ -150,6 +159,12 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
QWriteLocker jsonParameterLocker { &_jsonParametersLock };
_jsonParameters = newJsonDocument.object();
}
OctreeQueryFlags queryFlags;
memcpy(&queryFlags, sourceBuffer, sizeof(queryFlags));
sourceBuffer += sizeof(queryFlags);
_reportInitialCompletion = bool(queryFlags & OctreeQueryFlags::WantInitialCompletion);
return sourceBuffer - startPosition;
}

View file

@ -52,6 +52,10 @@ public:
bool hasReceivedFirstQuery() const { return _hasReceivedFirstQuery; }
// Want a report when the initial query is complete.
bool wantReportInitialCompletion() const { return _reportInitialCompletion; }
void setReportInitialCompletion(bool reportInitialCompletion) { _reportInitialCompletion = reportInitialCompletion; }
signals:
void incomingConnectionIDChanged();
@ -73,8 +77,12 @@ protected:
QJsonObject _jsonParameters;
QReadWriteLock _jsonParametersLock;
enum OctreeQueryFlags : uint16_t { NoFlags = 0x0, WantInitialCompletion = 0x1 };
friend OctreeQuery::OctreeQueryFlags operator|=(OctreeQuery::OctreeQueryFlags& lhs, const int rhs);
bool _hasReceivedFirstQuery { false };
bool _reportInitialCompletion { false };
};
#endif // hifi_OctreeQuery_h

View file

@ -64,9 +64,9 @@ ShapeManager* ObjectMotionState::getShapeManager() {
}
ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) :
_shape(shape),
_lastKinematicStep(worldSimulationStep)
{
setShape(shape);
}
ObjectMotionState::~ObjectMotionState() {

View file

@ -175,13 +175,13 @@ protected:
virtual void setMotionType(PhysicsMotionType motionType);
void updateCCDConfiguration();
void setRigidBody(btRigidBody* body);
virtual void setRigidBody(btRigidBody* body);
virtual void setShape(const btCollisionShape* shape);
MotionStateType _type { MOTIONSTATE_TYPE_INVALID }; // type of MotionState
PhysicsMotionType _motionType { MOTION_TYPE_STATIC }; // type of motion: KINEMATIC, DYNAMIC, or STATIC
const btCollisionShape* _shape;
const btCollisionShape* _shape { nullptr };
btRigidBody* _body { nullptr };
float _density { 1.0f };

View file

@ -219,6 +219,20 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const
}
logException(output);
});
if (_type == Type::ENTITY_CLIENT || _type == Type::ENTITY_SERVER) {
QObject::connect(this, &ScriptEngine::update, this, [this]() {
// process pending entity script content
if (!_contentAvailableQueue.empty()) {
EntityScriptContentAvailableMap pending;
std::swap(_contentAvailableQueue, pending);
for (auto& pair : pending) {
auto& args = pair.second;
entityScriptContentAvailable(args.entityID, args.scriptOrURL, args.contents, args.isURL, args.success, args.status);
}
}
});
}
}
QString ScriptEngine::getContext() const {
@ -2181,7 +2195,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << QUrl(url).fileName() << entityID.toString();
#endif
if (!isStopping() && _entityScripts.contains(entityID)) {
entityScriptContentAvailable(entityID, url, contents, isURL, success, status);
_contentAvailableQueue[entityID] = { entityID, url, contents, isURL, success, status };
} else {
#ifdef DEBUG_ENTITY_STATES
qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting";

View file

@ -12,6 +12,7 @@
#ifndef hifi_ScriptEngine_h
#define hifi_ScriptEngine_h
#include <unordered_map>
#include <vector>
#include <QtCore/QObject>
@ -71,6 +72,17 @@ public:
//bool forceRedownload;
};
struct EntityScriptContentAvailable {
EntityItemID entityID;
QString scriptOrURL;
QString contents;
bool isURL;
bool success;
QString status;
};
typedef std::unordered_map<EntityItemID, EntityScriptContentAvailable> EntityScriptContentAvailableMap;
typedef QList<CallbackData> CallbackList;
typedef QHash<QString, CallbackList> RegisteredEventHandlers;
@ -762,6 +774,7 @@ protected:
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
QHash<QString, EntityItemID> _occupiedScriptURLs;
QList<DeferredLoadEntity> _deferredEntityLoads;
EntityScriptContentAvailableMap _contentAvailableQueue;
bool _isThreaded { false };
QScriptEngineDebugger* _debugger { nullptr };

View file

@ -59,7 +59,11 @@ const int32_t BULLET_COLLISION_MASK_KINEMATIC = BULLET_COLLISION_MASK_STATIC;
// MY_AVATAR does not collide with itself
const int32_t BULLET_COLLISION_MASK_MY_AVATAR = ~(BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_MY_AVATAR);
const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_MASK_DEFAULT;
// OTHER_AVATARs are dynamic, but are slammed to whatever the avatar-mixer says, which means
// their motion can't actually be affected by the local physics simulation -- we rely on the remote simulation
// to move its avatar around correctly and to communicate its motion through the avatar-mixer.
// Therefore, they only need to collide against things that can be affected by their motion: dynamic and MyAvatar
const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_GROUP_DYNAMIC | BULLET_COLLISION_GROUP_MY_AVATAR;
// COLLISIONLESS gets an empty mask.
const int32_t BULLET_COLLISION_MASK_COLLISIONLESS = 0;

View file

@ -69,7 +69,7 @@ bool ConicalViewFrustum::intersects(const AABox& box) const {
return intersects(position, distance, radius);
}
bool ConicalViewFrustum::getAngularSize(const AACube& cube) const {
float ConicalViewFrustum::getAngularSize(const AACube& cube) const {
auto radius = 0.5f * SQRT_THREE * cube.getScale(); // radius of bounding sphere
auto position = cube.calcCenter() - _position; // position of bounding sphere in view-frame
float distance = glm::length(position);
@ -77,7 +77,7 @@ bool ConicalViewFrustum::getAngularSize(const AACube& cube) const {
return getAngularSize(distance, radius);
}
bool ConicalViewFrustum::getAngularSize(const AABox& box) const {
float ConicalViewFrustum::getAngularSize(const AABox& box) const {
auto radius = 0.5f * glm::length(box.getScale()); // radius of bounding sphere
auto position = box.calcCenter() - _position; // position of bounding sphere in view-frame
float distance = glm::length(position);
@ -107,7 +107,7 @@ bool ConicalViewFrustum::intersects(const glm::vec3& relativePosition, float dis
sqrtf(distance * distance - radius * radius) * _cosAngle - radius * _sinAngle;
}
bool ConicalViewFrustum::getAngularSize(float distance, float radius) const {
float ConicalViewFrustum::getAngularSize(float distance, float radius) const {
const float AVOID_DIVIDE_BY_ZERO = 0.001f;
float angularSize = radius / (distance + AVOID_DIVIDE_BY_ZERO);
return angularSize;
@ -144,3 +144,8 @@ int ConicalViewFrustum::deserialize(const unsigned char* sourceBuffer) {
return sourceBuffer - startPosition;
}
void ConicalViewFrustum::setSimpleRadius(float radius) {
_radius = radius;
_farClip = radius / 2.0f;
}

View file

@ -45,15 +45,18 @@ public:
bool intersects(const AACube& cube) const;
bool intersects(const AABox& box) const;
bool getAngularSize(const AACube& cube) const;
bool getAngularSize(const AABox& box) const;
float getAngularSize(const AACube& cube) const;
float getAngularSize(const AABox& box) const;
bool intersects(const glm::vec3& relativePosition, float distance, float radius) const;
bool getAngularSize(float distance, float radius) const;
float getAngularSize(float distance, float radius) const;
int serialize(unsigned char* destinationBuffer) const;
int deserialize(const unsigned char* sourceBuffer);
// Just test for within radius.
void setSimpleRadius(float radius);
private:
glm::vec3 _position { 0.0f, 0.0f, 0.0f };
glm::vec3 _direction { 0.0f, 0.0f, 1.0f };

View file

@ -36,6 +36,10 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
if (ovr::reorientRequested(status)) {
emit resetSensorsRequested();
}
if (ovr::hmdMounted(status) != _hmdMounted) {
_hmdMounted = !_hmdMounted;
emit hmdMountedChanged();
}
_currentRenderFrameInfo = FrameInfo();
_currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds();

View file

@ -47,4 +47,5 @@ protected:
ovrLayerEyeFov _sceneLayer;
ovrViewScaleDesc _viewScaleDesc;
// ovrLayerEyeFovDepth _depthLayer;
bool _hmdMounted { false };
};

View file

@ -699,7 +699,11 @@ void OpenVrDisplayPlugin::postPreview() {
_nextSimPoseData = nextSim;
});
_nextRenderPoseData = nextRender;
}
if (isHmdMounted() != _hmdMounted) {
_hmdMounted = !_hmdMounted;
emit hmdMountedChanged();
}
}

View file

@ -92,4 +92,6 @@ private:
friend class OpenVrSubmitThread;
bool _asyncReprojectionActive { false };
bool _hmdMounted { false };
};

View file

@ -66,11 +66,13 @@ Script.include("/~/system/libraries/utils.js");
this.sendPickData = function(controllerData) {
if (controllerData.triggerClicks[this.hand]) {
var hand = this.hand === RIGHT_HAND ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
if (!this.triggerClicked) {
this.selectedTarget = controllerData.rayPicks[this.hand];
if (!this.selectedTarget.intersects) {
Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
method: "clearSelection"
method: "clearSelection",
hand: hand
}));
}
}
@ -78,13 +80,15 @@ Script.include("/~/system/libraries/utils.js");
if (!this.isTabletMaterialEntity(this.selectedTarget.objectID)) {
Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
method: "selectEntity",
entityID: this.selectedTarget.objectID
entityID: this.selectedTarget.objectID,
hand: hand
}));
}
} else if (this.selectedTarget.type === Picks.INTERSECTED_OVERLAY) {
Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
method: "selectOverlay",
overlayID: this.selectedTarget.objectID
overlayID: this.selectedTarget.objectID,
hand: hand
}));
}

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