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

This commit is contained in:
stojce 2013-10-11 14:19:31 +02:00
commit a87346d39c
67 changed files with 2342 additions and 892 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

@ -594,8 +594,12 @@ void* animateVoxels(void* args) {
timeval lastSendTime;
bool firstTime = true;
while (true) {
gettimeofday(&lastSendTime, NULL);
int packetsStarting = ::voxelEditPacketSender->packetsToSendCount();
// some animations
//sendVoxelBlinkMessage();
@ -619,12 +623,22 @@ void* animateVoxels(void* args) {
if (::buildStreet) {
doBuildStreet();
}
::voxelEditPacketSender->releaseQueuedMessages();
int packetsEnding = ::voxelEditPacketSender->packetsToSendCount();
if (::voxelEditPacketSender) {
::voxelEditPacketSender->releaseQueuedMessages();
if (::nonThreadedPacketSender) {
::voxelEditPacketSender->process();
}
if (firstTime) {
int packetsPerSecond = std::max(ACTUAL_FPS, (packetsEnding - packetsStarting) * (ACTUAL_FPS));
std::cout << "Setting PPS to " << packetsPerSecond << "\n";
::voxelEditPacketSender->setPacketsPerSecond(packetsPerSecond);
firstTime = false;
}
if (::nonThreadedPacketSender) {
::voxelEditPacketSender->process();
}
uint64_t end = usecTimestampNow();
@ -715,14 +729,13 @@ int main(int argc, const char * argv[])
// Create out VoxelEditPacketSender
::voxelEditPacketSender = new VoxelEditPacketSender;
if (::voxelEditPacketSender) {
::voxelEditPacketSender->initialize(!::nonThreadedPacketSender);
if (::jurisdictionListener) {
::voxelEditPacketSender->setVoxelServerJurisdictions(::jurisdictionListener->getJurisdictions());
}
if (::nonThreadedPacketSender) {
::voxelEditPacketSender->setProcessCallIntervalHint(ANIMATE_VOXELS_INTERVAL_USECS);
}
::voxelEditPacketSender->initialize(!::nonThreadedPacketSender);
if (::jurisdictionListener) {
::voxelEditPacketSender->setVoxelServerJurisdictions(::jurisdictionListener->getJurisdictions());
}
if (::nonThreadedPacketSender) {
::voxelEditPacketSender->setProcessCallIntervalHint(ANIMATE_VOXELS_INTERVAL_USECS);
}
srand((unsigned)time(0));

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

@ -66,11 +66,12 @@ void childClient() {
// create a request assignment, accept assignments defined by the overidden type
Assignment requestAssignment(Assignment::RequestCommand, ::overiddenAssignmentType);
// if we're here we have no assignment, so send a request
qDebug() << "Waiting for assignment -" << requestAssignment << "\n";
while (true) {
if (usecTimestampNow() - usecTimestamp(&lastRequest) >= ASSIGNMENT_REQUEST_INTERVAL_USECS) {
gettimeofday(&lastRequest, NULL);
// if we're here we have no assignment, so send a request
qDebug() << "Sending an assignment request -" << requestAssignment << "\n";
nodeList->sendAssignment(requestAssignment);
}

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;
}
@ -324,13 +320,22 @@ void Application::initializeGL() {
}
void Application::paintGL() {
PerfStat("display");
PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings));
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::paintGL()");
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()) {
@ -342,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) {
@ -445,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();
@ -1153,6 +1164,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) {
@ -1186,6 +1200,7 @@ void Application::idle() {
double timeSinceLastUpdate = diffclock(&_lastTimeUpdated, &check);
if (timeSinceLastUpdate > IDLE_SIMULATE_MSECS) {
const float BIGGEST_DELTA_TIME_SECS = 0.25f;
update(glm::clamp((float)timeSinceLastUpdate / 1000.f, 0.f, BIGGEST_DELTA_TIME_SECS));
_glWidget->updateGL();
@ -1254,13 +1269,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) {
@ -1587,15 +1603,21 @@ 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());
_voxels.setUseVoxelShader(Menu::getInstance()->isOptionChecked(MenuOption::UseVoxelShader));
_voxels.setVoxelsAsPoints(Menu::getInstance()->isOptionChecked(MenuOption::VoxelsAsPoints));
_voxels.setUseFastVoxelPipeline(Menu::getInstance()->isOptionChecked(MenuOption::FastVoxelPipeline));
_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);
@ -1639,9 +1661,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;
@ -1782,8 +1804,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()) {
@ -1975,6 +1997,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;
@ -2032,7 +2059,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()) {
@ -2071,8 +2098,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()) {
@ -2172,8 +2198,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());
}
}
}
@ -2386,6 +2411,7 @@ void Application::computeOffAxisFrustum(float& left, float& right, float& bottom
}
void Application::displaySide(Camera& whichCamera) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()");
// transform by eye offset
// flip x if in mirror mode (also requires reversing winding order for backface culling)
@ -2417,6 +2443,8 @@ void Application::displaySide(Camera& whichCamera) {
setupWorldLight(whichCamera);
if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... stars...");
if (!_stars.getFileLoaded()) {
_stars.readInput(STAR_FILE, STAR_CACHE_FILE, 0);
}
@ -2442,6 +2470,8 @@ void Application::displaySide(Camera& whichCamera) {
// draw the sky dome
if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... atmosphere...");
_environment.renderAtmospheres(whichCamera);
}
@ -2464,6 +2494,9 @@ void Application::displaySide(Camera& whichCamera) {
//draw a grid ground plane....
if (Menu::getInstance()->isOptionChecked(MenuOption::GroundPlane)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... ground plane...");
// draw grass plane with fog
glEnable(GL_FOG);
glEnable(GL_NORMALIZE);
@ -2484,17 +2517,29 @@ 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)) {
_voxels.render(Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures));
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... voxels...");
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRenderVoxels)) {
_voxels.render(Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures));
}
}
// restore default, white specular
glMaterialfv(GL_FRONT, GL_SPECULAR, WHITE_SPECULAR_COLOR);
// indicate what we'll be adding/removing in mouse mode, if anything
if (_mouseVoxel.s != 0) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... voxels TOOLS UX...");
glDisable(GL_LIGHTING);
glPushMatrix();
glScalef(TREE_SCALE, TREE_SCALE, TREE_SCALE);
@ -2540,6 +2585,9 @@ void Application::displaySide(Camera& whichCamera) {
}
if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode) && _pasteMode) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... PASTE Preview...");
glPushMatrix();
glTranslatef(_mouseVoxel.x * TREE_SCALE,
_mouseVoxel.y * TREE_SCALE,
@ -2555,6 +2603,10 @@ void Application::displaySide(Camera& whichCamera) {
_myAvatar.renderScreenTint(SCREEN_TINT_BEFORE_AVATARS, whichCamera);
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... Avatars...");
// Render avatars of other nodes
NodeList* nodeList = NodeList::getInstance();
@ -2599,16 +2651,22 @@ void Application::displaySide(Camera& whichCamera) {
// render the ambient occlusion effect if enabled
if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... AmbientOcclusion...");
_ambientOcclusionEffect.render();
}
// brad's frustum for debugging
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... renderViewFrustum...");
renderViewFrustum(_viewFrustum);
}
// render voxel fades if they exist
if (_voxelFades.size() > 0) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... voxel fades...");
for(std::vector<VoxelFade>::iterator fade = _voxelFades.begin(); fade != _voxelFades.end();) {
fade->render();
if(fade->isDone()) {
@ -2618,11 +2676,18 @@ void Application::displaySide(Camera& whichCamera) {
}
}
}
renderFollowIndicator();
{
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... renderFollowIndicator...");
renderFollowIndicator();
}
// render transmitter pick ray, if non-empty
if (_transmitterPickStart != _transmitterPickEnd) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... transmitter pick ray...");
Glower glower;
const float TRANSMITTER_PICK_COLOR[] = { 1.0f, 1.0f, 0.0f };
glColor3fv(TRANSMITTER_PICK_COLOR);
@ -2644,6 +2709,8 @@ void Application::displaySide(Camera& whichCamera) {
}
void Application::displayOverlay() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displayOverlay()");
// Render 2D overlay: I/O level bar graphs and text
glMatrixMode(GL_PROJECTION);
glPushMatrix();
@ -3461,9 +3528,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) {
@ -3574,6 +3648,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");
@ -239,12 +263,24 @@ Menu::Menu() :
true,
appInstance,
SLOT(setRenderVoxels(bool)));
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::UseVoxelShader, 0,
false, this, SLOT(switchVoxelShader()));
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontRenderVoxels);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontCallOpenGLForVoxels);
_useVoxelShader = addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::UseVoxelShader, 0,
false, appInstance->getVoxels(), SLOT(setUseVoxelShader(bool)));
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelsAsPoints, 0,
false, appInstance->getVoxels(), SLOT(setVoxelsAsPoints(bool)));
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);
QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options");
@ -301,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,
@ -346,6 +376,7 @@ Menu::Menu() :
QMenu* renderDebugMenu = developerMenu->addMenu("Render Debugging Tools");
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings);
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings);
addActionToQMenuAndActionHash(renderDebugMenu,
MenuOption::KillLocalVoxels,
@ -494,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) {
@ -515,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);
}
@ -698,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;
@ -732,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);
@ -746,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());
@ -791,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());
@ -903,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() {
@ -1003,7 +1098,4 @@ void Menu::updateFrustumRenderModeAction() {
}
}
void Menu::switchVoxelShader() {
Application::getInstance()->getVoxels()->setUseVoxelShader(isOptionChecked(MenuOption::UseVoxelShader));
}

