Merge branch 'master' of https://github.com/highfidelity/hifi into exportToTestRail

This commit is contained in:
NissimHadar 2018-08-01 13:49:20 -07:00
commit 64f651a266
76 changed files with 4250 additions and 1660 deletions

View file

@ -19,6 +19,7 @@
#include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkReply>
#include <QThread> #include <QThread>
#include <AnimationCacheScriptingInterface.h>
#include <AssetClient.h> #include <AssetClient.h>
#include <AvatarHashMap.h> #include <AvatarHashMap.h>
#include <AudioInjectorManager.h> #include <AudioInjectorManager.h>
@ -32,6 +33,7 @@
#include <ResourceCache.h> #include <ResourceCache.h>
#include <ScriptCache.h> #include <ScriptCache.h>
#include <ScriptEngines.h> #include <ScriptEngines.h>
#include <SoundCacheScriptingInterface.h>
#include <SoundCache.h> #include <SoundCache.h>
#include <UsersScriptingInterface.h> #include <UsersScriptingInterface.h>
#include <UUID.h> #include <UUID.h>
@ -71,6 +73,7 @@ Agent::Agent(ReceivedMessage& message) :
DependencyManager::set<ResourceCacheSharedItems>(); DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<SoundCache>(); DependencyManager::set<SoundCache>();
DependencyManager::set<SoundCacheScriptingInterface>();
DependencyManager::set<AudioScriptingInterface>(); DependencyManager::set<AudioScriptingInterface>();
DependencyManager::set<AudioInjectorManager>(); DependencyManager::set<AudioInjectorManager>();
@ -453,8 +456,8 @@ void Agent::executeScript() {
// register ourselves to the script engine // register ourselves to the script engine
_scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); _scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this));
_scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data()); _scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
_scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data()); _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor); QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor);
_scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); _scriptEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
@ -843,6 +846,7 @@ void Agent::aboutToFinish() {
DependencyManager::destroy<ScriptEngines>(); DependencyManager::destroy<ScriptEngines>();
DependencyManager::destroy<ResourceCacheSharedItems>(); DependencyManager::destroy<ResourceCacheSharedItems>();
DependencyManager::destroy<SoundCacheScriptingInterface>();
DependencyManager::destroy<SoundCache>(); DependencyManager::destroy<SoundCache>();
DependencyManager::destroy<AudioScriptingInterface>(); DependencyManager::destroy<AudioScriptingInterface>();

View file

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

View file

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

View file

@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
ExternalProject_Add( ExternalProject_Add(
${EXTERNAL_NAME} ${EXTERNAL_NAME}
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC69.zip URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC70v2.zip
URL_MD5 e2467b08de069da7e22ec8e032435592 URL_MD5 35fcc8e635e71d0b00a08455a2582448
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND "" BUILD_COMMAND ""
INSTALL_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 color: 0
property int colorScheme: hifi.colorSchemes.light property int colorScheme: hifi.colorSchemes.light
property int fontSize: hifi.fontSizes.buttonLabel property int fontSize: hifi.fontSizes.buttonLabel
property int radius: hifi.buttons.radius
property alias implicitTextWidth: buttonText.implicitWidth property alias implicitTextWidth: buttonText.implicitWidth
property string buttonGlyph: ""; property string buttonGlyph: "";
property int fontCapitalization: Font.AllUppercase property int fontCapitalization: Font.AllUppercase
@ -46,7 +47,7 @@ Original.Button {
} }
background: Rectangle { background: Rectangle {
radius: hifi.buttons.radius radius: control.radius
border.width: (control.color === hifi.buttons.none || border.width: (control.color === hifi.buttons.none ||
(control.color === hifi.buttons.noneBorderless && control.hovered) || (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 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 { down.indicator: Item {
x: spinBox.width - implicitWidth - 5 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 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 { HifiControls.Label {
id: spinBoxLabel id: spinBoxLabel

View file

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

View file

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

View file

@ -1,25 +1,42 @@
import "../../styles-uit" import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import QtQuick 2.9 import QtQuick 2.9
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
ShadowRectangle { Item {
id: root
width: 44 width: 44
height: 28 height: 28
AvatarAppStyle { signal clicked();
id: style
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 { DropShadow {
GradientStop { position: 0.0; color: style.colors.blueHighlight } id: shadow
GradientStop { position: 1.0; color: style.colors.blueAccent } 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 glyphText: glyph.text
property alias glyphRotation: glyph.rotation property alias glyphRotation: glyph.rotation
property alias glyphSize: glyph.size property alias glyphSize: glyph.size
radius: 3
HiFiGlyphs { HiFiGlyphs {
id: glyph id: glyph
color: 'white' color: 'white'

View file

@ -63,6 +63,7 @@
#include <AddressManager.h> #include <AddressManager.h>
#include <AnimDebugDraw.h> #include <AnimDebugDraw.h>
#include <BuildInfo.h> #include <BuildInfo.h>
#include <AnimationCacheScriptingInterface.h>
#include <AssetClient.h> #include <AssetClient.h>
#include <AssetUpload.h> #include <AssetUpload.h>
#include <AutoUpdater.h> #include <AutoUpdater.h>
@ -98,6 +99,8 @@
#include <MainWindow.h> #include <MainWindow.h>
#include <MappingRequest.h> #include <MappingRequest.h>
#include <MessagesClient.h> #include <MessagesClient.h>
#include <model-networking/ModelCacheScriptingInterface.h>
#include <model-networking/TextureCacheScriptingInterface.h>
#include <ModelEntityItem.h> #include <ModelEntityItem.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <NetworkingConstants.h> #include <NetworkingConstants.h>
@ -127,7 +130,7 @@
#include <ScriptEngines.h> #include <ScriptEngines.h>
#include <ScriptCache.h> #include <ScriptCache.h>
#include <ShapeEntityItem.h> #include <ShapeEntityItem.h>
#include <SoundCache.h> #include <SoundCacheScriptingInterface.h>
#include <ui/TabletScriptingInterface.h> #include <ui/TabletScriptingInterface.h>
#include <ui/ToolbarScriptingInterface.h> #include <ui/ToolbarScriptingInterface.h>
#include <InteractiveWindow.h> #include <InteractiveWindow.h>
@ -143,6 +146,7 @@
#include <QmlFragmentClass.h> #include <QmlFragmentClass.h>
#include <Preferences.h> #include <Preferences.h>
#include <display-plugins/CompositorHelper.h> #include <display-plugins/CompositorHelper.h>
#include <display-plugins/hmd/HmdDisplayPlugin.h>
#include <trackers/EyeTracker.h> #include <trackers/EyeTracker.h>
#include <avatars-renderer/ScriptAvatar.h> #include <avatars-renderer/ScriptAvatar.h>
#include <RenderableEntityItem.h> #include <RenderableEntityItem.h>
@ -866,16 +870,20 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<recording::ClipCache>(); DependencyManager::set<recording::ClipCache>();
DependencyManager::set<GeometryCache>(); DependencyManager::set<GeometryCache>();
DependencyManager::set<ModelCache>(); DependencyManager::set<ModelCache>();
DependencyManager::set<ModelCacheScriptingInterface>();
DependencyManager::set<ScriptCache>(); DependencyManager::set<ScriptCache>();
DependencyManager::set<SoundCache>(); DependencyManager::set<SoundCache>();
DependencyManager::set<SoundCacheScriptingInterface>();
DependencyManager::set<DdeFaceTracker>(); DependencyManager::set<DdeFaceTracker>();
DependencyManager::set<EyeTracker>(); DependencyManager::set<EyeTracker>();
DependencyManager::set<AudioClient>(); DependencyManager::set<AudioClient>();
DependencyManager::set<AudioScope>(); DependencyManager::set<AudioScope>();
DependencyManager::set<DeferredLightingEffect>(); DependencyManager::set<DeferredLightingEffect>();
DependencyManager::set<TextureCache>(); DependencyManager::set<TextureCache>();
DependencyManager::set<TextureCacheScriptingInterface>();
DependencyManager::set<FramebufferCache>(); DependencyManager::set<FramebufferCache>();
DependencyManager::set<AnimationCache>(); DependencyManager::set<AnimationCache>();
DependencyManager::set<AnimationCacheScriptingInterface>();
DependencyManager::set<ModelBlender>(); DependencyManager::set<ModelBlender>();
DependencyManager::set<UsersScriptingInterface>(); DependencyManager::set<UsersScriptingInterface>();
DependencyManager::set<AvatarManager>(); DependencyManager::set<AvatarManager>();
@ -2562,12 +2570,18 @@ Application::~Application() {
DependencyManager::destroy<CompositorHelper>(); // must be destroyed before the FramebufferCache DependencyManager::destroy<CompositorHelper>(); // must be destroyed before the FramebufferCache
DependencyManager::destroy<SoundCacheScriptingInterface>();
DependencyManager::destroy<AvatarManager>(); DependencyManager::destroy<AvatarManager>();
DependencyManager::destroy<AnimationCacheScriptingInterface>();
DependencyManager::destroy<AnimationCache>(); DependencyManager::destroy<AnimationCache>();
DependencyManager::destroy<FramebufferCache>(); DependencyManager::destroy<FramebufferCache>();
DependencyManager::destroy<TextureCacheScriptingInterface>();
DependencyManager::destroy<TextureCache>(); DependencyManager::destroy<TextureCache>();
DependencyManager::destroy<ModelCacheScriptingInterface>();
DependencyManager::destroy<ModelCache>(); DependencyManager::destroy<ModelCache>();
DependencyManager::destroy<ScriptCache>(); DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<SoundCacheScriptingInterface>();
DependencyManager::destroy<SoundCache>(); DependencyManager::destroy<SoundCache>();
DependencyManager::destroy<OctreeStatsProvider>(); DependencyManager::destroy<OctreeStatsProvider>();
DependencyManager::destroy<GeometryCache>(); DependencyManager::destroy<GeometryCache>();
@ -2717,6 +2731,10 @@ void Application::initializeDisplayPlugins() {
QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged,
[this](const QSize& size) { resizeGL(); }); [this](const QSize& size) { resizeGL(); });
QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset); 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 // The default display plugin needs to be activated first, otherwise the display plugin thread
@ -2989,10 +3007,11 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data()); surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
// Caches // Caches
surfaceContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data()); surfaceContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
surfaceContext->setContextProperty("TextureCache", DependencyManager::get<TextureCache>().data()); surfaceContext->setContextProperty("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data());
surfaceContext->setContextProperty("ModelCache", DependencyManager::get<ModelCache>().data()); surfaceContext->setContextProperty("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data());
surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data()); surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data()); surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
@ -6606,10 +6625,10 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("Pointers", DependencyManager::get<PointerScriptingInterface>().data()); scriptEngine->registerGlobalObject("Pointers", DependencyManager::get<PointerScriptingInterface>().data());
// Caches // Caches
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data()); scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get<TextureCache>().data()); scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get<ModelCache>().data()); scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data()); scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface); scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface);

View file

@ -347,17 +347,6 @@ signals:
*/ */
bool shouldShowHandControllersChanged(); 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: public:
HMDScriptingInterface(); HMDScriptingInterface();
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine); static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);

View file

@ -55,7 +55,7 @@
#include "scripting/AccountServicesScriptingInterface.h" #include "scripting/AccountServicesScriptingInterface.h"
#include <plugins/InputConfiguration.h> #include <plugins/InputConfiguration.h>
#include "ui/Snapshot.h" #include "ui/Snapshot.h"
#include "SoundCache.h" #include "SoundCacheScriptingInterface.h"
#include "raypick/PointerScriptingInterface.h" #include "raypick/PointerScriptingInterface.h"
#include <display-plugins/CompositorHelper.h> #include <display-plugins/CompositorHelper.h>
#include "AboutUtil.h" #include "AboutUtil.h"
@ -253,7 +253,7 @@ void Web3DOverlay::setupQmlSurface() {
_webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data()); _webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
_webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data()); _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("MenuInterface", MenuScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data()); _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); _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); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, dt);
} }
processOutputJoints(triggersOut);
return _poses; return _poses;
} }
@ -51,7 +54,7 @@ const AnimPoseVec& AnimBlendLinear::getPosesInternal() const {
return _poses; 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) { size_t prevPoseIndex, size_t nextPoseIndex, float dt) {
if (prevPoseIndex == nextPoseIndex) { if (prevPoseIndex == nextPoseIndex) {
// this can happen if alpha is on an integer boundary // this can happen if alpha is on an integer boundary

View file

@ -30,7 +30,7 @@ public:
AnimBlendLinear(const QString& id, float alpha); AnimBlendLinear(const QString& id, float alpha);
virtual ~AnimBlendLinear() override; 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; } void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
@ -38,7 +38,7 @@ protected:
// for AnimDebugDraw rendering // for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override; 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); size_t prevPoseIndex, size_t nextPoseIndex, float dt);
AnimPoseVec _poses; 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()); assert(_children.size() == _characteristicSpeeds.size());
@ -54,6 +54,9 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars,
setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut);
evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime);
} }
processOutputJoints(triggersOut);
return _poses; return _poses;
} }
@ -62,7 +65,7 @@ const AnimPoseVec& AnimBlendLinearMove::getPosesInternal() const {
return _poses; 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, size_t prevPoseIndex, size_t nextPoseIndex,
float prevDeltaTime, float nextDeltaTime) { float prevDeltaTime, float nextDeltaTime) {
if (prevPoseIndex == nextPoseIndex) { if (prevPoseIndex == nextPoseIndex) {
@ -82,7 +85,7 @@ void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVar
} }
void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIndex, int nextPoseIndex, 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; const float FRAMES_PER_SECOND = 30.0f;
auto prevClipNode = std::dynamic_pointer_cast<AnimClip>(_children[prevPoseIndex]); 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 // detect loop trigger events
if (_phase >= 1.0f) { if (_phase >= 1.0f) {
triggersOut.push_back(_id + "Loop"); triggersOut.setTrigger(_id + "Loop");
_phase = glm::fract(_phase); _phase = glm::fract(_phase);
} }

View file

@ -39,7 +39,7 @@ public:
AnimBlendLinearMove(const QString& id, float alpha, float desiredSpeed, const std::vector<float>& characteristicSpeeds); AnimBlendLinearMove(const QString& id, float alpha, float desiredSpeed, const std::vector<float>& characteristicSpeeds);
virtual ~AnimBlendLinearMove() override; 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 setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
void setDesiredSpeedVar(const QString& desiredSpeedVar) { _desiredSpeedVar = desiredSpeedVar; } void setDesiredSpeedVar(const QString& desiredSpeedVar) { _desiredSpeedVar = desiredSpeedVar; }
@ -48,12 +48,12 @@ protected:
// for AnimDebugDraw rendering // for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override; 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, size_t prevPoseIndex, size_t nextPoseIndex,
float prevDeltaTime, float nextDeltaTime); float prevDeltaTime, float nextDeltaTime);
void setFrameAndPhase(float dt, float alpha, int prevPoseIndex, int nextPoseIndex, 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; 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. // lookup parameters from animVars, using current instance variables as defaults.
_startFrame = animVars.lookup(_startFrameVar, _startFrame); _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]); ::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]);
} }
processOutputJoints(triggersOut);
return _poses; return _poses;
} }
@ -89,7 +91,7 @@ void AnimClip::loadURL(const QString& url) {
void AnimClip::setCurrentFrameInternal(float frame) { void AnimClip::setCurrentFrameInternal(float frame) {
// because dt is 0, we should not encounter any triggers // because dt is 0, we should not encounter any triggers
const float dt = 0.0f; const float dt = 0.0f;
Triggers triggers; AnimVariantMap triggers;
_frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame + _startFrame, dt, _loopFlag, _id, 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); AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag);
virtual ~AnimClip() override; 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 setStartFrameVar(const QString& startFrameVar) { _startFrameVar = startFrameVar; }
void setEndFrameVar(const QString& endFrameVar) { _endFrameVar = endFrameVar; } 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) { if (_skeleton) {
_poses = _skeleton->getRelativeDefaultPoses(); _poses = _skeleton->getRelativeDefaultPoses();
} else { } else {
_poses.clear(); _poses.clear();
} }
processOutputJoints(triggersOut);
return _poses; return _poses;
} }

