handle conflicts on merge with upstream master

This commit is contained in:
Stephen Birarda 2013-10-11 14:24:05 -07:00
commit 515e2d5a23
59 changed files with 1913 additions and 663 deletions

View file

@ -15,7 +15,6 @@ set(CMAKE_AUTOMOC ON)
add_subdirectory(animation-server)
add_subdirectory(assignment-client)
add_subdirectory(domain-server)
add_subdirectory(eve)
add_subdirectory(interface)
add_subdirectory(injector)
add_subdirectory(pairing-server)

View file

@ -628,7 +628,7 @@ void* animateVoxels(void* args) {
int packetsEnding = ::voxelEditPacketSender->packetsToSendCount();
if (firstTime) {
int packetsPerSecond = (packetsEnding - packetsStarting) * (ACTUAL_FPS);
int packetsPerSecond = std::max(ACTUAL_FPS, (packetsEnding - packetsStarting) * (ACTUAL_FPS));
std::cout << "Setting PPS to " << packetsPerSecond << "\n";

View file

@ -10,6 +10,7 @@
#include <AvatarData.h>
#include <NodeList.h>
#include <UUID.h>
#include <VoxelConstants.h>
#include "Agent.h"
@ -80,7 +81,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

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

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="45px" height="45px" viewBox="0 0 45 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<title>Mic</title>
<description>Created with Sketch (http://www.bohemiancoding.com/sketch)</description>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="mic-tool" sketch:type="MSLayerGroup">
<g id="Page-1" sketch:type="MSShapeGroup">
<g id="Group">
<rect d="M0,0 L0,45 L45,45 L45,0 L0,0 Z M0,0" id="Rectangle-1" fill="#000000" x="0" y="0" width="45" height="45"></rect>
<g id="mic" transform="translate(3.000000, 2.000000)" fill="#CACACA">
<path d="M23.1863426,39.2982456 L27.9259259,39.2982456" id="Shape"></path>
<path d="M11.0740741,39.2982456 C12.6269271,36.6118421 15.3070486,34.8441118 18.2962963,34.4674781 L18.2962963,30.5769518 C12.8949016,29.9582346 8.66666667,25.3241886 8.66666667,19.6491228 L8.66666667,17.1929824 C8.66666667,16.5141667 9.20502316,15.9649123 9.87037034,15.9649123 C10.5357176,15.9649123 11.0740741,16.5141667 11.0740741,17.1929824 L11.0740741,19.6491228 C11.0740741,24.3887061 14.854456,28.2456141 19.5,28.2456141 C24.145544,28.2456141 27.9259259,24.3887061 27.9259259,19.6491228 L27.9259259,17.1929824 C27.9259259,16.5141667 28.4642824,15.9649123 29.1296297,15.9649123 C29.7949768,15.9649123 30.3333333,16.5141667 30.3333333,17.1929824 L30.3333333,19.6491228 C30.3333333,25.3241886 26.1062268,29.9582346 20.7037037,30.5770285 L20.7037037,34.4675548 C23.6941551,34.8441118 26.3742766,36.6118421 27.9259259,39.2982456 L23.1863426,39.2982456 C22.5586111,37.8207237 21.1386169,36.8589145 19.561088,36.8421053 C17.9858912,36.8252961 16.5447569,37.8351535 15.8888136,39.2982456 L11.0740741,39.2982456 L11.0740741,39.2982456 L11.0740741,39.2982456 L11.0740741,39.2982456 Z M11.0740741,39.2982456" id="Shape"></path>
<path d="M19.5,0 C16.1756713,0 13.4814815,2.74880483 13.4814815,6.14035088 L13.4814815,12.2807018 L13.4814815,19.6491228 C13.4814815,23.0407456 16.1757465,25.7894737 19.5,25.7894737 C22.8242535,25.7894737 25.5185185,23.0406689 25.5185185,19.6491228 L25.5185185,12.2807018 L25.5185185,6.14035088 C25.5185185,2.74880483 22.8243287,0 19.5,0 L19.5,0 L19.5,0 L19.5,0 L19.5,0 Z M15.8888889,12.4342105 L15.8888889,6.29385962 C15.8888889,4.26224781 17.5086979,2.60964912 19.5,2.60964912 C21.4913021,2.60964912 23.1111111,4.26224781 23.1111111,6.29385962 L23.1111111,12.4342105 L15.8888889,12.4342105 L15.8888889,12.4342105 L15.8888889,12.4342105 L15.8888889,12.4342105 Z M15.8888889,12.4342105" id="Shape"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="45px" height="45px" viewBox="0 0 45 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<title>mute</title>
<description>Created with Sketch (http://www.bohemiancoding.com/sketch)</description>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="mic-tool" sketch:type="MSLayerGroup" stroke-width="4" stroke="#CB0011">
<g id="Page-1" sketch:type="MSShapeGroup">
<g id="Group">
<g id="mic" transform="translate(3.000000, 2.000000)">
<g id="muted" transform="translate(0.000000, 1.250000)">
<ellipse d="M19.5,36.25 C30.2695532,36.25 39,28.1351616 39,18.125 C39,8.11483841 30.2695532,0 19.5,0 C8.73044685,0 0,8.11483841 0,18.125 C0,28.1351616 8.73044685,36.25 19.5,36.25 Z M19.5,36.25" id="Oval-1" cx="19.5" cy="18.125" rx="19.5" ry="18.125"></ellipse>
<path d="M6.65836705,31.3171677 L32.4462334,4.98158885" id="Line"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

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,17 +205,13 @@ 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);
cache->setCacheDirectory("interfaceCache");
_networkAccessManager->setCache(cache);
QRect available = desktop()->availableGeometry();
_window->resize(available.size());
restoreSizeAndPosition();
_window->setVisible(true);
_glWidget->setFocusPolicy(Qt::StrongFocus);
_glWidget->setFocus();
@ -230,6 +226,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
}
Application::~Application() {
storeSizeAndPosition();
NodeList::getInstance()->removeHook(&_voxels);
NodeList::getInstance()->removeHook(this);
NodeList::getInstance()->removeDomainListener(this);
@ -242,11 +239,41 @@ Application::~Application() {
delete _oculusProgram;
delete _settings;
delete _networkAccessManager;
delete _followMode;
delete _glWidget;
}
void Application::restoreSizeAndPosition() {
QSettings* settings = new QSettings(this);
QRect available = desktop()->availableGeometry();
settings->beginGroup("Window");
float x = loadSetting(settings, "x", 0);
float y = loadSetting(settings, "y", 0);
_window->move(x, y);
int width = loadSetting(settings, "width", available.width());
int height = loadSetting(settings, "height", available.height());
_window->resize(width, height);
settings->endGroup();
}
void Application::storeSizeAndPosition() {
QSettings* settings = new QSettings(this);
settings->beginGroup("Window");
settings->setValue("width", _window->rect().width());
settings->setValue("height", _window->rect().height());
settings->setValue("x", _window->pos().x());
settings->setValue("y", _window->pos().y());
settings->endGroup();
}
void Application::initializeGL() {
qDebug( "Created Display Window.\n" );
@ -326,13 +353,20 @@ void Application::initializeGL() {
void Application::paintGL() {
PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings));
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::paintGL()");
PerfStat("display");
glEnable(GL_LINE_SMOOTH);
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_myCamera.setTightness (100.0f);
_myCamera.setTargetPosition(_myAvatar.getUprightHeadPosition());
glm::vec3 targetPosition = _myAvatar.getUprightHeadPosition();
if (_myAvatar.getHead().getBlendFace().isActive()) {
// make sure we're aligned to the blend face eyes
glm::vec3 leftEyePosition, rightEyePosition;
if (_myAvatar.getHead().getBlendFace().getEyePositions(leftEyePosition, rightEyePosition, true)) {
targetPosition = (leftEyePosition + rightEyePosition) * 0.5f;
}
}
_myCamera.setTargetPosition(targetPosition);
_myCamera.setTargetRotation(_myAvatar.getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
} else if (OculusManager::isConnected()) {
@ -344,7 +378,7 @@ void Application::paintGL() {
} else if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
_myCamera.setTightness(0.0f); // In first person, camera follows head exactly without delay
_myCamera.setTargetPosition(_myAvatar.getUprightEyeLevelPosition());
_myCamera.setTargetPosition(_myAvatar.getEyeLevelPosition());
_myCamera.setTargetRotation(_myAvatar.getHead().getCameraOrientation());
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
@ -447,6 +481,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();
@ -973,7 +1013,12 @@ void Application::mousePressEvent(QMouseEvent* event) {
_mousePressed = true;
maybeEditVoxelUnderCursor();
if (_audio.mousePressEvent(_mouseX, _mouseY)) {
// stop propagation
return;
}
if (!_palette.isActive() && (!_isHoverVoxel || _lookatTargetAvatar)) {
_pieMenu.mousePressEvent(_mouseX, _mouseY);
}
@ -1150,6 +1195,9 @@ void Application::timer() {
// ask the node list to check in with the domain server
NodeList::getInstance()->sendDomainServerCheckIn();
// give the MyAvatar object position to the Profile so it can propagate to the data-server
_profile.updatePosition(_myAvatar.getPosition());
}
static glm::vec3 getFaceVector(BoxFace face) {
@ -1252,13 +1300,14 @@ void Application::processAvatarURLsMessage(unsigned char* packetData, size_t dat
return;
}
QDataStream in(QByteArray((char*)packetData, dataBytes));
QUrl voxelURL, faceURL;
QUrl voxelURL;
in >> voxelURL;
in >> faceURL;
// invoke the set URL functions on the simulate/render thread
QMetaObject::invokeMethod(avatar->getVoxels(), "setVoxelURL", Q_ARG(QUrl, voxelURL));
QMetaObject::invokeMethod(&avatar->getHead().getBlendFace(), "setModelURL", Q_ARG(QUrl, faceURL));
// use this timing to as the data-server for an updated mesh for this avatar (if we have UUID)
DataServerClient::getValueForKeyAndUUID(DataServerKey::FaceMeshURL, avatar->getUUID());
}
void Application::processAvatarFaceVideoMessage(unsigned char* packetData, size_t dataBytes) {
@ -1585,6 +1634,11 @@ void Application::init() {
_audio.setJitterBufferSamples(Menu::getInstance()->getAudioJitterBufferSamples());
}
qDebug("Loaded settings.\n");
if (!_profile.getUsername().isEmpty()) {
// we have a username for this avatar, ask the data-server for the mesh URL for this avatar
DataServerClient::getClientValueForKey(DataServerKey::FaceMeshURL);
}
// Set up VoxelSystem after loading preferences so we can get the desired max voxel count
_voxels.setMaxVoxels(Menu::getInstance()->getMaxVoxels());
@ -1594,7 +1648,7 @@ void Application::init() {
_voxels.init();
Avatar::sendAvatarURLsMessage(_myAvatar.getVoxels()->getVoxelURL(), _myAvatar.getHead().getBlendFace().getModelURL());
Avatar::sendAvatarURLsMessage(_myAvatar.getVoxels()->getVoxelURL());
_palette.init(_glWidget->width(), _glWidget->height());
_palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelAddMode), 0, 0);
@ -1611,6 +1665,8 @@ void Application::init() {
_followMode = new QAction(this);
connect(_followMode, SIGNAL(triggered()), this, SLOT(toggleFollowMode()));
_pieMenu.addAction(_followMode);
_audio.init(_glWidget);
}
@ -1636,9 +1692,9 @@ Avatar* Application::findLookatTargetAvatar(const glm::vec3& mouseRayOrigin, con
glm::vec3 headPosition = avatar->getHead().getPosition();
float distance;
if (rayIntersectsSphere(mouseRayOrigin, mouseRayDirection, headPosition,
HEAD_SPHERE_RADIUS * avatar->getScale(), distance)) {
HEAD_SPHERE_RADIUS * avatar->getHead().getScale(), distance)) {
eyePosition = avatar->getHead().getEyePosition();
_lookatIndicatorScale = avatar->getScale();
_lookatIndicatorScale = avatar->getHead().getScale();
_lookatOtherPosition = headPosition;
nodeID = avatar->getOwningNode()->getNodeID();
return avatar;
@ -1779,8 +1835,8 @@ void Application::update(float deltaTime) {
_faceshift.getEstimatedEyePitch(), _faceshift.getEstimatedEyeYaw(), 0.0f))) * glm::vec3(0.0f, 0.0f, -1.0f);
}
updateLookatTargetAvatar(lookAtRayOrigin, lookAtRayDirection, lookAtSpot);
if (_lookatTargetAvatar) {
updateLookatTargetAvatar(mouseRayOrigin, mouseRayDirection, lookAtSpot);
if (_lookatTargetAvatar && !_faceshift.isActive()) {
// If the mouse is over another avatar's head...
_myAvatar.getHead().setLookAtPosition(lookAtSpot);
} else if (_isHoverVoxel && !_faceshift.isActive()) {
@ -1972,6 +2028,11 @@ void Application::update(float deltaTime) {
_myAvatar.simulate(deltaTime, NULL);
}
// Simulate particle cloud movements
if (Menu::getInstance()->isOptionChecked(MenuOption::ParticleCloud)) {
_cloud.simulate(deltaTime);
}
// no transmitter drive implies transmitter pick
if (!Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _myTransmitter.isConnected()) {
_transmitterPickStart = _myAvatar.getSkeleton().joint[AVATAR_JOINT_CHEST].position;
@ -2029,7 +2090,7 @@ void Application::update(float deltaTime) {
if (_faceshift.isActive()) {
const float EYE_OFFSET_SCALE = 0.025f;
glm::vec3 position = _faceshift.getHeadTranslation() * EYE_OFFSET_SCALE;
_myCamera.setEyeOffsetPosition(glm::vec3(position.x * xSign, position.y, position.z));
_myCamera.setEyeOffsetPosition(glm::vec3(position.x * xSign, position.y, -position.z));
updateProjectionMatrix();
} else if (_webcam.isActive()) {
@ -2068,8 +2129,7 @@ void Application::updateAvatar(float deltaTime) {
_yawFromTouch = 0.f;
// Update my avatar's state from gyros and/or webcam
_myAvatar.updateFromGyrosAndOrWebcam(Menu::getInstance()->isOptionChecked(MenuOption::GyroLook),
_pitchFromTouch);
_myAvatar.updateFromGyrosAndOrWebcam(_pitchFromTouch, Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead));
// Update head mouse from faceshift if active
if (_faceshift.isActive()) {
@ -2169,8 +2229,7 @@ void Application::updateAvatar(float deltaTime) {
// once in a while, send my urls
const float AVATAR_URLS_SEND_INTERVAL = 1.0f; // seconds
if (shouldDo(AVATAR_URLS_SEND_INTERVAL, deltaTime)) {
Avatar::sendAvatarURLsMessage(_myAvatar.getVoxels()->getVoxelURL(),
_myAvatar.getHead().getBlendFace().getModelURL());
Avatar::sendAvatarURLsMessage(_myAvatar.getVoxels()->getVoxelURL());
}
}
}
@ -2489,7 +2548,11 @@ void Application::displaySide(Camera& whichCamera) {
glDisable(GL_NORMALIZE);
//renderGroundPlaneGrid(EDGE_SIZE_GROUND_PLANE, _audio.getCollisionSoundMagnitude());
}
}
// Draw Cloud Particles
if (Menu::getInstance()->isOptionChecked(MenuOption::ParticleCloud)) {
_cloud.render();
}
// Draw voxels
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
@ -2499,6 +2562,7 @@ void Application::displaySide(Camera& whichCamera) {
}
}
// restore default, white specular
glMaterialfv(GL_FRONT, GL_SPECULAR, WHITE_SPECULAR_COLOR);
@ -2696,7 +2760,7 @@ void Application::displayOverlay() {
#ifndef _WIN32
_audio.render(_glWidget->width(), _glWidget->height());
if (Menu::getInstance()->isOptionChecked(MenuOption::Oscilloscope)) {
_audioScope.render(20, _glWidget->height() - 200);
_audioScope.render(45, _glWidget->height() - 200);
}
#endif
@ -3495,9 +3559,19 @@ 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);
// kill the local voxels
_voxels.killLocalVoxels();
// reset the environment so that we don't erroneously end up with multiple
_environment.resetToDefault();
}
void Application::nodeAdded(Node* node) {
}
void Application::nodeKilled(Node* node) {
@ -3608,6 +3682,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"
@ -89,6 +91,8 @@ public:
Application(int& argc, char** argv, timeval &startup_time);
~Application();
void restoreSizeAndPosition();
void storeSizeAndPosition();
void initializeGL();
void paintGL();
void resizeGL(int width, int height);
@ -133,6 +137,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 +268,8 @@ private:
Stars _stars;
Cloud _cloud;
VoxelSystem _voxels;
VoxelTree _clipboard; // if I copy/paste
VoxelImporter _voxelImporter;
@ -275,6 +284,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

View file

@ -20,6 +20,7 @@
#include <SharedUtil.h>
#include <StdDev.h>
#include <UDPSocket.h>
#include <QSvgRenderer>
#include "Application.h"
#include "Audio.h"
@ -69,6 +70,10 @@ static const int PING_FRAMES_TO_RECORD = AEC_BUFFERED_FRAMES;
static const int PING_SAMPLES_TO_ANALYZE = AEC_BUFFERED_SAMPLES_PER_CHANNEL; // Samples to analyze (reusing AEC buffer)
static const int PING_BUFFER_OFFSET = BUFFER_LENGTH_SAMPLES_PER_CHANNEL - PING_PERIOD * 2.0f; // Signal start
// Mute icon configration
static const int ICON_SIZE = 24;
static const int ICON_LEFT = 20;
static const int BOTTOM_PADDING = 110;
inline void Audio::performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* outputRight) {
@ -84,17 +89,19 @@ inline void Audio::performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* o
if (nodeList && inputLeft) {
// Measure the loudness of the signal from the microphone and store in audio object
float loudness = 0;
for (int i = 0; i < BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) {
loudness += abs(inputLeft[i]);
if (!_muted) {
// Measure the loudness of the signal from the microphone and store in audio object
float loudness = 0;
for (int i = 0; i < BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) {
loudness += abs(inputLeft[i]);
}
loudness /= BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
_lastInputLoudness = loudness;
// add input (@microphone) data to the scope
_scope->addSamples(0, inputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
}
loudness /= BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
_lastInputLoudness = loudness;
// add input (@microphone) data to the scope
_scope->addSamples(0, inputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
Node* audioMixer = nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER);
@ -323,6 +330,11 @@ int Audio::audioCallback (const void* inputBuffer,
return paContinue;
}
void Audio::init(QGLWidget *parent) {
switchToResourcesParentIfRequired();
_micTextureId = parent->bindTexture(QImage("./resources/images/mic.svg"));
_muteTextureId = parent->bindTexture(QImage("./resources/images/mute.svg"));
}
static void outputPortAudioError(PaError error) {
if (error != paNoError) {
@ -384,6 +396,7 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples) :
_collisionSoundDuration(0.0f),
_proceduralEffectSample(0),
_heartbeatMagnitude(0.0f),
_muted(false),
_listenMode(AudioRingBuffer::NORMAL),
_listenRadius(0.0f)
{
@ -515,6 +528,14 @@ void Audio::addReceivedAudioToBuffer(unsigned char* receivedData, int receivedBy
_lastReceiveTime = currentReceiveTime;
}
bool Audio::mousePressEvent(int x, int y) {
if (_iconBounds.contains(x, y)) {
_muted = !_muted;
return true;
}
return false;
}
void Audio::render(int screenWidth, int screenHeight) {
if (_stream) {
glLineWidth(2.0);
@ -609,6 +630,7 @@ void Audio::render(int screenWidth, int screenHeight) {
glEnd();
}
renderToolIcon(screenHeight);
}
//
@ -856,4 +878,49 @@ bool Audio::eventuallyAnalyzePing() {
return true;
}
void Audio::renderToolIcon(int screenHeigh) {
_iconBounds = QRect(ICON_LEFT, screenHeigh - BOTTOM_PADDING, ICON_SIZE, ICON_SIZE);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, _micTextureId);
glColor3f(1, 1, 1);
glBegin(GL_QUADS);
glTexCoord2f(1, 1);
glVertex2f(_iconBounds.left(), _iconBounds.top());
glTexCoord2f(0, 1);
glVertex2f(_iconBounds.right(), _iconBounds.top());
glTexCoord2f(0, 0);
glVertex2f(_iconBounds.right(), _iconBounds.bottom());
glTexCoord2f(1, 0);
glVertex2f(_iconBounds.left(), _iconBounds.bottom());
glEnd();
if (_muted) {
glBindTexture(GL_TEXTURE_2D, _muteTextureId);
glBegin(GL_QUADS);
glTexCoord2f(1, 1);
glVertex2f(_iconBounds.left(), _iconBounds.top());
glTexCoord2f(0, 1);
glVertex2f(_iconBounds.right(), _iconBounds.top());
glTexCoord2f(0, 0);
glVertex2f(_iconBounds.right(), _iconBounds.bottom());
glTexCoord2f(1, 0);
glVertex2f(_iconBounds.left(), _iconBounds.bottom());
glEnd();
}
glDisable(GL_TEXTURE_2D);
}
#endif

View file

@ -12,6 +12,8 @@
#include <fstream>
#include <vector>
#include "InterfaceConfig.h"
#include <QObject>
#include <portaudio.h>
@ -21,6 +23,8 @@
#include "Oscilloscope.h"
#include <QGLWidget>
static const int NUM_AUDIO_CHANNELS = 2;
static const int PACKET_LENGTH_BYTES = 1024;
@ -56,6 +60,9 @@ public:
float getCollisionSoundMagnitude() { return _collisionSoundMagnitude; }
void ping();
void init(QGLWidget *parent = 0);
bool mousePressEvent(int x, int y);
// Call periodically to eventually perform round trip time analysis,
// in which case 'true' is returned - otherwise the return value is 'false'.
@ -69,6 +76,7 @@ public:
void clearListenSources();
private:
PaStream* _stream;
AudioRingBuffer _ringBuffer;
Oscilloscope* _scope;
@ -103,6 +111,11 @@ private:
float _collisionSoundDuration;
int _proceduralEffectSample;
float _heartbeatMagnitude;
bool _muted;
GLuint _micTextureId;
GLuint _muteTextureId;
QRect _iconBounds;
AudioRingBuffer::ListenMode _listenMode;
float _listenRadius;
@ -130,6 +143,7 @@ private:
PaStreamCallbackFlags statusFlags,
void *userData);
void renderToolIcon(int screenHeight);
};

85
interface/src/Cloud.cpp Normal file
View file

@ -0,0 +1,85 @@
//
// Cloud.cpp
// interface
//
// Created by Philip Rosedale on 11/17/12.
// Copyright (c) 2012 High Fidelity, Inc. All rights reserved.
//
#include <iostream>
#include <InterfaceConfig.h>
#include "Cloud.h"
#include "Util.h"
#include "Field.h"
const int NUM_PARTICLES = 100000;
const float FIELD_COUPLE = 0.001f;
const bool RENDER_FIELD = false;
Cloud::Cloud() {
glm::vec3 box = glm::vec3(PARTICLE_WORLD_SIZE);
_bounds = box;
_count = NUM_PARTICLES;
_particles = new Particle[_count];
_field = new Field(PARTICLE_WORLD_SIZE, FIELD_COUPLE);
for (int i = 0; i < _count; i++) {
_particles[i].position = randVector() * box;
const float INIT_VEL_SCALE = 0.03f;
_particles[i].velocity = randVector() * ((float)PARTICLE_WORLD_SIZE * INIT_VEL_SCALE);
_particles[i].color = randVector();
}
}
void Cloud::render() {
if (RENDER_FIELD) {
_field->render();
}
glPointSize(3.0f);
glDisable(GL_TEXTURE_2D);
glEnable(GL_POINT_SMOOTH);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, 3 * sizeof(glm::vec3), &_particles[0].position);
glColorPointer(3, GL_FLOAT, 3 * sizeof(glm::vec3), &_particles[0].color);
glDrawArrays(GL_POINTS, 0, NUM_PARTICLES);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
void Cloud::simulate (float deltaTime) {
unsigned int i;
_field->simulate(deltaTime);
for (i = 0; i < _count; ++i) {
// Update position
_particles[i].position += _particles[i].velocity * deltaTime;
// Decay Velocity (Drag)
const float CONSTANT_DAMPING = 0.15f;
_particles[i].velocity *= (1.f - CONSTANT_DAMPING * deltaTime);
// Interact with Field
_field->interact(deltaTime, _particles[i].position, _particles[i].velocity);
// Update color to velocity
_particles[i].color = (glm::normalize(_particles[i].velocity) * 0.5f) + 0.5f;
// Bounce at bounds
if ((_particles[i].position.x > _bounds.x) || (_particles[i].position.x < 0.f)) {
_particles[i].position.x = glm::clamp(_particles[i].position.x, 0.f, _bounds.x);
_particles[i].velocity.x *= -1.f;
}
if ((_particles[i].position.y > _bounds.y) || (_particles[i].position.y < 0.f)) {
_particles[i].position.y = glm::clamp(_particles[i].position.y, 0.f, _bounds.y);
_particles[i].velocity.y *= -1.f;
}
if ((_particles[i].position.z > _bounds.z) || (_particles[i].position.z < 0.f)) {
_particles[i].position.z = glm::clamp(_particles[i].position.z, 0.f, _bounds.z);
_particles[i].velocity.z *= -1.f;
}
}
}

32
interface/src/Cloud.h Normal file
View file

@ -0,0 +1,32 @@
//
// Cloud.h
// interface
//
// Created by Philip Rosedale on 11/17/12.
// Copyright (c) 2012 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Cloud__
#define __interface__Cloud__
#include "Field.h"
#define PARTICLE_WORLD_SIZE 256.0
class Cloud {
public:
Cloud();
void simulate(float deltaTime);
void render();
private:
struct Particle {
glm::vec3 position, velocity, color;
}* _particles;
unsigned int _count;
glm::vec3 _bounds;
Field* _field;
};
#endif

View file

@ -0,0 +1,222 @@
//
// DataServerClient.cpp
// hifi
//
// Created by Stephen Birarda on 10/7/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QUrl>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <UDPSocket.h>
#include <UUID.h>
#include "Application.h"
#include "avatar/Profile.h"
#include "DataServerClient.h"
std::map<unsigned char*, int> DataServerClient::_unmatchedPackets;
const char MULTI_KEY_VALUE_SEPARATOR = '|';
const char DATA_SERVER_HOSTNAME[] = "data.highfidelity.io";
const unsigned short DATA_SERVER_PORT = 3282;
const sockaddr_in DATA_SERVER_SOCKET = socketForHostnameAndHostOrderPort(DATA_SERVER_HOSTNAME, DATA_SERVER_PORT);
void DataServerClient::putValueForKey(const QString& key, const char* value) {
QString clientString = Application::getInstance()->getProfile()->getUserString();
if (!clientString.isEmpty()) {
unsigned char* putPacket = new unsigned char[MAX_PACKET_SIZE];
// setup the header for this packet
int numPacketBytes = populateTypeAndVersion(putPacket, PACKET_TYPE_DATA_SERVER_PUT);
// pack the client UUID, null terminated
memcpy(putPacket + numPacketBytes, clientString.toLocal8Bit().constData(), clientString.toLocal8Bit().size());
numPacketBytes += clientString.toLocal8Bit().size();
putPacket[numPacketBytes++] = '\0';
// pack a 1 to designate that we are putting a single value
putPacket[numPacketBytes++] = 1;
// pack the key, null terminated
strcpy((char*) putPacket + numPacketBytes, key.toLocal8Bit().constData());
numPacketBytes += key.size();
putPacket[numPacketBytes++] = '\0';
// pack the value, null terminated
strcpy((char*) putPacket + numPacketBytes, value);
numPacketBytes += strlen(value);
putPacket[numPacketBytes++] = '\0';
// add the putPacket to our vector of unconfirmed packets, will be deleted once put is confirmed
// _unmatchedPackets.insert(std::pair<unsigned char*, int>(putPacket, numPacketBytes));
// send this put request to the data server
NodeList::getInstance()->getNodeSocket()->send((sockaddr*) &DATA_SERVER_SOCKET, putPacket, numPacketBytes);
}
}
void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uuid) {
getValuesForKeysAndUUID(QStringList(key), uuid);
}
void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid) {
if (!uuid.isNull()) {
getValuesForKeysAndUserString(keys, uuidStringWithoutCurlyBraces(uuid));
}
}
void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString) {
if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) {
unsigned char* getPacket = new unsigned char[MAX_PACKET_SIZE];
// setup the header for this packet
int numPacketBytes = populateTypeAndVersion(getPacket, PACKET_TYPE_DATA_SERVER_GET);
// pack the user string (could be username or UUID string), null-terminate
memcpy(getPacket + numPacketBytes, userString.toLocal8Bit().constData(), userString.toLocal8Bit().size());
numPacketBytes += userString.toLocal8Bit().size();
getPacket[numPacketBytes++] = '\0';
// pack one byte to designate the number of keys
getPacket[numPacketBytes++] = keys.size();
QString keyString = keys.join(MULTI_KEY_VALUE_SEPARATOR);
// pack the key string, null terminated
strcpy((char*) getPacket + numPacketBytes, keyString.toLocal8Bit().constData());
numPacketBytes += keyString.size() + sizeof('\0');
// add the getPacket to our vector of uncofirmed packets, will be deleted once we get a response from the nameserver
// _unmatchedPackets.insert(std::pair<unsigned char*, int>(getPacket, numPacketBytes));
// send the get to the data server
NodeList::getInstance()->getNodeSocket()->send((sockaddr*) &DATA_SERVER_SOCKET, getPacket, numPacketBytes);
}
}
void DataServerClient::getClientValueForKey(const QString& key) {
getValuesForKeysAndUserString(QStringList(key), Application::getInstance()->getProfile()->getUserString());
}
void DataServerClient::processConfirmFromDataServer(unsigned char* packetData, int numPacketBytes) {
removeMatchedPacketFromMap(packetData, numPacketBytes);
}
void DataServerClient::processSendFromDataServer(unsigned char* packetData, int numPacketBytes) {
// pull the user string from the packet so we know who to associate this with
int numHeaderBytes = numBytesForPacketHeader(packetData);
char* userStringPosition = (char*) packetData + numHeaderBytes;
QString userString(QByteArray(userStringPosition, strlen(userStringPosition)));
QUuid userUUID(userString);
char* keysPosition = (char*) packetData + numHeaderBytes + strlen(userStringPosition)
+ sizeof('\0') + sizeof(unsigned char);
char* valuesPosition = keysPosition + strlen(keysPosition) + sizeof('\0');
QStringList keyList = QString(keysPosition).split(MULTI_KEY_VALUE_SEPARATOR);
QStringList valueList = QString(valuesPosition).split(MULTI_KEY_VALUE_SEPARATOR);
// user string was UUID, find matching avatar and associate data
for (int i = 0; i < keyList.size(); i++) {
if (valueList[i] != " ") {
if (keyList[i] == DataServerKey::FaceMeshURL) {
if (userUUID.isNull() || userUUID == Application::getInstance()->getProfile()->getUUID()) {
qDebug("Changing user's face model URL to %s\n", valueList[0].toLocal8Bit().constData());
Application::getInstance()->getProfile()->setFaceModelURL(QUrl(valueList[0]));
} else {
// mesh URL for a UUID, find avatar in our list
NodeList* nodeList = NodeList::getInstance();
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) {
Avatar* avatar = (Avatar *) node->getLinkedData();
if (avatar->getUUID() == userUUID) {
QMetaObject::invokeMethod(&avatar->getHead().getBlendFace(),
"setModelURL",
Q_ARG(QUrl, QUrl(valueList[0])));
}
}
}
}
} else if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position
&& valueList[i] != " " && valueList[i + 1] != " ") {
QStringList coordinateItems = valueList[i + 1].split(',');
if (coordinateItems.size() == 3) {
qDebug() << "Changing domain to" << valueList[i].toLocal8Bit().constData() <<
"and position to" << valueList[i + 1].toLocal8Bit().constData() <<
"to go to" << userString << "\n";
NodeList::getInstance()->setDomainHostname(valueList[i]);
glm::vec3 newPosition(coordinateItems[0].toFloat(),
coordinateItems[1].toFloat(),
coordinateItems[2].toFloat());
Application::getInstance()->getAvatar()->setPosition(newPosition);
}
} else if (keyList[i] == DataServerKey::UUID) {
// this is the user's UUID - set it on the profile
Application::getInstance()->getProfile()->setUUID(valueList[0]);
}
}
}
// remove the matched packet from our map so it isn't re-sent to the data-server
// removeMatchedPacketFromMap(packetData, numPacketBytes);
}
void DataServerClient::processMessageFromDataServer(unsigned char* packetData, int numPacketBytes) {
switch (packetData[0]) {
case PACKET_TYPE_DATA_SERVER_SEND:
processSendFromDataServer(packetData, numPacketBytes);
break;
case PACKET_TYPE_DATA_SERVER_CONFIRM:
processConfirmFromDataServer(packetData, numPacketBytes);
break;
default:
break;
}
}
void DataServerClient::removeMatchedPacketFromMap(unsigned char* packetData, int numPacketBytes) {
for (std::map<unsigned char*, int>::iterator mapIterator = _unmatchedPackets.begin();
mapIterator != _unmatchedPackets.end();
++mapIterator) {
if (memcmp(mapIterator->first + sizeof(PACKET_TYPE),
packetData + sizeof(PACKET_TYPE),
numPacketBytes - sizeof(PACKET_TYPE)) == 0) {
// this is a match - remove the confirmed packet from the vector and delete associated member
// so it isn't sent back out
delete[] mapIterator->first;
_unmatchedPackets.erase(mapIterator);
// we've matched the packet - bail out
break;
}
}
}
void DataServerClient::resendUnmatchedPackets() {
for (std::map<unsigned char*, int>::iterator mapIterator = _unmatchedPackets.begin();
mapIterator != _unmatchedPackets.end();
++mapIterator) {
// send the unmatched packet to the data server
NodeList::getInstance()->getNodeSocket()->send((sockaddr*) &DATA_SERVER_SOCKET,
mapIterator->first,
mapIterator->second);
}
}

View file

@ -0,0 +1,45 @@
//
// DataServerClient.h
// hifi
//
// Created by Stephen Birarda on 10/7/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__DataServerClient__
#define __hifi__DataServerClient__
#include <map>
#include <QtCore/QUuid>
#include "Application.h"
class DataServerClient {
public:
static void putValueForKey(const QString& key, const char* value);
static void getClientValueForKey(const QString& key);
static void getValueForKeyAndUUID(const QString& key, const QUuid& uuid);
static void getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid);
static void getValuesForKeysAndUserString(const QStringList& keys, const QString& userString);
static void processConfirmFromDataServer(unsigned char* packetData, int numPacketBytes);
static void processSendFromDataServer(unsigned char* packetData, int numPacketBytes);
static void processMessageFromDataServer(unsigned char* packetData, int numPacketBytes);
static void removeMatchedPacketFromMap(unsigned char* packetData, int numPacketBytes);
static void resendUnmatchedPackets();
private:
static std::map<unsigned char*, int> _unmatchedPackets;
};
namespace DataServerKey {
const QString Domain = "domain";
const QString FaceMeshURL = "mesh";
const QString Position = "position";
const QString UUID = "uuid";
}
#endif /* defined(__hifi__DataServerClient__) */

100
interface/src/Field.cpp Normal file
View file

@ -0,0 +1,100 @@
//
// Field.cpp
// interface
//
// Created by Philip Rosedale on 8/23/12.
// Copyright (c) 2012 High Fidelity, Inc. All rights reserved.
//
// A vector-valued field over an array of elements arranged as a 3D lattice
#include "Field.h"
int Field::value(float *value, float *pos) {
int index = (int)(pos[0] / _worldSize * 10.0) +
(int)(pos[1] / _worldSize * 10.0) * 10 +
(int)(pos[2] / _worldSize * 10.0) * 100;
if ((index >= 0) && (index < FIELD_ELEMENTS)) {
value[0] = _field[index].val.x;
value[1] = _field[index].val.y;
value[2] = _field[index].val.z;
return 1;
} else {
return 0;
}
}
Field::Field(float worldSize, float coupling) {
_worldSize = worldSize;
_coupling = coupling;
//float fx, fy, fz;
for (int i = 0; i < FIELD_ELEMENTS; i++) {
const float FIELD_INITIAL_MAG = 0.0f;
_field[i].val = randVector() * FIELD_INITIAL_MAG * _worldSize;
_field[i].center.x = ((float)(i % 10) + 0.5f);
_field[i].center.y = ((float)(i % 100 / 10) + 0.5f);
_field[i].center.z = ((float)(i / 100) + 0.5f);
_field[i].center *= _worldSize / 10.f;
}
}
void Field::add(float* add, float *pos) {
int index = (int)(pos[0] / _worldSize * 10.0) +
(int)(pos[1] / _worldSize * 10.0) * 10 +
(int)(pos[2] / _worldSize * 10.0) * 100;
if ((index >= 0) && (index < FIELD_ELEMENTS)) {
_field[index].val.x += add[0];
_field[index].val.y += add[1];
_field[index].val.z += add[2];
}
}
void Field::interact(float deltaTime, const glm::vec3& pos, glm::vec3& vel) {
int index = (int)(pos.x / _worldSize * 10.0) +
(int)(pos.y / _worldSize*10.0) * 10 +
(int)(pos.z / _worldSize*10.0) * 100;
if ((index >= 0) && (index < FIELD_ELEMENTS)) {
vel += _field[index].val * deltaTime; // Particle influenced by field
_field[index].val += vel * deltaTime * _coupling; // Field influenced by particle
}
}
void Field::simulate(float deltaTime) {
glm::vec3 neighbors, add, diff;
for (int i = 0; i < FIELD_ELEMENTS; i++) {
const float CONSTANT_DAMPING = 0.5f;
_field[i].val *= (1.f - CONSTANT_DAMPING * deltaTime);
}
}
void Field::render() {
int i;
float scale_view = 0.05f * _worldSize;
glDisable(GL_LIGHTING);
glBegin(GL_LINES);
for (i = 0; i < FIELD_ELEMENTS; i++) {
glColor3f(0, 1, 0);
glVertex3fv(&_field[i].center.x);
glVertex3f(_field[i].center.x + _field[i].val.x * scale_view,
_field[i].center.y + _field[i].val.y * scale_view,
_field[i].center.z + _field[i].val.z * scale_view);
}
glEnd();
glColor3f(0, 1, 0);
glPointSize(4.0);
glEnable(GL_POINT_SMOOTH);
glBegin(GL_POINTS);
for (i = 0; i < FIELD_ELEMENTS; i++) {
glVertex3fv(&_field[i].center.x);
}
glEnd();
}

46
interface/src/Field.h Normal file
View file

@ -0,0 +1,46 @@
//
// Field.h
// interface
//
// Created by Philip Rosedale on 8/23/12.
// Copyright (c) 2012 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__Field__
#define __interface__Field__
#include <iostream>
#include <glm/glm.hpp>
#include "InterfaceConfig.h"
#include "world.h"
#include "Util.h"
const int FIELD_ELEMENTS = 1000;
/// Field is a lattice of vectors uniformly distributed in 3D with FIELD_ELEMENTS^(1/3) per side
class Field {
public:
struct FieldElement {
glm::vec3 val;
glm::vec3 center;
glm::vec3 fld;
} _field[FIELD_ELEMENTS];
Field(float worldSize, float coupling);
/// The field value at a position in space, given simply as the value of the enclosing cell
int value(float *ret, float *pos);
/// Visualize the field as vector lines drawn at each center
void render();
/// Add to the field value cell enclosing a location
void add(float* add, float *loc);
/// A particle with a position and velocity interacts with the field given the coupling
/// constant passed when creating the field.
void interact(float deltaTime, const glm::vec3& pos, glm::vec3& vel);
/// Field evolves over timestep
void simulate(float deltaTime);
private:
float _worldSize;
float _coupling;
};
#endif

View file

@ -20,6 +20,8 @@
InfoView::InfoView(bool forced) :
_forced(forced) {
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint);
switchToResourcesParentIfRequired();
QString absPath = QFileInfo("resources/html/interface-welcome-allsvg.html").absoluteFilePath();
QUrl url = QUrl::fromLocalFile(absPath);

View file

@ -19,8 +19,12 @@
#include <QMainWindow>
#include <QSlider>
#include <QStandardPaths>
#include <QUuid>
#include <UUID.h>
#include "Application.h"
#include "DataServerClient.h"
#include "PairingHandler.h"
#include "Menu.h"
#include "Util.h"
@ -63,6 +67,12 @@ Menu::Menu() :
SLOT(aboutApp())))->setMenuRole(QAction::AboutRole);
#endif
(addActionToQMenuAndActionHash(fileMenu,
MenuOption::Login,
0,
this,
SLOT(login())));
(addActionToQMenuAndActionHash(fileMenu,
MenuOption::Preferences,
Qt::CTRL | Qt::Key_Comma,
@ -89,6 +99,11 @@ Menu::Menu() :
Qt::CTRL | Qt::SHIFT | Qt::Key_L,
this,
SLOT(goToLocation()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoToUser,
Qt::CTRL | Qt::SHIFT | Qt::Key_U,
this,
SLOT(goToUser()));
addDisabledActionAndSeparator(fileMenu, "Settings");
@ -208,7 +223,13 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::OffAxisProjection,
0,
false);
true);
addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::TurnWithHead,
0,
true);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HeadMouse, 0, false);
addDisabledActionAndSeparator(viewMenu, "Stats");
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash);
@ -230,6 +251,9 @@ Menu::Menu() :
0,
appInstance->getGlowEffect(),
SLOT(cycleRenderMode()));
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ParticleCloud, 0, false);
QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options");
@ -250,6 +274,11 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::FastVoxelPipeline, 0,
false, appInstance->getVoxels(), SLOT(setUseFastVoxelPipeline(bool)));
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontRemoveOutOfView);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::HideOutOfView);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::ConstantCulling);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AutomaticallyAuditTree);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
@ -308,12 +337,6 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(raveGloveOptionsMenu, MenuOption::SimulateLeapHand);
addCheckableActionToQMenuAndActionHash(raveGloveOptionsMenu, MenuOption::TestRaveGlove);
QMenu* gyroOptionsMenu = developerMenu->addMenu("Gyro Options");
addCheckableActionToQMenuAndActionHash(gyroOptionsMenu, MenuOption::GyroLook, 0, true);
addCheckableActionToQMenuAndActionHash(gyroOptionsMenu, MenuOption::HeadMouse);
QMenu* trackingOptionsMenu = developerMenu->addMenu("Tracking Options");
addCheckableActionToQMenuAndActionHash(trackingOptionsMenu,
MenuOption::SkeletonTracking,
@ -502,6 +525,8 @@ void Menu::loadSettings(QSettings* settings) {
scanMenuBar(&loadAction, settings);
Application::getInstance()->getAvatar()->loadData(settings);
Application::getInstance()->getSwatch()->loadData(settings);
Application::getInstance()->getProfile()->loadData(settings);
NodeList::getInstance()->loadData(settings);
}
void Menu::saveSettings(QSettings* settings) {
@ -523,8 +548,7 @@ void Menu::saveSettings(QSettings* settings) {
scanMenuBar(&saveAction, settings);
Application::getInstance()->getAvatar()->saveData(settings);
Application::getInstance()->getSwatch()->saveData(settings);
// ask the NodeList to save its data
Application::getInstance()->getProfile()->saveData(settings);
NodeList::getInstance()->saveData(settings);
}
@ -706,20 +730,8 @@ void updateDSHostname(const QString& domainServerHostname) {
newHostname = domainServerHostname;
}
// check if the domain server hostname is new
if (NodeList::getInstance()->getDomainHostname() != newHostname) {
NodeList::getInstance()->clear();
// kill the local voxels
Application::getInstance()->getVoxels()->killLocalVoxels();
// reset the environment to default
Application::getInstance()->getEnvironment()->resetToDefault();
// set the new hostname
NodeList::getInstance()->setDomainHostname(newHostname);
}
// give our nodeList the new domain-server hostname
NodeList::getInstance()->setDomainHostname(newHostname);
}
const int QLINE_MINIMUM_WIDTH = 400;
@ -740,8 +752,43 @@ QLineEdit* lineEditForDomainHostname() {
return domainServerLineEdit;
}
void Menu::login() {
Application* applicationInstance = Application::getInstance();
QDialog dialog(applicationInstance->getGLWidget());
dialog.setWindowTitle("Login");
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
dialog.setLayout(layout);
QFormLayout* form = new QFormLayout();
layout->addLayout(form, 1);
QString username = applicationInstance->getProfile()->getUsername();
QLineEdit* usernameLineEdit = new QLineEdit(username);
usernameLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Username:", usernameLineEdit);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
layout->addWidget(buttons);
int ret = dialog.exec();
applicationInstance->getWindow()->activateWindow();
if (ret != QDialog::Accepted) {
return;
}
if (usernameLineEdit->text() != username) {
// there has been a username change
// ask for a profile reset with the new username
applicationInstance->resetProfile(usernameLineEdit->text());
}
}
void Menu::editPreferences() {
Application* applicationInstance = Application::getInstance();
QDialog dialog(applicationInstance->getGLWidget());
dialog.setWindowTitle("Interface Preferences");
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
@ -754,9 +801,10 @@ void Menu::editPreferences() {
avatarURL->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Avatar URL:", avatarURL);
QLineEdit* faceURL = new QLineEdit(applicationInstance->getAvatar()->getHead().getBlendFace().getModelURL().toString());
faceURL->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Face URL:", faceURL);
QString faceURLString = applicationInstance->getProfile()->getFaceModelURL().toString();
QLineEdit* faceURLEdit = new QLineEdit(faceURLString);
faceURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Face URL:", faceURLEdit);
QSlider* pupilDilation = new QSlider(Qt::Horizontal);
pupilDilation->setValue(applicationInstance->getAvatar()->getHead().getPupilDilation() * pupilDilation->maximum());
@ -799,13 +847,21 @@ void Menu::editPreferences() {
return;
}
QUrl faceModelURL(faceURLEdit->text());
if (faceModelURL.toString() != faceURLString) {
// change the faceModelURL in the profile, it will also update this user's BlendFace
applicationInstance->getProfile()->setFaceModelURL(faceModelURL);
// send the new face mesh URL to the data-server (if we have a client UUID)
DataServerClient::putValueForKey(DataServerKey::FaceMeshURL,
faceModelURL.toString().toLocal8Bit().constData());
}
QUrl avatarVoxelURL(avatarURL->text());
applicationInstance->getAvatar()->getVoxels()->setVoxelURL(avatarVoxelURL);
QUrl faceModelURL(faceURL->text());
applicationInstance->getAvatar()->getHead().getBlendFace().setModelURL(faceModelURL);
Avatar::sendAvatarURLsMessage(avatarVoxelURL, faceModelURL);
Avatar::sendAvatarURLsMessage(avatarVoxelURL);
applicationInstance->getAvatar()->getHead().setPupilDilation(pupilDilation->value() / (float)pupilDilation->maximum());
@ -911,6 +967,37 @@ void Menu::goToLocation() {
}
}
void Menu::goToUser() {
Application* applicationInstance = Application::getInstance();
QDialog dialog(applicationInstance->getGLWidget());
dialog.setWindowTitle("Go To User");
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
dialog.setLayout(layout);
QFormLayout* form = new QFormLayout();
layout->addLayout(form, 1);
QLineEdit* usernameLineEdit = new QLineEdit();
usernameLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("", usernameLineEdit);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
layout->addWidget(buttons);
int ret = dialog.exec();
applicationInstance->getWindow()->activateWindow();
if (ret != QDialog::Accepted) {
return;
}
if (!usernameLineEdit->text().isEmpty()) {
// there's a username entered by the user, make a request to the data-server
DataServerClient::getValuesForKeysAndUserString((QStringList() << DataServerKey::Domain << DataServerKey::Position),
usernameLineEdit->text());
}
}
void Menu::bandwidthDetails() {

View file

@ -69,9 +69,11 @@ public slots:
private slots:
void aboutApp();
void login();
void editPreferences();
void goToDomain();
void goToLocation();
void goToUser();
void bandwidthDetailsClosed();
void voxelStatsDetailsClosed();
void cycleFrustumRenderMode();
@ -120,15 +122,16 @@ private:
};
namespace MenuOption {
const QString AboutApp = "About Interface";
const QString AmbientOcclusion = "Ambient Occlusion";
const QString Avatars = "Avatars";
const QString AvatarAsBalls = "Avatar as Balls";
const QString Atmosphere = "Atmosphere";
const QString AutomaticallyAuditTree = "Automatically Audit Tree Stats";
const QString Bandwidth = "Bandwidth Display";
const QString BandwidthDetails = "Bandwidth Details";
const QString Collisions = "Collisions";
const QString ConstantCulling = "Constant Culling";
const QString CopyVoxels = "Copy";
const QString CoverageMap = "Render Coverage Map";
const QString CoverageMapV2 = "Render Coverage Map V2";
@ -141,6 +144,7 @@ namespace MenuOption {
const QString DisplayFrustum = "Display Frustum";
const QString DontRenderVoxels = "Don't call _voxels.render()";
const QString DontCallOpenGLForVoxels = "Don't call glDrawElements()/glDrawRangeElementsEXT() for Voxels";
const QString DontRemoveOutOfView = "Don't Remove Out of View Voxels";
const QString EchoAudio = "Echo Audio";
const QString ExportVoxels = "Export Voxels";
const QString HeadMouse = "Head Mouse";
@ -161,6 +165,8 @@ namespace MenuOption {
const QString GlowMode = "Cycle Glow Mode";
const QString GoToDomain = "Go To Domain...";
const QString GoToLocation = "Go To Location...";
const QString HideOutOfView = "Hide Out of View Voxels";
const QString GoToUser = "Go To User...";
const QString ImportVoxels = "Import Voxels";
const QString ImportVoxelsClipboard = "Import Voxels to Clipboard";
const QString IncreaseAvatarSize = "Increase Avatar Size";
@ -169,11 +175,12 @@ namespace MenuOption {
const QString GoHome = "Go Home";
const QString Gravity = "Use Gravity";
const QString GroundPlane = "Ground Plane";
const QString GyroLook = "Smooth Gyro Look";
const QString ParticleCloud = "Particle Cloud";
const QString ListenModeNormal = "Listen Mode Normal";
const QString ListenModePoint = "Listen Mode Point";
const QString ListenModeSingleSource = "Listen Mode Single Source";
const QString Log = "Log";
const QString Login = "Login";
const QString LookAtIndicator = "Look-at Indicator";
const QString LookAtVectors = "Look-at Vectors";
const QString LowRes = "Lower Resolution While Moving";
@ -181,6 +188,7 @@ namespace MenuOption {
const QString NudgeVoxels = "Nudge";
const QString OcclusionCulling = "Occlusion Culling";
const QString OffAxisProjection = "Off-Axis Projection";
const QString TurnWithHead = "Turn using Head";
const QString Oscilloscope = "Audio Oscilloscope";
const QString Pair = "Pair";
const QString PasteVoxels = "Paste";

View file

@ -76,6 +76,7 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
_tree->rootNode->setVoxelSystem(this);
pthread_mutex_init(&_bufferWriteLock, NULL);
pthread_mutex_init(&_treeLock, NULL);
pthread_mutex_init(&_freeIndexLock, NULL);
VoxelNode::addDeleteHook(this);
VoxelNode::addUpdateHook(this);
@ -109,12 +110,7 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
void VoxelSystem::voxelDeleted(VoxelNode* node) {
if (node->isKnownBufferIndex() && (node->getVoxelSystem() == this)) {
if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) {
freeBufferIndex(node->getBufferIndex());
} else {
forceRemoveNodeFromArraysAsPartialVBO(node);
freeBufferIndex(node->getBufferIndex());
}
forceRemoveNodeFromArrays(node);
}
}
@ -160,7 +156,9 @@ void VoxelSystem::voxelUpdated(VoxelNode* node) {
}
}
updateNodeInArraysAsPartialVBO(node);
const bool REUSE_INDEX = true;
const bool DONT_FORCE_REDRAW = false;
updateNodeInArrays(node, REUSE_INDEX, DONT_FORCE_REDRAW);
_voxelsUpdated++;
node->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things.
@ -176,8 +174,10 @@ glBufferIndex VoxelSystem::getNextBufferIndex() {
glBufferIndex output = GLBUFFER_INDEX_UNKNOWN;
// if there's a free index, use it...
if (_freeIndexes.size() > 0) {
pthread_mutex_lock(&_freeIndexLock);
output = _freeIndexes.back();
_freeIndexes.pop_back();
pthread_mutex_unlock(&_freeIndexLock);
} else {
output = _voxelsInWriteArrays;
_voxelsInWriteArrays++;
@ -185,26 +185,48 @@ glBufferIndex VoxelSystem::getNextBufferIndex() {
return output;
}
// Doesn't actually clean up the VBOs for the index, but does release responsibility of the index from the VoxelNode,
// and makes the index available for some other node to use
// Release responsibility of the buffer/vbo index from the VoxelNode, and makes the index available for some other node to use
// will also "clean up" the index data for the buffer/vbo slot, so that if it's in the middle of the draw range, the triangles
// will be "invisible"
void VoxelSystem::freeBufferIndex(glBufferIndex index) {
_freeIndexes.push_back(index);
if (_voxelsInWriteArrays == 0) {
qDebug() << "freeBufferIndex() called when _voxelsInWriteArrays == 0!!!!\n";
}
// if the "freed" index was our max index, then just drop the _voxelsInWriteArrays down one...
bool inList = false;
// make sure the index isn't already in the free list..., this is a debugging measure only done if you've enabled audits
if (Menu::getInstance()->isOptionChecked(MenuOption::AutomaticallyAuditTree)) {
for (long i = 0; i < _freeIndexes.size(); i++) {
if (_freeIndexes[i] == index) {
printf("freeBufferIndex(glBufferIndex index)... index=%ld already in free list!\n", index);
inList = true;
break;
}
}
}
if (!inList) {
// make the index available for next node that needs to be drawn
pthread_mutex_lock(&_freeIndexLock);
_freeIndexes.push_back(index);
pthread_mutex_unlock(&_freeIndexLock);
// make the VBO slot "invisible" in case this slot is not used
const glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX);
const float voxelScale = 0;
const nodeColor BLACK = {0, 0, 0, 0};
updateArraysDetails(index, startVertex, voxelScale, BLACK);
}
}
// This will run through the list of _freeIndexes and reset their VBO array values to be "invisible".
void VoxelSystem::clearFreeBufferIndexes() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "###### clearFreeBufferIndexes()");
for (int i = 0; i < _freeIndexes.size(); i++) {
glBufferIndex nodeIndex = _freeIndexes[i];
glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX);
float voxelScale = 0;
_writeVoxelDirtyArray[nodeIndex] = true;
nodeColor color = {0, 0, 0, 0};
updateNodeInArrays(nodeIndex, startVertex, voxelScale, color);
_abandonedVBOSlots++;
}
pthread_mutex_lock(&_freeIndexLock);
_freeIndexes.clear();
_abandonedVBOSlots = 0;
pthread_mutex_unlock(&_freeIndexLock);
}
VoxelSystem::~VoxelSystem() {
@ -212,6 +234,7 @@ VoxelSystem::~VoxelSystem() {
delete _tree;
pthread_mutex_destroy(&_bufferWriteLock);
pthread_mutex_destroy(&_treeLock);
pthread_mutex_destroy(&_freeIndexLock);
VoxelNode::removeDeleteHook(this);
VoxelNode::removeUpdateHook(this);
@ -591,6 +614,7 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
if (!_useFastVoxelPipeline || _writeRenderFullVBO) {
setupNewVoxelsForDrawing();
} else {
checkForCulling();
setupNewVoxelsForDrawingSingleNode(DONT_BAIL_EARLY);
}
@ -610,9 +634,6 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
uint64_t start = usecTimestampNow();
uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
// clear up the VBOs for any nodes that have been recently deleted.
clearFreeBufferIndexes();
bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging
if (!iAmDebugging && sinceLastTime <= std::max((float) _setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) {
return; // bail early, it hasn't been long enough since the last time we ran
@ -631,8 +652,9 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
_callsToTreesToArrays++;
if (_writeRenderFullVBO) {
printf("resetting _freeIndexes and _voxelsInWriteArrays\n");
_voxelsInWriteArrays = 0; // reset our VBO
_freeIndexes.clear(); // reset our free indexes
clearFreeBufferIndexes();
}
_voxelsUpdated = newTreeToArrays(_tree->rootNode);
_tree->clearDirtyBit(); // after we pull the trees into the array, we can consider the tree clean
@ -675,17 +697,12 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
uint64_t start = usecTimestampNow();
uint64_t sinceLastTime = (start - _setupNewVoxelsForDrawingLastFinished) / 1000;
// clear up the VBOs for any nodes that have been recently deleted.
clearFreeBufferIndexes();
bool iAmDebugging = false; // if you're debugging set this to true, so you won't get skipped for slow debugging
if (allowBailEarly && !iAmDebugging &&
sinceLastTime <= std::max((float) _setupNewVoxelsForDrawingLastElapsed, SIXTY_FPS_IN_MILLISECONDS)) {
return; // bail early, it hasn't been long enough since the last time we ran
}
checkForCulling(); // check for out of view and deleted voxels...
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
{
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
@ -714,16 +731,27 @@ void VoxelSystem::checkForCulling() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()");
uint64_t start = usecTimestampNow();
uint64_t sinceLastViewCulling = (start - _lastViewCulling) / 1000;
bool constantCulling = Menu::getInstance()->isOptionChecked(MenuOption::ConstantCulling);
// If the view frustum is no longer changing, but has changed, since last time, then remove nodes that are out of view
if ((sinceLastViewCulling >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS))
&& !isViewChanging()) {
if (constantCulling || (
(sinceLastViewCulling >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS))
&& !isViewChanging()
)
) {
_lastViewCulling = start;
// When we call removeOutOfView() voxels, we don't actually remove the voxels from the VBOs, but we do remove
// them from tree, this makes our tree caclulations faster, but doesn't require us to fully rebuild the VBOs (which
// can be expensive).
removeOutOfView();
if (Menu::getInstance()->isOptionChecked(MenuOption::HideOutOfView)) {
hideOutOfView();
}
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRemoveOutOfView)) {
removeOutOfView();
}
// Once we call cleanupRemovedVoxels() we do need to rebuild our VBOs (if anything was actually removed). So,
// we should consider putting this someplace else... as this might be able to occur less frequently, and save us on
@ -732,7 +760,16 @@ void VoxelSystem::checkForCulling() {
uint64_t endViewCulling = usecTimestampNow();
_lastViewCullingElapsed = (endViewCulling - start) / 1000;
}
}
uint64_t sinceLastAudit = (start - _lastAudit) / 1000;
if (Menu::getInstance()->isOptionChecked(MenuOption::AutomaticallyAuditTree)) {
if (sinceLastAudit >= std::max((float) _lastViewCullingElapsed, VIEW_CULLING_RATE_IN_MILLISECONDS)) {
_lastAudit = start;
collectStatsForTreesAndVBOs();
}
}
}
void VoxelSystem::cleanupRemovedVoxels() {
@ -747,6 +784,7 @@ void VoxelSystem::cleanupRemovedVoxels() {
}
_writeRenderFullVBO = true; // if we remove voxels, we must update our full VBOs
}
// we also might have VBO slots that have been abandoned, if too many of our VBO slots
// are abandonded we want to rerender our full VBOs
const float TOO_MANY_ABANDONED_RATIO = 0.5f;
@ -830,6 +868,7 @@ void VoxelSystem::copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart,
void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"copyWrittenDataToReadArrays()");
if (_voxelsDirty && _voxelsUpdated) {
if (fullVBOs) {
copyWrittenDataToReadArraysFullVBOs();
@ -869,133 +908,106 @@ int VoxelSystem::newTreeToArrays(VoxelNode* node) {
}
}
if (_writeRenderFullVBO) {
voxelsUpdated += updateNodeInArraysAsFullVBO(node);
const bool DONT_REUSE_INDEX = false;
const bool FORCE_REDRAW = true;
voxelsUpdated += updateNodeInArrays(node, DONT_REUSE_INDEX, FORCE_REDRAW);
} else {
voxelsUpdated += updateNodeInArraysAsPartialVBO(node);
const bool REUSE_INDEX = true;
const bool DONT_FORCE_REDRAW = false;
voxelsUpdated += updateNodeInArrays(node, REUSE_INDEX, DONT_FORCE_REDRAW);
}
node->clearDirtyBit(); // clear the dirty bit, do this before we potentially delete things.
return voxelsUpdated;
}
int VoxelSystem::updateNodeInArraysAsFullVBO(VoxelNode* node) {
// If we've run out of room, then just bail...
if (_voxelsInWriteArrays >= _maxVoxels) {
return 0;
}
if (node->getShouldRender()) {
glm::vec3 startVertex = node->getCorner();
float voxelScale = node->getScale();
glBufferIndex nodeIndex = getNextBufferIndex();
// populate the array with points for the 8 vertices
// and RGB color for each added vertex
updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor());
node->setBufferIndex(nodeIndex);
node->setVoxelSystem(this);
return 1; // rendered
} else {
node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
}
return 0; // not-rendered
}
// called as response to voxelDeleted() in fast pipeline case. The node
// is being deleted, but it's state is such that it thinks it should render
// and therefore we can't use the normal render calculations. This method
// will forcibly remove it from the VBOs because we know better!!!
int VoxelSystem::forceRemoveNodeFromArraysAsPartialVBO(VoxelNode* node) {
int VoxelSystem::forceRemoveNodeFromArrays(VoxelNode* node) {
if (!_initialized) {
return 0;
}
// if the node is not in the VBOs then we have nothing to do!
if (node->isKnownBufferIndex()) {
// if we shouldn't render then set out location to some infinitely distant location,
// and our scale as infinitely small
glm::vec3 startVertex(FLT_MAX, FLT_MAX, FLT_MAX);
float voxelScale = 0;
_abandonedVBOSlots++;
// If this node has not yet been written to the array, then add it to the end of the array.
glBufferIndex nodeIndex = node->getBufferIndex();
_writeVoxelDirtyArray[nodeIndex] = true;
// populate the array with points for the 8 vertices
// and RGB color for each added vertex
const nodeColor BLACK = { 0,0,0};
updateNodeInArrays(nodeIndex, startVertex, voxelScale, BLACK);
node->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
freeBufferIndex(nodeIndex); // NOTE: This is make the node invisible!
return 1; // updated!
}
return 0; // not-updated
}
int VoxelSystem::updateNodeInArraysAsPartialVBO(VoxelNode* node) {
int VoxelSystem::updateNodeInArrays(VoxelNode* node, bool reuseIndex, bool forceDraw) {
// If we've run out of room, then just bail...
if (_voxelsInWriteArrays >= _maxVoxels) {
return 0;
}
if (!_initialized) {
return 0;
}
// Now, if we've changed any attributes (our renderness, our color, etc) then update the Arrays...
if (node->isDirty()) {
glm::vec3 startVertex;
float voxelScale = 0;
// If we've changed any attributes (our renderness, our color, etc), or we've been told to force a redraw
// then update the Arrays...
if (forceDraw || node->isDirty()) {
// If we're should render, use our legit location and scale,
if (node->getShouldRender()) {
startVertex = node->getCorner();
voxelScale = node->getScale();
} else {
// if we shouldn't render then set out location to some infinitely distant location,
// and our scale as infinitely small
startVertex[0] = startVertex[1] = startVertex[2] = FLT_MAX;
voxelScale = 0;
_abandonedVBOSlots++;
}
glm::vec3 startVertex = node->getCorner();
float voxelScale = node->getScale();
// If this node has not yet been written to the array, then add it to the end of the array.
glBufferIndex nodeIndex;
if (node->isKnownBufferIndex()) {
nodeIndex = node->getBufferIndex();
glBufferIndex nodeIndex = GLBUFFER_INDEX_UNKNOWN;
if (reuseIndex && node->isKnownBufferIndex()) {
nodeIndex = node->getBufferIndex();
} else {
nodeIndex = getNextBufferIndex();
node->setBufferIndex(nodeIndex);
node->setVoxelSystem(this);
}
// populate the array with points for the 8 vertices and RGB color for each added vertex
updateArraysDetails(nodeIndex, startVertex, voxelScale, node->getColor());
return 1; // updated!
} else {
nodeIndex = getNextBufferIndex();
node->setBufferIndex(nodeIndex);
node->setVoxelSystem(this);
// If we shouldn't render, but we did have a known index, then we will need to release our index
if (reuseIndex && node->isKnownBufferIndex()) {
forceRemoveNodeFromArrays(node);
return 1; // updated!
}
}
_writeVoxelDirtyArray[nodeIndex] = true;
// populate the array with points for the 8 vertices
// and RGB color for each added vertex
updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor());
return 1; // updated!
}
return 0; // not-updated
}
void VoxelSystem::updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex,
void VoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex,
float voxelScale, const nodeColor& color) {
if (_initialized) {
_writeVoxelDirtyArray[nodeIndex] = true;
if (_useVoxelShader) {
if (_writeVoxelShaderData) {
VoxelShaderVBOData* writeVerticesAt = &_writeVoxelShaderData[nodeIndex];
writeVerticesAt->x = startVertex.x * TREE_SCALE;
writeVerticesAt->y = startVertex.y * TREE_SCALE;
writeVerticesAt->z = startVertex.z * TREE_SCALE;
writeVerticesAt->s = voxelScale * TREE_SCALE;
writeVerticesAt->r = color[RED_INDEX];
writeVerticesAt->g = color[GREEN_INDEX];
writeVerticesAt->b = color[BLUE_INDEX];
}
} else {
if (_writeVerticesArray && _writeColorsArray) {
int vertexPointsPerVoxel = GLOBAL_NORMALS_VERTEX_POINTS_PER_VOXEL;
for (int j = 0; j < vertexPointsPerVoxel; j++ ) {
GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * vertexPointsPerVoxel);
GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * vertexPointsPerVoxel);
*(writeVerticesAt+j) = startVertex[j % 3] + (identityVerticesGlobalNormals[j] * voxelScale);
*(writeColorsAt +j) = color[j % 3];
if (_useVoxelShader) {
if (_writeVoxelShaderData) {
VoxelShaderVBOData* writeVerticesAt = &_writeVoxelShaderData[nodeIndex];
writeVerticesAt->x = startVertex.x * TREE_SCALE;
writeVerticesAt->y = startVertex.y * TREE_SCALE;
writeVerticesAt->z = startVertex.z * TREE_SCALE;
writeVerticesAt->s = voxelScale * TREE_SCALE;
writeVerticesAt->r = color[RED_INDEX];
writeVerticesAt->g = color[GREEN_INDEX];
writeVerticesAt->b = color[BLUE_INDEX];
}
} else {
if (_writeVerticesArray && _writeColorsArray) {
int vertexPointsPerVoxel = GLOBAL_NORMALS_VERTEX_POINTS_PER_VOXEL;
for (int j = 0; j < vertexPointsPerVoxel; j++ ) {
GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * vertexPointsPerVoxel);
GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * vertexPointsPerVoxel);
*(writeVerticesAt+j) = startVertex[j % 3] + (identityVerticesGlobalNormals[j] * voxelScale);
*(writeColorsAt +j) = color[j % 3];
}
}
}
}
@ -1017,7 +1029,7 @@ void VoxelSystem::init() {
_callsToTreesToArrays = 0;
_setupNewVoxelsForDrawingLastFinished = 0;
_setupNewVoxelsForDrawingLastElapsed = 0;
_lastViewCullingElapsed = _lastViewCulling = 0;
_lastViewCullingElapsed = _lastViewCulling = _lastAudit = 0;
_voxelsDirty = false;
_voxelsInWriteArrays = 0;
@ -1508,8 +1520,10 @@ public:
unsigned long nodesInside;
unsigned long nodesIntersect;
unsigned long nodesOutside;
VoxelNode* insideRoot;
VoxelNode* outsideRoot;
removeOutOfViewArgs(VoxelSystem* voxelSystem) :
removeOutOfViewArgs(VoxelSystem* voxelSystem, bool widenViewFrustum = true) :
thisVoxelSystem(voxelSystem),
thisViewFrustum(*voxelSystem->getViewFrustum()),
dontRecurseBag(),
@ -1517,13 +1531,17 @@ public:
nodesRemoved(0),
nodesInside(0),
nodesIntersect(0),
nodesOutside(0)
nodesOutside(0),
insideRoot(NULL),
outsideRoot(NULL)
{
// Widen the FOV for trimming
float originalFOV = thisViewFrustum.getFieldOfView();
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
thisViewFrustum.setFieldOfView(wideFOV);
thisViewFrustum.calculate();
if (widenViewFrustum) {
float originalFOV = thisViewFrustum.getFieldOfView();
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
thisViewFrustum.setFieldOfView(wideFOV);
thisViewFrustum.calculate();
}
}
};
@ -1579,7 +1597,6 @@ bool VoxelSystem::removeOutOfViewOperation(VoxelNode* node, void* extraData) {
return true; // keep going!
}
bool VoxelSystem::isViewChanging() {
bool result = false; // assume the best
@ -1624,6 +1641,139 @@ void VoxelSystem::removeOutOfView() {
}
}
// combines the removeOutOfView args into a single class
class hideOutOfViewArgs {
public:
VoxelSystem* thisVoxelSystem;
VoxelTree* tree;
ViewFrustum thisViewFrustum;
unsigned long nodesScanned;
unsigned long nodesRemoved;
unsigned long nodesInside;
unsigned long nodesIntersect;
unsigned long nodesOutside;
hideOutOfViewArgs(VoxelSystem* voxelSystem, VoxelTree* tree, bool widenViewFrustum = true) :
thisVoxelSystem(voxelSystem),
tree(tree),
thisViewFrustum(*voxelSystem->getViewFrustum()),
nodesScanned(0),
nodesRemoved(0),
nodesInside(0),
nodesIntersect(0),
nodesOutside(0)
{
// Widen the FOV for trimming
if (widenViewFrustum) {
float originalFOV = thisViewFrustum.getFieldOfView();
float wideFOV = originalFOV + VIEW_FRUSTUM_FOV_OVERSEND;
thisViewFrustum.setFieldOfView(wideFOV);
thisViewFrustum.calculate();
}
}
};
void VoxelSystem::hideOutOfView() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "hideOutOfView()");
hideOutOfViewArgs args(this, this->_tree, true); // widen to match server!
_tree->recurseTreeWithOperation(hideOutOfViewOperation,(void*)&args);
if (args.nodesRemoved) {
_tree->setDirtyBit();
setupNewVoxelsForDrawingSingleNode(DONT_BAIL_EARLY);
}
bool showRemoveDebugDetails = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
if (showRemoveDebugDetails) {
qDebug("hideOutOfView() scanned=%ld removed=%ld inside=%ld intersect=%ld outside=%ld\n",
args.nodesScanned, args.nodesRemoved, args.nodesInside,
args.nodesIntersect, args.nodesOutside
);
}
}
bool VoxelSystem::hideAllSubTreeOperation(VoxelNode* node, void* extraData) {
hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData;
args->nodesOutside++;
if (node->isKnownBufferIndex()) {
args->nodesRemoved++;
VoxelSystem* thisVoxelSystem = args->thisVoxelSystem;
thisVoxelSystem->forceRemoveNodeFromArrays(node);
thisVoxelSystem->_voxelsUpdated++;
thisVoxelSystem->setupNewVoxelsForDrawingSingleNode();
}
return true;
}
bool VoxelSystem::showAllSubTreeOperation(VoxelNode* node, void* extraData) {
hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData;
args->nodesInside++;
if (node->getShouldRender() && !node->isKnownBufferIndex()) {
node->setDirtyBit(); // will this make it draw?
}
return true;
}
// "hide" voxels in the VBOs that are still in the tree that but not in view.
// We don't remove them from the tree, we don't delete them, we do remove them
// from the VBOs and mark them as such in the tree.
bool VoxelSystem::hideOutOfViewOperation(VoxelNode* node, void* extraData) {
hideOutOfViewArgs* args = (hideOutOfViewArgs*)extraData;
// If we're still recursing the tree using this operator, then we don't know if we're inside or outside...
// so before we move forward we need to determine our frustum location
ViewFrustum::location inFrustum = node->inFrustum(args->thisViewFrustum);
// ok, now do some processing for this node...
switch (inFrustum) {
case ViewFrustum::OUTSIDE: {
// if this node is fully OUTSIDE the view, then we know that ALL of it's children are also fully OUTSIDE
// so we can recurse the children and simply mark them as hidden
args->tree->recurseNodeWithOperation(node, hideAllSubTreeOperation, args );
return false;
} break;
case ViewFrustum::INSIDE: {
// if this node is fully INSIDE the view, then we know that ALL of it's children are also fully INSIDE
// so we can recurse the children and simply mark them as visible (as appropriate based on LOD)
args->tree->recurseNodeWithOperation(node, showAllSubTreeOperation, args );
return false;
} break;
case ViewFrustum::INTERSECT: {
args->nodesScanned++;
args->nodesIntersect++;
// if the child node INTERSECTs the view, then we want to check to see if it thinks it should render
// if it should render but is missing it's VBO index, then we want to flip it on, and we can stop recursing from
// here because we know will block any children anyway
if (node->getShouldRender() && !node->isKnownBufferIndex()) {
node->setDirtyBit(); // will this make it draw?
return false;
}
// If it INTERSECTS but shouldn't be displayed, then it's probably a parent and it is at least partially in view.
// So we DO want to recurse the children because some of them may not be in view... nothing specifically to do,
// just keep iterating the children
return true;
} break;
} // switch
return true; // keep going!
}
bool VoxelSystem::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
VoxelDetail& detail, float& distance, BoxFace& face) {
pthread_mutex_lock(&_treeLock);
@ -1743,6 +1893,14 @@ bool VoxelSystem::collectStatsForTreesAndVBOsOperation(VoxelNode* node, void* ex
if (node->isKnownBufferIndex()) {
args->nodesInVBO++;
unsigned long nodeIndex = node->getBufferIndex();
const bool extraDebugging = false; // enable for extra debugging
if (extraDebugging) {
qDebug("node In VBO... [%f,%f,%f] %f ... index=%ld, isDirty=%s, shouldRender=%s \n",
node->getCorner().x, node->getCorner().y, node->getCorner().z, node->getScale(),
nodeIndex, debug::valueOf(node->isDirty()), debug::valueOf(node->getShouldRender()));
}
if (args->hasIndexFound[nodeIndex]) {
args->duplicateVBOIndex++;
qDebug("duplicateVBO found... index=%ld, isDirty=%s, shouldRender=%s \n", nodeIndex,
@ -1778,6 +1936,9 @@ void VoxelSystem::collectStatsForTreesAndVBOs() {
collectStatsForTreesAndVBOsArgs args;
args.expectedMax = _voxelsInWriteArrays;
qDebug("CALCULATING Local Voxel Tree Statistics >>>>>>>>>>>>\n");
_tree->recurseTreeWithOperation(collectStatsForTreesAndVBOsOperation,&args);
qDebug("Local Voxel Tree Statistics:\n total nodes %ld \n leaves %ld \n dirty %ld \n colored %ld \n shouldRender %ld \n",
@ -1802,6 +1963,10 @@ void VoxelSystem::collectStatsForTreesAndVBOs() {
qDebug(" minInVBO=%ld \n maxInVBO=%ld \n _voxelsInWriteArrays=%ld \n _voxelsInReadArrays=%ld \n",
minInVBO, maxInVBO, _voxelsInWriteArrays, _voxelsInReadArrays);
qDebug(" _freeIndexes.size()=%ld \n",
_freeIndexes.size());
qDebug("DONE WITH Local Voxel Tree Statistics >>>>>>>>>>>>\n");
}

View file

@ -81,6 +81,7 @@ public:
void killLocalVoxels();
virtual void removeOutOfView();
virtual void hideOutOfView();
bool hasViewChanged();
bool isViewChanging();
@ -151,7 +152,7 @@ protected:
glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const;
virtual void updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex,
virtual void updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex,
float voxelScale, const nodeColor& color);
virtual void copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd);
virtual void updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd);
@ -185,10 +186,13 @@ private:
static bool killSourceVoxelsOperation(VoxelNode* node, void* extraData);
static bool forceRedrawEntireTreeOperation(VoxelNode* node, void* extraData);
static bool clearAllNodesBufferIndexOperation(VoxelNode* node, void* extraData);
static bool hideOutOfViewOperation(VoxelNode* node, void* extraData);
static bool hideOutOfViewUnrollOperation(VoxelNode* node, void* extraData);
static bool hideAllSubTreeOperation(VoxelNode* node, void* extraData);
static bool showAllSubTreeOperation(VoxelNode* node, void* extraData);
int updateNodeInArraysAsFullVBO(VoxelNode* node);
int updateNodeInArraysAsPartialVBO(VoxelNode* node);
int forceRemoveNodeFromArraysAsPartialVBO(VoxelNode* node);
int updateNodeInArrays(VoxelNode* node, bool reuseIndex, bool forceDraw);
int forceRemoveNodeFromArrays(VoxelNode* node);
void copyWrittenDataToReadArraysFullVBOs();
void copyWrittenDataToReadArraysPartialVBOs();
@ -218,6 +222,7 @@ private:
int _setupNewVoxelsForDrawingLastElapsed;
uint64_t _setupNewVoxelsForDrawingLastFinished;
uint64_t _lastViewCulling;
uint64_t _lastAudit;
int _lastViewCullingElapsed;
void initVoxelMemory();
@ -265,6 +270,7 @@ private:
int _hookID;
std::vector<glBufferIndex> _freeIndexes;
pthread_mutex_t _freeIndexLock;
void freeBufferIndex(glBufferIndex index);
void clearFreeBufferIndexes();

View file

@ -18,6 +18,7 @@
#include "Application.h"
#include "Avatar.h"
#include "DataServerClient.h"
#include "Hand.h"
#include "Head.h"
#include "Physics.h"
@ -59,7 +60,7 @@ const int NUM_BODY_CONE_SIDES = 9;
const float chatMessageScale = 0.0015;
const float chatMessageHeight = 0.20;
void Avatar::sendAvatarURLsMessage(const QUrl& voxelURL, const QUrl& faceURL) {
void Avatar::sendAvatarURLsMessage(const QUrl& voxelURL) {
uint16_t ownerID = NodeList::getInstance()->getOwnerID();
if (ownerID == UNKNOWN_NODE_ID) {
@ -76,7 +77,6 @@ void Avatar::sendAvatarURLsMessage(const QUrl& voxelURL, const QUrl& faceURL) {
QDataStream out(&message, QIODevice::WriteOnly | QIODevice::Append);
out << voxelURL;
out << faceURL;
Application::controlledBroadcastToNodes((unsigned char*)message.data(), message.size(), &NODE_TYPE_AVATAR_MIXER, 1);
}
@ -102,11 +102,16 @@ Avatar::Avatar(Node* owningNode) :
_leadingAvatar(NULL),
_voxels(this),
_moving(false),
_hoverOnDuration(0.0f),
_hoverOffDuration(0.0f),
_initialized(false),
_handHoldingPosition(0.0f, 0.0f, 0.0f),
_maxArmLength(0.0f),
_pelvisStandingHeight(0.0f)
{
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
// give the pointer to our head to inherited _headData variable from AvatarData
_headData = &_head;
_handData = &_hand;
@ -388,15 +393,27 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) {
}
// head scale grows when avatar is looked at
const float BASE_MAX_SCALE = 3.0f;
float maxScale = BASE_MAX_SCALE * glm::distance(_position, Application::getInstance()->getCamera()->getPosition());
if (Application::getInstance()->getLookatTargetAvatar() == this) {
const float BASE_MAX_SCALE = 3.0f;
const float GROW_SPEED = 0.1f;
_head.setScale(min(BASE_MAX_SCALE * glm::distance(_position, Application::getInstance()->getCamera()->getPosition()),
_head.getScale() + deltaTime * GROW_SPEED));
_hoverOnDuration += deltaTime;
_hoverOffDuration = 0.0f;
const float GROW_DELAY = 1.0f;
const float GROW_RATE = 0.25f;
if (_hoverOnDuration > GROW_DELAY) {
_head.setScale(glm::mix(_head.getScale(), maxScale, GROW_RATE));
}
} else {
const float SHRINK_SPEED = 100.0f;
_head.setScale(max(_scale, _head.getScale() - deltaTime * SHRINK_SPEED));
_hoverOnDuration = 0.0f;
_hoverOffDuration += deltaTime;
const float SHRINK_DELAY = 1.0f;
const float SHRINK_RATE = 0.25f;
if (_hoverOffDuration > SHRINK_DELAY) {
_head.setScale(glm::mix(_head.getScale(), 1.0f, SHRINK_RATE));
}
}
_head.setBodyRotation(glm::vec3(_bodyPitch, _bodyYaw, _bodyRoll));
@ -465,15 +482,16 @@ void Avatar::render(bool lookingInMirror, bool renderAvatarBalls) {
renderDiskShadow(_position, glm::vec3(0.0f, 1.0f, 0.0f), _scale * 0.1f, 0.2f);
{
// glow when moving
Glower glower(_moving ? 1.0f : 0.0f);
// glow when moving in the distance
glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
const float GLOW_DISTANCE = 5.0f;
Glower glower(_moving && glm::length(toTarget) > GLOW_DISTANCE ? 1.0f : 0.0f);
// render body
renderBody(lookingInMirror, renderAvatarBalls);
// render sphere when far away
const float MAX_ANGLE = 10.f;
glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
glm::vec3 delta = _height * (_head.getCameraOrientation() * IDENTITY_UP) / 2.f;
float angle = abs(angleBetween(toTarget + delta, toTarget - delta));
@ -688,6 +706,13 @@ void Avatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
}
} else if (renderAvatarBalls || !_voxels.getVoxelURL().isValid()) {
// Render the body as balls and cones
glm::vec3 skinColor(SKIN_COLOR[0], SKIN_COLOR[1], SKIN_COLOR[2]);
glm::vec3 darkSkinColor(DARK_SKIN_COLOR[0], DARK_SKIN_COLOR[1], DARK_SKIN_COLOR[2]);
if (_head.getBlendFace().isActive()) {
skinColor = glm::vec3(_head.getBlendFace().computeAverageColor());
const float SKIN_DARKENING = 0.9f;
darkSkinColor = skinColor * SKIN_DARKENING;
}
for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) {
float alpha = getBallRenderAlpha(b, lookingInMirror);
@ -705,9 +730,9 @@ void Avatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
}
} else if (alpha > 0.0f) {
// Render the body ball sphere
glColor3f(SKIN_COLOR[0] + _bodyBall[b].touchForce * 0.3f,
SKIN_COLOR[1] - _bodyBall[b].touchForce * 0.2f,
SKIN_COLOR[2] - _bodyBall[b].touchForce * 0.1f);
glColor3f(skinColor.r + _bodyBall[b].touchForce * 0.3f,
skinColor.g - _bodyBall[b].touchForce * 0.2f,
skinColor.b - _bodyBall[b].touchForce * 0.1f);
if (b == BODY_BALL_NECK_BASE && _head.getBlendFace().isActive()) {
continue; // don't render the neck if we have a face model
@ -732,7 +757,7 @@ void Avatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
&& (b != BODY_BALL_LEFT_SHOULDER)
&& (b != BODY_BALL_RIGHT_COLLAR)
&& (b != BODY_BALL_RIGHT_SHOULDER)) {
glColor3fv(DARK_SKIN_COLOR);
glColor3fv((const GLfloat*)&darkSkinColor);
float r2 = _bodyBall[b].radius * 0.8;
renderJointConnectingCone(_bodyBall[_bodyBall[b].parentBall].position, _bodyBall[b].position, r2, r2);
@ -751,31 +776,6 @@ void Avatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
_hand.render(lookingInMirror);
}
void Avatar::loadData(QSettings* settings) {
settings->beginGroup("Avatar");
// in case settings is corrupt or missing loadSetting() will check for NaN
_bodyYaw = loadSetting(settings, "bodyYaw", 0.0f);
_bodyPitch = loadSetting(settings, "bodyPitch", 0.0f);
_bodyRoll = loadSetting(settings, "bodyRoll", 0.0f);
_position.x = loadSetting(settings, "position_x", 0.0f);
_position.y = loadSetting(settings, "position_y", 0.0f);
_position.z = loadSetting(settings, "position_z", 0.0f);
_voxels.setVoxelURL(settings->value("voxelURL").toUrl());
_head.getBlendFace().setModelURL(settings->value("faceModelURL").toUrl());
_head.setPupilDilation(settings->value("pupilDilation", 0.0f).toFloat());
_leanScale = loadSetting(settings, "leanScale", 0.05f);
_newScale = loadSetting(settings, "scale", 1.0f);
setScale(_scale);
Application::getInstance()->getCamera()->setScale(_scale);
settings->endGroup();
}
void Avatar::getBodyBallTransform(AvatarJointID jointID, glm::vec3& position, glm::quat& rotation) const {
position = _bodyBall[jointID].position;
rotation = _bodyBall[jointID].rotation;
@ -805,27 +805,6 @@ int Avatar::parseData(unsigned char* sourceBuffer, int numBytes) {
return bytesRead;
}
void Avatar::saveData(QSettings* set) {
set->beginGroup("Avatar");
set->setValue("bodyYaw", _bodyYaw);
set->setValue("bodyPitch", _bodyPitch);
set->setValue("bodyRoll", _bodyRoll);
set->setValue("position_x", _position.x);
set->setValue("position_y", _position.y);
set->setValue("position_z", _position.z);
set->setValue("voxelURL", _voxels.getVoxelURL());
set->setValue("faceModelURL", _head.getBlendFace().getModelURL());
set->setValue("pupilDilation", _head.getPupilDilation());
set->setValue("leanScale", _leanScale);
set->setValue("scale", _newScale);
set->endGroup();
}
// render a makeshift cone section that serves as a body part connecting joint spheres
void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2) {

View file

@ -11,7 +11,7 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QSettings>
#include <QtCore/QUuid>
#include <AvatarData.h>
@ -129,7 +129,7 @@ class Avatar : public AvatarData {
Q_OBJECT
public:
static void sendAvatarURLsMessage(const QUrl& voxelURL, const QUrl& faceURL);
static void sendAvatarURLsMessage(const QUrl& voxelURL);
Avatar(Node* owningNode = NULL);
~Avatar();
@ -155,10 +155,6 @@ public:
glm::quat getOrientation() const;
glm::quat getWorldAlignedOrientation() const;
AvatarVoxelSystem* getVoxels() { return &_voxels; }
// get/set avatar data
void saveData(QSettings* set);
void loadData(QSettings* set);
// Get the position/rotation of a single body ball
void getBodyBallTransform(AvatarJointID jointID, glm::vec3& position, glm::quat& rotation) const;
@ -226,6 +222,8 @@ protected:
AvatarVoxelSystem _voxels;
bool _moving; ///< set when position is changing
float _hoverOnDuration;
float _hoverOffDuration;
// protected methods...
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
@ -247,6 +245,7 @@ private:
float _maxArmLength;
float _pelvisStandingHeight;
// private methods...
glm::vec3 calculateAverageEyePosition() { return _head.calculateAverageEyePosition(); } // get the position smack-dab between the eyes (for lookat)
float getBallRenderAlpha(int ball, bool lookingInMirror) const;

View file

@ -151,9 +151,9 @@ void AvatarVoxelSystem::setVoxelURL(const QUrl& url) {
connect(_voxelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleVoxelReplyError()));
}
void AvatarVoxelSystem::updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex,
void AvatarVoxelSystem::updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex,
float voxelScale, const nodeColor& color) {
VoxelSystem::updateNodeInArrays(nodeIndex, startVertex, voxelScale, color);
VoxelSystem::updateArraysDetails(nodeIndex, startVertex, voxelScale, color);
GLubyte* writeBoneIndicesAt = _writeBoneIndicesArray + (nodeIndex * BONE_ELEMENTS_PER_VOXEL);
GLfloat* writeBoneWeightsAt = _writeBoneWeightsArray + (nodeIndex * BONE_ELEMENTS_PER_VOXEL);

View file

@ -42,7 +42,7 @@ public slots:
protected:
virtual void updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex,
virtual void updateArraysDetails(glBufferIndex nodeIndex, const glm::vec3& startVertex,
float voxelScale, const nodeColor& color);
virtual void copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd);
virtual void updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd);

View file

@ -8,6 +8,8 @@
#include <QNetworkReply>
#include <glm/gtx/transform.hpp>
#include "Application.h"
#include "BlendFace.h"
#include "Head.h"
@ -41,21 +43,117 @@ void BlendFace::init() {
}
}
void BlendFace::reset() {
_resetStates = true;
}
const glm::vec3 MODEL_TRANSLATION(0.0f, -120.0f, 40.0f); // temporary fudge factor
const float MODEL_SCALE = 0.0006f;
bool BlendFace::render(float alpha) {
void BlendFace::simulate(float deltaTime) {
if (!isActive()) {
return;
}
// set up world vertices on first simulate after load
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (_meshStates.isEmpty()) {
QVector<glm::vec3> vertices;
foreach (const FBXMesh& mesh, geometry.meshes) {
MeshState state;
if (mesh.springiness > 0.0f) {
state.worldSpaceVertices.resize(mesh.vertices.size());
state.vertexVelocities.resize(mesh.vertices.size());
state.worldSpaceNormals.resize(mesh.vertices.size());
}
_meshStates.append(state);
}
_resetStates = true;
}
glm::quat orientation = _owningHead->getOrientation();
glm::vec3 scale = glm::vec3(-1.0f, 1.0f, -1.0f) * _owningHead->getScale() * MODEL_SCALE;
glm::vec3 offset = MODEL_TRANSLATION - _geometry->getFBXGeometry().neckPivot;
glm::mat4 baseTransform = glm::translate(_owningHead->getPosition()) * glm::mat4_cast(orientation) *
glm::scale(scale) * glm::translate(offset);
for (int i = 0; i < _meshStates.size(); i++) {
MeshState& state = _meshStates[i];
int vertexCount = state.worldSpaceVertices.size();
if (vertexCount == 0) {
continue;
}
glm::vec3* destVertices = state.worldSpaceVertices.data();
glm::vec3* destVelocities = state.vertexVelocities.data();
glm::vec3* destNormals = state.worldSpaceNormals.data();
const FBXMesh& mesh = geometry.meshes.at(i);
const glm::vec3* sourceVertices = mesh.vertices.constData();
if (!mesh.blendshapes.isEmpty()) {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
// blend in each coefficient
const vector<float>& coefficients = _owningHead->getBlendshapeCoefficients();
for (int j = 0; j < coefficients.size(); j++) {
float coefficient = coefficients[j];
if (coefficient == 0.0f || j >= mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
continue;
}
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
for (const int* index = mesh.blendshapes[j].indices.constData(),
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++) {
_blendedVertices[*index] += *vertex * coefficient;
}
}
sourceVertices = _blendedVertices.constData();
}
glm::mat4 transform = baseTransform;
if (mesh.isEye) {
transform = transform * glm::translate(mesh.pivot) * glm::mat4_cast(glm::inverse(orientation) *
_owningHead->getEyeRotation(orientation * ((mesh.pivot + offset) * scale) + _owningHead->getPosition())) *
glm::translate(-mesh.pivot);
}
if (_resetStates) {
for (int j = 0; j < vertexCount; j++) {
destVertices[j] = glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f));
destVelocities[j] = glm::vec3();
}
} else {
const float SPRINGINESS_MULTIPLIER = 200.0f;
const float DAMPING = 5.0f;
for (int j = 0; j < vertexCount; j++) {
destVelocities[j] += ((glm::vec3(transform * glm::vec4(sourceVertices[j], 1.0f)) - destVertices[j]) *
mesh.springiness * SPRINGINESS_MULTIPLIER - destVelocities[j] * DAMPING) * deltaTime;
destVertices[j] += destVelocities[j] * deltaTime;
}
}
for (int j = 0; j < vertexCount; j++) {
destNormals[j] = glm::vec3();
const glm::vec3& middle = destVertices[j];
for (QVarLengthArray<QPair<int, int>, 4>::const_iterator connection = mesh.vertexConnections.at(j).constBegin();
connection != mesh.vertexConnections.at(j).constEnd(); connection++) {
destNormals[j] += glm::normalize(glm::cross(destVertices[connection->second] - middle,
destVertices[connection->first] - middle));
}
}
}
_resetStates = false;
}
bool BlendFace::render(float alpha) {
if (_meshStates.isEmpty()) {
return false;
}
// set up blended buffer ids on first render after load
// set up blended buffer ids on first render after load/simulate
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
if (_blendedVertexBufferIDs.isEmpty()) {
foreach (const FBXMesh& mesh, geometry.meshes) {
GLuint id = 0;
if (!mesh.blendshapes.isEmpty()) {
if (!mesh.blendshapes.isEmpty() || mesh.springiness > 0.0f) {
glGenBuffers(1, &id);
glBindBuffer(GL_ARRAY_BUFFER, id);
glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3),
@ -69,6 +167,9 @@ bool BlendFace::render(float alpha) {
_dilatedTextures.resize(geometry.meshes.size());
}
glm::mat4 viewMatrix;
glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&viewMatrix);
glPushMatrix();
glTranslatef(_owningHead->getPosition().x, _owningHead->getPosition().y, _owningHead->getPosition().z);
glm::quat orientation = _owningHead->getOrientation();
@ -90,6 +191,9 @@ bool BlendFace::render(float alpha) {
glEnable(GL_TEXTURE_2D);
glDisable(GL_COLOR_MATERIAL);
// the eye shader uses the color state even though color material is disabled
glColor4f(1.0f, 1.0f, 1.0f, alpha);
for (int i = 0; i < networkMeshes.size(); i++) {
const NetworkMesh& networkMesh = networkMeshes.at(i);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
@ -104,7 +208,7 @@ bool BlendFace::render(float alpha) {
if (mesh.isEye) {
glTranslatef(mesh.pivot.x, mesh.pivot.y, mesh.pivot.z);
glm::quat rotation = glm::inverse(orientation) * _owningHead->getEyeRotation(orientation *
(mesh.pivot * scale + MODEL_TRANSLATION) + _owningHead->getPosition());
((mesh.pivot + offset) * scale) + _owningHead->getPosition());
glm::vec3 rotationAxis = glm::axis(rotation);
glRotatef(glm::angle(rotation), -rotationAxis.x, rotationAxis.y, -rotationAxis.z);
glTranslatef(-mesh.pivot.x, -mesh.pivot.y, -mesh.pivot.z);
@ -130,39 +234,49 @@ bool BlendFace::render(float alpha) {
glBindTexture(GL_TEXTURE_2D, texture == NULL ? 0 : texture->getID());
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
if (mesh.blendshapes.isEmpty()) {
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
glTexCoordPointer(2, GL_FLOAT, 0, (void*)(vertexCount * 2 * sizeof(glm::vec3)));
} else {
glTexCoordPointer(2, GL_FLOAT, 0, 0);
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
_blendedNormals.resize(_blendedVertices.size());
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
// blend in each coefficient
const vector<float>& coefficients = _owningHead->getBlendshapeCoefficients();
for (int j = 0; j < coefficients.size(); j++) {
float coefficient = coefficients[j];
if (coefficient == 0.0f || j >= mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
continue;
}
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
const glm::vec3* normal = mesh.blendshapes[j].normals.constData();
for (const int* index = mesh.blendshapes[j].indices.constData(),
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) {
_blendedVertices[*index] += *vertex * coefficient;
_blendedNormals[*index] += *normal * normalCoefficient;
}
}
glBindBuffer(GL_ARRAY_BUFFER, _blendedVertexBufferIDs.at(i));
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
vertexCount * sizeof(glm::vec3), _blendedNormals.constData());
const MeshState& state = _meshStates.at(i);
if (!state.worldSpaceVertices.isEmpty()) {
glLoadMatrixf((const GLfloat*)&viewMatrix);
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), state.worldSpaceVertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
vertexCount * sizeof(glm::vec3), state.worldSpaceNormals.constData());
} else {
_blendedVertices.resize(max(_blendedVertices.size(), vertexCount));
_blendedNormals.resize(_blendedVertices.size());
memcpy(_blendedVertices.data(), mesh.vertices.constData(), vertexCount * sizeof(glm::vec3));
memcpy(_blendedNormals.data(), mesh.normals.constData(), vertexCount * sizeof(glm::vec3));
// blend in each coefficient
const vector<float>& coefficients = _owningHead->getBlendshapeCoefficients();
for (int j = 0; j < coefficients.size(); j++) {
float coefficient = coefficients[j];
if (coefficient == 0.0f || j >= mesh.blendshapes.size() || mesh.blendshapes[j].vertices.isEmpty()) {
continue;
}
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
float normalCoefficient = coefficient * NORMAL_COEFFICIENT_SCALE;
const glm::vec3* vertex = mesh.blendshapes[j].vertices.constData();
const glm::vec3* normal = mesh.blendshapes[j].normals.constData();
for (const int* index = mesh.blendshapes[j].indices.constData(),
*end = index + mesh.blendshapes[j].indices.size(); index != end; index++, vertex++, normal++) {
_blendedVertices[*index] += *vertex * coefficient;
_blendedNormals[*index] += *normal * normalCoefficient;
}
}
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(glm::vec3), _blendedVertices.constData());
glBufferSubData(GL_ARRAY_BUFFER, vertexCount * sizeof(glm::vec3),
vertexCount * sizeof(glm::vec3), _blendedNormals.constData());
}
}
glVertexPointer(3, GL_FLOAT, 0, 0);
glNormalPointer(GL_FLOAT, 0, (void*)(vertexCount * sizeof(glm::vec3)));
@ -199,12 +313,16 @@ bool BlendFace::render(float alpha) {
return true;
}
void BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
bool BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition, bool upright) const {
if (!isActive()) {
return;
return false;
}
glm::vec3 translation = _owningHead->getPosition();
glm::quat orientation = _owningHead->getOrientation();
if (upright) {
translation = static_cast<MyAvatar*>(_owningHead->_owningAvatar)->getUprightHeadPosition();
orientation = static_cast<Avatar*>(_owningHead->_owningAvatar)->getWorldAlignedOrientation();
}
glm::vec3 scale(-_owningHead->getScale() * MODEL_SCALE, _owningHead->getScale() * MODEL_SCALE,
-_owningHead->getScale() * MODEL_SCALE);
bool foundFirst = false;
@ -212,16 +330,20 @@ void BlendFace::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEy
const FBXGeometry& geometry = _geometry->getFBXGeometry();
foreach (const FBXMesh& mesh, geometry.meshes) {
if (mesh.isEye) {
glm::vec3 position = orientation * ((mesh.pivot + MODEL_TRANSLATION - geometry.neckPivot) * scale) +
_owningHead->getPosition();
glm::vec3 position = orientation * ((mesh.pivot + MODEL_TRANSLATION - geometry.neckPivot) * scale) + translation;
if (foundFirst) {
secondEyePosition = position;
return;
return true;
}
firstEyePosition = position;
foundFirst = true;
}
}
return false;
}
glm::vec4 BlendFace::computeAverageColor() const {
return _geometry ? _geometry->computeAverageColor() : glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
}
void BlendFace::setModelURL(const QUrl& url) {
@ -243,4 +365,5 @@ void BlendFace::deleteGeometry() {
glDeleteBuffers(1, &id);
}
_blendedVertexBufferIDs.clear();
_meshStates.clear();
}

View file

@ -33,12 +33,20 @@ public:
bool isActive() const { return _geometry && _geometry->isLoaded(); }
void init();
void reset();
void simulate(float deltaTime);
bool render(float alpha);
Q_INVOKABLE void setModelURL(const QUrl& url);
const QUrl& getModelURL() const { return _modelURL; }
void getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
/// Retrieve the positions of up to two eye meshes.
/// \param upright if true, retrieve the locations of the eyes in the upright position
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition, bool upright = false) const;
/// Returns the average color of all meshes in the geometry.
glm::vec4 computeAverageColor() const;
private:
@ -50,8 +58,17 @@ private:
QSharedPointer<NetworkGeometry> _geometry;
class MeshState {
public:
QVector<glm::vec3> worldSpaceVertices;
QVector<glm::vec3> vertexVelocities;
QVector<glm::vec3> worldSpaceNormals;
};
QVector<MeshState> _meshStates;
QVector<GLuint> _blendedVertexBufferIDs;
QVector<QSharedPointer<Texture> > _dilatedTextures;
bool _resetStates;
QVector<glm::vec3> _blendedVertices;
QVector<glm::vec3> _blendedNormals;

View file

@ -83,8 +83,6 @@ Head::Head(Avatar* owningAvatar) :
_mousePitch(0.f),
_cameraYaw(_yaw),
_isCameraMoving(false),
_cameraFollowsHead(false),
_cameraFollowHeadRate(0.0f),
_face(this),
_perlinFace(this),
_blendFace(this)
@ -117,6 +115,8 @@ void Head::reset() {
if (USING_PHYSICAL_MOHAWK) {
resetHairPhysics();
}
_blendFace.reset();
}
void Head::resetHairPhysics() {
@ -140,8 +140,10 @@ void Head::simulate(float deltaTime, bool isMine) {
// Update audio trailing average for rendering facial animations
Faceshift* faceshift = Application::getInstance()->getFaceshift();
_isFaceshiftConnected = faceshift != NULL;
if (isMine) {
_isFaceshiftConnected = faceshift->isActive();
}
if (isMine && faceshift->isActive()) {
const float EYE_OPEN_SCALE = 0.5f;
_leftEyeBlink = faceshift->getLeftBlink() - EYE_OPEN_SCALE * faceshift->getLeftEyeOpen();
@ -155,7 +157,7 @@ void Head::simulate(float deltaTime, bool isMine) {
_browAudioLift = faceshift->getBrowUpCenter() * BROW_HEIGHT_SCALE;
_blendshapeCoefficients = faceshift->getBlendshapeCoefficients();
} else if (!_isFaceshiftConnected) {
} else if (!_isFaceshiftConnected) {
// Update eye saccades
const float AVERAGE_MICROSACCADE_INTERVAL = 0.50f;
const float AVERAGE_SACCADE_INTERVAL = 4.0f;
@ -235,6 +237,8 @@ void Head::simulate(float deltaTime, bool isMine) {
if (USING_PHYSICAL_MOHAWK) {
updateHairPhysics(deltaTime);
}
_blendFace.simulate(deltaTime);
}
void Head::calculateGeometry() {
@ -300,15 +304,14 @@ void Head::render(float alpha, bool isMine) {
renderEyeBrows();
}
}
if (_blendFace.isActive()) {
// the blend face may have custom eye meshes
_blendFace.getEyePositions(_leftEyePosition, _rightEyePosition);
}
if (_renderLookatVectors) {
glm::vec3 firstEyePosition = _leftEyePosition;
glm::vec3 secondEyePosition = _rightEyePosition;
if (_blendFace.isActive()) {
// the blend face may have custom eye meshes
_blendFace.getEyePositions(firstEyePosition, secondEyePosition);
}
renderLookatVectors(firstEyePosition, secondEyePosition, _lookAtPosition);
renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition);
}
}

View file

@ -57,7 +57,6 @@ public:
void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; }
void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; }
void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; }
void setCameraFollowsHead(bool cameraFollowsHead) { _cameraFollowsHead = cameraFollowsHead; }
float getMousePitch() const { return _mousePitch; }
void setMousePitch(float mousePitch) { _mousePitch = mousePitch; }
@ -133,8 +132,6 @@ private:
float _mousePitch;
float _cameraYaw;
bool _isCameraMoving;
bool _cameraFollowsHead;
float _cameraFollowHeadRate;
Face _face;
PerlinFace _perlinFace;
BlendFace _blendFace;
@ -158,6 +155,7 @@ private:
void resetHairPhysics();
void updateHairPhysics(float deltaTime);
friend class BlendFace;
friend class PerlinFace;
};

View file

@ -16,6 +16,7 @@
#include <SharedUtil.h>
#include "Application.h"
#include "DataServerClient.h"
#include "MyAvatar.h"
#include "Physics.h"
#include "devices/OculusManager.h"
@ -356,8 +357,7 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter) {
}
// Update avatar head rotation with sensor data
void MyAvatar::updateFromGyrosAndOrWebcam(bool gyroLook,
float pitchFromTouch) {
void MyAvatar::updateFromGyrosAndOrWebcam(float pitchFromTouch, bool turnWithHead) {
Faceshift* faceshift = Application::getInstance()->getFaceshift();
SerialInterface* gyros = Application::getInstance()->getSerialHeadSensor();
Webcam* webcam = Application::getInstance()->getWebcam();
@ -367,11 +367,13 @@ void MyAvatar::updateFromGyrosAndOrWebcam(bool gyroLook,
estimatedPosition = faceshift->getHeadTranslation();
estimatedRotation = safeEulerAngles(faceshift->getHeadRotation());
// Rotate the body if the head is turned quickly
glm::vec3 headAngularVelocity = faceshift->getHeadAngularVelocity();
const float FACESHIFT_YAW_VIEW_SENSITIVITY = 20.f;
const float FACESHIFT_MIN_YAW_VELOCITY = 1.0f;
if (fabs(headAngularVelocity.y) > FACESHIFT_MIN_YAW_VELOCITY) {
_bodyYawDelta += headAngularVelocity.y * FACESHIFT_YAW_VIEW_SENSITIVITY;
if (turnWithHead) {
glm::vec3 headAngularVelocity = faceshift->getHeadAngularVelocity();
const float FACESHIFT_YAW_VIEW_SENSITIVITY = 20.f;
const float FACESHIFT_MIN_YAW_VELOCITY = 1.0f;
if (fabs(headAngularVelocity.y) > FACESHIFT_MIN_YAW_VELOCITY) {
_bodyYawDelta += headAngularVelocity.y * FACESHIFT_YAW_VIEW_SENSITIVITY;
}
}
} else if (gyros->isActive()) {
estimatedRotation = gyros->getEstimatedRotation();
@ -428,7 +430,6 @@ void MyAvatar::updateFromGyrosAndOrWebcam(bool gyroLook,
_head.setPitch(estimatedRotation.x * AVATAR_HEAD_PITCH_MAGNIFY);
_head.setYaw(estimatedRotation.y * AVATAR_HEAD_YAW_MAGNIFY);
_head.setRoll(estimatedRotation.z * AVATAR_HEAD_ROLL_MAGNIFY);
_head.setCameraFollowsHead(gyroLook);
// Update torso lean distance based on accelerometer data
const float TORSO_LENGTH = _scale * 0.5f;
@ -524,6 +525,50 @@ void MyAvatar::renderScreenTint(ScreenTintLayer layer, Camera& whichCamera) {
}
}
void MyAvatar::saveData(QSettings* settings) {
settings->beginGroup("Avatar");
settings->setValue("bodyYaw", _bodyYaw);
settings->setValue("bodyPitch", _bodyPitch);
settings->setValue("bodyRoll", _bodyRoll);
settings->setValue("position_x", _position.x);
settings->setValue("position_y", _position.y);
settings->setValue("position_z", _position.z);
settings->setValue("voxelURL", _voxels.getVoxelURL());
settings->setValue("pupilDilation", _head.getPupilDilation());
settings->setValue("leanScale", _leanScale);
settings->setValue("scale", _newScale);
settings->endGroup();
}
void MyAvatar::loadData(QSettings* settings) {
settings->beginGroup("Avatar");
// in case settings is corrupt or missing loadSetting() will check for NaN
_bodyYaw = loadSetting(settings, "bodyYaw", 0.0f);
_bodyPitch = loadSetting(settings, "bodyPitch", 0.0f);
_bodyRoll = loadSetting(settings, "bodyRoll", 0.0f);
_position.x = loadSetting(settings, "position_x", 0.0f);
_position.y = loadSetting(settings, "position_y", 0.0f);
_position.z = loadSetting(settings, "position_z", 0.0f);
_voxels.setVoxelURL(settings->value("voxelURL").toUrl());
_head.setPupilDilation(settings->value("pupilDilation", 0.0f).toFloat());
_leanScale = loadSetting(settings, "leanScale", 0.05f);
_newScale = loadSetting(settings, "scale", 1.0f);
setScale(_scale);
Application::getInstance()->getCamera()->setScale(_scale);
settings->endGroup();
}
float MyAvatar::getAbsoluteHeadYaw() const {
return glm::yaw(_head.getOrientation());
}
@ -532,10 +577,10 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const {
return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, _pelvisToHeadLength, 0.0f);
}
glm::vec3 MyAvatar::getUprightEyeLevelPosition() const {
const float EYE_UP_OFFSET = 0.36f;
glm::vec3 up = getWorldAlignedOrientation() * IDENTITY_UP;
return _position + up * _scale * BODY_BALL_RADIUS_HEAD_BASE * EYE_UP_OFFSET + glm::vec3(0.0f, _pelvisToHeadLength, 0.0f);
glm::vec3 MyAvatar::getEyeLevelPosition() const {
const float EYE_UP_OFFSET = 0.36f;
return _position + getWorldAlignedOrientation() * _skeleton.joint[AVATAR_JOINT_TORSO].rotation *
glm::vec3(0.0f, _pelvisToHeadLength + _scale * BODY_BALL_RADIUS_HEAD_BASE * EYE_UP_OFFSET, 0.0f);
}
float MyAvatar::getBallRenderAlpha(int ball, bool lookingInMirror) const {
@ -555,9 +600,6 @@ void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
return;
}
// glow when moving
Glower glower(_moving ? 1.0f : 0.0f);
if (_head.getFace().isFullFrame()) {
// Render the full-frame video
float alpha = getBallRenderAlpha(BODY_BALL_HEAD_BASE, lookingInMirror);
@ -566,6 +608,13 @@ void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
}
} else if (renderAvatarBalls || !_voxels.getVoxelURL().isValid()) {
// Render the body as balls and cones
glm::vec3 skinColor(SKIN_COLOR[0], SKIN_COLOR[1], SKIN_COLOR[2]);
glm::vec3 darkSkinColor(DARK_SKIN_COLOR[0], DARK_SKIN_COLOR[1], DARK_SKIN_COLOR[2]);
if (_head.getBlendFace().isActive()) {
skinColor = glm::vec3(_head.getBlendFace().computeAverageColor());
const float SKIN_DARKENING = 0.9f;
darkSkinColor = skinColor * SKIN_DARKENING;
}
for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) {
float alpha = getBallRenderAlpha(b, lookingInMirror);
@ -586,14 +635,14 @@ void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
if (b == BODY_BALL_RIGHT_ELBOW
|| b == BODY_BALL_RIGHT_WRIST
|| b == BODY_BALL_RIGHT_FINGERTIPS ) {
glColor3f(SKIN_COLOR[0] + _bodyBall[b].touchForce * 0.3f,
SKIN_COLOR[1] - _bodyBall[b].touchForce * 0.2f,
SKIN_COLOR[2] - _bodyBall[b].touchForce * 0.1f);
glColor3f(skinColor.r + _bodyBall[b].touchForce * 0.3f,
skinColor.g - _bodyBall[b].touchForce * 0.2f,
skinColor.b - _bodyBall[b].touchForce * 0.1f);
} else {
glColor4f(SKIN_COLOR[0] + _bodyBall[b].touchForce * 0.3f,
SKIN_COLOR[1] - _bodyBall[b].touchForce * 0.2f,
SKIN_COLOR[2] - _bodyBall[b].touchForce * 0.1f,
alpha);
glColor4f(skinColor.r + _bodyBall[b].touchForce * 0.3f,
skinColor.g - _bodyBall[b].touchForce * 0.2f,
skinColor.b - _bodyBall[b].touchForce * 0.1f,
alpha);
}
if (b == BODY_BALL_NECK_BASE && _head.getBlendFace().isActive()) {
@ -619,7 +668,7 @@ void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
&& (b != BODY_BALL_LEFT_SHOULDER)
&& (b != BODY_BALL_RIGHT_COLLAR)
&& (b != BODY_BALL_RIGHT_SHOULDER)) {
glColor3fv(DARK_SKIN_COLOR);
glColor3fv((const GLfloat*)&darkSkinColor);
float r2 = _bodyBall[b].radius * 0.8;

View file

@ -9,6 +9,8 @@
#ifndef __interface__myavatar__
#define __interface__myavatar__
#include <QSettings>
#include "Avatar.h"
class MyAvatar : public Avatar {
@ -17,7 +19,7 @@ public:
void reset();
void simulate(float deltaTime, Transmitter* transmitter);
void updateFromGyrosAndOrWebcam(bool gyroLook, float pitchFromTouch);
void updateFromGyrosAndOrWebcam(float pitchFromTouch, bool turnWithHead);
void render(bool lookingInMirror, bool renderAvatarBalls);
void renderScreenTint(ScreenTintLayer layer, Camera& whichCamera);
@ -46,7 +48,11 @@ public:
Avatar* getLeadingAvatar() const { return _leadingAvatar; }
glm::vec3 getGravity() const { return _gravity; }
glm::vec3 getUprightHeadPosition() const;
glm::vec3 getUprightEyeLevelPosition() const;
glm::vec3 getEyeLevelPosition() const;
// get/set avatar data
void saveData(QSettings* settings);
void loadData(QSettings* settings);
// Set what driving keys are being pressed to control thrust levels
void setDriveKeys(int key, bool val) { _driveKeys[key] = val; };
@ -57,7 +63,7 @@ public:
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
glm::vec3 getThrust() { return _thrust; };
private:
private:
bool _mousePressed;
float _bodyPitchDelta;
float _bodyRollDelta;

View file

@ -0,0 +1,106 @@
//
// Profile.cpp
// hifi
//
// Created by Stephen Birarda on 10/8/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QSettings>
#include <UUID.h>
#include "Profile.h"
#include "DataServerClient.h"
Profile::Profile(const QString &username) :
_username(username),
_uuid(),
_lastDomain(),
_lastPosition(0.0, 0.0, 0.0),
_faceModelURL()
{
if (!_username.isEmpty()) {
// we've been given a new username, ask the data-server for profile
DataServerClient::getClientValueForKey(DataServerKey::UUID);
}
}
QString Profile::getUserString() const {
if (_uuid.isNull()) {
return _username;
} else {
return uuidStringWithoutCurlyBraces(_uuid);
}
}
void Profile::setUUID(const QUuid& uuid) {
_uuid = uuid;
// when the UUID is changed we need set it appropriately on our avatar instance
Application::getInstance()->getAvatar()->setUUID(_uuid);
}
void Profile::setFaceModelURL(const QUrl& faceModelURL) {
_faceModelURL = faceModelURL;
QMetaObject::invokeMethod(&Application::getInstance()->getAvatar()->getHead().getBlendFace(),
"setModelURL",
Q_ARG(QUrl, _faceModelURL));
}
void Profile::updateDomain(const QString& domain) {
if (_lastDomain != domain) {
_lastDomain = domain;
// send the changed domain to the data-server
DataServerClient::putValueForKey(DataServerKey::Domain, domain.toLocal8Bit().constData());
}
}
void Profile::updatePosition(const glm::vec3 position) {
if (_lastPosition != position) {
static timeval lastPositionSend = {};
const uint64_t DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
const float DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS = 1;
if (usecTimestampNow() - usecTimestamp(&lastPositionSend) >= DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS &&
(fabsf(_lastPosition.x - position.x) >= DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS ||
fabsf(_lastPosition.y - position.y) >= DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS ||
fabsf(_lastPosition.z - position.z) >= DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS)) {
// if it has been 5 seconds since the last position change and the user has moved >= the threshold
// in at least one of the axis then send the position update to the data-server
_lastPosition = position;
// update the lastPositionSend to now
gettimeofday(&lastPositionSend, NULL);
// send the changed position to the data-server
QString positionString = QString("%1,%2,%3").arg(position.x).arg(position.y).arg(position.z);
DataServerClient::putValueForKey(DataServerKey::Position, positionString.toLocal8Bit().constData());
}
}
}
void Profile::saveData(QSettings* settings) {
settings->beginGroup("Profile");
settings->setValue("username", _username);
settings->setValue("UUID", _uuid);
settings->setValue("faceModelURL", _faceModelURL);
settings->endGroup();
}
void Profile::loadData(QSettings* settings) {
settings->beginGroup("Profile");
_username = settings->value("username").toString();
this->setUUID(settings->value("UUID").toUuid());
_faceModelURL = settings->value("faceModelURL").toUrl();
settings->endGroup();
}

View file

@ -0,0 +1,48 @@
//
// Profile.h
// hifi
//
// Created by Stephen Birarda on 10/8/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__Profile__
#define __hifi__Profile__
#include <QtCore/QString>
#include <QtCore/QUrl>
#include <QtCore/QUuid>
#include <glm/glm.hpp>
class Profile {
public:
Profile(const QString& username);
QString getUserString() const;
const QString& getUsername() const { return _username; }
void setUUID(const QUuid& uuid);
const QUuid& getUUID() { return _uuid; }
void setFaceModelURL(const QUrl& faceModelURL);
const QUrl& getFaceModelURL() const { return _faceModelURL; }
void updateDomain(const QString& domain);
void updatePosition(const glm::vec3 position);
QString getLastDomain() const { return _lastDomain; }
const glm::vec3& getLastPosition() const { return _lastPosition; }
void saveData(QSettings* settings);
void loadData(QSettings* settings);
private:
QString _username;
QUuid _uuid;
QString _lastDomain;
glm::vec3 _lastPosition;
QUrl _faceModelURL;
};
#endif /* defined(__hifi__Profile__) */

View file

@ -300,6 +300,7 @@ const char* FACESHIFT_BLENDSHAPES[] = {
class Transform {
public:
QByteArray name;
bool inheritScale;
glm::mat4 withScale;
glm::mat4 withoutScale;
@ -383,7 +384,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
QList<QVariant> mappings = blendshapeMappings.values(blendshapeName);
if (mappings.isEmpty()) {
blendshapeIndices.insert(blendshapeName, QPair<int, float>(i, 1.0f));
blendshapeIndices.insert("ExpressionBlendshapes." + blendshapeName, QPair<int, float>(i, 1.0f));
} else {
foreach (const QVariant& mapping, mappings) {
QVariantList blendshapeMapping = mapping.toList();
@ -536,7 +536,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
glm::vec3 preRotation, rotation, postRotation;
glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f);
glm::vec3 scalePivot, rotationPivot;
Transform transform = { true };
Transform transform = { name, true };
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "Properties70") {
foreach (const FBXNode& property, subobject.children) {
@ -636,8 +636,13 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
}
} else if (object.properties.at(2) == "BlendShapeChannel") {
QByteArray name = object.properties.at(1).toByteArray();
name = name.left(name.indexOf('\0'));
if (!blendshapeIndices.contains(name)) {
// try everything after the dot
name = name.mid(name.lastIndexOf('.') + 1);
}
blendshapeChannelIndices.insert(object.properties.at(0).value<qint64>(),
blendshapeIndices.value(name.left(name.indexOf('\0'))));
blendshapeIndices.value(name));
}
}
}
@ -679,14 +684,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())))) *
glm::scale(offsetScale, offsetScale, offsetScale);
// as a temporary hack, put the mesh with the most blendshapes on top; assume it to be the face
FBXGeometry geometry;
int mostBlendshapes = 0;
QVariantHash springs = mapping.value("spring").toHash();
QVariant defaultSpring = springs.value("default");
for (QHash<qint64, FBXMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
FBXMesh& mesh = it.value();
// accumulate local transforms
qint64 modelID = parentMap.value(it.key());
mesh.springiness = springs.value(localTransforms.value(modelID).name, defaultSpring).toFloat();
glm::mat4 modelTransform = getGlobalTransform(parentMap, localTransforms, modelID);
// look for textures, material properties
@ -731,13 +737,47 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
}
}
if (mesh.blendshapes.size() > mostBlendshapes) {
geometry.meshes.prepend(mesh);
mostBlendshapes = mesh.blendshapes.size();
// extract spring edges, connections if springy
if (mesh.springiness > 0.0f) {
QSet<QPair<int, int> > edges;
} else {
geometry.meshes.append(mesh);
mesh.vertexConnections.resize(mesh.vertices.size());
for (int i = 0; i < mesh.quadIndices.size(); i += 4) {
int index0 = mesh.quadIndices.at(i);
int index1 = mesh.quadIndices.at(i + 1);
int index2 = mesh.quadIndices.at(i + 2);
int index3 = mesh.quadIndices.at(i + 3);
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
edges.insert(QPair<int, int>(qMin(index2, index3), qMax(index2, index3)));
edges.insert(QPair<int, int>(qMin(index3, index0), qMax(index3, index0)));
mesh.vertexConnections[index0].append(QPair<int, int>(index3, index1));
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index3));
mesh.vertexConnections[index3].append(QPair<int, int>(index2, index0));
}
for (int i = 0; i < mesh.triangleIndices.size(); i += 3) {
int index0 = mesh.triangleIndices.at(i);
int index1 = mesh.triangleIndices.at(i + 1);
int index2 = mesh.triangleIndices.at(i + 2);
edges.insert(QPair<int, int>(qMin(index0, index1), qMax(index0, index1)));
edges.insert(QPair<int, int>(qMin(index1, index2), qMax(index1, index2)));
edges.insert(QPair<int, int>(qMin(index2, index0), qMax(index2, index0)));
mesh.vertexConnections[index0].append(QPair<int, int>(index2, index1));
mesh.vertexConnections[index1].append(QPair<int, int>(index0, index2));
mesh.vertexConnections[index2].append(QPair<int, int>(index1, index0));
}
for (QSet<QPair<int, int> >::const_iterator edge = edges.constBegin(); edge != edges.constEnd(); edge++) {
mesh.springEdges.append(*edge);
}
}
geometry.meshes.append(mesh);
}
// extract translation component for neck pivot

