Merge branch 'master' of https://github.com/worklist/hifi into 19494

This commit is contained in:
stojce 2014-02-11 18:14:42 +01:00
commit 20026da6cd
48 changed files with 1309 additions and 275 deletions

View file

@ -39,4 +39,5 @@ add_subdirectory(assignment-client)
add_subdirectory(domain-server)
add_subdirectory(interface)
add_subdirectory(pairing-server)
add_subdirectory(voxel-edit)
add_subdirectory(tests)
add_subdirectory(voxel-edit)

View file

@ -115,7 +115,6 @@ function sendNextCells() {
var sentFirstBoard = false;
function step() {
print("step()...");
if (sentFirstBoard) {
// we've already sent the first full board, perform a step in time
updateCells();

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

View file

@ -0,0 +1,18 @@
scale=130
joint = jointRoot = jointRoot
joint = jointLean = jointSpine
joint = jointNeck = jointNeck
joint = jointHead = jointHeadtop
joint = joint_L_shoulder = joint_L_shoulder
freeJoint = joint_L_arm
freeJoint = joint_L_elbow
joint = jointLeftHand = joint_L_hand
joint = joint_R_shoulder = joint_R_shoulder
freeJoint = joint_R_arm
freeJoint = joint_R_elbow
joint = jointRightHand = joint_R_hand

View file

@ -0,0 +1,45 @@
# faceshift target mapping file
name= defaultAvatar_head
filename=../../../Avatars/Jelly/jellyrob_blue.fbx
texdir=../../../Avatars/Jelly
scale=80
rx=0
ry=0
rz=0
tx=0
ty=0
tz=0
joint = jointNeck = jointNeck
bs = BrowsD_L = Leye1.BrowsD_L = 0.97
bs = BrowsD_R = Reye1.BrowsD_R = 1
bs = CheekSquint_L = Leye1.CheekSquint_L = 1
bs = CheekSquint_R = Reye1.CheekSquint_R = 1
bs = EyeBlink_L = Leye1.EyeBlink_L = 1
bs = EyeBlink_R = Reye1.EyeBlink_R = 1
bs = EyeDown_L = Leye1.EyeDown_L = 1
bs = EyeDown_R = Reye1.EyeDown_R = 0.99
bs = EyeIn_L = Leye1.EyeIn_L = 0.92
bs = EyeIn_R = Reye1.EyeIn_R = 1
bs = EyeOpen_L = Leye1.EyeOpen_L = 1
bs = EyeOpen_R = Reye1.EyeOpen_R = 1
bs = EyeOut_L = Leye1.EyeOut_L = 0.99
bs = EyeOut_R = Reye1.EyeOut_R = 1
bs = EyeUp_L = Leye1.EyeUp_L = 0.93
bs = EyeUp_R = Reye1.EyeUp_R = 1
bs = JawOpen = Mouth.JawOpen = 1
bs = LipsFunnel = Mouth.LipsFunnel = 1
bs = LipsLowerDown = Mouth.LipsLowerDown = 1
bs = LipsPucker = Mouth.LipsPucker = 1
bs = LipsStretch_L = Mouth.LipsStretch_L = 0.96
bs = LipsStretch_R = Mouth.LipsStretch_R = 1
bs = LipsUpperUp = Mouth.LipsUpperUp = 1
bs = MouthDimple_L = Mouth.MouthDimple_L = 1
bs = MouthDimple_R = Mouth.MouthDimple_R = 1
bs = MouthFrown_L = Mouth.MouthFrown_L = 1
bs = MouthFrown_R = Mouth.MouthFrown_R = 1
bs = MouthLeft = Mouth.MouthLeft = 1
bs = MouthRight = Mouth.MouthRight = 1
bs = MouthSmile_L = Mouth.MouthSmile_L = 1
bs = MouthSmile_R = Mouth.MouthSmile_R = 1
bs = Puff = Mouth.Puff = 1
bs = Sneer = Mouth.Sneer = 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -162,14 +162,18 @@ Menu::Menu() :
addDisabledActionAndSeparator(editMenu, "Physics");
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, true);
addCheckableActionToQMenuAndActionHash(editMenu,
MenuOption::Collisions,
0,
true,
appInstance->getAvatar(),
SLOT(setWantCollisionsOn(bool)));
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly);
QMenu* collisionsOptionsMenu = editMenu->addMenu("Collision Options");
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, 0, false, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, 0, false, avatar, SLOT(updateCollisionFlags()));
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, 0, false, avatar, SLOT(updateCollisionFlags()));
// TODO: make this option work
//addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, 0, false, avatar, SLOT(updateCollisionFlags()));
QMenu* toolsMenu = addMenu("Tools");
@ -774,11 +778,13 @@ void Menu::editPreferences() {
QString faceURLString = applicationInstance->getAvatar()->getHead().getFaceModel().getURL().toString();
QLineEdit* faceURLEdit = new QLineEdit(faceURLString);
faceURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
faceURLEdit->setPlaceholderText(DEFAULT_HEAD_MODEL_URL.toString());
form->addRow("Face URL:", faceURLEdit);
QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString();
QLineEdit* skeletonURLEdit = new QLineEdit(skeletonURLString);
skeletonURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
skeletonURLEdit->setPlaceholderText(DEFAULT_BODY_MODEL_URL.toString());
form->addRow("Skeleton URL:", skeletonURLEdit);
QSlider* pupilDilation = new QSlider(Qt::Horizontal);

View file

@ -168,6 +168,10 @@ namespace MenuOption {
const QString ChatCircling = "Chat Circling";
const QString CollisionProxies = "Collision Proxies";
const QString Collisions = "Collisions";
const QString CollideWithAvatars = "Collide With Avatars";
const QString CollideWithParticles = "Collide With Particles";
const QString CollideWithVoxels = "Collide With Voxels";
const QString CollideWithEnvironment = "Collide With World Boundaries";
const QString CopyVoxels = "Copy";
const QString CoverageMap = "Render Coverage Map";
const QString CoverageMapV2 = "Render Coverage Map V2";

View file

@ -15,7 +15,6 @@
#include <glm/gtc/noise.hpp>
#include <glm/gtx/quaternion.hpp>
#include <AvatarData.h>
#include <SharedUtil.h>
#include "InterfaceConfig.h"

View file

@ -74,6 +74,7 @@ Avatar::Avatar() :
_mouseRayDirection(0.0f, 0.0f, 0.0f),
_moving(false),
_owningAvatarMixer(),
_collisionFlags(0),
_initialized(false)
{
// we may have been created in the network thread, but we live in the main thread
@ -159,7 +160,13 @@ void Avatar::render(bool forceRenderHead) {
Glower glower(_moving && glm::length(toTarget) > GLOW_DISTANCE ? 1.0f : 0.0f);
// render body
renderBody(forceRenderHead);
if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) {
_skeletonModel.renderCollisionProxies(1.f);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
renderBody(forceRenderHead);
}
// render sphere when far away
const float MAX_ANGLE = 10.f;
@ -264,34 +271,28 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
return false;
}
bool Avatar::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
glm::vec3& penetration, int skeletonSkipIndex) const {
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionList& collisions, int skeletonSkipIndex) {
bool didPenetrate = false;
glm::vec3 totalPenetration;
glm::vec3 skeletonPenetration;
if (_skeletonModel.findSpherePenetration(penetratorCenter, penetratorRadius,
skeletonPenetration, 1.0f, skeletonSkipIndex)) {
totalPenetration = addPenetrations(totalPenetration, skeletonPenetration);
ModelCollisionInfo collisionInfo;
if (_skeletonModel.findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo, 1.0f, skeletonSkipIndex)) {
collisionInfo._model = &_skeletonModel;
collisions.push_back(collisionInfo);
didPenetrate = true;
}
glm::vec3 facePenetration;
if (_head.getFaceModel().findSpherePenetration(penetratorCenter, penetratorRadius, facePenetration)) {
totalPenetration = addPenetrations(totalPenetration, facePenetration);
if (_head.getFaceModel().findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo)) {
collisionInfo._model = &(_head.getFaceModel());
collisions.push_back(collisionInfo);
didPenetrate = true;
}
if (didPenetrate) {
penetration = totalPenetration;
return true;
}
return false;
return didPenetrate;
}
bool Avatar::findSphereCollision(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
// TODO: provide an early exit using bounding sphere of entire avatar
bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
const HandData* handData = getHandData();
if (handData) {
for (int i = 0; i < 2; i++) {
for (int i = 0; i < NUM_HANDS; i++) {
const PalmData* palm = handData->getPalm(i);
if (palm && palm->hasPaddle()) {
// create a disk collision proxy where the hand is
@ -327,23 +328,31 @@ bool Avatar::findSphereCollision(const glm::vec3& sphereCenter, float sphereRadi
}
}
}
return false;
}
if (_skeletonModel.findSpherePenetration(sphereCenter, sphereRadius, collision._penetration)) {
/* adebug TODO: make this work again
bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
int jointIndex = _skeletonModel.findSphereCollision(sphereCenter, sphereRadius, collision._penetration);
if (jointIndex != -1) {
collision._penetration /= (float)(TREE_SCALE);
collision._addedVelocity = getVelocity();
return true;
}
return false;
}
*/
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
_head.getFaceModel().setURL(faceModelURL);
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fbx");
_head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
}
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
AvatarData::setSkeletonModelURL(skeletonModelURL);
_skeletonModel.setURL(skeletonModelURL);
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fbx");
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
}
int Avatar::parseData(const QByteArray& packet) {
@ -406,6 +415,22 @@ void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2,
glEnd();
}
void Avatar::updateCollisionFlags() {
_collisionFlags = 0;
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithEnvironment)) {
_collisionFlags |= COLLISION_GROUP_ENVIRONMENT;
}
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithAvatars)) {
_collisionFlags |= COLLISION_GROUP_AVATARS;
}
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) {
_collisionFlags |= COLLISION_GROUP_VOXELS;
}
//if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) {
// _collisionFlags |= COLLISION_GROUP_PARTICLES;
//}
}
void Avatar::setScale(float scale) {
_scale = scale;
@ -420,6 +445,15 @@ float Avatar::getHeight() const {
return extents.maximum.y - extents.minimum.y;
}
bool Avatar::poke(ModelCollisionInfo& collision) {
// ATM poke() can only affect the Skeleton (not the head)
// TODO: make poke affect head
if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
return _skeletonModel.poke(collision);
}
return false;
}
float Avatar::getPelvisFloatingHeight() const {
return -_skeletonModel.getBindExtents().minimum.y;
}

View file

