diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index c961d8f19c..144df2202b 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -29,6 +29,8 @@ #include // TODO: consider moving to scriptengine.h #include // TODO: consider moving to scriptengine.h +#include "avatars/ScriptableAvatar.h" + #include "Agent.h" Agent::Agent(const QByteArray& packet) : @@ -228,7 +230,7 @@ void Agent::run() { qDebug() << "Downloaded script:" << scriptContents; // setup an Avatar for the script to use - AvatarData scriptedAvatar; + ScriptableAvatar scriptedAvatar(&_scriptEngine); scriptedAvatar.setForceFaceshiftConnected(true); // call model URL setters with empty URLs so our avatar, if user, will have the default models diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp new file mode 100644 index 0000000000..150e364ed7 --- /dev/null +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -0,0 +1,88 @@ +// +// ScriptableAvatar.cpp +// +// +// Created by Clement on 7/22/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "ScriptableAvatar.h" + +ScriptableAvatar::ScriptableAvatar(ScriptEngine* scriptEngine) : _scriptEngine(scriptEngine), _animation(NULL) { + connect(_scriptEngine, SIGNAL(update(float)), this, SLOT(update(float))); +} + +// hold and priority unused but kept so that client side JS can run. +void ScriptableAvatar::startAnimation(const QString& url, float fps, float priority, + bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), + Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(float, firstFrame), + Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints)); + return; + } + _animation = _scriptEngine->getAnimationCache()->getAnimation(url); + _animationDetails = AnimationDetails("", QUrl(url), fps, 0, loop, hold, false, firstFrame, lastFrame, true, firstFrame); + _maskedJoints = maskedJoints; +} + +void ScriptableAvatar::stopAnimation() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stopAnimation"); + return; + } + _animation.clear(); +} + +AnimationDetails ScriptableAvatar::getAnimationDetails() { + if (QThread::currentThread() != thread()) { + AnimationDetails result; + QMetaObject::invokeMethod(this, "getAnimationDetails", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AnimationDetails, result)); + return result; + } + return _animationDetails; +} + +void ScriptableAvatar::update(float deltatime) { + // Run animation + if (_animation != NULL && _animation->isValid() && _animation->getFrames().size() > 0) { + QStringList modelJoints = getJointNames(); + QStringList animationJoints = _animation->getJointNames(); + + if (_jointData.size() != modelJoints.size()) { + _jointData.resize(modelJoints.size()); + } + + float frameIndex = _animationDetails.frameIndex + deltatime * _animationDetails.fps; + if (_animationDetails.loop || frameIndex < _animationDetails.lastFrame) { + while (frameIndex >= _animationDetails.lastFrame) { + frameIndex -= (_animationDetails.lastFrame - _animationDetails.firstFrame); + } + _animationDetails.frameIndex = frameIndex; + + const int frameCount = _animation->getFrames().size(); + const FBXAnimationFrame& floorFrame = _animation->getFrames().at((int)glm::floor(frameIndex) % frameCount); + const FBXAnimationFrame& ceilFrame = _animation->getFrames().at((int)glm::ceil(frameIndex) % frameCount); + const float frameFraction = glm::fract(frameIndex); + + for (int i = 0; i < modelJoints.size(); i++) { + int mapping = animationJoints.indexOf(modelJoints[i]); + if (mapping != -1 && !_maskedJoints.contains(modelJoints[i])) { + JointData& data = _jointData[i]; + data.valid = true; + data.rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + } else { + _jointData[i].valid = false; + } + } + } else { + _animation.clear(); + } + } +} diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h new file mode 100644 index 0000000000..5a99c8c8da --- /dev/null +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -0,0 +1,40 @@ +// +// ScriptableAvatar.h +// +// +// Created by Clement on 7/22/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ScriptableAvatar_h +#define hifi_ScriptableAvatar_h + +#include +#include +#include + +class ScriptableAvatar : public AvatarData { + Q_OBJECT +public: + ScriptableAvatar(ScriptEngine* scriptEngine); + + /// Allows scripts to run animations. + Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false, + bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); + Q_INVOKABLE void stopAnimation(); + Q_INVOKABLE AnimationDetails getAnimationDetails(); + +private slots: + void update(float deltatime); + +private: + ScriptEngine* _scriptEngine; + AnimationPointer _animation; + AnimationDetails _animationDetails; + QStringList _maskedJoints; +}; + +#endif // hifi_ScriptableAvatar_h \ No newline at end of file diff --git a/examples/editModels.js b/examples/editModels.js index abb0c4c56e..d430800748 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -748,18 +748,19 @@ function Tooltip() { this.updateText = function(properties) { var angles = Quat.safeEulerAngles(properties.modelRotation); var text = "Model Properties:\n" - text += "x: " + properties.position.x.toFixed(this.decimals) + "\n" - text += "y: " + properties.position.y.toFixed(this.decimals) + "\n" - text += "z: " + properties.position.z.toFixed(this.decimals) + "\n" - text += "pitch: " + angles.x.toFixed(this.decimals) + "\n" - text += "yaw: " + angles.y.toFixed(this.decimals) + "\n" - text += "roll: " + angles.z.toFixed(this.decimals) + "\n" + text += "X: " + properties.position.x.toFixed(this.decimals) + "\n" + text += "Y: " + properties.position.y.toFixed(this.decimals) + "\n" + text += "Z: " + properties.position.z.toFixed(this.decimals) + "\n" + text += "Pitch: " + angles.x.toFixed(this.decimals) + "\n" + text += "Yaw: " + angles.y.toFixed(this.decimals) + "\n" + text += "Roll: " + angles.z.toFixed(this.decimals) + "\n" text += "Scale: " + 2 * properties.radius.toFixed(this.decimals) + "\n" text += "ID: " + properties.id + "\n" - text += "model url: " + properties.modelURL + "\n" - text += "animation url: " + properties.animationURL + "\n" + text += "Model URL: " + properties.modelURL + "\n" + text += "Animation URL: " + properties.animationURL + "\n" + text += "Animation is playing: " + properties.animationIsPlaying + "\n" if (properties.sittingPoints.length > 0) { - text += properties.sittingPoints.length + " sitting points: " + text += properties.sittingPoints.length + " Sitting points: " for (var i = 0; i < properties.sittingPoints.length; ++i) { text += properties.sittingPoints[i].name + " " } @@ -1146,6 +1147,7 @@ function handeMenuEvent(menuItem){ var decimals = 3; array.push({ label: "Model URL:", value: selectedModelProperties.modelURL }); array.push({ label: "Animation URL:", value: selectedModelProperties.animationURL }); + array.push({ label: "Animation is playing:", value: selectedModelProperties.animationIsPlaying }); array.push({ label: "X:", value: selectedModelProperties.position.x.toFixed(decimals) }); array.push({ label: "Y:", value: selectedModelProperties.position.y.toFixed(decimals) }); array.push({ label: "Z:", value: selectedModelProperties.position.z.toFixed(decimals) }); @@ -1158,16 +1160,18 @@ function handeMenuEvent(menuItem){ var propertyName = Window.form("Edit Properties", array); modelSelected = false; - selectedModelProperties.modelURL = array[0].value; - selectedModelProperties.animationURL = array[1].value; - selectedModelProperties.position.x = array[2].value; - selectedModelProperties.position.y = array[3].value; - selectedModelProperties.position.z = array[4].value; - angles.x = array[5].value; - angles.y = array[6].value; - angles.z = array[7].value; + var index = 0; + selectedModelProperties.modelURL = array[index++].value; + selectedModelProperties.animationURL = array[index++].value; + selectedModelProperties.animationIsPlaying = array[index++].value; + selectedModelProperties.position.x = array[index++].value; + selectedModelProperties.position.y = array[index++].value; + selectedModelProperties.position.z = array[index++].value; + angles.x = array[index++].value; + angles.y = array[index++].value; + angles.z = array[index++].value; selectedModelProperties.modelRotation = Quat.fromVec3Degrees(angles); - selectedModelProperties.radius = array[8].value / 2; + selectedModelProperties.radius = array[index++].value / 2; Models.editModel(selectedModelID, selectedModelProperties); } @@ -1200,6 +1204,7 @@ Controller.keyPressEvent.connect(function(event) { somethingChanged = true; } }); + Controller.keyReleaseEvent.connect(function(event) { if (event.text == "z" || event.text == "Z") { zIsPressed = false; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index aaeb99388a..4d2d679956 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1878,8 +1878,6 @@ void MyAvatar::renderLaserPointers() { //Gets the tip position for the laser pointer glm::vec3 MyAvatar::getLaserPointerTipPosition(const PalmData* palm) { const ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay(); - const float PALM_TIP_ROD_LENGTH_MULT = 40.0f; - glm::vec3 direction = glm::normalize(palm->getTipPosition() - palm->getPosition()); glm::vec3 position = palm->getPosition(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 53c2a72b00..fe39f286be 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -58,6 +58,7 @@ public: static ModelsScriptingInterface* getModelsScriptingInterface() { return &_modelsScriptingInterface; } ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } + AnimationCache* getAnimationCache() { return &_animationCache; } /// sets the script contents, will return false if failed, will fail if script is already running bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString(""));