View file

@ -21,7 +21,7 @@ public:
AnimDefaultPose(const QString& id); AnimDefaultPose(const QString& id);
virtual ~AnimDefaultPose() override; 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: protected:
// for AnimDebugDraw rendering // for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override; 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].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); 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 //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 // don't call this function, call overlay() instead
assert(false); assert(false);
return _relativePoses; return _relativePoses;
} }
//virtual //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 #ifdef Q_OS_ANDROID
// disable IK on android // disable IK on android
return underPoses; return underPoses;
@ -961,6 +953,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0);
if (_hipsTargetIndex >= 0) { if (_hipsTargetIndex >= 0) {
assert(_hipsTargetIndex < (int)targets.size()); assert(_hipsTargetIndex < (int)targets.size());
// slam the hips to match the _hipsTarget // 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); PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
setSecondaryTargets(context); setSecondaryTargets(context);
preconditionRelativePosesToAvoidLimbLock(context, targets); preconditionRelativePosesToAvoidLimbLock(context, targets);
solve(context, targets, dt, jointChainInfoVec); solve(context, targets, dt, jointChainInfoVec);
@ -1056,6 +1050,8 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
} }
} }
processOutputJoints(triggersOut);
return _relativePoses; return _relativePoses;
} }
@ -1750,7 +1746,7 @@ void AnimInverseKinematics::preconditionRelativePosesToAvoidLimbLock(const AnimC
const float MIN_AXIS_LENGTH = 1.0e-4f; const float MIN_AXIS_LENGTH = 1.0e-4f;
for (auto& target : targets) { 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++) { for (int i = 0; i < NUM_LIMBS; i++) {
if (limbs[i].first == target.getIndex()) { if (limbs[i].first == target.getIndex()) {
int tipIndex = limbs[i].first; int tipIndex = limbs[i].first;
@ -1843,6 +1839,10 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s
default: default:
case SolutionSource::RelaxToUnderPoses: case SolutionSource::RelaxToUnderPoses:
blendToPoses(underPoses, underPoses, RELAX_BLEND_FACTOR); 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; break;
case SolutionSource::RelaxToLimitCenterPoses: case SolutionSource::RelaxToLimitCenterPoses:
blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR); 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& typeVar, const QString& weightVar, float weight, const std::vector<float>& flexCoefficients,
const QString& poleVectorEnabledVar, const QString& poleReferenceVectorVar, const QString& poleVectorVar); 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& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut, const AnimPoseVec& underPoses) override;
void clearIKJointLimitHistory(); 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()); 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); _alpha = animVars.lookup(_alphaVar, _alpha);
_poses = underPoses; _poses = underPoses;
@ -74,6 +74,8 @@ const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, cons
} }
} }
processOutputJoints(triggersOut);
return _poses; return _poses;
} }