@ -57,7 +57,7 @@ enum ScreenTintLayer {
NUM_SCREEN_TINT_LAYERS
};
class MyAvatar;
typedef QVector<ModelCollisionInfo> ModelCollisionList;
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found)
// this is basically in the center of the ground plane. Slightly adjusted. This was asked for by
@ -97,18 +97,25 @@ public:
/// Checks for penetration between the described sphere and the avatar.
/// \param penetratorCenter the center of the penetration test sphere
/// \param penetratorRadius the radius of the penetration test sphere
/// \param penetration[out] the vector in which to store the penetration
/// \param collisions[out] a list of collisions
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
/// \return whether or not the sphere penetrated
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
glm::vec3& penetration, int skeletonSkipIndex = -1) const;
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionList& collisions, int skeletonSkipIndex = -1);
/// Checks for collision between the a sphere and the avatar.
/// Checks for collision between the a sphere and the avatar's (paddle) hands.
/// \param collisionCenter the center of the penetration test sphere
/// \param collisionRadius the radius of the penetration test sphere
/// \param collision[out] the details of the collision point
/// \return whether or not the sphere collided
virtual bool findSphereCollision(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
/// Checks for collision between the a sphere and the avatar's skeleton (including hand capsules).
/// \param collisionCenter the center of the penetration test sphere
/// \param collisionRadius the radius of the penetration test sphere
/// \param collision[out] the details of the collision point
/// \return whether or not the sphere collided
//bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
virtual bool isMyAvatar() { return false; }
@ -119,6 +126,15 @@ public:
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
float getHeight() const;
/// \param collision a data structure for storing info about collisions against Models
/// \return true if the collision affects the Avatar models
bool poke(ModelCollisionInfo& collision);
public slots:
void updateCollisionFlags();
protected:
Head _head;
Hand _hand;
@ -137,6 +153,8 @@ protected:
bool _moving; ///< set when position is changing
QWeakPointer<Node> _owningAvatarMixer;
uint32_t _collisionFlags;
// protected methods...
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; }
@ -144,7 +162,6 @@ protected:
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
void setScale(float scale);
float getHeight() const;
float getPelvisFloatingHeight() const;
float getPelvisToHeadLength() const;

View file

@ -67,9 +67,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
}
void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) {
if (!Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
return;
}
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::renderAvatars()");
bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors);

View file

@ -90,9 +90,7 @@ void Hand::simulate(float deltaTime, bool isMine) {
calculateGeometry();
if (isMine) {
// Iterate hand controllers, take actions as needed
for (size_t i = 0; i < getNumPalms(); ++i) {
PalmData& palm = getPalms()[i];
if (palm.isActive()) {
@ -173,6 +171,7 @@ void Hand::updateCollisions() {
int leftPalmIndex, rightPalmIndex;
getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
ModelCollisionList collisions;
// check for collisions
for (size_t i = 0; i < getNumPalms(); i++) {
PalmData& palm = getPalms()[i];
@ -182,69 +181,76 @@ void Hand::updateCollisions() {
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
glm::vec3 totalPenetration;
// check other avatars
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (avatar == _owningAvatar) {
// don't collid with our own hands
continue;
}
if (Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
// Check for palm collisions
glm::vec3 myPalmPosition = palm.getPosition();
float palmCollisionDistance = 0.1f;
bool wasColliding = palm.getIsCollidingWithPalm();
palm.setIsCollidingWithPalm(false);
// If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound
for (size_t j = 0; j < avatar->getHand().getNumPalms(); j++) {
PalmData& otherPalm = avatar->getHand().getPalms()[j];
if (!otherPalm.isActive()) {
continue;
}
glm::vec3 otherPalmPosition = otherPalm.getPosition();
if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) {
palm.setIsCollidingWithPalm(true);
if (!wasColliding) {
const float PALM_COLLIDE_VOLUME = 1.f;
const float PALM_COLLIDE_FREQUENCY = 1000.f;
const float PALM_COLLIDE_DURATION_MAX = 0.75f;
const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
PALM_COLLIDE_FREQUENCY,
PALM_COLLIDE_DURATION_MAX,
PALM_COLLIDE_DECAY_PER_SAMPLE);
// If the other person's palm is in motion, move mine downward to show I was hit
const float MIN_VELOCITY_FOR_SLAP = 0.05f;
if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {
// add slapback here
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithAvatars)) {
// check other avatars
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (avatar == _owningAvatar) {
// don't collid with our own hands
continue;
}
if (Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
// Check for palm collisions
glm::vec3 myPalmPosition = palm.getPosition();
float palmCollisionDistance = 0.1f;
bool wasColliding = palm.getIsCollidingWithPalm();
palm.setIsCollidingWithPalm(false);
// If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound
for (size_t j = 0; j < avatar->getHand().getNumPalms(); j++) {
PalmData& otherPalm = avatar->getHand().getPalms()[j];
if (!otherPalm.isActive()) {
continue;
}
glm::vec3 otherPalmPosition = otherPalm.getPosition();
if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) {
palm.setIsCollidingWithPalm(true);
if (!wasColliding) {
const float PALM_COLLIDE_VOLUME = 1.f;
const float PALM_COLLIDE_FREQUENCY = 1000.f;
const float PALM_COLLIDE_DURATION_MAX = 0.75f;
const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
PALM_COLLIDE_FREQUENCY,
PALM_COLLIDE_DURATION_MAX,
PALM_COLLIDE_DECAY_PER_SAMPLE);
// If the other person's palm is in motion, move mine downward to show I was hit
const float MIN_VELOCITY_FOR_SLAP = 0.05f;
if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {
// add slapback here
}
}
}
}
}
}
glm::vec3 avatarPenetration;
if (avatar->findSpherePenetration(palm.getPosition(), scaledPalmRadius, avatarPenetration)) {
totalPenetration = addPenetrations(totalPenetration, avatarPenetration);
// Check for collisions with the other avatar's leap palms
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions)) {
for (size_t j = 0; j < collisions.size(); ++j) {
if (!avatar->poke(collisions[j])) {
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
}
}
}
}
}
if (Menu::getInstance()->isOptionChecked(MenuOption::HandsCollideWithSelf)) {
// and the current avatar (ignoring everything below the parent of the parent of the last free joint)
glm::vec3 owningPenetration;
collisions.clear();
const Model& skeletonModel = _owningAvatar->getSkeletonModel();
int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
(i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
if (_owningAvatar->findSpherePenetration(palm.getPosition(), scaledPalmRadius, owningPenetration, skipIndex)) {
totalPenetration = addPenetrations(totalPenetration, owningPenetration);
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions, skipIndex)) {
for (size_t j = 0; j < collisions.size(); ++j) {
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
}
}
}
// un-penetrate
palm.addToPosition(-totalPenetration);
// we recycle the collisions container, so we clear it for the next loop
collisions.clear();
}
}
@ -278,14 +284,14 @@ void Hand::calculateGeometry() {
FingerData& finger = palm.getFingers()[f];
if (finger.isActive()) {
const float standardBallRadius = FINGERTIP_COLLISION_RADIUS;
_leapFingerTipBalls.resize(_leapFingerTipBalls.size() + 1);
HandBall& ball = _leapFingerTipBalls.back();
HandBall ball;
ball.rotation = getBaseOrientation();
ball.position = finger.getTipPosition();
ball.radius = standardBallRadius;
ball.touchForce = 0.0;
ball.isCollidable = true;
ball.isColliding = false;
_leapFingerTipBalls.push_back(ball);
}
}
}
@ -300,14 +306,14 @@ void Hand::calculateGeometry() {
FingerData& finger = palm.getFingers()[f];
if (finger.isActive()) {
const float standardBallRadius = 0.005f;
_leapFingerRootBalls.resize(_leapFingerRootBalls.size() + 1);
HandBall& ball = _leapFingerRootBalls.back();
HandBall ball;
ball.rotation = getBaseOrientation();
ball.position = finger.getRootPosition();
ball.radius = standardBallRadius;
ball.touchForce = 0.0;
ball.isCollidable = true;
ball.isColliding = false;
_leapFingerRootBalls.push_back(ball);
}
}
}
@ -473,8 +479,3 @@ void Hand::setLeapHands(const std::vector<glm::vec3>& handPositions,
}
}

View file

