Merge branch 'master' of https://github.com/worklist/hifi into 19437

This commit is contained in:
stojce 2013-10-11 16:22:30 +02:00
commit 75ccd3bfcc
57 changed files with 1734 additions and 650 deletions

View file

@ -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)

View file

@ -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";

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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());

View file

@ -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.

View file

@ -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();
}

View file

@ -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;

View file

@ -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
View 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
View 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

View 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);
}
}

View 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
View 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
View 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

View file

@ -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);

View file

@ -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() {

View file

@ -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";

View file

@ -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");
}

View file

@ -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();

View file

@ -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) {

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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();
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;
};

View file

@ -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;

View file

@ -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;

View 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();
}

View 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__) */

View file

@ -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

View file

@ -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.

View file

@ -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());

View file

@ -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();

View file

@ -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,

View file

@ -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.

View file

@ -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__

View file

@ -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));

View file

@ -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;

View file

@ -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;

View file

@ -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; }

View file

@ -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);

View file

@ -14,4 +14,6 @@ NodeData::NodeData(Node* owningNode) :
}
NodeData::~NodeData() {}
NodeData::~NodeData() {
}

View file

@ -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

View file

@ -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];

View file

@ -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;

View file

@ -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;

View file

@ -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);

View 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;
}

View 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__) */

View file

@ -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;

View file

@ -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]));

View file

@ -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) {

View file

@ -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);