This commit is contained in:
Philip Rosedale 2014-07-02 15:26:19 -07:00
commit 1a3becc123
30 changed files with 934 additions and 105 deletions

View file

@ -60,6 +60,7 @@
#include <ParticlesScriptingInterface.h>
#include <PerfStat.h>
#include <ResourceCache.h>
#include <UserActivityLogger.h>
#include <UUID.h>
#include <OctreeSceneStats.h>
#include <LocalVoxelsList.h>
@ -268,6 +269,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// set the account manager's root URL and trigger a login request if we don't have the access token
accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);
UserActivityLogger::getInstance().launch(applicationVersion());
// once the event loop has started, check and signal for an access token
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
@ -396,7 +398,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
}
Application::~Application() {
int DELAY_TIME = 1000;
UserActivityLogger::getInstance().close(DELAY_TIME);
qInstallMessageHandler(NULL);
// make sure we don't call the idle timer any more
@ -3577,6 +3581,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
UserActivityLogger::getInstance().loadedScript(scriptURLString);
}
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so

View file

@ -432,7 +432,7 @@ void Audio::handleAudioInput() {
float inputToNetworkInputRatio = calculateDeviceToNetworkInputRatio(_numInputCallbackBytes);
unsigned int inputSamplesRequired = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio;
int inputSamplesRequired = (int)((float)NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio);
QByteArray inputByteArray = _inputDevice->readAll();

View file

@ -51,10 +51,10 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX
glm::mat3 axes = glm::mat3_cast(glm::quat());
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInParentFrame()) *
joint.preTransform * glm::mat4_cast(joint.preRotation)));
state._rotationInParentFrame = glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2]))
state.setRotationInParentFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2]))
* glm::angleAxis(RADIANS_PER_DEGREE * _owningHead->getFinalYaw(), glm::normalize(inverse * axes[1]))
* glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalPitch(), glm::normalize(inverse * axes[0]))
* joint.rotation;
* joint.rotation);
}
void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
@ -68,8 +68,8 @@ void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJ
_owningHead->getSaccade() - _translation, 1.0f));
glm::quat between = rotationBetween(front, lookAt);
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
state._rotationInParentFrame = glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
joint.rotation;
state.setRotationInParentFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
joint.rotation);
}
void FaceModel::updateJointState(int index) {

View file

@ -219,10 +219,9 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
JointState& parentState = _jointStates[parentJointIndex];
parentState.setRotationFromBindFrame(palmRotation, PALM_PRIORITY);
// lock hand to forearm by slamming its rotation (in parent-frame) to identity
_jointStates[jointIndex]._rotationInParentFrame = glm::quat();
_jointStates[jointIndex].setRotationInParentFrame(glm::quat());
} else {
setJointPosition(jointIndex, palmPosition, palmRotation,
true, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY);
inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY);
}
}
@ -258,9 +257,9 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const
glm::mat3 axes = glm::mat3_cast(glm::quat());
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInParentFrame()) *
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)));
state._rotationInParentFrame = glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(),
state.setRotationInParentFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(),
glm::normalize(inverse * axes[2])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(),
glm::normalize(inverse * axes[0])) * joint.rotation;
glm::normalize(inverse * axes[0])) * joint.rotation);
}
void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
@ -537,6 +536,11 @@ void SkeletonModel::buildRagdollConstraints() {
}
}
void SkeletonModel::updateVisibleJointStates() {
Model::updateVisibleJointStates();
// TODO: implement this to move visible joints to agree with joint shape positions
}
// virtual
void SkeletonModel::stepRagdollForward(float deltaTime) {
const float RAGDOLL_FOLLOWS_JOINTS_TIMESCALE = 0.03f;
@ -644,7 +648,6 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
_ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position;
continue;
}
assert(parentIndex != -1);
glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation;
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
@ -700,7 +703,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
_boundingRadius = 0.5f * glm::length(diagonal);
}
void SkeletonModel::resetShapePositions() {
void SkeletonModel::resetShapePositionsToDefaultPose() {
// DEBUG method.
// Moves shapes to the joint default locations for debug visibility into
// how the bounding shape is computed.

View file

@ -93,6 +93,8 @@ public:
/// Retrieve the positions of up to two eye meshes.
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
virtual void updateVisibleJointStates();
// virtual overrride from Ragdoll
virtual void stepRagdollForward(float deltaTime);
@ -104,7 +106,7 @@ public:
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
void resetShapePositions(); // DEBUG method
void resetShapePositionsToDefaultPose(); // DEBUG method
void renderRagdoll();
protected:

View file

@ -17,6 +17,7 @@
#include <QOpenGLFramebufferObject>
#include <glm/glm.hpp>
#include <UserActivityLogger.h>
#include "Application.h"
@ -61,6 +62,9 @@ void OculusManager::connect() {
_ovrHmd = ovrHmd_Create(0);
if (_ovrHmd) {
if (!_isConnected) {
UserActivityLogger::getInstance().connectedDevice("hmd", "oculus");
}
_isConnected = true;
ovrHmd_GetDesc(_ovrHmd, &_ovrHmdDesc);

View file

@ -13,6 +13,7 @@
#include "Application.h"
#include "SixenseManager.h"
#include "UserActivityLogger.h"
#ifdef HAVE_SIXENSE
const int CALIBRATION_STATE_IDLE = 0;
@ -39,6 +40,7 @@ SixenseManager::SixenseManager() {
sixenseInit();
#endif
_hydrasConnected = false;
_triggerPressed[0] = false;
_bumperPressed[0] = false;
_oldX[0] = -1;
@ -70,7 +72,11 @@ void SixenseManager::setFilter(bool filter) {
void SixenseManager::update(float deltaTime) {
#ifdef HAVE_SIXENSE
if (sixenseGetNumActiveControllers() == 0) {
_hydrasConnected = false;
return;
} else if (!_hydrasConnected) {
_hydrasConnected = true;
UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra");
}
MyAvatar* avatar = Application::getInstance()->getAvatar();
Hand* hand = avatar->getHand();

View file

@ -71,6 +71,7 @@ private:
float _lastDistance;
#endif
bool _hydrasConnected;
quint64 _lastMovement;
glm::vec3 _amountMoved;

View file

@ -135,7 +135,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
args->_elementsTouched++;
// actually render it here...
// we need to iterate the actual modelItems of the element
ModelTreeElement* modelTreeElement = (ModelTreeElement*)element;
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
QList<ModelItem>& modelItems = modelTreeElement->getModels();

View file

@ -11,14 +11,26 @@
#include <glm/gtx/norm.hpp>
//#include <GeometryUtil.h>
#include <SharedUtil.h>
#include "JointState.h"
JointState::JointState() :
_animationPriority(0.0f),
_fbxJoint(NULL) {
_fbxJoint(NULL),
_isConstrained(false) {
}
void JointState::copyState(const JointState& state) {
_animationPriority = state._animationPriority;
_transform = state._transform;
_rotation = extractRotation(_transform);
_rotationInParentFrame = state._rotationInParentFrame;
_visibleTransform = state._visibleTransform;
_visibleRotation = extractRotation(_visibleTransform);
_visibleRotationInParentFrame = state._visibleRotationInParentFrame;
// DO NOT copy _fbxJoint
}
void JointState::setFBXJoint(const FBXJoint* joint) {
@ -26,14 +38,10 @@ void JointState::setFBXJoint(const FBXJoint* joint) {
_rotationInParentFrame = joint->rotation;
// NOTE: JointState does not own the FBXJoint to which it points.
_fbxJoint = joint;
}
void JointState::copyState(const JointState& state) {
_rotationInParentFrame = state._rotationInParentFrame;
_transform = state._transform;
_rotation = extractRotation(_transform);
_animationPriority = state._animationPriority;
// DO NOT copy _fbxJoint
// precompute whether there are any constraints or not
float distanceMin = glm::distance(_fbxJoint->rotationMin, glm::vec3(-PI));
float distanceMax = glm::distance(_fbxJoint->rotationMax, glm::vec3(PI));
_isConstrained = distanceMin > EPSILON || distanceMax > EPSILON;
}
void JointState::computeTransform(const glm::mat4& parentTransform) {
@ -43,6 +51,13 @@ void JointState::computeTransform(const glm::mat4& parentTransform) {
_rotation = extractRotation(_transform);
}
void JointState::computeVisibleTransform(const glm::mat4& parentTransform) {
glm::quat modifiedRotation = _fbxJoint->preRotation * _visibleRotationInParentFrame * _fbxJoint->postRotation;
glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(modifiedRotation) * _fbxJoint->postTransform;
_visibleTransform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform;
_visibleRotation = extractRotation(_visibleTransform);
}
glm::quat JointState::getRotationFromBindToModelFrame() const {
return _rotation * _fbxJoint->inverseBindRotation;
}
@ -50,16 +65,16 @@ glm::quat JointState::getRotationFromBindToModelFrame() const {
void JointState::restoreRotation(float fraction, float priority) {
assert(_fbxJoint != NULL);
if (priority == _animationPriority || _animationPriority == 0.0f) {
_rotationInParentFrame = safeMix(_rotationInParentFrame, _fbxJoint->rotation, fraction);
setRotationInParentFrame(safeMix(_rotationInParentFrame, _fbxJoint->rotation, fraction));
_animationPriority = 0.0f;
}
}
void JointState::setRotationFromBindFrame(const glm::quat& rotation, float priority) {
// rotation is from bind- to model-frame
assert(_fbxJoint != NULL);
if (priority >= _animationPriority) {
// rotation is from bind- to model-frame
_rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation);
setRotationInParentFrame(_rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation));
_animationPriority = priority;
}
}
@ -68,6 +83,9 @@ void JointState::clearTransformTranslation() {
_transform[3][0] = 0.0f;
_transform[3][1] = 0.0f;
_transform[3][2] = 0.0f;
_visibleTransform[3][0] = 0.0f;
_visibleTransform[3][1] = 0.0f;
_visibleTransform[3][2] = 0.0f;
}
void JointState::setRotation(const glm::quat& rotation, bool constrain, float priority) {
@ -75,27 +93,59 @@ void JointState::setRotation(const glm::quat& rotation, bool constrain, float pr
}
void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, float priority) {
// NOTE: delta is in jointParent-frame
// NOTE: delta is in model-frame
assert(_fbxJoint != NULL);
if (priority < _animationPriority) {
return;
}
_animationPriority = priority;
if (!constrain || (_fbxJoint->rotationMin == glm::vec3(-PI, -PI, -PI) &&
_fbxJoint->rotationMax == glm::vec3(PI, PI, PI))) {
if (!constrain || !_isConstrained) {
// no constraints
_rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation;
_rotation = delta * _rotation;
return;
}
glm::quat targetRotation = delta * _rotation;
glm::vec3 eulers = safeEulerAngles(_rotationInParentFrame * glm::inverse(_rotation) * targetRotation);
glm::quat newRotation = glm::quat(glm::clamp(eulers, _fbxJoint->rotationMin, _fbxJoint->rotationMax));
_rotation = _rotation * glm::inverse(_rotationInParentFrame) * newRotation;
_rotationInParentFrame = newRotation;
glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation;
setRotationInParentFrame(targetRotation);
}
/// Applies delta rotation to joint but mixes a little bit of the default pose as well.
/// This helps keep an IK solution stable.
void JointState::mixRotationDelta(const glm::quat& delta, float mixFactor, float priority) {
// NOTE: delta is in model-frame
assert(_fbxJoint != NULL);
if (priority < _animationPriority) {
return;
}
_animationPriority = priority;
glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation;
if (mixFactor > 0.0f && mixFactor <= 1.0f) {
targetRotation = safeMix(targetRotation, _fbxJoint->rotation, mixFactor);
}
setRotationInParentFrame(targetRotation);
}
glm::quat JointState::computeParentRotation() const {
// R = Rp * Rpre * r * Rpost
// Rp = R * (Rpre * r * Rpost)^
return _rotation * glm::inverse(_fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation);
}
void JointState::setRotationInParentFrame(const glm::quat& targetRotation) {
glm::quat parentRotation = computeParentRotation();
_rotationInParentFrame = targetRotation;
// R' = Rp * Rpre * r' * Rpost
_rotation = parentRotation * _fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation;
}
const glm::vec3& JointState::getDefaultTranslationInParentFrame() const {
assert(_fbxJoint != NULL);
return _fbxJoint->translation;
}
void JointState::slaveVisibleTransform() {
_visibleTransform = _transform;
_visibleRotation = _rotation;
_visibleRotationInParentFrame = _rotationInParentFrame;
}

View file

@ -22,12 +22,19 @@ class JointState {
public:
JointState();
void copyState(const JointState& state);
void setFBXJoint(const FBXJoint* joint);
const FBXJoint& getFBXJoint() const { return *_fbxJoint; }
void copyState(const JointState& state);
void computeTransform(const glm::mat4& parentTransform);
void computeVisibleTransform(const glm::mat4& parentTransform);
const glm::mat4& getVisibleTransform() const { return _visibleTransform; }
glm::quat getVisibleRotation() const { return _visibleRotation; }
glm::vec3 getVisiblePosition() const { return extractTranslation(_visibleTransform); }
const glm::mat4& getTransform() const { return _transform; }
glm::quat getRotation() const { return _rotation; }
@ -39,11 +46,19 @@ public:
/// \param rotation rotation of joint in model-frame
void setRotation(const glm::quat& rotation, bool constrain, float priority);
/// \param delta is in the jointParent-frame
/// \param delta is in the model-frame
void applyRotationDelta(const glm::quat& delta, bool constrain = true, float priority = 1.0f);
const glm::vec3& getDefaultTranslationInParentFrame() const;
/// Applies delta rotation to joint but mixes a little bit of the default pose as well.
/// This helps keep an IK solution stable.
/// \param delta rotation change in model-frame
/// \param mixFactor fraction in range [0,1] of how much default pose to blend in (0 is none, 1 is all)
/// \param priority priority level of this animation blend
void mixRotationDelta(const glm::quat& delta, float mixFactor, float priority = 1.0f);
/// Blends a fraciton of default pose into joint rotation.
/// \param fraction fraction in range [0,1] of how much default pose to blend in (0 is none, 1 is all)
/// \param priority priority level of this animation blend
void restoreRotation(float fraction, float priority);
/// \param rotation is from bind- to model-frame
@ -51,16 +66,36 @@ public:
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
void setRotationFromBindFrame(const glm::quat& rotation, float priority);
void setRotationInParentFrame(const glm::quat& targetRotation);
const glm::quat& getRotationInParentFrame() const { return _rotationInParentFrame; }
const glm::vec3& getDefaultTranslationInParentFrame() const;
void clearTransformTranslation();
glm::quat _rotationInParentFrame; // joint- to parentJoint-frame
void slaveVisibleTransform();
float _animationPriority; // the priority of the animation affecting this joint
private:
/// \return parent model-frame rotation
// (used to keep _rotation consistent when modifying _rotationInWorldFrame directly)
glm::quat computeParentRotation() const;
/// debug helper function
void loadBindRotation();
glm::mat4 _transform; // joint- to model-frame
glm::quat _rotation; // joint- to model-frame
glm::quat _rotationInParentFrame; // joint- to parentJoint-frame
glm::mat4 _visibleTransform;
glm::quat _visibleRotation;
glm::quat _visibleRotationInParentFrame;
const FBXJoint* _fbxJoint; // JointState does NOT own its FBXJoint
bool _isConstrained;
};
#endif // hifi_JointState_h

View file

@ -39,8 +39,8 @@ Model::Model(QObject* parent) :
_scaledToFit(false),
_snapModelToCenter(false),
_snappedToCenter(false),
_showTrueJointTransforms(false),
_rootIndex(-1),
//_enableCollisionShapes(false),
_lodDistance(0.0f),
_pupilDilation(0.0f),
_url("http://invalid.com") {
@ -460,7 +460,7 @@ void Model::reset() {
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i]._rotationInParentFrame = geometry.joints.at(i).rotation;
_jointStates[i].setRotationInParentFrame(geometry.joints.at(i).rotation);
}
}
@ -571,6 +571,9 @@ void Model::setJointStates(QVector<JointState> states) {
radius = distance;
}
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].slaveVisibleTransform();
}
_boundingRadius = radius;
}
@ -686,7 +689,7 @@ bool Model::getJointState(int index, glm::quat& rotation) const {
if (index == -1 || index >= _jointStates.size()) {
return false;
}
rotation = _jointStates.at(index)._rotationInParentFrame;
rotation = _jointStates.at(index).getRotationInParentFrame();
const glm::quat& defaultRotation = _geometry->getFBXGeometry().joints.at(index).rotation;
return glm::abs(rotation.x - defaultRotation.x) >= EPSILON ||
glm::abs(rotation.y - defaultRotation.y) >= EPSILON ||
@ -699,7 +702,7 @@ void Model::setJointState(int index, bool valid, const glm::quat& rotation, floa
JointState& state = _jointStates[index];
if (priority >= state._animationPriority) {
if (valid) {
state._rotationInParentFrame = rotation;
state.setRotationInParentFrame(rotation);
state._animationPriority = priority;
} else {
state.restoreRotation(1.0f, priority);
@ -765,6 +768,23 @@ bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const
return true;
}
bool Model::getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
// position is in world-frame
position = _translation + _rotation * _jointStates[jointIndex].getVisiblePosition();
return true;
}
bool Model::getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
rotation = _rotation * _jointStates[jointIndex].getVisibleRotation();
return true;
}
QStringList Model::getJointNames() const {
if (QThread::currentThread() != thread()) {
QStringList result;
@ -918,6 +938,8 @@ void Model::simulateInternal(float deltaTime) {
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i);
}
updateVisibleJointStates();
_shapesAreDirty = ! _shapes.isEmpty();
// update the attachment transforms and simulate them
@ -928,8 +950,13 @@ void Model::simulateInternal(float deltaTime) {
glm::vec3 jointTranslation = _translation;
glm::quat jointRotation = _rotation;
getJointPositionInWorldFrame(attachment.jointIndex, jointTranslation);
getJointRotationInWorldFrame(attachment.jointIndex, jointRotation);
if (_showTrueJointTransforms) {
getJointPositionInWorldFrame(attachment.jointIndex, jointTranslation);
getJointRotationInWorldFrame(attachment.jointIndex, jointRotation);
} else {
getVisibleJointPositionInWorldFrame(attachment.jointIndex, jointTranslation);
getVisibleJointRotationInWorldFrame(attachment.jointIndex, jointRotation);
}
model->setTranslation(jointTranslation + jointRotation * attachment.translation * _scale);
model->setRotation(jointRotation * attachment.rotation);
@ -944,9 +971,16 @@ void Model::simulateInternal(float deltaTime) {
for (int i = 0; i < _meshStates.size(); i++) {
MeshState& state = _meshStates[i];
const FBXMesh& mesh = geometry.meshes.at(i);
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getTransform() * cluster.inverseBindMatrix;
if (_showTrueJointTransforms) {
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getTransform() * cluster.inverseBindMatrix;
}
} else {
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getVisibleTransform() * cluster.inverseBindMatrix;
}
}
}
@ -972,6 +1006,14 @@ void Model::updateJointState(int index) {
}
}
void Model::updateVisibleJointStates() {
if (!_showTrueJointTransforms) {
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].slaveVisibleTransform();
}
}
}
bool Model::setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority) {
if (jointIndex == -1 || _jointStates.isEmpty()) {
@ -1058,6 +1100,128 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, const gl
return true;
}
void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority) {
// NOTE: targetRotation is from bind- to model-frame
if (endIndex == -1 || _jointStates.isEmpty()) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(endIndex).freeLineage;
if (freeLineage.isEmpty()) {
return;
}
int numFree = freeLineage.size();
// store and remember topmost parent transform
glm::mat4 topParentTransform;
{
int index = freeLineage.last();
const JointState& state = _jointStates.at(index);
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
topParentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
} else {
topParentTransform = _jointStates[parentIndex].getTransform();
}
}
// this is a cyclic coordinate descent algorithm: see
// http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d
// keep track of the position of the end-effector
JointState& endState = _jointStates[endIndex];
glm::vec3 endPosition = endState.getPosition();
float distanceToGo = glm::distance(targetPosition, endPosition);
const int MAX_ITERATION_COUNT = 2;
const float ACCEPTABLE_IK_ERROR = 0.005f; // 5mm
int numIterations = 0;
do {
++numIterations;
// moving up, rotate each free joint to get endPosition closer to target
for (int j = 1; j < numFree; j++) {
int nextIndex = freeLineage.at(j);
JointState& nextState = _jointStates[nextIndex];
FBXJoint nextJoint = nextState.getFBXJoint();
if (! nextJoint.isFree) {
continue;
}
glm::vec3 pivot = nextState.getPosition();
glm::vec3 leverArm = endPosition - pivot;
float leverLength = glm::length(leverArm);
if (leverLength < EPSILON) {
continue;
}
glm::quat deltaRotation = rotationBetween(leverArm, targetPosition - pivot);
/* DON'T REMOVE! This code provides the gravitational effect on the IK solution.
* It is commented out for the moment because we're blending the IK solution with
* the default pose which provides similar stability, but we might want to use
* gravity again later.
// We want to mix the shortest rotation with one that will pull the system down with gravity.
// So we compute a simplified center of mass, where each joint has a mass of 1.0 and we don't
// bother averaging it because we only need direction.
if (j > 1) {
glm::vec3 centerOfMass(0.0f);
for (int k = 0; k < j; ++k) {
int massIndex = freeLineage.at(k);
centerOfMass += _jointStates[massIndex].getPosition() - pivot;
}
// the gravitational effect is a rotation that tends to align the two cross products
const glm::vec3 worldAlignment = glm::vec3(0.0f, -1.f, 0.0f);
glm::quat gravityDelta = rotationBetween(glm::cross(centerOfMass, leverArm),
glm::cross(worldAlignment, leverArm));
float gravityAngle = glm::angle(gravityDelta);
const float MIN_GRAVITY_ANGLE = 0.1f;
float mixFactor = 0.5f;
if (gravityAngle < MIN_GRAVITY_ANGLE) {
// the final rotation is a mix of the two
mixFactor = 0.5f * gravityAngle / MIN_GRAVITY_ANGLE;
}
deltaRotation = safeMix(deltaRotation, gravityDelta, mixFactor);
}
*/
// Apply the rotation, but use mixRotationDelta() which blends a bit of the default pose
// at in the process. This provides stability to the IK solution and removes the necessity
// for the gravity effect.
glm::quat oldNextRotation = nextState.getRotation();
float mixFactor = 0.03f;
nextState.mixRotationDelta(deltaRotation, mixFactor, priority);
// measure the result of the rotation which may have been modified by
// blending and constraints
glm::quat actualDelta = nextState.getRotation() * glm::inverse(oldNextRotation);
endPosition = pivot + actualDelta * leverArm;
}
// recompute transforms from the top down
glm::mat4 parentTransform = topParentTransform;
for (int j = numFree - 1; j >= 0; --j) {
JointState& freeState = _jointStates[freeLineage.at(j)];
freeState.computeTransform(parentTransform);
parentTransform = freeState.getTransform();
}
// measure our success
endPosition = endState.getPosition();
distanceToGo = glm::distance(targetPosition, endPosition);
} while (numIterations < MAX_ITERATION_COUNT && distanceToGo < ACCEPTABLE_IK_ERROR);
// set final rotation of the end joint
endState.setRotationFromBindFrame(targetRotation, priority);
_shapesAreDirty = !_shapes.isEmpty();
}
bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) {
if (jointIndex == -1 || _jointStates.isEmpty()) {
return false;
@ -1605,7 +1769,7 @@ void AnimationHandle::applyFrame(float frameIndex) {
if (mapping != -1) {
JointState& state = _model->_jointStates[mapping];
if (_priority >= state._animationPriority) {
state._rotationInParentFrame = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction);
state.setRotationInParentFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction));
state._animationPriority = _priority;
}
}

View file

@ -120,6 +120,9 @@ public:
bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const;
bool getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const;
bool getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
/// \param jointIndex index of joint in model structure
/// \param position[out] position of joint in model-frame
/// \return true if joint exists
@ -152,6 +155,7 @@ protected:
bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space
bool _snappedToCenter; /// are we currently snapped to center
bool _showTrueJointTransforms;
int _rootIndex;
QVector<JointState> _jointStates;
@ -176,6 +180,8 @@ protected:
/// Updates the state of the joint at the specified index.
virtual void updateJointState(int index);
virtual void updateVisibleJointStates();
/// \param jointIndex index of joint in model structure
/// \param position position of joint in model-frame
@ -188,6 +194,8 @@ protected:
bool setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation = glm::quat(),
bool useRotation = false, int lastFreeIndex = -1, bool allIntermediatesFree = false,
const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f), float priority = 1.0f);
void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority);
/// Restores the indexed joint to its default position.
/// \param fraction the fraction of the default position to apply (i.e., 0.25f to slerp one fourth of the way to

