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

This commit is contained in:
Stephen Birarda 2013-09-04 10:07:02 -07:00
commit b64e3dd82f
33 changed files with 1232 additions and 700 deletions

111
README.md
View file

@ -8,17 +8,18 @@ In this repository you'll find the source to many of the components in our
alpha-stage virtual world. The project embraces distributed development
and if you'd like to help, we'll pay you -- find out more at Worklist.net.
If you find a small bug and have a fix, pull requests are welcome. If you'd
like to get paid for your work, make sure you report the bug via a job on Worklist.net.
like to get paid for your work, make sure you report the bug via a job on
Worklist.net.
We're hiring! We're looking for skilled developers;
send your resume to hiring@highfidelity.io
Building Interface
Building Interface & other High Fidelity Components
=========
Interface is our OS X and Linux build-able
client for accessing our virtual world.
Interface is our OS X and Linux build-able client for accessing our virtual
world.
CMake
-----
@ -27,7 +28,8 @@ for your platform. You can download CMake at cmake.org
Create a build directory in the root of your checkout and then run the
CMake build from there. This will keep the rest of the directory clean,
and makes the gitignore a little easier to handle (since we can just ignore build).
and makes the gitignore a little easier to handle (since we can just ignore
build).
mkdir build
cd build
@ -37,40 +39,105 @@ 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)
Building in XCode
-----
After running cmake, you will have the make files or Xcode project file
necessary to build all of the components. For OS X, load Xcode, open the
hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target
drop down), and click Run.
If the build completes successfully, you will have built targets for all HiFi
components located in the build/target_name/Debug directories.
Other dependencies & information
----
In addition to CMake, Qt 5.1 is required to build all components.
What can I build on?
We have successfully built on OS X 10.8, Ubuntu and a few other modern Linux distributions.
A Windows build is planned for the future, but not currently in development.
We have successfully built on OS X 10.8, Ubuntu and a few other modern Linux
distributions. A Windows build is planned for the future, but not currently in
development.
Running Interface
-----
Using finder locate the interface.app Application in build/interface/Debug,
double-click the icon, and wait for interface to launch. At this point you will
connect to our default domain: "root.highfidelity.io".
I'm in-world, what can I do?
----
If you don't see anything, make sure your preferences are pointing to root.highfidelity.io,
if you still have no luck it's possible our servers are simply down; if you're experiencing
a major bug, let us know by suggesting a Job on Worklist.net -- make sure to include details
about your operating system and your computer system.
If you don't see anything, make sure your preferences are pointing to
root.highfidelity.io, if you still have no luck it's possible our servers are
simply down; if you're experiencing a major bug, let us know by suggesting a Job
on Worklist.net -- make sure to include details about your operating system and
your computer system.
To move around in-world, use the arrow keys (and Shift + up/down to fly up or down)
or W A S D, and E or C to fly up/down. All of the other possible options and features
are available via menus in the Interface application.
To move around in-world, use the arrow keys (and Shift + up/down to fly up or
down) or W A S D, and E or C to fly up/down. All of the other possible options
and features are available via menus in the Interface application.
Other components
========
voxel-server, animation-server, audio-mixer, avatar-mixer, domain-server, pairing-server
and space-server are architectural components that will allow you to run the full stack of
the virtual world should you choose to.
voxel-server, animation-server, audio-mixer, avatar-mixer, domain-server,
pairing-server and space-server are architectural components that will allow
you to run the full stack of the virtual world should you choose to.
I want to run my own virtual world!
========
In the voxel-server/src directory you will find a README that explains
how to setup and administer a voxel-server.
Keep in mind that, at a minimum, you must run a domain-server, voxel-server,
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.
Basic documentation for the other components is on its way.
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.
./domain-server/Debug/domain-server --local &
./voxel-server/Debug/voxel-server --local &
./avatar-mixer/Debug/avatar-mixer --local &
./audio-mixer/Debug/audio-mixer --local &
To confirm that the components are running you can type the following command:
ps ax | grep -w "domain-server\|voxel-server\|audio-mixer\|avatar-mixer"
You should see something like this:
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
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.
http://kb.iu.edu/data/aapa.html
On Mac OS X, and many Unix systems you can use the ifconfig command. Typically,
the following command will give you the IP address you need to use.
ifconfig | grep inet | grep broadcast
You should get something like this:
inet 192.168.1.104 netmask 0xffffff00 broadcast 192.168.1.255
Your IP address is the first set of numbers. In this case "192.168.1.104". You
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 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
server computer in the "Domain" edit control.
In the voxel-server/src directory you will find a README that explains in
further detail how to setup and administer a voxel-server.

View file

