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

This commit is contained in:
Atlante45 2014-06-06 10:26:49 -07:00
commit dece3ea095
47 changed files with 2938 additions and 490 deletions

View file

@ -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");
});

52
examples/squeezeHands.js Normal file
View file

@ -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);
});

View file

@ -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)
/*

View file

@ -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

View file

@ -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();

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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 {

View file

@ -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;

View file

@ -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<Avatar*>(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;

View file

@ -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);

View file

@ -21,6 +21,11 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
_owningAvatar(owningAvatar) {
}
void SkeletonModel::setJointStates(QVector<JointState> 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<const Shape*>& shapes) const {
@ -119,6 +141,7 @@ void SkeletonModel::getBodyShapes(QVector<const Shape*>& 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<glm::vec3> 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);
}

View file

@ -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<JointState> 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

View file

@ -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);

View file

@ -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 <glm/gtx/norm.hpp>
//#include <GeometryUtil.h>
#include <SharedUtil.h>
#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;
}

View file

@ -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 <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/transform.hpp>
#include <FBXReader.h>
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

View file

@ -166,38 +166,21 @@ QVector<JointState> 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<bool> 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<JointState> 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<int>& 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<int>& 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;
}

View file

@ -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<AnimationHandle> AnimationHandlePointer;
typedef QWeakPointer<AnimationHandle> 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<JointState> 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<int> _jointMappings;

View file

@ -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 <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/transform.hpp>
#include <CollisionInfo.h>
#include <SharedUtil.h>
#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<JointState>& 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<JointState>& 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<DistanceConstraint*>(_constraints[i]);
//maxDistance = glm::max(maxDistance, _constraints[i]->enforce());
maxDistance = glm::max(maxDistance, c->enforce());
}
return maxDistance;
}

View file

@ -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<JointState>& 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<JointState>& states, float fraction);
/// Enforce contraints.
/// \return max distance of point movement
float enforceConstraints();
const QVector<glm::vec3>& getPoints() const { return _points; }
private:
QVector<Constraint*> _constraints;
QVector<glm::vec3> _points;
};
#endif // hifi_RagDoll_h

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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<GLuint, GLuint> VerticesIndices;
void renderTexturedHemisphere();
ProgramObject _textureProgram;
QOpenGLFramebufferObject* _framebufferObject;
float _trailingAudioLoudness;
float _oculusAngle;
float _distance;
UIType _uiType;
};
#endif // hifi_ApplicationOverlay_h

View file

@ -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<QByteArray, QByteArray>(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<SharedObject>& reference = _sharedObjectReferences[it.key()->getID()];
QPointer<SharedObject>& 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<SharedObject>& reference = _sharedObjectReferences[it.value()->getRemoteID()];
QPointer<SharedObject>& 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<PropertyWriter>& 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<PropertyWriter>& 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<int, int> 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<int, int>());
}
}
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<MetaField>& 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<SharedObject> reference = _sharedObjectReferences.value(object->getID());
*this << object->getOriginID();
QPointer<SharedObject> 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<SharedObject> reference = _sharedObjectReferences.value(id);
int originID;
*this >> originID;
QPointer<SharedObject> reference = _sharedObjectReferences.value(originID);
QPointer<SharedObject>& 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<SharedObject*>(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<SharedObject*>(pointer.data());
@ -893,13 +981,38 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
void Bitstream::clearSharedObject(QObject* object) {
SharedObject* sharedObject = static_cast<SharedObject*>(object);
_sharedObjectReferences.remove(sharedObject->getID());
_sharedObjectReferences.remove(sharedObject->getOriginID());
int id = _sharedObjectStreamer.takePersistentID(sharedObject);
if (id != 0) {
emit sharedObjectCleared(id);
}
}
const QVector<PropertyWriter>& Bitstream::getPropertyWriters(const QMetaObject* metaObject) {
QVector<PropertyWriter>& 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, QByteArray>(
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<QByteArray, const QMetaObject*>& Bitstream::getMetaObjects() {
static QHash<QByteArray, const QMetaObject*> metaObjects;
return metaObjects;
@ -915,6 +1028,16 @@ QHash<int, const TypeStreamer*>& Bitstream::getTypeStreamers() {
return typeStreamers;
}
QHash<QPair<QByteArray, QByteArray>, const TypeStreamer*>& Bitstream::getEnumStreamers() {
static QHash<QPair<QByteArray, QByteArray>, const TypeStreamer*> enumStreamers;
return enumStreamers;
}
QHash<QByteArray, const TypeStreamer*>& Bitstream::getEnumStreamersByName() {
static QHash<QByteArray, const TypeStreamer*> enumStreamersByName;
return enumStreamersByName;
}
QVector<PropertyReader> Bitstream::getPropertyReaders(const QMetaObject* metaObject) {
QVector<PropertyReader> propertyReaders;
if (!metaObject) {
@ -925,31 +1048,78 @@ QVector<PropertyReader> 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, QByteArray>(
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<FieldReader>& 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<int, int>& mappings) :
_typeName(typeName),
_streamer(streamer),
_exactMatch(false),
_type(ENUM_TYPE),
_bits(bits),
_mappings(mappings) {
}
TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, const QVector<FieldReader>& 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<int, int>& mappings) const {
// nothing by default
}
const QVector<MetaField>& TypeStreamer::getMetaFields() const {
static QVector<MetaField> 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<int, int>& mappings) const {
if (_metaEnum.isFlag()) {
int combined = 0;
for (QHash<int, int>::const_iterator it = mappings.constBegin(); it != mappings.constEnd(); it++) {
if (value & it.key()) {
combined |= it.value();
}
}
object = combined;
} else {
object = mappings.value(value);
}
}

View file

@ -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<class T> void writeRawDelta(const QList<T>& value, const QList<T>& reference);
template<class T> void readRawDelta(QList<T>& value, const QList<T>& reference);
template<class T> void writeRawDelta(const QVector<T>& value, const QVector<T>& reference);
template<class T> void readRawDelta(QVector<T>& value, const QVector<T>& reference);
template<class T> void writeRawDelta(const QSet<T>& value, const QSet<T>& reference);
template<class T> void readRawDelta(QSet<T>& value, const QSet<T>& reference);
@ -339,6 +346,9 @@ public:
template<class T> Bitstream& operator<<(const QList<T>& list);
template<class T> Bitstream& operator>>(QList<T>& list);
template<class T> Bitstream& operator<<(const QVector<T>& list);
template<class T> Bitstream& operator>>(QVector<T>& list);
template<class T> Bitstream& operator<<(const QSet<T>& set);
template<class T> Bitstream& operator>>(QSet<T>& set);
@ -390,6 +400,8 @@ private slots:
private:
const QVector<PropertyWriter>& getPropertyWriters(const QMetaObject* metaObject);
QDataStream& _underlying;
quint8 _byte;
int _position;
@ -409,9 +421,13 @@ private:
QHash<QByteArray, const QMetaObject*> _metaObjectSubstitutions;
QHash<QByteArray, const TypeStreamer*> _typeStreamerSubstitutions;
QHash<const QMetaObject*, QVector<PropertyWriter> > _propertyWriters;
static QHash<QByteArray, const QMetaObject*>& getMetaObjects();
static QMultiHash<const QMetaObject*, const QMetaObject*>& getMetaObjectSubClasses();
static QHash<int, const TypeStreamer*>& getTypeStreamers();
static QHash<QPair<QByteArray, QByteArray>, const TypeStreamer*>& getEnumStreamers();
static QHash<QByteArray, const TypeStreamer*>& getEnumStreamersByName();
static QVector<PropertyReader> getPropertyReaders(const QMetaObject* metaObject);
};
@ -472,6 +488,36 @@ template<class T> inline void Bitstream::readRawDelta(QList<T>& value, const QLi
}
}
template<class T> inline void Bitstream::writeRawDelta(const QVector<T>& value, const QVector<T>& 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<class T> inline void Bitstream::readRawDelta(QVector<T>& value, const QVector<T>& 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<class T> inline void Bitstream::writeRawDelta(const QSet<T>& value, const QSet<T>& reference) {
int addedOrRemoved = 0;
foreach (const T& element, value) {
@ -600,6 +646,27 @@ template<class T> inline Bitstream& Bitstream::operator>>(QList<T>& list) {
return *this;
}
template<class T> inline Bitstream& Bitstream::operator<<(const QVector<T>& vector) {
*this << vector.size();
foreach (const T& entry, vector) {
*this << entry;
}
return *this;
}
template<class T> inline Bitstream& Bitstream::operator>>(QVector<T>& 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<class T> inline Bitstream& Bitstream::operator<<(const QSet<T>& set) {
*this << set.size();
foreach (const T& entry, set) {
@ -651,13 +718,20 @@ typedef QSharedPointer<TypeReader> 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<FieldReader>& fields = QVector<FieldReader>());
TypeReader(const QByteArray& typeName = QByteArray(), const TypeStreamer* streamer = NULL);
TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, int bits, const QHash<int, int>& mappings);
TypeReader(const QByteArray& typeName, const TypeStreamer* streamer, const QVector<FieldReader>& 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<int, int> _mappings;
TypeReaderPointer _keyReader;
TypeReaderPointer _valueReader;
QVector<FieldReader> _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<int, int>& mappings) const;
virtual const QVector<MetaField>& 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 T> 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<T>(), reference.value<T>()); }
virtual void readDelta(Bitstream& in, QVariant& value, const QVariant& reference) const {
in.readDelta(*static_cast<T*>(value.data()), reference.value<T>()); }
T rawValue; in.readDelta(rawValue, reference.value<T>()); value = QVariant::fromValue(rawValue); }
virtual void writeRawDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const {
out.writeRawDelta(value.value<T>(), reference.value<T>()); }
virtual void readRawDelta(Bitstream& in, QVariant& value, const QVariant& reference) const {
in.readRawDelta(*static_cast<T*>(value.data()), reference.value<T>()); }
T rawValue; in.readRawDelta(rawValue, reference.value<T>()); 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<int, int>& mappings) const;
private:
QMetaEnum _metaEnum;
QByteArray _name;
int _bits;
};
/// A streamer for types compiled by mtc.
@ -858,6 +993,22 @@ public:
static_cast<QList<T>*>(object.data())->replace(index, value.value<T>()); }
};
/// A streamer for vector types.
template<class T> class CollectionTypeStreamer<QVector<T> > : public SimpleTypeStreamer<QVector<T> > {
public:
virtual TypeReader::Type getReaderType() const { return TypeReader::LIST_TYPE; }
virtual const TypeStreamer* getValueStreamer() const { return Bitstream::getTypeStreamer(qMetaTypeId<T>()); }
virtual void insert(QVariant& object, const QVariant& value) const {
static_cast<QVector<T>*>(object.data())->append(value.value<T>()); }
virtual void prune(QVariant& object, int size) const {
QVector<T>* list = static_cast<QVector<T>*>(object.data()); list->erase(list->begin() + size, list->end()); }
virtual QVariant getValue(const QVariant& object, int index) const {
return QVariant::fromValue(static_cast<const QVector<T>*>(object.constData())->at(index)); }
virtual void setValue(QVariant& object, int index, const QVariant& value) const {
static_cast<QVector<T>*>(object.data())->replace(index, value.value<T>()); }
};
/// A streamer for set types.
template<class T> class CollectionTypeStreamer<QSet<T> > : public SimpleTypeStreamer<QSet<T> > {
public:
@ -940,6 +1091,13 @@ template<class T> int registerStreamableMetaType() {
return type;
}
/// Registers a collection type and its streamer.
template<class T> int registerCollectionMetaType() {
int type = qRegisterMetaType<T>();
Bitstream::registerTypeStreamer(type, new CollectionTypeStreamer<T>());
return type;
}
/// Flags a class as streamable (use as you would Q_OBJECT).
#define STREAMABLE public: \
static const int Type; \

View file

@ -19,7 +19,7 @@
#include <QSet>
#include <QVector>
#include "Bitstream.h"
#include "AttributeRegistry.h"
class ReliableChannel;

View file

@ -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<SharedObject*>(metaObject->newInstance());
if (!target) {
target = static_cast<SharedObject*>(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<SharedObjectPointer>()) {
SharedObject* value = property.read(this).value<SharedObjectPointer>().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<QByteArray> 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;

View file

@ -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;

View file

@ -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<uint16_t> 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);

View file

@ -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;

View file

@ -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);

View file

@ -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) {

View file

@ -602,7 +602,6 @@ public:
bool findRayIntersectionOp(OctreeElement* element, void* extraData) {
RayArgs* args = static_cast<RayArgs*>(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) {

View file

@ -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];
}

View file

@ -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 <qlist.h>
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<float> _samplesSorted;
QList<int> _sampleIds; // incrementally assigned, is cyclic
int _newSampleId;
int _indexOfPercentile;
float _valueAtPercentile;
};
#endif

View file

@ -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 <algorithm>
#include <climits>
#include <QBitArray>
#include <QByteArray>
#include <SharedUtil.h>
template<typename Enum>class 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<typename Enum> PropertyFlags<Enum>& operator<<(PropertyFlags<Enum>& out, const PropertyFlags<Enum>& other) {
return out <<= other;
}
template<typename Enum> PropertyFlags<Enum>& operator<<(PropertyFlags<Enum>& out, Enum flag) {
return out <<= flag;
}
template<typename Enum> inline void PropertyFlags<Enum>::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<typename Enum> inline bool PropertyFlags<Enum>::getHasProperty(Enum flag) {
if (flag > _maxFlag) {
return _trailingFlipped; // usually false
}
return _flags.testBit(flag);
}
const int BITS_PER_BYTE = 8;
template<typename Enum> inline QByteArray PropertyFlags<Enum>::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<typename Enum> inline void PropertyFlags<Enum>::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<typename Enum> inline void PropertyFlags<Enum>::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<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::operator=(const PropertyFlags& other) {
_flags = other._flags;
_maxFlag = other._maxFlag;
_minFlag = other._minFlag;
return *this;
}
template<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::operator|=(PropertyFlags other) {
_flags |= other._flags;
_maxFlag = std::max(_maxFlag, other._maxFlag);
_minFlag = std::min(_minFlag, other._minFlag);
return *this;
}
template<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::operator|=(Enum flag) {
PropertyFlags other(flag);
_flags |= other._flags;
_maxFlag = std::max(_maxFlag, other._maxFlag);
_minFlag = std::min(_minFlag, other._minFlag);
return *this;
}
template<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::operator&=(PropertyFlags other) {
_flags &= other._flags;
shinkIfNeeded();
return *this;
}
template<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::operator&=(Enum flag) {
PropertyFlags other(flag);
_flags &= other._flags;
shinkIfNeeded();
return *this;
}
template<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::operator^=(PropertyFlags other) {
_flags ^= other._flags;
shinkIfNeeded();
return *this;
}
template<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::operator^=(Enum flag) {
PropertyFlags other(flag);
_flags ^= other._flags;
shinkIfNeeded();
return *this;
}
template<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::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<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::operator+=(Enum flag) {
setHasProperty(flag, true);
return *this;
}
template<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::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<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::operator-=(Enum flag) {
setHasProperty(flag, false);
return *this;
}
template<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::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<typename Enum> inline PropertyFlags<Enum>& PropertyFlags<Enum>::operator<<=(Enum flag) {
setHasProperty(flag, true);
return *this;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator|(PropertyFlags other) const {
PropertyFlags result(*this);
result |= other;
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator|(Enum flag) const {
PropertyFlags result(*this);
PropertyFlags other(flag);
result |= other;
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator&(PropertyFlags other) const {
PropertyFlags result(*this);
result &= other;
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator&(Enum flag) const {
PropertyFlags result(*this);
PropertyFlags other(flag);
result &= other;
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator^(PropertyFlags other) const {
PropertyFlags result(*this);
result ^= other;
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator^(Enum flag) const {
PropertyFlags result(*this);
PropertyFlags other(flag);
result ^= other;
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator+(PropertyFlags other) const {
PropertyFlags result(*this);
result += other;
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator+(Enum flag) const {
PropertyFlags result(*this);
result.setHasProperty(flag, true);
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator-(PropertyFlags other) const {
PropertyFlags result(*this);
result -= other;
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator-(Enum flag) const {
PropertyFlags result(*this);
result.setHasProperty(flag, false);
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator<<(PropertyFlags other) const {
PropertyFlags result(*this);
result <<= other;
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator<<(Enum flag) const {
PropertyFlags result(*this);
result.setHasProperty(flag, true);
return result;
}
template<typename Enum> inline PropertyFlags<Enum> PropertyFlags<Enum>::operator~() const {
PropertyFlags result(*this);
result._flags = ~_flags;
result._trailingFlipped = !_trailingFlipped;
return result;
}
template<typename Enum> inline void PropertyFlags<Enum>::shinkIfNeeded() {
bool maxFlagWas = _maxFlag;
while (_maxFlag >= 0) {
if (_flags.testBit(_maxFlag)) {
break;
}
_maxFlag--;
}
if (maxFlagWas != _maxFlag) {
_flags.resize(_maxFlag + 1);
}
}
#endif // hifi_PropertyFlags_h

View file

@ -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<TestSharedObjectA*>(testObjectWrittenA.data())->getFoo() !=
static_cast<TestSharedObjectB*>(testObjectReadA.data())->getFoo()) {
if (metadataType == Bitstream::FULL_METADATA && (static_cast<TestSharedObjectA*>(testObjectWrittenA.data())->getFoo() !=
static_cast<TestSharedObjectB*>(testObjectReadA.data())->getFoo() ||
static_cast<TestSharedObjectB*>(testObjectReadA.data())->getBaz() != TestSharedObjectB::SECOND_TEST_ENUM ||
static_cast<TestSharedObjectB*>(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<TestSharedObjectB*>(testObjectWrittenB.data())->getFoo() !=
static_cast<TestSharedObjectA*>(testObjectReadB.data())->getFoo()) {
if (metadataType == Bitstream::FULL_METADATA && (static_cast<TestSharedObjectB*>(testObjectWrittenB.data())->getFoo() !=
static_cast<TestSharedObjectA*>(testObjectReadB.data())->getFoo() ||
static_cast<TestSharedObjectA*>(testObjectReadB.data())->getBaz() != TestSharedObjectA::THIRD_TEST_ENUM ||
static_cast<TestSharedObjectA*>(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++;
}

View file

@ -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.

View file

@ -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)

View file

@ -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 <QDebug>
#include <PropertyFlags.h>
#include <SharedUtil.h>
#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<ModelPropertyList> 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<ParticlePropertyList> 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();
}

View file

@ -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

16
tests/octree/src/main.cpp Normal file
View file

@ -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;
}

View file

@ -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)

View file

@ -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 <qqueue.h>
float MovingPercentileTests::random() {
return rand() / (float)RAND_MAX;
}
void MovingPercentileTests::runAllTests() {
QVector<int> valuesForN;
valuesForN.append(1);
valuesForN.append(2);
valuesForN.append(3);
valuesForN.append(4);
valuesForN.append(5);
valuesForN.append(10);
valuesForN.append(100);
QQueue<float> lastNSamples;
for (int i=0; i<valuesForN.size(); i++) {
int N = valuesForN.at(i);
qDebug() << "testing moving percentile with N =" << N << "...";
{
bool fail = false;
qDebug() << "\t testing running min...";
lastNSamples.clear();
MovingPercentile movingMin(N, 0.0f);
for (int s = 0; s < 3*N; s++) {
float sample = random();
lastNSamples.push_back(sample);
if (lastNSamples.size() > 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<lastNSamples.size(); j++) {
if (lastNSamples.at(j) < experimentMedian) {
samplesLessThan++;
} else if (lastNSamples.at(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";
}
}
}
}

View file

@ -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

16
tests/shared/src/main.cpp Normal file
View file

@ -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;
}