@ -209,21 +209,26 @@ void MyAvatar::simulate(float deltaTime) {
_velocity += _scale * _gravity * (GRAVITY_EARTH * deltaTime);
}
// Only collide if we are not moving to a target
if (_isCollisionsOn && (glm::length(_moveTarget) < EPSILON)) {
if (_collisionFlags != 0) {
Camera* myCamera = Application::getInstance()->getCamera();
float radius = getHeight() * COLLISION_RADIUS_SCALE;
if (myCamera->getMode() == CAMERA_MODE_FIRST_PERSON && !OculusManager::isConnected()) {
_collisionRadius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.f));
_collisionRadius *= COLLISION_RADIUS_SCALAR;
} else {
_collisionRadius = getHeight() * COLLISION_RADIUS_SCALE;
radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.f));
radius *= COLLISION_RADIUS_SCALAR;
}
updateCollisionWithEnvironment(deltaTime);
updateCollisionWithVoxels(deltaTime);
updateAvatarCollisions(deltaTime);
if (_collisionFlags & COLLISION_GROUP_ENVIRONMENT) {
updateCollisionWithEnvironment(deltaTime, radius);
}
if (_collisionFlags & COLLISION_GROUP_VOXELS) {
updateCollisionWithVoxels(deltaTime, radius);
}
if (_collisionFlags & COLLISION_GROUP_AVATARS) {
// Note, hand-vs-avatar collisions are done elsewhere
// This is where we avatar-vs-avatar bounding capsule
updateCollisionWithAvatars(deltaTime);
}
}
// add thrust to velocity
@ -476,7 +481,12 @@ void MyAvatar::renderDebugBodyPoints() {
void MyAvatar::render(bool forceRenderHead) {
// render body
renderBody(forceRenderHead);
if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) {
_skeletonModel.renderCollisionProxies(1.f);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
renderBody(forceRenderHead);
}
//renderDebugBodyPoints();
@ -878,10 +888,9 @@ void MyAvatar::updateHandMovementAndTouching(float deltaTime) {
}
}
void MyAvatar::updateCollisionWithEnvironment(float deltaTime) {
void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
glm::vec3 up = getBodyUpDirection();
float radius = _collisionRadius;
const float ENVIRONMENT_SURFACE_ELASTICITY = 1.0f;
const float ENVIRONMENT_SURFACE_ELASTICITY = 0.0f;
const float ENVIRONMENT_SURFACE_DAMPING = 0.01f;
const float ENVIRONMENT_COLLISION_FREQUENCY = 0.05f;
glm::vec3 penetration;
@ -896,8 +905,7 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime) {
}
void MyAvatar::updateCollisionWithVoxels(float deltaTime) {
float radius = _collisionRadius;
void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
const float VOXEL_ELASTICITY = 0.4f;
const float VOXEL_DAMPING = 0.0f;
const float VOXEL_COLLISION_FREQUENCY = 0.5f;
@ -917,8 +925,8 @@ void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity
// Update the avatar in response to a hard collision. Position will be reset exactly
// to outside the colliding surface. Velocity will be modified according to elasticity.
//
// if elasticity = 1.0, collision is inelastic.
// if elasticity > 1.0, collision is elastic.
// if elasticity = 0.0, collision is 100% inelastic.
// if elasticity = 1.0, collision is elastic.
//
_position -= penetration;
static float HALTING_VELOCITY = 0.2f;
@ -927,7 +935,7 @@ void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity
if (penetrationLength > EPSILON) {
_elapsedTimeSinceCollision = 0.0f;
glm::vec3 direction = penetration / penetrationLength;
_velocity -= glm::dot(_velocity, direction) * direction * elasticity;
_velocity -= glm::dot(_velocity, direction) * direction * (1.f + elasticity);
_velocity *= glm::clamp(1.f - damping, 0.0f, 1.0f);
if ((glm::length(_velocity) < HALTING_VELOCITY) && (glm::length(_thrust) == 0.f)) {
// If moving really slowly after a collision, and not applying forces, stop altogether
@ -966,11 +974,34 @@ void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTim
}
}
void MyAvatar::updateAvatarCollisions(float deltaTime) {
const float DEFAULT_HAND_RADIUS = 0.1f;
void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
// Reset detector for nearest avatar
_distanceToNearestAvatar = std::numeric_limits<float>::max();
// loop through all the other avatars for potential interactions
const AvatarHash& avatars = Application::getInstance()->getAvatarManager().getAvatarHash();
if (avatars.size() <= 1) {
// no need to compute a bunch of stuff if we have one or fewer avatars
return;
}
float myRadius = getHeight();
CollisionInfo collisionInfo;
foreach (const AvatarSharedPointer& avatarPointer, avatars) {
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
if (static_cast<Avatar*>(this) == avatar) {
// don't collide with ourselves
continue;
}
float distance = glm::length(_position - avatar->getPosition());
if (_distanceToNearestAvatar > distance) {
_distanceToNearestAvatar = distance;
}
float theirRadius = avatar->getHeight();
if (distance < myRadius + theirRadius) {
// TODO: Andrew to make avatar-avatar capsule collisions work here
}
}
}
class SortedAvatar {

View file

@ -113,7 +113,6 @@ private:
bool _isCollisionsOn;
bool _isThrustOn;
float _thrustMultiplier;
float _collisionRadius;
glm::vec3 _moveTarget;
int _moveTargetStepCounter;
QWeakPointer<AvatarData> _lookAtTargetAvatar;
@ -126,9 +125,9 @@ private:
void renderBody(bool forceRenderHead);
void updateThrust(float deltaTime);
void updateHandMovementAndTouching(float deltaTime);
void updateAvatarCollisions(float deltaTime);
void updateCollisionWithEnvironment(float deltaTime);
void updateCollisionWithVoxels(float deltaTime);
void updateCollisionWithAvatars(float deltaTime);
void updateCollisionWithEnvironment(float deltaTime, float radius);
void updateCollisionWithVoxels(float deltaTime, float radius);
void applyHardCollision(const glm::vec3& penetration, float elasticity, float damping);
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
void updateChatCircle(float deltaTime);

View file

@ -70,10 +70,6 @@ bool SkeletonModel::render(float alpha) {
Model::render(alpha);
if (Menu::getInstance()->isOptionChecked(MenuOption::CollisionProxies)) {
renderCollisionProxies(alpha);
}
return true;
}

View file

@ -98,15 +98,6 @@ void SixenseManager::update(float deltaTime) {
// Compute current velocity from position change
glm::vec3 rawVelocity = (position - palm->getRawPosition()) / deltaTime / 1000.f;
palm->setRawVelocity(rawVelocity); // meters/sec
/*
if (i == 0)
{
printf("ADEBUG rawVelocity = [%e, %e, %e]\n",
rawVelocity.x,
rawVelocity.y,
rawVelocity.z);
}
*/
palm->setRawPosition(position);
// use the velocity to determine whether there's any movement (if the hand isn't new)

View file

@ -834,6 +834,12 @@ QString getTopModelID(const QMultiHash<QString, QString>& parentMap,
}
}
QString getString(const QVariant& value) {
// if it's a list, return the first entry
QVariantList list = value.toList();
return list.isEmpty() ? value.toString() : list.at(0).toString();
}
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
QHash<QString, ExtractedMesh> meshes;
QVector<ExtractedBlendshape> blendshapes;
@ -847,14 +853,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QHash<QString, QString> bumpTextures;
QVariantHash joints = mapping.value("joint").toHash();
QString jointEyeLeftName = processID(joints.value("jointEyeLeft", "jointEyeLeft").toString());
QString jointEyeRightName = processID(joints.value("jointEyeRight", "jointEyeRight").toString());
QString jointNeckName = processID(joints.value("jointNeck", "jointNeck").toString());
QString jointRootName = processID(joints.value("jointRoot", "jointRoot").toString());
QString jointLeanName = processID(joints.value("jointLean", "jointLean").toString());
QString jointHeadName = processID(joints.value("jointHead", "jointHead").toString());
QString jointLeftHandName = processID(joints.value("jointLeftHand", "jointLeftHand").toString());
QString jointRightHandName = processID(joints.value("jointRightHand", "jointRightHand").toString());
QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft")));
QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight")));
QString jointNeckName = processID(getString(joints.value("jointNeck", "jointNeck")));
QString jointRootName = processID(getString(joints.value("jointRoot", "jointRoot")));
QString jointLeanName = processID(getString(joints.value("jointLean", "jointLean")));
QString jointHeadName = processID(getString(joints.value("jointHead", "jointHead")));
QString jointLeftHandName = processID(getString(joints.value("jointLeftHand", "jointLeftHand")));
QString jointRightHandName = processID(getString(joints.value("jointRightHand", "jointRightHand")));
QVariantList jointLeftFingerNames = joints.values("jointLeftFinger");
QVariantList jointRightFingerNames = joints.values("jointRightFinger");
QVariantList jointLeftFingertipNames = joints.values("jointLeftFingertip");
@ -1265,7 +1271,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
const glm::mat4& transform = geometry.joints.at(geometry.neckJointIndex).transform;
geometry.neckPivot = glm::vec3(transform[3][0], transform[3][1], transform[3][2]);
}
geometry.bindExtents.minimum = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
geometry.bindExtents.maximum = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);

View file

@ -290,19 +290,24 @@ void GeometryCache::renderGrid(int xDivisions, int yDivisions) {
buffer.release();
}
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url) {
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback) {
if (!url.isValid() && fallback.isValid()) {
return getGeometry(fallback);
}
QSharedPointer<NetworkGeometry> geometry = _networkGeometry.value(url);
if (geometry.isNull()) {
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url));
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ?
getGeometry(fallback) : QSharedPointer<NetworkGeometry>()));
_networkGeometry.insert(url, geometry);
}
return geometry;
}
NetworkGeometry::NetworkGeometry(const QUrl& url) :
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback) :
_modelRequest(url),
_modelReply(NULL),
_mappingReply(NULL),
_fallback(fallback),
_attempts(0)
{
if (!url.isValid()) {
@ -369,18 +374,37 @@ void NetworkGeometry::makeModelRequest() {
void NetworkGeometry::handleModelReplyError() {
QDebug debug = qDebug() << _modelReply->errorString();
QNetworkReply::NetworkError error = _modelReply->error();
_modelReply->disconnect(this);
_modelReply->deleteLater();
_modelReply = NULL;
// retry with increasing delays
const int MAX_ATTEMPTS = 8;
const int BASE_DELAY_MS = 1000;
if (++_attempts < MAX_ATTEMPTS) {
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest()));
debug << " -- retrying...";
// retry for certain types of failures
switch (error) {
case QNetworkReply::RemoteHostClosedError:
case QNetworkReply::TimeoutError:
case QNetworkReply::TemporaryNetworkFailureError:
case QNetworkReply::ProxyConnectionClosedError:
case QNetworkReply::ProxyTimeoutError:
case QNetworkReply::UnknownNetworkError:
case QNetworkReply::UnknownProxyError:
case QNetworkReply::UnknownContentError:
case QNetworkReply::ProtocolFailure: {
// retry with increasing delays
const int MAX_ATTEMPTS = 8;
const int BASE_DELAY_MS = 1000;
if (++_attempts < MAX_ATTEMPTS) {
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest()));
debug << " -- retrying...";
return;
}
// fall through to final failure
}
default:
maybeLoadFallback();
break;
}
}
void NetworkGeometry::handleMappingReplyError() {
@ -415,6 +439,7 @@ void NetworkGeometry::maybeReadModelWithMapping() {
} catch (const QString& error) {
qDebug() << "Error reading " << url << ": " << error;
maybeLoadFallback();
return;
}
@ -507,6 +532,24 @@ void NetworkGeometry::maybeReadModelWithMapping() {
_meshes.append(networkMesh);
}
emit loaded();
}
void NetworkGeometry::loadFallback() {
_geometry = _fallback->_geometry;
_meshes = _fallback->_meshes;
emit loaded();
}
void NetworkGeometry::maybeLoadFallback() {
if (_fallback) {
if (_fallback->isLoaded()) {
loadFallback();
} else {
connect(_fallback.data(), SIGNAL(loaded()), SLOT(loadFallback()));
}
}
}
bool NetworkMeshPart::isTranslucent() const {

View file

@ -37,7 +37,8 @@ public:
void renderGrid(int xDivisions, int yDivisions);
/// Loads geometry from the specified URL.
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url);
/// \param fallback a fallback URL to load if the desired one is unavailable
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl());
private:
@ -58,7 +59,7 @@ class NetworkGeometry : public QObject {
public:
NetworkGeometry(const QUrl& url);
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback);
~NetworkGeometry();
bool isLoaded() const { return !_geometry.joints.isEmpty(); }
@ -69,18 +70,26 @@ public:
/// Returns the average color of all meshes in the geometry.
glm::vec4 computeAverageColor() const;
signals:
void loaded();
private slots:
void makeModelRequest();
void handleModelReplyError();
void handleMappingReplyError();
void maybeReadModelWithMapping();
void loadFallback();
private:
void maybeLoadFallback();
QNetworkRequest _modelRequest;
QNetworkReply* _modelReply;
QNetworkReply* _mappingReply;
QSharedPointer<NetworkGeometry> _fallback;
int _attempts;
FBXGeometry _geometry;

View file