View file

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

View file

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

View file

@ -25,6 +25,8 @@
#include "AnimManipulator.h" #include "AnimManipulator.h"
#include "AnimInverseKinematics.h" #include "AnimInverseKinematics.h"
#include "AnimDefaultPose.h" #include "AnimDefaultPose.h"
#include "AnimTwoBoneIK.h"
#include "AnimPoleVectorConstraint.h"
using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); 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); 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 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 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 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; 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::Manipulator: return "manipulator";
case AnimNode::Type::InverseKinematics: return "inverseKinematics"; case AnimNode::Type::InverseKinematics: return "inverseKinematics";
case AnimNode::Type::DefaultPose: return "defaultPose"; case AnimNode::Type::DefaultPose: return "defaultPose";
case AnimNode::Type::TwoBoneIK: return "twoBoneIK";
case AnimNode::Type::PoleVectorConstraint: return "poleVectorConstraint";
case AnimNode::Type::NumTypes: return nullptr; case AnimNode::Type::NumTypes: return nullptr;
}; };
return nullptr; return nullptr;
@ -116,6 +122,8 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
case AnimNode::Type::Manipulator: return loadManipulatorNode; case AnimNode::Type::Manipulator: return loadManipulatorNode;
case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode; case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode;
case AnimNode::Type::DefaultPose: return loadDefaultPoseNode; case AnimNode::Type::DefaultPose: return loadDefaultPoseNode;
case AnimNode::Type::TwoBoneIK: return loadTwoBoneIKNode;
case AnimNode::Type::PoleVectorConstraint: return loadPoleVectorConstraintNode;
case AnimNode::Type::NumTypes: return nullptr; case AnimNode::Type::NumTypes: return nullptr;
}; };
return nullptr; return nullptr;
@ -131,6 +139,8 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
case AnimNode::Type::Manipulator: return processDoNothing; case AnimNode::Type::Manipulator: return processDoNothing;
case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::InverseKinematics: return processDoNothing;
case AnimNode::Type::DefaultPose: 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; case AnimNode::Type::NumTypes: return nullptr;
}; };
return nullptr; return nullptr;
@ -189,6 +199,25 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
} \ } \
do {} while (0) 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) { static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) {
auto idVal = jsonObj.value("id"); auto idVal = jsonObj.value("id");
if (!idVal.isString()) { if (!idVal.isString()) {
@ -216,6 +245,16 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr
} }
auto dataObj = dataValue.toObject(); 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); assert((int)type >= 0 && type < AnimNode::Type::NumTypes);
auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl); auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl);
if (!node) { if (!node) {
@ -242,6 +281,9 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr
} }
if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) { if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) {
for (auto&& outputJoint : outputJoints) {
node->addOutputJoint(outputJoint);
}
return node; return node;
} else { } else {
return nullptr; return nullptr;
@ -531,6 +573,41 @@ static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const Q
return node; 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) { void buildChildMap(std::map<QString, int>& map, AnimNode::Pointer node) {
for (int i = 0; i < (int)node->getChildCount(); ++i) { for (int i = 0; i < (int)node->getChildCount(); ++i) {
map.insert(std::pair<QString, int>(node->getChild(i)->getID(), 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(); QString version = versionVal.toString();
// check version // 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(); qCCritical(animation) << "AnimNodeLoader, bad version number" << version << "expected \"1.0\", url =" << jsonUrl.toDisplayString();
return nullptr; 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. // lookup parameters from animVars, using current instance variables as defaults.
// NOTE: switching bonesets can be an expensive operation, let's try to avoid it. // 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; return _poses;
} }

View file

@ -45,7 +45,7 @@ public:
AnimOverlay(const QString& id, BoneSet boneSet, float alpha); AnimOverlay(const QString& id, BoneSet boneSet, float alpha);
virtual ~AnimOverlay() override; 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 setBoneSetVar(const QString& boneSetVar) { _boneSetVar = boneSetVar; }
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; } 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 <GLMHelpers.h>
#include <algorithm> #include <algorithm>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include "AnimUtil.h"
const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
glm::quat(), glm::quat(),
@ -77,4 +78,8 @@ AnimPose::operator glm::mat4() const {
glm::vec4(zAxis, 0.0f), glm::vec4(_trans, 1.0f)); 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; } const glm::vec3& trans() const { return _trans; }
glm::vec3& trans() { return _trans; } glm::vec3& trans() { return _trans; }
void blend(const AnimPose& srcPose, float alpha);
private: private:
friend QDebug operator<<(QDebug debug, const AnimPose& pose); friend QDebug operator<<(QDebug debug, const AnimPose& pose);
glm::vec3 _scale { 1.0f }; glm::vec3 _scale { 1.0f };

View file

@ -282,3 +282,17 @@ void AnimSkeleton::dump(const AnimPoseVec& poses) const {
qCDebug(animation) << "]"; 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(bool verbose) const;
void dump(const AnimPoseVec& poses) const; void dump(const AnimPoseVec& poses) const;
std::vector<int> lookUpJointIndices(const std::vector<QString>& jointNames) const;
protected: protected:
void buildSkeletonFromJoints(const std::vector<FBXJoint>& joints); 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()); QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID());
if (_currentState->getID() != desiredStateID) { if (_currentState->getID() != desiredStateID) {
@ -81,6 +81,9 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
if (!_duringInterp) { if (!_duringInterp) {
_poses = currentStateNode->evaluate(animVars, context, dt, triggersOut); _poses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
} }
processOutputJoints(triggersOut);
return _poses; return _poses;
} }
@ -107,7 +110,7 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, const AnimCon
// because dt is 0, we should not encounter any triggers // because dt is 0, we should not encounter any triggers
const float dt = 0.0f; const float dt = 0.0f;
Triggers triggers; AnimVariantMap triggers;
if (_interpType == InterpType::SnapshotBoth) { if (_interpType == InterpType::SnapshotBoth) {
// snapshot previous pose. // snapshot previous pose.

View file

@ -113,7 +113,7 @@ public:
explicit AnimStateMachine(const QString& id); explicit AnimStateMachine(const QString& id);
virtual ~AnimStateMachine() override; 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; } 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& aPose = a[i];
const AnimPose& bPose = b[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].scale() = lerp(aPose.scale(), bPose.scale(), alpha);
result[i].rot() = safeLerp(aPose.rot(), bPose.rot(), alpha); result[i].rot() = safeLerp(aPose.rot(), bPose.rot(), alpha);
result[i].trans() = lerp(aPose.trans(), bPose.trans(), 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, 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; const float EPSILON = 0.0001f;
float frame = currentFrame; float frame = currentFrame;
@ -79,12 +71,12 @@ float accumulateTime(float startFrame, float endFrame, float timeScale, float cu
if (framesRemaining >= framesTillEnd) { if (framesRemaining >= framesTillEnd) {
if (loopFlag) { if (loopFlag) {
// anim loop // anim loop
triggersOut.push_back(id + "OnLoop"); triggersOut.setTrigger(id + "OnLoop");
framesRemaining -= framesTillEnd; framesRemaining -= framesTillEnd;
frame = clampedStartFrame; frame = clampedStartFrame;
} else { } else {
// anim end // anim end
triggersOut.push_back(id + "OnDone"); triggersOut.setTrigger(id + "OnDone");
frame = endFrame; frame = endFrame;
framesRemaining = 0.0f; 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); glm::quat averageQuats(size_t numQuats, const glm::quat* quats);
float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, 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) { inline glm::quat safeLerp(const glm::quat& a, const glm::quat& b, float alpha) {
// adjust signs if necessary // adjust signs if necessary

View file

@ -24,71 +24,12 @@ class Animation;
typedef QSharedPointer<Animation> AnimationPointer; typedef QSharedPointer<Animation> AnimationPointer;
/// Scriptable interface for FBX animation loading.
class AnimationCache : public ResourceCache, public Dependency { class AnimationCache : public ResourceCache, public Dependency {
Q_OBJECT Q_OBJECT
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
public: 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 QString& url) { return getAnimation(QUrl(url)); }
Q_INVOKABLE AnimationPointer getAnimation(const 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_LEFT_EYE_POS(0.3f, 0.9f, 0.0f);
const glm::vec3 DEFAULT_HEAD_POS(0.0f, 0.75f, 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() { Rig::Rig() {
// Ensure thread-safe access to the rigRegistry. // Ensure thread-safe access to the rigRegistry.
std::lock_guard<std::mutex> guard(rigRegistryMutex); std::lock_guard<std::mutex> guard(rigRegistryMutex);
@ -1049,7 +1064,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
getGeometryToRigTransform(), rigToWorldTransform); getGeometryToRigTransform(), rigToWorldTransform);
// evaluate the animation // evaluate the animation
AnimNode::Triggers triggersOut; AnimVariantMap triggersOut;
_internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut);
if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { 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(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
} }
_animVars.clearTriggers(); _animVars.clearTriggers();
for (auto& trigger : triggersOut) { _animVars = triggersOut;
_animVars.setTrigger(trigger);
}
} }
applyOverridePoses(); applyOverridePoses();
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); 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, 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 AnimPose& leftHandPose, const AnimPose& rightHandPose,
const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo,
const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo,
@ -1305,7 +1318,13 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
_animVars.unset("leftHandPosition"); _animVars.unset("leftHandPosition");
_animVars.unset("leftHandRotation"); _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) { if (rightHandEnabled) {
@ -1364,21 +1383,41 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
_animVars.unset("rightHandPosition"); _animVars.unset("rightHandPosition");
_animVars.unset("rightHandRotation"); _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 glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) {
const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.95f;
int hipsIndex = indexOfJoint("Hips"); 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) { if (leftFootEnabled) {
_animVars.set("leftFootPosition", leftFootPose.trans());
_animVars.set("leftFootRotation", leftFootPose.rot()); _animVars.set(LEFT_FOOT_POSITION, leftFootPose.trans());
_animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); _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 footJointIndex = _animSkeleton->nameToJointIndex("LeftFoot");
int kneeJointIndex = _animSkeleton->nameToJointIndex("LeftLeg"); int kneeJointIndex = _animSkeleton->nameToJointIndex("LeftLeg");
@ -1396,20 +1435,25 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose
_prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector; _prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector;
_animVars.set("leftFootPoleVectorEnabled", true); _animVars.set("leftFootPoleVectorEnabled", true);
_animVars.set("leftFootPoleReferenceVector", Vectors::UNIT_Z);
_animVars.set("leftFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftFootPoleVector)); _animVars.set("leftFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftFootPoleVector));
} else { } else {
_animVars.unset("leftFootPosition"); // We want to drive the IK from the underlying animation.
_animVars.unset("leftFootRotation"); // This gives us the ability to squat while in the HMD, without the feet from dipping under the floor.
_animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); _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); _animVars.set("leftFootPoleVectorEnabled", false);
_prevLeftFootPoleVectorValid = false; _prevLeftFootPoleVectorValid = false;
} }
if (rightFootEnabled) { if (rightFootEnabled) {
_animVars.set("rightFootPosition", rightFootPose.trans()); _animVars.set(RIGHT_FOOT_POSITION, rightFootPose.trans());
_animVars.set("rightFootRotation", rightFootPose.rot()); _animVars.set(RIGHT_FOOT_ROTATION, rightFootPose.rot());
_animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
// 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 footJointIndex = _animSkeleton->nameToJointIndex("RightFoot");
int kneeJointIndex = _animSkeleton->nameToJointIndex("RightLeg"); int kneeJointIndex = _animSkeleton->nameToJointIndex("RightLeg");
@ -1427,13 +1471,16 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose
_prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector; _prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector;
_animVars.set("rightFootPoleVectorEnabled", true); _animVars.set("rightFootPoleVectorEnabled", true);
_animVars.set("rightFootPoleReferenceVector", Vectors::UNIT_Z);
_animVars.set("rightFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightFootPoleVector)); _animVars.set("rightFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightFootPoleVector));
} else { } else {
_animVars.unset("rightFootPosition"); // We want to drive the IK from the underlying animation.
_animVars.unset("rightFootRotation"); // 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("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++) { for (int i = 0; i < (int)children.size(); i++) {
int jointIndex = children[i]; int jointIndex = children[i];
int parentIndex = _animSkeleton->getParentIndex(jointIndex); int parentIndex = _animSkeleton->getParentIndex(jointIndex);
_internalPoseSet._absolutePoses[jointIndex] = _internalPoseSet._absolutePoses[jointIndex] =
_internalPoseSet._absolutePoses[parentIndex] * _internalPoseSet._relativePoses[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 { 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 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. // 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 backVector = oppositeArmPose.trans() - armPose.trans();
glm::vec3 backCenter = armPose.trans() + 0.5f * backVector; glm::vec3 backCenter = armPose.trans() + 0.5f * backVector;
const float OVER_BACK_HEAD_PERCENTAGE = 0.2f; const float OVER_BACK_HEAD_PERCENTAGE = 0.2f;
glm::vec3 headCenter = backCenter + glm::vec3(0, OVER_BACK_HEAD_PERCENTAGE * backVector.length(), 0); 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 headForward = headCenter + horizontalModule * frontVector;
glm::vec3 armToHead = headForward - armPose.trans(); glm::vec3 armToHead = headForward - armPose.trans();
float armToHandDistance = glm::length(armToHand); float armToHandDistance = glm::length(armToHand);
float armToElbowDistance = glm::length(armToElbow); float armToElbowDistance = glm::length(armToElbow);
float elbowToHandDistance = glm::length(elbowToHand); 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 // How much the hand is reaching for the opposite side
float oppositeProjection = glm::dot(armToHandDir, glm::normalize(backVector)); float oppositeProjection = glm::dot(armToHandDir, glm::normalize(backVector));
// Don't use pole vector when the hands are behind // Don't use pole vector when the hands are behind
if (glm::dot(frontVector, armToHand) < 0 && oppositeProjection < 0.5f * armTotalDistance) { if (glm::dot(frontVector, armToHand) < 0 && oppositeProjection < 0.5f * armTotalDistance) {
return false; return false;
@ -1552,7 +1588,7 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex,
const float FORWARD_CORRECTOR_WEIGHT = 3.0f; const float FORWARD_CORRECTOR_WEIGHT = 3.0f;
float elbowForwardTrigger = FORWARD_TRIGGER_PERCENTAGE * armToHandDistance; float elbowForwardTrigger = FORWARD_TRIGGER_PERCENTAGE * armToHandDistance;
if (oppositeProjection > -elbowForwardTrigger) { if (oppositeProjection > -elbowForwardTrigger) {
float forwardAmount = FORWARD_CORRECTOR_WEIGHT * (elbowForwardTrigger + oppositeProjection); float forwardAmount = FORWARD_CORRECTOR_WEIGHT * (elbowForwardTrigger + oppositeProjection);
correctionVector = forwardAmount * frontVector; correctionVector = forwardAmount * frontVector;
@ -1561,31 +1597,18 @@ bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex,
return true; 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 { 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 hipsPose = _externalPoseSet._absolutePoses[hipsIndex];
AnimPose footPose = targetFootPose; glm::vec3 hipsForward = hipsPose.rot() * Vectors::UNIT_Z;
AnimPose kneePose = _externalPoseSet._absolutePoses[kneeIndex];
AnimPose upLegPose = _externalPoseSet._absolutePoses[upLegIndex];
// ray from foot to upLeg return glm::normalize(lerp(hipsForward, footForward, 0.75f));
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);
} }
void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) { 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]); 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.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand],
params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo,
params.rigToSensorMatrix, sensorToRigMatrix); params.rigToSensorMatrix, sensorToRigMatrix);
updateFeet(leftFootEnabled, rightFootEnabled, updateFeet(leftFootEnabled, rightFootEnabled, headEnabled,
params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot], params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot],
params.rigToSensorMatrix, sensorToRigMatrix); params.rigToSensorMatrix, sensorToRigMatrix);

View file

@ -75,6 +75,10 @@ public:
}; };
struct ControllerParameters { struct ControllerParameters {
ControllerParameters() {
memset(primaryControllerFlags, 0, NumPrimaryControllerTypes);
memset(secondaryControllerFlags, 0, NumPrimaryControllerTypes);
}
glm::mat4 rigToSensorMatrix; glm::mat4 rigToSensorMatrix;
AnimPose primaryControllerPoses[NumPrimaryControllerTypes]; // rig space AnimPose primaryControllerPoses[NumPrimaryControllerTypes]; // rig space
uint8_t primaryControllerFlags[NumPrimaryControllerTypes]; uint8_t primaryControllerFlags[NumPrimaryControllerTypes];
@ -229,12 +233,13 @@ protected:
void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix); void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix);
void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, 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 AnimPose& leftHandPose, const AnimPose& rightHandPose,
const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo,
const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo,
const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); 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); 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); 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" #include "Sound.h"
/// Scriptable interface for sound loading.
class SoundCache : public ResourceCache, public Dependency { class SoundCache : public ResourceCache, public Dependency {
Q_OBJECT Q_OBJECT
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
public: 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); Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url);
protected: protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
const void* extra) override; 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); 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: private:
float _IPDScale{ 1.0 }; float _IPDScale{ 1.0 };
}; };