View file

@ -9,6 +9,7 @@
#ifndef __interface__FBXReader__
#define __interface__FBXReader__
#include <QVarLengthArray>
#include <QVariant>
#include <QVector>
@ -59,6 +60,10 @@ public:
QByteArray normalFilename;
QVector<FBXBlendshape> blendshapes;
float springiness;
QVector<QPair<int, int> > springEdges;
QVector<QVarLengthArray<QPair<int, int>, 4> > vertexConnections;
};
/// A set of meshes extracted from an FBX document.

View file

@ -288,6 +288,23 @@ NetworkGeometry::~NetworkGeometry() {
}
}
glm::vec4 NetworkGeometry::computeAverageColor() const {
glm::vec4 totalColor;
int totalVertices = 0;
for (int i = 0; i < _meshes.size(); i++) {
if (_geometry.meshes.at(i).isEye) {
continue; // skip eyes
}
glm::vec4 color = glm::vec4(_geometry.meshes.at(i).diffuseColor, 1.0f);
if (_meshes.at(i).diffuseTexture) {
color *= _meshes.at(i).diffuseTexture->getAverageColor();
}
totalColor += color * _geometry.meshes.at(i).vertices.size();
totalVertices += _geometry.meshes.at(i).vertices.size();
}
return (totalVertices == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalVertices;
}
void NetworkGeometry::handleModelReplyError() {
qDebug() << _modelReply->errorString() << "\n";
@ -346,7 +363,7 @@ void NetworkGeometry::maybeReadModelWithMapping() {
glGenBuffers(1, &networkMesh.vertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
if (mesh.blendshapes.isEmpty()) {
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
glBufferData(GL_ARRAY_BUFFER, (mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3) +
mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData());

View file

@ -62,6 +62,9 @@ public:
const FBXGeometry& getFBXGeometry() const { return _geometry; }
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
/// Returns the average color of all meshes in the geometry.
glm::vec4 computeAverageColor() const;
private slots:
void handleModelReplyError();

View file

@ -173,7 +173,7 @@ Texture::~Texture() {
glDeleteTextures(1, &_id);
}
NetworkTexture::NetworkTexture(const QUrl& url) : _reply(NULL) {
NetworkTexture::NetworkTexture(const QUrl& url) : _reply(NULL), _averageColor(1.0f, 1.0f, 1.0f, 1.0f) {
if (!url.isValid()) {
return;
}
@ -206,6 +206,21 @@ void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTo
_reply = NULL;
QImage image = QImage::fromData(entirety).convertToFormat(QImage::Format_ARGB32);
// sum up the colors for the average
glm::vec4 accumulated;
for (int y = 0; y < image.height(); y++) {
for (int x = 0; x < image.width(); x++) {
QRgb pixel = image.pixel(x, y);
accumulated.r += qRed(pixel);
accumulated.g += qGreen(pixel);
accumulated.b += qBlue(pixel);
accumulated.a += qAlpha(pixel);
}
}
const float EIGHT_BIT_MAXIMUM = 255.0f;
_averageColor = accumulated / (image.width() * image.height() * EIGHT_BIT_MAXIMUM);
imageLoaded(image);
glBindTexture(GL_TEXTURE_2D, getID());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 1,

View file

@ -99,6 +99,9 @@ public:
NetworkTexture(const QUrl& url);
~NetworkTexture();
/// Returns the average color over the entire texture.
const glm::vec4& getAverageColor() const { return _averageColor; }
protected:
virtual void imageLoaded(const QImage& image);
@ -111,6 +114,7 @@ private slots:
private:
QNetworkReply* _reply;
glm::vec4 _averageColor;
};
/// Caches derived, dilated textures.

View file

@ -4,9 +4,7 @@
//
// Created by Philip Rosedale on 8/23/12.
// Copyright (c) 2012 High Fidelity, Inc. All rights reserved.
//
// Simulation happens in positive cube with edge of size WORLD_SIZE
//
#ifndef __interface__world__
#define __interface__world__

View file

@ -23,6 +23,7 @@ static const float fingerVectorRadix = 4; // bits of precision when converting f
AvatarData::AvatarData(Node* owningNode) :
NodeData(owningNode),
_uuid(),
_handPosition(0,0,0),
_bodyYaw(-90.0),
_bodyPitch(0.0),
@ -84,6 +85,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;
@ -217,6 +223,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;
@ -227,7 +238,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 <RegisteredMetaTypes.h>
@ -69,6 +70,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; }
@ -76,7 +80,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; }
@ -130,6 +133,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) {
@ -254,6 +261,8 @@ int NodeList::getNumAliveNodes() const {
}
void NodeList::clear() {
qDebug() << "Clearing the NodeList. Deleting all nodes in list.\n";
// delete all of the nodes in the list, set the pointers back to NULL and the number of nodes to 0
for (int i = 0; i < _numNodes; i++) {
Node** nodeBucket = _nodeBuckets[i / NODES_PER_BUCKET];
@ -428,7 +437,6 @@ void NodeList::sendAssignment(Assignment& assignment) {
}
Node* NodeList::addOrUpdateNode(sockaddr* publicSocket, sockaddr* localSocket, char nodeType, uint16_t nodeId) {
qDebug() << "BEGIN:" << publicSocket << "and" << localSocket << "\n";
NodeList::iterator node = end();
if (publicSocket) {
@ -443,8 +451,6 @@ Node* NodeList::addOrUpdateNode(sockaddr* publicSocket, sockaddr* localSocket, c
}
if (node == end()) {
qDebug() << "We're considering this a new node!\n";
qDebug() << publicSocket << "and" << localSocket << "\n";
// we didn't have this node, so add them
Node* newNode = new Node(publicSocket, localSocket, nodeType, nodeId);
@ -537,20 +543,18 @@ Node* NodeList::soloNodeOfType(char nodeType) {
void* removeSilentNodes(void *args) {
NodeList* nodeList = (NodeList*) args;
uint64_t checkTimeUSecs;
int sleepTime;
uint64_t checkTimeUsecs = 0;
int sleepTime = 0;
while (!silentNodeThreadStopFlag) {
checkTimeUSecs = usecTimestampNow();
checkTimeUsecs = usecTimestampNow();
for(NodeList::iterator node = nodeList->begin(); node != nodeList->end(); ++node) {
node->lock();
qDebug() << "This node's LHMS is" << node->getLastHeardMicrostamp() << "\n";
if ((checkTimeUSecs - node->getLastHeardMicrostamp()) > NODE_SILENCE_THRESHOLD_USECS) {
if ((usecTimestampNow() - node->getLastHeardMicrostamp()) > NODE_SILENCE_THRESHOLD_USECS) {
qDebug() << "Killed " << *node << "\n";
nodeList->notifyHooksOfKilledNode(&*node);
@ -561,11 +565,18 @@ void* removeSilentNodes(void *args) {
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
usleep(sleepTime);
if (sleepTime > 0) {
usleep(sleepTime);
}
#endif
}

View file

@ -30,7 +30,7 @@ const int NODES_PER_BUCKET = 100;
const int MAX_PACKET_SIZE = 1500;
const uint64_t NODE_SILENCE_THRESHOLD_USECS = 2 * 1000000;
const uint64_t NODE_SILENCE_THRESHOLD_USECS = 2 * 1000 * 1000;
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

@ -0,0 +1,14 @@
//
// UUID.cpp
// hifi
//
// Created by Stephen Birarda on 10/7/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include "UUID.h"
QString uuidStringWithoutCurlyBraces(const QUuid& uuid) {
QString uuidStringNoBraces = uuid.toString().mid(1, uuid.toString().length() - 2);
return uuidStringNoBraces;
}

View file

@ -0,0 +1,16 @@
//
// UUID.h
// hifi
//
// Created by Stephen Birarda on 10/7/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__UUID__
#define __hifi__UUID__
#include <QtCore/QUuid>
QString uuidStringWithoutCurlyBraces(const QUuid& uuid);
#endif /* defined(__hifi__UUID__) */

View file

@ -295,19 +295,6 @@ void VoxelServer::run() {
}
qDebug("packetsPerSecond=%s PACKETS_PER_CLIENT_PER_INTERVAL=%d\n", packetsPerSecond, _packetsPerClientPerInterval);
}
// for now, initialize the environments with fixed values
_environmentData[1].setID(1);
_environmentData[1].setGravity(1.0f);
_environmentData[1].setAtmosphereCenter(glm::vec3(0.5, 0.5, (0.25 - 0.06125)) * (float)TREE_SCALE);
_environmentData[1].setAtmosphereInnerRadius(0.030625f * TREE_SCALE);
_environmentData[1].setAtmosphereOuterRadius(0.030625f * TREE_SCALE * 1.05f);
_environmentData[2].setID(2);
_environmentData[2].setGravity(1.0f);
_environmentData[2].setAtmosphereCenter(glm::vec3(0.5f, 0.5f, 0.5f) * (float)TREE_SCALE);
_environmentData[2].setAtmosphereInnerRadius(0.1875f * TREE_SCALE);
_environmentData[2].setAtmosphereOuterRadius(0.1875f * TREE_SCALE * 1.05f);
_environmentData[2].setScatteringWavelengths(glm::vec3(0.475f, 0.570f, 0.650f)); // swaps red and blue
sockaddr senderAddress;

View file

@ -371,7 +371,7 @@ bool ViewFrustum::matches(const ViewFrustum& compareTo, bool debug) const {
void ViewFrustum::computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const {
origin = _nearTopLeft + x*(_nearTopRight - _nearTopLeft) + y*(_nearBottomLeft - _nearTopLeft);
direction = glm::normalize(origin - _position);
direction = glm::normalize(origin - (_position + _orientation * _eyeOffsetPosition));
}
void ViewFrustum::computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& near, float& far,
@ -401,6 +401,10 @@ void ViewFrustum::computeOffAxisFrustum(float& left, float& right, float& bottom
far = max(far, -corners[i].z);
}
// make sure the near clip isn't too small to be valid
const float MIN_NEAR = 0.01f;
near = max(MIN_NEAR, near);
// get the near/far normal and use it to find the clip planes
glm::vec4 normal = eyeMatrix * glm::vec4(0.0f, 0.0f, 1.0f, 0.0f);
nearClipPlane = glm::vec4(-normal.x, -normal.y, -normal.z, glm::dot(normal, corners[0]));

View file

@ -79,6 +79,7 @@ void VoxelTree::recurseTreeWithOperation(RecurseVoxelTreeOperation operation, vo
// Recurses voxel node with an operation function
void VoxelTree::recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData) {
if (operation(node, extraData)) {
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
VoxelNode* child = node->getChildAtIndex(i);
@ -221,6 +222,13 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData,
nodeWasDirty = childNodeAt->isDirty();
childNodeAt->setColor(newColor);
childNodeAt->setSourceID(args.sourceID);
// if we had a local version of the node already, it's possible that we have it in the VBO but
// with the same color data, so this won't count as a change. To address this we check the following
if (!childNodeAt->isDirty() && !childNodeAt->isKnownBufferIndex() && childNodeAt->getShouldRender()) {
childNodeAt->setDirtyBit(); // force dirty!
}
nodeIsDirty = childNodeAt->isDirty();
}
if (nodeIsDirty) {

View file

@ -155,6 +155,7 @@ public:
creationMode mode, bool destructive = false, bool debug = false);
void recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData=NULL);
void recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation,
const glm::vec3& point, void* extraData=NULL);
@ -190,6 +191,7 @@ public:
bool getShouldReaverage() const { return _shouldReaverage; }
void recurseNodeWithOperation(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData);
void recurseNodeWithOperationDistanceSorted(VoxelNode* node, RecurseVoxelTreeOperation operation,
const glm::vec3& point, void* extraData);