mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-04 10:43:13 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into 19437
This commit is contained in:
commit
75ccd3bfcc
57 changed files with 1734 additions and 650 deletions
|
@ -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)
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <AvatarData.h>
|
||||
#include <NodeList.h>
|
||||
#include <UUID.h>
|
||||
#include <VoxelConstants.h>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#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());
|
||||
|
|
|
@ -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})
|
Binary file not shown.
212
eve/src/main.cpp
212
eve/src/main.cpp
|
@ -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 <cstring>
|
||||
#include <sys/time.h>
|
||||
#include <cstring>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
#include <NodeTypes.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <NodeList.h>
|
||||
#include <AvatarData.h>
|
||||
#include <AudioInjectionManager.h>
|
||||
#include <AudioInjector.h>
|
||||
|
||||
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();
|
||||
}
|
|
@ -58,6 +58,7 @@
|
|||
#include <VoxelSceneStats.h>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
85
interface/src/Cloud.cpp
Normal file
85
interface/src/Cloud.cpp
Normal file
|
@ -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 <iostream>
|
||||
#include <InterfaceConfig.h>
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
32
interface/src/Cloud.h
Normal file
32
interface/src/Cloud.h
Normal file
|
@ -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
|
222
interface/src/DataServerClient.cpp
Normal file
222
interface/src/DataServerClient.cpp
Normal file
|
@ -0,0 +1,222 @@
|
|||
//
|
||||
// DataServerClient.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Stephen Birarda on 10/7/13.
|
||||
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <UDPSocket.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "avatar/Profile.h"
|
||||
|
||||
#include "DataServerClient.h"
|
||||
|
||||
std::map<unsigned char*, int> 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<unsigned char*, int>(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<unsigned char*, int>(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<unsigned char*, int>::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<unsigned char*, int>::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);
|
||||
}
|
||||
}
|
45
interface/src/DataServerClient.h
Normal file
45
interface/src/DataServerClient.h
Normal file
|
@ -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 <map>
|
||||
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#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<unsigned char*, int> _unmatchedPackets;
|
||||
};
|
||||
|
||||
namespace DataServerKey {
|
||||
const QString Domain = "domain";
|
||||
const QString FaceMeshURL = "mesh";
|
||||
const QString Position = "position";
|
||||
const QString UUID = "uuid";
|
||||
}
|
||||
|
||||
#endif /* defined(__hifi__DataServerClient__) */
|
100
interface/src/Field.cpp
Normal file
100
interface/src/Field.cpp
Normal file
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
46
interface/src/Field.h
Normal file
46
interface/src/Field.h
Normal file
|
@ -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 <iostream>
|
||||
#include <glm/glm.hpp>
|
||||
#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
|
|
@ -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);
|
||||
|
|
|
@ -19,8 +19,12 @@
|
|||
#include <QMainWindow>
|
||||
#include <QSlider>
|
||||
#include <QStandardPaths>
|
||||
#include <QUuid>
|
||||
|
||||
#include <UUID.h>
|
||||
|
||||
#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() {
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<glBufferIndex> _freeIndexes;
|
||||
pthread_mutex_t _freeIndexLock;
|
||||
|
||||
void freeBufferIndex(glBufferIndex index);
|
||||
void clearFreeBufferIndexes();
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QSettings>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <AvatarData.h>
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
#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<glm::vec3> 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<float>& 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<QPair<int, int>, 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<NetworkMesh>& 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<float>& 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<float>& 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<MyAvatar*>(_owningHead->_owningAvatar)->getUprightHeadPosition();
|
||||
orientation = static_cast<Avatar*>(_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();
|
||||
}
|
||||
|
|
|
@ -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<NetworkGeometry> _geometry;
|
||||
|
||||
class MeshState {
|
||||
public:
|
||||
QVector<glm::vec3> worldSpaceVertices;
|
||||
QVector<glm::vec3> vertexVelocities;
|
||||
QVector<glm::vec3> worldSpaceNormals;
|
||||
};
|
||||
|
||||
QVector<MeshState> _meshStates;
|
||||
QVector<GLuint> _blendedVertexBufferIDs;
|
||||
QVector<QSharedPointer<Texture> > _dilatedTextures;
|
||||
bool _resetStates;
|
||||
|
||||
QVector<glm::vec3> _blendedVertices;
|
||||
QVector<glm::vec3> _blendedNormals;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <SharedUtil.h>
|
||||
|
||||
#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;
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#ifndef __interface__myavatar__
|
||||
#define __interface__myavatar__
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
#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;
|
||||
|
|
106
interface/src/avatar/Profile.cpp
Normal file
106
interface/src/avatar/Profile.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
//
|
||||
// Profile.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Stephen Birarda on 10/8/13.
|
||||
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <QtCore/QSettings>
|
||||
|
||||
#include <UUID.h>
|
||||
|
||||
#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();
|
||||
}
|
48
interface/src/avatar/Profile.h
Normal file
48
interface/src/avatar/Profile.h
Normal file
|
@ -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 <QtCore/QString>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
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__) */
|
|
@ -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<QVariant> mappings = blendshapeMappings.values(blendshapeName);
|
||||
if (mappings.isEmpty()) {
|
||||
blendshapeIndices.insert(blendshapeName, QPair<int, float>(i, 1.0f));
|
||||
blendshapeIndices.insert("ExpressionBlendshapes." + blendshapeName, QPair<int, float>(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<qint64>(),
|
||||
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<qint64, FBXMesh>::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<QPair<int, int> > 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<int, int>(qMin(index0, index1), qMax(index0, index1)));
|
||||
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
|
||||
edges.insert(QPair<int, int>(qMin(index2, index3), qMax(index2, index3)));
|
||||
edges.insert(QPair<int, int>(qMin(index3, index0), qMax(index3, index0)));
|
||||
|
||||
mesh.vertexConnections[index0].append(QPair<int, int>(index3, index1));
|
||||
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
|
||||
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index3));
|
||||
mesh.vertexConnections[index3].append(QPair<int, int>(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<int, int>(qMin(index0, index1), qMax(index0, index1)));
|
||||
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
|
||||
edges.insert(QPair<int, int>(qMin(index2, index0), qMax(index2, index0)));
|
||||
|
||||
mesh.vertexConnections[index0].append(QPair<int, int>(index2, index1));
|
||||
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
|
||||
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index0));
|
||||
}
|
||||
|
||||
for (QSet<QPair<int, int> >::const_iterator edge = edges.constBegin(); edge != edges.constEnd(); edge++) {
|
||||
mesh.springEdges.append(*edge);
|
||||
}
|
||||
}
|
||||
|
||||
geometry.meshes.append(mesh);
|
||||
}
|
||||
|
||||
// extract translation component for neck pivot
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#ifndef __interface__FBXReader__
|
||||
#define __interface__FBXReader__
|
||||
|
||||
#include <QVarLengthArray>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
|
||||
|
@ -59,6 +60,10 @@ public:
|
|||
QByteArray normalFilename;
|
||||
|
||||
QVector<FBXBlendshape> blendshapes;
|
||||
|
||||
float springiness;
|
||||
QVector<QPair<int, int> > springEdges;
|
||||
QVector<QVarLengthArray<QPair<int, int>, 4> > vertexConnections;
|
||||
};
|
||||
|
||||
/// A set of meshes extracted from an FBX document.
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -62,6 +62,9 @@ public:
|
|||
const FBXGeometry& getFBXGeometry() const { return _geometry; }
|
||||
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
|
||||
|
||||
/// Returns the average color of all meshes in the geometry.
|
||||
glm::vec4 computeAverageColor() const;
|
||||
|
||||
private slots:
|
||||
|
||||
void handleModelReplyError();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtCore/QVariantMap>
|
||||
|
||||
#include <NodeData.h>
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -14,4 +14,6 @@ NodeData::NodeData(Node* owningNode) :
|
|||
|
||||
}
|
||||
|
||||
NodeData::~NodeData() {}
|
||||
NodeData::~NodeData() {
|
||||
|
||||
}
|
|
@ -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<int>(1000.0f*sleepTime) );
|
||||
#else
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<char*>(receivedData), MAX_BUFFER_LENGTH_BYTES,
|
||||
0, recvAddress, &addressSize);
|
||||
|
|
14
libraries/shared/src/UUID.cpp
Normal file
14
libraries/shared/src/UUID.cpp
Normal file
|
@ -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;
|
||||
}
|
16
libraries/shared/src/UUID.h
Normal file
16
libraries/shared/src/UUID.h
Normal file
|
@ -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 <QtCore/QUuid>
|
||||
|
||||
QString uuidStringWithoutCurlyBraces(const QUuid& uuid);
|
||||
|
||||
#endif /* defined(__hifi__UUID__) */
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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]));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue