diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 52a027852c..23e8c0d965 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -878,10 +878,10 @@ void OctreeServer::setupDatagramProcessingThread() { // we do not want this event loop to be the handler for UDP datagrams, so disconnect disconnect(&nodeList->getNodeSocket(), 0, this, 0); - // setup a QThread with us as parent that will house the AudioMixerDatagramProcessor + // setup a QThread with us as parent that will house the OctreeServerDatagramProcessor _datagramProcessingThread = new QThread(this); - // create an AudioMixerDatagramProcessor and move it to that thread + // create an OctreeServerDatagramProcessor and move it to that thread OctreeServerDatagramProcessor* datagramProcessor = new OctreeServerDatagramProcessor(nodeList->getNodeSocket(), thread()); datagramProcessor->moveToThread(_datagramProcessingThread); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 56570a4b82..d17caf24f0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -189,6 +189,12 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { populateDefaultStaticAssignmentsExcludingTypes(parsedTypes); LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort); + + const QString DOMAIN_CONFIG_ID_KEY = "id"; + + // set our LimitedNodeList UUID to match the UUID from our config + // nodes will currently use this to add resources to data-web that relate to our domain + nodeList->setSessionUUID(_argumentVariantMap.value(DOMAIN_CONFIG_ID_KEY).toString()); connect(nodeList, &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); diff --git a/examples/butterflies.js b/examples/butterflies.js index 1f612ed4bf..6b15680fa0 100644 --- a/examples/butterflies.js +++ b/examples/butterflies.js @@ -37,6 +37,7 @@ function vInterpolate(a, b, fraction) { var startTimeInSeconds = new Date().getTime() / 1000; +var NATURAL_SIZE_OF_BUTTERFLY = { x: 9.512, y: 4.427, z: 1.169 }; var lifeTime = 600; // lifetime of the butterflies in seconds var range = 1.0; // Over what distance in meters do you want the flock to fly around var frame = 0; @@ -78,7 +79,13 @@ function addButterfly() { var color = { red: 100, green: 100, blue: 100 }; var size = 0; + var minSize = 0.06; + var randomSize = 0.2; + var maxSize = minSize + randomSize; + size = 0.06 + Math.random() * 0.2; + + var dimensions = Vec3.multiply(NATURAL_SIZE_OF_BUTTERFLY, (size / maxSize)); flockPosition = Vec3.sum(MyAvatar.position,Vec3.sum( Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_ABOVE_ME), @@ -91,7 +98,7 @@ function addButterfly() { velocity: { x: 0, y: 0.0, z: 0 }, gravity: { x: 0, y: 1.0, z: 0 }, damping: 0.1, - radius : size, + dimensions: dimensions, color: color, rotation: rotation, animationURL: "http://business.ozblog.me/objects/butterfly/newButterfly2.fbx", @@ -203,7 +210,8 @@ function updateButterflies(deltaTime) { // If we are near the target, we should get a new target - if (Vec3.length(Vec3.subtract(properties.position, butterflies[i].targetPosition)) < (properties.radius / 1.0)) { + var halfLargestDimension = Vec3.length(properties.dimensions) / 2.0; + if (Vec3.length(Vec3.subtract(properties.position, butterflies[i].targetPosition)) < (halfLargestDimension)) { butterflies[i].moving = false; } @@ -214,7 +222,7 @@ function updateButterflies(deltaTime) { } // Use a cosine wave offset to make it look like its flapping. - var offset = Math.cos(nowTimeInSeconds * BUTTERFLY_FLAP_SPEED) * (properties.radius); + var offset = Math.cos(nowTimeInSeconds * BUTTERFLY_FLAP_SPEED) * (halfLargestDimension); properties.position.y = properties.position.y + (offset - butterflies[i].previousFlapOffset); // Change position relative to previous offset. butterflies[i].previousFlapOffset = offset; diff --git a/examples/editModels.js b/examples/editModels.js index cec1bff783..740c992888 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -40,7 +40,7 @@ var LEFT = 0; var RIGHT = 1; var SPAWN_DISTANCE = 1; -var DEFAULT_RADIUS = 0.10; +var DEFAULT_DIMENSION = 0.20; var modelURLs = [ "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", @@ -1220,7 +1220,7 @@ var toolBar = (function () { Entities.addEntity({ type: "Model", position: position, - radius: DEFAULT_RADIUS, + dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, modelURL: url }); print("Model added: " + url); @@ -1311,8 +1311,9 @@ var toolBar = (function () { Entities.addEntity({ type: "Box", position: position, - radius: DEFAULT_RADIUS, + dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, color: { red: 255, green: 0, blue: 0 } + }); } else { print("Can't create box: Box would be out of bounds."); @@ -1327,7 +1328,7 @@ var toolBar = (function () { Entities.addEntity({ type: "Sphere", position: position, - radius: DEFAULT_RADIUS, + dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, color: { red: 255, green: 0, blue: 0 } }); } else { @@ -1804,7 +1805,7 @@ function controller(wichSide) { this.modelURL = ""; this.oldModelRotation; this.oldModelPosition; - this.oldModelRadius; + this.oldModelHalfDiagonal; this.positionAtGrab; this.rotationAtGrab; @@ -1864,7 +1865,7 @@ function controller(wichSide) { this.oldModelPosition = properties.position; this.oldModelRotation = properties.rotation; - this.oldModelRadius = properties.radius; + this.oldModelHalfDiagonal = Vec3.length(properties.dimensions) / 2.0; this.positionAtGrab = this.palmPosition; this.rotationAtGrab = this.rotation; @@ -1873,7 +1874,7 @@ function controller(wichSide) { this.jointsIntersectingFromStart = []; for (var i = 0; i < jointList.length; i++) { var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); - if (distance < this.oldModelRadius) { + if (distance < this.oldModelHalfDiagonal) { this.jointsIntersectingFromStart.push(i); } } @@ -1897,10 +1898,10 @@ function controller(wichSide) { if (closestJointIndex != -1) { print("closestJoint: " + jointList[closestJointIndex]); - print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelRadius + ")"); + print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelHalfDiagonal + ")"); } - if (closestJointDistance < this.oldModelRadius) { + if (closestJointDistance < this.oldModelHalfDiagonal) { if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1 || (leftController.grabbing && rightController.grabbing && @@ -1916,7 +1917,7 @@ function controller(wichSide) { var attachmentRotation = Quat.multiply(Quat.inverse(jointRotation), this.oldModelRotation); MyAvatar.attach(this.modelURL, jointList[closestJointIndex], - attachmentOffset, attachmentRotation, 2.0 * this.oldModelRadius, + attachmentOffset, attachmentRotation, 2.0 * this.oldModelHalfDiagonal, true, false); Entities.deleteEntity(this.entityID); } @@ -1970,11 +1971,12 @@ function controller(wichSide) { var z = Vec3.dot(Vec3.subtract(P, A), this.right); var X = Vec3.sum(A, Vec3.multiply(B, x)); var d = Vec3.length(Vec3.subtract(P, X)); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - var angularSize = 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; if (0 < x && angularSize > MIN_ANGULAR_SIZE) { if (angularSize > MAX_ANGULAR_SIZE) { - print("Angular size too big: " + 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14); + print("Angular size too big: " + angularSize); return { valid: false }; } @@ -2021,7 +2023,10 @@ function controller(wichSide) { origin: this.palmPosition, direction: this.front }); - var angularSize = 2 * Math.atan(intersection.properties.radius / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14; + + var halfDiagonal = Vec3.length(intersection.properties.dimensions) / 2.0; + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14; if (intersection.accurate && intersection.entityID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) { this.glowedIntersectingModel = intersection.entityID; Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 }); @@ -2099,7 +2104,7 @@ function controller(wichSide) { var indicesToRemove = []; for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) { var distance = Vec3.distance(MyAvatar.getJointPosition(this.jointsIntersectingFromStart[i]), this.oldModelPosition); - if (distance >= this.oldModelRadius) { + if (distance >= this.oldModelHalfDiagonal) { indicesToRemove.push(this.jointsIntersectingFromStart[i]); } @@ -2192,7 +2197,12 @@ function controller(wichSide) { Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), attachments[attachmentIndex].translation)), rotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), attachments[attachmentIndex].rotation), - radius: attachments[attachmentIndex].scale / 2.0, + + // TODO: how do we know the correct dimensions for detachment??? + dimensions: { x: attachments[attachmentIndex].scale / 2.0, + y: attachments[attachmentIndex].scale / 2.0, + z: attachments[attachmentIndex].scale / 2.0 }, + modelURL: attachments[attachmentIndex].modelURL }; @@ -2310,15 +2320,21 @@ function moveEntities() { Entities.editEntity(leftController.entityID, { position: newPosition, rotation: rotation, - radius: leftController.oldModelRadius * ratio + // TODO: how do we know the correct dimensions for detachment??? + //radius: leftController.oldModelHalfDiagonal * ratio + dimensions: { x: leftController.oldModelHalfDiagonal * ratio, + y: leftController.oldModelHalfDiagonal * ratio, + z: leftController.oldModelHalfDiagonal * ratio } + + }); leftController.oldModelPosition = newPosition; leftController.oldModelRotation = rotation; - leftController.oldModelRadius *= ratio; + leftController.oldModelHalfDiagonal *= ratio; rightController.oldModelPosition = newPosition; rightController.oldModelRotation = rotation; - rightController.oldModelRadius *= ratio; + rightController.oldModelHalfDiagonal *= ratio; return; } leftController.moveEntity(); @@ -2379,7 +2395,7 @@ function Tooltip() { this.x = 285; this.y = 115; this.width = 500; - this.height = 145; + this.height = 180; // 145; this.margin = 5; this.decimals = 3; @@ -2407,7 +2423,14 @@ function Tooltip() { text += "Pitch: " + angles.x.toFixed(this.decimals) + "\n" text += "Yaw: " + angles.y.toFixed(this.decimals) + "\n" text += "Roll: " + angles.z.toFixed(this.decimals) + "\n" - text += "Scale: " + 2 * properties.radius.toFixed(this.decimals) + "\n" + text += "Dimensions: " + properties.dimensions.x.toFixed(this.decimals) + ", " + + properties.dimensions.y.toFixed(this.decimals) + ", " + + properties.dimensions.z.toFixed(this.decimals) + "\n"; + + text += "Natural Dimensions: " + properties.naturalDimensions.x.toFixed(this.decimals) + ", " + + properties.naturalDimensions.y.toFixed(this.decimals) + ", " + + properties.naturalDimensions.z.toFixed(this.decimals) + "\n"; + text += "ID: " + properties.id + "\n" if (properties.type == "Model") { text += "Model URL: " + properties.modelURL + "\n" @@ -2426,6 +2449,7 @@ function Tooltip() { text += "Lifetime: " + properties.lifetime + "\n" } text += "Age: " + properties.ageAsText + "\n" + text += "Script: " + properties.script + "\n" Overlays.editOverlay(this.textOverlay, { text: text }); @@ -2477,7 +2501,9 @@ function mousePressEvent(event) { if (isLocked(properties)) { print("Model locked " + properties.id); } else { - print("Checking properties: " + properties.id + " " + properties.isKnownID); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal); // P P - Model // /| A - Palm // / | d B - unit vector toward tip @@ -2496,8 +2522,9 @@ function mousePressEvent(event) { var x = Vec3.dot(Vec3.subtract(P, A), B); var X = Vec3.sum(A, Vec3.multiply(B, x)); var d = Vec3.length(Vec3.subtract(P, X)); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - var angularSize = 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; if (0 < x && angularSize > MIN_ANGULAR_SIZE) { if (angularSize < MAX_ANGULAR_SIZE) { entitySelected = true; @@ -2506,13 +2533,13 @@ function mousePressEvent(event) { orientation = MyAvatar.orientation; intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); } else { - print("Angular size too big: " + 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14); + print("Angular size too big: " + angularSize); } } } } if (entitySelected) { - selectedEntityProperties.oldRadius = selectedEntityProperties.radius; + selectedEntityProperties.oldDimensions = selectedEntityProperties.dimensions; selectedEntityProperties.oldPosition = { x: selectedEntityProperties.position.x, y: selectedEntityProperties.position.y, @@ -2550,8 +2577,12 @@ function mouseMoveEvent(event) { glowedEntityID.id = -1; glowedEntityID.isKnownID = false; } + + var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; - var angularSize = 2 * Math.atan(entityIntersection.properties.radius / Vec3.distance(Camera.getPosition(), entityIntersection.properties.position)) * 180 / 3.14; + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), + entityIntersection.properties.position)) * 180 / 3.14; + if (entityIntersection.entityID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) { Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); glowedEntityID = entityIntersection.entityID; @@ -2573,7 +2604,7 @@ function mouseMoveEvent(event) { } pickRay = Camera.computePickRay(event.x, event.y); if (wasShifted != event.isShifted || modifier != oldModifier) { - selectedEntityProperties.oldRadius = selectedEntityProperties.radius; + selectedEntityProperties.oldDimensions = selectedEntityProperties.dimensions; selectedEntityProperties.oldPosition = { x: selectedEntityProperties.position.x, @@ -2603,9 +2634,12 @@ function mouseMoveEvent(event) { return; case 1: // Let's Scale - selectedEntityProperties.radius = (selectedEntityProperties.oldRadius * + selectedEntityProperties.dimensions = Vec3.multiply(selectedEntityProperties.dimensions, (1.0 + (mouseLastPosition.y - event.y) / SCALE_FACTOR)); - if (selectedEntityProperties.radius < 0.01) { + + var halfDiagonal = Vec3.length(selectedEntityProperties.dimensions) / 2.0; + + if (halfDiagonal < 0.01) { print("Scale too small ... bailling."); return; } @@ -2753,6 +2787,13 @@ Controller.mouseReleaseEvent.connect(mouseReleaseEvent); setupModelMenus(); +var propertiesForEditedEntity; +var editEntityFormArray; +var editModelID = -1; +var dimensionX; +var dimensionY; +var dimensionZ; + function handeMenuEvent(menuItem) { print("menuItemEvent() in JS... menuItem=" + menuItem); if (menuItem == "Delete") { @@ -2781,7 +2822,7 @@ function handeMenuEvent(menuItem) { print(" Delete Entity.... not holding..."); } } else if (menuItem == "Edit Properties...") { - var editModelID = -1; + editModelID = -1; if (leftController.grabbing) { print(" Edit Properties.... leftController.entityID="+ leftController.entityID); editModelID = leftController.entityID; @@ -2797,79 +2838,111 @@ function handeMenuEvent(menuItem) { if (editModelID != -1) { print(" Edit Properties.... about to edit properties..."); - var properties = Entities.getEntityProperties(editModelID); + propertiesForEditedEntity = Entities.getEntityProperties(editModelID); + var properties = propertiesForEditedEntity; var array = new Array(); + var index = 0; var decimals = 3; if (properties.type == "Model") { array.push({ label: "Model URL:", value: properties.modelURL }); + index++; array.push({ label: "Animation URL:", value: properties.animationURL }); + index++; array.push({ label: "Animation is playing:", value: properties.animationIsPlaying }); + index++; array.push({ label: "Animation FPS:", value: properties.animationFPS }); + index++; array.push({ label: "Animation Frame:", value: properties.animationFrameIndex }); + index++; } + array.push({ label: "Position:", type: "header" }); + index++; array.push({ label: "X:", value: properties.position.x.toFixed(decimals) }); + index++; array.push({ label: "Y:", value: properties.position.y.toFixed(decimals) }); + index++; array.push({ label: "Z:", value: properties.position.z.toFixed(decimals) }); + index++; + + array.push({ label: "Registration X:", value: properties.registrationPoint.x.toFixed(decimals) }); + index++; + array.push({ label: "Registration Y:", value: properties.registrationPoint.y.toFixed(decimals) }); + index++; + array.push({ label: "Registration Z:", value: properties.registrationPoint.z.toFixed(decimals) }); + index++; + + array.push({ label: "Rotation:", type: "header" }); + index++; var angles = Quat.safeEulerAngles(properties.rotation); array.push({ label: "Pitch:", value: angles.x.toFixed(decimals) }); + index++; array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) }); + index++; array.push({ label: "Roll:", value: angles.z.toFixed(decimals) }); - array.push({ label: "Scale:", value: 2 * properties.radius.toFixed(decimals) }); + index++; - array.push({ label: "Velocity X:", value: properties.velocity.x.toFixed(decimals) }); - array.push({ label: "Velocity Y:", value: properties.velocity.y.toFixed(decimals) }); - array.push({ label: "Velocity Z:", value: properties.velocity.z.toFixed(decimals) }); - array.push({ label: "Damping:", value: properties.damping.toFixed(decimals) }); + array.push({ label: "Dimensions:", type: "header" }); + index++; + array.push({ label: "Width:", value: properties.dimensions.x.toFixed(decimals) }); + dimensionX = index; + index++; + array.push({ label: "Height:", value: properties.dimensions.y.toFixed(decimals) }); + dimensionY = index; + index++; + array.push({ label: "Depth:", value: properties.dimensions.z.toFixed(decimals) }); + dimensionZ = index; + index++; + array.push({ label: "", type: "inlineButton", buttonLabel: "Reset to Natural Dimensions", name: "resetDimensions" }); + index++; + + array.push({ label: "Velocity:", type: "header" }); + index++; + array.push({ label: "Linear X:", value: properties.velocity.x.toFixed(decimals) }); + index++; + array.push({ label: "Linear Y:", value: properties.velocity.y.toFixed(decimals) }); + index++; + array.push({ label: "Linear Z:", value: properties.velocity.z.toFixed(decimals) }); + index++; + array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) }); + index++; + array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) }); + index++; + array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) }); + index++; + array.push({ label: "Angular Roll:", value: properties.angularVelocity.z.toFixed(decimals) }); + index++; + array.push({ label: "Angular Damping:", value: properties.angularDamping.toFixed(decimals) }); + index++; array.push({ label: "Gravity X:", value: properties.gravity.x.toFixed(decimals) }); + index++; array.push({ label: "Gravity Y:", value: properties.gravity.y.toFixed(decimals) }); + index++; array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) }); + index++; array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) }); + index++; + + array.push({ label: "Visible:", value: properties.visible }); + index++; if (properties.type == "Box" || properties.type == "Sphere") { + array.push({ label: "Color:", type: "header" }); + index++; array.push({ label: "Red:", value: properties.color.red }); + index++; array.push({ label: "Green:", value: properties.color.green }); + index++; array.push({ label: "Blue:", value: properties.color.blue }); + index++; } array.push({ button: "Cancel" }); - - if (Window.form("Edit Properties", array)) { - var index = 0; - if (properties.type == "Model") { - properties.modelURL = array[index++].value; - properties.animationURL = array[index++].value; - properties.animationIsPlaying = array[index++].value; - properties.animationFPS = array[index++].value; - properties.animationFrameIndex = array[index++].value; - } - properties.position.x = array[index++].value; - properties.position.y = array[index++].value; - properties.position.z = array[index++].value; - angles.x = array[index++].value; - angles.y = array[index++].value; - angles.z = array[index++].value; - properties.rotation = Quat.fromVec3Degrees(angles); - properties.radius = array[index++].value / 2; + index++; - properties.velocity.x = array[index++].value; - properties.velocity.y = array[index++].value; - properties.velocity.z = array[index++].value; - properties.damping = array[index++].value; - properties.gravity.x = array[index++].value; - properties.gravity.y = array[index++].value; - properties.gravity.z = array[index++].value; - properties.lifetime = array[index++].value; // give ourselves that many more seconds - - if (properties.type == "Box" || properties.type == "Sphere") { - properties.color.red = array[index++].value; - properties.color.green = array[index++].value; - properties.color.blue = array[index++].value; - } - Entities.editEntity(editModelID, properties); - } - modelSelected = false; + editEntityFormArray = array; + Window.nonBlockingForm("Edit Properties", array); } } else if (menuItem == "Paste Models") { modelImporter.paste(); @@ -2930,3 +3003,76 @@ Controller.keyReleaseEvent.connect(function (event) { handeMenuEvent("Delete"); } }); + +Window.inlineButtonClicked.connect(function (name) { + if (name == "resetDimensions") { + var decimals = 3; + Window.reloadNonBlockingForm([ + { value: propertiesForEditedEntity.naturalDimensions.x.toFixed(decimals), oldIndex: dimensionX }, + { value: propertiesForEditedEntity.naturalDimensions.y.toFixed(decimals), oldIndex: dimensionY }, + { value: propertiesForEditedEntity.naturalDimensions.z.toFixed(decimals), oldIndex: dimensionZ } + ]); + } +}); +Window.nonBlockingFormClosed.connect(function() { + array = editEntityFormArray; + if (Window.getNonBlockingFormResult(array)) { + var properties = propertiesForEditedEntity; + var index = 0; + if (properties.type == "Model") { + properties.modelURL = array[index++].value; + properties.animationURL = array[index++].value; + properties.animationIsPlaying = array[index++].value; + properties.animationFPS = array[index++].value; + properties.animationFrameIndex = array[index++].value; + } + index++; // skip header + properties.position.x = array[index++].value; + properties.position.y = array[index++].value; + properties.position.z = array[index++].value; + properties.registrationPoint.x = array[index++].value; + properties.registrationPoint.y = array[index++].value; + properties.registrationPoint.z = array[index++].value; + + index++; // skip header + var angles = Quat.safeEulerAngles(properties.rotation); + angles.x = array[index++].value; + angles.y = array[index++].value; + angles.z = array[index++].value; + properties.rotation = Quat.fromVec3Degrees(angles); + + index++; // skip header + properties.dimensions.x = array[index++].value; + properties.dimensions.y = array[index++].value; + properties.dimensions.z = array[index++].value; + index++; // skip reset button + + index++; // skip header + properties.velocity.x = array[index++].value; + properties.velocity.y = array[index++].value; + properties.velocity.z = array[index++].value; + properties.damping = array[index++].value; + + properties.angularVelocity.x = array[index++].value; + properties.angularVelocity.y = array[index++].value; + properties.angularVelocity.z = array[index++].value; + properties.angularDamping = array[index++].value; + + properties.gravity.x = array[index++].value; + properties.gravity.y = array[index++].value; + properties.gravity.z = array[index++].value; + properties.lifetime = array[index++].value; + properties.visible = array[index++].value; + + if (properties.type == "Box" || properties.type == "Sphere") { + index++; // skip header + properties.color.red = array[index++].value; + properties.color.green = array[index++].value; + properties.color.blue = array[index++].value; + } + Entities.editEntity(editModelID, properties); + } + modelSelected = false; +}); + + diff --git a/examples/gracefulControls.js b/examples/gracefulControls.js new file mode 100644 index 0000000000..e2b603c5d7 --- /dev/null +++ b/examples/gracefulControls.js @@ -0,0 +1,216 @@ +// +// gracefulControls.js +// examples +// +// Created by Ryan Huffman on 9/11/14 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var DEFAULT_PARAMETERS = { + // Coefficient to use for linear drag. Higher numbers will cause motion to + // slow down more quickly. + DRAG_COEFFICIENT: 0.9, + MAX_SPEED: 40.0, + ACCELERATION: 1.0, + + MOUSE_YAW_SCALE: -0.125, + MOUSE_PITCH_SCALE: -0.125, + MOUSE_SENSITIVITY: 0.5, + + // Damping frequency, adjust to change mouse look behavior + W: 4.2, +} + +var BRAKE_PARAMETERS = { + DRAG_COEFFICIENT: 4.9, + MAX_SPEED: DEFAULT_PARAMETERS.MAX_SPEED, + ACCELERATION: 0, + + W: 1.0, + MOUSE_YAW_SCALE: -0.125, + MOUSE_PITCH_SCALE: -0.125, + MOUSE_SENSITIVITY: 0.5, +} + +var movementParameters = DEFAULT_PARAMETERS; + +// Movement keys +var KEY_BRAKE = "q"; +var KEY_FORWARD = "w"; +var KEY_BACKWARD = "s"; +var KEY_LEFT = "a"; +var KEY_RIGHT = "d"; +var KEY_UP = "e"; +var KEY_DOWN = "c"; +var KEY_ENABLE = "SPACE"; +var CAPTURED_KEYS = [KEY_BRAKE, KEY_FORWARD, KEY_BACKWARD, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_ENABLE]; + +// Global Variables +var keys = {}; +var velocity = { x: 0, y: 0, z: 0 }; +var velocityVertical = 0; +var enabled = false; + +var lastX = Window.getCursorPositionX(); +var lastY = Window.getCursorPositionY(); +var yawFromMouse = 0; +var pitchFromMouse = 0; + +var yawSpeed = 0; +var pitchSpeed = 0; + +function keyPressEvent(event) { + if (event.text == "ESC") { + disable(); + } else if (event.text == KEY_ENABLE) { + if (Window.hasFocus()) { + enable(); + } + } else if (event.text == KEY_BRAKE) { + movementParameters = BRAKE_PARAMETERS; + } + keys[event.text] = true; +} + +function keyReleaseEvent(event) { + if (event.text == KEY_BRAKE) { + movementParameters = DEFAULT_PARAMETERS; + } + + delete keys[event.text]; +} + +function update(dt) { + var maxMove = 3.0 * dt; + var targetVelocity = { x: 0, y: 0, z: 0 }; + var targetVelocityVertical = 0; + var acceleration = movementParameters.ACCELERATION; + + if (keys[KEY_FORWARD]) { + targetVelocity.z -= acceleration * dt; + } + if (keys[KEY_LEFT]) { + targetVelocity.x -= acceleration * dt; + } + if (keys[KEY_BACKWARD]) { + targetVelocity.z += acceleration * dt; + } + if (keys[KEY_RIGHT]) { + targetVelocity.x += acceleration * dt; + } + if (keys[KEY_UP]) { + targetVelocityVertical += acceleration * dt; + } + if (keys[KEY_DOWN]) { + targetVelocityVertical -= acceleration * dt; + } + + if (enabled && Window.hasFocus()) { + var x = Window.getCursorPositionX(); + var y = Window.getCursorPositionY(); + + yawFromMouse += ((x - lastX) * movementParameters.MOUSE_YAW_SCALE * movementParameters.MOUSE_SENSITIVITY); + pitchFromMouse += ((y - lastY) * movementParameters.MOUSE_PITCH_SCALE * movementParameters.MOUSE_SENSITIVITY); + pitchFromMouse = Math.max(-180, Math.min(180, pitchFromMouse)); + + resetCursorPosition(); + } + + // Here we use a linear damping model - http://en.wikipedia.org/wiki/Damping#Linear_damping + // Because we are using a critically damped model (no oscillation), ΞΆ = 1 and + // so we derive the formula: acceleration = -(2 * w0 * v) - (w0^2 * x) + var W = movementParameters.W; + yawAccel = (W * W * yawFromMouse) - (2 * W * yawSpeed); + pitchAccel = (W * W * pitchFromMouse) - (2 * W * pitchSpeed); + + yawSpeed += yawAccel * dt; + var yawMove = yawSpeed * dt; + var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees( { x: 0, y: yawMove, z: 0 } )); + MyAvatar.orientation = newOrientation; + yawFromMouse -= yawMove; + + pitchSpeed += pitchAccel * dt; + var pitchMove = pitchSpeed * dt; + var newPitch = MyAvatar.headPitch + pitchMove; + MyAvatar.headPitch = newPitch; + pitchFromMouse -= pitchMove; + + // If force isn't being applied in a direction, add drag; + if (targetVelocity.x == 0) { + targetVelocity.x -= (velocity.x * movementParameters.DRAG_COEFFICIENT * dt); + } + if (targetVelocity.z == 0) { + targetVelocity.z -= (velocity.z * movementParameters.DRAG_COEFFICIENT * dt); + } + velocity = Vec3.sum(velocity, targetVelocity); + + var maxSpeed = movementParameters.MAX_SPEED; + velocity.x = Math.max(-maxSpeed, Math.min(maxSpeed, velocity.x)); + velocity.z = Math.max(-maxSpeed, Math.min(maxSpeed, velocity.z)); + var v = Vec3.multiplyQbyV(MyAvatar.headOrientation, velocity); + + if (targetVelocityVertical == 0) { + targetVelocityVertical -= (velocityVertical * movementParameters.DRAG_COEFFICIENT * dt); + } + velocityVertical += targetVelocityVertical; + velocityVertical = Math.max(-maxSpeed, Math.min(maxSpeed, velocityVertical)); + v.y += velocityVertical; + + MyAvatar.setVelocity(v); +} + +function vecToString(vec) { + return vec.x + ", " + vec.y + ", " + vec.z; +} + +function scriptEnding() { + disable(); +} + +function resetCursorPosition() { + var newX = Window.x + Window.innerWidth / 2; + var newY = Window.y + Window.innerHeight / 2; + Window.setCursorPosition(newX, newY); + lastX = newX; + lastY = newY; +} + +function enable() { + if (!enabled) { + enabled = true; + + resetCursorPosition(); + + // Reset movement variables + yawFromMouse = 0; + pitchFromMouse = 0; + yawSpeed = 0; + pitchSpeed = 0; + velocityVertical = 0; + + for (var i = 0; i < CAPTURED_KEYS.length; i++) { + Controller.captureKeyEvents({ text: CAPTURED_KEYS[i] }); + } + Window.setCursorVisible(false); + Script.update.connect(update); + } +} + +function disable() { + if (enabled) { + enabled = false; + for (var i = 0; i < CAPTURED_KEYS.length; i++) { + Controller.releaseKeyEvents({ text: CAPTURED_KEYS[i] }); + } + Window.setCursorVisible(true); + Script.update.disconnect(update); + } +} + +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); + +Script.scriptEnding.connect(scriptEnding); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0a86636437..a7bfe67398 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -51,6 +51,7 @@ #include #include +#include #include #include #include @@ -81,10 +82,10 @@ #include "scripting/AudioDeviceScriptingInterface.h" #include "scripting/ClipboardScriptingInterface.h" #include "scripting/GlobalServicesScriptingInterface.h" +#include "scripting/LocationScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" #include "scripting/WindowScriptingInterface.h" -#include "scripting/LocationScriptingInterface.h" #include "ui/InfoView.h" #include "ui/OAuthWebViewHandler.h" @@ -359,9 +360,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : Particle::setVoxelEditPacketSender(&_voxelEditSender); Particle::setParticleEditPacketSender(&_particleEditSender); - // when -url in command line, teleport to location - urlGoTo(argc, constArgv); - // For now we're going to set the PPS for outbound packets to be super high, this is // probably not the right long term solution. But for now, we're going to do this to // allow you to move a particle around in your hand @@ -406,9 +404,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(_window, &MainWindow::windowGeometryChanged, _runningScriptsWidget, &RunningScriptsWidget::setBoundary); + + AddressManager& addressManager = AddressManager::getInstance(); - //When -url in command line, teleport to location - urlGoTo(argc, constArgv); + // connect to the domainChangeRequired signal on AddressManager + connect(&addressManager, &AddressManager::possibleDomainChangeRequired, + this, &Application::changeDomainHostname); + + // when -url in command line, teleport to location + addressManager.handleLookupString(getCmdOption(argc, constArgv, "-url")); // call the OAuthWebviewHandler static getter so that its instance lives in our thread OAuthWebViewHandler::getInstance(); @@ -808,12 +812,14 @@ bool Application::event(QEvent* event) { // handle custom URL if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast(event); - bool isHifiSchemeURL = !fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME); - if (isHifiSchemeURL) { - Menu::getInstance()->goToURL(fileEvent->url().toLocalFile()); + + if (!fileEvent->url().isEmpty()) { + AddressManager::getInstance().handleLookupString(fileEvent->url().toLocalFile()); } + return false; } + return QApplication::event(event); } @@ -913,7 +919,12 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Return: case Qt::Key_Enter: - Menu::getInstance()->triggerOption(MenuOption::Chat); + if (isMeta) { + Menu::getInstance()->triggerOption(MenuOption::AddressBar); + } else { + Menu::getInstance()->triggerOption(MenuOption::Chat); + } + break; case Qt::Key_Up: @@ -1060,10 +1071,6 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Equal: _myAvatar->resetSize(); break; - - case Qt::Key_At: - Menu::getInstance()->goTo(); - break; default: event->ignore(); break; @@ -1322,7 +1329,7 @@ void Application::dropEvent(QDropEvent *event) { SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath); if (snapshotData) { if (!snapshotData->getDomain().isEmpty()) { - Menu::getInstance()->goToDomain(snapshotData->getDomain()); + changeDomainHostname(snapshotData->getDomain()); } _myAvatar->setPosition(snapshotData->getLocation()); @@ -3378,31 +3385,53 @@ void Application::updateWindowTitle(){ #ifndef WIN32 // crashes with vs2013/win32 qDebug("Application title set to: %s", title.toStdString().c_str()); -#endif //!WIN32 +#endif _window->setWindowTitle(title); } void Application::updateLocationInServer() { AccountManager& accountManager = AccountManager::getInstance(); - - if (accountManager.isLoggedIn()) { + const QUuid& domainUUID = NodeList::getInstance()->getDomainHandler().getUUID(); + + if (accountManager.isLoggedIn() && !domainUUID.isNull()) { // construct a QJsonObject given the user's current address information - QJsonObject updatedLocationObject; + QJsonObject rootObject; - QJsonObject addressObject; - addressObject.insert("position", QString(createByteArray(_myAvatar->getPosition()))); - addressObject.insert("orientation", QString(createByteArray(glm::degrees(safeEulerAngles(_myAvatar->getOrientation()))))); - addressObject.insert("domain", NodeList::getInstance()->getDomainHandler().getHostname()); + QJsonObject locationObject; + + QString pathString = AddressManager::pathForPositionAndOrientation(_myAvatar->getPosition(), + true, + _myAvatar->getOrientation()); + + const QString LOCATION_KEY_IN_ROOT = "location"; + const QString PATH_KEY_IN_LOCATION = "path"; + const QString DOMAIN_ID_KEY_IN_LOCATION = "domain_id"; + + locationObject.insert(PATH_KEY_IN_LOCATION, pathString); + locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION, domainUUID.toString()); - updatedLocationObject.insert("address", addressObject); + rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject); - accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation, - JSONCallbackParameters(), QJsonDocument(updatedLocationObject).toJson()); + accountManager.authenticatedRequest("/api/v1/users/location", QNetworkAccessManager::PutOperation, + JSONCallbackParameters(), QJsonDocument(rootObject).toJson()); } } +void Application::changeDomainHostname(const QString &newDomainHostname) { + NodeList* nodeList = NodeList::getInstance(); + + if (!nodeList->getDomainHandler().isCurrentHostname(newDomainHostname)) { + // tell the MyAvatar object to send a kill packet so that it dissapears from its old avatar mixer immediately + _myAvatar->sendKillAvatar(); + + // call the domain hostname change as a queued connection on the nodelist + QMetaObject::invokeMethod(&NodeList::getInstance()->getDomainHandler(), "setHostname", + Q_ARG(const QString&, newDomainHostname)); + } +} + void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); @@ -3786,12 +3815,11 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, - LocationScriptingInterface::locationSetter, windowValue); - + LocationScriptingInterface::locationSetter, windowValue); // register `location` on the global object. scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, - LocationScriptingInterface::locationSetter); - + LocationScriptingInterface::locationSetter); + scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); @@ -3921,6 +3949,15 @@ void Application::uploadAttachment() { uploadModel(ATTACHMENT_MODEL); } +void Application::openUrl(const QUrl& url) { + if (url.scheme() == HIFI_URL_SCHEME) { + AddressManager::getInstance().handleLookupString(url.toString()); + } else { + // address manager did not handle - ask QDesktopServices to handle + QDesktopServices::openUrl(url); + } +} + void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) { // from the domain-handler, figure out the satoshi cost per voxel and per meter cubed @@ -4091,6 +4128,14 @@ void Application::skipVersion(QString latestVersion) { skipFile.write(latestVersion.toStdString().c_str()); } +void Application::setCursorVisible(bool visible) { + if (visible) { + restoreOverrideCursor(); + } else { + setOverrideCursor(Qt::BlankCursor); + } +} + void Application::takeSnapshot() { QMediaPlayer* player = new QMediaPlayer(); QFileInfo inf = QFileInfo(Application::resourcesPath() + "sounds/snap.wav"); @@ -4109,37 +4154,3 @@ void Application::takeSnapshot() { } _snapshotShareDialog->show(); } - -void Application::urlGoTo(int argc, const char * constArgv[]) { - //Gets the url (hifi://domain/destination/orientation) - QString customUrl = getCmdOption(argc, constArgv, "-url"); - if(customUrl.startsWith(CUSTOM_URL_SCHEME + "//")) { - QStringList urlParts = customUrl.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); - if (urlParts.count() == 1) { - // location coordinates or place name - QString domain = urlParts[0]; - Menu::goToDomain(domain); - } else if (urlParts.count() > 1) { - // if url has 2 or more parts, the first one is domain name - QString domain = urlParts[0]; - - // second part is either a destination coordinate or - // a place name - QString destination = urlParts[1]; - - // any third part is an avatar orientation. - QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); - - Menu::goToDomain(domain); - - // goto either @user, #place, or x-xx,y-yy,z-zz - // style co-ordinate. - Menu::goTo(destination); - - if (!orientation.isEmpty()) { - // location orientation - Menu::goToOrientation(orientation); - } - } - } -} diff --git a/interface/src/Application.h b/interface/src/Application.h index fbcc67cf80..2466e54936 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -114,7 +114,6 @@ static const float NODE_KILLED_GREEN = 0.0f; static const float NODE_KILLED_BLUE = 0.0f; static const QString SNAPSHOT_EXTENSION = ".jpg"; -static const QString CUSTOM_URL_SCHEME = "hifi:"; static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees static const float BILLBOARD_DISTANCE = 5.0f; // meters @@ -153,7 +152,6 @@ public: void initializeGL(); void paintGL(); void resizeGL(int width, int height); - void urlGoTo(int argc, const char * constArgv[]); void keyPressEvent(QKeyEvent* event); void keyReleaseEvent(QKeyEvent* event); @@ -299,6 +297,8 @@ public: QStringList getRunningScripts() { return _scriptEnginesHash.keys(); } ScriptEngine* getScriptEngine(QString scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; } + void setCursorVisible(bool visible); + signals: /// Fired when we're simulating; allows external parties to hook in. @@ -314,6 +314,7 @@ signals: void importDone(); public slots: + void changeDomainHostname(const QString& newDomainHostname); void domainChanged(const QString& domainHostname); void updateWindowTitle(); void updateLocationInServer(); @@ -353,6 +354,8 @@ public slots: void uploadHead(); void uploadSkeleton(); void uploadAttachment(); + + void openUrl(const QUrl& url); void bumpSettings() { ++_numChangedSettings; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ff5e50d66c..c39e9abd3d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -155,21 +156,6 @@ Menu::Menu() : appInstance, SLOT(toggleRunningScriptsWidget())); addDisabledActionAndSeparator(fileMenu, "Go"); - addActionToQMenuAndActionHash(fileMenu, - MenuOption::GoHome, - Qt::CTRL | Qt::Key_G, - appInstance->getAvatar(), - SLOT(goHome())); - addActionToQMenuAndActionHash(fileMenu, - MenuOption::GoToDomain, - Qt::CTRL | Qt::Key_D, - this, - SLOT(goToDomainDialog())); - addActionToQMenuAndActionHash(fileMenu, - MenuOption::GoToLocation, - Qt::CTRL | Qt::SHIFT | Qt::Key_L, - this, - SLOT(goToLocation())); addActionToQMenuAndActionHash(fileMenu, MenuOption::NameLocation, Qt::CTRL | Qt::Key_N, @@ -181,12 +167,10 @@ Menu::Menu() : this, SLOT(toggleLocationList())); addActionToQMenuAndActionHash(fileMenu, - MenuOption::GoTo, - Qt::Key_At, + MenuOption::AddressBar, + Qt::CTRL | Qt::Key_Enter, this, - SLOT(goTo())); - connect(&LocationManager::getInstance(), &LocationManager::multipleDestinationsFound, - this, &Menu::multipleDestinationsDecision); + SLOT(toggleAddressBar())); addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model"); addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadHead, 0, Application::getInstance(), SLOT(uploadHead())); @@ -1155,147 +1139,25 @@ void Menu::changePrivateKey() { sendFakeEnterEvent(); } -void Menu::goToDomain(const QString newDomain) { - if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) { - // send a node kill request, indicating to other clients that they should play the "disappeared" effect - Application::getInstance()->getAvatar()->sendKillAvatar(); +void Menu::toggleAddressBar() { - // give our nodeList the new domain-server hostname - NodeList::getInstance()->getDomainHandler().setHostname(newDomain); + QInputDialog addressBarDialog(Application::getInstance()->getWindow()); + addressBarDialog.setWindowTitle("Address Bar"); + addressBarDialog.setWindowFlags(Qt::Sheet); + addressBarDialog.setLabelText("place, domain, @user, example.com, /position/orientation"); + + addressBarDialog.resize(addressBarDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, + addressBarDialog.size().height()); + + int dialogReturn = addressBarDialog.exec(); + if (dialogReturn == QDialog::Accepted && !addressBarDialog.textValue().isEmpty()) { + // let the AddressManger figure out what to do with this + AddressManager::getInstance().handleLookupString(addressBarDialog.textValue()); } -} - -void Menu::goToDomainDialog() { - - QString currentDomainHostname = NodeList::getInstance()->getDomainHandler().getHostname(); - - if (NodeList::getInstance()->getDomainHandler().getPort() != DEFAULT_DOMAIN_SERVER_PORT) { - // add the port to the currentDomainHostname string if it is custom - currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainHandler().getPort())); - } - - QInputDialog domainDialog(Application::getInstance()->getWindow()); - domainDialog.setWindowTitle("Go to Domain"); - domainDialog.setLabelText("Domain server:"); - domainDialog.setTextValue(currentDomainHostname); - domainDialog.resize(domainDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, domainDialog.size().height()); - - int dialogReturn = domainDialog.exec(); - if (dialogReturn == QDialog::Accepted) { - QString newHostname(DEFAULT_DOMAIN_HOSTNAME); - - if (domainDialog.textValue().size() > 0) { - // the user input a new hostname, use that - newHostname = domainDialog.textValue(); - } - - goToDomain(newHostname); - } - + sendFakeEnterEvent(); } -void Menu::goToOrientation(QString orientation) { - LocationManager::getInstance().goToOrientation(orientation); -} - -bool Menu::goToDestination(QString destination) { - return LocationManager::getInstance().goToDestination(destination); -} - -void Menu::goTo(QString destination) { - LocationManager::getInstance().goTo(destination); -} - -void Menu::goTo() { - - QInputDialog gotoDialog(Application::getInstance()->getWindow()); - gotoDialog.setWindowTitle("Go to"); - gotoDialog.setLabelText("Destination or URL:\n @user, #place, hifi://domain/location/orientation"); - QString destination = QString(); - - gotoDialog.setTextValue(destination); - gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height()); - - int dialogReturn = gotoDialog.exec(); - if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) { - QString desiredDestination = gotoDialog.textValue(); - if (!goToURL(desiredDestination)) {; - goTo(desiredDestination); - } - } - sendFakeEnterEvent(); -} - -bool Menu::goToURL(QString location) { - if (location.startsWith(CUSTOM_URL_SCHEME + "/")) { - QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length()).split('/', QString::SkipEmptyParts); - - if (urlParts.count() > 1) { - // if url has 2 or more parts, the first one is domain name - QString domain = urlParts[0]; - - // second part is either a destination coordinate or - // a place name - QString destination = urlParts[1]; - - // any third part is an avatar orientation. - QString orientation = urlParts.count() > 2 ? urlParts[2] : QString(); - - goToDomain(domain); - - // goto either @user, #place, or x-xx,y-yy,z-zz - // style co-ordinate. - goTo(destination); - - if (!orientation.isEmpty()) { - // location orientation - goToOrientation(orientation); - } - } else if (urlParts.count() == 1) { - QString destination = urlParts[0]; - - // If this starts with # or @, treat it as a user/location, otherwise treat it as a domain - if (destination[0] == '#' || destination[0] == '@') { - goTo(destination); - } else { - goToDomain(destination); - } - } - return true; - } - return false; -} - -void Menu::goToUser(const QString& user) { - LocationManager::getInstance().goTo(user); -} - -/// Open a url, shortcutting any "hifi" scheme URLs to the local application. -void Menu::openUrl(const QUrl& url) { - if (url.scheme() == "hifi") { - goToURL(url.toString()); - } else { - QDesktopServices::openUrl(url); - } -} - -void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJsonObject& placeData) { - QMessageBox msgBox; - msgBox.setText("Both user and location exists with same name"); - msgBox.setInformativeText("Where you wanna go?"); - msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Open); - msgBox.button(QMessageBox::Ok)->setText("User"); - msgBox.button(QMessageBox::Open)->setText("Place"); - int userResponse = msgBox.exec(); - - if (userResponse == QMessageBox::Ok) { - Application::getInstance()->getAvatar()->goToLocationFromAddress(userData["address"].toObject()); - } else if (userResponse == QMessageBox::Open) { - Application::getInstance()->getAvatar()->goToLocationFromAddress(placeData["address"].toObject()); - } -} - void Menu::muteEnvironment() { int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment); int packetSize = headerSize + sizeof(glm::vec3) + sizeof(float); @@ -1320,43 +1182,13 @@ void Menu::muteEnvironment() { free(packet); } -void Menu::goToLocation() { - MyAvatar* myAvatar = Application::getInstance()->getAvatar(); - glm::vec3 avatarPos = myAvatar->getPosition(); - QString currentLocation = QString("%1, %2, %3").arg(QString::number(avatarPos.x), - QString::number(avatarPos.y), QString::number(avatarPos.z)); +void Menu::displayNameLocationResponse(const QString& errorString) { - QInputDialog coordinateDialog(Application::getInstance()->getWindow()); - coordinateDialog.setWindowTitle("Go to Location"); - coordinateDialog.setLabelText("Coordinate as x,y,z:"); - coordinateDialog.setTextValue(currentLocation); - coordinateDialog.resize(coordinateDialog.parentWidget()->size().width() * 0.30, coordinateDialog.size().height()); - - int dialogReturn = coordinateDialog.exec(); - if (dialogReturn == QDialog::Accepted && !coordinateDialog.textValue().isEmpty()) { - goToDestination(coordinateDialog.textValue()); - } - - sendFakeEnterEvent(); -} - -void Menu::namedLocationCreated(LocationManager::NamedLocationCreateResponse response) { - - if (response == LocationManager::Created) { - return; - } - - QMessageBox msgBox; - switch (response) { - case LocationManager::AlreadyExists: - msgBox.setText("That name has been already claimed, try something else."); - break; - default: - msgBox.setText("An unexpected error has occurred, please try again later."); - break; - } - - msgBox.exec(); + if (!errorString.isEmpty()) { + QMessageBox msgBox; + msgBox.setText(errorString); + msgBox.exec(); + } } void Menu::toggleLocationList() { @@ -1374,23 +1206,34 @@ void Menu::nameLocation() { // check if user is logged in or show login dialog if not AccountManager& accountManager = AccountManager::getInstance(); + if (!accountManager.isLoggedIn()) { QMessageBox msgBox; msgBox.setText("We need to tie this location to your username."); msgBox.setInformativeText("Please login first, then try naming the location again."); msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); msgBox.button(QMessageBox::Ok)->setText("Login"); + if (msgBox.exec() == QMessageBox::Ok) { loginForCurrentDomain(); } return; } + + DomainHandler& domainHandler = NodeList::getInstance()->getDomainHandler(); + if (domainHandler.getUUID().isNull()) { + const QString UNREGISTERED_DOMAIN_MESSAGE = "This domain is not registered with High Fidelity." + "\n\nYou cannot create a global location in an unregistered domain."; + QMessageBox::critical(this, "Unregistered Domain", UNREGISTERED_DOMAIN_MESSAGE); + + return; + } QInputDialog nameDialog(Application::getInstance()->getWindow()); nameDialog.setWindowTitle("Name this location"); nameDialog.setLabelText("Name this location, then share that name with others.\n" - "When they come here, they'll be in the same location and orientation\n" + "When they come here, they'll have the same viewpoint\n" "(wherever you are standing and looking now) as you.\n\n" "Location name:"); @@ -1405,10 +1248,10 @@ void Menu::nameLocation() { MyAvatar* myAvatar = Application::getInstance()->getAvatar(); LocationManager* manager = new LocationManager(); - connect(manager, &LocationManager::creationCompleted, this, &Menu::namedLocationCreated); + connect(manager, &LocationManager::creationCompleted, this, &Menu::displayNameLocationResponse); NamedLocation* location = new NamedLocation(locationName, myAvatar->getPosition(), myAvatar->getOrientation(), - NodeList::getInstance()->getDomainHandler().getHostname()); + domainHandler.getUUID()); manager->createNamedLocation(location); } } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7ed963f473..307b011f74 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -164,11 +164,6 @@ public: int menuItemLocation = UNSPECIFIED_POSITION); void removeAction(QMenu* menu, const QString& actionName); - - bool static goToDestination(QString destination); - void static goToOrientation(QString orientation); - void static goToDomain(const QString newDomain); - void static goTo(QString destination); const QByteArray& getWalletPrivateKey() const { return _walletPrivateKey; } @@ -187,11 +182,8 @@ public slots: void saveSettings(QSettings* settings = NULL); void importSettings(); void exportSettings(); - void goTo(); - bool goToURL(QString location); - void goToUser(const QString& user); + void toggleAddressBar(); void pasteToVoxel(); - void openUrl(const QUrl& url); void toggleLoginMenuItem(); @@ -213,8 +205,6 @@ private slots: void editAttachments(); void editAnimations(); void changePrivateKey(); - void goToDomainDialog(); - void goToLocation(); void nameLocation(); void toggleLocationList(); void bandwidthDetailsClosed(); @@ -228,8 +218,7 @@ private slots: void toggleConsole(); void toggleChat(); void audioMuteToggled(); - void namedLocationCreated(LocationManager::NamedLocationCreateResponse response); - void multipleDestinationsDecision(const QJsonObject& userData, const QJsonObject& placeData); + void displayNameLocationResponse(const QString& errorString); void muteEnvironment(); private: @@ -318,6 +307,7 @@ private: namespace MenuOption { const QString AboutApp = "About Interface"; + const QString AddressBar = "Show Address Bar"; const QString AlignForearmsWithWrists = "Align Forearms with Wrists"; const QString AlternateIK = "Alternate IK"; const QString AmbientOcclusion = "Ambient Occlusion"; @@ -403,10 +393,7 @@ namespace MenuOption { const QString FullscreenMirror = "Fullscreen Mirror"; const QString GlowMode = "Cycle Glow Mode"; const QString GlowWhenSpeaking = "Glow When Speaking"; - const QString GoHome = "Go Home"; - const QString GoToDomain = "Go To Domain..."; - const QString GoTo = "Go To..."; - const QString GoToLocation = "Go To Location..."; + const QString GoToUser = "Go To User"; const QString HeadMouse = "Head Mouse"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IncreaseVoxelSize = "Increase Voxel Size"; diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index a8be2f9f5c..91c1770e6f 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -421,11 +421,11 @@ void ModelUploader::uploadSuccess(const QJsonObject& jsonResponse) { checkS3(); } -void ModelUploader::uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString) { +void ModelUploader::uploadFailed(QNetworkReply& errorReply) { if (_progressDialog) { _progressDialog->reject(); } - qDebug() << "Model upload failed (" << errorCode << "): " << errorString; + qDebug() << "Model upload failed (" << errorReply.error() << "): " << errorReply.errorString(); QMessageBox::warning(NULL, QString("ModelUploader::uploadFailed()"), QString("There was a problem with your upload, please try again later."), diff --git a/interface/src/ModelUploader.h b/interface/src/ModelUploader.h index 2596120751..a1c7a27393 100644 --- a/interface/src/ModelUploader.h +++ b/interface/src/ModelUploader.h @@ -43,7 +43,7 @@ private slots: void checkJSON(const QJsonObject& jsonResponse); void uploadUpdate(qint64 bytesSent, qint64 bytesTotal); void uploadSuccess(const QJsonObject& jsonResponse); - void uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString); + void uploadFailed(QNetworkReply& errorReply); void checkS3(); void processCheck(); diff --git a/interface/src/UserLocationsModel.cpp b/interface/src/UserLocationsModel.cpp index e84cae8f95..0fae0d8800 100644 --- a/interface/src/UserLocationsModel.cpp +++ b/interface/src/UserLocationsModel.cpp @@ -18,14 +18,13 @@ #include "Application.h" #include "UserLocationsModel.h" -static const QString PLACES_GET = "/api/v1/places"; -static const QString PLACES_UPDATE = "/api/v1/places/%1"; -static const QString PLACES_DELETE= "/api/v1/places/%1"; +static const QString LOCATIONS_GET = "/api/v1/locations"; +static const QString LOCATION_UPDATE_OR_DELETE = "/api/v1/locations/%1"; -UserLocation::UserLocation(QString id, QString name, QString location) : +UserLocation::UserLocation(const QString& id, const QString& name, const QString& address) : _id(id), _name(name), - _location(location), + _address(address), _previousName(name), _updating(false) { } @@ -35,10 +34,15 @@ void UserLocation::requestRename(const QString& newName) { _updating = true; JSONCallbackParameters callbackParams(this, "handleRenameResponse", this, "handleRenameError"); + QJsonObject jsonNameObject; - jsonNameObject.insert("name", QJsonValue(newName)); + jsonNameObject.insert("name", newName); + + QJsonObject locationObject; + locationObject.insert("location", jsonNameObject); + QJsonDocument jsonDocument(jsonNameObject); - AccountManager::getInstance().authenticatedRequest(PLACES_UPDATE.arg(_id), + AccountManager::getInstance().authenticatedRequest(LOCATION_UPDATE_OR_DELETE.arg(_id), QNetworkAccessManager::PutOperation, callbackParams, jsonDocument.toJson()); @@ -54,7 +58,9 @@ void UserLocation::handleRenameResponse(const QJsonObject& responseData) { QJsonValue status = responseData["status"]; if (!status.isUndefined() && status.toString() == "success") { - QString updatedName = responseData["data"].toObject()["name"].toString(); + qDebug() << responseData; + QString updatedName = responseData["data"].toObject()["location"].toObject()["name"].toString(); + qDebug() << "The updated name is" << updatedName; _name = updatedName; } else { _name = _previousName; @@ -75,10 +81,10 @@ void UserLocation::handleRenameResponse(const QJsonObject& responseData) { emit updated(_name); } -void UserLocation::handleRenameError(QNetworkReply::NetworkError error, const QString& errorString) { +void UserLocation::handleRenameError(QNetworkReply& errorReply) { _updating = false; - QString msg = "There was an error renaming location '" + _name + "': " + errorString; + QString msg = "There was an error renaming location '" + _name + "': " + errorReply.errorString(); qDebug() << msg; QMessageBox::warning(Application::getInstance()->getWindow(), "Error", msg); @@ -90,7 +96,7 @@ void UserLocation::requestDelete() { _updating = true; JSONCallbackParameters callbackParams(this, "handleDeleteResponse", this, "handleDeleteError"); - AccountManager::getInstance().authenticatedRequest(PLACES_DELETE.arg(_id), + AccountManager::getInstance().authenticatedRequest(LOCATION_UPDATE_OR_DELETE.arg(_id), QNetworkAccessManager::DeleteOperation, callbackParams); } @@ -109,10 +115,10 @@ void UserLocation::handleDeleteResponse(const QJsonObject& responseData) { } } -void UserLocation::handleDeleteError(QNetworkReply::NetworkError error, const QString& errorString) { +void UserLocation::handleDeleteError(QNetworkReply& errorReply) { _updating = false; - QString msg = "There was an error deleting location '" + _name + "': " + errorString; + QString msg = "There was an error deleting location '" + _name + "': " + errorReply.errorString(); qDebug() << msg; QMessageBox::warning(Application::getInstance()->getWindow(), "Error", msg); } @@ -153,7 +159,7 @@ void UserLocationsModel::refresh() { endResetModel(); JSONCallbackParameters callbackParams(this, "handleLocationsResponse"); - AccountManager::getInstance().authenticatedRequest(PLACES_GET, + AccountManager::getInstance().authenticatedRequest(LOCATIONS_GET, QNetworkAccessManager::GetOperation, callbackParams); } @@ -165,14 +171,13 @@ void UserLocationsModel::handleLocationsResponse(const QJsonObject& responseData QJsonValue status = responseData["status"]; if (!status.isUndefined() && status.toString() == "success") { beginResetModel(); - QJsonArray locations = responseData["data"].toObject()["places"].toArray(); + QJsonArray locations = responseData["data"].toObject()["locations"].toArray(); for (QJsonArray::const_iterator it = locations.constBegin(); it != locations.constEnd(); it++) { QJsonObject location = (*it).toObject(); - QJsonObject address = location["address"].toObject(); + QString locationAddress = "hifi://" + location["domain"].toObject()["name"].toString() + + location["path"].toString(); UserLocation* userLocation = new UserLocation(location["id"].toString(), location["name"].toString(), - "hifi://" + address["domain"].toString() - + "/" + address["position"].toString() - + "/" + address["orientation"].toString()); + locationAddress); _locations.append(userLocation); connect(userLocation, &UserLocation::deleted, this, &UserLocationsModel::removeLocation); connect(userLocation, &UserLocation::updated, this, &UserLocationsModel::update); @@ -214,8 +219,8 @@ QVariant UserLocationsModel::data(const QModelIndex& index, int role) const { return QVariant(); } else if (index.column() == NameColumn) { return _locations[index.row()]->name(); - } else if (index.column() == LocationColumn) { - return QVariant(_locations[index.row()]->location()); + } else if (index.column() == AddressColumn) { + return QVariant(_locations[index.row()]->address()); } } @@ -226,7 +231,7 @@ QVariant UserLocationsModel::headerData(int section, Qt::Orientation orientation if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case NameColumn: return "Name"; - case LocationColumn: return "Location"; + case AddressColumn: return "Address"; default: return QVariant(); } } diff --git a/interface/src/UserLocationsModel.h b/interface/src/UserLocationsModel.h index d3f86faa5a..54518d72e1 100644 --- a/interface/src/UserLocationsModel.h +++ b/interface/src/UserLocationsModel.h @@ -20,20 +20,20 @@ class UserLocation : public QObject { Q_OBJECT public: - UserLocation(QString id, QString name, QString location); + UserLocation(const QString& id, const QString& name, const QString& address); bool isUpdating() { return _updating; } void requestRename(const QString& newName); void requestDelete(); - QString id() { return _id; } - QString name() { return _name; } - QString location() { return _location; } + const QString& id() { return _id; } + const QString& name() { return _name; } + const QString& address() { return _address; } public slots: void handleRenameResponse(const QJsonObject& responseData); - void handleRenameError(QNetworkReply::NetworkError error, const QString& errorString); + void handleRenameError(QNetworkReply& errorReply); void handleDeleteResponse(const QJsonObject& responseData); - void handleDeleteError(QNetworkReply::NetworkError error, const QString& errorString); + void handleDeleteError(QNetworkReply& errorReply); signals: void updated(const QString& name); @@ -42,7 +42,7 @@ signals: private: QString _id; QString _name; - QString _location; + QString _address; QString _previousName; bool _updating; @@ -65,7 +65,7 @@ public: enum Columns { NameColumn = 0, - LocationColumn + AddressColumn }; public slots: diff --git a/interface/src/avatar/ModelReferential.cpp b/interface/src/avatar/ModelReferential.cpp index eb2a61a819..df6e272da7 100644 --- a/interface/src/avatar/ModelReferential.cpp +++ b/interface/src/avatar/ModelReferential.cpp @@ -33,7 +33,7 @@ ModelReferential::ModelReferential(Referential* referential, EntityTree* tree, A const EntityItem* item = _tree->findEntityByID(_entityID); if (item != NULL) { - _refScale = item->getRadius(); + _refScale = item->getLargestDimension(); _refRotation = item->getRotation(); _refPosition = item->getPosition() * (float)TREE_SCALE; update(); @@ -52,7 +52,7 @@ ModelReferential::ModelReferential(const QUuid& entityID, EntityTree* tree, Avat return; } - _refScale = item->getRadius(); + _refScale = item->getLargestDimension(); _refRotation = item->getRotation(); _refPosition = item->getPosition() * (float)TREE_SCALE; @@ -69,8 +69,8 @@ void ModelReferential::update() { } bool somethingChanged = false; - if (item->getRadius() != _refScale) { - _refScale = item->getRadius(); + if (item->getLargestDimension() != _refScale) { + _refScale = item->getLargestDimension(); _avatar->setTargetScale(_refScale * _scale, true); somethingChanged = true; } @@ -109,7 +109,7 @@ JointReferential::JointReferential(Referential* referential, EntityTree* tree, A const EntityItem* item = _tree->findEntityByID(_entityID); const Model* model = getModel(item); if (!isValid() || model == NULL || _jointIndex >= (uint32_t)(model->getJointStateCount())) { - _refScale = item->getRadius(); + _refScale = item->getLargestDimension(); model->getJointRotationInWorldFrame(_jointIndex, _refRotation); model->getJointPositionInWorldFrame(_jointIndex, _refPosition); } @@ -129,7 +129,7 @@ JointReferential::JointReferential(uint32_t jointIndex, const QUuid& entityID, E return; } - _refScale = item->getRadius(); + _refScale = item->getLargestDimension(); model->getJointRotationInWorldFrame(_jointIndex, _refRotation); model->getJointPositionInWorldFrame(_jointIndex, _refPosition); @@ -147,8 +147,8 @@ void JointReferential::update() { } bool somethingChanged = false; - if (item->getRadius() != _refScale) { - _refScale = item->getRadius(); + if (item->getLargestDimension() != _refScale) { + _refScale = item->getLargestDimension(); _avatar->setTargetScale(_refScale * _scale, true); somethingChanged = true; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 282ee512de..48d58fb02c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -91,6 +92,9 @@ MyAvatar::MyAvatar() : Ragdoll* ragdoll = _skeletonModel.buildRagdoll(); _physicsSimulation.setRagdoll(ragdoll); _physicsSimulation.addEntity(&_voxelShapeManager); + + // connect to AddressManager signal for location jumps + connect(&AddressManager::getInstance(), &AddressManager::locationChangeRequired, this, &MyAvatar::goToLocation); } MyAvatar::~MyAvatar() { @@ -1763,11 +1767,6 @@ void MyAvatar::maybeUpdateBillboard() { sendBillboardPacket(); } -void MyAvatar::goHome() { - qDebug("Going Home!"); - slamPosition(START_LOCATION); -} - void MyAvatar::increaseSize() { if ((1.0f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) { _targetScale *= (1.0f + SCALING_RATIO); @@ -1787,45 +1786,26 @@ void MyAvatar::resetSize() { qDebug("Reseted scale to %f", _targetScale); } -void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { - QJsonObject locationObject = jsonObject["data"].toObject()["address"].toObject(); - bool isOnline = jsonObject["data"].toObject()["online"].toBool(); - if (isOnline ) { - goToLocationFromAddress(locationObject); - } else { - QMessageBox::warning(Application::getInstance()->getWindow(), "", "The user is not online."); +void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, const glm::vec3& newOrientation) { + glm::quat quatOrientation = getOrientation(); + + qDebug().nospace() << "MyAvatar goToLocation - moving to " << newPosition.x << ", " + << newPosition.y << ", " << newPosition.z; + + if (hasOrientation) { + qDebug().nospace() << "MyAvatar goToLocation - new orientation is " + << newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z; + + // orient the user to face the target + glm::quat quatOrientation = glm::quat(glm::radians(newOrientation)) + * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); + setOrientation(quatOrientation); } -} - -void MyAvatar::goToLocationFromAddress(const QJsonObject& locationObject) { - // send a node kill request, indicating to other clients that they should play the "disappeared" effect - sendKillAvatar(); - - QString positionString = locationObject["position"].toString(); - QString orientationString = locationObject["orientation"].toString(); - QString domainHostnameString = locationObject["domain"].toString(); - - qDebug() << "Changing domain to" << domainHostnameString << - ", position to" << positionString << - ", and orientation to" << orientationString; - - QStringList coordinateItems = positionString.split(','); - QStringList orientationItems = orientationString.split(','); - - NodeList::getInstance()->getDomainHandler().setHostname(domainHostnameString); - - // orient the user to face the target - glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), - orientationItems[1].toFloat(), - orientationItems[2].toFloat()))) - * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); - setOrientation(newOrientation); // move the user a couple units away const float DISTANCE_TO_USER = 2.0f; - glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(), - coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; - slamPosition(newPosition); + glm::vec3 shiftedPosition = newPosition - quatOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; + slamPosition(shiftedPosition); emit transformChanged(); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f80689249d..758b1f92bb 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -150,19 +150,19 @@ public: const PlayerPointer getPlayer() const { return _player; } public slots: - void goHome(); void increaseSize(); void decreaseSize(); void resetSize(); - void goToLocationFromResponse(const QJsonObject& jsonObject); - void goToLocationFromAddress(const QJsonObject& jsonObject); + void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::vec3& newOrientation = glm::vec3()); // Set/Get update the thrust that will move the avatar around void addThrust(glm::vec3 newThrust) { _thrust += newThrust; }; glm::vec3 getThrust() { return _thrust; }; void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } + void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } + void updateMotionBehaviorsFromMenu(); glm::vec3 getLeftPalmPosition(); diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 8dd88a79cc..095277d960 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -192,7 +192,71 @@ bool EntityTreeRenderer::shouldRenderEntity(float largestDimension, float distan return (distanceToCamera <= visibleDistanceAtScale); } +void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* args) { + bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE; + bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds); + if (!isShadowMode && displayModelBounds) { + PerformanceTimer perfTimer("renderProxies"); + + AACube maxCube = entity->getMaximumAACube(); + AACube minCube = entity->getMinimumAACube(); + AABox entityBox = entity->getAABox(); + + maxCube.scale((float)TREE_SCALE); + minCube.scale((float)TREE_SCALE); + entityBox.scale((float)TREE_SCALE); + + glm::vec3 maxCenter = maxCube.calcCenter(); + glm::vec3 minCenter = minCube.calcCenter(); + glm::vec3 entityBoxCenter = entityBox.calcCenter(); + glm::vec3 entityBoxScale = entityBox.getScale(); + + // draw the max bounding cube + glColor4f(1.0f, 1.0f, 0.0f, 1.0f); + glPushMatrix(); + glTranslatef(maxCenter.x, maxCenter.y, maxCenter.z); + glutWireCube(maxCube.getScale()); + glPopMatrix(); + + // draw the min bounding cube + glColor4f(0.0f, 1.0f, 0.0f, 1.0f); + glPushMatrix(); + glTranslatef(minCenter.x, minCenter.y, minCenter.z); + glutWireCube(minCube.getScale()); + glPopMatrix(); + + // draw the entityBox bounding box + glColor4f(0.0f, 0.0f, 1.0f, 1.0f); + glPushMatrix(); + glTranslatef(entityBoxCenter.x, entityBoxCenter.y, entityBoxCenter.z); + glScalef(entityBoxScale.x, entityBoxScale.y, entityBoxScale.z); + glutWireCube(1.0f); + glPopMatrix(); + + + glm::vec3 position = entity->getPosition() * (float)TREE_SCALE; + glm::vec3 center = entity->getCenter() * (float)TREE_SCALE; + glm::vec3 dimensions = entity->getDimensions() * (float)TREE_SCALE; + glm::quat rotation = entity->getRotation(); + + glColor4f(1.0f, 0.0f, 1.0f, 1.0f); + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + glScalef(dimensions.x, dimensions.y, dimensions.z); + glutWireCube(1.0f); + glPopMatrix(); + glPopMatrix(); + } +} + void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) { + bool wantDebug = false; + args->_elementsTouched++; // actually render it here... // we need to iterate the actual entityItems of the element @@ -214,26 +278,48 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) for (uint16_t i = 0; i < numberOfEntities; i++) { EntityItem* entityItem = entityItems[i]; - // render entityItem - AACube entityCube = entityItem->getAACube(); - entityCube.scale(TREE_SCALE); - // TODO: some entity types (like lights) might want to be rendered even - // when they are outside of the view frustum... - float distance = distanceToCamera(entityCube.calcCenter(), *args->_viewFrustum); - if (shouldRenderEntity(entityCube.getLargestDimension(), distance) && - args->_viewFrustum->cubeInFrustum(entityCube) != ViewFrustum::OUTSIDE) { + if (entityItem->isVisible()) { + // render entityItem + AABox entityBox = entityItem->getAABox(); - Glower* glower = NULL; - if (entityItem->getGlowLevel() > 0.0f) { - glower = new Glower(entityItem->getGlowLevel()); + entityBox.scale(TREE_SCALE); + + // TODO: some entity types (like lights) might want to be rendered even + // when they are outside of the view frustum... + float distance = distanceToCamera(entityBox.calcCenter(), *args->_viewFrustum); + + if (wantDebug) { + qDebug() << "------- renderElement() ----------"; + qDebug() << " type:" << EntityTypes::getEntityTypeName(entityItem->getType()); + if (entityItem->getType() == EntityTypes::Model) { + ModelEntityItem* modelEntity = static_cast(entityItem); + qDebug() << " url:" << modelEntity->getModelURL(); + } + qDebug() << " entityBox:" << entityBox; + qDebug() << " dimensions:" << entityItem->getDimensionsInMeters() << "in meters"; + qDebug() << " largestDimension:" << entityBox.getLargestDimension() << "in meters"; + qDebug() << " shouldRender:" << shouldRenderEntity(entityBox.getLargestDimension(), distance); + qDebug() << " in frustum:" << (args->_viewFrustum->boxInFrustum(entityBox) != ViewFrustum::OUTSIDE); } - entityItem->render(args); - if (glower) { - delete glower; + + if (shouldRenderEntity(entityBox.getLargestDimension(), distance) && + args->_viewFrustum->boxInFrustum(entityBox) != ViewFrustum::OUTSIDE) { + + + renderProxies(entityItem, args); + + Glower* glower = NULL; + if (entityItem->getGlowLevel() > 0.0f) { + glower = new Glower(entityItem->getGlowLevel()); + } + entityItem->render(args); + if (glower) { + delete glower; + } + } else { + args->_itemsOutOfView++; } - } else { - args->_itemsOutOfView++; } } } diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index f6a5c41a39..9e0368b61b 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -79,6 +79,7 @@ private: float distanceToCamera(const glm::vec3& center, const ViewFrustum& viewFrustum) const; bool shouldRenderEntity(float largestDimension, float distanceToCamera) const; + void renderProxies(const EntityItem* entity, RenderArgs* args); }; diff --git a/interface/src/entities/RenderableBoxEntityItem.cpp b/interface/src/entities/RenderableBoxEntityItem.cpp index c5843bb31a..17cfbdcdaf 100644 --- a/interface/src/entities/RenderableBoxEntityItem.cpp +++ b/interface/src/entities/RenderableBoxEntityItem.cpp @@ -32,11 +32,12 @@ EntityItem* RenderableBoxEntityItem::factory(const EntityItemID& entityID, const void RenderableBoxEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableBoxEntityItem::render"); assert(getType() == EntityTypes::Box); - glm::vec3 position = getPosition() * (float)TREE_SCALE; - float size = getSize() * (float)TREE_SCALE; + glm::vec3 position = getPositionInMeters(); + glm::vec3 center = getCenter() * (float)TREE_SCALE; + glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; + glm::vec3 halfDimensions = dimensions / 2.0f; glm::quat rotation = getRotation(); - const bool useGlutCube = true; if (useGlutCube) { @@ -45,10 +46,14 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { glTranslatef(position.x, position.y, position.z); glm::vec3 axis = glm::axis(rotation); glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); - glutSolidCube(size); + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + glScalef(dimensions.x, dimensions.y, dimensions.z); + glutSolidCube(1.0f); + glPopMatrix(); glPopMatrix(); } else { - static GLfloat vertices[] = { 1, 1, 1, -1, 1, 1, -1,-1, 1, 1,-1, 1, // v0,v1,v2,v3 (front) 1, 1, 1, 1,-1, 1, 1,-1,-1, 1, 1,-1, // v0,v3,v4,v5 (right) 1, 1, 1, 1, 1,-1, -1, 1,-1, -1, 1, 1, // v0,v5,v6,v1 (top) @@ -79,20 +84,19 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { glNormalPointer(GL_FLOAT, 0, normals); glVertexPointer(3, GL_FLOAT, 0, vertices); - //glEnable(GL_BLEND); - glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]); glPushMatrix(); glTranslatef(position.x, position.y, position.z); glm::vec3 axis = glm::axis(rotation); glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); - - // we need to do half the size because the geometry in the VBOs are from -1,-1,-1 to 1,1,1 - float halfSize = size/2.0f; - - glScalef(halfSize, halfSize, halfSize); - glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indices); + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + // we need to do half the size because the geometry in the VBOs are from -1,-1,-1 to 1,1,1 + glScalef(halfDimensions.x, halfDimensions.y, halfDimensions.z); + glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indices); + glPopMatrix(); glPopMatrix(); glDisableClientState(GL_VERTEX_ARRAY); // disable vertex arrays diff --git a/interface/src/entities/RenderableModelEntityItem.cpp b/interface/src/entities/RenderableModelEntityItem.cpp index 03812904b5..4347087287 100644 --- a/interface/src/entities/RenderableModelEntityItem.cpp +++ b/interface/src/entities/RenderableModelEntityItem.cpp @@ -65,8 +65,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) { bool drawAsModel = hasModel(); glm::vec3 position = getPosition() * (float)TREE_SCALE; - float radius = getRadius() * (float)TREE_SCALE; float size = getSize() * (float)TREE_SCALE; + glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; if (drawAsModel) { glPushMatrix(); @@ -98,8 +98,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) { glm::quat rotation = getRotation(); if (needsSimulation() && _model->isActive()) { - _model->setScaleToFit(true, radius * 2.0f); - _model->setSnapModelToCenter(true); + _model->setScaleToFit(true, dimensions); + _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); _model->setRotation(rotation); _model->setTranslation(position); @@ -128,52 +128,6 @@ void RenderableModelEntityItem::render(RenderArgs* args) { glutWireCube(size); glPopMatrix(); } - - bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE; - bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds); - - if (!isShadowMode && displayModelBounds) { - PerformanceTimer perfTimer("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(); - rotatedExtents.rotate(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 { // if we couldn't get a model, then just draw a cube glColor3ub(getColor()[RED_INDEX],getColor()[GREEN_INDEX],getColor()[BLUE_INDEX]); diff --git a/interface/src/entities/RenderableSphereEntityItem.cpp b/interface/src/entities/RenderableSphereEntityItem.cpp index 01f41e83ae..d5286b0ab3 100644 --- a/interface/src/entities/RenderableSphereEntityItem.cpp +++ b/interface/src/entities/RenderableSphereEntityItem.cpp @@ -31,12 +31,24 @@ EntityItem* RenderableSphereEntityItem::factory(const EntityItemID& entityID, co void RenderableSphereEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableSphereEntityItem::render"); assert(getType() == EntityTypes::Sphere); - glm::vec3 position = getPosition() * (float)TREE_SCALE; - float radius = getRadius() * (float)TREE_SCALE; + glm::vec3 position = getPositionInMeters(); + glm::vec3 center = getCenterInMeters(); + glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; + glm::quat rotation = getRotation(); glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]); glPushMatrix(); glTranslatef(position.x, position.y, position.z); - glutSolidSphere(radius, 15, 15); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + + + glPushMatrix(); + glm::vec3 positionToCenter = center - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + + glScalef(dimensions.x, dimensions.y, dimensions.z); + glutSolidSphere(0.5f, 15, 15); + glPopMatrix(); glPopMatrix(); }; diff --git a/interface/src/location/LocationManager.cpp b/interface/src/location/LocationManager.cpp index 67e1b899ee..92e8616478 100644 --- a/interface/src/location/LocationManager.cpp +++ b/interface/src/location/LocationManager.cpp @@ -9,43 +9,33 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include + +#include -#include "Application.h" #include "LocationManager.h" -#include -const QString GET_USER_ADDRESS = "/api/v1/users/%1/address"; -const QString GET_PLACE_ADDRESS = "/api/v1/places/%1"; -const QString GET_ADDRESSES = "/api/v1/addresses/%1"; -const QString POST_PLACE_CREATE = "/api/v1/places/"; - - -LocationManager::LocationManager() { - -}; +const QString POST_LOCATION_CREATE = "/api/v1/locations/"; LocationManager& LocationManager::getInstance() { static LocationManager sharedInstance; return sharedInstance; } +const QString UNKNOWN_ERROR_MESSAGE = "Unknown error creating named location. Please try again!"; + void LocationManager::namedLocationDataReceived(const QJsonObject& data) { if (data.isEmpty()) { return; } if (data.contains("status") && data["status"].toString() == "success") { - emit creationCompleted(LocationManager::Created); + emit creationCompleted(QString()); } else { - emit creationCompleted(LocationManager::AlreadyExists); + emit creationCompleted(UNKNOWN_ERROR_MESSAGE); } } -void LocationManager::errorDataReceived(QNetworkReply::NetworkError error, const QString& message) { - emit creationCompleted(LocationManager::SystemError); -} - void LocationManager::createNamedLocation(NamedLocation* namedLocation) { AccountManager& accountManager = AccountManager::getInstance(); if (accountManager.isLoggedIn()) { @@ -55,185 +45,45 @@ void LocationManager::createNamedLocation(NamedLocation* namedLocation) { callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "errorDataReceived"; - accountManager.authenticatedRequest(POST_PLACE_CREATE, QNetworkAccessManager::PostOperation, + accountManager.authenticatedRequest(POST_LOCATION_CREATE, QNetworkAccessManager::PostOperation, callbackParams, namedLocation->toJsonString().toUtf8()); } } -void LocationManager::goTo(QString destination) { - const QString USER_DESTINATION_TYPE = "user"; - const QString PLACE_DESTINATION_TYPE = "place"; - const QString OTHER_DESTINATION_TYPE = "coordinate_or_username"; +void LocationManager::errorDataReceived(QNetworkReply& errorReply) { - if (destination.startsWith("@")) { - // remove '@' and go to user - QString destinationUser = destination.remove(0, 1); - UserActivityLogger::getInstance().wentTo(USER_DESTINATION_TYPE, destinationUser); - goToUser(destinationUser); - return; - } - - if (destination.startsWith("#")) { - // remove '#' and go to named place - QString destinationPlace = destination.remove(0, 1); - UserActivityLogger::getInstance().wentTo(PLACE_DESTINATION_TYPE, destinationPlace); - goToPlace(destinationPlace); - return; - } - - // go to coordinate destination or to Username - if (!goToDestination(destination)) { - destination = QString(QUrl::toPercentEncoding(destination)); - UserActivityLogger::getInstance().wentTo(OTHER_DESTINATION_TYPE, destination); + if (errorReply.header(QNetworkRequest::ContentTypeHeader).toString().startsWith("application/json")) { + // we have some JSON error data we can parse for our error message + QJsonDocument responseJson = QJsonDocument::fromJson(errorReply.readAll()); - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "goToAddressFromResponse"; - callbackParams.errorCallbackReceiver = this; - callbackParams.errorCallbackMethod = "handleAddressLookupError"; + QJsonObject dataObject = responseJson.object()["data"].toObject(); - AccountManager::getInstance().authenticatedRequest(GET_ADDRESSES.arg(destination), - QNetworkAccessManager::GetOperation, - callbackParams); - } -} - -void LocationManager::goToAddressFromResponse(const QJsonObject& responseData) { - QJsonValue status = responseData["status"]; - - const QJsonObject& data = responseData["data"].toObject(); - const QJsonValue& userObject = data["user"]; - const QJsonValue& placeObject = data["place"]; - - if (!placeObject.isUndefined() && !userObject.isUndefined()) { - emit multipleDestinationsFound(userObject.toObject(), placeObject.toObject()); - } else if (placeObject.isUndefined()) { - Application::getInstance()->getAvatar()->goToLocationFromAddress(userObject.toObject()["address"].toObject()); - } else { - Application::getInstance()->getAvatar()->goToLocationFromAddress(placeObject.toObject()["address"].toObject()); - } -} - -void LocationManager::goToUser(QString userName) { - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar(); - callbackParams.jsonCallbackMethod = "goToLocationFromResponse"; - callbackParams.errorCallbackReceiver = this; - callbackParams.errorCallbackMethod = "handleAddressLookupError"; - - userName = QString(QUrl::toPercentEncoding(userName)); - AccountManager::getInstance().authenticatedRequest(GET_USER_ADDRESS.arg(userName), - QNetworkAccessManager::GetOperation, - callbackParams); -} - -void LocationManager::goToPlace(QString placeName) { - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar(); - callbackParams.jsonCallbackMethod = "goToLocationFromResponse"; - callbackParams.errorCallbackReceiver = this; - callbackParams.errorCallbackMethod = "handleAddressLookupError"; - - placeName = QString(QUrl::toPercentEncoding(placeName)); - AccountManager::getInstance().authenticatedRequest(GET_PLACE_ADDRESS.arg(placeName), - QNetworkAccessManager::GetOperation, - callbackParams); -} - -void LocationManager::goToOrientation(QString orientation) { - if (orientation.isEmpty()) { - return; - } - - QStringList orientationItems = orientation.remove(' ').split(QRegExp("_|,"), QString::SkipEmptyParts); - - const int NUMBER_OF_ORIENTATION_ITEMS = 4; - const int W_ITEM = 0; - const int X_ITEM = 1; - const int Y_ITEM = 2; - const int Z_ITEM = 3; - - if (orientationItems.size() == NUMBER_OF_ORIENTATION_ITEMS) { - - // replace last occurrence of '_' with decimal point - replaceLastOccurrence('-', '.', orientationItems[W_ITEM]); - replaceLastOccurrence('-', '.', orientationItems[X_ITEM]); - replaceLastOccurrence('-', '.', orientationItems[Y_ITEM]); - replaceLastOccurrence('-', '.', orientationItems[Z_ITEM]); - - double w = orientationItems[W_ITEM].toDouble(); - double x = orientationItems[X_ITEM].toDouble(); - double y = orientationItems[Y_ITEM].toDouble(); - double z = orientationItems[Z_ITEM].toDouble(); - - glm::quat newAvatarOrientation(w, x, y, z); - - MyAvatar* myAvatar = Application::getInstance()->getAvatar(); - glm::quat avatarOrientation = myAvatar->getOrientation(); - if (newAvatarOrientation != avatarOrientation) { - myAvatar->setOrientation(newAvatarOrientation); - emit myAvatar->transformChanged(); + qDebug() << dataObject; + + QString errorString = "There was a problem creating that location.\n"; + + // construct the error string from the returned attribute errors + foreach(const QString& key, dataObject.keys()) { + errorString += "\n\u2022 " + key + " - "; + + QJsonValue keyedErrorValue = dataObject[key]; + + if (keyedErrorValue.isArray()) { + foreach(const QJsonValue& attributeErrorValue, keyedErrorValue.toArray()) { + errorString += attributeErrorValue.toString() + ", "; + } + + // remove the trailing comma at end of error list + errorString.remove(errorString.length() - 2, 2); + } else if (keyedErrorValue.isString()) { + errorString += keyedErrorValue.toString(); + } } - } -} - -bool LocationManager::goToDestination(QString destination) { - - QStringList coordinateItems = destination.remove(' ').split(QRegExp("_|,"), QString::SkipEmptyParts); - - const int NUMBER_OF_COORDINATE_ITEMS = 3; - const int X_ITEM = 0; - const int Y_ITEM = 1; - const int Z_ITEM = 2; - if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) { - - // replace last occurrence of '_' with decimal point - replaceLastOccurrence('-', '.', coordinateItems[X_ITEM]); - replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM]); - replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM]); - - double x = coordinateItems[X_ITEM].toDouble(); - double y = coordinateItems[Y_ITEM].toDouble(); - double z = coordinateItems[Z_ITEM].toDouble(); - - glm::vec3 newAvatarPos(x, y, z); - - MyAvatar* myAvatar = Application::getInstance()->getAvatar(); - glm::vec3 avatarPos = myAvatar->getPosition(); - if (newAvatarPos != avatarPos) { - // send a node kill request, indicating to other clients that they should play the "disappeared" effect - MyAvatar::sendKillAvatar(); - - qDebug("Going To Location: %f, %f, %f...", x, y, z); - myAvatar->slamPosition(newAvatarPos); - emit myAvatar->transformChanged(); - } - - return true; - } - - // no coordinates were parsed - return false; -} - -void LocationManager::handleAddressLookupError(QNetworkReply::NetworkError networkError, - const QString& errorString) { - QString messageBoxString; - - if (networkError == QNetworkReply::ContentNotFoundError) { - messageBoxString = "That address could not be found."; + + // emit our creationCompleted signal with the error + emit creationCompleted(errorString); + } else { - messageBoxString = errorString; - } - - QMessageBox::warning(Application::getInstance()->getWindow(), "", messageBoxString); -} - -void LocationManager::replaceLastOccurrence(const QChar search, const QChar replace, QString& string) { - int lastIndex; - lastIndex = string.lastIndexOf(search); - if (lastIndex > 0) { - lastIndex = string.lastIndexOf(search); - string.replace(lastIndex, 1, replace); + creationCompleted(UNKNOWN_ERROR_MESSAGE); } } diff --git a/interface/src/location/LocationManager.h b/interface/src/location/LocationManager.h index 30b4447ded..b6a662e323 100644 --- a/interface/src/location/LocationManager.h +++ b/interface/src/location/LocationManager.h @@ -13,8 +13,8 @@ #define hifi_LocationManager_h #include +#include -#include "AccountManager.h" #include "NamedLocation.h" class LocationManager : public QObject { @@ -29,29 +29,14 @@ public: SystemError }; - LocationManager(); void createNamedLocation(NamedLocation* namedLocation); - void goTo(QString destination); - void goToUser(QString userName); - void goToPlace(QString placeName); - void goToOrientation(QString orientation); - bool goToDestination(QString destination); - -public slots: - void handleAddressLookupError(QNetworkReply::NetworkError networkError, const QString& errorString); - -private: - void replaceLastOccurrence(const QChar search, const QChar replace, QString& string); - signals: - void creationCompleted(LocationManager::NamedLocationCreateResponse response); - void multipleDestinationsFound(const QJsonObject& userData, const QJsonObject& placeData); + void creationCompleted(const QString& errorMessage); private slots: void namedLocationDataReceived(const QJsonObject& data); - void errorDataReceived(QNetworkReply::NetworkError error, const QString& message); - void goToAddressFromResponse(const QJsonObject& jsonObject); + void errorDataReceived(QNetworkReply& errorReply); }; diff --git a/interface/src/location/NamedLocation.cpp b/interface/src/location/NamedLocation.cpp index ed7701a391..7785edfea1 100644 --- a/interface/src/location/NamedLocation.cpp +++ b/interface/src/location/NamedLocation.cpp @@ -9,19 +9,25 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +#include + #include "NamedLocation.h" -const QString JSON_FORMAT = "{\"address\":{\"position\":\"%1,%2,%3\"," - "\"orientation\":\"%4,%5,%6,%7\",\"domain\":\"%8\"},\"name\":\"%9\"}"; +NamedLocation::NamedLocation(const QString& name, + const glm::vec3& position, const glm::quat& orientation, + const QUuid& domainID) : + _name(name), + _position(position), + _orientation(orientation), + _domainID(domainID) +{ + +} + +const QString JSON_FORMAT = "{\"location\":{\"path\":\"%1\",\"domain_id\":\"%2\",\"name\":\"%3\"}}"; QString NamedLocation::toJsonString() { - return JSON_FORMAT.arg(QString::number(_location.x), - QString::number(_location.y), - QString::number(_location.z), - QString::number(_orientation.w), - QString::number(_orientation.x), - QString::number(_orientation.y), - QString::number(_orientation.z), - _domain, - _locationName); + return JSON_FORMAT.arg(AddressManager::pathForPositionAndOrientation(_position, true, _orientation), + uuidStringWithoutCurlyBraces(_domainID), _name); } diff --git a/interface/src/location/NamedLocation.h b/interface/src/location/NamedLocation.h index ffbd157263..fca6852062 100644 --- a/interface/src/location/NamedLocation.h +++ b/interface/src/location/NamedLocation.h @@ -22,39 +22,33 @@ class NamedLocation : public QObject { Q_OBJECT public: - NamedLocation(QString locationName, glm::vec3 location, glm::quat orientation, QString domain) { - _locationName = locationName; - _location = location; - _orientation = orientation; - _domain = domain; - } + NamedLocation(const QString& name, const glm::vec3& position, const glm::quat& orientation, const QUuid& domainID); QString toJsonString(); - bool isEmpty() { return _locationName.isNull() || _locationName.isEmpty(); } + bool isEmpty() { return _name.isNull() || _name.isEmpty(); } - void setLocationName(QString locationName) { _locationName = locationName; } - QString locationName() { return _locationName; } + void setName(QString name) { _name = name; } + const QString& getName() const { return _name; } - void setLocation(glm::vec3 location) { _location = location; } - glm::vec3 location() { return _location; } + void setLocation(glm::vec3 position) { _position = position; } + const glm::vec3& getPosition() const { return _position; } - void setOrientation(glm::quat orentation) { _orientation = orentation; } - glm::quat orientation() { return _orientation; } + void setOrientation(const glm::quat& orentation) { _orientation = orentation; } + const glm::quat& getOrientation() const { return _orientation; } - void setDomain(QString domain) { _domain = domain; } - QString domain() { return _domain; } + void setDomainID(const QUuid& domainID) { _domainID = domainID; } + const QUuid& getDomainID() const { return _domainID; } signals: void dataReceived(bool locationExists); private: - - QString _locationName; + QString _name; QString _createdBy; - glm::vec3 _location; + glm::vec3 _position; glm::quat _orientation; - QString _domain; + QUuid _domainID; }; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index d49eefa4bd..280f5bd21a 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -54,10 +54,10 @@ Model::Model(QObject* parent) : QObject(parent), _scale(1.0f, 1.0f, 1.0f), _scaleToFit(false), - _scaleToFitLargestDimension(0.0f), + _scaleToFitDimensions(0.0f), _scaledToFit(false), - _snapModelToCenter(false), - _snappedToCenter(false), + _snapModelToRegistrationPoint(false), + _snappedToRegistrationPoint(false), _showTrueJointTransforms(true), _lodDistance(0.0f), _pupilDilation(0.0f), @@ -157,8 +157,8 @@ void Model::setOffset(const glm::vec3& offset) { _offset = offset; // if someone manually sets our offset, then we are no longer snapped to center - _snapModelToCenter = false; - _snappedToCenter = false; + _snapModelToRegistrationPoint = false; + _snappedToRegistrationPoint = false; } void Model::initProgram(ProgramObject& program, Model::Locations& locations, @@ -882,50 +882,57 @@ void Blender::run() { Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); } -void Model::setScaleToFit(bool scaleToFit, float largestDimension) { - if (_scaleToFit != scaleToFit || _scaleToFitLargestDimension != largestDimension) { +void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) { + if (_scaleToFit != scaleToFit || _scaleToFitDimensions != dimensions) { _scaleToFit = scaleToFit; - _scaleToFitLargestDimension = largestDimension; + _scaleToFitDimensions = dimensions; _scaledToFit = false; // force rescaling } } +void Model::setScaleToFit(bool scaleToFit, float largestDimension) { + setScaleToFit(scaleToFit, glm::vec3(largestDimension, largestDimension, largestDimension)); +} + void Model::scaleToFit() { Extents modelMeshExtents = getUnscaledMeshExtents(); // 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... - float maxDimension = glm::distance(modelMeshExtents.maximum, modelMeshExtents.minimum); - float maxScale = _scaleToFitLargestDimension / maxDimension; - glm::vec3 scale(maxScale, maxScale, maxScale); - setScaleInternal(scale); + glm::vec3 meshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum; + glm::vec3 rescaleDimensions = _scaleToFitDimensions / meshDimensions; + setScaleInternal(rescaleDimensions); _scaledToFit = true; } -void Model::setSnapModelToCenter(bool snapModelToCenter) { - if (_snapModelToCenter != snapModelToCenter) { - _snapModelToCenter = snapModelToCenter; - _snappedToCenter = false; // force re-centering +void Model::setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint) { + glm::vec3 clampedRegistrationPoint = glm::clamp(registrationPoint, 0.0f, 1.0f); + if (_snapModelToRegistrationPoint != snapModelToRegistrationPoint || _registrationPoint != clampedRegistrationPoint) { + _snapModelToRegistrationPoint = snapModelToRegistrationPoint; + _registrationPoint = clampedRegistrationPoint; + _snappedToRegistrationPoint = false; // force re-centering } } -void Model::snapToCenter() { +void Model::snapToRegistrationPoint() { Extents modelMeshExtents = getUnscaledMeshExtents(); - glm::vec3 halfDimensions = (modelMeshExtents.maximum - modelMeshExtents.minimum) * 0.5f; - glm::vec3 offset = -modelMeshExtents.minimum - halfDimensions; + glm::vec3 dimensions = (modelMeshExtents.maximum - modelMeshExtents.minimum); + glm::vec3 offset = -modelMeshExtents.minimum - (dimensions * _registrationPoint); _offset = offset; - _snappedToCenter = true; + _snappedToRegistrationPoint = true; } void Model::simulate(float deltaTime, bool fullUpdate) { - fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit) || (_snapModelToCenter && !_snappedToCenter); + fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit) + || (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint); + if (isActive() && fullUpdate) { // check for scale to fit if (_scaleToFit && !_scaledToFit) { scaleToFit(); } - if (_snapModelToCenter && !_snappedToCenter) { - snapToCenter(); + if (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint) { + snapToRegistrationPoint(); } simulateInternal(deltaTime); } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 66baaac90d..6c27970d8e 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -52,10 +52,18 @@ public: void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f); bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit - bool getScaleToFitDimension() const { return _scaleToFitLargestDimension; } /// the dimension model is scaled to + const glm::vec3& getScaleToFitDimensions() const { return _scaleToFitDimensions; } /// the dimensions model is scaled to + void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions); - void setSnapModelToCenter(bool snapModelToCenter); - bool getSnapModelToCenter() { return _snapModelToCenter; } + void setSnapModelToCenter(bool snapModelToCenter) { + setSnapModelToRegistrationPoint(snapModelToCenter, glm::vec3(0.5f,0.5f,0.5f)); + }; + bool getSnapModelToCenter() { + return _snapModelToRegistrationPoint && _registrationPoint == glm::vec3(0.5f,0.5f,0.5f); + } + + void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint); + bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } void setScale(const glm::vec3& scale); const glm::vec3& getScale() const { return _scale; } @@ -181,11 +189,13 @@ protected: glm::vec3 _offset; bool _scaleToFit; /// If you set scaleToFit, we will calculate scale based on MeshExtents - float _scaleToFitLargestDimension; /// this is the dimension that scale to fit will use + glm::vec3 _scaleToFitDimensions; /// this is the dimensions that scale to fit will use bool _scaledToFit; /// have we scaled to fit - bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space - bool _snappedToCenter; /// are we currently snapped to center + bool _snapModelToRegistrationPoint; /// is the model's offset automatically adjusted to a registration point in model space + bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point + glm::vec3 _registrationPoint; /// the point in model space our center is snapped to + bool _showTrueJointTransforms; QVector _localLights; @@ -206,7 +216,7 @@ protected: void setScaleInternal(const glm::vec3& scale); void scaleToFit(); - void snapToCenter(); + void snapToRegistrationPoint(); void simulateInternal(float deltaTime); diff --git a/interface/src/scripting/LocationScriptingInterface.cpp b/interface/src/scripting/LocationScriptingInterface.cpp index 9e68778942..bead12117d 100644 --- a/interface/src/scripting/LocationScriptingInterface.cpp +++ b/interface/src/scripting/LocationScriptingInterface.cpp @@ -29,10 +29,9 @@ QString LocationScriptingInterface::getHref() { } QString LocationScriptingInterface::getPathname() { - const glm::vec3& position = Application::getInstance()->getAvatar()->getPosition(); - QString path; - path.sprintf("/%.4f,%.4f,%.4f", position.x, position.y, position.z); - return path; + MyAvatar* applicationAvatar = Application::getInstance()->getAvatar(); + return AddressManager::pathForPositionAndOrientation(applicationAvatar->getPosition(), + true, applicationAvatar->getOrientation()); } QString LocationScriptingInterface::getHostname() { @@ -40,7 +39,7 @@ QString LocationScriptingInterface::getHostname() { } void LocationScriptingInterface::assign(const QString& url) { - QMetaObject::invokeMethod(Menu::getInstance(), "goToURL", Q_ARG(const QString&, url)); + QMetaObject::invokeMethod(&AddressManager::getInstance(), "handleLookupString", Q_ARG(const QString&, url)); } QScriptValue LocationScriptingInterface::locationGetter(QScriptContext* context, QScriptEngine* engine) { diff --git a/interface/src/scripting/LocationScriptingInterface.h b/interface/src/scripting/LocationScriptingInterface.h index 20f63bceed..3d725776fd 100644 --- a/interface/src/scripting/LocationScriptingInterface.h +++ b/interface/src/scripting/LocationScriptingInterface.h @@ -18,6 +18,8 @@ #include #include +#include + #include "Application.h" class LocationScriptingInterface : public QObject { @@ -33,7 +35,7 @@ public: bool isConnected(); QString getHref(); - QString getProtocol() { return CUSTOM_URL_SCHEME; }; + QString getProtocol() { return HIFI_URL_SCHEME; }; QString getPathname(); QString getHostname(); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 6b339c49fd..c909d90fb7 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -34,6 +34,26 @@ WindowScriptingInterface::WindowScriptingInterface() : { } +QScriptValue WindowScriptingInterface::hasFocus() { + return Application::getInstance()->getGLWidget()->hasFocus(); +} + +void WindowScriptingInterface::setCursorVisible(bool visible) { + Application::getInstance()->setCursorVisible(visible); +} + +void WindowScriptingInterface::setCursorPosition(int x, int y) { + QCursor::setPos(x, y); +} + +QScriptValue WindowScriptingInterface::getCursorPositionX() { + return QCursor::pos().x(); +} + +QScriptValue WindowScriptingInterface::getCursorPositionY() { + return QCursor::pos().y(); +} + QScriptValue WindowScriptingInterface::alert(const QString& message) { QScriptValue retVal; QMetaObject::invokeMethod(this, "showAlert", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message)); @@ -516,3 +536,11 @@ int WindowScriptingInterface::getInnerWidth() { int WindowScriptingInterface::getInnerHeight() { return Application::getInstance()->getWindow()->geometry().height(); } + +int WindowScriptingInterface::getX() { + return Application::getInstance()->getWindow()->x(); +} + +int WindowScriptingInterface::getY() { + return Application::getInstance()->getWindow()->y(); +} diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 3324cd2ab6..c3a553d24a 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -20,12 +20,21 @@ class WindowScriptingInterface : public QObject { Q_OBJECT Q_PROPERTY(int innerWidth READ getInnerWidth) Q_PROPERTY(int innerHeight READ getInnerHeight) + Q_PROPERTY(int x READ getX) + Q_PROPERTY(int y READ getY) public: static WindowScriptingInterface* getInstance(); int getInnerWidth(); int getInnerHeight(); + int getX(); + int getY(); public slots: + QScriptValue getCursorPositionX(); + QScriptValue getCursorPositionY(); + void setCursorPosition(int x, int y); + void setCursorVisible(bool visible); + QScriptValue hasFocus(); QScriptValue alert(const QString& message = ""); QScriptValue confirm(const QString& message = ""); QScriptValue form(const QString& title, QScriptValue array); diff --git a/interface/src/ui/ChatMessageArea.cpp b/interface/src/ui/ChatMessageArea.cpp index 1e16a8a2db..1dc38e9c94 100644 --- a/interface/src/ui/ChatMessageArea.cpp +++ b/interface/src/ui/ChatMessageArea.cpp @@ -19,8 +19,8 @@ ChatMessageArea::ChatMessageArea(bool useFixedHeight) : QTextBrowser(), _useFixe connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, this, &ChatMessageArea::updateLayout); - connect(this, &QTextBrowser::anchorClicked, - Menu::getInstance(), &Menu::openUrl); + + connect(this, &QTextBrowser::anchorClicked, Application::getInstance(), &Application::openUrl); } void ChatMessageArea::setHtml(const QString& html) { diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index b9b5ed8e19..5add09a9a6 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -17,6 +17,9 @@ #include #include +#include +#include + #include "Application.h" #include "ChatMessageArea.h" #include "FlowLayout.h" @@ -28,7 +31,6 @@ #include "ChatWindow.h" - const int NUM_MESSAGES_TO_TIME_STAMP = 20; const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?)|(?:hifi))://\\S+)"); @@ -169,7 +171,7 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) { } else if (event->type() == QEvent::MouseButtonRelease) { QVariant userVar = sender->property("user"); if (userVar.isValid()) { - Menu::getInstance()->goToUser("@" + userVar.toString()); + AddressManager::getInstance().goToUser(userVar.toString()); return true; } } diff --git a/interface/src/ui/OAuthWebViewHandler.cpp b/interface/src/ui/OAuthWebViewHandler.cpp index 8ec415584d..a1dbfe9e0b 100644 --- a/interface/src/ui/OAuthWebViewHandler.cpp +++ b/interface/src/ui/OAuthWebViewHandler.cpp @@ -11,6 +11,8 @@ #include +#include + #include "Application.h" #include "OAuthWebViewHandler.h" diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index f99c9d5ac4..1473e4a6a0 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -23,6 +23,8 @@ #include #include +#include + #include "Application.h" #include "ScriptHighlighting.h" diff --git a/interface/src/ui/UserLocationsDialog.cpp b/interface/src/ui/UserLocationsDialog.cpp index f72e66ce77..31f388d045 100644 --- a/interface/src/ui/UserLocationsDialog.cpp +++ b/interface/src/ui/UserLocationsDialog.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include "Menu.h" #include "UserLocationsDialog.h" @@ -51,8 +53,8 @@ void UserLocationsDialog::updateEnabled() { } void UserLocationsDialog::goToModelIndex(const QModelIndex& index) { - QVariant location = _proxyModel.data(index.sibling(index.row(), UserLocationsModel::LocationColumn)); - Menu::getInstance()->goToURL(location.toString()); + QVariant address = _proxyModel.data(index.sibling(index.row(), UserLocationsModel::AddressColumn)); + AddressManager::getInstance().handleLookupString(address.toString()); } void UserLocationsDialog::deleteSelection() { diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 7d85d54fef..f29fa6ed8d 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -9,7 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "../../Application.h" +#include + +#include "Application.h" #include "BillboardOverlay.h" diff --git a/libraries/entities/src/AddEntityOperator.cpp b/libraries/entities/src/AddEntityOperator.cpp index 5a7be69dbf..34ccc75030 100644 --- a/libraries/entities/src/AddEntityOperator.cpp +++ b/libraries/entities/src/AddEntityOperator.cpp @@ -25,8 +25,7 @@ AddEntityOperator::AddEntityOperator(EntityTree* tree, { // caller must have verified existence of newEntity assert(_newEntity); - - _newEntityBox = _newEntity->getAACube().clamp(0.0f, 1.0f); + _newEntityBox = _newEntity->getMaximumAACube().clamp(0.0f, 1.0f); } bool AddEntityOperator::preRecursion(OctreeElement* element) { diff --git a/libraries/entities/src/BoxEntityItem.cpp b/libraries/entities/src/BoxEntityItem.cpp index c683a71562..83d6d7eff9 100644 --- a/libraries/entities/src/BoxEntityItem.cpp +++ b/libraries/entities/src/BoxEntityItem.cpp @@ -20,13 +20,15 @@ EntityItem* BoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return new BoxEntityItem(entityID, properties); + EntityItem* result = new BoxEntityItem(entityID, properties); + return result; } BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : - EntityItem(entityItemID, properties) -{ + EntityItem(entityItemID) +{ _type = EntityTypes::Box; + _created = properties.getCreated(); setProperties(properties, true); } @@ -44,18 +46,9 @@ EntityItemProperties BoxEntityItem::getProperties() const { bool BoxEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) { bool somethingChanged = false; - somethingChanged = EntityItem::setProperties(properties, forceCopy); // set the properties in our base class - if (properties._colorChanged || forceCopy) { - setColor(properties._color); - somethingChanged = true; - } - - if (properties._glowLevelChanged || forceCopy) { - setGlowLevel(properties._glowLevel); - somethingChanged = true; - } + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); if (somethingChanged) { bool wantDebug = false; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index a472e7ec39..1406239f23 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -27,14 +27,21 @@ const float EntityItem::IMMORTAL = -1.0f; /// special lifetime which means the e const float EntityItem::DEFAULT_GLOW_LEVEL = 0.0f; const float EntityItem::DEFAULT_MASS = 1.0f; const float EntityItem::DEFAULT_LIFETIME = EntityItem::IMMORTAL; -const float EntityItem::DEFAULT_DAMPING = 0.99f; +const float EntityItem::DEFAULT_DAMPING = 0.5f; const glm::vec3 EntityItem::NO_VELOCITY = glm::vec3(0, 0, 0); -const float EntityItem::EPSILON_VELOCITY_LENGTH = (1.0f / 10000.0f) / (float)TREE_SCALE; // really small +const float EntityItem::EPSILON_VELOCITY_LENGTH = (1.0f / 1000.0f) / (float)TREE_SCALE; // really small: 1mm/second const glm::vec3 EntityItem::DEFAULT_VELOCITY = EntityItem::NO_VELOCITY; const glm::vec3 EntityItem::NO_GRAVITY = glm::vec3(0, 0, 0); const glm::vec3 EntityItem::REGULAR_GRAVITY = glm::vec3(0, (-9.8f / TREE_SCALE), 0); const glm::vec3 EntityItem::DEFAULT_GRAVITY = EntityItem::NO_GRAVITY; const QString EntityItem::DEFAULT_SCRIPT = QString(""); +const glm::quat EntityItem::DEFAULT_ROTATION; +const glm::vec3 EntityItem::DEFAULT_DIMENSIONS = glm::vec3(0.1f, 0.1f, 0.1f); +const glm::vec3 EntityItem::DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f, 0.5f, 0.5f); // center +const glm::vec3 EntityItem::NO_ANGULAR_VELOCITY = glm::vec3(0.0f, 0.0f, 0.0f); +const glm::vec3 EntityItem::DEFAULT_ANGULAR_VELOCITY = NO_ANGULAR_VELOCITY; +const float EntityItem::DEFAULT_ANGULAR_DAMPING = 0.5f; +const bool EntityItem::DEFAULT_VISIBLE = true; void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { _id = entityItemID.id; @@ -50,8 +57,8 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { _created = 0; // TODO: when do we actually want to make this "now" _position = glm::vec3(0,0,0); - _radius = 0; - _rotation = ENTITY_DEFAULT_ROTATION; + _rotation = DEFAULT_ROTATION; + _dimensions = DEFAULT_DIMENSIONS; _glowLevel = DEFAULT_GLOW_LEVEL; _mass = DEFAULT_MASS; @@ -59,6 +66,20 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { _gravity = DEFAULT_GRAVITY; _damping = DEFAULT_DAMPING; _lifetime = DEFAULT_LIFETIME; + _registrationPoint = DEFAULT_REGISTRATION_POINT; + _angularVelocity = DEFAULT_ANGULAR_VELOCITY; + _angularDamping = DEFAULT_ANGULAR_DAMPING; + _visible = DEFAULT_VISIBLE; +} + +EntityItem::EntityItem(const EntityItemID& entityItemID) { + _type = EntityTypes::Unknown; + _lastEdited = 0; + _lastEditedFromRemote = 0; + _lastEditedFromRemoteInRemoteTime = 0; + _lastUpdated = 0; + _created = 0; + initFromEntityItemID(entityItemID); } EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) { @@ -76,7 +97,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param EntityPropertyFlags requestedProperties; requestedProperties += PROP_POSITION; - requestedProperties += PROP_RADIUS; + requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete requestedProperties += PROP_ROTATION; requestedProperties += PROP_MASS; requestedProperties += PROP_VELOCITY; @@ -84,6 +105,10 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_DAMPING; requestedProperties += PROP_LIFETIME; requestedProperties += PROP_SCRIPT; + requestedProperties += PROP_REGISTRATION_POINT; + requestedProperties += PROP_ANGULAR_VELOCITY; + requestedProperties += PROP_ANGULAR_DAMPING; + requestedProperties += PROP_VISIBLE; return requestedProperties; } @@ -178,10 +203,14 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet // These items would go here once supported.... // PROP_PAGED_PROPERTY, // PROP_CUSTOM_PROPERTIES_INCLUDED, - // PROP_VISIBLE, APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, getPosition()); - APPEND_ENTITY_PROPERTY(PROP_RADIUS, appendValue, getRadius()); + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, appendValue, getDimensions()); // NOTE: PROP_RADIUS obsolete + + if (wantDebug) { + qDebug() << " APPEND_ENTITY_PROPERTY() PROP_DIMENSIONS:" << getDimensions(); + } + APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, getRotation()); APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, getMass()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, getVelocity()); @@ -189,6 +218,10 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, getDamping()); APPEND_ENTITY_PROPERTY(PROP_LIFETIME, appendValue, getLifetime()); APPEND_ENTITY_PROPERTY(PROP_SCRIPT, appendValue, getScript()); + APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, appendValue, getRegistrationPoint()); + APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, appendValue, getAngularVelocity()); + APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, getAngularDamping()); + APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, getVisible()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -408,9 +441,36 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef EntityPropertyFlags propertyFlags = encodedPropertyFlags; dataAt += propertyFlags.getEncodedLength(); bytesRead += propertyFlags.getEncodedLength(); - + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, _position); - READ_ENTITY_PROPERTY(PROP_RADIUS, float, _radius); + + // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS + if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { + if (propertyFlags.getHasProperty(PROP_RADIUS)) { + float fromBuffer; + memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); + dataAt += sizeof(fromBuffer); + bytesRead += sizeof(fromBuffer); + if (overwriteLocalData) { + setRadius(fromBuffer); + } + + if (wantDebug) { + qDebug() << " readEntityDataFromBuffer() OLD FORMAT... found PROP_RADIUS"; + } + + } + } else { + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, _dimensions); + if (wantDebug) { + qDebug() << " readEntityDataFromBuffer() NEW FORMAT... look for PROP_DIMENSIONS"; + } + } + + if (wantDebug) { + qDebug() << " readEntityDataFromBuffer() _dimensions:" << getDimensionsInMeters() << " in meters"; + } + READ_ENTITY_PROPERTY_QUAT(PROP_ROTATION, _rotation); READ_ENTITY_PROPERTY(PROP_MASS, float, _mass); READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, _velocity); @@ -418,6 +478,15 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping); READ_ENTITY_PROPERTY(PROP_LIFETIME, float, _lifetime); READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript); + READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, _registrationPoint); + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, _angularVelocity); + READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping); + READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, _visible); + + if (wantDebug) { + qDebug() << " readEntityDataFromBuffer() _registrationPoint:" << _registrationPoint; + qDebug() << " readEntityDataFromBuffer() _visible:" << _visible; + } bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); @@ -429,7 +498,7 @@ void EntityItem::debugDump() const { qDebug() << "EntityItem id:" << getEntityItemID(); qDebug(" edited ago:%f", getEditedAgo()); qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z); - qDebug(" radius:%f", getRadius()); + qDebug() << " dimensions:" << _dimensions; } // adjust any internal timestamps to fix clock skew for this server @@ -453,9 +522,14 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s } } +// TODO: we probably want to change this to make "down" be the direction of the entity's gravity vector +// for now, this is always true DOWN even if entity has non-down gravity. +// TODO: the old code had "&& _velocity.y >= -EPSILON && _velocity.y <= EPSILON" --- what was I thinking? bool EntityItem::isRestingOnSurface() const { - return _position.y <= _radius - && _velocity.y >= -EPSILON && _velocity.y <= EPSILON + glm::vec3 downwardVelocity = glm::vec3(0.0f, _velocity.y, 0.0f); + + return _position.y <= getDistanceToBottomOfEntity() + && (glm::length(downwardVelocity) <= EPSILON_VELOCITY_LENGTH) && _gravity.y < 0.0f; } @@ -466,9 +540,39 @@ void EntityItem::update(const quint64& updateTime) { if (wantDebug) { qDebug() << "********** EntityItem::update()"; + qDebug() << " entity ID=" << getEntityItemID(); qDebug() << " updateTime=" << updateTime; qDebug() << " _lastUpdated=" << _lastUpdated; qDebug() << " timeElapsed=" << timeElapsed; + qDebug() << " hasVelocity=" << hasVelocity(); + qDebug() << " hasGravity=" << hasGravity(); + qDebug() << " isRestingOnSurface=" << isRestingOnSurface(); + qDebug() << " hasAngularVelocity=" << hasAngularVelocity(); + qDebug() << " getAngularVelocity=" << getAngularVelocity(); + qDebug() << " isMortal=" << isMortal(); + qDebug() << " getAge()=" << getAge(); + qDebug() << " getLifetime()=" << getLifetime(); + + + if (hasVelocity() || (hasGravity() && !isRestingOnSurface())) { + qDebug() << " MOVING...="; + qDebug() << " hasVelocity=" << hasVelocity(); + qDebug() << " hasGravity=" << hasGravity(); + qDebug() << " isRestingOnSurface=" << isRestingOnSurface(); + qDebug() << " hasAngularVelocity=" << hasAngularVelocity(); + qDebug() << " getAngularVelocity=" << getAngularVelocity(); + } + if (hasAngularVelocity()) { + qDebug() << " CHANGING...="; + qDebug() << " hasAngularVelocity=" << hasAngularVelocity(); + qDebug() << " getAngularVelocity=" << getAngularVelocity(); + } + if (isMortal()) { + qDebug() << " MORTAL...="; + qDebug() << " isMortal=" << isMortal(); + qDebug() << " getAge()=" << getAge(); + qDebug() << " getLifetime()=" << getLifetime(); + } } _lastUpdated = updateTime; @@ -477,22 +581,54 @@ void EntityItem::update(const quint64& updateTime) { qDebug() << "********** EntityItem::update() .... SETTING _lastUpdated=" << _lastUpdated; } + if (hasAngularVelocity()) { + glm::quat rotation = getRotation(); + glm::vec3 angularVelocity = glm::radians(getAngularVelocity()); + float angularSpeed = glm::length(angularVelocity); + + if (angularSpeed < EPSILON_VELOCITY_LENGTH) { + setAngularVelocity(NO_ANGULAR_VELOCITY); + } else { + float angle = timeElapsed * angularSpeed; + glm::quat dQ = glm::angleAxis(angle, glm::normalize(angularVelocity)); + rotation = dQ * rotation; + setRotation(rotation); + + // handle damping for angular velocity + if (getAngularDamping() > 0.0f) { + glm::vec3 dampingResistance = getAngularVelocity() * getAngularDamping(); + glm::vec3 newAngularVelocity = getAngularVelocity() - (dampingResistance * timeElapsed); + setAngularVelocity(newAngularVelocity); + if (wantDebug) { + qDebug() << " getDamping():" << getDamping(); + qDebug() << " dampingResistance:" << dampingResistance; + qDebug() << " newAngularVelocity:" << newAngularVelocity; + } + } + } + } + if (hasVelocity() || hasGravity()) { glm::vec3 position = getPosition(); glm::vec3 velocity = getVelocity(); + glm::vec3 newPosition = position + (velocity * timeElapsed); if (wantDebug) { qDebug() << "EntityItem::update()...."; qDebug() << " timeElapsed:" << timeElapsed; - qDebug() << " old AACube:" << getAACube(); + qDebug() << " old AACube:" << getMaximumAACube(); qDebug() << " old position:" << position; qDebug() << " old velocity:" << velocity; + qDebug() << " old getAABox:" << getAABox(); + qDebug() << " getDistanceToBottomOfEntity():" << getDistanceToBottomOfEntity() * (float)TREE_SCALE << " in meters"; + qDebug() << " newPosition:" << newPosition; + qDebug() << " glm::distance(newPosition, position):" << glm::distance(newPosition, position); } - position += velocity * timeElapsed; + position = newPosition; - // handle bounces off the ground... We bounce at the height of our radius... - if (position.y <= _radius) { + // handle bounces off the ground... We bounce at the distance to the bottom of our entity + if (position.y <= getDistanceToBottomOfEntity()) { velocity = velocity * glm::vec3(1,-1,1); // if we've slowed considerably, then just stop moving @@ -500,7 +636,7 @@ void EntityItem::update(const quint64& updateTime) { velocity = NO_VELOCITY; } - position.y = _radius; + position.y = getDistanceToBottomOfEntity(); } // handle gravity.... @@ -512,10 +648,10 @@ void EntityItem::update(const quint64& updateTime) { // "ground" plane of the domain, but for now it if (hasGravity() && isRestingOnSurface()) { velocity.y = 0.0f; - position.y = _radius; + position.y = getDistanceToBottomOfEntity(); } - // handle damping + // handle damping for velocity glm::vec3 dampingResistance = velocity * getDamping(); if (wantDebug) { qDebug() << " getDamping():" << getDamping(); @@ -526,21 +662,23 @@ void EntityItem::update(const quint64& updateTime) { if (wantDebug) { qDebug() << " velocity AFTER dampingResistance:" << velocity; + qDebug() << " glm::length(velocity):" << glm::length(velocity); + qDebug() << " EPSILON_VELOCITY_LENGTH:" << EPSILON_VELOCITY_LENGTH; } // round velocity to zero if it's close enough... if (glm::length(velocity) <= EPSILON_VELOCITY_LENGTH) { velocity = NO_VELOCITY; } + + setPosition(position); + setVelocity(velocity); if (wantDebug) { qDebug() << " new position:" << position; qDebug() << " new velocity:" << velocity; - } - setPosition(position); - setVelocity(velocity); - if (wantDebug) { - qDebug() << " new AACube:" << getAACube(); + qDebug() << " new AACube:" << getMaximumAACube(); + qDebug() << " old getAABox:" << getAABox(); } } } @@ -549,6 +687,9 @@ EntityItem::SimulationState EntityItem::getSimulationState() const { if (hasVelocity() || (hasGravity() && !isRestingOnSurface())) { return EntityItem::Moving; } + if (hasAngularVelocity()) { + return EntityItem::Changing; + } if (isMortal()) { return EntityItem::Mortal; } @@ -566,33 +707,26 @@ void EntityItem::copyChangedProperties(const EntityItem& other) { EntityItemProperties EntityItem::getProperties() const { EntityItemProperties properties; - properties._id = getID(); properties._idSet = true; properties._created = _created; properties._type = getType(); - properties._position = getPosition() * (float) TREE_SCALE; - properties._radius = getRadius() * (float) TREE_SCALE; - properties._rotation = getRotation(); - - properties._mass = getMass(); - properties._velocity = getVelocity() * (float) TREE_SCALE; - properties._gravity = getGravity() * (float) TREE_SCALE; - properties._damping = getDamping(); - properties._lifetime = getLifetime(); - properties._script = getScript(); - - properties._positionChanged = false; - properties._radiusChanged = false; - properties._rotationChanged = false; - properties._massChanged = false; - properties._velocityChanged = false; - properties._gravityChanged = false; - properties._dampingChanged = false; - properties._lifetimeChanged = false; - properties._scriptChanged = false; + COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPositionInMeters); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensionsInMeters); // NOTE: radius is obsolete + COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getRotation); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(mass, getMass); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getVelocityInMeters); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravityInMeters); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifetime, getLifetime); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(registrationPoint, getRegistrationPoint); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularVelocity, getAngularVelocity); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible); properties._defaultSettings = false; @@ -610,55 +744,21 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc _created = properties.getCreated(); } } - - if (properties._positionChanged || forceCopy) { - // clamp positions to the domain to prevent someone from moving an entity out of the domain - setPosition(glm::clamp(properties._position / (float) TREE_SCALE, 0.0f, 1.0f)); - somethingChanged = true; - } - if (properties._radiusChanged || forceCopy) { - setRadius(properties._radius / (float) TREE_SCALE); - somethingChanged = true; - } - - if (properties._rotationChanged || forceCopy) { - setRotation(properties._rotation); - somethingChanged = true; - } - - if (properties._massChanged || forceCopy) { - setMass(properties._mass); - somethingChanged = true; - } - - if (properties._velocityChanged || forceCopy) { - setVelocity(properties._velocity / (float) TREE_SCALE); - somethingChanged = true; - } - - if (properties._massChanged || forceCopy) { - setMass(properties._mass); - somethingChanged = true; - } - if (properties._gravityChanged || forceCopy) { - setGravity(properties._gravity / (float) TREE_SCALE); - somethingChanged = true; - } - - if (properties._dampingChanged || forceCopy) { - setDamping(properties._damping); - somethingChanged = true; - } - if (properties._lifetimeChanged || forceCopy) { - setLifetime(properties._lifetime); - somethingChanged = true; - } - - if (properties._scriptChanged || forceCopy) { - setScript(properties._script); - somethingChanged = true; - } + SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPositionInMeters); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setDimensionsInMeters); // NOTE: radius is obsolete + SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(mass, setMass); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, setVelocityInMeters); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, setGravityInMeters); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, setDamping); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, setLifetime); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, setAngularVelocity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); if (somethingChanged) { somethingChangedNotification(); // notify derived classes that something has changed @@ -675,3 +775,127 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc return somethingChanged; } + +// TODO: is this really correct? how do we use size, does it need to handle rotation? +float EntityItem::getSize() const { + return glm::length(_dimensions); +} + +float EntityItem::getDistanceToBottomOfEntity() const { + glm::vec3 minimumPoint = getAABox().getMinimumPoint(); + return getPosition().y - minimumPoint.y; +} + +// TODO: doesn't this need to handle rotation? +glm::vec3 EntityItem::getCenter() const { + return _position + (_dimensions * (glm::vec3(0.5f,0.5f,0.5f) - _registrationPoint)); +} + +/// The maximum bounding cube for the entity, independent of it's rotation. +/// This accounts for the registration point (upon which rotation occurs around). +/// +AACube EntityItem::getMaximumAACube() const { + // * we know that the position is the center of rotation + glm::vec3 centerOfRotation = _position; // also where _registration point is + + // * we know that the registration point is the center of rotation + // * we can calculate the length of the furthest extent from the registration point + // as the dimensions * max (registrationPoint, (1.0,1.0,1.0) - registrationPoint) + glm::vec3 registrationPoint = (_dimensions * _registrationPoint); + glm::vec3 registrationRemainder = (_dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint)); + glm::vec3 furthestExtentFromRegistration = glm::max(registrationPoint, registrationRemainder); + + // * we know that if you rotate in any direction you would create a sphere + // that has a radius of the length of furthest extent from registration point + float radius = glm::length(furthestExtentFromRegistration); + + // * we know that the minimum bounding cube of this maximum possible sphere is + // (center - radius) to (center + radius) + glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius); + + AACube boundingCube(minimumCorner, radius * 2.0f); + return boundingCube; +} + +/// The minimum bounding cube for the entity accounting for it's rotation. +/// This accounts for the registration point (upon which rotation occurs around). +/// +AACube EntityItem::getMinimumAACube() const { + // _position represents the position of the registration point. + glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; + + glm::vec3 unrotatedMinRelativeToEntity = glm::vec3(0.0f, 0.0f, 0.0f) - (_dimensions * _registrationPoint); + glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder; + Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; + Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); + + // shift the extents to be relative to the position/registration point + rotatedExtentsRelativeToRegistrationPoint.shiftBy(_position); + + // the cube that best encompasses extents is... + AABox box(rotatedExtentsRelativeToRegistrationPoint); + glm::vec3 centerOfBox = box.calcCenter(); + float longestSide = box.getLargestDimension(); + float halfLongestSide = longestSide / 2.0f; + glm::vec3 cornerOfCube = centerOfBox - glm::vec3(halfLongestSide, halfLongestSide, halfLongestSide); + + + // old implementation... not correct!!! + return AACube(cornerOfCube, longestSide); +} + +AABox EntityItem::getAABox() const { + + // _position represents the position of the registration point. + glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; + + glm::vec3 unrotatedMinRelativeToEntity = glm::vec3(0.0f, 0.0f, 0.0f) - (_dimensions * _registrationPoint); + glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder; + Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; + Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); + + // shift the extents to be relative to the position/registration point + rotatedExtentsRelativeToRegistrationPoint.shiftBy(_position); + + return AABox(rotatedExtentsRelativeToRegistrationPoint); +} + + +// NOTE: This should only be used in cases of old bitstreams which only contain radius data +// 0,0,0 --> maxDimension,maxDimension,maxDimension +// ... has a corner to corner distance of glm::length(maxDimension,maxDimension,maxDimension) +// ... radius = cornerToCornerLength / 2.0f +// ... radius * 2.0f = cornerToCornerLength +// ... cornerToCornerLength = sqrt(3 x maxDimension ^ 2) +// ... cornerToCornerLength = sqrt(3 x maxDimension ^ 2) +// ... radius * 2.0f = sqrt(3 x maxDimension ^ 2) +// ... (radius * 2.0f) ^2 = 3 x maxDimension ^ 2 +// ... ((radius * 2.0f) ^2) / 3 = maxDimension ^ 2 +// ... sqrt(((radius * 2.0f) ^2) / 3) = maxDimension +// ... sqrt((diameter ^2) / 3) = maxDimension +// +void EntityItem::setRadius(float value) { + float diameter = value * 2.0f; + float maxDimension = sqrt((diameter * diameter) / 3.0f); + _dimensions = glm::vec3(maxDimension, maxDimension, maxDimension); + + bool wantDebug = false; + if (wantDebug) { + qDebug() << "EntityItem::setRadius()..."; + qDebug() << " radius:" << value; + qDebug() << " diameter:" << diameter; + qDebug() << " maxDimension:" << maxDimension; + qDebug() << " _dimensions:" << _dimensions; + } +} + +// TODO: get rid of all users of this function... +// ... radius = cornerToCornerLength / 2.0f +// ... cornerToCornerLength = sqrt(3 x maxDimension ^ 2) +// ... radius = sqrt(3 x maxDimension ^ 2) / 2.0f; +float EntityItem::getRadius() const { + float length = glm::length(_dimensions); + float radius = length / 2.0f; + return radius; +} + diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 2d6af48f15..a41d4523f9 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -25,6 +25,7 @@ #include "EntityItemProperties.h" #include "EntityTypes.h" +class EntityTreeElement; class EntityTreeElementExtraEncodeData; #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; @@ -39,6 +40,7 @@ class EntityItem { public: DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly + EntityItem(const EntityItemID& entityItemID); EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); virtual ~EntityItem() { } @@ -119,11 +121,27 @@ public: // attributes applicable to all entity types EntityTypes::EntityType getType() const { return _type; } const glm::vec3& getPosition() const { return _position; } /// get position in domain scale units (0.0 - 1.0) + glm::vec3 getPositionInMeters() const { return _position * (float) TREE_SCALE; } /// get position in meters void setPosition(const glm::vec3& value) { _position = value; } /// set position in domain scale units (0.0 - 1.0) + void setPositionInMeters(const glm::vec3& value) /// set position in meter units (0.0 - TREE_SCALE) + { setPosition(glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f)); } - float getRadius() const { return _radius; } /// get radius in domain scale units (0.0 - 1.0) - void setRadius(float value) { _radius = value; } /// set radius in domain scale units (0.0 - 1.0) + glm::vec3 getCenter() const; /// calculates center of the entity in domain scale units (0.0 - 1.0) + glm::vec3 getCenterInMeters() const { return getCenter() * (float) TREE_SCALE; } + static const glm::vec3 DEFAULT_DIMENSIONS; + const glm::vec3& getDimensions() const { return _dimensions; } /// get dimensions in domain scale units (0.0 - 1.0) + glm::vec3 getDimensionsInMeters() const { return _dimensions * (float) TREE_SCALE; } /// get dimensions in meters + float getDistanceToBottomOfEntity() const; /// get the distance from the position of the entity to its "bottom" in y axis + float getLargestDimension() const { return glm::length(_dimensions); } /// get the largest possible dimension + + /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately + void setDimensions(const glm::vec3& value) { _dimensions = value; } + + /// set dimensions in meter units (0.0 - TREE_SCALE) this will also reset radius appropriately + void setDimensionsInMeters(const glm::vec3& value) { setDimensions(value / (float) TREE_SCALE); } + + static const glm::quat DEFAULT_ROTATION; const glm::quat& getRotation() const { return _rotation; } void setRotation(const glm::quat& rotation) { _rotation = rotation; } @@ -138,15 +156,19 @@ public: static const glm::vec3 DEFAULT_VELOCITY; static const glm::vec3 NO_VELOCITY; static const float EPSILON_VELOCITY_LENGTH; - const glm::vec3& getVelocity() const { return _velocity; } /// velocity in domain scale units (0.0-1.0) per second + const glm::vec3 getVelocity() const { return _velocity; } /// velocity in domain scale units (0.0-1.0) per second + glm::vec3 getVelocityInMeters() const { return _velocity * (float) TREE_SCALE; } /// get velocity in meters void setVelocity(const glm::vec3& value) { _velocity = value; } /// velocity in domain scale units (0.0-1.0) per second + void setVelocityInMeters(const glm::vec3& value) { _velocity = value / (float) TREE_SCALE; } /// velocity in meters bool hasVelocity() const { return _velocity != NO_VELOCITY; } static const glm::vec3 DEFAULT_GRAVITY; static const glm::vec3 REGULAR_GRAVITY; static const glm::vec3 NO_GRAVITY; const glm::vec3& getGravity() const { return _gravity; } /// gravity in domain scale units (0.0-1.0) per second squared + glm::vec3 getGravityInMeters() const { return _gravity * (float) TREE_SCALE; } /// get gravity in meters void setGravity(const glm::vec3& value) { _gravity = value; } /// gravity in domain scale units (0.0-1.0) per second squared + void setGravityInMeters(const glm::vec3& value) { _gravity = value / (float) TREE_SCALE; } /// gravity in meters bool hasGravity() const { return _gravity != NO_GRAVITY; } // TODO: this should eventually be updated to support resting on collisions with other surfaces @@ -173,14 +195,38 @@ public: bool lifetimeHasExpired() const; // position, size, and bounds related helpers - float getSize() const { return _radius * 2.0f; } /// get maximum dimension in domain scale units (0.0 - 1.0) - glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); } - glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); } - AACube getAACube() const { return AACube(getMinimumPoint(), getSize()); } /// AACube in domain scale units (0.0 - 1.0) + float getSize() const; /// get maximum dimension in domain scale units (0.0 - 1.0) + AACube getMaximumAACube() const; + AACube getMinimumAACube() const; + AABox getAABox() const; /// axis aligned bounding box in domain scale units (0.0 - 1.0) static const QString DEFAULT_SCRIPT; const QString& getScript() const { return _script; } void setScript(const QString& value) { _script = value; } + + static const glm::vec3 DEFAULT_REGISTRATION_POINT; + const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } /// registration point as ratio of entity + void setRegistrationPoint(const glm::vec3& value) { _registrationPoint = glm::clamp(value, 0.0f, 1.0f); } /// registration point as ratio of entity + + static const glm::vec3 NO_ANGULAR_VELOCITY; + static const glm::vec3 DEFAULT_ANGULAR_VELOCITY; + const glm::vec3& getAngularVelocity() const { return _angularVelocity; } + void setAngularVelocity(const glm::vec3& value) { _angularVelocity = value; } + bool hasAngularVelocity() const { return _angularVelocity != NO_ANGULAR_VELOCITY; } + + static const float DEFAULT_ANGULAR_DAMPING; + float getAngularDamping() const { return _angularDamping; } + void setAngularDamping(float value) { _angularDamping = value; } + + static const bool DEFAULT_VISIBLE; + bool getVisible() const { return _visible; } + void setVisible(bool value) { _visible = value; } + bool isVisible() const { return _visible; } + bool isInvisible() const { return !_visible; } + + // TODO: We need to get rid of these users of getRadius()... + float getRadius() const; + protected: virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init @@ -196,7 +242,7 @@ protected: quint64 _created; glm::vec3 _position; - float _radius; + glm::vec3 _dimensions; glm::quat _rotation; float _glowLevel; float _mass; @@ -205,6 +251,17 @@ protected: float _damping; float _lifetime; QString _script; + glm::vec3 _registrationPoint; + glm::vec3 _angularVelocity; + float _angularDamping; + bool _visible; + + // NOTE: Radius support is obsolete, but these private helper functions are available for this class to + // parse old data streams + + /// set radius in domain scale units (0.0 - 1.0) this will also reset dimensions to be equal for each axis + void setRadius(float value); + }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 65babb6e16..2ef307517f 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -18,6 +18,7 @@ #include "EntityItem.h" #include "EntityItemProperties.h" +#include "ModelEntityItem.h" EntityItemProperties::EntityItemProperties() : @@ -28,17 +29,21 @@ EntityItemProperties::EntityItemProperties() : _type(EntityTypes::Unknown), _position(0), - _radius(ENTITY_DEFAULT_RADIUS), - _rotation(ENTITY_DEFAULT_ROTATION), + _dimensions(EntityItem::DEFAULT_DIMENSIONS), + _rotation(EntityItem::DEFAULT_ROTATION), _mass(EntityItem::DEFAULT_MASS), _velocity(EntityItem::DEFAULT_VELOCITY), _gravity(EntityItem::DEFAULT_GRAVITY), _damping(EntityItem::DEFAULT_DAMPING), _lifetime(EntityItem::DEFAULT_LIFETIME), _script(EntityItem::DEFAULT_SCRIPT), + _registrationPoint(EntityItem::DEFAULT_REGISTRATION_POINT), + _angularVelocity(EntityItem::DEFAULT_ANGULAR_VELOCITY), + _angularDamping(EntityItem::DEFAULT_ANGULAR_DAMPING), + _visible(EntityItem::DEFAULT_VISIBLE), _positionChanged(false), - _radiusChanged(false), + _dimensionsChanged(false), _rotationChanged(false), _massChanged(false), _velocityChanged(false), @@ -46,15 +51,20 @@ EntityItemProperties::EntityItemProperties() : _dampingChanged(false), _lifetimeChanged(false), _scriptChanged(false), + _registrationPointChanged(false), + _angularVelocityChanged(false), + _angularDampingChanged(false), + _visibleChanged(false), _color(), _modelURL(""), _animationURL(""), - _animationIsPlaying(false), - _animationFrameIndex(0.0), - _animationFPS(ENTITY_DEFAULT_ANIMATION_FPS), + _animationIsPlaying(ModelEntityItem::DEFAULT_ANIMATION_IS_PLAYING), + _animationFrameIndex(ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX), + _animationFPS(ModelEntityItem::DEFAULT_ANIMATION_FPS), _glowLevel(0.0f), + _naturalDimensions(1.0f, 1.0f, 1.0f), _colorChanged(false), _modelURLChanged(false), _animationURLChanged(false), @@ -73,7 +83,7 @@ void EntityItemProperties::debugDump() const { qDebug() << " _id=" << _id; qDebug() << " _idSet=" << _idSet; qDebug() << " _position=" << _position.x << "," << _position.y << "," << _position.z; - qDebug() << " _radius=" << _radius; + qDebug() << " _dimensions=" << getDimensions(); qDebug() << " _modelURL=" << _modelURL; qDebug() << " changed properties..."; EntityPropertyFlags props = getChangedProperties(); @@ -82,65 +92,26 @@ void EntityItemProperties::debugDump() const { EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; - if (_radiusChanged) { - changedProperties += PROP_RADIUS; - } - - if (_positionChanged) { - changedProperties += PROP_POSITION; - } - - if (_rotationChanged) { - changedProperties += PROP_ROTATION; - } - - if (_massChanged) { - changedProperties += PROP_MASS; - } - - if (_velocityChanged) { - changedProperties += PROP_VELOCITY; - } - - if (_gravityChanged) { - changedProperties += PROP_GRAVITY; - } - - if (_dampingChanged) { - changedProperties += PROP_DAMPING; - } - - if (_lifetimeChanged) { - changedProperties += PROP_LIFETIME; - } - - if (_scriptChanged) { - changedProperties += PROP_SCRIPT; - } - - if (_colorChanged) { - changedProperties += PROP_COLOR; - } - - if (_modelURLChanged) { - changedProperties += PROP_MODEL_URL; - } - - if (_animationURLChanged) { - changedProperties += PROP_ANIMATION_URL; - } - - if (_animationIsPlayingChanged) { - changedProperties += PROP_ANIMATION_PLAYING; - } - - if (_animationFrameIndexChanged) { - changedProperties += PROP_ANIMATION_FRAME_INDEX; - } - - if (_animationFPSChanged) { - changedProperties += PROP_ANIMATION_FPS; - } + + CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions); + CHECK_PROPERTY_CHANGE(PROP_POSITION, position); + CHECK_PROPERTY_CHANGE(PROP_ROTATION, rotation); + CHECK_PROPERTY_CHANGE(PROP_MASS, mass); + CHECK_PROPERTY_CHANGE(PROP_VELOCITY, velocity); + CHECK_PROPERTY_CHANGE(PROP_GRAVITY, gravity); + CHECK_PROPERTY_CHANGE(PROP_DAMPING, damping); + CHECK_PROPERTY_CHANGE(PROP_LIFETIME, lifetime); + CHECK_PROPERTY_CHANGE(PROP_SCRIPT, script); + CHECK_PROPERTY_CHANGE(PROP_COLOR, color); + CHECK_PROPERTY_CHANGE(PROP_MODEL_URL, modelURL); + CHECK_PROPERTY_CHANGE(PROP_ANIMATION_URL, animationURL); + CHECK_PROPERTY_CHANGE(PROP_ANIMATION_PLAYING, animationIsPlaying); + CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FRAME_INDEX, animationFrameIndex); + CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FPS, animationFPS); + CHECK_PROPERTY_CHANGE(PROP_VISIBLE, visible); + CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint); + CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity); + CHECK_PROPERTY_CHANGE(PROP_ANGULAR_DAMPING, angularDamping); return changedProperties; } @@ -149,38 +120,35 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons QScriptValue properties = engine->newObject(); if (_idSet) { - properties.setProperty("id", _id.toString()); - bool isKnownID = (_id != UNKNOWN_ENTITY_ID); - properties.setProperty("isKnownID", isKnownID); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(id, _id.toString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(isKnownID, (_id != UNKNOWN_ENTITY_ID)); + } else { + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(isKnownID, false); } - properties.setProperty("type", EntityTypes::getEntityTypeName(_type)); - - QScriptValue position = vec3toScriptValue(engine, _position); - properties.setProperty("position", position); - properties.setProperty("radius", _radius); - QScriptValue rotation = quatToScriptValue(engine, _rotation); - properties.setProperty("rotation", rotation); - properties.setProperty("mass", _mass); - QScriptValue velocity = vec3toScriptValue(engine, _velocity); - properties.setProperty("velocity", velocity); - QScriptValue gravity = vec3toScriptValue(engine, _gravity); - properties.setProperty("gravity", gravity); - properties.setProperty("damping", _damping); - properties.setProperty("lifetime", _lifetime); - properties.setProperty("age", getAge()); // gettable, but not settable - properties.setProperty("ageAsText", formatSecondsElapsed(getAge())); // gettable, but not settable - properties.setProperty("script", _script); - - QScriptValue color = xColorToScriptValue(engine, _color); - properties.setProperty("color", color); - properties.setProperty("modelURL", _modelURL); - - properties.setProperty("animationURL", _animationURL); - properties.setProperty("animationIsPlaying", _animationIsPlaying); - properties.setProperty("animationFrameIndex", _animationFrameIndex); - properties.setProperty("animationFPS", _animationFPS); - properties.setProperty("glowLevel", _glowLevel); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(type, EntityTypes::getEntityTypeName(_type)); + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(position); + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(dimensions); + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(naturalDimensions); // gettable, but not settable + COPY_PROPERTY_TO_QSCRIPTVALUE_QUAT(rotation); + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(velocity); + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(gravity); + COPY_PROPERTY_TO_QSCRIPTVALUE(damping); + COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(age, getAge()); // gettable, but not settable + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable + COPY_PROPERTY_TO_QSCRIPTVALUE(script); + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(registrationPoint); + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(angularVelocity); + COPY_PROPERTY_TO_QSCRIPTVALUE(angularDamping); + COPY_PROPERTY_TO_QSCRIPTVALUE(visible); + COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR(color); + COPY_PROPERTY_TO_QSCRIPTVALUE(modelURL); + COPY_PROPERTY_TO_QSCRIPTVALUE(animationURL); + COPY_PROPERTY_TO_QSCRIPTVALUE(animationIsPlaying); + COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex); + COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS); + COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel); // Sitting properties support QScriptValue sittingPoints = engine->newObject(); @@ -192,217 +160,39 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons sittingPoints.setProperty(i, sittingPoint); } sittingPoints.setProperty("length", _sittingPoints.size()); - properties.setProperty("sittingPoints", sittingPoints); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable return properties; } void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { + QScriptValue typeScriptValue = object.property("type"); if (typeScriptValue.isValid()) { - QString typeName; - typeName = typeScriptValue.toVariant().toString(); - _type = EntityTypes::getEntityTypeFromName(typeName); + setType(typeScriptValue.toVariant().toString()); } - QScriptValue position = object.property("position"); - if (position.isValid()) { - QScriptValue x = position.property("x"); - QScriptValue y = position.property("y"); - QScriptValue z = position.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - glm::vec3 newPosition; - newPosition.x = x.toVariant().toFloat(); - newPosition.y = y.toVariant().toFloat(); - newPosition.z = z.toVariant().toFloat(); - if (_defaultSettings || newPosition != _position) { - setPosition(newPosition); // gives us automatic clamping - } - } - } - - QScriptValue radius = object.property("radius"); - if (radius.isValid()) { - float newRadius; - newRadius = radius.toVariant().toFloat(); - if (_defaultSettings || newRadius != _radius) { - _radius = newRadius; - _radiusChanged = true; - } - } - - QScriptValue rotation = object.property("rotation"); - if (rotation.isValid()) { - QScriptValue x = rotation.property("x"); - QScriptValue y = rotation.property("y"); - QScriptValue z = rotation.property("z"); - QScriptValue w = rotation.property("w"); - if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { - glm::quat newRotation; - newRotation.x = x.toVariant().toFloat(); - newRotation.y = y.toVariant().toFloat(); - newRotation.z = z.toVariant().toFloat(); - newRotation.w = w.toVariant().toFloat(); - if (_defaultSettings || newRotation != _rotation) { - _rotation = newRotation; - _rotationChanged = true; - } - } - } - - QScriptValue mass = object.property("mass"); - if (mass.isValid()) { - float newValue; - newValue = mass.toVariant().toFloat(); - if (_defaultSettings || newValue != _mass) { - _mass = newValue; - _massChanged = true; - } - } - - QScriptValue velocity = object.property("velocity"); - if (velocity.isValid()) { - QScriptValue x = velocity.property("x"); - QScriptValue y = velocity.property("y"); - QScriptValue z = velocity.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - glm::vec3 newValue; - newValue.x = x.toVariant().toFloat(); - newValue.y = y.toVariant().toFloat(); - newValue.z = z.toVariant().toFloat(); - if (_defaultSettings || newValue != _velocity) { - _velocity = newValue; - _velocityChanged = true; - } - } - } - - QScriptValue gravity = object.property("gravity"); - if (gravity.isValid()) { - QScriptValue x = gravity.property("x"); - QScriptValue y = gravity.property("y"); - QScriptValue z = gravity.property("z"); - if (x.isValid() && y.isValid() && z.isValid()) { - glm::vec3 newValue; - newValue.x = x.toVariant().toFloat(); - newValue.y = y.toVariant().toFloat(); - newValue.z = z.toVariant().toFloat(); - if (_defaultSettings || newValue != _gravity) { - _gravity = newValue; - _gravityChanged = true; - } - } - } - - QScriptValue damping = object.property("damping"); - if (damping.isValid()) { - float newValue; - newValue = damping.toVariant().toFloat(); - if (_defaultSettings || newValue != _damping) { - _damping = newValue; - _dampingChanged = true; - } - } - - QScriptValue lifetime = object.property("lifetime"); - if (lifetime.isValid()) { - float newValue; - newValue = lifetime.toVariant().toFloat(); - if (_defaultSettings || newValue != _lifetime) { - _lifetime = newValue; - _lifetimeChanged = true; - } - } - - QScriptValue script = object.property("script"); - if (script.isValid()) { - QString newValue; - newValue = script.toVariant().toString(); - if (_defaultSettings || newValue != _script) { - _script = newValue; - _scriptChanged = true; - } - } - - QScriptValue color = object.property("color"); - if (color.isValid()) { - QScriptValue red = color.property("red"); - QScriptValue green = color.property("green"); - QScriptValue blue = color.property("blue"); - if (red.isValid() && green.isValid() && blue.isValid()) { - xColor newColor; - newColor.red = red.toVariant().toInt(); - newColor.green = green.toVariant().toInt(); - newColor.blue = blue.toVariant().toInt(); - if (_defaultSettings || (newColor.red != _color.red || - newColor.green != _color.green || - newColor.blue != _color.blue)) { - _color = newColor; - _colorChanged = true; - } - } - } - - QScriptValue modelURL = object.property("modelURL"); - if (modelURL.isValid()) { - QString newModelURL; - newModelURL = modelURL.toVariant().toString(); - if (_defaultSettings || newModelURL != _modelURL) { - _modelURL = newModelURL; - _modelURLChanged = true; - } - } - - QScriptValue animationURL = object.property("animationURL"); - if (animationURL.isValid()) { - QString newAnimationURL; - newAnimationURL = animationURL.toVariant().toString(); - if (_defaultSettings || newAnimationURL != _animationURL) { - _animationURL = newAnimationURL; - _animationURLChanged = true; - } - } - - QScriptValue animationIsPlaying = object.property("animationIsPlaying"); - if (animationIsPlaying.isValid()) { - bool newIsAnimationPlaying; - newIsAnimationPlaying = animationIsPlaying.toVariant().toBool(); - if (_defaultSettings || newIsAnimationPlaying != _animationIsPlaying) { - _animationIsPlaying = newIsAnimationPlaying; - _animationIsPlayingChanged = true; - } - } - - QScriptValue animationFrameIndex = object.property("animationFrameIndex"); - if (animationFrameIndex.isValid()) { - float newFrameIndex; - newFrameIndex = animationFrameIndex.toVariant().toFloat(); - if (_defaultSettings || newFrameIndex != _animationFrameIndex) { - _animationFrameIndex = newFrameIndex; - _animationFrameIndexChanged = true; - } - } - - QScriptValue animationFPS = object.property("animationFPS"); - if (animationFPS.isValid()) { - float newFPS; - newFPS = animationFPS.toVariant().toFloat(); - if (_defaultSettings || newFPS != _animationFPS) { - _animationFPS = newFPS; - _animationFPSChanged = true; - } - } - - QScriptValue glowLevel = object.property("glowLevel"); - if (glowLevel.isValid()) { - float newGlowLevel; - newGlowLevel = glowLevel.toVariant().toFloat(); - if (_defaultSettings || newGlowLevel != _glowLevel) { - _glowLevel = newGlowLevel; - _glowLevelChanged = true; - } - } + COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(position, setPosition); + COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(dimensions, setDimensions); + COPY_PROPERTY_FROM_QSCRIPTVALUE_QUAT(rotation, setRotation); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(mass, setMass); + COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(velocity, setVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(gravity, setGravity); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(damping, setDamping); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(lifetime, setLifetime); + COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(script, setScript); + COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(registrationPoint, setRegistrationPoint); + COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(angularVelocity, setAngularVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(angularDamping, setAngularDamping); + COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(visible, setVisible); + COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(color, setColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(modelURL, setModelURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(animationURL, setAnimationURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(animationIsPlaying, setAnimationIsPlaying); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFPS, setAnimationFPS); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel); _lastEdited = usecTimestampNow(); } @@ -432,7 +222,6 @@ void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemP // bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, unsigned char* bufferOut, int sizeIn, int& sizeOut) { - OctreePacketData ourDataPacket(false, sizeIn); // create a packetData object to add out packet details too. OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro @@ -532,23 +321,26 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem // These items would go here once supported.... // PROP_PAGED_PROPERTY, // PROP_CUSTOM_PROPERTIES_INCLUDED, - // PROP_VISIBLE, APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, properties.getPosition()); - APPEND_ENTITY_PROPERTY(PROP_RADIUS, appendValue, properties.getRadius()); + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, appendValue, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, properties.getRotation()); APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, properties.getMass()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, properties.getVelocity()); APPEND_ENTITY_PROPERTY(PROP_GRAVITY, appendValue, properties.getGravity()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, properties.getDamping()); APPEND_ENTITY_PROPERTY(PROP_LIFETIME, appendValue, properties.getLifetime()); - //APPEND_ENTITY_PROPERTY(PROP_SCRIPT, appendValue, properties.getScript()); // not supported by edit messages + APPEND_ENTITY_PROPERTY(PROP_SCRIPT, appendValue, properties.getScript()); APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, properties.getColor()); APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, appendValue, properties.getModelURL()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, appendValue, properties.getAnimationURL()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, properties.getAnimationFPS()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, properties.getAnimationFrameIndex()); APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, appendValue, properties.getAnimationIsPlaying()); + APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, appendValue, properties.getRegistrationPoint()); + APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, appendValue, properties.getAngularVelocity()); + APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, properties.getAngularDamping()); + APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, properties.getVisible()); } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -727,20 +519,24 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int processedBytes += propertyFlags.getEncodedLength(); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RADIUS, float, setRadius); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete READ_ENTITY_PROPERTY_QUAT_TO_PROPERTIES(PROP_ROTATION, setRotation); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MASS, float, setMass); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRAVITY, glm::vec3, setGravity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DAMPING, float, setDamping); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFETIME, float, setLifetime); - //READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript); // not yet supported by edit messages... + READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_SCRIPT,setScript); READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(PROP_COLOR, setColor); READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_MODEL_URL, setModelURL); READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_ANIMATION_URL, setAnimationURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FPS, float, setAnimationFPS); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FRAME_INDEX, float, setAnimationFrameIndex); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_PLAYING, bool, setAnimationIsPlaying); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, glm::vec3, setAngularVelocity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_DAMPING, float, setAngularDamping); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible); return valid; } @@ -774,10 +570,8 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt return true; } - void EntityItemProperties::markAllChanged() { _positionChanged = true; - _radiusChanged = true; _rotationChanged = true; _massChanged = true; _velocityChanged = true; @@ -785,7 +579,10 @@ void EntityItemProperties::markAllChanged() { _dampingChanged = true; _lifetimeChanged = true; _scriptChanged = true; - + _registrationPointChanged = true; + _angularVelocityChanged = true; + _angularDampingChanged = true; + _visibleChanged = true; _colorChanged = true; _modelURLChanged = true; _animationURLChanged = true; @@ -793,5 +590,36 @@ void EntityItemProperties::markAllChanged() { _animationFrameIndexChanged = true; _animationFPSChanged = true; _glowLevelChanged = true; - +} + +AACube EntityItemProperties::getMaximumAACubeInTreeUnits() const { + AACube maxCube = getMaximumAACubeInMeters(); + maxCube.scale(1 / (float)TREE_SCALE); + return maxCube; +} + +/// The maximum bounding cube for the entity, independent of it's rotation. +/// This accounts for the registration point (upon which rotation occurs around). +/// +AACube EntityItemProperties::getMaximumAACubeInMeters() const { + // * we know that the position is the center of rotation + glm::vec3 centerOfRotation = _position; // also where _registration point is + + // * we know that the registration point is the center of rotation + // * we can calculate the length of the furthest extent from the registration point + // as the dimensions * max (registrationPoint, (1.0,1.0,1.0) - registrationPoint) + glm::vec3 registrationPoint = (_dimensions * _registrationPoint); + glm::vec3 registrationRemainder = (_dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint)); + glm::vec3 furthestExtentFromRegistration = glm::max(registrationPoint, registrationRemainder); + + // * we know that if you rotate in any direction you would create a sphere + // that has a radius of the length of furthest extent from registration point + float radius = glm::length(furthestExtentFromRegistration); + + // * we know that the minimum bounding cube of this maximum possible sphere is + // (center - radius) to (center + radius) + glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius); + float diameter = radius * 2.0f; + + return AACube(minimumCorner, diameter); } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index fc506ce10c..a52a75e071 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -28,20 +29,9 @@ #include "EntityItemID.h" +#include "EntityItemPropertiesMacros.h" #include "EntityTypes.h" - -// TODO: should these be static members of EntityItem or EntityItemProperties? -const float ENTITY_DEFAULT_RADIUS = 0.1f / TREE_SCALE; -const float ENTITY_MINIMUM_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container -const QString ENTITY_DEFAULT_MODEL_URL(""); -const glm::quat ENTITY_DEFAULT_ROTATION; -const QString ENTITY_DEFAULT_ANIMATION_URL(""); -const float ENTITY_DEFAULT_ANIMATION_FPS = 30.0f; - -const quint64 UNKNOWN_CREATED_TIME = (quint64)(-1); -const quint64 USE_EXISTING_CREATED_TIME = (quint64)(-2); - // PropertyFlags support enum EntityPropertyList { PROP_PAGED_PROPERTY, @@ -50,7 +40,8 @@ enum EntityPropertyList { // these properties are supported by the EntityItem base class PROP_VISIBLE, PROP_POSITION, - PROP_RADIUS, + PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams + PROP_DIMENSIONS = PROP_RADIUS, PROP_ROTATION, PROP_MASS, PROP_VELOCITY, @@ -67,16 +58,24 @@ enum EntityPropertyList { PROP_ANIMATION_FRAME_INDEX, PROP_ANIMATION_PLAYING, - PROP_LAST_ITEM = PROP_ANIMATION_PLAYING + // these properties are supported by the EntityItem base class + PROP_REGISTRATION_POINT, + PROP_ANGULAR_VELOCITY, + PROP_ANGULAR_DAMPING, + + PROP_LAST_ITEM = PROP_ANGULAR_DAMPING }; typedef PropertyFlags EntityPropertyFlags; +const quint64 UNKNOWN_CREATED_TIME = (quint64)(-1); +const quint64 USE_EXISTING_CREATED_TIME = (quint64)(-2); + /// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an /// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete /// set of entity item properties via JavaScript hashes/QScriptValues -/// all units for position, radius, etc are in meter units +/// all units for position, dimensions, etc are in meter units class EntityItemProperties { friend class EntityItem; // TODO: consider removing this friend relationship and use public methods friend class ModelEntityItem; // TODO: consider removing this friend relationship and use public methods @@ -96,31 +95,27 @@ public: /// used by EntityScriptingInterface to return EntityItemProperties for unknown models void setIsUnknownID() { _id = UNKNOWN_ENTITY_ID; _idSet = true; } - glm::vec3 getMinimumPointMeters() const { return _position - glm::vec3(_radius, _radius, _radius); } - glm::vec3 getMaximumPointMeters() const { return _position + glm::vec3(_radius, _radius, _radius); } - AACube getAACubeMeters() const { return AACube(getMinimumPointMeters(), getMaxDimension()); } /// AACube in meter units - - glm::vec3 getMinimumPointTreeUnits() const { return getMinimumPointMeters() / (float)TREE_SCALE; } - glm::vec3 getMaximumPointTreeUnits() const { return getMaximumPointMeters() / (float)TREE_SCALE; } - /// AACube in domain scale units (0.0 - 1.0) - AACube getAACubeTreeUnits() const { - return AACube(getMinimumPointMeters() / (float)TREE_SCALE, getMaxDimension() / (float)TREE_SCALE); - } + AACube getMaximumAACubeInTreeUnits() const; + AACube getMaximumAACubeInMeters() const; void debugDump() const; + // properties of all entities EntityTypes::EntityType getType() const { return _type; } - const glm::vec3& getPosition() const { return _position; } - float getRadius() const { return _radius; } - float getMaxDimension() const { return _radius * 2.0f; } - glm::vec3 getDimensions() const { return glm::vec3(_radius, _radius, _radius) * 2.0f; } - const glm::quat& getRotation() const { return _rotation; } void setType(EntityTypes::EntityType type) { _type = type; } + + const glm::vec3& getPosition() const { return _position; } /// set position in meter units, will be clamped to domain bounds void setPosition(const glm::vec3& value) { _position = glm::clamp(value, 0.0f, (float)TREE_SCALE); _positionChanged = true; } - void setRadius(float value) { _radius = value; _radiusChanged = true; } + + + const glm::vec3& getDimensions() const { return _dimensions; } + void setDimensions(const glm::vec3& value) { _dimensions = value; _dimensionsChanged = true; } + float getMaxDimension() const { return glm::max(_dimensions.x, _dimensions.y, _dimensions.z); } + + const glm::quat& getRotation() const { return _rotation; } void setRotation(const glm::quat& rotation) { _rotation = rotation; _rotationChanged = true; } float getMass() const { return _mass; } @@ -148,9 +143,9 @@ public: // NOTE: how do we handle _defaultSettings??? - bool containsBoundsProperties() const { return (_positionChanged || _radiusChanged); } + bool containsBoundsProperties() const { return (_positionChanged || _dimensionsChanged); } bool containsPositionChange() const { return _positionChanged; } - bool containsRadiusChange() const { return _radiusChanged; } + bool containsDimensionsChange() const { return _dimensionsChanged; } // TODO: this need to be more generic. for now, we're going to have the properties class support these as // named getter/setters, but we want to move them to generic types... @@ -162,6 +157,7 @@ public: bool getAnimationIsPlaying() const { return _animationIsPlaying; } float getAnimationFPS() const { return _animationFPS; } float getGlowLevel() const { return _glowLevel; } + const QString& getScript() const { return _script; } // model related properties void setColor(const xColor& value) { _color = value; _colorChanged = true; } @@ -171,6 +167,7 @@ public: void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; } void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; } void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; } + void setScript(const QString& value) { _script = value; _scriptChanged = true; } static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, @@ -184,7 +181,6 @@ public: EntityItemID& entityID, EntityItemProperties& properties); bool positionChanged() const { return _positionChanged; } - bool radiusChanged() const { return _radiusChanged; } bool rotationChanged() const { return _rotationChanged; } bool massChanged() const { return _massChanged; } bool velocityChanged() const { return _velocityChanged; } @@ -192,6 +188,8 @@ public: bool dampingChanged() const { return _dampingChanged; } bool lifetimeChanged() const { return _lifetimeChanged; } bool scriptChanged() const { return _scriptChanged; } + bool dimensionsChanged() const { return _dimensionsChanged; } + bool registrationPointChanged() const { return _registrationPointChanged; } bool colorChanged() const { return _colorChanged; } bool modelURLChanged() const { return _modelURLChanged; } bool animationURLChanged() const { return _animationURLChanged; } @@ -203,6 +201,24 @@ public: void clearID() { _id = UNKNOWN_ENTITY_ID; _idSet = false; } void markAllChanged(); + QVector getSittingPoints() const { return _sittingPoints; } + void setSittingPoints(QVector sittingPoints) { _sittingPoints = sittingPoints; } + + const glm::vec3& getNaturalDimensions() const { return _naturalDimensions; } + void setNaturalDimensions(const glm::vec3& value) { _naturalDimensions = value; } + + const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } + void setRegistrationPoint(const glm::vec3& value) { _registrationPoint = value; _registrationPointChanged = true; } + + const glm::vec3& getAngularVelocity() const { return _angularVelocity; } + void setAngularVelocity(const glm::vec3& value) { _angularVelocity = value; _angularVelocityChanged = true; } + + float getAngularDamping() const { return _angularDamping; } + void setAngularDamping(float value) { _angularDamping = value; _angularDampingChanged = true; } + + bool getVisible() const { return _visible; } + void setVisible(bool value) { _visible = value; _visibleChanged = true; } + private: void setLastEdited(quint64 usecTime) { _lastEdited = usecTime; } @@ -212,8 +228,11 @@ private: quint64 _created; EntityTypes::EntityType _type; + + void setType(const QString& typeName) { _type = EntityTypes::getEntityTypeFromName(typeName); } + glm::vec3 _position; - float _radius; + glm::vec3 _dimensions; glm::quat _rotation; float _mass; glm::vec3 _velocity; @@ -221,9 +240,13 @@ private: float _damping; float _lifetime; QString _script; + glm::vec3 _registrationPoint; + glm::vec3 _angularVelocity; + float _angularDamping; + bool _visible; bool _positionChanged; - bool _radiusChanged; + bool _dimensionsChanged; bool _rotationChanged; bool _massChanged; bool _velocityChanged; @@ -231,6 +254,10 @@ private: bool _dampingChanged; bool _lifetimeChanged; bool _scriptChanged; + bool _registrationPointChanged; + bool _angularVelocityChanged; + bool _angularDampingChanged; + bool _visibleChanged; // TODO: this need to be more generic. for now, we're going to have the properties class support these as // named getter/setters, but we want to move them to generic types... @@ -242,6 +269,7 @@ private: float _animationFPS; float _glowLevel; QVector _sittingPoints; + glm::vec3 _naturalDimensions; bool _colorChanged; bool _modelURLChanged; @@ -257,107 +285,4 @@ Q_DECLARE_METATYPE(EntityItemProperties); QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties); void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemProperties& properties); -#define APPEND_ENTITY_PROPERTY(P,O,V) \ - if (requestedProperties.getHasProperty(P)) { \ - LevelDetails propertyLevel = packetData->startLevel(); \ - successPropertyFits = packetData->O(V); \ - if (successPropertyFits) { \ - propertyFlags |= P; \ - propertiesDidntFit -= P; \ - propertyCount++; \ - packetData->endLevel(propertyLevel); \ - } else { \ - packetData->discardLevel(propertyLevel); \ - appendState = OctreeElement::PARTIAL; \ - } \ - } else { \ - propertiesDidntFit -= P; \ - } - -#define READ_ENTITY_PROPERTY(P,T,M) \ - if (propertyFlags.getHasProperty(P)) { \ - T fromBuffer; \ - memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); \ - dataAt += sizeof(fromBuffer); \ - bytesRead += sizeof(fromBuffer); \ - if (overwriteLocalData) { \ - M = fromBuffer; \ - } \ - } - -#define READ_ENTITY_PROPERTY_QUAT(P,M) \ - if (propertyFlags.getHasProperty(P)) { \ - glm::quat fromBuffer; \ - int bytes = unpackOrientationQuatFromBytes(dataAt, fromBuffer); \ - dataAt += bytes; \ - bytesRead += bytes; \ - if (overwriteLocalData) { \ - M = fromBuffer; \ - } \ - } - -#define READ_ENTITY_PROPERTY_STRING(P,O) \ - if (propertyFlags.getHasProperty(P)) { \ - uint16_t length; \ - memcpy(&length, dataAt, sizeof(length)); \ - dataAt += sizeof(length); \ - bytesRead += sizeof(length); \ - QString value((const char*)dataAt); \ - dataAt += length; \ - bytesRead += length; \ - if (overwriteLocalData) { \ - O(value); \ - } \ - } - -#define READ_ENTITY_PROPERTY_COLOR(P,M) \ - if (propertyFlags.getHasProperty(P)) { \ - if (overwriteLocalData) { \ - memcpy(M, dataAt, sizeof(M)); \ - } \ - dataAt += sizeof(rgbColor); \ - bytesRead += sizeof(rgbColor); \ - } - -#define READ_ENTITY_PROPERTY_TO_PROPERTIES(P,T,O) \ - if (propertyFlags.getHasProperty(P)) { \ - T fromBuffer; \ - memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); \ - dataAt += sizeof(fromBuffer); \ - processedBytes += sizeof(fromBuffer); \ - properties.O(fromBuffer); \ - } - -#define READ_ENTITY_PROPERTY_QUAT_TO_PROPERTIES(P,O) \ - if (propertyFlags.getHasProperty(P)) { \ - glm::quat fromBuffer; \ - int bytes = unpackOrientationQuatFromBytes(dataAt, fromBuffer); \ - dataAt += bytes; \ - processedBytes += bytes; \ - properties.O(fromBuffer); \ - } - -#define READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(P,O) \ - if (propertyFlags.getHasProperty(P)) { \ - uint16_t length; \ - memcpy(&length, dataAt, sizeof(length)); \ - dataAt += sizeof(length); \ - processedBytes += sizeof(length); \ - QString value((const char*)dataAt); \ - dataAt += length; \ - processedBytes += length; \ - properties.O(value); \ - } - -#define READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(P,O) \ - if (propertyFlags.getHasProperty(P)) { \ - xColor color; \ - memcpy(&color, dataAt, sizeof(color)); \ - dataAt += sizeof(color); \ - processedBytes += sizeof(color); \ - properties.O(color); \ - } - - - #endif // hifi_EntityItemProperties_h diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h new file mode 100644 index 0000000000..46c78c09ea --- /dev/null +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -0,0 +1,241 @@ +// +// EntityItemPropertiesMacros.h +// libraries/entities/src +// +// Created by Brad Hefta-Gaub on 9/10/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_EntityItemPropertiesMacros_h +#define hifi_EntityItemPropertiesMacros_h + +#define APPEND_ENTITY_PROPERTY(P,O,V) \ + if (requestedProperties.getHasProperty(P)) { \ + LevelDetails propertyLevel = packetData->startLevel(); \ + successPropertyFits = packetData->O(V); \ + if (successPropertyFits) { \ + propertyFlags |= P; \ + propertiesDidntFit -= P; \ + propertyCount++; \ + packetData->endLevel(propertyLevel); \ + } else { \ + packetData->discardLevel(propertyLevel); \ + appendState = OctreeElement::PARTIAL; \ + } \ + } else { \ + propertiesDidntFit -= P; \ + } + +#define READ_ENTITY_PROPERTY(P,T,M) \ + if (propertyFlags.getHasProperty(P)) { \ + T fromBuffer; \ + memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); \ + dataAt += sizeof(fromBuffer); \ + bytesRead += sizeof(fromBuffer); \ + if (overwriteLocalData) { \ + M = fromBuffer; \ + } \ + } + +#define READ_ENTITY_PROPERTY_QUAT(P,M) \ + if (propertyFlags.getHasProperty(P)) { \ + glm::quat fromBuffer; \ + int bytes = unpackOrientationQuatFromBytes(dataAt, fromBuffer); \ + dataAt += bytes; \ + bytesRead += bytes; \ + if (overwriteLocalData) { \ + M = fromBuffer; \ + } \ + } + +#define READ_ENTITY_PROPERTY_STRING(P,O) \ + if (propertyFlags.getHasProperty(P)) { \ + uint16_t length; \ + memcpy(&length, dataAt, sizeof(length)); \ + dataAt += sizeof(length); \ + bytesRead += sizeof(length); \ + QString value((const char*)dataAt); \ + dataAt += length; \ + bytesRead += length; \ + if (overwriteLocalData) { \ + O(value); \ + } \ + } + +#define READ_ENTITY_PROPERTY_COLOR(P,M) \ + if (propertyFlags.getHasProperty(P)) { \ + if (overwriteLocalData) { \ + memcpy(M, dataAt, sizeof(M)); \ + } \ + dataAt += sizeof(rgbColor); \ + bytesRead += sizeof(rgbColor); \ + } + +#define READ_ENTITY_PROPERTY_TO_PROPERTIES(P,T,O) \ + if (propertyFlags.getHasProperty(P)) { \ + T fromBuffer; \ + memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); \ + dataAt += sizeof(fromBuffer); \ + processedBytes += sizeof(fromBuffer); \ + properties.O(fromBuffer); \ + } + +#define READ_ENTITY_PROPERTY_QUAT_TO_PROPERTIES(P,O) \ + if (propertyFlags.getHasProperty(P)) { \ + glm::quat fromBuffer; \ + int bytes = unpackOrientationQuatFromBytes(dataAt, fromBuffer); \ + dataAt += bytes; \ + processedBytes += bytes; \ + properties.O(fromBuffer); \ + } + +#define READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(P,O) \ + if (propertyFlags.getHasProperty(P)) { \ + uint16_t length; \ + memcpy(&length, dataAt, sizeof(length)); \ + dataAt += sizeof(length); \ + processedBytes += sizeof(length); \ + QString value((const char*)dataAt); \ + dataAt += length; \ + processedBytes += length; \ + properties.O(value); \ + } + +#define READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(P,O) \ + if (propertyFlags.getHasProperty(P)) { \ + xColor color; \ + memcpy(&color, dataAt, sizeof(color)); \ + dataAt += sizeof(color); \ + processedBytes += sizeof(color); \ + properties.O(color); \ + } + +#define SET_ENTITY_PROPERTY_FROM_PROPERTIES(P,M) \ + if (properties._##P##Changed || forceCopy) { \ + M(properties._##P); \ + somethingChanged = true; \ + } + +#define SET_ENTITY_PROPERTY_FROM_PROPERTIES_GETTER(C,G,S) \ + if (properties.C() || forceCopy) { \ + S(properties.G()); \ + somethingChanged = true; \ + } + +#define COPY_ENTITY_PROPERTY_TO_PROPERTIES(M,G) \ + properties._##M = G(); \ + properties._##M##Changed = false; + +#define CHECK_PROPERTY_CHANGE(P,M) \ + if (_##M##Changed) { \ + changedProperties += P; \ + } + + +#define COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(P) \ + QScriptValue P = vec3toScriptValue(engine, _##P); \ + properties.setProperty(#P, P); + +#define COPY_PROPERTY_TO_QSCRIPTVALUE_QUAT(P) \ + QScriptValue P = quatToScriptValue(engine, _##P); \ + properties.setProperty(#P, P); + +#define COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR(P) \ + QScriptValue P = xColorToScriptValue(engine, _##P); \ + properties.setProperty(#P, P); + +#define COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(P, G) \ + properties.setProperty(#P, G); + +#define COPY_PROPERTY_TO_QSCRIPTVALUE(P) \ + properties.setProperty(#P, _##P); + +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(P, S) \ + QScriptValue P = object.property(#P); \ + if (P.isValid()) { \ + float newValue = P.toVariant().toFloat(); \ + if (_defaultSettings || newValue != _##P) { \ + S(newValue); \ + } \ + } + +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(P, S) \ + QScriptValue P = object.property(#P); \ + if (P.isValid()) { \ + bool newValue = P.toVariant().toBool(); \ + if (_defaultSettings || newValue != _##P) { \ + S(newValue); \ + } \ + } + +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(P, S)\ + QScriptValue P = object.property(#P); \ + if (P.isValid()) { \ + QString newValue = P.toVariant().toString();\ + if (_defaultSettings || newValue != _##P) { \ + S(newValue); \ + } \ + } + +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(P, S) \ + QScriptValue P = object.property(#P); \ + if (P.isValid()) { \ + QScriptValue x = P.property("x"); \ + QScriptValue y = P.property("y"); \ + QScriptValue z = P.property("z"); \ + if (x.isValid() && y.isValid() && z.isValid()) {\ + glm::vec3 newValue; \ + newValue.x = x.toVariant().toFloat(); \ + newValue.y = y.toVariant().toFloat(); \ + newValue.z = z.toVariant().toFloat(); \ + if (_defaultSettings || newValue != _##P) { \ + S(newValue); \ + } \ + } \ + } + +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_QUAT(P, S) \ + QScriptValue P = object.property(#P); \ + if (P.isValid()) { \ + QScriptValue x = P.property("x"); \ + QScriptValue y = P.property("y"); \ + QScriptValue z = P.property("z"); \ + QScriptValue w = P.property("w"); \ + if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) { \ + glm::quat newValue; \ + newValue.x = x.toVariant().toFloat(); \ + newValue.y = y.toVariant().toFloat(); \ + newValue.z = z.toVariant().toFloat(); \ + newValue.w = w.toVariant().toFloat(); \ + if (_defaultSettings || newValue != _##P) { \ + S(newValue); \ + } \ + } \ + } + +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(P, S) \ + QScriptValue P = object.property(#P); \ + if (P.isValid()) { \ + QScriptValue r = P.property("red"); \ + QScriptValue g = P.property("green"); \ + QScriptValue b = P.property("blue"); \ + if (r.isValid() && g.isValid() && b.isValid()) {\ + xColor newColor; \ + newColor.red = r.toVariant().toInt(); \ + newColor.green = g.toVariant().toInt(); \ + newColor.blue = b.toVariant().toInt(); \ + if (_defaultSettings || \ + (newColor.red != _color.red || \ + newColor.green != _color.green || \ + newColor.blue != _color.blue)) { \ + S(newColor); \ + } \ + } \ + } + + + +#endif // hifi_EntityItemPropertiesMacros_h diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 971d7066eb..8113168655 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -31,9 +31,6 @@ EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& pro EntityItemID id(NEW_ENTITY, creatorTokenID, false ); - // queue the packet - queueEntityMessage(PacketTypeEntityAddOrEdit, id, properties); - // If we have a local entity tree set, then also update it. if (_entityTree) { _entityTree->lockForWrite(); @@ -41,6 +38,9 @@ EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& pro _entityTree->unlock(); } + // queue the packet + queueEntityMessage(PacketTypeEntityAddOrEdit, id, properties); + return id; } @@ -68,18 +68,20 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(EntityItemID EntityItem* entity = const_cast(_entityTree->findEntityByEntityItemID(identity)); if (entity) { - - // TODO: improve sitting points in the future, for now we've included the old model behavior for entity - // types that are models + results = entity->getProperties(); + + // TODO: improve sitting points and naturalDimensions in the future, + // for now we've included the old sitting points model behavior for entity types that are models + // we've also added this hack for setting natural dimensions of models if (entity->getType() == EntityTypes::Model) { - ModelEntityItem* model = dynamic_cast(entity); const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity); if (geometry) { - model->setSittingPoints(geometry->sittingPoints); + results.setSittingPoints(geometry->sittingPoints); + Extents meshExtents = geometry->getUnscaledMeshExtents(); + results.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum); } } - - results = entity->getProperties(); + } else { results.setIsUnknownID(); } @@ -95,13 +97,10 @@ EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const E // if the entity is unknown, attempt to look it up if (!entityID.isKnownID) { actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID); - } - - // if at this point, we know the id, send the update to the entity server - if (actualID.id != UNKNOWN_ENTITY_ID) { - entityID.id = actualID.id; - entityID.isKnownID = true; - queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, properties); + if (actualID.id != UNKNOWN_ENTITY_ID) { + entityID.id = actualID.id; + entityID.isKnownID = true; + } } // If we have a local entity tree set, then also update it. We can do this even if we don't know @@ -111,6 +110,12 @@ EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const E _entityTree->updateEntity(entityID, properties); _entityTree->unlock(); } + + // if at this point, we know the id, send the update to the entity server + if (entityID.isKnownID) { + queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, properties); + } + return entityID; } @@ -121,13 +126,10 @@ void EntityScriptingInterface::deleteEntity(EntityItemID entityID) { // if the entity is unknown, attempt to look it up if (!entityID.isKnownID) { actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID); - } - - // if at this point, we know the id, send the update to the entity server - if (actualID.id != UNKNOWN_ENTITY_ID) { - entityID.id = actualID.id; - entityID.isKnownID = true; - getEntityPacketSender()->queueEraseEntityMessage(entityID); + if (actualID.id != UNKNOWN_ENTITY_ID) { + entityID.id = actualID.id; + entityID.isKnownID = true; + } } // If we have a local entity tree set, then also update it. @@ -136,6 +138,11 @@ void EntityScriptingInterface::deleteEntity(EntityItemID entityID) { _entityTree->deleteEntity(entityID); _entityTree->unlock(); } + + // if at this point, we know the id, send the update to the entity server + if (entityID.isKnownID) { + getEntityPacketSender()->queueEraseEntityMessage(entityID); + } } EntityItemID EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 4380171df2..8cec19eddd 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -652,13 +652,14 @@ void EntityTree::updateMovingEntities(quint64 now, QSet& entitiesT entitiesToDelete << thisEntity->getEntityItemID(); entitiesBecomingStatic << thisEntity; } else { - AACube oldCube = thisEntity->getAACube(); + AACube oldCube = thisEntity->getMaximumAACube(); thisEntity->update(now); - AACube newCube = thisEntity->getAACube(); + AACube newCube = thisEntity->getMaximumAACube(); // check to see if this movement has sent the entity outside of the domain. AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f); if (!domainBounds.touches(newCube)) { + qDebug() << "Entity " << thisEntity->getEntityItemID() << " moved out of domain bounds."; entitiesToDelete << thisEntity->getEntityItemID(); entitiesBecomingStatic << thisEntity; } else { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 47ae04846d..d97a87faca 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -122,6 +122,9 @@ public: const FBXGeometry* getGeometryForEntity(const EntityItem* entityItem) { return _fbxService ? _fbxService->getGeometryForEntity(entityItem) : NULL; } + const Model* getModelForEntityItem(const EntityItem* entityItem) { + return _fbxService ? _fbxService->getModelForEntityItem(entityItem) : NULL; + } EntityTreeElement* getContainingElement(const EntityItemID& entityItemID) /*const*/; void setContainingElement(const EntityItemID& entityItemID, EntityTreeElement* element); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 4dea9b271c..a58dd9065f 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -296,7 +296,12 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData } if (includeThisEntity && params.viewFrustum) { - AACube entityCube = entity->getAACube(); + + // we want to use the maximum possible box for this, so that we don't have to worry about the nuance of + // simulation changing what's visible. consider the case where the entity contains an angular velocity + // the entity may not be in view and then in view a frame later, let the client side handle it's view + // frustum culling on rendering. + AACube entityCube = entity->getMaximumAACube(); entityCube.scale(TREE_SCALE); if (params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) { includeThisEntity = false; // out of view, don't include it @@ -405,19 +410,19 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData } bool EntityTreeElement::containsEntityBounds(const EntityItem* entity) const { - return containsBounds(entity->getMinimumPoint(), entity->getMaximumPoint()); + return containsBounds(entity->getMaximumAACube()); } bool EntityTreeElement::bestFitEntityBounds(const EntityItem* entity) const { - return bestFitBounds(entity->getMinimumPoint(), entity->getMaximumPoint()); + return bestFitBounds(entity->getMaximumAACube()); } bool EntityTreeElement::containsBounds(const EntityItemProperties& properties) const { - return containsBounds(properties.getMinimumPointTreeUnits(), properties.getMaximumPointTreeUnits()); + return containsBounds(properties.getMaximumAACubeInTreeUnits()); } bool EntityTreeElement::bestFitBounds(const EntityItemProperties& properties) const { - return bestFitBounds(properties.getMinimumPointTreeUnits(), properties.getMaximumPointTreeUnits()); + return bestFitBounds(properties.getMaximumAACubeInTreeUnits()); } bool EntityTreeElement::containsBounds(const AACube& bounds) const { @@ -477,76 +482,37 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con while(entityItr != entityEnd) { EntityItem* entity = (*entityItr); - AACube entityCube = entity->getAACube(); + AABox entityBox = entity->getAABox(); float localDistance; BoxFace localFace; // if the ray doesn't intersect with our cube, we can stop searching! - if (entityCube.findRayIntersection(origin, direction, localDistance, localFace)) { - const FBXGeometry* fbxGeometry = _myTree->getGeometryForEntity(entity); - if (fbxGeometry && fbxGeometry->meshExtents.isValid()) { - Extents extents = fbxGeometry->meshExtents; + if (entityBox.findRayIntersection(origin, direction, localDistance, localFace)) { - // NOTE: If the entity 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 + // extents is the entity relative, scaled, centered extents of the entity + glm::mat4 rotation = glm::mat4_cast(entity->getRotation()); + glm::mat4 translation = glm::translate(entity->getPosition()); + glm::mat4 entityToWorldMatrix = translation * rotation; + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + + glm::vec3 dimensions = entity->getDimensions(); + glm::vec3 registrationPoint = entity->getRegistrationPoint(); + glm::vec3 corner = -(dimensions * registrationPoint); + + AABox entityFrameBox(corner, dimensions); + + glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 entityFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)); + + // we can use the AABox's ray intersection by mapping our origin and direction into the entity frame + // and testing intersection there. + if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, localFace)) { + if (localDistance < distance) { + distance = localDistance; + face = localFace; + *intersectedObject = (void*)entity; + somethingIntersected = true; } - - // NOTE: these extents are entity space, so we need to scale and center them accordingly - // size is our "target size in world space" - // we need to set our entity scale so that the extents of the mesh, fit in a cube that size... - float maxDimension = glm::distance(extents.maximum, extents.minimum); - float scale = entity->getSize() / maxDimension; - - glm::vec3 halfDimensions = (extents.maximum - extents.minimum) * 0.5f; - glm::vec3 offset = -extents.minimum - halfDimensions; - - extents.minimum += offset; - extents.maximum += offset; - - extents.minimum *= scale; - extents.maximum *= scale; - - Extents rotatedExtents = extents; - - rotatedExtents.rotate(entity->getRotation()); - - rotatedExtents.minimum += entity->getPosition(); - rotatedExtents.maximum += entity->getPosition(); - - - AABox rotatedExtentsBox(rotatedExtents.minimum, (rotatedExtents.maximum - rotatedExtents.minimum)); - - // if it's in our AABOX for our rotated extents, then check to see if it's in our non-AABox - if (rotatedExtentsBox.findRayIntersection(origin, direction, localDistance, localFace)) { - - // extents is the entity relative, scaled, centered extents of the entity - glm::mat4 rotation = glm::mat4_cast(entity->getRotation()); - glm::mat4 translation = glm::translate(entity->getPosition()); - glm::mat4 entityToWorldMatrix = translation * rotation; - glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); - - AABox entityFrameBox(extents.minimum, (extents.maximum - extents.minimum)); - - glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); - glm::vec3 entityFrameDirection = glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)); - - // we can use the AABox's ray intersection by mapping our origin and direction into the entity frame - // and testing intersection there. - if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, localFace)) { - if (localDistance < distance) { - distance = localDistance; - face = localFace; - *intersectedObject = (void*)entity; - somethingIntersected = true; - } - } - } - } else if (localDistance < distance) { - distance = localDistance; - face = localFace; - *intersectedObject = (void*)entity; - somethingIntersected = true; } } @@ -555,6 +521,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con return somethingIntersected; } +// TODO: change this to use better bounding shape for entity than sphere bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const { QList::iterator entityItr = _entityItems->begin(); @@ -605,6 +572,7 @@ const EntityItem* EntityTreeElement::getClosestEntity(glm::vec3 position) const return closestEntity; } +// TODO: change this to use better bounding shape for entity than sphere void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searchRadius, QVector& foundEntities) const { uint16_t numberOfEntities = _entityItems->size(); for (uint16_t i = 0; i < numberOfEntities; i++) { @@ -616,6 +584,7 @@ void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searc } } +// TODO: change this to use better bounding shape for entity than sphere void EntityTreeElement::getEntities(const AACube& box, QVector& foundEntities) { QList::iterator entityItr = _entityItems->begin(); QList::iterator entityEnd = _entityItems->end(); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 1c1cd19831..83958c329b 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -17,6 +17,13 @@ #include "ModelEntityItem.h" +const QString ModelEntityItem::DEFAULT_MODEL_URL = QString(""); +const QString ModelEntityItem::DEFAULT_ANIMATION_URL = QString(""); +const float ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX = 0.0f; +const bool ModelEntityItem::DEFAULT_ANIMATION_IS_PLAYING = false; +const float ModelEntityItem::DEFAULT_ANIMATION_FPS = 30.0f; + + EntityItem* ModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return new ModelEntityItem(entityID, properties); } @@ -40,7 +47,6 @@ EntityItemProperties ModelEntityItem::getProperties() const { properties._animationIsPlaying = getAnimationIsPlaying(); properties._animationFrameIndex = getAnimationFrameIndex(); properties._animationFPS = getAnimationFPS(); - properties._sittingPoints = getSittingPoints(); // sitting support properties._colorChanged = false; properties._modelURLChanged = false; properties._animationURLChanged = false; @@ -55,40 +61,13 @@ EntityItemProperties ModelEntityItem::getProperties() const { bool ModelEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) { bool somethingChanged = false; somethingChanged = EntityItem::setProperties(properties, forceCopy); // set the properties in our base class - if (properties._colorChanged || forceCopy) { - setColor(properties._color); - somethingChanged = true; - } - if (properties._modelURLChanged || forceCopy) { - setModelURL(properties._modelURL); - somethingChanged = true; - } - - if (properties._animationURLChanged || forceCopy) { - setAnimationURL(properties._animationURL); - somethingChanged = true; - } - - if (properties._animationIsPlayingChanged || forceCopy) { - setAnimationIsPlaying(properties._animationIsPlaying); - somethingChanged = true; - } - - if (properties._animationFrameIndexChanged || forceCopy) { - setAnimationFrameIndex(properties._animationFrameIndex); - somethingChanged = true; - } - - if (properties._animationFPSChanged || forceCopy) { - setAnimationFPS(properties._animationFPS); - somethingChanged = true; - } - - if (properties._glowLevelChanged || forceCopy) { - setGlowLevel(properties._glowLevel); - somethingChanged = true; - } + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationURL, setAnimationURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationIsPlaying, setAnimationIsPlaying); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFrameIndex, setAnimationFrameIndex); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFPS, setAnimationFPS); if (somethingChanged) { bool wantDebug = false; @@ -169,9 +148,11 @@ int ModelEntityItem::oldVersionReadEntityDataFromBuffer(const unsigned char* dat << "old ID=" << oldID << "new ID=" << _id; // radius - memcpy(&_radius, dataAt, sizeof(_radius)); - dataAt += sizeof(_radius); - bytesRead += sizeof(_radius); + float radius; + memcpy(&radius, dataAt, sizeof(radius)); + dataAt += sizeof(radius); + bytesRead += sizeof(radius); + setRadius(radius); // position memcpy(&_position, dataAt, sizeof(_position)); @@ -393,7 +374,7 @@ void ModelEntityItem::debugDump() const { qDebug() << "ModelEntityItem id:" << getEntityItemID(); qDebug() << " edited ago:" << getEditedAgo(); qDebug() << " position:" << getPosition() * (float)TREE_SCALE; - qDebug() << " radius:" << getRadius() * (float)TREE_SCALE; + qDebug() << " dimensions:" << getDimensions() * (float)TREE_SCALE; qDebug() << " model URL:" << getModelURL(); } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index c8d2abf29a..4ed47d4894 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -54,10 +54,13 @@ public: const rgbColor& getColor() const { return _color; } xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } bool hasModel() const { return !_modelURL.isEmpty(); } + + static const QString DEFAULT_MODEL_URL; const QString& getModelURL() const { return _modelURL; } + bool hasAnimation() const { return !_animationURL.isEmpty(); } + static const QString DEFAULT_ANIMATION_URL; const QString& getAnimationURL() const { return _animationURL; } - QVector getSittingPoints() const { return _sittingPoints; } void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } void setColor(const xColor& value) { @@ -69,10 +72,14 @@ public: // model related properties void setModelURL(const QString& url) { _modelURL = url; } void setAnimationURL(const QString& url) { _animationURL = url; } + static const float DEFAULT_ANIMATION_FRAME_INDEX; void setAnimationFrameIndex(float value) { _animationFrameIndex = value; } + + static const bool DEFAULT_ANIMATION_IS_PLAYING; void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; } + + static const float DEFAULT_ANIMATION_FPS; void setAnimationFPS(float value) { _animationFPS = value; } - void setSittingPoints(QVector sittingPoints) { _sittingPoints = sittingPoints; } void mapJoints(const QStringList& modelJointNames); QVector getAnimationFrame(); @@ -92,7 +99,6 @@ protected: rgbColor _color; QString _modelURL; - QVector _sittingPoints; quint64 _lastAnimated; QString _animationURL; diff --git a/libraries/entities/src/MovingEntitiesOperator.cpp b/libraries/entities/src/MovingEntitiesOperator.cpp index d6ccb1aad4..e6b48da232 100644 --- a/libraries/entities/src/MovingEntitiesOperator.cpp +++ b/libraries/entities/src/MovingEntitiesOperator.cpp @@ -20,21 +20,53 @@ MovingEntitiesOperator::MovingEntitiesOperator(EntityTree* tree) : _changeTime(usecTimestampNow()), _foundOldCount(0), _foundNewCount(0), - _lookingCount(0) + _lookingCount(0), + _wantDebug(false) { } MovingEntitiesOperator::~MovingEntitiesOperator() { + if (_wantDebug) { + bool stopExecution = false; + qDebug() << "MovingEntitiesOperator::~MovingEntitiesOperator() -----------------------------"; + qDebug() << " _lookingCount:" << _lookingCount; + qDebug() << " _foundOldCount:" << _foundOldCount; + qDebug() << " _foundNewCount:" << _foundNewCount; + if (_foundOldCount < _lookingCount) { + qDebug() << " FAILURE: **** _foundOldCount < _lookingCount ******"; + stopExecution = true; + } + if (_foundNewCount < _lookingCount) { + qDebug() << " FAILURE: **** _foundNewCount < _lookingCount ******"; + stopExecution = true; + } + qDebug() << "--------------------------------------------------------------------------"; + if(stopExecution) { + debug(); + assert(false); + } + } } void MovingEntitiesOperator::addEntityToMoveList(EntityItem* entity, const AACube& oldCube, const AACube& newCube) { EntityTreeElement* oldContainingElement = _tree->getContainingElement(entity->getEntityItemID()); - AABox newBox = newCube.clamp(0.0f, 1.0f); + AABox newCubeClamped = newCube.clamp(0.0f, 1.0f); + AABox oldCubeClamped = oldCube.clamp(0.0f, 1.0f); + + if (_wantDebug) { + qDebug() << "MovingEntitiesOperator::addEntityToMoveList() -----------------------------"; + qDebug() << " oldCube:" << oldCube; + qDebug() << " newCube:" << newCube; + qDebug() << " oldCubeClamped:" << oldCubeClamped; + qDebug() << " newCubeClamped:" << newCubeClamped; + qDebug() << " oldContainingElement:" << oldContainingElement->getAACube(); + qDebug() << " oldContainingElement->bestFitBounds(newCubeClamped):" << oldContainingElement->bestFitBounds(newCubeClamped); + } // If the original containing element is the best fit for the requested newCube locations then // we don't actually need to add the entity for moving and we can short circuit all this work - if (!oldContainingElement->bestFitBounds(newBox)) { + if (!oldContainingElement->bestFitBounds(newCubeClamped)) { // check our tree, to determine if this entity is known EntityToMoveDetails details; details.oldContainingElement = oldContainingElement; @@ -44,9 +76,29 @@ void MovingEntitiesOperator::addEntityToMoveList(EntityItem* entity, const AACub details.newFound = false; details.oldCube = oldCube; details.newCube = newCube; - details.newBox = newBox; + details.oldCubeClamped = oldCubeClamped; + details.newCubeClamped = newCubeClamped; _entitiesToMove << details; _lookingCount++; + + if (_wantDebug) { + qDebug() << "MovingEntitiesOperator::addEntityToMoveList() -----------------------------"; + qDebug() << " details.entity:" << details.entity->getEntityItemID(); + qDebug() << " details.oldContainingElementCube:" << details.oldContainingElementCube; + qDebug() << " details.oldCube:" << details.oldCube; + qDebug() << " details.newCube:" << details.newCube; + qDebug() << " details.newCubeClamped:" << details.newCubeClamped; + qDebug() << " _lookingCount:" << _lookingCount; + qDebug() << "--------------------------------------------------------------------------"; + } + } else { + if (_wantDebug) { + qDebug() << " oldContainingElement->bestFitBounds(newCubeClamped) IS BEST FIT... NOTHING TO DO"; + } + } + + if (_wantDebug) { + qDebug() << "--------------------------------------------------------------------------"; } } @@ -58,11 +110,29 @@ bool MovingEntitiesOperator::shouldRecurseSubTree(OctreeElement* element) { // check the bounds if (_entitiesToMove.size() > 0) { AACube elementCube = element->getAACube(); + int detailIndex = 0; foreach(const EntityToMoveDetails& details, _entitiesToMove) { - if (elementCube.contains(details.oldCube) || elementCube.contains(details.newCube)) { + + if (_wantDebug) { + qDebug() << "MovingEntitiesOperator::shouldRecurseSubTree() details["<< detailIndex <<"]-----------------------------"; + qDebug() << " element:" << element->getAACube(); + qDebug() << " details.entity:" << details.entity->getEntityItemID(); + qDebug() << " details.oldContainingElementCube:" << details.oldContainingElementCube; + qDebug() << " details.oldCube:" << details.oldCube; + qDebug() << " details.newCube:" << details.newCube; + qDebug() << " details.newCubeClamped:" << details.newCubeClamped; + qDebug() << " elementCube.contains(details.oldCube)" << elementCube.contains(details.oldCube); + qDebug() << " elementCube.contains(details.newCube)" << elementCube.contains(details.newCube); + qDebug() << " elementCube.contains(details.oldCubeClamped)" << elementCube.contains(details.oldCubeClamped); + qDebug() << " elementCube.contains(details.newCubeClamped)" << elementCube.contains(details.newCubeClamped); + qDebug() << "--------------------------------------------------------------------------"; + } + + if (elementCube.contains(details.oldCubeClamped) || elementCube.contains(details.newCubeClamped)) { containsEntity = true; break; // if it contains at least one, we're good to go } + detailIndex++; } } return containsEntity; @@ -87,13 +157,36 @@ bool MovingEntitiesOperator::preRecursion(OctreeElement* element) { if (keepSearching && shouldRecurseSubTree(element)) { // check against each of our search entities + int detailIndex = 0; foreach(const EntityToMoveDetails& details, _entitiesToMove) { + + if (_wantDebug) { + qDebug() << "MovingEntitiesOperator::preRecursion() details["<< detailIndex <<"]-----------------------------"; + qDebug() << " entityTreeElement:" << entityTreeElement->getAACube(); + qDebug() << " entityTreeElement->bestFitBounds(details.newCube):" << entityTreeElement->bestFitBounds(details.newCube); + qDebug() << " details.entity:" << details.entity->getEntityItemID(); + qDebug() << " details.oldContainingElementCube:" << details.oldContainingElementCube; + qDebug() << " entityTreeElement:" << entityTreeElement; + qDebug() << " details.oldCube:" << details.oldCube; + qDebug() << " details.newCube:" << details.newCube; + qDebug() << " details.newCubeClamped:" << details.newCubeClamped; + qDebug() << " _lookingCount:" << _lookingCount; + qDebug() << " _foundOldCount:" << _foundOldCount; + qDebug() << "--------------------------------------------------------------------------"; + } + // If this is one of the old elements we're looking for, then ask it to remove the old entity if (!details.oldFound && entityTreeElement == details.oldContainingElement) { entityTreeElement->removeEntityItem(details.entity); _foundOldCount++; //details.oldFound = true; // TODO: would be nice to add this optimization + if (_wantDebug) { + qDebug() << "MovingEntitiesOperator::preRecursion() -----------------------------"; + qDebug() << " FOUND OLD - REMOVING"; + qDebug() << " entityTreeElement == details.oldContainingElement"; + qDebug() << "--------------------------------------------------------------------------"; + } } // If this element is the best fit for the new bounds of this entity then add the entity to the element @@ -103,7 +196,14 @@ bool MovingEntitiesOperator::preRecursion(OctreeElement* element) { _tree->setContainingElement(entityItemID, entityTreeElement); _foundNewCount++; //details.newFound = true; // TODO: would be nice to add this optimization + if (_wantDebug) { + qDebug() << "MovingEntitiesOperator::preRecursion() -----------------------------"; + qDebug() << " FOUND NEW - ADDING"; + qDebug() << " entityTreeElement->bestFitBounds(details.newCube)"; + qDebug() << "--------------------------------------------------------------------------"; + } } + detailIndex++; } // if we haven't found all of our search for entities, then keep looking keepSearching = (_foundOldCount < _lookingCount) || (_foundNewCount < _lookingCount); @@ -163,9 +263,9 @@ OctreeElement* MovingEntitiesOperator::possiblyCreateChildAt(OctreeElement* elem foreach(const EntityToMoveDetails& details, _entitiesToMove) { // if the scale of our desired cube is smaller than our children, then consider making a child - if (details.newBox.getLargestDimension() <= childElementScale) { + if (details.newCubeClamped.getLargestDimension() <= childElementScale) { - int indexOfChildContainingNewEntity = element->getMyChildContaining(details.newBox); + int indexOfChildContainingNewEntity = element->getMyChildContaining(details.newCubeClamped); // If the childIndex we were asked if we wanted to create contains this newCube, // then we will create this branch and continue. We can exit this loop immediately diff --git a/libraries/entities/src/MovingEntitiesOperator.h b/libraries/entities/src/MovingEntitiesOperator.h index 047cc0c52b..fbec898cec 100644 --- a/libraries/entities/src/MovingEntitiesOperator.h +++ b/libraries/entities/src/MovingEntitiesOperator.h @@ -17,7 +17,8 @@ public: EntityItem* entity; AACube oldCube; AACube newCube; - AABox newBox; + AABox oldCubeClamped; + AABox newCubeClamped; EntityTreeElement* oldContainingElement; AACube oldContainingElementCube; bool oldFound; @@ -50,6 +51,8 @@ private: int _foundNewCount; int _lookingCount; bool shouldRecurseSubTree(OctreeElement* element); + + bool _wantDebug; }; #endif // hifi_MovingEntitiesOperator_h diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 836cc24524..09364ddbfe 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -43,15 +43,7 @@ EntityItemProperties SphereEntityItem::getProperties() const { bool SphereEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) { bool somethingChanged = EntityItem::setProperties(properties, forceCopy); // set the properties in our base class - if (properties.colorChanged() || forceCopy) { - setColor(properties.getColor()); - somethingChanged = true; - } - - if (properties.glowLevelChanged() || forceCopy) { - setGlowLevel(properties.getGlowLevel()); - somethingChanged = true; - } + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); if (somethingChanged) { bool wantDebug = false; diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index 65a86a80ca..284d8e9e24 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -31,23 +31,27 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, _dontMove(false), // assume we'll be moving _changeTime(usecTimestampNow()), _oldEntityCube(), - _newEntityCube() + _newEntityCube(), + _wantDebug(false) { // caller must have verified existence of containingElement and oldEntity assert(_containingElement && _existingEntity); - _oldEntityCube = _existingEntity->getAACube(); + // Here we have a choice to make, do we want to "tight fit" the actual minimum for the + // entity into the the element, or do we want to use the entities "relaxed" bounds + // which can handle all potential rotations? + // the getMaximumAACube is the relaxed form. + _oldEntityCube = _existingEntity->getMaximumAACube(); _oldEntityBox = _oldEntityCube.clamp(0.0f, 1.0f); // clamp to domain bounds - // If the new properties has position OR radius changes, but not both, we need to + // If the new properties has position OR dimension changes, but not both, we need to // get the old property value and set it in our properties in order for our bounds // calculations to work. - if (_properties.containsPositionChange() && !_properties.containsRadiusChange()) { - float oldRadiusInMeters = _existingEntity->getRadius() * (float)TREE_SCALE; - _properties.setRadius(oldRadiusInMeters); + if (_properties.containsPositionChange() && !_properties.containsDimensionsChange()) { + glm::vec3 oldDimensionsInMeters = _existingEntity->getDimensions() * (float)TREE_SCALE; + _properties.setDimensions(oldDimensionsInMeters); } - - if (!_properties.containsPositionChange() && _properties.containsRadiusChange()) { + if (!_properties.containsPositionChange() && _properties.containsDimensionsChange()) { glm::vec3 oldPositionInMeters = _existingEntity->getPosition() * (float)TREE_SCALE; _properties.setPosition(oldPositionInMeters); } @@ -72,11 +76,24 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, _newEntityCube = _oldEntityCube; _dontMove = true; } else { - _newEntityCube = _properties.getAACubeTreeUnits(); + _newEntityCube = _properties.getMaximumAACubeInTreeUnits(); _removeOld = true; // our properties are going to move us, so remember this for later processing } _newEntityBox = _newEntityCube.clamp(0.0f, 1.0f); // clamp to domain bounds + + + if (_wantDebug) { + qDebug() << "UpdateEntityOperator::UpdateEntityOperator() -----------------------------"; + qDebug() << " _entityItemID:" << _entityItemID; + qDebug() << " _containingElementCube:" << _containingElementCube; + qDebug() << " _oldEntityCube:" << _oldEntityCube; + qDebug() << " _oldEntityBox:" << _oldEntityBox; + qDebug() << " _newEntityCube:" << _newEntityCube; + qDebug() << " _newEntityBox:" << _newEntityBox; + qDebug() << "--------------------------------------------------------------------------"; + } + } diff --git a/libraries/entities/src/UpdateEntityOperator.h b/libraries/entities/src/UpdateEntityOperator.h index 0aef5927b2..8d40ddfd57 100644 --- a/libraries/entities/src/UpdateEntityOperator.h +++ b/libraries/entities/src/UpdateEntityOperator.h @@ -42,6 +42,8 @@ private: bool subTreeContainsOldEntity(OctreeElement* element); bool subTreeContainsNewEntity(OctreeElement* element); + + bool _wantDebug; }; #endif // hifi_UpdateEntityOperator_h diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 7fda9d74c9..1100371ac9 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -292,8 +292,7 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { if (callbackParams.errorCallbackReceiver) { // invoke the right method on the callback receiver QMetaObject::invokeMethod(callbackParams.errorCallbackReceiver, qPrintable(callbackParams.errorCallbackMethod), - Q_ARG(QNetworkReply::NetworkError, requestReply->error()), - Q_ARG(const QString&, requestReply->errorString())); + Q_ARG(QNetworkReply&, *requestReply)); // remove the related reply-callback group from the map _pendingCallbackMap.remove(requestReply); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp new file mode 100644 index 0000000000..f2e9ce64ab --- /dev/null +++ b/libraries/networking/src/AddressManager.cpp @@ -0,0 +1,244 @@ +// +// AddressManager.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-09-10. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +#include + +#include "AddressManager.h" + +AddressManager& AddressManager::getInstance() { + static AddressManager sharedInstance; + return sharedInstance; +} + +QString AddressManager::pathForPositionAndOrientation(const glm::vec3& position, bool hasOrientation, + const glm::quat& orientation) { + + QString pathString = "/" + createByteArray(position); + + if (hasOrientation) { + QString orientationString = createByteArray(glm::degrees(safeEulerAngles(orientation))); + pathString += "/" + orientationString; + } + + return pathString; +} + +const JSONCallbackParameters& AddressManager::apiCallbackParameters() { + static bool hasSetupParameters = false; + static JSONCallbackParameters callbackParams; + + if (!hasSetupParameters) { + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "handleAPIResponse"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "handleAPIError"; + } + + return callbackParams; +} + +bool AddressManager::handleUrl(const QUrl& lookupUrl) { + if (lookupUrl.scheme() == HIFI_URL_SCHEME) { + + // there are 4 possible lookup strings + + // 1. global place name (name of domain or place) - example: sanfrancisco + // 2. user name (prepended with @) - example: @philip + // 3. location string (posX,posY,posZ/eulerX,eulerY,eulerZ) + // 4. domain network address (IP or dns resolvable hostname) + + qDebug() << lookupUrl; + + if (lookupUrl.isRelative()) { + // if this is a relative path then handle it as a relative viewpoint + handleRelativeViewpoint(lookupUrl.path()); + } else { + // use our regex'ed helpers to figure out what we're supposed to do with this + if (!handleUsername(lookupUrl.authority())) { + // we're assuming this is either a network address or global place name + // check if it is a network address first + if (!handleNetworkAddress(lookupUrl.host())) { + // wasn't an address - lookup the place name + attemptPlaceNameLookup(lookupUrl.host()); + } + + // we may have a path that defines a relative viewpoint - if so we should jump to that now + handleRelativeViewpoint(lookupUrl.path()); + } + } + + return true; + } + + return false; +} + +void AddressManager::handleLookupString(const QString& lookupString) { + // we've verified that this is a valid hifi URL - hand it off to handleLookupString + QString sanitizedString = lookupString; + const QRegExp HIFI_SCHEME_REGEX = QRegExp(HIFI_URL_SCHEME + ":\\/{1,2}", Qt::CaseInsensitive); + sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX); + + handleUrl(QUrl(HIFI_URL_SCHEME + "://" + sanitizedString)); +} + +void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) { + QJsonObject dataObject = jsonObject["data"].toObject(); + + const QString ADDRESS_API_DOMAIN_KEY = "domain"; + const QString ADDRESS_API_ONLINE_KEY = "online"; + + if (!dataObject.contains(ADDRESS_API_ONLINE_KEY) + || dataObject[ADDRESS_API_ONLINE_KEY].toBool()) { + + if (dataObject.contains(ADDRESS_API_DOMAIN_KEY)) { + QJsonObject domainObject = dataObject[ADDRESS_API_DOMAIN_KEY].toObject(); + + const QString DOMAIN_NETWORK_ADDRESS_KEY = "network_address"; + QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); + + emit possibleDomainChangeRequired(domainHostname); + + // take the path that came back + const QString LOCATION_KEY = "location"; + const QString LOCATION_PATH_KEY = "path"; + QString returnedPath; + + if (domainObject.contains(LOCATION_PATH_KEY)) { + returnedPath = domainObject[LOCATION_PATH_KEY].toString(); + } else if (domainObject.contains(LOCATION_KEY)) { + returnedPath = domainObject[LOCATION_KEY].toObject()[LOCATION_PATH_KEY].toString(); + } + + if (!returnedPath.isEmpty()) { + // try to parse this returned path as a viewpoint, that's the only thing it could be for now + if (!handleRelativeViewpoint(returnedPath)) { + qDebug() << "Received a location path that was could not be handled as a viewpoint -" << returnedPath; + } + } + + } else { + qDebug() << "Received an address manager API response with no domain key. Cannot parse."; + qDebug() << jsonObject; + } + } else { + // we've been told that this result exists but is offline, emit our signal so the application can handle + emit lookupResultIsOffline(); + } +} + +void AddressManager::handleAPIError(QNetworkReply& errorReply) { + qDebug() << "AddressManager API error -" << errorReply.error() << "-" << errorReply.errorString(); +} + +const QString GET_PLACE = "/api/v1/places/%1"; + +void AddressManager::attemptPlaceNameLookup(const QString& lookupString) { + // assume this is a place name and see if we can get any info on it + QString placeName = QUrl::toPercentEncoding(lookupString); + AccountManager::getInstance().authenticatedRequest(GET_PLACE.arg(placeName), + QNetworkAccessManager::GetOperation, + apiCallbackParameters()); +} + +bool AddressManager::handleNetworkAddress(const QString& lookupString) { + const QString IP_ADDRESS_REGEX_STRING = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(:\\d{1,5})?$"; + + const QString HOSTNAME_REGEX_STRING = "^((?:[A-Z0-9]|[A-Z0-9][A-Z0-9\\-]{0,61}[A-Z0-9])" + "(?:\\.(?:[A-Z0-9]|[A-Z0-9][A-Z0-9\\-]{0,61}[A-Z0-9]))+|localhost)(:{1}\\d{1,5})?$"; + + QRegExp hostnameRegex(HOSTNAME_REGEX_STRING, Qt::CaseInsensitive); + + if (hostnameRegex.indexIn(lookupString) != -1) { + emit possibleDomainChangeRequired(hostnameRegex.cap(0)); + return true; + } + + QRegExp ipAddressRegex(IP_ADDRESS_REGEX_STRING); + + if (ipAddressRegex.indexIn(lookupString) != -1) { + emit possibleDomainChangeRequired(ipAddressRegex.cap(0)); + return true; + } + + return false; +} + +bool AddressManager::handleRelativeViewpoint(const QString& lookupString) { + const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; + const QString TRIPLE_FLOAT_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + "\\s*,\\s*" + + FLOAT_REGEX_STRING + "\\s*,\\s*" + FLOAT_REGEX_STRING + "\\s*(?:$|\\/)"; + + QRegExp tripleFloatRegex(TRIPLE_FLOAT_REGEX_STRING); + + if (tripleFloatRegex.indexIn(lookupString) != -1) { + // we have at least a position, so emit our signal to say we need to change position + glm::vec3 newPosition(tripleFloatRegex.cap(1).toFloat(), + tripleFloatRegex.cap(2).toFloat(), + tripleFloatRegex.cap(3).toFloat()); + + if (!isNaN(newPosition.x) && !isNaN(newPosition.y) && !isNaN(newPosition.z)) { + glm::vec3 newOrientation; + // we may also have an orientation + if (lookupString[tripleFloatRegex.matchedLength() - 1] == QChar('/') + && tripleFloatRegex.indexIn(lookupString, tripleFloatRegex.matchedLength() - 1) != -1) { + + glm::vec3 newOrientation(tripleFloatRegex.cap(1).toFloat(), + tripleFloatRegex.cap(2).toFloat(), + tripleFloatRegex.cap(3).toFloat()); + + if (!isNaN(newOrientation.x) && !isNaN(newOrientation.y) && !isNaN(newOrientation.z)) { + emit locationChangeRequired(newPosition, true, newOrientation); + return true; + } else { + qDebug() << "Orientation parsed from lookup string is invalid. Will not use for location change."; + } + } + + emit locationChangeRequired(newPosition, false, newOrientation); + + } else { + qDebug() << "Could not jump to position from lookup string because it has an invalid value."; + } + + return true; + } + + return false; +} + +const QString GET_USER_LOCATION = "/api/v1/users/%1/location"; + +bool AddressManager::handleUsername(const QString& lookupString) { + const QString USERNAME_REGEX_STRING = "^@(\\S+)"; + + QRegExp usernameRegex(USERNAME_REGEX_STRING); + + if (usernameRegex.indexIn(lookupString) != -1) { + goToUser(usernameRegex.cap(1)); + return true; + } + + return false; +} + +void AddressManager::goToUser(const QString& username) { + QString formattedUsername = QUrl::toPercentEncoding(username); + // this is a username - pull the captured name and lookup that user's location + AccountManager::getInstance().authenticatedRequest(GET_USER_LOCATION.arg(formattedUsername), + QNetworkAccessManager::GetOperation, + apiCallbackParameters()); +} diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h new file mode 100644 index 0000000000..f27fb475c2 --- /dev/null +++ b/libraries/networking/src/AddressManager.h @@ -0,0 +1,53 @@ +// +// AddressManager.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-09-10. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AddressManager_h +#define hifi_AddressManager_h + +#include + +#include +#include + +#include "AccountManager.h" + +static const QString HIFI_URL_SCHEME = "hifi"; + +class AddressManager : public QObject { + Q_OBJECT +public: + static AddressManager& getInstance(); + + static QString pathForPositionAndOrientation(const glm::vec3& position, bool hasOrientation = false, + const glm::quat& orientation = glm::quat()); + + void attemptPlaceNameLookup(const QString& lookupString); +public slots: + void handleLookupString(const QString& lookupString); + + void handleAPIResponse(const QJsonObject& jsonObject); + void handleAPIError(QNetworkReply& errorReply); + void goToUser(const QString& username); +signals: + void lookupResultIsOffline(); + void possibleDomainChangeRequired(const QString& newHostname); + void locationChangeRequired(const glm::vec3& newPosition, bool hasOrientationChange, const glm::vec3& newOrientation); +private: + const JSONCallbackParameters& apiCallbackParameters(); + + bool handleUrl(const QUrl& lookupUrl); + + bool handleNetworkAddress(const QString& lookupString); + bool handleRelativeViewpoint(const QString& pathSubsection); + bool handleUsername(const QString& lookupString); +}; + +#endif // hifi_AddressManager_h \ No newline at end of file diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 91166129ad..9c0ae55d7e 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -73,6 +73,22 @@ void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hos _hostname = hostname; } +void DomainHandler::setUUID(const QUuid& uuid) { + if (uuid != _uuid) { + _uuid = uuid; + qDebug() << "Domain uuid changed to" << uuidStringWithoutCurlyBraces(_uuid); + } +} + +QString DomainHandler::hostnameWithoutPort(const QString& hostname) { + int colonIndex = hostname.indexOf(':'); + return colonIndex > 0 ? hostname.left(colonIndex) : hostname; +} + +bool DomainHandler::isCurrentHostname(const QString& hostname) { + return hostnameWithoutPort(hostname) == _hostname; +} + void DomainHandler::setHostname(const QString& hostname) { if (hostname != _hostname) { diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 91caddca22..bfdb5d7f38 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -37,10 +37,11 @@ public: void clearSettings(); const QUuid& getUUID() const { return _uuid; } - void setUUID(const QUuid& uuid) { _uuid = uuid; } + void setUUID(const QUuid& uuid); + static QString hostnameWithoutPort(const QString& hostname); + bool isCurrentHostname(const QString& hostname); const QString& getHostname() const { return _hostname; } - void setHostname(const QString& hostname); const QHostAddress& getIP() const { return _sockAddr.getAddress(); } void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } @@ -63,6 +64,8 @@ public: void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket); void softReset(); +public slots: + void setHostname(const QString& hostname); private slots: void completedHostnameLookup(const QHostInfo& hostInfo); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index d77ef321a8..79a399c621 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -397,7 +397,10 @@ int NodeList::processDomainServerList(const QByteArray& packet) { _numNoReplyDomainCheckIns = 0; // if this was the first domain-server list from this domain, we've now connected - _domainHandler.setIsConnected(true); + if (!_domainHandler.isConnected()) { + _domainHandler.setUUID(uuidFromPacketHeader(packet)); + _domainHandler.setIsConnected(true); + } int readNodes = 0; diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index f3937dd2e3..5513c09a61 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -75,8 +75,11 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeParticleErase: return 1; + + case PacketTypeEntityAddOrEdit: case PacketTypeEntityData: - return VERSION_ENTITIES_SUPPORT_SPLIT_MTU; + return VERSION_ENTITIES_SUPPORT_DIMENSIONS; + case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index bb64388dd6..207cf680d3 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -117,6 +117,7 @@ const PacketVersion VERSION_ENTITIES_HAVE_ANIMATION = 1; const PacketVersion VERSION_ROOT_ELEMENT_HAS_DATA = 2; const PacketVersion VERSION_ENTITIES_SUPPORT_SPLIT_MTU = 3; const PacketVersion VERSION_ENTITIES_HAS_FILE_BREAKS = VERSION_ENTITIES_SUPPORT_SPLIT_MTU; +const PacketVersion VERSION_ENTITIES_SUPPORT_DIMENSIONS = 4; const PacketVersion VERSION_VOXELS_HAS_FILE_BREAKS = 1; #endif // hifi_PacketHeaders_h diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index e2d3434867..549f02ae3c 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -73,8 +73,8 @@ void UserActivityLogger::requestFinished(const QJsonObject& object) { // qDebug() << object; } -void UserActivityLogger::requestError(QNetworkReply::NetworkError error,const QString& string) { - qDebug() << error << ": " << string; +void UserActivityLogger::requestError(QNetworkReply& errorReply) { + qDebug() << errorReply.error() << "-" << errorReply.errorString(); } void UserActivityLogger::launch(QString applicationVersion) { diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 7e8abe9fb2..1bd966d632 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -40,7 +40,7 @@ public slots: private slots: void requestFinished(const QJsonObject& object); - void requestError(QNetworkReply::NetworkError error,const QString& string); + void requestError(QNetworkReply& errorReply); private: UserActivityLogger(); diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp index 9552129e29..1e8dc41cdd 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -392,6 +392,41 @@ ViewFrustum::location ViewFrustum::cubeInFrustum(const AACube& cube) const { return regularResult; } +ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const { + + ViewFrustum::location regularResult = INSIDE; + ViewFrustum::location keyholeResult = OUTSIDE; + + // If we have a keyholeRadius, check that first, since it's cheaper + if (_keyholeRadius >= 0.0f) { + keyholeResult = boxInKeyhole(box); + } + if (keyholeResult == INSIDE) { + return keyholeResult; + } + + // TODO: These calculations are expensive, taking up 80% of our time in this function. + // This appears to be expensive because we have to test the distance to each plane. + // One suggested optimization is to first check against the approximated cone. We might + // also be able to test against the cone to the bounding sphere of the box. + for(int i=0; i < 6; i++) { + const glm::vec3& normal = _planes[i].getNormal(); + const glm::vec3& boxVertexP = box.getVertexP(normal); + float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP); + + const glm::vec3& boxVertexN = box.getVertexN(normal); + float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN); + + if (planeToBoxVertexPDistance < 0) { + // This is outside the regular frustum, so just return the value from checking the keyhole + return keyholeResult; + } else if (planeToBoxVertexNDistance < 0) { + regularResult = INTERSECT; + } + } + return regularResult; +} + bool testMatches(glm::quat lhs, glm::quat rhs, float epsilon = EPSILON) { return (fabs(lhs.x - rhs.x) <= epsilon && fabs(lhs.y - rhs.y) <= epsilon && fabs(lhs.z - rhs.z) <= epsilon && fabs(lhs.w - rhs.w) <= epsilon); diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp index 0e6a27bc42..75b3eba1a3 100644 --- a/libraries/script-engine/src/EventTypes.cpp +++ b/libraries/script-engine/src/EventTypes.cpp @@ -253,7 +253,9 @@ void keyEventFromScriptValue(const QScriptValue& object, KeyEvent& event) { } else if (event.text.toUpper() == "CAPS LOCK") { event.key = Qt::Key_CapsLock; } else { - event.key = event.text.at(0).unicode(); + // Key values do not distinguish between uppercase and lowercase + // and use the uppercase key value. + event.key = event.text.toUpper().at(0).unicode(); } event.isValid = true; } diff --git a/libraries/shared/src/Extents.h b/libraries/shared/src/Extents.h index c0e68bd2b1..2da3042467 100644 --- a/libraries/shared/src/Extents.h +++ b/libraries/shared/src/Extents.h @@ -35,6 +35,10 @@ public: /// \return whether or not the extents are empty bool isEmpty() const { return minimum == maximum; } bool isValid() const { return !((minimum == glm::vec3(FLT_MAX)) && (maximum == glm::vec3(-FLT_MAX))); } + + /// \param vec3 for delta amount to shift the extents by + /// \return true if point is within current limits + void shiftBy(const glm::vec3& delta) { minimum += delta; maximum += delta; } /// rotate the extents around orign by rotation void rotate(const glm::quat& rotation); diff --git a/tests/octree/src/ModelTests.cpp b/tests/octree/src/ModelTests.cpp index b91963726f..405b4a95d3 100644 --- a/tests/octree/src/ModelTests.cpp +++ b/tests/octree/src/ModelTests.cpp @@ -45,7 +45,7 @@ void EntityTests::entityTreeTests(bool verbose) { entityID.isKnownID = false; // this is a temporary workaround to allow local tree entities to be added with known IDs EntityItemProperties properties; float oneMeter = 1.0f; - float halfMeter = oneMeter / 2.0f; + //float halfMeter = oneMeter / 2.0f; float halfOfDomain = TREE_SCALE * 0.5f; glm::vec3 positionNearOriginInMeters(oneMeter, oneMeter, oneMeter); // when using properties, these are in meter not tree units glm::vec3 positionAtCenterInMeters(halfOfDomain, halfOfDomain, halfOfDomain); @@ -60,7 +60,8 @@ void EntityTests::entityTreeTests(bool verbose) { } properties.setPosition(positionAtCenterInMeters); - properties.setRadius(halfMeter); + // TODO: Fix these unit tests. + //properties.setRadius(halfMeter); //properties.setModelURL("https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/theater.fbx"); tree.addEntity(entityID, properties); @@ -265,8 +266,10 @@ void EntityTests::entityTreeTests(bool verbose) { glm::vec3 randomPositionInTreeUnits = randomPositionInMeters / (float)TREE_SCALE; properties.setPosition(randomPositionInMeters); - properties.setRadius(halfMeter); -//properties.setModelURL("https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/theater.fbx"); + + // TODO: fix these unit tests + //properties.setRadius(halfMeter); + //properties.setModelURL("https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/theater.fbx"); if (extraVerbose) { qDebug() << "iteration:" << i