diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 63b2083aae..3acd783bb0 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -54,7 +54,7 @@ const short JITTER_BUFFER_MSECS = 12; const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_MSECS * (SAMPLE_RATE / 1000.0); -const float LOUDNESS_TO_DISTANCE_RATIO = 0.00305f; +const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f; const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer"; @@ -348,6 +348,16 @@ void AudioMixer::readPendingDatagrams() { || mixerPacketType == PacketTypeSilentAudioFrame) { nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket); + } else if (mixerPacketType == PacketTypeMuteEnvironment) { + QByteArray packet = receivedPacket; + populatePacketHeader(packet, PacketTypeMuteEnvironment); + + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && node != nodeList->sendingNodeForPacket(receivedPacket)) { + nodeList->writeDatagram(packet, packet.size(), node); + } + } + } else { // let processNodeData handle it. nodeList->processNodeData(senderSockAddr, receivedPacket); diff --git a/examples/airGuitar.js b/examples/airGuitar.js new file mode 100644 index 0000000000..08898579a7 --- /dev/null +++ b/examples/airGuitar.js @@ -0,0 +1,96 @@ +// +// airGuitar.js +// examples +// +// Copyright 2014 High Fidelity, Inc. +// +// This example musical instrument script plays guitar chords based on a strum motion and hand position +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +function length(v) { + return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); +} + + +function printVector(v) { + print(v.x + ", " + v.y + ", " + v.z); + return; +} + +function vMinus(a, b) { + var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; + return rval; +} + +// First, load two percussion sounds to be used on the sticks + +var guitarType = 2; + +if (guitarType == 1) { + var chord1 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+A.raw"); + var chord2 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+B.raw"); + var chord3 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+E.raw"); +} else { + var chord1 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+A+short.raw"); + var chord2 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+B+short.raw"); + var chord3 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+E+short.raw"); +} + + +var whichChord = chord1; + +var leftHanded = false; +if (leftHanded) { + var strumHand = 0; + var chordHand = 1; +} else { + var strumHand = 1; + var chordHand = 0; +} + +var lastPosition = { x: 0.0, + y: 0.0, + z: 0.0 }; + + +function checkHands(deltaTime) { + for (var palm = 0; palm < 2; palm++) { + var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1); + var speed = length(palmVelocity); + var position = Controller.getSpatialControlPosition(palm * 2 + 1); + var myPelvis = MyAvatar.position; + + if (palm == strumHand) { + + var STRUM_HEIGHT_ABOVE_PELVIS = -0.30; + var strumTriggerHeight = myPelvis.y + STRUM_HEIGHT_ABOVE_PELVIS; + //printVector(position); + if ((position.y < strumTriggerHeight) && (lastPosition.y >= strumTriggerHeight)) { + // If hand passes downward through guitar strings, play a chord! + var options = new AudioInjectionOptions(); + options.position = position; + if (speed > 1.0) { speed = 1.0; } + options.volume = speed; + Audio.playSound(whichChord, options); + } + lastPosition = Controller.getSpatialControlPosition(palm * 2 + 1); + } else { + // This is the chord controller + var distanceFromPelvis = Vec3.length(Vec3.subtract(position, myPelvis)); + //print(distanceFromPelvis); + if (distanceFromPelvis > 0.63) { + whichChord = chord3; + } else if (distanceFromPelvis > 0.55) { + whichChord = chord2; + } else { + whichChord = chord1; + } + } + } +} + +// Connect a call back that happens every frame +Script.update.connect(checkHands); \ No newline at end of file diff --git a/examples/editModels.js b/examples/editModels.js index 50b0137c4f..384d2f75a8 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var windowDimensions = Controller.getViewportDimensions(); + var LASER_WIDTH = 4; var LASER_COLOR = { red: 255, green: 0, blue: 0 }; var LASER_LENGTH_FACTOR = 1.5; @@ -16,6 +18,40 @@ var LASER_LENGTH_FACTOR = 1.5; var LEFT = 0; var RIGHT = 1; + +var SPAWN_DISTANCE = 1; +var radiusDefault = 0.10; + +var modelURLs = [ + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/orc.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx", + ]; + +var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; +var numberOfTools = 1; +var toolHeight = 50; +var toolWidth = 50; +var toolVerticalSpacing = 4; +var toolsHeight = toolHeight * numberOfTools + toolVerticalSpacing * (numberOfTools - 1); +var toolsX = windowDimensions.x - 8 - toolWidth; +var toolsY = (windowDimensions.y - toolsHeight) / 2; + + +var firstModel = Overlays.addOverlay("image", { + x: 0, y: 0, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "voxel-tool.svg", + x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }); + function controller(wichSide) { this.side = wichSide; this.palm = 2 * wichSide; @@ -46,7 +82,10 @@ function controller(wichSide) { this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously) this.grabbing = false; - this.modelID; + this.modelID = { isKnownID: false }; + this.oldModelRotation; + this.oldModelPosition; + this.oldModelRadius; this.laser = Overlays.addOverlay("line3d", { position: this.palmPosition, @@ -85,23 +124,19 @@ function controller(wichSide) { - this.grab = function (modelID) { - if (!modelID.isKnownID) { - var identify = Models.identifyModel(modelID); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + "(grab)"); - return; - } - modelID = identify; - } + this.grab = function (modelID, properties) { print("Grabbing " + modelID.id); this.grabbing = true; this.modelID = modelID; + + this.oldModelPosition = properties.position; + this.oldModelRotation = properties.modelRotation; + this.oldModelRadius = properties.radius; } this.release = function () { this.grabbing = false; - this.modelID = 0; + this.modelID.isKnownID = false; } this.checkTrigger = function () { @@ -118,6 +153,34 @@ function controller(wichSide) { } } + this.checkModel = function (properties) { + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X == A + ((P-A).B)B + // d = |P-X| + + var A = this.palmPosition; + var B = this.front; + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + var y = Vec3.dot(Vec3.subtract(P, A), this.up); + var z = Vec3.dot(Vec3.subtract(P, A), this.right); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + + if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) { + return { valid: true, x: x, y: y, z: z }; + } + return { valid: false }; + } + this.moveLaser = function () { var endPosition = Vec3.sum(this.palmPosition, Vec3.multiply(this.front, LASER_LENGTH_FACTOR)); @@ -143,44 +206,33 @@ function controller(wichSide) { }); } - this.checkModel = function (modelID) { - if (!modelID.isKnownID) { - var identify = Models.identifyModel(modelID); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + "(checkModel)"); - return; - } - modelID = identify; + this.moveModel = function () { + if (this.grabbing) { + var newPosition = Vec3.sum(this.palmPosition, + Vec3.multiply(this.front, this.x)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(this.up, this.y)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(this.right, this.z)); + + var newRotation = Quat.multiply(this.rotation, + Quat.inverse(this.oldRotation)); + newRotation = Quat.multiply(newRotation, + this.oldModelRotation); + + Models.editModel(this.modelID, { + position: newPosition, + modelRotation: newRotation + }); + print("Moving " + this.modelID.id); +// Vec3.print("Old Position: ", this.oldModelPosition); +// Vec3.print("Sav Position: ", newPosition); + Quat.print("Old Rotation: ", this.oldModelRotation); + Quat.print("New Rotation: ", newRotation); + + this.oldModelRotation = newRotation; + this.oldModelPosition = newPosition; } - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A - // - // |X-A| = (P-A).B - // X == A + ((P-A).B)B - // d = |P-X| - - var A = this.palmPosition; - var B = this.front; - var P = Models.getModelProperties(modelID).position; - - this.x = Vec3.dot(Vec3.subtract(P, A), B); - this.y = Vec3.dot(Vec3.subtract(P, A), this.up); - this.z = Vec3.dot(Vec3.subtract(P, A), this.right); - var X = Vec3.sum(A, Vec3.multiply(B, this.x)); - var d = Vec3.length(Vec3.subtract(P, X)); - -// Vec3.print("A: ", A); -// Vec3.print("B: ", B); -// Vec3.print("Particle pos: ", P); -// print("d: " + d + ", x: " + this.x); - if (d < Models.getModelProperties(modelID).radius && 0 < this.x && this.x < LASER_LENGTH_FACTOR) { - return true; - } - return false; } this.update = function () { @@ -205,25 +257,40 @@ function controller(wichSide) { this.checkTrigger(); - if (this.pressing) { - Vec3.print("Looking at: ", this.palmPosition); - var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR); - for (var i = 0; i < foundModels.length; i++) { - print("Model found ID (" + foundModels[i].id + ")"); - if (this.checkModel(foundModels[i])) { - if (this.grab(foundModels[i])) { - return; - } - } - } - } + this.moveLaser(); if (!this.pressed && this.grabbing) { // release if trigger not pressed anymore. this.release(); } - - this.moveLaser(); + + if (this.pressing) { + Vec3.print("Looking at: ", this.palmPosition); + var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR); + for (var i = 0; i < foundModels.length; i++) { + + if (!foundModels[i].isKnownID) { + var identify = Models.identifyModel(foundModels[i]); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + "(update loop)"); + return; + } + foundModels[i] = identify; + } + + var properties = Models.getModelProperties(foundModels[i]); + print("Checking properties: " + properties.id + " " + properties.isKnownID); + + var check = this.checkModel(properties); + if (check.valid) { + this.grab(foundModels[i], properties); + this.x = check.x; + this.y = check.y; + this.z = check.z; + return; + } + } + } } this.cleanup = function () { @@ -238,78 +305,44 @@ var leftController = new controller(LEFT); var rightController = new controller(RIGHT); function moveModels() { - if (leftController.grabbing) { - if (rightController.grabbing) { - var properties = Models.getModelProperties(leftController.modelID); - - var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); - var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); - - var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5); - var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint)); - - - var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x)); - var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x)); - - var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5); - var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint)); - - var ratio = length / oldLength; - - var newPosition = Vec3.sum(middle, - Vec3.multiply(Vec3.subtract(properties.position, oldMiddle), ratio)); - Vec3.print("Ratio : " + ratio + " New position: ", newPosition); - var rotation = Quat.multiply(leftController.rotation, - Quat.inverse(leftController.oldRotation)); - rotation = Quat.multiply(rotation, properties.modelRotation); - - Models.editModel(leftController.modelID, { - position: newPosition, - //modelRotation: rotation, - radius: properties.radius * ratio - }); - - return; - } else { - var newPosition = Vec3.sum(leftController.palmPosition, - Vec3.multiply(leftController.front, leftController.x)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(leftController.up, leftController.y)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(leftController.right, leftController.z)); - - var rotation = Quat.multiply(leftController.rotation, - Quat.inverse(leftController.oldRotation)); - rotation = Quat.multiply(rotation, - Models.getModelProperties(leftController.modelID).modelRotation); - - Models.editModel(leftController.modelID, { - position: newPosition, - modelRotation: rotation - }); - } - } - - - if (rightController.grabbing) { - var newPosition = Vec3.sum(rightController.palmPosition, - Vec3.multiply(rightController.front, rightController.x)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(rightController.up, rightController.y)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(rightController.right, rightController.z)); + if (leftController.grabbing && rightController.grabbing && rightController.modelID.id == leftController.modelID.id) { + print("Both controllers"); + var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); + var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); - var rotation = Quat.multiply(rightController.rotation, - Quat.inverse(rightController.oldRotation)); - rotation = Quat.multiply(rotation, - Models.getModelProperties(rightController.modelID).modelRotation); + var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5); + var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint)); - Models.editModel(rightController.modelID, { + + var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x)); + var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x)); + + var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5); + var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint)); + + var ratio = length / oldLength; + + var newPosition = Vec3.sum(middle, + Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio)); + Vec3.print("Ratio : " + ratio + " New position: ", newPosition); + var rotation = Quat.multiply(leftController.rotation, + Quat.inverse(leftController.oldRotation)); + rotation = Quat.multiply(rotation, leftController.oldModelRotation); + + Models.editModel(leftController.modelID, { position: newPosition, - modelRotation: rotation + //modelRotation: rotation, + radius: leftController.oldModelRadius * ratio }); + + leftController.oldModelPosition = newPosition; + leftController.oldModelRotation = rotation; + leftController.oldModelRadius *= ratio; + return; } + + leftController.moveModel(); + rightController.moveModel(); } function checkController(deltaTime) { @@ -318,6 +351,8 @@ function checkController(deltaTime) { var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; + moveOverlays(); + // this is expected for hydras if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) { //print("no hydra connected?"); @@ -329,14 +364,48 @@ function checkController(deltaTime) { moveModels(); } +function moveOverlays() { + windowDimensions = Controller.getViewportDimensions(); + + toolsX = windowDimensions.x - 8 - toolWidth; + toolsY = (windowDimensions.y - toolsHeight) / 2; + + Overlays.editOverlay(firstModel, { + x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight, + }); +} + +function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + var url; + + if (clickedOverlay == firstModel) { + url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]); + if (url == null) { + return; } + } else { + print("Didn't click on anything"); + return; + } + + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); + Models.addModel({ position: position, + radius: radiusDefault, + modelURL: url + }); +} + function scriptEnding() { leftController.cleanup(); rightController.cleanup(); + + Overlays.deleteOverlay(firstModel); } Script.scriptEnding.connect(scriptEnding); // register the call back so it fires before each data send Script.update.connect(checkController); +Controller.mousePressEvent.connect(mousePressEvent); diff --git a/examples/hydraMove.js b/examples/hydraMove.js index b73f990446..ad0eeddfca 100644 --- a/examples/hydraMove.js +++ b/examples/hydraMove.js @@ -31,14 +31,14 @@ var grabbingWithLeftHand = false; var wasGrabbingWithLeftHand = false; var EPSILON = 0.000001; var velocity = { x: 0, y: 0, z: 0}; -var THRUST_MAG_UP = 800.0; -var THRUST_MAG_DOWN = 300.0; -var THRUST_MAG_FWD = 500.0; -var THRUST_MAG_BACK = 300.0; -var THRUST_MAG_LATERAL = 250.0; +var THRUST_MAG_UP = 100.0; +var THRUST_MAG_DOWN = 100.0; +var THRUST_MAG_FWD = 150.0; +var THRUST_MAG_BACK = 100.0; +var THRUST_MAG_LATERAL = 150.0; var THRUST_JUMP = 120.0; -var YAW_MAG = 500.0; +var YAW_MAG = 100.0; var PITCH_MAG = 100.0; var THRUST_MAG_HAND_JETS = THRUST_MAG_FWD; var JOYSTICK_YAW_MAG = YAW_MAG; diff --git a/examples/placeModelsWithHands.js b/examples/placeModelsWithHands.js index 41d9d5dc86..e1ac151fe4 100644 --- a/examples/placeModelsWithHands.js +++ b/examples/placeModelsWithHands.js @@ -37,7 +37,7 @@ var radiusMinimum = 0.05; var radiusMaximum = 0.5; var modelURLs = [ - "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/music/EVHFrankenstein.fbx", + "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/attachments/topHat.fst", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", @@ -72,7 +72,6 @@ function keyPressEvent(event) { } } else if (event.text == "m") { var URL = Window.prompt("Model URL", "Enter URL, e.g. http://foo.com/model.fbx"); - Window.alert("Your response was: " + prompt); var modelPosition = getNewVoxelPosition(); var properties = { position: { x: modelPosition.x, y: modelPosition.y, diff --git a/interface/resources/html/interface-welcome-allsvg.html b/interface/resources/html/interface-welcome-allsvg.html index 0b45a4d717..d025f8059b 100644 --- a/interface/resources/html/interface-welcome-allsvg.html +++ b/interface/resources/html/interface-welcome-allsvg.html @@ -1,139 +1,628 @@ -
+
- - Welcome to Interface - Created with Sketch (http://www.bohemiancoding.com/sketch) - - - - What you can do with Hifi so far: - - - Move around. - - - Listen and talk. - - - Build something. - - - Connect devices. - - - Look around. - - - Move around with WASD - & fly up or down with E & C - - - Use your best headphones - and microphone for high fidelity - audio. Look for the blue balls - around the universe – walk up - to them (they become people - as you get closer) and talk! - - - Refer to the Tools menu for - available tools. Each tool is a - ‘mode’ that enables actions through - clicking. Press the V key to enter - voxel ‘add mode’ where you’ll be - able to click to add a voxel. - - - Have an Oculus Rift or a - Leap Motion? Gyros in your - headset? An Xbox Kinect? - We have experimental - features for them all. - - - Use two fingers to look - around via the trackpad - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A - - - C - - - D - - - S - - - WE - - - - - - path d="M41.277,11.18 L46.981,19.663 L35.579,19.663 L41.277,11.18" id="Fill-12" fill="#FFFFFF" sketch:type="MSShapeGroup"> - - - - + +Welcome to Interface +Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Use your best headphones and microphone for high fidelity audio. Chat via text by pressing the Enter key. + +Use two fingers to look around. Turn this off by opening Running Scripts (Cmnd/Cntrl+J) and clicking the X next to lookWithTouch.js + +Move around with WASD & fly up or down with E & C.Cmnd/Cntrl+G will send you home. @ (Shift+2) will let you teleport to a user or location. + +Have an Oculus Rift, a Razer Hydra, or a PrimeSense 3D camera? We support them all. + +Use the editVoxels.js script to build with your mouse – use the tab key to toggle the tools on/off. + + + + + + + + + + + + + + + + + + +Write a script; we’re always adding new features. Cmnd/Cntrl+J will launch a Running Scripts dialog to help manage your scripts. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + C + D + S + + W + + + + + + + + + E + + + + + + + + + + + + +
diff --git a/interface/resources/shaders/model_normal_specular_map.frag b/interface/resources/shaders/model_normal_specular_map.frag new file mode 100644 index 0000000000..79761446b1 --- /dev/null +++ b/interface/resources/shaders/model_normal_specular_map.frag @@ -0,0 +1,47 @@ +#version 120 + +// +// model_normal_specular_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/6/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the normal map texture +uniform sampler2D normalMap; + +// the specular map texture +uniform sampler2D specularMap; + +// the interpolated normal +varying vec4 interpolatedNormal; + +// the interpolated tangent +varying vec4 interpolatedTangent; + +void main(void) { + vec3 normalizedNormal = normalize(vec3(interpolatedNormal)); + vec3 normalizedTangent = normalize(vec3(interpolatedTangent)); + vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); + vec3 localNormal = vec3(texture2D(normalMap, gl_TexCoord[0].st)) * 2.0 - vec3(1.0, 1.0, 1.0); + + // compute the base color based on OpenGL lighting model + vec4 viewNormal = vec4(normalizedTangent * localNormal.x + + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * max(0.0, dot(viewNormal, gl_LightSource[0].position))); + + // compute the specular component (sans exponent) + float specular = max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), viewNormal)); + + // modulate texture by base color and add specular contribution + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) * + gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0); +} diff --git a/interface/resources/shaders/model_specular_map.frag b/interface/resources/shaders/model_specular_map.frag new file mode 100644 index 0000000000..972a8e2de6 --- /dev/null +++ b/interface/resources/shaders/model_specular_map.frag @@ -0,0 +1,35 @@ +#version 120 + +// +// model_specular_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/6/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the specular texture +uniform sampler2D specularMap; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the base color based on OpenGL lighting model + vec4 normalizedNormal = normalize(normal); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalizedNormal, gl_LightSource[0].position))); + + // compute the specular component (sans exponent) + float specular = max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), normalizedNormal)); + + // modulate texture by base color and add specular contribution + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) * + gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0); +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d9cc78304f..61666ccdeb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -151,6 +151,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _lastQueriedTime(usecTimestampNow()), _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)), _cameraPushback(0.0f), + _scaleMirror(1.0f), _mouseX(0), _mouseY(0), _lastMouseMove(usecTimestampNow()), @@ -570,9 +571,9 @@ void Application::paintGL() { _myCamera.setTightness(0.0f); glm::vec3 eyePosition = _myAvatar->getHead()->calculateAverageEyePosition(); float headHeight = eyePosition.y - _myAvatar->getPosition().y; - _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale()); - _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight, 0)); - _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); + _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale() * _scaleMirror); + _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight + (_raiseMirror * _myAvatar->getScale()), 0)); + _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); // if the head would intersect the near clip plane, we must push the camera out glm::vec3 relativePosition = glm::inverse(_myCamera.getTargetRotation()) * @@ -868,19 +869,43 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_Up: - _myAvatar->setDriveKeys(isShifted ? UP : FWD, 1.f); + if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { + if (!isShifted) { + _scaleMirror *= 0.95f; + } else { + _raiseMirror += 0.05f; + } + } else { + _myAvatar->setDriveKeys(isShifted ? UP : FWD, 1.f); + } break; case Qt::Key_Down: - _myAvatar->setDriveKeys(isShifted ? DOWN : BACK, 1.f); + if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { + if (!isShifted) { + _scaleMirror *= 1.05f; + } else { + _raiseMirror -= 0.05f; + } + } else { + _myAvatar->setDriveKeys(isShifted ? DOWN : BACK, 1.f); + } break; case Qt::Key_Left: - _myAvatar->setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1.f); + if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { + _rotateMirror += PI / 20.f; + } else { + _myAvatar->setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1.f); + } break; case Qt::Key_Right: - _myAvatar->setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1.f); + if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { + _rotateMirror -= PI / 20.f; + } else { + _myAvatar->setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1.f); + } break; case Qt::Key_I: @@ -1192,10 +1217,6 @@ void Application::touchBeginEvent(QTouchEvent* event) { return; } - // put any application specific touch behavior below here.. - _lastTouchAvgX = _touchAvgX; - _lastTouchAvgY = _touchAvgY; - } void Application::touchEndEvent(QTouchEvent* event) { @@ -1850,34 +1871,6 @@ void Application::updateMyAvatarLookAtPosition() { _myAvatar->getHead()->setLookAtPosition(lookAtSpot); } -void Application::updateHandAndTouch(float deltaTime) { - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); - PerformanceWarning warn(showWarnings, "Application::updateHandAndTouch()"); - - // Update from Touch - if (_isTouchPressed) { - _lastTouchAvgX = _touchAvgX; - _lastTouchAvgY = _touchAvgY; - } -} - -void Application::updateLeap(float deltaTime) { - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); - PerformanceWarning warn(showWarnings, "Application::updateLeap()"); -} - -void Application::updateSixense(float deltaTime) { - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); - PerformanceWarning warn(showWarnings, "Application::updateSixense()"); - - _sixenseManager.update(deltaTime); -} - -void Application::updateSerialDevices(float deltaTime) { - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); - PerformanceWarning warn(showWarnings, "Application::updateSerialDevices()"); -} - void Application::updateThreads(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateThreads()"); @@ -1991,11 +1984,7 @@ void Application::update(float deltaTime) { updateVisage(); _myAvatar->updateLookAtTargetAvatar(); updateMyAvatarLookAtPosition(); - - updateHandAndTouch(deltaTime); // Update state for touch sensors - updateLeap(deltaTime); // Leap finger-sensing device - updateSixense(deltaTime); // Razer Hydra controllers - updateSerialDevices(deltaTime); // Read serial port interface devices + _sixenseManager.update(deltaTime); updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... _avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them... @@ -2348,8 +2337,8 @@ void Application::updateShadowMap() { updateUntranslatedViewMatrix(); _avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE); - _particles.render(); - _models.render(); + _particles.render(OctreeRenderer::SHADOW_RENDER_MODE); + _models.render(OctreeRenderer::SHADOW_RENDER_MODE); glPopMatrix(); @@ -2843,7 +2832,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { // save absolute translations glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation(); glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation(); - + // get the eye positions relative to the neck and use them to set the face translation glm::vec3 leftEyePosition, rightEyePosition; _myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3()); @@ -2857,11 +2846,22 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { _myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() - neckPosition); + // update the attachments to match + QVector absoluteAttachmentTranslations; + glm::vec3 delta = _myAvatar->getSkeletonModel().getTranslation() - absoluteSkeletonTranslation; + foreach (Model* attachment, _myAvatar->getAttachmentModels()) { + absoluteAttachmentTranslations.append(attachment->getTranslation()); + attachment->setTranslation(attachment->getTranslation() + delta); + } + displaySide(_mirrorCamera, true); // restore absolute translations _myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation); _myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation); + for (int i = 0; i < absoluteAttachmentTranslations.size(); i++) { + _myAvatar->getAttachmentModels().at(i)->setTranslation(absoluteAttachmentTranslations.at(i)); + } } else { displaySide(_mirrorCamera, true); } diff --git a/interface/src/Application.h b/interface/src/Application.h index a7073ac4e9..91a7ebd29b 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -343,10 +343,6 @@ private: void updateFaceshift(); void updateVisage(); void updateMyAvatarLookAtPosition(); - void updateHandAndTouch(float deltaTime); - void updateLeap(float deltaTime); - void updateSixense(float deltaTime); - void updateSerialDevices(float deltaTime); void updateThreads(float deltaTime); void updateMetavoxels(float deltaTime); void updateCamera(float deltaTime); @@ -455,6 +451,10 @@ private: glm::mat4 _untranslatedViewMatrix; glm::vec3 _viewMatrixTranslation; glm::mat4 _projectionMatrix; + + float _scaleMirror; + float _rotateMirror; + float _raiseMirror; glm::mat4 _shadowMatrix; @@ -473,8 +473,6 @@ private: float _touchAvgX; float _touchAvgY; - float _lastTouchAvgX; - float _lastTouchAvgY; float _touchDragStartedAvgX; float _touchDragStartedAvgY; bool _isTouchPressed; // true if multitouch has been pressed (clear when finished) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 68e38615bf..bef6f4c2da 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -1068,6 +1068,7 @@ void Audio::toggleScope() { memset(_scopeInput.data(), 0, width * sizeof(int16_t)); memset(_scopeOutputLeft.data(), 0, width * sizeof(int16_t)); memset(_scopeOutputRight.data(), 0, width * sizeof(int16_t)); + _scopeEnabledPause = false; } } diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index 287744eba2..56078c1a8d 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -131,6 +131,20 @@ void DatagramProcessor::processDatagrams() { break; } + case PacketTypeMuteEnvironment: { + glm::vec3 position; + float radius; + + int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment); + memcpy(&position, incomingPacket.constData() + headerSize, sizeof(glm::vec3)); + memcpy(&radius, incomingPacket.constData() + headerSize + sizeof(glm::vec3), sizeof(float)); + + if (glm::distance(Application::getInstance()->getAvatar()->getPosition(), position) < radius + && !Application::getInstance()->getAudio()->getMuted()) { + Application::getInstance()->getAudio()->toggleMute(); + } + break; + } default: nodeList->processNodeData(senderSockAddr, incomingPacket); break; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 13b72bcdb3..27e07d747e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -68,6 +68,7 @@ const float DEFAULT_FACESHIFT_EYE_DEFLECTION = 0.25f; const float DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER = 1.0f; const int ONE_SECOND_OF_FRAMES = 60; const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES; +const float MUTE_RADIUS = 50; Menu::Menu() : _actionHash(), @@ -271,9 +272,6 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Bandwidth, 0, true); addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails())); addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, this, SLOT(octreeStatsDetails())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::AudioScope, 0, false, - appInstance->getAudio(), - SLOT(toggleScope())); QMenu* developerMenu = addMenu("Developer"); @@ -291,7 +289,6 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Models, 0, true); addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools())); QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options"); @@ -308,6 +305,12 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DisableAutoAdjustLOD); + QMenu* modelOptionsMenu = developerMenu->addMenu("Model Options"); + addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::Models, 0, true); + addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelBounds, 0, false); + addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelElementProxy, 0, false); + addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelElementChildProxies, 0, false); + QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options"); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true); @@ -370,20 +373,6 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings); addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings); - addCheckableActionToQMenuAndActionHash(renderDebugMenu, - MenuOption::CullSharedFaces, - Qt::CTRL | Qt::SHIFT | Qt::Key_C, - false, - appInstance->getVoxels(), - SLOT(cullSharedFaces())); - - addCheckableActionToQMenuAndActionHash(renderDebugMenu, - MenuOption::ShowCulledSharedFaces, - 0, - false, - appInstance->getVoxels(), - SLOT(showCulledSharedFaces())); - QMenu* audioDebugMenu = developerMenu->addMenu("Audio Debugging Tools"); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, @@ -397,13 +386,21 @@ Menu::Menu() : false, appInstance->getAudio(), SLOT(toggleMute())); + addActionToQMenuAndActionHash(audioDebugMenu, + MenuOption::MuteEnvironment, + 0, + this, + SLOT(muteEnvironment())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioToneInjection, 0, false, appInstance->getAudio(), SLOT(toggleToneInjection())); + addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false, + appInstance->getAudio(), + SLOT(toggleScope())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScopePause, - Qt::CTRL | Qt::Key_P, + Qt::CTRL | Qt::SHIFT | Qt::Key_P , false, appInstance->getAudio(), SLOT(toggleScopePause())); @@ -1000,6 +997,30 @@ void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJson disconnect(manager, &LocationManager::multipleDestinationsFound, this, &Menu::multipleDestinationsDecision); } +void Menu::muteEnvironment() { + int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment); + int packetSize = headerSize + sizeof(glm::vec3) + sizeof(float); + + glm::vec3 position = Application::getInstance()->getAvatar()->getPosition(); + + char* packet = (char*)malloc(packetSize); + populatePacketHeader(packet, PacketTypeMuteEnvironment); + memcpy(packet + headerSize, &position, sizeof(glm::vec3)); + memcpy(packet + headerSize + sizeof(glm::vec3), &MUTE_RADIUS, sizeof(float)); + + QByteArray mutePacket(packet, packetSize); + + // grab our audio mixer from the NodeList, if it exists + SharedNodePointer audioMixer = NodeList::getInstance()->soloNodeOfType(NodeType::AudioMixer); + + if (audioMixer) { + // send off this mute packet + NodeList::getInstance()->writeDatagram(mutePacket, audioMixer); + } + + free(packet); +} + void Menu::goToLocation() { MyAvatar* myAvatar = Application::getInstance()->getAvatar(); glm::vec3 avatarPos = myAvatar->getPosition(); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 723d320905..230584bf07 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -191,6 +191,7 @@ private slots: void audioMuteToggled(); void namedLocationCreated(LocationManager::NamedLocationCreateResponse response); void multipleDestinationsDecision(const QJsonObject& userData, const QJsonObject& placeData); + void muteEnvironment(); private: static Menu* _instance; @@ -296,13 +297,15 @@ namespace MenuOption { const QString CollideWithParticles = "Collide With Particles"; const QString CollideWithVoxels = "Collide With Voxels"; const QString Collisions = "Collisions"; - const QString CullSharedFaces = "Cull Shared Voxel Faces"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DecreaseVoxelSize = "Decrease Voxel Size"; const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD"; const QString DisplayFrustum = "Display Frustum"; const QString DisplayHands = "Display Hands"; const QString DisplayHandTargets = "Display Hand Targets"; + const QString DisplayModelBounds = "Display Model Bounds"; + const QString DisplayModelElementProxy = "Display Model Element Bounds"; + const QString DisplayModelElementChildProxies = "Display Model Element Children"; const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes"; const QString EchoLocalAudio = "Echo Local Audio"; const QString EchoServerAudio = "Echo Server Audio"; @@ -337,8 +340,10 @@ namespace MenuOption { const QString Metavoxels = "Metavoxels"; const QString Mirror = "Mirror"; const QString Models = "Models"; + const QString ModelOptions = "Model Options"; const QString MoveWithLean = "Move with Lean"; const QString MuteAudio = "Mute Microphone"; + const QString MuteEnvironment = "Mute Environment"; const QString NameLocation = "Name this location"; const QString NewVoxelCullingMode = "New Voxel Culling Mode"; const QString OctreeStats = "Voxel and Particle Statistics"; @@ -361,7 +366,6 @@ namespace MenuOption { const QString SettingsExport = "Export Settings"; const QString SettingsImport = "Import Settings"; const QString Shadows = "Shadows"; - const QString ShowCulledSharedFaces = "Show Culled Shared Voxel Faces"; const QString ShowIKConstraints = "Show IK Constraints"; const QString Stars = "Stars"; const QString Stats = "Stats"; diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index 2b86e04829..ce8691998d 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include #include @@ -42,6 +43,9 @@ static const QString TEXDIR_FIELD = "texdir"; static const QString LOD_FIELD = "lod"; static const QString JOINT_INDEX_FIELD = "jointIndex"; static const QString SCALE_FIELD = "scale"; +static const QString TRANSLATION_X_FIELD = "tx"; +static const QString TRANSLATION_Y_FIELD = "ty"; +static const QString TRANSLATION_Z_FIELD = "tz"; static const QString JOINT_FIELD = "joint"; static const QString FREE_JOINT_FIELD = "freeJoint"; @@ -424,19 +428,32 @@ void ModelUploader::processCheck() { } bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geometry) { + QSet added; foreach (FBXMesh mesh, geometry.meshes) { foreach (FBXMeshPart part, mesh.parts) { - if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty()) { + if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() && + !added.contains(part.diffuseTexture.filename)) { if (!addPart(texdir + "/" + part.diffuseTexture.filename, QString("texture%1").arg(++_texturesCount), true)) { return false; } + added.insert(part.diffuseTexture.filename); } - if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty()) { + if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty() && + !added.contains(part.normalTexture.filename)) { if (!addPart(texdir + "/" + part.normalTexture.filename, QString("texture%1").arg(++_texturesCount), true)) { return false; } + added.insert(part.normalTexture.filename); + } + if (!part.specularTexture.filename.isEmpty() && part.specularTexture.content.isEmpty() && + !added.contains(part.specularTexture.filename)) { + if (!addPart(texdir + "/" + part.specularTexture.filename, + QString("texture%1").arg(++_texturesCount), true)) { + return false; + } + added.insert(part.specularTexture.filename); } } } @@ -506,6 +523,14 @@ bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const return true; } +static QDoubleSpinBox* createTranslationBox() { + QDoubleSpinBox* box = new QDoubleSpinBox(); + const double MAX_TRANSLATION = 1000000.0; + box->setMinimum(-MAX_TRANSLATION); + box->setMaximum(MAX_TRANSLATION); + return box; +} + ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, const QString& basePath, const FBXGeometry& geometry) : _modelType(modelType), @@ -527,7 +552,18 @@ ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariant _scale->setMaximum(FLT_MAX); _scale->setSingleStep(0.01); - if (_modelType != ATTACHMENT_MODEL) { + if (_modelType == ATTACHMENT_MODEL) { + QHBoxLayout* translation = new QHBoxLayout(); + form->addRow("Translation:", translation); + translation->addWidget(_translationX = createTranslationBox()); + translation->addWidget(_translationY = createTranslationBox()); + translation->addWidget(_translationZ = createTranslationBox()); + form->addRow("Pivot About Center:", _pivotAboutCenter = new QCheckBox()); + form->addRow("Pivot Joint:", _pivotJoint = createJointBox()); + connect(_pivotAboutCenter, SIGNAL(toggled(bool)), SLOT(updatePivotJoint())); + _pivotAboutCenter->setChecked(true); + + } else { form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox()); form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox()); form->addRow("Neck Joint:", _neckJoint = createJointBox()); @@ -571,7 +607,19 @@ QVariantHash ModelPropertiesDialog::getMapping() const { mapping.insert(JOINT_INDEX_FIELD, jointIndices); QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); - if (_modelType != ATTACHMENT_MODEL) { + if (_modelType == ATTACHMENT_MODEL) { + glm::vec3 pivot; + if (_pivotAboutCenter->isChecked()) { + pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f; + + } else if (_pivotJoint->currentIndex() != 0) { + pivot = extractTranslation(_geometry.joints.at(_pivotJoint->currentIndex() - 1).transform); + } + mapping.insert(TRANSLATION_X_FIELD, -pivot.x * _scale->value() + _translationX->value()); + mapping.insert(TRANSLATION_Y_FIELD, -pivot.y * _scale->value() + _translationY->value()); + mapping.insert(TRANSLATION_Z_FIELD, -pivot.z * _scale->value() + _translationZ->value()); + + } else { insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText()); insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText()); insertJointMapping(joints, "jointNeck", _neckJoint->currentText()); @@ -604,7 +652,14 @@ void ModelPropertiesDialog::reset() { _scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble()); QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); - if (_modelType != ATTACHMENT_MODEL) { + if (_modelType == ATTACHMENT_MODEL) { + _translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble()); + _translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble()); + _translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble()); + _pivotAboutCenter->setChecked(true); + _pivotJoint->setCurrentIndex(0); + + } else { setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString()); setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString()); setJointText(_neckJoint, jointHash.value("jointNeck").toString()); @@ -641,6 +696,10 @@ void ModelPropertiesDialog::chooseTextureDirectory() { _textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1)); } +void ModelPropertiesDialog::updatePivotJoint() { + _pivotJoint->setEnabled(!_pivotAboutCenter->isChecked()); +} + void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) { QWidget* freeJoint = new QWidget(); QHBoxLayout* freeJointLayout = new QHBoxLayout(); diff --git a/interface/src/ModelUploader.h b/interface/src/ModelUploader.h index 499bfad03b..766bd55318 100644 --- a/interface/src/ModelUploader.h +++ b/interface/src/ModelUploader.h @@ -19,6 +19,7 @@ #include "ui/ModelsBrowser.h" +class QCheckBox; class QComboBox; class QDoubleSpinBox; class QFileInfo; @@ -83,6 +84,7 @@ public: private slots: void reset(); void chooseTextureDirectory(); + void updatePivotJoint(); void createNewFreeJoint(const QString& joint = QString()); private: @@ -96,6 +98,11 @@ private: QLineEdit* _name; QPushButton* _textureDirectory; QDoubleSpinBox* _scale; + QDoubleSpinBox* _translationX; + QDoubleSpinBox* _translationY; + QDoubleSpinBox* _translationZ; + QCheckBox* _pivotAboutCenter; + QComboBox* _pivotJoint; QComboBox* _leftEyeJoint; QComboBox* _rightEyeJoint; QComboBox* _neckJoint; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 1b3957322d..bc2bbb6712 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -215,17 +215,20 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { renderBody(renderMode, glowLevel); } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { + if (renderMode != SHADOW_RENDER_MODE && + Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { _skeletonModel.updateShapePositions(); _skeletonModel.renderJointCollisionShapes(0.7f); } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) { + if (renderMode != SHADOW_RENDER_MODE && + Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) { if (shouldRenderHead(cameraPosition, renderMode)) { getHead()->getFaceModel().updateShapePositions(); getHead()->getFaceModel().renderJointCollisionShapes(0.7f); } } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) { + if (renderMode != SHADOW_RENDER_MODE && + Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) { if (shouldRenderHead(cameraPosition, renderMode)) { getHead()->getFaceModel().updateShapePositions(); getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f); @@ -234,7 +237,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { } } // If this is the avatar being looked at, render a little ball above their head - if (_isLookAtTarget) { + if (renderMode != SHADOW_RENDER_MODE &&_isLookAtTarget) { const float LOOK_AT_INDICATOR_RADIUS = 0.03f; const float LOOK_AT_INDICATOR_HEIGHT = 0.60f; const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.5f }; @@ -340,7 +343,8 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { void Avatar::renderBody(RenderMode renderMode, float glowLevel) { Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? - Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + { Glower glower(glowLevel); @@ -351,7 +355,7 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { } _skeletonModel.render(1.0f, modelRenderMode); renderAttachments(modelRenderMode); - getHand()->render(false); + getHand()->render(false, modelRenderMode); } getHead()->render(1.0f, modelRenderMode); } @@ -369,7 +373,7 @@ void Avatar::simulateAttachments(float deltaTime) { glm::quat jointRotation; if (_skeletonModel.getJointPosition(jointIndex, jointPosition) && _skeletonModel.getJointRotation(jointIndex, jointRotation)) { - model->setTranslation(jointPosition + jointRotation * attachment.translation * _skeletonModel.getScale()); + model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale); model->setRotation(jointRotation * attachment.rotation); model->setScale(_skeletonModel.getScale() * attachment.scale); model->simulate(deltaTime); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index b96145b213..edd53e4b8f 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -83,6 +83,7 @@ public: //getters bool isInitialized() const { return _initialized; } SkeletonModel& getSkeletonModel() { return _skeletonModel; } + const QVector& getAttachmentModels() const { return _attachmentModels; } glm::vec3 getChestPosition() const; float getScale() const { return _scale; } const glm::vec3& getVelocity() const { return _velocity; } diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index c925e452b2..a6cd7ff10e 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -192,11 +192,11 @@ void Hand::calculateGeometry() { } } -void Hand::render(bool isMine) { - +void Hand::render(bool isMine, Model::RenderMode renderMode) { _renderAlpha = 1.0; - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { + if (renderMode != Model::SHADOW_RENDER_MODE && + Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { // draw a green sphere at hand joint location, which is actually near the wrist) for (size_t i = 0; i < getNumPalms(); i++) { PalmData& palm = getPalms()[i]; @@ -212,8 +212,8 @@ void Hand::render(bool isMine) { } } - if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayHands)) { - renderLeapHands(isMine); + if (renderMode != Model::SHADOW_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHands)) { + renderHandTargets(isMine); } glEnable(GL_DEPTH_TEST); @@ -221,11 +221,11 @@ void Hand::render(bool isMine) { } -void Hand::renderLeapHands(bool isMine) { +void Hand::renderHandTargets(bool isMine) { const float alpha = 1.0f; - const glm::vec3 handColor(1.0, 0.84, 0.66); // use the skin color + const glm::vec3 handColor(1.0, 0.0, 0.0); // Color the hand targets red to be different than skin glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); @@ -262,23 +262,37 @@ void Hand::renderLeapHands(bool isMine) { glPopMatrix(); } } - - // Draw the finger root cones + + const float PALM_BALL_RADIUS = 0.03f; + const float PALM_DISK_RADIUS = 0.06f; + const float PALM_DISK_THICKNESS = 0.01f; + const float PALM_FINGER_ROD_RADIUS = 0.003f; + + // Draw the palm ball and disk for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; if (palm.isActive()) { for (size_t f = 0; f < palm.getNumFingers(); ++f) { FingerData& finger = palm.getFingers()[f]; if (finger.isActive()) { - glColor4f(handColor.r, handColor.g, handColor.b, 0.5); + glColor4f(handColor.r, handColor.g, handColor.b, alpha); glm::vec3 tip = finger.getTipPosition(); glm::vec3 root = finger.getRootPosition(); - Avatar::renderJointConnectingCone(root, tip, 0.001f, 0.003f); + Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS); + // Render sphere at palm/finger root + glm::vec3 palmNormal = root + palm.getNormal() * PALM_DISK_THICKNESS; + Avatar::renderJointConnectingCone(root, palmNormal, PALM_DISK_RADIUS, 0.0f); + glPushMatrix(); + glTranslatef(root.x, root.y, root.z); + glutSolidSphere(PALM_BALL_RADIUS, 20.0f, 20.0f); + glPopMatrix(); + } } } } + /* // Draw the hand paddles int MAX_NUM_PADDLES = 2; // one for left and one for right glColor4f(handColor.r, handColor.g, handColor.b, 0.3f); @@ -309,6 +323,7 @@ void Hand::renderLeapHands(bool isMine) { Avatar::renderJointConnectingCone(root, tip, HAND_PADDLE_RADIUS, 0.f); } } + */ glDepthMask(GL_TRUE); glEnable(GL_DEPTH_TEST); diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h index 65a7dcb74a..9c2bc2c2c0 100755 --- a/interface/src/avatar/Hand.h +++ b/interface/src/avatar/Hand.h @@ -24,6 +24,7 @@ #include #include "InterfaceConfig.h" +#include "renderer/Model.h" #include "world.h" @@ -52,7 +53,7 @@ public: void init(); void reset(); void simulate(float deltaTime, bool isMine); - void render(bool isMine); + void render(bool isMine, Model::RenderMode renderMode = Model::DEFAULT_RENDER_MODE); // getters const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;} @@ -75,7 +76,7 @@ private: std::vector _leapFingerTipBalls; std::vector _leapFingerRootBalls; - void renderLeapHands(bool isMine); + void renderHandTargets(bool isMine); void renderLeapFingerTrails(); void calculateGeometry(); diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 19aebba25c..2d0599b31f 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -106,16 +106,10 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { const float BROW_LIFT_THRESHOLD = 100.0f; if (_audioAttack > BROW_LIFT_THRESHOLD) { - _browAudioLift += sqrtf(_audioAttack) * 0.00005f; + _browAudioLift += sqrtf(_audioAttack) * 0.01f; } + _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f); - const float CLAMP = 0.01f; - if (_browAudioLift > CLAMP) { - _browAudioLift = CLAMP; - } - - _browAudioLift *= 0.7f; - const float BLINK_SPEED = 10.0f; const float FULLY_OPEN = 0.0f; const float FULLY_CLOSED = 1.0f; @@ -147,12 +141,12 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { } // use data to update fake Faceshift blendshape coefficients - const float BROW_LIFT_SCALE = 500.0f; - const float JAW_OPEN_SCALE = 0.01f; - const float JAW_OPEN_DEAD_ZONE = 0.75f; - Application::getInstance()->getFaceshift()->updateFakeCoefficients(_leftEyeBlink, _rightEyeBlink, - min(1.0f, _browAudioLift * BROW_LIFT_SCALE), glm::clamp(sqrt(_averageLoudness * JAW_OPEN_SCALE) - - JAW_OPEN_DEAD_ZONE, 0.0f, 1.0f), _blendshapeCoefficients); + const float JAW_OPEN_SCALE = 10.f; + Application::getInstance()->getFaceshift()->updateFakeCoefficients(_leftEyeBlink, + _rightEyeBlink, + _browAudioLift, + glm::clamp(log(_averageLoudness) / JAW_OPEN_SCALE, 0.0f, 1.0f), + _blendshapeCoefficients); } if (!isMine) { @@ -182,7 +176,7 @@ void Head::relaxLean(float deltaTime) { } void Head::render(float alpha, Model::RenderMode mode) { - if (_faceModel.render(alpha, mode) && _renderLookatVectors) { + if (_faceModel.render(alpha, mode) && _renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) { renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8e44138679..e1b3fc612c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -102,13 +102,17 @@ void MyAvatar::reset() { setVelocity(glm::vec3(0.0f)); setThrust(glm::vec3(0.0f)); - setOrientation(glm::quat(glm::vec3(0.0f))); + // Reset the pitch and roll components of the avatar's orientation, preserve yaw direction + glm::vec3 eulers = safeEulerAngles(getOrientation()); + eulers.x = 0.f; + eulers.z = 0.f; + setOrientation(glm::quat(eulers)); } void MyAvatar::update(float deltaTime) { Head* head = getHead(); head->relaxLean(deltaTime); - updateFromGyros(deltaTime); + updateFromFaceTracker(deltaTime); if (Menu::getInstance()->isOptionChecked(MenuOption::MoveWithLean)) { // Faceshift drive is enabled, set the avatar drive based on the head position moveWithLean(); @@ -135,7 +139,14 @@ void MyAvatar::simulate(float deltaTime) { } // update the movement of the hand and process handshaking with other avatars... - updateHandMovementAndTouching(deltaTime); + bool pointing = false; + if (_mousePressed) { + _handState = HAND_STATE_GRASPING; + } else if (pointing) { + _handState = HAND_STATE_POINTING; + } else { + _handState = HAND_STATE_NULL; + } updateOrientation(deltaTime); @@ -231,7 +242,7 @@ void MyAvatar::simulate(float deltaTime) { } // Update avatar head rotation with sensor data -void MyAvatar::updateFromGyros(float deltaTime) { +void MyAvatar::updateFromFaceTracker(float deltaTime) { glm::vec3 estimatedPosition, estimatedRotation; FaceTracker* tracker = Application::getInstance()->getActiveFaceTracker(); @@ -340,7 +351,9 @@ void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { return; // exit early } Avatar::render(cameraPosition, renderMode); - if (Menu::getInstance()->isOptionChecked(MenuOption::ShowIKConstraints)) { + + // don't display IK constraints in shadow mode + if (Menu::getInstance()->isOptionChecked(MenuOption::ShowIKConstraints) && renderMode != SHADOW_RENDER_MODE) { _skeletonModel.renderIKConstraints(); } } @@ -589,7 +602,7 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) { getHead()->render(1.0f, modelRenderMode); } - getHand()->render(true); + getHand()->render(true, modelRenderMode); } const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; @@ -910,40 +923,6 @@ void MyAvatar::updateThrust(float deltaTime) { } */ -void MyAvatar::updateHandMovementAndTouching(float deltaTime) { - glm::quat orientation = getOrientation(); - - // reset hand and arm positions according to hand movement - glm::vec3 up = orientation * IDENTITY_UP; - - bool pointing = false; - if (glm::length(_mouseRayDirection) > EPSILON && !Application::getInstance()->isMouseHidden()) { - // confine to the approximate shoulder plane - glm::vec3 pointDirection = _mouseRayDirection; - if (glm::dot(_mouseRayDirection, up) > 0.0f) { - glm::vec3 projectedVector = glm::cross(up, glm::cross(_mouseRayDirection, up)); - if (glm::length(projectedVector) > EPSILON) { - pointDirection = glm::normalize(projectedVector); - } - } - glm::vec3 shoulderPosition; - if (_skeletonModel.getRightShoulderPosition(shoulderPosition)) { - glm::vec3 farVector = _mouseRayOrigin + pointDirection * (float)TREE_SCALE - shoulderPosition; - const float ARM_RETRACTION = 0.75f; - float retractedLength = _skeletonModel.getRightArmLength() * ARM_RETRACTION; - setHandPosition(shoulderPosition + glm::normalize(farVector) * retractedLength); - pointing = true; - } - } - - if (_mousePressed) { - _handState = HAND_STATE_GRASPING; - } else if (pointing) { - _handState = HAND_STATE_POINTING; - } else { - _handState = HAND_STATE_NULL; - } -} void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) { glm::vec3 up = getBodyUpDirection(); @@ -1424,6 +1403,8 @@ void MyAvatar::updateMotionBehaviorsFromMenu() { _motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; // Environmental and Local gravities are incompatible. Environmental setting trumps local. _motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY; + } else { + _motionBehaviors &= ~AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; } if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) { setGravity(glm::vec3(0.0f)); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index ec20f69d6a..a789984b01 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -38,7 +38,7 @@ public: void reset(); void update(float deltaTime); void simulate(float deltaTime); - void updateFromGyros(float deltaTime); + void updateFromFaceTracker(float deltaTime); void moveWithLean(); void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE); @@ -147,7 +147,6 @@ private: float computeMotorTimescale(); void applyMotor(float deltaTime); void applyThrust(float deltaTime); - void updateHandMovementAndTouching(float deltaTime); void updateCollisionWithAvatars(float deltaTime); void updateCollisionWithEnvironment(float deltaTime, float radius); void updateCollisionWithVoxels(float deltaTime, float radius); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index e4c796d3ce..8c21a3240f 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -38,24 +38,23 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { Hand* hand = _owningAvatar->getHand(); hand->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex); - const float HAND_RESTORATION_PERIOD = 1.f; // seconds - float handRestorePercent = glm::clamp(deltaTime / HAND_RESTORATION_PERIOD, 0.f, 1.f); + const float HAND_RESTORATION_RATE = 0.25f; const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (leftPalmIndex == -1) { // no Leap data; set hands from mouse if (_owningAvatar->getHandState() == HAND_STATE_NULL) { - restoreRightHandPosition(handRestorePercent); + restoreRightHandPosition(HAND_RESTORATION_RATE); } else { applyHandPosition(geometry.rightHandJointIndex, _owningAvatar->getHandPosition()); } - restoreLeftHandPosition(handRestorePercent); + restoreLeftHandPosition(HAND_RESTORATION_RATE); } else if (leftPalmIndex == rightPalmIndex) { // right hand only applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices, hand->getPalms()[leftPalmIndex]); - restoreLeftHandPosition(handRestorePercent); + restoreLeftHandPosition(HAND_RESTORATION_RATE); } else { applyPalmData(geometry.leftHandJointIndex, geometry.leftFingerJointIndices, geometry.leftFingertipJointIndices, diff --git a/interface/src/devices/Faceplus.cpp b/interface/src/devices/Faceplus.cpp index f8eec434b7..00bc9d2676 100644 --- a/interface/src/devices/Faceplus.cpp +++ b/interface/src/devices/Faceplus.cpp @@ -220,15 +220,10 @@ void FaceplusReader::update() { if (!_referenceInitialized) { _referenceX = x; _referenceY = y; - _referenceScale = scale; _referenceInitialized = true; } const float TRANSLATION_SCALE = 10.0f; - const float REFERENCE_DISTANCE = 10.0f; - float depthScale = _referenceScale / scale; - float z = REFERENCE_DISTANCE * (depthScale - 1.0f); - glm::vec3 headTranslation((x - _referenceX) * depthScale * TRANSLATION_SCALE, - (y - _referenceY) * depthScale * TRANSLATION_SCALE, z); + glm::vec3 headTranslation((x - _referenceX) * TRANSLATION_SCALE, (y - _referenceY) * TRANSLATION_SCALE, 0.0f); glm::quat headRotation(glm::radians(glm::vec3(-_outputVector.at(_headRotationIndices[0]), _outputVector.at(_headRotationIndices[1]), -_outputVector.at(_headRotationIndices[2])))); float estimatedEyePitch = (_outputVector.at(_leftEyeRotationIndices[0]) + diff --git a/interface/src/devices/Faceplus.h b/interface/src/devices/Faceplus.h index f3c680c2d6..d52740ca5f 100644 --- a/interface/src/devices/Faceplus.h +++ b/interface/src/devices/Faceplus.h @@ -76,7 +76,6 @@ private: int _rightEyeRotationIndices[2]; float _referenceX; float _referenceY; - float _referenceScale; bool _referenceInitialized; QVector _blendshapeCoefficients; #endif diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index f7c00411c1..0435519124 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -30,6 +30,7 @@ const float NECK_Z = 300.f; // millimeters SixenseManager::SixenseManager() { #ifdef HAVE_SIXENSE _lastMovement = 0; + _amountMoved = glm::vec3(0.0f); _calibrationState = CALIBRATION_STATE_IDLE; // By default we assume the _neckBase (in orb frame) is as high above the orb @@ -122,14 +123,21 @@ void SixenseManager::update(float deltaTime) { palm->setRawRotation(rotation); // Compute current velocity from position change - glm::vec3 rawVelocity = (position - palm->getRawPosition()) / deltaTime / 1000.f; + glm::vec3 rawVelocity; + if (deltaTime > 0.f) { + rawVelocity = (position - palm->getRawPosition()) / deltaTime / 1000.f; + } else { + rawVelocity = glm::vec3(0.0f); + } palm->setRawVelocity(rawVelocity); // meters/sec palm->setRawPosition(position); // use the velocity to determine whether there's any movement (if the hand isn't new) - const float MOVEMENT_SPEED_THRESHOLD = 0.05f; - if (glm::length(rawVelocity) > MOVEMENT_SPEED_THRESHOLD && foundHand) { + const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f; + _amountMoved += rawVelocity * deltaTime; + if (glm::length(_amountMoved) > MOVEMENT_DISTANCE_THRESHOLD && foundHand) { _lastMovement = usecTimestampNow(); + _amountMoved = glm::vec3(0.0f); } // initialize the "finger" based on the direction @@ -143,7 +151,11 @@ void SixenseManager::update(float deltaTime) { // Store the one fingertip in the palm structure so we can track velocity glm::vec3 oldTipPosition = palm->getTipRawPosition(); - palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime / 1000.f); + if (deltaTime > 0.f) { + palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime / 1000.f); + } else { + palm->setTipVelocity(glm::vec3(0.f)); + } palm->setTipPosition(newTipPosition); // three fingers indicates to the skeleton that we have enough data to determine direction @@ -158,8 +170,8 @@ void SixenseManager::update(float deltaTime) { } // if the controllers haven't been moved in a while, disable - const unsigned int MOVEMENT_DISABLE_DURATION = 30 * 1000 * 1000; - if (usecTimestampNow() - _lastMovement > MOVEMENT_DISABLE_DURATION) { + const unsigned int MOVEMENT_DISABLE_SECONDS = 3; + if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * 1000 * 1000)) { for (std::vector::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) { it->setActive(false); } diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h index 2fc6b3dcb3..a98d4c0e4e 100644 --- a/interface/src/devices/SixenseManager.h +++ b/interface/src/devices/SixenseManager.h @@ -64,6 +64,7 @@ private: #endif quint64 _lastMovement; + glm::vec3 _amountMoved; }; #endif // hifi_SixenseManager_h diff --git a/interface/src/models/ModelTreeRenderer.cpp b/interface/src/models/ModelTreeRenderer.cpp index 9546c7d1c4..c762182290 100644 --- a/interface/src/models/ModelTreeRenderer.cpp +++ b/interface/src/models/ModelTreeRenderer.cpp @@ -39,8 +39,8 @@ void ModelTreeRenderer::update() { } } -void ModelTreeRenderer::render() { - OctreeRenderer::render(); +void ModelTreeRenderer::render(RenderMode renderMode) { + OctreeRenderer::render(renderMode); } Model* ModelTreeRenderer::getModel(const QString& url) { @@ -66,54 +66,130 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) const QList& modelItems = modelTreeElement->getModels(); uint16_t numberOfModels = modelItems.size(); + + bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE; + + bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds); + bool displayElementProxy = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementProxy); + bool displayElementChildProxies = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelElementChildProxies); + + + if (!isShadowMode && displayElementProxy && numberOfModels > 0) { + glm::vec3 elementCenter = modelTreeElement->getAABox().calcCenter() * (float)TREE_SCALE; + float elementSize = modelTreeElement->getScale() * (float)TREE_SCALE; + glColor3f(1.0f, 0.0f, 0.0f); + glPushMatrix(); + glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z); + glutWireCube(elementSize); + glPopMatrix(); + + if (displayElementChildProxies) { + // draw the children + float halfSize = elementSize / 2.0f; + float quarterSize = elementSize / 4.0f; + glColor3f(1.0f, 1.0f, 0.0f); + glPushMatrix(); + glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize); + glutWireCube(halfSize); + glPopMatrix(); + + glColor3f(1.0f, 0.0f, 1.0f); + glPushMatrix(); + glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z - quarterSize); + glutWireCube(halfSize); + glPopMatrix(); + + glColor3f(0.0f, 1.0f, 0.0f); + glPushMatrix(); + glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize); + glutWireCube(halfSize); + glPopMatrix(); + + glColor3f(0.0f, 0.0f, 1.0f); + glPushMatrix(); + glTranslatef(elementCenter.x - quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize); + glutWireCube(halfSize); + glPopMatrix(); + + glColor3f(1.0f, 1.0f, 1.0f); + glPushMatrix(); + glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize); + glutWireCube(halfSize); + glPopMatrix(); + + glColor3f(0.0f, 0.5f, 0.5f); + glPushMatrix(); + glTranslatef(elementCenter.x - quarterSize, elementCenter.y + quarterSize, elementCenter.z + quarterSize); + glutWireCube(halfSize); + glPopMatrix(); + + glColor3f(0.5f, 0.0f, 0.0f); + glPushMatrix(); + glTranslatef(elementCenter.x + quarterSize, elementCenter.y - quarterSize, elementCenter.z + quarterSize); + glutWireCube(halfSize); + glPopMatrix(); + + glColor3f(0.0f, 0.5f, 0.0f); + glPushMatrix(); + glTranslatef(elementCenter.x + quarterSize, elementCenter.y + quarterSize, elementCenter.z - quarterSize); + glutWireCube(halfSize); + glPopMatrix(); + } + + } for (uint16_t i = 0; i < numberOfModels; i++) { const ModelItem& modelItem = modelItems[i]; // render modelItem aspoints - glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE; - glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]); - float radius = modelItem.getRadius() * (float)TREE_SCALE; - //glm::vec3 center = position + glm::vec3(radius, radius, radius); // center it around the position + AABox modelBox = modelItem.getAABox(); + modelBox.scale(TREE_SCALE); + if (args->_viewFrustum->boxInFrustum(modelBox) != ViewFrustum::OUTSIDE) { + glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE; + float radius = modelItem.getRadius() * (float)TREE_SCALE; + float size = modelItem.getSize() * (float)TREE_SCALE; - bool drawAsModel = modelItem.hasModel(); + bool drawAsModel = modelItem.hasModel(); - args->_renderedItems++; + args->_renderedItems++; - if (drawAsModel) { - glPushMatrix(); - const float alpha = 1.0f; + if (drawAsModel) { + glPushMatrix(); + const float alpha = 1.0f; - Model* model = getModel(modelItem.getModelURL()); + Model* model = getModel(modelItem.getModelURL()); - model->setScaleToFit(true, radius * 2.0f); - model->setSnapModelToCenter(true); + model->setScaleToFit(true, radius * 2.0f); + model->setSnapModelToCenter(true); - // set the rotation - glm::quat rotation = modelItem.getModelRotation(); - model->setRotation(rotation); + // set the rotation + glm::quat rotation = modelItem.getModelRotation(); + model->setRotation(rotation); - // set the position - model->setTranslation(position); + // set the position + model->setTranslation(position); + model->simulate(0.0f); - model->simulate(0.0f); + // TODO: should we allow modelItems to have alpha on their models? + Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE + ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + model->render(alpha, modelRenderMode); + if (!isShadowMode && displayModelBounds) { + glColor3f(0.0f, 1.0f, 0.0f); + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glutWireCube(size); + glPopMatrix(); + } - model->render(alpha); // TODO: should we allow modelItems to have alpha on their models? - - const bool wantDebugSphere = false; - if (wantDebugSphere) { - glPushMatrix(); - glTranslatef(position.x, position.y, position.z); - glutWireSphere(radius, 15, 15); - glPopMatrix(); - } - - glPopMatrix(); - } else { - glPushMatrix(); - glTranslatef(position.x, position.y, position.z); - glutSolidSphere(radius, 15, 15); - glPopMatrix(); + glPopMatrix(); + } else { + glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]); + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glutSolidSphere(radius, 15, 15); + glPopMatrix(); + } } } } diff --git a/interface/src/models/ModelTreeRenderer.h b/interface/src/models/ModelTreeRenderer.h index 5ed4720391..7af5bbf317 100644 --- a/interface/src/models/ModelTreeRenderer.h +++ b/interface/src/models/ModelTreeRenderer.h @@ -46,7 +46,7 @@ public: void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); virtual void init(); - virtual void render(); + virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE); protected: Model* getModel(const QString& url); diff --git a/interface/src/particles/ParticleTreeRenderer.cpp b/interface/src/particles/ParticleTreeRenderer.cpp index aa498082d9..2983093564 100644 --- a/interface/src/particles/ParticleTreeRenderer.cpp +++ b/interface/src/particles/ParticleTreeRenderer.cpp @@ -39,8 +39,8 @@ void ParticleTreeRenderer::update() { } } -void ParticleTreeRenderer::render() { - OctreeRenderer::render(); +void ParticleTreeRenderer::render(RenderMode renderMode) { + OctreeRenderer::render(renderMode); } Model* ParticleTreeRenderer::getModel(const QString& url) { @@ -102,7 +102,11 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg model->setScale(scale * MODEL_SCALE * radius * modelScale); model->simulate(0.0f); - model->render(alpha); // TODO: should we allow particles to have alpha on their models? + + // TODO: should we allow particles to have alpha on their models? + Model::RenderMode modelRenderMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE + ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + model->render(alpha, modelRenderMode); const bool wantDebugSphere = false; if (wantDebugSphere) { diff --git a/interface/src/particles/ParticleTreeRenderer.h b/interface/src/particles/ParticleTreeRenderer.h index ea52df3932..ccb8bfbdf3 100644 --- a/interface/src/particles/ParticleTreeRenderer.h +++ b/interface/src/particles/ParticleTreeRenderer.h @@ -43,7 +43,7 @@ public: void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); virtual void init(); - virtual void render(); + virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE); protected: Model* getModel(const QString& url); diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 6e93fc77af..8d31cdce1d 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -342,7 +342,8 @@ bool NetworkGeometry::isLoadedWithTextures() const { foreach (const NetworkMesh& mesh, _meshes) { foreach (const NetworkMeshPart& part, mesh.parts) { if ((part.diffuseTexture && !part.diffuseTexture->isLoaded()) || - (part.normalTexture && !part.normalTexture->isLoaded())) { + (part.normalTexture && !part.normalTexture->isLoaded()) || + (part.specularTexture && !part.specularTexture->isLoaded())) { return false; } } @@ -416,6 +417,9 @@ void NetworkGeometry::setLoadPriority(const QPointer& owner, float prio if (part.normalTexture) { part.normalTexture->setLoadPriority(owner, priority); } + if (part.specularTexture) { + part.specularTexture->setLoadPriority(owner, priority); + } } } } @@ -433,6 +437,9 @@ void NetworkGeometry::setLoadPriorities(const QHash, float>& p if (part.normalTexture) { part.normalTexture->setLoadPriorities(priorities); } + if (part.specularTexture) { + part.specularTexture->setLoadPriorities(priorities); + } } } } @@ -450,6 +457,9 @@ void NetworkGeometry::clearLoadPriority(const QPointer& owner) { if (part.normalTexture) { part.normalTexture->clearLoadPriority(owner); } + if (part.specularTexture) { + part.specularTexture->clearLoadPriority(owner); + } } } } @@ -566,6 +576,11 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) { _textureBase.resolved(QUrl(part.normalTexture.filename)), true, false, part.normalTexture.content); networkPart.normalTexture->setLoadPriorities(_loadPriorities); } + if (!part.specularTexture.filename.isEmpty()) { + networkPart.specularTexture = Application::getInstance()->getTextureCache()->getTexture( + _textureBase.resolved(QUrl(part.specularTexture.filename)), true, false, part.specularTexture.content); + networkPart.specularTexture->setLoadPriorities(_loadPriorities); + } networkMesh.parts.append(networkPart); totalIndices += (part.quadIndices.size() + part.triangleIndices.size()); diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index 0ad4f73904..a9b274fedc 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -124,6 +124,7 @@ public: QSharedPointer diffuseTexture; QSharedPointer normalTexture; + QSharedPointer specularTexture; bool isTranslucent() const; }; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index f6aa3e9354..f46fd48beb 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -56,13 +56,20 @@ Model::~Model() { ProgramObject Model::_program; ProgramObject Model::_normalMapProgram; +ProgramObject Model::_specularMapProgram; +ProgramObject Model::_normalSpecularMapProgram; ProgramObject Model::_shadowProgram; ProgramObject Model::_skinProgram; ProgramObject Model::_skinNormalMapProgram; +ProgramObject Model::_skinSpecularMapProgram; +ProgramObject Model::_skinNormalSpecularMapProgram; ProgramObject Model::_skinShadowProgram; int Model::_normalMapTangentLocation; +int Model::_normalSpecularMapTangentLocation; Model::SkinLocations Model::_skinLocations; Model::SkinLocations Model::_skinNormalMapLocations; +Model::SkinLocations Model::_skinSpecularMapLocations; +Model::SkinLocations Model::_skinNormalSpecularMapLocations; Model::SkinLocations Model::_skinShadowLocations; void Model::setScale(const glm::vec3& scale) { @@ -92,7 +99,7 @@ void Model::setOffset(const glm::vec3& offset) { } -void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) { +void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations, int specularTextureUnit) { program.bind(); locations.clusterMatrices = program.uniformLocation("clusterMatrices"); locations.clusterIndices = program.attributeLocation("clusterIndices"); @@ -100,6 +107,7 @@ void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locati locations.tangent = program.attributeLocation("tangent"); program.setUniformValue("diffuseMap", 0); program.setUniformValue("normalMap", 1); + program.setUniformValue("specularMap", specularTextureUnit); program.release(); } @@ -162,10 +170,10 @@ void Model::init() { _program.setUniformValue("texture", 0); _program.release(); - _normalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() - + "shaders/model_normal_map.vert"); - _normalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() - + "shaders/model_normal_map.frag"); + _normalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/model_normal_map.vert"); + _normalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_normal_map.frag"); _normalMapProgram.link(); _normalMapProgram.bind(); @@ -174,27 +182,65 @@ void Model::init() { _normalMapTangentLocation = _normalMapProgram.attributeLocation("tangent"); _normalMapProgram.release(); + _specularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/model.vert"); + _specularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_specular_map.frag"); + _specularMapProgram.link(); + + _specularMapProgram.bind(); + _specularMapProgram.setUniformValue("diffuseMap", 0); + _specularMapProgram.setUniformValue("specularMap", 1); + _specularMapProgram.release(); + + _normalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/model_normal_map.vert"); + _normalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_normal_specular_map.frag"); + _normalSpecularMapProgram.link(); + + _normalSpecularMapProgram.bind(); + _normalSpecularMapProgram.setUniformValue("diffuseMap", 0); + _normalSpecularMapProgram.setUniformValue("normalMap", 1); + _normalSpecularMapProgram.setUniformValue("specularMap", 2); + _normalSpecularMapTangentLocation = _normalMapProgram.attributeLocation("tangent"); + _normalSpecularMapProgram.release(); + _shadowProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/model_shadow.vert"); - _shadowProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + - "shaders/model_shadow.frag"); + _shadowProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_shadow.frag"); _shadowProgram.link(); - _skinProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() - + "shaders/skin_model.vert"); - _skinProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() - + "shaders/model.frag"); + _skinProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/skin_model.vert"); + _skinProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model.frag"); _skinProgram.link(); initSkinProgram(_skinProgram, _skinLocations); - _skinNormalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() - + "shaders/skin_model_normal_map.vert"); - _skinNormalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() - + "shaders/model_normal_map.frag"); + _skinNormalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/skin_model_normal_map.vert"); + _skinNormalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_normal_map.frag"); _skinNormalMapProgram.link(); initSkinProgram(_skinNormalMapProgram, _skinNormalMapLocations); + _skinSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/skin_model.vert"); + _skinSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_specular_map.frag"); + _skinSpecularMapProgram.link(); + + initSkinProgram(_skinSpecularMapProgram, _skinSpecularMapLocations); + + _skinNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/skin_model_normal_map.vert"); + _skinNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_normal_specular_map.frag"); + _skinNormalSpecularMapProgram.link(); + + initSkinProgram(_skinNormalSpecularMapProgram, _skinNormalSpecularMapLocations, 2); + _skinShadowProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/skin_model_shadow.vert"); _skinShadowProgram.addShaderFromSourceFile(QGLShader::Fragment, @@ -1331,15 +1377,29 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { ProgramObject* program = &_program; ProgramObject* skinProgram = &_skinProgram; SkinLocations* skinLocations = &_skinLocations; + GLenum specularTextureUnit = 0; if (mode == SHADOW_RENDER_MODE) { program = &_shadowProgram; skinProgram = &_skinShadowProgram; skinLocations = &_skinShadowLocations; } else if (!mesh.tangents.isEmpty()) { - program = &_normalMapProgram; - skinProgram = &_skinNormalMapProgram; - skinLocations = &_skinNormalMapLocations; + if (mesh.hasSpecularTexture()) { + program = &_normalSpecularMapProgram; + skinProgram = &_skinNormalSpecularMapProgram; + skinLocations = &_skinNormalSpecularMapLocations; + specularTextureUnit = GL_TEXTURE2; + + } else { + program = &_normalMapProgram; + skinProgram = &_skinNormalMapProgram; + skinLocations = &_skinNormalMapLocations; + } + } else if (mesh.hasSpecularTexture()) { + program = &_specularMapProgram; + skinProgram = &_skinSpecularMapProgram; + skinLocations = &_skinSpecularMapLocations; + specularTextureUnit = GL_TEXTURE1; } const MeshState& state = _meshStates.at(i); @@ -1427,13 +1487,23 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { glBindTexture(GL_TEXTURE_2D, !diffuseMap ? Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID()); + if (!mesh.tangents.isEmpty()) { + specularTextureUnit = GL_TEXTURE2; glActiveTexture(GL_TEXTURE1); Texture* normalMap = networkPart.normalTexture.data(); glBindTexture(GL_TEXTURE_2D, !normalMap ? Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID()); glActiveTexture(GL_TEXTURE0); } + + if (specularTextureUnit) { + glActiveTexture(specularTextureUnit); + Texture* specularMap = networkPart.specularTexture.data(); + glBindTexture(GL_TEXTURE_2D, !specularMap ? + Application::getInstance()->getTextureCache()->getWhiteTextureID() : specularMap->getID()); + glActiveTexture(GL_TEXTURE0); + } } glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset); offset += part.quadIndices.size() * sizeof(int); @@ -1456,7 +1526,13 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { activeProgram->disableAttributeArray(tangentLocation); } - + + if (specularTextureUnit) { + glActiveTexture(specularTextureUnit); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + } + if (state.clusterMatrices.size() > 1) { skinProgram->disableAttributeArray(skinLocations->clusterIndices); skinProgram->disableAttributeArray(skinLocations->clusterWeights); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 6a79772ca7..1a469c8122 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -318,12 +318,17 @@ private: static ProgramObject _program; static ProgramObject _normalMapProgram; + static ProgramObject _specularMapProgram; + static ProgramObject _normalSpecularMapProgram; static ProgramObject _shadowProgram; static ProgramObject _skinProgram; static ProgramObject _skinNormalMapProgram; + static ProgramObject _skinSpecularMapProgram; + static ProgramObject _skinNormalSpecularMapProgram; static ProgramObject _skinShadowProgram; static int _normalMapTangentLocation; + static int _normalSpecularMapTangentLocation; class SkinLocations { public: @@ -335,9 +340,11 @@ private: static SkinLocations _skinLocations; static SkinLocations _skinNormalMapLocations; + static SkinLocations _skinSpecularMapLocations; + static SkinLocations _skinNormalSpecularMapLocations; static SkinLocations _skinShadowLocations; - static void initSkinProgram(ProgramObject& program, SkinLocations& locations); + static void initSkinProgram(ProgramObject& program, SkinLocations& locations, int specularTextureUnit = 1); }; Q_DECLARE_METATYPE(QPointer) diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index edd28d461c..016098699b 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -37,6 +37,7 @@ AttachmentsDialog::AttachmentsDialog() : container->setLayout(_attachments = new QVBoxLayout()); container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); area->setWidget(container); + _attachments->addStretch(1); foreach (const AttachmentData& data, Application::getInstance()->getAvatar()->getAttachmentData()) { addAttachment(data); @@ -49,20 +50,30 @@ AttachmentsDialog::AttachmentsDialog() : QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); layout->addWidget(buttons); connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); + _ok = buttons->button(QDialogButtonBox::Ok); setMinimumSize(600, 600); } +void AttachmentsDialog::setVisible(bool visible) { + QDialog::setVisible(visible); + + // un-default the OK button + if (visible) { + _ok->setDefault(false); + } +} + void AttachmentsDialog::updateAttachmentData() { QVector data; - for (int i = 0; i < _attachments->count(); i++) { + for (int i = 0; i < _attachments->count() - 1; i++) { data.append(static_cast(_attachments->itemAt(i)->widget())->getAttachmentData()); } Application::getInstance()->getAvatar()->setAttachmentData(data); } void AttachmentsDialog::addAttachment(const AttachmentData& data) { - _attachments->addWidget(new AttachmentPanel(this, data)); + _attachments->insertWidget(_attachments->count() - 1, new AttachmentPanel(this, data)); } static QDoubleSpinBox* createTranslationBox(AttachmentsDialog* dialog, float value) { @@ -86,7 +97,10 @@ static QDoubleSpinBox* createRotationBox(AttachmentsDialog* dialog, float value) } AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) { + setFrameStyle(QFrame::StyledPanel); + QFormLayout* layout = new QFormLayout(); + layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); setLayout(layout); QHBoxLayout* urlBox = new QHBoxLayout(); @@ -130,6 +144,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); + dialog->connect(remove, SIGNAL(clicked(bool)), SLOT(updateAttachmentData()), Qt::QueuedConnection); } AttachmentData AttachmentPanel::getAttachmentData() const { diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h index c23bd2efb8..4e67ae8882 100644 --- a/interface/src/ui/AttachmentsDialog.h +++ b/interface/src/ui/AttachmentsDialog.h @@ -13,6 +13,7 @@ #define hifi_AttachmentsDialog_h #include +#include #include @@ -29,6 +30,8 @@ public: AttachmentsDialog(); + virtual void setVisible(bool visible); + public slots: void updateAttachmentData(); @@ -40,10 +43,11 @@ private slots: private: QVBoxLayout* _attachments; + QPushButton* _ok; }; /// A panel controlling a single attachment. -class AttachmentPanel : public QWidget { +class AttachmentPanel : public QFrame { Q_OBJECT public: diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index 9a54a08619..8937cef7dd 100644 --- a/interface/src/voxels/VoxelSystem.cpp +++ b/interface/src/voxels/VoxelSystem.cpp @@ -1711,38 +1711,6 @@ bool VoxelSystem::inspectForExteriorOcclusionsOperation(OctreeElement* element, return true; } - -void VoxelSystem::cullSharedFaces() { - - if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) { - _useVoxelShader = false; - _usePrimitiveRenderer = true; - inspectForOcclusions(); - } else { - _usePrimitiveRenderer = false; - clearAllNodesBufferIndex(); - } - _writeRenderFullVBO = true; - _tree->setDirtyBit(); - setupNewVoxelsForDrawing(); -} - -void VoxelSystem::showCulledSharedFaces() { - - _tree->lockForRead(); - if (Menu::getInstance()->isOptionChecked(MenuOption::ShowCulledSharedFaces)) { - _showCulledSharedFaces = true; - } else { - _showCulledSharedFaces = false; - } - _tree->unlock(); - if (Menu::getInstance()->isOptionChecked(MenuOption::CullSharedFaces)) { - _writeRenderFullVBO = true; - _tree->setDirtyBit(); - setupNewVoxelsForDrawing(); - } -} - void VoxelSystem::inspectForOcclusions() { if (_inOcclusions) { @@ -2032,7 +2000,7 @@ bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData // if this node is fully OUTSIDE the view, but previously intersected and/or was inside the last view, then // we need to hide it. Additionally we know that ALL of it's children are also fully OUTSIDE so we can recurse // the children and simply mark them as hidden - args->tree->recurseNodeWithOperation(voxel, hideAllSubTreeOperation, args ); + args->tree->recurseElementWithOperation(voxel, hideAllSubTreeOperation, args ); return false; } break; @@ -2049,7 +2017,7 @@ bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData // if this node is fully INSIDE the view, but previously INTERSECTED and/or was OUTSIDE the last view, then // we need to show it. Additionally we know that ALL of it's children are also fully INSIDE so we can recurse // the children and simply mark them as visible (as appropriate based on LOD) - args->tree->recurseNodeWithOperation(voxel, showAllSubTreeOperation, args); + args->tree->recurseElementWithOperation(voxel, showAllSubTreeOperation, args); return false; } break; case ViewFrustum::INTERSECT: { diff --git a/interface/src/voxels/VoxelSystem.h b/interface/src/voxels/VoxelSystem.h index b134fe1539..15e2b20a75 100644 --- a/interface/src/voxels/VoxelSystem.h +++ b/interface/src/voxels/VoxelSystem.h @@ -95,9 +95,7 @@ public slots: // Methods that recurse tree void forceRedrawEntireTree(); void clearAllNodesBufferIndex(); - void cullSharedFaces(); - void showCulledSharedFaces(); - + void setDisableFastVoxelPipeline(bool disableFastVoxelPipeline); void setUseVoxelShader(bool useVoxelShader); void setVoxelsAsPoints(bool voxelsAsPoints); diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index eed41ac849..1fe9f1336f 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -21,9 +21,19 @@ #include "AudioInjector.h" +AudioInjector::AudioInjector(QObject* parent) : + QObject(parent), + _sound(NULL), + _options(), + _shouldStop(false) +{ + +} + AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions) : _sound(sound), - _options(injectorOptions) + _options(injectorOptions), + _shouldStop(false) { } @@ -80,7 +90,7 @@ void AudioInjector::injectAudio() { int numPreAudioDataBytes = injectAudioPacket.size(); // loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks - while (currentSendPosition < soundByteArray.size()) { + while (currentSendPosition < soundByteArray.size() && !_shouldStop) { int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL, soundByteArray.size() - currentSendPosition); diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index abaa804fb0..08fe544255 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -24,14 +24,19 @@ class AudioInjector : public QObject { Q_OBJECT public: + AudioInjector(QObject* parent); AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions); +public slots: + void injectAudio(); + void stop() { _shouldStop = true; } +signals: + void finished(); private: Sound* _sound; AudioInjectorOptions _options; -public slots: - void injectAudio(); -signals: - void finished(); + bool _shouldStop; }; +Q_DECLARE_METATYPE(AudioInjector*) + #endif // hifi_AudioInjector_h diff --git a/libraries/audio/src/AudioScriptingInterface.cpp b/libraries/audio/src/AudioScriptingInterface.cpp index 0d76a42757..fa0d3a9565 100644 --- a/libraries/audio/src/AudioScriptingInterface.cpp +++ b/libraries/audio/src/AudioScriptingInterface.cpp @@ -11,7 +11,7 @@ #include "AudioScriptingInterface.h" -void AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions* injectorOptions) { +AudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions* injectorOptions) { AudioInjector* injector = new AudioInjector(sound, *injectorOptions); @@ -28,6 +28,18 @@ void AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions connect(injectorThread, SIGNAL(finished()), injectorThread, SLOT(deleteLater())); injectorThread->start(); + + return injector; +} + +void AudioScriptingInterface::stopInjector(AudioInjector* injector) { + if (injector) { + injector->stop(); + } +} + +bool AudioScriptingInterface::isInjectorPlaying(AudioInjector* injector) { + return (injector != NULL); } void AudioScriptingInterface::startDrumSound(float volume, float frequency, float duration, float decay, diff --git a/libraries/audio/src/AudioScriptingInterface.h b/libraries/audio/src/AudioScriptingInterface.h index f2e9b02e9a..343eac304c 100644 --- a/libraries/audio/src/AudioScriptingInterface.h +++ b/libraries/audio/src/AudioScriptingInterface.h @@ -20,7 +20,9 @@ const AudioInjectorOptions DEFAULT_INJECTOR_OPTIONS; class AudioScriptingInterface : public QObject { Q_OBJECT public slots: - static void playSound(Sound* sound, const AudioInjectorOptions* injectorOptions = NULL); + static AudioInjector* playSound(Sound* sound, const AudioInjectorOptions* injectorOptions = NULL); + static void stopInjector(AudioInjector* injector); + static bool isInjectorPlaying(AudioInjector* injector); static void startDrumSound(float volume, float frequency, float duration, float decay, const AudioInjectorOptions* injectorOptions = NULL); diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 70da363267..6fc16c57a9 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -89,7 +89,7 @@ void PositionalAudioRingBuffer::updateNextOutputTrailingLoudness() { const int TRAILING_AVERAGE_FRAMES = 100; const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; - const float LOUDNESS_EPSILON = 0.01f; + const float LOUDNESS_EPSILON = 0.000001f; if (nextLoudness >= _nextOutputTrailingLoudness) { _nextOutputTrailingLoudness = nextLoudness; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index a21ed2627a..1fc03ceb66 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -54,6 +54,15 @@ void Extents::addPoint(const glm::vec3& point) { maximum = glm::max(maximum, point); } +bool FBXMesh::hasSpecularTexture() const { + foreach (const FBXMeshPart& part, parts) { + if (!part.specularTexture.filename.isEmpty()) { + return true; + } + } + return false; +} + QStringList FBXGeometry::getJointNames() const { QStringList names; foreach (const FBXJoint& joint, joints) { @@ -963,6 +972,18 @@ FBXTexture getTexture(const QString& textureID, const QHash return texture; } +bool checkMaterialsHaveTextures(const QHash& materials, + const QHash& textureFilenames, const QMultiHash& childMap) { + foreach (const QString& materialID, materials.keys()) { + foreach (const QString& childID, childMap.values(materialID)) { + if (textureFilenames.contains(childID)) { + return true; + } + } + } + return false; +} + FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { QHash meshes; QVector blendshapes; @@ -976,6 +997,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QHash materials; QHash diffuseTextures; QHash bumpTextures; + QHash specularTextures; QHash localRotations; QHash xComponents; QHash yComponents; @@ -1330,6 +1352,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } else if (type.contains("bump") || type.contains("normal")) { bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("specular")) { + specularTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type == "lcl rotation") { localRotations.insert(getID(connection.properties, 2), getID(connection.properties, 1)); @@ -1502,6 +1527,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) geometry.bindExtents.reset(); geometry.meshExtents.reset(); + // see if any materials have texture children + bool materialsHaveTextures = checkMaterialsHaveTextures(materials, textureFilenames, childMap); + for (QHash::iterator it = meshes.begin(); it != meshes.end(); it++) { ExtractedMesh& extracted = it.value(); @@ -1546,6 +1574,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) generateTangents = true; } + FBXTexture specularTexture; + QString specularTextureID = specularTextures.value(childID); + if (!specularTextureID.isNull()) { + specularTexture = getTexture(specularTextureID, textureFilenames, textureContent); + } + for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { if (extracted.partMaterialTextures.at(j).first == materialIndex) { FBXMeshPart& part = extracted.mesh.parts[j]; @@ -1558,6 +1592,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) if (!normalTexture.filename.isNull()) { part.normalTexture = normalTexture; } + if (!specularTexture.filename.isNull()) { + part.specularTexture = specularTexture; + } } } materialIndex++; @@ -1565,7 +1602,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } else if (textureFilenames.contains(childID)) { FBXTexture texture = getTexture(childID, textureFilenames, textureContent); for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { - if (extracted.partMaterialTextures.at(j).second == textureIndex) { + int partTexture = extracted.partMaterialTextures.at(j).second; + if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) { extracted.mesh.parts[j].diffuseTexture = texture; } } diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index a4b04825ef..51e7380181 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -129,6 +129,7 @@ public: FBXTexture diffuseTexture; FBXTexture normalTexture; + FBXTexture specularTexture; }; /// A single mesh (with optional blendshapes) extracted from an FBX document. @@ -150,6 +151,8 @@ public: bool isEye; QVector blendshapes; + + bool hasSpecularTexture() const; }; /// A single animation frame extracted from an FBX document. diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index 317299be2d..9edcf482c0 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -46,7 +46,7 @@ const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048; const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE; const float MINIMUM_MODEL_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container const QString MODEL_DEFAULT_MODEL_URL(""); -const glm::quat MODEL_DEFAULT_MODEL_ROTATION(0, 0, 0, 0); +const glm::quat MODEL_DEFAULT_MODEL_ROTATION; /// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model /// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of @@ -85,6 +85,9 @@ public: /// used by ModelScriptingInterface to return ModelItemProperties for unknown models void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; } + + glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); } + glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); } private: glm::vec3 _position; @@ -156,11 +159,20 @@ public: /// get position in domain scale units (0.0 - 1.0) const glm::vec3& getPosition() const { return _position; } + glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); } + glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); } + const rgbColor& getColor() const { return _color; } xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } /// get radius in domain scale units (0.0 - 1.0) float getRadius() const { return _radius; } + + /// get maximum dimension in domain scale units (0.0 - 1.0) + float getSize() const { return _radius * 2.0f; } + + /// get maximum dimension in domain scale units (0.0 - 1.0) + AABox getAABox() const { return AABox(getMinimumPoint(), getSize()); } // model related properties bool hasModel() const { return !_modelURL.isEmpty(); } diff --git a/libraries/models/src/ModelTree.cpp b/libraries/models/src/ModelTree.cpp index 236138e2a8..45694b081d 100644 --- a/libraries/models/src/ModelTree.cpp +++ b/libraries/models/src/ModelTree.cpp @@ -12,7 +12,7 @@ #include "ModelTree.h" ModelTree::ModelTree(bool shouldReaverage) : Octree(shouldReaverage) { - _rootNode = createNewElement(); + _rootElement = createNewElement(); } ModelTreeElement* ModelTree::createNewElement(unsigned char * octalCode) { @@ -74,71 +74,98 @@ bool ModelTree::findAndDeleteOperation(OctreeElement* element, void* extraData) return true; } - -class FindAndUpdateModelArgs { +class FindAndUpdateModelOperator : public RecurseOctreeOperator { public: - const ModelItem& searchModel; - bool found; + FindAndUpdateModelOperator(const ModelItem& searchModel); + virtual bool PreRecursion(OctreeElement* element); + virtual bool PostRecursion(OctreeElement* element); + bool wasFound() const { return _found; } +private: + const ModelItem& _searchModel; + bool _found; }; -bool ModelTree::findAndUpdateOperation(OctreeElement* element, void* extraData) { - FindAndUpdateModelArgs* args = static_cast(extraData); +FindAndUpdateModelOperator::FindAndUpdateModelOperator(const ModelItem& searchModel) : + _searchModel(searchModel), + _found(false) { +}; + +bool FindAndUpdateModelOperator::PreRecursion(OctreeElement* element) { ModelTreeElement* modelTreeElement = static_cast(element); // Note: updateModel() will only operate on correctly found models - if (modelTreeElement->updateModel(args->searchModel)) { - args->found = true; + if (modelTreeElement->updateModel(_searchModel)) { + _found = true; return false; // stop searching } - return true; + + return !_found; // if we haven't yet found it, keep looking +} + +bool FindAndUpdateModelOperator::PostRecursion(OctreeElement* element) { + if (_found) { + element->markWithChangedTime(); + } + return !_found; // if we haven't yet found it, keep looking } void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& senderNode) { // First, look for the existing model in the tree.. - FindAndUpdateModelArgs args = { model, false }; - recurseTreeWithOperation(findAndUpdateOperation, &args); - + FindAndUpdateModelOperator theOperator(model); + recurseTreeWithOperator(&theOperator); + // if we didn't find it in the tree, then store it... - if (!args.found) { - glm::vec3 position = model.getPosition(); - float size = std::max(MINIMUM_MODEL_ELEMENT_SIZE, model.getRadius()); - - ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size); + if (!theOperator.wasFound()) { + AABox modelBox = model.getAABox(); + ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementContaining(model.getAABox()); element->storeModel(model); } // what else do we need to do here to get reaveraging to work _isDirty = true; } -class FindAndUpdateModelWithIDandPropertiesArgs { + +class FindAndUpdateModelWithIDandPropertiesOperator : public RecurseOctreeOperator { public: - const ModelItemID& modelID; - const ModelItemProperties& properties; - bool found; + FindAndUpdateModelWithIDandPropertiesOperator(const ModelItemID& modelID, const ModelItemProperties& properties); + virtual bool PreRecursion(OctreeElement* element); + virtual bool PostRecursion(OctreeElement* element); + bool wasFound() const { return _found; } +private: + const ModelItemID& _modelID; + const ModelItemProperties& _properties; + bool _found; }; -bool ModelTree::findAndUpdateWithIDandPropertiesOperation(OctreeElement* element, void* extraData) { - FindAndUpdateModelWithIDandPropertiesArgs* args = static_cast(extraData); +FindAndUpdateModelWithIDandPropertiesOperator::FindAndUpdateModelWithIDandPropertiesOperator(const ModelItemID& modelID, + const ModelItemProperties& properties) : + _modelID(modelID), + _properties(properties), + _found(false) { +}; + +bool FindAndUpdateModelWithIDandPropertiesOperator::PreRecursion(OctreeElement* element) { ModelTreeElement* modelTreeElement = static_cast(element); + // Note: updateModel() will only operate on correctly found models - if (modelTreeElement->updateModel(args->modelID, args->properties)) { - args->found = true; + if (modelTreeElement->updateModel(_modelID, _properties)) { + _found = true; return false; // stop searching } + return !_found; // if we haven't yet found it, keep looking +} - // if we've found our model stop searching - if (args->found) { - return false; +bool FindAndUpdateModelWithIDandPropertiesOperator::PostRecursion(OctreeElement* element) { + if (_found) { + element->markWithChangedTime(); } - - return true; + return !_found; // if we haven't yet found it, keep looking } void ModelTree::updateModel(const ModelItemID& modelID, const ModelItemProperties& properties) { - // First, look for the existing model in the tree.. - FindAndUpdateModelWithIDandPropertiesArgs args = { modelID, properties, false }; - recurseTreeWithOperation(findAndUpdateWithIDandPropertiesOperation, &args); - // if we found it in the tree, then mark the tree as dirty - if (args.found) { + // Look for the existing model in the tree.. + FindAndUpdateModelWithIDandPropertiesOperator theOperator(modelID, properties); + recurseTreeWithOperator(&theOperator); + if (theOperator.wasFound()) { _isDirty = true; } } @@ -467,21 +494,21 @@ void ModelTree::update() { ModelTreeUpdateArgs args = { }; recurseTreeWithOperation(updateOperation, &args); - // now add back any of the models that moved elements.... + // now add back any of the particles that moved elements.... int movingModels = args._movingModels.size(); for (int i = 0; i < movingModels; i++) { bool shouldDie = args._movingModels[i].getShouldDie(); - // if the model is still inside our total bounds, then re-add it + // if the particle is still inside our total bounds, then re-add it AABox treeBounds = getRoot()->getAABox(); if (!shouldDie && treeBounds.contains(args._movingModels[i].getPosition())) { storeModel(args._movingModels[i]); } else { - uint32_t modelID = args._movingModels[i].getID(); + uint32_t modelItemID = args._movingModels[i].getID(); quint64 deletedAt = usecTimestampNow(); _recentlyDeletedModelsLock.lockForWrite(); - _recentlyDeletedModelItemIDs.insert(deletedAt, modelID); + _recentlyDeletedModelItemIDs.insert(deletedAt, modelItemID); _recentlyDeletedModelsLock.unlock(); } } diff --git a/libraries/models/src/ModelTree.h b/libraries/models/src/ModelTree.h index 02086ecd89..ac25cdc003 100644 --- a/libraries/models/src/ModelTree.h +++ b/libraries/models/src/ModelTree.h @@ -29,7 +29,7 @@ public: virtual ModelTreeElement* createNewElement(unsigned char * octalCode = NULL); /// Type safe version of getRoot() - ModelTreeElement* getRoot() { return (ModelTreeElement*)_rootNode; } + ModelTreeElement* getRoot() { return static_cast(_rootElement); } // These methods will allow the OctreeServer to send your tree inbound edit packets of your diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index 687199827c..5c5d5100cf 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -47,15 +47,32 @@ ModelTreeElement* ModelTreeElement::addChildAtIndex(int index) { } -bool ModelTreeElement::appendElementData(OctreePacketData* packetData) const { +bool ModelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { bool success = true; // assume the best... - // write our models out... - uint16_t numberOfModels = _modelItems->size(); + // write our models out... first determine which of the models are in view based on our params + uint16_t numberOfModels = 0; + QVector indexesOfModelsToInclude; + + for (uint16_t i = 0; i < _modelItems->size(); i++) { + if (params.viewFrustum) { + const ModelItem& model = (*_modelItems)[i]; + AABox modelBox = model.getAABox(); + modelBox.scale(TREE_SCALE); + if (params.viewFrustum->boxInFrustum(modelBox) != ViewFrustum::OUTSIDE) { + indexesOfModelsToInclude << i; + numberOfModels++; + } + } else { + indexesOfModelsToInclude << i; + numberOfModels++; + } + } + success = packetData->appendValue(numberOfModels); if (success) { - for (uint16_t i = 0; i < numberOfModels; i++) { + foreach (uint16_t i, indexesOfModelsToInclude) { const ModelItem& model = (*_modelItems)[i]; success = model.appendModelData(packetData); if (!success) { @@ -66,10 +83,25 @@ bool ModelTreeElement::appendElementData(OctreePacketData* packetData) const { return success; } -void ModelTreeElement::update(ModelTreeUpdateArgs& args) { - markWithChangedTime(); - // TODO: early exit when _modelItems is empty +bool ModelTreeElement::containsModelBounds(const ModelItem& model) const { + return _box.contains(model.getMinimumPoint()) && _box.contains(model.getMaximumPoint()); +} +bool ModelTreeElement::bestFitModelBounds(const ModelItem& model) const { + if (_box.contains(model.getMinimumPoint()) && _box.contains(model.getMaximumPoint())) { + int childForMinimumPoint = getMyChildContainingPoint(model.getMinimumPoint()); + int childForMaximumPoint = getMyChildContainingPoint(model.getMaximumPoint()); + + // If I contain both the minimum and maximum point, but two different children of mine + // contain those points, then I am the best fit for that model + if (childForMinimumPoint != childForMaximumPoint) { + return true; + } + } + return false; +} + +void ModelTreeElement::update(ModelTreeUpdateArgs& args) { // update our contained models QList::iterator modelItr = _modelItems->begin(); while(modelItr != _modelItems->end()) { @@ -78,19 +110,18 @@ void ModelTreeElement::update(ModelTreeUpdateArgs& args) { // If the model wants to die, or if it's left our bounding box, then move it // into the arguments moving models. These will be added back or deleted completely - if (model.getShouldDie() || !_box.contains(model.getPosition())) { + if (model.getShouldDie() || !bestFitModelBounds(model)) { args._movingModels.push_back(model); // erase this model modelItr = _modelItems->erase(modelItr); + + // this element has changed so mark it... + markWithChangedTime(); } else { ++modelItr; } } - // TODO: if _modelItems is empty after while loop consider freeing memory in _modelItems if - // internal array is too big (QList internal array does not decrease size except in dtor and - // assignment operator). Otherwise _modelItems could become a "resource leak" for large - // roaming piles of models. } bool ModelTreeElement::findSpherePenetration(const glm::vec3& center, float radius, @@ -136,7 +167,9 @@ bool ModelTreeElement::updateModel(const ModelItem& model) { (localOlder ? "OLDER" : "NEWER"), difference, debug::valueOf(model.isNewlyCreated()) ); } + thisModel.copyChangedProperties(model); + markWithChangedTime(); } else { if (wantDebug) { qDebug(">>> IGNORING SERVER!!! Would've caused jutter! <<< " @@ -167,7 +200,7 @@ bool ModelTreeElement::updateModel(const ModelItemID& modelID, const ModelItemPr } if (found) { thisModel.setProperties(properties); - + markWithChangedTime(); // mark our element as changed.. const bool wantDebug = false; if (wantDebug) { uint64_t now = usecTimestampNow(); diff --git a/libraries/models/src/ModelTreeElement.h b/libraries/models/src/ModelTreeElement.h index ce03d50065..ce9e2dec7e 100644 --- a/libraries/models/src/ModelTreeElement.h +++ b/libraries/models/src/ModelTreeElement.h @@ -74,7 +74,7 @@ public: virtual bool requiresSplit() const { return false; } /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. - virtual bool appendElementData(OctreePacketData* packetData) const; + virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const; /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. @@ -118,6 +118,9 @@ public: bool removeModelWithID(uint32_t id); + bool containsModelBounds(const ModelItem& model) const; + bool bestFitModelBounds(const ModelItem& model) const; + protected: virtual void init(unsigned char * octalCode); diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 844fce77fe..d5b1e8301c 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -39,7 +39,7 @@ enum PacketType { PacketTypeRequestAssignment, PacketTypeCreateAssignment, PacketTypeDomainOAuthRequest, - PacketTypeDataServerGet, // reusable + PacketTypeMuteEnvironment, PacketTypeDataServerSend, // reusable PacketTypeDataServerConfirm, PacketTypeVoxelQuery, diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 266447e27e..5b766ecdd7 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -40,7 +40,7 @@ float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeSc } Octree::Octree(bool shouldReaverage) : - _rootNode(NULL), + _rootElement(NULL), _isDirty(true), _shouldReaverage(shouldReaverage), _stopImport(false), @@ -50,75 +50,75 @@ Octree::Octree(bool shouldReaverage) : } Octree::~Octree() { - // delete the children of the root node + // delete the children of the root element // this recursively deletes the tree - delete _rootNode; + delete _rootElement; } -// Recurses voxel tree calling the RecurseOctreeOperation function for each node. +// Recurses voxel tree calling the RecurseOctreeOperation function for each element. // stops recursion if operation function returns false. void Octree::recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData) { - recurseNodeWithOperation(_rootNode, operation, extraData); + recurseElementWithOperation(_rootElement, operation, extraData); } -// Recurses voxel tree calling the RecurseOctreePostFixOperation function for each node in post-fix order. +// Recurses voxel tree calling the RecurseOctreePostFixOperation function for each element in post-fix order. void Octree::recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData) { - recurseNodeWithPostOperation(_rootNode, operation, extraData); + recurseElementWithPostOperation(_rootElement, operation, extraData); } -// Recurses voxel node with an operation function -void Octree::recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperation operation, void* extraData, +// Recurses voxel element with an operation function +void Octree::recurseElementWithOperation(OctreeElement* element, RecurseOctreeOperation operation, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - qDebug() << "Octree::recurseNodeWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + qDebug() << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; return; } - if (operation(node, extraData)) { + if (operation(element, extraData)) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElement* child = node->getChildAtIndex(i); + OctreeElement* child = element->getChildAtIndex(i); if (child) { - recurseNodeWithOperation(child, operation, extraData, recursionCount+1); + recurseElementWithOperation(child, operation, extraData, recursionCount+1); } } } } -// Recurses voxel node with an operation function -void Octree::recurseNodeWithPostOperation(OctreeElement* node, RecurseOctreeOperation operation, void* extraData, +// Recurses voxel element with an operation function +void Octree::recurseElementWithPostOperation(OctreeElement* element, RecurseOctreeOperation operation, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - qDebug() << "Octree::recurseNodeWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; + qDebug() << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!\n"; return; } for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElement* child = node->getChildAtIndex(i); + OctreeElement* child = element->getChildAtIndex(i); if (child) { - recurseNodeWithPostOperation(child, operation, extraData, recursionCount+1); + recurseElementWithPostOperation(child, operation, extraData, recursionCount+1); } } - operation(node, extraData); + operation(element, extraData); } -// Recurses voxel tree calling the RecurseOctreeOperation function for each node. +// Recurses voxel tree calling the RecurseOctreeOperation function for each element. // stops recursion if operation function returns false. void Octree::recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation, const glm::vec3& point, void* extraData) { - recurseNodeWithOperationDistanceSorted(_rootNode, operation, point, extraData); + recurseElementWithOperationDistanceSorted(_rootElement, operation, point, extraData); } -// Recurses voxel node with an operation function -void Octree::recurseNodeWithOperationDistanceSorted(OctreeElement* node, RecurseOctreeOperation operation, +// Recurses voxel element with an operation function +void Octree::recurseElementWithOperationDistanceSorted(OctreeElement* element, RecurseOctreeOperation operation, const glm::vec3& point, void* extraData, int recursionCount) { if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { - qDebug() << "Octree::recurseNodeWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + qDebug() << "Octree::recurseElementWithOperationDistanceSorted() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; return; } - if (operation(node, extraData)) { + if (operation(element, extraData)) { // determine the distance sorted order of our children OctreeElement* sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -126,88 +126,108 @@ void Octree::recurseNodeWithOperationDistanceSorted(OctreeElement* node, Recurse int currentCount = 0; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElement* childNode = node->getChildAtIndex(i); - if (childNode) { + OctreeElement* childElement = element->getChildAtIndex(i); + if (childElement) { // chance to optimize, doesn't need to be actual distance!! Could be distance squared - float distanceSquared = childNode->distanceSquareToPoint(point); - //qDebug("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance); - //childNode->printDebugDetails(""); - currentCount = insertIntoSortedArrays((void*)childNode, distanceSquared, i, + float distanceSquared = childElement->distanceSquareToPoint(point); + currentCount = insertIntoSortedArrays((void*)childElement, distanceSquared, i, (void**)&sortedChildren, (float*)&distancesToChildren, (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); } } for (int i = 0; i < currentCount; i++) { - OctreeElement* childNode = sortedChildren[i]; - if (childNode) { - //qDebug("recurseNodeWithOperationDistanceSorted() PROCESSING child[%d] distance=%f...\n", i, distancesToChildren[i]); - //childNode->printDebugDetails(""); - recurseNodeWithOperationDistanceSorted(childNode, operation, point, extraData); + OctreeElement* childElement = sortedChildren[i]; + if (childElement) { + recurseElementWithOperationDistanceSorted(childElement, operation, point, extraData); } } } } +void Octree::recurseTreeWithOperator(RecurseOctreeOperator* operatorObject) { + recurseElementWithOperator(_rootElement, operatorObject); +} -OctreeElement* Octree::nodeForOctalCode(OctreeElement* ancestorNode, - const unsigned char* needleCode, OctreeElement** parentOfFoundNode) const { +bool Octree::recurseElementWithOperator(OctreeElement* element, RecurseOctreeOperator* operatorObject, int recursionCount) { + if (recursionCount > DANGEROUSLY_DEEP_RECURSION) { + qDebug() << "Octree::recurseElementWithOperation() reached DANGEROUSLY_DEEP_RECURSION, bailing!"; + return false; + } + + if (operatorObject->PreRecursion(element)) { + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + OctreeElement* child = element->getChildAtIndex(i); + if (child) { + if (!recurseElementWithOperator(child, operatorObject, recursionCount + 1)) { + break; // stop recursing if operator returns false... + } + } + } + } + + return operatorObject->PostRecursion(element); +} + + +OctreeElement* Octree::nodeForOctalCode(OctreeElement* ancestorElement, + const unsigned char* needleCode, OctreeElement** parentOfFoundElement) const { // special case for NULL octcode if (!needleCode) { - return _rootNode; + return _rootElement; } - // find the appropriate branch index based on this ancestorNode + // find the appropriate branch index based on this ancestorElement if (*needleCode > 0) { - int branchForNeedle = branchIndexWithDescendant(ancestorNode->getOctalCode(), needleCode); - OctreeElement* childNode = ancestorNode->getChildAtIndex(branchForNeedle); + int branchForNeedle = branchIndexWithDescendant(ancestorElement->getOctalCode(), needleCode); + OctreeElement* childElement = ancestorElement->getChildAtIndex(branchForNeedle); - if (childNode) { - if (*childNode->getOctalCode() == *needleCode) { + if (childElement) { + if (*childElement->getOctalCode() == *needleCode) { // If the caller asked for the parent, then give them that too... - if (parentOfFoundNode) { - *parentOfFoundNode = ancestorNode; + if (parentOfFoundElement) { + *parentOfFoundElement = ancestorElement; } // the fact that the number of sections is equivalent does not always guarantee - // that this is the same node, however due to the recursive traversal - // we know that this is our node - return childNode; + // that this is the same element, however due to the recursive traversal + // we know that this is our element + return childElement; } else { // we need to go deeper - return nodeForOctalCode(childNode, needleCode, parentOfFoundNode); + return nodeForOctalCode(childElement, needleCode, parentOfFoundElement); } } } - // we've been given a code we don't have a node for - // return this node as the last created parent - return ancestorNode; + // we've been given a code we don't have a element for + // return this element as the last created parent + return ancestorElement; } -// returns the node created! -OctreeElement* Octree::createMissingNode(OctreeElement* lastParentNode, const unsigned char* codeToReach) { - int indexOfNewChild = branchIndexWithDescendant(lastParentNode->getOctalCode(), codeToReach); - // If this parent node is a leaf, then you know the child path doesn't exist, so deal with +// returns the element created! +OctreeElement* Octree::createMissingElement(OctreeElement* lastParentElement, const unsigned char* codeToReach) { + int indexOfNewChild = branchIndexWithDescendant(lastParentElement->getOctalCode(), codeToReach); + // If this parent element is a leaf, then you know the child path doesn't exist, so deal with // breaking up the leaf first, which will also create a child path - if (lastParentNode->requiresSplit()) { - lastParentNode->splitChildren(); - } else if (!lastParentNode->getChildAtIndex(indexOfNewChild)) { + if (lastParentElement->requiresSplit()) { + lastParentElement->splitChildren(); + } else if (!lastParentElement->getChildAtIndex(indexOfNewChild)) { // we could be coming down a branch that was already created, so don't stomp on it. - lastParentNode->addChildAtIndex(indexOfNewChild); + lastParentElement->addChildAtIndex(indexOfNewChild); } // This works because we know we traversed down the same tree so if the length is the same, then the whole code is the same - if (*lastParentNode->getChildAtIndex(indexOfNewChild)->getOctalCode() == *codeToReach) { - return lastParentNode->getChildAtIndex(indexOfNewChild); + if (*lastParentElement->getChildAtIndex(indexOfNewChild)->getOctalCode() == *codeToReach) { + return lastParentElement->getChildAtIndex(indexOfNewChild); } else { - return createMissingNode(lastParentNode->getChildAtIndex(indexOfNewChild), codeToReach); + return createMissingElement(lastParentElement->getChildAtIndex(indexOfNewChild), codeToReach); } } -int Octree::readNodeData(OctreeElement* destinationNode, const unsigned char* nodeData, int bytesLeftToRead, +int Octree::readElementData(OctreeElement* destinationElement, const unsigned char* nodeData, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { - // give this destination node the child mask from the packet + // give this destination element the child mask from the packet const unsigned char ALL_CHILDREN_ASSUMED_TO_EXIST = 0xFF; unsigned char colorInPacketMask = *nodeData; @@ -217,26 +237,26 @@ int Octree::readNodeData(OctreeElement* destinationNode, const unsigned char* no // check the colors mask to see if we have a child to color in if (oneAtBit(colorInPacketMask, i)) { // create the child if it doesn't exist - if (!destinationNode->getChildAtIndex(i)) { - destinationNode->addChildAtIndex(i); - if (destinationNode->isDirty()) { + if (!destinationElement->getChildAtIndex(i)) { + destinationElement->addChildAtIndex(i); + if (destinationElement->isDirty()) { _isDirty = true; } } - OctreeElement* childNodeAt = destinationNode->getChildAtIndex(i); + OctreeElement* childElementAt = destinationElement->getChildAtIndex(i); bool nodeIsDirty = false; - if (childNodeAt) { - bytesRead += childNodeAt->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args); - childNodeAt->setSourceUUID(args.sourceUUID); + if (childElementAt) { + bytesRead += childElementAt->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args); + childElementAt->setSourceUUID(args.sourceUUID); - // if we had a local version of the node already, it's possible that we have it already but + // if we had a local version of the element already, it's possible that we have it already but // with the same color data, so this won't count as a change. To address this we check the following - if (!childNodeAt->isDirty() && childNodeAt->getShouldRender() && !childNodeAt->isRendered()) { - childNodeAt->setDirtyBit(); // force dirty! + if (!childElementAt->isDirty() && childElementAt->getShouldRender() && !childElementAt->isRendered()) { + childElementAt->setDirtyBit(); // force dirty! } - nodeIsDirty = childNodeAt->isDirty(); + nodeIsDirty = childElementAt->isDirty(); } if (nodeIsDirty) { _isDirty = true; @@ -244,7 +264,7 @@ int Octree::readNodeData(OctreeElement* destinationNode, const unsigned char* no } } - // give this destination node the child mask from the packet + // give this destination element the child mask from the packet unsigned char childrenInTreeMask = args.includeExistsBits ? *(nodeData + bytesRead) : ALL_CHILDREN_ASSUMED_TO_EXIST; unsigned char childMask = *(nodeData + bytesRead + (args.includeExistsBits ? sizeof(childrenInTreeMask) : 0)); @@ -255,17 +275,17 @@ int Octree::readNodeData(OctreeElement* destinationNode, const unsigned char* no // check the exists mask to see if we have a child to traverse into if (oneAtBit(childMask, childIndex)) { - if (!destinationNode->getChildAtIndex(childIndex)) { + if (!destinationElement->getChildAtIndex(childIndex)) { // add a child at that index, if it doesn't exist - destinationNode->addChildAtIndex(childIndex); - bool nodeIsDirty = destinationNode->isDirty(); + destinationElement->addChildAtIndex(childIndex); + bool nodeIsDirty = destinationElement->isDirty(); if (nodeIsDirty) { _isDirty = true; } } // tell the child to read the subsequent data - bytesRead += readNodeData(destinationNode->getChildAtIndex(childIndex), + bytesRead += readElementData(destinationElement->getChildAtIndex(childIndex), nodeData + bytesRead, bytesLeftToRead - bytesRead, args); } childIndex++; @@ -274,9 +294,9 @@ int Octree::readNodeData(OctreeElement* destinationNode, const unsigned char* no if (args.includeExistsBits) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { // now also check the childrenInTreeMask, if the mask is missing the bit, then it means we need to delete this child - // subtree/node, because it shouldn't actually exist in the tree. - if (!oneAtBit(childrenInTreeMask, i) && destinationNode->getChildAtIndex(i)) { - destinationNode->safeDeepDeleteChildAtIndex(i); + // subtree/element, because it shouldn't actually exist in the tree. + if (!oneAtBit(childrenInTreeMask, i) && destinationElement->getChildAtIndex(i)) { + destinationElement->safeDeepDeleteChildAtIndex(i); _isDirty = true; // by definition! } } @@ -289,25 +309,25 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long int bytesRead = 0; const unsigned char* bitstreamAt = bitstream; - // If destination node is not included, set it to root - if (!args.destinationNode) { - args.destinationNode = _rootNode; + // If destination element is not included, set it to root + if (!args.destinationElement) { + args.destinationElement = _rootElement; } - // Keep looping through the buffer calling readNodeData() this allows us to pack multiple root-relative Octal codes - // into a single network packet. readNodeData() basically goes down a tree from the root, and fills things in from there + // Keep looping through the buffer calling readElementData() this allows us to pack multiple root-relative Octal codes + // into a single network packet. readElementData() basically goes down a tree from the root, and fills things in from there // if there are more bytes after that, it's assumed to be another root relative tree while (bitstreamAt < bitstream + bufferSizeBytes) { - OctreeElement* bitstreamRootNode = nodeForOctalCode(args.destinationNode, (unsigned char *)bitstreamAt, NULL); - if (*bitstreamAt != *bitstreamRootNode->getOctalCode()) { + OctreeElement* bitstreamRootElement = nodeForOctalCode(args.destinationElement, (unsigned char *)bitstreamAt, NULL); + if (*bitstreamAt != *bitstreamRootElement->getOctalCode()) { // if the octal code returned is not on the same level as // the code being searched for, we have OctreeElements to create - // Note: we need to create this node relative to root, because we're assuming that the bitstream for the initial + // Note: we need to create this element relative to root, because we're assuming that the bitstream for the initial // octal code is always relative to root! - bitstreamRootNode = createMissingNode(args.destinationNode, (unsigned char*) bitstreamAt); - if (bitstreamRootNode->isDirty()) { + bitstreamRootElement = createMissingElement(args.destinationElement, (unsigned char*) bitstreamAt); + if (bitstreamRootElement->isDirty()) { _isDirty = true; } } @@ -316,7 +336,7 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, unsigned long int theseBytesRead = 0; theseBytesRead += octalCodeBytes; - theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes, + theseBytesRead += readElementData(bitstreamRootElement, bitstreamAt + octalCodeBytes, bufferSizeBytes - (bytesRead + octalCodeBytes), args); // skip bitstream to new startPoint @@ -347,9 +367,9 @@ public: }; // Note: uses the codeColorBuffer format, but the color's are ignored, because -// this only finds and deletes the node from the tree. +// this only finds and deletes the element from the tree. void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees) { - // recurse the tree while decoding the codeBuffer, once you find the node in question, recurse + // recurse the tree while decoding the codeBuffer, once you find the element in question, recurse // back and implement color reaveraging, and marking of lastChanged DeleteOctalCodeFromTreeArgs args; args.collapseEmptyTrees = collapseEmptyTrees; @@ -358,19 +378,17 @@ void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool colla args.deleteLastChild = false; args.pathChanged = false; - OctreeElement* node = _rootNode; - - deleteOctalCodeFromTreeRecursion(node, &args); + deleteOctalCodeFromTreeRecursion(_rootElement, &args); } -void Octree::deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraData) { +void Octree::deleteOctalCodeFromTreeRecursion(OctreeElement* element, void* extraData) { DeleteOctalCodeFromTreeArgs* args = (DeleteOctalCodeFromTreeArgs*)extraData; - int lengthOfNodeCode = numberOfThreeBitSectionsInCode(node->getOctalCode()); + int lengthOfElementCode = numberOfThreeBitSectionsInCode(element->getOctalCode()); // Since we traverse the tree in code order, we know that if our code - // matches, then we've reached our target node. - if (lengthOfNodeCode == args->lengthOfCode) { + // matches, then we've reached our target element. + if (lengthOfElementCode == args->lengthOfCode) { // we've reached our target, depending on how we're called we may be able to operate on it // it here, we need to recurse up, and delete it there. So we handle these cases the same to keep // the logic consistent. @@ -378,32 +396,32 @@ void Octree::deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraDa return; } - // Ok, we know we haven't reached our target node yet, so keep looking - int childIndex = branchIndexWithDescendant(node->getOctalCode(), args->codeBuffer); - OctreeElement* childNode = node->getChildAtIndex(childIndex); + // Ok, we know we haven't reached our target element yet, so keep looking + int childIndex = branchIndexWithDescendant(element->getOctalCode(), args->codeBuffer); + OctreeElement* childElement = element->getChildAtIndex(childIndex); - // If there is no child at the target location, and the current parent node is a colored leaf, + // If there is no child at the target location, and the current parent element is a colored leaf, // then it means we were asked to delete a child out of a larger leaf voxel. // We support this by breaking up the parent voxel into smaller pieces. - if (!childNode && node->requiresSplit()) { + if (!childElement && element->requiresSplit()) { // we need to break up ancestors until we get to the right level - OctreeElement* ancestorNode = node; + OctreeElement* ancestorElement = element; while (true) { - int index = branchIndexWithDescendant(ancestorNode->getOctalCode(), args->codeBuffer); + int index = branchIndexWithDescendant(ancestorElement->getOctalCode(), args->codeBuffer); // we end up with all the children, even the one we want to delete - ancestorNode->splitChildren(); + ancestorElement->splitChildren(); - int lengthOfAncestorNode = numberOfThreeBitSectionsInCode(ancestorNode->getOctalCode()); + int lengthOfAncestorElement = numberOfThreeBitSectionsInCode(ancestorElement->getOctalCode()); // If we've reached the parent of the target, then stop breaking up children - if (lengthOfAncestorNode == (args->lengthOfCode - 1)) { + if (lengthOfAncestorElement == (args->lengthOfCode - 1)) { // since we created all the children when we split, we need to delete this target one - ancestorNode->deleteChildAtIndex(index); + ancestorElement->deleteChildAtIndex(index); break; } - ancestorNode = ancestorNode->getChildAtIndex(index); + ancestorElement = ancestorElement->getChildAtIndex(index); } _isDirty = true; args->pathChanged = true; @@ -415,17 +433,17 @@ void Octree::deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraDa // if we don't have a child and we reach this point, then we actually know that the parent // isn't a colored leaf, and the child branch doesn't exist, so there's nothing to do below and // we can safely return, ending the recursion and unwinding - if (!childNode) { + if (!childElement) { return; } // If we got this far then we have a child for the branch we're looking for, but we're not there yet // recurse till we get there - deleteOctalCodeFromTreeRecursion(childNode, args); + deleteOctalCodeFromTreeRecursion(childElement, args); // If the lower level determined it needs to be deleted, then we should delete now. if (args->deleteLastChild) { - node->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this node + element->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this element // track our tree dirtiness _isDirty = true; @@ -433,11 +451,11 @@ void Octree::deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraDa // track that path has changed args->pathChanged = true; - // If we're in collapseEmptyTrees mode, and this was the last child of this node, then we also want - // to delete this node. This will collapse the empty tree above us. - if (args->collapseEmptyTrees && node->getChildCount() == 0) { + // If we're in collapseEmptyTrees mode, and this was the last child of this element, then we also want + // to delete this element. This will collapse the empty tree above us. + if (args->collapseEmptyTrees && element->getChildCount() == 0) { // Can't delete the root this way. - if (node == _rootNode) { + if (element == _rootElement) { args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything } } else { @@ -445,16 +463,16 @@ void Octree::deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraDa } } - // If the lower level did some work, then we need to let this node know, so it can + // If the lower level did some work, then we need to let this element know, so it can // do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc if (args->pathChanged) { - node->handleSubtreeChanged(this); + element->handleSubtreeChanged(this); } } void Octree::eraseAllOctreeElements() { - delete _rootNode; // this will recurse and delete all children - _rootNode = createNewElement(); + delete _rootElement; // this will recurse and delete all children + _rootElement = createNewElement(); _isDirty = true; } @@ -490,15 +508,15 @@ void Octree::processRemoveOctreeElementsBitstream(const unsigned char* bitstream } } -// Note: this is an expensive call. Don't call it unless you really need to reaverage the entire tree (from startNode) -void Octree::reaverageOctreeElements(OctreeElement* startNode) { - if (!startNode) { - startNode = getRoot(); +// Note: this is an expensive call. Don't call it unless you really need to reaverage the entire tree (from startElement) +void Octree::reaverageOctreeElements(OctreeElement* startElement) { + if (!startElement) { + startElement = getRoot(); } // if our tree is a reaveraging tree, then we do this, otherwise we don't do anything if (_shouldReaverage) { static int recursionCount; - if (startNode == _rootNode) { + if (startElement == _rootElement) { recursionCount = 0; } else { recursionCount++; @@ -512,16 +530,16 @@ void Octree::reaverageOctreeElements(OctreeElement* startNode) { bool hasChildren = false; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (startNode->getChildAtIndex(i)) { - reaverageOctreeElements(startNode->getChildAtIndex(i)); + if (startElement->getChildAtIndex(i)) { + reaverageOctreeElements(startElement->getChildAtIndex(i)); hasChildren = true; } } // collapseIdenticalLeaves() returns true if it collapses the leaves // in which case we don't need to set the average color - if (hasChildren && !startNode->collapseChildren()) { - startNode->calculateAverageFromChildren(); + if (hasChildren && !startElement->collapseChildren()) { + startElement->calculateAverageFromChildren(); } recursionCount--; } @@ -529,30 +547,30 @@ void Octree::reaverageOctreeElements(OctreeElement* startNode) { OctreeElement* Octree::getOctreeElementAt(float x, float y, float z, float s) const { unsigned char* octalCode = pointToOctalCode(x,y,z,s); - OctreeElement* node = nodeForOctalCode(_rootNode, octalCode, NULL); - if (*node->getOctalCode() != *octalCode) { - node = NULL; + OctreeElement* element = nodeForOctalCode(_rootElement, octalCode, NULL); + if (*element->getOctalCode() != *octalCode) { + element = NULL; } delete[] octalCode; // cleanup memory #ifdef HAS_AUDIT_CHILDREN - if (node) { - node->auditChildren("Octree::getOctreeElementAt()"); + if (element) { + element->auditChildren("Octree::getOctreeElementAt()"); } #endif // def HAS_AUDIT_CHILDREN - return node; + return element; } OctreeElement* Octree::getOctreeEnclosingElementAt(float x, float y, float z, float s) const { unsigned char* octalCode = pointToOctalCode(x,y,z,s); - OctreeElement* node = nodeForOctalCode(_rootNode, octalCode, NULL); + OctreeElement* element = nodeForOctalCode(_rootElement, octalCode, NULL); delete[] octalCode; // cleanup memory #ifdef HAS_AUDIT_CHILDREN - if (node) { - node->auditChildren("Octree::getOctreeElementAt()"); + if (element) { + element->auditChildren("Octree::getOctreeElementAt()"); } #endif // def HAS_AUDIT_CHILDREN - return node; + return element; } @@ -560,32 +578,36 @@ OctreeElement* Octree::getOrCreateChildElementAt(float x, float y, float z, floa return getRoot()->getOrCreateChildElementAt(x, y, z, s); } +OctreeElement* Octree::getOrCreateChildElementContaining(const AABox& box) { + return getRoot()->getOrCreateChildElementContaining(box); +} + // combines the ray cast arguments into a single object class RayArgs { public: glm::vec3 origin; glm::vec3 direction; - OctreeElement*& node; + OctreeElement*& element; float& distance; BoxFace& face; bool found; }; -bool findRayIntersectionOp(OctreeElement* node, void* extraData) { +bool findRayIntersectionOp(OctreeElement* element, void* extraData) { RayArgs* args = static_cast(extraData); - AABox box = node->getAABox(); + AABox box = element->getAABox(); float distance; BoxFace face; if (!box.findRayIntersection(args->origin, args->direction, distance, face)) { return false; } - if (!node->isLeaf()) { + if (!element->isLeaf()) { return true; // recurse on children } distance *= TREE_SCALE; - if (node->hasContent() && (!args->found || distance < args->distance)) { - args->node = node; + if (element->hasContent() && (!args->found || distance < args->distance)) { + args->element = element; args->distance = distance; args->face = face; args->found = true; @@ -594,9 +616,9 @@ bool findRayIntersectionOp(OctreeElement* node, void* extraData) { } bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElement*& node, float& distance, BoxFace& face, + OctreeElement*& element, float& distance, BoxFace& face, Octree::lockType lockType, bool* accurateResult) { - RayArgs args = { origin / (float)(TREE_SCALE), direction, node, distance, face, false}; + RayArgs args = { origin / (float)(TREE_SCALE), direction, element, distance, face, false}; bool gotLock = false; if (lockType == Octree::Lock) { @@ -712,18 +734,18 @@ public: bool found; }; -bool findCapsulePenetrationOp(OctreeElement* node, void* extraData) { +bool findCapsulePenetrationOp(OctreeElement* element, void* extraData) { CapsuleArgs* args = static_cast(extraData); // coarse check against bounds - const AABox& box = node->getAABox(); + const AABox& box = element->getAABox(); if (!box.expandedIntersectsSegment(args->start, args->end, args->radius)) { return false; } - if (!node->isLeaf()) { + if (!element->isLeaf()) { return true; // recurse on children } - if (node->hasContent()) { + if (element->hasContent()) { glm::vec3 nodePenetration; if (box.findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) { args->penetration = addPenetrations(args->penetration, nodePenetration * (float)(TREE_SCALE)); @@ -733,19 +755,19 @@ bool findCapsulePenetrationOp(OctreeElement* node, void* extraData) { return false; } -bool findShapeCollisionsOp(OctreeElement* node, void* extraData) { +bool findShapeCollisionsOp(OctreeElement* element, void* extraData) { ShapeArgs* args = static_cast(extraData); // coarse check against bounds - AABox cube = node->getAABox(); + AABox cube = element->getAABox(); cube.scale(TREE_SCALE); if (!cube.expandedContains(args->shape->getPosition(), args->shape->getBoundingRadius())) { return false; } - if (!node->isLeaf()) { + if (!element->isLeaf()) { return true; // recurse on children } - if (node->hasContent()) { + if (element->hasContent()) { if (ShapeCollider::collideShapeWithAACube(args->shape, cube.calcCenter(), cube.getScale(), args->collisions)) { args->found = true; return true; @@ -834,7 +856,7 @@ bool getElementEnclosingOperation(OctreeElement* element, void* extraData) { AABox elementBox = element->getAABox(); if (elementBox.contains(args->point)) { if (element->hasContent() && element->isLeaf()) { - // we've reached a solid leaf containing the point, return the node. + // we've reached a solid leaf containing the point, return the element. args->element = element; return false; } @@ -878,22 +900,22 @@ OctreeElement* Octree::getElementEnclosingPoint(const glm::vec3& point, Octree:: -int Octree::encodeTreeBitstream(OctreeElement* node, +int Octree::encodeTreeBitstream(OctreeElement* element, OctreePacketData* packetData, OctreeElementBag& bag, EncodeBitstreamParams& params) { // How many bytes have we written so far at this level; int bytesWritten = 0; - // you can't call this without a valid node - if (!node) { - qDebug("WARNING! encodeTreeBitstream() called with node=NULL"); + // you can't call this without a valid element + if (!element) { + qDebug("WARNING! encodeTreeBitstream() called with element=NULL"); params.stopReason = EncodeBitstreamParams::NULL_NODE; return bytesWritten; } - // If we're at a node that is out of view, then we can return, because no nodes below us will be in view! - if (params.viewFrustum && !node->isInView(*params.viewFrustum)) { + // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! + if (params.viewFrustum && !element->isInView(*params.viewFrustum)) { params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; return bytesWritten; } @@ -902,7 +924,7 @@ int Octree::encodeTreeBitstream(OctreeElement* node, bool roomForOctalCode = false; // assume the worst int codeLength = 1; // assume root if (params.chopLevels) { - unsigned char* newCode = chopOctalCode(node->getOctalCode(), params.chopLevels); + unsigned char* newCode = chopOctalCode(element->getOctalCode(), params.chopLevels); roomForOctalCode = packetData->startSubTree(newCode); if (newCode) { @@ -912,13 +934,13 @@ int Octree::encodeTreeBitstream(OctreeElement* node, codeLength = 1; } } else { - roomForOctalCode = packetData->startSubTree(node->getOctalCode()); - codeLength = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(node->getOctalCode())); + roomForOctalCode = packetData->startSubTree(element->getOctalCode()); + codeLength = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(element->getOctalCode())); } // If the octalcode couldn't fit, then we can return, because no nodes below us will fit... if (!roomForOctalCode) { - bag.insert(node); // add the node back to the bag so it will eventually get included + bag.insert(element); // add the element back to the bag so it will eventually get included params.stopReason = EncodeBitstreamParams::DIDNT_FIT; return bytesWritten; } @@ -927,15 +949,15 @@ int Octree::encodeTreeBitstream(OctreeElement* node, int currentEncodeLevel = 0; - // record some stats, this is the one node that we won't record below in the recursion function, so we need to + // record some stats, this is the one element that we won't record below in the recursion function, so we need to // track it here if (params.stats) { - params.stats->traversed(node); + params.stats->traversed(element); } ViewFrustum::location parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully - int childBytesWritten = encodeTreeBitstreamRecursion(node, packetData, bag, params, + int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params, currentEncodeLevel, parentLocationThisView); // if childBytesWritten == 1 then something went wrong... that's not possible @@ -966,16 +988,16 @@ int Octree::encodeTreeBitstream(OctreeElement* node, return bytesWritten; } -int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, +int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, OctreePacketData* packetData, OctreeElementBag& bag, EncodeBitstreamParams& params, int& currentEncodeLevel, const ViewFrustum::location& parentLocationThisView) const { // How many bytes have we written so far at this level; int bytesAtThisLevel = 0; - // you can't call this without a valid node - if (!node) { - qDebug("WARNING! encodeTreeBitstreamRecursion() called with node=NULL"); + // you can't call this without a valid element + if (!element) { + qDebug("WARNING! encodeTreeBitstreamRecursion() called with element=NULL"); params.stopReason = EncodeBitstreamParams::NULL_NODE; return bytesAtThisLevel; } @@ -983,7 +1005,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, // Keep track of how deep we've encoded. currentEncodeLevel++; - params.maxLevelReached = std::max(currentEncodeLevel,params.maxLevelReached); + params.maxLevelReached = std::max(currentEncodeLevel, params.maxLevelReached); // If we've reached our max Search Level, then stop searching. if (currentEncodeLevel >= params.maxEncodeLevel) { @@ -995,7 +1017,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, if (params.jurisdictionMap) { // here's how it works... if we're currently above our root jurisdiction, then we proceed normally. // but once we're in our own jurisdiction, then we need to make sure we're not below it. - if (JurisdictionMap::BELOW == params.jurisdictionMap->isMyJurisdiction(node->getOctalCode(), CHECK_NODE_ONLY)) { + if (JurisdictionMap::BELOW == params.jurisdictionMap->isMyJurisdiction(element->getOctalCode(), CHECK_NODE_ONLY)) { params.stopReason = EncodeBitstreamParams::OUT_OF_JURISDICTION; return bytesAtThisLevel; } @@ -1005,14 +1027,14 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, // caller can pass NULL as viewFrustum if they want everything if (params.viewFrustum) { - float distance = node->distanceToCamera(*params.viewFrustum); - float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust, + float distance = element->distanceToCamera(*params.viewFrustum); + float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, params.octreeElementSizeScale); // If we're too far away for our render level, then just return if (distance >= boundaryDistance) { if (params.stats) { - params.stats->skippedDistance(node); + params.stats->skippedDistance(element); } params.stopReason = EncodeBitstreamParams::LOD_SKIP; return bytesAtThisLevel; @@ -1022,15 +1044,15 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, // if we are INSIDE, INTERSECT, or OUTSIDE if (parentLocationThisView != ViewFrustum::INSIDE) { assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE! - nodeLocationThisView = node->inFrustum(*params.viewFrustum); + nodeLocationThisView = element->inFrustum(*params.viewFrustum); } - // If we're at a node that is out of view, then we can return, because no nodes below us will be in view! + // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! // although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if // we're out of view if (nodeLocationThisView == ViewFrustum::OUTSIDE) { if (params.stats) { - params.stats->skippedOutOfView(node); + params.stats->skippedOutOfView(element); } params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; return bytesAtThisLevel; @@ -1041,10 +1063,10 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, bool wasInView = false; if (params.deltaViewFrustum && params.lastViewFrustum) { - ViewFrustum::location location = node->inFrustum(*params.lastViewFrustum); + ViewFrustum::location location = element->inFrustum(*params.lastViewFrustum); // If we're a leaf, then either intersect or inside is considered "formerly in view" - if (node->isLeaf()) { + if (element->isLeaf()) { wasInView = location != ViewFrustum::OUTSIDE; } else { wasInView = location == ViewFrustum::INSIDE; @@ -1055,8 +1077,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, // to it, and so therefore it may now be visible from an LOD perspective, in which case we don't consider it // as "was in view"... if (wasInView) { - float distance = node->distanceToCamera(*params.lastViewFrustum); - float boundaryDistance = boundaryDistanceForRenderLevel(node->getLevel() + params.boundaryLevelAdjust, + float distance = element->distanceToCamera(*params.lastViewFrustum); + float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, params.octreeElementSizeScale); if (distance >= boundaryDistance) { // This would have been invisible... but now should be visible (we wouldn't be here otherwise)... @@ -1066,11 +1088,11 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, } // If we were previously in the view, then we normally will return out of here and stop recursing. But - // if we're in deltaViewFrustum mode, and this node has changed since it was last sent, then we do + // if we're in deltaViewFrustum mode, and this element has changed since it was last sent, then we do // need to send it. - if (wasInView && !(params.deltaViewFrustum && node->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))) { + if (wasInView && !(params.deltaViewFrustum && element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))) { if (params.stats) { - params.stats->skippedWasInView(node); + params.stats->skippedWasInView(element); } params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW; return bytesAtThisLevel; @@ -1079,18 +1101,18 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, // If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed, // then we can also bail early and save bits if (!params.forceSendScene && !params.deltaViewFrustum && - !node->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE)) { + !element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE)) { if (params.stats) { - params.stats->skippedNoChange(node); + params.stats->skippedNoChange(element); } params.stopReason = EncodeBitstreamParams::NO_CHANGE; return bytesAtThisLevel; } - // If the user also asked for occlusion culling, check if this node is occluded, but only if it's not a leaf. + // If the user also asked for occlusion culling, check if this element is occluded, but only if it's not a leaf. // leaf occlusion is handled down below when we check child nodes - if (params.wantOcclusionCulling && !node->isLeaf()) { - AABox voxelBox = node->getAABox(); + if (params.wantOcclusionCulling && !element->isLeaf()) { + AABox voxelBox = element->getAABox(); voxelBox.scale(TREE_SCALE); OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(params.viewFrustum->getProjectedPolygon(voxelBox)); @@ -1101,7 +1123,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, delete voxelPolygon; // cleanup if (result == OCCLUDED) { if (params.stats) { - params.stats->skippedOccluded(node); + params.stats->skippedOccluded(element); } params.stopReason = EncodeBitstreamParams::OCCLUDED; return bytesAtThisLevel; @@ -1138,72 +1160,73 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, int currentCount = 0; for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElement* childNode = node->getChildAtIndex(i); + OctreeElement* childElement = element->getChildAtIndex(i); // if the caller wants to include childExistsBits, then include them even if not in view, if however, // we're in a portion of the tree that's not our responsibility, then we assume the child nodes exist // even if they don't in our local tree bool notMyJurisdiction = false; if (params.jurisdictionMap) { - notMyJurisdiction = (JurisdictionMap::WITHIN != params.jurisdictionMap->isMyJurisdiction(node->getOctalCode(), i)); + notMyJurisdiction = JurisdictionMap::WITHIN != params.jurisdictionMap->isMyJurisdiction(element->getOctalCode(), i); } if (params.includeExistsBits) { // If the child is known to exist, OR, it's not my jurisdiction, then we mark the bit as existing - if (childNode || notMyJurisdiction) { + if (childElement || notMyJurisdiction) { childrenExistInTreeBits += (1 << (7 - i)); } } if (params.wantOcclusionCulling) { - if (childNode) { - float distance = params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; + if (childElement) { + float distance = params.viewFrustum ? childElement->distanceToCamera(*params.viewFrustum) : 0; - currentCount = insertIntoSortedArrays((void*)childNode, distance, i, + currentCount = insertIntoSortedArrays((void*)childElement, distance, i, (void**)&sortedChildren, (float*)&distancesToChildren, (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); } } else { - sortedChildren[i] = childNode; + sortedChildren[i] = childElement; indexOfChildren[i] = i; distancesToChildren[i] = 0.0f; currentCount++; } // track stats - // must check childNode here, because it could be we got here with no childNode - if (params.stats && childNode) { - params.stats->traversed(childNode); + // must check childElement here, because it could be we got here with no childElement + if (params.stats && childElement) { + params.stats->traversed(childElement); } } - // for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so + // for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so // add them to our distance ordered array of children for (int i = 0; i < currentCount; i++) { - OctreeElement* childNode = sortedChildren[i]; + OctreeElement* childElement = sortedChildren[i]; int originalIndex = indexOfChildren[i]; - bool childIsInView = (childNode && + bool childIsInView = (childElement && ( !params.viewFrustum || // no view frustum was given, everything is assumed in view - (nodeLocationThisView == ViewFrustum::INSIDE) || // the parent was fully in view, we can assume ALL children are - (nodeLocationThisView == ViewFrustum::INTERSECT && childNode->isInView(*params.viewFrustum)) // the parent intersects and the child is in view + (nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are + (nodeLocationThisView == ViewFrustum::INTERSECT && + childElement->isInView(*params.viewFrustum)) // the parent intersects and the child is in view )); if (!childIsInView) { - // must check childNode here, because it could be we got here because there was no childNode - if (params.stats && childNode) { - params.stats->skippedOutOfView(childNode); + // must check childElement here, because it could be we got here because there was no childElement + if (params.stats && childElement) { + params.stats->skippedOutOfView(childElement); } } else { // Before we determine consider this further, let's see if it's in our LOD scope... - float distance = distancesToChildren[i]; // params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; + float distance = distancesToChildren[i]; float boundaryDistance = !params.viewFrustum ? 1 : - boundaryDistanceForRenderLevel(childNode->getLevel() + params.boundaryLevelAdjust, + boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust, params.octreeElementSizeScale); if (!(distance < boundaryDistance)) { - // don't need to check childNode here, because we can't get here with no childNode + // don't need to check childElement here, because we can't get here with no childElement if (params.stats) { - params.stats->skippedDistance(childNode); + params.stats->skippedDistance(childElement); } } else { inViewCount++; @@ -1211,23 +1234,23 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, // track children in view as existing and not a leaf, if they're a leaf, // we don't care about recursing deeper on them, and we don't consider their // subtree to exist - if (!(childNode && childNode->isLeaf())) { + if (!(childElement && childElement->isLeaf())) { childrenExistInPacketBits += (1 << (7 - originalIndex)); inViewNotLeafCount++; } bool childIsOccluded = false; // assume it's not occluded - // If the user also asked for occlusion culling, check if this node is occluded - if (params.wantOcclusionCulling && childNode->isLeaf()) { + // If the user also asked for occlusion culling, check if this element is occluded + if (params.wantOcclusionCulling && childElement->isLeaf()) { // Don't check occlusion here, just add them to our distance ordered array... - AABox voxelBox = childNode->getAABox(); + AABox voxelBox = childElement->getAABox(); voxelBox.scale(TREE_SCALE); OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon( params.viewFrustum->getProjectedPolygon(voxelBox)); - // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion + // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we ignore occlusion // culling and proceed as normal if (voxelPolygon->getAllInView()) { CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, true); @@ -1251,18 +1274,18 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, bool shouldRender = !params.viewFrustum ? true - : childNode->calculateShouldRender(params.viewFrustum, + : childElement->calculateShouldRender(params.viewFrustum, params.octreeElementSizeScale, params.boundaryLevelAdjust); // track some stats if (params.stats) { - // don't need to check childNode here, because we can't get here with no childNode - if (!shouldRender && childNode->isLeaf()) { - params.stats->skippedDistance(childNode); + // don't need to check childElement here, because we can't get here with no childElement + if (!shouldRender && childElement->isLeaf()) { + params.stats->skippedDistance(childElement); } - // don't need to check childNode here, because we can't get here with no childNode + // don't need to check childElement here, because we can't get here with no childElement if (childIsOccluded) { - params.stats->skippedOccluded(childNode); + params.stats->skippedOccluded(childElement); } } @@ -1270,11 +1293,11 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, if (shouldRender && !childIsOccluded) { bool childWasInView = false; - if (childNode && params.deltaViewFrustum && params.lastViewFrustum) { - ViewFrustum::location location = childNode->inFrustum(*params.lastViewFrustum); + if (childElement && params.deltaViewFrustum && params.lastViewFrustum) { + ViewFrustum::location location = childElement->inFrustum(*params.lastViewFrustum); // If we're a leaf, then either intersect or inside is considered "formerly in view" - if (childNode->isLeaf()) { + if (childElement->isLeaf()) { childWasInView = location != ViewFrustum::OUTSIDE; } else { childWasInView = location == ViewFrustum::INSIDE; @@ -1282,22 +1305,22 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, } // If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items. - // Or if we were previously in the view, but this node has changed since it was last sent, then we do + // Or if we were previously in the view, but this element has changed since it was last sent, then we do // need to send it. if (!childWasInView || (params.deltaViewFrustum && - childNode->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))){ + childElement->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))){ childrenColoredBits += (1 << (7 - originalIndex)); inViewWithColorCount++; } else { // otherwise just track stats of the items we discarded - // don't need to check childNode here, because we can't get here with no childNode + // don't need to check childElement here, because we can't get here with no childElement if (params.stats) { if (childWasInView) { - params.stats->skippedWasInView(childNode); + params.stats->skippedWasInView(childElement); } else { - params.stats->skippedNoChange(childNode); + params.stats->skippedNoChange(childElement); } } } @@ -1320,10 +1343,10 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, if (continueThisLevel && params.includeColor) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { if (oneAtBit(childrenColoredBits, i)) { - OctreeElement* childNode = node->getChildAtIndex(i); - if (childNode) { + OctreeElement* childElement = element->getChildAtIndex(i); + if (childElement) { int bytesBeforeChild = packetData->getUncompressedSize(); - continueThisLevel = childNode->appendElementData(packetData); + continueThisLevel = childElement->appendElementData(packetData, params); int bytesAfterChild = packetData->getUncompressedSize(); if (!continueThisLevel) { @@ -1332,9 +1355,9 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child - // don't need to check childNode here, because we can't get here with no childNode + // don't need to check childElement here, because we can't get here with no childElement if (params.stats) { - params.stats->colorSent(childNode); + params.stats->colorSent(childElement); } } } @@ -1389,10 +1412,10 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, int firstRecursiveSliceOffset = packetData->getUncompressedByteOffset(); int allSlicesSize = 0; - // for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so + // for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so // add them to our distance ordered array of children for (int indexByDistance = 0; indexByDistance < currentCount; indexByDistance++) { - OctreeElement* childNode = sortedChildren[indexByDistance]; + OctreeElement* childElement = sortedChildren[indexByDistance]; int originalIndex = indexOfChildren[indexByDistance]; if (oneAtBit(childrenExistInPacketBits, originalIndex)) { @@ -1413,7 +1436,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, // This only applies in the view frustum case, in other cases, like file save and copy/past where // no viewFrustum was requested, we still want to recurse the child tree. if (!params.viewFrustum || !oneAtBit(childrenColoredBits, originalIndex)) { - childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, packetData, bag, params, + childTreeBytesOut = encodeTreeBitstreamRecursion(childElement, packetData, bag, params, thisLevel, nodeLocationThisView); } @@ -1431,7 +1454,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, // if the child tree wrote just 2 bytes, then it means: it had no colors and no child nodes, because... // if it had colors it would write 1 byte for the color mask, - // and at least a color's worth of bytes for the node of colors. + // and at least a color's worth of bytes for the element of colors. // if it had child trees (with something in them) then it would have the 1 byte for child mask // and some number of bytes of lower children... // so, if the child returns 2 bytes out, we can actually consider that an empty tree also!! @@ -1441,8 +1464,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees } // We used to try to collapse trees that didn't contain any data, but this does appear to create a problem - // in detecting node deletion. So, I've commented this out but left it in here as a warning to anyone else - // about not attempting to add this optimization back in, without solving the node deletion case. + // in detecting element deletion. So, I've commented this out but left it in here as a warning to anyone else + // about not attempting to add this optimization back in, without solving the element deletion case. // We need to send these bitMasks in case the exists in tree bitmask is indicating the deletion of a tree //if (params.includeColor && params.includeExistsBits && childTreeBytesOut == 3) { // childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees @@ -1508,7 +1531,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, qDebug(""); **/ - // if we were unable to fit this level in our packet, then rewind and add it to the node bag for + // if we were unable to fit this level in our packet, then rewind and add it to the element bag for // sending later... if (continueThisLevel) { continueThisLevel = packetData->endLevel(thisLevelKey); @@ -1517,11 +1540,11 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, } if (!continueThisLevel) { - bag.insert(node); + bag.insert(element); - // don't need to check node here, because we can't get here with no node + // don't need to check element here, because we can't get here with no element if (params.stats) { - params.stats->didntFit(node); + params.stats->didntFit(element); } params.stopReason = EncodeBitstreamParams::DIDNT_FIT; @@ -1591,7 +1614,7 @@ bool Octree::readFromSVOFile(const char* fileName) { return fileOk; } -void Octree::writeToSVOFile(const char* fileName, OctreeElement* node) { +void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) { std::ofstream file(fileName, std::ios::out|std::ios::binary); @@ -1608,11 +1631,11 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* node) { } OctreeElementBag nodeBag; - // If we were given a specific node, start from there, otherwise start from root - if (node) { - nodeBag.insert(node); + // If we were given a specific element, start from there, otherwise start from root + if (element) { + nodeBag.insert(element); } else { - nodeBag.insert(_rootNode); + nodeBag.insert(_rootElement); } static OctreePacketData packetData; @@ -1627,7 +1650,7 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* node) { bytesWritten = encodeTreeBitstream(subTree, &packetData, nodeBag, params); unlock(); - // if the subTree couldn't fit, and so we should reset the packet and reinsert the node in our bag and try again... + // if the subTree couldn't fit, and so we should reset the packet and reinsert the element in our bag and try again if (bytesWritten == 0 && (params.stopReason == EncodeBitstreamParams::DIDNT_FIT)) { if (packetData.hasContent()) { file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize()); @@ -1654,17 +1677,17 @@ unsigned long Octree::getOctreeElementsCount() { return nodeCount; } -bool Octree::countOctreeElementsOperation(OctreeElement* node, void* extraData) { +bool Octree::countOctreeElementsOperation(OctreeElement* element, void* extraData) { (*(unsigned long*)extraData)++; return true; // keep going } -void Octree::copySubTreeIntoNewTree(OctreeElement* startNode, Octree* destinationTree, bool rebaseToRoot) { +void Octree::copySubTreeIntoNewTree(OctreeElement* startElement, Octree* destinationTree, bool rebaseToRoot) { OctreeElementBag nodeBag; - nodeBag.insert(startNode); + nodeBag.insert(startElement); int chopLevels = 0; if (rebaseToRoot) { - chopLevels = numberOfThreeBitSectionsInCode(startNode->getOctalCode()); + chopLevels = numberOfThreeBitSectionsInCode(startElement->getOctalCode()); } static OctreePacketData packetData; @@ -1679,28 +1702,12 @@ void Octree::copySubTreeIntoNewTree(OctreeElement* startNode, Octree* destinatio ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS); destinationTree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); } - - // XXXBHG - what is this trying to do? - // This code appears to be trying to set the color of the destination root - // of a copy operation. But that shouldn't be necessary. I think this code might - // have been a hack that Mark added when he was trying to solve the copy of a single - // voxel bug. But this won't solve that problem, and doesn't appear to be needed for - // a normal copy operation. I'm leaving this in for a little bit until we see if anything - // about copy/paste is broken. - // - //OctreeElement* destinationStartNode; - //if (rebaseToRoot) { - // destinationStartNode = destinationTree->_rootNode; - //} else { - // destinationStartNode = nodeForOctalCode(destinationTree->_rootNode, startNode->getOctalCode(), NULL); - //} - //destinationStartNode->setColor(startNode->getColor()); } -void Octree::copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinationNode) { +void Octree::copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinationElement) { OctreeElementBag nodeBag; - // If we were given a specific node, start from there, otherwise start from root - nodeBag.insert(sourceTree->_rootNode); + // If we were given a specific element, start from there, otherwise start from root + nodeBag.insert(sourceTree->_rootElement); static OctreePacketData packetData; @@ -1715,20 +1722,12 @@ void Octree::copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinat // ask destination tree to read the bitstream bool wantImportProgress = true; - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationNode, 0, SharedNodePointer(), wantImportProgress); + ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationElement, + 0, SharedNodePointer(), wantImportProgress); readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); } } -void dumpSetContents(const char* name, std::set set) { - qDebug("set %s has %ld elements", name, set.size()); - /* - for (std::set::iterator i = set.begin(); i != set.end(); ++i) { - printOctalCode(*i); - } - */ -} - void Octree::cancelImport() { _stopImport = true; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 56e8d9d08c..4a17cb3c1d 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -36,8 +36,15 @@ class Shape; #include #include +/// derive from this class to use the Octree::recurseTreeWithOperator() method +class RecurseOctreeOperator { +public: + virtual bool PreRecursion(OctreeElement* element) = 0; + virtual bool PostRecursion(OctreeElement* element) = 0; +}; + // Callback function, for recuseTreeWithOperation -typedef bool (*RecurseOctreeOperation)(OctreeElement* node, void* extraData); +typedef bool (*RecurseOctreeOperation)(OctreeElement* element, void* extraData); typedef enum {GRADIENT, RANDOM, NATURAL} creationMode; const bool NO_EXISTS_BITS = false; @@ -159,7 +166,7 @@ class ReadBitstreamToTreeParams { public: bool includeColor; bool includeExistsBits; - OctreeElement* destinationNode; + OctreeElement* destinationElement; QUuid sourceUUID; SharedNodePointer sourceNode; bool wantImportProgress; @@ -167,13 +174,13 @@ public: ReadBitstreamToTreeParams( bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS, - OctreeElement* destinationNode = NULL, + OctreeElement* destinationElement = NULL, QUuid sourceUUID = QUuid(), SharedNodePointer sourceNode = SharedNodePointer(), bool wantImportProgress = false) : includeColor(includeColor), includeExistsBits(includeExistsBits), - destinationNode(destinationNode), + destinationElement(destinationElement), sourceUUID(sourceUUID), sourceNode(sourceNode), wantImportProgress(wantImportProgress) @@ -200,14 +207,14 @@ public: virtual void update() { }; // nothing to do by default - OctreeElement* getRoot() { return _rootNode; } + OctreeElement* getRoot() { return _rootElement; } void eraseAllOctreeElements(); void processRemoveOctreeElementsBitstream(const unsigned char* bitstream, int bufferSizeBytes); void readBitstreamToTree(const unsigned char* bitstream, unsigned long int bufferSizeBytes, ReadBitstreamToTreeParams& args); void deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE); - void reaverageOctreeElements(OctreeElement* startNode = NULL); + void reaverageOctreeElements(OctreeElement* startElement = NULL); void deleteOctreeElementAt(float x, float y, float z, float s); @@ -220,15 +227,17 @@ public: OctreeElement* getOctreeEnclosingElementAt(float x, float y, float z, float s) const; OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s); + OctreeElement* getOrCreateChildElementContaining(const AABox& box); void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData = NULL); - void recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData = NULL); void recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation, const glm::vec3& point, void* extraData = NULL); - int encodeTreeBitstream(OctreeElement* node, OctreePacketData* packetData, OctreeElementBag& bag, + void recurseTreeWithOperator(RecurseOctreeOperator* operatorObject); + + int encodeTreeBitstream(OctreeElement* element, OctreePacketData* packetData, OctreeElementBag& bag, EncodeBitstreamParams& params) ; bool isDirty() const { return _isDirty; } @@ -268,28 +277,30 @@ public: void loadOctreeFile(const char* fileName, bool wantColorRandomizer); // these will read/write files that match the wireformat, excluding the 'V' leading - void writeToSVOFile(const char* filename, OctreeElement* node = NULL); + void writeToSVOFile(const char* filename, OctreeElement* element = NULL); bool readFromSVOFile(const char* filename); unsigned long getOctreeElementsCount(); - void copySubTreeIntoNewTree(OctreeElement* startNode, Octree* destinationTree, bool rebaseToRoot); - void copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinationNode); + void copySubTreeIntoNewTree(OctreeElement* startElement, Octree* destinationTree, bool rebaseToRoot); + void copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinationElement); bool getShouldReaverage() const { return _shouldReaverage; } - void recurseNodeWithOperation(OctreeElement* node, RecurseOctreeOperation operation, + void recurseElementWithOperation(OctreeElement* element, RecurseOctreeOperation operation, void* extraData, int recursionCount = 0); /// Traverse child nodes of node applying operation in post-fix order /// - void recurseNodeWithPostOperation(OctreeElement* node, RecurseOctreeOperation operation, + void recurseElementWithPostOperation(OctreeElement* element, RecurseOctreeOperation operation, void* extraData, int recursionCount = 0); - void recurseNodeWithOperationDistanceSorted(OctreeElement* node, RecurseOctreeOperation operation, + void recurseElementWithOperationDistanceSorted(OctreeElement* element, RecurseOctreeOperation operation, const glm::vec3& point, void* extraData, int recursionCount = 0); + bool recurseElementWithOperator(OctreeElement* element, RecurseOctreeOperator* operatorObject, int recursionCount = 0); + bool getIsViewing() const { return _isViewing; } void setIsViewing(bool isViewing) { _isViewing = isViewing; } @@ -302,21 +313,21 @@ public slots: protected: - void deleteOctalCodeFromTreeRecursion(OctreeElement* node, void* extraData); + void deleteOctalCodeFromTreeRecursion(OctreeElement* element, void* extraData); - int encodeTreeBitstreamRecursion(OctreeElement* node, + int encodeTreeBitstreamRecursion(OctreeElement* element, OctreePacketData* packetData, OctreeElementBag& bag, EncodeBitstreamParams& params, int& currentEncodeLevel, const ViewFrustum::location& parentLocationThisView) const; - static bool countOctreeElementsOperation(OctreeElement* node, void* extraData); + static bool countOctreeElementsOperation(OctreeElement* element, void* extraData); - OctreeElement* nodeForOctalCode(OctreeElement* ancestorNode, const unsigned char* needleCode, OctreeElement** parentOfFoundNode) const; - OctreeElement* createMissingNode(OctreeElement* lastParentNode, const unsigned char* codeToReach); - int readNodeData(OctreeElement *destinationNode, const unsigned char* nodeData, + OctreeElement* nodeForOctalCode(OctreeElement* ancestorElement, const unsigned char* needleCode, OctreeElement** parentOfFoundElement) const; + OctreeElement* createMissingElement(OctreeElement* lastParentElement, const unsigned char* codeToReach); + int readElementData(OctreeElement *destinationElement, const unsigned char* nodeData, int bufferSizeBytes, ReadBitstreamToTreeParams& args); - OctreeElement* _rootNode; + OctreeElement* _rootElement; bool _isDirty; bool _shouldReaverage; diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index d54f7aa94b..edba26f2a7 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -1379,3 +1379,90 @@ OctreeElement* OctreeElement::getOrCreateChildElementAt(float x, float y, float // Now that we have the child to recurse down, let it answer the original question... return child->getOrCreateChildElementAt(x, y, z, s); } + + +OctreeElement* OctreeElement::getOrCreateChildElementContaining(const AABox& box) { + OctreeElement* child = NULL; + + float ourScale = getScale(); + float boxScale = box.getScale(); + + if(boxScale > ourScale) { + qDebug("UNEXPECTED -- OctreeElement::getOrCreateChildElementContaining() " + "boxScale=[%f] > ourScale=[%f] ", boxScale, ourScale); + } + + // Determine which of our children the minimum and maximum corners of the box live in... + glm::vec3 boxCornerMinimum = box.getCorner(); + glm::vec3 boxCornerMaximum = box.calcTopFarLeft(); + + int childIndexBoxMinimum = getMyChildContainingPoint(boxCornerMinimum); + int childIndexBoxMaximum = getMyChildContainingPoint(boxCornerMaximum); + + // If the minimum and maximum corners of the box are in two different children's boxes, then we are the containing element + if (childIndexBoxMinimum != childIndexBoxMaximum) { + return this; + } + + // otherwise, they are the same and that child should be considered as the correct element + int childIndex = childIndexBoxMinimum; // both the same... + + // Now, check if we have a child at that location + child = getChildAtIndex(childIndex); + if (!child) { + child = addChildAtIndex(childIndex); + } + + // Now that we have the child to recurse down, let it answer the original question... + return child->getOrCreateChildElementContaining(box); +} + +int OctreeElement::getMyChildContainingPoint(const glm::vec3& point) const { + glm::vec3 ourCenter = _box.calcCenter(); + int childIndex = CHILD_UNKNOWN; + // left half + if (point.x > ourCenter.x) { + if (point.y > ourCenter.y) { + // top left + if (point.z > ourCenter.z) { + // top left far + childIndex = CHILD_TOP_LEFT_FAR; + } else { + // top left near + childIndex = CHILD_TOP_LEFT_NEAR; + } + } else { + // bottom left + if (point.z > ourCenter.z) { + // bottom left far + childIndex = CHILD_BOTTOM_LEFT_FAR; + } else { + // bottom left near + childIndex = CHILD_BOTTOM_LEFT_NEAR; + } + } + } else { + // right half + if (point.y > ourCenter.y) { + // top right + if (point.z > ourCenter.z) { + // top right far + childIndex = CHILD_TOP_RIGHT_FAR; + } else { + // top right near + childIndex = CHILD_TOP_RIGHT_NEAR; + } + } else { + // bottom right + if (point.z > ourCenter.z) { + // bottom right far + childIndex = CHILD_BOTTOM_RIGHT_FAR; + } else { + // bottom right near + childIndex = CHILD_BOTTOM_RIGHT_NEAR; + } + } + } + return childIndex; +} + diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index c5eec1c9e2..42c9abad46 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -23,14 +23,14 @@ #include "AABox.h" #include "ViewFrustum.h" #include "OctreeConstants.h" -//#include "Octree.h" +class EncodeBitstreamParams; class Octree; class OctreeElement; class OctreeElementDeleteHook; class OctreePacketData; -class VoxelSystem; class ReadBitstreamToTreeParams; +class VoxelSystem; // Callers who want delete hook callbacks should implement this class class OctreeElementDeleteHook { @@ -81,7 +81,7 @@ public: virtual bool requiresSplit() const { return false; } /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. - virtual bool appendElementData(OctreePacketData* packetData) const { return true; } + virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { return true; } /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. @@ -217,6 +217,8 @@ public: OctreeElement* getOrCreateChildElementAt(float x, float y, float z, float s); + OctreeElement* getOrCreateChildElementContaining(const AABox& box); + int getMyChildContainingPoint(const glm::vec3& point) const; protected: diff --git a/libraries/octree/src/OctreeHeadlessViewer.h b/libraries/octree/src/OctreeHeadlessViewer.h index ebabf1dbad..3509713d50 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.h +++ b/libraries/octree/src/OctreeHeadlessViewer.h @@ -33,7 +33,7 @@ public: virtual void renderElement(OctreeElement* element, RenderArgs* args) { /* swallow these */ }; virtual void init(); - virtual void render() { /* swallow these */ }; + virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE) { /* swallow these */ }; void setJurisdictionListener(JurisdictionListener* jurisdictionListener) { _jurisdictionListener = jurisdictionListener; } diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index d802f8e808..d704923a11 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -28,6 +28,7 @@ typedef unsigned char OCTREE_PACKET_FLAGS; typedef uint16_t OCTREE_PACKET_SEQUENCE; +const uint16_t MAX_OCTREE_PACKET_SEQUENCE = 65535; typedef quint64 OCTREE_PACKET_SENT_TIME; typedef uint16_t OCTREE_PACKET_INTERNAL_SECTION_SIZE; const int MAX_OCTREE_PACKET_SIZE = MAX_PACKET_SIZE; diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index 5c5da2250f..c1ce3cb218 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -154,8 +154,8 @@ bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) { return false; } -void OctreeRenderer::render() { - RenderArgs args = { 0, this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust() }; +void OctreeRenderer::render(RenderMode renderMode) { + RenderArgs args = { 0, this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode }; if (_tree) { _tree->lockForRead(); _tree->recurseTreeWithOperation(renderOperation, &args); diff --git a/libraries/octree/src/OctreeRenderer.h b/libraries/octree/src/OctreeRenderer.h index 652f9d0399..73e26c97f6 100644 --- a/libraries/octree/src/OctreeRenderer.h +++ b/libraries/octree/src/OctreeRenderer.h @@ -25,15 +25,7 @@ #include "ViewFrustum.h" class OctreeRenderer; - -class RenderArgs { -public: - int _renderedItems; - OctreeRenderer* _renderer; - ViewFrustum* _viewFrustum; - float _sizeScale; - int _boundaryLevelAdjust; -}; +class RenderArgs; // Generic client side Octree renderer class. @@ -59,8 +51,10 @@ public: /// initialize and GPU/rendering related resources virtual void init(); + enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE }; + /// render the content of the octree - virtual void render(); + virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE); ViewFrustum* getViewFrustum() const { return _viewFrustum; } void setViewFrustum(ViewFrustum* viewFrustum) { _viewFrustum = viewFrustum; } @@ -75,4 +69,15 @@ protected: ViewFrustum* _viewFrustum; }; +class RenderArgs { +public: + int _renderedItems; + OctreeRenderer* _renderer; + ViewFrustum* _viewFrustum; + float _sizeScale; + int _boundaryLevelAdjust; + OctreeRenderer::RenderMode _renderMode; +}; + + #endif // hifi_OctreeRenderer_h diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 1dc1459771..c08e723f89 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -875,10 +875,13 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet, return; // ignore any packets that are unreasonable } + // determine our expected sequence number... handle rollover appropriately + OCTREE_PACKET_SEQUENCE expected = _incomingPacket > 0 ? _incomingLastSequence + 1 : sequence; + // Guard against possible corrupted packets... with bad sequence numbers const int MAX_RESONABLE_SEQUENCE_OFFSET = 2000; const int MIN_RESONABLE_SEQUENCE_OFFSET = -2000; - int sequenceOffset = (sequence - _incomingLastSequence); + int sequenceOffset = (sequence - expected); if (sequenceOffset > MAX_RESONABLE_SEQUENCE_OFFSET || sequenceOffset < MIN_RESONABLE_SEQUENCE_OFFSET) { qDebug() << "ignoring unreasonable packet... sequence:" << sequence << "_incomingLastSequence:" << _incomingLastSequence; return; // ignore any packets that are unreasonable @@ -901,7 +904,6 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet, qDebug() << "last packet duplicate got:" << sequence << "_incomingLastSequence:" << _incomingLastSequence; } } else { - OCTREE_PACKET_SEQUENCE expected = _incomingLastSequence+1; if (sequence != expected) { if (wantExtraDebugging) { qDebug() << "out of order... got:" << sequence << "expected:" << expected; @@ -958,9 +960,9 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet, } } - // only bump the last sequence if it was greater than our previous last sequence, this will keep us from + // only bump the last sequence if it was greater than our expected sequence, this will keep us from // accidentally going backwards when an out of order (recovered) packet comes in - if (sequence > _incomingLastSequence) { + if (sequence >= expected) { _incomingLastSequence = sequence; } diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index 09e034ccd1..dd8cb6e618 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -12,7 +12,7 @@ #include "ParticleTree.h" ParticleTree::ParticleTree(bool shouldReaverage) : Octree(shouldReaverage) { - _rootNode = createNewElement(); + _rootElement = createNewElement(); } ParticleTreeElement* ParticleTree::createNewElement(unsigned char * octalCode) { diff --git a/libraries/particles/src/ParticleTree.h b/libraries/particles/src/ParticleTree.h index 76b9926bdf..0a2ac285b7 100644 --- a/libraries/particles/src/ParticleTree.h +++ b/libraries/particles/src/ParticleTree.h @@ -29,7 +29,7 @@ public: virtual ParticleTreeElement* createNewElement(unsigned char * octalCode = NULL); /// Type safe version of getRoot() - ParticleTreeElement* getRoot() { return (ParticleTreeElement*)_rootNode; } + ParticleTreeElement* getRoot() { return static_cast(_rootElement); } // These methods will allow the OctreeServer to send your tree inbound edit packets of your diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index d28ccf2f5e..b6e59eb0ab 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -47,7 +47,7 @@ ParticleTreeElement* ParticleTreeElement::addChildAtIndex(int index) { } -bool ParticleTreeElement::appendElementData(OctreePacketData* packetData) const { +bool ParticleTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { bool success = true; // assume the best... // write our particles out... diff --git a/libraries/particles/src/ParticleTreeElement.h b/libraries/particles/src/ParticleTreeElement.h index 59f80d588a..4381cdd777 100644 --- a/libraries/particles/src/ParticleTreeElement.h +++ b/libraries/particles/src/ParticleTreeElement.h @@ -76,7 +76,7 @@ public: virtual bool requiresSplit() const { return false; } /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. - virtual bool appendElementData(OctreePacketData* packetData) const; + virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const; /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 402f1a2885..9be2cb5252 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -52,6 +53,14 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ return QScriptValue(); } +QScriptValue injectorToScriptValue(QScriptEngine *engine, AudioInjector* const &in) { + return engine->newQObject(in); +} + +void injectorFromScriptValue(const QScriptValue &object, AudioInjector* &out) { + out = qobject_cast(object.toQObject()); +} + ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, AbstractControllerScriptingInterface* controllerScriptingInterface) : @@ -226,6 +235,8 @@ void ScriptEngine::init() { QScriptValue localVoxelsValue = _engine.scriptValueFromQMetaObject(); _engine.globalObject().setProperty("LocalVoxels", localVoxelsValue); + + qScriptRegisterMetaType(&_engine, injectorToScriptValue, injectorFromScriptValue); registerGlobalObject("Script", this); registerGlobalObject("Audio", &_audioScriptingInterface); diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index b1ddf2e5b0..6372d7fd6d 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -23,13 +23,13 @@ VoxelTree::VoxelTree(bool shouldReaverage) : Octree(shouldReaverage) { - _rootNode = createNewElement(); + _rootElement = createNewElement(); } VoxelTreeElement* VoxelTree::createNewElement(unsigned char * octalCode) { VoxelSystem* voxelSystem = NULL; - if (_rootNode) { - voxelSystem = ((VoxelTreeElement*)_rootNode)->getVoxelSystem(); + if (_rootElement) { + voxelSystem = (static_cast(_rootElement))->getVoxelSystem(); } VoxelTreeElement* newElement = new VoxelTreeElement(octalCode); newElement->setVoxelSystem(voxelSystem); diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index eb24c182b2..2915774fe3 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -26,7 +26,7 @@ public: VoxelTree(bool shouldReaverage = false); virtual VoxelTreeElement* createNewElement(unsigned char * octalCode = NULL); - VoxelTreeElement* getRoot() { return (VoxelTreeElement*)_rootNode; } + VoxelTreeElement* getRoot() { return static_cast(_rootElement); } void deleteVoxelAt(float x, float y, float z, float s); diff --git a/libraries/voxels/src/VoxelTreeElement.cpp b/libraries/voxels/src/VoxelTreeElement.cpp index 2582980816..f72e628b74 100644 --- a/libraries/voxels/src/VoxelTreeElement.cpp +++ b/libraries/voxels/src/VoxelTreeElement.cpp @@ -65,7 +65,7 @@ void VoxelTreeElement::splitChildren() { } } -bool VoxelTreeElement::appendElementData(OctreePacketData* packetData) const { +bool VoxelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { return packetData->appendColor(getColor()); } diff --git a/libraries/voxels/src/VoxelTreeElement.h b/libraries/voxels/src/VoxelTreeElement.h index 8733987df4..788a728f6f 100644 --- a/libraries/voxels/src/VoxelTreeElement.h +++ b/libraries/voxels/src/VoxelTreeElement.h @@ -43,7 +43,7 @@ public: virtual bool hasContent() const { return isColored(); } virtual void splitChildren(); virtual bool requiresSplit() const; - virtual bool appendElementData(OctreePacketData* packetData) const; + virtual bool appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const; virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args); virtual void calculateAverageFromChildren(); virtual bool collapseChildren();