@ -82,7 +82,6 @@ const int STARTUP_JITTER_SAMPLES = PACKET_LENGTH_SAMPLES_PER_CHANNEL / 2;
// Startup optimistically with small jitter buffer that
// will start playback on the second received audio packet.
static const float CLIPBOARD_TREE_SCALE = 1.0f;
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message) {
fprintf(stdout, "%s", message.toLocal8Bit().constData());
@ -97,7 +96,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_frameCount(0),
_fps(120.0f),
_justStarted(true),
_clipboard(CLIPBOARD_TREE_SCALE),
_voxelImporter(_window),
_wantToKillLocalVoxels(false),
_audioScope(256, 200, true),
@ -237,6 +235,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
Application::~Application() {
NodeList::getInstance()->removeHook(&_voxels);
NodeList::getInstance()->removeHook(this);
_sharedVoxelSystem.changeTree(new VoxelTree);
}
void Application::initializeGL() {
@ -1041,10 +1041,8 @@ void Application::terminate() {
LeapManager::terminate();
if (Menu::getInstance()->isOptionChecked(MenuOption::SettingsAutosave)) {
Menu::getInstance()->saveSettings();
_settings->sync();
}
Menu::getInstance()->saveSettings();
_settings->sync();
if (_enableNetworkThread) {
_stopNetworkReceiveThread = true;
@ -1209,15 +1207,6 @@ void Application::exportVoxels() {
void Application::importVoxels() {
if (_voxelImporter.exec()) {
qDebug("[DEBUG] Import succedded.\n");
if (_voxelImporter.getImportIntoClipboard()) {
_clipboard.killLocalVoxels();
_voxelImporter.getVoxelSystem()->copySubTreeIntoNewTree(
_voxelImporter.getVoxelSystem()->getVoxelAt(0, 0, 0, 1),
&_clipboard,
true);
_voxelImporter.reset();
}
} else {
qDebug("[DEBUG] Import failed.\n");
}
@ -1232,13 +1221,17 @@ void Application::cutVoxels() {
}
void Application::copyVoxels() {
// switch to and clear the clipboard first...
_sharedVoxelSystem.killLocalVoxels();
if (_sharedVoxelSystem.getTree() != &_clipboard) {
_clipboard.eraseAllVoxels();
_sharedVoxelSystem.changeTree(&_clipboard);
}
// then copy onto it if there is something to copy
VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s);
if (selectedNode) {
// clear the clipboard first...
_clipboard.killLocalVoxels();
// then copy onto it
_voxels.copySubTreeIntoNewTree(selectedNode, &_clipboard, true);
_voxels.copySubTreeIntoNewTree(selectedNode, &_sharedVoxelSystem, true);
}
}
@ -1259,12 +1252,13 @@ void Application::pasteVoxels() {
args.newBaseOctCode = calculatedOctCode = pointToVoxel(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s);
}
if (_voxelImporter.getImportWaiting()) {
_voxelImporter.getVoxelSystem()->recurseTreeWithOperation(sendVoxelsOperation, &args);
_voxelImporter.reset();
} else {
_clipboard.recurseTreeWithOperation(sendVoxelsOperation, &args);
_sharedVoxelSystem.getTree()->recurseTreeWithOperation(sendVoxelsOperation, &args);
if (_sharedVoxelSystem.getTree() != &_clipboard) {
_sharedVoxelSystem.killLocalVoxels();
_sharedVoxelSystem.changeTree(&_clipboard);
}
_voxelEditSender.flushQueue();
if (calculatedOctCode) {
@ -1306,10 +1300,21 @@ void Application::initDisplay() {
void Application::init() {
_voxels.init();
_clipboard.init();
_clipboardViewFrustum.setKeyholeRadius(1000.0f);
_clipboardViewFrustum.calculate();
_clipboard.setViewFrustum(&_clipboardViewFrustum);
_sharedVoxelSystemViewFrustum.setPosition(glm::vec3(TREE_SCALE / 2.0f,
TREE_SCALE / 2.0f,
3.0f * TREE_SCALE / 2.0f));
_sharedVoxelSystemViewFrustum.setNearClip(TREE_SCALE / 2.0f);
_sharedVoxelSystemViewFrustum.setFarClip(3.0f * TREE_SCALE / 2.0f);
_sharedVoxelSystemViewFrustum.setFieldOfView(90);
_sharedVoxelSystemViewFrustum.setOrientation(glm::quat());
_sharedVoxelSystemViewFrustum.calculate();
_sharedVoxelSystem.setViewFrustum(&_sharedVoxelSystemViewFrustum);
_sharedVoxelSystem.init();
VoxelTree* tmpTree = _sharedVoxelSystem.getTree();
_sharedVoxelSystem.changeTree(&_clipboard);
delete tmpTree;
_voxelImporter.init();
_environment.init();
@ -1320,6 +1325,7 @@ void Application::init() {
_headMouseX = _mouseX = _glWidget->width() / 2;
_headMouseY = _mouseY = _glWidget->height() / 2;
QCursor::setPos(_headMouseX, _headMouseY);
_myAvatar.init();
_myAvatar.setPosition(START_LOCATION);
@ -1327,7 +1333,6 @@ void Application::init() {
_myCamera.setModeShiftRate(1.0f);
_myAvatar.setDisplayingLookatVectors(false);
QCursor::setPos(_headMouseX, _headMouseY);
OculusManager::connect();
if (OculusManager::isConnected()) {
@ -1384,7 +1389,9 @@ Avatar* Application::isLookingAtOtherAvatar(glm::vec3& mouseRayOrigin, glm::vec3
if (node->getLinkedData() != NULL && node->getType() == NODE_TYPE_AGENT) {
Avatar* avatar = (Avatar *) node->getLinkedData();
glm::vec3 headPosition = avatar->getHead().getPosition();
if (rayIntersectsSphere(mouseRayOrigin, mouseRayDirection, headPosition, HEAD_SPHERE_RADIUS * avatar->getScale())) {
float distance;
if (rayIntersectsSphere(mouseRayOrigin, mouseRayDirection, headPosition,
HEAD_SPHERE_RADIUS * avatar->getScale(), distance)) {
eyePosition = avatar->getHead().getEyePosition();
_lookatIndicatorScale = avatar->getScale();
_lookatOtherPosition = headPosition;
@ -1722,6 +1729,40 @@ void Application::update(float deltaTime) {
_myAvatar.simulate(deltaTime, NULL, Menu::getInstance()->getGyroCameraSensitivity());
}
// no transmitter drive implies transmitter pick
if (!Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _myTransmitter.isConnected()) {
_transmitterPickStart = _myAvatar.getSkeleton().joint[AVATAR_JOINT_CHEST].position;
glm::vec3 direction = _myAvatar.getOrientation() *
glm::quat(glm::radians(_myTransmitter.getEstimatedRotation())) * IDENTITY_FRONT;
// check against voxels, avatars
const float MAX_PICK_DISTANCE = 100.0f;
float minDistance = MAX_PICK_DISTANCE;
VoxelDetail detail;
float distance;
BoxFace face;
if (_voxels.findRayIntersection(_transmitterPickStart, direction, detail, distance, face)) {
minDistance = min(minDistance, distance);
}
for(NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
node->lock();
if (node->getLinkedData() != NULL) {
Avatar *avatar = (Avatar*)node->getLinkedData();
if (!avatar->isInitialized()) {
avatar->init();
}
if (avatar->findRayIntersection(_transmitterPickStart, direction, distance)) {
minDistance = min(minDistance, distance);
}
}
node->unlock();
}
_transmitterPickEnd = _transmitterPickStart + direction * minDistance;
} else {
_transmitterPickStart = _transmitterPickEnd = glm::vec3();
}
if (!OculusManager::isConnected()) {
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
@ -2191,15 +2232,11 @@ void Application::displaySide(Camera& whichCamera) {
glTranslatef(_mouseVoxel.x * TREE_SCALE,
_mouseVoxel.y * TREE_SCALE,
_mouseVoxel.z * TREE_SCALE);
glScalef(_mouseVoxel.s * TREE_SCALE,
_mouseVoxel.s * TREE_SCALE,
_mouseVoxel.s * TREE_SCALE);
glScalef(_mouseVoxel.s,
_mouseVoxel.s,
_mouseVoxel.s);
if (_voxelImporter.getImportWaiting()) {
_voxelImporter.getVoxelSystem()->render(true);
} else {
_clipboard.render(true);
}
_sharedVoxelSystem.render(true);
glPopMatrix();
}
@ -2271,6 +2308,27 @@ void Application::displaySide(Camera& whichCamera) {
}
renderFollowIndicator();
// render transmitter pick ray, if non-empty
if (_transmitterPickStart != _transmitterPickEnd) {
Glower glower;
const float TRANSMITTER_PICK_COLOR[] = { 1.0f, 1.0f, 0.0f };
glColor3fv(TRANSMITTER_PICK_COLOR);
glLineWidth(3.0f);
glBegin(GL_LINES);
glVertex3f(_transmitterPickStart.x, _transmitterPickStart.y, _transmitterPickStart.z);
glVertex3f(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z);
glEnd();
glLineWidth(1.0f);
glPushMatrix();
glTranslatef(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z);
const float PICK_END_RADIUS = 0.025f;
glutSolidSphere(PICK_END_RADIUS, 8, 8);
glPopMatrix();
}
}
void Application::displayOverlay() {

View file

@ -112,6 +112,8 @@ public:
Camera* getCamera() { return &_myCamera; }
ViewFrustum* getViewFrustum() { return &_viewFrustum; }
VoxelSystem* getVoxels() { return &_voxels; }
VoxelSystem* getSharedVoxelSystem() { return &_sharedVoxelSystem; }
VoxelTree* getClipboard() { return &_clipboard; }
Environment* getEnvironment() { return &_environment; }
SerialInterface* getSerialHeadSensor() { return &_serialHeadSensor; }
Webcam* getWebcam() { return &_webcam; }
@ -238,10 +240,11 @@ private:
Stars _stars;
VoxelSystem _voxels;
VoxelSystem _clipboard; // if I copy/paste
ViewFrustum _clipboardViewFrustum;
VoxelSystem _voxels;
VoxelTree _clipboard; // if I copy/paste
VoxelImporter _voxelImporter;
VoxelSystem _sharedVoxelSystem;
ViewFrustum _sharedVoxelSystemViewFrustum;
QByteArray _voxelsFilename;
bool _wantToKillLocalVoxels;
@ -298,6 +301,9 @@ private:
glm::vec3 _lookatOtherPosition;
float _lookatIndicatorScale;
glm::vec3 _transmitterPickStart;
glm::vec3 _transmitterPickEnd;
bool _perfStatsOn; // Do we want to display perfStats?
ChatEntry _chatEntry; // chat entry field

View file

@ -34,9 +34,9 @@ const float FIELD_OF_VIEW = 60.0f;
class GLWidget : public QGLWidget {
public:
GLWidget(QWidget* parent = NULL, VoxelSystem* voxelSystem = NULL);
GLWidget(QWidget* parent = NULL);
void setDraw(bool draw) {_draw = draw;}
void setTargetCenter(glm::vec3 targetCenter) {_targetCenter = targetCenter;}
void setTargetCenter(glm::vec3 targetCenter) { _targetCenter = targetCenter; }
protected:
virtual void initializeGL();
@ -61,9 +61,8 @@ private:
int _mouseY;
};
GLWidget::GLWidget(QWidget *parent, VoxelSystem *voxelSystem)
GLWidget::GLWidget(QWidget *parent)
: QGLWidget(parent, Application::getInstance()->getGLWidget()),
_voxelSystem(voxelSystem),
_draw(false),
_a(0.0f),
_h(VERTICAL_ANGLE),
@ -71,6 +70,7 @@ GLWidget::GLWidget(QWidget *parent, VoxelSystem *voxelSystem)
_pressed(false),
_mouseX(0),
_mouseY(0) {
_voxelSystem = Application::getInstance()->getSharedVoxelSystem();
}
void GLWidget::initializeGL() {
@ -110,7 +110,7 @@ void GLWidget::paintGL() {
UP_VECT.x, UP_VECT.y, UP_VECT.z);
if (_draw && _voxelSystem) {
if (_draw) {
glBegin(GL_LINES);
glColor3d(1, 1 ,1);
glVertex3d(0, 0, 0);
@ -134,11 +134,11 @@ void GLWidget::paintGL() {
glVertex3d(2 * _targetCenter.x, 2 * _targetCenter.y, 0 );
glEnd();
glScalef(1.0f / TREE_SCALE, 1.0f / TREE_SCALE, 1.0f / TREE_SCALE);
_voxelSystem->render(false);
}
}
void GLWidget::mousePressEvent(QMouseEvent* event) {
_pressed = true;
_mouseX = event->globalX();
@ -158,14 +158,13 @@ void GLWidget::mouseReleaseEvent(QMouseEvent* event) {
_pressed = false;
}
ImportDialog::ImportDialog(QWidget *parent, VoxelSystem* voxelSystem)
ImportDialog::ImportDialog(QWidget *parent)
: QFileDialog(parent, WINDOW_NAME, DESKTOP_LOCATION, IMPORT_FILE_TYPES),
_importButton (IMPORT_BUTTON_NAME, this),
_clipboardImportBox(IMPORT_TO_CLIPBOARD_CHECKBOX_STRING, this),
_previewBox (PREVIEW_CHECKBOX_STRING, this),
_previewBar (this),
_glPreview (new GLWidget(this, voxelSystem)) {
_glPreview (new GLWidget(this)) {
setOption(QFileDialog::DontUseNativeDialog, true);
setFileMode(QFileDialog::ExistingFile);
setViewMode(QFileDialog::Detail);
@ -188,15 +187,18 @@ ImportDialog::ImportDialog(QWidget *parent, VoxelSystem* voxelSystem)
connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString)));
connect(&_glTimer, SIGNAL(timeout()), SLOT(timer()));
connect(voxelSystem, SIGNAL(importSize(float,float,float)), SLOT(setGLCamera(float, float, float)));
connect(voxelSystem, SIGNAL(importProgress(int)), &_previewBar, SLOT(setValue(int)));
}
ImportDialog::~ImportDialog() {
delete _glPreview;
}
void ImportDialog::init() {
VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem();
connect(voxelSystem, SIGNAL(importSize(float,float,float)), SLOT(setGLCamera(float,float,float)));
connect(voxelSystem, SIGNAL(importProgress(int)), &_previewBar, SLOT(setValue(int)));
}
void ImportDialog::import() {
_importButton.setDisabled(true);
_clipboardImportBox.setDisabled(true);

View file

@ -23,15 +23,16 @@ class GLWidget;
class ImportDialog : public QFileDialog {
Q_OBJECT
public:
ImportDialog(QWidget* parent = NULL, VoxelSystem* voxelSystem = NULL);
ImportDialog(QWidget* parent = NULL);
~ImportDialog();
void init();
void reset();
bool getWantPreview() const { return _previewBox.isChecked(); }
QString getCurrentFile() const { return _currentFile; }
bool getImportIntoClipboard() const { return _clipboardImportBox.isChecked(); }
void reset();
signals:
void previewToggled(bool);
void accepted();

View file

@ -52,12 +52,7 @@ Menu::Menu() :
Application *appInstance = Application::getInstance();
QMenu* fileMenu = addMenu("File");
(addActionToQMenuAndActionHash(fileMenu,
MenuOption::Quit,
Qt::CTRL | Qt::Key_Q,
appInstance,
SLOT(quit())))->setMenuRole(QAction::QuitRole);
(addActionToQMenuAndActionHash(fileMenu,
MenuOption::Preferences,
Qt::CTRL | Qt::Key_Comma,
@ -66,159 +61,89 @@ Menu::Menu() :
#if defined(Q_OS_MAC) && defined(QT_NO_DEBUG)
// show "Check for Updates" in the menu
(addActionToQMenuAndActionHash(fileMenu, MenuOption::CheckForUpdates, 0, this, SLOT(checkForUpdates())))->setMenuRole(QAction::ApplicationSpecificRole);
(addActionToQMenuAndActionHash(fileMenu,
MenuOption::CheckForUpdates,
0,
this,
SLOT(checkForUpdates())))->setMenuRole(QAction::ApplicationSpecificRole);
#endif
QMenu* pairMenu = addMenu("Pair");
addActionToQMenuAndActionHash(pairMenu, MenuOption::Pair, 0, PairingHandler::getInstance(), SLOT(sendPairRequest()));
addDisabledActionAndSeparator(fileMenu, "Voxels");
addActionToQMenuAndActionHash(fileMenu, MenuOption::ExportVoxels, Qt::CTRL | Qt::Key_E, appInstance, SLOT(exportVoxels()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::ImportVoxels, Qt::CTRL | Qt::Key_I, appInstance, SLOT(importVoxels()));
addDisabledActionAndSeparator(fileMenu, "Go");
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoHome,
Qt::CTRL | Qt::Key_G,
appInstance->getAvatar(),
SLOT(goHome()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoToDomain,
Qt::CTRL | Qt::Key_D,
this,
SLOT(goToDomain()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoToLocation,
Qt::CTRL | Qt::SHIFT | Qt::Key_L,
this,
SLOT(goToLocation()));
QMenu* optionsMenu = addMenu("Options");
addDisabledActionAndSeparator(fileMenu, "Settings");
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsExport, 0, this, SLOT(exportSettings()));
addCheckableActionToQMenuAndActionHash(optionsMenu, MenuOption::Mirror, Qt::Key_H);
addCheckableActionToQMenuAndActionHash(optionsMenu, MenuOption::GyroLook, 0, true);
addCheckableActionToQMenuAndActionHash(optionsMenu, MenuOption::HeadMouse);
addCheckableActionToQMenuAndActionHash(optionsMenu, MenuOption::TransmitterDrive, 0, true);
addCheckableActionToQMenuAndActionHash(optionsMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, true);
addCheckableActionToQMenuAndActionHash(optionsMenu, MenuOption::TestPing, 0, true);
addDisabledActionAndSeparator(fileMenu, "Devices");
addActionToQMenuAndActionHash(fileMenu, MenuOption::Pair, 0, PairingHandler::getInstance(), SLOT(sendPairRequest()));
addCheckableActionToQMenuAndActionHash(fileMenu, MenuOption::TransmitterDrive, 0, true);
addCheckableActionToQMenuAndActionHash(optionsMenu,
MenuOption::Fullscreen,
Qt::Key_F,
false,
appInstance,
SLOT(setFullscreen(bool)));
(addActionToQMenuAndActionHash(fileMenu,
MenuOption::Quit,
Qt::CTRL | Qt::Key_Q,
appInstance,
SLOT(quit())))->setMenuRole(QAction::QuitRole);
addCheckableActionToQMenuAndActionHash(optionsMenu,
MenuOption::Webcam,
0,
false,
appInstance->getWebcam(),
SLOT(setEnabled(bool)));
QMenu* editMenu = addMenu("Edit");
addActionToQMenuAndActionHash(editMenu, MenuOption::CutVoxels, Qt::CTRL | Qt::Key_X, appInstance, SLOT(cutVoxels()));
addActionToQMenuAndActionHash(editMenu, MenuOption::CopyVoxels, Qt::CTRL | Qt::Key_C, appInstance, SLOT(copyVoxels()));
addActionToQMenuAndActionHash(editMenu, MenuOption::PasteVoxels, Qt::CTRL | Qt::Key_V, appInstance, SLOT(pasteVoxels()));
addCheckableActionToQMenuAndActionHash(optionsMenu,
MenuOption::SkeletonTracking,
0,
false,
appInstance->getWebcam(),
SLOT(setSkeletonTrackingOn(bool)));
addCheckableActionToQMenuAndActionHash(optionsMenu,
addDisabledActionAndSeparator(editMenu, "Physics");
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, true);
addCheckableActionToQMenuAndActionHash(editMenu,
MenuOption::Collisions,
0,
true,
appInstance->getAvatar(),
SLOT(setWantCollisionsOn(bool)));
addActionToQMenuAndActionHash(optionsMenu,
MenuOption::WebcamMode,
0,
appInstance->getWebcam()->getGrabber(),
SLOT(cycleVideoSendMode()));
addCheckableActionToQMenuAndActionHash(optionsMenu,
MenuOption::WebcamTexture,
0,
false,
appInstance->getWebcam()->getGrabber(),
SLOT(setDepthOnly(bool)));
addActionToQMenuAndActionHash(optionsMenu,
MenuOption::GoHome,
Qt::CTRL | Qt::Key_G,
appInstance->getAvatar(),
SLOT(goHome()));
QMenu* audioMenu = addMenu("Audio");
addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::EchoAudio);
QMenu* renderMenu = addMenu("Render");
addCheckableActionToQMenuAndActionHash(renderMenu,
MenuOption::Voxels,
Qt::SHIFT | Qt::Key_V,
true,
appInstance,
SLOT(setRenderVoxels(bool)));
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::VoxelTextures);
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::Stars, 0, true);
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true);
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::GroundPlane, 0, true);
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::Avatars, 0, true);
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::AvatarAsBalls);
addActionToQMenuAndActionHash(renderMenu,
MenuOption::VoxelMode,
0,
appInstance->getAvatar()->getVoxels(),
SLOT(cycleMode()));
addActionToQMenuAndActionHash(renderMenu,
MenuOption::FaceMode,
0,
&appInstance->getAvatar()->getHead().getFace(),
SLOT(cycleRenderMode()));
addActionToQMenuAndActionHash(renderMenu,
MenuOption::GlowMode,
0,
appInstance->getGlowEffect(),
SLOT(cycleRenderMode()));
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::FrameTimer);
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::LookAtVectors, 0, true);
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::LookAtIndicator, 0, true);
addCheckableActionToQMenuAndActionHash(renderMenu, MenuOption::FirstPerson, Qt::Key_P, true);
addActionToQMenuAndActionHash(renderMenu,
MenuOption::IncreaseAvatarSize,
Qt::Key_Plus,
appInstance->getAvatar(),
SLOT(increaseSize()));
addActionToQMenuAndActionHash(renderMenu,
MenuOption::DecreaseAvatarSize,
Qt::Key_Minus,
appInstance->getAvatar(),
SLOT(decreaseSize()));
addActionToQMenuAndActionHash(renderMenu,
MenuOption::ResetAvatarSize,
0,
appInstance->getAvatar(),
SLOT(resetSize()));
QMenu* toolsMenu = addMenu("Tools");
addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::Stats, Qt::Key_Slash);
addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::Log, Qt::CTRL | Qt::Key_L);
addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::Oscilloscope, 0, true);
addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::Bandwidth, 0, true);
addActionToQMenuAndActionHash(toolsMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails()));
addActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelStats, 0, this, SLOT(voxelStatsDetails()));
QMenu* voxelMenu = addMenu("Voxels");
_voxelModeActionsGroup = new QActionGroup(this);
_voxelModeActionsGroup->setExclusive(false);
QAction* addVoxelMode = addCheckableActionToQMenuAndActionHash(voxelMenu, MenuOption::VoxelAddMode, Qt::Key_V);
QAction* addVoxelMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelAddMode, Qt::Key_V);
_voxelModeActionsGroup->addAction(addVoxelMode);
QAction* deleteVoxelMode = addCheckableActionToQMenuAndActionHash(voxelMenu, MenuOption::VoxelDeleteMode, Qt::Key_R);
QAction* deleteVoxelMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelDeleteMode, Qt::Key_R);
_voxelModeActionsGroup->addAction(deleteVoxelMode);
QAction* colorVoxelMode = addCheckableActionToQMenuAndActionHash(voxelMenu, MenuOption::VoxelColorMode, Qt::Key_B);
QAction* colorVoxelMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelColorMode, Qt::Key_B);
_voxelModeActionsGroup->addAction(colorVoxelMode);
QAction* selectVoxelMode = addCheckableActionToQMenuAndActionHash(voxelMenu, MenuOption::VoxelSelectMode, Qt::Key_O);
QAction* selectVoxelMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelSelectMode, Qt::Key_O);
_voxelModeActionsGroup->addAction(selectVoxelMode);
QAction* getColorMode = addCheckableActionToQMenuAndActionHash(voxelMenu, MenuOption::VoxelGetColorMode, Qt::Key_G);
QAction* getColorMode = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::VoxelGetColorMode, Qt::Key_G);
_voxelModeActionsGroup->addAction(getColorMode);
// connect each of the voxel mode actions to the updateVoxelModeActionsSlot
foreach (QAction* action, _voxelModeActionsGroup->actions()) {
connect(action, SIGNAL(triggered()), this, SLOT(updateVoxelModeActions()));
}
QAction* voxelPaintColor = addActionToQMenuAndActionHash(voxelMenu,
QAction* voxelPaintColor = addActionToQMenuAndActionHash(toolsMenu,
MenuOption::VoxelPaintColor,
Qt::META | Qt::Key_C,
this,
@ -230,29 +155,106 @@ Menu::Menu() :
voxelPaintColor->setData(paintColor);
voxelPaintColor->setIcon(Swatch::createIcon(paintColor));
addActionToQMenuAndActionHash(voxelMenu,
addActionToQMenuAndActionHash(toolsMenu,
MenuOption::DecreaseVoxelSize,
QKeySequence::ZoomOut,
appInstance,
SLOT(decreaseVoxelSize()));
addActionToQMenuAndActionHash(voxelMenu,
addActionToQMenuAndActionHash(toolsMenu,
MenuOption::IncreaseVoxelSize,
QKeySequence::ZoomIn,
appInstance,
SLOT(increaseVoxelSize()));
addActionToQMenuAndActionHash(voxelMenu, MenuOption::ResetSwatchColors, 0, this, SLOT(resetSwatchColors()));
addCheckableActionToQMenuAndActionHash(voxelMenu, MenuOption::DestructiveAddVoxel);
addActionToQMenuAndActionHash(voxelMenu, MenuOption::ExportVoxels, Qt::CTRL | Qt::Key_E, appInstance, SLOT(exportVoxels()));
addActionToQMenuAndActionHash(voxelMenu, MenuOption::ImportVoxels, Qt::CTRL | Qt::Key_I, appInstance, SLOT(importVoxels()));
addActionToQMenuAndActionHash(voxelMenu, MenuOption::CutVoxels, Qt::CTRL | Qt::Key_X, appInstance, SLOT(cutVoxels()));
addActionToQMenuAndActionHash(voxelMenu, MenuOption::CopyVoxels, Qt::CTRL | Qt::Key_C, appInstance, SLOT(copyVoxels()));
addActionToQMenuAndActionHash(voxelMenu, MenuOption::PasteVoxels, Qt::CTRL | Qt::Key_V, appInstance, SLOT(pasteVoxels()));
QMenu* debugMenu = addMenu("Debug");
addActionToQMenuAndActionHash(toolsMenu, MenuOption::ResetSwatchColors, 0, this, SLOT(resetSwatchColors()));
QMenu* frustumMenu = debugMenu->addMenu("View Frustum Debugging Tools");
QMenu* viewMenu = addMenu("View");
addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::Fullscreen,
Qt::Key_F,
false,
appInstance,
SLOT(setFullscreen(bool)));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true);
addActionToQMenuAndActionHash(viewMenu,
MenuOption::IncreaseAvatarSize,
Qt::Key_Plus,
appInstance->getAvatar(),
SLOT(increaseSize()));
addActionToQMenuAndActionHash(viewMenu,
MenuOption::DecreaseAvatarSize,
Qt::Key_Minus,
appInstance->getAvatar(),
SLOT(decreaseSize()));
addActionToQMenuAndActionHash(viewMenu,
MenuOption::ResetAvatarSize,
0,
appInstance->getAvatar(),
SLOT(resetSize()));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::Key_H);
addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::SkeletonTracking,
0,
false,
appInstance->getWebcam(),
SLOT(setSkeletonTrackingOn(bool)));
addDisabledActionAndSeparator(viewMenu, "Stats");
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::Key_L);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Oscilloscope, 0, true);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Bandwidth, 0, true);
addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails()));
addActionToQMenuAndActionHash(viewMenu, MenuOption::VoxelStats, 0, this, SLOT(voxelStatsDetails()));
QMenu* developerMenu = addMenu("Developer");
addDisabledActionAndSeparator(developerMenu, "Rendering");
addCheckableActionToQMenuAndActionHash(developerMenu,
MenuOption::Voxels,
Qt::SHIFT | Qt::Key_V,
true,
appInstance,
SLOT(setRenderVoxels(bool)));
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::VoxelTextures);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AmbientOcclusion);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stars, 0, true);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::GroundPlane, 0, true);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Avatars, 0, true);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AvatarAsBalls);
addActionToQMenuAndActionHash(developerMenu,
MenuOption::VoxelMode,
0,
appInstance->getAvatar()->getVoxels(),
SLOT(cycleMode()));
addActionToQMenuAndActionHash(developerMenu,
MenuOption::FaceMode,
0,
&appInstance->getAvatar()->getHead().getFace(),
SLOT(cycleRenderMode()));
addActionToQMenuAndActionHash(developerMenu,
MenuOption::GlowMode,
0,
appInstance->getGlowEffect(),
SLOT(cycleRenderMode()));
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::LookAtVectors, 0, true);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::LookAtIndicator, 0, true);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::FrameTimer);
addDisabledActionAndSeparator(developerMenu, "Testing");
QMenu* frustumMenu = developerMenu->addMenu("View Frustum Debugging Tools");
addCheckableActionToQMenuAndActionHash(frustumMenu, MenuOption::DisplayFrustum, Qt::SHIFT | Qt::Key_F);
addActionToQMenuAndActionHash(frustumMenu,
@ -262,14 +264,14 @@ Menu::Menu() :
SLOT(cycleFrustumRenderMode()));
updateFrustumRenderModeAction();
addActionToQMenuAndActionHash(debugMenu, MenuOption::RunTimingTests, 0, this, SLOT(runTests()));
addActionToQMenuAndActionHash(debugMenu,
addActionToQMenuAndActionHash(developerMenu, MenuOption::RunTimingTests, 0, this, SLOT(runTests()));
addActionToQMenuAndActionHash(developerMenu,
MenuOption::TreeStats,
Qt::SHIFT | Qt::Key_S,
appInstance->getVoxels(),
SLOT(collectStatsForTreesAndVBOs()));
QMenu* renderDebugMenu = debugMenu->addMenu("Render Debugging Tools");
QMenu* renderDebugMenu = developerMenu->addMenu("Render Debugging Tools");
addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings);
addActionToQMenuAndActionHash(renderDebugMenu,
@ -330,41 +332,29 @@ Menu::Menu() :
Qt::CTRL | Qt::Key_T,
appInstance->getVoxels(),
SLOT(trueColorize()));
addCheckableActionToQMenuAndActionHash(debugMenu,
MenuOption::SendVoxelColors,
addCheckableActionToQMenuAndActionHash(developerMenu,
MenuOption::Webcam,
0,
true,
appInstance->getAvatar(),
SLOT(setWantColor(bool)));
addCheckableActionToQMenuAndActionHash(debugMenu,
MenuOption::LowRes,
false,
appInstance->getWebcam(),
SLOT(setEnabled(bool)));
addActionToQMenuAndActionHash(developerMenu,
MenuOption::WebcamMode,
0,
appInstance->getWebcam()->getGrabber(),
SLOT(cycleVideoSendMode()));
addCheckableActionToQMenuAndActionHash(developerMenu,
MenuOption::WebcamTexture,
0,
true,
appInstance->getAvatar(),
SLOT(setWantLowResMoving(bool)));
false,
appInstance->getWebcam()->getGrabber(),
SLOT(setDepthOnly(bool)));
addCheckableActionToQMenuAndActionHash(debugMenu,
MenuOption::DeltaSending,
0,
true,
appInstance->getAvatar(),
SLOT(setWantDelta(bool)));
addCheckableActionToQMenuAndActionHash(debugMenu,
MenuOption::OcclusionCulling,
Qt::SHIFT | Qt::Key_C,
true,
appInstance->getAvatar(),
SLOT(setWantOcclusionCulling(bool)));
addCheckableActionToQMenuAndActionHash(debugMenu, MenuOption::CoverageMap, Qt::SHIFT | Qt::CTRL | Qt::Key_O);
addCheckableActionToQMenuAndActionHash(debugMenu, MenuOption::CoverageMapV2, Qt::SHIFT | Qt::CTRL | Qt::Key_P);
addCheckableActionToQMenuAndActionHash(debugMenu, MenuOption::SimulateLeapHand);
addCheckableActionToQMenuAndActionHash(debugMenu, MenuOption::TestRaveGlove);
QMenu* audioDebugMenu = debugMenu->addMenu("Audio Debugging Tools");
QMenu* audioDebugMenu = developerMenu->addMenu("Audio Debugging Tools");
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoAudio);
addActionToQMenuAndActionHash(audioDebugMenu,
MenuOption::ListenModeNormal,
Qt::CTRL | Qt::Key_1,
@ -381,12 +371,46 @@ Menu::Menu() :
appInstance,
SLOT(setListenModeSingleSource()));
QMenu* settingsMenu = addMenu("Settings");
addCheckableActionToQMenuAndActionHash(settingsMenu, MenuOption::SettingsAutosave, 0, true);
addActionToQMenuAndActionHash(settingsMenu, MenuOption::SettingsLoad, 0, this, SLOT(loadSettings()));
addActionToQMenuAndActionHash(settingsMenu, MenuOption::SettingsSave, 0, this, SLOT(saveSettings()));
addActionToQMenuAndActionHash(settingsMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings()));
addActionToQMenuAndActionHash(settingsMenu, MenuOption::SettingsExport, 0, this, SLOT(exportSettings()));
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::TestPing, 0, true);
addCheckableActionToQMenuAndActionHash(developerMenu,
MenuOption::SendVoxelColors,
0,
true,
appInstance->getAvatar(),
SLOT(setWantColor(bool)));
addCheckableActionToQMenuAndActionHash(developerMenu,
MenuOption::LowRes,
0,
true,
appInstance->getAvatar(),
SLOT(setWantLowResMoving(bool)));
addCheckableActionToQMenuAndActionHash(developerMenu,
MenuOption::DeltaSending,
0,
true,
appInstance->getAvatar(),
SLOT(setWantDelta(bool)));
addCheckableActionToQMenuAndActionHash(developerMenu,
MenuOption::OcclusionCulling,
Qt::SHIFT | Qt::Key_C,
true,
appInstance->getAvatar(),
SLOT(setWantOcclusionCulling(bool)));
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::CoverageMap, Qt::SHIFT | Qt::CTRL | Qt::Key_O);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::CoverageMapV2, Qt::SHIFT | Qt::CTRL | Qt::Key_P);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::SimulateLeapHand);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::TestRaveGlove);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::GyroLook, 0, true);
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::HeadMouse);
addDisabledActionAndSeparator(developerMenu, "Voxels");
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DestructiveAddVoxel);
}
void Menu::loadSettings(QSettings* settings) {
@ -553,6 +577,11 @@ void Menu::handleViewFrustumOffsetKeyModifier(int key) {
}
}
void Menu::addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName) {
destinationMenu->addSeparator();
(destinationMenu->addAction(actionName))->setEnabled(false);
}
QAction* Menu::addActionToQMenuAndActionHash(QMenu* destinationMenu,
const QString actionName,
const QKeySequence& shortcut,
@ -607,7 +636,7 @@ bool Menu::isVoxelModeActionChecked() {
}
void Menu::editPreferences() {
Application *applicationInstance = Application::getInstance();
Application* applicationInstance = Application::getInstance();
QDialog dialog(applicationInstance->getGLWidget());
dialog.setWindowTitle("Interface Preferences");
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
@ -651,9 +680,11 @@ void Menu::editPreferences() {
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
layout->addWidget(buttons);
if (dialog.exec() != QDialog::Accepted) {
return;
}
int ret = dialog.exec();
applicationInstance->getWindow()->activateWindow();
if (ret != QDialog::Accepted) {
return;
}
QByteArray newHostname;
@ -698,6 +729,118 @@ void Menu::editPreferences() {
applicationInstance->resizeGL(applicationInstance->getGLWidget()->width(), applicationInstance->getGLWidget()->height());
}
void Menu::goToDomain() {
Application* applicationInstance = Application::getInstance();
QDialog dialog(applicationInstance->getGLWidget());
dialog.setWindowTitle("Go To Domain");
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
dialog.setLayout(layout);
QFormLayout* form = new QFormLayout();
layout->addLayout(form, 1);
const int QLINE_MINIMUM_WIDTH = 400;
QLineEdit* domainServerHostname = new QLineEdit(QString(NodeList::getInstance()->getDomainHostname()));
domainServerHostname->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Domain server:", domainServerHostname);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
layout->addWidget(buttons);
int ret = dialog.exec();
applicationInstance->getWindow()->activateWindow();
if (ret != QDialog::Accepted) {
return;
}
QByteArray newHostname;
if (domainServerHostname->text().size() > 0) {
// the user input a new hostname, use that
newHostname = domainServerHostname->text().toLocal8Bit();
} else {
// the user left the field blank, use the default hostname
newHostname = QByteArray(DEFAULT_DOMAIN_HOSTNAME);
}
// check if the domain server hostname is new
if (memcmp(NodeList::getInstance()->getDomainHostname(), newHostname.constData(), newHostname.size()) != 0) {
NodeList::getInstance()->clear();
// kill the local voxels
applicationInstance->getVoxels()->killLocalVoxels();
// reset the environment to default
applicationInstance->getEnvironment()->resetToDefault();
// set the new hostname
NodeList::getInstance()->setDomainHostname(newHostname.constData());
}
}
void Menu::goToLocation() {
Application* applicationInstance = Application::getInstance();
QDialog dialog(applicationInstance->getGLWidget());
dialog.setWindowTitle("Go To Location");
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
dialog.setLayout(layout);
QFormLayout* form = new QFormLayout();
layout->addLayout(form, 1);
const int QLINE_MINIMUM_WIDTH = 300;
Application* appInstance = Application::getInstance();
MyAvatar* myAvatar = appInstance->getAvatar();
glm::vec3 avatarPos = myAvatar->getPosition();
QString currentLocation = QString("%1, %2, %3").arg(QString::number(avatarPos.x),
QString::number(avatarPos.y), QString::number(avatarPos.z));
QLineEdit* coordinates = new QLineEdit(currentLocation);
coordinates->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Coordinates as x,y,z:", coordinates);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
layout->addWidget(buttons);
int ret = dialog.exec();
applicationInstance->getWindow()->activateWindow();
if (ret != QDialog::Accepted) {
return;
}
QByteArray newCoordinates;
if (coordinates->text().size() > 0) {
// the user input a new hostname, use that
QString delimiterPattern(",");
QStringList coordinateItems = coordinates->text().split(delimiterPattern);
const int NUMBER_OF_COORDINATE_ITEMS = 3;
const int X_ITEM = 0;
const int Y_ITEM = 1;
const int Z_ITEM = 2;
if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
double x = coordinateItems[X_ITEM].toDouble();
double y = coordinateItems[Y_ITEM].toDouble();
double z = coordinateItems[Z_ITEM].toDouble();
glm::vec3 newAvatarPos(x, y, z);
if (newAvatarPos != avatarPos) {
qDebug("Going To Location: %f, %f, %f...\n", x, y, z);
myAvatar->setPosition(newAvatarPos);
}
}
}
}
void Menu::bandwidthDetails() {

View file

@ -62,6 +62,8 @@ public slots:
private slots:
void editPreferences();
void goToDomain();
void goToLocation();
void bandwidthDetailsClosed();
void voxelStatsDetailsClosed();
void cycleFrustumRenderMode();
@ -81,6 +83,8 @@ private:
void scanMenuBar(settingsAction modifySetting, QSettings* set);
void scanMenu(QMenu* menu, settingsAction modifySetting, QSettings* set);
/// helper method to have separators with labels that are also compatible with OS X
void addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName);
QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu,
const QString actionName,
const QKeySequence& shortcut = 0,
@ -116,10 +120,10 @@ namespace MenuOption {
const QString BandwidthDetails = "Bandwidth Details";
const QString CheckForUpdates = "Check for Updates...";
const QString Collisions = "Collisions";
const QString CopyVoxels = "Copy Voxels";
const QString CopyVoxels = "Copy";
const QString CoverageMap = "Render Coverage Map";
const QString CoverageMapV2 = "Render Coverage Map V2";
const QString CutVoxels = "Cut Voxels";
const QString CutVoxels = "Cut";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DecreaseVoxelSize = "Decrease Voxel Size";
const QString DestructiveAddVoxel = "Create Voxel is Destructive";
@ -141,6 +145,8 @@ namespace MenuOption {
const QString FrustumRenderMode = "Render Mode";
const QString Fullscreen = "Fullscreen";
const QString GlowMode = "Cycle Glow Mode";
const QString GoToDomain = "Go To Domain...";
const QString GoToLocation = "Go To Location...";
const QString ImportVoxels = "Import Voxels";
const QString ImportVoxelsClipboard = "Import Voxels to Clipboard";
const QString IncreaseAvatarSize = "Increase Avatar Size";
@ -161,7 +167,7 @@ namespace MenuOption {
const QString OcclusionCulling = "Occlusion Culling";
const QString Oscilloscope = "Audio Oscilloscope";
const QString Pair = "Pair";
const QString PasteVoxels = "Paste Voxels";
const QString PasteVoxels = "Paste";
const QString PipelineWarnings = "Show Render Pipeline Warnings";
const QString Preferences = "Preferences...";
const QString RandomizeVoxelColors = "Randomize Voxel TRUE Colors";
@ -169,9 +175,6 @@ namespace MenuOption {
const QString ResetSwatchColors = "Reset Swatch Colors";
const QString RunTimingTests = "Run Timing Tests";
const QString SendVoxelColors = "Colored Voxels";
const QString SettingsAutosave = "Autosave";
const QString SettingsLoad = "Load Settings";
const QString SettingsSave = "Save Settings";
const QString SettingsImport = "Import Settings";
const QString SettingsExport = "Export Settings";
const QString ShowTrueColors = "Show TRUE Colors";

View file

@ -46,7 +46,6 @@ void OculusManager::updateYawOffset() {
}
void OculusManager::getEulerAngles(float& yaw, float& pitch, float& roll) {
yaw = pitch = roll = 0.0f;
#ifdef __APPLE__
_sensorFusion.GetOrientation().GetEulerAngles<Axis_Y, Axis_X, Axis_Z, Rotate_CCW, Handed_R>(&yaw, &pitch, &roll);

View file

@ -125,9 +125,15 @@ void SerialInterface::initializePort(char* portname) {
tcflush(_serialDescriptor, TCIOFLUSH);
// this disables streaming so there's no garbage data on reads
write(_serialDescriptor, "SD\n", 3);
if (write(_serialDescriptor, "SD\n", 3) != 3) {
qDebug("Failed.\n");
return;
}
char result[4];
read(_serialDescriptor, result, 4);
if (read(_serialDescriptor, result, 4) != 4) {
qDebug("Failed.\n");
return;
}
tty_set_file_descriptor(_serialDescriptor);
mpu_init(0);

View file

@ -598,13 +598,35 @@ float loadSetting(QSettings* settings, const char* name, float defaultValue) {
return value;
}
bool rayIntersectsSphere(glm::vec3& rayStarting, glm::vec3& rayNormalizedDirection, glm::vec3& sphereCenter, double sphereRadius) {
glm::vec3 vecFromRayToSphereCenter = sphereCenter - rayStarting;
double projection = glm::dot(vecFromRayToSphereCenter, rayNormalizedDirection);
double shortestDistance = sqrt(glm::dot(vecFromRayToSphereCenter, vecFromRayToSphereCenter) - projection * projection);
if (shortestDistance <= sphereRadius) {
return true;
bool rayIntersectsSphere(const glm::vec3& rayStarting, const glm::vec3& rayNormalizedDirection,
const glm::vec3& sphereCenter, float sphereRadius, float& distance) {
glm::vec3 relativeOrigin = rayStarting - sphereCenter;
// compute the b, c terms of the quadratic equation (a is dot(direction, direction), which is one)
float b = 2.0f * glm::dot(rayNormalizedDirection, relativeOrigin);
float c = glm::dot(relativeOrigin, relativeOrigin) - sphereRadius * sphereRadius;
// compute the radicand of the quadratic. if less than zero, there's no intersection
float radicand = b * b - 4.0f * c;
if (radicand < 0.0f) {
return false;
}
// compute the first solution of the quadratic
float root = sqrtf(radicand);
float firstSolution = -b - root;
if (firstSolution > 0.0f) {
distance = firstSolution / 2.0f;
return true; // origin is outside the sphere
}
// now try the second solution
float secondSolution = -b + root;
if (secondSolution > 0.0f) {
distance = 0.0f;
return true; // origin is inside the sphere
}
return false;
}
@ -615,4 +637,4 @@ bool pointInSphere(glm::vec3& point, glm::vec3& sphereCenter, double sphereRadiu
return true;
}
return false;
}
}

View file

@ -74,7 +74,8 @@ void runTimingTests();
float loadSetting(QSettings* settings, const char* name, float defaultValue);
bool rayIntersectsSphere(glm::vec3& rayStarting, glm::vec3& rayNormalizedDirection, glm::vec3& sphereCenter, double sphereRadius);
bool rayIntersectsSphere(const glm::vec3& rayStarting, const glm::vec3& rayNormalizedDirection,
const glm::vec3& sphereCenter, float sphereRadius, float& distance);
bool pointInSphere(glm::vec3& point, glm::vec3& sphereCenter, double sphereRadius);

View file

@ -7,29 +7,24 @@
//
#include <VoxelImporter.h>
#include <Application.h>
#include <QFileInfo>
#include <QThreadPool>
static const int IMPORT_SYSTEM_SCALE = 1.0f;
static const int MAX_VOXELS_PER_IMPORT = 2000000;
class ImportTask : public QObject, public QRunnable {
public:
ImportTask(VoxelSystem* voxelSystem, const QString &filename);
ImportTask(const QString &filename);
void run();
private:
VoxelSystem* _voxelSystem;
QString _filename;
QString _filename;
};
VoxelImporter::VoxelImporter(QWidget* parent)
: QObject(parent),
_voxelSystem(IMPORT_SYSTEM_SCALE, MAX_VOXELS_PER_IMPORT),
_initialized(false),
_importWaiting(false),
_importDialog(parent, &_voxelSystem),
_voxelTree(true),
_importDialog(parent),
_currentTask(NULL),
_nextTask(NULL) {
@ -38,6 +33,10 @@ VoxelImporter::VoxelImporter(QWidget* parent)
connect(&_importDialog, SIGNAL(accepted()), SLOT(import()));
}
void VoxelImporter::init() {
_importDialog.init();
}
VoxelImporter::~VoxelImporter() {
if (_nextTask) {
delete _nextTask;
@ -46,16 +45,15 @@ VoxelImporter::~VoxelImporter() {
if (_currentTask) {
disconnect(_currentTask, 0, 0, 0);
_voxelSystem.cancelImport();
_voxelTree.cancelImport();
_currentTask = NULL;
}
}
void VoxelImporter::reset() {
_voxelSystem.killLocalVoxels();
_voxelTree.eraseAllVoxels();
_importDialog.reset();
_filename = "";
_importWaiting = false;
if (_nextTask) {
delete _nextTask;
@ -63,17 +61,11 @@ void VoxelImporter::reset() {
}
if (_currentTask) {
_voxelSystem.cancelImport();
_voxelTree.cancelImport();
}
}
int VoxelImporter::exec() {
if (!_initialized) {
_voxelSystem.init();
_importViewFrustum.calculate();
_voxelSystem.setViewFrustum(&_importViewFrustum);
_initialized = true;
}
reset();
int ret = _importDialog.exec();
@ -82,7 +74,15 @@ int VoxelImporter::exec() {
reset();
} else {
_importDialog.reset();
_importWaiting = true;
if (_importDialog.getImportIntoClipboard()) {
VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem();
voxelSystem->copySubTreeIntoNewTree(voxelSystem->getTree()->rootNode,
Application::getInstance()->getClipboard(),
true);
voxelSystem->changeTree(Application::getInstance()->getClipboard());
}
}
return ret;
@ -102,11 +102,11 @@ int VoxelImporter::preImport() {
delete _nextTask;
}
_nextTask = new ImportTask(&_voxelSystem, _filename);
_nextTask = new ImportTask(_filename);
connect(_nextTask, SIGNAL(destroyed()), SLOT(launchTask()));
if (_currentTask != NULL) {
_voxelSystem.cancelImport();
_voxelTree.cancelImport();
} else {
launchTask();
}
@ -138,12 +138,12 @@ int VoxelImporter::import() {
delete _nextTask;
}
_nextTask = new ImportTask(&_voxelSystem, _filename);
_nextTask = new ImportTask(_filename);
connect(_nextTask, SIGNAL(destroyed()), SLOT(launchTask()));
connect(_nextTask, SIGNAL(destroyed()), &_importDialog, SLOT(accept()));
if (_currentTask != NULL) {
_voxelSystem.cancelImport();
_voxelTree.cancelImport();
} else {
launchTask();
}
@ -153,28 +153,36 @@ int VoxelImporter::import() {
void VoxelImporter::launchTask() {
if (_nextTask != NULL) {
_voxelSystem.killLocalVoxels();
_currentTask = _nextTask;
_nextTask = NULL;
if (Application::getInstance()->getSharedVoxelSystem()->getTree() != &_voxelTree) {
Application::getInstance()->getSharedVoxelSystem()->changeTree(&_voxelTree);
}
QThreadPool::globalInstance()->start(_currentTask);
} else {
_currentTask = NULL;
}
}
ImportTask::ImportTask(VoxelSystem* voxelSystem, const QString &filename)
: _voxelSystem(voxelSystem),
_filename(filename) {
ImportTask::ImportTask(const QString &filename)
: _filename(filename) {
}
void ImportTask::run() {
VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem();
voxelSystem->killLocalVoxels();
if (_filename.endsWith(".png", Qt::CaseInsensitive)) {
_voxelSystem->readFromSquareARGB32Pixels(_filename.toLocal8Bit().data());
voxelSystem->readFromSquareARGB32Pixels(_filename.toLocal8Bit().data());
} else if (_filename.endsWith(".svo", Qt::CaseInsensitive)) {
_voxelSystem->readFromSVOFile(_filename.toLocal8Bit().data());
voxelSystem->readFromSVOFile(_filename.toLocal8Bit().data());
} else if (_filename.endsWith(".schematic", Qt::CaseInsensitive)) {
_voxelSystem->readFromSchematicFile(_filename.toLocal8Bit().data());
voxelSystem->readFromSchematicFile(_filename.toLocal8Bit().data());
} else {
qDebug("[ERROR] Invalid file extension.\n");
}
voxelSystem->getTree()->reaverageVoxelColors(voxelSystem->getTree()->rootNode);
}

View file

@ -22,11 +22,11 @@ class VoxelImporter : public QObject {
public:
VoxelImporter(QWidget* parent = NULL);
~VoxelImporter();
void init();
void reset();
bool getImportWaiting() const { return _importWaiting; }
VoxelSystem* getVoxelSystem() { return &_voxelSystem; }
bool getImportIntoClipboard() const { return _importDialog.getImportIntoClipboard(); }
VoxelTree* getVoxelTree() { return &_voxelTree; }
public slots:
int exec();
@ -37,14 +37,10 @@ private slots:
void launchTask();
private:
VoxelSystem _voxelSystem;
ViewFrustum _importViewFrustum;
bool _initialized;
bool _importWaiting;
VoxelTree _voxelTree;
ImportDialog _importDialog;
QString _filename;
QString _filename;
ImportTask* _currentTask;
ImportTask* _nextTask;

View file

@ -115,6 +115,10 @@ void VoxelSystem::clearFreeBufferIndexes() {
}
VoxelSystem::~VoxelSystem() {
glDeleteBuffers(1, &_vboVerticesID);
glDeleteBuffers(1, &_vboNormalsID);
glDeleteBuffers(1, &_vboColorsID);
glDeleteBuffers(1, &_vboIndicesID);
delete[] _readVerticesArray;
delete[] _writeVerticesArray;
delete[] _readColorsArray;
@ -416,7 +420,7 @@ int VoxelSystem::newTreeToArrays(VoxelNode* node) {
bool shouldRender = false; // assume we don't need to render it
// if it's colored, we might need to render it!
shouldRender = node->calculateShouldRender(_viewFrustum);
node->setShouldRender(shouldRender);
// let children figure out their renderness
if (!node->isLeaf()) {
@ -627,6 +631,18 @@ void VoxelSystem::init() {
_perlinModulateProgram->release();
}
void VoxelSystem::changeTree(VoxelTree* newTree) {
disconnect(_tree, 0, this, 0);
_tree = newTree;
_tree->setDirtyBit();
connect(_tree, SIGNAL(importSize(float,float,float)), SIGNAL(importSize(float,float,float)));
connect(_tree, SIGNAL(importProgress(int)), SIGNAL(importProgress(int)));
setupNewVoxelsForDrawing();
}
void VoxelSystem::updateFullVBOs() {
updateVBOSegment(0, _voxelsInReadArrays);

View file

@ -34,19 +34,21 @@ public:
VoxelSystem(float treeScale = TREE_SCALE, int maxVoxels = MAX_VOXELS_PER_SYSTEM);
~VoxelSystem();
void setDataSourceID(int dataSourceID) { _dataSourceID = dataSourceID; };
int getDataSourceID() const { return _dataSourceID; };
void setDataSourceID(int dataSourceID) { _dataSourceID = dataSourceID; }
int getDataSourceID() const { return _dataSourceID; }
int parseData(unsigned char* sourceBuffer, int numBytes);
virtual void init();
void simulate(float deltaTime) { };
void simulate(float deltaTime) { }
void render(bool texture);
ViewFrustum* getViewFrustum() const {return _viewFrustum;}
void setViewFrustum(ViewFrustum* viewFrustum) {_viewFrustum = viewFrustum;}
unsigned long getVoxelsUpdated() const {return _voxelsUpdated;};
unsigned long getVoxelsRendered() const {return _voxelsInReadArrays;};
void changeTree(VoxelTree* newTree);
VoxelTree* getTree() const { return _tree; }
ViewFrustum* getViewFrustum() const { return _viewFrustum; }
void setViewFrustum(ViewFrustum* viewFrustum) { _viewFrustum = viewFrustum; }
unsigned long getVoxelsUpdated() const { return _voxelsUpdated; }
unsigned long getVoxelsRendered() const { return _voxelsInReadArrays; }
void loadVoxelsFile(const char* fileName,bool wantColorRandomizer);
void writeToSVOFile(const char* filename, VoxelNode* node) const;
@ -118,10 +120,11 @@ protected:
float _treeScale;
int _maxVoxels;
VoxelTree* _tree;
void setupNewVoxelsForDrawing();
glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const;
void setupNewVoxelsForDrawing();
virtual void updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex,
float voxelScale, const nodeColor& color);

View file

@ -105,7 +105,8 @@ Avatar::Avatar(Node* owningNode) :
_initialized(false),
_handHoldingPosition(0.0f, 0.0f, 0.0f),
_maxArmLength(0.0f),
_pelvisStandingHeight(0.0f)
_pelvisStandingHeight(0.0f),
_moving(false)
{
// give the pointer to our head to inherited _headData variable from AvatarData
_headData = &_head;
@ -485,22 +486,27 @@ void Avatar::render(bool lookingInMirror, bool renderAvatarBalls) {
// render a simple round on the ground projected down from the avatar's position
renderDiskShadow(_position, glm::vec3(0.0f, 1.0f, 0.0f), _scale * 0.1f, 0.2f);
// render body
renderBody(lookingInMirror, renderAvatarBalls);
{
// glow when moving
Glower glower(_moving ? 1.0f : 0.0f);
// render body
renderBody(lookingInMirror, renderAvatarBalls);
// render sphere when far away
const float MAX_ANGLE = 10.f;
glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
glm::vec3 delta = _height * (_head.getCameraOrientation() * IDENTITY_UP) / 2.f;
float angle = abs(angleBetween(toTarget + delta, toTarget - delta));
// render sphere when far away
const float MAX_ANGLE = 10.f;
glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
glm::vec3 delta = _height * (_head.getCameraOrientation() * IDENTITY_UP) / 2.f;
float angle = abs(angleBetween(toTarget + delta, toTarget - delta));
if (angle < MAX_ANGLE) {
glColor4f(0.5f, 0.8f, 0.8f, 1.f - angle / MAX_ANGLE);
glPushMatrix();
glTranslatef(_position.x, _position.y, _position.z);
glScalef(_height / 2.f, _height / 2.f, _height / 2.f);
glutSolidSphere(1.2f + _head.getAverageLoudness() * .0005f, 20, 20);
glPopMatrix();
if (angle < MAX_ANGLE) {
glColor4f(0.5f, 0.8f, 0.8f, 1.f - angle / MAX_ANGLE);
glPushMatrix();
glTranslatef(_position.x, _position.y, _position.z);
glScalef(_height / 2.f, _height / 2.f, _height / 2.f);
glutSolidSphere(1.2f + _head.getAverageLoudness() * .0005f, 20, 20);
glPopMatrix();
}
}
// Render the balls
@ -757,10 +763,11 @@ void Avatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
}
}
} else {
// Render the body's voxels
// Render the body's voxels and head
float alpha = getBallRenderAlpha(BODY_BALL_HEAD_BASE, lookingInMirror);
if (alpha > 0.0f) {
_voxels.render(false);
_head.render(alpha);
}
}
_hand.render(lookingInMirror);
@ -794,6 +801,30 @@ void Avatar::getBodyBallTransform(AvatarJointID jointID, glm::vec3& position, gl
rotation = _bodyBall[jointID].rotation;
}
bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
float minDistance = FLT_MAX;
for (int i = 0; i < NUM_AVATAR_BODY_BALLS; i++) {
float distance;
if (rayIntersectsSphere(origin, direction, _bodyBall[i].position, _bodyBall[i].radius, distance)) {
minDistance = min(minDistance, distance);
}
}
if (minDistance == FLT_MAX) {
return false;
}
distance = minDistance;
return true;
}
int Avatar::parseData(unsigned char* sourceBuffer, int numBytes) {
// change in position implies movement
glm::vec3 oldPosition = _position;
int bytesRead = AvatarData::parseData(sourceBuffer, numBytes);
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
_moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD;
return bytesRead;
}
void Avatar::saveData(QSettings* set) {
set->beginGroup("Avatar");

View file

@ -164,6 +164,15 @@ public:
// Get the position/rotation of a single body ball
void getBodyBallTransform(AvatarJointID jointID, glm::vec3& position, glm::quat& rotation) const;
/// Checks for an intersection between the described ray and any of the avatar's body balls.
/// \param origin the origin of the ray
/// \param direction the unit direction vector
/// \param[out] distance the variable in which to store the distance to intersection
/// \return whether or not the ray intersected
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
virtual int parseData(unsigned char* sourceBuffer, int numBytes);
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
public slots:
@ -219,6 +228,8 @@ protected:
float _stringLength;
AvatarVoxelSystem _voxels;
bool _moving; ///< set when position is changing
// protected methods...
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; }

View file

@ -318,6 +318,10 @@ void MyAvatar::simulate(float deltaTime, Transmitter* transmitter, float gyroCam
_mode = AVATAR_MODE_INTERACTING;
}
// update moving flag based on speed
const float MOVING_SPEED_THRESHOLD = 0.01f;
_moving = _speed > MOVING_SPEED_THRESHOLD;
// update position by velocity, and subtract the change added earlier for gravity
_position += _velocity * deltaTime;
@ -510,8 +514,16 @@ float MyAvatar::getBallRenderAlpha(int ball, bool lookingInMirror) const {
void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
if (Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_FIRST_PERSON) {
// Dont display body
} else if (_head.getFace().isFullFrame()) {
// Dont display body, only the hand
_hand.render(lookingInMirror);
return;
}
// glow when moving
Glower glower(_moving ? 1.0f : 0.0f);
if (_head.getFace().isFullFrame()) {
// Render the full-frame video
float alpha = getBallRenderAlpha(BODY_BALL_HEAD_BASE, lookingInMirror);
if (alpha > 0.0f) {
@ -585,10 +597,11 @@ void MyAvatar::renderBody(bool lookingInMirror, bool renderAvatarBalls) {
}
}
} else {
// Render the body's voxels
// Render the body's voxels and head
float alpha = getBallRenderAlpha(BODY_BALL_HEAD_BASE, lookingInMirror);
if (alpha > 0.0f) {
_voxels.render(false);
_head.render(alpha);
}
}
_hand.render(lookingInMirror);
@ -1032,4 +1045,4 @@ void MyAvatar::setOrientation(const glm::quat& orientation) {
void MyAvatar::setNewScale(const float scale) {
_newScale = scale;
}
}

View file

@ -15,7 +15,7 @@
#include "ProgramObject.h"
#include "RenderUtil.h"
GlowEffect::GlowEffect() : _renderMode(DIFFUSE_ADD_MODE), _isOddFrame(false) {
GlowEffect::GlowEffect() : _renderMode(DIFFUSE_ADD_MODE), _isOddFrame(false), _intensity(0.0f) {
}
QOpenGLFramebufferObject* GlowEffect::getFreeFramebufferObject() const {
@ -70,12 +70,15 @@ void GlowEffect::prepare() {
}
void GlowEffect::begin(float intensity) {
glBlendColor(0.0f, 0.0f, 0.0f, intensity);
_isEmpty = false;
// store the current intensity and add the new amount
_intensityStack.push(_intensity);
glBlendColor(0.0f, 0.0f, 0.0f, _intensity += intensity);
_isEmpty &= (_intensity == 0.0f);
}
void GlowEffect::end() {
glBlendColor(0.0f, 0.0f, 0.0f, 0.0f);
// restore the saved intensity
glBlendColor(0.0f, 0.0f, 0.0f, _intensity = _intensityStack.pop());
}
static void maybeBind(QOpenGLFramebufferObject* fbo) {
@ -268,3 +271,12 @@ void GlowEffect::cycleRenderMode() {
break;
}
}
Glower::Glower(float amount) {
Application::getInstance()->getGlowEffect()->begin(amount);
}
Glower::~Glower() {
Application::getInstance()->getGlowEffect()->end();
}

View file

@ -10,6 +10,7 @@
#define __interface__GlowEffect__
#include <QObject>
#include <QStack>
class QOpenGLFramebufferObject;
@ -63,6 +64,17 @@ private:
bool _isEmpty; ///< set when nothing in the scene is currently glowing
bool _isOddFrame; ///< controls the alternation between texture targets in diffuse add mode
float _intensity;
QStack<float> _intensityStack;
};
/// RAII-style glow handler. Applies glow when in scope.
class Glower {
public:
Glower(float amount = 1.0f);
~Glower();
};
#endif /* defined(__interface__GlowEffect__) */

View file

@ -8,9 +8,13 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cm
set(TARGET_NAME avatars)
find_package(Qt5Script REQUIRED)
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME})
qt5_use_modules(${TARGET_NAME} Script)
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} ${ROOT_DIR})

View file

@ -423,6 +423,7 @@ void ViewFrustum::printDebugDetails() const {
qDebug("_right=%f,%f,%f\n", _right.x, _right.y, _right.z );
qDebug("_fieldOfView=%f\n", _fieldOfView);
qDebug("_aspectRatio=%f\n", _aspectRatio);
qDebug("_keyHoleRadius=%f\n", _keyholeRadius);
qDebug("_nearClip=%f\n", _nearClip);
qDebug("_farClip=%f\n", _farClip);
qDebug("_eyeOffsetPosition=%f,%f,%f\n", _eyeOffsetPosition.x, _eyeOffsetPosition.y, _eyeOffsetPosition.z );

View file

@ -24,50 +24,50 @@ const float DEFAULT_KEYHOLE_RADIUS = 3.0f;
class ViewFrustum {
public:
// setters for camera attributes
void setPosition (const glm::vec3& p) { _position = p; };
void setOrientation (const glm::quat& orientationAsQuaternion);
void setPosition(const glm::vec3& p) { _position = p; }
void setOrientation(const glm::quat& orientationAsQuaternion);
// getters for camera attributes
const glm::vec3& getPosition() const { return _position; };
const glm::quat& getOrientation() const { return _orientation; };
const glm::vec3& getDirection() const { return _direction; };
const glm::vec3& getUp() const { return _up; };
const glm::vec3& getRight() const { return _right; };
const glm::vec3& getPosition() const { return _position; }
const glm::quat& getOrientation() const { return _orientation; }
const glm::vec3& getDirection() const { return _direction; }
const glm::vec3& getUp() const { return _up; }
const glm::vec3& getRight() const { return _right; }
// setters for lens attributes
void setFieldOfView ( float f ) { _fieldOfView = f; };
void setAspectRatio ( float a ) { _aspectRatio = a; };
void setNearClip ( float n ) { _nearClip = n; };
void setFarClip ( float f ) { _farClip = f; };
void setEyeOffsetPosition (const glm::vec3& p) { _eyeOffsetPosition = p; };
void setEyeOffsetOrientation (const glm::quat& o) { _eyeOffsetOrientation = o; };
void setFieldOfView(float f) { _fieldOfView = f; }
void setAspectRatio(float a) { _aspectRatio = a; }
void setNearClip(float n) { _nearClip = n; }
void setFarClip(float f) { _farClip = f; }
void setEyeOffsetPosition(const glm::vec3& p) { _eyeOffsetPosition = p; }
void setEyeOffsetOrientation(const glm::quat& o) { _eyeOffsetOrientation = o; }
// getters for lens attributes
float getFieldOfView() const { return _fieldOfView; };
float getAspectRatio() const { return _aspectRatio; };
float getNearClip() const { return _nearClip; };
float getFarClip() const { return _farClip; };
const glm::vec3& getEyeOffsetPosition() const { return _eyeOffsetPosition; };
const glm::quat& getEyeOffsetOrientation() const { return _eyeOffsetOrientation;};
float getFieldOfView() const { return _fieldOfView; }
float getAspectRatio() const { return _aspectRatio; }
float getNearClip() const { return _nearClip; }
float getFarClip() const { return _farClip; }
const glm::vec3& getEyeOffsetPosition() const { return _eyeOffsetPosition; }
const glm::quat& getEyeOffsetOrientation() const { return _eyeOffsetOrientation; }
const glm::vec3& getOffsetPosition() const { return _offsetPosition; };
const glm::vec3& getOffsetDirection() const { return _offsetDirection;};
const glm::vec3& getOffsetUp() const { return _offsetUp; };
const glm::vec3& getOffsetRight() const { return _offsetRight; };
const glm::vec3& getOffsetPosition() const { return _offsetPosition; }
const glm::vec3& getOffsetDirection() const { return _offsetDirection; }
const glm::vec3& getOffsetUp() const { return _offsetUp; }
const glm::vec3& getOffsetRight() const { return _offsetRight; }
const glm::vec3& getFarTopLeft() const { return _farTopLeft; };
const glm::vec3& getFarTopRight() const { return _farTopRight; };
const glm::vec3& getFarBottomLeft() const { return _farBottomLeft; };
const glm::vec3& getFarBottomRight() const { return _farBottomRight; };
const glm::vec3& getFarTopLeft() const { return _farTopLeft; }
const glm::vec3& getFarTopRight() const { return _farTopRight; }
const glm::vec3& getFarBottomLeft() const { return _farBottomLeft; }
const glm::vec3& getFarBottomRight() const { return _farBottomRight; }
const glm::vec3& getNearTopLeft() const { return _nearTopLeft; };
const glm::vec3& getNearTopRight() const { return _nearTopRight; };
const glm::vec3& getNearBottomLeft() const { return _nearBottomLeft; };
const glm::vec3& getNearBottomRight() const { return _nearBottomRight;};
const glm::vec3& getNearTopLeft() const { return _nearTopLeft; }
const glm::vec3& getNearTopRight() const { return _nearTopRight; }
const glm::vec3& getNearBottomLeft() const { return _nearBottomLeft; }
const glm::vec3& getNearBottomRight() const { return _nearBottomRight; }
// get/set for keyhole attribute
void setKeyholeRadius(float keyholdRadius) { _keyholeRadius = keyholdRadius; };
float getKeyholeRadius() const { return _keyholeRadius; };
void setKeyholeRadius(float keyholdRadius) { _keyholeRadius = keyholdRadius; }
float getKeyholeRadius() const { return _keyholeRadius; }
void calculate();
@ -81,7 +81,7 @@ public:
// some frustum comparisons
bool matches(const ViewFrustum& compareTo, bool debug = false) const;
bool matches(const ViewFrustum* compareTo, bool debug = false) const { return matches(*compareTo, debug); };
bool matches(const ViewFrustum* compareTo, bool debug = false) const { return matches(*compareTo, debug); }
void computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const;

View file

@ -1581,7 +1581,6 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
bool VoxelTree::readFromSVOFile(const char* fileName) {
std::ifstream file(fileName, std::ios::in|std::ios::binary|std::ios::ate);
if(file.is_open()) {
emit importSize(1.0f, 1.0f, 1.0f);
emit importProgress(0);
@ -1607,15 +1606,15 @@ bool VoxelTree::readFromSVOFile(const char* fileName) {
}
bool VoxelTree::readFromSquareARGB32Pixels(const char* filename) {
emit importSize(1.0f, 1.0f, 1.0f);
emit importProgress(0);
int minAlpha = INT_MAX;
QImage pngImage = QImage(filename);
for (int x = 0; x < pngImage.width() * pngImage.height(); ++x) {
minAlpha = std::min(qAlpha(pngImage.color(x)) , minAlpha);
for (int i = 0; i < pngImage.width(); ++i) {
for (int j = 0; j < pngImage.height(); ++j) {
minAlpha = std::min(qAlpha(pngImage.pixel(i, j)) , minAlpha);
}
}
int maxSize = std::max(pngImage.width(), pngImage.height());
@ -1624,6 +1623,8 @@ bool VoxelTree::readFromSquareARGB32Pixels(const char* filename) {
while (maxSize > scale) {scale *= 2;}
float size = 1.0f / scale;
emit importSize(size * pngImage.width(), 1.0f, size * pngImage.height());
QRgb pixel;
int minNeighborhoodAlpha;
@ -1692,14 +1693,13 @@ bool VoxelTree::readFromSchematicFile(const char *fileName) {
while (max > scale) {scale *= 2;}
float size = 1.0f / scale;
int create = 1;
int red = 128, green = 128, blue = 128;
int count = 0;
emit importSize(size * schematics.getWidth(),
size * schematics.getHeight(),
size * schematics.getLength());
emit importProgress(0);
int create = 1;
int red = 128, green = 128, blue = 128;
int count = 0;
for (int y = 0; y < schematics.getHeight(); ++y) {
for (int z = 0; z < schematics.getLength(); ++z) {

View file

@ -0,0 +1,26 @@
//
// NodeWatcher.h
// voxel-server
//
// Created by Brad Hefta-Gaub on 8/21/13
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// Node List Hook that watches for Node's being killed in order to clean up node specific memory and threads
//
#include <NodeList.h>
#include "NodeWatcher.h"
#include "VoxelNodeData.h"
void NodeWatcher::nodeAdded(Node* node) {
// do nothing
}
void NodeWatcher::nodeKilled(Node* node) {
// Use this to cleanup our node
if (node->getType() == NODE_TYPE_AGENT) {
VoxelNodeData* nodeData = (VoxelNodeData*)node->getLinkedData();
node->setLinkedData(NULL);
delete nodeData;
}
};

View file

@ -0,0 +1,23 @@
//
// NodeWatcher.h
// voxel-server
//
// Created by Brad Hefta-Gaub on 8/21/13
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// Node List Hook that watches for Node's being killed in order to clean up node specific memory and threads
//
#ifndef __voxel_server__NodeWatcher__
#define __voxel_server__NodeWatcher__
#include <NodeList.h>
/// Voxel server's node watcher, which watches for nodes being killed and cleans up their data and threads
class NodeWatcher : public virtual NodeListHook {
public:
virtual void nodeAdded(Node* node);
virtual void nodeKilled(Node* node);
};
#endif // __voxel_server__NodeWatcher__

View file

@ -11,6 +11,7 @@
#include "VoxelNodeData.h"
#include <cstring>
#include <cstdio>
#include "VoxelSendThread.h"
VoxelNodeData::VoxelNodeData(Node* owningNode) :
AvatarData(owningNode),
@ -21,11 +22,17 @@ VoxelNodeData::VoxelNodeData(Node* owningNode) :
_lastTimeBagEmpty(0),
_viewFrustumChanging(false),
_viewFrustumJustStoppedChanging(true),
_currentPacketIsColor(true)
_currentPacketIsColor(true),
_voxelSendThread(NULL)
{
_voxelPacket = new unsigned char[MAX_VOXEL_PACKET_SIZE];
_voxelPacketAt = _voxelPacket;
resetVoxelPacket();
// Create voxel sending thread...
uint16_t nodeID = getOwningNode()->getNodeID();
_voxelSendThread = new VoxelSendThread(nodeID);
_voxelSendThread->initialize(true);
}
@ -49,6 +56,9 @@ void VoxelNodeData::writeToPacket(unsigned char* buffer, int bytes) {
VoxelNodeData::~VoxelNodeData() {
delete[] _voxelPacket;
_voxelSendThread->terminate();
delete _voxelSendThread;
}
bool VoxelNodeData::updateCurrentViewFrustum() {

View file

@ -18,6 +18,8 @@
#include <VoxelNodeBag.h>
#include <VoxelSceneStats.h>
class VoxelSendThread;
class VoxelNodeData : public AvatarData {
public:
VoxelNodeData(Node* owningNode);
@ -80,6 +82,8 @@ private:
bool _viewFrustumChanging;
bool _viewFrustumJustStoppedChanging;
bool _currentPacketIsColor;
VoxelSendThread* _voxelSendThread;
};
#endif /* defined(__hifi__VoxelNodeData__) */

View file

@ -5,7 +5,7 @@
// Created by Brad Hefta-Gaub on 8/21/13
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// Threaded or non-threaded voxel persistance
// Threaded or non-threaded voxel persistence
//
#include <NodeList.h>

View file

@ -5,7 +5,7 @@
// Created by Brad Hefta-Gaub on 8/21/13
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// Threaded or non-threaded voxel persistance
// Threaded or non-threaded voxel persistence
//
#ifndef __voxel_server__VoxelPersistThread__

View file

@ -0,0 +1,303 @@
//
// VoxelSendThread.cpp
// voxel-server
//
// Created by Brad Hefta-Gaub on 8/21/13
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// Threaded or non-threaded voxel packet sender
//
#include <NodeList.h>
#include <SharedUtil.h>
#include <PacketHeaders.h>
#include "VoxelSendThread.h"
#include "VoxelServer.h"
VoxelSendThread::VoxelSendThread(uint16_t nodeID) :
_nodeID(nodeID) {
}
bool VoxelSendThread::process() {
uint64_t lastSendTime = usecTimestampNow();
Node* node = NodeList::getInstance()->nodeWithID(_nodeID);
VoxelNodeData* nodeData = NULL;
if (node) {
nodeData = (VoxelNodeData*) node->getLinkedData();
}
// Sometimes the node data has not yet been linked, in which case we can't really do anything
if (nodeData) {
bool viewFrustumChanged = nodeData->updateCurrentViewFrustum();
if (::debugVoxelSending) {
printf("nodeData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged));
}
deepestLevelVoxelDistributor(node, nodeData, viewFrustumChanged);
}
// dynamically sleep until we need to fire off the next set of voxels
int usecToSleep = VOXEL_SEND_INTERVAL_USECS - (usecTimestampNow() - lastSendTime);
if (usecToSleep > 0) {
usleep(usecToSleep);
} else {
if (::debugVoxelSending) {
std::cout << "Last send took too much time, not sleeping!\n";
}
}
return isStillRunning(); // keep running till they terminate us
}
void VoxelSendThread::handlePacketSend(Node* node, VoxelNodeData* nodeData, int& trueBytesSent, int& truePacketsSent) {
// If we've got a stats message ready to send, then see if we can piggyback them together
if (nodeData->stats.isReadyToSend()) {
// Send the stats message to the client
unsigned char* statsMessage = nodeData->stats.getStatsMessage();
int statsMessageLength = nodeData->stats.getStatsMessageLength();
// If the size of the stats message and the voxel message will fit in a packet, then piggyback them
if (nodeData->getPacketLength() + statsMessageLength < MAX_PACKET_SIZE) {
// copy voxel message to back of stats message
memcpy(statsMessage + statsMessageLength, nodeData->getPacket(), nodeData->getPacketLength());
statsMessageLength += nodeData->getPacketLength();
// actually send it
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(), statsMessage, statsMessageLength);
} else {
// not enough room in the packet, send two packets
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(), statsMessage, statsMessageLength);
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(),
nodeData->getPacket(), nodeData->getPacketLength());
}
} else {
// just send the voxel packet
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(),
nodeData->getPacket(), nodeData->getPacketLength());
}
// remember to track our stats
nodeData->stats.packetSent(nodeData->getPacketLength());
trueBytesSent += nodeData->getPacketLength();
truePacketsSent++;
nodeData->resetVoxelPacket();
}
/// Version of voxel distributor that sends the deepest LOD level at once
void VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nodeData, bool viewFrustumChanged) {
pthread_mutex_lock(&::treeLock);
int truePacketsSent = 0;
int trueBytesSent = 0;
// FOR NOW... node tells us if it wants to receive only view frustum deltas
bool wantDelta = viewFrustumChanged && nodeData->getWantDelta();
// If our packet already has content in it, then we must use the color choice of the waiting packet.
// If we're starting a fresh packet, then...
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
// the clients requested color state.
bool wantColor = LOW_RES_MONO && nodeData->getWantLowResMoving() && viewFrustumChanged ? false : nodeData->getWantColor();
// If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color
// then let's just send that waiting packet.
if (wantColor != nodeData->getCurrentPacketIsColor()) {
if (nodeData->isPacketWaiting()) {
if (::debugVoxelSending) {
printf("wantColor=%s --- SENDING PARTIAL PACKET! nodeData->getCurrentPacketIsColor()=%s\n",
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()));
}
handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
} else {
if (::debugVoxelSending) {
printf("wantColor=%s --- FIXING HEADER! nodeData->getCurrentPacketIsColor()=%s\n",
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()));
}
nodeData->resetVoxelPacket();
}
}
if (::debugVoxelSending) {
printf("wantColor=%s getCurrentPacketIsColor()=%s, viewFrustumChanged=%s, getWantLowResMoving()=%s\n",
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()),
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->getWantLowResMoving()));
}
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
if (::debugVoxelSending) {
printf("deepestLevelVoxelDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n",
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()),
debug::valueOf(nodeData->getViewSent())
);
}
// If the current view frustum has changed OR we have nothing to send, then search against
// the current view frustum for things to send.
if (viewFrustumChanged || nodeData->nodeBag.isEmpty()) {
uint64_t now = usecTimestampNow();
if (::debugVoxelSending) {
printf("(viewFrustumChanged=%s || nodeData->nodeBag.isEmpty() =%s)...\n",
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()));
if (nodeData->getLastTimeBagEmpty() > 0) {
float elapsedSceneSend = (now - nodeData->getLastTimeBagEmpty()) / 1000000.0f;
if (viewFrustumChanged) {
printf("viewFrustumChanged resetting after elapsed time to send scene = %f seconds", elapsedSceneSend);
} else {
printf("elapsed time to send scene = %f seconds", elapsedSceneSend);
}
printf(" [occlusionCulling:%s, wantDelta:%s, wantColor:%s ]\n",
debug::valueOf(nodeData->getWantOcclusionCulling()), debug::valueOf(wantDelta),
debug::valueOf(wantColor));
}
}
// if our view has changed, we need to reset these things...
if (viewFrustumChanged) {
if (::dumpVoxelsOnMove) {
nodeData->nodeBag.deleteAll();
}
nodeData->map.erase();
}
if (!viewFrustumChanged && !nodeData->getWantDelta()) {
// only set our last sent time if we weren't resetting due to frustum change
uint64_t now = usecTimestampNow();
nodeData->setLastTimeBagEmpty(now);
}
nodeData->stats.sceneCompleted();
if (::displayVoxelStats) {
nodeData->stats.printDebugDetails();
}
// start tracking our stats
bool isFullScene = (!viewFrustumChanged || !nodeData->getWantDelta()) && nodeData->getViewFrustumJustStoppedChanging();
// If we're starting a full scene, then definitely we want to empty the nodeBag
if (isFullScene) {
nodeData->nodeBag.deleteAll();
}
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, ::serverTree.rootNode, ::jurisdiction);
// This is the start of "resending" the scene.
nodeData->nodeBag.insert(serverTree.rootNode);
}
// If we have something in our nodeBag, then turn them into packets and send them out...
if (!nodeData->nodeBag.isEmpty()) {
int bytesWritten = 0;
int packetsSentThisInterval = 0;
uint64_t start = usecTimestampNow();
bool shouldSendEnvironments = ::sendEnvironments && shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, VOXEL_SEND_INTERVAL_USECS);
while (packetsSentThisInterval < PACKETS_PER_CLIENT_PER_INTERVAL - (shouldSendEnvironments ? 1 : 0)) {
// Check to see if we're taking too long, and if so bail early...
uint64_t now = usecTimestampNow();
long elapsedUsec = (now - start);
long elapsedUsecPerPacket = (truePacketsSent == 0) ? 0 : (elapsedUsec / truePacketsSent);
long usecRemaining = (VOXEL_SEND_INTERVAL_USECS - elapsedUsec);
if (elapsedUsecPerPacket + SENDING_TIME_TO_SPARE > usecRemaining) {
if (::debugVoxelSending) {
printf("packetLoop() usecRemaining=%ld bailing early took %ld usecs to generate %d bytes in %d packets (%ld usec avg), %d nodes still to send\n",
usecRemaining, elapsedUsec, trueBytesSent, truePacketsSent, elapsedUsecPerPacket,
nodeData->nodeBag.count());
}
break;
}
if (!nodeData->nodeBag.isEmpty()) {
VoxelNode* subTree = nodeData->nodeBag.extract();
bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
int boundaryLevelAdjust = viewFrustumChanged && nodeData->getWantLowResMoving()
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST;
bool isFullScene = (!viewFrustumChanged || !nodeData->getWantDelta()) &&
nodeData->getViewFrustumJustStoppedChanging();
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
wantOcclusionCulling, coverageMap, boundaryLevelAdjust,
nodeData->getLastTimeBagEmpty(),
isFullScene, &nodeData->stats, ::jurisdiction);
nodeData->stats.encodeStarted();
bytesWritten = serverTree.encodeTreeBitstream(subTree, _tempOutputBuffer, MAX_VOXEL_PACKET_SIZE - 1,
nodeData->nodeBag, params);
nodeData->stats.encodeStopped();
if (nodeData->getAvailable() >= bytesWritten) {
nodeData->writeToPacket(_tempOutputBuffer, bytesWritten);
} else {
handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
packetsSentThisInterval++;
nodeData->resetVoxelPacket();
nodeData->writeToPacket(_tempOutputBuffer, bytesWritten);
}
} else {
if (nodeData->isPacketWaiting()) {
handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
nodeData->resetVoxelPacket();
}
packetsSentThisInterval = PACKETS_PER_CLIENT_PER_INTERVAL; // done for now, no nodes left
}
}
// send the environment packet
if (shouldSendEnvironments) {
int numBytesPacketHeader = populateTypeAndVersion(_tempOutputBuffer, PACKET_TYPE_ENVIRONMENT_DATA);
int envPacketLength = numBytesPacketHeader;
int environmentsToSend = ::sendMinimalEnvironment ? 1 : sizeof(environmentData) / sizeof(EnvironmentData);
for (int i = 0; i < environmentsToSend; i++) {
envPacketLength += environmentData[i].getBroadcastData(_tempOutputBuffer + envPacketLength);
}
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(), _tempOutputBuffer, envPacketLength);
trueBytesSent += envPacketLength;
truePacketsSent++;
}
uint64_t end = usecTimestampNow();
int elapsedmsec = (end - start)/1000;
if (elapsedmsec > 100) {
if (elapsedmsec > 1000) {
int elapsedsec = (end - start)/1000000;
printf("WARNING! packetLoop() took %d seconds to generate %d bytes in %d packets %d nodes still to send\n",
elapsedsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
} else {
printf("WARNING! packetLoop() took %d milliseconds to generate %d bytes in %d packets, %d nodes still to send\n",
elapsedmsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
}
} else if (::debugVoxelSending) {
printf("packetLoop() took %d milliseconds to generate %d bytes in %d packets, %d nodes still to send\n",
elapsedmsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
}
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
// the voxels from the current view frustum
if (nodeData->nodeBag.isEmpty()) {
nodeData->updateLastKnownViewFrustum();
nodeData->setViewSent(true);
if (::debugVoxelSending) {
nodeData->map.printStats();
}
nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes
}
} // end if bag wasn't empty, and so we sent stuff...
pthread_mutex_unlock(&::treeLock);
}

View file

@ -0,0 +1,37 @@
//
// VoxelSendThread.h
// voxel-server
//
// Created by Brad Hefta-Gaub on 8/21/13
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
// Threaded or non-threaded object for sending voxels to a client
//
#ifndef __voxel_server__VoxelSendThread__
#define __voxel_server__VoxelSendThread__
#include <GenericThread.h>
#include <NetworkPacket.h>
#include <VoxelTree.h>
#include <VoxelNodeBag.h>
#include "VoxelNodeData.h"
/// Threaded processor for sending voxel packets to a single client
class VoxelSendThread : public virtual GenericThread {
public:
VoxelSendThread(uint16_t nodeID);
protected:
/// Implements generic processing behavior for this thread.
virtual bool process();
private:
uint16_t _nodeID;
void handlePacketSend(Node* node, VoxelNodeData* nodeData, int& trueBytesSent, int& truePacketsSent);
void deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nodeData, bool viewFrustumChanged);
unsigned char _tempOutputBuffer[MAX_VOXEL_PACKET_SIZE];
};
#endif // __voxel_server__VoxelSendThread__

View file

@ -23,7 +23,9 @@
#include <PerfStat.h>
#include <JurisdictionSender.h>
#include "NodeWatcher.h"
#include "VoxelPersistThread.h"
#include "VoxelSendThread.h"
#include "VoxelServerPacketProcessor.h"
#ifdef _WIN32
@ -58,297 +60,7 @@ JurisdictionSender* jurisdictionSender = NULL;
VoxelServerPacketProcessor* voxelServerPacketProcessor = NULL;
VoxelPersistThread* voxelPersistThread = NULL;
pthread_mutex_t treeLock;
void handlePacketSend(NodeList* nodeList,
NodeList::iterator& node,
VoxelNodeData* nodeData,
int& trueBytesSent, int& truePacketsSent) {
// If we've got a stats message ready to send, then see if we can piggyback them together
if (nodeData->stats.isReadyToSend()) {
// Send the stats message to the client
unsigned char* statsMessage = nodeData->stats.getStatsMessage();
int statsMessageLength = nodeData->stats.getStatsMessageLength();
// If the size of the stats message and the voxel message will fit in a packet, then piggyback them
if (nodeData->getPacketLength() + statsMessageLength < MAX_PACKET_SIZE) {
// copy voxel message to back of stats message
memcpy(statsMessage + statsMessageLength, nodeData->getPacket(), nodeData->getPacketLength());
statsMessageLength += nodeData->getPacketLength();
// actually send it
nodeList->getNodeSocket()->send(node->getActiveSocket(), statsMessage, statsMessageLength);
} else {
// not enough room in the packet, send two packets
nodeList->getNodeSocket()->send(node->getActiveSocket(), statsMessage, statsMessageLength);
nodeList->getNodeSocket()->send(node->getActiveSocket(),
nodeData->getPacket(), nodeData->getPacketLength());
}
} else {
// just send the voxel packet
nodeList->getNodeSocket()->send(node->getActiveSocket(),
nodeData->getPacket(), nodeData->getPacketLength());
}
// remember to track our stats
nodeData->stats.packetSent(nodeData->getPacketLength());
trueBytesSent += nodeData->getPacketLength();
truePacketsSent++;
nodeData->resetVoxelPacket();
}
// Version of voxel distributor that sends the deepest LOD level at once
void deepestLevelVoxelDistributor(NodeList* nodeList,
NodeList::iterator& node,
VoxelNodeData* nodeData,
bool viewFrustumChanged) {
pthread_mutex_lock(&::treeLock);
int truePacketsSent = 0;
int trueBytesSent = 0;
// FOR NOW... node tells us if it wants to receive only view frustum deltas
bool wantDelta = viewFrustumChanged && nodeData->getWantDelta();
// If our packet already has content in it, then we must use the color choice of the waiting packet.
// If we're starting a fresh packet, then...
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
// the clients requested color state.
bool wantColor = LOW_RES_MONO && nodeData->getWantLowResMoving() && viewFrustumChanged ? false : nodeData->getWantColor();
// If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color
// then let's just send that waiting packet.
if (wantColor != nodeData->getCurrentPacketIsColor()) {
if (nodeData->isPacketWaiting()) {
if (::debugVoxelSending) {
printf("wantColor=%s --- SENDING PARTIAL PACKET! nodeData->getCurrentPacketIsColor()=%s\n",
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()));
}
handlePacketSend(nodeList, node, nodeData, trueBytesSent, truePacketsSent);
} else {
if (::debugVoxelSending) {
printf("wantColor=%s --- FIXING HEADER! nodeData->getCurrentPacketIsColor()=%s\n",
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()));
}
nodeData->resetVoxelPacket();
}
}
if (::debugVoxelSending) {
printf("wantColor=%s getCurrentPacketIsColor()=%s, viewFrustumChanged=%s, getWantLowResMoving()=%s\n",
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()),
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->getWantLowResMoving()));
}
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
if (::debugVoxelSending) {
printf("deepestLevelVoxelDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n",
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()),
debug::valueOf(nodeData->getViewSent())
);
}
// If the current view frustum has changed OR we have nothing to send, then search against
// the current view frustum for things to send.
if (viewFrustumChanged || nodeData->nodeBag.isEmpty()) {
uint64_t now = usecTimestampNow();
if (::debugVoxelSending) {
printf("(viewFrustumChanged=%s || nodeData->nodeBag.isEmpty() =%s)...\n",
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()));
if (nodeData->getLastTimeBagEmpty() > 0) {
float elapsedSceneSend = (now - nodeData->getLastTimeBagEmpty()) / 1000000.0f;
if (viewFrustumChanged) {
printf("viewFrustumChanged resetting after elapsed time to send scene = %f seconds", elapsedSceneSend);
} else {
printf("elapsed time to send scene = %f seconds", elapsedSceneSend);
}
printf(" [occlusionCulling:%s, wantDelta:%s, wantColor:%s ]\n",
debug::valueOf(nodeData->getWantOcclusionCulling()), debug::valueOf(wantDelta),
debug::valueOf(wantColor));
}
}
// if our view has changed, we need to reset these things...
if (viewFrustumChanged) {
if (::dumpVoxelsOnMove) {
nodeData->nodeBag.deleteAll();
}
nodeData->map.erase();
}
if (!viewFrustumChanged && !nodeData->getWantDelta()) {
// only set our last sent time if we weren't resetting due to frustum change
uint64_t now = usecTimestampNow();
nodeData->setLastTimeBagEmpty(now);
}
nodeData->stats.sceneCompleted();
if (::displayVoxelStats) {
nodeData->stats.printDebugDetails();
}
// start tracking our stats
bool isFullScene = (!viewFrustumChanged || !nodeData->getWantDelta()) && nodeData->getViewFrustumJustStoppedChanging();
// If we're starting a full scene, then definitely we want to empty the nodeBag
if (isFullScene) {
nodeData->nodeBag.deleteAll();
}
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, ::serverTree.rootNode, ::jurisdiction);
// This is the start of "resending" the scene.
nodeData->nodeBag.insert(serverTree.rootNode);
}
// If we have something in our nodeBag, then turn them into packets and send them out...
if (!nodeData->nodeBag.isEmpty()) {
static unsigned char tempOutputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static
int bytesWritten = 0;
int packetsSentThisInterval = 0;
uint64_t start = usecTimestampNow();
bool shouldSendEnvironments = ::sendEnvironments && shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, VOXEL_SEND_INTERVAL_USECS);
while (packetsSentThisInterval < PACKETS_PER_CLIENT_PER_INTERVAL - (shouldSendEnvironments ? 1 : 0)) {
// Check to see if we're taking too long, and if so bail early...
uint64_t now = usecTimestampNow();
long elapsedUsec = (now - start);
long elapsedUsecPerPacket = (truePacketsSent == 0) ? 0 : (elapsedUsec / truePacketsSent);
long usecRemaining = (VOXEL_SEND_INTERVAL_USECS - elapsedUsec);
if (elapsedUsecPerPacket + SENDING_TIME_TO_SPARE > usecRemaining) {
if (::debugVoxelSending) {
printf("packetLoop() usecRemaining=%ld bailing early took %ld usecs to generate %d bytes in %d packets (%ld usec avg), %d nodes still to send\n",
usecRemaining, elapsedUsec, trueBytesSent, truePacketsSent, elapsedUsecPerPacket,
nodeData->nodeBag.count());
}
break;
}
if (!nodeData->nodeBag.isEmpty()) {
VoxelNode* subTree = nodeData->nodeBag.extract();
bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
int boundaryLevelAdjust = viewFrustumChanged && nodeData->getWantLowResMoving()
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST;
bool isFullScene = (!viewFrustumChanged || !nodeData->getWantDelta()) &&
nodeData->getViewFrustumJustStoppedChanging();
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
wantOcclusionCulling, coverageMap, boundaryLevelAdjust,
nodeData->getLastTimeBagEmpty(),
isFullScene, &nodeData->stats, ::jurisdiction);
nodeData->stats.encodeStarted();
bytesWritten = serverTree.encodeTreeBitstream(subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1,
nodeData->nodeBag, params);
nodeData->stats.encodeStopped();
if (nodeData->getAvailable() >= bytesWritten) {
nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten);
} else {
handlePacketSend(nodeList, node, nodeData, trueBytesSent, truePacketsSent);
packetsSentThisInterval++;
nodeData->resetVoxelPacket();
nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten);
}
} else {
if (nodeData->isPacketWaiting()) {
handlePacketSend(nodeList, node, nodeData, trueBytesSent, truePacketsSent);
nodeData->resetVoxelPacket();
}
packetsSentThisInterval = PACKETS_PER_CLIENT_PER_INTERVAL; // done for now, no nodes left
}
}
// send the environment packet
if (shouldSendEnvironments) {
int numBytesPacketHeader = populateTypeAndVersion(tempOutputBuffer, PACKET_TYPE_ENVIRONMENT_DATA);
int envPacketLength = numBytesPacketHeader;
int environmentsToSend = ::sendMinimalEnvironment ? 1 : sizeof(environmentData) / sizeof(EnvironmentData);
for (int i = 0; i < environmentsToSend; i++) {
envPacketLength += environmentData[i].getBroadcastData(tempOutputBuffer + envPacketLength);
}
nodeList->getNodeSocket()->send(node->getActiveSocket(), tempOutputBuffer, envPacketLength);
trueBytesSent += envPacketLength;
truePacketsSent++;
}
uint64_t end = usecTimestampNow();
int elapsedmsec = (end - start)/1000;
if (elapsedmsec > 100) {
if (elapsedmsec > 1000) {
int elapsedsec = (end - start)/1000000;
printf("WARNING! packetLoop() took %d seconds to generate %d bytes in %d packets %d nodes still to send\n",
elapsedsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
} else {
printf("WARNING! packetLoop() took %d milliseconds to generate %d bytes in %d packets, %d nodes still to send\n",
elapsedmsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
}
} else if (::debugVoxelSending) {
printf("packetLoop() took %d milliseconds to generate %d bytes in %d packets, %d nodes still to send\n",
elapsedmsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
}
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
// the voxels from the current view frustum
if (nodeData->nodeBag.isEmpty()) {
nodeData->updateLastKnownViewFrustum();
nodeData->setViewSent(true);
if (::debugVoxelSending) {
nodeData->map.printStats();
}
nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes
}
} // end if bag wasn't empty, and so we sent stuff...
pthread_mutex_unlock(&::treeLock);
}
void* distributeVoxelsToListeners(void* args) {
NodeList* nodeList = NodeList::getInstance();
timeval lastSendTime;
while (true) {
gettimeofday(&lastSendTime, NULL);
for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) {
VoxelNodeData* nodeData = (VoxelNodeData*) node->getLinkedData();
// Sometimes the node data has not yet been linked, in which case we can't really do anything
if (nodeData) {
bool viewFrustumChanged = nodeData->updateCurrentViewFrustum();
if (::debugVoxelSending) {
printf("nodeData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged));
}
deepestLevelVoxelDistributor(nodeList, node, nodeData, viewFrustumChanged);
}
}
// dynamically sleep until we need to fire off the next set of voxels
int usecToSleep = VOXEL_SEND_INTERVAL_USECS - (usecTimestampNow() - usecTimestamp(&lastSendTime));
if (usecToSleep > 0) {
usleep(usecToSleep);
} else {
if (::debugVoxelSending) {
std::cout << "Last send took too much time, not sleeping!\n";
}
}
}
pthread_exit(0);
}
NodeWatcher nodeWatcher; // used to cleanup AGENT data when agents are killed
void attachVoxelNodeDataToNode(Node* newNode) {
if (newNode->getLinkedData() == NULL) {
@ -420,6 +132,9 @@ int main(int argc, const char * argv[]) {
NodeList* nodeList = NodeList::createInstance(NODE_TYPE_VOXEL_SERVER, listenPort);
setvbuf(stdout, NULL, _IOLBF, 0);
// tell our NodeList about our desire to get notifications
nodeList->addHook(&nodeWatcher);
// Handle Local Domain testing with the --local command line
const char* local = "--local";
@ -462,7 +177,7 @@ int main(int argc, const char * argv[]) {
}
printf("wantVoxelPersist=%s\n", debug::valueOf(::wantVoxelPersist));
// if we want Voxel Persistance, load the local file now...
// if we want Voxel Persistence, load the local file now...
bool persistantFileRead = false;
if (::wantVoxelPersist) {
@ -532,9 +247,6 @@ int main(int argc, const char * argv[]) {
environmentData[2].setAtmosphereInnerRadius(0.1875f * TREE_SCALE);
environmentData[2].setAtmosphereOuterRadius(0.1875f * TREE_SCALE * 1.05f);
environmentData[2].setScatteringWavelengths(glm::vec3(0.475f, 0.570f, 0.650f)); // swaps red and blue
pthread_t sendVoxelThread;
pthread_create(&sendVoxelThread, NULL, distributeVoxelsToListeners, NULL);
sockaddr senderAddress;
@ -597,9 +309,6 @@ int main(int argc, const char * argv[]) {
}
}
pthread_join(sendVoxelThread, NULL);
pthread_mutex_destroy(&::treeLock);
if (::jurisdiction) {
delete ::jurisdiction;
}
@ -618,6 +327,11 @@ int main(int argc, const char * argv[]) {
::voxelPersistThread->terminate();
delete ::voxelPersistThread;
}
// tell our NodeList we're done with notifications
nodeList->removeHook(&nodeWatcher);
pthread_mutex_destroy(&::treeLock);
return 0;
}