@ -390,7 +390,7 @@ float Model::getRightArmLength() const {
return getLimbLength(getRightHandJointIndex());
}
void Model::setURL(const QUrl& url) {
void Model::setURL(const QUrl& url, const QUrl& fallback) {
// don't recreate the geometry if it's the same URL
if (_url == url) {
return;
@ -401,7 +401,7 @@ void Model::setURL(const QUrl& url) {
deleteGeometry();
_dilatedTextures.clear();
_geometry = Application::getInstance()->getGeometryCache()->getGeometry(url);
_geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
}
glm::vec4 Model::computeAverageColor() const {
@ -437,11 +437,11 @@ bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
return false;
}
bool Model::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
glm::vec3& penetration, float boneScale, int skipIndex) const {
bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionInfo& collisionInfo, float boneScale, int skipIndex) const {
int jointIndex = -1;
const glm::vec3 relativeCenter = penetratorCenter - _translation;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
bool didPenetrate = false;
glm::vec3 totalPenetration;
float radiusScale = extractUniformScale(_scale) * boneScale;
for (int i = 0; i < _jointStates.size(); i++) {
@ -468,12 +468,16 @@ bool Model::findSpherePenetration(const glm::vec3& penetratorCenter, float penet
if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end,
startRadius, endRadius, bonePenetration)) {
totalPenetration = addPenetrations(totalPenetration, bonePenetration);
didPenetrate = true;
// TODO: Andrew to try to keep the joint furthest toward the root
jointIndex = i;
}
outerContinue: ;
}
if (didPenetrate) {
penetration = totalPenetration;
if (jointIndex != -1) {
// don't store collisionInfo._model at this stage, let the outer context do that
collisionInfo._penetration = totalPenetration;
collisionInfo._jointIndex = jointIndex;
collisionInfo._contactPoint = penetratorCenter + penetratorRadius * glm::normalize(totalPenetration);
return true;
}
return false;
@ -548,6 +552,9 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int last
glm::vec3 relativePosition = position - _translation;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
if (freeLineage.isEmpty()) {
return false;
}
if (lastFreeIndex == -1) {
lastFreeIndex = freeLineage.last();
}
@ -706,6 +713,37 @@ void Model::renderCollisionProxies(float alpha) {
glPopMatrix();
}
bool Model::poke(ModelCollisionInfo& collision) {
// This needs work. At the moment it can wiggle joints that are free to move (such as arms)
// but unmovable joints (such as torso) cannot be influenced at all.
glm::vec3 jointPosition(0.f);
if (getJointPosition(collision._jointIndex, jointPosition)) {
int jointIndex = collision._jointIndex;
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);
glm::vec3 contactPoint = collision._contactPoint - start;
glm::vec3 penetrationEnd = contactPoint + collision._penetration;
glm::vec3 axis = glm::cross(contactPoint, penetrationEnd);
float travel = glm::length(axis);
const float MIN_TRAVEL = 1.0e-8f;
if (travel > MIN_TRAVEL) {
// compute the new position of the joint
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(glm::degrees(angle), axis) * (end - start);
// try to move it
return setJointPosition(jointIndex, newEnd, -1, true);
}
}
}
return false;
}
void Model::deleteGeometry() {
foreach (Model* attachment, _attachments) {
delete attachment;

View file

@ -17,6 +17,16 @@
#include "ProgramObject.h"
#include "TextureCache.h"
class Model;
// TODO: Andrew to move this into its own file
class ModelCollisionInfo : public CollisionInfo {
public:
ModelCollisionInfo() : CollisionInfo(), _model(NULL), _jointIndex(-1) {}
Model* _model;
int _jointIndex;
};
/// A generic 3D model displaying geometry loaded from a URL.
class Model : public QObject {
Q_OBJECT
@ -51,7 +61,7 @@ public:
void simulate(float deltaTime);
bool render(float alpha);
Q_INVOKABLE void setURL(const QUrl& url);
Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl());
const QUrl& getURL() const { return _url; }
/// Returns the extents of the model in its bind pose.
@ -149,8 +159,14 @@ public:
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
glm::vec3& penetration, float boneScale = 1.0f, int skipIndex = -1) const;
bool findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius,
ModelCollisionInfo& collision, float boneScale = 1.0f, int skipIndex = -1) const;
void renderCollisionProxies(float alpha);
/// \param collisionInfo info about the collision
/// \return true if collision affects the Model
bool poke(ModelCollisionInfo& collisionInfo);
protected:
@ -209,8 +225,6 @@ protected:
void applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain = true);
void renderCollisionProxies(float alpha);
private:
void deleteGeometry();

View file

@ -63,7 +63,7 @@ QByteArray AvatarData::toByteArray() {
if (!_headData) {
_headData = new HeadData(this);
}
// lazily allocate memory for HeadData in case we're not an Avatar instance
// lazily allocate memory for HandData in case we're not an Avatar instance
if (!_handData) {
_handData = new HandData(this);
}
@ -301,13 +301,15 @@ QByteArray AvatarData::identityByteArray() {
}
void AvatarData::setFaceModelURL(const QUrl& faceModelURL) {
qDebug() << "Changing face model for avatar to" << faceModelURL.toString();
_faceModelURL = faceModelURL;
_faceModelURL = faceModelURL.isEmpty() ? DEFAULT_HEAD_MODEL_URL : faceModelURL;
qDebug() << "Changing face model for avatar to" << _faceModelURL.toString();
}
void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
qDebug() << "Changing skeleton model for avatar to" << skeletonModelURL.toString();
_skeletonModelURL = skeletonModelURL;
_skeletonModelURL = skeletonModelURL.isEmpty() ? DEFAULT_BODY_MODEL_URL : skeletonModelURL;
qDebug() << "Changing skeleton model for avatar to" << _skeletonModelURL.toString();
}
void AvatarData::setClampedTargetScale(float targetScale) {

View file

@ -52,6 +52,9 @@ static const float MIN_AVATAR_SCALE = .005f;
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fbx");
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fbx");
enum KeyState {
NO_KEY_DOWN = 0,
INSERT_KEY_DOWN,
@ -131,16 +134,11 @@ public:
virtual const glm::vec3& getVelocity() const { return vec3Zero; }
/// Checks for penetration between the described sphere and the avatar.
/// \param penetratorCenter the center of the penetration test sphere
/// \param penetratorRadius the radius of the penetration test sphere
/// \param penetration[out] the vector in which to store the penetration
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
/// \return whether or not the sphere penetrated
virtual bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius,
glm::vec3& penetration, int skeletonSkipIndex = -1) const { return false; }
virtual bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
return false;
}
virtual bool findSphereCollision(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
virtual bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
return false;
}

View file

@ -82,6 +82,14 @@ bool AttributeValue::operator==(void* other) const {
return _attribute && _attribute->equal(_value, other);
}
bool AttributeValue::operator!=(const AttributeValue& other) const {
return _attribute != other._attribute || (_attribute && !_attribute->equal(_value, other._value));
}
bool AttributeValue::operator!=(void* other) const {
return !_attribute || !_attribute->equal(_value, other);
}
OwnedAttributeValue::OwnedAttributeValue(const AttributePointer& attribute, void* value) :
AttributeValue(attribute, value) {
}

View file

@ -105,6 +105,9 @@ public:
bool operator==(const AttributeValue& other) const;
bool operator==(void* other) const;
bool operator!=(const AttributeValue& other) const;
bool operator!=(void* other) const;
protected:
AttributePointer _attribute;

View file

