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 index a54ef82e7e..b7c4b72948 100644 --- a/examples/airGuitar.js +++ b/examples/airGuitar.js @@ -25,13 +25,24 @@ function vMinus(a, b) { return rval; } -// First, load two percussion sounds to be used on the sticks +// The model file to be used for the guitar +var guitarModel = "https://s3-us-west-1.amazonaws.com/highfidelity-public/models/attachments/guitar.fst"; -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"); +// Load sounds that will be played -var whichChord = chord1; +var chords = new Array(); +// Nylon string guitar +chords[1] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+A.raw"); +chords[2] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+B.raw"); +chords[3] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+E.raw"); +// Electric guitar +chords[4] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+A+short.raw"); +chords[5] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+B+short.raw"); +chords[6] = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Metal+E+short.raw"); + +var guitarSelector = 3; + +var whichChord = chords[guitarSelector + 1]; var leftHanded = false; if (leftHanded) { @@ -46,42 +57,74 @@ var lastPosition = { x: 0.0, y: 0.0, z: 0.0 }; +var soundPlaying = false; +var selectorPressed = false; + +MyAvatar.attach(guitarModel, "Hips", {x: -0.0, y: -0.0, z: 0.0}, Quat.fromPitchYawRollDegrees(0, 0, 0), 1.0); function checkHands(deltaTime) { for (var palm = 0; palm < 2; palm++) { var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1); - var speed = length(palmVelocity); + var speed = length(palmVelocity) / 4.0; var position = Controller.getSpatialControlPosition(palm * 2 + 1); var myPelvis = MyAvatar.position; + var trigger = Controller.getTriggerValue(strumHand); + var chord = Controller.getTriggerValue(chordHand); + + if ((chord > 0.1) && Audio.isInjectorPlaying(soundPlaying)) { + // If chord finger trigger pulled, stop current chord + Audio.stopInjector(soundPlaying); + } + + var BUTTON_COUNT = 6; + + // Change guitars if button FWD (5) pressed + if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 5)) { + if (!selectorPressed) { + if (guitarSelector == 0) { + guitarSelector = 3; + } else { + guitarSelector = 0; + } + selectorPressed = true; + } + } else { + selectorPressed = false; + } + + if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 1)) { + whichChord = chords[guitarSelector + 1]; + } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 2)) { + whichChord = chords[guitarSelector + 2]; + } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 3)) { + whichChord = chords[guitarSelector + 3]; + } if (palm == strumHand) { - var STRUM_HEIGHT_ABOVE_PELVIS = 0.15; + var STRUM_HEIGHT_ABOVE_PELVIS = 0.00; 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! + if ( ( ((position.y < strumTriggerHeight) && (lastPosition.y >= strumTriggerHeight)) || + ((position.y > strumTriggerHeight) && (lastPosition.y <= strumTriggerHeight)) ) && (trigger > 0.1) ){ + // If hand passes downward or upward through 'strings', and finger trigger pulled, play var options = new AudioInjectionOptions(); options.position = position; if (speed > 1.0) { speed = 1.0; } options.volume = speed; - Audio.playSound(whichChord, options); + if (Audio.isInjectorPlaying(soundPlaying)) { + Audio.stopInjector(soundPlaying); + } + soundPlaying = 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.50) { - whichChord = chord3; - } else if (distanceFromPelvis > 0.35) { - whichChord = chord2; - } else { - whichChord = chord1; - } - } + } } } +function scriptEnding() { + MyAvatar.detachOne(guitarModel); +} // Connect a call back that happens every frame -Script.update.connect(checkHands); \ No newline at end of file +Script.update.connect(checkHands); +Script.scriptEnding.connect(scriptEnding); diff --git a/examples/editModels.js b/examples/editModels.js index 737b6a85a5..9acbff4aa6 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -402,7 +402,7 @@ function moveOverlays() { toolsX = windowDimensions.x - 8 - toolWidth; toolsY = (windowDimensions.y - toolsHeight) / 2; - Overlays.addOverlay(firstModel, { + Overlays.editOverlay(firstModel, { x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight, }); } diff --git a/examples/gun.js b/examples/gun.js index 4bfdb140b8..e404ae1d4d 100644 --- a/examples/gun.js +++ b/examples/gun.js @@ -28,7 +28,7 @@ var BULLET_VELOCITY = 5.0; var MIN_THROWER_DELAY = 1000; var MAX_THROWER_DELAY = 1000; var LEFT_BUTTON_3 = 3; -var RELOAD_INTERVAL = 9; +var RELOAD_INTERVAL = 5; var showScore = false; @@ -39,6 +39,8 @@ var impactSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-pub var targetHitSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Space%20Invaders/hit.raw"); var targetLaunchSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Space%20Invaders/shoot.raw"); +var gunModel = "http://highfidelity-public.s3-us-west-1.amazonaws.com/models/attachments/Raygun2.fst"; + var audioOptions = new AudioInjectionOptions(); audioOptions.volume = 0.9; @@ -90,12 +92,12 @@ function printVector(string, vector) { } function shootBullet(position, velocity) { - var BULLET_SIZE = 0.02; + var BULLET_SIZE = 0.01; var BULLET_GRAVITY = -0.02; Particles.addParticle( { position: position, radius: BULLET_SIZE, - color: { red: 200, green: 0, blue: 0 }, + color: { red: 10, green: 10, blue: 10 }, velocity: velocity, gravity: { x: 0, y: BULLET_GRAVITY, z: 0 }, damping: 0 }); @@ -185,11 +187,24 @@ function keyPressEvent(event) { if (event.text == "t") { var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY; Script.setTimeout(shootTarget, time); - } if (event.text == ".") { + } else if (event.text == ".") { shootFromMouse(); + } else if (event.text == "r") { + playLoadSound(); } } +function playLoadSound() { + audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); + Audio.playSound(loadSound, audioOptions); +} + +MyAvatar.attach(gunModel, "RightHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20); +MyAvatar.attach(gunModel, "LeftHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20); + +// Give a bit of time to load before playing sound +Script.setTimeout(playLoadSound, 2000); + function update(deltaTime) { // Check for mouseLook movement, update rotation // rotate body yaw for yaw received from mouse @@ -303,7 +318,9 @@ function mouseMoveEvent(event) { function scriptEnding() { Overlays.deleteOverlay(reticle); - Overlays.deleteOverlay(text); + Overlays.deleteOverlay(text); + MyAvatar.detachOne(gunModel); + MyAvatar.detachOne(gunModel); } Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 10f113ad11..61666ccdeb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -572,8 +572,8 @@ void Application::paintGL() { glm::vec3 eyePosition = _myAvatar->getHead()->calculateAverageEyePosition(); float headHeight = eyePosition.y - _myAvatar->getPosition().y; _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale() * _scaleMirror); - _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight, 0)); - _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); + _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()) * @@ -870,7 +870,11 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Up: if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { - _scaleMirror *= 0.95; + if (!isShifted) { + _scaleMirror *= 0.95f; + } else { + _raiseMirror += 0.05f; + } } else { _myAvatar->setDriveKeys(isShifted ? UP : FWD, 1.f); } @@ -878,18 +882,30 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Down: if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { - _scaleMirror *= 1.05; + 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: @@ -1201,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) { @@ -1859,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()"); @@ -2000,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... @@ -2357,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(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 1d38a11357..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); @@ -457,6 +453,8 @@ private: glm::mat4 _projectionMatrix; float _scaleMirror; + float _rotateMirror; + float _raiseMirror; glm::mat4 _shadowMatrix; @@ -475,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 08719f0f25..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"; @@ -519,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), @@ -540,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()); @@ -584,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()); @@ -617,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()); @@ -654,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 f7bf4595d6..4de608fc6b 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -73,7 +73,6 @@ const float BILLBOARD_LOD_DISTANCE = 40.0f; void Avatar::init() { getHead()->init(); - getHand()->init(); _skeletonModel.init(); _initialized = true; _shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE); @@ -215,17 +214,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 +236,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 +342,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 +354,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); } @@ -634,8 +637,8 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti penetration)) { CollisionInfo* collision = collisions.getNewCollision(); if (collision) { - collision->_type = PADDLE_HAND_COLLISION; - collision->_flags = jointIndex; + collision->_type = COLLISION_TYPE_PADDLE_HAND; + collision->_intData = jointIndex; collision->_penetration = penetration; collision->_addedVelocity = palm->getVelocity(); collided = true; @@ -705,7 +708,9 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { void Avatar::setAttachmentData(const QVector& attachmentData) { AvatarData::setAttachmentData(attachmentData); - + if (QThread::currentThread() != thread()) { + return; + } // make sure we have as many models as attachments while (_attachmentModels.size() < attachmentData.size()) { Model* model = new Model(this); @@ -844,11 +849,11 @@ float Avatar::getHeadHeight() const { } bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const { - if (!collision._data || collision._type != MODEL_COLLISION) { + if (!collision._data || collision._type != COLLISION_TYPE_MODEL) { return false; } Model* model = static_cast(collision._data); - int jointIndex = collision._flags; + int jointIndex = collision._intData; if (model == &(_skeletonModel) && jointIndex != -1) { // collision response of skeleton is temporarily disabled @@ -856,7 +861,7 @@ bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const { //return _skeletonModel.collisionHitsMoveableJoint(collision); } if (model == &(getHead()->getFaceModel())) { - // ATM we always handle MODEL_COLLISIONS against the face. + // ATM we always handle COLLISION_TYPE_MODEL against the face. return true; } return false; diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index c925e452b2..576790714e 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -29,22 +29,11 @@ const float PALM_COLLISION_RADIUS = 0.03f; Hand::Hand(Avatar* owningAvatar) : HandData((AvatarData*)owningAvatar), - - _owningAvatar(owningAvatar), - _renderAlpha(1.0) + _owningAvatar(owningAvatar) { } -void Hand::init() { -} - -void Hand::reset() { -} - void Hand::simulate(float deltaTime, bool isMine) { - - calculateGeometry(); - if (isMine) { // Iterate hand controllers, take actions as needed for (size_t i = 0; i < getNumPalms(); ++i) { @@ -146,57 +135,9 @@ void Hand::resolvePenetrations() { } } -void Hand::calculateGeometry() { - // generate finger tip balls.... - _leapFingerTipBalls.clear(); - 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()) { - const float standardBallRadius = FINGERTIP_COLLISION_RADIUS; - HandBall ball; - ball.rotation = getBaseOrientation(); - ball.position = finger.getTipPosition(); - ball.radius = standardBallRadius; - ball.touchForce = 0.0; - ball.isCollidable = true; - ball.isColliding = false; - _leapFingerTipBalls.push_back(ball); - } - } - } - } - - // generate finger root balls.... - _leapFingerRootBalls.clear(); - 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()) { - const float standardBallRadius = 0.005f; - HandBall ball; - ball.rotation = getBaseOrientation(); - ball.position = finger.getRootPosition(); - ball.radius = standardBallRadius; - ball.touchForce = 0.0; - ball.isCollidable = true; - ball.isColliding = false; - _leapFingerRootBalls.push_back(ball); - } - } - } - } -} - -void Hand::render(bool isMine) { - - _renderAlpha = 1.0; - - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { +void Hand::render(bool isMine, Model::RenderMode renderMode) { + 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,20 +153,19 @@ 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); glEnable(GL_RESCALE_NORMAL); - } -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); @@ -239,6 +179,7 @@ void Hand::renderLeapHands(bool isMine) { glm::vec3 targetPosition; palm.getBallHoldPosition(targetPosition); glPushMatrix(); + glTranslatef(targetPosition.x, targetPosition.y, targetPosition.z); const float collisionRadius = 0.05f; glColor4f(0.5f,0.5f,0.5f, alpha); @@ -247,38 +188,36 @@ void Hand::renderLeapHands(bool isMine) { } } - glPushMatrix(); - // Draw the leap balls - for (size_t i = 0; i < _leapFingerTipBalls.size(); i++) { - if (alpha > 0.0f) { - if (_leapFingerTipBalls[i].isColliding) { - glColor4f(handColor.r, 0, 0, alpha); - } else { - glColor4f(handColor.r, handColor.g, handColor.b, alpha); - } - glPushMatrix(); - glTranslatef(_leapFingerTipBalls[i].position.x, _leapFingerTipBalls[i].position.y, _leapFingerTipBalls[i].position.z); - glutSolidSphere(_leapFingerTipBalls[i].radius, 20.0f, 20.0f); - 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 +248,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..5d171f2809 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" @@ -49,15 +50,9 @@ public: float touchForce; // a scalar determining the amount that the cursor (or hand) is penetrating the ball }; - 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;} - const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;} - void collideAgainstAvatar(Avatar* avatar, bool isMyHand); void collideAgainstOurself(); @@ -71,14 +66,8 @@ private: int _controllerButtons; /// Button states read from hand-held controllers Avatar* _owningAvatar; - float _renderAlpha; - std::vector _leapFingerTipBalls; - std::vector _leapFingerRootBalls; - void renderLeapHands(bool isMine); - void renderLeapFingerTrails(); - - void calculateGeometry(); + void renderHandTargets(bool isMine); }; #endif // hifi_Hand_h 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 e8782fa140..6ce362071b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -45,6 +46,9 @@ const float PITCH_SPEED = 100.0f; // degrees/sec const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions const float COLLISION_RADIUS_SCALE = 0.125f; +const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f; +const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED; + const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f; // TODO: normalize avatar speed for standard avatar size, then scale all motion logic @@ -63,6 +67,7 @@ MyAvatar::MyAvatar() : _distanceToNearestAvatar(std::numeric_limits::max()), _wasPushing(false), _isPushing(false), + _trapDuration(0.0f), _thrust(0.0f), _motorVelocity(0.0f), _motorTimescale(DEFAULT_MOTOR_TIMESCALE), @@ -92,18 +97,21 @@ MyAvatar::~MyAvatar() { void MyAvatar::reset() { _skeletonModel.reset(); getHead()->reset(); - getHand()->reset(); _oculusYawOffset = 0.0f; 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(); @@ -130,7 +138,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); @@ -212,6 +227,8 @@ void MyAvatar::simulate(float deltaTime) { } if (_collisionGroups & COLLISION_GROUP_VOXELS) { updateCollisionWithVoxels(deltaTime, radius); + } else { + _trapDuration = 0.0f; } if (_collisionGroups & COLLISION_GROUP_AVATARS) { updateCollisionWithAvatars(deltaTime); @@ -224,7 +241,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(); @@ -333,7 +350,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(); } } @@ -496,6 +515,55 @@ void MyAvatar::loadData(QSettings* settings) { settings->endGroup(); } +void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const { + QSettings* settings = Application::getInstance()->lockSettings(); + settings->beginGroup("savedAttachmentData"); + settings->beginGroup(_skeletonModel.getURL().toString()); + settings->beginGroup(attachment.modelURL.toString()); + + settings->setValue("jointName", attachment.jointName); + settings->setValue("translation_x", attachment.translation.x); + settings->setValue("translation_y", attachment.translation.y); + settings->setValue("translation_z", attachment.translation.z); + glm::vec3 eulers = safeEulerAngles(attachment.rotation); + settings->setValue("rotation_x", eulers.x); + settings->setValue("rotation_y", eulers.y); + settings->setValue("rotation_z", eulers.z); + settings->setValue("scale", attachment.scale); + + settings->endGroup(); + settings->endGroup(); + settings->endGroup(); + Application::getInstance()->unlockSettings(); +} + +AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL) const { + QSettings* settings = Application::getInstance()->lockSettings(); + settings->beginGroup("savedAttachmentData"); + settings->beginGroup(_skeletonModel.getURL().toString()); + settings->beginGroup(modelURL.toString()); + + AttachmentData attachment; + attachment.modelURL = modelURL; + attachment.jointName = settings->value("jointName").toString(); + attachment.translation.x = loadSetting(settings, "translation_x", 0.0f); + attachment.translation.y = loadSetting(settings, "translation_y", 0.0f); + attachment.translation.z = loadSetting(settings, "translation_z", 0.0f); + glm::vec3 eulers; + eulers.x = loadSetting(settings, "rotation_x", 0.0f); + eulers.y = loadSetting(settings, "rotation_y", 0.0f); + eulers.z = loadSetting(settings, "rotation_z", 0.0f); + attachment.rotation = glm::quat(eulers); + attachment.scale = loadSetting(settings, "scale", 1.0f); + + settings->endGroup(); + settings->endGroup(); + settings->endGroup(); + Application::getInstance()->unlockSettings(); + + return attachment; +} + int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) { qDebug() << "Error: ignoring update packet for MyAvatar" << " packetLength = " << packet.size() @@ -567,6 +635,31 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _billboardValid = false; } +void MyAvatar::setAttachmentData(const QVector& attachmentData) { + Avatar::setAttachmentData(attachmentData); + if (QThread::currentThread() != thread()) { + return; + } + _billboardValid = false; +} + +void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, + const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) { + if (QThread::currentThread() != thread()) { + Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved); + return; + } + if (useSaved) { + AttachmentData attachment = loadAttachmentData(modelURL); + if (!attachment.jointName.isEmpty()) { + Avatar::attach(modelURL, attachment.jointName, attachment.translation, + attachment.rotation, attachment.scale, allowDuplicates, useSaved); + return; + } + } + Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved); +} + void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { return; // wait until both models are loaded @@ -582,7 +675,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; @@ -693,8 +786,6 @@ void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) { if (directionLength > EPSILON) { direction /= directionLength; // the finalMotorSpeed depends on whether we are walking or not - const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f; - const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED; float finalMaxMotorSpeed = walking ? MAX_WALKING_SPEED : _maxMotorSpeed; float motorLength = glm::length(_motorVelocity); @@ -905,40 +996,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(); @@ -958,65 +1015,102 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) { static CollisionList myCollisions(64); void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { + const float MAX_VOXEL_COLLISION_SPEED = 100.0f; + float speed = glm::length(_velocity); + if (speed > MAX_VOXEL_COLLISION_SPEED) { + // don't even bother to try to collide against voxles when moving very fast + _trapDuration = 0.0f; + return; + } + bool isTrapped = false; myCollisions.clear(); const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); - if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions)) { + if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions, Octree::TryLock)) { const float VOXEL_ELASTICITY = 0.0f; const float VOXEL_DAMPING = 0.0f; + float capsuleRadius = boundingShape.getRadius(); + float capsuleHalfHeight = boundingShape.getHalfHeight(); + const float MAX_STEP_HEIGHT = capsuleRadius + capsuleHalfHeight; + const float MIN_STEP_HEIGHT = 0.0f; + glm::vec3 footBase = boundingShape.getPosition() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection; + float highestStep = 0.0f; + glm::vec3 stepPenetration(0.0f); + glm::vec3 totalPenetration(0.0f); - if (glm::length2(_gravity) > EPSILON) { - if (myCollisions.size() == 1) { - // trivial case - CollisionInfo* collision = myCollisions[0]; - applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING); - _lastFloorContactPoint = collision->_contactPoint - collision->_penetration; + for (int i = 0; i < myCollisions.size(); ++i) { + CollisionInfo* collision = myCollisions[i]; + glm::vec3 cubeCenter = collision->_vecData; + float cubeSide = collision->_floatData; + float verticalDepth = glm::dot(collision->_penetration, _worldUpDirection); + float horizontalDepth = glm::length(collision->_penetration - verticalDepth * _worldUpDirection); + const float MAX_TRAP_PERIOD = 0.125f; + if (horizontalDepth > capsuleRadius || fabsf(verticalDepth) > MAX_STEP_HEIGHT) { + isTrapped = true; + if (_trapDuration > MAX_TRAP_PERIOD) { + float distance = glm::dot(boundingShape.getPosition() - cubeCenter, _worldUpDirection); + if (distance < 0.0f) { + distance = fabsf(distance) + 0.5f * cubeSide; + } + distance += capsuleRadius + capsuleHalfHeight; + totalPenetration = addPenetrations(totalPenetration, - distance * _worldUpDirection); + continue; + } + } else if (_trapDuration > MAX_TRAP_PERIOD) { + // we're trapped, ignore this collision + continue; + } + totalPenetration = addPenetrations(totalPenetration, collision->_penetration); + if (glm::dot(collision->_penetration, _velocity) >= 0.0f) { + glm::vec3 cubeTop = cubeCenter + (0.5f * cubeSide) * _worldUpDirection; + float stepHeight = glm::dot(_worldUpDirection, cubeTop - footBase); + if (stepHeight > highestStep) { + highestStep = stepHeight; + stepPenetration = collision->_penetration; + } + } + } + + float penetrationLength = glm::length(totalPenetration); + if (penetrationLength < EPSILON) { + _trapDuration = 0.0f; + return; + } + float verticalPenetration = glm::dot(totalPenetration, _worldUpDirection); + if (highestStep > MIN_STEP_HEIGHT && highestStep < MAX_STEP_HEIGHT && verticalPenetration <= 0.0f) { + // we're colliding against an edge + glm::vec3 targetVelocity = _motorVelocity; + if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) { + // rotate _motorVelocity into world frame + glm::quat rotation = getHead()->getCameraOrientation(); + targetVelocity = rotation * _motorVelocity; + } + if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) { + // we're puhing into the edge, so we want to lift + + // remove unhelpful horizontal component of the step's penetration + totalPenetration -= stepPenetration - (glm::dot(stepPenetration, _worldUpDirection) * _worldUpDirection); + + // further adjust penetration to help lift + float liftSpeed = glm::max(MAX_WALKING_SPEED, speed); + float thisStep = glm::min(liftSpeed * deltaTime, highestStep); + float extraStep = glm::dot(totalPenetration, _worldUpDirection) + thisStep; + if (extraStep > 0.0f) { + totalPenetration -= extraStep * _worldUpDirection; + } + + _position -= totalPenetration; } else { - // This is special collision handling for when walking on a voxel field which - // prevents snagging at corners and seams. - - // sift through the collisions looking for one against the "floor" - int floorIndex = 0; - float distanceToFloor = 0.0f; - float penetrationWithFloor = 0.0f; - for (int i = 0; i < myCollisions.size(); ++i) { - CollisionInfo* collision = myCollisions[i]; - float distance = glm::dot(_gravity, collision->_contactPoint - _position); - if (distance > distanceToFloor) { - distanceToFloor = distance; - penetrationWithFloor = glm::dot(_gravity, collision->_penetration); - floorIndex = i; - } - } - - // step through the collisions again and apply each that is not redundant - glm::vec3 oldPosition = _position; - for (int i = 0; i < myCollisions.size(); ++i) { - CollisionInfo* collision = myCollisions[i]; - if (i == floorIndex) { - applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING); - _lastFloorContactPoint = collision->_contactPoint - collision->_penetration; - } else { - float distance = glm::dot(_gravity, collision->_contactPoint - oldPosition); - float penetration = glm::dot(_gravity, collision->_penetration); - if (fabsf(distance - distanceToFloor) > penetrationWithFloor || penetration > penetrationWithFloor) { - // resolution of the deepest penetration would not resolve this one - // so we apply the collision - applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING); - } - } - } + // we're not pushing into the edge, so let the avatar fall + applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING); } } else { - // no gravity -- apply all collisions - for (int i = 0; i < myCollisions.size(); ++i) { - CollisionInfo* collision = myCollisions[i]; - applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING); - } + applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING); } const float VOXEL_COLLISION_FREQUENCY = 0.5f; updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); } + _trapDuration = isTrapped ? _trapDuration + deltaTime : 0.0f; } void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity, float damping) { @@ -1281,6 +1375,11 @@ void MyAvatar::maybeUpdateBillboard() { if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) { return; } + foreach (Model* model, _attachmentModels) { + if (!model->isLoadedWithTextures()) { + return; + } + } QImage image = Application::getInstance()->renderAvatarBillboard(); _billboard.clear(); QBuffer buffer(&_billboard); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f893cc4f47..96249aec33 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); @@ -66,6 +66,9 @@ public: void saveData(QSettings* settings); void loadData(QSettings* settings); + void saveAttachmentData(const AttachmentData& attachment) const; + AttachmentData loadAttachmentData(const QUrl& modelURL) const; + // Set what driving keys are being pressed to control thrust levels void setDriveKeys(int key, float val) { _driveKeys[key] = val; }; bool getDriveKeys(int key) { return _driveKeys[key] != 0.f; }; @@ -86,7 +89,12 @@ public: virtual void clearJointData(int index); virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); - + virtual void setAttachmentData(const QVector& attachmentData); + + virtual void attach(const QString& modelURL, const QString& jointName = QString(), + const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, + bool allowDuplicates = false, bool useSaved = true); + virtual void setCollisionGroups(quint32 collisionGroups); void setMotionBehaviorsByScript(quint32 flags); @@ -125,6 +133,7 @@ private: bool _wasPushing; bool _isPushing; + float _trapDuration; // seconds that avatar has been trapped by collisions glm::vec3 _thrust; // final acceleration from outside sources for the current frame glm::vec3 _motorVelocity; // intended velocity of avatar motion @@ -146,7 +155,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/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/Model.cpp b/interface/src/renderer/Model.cpp index 2a9a55abf0..f46fd48beb 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -789,9 +789,9 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi } if (ShapeCollider::collideShapes(&sphere, _jointShapes[i], collisions)) { CollisionInfo* collision = collisions.getLastCollision(); - collision->_type = MODEL_COLLISION; + collision->_type = COLLISION_TYPE_MODEL; collision->_data = (void*)(this); - collision->_flags = i; + collision->_intData = i; collided = true; } outerContinue: ; @@ -805,9 +805,9 @@ bool Model::findPlaneCollisions(const glm::vec4& plane, CollisionList& collision for (int i = 0; i < _jointShapes.size(); i++) { if (ShapeCollider::collideShapes(&planeShape, _jointShapes[i], collisions)) { CollisionInfo* collision = collisions.getLastCollision(); - collision->_type = MODEL_COLLISION; + collision->_type = COLLISION_TYPE_MODEL; collision->_data = (void*)(this); - collision->_flags = i; + collision->_intData = i; collided = true; } } @@ -1256,15 +1256,15 @@ void Model::renderBoundingCollisionShapes(float alpha) { } bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const { - if (collision._type == MODEL_COLLISION) { + if (collision._type == COLLISION_TYPE_MODEL) { // the joint is pokable by a collision if it exists and is free to move - const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._flags]; + const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._intData]; if (joint.parentIndex == -1 || _jointStates.isEmpty()) { return false; } // an empty freeLineage means the joint can't move const FBXGeometry& geometry = _geometry->getFBXGeometry(); - int jointIndex = collision._flags; + int jointIndex = collision._intData; const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; return !freeLineage.isEmpty(); } @@ -1272,12 +1272,12 @@ bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const { } void Model::applyCollision(CollisionInfo& collision) { - if (collision._type != MODEL_COLLISION) { + if (collision._type != COLLISION_TYPE_MODEL) { return; } glm::vec3 jointPosition(0.f); - int jointIndex = collision._flags; + int jointIndex = collision._intData; if (getJointPosition(jointIndex, jointPosition)) { const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex]; if (joint.parentIndex != -1) { diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 016098699b..f9f49738e9 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -76,27 +76,28 @@ void AttachmentsDialog::addAttachment(const AttachmentData& data) { _attachments->insertWidget(_attachments->count() - 1, new AttachmentPanel(this, data)); } -static QDoubleSpinBox* createTranslationBox(AttachmentsDialog* dialog, float value) { +static QDoubleSpinBox* createTranslationBox(AttachmentPanel* panel, float value) { QDoubleSpinBox* box = new QDoubleSpinBox(); box->setSingleStep(0.01); box->setMinimum(-FLT_MAX); box->setMaximum(FLT_MAX); box->setValue(value); - dialog->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); + panel->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); return box; } -static QDoubleSpinBox* createRotationBox(AttachmentsDialog* dialog, float value) { +static QDoubleSpinBox* createRotationBox(AttachmentPanel* panel, float value) { QDoubleSpinBox* box = new QDoubleSpinBox(); box->setMinimum(-180.0); box->setMaximum(180.0); box->setWrapping(true); box->setValue(value); - dialog->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); + panel->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); return box; } -AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) { +AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) : + _dialog(dialog) { setFrameStyle(QFrame::StyledPanel); QFormLayout* layout = new QFormLayout(); @@ -107,7 +108,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData layout->addRow("Model URL:", urlBox); urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1); _modelURL->setText(data.modelURL.toString()); - dialog->connect(_modelURL, SIGNAL(returnPressed()), SLOT(updateAttachmentData())); + connect(_modelURL, SIGNAL(returnPressed()), SLOT(modelURLChanged())); QPushButton* chooseURL = new QPushButton("Choose"); urlBox->addWidget(chooseURL); connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL())); @@ -120,26 +121,26 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData } } _jointName->setCurrentText(data.jointName); - dialog->connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(updateAttachmentData())); + connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(updateAttachmentData())); QHBoxLayout* translationBox = new QHBoxLayout(); - translationBox->addWidget(_translationX = createTranslationBox(dialog, data.translation.x)); - translationBox->addWidget(_translationY = createTranslationBox(dialog, data.translation.y)); - translationBox->addWidget(_translationZ = createTranslationBox(dialog, data.translation.z)); + translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x)); + translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y)); + translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z)); layout->addRow("Translation:", translationBox); QHBoxLayout* rotationBox = new QHBoxLayout(); glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation)); - rotationBox->addWidget(_rotationX = createRotationBox(dialog, eulers.x)); - rotationBox->addWidget(_rotationY = createRotationBox(dialog, eulers.y)); - rotationBox->addWidget(_rotationZ = createRotationBox(dialog, eulers.z)); + rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x)); + rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y)); + rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z)); layout->addRow("Rotation:", rotationBox); layout->addRow("Scale:", _scale = new QDoubleSpinBox()); _scale->setSingleStep(0.01); _scale->setMaximum(FLT_MAX); _scale->setValue(data.scale); - dialog->connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); + connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); @@ -165,5 +166,30 @@ void AttachmentPanel::chooseModelURL() { void AttachmentPanel::setModelURL(const QString& url) { _modelURL->setText(url); - emit _modelURL->returnPressed(); + modelURLChanged(); +} + +void AttachmentPanel::modelURLChanged() { + // check for saved attachment data + AttachmentData attachment = Application::getInstance()->getAvatar()->loadAttachmentData(_modelURL->text()); + if (!attachment.jointName.isEmpty()) { + _jointName->setCurrentText(attachment.jointName); + _translationX->setValue(attachment.translation.x); + _translationY->setValue(attachment.translation.y); + _translationZ->setValue(attachment.translation.z); + glm::vec3 eulers = glm::degrees(safeEulerAngles(attachment.rotation)); + _rotationX->setValue(eulers.x); + _rotationY->setValue(eulers.y); + _rotationZ->setValue(eulers.z); + _scale->setValue(attachment.scale); + } + _dialog->updateAttachmentData(); +} + +void AttachmentPanel::updateAttachmentData() { + // save the attachment data under the model URL (if any) + if (!_modelURL->text().isEmpty()) { + Application::getInstance()->getAvatar()->saveAttachmentData(getAttachmentData()); + } + _dialog->updateAttachmentData(); } diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h index 4e67ae8882..7e9319fba8 100644 --- a/interface/src/ui/AttachmentsDialog.h +++ b/interface/src/ui/AttachmentsDialog.h @@ -60,9 +60,12 @@ private slots: void chooseModelURL(); void setModelURL(const QString& url); + void modelURLChanged(); + void updateAttachmentData(); private: + AttachmentsDialog* _dialog; QLineEdit* _modelURL; QComboBox* _jointName; QDoubleSpinBox* _translationX; diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index 0fac5a338c..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) { 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/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index bbfff8f025..94066d9a1c 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -660,16 +661,87 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { updateJointMappings(); } -void AvatarData::setAttachmentData(const QVector& attachmentData) { - _attachmentData = attachmentData; -} - void AvatarData::setDisplayName(const QString& displayName) { _displayName = displayName; qDebug() << "Changing display name for avatar to" << displayName; } +QVector AvatarData::getAttachmentData() const { + if (QThread::currentThread() != thread()) { + QVector result; + QMetaObject::invokeMethod(const_cast(this), "getAttachmentData", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVector, result)); + return result; + } + return _attachmentData; +} + +void AvatarData::setAttachmentData(const QVector& attachmentData) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setAttachmentData", Q_ARG(const QVector&, attachmentData)); + return; + } + _attachmentData = attachmentData; +} + +void AvatarData::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, + const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "attach", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName), + Q_ARG(const glm::vec3&, translation), Q_ARG(const glm::quat&, rotation), + Q_ARG(float, scale), Q_ARG(bool, allowDuplicates), Q_ARG(bool, useSaved)); + return; + } + QVector attachmentData = getAttachmentData(); + if (!allowDuplicates) { + foreach (const AttachmentData& data, attachmentData) { + if (data.modelURL == modelURL && (jointName.isEmpty() || data.jointName == jointName)) { + return; + } + } + } + AttachmentData data; + data.modelURL = modelURL; + data.jointName = jointName; + data.translation = translation; + data.rotation = rotation; + data.scale = scale; + attachmentData.append(data); + setAttachmentData(attachmentData); +} + +void AvatarData::detachOne(const QString& modelURL, const QString& jointName) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "detachOne", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName)); + return; + } + QVector attachmentData = getAttachmentData(); + for (QVector::iterator it = attachmentData.begin(); it != attachmentData.end(); it++) { + if (it->modelURL == modelURL && (jointName.isEmpty() || it->jointName == jointName)) { + attachmentData.erase(it); + setAttachmentData(attachmentData); + return; + } + } +} + +void AvatarData::detachAll(const QString& modelURL, const QString& jointName) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "detachAll", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName)); + return; + } + QVector attachmentData = getAttachmentData(); + for (QVector::iterator it = attachmentData.begin(); it != attachmentData.end(); ) { + if (it->modelURL == modelURL && (jointName.isEmpty() || it->jointName == jointName)) { + it = attachmentData.erase(it); + } else { + it++; + } + } + setAttachmentData(attachmentData); +} + void AvatarData::setBillboard(const QByteArray& billboard) { _billboard = billboard; @@ -792,3 +864,59 @@ QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) { attachment.translation >> attachment.rotation >> attachment.scale; } +void AttachmentDataObject::setModelURL(const QString& modelURL) const { + AttachmentData data = qscriptvalue_cast(thisObject()); + data.modelURL = modelURL; + thisObject() = engine()->toScriptValue(data); +} + +QString AttachmentDataObject::getModelURL() const { + return qscriptvalue_cast(thisObject()).modelURL.toString(); +} + +void AttachmentDataObject::setJointName(const QString& jointName) const { + AttachmentData data = qscriptvalue_cast(thisObject()); + data.jointName = jointName; + thisObject() = engine()->toScriptValue(data); +} + +QString AttachmentDataObject::getJointName() const { + return qscriptvalue_cast(thisObject()).jointName; +} + +void AttachmentDataObject::setTranslation(const glm::vec3& translation) const { + AttachmentData data = qscriptvalue_cast(thisObject()); + data.translation = translation; + thisObject() = engine()->toScriptValue(data); +} + +glm::vec3 AttachmentDataObject::getTranslation() const { + return qscriptvalue_cast(thisObject()).translation; +} + +void AttachmentDataObject::setRotation(const glm::quat& rotation) const { + AttachmentData data = qscriptvalue_cast(thisObject()); + data.rotation = rotation; + thisObject() = engine()->toScriptValue(data); +} + +glm::quat AttachmentDataObject::getRotation() const { + return qscriptvalue_cast(thisObject()).rotation; +} + +void AttachmentDataObject::setScale(float scale) const { + AttachmentData data = qscriptvalue_cast(thisObject()); + data.scale = scale; + thisObject() = engine()->toScriptValue(data); +} + +float AttachmentDataObject::getScale() const { + return qscriptvalue_cast(thisObject()).scale; +} + +void registerAvatarTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType >(engine); + engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( + new AttachmentDataObject(), QScriptEngine::ScriptOwnership)); +} + diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 79ca638585..dd604d06f5 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -41,11 +41,11 @@ typedef unsigned long long quint64; #include #include #include +#include #include #include #include -#include #include @@ -124,6 +124,7 @@ class AvatarData : public QObject { Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName) Q_PROPERTY(QString faceModelURL READ getFaceModelURLFromScript WRITE setFaceModelURLFromScript) Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) + Q_PROPERTY(QVector attachmentData READ getAttachmentData WRITE setAttachmentData) Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL) Q_PROPERTY(QStringList jointNames READ getJointNames) @@ -230,13 +231,22 @@ public: const QUrl& getFaceModelURL() const { return _faceModelURL; } QString getFaceModelURLString() const { return _faceModelURL.toString(); } const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } - const QVector& getAttachmentData() const { return _attachmentData; } const QString& getDisplayName() const { return _displayName; } virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); - virtual void setAttachmentData(const QVector& attachmentData); + virtual void setDisplayName(const QString& displayName); + Q_INVOKABLE QVector getAttachmentData() const; + Q_INVOKABLE virtual void setAttachmentData(const QVector& attachmentData); + + Q_INVOKABLE virtual void attach(const QString& modelURL, const QString& jointName = QString(), + const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, + bool allowDuplicates = false, bool useSaved = true); + + Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString()); + Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString()); + virtual void setBillboard(const QByteArray& billboard); const QByteArray& getBillboard() const { return _billboard; } @@ -348,4 +358,36 @@ public: QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment); QDataStream& operator>>(QDataStream& in, AttachmentData& attachment); +Q_DECLARE_METATYPE(AttachmentData) +Q_DECLARE_METATYPE(QVector) + +/// Scriptable wrapper for attachments. +class AttachmentDataObject : public QObject, protected QScriptable { + Q_OBJECT + Q_PROPERTY(QString modelURL READ getModelURL WRITE setModelURL) + Q_PROPERTY(QString jointName READ getJointName WRITE setJointName) + Q_PROPERTY(glm::vec3 translation READ getTranslation WRITE setTranslation) + Q_PROPERTY(glm::quat rotation READ getRotation WRITE setRotation) + Q_PROPERTY(float scale READ getScale WRITE setScale) + +public: + + Q_INVOKABLE void setModelURL(const QString& modelURL) const; + Q_INVOKABLE QString getModelURL() const; + + Q_INVOKABLE void setJointName(const QString& jointName) const; + Q_INVOKABLE QString getJointName() const; + + Q_INVOKABLE void setTranslation(const glm::vec3& translation) const; + Q_INVOKABLE glm::vec3 getTranslation() const; + + Q_INVOKABLE void setRotation(const glm::quat& rotation) const; + Q_INVOKABLE glm::quat getRotation() const; + + Q_INVOKABLE void setScale(float scale) const; + Q_INVOKABLE float getScale() const; +}; + +void registerAvatarTypes(QScriptEngine* engine); + #endif // hifi_AvatarData_h diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index d637526067..1fc03ceb66 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -826,7 +826,7 @@ ExtractedMesh extractMesh(const FBXNode& object) { while (endIndex < data.polygonIndices.size() && data.polygonIndices.at(endIndex++) >= 0); QPair materialTexture((polygonIndex < materials.size()) ? materials.at(polygonIndex) : 0, - (polygonIndex < textures.size()) ? textures.at(polygonIndex) : -1); + (polygonIndex < textures.size()) ? textures.at(polygonIndex) : 0); int& partIndex = materialTextureParts[materialTexture]; if (partIndex == 0) { data.extracted.partMaterialTextures.append(materialTexture); @@ -972,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; @@ -1515,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(); @@ -1587,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/models/src/ModelItem.h b/libraries/models/src/ModelItem.h index 76a78122ff..9edcf482c0 100644 --- a/libraries/models/src/ModelItem.h +++ b/libraries/models/src/ModelItem.h @@ -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 cef38a9422..45694b081d 100644 --- a/libraries/models/src/ModelTree.cpp +++ b/libraries/models/src/ModelTree.cpp @@ -113,13 +113,10 @@ void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& send FindAndUpdateModelOperator theOperator(model); recurseTreeWithOperator(&theOperator); - // if we didn't find it in the tree, then store it... if (!theOperator.wasFound()) { - 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); + 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 @@ -494,12 +491,29 @@ void ModelTree::update() { lockForWrite(); _isDirty = true; - // TODO: we don't need to update models yet, but when we do, for example - // when we add animation support, we will revisit this code. - //ModelTreeUpdateArgs args = { }; - //recurseTreeWithOperation(updateOperation, &args); + ModelTreeUpdateArgs args = { }; + recurseTreeWithOperation(updateOperation, &args); - // Now is a reasonable time to prune the tree... + // 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 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 modelItemID = args._movingModels[i].getID(); + quint64 deletedAt = usecTimestampNow(); + _recentlyDeletedModelsLock.lockForWrite(); + _recentlyDeletedModelItemIDs.insert(deletedAt, modelItemID); + _recentlyDeletedModelsLock.unlock(); + } + } + + // prune the tree... recurseTreeWithOperation(pruneOperation, NULL); unlock(); } diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index 0327d8a0c4..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,6 +167,7 @@ bool ModelTreeElement::updateModel(const ModelItem& model) { (localOlder ? "OLDER" : "NEWER"), difference, debug::valueOf(model.isNewlyCreated()) ); } + thisModel.copyChangedProperties(model); markWithChangedTime(); } else { 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 d308d007cd..5b766ecdd7 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -578,6 +578,10 @@ 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 { @@ -1001,7 +1005,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, // 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) { @@ -1342,7 +1346,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, OctreeElement* childElement = element->getChildAtIndex(i); if (childElement) { int bytesBeforeChild = packetData->getUncompressedSize(); - continueThisLevel = childElement->appendElementData(packetData); + continueThisLevel = childElement->appendElementData(packetData, params); int bytesAfterChild = packetData->getUncompressedSize(); if (!continueThisLevel) { diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 6e0693dc23..4a17cb3c1d 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -227,6 +227,7 @@ 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); 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/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/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index 57a339d81f..358c5a1b84 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -224,7 +224,7 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) { // (doing this prevents some "collision snagging" when particle penetrates the object) // HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against them. - if (collision->_type == PADDLE_HAND_COLLISION) { + if (collision->_type == COLLISION_TYPE_PADDLE_HAND) { // NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle. // TODO: make this less hacky when we have more per-collision details float elasticity = ELASTICITY; 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..be97b37b46 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) : @@ -201,6 +210,7 @@ void ScriptEngine::init() { registerEventTypes(&_engine); registerMenuItemProperties(&_engine); registerAnimationTypes(&_engine); + registerAvatarTypes(&_engine); qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue); qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue); @@ -226,6 +236,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/shared/src/CollisionInfo.cpp b/libraries/shared/src/CollisionInfo.cpp index 22929fbd51..38e3a4b2db 100644 --- a/libraries/shared/src/CollisionInfo.cpp +++ b/libraries/shared/src/CollisionInfo.cpp @@ -38,19 +38,24 @@ CollisionInfo* CollisionList::getLastCollision() { } void CollisionList::clear() { + // we rely on the external context to properly set or clear the data members of a collision + // whenever it is used. + /* for (int i = 0; i < _size; ++i) { // we only clear the important stuff CollisionInfo& collision = _collisions[i]; - collision._type = BASE_COLLISION; - collision._data = NULL; // CollisionInfo does not own whatever this points to. - collision._flags = 0; - // we rely on the consumer to properly overwrite these fields when the collision is "created" + collision._type = COLLISION_TYPE_UNKNOWN; + //collision._data = NULL; + //collision._intData = 0; + //collision._floatDAta = 0.0f; + //collision._vecData = glm::vec3(0.0f); //collision._damping; //collision._elasticity; //collision._contactPoint; //collision._penetration; //collision._addedVelocity; } + */ _size = 0; } diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h index f014a31f36..52d5298fde 100644 --- a/libraries/shared/src/CollisionInfo.h +++ b/libraries/shared/src/CollisionInfo.h @@ -18,9 +18,14 @@ #include enum CollisionType { - BASE_COLLISION = 0, - PADDLE_HAND_COLLISION, - MODEL_COLLISION, + COLLISION_TYPE_UNKNOWN = 0, + COLLISION_TYPE_PADDLE_HAND, + COLLISION_TYPE_MODEL, + // _data = pointer to Model that owns joint + // _intData = joint index + COLLISION_TYPE_AACUBE, + // _floatData = cube side + // _vecData = cube center }; const quint32 COLLISION_GROUP_ENVIRONMENT = 1U << 0; @@ -39,7 +44,7 @@ public: CollisionInfo() : _type(0), _data(NULL), - _flags(0), + _intData(0), _damping(0.f), _elasticity(1.f), _contactPoint(0.f), @@ -50,7 +55,7 @@ public: CollisionInfo(qint32 type) : _type(type), _data(NULL), - _flags(0), + _intData(0), _damping(0.f), _elasticity(1.f), _contactPoint(0.f), @@ -60,9 +65,13 @@ public: ~CollisionInfo() {} - qint32 _type; // type of Collision (will determine what is supposed to be in _data and _flags) - void* _data; // pointer to user supplied data - quint32 _flags; // 32 bits for whatever + int _type; // type of Collision + + // the value of the *Data fields depend on the type + void* _data; + int _intData; + float _floatData; + glm::vec3 _vecData; float _damping; // range [0,1] of friction coeficient float _elasticity; // range [0,1] of energy conservation diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index 348f8ac97d..7c29fbae00 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -132,6 +132,7 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis // penetration points from A into B CollisionInfo* collision = collisions.getNewCollision(); if (collision) { + collision->_type = COLLISION_TYPE_UNKNOWN; collision->_penetration = BA * (totalRadius - distance); // contactPoint is on surface of A collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * BA; @@ -179,6 +180,7 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col collision->_penetration = (totalRadius - radialDistance) * radialAxis; // points from A into B // contactPoint is on surface of sphereA collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * radialAxis; + collision->_type = COLLISION_TYPE_UNKNOWN; } else { // A is on B's axis, so the penetration is undefined... if (absAxialDistance > capsuleB->getHalfHeight()) { @@ -200,6 +202,7 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col collision->_penetration = (sign * (totalRadius + capsuleB->getHalfHeight() - absAxialDistance)) * capsuleAxis; // contactPoint is on surface of sphereA collision->_contactPoint = sphereA->getPosition() + (sign * sphereA->getRadius()) * capsuleAxis; + collision->_type = COLLISION_TYPE_UNKNOWN; } return true; } @@ -215,6 +218,7 @@ bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, Collision } collision->_penetration = penetration; collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * glm::normalize(penetration); + collision->_type = COLLISION_TYPE_UNKNOWN; return true; } return false; @@ -264,6 +268,7 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col collision->_penetration = (radialDistance - totalRadius) * radialAxis; // points from A into B // contactPoint is on surface of capsuleA collision->_contactPoint = closestApproach - capsuleA->getRadius() * radialAxis; + collision->_type = COLLISION_TYPE_UNKNOWN; } else { // A is on B's axis, so the penetration is undefined... if (absAxialDistance > capsuleA->getHalfHeight()) { @@ -284,6 +289,7 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col collision->_penetration = (sign * (totalRadius + capsuleA->getHalfHeight() - absAxialDistance)) * capsuleAxis; // contactPoint is on surface of sphereA collision->_contactPoint = closestApproach + (sign * capsuleA->getRadius()) * capsuleAxis; + collision->_type = COLLISION_TYPE_UNKNOWN; } } return true; @@ -355,6 +361,7 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, collision->_penetration = BA * (totalRadius - distance); // contactPoint is on surface of A collision->_contactPoint = centerA + distanceA * axisA + capsuleA->getRadius() * BA; + collision->_type = COLLISION_TYPE_UNKNOWN; return true; } } else { @@ -420,6 +427,7 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, // average the internal pair, and then do the math from centerB collision->_contactPoint = centerB + (0.5f * (points[1] + points[2])) * axisB + (capsuleA->getRadius() - distance) * BA; + collision->_type = COLLISION_TYPE_UNKNOWN; return true; } } @@ -439,6 +447,7 @@ bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, Collis collision->_penetration = penetration; glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end; collision->_contactPoint = deepestEnd + capsuleA->getRadius() * glm::normalize(penetration); + collision->_type = COLLISION_TYPE_UNKNOWN; return true; } return false; @@ -454,6 +463,7 @@ bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, Collision collision->_penetration = -penetration; collision->_contactPoint = sphereB->getPosition() + (sphereB->getRadius() / glm::length(penetration) - 1.0f) * penetration; + collision->_type = COLLISION_TYPE_UNKNOWN; return true; } return false; @@ -472,6 +482,7 @@ bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, Collis collision->_penetration = -penetration; glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end; collision->_contactPoint = deepestEnd + (capsuleB->getRadius() / glm::length(penetration) - 1.0f) * penetration; + collision->_type = COLLISION_TYPE_UNKNOWN; return true; } return false; @@ -653,12 +664,19 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm:: glm::vec3 direction; BA /= maxBA; glm::modf(BA, direction); - direction = glm::normalize(direction); + float lengthDirection = glm::length(direction); + direction /= lengthDirection; // compute collision details - collision->_penetration = (halfCubeSide + sphereRadius - distance * glm::dot(BA, direction)) * direction; + collision->_type = COLLISION_TYPE_AACUBE; + collision->_floatData = cubeSide; + collision->_vecData = cubeCenter; + collision->_penetration = (halfCubeSide * lengthDirection + sphereRadius - maxBA * glm::dot(BA, direction)) * direction; collision->_contactPoint = sphereCenter + sphereRadius * direction; } + collision->_type = COLLISION_TYPE_AACUBE; + collision->_floatData = cubeSide; + collision->_vecData = cubeCenter; return true; } else if (sphereRadius + halfCubeSide > distance) { // NOTE: for cocentric approximation we collide sphere and cube as two spheres which means @@ -669,6 +687,10 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm:: collision->_penetration = (sphereRadius + halfCubeSide) * glm::vec3(0.0f, -1.0f, 0.0f); // contactPoint is on surface of A collision->_contactPoint = sphereCenter + collision->_penetration; + + collision->_type = COLLISION_TYPE_AACUBE; + collision->_floatData = cubeSide; + collision->_vecData = cubeCenter; return true; } } 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();