View file

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

View file

@ -40,7 +40,6 @@
#include <PointerManager.h> #include <PointerManager.h>
size_t std::hash<EntityItemID>::operator()(const EntityItemID& id) const { return qHash(id); }
std::function<bool()> EntityTreeRenderer::_entitiesShouldFadeFunction; std::function<bool()> EntityTreeRenderer::_entitiesShouldFadeFunction;
QString resolveScriptURL(const QString& scriptUrl) { 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 EntityRenderer = render::entities::EntityRenderer;
using EntityRendererPointer = render::entities::EntityRendererPointer; using EntityRendererPointer = render::entities::EntityRendererPointer;
using EntityRendererWeakPointer = render::entities::EntityRendererWeakPointer; using EntityRendererWeakPointer = render::entities::EntityRendererWeakPointer;

View file

@ -69,3 +69,4 @@ QVector<EntityItemID> qVectorEntityItemIDFromScriptValue(const QScriptValue& arr
return newVector; 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); void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& properties);
QVector<EntityItemID> qVectorEntityItemIDFromScriptValue(const QScriptValue& array); 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 #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, * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency,
* use PNG format. * use PNG format.
* @property {number} particleRadius=0.025 - The radius of each particle at the middle of its life. * @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. * <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. * <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> * @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} 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 * @property {Color} colorStart={} - The color of each particle at the start of its life. If any of the component values are
* <code>color</code> value is used. * undefined, 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 * @property {Color} colorFinish={} - The color of each particle at the end of its life. If any of the component values are
* <code>color</code> value is used. * undefined, the <code>color</code> value is used.
* @property {Color} colorSpread=0,0,0 - The spread in color that each particle is given. If * @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 == * <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> * {red: 10, green: 25, blue: 50}</code>, each particle will have a color in the range
* &ndash; <code>{red: 110, green: 125, blue: 150}</code>. * <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} 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. * <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. * <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> * @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} 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>. * @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. * If <code>NaN</code>, the <code>particleSpin</code> value is used.