View file

@ -41,8 +41,7 @@ ApplicationOverlay::ApplicationOverlay() :
_textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE),
_alpha(1.0f),
_active(true),
_crosshairTexture(0)
{
_crosshairTexture(0) {
memset(_reticleActive, 0, sizeof(_reticleActive));
memset(_magActive, 0, sizeof(_reticleActive));

View file

@ -12,8 +12,9 @@
#include "Application.h"
#include "Menu.h"
#include "PreferencesDialog.h"
#include "ModelsBrowser.h"
#include "PreferencesDialog.h"
#include "UserActivityLogger.h"
const int SCROLL_PANEL_BOTTOM_MARGIN = 30;
const int OK_BUTTON_RIGHT_MARGIN = 30;
@ -176,6 +177,7 @@ void PreferencesDialog::savePreferences() {
QString displayNameStr(ui.displayNameEdit->text());
if (displayNameStr != _displayNameString) {
myAvatar->setDisplayName(displayNameStr);
UserActivityLogger::getInstance().changedDisplayName(displayNameStr);
shouldDispatchIdentityPacket = true;
}
@ -183,6 +185,7 @@ void PreferencesDialog::savePreferences() {
if (faceModelURL.toString() != _faceURLString) {
// change the faceModelURL in the profile, it will also update this user's BlendFace
myAvatar->setFaceModelURL(faceModelURL);
UserActivityLogger::getInstance().changedModel("head", faceModelURL.toString());
shouldDispatchIdentityPacket = true;
}
@ -190,6 +193,7 @@ void PreferencesDialog::savePreferences() {
if (skeletonModelURL.toString() != _skeletonURLString) {
// change the skeletonModelURL in the profile, it will also update this user's Body
myAvatar->setSkeletonModelURL(skeletonModelURL);
UserActivityLogger::getInstance().changedModel("skeleton", skeletonModelURL.toString());
shouldDispatchIdentityPacket = true;
}

View file

@ -0,0 +1,136 @@
//
// BillboardOverlay.cpp
//
//
// Created by Clement on 7/1/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 "../../Application.h"
#include "BillboardOverlay.h"
BillboardOverlay::BillboardOverlay()
: _manager(NULL),
_scale(1.0f),
_isFacingAvatar(true) {
}
void BillboardOverlay::render() {
if (_billboard.isEmpty()) {
return;
}
if (!_billboardTexture) {
QImage image = QImage::fromData(_billboard);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
_size = image.size();
_billboardTexture.reset(new Texture());
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0,
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
} else {
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
}
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
glEnable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
glPushMatrix(); {
glTranslatef(_position.x, _position.y, _position.z);
if (_isFacingAvatar) {
// rotate about vertical to face the camera
glm::quat rotation = Application::getInstance()->getCamera()->getRotation();
rotation *= glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
} else {
glm::vec3 axis = glm::axis(_rotation);
glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z);
}
glScalef(_scale, _scale, _scale);
float maxSize = glm::max(_size.width(), _size.height());
float x = _size.width() / (2.0f * maxSize);
float y = -_size.height() / (2.0f * maxSize);
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS); {
glTexCoord2f(0.0f, 0.0f);
glVertex2f(-x, -y);
glTexCoord2f(1.0f, 0.0f);
glVertex2f(x, -y);
glTexCoord2f(1.0f, 1.0f);
glVertex2f(x, y);
glTexCoord2f(0.0f, 1.0f);
glVertex2f(-x, y);
} glEnd();
} glPopMatrix();
glDisable(GL_TEXTURE_2D);
glEnable(GL_LIGHTING);
glDisable(GL_ALPHA_TEST);
glBindTexture(GL_TEXTURE_2D, 0);
}
void BillboardOverlay::setProperties(const QScriptValue &properties) {
Base3DOverlay::setProperties(properties);
QScriptValue urlValue = properties.property("url");
if (urlValue.isValid()) {
_url = urlValue.toVariant().toString();
setBillboardURL(_url);
}
QScriptValue scaleValue = properties.property("scale");
if (scaleValue.isValid()) {
_scale = scaleValue.toVariant().toFloat();
}
QScriptValue rotationValue = properties.property("rotation");
if (rotationValue.isValid()) {
QScriptValue x = rotationValue.property("x");
QScriptValue y = rotationValue.property("y");
QScriptValue z = rotationValue.property("z");
QScriptValue w = rotationValue.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
_rotation.x = x.toVariant().toFloat();
_rotation.y = y.toVariant().toFloat();
_rotation.z = z.toVariant().toFloat();
_rotation.w = w.toVariant().toFloat();
}
}
QScriptValue isFacingAvatarValue = properties.property("isFacingAvatar");
if (isFacingAvatarValue.isValid()) {
_isFacingAvatar = isFacingAvatarValue.toVariant().toBool();
}
}
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
void BillboardOverlay::setBillboardURL(const QUrl url) {
// TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
_manager->deleteLater();
_manager = new QNetworkAccessManager();
connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
_manager->get(QNetworkRequest(url));
}
void BillboardOverlay::replyFinished(QNetworkReply* reply) {
// replace our byte array with the downloaded data
_billboard = reply->readAll();
_manager->deleteLater();
_manager = NULL;
}

View file

@ -0,0 +1,46 @@
//
// BillboardOverlay.h
//
//
// Created by Clement on 7/1/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_BillboardOverlay_h
#define hifi_BillboardOverlay_h
#include <QScopedPointer>
#include <QUrl>
#include "Base3DOverlay.h"
#include "../../renderer/TextureCache.h"
class BillboardOverlay : public Base3DOverlay {
Q_OBJECT
public:
BillboardOverlay();
virtual void render();
virtual void setProperties(const QScriptValue& properties);
private slots:
void replyFinished(QNetworkReply* reply);
private:
void setBillboardURL(const QUrl url);
QNetworkAccessManager* _manager;
QUrl _url;
QByteArray _billboard;
QSize _size;
QScopedPointer<Texture> _billboardTexture;
glm::quat _rotation;
float _scale;
bool _isFacingAvatar;
};
#endif // hifi_BillboardOverlay_h

View file

@ -19,7 +19,7 @@
#include "ImageOverlay.h"
ImageOverlay::ImageOverlay() :
_manager(0),
_manager(NULL),
_textureID(0),
_renderImage(false),
_textureBound(false),
@ -37,6 +37,7 @@ ImageOverlay::~ImageOverlay() {
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
void ImageOverlay::setImageURL(const QUrl& url) {
// TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
_manager->deleteLater();
_manager = new QNetworkAccessManager();
connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
_manager->get(QNetworkRequest(url));
@ -49,6 +50,7 @@ void ImageOverlay::replyFinished(QNetworkReply* reply) {
_textureImage.loadFromData(rawData);
_renderImage = true;
_manager->deleteLater();
_manager = NULL;
}
void ImageOverlay::render() {

View file

@ -0,0 +1,115 @@
//
// ModelOverlay.cpp
//
//
// Created by Clement on 6/30/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 "../../Menu.h"
#include "ModelOverlay.h"
ModelOverlay::ModelOverlay()
: _model(),
_scale(1.0f),
_updateModel(false) {
_model.init();
}
void ModelOverlay::update(float deltatime) {
if (_updateModel) {
_updateModel = false;
_model.setScaleToFit(true, _scale);
_model.setSnapModelToCenter(true);
_model.setRotation(_rotation);
_model.setTranslation(_position);
_model.setURL(_url);
_model.simulate(deltatime, true);
} else {
_model.simulate(deltatime);
}
}
void ModelOverlay::render() {
if (_model.isActive()) {
if (_model.isRenderable()) {
_model.render(_alpha);
}
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
if (displayModelBounds) {
glm::vec3 unRotatedMinimum = _model.getUnscaledMeshExtents().minimum;
glm::vec3 unRotatedMaximum = _model.getUnscaledMeshExtents().maximum;
glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum;
float width = unRotatedExtents.x;
float height = unRotatedExtents.y;
float depth = unRotatedExtents.z;
Extents rotatedExtents = _model.getUnscaledMeshExtents();
calculateRotatedExtents(rotatedExtents, _rotation);
glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum;
const glm::vec3& modelScale = _model.getScale();
glPushMatrix(); {
glTranslatef(_position.x, _position.y, _position.z);
// draw the rotated bounding cube
glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
glPushMatrix(); {
glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z);
glutWireCube(1.0);
} glPopMatrix();
// draw the model relative bounding box
glm::vec3 axis = glm::axis(_rotation);
glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z);
glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z);
glColor3f(0.0f, 1.0f, 0.0f);
glutWireCube(1.0);
} glPopMatrix();
}
}
}
void ModelOverlay::setProperties(const QScriptValue &properties) {
Base3DOverlay::setProperties(properties);
QScriptValue urlValue = properties.property("url");
if (urlValue.isValid()) {
_url = urlValue.toVariant().toString();
_updateModel = true;
}
QScriptValue scaleValue = properties.property("scale");
if (scaleValue.isValid()) {
_scale = scaleValue.toVariant().toFloat();
_updateModel = true;
}
QScriptValue rotationValue = properties.property("rotation");
if (rotationValue.isValid()) {
QScriptValue x = rotationValue.property("x");
QScriptValue y = rotationValue.property("y");
QScriptValue z = rotationValue.property("z");
QScriptValue w = rotationValue.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
_rotation.x = x.toVariant().toFloat();
_rotation.y = y.toVariant().toFloat();
_rotation.z = z.toVariant().toFloat();
_rotation.w = w.toVariant().toFloat();
}
_updateModel = true;
}
if (properties.property("position").isValid()) {
_updateModel = true;
}
}

View file

@ -0,0 +1,38 @@
//
// ModelOverlay.h
//
//
// Created by Clement on 6/30/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_ModelOverlay_h
#define hifi_ModelOverlay_h
#include "Base3DOverlay.h"
#include "../../renderer/Model.h"
class ModelOverlay : public Base3DOverlay {
Q_OBJECT
public:
ModelOverlay();
virtual void update(float deltatime);
virtual void render();
virtual void setProperties(const QScriptValue& properties);
private:
Model _model;
QUrl _url;
glm::quat _rotation;
float _scale;
bool _updateModel;
};
#endif // hifi_ModelOverlay_h

View file

@ -10,13 +10,15 @@
#include <Application.h>
#include "BillboardOverlay.h"
#include "Cube3DOverlay.h"
#include "ImageOverlay.h"
#include "Line3DOverlay.h"
#include "LocalVoxelsOverlay.h"
#include "ModelOverlay.h"
#include "Overlays.h"
#include "Sphere3DOverlay.h"
#include "TextOverlay.h"
#include "LocalVoxelsOverlay.h"
Overlays::Overlays() : _nextOverlayID(1) {
}
@ -82,13 +84,13 @@ void Overlays::render3D() {
return;
}
bool myAvatarComputed = false;
MyAvatar* avatar;
MyAvatar* avatar = NULL;
glm::quat myAvatarRotation;
glm::vec3 myAvatarPosition;
float angle;
glm::vec3 axis;
float myAvatarScale;
glm::vec3 myAvatarPosition(0.0f);
float angle = 0.0f;
glm::vec3 axis(0.0f, 1.0f, 0.0f);
float myAvatarScale = 1.0f;
foreach(Overlay* thisOverlay, _overlays3D) {
glPushMatrix();
switch (thisOverlay->getAnchor()) {
@ -156,6 +158,18 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "model") {
thisOverlay = new ModelOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "billboard") {
thisOverlay = new BillboardOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
}
if (created) {

View file

@ -137,7 +137,7 @@ background: transparent;</string>
<property name="font">
<font>
<family>Helvetica,Arial,sans-serif</family>
<pointsize>16</pointsize>
<pointsize>-1</pointsize>
<weight>75</weight>
<italic>false</italic>
<bold>true</bold>
@ -326,9 +326,6 @@ padding-top: 3px;</string>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">margin: 0;</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
@ -336,7 +333,7 @@ padding-top: 3px;</string>
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
@ -352,7 +349,7 @@ padding-top: 3px;</string>
<rect>
<x>0</x>
<y>0</y>
<width>269</width>
<width>284</width>
<height>16</height>
</rect>
</property>
@ -460,7 +457,6 @@ font: bold 16px;</string>
<string notr="true">background: transparent;
font-size: 14px;</string>
</property>
<zorder>runningScriptsList</zorder>
</widget>
</item>
</layout>
@ -627,10 +623,10 @@ QListView::item {
<enum>QFrame::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
</widget>
</item>

View file

@ -53,7 +53,7 @@ void AudioRingBuffer::reset() {
_isStarved = true;
}
void AudioRingBuffer::resizeForFrameSize(qint64 numFrameSamples) {
void AudioRingBuffer::resizeForFrameSize(int numFrameSamples) {
delete[] _buffer;
_sampleCapacity = numFrameSamples * RING_BUFFER_LENGTH_FRAMES;
_buffer = new int16_t[_sampleCapacity];
@ -70,14 +70,14 @@ int AudioRingBuffer::parseData(const QByteArray& packet) {
return writeData(packet.data() + numBytesBeforeAudioData, packet.size() - numBytesBeforeAudioData);
}
qint64 AudioRingBuffer::readSamples(int16_t* destination, qint64 maxSamples) {
int AudioRingBuffer::readSamples(int16_t* destination, int maxSamples) {
return readData((char*) destination, maxSamples * sizeof(int16_t));
}
qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) {
int AudioRingBuffer::readData(char *data, int maxSize) {
// only copy up to the number of samples we have available
int numReadSamples = std::min((unsigned) (maxSize / sizeof(int16_t)), samplesAvailable());
int numReadSamples = std::min((int) (maxSize / sizeof(int16_t)), samplesAvailable());
// If we're in random access mode, then we consider our number of available read samples slightly
// differently. Namely, if anything has been written, we say we have as many samples as they ask for
@ -118,14 +118,14 @@ qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) {
return numReadSamples * sizeof(int16_t);
}
qint64 AudioRingBuffer::writeSamples(const int16_t* source, qint64 maxSamples) {
int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) {
return writeData((const char*) source, maxSamples * sizeof(int16_t));
}
qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
int AudioRingBuffer::writeData(const char* data, int maxSize) {
// make sure we have enough bytes left for this to be the right amount of audio
// otherwise we should not copy that data, and leave the buffer pointers where they are
int samplesToCopy = std::min((quint64)(maxSize / sizeof(int16_t)), (quint64)_sampleCapacity);
int samplesToCopy = std::min((int)(maxSize / sizeof(int16_t)), _sampleCapacity);
int samplesRoomFor = _sampleCapacity - samplesAvailable();
if (samplesToCopy > samplesRoomFor) {
@ -167,7 +167,7 @@ void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) {
}
}
unsigned int AudioRingBuffer::samplesAvailable() const {
int AudioRingBuffer::samplesAvailable() const {
if (!_endOfLastWrite) {
return 0;
}
@ -208,7 +208,7 @@ int AudioRingBuffer::addSilentFrame(int numSilentSamples) {
return numSilentSamples * sizeof(int16_t);
}
bool AudioRingBuffer::isNotStarvedOrHasMinimumSamples(unsigned int numRequiredSamples) const {
bool AudioRingBuffer::isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const {
if (!_isStarved) {
return true;
} else {

View file

@ -43,7 +43,7 @@ public:
~AudioRingBuffer();
void reset();
void resizeForFrameSize(qint64 numFrameSamples);
void resizeForFrameSize(int numFrameSamples);
int getSampleCapacity() const { return _sampleCapacity; }
@ -53,20 +53,20 @@ public:
const int16_t* getNextOutput() const { return _nextOutput; }
const int16_t* getBuffer() const { return _buffer; }
qint64 readSamples(int16_t* destination, qint64 maxSamples);
qint64 writeSamples(const int16_t* source, qint64 maxSamples);
int readSamples(int16_t* destination, int maxSamples);
int writeSamples(const int16_t* source, int maxSamples);
qint64 readData(char* data, qint64 maxSize);
qint64 writeData(const char* data, qint64 maxSize);
int readData(char* data, int maxSize);
int writeData(const char* data, int maxSize);
int16_t& operator[](const int index);
const int16_t& operator[] (const int index) const;
void shiftReadPosition(unsigned int numSamples);
unsigned int samplesAvailable() const;
int samplesAvailable() const;
bool isNotStarvedOrHasMinimumSamples(unsigned int numRequiredSamples) const;
bool isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const;
bool isStarved() const { return _isStarved; }
void setIsStarved(bool isStarved) { _isStarved = isStarved; }

View file

@ -212,7 +212,7 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
}
return false;
} else if (samplesAvailable() < (unsigned int)samplesPerFrame) {
} else if (samplesAvailable() < samplesPerFrame) {
// if the buffer doesn't have a full frame of samples to take for mixing, it is starved
_isStarved = true;

View file

@ -11,6 +11,7 @@
#include "NodeList.h"
#include "PacketHeaders.h"
#include "UserActivityLogger.h"
#include "DomainHandler.h"
@ -83,6 +84,7 @@ void DomainHandler::setHostname(const QString& hostname) {
qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData());
QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&)));
UserActivityLogger::getInstance().changedDomain(_hostname);
emit hostnameChanged(_hostname);
}
}

View file

@ -0,0 +1,155 @@
//
// UserActivityLogger.cpp
//
//
// Created by Clement on 5/21/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 "UserActivityLogger.h"
#include <QEventLoop>
#include <QJsonDocument>
#include <QHttpMultiPart>
#include <QTimer>
static const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
UserActivityLogger& UserActivityLogger::getInstance() {
static UserActivityLogger sharedInstance;
return sharedInstance;
}
UserActivityLogger::UserActivityLogger() {
}
void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCallbackParameters params) {
AccountManager& accountManager = AccountManager::getInstance();
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
// Adding the action name
QHttpPart actionPart;
actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\"");
actionPart.setBody(QByteArray().append(action));
multipart->append(actionPart);
// If there are action details, add them to the multipart
if (!details.isEmpty()) {
QHttpPart detailsPart;
detailsPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
" name=\"action_details\"");
detailsPart.setBody(QJsonDocument(details).toJson(QJsonDocument::Compact));
multipart->append(detailsPart);
}
qDebug() << "Logging activity" << action;
// if no callbacks specified, call our owns
if (params.isEmpty()) {
params.jsonCallbackReceiver = this;
params.jsonCallbackMethod = "requestFinished";
params.errorCallbackReceiver = this;
params.errorCallbackMethod = "requestError";
}
accountManager.authenticatedRequest(USER_ACTIVITY_URL,
QNetworkAccessManager::PostOperation,
params,
NULL,
multipart);
}
void UserActivityLogger::requestFinished(const QJsonObject& object) {
qDebug() << object;
}
void UserActivityLogger::requestError(QNetworkReply::NetworkError error,const QString& string) {
qDebug() << error << ": " << string;
}
void UserActivityLogger::launch(QString applicationVersion) {
const QString ACTION_NAME = "launch";
QJsonObject actionDetails;
QString VERSION_KEY = "version";
actionDetails.insert(VERSION_KEY, applicationVersion);
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::close(int delayTime) {
const QString ACTION_NAME = "close";
// In order to get the end of the session, we need to give the account manager enough time to send the packet.
QEventLoop loop;
// Here we connect the callbacks to stop the event loop
JSONCallbackParameters params;
params.jsonCallbackReceiver = &loop;
params.errorCallbackReceiver = &loop;
params.jsonCallbackMethod = "quit";
params.errorCallbackMethod = "quit";
// In case something goes wrong, we also setup a timer so that the delai is not greater than delayTime
QTimer timer;
connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
// Now we can log it
logAction(ACTION_NAME, QJsonObject(), params);
timer.start(delayTime);
loop.exec();
}
void UserActivityLogger::changedDisplayName(QString displayName) {
const QString ACTION_NAME = "changed_display_name";
QJsonObject actionDetails;
const QString DISPLAY_NAME = "display_name";
actionDetails.insert(DISPLAY_NAME, displayName);
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::changedModel(QString typeOfModel, QString modelURL) {
const QString ACTION_NAME = "changed_model";
QJsonObject actionDetails;
const QString TYPE_OF_MODEL = "type_of_model";
const QString MODEL_URL = "model_url";
actionDetails.insert(TYPE_OF_MODEL, typeOfModel);
actionDetails.insert(MODEL_URL, modelURL);
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::changedDomain(QString domainURL) {
const QString ACTION_NAME = "changed_domain";
QJsonObject actionDetails;
const QString DOMAIN_URL = "domain_url";
actionDetails.insert(DOMAIN_URL, domainURL);
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceName) {
const QString ACTION_NAME = "connected_device";
QJsonObject actionDetails;
const QString TYPE_OF_DEVICE = "type_of_device";
const QString DEVICE_NAME = "device_name";
actionDetails.insert(TYPE_OF_DEVICE, typeOfDevice);
actionDetails.insert(DEVICE_NAME, deviceName);
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::loadedScript(QString scriptName) {
const QString ACTION_NAME = "loaded_script";
QJsonObject actionDetails;
const QString SCRIPT_NAME = "script_name";
actionDetails.insert(SCRIPT_NAME, scriptName);
logAction(ACTION_NAME, actionDetails);
}

View file

@ -0,0 +1,47 @@
//
// UserActivityLogger.h
//
//
// Created by Clement on 5/21/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_UserActivityLogger_h
#define hifi_UserActivityLogger_h
#include "AccountManager.h"
#include <QObject>
#include <QString>
#include <QJsonObject>
#include <QNetworkReply>
class UserActivityLogger : public QObject {
Q_OBJECT
public:
static UserActivityLogger& getInstance();
public slots:
void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters());
void launch(QString applicationVersion);
void close(int delayTime);
void changedDisplayName(QString displayName);
void changedModel(QString typeOfModel, QString modelURL);
void changedDomain(QString domainURL);
void connectedDevice(QString typeOfDevice, QString deviceName);
void loadedScript(QString scriptName);
private slots:
void requestFinished(const QJsonObject& object);
void requestError(QNetworkReply::NetworkError error,const QString& string);
private:
UserActivityLogger();
};
#endif // hifi_UserActivityLogger_h

View file

@ -24,9 +24,6 @@ int MAX_ENTITIES_PER_SIMULATION = 64;
int MAX_COLLISIONS_PER_SIMULATION = 256;
const int NUM_SHAPE_BITS = 6;
const int SHAPE_INDEX_MASK = (1 << (NUM_SHAPE_BITS + 1)) - 1;
PhysicsSimulation::PhysicsSimulation() : _collisionList(MAX_COLLISIONS_PER_SIMULATION),
_numIterations(0), _numCollisions(0), _constraintError(0.0f), _stepTime(0) {
}

View file

@ -23,7 +23,7 @@ void SequenceNumberStatsTests::runAllTests() {
pruneTest();
}
const int UINT16_RANGE = std::numeric_limits<quint16>::max() + 1;
const quint32 UINT16_RANGE = std::numeric_limits<quint16>::max() + 1;
void SequenceNumberStatsTests::rolloverTest() {
@ -34,7 +34,7 @@ void SequenceNumberStatsTests::rolloverTest() {
quint16 seq = 79; // start on some random number
for (int R = 0; R < 2; R++) {
for (int i = 0; i < 3 * UINT16_RANGE; i++) {
for (quint32 i = 0; i < 3 * UINT16_RANGE; i++) {
stats.sequenceNumberReceived(seq);
seq = seq + (quint16)1;
@ -53,12 +53,12 @@ void SequenceNumberStatsTests::earlyLateTest() {
SequenceNumberStats stats;
quint16 seq = 65530;
int numSent = 0;
quint32 numSent = 0;
int numEarly = 0;
int numLate = 0;
int numLost = 0;
int numRecovered = 0;
quint32 numEarly = 0;
quint32 numLate = 0;
quint32 numLost = 0;
quint32 numRecovered = 0;
for (int R = 0; R < 2; R++) {
for (int T = 0; T < 10000; T++) {
@ -122,12 +122,12 @@ void SequenceNumberStatsTests::duplicateTest() {
SequenceNumberStats stats;
quint16 seq = 12345;
int numSent = 0;
quint32 numSent = 0;
int numDuplicate = 0;
int numEarly = 0;
int numLate = 0;
int numLost = 0;
quint32 numDuplicate = 0;
quint32 numEarly = 0;
quint32 numLate = 0;
quint32 numLost = 0;
for (int R = 0; R < 2; R++) {
for (int T = 0; T < 10000; T++) {
@ -210,10 +210,10 @@ void SequenceNumberStatsTests::pruneTest() {
SequenceNumberStats stats;
quint16 seq = 54321;
int numSent = 0;
quint32 numSent = 0;
int numEarly = 0;
int numLost = 0;
quint32 numEarly = 0;
quint32 numLost = 0;
for (int R = 0; R < 2; R++) {
for (int T = 0; T < 1000; T++) {