diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 314ffb28e4..89b3102391 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -311,7 +311,7 @@ MetavoxelPersister::MetavoxelPersister(MetavoxelServer* server) : const char* SAVE_FILE = "/resources/metavoxels.dat"; const int FILE_MAGIC = 0xDADAFACE; -const int FILE_VERSION = 1; +const int FILE_VERSION = 2; void MetavoxelPersister::load() { QString path = QCoreApplication::applicationDirPath() + SAVE_FILE; diff --git a/examples/butterflies.js b/examples/butterflies.js index 8055e5b7d9..edc33f7625 100644 --- a/examples/butterflies.js +++ b/examples/butterflies.js @@ -13,18 +13,13 @@ // -var numButterflies = 20; +var numButterflies = 25; function getRandomFloat(min, max) { return Math.random() * (max - min) + min; } -// Multiply vector by scalar -function vScalarMult(v, s) { - var rval = { x: v.x * s, y: v.y * s, z: v.z * s }; - return rval; -} // Create a random vector with individual lengths between a,b function randVector(a, b) { @@ -32,50 +27,36 @@ function randVector(a, b) { return rval; } -// Returns a vector which is fraction of the way between a and b -function vInterpolate(a, b, fraction) { - var rval = { x: a.x + (b.x - a.x) * fraction, y: a.y + (b.y - a.y) * fraction, z: a.z + (b.z - a.z) * fraction }; - return rval; -} - var startTimeInSeconds = new Date().getTime() / 1000; -var NATURAL_SIZE_OF_BUTTERFLY = { x: 1.76, y: 0.825, z: 0.20 }; -var lifeTime = 600; // lifetime of the butterflies in seconds -var range = 3.0; // Over what distance in meters do you want the flock to fly around +var NATURAL_SIZE_OF_BUTTERFLY = { x: 1.0, y: 0.4, z: 0.2 }; + +var lifeTime = 3600; // One hour lifespan +var range = 7.0; // Over what distance in meters do you want the flock to fly around var frame = 0; -var CHANCE_OF_MOVING = 0.9; -var BUTTERFLY_GRAVITY = 0; -var BUTTERFLY_FLAP_SPEED = 0.5; -var BUTTERFLY_VELOCITY = 0.55; var DISTANCE_IN_FRONT_OF_ME = 1.5; var DISTANCE_ABOVE_ME = 1.5; -var flockPosition = Vec3.sum(MyAvatar.position,Vec3.sum( +var FIXED_LOCATION = false; + +if (!FIXED_LOCATION) { + var flockPosition = Vec3.sum(MyAvatar.position,Vec3.sum( Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_ABOVE_ME), Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_IN_FRONT_OF_ME))); - +} else { + var flockPosition = { x: 4999.6, y: 4986.5, z: 5003.5 }; +} -// set these pitch, yaw, roll to the needed values to orient the model as you want it -var pitchInDegrees = 270.0; -var yawInDegrees = 0.0; -var rollInDegrees = 0.0; -var pitchInRadians = pitchInDegrees / 180.0 * Math.PI; -var yawInRadians = yawInDegrees / 180.0 * Math.PI; -var rollInRadians = rollInDegrees / 180.0 * Math.PI; - -var rotation = Quat.fromPitchYawRollDegrees(pitchInDegrees, yawInDegrees, rollInDegrees);//experimental // This is our butterfly object function defineButterfly(entityID, targetPosition) { this.entityID = entityID; - this.previousFlapOffset = 0; this.targetPosition = targetPosition; - this.moving = false; } // Array of butterflies var butterflies = []; + function addButterfly() { // Decide the size of butterfly var color = { red: 100, green: 100, blue: 100 }; @@ -88,26 +69,24 @@ function addButterfly() { size = MINSIZE + Math.random() * RANGESIZE; 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), - Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_IN_FRONT_OF_ME))); - + + var GRAVITY = -0.2; + var newFrameRate = 20 + Math.random() * 30; var properties = { type: "Model", lifetime: lifeTime, position: Vec3.sum(randVector(-range, range), flockPosition), - velocity: { x: 0, y: 0.0, z: 0 }, - gravity: { x: 0, y: 1.0, z: 0 }, - damping: 0.1, + rotation: Quat.fromPitchYawRollDegrees(-80 + Math.random() * 20, Math.random() * 360.0, 0.0), + velocity: { x: 0, y: 0, z: 0 }, + gravity: { x: 0, y: GRAVITY, z: 0 }, + damping: 0.9999, dimensions: dimensions, color: color, - rotation: rotation, animationURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/content/butterfly/butterfly.fbx", - animationIsPlaying: true, + animationSettings: "{\"firstFrame\":0,\"fps\":" + newFrameRate + ",\"frameIndex\":0,\"hold\":false,\"lastFrame\":10000,\"loop\":true,\"running\":true,\"startAutomatically\":false}", modelURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/content/butterfly/butterfly.fbx" }; - butterflies.push(new defineButterfly(Entities.addEntity(properties), properties.position)); + butterflies.push(Entities.addEntity(properties)); } // Generate the butterflies @@ -116,117 +95,37 @@ for (var i = 0; i < numButterflies; i++) { } // Main update function -function updateButterflies(deltaTime) { - // Check to see if we've been running long enough that our butterflies are dead - var nowTimeInSeconds = new Date().getTime() / 1000; - if ((nowTimeInSeconds - startTimeInSeconds) >= lifeTime) { - Script.stop(); - return; - } - +function updateButterflies(deltaTime) { frame++; // Only update every third frame because we don't need to do it too quickly if ((frame % 3) == 0) { - flockPosition = Vec3.sum(MyAvatar.position,Vec3.sum(Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_ABOVE_ME), - Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_IN_FRONT_OF_ME))); - // Update all the butterflies + var CHANCE_OF_IMPULSE = 0.04; for (var i = 0; i < numButterflies; i++) { - entityID = Entities.identifyEntity(butterflies[i].entityID); - butterflies[i].entityID = entityID; - var properties = Entities.getEntityProperties(entityID); - - if (properties.position.y > flockPosition.y + getRandomFloat(0.0,0.3)){ //0.3 //ceiling - properties.gravity.y = - 3.0; - properties.damping.y = 1.0; - properties.velocity.y = 0; - properties.velocity.x = properties.velocity.x; - properties.velocity.z = properties.velocity.z; - if (properties.velocity.x < 0.5){ - butterflies[i].moving = false; + if (Math.random() < CHANCE_OF_IMPULSE) { + if (!butterflies[i].isKnownID) { + butterflies[i] = Entities.identifyEntity(butterflies[i]); } - if (properties.velocity.z < 0.5){ - butterflies[i].moving = false; - } - } - - if (properties.velocity.y <= -0.2) { - properties.velocity.y = 0.22; - properties.velocity.x = properties.velocity.x; - properties.velocity.z = properties.velocity.z; - } - - if (properties.position.y < flockPosition.y - getRandomFloat(0.0,0.3)) { //-0.3 // floor - properties.velocity.y = 0.9; - properties.gravity.y = - 4.0; - properties.velocity.x = properties.velocity.x; - properties.velocity.z = properties.velocity.z; - if (properties.velocity.x < 0.5){ - butterflies[i].moving = false; - } - if (properties.velocity.z < 0.5){ - butterflies[i].moving = false; - } - } - - - // Begin movement by getting a target - if (butterflies[i].moving == false) { - if (Math.random() < CHANCE_OF_MOVING) { - var targetPosition = Vec3.sum(randVector(-range, range), flockPosition); - if (targetPosition.x < 0) { - targetPosition.x = 0; - } - if (targetPosition.y < 0) { - targetPosition.y = 0; - } - if (targetPosition.z < 0) { - targetPosition.z = 0; - } - if (targetPosition.x > TREE_SCALE) { - targetPosition.x = TREE_SCALE; - } - if (targetPosition.y > TREE_SCALE) { - targetPosition.y = TREE_SCALE; - } - if (targetPosition.z > TREE_SCALE) { - targetPosition.z = TREE_SCALE; - } - butterflies[i].targetPosition = targetPosition; - butterflies[i].moving = true; + var properties = Entities.getEntityProperties(butterflies[i]); + if (Vec3.length(Vec3.subtract(properties.position, flockPosition)) > range) { + Entities.editEntity(butterflies[i], { position: flockPosition } ); + } else if (properties.velocity.y < 0.0) { + // If falling, Create a new direction and impulse + var HORIZ_SCALE = 0.50; + var VERT_SCALE = 0.50; + var newHeading = Math.random() * 360.0; + var newVelocity = Vec3.multiply(HORIZ_SCALE, Quat.getFront(Quat.fromPitchYawRollDegrees(0.0, newHeading, 0.0))); + newVelocity.y = (Math.random() + 0.5) * VERT_SCALE; + Entities.editEntity(butterflies[i], { rotation: Quat.fromPitchYawRollDegrees(-80 + Math.random() * 20, newHeading, (Math.random() - 0.5) * 10), + velocity: newVelocity } ); } } - - // If we are moving, move towards the target - if (butterflies[i].moving) { - - var holding = properties.velocity.y; - - var desiredVelocity = Vec3.subtract(butterflies[i].targetPosition, properties.position); - desiredVelocity = vScalarMult(Vec3.normalize(desiredVelocity), BUTTERFLY_VELOCITY); - - properties.velocity = vInterpolate(properties.velocity, desiredVelocity, 0.5); - properties.velocity.y = holding ; - - - // If we are near the target, we should get a new target - var halfLargestDimension = Vec3.length(properties.dimensions) / 2.0; - if (Vec3.length(Vec3.subtract(properties.position, butterflies[i].targetPosition)) < (halfLargestDimension)) { - butterflies[i].moving = false; - } - - var yawRads = Math.atan2(properties.velocity.z, properties.velocity.x); - yawRads = yawRads + Math.PI / 2.0; - var newOrientation = Quat.fromPitchYawRollRadians(pitchInRadians, yawRads, rollInRadians); - properties.rotation = newOrientation; - } - - // Use a cosine wave offset to make it look like its flapping. - 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; - Entities.editEntity(entityID, properties); + } + // Check to see if we've been running long enough that our butterflies are dead + var nowTimeInSeconds = new Date().getTime() / 1000; + if ((nowTimeInSeconds - startTimeInSeconds) >= lifeTime) { + Script.stop(); + return; } } } @@ -237,6 +136,6 @@ Script.update.connect(updateButterflies); // Delete our little friends if script is stopped Script.scriptEnding.connect(function() { for (var i = 0; i < numButterflies; i++) { - Entities.deleteEntity(butterflies[i].entityID); + Entities.deleteEntity(butterflies[i]); } }); \ No newline at end of file diff --git a/examples/developerMenuItems.js b/examples/developerMenuItems.js index 221975c9c8..34bd3b3a75 100644 --- a/examples/developerMenuItems.js +++ b/examples/developerMenuItems.js @@ -19,14 +19,17 @@ function setupMenus() { if (!Menu.menuExists("Developer > Entities")) { Menu.addMenu("Developer > Entities"); Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Bounds", isCheckable: true, isChecked: false }); + Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Triangles", isCheckable: true, isChecked: false }); Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Element Bounds", isCheckable: true, isChecked: false }); Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Element Children", isCheckable: true, isChecked: false }); - Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Disable Light Entities", isCheckable: true, isChecked: false }); + Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Do Precision Picking", isCheckable: true, isChecked: false }); Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Attempt to Reduce Material Switches", isCheckable: true, isChecked: false }); Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Attempt Render Entities as Scene", isCheckable: true, isChecked: false }); + Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Do Precision Picking", isCheckable: true, isChecked: false }); Menu.addMenu("Developer > Entities > Culling"); Menu.addMenuItem({ menuName: "Developer > Entities > Culling", menuItemName: "Don't Cull Out Of View Mesh Parts", isCheckable: true, isChecked: false }); Menu.addMenuItem({ menuName: "Developer > Entities > Culling", menuItemName: "Don't Cull Too Small Mesh Parts", isCheckable: true, isChecked: false }); + Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Disable Light Entities", isCheckable: true, isChecked: false }); } } diff --git a/examples/editModels.js b/examples/editModels.js index 8e3503b9b2..60ca9b6ca6 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -2554,7 +2554,7 @@ function mousePressEvent(event) { var pickRay = Camera.computePickRay(event.x, event.y); Vec3.print("[Mouse] Looking at: ", pickRay.origin); - var foundIntersection = Entities.findRayIntersection(pickRay); + var foundIntersection = Entities.findRayIntersection(pickRay, true); // we want precision picking here if(!foundIntersection.accurate) { return; diff --git a/examples/headMove.js b/examples/headMove.js index 4d2e4ded07..943664b70f 100644 --- a/examples/headMove.js +++ b/examples/headMove.js @@ -11,6 +11,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var gamepads = {}; + var debug = false; var willMove = false; @@ -19,7 +21,6 @@ var warpPosition = { x: 0, y: 0, z: 0 }; var hipsToEyes; var restoreCountdownTimer; -var headTurningTimer = 0.0; // Overlays to show target location @@ -62,13 +63,6 @@ function restoreCameraState() { Camera.mode = oldMode; } -function activateWarp() { - if (warpActive) return; - warpActive = true; - - updateWarp(); -} - var WATCH_AVATAR_DISTANCE = 2.5; var sound = SoundCache.getSound("http://public.highfidelity.io/sounds/Footsteps/FootstepW2Right-12db.wav"); @@ -132,6 +126,22 @@ function updateWarp() { }); } +function activateWarp() { + if (warpActive) return; + warpActive = true; + movingWithHead = true; + hipsToEyes = MyAvatar.getEyePosition().y - MyAvatar.position.y; + headStartPosition = MyAvatar.getTrackedHeadPosition(); + headStartDeltaPitch = MyAvatar.getHeadDeltaPitch(); + headStartFinalPitch = MyAvatar.getHeadFinalPitch(); + headStartRoll = MyAvatar.getHeadFinalRoll(); + headStartYaw = MyAvatar.getHeadFinalYaw(); + deltaYaw = 0.0; + warpPosition = MyAvatar.position; + warpPosition.y += hipsToEyes; + updateWarp(); +} + function finishWarp() { if (!warpActive) return; warpActive = false; @@ -152,6 +162,9 @@ function finishWarp() { cameraPosition = Vec3.subtract(MyAvatar.position, Vec3.multiplyQbyV(Camera.getOrientation(), { x: 0, y: -hipsToEyes, z: -hipsToEyes * WATCH_AVATAR_DISTANCE })); Camera.setPosition(cameraPosition); playSound(); + if (watchAvatar) { + restoreCountdownTimer = RESTORE_TIME; + } } } @@ -169,35 +182,11 @@ function update(deltaTime) { restoreCountDownTimer = 0.0; } } - var HEAD_TURN_TIME = 0.10; - var HEAD_TURN_DEGREES = 4.0; - var HEAD_TURN_START_ANGLE = 45.0; - var currentYaw = MyAvatar.getHeadFinalYaw(); - if (Math.abs(currentYaw) > HEAD_TURN_START_ANGLE) { - headTurningTimer += deltaTime; - if (headTurningTimer > HEAD_TURN_TIME) { - headTurningTimer = 0.0; - MyAvatar.orientation = Quat.multiply(Quat.fromPitchYawRollDegrees(0, (currentYaw > 0) ? HEAD_TURN_DEGREES: -HEAD_TURN_DEGREES, 0), - MyAvatar.orientation); - } - } else { - headTurningTimer = 0.0; - } } Controller.keyPressEvent.connect(function(event) { if (event.text == "SPACE" && !event.isAutoRepeat && !movingWithHead) { keyDownTime = 0.0; - movingWithHead = true; - hipsToEyes = MyAvatar.getEyePosition().y - MyAvatar.position.y; - headStartPosition = MyAvatar.getTrackedHeadPosition(); - headStartDeltaPitch = MyAvatar.getHeadDeltaPitch(); - headStartFinalPitch = MyAvatar.getHeadFinalPitch(); - headStartRoll = MyAvatar.getHeadFinalRoll(); - headStartYaw = MyAvatar.getHeadFinalYaw(); - deltaYaw = 0.0; - warpPosition = MyAvatar.position; - warpPosition.y += hipsToEyes; activateWarp(); } }); @@ -223,11 +212,40 @@ Controller.keyReleaseEvent.connect(function(event) { } timeSinceLastUp = 0.0; finishWarp(); - if (watchAvatar) { - restoreCountdownTimer = RESTORE_TIME; - } } }); +function reportButtonValue(button, newValue, oldValue) { + if (button == Joysticks.BUTTON_FACE_RIGHT) { + if (newValue) { + activateWarp(); + } else { + finishWarp(); + } + } +} + Script.update.connect(update); +function addJoystick(gamepad) { + gamepad.buttonStateChanged.connect(reportButtonValue); + + gamepads[gamepad.instanceId] = gamepad; + + print("Added gamepad: " + gamepad.name + " (" + gamepad.instanceId + ")"); +} + +function removeJoystick(gamepad) { + delete gamepads[gamepad.instanceId] + + print("Removed gamepad: " + gamepad.name + " (" + gamepad.instanceId + ")"); +} + +var allJoysticks = Joysticks.getAllJoysticks(); +for (var i = 0; i < allJoysticks.length; i++) { + addJoystick(allJoysticks[i]); +} + +Joysticks.joystickAdded.connect(addJoystick); +Joysticks.joystickRemoved.connect(removeJoystick); + diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index a79edfb181..17eb0ad88f 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -103,13 +103,14 @@ var elIgnoreForCollisions = document.getElementById("property-ignore-for-collisions"); var elCollisionsWillMove = document.getElementById("property-collisions-will-move"); var elLifetime = document.getElementById("property-lifetime"); + var elScriptURL = document.getElementById("property-script-url"); - var elBoxSection = document.getElementById("box-section"); + var elBoxSections = document.querySelectorAll(".box-section"); var elBoxColorRed = document.getElementById("property-box-red"); var elBoxColorGreen = document.getElementById("property-box-green"); var elBoxColorBlue = document.getElementById("property-box-blue"); - var elLightSection = document.getElementById('light-section'); + var elLightSections = document.querySelectorAll(".light-section"); var elLightSpotLight = document.getElementById("property-light-spot-light"); var elLightDiffuseRed = document.getElementById("property-light-diffuse-red"); var elLightDiffuseGreen = document.getElementById("property-light-diffuse-green"); @@ -129,14 +130,14 @@ var elLightExponent = document.getElementById("property-light-exponent"); var elLightCutoff = document.getElementById("property-light-cutoff"); - var elModelSection = document.getElementById("model-section"); + var elModelSections = document.querySelectorAll(".model-section"); var elModelURL = document.getElementById("property-model-url"); var elModelAnimationURL = document.getElementById("property-model-animation-url"); var elModelAnimationPlaying = document.getElementById("property-model-animation-playing"); var elModelAnimationFPS = document.getElementById("property-model-animation-fps"); var elModelAnimationFrame = document.getElementById("property-model-animation-frame"); - var elTextSection = document.getElementById("text-section"); + var elTextSections = document.querySelectorAll(".text-section"); var elTextText = document.getElementById("property-text-text"); var elTextLineHeight = document.getElementById("property-text-line-height"); var elTextTextColorRed = document.getElementById("property-text-text-color-red"); @@ -160,102 +161,120 @@ elLocked.checked = properties.locked; if (properties.locked) { - disableChildren(document.getElementById("properties"), 'input'); + disableChildren(document.getElementById("properties-table"), 'input'); elLocked.removeAttribute('disabled'); } else { - enableChildren(document.getElementById("properties"), 'input'); + enableChildren(document.getElementById("properties-table"), 'input'); + } - elVisible.checked = properties.visible; + elVisible.checked = properties.visible; - elPositionX.value = properties.position.x.toFixed(2); - elPositionY.value = properties.position.y.toFixed(2); - elPositionZ.value = properties.position.z.toFixed(2); + elPositionX.value = properties.position.x.toFixed(2); + elPositionY.value = properties.position.y.toFixed(2); + elPositionZ.value = properties.position.z.toFixed(2); - elDimensionsX.value = properties.dimensions.x.toFixed(2); - elDimensionsY.value = properties.dimensions.y.toFixed(2); - elDimensionsZ.value = properties.dimensions.z.toFixed(2); + elDimensionsX.value = properties.dimensions.x.toFixed(2); + elDimensionsY.value = properties.dimensions.y.toFixed(2); + elDimensionsZ.value = properties.dimensions.z.toFixed(2); - elRegistrationX.value = properties.registrationPoint.x.toFixed(2); - elRegistrationY.value = properties.registrationPoint.y.toFixed(2); - elRegistrationZ.value = properties.registrationPoint.z.toFixed(2); + elRegistrationX.value = properties.registrationPoint.x.toFixed(2); + elRegistrationY.value = properties.registrationPoint.y.toFixed(2); + elRegistrationZ.value = properties.registrationPoint.z.toFixed(2); - elLinearVelocityX.value = properties.velocity.x.toFixed(2); - elLinearVelocityY.value = properties.velocity.y.toFixed(2); - elLinearVelocityZ.value = properties.velocity.z.toFixed(2); - elLinearDamping.value = properties.damping.toFixed(2); + elLinearVelocityX.value = properties.velocity.x.toFixed(2); + elLinearVelocityY.value = properties.velocity.y.toFixed(2); + elLinearVelocityZ.value = properties.velocity.z.toFixed(2); + elLinearDamping.value = properties.damping.toFixed(2); - elAngularVelocityX.value = properties.angularVelocity.x.toFixed(2); - elAngularVelocityY.value = properties.angularVelocity.y.toFixed(2); - elAngularVelocityZ.value = properties.angularVelocity.z.toFixed(2); - elAngularDamping.value = properties.angularDamping.toFixed(2); + elAngularVelocityX.value = properties.angularVelocity.x.toFixed(2); + elAngularVelocityY.value = properties.angularVelocity.y.toFixed(2); + elAngularVelocityZ.value = properties.angularVelocity.z.toFixed(2); + elAngularDamping.value = properties.angularDamping.toFixed(2); - elGravityX.value = properties.gravity.x.toFixed(2); - elGravityY.value = properties.gravity.y.toFixed(2); - elGravityZ.value = properties.gravity.z.toFixed(2); + elGravityX.value = properties.gravity.x.toFixed(2); + elGravityY.value = properties.gravity.y.toFixed(2); + elGravityZ.value = properties.gravity.z.toFixed(2); - elMass.value = properties.mass.toFixed(2); - elIgnoreForCollisions.checked = properties.ignoreForCollisions; - elCollisionsWillMove.checked = properties.collisionsWillMove; - elLifetime.value = properties.lifetime; + elMass.value = properties.mass.toFixed(2); + elIgnoreForCollisions.checked = properties.ignoreForCollisions; + elCollisionsWillMove.checked = properties.collisionsWillMove; + elLifetime.value = properties.lifetime; + elScriptURL.value = properties.script; - if (properties.type != "Box") { - elBoxSection.style.display = 'none'; - } else { - elBoxSection.style.display = 'block'; - - elBoxColorRed.value = properties.color.red; - elBoxColorGreen.value = properties.color.green; - elBoxColorBlue.value = properties.color.blue; + if (properties.type != "Box") { + for (var i = 0; i < elBoxSections.length; i++) { + elBoxSections[i].style.display = 'none'; + } + } else { + for (var i = 0; i < elBoxSections.length; i++) { + elBoxSections[i].style.display = 'table-row'; } - if (properties.type != "Model") { - elModelSection.style.display = 'none'; - } else { - elModelSection.style.display = 'block'; - elModelURL.value = properties.modelURL; - elModelAnimationURL.value = properties.animationURL; - elModelAnimationPlaying.checked = properties.animationIsPlaying; - elModelAnimationFPS.value = properties.animationFPS; + elBoxColorRed.value = properties.color.red; + elBoxColorGreen.value = properties.color.green; + elBoxColorBlue.value = properties.color.blue; + } + + if (properties.type != "Model") { + for (var i = 0; i < elModelSections.length; i++) { + elModelSections[i].style.display = 'none'; + } + } else { + for (var i = 0; i < elModelSections.length; i++) { + elModelSections[i].style.display = 'table-row'; } - if (properties.type != "Text") { - elTextSection.style.display = 'none'; - } else { - elTextSection.style.display = 'block'; + elModelURL.value = properties.modelURL; + elModelAnimationURL.value = properties.animationURL; + elModelAnimationPlaying.checked = properties.animationIsPlaying; + elModelAnimationFPS.value = properties.animationFPS; + } - elTextText.value = properties.text; - elTextLineHeight.value = properties.lineHeight; - elTextTextColorRed.value = properties.textColor.red; - elTextTextColorGreen.value = properties.textColor.green; - elTextTextColorBlue.value = properties.textColor.blue; - elTextBackgroundColorRed.value = properties.backgroundColor.red; - elTextBackgroundColorGreen.value = properties.backgroundColor.green; - elTextBackgroundColorBlue.value = properties.backgroundColor.blue; + if (properties.type != "Text") { + for (var i = 0; i < elTextSections.length; i++) { + elTextSections[i].style.display = 'none'; + } + } else { + for (var i = 0; i < elTextSections.length; i++) { + elTextSections[i].style.display = 'table-row'; } - if (properties.type != "Light") { - elLightSection.style.display = 'none'; - } else { - elLightSection.style.display = 'block'; + elTextText.value = properties.text; + elTextLineHeight.value = properties.lineHeight; + elTextTextColorRed.value = properties.textColor.red; + elTextTextColorGreen.value = properties.textColor.green; + elTextTextColorBlue.value = properties.textColor.blue; + elTextBackgroundColorRed.value = properties.backgroundColor.red; + elTextBackgroundColorGreen.value = properties.backgroundColor.green; + elTextBackgroundColorBlue.value = properties.backgroundColor.blue; + } - elLightDiffuseRed.value = properties.diffuseColor.red; - elLightDiffuseGreen.value = properties.diffuseColor.green; - elLightDiffuseBlue.value = properties.diffuseColor.blue; - - elLightAmbientRed.value = properties.ambientColor.red; - elLightAmbientGreen.value = properties.ambientColor.green; - elLightAmbientBlue.value = properties.ambientColor.blue; - - elLightSpecularRed.value = properties.specularColor.red; - elLightSpecularGreen.value = properties.specularColor.green; - elLightSpecularBlue.value = properties.specularColor.blue; - - elLightConstantAttenuation.value = properties.constantAttenuation; - elLightLinearAttenuation.value = properties.linearAttenuation; - elLightQuadraticAttenuation.value = properties.quadraticAttenuation; - elLightExponent.value = properties.exponent; - elLightCutoff.value = properties.cutoff; + if (properties.type != "Light") { + for (var i = 0; i < elLightSections.length; i++) { + elLightSections[i].style.display = 'none'; } + } else { + for (var i = 0; i < elLightSections.length; i++) { + elLightSections[i].style.display = 'table-row'; + } + + elLightDiffuseRed.value = properties.diffuseColor.red; + elLightDiffuseGreen.value = properties.diffuseColor.green; + elLightDiffuseBlue.value = properties.diffuseColor.blue; + + elLightAmbientRed.value = properties.ambientColor.red; + elLightAmbientGreen.value = properties.ambientColor.green; + elLightAmbientBlue.value = properties.ambientColor.blue; + + elLightSpecularRed.value = properties.specularColor.red; + elLightSpecularGreen.value = properties.specularColor.green; + elLightSpecularBlue.value = properties.specularColor.blue; + + elLightConstantAttenuation.value = properties.constantAttenuation; + elLightLinearAttenuation.value = properties.linearAttenuation; + elLightQuadraticAttenuation.value = properties.quadraticAttenuation; + elLightExponent.value = properties.exponent; + elLightCutoff.value = properties.cutoff; } } } @@ -307,6 +326,7 @@ elIgnoreForCollisions.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ignoreForCollisions')); elCollisionsWillMove.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionsWillMove')); elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime')); + elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script')); var boxColorChangeFunction = createEmitColorPropertyUpdateFunction( 'color', elBoxColorRed, elBoxColorGreen, elBoxColorBlue); @@ -345,7 +365,7 @@ elModelAnimationPlaying.addEventListener('change', createEmitCheckedPropertyUpdateFunction('animationIsPlaying')); elModelAnimationFPS.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFPS')); elModelAnimationFrame.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFrameIndex')); - + elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text')); elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight')); @@ -361,6 +381,50 @@ elTextBackgroundColorGreen.addEventListener('change', textBackgroundColorChangeFunction); elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction); + + var resizing = false; + var startX = 0; + var originalWidth = 0; + var resizeHandleWidth = 10; + + var col1 = document.querySelector("#col-label"); + + document.body.addEventListener('mousemove', function(event) { + if (resizing) { + var dX = event.x - startX; + col1.style.width = (originalWidth + dX) + "px"; + } + }); + document.body.addEventListener('mouseup', function(event) { + resizing = false; + }); + document.body.addEventListener('mouseleave', function(event) { + resizing = false; + }); + var els = document.querySelectorAll("#properties-table td"); + for (var i = 0; i < els.length; i++) { + var el = els[i]; + el.addEventListener('mousemove', function(event) { + if (!resizing) { + var distance = this.offsetWidth - event.offsetX; + if (distance < resizeHandleWidth) { + document.body.style.cursor = "ew-resize"; + } else { + document.body.style.cursor = "initial"; + } + } + }); + el.addEventListener('mousedown', function(event) { + var distance = this.offsetWidth - event.offsetX; + if (distance < resizeHandleWidth) { + startX = event.x; + originalWidth = this.offsetWidth; + resizing = true; + target = this; + } + }); + } + } @@ -368,272 +432,267 @@
-
-
- - - -
- -
- - + + + + + + + + + + + + + -
- - +
+ + + -
- - - X - Y - Z - -
+ + + + -
- - - X - Y - Z - -
+ + + + -
- - - - -
-
- - - - -
-
- - - - -
+ + + + -
- - - X - Y - Z - -
-
- - +
+ + + + + + + + + + + + + + + -
- - - X - Y - Z - -
+ + + + -
- - +
+ + + -
- - +
+ + + -
- - +
+ + + -
- - +
+ + + + + + + + -
-
- - - Red - Green - Blue - -
-
+ + + + -
-
- - - - -
-
- - - - -
-
- - - - -
-
- - - - -
-
- - - - -
-
-
-
- - - - -
-
- - - - -
-
- - - Red - Green - Blue - -
-
- - - Red - Green - Blue - -
-
+ + + + + + + + + + + + + + + + + + + + -
-
- - - - -
-
- - - Red - Green - Blue - -
-
- - - Red - Green - Blue - -
-
- - - Red - Green - Blue - -
-
- - - - -
-
- - - - -
-
- - - - -
-
- - - - -
-
- - - - -
-
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Type + + +
Locked - - +
Visible - - +
Position +
X
+
Y
+
Z
+
Registration +
X
+
Y
+
Z
+
Dimensions +
X
+
Y
+
Z
+
Linear Velocity +
X
+
Y
+
Z
+
Linear Damping - - -
- - - Pitch - Roll - Yaw - -
-
- - +
Angular Velocity +
Pitch
+
Yaw
+
Roll
+
Angular Damping - - +
Gravity +
X
+
Y
+
Z
+
Mass - - +
Ignore For Collisions - - +
Collisions Will Move - - +
Lifetime - - +
Script URL + +
Color +
R
+
G
+
B
+
Model URL + +
Animation URL + +
Animation Playing + +
Animation FPS + +
Animation Frame + +
Text + +
Line Height + +
Text Color +
R
+
G
+
B
+
Background Color +
R
+
G
+
B
+
Spot Light + +
Diffuse +
R
+
G
+
B
+
Ambient +
R
+
G
+
B
+
Specular +
R
+
G
+
B
+
Constant Attenuation + +
Linear Attenuation + +
Quadratic Attenuation + +
Exponent + +
Cutoff (degrees) + +
diff --git a/examples/html/style.css b/examples/html/style.css index b721c31b88..424933e14e 100644 --- a/examples/html/style.css +++ b/examples/html/style.css @@ -17,15 +17,6 @@ body { user-select: none; } -input { - line-height: 2; -} - -.input-left { - display: inline-block; - width: 20px; -} - .color-box { display: inline-block; width: 20px; @@ -63,7 +54,6 @@ input { .property-section label { font-weight: bold; - vertical-align: middle; } .property-section span { @@ -89,9 +79,10 @@ input[type=button] { font-size: .9em; } -input.coord { - width: 6em; - height: 2em; +input { + padding: 2px; + border: 1px solid #999; + background-color: #eee; } table#entity-table { @@ -105,7 +96,7 @@ table#entity-table { cursor: pointer; } -tr.selected { +#entity-table tr.selected { background-color: #AAA; } @@ -130,3 +121,48 @@ th#entity-type { th#entity-url { } + + +div.input-area { + display: inline-block; +} + +input { +} + + + +table#properties-table { + border: none; + border-collapse: collapse; + width: 100%; + background-color: #efefef; + font-family: Arial; + font-size: 12px; + table-layout: fixed; +} + +#properties-table tr { + border-bottom: 1px solid #e5e5e5; +} + +#properties-table td.label { + padding-right: 10px; + border-right: 1px solid #999; + text-align: right; + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + vertical-align: middle; + height: 1.2em; +} + +#properties-table td { + padding: 5px 0px 5px 10px; +} + +col#col-label { + width: 130px; +} diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index fa97e9351f..a96eec9bbb 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -1772,17 +1772,27 @@ SelectionDisplay = (function () { var centerToZero = Vec3.subtract(center, zero); var centerToIntersect = Vec3.subtract(center, result.intersection); var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - var distanceFromCenter = Vec3.distance(center, result.intersection); var snapToInner = distanceFromCenter < innerRadius; var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - + // for debugging if (debug) { Vec3.print(" result.intersection:",result.intersection); Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: center, end: result.intersection }); + Vec3.print(" centerToZero:", centerToZero); + Vec3.print(" centerToIntersect:", centerToIntersect); + Vec3.print(" rotationNormal:", rotationNormal); print(" angleFromZero:" + angleFromZero); + print(" distanceFromCenter:" + distanceFromCenter); + print(" snapAngle:" + snapAngle); + } + + angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; + + // for debugging + if (debug) { + print(" angleFromZero:" + angleFromZero + " --- after snap"); } var yawChange = Quat.fromVec3Degrees({ x: 0, y: angleFromZero, z: 0 }); diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index ef1be8fef9..606ff5955a 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -465,7 +465,7 @@ function rayPlaneIntersection(pickRay, point, normal) { function findClickedEntity(event) { var pickRay = Camera.computePickRay(event.x, event.y); - var foundIntersection = Entities.findRayIntersection(pickRay); + var foundIntersection = Entities.findRayIntersection(pickRay, true); // want precision picking if (!foundIntersection.accurate) { return null; diff --git a/examples/orbitingSound.js b/examples/orbitingSound.js new file mode 100644 index 0000000000..54e319faaa --- /dev/null +++ b/examples/orbitingSound.js @@ -0,0 +1,45 @@ +// +// orbitingSound.js +// examples +// +// Created by Philip Rosedale on December 4, 2014 +// Copyright 2014 High Fidelity, Inc. +// +// An object playing a sound appears and circles you, changing brightness with the audio playing. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +var RADIUS = 2.0; +var orbitCenter = Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.getOrientation()), RADIUS)); +var time = 0; +var SPEED = 1.0; +var currentPosition = { x: 0, y: 0, z: 0 }; +var trailingLoudness = 0.0; + +var soundClip = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Tabla+Loops/Tabla1.wav"); + +var properties = { + type: "Box", + position: orbitCenter, + dimensions: { x: 0.25, y: 0.25, z: 0.25 }, + color: { red: 100, green: 0, blue : 0 } + }; + +var objectId = Entities.addEntity(properties); +var sound = Audio.playSound(soundClip, { position: orbitCenter, loop: true, volume: 0.5 }); + +function update(deltaTime) { + time += deltaTime; + currentPosition = { x: orbitCenter.x + Math.cos(time * SPEED) * RADIUS, y: orbitCenter.y, z: orbitCenter.z + Math.sin(time * SPEED) * RADIUS }; + trailingLoudness = 0.9 * trailingLoudness + 0.1 * Audio.getLoudness(sound); + Entities.editEntity( objectId, { position: currentPosition, color: { red: Math.min(trailingLoudness * 2000, 255), green: 0, blue: 0 } } ); + Audio.setInjectorOptions(sound, { position: currentPosition }); +} + +Script.scriptEnding.connect(function() { + Entities.deleteEntity(objectId); + Audio.stopInjector(sound); +}); + +Script.update.connect(update); \ No newline at end of file diff --git a/examples/virtualKeyboard.js b/examples/virtualKeyboard.js new file mode 100644 index 0000000000..c89dc6fb04 --- /dev/null +++ b/examples/virtualKeyboard.js @@ -0,0 +1,598 @@ +// +// virtualKeyboard.js +// examples +// +// Created by Thijs Wenker on 11/18/14. +// Copyright 2014 High Fidelity, Inc. +// +// Control a virtual keyboard using your favorite HMD. +// Usage: Enable VR-mode and go to First person mode, +// look at the key that you would like to press, and press the spacebar on your "REAL" keyboard. +// +// leased some code from newEditEntities.js for Text Entity example +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/globals.js"); + +const KBD_UPPERCASE_DEFAULT = 0; +const KBD_LOWERCASE_DEFAULT = 1; +const KBD_UPPERCASE_HOVER = 2; +const KBD_LOWERCASE_HOVER = 3; +const KBD_BACKGROUND = 4; + +const KEYBOARD_URL = HIFI_PUBLIC_BUCKET + "images/keyboard.svg"; +const CURSOR_URL = HIFI_PUBLIC_BUCKET + "images/cursor.svg"; + +const SPACEBAR_CHARCODE = 32; + +const KEYBOARD_WIDTH = 1174.7; +const KEYBOARD_HEIGHT = 434.1; + +const CURSOR_WIDTH = 33.9; +const CURSOR_HEIGHT = 33.9; + +// VIEW_ANGLE can be adjusted to your likings, the smaller the faster movement. +// Try setting it to 60 if it goes too fast for you. +const VIEW_ANGLE = 40.0; +const VIEW_ANGLE_BY_TWO = VIEW_ANGLE / 2; + +const SPAWN_DISTANCE = 1; +const DEFAULT_TEXT_DIMENSION_Z = 0.02; + +const BOUND_X = 0; +const BOUND_Y = 1; +const BOUND_W = 2; +const BOUND_H = 3; + +const KEY_STATE_LOWER = 0; +const KEY_STATE_UPPER = 1; + +const TEXT_MARGIN_TOP = 0.15; +const TEXT_MARGIN_LEFT = 0.15; +const TEXT_MARGIN_RIGHT = 0.17; +const TEXT_MARGIN_BOTTOM = 0.17; + +var windowDimensions = Controller.getViewportDimensions(); +var cursor = null; +var keyboard = new Keyboard(); +var textFontSize = 9; +var text = null; +var textText = ""; +var textSizeMeasureOverlay = Overlays.addOverlay("text3d", {visible: false}); + +function appendChar(char) { + textText += char; + updateTextOverlay(); + Overlays.editOverlay(text, {text: textText}); +} + +function deleteChar() { + if (textText.length > 0) { + textText = textText.substring(0, textText.length - 1); + updateTextOverlay(); + } +} + +function updateTextOverlay() { + var textLines = textText.split("\n"); + var maxLineWidth = 0; + for (textLine in textLines) { + var lineWidth = Overlays.textWidth(text, textLines[textLine]); + if (lineWidth > maxLineWidth) { + maxLineWidth = lineWidth; + } + } + var suggestedFontSize = (windowDimensions.x / maxLineWidth) * textFontSize * 0.90; + var maxFontSize = 190 / textLines.length; + textFontSize = (suggestedFontSize > maxFontSize) ? maxFontSize : suggestedFontSize; + var topMargin = (250 - (textFontSize * textLines.length)) / 4; + Overlays.editOverlay(text, {text: textText, font: {size: textFontSize}, topMargin: topMargin}); + var maxLineWidth = 0; + for (textLine in textLines) { + var lineWidth = Overlays.textWidth(text, textLines[textLine]); + if (lineWidth > maxLineWidth) { + maxLineWidth = lineWidth; + } + } + Overlays.editOverlay(text, {leftMargin: (windowDimensions.x - maxLineWidth) / 2}); +} + +keyboard.onKeyPress = function(event) { + if (event.event == 'keypress') { + appendChar(event.char); + } else if (event.event == 'enter') { + appendChar("\n"); + } +}; + +keyboard.onKeyRelease = function(event) { + print("Key release event test"); + // you can cancel a key by releasing its focusing before releasing it + if (event.focus) { + if (event.event == 'delete') { + deleteChar(); + } else if (event.event == 'submit') { + print(textText); + + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); + + var textLines = textText.split("\n"); + var maxLineWidth = 0; + for (textLine in textLines) { + var lineWidth = Overlays.textWidth(textSizeMeasureOverlay, textLines[textLine]); + if (lineWidth > maxLineWidth) { + maxLineWidth = lineWidth; + } + } + var usernameLine = "--" + GlobalServices.myUsername; + var usernameWidth = Overlays.textWidth(textSizeMeasureOverlay, usernameLine); + if (maxLineWidth < usernameWidth) { + maxLineWidth = usernameWidth; + } else { + var spaceableWidth = maxLineWidth - usernameWidth; + var spaceWidth = Overlays.textWidth(textSizeMeasureOverlay, " "); + var numberOfSpaces = Math.floor(spaceableWidth / spaceWidth); + for (var i = 0; i < numberOfSpaces; i++) { + usernameLine = " " + usernameLine; + } + } + var dimension_x = maxLineWidth + TEXT_MARGIN_RIGHT + TEXT_MARGIN_LEFT; + if (position.x > 0 && position.y > 0 && position.z > 0) { + Entities.addEntity({ + type: "Text", + rotation: MyAvatar.orientation, + position: position, + dimensions: { x: dimension_x, y: (textLines.length + 1) * 0.14 + TEXT_MARGIN_TOP + TEXT_MARGIN_BOTTOM, z: DEFAULT_TEXT_DIMENSION_Z }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + textColor: { red: 255, green: 255, blue: 255 }, + text: textText + "\n" + usernameLine + }); + } + textText = ""; + updateTextOverlay(); + } + } +}; + +keyboard.onFullyLoaded = function() { + print("Virtual-keyboard fully loaded."); + var dimensions = Controller.getViewportDimensions(); + text = Overlays.addOverlay("text", { + x: 0, + y: dimensions.y - keyboard.height() - 260, + width: dimensions.x, + height: 250, + backgroundColor: { red: 255, green: 255, blue: 255}, + color: { red: 0, green: 0, blue: 0}, + topMargin: 5, + leftMargin: 0, + font: {size: textFontSize}, + text: "", + alpha: 0.8 + }); + updateTextOverlay(); + // the cursor is being loaded after the keyboard, else it will be on the background of the keyboard + cursor = new Cursor(); + cursor.onUpdate = function(position) { + keyboard.setFocusPosition(position.x, position.y); + }; +}; + +function KeyboardKey(keyboard, keyProperties) { + var tthis = this; + this._focus = false; + this._beingPressed = false; + this.event = keyProperties.event != undefined ? + keyProperties.event : 'keypress'; + this.bounds = keyProperties.bounds; + this.states = keyProperties.states; + this.keyboard = keyboard; + this.keyState = keyProperties.keyState != undefined ? keyProperties.keyState : KBD_LOWERCASE_DEFAULT; + // one overlay per bound vector [this.bounds] + this.overlays = []; + this.getKeyEvent = function() { + if (tthis.event == 'keypress') { + var state = tthis.states[(tthis.keyboard.shift ? 1 : 2) % tthis.states.length]; + return {key: state.charCode, char: state.char, event: tthis.event, focus: tthis._focus}; + } + return {event: tthis.event, focus: tthis._focus}; + }; + this.containsCoord = function(x, y) { + for (var i = 0; i < this.bounds.length; i++) { + if (x >= this.bounds[i][BOUND_X] && + x <= (this.bounds[i][BOUND_X] + this.bounds[i][BOUND_W]) && + y >= this.bounds[i][BOUND_Y] && + y <= (this.bounds[i][BOUND_Y] + this.bounds[i][BOUND_H])) + { + return true; + } + } + return false; + }; + this.updateState = function() { + tthis.setState(eval('KBD_' + (tthis.keyboard.shift ? 'UPPERCASE' : 'LOWERCASE') + '_' + (tthis._focus ? 'HOVER' : 'DEFAULT'))); + }; + this.updateColor = function() { + var colorIntensity = this._beingPressed ? 128 : 255; + for (var i = 0; i < tthis.bounds.length; i++) { + Overlays.editOverlay(tthis.overlays[i], + {color: {red: colorIntensity, green: colorIntensity, blue: colorIntensity}} + ); + } + }; + this.press = function() { + tthis._beingPressed = true; + tthis.updateColor(); + }; + this.release = function() { + tthis._beingPressed = false; + tthis.updateColor(); + }; + this.blur = function() { + tthis._focus = false; + tthis.updateState(); + }; + this.focus = function() { + tthis._focus = true; + tthis.updateState(); + }; + this.setState = function(state) { + tthis.keyState = state; + for (var i = 0; i < tthis.bounds.length; i++) { + Overlays.editOverlay(tthis.overlays[i], { + subImage: {width: tthis.bounds[i][BOUND_W], height: tthis.bounds[i][BOUND_H], x: tthis.bounds[i][BOUND_X], y: (KEYBOARD_HEIGHT * tthis.keyState) + tthis.bounds[i][BOUND_Y]} + }); + } + }; + this.rescale = function() { + for (var i = 0; i < tthis.bounds.length; i++) { + Overlays.editOverlay(tthis.overlays[i], { + x: tthis.keyboard.getX() + tthis.bounds[i][BOUND_X] * keyboard.scale, + y: tthis.keyboard.getY() + tthis.bounds[i][BOUND_Y] * keyboard.scale, + width: this.bounds[i][BOUND_W] * keyboard.scale, + height: this.bounds[i][BOUND_H] * keyboard.scale + }); + } + }; + this.remove = function() { + for (var i = 0; i < this.overlays.length; i++) { + Overlays.deleteOverlay(this.overlays[i]); + } + }; + this.isLoaded = function() { + for (var i = 0; i < this.overlays.length; i++) { + if (!Overlays.isLoaded(this.overlays[i])) { + return false; + } + } + return true; + }; + for (var i = 0; i < this.bounds.length; i++) { + var newOverlay = Overlays.cloneOverlay(this.keyboard.background); + Overlays.editOverlay(newOverlay, { + x: this.keyboard.getX() + this.bounds[i][BOUND_X] * keyboard.scale, + y: this.keyboard.getY() + this.bounds[i][BOUND_Y] * keyboard.scale, + width: this.bounds[i][BOUND_W] * keyboard.scale, + height: this.bounds[i][BOUND_H] * keyboard.scale, + subImage: {width: this.bounds[i][BOUND_W], height: this.bounds[i][BOUND_H], x: this.bounds[i][BOUND_X], y: (KEYBOARD_HEIGHT * this.keyState) + this.bounds[i][BOUND_Y]}, + alpha: 1 + }); + this.overlays.push(newOverlay); + } +} + +function Keyboard() { + var tthis = this; + this.focussed_key = -1; + this.scale = windowDimensions.x / KEYBOARD_WIDTH; + this.shift = false; + this.width = function() { + return KEYBOARD_WIDTH * tthis.scale; + }; + this.height = function() { + return KEYBOARD_HEIGHT * tthis.scale; + }; + this.getX = function() { + return (windowDimensions.x / 2) - (this.width() / 2); + }; + this.getY = function() { + return windowDimensions.y - this.height(); + }; + this.background = Overlays.addOverlay("image", { + x: this.getX(), + y: this.getY(), + width: this.width(), + height: this.height(), + subImage: {width: KEYBOARD_WIDTH, height: KEYBOARD_HEIGHT, y: KEYBOARD_HEIGHT * KBD_BACKGROUND}, + imageURL: KEYBOARD_URL, + alpha: 1 + }); + this.rescale = function() { + this.scale = windowDimensions.x / KEYBOARD_WIDTH; + Overlays.editOverlay(tthis.background, { + x: this.getX(), + y: this.getY(), + width: this.width(), + height: this.height() + }); + for (var i = 0; i < tthis.keys.length; i++) { + tthis.keys[i].rescale(); + } + }; + + this.setFocusPosition = function(x, y) { + // set to local unscaled position + var localx = (x - tthis.getX()) / tthis.scale; + var localy = (y - tthis.getY()) / tthis.scale; + var new_focus_key = -1; + if (localx >= 0 && localy >= 0 && localx <= KEYBOARD_WIDTH && localy <= KEYBOARD_HEIGHT) { + for (var i = 0; i < tthis.keys.length; i++) { + if (tthis.keys[i].containsCoord(localx, localy)) { + new_focus_key = i; + break; + } + } + } + if (new_focus_key != tthis.focussed_key) { + if (tthis.focussed_key != -1) { + tthis.keys[tthis.focussed_key].blur(); + } + tthis.focussed_key = new_focus_key; + if (tthis.focussed_key != -1) { + tthis.keys[tthis.focussed_key].focus(); + } + } + return tthis; + }; + + this.pressFocussedKey = function() { + if (tthis.focussed_key != -1) { + if (tthis.keys[tthis.focussed_key].event == 'shift') { + tthis.toggleShift(); + } else { + tthis.keys[tthis.focussed_key].press(); + } + if (this.onKeyPress != null) { + this.onKeyPress(tthis.keys[tthis.focussed_key].getKeyEvent()); + } + } + + return tthis; + }; + + this.releaseKeys = function() { + for (var i = 0; i < tthis.keys.length; i++) { + if (tthis.keys[i]._beingPressed) { + if (tthis.keys[i].event != 'shift') { + tthis.keys[i].release(); + } + if (this.onKeyRelease != null) { + this.onKeyRelease(tthis.keys[i].getKeyEvent()); + } + } + } + }; + + this.toggleShift = function() { + tthis.shift = !tthis.shift; + for (var i = 0; i < tthis.keys.length; i++) { + tthis.keys[i].updateState(); + if (tthis.keys[i].event == 'shift') { + if (tthis.shift) { + tthis.keys[i].press(); + continue; + } + tthis.keys[i].release(); + } + } + }; + + this.getFocussedKey = function() { + if (tthis.focussed_key == -1) { + return null; + } + return tthis.keys[tthis.focussed_key]; + }; + + this.remove = function() { + Overlays.deleteOverlay(this.background); + for (var i = 0; i < this.keys.length; i++) { + this.keys[i].remove(); + } + }; + + this.onKeyPress = null; + this.onKeyRelease = null; + this.onSubmit = null; + this.onFullyLoaded = null; + + this.keys = []; + // + // keyProperties contains the key data + // + // coords [[x,y,w,h],[x,y,w,h]] + // states array of 1 or 2 objects [lowercase, uppercase] each object contains a charCode and a char + var keyProperties = [ + {bounds: [[12, 12, 65, 52]], states: [{charCode: 126, char: '~'}]}, + {bounds: [[84, 12, 65, 52]], states: [{charCode: 33, char: '!'}]}, + {bounds: [[156, 12, 65, 52]], states: [{charCode: 64, char: '@'}]}, + {bounds: [[228, 12, 65, 52]], states: [{charCode: 35, char: '#'}]}, + {bounds: [[300, 12, 65, 52]], states: [{charCode: 36, char: '$'}]}, + {bounds: [[372, 12, 65, 52]], states: [{charCode: 37, char: '%'}]}, + {bounds: [[445, 12, 65, 52]], states: [{charCode: 94, char: '^'}]}, + {bounds: [[517, 12, 65, 52]], states: [{charCode: 38, char: '&'}]}, + {bounds: [[589, 12, 65, 52]], states: [{charCode: 42, char: '*'}]}, + {bounds: [[662, 12, 65, 52]], states: [{charCode: 40, char: '('}]}, + {bounds: [[734, 12, 65, 52]], states: [{charCode: 41, char: ')'}]}, + {bounds: [[806, 12, 65, 52]], states: [{charCode: 95, char: '_'}]}, + {bounds: [[881, 12, 65, 52]], states: [{charCode: 123, char: '{'}]}, + {bounds: [[953, 12, 65, 52]], states: [{charCode: 125, char: '}'}]}, + {bounds: [[1025, 12, 65, 52]], states: [{charCode: 60, char: '<'}]}, + {bounds: [[1097, 12, 65, 52]], states: [{charCode: 62, char: '>'}]}, + + {bounds: [[12, 71, 65, 63]], states: [{charCode: 96, char: '`'}]}, + {bounds: [[84, 71, 65, 63]], states: [{charCode: 49, char: '1'}]}, + {bounds: [[156, 71, 65, 63]], states: [{charCode: 50, char: '2'}]}, + {bounds: [[228, 71, 65, 63]], states: [{charCode: 51, char: '3'}]}, + {bounds: [[300, 71, 65, 63]], states: [{charCode: 52, char: '4'}]}, + {bounds: [[372, 71, 65, 63]], states: [{charCode: 53, char: '5'}]}, + {bounds: [[445, 71, 65, 63]], states: [{charCode: 54, char: '6'}]}, + {bounds: [[517, 71, 65, 63]], states: [{charCode: 55, char: '7'}]}, + {bounds: [[589, 71, 65, 63]], states: [{charCode: 56, char: '8'}]}, + {bounds: [[661, 71, 65, 63]], states: [{charCode: 57, char: '9'}]}, + {bounds: [[733, 71, 65, 63]], states: [{charCode: 48, char: '0'}]}, + {bounds: [[806, 71, 65, 63]], states: [{charCode: 45, char: '-'}]}, + {bounds: [[880, 71, 65, 63]], states: [{charCode: 61, char: '='}]}, + {bounds: [[953, 71, 65, 63]], states: [{charCode: 43, char: '+'}]}, + {bounds: [[1024, 71, 139, 63]], event: 'delete'}, + + // enter key has 2 bounds and one state + {bounds: [[11, 143, 98, 71], [11, 213, 121, 62]], event: 'enter'}, + + {bounds: [[118, 142, 64, 63]], states: [{charCode: 113, char: 'q'}, {charCode: 81, char: 'Q'}]}, + {bounds: [[190, 142, 64, 63]], states: [{charCode: 119, char: 'w'}, {charCode: 87, char: 'W'}]}, + {bounds: [[262, 142, 64, 63]], states: [{charCode: 101, char: 'e'}, {charCode: 69, char: 'E'}]}, + {bounds: [[334, 142, 64, 63]], states: [{charCode: 114, char: 'r'}, {charCode: 82, char: 'R'}]}, + {bounds: [[407, 142, 64, 63]], states: [{charCode: 116, char: 't'}, {charCode: 84, char: 'T'}]}, + {bounds: [[479, 142, 64, 63]], states: [{charCode: 121, char: 'y'}, {charCode: 89, char: 'Y'}]}, + {bounds: [[551, 142, 65, 63]], states: [{charCode: 117, char: 'u'}, {charCode: 85, char: 'U'}]}, + {bounds: [[623, 142, 65, 63]], states: [{charCode: 105, char: 'i'}, {charCode: 73, char: 'I'}]}, + {bounds: [[695, 142, 65, 63]], states: [{charCode: 111, char: 'o'}, {charCode: 79, char: 'O'}]}, + {bounds: [[768, 142, 64, 63]], states: [{charCode: 112, char: 'p'}, {charCode: 80, char: 'P'}]}, + {bounds: [[840, 142, 64, 63]], states: [{charCode: 91, char: '['}]}, + {bounds: [[912, 142, 65, 63]], states: [{charCode: 93, char: ']'}]}, + {bounds: [[984, 142, 65, 63]], states: [{charCode: 92, char: '\\'}]}, + {bounds: [[1055, 142, 65, 63]], states: [{charCode: 124, char: '|'}]}, + + {bounds: [[1126, 143, 35, 72], [1008, 214, 153, 62]], event: 'enter'}, + + {bounds: [[140, 213, 65, 63]], states: [{charCode: 97, char: 'a'}, {charCode: 65, char: 'A'}]}, + {bounds: [[211, 213, 64, 63]], states: [{charCode: 115, char: 's'}, {charCode: 83, char: 'S'}]}, + {bounds: [[283, 213, 65, 63]], states: [{charCode: 100, char: 'd'}, {charCode: 68, char: 'D'}]}, + {bounds: [[355, 213, 65, 63]], states: [{charCode: 102, char: 'f'}, {charCode: 70, char: 'F'}]}, + {bounds: [[428, 213, 64, 63]], states: [{charCode: 103, char: 'g'}, {charCode: 71, char: 'G'}]}, + {bounds: [[500, 213, 64, 63]], states: [{charCode: 104, char: 'h'}, {charCode: 72, char: 'H'}]}, + {bounds: [[572, 213, 65, 63]], states: [{charCode: 106, char: 'j'}, {charCode: 74, char: 'J'}]}, + {bounds: [[644, 213, 65, 63]], states: [{charCode: 107, char: 'k'}, {charCode: 75, char: 'K'}]}, + {bounds: [[716, 213, 65, 63]], states: [{charCode: 108, char: 'l'}, {charCode: 76, char: 'L'}]}, + {bounds: [[789, 213, 64, 63]], states: [{charCode: 59, char: ';'}]}, + {bounds: [[861, 213, 64, 63]], states: [{charCode: 39, char: '\''}]}, + {bounds: [[934, 213, 65, 63]], states: [{charCode: 58, char: ':'}]}, + + {bounds: [[12, 283, 157, 63]], event: 'shift'}, + + {bounds: [[176, 283, 65, 63]], states: [{charCode: 122, char: 'z'}, {charCode: 90, char: 'Z'}]}, + {bounds: [[249, 283, 64, 63]], states: [{charCode: 120, char: 'x'}, {charCode: 88, char: 'X'}]}, + {bounds: [[321, 283, 64, 63]], states: [{charCode: 99, char: 'c'}, {charCode: 67, char: 'C'}]}, + {bounds: [[393, 283, 64, 63]], states: [{charCode: 118, char: 'v'}, {charCode: 86, char: 'V'}]}, + {bounds: [[465, 283, 65, 63]], states: [{charCode: 98, char: 'b'}, {charCode: 66, char: 'B'}]}, + {bounds: [[537, 283, 65, 63]], states: [{charCode: 110, char: 'n'}, {charCode: 78, char: 'N'}]}, + {bounds: [[610, 283, 64, 63]], states: [{charCode: 109, char: 'm'}, {charCode: 77, char: 'M'}]}, + {bounds: [[682, 283, 64, 63]], states: [{charCode: 44, char: ','}]}, + {bounds: [[754, 283, 65, 63]], states: [{charCode: 46, char: '.'}]}, + {bounds: [[826, 283, 65, 63]], states: [{charCode: 47, char: '/'}]}, + {bounds: [[899, 283, 64, 63]], states: [{charCode: 63, char: '?'}]}, + + {bounds: [[972, 283, 190, 63]], event: 'shift'}, + + {bounds: [[249, 355, 573, 67]], states: [{charCode: 32, char: ' '}]}, + + {bounds: [[899, 355, 263, 67]], event: 'submit'} + ]; + + this.keyboardTextureLoaded = function() { + if (Overlays.isLoaded(tthis.background)) { + Script.clearInterval(tthis.keyboardTextureLoaded_timer); + for (var i = 0; i < keyProperties.length; i++) { + tthis.keys.push(new KeyboardKey(tthis, keyProperties[i])); + } + if (keyboard.onFullyLoaded != null) { + tthis.onFullyLoaded(); + } + } + }; + this.keyboardTextureLoaded_timer = Script.setInterval(this.keyboardTextureLoaded, 250); +} + +function Cursor() { + var tthis = this; + this.x = windowDimensions.x / 2; + this.y = windowDimensions.y / 2; + this.overlay = Overlays.addOverlay("image", { + x: this.x, + y: this.y, + width: CURSOR_WIDTH, + height: CURSOR_HEIGHT, + imageURL: CURSOR_URL, + alpha: 1 + }); + this.remove = function() { + Overlays.deleteOverlay(this.overlay); + }; + this.getPosition = function() { + return {x: tthis.getX(), y: tthis.getY()}; + }; + this.getX = function() { + return tthis.x; + }; + this.getY = function() { + return tthis.y; + }; + this.onUpdate = null; + this.update = function() { + var newWindowDimensions = Controller.getViewportDimensions(); + if (newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y) { + windowDimensions = newWindowDimensions; + keyboard.rescale(); + Overlays.editOverlay(text, { + y: windowDimensions.y - keyboard.height() - 260, + width: windowDimensions.x + }); + } + var editobject = {}; + if (MyAvatar.getHeadFinalYaw() <= VIEW_ANGLE_BY_TWO && MyAvatar.getHeadFinalYaw() >= -1 * VIEW_ANGLE_BY_TWO) { + angle = ((-1 * MyAvatar.getHeadFinalYaw()) + VIEW_ANGLE_BY_TWO) / VIEW_ANGLE; + tthis.x = angle * windowDimensions.x; + editobject.x = tthis.x - (CURSOR_WIDTH / 2); + } + if (MyAvatar.getHeadFinalPitch() <= VIEW_ANGLE_BY_TWO && MyAvatar.getHeadFinalPitch() >= -1 * VIEW_ANGLE_BY_TWO) { + angle = ((-1 * MyAvatar.getHeadFinalPitch()) + VIEW_ANGLE_BY_TWO) / VIEW_ANGLE; + tthis.y = angle * windowDimensions.y; + editobject.y = tthis.y - (CURSOR_HEIGHT / 2); + } + if (Object.keys(editobject).length > 0) { + Overlays.editOverlay(tthis.overlay, editobject); + if (tthis.onUpdate != null) { + tthis.onUpdate(tthis.getPosition()); + } + } + }; + Script.update.connect(this.update); +} + +function keyPressEvent(event) { + if (event.key === SPACEBAR_CHARCODE) { + keyboard.pressFocussedKey(); + } +} + +function keyReleaseEvent(event) { + if (event.key === SPACEBAR_CHARCODE) { + keyboard.releaseKeys(); + } +} + +function scriptEnding() { + keyboard.remove(); + cursor.remove(); + Overlays.deleteOverlay(text); + Overlays.deleteOverlay(textSizeMeasureOverlay); + Controller.releaseKeyEvents({key: SPACEBAR_CHARCODE}); +} +Controller.captureKeyEvents({key: SPACEBAR_CHARCODE}); +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Script.scriptEnding.connect(scriptEnding); \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index eb788ac49a..38dd02c655 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -107,7 +107,7 @@ endif() add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM}) # link required hifi libraries -link_hifi_libraries(shared octree voxels fbx metavoxels networking entities avatars audio animation script-engine physics) +link_hifi_libraries(shared octree voxels gpu fbx metavoxels networking entities avatars audio animation script-engine physics) # find any optional and required libraries find_package(ZLIB REQUIRED) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 85b21b073f..2d92a99cab 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -165,8 +165,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _scaleMirror(1.0f), _rotateMirror(0.0f), _raiseMirror(0.0f), - _mouseX(0), - _mouseY(0), _lastMouseMove(usecTimestampNow()), _lastMouseMoveWasSimulated(false), _mouseHidden(false), @@ -1132,7 +1130,7 @@ void Application::keyPressEvent(QKeyEvent* event) { if (!event->isAutoRepeat()) { // this starts an HFActionEvent HFActionEvent startActionEvent(HFActionEvent::startType(), - _viewFrustum.computePickRay(0.5f, 0.5f)); + _myCamera.computeViewPickRay(0.5f, 0.5f)); sendEvent(this, &startActionEvent); } @@ -1223,7 +1221,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) { case Qt::Key_Space: { if (!event->isAutoRepeat()) { // this ends the HFActionEvent - HFActionEvent endActionEvent(HFActionEvent::endType(), _viewFrustum.computePickRay(0.5f, 0.5f)); + HFActionEvent endActionEvent(HFActionEvent::endType(), _myCamera.computeViewPickRay(0.5f, 0.5f)); sendEvent(this, &endActionEvent); } @@ -1254,7 +1252,6 @@ void Application::focusOutEvent(QFocusEvent* event) { } void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { - bool showMouse = true; // Used by application overlay to determine how to draw cursor(s) @@ -1283,13 +1280,9 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { _mouseHidden = false; _seenMouseMove = true; } - - _mouseX = event->x(); - _mouseY = event->y(); } void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { - if (!_aboutToQuit) { _entities.mousePressEvent(event, deviceID); } @@ -1304,20 +1297,20 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { if (activeWindow() == _window) { if (event->button() == Qt::LeftButton) { - _mouseX = event->x(); - _mouseY = event->y(); - _mouseDragStartedX = _mouseX; - _mouseDragStartedY = _mouseY; + _mouseDragStartedX = getTrueMouseX(); + _mouseDragStartedY = getTrueMouseY(); _mousePressed = true; - - if (_audio.mousePressEvent(_mouseX, _mouseY)) { - // stop propagation - return; - } - - if (_rearMirrorTools->mousePressEvent(_mouseX, _mouseY)) { - // stop propagation - return; + + if (mouseOnScreen()) { + if (_audio.mousePressEvent(getMouseX(), getMouseY())) { + // stop propagation + return; + } + + if (_rearMirrorTools->mousePressEvent(getMouseX(), getMouseY())) { + // stop propagation + return; + } } // nobody handled this - make it an action event on the _window object @@ -1346,15 +1339,14 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { if (activeWindow() == _window) { if (event->button() == Qt::LeftButton) { - _mouseX = event->x(); - _mouseY = event->y(); _mousePressed = false; - checkBandwidthMeterClick(); - if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::Stats) && mouseOnScreen()) { // let's set horizontal offset to give stats some margin to mirror int horizontalOffset = MIRROR_VIEW_WIDTH; - Stats::getInstance()->checkClick(_mouseX, _mouseY, _mouseDragStartedX, _mouseDragStartedY, horizontalOffset); + Stats::getInstance()->checkClick(getMouseX(), getMouseY(), + getMouseDragStartedX(), getMouseDragStartedY(), horizontalOffset); + checkBandwidthMeterClick(); } // fire an action end event @@ -1547,13 +1539,13 @@ void Application::idle() { void Application::checkBandwidthMeterClick() { // ... to be called upon button release - if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth) && Menu::getInstance()->isOptionChecked(MenuOption::Stats) && Menu::getInstance()->isOptionChecked(MenuOption::UserInterface) && - glm::compMax(glm::abs(glm::ivec2(_mouseX - _mouseDragStartedX, _mouseY - _mouseDragStartedY))) - <= BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH - && _bandwidthMeter.isWithinArea(_mouseX, _mouseY, _glWidget->width(), _glWidget->height())) { + glm::compMax(glm::abs(glm::ivec2(getMouseX() - getMouseDragStartedX(), + getMouseY() - getMouseDragStartedY()))) + <= BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH + && _bandwidthMeter.isWithinArea(getMouseX(), getMouseY(), _glWidget->width(), _glWidget->height())) { // The bandwidth meter is visible, the click didn't get dragged too far and // we actually hit the bandwidth meter @@ -1667,6 +1659,48 @@ glm::vec3 Application::getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVox (mouseVoxel.z + mouseVoxel.s / 2.0f) * TREE_SCALE); } +bool Application::mouseOnScreen() const { + if (OculusManager::isConnected()) { + return getMouseX() >= 0 && getMouseX() <= _glWidget->getDeviceWidth() && + getMouseY() >= 0 && getMouseY() <= _glWidget->getDeviceHeight(); + } + return true; +} + +int Application::getMouseX() const { + if (OculusManager::isConnected()) { + glm::vec2 pos = _applicationOverlay.screenToOverlay(glm::vec2(getTrueMouseX(), getTrueMouseY())); + return pos.x; + } + return getTrueMouseX(); +} + +int Application::getMouseY() const { + if (OculusManager::isConnected()) { + glm::vec2 pos = _applicationOverlay.screenToOverlay(glm::vec2(getTrueMouseX(), getTrueMouseY())); + return pos.y; + } + return getTrueMouseY(); +} + +int Application::getMouseDragStartedX() const { + if (OculusManager::isConnected()) { + glm::vec2 pos = _applicationOverlay.screenToOverlay(glm::vec2(getTrueMouseDragStartedX(), + getTrueMouseDragStartedY())); + return pos.x; + } + return getTrueMouseDragStartedX(); +} + +int Application::getMouseDragStartedY() const { + if (OculusManager::isConnected()) { + glm::vec2 pos = _applicationOverlay.screenToOverlay(glm::vec2(getTrueMouseDragStartedX(), + getTrueMouseDragStartedY())); + return pos.y; + } + return getTrueMouseDragStartedY(); +} + FaceTracker* Application::getActiveFaceTracker() { return (_dde.isActive() ? static_cast(&_dde) : (_faceshift.isActive() ? static_cast(&_faceshift) : @@ -1897,10 +1931,6 @@ void Application::init() { _voxelShader.init(); _pointShader.init(); - _mouseX = _glWidget->width() / 2; - _mouseY = _glWidget->height() / 2; - QCursor::setPos(_mouseX, _mouseY); - // TODO: move _myAvatar out of Application. Move relevant code to MyAvataar or AvatarManager _avatarManager.init(); _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); @@ -2077,11 +2107,13 @@ void Application::updateMouseRay() { // if the mouse pointer isn't visible, act like it's at the center of the screen float x = 0.5f, y = 0.5f; if (!_mouseHidden) { - x = _mouseX / (float)_glWidget->width(); - y = _mouseY / (float)_glWidget->height(); + x = getTrueMouseX() / (float)_glWidget->width(); + y = getTrueMouseY() / (float)_glWidget->height(); } - _viewFrustum.computePickRay(x, y, _mouseRayOrigin, _mouseRayDirection); - + PickRay pickRay = _myCamera.computeViewPickRay(x, y); + _mouseRayOrigin = pickRay.origin; + _mouseRayDirection = pickRay.direction; + // adjust for mirroring if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { glm::vec3 mouseRayOffset = _mouseRayOrigin - _viewFrustum.getPosition(); @@ -2310,7 +2342,20 @@ void Application::update(float deltaTime) { _prioVR.update(deltaTime); } - + + static QCursor cursor; + if (OculusManager::isConnected() && + Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)){ + if (_window->cursor().shape() != Qt::BlankCursor) { + qDebug() << "Hiding cursor" << _window->cursor().shape(); + cursor = _window->cursor(); + _window->setCursor(QCursor(Qt::BlankCursor)); + } + } else if(_window->cursor().shape() == Qt::BlankCursor) { + qDebug() << "Showing cursor" << _window->cursor().shape(); + _window->setCursor(cursor); + } + // Dispatch input events _controllerScriptingInterface.updateInputControllers(); @@ -2910,7 +2955,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly, RenderAr // transform by eye offset // load the view frustum - loadViewFrustum(whichCamera, _viewFrustum); + loadViewFrustum(whichCamera, _displayViewFrustum); // flip x if in mirror mode (also requires reversing winding order for backface culling) if (whichCamera.getMode() == CAMERA_MODE_MIRROR) { @@ -3184,7 +3229,7 @@ void Application::computeOffAxisFrustum(float& left, float& right, float& bottom float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const { // allow 3DTV/Oculus to override parameters from camera - _viewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); + _displayViewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); if (OculusManager::isConnected()) { OculusManager::overrideOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); @@ -3513,9 +3558,6 @@ void Application::deleteVoxelAt(const VoxelDetail& voxel) { } void Application::resetSensors() { - _mouseX = _glWidget->width() / 2; - _mouseY = _glWidget->height() / 2; - _faceshift.reset(); _visage.reset(); _dde.reset(); @@ -3528,7 +3570,7 @@ void Application::resetSensors() { QScreen* currentScreen = _window->windowHandle()->screen(); QWindow* mainWindow = _window->windowHandle(); QPoint windowCenter = mainWindow->geometry().center(); - QCursor::setPos(currentScreen, windowCenter); + _glWidget->cursor().setPos(currentScreen, windowCenter); _myAvatar->reset(); diff --git a/interface/src/Application.h b/interface/src/Application.h index b737141b40..58dec1da1f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -196,6 +196,7 @@ public: const AudioReflector* getAudioReflector() const { return &_audioReflector; } Camera* getCamera() { return &_myCamera; } ViewFrustum* getViewFrustum() { return &_viewFrustum; } + ViewFrustum* getDisplayViewFrustum() { return &_displayViewFrustum; } ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; } VoxelImporter* getVoxelImporter() { return &_voxelImporter; } VoxelSystem* getVoxels() { return &_voxels; } @@ -213,8 +214,15 @@ public: bool isMouseHidden() const { return _mouseHidden; } const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; } const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; } - int getMouseX() const { return _mouseX; } - int getMouseY() const { return _mouseY; } + bool mouseOnScreen() const; + int getMouseX() const; + int getMouseY() const; + int getTrueMouseX() const { return _glWidget->mapFromGlobal(QCursor::pos()).x(); } + int getTrueMouseY() const { return _glWidget->mapFromGlobal(QCursor::pos()).y(); } + int getMouseDragStartedX() const; + int getMouseDragStartedY() const; + int getTrueMouseDragStartedX() const { return _mouseDragStartedX; } + int getTrueMouseDragStartedY() const { return _mouseDragStartedY; } bool getLastMouseMoveWasSimulated() const { return _lastMouseMoveWasSimulated;; } Faceshift* getFaceshift() { return &_faceshift; } Visage* getVisage() { return &_visage; } @@ -517,6 +525,7 @@ private: ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. ViewFrustum _lastQueriedViewFrustum; /// last view frustum used to query octree servers (voxels) + ViewFrustum _displayViewFrustum; ViewFrustum _shadowViewFrustum; quint64 _lastQueriedTime; @@ -554,8 +563,6 @@ private: Environment _environment; - int _mouseX; - int _mouseY; int _mouseDragStartedX; int _mouseDragStartedY; quint64 _lastMouseMove; diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 6e5a83790e..d069afb96a 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -97,20 +97,17 @@ void Camera::setFarClip(float f) { PickRay Camera::computePickRay(float x, float y) { float screenWidth = Application::getInstance()->getGLWidget()->width(); float screenHeight = Application::getInstance()->getGLWidget()->height(); - PickRay result; - if (OculusManager::isConnected()) { - result.origin = getPosition(); - Application::getInstance()->getApplicationOverlay().computeOculusPickRay(x / screenWidth, y / screenHeight, result.direction); - } else { - Application::getInstance()->getViewFrustum()->computePickRay(x / screenWidth, y / screenHeight, - result.origin, result.direction); - } - return result; + + return computeViewPickRay(x / screenWidth, y / screenHeight); } PickRay Camera::computeViewPickRay(float xRatio, float yRatio) { PickRay result; - Application::getInstance()->getViewFrustum()->computePickRay(xRatio, yRatio, result.origin, result.direction); + if (OculusManager::isConnected()) { + Application::getInstance()->getApplicationOverlay().computeOculusPickRay(xRatio, yRatio, result.origin, result.direction); + } else { + Application::getInstance()->getViewFrustum()->computePickRay(xRatio, yRatio, result.origin, result.direction); + } return result; } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 0d46c4020d..138828d3e8 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -367,6 +367,7 @@ namespace MenuOption { const QString DontCullTooSmallMeshParts = "Don't Cull Too Small Mesh Parts"; const QString DontReduceMaterialSwitches = "Don't Attempt to Reduce Material Switches"; const QString DontRenderEntitiesAsScene = "Don't Render Entities as Scene"; + const QString DontDoPrecisionPicking = "Don't Do Precision Picking"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DecreaseVoxelSize = "Decrease Voxel Size"; const QString DisableActivityLogger = "Disable Activity Logger"; @@ -378,6 +379,7 @@ namespace MenuOption { const QString DisplayHandTargets = "Show Hand Targets"; const QString DisplayHermiteData = "Display Hermite Data"; const QString DisplayModelBounds = "Display Model Bounds"; + const QString DisplayModelTriangles = "Display Model Triangles"; const QString DisplayModelElementChildProxies = "Display Model Element Children"; const QString DisplayModelElementProxy = "Display Model Element Bounds"; const QString DisplayTimingDetails = "Display Timing Details"; diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 996b92e22d..87060d7dfa 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -21,6 +21,7 @@ #include +#include #include #include @@ -192,7 +193,7 @@ static const float EIGHT_BIT_MAXIMUM_RECIPROCAL = 1.0f / EIGHT_BIT_MAXIMUM; void MetavoxelSystem::render() { // update the frustum - ViewFrustum* viewFrustum = Application::getInstance()->getViewFrustum(); + ViewFrustum* viewFrustum = Application::getInstance()->getDisplayViewFrustum(); _frustum.set(viewFrustum->getFarTopLeft(), viewFrustum->getFarTopRight(), viewFrustum->getFarBottomLeft(), viewFrustum->getFarBottomRight(), viewFrustum->getNearTopLeft(), viewFrustum->getNearTopRight(), viewFrustum->getNearBottomLeft(), viewFrustum->getNearBottomRight()); @@ -461,6 +462,34 @@ void MetavoxelSystem::render() { _voxelBaseBatches.clear(); } + if (!_hermiteBatches.isEmpty() && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHermiteData)) { + Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true, true); + + glEnableClientState(GL_VERTEX_ARRAY); + + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glNormal3f(0.0f, 1.0f, 0.0f); + + Application::getInstance()->getDeferredLightingEffect()->bindSimpleProgram(); + + foreach (const HermiteBatch& batch, _hermiteBatches) { + batch.vertexBuffer->bind(); + + glVertexPointer(3, GL_FLOAT, 0, 0); + + glDrawArrays(GL_LINES, 0, batch.vertexCount); + + batch.vertexBuffer->release(); + } + + Application::getInstance()->getDeferredLightingEffect()->releaseSimpleProgram(); + + glDisableClientState(GL_VERTEX_ARRAY); + + Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true, false); + } + _hermiteBatches.clear(); + // give external parties a chance to join in emit rendering(); } @@ -550,10 +579,53 @@ void MetavoxelSystem::setVoxelMaterial(const SharedObjectPointer& spanner, const applyMaterialEdit(edit, true); } -class SpannerCursorRenderVisitor : public SpannerVisitor { +void MetavoxelSystem::deleteTextures(int heightTextureID, int colorTextureID, int materialTextureID) const { + glDeleteTextures(1, (const GLuint*)&heightTextureID); + glDeleteTextures(1, (const GLuint*)&colorTextureID); + glDeleteTextures(1, (const GLuint*)&materialTextureID); +} + +class SpannerRenderVisitor : public SpannerVisitor { public: - SpannerCursorRenderVisitor(const Box& bounds); + SpannerRenderVisitor(const MetavoxelLOD& lod); + + virtual int visit(MetavoxelInfo& info); + virtual bool visit(Spanner* spanner); + +protected: + + int _containmentDepth; +}; + +SpannerRenderVisitor::SpannerRenderVisitor(const MetavoxelLOD& lod) : + SpannerVisitor(QVector() << AttributeRegistry::getInstance()->getSpannersAttribute(), + QVector(), QVector(), lod, + encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())), + _containmentDepth(INT_MAX) { +} + +int SpannerRenderVisitor::visit(MetavoxelInfo& info) { + if (_containmentDepth >= _depth) { + Frustum::IntersectionType intersection = Application::getInstance()->getMetavoxels()->getFrustum().getIntersectionType( + info.getBounds()); + if (intersection == Frustum::NO_INTERSECTION) { + return STOP_RECURSION; + } + _containmentDepth = (intersection == Frustum::CONTAINS_INTERSECTION) ? _depth : INT_MAX; + } + return SpannerVisitor::visit(info); +} + +bool SpannerRenderVisitor::visit(Spanner* spanner) { + spanner->getRenderer()->render(_lod, _containmentDepth <= _depth); + return true; +} + +class SpannerCursorRenderVisitor : public SpannerRenderVisitor { +public: + + SpannerCursorRenderVisitor(const MetavoxelLOD& lod, const Box& bounds); virtual bool visit(Spanner* spanner); @@ -564,20 +636,20 @@ private: Box _bounds; }; -SpannerCursorRenderVisitor::SpannerCursorRenderVisitor(const Box& bounds) : - SpannerVisitor(QVector() << AttributeRegistry::getInstance()->getSpannersAttribute()), +SpannerCursorRenderVisitor::SpannerCursorRenderVisitor(const MetavoxelLOD& lod, const Box& bounds) : + SpannerRenderVisitor(lod), _bounds(bounds) { } bool SpannerCursorRenderVisitor::visit(Spanner* spanner) { if (spanner->isHeightfield()) { - spanner->getRenderer()->render(true); + spanner->getRenderer()->render(_lod, _containmentDepth <= _depth, true); } return true; } int SpannerCursorRenderVisitor::visit(MetavoxelInfo& info) { - return info.getBounds().intersects(_bounds) ? SpannerVisitor::visit(info) : STOP_RECURSION; + return info.getBounds().intersects(_bounds) ? SpannerRenderVisitor::visit(info) : STOP_RECURSION; } void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float radius) { @@ -604,7 +676,7 @@ void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float r glActiveTexture(GL_TEXTURE0); glm::vec3 extents(radius, radius, radius); - SpannerCursorRenderVisitor visitor(Box(position - extents, position + extents)); + SpannerCursorRenderVisitor visitor(getLOD(), Box(position - extents, position + extents)); guide(visitor); _heightfieldCursorProgram.release(); @@ -678,7 +750,7 @@ void MetavoxelSystem::renderVoxelCursor(const glm::vec3& position, float radius) _heightfieldCursorProgram.bind(); - SpannerCursorRenderVisitor spannerVisitor(bounds); + SpannerCursorRenderVisitor spannerVisitor(getLOD(), bounds); guide(spannerVisitor); _heightfieldCursorProgram.release(); @@ -1024,30 +1096,6 @@ VoxelBuffer::VoxelBuffer(const QVector& vertices, const QVector _materials(materials) { } -static bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, - const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance) { - glm::vec3 firstSide = v0 - v1; - glm::vec3 secondSide = v2 - v1; - glm::vec3 normal = glm::cross(secondSide, firstSide); - float dividend = glm::dot(normal, v1) - glm::dot(origin, normal); - if (dividend > 0.0f) { - return false; // origin below plane - } - float divisor = glm::dot(normal, direction); - if (divisor > -EPSILON) { - return false; - } - float t = dividend / divisor; - glm::vec3 point = origin + direction * t; - if (glm::dot(normal, glm::cross(point - v1, firstSide)) > 0.0f && - glm::dot(normal, glm::cross(secondSide, point - v1)) > 0.0f && - glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) { - distance = t; - return true; - } - return false; -} - bool VoxelBuffer::findFirstRayIntersection(const glm::vec3& entry, const glm::vec3& origin, const glm::vec3& direction, float& distance) const { float highest = _size - 1.0f; @@ -1192,31 +1240,18 @@ void VoxelBuffer::render(bool cursor) { } } - if (_hermiteCount > 0 && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHermiteData)) { + if (_hermiteCount > 0) { if (!_hermiteBuffer.isCreated()) { _hermiteBuffer.create(); _hermiteBuffer.bind(); - _hermiteBuffer.allocate(_hermite.constData(), _hermite.size() * sizeof(glm::vec3)); + _hermiteBuffer.allocate(_hermite.constData(), _hermite.size() * sizeof(glm::vec3)); + _hermiteBuffer.release(); _hermite.clear(); - - } else { - _hermiteBuffer.bind(); } - - glVertexPointer(3, GL_FLOAT, 0, 0); - - Application::getInstance()->getDeferredLightingEffect()->getSimpleProgram().bind(); - - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - glNormal3f(0.0f, 1.0f, 0.0f); - - glLineWidth(1.0f); - - glDrawArrays(GL_LINES, 0, _hermiteCount); - - Application::getInstance()->getDeferredLightingEffect()->getSimpleProgram().release(); - - _hermiteBuffer.release(); + HermiteBatch hermiteBatch; + hermiteBatch.vertexBuffer = &_hermiteBuffer; + hermiteBatch.vertexCount = _hermiteCount; + Application::getInstance()->getMetavoxels()->addHermiteBatch(hermiteBatch); } } @@ -1880,43 +1915,6 @@ void DefaultMetavoxelRendererImplementation::simulate(MetavoxelData& data, float data.guide(spannerSimulateVisitor); } -class SpannerRenderVisitor : public SpannerVisitor { -public: - - SpannerRenderVisitor(const MetavoxelLOD& lod); - - virtual int visit(MetavoxelInfo& info); - virtual bool visit(Spanner* spanner); - -private: - - int _containmentDepth; -}; - -SpannerRenderVisitor::SpannerRenderVisitor(const MetavoxelLOD& lod) : - SpannerVisitor(QVector() << AttributeRegistry::getInstance()->getSpannersAttribute(), - QVector(), QVector(), lod, - encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())), - _containmentDepth(INT_MAX) { -} - -int SpannerRenderVisitor::visit(MetavoxelInfo& info) { - if (_containmentDepth >= _depth) { - Frustum::IntersectionType intersection = Application::getInstance()->getMetavoxels()->getFrustum().getIntersectionType( - info.getBounds()); - if (intersection == Frustum::NO_INTERSECTION) { - return STOP_RECURSION; - } - _containmentDepth = (intersection == Frustum::CONTAINS_INTERSECTION) ? _depth : INT_MAX; - } - return SpannerVisitor::visit(info); -} - -bool SpannerRenderVisitor::visit(Spanner* spanner) { - spanner->getRenderer()->render(); - return true; -} - class BufferRenderVisitor : public MetavoxelVisitor { public: @@ -1932,7 +1930,7 @@ private: BufferRenderVisitor::BufferRenderVisitor(const AttributePointer& attribute) : MetavoxelVisitor(QVector() << attribute), - _order(encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())), + _order(encodeOrder(Application::getInstance()->getDisplayViewFrustum()->getDirection())), _containmentDepth(INT_MAX) { } @@ -1970,7 +1968,7 @@ SphereRenderer::SphereRenderer() { } -void SphereRenderer::render(bool cursor) { +void SphereRenderer::render(const MetavoxelLOD& lod, bool contained, bool cursor) { Sphere* sphere = static_cast(_spanner); const QColor& color = sphere->getColor(); glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF()); @@ -1990,7 +1988,7 @@ void SphereRenderer::render(bool cursor) { CuboidRenderer::CuboidRenderer() { } -void CuboidRenderer::render(bool cursor) { +void CuboidRenderer::render(const MetavoxelLOD& lod, bool contained, bool cursor) { Cuboid* cuboid = static_cast(_spanner); const QColor& color = cuboid->getColor(); glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF()); @@ -2041,7 +2039,7 @@ void StaticModelRenderer::simulate(float deltaTime) { _model->simulate(deltaTime); } -void StaticModelRenderer::render(bool cursor) { +void StaticModelRenderer::render(const MetavoxelLOD& lod, bool contained, bool cursor) { _model->render(); } @@ -2073,57 +2071,66 @@ void StaticModelRenderer::applyURL(const QUrl& url) { } HeightfieldRenderer::HeightfieldRenderer() { - glGenTextures(1, &_heightTextureID); - glBindTexture(GL_TEXTURE_2D, _heightTextureID); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glGenTextures(1, &_colorTextureID); - glBindTexture(GL_TEXTURE_2D, _colorTextureID); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glGenTextures(1, &_materialTextureID); - glBindTexture(GL_TEXTURE_2D, _materialTextureID); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glBindTexture(GL_TEXTURE_2D, 0); } -HeightfieldRenderer::~HeightfieldRenderer() { - glDeleteTextures(1, &_heightTextureID); - glDeleteTextures(1, &_colorTextureID); - glDeleteTextures(1, &_materialTextureID); -} +const int X_MAXIMUM_FLAG = 1; +const int Y_MAXIMUM_FLAG = 2; -void HeightfieldRenderer::init(Spanner* spanner) { - SpannerRenderer::init(spanner); - - Heightfield* heightfield = static_cast(spanner); - applyHeight(heightfield->getHeight()); - applyColor(heightfield->getColor()); - applyMaterial(heightfield->getMaterial()); - - connect(heightfield, &Heightfield::heightChanged, this, &HeightfieldRenderer::applyHeight); - connect(heightfield, &Heightfield::colorChanged, this, &HeightfieldRenderer::applyColor); - connect(heightfield, &Heightfield::materialChanged, this, &HeightfieldRenderer::applyMaterial); -} - -void HeightfieldRenderer::render(bool cursor) { - // create the buffer objects lazily - Heightfield* heightfield = static_cast(_spanner); - if (!heightfield->getHeight()) { +static void renderNode(const HeightfieldNodePointer& node, Heightfield* heightfield, const MetavoxelLOD& lod, + const glm::vec2& minimum, float size, bool contained, bool cursor) { + const glm::quat& rotation = heightfield->getRotation(); + glm::vec3 scale(heightfield->getScale() * size, heightfield->getScale() * heightfield->getAspectY(), + heightfield->getScale() * heightfield->getAspectZ() * size); + glm::vec3 translation = heightfield->getTranslation() + rotation * glm::vec3(minimum.x * heightfield->getScale(), + 0.0f, minimum.y * heightfield->getScale() * heightfield->getAspectZ()); + if (!contained) { + Frustum::IntersectionType type = Application::getInstance()->getMetavoxels()->getFrustum().getIntersectionType( + glm::translate(translation) * glm::mat4_cast(rotation) * Box(glm::vec3(), scale)); + if (type == Frustum::NO_INTERSECTION) { + return; + } + if (type == Frustum::CONTAINS_INTERSECTION) { + contained = true; + } + } + if (!node->isLeaf() && lod.shouldSubdivide(minimum, size)) { + float nextSize = size * 0.5f; + for (int i = 0; i < HeightfieldNode::CHILD_COUNT; i++) { + renderNode(node->getChild(i), heightfield, lod, minimum + glm::vec2(i & X_MAXIMUM_FLAG ? nextSize : 0.0f, + i & Y_MAXIMUM_FLAG ? nextSize : 0.0f), nextSize, contained, cursor); + } return; } - int width = heightfield->getHeight()->getWidth(); - int height = heightfield->getHeight()->getContents().size() / width; - + HeightfieldNodeRenderer* renderer = static_cast(node->getRenderer()); + if (!renderer) { + node->setRenderer(renderer = new HeightfieldNodeRenderer()); + } + renderer->render(node, translation, rotation, scale, cursor); +} + +void HeightfieldRenderer::render(const MetavoxelLOD& lod, bool contained, bool cursor) { + Heightfield* heightfield = static_cast(_spanner); + renderNode(heightfield->getRoot(), heightfield, heightfield->transformLOD(lod), glm::vec2(), 1.0f, contained, cursor); +} + +HeightfieldNodeRenderer::HeightfieldNodeRenderer() : + _heightTextureID(0), + _colorTextureID(0), + _materialTextureID(0) { +} + +HeightfieldNodeRenderer::~HeightfieldNodeRenderer() { + QMetaObject::invokeMethod(Application::getInstance()->getMetavoxels(), "deleteTextures", Q_ARG(int, _heightTextureID), + Q_ARG(int, _colorTextureID), Q_ARG(int, _materialTextureID)); +} + +void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const glm::vec3& translation, + const glm::quat& rotation, const glm::vec3& scale, bool cursor) { + if (!node->getHeight()) { + return; + } + int width = node->getHeight()->getWidth(); + int height = node->getHeight()->getContents().size() / width; int innerWidth = width - 2 * HeightfieldHeight::HEIGHT_BORDER; int innerHeight = height - 2 * HeightfieldHeight::HEIGHT_BORDER; int vertexCount = width * height; @@ -2180,17 +2187,71 @@ void HeightfieldRenderer::render(bool cursor) { bufferPair.second.allocate(indices.constData(), indexCount * sizeof(int)); bufferPair.second.release(); } + if (_heightTextureID == 0) { + glGenTextures(1, &_heightTextureID); + glBindTexture(GL_TEXTURE_2D, _heightTextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + const QVector& heightContents = node->getHeight()->getContents(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R16, width, height, 0, + GL_RED, GL_UNSIGNED_SHORT, heightContents.constData()); + + glGenTextures(1, &_colorTextureID); + glBindTexture(GL_TEXTURE_2D, _colorTextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if (node->getColor()) { + const QByteArray& contents = node->getColor()->getContents(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, node->getColor()->getWidth(), + contents.size() / (node->getColor()->getWidth() * DataBlock::COLOR_BYTES), + 0, GL_RGB, GL_UNSIGNED_BYTE, contents.constData()); + + } else { + const quint8 WHITE_COLOR[] = { 255, 255, 255 }; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, WHITE_COLOR); + } + + glGenTextures(1, &_materialTextureID); + glBindTexture(GL_TEXTURE_2D, _materialTextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if (node->getMaterial()) { + const QByteArray& contents = node->getMaterial()->getContents(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, node->getMaterial()->getWidth(), + contents.size() / node->getMaterial()->getWidth(), + 0, GL_RED, GL_UNSIGNED_BYTE, contents.constData()); + + const QVector& materials = node->getMaterial()->getMaterials(); + _networkTextures.resize(materials.size()); + for (int i = 0; i < materials.size(); i++) { + const SharedObjectPointer& material = materials.at(i); + if (material) { + _networkTextures[i] = Application::getInstance()->getTextureCache()->getTexture( + static_cast(material.data())->getDiffuse(), SPLAT_TEXTURE); + } + } + } else { + const quint8 ZERO_VALUE = 0; + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &ZERO_VALUE); + } + glBindTexture(GL_TEXTURE_2D, 0); + } - float xScale = heightfield->getScale(), zScale = xScale * heightfield->getAspectZ(); if (cursor) { bufferPair.first.bind(); bufferPair.second.bind(); glPushMatrix(); - glTranslatef(heightfield->getTranslation().x, heightfield->getTranslation().y, heightfield->getTranslation().z); - glm::vec3 axis = glm::axis(heightfield->getRotation()); - glRotatef(glm::degrees(glm::angle(heightfield->getRotation())), axis.x, axis.y, axis.z); - glScalef(xScale, xScale * heightfield->getAspectY(), zScale); + glTranslatef(translation.x, translation.y, translation.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glScalef(scale.x, scale.y, scale.z); HeightfieldPoint* point = 0; glVertexPointer(3, GL_FLOAT, sizeof(HeightfieldPoint), &point->vertex); @@ -2208,13 +2269,12 @@ void HeightfieldRenderer::render(bool cursor) { bufferPair.second.release(); return; } - HeightfieldBaseLayerBatch baseBatch; baseBatch.vertexBuffer = &bufferPair.first; baseBatch.indexBuffer = &bufferPair.second; - baseBatch.translation = heightfield->getTranslation(); - baseBatch.rotation = heightfield->getRotation(); - baseBatch.scale = glm::vec3(xScale, xScale * heightfield->getAspectY(), zScale); + baseBatch.translation = translation; + baseBatch.rotation = rotation; + baseBatch.scale = scale; baseBatch.vertexCount = vertexCount; baseBatch.indexCount = indexCount; baseBatch.heightTextureID = _heightTextureID; @@ -2223,13 +2283,13 @@ void HeightfieldRenderer::render(bool cursor) { baseBatch.colorScale = glm::vec2((float)width / innerWidth, (float)height / innerHeight); Application::getInstance()->getMetavoxels()->addHeightfieldBaseBatch(baseBatch); - if (heightfield->getMaterial() && !_networkTextures.isEmpty()) { + if (!_networkTextures.isEmpty()) { HeightfieldSplatBatch splatBatch; splatBatch.vertexBuffer = &bufferPair.first; splatBatch.indexBuffer = &bufferPair.second; - splatBatch.translation = heightfield->getTranslation(); - splatBatch.rotation = heightfield->getRotation(); - splatBatch.scale = glm::vec3(xScale, xScale * heightfield->getAspectY(), zScale); + splatBatch.translation = translation; + splatBatch.rotation = rotation; + splatBatch.scale = scale; splatBatch.vertexCount = vertexCount; splatBatch.indexCount = indexCount; splatBatch.heightTextureID = _heightTextureID; @@ -2237,10 +2297,10 @@ void HeightfieldRenderer::render(bool cursor) { splatBatch.materialTextureID = _materialTextureID; splatBatch.textureScale = glm::vec2((float)width / innerWidth, (float)height / innerHeight); splatBatch.splatTextureOffset = glm::vec2( - glm::dot(heightfield->getTranslation(), heightfield->getRotation() * glm::vec3(1.0f, 0.0f, 0.0f)) / xScale, - glm::dot(heightfield->getTranslation(), heightfield->getRotation() * glm::vec3(0.0f, 0.0f, 1.0f)) / zScale); + glm::dot(translation, rotation * glm::vec3(1.0f, 0.0f, 0.0f)) / scale.x, + glm::dot(translation, rotation * glm::vec3(0.0f, 0.0f, 1.0f)) / scale.z); - const QVector& materials = heightfield->getMaterial()->getMaterials(); + const QVector& materials = node->getMaterial()->getMaterials(); for (int i = 0; i < materials.size(); i += SPLAT_COUNT) { for (int j = 0; j < SPLAT_COUNT; j++) { int index = i + j; @@ -2248,8 +2308,8 @@ void HeightfieldRenderer::render(bool cursor) { const NetworkTexturePointer& texture = _networkTextures.at(index); if (texture) { MaterialObject* material = static_cast(materials.at(index).data()); - splatBatch.splatTextureScalesS[j] = xScale / material->getScaleS(); - splatBatch.splatTextureScalesT[j] = zScale / material->getScaleT(); + splatBatch.splatTextureScalesS[j] = scale.x / material->getScaleS(); + splatBatch.splatTextureScalesT[j] = scale.z / material->getScaleT(); splatBatch.splatTextureIDs[j] = texture->getID(); } else { @@ -2265,62 +2325,5 @@ void HeightfieldRenderer::render(bool cursor) { } } -void HeightfieldRenderer::applyHeight(const HeightfieldHeightPointer& height) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glBindTexture(GL_TEXTURE_2D, _heightTextureID); - if (height) { - const QVector& contents = height->getContents(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R16, height->getWidth(), contents.size() / height->getWidth(), 0, - GL_RED, GL_UNSIGNED_SHORT, contents.constData()); - - } else { - const quint16 ZERO_VALUE = 0; - glTexImage2D(GL_TEXTURE_2D, 0, GL_R16, 1, 1, 0, GL_RED, GL_UNSIGNED_SHORT, &ZERO_VALUE); - } - glBindTexture(GL_TEXTURE_2D, 0); -} - -void HeightfieldRenderer::applyColor(const HeightfieldColorPointer& color) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glBindTexture(GL_TEXTURE_2D, _colorTextureID); - if (color) { - const QByteArray& contents = color->getContents(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, color->getWidth(), - contents.size() / (color->getWidth() * DataBlock::COLOR_BYTES), 0, GL_RGB, GL_UNSIGNED_BYTE, contents.constData()); - - } else { - const quint8 WHITE_COLOR[] = { 255, 255, 255 }; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, WHITE_COLOR); - } - glBindTexture(GL_TEXTURE_2D, 0); -} - -void HeightfieldRenderer::applyMaterial(const HeightfieldMaterialPointer& material) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glBindTexture(GL_TEXTURE_2D, _materialTextureID); - if (material) { - const QByteArray& contents = material->getContents(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, material->getWidth(), contents.size() / material->getWidth(), 0, - GL_RED, GL_UNSIGNED_BYTE, contents.constData()); - - const QVector& materials = material->getMaterials(); - _networkTextures.resize(materials.size()); - for (int i = 0; i < materials.size(); i++) { - const SharedObjectPointer& material = materials.at(i); - if (material) { - _networkTextures[i] = Application::getInstance()->getTextureCache()->getTexture( - static_cast(material.data())->getDiffuse(), SPLAT_TEXTURE); - } else { - _networkTextures[i].clear(); - } - } - } else { - const quint8 ZERO_VALUE = 0; - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &ZERO_VALUE); - _networkTextures.clear(); - } - glBindTexture(GL_TEXTURE_2D, 0); -} - -QHash HeightfieldRenderer::_bufferPairs; +QHash HeightfieldNodeRenderer::_bufferPairs; diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index c1cdfd3624..26f3bd68a3 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -25,6 +25,7 @@ class HeightfieldBaseLayerBatch; class HeightfieldSplatBatch; +class HermiteBatch; class Model; class VoxelBatch; class VoxelSplatBatch; @@ -88,6 +89,10 @@ public: void addVoxelBaseBatch(const VoxelBatch& batch) { _voxelBaseBatches.append(batch); } void addVoxelSplatBatch(const VoxelSplatBatch& batch) { _voxelSplatBatches.append(batch); } + void addHermiteBatch(const HermiteBatch& batch) { _hermiteBatches.append(batch); } + + Q_INVOKABLE void deleteTextures(int heightTextureID, int colorTextureID, int materialTextureID) const; + signals: void rendering(); @@ -120,6 +125,7 @@ private: QVector _heightfieldSplatBatches; QVector _voxelBaseBatches; QVector _voxelSplatBatches; + QVector _hermiteBatches; ProgramObject _baseHeightfieldProgram; int _baseHeightScaleLocation; @@ -211,6 +217,13 @@ public: int materialIndex; }; +/// A batch containing Hermite data for debugging. +class HermiteBatch { +public: + QOpenGLBuffer* vertexBuffer; + int vertexCount; +}; + /// Generic abstract base class for objects that handle a signal. class SignalHandler : public QObject { Q_OBJECT @@ -380,7 +393,7 @@ public: Q_INVOKABLE SphereRenderer(); - virtual void render(bool cursor = false); + virtual void render(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false); }; /// Renders cuboids. @@ -391,7 +404,7 @@ public: Q_INVOKABLE CuboidRenderer(); - virtual void render(bool cursor = false); + virtual void render(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false); }; /// Renders static models. @@ -404,7 +417,7 @@ public: virtual void init(Spanner* spanner); virtual void simulate(float deltaTime); - virtual void render(bool cursor = false); + virtual void render(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false); virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; private slots: @@ -426,26 +439,29 @@ class HeightfieldRenderer : public SpannerRenderer { public: Q_INVOKABLE HeightfieldRenderer(); - virtual ~HeightfieldRenderer(); - virtual void init(Spanner* spanner); - virtual void render(bool cursor = false); - -private slots: - - void applyHeight(const HeightfieldHeightPointer& height); - void applyColor(const HeightfieldColorPointer& color); - void applyMaterial(const HeightfieldMaterialPointer& material); + virtual void render(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false); +}; +/// Renders a single quadtree node. +class HeightfieldNodeRenderer : public AbstractHeightfieldNodeRenderer { +public: + + HeightfieldNodeRenderer(); + virtual ~HeightfieldNodeRenderer(); + + void render(const HeightfieldNodePointer& node, const glm::vec3& translation, + const glm::quat& rotation, const glm::vec3& scale, bool cursor); + private: - + GLuint _heightTextureID; GLuint _colorTextureID; GLuint _materialTextureID; QVector _networkTextures; - typedef QPair IntPair; - typedef QPair BufferPair; + typedef QPair IntPair; + typedef QPair BufferPair; static QHash _bufferPairs; }; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 25916d761c..ca6a6db4a7 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -68,10 +68,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceWarning warn(showWarnings, "Application::updateAvatars()"); PerformanceTimer perfTimer("otherAvatars"); - Application* applicationInstance = Application::getInstance(); - glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin(); - glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection(); - + // simulate avatars AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index 6d227027ed..7a555eb902 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -540,8 +540,6 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) { //a magnification window was clicked on int clickX = pos.x(); int clickY = pos.y(); - //Checks for magnification window click - application->getApplicationOverlay().getClickLocation(clickX, clickY); //Set pos to the new click location, which may be the same if no magnification window is open pos.setX(clickX); pos.setY(clickY); diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 5aacd36a12..055cbf5026 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -635,22 +635,12 @@ void EntityTreeRenderer::deleteReleasedModels() { } PickRay EntityTreeRenderer::computePickRay(float x, float y) { - float screenWidth = Application::getInstance()->getGLWidget()->width(); - float screenHeight = Application::getInstance()->getGLWidget()->height(); - PickRay result; - if (OculusManager::isConnected()) { - Camera* camera = Application::getInstance()->getCamera(); - result.origin = camera->getPosition(); - Application::getInstance()->getApplicationOverlay().computeOculusPickRay(x / screenWidth, y / screenHeight, result.direction); - } else { - ViewFrustum* viewFrustum = Application::getInstance()->getViewFrustum(); - viewFrustum->computePickRay(x / screenWidth, y / screenHeight, result.origin, result.direction); - } - return result; + return Application::getInstance()->getCamera()->computePickRay(x, y); } -RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType) { +RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, + bool precisionPicking) { RayToEntityIntersectionResult result; if (_tree) { EntityTree* entityTree = static_cast(_tree); @@ -658,7 +648,8 @@ RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(cons OctreeElement* element; EntityItem* intersectedEntity = NULL; result.intersects = entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, - (void**)&intersectedEntity, lockType, &result.accurate); + (void**)&intersectedEntity, lockType, &result.accurate, + precisionPicking); if (result.intersects && intersectedEntity) { result.entityID = intersectedEntity->getEntityItemID(); result.properties = intersectedEntity->getProperties(); @@ -710,7 +701,9 @@ QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entity void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent"); PickRay ray = computePickRay(event->x(), event->y()); - RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock); + + bool precisionPicking = !Menu::getInstance()->isOptionChecked(MenuOption::DontDoPrecisionPicking); + RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); if (rayPickResult.intersects) { //qDebug() << "mousePressEvent over entity:" << rayPickResult.entityID; emit mousePressOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); @@ -734,7 +727,8 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent"); PickRay ray = computePickRay(event->x(), event->y()); - RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock); + bool precisionPicking = !Menu::getInstance()->isOptionChecked(MenuOption::DontDoPrecisionPicking); + RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); if (rayPickResult.intersects) { //qDebug() << "mouseReleaseEvent over entity:" << rayPickResult.entityID; emit mouseReleaseOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); @@ -768,7 +762,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent"); PickRay ray = computePickRay(event->x(), event->y()); - RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock); + + bool precisionPicking = false; // for mouse moves we do not do precision picking + RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking); if (rayPickResult.intersects) { QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index 0042dd495f..40df81b46c 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -117,7 +117,8 @@ private: QList _releasedModels; void renderProxies(const EntityItem* entity, RenderArgs* args); PickRay computePickRay(float x, float y); - RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType); + RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, + bool precisionPicking); EntityItemID _currentHoverOverEntityID; EntityItemID _currentClickingOnEntityID; diff --git a/interface/src/entities/RenderableLightEntityItem.cpp b/interface/src/entities/RenderableLightEntityItem.cpp index 91b2d35106..77dbb5da0b 100644 --- a/interface/src/entities/RenderableLightEntityItem.cpp +++ b/interface/src/entities/RenderableLightEntityItem.cpp @@ -93,7 +93,7 @@ void RenderableLightEntityItem::render(RenderArgs* args) { bool RenderableLightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject) const { + void** intersectedObject, bool precisionPicking) const { // TODO: this isn't really correct because we don't know if we actually live in the main tree of the applications's // EntityTreeRenderer. But we probably do. Technically we could be on the clipboard and someone might be trying to diff --git a/interface/src/entities/RenderableLightEntityItem.h b/interface/src/entities/RenderableLightEntityItem.h index 40fa31a4ce..2113f486cc 100644 --- a/interface/src/entities/RenderableLightEntityItem.h +++ b/interface/src/entities/RenderableLightEntityItem.h @@ -37,7 +37,7 @@ public: virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject) const; + void** intersectedObject, bool precisionPicking) const; }; diff --git a/interface/src/entities/RenderableModelEntityItem.cpp b/interface/src/entities/RenderableModelEntityItem.cpp index 6c8d85507d..8509fa1f67 100644 --- a/interface/src/entities/RenderableModelEntityItem.cpp +++ b/interface/src/entities/RenderableModelEntityItem.cpp @@ -173,10 +173,18 @@ void RenderableModelEntityItem::render(RenderArgs* args) { // is significantly more expensive. Is there a way to call this that doesn't cost us as much? PerformanceTimer perfTimer("model->render"); bool dontRenderAsScene = Menu::getInstance()->isOptionChecked(MenuOption::DontRenderEntitiesAsScene); - if (dontRenderAsScene) { - _model->render(alpha, modelRenderMode, args); - } else { - _model->renderInScene(alpha, args); + bool displayModelTriangles = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelTriangles); + bool rendered = false; + if (displayModelTriangles) { + rendered = _model->renderTriangleProxies(); + } + + if (!rendered) { + if (dontRenderAsScene) { + _model->render(alpha, modelRenderMode, args); + } else { + _model->renderInScene(alpha, args); + } } } else { // if we couldn't get a model, then just draw a cube @@ -257,7 +265,26 @@ EntityItemProperties RenderableModelEntityItem::getProperties() const { return properties; } - - +bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject, bool precisionPicking) const { + + glm::vec3 originInMeters = origin * (float)TREE_SCALE; + QString extraInfo; + float localDistance; + + //qDebug() << "RenderableModelEntityItem::findDetailedRayIntersection() precisionPicking:" << precisionPicking; + + bool intersectsModel = _model->findRayIntersectionAgainstSubMeshes(originInMeters, direction, + localDistance, face, extraInfo, precisionPicking); + + if (intersectsModel) { + // NOTE: findRayIntersectionAgainstSubMeshes() does work in meters, but we're expected to return + // results in tree scale. + distance = localDistance / (float)TREE_SCALE; + } + + return intersectsModel; // we only got here if we intersected our non-aabox +} diff --git a/interface/src/entities/RenderableModelEntityItem.h b/interface/src/entities/RenderableModelEntityItem.h index 0aa2578a6c..2401d0ea64 100644 --- a/interface/src/entities/RenderableModelEntityItem.h +++ b/interface/src/entities/RenderableModelEntityItem.h @@ -51,6 +51,11 @@ public: virtual void somethingChangedNotification() { _needsInitialSimulation = true; } virtual void render(RenderArgs* args); + virtual bool supportsDetailedRayIntersection() const { return true; } + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject, bool precisionPicking) const; + Model* getModel(EntityTreeRenderer* renderer); bool needsToCallUpdate() const; diff --git a/interface/src/renderer/DeferredLightingEffect.cpp b/interface/src/renderer/DeferredLightingEffect.cpp index 63d874cda7..be4e457131 100644 --- a/interface/src/renderer/DeferredLightingEffect.cpp +++ b/interface/src/renderer/DeferredLightingEffect.cpp @@ -232,8 +232,8 @@ void DeferredLightingEffect::render() { // enlarge the scales slightly to account for tesselation const float SCALE_EXPANSION = 0.05f; - const glm::vec3& eyePoint = Application::getInstance()->getViewFrustum()->getPosition(); - float nearRadius = glm::distance(eyePoint, Application::getInstance()->getViewFrustum()->getNearTopLeft()); + const glm::vec3& eyePoint = Application::getInstance()->getDisplayViewFrustum()->getPosition(); + float nearRadius = glm::distance(eyePoint, Application::getInstance()->getDisplayViewFrustum()->getNearTopLeft()); if (!_pointLights.isEmpty()) { _pointLight.bind(); diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index e523d7e608..2d876a287f 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -829,28 +829,44 @@ void GeometryReader::run() { return; } try { - std::string urlname = _url.path().toLower().toStdString(); - FBXGeometry fbxgeo; - if (_url.path().toLower().endsWith(".svo")) { - fbxgeo = readSVO(_reply->readAll()); - } else { - bool grabLightmaps = true; - float lightmapLevel = 1.0f; - // HACK: For monday 12/01/2014 we need to kill lighmaps loading in starchamber... - if (_url.path().toLower().endsWith("loungev4_11-18.fbx")) { - grabLightmaps = false; - } else if (_url.path().toLower().endsWith("apt8_reboot.fbx")) { - lightmapLevel = 4.0f; - } else if (_url.path().toLower().endsWith("palaceoforinthilian4.fbx")) { - lightmapLevel = 3.5f; - } - fbxgeo = readFBX(_reply->readAll(), _mapping, grabLightmaps, lightmapLevel); + if (!_reply) { + throw QString("Reply is NULL ?!"); } - QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, fbxgeo)); + std::string urlname = _url.path().toLower().toStdString(); + bool urlValid = true; + urlValid &= !urlname.empty(); + urlValid &= !_url.path().isEmpty(); + urlValid &= _url.path().toLower().endsWith(".fbx") + || _url.path().toLower().endsWith(".svo"); + if (urlValid) { + // Let's read the binaries from the network + QByteArray fileBinary = _reply->readAll(); + if (fileBinary.isEmpty() || fileBinary.isNull()) { + throw QString("Read File binary is empty?!"); + } + + FBXGeometry fbxgeo; + if (_url.path().toLower().endsWith(".svo")) { + fbxgeo = readSVO(fileBinary); + } else if (_url.path().toLower().endsWith(".fbx")) { + bool grabLightmaps = true; + float lightmapLevel = 1.0f; + // HACK: For monday 12/01/2014 we need to kill lighmaps loading in starchamber... + if (_url.path().toLower().endsWith("loungev4_11-18.fbx")) { + grabLightmaps = false; + } else if (_url.path().toLower().endsWith("apt8_reboot.fbx")) { + lightmapLevel = 4.0f; + } else if (_url.path().toLower().endsWith("palaceoforinthilian4.fbx")) { + lightmapLevel = 3.5f; + } + fbxgeo = readFBX(fileBinary, _mapping, grabLightmaps, lightmapLevel); + } + QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, fbxgeo)); + } else { + throw QString("url is invalid"); + } - // _url.path().toLower().endsWith(".svo") ? readSVO(_reply->readAll()) : readFBX(_reply->readAll(), _mapping))); - } catch (const QString& error) { qDebug() << "Error reading " << _url << ": " << error; QMetaObject::invokeMethod(geometry.data(), "finishedLoading", Q_ARG(bool, false)); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index b2570b7c28..71f5129a1e 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -54,6 +54,7 @@ Model::Model(QObject* parent) : _blendNumber(0), _appliedBlendNumber(0), _calculatedMeshBoxesValid(false), + _calculatedMeshTrianglesValid(false), _meshGroupsKnown(false) { // we may have been created in the network thread, but we live in the main thread @@ -269,7 +270,7 @@ void Model::init() { _program.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/model.vert"); _program.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model.frag"); _program.link(); - + initProgram(_program, _locations); _normalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, @@ -515,8 +516,61 @@ void Model::setJointStates(QVector states) { _boundingRadius = radius; } -bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, QString& extraInfo) const { +bool Model::renderTriangleProxies() { + if (!isActive()) { + return false; + } + if (_calculatedMeshTrianglesValid) { + int color = 0; + foreach (const QVector& meshTriangles, _calculatedMeshTriangles) { + switch(color) { + case 0: glColor3ub( 0, 0, 255); break; + case 1: glColor3ub( 0, 255, 0); break; + case 2: glColor3ub( 0, 255, 255); break; + case 3: glColor3ub(255, 0, 0); break; + case 4: glColor3ub(255, 0, 255); break; + case 5: glColor3ub(255, 255, 0); break; + case 6: glColor3ub( 0, 0, 128); break; + case 7: glColor3ub( 0, 128, 0); break; + case 8: glColor3ub( 0, 128, 128); break; + case 9: glColor3ub(128, 0, 0); break; + case 10: glColor3ub(128, 0, 128); break; + case 11: glColor3ub(128, 128, 0); break; + case 12: glColor3ub(128, 128, 255); break; + case 13: glColor3ub(128, 255, 128); break; + case 14: glColor3ub(128, 255, 255); break; + case 15: glColor3ub(255, 128, 128); break; + case 16: glColor3ub(255, 128, 255); break; + case 17: glColor3ub(255, 255, 128); break; + default: glColor3ub(255,255, 255); break; + } + + if (_calculatedMeshBoxes.size() > color) { + const AABox& box = _calculatedMeshBoxes[color]; + glm::vec3 center = box.calcCenter(); + glm::vec3 dimensions = box.getDimensions(); + glPushMatrix(); + glTranslatef(center.x, center.y, center.z); + glScalef(dimensions.x, dimensions.y, dimensions.z); + Application::getInstance()->getDeferredLightingEffect()->renderWireCube(1.0f); + glPopMatrix(); + } + + glBegin(GL_TRIANGLES); + foreach (const Triangle& triangle, meshTriangles) { + glVertex3f( triangle.v0.x, triangle.v0.y, triangle.v0.z); + glVertex3f( triangle.v1.x, triangle.v1.y, triangle.v1.z); + glVertex3f( triangle.v2.x, triangle.v2.y, triangle.v2.z); + } + glEnd(); + color++; + } + } + return _calculatedMeshTrianglesValid; +} + +bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, + BoxFace& face, QString& extraInfo, bool pickAgainstTriangles) { bool intersectedSomething = false; @@ -524,7 +578,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g if (!isActive()) { return intersectedSomething; } - + // extents is the entity relative, scaled, centered extents of the entity glm::vec3 position = _translation; glm::mat4 rotation = glm::mat4_cast(_rotation); @@ -535,35 +589,70 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g Extents modelExtents = getMeshExtents(); // NOTE: unrotated glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; - glm::vec3 corner = dimensions * -0.5f; // since we're going to do the ray picking in the model frame of reference - AABox overlayFrameBox(corner, dimensions); + glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the ray picking in the model frame of reference + AABox modelFrameBox(corner, dimensions); glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f)); glm::vec3 modelFrameDirection = glm::vec3(worldToModelMatrix * glm::vec4(direction, 0.0f)); // we can use the AABox's ray intersection by mapping our origin and direction into the model frame // and testing intersection there. - if (overlayFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face)) { + if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face)) { float bestDistance = std::numeric_limits::max(); + float bestTriangleDistance = std::numeric_limits::max(); + bool someTriangleHit = false; + float distanceToSubMesh; BoxFace subMeshFace; int subMeshIndex = 0; - + + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + // If we hit the models box, then consider the submeshes... foreach(const AABox& subMeshBox, _calculatedMeshBoxes) { - const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (subMeshBox.findRayIntersection(origin, direction, distanceToSubMesh, subMeshFace)) { if (distanceToSubMesh < bestDistance) { - bestDistance = distanceToSubMesh; - intersectedSomething = true; - face = subMeshFace; - extraInfo = geometry.getModelNameOfMesh(subMeshIndex); + if (pickAgainstTriangles) { + someTriangleHit = false; + if (!_calculatedMeshTrianglesValid) { + recalculateMeshBoxes(pickAgainstTriangles); + } + // check our triangles here.... + const QVector& meshTriangles = _calculatedMeshTriangles[subMeshIndex]; + int t = 0; + foreach (const Triangle& triangle, meshTriangles) { + t++; + + float thisTriangleDistance; + if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { + if (thisTriangleDistance < bestDistance) { + bestTriangleDistance = thisTriangleDistance; + someTriangleHit = true; + + bestDistance = thisTriangleDistance; + intersectedSomething = true; + face = subMeshFace; + extraInfo = geometry.getModelNameOfMesh(subMeshIndex); + } + } + } + } else { + // this is the non-triangle picking case... + bestDistance = distanceToSubMesh; + intersectedSomething = true; + face = subMeshFace; + extraInfo = geometry.getModelNameOfMesh(subMeshIndex); + } } - } + } subMeshIndex++; } + + if (intersectedSomething) { + distance = bestDistance; + } return intersectedSomething; } @@ -571,18 +660,81 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g return intersectedSomething; } -void Model::recalcuateMeshBoxes() { - if (!_calculatedMeshBoxesValid) { +// TODO: we seem to call this too often when things haven't actually changed... look into optimizing this +void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { + bool calculatedMeshTrianglesNeeded = pickAgainstTriangles && !_calculatedMeshTrianglesValid; + + if (!_calculatedMeshBoxesValid || calculatedMeshTrianglesNeeded) { PerformanceTimer perfTimer("calculatedMeshBoxes"); const FBXGeometry& geometry = _geometry->getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); _calculatedMeshBoxes.resize(numberOfMeshes); + _calculatedMeshTriangles.clear(); + _calculatedMeshTriangles.resize(numberOfMeshes); for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& mesh = geometry.meshes.at(i); Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents); + _calculatedMeshBoxes[i] = AABox(scaledMeshExtents); + + if (pickAgainstTriangles) { + QVector thisMeshTriangles; + for (int j = 0; j < mesh.parts.size(); j++) { + const FBXMeshPart& part = mesh.parts.at(j); + + const int INDICES_PER_TRIANGLE = 3; + const int INDICES_PER_QUAD = 4; + + if (part.quadIndices.size() > 0) { + int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; + int vIndex = 0; + for (int q = 0; q < numberOfQuads; q++) { + int i0 = part.quadIndices[vIndex++]; + int i1 = part.quadIndices[vIndex++]; + int i2 = part.quadIndices[vIndex++]; + int i3 = part.quadIndices[vIndex++]; + + glm::vec3 v0 = calculateScaledOffsetPoint(glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f))); + glm::vec3 v1 = calculateScaledOffsetPoint(glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f))); + glm::vec3 v2 = calculateScaledOffsetPoint(glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f))); + glm::vec3 v3 = calculateScaledOffsetPoint(glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i3], 1.0f))); + + // Sam's recommended triangle slices + Triangle tri1 = { v0, v1, v3 }; + Triangle tri2 = { v1, v2, v3 }; + + // NOTE: Random guy on the internet's recommended triangle slices + //Triangle tri1 = { v0, v1, v2 }; + //Triangle tri2 = { v2, v3, v0 }; + + thisMeshTriangles.push_back(tri1); + thisMeshTriangles.push_back(tri2); + } + } + + if (part.triangleIndices.size() > 0) { + int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; + int vIndex = 0; + for (int t = 0; t < numberOfTris; t++) { + int i0 = part.triangleIndices[vIndex++]; + int i1 = part.triangleIndices[vIndex++]; + int i2 = part.triangleIndices[vIndex++]; + + glm::vec3 v0 = calculateScaledOffsetPoint(glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i0], 1.0f))); + glm::vec3 v1 = calculateScaledOffsetPoint(glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i1], 1.0f))); + glm::vec3 v2 = calculateScaledOffsetPoint(glm::vec3(mesh.modelTransform * glm::vec4(mesh.vertices[i2], 1.0f))); + + Triangle tri = { v0, v1, v2 }; + + thisMeshTriangles.push_back(tri); + } + } + } + _calculatedMeshTriangles[i] = thisMeshTriangles; + } } _calculatedMeshBoxesValid = true; + _calculatedMeshTrianglesValid = pickAgainstTriangles; } } @@ -592,7 +744,7 @@ void Model::renderSetup(RenderArgs* args) { // against. We cache the results of these calculations so long as the model hasn't been // simulated and the mesh hasn't changed. if (args && !_calculatedMeshBoxesValid) { - recalcuateMeshBoxes(); + recalculateMeshBoxes(); } // set up dilated textures on first render after load/simulate @@ -844,6 +996,15 @@ Extents Model::calculateScaledOffsetExtents(const Extents& extents) const { return translatedExtents; } +glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const { + // we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix + glm::vec3 offsetPoint = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(point, 1.0f)); + glm::vec3 scaledPoint = ((offsetPoint + _offset) * _scale); + glm::vec3 rotatedPoint = _rotation * scaledPoint; + glm::vec3 translatedPoint = rotatedPoint + _translation; + return translatedPoint; +} + bool Model::getJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { @@ -1142,6 +1303,7 @@ void Model::simulate(float deltaTime, bool fullUpdate) { if (isActive() && fullUpdate) { _calculatedMeshBoxesValid = false; // if we have to simulate, we need to assume our mesh boxes are all invalid + _calculatedMeshTrianglesValid = false; // check for scale to fit if (_scaleToFit && !_scaledToFit) { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index d24e4d9f2e..43b04b7a46 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -19,6 +19,7 @@ #include "Transform.h" #include #include +#include #include #include "AnimationHandle.h" @@ -34,7 +35,6 @@ class Shape; #include "RenderArgs.h" class ViewFrustum; - #include "gpu/Stream.h" #include "gpu/Batch.h" @@ -89,6 +89,7 @@ public: enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE }; bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL); + bool renderTriangleProxies(); // Scene rendering support static void startScene(RenderArgs::RenderSide renderSide); @@ -119,6 +120,9 @@ public: /// Returns the scaled equivalent of some extents in model space. Extents calculateScaledOffsetExtents(const Extents& extents) const; + /// Returns the scaled equivalent of a point in model space. + glm::vec3 calculateScaledOffsetPoint(const glm::vec3& point) const; + /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } @@ -193,8 +197,8 @@ public: Q_INVOKABLE void setTextureWithNameToURL(const QString& name, const QUrl& url) { _geometry->setTextureWithNameToURL(name, url); } - bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, QString& extraInfo) const; + bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, + BoxFace& face, QString& extraInfo, bool pickAgainstTriangles = false); protected: QSharedPointer _geometry; @@ -318,7 +322,7 @@ private: static ProgramObject _skinTranslucentProgram; static ProgramObject _skinShadowProgram; - + static int _normalMapTangentLocation; static int _normalSpecularMapTangentLocation; @@ -361,10 +365,13 @@ private: static void initSkinProgram(ProgramObject& program, SkinLocations& locations, int specularTextureUnit = 1); - QVector _calculatedMeshBoxes; + QVector _calculatedMeshBoxes; // world coordinate AABoxes for all sub mesh boxes bool _calculatedMeshBoxesValid; + + QVector< QVector > _calculatedMeshTriangles; // world coordinate triangles for all sub meshes + bool _calculatedMeshTrianglesValid; - void recalcuateMeshBoxes(); + void recalculateMeshBoxes(bool pickAgainstTriangles = false); void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes diff --git a/interface/src/scripting/JoystickScriptingInterface.cpp b/interface/src/scripting/JoystickScriptingInterface.cpp index 40109703d6..039dc40961 100644 --- a/interface/src/scripting/JoystickScriptingInterface.cpp +++ b/interface/src/scripting/JoystickScriptingInterface.cpp @@ -133,7 +133,7 @@ void JoystickScriptingInterface::update() { // global action events fire in the center of the screen HFActionEvent actionEvent(actionType, - Application::getInstance()->getViewFrustum()->computePickRay(0.5f, 0.5f)); + Application::getInstance()->getCamera()->computeViewPickRay(0.5f, 0.5f)); qApp->sendEvent(qApp, &actionEvent); } diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 305119a019..f4e9e627ad 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -27,20 +27,109 @@ const float MAG_SPEED = 0.08f; const quint64 MSECS_TO_USECS = 1000ULL; -// Fast helper functions -inline float max(float a, float b) { - return (a > b) ? a : b; +const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; +const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f }; +const float reticleSize = TWO_PI / 100.0f; + + +const float CONNECTION_STATUS_BORDER_COLOR[] = { 1.0f, 0.0f, 0.0f }; +const float CONNECTION_STATUS_BORDER_LINE_WIDTH = 4.0f; + +static const float MOUSE_PITCH_RANGE = 1.0f * PI; +static const float MOUSE_YAW_RANGE = 0.5f * TWO_PI; + + +// Return a point's cartesian coordinates on a sphere from pitch and yaw +glm::vec3 getPoint(float yaw, float pitch) { + return glm::vec3(glm::cos(-pitch) * (-glm::sin(yaw)), + glm::sin(-pitch), + glm::cos(-pitch) * (-glm::cos(yaw))); } -inline float min(float a, float b) { - return (a < b) ? a : b; +//Checks if the given ray intersects the sphere at the origin. result will store a multiplier that should +//be multiplied by dir and added to origin to get the location of the collision +bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, float* result) +{ + //Source: http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection + + //Compute A, B and C coefficients + float a = glm::dot(dir, dir); + float b = 2 * glm::dot(dir, origin); + float c = glm::dot(origin, origin) - (r * r); + + //Find discriminant + float disc = b * b - 4 * a * c; + + // if discriminant is negative there are no real roots, so return + // false as ray misses sphere + if (disc < 0) { + return false; + } + + // compute q as described above + float distSqrt = sqrtf(disc); + float q; + if (b < 0) { + q = (-b - distSqrt) / 2.0; + } else { + q = (-b + distSqrt) / 2.0; + } + + // compute t0 and t1 + float t0 = q / a; + float t1 = c / q; + + // make sure t0 is smaller than t1 + if (t0 > t1) { + // if t0 is bigger than t1 swap them around + float temp = t0; + t0 = t1; + t1 = temp; + } + + // if t1 is less than zero, the object is in the ray's negative direction + // and consequently the ray misses the sphere + if (t1 < 0) { + return false; + } + + // if t0 is less than zero, the intersection point is at t1 + if (t0 < 0) { + *result = t1; + return true; + } else { // else the intersection point is at t0 + *result = t0; + return true; + } } -ApplicationOverlay::ApplicationOverlay() : - _framebufferObject(NULL), - _textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE), +void renderReticule(glm::quat orientation, float alpha) { + glm::vec3 topLeft = getPoint(reticleSize / 2.0f, -reticleSize / 2.0f); + glm::vec3 topRight = getPoint(-reticleSize / 2.0f, -reticleSize / 2.0f); + glm::vec3 bottomLeft = getPoint(reticleSize / 2.0f, reticleSize / 2.0f); + glm::vec3 bottomRight = getPoint(-reticleSize / 2.0f, reticleSize / 2.0f); + + glPushMatrix(); { + glm::vec3 axis = glm::axis(orientation); + glRotatef(glm::degrees(glm::angle(orientation)), axis.x, axis.y, axis.z); + + glBegin(GL_QUADS); { + glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], alpha); + + glTexCoord2f(0.0f, 0.0f); glVertex3f(topLeft.x, topLeft.y, topLeft.z); + glTexCoord2f(1.0f, 0.0f); glVertex3f(bottomLeft.x, bottomLeft.y, bottomLeft.z); + glTexCoord2f(1.0f, 1.0f); glVertex3f(bottomRight.x, bottomRight.y, bottomRight.z); + glTexCoord2f(0.0f, 1.0f); glVertex3f(topRight.x, topRight.y, topRight.z); + } glEnd(); + } glPopMatrix(); +} + +ApplicationOverlay::ApplicationOverlay() : + _textureFov(glm::radians(DEFAULT_OCULUS_UI_ANGULAR_SIZE)), + _textureAspectRatio(1.0f), + _lastMouseMove(0), _alpha(1.0f), - _oculusuiRadius(1.0f), + _oculusUIRadius(1.0f), _crosshairTexture(0) { memset(_reticleActive, 0, sizeof(_reticleActive)); @@ -49,29 +138,18 @@ ApplicationOverlay::ApplicationOverlay() : } ApplicationOverlay::~ApplicationOverlay() { - if (_framebufferObject != NULL) { - delete _framebufferObject; - } } -const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; -const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f }; - -const float CONNECTION_STATUS_BORDER_COLOR[] = { 1.0f, 0.0f, 0.0f }; -const float CONNECTION_STATUS_BORDER_LINE_WIDTH = 4.0f; - // Renders the overlays either to a texture or to the screen void ApplicationOverlay::renderOverlay(bool renderToTexture) { - PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()"); - - _textureFov = Menu::getInstance()->getOculusUIAngularSize() * RADIANS_PER_DEGREE; - Application* application = Application::getInstance(); - Overlays& overlays = application->getOverlays(); GLCanvas* glWidget = application->getGLWidget(); MyAvatar* myAvatar = application->getAvatar(); + + _textureFov = glm::radians(Menu::getInstance()->getOculusUIAngularSize()); + _textureAspectRatio = (float)application->getGLWidget()->getDeviceWidth() / (float)application->getGLWidget()->getDeviceHeight(); //Handle fading and deactivation/activation of UI if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) { @@ -86,42 +164,39 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { } } - if (renderToTexture) { - getFramebufferObject()->bind(); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - } - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Render 2D overlay glMatrixMode(GL_PROJECTION); - glPushMatrix(); - - glLoadIdentity(); - gluOrtho2D(0, glWidget->width(), glWidget->height(), 0); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); - - renderAudioMeter(); - - if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) { - myAvatar->renderHeadMouse(glWidget->width(), glWidget->height()); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (renderToTexture) { + _overlays.buildFramebufferObject(); + _overlays.bind(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } - - renderStatsAndLogs(); - - // give external parties a change to hook in - emit application->renderingOverlay(); - - overlays.render2D(); - - renderPointers(); - - renderDomainConnectionStatusBorder(); - - glPopMatrix(); - + + glPushMatrix(); { + glLoadIdentity(); + gluOrtho2D(0, glWidget->width(), glWidget->height(), 0); + + renderAudioMeter(); + + if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) { + myAvatar->renderHeadMouse(glWidget->width(), glWidget->height()); + } + + renderStatsAndLogs(); + + // give external parties a change to hook in + emit application->renderingOverlay(); + + overlays.render2D(); + + renderPointers(); + + renderDomainConnectionStatusBorder(); + } glPopMatrix(); glMatrixMode(GL_MODELVIEW); glEnable(GL_DEPTH_TEST); @@ -129,160 +204,242 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); if (renderToTexture) { - getFramebufferObject()->release(); + _overlays.release(); } } // Draws the FBO texture for the screen void ApplicationOverlay::displayOverlayTexture() { - if (_alpha == 0.0f) { return; } - - Application* application = Application::getInstance(); - GLCanvas* glWidget = application->getGLWidget(); - + GLCanvas* glWidget = Application::getInstance()->getGLWidget(); glEnable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture()); + _overlays.bindTexture(); glMatrixMode(GL_PROJECTION); - glPushMatrix(); - - glLoadIdentity(); - gluOrtho2D(0, glWidget->getDeviceWidth(), glWidget->getDeviceHeight(), 0); - glDisable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - glEnable(GL_BLEND); - - glBegin(GL_QUADS); - glColor4f(1.0f, 1.0f, 1.0f, _alpha); - glTexCoord2f(0, 0); glVertex2i(0, glWidget->getDeviceHeight()); - glTexCoord2f(1, 0); glVertex2i(glWidget->getDeviceWidth(), glWidget->getDeviceHeight()); - glTexCoord2f(1, 1); glVertex2i(glWidget->getDeviceWidth(), 0); - glTexCoord2f(0, 1); glVertex2i(0, 0); - glEnd(); - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - - glPopMatrix(); + glPushMatrix(); { + glLoadIdentity(); + gluOrtho2D(0, glWidget->getDeviceWidth(), glWidget->getDeviceHeight(), 0); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glEnable(GL_BLEND); + + glBegin(GL_QUADS); { + glColor4f(1.0f, 1.0f, 1.0f, _alpha); + glTexCoord2f(0, 0); glVertex2i(0, glWidget->getDeviceHeight()); + glTexCoord2f(1, 0); glVertex2i(glWidget->getDeviceWidth(), glWidget->getDeviceHeight()); + glTexCoord2f(1, 1); glVertex2i(glWidget->getDeviceWidth(), 0); + glTexCoord2f(0, 1); glVertex2i(0, 0); + } glEnd(); + } glPopMatrix(); + glDisable(GL_TEXTURE_2D); } -void ApplicationOverlay::computeOculusPickRay(float x, float y, glm::vec3& direction) const { +// Draws the FBO texture for Oculus rift. +void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { + if (_alpha == 0.0f) { + return; + } + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + _overlays.bindTexture(); + + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glDisable(GL_LIGHTING); + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.01f); + + + //Update and draw the magnifiers MyAvatar* myAvatar = Application::getInstance()->getAvatar(); - - //invert y direction - y = 1.0 - y; - - //Get position on hemisphere UI - x = sin((x - 0.5f) * _textureFov); - y = sin((y - 0.5f) * _textureFov); - - float dist = sqrt(x * x + y * y); - float z = -sqrt(1.0f - dist * dist); - - glm::vec3 relativePosition = myAvatar->getDefaultEyePosition() + - glm::normalize(myAvatar->getOrientation() * glm::vec3(x, y, z)); - - //Rotate the UI pick ray by the avatar orientation - direction = glm::normalize(relativePosition - Application::getInstance()->getCamera()->getPosition()); -} - -// Calculates the click location on the screen by taking into account any -// opened magnification windows. -void ApplicationOverlay::getClickLocation(int &x, int &y) const { - int dx; - int dy; - const float xRange = MAGNIFY_WIDTH * MAGNIFY_MULT / 2.0f; - const float yRange = MAGNIFY_HEIGHT * MAGNIFY_MULT / 2.0f; - - //Loop through all magnification windows - for (int i = 0; i < NUMBER_OF_MAGNIFIERS; i++) { - if (_magActive[i]) { - dx = x - _magX[i]; - dy = y - _magY[i]; - //Check to see if they clicked inside a mag window - if (abs(dx) <= xRange && abs(dy) <= yRange) { - //Move the click to the actual UI location by inverting the magnification - x = dx / MAGNIFY_MULT + _magX[i]; - y = dy / MAGNIFY_MULT + _magY[i]; - return; + const glm::quat& orientation = myAvatar->getOrientation(); + const glm::vec3& position = myAvatar->getDefaultEyePosition(); + const float scale = myAvatar->getScale() * _oculusUIRadius; + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); { + glTranslatef(position.x, position.y, position.z); + glm::mat4 rotation = glm::toMat4(orientation); + glMultMatrixf(&rotation[0][0]); + glScalef(scale, scale, scale); + for (int i = 0; i < NUMBER_OF_RETICULES; i++) { + + if (_magActive[i]) { + _magSizeMult[i] += MAG_SPEED; + if (_magSizeMult[i] > 1.0f) { + _magSizeMult[i] = 1.0f; + } + } else { + _magSizeMult[i] -= MAG_SPEED; + if (_magSizeMult[i] < 0.0f) { + _magSizeMult[i] = 0.0f; + } + } + + if (_magSizeMult[i] > 0.0f) { + //Render magnifier, but dont show border for mouse magnifier + glm::vec2 projection = screenToOverlay(glm::vec2(_reticulePosition[MOUSE].x(), + _reticulePosition[MOUSE].y())); + + renderMagnifier(projection, _magSizeMult[i], i != MOUSE); } } - } + + glDepthMask(GL_FALSE); + glDisable(GL_ALPHA_TEST); + + glColor4f(1.0f, 1.0f, 1.0f, _alpha); + + static float textureFOV = 0.0f, textureAspectRatio = 1.0f; + if (textureFOV != _textureFov || + textureAspectRatio != _textureAspectRatio) { + textureFOV = _textureFov; + textureAspectRatio = _textureAspectRatio; + + _overlays.buildVBO(_textureFov, _textureAspectRatio, 80, 80); + } + _overlays.render(); + renderPointersOculus(myAvatar->getDefaultEyePosition()); + + glDepthMask(GL_TRUE); + _overlays.releaseTexture(); + glDisable(GL_TEXTURE_2D); + + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); + glEnable(GL_LIGHTING); + } glPopMatrix(); } -//Checks if the given ray intersects the sphere at the origin. result will store a multiplier that should -//be multiplied by dir and added to origin to get the location of the collision -bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, float* result) -{ - //Source: http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection - - //Compute A, B and C coefficients - float a = glm::dot(dir, dir); - float b = 2 * glm::dot(dir, origin); - float c = glm::dot(origin, origin) - (r * r); - - //Find discriminant - float disc = b * b - 4 * a * c; - - // if discriminant is negative there are no real roots, so return - // false as ray misses sphere - if (disc < 0) { - return false; +// Draws the FBO texture for 3DTV. +void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov) { + if (_alpha == 0.0f) { + return; } - - // compute q as described above - float distSqrt = sqrtf(disc); - float q; - if (b < 0) { - q = (-b - distSqrt) / 2.0; - } else { - q = (-b + distSqrt) / 2.0; + + Application* application = Application::getInstance(); + + MyAvatar* myAvatar = application->getAvatar(); + const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); + + glActiveTexture(GL_TEXTURE0); + + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); + _overlays.bindTexture(); + glEnable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glEnable(GL_TEXTURE_2D); + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glLoadIdentity(); + // Transform to world space + glm::quat rotation = whichCamera.getRotation(); + glm::vec3 axis2 = glm::axis(rotation); + glRotatef(-glm::degrees(glm::angle(rotation)), axis2.x, axis2.y, axis2.z); + glTranslatef(viewMatrixTranslation.x, viewMatrixTranslation.y, viewMatrixTranslation.z); + + // Translate to the front of the camera + glm::vec3 pos = whichCamera.getPosition(); + glm::quat rot = myAvatar->getOrientation(); + glm::vec3 axis = glm::axis(rot); + + glTranslatef(pos.x, pos.y, pos.z); + glRotatef(glm::degrees(glm::angle(rot)), axis.x, axis.y, axis.z); + + glColor4f(1.0f, 1.0f, 1.0f, _alpha); + + //Render + const GLfloat distance = 1.0f; + + const GLfloat halfQuadHeight = distance * tan(fov); + const GLfloat halfQuadWidth = halfQuadHeight * aspectRatio; + const GLfloat quadWidth = halfQuadWidth * 2.0f; + const GLfloat quadHeight = halfQuadHeight * 2.0f; + + GLfloat x = -halfQuadWidth; + GLfloat y = -halfQuadHeight; + glDisable(GL_DEPTH_TEST); + + glBegin(GL_QUADS); + + glTexCoord2f(0.0f, 1.0f); glVertex3f(x, y + quadHeight, -distance); + glTexCoord2f(1.0f, 1.0f); glVertex3f(x + quadWidth, y + quadHeight, -distance); + glTexCoord2f(1.0f, 0.0f); glVertex3f(x + quadWidth, y, -distance); + glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, -distance); + + glEnd(); + + if (_crosshairTexture == 0) { + _crosshairTexture = Application::getInstance()->getGLWidget()->bindTexture(QImage(Application::resourcesPath() + "images/sixense-reticle.png")); } + + //draw the mouse pointer + glBindTexture(GL_TEXTURE_2D, _crosshairTexture); + + const float reticleSize = 40.0f / application->getGLWidget()->width() * quadWidth; + x -= reticleSize / 2.0f; + y += reticleSize / 2.0f; + const float mouseX = (application->getMouseX() / (float)application->getGLWidget()->width()) * quadWidth; + const float mouseY = (1.0 - (application->getMouseY() / (float)application->getGLWidget()->height())) * quadHeight; + + glBegin(GL_QUADS); + + glColor3f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2]); + + glTexCoord2d(0.0f, 0.0f); glVertex3f(x + mouseX, y + mouseY, -distance); + glTexCoord2d(1.0f, 0.0f); glVertex3f(x + mouseX + reticleSize, y + mouseY, -distance); + glTexCoord2d(1.0f, 1.0f); glVertex3f(x + mouseX + reticleSize, y + mouseY - reticleSize, -distance); + glTexCoord2d(0.0f, 1.0f); glVertex3f(x + mouseX, y + mouseY - reticleSize, -distance); + + glEnd(); + + glEnable(GL_DEPTH_TEST); + + glPopMatrix(); + + glDepthMask(GL_TRUE); + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); + glEnable(GL_LIGHTING); + + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); +} - // compute t0 and t1 - float t0 = q / a; - float t1 = c / q; +void ApplicationOverlay::computeOculusPickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const { + const float pitch = (0.5f - y) * MOUSE_PITCH_RANGE; + const float yaw = (0.5f - x) * MOUSE_YAW_RANGE; + const glm::quat orientation(glm::vec3(pitch, yaw, 0.0f)); + const glm::vec3 localDirection = orientation * IDENTITY_FRONT; - // make sure t0 is smaller than t1 - if (t0 > t1) { - // if t0 is bigger than t1 swap them around - float temp = t0; - t0 = t1; - t1 = temp; - } - - // if t1 is less than zero, the object is in the ray's negative direction - // and consequently the ray misses the sphere - if (t1 < 0) { - return false; - } - - // if t0 is less than zero, the intersection point is at t1 - if (t0 < 0) { - *result = t1; - return true; - } else { // else the intersection point is at t0 - *result = t0; - return true; - } + //Rotate the UI pick ray by the avatar orientation + const MyAvatar* myAvatar = Application::getInstance()->getAvatar(); + origin = myAvatar->getDefaultEyePosition(); + direction = myAvatar->getOrientation() * localDirection; } //Caculate the click location using one of the sixense controllers. Scale is not applied QPoint ApplicationOverlay::getPalmClickLocation(const PalmData *palm) const { - Application* application = Application::getInstance(); GLCanvas* glWidget = application->getGLWidget(); MyAvatar* myAvatar = application->getAvatar(); glm::vec3 tip = myAvatar->getLaserPointerTipPosition(palm); glm::vec3 eyePos = myAvatar->getHead()->getEyePosition(); - glm::quat orientation = glm::inverse(myAvatar->getOrientation()); - glm::vec3 dir = orientation * glm::normalize(application->getCamera()->getPosition() - tip); //direction of ray goes towards camera - glm::vec3 tipPos = orientation * (tip - eyePos); + glm::quat invOrientation = glm::inverse(myAvatar->getOrientation()); + //direction of ray goes towards camera + glm::vec3 dir = invOrientation * glm::normalize(application->getCamera()->getPosition() - tip); + glm::vec3 tipPos = invOrientation * (tip - eyePos); QPoint rv; @@ -292,7 +449,7 @@ QPoint ApplicationOverlay::getPalmClickLocation(const PalmData *palm) const { //We back the ray up by dir to ensure that it will not start inside the UI. glm::vec3 adjustedPos = tipPos - dir; //Find intersection of crosshair ray. - if (raySphereIntersect(dir, adjustedPos, _oculusuiRadius * myAvatar->getScale(), &t)){ + if (raySphereIntersect(dir, adjustedPos, _oculusUIRadius * myAvatar->getScale(), &t)){ glm::vec3 collisionPos = adjustedPos + dir * t; //Normalize it in case its not a radius of 1 collisionPos = glm::normalize(collisionPos); @@ -336,11 +493,11 @@ bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, glm::quat orientation = myAvatar->getOrientation(); - glm::vec3 relativePosition = orientation * (position - myAvatar->getHead()->getEyePosition()); + glm::vec3 relativePosition = orientation * (position - myAvatar->getDefaultEyePosition()); glm::vec3 relativeDirection = orientation * direction; float t; - if (raySphereIntersect(relativeDirection, relativePosition, _oculusuiRadius * myAvatar->getScale(), &t)){ + if (raySphereIntersect(relativeDirection, relativePosition, _oculusUIRadius * myAvatar->getScale(), &t)){ result = position + direction * t; return true; } @@ -348,184 +505,7 @@ bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, return false; } -// Draws the FBO texture for Oculus rift. -void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { - if (_alpha == 0.0f) { - return; - } - - Application* application = Application::getInstance(); - - MyAvatar* myAvatar = application->getAvatar(); - - glActiveTexture(GL_TEXTURE0); - - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - glEnable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - glEnable(GL_TEXTURE_2D); - - glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture()); - - glMatrixMode(GL_MODELVIEW); - - glDepthMask(GL_TRUE); - - glEnable(GL_ALPHA_TEST); - glAlphaFunc(GL_GREATER, 0.01f); - - //Update and draw the magnifiers - - glPushMatrix(); - const glm::quat& orientation = myAvatar->getOrientation(); - const glm::vec3& position = myAvatar->getDefaultEyePosition(); - - glm::mat4 rotation = glm::toMat4(orientation); - - glTranslatef(position.x, position.y, position.z); - glMultMatrixf(&rotation[0][0]); - for (int i = 0; i < NUMBER_OF_MAGNIFIERS; i++) { - - if (_magActive[i]) { - _magSizeMult[i] += MAG_SPEED; - if (_magSizeMult[i] > 1.0f) { - _magSizeMult[i] = 1.0f; - } - } else { - _magSizeMult[i] -= MAG_SPEED; - if (_magSizeMult[i] < 0.0f) { - _magSizeMult[i] = 0.0f; - } - } - - if (_magSizeMult[i] > 0.0f) { - //Render magnifier, but dont show border for mouse magnifier - renderMagnifier(_magX[i], _magY[i], _magSizeMult[i], i != MOUSE); - } - } - glPopMatrix(); - - glDepthMask(GL_FALSE); - glDisable(GL_ALPHA_TEST); - - glColor4f(1.0f, 1.0f, 1.0f, _alpha); - - renderTexturedHemisphere(); - - renderPointersOculus(myAvatar->getDefaultEyePosition()); - - glDepthMask(GL_TRUE); - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - glEnable(GL_LIGHTING); - - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - -} - -// Draws the FBO texture for 3DTV. -void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov) { - - if (_alpha == 0.0f) { - return; - } - - Application* application = Application::getInstance(); - - MyAvatar* myAvatar = application->getAvatar(); - const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); - - glActiveTexture(GL_TEXTURE0); - - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture()); - glEnable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - glEnable(GL_TEXTURE_2D); - - glMatrixMode(GL_MODELVIEW); - - glPushMatrix(); - glLoadIdentity(); - // Transform to world space - glm::quat rotation = whichCamera.getRotation(); - glm::vec3 axis2 = glm::axis(rotation); - glRotatef(-glm::degrees(glm::angle(rotation)), axis2.x, axis2.y, axis2.z); - glTranslatef(viewMatrixTranslation.x, viewMatrixTranslation.y, viewMatrixTranslation.z); - - // Translate to the front of the camera - glm::vec3 pos = whichCamera.getPosition(); - glm::quat rot = myAvatar->getOrientation(); - glm::vec3 axis = glm::axis(rot); - - glTranslatef(pos.x, pos.y, pos.z); - glRotatef(glm::degrees(glm::angle(rot)), axis.x, axis.y, axis.z); - - glColor4f(1.0f, 1.0f, 1.0f, _alpha); - - //Render - const GLfloat distance = 1.0f; - - const GLfloat halfQuadHeight = distance * tan(fov); - const GLfloat halfQuadWidth = halfQuadHeight * aspectRatio; - const GLfloat quadWidth = halfQuadWidth * 2.0f; - const GLfloat quadHeight = halfQuadHeight * 2.0f; - - GLfloat x = -halfQuadWidth; - GLfloat y = -halfQuadHeight; - glDisable(GL_DEPTH_TEST); - - glBegin(GL_QUADS); - - glTexCoord2f(0.0f, 1.0f); glVertex3f(x, y + quadHeight, -distance); - glTexCoord2f(1.0f, 1.0f); glVertex3f(x + quadWidth, y + quadHeight, -distance); - glTexCoord2f(1.0f, 0.0f); glVertex3f(x + quadWidth, y, -distance); - glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, -distance); - - glEnd(); - - if (_crosshairTexture == 0) { - _crosshairTexture = Application::getInstance()->getGLWidget()->bindTexture(QImage(Application::resourcesPath() + "images/sixense-reticle.png")); - } - - //draw the mouse pointer - glBindTexture(GL_TEXTURE_2D, _crosshairTexture); - - const float reticleSize = 40.0f / application->getGLWidget()->width() * quadWidth; - x -= reticleSize / 2.0f; - y += reticleSize / 2.0f; - const float mouseX = (application->getMouseX() / (float)application->getGLWidget()->width()) * quadWidth; - const float mouseY = (1.0 - (application->getMouseY() / (float)application->getGLWidget()->height())) * quadHeight; - - glBegin(GL_QUADS); - - glColor3f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2]); - - glTexCoord2d(0.0f, 0.0f); glVertex3f(x + mouseX, y + mouseY, -distance); - glTexCoord2d(1.0f, 0.0f); glVertex3f(x + mouseX + reticleSize, y + mouseY, -distance); - glTexCoord2d(1.0f, 1.0f); glVertex3f(x + mouseX + reticleSize, y + mouseY - reticleSize, -distance); - glTexCoord2d(0.0f, 1.0f); glVertex3f(x + mouseX, y + mouseY - reticleSize, -distance); - - glEnd(); - - glEnable(GL_DEPTH_TEST); - - glPopMatrix(); - - glDepthMask(GL_TRUE); - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - glEnable(GL_LIGHTING); - - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); -} //Renders optional pointers void ApplicationOverlay::renderPointers() { @@ -542,16 +522,30 @@ void ApplicationOverlay::renderPointers() { if (OculusManager::isConnected() && !application->getLastMouseMoveWasSimulated()) { //If we are in oculus, render reticle later + if (_lastMouseMove == 0) { + _lastMouseMove = usecTimestampNow(); + } + QPoint position = QPoint(application->getTrueMouseX(), application->getTrueMouseY()); + + static const int MAX_IDLE_TIME = 3; + if (_reticulePosition[MOUSE] != position) { + _lastMouseMove = usecTimestampNow(); + } else if (usecTimestampNow() - _lastMouseMove > MAX_IDLE_TIME * USECS_PER_SECOND) { + float pitch, yaw, roll; + OculusManager::getEulerAngles(yaw, pitch, roll); + glm::vec2 screenPos = sphericalToScreen(glm::vec2(yaw, -pitch)); + + position = QPoint(screenPos.x, screenPos.y); + QCursor::setPos(application->getGLWidget()->mapToGlobal(position)); + } + + _reticulePosition[MOUSE] = position; _reticleActive[MOUSE] = true; _magActive[MOUSE] = true; - _mouseX[MOUSE] = application->getMouseX(); - _mouseY[MOUSE] = application->getMouseY(); - _magX[MOUSE] = _mouseX[MOUSE]; - _magY[MOUSE] = _mouseY[MOUSE]; _reticleActive[LEFT_CONTROLLER] = false; _reticleActive[RIGHT_CONTROLLER] = false; - } else if (application->getLastMouseMoveWasSimulated() && Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) { + _lastMouseMove = 0; //only render controller pointer if we aren't already rendering a mouse pointer _reticleActive[MOUSE] = false; _magActive[MOUSE] = false; @@ -559,7 +553,6 @@ void ApplicationOverlay::renderPointers() { } glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); - } void ApplicationOverlay::renderControllerPointers() { @@ -568,9 +561,9 @@ void ApplicationOverlay::renderControllerPointers() { MyAvatar* myAvatar = application->getAvatar(); //Static variables used for storing controller state - static quint64 pressedTime[NUMBER_OF_MAGNIFIERS] = { 0ULL, 0ULL, 0ULL }; - static bool isPressed[NUMBER_OF_MAGNIFIERS] = { false, false, false }; - static bool stateWhenPressed[NUMBER_OF_MAGNIFIERS] = { false, false, false }; + static quint64 pressedTime[NUMBER_OF_RETICULES] = { 0ULL, 0ULL, 0ULL }; + static bool isPressed[NUMBER_OF_RETICULES] = { false, false, false }; + static bool stateWhenPressed[NUMBER_OF_RETICULES] = { false, false, false }; const HandData* handData = Application::getInstance()->getAvatar()->getHandData(); @@ -617,14 +610,11 @@ void ApplicationOverlay::renderControllerPointers() { QPoint point = getPalmClickLocation(palmData); - _mouseX[index] = point.x(); - _mouseY[index] = point.y(); + _reticulePosition[index] = point; //When button 2 is pressed we drag the mag window if (isPressed[index]) { _magActive[index] = true; - _magX[index] = point.x(); - _magY[index] = point.y(); } // If oculus is enabled, we draw the crosshairs later @@ -678,256 +668,93 @@ void ApplicationOverlay::renderControllerPointers() { } void ApplicationOverlay::renderPointersOculus(const glm::vec3& eyePos) { - - Application* application = Application::getInstance(); - GLCanvas* glWidget = application->getGLWidget(); - glm::vec3 cursorVerts[4]; - - const int widgetWidth = glWidget->width(); - const int widgetHeight = glWidget->height(); - - const float reticleSize = 50.0f; - glBindTexture(GL_TEXTURE_2D, _crosshairTexture); glDisable(GL_DEPTH_TEST); glMatrixMode(GL_MODELVIEW); - MyAvatar* myAvatar = application->getAvatar(); - + //Controller Pointers + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { glm::vec3 tip = myAvatar->getLaserPointerTipPosition(&palm); - glm::vec3 tipPos = (tip - eyePos); - - float length = glm::length(eyePos - tip); - float size = 0.03f * length; - - glm::vec3 up = glm::vec3(0.0, 1.0, 0.0) * size; - glm::vec3 right = glm::vec3(1.0, 0.0, 0.0) * size; - - cursorVerts[0] = -right + up; - cursorVerts[1] = right + up; - cursorVerts[2] = right - up; - cursorVerts[3] = -right - up; - - glPushMatrix(); - - // objToCamProj is the vector in world coordinates from the - // local origin to the camera projected in the XZ plane - glm::vec3 cursorToCameraXZ(-tipPos.x, 0, -tipPos.z); - cursorToCameraXZ = glm::normalize(cursorToCameraXZ); - - //Translate the cursor to the tip of the oculus ray - glTranslatef(tip.x, tip.y, tip.z); - - glm::vec3 direction(0, 0, 1); - // easy fix to determine wether the angle is negative or positive - // for positive angles upAux will be a vector pointing in the - // positive y direction, otherwise upAux will point downwards - // effectively reversing the rotation. - glm::vec3 upAux = glm::cross(direction, cursorToCameraXZ); - - // compute the angle - float angleCosine = glm::dot(direction, cursorToCameraXZ); - - //Rotate in XZ direction - glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, upAux[0], upAux[1], upAux[2]); - - glm::vec3 cursorToCamera = glm::normalize(-tipPos); - - // Compute the angle between cursorToCameraXZ and cursorToCamera, - angleCosine = glm::dot(cursorToCameraXZ, cursorToCamera); - - //Rotate in Y direction - if (cursorToCamera.y < 0) { - glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, 1, 0, 0); - } else { - glRotatef(acos(angleCosine) * DEGREES_PER_RADIAN, -1, 0, 0); - } - - glBegin(GL_QUADS); - - glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], _alpha); - - glTexCoord2f(0.0f, 0.0f); glVertex3f(cursorVerts[0].x, cursorVerts[0].y, cursorVerts[0].z); - glTexCoord2f(1.0f, 0.0f); glVertex3f(cursorVerts[1].x, cursorVerts[1].y, cursorVerts[1].z); - glTexCoord2f(1.0f, 1.0f); glVertex3f(cursorVerts[2].x, cursorVerts[2].y, cursorVerts[2].z); - glTexCoord2f(0.0f, 1.0f); glVertex3f(cursorVerts[3].x, cursorVerts[3].y, cursorVerts[3].z); - - glEnd(); - - glPopMatrix(); + glm::vec3 tipDirection = glm::normalize(glm::inverse(myAvatar->getOrientation()) * (tip - eyePos)); + float pitch = -glm::asin(tipDirection.y); + float yawSign = glm::sign(-tipDirection.x); + float yaw = glm::acos(-tipDirection.z) * + ((yawSign == 0.0f) ? 1.0f : yawSign); + glm::quat orientation = glm::quat(glm::vec3(pitch, yaw, 0.0f)); + renderReticule(orientation, _alpha); } } //Mouse Pointer if (_reticleActive[MOUSE]) { - - float mouseX = (float)_mouseX[MOUSE]; - float mouseY = (float)_mouseY[MOUSE]; - mouseX -= reticleSize / 2; - mouseY += reticleSize / 2; - - //Get new UV coordinates from our magnification window - float newULeft = mouseX / widgetWidth; - float newURight = (mouseX + reticleSize) / widgetWidth; - float newVBottom = 1.0 - mouseY / widgetHeight; - float newVTop = 1.0 - (mouseY - reticleSize) / widgetHeight; - - // Project our position onto the hemisphere using the UV coordinates - float lX = sin((newULeft - 0.5f) * _textureFov); - float rX = sin((newURight - 0.5f) * _textureFov); - float bY = sin((newVBottom - 0.5f) * _textureFov); - float tY = sin((newVTop - 0.5f) * _textureFov); - - float dist; - //Bottom Left - dist = sqrt(lX * lX + bY * bY); - float blZ = sqrt(1.0f - dist * dist); - //Top Left - dist = sqrt(lX * lX + tY * tY); - float tlZ = sqrt(1.0f - dist * dist); - //Bottom Right - dist = sqrt(rX * rX + bY * bY); - float brZ = sqrt(1.0f - dist * dist); - //Top Right - dist = sqrt(rX * rX + tY * tY); - float trZ = sqrt(1.0f - dist * dist); - - glBegin(GL_QUADS); - - glColor4f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], _alpha); - - const glm::quat& orientation = myAvatar->getOrientation(); - cursorVerts[0] = orientation * glm::vec3(lX, tY, -tlZ) + eyePos; - cursorVerts[1] = orientation * glm::vec3(rX, tY, -trZ) + eyePos; - cursorVerts[2] = orientation * glm::vec3(rX, bY, -brZ) + eyePos; - cursorVerts[3] = orientation * glm::vec3(lX, bY, -blZ) + eyePos; - - glTexCoord2f(0.0f, 0.0f); glVertex3f(cursorVerts[0].x, cursorVerts[0].y, cursorVerts[0].z); - glTexCoord2f(1.0f, 0.0f); glVertex3f(cursorVerts[1].x, cursorVerts[1].y, cursorVerts[1].z); - glTexCoord2f(1.0f, 1.0f); glVertex3f(cursorVerts[2].x, cursorVerts[2].y, cursorVerts[2].z); - glTexCoord2f(0.0f, 1.0f); glVertex3f(cursorVerts[3].x, cursorVerts[3].y, cursorVerts[3].z); - - glEnd(); + glm::vec2 projection = screenToSpherical(glm::vec2(_reticulePosition[MOUSE].x(), + _reticulePosition[MOUSE].y())); + glm::quat orientation(glm::vec3(-projection.y, projection.x, 0.0f)); + renderReticule(orientation, _alpha); } - glEnable(GL_DEPTH_TEST); } //Renders a small magnification of the currently bound texture at the coordinates -void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY, float sizeMult, bool showBorder) const -{ +void ApplicationOverlay::renderMagnifier(glm::vec2 magPos, float sizeMult, bool showBorder) const { Application* application = Application::getInstance(); GLCanvas* glWidget = application->getGLWidget(); - + const int widgetWidth = glWidget->width(); const int widgetHeight = glWidget->height(); - - const float magnifyWidth = MAGNIFY_WIDTH * sizeMult; - const float magnifyHeight = MAGNIFY_HEIGHT * sizeMult; - - mouseX -= magnifyWidth / 2; - mouseY -= magnifyHeight / 2; - - float newWidth = magnifyWidth * MAGNIFY_MULT; - float newHeight = magnifyHeight * MAGNIFY_MULT; - - // Magnification Texture Coordinates - float magnifyULeft = mouseX / (float)widgetWidth; - float magnifyURight = (mouseX + magnifyWidth) / (float)widgetWidth; - float magnifyVBottom = 1.0f - mouseY / (float)widgetHeight; - float magnifyVTop = 1.0f - (mouseY + magnifyHeight) / (float)widgetHeight; - - // Coordinates of magnification overlay - float newMouseX = (mouseX + magnifyWidth / 2) - newWidth / 2.0f; - float newMouseY = (mouseY + magnifyHeight / 2) + newHeight / 2.0f; - - // Get position on hemisphere using angle - - //Get new UV coordinates from our magnification window - float newULeft = newMouseX / widgetWidth; - float newURight = (newMouseX + newWidth) / widgetWidth; - float newVBottom = 1.0 - newMouseY / widgetHeight; - float newVTop = 1.0 - (newMouseY - newHeight) / widgetHeight; - - // Project our position onto the hemisphere using the UV coordinates - float radius = _oculusuiRadius * application->getAvatar()->getScale(); - float radius2 = radius * radius; - - float lX = radius * sin((newULeft - 0.5f) * _textureFov); - float rX = radius * sin((newURight - 0.5f) * _textureFov); - float bY = radius * sin((newVBottom - 0.5f) * _textureFov); - float tY = radius * sin((newVTop - 0.5f) * _textureFov); - - float blZ, tlZ, brZ, trZ; - - float dist; - float discriminant; - - //Bottom Left - dist = sqrt(lX * lX + bY * bY); - discriminant = radius2 - dist * dist; - if (discriminant > 0) { - blZ = sqrt(discriminant); - } else { - blZ = 0; - } - //Top Left - dist = sqrt(lX * lX + tY * tY); - discriminant = radius2 - dist * dist; - if (discriminant > 0) { - tlZ = sqrt(discriminant); - } else { - tlZ = 0; - } - //Bottom Right - dist = sqrt(rX * rX + bY * bY); - discriminant = radius2 - dist * dist; - if (discriminant > 0) { - brZ = sqrt(discriminant); - } else { - brZ = 0; - } - //Top Right - dist = sqrt(rX * rX + tY * tY); - discriminant = radius2 - dist * dist; - if (discriminant > 0) { - trZ = sqrt(discriminant); - } else { - trZ = 0; - } - if (showBorder) { - glDisable(GL_TEXTURE_2D); - glLineWidth(1.0f); - //Outer Line - glBegin(GL_LINE_STRIP); - glColor4f(1.0f, 0.0f, 0.0f, _alpha); - - glVertex3f(lX, tY, -tlZ); - glVertex3f(rX, tY, -trZ); - glVertex3f(rX, bY, -brZ); - glVertex3f(lX, bY, -blZ); - glVertex3f(lX, tY, -tlZ); - - glEnd(); - glEnable(GL_TEXTURE_2D); - } - glColor4f(1.0f, 1.0f, 1.0f, _alpha); - - glBegin(GL_QUADS); - - glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(lX, tY, -tlZ); - glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rX, tY, -trZ); - glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rX, bY, -brZ); - glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(lX, bY, -blZ); - - glEnd(); - + const float halfWidth = (MAGNIFY_WIDTH / _textureAspectRatio) * sizeMult / 2.0f; + const float halfHeight = MAGNIFY_HEIGHT * sizeMult / 2.0f; + // Magnification Texture Coordinates + const float magnifyULeft = (magPos.x - halfWidth) / (float)widgetWidth; + const float magnifyURight = (magPos.x + halfWidth) / (float)widgetWidth; + const float magnifyVTop = 1.0f - (magPos.y - halfHeight) / (float)widgetHeight; + const float magnifyVBottom = 1.0f - (magPos.y + halfHeight) / (float)widgetHeight; + + const float newHalfWidth = halfWidth * MAGNIFY_MULT; + const float newHalfHeight = halfHeight * MAGNIFY_MULT; + //Get yaw / pitch value for the corners + const glm::vec2 topLeftYawPitch = overlayToSpherical(glm::vec2(magPos.x - newHalfWidth, + magPos.y - newHalfHeight)); + const glm::vec2 bottomRightYawPitch = overlayToSpherical(glm::vec2(magPos.x + newHalfWidth, + magPos.y + newHalfHeight)); + + const glm::vec3 bottomLeft = getPoint(topLeftYawPitch.x, bottomRightYawPitch.y); + const glm::vec3 bottomRight = getPoint(bottomRightYawPitch.x, bottomRightYawPitch.y); + const glm::vec3 topLeft = getPoint(topLeftYawPitch.x, topLeftYawPitch.y); + const glm::vec3 topRight = getPoint(bottomRightYawPitch.x, topLeftYawPitch.y); + + glPushMatrix(); { + if (showBorder) { + glDisable(GL_TEXTURE_2D); + glLineWidth(1.0f); + //Outer Line + glBegin(GL_LINE_STRIP); { + glColor4f(1.0f, 0.0f, 0.0f, _alpha); + + glVertex3f(topLeft.x, topLeft.y, topLeft.z); + glVertex3f(bottomLeft.x, bottomLeft.y, bottomLeft.z); + glVertex3f(bottomRight.x, bottomRight.y, bottomRight.z); + glVertex3f(topRight.x, topRight.y, topRight.z); + glVertex3f(topLeft.x, topLeft.y, topLeft.z); + } glEnd(); + + glEnable(GL_TEXTURE_2D); + } + glColor4f(1.0f, 1.0f, 1.0f, _alpha); + + glBegin(GL_QUADS); { + glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(bottomLeft.x, bottomLeft.y, bottomLeft.z); + glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(bottomRight.x, bottomRight.y, bottomRight.z); + glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(topRight.x, topRight.y, topRight.z); + glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(topLeft.x, topLeft.y, topLeft.z); + } glEnd(); + } glPopMatrix(); } void ApplicationOverlay::renderAudioMeter() { @@ -1103,120 +930,6 @@ void ApplicationOverlay::renderStatsAndLogs() { nodeBoundsDisplay.drawOverlay(); } -//Renders a hemisphere with texture coordinates. -void ApplicationOverlay::renderTexturedHemisphere() { - const int slices = 80; - const int stacks = 80; - - //UV mapping source: http://www.mvps.org/directx/articles/spheremap.htm - static VerticesIndices vbo(0, 0); - int vertices = slices * (stacks - 1) + 1; - int indices = slices * 2 * 3 * (stacks - 2) + slices * 3; - - static float oldTextureFOV = _textureFov; - //We only generate the VBO when the _textureFov changes - if (vbo.first == 0 || oldTextureFOV != _textureFov) { - oldTextureFOV = _textureFov; - TextureVertex* vertexData = new TextureVertex[vertices]; - TextureVertex* vertex = vertexData; - for (int i = 0; i < stacks - 1; i++) { - float phi = PI_OVER_TWO * (float)i / (float)(stacks - 1); - float z = -sinf(phi), radius = cosf(phi); - - for (int j = 0; j < slices; j++) { - float theta = TWO_PI * (float)j / (float)slices; - - vertex->position.x = sinf(theta) * radius; - vertex->position.y = cosf(theta) * radius; - vertex->position.z = z; - vertex->uv.x = asin(vertex->position.x) / (_textureFov) + 0.5f; - vertex->uv.y = asin(vertex->position.y) / (_textureFov) + 0.5f; - vertex++; - } - } - vertex->position.x = 0.0f; - vertex->position.y = 0.0f; - vertex->position.z = -1.0f; - vertex->uv.x = 0.5f; - vertex->uv.y = 0.5f; - vertex++; - - if (vbo.first == 0){ - glGenBuffers(1, &vbo.first); - } - glBindBuffer(GL_ARRAY_BUFFER, vbo.first); - const int BYTES_PER_VERTEX = sizeof(TextureVertex); - glBufferData(GL_ARRAY_BUFFER, vertices * BYTES_PER_VERTEX, vertexData, GL_STATIC_DRAW); - delete[] vertexData; - - GLushort* indexData = new GLushort[indices]; - GLushort* index = indexData; - for (int i = 0; i < stacks - 2; i++) { - GLushort bottom = i * slices; - GLushort top = bottom + slices; - for (int j = 0; j < slices; j++) { - int next = (j + 1) % slices; - - *(index++) = bottom + j; - *(index++) = top + next; - *(index++) = top + j; - - *(index++) = bottom + j; - *(index++) = bottom + next; - *(index++) = top + next; - } - } - GLushort bottom = (stacks - 2) * slices; - GLushort top = bottom + slices; - for (int i = 0; i < slices; i++) { - *(index++) = bottom + i; - *(index++) = bottom + (i + 1) % slices; - *(index++) = top; - } - - glGenBuffers(1, &vbo.second); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo.second); - const int BYTES_PER_INDEX = sizeof(GLushort); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices * BYTES_PER_INDEX, indexData, GL_STATIC_DRAW); - delete[] indexData; - - } else { - glBindBuffer(GL_ARRAY_BUFFER, vbo.first); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo.second); - } - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - glVertexPointer(3, GL_FLOAT, sizeof(TextureVertex), (void*)0); - glTexCoordPointer(2, GL_FLOAT, sizeof(TextureVertex), (void*)12); - - glPushMatrix(); - Application* application = Application::getInstance(); - MyAvatar* myAvatar = application->getAvatar(); - const glm::quat& orientation = myAvatar->getOrientation(); - const glm::vec3& position = myAvatar->getDefaultEyePosition(); - - glm::mat4 rotation = glm::toMat4(orientation); - - glTranslatef(position.x, position.y, position.z); - glMultMatrixf(&rotation[0][0]); - - const float scale = _oculusuiRadius * myAvatar->getScale(); - glScalef(scale, scale, scale); - - glDrawRangeElements(GL_TRIANGLES, 0, vertices - 1, indices, GL_UNSIGNED_SHORT, 0); - - glPopMatrix(); - - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - -} - void ApplicationOverlay::renderDomainConnectionStatusBorder() { NodeList* nodeList = NodeList::getInstance(); @@ -1241,22 +954,209 @@ void ApplicationOverlay::renderDomainConnectionStatusBorder() { } } -QOpenGLFramebufferObject* ApplicationOverlay::getFramebufferObject() { - QSize size = Application::getInstance()->getGLWidget()->getDeviceSize(); - if (!_framebufferObject || _framebufferObject->size() != size) { - - delete _framebufferObject; - - _framebufferObject = new QOpenGLFramebufferObject(size); - glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - GLfloat borderColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - glBindTexture(GL_TEXTURE_2D, 0); - } - return _framebufferObject; +ApplicationOverlay::TexturedHemisphere::TexturedHemisphere() : + _vertices(0), + _indices(0), + _framebufferObject(NULL), + _vbo(0, 0) { } +ApplicationOverlay::TexturedHemisphere::~TexturedHemisphere() { + cleanupVBO(); + if (_framebufferObject != NULL) { + delete _framebufferObject; + } +} + +void ApplicationOverlay::TexturedHemisphere::bind() { + _framebufferObject->bind(); +} + +void ApplicationOverlay::TexturedHemisphere::release() { + _framebufferObject->release(); +} + +void ApplicationOverlay::TexturedHemisphere::bindTexture() { + glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); +} + +void ApplicationOverlay::TexturedHemisphere::releaseTexture() { + glBindTexture(GL_TEXTURE_2D, 0); +} + +void ApplicationOverlay::TexturedHemisphere::buildVBO(const float fov, + const float aspectRatio, + const int slices, + const int stacks) { + if (fov >= PI) { + qDebug() << "TexturedHemisphere::buildVBO(): FOV greater or equal than Pi will create issues"; + } + // Cleanup old VBO if necessary + cleanupVBO(); + + //UV mapping source: http://www.mvps.org/directx/articles/spheremap.htm + + // Compute number of vertices needed + _vertices = slices * stacks; + + // Compute vertices positions and texture UV coordinate + TextureVertex* vertexData = new TextureVertex[_vertices]; + TextureVertex* vertexPtr = &vertexData[0]; + for (int i = 0; i < stacks; i++) { + float stacksRatio = (float)i / (float)(stacks - 1); // First stack is 0.0f, last stack is 1.0f + // abs(theta) <= fov / 2.0f + float pitch = -fov * (stacksRatio - 0.5f); + + for (int j = 0; j < slices; j++) { + float slicesRatio = (float)j / (float)(slices - 1); // First slice is 0.0f, last slice is 1.0f + // abs(phi) <= fov * aspectRatio / 2.0f + float yaw = -fov * aspectRatio * (slicesRatio - 0.5f); + + vertexPtr->position = getPoint(yaw, pitch); + vertexPtr->uv.x = slicesRatio; + vertexPtr->uv.y = stacksRatio; + vertexPtr++; + } + } + // Create and write to buffer + glGenBuffers(1, &_vbo.first); + glBindBuffer(GL_ARRAY_BUFFER, _vbo.first); + static const int BYTES_PER_VERTEX = sizeof(TextureVertex); + glBufferData(GL_ARRAY_BUFFER, _vertices * BYTES_PER_VERTEX, vertexData, GL_STATIC_DRAW); + delete[] vertexData; + + + // Compute number of indices needed + static const int VERTEX_PER_TRANGLE = 3; + static const int TRIANGLE_PER_RECTANGLE = 2; + int numberOfRectangles = (slices - 1) * (stacks - 1); + _indices = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE; + + // Compute indices order + GLushort* indexData = new GLushort[_indices]; + GLushort* indexPtr = indexData; + for (int i = 0; i < stacks - 1; i++) { + for (int j = 0; j < slices - 1; j++) { + GLushort bottomLeftIndex = i * slices + j; + GLushort bottomRightIndex = bottomLeftIndex + 1; + GLushort topLeftIndex = bottomLeftIndex + slices; + GLushort topRightIndex = topLeftIndex + 1; + + *(indexPtr++) = topLeftIndex; + *(indexPtr++) = bottomLeftIndex; + *(indexPtr++) = topRightIndex; + + *(indexPtr++) = topRightIndex; + *(indexPtr++) = bottomLeftIndex; + *(indexPtr++) = bottomRightIndex; + } + } + // Create and write to buffer + glGenBuffers(1, &_vbo.second); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vbo.second); + static const int BYTES_PER_INDEX = sizeof(GLushort); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indices * BYTES_PER_INDEX, indexData, GL_STATIC_DRAW); + delete[] indexData; +} + +void ApplicationOverlay::TexturedHemisphere::cleanupVBO() { + if (_vbo.first != 0) { + glDeleteBuffers(1, &_vbo.first); + _vbo.first = 0; + } + if (_vbo.second != 0) { + glDeleteBuffers(1, &_vbo.second); + _vbo.second = 0; + } +} + +void ApplicationOverlay::TexturedHemisphere::buildFramebufferObject() { + QSize size = Application::getInstance()->getGLWidget()->getDeviceSize(); + if (_framebufferObject != NULL && size == _framebufferObject->size()) { + // Already build + return; + } + + if (_framebufferObject != NULL) { + delete _framebufferObject; + } + + _framebufferObject = new QOpenGLFramebufferObject(size); + bindTexture(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + GLfloat borderColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + releaseTexture(); +} + +//Renders a hemisphere with texture coordinates. +void ApplicationOverlay::TexturedHemisphere::render() { + if (_framebufferObject == NULL || _vbo.first == 0 || _vbo.second == 0) { + qDebug() << "TexturedHemisphere::render(): Incorrect initialisation"; + return; + } + + glBindBuffer(GL_ARRAY_BUFFER, _vbo.first); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vbo.second); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + static const int STRIDE = sizeof(TextureVertex); + static const void* VERTEX_POINTER = 0; + static const void* TEX_COORD_POINTER = (void*)sizeof(glm::vec3); + glVertexPointer(3, GL_FLOAT, STRIDE, VERTEX_POINTER); + glTexCoordPointer(2, GL_FLOAT, STRIDE, TEX_COORD_POINTER); + + glDrawRangeElements(GL_TRIANGLES, 0, _vertices - 1, _indices, GL_UNSIGNED_SHORT, 0); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + + +glm::vec2 ApplicationOverlay::screenToSpherical(glm::vec2 screenPos) const { + QSize screenSize = Application::getInstance()->getGLWidget()->getDeviceSize(); + float yaw = -(screenPos.x / screenSize.width() - 0.5f) * MOUSE_YAW_RANGE; + float pitch = (screenPos.y / screenSize.height() - 0.5f) * MOUSE_PITCH_RANGE; + + return glm::vec2(yaw, pitch); +} + +glm::vec2 ApplicationOverlay::sphericalToScreen(glm::vec2 sphericalPos) const { + QSize screenSize = Application::getInstance()->getGLWidget()->getDeviceSize(); + float x = (-sphericalPos.x / MOUSE_YAW_RANGE + 0.5f) * screenSize.width(); + float y = (sphericalPos.y / MOUSE_PITCH_RANGE + 0.5f) * screenSize.height(); + + return glm::vec2(x, y); +} + +glm::vec2 ApplicationOverlay::sphericalToOverlay(glm::vec2 sphericalPos) const { + QSize screenSize = Application::getInstance()->getGLWidget()->getDeviceSize(); + float x = (-sphericalPos.x / (_textureFov * _textureAspectRatio) + 0.5f) * screenSize.width(); + float y = (sphericalPos.y / _textureFov + 0.5f) * screenSize.height(); + + return glm::vec2(x, y); +} + +glm::vec2 ApplicationOverlay::overlayToSpherical(glm::vec2 overlayPos) const { + QSize screenSize = Application::getInstance()->getGLWidget()->getDeviceSize(); + float yaw = -(overlayPos.x / screenSize.width() - 0.5f) * _textureFov * _textureAspectRatio; + float pitch = (overlayPos.y / screenSize.height() - 0.5f) * _textureFov; + + return glm::vec2(yaw, pitch); +} + +glm::vec2 ApplicationOverlay::screenToOverlay(glm::vec2 screenPos) const { + return sphericalToOverlay(screenToSpherical(screenPos)); +} + +glm::vec2 ApplicationOverlay::overlayToScreen(glm::vec2 overlayPos) const { + return sphericalToScreen(overlayToSpherical(overlayPos)); +} diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index a493f6cd1b..538a163d0e 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -15,14 +15,13 @@ class Overlays; class QOpenGLFramebufferObject; -const float MAGNIFY_WIDTH = 160.0f; -const float MAGNIFY_HEIGHT = 80.0f; -const float MAGNIFY_MULT = 4.0f; +const float MAGNIFY_WIDTH = 220.0f; +const float MAGNIFY_HEIGHT = 100.0f; +const float MAGNIFY_MULT = 2.0f; // Handles the drawing of the overlays to the screen class ApplicationOverlay { public: - ApplicationOverlay(); ~ApplicationOverlay(); @@ -30,16 +29,25 @@ public: void displayOverlayTexture(); void displayOverlayTextureOculus(Camera& whichCamera); void displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov); - void computeOculusPickRay(float x, float y, glm::vec3& direction) const; - void getClickLocation(int &x, int &y) const; + + void computeOculusPickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const; QPoint getPalmClickLocation(const PalmData *palm) const; bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const; - - - // Getters - QOpenGLFramebufferObject* getFramebufferObject(); - float getAlpha() const { return _alpha; } - + + // Converter from one frame of reference to another. + // Frame of reference: + // Screen: Position on the screen (x,y) + // Spherical: Pitch and yaw that gives the position on the sphere we project on (yaw,pitch) + // Overlay: Position on the overlay (x,y) + // (x,y) in Overlay are similar than (x,y) in Screen except they can be outside of the bound of te screen. + // This allows for picking outside of the screen projection in 3D. + glm::vec2 screenToSpherical(glm::vec2 screenPos) const; + glm::vec2 sphericalToScreen(glm::vec2 sphericalPos) const; + glm::vec2 sphericalToOverlay(glm::vec2 sphericalPos) const; + glm::vec2 overlayToSpherical(glm::vec2 overlayPos) const; + glm::vec2 screenToOverlay(glm::vec2 screenPos) const; + glm::vec2 overlayToScreen(glm::vec2 overlayPos) const; + private: // Interleaved vertex data struct TextureVertex { @@ -48,31 +56,54 @@ private: }; typedef QPair VerticesIndices; - - void renderPointers(); + class TexturedHemisphere { + public: + TexturedHemisphere(); + ~TexturedHemisphere(); + + void bind(); + void release(); + void bindTexture(); + void releaseTexture(); + + void buildFramebufferObject(); + void buildVBO(const float fov, const float aspectRatio, const int slices, const int stacks); + void render(); + + private: + void cleanupVBO(); + + GLuint _vertices; + GLuint _indices; + QOpenGLFramebufferObject* _framebufferObject; + VerticesIndices _vbo; + }; + + void renderPointers();; + void renderMagnifier(glm::vec2 magPos, float sizeMult, bool showBorder) const; + void renderControllerPointers(); void renderPointersOculus(const glm::vec3& eyePos); - void renderMagnifier(int mouseX, int mouseY, float sizeMult, bool showBorder) const; + void renderAudioMeter(); void renderStatsAndLogs(); - void renderTexturedHemisphere(); void renderDomainConnectionStatusBorder(); - QOpenGLFramebufferObject* _framebufferObject; - float _trailingAudioLoudness; - float _textureFov; + TexturedHemisphere _overlays; - enum MagnifyDevices { MOUSE, LEFT_CONTROLLER, RIGHT_CONTROLLER, NUMBER_OF_MAGNIFIERS = RIGHT_CONTROLLER + 1 }; - bool _reticleActive[NUMBER_OF_MAGNIFIERS]; - int _mouseX[NUMBER_OF_MAGNIFIERS]; - int _mouseY[NUMBER_OF_MAGNIFIERS]; - bool _magActive[NUMBER_OF_MAGNIFIERS]; - int _magX[NUMBER_OF_MAGNIFIERS]; - int _magY[NUMBER_OF_MAGNIFIERS]; - float _magSizeMult[NUMBER_OF_MAGNIFIERS]; + float _textureFov; + float _textureAspectRatio; + + enum Reticules { MOUSE, LEFT_CONTROLLER, RIGHT_CONTROLLER, NUMBER_OF_RETICULES }; + bool _reticleActive[NUMBER_OF_RETICULES]; + QPoint _reticulePosition[NUMBER_OF_RETICULES]; + bool _magActive[NUMBER_OF_RETICULES]; + float _magSizeMult[NUMBER_OF_RETICULES]; + quint64 _lastMouseMove; float _alpha; - float _oculusuiRadius; + float _oculusUIRadius; + float _trailingAudioLoudness; GLuint _crosshairTexture; }; diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 349a73b9c7..97c4c08b41 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -182,13 +182,6 @@ QVariant MetavoxelEditor::getValue() const { return editor ? editor->metaObject()->userProperty().read(editor) : QVariant(); } -void MetavoxelEditor::detachValue() { - SharedObjectEditor* editor = qobject_cast(_valueArea->widget()); - if (editor) { - editor->detachObject(); - } -} - bool MetavoxelEditor::eventFilter(QObject* watched, QEvent* event) { // pass along to the active tool MetavoxelTool* tool = getActiveTool(); @@ -616,7 +609,7 @@ PlaceSpannerTool::PlaceSpannerTool(MetavoxelEditor* editor, const QString& name, } void PlaceSpannerTool::simulate(float deltaTime) { - Spanner* spanner = static_cast(getSpanner(true).data()); + Spanner* spanner = static_cast(getSpanner().data()); Transformable* transformable = qobject_cast(spanner); if (transformable && _followMouse->isChecked() && !Application::getInstance()->isMouseHidden()) { // find the intersection of the mouse ray with the grid and place the transformable there @@ -634,7 +627,7 @@ void PlaceSpannerTool::simulate(float deltaTime) { void PlaceSpannerTool::renderPreview() { Spanner* spanner = static_cast(getSpanner().data()); - spanner->getRenderer()->render(); + spanner->getRenderer()->render(Application::getInstance()->getMetavoxels()->getLOD()); } bool PlaceSpannerTool::appliesTo(const AttributePointer& attribute) const { @@ -649,10 +642,7 @@ bool PlaceSpannerTool::eventFilter(QObject* watched, QEvent* event) { return false; } -SharedObjectPointer PlaceSpannerTool::getSpanner(bool detach) { - if (detach) { - _editor->detachValue(); - } +SharedObjectPointer PlaceSpannerTool::getSpanner() { return _editor->getValue().value(); } @@ -663,7 +653,7 @@ QColor PlaceSpannerTool::getColor() { void PlaceSpannerTool::place() { AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute()); if (attribute) { - applyEdit(attribute, getSpanner()); + applyEdit(attribute, getSpanner()->clone()); } } @@ -732,11 +722,11 @@ HeightfieldTool::HeightfieldTool(MetavoxelEditor* editor, const QString& name) : layout()->addWidget(widget); _form->addRow("Translation:", _translation = new Vec3Editor(widget)); - _form->addRow("Scale:", _scale = new QDoubleSpinBox()); - _scale->setMinimum(-FLT_MAX); - _scale->setMaximum(FLT_MAX); - _scale->setPrefix("2^"); - _scale->setValue(2.0); + _form->addRow("Spacing:", _spacing = new QDoubleSpinBox()); + _spacing->setMaximum(FLT_MAX); + _spacing->setDecimals(3); + _spacing->setSingleStep(0.001); + _spacing->setValue(1.0); QPushButton* applyButton = new QPushButton("Apply"); layout()->addWidget(applyButton); @@ -747,28 +737,20 @@ bool HeightfieldTool::appliesTo(const AttributePointer& attribute) const { return attribute->inherits("SpannerSetAttribute"); } -void HeightfieldTool::render() { - float scale = pow(2.0, _scale->value()); - _translation->setSingleStep(scale); - glm::vec3 quantizedTranslation = scale * glm::floor(_translation->getValue() / scale); - _translation->setValue(quantizedTranslation); -} - ImportHeightfieldTool::ImportHeightfieldTool(MetavoxelEditor* editor) : HeightfieldTool(editor, "Import Heightfield"), _spanner(new Heightfield()) { _form->addRow("Height Scale:", _heightScale = new QDoubleSpinBox()); - const double MAX_OFFSET_SCALE = 100000.0; - _heightScale->setMaximum(MAX_OFFSET_SCALE); + _heightScale->setMaximum(FLT_MAX); _heightScale->setSingleStep(0.01); - _heightScale->setValue(8.0); + _heightScale->setValue(16.0); connect(_heightScale, static_cast(&QDoubleSpinBox::valueChanged), this, &ImportHeightfieldTool::updateSpanner); _form->addRow("Height Offset:", _heightOffset = new QDoubleSpinBox()); - _heightOffset->setMinimum(-MAX_OFFSET_SCALE); - _heightOffset->setMaximum(MAX_OFFSET_SCALE); + _heightOffset->setMinimum(-FLT_MAX); + _heightOffset->setMaximum(FLT_MAX); _heightOffset->setSingleStep(0.01); connect(_heightOffset, static_cast(&QDoubleSpinBox::valueChanged), this, &ImportHeightfieldTool::updateSpanner); @@ -780,7 +762,7 @@ ImportHeightfieldTool::ImportHeightfieldTool(MetavoxelEditor* editor) : connect(_color, &HeightfieldColorEditor::colorChanged, this, &ImportHeightfieldTool::updateSpanner); connect(_translation, &Vec3Editor::valueChanged, this, &ImportHeightfieldTool::updateSpanner); - connect(_scale, static_cast(&QDoubleSpinBox::valueChanged), this, + connect(_spacing, static_cast(&QDoubleSpinBox::valueChanged), this, &ImportHeightfieldTool::updateSpanner); } @@ -789,77 +771,16 @@ void ImportHeightfieldTool::simulate(float deltaTime) { } void ImportHeightfieldTool::renderPreview() { - static_cast(_spanner.data())->getRenderer()->render(); + static_cast(_spanner.data())->getRenderer()->render(Application::getInstance()->getMetavoxels()->getLOD()); } -const int HEIGHTFIELD_BLOCK_SIZE = 256; - void ImportHeightfieldTool::apply() { AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute()); if (!(_height->getHeight() && attribute)) { return; } - int width = _height->getHeight()->getWidth(); - const QVector& contents = _height->getHeight()->getContents(); - int height = contents.size() / width; - int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION; - int innerHeight = height - HeightfieldHeight::HEIGHT_EXTENSION; - float scale = pow(2.0, _scale->value()); - - for (int i = 0; i < innerHeight; i += HEIGHTFIELD_BLOCK_SIZE) { - for (int j = 0; j < innerWidth; j += HEIGHTFIELD_BLOCK_SIZE) { - Heightfield* heightfield = new Heightfield(); - - int extendedHeightSize = HEIGHTFIELD_BLOCK_SIZE + HeightfieldHeight::HEIGHT_EXTENSION; - QVector heightContents(extendedHeightSize * extendedHeightSize); - quint16* dest = heightContents.data(); - const quint16* src = contents.constData() + i * width + j; - int copyWidth = qMin(width - j, extendedHeightSize); - int copyHeight = qMin(height - i, extendedHeightSize); - for (int z = 0; z < copyHeight; z++, src += width, dest += extendedHeightSize) { - memcpy(dest, src, copyWidth * sizeof(quint16)); - } - heightfield->setHeight(HeightfieldHeightPointer(new HeightfieldHeight(extendedHeightSize, heightContents))); - - int materialWidth = HEIGHTFIELD_BLOCK_SIZE + HeightfieldData::SHARED_EDGE; - int materialHeight = materialWidth; - if (_color->getColor()) { - int colorWidth = _color->getColor()->getWidth(); - const QByteArray& contents = _color->getColor()->getContents(); - int colorHeight = contents.size() / (colorWidth * DataBlock::COLOR_BYTES); - int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; - int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; - materialWidth = HEIGHTFIELD_BLOCK_SIZE * innerColorWidth / innerWidth + HeightfieldData::SHARED_EDGE; - materialHeight = HEIGHTFIELD_BLOCK_SIZE * innerColorHeight / innerHeight + HeightfieldData::SHARED_EDGE; - QByteArray colorContents(materialWidth * materialHeight * DataBlock::COLOR_BYTES, 0); - int colorI = i * (materialWidth - HeightfieldData::SHARED_EDGE) / HEIGHTFIELD_BLOCK_SIZE; - int colorJ = j * (materialHeight - HeightfieldData::SHARED_EDGE) / HEIGHTFIELD_BLOCK_SIZE; - char* dest = colorContents.data(); - const char* src = contents.constData() + (colorI * colorWidth + colorJ) * DataBlock::COLOR_BYTES; - int copyWidth = qMin(colorWidth - colorJ, materialWidth); - int copyHeight = qMin(colorHeight - colorI, materialHeight); - for (int z = 0; z < copyHeight; z++, src += colorWidth * DataBlock::COLOR_BYTES, - dest += materialWidth * DataBlock::COLOR_BYTES) { - memcpy(dest, src, copyWidth * DataBlock::COLOR_BYTES); - } - heightfield->setColor(HeightfieldColorPointer(new HeightfieldColor(materialWidth, colorContents))); - - } else { - heightfield->setColor(HeightfieldColorPointer(new HeightfieldColor(materialWidth, - QByteArray(materialWidth * materialHeight * DataBlock::COLOR_BYTES, 0xFF)))); - } - heightfield->setMaterial(HeightfieldMaterialPointer(new HeightfieldMaterial(materialWidth, - QByteArray(materialWidth * materialHeight, 0), QVector()))); - - heightfield->setScale(scale); - heightfield->setAspectY(_heightScale->value() / scale); - heightfield->setTranslation(_translation->getValue() + glm::vec3((j / HEIGHTFIELD_BLOCK_SIZE) * scale, - _heightOffset->value(), (i / HEIGHTFIELD_BLOCK_SIZE) * scale)); - - MetavoxelEditMessage message = { QVariant::fromValue(InsertSpannerEdit(attribute, heightfield)) }; - Application::getInstance()->getMetavoxels()->applyEdit(message, true); - } - } + MetavoxelEditMessage message = { QVariant::fromValue(InsertSpannerEdit(attribute, _spanner->clone())) }; + Application::getInstance()->getMetavoxels()->applyEdit(message, true); } void ImportHeightfieldTool::updateSpanner() { @@ -867,15 +788,14 @@ void ImportHeightfieldTool::updateSpanner() { heightfield->setHeight(_height->getHeight()); heightfield->setColor(_color->getColor()); - float scale = pow(2.0, _scale->value()); + float scale = 1.0f; float aspectZ = 1.0f; if (_height->getHeight()) { int width = _height->getHeight()->getWidth(); int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeight = _height->getHeight()->getContents().size() / width - HeightfieldHeight::HEIGHT_EXTENSION; - float widthBlocks = glm::ceil((float)innerWidth / HEIGHTFIELD_BLOCK_SIZE); - scale *= widthBlocks; - aspectZ = glm::ceil((float)innerHeight / HEIGHTFIELD_BLOCK_SIZE) / widthBlocks; + scale = innerWidth * _spacing->value(); + aspectZ = (float)innerHeight / innerWidth; } heightfield->setScale(scale); heightfield->setAspectY(_heightScale->value() / scale); @@ -970,7 +890,8 @@ MaterialControl::MaterialControl(QWidget* widget, QFormLayout* form, bool cleara SharedObjectPointer MaterialControl::getMaterial() { SharedObjectPointer material = _materialEditor->getObject(); if (static_cast(material.data())->getDiffuse().isValid()) { - _materialEditor->detachObject(); + material = material->clone(); + } else { material = SharedObjectPointer(); } @@ -1087,10 +1008,7 @@ bool VoxelMaterialSpannerTool::appliesTo(const AttributePointer& attribute) cons return attribute->inherits("VoxelColorAttribute"); } -SharedObjectPointer VoxelMaterialSpannerTool::getSpanner(bool detach) { - if (detach) { - _spannerEditor->detachObject(); - } +SharedObjectPointer VoxelMaterialSpannerTool::getSpanner() { return _spannerEditor->getObject(); } @@ -1099,7 +1017,7 @@ QColor VoxelMaterialSpannerTool::getColor() { } void VoxelMaterialSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) { - _spannerEditor->detachObject(); + static_cast(spanner.data())->setWillBeVoxelized(true); MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSpannerEdit(spanner, _materialControl->getMaterial(), _materialControl->getColor())) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 15e731e2ee..c154d7bc59 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -46,7 +46,6 @@ public: glm::quat getGridRotation() const; QVariant getValue() const; - void detachValue(); virtual bool eventFilter(QObject* watched, QEvent* event); @@ -197,7 +196,7 @@ public: protected: virtual QColor getColor(); - virtual SharedObjectPointer getSpanner(bool detach = false); + virtual SharedObjectPointer getSpanner(); virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) = 0; protected slots: @@ -260,8 +259,6 @@ public: virtual bool appliesTo(const AttributePointer& attribute) const; - virtual void render(); - protected slots: virtual void apply() = 0; @@ -270,7 +267,7 @@ protected: QFormLayout* _form; Vec3Editor* _translation; - QDoubleSpinBox* _scale; + QDoubleSpinBox* _spacing; }; /// Allows importing a heightfield. @@ -425,7 +422,7 @@ public: protected: - virtual SharedObjectPointer getSpanner(bool detach = false); + virtual SharedObjectPointer getSpanner(); virtual QColor getColor(); virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner); diff --git a/interface/src/ui/NodeBounds.cpp b/interface/src/ui/NodeBounds.cpp index 49509cc9cf..3c6b4c625a 100644 --- a/interface/src/ui/NodeBounds.cpp +++ b/interface/src/ui/NodeBounds.cpp @@ -38,12 +38,7 @@ void NodeBounds::draw() { // Compute ray to find selected nodes later on. We can't use the pre-computed ray in Application because it centers // itself after the cursor disappears. Application* application = Application::getInstance(); - GLCanvas* glWidget = application->getGLWidget(); - float mouseX = application->getMouseX() / (float)glWidget->width(); - float mouseY = application->getMouseY() / (float)glWidget->height(); - glm::vec3 mouseRayOrigin; - glm::vec3 mouseRayDirection; - application->getViewFrustum()->computePickRay(mouseX, mouseY, mouseRayOrigin, mouseRayDirection); + PickRay pickRay = application->getCamera()->computeViewPickRay(application->getTrueMouseX(), application->getTrueMouseY()); // Variables to keep track of the selected node and properties to draw the cube later if needed Node* selectedNode = NULL; @@ -106,8 +101,8 @@ void NodeBounds::draw() { float distance; BoxFace face; - bool inside = serverBounds.contains(mouseRayOrigin); - bool colliding = serverBounds.findRayIntersection(mouseRayOrigin, mouseRayDirection, distance, face); + bool inside = serverBounds.contains(pickRay.origin); + bool colliding = serverBounds.findRayIntersection(pickRay.origin, pickRay.direction, distance, face); // If the camera is inside a node it will be "selected" if you don't have your cursor over another node // that you aren't inside. @@ -225,8 +220,8 @@ void NodeBounds::drawOverlay() { const int MOUSE_OFFSET = 10; const int BACKGROUND_BEVEL = 3; - int mouseX = application->getMouseX(), - mouseY = application->getMouseY(), + int mouseX = application->getTrueMouseX(), + mouseY = application->getTrueMouseY(), textWidth = widthText(TEXT_SCALE, 0, _overlayText); glColor4f(0.4f, 0.4f, 0.4f, 0.6f); renderBevelCornersRect(mouseX + MOUSE_OFFSET, mouseY - TEXT_HEIGHT - PADDING, diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 55b4c88812..a9588cd7a3 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -167,7 +167,7 @@ QScriptValue Base3DOverlay::getProperty(const QString& property) { } bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face) const { + float& distance, BoxFace& face) { return false; } diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index d57f9731c4..b5314bd6d3 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -50,10 +50,10 @@ public: virtual void setProperties(const QScriptValue& properties); virtual QScriptValue getProperty(const QString& property); - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face); virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, QString& extraInfo) const { + float& distance, BoxFace& face, QString& extraInfo) { return findRayIntersection(origin, direction, distance, face); } diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 5fbad7839a..f9ad7fdb38 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -213,7 +213,7 @@ void BillboardOverlay::replyFinished() { } bool BillboardOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face) const { + float& distance, BoxFace& face) { if (_billboardTexture) { float maxSize = glm::max(_fromImage.width(), _fromImage.height()); diff --git a/interface/src/ui/overlays/BillboardOverlay.h b/interface/src/ui/overlays/BillboardOverlay.h index be947acf98..03daef934d 100644 --- a/interface/src/ui/overlays/BillboardOverlay.h +++ b/interface/src/ui/overlays/BillboardOverlay.h @@ -35,7 +35,7 @@ public: void setClipFromSource(const QRect& bounds) { _fromImage = bounds; } virtual QScriptValue getProperty(const QString& property); - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face); virtual BillboardOverlay* createClone() const; diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index d19297b589..68d589d20b 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -355,7 +355,7 @@ QScriptValue Circle3DOverlay::getProperty(const QString& property) { bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, - const glm::vec3& direction, float& distance, BoxFace& face) const { + const glm::vec3& direction, float& distance, BoxFace& face) { bool intersects = Planar3DOverlay::findRayIntersection(origin, direction, distance, face); if (intersects) { diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index b428be7a43..92fdf54c82 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -48,7 +48,7 @@ public: void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; } void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; } - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face); virtual Circle3DOverlay* createClone() const; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 60049e0b3b..ecce137f4d 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -170,14 +170,14 @@ QScriptValue ModelOverlay::getProperty(const QString& property) { } bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face) const { + float& distance, BoxFace& face) { QString subMeshNameTemp; return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, subMeshNameTemp); } bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, QString& extraInfo) const { + float& distance, BoxFace& face, QString& extraInfo) { return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo); } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 80b52ea27e..567498feb5 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -26,9 +26,9 @@ public: virtual void render(RenderArgs* args); virtual void setProperties(const QScriptValue& properties); virtual QScriptValue getProperty(const QString& property); - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face); virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, QString& extraInfo) const; + float& distance, BoxFace& face, QString& extraInfo); virtual ModelOverlay* createClone() const; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 455b73fb80..25e667e56c 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -244,6 +245,11 @@ void Overlays::deleteOverlay(unsigned int id) { } unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) { + glm::vec2 pointCopy = point; + if (OculusManager::isConnected()) { + pointCopy = Application::getInstance()->getApplicationOverlay().screenToOverlay(point); + } + QReadLocker lock(&_lock); QMapIterator i(_overlays2D); i.toBack(); @@ -251,7 +257,8 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) { i.previous(); unsigned int thisID = i.key(); Overlay2D* thisOverlay = static_cast(i.value()); - if (thisOverlay->getVisible() && thisOverlay->isLoaded() && thisOverlay->getBounds().contains(point.x, point.y, false)) { + if (thisOverlay->getVisible() && thisOverlay->isLoaded() && + thisOverlay->getBounds().contains(pointCopy.x, pointCopy.y, false)) { return thisID; } } diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp index a8288b241c..628ff6a7dc 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -92,7 +92,7 @@ QScriptValue Planar3DOverlay::getProperty(const QString& property) { } bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face) const { + float& distance, BoxFace& face) { RayIntersectionInfo rayInfo; rayInfo._rayStart = origin; diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h index d34fe44ebc..9355265f80 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.h +++ b/interface/src/ui/overlays/Planar3DOverlay.h @@ -39,7 +39,7 @@ public: virtual void setProperties(const QScriptValue& properties); virtual QScriptValue getProperty(const QString& property); - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face); protected: glm::vec2 _dimensions; diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index c4192a15b2..40fea5c8c9 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -100,7 +100,7 @@ QScriptValue Volume3DOverlay::getProperty(const QString& property) { } bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face) const { + float& distance, BoxFace& face) { // extents is the entity relative, scaled, centered extents of the entity glm::vec3 position = getPosition(); diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index 005646c036..7938641a8f 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -41,7 +41,7 @@ public: virtual void setProperties(const QScriptValue& properties); virtual QScriptValue getProperty(const QString& property); - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face); protected: glm::vec3 _dimensions; diff --git a/libraries/audio/src/AudioScriptingInterface.cpp b/libraries/audio/src/AudioScriptingInterface.cpp index b604e2825b..8cd133ad40 100644 --- a/libraries/audio/src/AudioScriptingInterface.cpp +++ b/libraries/audio/src/AudioScriptingInterface.cpp @@ -85,6 +85,13 @@ bool AudioScriptingInterface::isInjectorPlaying(AudioInjector* injector) { return (injector != NULL); } +void AudioScriptingInterface::setInjectorOptions(AudioInjector* injector, const AudioInjectorOptions& injectorOptions) { + AudioInjectorOptions optionsCopy = injectorOptions; + if (injector) { + injector->setOptions(optionsCopy); + } +} + float AudioScriptingInterface::getLoudness(AudioInjector* injector) { if (injector) { return injector->getLoudness(); diff --git a/libraries/audio/src/AudioScriptingInterface.h b/libraries/audio/src/AudioScriptingInterface.h index 5b67666a97..b437286ecf 100644 --- a/libraries/audio/src/AudioScriptingInterface.h +++ b/libraries/audio/src/AudioScriptingInterface.h @@ -35,6 +35,8 @@ public slots: void stopInjector(AudioInjector* injector); bool isInjectorPlaying(AudioInjector* injector); + void setInjectorOptions(AudioInjector* injector, const AudioInjectorOptions& injectorOptions); + void injectorStopped(); signals: diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index bde1ebc684..314b1c23d5 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -137,7 +137,7 @@ public: virtual bool supportsDetailedRayIntersection() const { return false; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject) const { return true; } + void** intersectedObject, bool precisionPicking) const { return true; } // attributes applicable to all entity types EntityTypes::EntityType getType() const { return _type; } @@ -159,7 +159,7 @@ public: 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; recalculateCollisionShape(); } + virtual void setDimensions(const glm::vec3& value) { _dimensions = value; recalculateCollisionShape(); } /// 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); } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 575a6c1a78..26870ad9bb 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -196,22 +196,26 @@ QVector EntityScriptingInterface::findEntities(const glm::vec3& ce return result; } -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray) { - return findRayIntersectionWorker(ray, Octree::TryLock); +RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking) { + return findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking); } -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray) { - return findRayIntersectionWorker(ray, Octree::Lock); +RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking) { + return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); } RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray, - Octree::lockType lockType) { + Octree::lockType lockType, + bool precisionPicking) { + + RayToEntityIntersectionResult result; if (_entityTree) { OctreeElement* element; EntityItem* intersectedEntity = NULL; result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, - (void**)&intersectedEntity, lockType, &result.accurate); + (void**)&intersectedEntity, lockType, &result.accurate, + precisionPicking); if (result.intersects && intersectedEntity) { result.entityID = intersectedEntity->getEntityItemID(); result.properties = intersectedEntity->getProperties(); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index da0c6c9f1a..1233af678d 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -90,11 +90,11 @@ public slots: /// If the scripting context has visible voxels, this will determine a ray intersection, the results /// may be inaccurate if the engine is unable to access the visible voxels, in which case result.accurate /// will be false. - Q_INVOKABLE RayToEntityIntersectionResult findRayIntersection(const PickRay& ray); + Q_INVOKABLE RayToEntityIntersectionResult findRayIntersection(const PickRay& ray, bool precisionPicking = false); /// If the scripting context has visible voxels, this will determine a ray intersection, and will block in /// order to return an accurate result - Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray); + Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false); Q_INVOKABLE void setLightsArePickable(bool value); Q_INVOKABLE bool getLightsArePickable() const; @@ -124,7 +124,8 @@ private: void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); /// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode - RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType); + RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, + bool precisionPicking); uint32_t _nextCreatorTokenID; EntityTree* _entityTree; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8c18f965aa..48ea2b7c07 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -19,9 +19,13 @@ #include "MovingEntitiesOperator.h" #include "UpdateEntityOperator.h" -EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage), _simulation(NULL) { +EntityTree::EntityTree(bool shouldReaverage) : + Octree(shouldReaverage), + _fbxService(NULL), + _lightsArePickable(true), + _simulation(NULL) +{ _rootElement = createNewElement(); - _lightsArePickable = true; // assume they are by default } EntityTree::~EntityTree() { diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index d3e303009c..05928990d1 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -475,13 +475,17 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3 bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject) { + void** intersectedObject, bool precisionPicking, float distanceToElementCube) { // only called if we do intersect our bounding cube, but find if we actually intersect with entities... + int entityNumber = 0; QList::iterator entityItr = _entityItems->begin(); QList::const_iterator entityEnd = _entityItems->end(); bool somethingIntersected = false; + + //float bestEntityDistance = distance; + while(entityItr != entityEnd) { EntityItem* entity = (*entityItr); @@ -513,10 +517,9 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con if (localDistance < distance) { // now ask the entity if we actually intersect if (entity->supportsDetailedRayIntersection()) { - if (entity->findDetailedRayIntersection(origin, direction, keepSearching, element, localDistance, - localFace, intersectedObject)) { - + localFace, intersectedObject, precisionPicking)) { + if (localDistance < distance) { distance = localDistance; face = localFace; @@ -538,6 +541,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con } ++entityItr; + entityNumber++; } return somethingIntersected; } diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index ab3754749b..4fbe9db323 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -137,7 +137,7 @@ public: virtual bool canRayIntersect() const { return hasEntities(); } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject); + void** intersectedObject, bool precisionPicking, float distanceToElementCube); virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const; diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 20f28cd98c..a24fe58c2a 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -47,6 +47,13 @@ LightEntityItem::LightEntityItem(const EntityItemID& entityItemID, const EntityI _emptyShape.setRadius(0.0f); } +void LightEntityItem::setDimensions(const glm::vec3& value) { + float maxDimension = glm::max(value.x, value.y, value.z); + _dimensions = glm::vec3(maxDimension, maxDimension, maxDimension); + recalculateCollisionShape(); +} + + EntityItemProperties LightEntityItem::getProperties() const { EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 2006efb896..eb9a2ed051 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -22,6 +22,9 @@ public: LightEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); ALLOW_INSTANTIATION // This class can be instantiated + + /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately + virtual void setDimensions(const glm::vec3& value); // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties() const; diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 12fdd7a8c4..b0df310ad2 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -10,9 +10,12 @@ // +#include + #include #include +#include #include "EntityTree.h" #include "EntityTreeElement.h" @@ -96,19 +99,25 @@ void SphereEntityItem::recalculateCollisionShape() { bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject) const { - - // NOTE: origin and direction are in tree units. But our _sphereShape is in meters, so we need to - // do a little math to make these match each other. - RayIntersectionInfo rayInfo; - rayInfo._rayStart = origin * (float)TREE_SCALE; - rayInfo._rayDirection = direction; + void** intersectedObject, bool precisionPicking) const { + // determine the ray in the frame of the entity transformed from a unit sphere + glm::mat4 translation = glm::translate(getPosition()); + glm::mat4 rotation = glm::mat4_cast(getRotation()); + glm::mat4 scale = glm::scale(getDimensions()); + glm::mat4 registration = glm::translate(glm::vec3(0.5f, 0.5f, 0.5f) - getRegistrationPoint()); + glm::mat4 entityToWorldMatrix = translation * rotation * scale * registration; + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 1.0f))); - // TODO: Note this is really doing ray intersections against a sphere, which is fine except in cases - // where our dimensions actually make us an ellipsoid. But we'll live with this for now until we - // get a more full fledged physics library - if (_sphereShape.findRayIntersection(rayInfo)) { - distance = rayInfo._hitDistance / (float)TREE_SCALE; + float localDistance; + // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 + if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, localDistance)) { + // determine where on the unit sphere the hit point occured + glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance); + // then translate back to work coordinates + glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); + distance = glm::distance(origin,hitAt); return true; } return false; diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index bb4f41726c..74cc7d1635 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -59,8 +59,8 @@ public: virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject) const; - + void** intersectedObject, bool precisionPicking) const; + protected: virtual void recalculateCollisionShape(); diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 17ef33ee1c..bb76a2e690 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -40,6 +40,13 @@ TextEntityItem::TextEntityItem(const EntityItemID& entityItemID, const EntityIte setProperties(properties, true); } +void TextEntityItem::setDimensions(const glm::vec3& value) { + // NOTE: Text Entities always have a "depth" of 1cm. + float fixedDepth = 0.01f / (float)TREE_SCALE; + _dimensions = glm::vec3(value.x, value.y, fixedDepth); + recalculateCollisionShape(); +} + EntityItemProperties TextEntityItem::getProperties() const { EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class @@ -118,7 +125,7 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject) const { + void** intersectedObject, bool precisionPicking) const { RayIntersectionInfo rayInfo; rayInfo._rayStart = origin; diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index a3d323aefd..83143b1134 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -21,6 +21,9 @@ public: TextEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); ALLOW_INSTANTIATION // This class can be instantiated + + /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately + virtual void setDimensions(const glm::vec3& value); // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties() const; @@ -44,7 +47,7 @@ public: virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject) const; + void** intersectedObject, bool precisionPicking) const; static const QString DEFAULT_TEXT; void setText(const QString& value) { _text = value; } diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 5f215ac4d0..d52d342d78 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1046,6 +1046,7 @@ FBXBlendshape extractBlendshape(const FBXNode& object) { return blendshape; } + void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) { const glm::vec3& normal = mesh.normals.at(firstIndex); glm::vec3 bitangent = glm::cross(normal, mesh.vertices.at(secondIndex) - mesh.vertices.at(firstIndex)); @@ -1194,6 +1195,44 @@ int matchTextureUVSetToAttributeChannel(const std::string& texUVSetName, const Q } } + +FBXLight extractLight(const FBXNode& object) { + FBXLight light; + + foreach (const FBXNode& subobject, object.children) { + std::string childname = QString(subobject.name).toStdString(); + if (subobject.name == "Properties70") { + foreach (const FBXNode& property, subobject.children) { + int valIndex = 4; + std::string propName = QString(property.name).toStdString(); + if (property.name == "P") { + std::string propname = property.properties.at(0).toString().toStdString(); + if (propname == "Intensity") { + light.intensity = 0.01f * property.properties.at(valIndex).value(); + } + } + } + } else if ( subobject.name == "GeometryVersion" + || subobject.name == "TypeFlags") { + } + } +#if defined(DEBUG_FBXREADER) + + std::string type = object.properties.at(0).toString().toStdString(); + type = object.properties.at(1).toString().toStdString(); + type = object.properties.at(2).toString().toStdString(); + + foreach (const QVariant& prop, object.properties) { + std::string proptype = prop.typeName(); + std::string propval = prop.toString().toStdString(); + if (proptype == "Properties70") { + } + } +#endif + + return light; +} + FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, bool loadLightmaps, float lightmapLevel) { QHash meshes; QHash modelIDsToNames; @@ -1222,6 +1261,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, QHash yComponents; QHash zComponents; + std::map lights; + QVariantHash joints = mapping.value("joint").toHash(); QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight"))); @@ -1276,6 +1317,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, #endif FBXGeometry geometry; float unitScaleFactor = 1.0f; + glm::vec3 ambientColor; + QString hifiGlobalNodeID; foreach (const FBXNode& child, node.children) { if (child.name == "FBXHeaderExtension") { @@ -1302,10 +1345,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } else if (child.name == "GlobalSettings") { foreach (const FBXNode& object, child.children) { if (object.name == "Properties70") { + QString propertyName = "P"; + int index = 4; foreach (const FBXNode& subobject, object.children) { - if (subobject.name == "P" && subobject.properties.size() >= 5 && - subobject.properties.at(0) == "UnitScaleFactor") { - unitScaleFactor = subobject.properties.at(4).toFloat(); + if (subobject.name == propertyName) { + std::string subpropName = subobject.properties.at(0).toString().toStdString(); + if (subpropName == "UnitScaleFactor") { + unitScaleFactor = subobject.properties.at(index).toFloat(); + } else if (subpropName == "AmbientColor") { + ambientColor = getVec3(subobject.properties, index); + } } } } @@ -1324,6 +1373,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, QString id = getID(object.properties); modelIDsToNames.insert(id, name); + std::string modelname = name.toLower().toStdString(); + if (modelname.find("hifi") == 0) { + hifiGlobalNodeID = id; + } + if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye") { jointEyeLeftID = getID(object.properties); @@ -1354,6 +1408,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } else if (name == "RightToe" || name == "joint_R_toe" || name == "RightToe_End") { jointRightToeID = getID(object.properties); } + int humanIKJointIndex = humanIKJointNames.indexOf(name); if (humanIKJointIndex != -1) { humanIKJointIDs[humanIKJointIndex] = getID(object.properties); @@ -1450,6 +1505,25 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, extractBlendshape(subobject) }; blendshapes.append(blendshape); } +#if defined(DEBUG_FBXREADER) + else if (subobject.name == "TypeFlags") { + std::string attributetype = subobject.properties.at(0).toString().toStdString(); + if (!attributetype.empty()) { + if (attributetype == "Light") { + std::string lightprop; + foreach (const QVariant& vprop, subobject.properties) { + lightprop = vprop.toString().toStdString(); + } + + FBXLight light = extractLight(object); + } + } + } else { + std::string whatisthat = subobject.name; + if (whatisthat == "WTF") { + } + } +#endif } // add the blendshapes included in the model, if any @@ -1477,7 +1551,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } else if (object.name == "Texture") { TextureParam tex; - bool texparam = false; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "RelativeFilename") { // trim off any path information @@ -1625,11 +1698,28 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, materials.insert(material.id, material); } else if (object.name == "NodeAttribute") { +#if defined(DEBUG_FBXREADER) + std::vector properties; + foreach(const QVariant& v, object.properties) { + properties.push_back(v.toString().toStdString()); + } +#endif + std::string attribID = getID(object.properties).toStdString(); + std::string attributetype; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "TypeFlags") { typeFlags.insert(getID(object.properties), subobject.properties.at(0).toString()); + attributetype = subobject.properties.at(0).toString().toStdString(); } } + + if (!attributetype.empty()) { + if (attributetype == "Light") { + FBXLight light = extractLight(object); + lights[attribID] = light; + } + } + } else if (object.name == "Deformer") { if (object.properties.last() == "Cluster") { Cluster cluster; @@ -1667,7 +1757,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } } animationCurves.insert(getID(object.properties), curve); + } +#if defined(DEBUG_FBXREADER) + else { + std::string objectname = object.name.data(); + if ( objectname == "Pose" + || objectname == "AnimationStack" + || objectname == "AnimationLayer" + || objectname == "AnimationCurveNode") { + } else { + unknown++; + } + } +#endif } } else if (child.name == "Connections") { foreach (const FBXNode& connection, child.children) { @@ -1676,6 +1779,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, QString childID = getID(connection.properties, 1); QString parentID = getID(connection.properties, 2); ooChildToParent.insert(childID, parentID); + if (!hifiGlobalNodeID.isEmpty() && (parentID == hifiGlobalNodeID)) { + std::map< std::string, FBXLight >::iterator lit = lights.find(childID.toStdString()); + if (lit != lights.end()) { + lightmapLevel = (*lit).second.intensity; + if (lightmapLevel <= 0.0f) { + loadLightmaps = false; + } + } + } } if (connection.properties.at(0) == "OP") { int counter = 0; @@ -1719,6 +1831,33 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } } } +#if defined(DEBUG_FBXREADER) + else { + std::string objectname = child.name.data(); + if ( objectname == "Pose" + || objectname == "CreationTime" + || objectname == "FileId" + || objectname == "Creator" + || objectname == "Documents" + || objectname == "References" + || objectname == "Definitions" + || objectname == "Takes" + || objectname == "AnimationStack" + || objectname == "AnimationLayer" + || objectname == "AnimationCurveNode") { + } else { + unknown++; + } + } +#endif + } + + // TODO: check if is code is needed + if (!lights.empty()) { + if (hifiGlobalNodeID.isEmpty()) { + std::map< std::string, FBXLight >::iterator l = lights.begin(); + lightmapLevel = (*l).second.intensity; + } } // assign the blendshapes to their corresponding meshes @@ -1905,6 +2044,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, extracted.mesh.meshExtents.minimum = glm::min(extracted.mesh.meshExtents.minimum, transformedVertex); extracted.mesh.meshExtents.maximum = glm::max(extracted.mesh.meshExtents.maximum, transformedVertex); + extracted.mesh.modelTransform = modelTransform; } // look for textures, material properties @@ -1957,7 +2097,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, emissiveParams.y = lightmapLevel; QString emissiveTextureID = emissiveTextures.value(childID); QString ambientTextureID = ambientTextures.value(childID); - if (!emissiveTextureID.isNull() || !ambientTextureID.isNull()) { + if (loadLightmaps && (!emissiveTextureID.isNull() || !ambientTextureID.isNull())) { if (!emissiveTextureID.isNull()) { emissiveTexture = getTexture(emissiveTextureID, textureNames, textureFilenames, textureContent, textureParams); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index a5df7ccc0c..659893c128 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -149,6 +149,7 @@ public: QVector clusters; Extents meshExtents; + glm::mat4 modelTransform; bool isEye; @@ -165,6 +166,22 @@ public: QVector rotations; }; +/// A light in an FBX document. +class FBXLight { +public: + QString name; + Transform transform; + float intensity; + glm::vec3 color; + + FBXLight() : + name(), + transform(), + intensity(1.0f), + color(1.0f) + {} +}; + Q_DECLARE_METATYPE(FBXAnimationFrame) Q_DECLARE_METATYPE(QVector) diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt new file mode 100644 index 0000000000..577f9f7a58 --- /dev/null +++ b/libraries/gpu/CMakeLists.txt @@ -0,0 +1,46 @@ +set(TARGET_NAME gpu) + +# use setup_hifi_library macro to setup our project and link appropriate Qt modules +setup_hifi_library() + +include_glm() + +link_hifi_libraries(shared) +if (APPLE) + # link in required OS X frameworks and include the right GL headers + find_library(OpenGL OpenGL) + + target_link_libraries(${TARGET_NAME} ${OpenGL}) + +else (APPLE) + find_package(OpenGL REQUIRED) + + if (${OPENGL_INCLUDE_DIR}) + include_directories(SYSTEM "${OPENGL_INCLUDE_DIR}") + endif () + + target_link_libraries(${TARGET_NAME} "${OPENGL_LIBRARY}") + + # link target to external libraries + if (WIN32) + find_package(GLEW REQUIRED) + include_directories(${GLEW_INCLUDE_DIRS}) + + # we're using static GLEW, so define GLEW_STATIC + add_definitions(-DGLEW_STATIC) + + target_link_libraries(${TARGET_NAME} "${GLEW_LIBRARIES}" "${NSIGHT_LIBRARIES}" opengl32.lib) + + # try to find the Nsight package and add it to the build if we find it + find_package(NSIGHT) + if (NSIGHT_FOUND) + include_directories(${NSIGHT_INCLUDE_DIRS}) + add_definitions(-DNSIGHT_FOUND) + target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}") + endif () + + endif() +endif (APPLE) + +# call macro to link our dependencies and bubble them up via a property on our target +link_shared_dependencies() diff --git a/interface/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp similarity index 100% rename from interface/src/gpu/Batch.cpp rename to libraries/gpu/src/gpu/Batch.cpp diff --git a/interface/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h similarity index 98% rename from interface/src/gpu/Batch.h rename to libraries/gpu/src/gpu/Batch.h index 5304740dd3..601ae63a77 100644 --- a/interface/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -12,13 +12,13 @@ #define hifi_gpu_Batch_h #include -#include "InterfaceConfig.h" +#include "GPUConfig.h" #include "Transform.h" #include -#include "gpu/Stream.h" +#include "Stream.h" #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" diff --git a/interface/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp similarity index 100% rename from interface/src/gpu/Context.cpp rename to libraries/gpu/src/gpu/Context.cpp diff --git a/interface/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h similarity index 94% rename from interface/src/gpu/Context.h rename to libraries/gpu/src/gpu/Context.h index 8398288cb9..3a0fffb4ef 100644 --- a/interface/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -12,9 +12,9 @@ #define hifi_gpu_Context_h #include -#include "InterfaceConfig.h" +#include "GPUConfig.h" -#include "gpu/Resource.h" +#include "Resource.h" namespace gpu { diff --git a/interface/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h similarity index 98% rename from interface/src/gpu/Format.h rename to libraries/gpu/src/gpu/Format.h index 8faa995924..d216495b4c 100644 --- a/interface/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -12,7 +12,7 @@ #define hifi_gpu_Format_h #include -#include "InterfaceConfig.h" +#include "GPUConfig.h" namespace gpu { diff --git a/interface/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp similarity index 99% rename from interface/src/gpu/GLBackend.cpp rename to libraries/gpu/src/gpu/GLBackend.cpp index 1f8e7bf99f..90639929ca 100644 --- a/interface/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -12,7 +12,7 @@ #include -#include "gpu/Batch.h" +#include "Batch.h" using namespace gpu; @@ -445,7 +445,6 @@ void GLBackend::updateTransform() { _transform._lastMode = GL_PROJECTION; } CHECK_GL_ERROR();*/ - _transform._invalidProj; } if (_transform._invalidModel || _transform._invalidView) { diff --git a/interface/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h similarity index 98% rename from interface/src/gpu/GLBackend.h rename to libraries/gpu/src/gpu/GLBackend.h index 0e4b38d89e..5a40e9ca36 100644 --- a/interface/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -12,10 +12,10 @@ #define hifi_gpu_GLBackend_h #include -#include "InterfaceConfig.h" +#include "GPUConfig.h" -#include "gpu/Context.h" -#include "gpu/Batch.h" +#include "Context.h" +#include "Batch.h" #include diff --git a/libraries/gpu/src/gpu/GPUConfig.h b/libraries/gpu/src/gpu/GPUConfig.h new file mode 100644 index 0000000000..393a476182 --- /dev/null +++ b/libraries/gpu/src/gpu/GPUConfig.h @@ -0,0 +1,32 @@ +// +// GPUConfig.h +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 12/4/14. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef gpu__GPUConfig__ +#define gpu__GPUConfig__ + +#define GL_GLEXT_PROTOTYPES 1 + +#if defined(__APPLE__) +#include +#include + +#elif defined(WIN32) +#include +#include + +#else +#include +#include + + +#endif + +#endif diff --git a/interface/src/gpu/Resource.cpp b/libraries/gpu/src/gpu/Resource.cpp similarity index 100% rename from interface/src/gpu/Resource.cpp rename to libraries/gpu/src/gpu/Resource.cpp diff --git a/interface/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h similarity index 98% rename from interface/src/gpu/Resource.h rename to libraries/gpu/src/gpu/Resource.h index 52108c215a..6247efe675 100644 --- a/interface/src/gpu/Resource.h +++ b/libraries/gpu/src/gpu/Resource.h @@ -12,9 +12,9 @@ #define hifi_gpu_Resource_h #include -#include "InterfaceConfig.h" +#include "GPUConfig.h" -#include "gpu/Format.h" +#include "Format.h" #include diff --git a/interface/src/gpu/Stream.cpp b/libraries/gpu/src/gpu/Stream.cpp similarity index 100% rename from interface/src/gpu/Stream.cpp rename to libraries/gpu/src/gpu/Stream.cpp diff --git a/interface/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h similarity index 97% rename from interface/src/gpu/Stream.h rename to libraries/gpu/src/gpu/Stream.h index d024182531..93abfeeca3 100644 --- a/interface/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -12,10 +12,10 @@ #define hifi_gpu_Stream_h #include -#include "InterfaceConfig.h" +#include "GPUConfig.h" -#include "gpu/Resource.h" -#include "gpu/Format.h" +#include "Resource.h" +#include "Format.h" #include #include diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index fbc7f0d110..afdc0c923f 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -1274,25 +1274,37 @@ void SpannerSetAttribute::writeMetavoxelRoot(const MetavoxelNode& root, Metavoxe void SpannerSetAttribute::readMetavoxelDelta(MetavoxelData& data, const MetavoxelNode& reference, MetavoxelStreamState& state) { - forever { - SharedObjectPointer object; - state.base.stream >> object; - if (!object) { - break; + readMetavoxelSubdivision(data, state); +} + +static void writeDeltaSubdivision(SharedObjectSet& oldSet, SharedObjectSet& newSet, Bitstream& stream) { + for (SharedObjectSet::iterator newIt = newSet.begin(); newIt != newSet.end(); ) { + SharedObjectSet::iterator oldIt = oldSet.find(*newIt); + if (oldIt == oldSet.end()) { + stream << *newIt; // added + newIt = newSet.erase(newIt); + + } else { + oldSet.erase(oldIt); + newIt++; } - data.toggle(state.base.attribute, object); } - // even if the root is empty, it should still exist - if (!data.getRoot(state.base.attribute)) { - data.createRoot(state.base.attribute); + foreach (const SharedObjectPointer& object, oldSet) { + stream << object; // removed } + stream << SharedObjectPointer(); + foreach (const SharedObjectPointer& object, newSet) { + object->maybeWriteSubdivision(stream); + } + stream << SharedObjectPointer(); } void SpannerSetAttribute::writeMetavoxelDelta(const MetavoxelNode& root, const MetavoxelNode& reference, MetavoxelStreamState& state) { - state.base.visit = Spanner::getAndIncrementNextVisit(); - root.writeSpannerDelta(reference, state); - state.base.stream << SharedObjectPointer(); + SharedObjectSet oldSet, newSet; + reference.getSpanners(this, state.minimum, state.size, state.base.referenceLOD, oldSet); + root.getSpanners(this, state.minimum, state.size, state.base.lod, newSet); + writeDeltaSubdivision(oldSet, newSet, state.base.stream); } void SpannerSetAttribute::readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state) { @@ -1302,14 +1314,31 @@ void SpannerSetAttribute::readMetavoxelSubdivision(MetavoxelData& data, Metavoxe if (!object) { break; } - data.insert(state.base.attribute, object); + data.toggle(state.base.attribute, object); + } + forever { + SharedObjectPointer object; + state.base.stream >> object; + if (!object) { + break; + } + SharedObjectPointer newObject = object->readSubdivision(state.base.stream); + if (newObject != object) { + data.replace(state.base.attribute, object, newObject); + state.base.stream.addSubdividedObject(newObject); + } + } + // even if the root is empty, it should still exist + if (!data.getRoot(state.base.attribute)) { + data.createRoot(state.base.attribute); } } void SpannerSetAttribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state) { - state.base.visit = Spanner::getAndIncrementNextVisit(); - root.writeSpannerSubdivision(state); - state.base.stream << SharedObjectPointer(); + SharedObjectSet oldSet, newSet; + root.getSpanners(this, state.minimum, state.size, state.base.referenceLOD, oldSet); + root.getSpanners(this, state.minimum, state.size, state.base.lod, newSet); + writeDeltaSubdivision(oldSet, newSet, state.base.stream); } bool SpannerSetAttribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot, diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index e9ac3d6319..c3bd05d3c7 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -151,6 +151,7 @@ Bitstream::Bitstream(QDataStream& underlying, MetadataType metadataType, Generic _position(0), _metadataType(metadataType), _genericsMode(genericsMode), + _context(NULL), _objectStreamerStreamer(*this), _typeStreamerStreamer(*this), _attributeStreamer(*this), @@ -266,7 +267,9 @@ Bitstream::ReadMappings Bitstream::getAndResetReadMappings() { _typeStreamerStreamer.getAndResetTransientValues(), _attributeStreamer.getAndResetTransientValues(), _scriptStringStreamer.getAndResetTransientValues(), - _sharedObjectStreamer.getAndResetTransientValues() }; + _sharedObjectStreamer.getAndResetTransientValues(), + _subdividedObjects }; + _subdividedObjects.clear(); return mappings; } @@ -290,6 +293,16 @@ void Bitstream::persistReadMappings(const ReadMappings& mappings) { reference = it.value(); _weakSharedObjectHash.remove(it.value()->getRemoteID()); } + foreach (const SharedObjectPointer& object, mappings.subdividedObjects) { + QPointer& reference = _sharedObjectReferences[object->getRemoteOriginID()]; + if (reference && reference != object) { + int id = _sharedObjectStreamer.removePersistentValue(reference.data()); + if (id != 0) { + _sharedObjectStreamer.insertPersistentValue(id, object); + } + } + reference = object; + } } void Bitstream::persistAndResetReadMappings() { @@ -1155,6 +1168,16 @@ Bitstream& Bitstream::operator<(const ObjectStreamer* streamer) { return *this; } +static MappedObjectStreamer* createMappedObjectStreamer(const QMetaObject* metaObject, + const QVector& properties) { + for (const QMetaObject* super = metaObject; super; super = super->superClass()) { + if (super == &SharedObject::staticMetaObject) { + return new SharedObjectStreamer(metaObject, properties); + } + } + return new MappedObjectStreamer(metaObject, properties); +} + Bitstream& Bitstream::operator>(ObjectStreamerPointer& streamer) { QByteArray className; *this >> className; @@ -1230,7 +1253,7 @@ Bitstream& Bitstream::operator>(ObjectStreamerPointer& streamer) { } else if (metaObject) { const QVector& localProperties = streamer->getProperties(); if (localProperties.size() != properties.size()) { - streamer = ObjectStreamerPointer(new MappedObjectStreamer(metaObject, properties)); + streamer = ObjectStreamerPointer(createMappedObjectStreamer(metaObject, properties)); return *this; } for (int i = 0; i < localProperties.size(); i++) { @@ -1238,13 +1261,13 @@ Bitstream& Bitstream::operator>(ObjectStreamerPointer& streamer) { const StreamerPropertyPair& localProperty = localProperties.at(i); if (property.first != localProperty.first || property.second.propertyIndex() != localProperty.second.propertyIndex()) { - streamer = ObjectStreamerPointer(new MappedObjectStreamer(metaObject, properties)); + streamer = ObjectStreamerPointer(createMappedObjectStreamer(metaObject, properties)); return *this; } } return *this; } - streamer = ObjectStreamerPointer(new MappedObjectStreamer(metaObject, properties)); + streamer = ObjectStreamerPointer(createMappedObjectStreamer(metaObject, properties)); return *this; } @@ -1670,7 +1693,7 @@ QHash Bitstream::createObjectStreamer properties.append(StreamerPropertyPair(streamer->getSelf(), property)); } } - ObjectStreamerPointer streamer = ObjectStreamerPointer(new MappedObjectStreamer(metaObject, properties)); + ObjectStreamerPointer streamer = ObjectStreamerPointer(createMappedObjectStreamer(metaObject, properties)); streamer->_self = streamer; objectStreamers.insert(metaObject, streamer.data()); } @@ -2121,7 +2144,7 @@ JSONReader::JSONReader(const QJsonDocument& document, Bitstream::GenericsMode ge if (matches) { _objectStreamers.insert(name, baseStreamer->getSelf()); } else { - _objectStreamers.insert(name, ObjectStreamerPointer(new MappedObjectStreamer(metaObject, properties))); + _objectStreamers.insert(name, ObjectStreamerPointer(createMappedObjectStreamer(metaObject, properties))); } } @@ -2436,6 +2459,32 @@ QObject* MappedObjectStreamer::readRawDelta(Bitstream& in, const QObject* refere return object; } +SharedObjectStreamer::SharedObjectStreamer(const QMetaObject* metaObject, const QVector& properties) : + MappedObjectStreamer(metaObject, properties) { +} + +void SharedObjectStreamer::write(Bitstream& out, const QObject* object) const { + MappedObjectStreamer::write(out, object); + static_cast(object)->writeExtra(out); +} + +void SharedObjectStreamer::writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const { + MappedObjectStreamer::writeRawDelta(out, object, reference); + static_cast(object)->writeExtraDelta(out, static_cast(reference)); +} + +QObject* SharedObjectStreamer::read(Bitstream& in, QObject* object) const { + QObject* result = MappedObjectStreamer::read(in, object); + static_cast(result)->readExtra(in); + return result; +} + +QObject* SharedObjectStreamer::readRawDelta(Bitstream& in, const QObject* reference, QObject* object) const { + QObject* result = MappedObjectStreamer::readRawDelta(in, reference, object); + static_cast(result)->readExtraDelta(in, static_cast(reference)); + return result; +} + GenericObjectStreamer::GenericObjectStreamer(const QByteArray& name, const QVector& properties, const QByteArray& hash) : ObjectStreamer(&GenericSharedObject::staticMetaObject), diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 1fd9205387..121aa6c672 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -99,10 +99,12 @@ public: int takePersistentID(P value) { return _persistentIDs.take(value); } - void removePersistentValue(V value) { int id = _valueIDs.take(value); _persistentValues.remove(id); } + int removePersistentValue(V value) { int id = _valueIDs.take(value); _persistentValues.remove(id); return id; } V takePersistentValue(int id) { V value = _persistentValues.take(id); _valueIDs.remove(value); return value; } + void insertPersistentValue(int id, V value) { _valueIDs.insert(value, id); _persistentValues.insert(id, value); } + void copyPersistentMappings(const RepeatedValueStreamer& other); void clearPersistentMappings(); @@ -289,6 +291,7 @@ public: QHash attributeValues; QHash scriptStringValues; QHash sharedObjectValues; + QVector subdividedObjects; }; /// Performs all of the various lazily initializations (of object streamers, etc.) If multiple threads need to use @@ -342,6 +345,12 @@ public: /// Returns a reference to the underlying data stream. QDataStream& getUnderlying() { return _underlying; } + /// Sets the context pointer. + void setContext(void* context) { _context = context; } + + /// Returns the context pointer. + void* getContext() const { return _context; } + /// Substitutes the supplied metaobject for the given class name's default mapping. This is mostly useful for testing the /// process of mapping between different types, but may in the future be used for permanently renaming classes. void addMetaObjectSubstitution(const QByteArray& className, const QMetaObject* metaObject); @@ -368,6 +377,9 @@ public: /// Resets to the initial state. void reset(); + /// Adds a subdivided object, which will be added to the read mappings and used as a reference if persisted. + void addSubdividedObject(const SharedObjectPointer& object) { _subdividedObjects.append(object); } + /// Returns the set of transient mappings gathered during writing and resets them. WriteMappings getAndResetWriteMappings(); @@ -562,12 +574,16 @@ private: MetadataType _metadataType; GenericsMode _genericsMode; + void* _context; + RepeatedValueStreamer _objectStreamerStreamer; RepeatedValueStreamer _typeStreamerStreamer; RepeatedValueStreamer _attributeStreamer; RepeatedValueStreamer _scriptStringStreamer; RepeatedValueStreamer _sharedObjectStreamer; + QVector _subdividedObjects; + WeakSharedObjectHash _sharedObjectReferences; WeakSharedObjectHash _weakSharedObjectHash; @@ -1125,6 +1141,18 @@ private: QVector _properties; }; +/// A streamer that maps to a local shared object class. Shared objects can write extra, non-property data. +class SharedObjectStreamer : public MappedObjectStreamer { +public: + + SharedObjectStreamer(const QMetaObject* metaObject, const QVector& properties); + + virtual void write(Bitstream& out, const QObject* object) const; + virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const; + virtual QObject* read(Bitstream& in, QObject* object = NULL) const; + virtual QObject* readRawDelta(Bitstream& in, const QObject* reference, QObject* object = NULL) const; +}; + typedef QPair StreamerNamePair; /// A streamer for generic objects. diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index d97ed67644..1aeef8e450 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -239,39 +239,44 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) { _sendRecords.erase(_sendRecords.begin(), it + 1); } - // alert external parties so that they can read the middle - emit readyToRead(_inputStream); - - // read and dispatch the high-priority messages - int highPriorityMessageCount; - _inputStream >> highPriorityMessageCount; - int newHighPriorityMessages = highPriorityMessageCount - _receivedHighPriorityMessages; - for (int i = 0; i < highPriorityMessageCount; i++) { - QVariant data; - _inputStream >> data; - if (i >= _receivedHighPriorityMessages) { - emit receivedHighPriorityMessage(data); + try { + // alert external parties so that they can read the middle + emit readyToRead(_inputStream); + + // read and dispatch the high-priority messages + int highPriorityMessageCount; + _inputStream >> highPriorityMessageCount; + int newHighPriorityMessages = highPriorityMessageCount - _receivedHighPriorityMessages; + for (int i = 0; i < highPriorityMessageCount; i++) { + QVariant data; + _inputStream >> data; + if (i >= _receivedHighPriorityMessages) { + emit receivedHighPriorityMessage(data); + } } - } - _receivedHighPriorityMessages = highPriorityMessageCount; + _receivedHighPriorityMessages = highPriorityMessageCount; + + // read the reliable data, if any + quint32 reliableChannels; + _incomingPacketStream >> reliableChannels; + for (quint32 i = 0; i < reliableChannels; i++) { + quint32 channelIndex; + _incomingPacketStream >> channelIndex; + getReliableInputChannel(channelIndex)->readData(_incomingPacketStream); + } + + // record the receipt + ReceiveRecord record = { _incomingPacketNumber, _inputStream.getAndResetReadMappings(), newHighPriorityMessages }; + _receiveRecords.append(record); + + emit receiveRecorded(); - // read the reliable data, if any - quint32 reliableChannels; - _incomingPacketStream >> reliableChannels; - for (quint32 i = 0; i < reliableChannels; i++) { - quint32 channelIndex; - _incomingPacketStream >> channelIndex; - getReliableInputChannel(channelIndex)->readData(_incomingPacketStream); + } catch (const BitstreamException& e) { + qWarning() << "Error reading datagram:" << e.getDescription(); } _incomingPacketStream.device()->seek(0); - _inputStream.reset(); - - // record the receipt - ReceiveRecord record = { _incomingPacketNumber, _inputStream.getAndResetReadMappings(), newHighPriorityMessages }; - _receiveRecords.append(record); - - emit receiveRecorded(); + _inputStream.reset(); } void DatagramSequencer::sendClearSharedObjectMessage(int id) { diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index c340a7dd4a..860ab3e5e9 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -56,6 +56,34 @@ bool MetavoxelLOD::becameSubdividedOrCollapsed(const glm::vec3& minimum, float s return true; } +bool MetavoxelLOD::shouldSubdivide(const glm::vec2& minimum, float size, float multiplier) const { + return size >= glm::distance(glm::vec2(position), minimum + glm::vec2(size, size) * 0.5f) * threshold * multiplier; +} + +bool MetavoxelLOD::becameSubdivided(const glm::vec2& minimum, float size, + const MetavoxelLOD& reference, float multiplier) const { + if (position == reference.position && threshold >= reference.threshold) { + return false; // first off, nothing becomes subdivided if it doesn't change + } + if (!shouldSubdivide(minimum, size, multiplier)) { + return false; // this one must be subdivided + } + // TODO: find some way of culling subtrees that can't possibly contain subdivided nodes + return true; +} + +bool MetavoxelLOD::becameSubdividedOrCollapsed(const glm::vec2& minimum, float size, + const MetavoxelLOD& reference, float multiplier) const { + if (position == reference.position && threshold == reference.threshold) { + return false; // first off, nothing becomes subdivided or collapsed if it doesn't change + } + if (!(shouldSubdivide(minimum, size, multiplier) || reference.shouldSubdivide(minimum, size, multiplier))) { + return false; // this one or the reference must be subdivided + } + // TODO: find some way of culling subtrees that can't possibly contain subdivided or collapsed nodes + return true; +} + MetavoxelData::MetavoxelData() : _size(1.0f) { } @@ -577,7 +605,9 @@ void MetavoxelData::read(Bitstream& in, const MetavoxelLOD& lod) { } MetavoxelStreamBase base = { attribute, in, lod, lod }; MetavoxelStreamState state = { base, getMinimum(), _size }; + in.setContext(&base); attribute->readMetavoxelRoot(*this, state); + in.setContext(NULL); } } @@ -587,7 +617,9 @@ void MetavoxelData::write(Bitstream& out, const MetavoxelLOD& lod) const { out << it.key(); MetavoxelStreamBase base = { it.key(), out, lod, lod }; MetavoxelStreamState state = { base, getMinimum(), _size }; + out.setContext(&base); it.key()->writeMetavoxelRoot(*it.value(), state); + out.setContext(NULL); } out << AttributePointer(); } @@ -622,6 +654,7 @@ void MetavoxelData::readDelta(const MetavoxelData& reference, const MetavoxelLOD MetavoxelStreamBase base = { attribute, in, lod, referenceLOD }; MetavoxelStreamState state = { base, minimum, _size }; MetavoxelNode* oldRoot = _roots.value(attribute); + in.setContext(&base); if (oldRoot) { bool changed; in >> changed; @@ -637,6 +670,7 @@ void MetavoxelData::readDelta(const MetavoxelData& reference, const MetavoxelLOD } else { attribute->readMetavoxelRoot(*this, state); } + in.setContext(NULL); } forever { @@ -657,7 +691,9 @@ void MetavoxelData::readDelta(const MetavoxelData& reference, const MetavoxelLOD it != remainingRoots.constEnd(); it++) { MetavoxelStreamBase base = { it.key(), in, lod, referenceLOD }; MetavoxelStreamState state = { base, minimum, _size }; + in.setContext(&base); it.key()->readMetavoxelSubdivision(*this, state); + in.setContext(NULL); } } } @@ -693,6 +729,7 @@ void MetavoxelData::writeDelta(const MetavoxelData& reference, const MetavoxelLO MetavoxelNode* referenceRoot = expandedReference->_roots.value(it.key()); MetavoxelStreamBase base = { it.key(), out, lod, referenceLOD }; MetavoxelStreamState state = { base, minimum, _size }; + out.setContext(&base); if (it.value() != referenceRoot || becameSubdivided) { out << it.key(); if (referenceRoot) { @@ -707,6 +744,7 @@ void MetavoxelData::writeDelta(const MetavoxelData& reference, const MetavoxelLO it.key()->writeMetavoxelRoot(*it.value(), state); } } + out.setContext(NULL); } out << AttributePointer(); diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 7bfd2a7522..56d9dd3a8a 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -53,6 +53,17 @@ public: /// enabled or disabled as compared to the reference. bool becameSubdividedOrCollapsed(const glm::vec3& minimum, float size, const MetavoxelLOD& reference, float multiplier = 1.0f) const; + + /// Checks whether, according to this LOD, we should subdivide the described region. + bool shouldSubdivide(const glm::vec2& minimum, float size, float multiplier = 1.0f) const; + + /// Checks whether the node or any of the nodes underneath it have had subdivision enabled as compared to the reference. + bool becameSubdivided(const glm::vec2& minimum, float size, const MetavoxelLOD& reference, float multiplier = 1.0f) const; + + /// Checks whether the node or any of the nodes underneath it have had subdivision + /// enabled or disabled as compared to the reference. + bool becameSubdividedOrCollapsed(const glm::vec2& minimum, float size, + const MetavoxelLOD& reference, float multiplier = 1.0f) const; }; DECLARE_STREAMABLE_METATYPE(MetavoxelLOD) diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index a4d2569de0..1225752df7 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -123,11 +123,9 @@ RemoveSpannerEdit::RemoveSpannerEdit(const AttributePointer& attribute, int id) void RemoveSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { SharedObject* object = objects.value(id); - if (!object) { - qDebug() << "Missing object to remove" << id; - return; + if (object) { + data.remove(attribute, object); } - data.remove(attribute, object); } ClearSpannersEdit::ClearSpannersEdit(const AttributePointer& attribute) : diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp index bf9b123a36..dcfa9732b3 100644 --- a/libraries/metavoxels/src/SharedObject.cpp +++ b/libraries/metavoxels/src/SharedObject.cpp @@ -131,6 +131,30 @@ void SharedObject::dump(QDebug debug) const { } } +void SharedObject::writeExtra(Bitstream& out) const { + // nothing by default +} + +void SharedObject::readExtra(Bitstream& in) { + // nothing by default +} + +void SharedObject::writeExtraDelta(Bitstream& out, const SharedObject* reference) const { + // nothing by default +} + +void SharedObject::readExtraDelta(Bitstream& in, const SharedObject* reference) { + // nothing by default +} + +void SharedObject::maybeWriteSubdivision(Bitstream& out) { + // nothing by default +} + +SharedObject* SharedObject::readSubdivision(Bitstream& in) { + return this; +} + QAtomicInt SharedObject::_nextID(1); WeakSharedObjectHash SharedObject::_weakHash; QReadWriteLock SharedObject::_weakHashLock; diff --git a/libraries/metavoxels/src/SharedObject.h b/libraries/metavoxels/src/SharedObject.h index 157987ed6f..ebea322bf1 100644 --- a/libraries/metavoxels/src/SharedObject.h +++ b/libraries/metavoxels/src/SharedObject.h @@ -24,6 +24,7 @@ class QComboBox; +class Bitstream; class SharedObject; typedef QHash > WeakSharedObjectHash; @@ -76,9 +77,29 @@ public: /// this is an instance of a superclass of the other object's class) rather than simply returning false. virtual bool equals(const SharedObject* other, bool sharedAncestry = false) const; - // Dumps the contents of this object to the debug output. + /// Dumps the contents of this object to the debug output. virtual void dump(QDebug debug = QDebug(QtDebugMsg)) const; + /// Writes the non-property contents of this object to the specified stream. + virtual void writeExtra(Bitstream& out) const; + + /// Reads the non-property contents of this object from the specified stream. + virtual void readExtra(Bitstream& in); + + /// Writes the delta-encoded non-property contents of this object to the specified stream. + virtual void writeExtraDelta(Bitstream& out, const SharedObject* reference) const; + + /// Reads the delta-encoded non-property contents of this object from the specified stream. + virtual void readExtraDelta(Bitstream& in, const SharedObject* reference); + + /// Writes the subdivision of the contents of this object (preceeded by a + /// reference to the object itself) to the specified stream if necessary. + virtual void maybeWriteSubdivision(Bitstream& out); + + /// Reads the subdivision of this object from the specified stream. + /// \return the modified object, or this if no modification was performed + virtual SharedObject* readSubdivision(Bitstream& in); + private: int _id; diff --git a/libraries/metavoxels/src/Spanner.cpp b/libraries/metavoxels/src/Spanner.cpp index 617d753414..956dc45729 100644 --- a/libraries/metavoxels/src/Spanner.cpp +++ b/libraries/metavoxels/src/Spanner.cpp @@ -24,6 +24,7 @@ #include +#include "MetavoxelData.h" #include "Spanner.h" using namespace std; @@ -60,7 +61,8 @@ Spanner::Spanner() : _renderer(NULL), _placementGranularity(DEFAULT_PLACEMENT_GRANULARITY), _voxelizationGranularity(DEFAULT_VOXELIZATION_GRANULARITY), - _merged(false) { + _merged(false), + _willBeVoxelized(false) { } void Spanner::setBounds(const Box& bounds) { @@ -167,7 +169,7 @@ void SpannerRenderer::simulate(float deltaTime) { // nothing by default } -void SpannerRenderer::render(bool cursor) { +void SpannerRenderer::render(const MetavoxelLOD& lod, bool contained, bool cursor) { // nothing by default } @@ -606,22 +608,27 @@ static int getHeightfieldSize(int size) { void HeightfieldHeightEditor::select() { QSettings settings; QString result = QFileDialog::getOpenFileName(this, "Select Height Image", settings.value("heightDir").toString(), - "Images (*.png *.jpg *.bmp *.raw)"); + "Images (*.png *.jpg *.bmp *.raw *.mdr)"); if (result.isNull()) { return; } settings.setValue("heightDir", QFileInfo(result).path()); const quint16 CONVERSION_OFFSET = 1; - if (result.toLower().endsWith(".raw")) { + QString lowerResult = result.toLower(); + bool isMDR = lowerResult.endsWith(".mdr"); + if (lowerResult.endsWith(".raw") || isMDR) { QFile input(result); input.open(QIODevice::ReadOnly); QDataStream in(&input); in.setByteOrder(QDataStream::LittleEndian); - QVector rawContents; - while (!in.atEnd()) { - quint16 height; - in >> height; - rawContents.append(height); + if (isMDR) { + const int MDR_HEADER_SIZE = 1024; + input.seek(MDR_HEADER_SIZE); + } + int available = input.bytesAvailable() / sizeof(quint16); + QVector rawContents(available); + for (quint16* height = rawContents.data(), *end = height + available; height != end; height++) { + in >> *height; } if (rawContents.isEmpty()) { QMessageBox::warning(this, "Invalid Image", "The selected image could not be read."); @@ -1107,53 +1114,174 @@ template<> void Bitstream::readRawDelta(HeightfieldMaterialPointer& value, const } } -Heightfield::Heightfield() : - _aspectY(1.0f), - _aspectZ(1.0f) { +bool HeightfieldStreamState::shouldSubdivide() const { + return base.lod.shouldSubdivide(minimum, size); +} + +bool HeightfieldStreamState::shouldSubdivideReference() const { + return base.referenceLOD.shouldSubdivide(minimum, size); +} + +bool HeightfieldStreamState::becameSubdivided() const { + return base.lod.becameSubdivided(minimum, size, base.referenceLOD); +} + +bool HeightfieldStreamState::becameSubdividedOrCollapsed() const { + return base.lod.becameSubdividedOrCollapsed(minimum, size, base.referenceLOD); +} + +const int X_MAXIMUM_FLAG = 1; +const int Y_MAXIMUM_FLAG = 2; + +static glm::vec2 getNextMinimum(const glm::vec2& minimum, float nextSize, int index) { + return minimum + glm::vec2( + (index & X_MAXIMUM_FLAG) ? nextSize : 0.0f, + (index & Y_MAXIMUM_FLAG) ? nextSize : 0.0f); +} + +void HeightfieldStreamState::setMinimum(const glm::vec2& lastMinimum, int index) { + minimum = getNextMinimum(lastMinimum, size, index); +} + +HeightfieldNode::HeightfieldNode(const HeightfieldHeightPointer& height, const HeightfieldColorPointer& color, + const HeightfieldMaterialPointer& material) : + _height(height), + _color(color), + _material(material), + _renderer(NULL) { +} + +HeightfieldNode::HeightfieldNode(const HeightfieldNode& other) : + _height(other.getHeight()), + _color(other.getColor()), + _material(other.getMaterial()), + _renderer(NULL) { - connect(this, &Heightfield::translationChanged, this, &Heightfield::updateBounds); - connect(this, &Heightfield::rotationChanged, this, &Heightfield::updateBounds); - connect(this, &Heightfield::scaleChanged, this, &Heightfield::updateBounds); - connect(this, &Heightfield::aspectYChanged, this, &Heightfield::updateBounds); - connect(this, &Heightfield::aspectZChanged, this, &Heightfield::updateBounds); - updateBounds(); -} - -void Heightfield::setAspectY(float aspectY) { - if (_aspectY != aspectY) { - emit aspectYChanged(_aspectY = aspectY); + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i] = other.getChild(i); } } -void Heightfield::setAspectZ(float aspectZ) { - if (_aspectZ != aspectZ) { - emit aspectZChanged(_aspectZ = aspectZ); - } +HeightfieldNode::~HeightfieldNode() { + delete _renderer; } -void Heightfield::setHeight(const HeightfieldHeightPointer& height) { - if (_height != height) { - emit heightChanged(_height = height); +const int HEIGHT_LEAF_SIZE = 256 + HeightfieldHeight::HEIGHT_EXTENSION; + +void HeightfieldNode::setContents(const HeightfieldHeightPointer& height, const HeightfieldColorPointer& color, + const HeightfieldMaterialPointer& material) { + clearChildren(); + + int heightWidth = height->getWidth(); + if (heightWidth <= HEIGHT_LEAF_SIZE) { + _height = height; + _color = color; + _material = material; + return; } + int heightHeight = height->getContents().size() / heightWidth; + int innerChildHeightWidth = (heightWidth - HeightfieldHeight::HEIGHT_EXTENSION) / 2; + int innerChildHeightHeight = (heightHeight - HeightfieldHeight::HEIGHT_EXTENSION) / 2; + int childHeightWidth = innerChildHeightWidth + HeightfieldHeight::HEIGHT_EXTENSION; + int childHeightHeight = innerChildHeightHeight + HeightfieldHeight::HEIGHT_EXTENSION; + + for (int i = 0; i < CHILD_COUNT; i++) { + QVector childHeightContents(childHeightWidth * childHeightHeight); + quint16* heightDest = childHeightContents.data(); + bool maximumX = (i & X_MAXIMUM_FLAG), maximumY = (i & Y_MAXIMUM_FLAG); + const quint16* heightSrc = height->getContents().constData() + (maximumY ? innerChildHeightHeight * heightWidth : 0) + + (maximumX ? innerChildHeightWidth : 0); + for (int z = 0; z < childHeightHeight; z++, heightDest += childHeightWidth, heightSrc += heightWidth) { + memcpy(heightDest, heightSrc, childHeightWidth * sizeof(quint16)); + } + + HeightfieldColorPointer childColor; + if (color) { + int colorWidth = color->getWidth(); + int colorHeight = color->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES); + int innerChildColorWidth = (colorWidth - HeightfieldData::SHARED_EDGE) / 2; + int innerChildColorHeight = (colorHeight - HeightfieldData::SHARED_EDGE) / 2; + int childColorWidth = innerChildColorWidth + HeightfieldData::SHARED_EDGE; + int childColorHeight = innerChildColorHeight + HeightfieldData::SHARED_EDGE; + QByteArray childColorContents(childColorWidth * childColorHeight * DataBlock::COLOR_BYTES, 0); + char* dest = childColorContents.data(); + const char* src = color->getContents().constData() + ((maximumY ? innerChildColorHeight * colorWidth : 0) + + (maximumX ? innerChildColorWidth : 0)) * DataBlock::COLOR_BYTES; + for (int z = 0; z < childColorHeight; z++, dest += childColorWidth * DataBlock::COLOR_BYTES, + src += colorWidth * DataBlock::COLOR_BYTES) { + memcpy(dest, src, childColorWidth * DataBlock::COLOR_BYTES); + } + childColor = new HeightfieldColor(childColorWidth, childColorContents); + } + + HeightfieldMaterialPointer childMaterial; + if (material) { + int materialWidth = material->getWidth(); + int materialHeight = material->getContents().size() / materialWidth; + int innerChildMaterialWidth = (materialWidth - HeightfieldData::SHARED_EDGE) / 2; + int innerChildMaterialHeight = (materialHeight - HeightfieldData::SHARED_EDGE) / 2; + int childMaterialWidth = innerChildMaterialWidth + HeightfieldData::SHARED_EDGE; + int childMaterialHeight = innerChildMaterialHeight + HeightfieldData::SHARED_EDGE; + QByteArray childMaterialContents(childMaterialWidth * childMaterialHeight, 0); + QVector childMaterials; + uchar* dest = (uchar*)childMaterialContents.data(); + const uchar* src = (const uchar*)material->getContents().data() + + (maximumY ? innerChildMaterialHeight * materialWidth : 0) + (maximumX ? innerChildMaterialWidth : 0); + QHash materialMap; + for (int z = 0; z < childMaterialHeight; z++, dest += childMaterialWidth, src += materialWidth) { + const uchar* lineSrc = src; + for (uchar* lineDest = dest, *end = dest + childMaterialWidth; lineDest != end; lineDest++, lineSrc++) { + int value = *lineSrc; + if (value != 0) { + int& mapping = materialMap[value]; + if (mapping == 0) { + childMaterials.append(material->getMaterials().at(value - 1)); + mapping = childMaterials.size(); + } + value = mapping; + } + *lineDest = value; + } + } + childMaterial = new HeightfieldMaterial(childMaterialWidth, childMaterialContents, childMaterials); + } + + _children[i] = new HeightfieldNode(); + _children[i]->setContents(HeightfieldHeightPointer(new HeightfieldHeight(childHeightWidth, childHeightContents)), + childColor, childMaterial); + } + + mergeChildren(); } -void Heightfield::setColor(const HeightfieldColorPointer& color) { - if (_color != color) { - emit colorChanged(_color = color); +bool HeightfieldNode::isLeaf() const { + for (int i = 0; i < CHILD_COUNT; i++) { + if (_children[i]) { + return false; + } } -} - -void Heightfield::setMaterial(const HeightfieldMaterialPointer& material) { - if (_material != material) { - emit materialChanged(_material = material); - } -} - -bool Heightfield::isHeightfield() const { return true; } -float Heightfield::getHeight(const glm::vec3& location) const { +float HeightfieldNode::getHeight(const glm::vec3& location) const { + if (location.x < 0.0f || location.z < 0.0f || location.x > 1.0f || location.z > 1.0f) { + return -FLT_MAX; + } + if (!isLeaf()) { + if (location.x < 0.5f) { + if (location.z < 0.5f) { + return _children[0]->getHeight(location * 2.0f); + } else { + return _children[Y_MAXIMUM_FLAG]->getHeight(location * 2.0f - glm::vec3(0.0f, 0.0f, 1.0f)); + } + } else { + if (location.z < 0.5f) { + return _children[X_MAXIMUM_FLAG]->getHeight(location * 2.0f - glm::vec3(1.0f, 0.0f, 0.0f)); + } else { + return _children[X_MAXIMUM_FLAG | Y_MAXIMUM_FLAG]->getHeight(location * 2.0f - glm::vec3(1.0f, 0.0f, 1.0f)); + } + } + } if (!_height) { return -FLT_MAX; } @@ -1164,13 +1292,9 @@ float Heightfield::getHeight(const glm::vec3& location) const { int innerWidth = width - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeight = height - HeightfieldHeight::HEIGHT_EXTENSION; - glm::vec3 relative = glm::inverse(getRotation()) * (location - getTranslation()) * glm::vec3(1.0f / getScale(), - 1.0f, 1.0f / (getScale() * _aspectZ)); + glm::vec3 relative = location; relative.x = relative.x * innerWidth + HeightfieldHeight::HEIGHT_BORDER; relative.z = relative.z * innerHeight + HeightfieldHeight::HEIGHT_BORDER; - if (relative.x < 0.0f || relative.z < 0.0f || relative.x > width - 1 || relative.z > height - 1) { - return -FLT_MAX; - } // find the bounds of the cell containing the point and the shared vertex heights glm::vec3 floors = glm::floor(relative); @@ -1197,12 +1321,30 @@ float Heightfield::getHeight(const glm::vec3& location) const { if (interpolatedHeight == 0.0f) { return -FLT_MAX; // ignore zero values } - - // convert the interpolated height into world space - return getTranslation().y + interpolatedHeight * getScale() * _aspectY / numeric_limits::max(); + return interpolatedHeight / numeric_limits::max(); } -bool Heightfield::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { +bool HeightfieldNode::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { + float boundsDistance; + if (!Box(glm::vec3(), glm::vec3(1.0f, 1.0f, 1.0f)).findRayIntersection(origin, direction, boundsDistance)) { + return false; + } + if (!isLeaf()) { + float closestDistance = FLT_MAX; + for (int i = 0; i < CHILD_COUNT; i++) { + float childDistance; + if (_children[i]->findRayIntersection(origin * glm::vec3(2.0f, 1.0f, 2.0f) - + glm::vec3(i & X_MAXIMUM_FLAG ? 1.0f : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? 1.0f : 0.0f), + direction * glm::vec3(2.0f, 1.0f, 2.0f), childDistance)) { + closestDistance = qMin(closestDistance, childDistance); + } + } + if (closestDistance == FLT_MAX) { + return false; + } + distance = closestDistance; + return true; + } if (!_height) { return false; } @@ -1215,18 +1357,9 @@ bool Heightfield::findRayIntersection(const glm::vec3& origin, const glm::vec3& int highestX = innerWidth + HeightfieldHeight::HEIGHT_BORDER; int highestZ = innerHeight + HeightfieldHeight::HEIGHT_BORDER; - glm::quat inverseRotation = glm::inverse(getRotation()); - glm::vec3 inverseScale(innerWidth / getScale(), numeric_limits::max() / (getScale() * _aspectY), - innerHeight / (getScale() * _aspectZ)); - glm::vec3 dir = inverseRotation * direction * inverseScale; - glm::vec3 entry = inverseRotation * (origin - getTranslation()) * inverseScale; - - float boundsDistance; - if (!Box(glm::vec3(), glm::vec3((float)innerWidth, (float)numeric_limits::max(), - (float)innerHeight)).findRayIntersection(entry, dir, boundsDistance)) { - return false; - } - entry += dir * boundsDistance; + glm::vec3 scale((float)innerWidth, (float)numeric_limits::max(), (float)innerHeight); + glm::vec3 dir = direction * scale; + glm::vec3 entry = origin * scale + dir * boundsDistance; entry.x += HeightfieldHeight::HEIGHT_BORDER; entry.z += HeightfieldHeight::HEIGHT_BORDER; @@ -1356,8 +1489,30 @@ bool Heightfield::findRayIntersection(const glm::vec3& origin, const glm::vec3& return false; } -Spanner* Heightfield::paintMaterial(const glm::vec3& position, float radius, +HeightfieldNode* HeightfieldNode::paintMaterial(const glm::vec3& position, const glm::vec3& radius, const SharedObjectPointer& material, const QColor& color) { + if (position.x + radius.x < 0.0f || position.z + radius.z < 0.0f || + position.x - radius.x > 1.0f || position.z - radius.z > 1.0f) { + return this; + } + if (!isLeaf()) { + HeightfieldNode* newNode = this; + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldNode* newChild = _children[i]->paintMaterial(position * glm::vec3(2.0f, 1.0f, 2.0f) - + glm::vec3(i & X_MAXIMUM_FLAG ? 1.0f : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? 1.0f : 0.0f), + radius * glm::vec3(2.0f, 1.0f, 2.0f), material, color); + if (_children[i] != newChild) { + if (newNode == this) { + newNode = new HeightfieldNode(*this); + } + newNode->setChild(i, HeightfieldNodePointer(newChild)); + } + } + if (newNode != this) { + newNode->mergeChildren(false, true); + } + return newNode; + } if (!_height) { return this; } @@ -1365,7 +1520,6 @@ Spanner* Heightfield::paintMaterial(const glm::vec3& position, float radius, int heightHeight = _height->getContents().size() / heightWidth; int baseWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION + HeightfieldData::SHARED_EDGE; int baseHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION + HeightfieldData::SHARED_EDGE; - Heightfield* newHeightfield = static_cast(clone(true)); int colorWidth = baseWidth, colorHeight = baseHeight; QByteArray colorContents; @@ -1393,10 +1547,10 @@ Spanner* Heightfield::paintMaterial(const glm::vec3& position, float radius, int highestX = colorWidth - 1; int highestZ = colorHeight - 1; - glm::vec3 inverseScale(highestX / getScale(), 1.0f, highestZ / (getScale() * _aspectZ)); - glm::vec3 center = glm::inverse(getRotation()) * (position - getTranslation()) * inverseScale; + glm::vec3 scale((float)highestX, 1.0f, (float)highestZ); + glm::vec3 center = position * scale; - glm::vec3 extents = glm::vec3(radius, radius, radius) * inverseScale; + glm::vec3 extents = radius * scale; glm::vec3 start = glm::floor(center - extents); glm::vec3 end = glm::ceil(center + extents); @@ -1406,7 +1560,7 @@ Spanner* Heightfield::paintMaterial(const glm::vec3& position, float radius, int stride = colorWidth * DataBlock::COLOR_BYTES; uchar* lineDest = (uchar*)colorContents.data() + (int)z * stride + (int)startX * DataBlock::COLOR_BYTES; float squaredRadius = extents.x * extents.x; - float multiplierZ = inverseScale.x / inverseScale.z; + float multiplierZ = extents.x / extents.z; char red = color.red(), green = color.green(), blue = color.blue(); bool changed = false; for (float endZ = qMin(end.z, (float)highestZ); z <= endZ; z += 1.0f) { @@ -1422,16 +1576,18 @@ Spanner* Heightfield::paintMaterial(const glm::vec3& position, float radius, } lineDest += stride; } + HeightfieldNode* newNode = this; if (changed) { - newHeightfield->setColor(HeightfieldColorPointer(new HeightfieldColor(colorWidth, colorContents))); + newNode = new HeightfieldNode(*this); + newNode->setColor(HeightfieldColorPointer(new HeightfieldColor(colorWidth, colorContents))); } highestX = materialWidth - 1; highestZ = materialHeight - 1; - inverseScale = glm::vec3(highestX / getScale(), 1.0f, highestZ / (getScale() * _aspectZ)); - center = glm::inverse(getRotation()) * (position - getTranslation()) * inverseScale; + scale = glm::vec3((float)highestX, 1.0f, (float)highestZ); + center = position * scale; - extents = glm::vec3(radius, radius, radius) * inverseScale; + extents = radius * scale; start = glm::floor(center - extents); end = glm::ceil(center + extents); @@ -1439,7 +1595,8 @@ Spanner* Heightfield::paintMaterial(const glm::vec3& position, float radius, z = qMax(start.z, 0.0f); startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highestX); lineDest = (uchar*)materialContents.data() + (int)z * materialWidth + (int)startX; - squaredRadius = extents.x * extents.x; + squaredRadius = extents.x * extents.x; + multiplierZ = extents.x / extents.z; uchar materialIndex = getMaterialIndex(material, materials, materialContents); changed = false; for (float endZ = qMin(end.z, (float)highestZ); z <= endZ; z += 1.0f) { @@ -1454,15 +1611,101 @@ Spanner* Heightfield::paintMaterial(const glm::vec3& position, float radius, lineDest += materialWidth; } if (changed) { + if (newNode == this) { + newNode = new HeightfieldNode(*this); + } clearUnusedMaterials(materials, materialContents); - newHeightfield->setMaterial(HeightfieldMaterialPointer(new HeightfieldMaterial(materialWidth, + newNode->setMaterial(HeightfieldMaterialPointer(new HeightfieldMaterial(materialWidth, materialContents, materials))); } - return newHeightfield; + return newNode; } -Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float height) { +void HeightfieldNode::getRangeAfterHeightPaint(const glm::vec3& position, const glm::vec3& radius, + float height, int& minimum, int& maximum) const { + if (position.x + radius.x < 0.0f || position.z + radius.z < 0.0f || + position.x - radius.x > 1.0f || position.z - radius.z > 1.0f) { + return; + } + if (!isLeaf()) { + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i]->getRangeAfterHeightPaint(position * glm::vec3(2.0f, 1.0f, 2.0f) - + glm::vec3(i & X_MAXIMUM_FLAG ? 1.0f : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? 1.0f : 0.0f), + radius * glm::vec3(2.0f, 1.0f, 2.0f), height, minimum, maximum); + } + return; + } + if (!_height) { + return; + } + int heightWidth = _height->getWidth(); + int heightHeight = _height->getContents().size() / heightWidth; + QVector contents = _height->getContents(); + int innerWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; + int innerHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; + int highestX = heightWidth - 1; + int highestZ = heightHeight - 1; + + glm::vec3 scale((float)innerWidth, 1.0f, (float)innerHeight); + glm::vec3 center = position * scale; + center.x += 1.0f; + center.z += 1.0f; + + glm::vec3 extents = radius * scale; + glm::vec3 start = glm::floor(center - extents); + glm::vec3 end = glm::ceil(center + extents); + + // first see if we're going to exceed the range limits + float z = qMax(start.z, 0.0f); + float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highestX); + quint16* lineDest = contents.data() + (int)z * heightWidth + (int)startX; + float squaredRadius = extents.x * extents.x; + float squaredRadiusReciprocal = 1.0f / squaredRadius; + float multiplierZ = extents.x / extents.z; + for (float endZ = qMin(end.z, (float)highestZ); z <= endZ; z += 1.0f) { + quint16* dest = lineDest; + for (float x = startX; x <= endX; x += 1.0f, dest++) { + float dx = x - center.x, dz = (z - center.z) * multiplierZ; + float distanceSquared = dx * dx + dz * dz; + if (distanceSquared <= squaredRadius) { + // height falls off towards edges + int value = *dest; + if (value != 0) { + value += height * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; + minimum = qMin(minimum, value); + maximum = qMax(maximum, value); + } + } + } + lineDest += heightWidth; + } +} + +HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& position, const glm::vec3& radius, + float height, float normalizeScale, float normalizeOffset) { + if ((position.x + radius.x < 0.0f || position.z + radius.z < 0.0f || position.x - radius.x > 1.0f || + position.z - radius.z > 1.0f) && normalizeScale == 1.0f && normalizeOffset == 0.0f) { + return this; + } + if (!isLeaf()) { + HeightfieldNode* newNode = this; + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldNode* newChild = _children[i]->paintHeight(position * glm::vec3(2.0f, 1.0f, 2.0f) - + glm::vec3(i & X_MAXIMUM_FLAG ? 1.0f : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? 1.0f : 0.0f), + radius * glm::vec3(2.0f, 1.0f, 2.0f), height, normalizeScale, normalizeOffset); + if (_children[i] != newChild) { + if (newNode == this) { + newNode = new HeightfieldNode(*this); + } + newNode->setChild(i, HeightfieldNodePointer(newChild)); + } + } + if (newNode != this) { + newNode->mergeChildren(true, false); + } + return newNode; + } if (!_height) { return this; } @@ -1473,64 +1716,35 @@ Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float int innerHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; int highestX = heightWidth - 1; int highestZ = heightHeight - 1; - Heightfield* newHeightfield = static_cast(clone(true)); - glm::vec3 inverseScale(innerWidth / getScale(), 1.0f, innerHeight / (getScale() * _aspectZ)); - glm::vec3 center = glm::inverse(getRotation()) * (position - getTranslation()) * inverseScale; + glm::vec3 scale((float)innerWidth, 1.0f, (float)innerHeight); + glm::vec3 center = position * scale; center.x += 1.0f; center.z += 1.0f; - glm::vec3 extents = glm::vec3(radius, radius, radius) * inverseScale; + glm::vec3 extents = radius * scale; glm::vec3 start = glm::floor(center - extents); glm::vec3 end = glm::ceil(center + extents); - // first see if we're going to exceed the range limits - float z = qMax(start.z, 0.0f); - float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highestX); - quint16* lineDest = contents.data() + (int)z * heightWidth + (int)startX; - float squaredRadius = extents.x * extents.x; - float squaredRadiusReciprocal = 1.0f / squaredRadius; - float scaledHeight = height * numeric_limits::max() / (getScale() * _aspectY); - float multiplierZ = inverseScale.x / inverseScale.z; - int minimumValue = 1, maximumValue = numeric_limits::max(); - for (float endZ = qMin(end.z, (float)highestZ); z <= endZ; z += 1.0f) { - quint16* dest = lineDest; - for (float x = startX; x <= endX; x += 1.0f, dest++) { - float dx = x - center.x, dz = (z - center.z) * multiplierZ; - float distanceSquared = dx * dx + dz * dz; - if (distanceSquared <= squaredRadius) { - // height falls off towards edges - int value = *dest; - if (value != 0) { - value += scaledHeight * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; - minimumValue = qMin(minimumValue, value); - maximumValue = qMax(maximumValue, value); - } - } - } - lineDest += heightWidth; - } - // renormalize if necessary - if (minimumValue < 1 || maximumValue > numeric_limits::max()) { - float scale = (numeric_limits::max() - 1.0f) / (maximumValue - minimumValue); - float offset = 1.0f - minimumValue; - newHeightfield->setAspectY(_aspectY / scale); - newHeightfield->setTranslation(getTranslation() - getRotation() * - glm::vec3(0.0f, offset * _aspectY * getScale() / (numeric_limits::max() - 1), 0.0f)); + bool changed = false; + if (normalizeScale != 1.0f || normalizeOffset != 0.0f) { + changed = true; for (quint16* dest = contents.data(), *end = contents.data() + contents.size(); dest != end; dest++) { int value = *dest; if (value != 0) { - *dest = (value + offset) * scale; + *dest = (value + normalizeOffset) * normalizeScale; } } } // now apply the actual change - z = qMax(start.z, 0.0f); - lineDest = contents.data() + (int)z * heightWidth + (int)startX; - scaledHeight = height * numeric_limits::max() / (getScale() * newHeightfield->getAspectY()); - bool changed = false; + float z = qMax(start.z, 0.0f); + float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highestX); + quint16* lineDest = contents.data() + (int)z * heightWidth + (int)startX; + float squaredRadius = extents.x * extents.x; + float squaredRadiusReciprocal = 1.0f / squaredRadius; + float multiplierZ = extents.x / extents.z; for (float endZ = qMin(end.z, (float)highestZ); z <= endZ; z += 1.0f) { quint16* dest = lineDest; for (float x = startX; x <= endX; x += 1.0f, dest++) { @@ -1540,21 +1754,47 @@ Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float // height falls off towards edges int value = *dest; if (value != 0) { - *dest = value + scaledHeight * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; + *dest = value + height * (squaredRadius - distanceSquared) * squaredRadiusReciprocal; changed = true; } } } lineDest += heightWidth; } - if (changed) { - newHeightfield->setHeight(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, contents))); + if (!changed) { + return this; } - - return newHeightfield; + HeightfieldNode* newNode = new HeightfieldNode(*this); + newNode->setHeight(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, contents))); + return newNode; } -Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer& heightfield) { +HeightfieldNode* HeightfieldNode::clearAndFetchHeight(const glm::vec3& translation, const glm::quat& rotation, + const glm::vec3& scale, const Box& bounds, SharedObjectPointer& heightfield) { + Box nodeBounds = glm::translate(translation) * glm::mat4_cast(rotation) * Box(glm::vec3(), scale); + if (!nodeBounds.intersects(bounds)) { + return this; + } + if (!isLeaf()) { + HeightfieldNode* newNode = this; + for (int i = 0; i < CHILD_COUNT; i++) { + glm::vec3 nextScale = scale * glm::vec3(0.5f, 1.0f, 0.5f); + HeightfieldNode* newChild = _children[i]->clearAndFetchHeight(translation + + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, + i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, + nextScale, bounds, heightfield); + if (_children[i] != newChild) { + if (newNode == this) { + newNode = new HeightfieldNode(*this); + } + newNode->setChild(i, HeightfieldNodePointer(newChild)); + } + } + if (newNode != this) { + newNode->mergeChildren(); + } + return newNode; + } if (!_height) { return this; } @@ -1562,8 +1802,8 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer int heightHeight = _height->getContents().size() / heightWidth; int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; - float heightIncrementX = getScale() / innerHeightWidth; - float heightIncrementZ = (getScale() * _aspectZ) / innerHeightHeight; + float heightIncrementX = scale.x / innerHeightWidth; + float heightIncrementZ = scale.z / innerHeightHeight; int colorWidth = heightWidth; int colorHeight = heightHeight; @@ -1573,8 +1813,8 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer } int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; - float colorIncrementX = getScale() / innerColorWidth; - float colorIncrementZ = (getScale() * _aspectZ) / innerColorHeight; + float colorIncrementX = scale.x / innerColorWidth; + float colorIncrementZ = scale.z / innerColorHeight; int materialWidth = colorWidth; int materialHeight = colorHeight; @@ -1584,15 +1824,15 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer } int innerMaterialWidth = materialWidth - HeightfieldData::SHARED_EDGE; int innerMaterialHeight = materialHeight - HeightfieldData::SHARED_EDGE; - float materialIncrementX = getScale() / innerMaterialWidth; - float materialIncrementZ = (getScale() * _aspectZ) / innerMaterialHeight; + float materialIncrementX = scale.x / innerMaterialWidth; + float materialIncrementZ = scale.z / innerMaterialHeight; float largestIncrementX = qMax(heightIncrementX, qMax(colorIncrementX, materialIncrementX)); float largestIncrementZ = qMax(heightIncrementZ, qMax(colorIncrementZ, materialIncrementZ)); - glm::vec3 minimum(glm::floor(bounds.minimum.x / largestIncrementX) * largestIncrementX, getBounds().minimum.y, + glm::vec3 minimum(glm::floor(bounds.minimum.x / largestIncrementX) * largestIncrementX, nodeBounds.minimum.y, glm::floor(bounds.minimum.z / largestIncrementZ) * largestIncrementZ); - glm::vec3 maximum(glm::ceil(bounds.maximum.x / largestIncrementX) * largestIncrementX, getBounds().maximum.y, + glm::vec3 maximum(glm::ceil(bounds.maximum.x / largestIncrementX) * largestIncrementX, nodeBounds.maximum.y, glm::ceil(bounds.maximum.z / largestIncrementZ) * largestIncrementZ); Box largestBounds(minimum, maximum); @@ -1602,8 +1842,8 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer minimum.z -= largestIncrementZ; maximum.z += largestIncrementX; - glm::mat4 baseTransform = glm::mat4_cast(glm::inverse(getRotation())) * glm::translate(-getTranslation()); - glm::vec3 inverseScale(innerHeightWidth / getScale(), 1.0f, innerHeightHeight / (getScale() * _aspectZ)); + glm::mat4 baseTransform = glm::mat4_cast(glm::inverse(rotation)) * glm::translate(-translation); + glm::vec3 inverseScale(innerHeightWidth / scale.x, 1.0f, innerHeightHeight / scale.z); glm::mat4 transform = glm::scale(inverseScale) * baseTransform; Box transformedBounds = transform * largestBounds; @@ -1655,7 +1895,7 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer (spannerHeightHeight - HeightfieldHeight::HEIGHT_EXTENSION) / (spanner->getScale() * spanner->getAspectZ())); glm::mat4 spannerBaseTransform = glm::translate(-spanner->getTranslation()); glm::mat4 spannerTransform = glm::scale(spannerInverseScale) * spannerBaseTransform; - Box spannerTransformedBounds = spannerTransform * getBounds(); + Box spannerTransformedBounds = spannerTransform * nodeBounds; int spannerStartX = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.x) + HeightfieldHeight::HEIGHT_BORDER, 0, spannerHeightWidth - 1); int spannerStartZ = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.z) + HeightfieldHeight::HEIGHT_BORDER, @@ -1666,15 +1906,16 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer 0, spannerHeightHeight - 1); quint16* dest = spanner->getHeight()->getContents().data() + spannerStartZ * spannerHeightWidth + spannerStartX; glm::vec3 step = 1.0f / spannerInverseScale; - glm::vec3 initialPosition = glm::vec3(spannerStartX - HeightfieldHeight::HEIGHT_BORDER, 0, - spannerStartZ - HeightfieldHeight::HEIGHT_BORDER) * step + spanner->getTranslation(); + glm::vec3 initialPosition = glm::inverse(rotation) * (glm::vec3(spannerStartX - HeightfieldHeight::HEIGHT_BORDER, 0, + spannerStartZ - HeightfieldHeight::HEIGHT_BORDER) * step + spanner->getTranslation() - translation) / scale; glm::vec3 position = initialPosition; - float heightScale = numeric_limits::max() / (getScale() * _aspectY); + step = glm::inverse(rotation) * step / scale; + float heightScale = numeric_limits::max(); for (int z = spannerStartZ; z <= spannerEndZ; z++, dest += spannerHeightWidth, position.z += step.z) { quint16* lineDest = dest; position.x = initialPosition.x; for (int x = spannerStartX; x <= spannerEndX; x++, lineDest++, position.x += step.x) { - float height = (getHeight(position) - getTranslation().y) * heightScale; + float height = getHeight(position) * heightScale; if (height > *lineDest) { *lineDest = height; } @@ -1686,7 +1927,7 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer spannerInverseScale = glm::vec3((spannerColorWidth - HeightfieldData::SHARED_EDGE) / spanner->getScale(), 1.0f, (spannerColorHeight - HeightfieldData::SHARED_EDGE) / (spanner->getScale() * spanner->getAspectZ())); spannerTransform = glm::scale(spannerInverseScale) * spannerBaseTransform; - spannerTransformedBounds = spannerTransform * getBounds(); + spannerTransformedBounds = spannerTransform * nodeBounds; spannerStartX = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.x), 0, spannerColorWidth - 1); spannerStartZ = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.z), 0, spannerColorHeight - 1); spannerEndX = glm::clamp((int)glm::ceil(spannerTransformedBounds.maximum.x), 0, spannerColorWidth - 1); @@ -1695,8 +1936,10 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer char* dest = spanner->getColor()->getContents().data() + (spannerStartZ * spannerColorWidth + spannerStartX) * DataBlock::COLOR_BYTES; step = 1.0f / spannerInverseScale; - initialPosition = glm::vec3(spannerStartX, 0, spannerStartZ) * step + spanner->getTranslation(); + initialPosition = glm::inverse(rotation) * (glm::vec3(spannerStartX, 0, spannerStartZ) * step + + spanner->getTranslation() - translation) / scale; position = initialPosition; + step = glm::inverse(rotation) * step / scale; for (int z = spannerStartZ; z <= spannerEndZ; z++, dest += spannerColorWidth * DataBlock::COLOR_BYTES, position.z += step.z) { char* lineDest = dest; @@ -1717,7 +1960,7 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer spannerInverseScale = glm::vec3((spannerMaterialWidth - HeightfieldData::SHARED_EDGE) / spanner->getScale(), 1.0f, (spannerMaterialHeight - HeightfieldData::SHARED_EDGE) / (spanner->getScale() * spanner->getAspectZ())); spannerTransform = glm::scale(spannerInverseScale) * spannerBaseTransform; - spannerTransformedBounds = spannerTransform * getBounds(); + spannerTransformedBounds = spannerTransform * nodeBounds; spannerStartX = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.x), 0, spannerMaterialWidth - 1); spannerStartZ = glm::clamp((int)glm::floor(spannerTransformedBounds.minimum.z), 0, spannerMaterialHeight - 1); spannerEndX = glm::clamp((int)glm::ceil(spannerTransformedBounds.maximum.x), 0, spannerMaterialWidth - 1); @@ -1725,8 +1968,10 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer char* dest = spanner->getMaterial()->getContents().data() + spannerStartZ * spannerMaterialWidth + spannerStartX; step = 1.0f / spannerInverseScale; - initialPosition = glm::vec3(spannerStartX, 0, spannerStartZ) * step + spanner->getTranslation(); + initialPosition = glm::inverse(rotation) * (glm::vec3(spannerStartX, 0, spannerStartZ) * step + + spanner->getTranslation() - translation) / scale; position = initialPosition; + step = glm::inverse(rotation) * step / scale; QHash materialMap; for (int z = spannerStartZ; z <= spannerEndZ; z++, dest += spannerMaterialWidth, position.z += step.z) { char* lineDest = dest; @@ -1754,25 +1999,12 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer memset(dest, 0, (endX - startX + 1) * sizeof(quint16)); } - // if we've cleared all the inner height, we can remove the spanner entirely - src = newHeightContents.constData() + heightWidth + HeightfieldHeight::HEIGHT_BORDER; - for (int z = 0; z < innerHeightHeight; z++, src += heightWidth) { - const quint16* lineSrc = src; - for (int x = 0; x < innerHeightWidth; x++) { - if (*lineSrc++ != 0) { - goto nonEmptyBreak; - } - } - } - return NULL; - nonEmptyBreak: - - Heightfield* newHeightfield = static_cast(clone(true)); - newHeightfield->setHeight(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents))); + HeightfieldNode* newNode = new HeightfieldNode(); + newNode->setHeight(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents))); // and the color if (_color) { - inverseScale = glm::vec3(innerColorWidth / getScale(), 1.0f, innerColorHeight / (getScale() * _aspectZ)); + inverseScale = glm::vec3(innerColorWidth / scale.x, 1.0f, innerColorHeight / scale.z); transform = glm::scale(inverseScale) * baseTransform; transformedBounds = transform * largestBounds; startX = glm::clamp((int)glm::ceil(transformedBounds.minimum.x), 0, colorWidth - 1); @@ -1784,12 +2016,12 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer for (int z = startZ; z <= endZ; z++, dest += colorWidth * DataBlock::COLOR_BYTES) { memset(dest, 0, (endX - startX + 1) * DataBlock::COLOR_BYTES); } - newHeightfield->setColor(HeightfieldColorPointer(new HeightfieldColor(colorWidth, newColorContents))); + newNode->setColor(HeightfieldColorPointer(new HeightfieldColor(colorWidth, newColorContents))); } // and the material if (_material) { - inverseScale = glm::vec3(innerMaterialWidth / getScale(), 1.0f, innerMaterialHeight / (getScale() * _aspectZ)); + inverseScale = glm::vec3(innerMaterialWidth / scale.x, 1.0f, innerMaterialHeight / scale.z); transform = glm::scale(inverseScale) * baseTransform; transformedBounds = transform * largestBounds; startX = glm::clamp((int)glm::ceil(transformedBounds.minimum.x), 0, materialWidth - 1); @@ -1803,10 +2035,609 @@ Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer memset(dest, 0, endX - startX + 1); } clearUnusedMaterials(newMaterials, newMaterialContents); - newHeightfield->setMaterial(HeightfieldMaterialPointer(new HeightfieldMaterial( + newNode->setMaterial(HeightfieldMaterialPointer(new HeightfieldMaterial( materialWidth, newMaterialContents, newMaterials))); } + return newNode; +} + +void HeightfieldNode::read(HeightfieldStreamState& state) { + clearChildren(); + + if (!state.shouldSubdivide()) { + state.base.stream >> _height >> _color >> _material; + return; + } + bool leaf; + state.base.stream >> leaf; + if (leaf) { + state.base.stream >> _height >> _color >> _material; + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i] = new HeightfieldNode(); + _children[i]->read(nextState); + } + mergeChildren(); + } +} + +void HeightfieldNode::write(HeightfieldStreamState& state) const { + if (!state.shouldSubdivide()) { + state.base.stream << _height << _color << _material; + return; + } + bool leaf = isLeaf(); + state.base.stream << leaf; + if (leaf) { + state.base.stream << _height << _color << _material; + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i]->write(nextState); + } + } +} + +void HeightfieldNode::readDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) { + clearChildren(); + + if (!state.shouldSubdivide()) { + state.base.stream.readDelta(_height, reference->getHeight()); + state.base.stream.readDelta(_color, reference->getColor()); + state.base.stream.readDelta(_material, reference->getMaterial()); + return; + } + bool leaf; + state.base.stream >> leaf; + if (leaf) { + state.base.stream.readDelta(_height, reference->getHeight()); + state.base.stream.readDelta(_color, reference->getColor()); + state.base.stream.readDelta(_material, reference->getMaterial()); + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + if (reference->isLeaf() || !state.shouldSubdivideReference()) { + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i] = new HeightfieldNode(); + _children[i]->read(nextState); + } + } else { + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + bool changed; + state.base.stream >> changed; + if (changed) { + _children[i] = new HeightfieldNode(); + _children[i]->readDelta(reference->getChild(i), nextState); + } else { + if (nextState.becameSubdividedOrCollapsed()) { + _children[i] = reference->getChild(i)->readSubdivision(nextState); + + } else { + _children[i] = reference->getChild(i); + } + } + } + } + mergeChildren(); + } +} + +void HeightfieldNode::writeDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) const { + if (!state.shouldSubdivide()) { + state.base.stream.writeDelta(_height, reference->getHeight()); + state.base.stream.writeDelta(_color, reference->getColor()); + state.base.stream.writeDelta(_material, reference->getMaterial()); + return; + } + bool leaf = isLeaf(); + state.base.stream << leaf; + if (leaf) { + state.base.stream.writeDelta(_height, reference->getHeight()); + state.base.stream.writeDelta(_color, reference->getColor()); + state.base.stream.writeDelta(_material, reference->getMaterial()); + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + if (reference->isLeaf() || !state.shouldSubdivideReference()) { + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i]->write(nextState); + } + } else { + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + if (_children[i] == reference->getChild(i)) { + state.base.stream << false; + if (nextState.becameSubdivided()) { + _children[i]->writeSubdivision(nextState); + } + } else { + state.base.stream << true; + _children[i]->writeDelta(reference->getChild(i), nextState); + } + } + } + } +} + +HeightfieldNode* HeightfieldNode::readSubdivision(HeightfieldStreamState& state) { + if (state.shouldSubdivide()) { + if (!state.shouldSubdivideReference()) { + bool leaf; + state.base.stream >> leaf; + if (leaf) { + return isLeaf() ? this : new HeightfieldNode(_height, _color, _material); + + } else { + HeightfieldNode* newNode = new HeightfieldNode(_height, _color, _material); + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + newNode->_children[i] = new HeightfieldNode(); + newNode->_children[i]->readSubdivided(nextState, state, this); + } + return newNode; + } + } else if (!isLeaf()) { + HeightfieldNode* node = this; + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + if (nextState.becameSubdividedOrCollapsed()) { + HeightfieldNode* child = _children[i]->readSubdivision(nextState); + if (_children[i] != child) { + if (node == this) { + node = new HeightfieldNode(*this); + } + node->_children[i] = child; + } + } + } + if (node != this) { + node->mergeChildren(); + } + return node; + } + } else if (!isLeaf()) { + return new HeightfieldNode(_height, _color, _material); + } + return this; +} + +void HeightfieldNode::writeSubdivision(HeightfieldStreamState& state) const { + if (!state.shouldSubdivide()) { + return; + } + bool leaf = isLeaf(); + if (!state.shouldSubdivideReference()) { + state.base.stream << leaf; + if (!leaf) { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i]->writeSubdivided(nextState, state, this); + } + } + } else if (!leaf) { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + if (nextState.becameSubdivided()) { + _children[i]->writeSubdivision(nextState); + } + } + } +} + +void HeightfieldNode::readSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, + const HeightfieldNode* ancestor) { + clearChildren(); + + if (!state.shouldSubdivide()) { + // TODO: subdivision encoding + state.base.stream >> _height >> _color >> _material; + return; + } + bool leaf; + state.base.stream >> leaf; + if (leaf) { + state.base.stream >> _height >> _color >> _material; + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i] = new HeightfieldNode(); + _children[i]->readSubdivided(nextState, ancestorState, ancestor); + } + mergeChildren(); + } +} + +void HeightfieldNode::writeSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, + const HeightfieldNode* ancestor) const { + if (!state.shouldSubdivide()) { + // TODO: subdivision encoding + state.base.stream << _height << _color << _material; + return; + } + bool leaf = isLeaf(); + state.base.stream << leaf; + if (leaf) { + state.base.stream << _height << _color << _material; + + } else { + HeightfieldStreamState nextState = { state.base, glm::vec2(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i]->writeSubdivided(nextState, ancestorState, ancestor); + } + } +} + +void HeightfieldNode::clearChildren() { + for (int i = 0; i < CHILD_COUNT; i++) { + _children[i].reset(); + } +} + +void HeightfieldNode::mergeChildren(bool height, bool colorMaterial) { + if (isLeaf()) { + return; + } + int heightWidth = 0; + int heightHeight = 0; + int colorWidth = 0; + int colorHeight = 0; + int materialWidth = 0; + int materialHeight = 0; + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldHeightPointer childHeight = _children[i]->getHeight(); + if (childHeight) { + int childHeightWidth = childHeight->getWidth(); + int childHeightHeight = childHeight->getContents().size() / childHeightWidth; + heightWidth = qMax(heightWidth, childHeightWidth); + heightHeight = qMax(heightHeight, childHeightHeight); + } + HeightfieldColorPointer childColor = _children[i]->getColor(); + if (childColor) { + int childColorWidth = childColor->getWidth(); + int childColorHeight = childColor->getContents().size() / (childColorWidth * DataBlock::COLOR_BYTES); + colorWidth = qMax(colorWidth, childColorWidth); + colorHeight = qMax(colorHeight, childColorHeight); + } + HeightfieldMaterialPointer childMaterial = _children[i]->getMaterial(); + if (childMaterial) { + int childMaterialWidth = childMaterial->getWidth(); + int childMaterialHeight = childMaterial->getContents().size() / childMaterialWidth; + materialWidth = qMax(materialWidth, childMaterialWidth); + materialHeight = qMax(materialHeight, childMaterialHeight); + } + } + if (heightWidth > 0 && height) { + QVector heightContents(heightWidth * heightHeight); + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldHeightPointer childHeight = _children[i]->getHeight(); + int childHeightWidth = childHeight->getWidth(); + int childHeightHeight = childHeight->getContents().size() / childHeightWidth; + if (childHeightWidth != heightWidth || childHeightHeight != heightHeight) { + qWarning() << "Height dimension mismatch [heightWidth=" << heightWidth << ", heightHeight=" << heightHeight << + ", childHeightWidth=" << childHeightWidth << ", childHeightHeight=" << childHeightHeight << "]"; + continue; + } + int innerHeightWidth = heightWidth - HeightfieldHeight::HEIGHT_EXTENSION; + int innerHeightHeight = heightHeight - HeightfieldHeight::HEIGHT_EXTENSION; + int innerQuadrantHeightWidth = innerHeightWidth / 2; + int innerQuadrantHeightHeight = innerHeightHeight / 2; + int quadrantHeightWidth = innerQuadrantHeightWidth + HeightfieldHeight::HEIGHT_EXTENSION - 1; + int quadrantHeightHeight = innerQuadrantHeightHeight + HeightfieldHeight::HEIGHT_EXTENSION - 1; + quint16* dest = heightContents.data() + (i & Y_MAXIMUM_FLAG ? (innerQuadrantHeightHeight + 1) * heightWidth : 0) + + (i & X_MAXIMUM_FLAG ? innerQuadrantHeightWidth + 1 : 0); + const quint16* src = childHeight->getContents().constData(); + for (int z = 0; z < quadrantHeightHeight; z++, dest += heightWidth, src += heightWidth * 2) { + const quint16* lineSrc = src; + for (quint16* lineDest = dest, *end = dest + quadrantHeightWidth; lineDest != end; lineDest++, lineSrc += 2) { + *lineDest = *lineSrc; + } + } + } + _height = new HeightfieldHeight(heightWidth, heightContents); + + } else if (height) { + _height.reset(); + } + if (!colorMaterial) { + return; + } + if (colorWidth > 0) { + QByteArray colorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF); + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldColorPointer childColor = _children[i]->getColor(); + if (!childColor) { + continue; + } + int childColorWidth = childColor->getWidth(); + int childColorHeight = childColor->getContents().size() / (childColorWidth * DataBlock::COLOR_BYTES); + if (childColorWidth != colorWidth || childColorHeight != colorHeight) { + qWarning() << "Color dimension mismatch [colorWidth=" << colorWidth << ", colorHeight=" << colorHeight << + ", childColorWidth=" << childColorWidth << ", childColorHeight=" << childColorHeight << "]"; + continue; + } + int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE; + int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE; + int innerQuadrantColorWidth = innerColorWidth / 2; + int innerQuadrantColorHeight = innerColorHeight / 2; + int quadrantColorWidth = innerQuadrantColorWidth + HeightfieldData::SHARED_EDGE; + int quadrantColorHeight = innerQuadrantColorHeight + HeightfieldData::SHARED_EDGE; + char* dest = colorContents.data() + ((i & Y_MAXIMUM_FLAG ? innerQuadrantColorHeight * colorWidth : 0) + + (i & X_MAXIMUM_FLAG ? innerQuadrantColorWidth : 0)) * DataBlock::COLOR_BYTES; + const uchar* src = (const uchar*)childColor->getContents().constData(); + for (int z = 0; z < quadrantColorHeight; z++, dest += colorWidth * DataBlock::COLOR_BYTES, + src += colorWidth * DataBlock::COLOR_BYTES * 2) { + const uchar* lineSrc = src; + for (char* lineDest = dest, *end = dest + quadrantColorWidth * DataBlock::COLOR_BYTES; + lineDest != end; lineDest += DataBlock::COLOR_BYTES, lineSrc += DataBlock::COLOR_BYTES * 2) { + lineDest[0] = lineSrc[0]; + lineDest[1] = lineSrc[1]; + lineDest[2] = lineSrc[2]; + } + } + } + _color = new HeightfieldColor(colorWidth, colorContents); + + } else { + _color.reset(); + } + if (materialWidth > 0) { + QByteArray materialContents(materialWidth * materialHeight, 0); + QVector materials; + for (int i = 0; i < CHILD_COUNT; i++) { + HeightfieldMaterialPointer childMaterial = _children[i]->getMaterial(); + if (!childMaterial) { + continue; + } + int childMaterialWidth = childMaterial->getWidth(); + int childMaterialHeight = childMaterial->getContents().size() / childMaterialWidth; + if (childMaterialWidth != materialWidth || childMaterialHeight != materialHeight) { + qWarning() << "Material dimension mismatch [materialWidth=" << materialWidth << ", materialHeight=" << + materialHeight << ", childMaterialWidth=" << childMaterialWidth << ", childMaterialHeight=" << + childMaterialHeight << "]"; + continue; + } + int innerMaterialWidth = materialWidth - HeightfieldData::SHARED_EDGE; + int innerMaterialHeight = materialHeight - HeightfieldData::SHARED_EDGE; + int innerQuadrantMaterialWidth = innerMaterialWidth / 2; + int innerQuadrantMaterialHeight = innerMaterialHeight / 2; + int quadrantMaterialWidth = innerQuadrantMaterialWidth + HeightfieldData::SHARED_EDGE; + int quadrantMaterialHeight = innerQuadrantMaterialHeight + HeightfieldData::SHARED_EDGE; + uchar* dest = (uchar*)materialContents.data() + + (i & Y_MAXIMUM_FLAG ? innerQuadrantMaterialHeight * materialWidth : 0) + + (i & X_MAXIMUM_FLAG ? innerQuadrantMaterialWidth : 0); + const uchar* src = (const uchar*)childMaterial->getContents().constData(); + QHash materialMap; + for (int z = 0; z < quadrantMaterialHeight; z++, dest += materialWidth, src += materialWidth * 2) { + const uchar* lineSrc = src; + for (uchar* lineDest = dest, *end = dest + quadrantMaterialWidth; lineDest != end; lineDest++, lineSrc += 2) { + int value = *lineSrc; + if (value != 0) { + int& mapping = materialMap[value]; + if (mapping == 0) { + mapping = getMaterialIndex(childMaterial->getMaterials().at(value - 1), + materials, materialContents); + } + value = mapping; + } + *lineDest = value; + } + } + } + _material = new HeightfieldMaterial(materialWidth, materialContents, materials); + + } else { + _material.reset(); + } +} + +QRgb HeightfieldNode::getColorAt(const glm::vec3& location) const { + if (location.x < 0.0f || location.z < 0.0f || location.x > 1.0f || location.z > 1.0f) { + return 0; + } + int width = _color->getWidth(); + const QByteArray& contents = _color->getContents(); + const uchar* src = (const uchar*)contents.constData(); + int height = contents.size() / (width * DataBlock::COLOR_BYTES); + int innerWidth = width - HeightfieldData::SHARED_EDGE; + int innerHeight = height - HeightfieldData::SHARED_EDGE; + + glm::vec3 relative = location * glm::vec3((float)innerWidth, 1.0f, (float)innerHeight); + glm::vec3 floors = glm::floor(relative); + glm::vec3 ceils = glm::ceil(relative); + glm::vec3 fracts = glm::fract(relative); + int floorX = (int)floors.x; + int floorZ = (int)floors.z; + int ceilX = (int)ceils.x; + int ceilZ = (int)ceils.z; + const uchar* upperLeft = src + (floorZ * width + floorX) * DataBlock::COLOR_BYTES; + const uchar* lowerRight = src + (ceilZ * width + ceilX) * DataBlock::COLOR_BYTES; + glm::vec3 interpolatedColor = glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]), + glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fracts.z); + + // the final vertex (and thus which triangle we check) depends on which half we're on + if (fracts.x >= fracts.z) { + const uchar* upperRight = src + (floorZ * width + ceilX) * DataBlock::COLOR_BYTES; + interpolatedColor = glm::mix(interpolatedColor, glm::mix(glm::vec3(upperRight[0], upperRight[1], upperRight[2]), + glm::vec3(lowerRight[0], lowerRight[1], lowerRight[2]), fracts.z), (fracts.x - fracts.z) / (1.0f - fracts.z)); + + } else { + const uchar* lowerLeft = src + (ceilZ * width + floorX) * DataBlock::COLOR_BYTES; + interpolatedColor = glm::mix(glm::mix(glm::vec3(upperLeft[0], upperLeft[1], upperLeft[2]), + glm::vec3(lowerLeft[0], lowerLeft[1], lowerLeft[2]), fracts.z), interpolatedColor, fracts.x / fracts.z); + } + return qRgb(interpolatedColor.r, interpolatedColor.g, interpolatedColor.b); +} + +int HeightfieldNode::getMaterialAt(const glm::vec3& location) const { + if (location.x < 0.0f || location.z < 0.0f || location.x > 1.0f || location.z > 1.0f) { + return -1; + } + int width = _material->getWidth(); + const QByteArray& contents = _material->getContents(); + const uchar* src = (const uchar*)contents.constData(); + int height = contents.size() / width; + int innerWidth = width - HeightfieldData::SHARED_EDGE; + int innerHeight = height - HeightfieldData::SHARED_EDGE; + + glm::vec3 relative = location * glm::vec3((float)innerWidth, 1.0f, (float)innerHeight); + return src[(int)glm::round(relative.z) * width + (int)glm::round(relative.x)]; +} + +AbstractHeightfieldNodeRenderer::~AbstractHeightfieldNodeRenderer() { +} + +Heightfield::Heightfield() : + _aspectY(1.0f), + _aspectZ(1.0f) { + + connect(this, &Heightfield::translationChanged, this, &Heightfield::updateBounds); + connect(this, &Heightfield::rotationChanged, this, &Heightfield::updateBounds); + connect(this, &Heightfield::scaleChanged, this, &Heightfield::updateBounds); + connect(this, &Heightfield::aspectYChanged, this, &Heightfield::updateBounds); + connect(this, &Heightfield::aspectZChanged, this, &Heightfield::updateBounds); + updateBounds(); + + connect(this, &Heightfield::heightChanged, this, &Heightfield::updateRoot); + connect(this, &Heightfield::colorChanged, this, &Heightfield::updateRoot); + connect(this, &Heightfield::materialChanged, this, &Heightfield::updateRoot); + updateRoot(); +} + +void Heightfield::setAspectY(float aspectY) { + if (_aspectY != aspectY) { + emit aspectYChanged(_aspectY = aspectY); + } +} + +void Heightfield::setAspectZ(float aspectZ) { + if (_aspectZ != aspectZ) { + emit aspectZChanged(_aspectZ = aspectZ); + } +} + +void Heightfield::setHeight(const HeightfieldHeightPointer& height) { + if (_height != height) { + emit heightChanged(_height = height); + } +} + +void Heightfield::setColor(const HeightfieldColorPointer& color) { + if (_color != color) { + emit colorChanged(_color = color); + } +} + +void Heightfield::setMaterial(const HeightfieldMaterialPointer& material) { + if (_material != material) { + emit materialChanged(_material = material); + } +} + +void Heightfield::setRoot(const HeightfieldNodePointer& root) { + if (_root != root) { + emit rootChanged(_root = root); + } +} + +MetavoxelLOD Heightfield::transformLOD(const MetavoxelLOD& lod) const { + // after transforming into unit space, we scale the threshold in proportion to vertical distance + glm::vec3 inverseScale(1.0f / getScale(), 1.0f / (getScale() * _aspectY), 1.0f / (getScale() * _aspectZ)); + glm::vec3 position = glm::inverse(getRotation()) * (lod.position - getTranslation()) * inverseScale; + const float THRESHOLD_MULTIPLIER = 256.0f; + return MetavoxelLOD(glm::vec3(position.x, position.z, 0.0f), lod.threshold * + qMax(0.5f, glm::abs(position.y * _aspectY - 0.5f)) * THRESHOLD_MULTIPLIER); +} + +SharedObject* Heightfield::clone(bool withID, SharedObject* target) const { + Heightfield* newHeightfield = static_cast(Spanner::clone(withID, target)); + newHeightfield->setHeight(_height); + newHeightfield->setColor(_color); + newHeightfield->setMaterial(_material); + newHeightfield->setRoot(_root); + return newHeightfield; +} + +bool Heightfield::isHeightfield() const { + return true; +} + +float Heightfield::getHeight(const glm::vec3& location) const { + float result = _root->getHeight(glm::inverse(getRotation()) * (location - getTranslation()) * glm::vec3(1.0f / getScale(), + 0.0f, 1.0f / (getScale() * _aspectZ))); + return (result == -FLT_MAX) ? -FLT_MAX : (getTranslation().y + result * getScale() * _aspectY); +} + +bool Heightfield::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { + glm::quat inverseRotation = glm::inverse(getRotation()); + glm::vec3 inverseScale(1.0f / getScale(), 1.0f / (getScale() * _aspectY), 1.0f / (getScale() * _aspectZ)); + return _root->findRayIntersection(inverseRotation * (origin - getTranslation()) * inverseScale, + inverseRotation * direction * inverseScale, distance); +} + +Spanner* Heightfield::paintMaterial(const glm::vec3& position, float radius, + const SharedObjectPointer& material, const QColor& color) { + glm::vec3 inverseScale(1.0f / getScale(), 1.0f, 1.0f / (getScale() * _aspectZ)); + HeightfieldNode* newRoot = _root->paintMaterial(glm::inverse(getRotation()) * (position - getTranslation()) * + inverseScale, radius * inverseScale, material, color); + if (_root == newRoot) { + return this; + } + Heightfield* newHeightfield = static_cast(clone(true)); + newHeightfield->setRoot(HeightfieldNodePointer(newRoot)); + return newHeightfield; +} + +Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float height) { + // first see if we're going to exceed the range limits + glm::vec3 inverseScale(1.0f / getScale(), 1.0f, 1.0f / (getScale() * _aspectZ)); + glm::vec3 relativePosition = glm::inverse(getRotation()) * (position - getTranslation()) * inverseScale; + glm::vec3 relativeRadius = radius * inverseScale; + int minimumValue = 1, maximumValue = numeric_limits::max(); + _root->getRangeAfterHeightPaint(relativePosition, relativeRadius, + height * numeric_limits::max() / (getScale() * _aspectY), minimumValue, maximumValue); + + // renormalize if necessary + Heightfield* newHeightfield = static_cast(clone(true)); + float normalizeScale = 1.0f, normalizeOffset = 0.0f; + if (minimumValue < 1 || maximumValue > numeric_limits::max()) { + normalizeScale = (numeric_limits::max() - 1.0f) / (maximumValue - minimumValue); + normalizeOffset = 1.0f - minimumValue; + newHeightfield->setAspectY(_aspectY / normalizeScale); + newHeightfield->setTranslation(getTranslation() - getRotation() * + glm::vec3(0.0f, normalizeOffset * _aspectY * getScale() / (numeric_limits::max() - 1), 0.0f)); + } + + // now apply the actual change + newHeightfield->setRoot(HeightfieldNodePointer(_root->paintHeight(relativePosition, relativeRadius, + height * numeric_limits::max() / (getScale() * newHeightfield->getAspectY()), + normalizeScale, normalizeOffset))); + return newHeightfield; +} + +Spanner* Heightfield::clearAndFetchHeight(const Box& bounds, SharedObjectPointer& heightfield) { + HeightfieldNode* newRoot = _root->clearAndFetchHeight(getTranslation(), getRotation(), + glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), bounds, heightfield); + if (_root == newRoot) { + return this; + } + Heightfield* newHeightfield = static_cast(clone(true)); + newHeightfield->setRoot(HeightfieldNodePointer(newRoot)); return newHeightfield; } @@ -2115,6 +2946,119 @@ bool Heightfield::intersects(const glm::vec3& start, const glm::vec3& end, float return false; } +void Heightfield::writeExtra(Bitstream& out) const { + if (getWillBeVoxelized()) { + out << _height << _color << _material; + return; + } + MetavoxelLOD lod; + if (out.getContext()) { + lod = transformLOD(static_cast(out.getContext())->lod); + } + HeightfieldStreamBase base = { out, lod, lod }; + HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; + _root->write(state); +} + +void Heightfield::readExtra(Bitstream& in) { + if (getWillBeVoxelized()) { + in >> _height >> _color >> _material; + return; + } + MetavoxelLOD lod; + if (in.getContext()) { + lod = transformLOD(static_cast(in.getContext())->lod); + } + HeightfieldStreamBase base = { in, lod, lod }; + HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; + + HeightfieldNodePointer root(new HeightfieldNode()); + root->read(state); + setRoot(root); +} + +void Heightfield::writeExtraDelta(Bitstream& out, const SharedObject* reference) const { + MetavoxelLOD lod, referenceLOD; + if (out.getContext()) { + MetavoxelStreamBase* base = static_cast(out.getContext()); + lod = transformLOD(base->lod); + referenceLOD = transformLOD(base->referenceLOD); + } + HeightfieldStreamBase base = { out, lod, referenceLOD }; + HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; + const HeightfieldNodePointer& referenceRoot = static_cast(reference)->getRoot(); + if (_root == referenceRoot) { + out << false; + if (state.becameSubdivided()) { + _root->writeSubdivision(state); + } + } else { + out << true; + _root->writeDelta(referenceRoot, state); + } +} + +void Heightfield::readExtraDelta(Bitstream& in, const SharedObject* reference) { + MetavoxelLOD lod, referenceLOD; + if (in.getContext()) { + MetavoxelStreamBase* base = static_cast(in.getContext()); + lod = transformLOD(base->lod); + referenceLOD = transformLOD(base->referenceLOD); + } + HeightfieldStreamBase base = { in, lod, referenceLOD }; + HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; + + bool changed; + in >> changed; + if (changed) { + HeightfieldNodePointer root(new HeightfieldNode()); + root->readDelta(static_cast(reference)->getRoot(), state); + setRoot(root); + + } else if (state.becameSubdividedOrCollapsed()) { + setRoot(HeightfieldNodePointer(_root->readSubdivision(state))); + } +} + +void Heightfield::maybeWriteSubdivision(Bitstream& out) { + MetavoxelLOD lod, referenceLOD; + if (out.getContext()) { + MetavoxelStreamBase* base = static_cast(out.getContext()); + lod = transformLOD(base->lod); + referenceLOD = transformLOD(base->referenceLOD); + } + HeightfieldStreamBase base = { out, lod, referenceLOD }; + HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; + + if (state.becameSubdividedOrCollapsed()) { + out << SharedObjectPointer(this); + _root->writeSubdivision(state); + } +} + +SharedObject* Heightfield::readSubdivision(Bitstream& in) { + MetavoxelLOD lod, referenceLOD; + if (in.getContext()) { + MetavoxelStreamBase* base = static_cast(in.getContext()); + lod = transformLOD(base->lod); + referenceLOD = transformLOD(base->referenceLOD); + } + HeightfieldStreamBase base = { in, lod, referenceLOD }; + HeightfieldStreamState state = { base, glm::vec2(), 1.0f }; + + if (state.becameSubdividedOrCollapsed()) { + HeightfieldNodePointer root(_root->readSubdivision(state)); + if (_root != root) { + Heightfield* newHeightfield = static_cast(clone(true)); + newHeightfield->setRemoteID(getRemoteID()); + newHeightfield->setRemoteOriginID(getRemoteOriginID()); + newHeightfield->setRoot(root); + return newHeightfield; + } + } + return this; +} + QByteArray Heightfield::getRendererClassName() const { return "HeightfieldRenderer"; } @@ -2124,3 +3068,11 @@ void Heightfield::updateBounds() { glm::mat4 rotationMatrix = glm::mat4_cast(getRotation()); setBounds(glm::translate(getTranslation()) * rotationMatrix * Box(glm::vec3(), extent)); } + +void Heightfield::updateRoot() { + HeightfieldNodePointer root(new HeightfieldNode()); + if (_height) { + root->setContents(_height, _color, _material); + } + setRoot(root); +} diff --git a/libraries/metavoxels/src/Spanner.h b/libraries/metavoxels/src/Spanner.h index 7fe32b56a6..bec1355b48 100644 --- a/libraries/metavoxels/src/Spanner.h +++ b/libraries/metavoxels/src/Spanner.h @@ -17,9 +17,12 @@ #include "AttributeRegistry.h" #include "MetavoxelUtil.h" +class AbstractHeightfieldNodeRenderer; +class Heightfield; class HeightfieldColor; class HeightfieldHeight; class HeightfieldMaterial; +class HeightfieldNode; class SpannerRenderer; /// An object that spans multiple octree cells. @@ -28,6 +31,7 @@ class Spanner : public SharedObject { Q_PROPERTY(Box bounds MEMBER _bounds WRITE setBounds NOTIFY boundsChanged DESIGNABLE false) Q_PROPERTY(float placementGranularity MEMBER _placementGranularity DESIGNABLE false) Q_PROPERTY(float voxelizationGranularity MEMBER _voxelizationGranularity DESIGNABLE false) + Q_PROPERTY(bool willBeVoxelized MEMBER _willBeVoxelized DESIGNABLE false) public: @@ -48,6 +52,9 @@ public: void setMerged(bool merged) { _merged = merged; } bool isMerged() const { return _merged; } + void setWillBeVoxelized(bool willBeVoxelized) { _willBeVoxelized = willBeVoxelized; } + bool getWillBeVoxelized() const { return _willBeVoxelized; } + /// Checks whether we've visited this object on the current traversal. If we have, returns false. /// If we haven't, sets the last visit identifier and returns true. bool testAndSetVisited(int visit); @@ -117,6 +124,7 @@ private: float _placementGranularity; float _voxelizationGranularity; bool _merged; + bool _willBeVoxelized; QHash _lastVisits; ///< last visit identifiers for each thread QMutex _lastVisitsMutex; @@ -133,7 +141,7 @@ public: virtual void init(Spanner* spanner); virtual void simulate(float deltaTime); - virtual void render(bool cursor = false); + virtual void render(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false); virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; protected: @@ -458,14 +466,129 @@ Bitstream& operator>>(Bitstream& in, HeightfieldMaterialPointer& value); template<> void Bitstream::writeRawDelta(const HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference); template<> void Bitstream::readRawDelta(HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference); +typedef QExplicitlySharedDataPointer HeightfieldNodePointer; + +/// Holds the base state used in streaming heightfield data. +class HeightfieldStreamBase { +public: + Bitstream& stream; + const MetavoxelLOD& lod; + const MetavoxelLOD& referenceLOD; +}; + +/// Holds the state used in streaming a heightfield node. +class HeightfieldStreamState { +public: + HeightfieldStreamBase& base; + glm::vec2 minimum; + float size; + + bool shouldSubdivide() const; + bool shouldSubdivideReference() const; + bool becameSubdivided() const; + bool becameSubdividedOrCollapsed() const; + + void setMinimum(const glm::vec2& lastMinimum, int index); +}; + +/// A node in a heightfield quadtree. +class HeightfieldNode : public QSharedData { +public: + + static const int CHILD_COUNT = 4; + + HeightfieldNode(const HeightfieldHeightPointer& height = HeightfieldHeightPointer(), + const HeightfieldColorPointer& color = HeightfieldColorPointer(), + const HeightfieldMaterialPointer& material = HeightfieldMaterialPointer()); + + HeightfieldNode(const HeightfieldNode& other); + + ~HeightfieldNode(); + + void setContents(const HeightfieldHeightPointer& height, const HeightfieldColorPointer& color, + const HeightfieldMaterialPointer& material); + + void setHeight(const HeightfieldHeightPointer& height) { _height = height; } + const HeightfieldHeightPointer& getHeight() const { return _height; } + + void setColor(const HeightfieldColorPointer& color) { _color = color; } + const HeightfieldColorPointer& getColor() const { return _color; } + + void setMaterial(const HeightfieldMaterialPointer& material) { _material = material; } + const HeightfieldMaterialPointer& getMaterial() const { return _material; } + + void setRenderer(AbstractHeightfieldNodeRenderer* renderer) { _renderer = renderer; } + AbstractHeightfieldNodeRenderer* getRenderer() const { return _renderer; } + + bool isLeaf() const; + + void setChild(int index, const HeightfieldNodePointer& child) { _children[index] = child; } + const HeightfieldNodePointer& getChild(int index) const { return _children[index]; } + + float getHeight(const glm::vec3& location) const; + + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; + + HeightfieldNode* paintMaterial(const glm::vec3& position, const glm::vec3& radius, const SharedObjectPointer& material, + const QColor& color); + + void getRangeAfterHeightPaint(const glm::vec3& position, const glm::vec3& radius, + float height, int& minimum, int& maximum) const; + + HeightfieldNode* paintHeight(const glm::vec3& position, const glm::vec3& radius, float height, + float normalizeScale, float normalizeOffset); + + HeightfieldNode* clearAndFetchHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, + const Box& bounds, SharedObjectPointer& heightfield); + + void read(HeightfieldStreamState& state); + void write(HeightfieldStreamState& state) const; + + void readDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state); + void writeDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) const; + + HeightfieldNode* readSubdivision(HeightfieldStreamState& state); + void writeSubdivision(HeightfieldStreamState& state) const; + + void readSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, + const HeightfieldNode* ancestor); + void writeSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, + const HeightfieldNode* ancestor) const; + +private: + + void clearChildren(); + void mergeChildren(bool height = true, bool colorMaterial = true); + + QRgb getColorAt(const glm::vec3& location) const; + int getMaterialAt(const glm::vec3& location) const; + + HeightfieldHeightPointer _height; + HeightfieldColorPointer _color; + HeightfieldMaterialPointer _material; + + HeightfieldNodePointer _children[CHILD_COUNT]; + + AbstractHeightfieldNodeRenderer* _renderer; +}; + +/// Base class for heightfield node rendering. +class AbstractHeightfieldNodeRenderer { +public: + + virtual ~AbstractHeightfieldNodeRenderer(); +}; + /// A heightfield represented as a spanner. class Heightfield : public Transformable { Q_OBJECT Q_PROPERTY(float aspectY MEMBER _aspectY WRITE setAspectY NOTIFY aspectYChanged) Q_PROPERTY(float aspectZ MEMBER _aspectZ WRITE setAspectZ NOTIFY aspectZChanged) - Q_PROPERTY(HeightfieldHeightPointer height MEMBER _height WRITE setHeight NOTIFY heightChanged) - Q_PROPERTY(HeightfieldColorPointer color MEMBER _color WRITE setColor NOTIFY colorChanged) - Q_PROPERTY(HeightfieldMaterialPointer material MEMBER _material WRITE setMaterial NOTIFY materialChanged DESIGNABLE false) + Q_PROPERTY(HeightfieldHeightPointer height MEMBER _height WRITE setHeight NOTIFY heightChanged STORED false) + Q_PROPERTY(HeightfieldColorPointer color MEMBER _color WRITE setColor NOTIFY colorChanged STORED false) + Q_PROPERTY(HeightfieldMaterialPointer material MEMBER _material WRITE setMaterial NOTIFY materialChanged STORED false + DESIGNABLE false) + Q_PROPERTY(HeightfieldNodePointer root MEMBER _root WRITE setRoot NOTIFY rootChanged STORED false DESIGNABLE false) public: @@ -486,6 +609,13 @@ public: void setMaterial(const HeightfieldMaterialPointer& material); const HeightfieldMaterialPointer& getMaterial() const { return _material; } + void setRoot(const HeightfieldNodePointer& root); + const HeightfieldNodePointer& getRoot() const { return _root; } + + MetavoxelLOD transformLOD(const MetavoxelLOD& lod) const; + + virtual SharedObject* clone(bool withID = false, SharedObject* target = NULL) const; + virtual bool isHeightfield() const; virtual float getHeight(const glm::vec3& location) const; @@ -508,6 +638,13 @@ public: virtual bool contains(const glm::vec3& point); virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); + virtual void writeExtra(Bitstream& out) const; + virtual void readExtra(Bitstream& in); + virtual void writeExtraDelta(Bitstream& out, const SharedObject* reference) const; + virtual void readExtraDelta(Bitstream& in, const SharedObject* reference); + virtual void maybeWriteSubdivision(Bitstream& out); + virtual SharedObject* readSubdivision(Bitstream& in); + signals: void aspectYChanged(float aspectY); @@ -515,6 +652,7 @@ signals: void heightChanged(const HeightfieldHeightPointer& height); void colorChanged(const HeightfieldColorPointer& color); void materialChanged(const HeightfieldMaterialPointer& material); + void rootChanged(const HeightfieldNodePointer& root); protected: @@ -523,14 +661,18 @@ protected: private slots: void updateBounds(); + void updateRoot(); private: float _aspectY; float _aspectZ; + HeightfieldHeightPointer _height; HeightfieldColorPointer _color; HeightfieldMaterialPointer _material; + + HeightfieldNodePointer _root; }; #endif // hifi_Spanner_h diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index b1c47c0ebf..3f08cdec69 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -81,7 +81,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeAudioStreamStats: return 1; case PacketTypeMetavoxelData: - return 9; + return 10; case PacketTypeVoxelData: return VERSION_VOXELS_HAS_FILE_BREAKS; default: diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index f13f832920..1a432c8e59 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -693,13 +693,14 @@ public: BoxFace& face; void** intersectedObject; bool found; + bool precisionPicking; }; bool findRayIntersectionOp(OctreeElement* element, void* extraData) { RayArgs* args = static_cast(extraData); bool keepSearching = true; if (element->findRayIntersection(args->origin, args->direction, keepSearching, - args->element, args->distance, args->face, args->intersectedObject)) { + args->element, args->distance, args->face, args->intersectedObject, args->precisionPicking)) { args->found = true; } return keepSearching; @@ -707,8 +708,9 @@ bool findRayIntersectionOp(OctreeElement* element, void* extraData) { bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, - Octree::lockType lockType, bool* accurateResult) { - RayArgs args = { origin / (float)(TREE_SCALE), direction, element, distance, face, intersectedObject, false}; + Octree::lockType lockType, bool* accurateResult, bool precisionPicking) { + RayArgs args = { origin / (float)(TREE_SCALE), direction, element, distance, face, + intersectedObject, false, precisionPicking}; distance = FLT_MAX; bool gotLock = false; diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 4ac7e22d90..cde8565ca2 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -298,7 +298,9 @@ public: bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElement*& node, float& distance, BoxFace& face, void** intersectedObject = NULL, - Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); + Octree::lockType lockType = Octree::TryLock, + bool* accurateResult = NULL, + bool precisionPicking = false); bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject = NULL, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index e5db8b24f8..c6938ff1f6 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -1334,16 +1334,20 @@ void OctreeElement::notifyUpdateHooks() { bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject) { + void** intersectedObject, bool precisionPicking) { keepSearching = true; // assume that we will continue searching after this. AACube cube = getAACube(); - float localDistance; + float distanceToElementCube = std::numeric_limits::max(); + float distanceToElementDetails = distance; BoxFace localFace; + AACube debugCube = cube; + debugCube.scale((float)TREE_SCALE); + // if the ray doesn't intersect with our cube, we can stop searching! - if (!cube.findRayIntersection(origin, direction, localDistance, localFace)) { + if (!cube.findRayIntersection(origin, direction, distanceToElementCube, localFace)) { keepSearching = false; // no point in continuing to search return false; // we did not intersect } @@ -1353,14 +1357,18 @@ bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3 return false; // we don't intersect with non-leaves, and we keep searching } - // we did hit this element, so calculate appropriate distances - localDistance *= TREE_SCALE; - if (localDistance < distance) { - if (findDetailedRayIntersection(origin, direction, keepSearching, - element, distance, face, intersectedObject)) { - distance = localDistance; - face = localFace; - return true; + // if the distance to the element cube is not less than the current best distance, then it's not possible + // for any details inside the cube to be closer so we don't need to consider them. + if (cube.contains(origin) || distanceToElementCube < distance) { + + if (findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails, + face, intersectedObject, precisionPicking, distanceToElementCube)) { + + if (distanceToElementDetails < distance) { + distance = distanceToElementDetails; + face = localFace; + return true; + } } } return false; @@ -1368,11 +1376,12 @@ bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3 bool OctreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject) { + void** intersectedObject, bool precisionPicking, float distanceToElementCube) { // we did hit this element, so calculate appropriate distances if (hasContent()) { element = this; + distance = distanceToElementCube; if (intersectedObject) { *intersectedObject = this; } diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 2bd5e3ae1e..3bd13a2f3c 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -119,11 +119,11 @@ public: virtual bool canRayIntersect() const { return isLeaf(); } virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& node, float& distance, BoxFace& face, - void** intersectedObject = NULL); + void** intersectedObject = NULL, bool precisionPicking = false); virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject); + void** intersectedObject, bool precisionPicking, float distanceToElementCube); virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const; diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 1b6472a18f..5376883438 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -252,6 +252,30 @@ bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direct return true; } +bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance) { + glm::vec3 firstSide = v0 - v1; + glm::vec3 secondSide = v2 - v1; + glm::vec3 normal = glm::cross(secondSide, firstSide); + float dividend = glm::dot(normal, v1) - glm::dot(origin, normal); + if (dividend > 0.0f) { + return false; // origin below plane + } + float divisor = glm::dot(normal, direction); + if (divisor > -EPSILON) { + return false; + } + float t = dividend / divisor; + glm::vec3 point = origin + direction * t; + if (glm::dot(normal, glm::cross(point - v1, firstSide)) > 0.0f && + glm::dot(normal, glm::cross(secondSide, point - v1)) > 0.0f && + glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) { + distance = t; + return true; + } + return false; +} + // Do line segments (r1p1.x, r1p1.y)--(r1p2.x, r1p2.y) and (r2p1.x, r2p1.y)--(r2p2.x, r2p2.y) intersect? // from: http://ptspts.blogspot.com/2010/06/how-to-determine-if-two-line-segments.html bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2) { diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index b521a79771..a6889ef73e 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -76,6 +76,22 @@ bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& directi bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& start, const glm::vec3& end, float radius, float& distance); +bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance); + +class Triangle { +public: + glm::vec3 v0; + glm::vec3 v1; + glm::vec3 v2; +}; + +inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, + const Triangle& triangle, float& distance) { + return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance); +} + + bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2); bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk); int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk);