diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e464e9eca..279f6a6776 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,6 @@ set(CMAKE_AUTOMOC ON) add_subdirectory(animation-server) add_subdirectory(assignment-client) add_subdirectory(domain-server) -add_subdirectory(eve) add_subdirectory(interface) add_subdirectory(injector) add_subdirectory(pairing-server) diff --git a/animation-server/src/main.cpp b/animation-server/src/main.cpp index 125a14f6b6..2e13636eda 100644 --- a/animation-server/src/main.cpp +++ b/animation-server/src/main.cpp @@ -628,7 +628,7 @@ void* animateVoxels(void* args) { int packetsEnding = ::voxelEditPacketSender->packetsToSendCount(); if (firstTime) { - int packetsPerSecond = (packetsEnding - packetsStarting) * (ACTUAL_FPS); + int packetsPerSecond = std::max(ACTUAL_FPS, (packetsEnding - packetsStarting) * (ACTUAL_FPS)); std::cout << "Setting PPS to " << packetsPerSecond << "\n"; diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 2906233d78..133a72859e 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include "Agent.h" @@ -50,7 +51,7 @@ void Agent::run() { // figure out the URL for the script for this agent assignment QString scriptURLString("http://%1:8080/assignment/%2"); scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainIP().toString(), - this->getUUIDStringWithoutCurlyBraces()); + uuidStringWithoutCurlyBraces(_uuid)); // setup curl for script download CURLcode curlResult; diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 1a4f9844eb..5c7e47a42a 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -98,7 +98,7 @@ void AvatarMixer::run() { nodeList->startSilentNodeRemovalThread(); - sockaddr* nodeAddress = new sockaddr; + sockaddr nodeAddress = {}; ssize_t receivedBytes = 0; unsigned char* packetData = new unsigned char[MAX_PACKET_SIZE]; @@ -107,8 +107,6 @@ void AvatarMixer::run() { Node* avatarNode = NULL; timeval lastDomainServerCheckIn = {}; - // we only need to hear back about avatar nodes from the DS - nodeList->setNodeTypesOfInterest(&NODE_TYPE_AGENT, 1); while (true) { @@ -122,7 +120,7 @@ void AvatarMixer::run() { NodeList::getInstance()->sendDomainServerCheckIn(_uuid.toRfc4122().constData()); } - if (nodeList->getNodeSocket()->receive(nodeAddress, packetData, &receivedBytes) && + if (nodeList->getNodeSocket()->receive(&nodeAddress, packetData, &receivedBytes) && packetVersionMatch(packetData)) { switch (packetData[0]) { case PACKET_TYPE_HEAD_DATA: @@ -130,12 +128,12 @@ void AvatarMixer::run() { unpackNodeId(packetData + numBytesForPacketHeader(packetData), &nodeID); // add or update the node in our list - avatarNode = nodeList->addOrUpdateNode(nodeAddress, nodeAddress, NODE_TYPE_AGENT, nodeID); + avatarNode = nodeList->addOrUpdateNode(&nodeAddress, &nodeAddress, NODE_TYPE_AGENT, nodeID); // parse positional data from an node nodeList->updateNodeWithData(avatarNode, packetData, receivedBytes); case PACKET_TYPE_INJECT_AUDIO: - broadcastAvatarData(nodeList, nodeAddress); + broadcastAvatarData(nodeList, &nodeAddress); break; case PACKET_TYPE_AVATAR_URLS: case PACKET_TYPE_AVATAR_FACE_VIDEO: @@ -151,7 +149,7 @@ void AvatarMixer::run() { break; default: // hand this off to the NodeList - nodeList->processNodeData(nodeAddress, packetData, receivedBytes); + nodeList->processNodeData(&nodeAddress, packetData, receivedBytes); break; } } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index a062c22ad3..62dfea3857 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -12,6 +12,7 @@ #include #include +#include #include "DomainServer.h" @@ -62,7 +63,7 @@ void DomainServer::civetwebUploadHandler(struct mg_connection *connection, const QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION); newPath += "/"; // append the UUID for this script as the new filename, remove the curly braces - newPath += scriptAssignment->getUUIDStringWithoutCurlyBraces(); + newPath += uuidStringWithoutCurlyBraces(scriptAssignment->getUUID()); // rename the saved script to the GUID of the assignment and move it to the script host locaiton rename(path, newPath.toLocal8Bit().constData()); diff --git a/eve/CMakeLists.txt b/eve/CMakeLists.txt deleted file mode 100644 index 2b281f2783..0000000000 --- a/eve/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -set(ROOT_DIR ..) -set(MACRO_DIR ${ROOT_DIR}/cmake/macros) - -# setup for find modules -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") - -set(TARGET_NAME eve) - -include(${MACRO_DIR}/SetupHifiProject.cmake) -setup_hifi_project(${TARGET_NAME} TRUE) - -include(${MACRO_DIR}/IncludeGLM.cmake) -include_glm(${TARGET_NAME} ${ROOT_DIR}) - -# link the required hifi libraries -include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(avatars ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR}) diff --git a/eve/resources/audio/eve.raw b/eve/resources/audio/eve.raw deleted file mode 100644 index 012fbe0e23..0000000000 Binary files a/eve/resources/audio/eve.raw and /dev/null differ diff --git a/eve/src/main.cpp b/eve/src/main.cpp deleted file mode 100644 index 5bbe5c7eac..0000000000 --- a/eve/src/main.cpp +++ /dev/null @@ -1,212 +0,0 @@ -// -// main.cpp -// eve -// -// Created by Stephen Birarda on 4/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -// - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -const int EVE_NODE_LISTEN_PORT = 55441; - -const float RANDOM_POSITION_MAX_DIMENSION = 10.0f; - -const float DATA_SEND_INTERVAL_MSECS = 15; -const float MIN_AUDIO_SEND_INTERVAL_SECS = 10; -const int MIN_ITERATIONS_BETWEEN_AUDIO_SENDS = (MIN_AUDIO_SEND_INTERVAL_SECS * 1000) / DATA_SEND_INTERVAL_MSECS; -const int MAX_AUDIO_SEND_INTERVAL_SECS = 15; -const float MAX_ITERATIONS_BETWEEN_AUDIO_SENDS = (MAX_AUDIO_SEND_INTERVAL_SECS * 1000) / DATA_SEND_INTERVAL_MSECS; - -const int ITERATIONS_BEFORE_HAND_GRAB = 100; -const int HAND_GRAB_DURATION_ITERATIONS = 50; -const int HAND_TIMER_SLEEP_ITERATIONS = 50; - -const float EVE_PELVIS_HEIGHT = 0.565925f; - -const float AUDIO_INJECT_PROXIMITY = 0.4f; -const int EVE_VOLUME_BYTE = 190; - -const char EVE_AUDIO_FILENAME[] = "/etc/highfidelity/eve/resources/eve.raw"; - -bool stopReceiveNodeDataThread; - -void *receiveNodeData(void *args) { - sockaddr senderAddress; - ssize_t bytesReceived; - unsigned char incomingPacket[MAX_PACKET_SIZE]; - - NodeList* nodeList = NodeList::getInstance(); - - while (!::stopReceiveNodeDataThread) { - if (nodeList->getNodeSocket()->receive(&senderAddress, incomingPacket, &bytesReceived) && - packetVersionMatch(incomingPacket)) { - switch (incomingPacket[0]) { - case PACKET_TYPE_BULK_AVATAR_DATA: - // this is the positional data for other nodes - // pass that off to the nodeList processBulkNodeData method - nodeList->processBulkNodeData(&senderAddress, incomingPacket, bytesReceived); - - break; - default: - // have the nodeList handle list of nodes from DS, replies from other nodes, etc. - nodeList->processNodeData(&senderAddress, incomingPacket, bytesReceived); - break; - } - } - } - - pthread_exit(0); - return NULL; -} - -void createAvatarDataForNode(Node* node) { - if (!node->getLinkedData()) { - node->setLinkedData(new AvatarData(node)); - } -} - -int main(int argc, const char* argv[]) { - // new seed for random audio sleep times - srand(time(0)); - - // create an NodeList instance to handle communication with other nodes - NodeList* nodeList = NodeList::createInstance(NODE_TYPE_AGENT, EVE_NODE_LISTEN_PORT); - - // start the node list thread that will kill off nodes when they stop talking - nodeList->startSilentNodeRemovalThread(); - - pthread_t receiveNodeDataThread; - pthread_create(&receiveNodeDataThread, NULL, receiveNodeData, NULL); - - // create an AvatarData object, "eve" - AvatarData eve; - - // move eve away from the origin - // pick a random point inside a 10x10 grid - - eve.setPosition(glm::vec3(randFloatInRange(0, RANDOM_POSITION_MAX_DIMENSION), - EVE_PELVIS_HEIGHT, // this should be the same as the avatar's pelvis standing height - randFloatInRange(0, RANDOM_POSITION_MAX_DIMENSION))); - - // face any instance of eve down the z-axis - eve.setBodyYaw(0); - - // put her hand out so somebody can shake it - eve.setHandPosition(glm::vec3(eve.getPosition()[0] - 0.2, - 0.5, - eve.getPosition()[2] + 0.1)); - - // prepare the audio injection manager by giving it a handle to our node socket - AudioInjectionManager::setInjectorSocket(nodeList->getNodeSocket()); - - // read eve's audio data - AudioInjector eveAudioInjector(EVE_AUDIO_FILENAME); - - // lower Eve's volume by setting the attentuation modifier (this is a value out of 255) - eveAudioInjector.setVolume(EVE_VOLUME_BYTE); - - // set the position of the audio injector - eveAudioInjector.setPosition(eve.getPosition()); - - // register the callback for node data creation - nodeList->linkedDataCreateCallback = createAvatarDataForNode; - - unsigned char broadcastPacket[MAX_PACKET_SIZE]; - int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_HEAD_DATA); - - timeval thisSend; - int numMicrosecondsSleep = 0; - - int handStateTimer = 0; - - timeval lastDomainServerCheckIn = {}; - - // eve wants to hear about an avatar mixer and an audio mixer from the domain server - const char EVE_NODE_TYPES_OF_INTEREST[] = {NODE_TYPE_AVATAR_MIXER, NODE_TYPE_AUDIO_MIXER}; - NodeList::getInstance()->setNodeTypesOfInterest(EVE_NODE_TYPES_OF_INTEREST, sizeof(EVE_NODE_TYPES_OF_INTEREST)); - - while (true) { - // send a check in packet to the domain server if DOMAIN_SERVER_CHECK_IN_USECS has elapsed - if (usecTimestampNow() - usecTimestamp(&lastDomainServerCheckIn) >= DOMAIN_SERVER_CHECK_IN_USECS) { - gettimeofday(&lastDomainServerCheckIn, NULL); - NodeList::getInstance()->sendDomainServerCheckIn(); - } - - // update the thisSend timeval to the current time - gettimeofday(&thisSend, NULL); - - // find the current avatar mixer - Node* avatarMixer = nodeList->soloNodeOfType(NODE_TYPE_AVATAR_MIXER); - - // make sure we actually have an avatar mixer with an active socket - if (nodeList->getOwnerID() != UNKNOWN_NODE_ID && avatarMixer && avatarMixer->getActiveSocket() != NULL) { - unsigned char* packetPosition = broadcastPacket + numHeaderBytes; - packetPosition += packNodeId(packetPosition, nodeList->getOwnerID()); - - // use the getBroadcastData method in the AvatarData class to populate the broadcastPacket buffer - packetPosition += eve.getBroadcastData(packetPosition); - - // use the UDPSocket instance attached to our node list to send avatar data to mixer - nodeList->getNodeSocket()->send(avatarMixer->getActiveSocket(), broadcastPacket, packetPosition - broadcastPacket); - } - - if (!eveAudioInjector.isInjectingAudio()) { - // enumerate the other nodes to decide if one is close enough that eve should talk - for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { - AvatarData* avatarData = (AvatarData*) node->getLinkedData(); - - if (avatarData) { - glm::vec3 tempVector = eve.getPosition() - avatarData->getPosition(); - float squareDistance = glm::dot(tempVector, tempVector); - - if (squareDistance <= AUDIO_INJECT_PROXIMITY) { - // look for an audio mixer in our node list - Node* audioMixer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AUDIO_MIXER); - - if (audioMixer) { - // update the destination socket for the AIM, in case the mixer has changed - AudioInjectionManager::setDestinationSocket(*audioMixer->getPublicSocket()); - - // we have an active audio mixer we can send data to - AudioInjectionManager::threadInjector(&eveAudioInjector); - } - } - } - } - } - - // simulate the effect of pressing and un-pressing the mouse button/pad - handStateTimer++; - - if (handStateTimer == ITERATIONS_BEFORE_HAND_GRAB) { - eve.setHandState(1); - } else if (handStateTimer == ITERATIONS_BEFORE_HAND_GRAB + HAND_GRAB_DURATION_ITERATIONS) { - eve.setHandState(0); - } else if (handStateTimer >= ITERATIONS_BEFORE_HAND_GRAB + HAND_GRAB_DURATION_ITERATIONS + HAND_TIMER_SLEEP_ITERATIONS) { - handStateTimer = 0; - } - - // sleep for the correct amount of time to have data send be consistently timed - if ((numMicrosecondsSleep = (DATA_SEND_INTERVAL_MSECS * 1000) - (usecTimestampNow() - usecTimestamp(&thisSend))) > 0) { - usleep(numMicrosecondsSleep); - } - } - - // stop the receive node data thread - stopReceiveNodeDataThread = true; - pthread_join(receiveNodeDataThread, NULL); - - // stop the node list's threads - nodeList->stopSilentNodeRemovalThread(); -} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 46a2b9afde..5a3d85ded8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -58,6 +58,7 @@ #include #include "Application.h" +#include "DataServerClient.h" #include "LogDisplay.h" #include "Menu.h" #include "Swatch.h" @@ -101,6 +102,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _voxelImporter(_window), _wantToKillLocalVoxels(false), _audioScope(256, 200, true), + _profile(QString()), _mouseX(0), _mouseY(0), _touchAvgX(0.0f), @@ -180,10 +182,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _settings = new QSettings(this); - // check if there is a saved domain server hostname - // this must be done now instead of with the other setting checks to allow manual override with - // --domain or --local options - NodeList::getInstance()->loadData(_settings); + // call Menu getInstance static method to set up the menu + _window->setMenuBar(Menu::getInstance()); // Check to see if the user passed in a command line option for loading a local // Voxel File. @@ -205,9 +205,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : NodeList::getInstance()->startSilentNodeRemovalThread(); _window->setCentralWidget(_glWidget); - - // call Menu getInstance static method to set up the menu - _window->setMenuBar(Menu::getInstance()); _networkAccessManager = new QNetworkAccessManager(this); QNetworkDiskCache* cache = new QNetworkDiskCache(_networkAccessManager); @@ -242,7 +239,6 @@ Application::~Application() { delete _oculusProgram; delete _settings; - delete _networkAccessManager; delete _followMode; delete _glWidget; } @@ -326,13 +322,20 @@ void Application::initializeGL() { void Application::paintGL() { PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings)); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::paintGL()"); - PerfStat("display"); glEnable(GL_LINE_SMOOTH); if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setTightness (100.0f); - _myCamera.setTargetPosition(_myAvatar.getUprightHeadPosition()); + glm::vec3 targetPosition = _myAvatar.getUprightHeadPosition(); + if (_myAvatar.getHead().getBlendFace().isActive()) { + // make sure we're aligned to the blend face eyes + glm::vec3 leftEyePosition, rightEyePosition; + if (_myAvatar.getHead().getBlendFace().getEyePositions(leftEyePosition, rightEyePosition, true)) { + targetPosition = (leftEyePosition + rightEyePosition) * 0.5f; + } + } + _myCamera.setTargetPosition(targetPosition); _myCamera.setTargetRotation(_myAvatar.getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f))); } else if (OculusManager::isConnected()) { @@ -344,7 +347,7 @@ void Application::paintGL() { } else if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { _myCamera.setTightness(0.0f); // In first person, camera follows head exactly without delay - _myCamera.setTargetPosition(_myAvatar.getUprightEyeLevelPosition()); + _myCamera.setTargetPosition(_myAvatar.getEyeLevelPosition()); _myCamera.setTargetRotation(_myAvatar.getHead().getCameraOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { @@ -447,6 +450,12 @@ void Application::updateProjectionMatrix() { glMatrixMode(GL_MODELVIEW); } +void Application::resetProfile(const QString& username) { + // call the destructor on the old profile and construct a new one + (&_profile)->~Profile(); + new (&_profile) Profile(username); +} + void Application::controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes, const char* nodeTypes, int numNodeTypes) { Application* self = getInstance(); @@ -1150,6 +1159,9 @@ void Application::timer() { // ask the node list to check in with the domain server NodeList::getInstance()->sendDomainServerCheckIn(); + + // give the MyAvatar object position to the Profile so it can propagate to the data-server + _profile.updatePosition(_myAvatar.getPosition()); } static glm::vec3 getFaceVector(BoxFace face) { @@ -1252,13 +1264,14 @@ void Application::processAvatarURLsMessage(unsigned char* packetData, size_t dat return; } QDataStream in(QByteArray((char*)packetData, dataBytes)); - QUrl voxelURL, faceURL; + QUrl voxelURL; in >> voxelURL; - in >> faceURL; // invoke the set URL functions on the simulate/render thread QMetaObject::invokeMethod(avatar->getVoxels(), "setVoxelURL", Q_ARG(QUrl, voxelURL)); - QMetaObject::invokeMethod(&avatar->getHead().getBlendFace(), "setModelURL", Q_ARG(QUrl, faceURL)); + + // use this timing to as the data-server for an updated mesh for this avatar (if we have UUID) + DataServerClient::getValueForKeyAndUUID(DataServerKey::FaceMeshURL, avatar->getUUID()); } void Application::processAvatarFaceVideoMessage(unsigned char* packetData, size_t dataBytes) { @@ -1585,6 +1598,11 @@ void Application::init() { _audio.setJitterBufferSamples(Menu::getInstance()->getAudioJitterBufferSamples()); } qDebug("Loaded settings.\n"); + + if (!_profile.getUsername().isEmpty()) { + // we have a username for this avatar, ask the data-server for the mesh URL for this avatar + DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL); + } // Set up VoxelSystem after loading preferences so we can get the desired max voxel count _voxels.setMaxVoxels(Menu::getInstance()->getMaxVoxels()); @@ -1594,7 +1612,7 @@ void Application::init() { _voxels.init(); - Avatar::sendAvatarURLsMessage(_myAvatar.getVoxels()->getVoxelURL(), _myAvatar.getHead().getBlendFace().getModelURL()); + Avatar::sendAvatarURLsMessage(_myAvatar.getVoxels()->getVoxelURL()); _palette.init(_glWidget->width(), _glWidget->height()); _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelAddMode), 0, 0); @@ -1636,9 +1654,9 @@ Avatar* Application::findLookatTargetAvatar(const glm::vec3& mouseRayOrigin, con glm::vec3 headPosition = avatar->getHead().getPosition(); float distance; if (rayIntersectsSphere(mouseRayOrigin, mouseRayDirection, headPosition, - HEAD_SPHERE_RADIUS * avatar->getScale(), distance)) { + HEAD_SPHERE_RADIUS * avatar->getHead().getScale(), distance)) { eyePosition = avatar->getHead().getEyePosition(); - _lookatIndicatorScale = avatar->getScale(); + _lookatIndicatorScale = avatar->getHead().getScale(); _lookatOtherPosition = headPosition; nodeID = avatar->getOwningNode()->getNodeID(); return avatar; @@ -1779,8 +1797,8 @@ void Application::update(float deltaTime) { _faceshift.getEstimatedEyePitch(), _faceshift.getEstimatedEyeYaw(), 0.0f))) * glm::vec3(0.0f, 0.0f, -1.0f); } - updateLookatTargetAvatar(lookAtRayOrigin, lookAtRayDirection, lookAtSpot); - if (_lookatTargetAvatar) { + updateLookatTargetAvatar(mouseRayOrigin, mouseRayDirection, lookAtSpot); + if (_lookatTargetAvatar && !_faceshift.isActive()) { // If the mouse is over another avatar's head... _myAvatar.getHead().setLookAtPosition(lookAtSpot); } else if (_isHoverVoxel && !_faceshift.isActive()) { @@ -1972,6 +1990,11 @@ void Application::update(float deltaTime) { _myAvatar.simulate(deltaTime, NULL); } + // Simulate particle cloud movements + if (Menu::getInstance()->isOptionChecked(MenuOption::ParticleCloud)) { + _cloud.simulate(deltaTime); + } + // no transmitter drive implies transmitter pick if (!Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _myTransmitter.isConnected()) { _transmitterPickStart = _myAvatar.getSkeleton().joint[AVATAR_JOINT_CHEST].position; @@ -2029,7 +2052,7 @@ void Application::update(float deltaTime) { if (_faceshift.isActive()) { const float EYE_OFFSET_SCALE = 0.025f; glm::vec3 position = _faceshift.getHeadTranslation() * EYE_OFFSET_SCALE; - _myCamera.setEyeOffsetPosition(glm::vec3(position.x * xSign, position.y, position.z)); + _myCamera.setEyeOffsetPosition(glm::vec3(position.x * xSign, position.y, -position.z)); updateProjectionMatrix(); } else if (_webcam.isActive()) { @@ -2068,8 +2091,7 @@ void Application::updateAvatar(float deltaTime) { _yawFromTouch = 0.f; // Update my avatar's state from gyros and/or webcam - _myAvatar.updateFromGyrosAndOrWebcam(Menu::getInstance()->isOptionChecked(MenuOption::GyroLook), - _pitchFromTouch); + _myAvatar.updateFromGyrosAndOrWebcam(_pitchFromTouch, Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)); // Update head mouse from faceshift if active if (_faceshift.isActive()) { @@ -2169,8 +2191,7 @@ void Application::updateAvatar(float deltaTime) { // once in a while, send my urls const float AVATAR_URLS_SEND_INTERVAL = 1.0f; // seconds if (shouldDo(AVATAR_URLS_SEND_INTERVAL, deltaTime)) { - Avatar::sendAvatarURLsMessage(_myAvatar.getVoxels()->getVoxelURL(), - _myAvatar.getHead().getBlendFace().getModelURL()); + Avatar::sendAvatarURLsMessage(_myAvatar.getVoxels()->getVoxelURL()); } } } @@ -2489,7 +2510,11 @@ void Application::displaySide(Camera& whichCamera) { glDisable(GL_NORMALIZE); //renderGroundPlaneGrid(EDGE_SIZE_GROUND_PLANE, _audio.getCollisionSoundMagnitude()); - } + } + // Draw Cloud Particles + if (Menu::getInstance()->isOptionChecked(MenuOption::ParticleCloud)) { + _cloud.render(); + } // Draw voxels if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), @@ -2499,6 +2524,7 @@ void Application::displaySide(Camera& whichCamera) { } } + // restore default, white specular glMaterialfv(GL_FRONT, GL_SPECULAR, WHITE_SPECULAR_COLOR); @@ -3495,9 +3521,16 @@ void Application::attachNewHeadToNode(Node* newNode) { void Application::domainChanged(QString domain) { qDebug("Application title set to: %s.\n", domain.toStdString().c_str()); _window->setWindowTitle(domain); + + // update the user's last domain in their Profile (which will propagate to data-server) + _profile.updateDomain(domain); + + // reset the environment so that we don't erroneously end up with multiple + _environment.resetToDefault(); } void Application::nodeAdded(Node* node) { + } void Application::nodeKilled(Node* node) { @@ -3608,6 +3641,12 @@ void* Application::networkReceive(void* args) { case PACKET_TYPE_AVATAR_FACE_VIDEO: processAvatarFaceVideoMessage(app->_incomingPacket, bytesReceived); break; + case PACKET_TYPE_DATA_SERVER_GET: + case PACKET_TYPE_DATA_SERVER_PUT: + case PACKET_TYPE_DATA_SERVER_SEND: + case PACKET_TYPE_DATA_SERVER_CONFIRM: + DataServerClient::processMessageFromDataServer(app->_incomingPacket, bytesReceived); + break; default: NodeList::getInstance()->processNodeData(&senderAddress, app->_incomingPacket, bytesReceived); break; diff --git a/interface/src/Application.h b/interface/src/Application.h index 620d6d57f5..4730c8fcc3 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -29,6 +29,7 @@ #include "BandwidthMeter.h" #include "Camera.h" +#include "Cloud.h" #include "Environment.h" #include "GLCanvas.h" #include "PacketHeaders.h" @@ -44,6 +45,7 @@ #include "VoxelImporter.h" #include "avatar/Avatar.h" #include "avatar/MyAvatar.h" +#include "avatar/Profile.h" #include "avatar/HandControl.h" #include "devices/Faceshift.h" #include "devices/SerialInterface.h" @@ -133,6 +135,9 @@ public: Avatar* getLookatTargetAvatar() const { return _lookatTargetAvatar; } + Profile* getProfile() { return &_profile; } + void resetProfile(const QString& username); + static void controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes, const char* nodeTypes, int numNodeTypes); @@ -261,6 +266,8 @@ private: Stars _stars; + Cloud _cloud; + VoxelSystem _voxels; VoxelTree _clipboard; // if I copy/paste VoxelImporter _voxelImporter; @@ -275,6 +282,7 @@ private: Oscilloscope _audioScope; MyAvatar _myAvatar; // The rendered avatar of oneself + Profile _profile; // The data-server linked profile for this user Transmitter _myTransmitter; // Gets UDP data from transmitter app used to animate the avatar diff --git a/interface/src/Cloud.cpp b/interface/src/Cloud.cpp new file mode 100644 index 0000000000..ba4c16b8fd --- /dev/null +++ b/interface/src/Cloud.cpp @@ -0,0 +1,85 @@ +// +// Cloud.cpp +// interface +// +// Created by Philip Rosedale on 11/17/12. +// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// + +#include +#include +#include "Cloud.h" +#include "Util.h" +#include "Field.h" + +const int NUM_PARTICLES = 100000; +const float FIELD_COUPLE = 0.001f; +const bool RENDER_FIELD = false; + +Cloud::Cloud() { + glm::vec3 box = glm::vec3(PARTICLE_WORLD_SIZE); + _bounds = box; + _count = NUM_PARTICLES; + _particles = new Particle[_count]; + _field = new Field(PARTICLE_WORLD_SIZE, FIELD_COUPLE); + + for (int i = 0; i < _count; i++) { + _particles[i].position = randVector() * box; + const float INIT_VEL_SCALE = 0.03f; + _particles[i].velocity = randVector() * ((float)PARTICLE_WORLD_SIZE * INIT_VEL_SCALE); + _particles[i].color = randVector(); + } +} + +void Cloud::render() { + if (RENDER_FIELD) { + _field->render(); + } + + glPointSize(3.0f); + glDisable(GL_TEXTURE_2D); + glEnable(GL_POINT_SMOOTH); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glVertexPointer(3, GL_FLOAT, 3 * sizeof(glm::vec3), &_particles[0].position); + glColorPointer(3, GL_FLOAT, 3 * sizeof(glm::vec3), &_particles[0].color); + glDrawArrays(GL_POINTS, 0, NUM_PARTICLES); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + +} + +void Cloud::simulate (float deltaTime) { + unsigned int i; + _field->simulate(deltaTime); + for (i = 0; i < _count; ++i) { + + // Update position + _particles[i].position += _particles[i].velocity * deltaTime; + + // Decay Velocity (Drag) + const float CONSTANT_DAMPING = 0.15f; + _particles[i].velocity *= (1.f - CONSTANT_DAMPING * deltaTime); + + // Interact with Field + _field->interact(deltaTime, _particles[i].position, _particles[i].velocity); + + // Update color to velocity + _particles[i].color = (glm::normalize(_particles[i].velocity) * 0.5f) + 0.5f; + + // Bounce at bounds + if ((_particles[i].position.x > _bounds.x) || (_particles[i].position.x < 0.f)) { + _particles[i].position.x = glm::clamp(_particles[i].position.x, 0.f, _bounds.x); + _particles[i].velocity.x *= -1.f; + } + if ((_particles[i].position.y > _bounds.y) || (_particles[i].position.y < 0.f)) { + _particles[i].position.y = glm::clamp(_particles[i].position.y, 0.f, _bounds.y); + _particles[i].velocity.y *= -1.f; + } + if ((_particles[i].position.z > _bounds.z) || (_particles[i].position.z < 0.f)) { + _particles[i].position.z = glm::clamp(_particles[i].position.z, 0.f, _bounds.z); + _particles[i].velocity.z *= -1.f; + } + } + } diff --git a/interface/src/Cloud.h b/interface/src/Cloud.h new file mode 100644 index 0000000000..fcf414b62e --- /dev/null +++ b/interface/src/Cloud.h @@ -0,0 +1,32 @@ +// +// Cloud.h +// interface +// +// Created by Philip Rosedale on 11/17/12. +// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__Cloud__ +#define __interface__Cloud__ + +#include "Field.h" + +#define PARTICLE_WORLD_SIZE 256.0 + +class Cloud { +public: + Cloud(); + void simulate(float deltaTime); + void render(); + +private: + struct Particle { + glm::vec3 position, velocity, color; + }* _particles; + + unsigned int _count; + glm::vec3 _bounds; + Field* _field; +}; + +#endif diff --git a/interface/src/DataServerClient.cpp b/interface/src/DataServerClient.cpp new file mode 100644 index 0000000000..26e297375c --- /dev/null +++ b/interface/src/DataServerClient.cpp @@ -0,0 +1,222 @@ +// +// DataServerClient.cpp +// hifi +// +// Created by Stephen Birarda on 10/7/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#include + +#include +#include +#include +#include + +#include "Application.h" +#include "avatar/Profile.h" + +#include "DataServerClient.h" + +std::map DataServerClient::_unmatchedPackets; + +const char MULTI_KEY_VALUE_SEPARATOR = '|'; + +const char DATA_SERVER_HOSTNAME[] = "data.highfidelity.io"; +const unsigned short DATA_SERVER_PORT = 3282; +const sockaddr_in DATA_SERVER_SOCKET = socketForHostnameAndHostOrderPort(DATA_SERVER_HOSTNAME, DATA_SERVER_PORT); + +void DataServerClient::putValueForKey(const QString& key, const char* value) { + QString clientString = Application::getInstance()->getProfile()->getUserString(); + if (!clientString.isEmpty()) { + + unsigned char* putPacket = new unsigned char[MAX_PACKET_SIZE]; + + // setup the header for this packet + int numPacketBytes = populateTypeAndVersion(putPacket, PACKET_TYPE_DATA_SERVER_PUT); + + // pack the client UUID, null terminated + memcpy(putPacket + numPacketBytes, clientString.toLocal8Bit().constData(), clientString.toLocal8Bit().size()); + numPacketBytes += clientString.toLocal8Bit().size(); + putPacket[numPacketBytes++] = '\0'; + + // pack a 1 to designate that we are putting a single value + putPacket[numPacketBytes++] = 1; + + // pack the key, null terminated + strcpy((char*) putPacket + numPacketBytes, key.toLocal8Bit().constData()); + numPacketBytes += key.size(); + putPacket[numPacketBytes++] = '\0'; + + // pack the value, null terminated + strcpy((char*) putPacket + numPacketBytes, value); + numPacketBytes += strlen(value); + putPacket[numPacketBytes++] = '\0'; + + // add the putPacket to our vector of unconfirmed packets, will be deleted once put is confirmed + // _unmatchedPackets.insert(std::pair(putPacket, numPacketBytes)); + + // send this put request to the data server + NodeList::getInstance()->getNodeSocket()->send((sockaddr*) &DATA_SERVER_SOCKET, putPacket, numPacketBytes); + } +} + +void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uuid) { + getValuesForKeysAndUUID(QStringList(key), uuid); +} + +void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid) { + if (!uuid.isNull()) { + getValuesForKeysAndUserString(keys, uuidStringWithoutCurlyBraces(uuid)); + } +} + +void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString) { + if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) { + unsigned char* getPacket = new unsigned char[MAX_PACKET_SIZE]; + + // setup the header for this packet + int numPacketBytes = populateTypeAndVersion(getPacket, PACKET_TYPE_DATA_SERVER_GET); + + // pack the user string (could be username or UUID string), null-terminate + memcpy(getPacket + numPacketBytes, userString.toLocal8Bit().constData(), userString.toLocal8Bit().size()); + numPacketBytes += userString.toLocal8Bit().size(); + getPacket[numPacketBytes++] = '\0'; + + // pack one byte to designate the number of keys + getPacket[numPacketBytes++] = keys.size(); + + QString keyString = keys.join(MULTI_KEY_VALUE_SEPARATOR); + + // pack the key string, null terminated + strcpy((char*) getPacket + numPacketBytes, keyString.toLocal8Bit().constData()); + numPacketBytes += keyString.size() + sizeof('\0'); + + // add the getPacket to our vector of uncofirmed packets, will be deleted once we get a response from the nameserver + // _unmatchedPackets.insert(std::pair(getPacket, numPacketBytes)); + + // send the get to the data server + NodeList::getInstance()->getNodeSocket()->send((sockaddr*) &DATA_SERVER_SOCKET, getPacket, numPacketBytes); + } +} + +void DataServerClient::getClientValueForKey(const QString& key) { + getValuesForKeysAndUserString(QStringList(key), Application::getInstance()->getProfile()->getUserString()); +} + +void DataServerClient::processConfirmFromDataServer(unsigned char* packetData, int numPacketBytes) { + removeMatchedPacketFromMap(packetData, numPacketBytes); +} + +void DataServerClient::processSendFromDataServer(unsigned char* packetData, int numPacketBytes) { + // pull the user string from the packet so we know who to associate this with + int numHeaderBytes = numBytesForPacketHeader(packetData); + + char* userStringPosition = (char*) packetData + numHeaderBytes; + + QString userString(QByteArray(userStringPosition, strlen(userStringPosition))); + + QUuid userUUID(userString); + + char* keysPosition = (char*) packetData + numHeaderBytes + strlen(userStringPosition) + + sizeof('\0') + sizeof(unsigned char); + char* valuesPosition = keysPosition + strlen(keysPosition) + sizeof('\0'); + + QStringList keyList = QString(keysPosition).split(MULTI_KEY_VALUE_SEPARATOR); + QStringList valueList = QString(valuesPosition).split(MULTI_KEY_VALUE_SEPARATOR); + + // user string was UUID, find matching avatar and associate data + for (int i = 0; i < keyList.size(); i++) { + if (valueList[i] != " ") { + if (keyList[i] == DataServerKey::FaceMeshURL) { + + if (userUUID.isNull() || userUUID == Application::getInstance()->getProfile()->getUUID()) { + qDebug("Changing user's face model URL to %s\n", valueList[0].toLocal8Bit().constData()); + Application::getInstance()->getProfile()->setFaceModelURL(QUrl(valueList[0])); + } else { + // mesh URL for a UUID, find avatar in our list + NodeList* nodeList = NodeList::getInstance(); + for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { + if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) { + Avatar* avatar = (Avatar *) node->getLinkedData(); + + if (avatar->getUUID() == userUUID) { + QMetaObject::invokeMethod(&avatar->getHead().getBlendFace(), + "setModelURL", + Q_ARG(QUrl, QUrl(valueList[0]))); + } + } + } + } + } else if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position + && valueList[i] != " " && valueList[i + 1] != " ") { + + QStringList coordinateItems = valueList[i + 1].split(','); + + if (coordinateItems.size() == 3) { + + qDebug() << "Changing domain to" << valueList[i].toLocal8Bit().constData() << + "and position to" << valueList[i + 1].toLocal8Bit().constData() << + "to go to" << userString << "\n"; + + NodeList::getInstance()->setDomainHostname(valueList[i]); + + glm::vec3 newPosition(coordinateItems[0].toFloat(), + coordinateItems[1].toFloat(), + coordinateItems[2].toFloat()); + Application::getInstance()->getAvatar()->setPosition(newPosition); + } + + } else if (keyList[i] == DataServerKey::UUID) { + // this is the user's UUID - set it on the profile + Application::getInstance()->getProfile()->setUUID(valueList[0]); + } + } + } + + // remove the matched packet from our map so it isn't re-sent to the data-server + // removeMatchedPacketFromMap(packetData, numPacketBytes); +} + +void DataServerClient::processMessageFromDataServer(unsigned char* packetData, int numPacketBytes) { + switch (packetData[0]) { + case PACKET_TYPE_DATA_SERVER_SEND: + processSendFromDataServer(packetData, numPacketBytes); + break; + case PACKET_TYPE_DATA_SERVER_CONFIRM: + processConfirmFromDataServer(packetData, numPacketBytes); + break; + default: + break; + } +} + +void DataServerClient::removeMatchedPacketFromMap(unsigned char* packetData, int numPacketBytes) { + for (std::map::iterator mapIterator = _unmatchedPackets.begin(); + mapIterator != _unmatchedPackets.end(); + ++mapIterator) { + if (memcmp(mapIterator->first + sizeof(PACKET_TYPE), + packetData + sizeof(PACKET_TYPE), + numPacketBytes - sizeof(PACKET_TYPE)) == 0) { + + // this is a match - remove the confirmed packet from the vector and delete associated member + // so it isn't sent back out + delete[] mapIterator->first; + _unmatchedPackets.erase(mapIterator); + + // we've matched the packet - bail out + break; + } + } +} + +void DataServerClient::resendUnmatchedPackets() { + for (std::map::iterator mapIterator = _unmatchedPackets.begin(); + mapIterator != _unmatchedPackets.end(); + ++mapIterator) { + // send the unmatched packet to the data server + NodeList::getInstance()->getNodeSocket()->send((sockaddr*) &DATA_SERVER_SOCKET, + mapIterator->first, + mapIterator->second); + } +} diff --git a/interface/src/DataServerClient.h b/interface/src/DataServerClient.h new file mode 100644 index 0000000000..dabb1c822c --- /dev/null +++ b/interface/src/DataServerClient.h @@ -0,0 +1,45 @@ +// +// DataServerClient.h +// hifi +// +// Created by Stephen Birarda on 10/7/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__DataServerClient__ +#define __hifi__DataServerClient__ + +#include + +#include + +#include "Application.h" + +class DataServerClient { +public: + static void putValueForKey(const QString& key, const char* value); + static void getClientValueForKey(const QString& key); + + static void getValueForKeyAndUUID(const QString& key, const QUuid& uuid); + static void getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid); + static void getValuesForKeysAndUserString(const QStringList& keys, const QString& userString); + + static void processConfirmFromDataServer(unsigned char* packetData, int numPacketBytes); + static void processSendFromDataServer(unsigned char* packetData, int numPacketBytes); + static void processMessageFromDataServer(unsigned char* packetData, int numPacketBytes); + static void removeMatchedPacketFromMap(unsigned char* packetData, int numPacketBytes); + static void resendUnmatchedPackets(); +private: + + + static std::map _unmatchedPackets; +}; + +namespace DataServerKey { + const QString Domain = "domain"; + const QString FaceMeshURL = "mesh"; + const QString Position = "position"; + const QString UUID = "uuid"; +} + +#endif /* defined(__hifi__DataServerClient__) */ diff --git a/interface/src/Field.cpp b/interface/src/Field.cpp new file mode 100644 index 0000000000..1f4024653b --- /dev/null +++ b/interface/src/Field.cpp @@ -0,0 +1,100 @@ +// +// Field.cpp +// interface +// +// Created by Philip Rosedale on 8/23/12. +// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// +// A vector-valued field over an array of elements arranged as a 3D lattice + +#include "Field.h" + +int Field::value(float *value, float *pos) { + int index = (int)(pos[0] / _worldSize * 10.0) + + (int)(pos[1] / _worldSize * 10.0) * 10 + + (int)(pos[2] / _worldSize * 10.0) * 100; + + if ((index >= 0) && (index < FIELD_ELEMENTS)) { + value[0] = _field[index].val.x; + value[1] = _field[index].val.y; + value[2] = _field[index].val.z; + return 1; + } else { + return 0; + } +} + +Field::Field(float worldSize, float coupling) { + _worldSize = worldSize; + _coupling = coupling; + //float fx, fy, fz; + for (int i = 0; i < FIELD_ELEMENTS; i++) { + const float FIELD_INITIAL_MAG = 0.0f; + _field[i].val = randVector() * FIELD_INITIAL_MAG * _worldSize; + _field[i].center.x = ((float)(i % 10) + 0.5f); + _field[i].center.y = ((float)(i % 100 / 10) + 0.5f); + _field[i].center.z = ((float)(i / 100) + 0.5f); + _field[i].center *= _worldSize / 10.f; + + } +} + +void Field::add(float* add, float *pos) { + int index = (int)(pos[0] / _worldSize * 10.0) + + (int)(pos[1] / _worldSize * 10.0) * 10 + + (int)(pos[2] / _worldSize * 10.0) * 100; + + if ((index >= 0) && (index < FIELD_ELEMENTS)) { + _field[index].val.x += add[0]; + _field[index].val.y += add[1]; + _field[index].val.z += add[2]; + } +} + +void Field::interact(float deltaTime, const glm::vec3& pos, glm::vec3& vel) { + + int index = (int)(pos.x / _worldSize * 10.0) + + (int)(pos.y / _worldSize*10.0) * 10 + + (int)(pos.z / _worldSize*10.0) * 100; + if ((index >= 0) && (index < FIELD_ELEMENTS)) { + vel += _field[index].val * deltaTime; // Particle influenced by field + _field[index].val += vel * deltaTime * _coupling; // Field influenced by particle + } +} + +void Field::simulate(float deltaTime) { + glm::vec3 neighbors, add, diff; + + for (int i = 0; i < FIELD_ELEMENTS; i++) { + const float CONSTANT_DAMPING = 0.5f; + _field[i].val *= (1.f - CONSTANT_DAMPING * deltaTime); + } +} + +void Field::render() { + int i; + float scale_view = 0.05f * _worldSize; + + glDisable(GL_LIGHTING); + glBegin(GL_LINES); + for (i = 0; i < FIELD_ELEMENTS; i++) { + glColor3f(0, 1, 0); + glVertex3fv(&_field[i].center.x); + glVertex3f(_field[i].center.x + _field[i].val.x * scale_view, + _field[i].center.y + _field[i].val.y * scale_view, + _field[i].center.z + _field[i].val.z * scale_view); + } + glEnd(); + + glColor3f(0, 1, 0); + glPointSize(4.0); + glEnable(GL_POINT_SMOOTH); + glBegin(GL_POINTS); + for (i = 0; i < FIELD_ELEMENTS; i++) { + glVertex3fv(&_field[i].center.x); + } + glEnd(); +} + + + diff --git a/interface/src/Field.h b/interface/src/Field.h new file mode 100644 index 0000000000..c85c3d33a4 --- /dev/null +++ b/interface/src/Field.h @@ -0,0 +1,46 @@ +// +// Field.h +// interface +// +// Created by Philip Rosedale on 8/23/12. +// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__Field__ +#define __interface__Field__ + +#include +#include +#include "InterfaceConfig.h" +#include "world.h" +#include "Util.h" + +const int FIELD_ELEMENTS = 1000; + +/// Field is a lattice of vectors uniformly distributed in 3D with FIELD_ELEMENTS^(1/3) per side +class Field { +public: + struct FieldElement { + glm::vec3 val; + glm::vec3 center; + glm::vec3 fld; + } _field[FIELD_ELEMENTS]; + + Field(float worldSize, float coupling); + /// The field value at a position in space, given simply as the value of the enclosing cell + int value(float *ret, float *pos); + /// Visualize the field as vector lines drawn at each center + void render(); + /// Add to the field value cell enclosing a location + void add(float* add, float *loc); + /// A particle with a position and velocity interacts with the field given the coupling + /// constant passed when creating the field. + void interact(float deltaTime, const glm::vec3& pos, glm::vec3& vel); + /// Field evolves over timestep + void simulate(float deltaTime); +private: + float _worldSize; + float _coupling; +}; + +#endif diff --git a/interface/src/InfoView.cpp b/interface/src/InfoView.cpp index 867c8f0f9e..cb10b2702d 100644 --- a/interface/src/InfoView.cpp +++ b/interface/src/InfoView.cpp @@ -20,6 +20,8 @@ InfoView::InfoView(bool forced) : _forced(forced) { + setWindowFlags(Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint); + switchToResourcesParentIfRequired(); QString absPath = QFileInfo("resources/html/interface-welcome-allsvg.html").absoluteFilePath(); QUrl url = QUrl::fromLocalFile(absPath); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8b92448e71..ac037902ec 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -19,8 +19,12 @@ #include #include #include +#include + +#include #include "Application.h" +#include "DataServerClient.h" #include "PairingHandler.h" #include "Menu.h" #include "Util.h" @@ -63,6 +67,12 @@ Menu::Menu() : SLOT(aboutApp())))->setMenuRole(QAction::AboutRole); #endif + (addActionToQMenuAndActionHash(fileMenu, + MenuOption::Login, + 0, + this, + SLOT(login()))); + (addActionToQMenuAndActionHash(fileMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_Comma, @@ -89,6 +99,11 @@ Menu::Menu() : Qt::CTRL | Qt::SHIFT | Qt::Key_L, this, SLOT(goToLocation())); + addActionToQMenuAndActionHash(fileMenu, + MenuOption::GoToUser, + Qt::CTRL | Qt::SHIFT | Qt::Key_U, + this, + SLOT(goToUser())); addDisabledActionAndSeparator(fileMenu, "Settings"); @@ -208,7 +223,13 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::OffAxisProjection, 0, - false); + true); + addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::TurnWithHead, + 0, + true); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HeadMouse, 0, false); + addDisabledActionAndSeparator(viewMenu, "Stats"); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash); @@ -230,6 +251,9 @@ Menu::Menu() : 0, appInstance->getGlowEffect(), SLOT(cycleRenderMode())); + + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ParticleCloud, 0, false); + QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options"); @@ -250,6 +274,11 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::FastVoxelPipeline, 0, false, appInstance->getVoxels(), SLOT(setUseFastVoxelPipeline(bool))); + + addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontRemoveOutOfView); + addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::HideOutOfView); + addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::ConstantCulling); + addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AutomaticallyAuditTree); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion); @@ -308,12 +337,6 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(raveGloveOptionsMenu, MenuOption::SimulateLeapHand); addCheckableActionToQMenuAndActionHash(raveGloveOptionsMenu, MenuOption::TestRaveGlove); - - QMenu* gyroOptionsMenu = developerMenu->addMenu("Gyro Options"); - addCheckableActionToQMenuAndActionHash(gyroOptionsMenu, MenuOption::GyroLook, 0, true); - addCheckableActionToQMenuAndActionHash(gyroOptionsMenu, MenuOption::HeadMouse); - - QMenu* trackingOptionsMenu = developerMenu->addMenu("Tracking Options"); addCheckableActionToQMenuAndActionHash(trackingOptionsMenu, MenuOption::SkeletonTracking, @@ -502,6 +525,8 @@ void Menu::loadSettings(QSettings* settings) { scanMenuBar(&loadAction, settings); Application::getInstance()->getAvatar()->loadData(settings); Application::getInstance()->getSwatch()->loadData(settings); + Application::getInstance()->getProfile()->loadData(settings); + NodeList::getInstance()->loadData(settings); } void Menu::saveSettings(QSettings* settings) { @@ -523,8 +548,7 @@ void Menu::saveSettings(QSettings* settings) { scanMenuBar(&saveAction, settings); Application::getInstance()->getAvatar()->saveData(settings); Application::getInstance()->getSwatch()->saveData(settings); - - // ask the NodeList to save its data + Application::getInstance()->getProfile()->saveData(settings); NodeList::getInstance()->saveData(settings); } @@ -706,20 +730,8 @@ void updateDSHostname(const QString& domainServerHostname) { newHostname = domainServerHostname; } - // check if the domain server hostname is new - if (NodeList::getInstance()->getDomainHostname() != newHostname) { - - NodeList::getInstance()->clear(); - - // kill the local voxels - Application::getInstance()->getVoxels()->killLocalVoxels(); - - // reset the environment to default - Application::getInstance()->getEnvironment()->resetToDefault(); - - // set the new hostname - NodeList::getInstance()->setDomainHostname(newHostname); - } + // give our nodeList the new domain-server hostname + NodeList::getInstance()->setDomainHostname(newHostname); } const int QLINE_MINIMUM_WIDTH = 400; @@ -740,8 +752,43 @@ QLineEdit* lineEditForDomainHostname() { return domainServerLineEdit; } + +void Menu::login() { + Application* applicationInstance = Application::getInstance(); + QDialog dialog(applicationInstance->getGLWidget()); + dialog.setWindowTitle("Login"); + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); + dialog.setLayout(layout); + + QFormLayout* form = new QFormLayout(); + layout->addLayout(form, 1); + + QString username = applicationInstance->getProfile()->getUsername(); + QLineEdit* usernameLineEdit = new QLineEdit(username); + usernameLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); + form->addRow("Username:", usernameLineEdit); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); + dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); + layout->addWidget(buttons); + + int ret = dialog.exec(); + applicationInstance->getWindow()->activateWindow(); + if (ret != QDialog::Accepted) { + return; + } + + if (usernameLineEdit->text() != username) { + // there has been a username change + // ask for a profile reset with the new username + applicationInstance->resetProfile(usernameLineEdit->text()); + } +} + void Menu::editPreferences() { Application* applicationInstance = Application::getInstance(); + QDialog dialog(applicationInstance->getGLWidget()); dialog.setWindowTitle("Interface Preferences"); QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); @@ -754,9 +801,10 @@ void Menu::editPreferences() { avatarURL->setMinimumWidth(QLINE_MINIMUM_WIDTH); form->addRow("Avatar URL:", avatarURL); - QLineEdit* faceURL = new QLineEdit(applicationInstance->getAvatar()->getHead().getBlendFace().getModelURL().toString()); - faceURL->setMinimumWidth(QLINE_MINIMUM_WIDTH); - form->addRow("Face URL:", faceURL); + QString faceURLString = applicationInstance->getProfile()->getFaceModelURL().toString(); + QLineEdit* faceURLEdit = new QLineEdit(faceURLString); + faceURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); + form->addRow("Face URL:", faceURLEdit); QSlider* pupilDilation = new QSlider(Qt::Horizontal); pupilDilation->setValue(applicationInstance->getAvatar()->getHead().getPupilDilation() * pupilDilation->maximum()); @@ -799,13 +847,21 @@ void Menu::editPreferences() { return; } + QUrl faceModelURL(faceURLEdit->text()); + + if (faceModelURL.toString() != faceURLString) { + // change the faceModelURL in the profile, it will also update this user's BlendFace + applicationInstance->getProfile()->setFaceModelURL(faceModelURL); + + // send the new face mesh URL to the data-server (if we have a client UUID) + DataServerClient::putValueForKey(DataServerKey::FaceMeshURL, + faceModelURL.toString().toLocal8Bit().constData()); + } + QUrl avatarVoxelURL(avatarURL->text()); applicationInstance->getAvatar()->getVoxels()->setVoxelURL(avatarVoxelURL); - QUrl faceModelURL(faceURL->text()); - applicationInstance->getAvatar()->getHead().getBlendFace().setModelURL(faceModelURL); - - Avatar::sendAvatarURLsMessage(avatarVoxelURL, faceModelURL); + Avatar::sendAvatarURLsMessage(avatarVoxelURL); applicationInstance->getAvatar()->getHead().setPupilDilation(pupilDilation->value() / (float)pupilDilation->maximum()); @@ -911,6 +967,37 @@ void Menu::goToLocation() { } } +void Menu::goToUser() { + Application* applicationInstance = Application::getInstance(); + QDialog dialog(applicationInstance->getGLWidget()); + dialog.setWindowTitle("Go To User"); + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); + dialog.setLayout(layout); + + QFormLayout* form = new QFormLayout(); + layout->addLayout(form, 1); + + QLineEdit* usernameLineEdit = new QLineEdit(); + usernameLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); + form->addRow("", usernameLineEdit); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); + dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); + layout->addWidget(buttons); + + int ret = dialog.exec(); + applicationInstance->getWindow()->activateWindow(); + if (ret != QDialog::Accepted) { + return; + } + + if (!usernameLineEdit->text().isEmpty()) { + // there's a username entered by the user, make a request to the data-server + DataServerClient::getValuesForKeysAndUserString((QStringList() << DataServerKey::Domain << DataServerKey::Position), + usernameLineEdit->text()); + } +} void Menu::bandwidthDetails() { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index f54d3767a1..86d0d4d535 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -69,9 +69,11 @@ public slots: private slots: void aboutApp(); + void login(); void editPreferences(); void goToDomain(); void goToLocation(); + void goToUser(); void bandwidthDetailsClosed(); void voxelStatsDetailsClosed(); void cycleFrustumRenderMode(); @@ -120,15 +122,16 @@ private: }; namespace MenuOption { - const QString AboutApp = "About Interface"; const QString AmbientOcclusion = "Ambient Occlusion"; const QString Avatars = "Avatars"; const QString AvatarAsBalls = "Avatar as Balls"; const QString Atmosphere = "Atmosphere"; + const QString AutomaticallyAuditTree = "Automatically Audit Tree Stats"; const QString Bandwidth = "Bandwidth Display"; const QString BandwidthDetails = "Bandwidth Details"; const QString Collisions = "Collisions"; + const QString ConstantCulling = "Constant Culling"; const QString CopyVoxels = "Copy"; const QString CoverageMap = "Render Coverage Map"; const QString CoverageMapV2 = "Render Coverage Map V2"; @@ -141,6 +144,7 @@ namespace MenuOption { const QString DisplayFrustum = "Display Frustum"; const QString DontRenderVoxels = "Don't call _voxels.render()"; const QString DontCallOpenGLForVoxels = "Don't call glDrawElements()/glDrawRangeElementsEXT() for Voxels"; + const QString DontRemoveOutOfView = "Don't Remove Out of View Voxels"; const QString EchoAudio = "Echo Audio"; const QString ExportVoxels = "Export Voxels"; const QString HeadMouse = "Head Mouse"; @@ -161,6 +165,8 @@ namespace MenuOption { const QString GlowMode = "Cycle Glow Mode"; const QString GoToDomain = "Go To Domain..."; const QString GoToLocation = "Go To Location..."; + const QString HideOutOfView = "Hide Out of View Voxels"; + const QString GoToUser = "Go To User..."; const QString ImportVoxels = "Import Voxels"; const QString ImportVoxelsClipboard = "Import Voxels to Clipboard"; const QString IncreaseAvatarSize = "Increase Avatar Size"; @@ -169,11 +175,12 @@ namespace MenuOption { const QString GoHome = "Go Home"; const QString Gravity = "Use Gravity"; const QString GroundPlane = "Ground Plane"; - const QString GyroLook = "Smooth Gyro Look"; + const QString ParticleCloud = "Particle Cloud"; const QString ListenModeNormal = "Listen Mode Normal"; const QString ListenModePoint = "Listen Mode Point"; const QString ListenModeSingleSource = "Listen Mode Single Source"; const QString Log = "Log"; + const QString Login = "Login"; const QString LookAtIndicator = "Look-at Indicator"; const QString LookAtVectors = "Look-at Vectors"; const QString LowRes = "Lower Resolution While Moving"; @@ -181,6 +188,7 @@ namespace MenuOption { const QString NudgeVoxels = "Nudge"; const QString OcclusionCulling = "Occlusion Culling"; const QString OffAxisProjection = "Off-Axis Projection"; + const QString TurnWithHead = "Turn using Head"; const QString Oscilloscope = "Audio Oscilloscope"; const QString Pair = "Pair"; const QString PasteVoxels = "Paste"; diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 24ca83b8a3..9ea37be662 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -76,6 +76,7 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) _tree->rootNode->setVoxelSystem(this); pthread_mutex_init(&_bufferWriteLock, NULL); pthread_mutex_init(&_treeLock, NULL); + pthread_mutex_init(&_freeIndexLock, NULL); VoxelNode::addDeleteHook(this); VoxelNode::addUpdateHook(this); @@ -109,12 +110,7 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) void VoxelSystem::voxelDeleted(VoxelNode* node) { if (node->isKnownBufferIndex() && (node->getVoxelSystem() == this)) { - if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) { - freeBufferIndex(node->getBufferIndex()); - } else { - forceRemoveNodeFromArraysAsPartialVBO(node); - freeBufferIndex(node->getBufferIndex()); - } + forceRemoveNodeFromArrays(node); } } @@ -160,7 +156,9 @@ void VoxelSystem::voxelUpdated(VoxelNode* node) { } } - updateNodeInArraysAsPartialVBO(node); + const bool REUSE_INDEX = true; + const bool DONT_FORCE_REDRAW = false; + updateNodeInArrays(node, REUSE_INDEX, DONT_FORCE_REDRAW); _voxelsUpdated++; node->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. @@ -176,8 +174,10 @@ glBufferIndex VoxelSystem::getNextBufferIndex() { glBufferIndex output = GLBUFFER_INDEX_UNKNOWN; // if there's a free index, use it... if (_freeIndexes.size() > 0) { + pthread_mutex_lock(&_freeIndexLock); output = _freeIndexes.back(); _freeIndexes.pop_back(); + pthread_mutex_unlock(&_freeIndexLock); } else { output = _voxelsInWriteArrays; _voxelsInWriteArrays++; @@ -185,26 +185,48 @@ glBufferIndex VoxelSystem::getNextBufferIndex() { return output; } -// Doesn't actually clean up the VBOs for the index, but does release responsibility of the index from the VoxelNode, -// and makes the index available for some other node to use +// Release responsibility of the buffer/vbo index from the VoxelNode, and makes the index available for some other node to use +// will also "clean up" the index data for the buffer/vbo slot, so that if it's in the middle of the draw range, the triangles +// will be "invisible" void VoxelSystem::freeBufferIndex(glBufferIndex index) { - _freeIndexes.push_back(index); + if (_voxelsInWriteArrays == 0) { + qDebug() << "freeBufferIndex() called when _voxelsInWriteArrays == 0!!!!\n"; + } + + // if the "freed" index was our max index, then just drop the _voxelsInWriteArrays down one... + bool inList = false; + + // make sure the index isn't already in the free list..., this is a debugging measure only done if you've enabled audits + if (Menu::getInstance()->isOptionChecked(MenuOption::AutomaticallyAuditTree)) { + for (long i = 0; i < _freeIndexes.size(); i++) { + if (_freeIndexes[i] == index) { + printf("freeBufferIndex(glBufferIndex index)... index=%ld already in free list!\n", index); + inList = true; + break; + } + } + } + if (!inList) { + // make the index available for next node that needs to be drawn + pthread_mutex_lock(&_freeIndexLock); + _freeIndexes.push_back(index); + pthread_mutex_unlock(&_freeIndexLock); + + // make the VBO slot "invisible" in case this slot is not used + const glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX); + const float voxelScale = 0; + const nodeColor BLACK = {0, 0, 0, 0}; + updateArraysDetails(index, startVertex, voxelScale, BLACK); + } } // This will run through the list of _freeIndexes and reset their VBO array values to be "invisible". void VoxelSystem::clearFreeBufferIndexes() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "###### clearFreeBufferIndexes()"); - - for (int i = 0; i < _freeIndexes.size(); i++) { - glBufferIndex nodeIndex = _freeIndexes[i]; - glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX); - float voxelScale = 0; - _writeVoxelDirtyArray[nodeIndex] = true; - nodeColor color = {0, 0, 0, 0}; - updateNodeInArrays(nodeIndex, startVertex, voxelScale, color); - _abandonedVBOSlots++; - } + pthread_mutex_lock(&_freeIndexLock); _freeIndexes.clear(); + _abandonedVBOSlots = 0; + pthread_mutex_unlock(&_freeIndexLock); } VoxelSystem::~VoxelSystem() { @@ -212,6 +234,7 @@ VoxelSystem::~VoxelSystem() { delete _tree; pthread_mutex_destroy(&_bufferWriteLock); pthread_mutex_destroy(&_treeLock); + pthread_mutex_destroy(&_freeIndexLock); VoxelNode::removeDeleteHook(this); VoxelNode::removeUpdateHook(this); @@ -591,6 +614,7 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) { if (!_useFastVoxelPipeline || _writeRenderFullVBO) { setupNewVoxelsForDrawing(); } else { + checkForCulling(); setupNewVoxelsForDrawingSingleNode(DONT_BAIL_EARLY); } @@ -610,9 +634,6 @@ void VoxelSystem::setupNewVoxelsForDrawing() { uint64_t start = usecTimestampNow(); uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000; - // clear up the VBOs for any nodes that have been recently deleted. - clearFreeBufferIndexes(); - bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging if (!iAmDebugging && sinceLastTime <= std::max((float) _setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) { return; // bail early, it hasn't been long enough since the last time we ran @@ -631,8 +652,9 @@ void VoxelSystem::setupNewVoxelsForDrawing() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer); _callsToTreesToArrays++; if (_writeRenderFullVBO) { + printf("resetting _freeIndexes and _voxelsInWriteArrays\n"); _voxelsInWriteArrays = 0; // reset our VBO - _freeIndexes.clear(); // reset our free indexes + clearFreeBufferIndexes(); } _voxelsUpdated = newTreeToArrays(_tree->rootNode); _tree->clearDirtyBit(); // after we pull the trees into the array, we can consider the tree clean @@ -675,17 +697,12 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) { uint64_t start = usecTimestampNow(); uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000; - // clear up the VBOs for any nodes that have been recently deleted. - clearFreeBufferIndexes(); - bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging if (allowBailEarly && !iAmDebugging && sinceLastTime <= std::max((float) _setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) { return; // bail early, it hasn't been long enough since the last time we ran } - checkForCulling(); // check for out of view and deleted voxels... - // lock on the buffer write lock so we can't modify the data when the GPU is reading it { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), @@ -714,16 +731,27 @@ void VoxelSystem::checkForCulling() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()"); uint64_t start = usecTimestampNow(); uint64_t sinceLastViewCulling = (start - _lastViewCulling) / 1000; + + bool constantCulling = Menu::getInstance()->isOptionChecked(MenuOption::ConstantCulling); + // If the view frustum is no longer changing, but has changed, since last time, then remove nodes that are out of view - if ((sinceLastViewCulling >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) - && !isViewChanging()) { + if (constantCulling || ( + (sinceLastViewCulling >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) + && !isViewChanging() + ) + ) { _lastViewCulling = start; // When we call removeOutOfView() voxels, we don't actually remove the voxels from the VBOs, but we do remove // them from tree, this makes our tree caclulations faster, but doesn't require us to fully rebuild the VBOs (which // can be expensive). - removeOutOfView(); + if (Menu::getInstance()->isOptionChecked(MenuOption::HideOutOfView)) { + hideOutOfView(); + } + if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRemoveOutOfView)) { + removeOutOfView(); + } // Once we call cleanupRemovedVoxels() we do need to rebuild our VBOs (if anything was actually removed). So, // we should consider putting this someplace else... as this might be able to occur less frequently, and save us on @@ -732,7 +760,16 @@ void VoxelSystem::checkForCulling() { uint64_t endViewCulling = usecTimestampNow(); _lastViewCullingElapsed = (endViewCulling - start) / 1000; - } + } + + uint64_t sinceLastAudit = (start - _lastAudit) / 1000; + + if (Menu::getInstance()->isOptionChecked(MenuOption::AutomaticallyAuditTree)) { + if (sinceLastAudit >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) { + _lastAudit = start; + collectStatsForTreesAndVBOs(); + } + } } void VoxelSystem::cleanupRemovedVoxels() { @@ -747,6 +784,7 @@ void VoxelSystem::cleanupRemovedVoxels() { } _writeRenderFullVBO = true; // if we remove voxels, we must update our full VBOs } + // we also might have VBO slots that have been abandoned, if too many of our VBO slots // are abandonded we want to rerender our full VBOs const float TOO_MANY_ABANDONED_RATIO = 0.5f; @@ -830,6 +868,7 @@ void VoxelSystem::copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "copyWrittenDataToReadArrays()"); + if (_voxelsDirty && _voxelsUpdated) { if (fullVBOs) { copyWrittenDataToReadArraysFullVBOs(); @@ -869,133 +908,106 @@ int VoxelSystem::newTreeToArrays(VoxelNode* node) { } } if (_writeRenderFullVBO) { - voxelsUpdated += updateNodeInArraysAsFullVBO(node); + const bool DONT_REUSE_INDEX = false; + const bool FORCE_REDRAW = true; + voxelsUpdated += updateNodeInArrays(node, DONT_REUSE_INDEX, FORCE_REDRAW); } else { - voxelsUpdated += updateNodeInArraysAsPartialVBO(node); + const bool REUSE_INDEX = true; + const bool DONT_FORCE_REDRAW = false; + voxelsUpdated += updateNodeInArrays(node, REUSE_INDEX, DONT_FORCE_REDRAW); } node->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things. return voxelsUpdated; } -int VoxelSystem::updateNodeInArraysAsFullVBO(VoxelNode* node) { - // If we've run out of room, then just bail... - if (_voxelsInWriteArrays >= _maxVoxels) { - return 0; - } - - if (node->getShouldRender()) { - glm::vec3 startVertex = node->getCorner(); - float voxelScale = node->getScale(); - glBufferIndex nodeIndex = getNextBufferIndex(); - - // populate the array with points for the 8 vertices - // and RGB color for each added vertex - updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor()); - node->setBufferIndex(nodeIndex); - node->setVoxelSystem(this); - return 1; // rendered - } else { - node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); - } - - return 0; // not-rendered -} - // called as response to voxelDeleted() in fast pipeline case. The node // is being deleted, but it's state is such that it thinks it should render // and therefore we can't use the normal render calculations. This method // will forcibly remove it from the VBOs because we know better!!! -int VoxelSystem::forceRemoveNodeFromArraysAsPartialVBO(VoxelNode* node) { +int VoxelSystem::forceRemoveNodeFromArrays(VoxelNode* node) { + + if (!_initialized) { + return 0; + } // if the node is not in the VBOs then we have nothing to do! if (node->isKnownBufferIndex()) { - - // if we shouldn't render then set out location to some infinitely distant location, - // and our scale as infinitely small - glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX); - float voxelScale = 0; - _abandonedVBOSlots++; - // If this node has not yet been written to the array, then add it to the end of the array. glBufferIndex nodeIndex = node->getBufferIndex(); - - _writeVoxelDirtyArray[nodeIndex] = true; - - // populate the array with points for the 8 vertices - // and RGB color for each added vertex - const nodeColor BLACK = { 0,0,0}; - updateNodeInArrays(nodeIndex, startVertex, voxelScale, BLACK); - + node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN); + freeBufferIndex(nodeIndex); // NOTE: This is make the node invisible! return 1; // updated! } return 0; // not-updated } -int VoxelSystem::updateNodeInArraysAsPartialVBO(VoxelNode* node) { +int VoxelSystem::updateNodeInArrays(VoxelNode* node, bool reuseIndex, bool forceDraw) { // If we've run out of room, then just bail... if (_voxelsInWriteArrays >= _maxVoxels) { return 0; } + + if (!_initialized) { + return 0; + } - // Now, if we've changed any attributes (our renderness, our color, etc) then update the Arrays... - if (node->isDirty()) { - glm::vec3 startVertex; - float voxelScale = 0; + // If we've changed any attributes (our renderness, our color, etc), or we've been told to force a redraw + // then update the Arrays... + if (forceDraw || node->isDirty()) { // If we're should render, use our legit location and scale, if (node->getShouldRender()) { - startVertex = node->getCorner(); - voxelScale = node->getScale(); - } else { - // if we shouldn't render then set out location to some infinitely distant location, - // and our scale as infinitely small - startVertex[0] = startVertex[1] = startVertex[2] = FLT_MAX; - voxelScale = 0; - _abandonedVBOSlots++; - } + glm::vec3 startVertex = node->getCorner(); + float voxelScale = node->getScale(); - // If this node has not yet been written to the array, then add it to the end of the array. - glBufferIndex nodeIndex; - if (node->isKnownBufferIndex()) { - nodeIndex = node->getBufferIndex(); + glBufferIndex nodeIndex = GLBUFFER_INDEX_UNKNOWN; + if (reuseIndex && node->isKnownBufferIndex()) { + nodeIndex = node->getBufferIndex(); + } else { + nodeIndex = getNextBufferIndex(); + node->setBufferIndex(nodeIndex); + node->setVoxelSystem(this); + } + // populate the array with points for the 8 vertices and RGB color for each added vertex + updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor()); + return 1; // updated! } else { - nodeIndex = getNextBufferIndex(); - node->setBufferIndex(nodeIndex); - node->setVoxelSystem(this); + // If we shouldn't render, but we did have a known index, then we will need to release our index + if (reuseIndex && node->isKnownBufferIndex()) { + forceRemoveNodeFromArrays(node); + return 1; // updated! + } } - _writeVoxelDirtyArray[nodeIndex] = true; - - // populate the array with points for the 8 vertices - // and RGB color for each added vertex - updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor()); - - return 1; // updated! } return 0; // not-updated } -void VoxelSystem::updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex, +void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex, float voxelScale, const nodeColor& color) { + + if (_initialized) { + _writeVoxelDirtyArray[nodeIndex] = true; - if (_useVoxelShader) { - if (_writeVoxelShaderData) { - VoxelShaderVBOData* writeVerticesAt = &_writeVoxelShaderData[nodeIndex]; - writeVerticesAt->x = startVertex.x * TREE_SCALE; - writeVerticesAt->y = startVertex.y * TREE_SCALE; - writeVerticesAt->z = startVertex.z * TREE_SCALE; - writeVerticesAt->s = voxelScale * TREE_SCALE; - writeVerticesAt->r = color[RED_INDEX]; - writeVerticesAt->g = color[GREEN_INDEX]; - writeVerticesAt->b = color[BLUE_INDEX]; - } - } else { - if (_writeVerticesArray && _writeColorsArray) { - int vertexPointsPerVoxel = GLOBAL_NORMALS_VERTEX_POINTS_PER_VOXEL; - for (int j = 0; j < vertexPointsPerVoxel; j++ ) { - GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * vertexPointsPerVoxel); - GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * vertexPointsPerVoxel); - *(writeVerticesAt+j) = startVertex[j % 3] + (identityVerticesGlobalNormals[j] * voxelScale); - *(writeColorsAt +j) = color[j % 3]; + if (_useVoxelShader) { + if (_writeVoxelShaderData) { + VoxelShaderVBOData* writeVerticesAt = &_writeVoxelShaderData[nodeIndex]; + writeVerticesAt->x = startVertex.x * TREE_SCALE; + writeVerticesAt->y = startVertex.y * TREE_SCALE; + writeVerticesAt->z = startVertex.z * TREE_SCALE; + writeVerticesAt->s = voxelScale * TREE_SCALE; + writeVerticesAt->r = color[RED_INDEX]; + writeVerticesAt->g = color[GREEN_INDEX]; + writeVerticesAt->b = color[BLUE_INDEX]; + } + } else { + if (_writeVerticesArray && _writeColorsArray) { + int vertexPointsPerVoxel = GLOBAL_NORMALS_VERTEX_POINTS_PER_VOXEL; + for (int j = 0; j < vertexPointsPerVoxel; j++ ) { + GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * vertexPointsPerVoxel); + GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * vertexPointsPerVoxel); + *(writeVerticesAt+j) = startVertex[j % 3] + (identityVerticesGlobalNormals[j] * voxelScale); + *(writeColorsAt +j) = color[j % 3]; + } } } } @@ -1017,7 +1029,7 @@ void VoxelSystem::init() { _callsToTreesToArrays = 0; _setupNewVoxelsForDrawingLastFinished = 0; _setupNewVoxelsForDrawingLastElapsed = 0; - _lastViewCullingElapsed = _lastViewCulling = 0; + _lastViewCullingElapsed = _lastViewCulling = _lastAudit = 0; _voxelsDirty = false; _voxelsInWriteArrays = 0; @@ -1508,8 +1520,10 @@ public: unsigned long nodesInside; unsigned long nodesIntersect; unsigned long nodesOutside; + VoxelNode* insideRoot; + VoxelNode* outsideRoot; - removeOutOfViewArgs(VoxelSystem* voxelSystem) : + removeOutOfViewArgs(VoxelSystem* voxelSystem, bool widenViewFrustum = true) : thisVoxelSystem(voxelSystem), thisViewFrustum(*voxelSystem->getViewFrustum()), dontRecurseBag(), @@ -1517,13 +1531,17 @@ public: nodesRemoved(0), nodesInside(0), nodesIntersect(0), - nodesOutside(0) + nodesOutside(0), + insideRoot(NULL), + outsideRoot(NULL) { // Widen the FOV for trimming - float originalFOV = thisViewFrustum.getFieldOfView(); - float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND; - thisViewFrustum.setFieldOfView(wideFOV); - thisViewFrustum.calculate(); + if (widenViewFrustum) { + float originalFOV = thisViewFrustum.getFieldOfView(); + float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND; + thisViewFrustum.setFieldOfView(wideFOV); + thisViewFrustum.calculate(); + } } }; @@ -1579,7 +1597,6 @@ bool VoxelSystem::removeOutOfViewOperation(VoxelNode* node, void* extraData) { return true; // keep going! } - bool VoxelSystem::isViewChanging() { bool result = false; // assume the best @@ -1624,6 +1641,139 @@ void VoxelSystem::removeOutOfView() { } } +// combines the removeOutOfView args into a single class +class hideOutOfViewArgs { +public: + VoxelSystem* thisVoxelSystem; + VoxelTree* tree; + ViewFrustum thisViewFrustum; + unsigned long nodesScanned; + unsigned long nodesRemoved; + unsigned long nodesInside; + unsigned long nodesIntersect; + unsigned long nodesOutside; + + hideOutOfViewArgs(VoxelSystem* voxelSystem, VoxelTree* tree, bool widenViewFrustum = true) : + thisVoxelSystem(voxelSystem), + tree(tree), + thisViewFrustum(*voxelSystem->getViewFrustum()), + nodesScanned(0), + nodesRemoved(0), + nodesInside(0), + nodesIntersect(0), + nodesOutside(0) + { + // Widen the FOV for trimming + if (widenViewFrustum) { + float originalFOV = thisViewFrustum.getFieldOfView(); + float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND; + thisViewFrustum.setFieldOfView(wideFOV); + thisViewFrustum.calculate(); + } + } +}; + +void VoxelSystem::hideOutOfView() { + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "hideOutOfView()"); + hideOutOfViewArgs args(this, this->_tree, true); // widen to match server! + _tree->recurseTreeWithOperation(hideOutOfViewOperation,(void*)&args); + + if (args.nodesRemoved) { + _tree->setDirtyBit(); + setupNewVoxelsForDrawingSingleNode(DONT_BAIL_EARLY); + } + + + bool showRemoveDebugDetails = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); + if (showRemoveDebugDetails) { + qDebug("hideOutOfView() scanned=%ld removed=%ld inside=%ld intersect=%ld outside=%ld\n", + args.nodesScanned, args.nodesRemoved, args.nodesInside, + args.nodesIntersect, args.nodesOutside + ); + } +} + +bool VoxelSystem::hideAllSubTreeOperation(VoxelNode* node, void* extraData) { + hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData; + + args->nodesOutside++; + if (node->isKnownBufferIndex()) { + args->nodesRemoved++; + VoxelSystem* thisVoxelSystem = args->thisVoxelSystem; + thisVoxelSystem->forceRemoveNodeFromArrays(node); + thisVoxelSystem->_voxelsUpdated++; + thisVoxelSystem->setupNewVoxelsForDrawingSingleNode(); + } + + return true; +} + +bool VoxelSystem::showAllSubTreeOperation(VoxelNode* node, void* extraData) { + hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData; + + args->nodesInside++; + + if (node->getShouldRender() && !node->isKnownBufferIndex()) { + node->setDirtyBit(); // will this make it draw? + } + + return true; +} + + + +// "hide" voxels in the VBOs that are still in the tree that but not in view. +// We don't remove them from the tree, we don't delete them, we do remove them +// from the VBOs and mark them as such in the tree. +bool VoxelSystem::hideOutOfViewOperation(VoxelNode* node, void* extraData) { + hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData; + + // If we're still recursing the tree using this operator, then we don't know if we're inside or outside... + // so before we move forward we need to determine our frustum location + ViewFrustum::location inFrustum = node->inFrustum(args->thisViewFrustum); + + // ok, now do some processing for this node... + switch (inFrustum) { + case ViewFrustum::OUTSIDE: { + // if this node is fully OUTSIDE the view, then we know that ALL of it's children are also fully OUTSIDE + // so we can recurse the children and simply mark them as hidden + args->tree->recurseNodeWithOperation(node, hideAllSubTreeOperation, args ); + + return false; + + } break; + case ViewFrustum::INSIDE: { + // if this node is fully INSIDE the view, then we know that ALL of it's children are also fully INSIDE + // so we can recurse the children and simply mark them as visible (as appropriate based on LOD) + args->tree->recurseNodeWithOperation(node, showAllSubTreeOperation, args ); + + return false; + } break; + case ViewFrustum::INTERSECT: { + args->nodesScanned++; + args->nodesIntersect++; + + // if the child node INTERSECTs the view, then we want to check to see if it thinks it should render + // if it should render but is missing it's VBO index, then we want to flip it on, and we can stop recursing from + // here because we know will block any children anyway + if (node->getShouldRender() && !node->isKnownBufferIndex()) { + node->setDirtyBit(); // will this make it draw? + return false; + } + + // If it INTERSECTS but shouldn't be displayed, then it's probably a parent and it is at least partially in view. + // So we DO want to recurse the children because some of them may not be in view... nothing specifically to do, + // just keep iterating the children + return true; + + } break; + } // switch + + + return true; // keep going! +} + + bool VoxelSystem::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, VoxelDetail& detail, float& distance, BoxFace& face) { pthread_mutex_lock(&_treeLock); @@ -1743,6 +1893,14 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* ex if (node->isKnownBufferIndex()) { args->nodesInVBO++; unsigned long nodeIndex = node->getBufferIndex(); + + const bool extraDebugging = false; // enable for extra debugging + if (extraDebugging) { + qDebug("node In VBO... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s \n", + node->getCorner().x, node->getCorner().y, node->getCorner().z, node->getScale(), + nodeIndex, debug::valueOf(node->isDirty()), debug::valueOf(node->getShouldRender())); + } + if (args->hasIndexFound[nodeIndex]) { args->duplicateVBOIndex++; qDebug("duplicateVBO found... index=%ld, isDirty=%s, shouldRender=%s \n", nodeIndex, @@ -1778,6 +1936,9 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { collectStatsForTreesAndVBOsArgs args; args.expectedMax = _voxelsInWriteArrays; + + qDebug("CALCULATING Local Voxel Tree Statistics >>>>>>>>>>>>\n"); + _tree->recurseTreeWithOperation(collectStatsForTreesAndVBOsOperation,&args); qDebug("Local Voxel Tree Statistics:\n total nodes %ld \n leaves %ld \n dirty %ld \n colored %ld \n shouldRender %ld \n", @@ -1802,6 +1963,10 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { qDebug(" minInVBO=%ld \n maxInVBO=%ld \n _voxelsInWriteArrays=%ld \n _voxelsInReadArrays=%ld \n", minInVBO, maxInVBO, _voxelsInWriteArrays, _voxelsInReadArrays); + qDebug(" _freeIndexes.size()=%ld \n", + _freeIndexes.size()); + + qDebug("DONE WITH Local Voxel Tree Statistics >>>>>>>>>>>>\n"); } diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index 456a9bc8c6..5f026e043c 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -81,6 +81,7 @@ public: void killLocalVoxels(); virtual void removeOutOfView(); + virtual void hideOutOfView(); bool hasViewChanged(); bool isViewChanging(); @@ -151,7 +152,7 @@ protected: glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const; - virtual void updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex, + virtual void updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex, float voxelScale, const nodeColor& color); virtual void copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd); virtual void updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd); @@ -185,10 +186,13 @@ private: static bool killSourceVoxelsOperation(VoxelNode* node, void* extraData); static bool forceRedrawEntireTreeOperation(VoxelNode* node, void* extraData); static bool clearAllNodesBufferIndexOperation(VoxelNode* node, void* extraData); + static bool hideOutOfViewOperation(VoxelNode* node, void* extraData); + static bool hideOutOfViewUnrollOperation(VoxelNode* node, void* extraData); + static bool hideAllSubTreeOperation(VoxelNode* node, void* extraData); + static bool showAllSubTreeOperation(VoxelNode* node, void* extraData); - int updateNodeInArraysAsFullVBO(VoxelNode* node); - int updateNodeInArraysAsPartialVBO(VoxelNode* node); - int forceRemoveNodeFromArraysAsPartialVBO(VoxelNode* node); + int updateNodeInArrays(VoxelNode* node, bool reuseIndex, bool forceDraw); + int forceRemoveNodeFromArrays(VoxelNode* node); void copyWrittenDataToReadArraysFullVBOs(); void copyWrittenDataToReadArraysPartialVBOs(); @@ -218,6 +222,7 @@ private: int _setupNewVoxelsForDrawingLastElapsed; uint64_t _setupNewVoxelsForDrawingLastFinished; uint64_t _lastViewCulling; + uint64_t _lastAudit; int _lastViewCullingElapsed; void initVoxelMemory(); @@ -265,6 +270,7 @@ private: int _hookID; std::vector _freeIndexes; + pthread_mutex_t _freeIndexLock; void freeBufferIndex(glBufferIndex index); void clearFreeBufferIndexes(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index c278f9b697..112855343d 100755 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -18,6 +18,7 @@ #include "Application.h" #include "Avatar.h" +#include "DataServerClient.h" #include "Hand.h" #include "Head.h" #include "Physics.h" @@ -59,7 +60,7 @@ const int NUM_BODY_CONE_SIDES = 9; const float chatMessageScale = 0.0015; const float chatMessageHeight = 0.20; -void Avatar::sendAvatarURLsMessage(const QUrl& voxelURL, const QUrl& faceURL) { +void Avatar::sendAvatarURLsMessage(const QUrl& voxelURL) { uint16_t ownerID = NodeList::getInstance()->getOwnerID(); if (ownerID == UNKNOWN_NODE_ID) { @@ -76,7 +77,6 @@ void Avatar::sendAvatarURLsMessage(const QUrl& voxelURL, const QUrl& faceURL) { QDataStream out(&message, QIODevice::WriteOnly | QIODevice::Append); out << voxelURL; - out << faceURL; Application::controlledBroadcastToNodes((unsigned char*)message.data(), message.size(), &NODE_TYPE_AVATAR_MIXER, 1); } @@ -102,11 +102,16 @@ Avatar::Avatar(Node* owningNode) : _leadingAvatar(NULL), _voxels(this), _moving(false), + _hoverOnDuration(0.0f), + _hoverOffDuration(0.0f), _initialized(false), _handHoldingPosition(0.0f, 0.0f, 0.0f), _maxArmLength(0.0f), _pelvisStandingHeight(0.0f) { + // we may have been created in the network thread, but we live in the main thread + moveToThread(Application::getInstance()->thread()); + // give the pointer to our head to inherited _headData variable from AvatarData _headData = &_head; _handData = &_hand; @@ -388,15 +393,27 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { } // head scale grows when avatar is looked at + const float BASE_MAX_SCALE = 3.0f; + float maxScale = BASE_MAX_SCALE * glm::distance(_position, Application::getInstance()->getCamera()->getPosition()); if (Application::getInstance()->getLookatTargetAvatar() == this) { - const float BASE_MAX_SCALE = 3.0f; - const float GROW_SPEED = 0.1f; - _head.setScale(min(BASE_MAX_SCALE * glm::distance(_position, Application::getInstance()->getCamera()->getPosition()), - _head.getScale() + deltaTime * GROW_SPEED)); + _hoverOnDuration += deltaTime; + _hoverOffDuration = 0.0f; + + const float GROW_DELAY = 1.0f; + const float GROW_RATE = 0.25f; + if (_hoverOnDuration > GROW_DELAY) { + _head.setScale(glm::mix(_head.getScale(), maxScale, GROW_RATE)); + } } else { - const float SHRINK_SPEED = 100.0f; - _head.setScale(max(_scale, _head.getScale() - deltaTime * SHRINK_SPEED)); + _hoverOnDuration = 0.0f; + _hoverOffDuration += deltaTime; + + const float SHRINK_DELAY = 1.0f; + const float SHRINK_RATE = 0.25f; + if (_hoverOffDuration > SHRINK_DELAY) { + _head.setScale(glm::mix(_head.getScale(), 1.0f, SHRINK_RATE)); + } } _head.setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll)); @@ -465,15 +482,16 @@ void Avatar::render(bool lookingInMirror, bool renderAvatarBalls) { renderDiskShadow(_position, glm::vec3(0.0f, 1.0f, 0.0f), _scale * 0.1f, 0.2f); { - // glow when moving - Glower glower(_moving ? 1.0f : 0.0f); + // glow when moving in the distance + glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition(); + const float GLOW_DISTANCE = 5.0f; + Glower glower(_moving && glm::length(toTarget) > GLOW_DISTANCE ? 1.0f : 0.0f); // render body renderBody(lookingInMirror, renderAvatarBalls); // render sphere when far away const float MAX_ANGLE = 10.f; - glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition(); glm::vec3 delta = _height * (_head.getCameraOrientation() * IDENTITY_UP) / 2.f; float angle = abs(angleBetween(toTarget + delta, toTarget - delta)); @@ -688,6 +706,13 @@ void Avatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) { } } else if (renderAvatarBalls || !_voxels.getVoxelURL().isValid()) { // Render the body as balls and cones + glm::vec3 skinColor(SKIN_COLOR[0], SKIN_COLOR[1], SKIN_COLOR[2]); + glm::vec3 darkSkinColor(DARK_SKIN_COLOR[0], DARK_SKIN_COLOR[1], DARK_SKIN_COLOR[2]); + if (_head.getBlendFace().isActive()) { + skinColor = glm::vec3(_head.getBlendFace().computeAverageColor()); + const float SKIN_DARKENING = 0.9f; + darkSkinColor = skinColor * SKIN_DARKENING; + } for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) { float alpha = getBallRenderAlpha(b, lookingInMirror); @@ -705,9 +730,9 @@ void Avatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) { } } else if (alpha > 0.0f) { // Render the body ball sphere - glColor3f(SKIN_COLOR[0] + _bodyBall[b].touchForce * 0.3f, - SKIN_COLOR[1] - _bodyBall[b].touchForce * 0.2f, - SKIN_COLOR[2] - _bodyBall[b].touchForce * 0.1f); + glColor3f(skinColor.r + _bodyBall[b].touchForce * 0.3f, + skinColor.g - _bodyBall[b].touchForce * 0.2f, + skinColor.b - _bodyBall[b].touchForce * 0.1f); if (b == BODY_BALL_NECK_BASE && _head.getBlendFace().isActive()) { continue; // don't render the neck if we have a face model @@ -732,7 +757,7 @@ void Avatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) { && (b != BODY_BALL_LEFT_SHOULDER) && (b != BODY_BALL_RIGHT_COLLAR) && (b != BODY_BALL_RIGHT_SHOULDER)) { - glColor3fv(DARK_SKIN_COLOR); + glColor3fv((const GLfloat*)&darkSkinColor); float r2 = _bodyBall[b].radius * 0.8; renderJointConnectingCone(_bodyBall[_bodyBall[b].parentBall].position, _bodyBall[b].position, r2, r2); @@ -751,31 +776,6 @@ void Avatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) { _hand.render(lookingInMirror); } - -void Avatar::loadData(QSettings* settings) { - settings->beginGroup("Avatar"); - - // in case settings is corrupt or missing loadSetting() will check for NaN - _bodyYaw = loadSetting(settings, "bodyYaw", 0.0f); - _bodyPitch = loadSetting(settings, "bodyPitch", 0.0f); - _bodyRoll = loadSetting(settings, "bodyRoll", 0.0f); - _position.x = loadSetting(settings, "position_x", 0.0f); - _position.y = loadSetting(settings, "position_y", 0.0f); - _position.z = loadSetting(settings, "position_z", 0.0f); - - _voxels.setVoxelURL(settings->value("voxelURL").toUrl()); - _head.getBlendFace().setModelURL(settings->value("faceModelURL").toUrl()); - _head.setPupilDilation(settings->value("pupilDilation", 0.0f).toFloat()); - - _leanScale = loadSetting(settings, "leanScale", 0.05f); - - _newScale = loadSetting(settings, "scale", 1.0f); - setScale(_scale); - Application::getInstance()->getCamera()->setScale(_scale); - - settings->endGroup(); -} - void Avatar::getBodyBallTransform(AvatarJointID jointID, glm::vec3& position, glm::quat& rotation) const { position = _bodyBall[jointID].position; rotation = _bodyBall[jointID].rotation; @@ -805,27 +805,6 @@ int Avatar::parseData(unsigned char* sourceBuffer, int numBytes) { return bytesRead; } -void Avatar::saveData(QSettings* set) { - set->beginGroup("Avatar"); - - set->setValue("bodyYaw", _bodyYaw); - set->setValue("bodyPitch", _bodyPitch); - set->setValue("bodyRoll", _bodyRoll); - - set->setValue("position_x", _position.x); - set->setValue("position_y", _position.y); - set->setValue("position_z", _position.z); - - set->setValue("voxelURL", _voxels.getVoxelURL()); - set->setValue("faceModelURL", _head.getBlendFace().getModelURL()); - set->setValue("pupilDilation", _head.getPupilDilation()); - - set->setValue("leanScale", _leanScale); - set->setValue("scale", _newScale); - - set->endGroup(); -} - // render a makeshift cone section that serves as a body part connecting joint spheres void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2) { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 9396480d27..1451abd180 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -11,7 +11,7 @@ #include #include -#include +#include #include @@ -129,7 +129,7 @@ class Avatar : public AvatarData { Q_OBJECT public: - static void sendAvatarURLsMessage(const QUrl& voxelURL, const QUrl& faceURL); + static void sendAvatarURLsMessage(const QUrl& voxelURL); Avatar(Node* owningNode = NULL); ~Avatar(); @@ -155,10 +155,6 @@ public: glm::quat getOrientation() const; glm::quat getWorldAlignedOrientation() const; AvatarVoxelSystem* getVoxels() { return &_voxels; } - - // get/set avatar data - void saveData(QSettings* set); - void loadData(QSettings* set); // Get the position/rotation of a single body ball void getBodyBallTransform(AvatarJointID jointID, glm::vec3& position, glm::quat& rotation) const; @@ -226,6 +222,8 @@ protected: AvatarVoxelSystem _voxels; bool _moving; ///< set when position is changing + float _hoverOnDuration; + float _hoverOffDuration; // protected methods... glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } @@ -247,6 +245,7 @@ private: float _maxArmLength; float _pelvisStandingHeight; + // private methods... glm::vec3 calculateAverageEyePosition() { return _head.calculateAverageEyePosition(); } // get the position smack-dab between the eyes (for lookat) float getBallRenderAlpha(int ball, bool lookingInMirror) const; diff --git a/interface/src/avatar/AvatarVoxelSystem.cpp b/interface/src/avatar/AvatarVoxelSystem.cpp index 9c94d1a06b..db02e20f85 100644 --- a/interface/src/avatar/AvatarVoxelSystem.cpp +++ b/interface/src/avatar/AvatarVoxelSystem.cpp @@ -151,9 +151,9 @@ void AvatarVoxelSystem::setVoxelURL(const QUrl& url) { connect(_voxelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleVoxelReplyError())); } -void AvatarVoxelSystem::updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex, +void AvatarVoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex, float voxelScale, const nodeColor& color) { - VoxelSystem::updateNodeInArrays(nodeIndex, startVertex, voxelScale, color); + VoxelSystem::updateArraysDetails(nodeIndex, startVertex, voxelScale, color); GLubyte* writeBoneIndicesAt = _writeBoneIndicesArray + (nodeIndex * BONE_ELEMENTS_PER_VOXEL); GLfloat* writeBoneWeightsAt = _writeBoneWeightsArray + (nodeIndex * BONE_ELEMENTS_PER_VOXEL); diff --git a/interface/src/avatar/AvatarVoxelSystem.h b/interface/src/avatar/AvatarVoxelSystem.h index 43c76fb5c2..2560af11a5 100644 --- a/interface/src/avatar/AvatarVoxelSystem.h +++ b/interface/src/avatar/AvatarVoxelSystem.h @@ -42,7 +42,7 @@ public slots: protected: - virtual void updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex, + virtual void updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex, float voxelScale, const nodeColor& color); virtual void copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd); virtual void updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd); diff --git a/interface/src/avatar/BlendFace.cpp b/interface/src/avatar/BlendFace.cpp index d376c976eb..ce6422dcb4 100644 --- a/interface/src/avatar/BlendFace.cpp +++ b/interface/src/avatar/BlendFace.cpp @@ -8,6 +8,8 @@ #include +#include + #include "Application.h" #include "BlendFace.h" #include "Head.h" @@ -41,21 +43,117 @@ void BlendFace::init() { } } +void BlendFace::reset() { + _resetStates = true; +} + const glm::vec3 MODEL_TRANSLATION(0.0f, -120.0f, 40.0f); // temporary fudge factor const float MODEL_SCALE = 0.0006f; -bool BlendFace::render(float alpha) { +void BlendFace::simulate(float deltaTime) { if (!isActive()) { + return; + } + + // set up world vertices on first simulate after load + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + if (_meshStates.isEmpty()) { + QVector vertices; + foreach (const FBXMesh& mesh, geometry.meshes) { + MeshState state; + if (mesh.springiness > 0.0f) { + state.worldSpaceVertices.resize(mesh.vertices.size()); + state.vertexVelocities.resize(mesh.vertices.size()); + state.worldSpaceNormals.resize(mesh.vertices.size()); + } + _meshStates.append(state); + } + _resetStates = true; + } + + glm::quat orientation = _owningHead->getOrientation(); + glm::vec3 scale = glm::vec3(-1.0f, 1.0f, -1.0f) * _owningHead->getScale() * MODEL_SCALE; + glm::vec3 offset = MODEL_TRANSLATION - _geometry->getFBXGeometry().neckPivot; + glm::mat4 baseTransform = glm::translate(_owningHead->getPosition()) * glm::mat4_cast(orientation) * + glm::scale(scale) * glm::translate(offset); + + for (int i = 0; i < _meshStates.size(); i++) { + MeshState& state = _meshStates[i]; + int vertexCount = state.worldSpaceVertices.size(); + if (vertexCount == 0) { + continue; + } + glm::vec3* destVertices = state.worldSpaceVertices.data(); + glm::vec3* destVelocities = state.vertexVelocities.data(); + glm::vec3* destNormals = state.worldSpaceNormals.data(); + const FBXMesh& mesh = geometry.meshes.at(i); + const glm::vec3* sourceVertices = mesh.vertices.constData(); + if (!mesh.blendshapes.isEmpty()) { + _blendedVertices.resize(max(_blendedVertices.size(), vertexCount)); + memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3)); + + // blend in each coefficient + const vector& coefficients = _owningHead->getBlendshapeCoefficients(); + for (int j = 0; j < coefficients.size(); j++) { + float coefficient = coefficients[j]; + if (coefficient == 0.0f || j >= mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) { + continue; + } + const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData(); + for (const int* index = mesh.blendshapes[j].indices.constData(), + *end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++) { + _blendedVertices[*index] += *vertex * coefficient; + } + } + + sourceVertices = _blendedVertices.constData(); + } + glm::mat4 transform = baseTransform; + if (mesh.isEye) { + transform = transform * glm::translate(mesh.pivot) * glm::mat4_cast(glm::inverse(orientation) * + _owningHead->getEyeRotation(orientation * ((mesh.pivot + offset) * scale) + _owningHead->getPosition())) * + glm::translate(-mesh.pivot); + } + if (_resetStates) { + for (int j = 0; j < vertexCount; j++) { + destVertices[j] = glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)); + destVelocities[j] = glm::vec3(); + } + } else { + const float SPRINGINESS_MULTIPLIER = 200.0f; + const float DAMPING = 5.0f; + for (int j = 0; j < vertexCount; j++) { + destVelocities[j] += ((glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)) - destVertices[j]) * + mesh.springiness * SPRINGINESS_MULTIPLIER - destVelocities[j] * DAMPING) * deltaTime; + destVertices[j] += destVelocities[j] * deltaTime; + } + } + for (int j = 0; j < vertexCount; j++) { + destNormals[j] = glm::vec3(); + + const glm::vec3& middle = destVertices[j]; + for (QVarLengthArray, 4>::const_iterator connection = mesh.vertexConnections.at(j).constBegin(); + connection != mesh.vertexConnections.at(j).constEnd(); connection++) { + destNormals[j] += glm::normalize(glm::cross(destVertices[connection->second] - middle, + destVertices[connection->first] - middle)); + } + } + } + _resetStates = false; +} + +bool BlendFace::render(float alpha) { + if (_meshStates.isEmpty()) { return false; } - // set up blended buffer ids on first render after load + // set up blended buffer ids on first render after load/simulate const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& networkMeshes = _geometry->getMeshes(); if (_blendedVertexBufferIDs.isEmpty()) { foreach (const FBXMesh& mesh, geometry.meshes) { GLuint id = 0; - if (!mesh.blendshapes.isEmpty()) { + if (!mesh.blendshapes.isEmpty() || mesh.springiness > 0.0f) { glGenBuffers(1, &id); glBindBuffer(GL_ARRAY_BUFFER, id); glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3), @@ -69,6 +167,9 @@ bool BlendFace::render(float alpha) { _dilatedTextures.resize(geometry.meshes.size()); } + glm::mat4 viewMatrix; + glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&viewMatrix); + glPushMatrix(); glTranslatef(_owningHead->getPosition().x, _owningHead->getPosition().y, _owningHead->getPosition().z); glm::quat orientation = _owningHead->getOrientation(); @@ -90,6 +191,9 @@ bool BlendFace::render(float alpha) { glEnable(GL_TEXTURE_2D); glDisable(GL_COLOR_MATERIAL); + // the eye shader uses the color state even though color material is disabled + glColor4f(1.0f, 1.0f, 1.0f, alpha); + for (int i = 0; i < networkMeshes.size(); i++) { const NetworkMesh& networkMesh = networkMeshes.at(i); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID); @@ -104,7 +208,7 @@ bool BlendFace::render(float alpha) { if (mesh.isEye) { glTranslatef(mesh.pivot.x, mesh.pivot.y, mesh.pivot.z); glm::quat rotation = glm::inverse(orientation) * _owningHead->getEyeRotation(orientation * - (mesh.pivot * scale + MODEL_TRANSLATION) + _owningHead->getPosition()); + ((mesh.pivot + offset) * scale) + _owningHead->getPosition()); glm::vec3 rotationAxis = glm::axis(rotation); glRotatef(glm::angle(rotation), -rotationAxis.x, rotationAxis.y, -rotationAxis.z); glTranslatef(-mesh.pivot.x, -mesh.pivot.y, -mesh.pivot.z); @@ -130,39 +234,49 @@ bool BlendFace::render(float alpha) { glBindTexture(GL_TEXTURE_2D, texture == NULL ? 0 : texture->getID()); glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID); - if (mesh.blendshapes.isEmpty()) { + if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) { glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3))); } else { glTexCoordPointer(2, GL_FLOAT, 0, 0); - - _blendedVertices.resize(max(_blendedVertices.size(), vertexCount)); - _blendedNormals.resize(_blendedVertices.size()); - memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3)); - memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3)); - - // blend in each coefficient - const vector& coefficients = _owningHead->getBlendshapeCoefficients(); - for (int j = 0; j < coefficients.size(); j++) { - float coefficient = coefficients[j]; - if (coefficient == 0.0f || j >= mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) { - continue; - } - const float NORMAL_COEFFICIENT_SCALE = 0.01f; - float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE; - const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData(); - const glm::vec3* normal = mesh.blendshapes[j].normals.constData(); - for (const int* index = mesh.blendshapes[j].indices.constData(), - *end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) { - _blendedVertices[*index] += *vertex * coefficient; - _blendedNormals[*index] += *normal * normalCoefficient; - } - } - glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i)); - glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData()); - glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3), - vertexCount * sizeof(glm::vec3), _blendedNormals.constData()); + + const MeshState& state = _meshStates.at(i); + if (!state.worldSpaceVertices.isEmpty()) { + glLoadMatrixf((const GLfloat*)&viewMatrix); + + glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData()); + glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3), + vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData()); + + } else { + _blendedVertices.resize(max(_blendedVertices.size(), vertexCount)); + _blendedNormals.resize(_blendedVertices.size()); + memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3)); + memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3)); + + // blend in each coefficient + const vector& coefficients = _owningHead->getBlendshapeCoefficients(); + for (int j = 0; j < coefficients.size(); j++) { + float coefficient = coefficients[j]; + if (coefficient == 0.0f || j >= mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) { + continue; + } + const float NORMAL_COEFFICIENT_SCALE = 0.01f; + float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE; + const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData(); + const glm::vec3* normal = mesh.blendshapes[j].normals.constData(); + for (const int* index = mesh.blendshapes[j].indices.constData(), + *end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) { + _blendedVertices[*index] += *vertex * coefficient; + _blendedNormals[*index] += *normal * normalCoefficient; + } + } + + glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData()); + glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3), + vertexCount * sizeof(glm::vec3), _blendedNormals.constData()); + } } glVertexPointer(3, GL_FLOAT, 0, 0); glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3))); @@ -199,12 +313,16 @@ bool BlendFace::render(float alpha) { return true; } -void BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { +bool BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition, bool upright) const { if (!isActive()) { - return; + return false; } - + glm::vec3 translation = _owningHead->getPosition(); glm::quat orientation = _owningHead->getOrientation(); + if (upright) { + translation = static_cast(_owningHead->_owningAvatar)->getUprightHeadPosition(); + orientation = static_cast(_owningHead->_owningAvatar)->getWorldAlignedOrientation(); + } glm::vec3 scale(-_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE, -_owningHead->getScale() * MODEL_SCALE); bool foundFirst = false; @@ -212,16 +330,20 @@ void BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEy const FBXGeometry& geometry = _geometry->getFBXGeometry(); foreach (const FBXMesh& mesh, geometry.meshes) { if (mesh.isEye) { - glm::vec3 position = orientation * ((mesh.pivot + MODEL_TRANSLATION - geometry.neckPivot) * scale) + - _owningHead->getPosition(); + glm::vec3 position = orientation * ((mesh.pivot + MODEL_TRANSLATION - geometry.neckPivot) * scale) + translation; if (foundFirst) { secondEyePosition = position; - return; + return true; } firstEyePosition = position; foundFirst = true; } } + return false; +} + +glm::vec4 BlendFace::computeAverageColor() const { + return _geometry ? _geometry->computeAverageColor() : glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); } void BlendFace::setModelURL(const QUrl& url) { @@ -243,4 +365,5 @@ void BlendFace::deleteGeometry() { glDeleteBuffers(1, &id); } _blendedVertexBufferIDs.clear(); + _meshStates.clear(); } diff --git a/interface/src/avatar/BlendFace.h b/interface/src/avatar/BlendFace.h index 9307dc5c89..2a954cc249 100644 --- a/interface/src/avatar/BlendFace.h +++ b/interface/src/avatar/BlendFace.h @@ -33,12 +33,20 @@ public: bool isActive() const { return _geometry && _geometry->isLoaded(); } void init(); + void reset(); + void simulate(float deltaTime); bool render(float alpha); Q_INVOKABLE void setModelURL(const QUrl& url); const QUrl& getModelURL() const { return _modelURL; } - void getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; + /// Retrieve the positions of up to two eye meshes. + /// \param upright if true, retrieve the locations of the eyes in the upright position + /// \return whether or not both eye meshes were found + bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition, bool upright = false) const; + + /// Returns the average color of all meshes in the geometry. + glm::vec4 computeAverageColor() const; private: @@ -50,8 +58,17 @@ private: QSharedPointer _geometry; + class MeshState { + public: + QVector worldSpaceVertices; + QVector vertexVelocities; + QVector worldSpaceNormals; + }; + + QVector _meshStates; QVector _blendedVertexBufferIDs; QVector > _dilatedTextures; + bool _resetStates; QVector _blendedVertices; QVector _blendedNormals; diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index e54d2fb18c..d5a6991d54 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -83,8 +83,6 @@ Head::Head(Avatar* owningAvatar) : _mousePitch(0.f), _cameraYaw(_yaw), _isCameraMoving(false), - _cameraFollowsHead(false), - _cameraFollowHeadRate(0.0f), _face(this), _perlinFace(this), _blendFace(this) @@ -117,6 +115,8 @@ void Head::reset() { if (USING_PHYSICAL_MOHAWK) { resetHairPhysics(); } + + _blendFace.reset(); } void Head::resetHairPhysics() { @@ -140,8 +140,10 @@ void Head::simulate(float deltaTime, bool isMine) { // Update audio trailing average for rendering facial animations Faceshift* faceshift = Application::getInstance()->getFaceshift(); - _isFaceshiftConnected = faceshift != NULL; - + if (isMine) { + _isFaceshiftConnected = faceshift->isActive(); + } + if (isMine && faceshift->isActive()) { const float EYE_OPEN_SCALE = 0.5f; _leftEyeBlink = faceshift->getLeftBlink() - EYE_OPEN_SCALE * faceshift->getLeftEyeOpen(); @@ -155,7 +157,7 @@ void Head::simulate(float deltaTime, bool isMine) { _browAudioLift = faceshift->getBrowUpCenter() * BROW_HEIGHT_SCALE; _blendshapeCoefficients = faceshift->getBlendshapeCoefficients(); - } else if (!_isFaceshiftConnected) { + } else if (!_isFaceshiftConnected) { // Update eye saccades const float AVERAGE_MICROSACCADE_INTERVAL = 0.50f; const float AVERAGE_SACCADE_INTERVAL = 4.0f; @@ -235,6 +237,8 @@ void Head::simulate(float deltaTime, bool isMine) { if (USING_PHYSICAL_MOHAWK) { updateHairPhysics(deltaTime); } + + _blendFace.simulate(deltaTime); } void Head::calculateGeometry() { @@ -300,15 +304,14 @@ void Head::render(float alpha, bool isMine) { renderEyeBrows(); } } + + if (_blendFace.isActive()) { + // the blend face may have custom eye meshes + _blendFace.getEyePositions(_leftEyePosition, _rightEyePosition); + } if (_renderLookatVectors) { - glm::vec3 firstEyePosition = _leftEyePosition; - glm::vec3 secondEyePosition = _rightEyePosition; - if (_blendFace.isActive()) { - // the blend face may have custom eye meshes - _blendFace.getEyePositions(firstEyePosition, secondEyePosition); - } - renderLookatVectors(firstEyePosition, secondEyePosition, _lookAtPosition); + renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition); } } diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 38574fa430..b84d988026 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -57,7 +57,6 @@ public: void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; } void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; } void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; } - void setCameraFollowsHead(bool cameraFollowsHead) { _cameraFollowsHead = cameraFollowsHead; } float getMousePitch() const { return _mousePitch; } void setMousePitch(float mousePitch) { _mousePitch = mousePitch; } @@ -133,8 +132,6 @@ private: float _mousePitch; float _cameraYaw; bool _isCameraMoving; - bool _cameraFollowsHead; - float _cameraFollowHeadRate; Face _face; PerlinFace _perlinFace; BlendFace _blendFace; @@ -158,6 +155,7 @@ private: void resetHairPhysics(); void updateHairPhysics(float deltaTime); + friend class BlendFace; friend class PerlinFace; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f7c26e75aa..ee4f0c4811 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -16,6 +16,7 @@ #include #include "Application.h" +#include "DataServerClient.h" #include "MyAvatar.h" #include "Physics.h" #include "devices/OculusManager.h" @@ -356,8 +357,7 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) { } // Update avatar head rotation with sensor data -void MyAvatar::updateFromGyrosAndOrWebcam(bool gyroLook, - float pitchFromTouch) { +void MyAvatar::updateFromGyrosAndOrWebcam(float pitchFromTouch, bool turnWithHead) { Faceshift* faceshift = Application::getInstance()->getFaceshift(); SerialInterface* gyros = Application::getInstance()->getSerialHeadSensor(); Webcam* webcam = Application::getInstance()->getWebcam(); @@ -367,11 +367,13 @@ void MyAvatar::updateFromGyrosAndOrWebcam(bool gyroLook, estimatedPosition = faceshift->getHeadTranslation(); estimatedRotation = safeEulerAngles(faceshift->getHeadRotation()); // Rotate the body if the head is turned quickly - glm::vec3 headAngularVelocity = faceshift->getHeadAngularVelocity(); - const float FACESHIFT_YAW_VIEW_SENSITIVITY = 20.f; - const float FACESHIFT_MIN_YAW_VELOCITY = 1.0f; - if (fabs(headAngularVelocity.y) > FACESHIFT_MIN_YAW_VELOCITY) { - _bodyYawDelta += headAngularVelocity.y * FACESHIFT_YAW_VIEW_SENSITIVITY; + if (turnWithHead) { + glm::vec3 headAngularVelocity = faceshift->getHeadAngularVelocity(); + const float FACESHIFT_YAW_VIEW_SENSITIVITY = 20.f; + const float FACESHIFT_MIN_YAW_VELOCITY = 1.0f; + if (fabs(headAngularVelocity.y) > FACESHIFT_MIN_YAW_VELOCITY) { + _bodyYawDelta += headAngularVelocity.y * FACESHIFT_YAW_VIEW_SENSITIVITY; + } } } else if (gyros->isActive()) { estimatedRotation = gyros->getEstimatedRotation(); @@ -428,7 +430,6 @@ void MyAvatar::updateFromGyrosAndOrWebcam(bool gyroLook, _head.setPitch(estimatedRotation.x * AVATAR_HEAD_PITCH_MAGNIFY); _head.setYaw(estimatedRotation.y * AVATAR_HEAD_YAW_MAGNIFY); _head.setRoll(estimatedRotation.z * AVATAR_HEAD_ROLL_MAGNIFY); - _head.setCameraFollowsHead(gyroLook); // Update torso lean distance based on accelerometer data const float TORSO_LENGTH = _scale * 0.5f; @@ -524,6 +525,50 @@ void MyAvatar::renderScreenTint(ScreenTintLayer layer, Camera& whichCamera) { } } +void MyAvatar::saveData(QSettings* settings) { + settings->beginGroup("Avatar"); + + settings->setValue("bodyYaw", _bodyYaw); + settings->setValue("bodyPitch", _bodyPitch); + settings->setValue("bodyRoll", _bodyRoll); + + settings->setValue("position_x", _position.x); + settings->setValue("position_y", _position.y); + settings->setValue("position_z", _position.z); + + settings->setValue("voxelURL", _voxels.getVoxelURL()); + settings->setValue("pupilDilation", _head.getPupilDilation()); + + settings->setValue("leanScale", _leanScale); + settings->setValue("scale", _newScale); + + settings->endGroup(); +} + +void MyAvatar::loadData(QSettings* settings) { + settings->beginGroup("Avatar"); + + // in case settings is corrupt or missing loadSetting() will check for NaN + _bodyYaw = loadSetting(settings, "bodyYaw", 0.0f); + _bodyPitch = loadSetting(settings, "bodyPitch", 0.0f); + _bodyRoll = loadSetting(settings, "bodyRoll", 0.0f); + _position.x = loadSetting(settings, "position_x", 0.0f); + _position.y = loadSetting(settings, "position_y", 0.0f); + _position.z = loadSetting(settings, "position_z", 0.0f); + + _voxels.setVoxelURL(settings->value("voxelURL").toUrl()); + _head.setPupilDilation(settings->value("pupilDilation", 0.0f).toFloat()); + + _leanScale = loadSetting(settings, "leanScale", 0.05f); + + _newScale = loadSetting(settings, "scale", 1.0f); + setScale(_scale); + Application::getInstance()->getCamera()->setScale(_scale); + + settings->endGroup(); +} + + float MyAvatar::getAbsoluteHeadYaw() const { return glm::yaw(_head.getOrientation()); } @@ -532,10 +577,10 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const { return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, _pelvisToHeadLength, 0.0f); } -glm::vec3 MyAvatar::getUprightEyeLevelPosition() const { - const float EYE_UP_OFFSET = 0.36f; - glm::vec3 up = getWorldAlignedOrientation() * IDENTITY_UP; - return _position + up * _scale * BODY_BALL_RADIUS_HEAD_BASE * EYE_UP_OFFSET + glm::vec3(0.0f, _pelvisToHeadLength, 0.0f); +glm::vec3 MyAvatar::getEyeLevelPosition() const { + const float EYE_UP_OFFSET = 0.36f; + return _position + getWorldAlignedOrientation() * _skeleton.joint[AVATAR_JOINT_TORSO].rotation * + glm::vec3(0.0f, _pelvisToHeadLength + _scale * BODY_BALL_RADIUS_HEAD_BASE * EYE_UP_OFFSET, 0.0f); } float MyAvatar::getBallRenderAlpha(int ball, bool lookingInMirror) const { @@ -555,9 +600,6 @@ void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) { return; } - // glow when moving - Glower glower(_moving ? 1.0f : 0.0f); - if (_head.getFace().isFullFrame()) { // Render the full-frame video float alpha = getBallRenderAlpha(BODY_BALL_HEAD_BASE, lookingInMirror); @@ -566,6 +608,13 @@ void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) { } } else if (renderAvatarBalls || !_voxels.getVoxelURL().isValid()) { // Render the body as balls and cones + glm::vec3 skinColor(SKIN_COLOR[0], SKIN_COLOR[1], SKIN_COLOR[2]); + glm::vec3 darkSkinColor(DARK_SKIN_COLOR[0], DARK_SKIN_COLOR[1], DARK_SKIN_COLOR[2]); + if (_head.getBlendFace().isActive()) { + skinColor = glm::vec3(_head.getBlendFace().computeAverageColor()); + const float SKIN_DARKENING = 0.9f; + darkSkinColor = skinColor * SKIN_DARKENING; + } for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) { float alpha = getBallRenderAlpha(b, lookingInMirror); @@ -586,14 +635,14 @@ void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) { if (b == BODY_BALL_RIGHT_ELBOW || b == BODY_BALL_RIGHT_WRIST || b == BODY_BALL_RIGHT_FINGERTIPS ) { - glColor3f(SKIN_COLOR[0] + _bodyBall[b].touchForce * 0.3f, - SKIN_COLOR[1] - _bodyBall[b].touchForce * 0.2f, - SKIN_COLOR[2] - _bodyBall[b].touchForce * 0.1f); + glColor3f(skinColor.r + _bodyBall[b].touchForce * 0.3f, + skinColor.g - _bodyBall[b].touchForce * 0.2f, + skinColor.b - _bodyBall[b].touchForce * 0.1f); } else { - glColor4f(SKIN_COLOR[0] + _bodyBall[b].touchForce * 0.3f, - SKIN_COLOR[1] - _bodyBall[b].touchForce * 0.2f, - SKIN_COLOR[2] - _bodyBall[b].touchForce * 0.1f, - alpha); + glColor4f(skinColor.r + _bodyBall[b].touchForce * 0.3f, + skinColor.g - _bodyBall[b].touchForce * 0.2f, + skinColor.b - _bodyBall[b].touchForce * 0.1f, + alpha); } if (b == BODY_BALL_NECK_BASE && _head.getBlendFace().isActive()) { @@ -619,7 +668,7 @@ void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) { && (b != BODY_BALL_LEFT_SHOULDER) && (b != BODY_BALL_RIGHT_COLLAR) && (b != BODY_BALL_RIGHT_SHOULDER)) { - glColor3fv(DARK_SKIN_COLOR); + glColor3fv((const GLfloat*)&darkSkinColor); float r2 = _bodyBall[b].radius * 0.8; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 95c1efd5a8..4dc04a978e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -9,6 +9,8 @@ #ifndef __interface__myavatar__ #define __interface__myavatar__ +#include + #include "Avatar.h" class MyAvatar : public Avatar { @@ -17,7 +19,7 @@ public: void reset(); void simulate(float deltaTime, Transmitter* transmitter); - void updateFromGyrosAndOrWebcam(bool gyroLook, float pitchFromTouch); + void updateFromGyrosAndOrWebcam(float pitchFromTouch, bool turnWithHead); void render(bool lookingInMirror, bool renderAvatarBalls); void renderScreenTint(ScreenTintLayer layer, Camera& whichCamera); @@ -46,7 +48,11 @@ public: Avatar* getLeadingAvatar() const { return _leadingAvatar; } glm::vec3 getGravity() const { return _gravity; } glm::vec3 getUprightHeadPosition() const; - glm::vec3 getUprightEyeLevelPosition() const; + glm::vec3 getEyeLevelPosition() const; + + // get/set avatar data + void saveData(QSettings* settings); + void loadData(QSettings* settings); // Set what driving keys are being pressed to control thrust levels void setDriveKeys(int key, bool val) { _driveKeys[key] = val; }; @@ -57,7 +63,7 @@ public: void addThrust(glm::vec3 newThrust) { _thrust += newThrust; }; glm::vec3 getThrust() { return _thrust; }; - private: +private: bool _mousePressed; float _bodyPitchDelta; float _bodyRollDelta; diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp new file mode 100644 index 0000000000..4b89849620 --- /dev/null +++ b/interface/src/avatar/Profile.cpp @@ -0,0 +1,106 @@ +// +// Profile.cpp +// hifi +// +// Created by Stephen Birarda on 10/8/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#include + +#include + +#include "Profile.h" +#include "DataServerClient.h" + +Profile::Profile(const QString &username) : + _username(username), + _uuid(), + _lastDomain(), + _lastPosition(0.0, 0.0, 0.0), + _faceModelURL() +{ + if (!_username.isEmpty()) { + // we've been given a new username, ask the data-server for profile + DataServerClient::getClientValueForKey(DataServerKey::UUID); + } +} + +QString Profile::getUserString() const { + if (_uuid.isNull()) { + return _username; + } else { + return uuidStringWithoutCurlyBraces(_uuid); + } +} + +void Profile::setUUID(const QUuid& uuid) { + _uuid = uuid; + + // when the UUID is changed we need set it appropriately on our avatar instance + Application::getInstance()->getAvatar()->setUUID(_uuid); +} + +void Profile::setFaceModelURL(const QUrl& faceModelURL) { + _faceModelURL = faceModelURL; + + QMetaObject::invokeMethod(&Application::getInstance()->getAvatar()->getHead().getBlendFace(), + "setModelURL", + Q_ARG(QUrl, _faceModelURL)); +} + +void Profile::updateDomain(const QString& domain) { + if (_lastDomain != domain) { + _lastDomain = domain; + + // send the changed domain to the data-server + DataServerClient::putValueForKey(DataServerKey::Domain, domain.toLocal8Bit().constData()); + } +} + +void Profile::updatePosition(const glm::vec3 position) { + if (_lastPosition != position) { + + static timeval lastPositionSend = {}; + const uint64_t DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000; + const float DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS = 1; + + if (usecTimestampNow() - usecTimestamp(&lastPositionSend) >= DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS && + (fabsf(_lastPosition.x - position.x) >= DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS || + fabsf(_lastPosition.y - position.y) >= DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS || + fabsf(_lastPosition.z - position.z) >= DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS)) { + + // if it has been 5 seconds since the last position change and the user has moved >= the threshold + // in at least one of the axis then send the position update to the data-server + + _lastPosition = position; + + // update the lastPositionSend to now + gettimeofday(&lastPositionSend, NULL); + + // send the changed position to the data-server + QString positionString = QString("%1,%2,%3").arg(position.x).arg(position.y).arg(position.z); + DataServerClient::putValueForKey(DataServerKey::Position, positionString.toLocal8Bit().constData()); + } + } +} + +void Profile::saveData(QSettings* settings) { + settings->beginGroup("Profile"); + + settings->setValue("username", _username); + settings->setValue("UUID", _uuid); + settings->setValue("faceModelURL", _faceModelURL); + + settings->endGroup(); +} + +void Profile::loadData(QSettings* settings) { + settings->beginGroup("Profile"); + + _username = settings->value("username").toString(); + this->setUUID(settings->value("UUID").toUuid()); + _faceModelURL = settings->value("faceModelURL").toUrl(); + + settings->endGroup(); +} \ No newline at end of file diff --git a/interface/src/avatar/Profile.h b/interface/src/avatar/Profile.h new file mode 100644 index 0000000000..46086d1476 --- /dev/null +++ b/interface/src/avatar/Profile.h @@ -0,0 +1,48 @@ +// +// Profile.h +// hifi +// +// Created by Stephen Birarda on 10/8/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__Profile__ +#define __hifi__Profile__ + +#include +#include +#include + +#include + +class Profile { +public: + Profile(const QString& username); + + QString getUserString() const; + + const QString& getUsername() const { return _username; } + + void setUUID(const QUuid& uuid); + const QUuid& getUUID() { return _uuid; } + + void setFaceModelURL(const QUrl& faceModelURL); + const QUrl& getFaceModelURL() const { return _faceModelURL; } + + void updateDomain(const QString& domain); + void updatePosition(const glm::vec3 position); + + QString getLastDomain() const { return _lastDomain; } + const glm::vec3& getLastPosition() const { return _lastPosition; } + + void saveData(QSettings* settings); + void loadData(QSettings* settings); +private: + QString _username; + QUuid _uuid; + QString _lastDomain; + glm::vec3 _lastPosition; + QUrl _faceModelURL; +}; + +#endif /* defined(__hifi__Profile__) */ diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index afacc86204..8091687488 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -300,6 +300,7 @@ const char* FACESHIFT_BLENDSHAPES[] = { class Transform { public: + QByteArray name; bool inheritScale; glm::mat4 withScale; glm::mat4 withoutScale; @@ -383,7 +384,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QList mappings = blendshapeMappings.values(blendshapeName); if (mappings.isEmpty()) { blendshapeIndices.insert(blendshapeName, QPair(i, 1.0f)); - blendshapeIndices.insert("ExpressionBlendshapes." + blendshapeName, QPair(i, 1.0f)); } else { foreach (const QVariant& mapping, mappings) { QVariantList blendshapeMapping = mapping.toList(); @@ -536,7 +536,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) glm::vec3 preRotation, rotation, postRotation; glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f); glm::vec3 scalePivot, rotationPivot; - Transform transform = { true }; + Transform transform = { name, true }; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "Properties70") { foreach (const FBXNode& property, subobject.children) { @@ -636,8 +636,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } } else if (object.properties.at(2) == "BlendShapeChannel") { QByteArray name = object.properties.at(1).toByteArray(); + name = name.left(name.indexOf('\0')); + if (!blendshapeIndices.contains(name)) { + // try everything after the dot + name = name.mid(name.lastIndexOf('.') + 1); + } blendshapeChannelIndices.insert(object.properties.at(0).value(), - blendshapeIndices.value(name.left(name.indexOf('\0')))); + blendshapeIndices.value(name)); } } } @@ -679,14 +684,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) mapping.value("ry").toFloat(), mapping.value("rz").toFloat())))) * glm::scale(offsetScale, offsetScale, offsetScale); - // as a temporary hack, put the mesh with the most blendshapes on top; assume it to be the face FBXGeometry geometry; - int mostBlendshapes = 0; + QVariantHash springs = mapping.value("spring").toHash(); + QVariant defaultSpring = springs.value("default"); for (QHash::iterator it = meshes.begin(); it != meshes.end(); it++) { FBXMesh& mesh = it.value(); // accumulate local transforms qint64 modelID = parentMap.value(it.key()); + mesh.springiness = springs.value(localTransforms.value(modelID).name, defaultSpring).toFloat(); glm::mat4 modelTransform = getGlobalTransform(parentMap, localTransforms, modelID); // look for textures, material properties @@ -731,13 +737,47 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } } - if (mesh.blendshapes.size() > mostBlendshapes) { - geometry.meshes.prepend(mesh); - mostBlendshapes = mesh.blendshapes.size(); + // extract spring edges, connections if springy + if (mesh.springiness > 0.0f) { + QSet > edges; - } else { - geometry.meshes.append(mesh); + mesh.vertexConnections.resize(mesh.vertices.size()); + for (int i = 0; i < mesh.quadIndices.size(); i += 4) { + int index0 = mesh.quadIndices.at(i); + int index1 = mesh.quadIndices.at(i + 1); + int index2 = mesh.quadIndices.at(i + 2); + int index3 = mesh.quadIndices.at(i + 3); + + edges.insert(QPair(qMin(index0, index1), qMax(index0, index1))); + edges.insert(QPair(qMin(index1, index2), qMax(index1, index2))); + edges.insert(QPair(qMin(index2, index3), qMax(index2, index3))); + edges.insert(QPair(qMin(index3, index0), qMax(index3, index0))); + + mesh.vertexConnections[index0].append(QPair(index3, index1)); + mesh.vertexConnections[index1].append(QPair(index0, index2)); + mesh.vertexConnections[index2].append(QPair(index1, index3)); + mesh.vertexConnections[index3].append(QPair(index2, index0)); + } + for (int i = 0; i < mesh.triangleIndices.size(); i += 3) { + int index0 = mesh.triangleIndices.at(i); + int index1 = mesh.triangleIndices.at(i + 1); + int index2 = mesh.triangleIndices.at(i + 2); + + edges.insert(QPair(qMin(index0, index1), qMax(index0, index1))); + edges.insert(QPair(qMin(index1, index2), qMax(index1, index2))); + edges.insert(QPair(qMin(index2, index0), qMax(index2, index0))); + + mesh.vertexConnections[index0].append(QPair(index2, index1)); + mesh.vertexConnections[index1].append(QPair(index0, index2)); + mesh.vertexConnections[index2].append(QPair(index1, index0)); + } + + for (QSet >::const_iterator edge = edges.constBegin(); edge != edges.constEnd(); edge++) { + mesh.springEdges.append(*edge); + } } + + geometry.meshes.append(mesh); } // extract translation component for neck pivot diff --git a/interface/src/renderer/FBXReader.h b/interface/src/renderer/FBXReader.h index 310dea265f..5c2ffd9ee0 100644 --- a/interface/src/renderer/FBXReader.h +++ b/interface/src/renderer/FBXReader.h @@ -9,6 +9,7 @@ #ifndef __interface__FBXReader__ #define __interface__FBXReader__ +#include #include #include @@ -59,6 +60,10 @@ public: QByteArray normalFilename; QVector blendshapes; + + float springiness; + QVector > springEdges; + QVector, 4> > vertexConnections; }; /// A set of meshes extracted from an FBX document. diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index c63ec7eed4..926f226d6c 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -288,6 +288,23 @@ NetworkGeometry::~NetworkGeometry() { } } +glm::vec4 NetworkGeometry::computeAverageColor() const { + glm::vec4 totalColor; + int totalVertices = 0; + for (int i = 0; i < _meshes.size(); i++) { + if (_geometry.meshes.at(i).isEye) { + continue; // skip eyes + } + glm::vec4 color = glm::vec4(_geometry.meshes.at(i).diffuseColor, 1.0f); + if (_meshes.at(i).diffuseTexture) { + color *= _meshes.at(i).diffuseTexture->getAverageColor(); + } + totalColor += color * _geometry.meshes.at(i).vertices.size(); + totalVertices += _geometry.meshes.at(i).vertices.size(); + } + return (totalVertices == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalVertices; +} + void NetworkGeometry::handleModelReplyError() { qDebug() << _modelReply->errorString() << "\n"; @@ -346,7 +363,7 @@ void NetworkGeometry::maybeReadModelWithMapping() { glGenBuffers(1, &networkMesh.vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID); - if (mesh.blendshapes.isEmpty()) { + if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) { glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) + mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData()); diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index cf868fb69b..ba7ef8db0e 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -62,6 +62,9 @@ public: const FBXGeometry& getFBXGeometry() const { return _geometry; } const QVector& getMeshes() const { return _meshes; } + /// Returns the average color of all meshes in the geometry. + glm::vec4 computeAverageColor() const; + private slots: void handleModelReplyError(); diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 479459f30c..d12b8098da 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -173,7 +173,7 @@ Texture::~Texture() { glDeleteTextures(1, &_id); } -NetworkTexture::NetworkTexture(const QUrl& url) : _reply(NULL) { +NetworkTexture::NetworkTexture(const QUrl& url) : _reply(NULL), _averageColor(1.0f, 1.0f, 1.0f, 1.0f) { if (!url.isValid()) { return; } @@ -206,6 +206,21 @@ void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTo _reply = NULL; QImage image = QImage::fromData(entirety).convertToFormat(QImage::Format_ARGB32); + + // sum up the colors for the average + glm::vec4 accumulated; + for (int y = 0; y < image.height(); y++) { + for (int x = 0; x < image.width(); x++) { + QRgb pixel = image.pixel(x, y); + accumulated.r += qRed(pixel); + accumulated.g += qGreen(pixel); + accumulated.b += qBlue(pixel); + accumulated.a += qAlpha(pixel); + } + } + const float EIGHT_BIT_MAXIMUM = 255.0f; + _averageColor = accumulated / (image.width() * image.height() * EIGHT_BIT_MAXIMUM); + imageLoaded(image); glBindTexture(GL_TEXTURE_2D, getID()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1, diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index a562d2c046..4136d23d86 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -99,6 +99,9 @@ public: NetworkTexture(const QUrl& url); ~NetworkTexture(); + /// Returns the average color over the entire texture. + const glm::vec4& getAverageColor() const { return _averageColor; } + protected: virtual void imageLoaded(const QImage& image); @@ -111,6 +114,7 @@ private slots: private: QNetworkReply* _reply; + glm::vec4 _averageColor; }; /// Caches derived, dilated textures. diff --git a/interface/src/world.h b/interface/src/world.h index f53db4ff49..a226dc228e 100644 --- a/interface/src/world.h +++ b/interface/src/world.h @@ -4,9 +4,7 @@ // // Created by Philip Rosedale on 8/23/12. // Copyright (c) 2012 High Fidelity, Inc. All rights reserved. -// - -// Simulation happens in positive cube with edge of size WORLD_SIZE +// #ifndef __interface__world__ #define __interface__world__ diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index edc76bedca..f0085a4138 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -23,6 +23,7 @@ static const float fingerVectorRadix = 4; // bits of precision when converting f AvatarData::AvatarData(Node* owningNode) : NodeData(owningNode), + _uuid(), _handPosition(0,0,0), _bodyYaw(-90.0), _bodyPitch(0.0), @@ -116,6 +117,11 @@ int AvatarData::getBroadcastData(unsigned char* destinationBuffer) { _handData = new HandData(this); } + // UUID + QByteArray uuidByteArray = _uuid.toRfc4122(); + memcpy(destinationBuffer, uuidByteArray.constData(), uuidByteArray.size()); + destinationBuffer += uuidByteArray.size(); + // Body world position memcpy(destinationBuffer, &_position, sizeof(float) * 3); destinationBuffer += sizeof(float) * 3; @@ -249,6 +255,11 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) { // push past the node ID sourceBuffer += sizeof(uint16_t); + // UUID + const int NUM_BYTES_RFC4122_UUID = 16; + _uuid = QUuid::fromRfc4122(QByteArray((char*) sourceBuffer, NUM_BYTES_RFC4122_UUID)); + sourceBuffer += NUM_BYTES_RFC4122_UUID; + // Body world position memcpy(&_position, sourceBuffer, sizeof(float) * 3); sourceBuffer += sizeof(float) * 3; @@ -259,7 +270,7 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) { sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyRoll); // Body scale - sourceBuffer += unpackFloatRatioFromTwoByte( sourceBuffer, _newScale); + sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _newScale); // Follow mode info memcpy(&_leaderID, sourceBuffer, sizeof(uint16_t)); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 82ef0d671a..57327f3bba 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -72,6 +73,9 @@ public: int getBroadcastData(unsigned char* destinationBuffer); int parseData(unsigned char* sourceBuffer, int numBytes); + QUuid& getUUID() { return _uuid; } + void setUUID(const QUuid& uuid) { _uuid = uuid; } + // Body Rotation float getBodyYaw() const { return _bodyYaw; } void setBodyYaw(float bodyYaw) { _bodyYaw = bodyYaw; } @@ -79,7 +83,6 @@ public: void setBodyPitch(float bodyPitch) { _bodyPitch = bodyPitch; } float getBodyRoll() const { return _bodyRoll; } void setBodyRoll(float bodyRoll) { _bodyRoll = bodyRoll; } - // Hand State void setHandState(char s) { _handState = s; } @@ -133,6 +136,8 @@ public slots: void setWantOcclusionCulling(bool wantOcclusionCulling) { _wantOcclusionCulling = wantOcclusionCulling; } protected: + QUuid _uuid; + glm::vec3 _position; glm::vec3 _handPosition; diff --git a/libraries/shared/src/Assignment.cpp b/libraries/shared/src/Assignment.cpp index f9cf727894..6829a8b266 100644 --- a/libraries/shared/src/Assignment.cpp +++ b/libraries/shared/src/Assignment.cpp @@ -139,10 +139,6 @@ void Assignment::setPayload(const uchar* payload, int numBytes) { memcpy(_payload, payload, _numPayloadBytes); } -QString Assignment::getUUIDStringWithoutCurlyBraces() const { - return _uuid.toString().mid(1, _uuid.toString().length() - 2); -} - int Assignment::packToBuffer(unsigned char* buffer) { int numPackedBytes = 0; diff --git a/libraries/shared/src/Assignment.h b/libraries/shared/src/Assignment.h index 55e9f2816b..694be61e0e 100644 --- a/libraries/shared/src/Assignment.h +++ b/libraries/shared/src/Assignment.h @@ -60,7 +60,6 @@ public: void setUUID(const QUuid& uuid) { _uuid = uuid; } const QUuid& getUUID() const { return _uuid; } - QString getUUIDStringWithoutCurlyBraces() const; void resetUUID() { _uuid = QUuid::createUuid(); } Assignment::Command getCommand() const { return _command; } diff --git a/libraries/shared/src/Node.cpp b/libraries/shared/src/Node.cpp index c91626d99d..01002a3d61 100644 --- a/libraries/shared/src/Node.cpp +++ b/libraries/shared/src/Node.cpp @@ -61,7 +61,11 @@ Node::Node(sockaddr* publicSocket, sockaddr* localSocket, char type, uint16_t no Node::~Node() { delete _publicSocket; delete _localSocket; - delete _linkedData; + + if (_linkedData) { + _linkedData->deleteLater(); + } + delete _bytesReceivedMovingAverage; pthread_mutex_destroy(&_mutex); diff --git a/libraries/shared/src/NodeData.cpp b/libraries/shared/src/NodeData.cpp index 2d21f11647..45e8357d4f 100644 --- a/libraries/shared/src/NodeData.cpp +++ b/libraries/shared/src/NodeData.cpp @@ -14,4 +14,6 @@ NodeData::NodeData(Node* owningNode) : } -NodeData::~NodeData() {} \ No newline at end of file +NodeData::~NodeData() { + +} \ No newline at end of file diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 4307810db3..d7f27ff9cd 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -88,28 +88,35 @@ NodeList::~NodeList() { void NodeList::setDomainHostname(const QString& domainHostname) { - int colonIndex = domainHostname.indexOf(':'); - - if (colonIndex > 0) { - // the user has included a custom DS port with the hostname + if (domainHostname != _domainHostname) { + int colonIndex = domainHostname.indexOf(':'); - // the new hostname is everything up to the colon - _domainHostname = domainHostname.left(colonIndex); + if (colonIndex > 0) { + // the user has included a custom DS port with the hostname + + // the new hostname is everything up to the colon + _domainHostname = domainHostname.left(colonIndex); + + // grab the port by reading the string after the colon + _domainPort = atoi(domainHostname.mid(colonIndex + 1, domainHostname.size()).toLocal8Bit().constData()); + + qDebug() << "Updated hostname to" << _domainHostname << "and port to" << _domainPort << "\n"; + + } else { + // no port included with the hostname, simply set the member variable and reset the domain server port to default + _domainHostname = domainHostname; + _domainPort = DEFAULT_DOMAIN_SERVER_PORT; + } - // grab the port by reading the string after the colon - _domainPort = atoi(domainHostname.mid(colonIndex + 1, domainHostname.size()).toLocal8Bit().constData()); + // clear the NodeList so nodes from this domain are killed + clear(); - qDebug() << "Updated hostname to" << _domainHostname << "and port to" << _domainPort << "\n"; - - } else { - // no port included with the hostname, simply set the member variable and reset the domain server port to default - _domainHostname = domainHostname; - _domainPort = DEFAULT_DOMAIN_SERVER_PORT; + // reset our _domainIP to the null address so that a lookup happens on next check in + _domainIP.clear(); + notifyDomainChanged(); } - // reset our _domainIP to the null address so that a lookup happens on next check in - _domainIP.clear(); - notifyDomainChanged(); + } void NodeList::timePingReply(sockaddr *nodeAddress, unsigned char *packetData) { @@ -428,6 +435,7 @@ void NodeList::sendAssignment(Assignment& assignment) { } Node* NodeList::addOrUpdateNode(sockaddr* publicSocket, sockaddr* localSocket, char nodeType, uint16_t nodeId) { + NodeList::iterator node = end(); if (publicSocket) { @@ -439,7 +447,7 @@ Node* NodeList::addOrUpdateNode(sockaddr* publicSocket, sockaddr* localSocket, c } } - if (node == end()) { + if (node == end()) { // we didn't have this node, so add them Node* newNode = new Node(publicSocket, localSocket, nodeType, nodeId); @@ -532,15 +540,16 @@ Node* NodeList::soloNodeOfType(char nodeType) { void* removeSilentNodes(void *args) { NodeList* nodeList = (NodeList*) args; - uint64_t checkTimeUSecs; + uint64_t checkTimeUsecs = usecTimestampNow(); int sleepTime; while (!silentNodeThreadStopFlag) { - checkTimeUSecs = usecTimestampNow(); for(NodeList::iterator node = nodeList->begin(); node != nodeList->end(); ++node) { - if ((checkTimeUSecs - node->getLastHeardMicrostamp()) > NODE_SILENCE_THRESHOLD_USECS) { + node->lock(); + + if ((usecTimestampNow() - node->getLastHeardMicrostamp()) > NODE_SILENCE_THRESHOLD_USECS) { qDebug() << "Killed " << *node << "\n"; @@ -548,9 +557,11 @@ void* removeSilentNodes(void *args) { node->setAlive(false); } + + node->unlock(); } - sleepTime = NODE_SILENCE_THRESHOLD_USECS - (usecTimestampNow() - checkTimeUSecs); + sleepTime = NODE_SILENCE_THRESHOLD_USECS - (usecTimestampNow() - checkTimeUsecs); #ifdef _WIN32 Sleep( static_cast(1000.0f*sleepTime) ); #else diff --git a/libraries/shared/src/NodeList.h b/libraries/shared/src/NodeList.h index 1bac084fe5..9914ac9094 100644 --- a/libraries/shared/src/NodeList.h +++ b/libraries/shared/src/NodeList.h @@ -30,7 +30,7 @@ const int NODES_PER_BUCKET = 100; const int MAX_PACKET_SIZE = 1500; -const int NODE_SILENCE_THRESHOLD_USECS = 2 * 1000000; +const uint64_t NODE_SILENCE_THRESHOLD_USECS = 2 * 1000000; const int DOMAIN_SERVER_CHECK_IN_USECS = 1 * 1000000; extern const char SOLO_NODE_TYPES[2]; diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp index 1d5eb3f5d4..19efd7bd5a 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/shared/src/PacketHeaders.cpp @@ -20,7 +20,7 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type) { return 1; case PACKET_TYPE_HEAD_DATA: - return 8; + return 9; case PACKET_TYPE_AVATAR_URLS: return 1; diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index 6d20eac078..254f2f74fc 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -41,6 +41,10 @@ const PACKET_TYPE PACKET_TYPE_DEPLOY_ASSIGNMENT = 'd'; const PACKET_TYPE PACKET_TYPE_VOXEL_STATS = '#'; const PACKET_TYPE PACKET_TYPE_VOXEL_JURISDICTION = 'J'; const PACKET_TYPE PACKET_TYPE_VOXEL_JURISDICTION_REQUEST = 'j'; +const PACKET_TYPE PACKET_TYPE_DATA_SERVER_PUT = 'p'; +const PACKET_TYPE PACKET_TYPE_DATA_SERVER_GET = 'g'; +const PACKET_TYPE PACKET_TYPE_DATA_SERVER_SEND = 'u'; +const PACKET_TYPE PACKET_TYPE_DATA_SERVER_CONFIRM = 'c'; typedef char PACKET_VERSION; diff --git a/libraries/shared/src/UDPSocket.cpp b/libraries/shared/src/UDPSocket.cpp index beee5c6879..68eb2ad82e 100644 --- a/libraries/shared/src/UDPSocket.cpp +++ b/libraries/shared/src/UDPSocket.cpp @@ -43,7 +43,7 @@ bool socketMatch(const sockaddr* first, const sockaddr* second) { const sockaddr_in *secondIn = (const sockaddr_in *) second; return firstIn->sin_addr.s_addr == secondIn->sin_addr.s_addr - && firstIn->sin_port == secondIn->sin_port; + && firstIn->sin_port == secondIn->sin_port; } else { return false; } @@ -254,7 +254,7 @@ bool UDPSocket::receive(sockaddr* recvAddress, void* receivedData, ssize_t* rece #ifdef _WIN32 int addressSize = sizeof(*recvAddress); #else - socklen_t addressSize = sizeof(&recvAddress); + socklen_t addressSize = sizeof(*recvAddress); #endif *receivedBytes = recvfrom(handle, static_cast(receivedData), MAX_BUFFER_LENGTH_BYTES, 0, recvAddress, &addressSize); diff --git a/libraries/shared/src/UUID.cpp b/libraries/shared/src/UUID.cpp new file mode 100644 index 0000000000..e24c81ebe3 --- /dev/null +++ b/libraries/shared/src/UUID.cpp @@ -0,0 +1,14 @@ +// +// UUID.cpp +// hifi +// +// Created by Stephen Birarda on 10/7/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#include "UUID.h" + +QString uuidStringWithoutCurlyBraces(const QUuid& uuid) { + QString uuidStringNoBraces = uuid.toString().mid(1, uuid.toString().length() - 2); + return uuidStringNoBraces; +} \ No newline at end of file diff --git a/libraries/shared/src/UUID.h b/libraries/shared/src/UUID.h new file mode 100644 index 0000000000..985f44ae7b --- /dev/null +++ b/libraries/shared/src/UUID.h @@ -0,0 +1,16 @@ +// +// UUID.h +// hifi +// +// Created by Stephen Birarda on 10/7/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__UUID__ +#define __hifi__UUID__ + +#include + +QString uuidStringWithoutCurlyBraces(const QUuid& uuid); + +#endif /* defined(__hifi__UUID__) */ diff --git a/libraries/voxel-server-library/src/VoxelServer.cpp b/libraries/voxel-server-library/src/VoxelServer.cpp index 22ddec1627..aeda7cf277 100644 --- a/libraries/voxel-server-library/src/VoxelServer.cpp +++ b/libraries/voxel-server-library/src/VoxelServer.cpp @@ -295,19 +295,6 @@ void VoxelServer::run() { } qDebug("packetsPerSecond=%s PACKETS_PER_CLIENT_PER_INTERVAL=%d\n", packetsPerSecond, _packetsPerClientPerInterval); } - - // for now, initialize the environments with fixed values - _environmentData[1].setID(1); - _environmentData[1].setGravity(1.0f); - _environmentData[1].setAtmosphereCenter(glm::vec3(0.5, 0.5, (0.25 - 0.06125)) * (float)TREE_SCALE); - _environmentData[1].setAtmosphereInnerRadius(0.030625f * TREE_SCALE); - _environmentData[1].setAtmosphereOuterRadius(0.030625f * TREE_SCALE * 1.05f); - _environmentData[2].setID(2); - _environmentData[2].setGravity(1.0f); - _environmentData[2].setAtmosphereCenter(glm::vec3(0.5f, 0.5f, 0.5f) * (float)TREE_SCALE); - _environmentData[2].setAtmosphereInnerRadius(0.1875f * TREE_SCALE); - _environmentData[2].setAtmosphereOuterRadius(0.1875f * TREE_SCALE * 1.05f); - _environmentData[2].setScatteringWavelengths(glm::vec3(0.475f, 0.570f, 0.650f)); // swaps red and blue sockaddr senderAddress; diff --git a/libraries/voxels/src/ViewFrustum.cpp b/libraries/voxels/src/ViewFrustum.cpp index b6249ad3ab..e0cfa7cd97 100644 --- a/libraries/voxels/src/ViewFrustum.cpp +++ b/libraries/voxels/src/ViewFrustum.cpp @@ -371,7 +371,7 @@ bool ViewFrustum::matches(const ViewFrustum& compareTo, bool debug) const { void ViewFrustum::computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const { origin = _nearTopLeft + x*(_nearTopRight - _nearTopLeft) + y*(_nearBottomLeft - _nearTopLeft); - direction = glm::normalize(origin - _position); + direction = glm::normalize(origin - (_position + _orientation * _eyeOffsetPosition)); } void ViewFrustum::computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& near, float& far, @@ -401,6 +401,10 @@ void ViewFrustum::computeOffAxisFrustum(float& left, float& right, float& bottom far = max(far, -corners[i].z); } + // make sure the near clip isn't too small to be valid + const float MIN_NEAR = 0.01f; + near = max(MIN_NEAR, near); + // get the near/far normal and use it to find the clip planes glm::vec4 normal = eyeMatrix * glm::vec4(0.0f, 0.0f, 1.0f, 0.0f); nearClipPlane = glm::vec4(-normal.x, -normal.y, -normal.z, glm::dot(normal, corners[0])); diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index a5ad61208c..3b79722686 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -79,6 +79,7 @@ void VoxelTree::recurseTreeWithOperation(RecurseVoxelTreeOperation operation, vo // Recurses voxel node with an operation function void VoxelTree::recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData) { + if (operation(node, extraData)) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { VoxelNode* child = node->getChildAtIndex(i); @@ -221,6 +222,13 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, nodeWasDirty = childNodeAt->isDirty(); childNodeAt->setColor(newColor); childNodeAt->setSourceID(args.sourceID); + + // if we had a local version of the node already, it's possible that we have it in the VBO but + // with the same color data, so this won't count as a change. To address this we check the following + if (!childNodeAt->isDirty() && !childNodeAt->isKnownBufferIndex() && childNodeAt->getShouldRender()) { + childNodeAt->setDirtyBit(); // force dirty! + } + nodeIsDirty = childNodeAt->isDirty(); } if (nodeIsDirty) { diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index e7b8818cb4..318b8fc5eb 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -155,6 +155,7 @@ public: creationMode mode, bool destructive = false, bool debug = false); void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL); + void recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation, const glm::vec3& point, void* extraData=NULL); @@ -190,6 +191,7 @@ public: bool getShouldReaverage() const { return _shouldReaverage; } void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData); + void recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation, const glm::vec3& point, void* extraData);