diff --git a/examples/crazylegs.js b/examples/crazylegs.js index b0f8e937bc..7a6fb68520 100644 --- a/examples/crazylegs.js +++ b/examples/crazylegs.js @@ -20,21 +20,21 @@ var jointMappings = "\n# Joint list start"; for (var i = 0; i < jointList.length; i++) { jointMappings = jointMappings + "\njointIndex = " + jointList[i] + " = " + i; } -print(jointMappings + "\n# Joint list end"); +print(jointMappings + "\n# Joint list end"); Script.update.connect(function(deltaTime) { cumulativeTime += deltaTime; - MyAvatar.setJointData("joint_R_hip", Quat.fromPitchYawRollDegrees(0.0, 0.0, AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY))); - MyAvatar.setJointData("joint_L_hip", Quat.fromPitchYawRollDegrees(0.0, 0.0, -AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY))); - MyAvatar.setJointData("joint_R_knee", Quat.fromPitchYawRollDegrees(0.0, 0.0, - AMPLITUDE * (1.0 + Math.sin(cumulativeTime * FREQUENCY)))); - MyAvatar.setJointData("joint_L_knee", Quat.fromPitchYawRollDegrees(0.0, 0.0, - AMPLITUDE * (1.0 - Math.sin(cumulativeTime * FREQUENCY)))); + MyAvatar.setJointData("RightUpLeg", Quat.fromPitchYawRollDegrees(AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY), 0.0, 0.0)); + MyAvatar.setJointData("LeftUpLeg", Quat.fromPitchYawRollDegrees(-AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY), 0.0, 0.0)); + MyAvatar.setJointData("RightLeg", Quat.fromPitchYawRollDegrees( + AMPLITUDE * (1.0 + Math.sin(cumulativeTime * FREQUENCY)),0.0, 0.0)); + MyAvatar.setJointData("LeftLeg", Quat.fromPitchYawRollDegrees( + AMPLITUDE * (1.0 - Math.sin(cumulativeTime * FREQUENCY)),0.0, 0.0)); }); Script.scriptEnding.connect(function() { - MyAvatar.clearJointData("joint_R_hip"); - MyAvatar.clearJointData("joint_L_hip"); - MyAvatar.clearJointData("joint_R_knee"); - MyAvatar.clearJointData("joint_L_knee"); + MyAvatar.clearJointData("RightUpLeg"); + MyAvatar.clearJointData("LeftUpLeg"); + MyAvatar.clearJointData("RightLeg"); + MyAvatar.clearJointData("LeftLeg"); }); diff --git a/examples/squeezeHands.js b/examples/squeezeHands.js new file mode 100644 index 0000000000..e53dd9569c --- /dev/null +++ b/examples/squeezeHands.js @@ -0,0 +1,52 @@ +// +// squeezeHands.js +// examples +// +// Created by Philip Rosedale on June 4, 2014 +// 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 +// + +var rightHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/RightHandAnim.fbx"; +var leftHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/LeftHandAnim.fbx"; + +var LEFT = 0; +var RIGHT = 1; + +var lastLeftFrame = 0; +var lastRightFrame = 0; + +var LAST_FRAME = 11.0; // What is the number of the last frame we want to use in the animation? +var SMOOTH_FACTOR = 0.80; + + +Script.update.connect(function(deltaTime) { + var leftTriggerValue = Math.sqrt(Controller.getTriggerValue(LEFT)); + var rightTriggerValue = Math.sqrt(Controller.getTriggerValue(RIGHT)); + + var leftFrame, rightFrame; + + // Average last few trigger frames together for a bit of smoothing + leftFrame = (leftTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastLeftFrame * SMOOTH_FACTOR; + rightFrame = (rightTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastRightFrame * SMOOTH_FACTOR; + + + if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){ + MyAvatar.stopAnimation(leftHandAnimation); + MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame); + } + if ((rightFrame != lastRightFrame) && rightHandAnimation.length) { + MyAvatar.stopAnimation(rightHandAnimation); + MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame); + } + + lastLeftFrame = leftFrame; + lastRightFrame = rightFrame; +}); + +Script.scriptEnding.connect(function() { + MyAvatar.stopAnimation(leftHandAnimation); + MyAvatar.stopAnimation(rightHandAnimation); +}); \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c877822a45..e79cb7a07b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1007,12 +1007,6 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_At: Menu::getInstance()->goTo(); break; - case Qt::Key_B: - _applicationOverlay.setOculusAngle(_applicationOverlay.getOculusAngle() - RADIANS_PER_DEGREE); - break; - case Qt::Key_N: - _applicationOverlay.setOculusAngle(_applicationOverlay.getOculusAngle() + RADIANS_PER_DEGREE); - break; default: event->ignore(); break; @@ -1848,7 +1842,8 @@ void Application::updateMyAvatarLookAtPosition() { } } else { // I am not looking at anyone else, so just look forward - lookAtSpot = _myAvatar->getHead()->calculateAverageEyePosition() + (_myAvatar->getHead()->getFinalOrientation() * glm::vec3(0.f, 0.f, -TREE_SCALE)); + lookAtSpot = _myAvatar->getHead()->calculateAverageEyePosition() + + (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.f, 0.f, -TREE_SCALE)); } // TODO: Add saccade to mouse pointer when stable, IF not looking at someone (since we know we are looking at it) /* diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 50ab720450..ea93d78a25 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -603,7 +603,7 @@ void Audio::handleAudioInput() { if (audioMixer && audioMixer->getActiveSocket()) { MyAvatar* interfaceAvatar = Application::getInstance()->getAvatar(); glm::vec3 headPosition = interfaceAvatar->getHead()->getPosition(); - glm::quat headOrientation = interfaceAvatar->getHead()->getFinalOrientation(); + glm::quat headOrientation = interfaceAvatar->getHead()->getFinalOrientationInWorldFrame(); // we need the amount of bytes in the buffer + 1 for type // + 12 for 3 floats for position + float for bearing + 1 attenuation byte diff --git a/interface/src/AudioReflector.cpp b/interface/src/AudioReflector.cpp index 52d23b4fee..e18a0ad36e 100644 --- a/interface/src/AudioReflector.cpp +++ b/interface/src/AudioReflector.cpp @@ -459,7 +459,7 @@ void AudioReflector::calculateAllReflections() { // only recalculate when we've moved, or if the attributes have changed // TODO: what about case where new voxels are added in front of us??? bool wantHeadOrientation = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingHeadOriented); - glm::quat orientation = wantHeadOrientation ? _myAvatar->getHead()->getFinalOrientation() : _myAvatar->getOrientation(); + glm::quat orientation = wantHeadOrientation ? _myAvatar->getHead()->getFinalOrientationInWorldFrame() : _myAvatar->getOrientation(); glm::vec3 origin = _myAvatar->getHead()->getPosition(); glm::vec3 listenerPosition = _myAvatar->getHead()->getPosition(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 870d5b1a53..baf46605fd 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -377,7 +377,7 @@ void Avatar::simulateAttachments(float deltaTime) { if (!isMyAvatar()) { model->setLODDistance(getLODDistance()); } - if (_skeletonModel.getJointPosition(jointIndex, jointPosition) && + if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && _skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) { model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale); model->setRotation(jointRotation * attachment.rotation); @@ -713,7 +713,7 @@ glm::vec3 Avatar::getJointPosition(int index) const { return position; } glm::vec3 position; - _skeletonModel.getJointPosition(index, position); + _skeletonModel.getJointPositionInWorldFrame(index, position); return position; } @@ -725,7 +725,7 @@ glm::vec3 Avatar::getJointPosition(const QString& name) const { return position; } glm::vec3 position; - _skeletonModel.getJointPosition(getJointIndex(name), position); + _skeletonModel.getJointPositionInWorldFrame(getJointIndex(name), position); return position; } diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index acd3d2c31f..601dad5563 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -48,10 +48,10 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) { void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { // get the rotation axes in joint space and use them to adjust the rotation - glm::mat3 axes = glm::mat3_cast(_rotation); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState._transform * glm::translate(state.getDefaultTranslationInParentFrame()) * + 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._rotation = glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2])) + state._rotationInParentFrame = 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; @@ -59,14 +59,16 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { // likewise with the eye joints - glm::mat4 inverse = glm::inverse(parentState._transform * glm::translate(state.getDefaultTranslationInParentFrame()) * - joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)); - glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientation() * IDENTITY_FRONT, 0.0f)); + // NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual. + glm::mat4 inverse = glm::inverse(glm::mat4_cast(_rotation) * parentState.getTransform() * + glm::translate(state.getDefaultTranslationInParentFrame()) * + joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)); + glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f)); glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() + _owningHead->getSaccade() - _translation, 1.0f)); glm::quat between = rotationBetween(front, lookAt); const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; - state._rotation = glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * + state._rotationInParentFrame = glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * joint.rotation; } @@ -92,6 +94,6 @@ bool FaceModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEy return false; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); - return getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) && - getJointPosition(geometry.rightEyeJointIndex, secondEyePosition); + return getJointPositionInWorldFrame(geometry.leftEyeJointIndex, firstEyePosition) && + getJointPositionInWorldFrame(geometry.rightEyeJointIndex, secondEyePosition); } diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 6f09007b31..ee242d179a 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -188,9 +188,12 @@ void Head::setScale (float scale) { _scale = scale; } -glm::quat Head::getFinalOrientation() const { - return _owningAvatar->getOrientation() * glm::quat(glm::radians( - glm::vec3(getFinalPitch(), getFinalYaw(), getFinalRoll() ))); +glm::quat Head::getFinalOrientationInWorldFrame() const { + return _owningAvatar->getOrientation() * getFinalOrientationInLocalFrame(); +} + +glm::quat Head::getFinalOrientationInLocalFrame() const { + return glm::quat(glm::radians(glm::vec3(getFinalPitch(), getFinalYaw(), getFinalRoll() ))); } glm::quat Head::getCameraOrientation () const { diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index cd7abeb9de..36df51fa6f 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -50,9 +50,13 @@ public: void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; } void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; } void setLeanForward(float leanForward) { _leanForward = leanForward; } + + /// \return orientationBase+Delta + glm::quat getFinalOrientationInLocalFrame() const; - /// \return orientationBody * orientationBase+Delta - glm::quat getFinalOrientation() const; + /// \return orientationBody * (orientationBase+Delta) + glm::quat getFinalOrientationInWorldFrame() const; + /// \return orientationBody * orientationBasePitch glm::quat getCameraOrientation () const; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c13950e520..a14152aa04 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -236,7 +236,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) { estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation())); } } - + // Rotate the body if the head is turned beyond the screen if (Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)) { const float TRACKER_YAW_TURN_SENSITIVITY = 0.5f; @@ -433,11 +433,11 @@ void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) { } void MyAvatar::startAnimation(const QString& url, float fps, float priority, - bool loop, bool hold, int firstFrame, int lastFrame, const QStringList& maskedJoints) { + 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(int, firstFrame), - Q_ARG(int, lastFrame), Q_ARG(const QStringList&, maskedJoints)); + 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; } AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); @@ -453,11 +453,11 @@ void MyAvatar::startAnimation(const QString& url, float fps, float priority, } void MyAvatar::startAnimationByRole(const QString& role, const QString& url, float fps, float priority, - bool loop, bool hold, int firstFrame, int lastFrame, const QStringList& maskedJoints) { + bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "startAnimationByRole", Q_ARG(const QString&, role), Q_ARG(const QString&, url), - Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(int, firstFrame), - Q_ARG(int, lastFrame), Q_ARG(const QStringList&, maskedJoints)); + 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; } // check for a configured animation for the role @@ -627,8 +627,8 @@ void MyAvatar::loadData(QSettings* settings) { handle->setLoop(settings->value("loop", true).toBool()); handle->setHold(settings->value("hold", false).toBool()); handle->setStartAutomatically(settings->value("startAutomatically", true).toBool()); - handle->setFirstFrame(settings->value("firstFrame", 0).toInt()); - handle->setLastFrame(settings->value("lastFrame", INT_MAX).toInt()); + handle->setFirstFrame(settings->value("firstFrame", 0.0f).toFloat()); + handle->setLastFrame(settings->value("lastFrame", INT_MAX).toFloat()); handle->setMaskedJoints(settings->value("maskedJoints").toStringList()); } settings->endArray(); @@ -724,7 +724,8 @@ void MyAvatar::updateLookAtTargetAvatar() { Avatar* avatar = static_cast(avatarPointer.data()); avatar->setIsLookAtTarget(false); if (!avatar->isMyAvatar()) { - float angleTo = glm::angle(getHead()->getFinalOrientation() * glm::vec3(0.0f, 0.0f, -1.0f), + glm::vec3 DEFAULT_GAZE_IN_HEAD_FRAME = glm::vec3(0.0f, 0.0f, -1.0f); + float angleTo = glm::angle(getHead()->getFinalOrientationInWorldFrame() * DEFAULT_GAZE_IN_HEAD_FRAME, glm::normalize(avatar->getHead()->getEyePosition() - getHead()->getEyePosition())); if (angleTo < smallestAngleTo) { _lookAtTargetAvatar = avatarPointer; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c0bc12b6b0..d99102c356 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -68,7 +68,7 @@ public: /// 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, int firstFrame = 0, int lastFrame = INT_MAX, const QStringList& maskedJoints = QStringList()); + bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); /// Stops an animation as identified by a URL. Q_INVOKABLE void stopAnimation(const QString& url); @@ -76,8 +76,8 @@ public: /// Starts an animation by its role, using the provided URL and parameters if the avatar doesn't have a custom /// animation for the role. Q_INVOKABLE void startAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f, - float priority = 1.0f, bool loop = false, bool hold = false, int firstFrame = 0, - int lastFrame = INT_MAX, const QStringList& maskedJoints = QStringList()); + float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, + float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); /// Stops an animation identified by its role. Q_INVOKABLE void stopAnimationByRole(const QString& role); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index f60c38198a..f5ca1ab218 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -21,6 +21,11 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) : _owningAvatar(owningAvatar) { } +void SkeletonModel::setJointStates(QVector states) { + Model::setJointStates(states); + _ragDoll.init(_jointStates); +} + const float PALM_PRIORITY = 3.0f; void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { @@ -46,7 +51,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { int jointIndex = geometry.humanIKJointIndices.at(humanIKJointIndex); if (jointIndex != -1) { JointState& state = _jointStates[jointIndex]; - state.setRotation(_rotation * prioVR->getJointRotations().at(i), PALM_PRIORITY); + state.setRotationFromBindFrame(prioVR->getJointRotations().at(i), PALM_PRIORITY); } } return; @@ -63,7 +68,9 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { if (_owningAvatar->getHandState() == HAND_STATE_NULL) { restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); } else { - applyHandPosition(geometry.rightHandJointIndex, _owningAvatar->getHandPosition()); + // transform into model-frame + glm::vec3 handPosition = glm::inverse(_rotation) * (_owningAvatar->getHandPosition() - _translation); + applyHandPosition(geometry.rightHandJointIndex, handPosition); } restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); @@ -76,6 +83,21 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { applyPalmData(geometry.leftHandJointIndex, hand->getPalms()[leftPalmIndex]); applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[rightPalmIndex]); } + + simulateRagDoll(deltaTime); +} + +void SkeletonModel::simulateRagDoll(float deltaTime) { + _ragDoll.slaveToSkeleton(_jointStates, 0.5f); + + float MIN_CONSTRAINT_ERROR = 0.005f; // 5mm + int MAX_ITERATIONS = 4; + int iterations = 0; + float delta = 0.0f; + do { + delta = _ragDoll.enforceConstraints(); + ++iterations; + } while (delta > MIN_CONSTRAINT_ERROR && iterations < MAX_ITERATIONS); } void SkeletonModel::getHandShapes(int jointIndex, QVector& shapes) const { @@ -119,6 +141,7 @@ void SkeletonModel::getBodyShapes(QVector& shapes) const { void SkeletonModel::renderIKConstraints() { renderJointConstraints(getRightHandJointIndex()); renderJointConstraints(getLeftHandJointIndex()); + renderRagDoll(); } class IndexValue { @@ -135,6 +158,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return; } + // NOTE: 'position' is in model-frame setJointPosition(jointIndex, position, glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); const FBXGeometry& geometry = _geometry->getFBXGeometry(); @@ -147,7 +171,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) return; } JointState& state = _jointStates[jointIndex]; - glm::quat handRotation = state.getJointRotation(true); + glm::quat handRotation = state.getRotation(); // align hand with forearm float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f; @@ -167,36 +191,41 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { // rotate palm to align with its normal (normal points out of hand's palm) glm::quat palmRotation; + glm::quat r0, r1; if (!Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK) && Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { JointState parentState = _jointStates[parentJointIndex]; - palmRotation = parentState.getJointRotation(true); + palmRotation = parentState.getRotationFromBindToModelFrame(); + r0 = palmRotation; } else { JointState state = _jointStates[jointIndex]; - palmRotation = state.getJointRotation(true); + palmRotation = state.getRotationFromBindToModelFrame(); } - palmRotation = rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()) * palmRotation; + glm::quat inverseRotation = glm::inverse(_rotation); + glm::vec3 palmNormal = inverseRotation * palm.getNormal(); + palmRotation = rotationBetween(palmRotation * geometry.palmDirection, palmNormal) * palmRotation; + r1 = palmRotation; // rotate palm to align with finger direction - glm::vec3 direction = palm.getFingerDirection(); + glm::vec3 direction = inverseRotation * palm.getFingerDirection(); palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction) * palmRotation; // set hand position, rotation + glm::vec3 palmPosition = inverseRotation * (palm.getPosition() - _translation); if (Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK)) { - setHandPosition(jointIndex, palm.getPosition(), palmRotation); + setHandPosition(jointIndex, palmPosition, palmRotation); } else if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { glm::vec3 forearmVector = palmRotation * glm::vec3(sign, 0.0f, 0.0f); - setJointPosition(parentJointIndex, palm.getPosition() + forearmVector * + setJointPosition(parentJointIndex, palmPosition + forearmVector * geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale), glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); JointState& parentState = _jointStates[parentJointIndex]; - parentState.setRotation(palmRotation, PALM_PRIORITY); - // slam parent-relative rotation to identity - _jointStates[jointIndex]._rotation = glm::quat(); - + parentState.setRotationFromBindFrame(palmRotation, PALM_PRIORITY); + // lock hand to forearm by slamming its rotation (in parent-frame) to identity + _jointStates[jointIndex]._rotationInParentFrame = glm::quat(); } else { - setJointPosition(jointIndex, palm.getPosition(), palmRotation, + setJointPosition(jointIndex, palmPosition, palmRotation, true, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); } } @@ -221,9 +250,7 @@ void SkeletonModel::updateJointState(int index) { Model::updateJointState(index); if (index == _geometry->getFBXGeometry().rootJointIndex) { - state._transform[3][0] = 0.0f; - state._transform[3][1] = 0.0f; - state._transform[3][2] = 0.0f; + state.clearTransformTranslation(); } } @@ -232,10 +259,10 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const return; } // get the rotation axes in joint space and use them to adjust the rotation - glm::mat3 axes = glm::mat3_cast(_rotation); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState._transform * glm::translate(state.getDefaultTranslationInParentFrame()) * + 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._rotation = glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), + state._rotationInParentFrame = 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; } @@ -259,11 +286,11 @@ void SkeletonModel::renderJointConstraints(int jointIndex) { do { const FBXJoint& joint = geometry.joints.at(jointIndex); const JointState& jointState = _jointStates.at(jointIndex); - glm::vec3 position = extractTranslation(jointState._transform) + _translation; + glm::vec3 position = _rotation * jointState.getPosition() + _translation; glPushMatrix(); glTranslatef(position.x, position.y, position.z); - glm::quat parentRotation = (joint.parentIndex == -1) ? _rotation : _jointStates.at(joint.parentIndex)._combinedRotation; + glm::quat parentRotation = (joint.parentIndex == -1) ? _rotation : _rotation * _jointStates.at(joint.parentIndex).getRotation(); glm::vec3 rotationAxis = glm::axis(parentRotation); glRotatef(glm::degrees(glm::angle(parentRotation)), rotationAxis.x, rotationAxis.y, rotationAxis.z); float fanScale = directionSize * 0.75f; @@ -296,7 +323,7 @@ void SkeletonModel::renderJointConstraints(int jointIndex) { } glPopMatrix(); - renderOrientationDirections(position, jointState._combinedRotation, directionSize); + renderOrientationDirections(position, _rotation * jointState.getRotation(), directionSize); jointIndex = joint.parentIndex; } while (jointIndex != -1 && geometry.joints.at(jointIndex).isFree); @@ -359,21 +386,21 @@ void SkeletonModel::setHandPosition(int jointIndex, const glm::vec3& position, c glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition); JointState& shoulderState = _jointStates[shoulderJointIndex]; - shoulderState.setRotation(shoulderRotation, PALM_PRIORITY); + shoulderState.setRotationFromBindFrame(shoulderRotation, PALM_PRIORITY); JointState& elbowState = _jointStates[elbowJointIndex]; - elbowState.setRotation(rotationBetween(shoulderRotation * forwardVector, wristPosition - elbowPosition) * shoulderRotation, PALM_PRIORITY); + elbowState.setRotationFromBindFrame(rotationBetween(shoulderRotation * forwardVector, wristPosition - elbowPosition) * shoulderRotation, PALM_PRIORITY); JointState& handState = _jointStates[jointIndex]; - handState.setRotation(rotation, PALM_PRIORITY); + handState.setRotationFromBindFrame(rotation, PALM_PRIORITY); } bool SkeletonModel::getLeftHandPosition(glm::vec3& position) const { - return getJointPosition(getLeftHandJointIndex(), position); + return getJointPositionInWorldFrame(getLeftHandJointIndex(), position); } bool SkeletonModel::getRightHandPosition(glm::vec3& position) const { - return getJointPosition(getRightHandJointIndex(), position); + return getJointPositionInWorldFrame(getRightHandJointIndex(), position); } bool SkeletonModel::restoreLeftHandPosition(float fraction, float priority) { @@ -381,7 +408,7 @@ bool SkeletonModel::restoreLeftHandPosition(float fraction, float priority) { } bool SkeletonModel::getLeftShoulderPosition(glm::vec3& position) const { - return getJointPosition(getLastFreeJointIndex(getLeftHandJointIndex()), position); + return getJointPositionInWorldFrame(getLastFreeJointIndex(getLeftHandJointIndex()), position); } float SkeletonModel::getLeftArmLength() const { @@ -393,7 +420,7 @@ bool SkeletonModel::restoreRightHandPosition(float fraction, float priority) { } bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const { - return getJointPosition(getLastFreeJointIndex(getRightHandJointIndex()), position); + return getJointPositionInWorldFrame(getLastFreeJointIndex(getRightHandJointIndex()), position); } float SkeletonModel::getRightArmLength() const { @@ -401,11 +428,11 @@ float SkeletonModel::getRightArmLength() const { } bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const { - return isActive() && getJointPosition(_geometry->getFBXGeometry().headJointIndex, headPosition); + return isActive() && getJointPositionInWorldFrame(_geometry->getFBXGeometry().headJointIndex, headPosition); } bool SkeletonModel::getNeckPosition(glm::vec3& neckPosition) const { - return isActive() && getJointPosition(_geometry->getFBXGeometry().neckJointIndex, neckPosition); + return isActive() && getJointPositionInWorldFrame(_geometry->getFBXGeometry().neckJointIndex, neckPosition); } bool SkeletonModel::getNeckParentRotation(glm::quat& neckParentRotation) const { @@ -416,7 +443,7 @@ bool SkeletonModel::getNeckParentRotation(glm::quat& neckParentRotation) const { if (geometry.neckJointIndex == -1) { return false; } - return getJointRotation(geometry.joints.at(geometry.neckJointIndex).parentIndex, neckParentRotation); + return getJointRotationInWorldFrame(geometry.joints.at(geometry.neckJointIndex).parentIndex, neckParentRotation); } bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { @@ -424,18 +451,18 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco return false; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); - if (getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) && - getJointPosition(geometry.rightEyeJointIndex, secondEyePosition)) { + if (getJointPositionInWorldFrame(geometry.leftEyeJointIndex, firstEyePosition) && + getJointPositionInWorldFrame(geometry.rightEyeJointIndex, secondEyePosition)) { return true; } // no eye joints; try to estimate based on head/neck joints glm::vec3 neckPosition, headPosition; - if (getJointPosition(geometry.neckJointIndex, neckPosition) && - getJointPosition(geometry.headJointIndex, headPosition)) { + if (getJointPositionInWorldFrame(geometry.neckJointIndex, neckPosition) && + getJointPositionInWorldFrame(geometry.headJointIndex, headPosition)) { const float EYE_PROPORTION = 0.6f; glm::vec3 baseEyePosition = glm::mix(neckPosition, headPosition, EYE_PROPORTION); glm::quat headRotation; - getJointRotation(geometry.headJointIndex, headRotation); + getJointRotationInWorldFrame(geometry.headJointIndex, headRotation); const float EYES_FORWARD = 0.25f; const float EYE_SEPARATION = 0.1f; float headHeight = glm::distance(neckPosition, headPosition); @@ -446,3 +473,30 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco return false; } +void SkeletonModel::renderRagDoll() { + const int BALL_SUBDIVISIONS = 6; + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glPushMatrix(); + + Application::getInstance()->loadTranslatedViewMatrix(_translation); + QVector points = _ragDoll.getPoints(); + int numPoints = points.size(); + float alpha = 0.3f; + float radius1 = 0.008f; + float radius2 = 0.01f; + for (int i = 0; i < numPoints; ++i) { + glPushMatrix(); + // draw each point as a yellow hexagon with black border + glm::vec3 position = _rotation * points[i]; + glTranslatef(position.x, position.y, position.z); + glColor4f(0.0f, 0.0f, 0.0f, alpha); + glutSolidSphere(radius2, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); + glColor4f(1.0f, 1.0f, 0.0f, alpha); + glutSolidSphere(radius1, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); + glPopMatrix(); + } + glPopMatrix(); + glEnable(GL_DEPTH_TEST); + glEnable(GL_LIGHTING); +} diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 91070e4ad6..d733d937ee 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -13,6 +13,7 @@ #define hifi_SkeletonModel_h #include "renderer/Model.h" +#include "renderer/RagDoll.h" class Avatar; @@ -23,8 +24,11 @@ class SkeletonModel : public Model { public: SkeletonModel(Avatar* owningAvatar); - + + void setJointStates(QVector states); + void simulate(float deltaTime, bool fullUpdate = true); + void simulateRagDoll(float deltaTime); /// \param jointIndex index of hand joint /// \param shapes[out] list in which is stored pointers to hand shapes @@ -89,8 +93,11 @@ public: /// \return whether or not both eye meshes were found bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; + void renderRagDoll(); protected: + /// \param jointIndex index of joint in model + /// \param position position of joint in model-frame void applyHandPosition(int jointIndex, const glm::vec3& position); void applyPalmData(int jointIndex, PalmData& palm); @@ -105,9 +112,14 @@ protected: private: void renderJointConstraints(int jointIndex); + + /// \param jointIndex index of joint in model + /// \param position position of joint in model-frame + /// \param rotation rotation of joint in model-frame void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation); Avatar* _owningAvatar; + RagDoll _ragDoll; }; #endif // hifi_SkeletonModel_h diff --git a/interface/src/devices/PrioVR.cpp b/interface/src/devices/PrioVR.cpp index ab29fc004b..e96f4f04d5 100644 --- a/interface/src/devices/PrioVR.cpp +++ b/interface/src/devices/PrioVR.cpp @@ -76,23 +76,24 @@ static void setPalm(float deltaTime, int index) { } } + // NOTE: this math is done in the worl-frame with unecessary complexity. + // TODO: transfom this to stay in the model-frame. glm::vec3 position; glm::quat rotation; - SkeletonModel* skeletonModel = &Application::getInstance()->getAvatar()->getSkeletonModel(); int jointIndex; glm::quat inverseRotation = glm::inverse(Application::getInstance()->getAvatar()->getOrientation()); if (index == LEFT_HAND_INDEX) { jointIndex = skeletonModel->getLeftHandJointIndex(); - skeletonModel->getJointRotation(jointIndex, rotation, true); + skeletonModel->getJointRotationInWorldFrame(jointIndex, rotation); rotation = inverseRotation * rotation * glm::quat(glm::vec3(0.0f, PI_OVER_TWO, 0.0f)); } else { jointIndex = skeletonModel->getRightHandJointIndex(); - skeletonModel->getJointRotation(jointIndex, rotation, true); + skeletonModel->getJointRotationInWorldFrame(jointIndex, rotation); rotation = inverseRotation * rotation * glm::quat(glm::vec3(0.0f, -PI_OVER_TWO, 0.0f)); } - skeletonModel->getJointPosition(jointIndex, position); + skeletonModel->getJointPositionInWorldFrame(jointIndex, position); position = inverseRotation * (position - skeletonModel->getTranslation()); palm->setRawRotation(rotation); diff --git a/interface/src/renderer/JointState.cpp b/interface/src/renderer/JointState.cpp new file mode 100644 index 0000000000..e66a2f44e9 --- /dev/null +++ b/interface/src/renderer/JointState.cpp @@ -0,0 +1,101 @@ +// +// JointState.cpp +// interface/src/renderer +// +// Created by Andrzej Kapolka on 10/18/13. +// Copyright 2013 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 +#include + +#include "JointState.h" + +JointState::JointState() : + _animationPriority(0.0f), + _fbxJoint(NULL) { +} + +void JointState::setFBXJoint(const FBXJoint* joint) { + assert(joint != NULL); + _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 +} + +void JointState::computeTransform(const glm::mat4& parentTransform) { + glm::quat modifiedRotation = _fbxJoint->preRotation * _rotationInParentFrame * _fbxJoint->postRotation; + glm::mat4 modifiedTransform = _fbxJoint->preTransform * glm::mat4_cast(modifiedRotation) * _fbxJoint->postTransform; + _transform = parentTransform * glm::translate(_fbxJoint->translation) * modifiedTransform; + _rotation = extractRotation(_transform); +} + +glm::quat JointState::getRotationFromBindToModelFrame() const { + return _rotation * _fbxJoint->inverseBindRotation; +} + +void JointState::restoreRotation(float fraction, float priority) { + assert(_fbxJoint != NULL); + if (priority == _animationPriority || _animationPriority == 0.0f) { + _rotationInParentFrame = safeMix(_rotationInParentFrame, _fbxJoint->rotation, fraction); + _animationPriority = 0.0f; + } +} + +void JointState::setRotationFromBindFrame(const glm::quat& rotation, float priority) { + assert(_fbxJoint != NULL); + if (priority >= _animationPriority) { + // rotation is from bind- to model-frame + _rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation); + _animationPriority = priority; + } +} + +void JointState::clearTransformTranslation() { + _transform[3][0] = 0.0f; + _transform[3][1] = 0.0f; + _transform[3][2] = 0.0f; +} + +void JointState::setRotation(const glm::quat& rotation, bool constrain, float priority) { + applyRotationDelta(rotation * glm::inverse(_rotation), true, priority); +} + +void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, float priority) { + // NOTE: delta is in jointParent-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))) { + // 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; +} + +const glm::vec3& JointState::getDefaultTranslationInParentFrame() const { + assert(_fbxJoint != NULL); + return _fbxJoint->translation; +} diff --git a/interface/src/renderer/JointState.h b/interface/src/renderer/JointState.h new file mode 100644 index 0000000000..b1a584d4ec --- /dev/null +++ b/interface/src/renderer/JointState.h @@ -0,0 +1,66 @@ +// +// JointState.h +// interface/src/renderer +// +// Created by Andrzej Kapolka on 10/18/13. +// Copyright 2013 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_JointState_h +#define hifi_JointState_h + +#include +#include +#include + +#include + +class JointState { +public: + JointState(); + + void setFBXJoint(const FBXJoint* joint); + const FBXJoint& getFBXJoint() const { return *_fbxJoint; } + + void copyState(const JointState& state); + + void computeTransform(const glm::mat4& parentTransform); + const glm::mat4& getTransform() const { return _transform; } + + glm::quat getRotation() const { return _rotation; } + glm::vec3 getPosition() const { return extractTranslation(_transform); } + + /// \return rotation from bind to model frame + glm::quat getRotationFromBindToModelFrame() const; + + /// \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 + void applyRotationDelta(const glm::quat& delta, bool constrain = true, float priority = 1.0f); + + const glm::vec3& getDefaultTranslationInParentFrame() const; + + void restoreRotation(float fraction, float priority); + + /// \param rotation is from bind- to model-frame + /// computes and sets new _rotationInParentFrame + /// NOTE: the JointState's model-frame transform/rotation are NOT updated! + void setRotationFromBindFrame(const glm::quat& rotation, float priority); + + void clearTransformTranslation(); + + glm::quat _rotationInParentFrame; // joint- to parentJoint-frame + float _animationPriority; // the priority of the animation affecting this joint + +private: + glm::mat4 _transform; // joint- to model-frame + glm::quat _rotation; // joint- to model-frame + + const FBXJoint* _fbxJoint; // JointState does NOT own its FBXJoint +}; + +#endif // hifi_JointState_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 4ed0e2a4f2..105301054b 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -166,38 +166,21 @@ QVector Model::createJointStates(const FBXGeometry& geometry) { jointStates.append(state); } - // compute transforms - // Unfortunately, the joints are not neccessarily in order from parents to children, - // so we must iterate over the list multiple times until all are set correctly. - QVector jointIsSet; + // compute model transforms int numJoints = jointStates.size(); - jointIsSet.fill(false, numJoints); - int numJointsSet = 0; - int lastNumJointsSet = -1; - while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) { - lastNumJointsSet = numJointsSet; - for (int i = 0; i < numJoints; ++i) { - if (jointIsSet[i]) { - continue; - } - JointState& state = jointStates[i]; - const FBXJoint& joint = state.getFBXJoint(); - int parentIndex = joint.parentIndex; - if (parentIndex == -1) { - _rootIndex = i; - glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset) * geometry.offset; - state.computeTransforms(baseTransform, _rotation); - ++numJointsSet; - jointIsSet[i] = true; - } else if (jointIsSet[parentIndex]) { - const JointState& parentState = jointStates.at(parentIndex); - state.computeTransforms(parentState._transform, parentState._combinedRotation); - ++numJointsSet; - jointIsSet[i] = true; - } + for (int i = 0; i < numJoints; ++i) { + JointState& state = jointStates[i]; + const FBXJoint& joint = state.getFBXJoint(); + int parentIndex = joint.parentIndex; + if (parentIndex == -1) { + _rootIndex = i; + glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; + state.computeTransform(parentTransform); + } else { + const JointState& parentState = jointStates.at(parentIndex); + state.computeTransform(parentState.getTransform()); } } - return jointStates; } @@ -476,7 +459,7 @@ void Model::reset() { } const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { - _jointStates[i]._rotation = geometry.joints.at(i).rotation; + _jointStates[i]._rotationInParentFrame = geometry.joints.at(i).rotation; } } @@ -527,12 +510,12 @@ bool Model::updateGeometry() { deleteGeometry(); _dilatedTextures.clear(); _geometry = geometry; - _jointStates = newJointStates; + setJointStates(newJointStates); needToRebuild = true; } else if (_jointStates.isEmpty()) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); if (fbxGeometry.joints.size() > 0) { - _jointStates = createJointStates(fbxGeometry); + setJointStates(createJointStates(fbxGeometry)); needToRebuild = true; } } else if (!geometry->isLoaded()) { @@ -574,6 +557,11 @@ bool Model::updateGeometry() { return needFullUpdate; } +// virtual +void Model::setJointStates(QVector states) { + _jointStates = states; +} + bool Model::render(float alpha, RenderMode mode, bool receiveShadows) { // render the attachments foreach (Model* attachment, _attachments) { @@ -686,7 +674,7 @@ bool Model::getJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { return false; } - rotation = _jointStates.at(index)._rotation; + rotation = _jointStates.at(index)._rotationInParentFrame; 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 +687,7 @@ void Model::setJointState(int index, bool valid, const glm::quat& rotation, floa JointState& state = _jointStates[index]; if (priority >= state._animationPriority) { if (valid) { - state._rotation = rotation; + state._rotationInParentFrame = rotation; state._animationPriority = priority; } else { state.restoreRotation(1.0f, priority); @@ -731,19 +719,29 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo } } +bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { + return false; + } + // position is in world-frame + position = _translation + _rotation * _jointStates[jointIndex].getPosition(); + return true; +} + bool Model::getJointPosition(int jointIndex, glm::vec3& position) const { if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return false; } - position = _translation + extractTranslation(_jointStates[jointIndex]._transform); + // position is in model-frame + position = extractTranslation(_jointStates[jointIndex].getTransform()); return true; } -bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) const { +bool Model::getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const { if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return false; } - rotation = _jointStates[jointIndex].getJointRotation(fromBind); + rotation = _rotation * _jointStates[jointIndex].getRotation(); return true; } @@ -751,7 +749,7 @@ bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return false; } - rotation = _jointStates[jointIndex]._combinedRotation; + rotation = _rotation * _jointStates[jointIndex].getRotation(); return true; } @@ -963,15 +961,16 @@ void Model::updateShapePositions() { glm::vec3 rootPosition(0.0f); _boundingRadius = 0.0f; float uniformScale = extractUniformScale(_scale); - const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { - const FBXJoint& joint = geometry.joints[i]; + const JointState& state = _jointStates[i]; + const FBXJoint& joint = state.getFBXJoint(); // shape position and rotation need to be in world-frame - glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i]._combinedRotation * joint.shapePosition); - glm::vec3 worldPosition = extractTranslation(_jointStates[i]._transform) + jointToShapeOffset + _translation; + glm::quat stateRotation = state.getRotation(); + glm::vec3 shapeOffset = uniformScale * (stateRotation * joint.shapePosition); + glm::vec3 worldPosition = _translation + _rotation * (state.getPosition() + shapeOffset); Shape* shape = _jointShapes[i]; shape->setPosition(worldPosition); - shape->setRotation(_jointStates[i]._combinedRotation * joint.shapeRotation); + shape->setRotation(_rotation * stateRotation * joint.shapeRotation); float distance = glm::distance(worldPosition, _translation) + shape->getBoundingRadius(); if (distance > _boundingRadius) { _boundingRadius = distance; @@ -993,12 +992,12 @@ bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct float radiusScale = extractUniformScale(_scale); for (int i = 0; i < _jointStates.size(); i++) { const FBXJoint& joint = geometry.joints[i]; - glm::vec3 end = extractTranslation(_jointStates[i]._transform); + glm::vec3 end = _translation + _rotation * _jointStates[i].getPosition(); float endRadius = joint.boneRadius * radiusScale; glm::vec3 start = end; float startRadius = joint.boneRadius * radiusScale; if (joint.parentIndex != -1) { - start = extractTranslation(_jointStates[joint.parentIndex]._transform); + start = _translation + _rotation * _jointStates[joint.parentIndex].getPosition(); startRadius = geometry.joints[joint.parentIndex].boneRadius * radiusScale; } // for now, use average of start and end radii @@ -1208,8 +1207,8 @@ void Model::simulateInternal(float deltaTime) { glm::vec3 jointTranslation = _translation; glm::quat jointRotation = _rotation; - getJointPosition(attachment.jointIndex, jointTranslation); - getJointRotation(attachment.jointIndex, jointRotation); + getJointPositionInWorldFrame(attachment.jointIndex, jointTranslation); + getJointRotationInWorldFrame(attachment.jointIndex, jointRotation); model->setTranslation(jointTranslation + jointRotation * attachment.translation * _scale); model->setRotation(jointRotation * attachment.rotation); @@ -1220,12 +1219,13 @@ void Model::simulateInternal(float deltaTime) { } } + glm::mat4 modelToWorld = glm::mat4_cast(_rotation); 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] = _jointStates[cluster.jointIndex]._transform * cluster.inverseBindMatrix; + state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getTransform() * cluster.inverseBindMatrix; } } @@ -1239,22 +1239,23 @@ void Model::updateJointState(int index) { JointState& state = _jointStates[index]; const FBXJoint& joint = state.getFBXJoint(); - if (joint.parentIndex == -1) { + // compute model transforms + int parentIndex = joint.parentIndex; + if (parentIndex == -1) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); - glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset) * geometry.offset; - state.computeTransforms(baseTransform, _rotation); + glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; + state.computeTransform(parentTransform); } else { - const JointState& parentState = _jointStates.at(joint.parentIndex); - state.computeTransforms(parentState._transform, parentState._combinedRotation); + const JointState& parentState = _jointStates.at(parentIndex); + state.computeTransform(parentState.getTransform()); } } -bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation, bool useRotation, +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()) { return false; } - glm::vec3 relativePosition = translation - _translation; const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; if (freeLineage.isEmpty()) { @@ -1267,21 +1268,19 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const // this is a cyclic coordinate descent algorithm: see // http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d const int ITERATION_COUNT = 1; - glm::vec3 worldAlignment = _rotation * alignment; + glm::vec3 worldAlignment = alignment; for (int i = 0; i < ITERATION_COUNT; i++) { // first, try to rotate the end effector as close as possible to the target rotation, if any glm::quat endRotation; if (useRotation) { JointState& state = _jointStates[jointIndex]; - // TODO: figure out what this is trying to do and combine it into one JointState method - endRotation = state.getJointRotation(true); - state.applyRotationDelta(rotation * glm::inverse(endRotation), true, priority); - endRotation = state.getJointRotation(true); + state.setRotation(rotation, true, priority); + endRotation = state.getRotation(); } // then, we go from the joint upwards, rotating the end as close as possible to the target - glm::vec3 endPosition = extractTranslation(_jointStates[jointIndex]._transform); + glm::vec3 endPosition = extractTranslation(_jointStates[jointIndex].getTransform()); for (int j = 1; freeLineage.at(j - 1) != lastFreeIndex; j++) { int index = freeLineage.at(j); JointState& state = _jointStates[index]; @@ -1289,18 +1288,18 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const if (!(joint.isFree || allIntermediatesFree)) { continue; } - glm::vec3 jointPosition = extractTranslation(state._transform); + glm::vec3 jointPosition = extractTranslation(state.getTransform()); glm::vec3 jointVector = endPosition - jointPosition; - glm::quat oldCombinedRotation = state._combinedRotation; + glm::quat oldCombinedRotation = state.getRotation(); glm::quat combinedDelta; float combinedWeight; if (useRotation) { combinedDelta = safeMix(rotation * glm::inverse(endRotation), - rotationBetween(jointVector, relativePosition - jointPosition), 0.5f); + rotationBetween(jointVector, position - jointPosition), 0.5f); combinedWeight = 2.0f; } else { - combinedDelta = rotationBetween(jointVector, relativePosition - jointPosition); + combinedDelta = rotationBetween(jointVector, position - jointPosition); combinedWeight = 1.0f; } if (alignment != glm::vec3() && j > 1) { @@ -1309,7 +1308,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const for (int k = j - 1; k > 0; k--) { int index = freeLineage.at(k); updateJointState(index); - positionSum += extractTranslation(_jointStates.at(index)._transform); + positionSum += extractTranslation(_jointStates.at(index).getTransform()); } glm::vec3 projectedCenterOfMass = glm::cross(jointVector, glm::cross(positionSum / (j - 1.0f) - jointPosition, jointVector)); @@ -1321,7 +1320,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const } } state.applyRotationDelta(combinedDelta, true, priority); - glm::quat actualDelta = state._combinedRotation * glm::inverse(oldCombinedRotation); + glm::quat actualDelta = state.getRotation() * glm::inverse(oldCombinedRotation); endPosition = actualDelta * jointVector + jointPosition; if (useRotation) { endRotation = actualDelta * endRotation; @@ -1344,7 +1343,7 @@ bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) } const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; - + foreach (int index, freeLineage) { JointState& state = _jointStates[index]; state.restoreRotation(fraction, priority); @@ -1470,12 +1469,12 @@ void Model::applyCollision(CollisionInfo& collision) { glm::vec3 jointPosition(0.0f); int jointIndex = collision._intData; - if (getJointPosition(jointIndex, jointPosition)) { + if (getJointPositionInWorldFrame(jointIndex, jointPosition)) { const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex]; if (joint.parentIndex != -1) { // compute the approximate distance (travel) that the joint needs to move glm::vec3 start; - getJointPosition(joint.parentIndex, start); + getJointPositionInWorldFrame(joint.parentIndex, start); glm::vec3 contactPoint = collision._contactPoint - start; glm::vec3 penetrationEnd = contactPoint + collision._penetration; glm::vec3 axis = glm::cross(contactPoint, penetrationEnd); @@ -1486,8 +1485,9 @@ void Model::applyCollision(CollisionInfo& collision) { float angle = asinf(travel / (glm::length(contactPoint) * glm::length(penetrationEnd))); axis = glm::normalize(axis); glm::vec3 end; - getJointPosition(jointIndex, end); - glm::vec3 newEnd = start + glm::angleAxis(angle, axis) * (end - start); + getJointPositionInWorldFrame(jointIndex, end); + // transform into model-frame + glm::vec3 newEnd = glm::inverse(_rotation) * (start + glm::angleAxis(angle, axis) * (end - start) - _translation); // try to move it setJointPosition(jointIndex, newEnd, glm::quat(), false, -1, true); } @@ -1895,8 +1895,8 @@ AnimationHandle::AnimationHandle(Model* model) : _loop(false), _hold(false), _startAutomatically(false), - _firstFrame(0), - _lastFrame(INT_MAX), + _firstFrame(0.0f), + _lastFrame(FLT_MAX), _running(false) { } @@ -1927,41 +1927,40 @@ void AnimationHandle::simulate(float deltaTime) { stop(); return; } - int lastFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - 1); - int firstFrameIndex = qMin(_firstFrame, lastFrameIndex); - if ((!_loop && _frameIndex >= lastFrameIndex) || firstFrameIndex == lastFrameIndex) { + float endFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - (_loop ? 0.0f : 1.0f)); + float startFrameIndex = qMin(_firstFrame, endFrameIndex); + if ((!_loop && (_frameIndex < startFrameIndex || _frameIndex > endFrameIndex)) || startFrameIndex == endFrameIndex) { // passed the end; apply the last frame - const FBXAnimationFrame& frame = animationGeometry.animationFrames.at(lastFrameIndex); - for (int i = 0; i < _jointMappings.size(); i++) { - int mapping = _jointMappings.at(i); - if (mapping != -1) { - JointState& state = _model->_jointStates[mapping]; - if (_priority >= state._animationPriority) { - state._rotation = frame.rotations.at(i); - state._animationPriority = _priority; - } - } - } + applyFrame(glm::clamp(_frameIndex, startFrameIndex, endFrameIndex)); if (!_hold) { stop(); } return; } - int frameCount = lastFrameIndex - firstFrameIndex + 1; - _frameIndex = firstFrameIndex + glm::mod(qMax(_frameIndex - firstFrameIndex, 0.0f), (float)frameCount); + // wrap within the the desired range + if (_frameIndex < startFrameIndex) { + _frameIndex = endFrameIndex - glm::mod(endFrameIndex - _frameIndex, endFrameIndex - startFrameIndex); + + } else if (_frameIndex > endFrameIndex) { + _frameIndex = startFrameIndex + glm::mod(_frameIndex - startFrameIndex, endFrameIndex - startFrameIndex); + } // blend between the closest two frames - const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at( - firstFrameIndex + ((int)glm::ceil(_frameIndex) - firstFrameIndex) % frameCount); - const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at( - firstFrameIndex + ((int)glm::floor(_frameIndex) - firstFrameIndex) % frameCount); - float frameFraction = glm::fract(_frameIndex); + applyFrame(_frameIndex); +} + +void AnimationHandle::applyFrame(float frameIndex) { + const FBXGeometry& animationGeometry = _animation->getGeometry(); + int frameCount = animationGeometry.animationFrames.size(); + const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount); + const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount); + float frameFraction = glm::fract(frameIndex); for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { JointState& state = _model->_jointStates[mapping]; if (_priority >= state._animationPriority) { - state._rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + state._rotationInParentFrame = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); state._animationPriority = _priority; } } @@ -1980,79 +1979,3 @@ void AnimationHandle::replaceMatchingPriorities(float newPriority) { } } -// ---------------------------------------------------------------------------- -// JointState TODO: move this class to its own files -// ---------------------------------------------------------------------------- -JointState::JointState() : - _animationPriority(0.0f), - _fbxJoint(NULL) { -} - -void JointState::setFBXJoint(const FBXJoint* joint) { - assert(joint != NULL); - _rotation = joint->rotation; - // NOTE: JointState does not own the FBXJoint to which it points. - _fbxJoint = joint; -} - -void JointState::copyState(const JointState& state) { - _rotation = state._rotation; - _transform = state._transform; - _combinedRotation = state._combinedRotation; - _animationPriority = state._animationPriority; - // DO NOT copy _fbxJoint -} - -void JointState::computeTransforms(const glm::mat4& baseTransform, const glm::quat& baseRotation) { - assert(_fbxJoint != NULL); - glm::quat combinedRotation = _fbxJoint->preRotation * _rotation * _fbxJoint->postRotation; - _transform = baseTransform * glm::translate(_fbxJoint->translation) * _fbxJoint->preTransform - * glm::mat4_cast(combinedRotation) * _fbxJoint->postTransform; - _combinedRotation = baseRotation * combinedRotation; -} - -glm::quat JointState::getJointRotation(bool fromBind) const { - assert(_fbxJoint != NULL); - return _combinedRotation * (fromBind ? _fbxJoint->inverseBindRotation : _fbxJoint->inverseDefaultRotation); -} - -void JointState::restoreRotation(float fraction, float priority) { - assert(_fbxJoint != NULL); - if (priority == _animationPriority) { - _rotation = safeMix(_rotation, _fbxJoint->rotation, fraction); - _animationPriority = 0.0f; - } -} - -void JointState::setRotation(const glm::quat& rotation, float priority) { - assert(_fbxJoint != NULL); - if (priority >= _animationPriority) { - _rotation = _rotation * glm::inverse(_combinedRotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation); - _animationPriority = priority; - } -} - -void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, float priority) { - 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))) { - // no constraints - _rotation = _rotation * glm::inverse(_combinedRotation) * delta * _combinedRotation; - _combinedRotation = delta * _combinedRotation; - return; - } - glm::quat targetRotation = delta * _combinedRotation; - glm::vec3 eulers = safeEulerAngles(_rotation * glm::inverse(_combinedRotation) * targetRotation); - glm::quat newRotation = glm::quat(glm::clamp(eulers, _fbxJoint->rotationMin, _fbxJoint->rotationMax)); - _combinedRotation = _combinedRotation * glm::inverse(_rotation) * newRotation; - _rotation = newRotation; -} - -const glm::vec3& JointState::getDefaultTranslationInParentFrame() const { - assert(_fbxJoint != NULL); - return _fbxJoint->translation; -} diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index fed83ecaae..3bc261ed44 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -22,6 +22,7 @@ #include "GeometryCache.h" #include "InterfaceConfig.h" +#include "JointState.h" #include "ProgramObject.h" #include "TextureCache.h" @@ -30,40 +31,6 @@ class Shape; typedef QSharedPointer AnimationHandlePointer; typedef QWeakPointer WeakAnimationHandlePointer; - -class JointState { -public: - JointState(); - - void setFBXJoint(const FBXJoint* joint); - const FBXJoint& getFBXJoint() const { return *_fbxJoint; } - - void copyState(const JointState& state); - - /// computes new _transform and _combinedRotation - void computeTransforms(const glm::mat4& baseTransform, const glm::quat& baseRotation); - - /// \return rotation from the joint's default (or bind) frame to world frame - glm::quat getJointRotation(bool fromBind = false) const; - - void applyRotationDelta(const glm::quat& delta, bool constrain = true, float priority = 1.0f); - - const glm::vec3& getDefaultTranslationInParentFrame() const; - - void restoreRotation(float fraction, float priority); - - /// \param rotation is from bind- to world-frame - /// computes parent relative _rotation and sets that - void setRotation(const glm::quat& rotation, float priority); - - glm::quat _rotation; // rotation relative to parent - glm::mat4 _transform; // rotation to world frame + translation in model frame - glm::quat _combinedRotation; // rotation from joint local to world frame - float _animationPriority; // the priority of the animation affecting this joint - -private: - const FBXJoint* _fbxJoint; // JointState does NOT own its FBXJoint -}; /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject { @@ -155,10 +122,15 @@ public: /// Returns the index of the last free ancestor of the indexed joint, or -1 if not found. int getLastFreeJointIndex(int jointIndex) const; - bool getJointPosition(int jointIndex, glm::vec3& position) const; - bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const; + bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const; + bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const; bool getJointCombinedRotation(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 + bool getJointPosition(int jointIndex, glm::vec3& position) const; + QStringList getJointNames() const; AnimationHandlePointer createAnimationHandle(); @@ -234,6 +206,8 @@ protected: // returns 'true' if needs fullUpdate after geometry change bool updateGeometry(); + + virtual void setJointStates(QVector states); void setScaleInternal(const glm::vec3& scale); void scaleToFit(); @@ -244,7 +218,15 @@ protected: /// Updates the state of the joint at the specified index. virtual void updateJointState(int index); - bool setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation = glm::quat(), + /// \param jointIndex index of joint in model structure + /// \param position position of joint in model-frame + /// \param rotation rotation of joint in model-frame + /// \param useRotation false if rotation should be ignored + /// \param lastFreeIndex + /// \param allIntermediatesFree + /// \param alignment + /// \return true if joint exists + 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); @@ -395,11 +377,11 @@ public: void setStartAutomatically(bool startAutomatically); bool getStartAutomatically() const { return _startAutomatically; } - void setFirstFrame(int firstFrame) { _firstFrame = firstFrame; } - int getFirstFrame() const { return _firstFrame; } + void setFirstFrame(float firstFrame) { _firstFrame = firstFrame; } + float getFirstFrame() const { return _firstFrame; } - void setLastFrame(int lastFrame) { _lastFrame = lastFrame; } - int getLastFrame() const { return _lastFrame; } + void setLastFrame(float lastFrame) { _lastFrame = lastFrame; } + float getLastFrame() const { return _lastFrame; } void setMaskedJoints(const QStringList& maskedJoints); const QStringList& getMaskedJoints() const { return _maskedJoints; } @@ -423,6 +405,7 @@ private: AnimationHandle(Model* model); void simulate(float deltaTime); + void applyFrame(float frameIndex); void replaceMatchingPriorities(float newPriority); Model* _model; @@ -435,8 +418,8 @@ private: bool _loop; bool _hold; bool _startAutomatically; - int _firstFrame; - int _lastFrame; + float _firstFrame; + float _lastFrame; QStringList _maskedJoints; bool _running; QVector _jointMappings; diff --git a/interface/src/renderer/RagDoll.cpp b/interface/src/renderer/RagDoll.cpp new file mode 100644 index 0000000000..54fb776552 --- /dev/null +++ b/interface/src/renderer/RagDoll.cpp @@ -0,0 +1,131 @@ +// +// RagDoll.cpp +// interface/src/avatar +// +// Created by Andrew Meadows 2014.05.30 +// 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 +#include + +#include +#include + +#include "RagDoll.h" + +// ---------------------------------------------------------------------------- +// FixedConstraint +// ---------------------------------------------------------------------------- +FixedConstraint::FixedConstraint() : _point(NULL), _anchor(0.0f, 0.0f, 0.0f) { +} + +float FixedConstraint::enforce() { + assert(_point != NULL); + float distance = glm::distance(_anchor, *_point); + *_point = _anchor; + return distance; +} + +void FixedConstraint::setPoint(glm::vec3* point) { + _point = point; +} + +void FixedConstraint::setAnchor(const glm::vec3& anchor) { + _anchor = anchor; +} + +// ---------------------------------------------------------------------------- +// DistanceConstraint +// ---------------------------------------------------------------------------- +DistanceConstraint::DistanceConstraint(glm::vec3* pointA, glm::vec3* pointB) : _distance(-1.0f) { + _points[0] = pointA; + _points[1] = pointB; + _distance = glm::distance(*(_points[0]), *(_points[1])); +} + +DistanceConstraint::DistanceConstraint(const DistanceConstraint& other) { + _distance = other._distance; + _points[0] = other._points[0]; + _points[1] = other._points[1]; +} + +void DistanceConstraint::setDistance(float distance) { + _distance = fabsf(distance); +} + +float DistanceConstraint::enforce() { + float newDistance = glm::distance(*(_points[0]), *(_points[1])); + glm::vec3 direction(0.0f, 1.0f, 0.0f); + if (newDistance > EPSILON) { + direction = (*(_points[0]) - *(_points[1])) / newDistance; + } + glm::vec3 center = 0.5f * (*(_points[0]) + *(_points[1])); + *(_points[0]) = center + (0.5f * _distance) * direction; + *(_points[1]) = center - (0.5f * _distance) * direction; + return glm::abs(newDistance - _distance); +} + +// ---------------------------------------------------------------------------- +// RagDoll +// ---------------------------------------------------------------------------- + +RagDoll::RagDoll() { +} + +RagDoll::~RagDoll() { + clear(); +} + +void RagDoll::init(const QVector& states) { + clear(); + const int numStates = states.size(); + _points.reserve(numStates); + for (int i = 0; i < numStates; ++i) { + const JointState& state = states[i]; + _points.push_back(state.getPosition()); + int parentIndex = state.getFBXJoint().parentIndex; + assert(parentIndex < i); + if (parentIndex != -1) { + DistanceConstraint* stick = new DistanceConstraint(&(_points[i]), &(_points[parentIndex])); + _constraints.push_back(stick); + } + } +} +/// Delete all data. +void RagDoll::clear() { + int numConstraints = _constraints.size(); + for (int i = 0; i < numConstraints; ++i) { + delete _constraints[i]; + } + _constraints.clear(); + _points.clear(); +} + +float RagDoll::slaveToSkeleton(const QVector& states, float fraction) { + const int numStates = states.size(); + assert(numStates == _points.size()); + fraction = glm::clamp(fraction, 0.0f, 1.0f); + float maxDistance = 0.0f; + for (int i = 0; i < numStates; ++i) { + glm::vec3 oldPoint = _points[i]; + _points[i] = (1.0f - fraction) * _points[i] + fraction * states[i].getPosition(); + maxDistance = glm::max(maxDistance, glm::distance(oldPoint, _points[i])); + } + return maxDistance; +} + +float RagDoll::enforceConstraints() { + float maxDistance = 0.0f; + const int numConstraints = _constraints.size(); + for (int i = 0; i < numConstraints; ++i) { + DistanceConstraint* c = static_cast(_constraints[i]); + //maxDistance = glm::max(maxDistance, _constraints[i]->enforce()); + maxDistance = glm::max(maxDistance, c->enforce()); + } + return maxDistance; +} diff --git a/interface/src/renderer/RagDoll.h b/interface/src/renderer/RagDoll.h new file mode 100644 index 0000000000..1d23973827 --- /dev/null +++ b/interface/src/renderer/RagDoll.h @@ -0,0 +1,78 @@ +// +// RagDoll.h +// interface/src/avatar +// +// Created by Andrew Meadows 2014.05.30 +// 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_RagDoll_h +#define hifi_RagDoll_h + +#include "renderer/Model.h" + +class Constraint { +public: + Constraint() {} + virtual ~Constraint() {} + + /// Enforce contraint by moving relevant points. + /// \return max distance of point movement + virtual float enforce() = 0; +}; + +class FixedConstraint : public Constraint { +public: + FixedConstraint(); + float enforce(); + void setPoint(glm::vec3* point); + void setAnchor(const glm::vec3& anchor); +private: + glm::vec3* _point; + glm::vec3 _anchor; +}; + +class DistanceConstraint : public Constraint { +public: + DistanceConstraint(glm::vec3* pointA, glm::vec3* pointB); + DistanceConstraint(const DistanceConstraint& other); + float enforce(); + void setDistance(float distance); +private: + float _distance; + glm::vec3* _points[2]; +}; + +class RagDoll { +public: + + RagDoll(); + virtual ~RagDoll(); + + /// Create points and constraints based on topology of collection of joints + /// \param joints list of connected joint states + void init(const QVector& states); + + /// Delete all data. + void clear(); + + /// \param states list of joint states + /// \param fraction range from 0.0 (no movement) to 1.0 (use joint locations) + /// \return max distance of point movement + float slaveToSkeleton(const QVector& states, float fraction); + + /// Enforce contraints. + /// \return max distance of point movement + float enforceConstraints(); + + const QVector& getPoints() const { return _points; } + +private: + QVector _constraints; + QVector _points; +}; + +#endif // hifi_RagDoll_h diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp index 2456b589da..c5ab826ebb 100644 --- a/interface/src/ui/AnimationsDialog.cpp +++ b/interface/src/ui/AnimationsDialog.cpp @@ -98,6 +98,7 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo layout->addRow("FPS:", _fps = new QDoubleSpinBox()); _fps->setSingleStep(0.01); + _fps->setMinimum(-FLT_MAX); _fps->setMaximum(FLT_MAX); _fps->setValue(handle->getFPS()); connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateHandle())); @@ -128,15 +129,17 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo _startAutomatically->setChecked(handle->getStartAutomatically()); connect(_startAutomatically, SIGNAL(toggled(bool)), SLOT(updateHandle())); - layout->addRow("First Frame:", _firstFrame = new QSpinBox()); + layout->addRow("First Frame:", _firstFrame = new QDoubleSpinBox()); + _firstFrame->setSingleStep(0.01); _firstFrame->setMaximum(INT_MAX); _firstFrame->setValue(handle->getFirstFrame()); - connect(_firstFrame, SIGNAL(valueChanged(int)), SLOT(updateHandle())); + connect(_firstFrame, SIGNAL(valueChanged(double)), SLOT(updateHandle())); - layout->addRow("Last Frame:", _lastFrame = new QSpinBox()); + layout->addRow("Last Frame:", _lastFrame = new QDoubleSpinBox()); + _lastFrame->setSingleStep(0.01); _lastFrame->setMaximum(INT_MAX); _lastFrame->setValue(handle->getLastFrame()); - connect(_lastFrame, SIGNAL(valueChanged(int)), SLOT(updateHandle())); + connect(_lastFrame, SIGNAL(valueChanged(double)), SLOT(updateHandle())); QHBoxLayout* buttons = new QHBoxLayout(); layout->addRow(buttons); diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h index dd3865741e..b9454d94e5 100644 --- a/interface/src/ui/AnimationsDialog.h +++ b/interface/src/ui/AnimationsDialog.h @@ -22,7 +22,6 @@ class QComboBox; class QDoubleSpinner; class QLineEdit; class QPushButton; -class QSpinBox; class QVBoxLayout; /// Allows users to edit the avatar animations. @@ -71,8 +70,8 @@ private: QCheckBox* _loop; QCheckBox* _hold; QCheckBox* _startAutomatically; - QSpinBox* _firstFrame; - QSpinBox* _lastFrame; + QDoubleSpinBox* _firstFrame; + QDoubleSpinBox* _lastFrame; QLineEdit* _maskedJoints; QPushButton* _chooseMaskedJoints; QPushButton* _start; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 5869394f04..49ec8ecddb 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -22,7 +22,8 @@ ApplicationOverlay::ApplicationOverlay() : _framebufferObject(NULL), _oculusAngle(65.0f * RADIANS_PER_DEGREE), - _distance(0.5f) { + _distance(0.5f), + _uiType(HEMISPHERE) { } @@ -305,6 +306,8 @@ inline float min(float a, float b) { return (a < b) ? a : b; } +const float textureFov = PI / 2.5f; + // Draws the FBO texture for Oculus rift. TODO: Draw a curved texture instead of plane. void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { @@ -316,28 +319,32 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { int mouseX = application->getMouseX(); int mouseY = application->getMouseY(); - int widgetWidth = glWidget->width(); - int widgetHeight = glWidget->height(); + const int widgetWidth = glWidget->width(); + const int widgetHeight = glWidget->height(); float magnifyWidth = 80.0f; float magnifyHeight = 60.0f; const float magnification = 4.0f; // Get vertical FoV of the displayed overlay texture const float halfVerticalAngle = _oculusAngle / 2.0f; - const float verticalAngle = halfVerticalAngle * 2.0f; const float overlayAspectRatio = glWidget->width() / (float)glWidget->height(); const float halfOverlayHeight = _distance * tan(halfVerticalAngle); + const float overlayHeight = halfOverlayHeight * 2.0f; // The more vertices, the better the curve const int numHorizontalVertices = 20; + const int numVerticalVertices = 20; // U texture coordinate width at each quad const float quadTexWidth = 1.0f / (numHorizontalVertices - 1); + const float quadTexHeight = 1.0f / (numVerticalVertices - 1); // Get horizontal angle and angle increment from vertical angle and aspect ratio const float horizontalAngle = halfVerticalAngle * 2.0f * overlayAspectRatio; const float angleIncrement = horizontalAngle / (numHorizontalVertices - 1); const float halfHorizontalAngle = horizontalAngle / 2; + const float verticalAngleIncrement = _oculusAngle / (numVerticalVertices - 1); + glActiveTexture(GL_TEXTURE0); glEnable(GL_BLEND); @@ -391,9 +398,10 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { magnifyHeight = widgetHeight - mouseY; } + const float halfMagnifyHeight = magnifyHeight / 2.0f; + float newWidth = magnifyWidth * magnification; float newHeight = magnifyHeight * magnification; - float tmp; // Magnification Texture Coordinates float magnifyULeft = mouseX / (float)widgetWidth; @@ -408,55 +416,118 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { // Get angle on the UI float leftAngle = (newMouseX / (float)widgetWidth) * horizontalAngle - halfHorizontalAngle; float rightAngle = ((newMouseX + newWidth) / (float)widgetWidth) * horizontalAngle - halfHorizontalAngle; - - float halfMagnifyHeight = magnifyHeight / 2.0f; - float leftX, rightX, leftZ, rightZ; + float bottomAngle = (newMouseY / (float)widgetHeight) * _oculusAngle - halfVerticalAngle; + float topAngle = ((newMouseY - newHeight) / (float)widgetHeight) * _oculusAngle - halfVerticalAngle; + + float leftX, rightX, leftZ, rightZ, topZ, bottomZ; // Get position on hemisphere using angle - leftX = sin(leftAngle) * _distance; - rightX = sin(rightAngle) * _distance; - leftZ = -cos(leftAngle) * _distance; - rightZ = -cos(rightAngle) * _distance; - - float bottomY = (1.0 - newMouseY / (float)widgetHeight) * halfOverlayHeight * 2.0f - halfOverlayHeight; - float topY = bottomY + (newHeight / widgetHeight) * halfOverlayHeight * 2; + if (_uiType == HEMISPHERE) { - //TODO: Remove immediate mode in favor of VBO - glBegin(GL_QUADS); + //Get new UV coordinates from our magnification window + float newULeft = newMouseX / widgetWidth; + float newURight = (newMouseX + newWidth) / widgetWidth; + float newVBottom = 1.0 - newMouseY / widgetHeight; + float newVTop = 1.0 - (newMouseY - newHeight) / widgetHeight; - glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(leftX, topY, leftZ); - glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rightX, topY, rightZ); - glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rightX, bottomY, rightZ); - glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(leftX, bottomY, leftZ); + // Project our position onto the hemisphere using the UV coordinates + float lX = sin((newULeft - 0.5f) * textureFov); + float rX = sin((newURight - 0.5f) * textureFov); + float bY = sin((newVBottom - 0.5f) * textureFov); + float tY = sin((newVTop - 0.5f) * textureFov); + + float dist; + //Bottom Left + dist = sqrt(lX * lX + bY * bY); + float blZ = sqrt(1.0f - dist * dist); + //Top Left + dist = sqrt(lX * lX + tY * tY); + float tlZ = sqrt(1.0f - dist * dist); + //Bottom Right + dist = sqrt(rX * rX + bY * bY); + float brZ = sqrt(1.0f - dist * dist); + //Top Right + dist = sqrt(rX * rX + tY * tY); + float trZ = sqrt(1.0f - dist * dist); - glEnd(); + glBegin(GL_QUADS); + glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(lX, tY, -tlZ); + glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rX, tY, -trZ); + glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rX, bY, -brZ); + glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(lX, bY, -blZ); + + glEnd(); + + } else { + leftX = sin(leftAngle) * _distance; + rightX = sin(rightAngle) * _distance; + leftZ = -cos(leftAngle) * _distance; + rightZ = -cos(rightAngle) * _distance; + if (_uiType == CURVED_SEMICIRCLE) { + topZ = -cos(topAngle * overlayAspectRatio) * _distance; + bottomZ = -cos(bottomAngle * overlayAspectRatio) * _distance; + } else { + // Dont want to use topZ or bottomZ for SEMICIRCLE + topZ = -99999; + bottomZ = -99999; + } + + float bottomY = (1.0 - newMouseY / (float)widgetHeight) * halfOverlayHeight * 2.0f - halfOverlayHeight; + float topY = bottomY + (newHeight / widgetHeight) * halfOverlayHeight * 2; + + //TODO: Remove immediate mode in favor of VBO + glBegin(GL_QUADS); + + glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(leftX, topY, max(topZ, leftZ)); + glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rightX, topY, max(topZ, rightZ)); + glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rightX, bottomY, max(bottomZ, rightZ)); + glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(leftX, bottomY, max(bottomZ, leftZ)); + + glEnd(); + } glDepthMask(GL_FALSE); glDisable(GL_ALPHA_TEST); //TODO: Remove immediate mode in favor of VBO - glBegin(GL_QUADS); - // Place the vertices in a semicircle curve around the camera - for (int i = 0; i < numHorizontalVertices-1; i++) { + if (_uiType == HEMISPHERE) { + renderTexturedHemisphere(); + } else{ + glBegin(GL_QUADS); + // Place the vertices in a semicircle curve around the camera + for (int i = 0; i < numHorizontalVertices - 1; i++) { + for (int j = 0; j < numVerticalVertices - 1; j++) { - // Calculate the X and Z coordinates from the angles and radius from camera - leftX = sin(angleIncrement * i - halfHorizontalAngle) * _distance; - rightX = sin(angleIncrement * (i + 1) - halfHorizontalAngle) * _distance; - leftZ = -cos(angleIncrement * i - halfHorizontalAngle) * _distance; - rightZ = -cos(angleIncrement * (i + 1) - halfHorizontalAngle) * _distance; + // Calculate the X and Z coordinates from the angles and radius from camera + leftX = sin(angleIncrement * i - halfHorizontalAngle) * _distance; + rightX = sin(angleIncrement * (i + 1) - halfHorizontalAngle) * _distance; + leftZ = -cos(angleIncrement * i - halfHorizontalAngle) * _distance; + rightZ = -cos(angleIncrement * (i + 1) - halfHorizontalAngle) * _distance; + if (_uiType == 2) { + topZ = -cos((verticalAngleIncrement * (j + 1) - halfVerticalAngle) * overlayAspectRatio) * _distance; + bottomZ = -cos((verticalAngleIncrement * j - halfVerticalAngle) * overlayAspectRatio) * _distance; + } else { + topZ = -99999; + bottomZ = -99999; + } - glTexCoord2f(quadTexWidth * i, 1); glVertex3f(leftX, halfOverlayHeight, leftZ); - glTexCoord2f(quadTexWidth * (i + 1), 1); glVertex3f(rightX, halfOverlayHeight, rightZ); - glTexCoord2f(quadTexWidth * (i + 1), 0); glVertex3f(rightX, -halfOverlayHeight, rightZ); - glTexCoord2f(quadTexWidth * i, 0); glVertex3f(leftX, -halfOverlayHeight, leftZ); + glTexCoord2f(quadTexWidth * i, (j + 1) * quadTexHeight); + glVertex3f(leftX, (j + 1) * quadTexHeight * overlayHeight - halfOverlayHeight, max(topZ, leftZ)); + glTexCoord2f(quadTexWidth * (i + 1), (j + 1) * quadTexHeight); + glVertex3f(rightX, (j + 1) * quadTexHeight * overlayHeight - halfOverlayHeight, max(topZ, rightZ)); + glTexCoord2f(quadTexWidth * (i + 1), j * quadTexHeight); + glVertex3f(rightX, j * quadTexHeight * overlayHeight - halfOverlayHeight, max(bottomZ, rightZ)); + glTexCoord2f(quadTexWidth * i, j * quadTexHeight); + glVertex3f(leftX, j * quadTexHeight * overlayHeight - halfOverlayHeight, max(bottomZ, leftZ)); + } + } + + glEnd(); } - - glEnd(); glPopMatrix(); - glDepthMask(GL_TRUE); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); @@ -466,13 +537,106 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { } +void ApplicationOverlay::renderTexturedHemisphere() { + const int slices = 80; + const int stacks = 80; + + static VerticesIndices vbo(0, 0); + int vertices = slices * (stacks - 1) + 1; + int indices = slices * 2 * 3 * (stacks - 2) + slices * 3; + if (vbo.first == 0) { + TextureVertex* vertexData = new TextureVertex[vertices]; + TextureVertex* vertex = vertexData; + for (int i = 0; i < stacks - 1; i++) { + float phi = PI_OVER_TWO * (float)i / (float)(stacks - 1); + float z = -sinf(phi), radius = cosf(phi); + + for (int j = 0; j < slices; j++) { + float theta = TWO_PI * (float)j / (float)slices; + + vertex->position.x = sinf(theta) * radius; + vertex->position.y = cosf(theta) * radius; + vertex->position.z = z; + vertex->uv.x = asin(vertex->position.x) / (textureFov) + 0.5f; + vertex->uv.y = asin(vertex->position.y) / (textureFov) + 0.5f; + vertex++; + } + } + vertex->position.x = 0.0f; + vertex->position.y = 0.0f; + vertex->position.z = -1.0f; + vertex->uv.x = 0.5f; + vertex->uv.y = 0.5f; + vertex++; + + glGenBuffers(1, &vbo.first); + glBindBuffer(GL_ARRAY_BUFFER, vbo.first); + const int BYTES_PER_VERTEX = sizeof(TextureVertex); + glBufferData(GL_ARRAY_BUFFER, vertices * BYTES_PER_VERTEX, vertexData, GL_STATIC_DRAW); + delete[] vertexData; + + GLushort* indexData = new GLushort[indices]; + GLushort* index = indexData; + for (int i = 0; i < stacks - 2; i++) { + GLushort bottom = i * slices; + GLushort top = bottom + slices; + for (int j = 0; j < slices; j++) { + int next = (j + 1) % slices; + + *(index++) = bottom + j; + *(index++) = top + next; + *(index++) = top + j; + + *(index++) = bottom + j; + *(index++) = bottom + next; + *(index++) = top + next; + } + } + GLushort bottom = (stacks - 2) * slices; + GLushort top = bottom + slices; + for (int i = 0; i < slices; i++) { + *(index++) = bottom + i; + *(index++) = bottom + (i + 1) % slices; + *(index++) = top; + } + + glGenBuffers(1, &vbo.second); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo.second); + const int BYTES_PER_INDEX = sizeof(GLushort); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices * BYTES_PER_INDEX, indexData, GL_STATIC_DRAW); + delete[] indexData; + + } else { + glBindBuffer(GL_ARRAY_BUFFER, vbo.first); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo.second); + } + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(3, GL_FLOAT, sizeof(TextureVertex), (void*)0); + glTexCoordPointer(2, GL_FLOAT, sizeof(TextureVertex), (void*)12); + + glDrawRangeElements(GL_TRIANGLES, 0, vertices - 1, indices, GL_UNSIGNED_SHORT, 0); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + +} + QOpenGLFramebufferObject* ApplicationOverlay::getFramebufferObject() { if (!_framebufferObject) { _framebufferObject = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size()); - glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + GLfloat borderColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); glBindTexture(GL_TEXTURE_2D, 0); } return _framebufferObject; diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 4faa0b69f5..8817549277 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -19,6 +19,8 @@ class QOpenGLFramebufferObject; class ApplicationOverlay { public: + enum UIType { HEMISPHERE, SEMICIRCLE, CURVED_SEMICIRCLE }; + ApplicationOverlay(); ~ApplicationOverlay(); @@ -32,14 +34,24 @@ public: // Setters void setOculusAngle(float oculusAngle) { _oculusAngle = oculusAngle; } + void setUIType(UIType uiType) { _uiType = uiType; } private: + // Interleaved vertex data + struct TextureVertex { + glm::vec3 position; + glm::vec2 uv; + }; + + typedef QPair VerticesIndices; + + void renderTexturedHemisphere(); - ProgramObject _textureProgram; QOpenGLFramebufferObject* _framebufferObject; float _trailingAudioLoudness; float _oculusAngle; float _distance; + UIType _uiType; }; #endif // hifi_ApplicationOverlay_h \ No newline at end of file diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index ad929e533c..387b41a839 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -47,11 +47,19 @@ IDStreamer::IDStreamer(Bitstream& stream) : _bits(1) { } -void IDStreamer::setBitsFromValue(int value) { - _bits = 1; - while (value >= (1 << _bits) - 1) { - _bits++; +static int getBitsForHighestValue(int highestValue) { + // if this turns out to be a bottleneck, there are fancier ways to do it (get the position of the highest set bit): + // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious + int bits = 0; + while (highestValue != 0) { + bits++; + highestValue >>= 1; } + return bits; +} + +void IDStreamer::setBitsFromValue(int value) { + _bits = getBitsForHighestValue(value + 1); } IDStreamer& IDStreamer::operator<<(int value) { @@ -71,6 +79,10 @@ IDStreamer& IDStreamer::operator>>(int& value) { return *this; } +static QByteArray getEnumName(const QMetaEnum& metaEnum) { + return QByteArray(metaEnum.scope()) + "::" + metaEnum.name(); +} + int Bitstream::registerMetaObject(const char* className, const QMetaObject* metaObject) { getMetaObjects().insert(className, metaObject); @@ -78,6 +90,17 @@ int Bitstream::registerMetaObject(const char* className, const QMetaObject* meta for (const QMetaObject* superClass = metaObject; superClass; superClass = superClass->superClass()) { getMetaObjectSubClasses().insert(superClass, metaObject); } + + // register the streamers for all enumerators + // temporarily disabled: crashes on Windows + //for (int i = 0; i < metaObject->enumeratorCount(); i++) { + // QMetaEnum metaEnum = metaObject->enumerator(i); + // const TypeStreamer*& streamer = getEnumStreamers()[QPair(metaEnum.scope(), metaEnum.name())]; + // if (!streamer) { + // getEnumStreamersByName().insert(getEnumName(metaEnum), streamer = new EnumTypeStreamer(metaEnum)); + // } + //} + return 0; } @@ -120,6 +143,14 @@ void Bitstream::addTypeSubstitution(const QByteArray& typeName, int type) { _typeStreamerSubstitutions.insert(typeName, getTypeStreamers().value(type)); } +void Bitstream::addTypeSubstitution(const QByteArray& typeName, const char* replacementTypeName) { + const TypeStreamer* streamer = getTypeStreamers().value(QMetaType::type(replacementTypeName)); + if (!streamer) { + streamer = getEnumStreamersByName().value(replacementTypeName); + } + _typeStreamerSubstitutions.insert(typeName, streamer); +} + const int LAST_BIT_POSITION = BITS_IN_BYTE - 1; Bitstream& Bitstream::write(const void* data, int bits, int offset) { @@ -193,7 +224,7 @@ void Bitstream::persistWriteMappings(const WriteMappings& mappings) { continue; } connect(it.key().data(), SIGNAL(destroyed(QObject*)), SLOT(clearSharedObject(QObject*))); - QPointer& reference = _sharedObjectReferences[it.key()->getID()]; + QPointer& reference = _sharedObjectReferences[it.key()->getOriginID()]; if (reference) { _sharedObjectStreamer.removePersistentID(reference); reference->disconnect(this); @@ -227,7 +258,7 @@ void Bitstream::persistReadMappings(const ReadMappings& mappings) { if (!it.value()) { continue; } - QPointer& reference = _sharedObjectReferences[it.value()->getRemoteID()]; + QPointer& reference = _sharedObjectReferences[it.value()->getRemoteOriginID()]; if (reference) { _sharedObjectStreamer.removePersistentValue(reference.data()); } @@ -280,16 +311,8 @@ void Bitstream::writeRawDelta(const QObject* value, const QObject* reference) { } const QMetaObject* metaObject = value->metaObject(); _metaObjectStreamer << metaObject; - for (int i = 0; i < metaObject->propertyCount(); i++) { - QMetaProperty property = metaObject->property(i); - if (!property.isStored(value)) { - continue; - } - const TypeStreamer* streamer = getTypeStreamers().value(property.userType()); - if (streamer) { - streamer->writeDelta(*this, property.read(value), reference && metaObject == reference->metaObject() ? - property.read(reference) : QVariant()); - } + foreach (const PropertyWriter& propertyWriter, getPropertyWriters(metaObject)) { + propertyWriter.writeDelta(*this, value, reference); } } @@ -411,6 +434,10 @@ Bitstream& Bitstream::operator>>(QUrl& url) { } Bitstream& Bitstream::operator<<(const QVariant& value) { + if (!value.isValid()) { + _typeStreamerStreamer << NULL; + return *this; + } const TypeStreamer* streamer = getTypeStreamers().value(value.userType()); if (streamer) { _typeStreamerStreamer << streamer; @@ -424,7 +451,11 @@ Bitstream& Bitstream::operator<<(const QVariant& value) { Bitstream& Bitstream::operator>>(QVariant& value) { TypeReader reader; _typeStreamerStreamer >> reader; - value = reader.read(*this); + if (reader.getTypeName().isEmpty()) { + value = QVariant(); + } else { + value = reader.read(*this); + } return *this; } @@ -458,15 +489,8 @@ Bitstream& Bitstream::operator<<(const QObject* object) { } const QMetaObject* metaObject = object->metaObject(); _metaObjectStreamer << metaObject; - for (int i = 0; i < metaObject->propertyCount(); i++) { - QMetaProperty property = metaObject->property(i); - if (!property.isStored(object)) { - continue; - } - const TypeStreamer* streamer = getTypeStreamers().value(property.userType()); - if (streamer) { - streamer->write(*this, property.read(object)); - } + foreach (const PropertyWriter& propertyWriter, getPropertyWriters(metaObject)) { + propertyWriter.write(*this, object); } return *this; } @@ -550,25 +574,12 @@ Bitstream& Bitstream::operator<(const QMetaObject* metaObject) { if (_metadataType == NO_METADATA) { return *this; } - int storedPropertyCount = 0; - for (int i = 0; i < metaObject->propertyCount(); i++) { - QMetaProperty property = metaObject->property(i); - if (property.isStored() && getTypeStreamers().contains(property.userType())) { - storedPropertyCount++; - } - } - *this << storedPropertyCount; + const QVector& propertyWriters = getPropertyWriters(metaObject); + *this << propertyWriters.size(); QCryptographicHash hash(QCryptographicHash::Md5); - for (int i = 0; i < metaObject->propertyCount(); i++) { - QMetaProperty property = metaObject->property(i); - if (!property.isStored()) { - continue; - } - const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType()); - if (!typeStreamer) { - continue; - } - _typeStreamerStreamer << typeStreamer; + foreach (const PropertyWriter& propertyWriter, propertyWriters) { + _typeStreamerStreamer << propertyWriter.getStreamer(); + const QMetaProperty& property = propertyWriter.getProperty(); if (_metadataType == FULL_METADATA) { *this << QByteArray::fromRawData(property.name(), strlen(property.name())); } else { @@ -621,25 +632,18 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) { QCryptographicHash hash(QCryptographicHash::Md5); bool matches = true; if (metaObject) { - int propertyIndex = 0; - for (int i = 0; i < metaObject->propertyCount(); i++) { - QMetaProperty property = metaObject->property(i); - if (!property.isStored()) { - continue; + const QVector& propertyWriters = getPropertyWriters(metaObject); + if (propertyWriters.size() == properties.size()) { + for (int i = 0; i < propertyWriters.size(); i++) { + const PropertyWriter& propertyWriter = propertyWriters.at(i); + if (!properties.at(i).getReader().matchesExactly(propertyWriter.getStreamer())) { + matches = false; + break; + } + const QMetaProperty& property = propertyWriter.getProperty(); + hash.addData(property.name(), strlen(property.name()) + 1); } - const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType()); - if (!typeStreamer) { - continue; - } - if (propertyIndex >= properties.size() || - !properties.at(propertyIndex).getReader().matchesExactly(typeStreamer)) { - matches = false; - break; - } - hash.addData(property.name(), strlen(property.name()) + 1); - propertyIndex++; - } - if (propertyIndex != properties.size()) { + } else { matches = false; } } @@ -656,7 +660,11 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) { } Bitstream& Bitstream::operator<(const TypeStreamer* streamer) { - const char* typeName = QMetaType::typeName(streamer->getType()); + if (!streamer) { + *this << QByteArray(); + return *this; + } + const char* typeName = streamer->getName(); *this << QByteArray::fromRawData(typeName, strlen(typeName)); if (_metadataType == NO_METADATA) { return *this; @@ -667,6 +675,27 @@ Bitstream& Bitstream::operator<(const TypeStreamer* streamer) { case TypeReader::SIMPLE_TYPE: return *this; + case TypeReader::ENUM_TYPE: { + QMetaEnum metaEnum = streamer->getMetaEnum(); + if (_metadataType == FULL_METADATA) { + *this << metaEnum.keyCount(); + for (int i = 0; i < metaEnum.keyCount(); i++) { + *this << QByteArray::fromRawData(metaEnum.key(i), strlen(metaEnum.key(i))); + *this << metaEnum.value(i); + } + } else { + *this << streamer->getBits(); + QCryptographicHash hash(QCryptographicHash::Md5); + for (int i = 0; i < metaEnum.keyCount(); i++) { + hash.addData(metaEnum.key(i), strlen(metaEnum.key(i)) + 1); + qint32 value = metaEnum.value(i); + hash.addData((const char*)&value, sizeof(qint32)); + } + QByteArray hashResult = hash.result(); + write(hashResult.constData(), hashResult.size() * BITS_IN_BYTE); + } + return *this; + } case TypeReader::LIST_TYPE: case TypeReader::SET_TYPE: return *this << streamer->getValueStreamer(); @@ -702,9 +731,16 @@ Bitstream& Bitstream::operator<(const TypeStreamer* streamer) { Bitstream& Bitstream::operator>(TypeReader& reader) { QByteArray typeName; *this >> typeName; + if (typeName.isEmpty()) { + reader = TypeReader(); + return *this; + } const TypeStreamer* streamer = _typeStreamerSubstitutions.value(typeName); if (!streamer) { streamer = getTypeStreamers().value(QMetaType::type(typeName.constData())); + if (!streamer) { + streamer = getEnumStreamersByName().value(typeName); + } } if (!streamer) { qWarning() << "Unknown type name: " << typeName << "\n"; @@ -719,7 +755,55 @@ Bitstream& Bitstream::operator>(TypeReader& reader) { case TypeReader::SIMPLE_TYPE: reader = TypeReader(typeName, streamer); return *this; - + + case TypeReader::ENUM_TYPE: { + if (_metadataType == FULL_METADATA) { + int keyCount; + *this >> keyCount; + QMetaEnum metaEnum = (streamer && streamer->getReaderType() == TypeReader::ENUM_TYPE) ? + streamer->getMetaEnum() : QMetaEnum(); + QHash mappings; + bool matches = (keyCount == metaEnum.keyCount()); + int highestValue = 0; + for (int i = 0; i < keyCount; i++) { + QByteArray key; + int value; + *this >> key >> value; + highestValue = qMax(value, highestValue); + int localValue = metaEnum.keyToValue(key); + if (localValue != -1) { + mappings.insert(value, localValue); + } + matches &= (value == localValue); + } + if (matches) { + reader = TypeReader(typeName, streamer); + } else { + reader = TypeReader(typeName, streamer, getBitsForHighestValue(highestValue), mappings); + } + } else { + int bits; + *this >> bits; + QCryptographicHash hash(QCryptographicHash::Md5); + if (streamer && streamer->getReaderType() == TypeReader::ENUM_TYPE) { + QMetaEnum metaEnum = streamer->getMetaEnum(); + for (int i = 0; i < metaEnum.keyCount(); i++) { + hash.addData(metaEnum.key(i), strlen(metaEnum.key(i)) + 1); + qint32 value = metaEnum.value(i); + hash.addData((const char*)&value, sizeof(qint32)); + } + } + QByteArray localHashResult = hash.result(); + QByteArray remoteHashResult(localHashResult.size(), 0); + read(remoteHashResult.data(), remoteHashResult.size() * BITS_IN_BYTE); + if (localHashResult == remoteHashResult) { + reader = TypeReader(typeName, streamer); + } else { + reader = TypeReader(typeName, streamer, bits, QHash()); + } + } + return *this; + } case TypeReader::LIST_TYPE: case TypeReader::SET_TYPE: { TypeReader valueReader; @@ -728,7 +812,7 @@ Bitstream& Bitstream::operator>(TypeReader& reader) { valueReader.matchesExactly(streamer->getValueStreamer())) { reader = TypeReader(typeName, streamer); } else { - reader = TypeReader(typeName, streamer, false, (TypeReader::Type)type, TypeReaderPointer(), + reader = TypeReader(typeName, streamer, (TypeReader::Type)type, TypeReaderPointer(new TypeReader(valueReader))); } return *this; @@ -741,8 +825,8 @@ Bitstream& Bitstream::operator>(TypeReader& reader) { valueReader.matchesExactly(streamer->getValueStreamer())) { reader = TypeReader(typeName, streamer); } else { - reader = TypeReader(typeName, streamer, false, TypeReader::MAP_TYPE, - TypeReaderPointer(new TypeReader(keyReader)), TypeReaderPointer(new TypeReader(valueReader))); + reader = TypeReader(typeName, streamer, TypeReaderPointer(new TypeReader(keyReader)), + TypeReaderPointer(new TypeReader(valueReader))); } return *this; } @@ -800,23 +884,20 @@ Bitstream& Bitstream::operator>(TypeReader& reader) { // if all fields are the same type and in the right order, we can use the (more efficient) default streamer const QVector& localFields = streamer->getMetaFields(); if (fieldCount != localFields.size()) { - reader = TypeReader(typeName, streamer, false, TypeReader::STREAMABLE_TYPE, - TypeReaderPointer(), TypeReaderPointer(), fields); + reader = TypeReader(typeName, streamer, fields); return *this; } for (int i = 0; i < fieldCount; i++) { const FieldReader& fieldReader = fields.at(i); if (!fieldReader.getReader().matchesExactly(localFields.at(i).getStreamer()) || fieldReader.getIndex() != i) { - reader = TypeReader(typeName, streamer, false, TypeReader::STREAMABLE_TYPE, - TypeReaderPointer(), TypeReaderPointer(), fields); + reader = TypeReader(typeName, streamer, fields); return *this; } } reader = TypeReader(typeName, streamer); return *this; } - reader = TypeReader(typeName, streamer, false, TypeReader::STREAMABLE_TYPE, - TypeReaderPointer(), TypeReaderPointer(), fields); + reader = TypeReader(typeName, streamer, fields); return *this; } @@ -847,9 +928,10 @@ Bitstream& Bitstream::operator<(const SharedObjectPointer& object) { return *this << (int)0; } *this << object->getID(); - QPointer reference = _sharedObjectReferences.value(object->getID()); + *this << object->getOriginID(); + QPointer reference = _sharedObjectReferences.value(object->getOriginID()); if (reference) { - writeRawDelta((QObject*)object.data(), (QObject*)reference.data()); + writeRawDelta((const QObject*)object.data(), (const QObject*)reference.data()); } else { *this << (QObject*)object.data(); } @@ -863,7 +945,9 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) { object = SharedObjectPointer(); return *this; } - QPointer reference = _sharedObjectReferences.value(id); + int originID; + *this >> originID; + QPointer reference = _sharedObjectReferences.value(originID); QPointer& pointer = _weakSharedObjectHash[id]; if (pointer) { ObjectReader objectReader; @@ -876,15 +960,19 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) { } else { QObject* rawObject; if (reference) { - readRawDelta(rawObject, (QObject*)reference.data()); + readRawDelta(rawObject, (const QObject*)reference.data()); } else { *this >> rawObject; } pointer = static_cast(rawObject); if (pointer) { + if (reference) { + pointer->setOriginID(reference->getOriginID()); + } pointer->setRemoteID(id); + pointer->setRemoteOriginID(originID); } else { - qDebug() << "Null object" << pointer << reference; + qDebug() << "Null object" << pointer << reference << id; } } object = static_cast(pointer.data()); @@ -893,13 +981,38 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) { void Bitstream::clearSharedObject(QObject* object) { SharedObject* sharedObject = static_cast(object); - _sharedObjectReferences.remove(sharedObject->getID()); + _sharedObjectReferences.remove(sharedObject->getOriginID()); int id = _sharedObjectStreamer.takePersistentID(sharedObject); if (id != 0) { emit sharedObjectCleared(id); } } +const QVector& Bitstream::getPropertyWriters(const QMetaObject* metaObject) { + QVector& propertyWriters = _propertyWriters[metaObject]; + if (propertyWriters.isEmpty()) { + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (!property.isStored()) { + continue; + } + const TypeStreamer* streamer; + if (property.isEnumType()) { + QMetaEnum metaEnum = property.enumerator(); + streamer = getEnumStreamers().value(QPair( + QByteArray::fromRawData(metaEnum.scope(), strlen(metaEnum.scope())), + QByteArray::fromRawData(metaEnum.name(), strlen(metaEnum.name())))); + } else { + streamer = getTypeStreamers().value(property.userType()); + } + if (streamer) { + propertyWriters.append(PropertyWriter(property, streamer)); + } + } + } + return propertyWriters; +} + QHash& Bitstream::getMetaObjects() { static QHash metaObjects; return metaObjects; @@ -915,6 +1028,16 @@ QHash& Bitstream::getTypeStreamers() { return typeStreamers; } +QHash, const TypeStreamer*>& Bitstream::getEnumStreamers() { + static QHash, const TypeStreamer*> enumStreamers; + return enumStreamers; +} + +QHash& Bitstream::getEnumStreamersByName() { + static QHash enumStreamersByName; + return enumStreamersByName; +} + QVector Bitstream::getPropertyReaders(const QMetaObject* metaObject) { QVector propertyReaders; if (!metaObject) { @@ -925,31 +1048,78 @@ QVector Bitstream::getPropertyReaders(const QMetaObject* metaObj if (!property.isStored()) { continue; } - const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType()); - if (typeStreamer) { - propertyReaders.append(PropertyReader(TypeReader(QByteArray(), typeStreamer), property)); + const TypeStreamer* streamer; + if (property.isEnumType()) { + QMetaEnum metaEnum = property.enumerator(); + streamer = getEnumStreamers().value(QPair( + QByteArray::fromRawData(metaEnum.scope(), strlen(metaEnum.scope())), + QByteArray::fromRawData(metaEnum.name(), strlen(metaEnum.name())))); + } else { + streamer = getTypeStreamers().value(property.userType()); + } + if (streamer) { + propertyReaders.append(PropertyReader(TypeReader(QByteArray(), streamer), property)); } } return propertyReaders; } -TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, bool exactMatch, Type type, - const TypeReaderPointer& keyReader, const TypeReaderPointer& valueReader, const QVector& fields) : +TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer) : _typeName(typeName), _streamer(streamer), - _exactMatch(exactMatch), - _type(type), - _keyReader(keyReader), - _valueReader(valueReader), + _exactMatch(true) { +} + +TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, int bits, const QHash& mappings) : + _typeName(typeName), + _streamer(streamer), + _exactMatch(false), + _type(ENUM_TYPE), + _bits(bits), + _mappings(mappings) { +} + +TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, const QVector& fields) : + _typeName(typeName), + _streamer(streamer), + _exactMatch(false), + _type(STREAMABLE_TYPE), _fields(fields) { } +TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, + Type type, const TypeReaderPointer& valueReader) : + _typeName(typeName), + _streamer(streamer), + _exactMatch(false), + _type(type), + _valueReader(valueReader) { +} + +TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, + const TypeReaderPointer& keyReader, const TypeReaderPointer& valueReader) : + _typeName(typeName), + _streamer(streamer), + _exactMatch(false), + _type(MAP_TYPE), + _keyReader(keyReader), + _valueReader(valueReader) { +} + QVariant TypeReader::read(Bitstream& in) const { if (_exactMatch) { return _streamer->read(in); } QVariant object = _streamer ? QVariant(_streamer->getType(), 0) : QVariant(); switch (_type) { + case ENUM_TYPE: { + int value = 0; + in.read(&value, _bits); + if (_streamer) { + _streamer->setEnumValue(object, value, _mappings); + } + break; + } case STREAMABLE_TYPE: { foreach (const FieldReader& field, _fields) { field.read(in, _streamer, object); @@ -1006,6 +1176,14 @@ void TypeReader::readRawDelta(Bitstream& in, QVariant& object, const QVariant& r return; } switch (_type) { + case ENUM_TYPE: { + int value = 0; + in.read(&value, _bits); + if (_streamer) { + _streamer->setEnumValue(object, value, _mappings); + } + break; + } case STREAMABLE_TYPE: { foreach (const FieldReader& field, _fields) { field.readDelta(in, _streamer, object, reference); @@ -1099,6 +1277,10 @@ uint qHash(const TypeReader& typeReader, uint seed) { return qHash(typeReader.getTypeName(), seed); } +QDebug& operator<<(QDebug& debug, const TypeReader& typeReader) { + return debug << typeReader.getTypeName(); +} + FieldReader::FieldReader(const TypeReader& reader, int index) : _reader(reader), _index(index) { @@ -1152,6 +1334,10 @@ uint qHash(const ObjectReader& objectReader, uint seed) { return qHash(objectReader.getClassName(), seed); } +QDebug& operator<<(QDebug& debug, const ObjectReader& objectReader) { + return debug << objectReader.getClassName(); +} + PropertyReader::PropertyReader(const TypeReader& reader, const QMetaProperty& property) : _reader(reader), _property(property) { @@ -1172,6 +1358,20 @@ void PropertyReader::readDelta(Bitstream& in, QObject* object, const QObject* re } } +PropertyWriter::PropertyWriter(const QMetaProperty& property, const TypeStreamer* streamer) : + _property(property), + _streamer(streamer) { +} + +void PropertyWriter::write(Bitstream& out, const QObject* object) const { + _streamer->write(out, _property.read(object)); +} + +void PropertyWriter::writeDelta(Bitstream& out, const QObject* object, const QObject* reference) const { + _streamer->writeDelta(out, _property.read(object), reference && object->metaObject() == reference->metaObject() ? + _property.read(reference) : QVariant()); +} + MetaField::MetaField(const QByteArray& name, const TypeStreamer* streamer) : _name(name), _streamer(streamer) { @@ -1180,6 +1380,14 @@ MetaField::MetaField(const QByteArray& name, const TypeStreamer* streamer) : TypeStreamer::~TypeStreamer() { } +const char* TypeStreamer::getName() const { + return QMetaType::typeName(_type); +} + +void TypeStreamer::setEnumValue(QVariant& object, int value, const QHash& mappings) const { + // nothing by default +} + const QVector& TypeStreamer::getMetaFields() const { static QVector emptyMetaFields; return emptyMetaFields; @@ -1201,6 +1409,14 @@ TypeReader::Type TypeStreamer::getReaderType() const { return TypeReader::SIMPLE_TYPE; } +int TypeStreamer::getBits() const { + return 0; +} + +QMetaEnum TypeStreamer::getMetaEnum() const { + return QMetaEnum(); +} + const TypeStreamer* TypeStreamer::getKeyStreamer() const { return NULL; } @@ -1236,3 +1452,104 @@ QVariant TypeStreamer::getValue(const QVariant& object, int index) const { void TypeStreamer::setValue(QVariant& object, int index, const QVariant& value) const { // nothing by default } + +QDebug& operator<<(QDebug& debug, const TypeStreamer* typeStreamer) { + return debug << (typeStreamer ? QMetaType::typeName(typeStreamer->getType()) : "null"); +} + +QDebug& operator<<(QDebug& debug, const QMetaObject* metaObject) { + return debug << (metaObject ? metaObject->className() : "null"); +} + +EnumTypeStreamer::EnumTypeStreamer(const QMetaEnum& metaEnum) : + _metaEnum(metaEnum), + _name(getEnumName(metaEnum)) { + + setType(QMetaType::Int); + + int highestValue = 0; + for (int j = 0; j < metaEnum.keyCount(); j++) { + highestValue = qMax(highestValue, metaEnum.value(j)); + } + _bits = getBitsForHighestValue(highestValue); +} + +const char* EnumTypeStreamer::getName() const { + return _name.constData(); +} + +TypeReader::Type EnumTypeStreamer::getReaderType() const { + return TypeReader::ENUM_TYPE; +} + +int EnumTypeStreamer::getBits() const { + return _bits; +} + +QMetaEnum EnumTypeStreamer::getMetaEnum() const { + return _metaEnum; +} + +bool EnumTypeStreamer::equal(const QVariant& first, const QVariant& second) const { + return first.toInt() == second.toInt(); +} + +void EnumTypeStreamer::write(Bitstream& out, const QVariant& value) const { + int intValue = value.toInt(); + out.write(&intValue, _bits); +} + +QVariant EnumTypeStreamer::read(Bitstream& in) const { + int intValue = 0; + in.read(&intValue, _bits); + return intValue; +} + +void EnumTypeStreamer::writeDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const { + int intValue = value.toInt(), intReference = reference.toInt(); + if (intValue == intReference) { + out << false; + } else { + out << true; + out.write(&intValue, _bits); + } +} + +void EnumTypeStreamer::readDelta(Bitstream& in, QVariant& value, const QVariant& reference) const { + bool changed; + in >> changed; + if (changed) { + int intValue = 0; + in.read(&intValue, _bits); + value = intValue; + } else { + value = reference; + } +} + +void EnumTypeStreamer::writeRawDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const { + int intValue = value.toInt(); + out.write(&intValue, _bits); +} + +void EnumTypeStreamer::readRawDelta(Bitstream& in, QVariant& value, const QVariant& reference) const { + int intValue = 0; + in.read(&intValue, _bits); + value = intValue; +} + +void EnumTypeStreamer::setEnumValue(QVariant& object, int value, const QHash& mappings) const { + if (_metaEnum.isFlag()) { + int combined = 0; + for (QHash::const_iterator it = mappings.constBegin(); it != mappings.constEnd(); it++) { + if (value & it.key()) { + combined |= it.value(); + } + } + object = combined; + + } else { + object = mappings.value(value); + } +} + diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 34b66eb9f2..146713910f 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -38,6 +38,7 @@ class FieldReader; class ObjectReader; class OwnedAttributeValue; class PropertyReader; +class PropertyWriter; class TypeReader; class TypeStreamer; @@ -235,6 +236,9 @@ public: /// Substitutes the supplied type for the given type name's default mapping. void addTypeSubstitution(const QByteArray& typeName, int type); + /// Substitutes the named type for the given type name's default mapping. + void addTypeSubstitution(const QByteArray& typeName, const char* replacementTypeName); + /// Writes a set of bits to the underlying stream. /// \param bits the number of bits to write /// \param offset the offset of the first bit @@ -294,6 +298,9 @@ public: template void writeRawDelta(const QList& value, const QList& reference); template void readRawDelta(QList& value, const QList& reference); + template void writeRawDelta(const QVector& value, const QVector& reference); + template void readRawDelta(QVector& value, const QVector& reference); + template void writeRawDelta(const QSet& value, const QSet& reference); template void readRawDelta(QSet& value, const QSet& reference); @@ -339,6 +346,9 @@ public: template Bitstream& operator<<(const QList& list); template Bitstream& operator>>(QList& list); + template Bitstream& operator<<(const QVector& list); + template Bitstream& operator>>(QVector& list); + template Bitstream& operator<<(const QSet& set); template Bitstream& operator>>(QSet& set); @@ -390,6 +400,8 @@ private slots: private: + const QVector& getPropertyWriters(const QMetaObject* metaObject); + QDataStream& _underlying; quint8 _byte; int _position; @@ -409,9 +421,13 @@ private: QHash _metaObjectSubstitutions; QHash _typeStreamerSubstitutions; + QHash > _propertyWriters; + static QHash& getMetaObjects(); static QMultiHash& getMetaObjectSubClasses(); static QHash& getTypeStreamers(); + static QHash, const TypeStreamer*>& getEnumStreamers(); + static QHash& getEnumStreamersByName(); static QVector getPropertyReaders(const QMetaObject* metaObject); }; @@ -472,6 +488,36 @@ template inline void Bitstream::readRawDelta(QList& value, const QLi } } +template inline void Bitstream::writeRawDelta(const QVector& value, const QVector& reference) { + *this << value.size(); + *this << reference.size(); + for (int i = 0; i < value.size(); i++) { + if (i < reference.size()) { + writeDelta(value.at(i), reference.at(i)); + } else { + *this << value.at(i); + } + } +} + +template inline void Bitstream::readRawDelta(QVector& value, const QVector& reference) { + value = reference; + int size, referenceSize; + *this >> size >> referenceSize; + if (size < value.size()) { + value.erase(value.begin() + size, value.end()); + } + for (int i = 0; i < size; i++) { + if (i < referenceSize) { + readDelta(value[i], reference.at(i)); + } else { + T element; + *this >> element; + value.append(element); + } + } +} + template inline void Bitstream::writeRawDelta(const QSet& value, const QSet& reference) { int addedOrRemoved = 0; foreach (const T& element, value) { @@ -600,6 +646,27 @@ template inline Bitstream& Bitstream::operator>>(QList& list) { return *this; } +template inline Bitstream& Bitstream::operator<<(const QVector& vector) { + *this << vector.size(); + foreach (const T& entry, vector) { + *this << entry; + } + return *this; +} + +template inline Bitstream& Bitstream::operator>>(QVector& vector) { + int size; + *this >> size; + vector.clear(); + vector.reserve(size); + for (int i = 0; i < size; i++) { + T entry; + *this >> entry; + vector.append(entry); + } + return *this; +} + template inline Bitstream& Bitstream::operator<<(const QSet& set) { *this << set.size(); foreach (const T& entry, set) { @@ -651,13 +718,20 @@ typedef QSharedPointer TypeReaderPointer; class TypeReader { public: - enum Type { SIMPLE_TYPE, STREAMABLE_TYPE, LIST_TYPE, SET_TYPE, MAP_TYPE }; + enum Type { SIMPLE_TYPE, ENUM_TYPE, STREAMABLE_TYPE, LIST_TYPE, SET_TYPE, MAP_TYPE }; - TypeReader(const QByteArray& typeName = QByteArray(), const TypeStreamer* streamer = NULL, bool exactMatch = true, - Type type = SIMPLE_TYPE, const TypeReaderPointer& keyReader = TypeReaderPointer(), - const TypeReaderPointer& valueReader = TypeReaderPointer(), - const QVector& fields = QVector()); + TypeReader(const QByteArray& typeName = QByteArray(), const TypeStreamer* streamer = NULL); + + TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, int bits, const QHash& mappings); + + TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, const QVector& fields); + TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, Type type, + const TypeReaderPointer& valueReader); + + TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, + const TypeReaderPointer& keyReader, const TypeReaderPointer& valueReader); + const QByteArray& getTypeName() const { return _typeName; } const TypeStreamer* getStreamer() const { return _streamer; } @@ -676,6 +750,8 @@ private: const TypeStreamer* _streamer; bool _exactMatch; Type _type; + int _bits; + QHash _mappings; TypeReaderPointer _keyReader; TypeReaderPointer _valueReader; QVector _fields; @@ -683,6 +759,8 @@ private: uint qHash(const TypeReader& typeReader, uint seed = 0); +QDebug& operator<<(QDebug& debug, const TypeReader& typeReader); + /// Contains the information required to read a metatype field from the stream and apply it. class FieldReader { public: @@ -726,6 +804,8 @@ private: uint qHash(const ObjectReader& objectReader, uint seed = 0); +QDebug& operator<<(QDebug& debug, const ObjectReader& objectReader); + /// Contains the information required to read an object property from the stream and apply it. class PropertyReader { public: @@ -743,6 +823,24 @@ private: QMetaProperty _property; }; +/// Contains the information required to obtain an object property and write it to the stream. +class PropertyWriter { +public: + + PropertyWriter(const QMetaProperty& property = QMetaProperty(), const TypeStreamer* streamer = NULL); + + const QMetaProperty& getProperty() const { return _property; } + const TypeStreamer* getStreamer() const { return _streamer; } + + void write(Bitstream& out, const QObject* object) const; + void writeDelta(Bitstream& out, const QObject* object, const QObject* reference) const; + +private: + + QMetaProperty _property; + const TypeStreamer* _streamer; +}; + /// Describes a metatype field. class MetaField { public: @@ -772,6 +870,8 @@ public: void setType(int type) { _type = type; } int getType() const { return _type; } + virtual const char* getName() const; + virtual bool equal(const QVariant& first, const QVariant& second) const = 0; virtual void write(Bitstream& out, const QVariant& value) const = 0; @@ -783,6 +883,8 @@ public: virtual void writeRawDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const = 0; virtual void readRawDelta(Bitstream& in, QVariant& value, const QVariant& reference) const = 0; + virtual void setEnumValue(QVariant& object, int value, const QHash& mappings) const; + virtual const QVector& getMetaFields() const; virtual int getFieldIndex(const QByteArray& name) const; virtual void setField(QVariant& object, int index, const QVariant& value) const; @@ -790,6 +892,9 @@ public: virtual TypeReader::Type getReaderType() const; + virtual int getBits() const; + virtual QMetaEnum getMetaEnum() const; + virtual const TypeStreamer* getKeyStreamer() const; virtual const TypeStreamer* getValueStreamer() const; @@ -808,6 +913,10 @@ private: int _type; }; +QDebug& operator<<(QDebug& debug, const TypeStreamer* typeStreamer); + +QDebug& operator<<(QDebug& debug, const QMetaObject* metaObject); + /// A streamer that works with Bitstream's operators. template class SimpleTypeStreamer : public TypeStreamer { public: @@ -818,11 +927,37 @@ public: virtual void writeDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const { out.writeDelta(value.value(), reference.value()); } virtual void readDelta(Bitstream& in, QVariant& value, const QVariant& reference) const { - in.readDelta(*static_cast(value.data()), reference.value()); } + T rawValue; in.readDelta(rawValue, reference.value()); value = QVariant::fromValue(rawValue); } virtual void writeRawDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const { out.writeRawDelta(value.value(), reference.value()); } virtual void readRawDelta(Bitstream& in, QVariant& value, const QVariant& reference) const { - in.readRawDelta(*static_cast(value.data()), reference.value()); } + T rawValue; in.readRawDelta(rawValue, reference.value()); value = QVariant::fromValue(rawValue); } +}; + +/// A streamer class for enumerated types. +class EnumTypeStreamer : public TypeStreamer { +public: + + EnumTypeStreamer(const QMetaEnum& metaEnum); + + virtual const char* getName() const; + virtual TypeReader::Type getReaderType() const; + virtual int getBits() const; + virtual QMetaEnum getMetaEnum() const; + virtual bool equal(const QVariant& first, const QVariant& second) const; + virtual void write(Bitstream& out, const QVariant& value) const; + virtual QVariant read(Bitstream& in) const; + virtual void writeDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const; + virtual void readDelta(Bitstream& in, QVariant& value, const QVariant& reference) const; + virtual void writeRawDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const; + virtual void readRawDelta(Bitstream& in, QVariant& value, const QVariant& reference) const; + virtual void setEnumValue(QVariant& object, int value, const QHash& mappings) const; + +private: + + QMetaEnum _metaEnum; + QByteArray _name; + int _bits; }; /// A streamer for types compiled by mtc. @@ -858,6 +993,22 @@ public: static_cast*>(object.data())->replace(index, value.value()); } }; +/// A streamer for vector types. +template class CollectionTypeStreamer > : public SimpleTypeStreamer > { +public: + + virtual TypeReader::Type getReaderType() const { return TypeReader::LIST_TYPE; } + virtual const TypeStreamer* getValueStreamer() const { return Bitstream::getTypeStreamer(qMetaTypeId()); } + virtual void insert(QVariant& object, const QVariant& value) const { + static_cast*>(object.data())->append(value.value()); } + virtual void prune(QVariant& object, int size) const { + QVector* list = static_cast*>(object.data()); list->erase(list->begin() + size, list->end()); } + virtual QVariant getValue(const QVariant& object, int index) const { + return QVariant::fromValue(static_cast*>(object.constData())->at(index)); } + virtual void setValue(QVariant& object, int index, const QVariant& value) const { + static_cast*>(object.data())->replace(index, value.value()); } +}; + /// A streamer for set types. template class CollectionTypeStreamer > : public SimpleTypeStreamer > { public: @@ -940,6 +1091,13 @@ template int registerStreamableMetaType() { return type; } +/// Registers a collection type and its streamer. +template int registerCollectionMetaType() { + int type = qRegisterMetaType(); + Bitstream::registerTypeStreamer(type, new CollectionTypeStreamer()); + return type; +} + /// Flags a class as streamable (use as you would Q_OBJECT). #define STREAMABLE public: \ static const int Type; \ diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index ce9f36ba33..cf6ded74da 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -19,7 +19,7 @@ #include #include -#include "Bitstream.h" +#include "AttributeRegistry.h" class ReliableChannel; diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp index b578d70959..47d69f4abe 100644 --- a/libraries/metavoxels/src/SharedObject.cpp +++ b/libraries/metavoxels/src/SharedObject.cpp @@ -23,7 +23,9 @@ REGISTER_META_OBJECT(SharedObject) SharedObject::SharedObject() : _id(++_lastID), - _remoteID(0) { + _originID(_id), + _remoteID(0), + _remoteOriginID(0) { _weakHash.insert(_id, this); } @@ -39,26 +41,33 @@ void SharedObject::decrementReferenceCount() { } } -SharedObject* SharedObject::clone(bool withID) const { +SharedObject* SharedObject::clone(bool withID, SharedObject* target) const { // default behavior is to make a copy using the no-arg constructor and copy the stored properties const QMetaObject* metaObject = this->metaObject(); - SharedObject* newObject = static_cast(metaObject->newInstance()); + if (!target) { + target = static_cast(metaObject->newInstance()); + } for (int i = 0; i < metaObject->propertyCount(); i++) { QMetaProperty property = metaObject->property(i); if (property.isStored()) { - property.write(newObject, property.read(this)); + if (property.userType() == qMetaTypeId()) { + SharedObject* value = property.read(this).value().data(); + property.write(target, QVariant::fromValue(value ? value->clone(withID) : value)); + } else { + property.write(target, property.read(this)); + } } } foreach (const QByteArray& propertyName, dynamicPropertyNames()) { - newObject->setProperty(propertyName, property(propertyName)); + target->setProperty(propertyName, property(propertyName)); } if (withID) { - newObject->setID(_id); + target->setOriginID(_originID); } - return newObject; + return target; } -bool SharedObject::equals(const SharedObject* other) const { +bool SharedObject::equals(const SharedObject* other, bool sharedAncestry) const { if (!other) { return false; } @@ -67,7 +76,7 @@ bool SharedObject::equals(const SharedObject* other) const { } // default behavior is to compare the properties const QMetaObject* metaObject = this->metaObject(); - if (metaObject != other->metaObject()) { + if (metaObject != other->metaObject() && !sharedAncestry) { return false; } for (int i = 0; i < metaObject->propertyCount(); i++) { @@ -92,13 +101,15 @@ void SharedObject::dump(QDebug debug) const { debug << this; const QMetaObject* metaObject = this->metaObject(); for (int i = 0; i < metaObject->propertyCount(); i++) { - debug << metaObject->property(i).name() << metaObject->property(i).read(this); + QMetaProperty property = metaObject->property(i); + if (property.isStored()) { + debug << property.name() << property.read(this); + } + } + QList dynamicPropertyNames = this->dynamicPropertyNames(); + foreach (const QByteArray& propertyName, dynamicPropertyNames) { + debug << propertyName << property(propertyName); } -} - -void SharedObject::setID(int id) { - _weakHash.remove(_id); - _weakHash.insert(_id = id, this); } int SharedObject::_lastID = 0; diff --git a/libraries/metavoxels/src/SharedObject.h b/libraries/metavoxels/src/SharedObject.h index aba6b86bea..41c3c01ffe 100644 --- a/libraries/metavoxels/src/SharedObject.h +++ b/libraries/metavoxels/src/SharedObject.h @@ -41,31 +41,44 @@ public: /// Returns the unique local ID for this object. int getID() const { return _id; } + /// Returns the local origin ID for this object. + int getOriginID() const { return _originID; } + + void setOriginID(int originID) { _originID = originID; } + /// Returns the unique remote ID for this object, or zero if this is a local object. int getRemoteID() const { return _remoteID; } void setRemoteID(int remoteID) { _remoteID = remoteID; } + /// Returns the remote origin ID for this object, or zero if this is a local object. + int getRemoteOriginID() const { return _remoteOriginID; } + + void setRemoteOriginID(int remoteOriginID) { _remoteOriginID = remoteOriginID; } + int getReferenceCount() const { return _referenceCount.load(); } void incrementReferenceCount(); void decrementReferenceCount(); /// Creates a new clone of this object. - /// \param withID if true, give the clone the same ID as this object - virtual SharedObject* clone(bool withID = false) const; + /// \param withID if true, give the clone the same origin ID as this object + /// \target if non-NULL, a target object to populate (as opposed to creating a new instance of this object's class) + virtual SharedObject* clone(bool withID = false, SharedObject* target = NULL) const; /// Tests this object for equality with another. - virtual bool equals(const SharedObject* other) const; + /// \param sharedAncestry if true and the classes of the objects differ, compare their shared ancestry (assuming that + /// this is an instance of a superclass of the other object's class) rather than simply returning false. + virtual bool equals(const SharedObject* other, bool sharedAncestry = false) const; // Dumps the contents of this object to the debug output. virtual void dump(QDebug debug = QDebug(QtDebugMsg)) const; private: - void setID(int id); - int _id; + int _originID; int _remoteID; + int _remoteOriginID; QAtomicInt _referenceCount; static int _lastID; diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index b87557d073..b0c7e125b4 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -50,11 +50,16 @@ ModelTreeElement* ModelTreeElement::addChildAtIndex(int index) { } +// TODO: This will attempt to store as many models as will fit in the packetData, if an individual model won't +// fit, but some models did fit, then the element outputs what can fit. Once the general Octree::encodeXXX() +// process supports partial encoding of an octree element, this will need to be updated to handle spanning its +// contents across multiple packets. bool ModelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { bool success = true; // assume the best... // write our models out... first determine which of the models are in view based on our params uint16_t numberOfModels = 0; + uint16_t actualNumberOfModels = 0; QVector indexesOfModelsToInclude; for (uint16_t i = 0; i < _modelItems->size(); i++) { @@ -72,17 +77,33 @@ bool ModelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBit } } + int numberOfModelsOffset = packetData->getUncompressedByteOffset(); success = packetData->appendValue(numberOfModels); if (success) { foreach (uint16_t i, indexesOfModelsToInclude) { const ModelItem& model = (*_modelItems)[i]; + + LevelDetails modelLevel = packetData->startLevel(); + success = model.appendModelData(packetData); + + if (success) { + packetData->endLevel(modelLevel); + actualNumberOfModels++; + } if (!success) { + packetData->discardLevel(modelLevel); break; } } } + + if (!success) { + success = packetData->updatePriorBytes(numberOfModelsOffset, + (const unsigned char*)&actualNumberOfModels, sizeof(actualNumberOfModels)); + } + return success; } @@ -433,6 +454,7 @@ int ModelTreeElement::readElementDataFromBuffer(const unsigned char* data, int b if (bytesLeftToRead >= (int)sizeof(numberOfModels)) { // read our models in.... numberOfModels = *(uint16_t*)dataAt; + dataAt += sizeof(numberOfModels); bytesLeftToRead -= (int)sizeof(numberOfModels); bytesRead += sizeof(numberOfModels); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 05f425374b..360172efde 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -57,7 +57,8 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, _linkedData(NULL), _isAlive(true), _clockSkewUsec(0), - _mutex() + _mutex(), + _clockSkewMovingPercentile(30, 0.8f) // moving 80th percentile of 30 samples { } @@ -133,6 +134,11 @@ float Node::getAverageKilobitsPerSecond() { } } +void Node::updateClockSkewUsec(int clockSkewSample) { + _clockSkewMovingPercentile.updatePercentile((float)clockSkewSample); + _clockSkewUsec = (int)_clockSkewMovingPercentile.getValueAtPercentile(); +} + QDataStream& operator<<(QDataStream& out, const Node& node) { out << node._type; out << node._uuid; diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index f52cda0d0d..85fe2e4458 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -23,6 +23,7 @@ #include "HifiSockAddr.h" #include "NodeData.h" #include "SimpleMovingAverage.h" +#include "MovingPercentile.h" typedef quint8 NodeType_t; @@ -94,7 +95,7 @@ public: void setPingMs(int pingMs) { _pingMs = pingMs; } int getClockSkewUsec() const { return _clockSkewUsec; } - void setClockSkewUsec(int clockSkew) { _clockSkewUsec = clockSkew; } + void updateClockSkewUsec(int clockSkewSample); QMutex& getMutex() { return _mutex; } friend QDataStream& operator<<(QDataStream& out, const Node& node); @@ -120,6 +121,7 @@ private: int _pingMs; int _clockSkewUsec; QMutex _mutex; + MovingPercentile _clockSkewMovingPercentile; }; QDebug operator<<(QDebug debug, const Node &message); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index ebd33ef132..9a298ce26c 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -96,8 +96,8 @@ void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& int clockSkew = othersReplyTime - othersExprectedReply; sendingNode->setPingMs(pingTime / 1000); - sendingNode->setClockSkewUsec(clockSkew); - + sendingNode->updateClockSkewUsec(clockSkew); + const bool wantDebug = false; if (wantDebug) { diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 3ec1871023..cbdc4753dc 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -602,7 +602,6 @@ public: bool findRayIntersectionOp(OctreeElement* element, void* extraData) { RayArgs* args = static_cast(extraData); - bool keepSearching = true; if (element->findRayIntersection(args->origin, args->direction, keepSearching, args->element, args->distance, args->face, args->intersectedObject)) { @@ -1336,14 +1335,23 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, } } - // write the color data... + // write the child element data... if (continueThisLevel && params.includeColor) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { if (oneAtBit(childrenColoredBits, i)) { OctreeElement* childElement = element->getChildAtIndex(i); if (childElement) { + int bytesBeforeChild = packetData->getUncompressedSize(); + + // TODO: we want to support the ability for a childElement to "partially" write it's data. + // for example, consider the case of the model server where the entire contents of the + // element may be larger than can fit in a single MTU/packetData. In this case, we want + // to allow the appendElementData() to respond that it produced partial data, which should be + // written, but that the childElement needs to be reprocessed in an additional pass or passes + // to be completed. In the case that an element was partially written, we need to continueThisLevel = childElement->appendElementData(packetData, params); + int bytesAfterChild = packetData->getUncompressedSize(); if (!continueThisLevel) { diff --git a/libraries/shared/src/MovingPercentile.cpp b/libraries/shared/src/MovingPercentile.cpp new file mode 100644 index 0000000000..ec007b5c22 --- /dev/null +++ b/libraries/shared/src/MovingPercentile.cpp @@ -0,0 +1,61 @@ +// +// MovingPercentile.cpp +// libraries/shared/src +// +// Created by Yixin Wang on 6/4/2014 +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MovingPercentile.h" + +MovingPercentile::MovingPercentile(int numSamples, float percentile) + : _numSamples(numSamples), + _percentile(percentile), + _samplesSorted(), + _sampleIds(), + _newSampleId(0), + _indexOfPercentile(0), + _valueAtPercentile(0.0f) +{ +} + +void MovingPercentile::updatePercentile(float sample) { + + // insert the new sample into _samplesSorted + int newSampleIndex; + if (_samplesSorted.size() < _numSamples) { + // if not all samples have been filled yet, simply append it + newSampleIndex = _samplesSorted.size(); + _samplesSorted.append(sample); + _sampleIds.append(_newSampleId); + + // update _indexOfPercentile + float index = _percentile * (float)(_samplesSorted.size() - 1); + _indexOfPercentile = (int)(index + 0.5f); // round to int + } else { + // find index of sample with id = _newSampleId and replace it with new sample + newSampleIndex = _sampleIds.indexOf(_newSampleId); + _samplesSorted[newSampleIndex] = sample; + } + + // increment _newSampleId. cycles from 0 thru N-1 + _newSampleId = (_newSampleId == _numSamples - 1) ? 0 : _newSampleId + 1; + + // swap new sample with neighbors in _samplesSorted until it's in sorted order + // try swapping up first, then down. element will only be swapped one direction. + while (newSampleIndex < _samplesSorted.size() - 1 && sample > _samplesSorted[newSampleIndex + 1]) { + _samplesSorted.swap(newSampleIndex, newSampleIndex + 1); + _sampleIds.swap(newSampleIndex, newSampleIndex + 1); + newSampleIndex++; + } + while (newSampleIndex > 0 && sample < _samplesSorted[newSampleIndex - 1]) { + _samplesSorted.swap(newSampleIndex, newSampleIndex - 1); + _sampleIds.swap(newSampleIndex, newSampleIndex - 1); + newSampleIndex--; + } + + // find new value at percentile + _valueAtPercentile = _samplesSorted[_indexOfPercentile]; +} diff --git a/libraries/shared/src/MovingPercentile.h b/libraries/shared/src/MovingPercentile.h new file mode 100644 index 0000000000..284ed9d890 --- /dev/null +++ b/libraries/shared/src/MovingPercentile.h @@ -0,0 +1,36 @@ +// +// MovingPercentile.h +// libraries/shared/src +// +// Created by Yixin Wang on 6/4/2014 +// +// 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_MovingPercentile_h +#define hifi_MovingPercentile_h + +#include + +class MovingPercentile { + +public: + MovingPercentile(int numSamples, float percentile = 0.5f); + + void updatePercentile(float sample); + float getValueAtPercentile() const { return _valueAtPercentile; } + +private: + const int _numSamples; + const float _percentile; + + QList _samplesSorted; + QList _sampleIds; // incrementally assigned, is cyclic + int _newSampleId; + + int _indexOfPercentile; + float _valueAtPercentile; +}; + +#endif diff --git a/libraries/shared/src/PropertyFlags.h b/libraries/shared/src/PropertyFlags.h new file mode 100644 index 0000000000..b9253379c6 --- /dev/null +++ b/libraries/shared/src/PropertyFlags.h @@ -0,0 +1,418 @@ +// +// PropertyFlags.h +// libraries/shared/src +// +// Created by Brad Hefta-Gaub on 6/3/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 +// +// +// TODO: +// * consider adding iterator to enumerate the properties that have been set? + +#ifndef hifi_PropertyFlags_h +#define hifi_PropertyFlags_h + +#include +#include + +#include +#include + +#include + +templateclass PropertyFlags { +public: + typedef Enum enum_type; + inline PropertyFlags() : + _maxFlag(INT_MIN), _minFlag(INT_MAX), _trailingFlipped(false) { }; + inline PropertyFlags(const PropertyFlags& other) : + _flags(other._flags), _maxFlag(other._maxFlag), _minFlag(other._minFlag), + _trailingFlipped(other._trailingFlipped) {} + inline PropertyFlags(Enum flag) : + _maxFlag(INT_MIN), _minFlag(INT_MAX), _trailingFlipped(false) { setHasProperty(flag); } + + void clear() { _flags.clear(); _maxFlag = INT_MIN; _minFlag = INT_MAX; _trailingFlipped = false; } + + Enum firstFlag() const { return (Enum)_minFlag; } + Enum lastFlag() const { return (Enum)_maxFlag; } + + void setHasProperty(Enum flag, bool value = true); + bool getHasProperty(Enum flag); + QByteArray encode(); + void decode(const QByteArray& fromEncoded); + + + bool operator==(const PropertyFlags& other) const { return _flags == other._flags; } + bool operator!=(const PropertyFlags& other) const { return _flags != other._flags; } + bool operator!() const { return _flags.size() == 0; } + + PropertyFlags& operator=(const PropertyFlags& other); + + PropertyFlags& operator|=(PropertyFlags other); + PropertyFlags& operator|=(Enum flag); + + PropertyFlags& operator&=(PropertyFlags other); + PropertyFlags& operator&=(Enum flag); + + PropertyFlags& operator+=(PropertyFlags other); + PropertyFlags& operator+=(Enum flag); + + PropertyFlags& operator-=(PropertyFlags other); + PropertyFlags& operator-=(Enum flag); + + PropertyFlags& operator<<=(PropertyFlags other); + PropertyFlags& operator<<=(Enum flag); + + PropertyFlags operator|(PropertyFlags other) const; + PropertyFlags operator|(Enum flag) const; + + PropertyFlags operator&(PropertyFlags other) const; + PropertyFlags operator&(Enum flag) const; + + PropertyFlags operator+(PropertyFlags other) const; + PropertyFlags operator+(Enum flag) const; + + PropertyFlags operator-(PropertyFlags other) const; + PropertyFlags operator-(Enum flag) const; + + PropertyFlags operator<<(PropertyFlags other) const; + PropertyFlags operator<<(Enum flag) const; + + // NOTE: due to the nature of the compact storage of these property flags, and the fact that the upper bound of the + // enum is not know, these operators will only perform their bitwise operations on the set of properties that have + // been previously set + PropertyFlags& operator^=(PropertyFlags other); + PropertyFlags& operator^=(Enum flag); + PropertyFlags operator^(PropertyFlags other) const; + PropertyFlags operator^(Enum flag) const; + PropertyFlags operator~() const; + + void debugDumpBits(); + + +private: + void shinkIfNeeded(); + + QBitArray _flags; + int _maxFlag; + int _minFlag; + bool _trailingFlipped; /// are the trailing properties flipping in their state (e.g. assumed true, instead of false) +}; + +template PropertyFlags& operator<<(PropertyFlags& out, const PropertyFlags& other) { + return out <<= other; +} + +template PropertyFlags& operator<<(PropertyFlags& out, Enum flag) { + return out <<= flag; +} + + +template inline void PropertyFlags::setHasProperty(Enum flag, bool value) { + // keep track of our min flag + if (flag < _minFlag) { + if (value) { + _minFlag = flag; + } + } + if (flag > _maxFlag) { + if (value) { + _maxFlag = flag; + _flags.resize(_maxFlag + 1); + } else { + return; // bail early, we're setting a flag outside of our current _maxFlag to false, which is already the default + } + } + _flags.setBit(flag, value); + + if (flag == _maxFlag && !value) { + shinkIfNeeded(); + } +} + +template inline bool PropertyFlags::getHasProperty(Enum flag) { + if (flag > _maxFlag) { + return _trailingFlipped; // usually false + } + return _flags.testBit(flag); +} + +const int BITS_PER_BYTE = 8; + +template inline QByteArray PropertyFlags::encode() { + QByteArray output; + + if (_maxFlag < _minFlag) { + output.fill(0, 1); + return output; // no flags... nothing to encode + } + + // we should size the array to the correct size. + int lengthInBytes = (_maxFlag / (BITS_PER_BYTE - 1)) + 1; + + output.fill(0, lengthInBytes); + + // next pack the number of header bits in, the first N-1 to be set to 1, the last to be set to 0 + for(int i = 0; i < lengthInBytes; i++) { + int outputIndex = i; + int bitValue = (i < (lengthInBytes - 1) ? 1 : 0); + char original = output.at(outputIndex / BITS_PER_BYTE); + int shiftBy = BITS_PER_BYTE - ((outputIndex % BITS_PER_BYTE) + 1); + char thisBit = ( bitValue << shiftBy); + output[i / BITS_PER_BYTE] = (original | thisBit); + } + + // finally pack the the actual bits from the bit array + for(int i = lengthInBytes; i < (lengthInBytes + _maxFlag + 1); i++) { + int flagIndex = i - lengthInBytes; + int outputIndex = i; + int bitValue = ( _flags[flagIndex] ? 1 : 0); + char original = output.at(outputIndex / BITS_PER_BYTE); + int shiftBy = BITS_PER_BYTE - ((outputIndex % BITS_PER_BYTE) + 1); + char thisBit = ( bitValue << shiftBy); + output[i / BITS_PER_BYTE] = (original | thisBit); + } + return output; +} + +template inline void PropertyFlags::decode(const QByteArray& fromEncodedBytes) { + + clear(); // we are cleared out! + + // first convert the ByteArray into a BitArray... + QBitArray encodedBits; + int bitCount = BITS_PER_BYTE * fromEncodedBytes.count(); + encodedBits.resize(bitCount); + + for(int byte = 0; byte < fromEncodedBytes.count(); byte++) { + char originalByte = fromEncodedBytes.at(byte); + for(int bit = 0; bit < BITS_PER_BYTE; bit++) { + int shiftBy = BITS_PER_BYTE - (bit + 1); + char maskBit = ( 1 << shiftBy); + bool bitValue = originalByte & maskBit; + encodedBits.setBit(byte * BITS_PER_BYTE + bit, bitValue); + } + } + + // next, read the leading bits to determine the correct number of bytes to decode (may not match the QByteArray) + int encodedByteCount = 0; + int bitAt; + for (bitAt = 0; bitAt < bitCount; bitAt++) { + if (encodedBits.at(bitAt)) { + encodedByteCount++; + } else { + break; + } + } + encodedByteCount++; // always at least one byte + int expectedBitCount = encodedByteCount * BITS_PER_BYTE; + + // Now, keep reading... + int flagsStartAt = bitAt + 1; + for (bitAt = flagsStartAt; bitAt < expectedBitCount; bitAt++) { + if (encodedBits.at(bitAt)) { + setHasProperty((Enum)(bitAt - flagsStartAt)); + } + } +} + +template inline void PropertyFlags::debugDumpBits() { + qDebug() << "_minFlag=" << _minFlag; + qDebug() << "_maxFlag=" << _maxFlag; + qDebug() << "_trailingFlipped=" << _trailingFlipped; + for(int i = 0; i < _flags.size(); i++) { + qDebug() << "bit[" << i << "]=" << _flags.at(i); + } +} + + +template inline PropertyFlags& PropertyFlags::operator=(const PropertyFlags& other) { + _flags = other._flags; + _maxFlag = other._maxFlag; + _minFlag = other._minFlag; + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator|=(PropertyFlags other) { + _flags |= other._flags; + _maxFlag = std::max(_maxFlag, other._maxFlag); + _minFlag = std::min(_minFlag, other._minFlag); + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator|=(Enum flag) { + PropertyFlags other(flag); + _flags |= other._flags; + _maxFlag = std::max(_maxFlag, other._maxFlag); + _minFlag = std::min(_minFlag, other._minFlag); + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator&=(PropertyFlags other) { + _flags &= other._flags; + shinkIfNeeded(); + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator&=(Enum flag) { + PropertyFlags other(flag); + _flags &= other._flags; + shinkIfNeeded(); + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator^=(PropertyFlags other) { + _flags ^= other._flags; + shinkIfNeeded(); + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator^=(Enum flag) { + PropertyFlags other(flag); + _flags ^= other._flags; + shinkIfNeeded(); + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator+=(PropertyFlags other) { + for(int flag = (int)other.firstFlag(); flag <= (int)other.lastFlag(); flag++) { + if (other.getHasProperty((Enum)flag)) { + setHasProperty((Enum)flag, true); + } + } + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator+=(Enum flag) { + setHasProperty(flag, true); + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator-=(PropertyFlags other) { + for(int flag = (int)other.firstFlag(); flag <= (int)other.lastFlag(); flag++) { + if (other.getHasProperty((Enum)flag)) { + setHasProperty((Enum)flag, false); + } + } + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator-=(Enum flag) { + setHasProperty(flag, false); + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator<<=(PropertyFlags other) { + for(int flag = (int)other.firstFlag(); flag <= (int)other.lastFlag(); flag++) { + if (other.getHasProperty((Enum)flag)) { + setHasProperty((Enum)flag, true); + } + } + return *this; +} + +template inline PropertyFlags& PropertyFlags::operator<<=(Enum flag) { + setHasProperty(flag, true); + return *this; +} + +template inline PropertyFlags PropertyFlags::operator|(PropertyFlags other) const { + PropertyFlags result(*this); + result |= other; + return result; +} + +template inline PropertyFlags PropertyFlags::operator|(Enum flag) const { + PropertyFlags result(*this); + PropertyFlags other(flag); + result |= other; + return result; +} + +template inline PropertyFlags PropertyFlags::operator&(PropertyFlags other) const { + PropertyFlags result(*this); + result &= other; + return result; +} + +template inline PropertyFlags PropertyFlags::operator&(Enum flag) const { + PropertyFlags result(*this); + PropertyFlags other(flag); + result &= other; + return result; +} + +template inline PropertyFlags PropertyFlags::operator^(PropertyFlags other) const { + PropertyFlags result(*this); + result ^= other; + return result; +} + +template inline PropertyFlags PropertyFlags::operator^(Enum flag) const { + PropertyFlags result(*this); + PropertyFlags other(flag); + result ^= other; + return result; +} + +template inline PropertyFlags PropertyFlags::operator+(PropertyFlags other) const { + PropertyFlags result(*this); + result += other; + return result; +} + +template inline PropertyFlags PropertyFlags::operator+(Enum flag) const { + PropertyFlags result(*this); + result.setHasProperty(flag, true); + return result; +} + +template inline PropertyFlags PropertyFlags::operator-(PropertyFlags other) const { + PropertyFlags result(*this); + result -= other; + return result; +} + +template inline PropertyFlags PropertyFlags::operator-(Enum flag) const { + PropertyFlags result(*this); + result.setHasProperty(flag, false); + return result; +} + +template inline PropertyFlags PropertyFlags::operator<<(PropertyFlags other) const { + PropertyFlags result(*this); + result <<= other; + return result; +} + +template inline PropertyFlags PropertyFlags::operator<<(Enum flag) const { + PropertyFlags result(*this); + result.setHasProperty(flag, true); + return result; +} + +template inline PropertyFlags PropertyFlags::operator~() const { + PropertyFlags result(*this); + result._flags = ~_flags; + result._trailingFlipped = !_trailingFlipped; + return result; +} + +template inline void PropertyFlags::shinkIfNeeded() { + bool maxFlagWas = _maxFlag; + while (_maxFlag >= 0) { + if (_flags.testBit(_maxFlag)) { + break; + } + _maxFlag--; + } + if (maxFlagWas != _maxFlag) { + _flags.resize(_maxFlag + 1); + } +} + +#endif // hifi_PropertyFlags_h + diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 2400e086eb..603f63b587 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -51,6 +51,29 @@ static QByteArray createRandomBytes() { return createRandomBytes(MIN_BYTES, MAX_BYTES); } +static TestSharedObjectA::TestEnum getRandomTestEnum() { + switch (randIntInRange(0, 2)) { + case 0: return TestSharedObjectA::FIRST_TEST_ENUM; + case 1: return TestSharedObjectA::SECOND_TEST_ENUM; + case 2: + default: return TestSharedObjectA::THIRD_TEST_ENUM; + } +} + +static TestSharedObjectA::TestFlags getRandomTestFlags() { + TestSharedObjectA::TestFlags flags = 0; + if (randomBoolean()) { + flags |= TestSharedObjectA::FIRST_TEST_FLAG; + } + if (randomBoolean()) { + flags |= TestSharedObjectA::SECOND_TEST_FLAG; + } + if (randomBoolean()) { + flags |= TestSharedObjectA::THIRD_TEST_FLAG; + } + return flags; +} + static TestMessageC createRandomMessageC() { TestMessageC message; message.foo = randomBoolean(); @@ -64,9 +87,11 @@ static bool testSerialization(Bitstream::MetadataType metadataType) { QByteArray array; QDataStream outStream(&array, QIODevice::WriteOnly); Bitstream out(outStream, metadataType); - SharedObjectPointer testObjectWrittenA = new TestSharedObjectA(randFloat()); + SharedObjectPointer testObjectWrittenA = new TestSharedObjectA(randFloat(), TestSharedObjectA::SECOND_TEST_ENUM, + TestSharedObjectA::TestFlags(TestSharedObjectA::FIRST_TEST_FLAG | TestSharedObjectA::THIRD_TEST_FLAG)); out << testObjectWrittenA; - SharedObjectPointer testObjectWrittenB = new TestSharedObjectB(randFloat(), createRandomBytes()); + SharedObjectPointer testObjectWrittenB = new TestSharedObjectB(randFloat(), createRandomBytes(), + TestSharedObjectB::THIRD_TEST_ENUM, TestSharedObjectB::SECOND_TEST_FLAG); out << testObjectWrittenB; TestMessageC messageWritten = createRandomMessageC(); out << QVariant::fromValue(messageWritten); @@ -79,6 +104,10 @@ static bool testSerialization(Bitstream::MetadataType metadataType) { in.addMetaObjectSubstitution("TestSharedObjectA", &TestSharedObjectB::staticMetaObject); in.addMetaObjectSubstitution("TestSharedObjectB", &TestSharedObjectA::staticMetaObject); in.addTypeSubstitution("TestMessageC", TestMessageA::Type); + in.addTypeSubstitution("TestSharedObjectA::TestEnum", "TestSharedObjectB::TestEnum"); + in.addTypeSubstitution("TestSharedObjectB::TestEnum", "TestSharedObjectA::TestEnum"); + in.addTypeSubstitution("TestSharedObjectA::TestFlags", "TestSharedObjectB::TestFlags"); + in.addTypeSubstitution("TestSharedObjectB::TestFlags", "TestSharedObjectA::TestFlags"); SharedObjectPointer testObjectReadA; in >> testObjectReadA; @@ -86,8 +115,11 @@ static bool testSerialization(Bitstream::MetadataType metadataType) { qDebug() << "Wrong class for A" << testObjectReadA << metadataType; return true; } - if (metadataType == Bitstream::FULL_METADATA && static_cast(testObjectWrittenA.data())->getFoo() != - static_cast(testObjectReadA.data())->getFoo()) { + if (metadataType == Bitstream::FULL_METADATA && (static_cast(testObjectWrittenA.data())->getFoo() != + static_cast(testObjectReadA.data())->getFoo() || + static_cast(testObjectReadA.data())->getBaz() != TestSharedObjectB::SECOND_TEST_ENUM || + static_cast(testObjectReadA.data())->getBong() != + TestSharedObjectB::TestFlags(TestSharedObjectB::FIRST_TEST_FLAG | TestSharedObjectB::THIRD_TEST_FLAG))) { QDebug debug = qDebug() << "Failed to transfer shared field from A to B"; testObjectWrittenA->dump(debug); testObjectReadA->dump(debug); @@ -100,8 +132,10 @@ static bool testSerialization(Bitstream::MetadataType metadataType) { qDebug() << "Wrong class for B" << testObjectReadB << metadataType; return true; } - if (metadataType == Bitstream::FULL_METADATA && static_cast(testObjectWrittenB.data())->getFoo() != - static_cast(testObjectReadB.data())->getFoo()) { + if (metadataType == Bitstream::FULL_METADATA && (static_cast(testObjectWrittenB.data())->getFoo() != + static_cast(testObjectReadB.data())->getFoo() || + static_cast(testObjectReadB.data())->getBaz() != TestSharedObjectA::THIRD_TEST_ENUM || + static_cast(testObjectReadB.data())->getBong() != TestSharedObjectA::SECOND_TEST_FLAG)) { QDebug debug = qDebug() << "Failed to transfer shared field from B to A"; testObjectWrittenB->dump(debug); testObjectReadB->dump(debug); @@ -175,7 +209,7 @@ bool MetavoxelTests::run() { static SharedObjectPointer createRandomSharedObject() { switch (randIntInRange(0, 2)) { - case 0: return new TestSharedObjectA(randFloat()); + case 0: return new TestSharedObjectA(randFloat(), getRandomTestEnum(), getRandomTestFlags()); case 1: return new TestSharedObjectB(); case 2: default: return SharedObjectPointer(); @@ -393,8 +427,10 @@ void Endpoint::readReliableChannel() { streamedBytesReceived += bytes.size(); } -TestSharedObjectA::TestSharedObjectA(float foo) : - _foo(foo) { +TestSharedObjectA::TestSharedObjectA(float foo, TestEnum baz, TestFlags bong) : + _foo(foo), + _baz(baz), + _bong(bong) { sharedObjectsCreated++; } @@ -408,9 +444,11 @@ void TestSharedObjectA::setFoo(float foo) { } } -TestSharedObjectB::TestSharedObjectB(float foo, const QByteArray& bar) : +TestSharedObjectB::TestSharedObjectB(float foo, const QByteArray& bar, TestEnum baz, TestFlags bong) : _foo(foo), - _bar(bar) { + _bar(bar), + _baz(baz), + _bong(bong) { sharedObjectsCreated++; } diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index 206c818c6e..5e020b1e60 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -70,16 +70,31 @@ private: /// A simple shared object. class TestSharedObjectA : public SharedObject { Q_OBJECT + Q_ENUMS(TestEnum) + Q_FLAGS(TestFlag TestFlags) Q_PROPERTY(float foo READ getFoo WRITE setFoo NOTIFY fooChanged) + Q_PROPERTY(TestEnum baz READ getBaz WRITE setBaz) + Q_PROPERTY(TestFlags bong READ getBong WRITE setBong) public: - Q_INVOKABLE TestSharedObjectA(float foo = 0.0f); + enum TestEnum { FIRST_TEST_ENUM, SECOND_TEST_ENUM, THIRD_TEST_ENUM }; + + enum TestFlag { NO_TEST_FLAGS = 0x0, FIRST_TEST_FLAG = 0x01, SECOND_TEST_FLAG = 0x02, THIRD_TEST_FLAG = 0x04 }; + Q_DECLARE_FLAGS(TestFlags, TestFlag) + + Q_INVOKABLE TestSharedObjectA(float foo = 0.0f, TestEnum baz = FIRST_TEST_ENUM, TestFlags bong = 0); virtual ~TestSharedObjectA(); void setFoo(float foo); float getFoo() const { return _foo; } + void setBaz(TestEnum baz) { _baz = baz; } + TestEnum getBaz() const { return _baz; } + + void setBong(TestFlags bong) { _bong = bong; } + TestFlags getBong() const { return _bong; } + signals: void fooChanged(float foo); @@ -87,17 +102,30 @@ signals: private: float _foo; + TestEnum _baz; + TestFlags _bong; }; /// Another simple shared object. class TestSharedObjectB : public SharedObject { Q_OBJECT + Q_ENUMS(TestEnum) + Q_FLAGS(TestFlag TestFlags) Q_PROPERTY(float foo READ getFoo WRITE setFoo) Q_PROPERTY(QByteArray bar READ getBar WRITE setBar) - + Q_PROPERTY(TestEnum baz READ getBaz WRITE setBaz) + Q_PROPERTY(TestFlags bong READ getBong WRITE setBong) + public: - Q_INVOKABLE TestSharedObjectB(float foo = 0.0f, const QByteArray& bar = QByteArray()); + enum TestEnum { ZEROTH_TEST_ENUM, FIRST_TEST_ENUM, SECOND_TEST_ENUM, THIRD_TEST_ENUM, FOURTH_TEST_ENUM }; + + enum TestFlag { NO_TEST_FLAGS = 0x0, ZEROTH_TEST_FLAG = 0x01, FIRST_TEST_FLAG = 0x02, + SECOND_TEST_FLAG = 0x04, THIRD_TEST_FLAG = 0x08, FOURTH_TEST_FLAG = 0x10 }; + Q_DECLARE_FLAGS(TestFlags, TestFlag) + + Q_INVOKABLE TestSharedObjectB(float foo = 0.0f, const QByteArray& bar = QByteArray(), + TestEnum baz = FIRST_TEST_ENUM, TestFlags bong = 0); virtual ~TestSharedObjectB(); void setFoo(float foo) { _foo = foo; } @@ -106,10 +134,18 @@ public: void setBar(const QByteArray& bar) { _bar = bar; } const QByteArray& getBar() const { return _bar; } + void setBaz(TestEnum baz) { _baz = baz; } + TestEnum getBaz() const { return _baz; } + + void setBong(TestFlags bong) { _bong = bong; } + TestFlags getBong() const { return _bong; } + private: float _foo; QByteArray _bar; + TestEnum _baz; + TestFlags _bong; }; /// A simple test message. diff --git a/tests/octree/CMakeLists.txt b/tests/octree/CMakeLists.txt new file mode 100644 index 0000000000..cbdfd02054 --- /dev/null +++ b/tests/octree/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 2.8) + +if (WIN32) + cmake_policy (SET CMP0020 NEW) +endif (WIN32) + +set(TARGET_NAME octree-tests) + +set(ROOT_DIR ../..) +set(MACRO_DIR ${ROOT_DIR}/cmake/macros) + +# setup for find modules +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") + +#find_package(Qt5Network REQUIRED) +#find_package(Qt5Script REQUIRED) +#find_package(Qt5Widgets REQUIRED) + +include(${MACRO_DIR}/SetupHifiProject.cmake) +setup_hifi_project(${TARGET_NAME} TRUE) + +include(${MACRO_DIR}/AutoMTC.cmake) +auto_mtc(${TARGET_NAME} ${ROOT_DIR}) + +#qt5_use_modules(${TARGET_NAME} Network Script Widgets) + +#include glm +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} ${ROOT_DIR}) + +# link in the shared libraries +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) +link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) + +IF (WIN32) + #target_link_libraries(${TARGET_NAME} Winmm Ws2_32) +ENDIF(WIN32) + diff --git a/tests/octree/src/OctreeTests.cpp b/tests/octree/src/OctreeTests.cpp new file mode 100644 index 0000000000..ddc3f2c74d --- /dev/null +++ b/tests/octree/src/OctreeTests.cpp @@ -0,0 +1,416 @@ +// +// OctreeTests.h +// tests/physics/src +// +// Created by Brad Hefta-Gaub on 06/04/2014. +// 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 +#include + +#include "OctreeTests.h" + +enum ModelPropertyList { + PROP_PAGED_PROPERTY, + PROP_CUSTOM_PROPERTIES_INCLUDED, + PROP_VISIBLE, + PROP_POSITION, + PROP_RADIUS, + PROP_MODEL_URL, + PROP_ROTATION, + PROP_COLOR, + PROP_SCRIPT, + PROP_ANIMATION_URL, + PROP_ANIMATION_FPS, + PROP_ANIMATION_FRAME_INDEX, + PROP_ANIMATION_PLAYING, + PROP_SHOULD_BE_DELETED +}; + +typedef PropertyFlags ModelPropertyFlags; + +enum ParticlePropertyList { + PARTICLE_PROP_PAGED_PROPERTY, + PARTICLE_PROP_CUSTOM_PROPERTIES_INCLUDED, + PARTICLE_PROP_VISIBLE, + PARTICLE_PROP_POSITION, + PARTICLE_PROP_RADIUS, + PARTICLE_PROP_MODEL_URL, + PARTICLE_PROP_ROTATION, + PARTICLE_PROP_COLOR, + PARTICLE_PROP_SCRIPT, + PARTICLE_PROP_ANIMATION_URL, + PARTICLE_PROP_ANIMATION_FPS, + PARTICLE_PROP_ANIMATION_FRAME_INDEX, + PARTICLE_PROP_ANIMATION_PLAYING, + PARTICLE_PROP_SHOULD_BE_DELETED, + PARTICLE_PROP_VELOCITY, + PARTICLE_PROP_GRAVITY, + PARTICLE_PROP_DAMPING, + PARTICLE_PROP_MASS, + PARTICLE_PROP_LIFETIME, + PARTICLE_PROP_PAUSE_SIMULATION, +}; + +typedef PropertyFlags ParticlePropertyFlags; + + +void OctreeTests::propertyFlagsTests() { + qDebug() << "******************************************************************************************"; + qDebug() << "OctreeTests::propertyFlagsTests()"; + + { + qDebug() << "Test 1: ModelProperties: using setHasProperty()"; + ModelPropertyFlags props; + props.setHasProperty(PROP_VISIBLE); + props.setHasProperty(PROP_POSITION); + props.setHasProperty(PROP_RADIUS); + props.setHasProperty(PROP_MODEL_URL); + props.setHasProperty(PROP_ROTATION); + + QByteArray encoded = props.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + } + + { + qDebug() << "Test 2: ParticlePropertyFlags: using setHasProperty()"; + ParticlePropertyFlags props2; + props2.setHasProperty(PARTICLE_PROP_VISIBLE); + props2.setHasProperty(PARTICLE_PROP_ANIMATION_URL); + props2.setHasProperty(PARTICLE_PROP_ANIMATION_FPS); + props2.setHasProperty(PARTICLE_PROP_ANIMATION_FRAME_INDEX); + props2.setHasProperty(PARTICLE_PROP_ANIMATION_PLAYING); + props2.setHasProperty(PARTICLE_PROP_PAUSE_SIMULATION); + + QByteArray encoded = props2.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + + qDebug() << "Test 2b: remove flag with setHasProperty() PARTICLE_PROP_PAUSE_SIMULATION"; + + props2.setHasProperty(PARTICLE_PROP_PAUSE_SIMULATION, false); + + encoded = props2.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + + } + + { + qDebug() << "Test 3: ParticlePropertyFlags: using | operator"; + ParticlePropertyFlags props; + + props = ParticlePropertyFlags(PARTICLE_PROP_VISIBLE) + | ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_URL) + | ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FPS) + | ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FRAME_INDEX) + | ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_PLAYING) + | ParticlePropertyFlags(PARTICLE_PROP_PAUSE_SIMULATION); + + QByteArray encoded = props.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + + qDebug() << "Test 3b: remove flag with -= PARTICLE_PROP_PAUSE_SIMULATION"; + props -= PARTICLE_PROP_PAUSE_SIMULATION; + + encoded = props.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + } + + { + qDebug() << "Test 3c: ParticlePropertyFlags: using |= operator"; + ParticlePropertyFlags props; + + props |= PARTICLE_PROP_VISIBLE; + props |= PARTICLE_PROP_ANIMATION_URL; + props |= PARTICLE_PROP_ANIMATION_FPS; + props |= PARTICLE_PROP_ANIMATION_FRAME_INDEX; + props |= PARTICLE_PROP_ANIMATION_PLAYING; + props |= PARTICLE_PROP_PAUSE_SIMULATION; + + QByteArray encoded = props.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + } + + { + qDebug() << "Test 4: ParticlePropertyFlags: using + operator"; + ParticlePropertyFlags props; + + props = ParticlePropertyFlags(PARTICLE_PROP_VISIBLE) + + ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_URL) + + ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FPS) + + ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FRAME_INDEX) + + ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_PLAYING) + + ParticlePropertyFlags(PARTICLE_PROP_PAUSE_SIMULATION); + + QByteArray encoded = props.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + } + + { + qDebug() << "Test 4b: ParticlePropertyFlags: using += operator"; + ParticlePropertyFlags props; + + props += PARTICLE_PROP_VISIBLE; + props += PARTICLE_PROP_ANIMATION_URL; + props += PARTICLE_PROP_ANIMATION_FPS; + props += PARTICLE_PROP_ANIMATION_FRAME_INDEX; + props += PARTICLE_PROP_ANIMATION_PLAYING; + props += PARTICLE_PROP_PAUSE_SIMULATION; + + QByteArray encoded = props.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + } + + { + qDebug() << "Test 5: ParticlePropertyFlags: using = ... << operator"; + ParticlePropertyFlags props; + + props = ParticlePropertyFlags(PARTICLE_PROP_VISIBLE) + << ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_URL) + << ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FPS) + << ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FRAME_INDEX) + << ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_PLAYING) + << ParticlePropertyFlags(PARTICLE_PROP_PAUSE_SIMULATION); + + QByteArray encoded = props.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + } + + { + qDebug() << "Test 5b: ParticlePropertyFlags: using <<= operator"; + ParticlePropertyFlags props; + + props <<= PARTICLE_PROP_VISIBLE; + props <<= PARTICLE_PROP_ANIMATION_URL; + props <<= PARTICLE_PROP_ANIMATION_FPS; + props <<= PARTICLE_PROP_ANIMATION_FRAME_INDEX; + props <<= PARTICLE_PROP_ANIMATION_PLAYING; + props <<= PARTICLE_PROP_PAUSE_SIMULATION; + + QByteArray encoded = props.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + } + + { + qDebug() << "Test 5c: ParticlePropertyFlags: using << enum operator"; + ParticlePropertyFlags props; + + props << PARTICLE_PROP_VISIBLE; + props << PARTICLE_PROP_ANIMATION_URL; + props << PARTICLE_PROP_ANIMATION_FPS; + props << PARTICLE_PROP_ANIMATION_FRAME_INDEX; + props << PARTICLE_PROP_ANIMATION_PLAYING; + props << PARTICLE_PROP_PAUSE_SIMULATION; + + QByteArray encoded = props.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + } + + { + qDebug() << "Test 5d: ParticlePropertyFlags: using << flags operator "; + ParticlePropertyFlags props; + ParticlePropertyFlags props2; + + props << PARTICLE_PROP_VISIBLE; + props << PARTICLE_PROP_ANIMATION_URL; + props << PARTICLE_PROP_ANIMATION_FPS; + + props2 << PARTICLE_PROP_ANIMATION_FRAME_INDEX; + props2 << PARTICLE_PROP_ANIMATION_PLAYING; + props2 << PARTICLE_PROP_PAUSE_SIMULATION; + + props << props2; + + QByteArray encoded = props.encode(); + + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + } + + { + qDebug() << "Test 6: ParticlePropertyFlags comparison"; + ParticlePropertyFlags propsA; + + qDebug() << "!propsA:" << (!propsA) << "{ expect true }"; + + propsA << PARTICLE_PROP_VISIBLE; + propsA << PARTICLE_PROP_ANIMATION_URL; + propsA << PARTICLE_PROP_ANIMATION_FPS; + propsA << PARTICLE_PROP_ANIMATION_FRAME_INDEX; + propsA << PARTICLE_PROP_ANIMATION_PLAYING; + propsA << PARTICLE_PROP_PAUSE_SIMULATION; + + qDebug() << "!propsA:" << (!propsA) << "{ expect false }"; + + ParticlePropertyFlags propsB; + qDebug() << "!propsB:" << (!propsB) << "{ expect true }"; + + + propsB << PARTICLE_PROP_VISIBLE; + propsB << PARTICLE_PROP_ANIMATION_URL; + propsB << PARTICLE_PROP_ANIMATION_FPS; + propsB << PARTICLE_PROP_ANIMATION_FRAME_INDEX; + propsB << PARTICLE_PROP_ANIMATION_PLAYING; + propsB << PARTICLE_PROP_PAUSE_SIMULATION; + + qDebug() << "!propsB:" << (!propsB) << "{ expect false }"; + + qDebug() << "propsA == propsB:" << (propsA == propsB) << "{ expect true }"; + qDebug() << "propsA != propsB:" << (propsA != propsB) << "{ expect false }"; + + + qDebug() << "AFTER propsB -= PARTICLE_PROP_PAUSE_SIMULATION..."; + propsB -= PARTICLE_PROP_PAUSE_SIMULATION; + + qDebug() << "propsA == propsB:" << (propsA == propsB) << "{ expect false }"; + qDebug() << "propsA != propsB:" << (propsA != propsB) << "{ expect true }"; + + qDebug() << "AFTER propsB = propsA..."; + propsB = propsA; + + qDebug() << "propsA == propsB:" << (propsA == propsB) << "{ expect true }"; + qDebug() << "propsA != propsB:" << (propsA != propsB) << "{ expect false }"; + + } + + { + qDebug() << "Test 7: ParticlePropertyFlags testing individual properties"; + ParticlePropertyFlags props; + + qDebug() << "ParticlePropertyFlags props;"; + QByteArray encoded = props.encode(); + qDebug() << "props... encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + + qDebug() << "props.getHasProperty(PARTICLE_PROP_VISIBLE)" << (props.getHasProperty(PARTICLE_PROP_VISIBLE)) + << "{ expect false }"; + + qDebug() << "props << PARTICLE_PROP_VISIBLE;"; + props << PARTICLE_PROP_VISIBLE; + + encoded = props.encode(); + qDebug() << "props... encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + qDebug() << "props.getHasProperty(PARTICLE_PROP_VISIBLE)" << (props.getHasProperty(PARTICLE_PROP_VISIBLE)) + << "{ expect true }"; + + qDebug() << "props << PARTICLE_PROP_ANIMATION_URL;"; + props << PARTICLE_PROP_ANIMATION_URL; + + encoded = props.encode(); + qDebug() << "props... encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + qDebug() << "props.getHasProperty(PARTICLE_PROP_VISIBLE)" << (props.getHasProperty(PARTICLE_PROP_VISIBLE)) + << "{ expect true }"; + + qDebug() << "props << ... more ..."; + props << PARTICLE_PROP_ANIMATION_FPS; + props << PARTICLE_PROP_ANIMATION_FRAME_INDEX; + props << PARTICLE_PROP_ANIMATION_PLAYING; + props << PARTICLE_PROP_PAUSE_SIMULATION; + + encoded = props.encode(); + qDebug() << "props... encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + qDebug() << "props.getHasProperty(PARTICLE_PROP_VISIBLE)" << (props.getHasProperty(PARTICLE_PROP_VISIBLE)) + << "{ expect true }"; + + qDebug() << "ParticlePropertyFlags propsB = props & PARTICLE_PROP_VISIBLE;"; + ParticlePropertyFlags propsB = props & PARTICLE_PROP_VISIBLE; + + qDebug() << "propsB.getHasProperty(PARTICLE_PROP_VISIBLE)" << (propsB.getHasProperty(PARTICLE_PROP_VISIBLE)) + << "{ expect true }"; + + encoded = propsB.encode(); + qDebug() << "propsB... encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + + qDebug() << "ParticlePropertyFlags propsC = ~propsB;"; + ParticlePropertyFlags propsC = ~propsB; + + qDebug() << "propsC.getHasProperty(PARTICLE_PROP_VISIBLE)" << (propsC.getHasProperty(PARTICLE_PROP_VISIBLE)) + << "{ expect false }"; + + encoded = propsC.encode(); + qDebug() << "propsC... encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + } + + { + qDebug() << "Test 8: ParticlePropertyFlags: decode tests"; + ParticlePropertyFlags props; + + props << PARTICLE_PROP_VISIBLE; + props << PARTICLE_PROP_ANIMATION_URL; + props << PARTICLE_PROP_ANIMATION_FPS; + props << PARTICLE_PROP_ANIMATION_FRAME_INDEX; + props << PARTICLE_PROP_ANIMATION_PLAYING; + props << PARTICLE_PROP_PAUSE_SIMULATION; + + QByteArray encoded = props.encode(); + qDebug() << "encoded="; + outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); + + qDebug() << "encoded.size()=" << encoded.size(); + + ParticlePropertyFlags propsDecoded; + propsDecoded.decode(encoded); + + qDebug() << "propsDecoded == props:" << (propsDecoded == props) << "{ expect true }"; + + QByteArray encodedAfterDecoded = propsDecoded.encode(); + + qDebug() << "encodedAfterDecoded="; + outputBufferBits((const unsigned char*)encodedAfterDecoded.constData(), encodedAfterDecoded.size()); + + qDebug() << "fill encoded byte array with extra garbage (as if it was bitstream with more content)"; + QByteArray extraContent; + extraContent.fill(0xba, 10); + encoded.append(extraContent); + qDebug() << "encoded.size()=" << encoded.size() << "includes extra garbage"; + + ParticlePropertyFlags propsDecodedExtra; + propsDecodedExtra.decode(encoded); + + qDebug() << "propsDecodedExtra == props:" << (propsDecodedExtra == props) << "{ expect true }"; + + QByteArray encodedAfterDecodedExtra = propsDecodedExtra.encode(); + + qDebug() << "encodedAfterDecodedExtra="; + outputBufferBits((const unsigned char*)encodedAfterDecodedExtra.constData(), encodedAfterDecodedExtra.size()); + + } + + qDebug() << "******************************************************************************************"; +} + +void OctreeTests::runAllTests() { + propertyFlagsTests(); +} diff --git a/tests/octree/src/OctreeTests.h b/tests/octree/src/OctreeTests.h new file mode 100644 index 0000000000..53b0d9fb83 --- /dev/null +++ b/tests/octree/src/OctreeTests.h @@ -0,0 +1,22 @@ +// +// OctreeTests.h +// tests/physics/src +// +// Created by Brad Hefta-Gaub on 06/04/2014. +// 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_OctreeTests_h +#define hifi_OctreeTests_h + +namespace OctreeTests { + + void propertyFlagsTests(); + + void runAllTests(); +} + +#endif // hifi_OctreeTests_h diff --git a/tests/octree/src/main.cpp b/tests/octree/src/main.cpp new file mode 100644 index 0000000000..ec3dc19e01 --- /dev/null +++ b/tests/octree/src/main.cpp @@ -0,0 +1,16 @@ +// +// main.cpp +// tests/octree/src +// +// 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 "OctreeTests.h" + +int main(int argc, char** argv) { + OctreeTests::runAllTests(); + return 0; +} diff --git a/tests/shared/CMakeLists.txt b/tests/shared/CMakeLists.txt new file mode 100644 index 0000000000..b9513e3f26 --- /dev/null +++ b/tests/shared/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 2.8) + +if (WIN32) + cmake_policy (SET CMP0020 NEW) +endif (WIN32) + +set(TARGET_NAME shared-tests) + +set(ROOT_DIR ../..) +set(MACRO_DIR ${ROOT_DIR}/cmake/macros) + +# setup for find modules +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") + +#find_package(Qt5Network REQUIRED) +#find_package(Qt5Script REQUIRED) +#find_package(Qt5Widgets REQUIRED) + +include(${MACRO_DIR}/SetupHifiProject.cmake) +setup_hifi_project(${TARGET_NAME} TRUE) + +include(${MACRO_DIR}/AutoMTC.cmake) +auto_mtc(${TARGET_NAME} ${ROOT_DIR}) + +#qt5_use_modules(${TARGET_NAME} Network Script Widgets) + +#include glm +include(${MACRO_DIR}/IncludeGLM.cmake) +include_glm(${TARGET_NAME} ${ROOT_DIR}) + +# link in the shared libraries +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) + +IF (WIN32) + #target_link_libraries(${TARGET_NAME} Winmm Ws2_32) +ENDIF(WIN32) + diff --git a/tests/shared/src/MovingPercentileTests.cpp b/tests/shared/src/MovingPercentileTests.cpp new file mode 100644 index 0000000000..26870717ca --- /dev/null +++ b/tests/shared/src/MovingPercentileTests.cpp @@ -0,0 +1,169 @@ +// +// MovingPercentileTests.cpp +// tests/shared/src +// +// Created by Yixin Wang on 6/4/2014 +// 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 "MovingPercentileTests.h" + +#include "SharedUtil.h" +#include "MovingPercentile.h" + +#include + +float MovingPercentileTests::random() { + return rand() / (float)RAND_MAX; +} + +void MovingPercentileTests::runAllTests() { + + QVector valuesForN; + + valuesForN.append(1); + valuesForN.append(2); + valuesForN.append(3); + valuesForN.append(4); + valuesForN.append(5); + valuesForN.append(10); + valuesForN.append(100); + + + QQueue lastNSamples; + + for (int i=0; i N) { + lastNSamples.pop_front(); + } + + movingMin.updatePercentile(sample); + + float experimentMin = movingMin.getValueAtPercentile(); + + float actualMin = lastNSamples[0]; + for (int j = 0; j < lastNSamples.size(); j++) { + if (lastNSamples.at(j) < actualMin) { + actualMin = lastNSamples.at(j); + } + } + + if (experimentMin != actualMin) { + qDebug() << "\t\t FAIL at sample" << s; + fail = true; + break; + } + } + if (!fail) { + qDebug() << "\t\t PASS"; + } + } + + + { + bool fail = false; + + qDebug() << "\t testing running max..."; + + lastNSamples.clear(); + MovingPercentile movingMax(N, 1.0f); + + for (int s = 0; s < 10000; s++) { + + float sample = random(); + + lastNSamples.push_back(sample); + if (lastNSamples.size() > N) { + lastNSamples.pop_front(); + } + + movingMax.updatePercentile(sample); + + float experimentMax = movingMax.getValueAtPercentile(); + + float actualMax = lastNSamples[0]; + for (int j = 0; j < lastNSamples.size(); j++) { + if (lastNSamples.at(j) > actualMax) { + actualMax = lastNSamples.at(j); + } + } + + if (experimentMax != actualMax) { + qDebug() << "\t\t FAIL at sample" << s; + fail = true; + break; + } + } + if (!fail) { + qDebug() << "\t\t PASS"; + } + } + + + { + bool fail = false; + + qDebug() << "\t testing running median..."; + + lastNSamples.clear(); + MovingPercentile movingMedian(N, 0.5f); + + for (int s = 0; s < 10000; s++) { + + float sample = random(); + + lastNSamples.push_back(sample); + if (lastNSamples.size() > N) { + lastNSamples.pop_front(); + } + + movingMedian.updatePercentile(sample); + + float experimentMedian = movingMedian.getValueAtPercentile(); + + int samplesLessThan = 0; + int samplesMoreThan = 0; + + for (int j=0; j experimentMedian) { + samplesMoreThan++; + } + } + + + if (!(samplesLessThan <= N/2 && samplesMoreThan <= N-1/2)) { + qDebug() << "\t\t FAIL at sample" << s; + fail = true; + break; + } + } + if (!fail) { + qDebug() << "\t\t PASS"; + } + } + } +} + diff --git a/tests/shared/src/MovingPercentileTests.h b/tests/shared/src/MovingPercentileTests.h new file mode 100644 index 0000000000..34460880fb --- /dev/null +++ b/tests/shared/src/MovingPercentileTests.h @@ -0,0 +1,22 @@ +// +// MovingPercentileTests.h +// tests/shared/src +// +// Created by Yixin Wang on 6/4/2014 +// 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_MovingPercentileTests_h +#define hifi_MovingPercentileTests_h + +namespace MovingPercentileTests { + + float random(); + + void runAllTests(); +} + +#endif // hifi_MovingPercentileTests_h diff --git a/tests/shared/src/main.cpp b/tests/shared/src/main.cpp new file mode 100644 index 0000000000..3ae1b7b34d --- /dev/null +++ b/tests/shared/src/main.cpp @@ -0,0 +1,16 @@ +// +// main.cpp +// tests/physics/src +// +// 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 "MovingPercentileTests.h" + +int main(int argc, char** argv) { + MovingPercentileTests::runAllTests(); + return 0; +}