@ -415,12 +415,16 @@ public:
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
Bitstream& operator<<(Bitstream& out, const X& obj); \
Bitstream& operator>>(Bitstream& in, X& obj); \
bool operator==(const X& first, const X& second); \
bool operator!=(const X& first, const X& second); \
static const int* _TypePtr##X = &X::Type;
#else
#define STRINGIFY(x) #x
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
Bitstream& operator<<(Bitstream& out, const X& obj); \
Bitstream& operator>>(Bitstream& in, X& obj); \
bool operator==(const X& first, const X& second); \
bool operator!=(const X& first, const X& second); \
static const int* _TypePtr##X = &X::Type; \
_Pragma(STRINGIFY(unused(_TypePtr##X)))
#endif

View file

@ -20,7 +20,8 @@ const int MAX_DATAGRAM_SIZE = MAX_PACKET_SIZE;
const int DEFAULT_MAX_PACKET_SIZE = 3000;
DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader) :
DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject* parent) :
QObject(parent),
_outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly),
_outputStream(_outgoingPacketStream),
_incomingDatagramStream(&_incomingDatagramBuffer),
@ -174,13 +175,13 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
}
// read and dispatch the high-priority messages
int highPriorityMessageCount;
quint32 highPriorityMessageCount;
_incomingPacketStream >> highPriorityMessageCount;
int newHighPriorityMessages = highPriorityMessageCount - _receivedHighPriorityMessages;
for (int i = 0; i < highPriorityMessageCount; i++) {
for (quint32 i = 0; i < highPriorityMessageCount; i++) {
QVariant data;
_inputStream >> data;
if (i >= _receivedHighPriorityMessages) {
if ((int)i >= _receivedHighPriorityMessages) {
handleHighPriorityMessage(data);
}
}
@ -192,10 +193,10 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
// read the reliable data, if any
quint32 reliableChannels;
_incomingPacketStream >> reliableChannels;
for (int i = 0; i < reliableChannels; i++) {
for (quint32 i = 0; i < reliableChannels; i++) {
quint32 channelIndex;
_incomingPacketStream >> channelIndex;
getReliableOutputChannel(channelIndex)->readData(_incomingPacketStream);
getReliableInputChannel(channelIndex)->readData(_incomingPacketStream);
}
_incomingPacketStream.device()->seek(0);
@ -311,6 +312,178 @@ void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) {
}
}
const int INITIAL_CIRCULAR_BUFFER_CAPACITY = 16;
CircularBuffer::CircularBuffer(QObject* parent) :
QIODevice(parent),
_data(INITIAL_CIRCULAR_BUFFER_CAPACITY, 0),
_position(0),
_size(0),
_offset(0) {
}
void CircularBuffer::append(const char* data, int length) {
// resize to fit
int oldSize = _size;
resize(_size + length);
// write our data in up to two segments: one from the position to the end, one from the beginning
int end = (_position + oldSize) % _data.size();
int firstSegment = qMin(length, _data.size() - end);
memcpy(_data.data() + end, data, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
memcpy(_data.data(), data + firstSegment, secondSegment);
}
}
void CircularBuffer::remove(int length) {
_position = (_position + length) % _data.size();
_size -= length;
}
QByteArray CircularBuffer::readBytes(int offset, int length) const {
// write in up to two segments
QByteArray array;
int start = (_position + offset) % _data.size();
int firstSegment = qMin(length, _data.size() - start);
array.append(_data.constData() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
array.append(_data.constData(), secondSegment);
}
return array;
}
void CircularBuffer::writeToStream(int offset, int length, QDataStream& out) const {
// write in up to two segments
int start = (_position + offset) % _data.size();
int firstSegment = qMin(length, _data.size() - start);
out.writeRawData(_data.constData() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
out.writeRawData(_data.constData(), secondSegment);
}
}
void CircularBuffer::readFromStream(int offset, int length, QDataStream& in) {
// resize to fit
int requiredSize = offset + length;
if (requiredSize > _size) {
resize(requiredSize);
}
// read in up to two segments
int start = (_position + offset) % _data.size();
int firstSegment = qMin(length, _data.size() - start);
in.readRawData(_data.data() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
in.readRawData(_data.data(), secondSegment);
}
}
void CircularBuffer::appendToBuffer(int offset, int length, CircularBuffer& buffer) const {
// append in up to two segments
int start = (_position + offset) % _data.size();
int firstSegment = qMin(length, _data.size() - start);
buffer.append(_data.constData() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
buffer.append(_data.constData(), secondSegment);
}
}
bool CircularBuffer::atEnd() const {
return _offset >= _size;
}
qint64 CircularBuffer::bytesAvailable() const {
return _size - _offset + QIODevice::bytesAvailable();
}
bool CircularBuffer::canReadLine() const {
for (int offset = _offset; offset < _size; offset++) {
if (_data.at((_position + offset) % _data.size()) == '\n') {
return true;
}
}
return false;
}
bool CircularBuffer::open(OpenMode flags) {
return QIODevice::open(flags | QIODevice::Unbuffered);
}
qint64 CircularBuffer::pos() const {
return _offset;
}
bool CircularBuffer::seek(qint64 pos) {
if (pos < 0 || pos > _size) {
return false;
}
_offset = pos;
return true;
}
qint64 CircularBuffer::size() const {
return _size;
}
qint64 CircularBuffer::readData(char* data, qint64 length) {
int readable = qMin((int)length, _size - _offset);
// read in up to two segments
int start = (_position + _offset) % _data.size();
int firstSegment = qMin((int)length, _data.size() - start);
memcpy(data, _data.constData() + start, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
memcpy(data + firstSegment, _data.constData(), secondSegment);
}
_offset += readable;
return readable;
}
qint64 CircularBuffer::writeData(const char* data, qint64 length) {
// resize to fit
int requiredSize = _offset + length;
if (requiredSize > _size) {
resize(requiredSize);
}
// write in up to two segments
int start = (_position + _offset) % _data.size();
int firstSegment = qMin((int)length, _data.size() - start);
memcpy(_data.data() + start, data, firstSegment);
int secondSegment = length - firstSegment;
if (secondSegment > 0) {
memcpy(_data.data(), data + firstSegment, secondSegment);
}
_offset += length;
return length;
}
void CircularBuffer::resize(int size) {
if (size > _data.size()) {
// double our capacity until we can fit the desired length
int newCapacity = _data.size();
do {
newCapacity *= 2;
} while (size > newCapacity);
int oldCapacity = _data.size();
_data.resize(newCapacity);
int trailing = _position + _size - oldCapacity;
if (trailing > 0) {
memcpy(_data.data() + oldCapacity, _data.constData(), trailing);
}
}
_size = size;
}
SpanList::SpanList() : _totalSet(0) {
}
@ -369,7 +542,7 @@ int SpanList::set(int offset, int length) {
int SpanList::setSpans(QList<Span>::iterator it, int length) {
int remainingLength = length;
int totalRemoved = 0;
for (; it != _spans.end(); it++) {
for (; it != _spans.end(); it = _spans.erase(it)) {
if (remainingLength < it->unset) {
it->unset -= remainingLength;
totalRemoved += remainingLength;
@ -378,7 +551,6 @@ int SpanList::setSpans(QList<Span>::iterator it, int length) {
int combined = it->unset + it->set;
remainingLength = qMax(remainingLength - combined, 0);
totalRemoved += combined;
it = _spans.erase(it);
_totalSet -= it->set;
}
return qMax(length, totalRemoved);
@ -424,14 +596,13 @@ void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSeq
break;
}
spanCount++;
remainingBytes -= getBytesToWrite(first, span.unset);
remainingBytes -= getBytesToWrite(first, qMin(remainingBytes, span.unset));
position += (span.unset + span.set);
}
int leftover = _buffer.pos() - position;
if (remainingBytes > 0 && leftover > 0) {
spanCount++;
remainingBytes -= getBytesToWrite(first, leftover);
remainingBytes -= getBytesToWrite(first, qMin(remainingBytes, leftover));
}
}
@ -448,8 +619,9 @@ void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSeq
remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, span.unset), spans);
position += (span.unset + span.set);
}
if (remainingBytes > 0 && position < _buffer.pos()) {
remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, (int)(_buffer.pos() - position)), spans);
int leftover = _buffer.pos() - position;
if (remainingBytes > 0 && leftover > 0) {
remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, leftover), spans);
}
}
}
@ -473,16 +645,16 @@ int ReliableChannel::writeSpan(QDataStream& out, bool& first, int position, int
spans.append(span);
out << (quint32)span.offset;
out << (quint32)length;
out.writeRawData(_buffer.data().constData() + position, length);
_buffer.writeToStream(position, length, out);
return length;
}
void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& span) {
int advancement = _acknowledged.set(span.offset - _offset, span.length);
if (advancement > 0) {
// TODO: better way of pruning buffer
_buffer.buffer() = _buffer.buffer().right(_buffer.size() - advancement);
_buffer.remove(advancement);
_buffer.seek(_buffer.size());
_offset += advancement;
_writePosition = qMax(_writePosition - advancement, 0);
}
@ -491,38 +663,40 @@ void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& spa
void ReliableChannel::readData(QDataStream& in) {
quint32 segments;
in >> segments;
for (int i = 0; i < segments; i++) {
bool readSome = false;
for (quint32 i = 0; i < segments; i++) {
quint32 offset, size;
in >> offset >> size;
int position = offset - _offset;
int end = position + size;
if (_assemblyBuffer.size() < end) {
_assemblyBuffer.resize(end);
}
if (end <= 0) {
in.skipRawData(size);
} else if (position < 0) {
in.skipRawData(-position);
in.readRawData(_assemblyBuffer.data(), size + position);
_assemblyBuffer.readFromStream(0, end, in);
} else {
in.readRawData(_assemblyBuffer.data() + position, size);
_assemblyBuffer.readFromStream(position, size, in);
}
int advancement = _acknowledged.set(position, size);
if (advancement > 0) {
// TODO: better way of pruning buffer
_buffer.buffer().append(_assemblyBuffer.constData(), advancement);
emit _buffer.readyRead();
_assemblyBuffer = _assemblyBuffer.right(_assemblyBuffer.size() - advancement);
_assemblyBuffer.appendToBuffer(0, advancement, _buffer);
_assemblyBuffer.remove(advancement);
_offset += advancement;
readSome = true;
}
}
// when the read head is sufficiently advanced into the buffer, prune it off. this along
// with other buffer usages should be replaced with a circular buffer
const int PRUNE_SIZE = 8192;
if (_buffer.pos() > PRUNE_SIZE) {
_buffer.buffer() = _buffer.buffer().right(_buffer.size() - _buffer.pos());
// let listeners know that there's data to read
if (readSome) {
emit _buffer.readyRead();
}
// prune any read data from the buffer
if (_buffer.pos() > 0) {
_buffer.remove((int)_buffer.pos());
_buffer.seek(0);
}
}

View file

@ -32,7 +32,7 @@ public:
int firstPacketNumber;
};
DatagramSequencer(const QByteArray& datagramHeader = QByteArray());
DatagramSequencer(const QByteArray& datagramHeader = QByteArray(), QObject* parent = NULL);
/// Returns the packet number of the last packet sent.
int getOutgoingPacketNumber() const { return _outgoingPacketNumber; }
@ -58,7 +58,7 @@ public:
/// Returns the output channel at the specified index, creating it if necessary.
ReliableChannel* getReliableOutputChannel(int index = 0);
/// Returns the intput channel at the
/// Returns the intput channel at the specified index, creating it if necessary.
ReliableChannel* getReliableInputChannel(int index = 0);
/// Starts a new packet for transmission.
@ -167,6 +167,56 @@ private:
QHash<int, ReliableChannel*> _reliableInputChannels;
};
/// A circular buffer, where one may efficiently append data to the end or remove data from the beginning.
class CircularBuffer : public QIODevice {
public:
CircularBuffer(QObject* parent = NULL);
/// Appends data to the end of the buffer.
void append(const QByteArray& data) { append(data.constData(), data.size()); }
/// Appends data to the end of the buffer.
void append(const char* data, int length);
/// Removes data from the beginning of the buffer.
void remove(int length);
/// Reads part of the data from the buffer.
QByteArray readBytes(int offset, int length) const;
/// Writes part of the buffer to the supplied stream.
void writeToStream(int offset, int length, QDataStream& out) const;
/// Reads part of the buffer from the supplied stream.
void readFromStream(int offset, int length, QDataStream& in);
/// Appends part of the buffer to the supplied other buffer.
void appendToBuffer(int offset, int length, CircularBuffer& buffer) const;
virtual bool atEnd() const;
virtual qint64 bytesAvailable() const;
virtual bool canReadLine() const;
virtual bool open(OpenMode flags);
virtual qint64 pos() const;
virtual bool seek(qint64 pos);
virtual qint64 size() const;
protected:
virtual qint64 readData(char* data, qint64 length);
virtual qint64 writeData(const char* data, qint64 length);
private:
void resize(int size);
QByteArray _data;
int _position;
int _size;
int _offset;
};
/// A list of contiguous spans, alternating between set and unset. Conceptually, the list is preceeded by a set
/// span of infinite length and followed by an unset span of infinite length. Within those bounds, it alternates
/// between unset and set.
@ -208,6 +258,7 @@ public:
int getIndex() const { return _index; }
CircularBuffer& getBuffer() { return _buffer; }
QDataStream& getDataStream() { return _dataStream; }
Bitstream& getBitstream() { return _bitstream; }
@ -237,8 +288,8 @@ private:
void readData(QDataStream& in);
int _index;
QBuffer _buffer;
QByteArray _assemblyBuffer;
CircularBuffer _buffer;
CircularBuffer _assemblyBuffer;
QDataStream _dataStream;
Bitstream _bitstream;
float _priority;

View file

@ -11,12 +11,12 @@
#include <QDoubleSpinBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QItemEditorCreatorBase>
#include <QItemEditorFactory>
#include <QLineEdit>
#include <QMetaType>
#include <QPushButton>
#include <QScriptEngine>
#include <QStandardItemEditorCreator>
#include <QVBoxLayout>
#include <QtDebug>
@ -76,27 +76,48 @@ static QItemEditorFactory* getItemEditorFactory() {
return factory;
}
/// Because Windows doesn't necessarily have the staticMetaObject available when we want to create,
/// this class simply delays the value property name lookup until actually requested.
template<class T> class LazyItemEditorCreator : public QItemEditorCreatorBase {
public:
virtual QWidget* createWidget(QWidget* parent) const { return new T(parent); }
virtual QByteArray valuePropertyName() const;
protected:
QByteArray _valuePropertyName;
};
template<class T> QByteArray LazyItemEditorCreator<T>::valuePropertyName() const {
if (_valuePropertyName.isNull()) {
const_cast<LazyItemEditorCreator<T>*>(this)->_valuePropertyName = T::staticMetaObject.userProperty().name();
}
return _valuePropertyName;
}
static QItemEditorCreatorBase* createDoubleEditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<DoubleEditor>();
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<DoubleEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<double>(), creator);
getItemEditorFactory()->registerEditor(qMetaTypeId<float>(), creator);
return creator;
}
static QItemEditorCreatorBase* createQColorEditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<QColorEditor>();
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<QColorEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<QColor>(), creator);
return creator;
}
static QItemEditorCreatorBase* createVec3EditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<Vec3Editor>();
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<Vec3Editor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<glm::vec3>(), creator);
return creator;
}
static QItemEditorCreatorBase* createParameterizedURLEditorCreator() {
QItemEditorCreatorBase* creator = new QStandardItemEditorCreator<ParameterizedURLEditor>();
QItemEditorCreatorBase* creator = new LazyItemEditorCreator<ParameterizedURLEditor>();
getItemEditorFactory()->registerEditor(qMetaTypeId<ParameterizedURL>(), creator);
return creator;
}
@ -120,6 +141,12 @@ QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode
return QUuid::fromRfc4122(QByteArray::fromRawData(data.constData() + headerSize, UUID_BYTES));
}
QByteArray signal(const char* signature) {
static QByteArray prototype = SIGNAL(dummyMethod());
QByteArray signal = prototype;
return signal.replace("dummyMethod()", signature);
}
bool Box::contains(const Box& other) const {
return other.minimum.x >= minimum.x && other.maximum.x <= maximum.x &&
other.minimum.y >= minimum.y && other.maximum.y <= maximum.y &&
@ -301,8 +328,7 @@ void ParameterizedURLEditor::continueUpdatingParameters() {
QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName));
widgetProperty.write(widget, _url.getParameters().value(parameter.name));
if (widgetProperty.hasNotifySignal()) {
connect(widget, QByteArray(SIGNAL()).append(widgetProperty.notifySignal().methodSignature()),
SLOT(updateURL()));
connect(widget, signal(widgetProperty.notifySignal().methodSignature()), SLOT(updateURL()));
}
}
}