View file

@ -54,6 +54,7 @@ public:
ViewFrustumOffset getViewFrustumOffset() const { return _viewFrustumOffset; }
VoxelStatsDialog* getVoxelStatsDialog() const { return _voxelStatsDialog; }
int getMaxVoxels() const { return _maxVoxels; }
QAction* getUseVoxelShader() const { return _useVoxelShader; }
void handleViewFrustumOffsetKeyModifier(int key);
@ -68,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();
@ -78,7 +81,6 @@ private slots:
void chooseVoxelPaintColor();
void runTests();
void resetSwatchColors();
void switchVoxelShader();
private:
static Menu* _instance;
@ -116,18 +118,20 @@ private:
QActionGroup* _voxelModeActionsGroup;
VoxelStatsDialog* _voxelStatsDialog;
int _maxVoxels;
QAction* _useVoxelShader;
};
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";
@ -138,6 +142,9 @@ namespace MenuOption {
const QString DestructiveAddVoxel = "Create Voxel is Destructive";
const QString DeltaSending = "Delta Sending";
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";
@ -158,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";
@ -166,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";
@ -178,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";
@ -193,6 +204,7 @@ namespace MenuOption {
const QString ShowTrueColors = "Show TRUE Colors";
const QString SimulateLeapHand = "Simulate Leap Hand";
const QString SkeletonTracking = "Skeleton Tracking";
const QString SuppressShortTimings = "Suppress Timings Less than 10ms";
const QString LEDTracking = "LED Tracking";
const QString Stars = "Stars";
const QString Stats = "Stats";
@ -203,8 +215,7 @@ namespace MenuOption {
const QString UsePerlinFace = "Use Perlin's Face";
const QString Quit = "Quit";
const QString UseVoxelShader = "Use Voxel Shader";
const QString UseByteNormals = "Use Byte Normals";
const QString UseGlobalNormals = "Use Global Normals";
const QString VoxelsAsPoints = "Draw Voxels as Points";
const QString Voxels = "Voxels";
const QString VoxelAddMode = "Add Voxel Mode";
const QString VoxelColorMode = "Color Voxel Mode";

View file

@ -47,15 +47,14 @@ void VoxelPacketProcessor::processPacket(sockaddr& senderAddress, unsigned char*
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
Node* voxelServer = NodeList::getInstance()->nodeWithAddress(&senderAddress);
if (voxelServer && socketMatch(voxelServer->getActiveSocket(), &senderAddress)) {
voxelServer->lock();
int nodeID = voxelServer->getNodeID();
if (packetData[0] == PACKET_TYPE_ENVIRONMENT_DATA) {
app->_environment.parseData(&senderAddress, packetData, messageLength);
} else {
app->_voxels.setDataSourceID(voxelServer->getNodeID());
app->_voxels.setDataSourceID(nodeID);
app->_voxels.parseData(packetData, messageLength);
app->_voxels.setDataSourceID(UNKNOWN_NODE_ID);
}
voxelServer->unlock();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -65,8 +65,6 @@ public:
bool readFromSquareARGB32Pixels(const char* filename);
bool readFromSchematicFile(const char* filename);
void setUseVoxelShader(bool useVoxelShader);
void setMaxVoxels(int maxVoxels);
long int getMaxVoxels() const { return _maxVoxels; }
unsigned long getVoxelMemoryUsageRAM() const { return _memoryUsageRAM; }
@ -83,6 +81,7 @@ public:
void killLocalVoxels();
virtual void removeOutOfView();
virtual void hideOutOfView();
bool hasViewChanged();
bool isViewChanging();
@ -137,6 +136,8 @@ public slots:
void cancelImport();
void setUseFastVoxelPipeline(bool useFastVoxelPipeline);
void setUseVoxelShader(bool useVoxelShader);
void setVoxelsAsPoints(bool voxelsAsPoints);
protected:
float _treeScale;
@ -144,13 +145,14 @@ protected:
VoxelTree* _tree;
void setupNewVoxelsForDrawing();
void setupNewVoxelsForDrawingSingleNode();
static const bool DONT_BAIL_EARLY; // by default we will bail early, if you want to force not bailing, then use this
void setupNewVoxelsForDrawingSingleNode(bool allowBailEarly = true);
void checkForCulling();
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);
@ -184,9 +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 updateNodeInArrays(VoxelNode* node, bool reuseIndex, bool forceDraw);
int forceRemoveNodeFromArrays(VoxelNode* node);
void copyWrittenDataToReadArraysFullVBOs();
void copyWrittenDataToReadArraysPartialVBOs();
@ -216,12 +222,16 @@ private:
int _setupNewVoxelsForDrawingLastElapsed;
uint64_t _setupNewVoxelsForDrawingLastFinished;
uint64_t _lastViewCulling;
uint64_t _lastAudit;
int _lastViewCullingElapsed;
void initVoxelMemory();
void cleanupVoxelMemory();
bool _useVoxelShader;
bool _voxelsAsPoints;
bool _voxelShaderModeWhenVoxelsAsPointsEnabled;
GLuint _vboVoxelsID; /// when using voxel shader, we'll use this VBO
GLuint _vboVoxelsIndicesID; /// when using voxel shader, we'll use this VBO for our indexes
VoxelShaderVBOData* _writeVoxelShaderData;
@ -260,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();
@ -88,8 +189,10 @@ bool BlendFace::render(float alpha) {
// enable normalization under the expectation that the GPU can do it faster
glEnable(GL_NORMALIZE);
glEnable(GL_TEXTURE_2D);
glDisable(GL_COLOR_MATERIAL);
glColor4f(_owningHead->getSkinColor().r, _owningHead->getSkinColor().g, _owningHead->getSkinColor().b, alpha);
// 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);
@ -105,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);
@ -118,49 +221,62 @@ bool BlendFace::render(float alpha) {
}
}
glMultMatrixf((const GLfloat*)&mesh.transform);
// apply material properties
glm::vec4 diffuse = glm::vec4(mesh.diffuseColor, alpha);
glm::vec4 specular = glm::vec4(mesh.specularColor, alpha);
glMaterialfv(GL_FRONT, GL_AMBIENT, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, (const float*)&specular);
glMaterialf(GL_FRONT, GL_SHININESS, mesh.shininess);
// all meshes after the first are white
if (i == 1) {
glColor4f(1.0f, 1.0f, 1.0f, alpha);
}
glMultMatrixf((const GLfloat*)&mesh.transform);
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)));
@ -191,15 +307,22 @@ bool BlendFace::render(float alpha) {
glPopMatrix();
// restore all the default material settings
Application::getInstance()->setupWorldLight(*Application::getInstance()->getCamera());
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;
@ -207,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) {
@ -238,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,19 +304,21 @@ 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);
}
}
void Head::setScale (float scale) {
if (_scale == scale) {
return;
}
_scale = scale;
createMohawk();

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

@ -19,12 +19,6 @@
using namespace std;
FBXNode parseFBX(const QByteArray& data) {
QBuffer buffer(const_cast<QByteArray*>(&data));
buffer.open(QIODevice::ReadOnly);
return parseFBX(&buffer);
}
template<class T> QVariant readArray(QDataStream& in) {
quint32 arrayLength;
quint32 encoding;
@ -189,6 +183,41 @@ FBXNode parseFBX(QIODevice* device) {
return top;
}
QVariantHash parseMapping(QIODevice* device) {
QVariantHash properties;
QByteArray line;
while (!(line = device->readLine()).isEmpty()) {
if ((line = line.trimmed()).startsWith('#')) {
continue; // comment
}
QList<QByteArray> sections = line.split('=');
if (sections.size() < 2) {
continue;
}
QByteArray name = sections.at(0).trimmed();
if (sections.size() == 2) {
properties.insert(name, sections.at(1).trimmed());
} else if (sections.size() == 3) {
QVariantHash heading = properties.value(name).toHash();
heading.insert(sections.at(1).trimmed(), sections.at(2).trimmed());
properties.insert(name, heading);
} else if (sections.size() >= 4) {
QVariantHash heading = properties.value(name).toHash();
QVariantList contents;
for (int i = 2; i < sections.size(); i++) {
contents.append(sections.at(i).trimmed());
}
heading.insertMulti(sections.at(1).trimmed(), contents);
properties.insert(name, heading);
}
}
return properties;
}
QVector<glm::vec3> createVec3Vector(const QVector<double>& doubleVector) {
QVector<glm::vec3> values;
for (const double* it = doubleVector.constData(), *end = it + doubleVector.size(); it != end; ) {
@ -269,21 +298,9 @@ const char* FACESHIFT_BLENDSHAPES[] = {
""
};
QHash<QByteArray, int> createBlendshapeMap() {
QHash<QByteArray, int> map;
for (int i = 0;; i++) {
QByteArray name = FACESHIFT_BLENDSHAPES[i];
if (name != "") {
map.insert(name, i);
} else {
return map;
}
}
}
class Transform {
public:
QByteArray name;
bool inheritScale;
glm::mat4 withScale;
glm::mat4 withoutScale;
@ -315,11 +332,29 @@ glm::mat4 getGlobalTransform(const QMultiHash<qint64, qint64>& parentMap, const
class ExtractedBlendshape {
public:
qint64 id;
int index;
FBXBlendshape blendshape;
};
FBXGeometry extractFBXGeometry(const FBXNode& node) {
void printNode(const FBXNode& node, int indent) {
QByteArray spaces(indent, ' ');
qDebug("%s%s: ", spaces.data(), node.name.data());
foreach (const QVariant& property, node.properties) {
qDebug() << property;
}
qDebug() << "\n";
foreach (const FBXNode& child, node.children) {
printNode(child, indent + 1);
}
}
class Material {
public:
glm::vec3 diffuse;
glm::vec3 specular;
float shininess;
};
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
QHash<qint64, FBXMesh> meshes;
QVector<ExtractedBlendshape> blendshapes;
QMultiHash<qint64, qint64> parentMap;
@ -327,12 +362,38 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
QHash<qint64, Transform> localTransforms;
QHash<qint64, glm::mat4> transformLinkMatrices;
QHash<qint64, QByteArray> textureFilenames;
QHash<qint64, Material> materials;
QHash<qint64, qint64> diffuseTextures;
QHash<qint64, qint64> bumpTextures;
QVariantHash joints = mapping.value("joint").toHash();
QByteArray jointEyeLeftName = joints.value("jointEyeLeft", "jointEyeLeft").toByteArray();
QByteArray jointEyeRightName = joints.value("jointEyeRight", "jointEyeRight").toByteArray();
QByteArray jointNeckName = joints.value("jointNeck", "jointNeck").toByteArray();
qint64 jointEyeLeftID = 0;
qint64 jointEyeRightID = 0;
qint64 jointNeckID = 0;
QVariantHash blendshapeMappings = mapping.value("bs").toHash();
QHash<QByteArray, QPair<int, float> > blendshapeIndices;
for (int i = 0;; i++) {
QByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i];
if (blendshapeName.isEmpty()) {
break;
}
QList<QVariant> mappings = blendshapeMappings.values(blendshapeName);
if (mappings.isEmpty()) {
blendshapeIndices.insert(blendshapeName, QPair<int, float>(i, 1.0f));
} else {
foreach (const QVariant& mapping, mappings) {
QVariantList blendshapeMapping = mapping.toList();
blendshapeIndices.insert(blendshapeMapping.at(0).toByteArray(),
QPair<int, float>(i, blendshapeMapping.at(1).toFloat()));
}
}
}
QHash<qint64, QPair<int, float> > blendshapeChannelIndices;
foreach (const FBXNode& child, node.children) {
if (child.name == "Objects") {
foreach (const FBXNode& object, child.children) {
@ -342,6 +403,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
QVector<int> polygonIndices;
QVector<glm::vec3> normals;
QVector<int> normalIndices;
QVector<glm::vec2> texCoords;
QVector<int> texCoordIndices;
foreach (const FBXNode& data, object.children) {
@ -357,6 +419,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
if (subdata.name == "Normals") {
normals = createVec3Vector(subdata.properties.at(0).value<QVector<double> >());
} else if (subdata.name == "NormalsIndex") {
normalIndices = subdata.properties.at(0).value<QVector<int> >();
} else if (subdata.name == "MappingInformationType" &&
subdata.properties.at(0) == "ByVertice") {
byVertex = true;
@ -380,21 +445,33 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
// convert normals from per-index to per-vertex if necessary
if (mesh.normals.isEmpty()) {
mesh.normals.resize(mesh.vertices.size());
for (int i = 0, n = polygonIndices.size(); i < n; i++) {
int index = polygonIndices.at(i);
mesh.normals[index < 0 ? (-index - 1) : index] = normals.at(i);
}
if (normalIndices.isEmpty()) {
for (int i = 0, n = polygonIndices.size(); i < n; i++) {
int index = polygonIndices.at(i);
mesh.normals[index < 0 ? (-index - 1) : index] = normals.at(i);
}
} else {
for (int i = 0, n = polygonIndices.size(); i < n; i++) {
int index = polygonIndices.at(i);
int normalIndex = normalIndices.at(i);
if (normalIndex >= 0) {
mesh.normals[index < 0 ? (-index - 1) : index] = normals.at(normalIndex);
}
}
}
}
// same with the tex coords
mesh.texCoords.resize(mesh.vertices.size());
for (int i = 0, n = polygonIndices.size(); i < n; i++) {
int index = polygonIndices.at(i);
int texCoordIndex = texCoordIndices.at(i);
if (texCoordIndex >= 0) {
mesh.texCoords[index < 0 ? (-index - 1) : index] = texCoords.at(texCoordIndex);
}
}
if (!texCoordIndices.isEmpty()) {
mesh.texCoords.resize(mesh.vertices.size());
for (int i = 0, n = polygonIndices.size(); i < n; i++) {
int index = polygonIndices.at(i);
int texCoordIndex = texCoordIndices.at(i);
if (texCoordIndex >= 0) {
mesh.texCoords[index < 0 ? (-index - 1) : index] = texCoords.at(texCoordIndex);
}
}
}
// convert the polygons to quads and triangles
for (const int* beginIndex = polygonIndices.constData(), *end = beginIndex + polygonIndices.size();
@ -441,29 +518,25 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
}
}
// the name is followed by a null and some type info
QByteArray name = object.properties.at(1).toByteArray();
static QHash<QByteArray, int> blendshapeMap = createBlendshapeMap();
extracted.index = blendshapeMap.value(name.left(name.indexOf('\0')));
blendshapes.append(extracted);
}
} else if (object.name == "Model") {
QByteArray name = object.properties.at(1).toByteArray();
if (name.startsWith("jointEyeLeft") || name.startsWith("EyeL") || name.startsWith("joint_Leye")) {
name = name.left(name.indexOf('\0'));
if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye") {
jointEyeLeftID = object.properties.at(0).value<qint64>();
} else if (name.startsWith("jointEyeRight") || name.startsWith("EyeR") || name.startsWith("joint_Reye")) {
} else if (name == jointEyeRightName || name == "EyeR" || name == "joint_Reye") {
jointEyeRightID = object.properties.at(0).value<qint64>();
} else if (name.startsWith("jointNeck") || name.startsWith("NeckRot") || name.startsWith("joint_neck")) {
} else if (name == jointNeckName || name == "NeckRot" || name == "joint_neck") {
jointNeckID = object.properties.at(0).value<qint64>();
}
glm::vec3 translation;
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) {
@ -522,17 +595,55 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
} else if (object.name == "Texture") {
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "RelativeFilename") {
textureFilenames.insert(object.properties.at(0).value<qint64>(),
subobject.properties.at(0).toByteArray());
// trim off any path information
QByteArray filename = subobject.properties.at(0).toByteArray();
filename = filename.mid(qMax(filename.lastIndexOf('\\'), filename.lastIndexOf('/')) + 1);
textureFilenames.insert(object.properties.at(0).value<qint64>(), filename);
}
}
} else if (object.name == "Deformer" && object.properties.at(2) == "Cluster") {
} else if (object.name == "Material") {
Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f };
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "TransformLink") {
QVector<double> values = subobject.properties.at(0).value<QVector<double> >();
transformLinkMatrices.insert(object.properties.at(0).value<qint64>(), createMat4(values));
if (subobject.name == "Properties70") {
foreach (const FBXNode& property, subobject.children) {
if (property.name == "P") {
if (property.properties.at(0) == "DiffuseColor") {
material.diffuse = glm::vec3(property.properties.at(4).value<double>(),
property.properties.at(5).value<double>(),
property.properties.at(6).value<double>());
} else if (property.properties.at(0) == "SpecularColor") {
material.specular = glm::vec3(property.properties.at(4).value<double>(),
property.properties.at(5).value<double>(),
property.properties.at(6).value<double>());
} else if (property.properties.at(0) == "Shininess") {
material.shininess = property.properties.at(4).value<double>();
}
}
}
}
}
materials.insert(object.properties.at(0).value<qint64>(), material);
} else if (object.name == "Deformer") {
if (object.properties.at(2) == "Cluster") {
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "TransformLink") {
QVector<double> values = subobject.properties.at(0).value<QVector<double> >();
transformLinkMatrices.insert(object.properties.at(0).value<qint64>(), createMat4(values));
}
}
} 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));
}
}
}
} else if (child.name == "Connections") {
@ -558,25 +669,41 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
// assign the blendshapes to their corresponding meshes
foreach (const ExtractedBlendshape& extracted, blendshapes) {
qint64 blendshapeChannelID = parentMap.value(extracted.id);
QPair<int, float> index = blendshapeChannelIndices.value(blendshapeChannelID);
qint64 blendshapeID = parentMap.value(blendshapeChannelID);
qint64 meshID = parentMap.value(blendshapeID);
FBXMesh& mesh = meshes[meshID];
mesh.blendshapes.resize(max(mesh.blendshapes.size(), extracted.index + 1));
mesh.blendshapes[extracted.index] = extracted.blendshape;
mesh.blendshapes.resize(max(mesh.blendshapes.size(), index.first + 1));
mesh.blendshapes[index.first] = extracted.blendshape;
}
// as a temporary hack, put the mesh with the most blendshapes on top; assume it to be the face
// get offset transform from mapping
float offsetScale = mapping.value("scale", 1.0f).toFloat();
glm::mat4 offset = glm::translate(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
mapping.value("tz").toFloat()) * glm::mat4_cast(glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())))) *
glm::scale(offsetScale, offsetScale, offsetScale);
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
// look for textures, material properties
foreach (qint64 childID, childMap.values(modelID)) {
if (!materials.contains(childID)) {
continue;
}
Material material = materials.value(childID);
mesh.diffuseColor = material.diffuse;
mesh.specularColor = material.specular;
mesh.shininess = material.shininess;
qint64 diffuseTextureID = diffuseTextures.value(childID);
if (diffuseTextureID != 0) {
mesh.diffuseFilename = textureFilenames.value(diffuseTextureID);
@ -601,40 +728,72 @@ FBXGeometry extractFBXGeometry(const FBXNode& node) {
// see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion
// of skinning information in FBX
glm::mat4 jointTransform = getGlobalTransform(parentMap, localTransforms, jointID);
glm::mat4 jointTransform = offset * getGlobalTransform(parentMap, localTransforms, jointID);
mesh.transform = jointTransform * glm::inverse(transformLinkMatrices.value(clusterID)) * modelTransform;
// extract translation component for pivot
glm::mat4 jointTransformScaled = getGlobalTransform(parentMap, localTransforms, jointID, true);
glm::mat4 jointTransformScaled = offset * getGlobalTransform(parentMap, localTransforms, jointID, true);
mesh.pivot = glm::vec3(jointTransformScaled[3][0], jointTransformScaled[3][1], jointTransformScaled[3][2]);
}
}
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
glm::mat4 neckTransform = getGlobalTransform(parentMap, localTransforms, jointNeckID, true);
glm::mat4 neckTransform = offset * getGlobalTransform(parentMap, localTransforms, jointNeckID, true);
geometry.neckPivot = glm::vec3(neckTransform[3][0], neckTransform[3][1], neckTransform[3][2]);
return geometry;
}
void printNode(const FBXNode& node, int indent) {
QByteArray spaces(indent, ' ');
qDebug("%s%s: ", spaces.data(), node.name.data());
foreach (const QVariant& property, node.properties) {
qDebug() << property;
}
qDebug() << "\n";
foreach (const FBXNode& child, node.children) {
printNode(child, indent + 1);
}
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) {
QBuffer modelBuffer(const_cast<QByteArray*>(&model));
modelBuffer.open(QIODevice::ReadOnly);
QBuffer mappingBuffer(const_cast<QByteArray*>(&mapping));
mappingBuffer.open(QIODevice::ReadOnly);
return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer));
}

