another merge commit

This commit is contained in:
atlante45 2013-09-14 14:41:03 -07:00
commit ac0ee6c7d6
32 changed files with 6964 additions and 462 deletions

1
.gitignore vendored
View file

@ -5,6 +5,7 @@ CMakeScripts/
cmake_install.cmake
build/
Makefile
*.user
# Xcode
*.xcodeproj

View file

@ -36,8 +36,10 @@ build).
cmake .. -G Xcode
Those are the commands used on OS X to run CMake from the build folder
and generate Xcode project files. If you are building on a *nix system,
you'll run something like "cmake .." (this will depend on your exact needs)
and generate Xcode project files.
If you are building on a *nix system,
you'll run something like "cmake ..", which uses the default Cmake generator for Unix Makefiles.
Building in XCode
-----
@ -92,29 +94,36 @@ I want to run my own virtual world!
In order to set up your own virtual world, you need to set up and run your own
local "domain". At a minimum, you must run a domain-server, voxel-server,
audio-mixer, and avatar-mixer to have a working virtual world.
audio-mixer, and avatar-mixer to have a working virtual world. The audio-mixer and avatar-mixer are assignments given from the domain-server to any assignment-client that reports directly to it.
Complete the steps above to build the system components. Then from the terminal
window, change directory into the build direction, then launch the following
components.
Complete the steps above to build the system components, using the default Cmake Unix Makefiles generator. Start with an empty build directory.
./domain-server/Debug/domain-server --local &
./voxel-server/Debug/voxel-server --local &
./avatar-mixer/Debug/avatar-mixer --local &
./audio-mixer/Debug/audio-mixer --local &
cmake ..
To confirm that the components are running you can type the following command:
Then from the terminal
window, change directory into the build directory, make the needed components, and then launch them.
ps ax | grep -w "domain-server\|voxel-server\|audio-mixer\|avatar-mixer"
First we make the targets we'll need.
You should see something like this:
cd build
make domain-server voxel-server assignment-client
70488 s001 S 0:00.04 ./domain-server/Debug/domain-server --local
70489 s001 S 0:00.23 ./voxel-server/Debug/voxel-server --local
70490 s001 S 0:00.03 ./avatar-mixer/Debug/avatar-mixer --local
70491 s001 S 0:00.48 ./audio-mixer/Debug/audio-mixer --local
70511 s001 S+ 0:00.00 grep -w domain-server\|voxel-server\|audio-mixer\
|avatar-mixer
If after this step you're seeing something like the following
make: Nothing to be done for `domain-server'.
you likely had Cmake generate Xcode project files and have not run `cmake ..` in a clean build directory.
Then, launch the static components - a domain-server and a voxel-server. All of the targets will run in the foreground, so you'll either want to background it yourself or open a seperate terminal window per target.
cd domain-server && ./domain-server
./voxel-server/voxel-server --local > /tmp/voxel-server.log 2>&1 &
Then, run an assignment-client with 2 forks to fulfill the avatar-mixer and audio-mixer assignments. It uses localhost as its assignment-server and talks to it on port 40102 (the default domain-server port).
./assignment-client/assignment-client -n 2 -a localhost -p 40102
Any target can be terminated with CTRL-C (SIGINT) in the associated terminal window.
Determine the IP address of the machine you're running these servers on. Here's
a handy resource that explains how to do this for different operating systems.
@ -134,8 +143,14 @@ may now use this IP address to access your domain. If you are running a local
DNS or other name service you should be able to access this IP address by name
as well.
To test things out you'll want to run the Interface client. You can make that target with the following command:
make interface
Then run the executable it builds, or open interface.app if you're on OS X.
To access your local domain in Interface, open the Preferences dialog box, from
the Interface menu, and enter the IP address of the local DNS name for the
the Interface menu on OS X or the File menu on Linux, and enter the IP address of the local DNS name for the
server computer in the "Domain" edit control.
In the voxel-server/src directory you will find a README that explains in

View file

@ -8,9 +8,17 @@ set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/")
find_package(Qt5Network REQUIRED)
include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
qt5_use_modules(${TARGET_NAME} Network)
# include glm
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} ${ROOT_DIR})
# link in the shared library
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})

View file

@ -25,11 +25,11 @@ void Agent::run(QUrl scriptURL) {
NodeList::getInstance()->setOwnerType(NODE_TYPE_AGENT);
NodeList::getInstance()->setNodeTypesOfInterest(&NODE_TYPE_AVATAR_MIXER, 1);
QNetworkAccessManager* manager = new QNetworkAccessManager();
QNetworkAccessManager manager;
qDebug() << "Attemping download of " << scriptURL;
qDebug() << "Attemping download of " << scriptURL << "\n";
QNetworkReply* reply = manager->get(QNetworkRequest(scriptURL));
QNetworkReply* reply = manager.get(QNetworkRequest(scriptURL));
QEventLoop loop;
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
@ -47,15 +47,14 @@ void Agent::run(QUrl scriptURL) {
QScriptValue agentValue = engine.newQObject(this);
engine.globalObject().setProperty("Agent", agentValue);
qDebug() << "Downloaded script:" << scriptString;
qDebug() << "Evaluated script:" << engine.evaluate(scriptString).toString();
qDebug() << "Downloaded script:" << scriptString << "\n";
qDebug() << "Evaluated script:" << engine.evaluate(scriptString).toString() << "\n";
timeval thisSend;
timeval lastDomainServerCheckIn = {};
int numMicrosecondsSleep = 0;
const float DATA_SEND_INTERVAL_USECS = (1 / 60.0f) * 1000 * 1000;
const long long DATA_SEND_INTERVAL_USECS = (1 / 60.0f) * 1000 * 1000;
sockaddr_in senderAddress;
unsigned char receivedData[MAX_PACKET_SIZE];
@ -65,6 +64,11 @@ void Agent::run(QUrl scriptURL) {
// update the thisSend timeval to the current time
gettimeofday(&thisSend, NULL);
// if we're not hearing from the domain-server we should stop running
if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
break;
}
// 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);

View file

@ -9,9 +9,6 @@
#ifndef __hifi__Agent__
#define __hifi__Agent__
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include "SharedUtil.h"
#include <QtCore/QObject>

View file

@ -12,6 +12,9 @@
#include <sys/time.h>
#include <sys/wait.h>
#include <QtCore/QCoreApplication>
#include "Agent.h"
#include <Assignment.h>
#include <AudioMixer.h>
#include <AvatarMixer.h>
@ -26,7 +29,6 @@ const char CHILD_TARGET_NAME[] = "assignment-client";
pid_t* childForks = NULL;
sockaddr_in customAssignmentSocket = {};
const char* assignmentPool = NULL;
int numForks = 0;
void childClient() {
@ -51,35 +53,60 @@ void childClient() {
unsigned char packetData[MAX_PACKET_SIZE];
ssize_t receivedBytes = 0;
sockaddr_in senderSocket = {};
// create a request assignment, accept all assignments, pass the desired pool (if it exists)
Assignment requestAssignment(Assignment::Request, Assignment::All, assignmentPool);
Assignment requestAssignment(Assignment::RequestCommand, Assignment::AllTypes);
while (true) {
if (usecTimestampNow() - usecTimestamp(&lastRequest) >= ASSIGNMENT_REQUEST_INTERVAL_USECS) {
gettimeofday(&lastRequest, NULL);
// if we're here we have no assignment, so send a request
qDebug() << "Sending an assignment request -" << requestAssignment << "\n";
nodeList->sendAssignment(requestAssignment);
}
if (nodeList->getNodeSocket()->receive(packetData, &receivedBytes) &&
packetData[0] == PACKET_TYPE_DEPLOY_ASSIGNMENT && packetVersionMatch(packetData)) {
if (nodeList->getNodeSocket()->receive((sockaddr*) &senderSocket, packetData, &receivedBytes) &&
(packetData[0] == PACKET_TYPE_DEPLOY_ASSIGNMENT || packetData[0] == PACKET_TYPE_CREATE_ASSIGNMENT)
&& packetVersionMatch(packetData)) {
// construct the deployed assignment from the packet data
Assignment deployedAssignment(packetData, receivedBytes);
qDebug() << "Received an assignment -" << deployedAssignment << "\n";
// switch our nodelist DOMAIN_IP to the ip receieved in the assignment
if (deployedAssignment.getAttachedPublicSocket()->sa_family == AF_INET) {
in_addr domainSocketAddr = ((sockaddr_in*) deployedAssignment.getAttachedPublicSocket())->sin_addr;
// switch our nodelist DOMAIN_IP
if (packetData[0] == PACKET_TYPE_CREATE_ASSIGNMENT ||
deployedAssignment.getAttachedPublicSocket()->sa_family == AF_INET) {
in_addr domainSocketAddr = {};
if (packetData[0] == PACKET_TYPE_CREATE_ASSIGNMENT) {
// the domain server IP address is the address we got this packet from
domainSocketAddr = senderSocket.sin_addr;
} else {
// grab the domain server IP address from the packet from the AS
domainSocketAddr = ((sockaddr_in*) deployedAssignment.getAttachedPublicSocket())->sin_addr;
}
nodeList->setDomainIP(inet_ntoa(domainSocketAddr));
qDebug("Destination IP for assignment is %s\n", inet_ntoa(domainSocketAddr));
if (deployedAssignment.getType() == Assignment::AudioMixer) {
if (deployedAssignment.getType() == Assignment::AudioMixerType) {
AudioMixer::run();
} else {
} else if (deployedAssignment.getType() == Assignment::AvatarMixerType) {
AvatarMixer::run();
} else {
// figure out the URL for the script for this agent assignment
QString scriptURLString("http://%1:8080/assignment/%2");
scriptURLString = scriptURLString.arg(inet_ntoa(domainSocketAddr),
deployedAssignment.getUUIDStringWithoutCurlyBraces());
qDebug() << "Starting an Agent assignment-client with script at" << scriptURLString << "\n";
Agent scriptAgent;
scriptAgent.run(QUrl(scriptURLString));
}
} else {
qDebug("Received a bad destination socket for assignment.\n");
@ -157,6 +184,8 @@ void parentMonitor() {
int main(int argc, const char* argv[]) {
QCoreApplication app(argc, (char**) argv);
setvbuf(stdout, NULL, _IOLBF, 0);
// use the verbose message handler in Logging
@ -165,19 +194,23 @@ int main(int argc, const char* argv[]) {
// start the Logging class with the parent's target name
Logging::setTargetName(PARENT_TARGET_NAME);
const char CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION[] = "-a";
const char CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION[] = "-p";
// grab the overriden assignment-server hostname from argv, if it exists
const char* customAssignmentServer = getCmdOption(argc, argv, "-a");
if (customAssignmentServer) {
::customAssignmentSocket = socketForHostnameAndHostOrderPort(customAssignmentServer, ASSIGNMENT_SERVER_PORT);
const char* customAssignmentServerHostname = getCmdOption(argc, argv, CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION);
if (customAssignmentServerHostname) {
const char* customAssignmentServerPortString = getCmdOption(argc, argv, CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION);
unsigned short assignmentServerPort = customAssignmentServerPortString
? atoi(customAssignmentServerPortString) : ASSIGNMENT_SERVER_PORT;
::customAssignmentSocket = socketForHostnameAndHostOrderPort(customAssignmentServerHostname, assignmentServerPort);
}
const char* NUM_FORKS_PARAMETER = "-n";
const char* numForksString = getCmdOption(argc, argv, NUM_FORKS_PARAMETER);
// grab the assignment pool from argv, if it was passed
const char* ASSIGNMENT_POOL_PARAMETER = "-p";
::assignmentPool = getCmdOption(argc, argv, ASSIGNMENT_POOL_PARAMETER);
int processID = 0;
if (numForksString) {

View file

@ -8,11 +8,12 @@
#include <arpa/inet.h>
#include <fstream>
#include <queue>
#include <deque>
#include <QtCore/QString>
#include <Assignment.h>
#include <Logging.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <UDPSocket.h>
@ -22,6 +23,8 @@ const long long NUM_DEFAULT_ASSIGNMENT_STALENESS_USECS = 10 * 1000 * 1000;
int main(int argc, const char* argv[]) {
qInstallMessageHandler(Logging::verboseMessageHandler);
std::deque<Assignment*> assignmentQueue;
sockaddr_in senderSocket;
@ -39,8 +42,8 @@ int main(int argc, const char* argv[]) {
// construct the requested assignment from the packet data
Assignment requestAssignment(senderData, receivedBytes);
qDebug() << "Received request for assignment:" << requestAssignment;
qDebug() << "Current queue size is" << assignmentQueue.size();
qDebug() << "Received request for assignment:" << requestAssignment << "\n";
qDebug() << "Current queue size is" << assignmentQueue.size() << "\n";
// make sure there are assignments in the queue at all
if (assignmentQueue.size() > 0) {
@ -59,51 +62,38 @@ int main(int argc, const char* argv[]) {
continue;
}
bool eitherHasPool = ((*assignment)->getPool() || requestAssignment.getPool());
bool bothHavePool = ((*assignment)->getPool() && requestAssignment.getPool());
// make sure there is a pool match for the created and requested assignment
// or that neither has a designated pool
if ((eitherHasPool && bothHavePool
&& strcmp((*assignment)->getPool(), requestAssignment.getPool()) == 0)
|| !eitherHasPool) {
// check if the requestor is on the same network as the destination for the assignment
if (senderSocket.sin_addr.s_addr ==
((sockaddr_in*) (*assignment)->getAttachedPublicSocket())->sin_addr.s_addr) {
// if this is the case we remove the public socket on the assignment by setting it to NULL
// this ensures the local IP and port sent to the requestor is the local address of destination
(*assignment)->setAttachedPublicSocket(NULL);
}
int numAssignmentBytes = (*assignment)->packToBuffer(assignmentPacket + numSendHeaderBytes);
// send the assignment
serverSocket.send((sockaddr*) &senderSocket,
assignmentPacket,
numSendHeaderBytes + numAssignmentBytes);
// delete this assignment now that it has been sent out
delete *assignment;
// remove it from the deque and make the iterator the next assignment
assignmentQueue.erase(assignment);
// stop looping - we've handed out an assignment
break;
} else {
// push forward the iterator
assignment++;
// check if the requestor is on the same network as the destination for the assignment
if (senderSocket.sin_addr.s_addr ==
((sockaddr_in*) (*assignment)->getAttachedPublicSocket())->sin_addr.s_addr) {
// if this is the case we remove the public socket on the assignment by setting it to NULL
// this ensures the local IP and port sent to the requestor is the local address of destination
(*assignment)->setAttachedPublicSocket(NULL);
}
int numAssignmentBytes = (*assignment)->packToBuffer(assignmentPacket + numSendHeaderBytes);
// send the assignment
serverSocket.send((sockaddr*) &senderSocket,
assignmentPacket,
numSendHeaderBytes + numAssignmentBytes);
// delete this assignment now that it has been sent out
delete *assignment;
// remove it from the deque and make the iterator the next assignment
assignmentQueue.erase(assignment);
// stop looping - we've handed out an assignment
break;
}
}
} else if (senderData[0] == PACKET_TYPE_CREATE_ASSIGNMENT && packetVersionMatch(senderData)) {
// construct the create assignment from the packet data
Assignment* createdAssignment = new Assignment(senderData, receivedBytes);
qDebug() << "Received a created assignment:" << *createdAssignment;
qDebug() << "Current queue size is" << assignmentQueue.size();
qDebug() << "Received a created assignment:" << *createdAssignment << "\n";
qDebug() << "Current queue size is" << assignmentQueue.size() << "\n";
// assignment server is likely on a public server
// assume that the address we now have for the sender is the public address/port

View file

@ -8,6 +8,15 @@ set(TARGET_NAME domain-server)
include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
# remove and then copy the files for the webserver
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E remove_directory
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web)
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${PROJECT_SOURCE_DIR}/resources/web"
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/web)
# link the shared hifi library
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})

View file

@ -19,12 +19,15 @@
#include <arpa/inet.h>
#include <fcntl.h>
#include <deque>
#include <map>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <QtCore/QMutex>
#include "Assignment.h"
#include "NodeList.h"
#include "NodeTypes.h"
@ -32,11 +35,16 @@
#include "PacketHeaders.h"
#include "SharedUtil.h"
#include "mongoose.h"
const int DOMAIN_LISTEN_PORT = 40102;
unsigned char packetData[MAX_PACKET_SIZE];
const int NODE_COUNT_STAT_INTERVAL_MSECS = 5000;
QMutex assignmentQueueMutex;
std::deque<Assignment*> assignmentQueue;
unsigned char* addNodeToBroadcastPacket(unsigned char* currentPosition, Node* nodeToAdd) {
*currentPosition++ = nodeToAdd->getType();
@ -48,22 +56,51 @@ unsigned char* addNodeToBroadcastPacket(unsigned char* currentPosition, Node* no
return currentPosition;
}
static int mongooseRequestHandler(struct mg_connection *conn) {
const struct mg_request_info *ri = mg_get_request_info(conn);
if (strcmp(ri->uri, "/assignment") == 0 && strcmp(ri->request_method, "POST") == 0) {
// return a 200
mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
// upload the file
mg_upload(conn, "/tmp");
return 1;
} else {
// have mongoose process this request from the document_root
return 0;
}
}
const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment";
static void mongooseUploadHandler(struct mg_connection *conn, const char *path) {
// create an assignment for this saved script, for now make it local only
Assignment *scriptAssignment = new Assignment(Assignment::CreateCommand, Assignment::AgentType, Assignment::LocalLocation);
QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION);
newPath += "/";
// append the UUID for this script as the new filename, remove the curly braces
newPath += scriptAssignment->getUUIDStringWithoutCurlyBraces();
// rename the saved script to the GUID of the assignment and move it to the script host locaiton
rename(path, newPath.toStdString().c_str());
qDebug("Saved a script for assignment at %s\n", newPath.toStdString().c_str());
// add the script assigment to the assignment queue
// lock the assignment queue mutex since we're operating on a different thread than DS main
::assignmentQueueMutex.lock();
::assignmentQueue.push_back(scriptAssignment);
::assignmentQueueMutex.unlock();
}
int main(int argc, const char* argv[]) {
qInstallMessageHandler(Logging::verboseMessageHandler);
NodeList* nodeList = NodeList::createInstance(NODE_TYPE_DOMAIN, DOMAIN_LISTEN_PORT);
// If user asks to run in "local" mode then we do NOT replace the IP
// with the EC2 IP. Otherwise, we will replace the IP like we used to
// this allows developers to run a local domain without recompiling the
// domain server
bool isLocalMode = cmdOptionExists(argc, (const char**) argv, "--local");
if (isLocalMode) {
printf("NOTE: Running in local mode!\n");
} else {
printf("--------------------------------------------------\n");
printf("NOTE: Not running in local mode. \n");
printf("If you're a developer testing a local system, you\n");
printf("probably want to include --local on command line.\n");
printf("--------------------------------------------------\n");
}
setvbuf(stdout, NULL, _IOLBF, 0);
@ -71,12 +108,11 @@ int main(int argc, const char* argv[]) {
char nodeType = '\0';
unsigned char broadcastPacket[MAX_PACKET_SIZE];
int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_DOMAIN);
unsigned char* currentBufferPos;
unsigned char* startPointer;
sockaddr_in nodePublicAddress, nodeLocalAddress;
sockaddr_in nodePublicAddress, nodeLocalAddress, replyDestinationSocket;
nodeLocalAddress.sin_family = AF_INET;
in_addr_t serverLocalAddress = getLocalAddress();
@ -84,13 +120,8 @@ int main(int argc, const char* argv[]) {
nodeList->startSilentNodeRemovalThread();
timeval lastStatSendTime = {};
const char ASSIGNMENT_POOL_OPTION[] = "-p";
const char ASSIGNMENT_SERVER_OPTION[] = "-a";
// set our assignment pool from argv, if it exists
const char* assignmentPool = getCmdOption(argc, argv, ASSIGNMENT_POOL_OPTION);
// grab the overriden assignment-server hostname from argv, if it exists
const char* customAssignmentServer = getCmdOption(argc, argv, ASSIGNMENT_SERVER_OPTION);
if (customAssignmentServer) {
@ -99,149 +130,224 @@ int main(int argc, const char* argv[]) {
}
// use a map to keep track of iterations of silence for assignment creation requests
const long long ASSIGNMENT_SILENCE_MAX_USECS = 5 * 1000 * 1000;
const long long GLOBAL_ASSIGNMENT_REQUEST_INTERVAL_USECS = 1 * 1000 * 1000;
timeval lastGlobalAssignmentRequest = {};
// as a domain-server we will always want an audio mixer and avatar mixer
// setup the create assignment pointers for those
Assignment* audioAssignment = NULL;
Assignment* avatarAssignment = NULL;
// setup the create assignments for those
Assignment audioMixerAssignment(Assignment::CreateCommand,
Assignment::AudioMixerType,
Assignment::LocalLocation);
// construct a local socket to send with our created assignments
Assignment avatarMixerAssignment(Assignment::CreateCommand,
Assignment::AvatarMixerType,
Assignment::LocalLocation);
// construct a local socket to send with our created assignments to the global AS
sockaddr_in localSocket = {};
localSocket.sin_family = AF_INET;
localSocket.sin_port = htons(nodeList->getInstance()->getNodeSocket()->getListeningPort());
localSocket.sin_addr.s_addr = serverLocalAddress;
// setup the mongoose web server
struct mg_context *ctx;
struct mg_callbacks callbacks = {};
// list of options. Last element must be NULL.
const char *options[] = {"listening_ports", "8080",
"document_root", "./resources/web", NULL};
callbacks.begin_request = mongooseRequestHandler;
callbacks.upload = mongooseUploadHandler;
// Start the web server.
ctx = mg_start(&callbacks, NULL, options);
while (true) {
if (!nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER)) {
if (!audioAssignment
|| usecTimestampNow() - usecTimestamp(&audioAssignment->getTime()) >= ASSIGNMENT_SILENCE_MAX_USECS) {
if (!audioAssignment) {
audioAssignment = new Assignment(Assignment::Create, Assignment::AudioMixer, assignmentPool);
audioAssignment->setAttachedLocalSocket((sockaddr*) &localSocket);
}
nodeList->sendAssignment(*audioAssignment);
audioAssignment->setCreateTimeToNow();
}
::assignmentQueueMutex.lock();
// check if our audio-mixer or avatar-mixer are dead and we don't have existing assignments in the queue
// so we can add those assignments back to the front of the queue since they are high-priority
if (!nodeList->soloNodeOfType(NODE_TYPE_AVATAR_MIXER) &&
std::find(::assignmentQueue.begin(), assignmentQueue.end(), &avatarMixerAssignment) == ::assignmentQueue.end()) {
qDebug("Missing an avatar mixer and assignment not in queue. Adding.\n");
::assignmentQueue.push_front(&avatarMixerAssignment);
}
if (!nodeList->soloNodeOfType(NODE_TYPE_AVATAR_MIXER)) {
if (!avatarAssignment
|| usecTimestampNow() - usecTimestamp(&avatarAssignment->getTime()) >= ASSIGNMENT_SILENCE_MAX_USECS) {
if (!avatarAssignment) {
avatarAssignment = new Assignment(Assignment::Create, Assignment::AvatarMixer, assignmentPool);
avatarAssignment->setAttachedLocalSocket((sockaddr*) &localSocket);
}
nodeList->sendAssignment(*avatarAssignment);
// reset the create time on the assignment so re-request is in ASSIGNMENT_SILENCE_MAX_USECS
avatarAssignment->setCreateTimeToNow();
}
if (!nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER) &&
std::find(::assignmentQueue.begin(), ::assignmentQueue.end(), &audioMixerAssignment) == ::assignmentQueue.end()) {
qDebug("Missing an audio mixer and assignment not in queue. Adding.\n");
::assignmentQueue.push_front(&audioMixerAssignment);
}
::assignmentQueueMutex.unlock();
if (nodeList->getNodeSocket()->receive((sockaddr *)&nodePublicAddress, packetData, &receivedBytes) &&
(packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY || packetData[0] == PACKET_TYPE_DOMAIN_LIST_REQUEST) &&
packetVersionMatch(packetData)) {
// this is an RFD or domain list request packet, and there is a version match
std::map<char, Node *> newestSoloNodes;
int numBytesSenderHeader = numBytesForPacketHeader(packetData);
nodeType = *(packetData + numBytesSenderHeader);
int numBytesSocket = unpackSocket(packetData + numBytesSenderHeader + sizeof(NODE_TYPE),
(sockaddr*) &nodeLocalAddress);
sockaddr* destinationSocket = (sockaddr*) &nodePublicAddress;
// check the node public address
// if it matches our local address we're on the same box
// so hardcode the EC2 public address for now
if (nodePublicAddress.sin_addr.s_addr == serverLocalAddress) {
// If we're not running "local" then we do replace the IP
// with 0. This designates to clients that the server is reachable
// at the same IP address
if (!isLocalMode) {
nodePublicAddress.sin_addr.s_addr = 0;
destinationSocket = (sockaddr*) &nodeLocalAddress;
}
}
Node* newNode = nodeList->addOrUpdateNode((sockaddr*) &nodePublicAddress,
(sockaddr*) &nodeLocalAddress,
nodeType,
nodeList->getLastNodeID());
// if addOrUpdateNode returns NULL this was a solo node we already have, don't talk back to it
if (newNode) {
if (newNode->getNodeID() == nodeList->getLastNodeID()) {
nodeList->increaseNodeID();
while (nodeList->getNodeSocket()->receive((sockaddr *)&nodePublicAddress, packetData, &receivedBytes) &&
packetVersionMatch(packetData)) {
if (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY || packetData[0] == PACKET_TYPE_DOMAIN_LIST_REQUEST) {
// this is an RFD or domain list request packet, and there is a version match
std::map<char, Node *> newestSoloNodes;
int numBytesSenderHeader = numBytesForPacketHeader(packetData);
nodeType = *(packetData + numBytesSenderHeader);
int numBytesSocket = unpackSocket(packetData + numBytesSenderHeader + sizeof(NODE_TYPE),
(sockaddr*) &nodeLocalAddress);
replyDestinationSocket = nodePublicAddress;
// check the node public address
// if it matches our local address
// or if it's the loopback address we're on the same box
if (nodePublicAddress.sin_addr.s_addr == serverLocalAddress ||
nodePublicAddress.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
nodePublicAddress.sin_addr.s_addr = 0;
}
currentBufferPos = broadcastPacket + numHeaderBytes;
startPointer = currentBufferPos;
Node* newNode = nodeList->addOrUpdateNode((sockaddr*) &nodePublicAddress,
(sockaddr*) &nodeLocalAddress,
nodeType,
nodeList->getLastNodeID());
unsigned char* nodeTypesOfInterest = packetData + numBytesSenderHeader + sizeof(NODE_TYPE)
+ numBytesSocket + sizeof(unsigned char);
int numInterestTypes = *(nodeTypesOfInterest - 1);
if (numInterestTypes > 0) {
// if the node has sent no types of interest, assume they want nothing but their own ID back
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
if (!node->matches((sockaddr*) &nodePublicAddress, (sockaddr*) &nodeLocalAddress, nodeType) &&
memchr(nodeTypesOfInterest, node->getType(), numInterestTypes)) {
// this is not the node themselves
// and this is an node of a type in the passed node types of interest
// or the node did not pass us any specific types they are interested in
if (memchr(SOLO_NODE_TYPES, node->getType(), sizeof(SOLO_NODE_TYPES)) == NULL) {
// this is an node of which there can be multiple, just add them to the packet
// don't send avatar nodes to other avatars, that will come from avatar mixer
if (nodeType != NODE_TYPE_AGENT || node->getType() != NODE_TYPE_AGENT) {
currentBufferPos = addNodeToBroadcastPacket(currentBufferPos, &(*node));
}
// if addOrUpdateNode returns NULL this was a solo node we already have, don't talk back to it
if (newNode) {
if (newNode->getNodeID() == nodeList->getLastNodeID()) {
nodeList->increaseNodeID();
}
int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_DOMAIN);
currentBufferPos = broadcastPacket + numHeaderBytes;
startPointer = currentBufferPos;
unsigned char* nodeTypesOfInterest = packetData + numBytesSenderHeader + sizeof(NODE_TYPE)
+ numBytesSocket + sizeof(unsigned char);
int numInterestTypes = *(nodeTypesOfInterest - 1);
if (numInterestTypes > 0) {
// if the node has sent no types of interest, assume they want nothing but their own ID back
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
if (!node->matches((sockaddr*) &nodePublicAddress, (sockaddr*) &nodeLocalAddress, nodeType) &&
memchr(nodeTypesOfInterest, node->getType(), numInterestTypes)) {
// this is not the node themselves
// and this is an node of a type in the passed node types of interest
// or the node did not pass us any specific types they are interested in
} else {
// solo node, we need to only send newest
if (newestSoloNodes[node->getType()] == NULL ||
newestSoloNodes[node->getType()]->getWakeMicrostamp() < node->getWakeMicrostamp()) {
// we have to set the newer solo node to add it to the broadcast later
newestSoloNodes[node->getType()] = &(*node);
if (memchr(SOLO_NODE_TYPES, node->getType(), sizeof(SOLO_NODE_TYPES)) == NULL) {
// this is an node of which there can be multiple, just add them to the packet
// don't send avatar nodes to other avatars, that will come from avatar mixer
if (nodeType != NODE_TYPE_AGENT || node->getType() != NODE_TYPE_AGENT) {
currentBufferPos = addNodeToBroadcastPacket(currentBufferPos, &(*node));
}
} else {
// solo node, we need to only send newest
if (newestSoloNodes[node->getType()] == NULL ||
newestSoloNodes[node->getType()]->getWakeMicrostamp() < node->getWakeMicrostamp()) {
// we have to set the newer solo node to add it to the broadcast later
newestSoloNodes[node->getType()] = &(*node);
}
}
}
}
for (std::map<char, Node *>::iterator soloNode = newestSoloNodes.begin();
soloNode != newestSoloNodes.end();
soloNode++) {
// this is the newest alive solo node, add them to the packet
currentBufferPos = addNodeToBroadcastPacket(currentBufferPos, soloNode->second);
}
}
for (std::map<char, Node *>::iterator soloNode = newestSoloNodes.begin();
soloNode != newestSoloNodes.end();
soloNode++) {
// this is the newest alive solo node, add them to the packet
currentBufferPos = addNodeToBroadcastPacket(currentBufferPos, soloNode->second);
// update last receive to now
uint64_t timeNow = usecTimestampNow();
newNode->setLastHeardMicrostamp(timeNow);
if (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY
&& memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) {
newNode->setWakeMicrostamp(timeNow);
}
// add the node ID to the end of the pointer
currentBufferPos += packNodeId(currentBufferPos, newNode->getNodeID());
// send the constructed list back to this node
nodeList->getNodeSocket()->send((sockaddr*)&replyDestinationSocket,
broadcastPacket,
(currentBufferPos - startPointer) + numHeaderBytes);
}
} else if (packetData[0] == PACKET_TYPE_REQUEST_ASSIGNMENT) {
qDebug("Received a request for assignment.\n");
::assignmentQueueMutex.lock();
// this is an unassigned client talking to us directly for an assignment
// go through our queue and see if there are any assignments to give out
std::deque<Assignment*>::iterator assignment = ::assignmentQueue.begin();
while (assignment != ::assignmentQueue.end()) {
// give this assignment out, no conditions stop us from giving it to the local assignment client
int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_CREATE_ASSIGNMENT);
int numAssignmentBytes = (*assignment)->packToBuffer(broadcastPacket + numHeaderBytes);
nodeList->getNodeSocket()->send((sockaddr*) &nodePublicAddress,
broadcastPacket,
numHeaderBytes + numAssignmentBytes);
// remove the assignment from the queue
::assignmentQueue.erase(assignment);
if ((*assignment)->getType() == Assignment::AgentType) {
// if this is a script assignment we need to delete it to avoid a memory leak
delete *assignment;
}
// stop looping, we've handed out an assignment
break;
}
// update last receive to now
uint64_t timeNow = usecTimestampNow();
newNode->setLastHeardMicrostamp(timeNow);
if (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY
&& memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) {
newNode->setWakeMicrostamp(timeNow);
}
// add the node ID to the end of the pointer
currentBufferPos += packNodeId(currentBufferPos, newNode->getNodeID());
// send the constructed list back to this node
nodeList->getNodeSocket()->send(destinationSocket,
broadcastPacket,
(currentBufferPos - startPointer) + numHeaderBytes);
::assignmentQueueMutex.unlock();
}
}
// if ASSIGNMENT_REQUEST_INTERVAL_USECS have passed since last global assignment request then fire off another
if (usecTimestampNow() - usecTimestamp(&lastGlobalAssignmentRequest) >= GLOBAL_ASSIGNMENT_REQUEST_INTERVAL_USECS) {
gettimeofday(&lastGlobalAssignmentRequest, NULL);
::assignmentQueueMutex.lock();
// go through our queue and see if there are any assignments to send to the global assignment server
std::deque<Assignment*>::iterator assignment = ::assignmentQueue.begin();
while (assignment != assignmentQueue.end()) {
if ((*assignment)->getLocation() != Assignment::LocalLocation) {
// attach our local socket to the assignment so the assignment-server can optionally hand it out
(*assignment)->setAttachedLocalSocket((sockaddr*) &localSocket);
nodeList->sendAssignment(*(*assignment));
// remove the assignment from the queue
::assignmentQueue.erase(assignment);
if ((*assignment)->getType() == Assignment::AgentType) {
// if this is a script assignment we need to delete it to avoid a memory leak
delete *assignment;
}
// stop looping, we've handed out an assignment
break;
} else {
// push forward the iterator to check the next assignment
assignment++;
}
}
::assignmentQueueMutex.unlock();
}
if (Logging::shouldSendStats()) {
if (usecTimestampNow() - usecTimestamp(&lastStatSendTime) >= (NODE_COUNT_STAT_INTERVAL_MSECS * 1000)) {
// time to send our count of nodes and servers to logstash
@ -253,9 +359,6 @@ int main(int argc, const char* argv[]) {
}
}
}
delete audioAssignment;
delete avatarAssignment;
return 0;
}

5383
domain-server/src/mongoose.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,384 @@
// Copyright (c) 2004-2012 Sergey Lyubka
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_HEADER_INCLUDED
#include <stdio.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
struct mg_context; // Handle for the HTTP service itself
struct mg_connection; // Handle for the individual connection
// This structure contains information about the HTTP request.
struct mg_request_info {
const char *request_method; // "GET", "POST", etc
const char *uri; // URL-decoded URI
const char *http_version; // E.g. "1.0", "1.1"
const char *query_string; // URL part after '?', not including '?', or NULL
const char *remote_user; // Authenticated user, or NULL if no auth used
long remote_ip; // Client's IP address
int remote_port; // Client's port
int is_ssl; // 1 if SSL-ed, 0 if not
void *user_data; // User data pointer passed to mg_start()
int num_headers; // Number of HTTP headers
struct mg_header {
const char *name; // HTTP header name
const char *value; // HTTP header value
} http_headers[64]; // Maximum 64 headers
};
// This structure needs to be passed to mg_start(), to let mongoose know
// which callbacks to invoke. For detailed description, see
// https://github.com/valenok/mongoose/blob/master/UserManual.md
struct mg_callbacks {
// Called when mongoose has received new HTTP request.
// If callback returns non-zero,
// callback must process the request by sending valid HTTP headers and body,
// and mongoose will not do any further processing.
// If callback returns 0, mongoose processes the request itself. In this case,
// callback must not send any data to the client.
int (*begin_request)(struct mg_connection *);
// Called when mongoose has finished processing request.
void (*end_request)(const struct mg_connection *, int reply_status_code);
// Called when mongoose is about to log a message. If callback returns
// non-zero, mongoose does not log anything.
int (*log_message)(const struct mg_connection *, const char *message);
// Called when mongoose initializes SSL library.
int (*init_ssl)(void *ssl_context, void *user_data);
// Called when websocket request is received, before websocket handshake.
// If callback returns 0, mongoose proceeds with handshake, otherwise
// cinnection is closed immediately.
int (*websocket_connect)(const struct mg_connection *);
// Called when websocket handshake is successfully completed, and
// connection is ready for data exchange.
void (*websocket_ready)(struct mg_connection *);
// Called when data frame has been received from the client.
// Parameters:
// bits: first byte of the websocket frame, see websocket RFC at
// http://tools.ietf.org/html/rfc6455, section 5.2
// data, data_len: payload, with mask (if any) already applied.
// Return value:
// non-0: keep this websocket connection opened.
// 0: close this websocket connection.
int (*websocket_data)(struct mg_connection *, int bits,
char *data, size_t data_len);
// Called when mongoose tries to open a file. Used to intercept file open
// calls, and serve file data from memory instead.
// Parameters:
// path: Full path to the file to open.
// data_len: Placeholder for the file size, if file is served from memory.
// Return value:
// NULL: do not serve file from memory, proceed with normal file open.
// non-NULL: pointer to the file contents in memory. data_len must be
// initilized with the size of the memory block.
const char * (*open_file)(const struct mg_connection *,
const char *path, size_t *data_len);
// Called when mongoose is about to serve Lua server page (.lp file), if
// Lua support is enabled.
// Parameters:
// lua_context: "lua_State *" pointer.
void (*init_lua)(struct mg_connection *, void *lua_context);
// Called when mongoose has uploaded a file to a temporary directory as a
// result of mg_upload() call.
// Parameters:
// file_file: full path name to the uploaded file.
void (*upload)(struct mg_connection *, const char *file_name);
// Called when mongoose is about to send HTTP error to the client.
// Implementing this callback allows to create custom error pages.
// Parameters:
// status: HTTP error status code.
int (*http_error)(struct mg_connection *, int status);
};
// Start web server.
//
// Parameters:
// callbacks: mg_callbacks structure with user-defined callbacks.
// options: NULL terminated list of option_name, option_value pairs that
// specify Mongoose configuration parameters.
//
// Side-effects: on UNIX, ignores SIGCHLD and SIGPIPE signals. If custom
// processing is required for these, signal handlers must be set up
// after calling mg_start().
//
//
// Example:
// const char *options[] = {
// "document_root", "/var/www",
// "listening_ports", "80,443s",
// NULL
// };
// struct mg_context *ctx = mg_start(&my_func, NULL, options);
//
// Refer to https://github.com/valenok/mongoose/blob/master/UserManual.md
// for the list of valid option and their possible values.
//
// Return:
// web server context, or NULL on error.
struct mg_context *mg_start(const struct mg_callbacks *callbacks,
void *user_data,
const char **configuration_options);
// Stop the web server.
//
// Must be called last, when an application wants to stop the web server and
// release all associated resources. This function blocks until all Mongoose
// threads are stopped. Context pointer becomes invalid.
void mg_stop(struct mg_context *);
// Get the value of particular configuration parameter.
// The value returned is read-only. Mongoose does not allow changing
// configuration at run time.
// If given parameter name is not valid, NULL is returned. For valid
// names, return value is guaranteed to be non-NULL. If parameter is not
// set, zero-length string is returned.
const char *mg_get_option(const struct mg_context *ctx, const char *name);
// Return array of strings that represent valid configuration options.
// For each option, a short name, long name, and default value is returned.
// Array is NULL terminated.
const char **mg_get_valid_option_names(void);
// Add, edit or delete the entry in the passwords file.
//
// This function allows an application to manipulate .htpasswd files on the
// fly by adding, deleting and changing user records. This is one of the
// several ways of implementing authentication on the server side. For another,
// cookie-based way please refer to the examples/chat.c in the source tree.
//
// If password is not NULL, entry is added (or modified if already exists).
// If password is NULL, entry is deleted.
//
// Return:
// 1 on success, 0 on error.
int mg_modify_passwords_file(const char *passwords_file_name,
const char *domain,
const char *user,
const char *password);
// Return information associated with the request.
struct mg_request_info *mg_get_request_info(struct mg_connection *);
// Send data to the client.
// Return:
// 0 when the connection has been closed
// -1 on error
// >0 number of bytes written on success
int mg_write(struct mg_connection *, const void *buf, size_t len);
// Send data to a websocket client wrapped in a websocket frame.
// It is unsafe to read/write to this connection from another thread.
// This function is available when mongoose is compiled with -DUSE_WEBSOCKET
//
// Return:
// 0 when the connection has been closed
// -1 on error
// >0 number of bytes written on success
int mg_websocket_write(struct mg_connection* conn, int opcode,
const char *data, size_t data_len);
// Opcodes, from http://tools.ietf.org/html/rfc6455
enum {
WEBSOCKET_OPCODE_CONTINUATION = 0x0,
WEBSOCKET_OPCODE_TEXT = 0x1,
WEBSOCKET_OPCODE_BINARY = 0x2,
WEBSOCKET_OPCODE_CONNECTION_CLOSE = 0x8,
WEBSOCKET_OPCODE_PING = 0x9,
WEBSOCKET_OPCODE_PONG = 0xa
};
// Macros for enabling compiler-specific checks for printf-like arguments.
#undef PRINTF_FORMAT_STRING
#if _MSC_VER >= 1400
#include <sal.h>
#if _MSC_VER > 1400
#define PRINTF_FORMAT_STRING(s) _Printf_format_string_ s
#else
#define PRINTF_FORMAT_STRING(s) __format_string s
#endif
#else
#define PRINTF_FORMAT_STRING(s) s
#endif
#ifdef __GNUC__
#define PRINTF_ARGS(x, y) __attribute__((format(printf, x, y)))
#else
#define PRINTF_ARGS(x, y)
#endif
// Send data to the client using printf() semantics.
//
// Works exactly like mg_write(), but allows to do message formatting.
int mg_printf(struct mg_connection *,
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
// Send contents of the entire file together with HTTP headers.
void mg_send_file(struct mg_connection *conn, const char *path);
// Read data from the remote end, return number of bytes read.
// Return:
// 0 connection has been closed by peer. No more data could be read.
// < 0 read error. No more data could be read from the connection.
// > 0 number of bytes read into the buffer.
int mg_read(struct mg_connection *, void *buf, size_t len);
// Get the value of particular HTTP header.
//
// This is a helper function. It traverses request_info->http_headers array,
// and if the header is present in the array, returns its value. If it is
// not present, NULL is returned.
const char *mg_get_header(const struct mg_connection *, const char *name);
// Get a value of particular form variable.
//
// Parameters:
// data: pointer to form-uri-encoded buffer. This could be either POST data,
// or request_info.query_string.
// data_len: length of the encoded data.
// var_name: variable name to decode from the buffer
// dst: destination buffer for the decoded variable
// dst_len: length of the destination buffer
//
// Return:
// On success, length of the decoded variable.
// On error:
// -1 (variable not found).
// -2 (destination buffer is NULL, zero length or too small to hold the
// decoded variable).
//
// Destination buffer is guaranteed to be '\0' - terminated if it is not
// NULL or zero length.
int mg_get_var(const char *data, size_t data_len,
const char *var_name, char *dst, size_t dst_len);
// Fetch value of certain cookie variable into the destination buffer.
//
// Destination buffer is guaranteed to be '\0' - terminated. In case of
// failure, dst[0] == '\0'. Note that RFC allows many occurrences of the same
// parameter. This function returns only first occurrence.
//
// Return:
// On success, value length.
// On error:
// -1 (either "Cookie:" header is not present at all or the requested
// parameter is not found).
// -2 (destination buffer is NULL, zero length or too small to hold the
// value).
int mg_get_cookie(const char *cookie, const char *var_name,
char *buf, size_t buf_len);
// Download data from the remote web server.
// host: host name to connect to, e.g. "foo.com", or "10.12.40.1".
// port: port number, e.g. 80.
// use_ssl: wether to use SSL connection.
// error_buffer, error_buffer_size: error message placeholder.
// request_fmt,...: HTTP request.
// Return:
// On success, valid pointer to the new connection, suitable for mg_read().
// On error, NULL. error_buffer contains error message.
// Example:
// char ebuf[100];
// struct mg_connection *conn;
// conn = mg_download("google.com", 80, 0, ebuf, sizeof(ebuf),
// "%s", "GET / HTTP/1.0\r\nHost: google.com\r\n\r\n");
struct mg_connection *mg_download(const char *host, int port, int use_ssl,
char *error_buffer, size_t error_buffer_size,
PRINTF_FORMAT_STRING(const char *request_fmt),
...) PRINTF_ARGS(6, 7);
// Close the connection opened by mg_download().
void mg_close_connection(struct mg_connection *conn);
// File upload functionality. Each uploaded file gets saved into a temporary
// file and MG_UPLOAD event is sent.
// Return number of uploaded files.
int mg_upload(struct mg_connection *conn, const char *destination_dir);
// Convenience function -- create detached thread.
// Return: 0 on success, non-0 on error.
typedef void * (*mg_thread_func_t)(void *);
int mg_start_thread(mg_thread_func_t f, void *p);
// Return builtin mime type for the given file name.
// For unrecognized extensions, "text/plain" is returned.
const char *mg_get_builtin_mime_type(const char *file_name);
// Return Mongoose version.
const char *mg_version(void);
// URL-decode input buffer into destination buffer.
// 0-terminate the destination buffer.
// form-url-encoded data differs from URI encoding in a way that it
// uses '+' as character for space, see RFC 1866 section 8.2.1
// http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt
// Return: length of the decoded data, or -1 if dst buffer is too small.
int mg_url_decode(const char *src, int src_len, char *dst,
int dst_len, int is_form_url_encoded);
// MD5 hash given strings.
// Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of
// ASCIIz strings. When function returns, buf will contain human-readable
// MD5 hash. Example:
// char buf[33];
// mg_md5(buf, "aa", "bb", NULL);
char *mg_md5(char buf[33], ...);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // MONGOOSE_HEADER_INCLUDED

View file

@ -52,6 +52,7 @@ find_package(Qt5Multimedia REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5OpenGL REQUIRED)
find_package(Qt5Svg REQUIRED)
find_package(Qt5WebKitWidgets REQUIRED)
if (APPLE)
set(MACOSX_BUNDLE_BUNDLE_NAME Interface)

File diff suppressed because one or more lines are too long

View file

@ -65,6 +65,7 @@
#include "devices/OculusManager.h"
#include "renderer/ProgramObject.h"
#include "ui/TextRenderer.h"
#include "InfoView.h"
using namespace std;
@ -111,6 +112,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_isHoverVoxelSounding(false),
_mouseVoxelScale(1.0f / 1024.0f),
_justEditedVoxel(false),
_nudgeStarted(false),
_lookingAlongX(false),
_lookingAwayFromOrigin(true),
_isLookingAtOtherAvatar(false),
_lookatIndicatorScale(1.0f),
_perfStatsOn(false),
@ -128,8 +132,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_bytesPerSecond(0),
_bytesCount(0),
_swatch(NULL),
_pasteMode(false),
_finishedNudge(true)
_pasteMode(false)
{
_applicationStartupTime = startup_time;
_window->setWindowTitle("Interface");
@ -329,6 +332,8 @@ void Application::initializeGL() {
#if defined(Q_OS_MAC) && defined(QT_NO_DEBUG)
Menu::getInstance()->checkForUpdates();
#endif
InfoView::showFirstTime();
}
void Application::paintGL() {
@ -535,27 +540,63 @@ void Application::keyPressEvent(QKeyEvent* event) {
break;
case Qt::Key_E:
if (!_myAvatar.getDriveKeys(UP)) {
_myAvatar.jump();
if (_nudgeStarted) {
_nudgeGuidePosition.y += _mouseVoxel.s;
} else {
if (!_myAvatar.getDriveKeys(UP)) {
_myAvatar.jump();
}
_myAvatar.setDriveKeys(UP, 1);
}
_myAvatar.setDriveKeys(UP, 1);
break;
case Qt::Key_C:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::OcclusionCulling);
} else if (_nudgeStarted) {
_nudgeGuidePosition.y -= _mouseVoxel.s;
} else {
_myAvatar.setDriveKeys(DOWN, 1);
}
break;
case Qt::Key_W:
_myAvatar.setDriveKeys(FWD, 1);
if (_nudgeStarted) {
if (_lookingAlongX) {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.x += _mouseVoxel.s;
} else {
_nudgeGuidePosition.x -= _mouseVoxel.s;
}
} else {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.z += _mouseVoxel.s;
} else {
_nudgeGuidePosition.z -= _mouseVoxel.s;
}
}
} else {
_myAvatar.setDriveKeys(FWD, 1);
}
break;
case Qt::Key_S:
if (isShifted) {
_voxels.collectStatsForTreesAndVBOs();
} else if (_nudgeStarted) {
if (_lookingAlongX) {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.x -= _mouseVoxel.s;
} else {
_nudgeGuidePosition.x += _mouseVoxel.s;
}
} else {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.z -= _mouseVoxel.s;
} else {
_nudgeGuidePosition.z += _mouseVoxel.s;
}
}
} else {
_myAvatar.setDriveKeys(BACK, 1);
}
@ -577,37 +618,139 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_A:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::Atmosphere);
} else if (_nudgeStarted) {
if (_lookingAlongX) {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.z -= _mouseVoxel.s;
} else {
_nudgeGuidePosition.z += _mouseVoxel.s;
}
} else {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.x += _mouseVoxel.s;
} else {
_nudgeGuidePosition.x -= _mouseVoxel.s;
}
}
} else {
_myAvatar.setDriveKeys(ROT_LEFT, 1);
}
break;
case Qt::Key_D:
_myAvatar.setDriveKeys(ROT_RIGHT, 1);
if (_nudgeStarted) {
if (_lookingAlongX) {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.z += _mouseVoxel.s;
} else {
_nudgeGuidePosition.z -= _mouseVoxel.s;
}
} else {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.x -= _mouseVoxel.s;
} else {
_nudgeGuidePosition.x += _mouseVoxel.s;
}
}
} else {
_myAvatar.setDriveKeys(ROT_RIGHT, 1);
}
break;
case Qt::Key_Return:
case Qt::Key_Enter:
_chatEntryOn = true;
_myAvatar.setKeyState(NO_KEY_DOWN);
_myAvatar.setChatMessage(string());
setMenuShortcutsEnabled(false);
if (_nudgeStarted) {
nudgeVoxels();
} else {
_chatEntryOn = true;
_myAvatar.setKeyState(NO_KEY_DOWN);
_myAvatar.setChatMessage(string());
setMenuShortcutsEnabled(false);
}
break;
case Qt::Key_Up:
_myAvatar.setDriveKeys(isShifted ? UP : FWD, 1);
if (_nudgeStarted && !isShifted) {
if (_lookingAlongX) {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.x += _mouseVoxel.s;
} else {
_nudgeGuidePosition.x -= _mouseVoxel.s;
}
} else {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.z += _mouseVoxel.s;
} else {
_nudgeGuidePosition.z -= _mouseVoxel.s;
}
}
} else if (_nudgeStarted && isShifted) {
_nudgeGuidePosition.y += _mouseVoxel.s;
} else {
_myAvatar.setDriveKeys(isShifted ? UP : FWD, 1);
}
break;
case Qt::Key_Down:
_myAvatar.setDriveKeys(isShifted ? DOWN : BACK, 1);
if (_nudgeStarted && !isShifted) {
if (_lookingAlongX) {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.x -= _mouseVoxel.s;
} else {
_nudgeGuidePosition.x += _mouseVoxel.s;
}
} else {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.z -= _mouseVoxel.s;
} else {
_nudgeGuidePosition.z += _mouseVoxel.s;
}
}
} else if (_nudgeStarted && isShifted) {
_nudgeGuidePosition.y -= _mouseVoxel.s;
} else {
_myAvatar.setDriveKeys(isShifted ? DOWN : BACK, 1);
}
break;
case Qt::Key_Left:
_myAvatar.setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1);
if (_nudgeStarted) {
if (_lookingAlongX) {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.z -= _mouseVoxel.s;
} else {
_nudgeGuidePosition.z += _mouseVoxel.s;
}
} else {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.x += _mouseVoxel.s;
} else {
_nudgeGuidePosition.x -= _mouseVoxel.s;
}
}
} else {
_myAvatar.setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1);
}
break;
case Qt::Key_Right:
_myAvatar.setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1);
if (_nudgeStarted) {
if (_lookingAlongX) {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.z += _mouseVoxel.s;
} else {
_nudgeGuidePosition.z -= _mouseVoxel.s;
}
} else {
if (_lookingAwayFromOrigin) {
_nudgeGuidePosition.x -= _mouseVoxel.s;
} else {
_nudgeGuidePosition.x += _mouseVoxel.s;
}
}
} else {
_myAvatar.setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1);
}
break;
case Qt::Key_I:
@ -684,6 +827,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
Menu::getInstance()->triggerOption(MenuOption::Voxels);
} else {
Menu::getInstance()->triggerOption(MenuOption::VoxelAddMode);
_nudgeStarted = false;
}
break;
case Qt::Key_P:
@ -694,23 +838,24 @@ void Application::keyPressEvent(QKeyEvent* event) {
Menu::getInstance()->triggerOption(MenuOption::FrustumRenderMode);
} else {
Menu::getInstance()->triggerOption(MenuOption::VoxelDeleteMode);
_nudgeStarted = false;
}
break;
case Qt::Key_B:
Menu::getInstance()->triggerOption(MenuOption::VoxelColorMode);
_nudgeStarted = false;
break;
case Qt::Key_O:
Menu::getInstance()->triggerOption(MenuOption::VoxelSelectMode);
break;
case Qt::Key_N:
Menu::getInstance()->triggerOption(MenuOption::VoxelNudgeMode);
_nudgeStarted = false;
break;
case Qt::Key_Slash:
Menu::getInstance()->triggerOption(MenuOption::Stats);
break;
case Qt::Key_Backspace:
case Qt::Key_Delete:
if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelDeleteMode)) {
if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelDeleteMode) ||
Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) {
deleteVoxelUnderCursor();
}
break;
@ -845,14 +990,6 @@ void Application::mousePressEvent(QMouseEvent* event) {
pasteVoxels();
}
if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) {
VoxelNode* clickedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s);
if (clickedNode) {
_nudgeVoxel = _mouseVoxel;
_finishedNudge = false;
}
}
if (MAKE_SOUND_ON_VOXEL_CLICK && _isHoverVoxel && !_isHoverVoxelSounding) {
_hoverVoxelOriginalColor[0] = _hoverVoxel.red;
_hoverVoxelOriginalColor[1] = _hoverVoxel.green;
@ -1160,12 +1297,26 @@ const glm::vec3 Application::getMouseVoxelWorldCoordinates(const VoxelDetail _mo
(_mouseVoxel.z + _mouseVoxel.s / 2.f) * TREE_SCALE);
}
const float NUDGE_PRECISION_MIN = 1 / pow(2.0, 12.0);
void Application::decreaseVoxelSize() {
_mouseVoxelScale /= 2;
if (_nudgeStarted) {
if (_mouseVoxelScale >= NUDGE_PRECISION_MIN) {
_mouseVoxelScale /= 2;
}
} else {
_mouseVoxelScale /= 2;
}
}
void Application::increaseVoxelSize() {
_mouseVoxelScale *= 2;
if (_nudgeStarted) {
if (_mouseVoxelScale < _nudgeVoxel.s) {
_mouseVoxelScale *= 2;
}
} else {
_mouseVoxelScale *= 2;
}
}
const int MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE = 1500;
@ -1291,20 +1442,53 @@ void Application::pasteVoxels() {
}
}
void Application::findAxisAlignment() {
glm::vec3 direction = _myAvatar.getMouseRayDirection();
if (fabs(direction.z) > fabs(direction.x)) {
_lookingAlongX = false;
if (direction.z < 0) {
_lookingAwayFromOrigin = false;
} else {
_lookingAwayFromOrigin = true;
}
} else {
_lookingAlongX = true;
if (direction.x < 0) {
_lookingAwayFromOrigin = false;
} else {
_lookingAwayFromOrigin = true;
}
}
}
void Application::nudgeVoxels() {
if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) {
VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s);
if (!Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode) && selectedNode) {
Menu::getInstance()->triggerOption(MenuOption::VoxelSelectMode);
}
if (!_nudgeStarted && selectedNode) {
_nudgeVoxel = _mouseVoxel;
_nudgeStarted = true;
_nudgeGuidePosition = glm::vec3(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z);
findAxisAlignment();
} else {
// calculate nudgeVec
glm::vec3 nudgeVec(_mouseVoxel.x - _nudgeVoxel.x, _mouseVoxel.y - _nudgeVoxel.y, _mouseVoxel.z - _nudgeVoxel.z);
glm::vec3 nudgeVec(_nudgeGuidePosition.x - _nudgeVoxel.x, _nudgeGuidePosition.y - _nudgeVoxel.y, _nudgeGuidePosition.z - _nudgeVoxel.z);
VoxelNode* nodeToNudge = _voxels.getVoxelAt(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z, _nudgeVoxel.s);
if (nodeToNudge) {
_voxels.getTree()->nudgeSubTree(nodeToNudge, nudgeVec, _voxelEditSender);
_finishedNudge = true;
_nudgeStarted = false;
}
}
}
void Application::deleteVoxels() {
deleteVoxelUnderCursor();
}
void Application::setListenModeNormal() {
_audio.setListenMode(AudioRingBuffer::NORMAL);
}
@ -1401,7 +1585,6 @@ void Application::init() {
_palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelColorMode), 0, 2);
_palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelGetColorMode), 0, 3);
_palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelSelectMode), 0, 4);
_palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelNudgeMode), 0, 5);
_pieMenu.init("./resources/images/hifi-interface-tools-v2-pie.svg",
_glWidget->width(),
@ -1677,8 +1860,7 @@ void Application::update(float deltaTime) {
_mouseVoxel.s = 0.0f;
}
} else if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelAddMode)
|| Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)
|| Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) {
|| Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) {
// place the voxel a fixed distance away
float worldMouseVoxelScale = _mouseVoxelScale * TREE_SCALE;
glm::vec3 pt = mouseRayOrigin + mouseRayDirection * (2.0f + worldMouseVoxelScale * 0.5f);
@ -1693,11 +1875,13 @@ void Application::update(float deltaTime) {
_mouseVoxel.red = 255;
_mouseVoxel.green = _mouseVoxel.blue = 0;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelSelectMode)) {
// yellow indicates selection
_mouseVoxel.red = _mouseVoxel.green = 255;
_mouseVoxel.blue = 0;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) {
_mouseVoxel.red = _mouseVoxel.green = _mouseVoxel.blue = 255;
if (_nudgeStarted) {
_mouseVoxel.red = _mouseVoxel.green = _mouseVoxel.blue = 255;
} else {
// yellow indicates selection
_mouseVoxel.red = _mouseVoxel.green = 255;
_mouseVoxel.blue = 0;
}
} else { // _addVoxelMode->isChecked() || _colorVoxelMode->isChecked()
QColor paintColor = Menu::getInstance()->getActionForOption(MenuOption::VoxelPaintColor)->data().value<QColor>();
_mouseVoxel.red = paintColor.red();
@ -2292,19 +2476,17 @@ void Application::displaySide(Camera& whichCamera) {
glDisable(GL_LIGHTING);
glPushMatrix();
glScalef(TREE_SCALE, TREE_SCALE, TREE_SCALE);
if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) {
if (!_finishedNudge) {
renderNudgeGuide(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _nudgeVoxel.s);
renderNudgeGrid(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z, _nudgeVoxel.s, _mouseVoxel.s);
glPushMatrix();
glTranslatef(_nudgeVoxel.x + _nudgeVoxel.s * 0.5f,
_nudgeVoxel.y + _nudgeVoxel.s * 0.5f,
_nudgeVoxel.z + _nudgeVoxel.s * 0.5f);
glColor3ub(255, 255, 255);
glLineWidth(4.0f);
glutWireCube(_nudgeVoxel.s);
glPopMatrix();
}
if (_nudgeStarted) {
renderNudgeGuide(_nudgeGuidePosition.x, _nudgeGuidePosition.y, _nudgeGuidePosition.z, _nudgeVoxel.s);
renderNudgeGrid(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z, _nudgeVoxel.s, _mouseVoxel.s);
glPushMatrix();
glTranslatef(_nudgeVoxel.x + _nudgeVoxel.s * 0.5f,
_nudgeVoxel.y + _nudgeVoxel.s * 0.5f,
_nudgeVoxel.z + _nudgeVoxel.s * 0.5f);
glColor3ub(255, 255, 255);
glLineWidth(4.0f);
glutWireCube(_nudgeVoxel.s);
glPopMatrix();
} else {
renderMouseVoxelGrid(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s);
}
@ -2315,17 +2497,19 @@ void Application::displaySide(Camera& whichCamera) {
} else {
glColor3ub(_mouseVoxel.red, _mouseVoxel.green, _mouseVoxel.blue);
}
glTranslatef(_mouseVoxel.x + _mouseVoxel.s*0.5f,
_mouseVoxel.y + _mouseVoxel.s*0.5f,
_mouseVoxel.z + _mouseVoxel.s*0.5f);
glLineWidth(4.0f);
if (Menu::getInstance()->isOptionChecked(MenuOption::VoxelNudgeMode)) {
if (_nudgeVoxel.s) {
glutWireCube(_nudgeVoxel.s);
} else {
glutWireCube(_mouseVoxel.s);
}
if (_nudgeStarted) {
// render nudge guide cube
glTranslatef(_nudgeGuidePosition.x + _nudgeVoxel.s*0.5f,
_nudgeGuidePosition.y + _nudgeVoxel.s*0.5f,
_nudgeGuidePosition.z + _nudgeVoxel.s*0.5f);
glLineWidth(4.0f);
glutWireCube(_nudgeVoxel.s);
} else {
glTranslatef(_mouseVoxel.x + _mouseVoxel.s*0.5f,
_mouseVoxel.y + _mouseVoxel.s*0.5f,
_mouseVoxel.z + _mouseVoxel.s*0.5f);
glLineWidth(4.0f);
glutWireCube(_mouseVoxel.s);
}
glLineWidth(1.0f);

View file

@ -151,6 +151,7 @@ public slots:
void copyVoxels();
void pasteVoxels();
void nudgeVoxels();
void deleteVoxels();
void setRenderVoxels(bool renderVoxels);
void doKillLocalVoxels();
@ -222,7 +223,9 @@ private:
void updateCursor();
static void attachNewHeadToNode(Node *newNode);
static void* networkReceive(void* args); // network receive thread
static void* networkReceive(void* args); // network receive thread
void findAxisAlignment();
QMainWindow* _window;
QGLWidget* _glWidget;
@ -308,6 +311,10 @@ private:
bool _justEditedVoxel; // set when we've just added/deleted/colored a voxel
VoxelDetail _nudgeVoxel; // details of the voxel to be nudged
bool _nudgeStarted;
bool _lookingAlongX;
bool _lookingAwayFromOrigin;
glm::vec3 _nudgeGuidePosition;
bool _isLookingAtOtherAvatar;
glm::vec3 _lookatOtherPosition;
@ -361,7 +368,6 @@ private:
Swatch _swatch;
bool _pasteMode;
bool _finishedNudge;
PieMenu _pieMenu;

View file

@ -0,0 +1,79 @@
//
// InfoView
// hifi
//
// Created by Stojce Slavkovski on 9/7/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include "InfoView.h"
#include <QApplication>
#include "Application.h"
#include <QtWebKitWidgets/QWebFrame>
#include <QtWebKit/QWebElement>
#include <QDesktopWidget>
#define SETTINGS_VERSION_KEY "info-version"
#define MAX_DIALOG_HEIGHT_RATIO 0.9
InfoView::InfoView(bool forced) {
_forced = forced;
settings()->setAttribute(QWebSettings::LocalContentCanAccessFileUrls, true);
switchToResourcesParentIfRequired();
QString absPath = QFileInfo("resources/html/interface-welcome-allsvg.html").absoluteFilePath();
QUrl url = QUrl::fromLocalFile(absPath);
load(url);
connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loaded(bool)));
}
void InfoView::showFirstTime() {
new InfoView(false);
}
void InfoView::forcedShow() {
new InfoView(true);
}
bool InfoView::shouldShow() {
if (_forced) {
return true;
}
QSettings* settings = Application::getInstance()->getSettings();
QString lastVersion = settings->value(SETTINGS_VERSION_KEY).toString();
QWebFrame* mainFrame = page()->mainFrame();
QWebElement versionTag = mainFrame->findFirstElement("#version");
QString version = versionTag.attribute("value");
if (lastVersion == QString::null || version == QString::null || lastVersion != version) {
if (version != QString::null) {
settings->setValue(SETTINGS_VERSION_KEY, version);
}
return true;
}
return false;
}
void InfoView::loaded(bool ok) {
if (!ok || !shouldShow()) {
return;
}
QDesktopWidget* desktop = Application::getInstance()->desktop();
QWebFrame* mainFrame = page()->mainFrame();
int height = mainFrame->contentsSize().height() > desktop->height() ?
desktop->height() * MAX_DIALOG_HEIGHT_RATIO :
mainFrame->contentsSize().height();
resize(mainFrame->contentsSize().width(), height);
move(desktop->screen()->rect().center() - rect().center());
setWindowTitle(title());
show();
}

29
interface/src/InfoView.h Normal file
View file

@ -0,0 +1,29 @@
//
// InfoView.h
// hifi
//
// Created by Stojce Slavkovski on 9/7/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__InfoView__
#define __hifi__InfoView__
#include <QtWebKitWidgets/QWebView>
class InfoView : public QWebView {
Q_OBJECT
public:
static void showFirstTime();
static void forcedShow();
private:
InfoView(bool forced);
bool _forced;
bool shouldShow();
private slots:
void loaded(bool ok);
};
#endif /* defined(__hifi__InfoView__) */

View file

@ -24,6 +24,7 @@
#include "PairingHandler.h"
#include "Menu.h"
#include "Util.h"
#include "InfoView.h"
Menu* Menu::_instance = NULL;
@ -52,7 +53,15 @@ Menu::Menu() :
Application *appInstance = Application::getInstance();
QMenu* fileMenu = addMenu("File");
#ifdef Q_OS_MAC
(addActionToQMenuAndActionHash(fileMenu,
MenuOption::AboutApp,
0,
this,
SLOT(aboutApp())))->setMenuRole(QAction::AboutRole);
#endif
(addActionToQMenuAndActionHash(fileMenu,
MenuOption::Preferences,
Qt::CTRL | Qt::Key_Comma,
@ -110,6 +119,12 @@ Menu::Menu() :
addActionToQMenuAndActionHash(editMenu, MenuOption::PasteVoxels, Qt::CTRL | Qt::Key_V, appInstance, SLOT(pasteVoxels()));
addActionToQMenuAndActionHash(editMenu, MenuOption::NudgeVoxels, Qt::CTRL | Qt::Key_N, appInstance, SLOT(nudgeVoxels()));
#ifdef __APPLE__
addActionToQMenuAndActionHash(editMenu, MenuOption::DeleteVoxels, Qt::Key_Backspace, appInstance, SLOT(deleteVoxels()));
#else
addActionToQMenuAndActionHash(editMenu, MenuOption::DeleteVoxels, Qt::Key_Delete, appInstance, SLOT(deleteVoxels()));
#endif
addDisabledActionAndSeparator(editMenu, "Physics");
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, true);
addCheckableActionToQMenuAndActionHash(editMenu,
@ -132,9 +147,6 @@ Menu::Menu() :
QAction* colorVoxelMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelColorMode, Qt::Key_B);
_voxelModeActionsGroup->addAction(colorVoxelMode);
QAction* nudgeVoxelMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelNudgeMode, Qt::Key_N);
_voxelModeActionsGroup->addAction(nudgeVoxelMode);
QAction* selectVoxelMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelSelectMode, Qt::Key_O);
_voxelModeActionsGroup->addAction(selectVoxelMode);
@ -264,6 +276,7 @@ Menu::Menu() :
SLOT(cycleRenderMode()));
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::UsePerlinFace, 0, false);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::LookAtVectors, 0, true);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::LookAtIndicator, 0, true);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::FrameTimer);
@ -370,11 +383,11 @@ Menu::Menu() :
SLOT(setDepthOnly(bool)));
addCheckableActionToQMenuAndActionHash(developerMenu,
MenuOption::Faceshift,
MenuOption::FaceshiftTCP,
0,
false,
appInstance->getFaceshift(),
SLOT(setEnabled(bool)));
SLOT(setTCPEnabled(bool)));
QMenu* audioDebugMenu = developerMenu->addMenu("Audio Debugging Tools");
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoAudio);
@ -434,6 +447,13 @@ Menu::Menu() :
addDisabledActionAndSeparator(developerMenu, "Voxels");
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DestructiveAddVoxel);
#ifndef Q_OS_MAC
QMenu* helpMenu = addMenu("Help");
QAction* helpAction = helpMenu->addAction(MenuOption::AboutApp);
connect(helpAction, SIGNAL(triggered()), this, SLOT(aboutApp()));
#endif
}
Menu::~Menu() {
@ -663,6 +683,10 @@ bool Menu::isVoxelModeActionChecked() {
return false;
}
void Menu::aboutApp() {
InfoView::forcedShow();
}
void Menu::editPreferences() {
Application* applicationInstance = Application::getInstance();
QDialog dialog(applicationInstance->getGLWidget());

View file

@ -65,6 +65,7 @@ public slots:
void checkForUpdates();
private slots:
void aboutApp();
void editPreferences();
void goToDomain();
void goToLocation();
@ -116,6 +117,7 @@ private:
namespace MenuOption {
const QString AboutApp = "About Interface";
const QString AmbientOcclusion = "Ambient Occlusion";
const QString Avatars = "Avatars";
const QString AvatarAsBalls = "Avatar as Balls";
@ -130,6 +132,7 @@ namespace MenuOption {
const QString CutVoxels = "Cut";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DecreaseVoxelSize = "Decrease Voxel Size";
const QString DeleteVoxels = "Delete";
const QString DestructiveAddVoxel = "Create Voxel is Destructive";
const QString DeltaSending = "Delta Sending";
const QString DisplayFrustum = "Display Frustum";
@ -137,7 +140,7 @@ namespace MenuOption {
const QString ExportVoxels = "Export Voxels";
const QString HeadMouse = "Head Mouse";
const QString FaceMode = "Cycle Face Mode";
const QString Faceshift = "Faceshift";
const QString FaceshiftTCP = "Faceshift (TCP)";
const QString FalseColorByDistance = "FALSE Color By Distance";
const QString FalseColorBySource = "FALSE Color By Source";
const QString FalseColorEveryOtherVoxel = "FALSE Color Every Other Randomly";
@ -169,7 +172,7 @@ namespace MenuOption {
const QString LookAtVectors = "Look-at Vectors";
const QString LowRes = "Lower Resolution While Moving";
const QString Mirror = "Mirror";
const QString NudgeVoxels = "Nudge Voxels";
const QString NudgeVoxels = "Nudge";
const QString OcclusionCulling = "Occlusion Culling";
const QString OffAxisProjection = "Off-Axis Projection";
const QString Oscilloscope = "Audio Oscilloscope";
@ -194,6 +197,7 @@ namespace MenuOption {
const QString TestRaveGlove = "Test Rave Glove";
const QString TreeStats = "Calculate Tree Stats";
const QString TransmitterDrive = "Transmitter Drive";
const QString UsePerlinFace = "Use Perlin's Face";
const QString Quit = "Quit";
const QString Webcam = "Webcam";
const QString WebcamMode = "Cycle Webcam Send Mode";
@ -205,7 +209,6 @@ namespace MenuOption {
const QString VoxelGetColorMode = "Get Color Mode";
const QString VoxelMode = "Cycle Voxel Mode";
const QString VoxelPaintColor = "Voxel Paint Color";
const QString VoxelNudgeMode = "Nudge Voxel Mode";
const QString VoxelSelectMode = "Select Voxel Mode";
const QString VoxelStats = "Voxel Stats";
const QString VoxelTextures = "Voxel Textures";

View file

@ -11,6 +11,7 @@
#include <NodeList.h>
#include "Application.h"
#include "Menu.h"
#include "Avatar.h"
#include "Head.h"
#include "Util.h"
@ -67,10 +68,8 @@ Head::Head(Avatar* owningAvatar) :
_rightEarPosition(0.0f, 0.0f, 0.0f),
_mouthPosition(0.0f, 0.0f, 0.0f),
_scale(1.0f),
_browAudioLift(0.0f),
_gravity(0.0f, -1.0f, 0.0f),
_lastLoudness(0.0f),
_averageLoudness(0.0f),
_audioAttack(0.0f),
_returnSpringScale(1.0f),
_bodyRotation(0.0f, 0.0f, 0.0f),
@ -78,8 +77,6 @@ Head::Head(Avatar* owningAvatar) :
_mohawkInitialized(false),
_saccade(0.0f, 0.0f, 0.0f),
_saccadeTarget(0.0f, 0.0f, 0.0f),
_leftEyeBlink(0.0f),
_rightEyeBlink(0.0f),
_leftEyeBlinkVelocity(0.0f),
_rightEyeBlinkVelocity(0.0f),
_timeWithoutTalking(0.0f),
@ -149,6 +146,8 @@ void Head::simulate(float deltaTime, bool isMine, float gyroCameraSensitivity) {
// Update audio trailing average for rendering facial animations
Faceshift* faceshift = Application::getInstance()->getFaceshift();
_isFaceshiftConnected = faceshift != NULL;
if (isMine && faceshift->isActive()) {
const float EYE_OPEN_SCALE = 0.5f;
_leftEyeBlink = faceshift->getLeftBlink() - EYE_OPEN_SCALE * faceshift->getLeftEyeOpen();
@ -161,7 +160,7 @@ void Head::simulate(float deltaTime, bool isMine, float gyroCameraSensitivity) {
const float BROW_HEIGHT_SCALE = 0.005f;
_browAudioLift = faceshift->getBrowUpCenter() * BROW_HEIGHT_SCALE;
} else {
} else if (!_isFaceshiftConnected) {
// Update eye saccades
const float AVERAGE_MICROSACCADE_INTERVAL = 0.50f;
const float AVERAGE_SACCADE_INTERVAL = 4.0f;
@ -332,7 +331,7 @@ void Head::render(float alpha) {
glEnable(GL_DEPTH_TEST);
glEnable(GL_RESCALE_NORMAL);
if (true) {
if (Menu::getInstance()->isOptionChecked(MenuOption::UsePerlinFace)) {
_perlinFace.render();
} else {
renderMohawk();

View file

@ -105,10 +105,8 @@ private:
glm::vec3 _mouthPosition;
Nose _nose;
float _scale;
float _browAudioLift;
glm::vec3 _gravity;
float _lastLoudness;
float _averageLoudness;
float _audioAttack;
float _returnSpringScale; //strength of return springs
glm::vec3 _bodyRotation;
@ -119,8 +117,6 @@ private:
glm::vec3 _mohawkColors[MOHAWK_TRIANGLES];
glm::vec3 _saccade;
glm::vec3 _saccadeTarget;
float _leftEyeBlink;
float _rightEyeBlink;
float _leftEyeBlinkVelocity;
float _rightEyeBlinkVelocity;
float _timeWithoutTalking;

View file

@ -191,52 +191,52 @@ void PerlinFace::render() {
glRotatef(glm::angle(orientation), axis.x, axis.y, axis.z);
glPushMatrix();
// Correct head scale and offset from hard coded points coordinates.
glScalef(2.0f * BODY_BALL_RADIUS_HEAD_BASE / (_vertices[HAIR_2].y - _vertices[JAW_BOTTOM].y),
2.0f * BODY_BALL_RADIUS_HEAD_BASE / (_vertices[HAIR_2].y - _vertices[JAW_BOTTOM].y),
2.0f * BODY_BALL_RADIUS_HEAD_BASE / (_vertices[HAIR_2].y - _vertices[JAW_BOTTOM].y));
glTranslatef(0, -60.0f, 20.0f);
glPushMatrix(); { // This block use the coordinates system of perlin's face points.
// Correct head scale and offset from hard coded points coordinates.
glScalef(2.0f * BODY_BALL_RADIUS_HEAD_BASE / (_vertices[HAIR_2].y - _vertices[JAW_BOTTOM].y),
2.0f * BODY_BALL_RADIUS_HEAD_BASE / (_vertices[HAIR_2].y - _vertices[JAW_BOTTOM].y),
2.0f * BODY_BALL_RADIUS_HEAD_BASE / (_vertices[HAIR_2].y - _vertices[JAW_BOTTOM].y));
glTranslatef(0, -60.0f, 20.0f);
/**/
glEnableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, _vboID);
glVertexPointer(FLOAT_PER_VERTEX, GL_FLOAT, 0, 0);
/**/
glEnableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, _vboID);
glVertexPointer(FLOAT_PER_VERTEX, GL_FLOAT, 0, 0);
glEnableClientState(GL_NORMAL_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, _nboID);
glNormalPointer(GL_FLOAT, 0, 0);
glEnableClientState(GL_NORMAL_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, _nboID);
glNormalPointer(GL_FLOAT, 0, 0);
glEnableClientState(GL_COLOR_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, _cboID);
glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0);
glEnableClientState(GL_COLOR_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, _cboID);
glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, VERTEX_PER_TRIANGLE * _trianglesCount);
glDrawArrays(GL_TRIANGLES, 0, VERTEX_PER_TRIANGLE * _trianglesCount);
// Draw eyes
glColor3d(0, 0, 0);
glBegin(GL_LINE_LOOP);
glVertex3d(_vertices[EYE_LEFT].x, _vertices[EYE_LEFT].y, _vertices[EYE_LEFT].z);
glVertex3d(_vertices[EYE_MID_TOP].x, _vertices[EYE_MID_TOP].y, _vertices[EYE_MID_TOP].z);
glVertex3d(_vertices[EYE_RIGHT].x, _vertices[EYE_RIGHT].y, _vertices[EYE_RIGHT].z);
glVertex3d(_vertices[EYE_MID_BOTTOM].x, _vertices[EYE_MID_BOTTOM].y, _vertices[EYE_MID_BOTTOM].z);
glEnd();
// Draw eyes
glColor3d(0, 0, 0);
glBegin(GL_LINE_LOOP);
glVertex3d(_vertices[EYE_LEFT].x, _vertices[EYE_LEFT].y, _vertices[EYE_LEFT].z);
glVertex3d(_vertices[EYE_MID_TOP].x, _vertices[EYE_MID_TOP].y, _vertices[EYE_MID_TOP].z);
glVertex3d(_vertices[EYE_RIGHT].x, _vertices[EYE_RIGHT].y, _vertices[EYE_RIGHT].z);
glVertex3d(_vertices[EYE_MID_BOTTOM].x, _vertices[EYE_MID_BOTTOM].y, _vertices[EYE_MID_BOTTOM].z);
glEnd();
glBegin(GL_LINE_LOOP);
glVertex3d(_vertices[NUM_VERTICES + EYE_LEFT].x,
_vertices[NUM_VERTICES + EYE_LEFT].y,
_vertices[NUM_VERTICES + EYE_LEFT].z);
glVertex3d(_vertices[NUM_VERTICES + EYE_MID_TOP].x,
_vertices[NUM_VERTICES + EYE_MID_TOP].y,
_vertices[NUM_VERTICES + EYE_MID_TOP].z);
glVertex3d(_vertices[NUM_VERTICES + EYE_RIGHT].x,
_vertices[NUM_VERTICES + EYE_RIGHT].y,
_vertices[NUM_VERTICES + EYE_RIGHT].z);
glVertex3d(_vertices[NUM_VERTICES + EYE_MID_BOTTOM].x,
_vertices[NUM_VERTICES + EYE_MID_BOTTOM].y,
_vertices[NUM_VERTICES + EYE_MID_BOTTOM].z);
glEnd();
glPopMatrix();
glBegin(GL_LINE_LOOP);
glVertex3d(_vertices[NUM_VERTICES + EYE_LEFT].x,
_vertices[NUM_VERTICES + EYE_LEFT].y,
_vertices[NUM_VERTICES + EYE_LEFT].z);
glVertex3d(_vertices[NUM_VERTICES + EYE_MID_TOP].x,
_vertices[NUM_VERTICES + EYE_MID_TOP].y,
_vertices[NUM_VERTICES + EYE_MID_TOP].z);
glVertex3d(_vertices[NUM_VERTICES + EYE_RIGHT].x,
_vertices[NUM_VERTICES + EYE_RIGHT].y,
_vertices[NUM_VERTICES + EYE_RIGHT].z);
glVertex3d(_vertices[NUM_VERTICES + EYE_MID_BOTTOM].x,
_vertices[NUM_VERTICES + EYE_MID_BOTTOM].y,
_vertices[NUM_VERTICES + EYE_MID_BOTTOM].z);
glEnd();
} glPopMatrix();
const float EYEBALL_RADIUS = 0.008f;
const float EYEBALL_COLOR[4] = { 0.9f, 0.9f, 0.8f, 1.0f };
@ -327,25 +327,6 @@ void PerlinFace::render() {
glPopMatrix();
/*/
// Draw points for debug.
glColor3d(0.0f, 1.0f, 0.0f);
glPointSize(4);
glBegin(GL_POINTS);
for (int i = 0; i < 2 * NUM_VERTICES; ++i) {
if (i == NUM_VERTICES) {
glColor3d(1.0f, 0.0f, 0.0f);
}
glVertex3d(VERTICES[3 * i], VERTICES[(3 * i) + 1], VERTICES[(3 * i) + 2]);
}
glColor3d(0.0f, 0.0f, 1.0f);
for (int i = 2 * NUM_VERTICES; i < _vertexNumber; ++i) {
break;
glVertex3d(VERTICES[3 * i], VERTICES[(3 * i) + 1], VERTICES[(3 * i) + 2]);
glVertex3d(-VERTICES[3 * i], VERTICES[(3 * i) + 1], VERTICES[(3 * i) + 2]);
}
glEnd();
/**/
glPopMatrix();
}
@ -413,47 +394,50 @@ void PerlinFace::updatePositions() {
- _browsD_R * BROWS_DOWN_MAX
+ _browsU_C * BROWS_UP_CENTER_MAX;
const float MOUTH_UP_MAX = 6.5f;
const float MOUTH_SIDE_UP_MAX = 4.0f;
const float SMILE_FACTOR = 1.0f / 3.0f; // 0 = No smile, 1 = The Jocker.
// Mouth
_vertices[MOUTH_BOTTOM_IN].y = getVec3(MOUTH_BOTTOM_IN).y
+ (1.0 - _mouthSize) * 6.5;
+ (1.0 - _mouthSize) * MOUTH_UP_MAX;
_vertices[MOUTH_BOTTOM_OUT].y = getVec3(MOUTH_BOTTOM_OUT).y
+ (1.0 - _mouthSize) * 6.5;
_vertices[MOUTH_MID_IN] = (1.0f - (_mouthSmileLeft / 3.0f)) * (getVec3(MOUTH_MID_IN)
+ glm::vec3(0, (1.0 - _mouthSize) * 4, 0))
+ (_mouthSmileLeft / 3.0f) * getVec3(CHICK_MID);
_vertices[MOUTH_MID_OUT] = (1.0f - (_mouthSmileLeft / 3.0f)) * (getVec3(MOUTH_MID_OUT)
+ glm::vec3(0, (1.0 - _mouthSize) * 4, 0))
+ (_mouthSmileLeft / 3.0f) * getVec3(CHICK_MID);
+ (1.0 - _mouthSize) * MOUTH_UP_MAX;
_vertices[MOUTH_MID_IN] = (1.0f - (_mouthSmileLeft * SMILE_FACTOR)) * (getVec3(MOUTH_MID_IN)
+ glm::vec3(0, (1.0 - _mouthSize) * MOUTH_SIDE_UP_MAX, 0))
+ (_mouthSmileLeft * SMILE_FACTOR) * getVec3(CHICK_MID);
_vertices[MOUTH_MID_OUT] = (1.0f - (_mouthSmileLeft * SMILE_FACTOR)) * (getVec3(MOUTH_MID_OUT)
+ glm::vec3(0, (1.0 - _mouthSize) * MOUTH_SIDE_UP_MAX, 0))
+ (_mouthSmileLeft * SMILE_FACTOR) * getVec3(CHICK_MID);
_vertices[NUM_VERTICES + MOUTH_BOTTOM_IN].y = getVec3(NUM_VERTICES + MOUTH_BOTTOM_IN).y
+ (1.0 - _mouthSize) * 6.5;
+ (1.0 - _mouthSize) * MOUTH_UP_MAX;
_vertices[NUM_VERTICES + MOUTH_BOTTOM_OUT].y = getVec3(NUM_VERTICES + MOUTH_BOTTOM_OUT).y
+ (1.0 - _mouthSize) * 6.5;
_vertices[NUM_VERTICES + MOUTH_MID_IN] = (1.0f - (_mouthSmileLeft / 3.0f)) * (getVec3(NUM_VERTICES + MOUTH_MID_IN)
+ glm::vec3(0, (1.0 - _mouthSize) * 4, 0))
+ (_mouthSmileLeft / 3.0f) * getVec3(NUM_VERTICES + CHICK_MID);
_vertices[NUM_VERTICES + MOUTH_MID_OUT] = (1.0f - (_mouthSmileLeft / 3.0f)) * (getVec3(NUM_VERTICES + MOUTH_MID_OUT)
+ glm::vec3(0, (1.0 - _mouthSize) * 4, 0))
+ (_mouthSmileLeft / 3.0f) * getVec3(NUM_VERTICES + CHICK_MID);
+ (1.0 - _mouthSize) * MOUTH_UP_MAX;
_vertices[NUM_VERTICES + MOUTH_MID_IN] = (1.0f - (_mouthSmileLeft * SMILE_FACTOR)) * (getVec3(NUM_VERTICES + MOUTH_MID_IN)
+ glm::vec3(0, (1.0 - _mouthSize) * MOUTH_SIDE_UP_MAX, 0))
+ (_mouthSmileLeft * SMILE_FACTOR) * getVec3(NUM_VERTICES + CHICK_MID);
_vertices[NUM_VERTICES + MOUTH_MID_OUT] = (1.0f - (_mouthSmileLeft * SMILE_FACTOR)) * (getVec3(NUM_VERTICES + MOUTH_MID_OUT)
+ glm::vec3(0, (1.0 - _mouthSize) * MOUTH_SIDE_UP_MAX, 0))
+ (_mouthSmileLeft * SMILE_FACTOR) * getVec3(NUM_VERTICES + CHICK_MID);
// Jaw
_vertices[CHIN_IN].y = getVec3(CHIN_IN).y
+ (1.0 - _mouthSize) * 4;
+ (1.0 - _mouthSize) * MOUTH_UP_MAX;
_vertices[CHIN_TIP].y = getVec3(CHIN_TIP).y
+ (1.0 - _mouthSize) * 4;
+ (1.0 - _mouthSize) * MOUTH_UP_MAX;
_vertices[CHIN_BOTTOM].y = getVec3(CHIN_BOTTOM).y
+ (1.0 - _mouthSize) * 4;
+ (1.0 - _mouthSize) * MOUTH_UP_MAX;
_vertices[NUM_VERTICES +CHIN_IN].y = getVec3(NUM_VERTICES + CHIN_IN).y
+ (1.0 - _mouthSize) * 4;
+ (1.0 - _mouthSize) * MOUTH_UP_MAX;
_vertices[NUM_VERTICES +CHIN_TIP].y = getVec3(NUM_VERTICES + CHIN_TIP).y
+ (1.0 - _mouthSize) * 4;
+ (1.0 - _mouthSize) * MOUTH_UP_MAX;
_vertices[NUM_VERTICES +CHIN_BOTTOM].y = getVec3(NUM_VERTICES + CHIN_BOTTOM).y
+ (1.0 - _mouthSize) * 4;
+ (1.0 - _mouthSize) * MOUTH_UP_MAX;
// Eyelids

View file

@ -155,7 +155,7 @@ int AvatarData::getBroadcastData(unsigned char* destinationBuffer) {
// Instantaneous audio loudness (used to drive facial animation)
//destinationBuffer += packFloatToByte(destinationBuffer, std::min(MAX_AUDIO_LOUDNESS, _audioLoudness), MAX_AUDIO_LOUDNESS);
memcpy(destinationBuffer, &_headData->_audioLoudness, sizeof(float));
destinationBuffer += sizeof(float);
destinationBuffer += sizeof(float);
// camera details
memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition));
@ -185,6 +185,26 @@ int AvatarData::getBroadcastData(unsigned char* destinationBuffer) {
// hand state
setSemiNibbleAt(bitItems,HAND_STATE_START_BIT,_handState);
*destinationBuffer++ = bitItems;
bitItems = 0;
if (_headData->_isFaceshiftConnected) { setAtBit(bitItems, IS_FACESHIFT_CONNECTED); }
*destinationBuffer++ = bitItems;
// If it is connected, pack up the data
if (_headData->_isFaceshiftConnected) {
memcpy(destinationBuffer, &_headData->_leftEyeBlink, sizeof(float));
destinationBuffer += sizeof(float);
memcpy(destinationBuffer, &_headData->_rightEyeBlink, sizeof(float));
destinationBuffer += sizeof(float);
memcpy(destinationBuffer, &_headData->_averageLoudness, sizeof(float));
destinationBuffer += sizeof(float);
memcpy(destinationBuffer, &_headData->_browAudioLift, sizeof(float));
destinationBuffer += sizeof(float);
}
// leap hand data
destinationBuffer += _handData->encodeRemoteData(destinationBuffer);
@ -267,7 +287,7 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) {
//sourceBuffer += unpackFloatFromByte(sourceBuffer, _audioLoudness, MAX_AUDIO_LOUDNESS);
memcpy(&_headData->_audioLoudness, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
// camera details
memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition));
sourceBuffer += sizeof(_cameraPosition);
@ -298,6 +318,24 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) {
// hand state, stored as a semi-nibble in the bitItems
_handState = getSemiNibbleAt(bitItems,HAND_STATE_START_BIT);
bitItems = (unsigned char)*sourceBuffer++;
_headData->_isFaceshiftConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED);
// If it is connected, pack up the data
if (_headData->_isFaceshiftConnected) {
memcpy(&_headData->_leftEyeBlink, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
memcpy(&_headData->_rightEyeBlink, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
memcpy(&_headData->_averageLoudness, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
memcpy(&_headData->_browAudioLift, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float);
}
// leap hand data
if (sourceBuffer - startPosition < numBytes) {
// check passed, bytes match

View file

@ -23,6 +23,7 @@
#include "HeadData.h"
#include "HandData.h"
// First bitset
const int WANT_LOW_RES_MOVING_BIT = 0;
const int WANT_COLOR_AT_BIT = 1;
const int WANT_DELTA_AT_BIT = 2;
@ -30,6 +31,9 @@ const int KEY_STATE_START_BIT = 3; // 4th and 5th bits
const int HAND_STATE_START_BIT = 5; // 6th and 7th bits
const int WANT_OCCLUSION_CULLING_BIT = 7; // 8th bit
// Second bitset
const int IS_FACESHIFT_CONNECTED = 0;
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
enum KeyState

View file

@ -16,6 +16,11 @@ HeadData::HeadData(AvatarData* owningAvatar) :
_leanSideways(0.0f),
_leanForward(0.0f),
_audioLoudness(0.0f),
_isFaceshiftConnected(false),
_leftEyeBlink(0.0f),
_rightEyeBlink(0.0f),
_averageLoudness(0.0f),
_browAudioLift(0.0f),
_owningAvatar(owningAvatar)
{
@ -38,4 +43,4 @@ void HeadData::addLean(float sideways, float forwards) {
// Add lean as impulse
_leanSideways += sideways;
_leanForward += forwards;
}
}

View file

@ -60,6 +60,11 @@ protected:
float _leanSideways;
float _leanForward;
float _audioLoudness;
bool _isFaceshiftConnected;
float _leftEyeBlink;
float _rightEyeBlink;
float _averageLoudness;
float _browAudioLift;
AvatarData* _owningAvatar;
private:
// privatize copy ctor and assignment operator so copies of this object cannot be made

View file

@ -7,34 +7,33 @@
//
#include "PacketHeaders.h"
#include "SharedUtil.h"
#include "Assignment.h"
const char IPv4_ADDRESS_DESIGNATOR = 4;
const char IPv6_ADDRESS_DESIGNATOR = 6;
Assignment::Assignment(Assignment::Direction direction, Assignment::Type type, const char* pool) :
_direction(direction),
const int NUM_BYTES_RFC4122_UUID = 16;
Assignment::Assignment(Assignment::Command command, Assignment::Type type, Assignment::Location location) :
_command(command),
_type(type),
_pool(NULL),
_location(location),
_attachedPublicSocket(NULL),
_attachedLocalSocket(NULL)
{
// set the create time on this assignment
gettimeofday(&_time, NULL);
// copy the pool, if we got one
if (pool) {
int poolLength = strlen(pool);
// create the char array and make it large enough for string and null termination
_pool = new char[poolLength + sizeof(char)];
strcpy(_pool, pool);
if (_command == Assignment::CreateCommand) {
// this is a newly created assignment, generate a random UUID
_uuid = QUuid::createUuid();
}
}
Assignment::Assignment(const unsigned char* dataBuffer, int numBytes) :
_pool(NULL),
_location(GlobalLocation),
_attachedPublicSocket(NULL),
_attachedLocalSocket(NULL)
{
@ -44,27 +43,24 @@ Assignment::Assignment(const unsigned char* dataBuffer, int numBytes) :
int numBytesRead = 0;
if (dataBuffer[0] == PACKET_TYPE_REQUEST_ASSIGNMENT) {
_direction = Assignment::Request;
_command = Assignment::RequestCommand;
} else if (dataBuffer[0] == PACKET_TYPE_CREATE_ASSIGNMENT) {
_direction = Assignment::Create;
_command = Assignment::CreateCommand;
} else if (dataBuffer[0] == PACKET_TYPE_DEPLOY_ASSIGNMENT) {
_direction = Assignment::Deploy;
_command = Assignment::DeployCommand;
}
numBytesRead += numBytesForPacketHeader(dataBuffer);
if (dataBuffer[0] != PACKET_TYPE_REQUEST_ASSIGNMENT) {
// read the GUID for this assignment
_uuid = QUuid::fromRfc4122(QByteArray((const char*) dataBuffer + numBytesRead, NUM_BYTES_RFC4122_UUID));
numBytesRead += NUM_BYTES_RFC4122_UUID;
}
memcpy(&_type, dataBuffer + numBytesRead, sizeof(Assignment::Type));
numBytesRead += sizeof(Assignment::Type);
if (dataBuffer[numBytesRead] != 0) {
int poolLength = strlen((const char*) dataBuffer + numBytesRead);
_pool = new char[poolLength + sizeof(char)];
strcpy(_pool, (char*) dataBuffer + numBytesRead);
numBytesRead += poolLength + sizeof(char);
} else {
numBytesRead += sizeof(char);
}
if (numBytes > numBytesRead) {
sockaddr* newSocket = NULL;
@ -78,7 +74,7 @@ Assignment::Assignment(const unsigned char* dataBuffer, int numBytes) :
qDebug("Received a socket that cannot be unpacked!\n");
}
if (_direction == Assignment::Create) {
if (_command == Assignment::CreateCommand) {
delete _attachedLocalSocket;
_attachedLocalSocket = newSocket;
} else {
@ -91,7 +87,10 @@ Assignment::Assignment(const unsigned char* dataBuffer, int numBytes) :
Assignment::~Assignment() {
delete _attachedPublicSocket;
delete _attachedLocalSocket;
delete _pool;
}
QString Assignment::getUUIDStringWithoutCurlyBraces() const {
return _uuid.toString().mid(1, _uuid.toString().length() - 2);
}
void Assignment::setAttachedPublicSocket(const sockaddr* attachedPublicSocket) {
@ -121,19 +120,15 @@ void Assignment::setAttachedLocalSocket(const sockaddr* attachedLocalSocket) {
int Assignment::packToBuffer(unsigned char* buffer) {
int numPackedBytes = 0;
// pack the UUID for this assignment, if this is an assignment create or deploy
if (_command != Assignment::RequestCommand) {
memcpy(buffer, _uuid.toRfc4122().constData(), NUM_BYTES_RFC4122_UUID);
numPackedBytes += NUM_BYTES_RFC4122_UUID;
}
memcpy(buffer + numPackedBytes, &_type, sizeof(_type));
numPackedBytes += sizeof(_type);
if (_pool) {
int poolLength = strlen(_pool);
strcpy((char*) buffer + numPackedBytes, _pool);
numPackedBytes += poolLength + sizeof(char);
} else {
buffer[numPackedBytes] = '\0';
numPackedBytes += sizeof(char);
}
if (_attachedPublicSocket || _attachedLocalSocket) {
sockaddr* socketToPack = (_attachedPublicSocket) ? _attachedPublicSocket : _attachedLocalSocket;
@ -148,6 +143,6 @@ int Assignment::packToBuffer(unsigned char* buffer) {
}
QDebug operator<<(QDebug debug, const Assignment &assignment) {
debug << "T:" << assignment.getType() << "P:" << assignment.getPool();
debug << "T:" << assignment.getType();
return debug.nospace();
}

View file

@ -11,6 +11,8 @@
#include <sys/time.h>
#include <QtCore/QUuid>
#include "NodeList.h"
/// Holds information used for request, creation, and deployment of assignments
@ -18,18 +20,26 @@ class Assignment {
public:
enum Type {
AudioMixer,
AvatarMixer,
All
AudioMixerType,
AvatarMixerType,
AgentType,
AllTypes
};
enum Direction {
Create,
Deploy,
Request
enum Command {
CreateCommand,
DeployCommand,
RequestCommand
};
Assignment(Assignment::Direction direction, Assignment::Type type, const char* pool = NULL);
enum Location {
GlobalLocation,
LocalLocation
};
Assignment(Assignment::Command command,
Assignment::Type type,
Assignment::Location location = Assignment::GlobalLocation);
/// Constructs an Assignment from the data in the buffer
/// \param dataBuffer the source buffer to un-pack the assignment from
@ -38,9 +48,11 @@ public:
~Assignment();
Assignment::Direction getDirection() const { return _direction; }
const QUuid& getUUID() const { return _uuid; }
QString getUUIDStringWithoutCurlyBraces() const;
Assignment::Command getCommand() const { return _command; }
Assignment::Type getType() const { return _type; }
const char* getPool() const { return _pool; }
Assignment::Location getLocation() const { return _location; }
const timeval& getTime() const { return _time; }
const sockaddr* getAttachedPublicSocket() { return _attachedPublicSocket; }
@ -58,9 +70,10 @@ public:
void setCreateTimeToNow() { gettimeofday(&_time, NULL); }
private:
Assignment::Direction _direction; /// the direction of the assignment (Create, Deploy, Request)
QUuid _uuid; /// the 16 byte UUID for this assignment
Assignment::Command _command; /// the command for this assignment (Create, Deploy, Request)
Assignment::Type _type; /// the type of the assignment, defines what the assignee will do
char* _pool; /// the pool this assignment is for/from
Assignment::Location _location; /// the location of the assignment, allows a domain to preferentially use local ACs
sockaddr* _attachedPublicSocket; /// pointer to a public socket that relates to assignment, depends on direction
sockaddr* _attachedLocalSocket; /// pointer to a local socket that relates to assignment, depends on direction
timeval _time; /// time the assignment was created (set in constructor)

View file

@ -382,7 +382,7 @@ const sockaddr_in GLOBAL_ASSIGNMENT_SOCKET = socketForHostnameAndHostOrderPort(G
void NodeList::sendAssignment(Assignment& assignment) {
unsigned char assignmentPacket[MAX_PACKET_SIZE];
PACKET_TYPE assignmentPacketType = assignment.getDirection() == Assignment::Create
PACKET_TYPE assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand
? PACKET_TYPE_CREATE_ASSIGNMENT
: PACKET_TYPE_REQUEST_ASSIGNMENT;
@ -393,7 +393,7 @@ void NodeList::sendAssignment(Assignment& assignment) {
? (sockaddr*) &GLOBAL_ASSIGNMENT_SOCKET
: _assignmentServerSocket;
_nodeSocket.send((sockaddr*) assignmentServerSocket, assignmentPacket, numHeaderBytes + numAssignmentBytes);
_nodeSocket.send(assignmentServerSocket, assignmentPacket, numHeaderBytes + numAssignmentBytes);
}
Node* NodeList::addOrUpdateNode(sockaddr* publicSocket, sockaddr* localSocket, char nodeType, uint16_t nodeId) {

View file

@ -20,7 +20,7 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type) {
return 1;
case PACKET_TYPE_HEAD_DATA:
return 5;
return 6;
case PACKET_TYPE_AVATAR_FACE_VIDEO:
return 1;

View file

@ -1772,6 +1772,14 @@ void VoxelTree::copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinat
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS);
destinationTree->readBitstreamToTree(&outputBuffer[0], bytesWritten, args);
}
VoxelNode* destinationStartNode;
if (rebaseToRoot) {
destinationStartNode = destinationTree->rootNode;
} else {
destinationStartNode = nodeForOctalCode(destinationTree->rootNode, startNode->getOctalCode(), NULL);
}
destinationStartNode->setColor(startNode->getColor());
}
void VoxelTree::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode) {
@ -1861,9 +1869,9 @@ void VoxelTree::cancelImport() {
class NodeChunkArgs {
public:
VoxelTree* thisVoxelTree;
float ancestorSize;
glm::vec3 nudgeVec;
VoxelEditPacketSender* voxelEditSenderPtr;
int childOrder[NUMBER_OF_CHILDREN];
};
float findNewLeafSize(const glm::vec3& nudgeAmount, float leafSize) {
@ -1885,6 +1893,65 @@ float findNewLeafSize(const glm::vec3& nudgeAmount, float leafSize) {
return newLeafSize;
}
void reorderChildrenForNudge(void* extraData) {
NodeChunkArgs* args = (NodeChunkArgs*)extraData;
glm::vec3 nudgeVec = args->nudgeVec;
int lastToNudgeVote[NUMBER_OF_CHILDREN] = { 0 };
const int POSITIVE_X_ORDERING[4] = {0, 1, 2, 3};
const int NEGATIVE_X_ORDERING[4] = {4, 5, 6, 7};
const int POSITIVE_Y_ORDERING[4] = {0, 1, 4, 5};
const int NEGATIVE_Y_ORDERING[4] = {2, 3, 6, 7};
const int POSITIVE_Z_ORDERING[4] = {0, 2, 4, 6};
const int NEGATIVE_Z_ORDERING[4] = {1, 3, 5, 7};
if (nudgeVec.x > 0) {
for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) {
lastToNudgeVote[POSITIVE_X_ORDERING[i]]++;
}
} else if (nudgeVec.x < 0) {
for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) {
lastToNudgeVote[NEGATIVE_X_ORDERING[i]]++;
}
}
if (nudgeVec.y > 0) {
for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) {
lastToNudgeVote[POSITIVE_Y_ORDERING[i]]++;
}
} else if (nudgeVec.y < 0) {
for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) {
lastToNudgeVote[NEGATIVE_Y_ORDERING[i]]++;
}
}
if (nudgeVec.z > 0) {
for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) {
lastToNudgeVote[POSITIVE_Z_ORDERING[i]]++;
}
} else if (nudgeVec.z < 0) {
for (int i = 0; i < NUMBER_OF_CHILDREN / 2; i++) {
lastToNudgeVote[NEGATIVE_Z_ORDERING[i]]++;
}
}
int nUncountedVotes = NUMBER_OF_CHILDREN;
while (nUncountedVotes > 0) {
int maxNumVotes = 0;
int maxVoteIndex = -1;
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
if (lastToNudgeVote[i] > maxNumVotes) {
maxNumVotes = lastToNudgeVote[i];
maxVoteIndex = i;
} else if (lastToNudgeVote[i] == maxNumVotes && maxVoteIndex == -1) {
maxVoteIndex = i;
}
}
lastToNudgeVote[maxVoteIndex] = -1;
args->childOrder[nUncountedVotes - 1] = maxVoteIndex;
nUncountedVotes--;
}
}
bool VoxelTree::nudgeCheck(VoxelNode* node, void* extraData) {
if (node->isLeaf()) {
// we have reached the deepest level of nodes/voxels
@ -1953,37 +2020,40 @@ void VoxelTree::nudgeLeaf(VoxelNode* node, void* extraData) {
glm::vec3 nudge = args->nudgeVec;
// delete the old node
// if the nudge replaces the node in an area outside of the ancestor node
if (fabs(nudge.x) >= args->ancestorSize || fabs(nudge.y) >= args->ancestorSize || fabs(nudge.z) >= args->ancestorSize) {
args->voxelEditSenderPtr->sendVoxelEditMessage(PACKET_TYPE_ERASE_VOXEL, voxelDetails);
}
args->voxelEditSenderPtr->sendVoxelEditMessage(PACKET_TYPE_ERASE_VOXEL, voxelDetails);
// nudge the old node
voxelDetails.x = unNudgedDetails.x + nudge.x;
voxelDetails.y = unNudgedDetails.y + nudge.y;
voxelDetails.z = unNudgedDetails.z + nudge.z;
voxelDetails.x += nudge.x;
voxelDetails.y += nudge.y;
voxelDetails.z += nudge.z;
// create a new voxel in its stead
args->voxelEditSenderPtr->sendVoxelEditMessage(PACKET_TYPE_SET_VOXEL_DESTRUCTIVE, voxelDetails);
}
// Recurses voxel node with an operation function
void VoxelTree::recurseNodeForNudge(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData) {
NodeChunkArgs* args = (NodeChunkArgs*)extraData;
if (operation(node, extraData)) {
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
VoxelNode* child = node->getChildAtIndex(args->childOrder[i]);
if (child) {
recurseNodeForNudge(child, operation, extraData);
}
}
}
}
void VoxelTree::nudgeSubTree(VoxelNode* nodeToNudge, const glm::vec3& nudgeAmount, VoxelEditPacketSender& voxelEditSender) {
if (nudgeAmount == glm::vec3(0, 0, 0)) {
return;
}
// get octal code of this node
unsigned char* octalCode = nodeToNudge->getOctalCode();
// get voxel position/size
VoxelPositionSize ancestorDetails;
voxelDetailsForCode(octalCode, ancestorDetails);
NodeChunkArgs args;
args.thisVoxelTree = this;
args.ancestorSize = ancestorDetails.s;
args.nudgeVec = nudgeAmount;
args.voxelEditSenderPtr = &voxelEditSender;
reorderChildrenForNudge(&args);
recurseNodeWithOperation(nodeToNudge, nudgeCheck, &args);
recurseNodeForNudge(nodeToNudge, nudgeCheck, &args);
}

View file

@ -253,6 +253,7 @@ private:
void emptyDeleteQueue();
// helper functions for nudgeSubTree
void recurseNodeForNudge(VoxelNode* node, RecurseVoxelTreeOperation operation, void* extraData);
static bool nudgeCheck(VoxelNode* node, void* extraData);
void nudgeLeaf(VoxelNode* node, void* extraData);
void chunkifyLeaf(VoxelNode* node);