View file

@ -140,58 +140,6 @@ class ModelCache : public ResourceCache, public Dependency {
public: 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, GeometryResource::Pointer getGeometryResource(const QUrl& url,
const QVariantHash& mapping = QVariantHash(), const QVariantHash& mapping = QVariantHash(),
const QUrl& textureBaseUrl = QUrl()); 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: 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 /// 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 /// 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. /// 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; } gpu::ContextPointer getGPUContext() const { return _gpuContext; }
signals: signals:
/**jsdoc
* @function TextureCache.spectatorCameraFramebufferReset
* @returns {Signal}
*/
void spectatorCameraFramebufferReset(); void spectatorCameraFramebufferReset();
protected: 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 // 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); 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 ImageReader;
friend class NetworkTexture; friend class NetworkTexture;
friend class DilatableNetworkTexture; friend class DilatableNetworkTexture;
friend class TextureCacheScriptingInterface;
TextureCache(); TextureCache();
virtual ~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; 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) : ScriptableResource::ScriptableResource(const QUrl& url) :
QObject(nullptr), QObject(nullptr),
_url(url) { } _url(url) { }

View file

@ -124,9 +124,9 @@ public:
virtual ~ScriptableResource() = default; virtual ~ScriptableResource() = default;
/**jsdoc /**jsdoc
* Release this resource. * Release this resource.
* @function ResourceObject#release * @function ResourceObject#release
*/ */
Q_INVOKABLE void release(); Q_INVOKABLE void release();
const QUrl& getURL() const { return _url; } const QUrl& getURL() const { return _url; }
@ -186,15 +186,6 @@ Q_DECLARE_METATYPE(ScriptableResource*);
class ResourceCache : public QObject { class ResourceCache : public QObject {
Q_OBJECT 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 numTotal READ getNumTotalResources NOTIFY dirty)
Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty) Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty)
Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty) Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty)
@ -207,11 +198,6 @@ public:
size_t getNumCachedResources() const { return _numUnusedResources; } size_t getNumCachedResources() const { return _numUnusedResources; }
size_t getSizeCachedResources() const { return _unusedResourcesSize; } size_t getSizeCachedResources() const { return _unusedResourcesSize; }
/**jsdoc
* Get the list of all resource URLs.
* @function ResourceCache.getResourceList
* @returns {string[]}
*/
Q_INVOKABLE QVariantList getResourceList(); Q_INVOKABLE QVariantList getResourceList();
static void setRequestLimit(int limit); static void setRequestLimit(int limit);
@ -237,40 +223,17 @@ public:
signals: signals:
/**jsdoc
* @function ResourceCache.dirty
* @returns {Signal}
*/
void dirty(); void dirty();
protected slots: protected slots:
/**jsdoc
* @function ResourceCache.updateTotalSize
* @param {number} deltaSize
*/
void updateTotalSize(const qint64& 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. // Prefetches a resource to be held by the QScriptEngine.
// Left as a protected member so subclasses can overload prefetch // Left as a protected member so subclasses can overload prefetch
// and delegate to it (see TextureCache::prefetch(const QUrl&, int). // and delegate to it (see TextureCache::prefetch(const QUrl&, int).
ScriptableResource* prefetch(const QUrl& url, void* extra); 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. // FIXME: The return type is not recognized by JavaScript.
/// Loads a resource from the specified URL and returns it. /// Loads a resource from the specified URL and returns it.
/// If the caller is on a different thread than the ResourceCache, /// If the caller is on a different thread than the ResourceCache,
@ -306,6 +269,7 @@ protected:
private: private:
friend class Resource; friend class Resource;
friend class ScriptableResourceCache;
void reserveUnusedResource(qint64 resourceSize); void reserveUnusedResource(qint64 resourceSize);
void resetResourceCounters(); void resetResourceCounters();
@ -335,6 +299,66 @@ private:
QReadWriteLock _resourcesToBeGottenLock { QReadWriteLock::Recursive }; 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. /// Base class for resources.
class Resource : public QObject { class Resource : public QObject {
Q_OBJECT Q_OBJECT

View file

@ -219,6 +219,20 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const
} }
logException(output); 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 { 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(); qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << QUrl(url).fileName() << entityID.toString();
#endif #endif
if (!isStopping() && _entityScripts.contains(entityID)) { if (!isStopping() && _entityScripts.contains(entityID)) {
entityScriptContentAvailable(entityID, url, contents, isURL, success, status); _contentAvailableQueue[entityID] = { entityID, url, contents, isURL, success, status };
} else { } else {
#ifdef DEBUG_ENTITY_STATES #ifdef DEBUG_ENTITY_STATES
qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting"; qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -316,10 +316,10 @@ var toolBar = (function () {
direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z);
// Align entity with Avatar orientation. // Align entity with Avatar orientation.
properties.rotation = MyAvatar.orientation; properties.rotation = MyAvatar.orientation;
var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web", "Material"]; var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web", "Material"];
if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) {
// Adjust position of entity per bounding box prior to creating it. // Adjust position of entity per bounding box prior to creating it.
var registration = properties.registration; var registration = properties.registration;
if (registration === undefined) { if (registration === undefined) {
@ -352,7 +352,12 @@ var toolBar = (function () {
properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } }); properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } });
} }
SelectionManager.saveProperties();
entityID = Entities.addEntity(properties); entityID = Entities.addEntity(properties);
pushCommandForSelections([{
entityID: entityID,
properties: properties
}], [], true);
if (properties.type === "ParticleEffect") { if (properties.type === "ParticleEffect") {
selectParticleEntity(entityID); selectParticleEntity(entityID);
@ -963,13 +968,15 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) {
var data = JSON.parse(message); var data = JSON.parse(message);
if (data.method === "selectOverlay") { if (data.method === "selectOverlay") {
if (wantDebug) { if (!selectionDisplay.triggered() || selectionDisplay.triggeredHand === data.hand) {
print("setting selection to overlay " + data.overlayID); if (wantDebug) {
} print("setting selection to overlay " + data.overlayID);
var entity = entityIconOverlayManager.findEntity(data.overlayID); }
var entity = entityIconOverlayManager.findEntity(data.overlayID);
if (entity !== null) { if (entity !== null) {
selectionManager.setSelections([entity]); selectionManager.setSelections([entity]);
}
} }
} }
} }
@ -1590,7 +1597,7 @@ function deleteSelectedEntities() {
Entities.deleteEntity(entityID); Entities.deleteEntity(entityID);
} }
} }
if (savedProperties.length > 0) { if (savedProperties.length > 0) {
SelectionManager.clearSelections(); SelectionManager.clearSelections();
pushCommandForSelections([], savedProperties); pushCommandForSelections([], savedProperties);
@ -1880,12 +1887,14 @@ Controller.keyReleaseEvent.connect(keyReleaseEvent);
Controller.keyPressEvent.connect(keyPressEvent); Controller.keyPressEvent.connect(keyPressEvent);
function recursiveAdd(newParentID, parentData) { function recursiveAdd(newParentID, parentData) {
var children = parentData.children; if (parentData.children !== undefined) {
for (var i = 0; i < children.length; i++) { var children = parentData.children;
var childProperties = children[i].properties; for (var i = 0; i < children.length; i++) {
childProperties.parentID = newParentID; var childProperties = children[i].properties;
var newChildID = Entities.addEntity(childProperties); childProperties.parentID = newParentID;
recursiveAdd(newChildID, children[i]); var newChildID = Entities.addEntity(childProperties);
recursiveAdd(newChildID, children[i]);
}
} }
} }
@ -1895,16 +1904,22 @@ function recursiveAdd(newParentID, parentData) {
var DELETED_ENTITY_MAP = {}; var DELETED_ENTITY_MAP = {};
function applyEntityProperties(data) { function applyEntityProperties(data) {
var properties = data.setProperties; var editEntities = data.editEntities;
var selectedEntityIDs = []; var selectedEntityIDs = [];
var selectEdits = data.createEntities.length == 0 || !data.selectCreated;
var i, entityID; var i, entityID;
for (i = 0; i < properties.length; i++) { for (i = 0; i < editEntities.length; i++) {
entityID = properties[i].entityID; var entityID = editEntities[i].entityID;
if (DELETED_ENTITY_MAP[entityID] !== undefined) { if (DELETED_ENTITY_MAP[entityID] !== undefined) {
entityID = DELETED_ENTITY_MAP[entityID]; entityID = DELETED_ENTITY_MAP[entityID];
} }
Entities.editEntity(entityID, properties[i].properties); var entityProperties = editEntities[i].properties;
selectedEntityIDs.push(entityID); if (entityProperties !== null) {
Entities.editEntity(entityID, entityProperties);
}
if (selectEdits) {
selectedEntityIDs.push(entityID);
}
} }
for (i = 0; i < data.createEntities.length; i++) { for (i = 0; i < data.createEntities.length; i++) {
entityID = data.createEntities[i].entityID; entityID = data.createEntities[i].entityID;
@ -1933,31 +1948,39 @@ function applyEntityProperties(data) {
// For currently selected entities, push a command to the UndoStack that uses the current entity properties for the // For currently selected entities, push a command to the UndoStack that uses the current entity properties for the
// redo command, and the saved properties for the undo command. Also, include create and delete entity data. // redo command, and the saved properties for the undo command. Also, include create and delete entity data.
function pushCommandForSelections(createdEntityData, deletedEntityData) { function pushCommandForSelections(createdEntityData, deletedEntityData, doNotSaveEditProperties) {
doNotSaveEditProperties = false;
var undoData = { var undoData = {
setProperties: [], editEntities: [],
createEntities: deletedEntityData || [], createEntities: deletedEntityData || [],
deleteEntities: createdEntityData || [], deleteEntities: createdEntityData || [],
selectCreated: true selectCreated: true
}; };
var redoData = { var redoData = {
setProperties: [], editEntities: [],
createEntities: createdEntityData || [], createEntities: createdEntityData || [],
deleteEntities: deletedEntityData || [], deleteEntities: deletedEntityData || [],
selectCreated: false selectCreated: true
}; };
for (var i = 0; i < SelectionManager.selections.length; i++) { for (var i = 0; i < SelectionManager.selections.length; i++) {
var entityID = SelectionManager.selections[i]; var entityID = SelectionManager.selections[i];
var initialProperties = SelectionManager.savedProperties[entityID]; var initialProperties = SelectionManager.savedProperties[entityID];
var currentProperties = Entities.getEntityProperties(entityID); var currentProperties = null;
if (!initialProperties) { if (!initialProperties) {
continue; continue;
} }
undoData.setProperties.push({
if (doNotSaveEditProperties) {
initialProperties = null;
} else {
currentProperties = Entities.getEntityProperties(entityID);
}
undoData.editEntities.push({
entityID: entityID, entityID: entityID,
properties: initialProperties properties: initialProperties
}); });
redoData.setProperties.push({ redoData.editEntities.push({
entityID: entityID, entityID: entityID,
properties: currentProperties properties: currentProperties
}); });
@ -2229,7 +2252,7 @@ var PropertiesTool = function (opts) {
updateSelections(true); updateSelections(true);
} }
}; };
createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); createToolsWindow.webEventReceived.addListener(this, onWebEventReceived);
webView.webEventReceived.connect(onWebEventReceived); webView.webEventReceived.connect(onWebEventReceived);

View file

@ -53,14 +53,18 @@ SelectionManager = (function() {
} }
if (messageParsed.method === "selectEntity") { if (messageParsed.method === "selectEntity") {
if (wantDebug) { if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) {
print("setting selection to " + messageParsed.entityID); if (wantDebug) {
print("setting selection to " + messageParsed.entityID);
}
that.setSelections([messageParsed.entityID]);
} }
that.setSelections([messageParsed.entityID]);
} else if (messageParsed.method === "clearSelection") { } else if (messageParsed.method === "clearSelection") {
that.clearSelections(); if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) {
that.clearSelections();
}
} else if (messageParsed.method === "pointingAt") { } else if (messageParsed.method === "pointingAt") {
if (messageParsed.rightHand) { if (messageParsed.hand === Controller.Standard.RightHand) {
that.pointingAtDesktopWindowRight = messageParsed.desktopWindow; that.pointingAtDesktopWindowRight = messageParsed.desktopWindow;
that.pointingAtTabletRight = messageParsed.tablet; that.pointingAtTabletRight = messageParsed.tablet;
} else { } else {
@ -194,11 +198,40 @@ SelectionManager = (function() {
} }
}; };
// Return true if the given entity with `properties` is being grabbed by an avatar.
// This is mostly a heuristic - there is no perfect way to know if an entity is being
// grabbed.
function nonDynamicEntityIsBeingGrabbedByAvatar(properties) {
if (properties.dynamic || Uuid.isNull(properties.parentID)) {
return false;
}
var avatar = AvatarList.getAvatar(properties.parentID);
if (Uuid.isNull(avatar.sessionUUID)) {
return false;
}
var grabJointNames = [
'RightHand', 'LeftHand',
'_CONTROLLER_RIGHTHAND', '_CONTROLLER_LEFTHAND',
'_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND', '_CAMERA_RELATIVE_CONTROLLER_LEFTHAND'];
for (var i = 0; i < grabJointNames.length; ++i) {
if (avatar.getJointIndex(grabJointNames[i]) === properties.parentJointIndex) {
return true;
}
}
return false;
}
that.duplicateSelection = function() { that.duplicateSelection = function() {
var entitiesToDuplicate = []; var entitiesToDuplicate = [];
var duplicatedEntityIDs = []; var duplicatedEntityIDs = [];
var duplicatedChildrenWithOldParents = []; var duplicatedChildrenWithOldParents = [];
var originalEntityToNewEntityID = []; var originalEntityToNewEntityID = [];
SelectionManager.saveProperties();
// build list of entities to duplicate by including any unselected children of selected parent entities // build list of entities to duplicate by including any unselected children of selected parent entities
Object.keys(that.savedProperties).forEach(function(originalEntityID) { Object.keys(that.savedProperties).forEach(function(originalEntityID) {
@ -216,7 +249,30 @@ SelectionManager = (function() {
properties = Entities.getEntityProperties(originalEntityID); properties = Entities.getEntityProperties(originalEntityID);
} }
if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) { if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) {
if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) {
properties.parentID = null;
properties.parentJointIndex = null;
properties.localPosition = properties.position;
properties.localRotation = properties.rotation;
}
delete properties.actionData;
var newEntityID = Entities.addEntity(properties); var newEntityID = Entities.addEntity(properties);
// Re-apply actions from the original entity
var actionIDs = Entities.getActionIDs(properties.id);
for (var i = 0; i < actionIDs.length; ++i) {
var actionID = actionIDs[i];
var actionArguments = Entities.getActionArguments(properties.id, actionID);
if (actionArguments) {
var type = actionArguments.type;
if (type == 'hold' || type == 'far-grab') {
continue;
}
delete actionArguments.ttl;
Entities.addAction(type, newEntityID, actionArguments);
}
}
duplicatedEntityIDs.push({ duplicatedEntityIDs.push({
entityID: newEntityID, entityID: newEntityID,
properties: properties properties: properties
@ -255,7 +311,8 @@ SelectionManager = (function() {
that.worldPosition = null; that.worldPosition = null;
that.worldRotation = null; that.worldRotation = null;
} else if (that.selections.length === 1) { } else if (that.selections.length === 1) {
properties = Entities.getEntityProperties(that.selections[0]); properties = Entities.getEntityProperties(that.selections[0],
['dimensions', 'position', 'rotation', 'registrationPoint', 'boundingBox', 'type']);
that.localDimensions = properties.dimensions; that.localDimensions = properties.dimensions;
that.localPosition = properties.position; that.localPosition = properties.position;
that.localRotation = properties.rotation; that.localRotation = properties.rotation;
@ -271,7 +328,7 @@ SelectionManager = (function() {
SelectionDisplay.setSpaceMode(SPACE_LOCAL); SelectionDisplay.setSpaceMode(SPACE_LOCAL);
} }
} else { } else {
properties = Entities.getEntityProperties(that.selections[0]); properties = Entities.getEntityProperties(that.selections[0], ['type', 'boundingBox']);
that.entityType = properties.type; that.entityType = properties.type;
@ -279,7 +336,7 @@ SelectionManager = (function() {
var tfl = properties.boundingBox.tfl; var tfl = properties.boundingBox.tfl;
for (var i = 1; i < that.selections.length; i++) { for (var i = 1; i < that.selections.length; i++) {
properties = Entities.getEntityProperties(that.selections[i]); properties = Entities.getEntityProperties(that.selections[i], 'boundingBox');
var bb = properties.boundingBox; var bb = properties.boundingBox;
brn.x = Math.min(bb.brn.x, brn.x); brn.x = Math.min(bb.brn.x, brn.x);
brn.y = Math.min(bb.brn.y, brn.y); brn.y = Math.min(bb.brn.y, brn.y);
@ -410,6 +467,8 @@ SelectionDisplay = (function() {
YAW: 1, YAW: 1,
ROLL: 2 ROLL: 2
}; };
var NO_TRIGGER_HAND = -1;
var spaceMode = SPACE_LOCAL; var spaceMode = SPACE_LOCAL;
var overlayNames = []; var overlayNames = [];
@ -751,20 +810,17 @@ SelectionDisplay = (function() {
// But we dont' get mousePressEvents. // But we dont' get mousePressEvents.
that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
Script.scriptEnding.connect(that.triggerMapping.disable); Script.scriptEnding.connect(that.triggerMapping.disable);
that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. that.triggeredHand = NO_TRIGGER_HAND;
that.TRIGGER_ON_VALUE = 0.4; that.triggered = function() {
that.TRIGGER_OFF_VALUE = 0.15; return that.triggeredHand !== NO_TRIGGER_HAND;
that.triggered = false; }
var activeHand = Controller.Standard.RightHand; function makeClickHandler(hand) {
function makeTriggerHandler(hand) { return function (clicked) {
return function (value) { // Don't allow both hands to trigger at the same time
if (!that.triggered && (value > that.TRIGGER_GRAB_VALUE)) { // should we smooth? if (that.triggered() && hand !== that.triggeredHand) {
that.triggered = true; return;
if (activeHand !== hand) { }
// No switching while the other is already triggered, so no need to release. if (!that.triggered() && clicked) {
activeHand = (activeHand === Controller.Standard.RightHand) ?
Controller.Standard.LeftHand : Controller.Standard.RightHand;
}
var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand &&
SelectionManager.pointingAtDesktopWindowRight) || SelectionManager.pointingAtDesktopWindowRight) ||
(hand === Controller.Standard.LeftHand && (hand === Controller.Standard.LeftHand &&
@ -774,15 +830,16 @@ SelectionDisplay = (function() {
if (pointingAtDesktopWindow || pointingAtTablet) { if (pointingAtDesktopWindow || pointingAtTablet) {
return; return;
} }
that.triggeredHand = hand;
that.mousePressEvent({}); that.mousePressEvent({});
} else if (that.triggered && (value < that.TRIGGER_OFF_VALUE)) { } else if (that.triggered() && !clicked) {
that.triggered = false; that.triggeredHand = NO_TRIGGER_HAND;
that.mouseReleaseEvent({}); that.mouseReleaseEvent({});
} }
}; };
} }
that.triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); that.triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
that.triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); that.triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
// FUNCTION DEF(s): Intersection Check Helpers // FUNCTION DEF(s): Intersection Check Helpers
function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) {
@ -833,7 +890,7 @@ SelectionDisplay = (function() {
if (wantDebug) { if (wantDebug) {
print("=============== eST::MousePressEvent BEG ======================="); print("=============== eST::MousePressEvent BEG =======================");
} }
if (!event.isLeftButton && !that.triggered) { if (!event.isLeftButton && !that.triggered()) {
// EARLY EXIT-(if another mouse button than left is pressed ignore it) // EARLY EXIT-(if another mouse button than left is pressed ignore it)
return false; return false;
} }
@ -1079,9 +1136,9 @@ SelectionDisplay = (function() {
that.checkControllerMove = function() { that.checkControllerMove = function() {
if (SelectionManager.hasSelection()) { if (SelectionManager.hasSelection()) {
var controllerPose = getControllerWorldLocation(activeHand, true); var controllerPose = getControllerWorldLocation(that.triggeredHand, true);
var hand = (activeHand === Controller.Standard.LeftHand) ? 0 : 1; var hand = (that.triggeredHand === Controller.Standard.LeftHand) ? 0 : 1;
if (controllerPose.valid && lastControllerPoses[hand].valid && that.triggered) { if (controllerPose.valid && lastControllerPoses[hand].valid && that.triggered()) {
if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) || if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) ||
!Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) { !Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) {
that.mouseMoveEvent({}); that.mouseMoveEvent({});
@ -1092,8 +1149,8 @@ SelectionDisplay = (function() {
}; };
function controllerComputePickRay() { function controllerComputePickRay() {
var controllerPose = getControllerWorldLocation(activeHand, true); var controllerPose = getControllerWorldLocation(that.triggeredHand, true);
if (controllerPose.valid && that.triggered) { if (controllerPose.valid && that.triggered()) {
var controllerPosition = controllerPose.translation; var controllerPosition = controllerPose.translation;
// This gets point direction right, but if you want general quaternion it would be more complicated: // This gets point direction right, but if you want general quaternion it would be more complicated:
var controllerDirection = Quat.getUp(controllerPose.rotation); var controllerDirection = Quat.getUp(controllerPose.rotation);
@ -1716,6 +1773,20 @@ SelectionDisplay = (function() {
Vec3.print(" pickResult.intersection", pickResult.intersection); Vec3.print(" pickResult.intersection", pickResult.intersection);
} }
// Duplicate entities if alt is pressed. This will make a
// copy of the selected entities and move the _original_ entities, not
// the new ones.
if (event.isAlt || doClone) {
duplicatedEntityIDs = SelectionManager.duplicateSelection();
var ids = [];
for (var i = 0; i < duplicatedEntityIDs.length; ++i) {
ids.push(duplicatedEntityIDs[i].entityID);
}
SelectionManager.setSelections(ids);
} else {
duplicatedEntityIDs = null;
}
SelectionManager.saveProperties(); SelectionManager.saveProperties();
that.resetPreviousHandleColor(); that.resetPreviousHandleColor();
@ -1745,15 +1816,6 @@ SelectionDisplay = (function() {
z: 0 z: 0
}); });
// Duplicate entities if alt is pressed. This will make a
// copy of the selected entities and move the _original_ entities, not
// the new ones.
if (event.isAlt || doClone) {
duplicatedEntityIDs = SelectionManager.duplicateSelection();
} else {
duplicatedEntityIDs = null;
}
isConstrained = false; isConstrained = false;
if (wantDebug) { if (wantDebug) {
print("================== TRANSLATE_XZ(End) <- ======================="); print("================== TRANSLATE_XZ(End) <- =======================");
@ -1929,6 +1991,20 @@ SelectionDisplay = (function() {
addHandleTool(overlay, { addHandleTool(overlay, {
mode: mode, mode: mode,
onBegin: function(event, pickRay, pickResult) { onBegin: function(event, pickRay, pickResult) {
// Duplicate entities if alt is pressed. This will make a
// copy of the selected entities and move the _original_ entities, not
// the new ones.
if (event.isAlt) {
duplicatedEntityIDs = SelectionManager.duplicateSelection();
var ids = [];
for (var i = 0; i < duplicatedEntityIDs.length; ++i) {
ids.push(duplicatedEntityIDs[i].entityID);
}
SelectionManager.setSelections(ids);
} else {
duplicatedEntityIDs = null;
}
var axisVector; var axisVector;
if (direction === TRANSLATE_DIRECTION.X) { if (direction === TRANSLATE_DIRECTION.X) {
axisVector = { x: 1, y: 0, z: 0 }; axisVector = { x: 1, y: 0, z: 0 };
@ -1955,15 +2031,6 @@ SelectionDisplay = (function() {
that.setHandleStretchVisible(false); that.setHandleStretchVisible(false);
that.setHandleScaleCubeVisible(false); that.setHandleScaleCubeVisible(false);
that.setHandleClonerVisible(false); that.setHandleClonerVisible(false);
// Duplicate entities if alt is pressed. This will make a
// copy of the selected entities and move the _original_ entities, not
// the new ones.
if (event.isAlt) {
duplicatedEntityIDs = SelectionManager.duplicateSelection();
} else {
duplicatedEntityIDs = null;
}
previousPickRay = pickRay; previousPickRay = pickRay;
}, },
@ -2243,11 +2310,11 @@ SelectionDisplay = (function() {
} }
// Are we using handControllers or Mouse - only relevant for 3D tools // Are we using handControllers or Mouse - only relevant for 3D tools
var controllerPose = getControllerWorldLocation(activeHand, true); var controllerPose = getControllerWorldLocation(that.triggeredHand, true);
var vector = null; var vector = null;
var newPick = null; var newPick = null;
if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() &&
controllerPose.valid && that.triggered && directionFor3DStretch) { controllerPose.valid && that.triggered() && directionFor3DStretch) {
localDeltaPivot = deltaPivot3D; localDeltaPivot = deltaPivot3D;
newPick = pickRay.origin; newPick = pickRay.origin;
vector = Vec3.subtract(newPick, lastPick3D); vector = Vec3.subtract(newPick, lastPick3D);