View file

@ -33,6 +33,9 @@ class NetworkProgram;
/// \return the session ID, or a null ID if invalid (in which case a warning will be logged)
QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode, int& headerPlusIDSize);
/// Performs the runtime equivalent of Qt's SIGNAL macro, which is to attach a prefix to the signature.
QByteArray signal(const char* signature);
/// A streamable axis-aligned bounding box.
class Box {
STREAMABLE

View file

@ -13,6 +13,7 @@
#include <QVBoxLayout>
#include "Bitstream.h"
#include "MetavoxelUtil.h"
#include "SharedObject.h"
SharedObject::SharedObject() : _referenceCount(0) {
@ -204,8 +205,7 @@ void SharedObjectEditor::updateType() {
QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName));
widgetProperty.write(widget, property.read(newObject));
if (widgetProperty.hasNotifySignal()) {
connect(widget, QByteArray(SIGNAL()).append(widgetProperty.notifySignal().methodSignature()),
SLOT(propertyChanged()));
connect(widget, signal(widgetProperty.notifySignal().methodSignature()), SLOT(propertyChanged()));
}
}
}

View file

@ -39,6 +39,7 @@ OctreeEditPacketSender::OctreeEditPacketSender() :
}
OctreeEditPacketSender::~OctreeEditPacketSender() {
_pendingPacketsLock.lock();
while (!_preServerSingleMessagePackets.empty()) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
delete packet;
@ -49,6 +50,7 @@ OctreeEditPacketSender::~OctreeEditPacketSender() {
delete packet;
_preServerPackets.erase(_preServerPackets.begin());
}
_pendingPacketsLock.unlock();
//printf("OctreeEditPacketSender::~OctreeEditPacketSender() [%p] destroyed... \n", this);
}
@ -115,6 +117,7 @@ void OctreeEditPacketSender::processPreServerExistsPackets() {
assert(serversExist()); // we should only be here if we have jurisdictions
// First send out all the single message packets...
_pendingPacketsLock.lock();
while (!_preServerSingleMessagePackets.empty()) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
queuePacketToNodes(&packet->_currentBuffer[0], packet->_currentSize);
@ -129,6 +132,7 @@ void OctreeEditPacketSender::processPreServerExistsPackets() {
delete packet;
_preServerPackets.erase(_preServerPackets.begin());
}
_pendingPacketsLock.unlock();
// if while waiting for the jurisdictions the caller called releaseQueuedMessages()
// then we want to honor that request now.
@ -140,8 +144,10 @@ void OctreeEditPacketSender::processPreServerExistsPackets() {
void OctreeEditPacketSender::queuePendingPacketToNodes(PacketType type, unsigned char* buffer, ssize_t length) {
// If we're asked to save messages while waiting for voxel servers to arrive, then do so...
if (_maxPendingMessages > 0) {
EditPacketBuffer* packet = new EditPacketBuffer(type, buffer, length);
_pendingPacketsLock.lock();
_preServerSingleMessagePackets.push_back(packet);
// if we've saved MORE than our max, then clear out the oldest packet...
int allPendingMessages = _preServerSingleMessagePackets.size() + _preServerPackets.size();
@ -150,6 +156,7 @@ void OctreeEditPacketSender::queuePendingPacketToNodes(PacketType type, unsigned
delete packet;
_preServerSingleMessagePackets.erase(_preServerSingleMessagePackets.begin());
}
_pendingPacketsLock.unlock();
}
}
@ -197,6 +204,7 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned ch
if (!serversExist()) {
if (_maxPendingMessages > 0) {
EditPacketBuffer* packet = new EditPacketBuffer(type, codeColorBuffer, length);
_pendingPacketsLock.lock();
_preServerPackets.push_back(packet);
// if we've saved MORE than out max, then clear out the oldest packet...
@ -206,12 +214,11 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned ch
delete packet;
_preServerPackets.erase(_preServerPackets.begin());
}
}
_pendingPacketsLock.unlock();
}
return; // bail early
}
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
// We want to filter out edit messages for servers based on the server's Jurisdiction
// But we can't really do that with a packed message, since each edit message could be destined
// for a different server... So we need to actually manage multiple queued packets... one
@ -229,18 +236,14 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned ch
if ((*_serverJurisdictions).find(nodeUUID) != (*_serverJurisdictions).end()) {
const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID];
isMyJurisdiction = (map.isMyJurisdiction(codeColorBuffer, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__ << " isMyJurisdiction=" << isMyJurisdiction;
} else {
isMyJurisdiction = false;
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
}
}
if (isMyJurisdiction) {
EditPacketBuffer& packetBuffer = _pendingEditPackets[nodeUUID];
packetBuffer._nodeUUID = nodeUUID;
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
// If we're switching type, then we send the last one and start over
if ((type != packetBuffer._currentType && packetBuffer._currentSize > 0) ||
(packetBuffer._currentSize + length >= _maxPacketSize)) {
@ -259,11 +262,8 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned ch
// fixup the buffer for any clock skew
if (node->getClockSkewUsec() != 0) {
adjustEditPacketForClockSkew(codeColorBuffer, length, node->getClockSkewUsec());
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
}
//qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
memcpy(&packetBuffer._currentBuffer[packetBuffer._currentSize], codeColorBuffer, length);
packetBuffer._currentSize += length;
}
@ -280,14 +280,12 @@ void OctreeEditPacketSender::releaseQueuedMessages() {
} else {
for (std::map<QUuid, EditPacketBuffer>::iterator i = _pendingEditPackets.begin(); i != _pendingEditPackets.end(); i++) {
releaseQueuedPacket(i->second);
//qDebug() << "releaseQueuedMessages() line:" << __LINE__;
}
}
}
void OctreeEditPacketSender::releaseQueuedPacket(EditPacketBuffer& packetBuffer) {
if (packetBuffer._currentSize > 0 && packetBuffer._currentType != PacketTypeUnknown) {
//qDebug() << "OctreeEditPacketSender::releaseQueuedPacket() line:" << __LINE__;
queuePacketToNode(packetBuffer._nodeUUID, &packetBuffer._currentBuffer[0], packetBuffer._currentSize);
}
packetBuffer._currentSize = 0;

View file

@ -105,8 +105,9 @@ protected:
// These are packets that are waiting to be processed because we don't yet know if there are servers
int _maxPendingMessages;
bool _releaseQueuedMessagesPending;
std::vector<EditPacketBuffer*> _preServerPackets; // these will get packed into other larger packets
std::vector<EditPacketBuffer*> _preServerSingleMessagePackets; // these will go out as is
QMutex _pendingPacketsLock;
QVector<EditPacketBuffer*> _preServerPackets; // these will get packed into other larger packets
QVector<EditPacketBuffer*> _preServerSingleMessagePackets; // these will go out as is
NodeToJurisdictionMap* _serverJurisdictions;

View file

@ -139,18 +139,22 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA)
// handle A particle
particleA->setVelocity(particleA->getVelocity() - axialVelocity * (2.0f * massB / totalMass));
particleA->setPosition(particleA->getPosition() - 0.5f * penetration);
ParticleProperties propertiesA;
ParticleID particleAid(particleA->getID());
propertiesA.copyFromParticle(*particleA);
propertiesA.setVelocity(particleA->getVelocity() * (float)TREE_SCALE);
propertiesA.setPosition(particleA->getPosition() * (float)TREE_SCALE);
_packetSender->queueParticleEditMessage(PacketTypeParticleAddOrEdit, particleAid, propertiesA);
// handle B particle
particleB->setVelocity(particleB->getVelocity() + axialVelocity * (2.0f * massA / totalMass));
particleA->setPosition(particleB->getPosition() + 0.5f * penetration);
ParticleProperties propertiesB;
ParticleID particleBid(particleB->getID());
propertiesB.copyFromParticle(*particleB);
propertiesB.setVelocity(particleB->getVelocity() * (float)TREE_SCALE);
propertiesB.setPosition(particleB->getPosition() * (float)TREE_SCALE);
_packetSender->queueParticleEditMessage(PacketTypeParticleAddOrEdit, particleBid, propertiesB);
_packetSender->releaseQueuedMessages();
@ -182,7 +186,9 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
CollisionInfo collisionInfo;
collisionInfo._damping = DAMPING;
collisionInfo._elasticity = ELASTICITY;
if (avatar->findSphereCollision(center, radius, collisionInfo)) {
if (avatar->findSphereCollisionWithHands(center, radius, collisionInfo)) {
// TODO: Andrew to resurrect particles-vs-avatar body collisions
//avatar->findSphereCollisionWithSkeleton(center, radius, collisionInfo)) {
collisionInfo._addedVelocity /= (float)(TREE_SCALE);
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity();
if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) {

View file

@ -11,22 +11,28 @@
#include <glm/glm.hpp>
const uint32_t COLLISION_GROUP_ENVIRONMENT = 1U << 0;
const uint32_t COLLISION_GROUP_AVATARS = 1U << 1;
const uint32_t COLLISION_GROUP_VOXELS = 1U << 2;
const uint32_t COLLISION_GROUP_PARTICLES = 1U << 3;
class CollisionInfo {
public:
CollisionInfo()
: _damping(0.f),
_elasticity(1.f),
_contactPoint(0.f),
_penetration(0.f),
_addedVelocity(0.f) {
}
_addedVelocity(0.f) {
}
~CollisionInfo() {}
//glm::vec3 _point;
//glm::vec3 _normal;
float _damping;
float _elasticity;
glm::vec3 _penetration; // depth that bodyA is penetrates bodyB
glm::vec3 _contactPoint; // world-frame point on bodyA that is deepest into bodyB
glm::vec3 _penetration; // depth that bodyA penetrates into bodyB
glm::vec3 _addedVelocity;
};

View file

@ -98,6 +98,9 @@ const char* stringForLogType(QtMsgType msgType) {
const char DATE_STRING_FORMAT[] = "%F %H:%M:%S %z";
void Logging::verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (message.isEmpty()) {
return;
}
// log prefix is in the following format
// [DEBUG] [TIMESTAMP] [PID:PARENT_PID] [TARGET] logged string

View file

@ -81,13 +81,10 @@ NodeList::~NodeList() {
}
bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) {
// currently this just checks if the version in the packet matches our return from versionForPacketType
// may need to be expanded in the future for types and versions that take > than 1 byte
if (packet[1] != versionForPacketType(packetTypeForPacket(packet))
&& packetTypeForPacket(packet) != PacketTypeStunResponse) {
PacketType mismatchType = packetTypeForPacket(packet);
int numPacketTypeBytes = arithmeticCodingValueFromBuffer(packet.data());
int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data());
qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender"
<< uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but"

View file

@ -57,6 +57,9 @@ PacketVersion versionForPacketType(PacketType type) {
case PacketTypeDataServerConfirm:
case PacketTypeDataServerSend:
return 1;
case PacketTypeVoxelSet:
case PacketTypeVoxelSetDestructive:
return 1;
default:
return 0;
}

View file

@ -14,24 +14,13 @@
#include <PacketHeaders.h>
#include "VoxelEditPacketSender.h"
//////////////////////////////////////////////////////////////////////////////////////////
// Function: createVoxelEditMessage()
// Description: creates an "insert" or "remove" voxel message for a voxel code
// corresponding to the closest voxel which encloses a cube with
// lower corners at x,y,z, having side of length S.
// The input values x,y,z range 0.0 <= v < 1.0
// message should be either 'S' for SET or 'E' for ERASE
//
// IMPORTANT: The buffer is returned to you a buffer which you MUST delete when you are
// done with it.
//
// HACK ATTACK: Well, what if this is larger than the MTU? That's the caller's problem, we
// just truncate the message
//
// Complaints: Brad :)
#define GUESS_OF_VOXELCODE_SIZE 10
#define MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE 1500
#define SIZE_OF_COLOR_DATA sizeof(rgbColor)
/// creates an "insert" or "remove" voxel message for a voxel code corresponding to the closest voxel which encloses a cube
/// with lower corners at x,y,z, having side of length S. The input values x,y,z range 0.0 <= v < 1.0 message should be either
/// PacketTypeVoxelSet, PacketTypeVoxelSetDestructive, or PacketTypeVoxelErase. The buffer is returned to caller becomes
/// responsibility of caller and MUST be deleted by caller.
bool createVoxelEditMessage(PacketType command, short int sequence,
int voxelCount, VoxelDetail* voxelDetails, unsigned char*& bufferOut, int& sizeOut) {
@ -144,9 +133,9 @@ void VoxelEditPacketSender::queueVoxelEditMessages(PacketType type, int numberOf
for (int i = 0; i < numberOfDetails; i++) {
// use MAX_PACKET_SIZE since it's static and guarenteed to be larger than _maxPacketSize
static unsigned char bufferOut[MAX_PACKET_SIZE];
unsigned char bufferOut[MAX_PACKET_SIZE];
int sizeOut = 0;
if (encodeVoxelEditMessageDetails(type, 1, &details[i], &bufferOut[0], _maxPacketSize, sizeOut)) {
queueOctreeEditMessage(type, bufferOut, sizeOut);
}

View file

@ -521,6 +521,8 @@ bool VoxelTree::handlesEditPacketType(PacketType packetType) const {
}
}
const unsigned int REPORT_OVERFLOW_WARNING_INTERVAL = 100;
unsigned int overflowWarnings = 0;
int VoxelTree::processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
const unsigned char* editData, int maxLength, const SharedNodePointer& node) {
@ -532,9 +534,17 @@ int VoxelTree::processEditPacketData(PacketType packetType, const unsigned char*
int octets = numberOfThreeBitSectionsInCode(editData, maxLength);
if (octets == OVERFLOWED_OCTCODE_BUFFER) {
printf("WARNING! Got voxel edit record that would overflow buffer in numberOfThreeBitSectionsInCode(), ");
printf("bailing processing of packet!\n");
return 0;
overflowWarnings++;
if (overflowWarnings % REPORT_OVERFLOW_WARNING_INTERVAL == 1) {
qDebug() << "WARNING! Got voxel edit record that would overflow buffer in numberOfThreeBitSectionsInCode()"
" [NOTE: this is warning number" << overflowWarnings << ", the next" <<
(REPORT_OVERFLOW_WARNING_INTERVAL-1) << "will be suppressed.]";
QDebug debug = qDebug();
debug << "edit data contents:";
outputBufferBits(editData, maxLength, &debug);
}
return maxLength;
}
const int COLOR_SIZE_IN_BYTES = 3;
@ -542,9 +552,17 @@ int VoxelTree::processEditPacketData(PacketType packetType, const unsigned char*
int voxelDataSize = voxelCodeSize + COLOR_SIZE_IN_BYTES;
if (voxelDataSize > maxLength) {
printf("WARNING! Got voxel edit record that would overflow buffer, bailing processing of packet!\n");
printf("bailing processing of packet!\n");
return 0;
overflowWarnings++;
if (overflowWarnings % REPORT_OVERFLOW_WARNING_INTERVAL == 1) {
qDebug() << "WARNING! Got voxel edit record that would overflow buffer."
" [NOTE: this is warning number" << overflowWarnings << ", the next" <<
(REPORT_OVERFLOW_WARNING_INTERVAL-1) << "will be suppressed.]";
QDebug debug = qDebug();
debug << "edit data contents:";
outputBufferBits(editData, maxLength, &debug);
}
return maxLength;
}
readCodeColorBufferToTree(editData, destructive);

10
tests/CMakeLists.txt Normal file
View file

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 2.8)
# add the test directories
file(GLOB TEST_SUBDIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)
foreach(DIR ${TEST_SUBDIRS})
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${DIR})
add_subdirectory(${DIR})
endif()
endforeach()

View file

@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 2.8)
set(TARGET_NAME metavoxel-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(metavoxels ${TARGET_NAME} ${ROOT_DIR})
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,272 @@
//
// MetavoxelTests.cpp
// metavoxel-tests
//
// Created by Andrzej Kapolka on 2/7/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include <stdlib.h>
#include <SharedUtil.h>
#include "MetavoxelTests.h"
MetavoxelTests::MetavoxelTests(int& argc, char** argv) :
QCoreApplication(argc, argv) {
}
static int datagramsSent = 0;
static int datagramsReceived = 0;
static int highPriorityMessagesSent = 0;
static int highPriorityMessagesReceived = 0;
static int unreliableMessagesSent = 0;
static int unreliableMessagesReceived = 0;
static int streamedBytesSent = 0;
static int streamedBytesReceived = 0;
static int lowPriorityStreamedBytesSent = 0;
static int lowPriorityStreamedBytesReceived = 0;
bool MetavoxelTests::run() {
qDebug() << "Running metavoxel tests...";
// seed the random number generator so that our tests are reproducible
srand(0xBAAAAABE);
// create two endpoints with the same header
QByteArray datagramHeader("testheader");
Endpoint alice(datagramHeader), bob(datagramHeader);
alice.setOther(&bob);
bob.setOther(&alice);
// perform a large number of simulation iterations
const int SIMULATION_ITERATIONS = 100000;
for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
if (alice.simulate(i) || bob.simulate(i)) {
return true;
}
}
qDebug() << "Sent" << highPriorityMessagesSent << "high priority messages, received" << highPriorityMessagesReceived;
qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived;
qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
qDebug() << "Sent" << lowPriorityStreamedBytesSent << "low-priority streamed bytes, received" <<
lowPriorityStreamedBytesReceived;
qDebug() << "Sent" << datagramsSent << "datagrams, received" << datagramsReceived;
qDebug() << "All tests passed!";
return false;
}
static QByteArray createRandomBytes(int minimumSize, int maximumSize) {
QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0);
for (int i = 0; i < bytes.size(); i++) {
bytes[i] = rand();
}
return bytes;
}
static QByteArray createRandomBytes() {
const int MIN_BYTES = 4;
const int MAX_BYTES = 16;
return createRandomBytes(MIN_BYTES, MAX_BYTES);
}
Endpoint::Endpoint(const QByteArray& datagramHeader) :
_sequencer(new DatagramSequencer(datagramHeader, this)),
_highPriorityMessagesToSend(0.0f) {
connect(_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
connect(_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
connect(_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)),
SLOT(handleHighPriorityMessage(const QVariant&)));
connect(&_sequencer->getReliableInputChannel()->getBuffer(), SIGNAL(readyRead()), SLOT(readReliableChannel()));
connect(&_sequencer->getReliableInputChannel(1)->getBuffer(), SIGNAL(readyRead()), SLOT(readLowPriorityReliableChannel()));
// enqueue a large amount of data in a low-priority channel
ReliableChannel* output = _sequencer->getReliableOutputChannel(1);
output->setPriority(0.25f);
const int MIN_LOW_PRIORITY_DATA = 100000;
const int MAX_LOW_PRIORITY_DATA = 200000;
QByteArray bytes = createRandomBytes(MIN_LOW_PRIORITY_DATA, MAX_LOW_PRIORITY_DATA);
_lowPriorityDataStreamed.append(bytes);
output->getBuffer().write(bytes);
lowPriorityStreamedBytesSent += bytes.size();
}
static QVariant createRandomMessage() {
switch (randIntInRange(0, 2)) {
case 0: {
TestMessageA message = { randomBoolean(), rand(), randFloat() };
return QVariant::fromValue(message);
}
case 1: {
TestMessageB message = { createRandomBytes() };
return QVariant::fromValue(message);
}
case 2:
default: {
TestMessageC message;
message.foo = randomBoolean();
message.bar = rand();
message.baz = randFloat();
message.bong.foo = createRandomBytes();
return QVariant::fromValue(message);
}
}
}
static bool messagesEqual(const QVariant& firstMessage, const QVariant& secondMessage) {
int type = firstMessage.userType();
if (secondMessage.userType() != type) {
return false;
}
if (type == TestMessageA::Type) {
return firstMessage.value<TestMessageA>() == secondMessage.value<TestMessageA>();
} else if (type == TestMessageB::Type) {
return firstMessage.value<TestMessageB>() == secondMessage.value<TestMessageB>();
} else if (type == TestMessageC::Type) {
return firstMessage.value<TestMessageC>() == secondMessage.value<TestMessageC>();
} else {
return firstMessage == secondMessage;
}
}
bool Endpoint::simulate(int iterationNumber) {
// update/send our delayed datagrams
for (QList<QPair<QByteArray, int> >::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) {
if (it->second-- == 1) {
_other->_sequencer->receivedDatagram(it->first);
datagramsReceived++;
it = _delayedDatagrams.erase(it);
} else {
it++;
}
}
// enqueue some number of high priority messages
const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f;
const float MAX_HIGH_PRIORITY_MESSAGES = 2.0f;
_highPriorityMessagesToSend += randFloatInRange(MIN_HIGH_PRIORITY_MESSAGES, MAX_HIGH_PRIORITY_MESSAGES);
while (_highPriorityMessagesToSend >= 1.0f) {
QVariant message = createRandomMessage();
_highPriorityMessagesSent.append(message);
_sequencer->sendHighPriorityMessage(message);
highPriorityMessagesSent++;
_highPriorityMessagesToSend -= 1.0f;
}
// stream some random data
const int MIN_BYTES_TO_STREAM = 10;
const int MAX_BYTES_TO_STREAM = 100;
QByteArray bytes = createRandomBytes(MIN_BYTES_TO_STREAM, MAX_BYTES_TO_STREAM);
_dataStreamed.append(bytes);
streamedBytesSent += bytes.size();
_sequencer->getReliableOutputChannel()->getDataStream().writeRawData(bytes.constData(), bytes.size());
// send a packet
try {
Bitstream& out = _sequencer->startPacket();
SequencedTestMessage message = { iterationNumber, createRandomMessage() };
_unreliableMessagesSent.append(message);
unreliableMessagesSent++;
out << message;
_sequencer->endPacket();
} catch (const QString& message) {
qDebug() << message;
return true;
}
return false;
}
void Endpoint::sendDatagram(const QByteArray& datagram) {
datagramsSent++;
// some datagrams are dropped
const float DROP_PROBABILITY = 0.1f;
if (randFloat() < DROP_PROBABILITY) {
return;
}
// some are received out of order
const float REORDER_PROBABILITY = 0.1f;
if (randFloat() < REORDER_PROBABILITY) {
const int MIN_DELAY = 1;
const int MAX_DELAY = 5;
// have to copy the datagram; the one we're passed is a reference to a shared buffer
_delayedDatagrams.append(QPair<QByteArray, int>(QByteArray(datagram.constData(), datagram.size()),
randIntInRange(MIN_DELAY, MAX_DELAY)));
// and some are duplicated
const float DUPLICATE_PROBABILITY = 0.01f;
if (randFloat() > DUPLICATE_PROBABILITY) {
return;
}
}
_other->_sequencer->receivedDatagram(datagram);
datagramsReceived++;
}
void Endpoint::handleHighPriorityMessage(const QVariant& message) {
if (_other->_highPriorityMessagesSent.isEmpty()) {
throw QString("Received unsent/already sent high priority message.");
}
QVariant sentMessage = _other->_highPriorityMessagesSent.takeFirst();
if (!messagesEqual(message, sentMessage)) {
throw QString("Sent/received high priority message mismatch.");
}
highPriorityMessagesReceived++;
}
void Endpoint::readMessage(Bitstream& in) {
SequencedTestMessage message;
in >> message;
for (QList<SequencedTestMessage>::iterator it = _other->_unreliableMessagesSent.begin();
it != _other->_unreliableMessagesSent.end(); it++) {
if (it->sequenceNumber == message.sequenceNumber) {
if (!messagesEqual(it->submessage, message.submessage)) {
throw QString("Sent/received unreliable message mismatch.");
}
_other->_unreliableMessagesSent.erase(_other->_unreliableMessagesSent.begin(), it + 1);
unreliableMessagesReceived++;
return;
}
}
throw QString("Received unsent/already sent unreliable message.");
}
void Endpoint::readReliableChannel() {
CircularBuffer& buffer = _sequencer->getReliableInputChannel()->getBuffer();
QByteArray bytes = buffer.read(buffer.bytesAvailable());
if (_other->_dataStreamed.size() < bytes.size()) {
throw QString("Received unsent/already sent streamed data.");
}
QByteArray compare = _other->_dataStreamed.readBytes(0, bytes.size());
_other->_dataStreamed.remove(bytes.size());
if (compare != bytes) {
throw QString("Sent/received streamed data mismatch.");
}
streamedBytesReceived += bytes.size();
}
void Endpoint::readLowPriorityReliableChannel() {
CircularBuffer& buffer = _sequencer->getReliableInputChannel(1)->getBuffer();
QByteArray bytes = buffer.read(buffer.bytesAvailable());
if (_other->_lowPriorityDataStreamed.size() < bytes.size()) {
throw QString("Received unsent/already sent low-priority streamed data.");
}
QByteArray compare = _other->_lowPriorityDataStreamed.readBytes(0, bytes.size());
_other->_lowPriorityDataStreamed.remove(bytes.size());
if (compare != bytes) {
throw QString("Sent/received low-priority streamed data mismatch.");
}
lowPriorityStreamedBytesReceived += bytes.size();
}

View file

@ -0,0 +1,113 @@
//
// MetavoxelTests.h
// metavoxel-tests
//
// Created by Andrzej Kapolka on 2/7/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__MetavoxelTests__
#define __interface__MetavoxelTests__
#include <QCoreApplication>
#include <QVariantList>
#include <DatagramSequencer.h>
class SequencedTestMessage;
/// Tests various aspects of the metavoxel library.
class MetavoxelTests : public QCoreApplication {
Q_OBJECT
public:
MetavoxelTests(int& argc, char** argv);
/// Performs our various tests.
/// \return true if any of the tests failed.
bool run();
};
/// Represents a simulated endpoint.
class Endpoint : public QObject {
Q_OBJECT
public:
Endpoint(const QByteArray& datagramHeader);
void setOther(Endpoint* other) { _other = other; }
/// Perform a simulation step.
/// \return true if failure was detected
bool simulate(int iterationNumber);
private slots:
void sendDatagram(const QByteArray& datagram);
void handleHighPriorityMessage(const QVariant& message);
void readMessage(Bitstream& in);
void readReliableChannel();
void readLowPriorityReliableChannel();
private:
DatagramSequencer* _sequencer;
Endpoint* _other;
QList<QPair<QByteArray, int> > _delayedDatagrams;
float _highPriorityMessagesToSend;
QVariantList _highPriorityMessagesSent;
QList<SequencedTestMessage> _unreliableMessagesSent;
CircularBuffer _dataStreamed;
CircularBuffer _lowPriorityDataStreamed;
};
/// A simple test message.
class TestMessageA {
STREAMABLE
public:
STREAM bool foo;
STREAM int bar;
STREAM float baz;
};
DECLARE_STREAMABLE_METATYPE(TestMessageA)
// Another simple test message.
class TestMessageB {
STREAMABLE
public:
STREAM QByteArray foo;
};
DECLARE_STREAMABLE_METATYPE(TestMessageB)
// A test message that demonstrates inheritance and composition.
class TestMessageC : public TestMessageA {
STREAMABLE
public:
STREAM TestMessageB bong;
};
DECLARE_STREAMABLE_METATYPE(TestMessageC)
/// Combines a sequence number with a submessage; used for testing unreliable transport.
class SequencedTestMessage {
STREAMABLE
public:
STREAM int sequenceNumber;
STREAM QVariant submessage;
};
DECLARE_STREAMABLE_METATYPE(SequencedTestMessage)
#endif /* defined(__interface__MetavoxelTests__) */

View file

@ -0,0 +1,14 @@
//
// main.cpp
// metavoxel-tests
//
// Created by Andrzej Kapolka on 2/7/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
#include <QDebug>
#include "MetavoxelTests.h"
int main(int argc, char** argv) {
return MetavoxelTests(argc, argv).run();
}

View file

@ -90,7 +90,7 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
foreach (const Streamable& str, streamables) {
const QString& name = str.clazz.name;
out << "Bitstream& operator<< (Bitstream& out, const " << name << "& obj) {\n";
out << "Bitstream& operator<<(Bitstream& out, const " << name << "& obj) {\n";
foreach (const QString& base, str.clazz.bases) {
out << " out << static_cast<const " << base << "&>(obj);\n";
}
@ -100,7 +100,7 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
out << " return out;\n";
out << "}\n";
out << "Bitstream& operator>> (Bitstream& in, " << name << "& obj) {\n";
out << "Bitstream& operator>>(Bitstream& in, " << name << "& obj) {\n";
foreach (const QString& base, str.clazz.bases) {
out << " in >> static_cast<" << base << "&>(obj);\n";
}
@ -110,6 +110,58 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
out << " return in;\n";
out << "}\n";
out << "bool operator==(const " << name << "& first, const " << name << "& second) {\n";
if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) {
out << " return true";
} else {
out << " return ";
bool first = true;
foreach (const QString& base, str.clazz.bases) {
if (!first) {
out << " &&\n";
out << " ";
}
out << "static_cast<" << base << "&>(first) == static_cast<" << base << "&>(second)";
first = false;
}
foreach (const QString& field, str.fields) {
if (!first) {
out << " &&\n";
out << " ";
}
out << "first." << field << " == second." << field;
first = false;
}
}
out << ";\n";
out << "}\n";
out << "bool operator!=(const " << name << "& first, const " << name << "& second) {\n";
if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) {
out << " return false";
} else {
out << " return ";
bool first = true;
foreach (const QString& base, str.clazz.bases) {
if (!first) {
out << " ||\n";
out << " ";
}
out << "static_cast<" << base << "&>(first) != static_cast<" << base << "&>(second)";
first = false;
}
foreach (const QString& field, str.fields) {
if (!first) {
out << " ||\n";
out << " ";
}
out << "first." << field << " != second." << field;
first = false;
}
}
out << ";\n";
out << "}\n";
out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n\n";
}
}