View file

@ -9,13 +9,12 @@
#ifndef __interface__FBXReader__
#define __interface__FBXReader__
#include <QVarLengthArray>
#include <QVariant>
#include <QVector>
#include <glm/glm.hpp>
class QIODevice;
class FBXNode;
typedef QList<FBXNode> FBXNodeList;
@ -53,10 +52,18 @@ public:
bool isEye;
glm::vec3 diffuseColor;
glm::vec3 specularColor;
float shininess;
QByteArray diffuseFilename;
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.
@ -68,17 +75,8 @@ public:
glm::vec3 neckPivot;
};
/// Parses the input from the supplied data as an FBX file.
/// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing
FBXNode parseFBX(const QByteArray& data);
/// Parses the input from the supplied device as an FBX file.
/// \exception QString if an error occurs in parsing
FBXNode parseFBX(QIODevice* device);
/// Extracts the geometry from a parsed FBX node.
FBXGeometry extractFBXGeometry(const FBXNode& node);
void printNode(const FBXNode& node, int indent = 0);
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping);
#endif /* defined(__interface__FBXReader__) */

View file

@ -250,21 +250,37 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url) {
return geometry;
}
NetworkGeometry::NetworkGeometry(const QUrl& url) : _reply(NULL) {
NetworkGeometry::NetworkGeometry(const QUrl& url) :
_modelReply(NULL),
_mappingReply(NULL)
{
if (!url.isValid()) {
return;
}
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
_reply = Application::getInstance()->getNetworkAccessManager()->get(request);
QNetworkRequest modelRequest(url);
modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
_modelReply = Application::getInstance()->getNetworkAccessManager()->get(modelRequest);
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError()));
QUrl mappingURL = url;
QString path = url.path();
mappingURL.setPath(path.left(path.lastIndexOf('.')) + ".fst");
QNetworkRequest mappingRequest(mappingURL);
mappingRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
_mappingReply = Application::getInstance()->getNetworkAccessManager()->get(mappingRequest);
connect(_mappingReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
connect(_mappingReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleMappingReplyError()));
}
NetworkGeometry::~NetworkGeometry() {
if (_reply != NULL) {
delete _reply;
if (_modelReply != NULL) {
delete _modelReply;
}
if (_mappingReply != NULL) {
delete _mappingReply;
}
foreach (const NetworkMesh& mesh, _meshes) {
glDeleteBuffers(1, &mesh.indexBufferID);
@ -272,19 +288,60 @@ NetworkGeometry::~NetworkGeometry() {
}
}
void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
if (bytesReceived < bytesTotal && !_reply->isFinished()) {
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";
_modelReply->disconnect(this);
_modelReply->deleteLater();
_modelReply = NULL;
}
void NetworkGeometry::handleMappingReplyError() {
_mappingReply->disconnect(this);
_mappingReply->deleteLater();
_mappingReply = NULL;
maybeReadModelWithMapping();
}
void NetworkGeometry::maybeReadModelWithMapping() {
if (_modelReply == NULL || !_modelReply->isFinished() || (_mappingReply != NULL && !_mappingReply->isFinished())) {
return;
}
QUrl url = _reply->url();
QByteArray entirety = _reply->readAll();
_reply->disconnect(this);
_reply->deleteLater();
_reply = NULL;
QUrl url = _modelReply->url();
QByteArray model = _modelReply->readAll();
_modelReply->disconnect(this);
_modelReply->deleteLater();
_modelReply = NULL;
QByteArray mapping;
if (_mappingReply != NULL) {
mapping = _mappingReply->readAll();
_mappingReply->disconnect(this);
_mappingReply->deleteLater();
_mappingReply = NULL;
}
try {
_geometry = extractFBXGeometry(parseFBX(entirety));
_geometry = readFBX(model, mapping);
} catch (const QString& error) {
qDebug() << "Error reading " << url << ": " << error << "\n";
@ -306,7 +363,7 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT
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());
@ -323,10 +380,7 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT
glBindBuffer(GL_ARRAY_BUFFER, 0);
QString basePath = url.path();
int idx = basePath.lastIndexOf('/');
if (idx != -1) {
basePath = basePath.left(idx);
}
basePath = basePath.left(basePath.lastIndexOf('/') + 1);
if (!mesh.diffuseFilename.isEmpty()) {
url.setPath(basePath + mesh.diffuseFilename);
networkMesh.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, mesh.isEye);
@ -338,11 +392,3 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT
_meshes.append(networkMesh);
}
}
void NetworkGeometry::handleReplyError() {
qDebug() << _reply->errorString() << "\n";
_reply->disconnect(this);
_reply->deleteLater();
_reply = NULL;
}

View file

@ -62,14 +62,19 @@ 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 handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void handleReplyError();
void handleModelReplyError();
void handleMappingReplyError();
void maybeReadModelWithMapping();
private:
QNetworkReply* _reply;
QNetworkReply* _modelReply;
QNetworkReply* _mappingReply;
FBXGeometry _geometry;
QVector<NetworkMesh> _meshes;

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

@ -82,17 +82,11 @@ bool PacketSender::process() {
}
}
bool keepGoing = _packets.size() > 0;
int packetsLeft = _packets.size();
bool keepGoing = packetsLeft > 0;
while (keepGoing) {
// in threaded mode, we go till we're empty
if (isThreaded()) {
keepGoing = _packets.size() > 0;
} else {
// in non-threaded mode, we send as many packets as we need per expected call to process()
keepGoing = (packetsThisCall < packetsPerCall) && (_packets.size() > 0);
}
uint64_t SEND_INTERVAL_USECS = (_packetsPerSecond == 0) ? USECS_PER_SECOND : (USECS_PER_SECOND / _packetsPerSecond);
NetworkPacket& packet = _packets.front();
// send the packet through the NodeList...
@ -100,7 +94,7 @@ bool PacketSender::process() {
nodeSocket->send(&packet.getAddress(), packet.getData(), packet.getLength());
packetsThisCall++;
if (_notify) {
_notify->packetSentNotification(packet.getLength());
}
@ -109,16 +103,32 @@ bool PacketSender::process() {
_packets.erase(_packets.begin());
unlock();
// dynamically sleep until we need to fire off the next set of voxels we only sleep in threaded mode
packetsLeft = _packets.size();
// in threaded mode, we go till we're empty
if (isThreaded()) {
uint64_t elapsed = now - _lastSendTime;
int usecToSleep = std::max(SEND_INTERVAL_USECS, SEND_INTERVAL_USECS - elapsed);
// we only sleep in non-threaded mode
if (usecToSleep > 0) {
usleep(usecToSleep);
keepGoing = packetsLeft > 0;
// dynamically sleep until we need to fire off the next set of voxels we only sleep in threaded mode
if (keepGoing) {
now = usecTimestampNow();
uint64_t elapsed = now - _lastSendTime;
int usecToSleep = SEND_INTERVAL_USECS - elapsed;
// we only sleep in non-threaded mode
if (usecToSleep > 0) {
if (usecToSleep > SEND_INTERVAL_USECS) {
usecToSleep = SEND_INTERVAL_USECS;
}
usleep(usecToSleep);
}
}
} else {
// in non-threaded mode, we send as many packets as we need per expected call to process()
keepGoing = (packetsThisCall < packetsPerCall) && (packetsLeft > 0);
}
_lastSendTime = now;
}

View file

@ -36,7 +36,7 @@ public:
/// \thread any thread, typically the application thread
void queuePacketForSending(sockaddr& address, unsigned char* packetData, ssize_t packetLength);
void setPacketsPerSecond(int packetsPerSecond) { _packetsPerSecond = std::min(MINIMUM_PACKETS_PER_SECOND, packetsPerSecond); }
void setPacketsPerSecond(int packetsPerSecond) { _packetsPerSecond = std::max(MINIMUM_PACKETS_PER_SECOND, packetsPerSecond); }
int getPacketsPerSecond() const { return _packetsPerSecond; }
void setPacketSenderNotify(PacketSenderNotify* notify) { _notify = notify; }

View file

@ -103,6 +103,7 @@ int PerfStat::DumpStats(char** array) {
return lineCount;
}
bool PerformanceWarning::_suppressShortTimings = false;
// Destructor handles recording all of our stats
PerformanceWarning::~PerformanceWarning() {
@ -111,9 +112,17 @@ PerformanceWarning::~PerformanceWarning() {
if ((_alwaysDisplay || _renderWarningsOn) && elapsedmsec > 1) {
if (elapsedmsec > 1000) {
double elapsedsec = (end - _start) / 1000000.0;
qDebug("%s%s took %lf seconds\n", (_alwaysDisplay ? "" : "WARNING!"), _message, elapsedsec);
qDebug("%s took %lf seconds %s\n", _message, elapsedsec, (_alwaysDisplay ? "" : "WARNING!") );
} else {
qDebug("%s%s took %lf milliseconds\n", (_alwaysDisplay ? "" : "WARNING!"), _message, elapsedmsec);
if (_suppressShortTimings) {
if (elapsedmsec > 10) {
qDebug("%s took %lf milliseconds %s\n", _message, elapsedmsec,
(_alwaysDisplay || (elapsedmsec < 10) ? "" : "WARNING!"));
}
} else {
qDebug("%s took %lf milliseconds %s\n", _message, elapsedmsec,
(_alwaysDisplay || (elapsedmsec < 10) ? "" : "WARNING!"));
}
}
} else if (_alwaysDisplay) {
qDebug("%s took %lf milliseconds\n", _message, elapsedmsec);

View file

@ -89,6 +89,7 @@ private:
const char* _message;
bool _renderWarningsOn;
bool _alwaysDisplay;
static bool _suppressShortTimings;
public:
PerformanceWarning(bool renderWarnings, const char* message, bool alwaysDisplay = false) :
_start(usecTimestampNow()),
@ -97,6 +98,8 @@ public:
_alwaysDisplay(alwaysDisplay) { }
~PerformanceWarning();
static void setSuppressShortTimings(bool suppressShortTimings) { _suppressShortTimings = suppressShortTimings; }
};

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

@ -74,7 +74,10 @@ bool VoxelNodeData::updateCurrentViewFrustum() {
newestViewFrustum.setOrientation(getCameraOrientation());
// Also make sure it's got the correct lens details from the camera
newestViewFrustum.setFieldOfView(getCameraFov());
float originalFOV = getCameraFov();
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
newestViewFrustum.setFieldOfView(wideFOV); // hack
newestViewFrustum.setAspectRatio(getCameraAspectRatio());
newestViewFrustum.setNearClip(getCameraNearClip());
newestViewFrustum.setFarClip(getCameraFarClip());

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

@ -52,4 +52,6 @@ const float VIEW_CULLING_RATE_IN_MILLISECONDS = 1000.0f; // once a second is fin
const uint64_t CLIENT_TO_SERVER_VOXEL_SEND_INTERVAL_USECS = 1000 * 5; // 1 packet every 50 milliseconds
const float VIEW_FRUSTUM_FOV_OVERSEND = 60.0f;
#endif

View file

@ -60,11 +60,9 @@ void VoxelEditPacketSender::sendVoxelEditMessage(PACKET_TYPE type, VoxelDetail&
// This encodes the voxel edit message into a buffer...
if (createVoxelEditMessage(type, 0, 1, &detail, bufferOut, sizeOut)){
// If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have
// jurisdictions for processing
if (!voxelServersExist()) {
// If we're asked to save messages while waiting for voxel servers to arrive, then do so...
if (_maxPendingMessages > 0) {
EditPacketBuffer* packet = new EditPacketBuffer(type, bufferOut, sizeOut);
@ -79,7 +77,7 @@ void VoxelEditPacketSender::sendVoxelEditMessage(PACKET_TYPE type, VoxelDetail&
}
return; // bail early
} else {
queuePacketToNode(UNKNOWN_NODE_ID, bufferOut, sizeOut); // sends to all servers... not ideal!
queuePacketToNodes(bufferOut, sizeOut);
}
// either way, clean up the created buffer
@ -100,14 +98,14 @@ bool VoxelEditPacketSender::voxelServersExist() const {
// This method is called when the edit packet layer has determined that it has a fully formed packet destined for
// a known nodeID. However, we also want to handle the case where the
void VoxelEditPacketSender::queuePacketToNode(uint16_t nodeID, unsigned char* bufferOut, ssize_t sizeOut) {
void VoxelEditPacketSender::queuePacketToNode(uint16_t nodeID, unsigned char* buffer, ssize_t length) {
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
// only send to the NodeTypes that are NODE_TYPE_VOXEL_SERVER
if (node->getActiveSocket() != NULL && node->getType() == NODE_TYPE_VOXEL_SERVER &&
((node->getNodeID() == nodeID) || (nodeID == (uint16_t)UNKNOWN_NODE_ID)) ) {
sockaddr* nodeAddress = node->getActiveSocket();
queuePacketForSending(*nodeAddress, bufferOut, sizeOut);
queuePacketForSending(*nodeAddress, buffer, length);
}
}
}
@ -133,7 +131,7 @@ void VoxelEditPacketSender::processPreServerExistsPackets() {
// First send out all the single message packets...
while (!_preServerSingleMessagePackets.empty()) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
queuePacketToNode(UNKNOWN_NODE_ID, &packet->_currentBuffer[0], packet->_currentSize);
queuePacketToNodes(&packet->_currentBuffer[0], packet->_currentSize);
delete packet;
_preServerSingleMessagePackets.erase(_preServerSingleMessagePackets.begin());
}
@ -154,12 +152,15 @@ void VoxelEditPacketSender::processPreServerExistsPackets() {
}
}
void VoxelEditPacketSender::queueVoxelEditMessageToNodes(unsigned char* codeColorBuffer, ssize_t length) {
void VoxelEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t length) {
if (!_shouldSend) {
return; // bail early
}
assert(voxelServersExist()); // we must have jurisdictions to be here!!
int headerBytes = numBytesForPacketHeader(buffer) + sizeof(short);
unsigned char* octCode = buffer + headerBytes; // skip the packet header to get to the octcode
// We want to filter out edit messages for voxel servers based on the server's Jurisdiction
// But we can't really do that with a packed message, since each edit message could be destined
@ -171,20 +172,19 @@ void VoxelEditPacketSender::queueVoxelEditMessageToNodes(unsigned char* codeColo
if (node->getActiveSocket() != NULL && node->getType() == NODE_TYPE_VOXEL_SERVER) {
uint16_t nodeID = node->getNodeID();
bool isMyJurisdiction = true;
// we need to get the jurisdiction for this
// here we need to get the "pending packet" for this server
const JurisdictionMap& map = (*_voxelServerJurisdictions)[nodeID];
isMyJurisdiction = (map.isMyJurisdiction(codeColorBuffer, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
if (isMyJurisdiction) {
queuePacketToNode(nodeID, codeColorBuffer, length);
queuePacketToNode(nodeID, buffer, length);
}
}
}
}
// NOTE: codeColorBuffer - is JUST the octcode/color and does not contain the packet header!
void VoxelEditPacketSender::queueVoxelEditMessage(PACKET_TYPE type, unsigned char* codeColorBuffer, ssize_t length) {
if (!_shouldSend) {
return; // bail early

View file

@ -86,8 +86,8 @@ public:
private:
bool _shouldSend;
void queuePacketToNode(uint16_t nodeID, unsigned char* bufferOut, ssize_t sizeOut);
void queueVoxelEditMessageToNodes(unsigned char* codeColorBuffer, ssize_t length);
void queuePacketToNode(uint16_t nodeID, unsigned char* buffer, ssize_t length);
void queuePacketToNodes(unsigned char* buffer, ssize_t length);
void initializePacket(EditPacketBuffer& packetBuffer, PACKET_TYPE type);
void releaseQueuedPacket(EditPacketBuffer& packetBuffer); // releases specific queued packet
bool voxelServersExist() const;

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