diff --git a/examples/editModels.js b/examples/editModels.js index 24ab7da1a1..93a34b9a3a 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -18,7 +18,8 @@ var toolWidth = 50; var LASER_WIDTH = 4; var LASER_COLOR = { red: 255, green: 0, blue: 0 }; -var LASER_LENGTH_FACTOR = 5; +var LASER_LENGTH_FACTOR = 500 +; var LEFT = 0; var RIGHT = 1; @@ -42,6 +43,8 @@ var toolBar; var jointList = MyAvatar.getJointNames(); +var mode = 0; + function isLocked(properties) { // special case to lock the ground plane model in hq. if (location.hostname == "hq.highfidelity.io" && @@ -57,6 +60,7 @@ function controller(wichSide) { this.palm = 2 * wichSide; this.tip = 2 * wichSide + 1; this.trigger = wichSide; + this.bumper = 6 * wichSide + 5; this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm); this.palmPosition = Controller.getSpatialControlPosition(this.palm); @@ -77,6 +81,7 @@ function controller(wichSide) { this.rotation = this.oldRotation; this.triggerValue = Controller.getTriggerValue(this.trigger); + this.bumperValue = Controller.isButtonPressed(this.bumper); this.pressed = false; // is trigger pressed this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously) @@ -88,6 +93,11 @@ function controller(wichSide) { this.oldModelPosition; this.oldModelRadius; + this.positionAtGrab; + this.rotationAtGrab; + this.modelPositionAtGrab; + this.modelRotationAtGrab; + this.jointsIntersectingFromStart = []; this.laser = Overlays.addOverlay("line3d", { @@ -145,6 +155,11 @@ function controller(wichSide) { this.oldModelRotation = properties.modelRotation; this.oldModelRadius = properties.radius; + this.positionAtGrab = this.palmPosition; + this.rotationAtGrab = this.rotation; + this.modelPositionAtGrab = properties.position; + this.modelRotationAtGrab = properties.modelRotation; + this.jointsIntersectingFromStart = []; for (var i = 0; i < jointList.length; i++) { var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); @@ -152,6 +167,7 @@ function controller(wichSide) { this.jointsIntersectingFromStart.push(i); } } + this.showLaser(false); } } @@ -169,12 +185,16 @@ function controller(wichSide) { } } - print("closestJoint: " + jointList[closestJointIndex]); - print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelRadius + ")"); + if (closestJointIndex != -1) { + print("closestJoint: " + jointList[closestJointIndex]); + print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelRadius + ")"); + } if (closestJointDistance < this.oldModelRadius) { - if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1) { + if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1 || + (leftController.grabbing && rightController.grabbing && + leftController.modelID.id == rightController.modelID.id)) { // Do nothing } else { print("Attaching to " + jointList[closestJointIndex]); @@ -188,6 +208,7 @@ function controller(wichSide) { MyAvatar.attach(this.modelURL, jointList[closestJointIndex], attachmentOffset, attachmentRotation, 2.0 * this.oldModelRadius, true, false); + Models.deleteModel(this.modelID); } } @@ -196,6 +217,7 @@ function controller(wichSide) { this.grabbing = false; this.modelID.isKnownID = false; this.jointsIntersectingFromStart = []; + this.showLaser(true); } this.checkTrigger = function () { @@ -246,6 +268,7 @@ function controller(wichSide) { return { valid: false }; } + this.glowedIntersectingModel = { isKnownID: false }; this.moveLaser = function () { // the overlays here are anchored to the avatar, which means they are specified in the avatar's local frame @@ -258,46 +281,99 @@ function controller(wichSide) { Overlays.editOverlay(this.laser, { position: startPosition, - end: endPosition, - visible: true + end: endPosition }); Overlays.editOverlay(this.ball, { - position: endPosition, - visible: true + position: endPosition }); Overlays.editOverlay(this.leftRight, { position: Vec3.sum(endPosition, Vec3.multiply(this.right, 2 * this.guideScale)), - end: Vec3.sum(endPosition, Vec3.multiply(this.right, -2 * this.guideScale)), - visible: true + end: Vec3.sum(endPosition, Vec3.multiply(this.right, -2 * this.guideScale)) }); Overlays.editOverlay(this.topDown, {position: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)), - end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale)), - visible: true + end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale)) }); + this.showLaser(!this.grabbing || mode == 0); + + if (this.glowedIntersectingModel.isKnownID) { + Models.editModel(this.glowedIntersectingModel, { glowLevel: 0.0 }); + this.glowedIntersectingModel.isKnownID = false; + } + if (!this.grabbing) { + var intersection = Models.findRayIntersection({ + origin: this.palmPosition, + direction: this.front + }); + if (intersection.accurate && intersection.modelID.isKnownID) { + this.glowedIntersectingModel = intersection.modelID; + Models.editModel(this.glowedIntersectingModel, { glowLevel: 0.25 }); + } + } } - this.hideLaser = function() { - Overlays.editOverlay(this.laser, { visible: false }); - Overlays.editOverlay(this.ball, { visible: false }); - Overlays.editOverlay(this.leftRight, { visible: false }); - Overlays.editOverlay(this.topDown, { visible: false }); + this.showLaser = function(show) { + Overlays.editOverlay(this.laser, { visible: show }); + Overlays.editOverlay(this.ball, { visible: show }); + Overlays.editOverlay(this.leftRight, { visible: show }); + Overlays.editOverlay(this.topDown, { visible: show }); } this.moveModel = function () { if (this.grabbing) { - var newPosition = Vec3.sum(this.palmPosition, - Vec3.multiply(this.front, this.x)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(this.up, this.y)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(this.right, this.z)); + if (!this.modelID.isKnownID) { + print("Unknown grabbed ID " + this.modelID.id + ", isKnown: " + this.modelID.isKnownID); + this.modelID = Models.findRayIntersection({ + origin: this.palmPosition, + direction: this.front + }).modelID; + print("Identified ID " + this.modelID.id + ", isKnown: " + this.modelID.isKnownID); + } + var newPosition; + var newRotation; - var newRotation = Quat.multiply(this.rotation, - Quat.inverse(this.oldRotation)); - newRotation = Quat.multiply(newRotation, - this.oldModelRotation); + switch (mode) { + case 0: + newPosition = Vec3.sum(this.palmPosition, + Vec3.multiply(this.front, this.x)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(this.up, this.y)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(this.right, this.z)); + + + newRotation = Quat.multiply(this.rotation, + Quat.inverse(this.oldRotation)); + newRotation = Quat.multiply(newRotation, + this.oldModelRotation); + break; + case 1: + var forward = Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -1 }); + var d = Vec3.dot(forward, MyAvatar.position); + + var factor1 = Vec3.dot(forward, this.positionAtGrab) - d; + var factor2 = Vec3.dot(forward, this.modelPositionAtGrab) - d; + var vector = Vec3.subtract(this.palmPosition, this.positionAtGrab); + + if (factor2 < 0) { + factor2 = 0; + } + if (factor1 <= 0) { + factor1 = 1; + factor2 = 1; + } + + newPosition = Vec3.sum(this.modelPositionAtGrab, + Vec3.multiply(vector, + factor2 / factor1)); + + newRotation = Quat.multiply(this.rotation, + Quat.inverse(this.rotationAtGrab)); + newRotation = Quat.multiply(newRotation, + this.modelRotationAtGrab); + break; + } Models.editModel(this.modelID, { position: newPosition, @@ -341,6 +417,21 @@ function controller(wichSide) { this.triggerValue = Controller.getTriggerValue(this.trigger); + var bumperValue = Controller.isButtonPressed(this.bumper); + if (bumperValue && !this.bumperValue) { + if (mode == 0) { + mode = 1; + Overlays.editOverlay(leftController.laser, { color: { red: 0, green: 0, blue: 255 } }); + Overlays.editOverlay(rightController.laser, { color: { red: 0, green: 0, blue: 255 } }); + } else { + mode = 0; + Overlays.editOverlay(leftController.laser, { color: { red: 255, green: 0, blue: 0 } }); + Overlays.editOverlay(rightController.laser, { color: { red: 255, green: 0, blue: 0 } }); + } + } + this.bumperValue = bumperValue; + + this.checkTrigger(); this.moveLaser(); @@ -356,8 +447,12 @@ function controller(wichSide) { var attachmentIndex = -1; var attachmentX = LASER_LENGTH_FACTOR; + var newModel; + var newProperties; + for (var i = 0; i < attachments.length; ++i) { - var position = Vec3.sum(MyAvatar.getJointPosition(attachments[i].jointName), attachments[i].translation); + var position = Vec3.sum(MyAvatar.getJointPosition(attachments[i].jointName), + Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[i].jointName), attachments[i].translation)); var scale = attachments[i].scale; var A = this.palmPosition; @@ -375,53 +470,56 @@ function controller(wichSide) { } if (attachmentIndex != -1) { + print("Detaching: " + attachments[attachmentIndex].modelURL); MyAvatar.detachOne(attachments[attachmentIndex].modelURL, attachments[attachmentIndex].jointName); - Models.addModel({ - position: Vec3.sum(MyAvatar.getJointPosition(attachments[attachmentIndex].jointName), - attachments[attachmentIndex].translation), - modelRotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), - attachments[attachmentIndex].rotation), - radius: attachments[attachmentIndex].scale / 2.0, - modelURL: attachments[attachmentIndex].modelURL - }); - } - - // There is none so ... - // Checking model tree - Vec3.print("Looking at: ", this.palmPosition); - var pickRay = { origin: this.palmPosition, - direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) }; - var foundIntersection = Models.findRayIntersection(pickRay); - - if(!foundIntersection.accurate) { - return; - } - var foundModel = foundIntersection.modelID; - - if (!foundModel.isKnownID) { - var identify = Models.identifyModel(foundModel); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + " (update loop " + foundModel.id + ")"); - return; - } - foundModel = identify; - } - - var properties = Models.getModelProperties(foundModel); - print("foundModel.modelURL=" + properties.modelURL); - - if (isLocked(properties)) { - print("Model locked " + properties.id); + + newProperties = { + position: Vec3.sum(MyAvatar.getJointPosition(attachments[attachmentIndex].jointName), + Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), attachments[attachmentIndex].translation)), + modelRotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), + attachments[attachmentIndex].rotation), + radius: attachments[attachmentIndex].scale / 2.0, + modelURL: attachments[attachmentIndex].modelURL + }; + newModel = Models.addModel(newProperties); } else { - print("Checking properties: " + properties.id + " " + properties.isKnownID); - var check = this.checkModel(properties); - if (check.valid) { - this.grab(foundModel, properties); - this.x = check.x; - this.y = check.y; - this.z = check.z; + // There is none so ... + // Checking model tree + Vec3.print("Looking at: ", this.palmPosition); + var pickRay = { origin: this.palmPosition, + direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) }; + var foundIntersection = Models.findRayIntersection(pickRay); + + if(!foundIntersection.accurate) { + print("No accurate intersection"); return; } + newModel = foundIntersection.modelID; + + if (!newModel.isKnownID) { + var identify = Models.identifyModel(newModel); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + " (update loop " + newModel.id + ")"); + return; + } + newModel = identify; + } + newProperties = Models.getModelProperties(newModel); + } + + + print("foundModel.modelURL=" + newProperties.modelURL); + + if (isLocked(newProperties)) { + print("Model locked " + newProperties.id); + } else { + this.grab(newModel, newProperties); + + var check = this.checkModel(newProperties); + this.x = check.x; + this.y = check.y; + this.z = check.z; + return; } } } @@ -439,38 +537,74 @@ var rightController = new controller(RIGHT); function moveModels() { if (leftController.grabbing && rightController.grabbing && rightController.modelID.id == leftController.modelID.id) { - //print("Both controllers"); - var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); - var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); - - var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5); - var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint)); + var newPosition = leftController.oldModelPosition; + var rotation = leftController.oldModelRotation; + var ratio = 1; - var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x)); - var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x)); - - var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5); - var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint)); - - var ratio = length / oldLength; - - var newPosition = Vec3.sum(middle, - Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio)); - //Vec3.print("Ratio : " + ratio + " New position: ", newPosition); - var rotation = Quat.multiply(leftController.rotation, - Quat.inverse(leftController.oldRotation)); - rotation = Quat.multiply(rotation, leftController.oldModelRotation); + switch (mode) { + case 0: + var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); + var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); + + var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5); + var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint)); + + + var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x)); + var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x)); + + var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5); + var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint)); + + + ratio = length / oldLength; + newPosition = Vec3.sum(middle, + Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio)); + break; + case 1: + var u = Vec3.normalize(Vec3.subtract(rightController.oldPalmPosition, leftController.oldPalmPosition)); + var v = Vec3.normalize(Vec3.subtract(rightController.palmPosition, leftController.palmPosition)); + + var cos_theta = Vec3.dot(u, v); + if (cos_theta > 1) { + cos_theta = 1; + } + var angle = Math.acos(cos_theta) / Math.PI * 180; + if (angle < 0.1) { + return; + + } + var w = Vec3.normalize(Vec3.cross(u, v)); + + rotation = Quat.multiply(Quat.angleAxis(angle, w), leftController.oldModelRotation); + + + leftController.positionAtGrab = leftController.palmPosition; + leftController.rotationAtGrab = leftController.rotation; + leftController.modelPositionAtGrab = leftController.oldModelPosition; + leftController.modelRotationAtGrab = rotation; + + rightController.positionAtGrab = rightController.palmPosition; + rightController.rotationAtGrab = rightController.rotation; + rightController.modelPositionAtGrab = rightController.oldModelPosition; + rightController.modelRotationAtGrab = rotation; + break; + } Models.editModel(leftController.modelID, { position: newPosition, - //modelRotation: rotation, + modelRotation: rotation, radius: leftController.oldModelRadius * ratio }); leftController.oldModelPosition = newPosition; leftController.oldModelRotation = rotation; leftController.oldModelRadius *= ratio; + + rightController.oldModelPosition = newPosition; + rightController.oldModelRotation = rotation; + rightController.oldModelRadius *= ratio; return; } @@ -498,8 +632,8 @@ function checkController(deltaTime) { if (hydraConnected) { hydraConnected = false; - leftController.hideLaser(); - rightController.hideLaser(); + leftController.showLaser(false); + rightController.showLaser(false); } } @@ -510,7 +644,7 @@ function initToolBar() { toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); // New Model newModel = toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", + imageURL: toolIconUrl + "add-model-tool.svg", subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, width: toolWidth, height: toolHeight, visible: true, diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index f9ee5bdd25..0e33e14f32 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -18,6 +18,7 @@ #include "Camera.h" #include "Menu.h" #include "Util.h" +#include "devices/OculusManager.h" const float CAMERA_FIRST_PERSON_MODE_UP_SHIFT = 0.0f; const float CAMERA_FIRST_PERSON_MODE_DISTANCE = 0.0f; @@ -264,7 +265,12 @@ PickRay CameraScriptableObject::computePickRay(float x, float y) { float screenWidth = Application::getInstance()->getGLWidget()->width(); float screenHeight = Application::getInstance()->getGLWidget()->height(); PickRay result; - _viewFrustum->computePickRay(x / screenWidth, y / screenHeight, result.origin, result.direction); + if (OculusManager::isConnected()) { + result.origin = _camera->getPosition(); + Application::getInstance()->getApplicationOverlay().computeOculusPickRay(x / screenWidth, y / screenHeight, result.direction); + } else { + _viewFrustum->computePickRay(x / screenWidth, y / screenHeight, result.origin, result.direction); + } return result; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 42874ea02e..f791d20588 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -353,6 +353,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagDoll); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 03ffa0b848..69015a938b 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -311,6 +311,7 @@ namespace MenuOption { const QString CascadedShadows = "Cascaded"; const QString Chat = "Chat..."; const QString ChatCircling = "Chat Circling"; + const QString CollideAsRagDoll = "Collide As RagDoll"; const QString CollideWithAvatars = "Collide With Avatars"; const QString CollideWithEnvironment = "Collide With World Boundaries"; const QString CollideWithParticles = "Collide With Particles"; diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 601dad5563..c3f31ff6e7 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -30,7 +30,7 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) { } setTranslation(neckPosition); glm::quat neckParentRotation; - if (!owningAvatar->getSkeletonModel().getNeckParentRotation(neckParentRotation)) { + if (!owningAvatar->getSkeletonModel().getNeckParentRotationFromDefaultOrientation(neckParentRotation)) { neckParentRotation = owningAvatar->getOrientation(); } setRotation(neckParentRotation); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index f5ca1ab218..576545f115 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -23,7 +23,10 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) : void SkeletonModel::setJointStates(QVector states) { Model::setJointStates(states); - _ragDoll.init(_jointStates); + + if (isActive() && _owningAvatar->isMyAvatar()) { + _ragDoll.init(_jointStates); + } } const float PALM_PRIORITY = 3.0f; @@ -88,7 +91,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { } void SkeletonModel::simulateRagDoll(float deltaTime) { - _ragDoll.slaveToSkeleton(_jointStates, 0.5f); + _ragDoll.slaveToSkeleton(_jointStates, 0.1f); // fraction = 0.1f left intentionally low for demo purposes float MIN_CONSTRAINT_ERROR = 0.005f; // 5mm int MAX_ITERATIONS = 4; @@ -141,7 +144,9 @@ void SkeletonModel::getBodyShapes(QVector& shapes) const { void SkeletonModel::renderIKConstraints() { renderJointConstraints(getRightHandJointIndex()); renderJointConstraints(getLeftHandJointIndex()); - renderRagDoll(); + //if (isActive() && _owningAvatar->isMyAvatar()) { + // renderRagDoll(); + //} } class IndexValue { @@ -188,45 +193,30 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { if (parentJointIndex == -1) { return; } - + // 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.getRotationFromBindToModelFrame(); - r0 = palmRotation; - } else { - JointState state = _jointStates[jointIndex]; - palmRotation = state.getRotationFromBindToModelFrame(); - } 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 = 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); + glm::vec3 palmNormal = inverseRotation * palm.getNormal(); + glm::vec3 fingerDirection = inverseRotation * palm.getFingerDirection(); + + glm::quat palmRotation = rotationBetween(geometry.palmDirection, palmNormal); + palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), fingerDirection) * palmRotation; + if (Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK)) { setHandPosition(jointIndex, palmPosition, palmRotation); - } else if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { - glm::vec3 forearmVector = palmRotation * glm::vec3(sign, 0.0f, 0.0f); - setJointPosition(parentJointIndex, palmPosition + forearmVector * - geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale), + float forearmLength = geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale); + glm::vec3 forearm = palmRotation * glm::vec3(sign * forearmLength, 0.0f, 0.0f); + setJointPosition(parentJointIndex, palmPosition + forearm, glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); JointState& parentState = _jointStates[parentJointIndex]; parentState.setRotationFromBindFrame(palmRotation, PALM_PRIORITY); // lock hand to forearm by slamming its rotation (in parent-frame) to identity _jointStates[jointIndex]._rotationInParentFrame = glm::quat(); } else { - setJointPosition(jointIndex, palmPosition, palmRotation, - true, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); + setJointPosition(jointIndex, palmPosition, palmRotation, + true, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); } } @@ -254,6 +244,15 @@ void SkeletonModel::updateJointState(int index) { } } +void SkeletonModel::updateShapePositions() { + if (isActive() && _owningAvatar->isMyAvatar() && + Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagDoll)) { + _ragDoll.updateShapes(_jointShapes, _rotation, _translation); + } else { + Model::updateShapePositions(); + } +} + void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { if (!_owningAvatar->isMyAvatar() || Application::getInstance()->getPrioVR()->isActive()) { return; @@ -435,7 +434,7 @@ bool SkeletonModel::getNeckPosition(glm::vec3& neckPosition) const { return isActive() && getJointPositionInWorldFrame(_geometry->getFBXGeometry().neckJointIndex, neckPosition); } -bool SkeletonModel::getNeckParentRotation(glm::quat& neckParentRotation) const { +bool SkeletonModel::getNeckParentRotationFromDefaultOrientation(glm::quat& neckParentRotation) const { if (!isActive()) { return false; } @@ -443,7 +442,13 @@ bool SkeletonModel::getNeckParentRotation(glm::quat& neckParentRotation) const { if (geometry.neckJointIndex == -1) { return false; } - return getJointRotationInWorldFrame(geometry.joints.at(geometry.neckJointIndex).parentIndex, neckParentRotation); + int parentIndex = geometry.joints.at(geometry.neckJointIndex).parentIndex; + glm::quat worldFrameRotation; + if (getJointRotationInWorldFrame(parentIndex, worldFrameRotation)) { + neckParentRotation = worldFrameRotation * _jointStates[parentIndex].getFBXJoint().inverseDefaultRotation; + return true; + } + return false; } bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index d733d937ee..3b8e67df47 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -29,6 +29,7 @@ public: void simulate(float deltaTime, bool fullUpdate = true); void simulateRagDoll(float deltaTime); + void updateShapePositions(); /// \param jointIndex index of hand joint /// \param shapes[out] list in which is stored pointers to hand shapes @@ -85,9 +86,9 @@ public: /// \return whether or not the neck was found bool getNeckPosition(glm::vec3& neckPosition) const; - /// Returns the rotation of the neck joint's parent. + /// Returns the rotation of the neck joint's parent from default orientation /// \return whether or not the neck was found - bool getNeckParentRotation(glm::quat& neckRotation) const; + bool getNeckParentRotationFromDefaultOrientation(glm::quat& neckParentRotation) const; /// Retrieve the positions of up to two eye meshes. /// \return whether or not both eye meshes were found diff --git a/interface/src/models/ModelTreeRenderer.cpp b/interface/src/models/ModelTreeRenderer.cpp index 68bebd7b99..b20bdce8f5 100644 --- a/interface/src/models/ModelTreeRenderer.cpp +++ b/interface/src/models/ModelTreeRenderer.cpp @@ -57,21 +57,29 @@ void ModelTreeRenderer::render(RenderMode renderMode) { const FBXGeometry* ModelTreeRenderer::getGeometryForModel(const ModelItem& modelItem) { const FBXGeometry* result = NULL; + Model* model = getModel(modelItem); if (model) { result = &model->getGeometry()->getFBXGeometry(); - } return result; } Model* ModelTreeRenderer::getModel(const ModelItem& modelItem) { Model* model = NULL; - + if (modelItem.isKnownID()) { if (_knownModelsItemModels.find(modelItem.getID()) != _knownModelsItemModels.end()) { model = _knownModelsItemModels[modelItem.getID()]; } else { + + // Make sure we only create new models on the thread that owns the ModelTreeRenderer + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "getModel", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(Model*, model), Q_ARG(const ModelItem&, modelItem)); + return model; + } + model = new Model(); model->init(); model->setURL(QUrl(modelItem.getModelURL())); @@ -81,6 +89,13 @@ Model* ModelTreeRenderer::getModel(const ModelItem& modelItem) { if (_unknownModelsItemModels.find(modelItem.getCreatorTokenID()) != _unknownModelsItemModels.end()) { model = _unknownModelsItemModels[modelItem.getCreatorTokenID()]; } else { + // Make sure we only create new models on the thread that owns the ModelTreeRenderer + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "getModel", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(Model*, model), Q_ARG(const ModelItem&, modelItem)); + return model; + } + model = new Model(); model->init(); model->setURL(QUrl(modelItem.getModelURL())); @@ -187,92 +202,122 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) if (drawAsModel) { glPushMatrix(); + { const float alpha = 1.0f; Model* model = getModel(modelItem); - - model->setScaleToFit(true, radius * 2.0f); - model->setSnapModelToCenter(true); - - // set the rotation - glm::quat rotation = modelItem.getModelRotation(); - model->setRotation(rotation); - - // set the position - model->setTranslation(position); - // handle animations.. - if (modelItem.hasAnimation()) { - if (!modelItem.jointsMapped()) { - QStringList modelJointNames = model->getJointNames(); - modelItem.mapJoints(modelJointNames); + if (model) { + model->setScaleToFit(true, radius * 2.0f); + model->setSnapModelToCenter(true); + + // set the rotation + glm::quat rotation = modelItem.getModelRotation(); + model->setRotation(rotation); + + // set the position + model->setTranslation(position); + + // handle animations.. + if (modelItem.hasAnimation()) { + if (!modelItem.jointsMapped()) { + QStringList modelJointNames = model->getJointNames(); + modelItem.mapJoints(modelJointNames); + } + + QVector frameData = modelItem.getAnimationFrame(); + for (int i = 0; i < frameData.size(); i++) { + model->setJointState(i, true, frameData[i]); + } + } + + // make sure to simulate so everything gets set up correctly for rendering + model->simulate(0.0f); + + // TODO: should we allow modelItems to have alpha on their models? + Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE + ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + + if (modelItem.getGlowLevel() > 0.0f) { + Glower glower(modelItem.getGlowLevel()); + + if (model->isActive()) { + model->render(alpha, modelRenderMode); + } else { + // if we couldn't get a model, then just draw a sphere + glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]); + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glutSolidSphere(radius, 15, 15); + glPopMatrix(); + } + } else { + if (model->isActive()) { + model->render(alpha, modelRenderMode); + } else { + // if we couldn't get a model, then just draw a sphere + glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]); + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glutSolidSphere(radius, 15, 15); + glPopMatrix(); + } } - QVector frameData = modelItem.getAnimationFrame(); - for (int i = 0; i < frameData.size(); i++) { - model->setJointState(i, true, frameData[i]); - } - } - - // make sure to simulate so everything gets set up correctly for rendering - model->simulate(0.0f); + if (!isShadowMode && displayModelBounds) { - // TODO: should we allow modelItems to have alpha on their models? - Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE - ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; - - if (modelItem.getGlowLevel() > 0.0f) { - Glower glower(modelItem.getGlowLevel()); - model->render(alpha, modelRenderMode); + glm::vec3 unRotatedMinimum = model->getUnscaledMeshExtents().minimum; + glm::vec3 unRotatedMaximum = model->getUnscaledMeshExtents().maximum; + glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum; + + float width = unRotatedExtents.x; + float height = unRotatedExtents.y; + float depth = unRotatedExtents.z; + + Extents rotatedExtents = model->getUnscaledMeshExtents(); + calculateRotatedExtents(rotatedExtents, rotation); + + glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum; + + const glm::vec3& modelScale = model->getScale(); + + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + + // draw the orignal bounding cube + glColor4f(1.0f, 1.0f, 0.0f, 1.0f); + glutWireCube(size); + + // draw the rotated bounding cube + glColor4f(0.0f, 0.0f, 1.0f, 1.0f); + glPushMatrix(); + glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z); + glutWireCube(1.0); + glPopMatrix(); + + // draw the model relative bounding box + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z); + glColor3f(0.0f, 1.0f, 0.0f); + glutWireCube(1.0); + + glPopMatrix(); + + } } else { - model->render(alpha, modelRenderMode); - } - - if (!isShadowMode && displayModelBounds) { - - glm::vec3 unRotatedMinimum = model->getUnscaledMeshExtents().minimum; - glm::vec3 unRotatedMaximum = model->getUnscaledMeshExtents().maximum; - glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum; - - float width = unRotatedExtents.x; - float height = unRotatedExtents.y; - float depth = unRotatedExtents.z; - - Extents rotatedExtents = model->getUnscaledMeshExtents(); - calculateRotatedExtents(rotatedExtents, rotation); - - glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum; - - const glm::vec3& modelScale = model->getScale(); - + // if we couldn't get a model, then just draw a sphere + glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]); glPushMatrix(); glTranslatef(position.x, position.y, position.z); - - // draw the orignal bounding cube - glColor4f(1.0f, 1.0f, 0.0f, 1.0f); - glutWireCube(size); - - // draw the rotated bounding cube - glColor4f(0.0f, 0.0f, 1.0f, 1.0f); - glPushMatrix(); - glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z); - glutWireCube(1.0); - glPopMatrix(); - - // draw the model relative bounding box - glm::vec3 axis = glm::axis(rotation); - glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); - glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z); - glColor3f(0.0f, 1.0f, 0.0f); - glutWireCube(1.0); - + glutSolidSphere(radius, 15, 15); glPopMatrix(); - } - + } glPopMatrix(); } else { - glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]); + //glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]); + glColor3f(1.0f, 0.0f, 0.0f); glPushMatrix(); glTranslatef(position.x, position.y, position.z); glutSolidSphere(radius, 15, 15); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 105301054b..aff023c2a0 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1275,8 +1275,8 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, const gl if (useRotation) { JointState& state = _jointStates[jointIndex]; - state.setRotation(rotation, true, priority); - endRotation = state.getRotation(); + state.setRotationFromBindFrame(rotation, priority); + endRotation = state.getRotationFromBindToModelFrame(); } // then, we go from the joint upwards, rotating the end as close as possible to the target diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 3bc261ed44..11e6861775 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -140,7 +140,7 @@ public: void clearShapes(); void rebuildShapes(); void resetShapePositions(); - void updateShapePositions(); + virtual void updateShapePositions(); void renderJointCollisionShapes(float alpha); void renderBoundingCollisionShapes(float alpha); diff --git a/interface/src/renderer/RagDoll.cpp b/interface/src/renderer/RagDoll.cpp index 54fb776552..305724d6e4 100644 --- a/interface/src/renderer/RagDoll.cpp +++ b/interface/src/renderer/RagDoll.cpp @@ -15,13 +15,15 @@ #include #include +#include +#include #include "RagDoll.h" // ---------------------------------------------------------------------------- // FixedConstraint // ---------------------------------------------------------------------------- -FixedConstraint::FixedConstraint() : _point(NULL), _anchor(0.0f, 0.0f, 0.0f) { +FixedConstraint::FixedConstraint(glm::vec3* point, const glm::vec3& anchor) : _point(point), _anchor(anchor) { } float FixedConstraint::enforce() { @@ -42,9 +44,9 @@ void FixedConstraint::setAnchor(const glm::vec3& anchor) { // ---------------------------------------------------------------------------- // DistanceConstraint // ---------------------------------------------------------------------------- -DistanceConstraint::DistanceConstraint(glm::vec3* pointA, glm::vec3* pointB) : _distance(-1.0f) { - _points[0] = pointA; - _points[1] = pointB; +DistanceConstraint::DistanceConstraint(glm::vec3* startPoint, glm::vec3* endPoint) : _distance(-1.0f) { + _points[0] = startPoint; + _points[1] = endPoint; _distance = glm::distance(*(_points[0]), *(_points[1])); } @@ -70,6 +72,28 @@ float DistanceConstraint::enforce() { return glm::abs(newDistance - _distance); } +void DistanceConstraint::updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const { + if (!shape) { + return; + } + switch (shape->getType()) { + case Shape::SPHERE_SHAPE: { + // sphere collides at endPoint + SphereShape* sphere = static_cast(shape); + sphere->setPosition(translation + rotation * (*_points[1])); + } + break; + case Shape::CAPSULE_SHAPE: { + // capsule collides from startPoint to endPoint + CapsuleShape* capsule = static_cast(shape); + capsule->setEndPoints(translation + rotation * (*_points[0]), translation + rotation * (*_points[1])); + } + break; + default: + break; + } +} + // ---------------------------------------------------------------------------- // RagDoll // ---------------------------------------------------------------------------- @@ -90,12 +114,16 @@ void RagDoll::init(const QVector& states) { _points.push_back(state.getPosition()); int parentIndex = state.getFBXJoint().parentIndex; assert(parentIndex < i); - if (parentIndex != -1) { + if (parentIndex == -1) { + FixedConstraint* anchor = new FixedConstraint(&(_points[i]), glm::vec3(0.0f)); + _constraints.push_back(anchor); + } else { DistanceConstraint* stick = new DistanceConstraint(&(_points[i]), &(_points[parentIndex])); _constraints.push_back(stick); } } } + /// Delete all data. void RagDoll::clear() { int numConstraints = _constraints.size(); @@ -129,3 +157,11 @@ float RagDoll::enforceConstraints() { } return maxDistance; } + +void RagDoll::updateShapes(const QVector& shapes, const glm::quat& rotation, const glm::vec3& translation) const { + int numShapes = shapes.size(); + int numConstraints = _constraints.size(); + for (int i = 0; i < numShapes && i < numConstraints; ++i) { + _constraints[i]->updateProxyShape(shapes[i], rotation, translation); + } +} diff --git a/interface/src/renderer/RagDoll.h b/interface/src/renderer/RagDoll.h index 1d23973827..60e242d19b 100644 --- a/interface/src/renderer/RagDoll.h +++ b/interface/src/renderer/RagDoll.h @@ -14,6 +14,8 @@ #include "renderer/Model.h" +class Shape; + class Constraint { public: Constraint() {} @@ -22,11 +24,20 @@ public: /// Enforce contraint by moving relevant points. /// \return max distance of point movement virtual float enforce() = 0; + + /// \param shape pointer to shape that will be this Constraint's collision proxy + /// \param rotation rotation into shape's collision frame + /// \param translation translation into shape's collision frame + /// Moves the shape such that it will collide at this constraint's position + virtual void updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const {} + +protected: + int _type; }; class FixedConstraint : public Constraint { public: - FixedConstraint(); + FixedConstraint(glm::vec3* point, const glm::vec3& anchor); float enforce(); void setPoint(glm::vec3* point); void setAnchor(const glm::vec3& anchor); @@ -37,10 +48,11 @@ private: class DistanceConstraint : public Constraint { public: - DistanceConstraint(glm::vec3* pointA, glm::vec3* pointB); + DistanceConstraint(glm::vec3* startPoint, glm::vec3* endPoint); DistanceConstraint(const DistanceConstraint& other); float enforce(); void setDistance(float distance); + void updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const; private: float _distance; glm::vec3* _points[2]; @@ -69,7 +81,12 @@ public: float enforceConstraints(); const QVector& getPoints() const { return _points; } - + + /// \param shapes list of shapes to be updated with new positions + /// \param rotation rotation into shapes' collision frame + /// \param translation translation into shapes' collision frame + void updateShapes(const QVector& shapes, const glm::quat& rotation, const glm::vec3& translation) const; + private: QVector _constraints; QVector _points; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index ec2d6c73b3..a48e0a2c1a 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -297,6 +297,25 @@ void ApplicationOverlay::displayOverlayTexture(Camera& whichCamera) { glDisable(GL_TEXTURE_2D); } +const float textureFov = PI / 2.5f; + +void ApplicationOverlay::computeOculusPickRay(float x, float y, glm::vec3& direction) const { + glm::quat rot = Application::getInstance()->getAvatar()->getOrientation(); + + //invert y direction + y = 1.0 - y; + + //Get position on hemisphere UI + x = sin((x - 0.5f) * textureFov); + y = sin((y - 0.5f) * textureFov); + + float dist = sqrt(x * x + y * y); + float z = -sqrt(1.0f - dist * dist); + + //Rotate the UI pick ray by the avatar orientation + direction = glm::normalize(rot * glm::vec3(x, y, z)); +} + // Fast helper functions inline float max(float a, float b) { return (a > b) ? a : b; @@ -306,8 +325,6 @@ 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) { diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 8817549277..1bf0e18816 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -15,7 +15,7 @@ class Overlays; class QOpenGLFramebufferObject; -// Handles the drawing of the overlays to the scree +// Handles the drawing of the overlays to the screen class ApplicationOverlay { public: @@ -27,6 +27,7 @@ public: void renderOverlay(bool renderToTexture = false); void displayOverlayTexture(Camera& whichCamera); void displayOverlayTextureOculus(Camera& whichCamera); + void computeOculusPickRay(float x, float y, glm::vec3& direction) const; // Getters QOpenGLFramebufferObject* getFramebufferObject(); diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 94a88897e3..bb47c6de9e 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -55,7 +55,9 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) { readBytes += sizeof(int16_t); - addSilentFrame(numSilentSamples); + if (numSilentSamples > 0) { + addSilentFrame(numSilentSamples); + } } else { // there is audio data to read readBytes += writeData(packet.data() + readBytes, packet.size() - readBytes); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 60412cf0ce..9aeb81a2a3 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -894,14 +894,15 @@ FBXBlendshape extractBlendshape(const FBXNode& object) { } void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) { - glm::vec3 normal = glm::normalize(mesh.normals.at(firstIndex)); + const glm::vec3& normal = mesh.normals.at(firstIndex); glm::vec3 bitangent = glm::cross(normal, mesh.vertices.at(secondIndex) - mesh.vertices.at(firstIndex)); if (glm::length(bitangent) < EPSILON) { return; } glm::vec2 texCoordDelta = mesh.texCoords.at(secondIndex) - mesh.texCoords.at(firstIndex); - mesh.tangents[firstIndex] += glm::cross(glm::angleAxis( - - atan2f(-texCoordDelta.t, texCoordDelta.s), normal) * glm::normalize(bitangent), normal); + glm::vec3 normalizedNormal = glm::normalize(normal); + mesh.tangents[firstIndex] += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * + glm::normalize(bitangent), normalizedNormal); } QVector getIndices(const QVector ids, QVector modelIDs) { diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index 387b41a839..d7e15a9dd6 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,7 @@ REGISTER_SIMPLE_TYPE_STREAMER(uint) REGISTER_SIMPLE_TYPE_STREAMER(float) REGISTER_SIMPLE_TYPE_STREAMER(QByteArray) REGISTER_SIMPLE_TYPE_STREAMER(QColor) +REGISTER_SIMPLE_TYPE_STREAMER(QScriptValue) REGISTER_SIMPLE_TYPE_STREAMER(QString) REGISTER_SIMPLE_TYPE_STREAMER(QUrl) REGISTER_SIMPLE_TYPE_STREAMER(QVariantList) @@ -79,10 +81,6 @@ 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); @@ -90,17 +88,6 @@ int Bitstream::registerMetaObject(const char* className, const QMetaObject* meta for (const QMetaObject* superClass = metaObject; superClass; superClass = superClass->superClass()) { getMetaObjectSubClasses().insert(superClass, metaObject); } - - // register the streamers for all enumerators - // temporarily disabled: crashes on Windows - //for (int i = 0; i < metaObject->enumeratorCount(); i++) { - // QMetaEnum metaEnum = metaObject->enumerator(i); - // const TypeStreamer*& streamer = getEnumStreamers()[QPair(metaEnum.scope(), metaEnum.name())]; - // if (!streamer) { - // getEnumStreamersByName().insert(getEnumName(metaEnum), streamer = new EnumTypeStreamer(metaEnum)); - // } - //} - return 0; } @@ -225,7 +212,8 @@ void Bitstream::persistWriteMappings(const WriteMappings& mappings) { } connect(it.key().data(), SIGNAL(destroyed(QObject*)), SLOT(clearSharedObject(QObject*))); QPointer& reference = _sharedObjectReferences[it.key()->getOriginID()]; - if (reference) { + if (reference && reference != it.key()) { + // the object has been replaced by a successor, so we can forget about the original _sharedObjectStreamer.removePersistentID(reference); reference->disconnect(this); } @@ -259,7 +247,8 @@ void Bitstream::persistReadMappings(const ReadMappings& mappings) { continue; } QPointer& reference = _sharedObjectReferences[it.value()->getRemoteOriginID()]; - if (reference) { + if (reference && reference != it.value()) { + // the object has been replaced by a successor, so we can forget about the original _sharedObjectStreamer.removePersistentValue(reference.data()); } reference = it.value(); @@ -298,6 +287,12 @@ void Bitstream::writeDelta(const QVariant& value, const QVariant& reference) { streamer->writeRawDelta(*this, value, reference); } +void Bitstream::writeRawDelta(const QVariant& value, const QVariant& reference) { + const TypeStreamer* streamer = getTypeStreamers().value(value.userType()); + _typeStreamerStreamer << streamer; + streamer->writeRawDelta(*this, value, reference); +} + void Bitstream::readRawDelta(QVariant& value, const QVariant& reference) { TypeReader typeReader; _typeStreamerStreamer >> typeReader; @@ -311,7 +306,7 @@ void Bitstream::writeRawDelta(const QObject* value, const QObject* reference) { } const QMetaObject* metaObject = value->metaObject(); _metaObjectStreamer << metaObject; - foreach (const PropertyWriter& propertyWriter, getPropertyWriters(metaObject)) { + foreach (const PropertyWriter& propertyWriter, getPropertyWriters().value(metaObject)) { propertyWriter.writeDelta(*this, value, reference); } } @@ -322,6 +317,272 @@ void Bitstream::readRawDelta(QObject*& value, const QObject* reference) { value = objectReader.readDelta(*this, reference); } +void Bitstream::writeRawDelta(const QScriptValue& value, const QScriptValue& reference) { + if (reference.isUndefined() || reference.isNull()) { + *this << value; + + } else if (reference.isBool()) { + if (value.isBool()) { + *this << false; + *this << value.toBool(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isNumber()) { + if (value.isNumber()) { + *this << false; + *this << value.toNumber(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isString()) { + if (value.isString()) { + *this << false; + *this << value.toString(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isVariant()) { + if (value.isVariant()) { + *this << false; + writeRawDelta(value.toVariant(), reference.toVariant()); + + } else { + *this << true; + *this << value; + } + } else if (reference.isQObject()) { + if (value.isQObject()) { + *this << false; + writeRawDelta(value.toQObject(), reference.toQObject()); + + } else { + *this << true; + *this << value; + } + } else if (reference.isQMetaObject()) { + if (value.isQMetaObject()) { + *this << false; + *this << value.toQMetaObject(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isDate()) { + if (value.isDate()) { + *this << false; + *this << value.toDateTime(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isRegExp()) { + if (value.isRegExp()) { + *this << false; + *this << value.toRegExp(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isArray()) { + if (value.isArray()) { + *this << false; + int length = value.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + *this << length; + int referenceLength = reference.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + for (int i = 0; i < length; i++) { + if (i < referenceLength) { + writeDelta(value.property(i), reference.property(i)); + } else { + *this << value.property(i); + } + } + } else { + *this << true; + *this << value; + } + } else if (reference.isObject()) { + if (value.isObject() && !(value.isArray() || value.isRegExp() || value.isDate() || + value.isQMetaObject() || value.isQObject() || value.isVariant())) { + *this << false; + for (QScriptValueIterator it(value); it.hasNext(); ) { + it.next(); + QScriptValue referenceValue = reference.property(it.scriptName()); + if (it.value() != referenceValue) { + *this << it.scriptName(); + writeRawDelta(it.value(), referenceValue); + } + } + for (QScriptValueIterator it(reference); it.hasNext(); ) { + it.next(); + if (!value.property(it.scriptName()).isValid()) { + *this << it.scriptName(); + writeRawDelta(QScriptValue(), it.value()); + } + } + *this << QScriptString(); + + } else { + *this << true; + *this << value; + } + } else { + *this << value; + } +} + +void Bitstream::readRawDelta(QScriptValue& value, const QScriptValue& reference) { + if (reference.isUndefined() || reference.isNull()) { + *this >> value; + + } else if (reference.isBool()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + bool boolValue; + *this >> boolValue; + value = QScriptValue(boolValue); + } + } else if (reference.isNumber()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + qsreal numberValue; + *this >> numberValue; + value = QScriptValue(numberValue); + } + } else if (reference.isString()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + QString stringValue; + *this >> stringValue; + value = QScriptValue(stringValue); + } + } else if (reference.isVariant()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + QVariant variant; + readRawDelta(variant, reference.toVariant()); + value = ScriptCache::getInstance()->getEngine()->newVariant(variant); + } + } else if (reference.isQObject()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + QObject* object; + readRawDelta(object, reference.toQObject()); + value = ScriptCache::getInstance()->getEngine()->newQObject(object, QScriptEngine::ScriptOwnership); + } + } else if (reference.isQMetaObject()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + const QMetaObject* metaObject; + *this >> metaObject; + value = ScriptCache::getInstance()->getEngine()->newQMetaObject(metaObject); + } + } else if (reference.isDate()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + QDateTime dateTime; + *this >> dateTime; + value = ScriptCache::getInstance()->getEngine()->newDate(dateTime); + } + } else if (reference.isRegExp()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + QRegExp regExp; + *this >> regExp; + value = ScriptCache::getInstance()->getEngine()->newRegExp(regExp); + } + } else if (reference.isArray()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + int length; + *this >> length; + value = ScriptCache::getInstance()->getEngine()->newArray(length); + int referenceLength = reference.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + for (int i = 0; i < length; i++) { + QScriptValue element; + if (i < referenceLength) { + readDelta(element, reference.property(i)); + } else { + *this >> element; + } + value.setProperty(i, element); + } + } + } else if (reference.isObject()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + // start by shallow-copying the reference + value = ScriptCache::getInstance()->getEngine()->newObject(); + for (QScriptValueIterator it(reference); it.hasNext(); ) { + it.next(); + value.setProperty(it.scriptName(), it.value()); + } + // then apply the requested changes + forever { + QScriptString name; + *this >> name; + if (!name.isValid()) { + break; + } + QScriptValue scriptValue; + readRawDelta(scriptValue, reference.property(name)); + value.setProperty(name, scriptValue); + } + } + } else { + *this >> value; + } +} + Bitstream& Bitstream::operator<<(bool value) { if (value) { _byte |= (1 << _position); @@ -363,6 +624,14 @@ Bitstream& Bitstream::operator>>(uint& value) { return *this; } +Bitstream& Bitstream::operator<<(qint64 value) { + return write(&value, 64); +} + +Bitstream& Bitstream::operator>>(qint64& value) { + return read(&value, 64); +} + Bitstream& Bitstream::operator<<(float value) { return write(&value, 32); } @@ -371,6 +640,14 @@ Bitstream& Bitstream::operator>>(float& value) { return read(&value, 32); } +Bitstream& Bitstream::operator<<(double value) { + return write(&value, 64); +} + +Bitstream& Bitstream::operator>>(double& value) { + return read(&value, 64); +} + Bitstream& Bitstream::operator<<(const glm::vec3& value) { return *this << value.x << value.y << value.z; } @@ -433,6 +710,40 @@ Bitstream& Bitstream::operator>>(QUrl& url) { return *this; } +Bitstream& Bitstream::operator<<(const QDateTime& dateTime) { + return *this << dateTime.toMSecsSinceEpoch(); +} + +Bitstream& Bitstream::operator>>(QDateTime& dateTime) { + qint64 msecsSinceEpoch; + *this >> msecsSinceEpoch; + dateTime = QDateTime::fromMSecsSinceEpoch(msecsSinceEpoch); + return *this; +} + +Bitstream& Bitstream::operator<<(const QRegExp& regExp) { + *this << regExp.pattern(); + Qt::CaseSensitivity caseSensitivity = regExp.caseSensitivity(); + write(&caseSensitivity, 1); + QRegExp::PatternSyntax syntax = regExp.patternSyntax(); + write(&syntax, 3); + return *this << regExp.isMinimal(); +} + +Bitstream& Bitstream::operator>>(QRegExp& regExp) { + QString pattern; + *this >> pattern; + Qt::CaseSensitivity caseSensitivity = (Qt::CaseSensitivity)0; + read(&caseSensitivity, 1); + QRegExp::PatternSyntax syntax = (QRegExp::PatternSyntax)0; + read(&syntax, 3); + regExp = QRegExp(pattern, caseSensitivity, syntax); + bool minimal; + *this >> minimal; + regExp.setMinimal(minimal); + return *this; +} + Bitstream& Bitstream::operator<<(const QVariant& value) { if (!value.isValid()) { _typeStreamerStreamer << NULL; @@ -489,7 +800,7 @@ Bitstream& Bitstream::operator<<(const QObject* object) { } const QMetaObject* metaObject = object->metaObject(); _metaObjectStreamer << metaObject; - foreach (const PropertyWriter& propertyWriter, getPropertyWriters(metaObject)) { + foreach (const PropertyWriter& propertyWriter, getPropertyWriters().value(metaObject)) { propertyWriter.write(*this, object); } return *this; @@ -556,6 +867,185 @@ Bitstream& Bitstream::operator>>(QScriptString& string) { return *this; } +enum ScriptValueType { + INVALID_SCRIPT_VALUE, + UNDEFINED_SCRIPT_VALUE, + NULL_SCRIPT_VALUE, + BOOL_SCRIPT_VALUE, + NUMBER_SCRIPT_VALUE, + STRING_SCRIPT_VALUE, + VARIANT_SCRIPT_VALUE, + QOBJECT_SCRIPT_VALUE, + QMETAOBJECT_SCRIPT_VALUE, + DATE_SCRIPT_VALUE, + REGEXP_SCRIPT_VALUE, + ARRAY_SCRIPT_VALUE, + OBJECT_SCRIPT_VALUE +}; + +const int SCRIPT_VALUE_BITS = 4; + +static void writeScriptValueType(Bitstream& out, ScriptValueType type) { + out.write(&type, SCRIPT_VALUE_BITS); +} + +static ScriptValueType readScriptValueType(Bitstream& in) { + ScriptValueType type = (ScriptValueType)0; + in.read(&type, SCRIPT_VALUE_BITS); + return type; +} + +Bitstream& Bitstream::operator<<(const QScriptValue& value) { + if (value.isUndefined()) { + writeScriptValueType(*this, UNDEFINED_SCRIPT_VALUE); + + } else if (value.isNull()) { + writeScriptValueType(*this, NULL_SCRIPT_VALUE); + + } else if (value.isBool()) { + writeScriptValueType(*this, BOOL_SCRIPT_VALUE); + *this << value.toBool(); + + } else if (value.isNumber()) { + writeScriptValueType(*this, NUMBER_SCRIPT_VALUE); + *this << value.toNumber(); + + } else if (value.isString()) { + writeScriptValueType(*this, STRING_SCRIPT_VALUE); + *this << value.toString(); + + } else if (value.isVariant()) { + writeScriptValueType(*this, VARIANT_SCRIPT_VALUE); + *this << value.toVariant(); + + } else if (value.isQObject()) { + writeScriptValueType(*this, QOBJECT_SCRIPT_VALUE); + *this << value.toQObject(); + + } else if (value.isQMetaObject()) { + writeScriptValueType(*this, QMETAOBJECT_SCRIPT_VALUE); + *this << value.toQMetaObject(); + + } else if (value.isDate()) { + writeScriptValueType(*this, DATE_SCRIPT_VALUE); + *this << value.toDateTime(); + + } else if (value.isRegExp()) { + writeScriptValueType(*this, REGEXP_SCRIPT_VALUE); + *this << value.toRegExp(); + + } else if (value.isArray()) { + writeScriptValueType(*this, ARRAY_SCRIPT_VALUE); + int length = value.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + *this << length; + for (int i = 0; i < length; i++) { + *this << value.property(i); + } + } else if (value.isObject()) { + writeScriptValueType(*this, OBJECT_SCRIPT_VALUE); + for (QScriptValueIterator it(value); it.hasNext(); ) { + it.next(); + *this << it.scriptName(); + *this << it.value(); + } + *this << QScriptString(); + + } else { + writeScriptValueType(*this, INVALID_SCRIPT_VALUE); + } + return *this; +} + +Bitstream& Bitstream::operator>>(QScriptValue& value) { + switch (readScriptValueType(*this)) { + case UNDEFINED_SCRIPT_VALUE: + value = QScriptValue(QScriptValue::UndefinedValue); + break; + + case NULL_SCRIPT_VALUE: + value = QScriptValue(QScriptValue::NullValue); + break; + + case BOOL_SCRIPT_VALUE: { + bool boolValue; + *this >> boolValue; + value = QScriptValue(boolValue); + break; + } + case NUMBER_SCRIPT_VALUE: { + qsreal numberValue; + *this >> numberValue; + value = QScriptValue(numberValue); + break; + } + case STRING_SCRIPT_VALUE: { + QString stringValue; + *this >> stringValue; + value = QScriptValue(stringValue); + break; + } + case VARIANT_SCRIPT_VALUE: { + QVariant variantValue; + *this >> variantValue; + value = ScriptCache::getInstance()->getEngine()->newVariant(variantValue); + break; + } + case QOBJECT_SCRIPT_VALUE: { + QObject* object; + *this >> object; + ScriptCache::getInstance()->getEngine()->newQObject(object, QScriptEngine::ScriptOwnership); + break; + } + case QMETAOBJECT_SCRIPT_VALUE: { + const QMetaObject* metaObject; + *this >> metaObject; + ScriptCache::getInstance()->getEngine()->newQMetaObject(metaObject); + break; + } + case DATE_SCRIPT_VALUE: { + QDateTime dateTime; + *this >> dateTime; + value = ScriptCache::getInstance()->getEngine()->newDate(dateTime); + break; + } + case REGEXP_SCRIPT_VALUE: { + QRegExp regExp; + *this >> regExp; + value = ScriptCache::getInstance()->getEngine()->newRegExp(regExp); + break; + } + case ARRAY_SCRIPT_VALUE: { + int length; + *this >> length; + value = ScriptCache::getInstance()->getEngine()->newArray(length); + for (int i = 0; i < length; i++) { + QScriptValue element; + *this >> element; + value.setProperty(i, element); + } + break; + } + case OBJECT_SCRIPT_VALUE: { + value = ScriptCache::getInstance()->getEngine()->newObject(); + forever { + QScriptString name; + *this >> name; + if (!name.isValid()) { + break; + } + QScriptValue scriptValue; + *this >> scriptValue; + value.setProperty(name, scriptValue); + } + break; + } + default: + value = QScriptValue(); + break; + } + return *this; +} + Bitstream& Bitstream::operator<<(const SharedObjectPointer& object) { _sharedObjectStreamer << object; return *this; @@ -574,7 +1064,7 @@ Bitstream& Bitstream::operator<(const QMetaObject* metaObject) { if (_metadataType == NO_METADATA) { return *this; } - const QVector& propertyWriters = getPropertyWriters(metaObject); + const PropertyWriterVector& propertyWriters = getPropertyWriters().value(metaObject); *this << propertyWriters.size(); QCryptographicHash hash(QCryptographicHash::Md5); foreach (const PropertyWriter& propertyWriter, propertyWriters) { @@ -608,12 +1098,12 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) { qWarning() << "Unknown class name: " << className << "\n"; } if (_metadataType == NO_METADATA) { - objectReader = ObjectReader(className, metaObject, getPropertyReaders(metaObject)); + objectReader = ObjectReader(className, metaObject, getPropertyReaders().value(metaObject)); return *this; } int storedPropertyCount; *this >> storedPropertyCount; - QVector properties(storedPropertyCount); + PropertyReaderVector properties(storedPropertyCount); for (int i = 0; i < storedPropertyCount; i++) { TypeReader typeReader; *this >> typeReader; @@ -632,7 +1122,7 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) { QCryptographicHash hash(QCryptographicHash::Md5); bool matches = true; if (metaObject) { - const QVector& propertyWriters = getPropertyWriters(metaObject); + const PropertyWriterVector& propertyWriters = getPropertyWriters().value(metaObject); if (propertyWriters.size() == properties.size()) { for (int i = 0; i < propertyWriters.size(); i++) { const PropertyWriter& propertyWriter = propertyWriters.at(i); @@ -651,7 +1141,7 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) { QByteArray remoteHashResult(localHashResult.size(), 0); read(remoteHashResult.data(), remoteHashResult.size() * BITS_IN_BYTE); if (metaObject && matches && localHashResult == remoteHashResult) { - objectReader = ObjectReader(className, metaObject, getPropertyReaders(metaObject)); + objectReader = ObjectReader(className, metaObject, getPropertyReaders().value(metaObject)); return *this; } } @@ -912,14 +1402,17 @@ Bitstream& Bitstream::operator>(AttributePointer& attribute) { return *this; } +const QString INVALID_STRING("%INVALID%"); + Bitstream& Bitstream::operator<(const QScriptString& string) { - return *this << string.toString(); + return *this << (string.isValid() ? string.toString() : INVALID_STRING); } Bitstream& Bitstream::operator>(QScriptString& string) { QString rawString; *this >> rawString; - string = ScriptCache::getInstance()->getEngine()->toStringHandle(rawString); + string = (rawString == INVALID_STRING) ? QScriptString() : + ScriptCache::getInstance()->getEngine()->toStringHandle(rawString); return *this; } @@ -988,31 +1481,6 @@ void Bitstream::clearSharedObject(QObject* object) { } } -const QVector& Bitstream::getPropertyWriters(const QMetaObject* metaObject) { - QVector& propertyWriters = _propertyWriters[metaObject]; - if (propertyWriters.isEmpty()) { - for (int i = 0; i < metaObject->propertyCount(); i++) { - QMetaProperty property = metaObject->property(i); - if (!property.isStored()) { - continue; - } - const TypeStreamer* streamer; - if (property.isEnumType()) { - QMetaEnum metaEnum = property.enumerator(); - streamer = getEnumStreamers().value(QPair( - QByteArray::fromRawData(metaEnum.scope(), strlen(metaEnum.scope())), - QByteArray::fromRawData(metaEnum.name(), strlen(metaEnum.name())))); - } else { - streamer = getTypeStreamers().value(property.userType()); - } - if (streamer) { - propertyWriters.append(PropertyWriter(property, streamer)); - } - } - } - return propertyWriters; -} - QHash& Bitstream::getMetaObjects() { static QHash metaObjects; return metaObjects; @@ -1028,42 +1496,100 @@ QHash& Bitstream::getTypeStreamers() { return typeStreamers; } -QHash, const TypeStreamer*>& Bitstream::getEnumStreamers() { - static QHash, const TypeStreamer*> enumStreamers; +const QHash& Bitstream::getEnumStreamers() { + static QHash enumStreamers = createEnumStreamers(); return enumStreamers; } -QHash& Bitstream::getEnumStreamersByName() { - static QHash enumStreamersByName; +QHash Bitstream::createEnumStreamers() { + QHash enumStreamers; + foreach (const QMetaObject* metaObject, getMetaObjects()) { + for (int i = 0; i < metaObject->enumeratorCount(); i++) { + QMetaEnum metaEnum = metaObject->enumerator(i); + const TypeStreamer*& streamer = enumStreamers[ScopeNamePair(metaEnum.scope(), metaEnum.name())]; + if (!streamer) { + streamer = new EnumTypeStreamer(metaEnum); + } + } + } + return enumStreamers; +} + +const QHash& Bitstream::getEnumStreamersByName() { + static QHash enumStreamersByName = createEnumStreamersByName(); return enumStreamersByName; } -QVector Bitstream::getPropertyReaders(const QMetaObject* metaObject) { - QVector propertyReaders; - if (!metaObject) { - return propertyReaders; +QHash Bitstream::createEnumStreamersByName() { + QHash enumStreamersByName; + foreach (const TypeStreamer* streamer, getEnumStreamers()) { + enumStreamersByName.insert(streamer->getName(), streamer); } - for (int i = 0; i < metaObject->propertyCount(); i++) { - QMetaProperty property = metaObject->property(i); - if (!property.isStored()) { - continue; - } - const TypeStreamer* streamer; - if (property.isEnumType()) { - QMetaEnum metaEnum = property.enumerator(); - streamer = getEnumStreamers().value(QPair( - QByteArray::fromRawData(metaEnum.scope(), strlen(metaEnum.scope())), - QByteArray::fromRawData(metaEnum.name(), strlen(metaEnum.name())))); - } else { - streamer = getTypeStreamers().value(property.userType()); - } - if (streamer) { - propertyReaders.append(PropertyReader(TypeReader(QByteArray(), streamer), property)); + return enumStreamersByName; +} + +const QHash& Bitstream::getPropertyReaders() { + static QHash propertyReaders = createPropertyReaders(); + return propertyReaders; +} + +QHash Bitstream::createPropertyReaders() { + QHash propertyReaders; + foreach (const QMetaObject* metaObject, getMetaObjects()) { + PropertyReaderVector& readers = propertyReaders[metaObject]; + 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(ScopeNamePair( + QByteArray::fromRawData(metaEnum.scope(), strlen(metaEnum.scope())), + QByteArray::fromRawData(metaEnum.name(), strlen(metaEnum.name())))); + } else { + streamer = getTypeStreamers().value(property.userType()); + } + if (streamer) { + readers.append(PropertyReader(TypeReader(QByteArray(), streamer), property)); + } } } return propertyReaders; } +const QHash& Bitstream::getPropertyWriters() { + static QHash propertyWriters = createPropertyWriters(); + return propertyWriters; +} + +QHash Bitstream::createPropertyWriters() { + QHash propertyWriters; + foreach (const QMetaObject* metaObject, getMetaObjects()) { + PropertyWriterVector& writers = propertyWriters[metaObject]; + 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(ScopeNamePair( + QByteArray::fromRawData(metaEnum.scope(), strlen(metaEnum.scope())), + QByteArray::fromRawData(metaEnum.name(), strlen(metaEnum.name())))); + } else { + streamer = getTypeStreamers().value(property.userType()); + } + if (streamer) { + writers.append(PropertyWriter(property, streamer)); + } + } + } + return propertyWriters; +} + TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer) : _typeName(typeName), _streamer(streamer), @@ -1304,7 +1830,7 @@ void FieldReader::readDelta(Bitstream& in, const TypeStreamer* streamer, QVarian } ObjectReader::ObjectReader(const QByteArray& className, const QMetaObject* metaObject, - const QVector& properties) : + const PropertyReaderVector& properties) : _className(className), _metaObject(metaObject), _properties(properties) { @@ -1461,17 +1987,21 @@ QDebug& operator<<(QDebug& debug, const QMetaObject* metaObject) { return debug << (metaObject ? metaObject->className() : "null"); } +EnumTypeStreamer::EnumTypeStreamer(const QMetaObject* metaObject, const char* name) : + _metaObject(metaObject), + _enumName(name), + _name(QByteArray(metaObject->className()) + "::" + name), + _bits(-1) { + + setType(QMetaType::Int); +} + EnumTypeStreamer::EnumTypeStreamer(const QMetaEnum& metaEnum) : + _name(QByteArray(metaEnum.scope()) + "::" + metaEnum.name()), _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); + _bits(-1) { + + setType(QMetaType::Int); } const char* EnumTypeStreamer::getName() const { @@ -1483,10 +2013,21 @@ TypeReader::Type EnumTypeStreamer::getReaderType() const { } int EnumTypeStreamer::getBits() const { + if (_bits == -1) { + int highestValue = 0; + QMetaEnum metaEnum = getMetaEnum(); + for (int j = 0; j < metaEnum.keyCount(); j++) { + highestValue = qMax(highestValue, metaEnum.value(j)); + } + const_cast(this)->_bits = getBitsForHighestValue(highestValue); + } return _bits; } QMetaEnum EnumTypeStreamer::getMetaEnum() const { + if (!_metaEnum.isValid()) { + const_cast(this)->_metaEnum = _metaObject->enumerator(_metaObject->indexOfEnumerator(_enumName)); + } return _metaEnum; } @@ -1496,12 +2037,12 @@ bool EnumTypeStreamer::equal(const QVariant& first, const QVariant& second) cons void EnumTypeStreamer::write(Bitstream& out, const QVariant& value) const { int intValue = value.toInt(); - out.write(&intValue, _bits); + out.write(&intValue, getBits()); } QVariant EnumTypeStreamer::read(Bitstream& in) const { int intValue = 0; - in.read(&intValue, _bits); + in.read(&intValue, getBits()); return intValue; } @@ -1511,7 +2052,7 @@ void EnumTypeStreamer::writeDelta(Bitstream& out, const QVariant& value, const Q out << false; } else { out << true; - out.write(&intValue, _bits); + out.write(&intValue, getBits()); } } @@ -1520,7 +2061,7 @@ void EnumTypeStreamer::readDelta(Bitstream& in, QVariant& value, const QVariant& in >> changed; if (changed) { int intValue = 0; - in.read(&intValue, _bits); + in.read(&intValue, getBits()); value = intValue; } else { value = reference; @@ -1529,17 +2070,17 @@ void EnumTypeStreamer::readDelta(Bitstream& in, QVariant& value, const QVariant& void EnumTypeStreamer::writeRawDelta(Bitstream& out, const QVariant& value, const QVariant& reference) const { int intValue = value.toInt(); - out.write(&intValue, _bits); + out.write(&intValue, getBits()); } void EnumTypeStreamer::readRawDelta(Bitstream& in, QVariant& value, const QVariant& reference) const { int intValue = 0; - in.read(&intValue, _bits); + in.read(&intValue, getBits()); value = intValue; } void EnumTypeStreamer::setEnumValue(QVariant& object, int value, const QHash& mappings) const { - if (_metaEnum.isFlag()) { + if (getMetaEnum().isFlag()) { int combined = 0; for (QHash::const_iterator it = mappings.constBegin(); it != mappings.constEnd(); it++) { if (value & it.key()) { diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 146713910f..d05f6574c0 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -29,6 +29,7 @@ class QByteArray; class QColor; class QDataStream; +class QScriptValue; class QUrl; class Attribute; @@ -44,6 +45,10 @@ class TypeStreamer; typedef SharedObjectPointerTemplate AttributePointer; +typedef QPair ScopeNamePair; +typedef QVector PropertyReaderVector; +typedef QVector PropertyWriterVector; + /// Streams integer identifiers that conform to the following pattern: each ID encountered in the stream is either one that /// has been sent (received) before, or is one more than the highest previously encountered ID (starting at zero). This allows /// us to use the minimum number of bits to encode the IDs. @@ -287,11 +292,15 @@ public: template void writeDelta(const T& value, const T& reference); template void readDelta(T& value, const T& reference); + void writeRawDelta(const QVariant& value, const QVariant& reference); void readRawDelta(QVariant& value, const QVariant& reference); void writeRawDelta(const QObject* value, const QObject* reference); void readRawDelta(QObject*& value, const QObject* reference); + void writeRawDelta(const QScriptValue& value, const QScriptValue& reference); + void readRawDelta(QScriptValue& value, const QScriptValue& reference); + template void writeRawDelta(const T& value, const T& reference); template void readRawDelta(T& value, const T& reference); @@ -316,9 +325,15 @@ public: Bitstream& operator<<(uint value); Bitstream& operator>>(uint& value); + Bitstream& operator<<(qint64 value); + Bitstream& operator>>(qint64& value); + Bitstream& operator<<(float value); Bitstream& operator>>(float& value); + Bitstream& operator<<(double value); + Bitstream& operator>>(double& value); + Bitstream& operator<<(const glm::vec3& value); Bitstream& operator>>(glm::vec3& value); @@ -337,6 +352,12 @@ public: Bitstream& operator<<(const QUrl& url); Bitstream& operator>>(QUrl& url); + Bitstream& operator<<(const QDateTime& dateTime); + Bitstream& operator>>(QDateTime& dateTime); + + Bitstream& operator<<(const QRegExp& regExp); + Bitstream& operator>>(QRegExp& regExp); + Bitstream& operator<<(const QVariant& value); Bitstream& operator>>(QVariant& value); @@ -372,6 +393,9 @@ public: Bitstream& operator<<(const QScriptString& string); Bitstream& operator>>(QScriptString& string); + Bitstream& operator<<(const QScriptValue& value); + Bitstream& operator>>(QScriptValue& value); + Bitstream& operator<<(const SharedObjectPointer& object); Bitstream& operator>>(SharedObjectPointer& object); @@ -400,8 +424,6 @@ private slots: private: - const QVector& getPropertyWriters(const QMetaObject* metaObject); - QDataStream& _underlying; quint8 _byte; int _position; @@ -421,14 +443,21 @@ private: QHash _metaObjectSubstitutions; QHash _typeStreamerSubstitutions; - QHash > _propertyWriters; - static QHash& getMetaObjects(); static QMultiHash& getMetaObjectSubClasses(); static QHash& getTypeStreamers(); - static QHash, const TypeStreamer*>& getEnumStreamers(); - static QHash& getEnumStreamersByName(); - static QVector getPropertyReaders(const QMetaObject* metaObject); + + static const QHash& getEnumStreamers(); + static QHash createEnumStreamers(); + + static const QHash& getEnumStreamersByName(); + static QHash createEnumStreamersByName(); + + static const QHash& getPropertyReaders(); + static QHash createPropertyReaders(); + + static const QHash& getPropertyWriters(); + static QHash createPropertyWriters(); }; template inline void Bitstream::writeDelta(const T& value, const T& reference) { @@ -938,8 +967,9 @@ public: class EnumTypeStreamer : public TypeStreamer { public: + EnumTypeStreamer(const QMetaObject* metaObject, const char* name); EnumTypeStreamer(const QMetaEnum& metaEnum); - + virtual const char* getName() const; virtual TypeReader::Type getReaderType() const; virtual int getBits() const; @@ -955,8 +985,10 @@ public: private: - QMetaEnum _metaEnum; + const QMetaObject* _metaObject; + const char* _enumName; QByteArray _name; + QMetaEnum _metaEnum; int _bits; }; @@ -1037,12 +1069,12 @@ public: }; /// Macro for registering simple type streamers. -#define REGISTER_SIMPLE_TYPE_STREAMER(x) static int x##Streamer = \ - Bitstream::registerTypeStreamer(qMetaTypeId(), new SimpleTypeStreamer()); +#define REGISTER_SIMPLE_TYPE_STREAMER(X) static int X##Streamer = \ + Bitstream::registerTypeStreamer(qMetaTypeId(), new SimpleTypeStreamer()); /// Macro for registering collection type streamers. -#define REGISTER_COLLECTION_TYPE_STREAMER(x) static int x##Streamer = \ - Bitstream::registerTypeStreamer(qMetaTypeId(), new CollectionTypeStreamer()); +#define REGISTER_COLLECTION_TYPE_STREAMER(X) static int x##Streamer = \ + Bitstream::registerTypeStreamer(qMetaTypeId(), new CollectionTypeStreamer()); /// Declares the metatype and the streaming operators. The last lines /// ensure that the generated file will be included in the link phase. @@ -1077,14 +1109,42 @@ public: _Pragma(STRINGIFY(unused(_TypePtr##X))) #endif +#define DECLARE_ENUM_METATYPE(S, N) Q_DECLARE_METATYPE(S::N) \ + Bitstream& operator<<(Bitstream& out, const S::N& obj); \ + Bitstream& operator>>(Bitstream& in, S::N& obj); \ + template<> inline void Bitstream::writeRawDelta(const S::N& value, const S::N& reference) { *this << value; } \ + template<> inline void Bitstream::readRawDelta(S::N& value, const S::N& reference) { *this >> value; } + +#define IMPLEMENT_ENUM_METATYPE(S, N) \ + static int S##N##MetaTypeId = registerEnumMetaType(&S::staticMetaObject, #N); \ + Bitstream& operator<<(Bitstream& out, const S::N& obj) { \ + static int bits = Bitstream::getTypeStreamer(qMetaTypeId())->getBits(); \ + return out.write(&obj, bits); \ + } \ + Bitstream& operator>>(Bitstream& in, S::N& obj) { \ + static int bits = Bitstream::getTypeStreamer(qMetaTypeId())->getBits(); \ + obj = (S::N)0; \ + return in.read(&obj, bits); \ + } + /// Registers a simple type and its streamer. +/// \return the metatype id template int registerSimpleMetaType() { int type = qRegisterMetaType(); Bitstream::registerTypeStreamer(type, new SimpleTypeStreamer()); return type; } +/// Registers an enum type and its streamer. +/// \return the metatype id +template int registerEnumMetaType(const QMetaObject* metaObject, const char* name) { + int type = qRegisterMetaType(); + Bitstream::registerTypeStreamer(type, new EnumTypeStreamer(metaObject, name)); + return type; +} + /// Registers a streamable type and its streamer. +/// \return the metatype id template int registerStreamableMetaType() { int type = qRegisterMetaType(); Bitstream::registerTypeStreamer(type, new StreamableTypeStreamer()); @@ -1092,6 +1152,7 @@ template int registerStreamableMetaType() { } /// Registers a collection type and its streamer. +/// \return the metatype id template int registerCollectionMetaType() { int type = qRegisterMetaType(); Bitstream::registerTypeStreamer(type, new CollectionTypeStreamer()); diff --git a/libraries/metavoxels/src/ScriptCache.cpp b/libraries/metavoxels/src/ScriptCache.cpp index dd090613b7..9648a047cb 100644 --- a/libraries/metavoxels/src/ScriptCache.cpp +++ b/libraries/metavoxels/src/ScriptCache.cpp @@ -13,11 +13,95 @@ #include #include +#include #include #include "AttributeRegistry.h" #include "ScriptCache.h" +static int scriptValueMetaTypeId = qRegisterMetaType(); +static bool scriptValueComparators = QMetaType::registerComparators(); + +bool operator==(const QScriptValue& first, const QScriptValue& second) { + if (first.isUndefined()) { + return second.isUndefined(); + + } else if (first.isNull()) { + return second.isNull(); + + } else if (first.isBool()) { + return second.isBool() && first.toBool() == second.toBool(); + + } else if (first.isNumber()) { + return second.isNumber() && first.toNumber() == second.toNumber(); + + } else if (first.isString()) { + return second.isString() && first.toString() == second.toString(); + + } else if (first.isVariant()) { + return second.isVariant() && first.toVariant() == second.toVariant(); + + } else if (first.isQObject()) { + return second.isQObject() && first.toQObject() == second.toQObject(); + + } else if (first.isQMetaObject()) { + return second.isQMetaObject() && first.toQMetaObject() == second.toQMetaObject(); + + } else if (first.isDate()) { + return second.isDate() && first.toDateTime() == second.toDateTime(); + + } else if (first.isRegExp()) { + return second.isRegExp() && first.toRegExp() == second.toRegExp(); + + } else if (first.isArray()) { + if (!second.isArray()) { + return false; + } + int length = first.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + if (second.property(ScriptCache::getInstance()->getLengthString()).toInt32() != length) { + return false; + } + for (int i = 0; i < length; i++) { + if (first.property(i) != second.property(i)) { + return false; + } + } + return true; + + } else if (first.isObject()) { + if (!second.isObject()) { + return false; + } + int propertyCount = 0; + for (QScriptValueIterator it(first); it.hasNext(); ) { + it.next(); + if (second.property(it.scriptName()) != it.value()) { + return false; + } + propertyCount++; + } + // make sure the second has exactly as many properties as the first + for (QScriptValueIterator it(second); it.hasNext(); ) { + it.next(); + if (--propertyCount < 0) { + return false; + } + } + return true; + + } else { + return !second.isValid(); + } +} + +bool operator!=(const QScriptValue& first, const QScriptValue& second) { + return !(first == second); +} + +bool operator<(const QScriptValue& first, const QScriptValue& second) { + return first.lessThan(second); +} + ScriptCache* ScriptCache::getInstance() { static ScriptCache cache; return &cache; diff --git a/libraries/metavoxels/src/ScriptCache.h b/libraries/metavoxels/src/ScriptCache.h index f393d0e0a8..5d29157b3d 100644 --- a/libraries/metavoxels/src/ScriptCache.h +++ b/libraries/metavoxels/src/ScriptCache.h @@ -65,6 +65,12 @@ private: QScriptString _generatorString; }; +Q_DECLARE_METATYPE(QScriptValue) + +bool operator==(const QScriptValue& first, const QScriptValue& second); +bool operator!=(const QScriptValue& first, const QScriptValue& second); +bool operator<(const QScriptValue& first, const QScriptValue& second); + /// A program loaded from the network. class NetworkProgram : public Resource { Q_OBJECT diff --git a/libraries/models/src/ModelTree.cpp b/libraries/models/src/ModelTree.cpp index e88a969061..466d4c5273 100644 --- a/libraries/models/src/ModelTree.cpp +++ b/libraries/models/src/ModelTree.cpp @@ -185,7 +185,7 @@ void ModelTree::addModel(const ModelItemID& modelID, const ModelItemProperties& glm::vec3 position = model.getPosition(); float size = std::max(MINIMUM_MODEL_ELEMENT_SIZE, model.getRadius()); - ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size); + ModelTreeElement* element = static_cast(getOrCreateChildElementAt(position.x, position.y, position.z, size)); element->storeModel(model); _isDirty = true; diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index b0c7e125b4..75b9670d0f 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -186,6 +186,11 @@ bool ModelTreeElement::findDetailedRayIntersection(const glm::vec3& origin, cons if (fbxGeometry && fbxGeometry->meshExtents.isValid()) { Extents extents = fbxGeometry->meshExtents; + // NOTE: If the model has a bad mesh, then extents will be 0,0,0 & 0,0,0 + if (extents.minimum == extents.maximum && extents.minimum == glm::vec3(0,0,0)) { + extents.maximum = glm::vec3(1.0f,1.0f,1.0f); // in this case we will simulate the unit cube + } + // NOTE: these extents are model space, so we need to scale and center them accordingly // size is our "target size in world space" // we need to set our model scale so that the extents of the mesh, fit in a cube that size... diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 7d27332a57..b4aedbcb7c 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -313,7 +313,7 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas QByteArray postData; postData.append("grant_type=password&"); postData.append("username=" + login + "&"); - postData.append("password=" + password + "&"); + postData.append("password=" + QUrl::toPercentEncoding(password) + "&"); postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE); request.setUrl(grantURL); diff --git a/libraries/shared/src/CapsuleShape.cpp b/libraries/shared/src/CapsuleShape.cpp index 11bd70f8d2..8e887107dc 100644 --- a/libraries/shared/src/CapsuleShape.cpp +++ b/libraries/shared/src/CapsuleShape.cpp @@ -33,20 +33,7 @@ CapsuleShape::CapsuleShape(float radius, float halfHeight, const glm::vec3& posi CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint) : Shape(Shape::CAPSULE_SHAPE), _radius(radius), _halfHeight(0.0f) { - glm::vec3 axis = endPoint - startPoint; - _position = 0.5f * (endPoint + startPoint); - float height = glm::length(axis); - if (height > EPSILON) { - _halfHeight = 0.5f * height; - axis /= height; - glm::vec3 yAxis(0.0f, 1.0f, 0.0f); - float angle = glm::angle(axis, yAxis); - if (angle > EPSILON) { - axis = glm::normalize(glm::cross(yAxis, axis)); - _rotation = glm::angleAxis(angle, axis); - } - } - updateBoundingRadius(); + setEndPoints(startPoint, endPoint); } /// \param[out] startPoint is the center of start cap @@ -80,3 +67,20 @@ void CapsuleShape::setRadiusAndHalfHeight(float radius, float halfHeight) { updateBoundingRadius(); } +void CapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint) { + glm::vec3 axis = endPoint - startPoint; + _position = 0.5f * (endPoint + startPoint); + float height = glm::length(axis); + if (height > EPSILON) { + _halfHeight = 0.5f * height; + axis /= height; + glm::vec3 yAxis(0.0f, 1.0f, 0.0f); + float angle = glm::angle(axis, yAxis); + if (angle > EPSILON) { + axis = glm::normalize(glm::cross(yAxis, axis)); + _rotation = glm::angleAxis(angle, axis); + } + } + updateBoundingRadius(); +} + diff --git a/libraries/shared/src/CapsuleShape.h b/libraries/shared/src/CapsuleShape.h index 0889f6b2f3..756ae18911 100644 --- a/libraries/shared/src/CapsuleShape.h +++ b/libraries/shared/src/CapsuleShape.h @@ -37,6 +37,7 @@ public: void setRadius(float radius); void setHalfHeight(float height); void setRadiusAndHalfHeight(float radius, float height); + void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint); protected: void updateBoundingRadius() { _boundingRadius = _radius + _halfHeight; } diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 603f63b587..6ec2331b14 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -11,6 +11,8 @@ #include +#include + #include #include @@ -20,12 +22,16 @@ REGISTER_META_OBJECT(TestSharedObjectA) REGISTER_META_OBJECT(TestSharedObjectB) +IMPLEMENT_ENUM_METATYPE(TestSharedObjectA, TestEnum) + MetavoxelTests::MetavoxelTests(int& argc, char** argv) : QCoreApplication(argc, argv) { } static int datagramsSent = 0; static int datagramsReceived = 0; +static int bytesSent = 0; +static int bytesReceived = 0; static int highPriorityMessagesSent = 0; static int highPriorityMessagesReceived = 0; static int unreliableMessagesSent = 0; @@ -36,6 +42,9 @@ static int streamedBytesSent = 0; static int streamedBytesReceived = 0; static int sharedObjectsCreated = 0; static int sharedObjectsDestroyed = 0; +static int objectMutationsPerformed = 0; +static int scriptObjectsCreated = 0; +static int scriptMutationsPerformed = 0; static QByteArray createRandomBytes(int minimumSize, int maximumSize) { QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0); @@ -74,12 +83,56 @@ static TestSharedObjectA::TestFlags getRandomTestFlags() { return flags; } +static QScriptValue createRandomScriptValue(bool complex = false) { + scriptObjectsCreated++; + switch (randIntInRange(0, complex ? 5 : 3)) { + case 0: + return QScriptValue(QScriptValue::NullValue); + + case 1: + return QScriptValue(randomBoolean()); + + case 2: + return QScriptValue(randFloat()); + + case 3: + return QScriptValue(QString(createRandomBytes())); + + case 4: { + int length = randIntInRange(2, 6); + QScriptValue value = ScriptCache::getInstance()->getEngine()->newArray(length); + for (int i = 0; i < length; i++) { + value.setProperty(i, createRandomScriptValue()); + } + return value; + } + default: { + QScriptValue value = ScriptCache::getInstance()->getEngine()->newObject(); + if (randomBoolean()) { + value.setProperty("foo", createRandomScriptValue()); + } + if (randomBoolean()) { + value.setProperty("bar", createRandomScriptValue()); + } + if (randomBoolean()) { + value.setProperty("baz", createRandomScriptValue()); + } + if (randomBoolean()) { + value.setProperty("bong", createRandomScriptValue()); + } + return value; + } + } +} + static TestMessageC createRandomMessageC() { TestMessageC message; message.foo = randomBoolean(); message.bar = rand(); message.baz = randFloat(); message.bong.foo = createRandomBytes(); + message.bong.baz = getRandomTestEnum(); + message.bizzle = createRandomScriptValue(true); return message; } @@ -180,7 +233,7 @@ bool MetavoxelTests::run() { bob.setOther(&alice); // perform a large number of simulation iterations - const int SIMULATION_ITERATIONS = 100000; + const int SIMULATION_ITERATIONS = 10000; for (int i = 0; i < SIMULATION_ITERATIONS; i++) { if (alice.simulate(i) || bob.simulate(i)) { return true; @@ -191,8 +244,11 @@ bool MetavoxelTests::run() { qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived; qDebug() << "Sent" << reliableMessagesSent << "reliable messages, received" << reliableMessagesReceived; qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived; - qDebug() << "Sent" << datagramsSent << "datagrams, received" << datagramsReceived; + qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" << + datagramsReceived << "with" << bytesReceived << "bytes"; qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed; + qDebug() << "Performed" << objectMutationsPerformed << "object mutations"; + qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed; qDebug(); qDebug() << "Running serialization tests..."; @@ -226,6 +282,20 @@ Endpoint::Endpoint(const QByteArray& datagramHeader) : connect(_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleHighPriorityMessage(const QVariant&))); + connect(_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int))); + connect(_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int))); + + // insert the baseline send record + SendRecord sendRecord = { 0 }; + _sendRecords.append(sendRecord); + + // insert the baseline receive record + ReceiveRecord receiveRecord = { 0 }; + _receiveRecords.append(receiveRecord); + + // create the object that represents out delta-encoded state + _localState = new TestSharedObjectA(); + connect(_sequencer->getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)), SLOT(handleReliableMessage(const QVariant&))); @@ -252,16 +322,72 @@ static QVariant createRandomMessage() { return QVariant::fromValue(message); } case 1: { - TestMessageB message = { createRandomBytes(), createRandomSharedObject() }; + TestMessageB message = { createRandomBytes(), createRandomSharedObject(), getRandomTestEnum() }; return QVariant::fromValue(message); } - case 2: default: { return QVariant::fromValue(createRandomMessageC()); } } } +static SharedObjectPointer mutate(const SharedObjectPointer& state) { + switch (randIntInRange(0, 4)) { + case 0: { + SharedObjectPointer newState = state->clone(true); + static_cast(newState.data())->setFoo(randFloat()); + objectMutationsPerformed++; + return newState; + } + case 1: { + SharedObjectPointer newState = state->clone(true); + static_cast(newState.data())->setBaz(getRandomTestEnum()); + objectMutationsPerformed++; + return newState; + } + case 2: { + SharedObjectPointer newState = state->clone(true); + static_cast(newState.data())->setBong(getRandomTestFlags()); + objectMutationsPerformed++; + return newState; + } + case 3: { + SharedObjectPointer newState = state->clone(true); + QScriptValue oldValue = static_cast(newState.data())->getBizzle(); + QScriptValue newValue = ScriptCache::getInstance()->getEngine()->newObject(); + for (QScriptValueIterator it(oldValue); it.hasNext(); ) { + it.next(); + newValue.setProperty(it.scriptName(), it.value()); + } + switch (randIntInRange(0, 2)) { + case 0: { + QScriptValue oldArray = oldValue.property("foo"); + int oldLength = oldArray.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + QScriptValue newArray = ScriptCache::getInstance()->getEngine()->newArray(oldLength); + for (int i = 0; i < oldLength; i++) { + newArray.setProperty(i, oldArray.property(i)); + } + newArray.setProperty(randIntInRange(0, oldLength - 1), createRandomScriptValue(true)); + break; + } + case 1: + newValue.setProperty("bar", QScriptValue(randFloat())); + break; + + default: + newValue.setProperty("baz", createRandomScriptValue(true)); + break; + } + static_cast(newState.data())->setBizzle(newValue); + scriptMutationsPerformed++; + objectMutationsPerformed++; + return newState; + } + default: + return state; + } +} + static bool messagesEqual(const QVariant& firstMessage, const QVariant& secondMessage) { int type = firstMessage.userType(); if (secondMessage.userType() != type) { @@ -273,7 +399,7 @@ static bool messagesEqual(const QVariant& firstMessage, const QVariant& secondMe } else if (type == TestMessageB::Type) { TestMessageB first = firstMessage.value(); TestMessageB second = secondMessage.value(); - return first.foo == second.foo && equals(first.bar, second.bar); + return first.foo == second.foo && equals(first.bar, second.bar) && first.baz == second.baz; } else if (type == TestMessageC::Type) { return firstMessage.value() == secondMessage.value(); @@ -320,10 +446,13 @@ bool Endpoint::simulate(int iterationNumber) { _reliableMessagesToSend -= 1.0f; } + // tweak the local state + _localState = mutate(_localState); + // send a packet try { Bitstream& out = _sequencer->startPacket(); - SequencedTestMessage message = { iterationNumber, createRandomMessage() }; + SequencedTestMessage message = { iterationNumber, createRandomMessage(), _localState }; _unreliableMessagesSent.append(message); unreliableMessagesSent++; out << message; @@ -334,11 +463,16 @@ bool Endpoint::simulate(int iterationNumber) { return true; } + // record the send + SendRecord record = { _sequencer->getOutgoingPacketNumber(), _localState }; + _sendRecords.append(record); + return false; } void Endpoint::sendDatagram(const QByteArray& datagram) { datagramsSent++; + bytesSent += datagram.size(); // some datagrams are dropped const float DROP_PROBABILITY = 0.1f; @@ -364,6 +498,7 @@ void Endpoint::sendDatagram(const QByteArray& datagram) { _other->_sequencer->receivedDatagram(datagram); datagramsReceived++; + bytesReceived += datagram.size(); } void Endpoint::handleHighPriorityMessage(const QVariant& message) { @@ -384,12 +519,21 @@ void Endpoint::readMessage(Bitstream& in) { SequencedTestMessage message; in >> message; + _remoteState = message.state; + + // record the receipt + ReceiveRecord record = { _sequencer->getIncomingPacketNumber(), message.state }; + _receiveRecords.append(record); + for (QList::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."); } + if (!it->state->equals(message.state)) { + throw QString("Delta-encoded object mismatch."); + } _other->_unreliableMessagesSent.erase(_other->_unreliableMessagesSent.begin(), it + 1); unreliableMessagesReceived++; return; @@ -427,11 +571,22 @@ void Endpoint::readReliableChannel() { streamedBytesReceived += bytes.size(); } +void Endpoint::clearSendRecordsBefore(int index) { + _sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1); +} + +void Endpoint::clearReceiveRecordsBefore(int index) { + _receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1); +} + TestSharedObjectA::TestSharedObjectA(float foo, TestEnum baz, TestFlags bong) : _foo(foo), _baz(baz), _bong(bong) { - sharedObjectsCreated++; + sharedObjectsCreated++; + + _bizzle = ScriptCache::getInstance()->getEngine()->newObject(); + _bizzle.setProperty("foo", ScriptCache::getInstance()->getEngine()->newArray(4)); } TestSharedObjectA::~TestSharedObjectA() { diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index 5e020b1e60..ac9eda2659 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -16,6 +16,7 @@ #include #include +#include class SequencedTestMessage; @@ -54,9 +55,30 @@ private slots: void handleReliableMessage(const QVariant& message); void readReliableChannel(); + void clearSendRecordsBefore(int index); + void clearReceiveRecordsBefore(int index); + private: + class SendRecord { + public: + int packetNumber; + SharedObjectPointer localState; + }; + + class ReceiveRecord { + public: + int packetNumber; + SharedObjectPointer remoteState; + }; + DatagramSequencer* _sequencer; + QList _sendRecords; + QList _receiveRecords; + + SharedObjectPointer _localState; + SharedObjectPointer _remoteState; + Endpoint* _other; QList > _delayedDatagrams; float _highPriorityMessagesToSend; @@ -75,7 +97,8 @@ class TestSharedObjectA : public SharedObject { 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) - + Q_PROPERTY(QScriptValue bizzle READ getBizzle WRITE setBizzle) + public: enum TestEnum { FIRST_TEST_ENUM, SECOND_TEST_ENUM, THIRD_TEST_ENUM }; @@ -95,6 +118,9 @@ public: void setBong(TestFlags bong) { _bong = bong; } TestFlags getBong() const { return _bong; } + void setBizzle(const QScriptValue& bizzle) { _bizzle = bizzle; } + const QScriptValue& getBizzle() const { return _bizzle; } + signals: void fooChanged(float foo); @@ -104,8 +130,11 @@ private: float _foo; TestEnum _baz; TestFlags _bong; + QScriptValue _bizzle; }; +DECLARE_ENUM_METATYPE(TestSharedObjectA, TestEnum) + /// Another simple shared object. class TestSharedObjectB : public SharedObject { Q_OBJECT @@ -169,6 +198,7 @@ public: STREAM QByteArray foo; STREAM SharedObjectPointer bar; + STREAM TestSharedObjectA::TestEnum baz; }; DECLARE_STREAMABLE_METATYPE(TestMessageB) @@ -180,6 +210,7 @@ class TestMessageC : STREAM public TestMessageA { public: STREAM TestMessageB bong; + STREAM QScriptValue bizzle; }; DECLARE_STREAMABLE_METATYPE(TestMessageC) @@ -192,6 +223,7 @@ public: STREAM int sequenceNumber; STREAM QVariant submessage; + STREAM SharedObjectPointer state; }; DECLARE_STREAMABLE_METATYPE(SequencedTestMessage)