mirror of
https://github.com/JulianGro/overte.git
synced 2025-08-19 02:41:17 +02:00
Merge branch 'master' of https://github.com/worklist/hifi
This commit is contained in:
commit
1bc1998023
36 changed files with 3044 additions and 2350 deletions
111
README.md
111
README.md
|
@ -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.
|
||||
|
||||
|
|
|
@ -44,7 +44,8 @@ float texCoordToViewSpaceZ(vec2 texCoord) {
|
|||
// given a texture coordinate, returns the 3D view space coordinate
|
||||
vec3 texCoordToViewSpace(vec2 texCoord) {
|
||||
float z = texCoordToViewSpaceZ(texCoord);
|
||||
return vec3(((texCoord * 2.0 - vec2(1.0, 1.0)) * (rightTop - leftBottom) + rightTop + leftBottom) * z / (-2.0 * near), z);
|
||||
return vec3(((texCoord * 2.0 - vec2(1.0 - gl_ProjectionMatrix[2][0], 1.0)) *
|
||||
(rightTop - leftBottom) + rightTop + leftBottom) * z / (-2.0 * near), z);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <QMenuBar>
|
||||
#include <QMouseEvent>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QOpenGLFramebufferObject>
|
||||
#include <QWheelEvent>
|
||||
#include <QSettings>
|
||||
#include <QShortcut>
|
||||
|
@ -116,7 +117,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
|
|||
_lookatIndicatorScale(1.0f),
|
||||
_perfStatsOn(false),
|
||||
_chatEntryOn(false),
|
||||
_oculusTextureID(0),
|
||||
_oculusProgram(0),
|
||||
_oculusDistortionScale(1.25),
|
||||
#ifndef _WIN32
|
||||
|
@ -378,13 +378,18 @@ void Application::paintGL() {
|
|||
|
||||
if (OculusManager::isConnected()) {
|
||||
displayOculus(whichCamera);
|
||||
|
||||
} else {
|
||||
_glowEffect.prepare();
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
displaySide(whichCamera);
|
||||
glPopMatrix();
|
||||
|
||||
_glowEffect.render();
|
||||
|
||||
displayOverlay();
|
||||
}
|
||||
|
||||
|
@ -408,13 +413,6 @@ void Application::resizeGL(int width, int height) {
|
|||
resetCamerasOnResizeGL(_viewFrustumOffsetCamera, width, height);
|
||||
resetCamerasOnResizeGL(_myCamera, width, height);
|
||||
|
||||
// resize the render texture
|
||||
if (OculusManager::isConnected() && _oculusTextureID != 0) {
|
||||
glBindTexture(GL_TEXTURE_2D, _oculusTextureID);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
// Tell our viewFrustum about this change, using the application camera
|
||||
loadViewFrustum(_myCamera, _viewFrustum);
|
||||
|
||||
|
@ -1043,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;
|
||||
|
@ -1386,7 +1382,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;
|
||||
|
@ -1724,6 +1722,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) {
|
||||
|
@ -1923,6 +1955,8 @@ static const char* DISTORTION_FRAGMENT_SHADER =
|
|||
"}";
|
||||
|
||||
void Application::displayOculus(Camera& whichCamera) {
|
||||
_glowEffect.prepare();
|
||||
|
||||
// magic numbers ahoy! in order to avoid pulling in the Oculus utility library that calculates
|
||||
// the rendering parameters from the hardware stats, i just folded their calculations into
|
||||
// constants using the stats for the current-model hardware as contained in the SDK file
|
||||
|
@ -1965,12 +1999,10 @@ void Application::displayOculus(Camera& whichCamera) {
|
|||
// restore our normal viewport
|
||||
glViewport(0, 0, _glWidget->width(), _glWidget->height());
|
||||
|
||||
if (_oculusTextureID == 0) {
|
||||
glGenTextures(1, &_oculusTextureID);
|
||||
glBindTexture(GL_TEXTURE_2D, _oculusTextureID);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _glWidget->width(), _glWidget->height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
|
||||
QOpenGLFramebufferObject* fbo = _glowEffect.render(true);
|
||||
glBindTexture(GL_TEXTURE_2D, fbo->texture());
|
||||
|
||||
if (_oculusProgram == 0) {
|
||||
_oculusProgram = new ProgramObject();
|
||||
_oculusProgram->addShaderFromSourceCode(QGLShader::Fragment, DISTORTION_FRAGMENT_SHADER);
|
||||
_oculusProgram->link();
|
||||
|
@ -1980,18 +2012,13 @@ void Application::displayOculus(Camera& whichCamera) {
|
|||
_screenCenterLocation = _oculusProgram->uniformLocation("screenCenter");
|
||||
_scaleLocation = _oculusProgram->uniformLocation("scale");
|
||||
_scaleInLocation = _oculusProgram->uniformLocation("scaleIn");
|
||||
_hmdWarpParamLocation = _oculusProgram->uniformLocation("hmdWarpParam");
|
||||
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, _oculusTextureID);
|
||||
_hmdWarpParamLocation = _oculusProgram->uniformLocation("hmdWarpParam");
|
||||
}
|
||||
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, _glWidget->width(), _glWidget->height());
|
||||
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluOrtho2D(0, _glWidget->width(), 0, _glWidget->height());
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_LIGHTING);
|
||||
|
||||
// for reference on setting these values, see SDK file Samples/OculusRoomTiny/RenderTiny_Device.cpp
|
||||
|
||||
|
@ -1999,7 +2026,6 @@ void Application::displayOculus(Camera& whichCamera) {
|
|||
float aspectRatio = (_glWidget->width() * 0.5) / _glWidget->height();
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
_oculusProgram->bind();
|
||||
_oculusProgram->setUniformValue(_textureLocation, 0);
|
||||
_oculusProgram->setUniformValue(_lensCenterLocation, 0.287994, 0.5); // see SDK docs, p. 29
|
||||
|
@ -2035,7 +2061,6 @@ void Application::displayOculus(Camera& whichCamera) {
|
|||
glEnd();
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
_oculusProgram->release();
|
||||
|
||||
|
@ -2096,9 +2121,6 @@ void Application::displaySide(Camera& whichCamera) {
|
|||
// Setup 3D lights (after the camera transform, so that they are positioned in world space)
|
||||
setupWorldLight(whichCamera);
|
||||
|
||||
// prepare the glow effect
|
||||
_glowEffect.prepare();
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
|
||||
if (!_stars.getFileLoaded()) {
|
||||
_stars.readInput(STAR_FILE, STAR_CACHE_FILE, 0);
|
||||
|
@ -2256,7 +2278,7 @@ void Application::displaySide(Camera& whichCamera) {
|
|||
_myAvatar.renderScreenTint(SCREEN_TINT_AFTER_AVATARS, whichCamera);
|
||||
|
||||
// Render the world box
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::Mirror) && Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::Mirror) && Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
|
||||
renderWorldBox();
|
||||
}
|
||||
|
||||
|
@ -2283,9 +2305,27 @@ void Application::displaySide(Camera& whichCamera) {
|
|||
}
|
||||
|
||||
renderFollowIndicator();
|
||||
|
||||
// render the glow effect
|
||||
_glowEffect.render();
|
||||
|
||||
// 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() {
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "VoxelImporter.h"
|
||||
#include "Webcam.h"
|
||||
#include "avatar/Avatar.h"
|
||||
#include "avatar/MyAvatar.h"
|
||||
#include "avatar/HandControl.h"
|
||||
#include "renderer/AmbientOcclusionEffect.h"
|
||||
#include "renderer/GeometryCache.h"
|
||||
|
@ -106,7 +107,7 @@ public:
|
|||
const glm::vec3 getMouseVoxelWorldCoordinates(const VoxelDetail _mouseVoxel);
|
||||
|
||||
QGLWidget* getGLWidget() { return _glWidget; }
|
||||
Avatar* getAvatar() { return &_myAvatar; }
|
||||
MyAvatar* getAvatar() { return &_myAvatar; }
|
||||
Audio* getAudio() { return &_audio; }
|
||||
Camera* getCamera() { return &_myCamera; }
|
||||
ViewFrustum* getViewFrustum() { return &_viewFrustum; }
|
||||
|
@ -249,7 +250,7 @@ private:
|
|||
|
||||
Oscilloscope _audioScope;
|
||||
|
||||
Avatar _myAvatar; // The rendered avatar of oneself
|
||||
MyAvatar _myAvatar; // The rendered avatar of oneself
|
||||
|
||||
Transmitter _myTransmitter; // Gets UDP data from transmitter app used to animate the avatar
|
||||
|
||||
|
@ -297,12 +298,14 @@ 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
|
||||
bool _chatEntryOn; // Whether to show the chat entry
|
||||
|
||||
GLuint _oculusTextureID; // The texture to which we render for Oculus distortion
|
||||
ProgramObject* _oculusProgram; // The GLSL program containing the distortion shader
|
||||
float _oculusDistortionScale; // Controls the Oculus field of view
|
||||
int _textureLocation;
|
||||
|
|
|
@ -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);
|
||||
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() {
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,36 +27,41 @@
|
|||
#include "world.h"
|
||||
|
||||
|
||||
static const float MAX_SCALE = 1000.f;
|
||||
static const float MIN_SCALE = .005f;
|
||||
static const float SCALING_RATIO = .05f;
|
||||
static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1
|
||||
static const float MAX_SCALE = 1000.f;
|
||||
static const float MIN_SCALE = .005f;
|
||||
static const float SCALING_RATIO = .05f;
|
||||
static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1
|
||||
static const float RESCALING_TOLERANCE = .02f;
|
||||
|
||||
const float BODY_BALL_RADIUS_PELVIS = 0.07;
|
||||
const float BODY_BALL_RADIUS_TORSO = 0.065;
|
||||
const float BODY_BALL_RADIUS_CHEST = 0.08;
|
||||
const float BODY_BALL_RADIUS_NECK_BASE = 0.03;
|
||||
const float BODY_BALL_RADIUS_HEAD_BASE = 0.07;
|
||||
const float BODY_BALL_RADIUS_LEFT_COLLAR = 0.04;
|
||||
const float BODY_BALL_RADIUS_LEFT_SHOULDER = 0.03;
|
||||
const float BODY_BALL_RADIUS_LEFT_ELBOW = 0.02;
|
||||
const float BODY_BALL_RADIUS_LEFT_WRIST = 0.02;
|
||||
const float BODY_BALL_RADIUS_LEFT_FINGERTIPS = 0.01;
|
||||
const float BODY_BALL_RADIUS_RIGHT_COLLAR = 0.04;
|
||||
const float BODY_BALL_RADIUS_RIGHT_SHOULDER = 0.03;
|
||||
const float BODY_BALL_RADIUS_RIGHT_ELBOW = 0.02;
|
||||
const float BODY_BALL_RADIUS_RIGHT_WRIST = 0.02;
|
||||
const float BODY_BALL_RADIUS_PELVIS = 0.07;
|
||||
const float BODY_BALL_RADIUS_TORSO = 0.065;
|
||||
const float BODY_BALL_RADIUS_CHEST = 0.08;
|
||||
const float BODY_BALL_RADIUS_NECK_BASE = 0.03;
|
||||
const float BODY_BALL_RADIUS_HEAD_BASE = 0.07;
|
||||
const float BODY_BALL_RADIUS_LEFT_COLLAR = 0.04;
|
||||
const float BODY_BALL_RADIUS_LEFT_SHOULDER = 0.03;
|
||||
const float BODY_BALL_RADIUS_LEFT_ELBOW = 0.02;
|
||||
const float BODY_BALL_RADIUS_LEFT_WRIST = 0.02;
|
||||
const float BODY_BALL_RADIUS_LEFT_FINGERTIPS = 0.01;
|
||||
const float BODY_BALL_RADIUS_RIGHT_COLLAR = 0.04;
|
||||
const float BODY_BALL_RADIUS_RIGHT_SHOULDER = 0.03;
|
||||
const float BODY_BALL_RADIUS_RIGHT_ELBOW = 0.02;
|
||||
const float BODY_BALL_RADIUS_RIGHT_WRIST = 0.02;
|
||||
const float BODY_BALL_RADIUS_RIGHT_FINGERTIPS = 0.01;
|
||||
const float BODY_BALL_RADIUS_LEFT_HIP = 0.04;
|
||||
const float BODY_BALL_RADIUS_LEFT_MID_THIGH = 0.03;
|
||||
const float BODY_BALL_RADIUS_LEFT_KNEE = 0.025;
|
||||
const float BODY_BALL_RADIUS_LEFT_HEEL = 0.025;
|
||||
const float BODY_BALL_RADIUS_LEFT_TOES = 0.025;
|
||||
const float BODY_BALL_RADIUS_RIGHT_HIP = 0.04;
|
||||
const float BODY_BALL_RADIUS_RIGHT_KNEE = 0.025;
|
||||
const float BODY_BALL_RADIUS_RIGHT_HEEL = 0.025;
|
||||
const float BODY_BALL_RADIUS_RIGHT_TOES = 0.025;
|
||||
const float BODY_BALL_RADIUS_LEFT_HIP = 0.04;
|
||||
const float BODY_BALL_RADIUS_LEFT_MID_THIGH = 0.03;
|
||||
const float BODY_BALL_RADIUS_LEFT_KNEE = 0.025;
|
||||
const float BODY_BALL_RADIUS_LEFT_HEEL = 0.025;
|
||||
const float BODY_BALL_RADIUS_LEFT_TOES = 0.025;
|
||||
const float BODY_BALL_RADIUS_RIGHT_HIP = 0.04;
|
||||
const float BODY_BALL_RADIUS_RIGHT_KNEE = 0.025;
|
||||
const float BODY_BALL_RADIUS_RIGHT_HEEL = 0.025;
|
||||
const float BODY_BALL_RADIUS_RIGHT_TOES = 0.025;
|
||||
|
||||
extern const bool usingBigSphereCollisionTest;
|
||||
|
||||
extern const float chatMessageScale;
|
||||
extern const float chatMessageHeight;
|
||||
|
||||
enum AvatarBodyBallID {
|
||||
BODY_BALL_NULL = -1,
|
||||
|
@ -84,9 +89,6 @@ enum AvatarBodyBallID {
|
|||
BODY_BALL_RIGHT_KNEE,
|
||||
BODY_BALL_RIGHT_HEEL,
|
||||
BODY_BALL_RIGHT_TOES,
|
||||
|
||||
//TEST!
|
||||
//BODY_BALL_LEFT_MID_THIGH,
|
||||
NUM_AVATAR_BODY_BALLS
|
||||
};
|
||||
|
||||
|
@ -117,6 +119,8 @@ enum ScreenTintLayer {
|
|||
NUM_SCREEN_TINT_LAYERS
|
||||
};
|
||||
|
||||
class MyAvatar;
|
||||
|
||||
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found)
|
||||
// this is basically in the center of the ground plane. Slightly adjusted. This was asked for by
|
||||
// Grayson as he's building a street around here for demo dinner 2
|
||||
|
@ -124,6 +128,7 @@ const glm::vec3 START_LOCATION(0.485f * TREE_SCALE, 0.f, 0.5f * TREE_SCALE);
|
|||
|
||||
class Avatar : public AvatarData {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static void sendAvatarVoxelURLMessage(const QUrl& url);
|
||||
|
||||
|
@ -131,86 +136,43 @@ public:
|
|||
~Avatar();
|
||||
|
||||
void init();
|
||||
void reset();
|
||||
void simulate(float deltaTime, Transmitter* transmitter, float gyroCameraSensitivity);
|
||||
void updateThrust(float deltaTime, Transmitter * transmitter);
|
||||
void follow(Avatar* leadingAvatar);
|
||||
void updateFromGyrosAndOrWebcam(bool gyroLook,
|
||||
float pitchFromTouch);
|
||||
void addBodyYaw(float bodyYaw) {_bodyYaw += bodyYaw;};
|
||||
void addBodyYawDelta(float bodyYawDelta) {_bodyYawDelta += bodyYawDelta;}
|
||||
void render(bool lookingInMirror, bool renderAvatarBalls);
|
||||
void renderScreenTint(ScreenTintLayer layer, Camera& whichCamera);
|
||||
|
||||
//setters
|
||||
void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; }
|
||||
void setMovedHandOffset(glm::vec3 movedHandOffset) { _movedHandOffset = movedHandOffset;}
|
||||
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
|
||||
void setDisplayingLookatVectors(bool displayingLookatVectors) { _head.setRenderLookatVectors(displayingLookatVectors); }
|
||||
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; };
|
||||
void setLeanScale(float scale) { _leanScale = scale;}
|
||||
|
||||
void setGravity(glm::vec3 gravity);
|
||||
void setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction);
|
||||
void setOrientation(const glm::quat& orientation);
|
||||
void setNewScale(const float scale);
|
||||
|
||||
//getters
|
||||
bool isInitialized() const { return _initialized;}
|
||||
bool isMyAvatar() const { return _owningNode == NULL; }
|
||||
const Skeleton& getSkeleton() const { return _skeleton;}
|
||||
float getHeadYawRate() const { return _head.yawRate;}
|
||||
float getBodyYaw() const { return _bodyYaw;}
|
||||
bool getIsNearInteractingOther() const { return _avatarTouch.getAbleToReachOtherAvatar();}
|
||||
const glm::vec3& getHeadJointPosition() const { return _skeleton.joint[ AVATAR_JOINT_HEAD_BASE ].position;}
|
||||
const glm::vec3& getBallPosition(AvatarJointID j) const { return _bodyBall[j].position;}
|
||||
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
|
||||
glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; }
|
||||
glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
|
||||
float getScale() const { return _scale;}
|
||||
float getNewScale() const { return _newScale;}
|
||||
const glm::vec3& getVelocity() const { return _velocity;}
|
||||
float getSpeed() const { return _speed;}
|
||||
float getHeight() const { return _height;}
|
||||
AvatarMode getMode() const { return _mode;}
|
||||
float getLeanScale() const { return _leanScale;}
|
||||
float getElapsedTimeStopped() const { return _elapsedTimeStopped;}
|
||||
float getElapsedTimeMoving() const { return _elapsedTimeMoving;}
|
||||
float getElapsedTimeSinceCollision() const { return _elapsedTimeSinceCollision;}
|
||||
const glm::vec3& getLastCollisionPosition() const { return _lastCollisionPosition;}
|
||||
float getAbsoluteHeadYaw() const;
|
||||
float getAbsoluteHeadPitch() const;
|
||||
bool isInitialized() const { return _initialized; }
|
||||
const Skeleton& getSkeleton() const { return _skeleton; }
|
||||
float getHeadYawRate() const { return _head.yawRate; }
|
||||
const glm::vec3& getHeadJointPosition() const { return _skeleton.joint[ AVATAR_JOINT_HEAD_BASE ].position; }
|
||||
float getScale() const { return _scale; }
|
||||
const glm::vec3& getVelocity() const { return _velocity; }
|
||||
Head& getHead() {return _head; }
|
||||
Hand& getHand() {return _hand; }
|
||||
glm::quat getOrientation() const;
|
||||
glm::quat getWorldAlignedOrientation() const;
|
||||
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
|
||||
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
|
||||
Avatar* getLeadingAvatar() const { return _leadingAvatar; }
|
||||
glm::vec3 getGravity() const { return _gravity; }
|
||||
|
||||
glm::vec3 getUprightHeadPosition() const;
|
||||
glm::vec3 getUprightEyeLevelPosition() const;
|
||||
glm::vec3 getEyePosition();
|
||||
|
||||
AvatarVoxelSystem* getVoxels() { return &_voxels; }
|
||||
|
||||
// Set what driving keys are being pressed to control thrust levels
|
||||
void setDriveKeys(int key, bool val) { _driveKeys[key] = val; };
|
||||
bool getDriveKeys(int key) { return _driveKeys[key]; };
|
||||
void jump() { _shouldJump = true; };
|
||||
|
||||
// Set/Get update the thrust that will move the avatar around
|
||||
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
|
||||
glm::vec3 getThrust() { return _thrust; };
|
||||
|
||||
// get/set avatar data
|
||||
void saveData(QSettings* set);
|
||||
void loadData(QSettings* set);
|
||||
|
||||
// Get the position/rotation of a single body ball
|
||||
// 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:
|
||||
|
@ -220,96 +182,82 @@ public slots:
|
|||
void decreaseSize();
|
||||
void resetSize();
|
||||
|
||||
friend class MyAvatar;
|
||||
|
||||
|
||||
protected:
|
||||
struct AvatarBall {
|
||||
AvatarJointID parentJoint; /// the skeletal joint that serves as a reference for determining the position
|
||||
glm::vec3 parentOffset; /// a 3D vector in the frame of reference of the parent skeletal joint
|
||||
AvatarBodyBallID parentBall; /// the ball to which this ball is constrained for spring forces
|
||||
glm::vec3 position; /// the actual dynamic position of the ball at any given time
|
||||
glm::quat rotation; /// the rotation of the ball
|
||||
glm::vec3 velocity; /// the velocity of the ball
|
||||
float springLength; /// the ideal length of the spring between this ball and its parentBall
|
||||
float jointTightness; /// how tightly the ball position attempts to stay at its ideal position (determined by parentOffset)
|
||||
float radius; /// the radius of the ball
|
||||
bool isCollidable; /// whether or not the ball responds to collisions
|
||||
float touchForce; /// a scalar determining the amount that the cursor (or hand) is penetrating the ball
|
||||
};
|
||||
|
||||
Head _head;
|
||||
Hand _hand;
|
||||
Skeleton _skeleton;
|
||||
bool _ballSpringsInitialized;
|
||||
float _TEST_bigSphereRadius;
|
||||
glm::vec3 _TEST_bigSpherePosition;
|
||||
float _bodyYawDelta;
|
||||
glm::vec3 _movedHandOffset;
|
||||
AvatarBall _bodyBall[ NUM_AVATAR_BODY_BALLS ];
|
||||
AvatarMode _mode;
|
||||
glm::vec3 _velocity;
|
||||
glm::vec3 _thrust;
|
||||
float _speed;
|
||||
float _leanScale;
|
||||
float _pelvisFloatingHeight;
|
||||
float _pelvisToHeadLength;
|
||||
float _scale;
|
||||
float _height;
|
||||
Balls* _balls;
|
||||
AvatarTouch _avatarTouch;
|
||||
glm::vec3 _worldUpDirection;
|
||||
glm::vec3 _mouseRayOrigin;
|
||||
glm::vec3 _mouseRayDirection;
|
||||
bool _isCollisionsOn;
|
||||
Avatar* _leadingAvatar;
|
||||
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; }
|
||||
glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
|
||||
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
|
||||
void updateCollisionWithSphere(glm::vec3 position, float radius, float deltaTime);
|
||||
void updateBodyBalls(float deltaTime);
|
||||
void updateArmIKAndConstraints(float deltaTime);
|
||||
void setScale(const float scale);
|
||||
|
||||
|
||||
private:
|
||||
// privatize copy constructor and assignment operator to avoid copying
|
||||
Avatar(const Avatar&);
|
||||
Avatar& operator= (const Avatar&);
|
||||
|
||||
struct AvatarBall {
|
||||
AvatarJointID parentJoint; // the skeletal joint that serves as a reference for determining the position
|
||||
glm::vec3 parentOffset; // a 3D vector in the frame of reference of the parent skeletal joint
|
||||
AvatarBodyBallID parentBall; // the ball to which this ball is constrained for spring forces
|
||||
glm::vec3 position; // the actual dynamic position of the ball at any given time
|
||||
glm::quat rotation; // the rotation of the ball
|
||||
glm::vec3 velocity; // the velocity of the ball
|
||||
float springLength; // the ideal length of the spring between this ball and its parentBall
|
||||
float jointTightness; // how tightly the ball position attempts to stay at its ideal position (determined by parentOffset)
|
||||
float radius; // the radius of the ball
|
||||
bool isCollidable; // whether or not the ball responds to collisions
|
||||
float touchForce; // a scalar determining the amount that the cursor (or hand) is penetrating the ball
|
||||
};
|
||||
|
||||
bool _initialized;
|
||||
Head _head;
|
||||
Hand _hand;
|
||||
Skeleton _skeleton;
|
||||
bool _ballSpringsInitialized;
|
||||
float _TEST_bigSphereRadius;
|
||||
glm::vec3 _TEST_bigSpherePosition;
|
||||
bool _mousePressed;
|
||||
float _bodyPitchDelta;
|
||||
float _bodyYawDelta;
|
||||
float _bodyRollDelta;
|
||||
glm::vec3 _movedHandOffset;
|
||||
AvatarBall _bodyBall[ NUM_AVATAR_BODY_BALLS ];
|
||||
AvatarMode _mode;
|
||||
glm::vec3 _handHoldingPosition;
|
||||
glm::vec3 _velocity;
|
||||
glm::vec3 _thrust;
|
||||
bool _shouldJump;
|
||||
float _speed;
|
||||
float _maxArmLength;
|
||||
float _leanScale;
|
||||
int _driveKeys[MAX_DRIVE_KEYS];
|
||||
float _pelvisStandingHeight;
|
||||
float _pelvisFloatingHeight;
|
||||
float _pelvisToHeadLength;
|
||||
float _scale;
|
||||
float _height;
|
||||
Balls* _balls;
|
||||
AvatarTouch _avatarTouch;
|
||||
float _distanceToNearestAvatar; // How close is the nearest avatar?
|
||||
glm::vec3 _gravity;
|
||||
glm::vec3 _worldUpDirection;
|
||||
glm::vec3 _mouseRayOrigin;
|
||||
glm::vec3 _mouseRayDirection;
|
||||
Avatar* _interactingOther;
|
||||
bool _isMouseTurningRight;
|
||||
float _elapsedTimeMoving; // Timers to drive camera transitions when moving
|
||||
float _elapsedTimeStopped;
|
||||
float _elapsedTimeSinceCollision;
|
||||
glm::vec3 _lastCollisionPosition;
|
||||
bool _speedBrakes;
|
||||
bool _isThrustOn;
|
||||
bool _isCollisionsOn;
|
||||
float _collisionRadius;
|
||||
|
||||
Avatar* _leadingAvatar;
|
||||
float _stringLength;
|
||||
|
||||
AvatarVoxelSystem _voxels;
|
||||
bool _initialized;
|
||||
glm::vec3 _handHoldingPosition;
|
||||
float _maxArmLength;
|
||||
float _pelvisStandingHeight;
|
||||
|
||||
// private methods...
|
||||
glm::vec3 calculateAverageEyePosition() { return _head.calculateAverageEyePosition(); } // get the position smack-dab between the eyes (for lookat)
|
||||
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
|
||||
float getBallRenderAlpha(int ball, bool lookingInMirror) const;
|
||||
void renderBody(bool lookingInMirror, bool renderAvatarBalls);
|
||||
void initializeBodyBalls();
|
||||
void resetBodyBalls();
|
||||
void updateBodyBalls( float deltaTime );
|
||||
void calculateBoneLengths();
|
||||
void readSensors();
|
||||
void updateHandMovementAndTouching(float deltaTime, bool enableHandMovement);
|
||||
void updateAvatarCollisions(float deltaTime);
|
||||
void updateArmIKAndConstraints( float deltaTime );
|
||||
void updateCollisionWithSphere( glm::vec3 position, float radius, float deltaTime );
|
||||
void updateCollisionWithEnvironment(float deltaTime);
|
||||
void updateCollisionWithVoxels(float deltaTime);
|
||||
void applyHardCollision(const glm::vec3& penetration, float elasticity, float damping);
|
||||
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
|
||||
void applyCollisionWithOtherAvatar( Avatar * other, float deltaTime );
|
||||
void checkForMouseRayTouching();
|
||||
void setScale (const float scale);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -36,7 +36,7 @@ Hand::Hand(Avatar* owningAvatar) :
|
|||
|
||||
void Hand::init() {
|
||||
// Different colors for my hand and others' hands
|
||||
if (_owningAvatar && _owningAvatar->isMyAvatar()) {
|
||||
if (_owningAvatar && _owningAvatar->getOwningNode() == NULL) {
|
||||
_ballColor = glm::vec3(0.0, 0.4, 0.0);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -742,6 +742,8 @@ void Head::renderEyeBalls() {
|
|||
|
||||
void Head::renderLookatVectors(glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition) {
|
||||
|
||||
Application::getInstance()->getGlowEffect()->begin();
|
||||
|
||||
glLineWidth(2.0);
|
||||
glBegin(GL_LINES);
|
||||
glColor4f(0.2f, 0.2f, 0.2f, 1.f);
|
||||
|
@ -753,6 +755,8 @@ void Head::renderLookatVectors(glm::vec3 leftEyePosition, glm::vec3 rightEyePosi
|
|||
glColor4f(1.0f, 1.0f, 1.0f, 0.f);
|
||||
glVertex3f(lookatPosition.x, lookatPosition.y, lookatPosition.z);
|
||||
glEnd();
|
||||
|
||||
Application::getInstance()->getGlowEffect()->end();
|
||||
}
|
||||
|
||||
void Head::updateHairPhysics(float deltaTime) {
|
||||
|
|
1048
interface/src/avatar/MyAvatar.cpp
Normal file
1048
interface/src/avatar/MyAvatar.cpp
Normal file
File diff suppressed because it is too large
Load diff
90
interface/src/avatar/MyAvatar.h
Normal file
90
interface/src/avatar/MyAvatar.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// MyAvatar.h
|
||||
// interface
|
||||
//
|
||||
// Created by Mark Peng on 8/16/13.
|
||||
// Copyright (c) 2012 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __interface__myavatar__
|
||||
#define __interface__myavatar__
|
||||
|
||||
#include "Avatar.h"
|
||||
|
||||
class MyAvatar : public Avatar {
|
||||
public:
|
||||
MyAvatar(Node* owningNode = NULL);
|
||||
|
||||
void reset();
|
||||
void simulate(float deltaTime, Transmitter* transmitter, float gyroCameraSensitivity);
|
||||
void updateFromGyrosAndOrWebcam(bool gyroLook, float pitchFromTouch);
|
||||
void render(bool lookingInMirror, bool renderAvatarBalls);
|
||||
void renderScreenTint(ScreenTintLayer layer, Camera& whichCamera);
|
||||
|
||||
// setters
|
||||
void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; }
|
||||
void setMovedHandOffset(glm::vec3 movedHandOffset) { _movedHandOffset = movedHandOffset; }
|
||||
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
|
||||
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
|
||||
void setLeanScale(float scale) { _leanScale = scale; }
|
||||
void setGravity(glm::vec3 gravity);
|
||||
void setOrientation(const glm::quat& orientation);
|
||||
void setNewScale(const float scale);
|
||||
void setWantCollisionsOn(bool wantCollisionsOn) { _isCollisionsOn = wantCollisionsOn; }
|
||||
|
||||
// getters
|
||||
float getNewScale() const { return _newScale; }
|
||||
float getSpeed() const { return _speed; }
|
||||
AvatarMode getMode() const { return _mode; }
|
||||
float getLeanScale() const { return _leanScale; }
|
||||
float getElapsedTimeStopped() const { return _elapsedTimeStopped; }
|
||||
float getElapsedTimeMoving() const { return _elapsedTimeMoving; }
|
||||
float getAbsoluteHeadYaw() const;
|
||||
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
|
||||
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
|
||||
Avatar* getLeadingAvatar() const { return _leadingAvatar; }
|
||||
glm::vec3 getGravity() const { return _gravity; }
|
||||
glm::vec3 getUprightHeadPosition() const;
|
||||
glm::vec3 getUprightEyeLevelPosition() const;
|
||||
|
||||
// Set what driving keys are being pressed to control thrust levels
|
||||
void setDriveKeys(int key, bool val) { _driveKeys[key] = val; };
|
||||
bool getDriveKeys(int key) { return _driveKeys[key]; };
|
||||
void jump() { _shouldJump = true; };
|
||||
|
||||
// Set/Get update the thrust that will move the avatar around
|
||||
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
|
||||
glm::vec3 getThrust() { return _thrust; };
|
||||
|
||||
private:
|
||||
bool _mousePressed;
|
||||
float _bodyPitchDelta;
|
||||
float _bodyRollDelta;
|
||||
bool _shouldJump;
|
||||
int _driveKeys[MAX_DRIVE_KEYS];
|
||||
glm::vec3 _gravity;
|
||||
float _distanceToNearestAvatar; // How close is the nearest avatar?
|
||||
Avatar* _interactingOther;
|
||||
float _elapsedTimeMoving; // Timers to drive camera transitions when moving
|
||||
float _elapsedTimeStopped;
|
||||
float _elapsedTimeSinceCollision;
|
||||
glm::vec3 _lastCollisionPosition;
|
||||
bool _speedBrakes;
|
||||
bool _isThrustOn;
|
||||
float _collisionRadius;
|
||||
|
||||
// private methods
|
||||
float getBallRenderAlpha(int ball, bool lookingInMirror) const;
|
||||
void renderBody(bool lookingInMirror, bool renderAvatarBalls);
|
||||
void updateThrust(float deltaTime, Transmitter * transmitter);
|
||||
void updateHandMovementAndTouching(float deltaTime, bool enableHandMovement);
|
||||
void updateAvatarCollisions(float deltaTime);
|
||||
void updateCollisionWithEnvironment(float deltaTime);
|
||||
void updateCollisionWithVoxels(float deltaTime);
|
||||
void applyHardCollision(const glm::vec3& penetration, float elasticity, float damping);
|
||||
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
|
||||
void applyCollisionWithOtherAvatar( Avatar * other, float deltaTime );
|
||||
void checkForMouseRayTouching();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -111,11 +111,17 @@ void AmbientOcclusionEffect::render() {
|
|||
_occlusionProgram->setUniformValue(_farLocation, farVal);
|
||||
_occlusionProgram->setUniformValue(_leftBottomLocation, left, bottom);
|
||||
_occlusionProgram->setUniformValue(_rightTopLocation, right, top);
|
||||
QSize size = Application::getInstance()->getGLWidget()->size();
|
||||
_occlusionProgram->setUniformValue(_noiseScaleLocation, size.width() / (float)ROTATION_WIDTH,
|
||||
size.height() / (float)ROTATION_HEIGHT);
|
||||
QSize widgetSize = Application::getInstance()->getGLWidget()->size();
|
||||
_occlusionProgram->setUniformValue(_noiseScaleLocation, widgetSize.width() / (float)ROTATION_WIDTH,
|
||||
widgetSize.height() / (float)ROTATION_HEIGHT);
|
||||
|
||||
renderFullscreenQuad();
|
||||
int viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
const int VIEWPORT_X_INDEX = 0;
|
||||
const int VIEWPORT_WIDTH_INDEX = 2;
|
||||
float sMin = viewport[VIEWPORT_X_INDEX] / (float)widgetSize.width();
|
||||
float sMax = (viewport[VIEWPORT_X_INDEX] + viewport[VIEWPORT_WIDTH_INDEX]) / (float)widgetSize.width();
|
||||
renderFullscreenQuad(sMin, sMax);
|
||||
|
||||
_occlusionProgram->release();
|
||||
|
||||
|
@ -133,9 +139,9 @@ void AmbientOcclusionEffect::render() {
|
|||
glBindTexture(GL_TEXTURE_2D, freeFBO->texture());
|
||||
|
||||
_blurProgram->bind();
|
||||
_blurProgram->setUniformValue(_blurScaleLocation, 1.0f / size.width(), 1.0f / size.height());
|
||||
_blurProgram->setUniformValue(_blurScaleLocation, 1.0f / widgetSize.width(), 1.0f / widgetSize.height());
|
||||
|
||||
renderFullscreenQuad();
|
||||
renderFullscreenQuad(sMin, sMax);
|
||||
|
||||
_blurProgram->release();
|
||||
|
||||
|
|
|
@ -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,15 +70,30 @@ 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());
|
||||
}
|
||||
|
||||
void GlowEffect::render() {
|
||||
static void maybeBind(QOpenGLFramebufferObject* fbo) {
|
||||
if (fbo != NULL) {
|
||||
fbo->bind();
|
||||
}
|
||||
}
|
||||
|
||||
static void maybeRelease(QOpenGLFramebufferObject* fbo) {
|
||||
if (fbo != NULL) {
|
||||
fbo->release();
|
||||
}
|
||||
}
|
||||
|
||||
QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) {
|
||||
QOpenGLFramebufferObject* primaryFBO = Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject();
|
||||
primaryFBO->release();
|
||||
glBindTexture(GL_TEXTURE_2D, primaryFBO->texture());
|
||||
|
@ -94,23 +109,29 @@ void GlowEffect::render() {
|
|||
glDisable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_FALSE);
|
||||
|
||||
QOpenGLFramebufferObject* destFBO = toTexture ?
|
||||
Application::getInstance()->getTextureCache()->getSecondaryFramebufferObject() : NULL;
|
||||
if (_isEmpty) {
|
||||
// copy the primary to the screen
|
||||
if (QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()) {
|
||||
QOpenGLFramebufferObject::blitFramebuffer(NULL, primaryFBO);
|
||||
QOpenGLFramebufferObject::blitFramebuffer(destFBO, primaryFBO);
|
||||
|
||||
} else {
|
||||
maybeBind(destFBO);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glDisable(GL_LIGHTING);
|
||||
glColor3f(1.0f, 1.0f, 1.0f);
|
||||
renderFullscreenQuad();
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glEnable(GL_LIGHTING);
|
||||
maybeRelease(destFBO);
|
||||
}
|
||||
} else if (_renderMode == ADD_MODE) {
|
||||
maybeBind(destFBO);
|
||||
_addProgram->bind();
|
||||
renderFullscreenQuad();
|
||||
_addProgram->release();
|
||||
maybeRelease(destFBO);
|
||||
|
||||
} else if (_renderMode == DIFFUSE_ADD_MODE) {
|
||||
// diffuse into the secondary/tertiary (alternating between frames)
|
||||
|
@ -139,9 +160,14 @@ void GlowEffect::render() {
|
|||
// add diffused texture to the primary
|
||||
glBindTexture(GL_TEXTURE_2D, newDiffusedFBO->texture());
|
||||
|
||||
if (toTexture) {
|
||||
destFBO = oldDiffusedFBO;
|
||||
}
|
||||
maybeBind(destFBO);
|
||||
_addSeparateProgram->bind();
|
||||
renderFullscreenQuad();
|
||||
_addSeparateProgram->release();
|
||||
maybeRelease(destFBO);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
@ -163,9 +189,14 @@ void GlowEffect::render() {
|
|||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, secondaryFBO->texture());
|
||||
|
||||
if (toTexture) {
|
||||
destFBO = Application::getInstance()->getTextureCache()->getTertiaryFramebufferObject();
|
||||
}
|
||||
maybeBind(destFBO);
|
||||
_verticalBlurAddProgram->bind();
|
||||
renderFullscreenQuad();
|
||||
_verticalBlurAddProgram->release();
|
||||
maybeRelease(destFBO);
|
||||
|
||||
} else { // _renderMode == BLUR_PERSIST_ADD_MODE
|
||||
// render the secondary to the tertiary with horizontal blur and persistence
|
||||
|
@ -196,9 +227,11 @@ void GlowEffect::render() {
|
|||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, tertiaryFBO->texture());
|
||||
|
||||
maybeBind(destFBO);
|
||||
_addSeparateProgram->bind();
|
||||
renderFullscreenQuad();
|
||||
_addSeparateProgram->release();
|
||||
maybeRelease(destFBO);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
@ -214,6 +247,8 @@ void GlowEffect::render() {
|
|||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_TRUE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
return destFBO;
|
||||
}
|
||||
|
||||
void GlowEffect::cycleRenderMode() {
|
||||
|
@ -236,3 +271,12 @@ void GlowEffect::cycleRenderMode() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Glower::Glower(float amount) {
|
||||
Application::getInstance()->getGlowEffect()->begin(amount);
|
||||
}
|
||||
|
||||
Glower::~Glower() {
|
||||
Application::getInstance()->getGlowEffect()->end();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#define __interface__GlowEffect__
|
||||
|
||||
#include <QObject>
|
||||
#include <QStack>
|
||||
|
||||
class QOpenGLFramebufferObject;
|
||||
|
||||
|
@ -40,7 +41,9 @@ public:
|
|||
void end();
|
||||
|
||||
/// Renders the glow effect. To be called after rendering the scene.
|
||||
void render();
|
||||
/// \param toTexture whether to render to a texture, rather than to the frame buffer
|
||||
/// \return the framebuffer object to which we rendered, or NULL if to the frame buffer
|
||||
QOpenGLFramebufferObject* render(bool toTexture = false);
|
||||
|
||||
public slots:
|
||||
|
||||
|
@ -61,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__) */
|
||||
|
|
|
@ -8,15 +8,15 @@
|
|||
#include "InterfaceConfig.h"
|
||||
#include "RenderUtil.h"
|
||||
|
||||
void renderFullscreenQuad() {
|
||||
void renderFullscreenQuad(float sMin, float sMax) {
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(0.0f, 0.0f);
|
||||
glTexCoord2f(sMin, 0.0f);
|
||||
glVertex2f(-1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f);
|
||||
glTexCoord2f(sMax, 0.0f);
|
||||
glVertex2f(1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f);
|
||||
glTexCoord2f(sMax, 1.0f);
|
||||
glVertex2f(1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f);
|
||||
glTexCoord2f(sMin, 1.0f);
|
||||
glVertex2f(-1.0f, 1.0f);
|
||||
glEnd();
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#ifndef __interface__RenderUtil__
|
||||
#define __interface__RenderUtil__
|
||||
|
||||
/// Renders a quad from (-1, -1, 0) to (1, 1, 0) with texture coordinates from (0, 0) to (1, 1).
|
||||
void renderFullscreenQuad();
|
||||
/// Renders a quad from (-1, -1, 0) to (1, 1, 0) with texture coordinates from (sMin, 0) to (sMax, 1).
|
||||
void renderFullscreenQuad(float sMin = 0.0f, float sMax = 1.0f);
|
||||
|
||||
#endif /* defined(__interface__RenderUtil__) */
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -1,242 +0,0 @@
|
|||
//
|
||||
// SquarePixelMap.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Tomáš Horáček on 6/25/13.
|
||||
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "SquarePixelMap.h"
|
||||
|
||||
#define CHILD_COORD_X_IS_1 0x1
|
||||
#define CHILD_COORD_Y_IS_1 0x2
|
||||
#define ALPHA_CHANNEL_RANGE_FLOAT 256.f
|
||||
#define ALPHA_CHANNEL_BIT_OFFSET 24
|
||||
#define RED_CHANNEL_BIT_OFFSET 16
|
||||
#define GREEN_CHANNEL_BIT_OFFSET 8
|
||||
|
||||
unsigned int numberOfBitsForSize(unsigned int size) {
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size--;
|
||||
|
||||
unsigned int ans = 1;
|
||||
while (size >>= 1) {
|
||||
ans++;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
struct PixelQuadTreeCoordinates {
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
unsigned int size;
|
||||
};
|
||||
|
||||
class PixelQuadTreeNode {
|
||||
public:
|
||||
PixelQuadTreeCoordinates _coord;
|
||||
uint32_t _color; // undefined value for _allChildrenHasSameColor = false
|
||||
bool _allChildrenHasSameColor;
|
||||
uint8_t _minimumNeighbourhoodAplha;
|
||||
|
||||
// 0 x -> 1
|
||||
// +---+---+
|
||||
// y | 0 | 1 | <- child index
|
||||
// | +---+---+
|
||||
// v | 2 | 3 |
|
||||
// +---+---+
|
||||
// 1
|
||||
PixelQuadTreeNode* _children[4];
|
||||
|
||||
PixelQuadTreeNode(PixelQuadTreeCoordinates coord, SquarePixelMap* pixelMap);
|
||||
~PixelQuadTreeNode() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
delete _children[i];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void updateChildCoordinates(int i, PixelQuadTreeCoordinates& childCoord) {
|
||||
childCoord.x = _coord.x;
|
||||
childCoord.y = _coord.y;
|
||||
|
||||
if (i & CHILD_COORD_X_IS_1) {
|
||||
childCoord.x += childCoord.size;
|
||||
}
|
||||
if (i & CHILD_COORD_Y_IS_1) {
|
||||
childCoord.y += childCoord.size;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasAllChildrenSameColor() {
|
||||
return false; //turn off import voxel grouping
|
||||
|
||||
for (int i = 1; i < 4; i++) {
|
||||
if (!_children[i]->_allChildrenHasSameColor) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t firstColor = _children[0]->_color;
|
||||
|
||||
for (int i = 1; i < 4; i++) {
|
||||
if (firstColor != _children[i]->_color) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
PixelQuadTreeNode::PixelQuadTreeNode(PixelQuadTreeCoordinates coord, SquarePixelMap* pixelMap) : _coord(coord), _minimumNeighbourhoodAplha(-1) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
_children[i] = NULL;
|
||||
}
|
||||
|
||||
if (_coord.size == 1) {
|
||||
_color = pixelMap->getPixelAt(_coord.x, _coord.y);
|
||||
|
||||
_minimumNeighbourhoodAplha = std::min<uint8_t>(pixelMap->getAlphaAt(_coord.x + 1, _coord.y), _minimumNeighbourhoodAplha);
|
||||
_minimumNeighbourhoodAplha = std::min<uint8_t>(pixelMap->getAlphaAt(_coord.x - 1, _coord.y), _minimumNeighbourhoodAplha);
|
||||
_minimumNeighbourhoodAplha = std::min<uint8_t>(pixelMap->getAlphaAt(_coord.x, _coord.y + 1), _minimumNeighbourhoodAplha);
|
||||
_minimumNeighbourhoodAplha = std::min<uint8_t>(pixelMap->getAlphaAt(_coord.x, _coord.y - 1), _minimumNeighbourhoodAplha);
|
||||
|
||||
_allChildrenHasSameColor = true;
|
||||
} else {
|
||||
PixelQuadTreeCoordinates childCoord = PixelQuadTreeCoordinates();
|
||||
childCoord.size = _coord.size / 2;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
this->updateChildCoordinates(i, childCoord);
|
||||
|
||||
|
||||
if (childCoord.x < pixelMap->dimension() &&
|
||||
childCoord.y < pixelMap->dimension()) {
|
||||
|
||||
_children[i] = new PixelQuadTreeNode(childCoord, pixelMap);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->hasAllChildrenSameColor()) {
|
||||
_allChildrenHasSameColor = true;
|
||||
_color = _children[0]->_color;
|
||||
|
||||
_minimumNeighbourhoodAplha = _children[0]->_minimumNeighbourhoodAplha;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
_minimumNeighbourhoodAplha = std::min<uint8_t>(_children[i]->_minimumNeighbourhoodAplha, _minimumNeighbourhoodAplha);
|
||||
delete _children[i];
|
||||
_children[i] = NULL;
|
||||
}
|
||||
} else {
|
||||
_allChildrenHasSameColor = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SquarePixelMap::SquarePixelMap(const uint32_t* pixels, int dimension) : _rootPixelQuadTreeNode(NULL) {
|
||||
_data = new SquarePixelMapData();
|
||||
_data->dimension = dimension;
|
||||
_data->reference_counter = 1;
|
||||
|
||||
size_t pixels_size = dimension * dimension;
|
||||
_data->pixels = new uint32_t[pixels_size];
|
||||
memcpy((void*)_data->pixels, (void*)pixels, sizeof(uint32_t) * pixels_size);
|
||||
}
|
||||
|
||||
SquarePixelMap::SquarePixelMap(const SquarePixelMap& other) {
|
||||
this->_data = other._data;
|
||||
this->_data->reference_counter++;
|
||||
}
|
||||
|
||||
SquarePixelMap::~SquarePixelMap() {
|
||||
delete _rootPixelQuadTreeNode;
|
||||
|
||||
if (--_data->reference_counter == 0) {
|
||||
delete _data->pixels;
|
||||
delete _data;
|
||||
}
|
||||
}
|
||||
|
||||
void SquarePixelMap::addVoxelsToVoxelTree(VoxelTree* voxelTree) {
|
||||
this->generateRootPixelQuadTreeNode();
|
||||
this->createVoxelsFromPixelQuadTreeToVoxelTree(_rootPixelQuadTreeNode, voxelTree);
|
||||
}
|
||||
|
||||
int SquarePixelMap::dimension() {
|
||||
return _data->dimension;
|
||||
}
|
||||
|
||||
uint32_t SquarePixelMap::getPixelAt(unsigned int x, unsigned int y) {
|
||||
return _data->pixels[x + y * _data->dimension];
|
||||
}
|
||||
|
||||
uint8_t SquarePixelMap::getAlphaAt(int x, int y) {
|
||||
int max_coord = this->dimension() - 1;
|
||||
|
||||
if (x < 0 || y < 0 || x > max_coord || y > max_coord) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this->getPixelAt(x, y) >> ALPHA_CHANNEL_BIT_OFFSET;
|
||||
}
|
||||
|
||||
void SquarePixelMap::generateRootPixelQuadTreeNode() {
|
||||
delete _rootPixelQuadTreeNode;
|
||||
|
||||
PixelQuadTreeCoordinates rootNodeCoord = PixelQuadTreeCoordinates();
|
||||
rootNodeCoord.size = 1 << numberOfBitsForSize(_data->dimension);
|
||||
rootNodeCoord.x = rootNodeCoord.y = 0;
|
||||
|
||||
_rootPixelQuadTreeNode = new PixelQuadTreeNode(rootNodeCoord, this);
|
||||
}
|
||||
|
||||
void SquarePixelMap::createVoxelsFromPixelQuadTreeToVoxelTree(PixelQuadTreeNode* pixelQuadTreeNode, VoxelTree* voxelTree) {
|
||||
if (pixelQuadTreeNode->_allChildrenHasSameColor) {
|
||||
VoxelDetail voxel = this->getVoxelDetail(pixelQuadTreeNode);
|
||||
|
||||
unsigned char minimumNeighbourhoodAplha = std::max<int>(0, pixelQuadTreeNode->_minimumNeighbourhoodAplha - 1);
|
||||
|
||||
float minimumNeighbourhoodY = voxel.s * (floor(minimumNeighbourhoodAplha / (ALPHA_CHANNEL_RANGE_FLOAT * voxel.s)) + 0.5);
|
||||
|
||||
do {
|
||||
voxelTree->createVoxel(voxel.x, voxel.y, voxel.z, voxel.s, voxel.red, voxel.green, voxel.blue, true);
|
||||
} while ((voxel.y -= voxel.s) > minimumNeighbourhoodY);
|
||||
} else {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
PixelQuadTreeNode* child = pixelQuadTreeNode->_children[i];
|
||||
if (child) {
|
||||
this->createVoxelsFromPixelQuadTreeToVoxelTree(child, voxelTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VoxelDetail SquarePixelMap::getVoxelDetail(PixelQuadTreeNode* pixelQuadTreeNode) {
|
||||
VoxelDetail voxel = VoxelDetail();
|
||||
|
||||
uint32_t color = pixelQuadTreeNode->_color;
|
||||
unsigned char alpha = std::max<int>(0, (color >> ALPHA_CHANNEL_BIT_OFFSET) - 1);
|
||||
|
||||
voxel.red = color >> RED_CHANNEL_BIT_OFFSET;
|
||||
voxel.green = color >> GREEN_CHANNEL_BIT_OFFSET;
|
||||
voxel.blue = color;
|
||||
|
||||
|
||||
float rootSize = _rootPixelQuadTreeNode->_coord.size;
|
||||
|
||||
voxel.s = pixelQuadTreeNode->_coord.size / rootSize;
|
||||
voxel.y = voxel.s * (floor(alpha / (ALPHA_CHANNEL_RANGE_FLOAT * voxel.s)) + 0.5);
|
||||
voxel.x = pixelQuadTreeNode->_coord.x / rootSize + voxel.s / 2;
|
||||
voxel.z = pixelQuadTreeNode->_coord.y / rootSize + voxel.s / 2;
|
||||
|
||||
return voxel;
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// SquarePixelMap.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Tomáš Horáček on 6/25/13.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef __hifi__SquarePixelMap__
|
||||
#define __hifi__SquarePixelMap__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "VoxelTree.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
class PixelQuadTreeNode;
|
||||
|
||||
struct SquarePixelMapData {
|
||||
const uint32_t* pixels;
|
||||
int dimension;
|
||||
int reference_counter;
|
||||
};
|
||||
|
||||
class SquarePixelMap {
|
||||
public:
|
||||
SquarePixelMap(const uint32_t* pixels, int dimension);
|
||||
SquarePixelMap(const SquarePixelMap& other);
|
||||
~SquarePixelMap();
|
||||
|
||||
void addVoxelsToVoxelTree(VoxelTree* voxelTree);
|
||||
|
||||
int dimension();
|
||||
uint32_t getPixelAt(unsigned int x, unsigned int y);
|
||||
uint8_t getAlphaAt(int x, int y);
|
||||
private:
|
||||
SquarePixelMapData* _data;
|
||||
PixelQuadTreeNode* _rootPixelQuadTreeNode;
|
||||
|
||||
void generateRootPixelQuadTreeNode();
|
||||
void createVoxelsFromPixelQuadTreeToVoxelTree(PixelQuadTreeNode* pixelQuadTreeNode, VoxelTree* voxelTree);
|
||||
VoxelDetail getVoxelDetail(PixelQuadTreeNode* pixelQuadTreeNode);
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__SquarePixelMap__) */
|
|
@ -19,13 +19,13 @@
|
|||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QImage>
|
||||
#include <QRgb>
|
||||
|
||||
#include "CoverageMap.h"
|
||||
#include "GeometryUtil.h"
|
||||
#include "OctalCode.h"
|
||||
#include "PacketHeaders.h"
|
||||
#include "SharedUtil.h"
|
||||
#include "SquarePixelMap.h"
|
||||
#include "Tags.h"
|
||||
#include "ViewFrustum.h"
|
||||
#include "VoxelConstants.h"
|
||||
|
@ -52,6 +52,10 @@ VoxelTree::VoxelTree(bool shouldReaverage) :
|
|||
_shouldReaverage(shouldReaverage),
|
||||
_stopImport(false) {
|
||||
rootNode = new VoxelNode();
|
||||
|
||||
pthread_mutex_init(&_encodeSetLock, NULL);
|
||||
pthread_mutex_init(&_deleteSetLock, NULL);
|
||||
pthread_mutex_init(&_deletePendingSetLock, NULL);
|
||||
}
|
||||
|
||||
VoxelTree::~VoxelTree() {
|
||||
|
@ -60,6 +64,10 @@ VoxelTree::~VoxelTree() {
|
|||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
delete rootNode->getChildAtIndex(i);
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&_encodeSetLock);
|
||||
pthread_mutex_destroy(&_deleteSetLock);
|
||||
pthread_mutex_destroy(&_deletePendingSetLock);
|
||||
}
|
||||
|
||||
|
||||
|
@ -397,7 +405,16 @@ void VoxelTree::deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool collapse
|
|||
args.pathChanged = false;
|
||||
|
||||
VoxelNode* node = rootNode;
|
||||
deleteVoxelCodeFromTreeRecursion(node, &args);
|
||||
|
||||
// We can't encode and delete nodes at the same time, so we guard against deleting any node that is actively
|
||||
// being encoded. And we stick that code on our pendingDelete list.
|
||||
if (isEncoding(codeBuffer)) {
|
||||
queueForLaterDelete(codeBuffer);
|
||||
} else {
|
||||
startDeleting(codeBuffer);
|
||||
deleteVoxelCodeFromTreeRecursion(node, &args);
|
||||
doneDeleting(codeBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraData) {
|
||||
|
@ -1002,15 +1019,16 @@ bool VoxelTree::findCapsulePenetration(const glm::vec3& start, const glm::vec3&
|
|||
return args.found;
|
||||
}
|
||||
|
||||
|
||||
int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag,
|
||||
EncodeBitstreamParams& params) const {
|
||||
EncodeBitstreamParams& params) {
|
||||
|
||||
startEncoding(node);
|
||||
// How many bytes have we written so far at this level;
|
||||
int bytesWritten = 0;
|
||||
|
||||
// If we're at a node that is out of view, then we can return, because no nodes below us will be in view!
|
||||
if (params.viewFrustum && !node->isInView(*params.viewFrustum)) {
|
||||
doneEncoding(node);
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
|
@ -1061,6 +1079,8 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer,
|
|||
// otherwise... if we didn't write any child bytes, then pretend like we also didn't write our octal code
|
||||
bytesWritten = 0;
|
||||
}
|
||||
|
||||
doneEncoding(node);
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
|
@ -1587,25 +1607,61 @@ bool VoxelTree::readFromSVOFile(const char* fileName) {
|
|||
}
|
||||
|
||||
bool VoxelTree::readFromSquareARGB32Pixels(const char* filename) {
|
||||
QImage pngImage = QImage(filename);
|
||||
if (pngImage.height() != pngImage.width()) {
|
||||
qDebug("ERROR: Bad PNG size: height != width.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
emit importSize(1.0f, 1.0f, 1.0f);
|
||||
emit importProgress(0);
|
||||
|
||||
const uint32_t* pixels;
|
||||
if (pngImage.format() == QImage::Format_ARGB32) {
|
||||
pixels = reinterpret_cast<const uint32_t*>(pngImage.constBits());
|
||||
} else {
|
||||
QImage tmp = pngImage.convertToFormat(QImage::Format_ARGB32);
|
||||
pixels = reinterpret_cast<const uint32_t*>(tmp.constBits());
|
||||
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);
|
||||
}
|
||||
|
||||
SquarePixelMap pixelMap = SquarePixelMap(pixels, pngImage.height());
|
||||
pixelMap.addVoxelsToVoxelTree(this);
|
||||
int maxSize = std::max(pngImage.width(), pngImage.height());
|
||||
|
||||
int scale = 1;
|
||||
while (maxSize > scale) {scale *= 2;}
|
||||
float size = 1.0f / scale;
|
||||
|
||||
QRgb pixel;
|
||||
int minNeighborhoodAlpha;
|
||||
|
||||
for (int i = 0; i < pngImage.width(); ++i) {
|
||||
for (int j = 0; j < pngImage.height(); ++j) {
|
||||
emit importProgress((100 * (i * pngImage.height() + j)) /
|
||||
(pngImage.width() * pngImage.height()));
|
||||
|
||||
pixel = pngImage.pixel(i, j);
|
||||
minNeighborhoodAlpha = qAlpha(pixel) - 1;
|
||||
|
||||
if (i != 0) {
|
||||
minNeighborhoodAlpha = std::min(minNeighborhoodAlpha, qAlpha(pngImage.pixel(i - 1, j)));
|
||||
}
|
||||
if (j != 0) {
|
||||
minNeighborhoodAlpha = std::min(minNeighborhoodAlpha, qAlpha(pngImage.pixel(i, j - 1)));
|
||||
}
|
||||
if (i < pngImage.width() - 1) {
|
||||
minNeighborhoodAlpha = std::min(minNeighborhoodAlpha, qAlpha(pngImage.pixel(i + 1, j)));
|
||||
}
|
||||
if (j < pngImage.height() - 1) {
|
||||
minNeighborhoodAlpha = std::min(minNeighborhoodAlpha, qAlpha(pngImage.pixel(i, j + 1)));
|
||||
}
|
||||
|
||||
while (qAlpha(pixel) > minNeighborhoodAlpha) {
|
||||
++minNeighborhoodAlpha;
|
||||
createVoxel(i * size,
|
||||
(minNeighborhoodAlpha - minAlpha) * size,
|
||||
j * size,
|
||||
size,
|
||||
qRed(pixel),
|
||||
qGreen(pixel),
|
||||
qBlue(pixel),
|
||||
true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
emit importProgress(100);
|
||||
return true;
|
||||
|
@ -1707,7 +1763,7 @@ bool VoxelTree::readFromSchematicFile(const char *fileName) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void VoxelTree::writeToSVOFile(const char* fileName, VoxelNode* node) const {
|
||||
void VoxelTree::writeToSVOFile(const char* fileName, VoxelNode* node) {
|
||||
|
||||
std::ofstream file(fileName, std::ios::out|std::ios::binary);
|
||||
|
||||
|
@ -1793,6 +1849,65 @@ void VoxelTree::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destin
|
|||
}
|
||||
}
|
||||
|
||||
void dumpSetContents(const char* name, std::set<unsigned char*> set) {
|
||||
printf("set %s has %ld elements\n", name, set.size());
|
||||
/*
|
||||
for (std::set<unsigned char*>::iterator i = set.begin(); i != set.end(); ++i) {
|
||||
printOctalCode(*i);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void VoxelTree::startEncoding(VoxelNode* node) {
|
||||
pthread_mutex_lock(&_encodeSetLock);
|
||||
_codesBeingEncoded.insert(node->getOctalCode());
|
||||
pthread_mutex_unlock(&_encodeSetLock);
|
||||
}
|
||||
|
||||
void VoxelTree::doneEncoding(VoxelNode* node) {
|
||||
pthread_mutex_lock(&_encodeSetLock);
|
||||
_codesBeingEncoded.erase(node->getOctalCode());
|
||||
pthread_mutex_unlock(&_encodeSetLock);
|
||||
|
||||
// if we have any pending delete codes, then delete them now.
|
||||
emptyDeleteQueue();
|
||||
}
|
||||
|
||||
void VoxelTree::startDeleting(unsigned char* code) {
|
||||
pthread_mutex_lock(&_deleteSetLock);
|
||||
_codesBeingDeleted.insert(code);
|
||||
pthread_mutex_unlock(&_deleteSetLock);
|
||||
}
|
||||
|
||||
void VoxelTree::doneDeleting(unsigned char* code) {
|
||||
pthread_mutex_lock(&_deleteSetLock);
|
||||
_codesBeingDeleted.erase(code);
|
||||
pthread_mutex_unlock(&_deleteSetLock);
|
||||
}
|
||||
|
||||
bool VoxelTree::isEncoding(unsigned char* codeBuffer) {
|
||||
pthread_mutex_lock(&_encodeSetLock);
|
||||
bool isEncoding = (_codesBeingEncoded.find(codeBuffer) != _codesBeingEncoded.end());
|
||||
pthread_mutex_unlock(&_encodeSetLock);
|
||||
return isEncoding;
|
||||
}
|
||||
|
||||
void VoxelTree::queueForLaterDelete(unsigned char* codeBuffer) {
|
||||
pthread_mutex_lock(&_deletePendingSetLock);
|
||||
_codesPendingDelete.insert(codeBuffer);
|
||||
pthread_mutex_unlock(&_deletePendingSetLock);
|
||||
}
|
||||
|
||||
void VoxelTree::emptyDeleteQueue() {
|
||||
pthread_mutex_lock(&_deletePendingSetLock);
|
||||
for (std::set<unsigned char*>::iterator i = _codesPendingDelete.begin(); i != _codesPendingDelete.end(); ++i) {
|
||||
unsigned char* codeToDelete = *i;
|
||||
_codesBeingDeleted.erase(codeToDelete);
|
||||
deleteVoxelCodeFromTree(codeToDelete, COLLAPSE_EMPTY_TREE);
|
||||
}
|
||||
pthread_mutex_unlock(&_deletePendingSetLock);
|
||||
}
|
||||
|
||||
void VoxelTree::cancelImport() {
|
||||
_stopImport = true;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#ifndef __hifi__VoxelTree__
|
||||
#define __hifi__VoxelTree__
|
||||
|
||||
#include <set>
|
||||
#include <PointerStack.h>
|
||||
#include <SimpleMovingAverage.h>
|
||||
|
||||
|
@ -155,7 +156,7 @@ public:
|
|||
const glm::vec3& point, void* extraData=NULL);
|
||||
|
||||
int encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag,
|
||||
EncodeBitstreamParams& params) const;
|
||||
EncodeBitstreamParams& params) ;
|
||||
|
||||
bool isDirty() const { return _isDirty; };
|
||||
void clearDirtyBit() { _isDirty = false; };
|
||||
|
@ -172,7 +173,7 @@ public:
|
|||
void loadVoxelsFile(const char* fileName, bool wantColorRandomizer);
|
||||
|
||||
// these will read/write files that match the wireformat, excluding the 'V' leading
|
||||
void writeToSVOFile(const char* filename, VoxelNode* node = NULL) const;
|
||||
void writeToSVOFile(const char* filename, VoxelNode* node = NULL);
|
||||
bool readFromSVOFile(const char* filename);
|
||||
// reads voxels from square image with alpha as a Y-axis
|
||||
bool readFromSquareARGB32Pixels(const char *filename);
|
||||
|
@ -219,6 +220,40 @@ private:
|
|||
unsigned long int _nodesChangedFromBitstream;
|
||||
bool _shouldReaverage;
|
||||
bool _stopImport;
|
||||
|
||||
/// Octal Codes of any subtrees currently being encoded. While any of these codes is being encoded, ancestors and
|
||||
/// descendants of them can not be deleted.
|
||||
std::set<unsigned char*> _codesBeingEncoded;
|
||||
/// mutex lock to protect the encoding set
|
||||
pthread_mutex_t _encodeSetLock;
|
||||
|
||||
/// Called to indicate that a VoxelNode is in the process of being encoded.
|
||||
void startEncoding(VoxelNode* node);
|
||||
/// Called to indicate that a VoxelNode is done being encoded.
|
||||
void doneEncoding(VoxelNode* node);
|
||||
/// Is the Octal Code currently being deleted?
|
||||
bool isEncoding(unsigned char* codeBuffer);
|
||||
|
||||
/// Octal Codes of any subtrees currently being deleted. While any of these codes is being deleted, ancestors and
|
||||
/// descendants of them can not be encoded.
|
||||
std::set<unsigned char*> _codesBeingDeleted;
|
||||
/// mutex lock to protect the deleting set
|
||||
pthread_mutex_t _deleteSetLock;
|
||||
|
||||
/// Called to indicate that an octal code is in the process of being deleted.
|
||||
void startDeleting(unsigned char* code);
|
||||
/// Called to indicate that an octal code is done being deleted.
|
||||
void doneDeleting(unsigned char* code);
|
||||
/// Octal Codes that were attempted to be deleted but couldn't be because they were actively being encoded, and were
|
||||
/// instead queued for later delete
|
||||
std::set<unsigned char*> _codesPendingDelete;
|
||||
/// mutex lock to protect the deleting set
|
||||
pthread_mutex_t _deletePendingSetLock;
|
||||
|
||||
/// Adds an Octal Code to the set of codes that needs to be deleted
|
||||
void queueForLaterDelete(unsigned char* codeBuffer);
|
||||
/// flushes out any Octal Codes that had to be queued
|
||||
void emptyDeleteQueue();
|
||||
};
|
||||
|
||||
float boundaryDistanceForRenderLevel(unsigned int renderLevel);
|
||||
|
|
26
voxel-server/src/NodeWatcher.cpp
Normal file
26
voxel-server/src/NodeWatcher.cpp
Normal 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;
|
||||
}
|
||||
};
|
23
voxel-server/src/NodeWatcher.h
Normal file
23
voxel-server/src/NodeWatcher.h
Normal 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__
|
|
@ -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() {
|
||||
|
|
|
@ -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__) */
|
||||
|
|
37
voxel-server/src/VoxelPersistThread.cpp
Normal file
37
voxel-server/src/VoxelPersistThread.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// VoxelPersistThread.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 persistence
|
||||
//
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "VoxelPersistThread.h"
|
||||
#include "VoxelServer.h"
|
||||
|
||||
VoxelPersistThread::VoxelPersistThread(VoxelTree* tree, const char* filename, int persistInterval) :
|
||||
_tree(tree),
|
||||
_filename(filename),
|
||||
_persistInterval(persistInterval) {
|
||||
}
|
||||
|
||||
bool VoxelPersistThread::process() {
|
||||
uint64_t MSECS_TO_USECS = 1000;
|
||||
usleep(_persistInterval * MSECS_TO_USECS);
|
||||
|
||||
|
||||
// check the dirty bit and persist here...
|
||||
if (_tree->isDirty()) {
|
||||
printf("saving voxels to file %s...\n",_filename);
|
||||
_tree->writeToSVOFile(_filename);
|
||||
_tree->clearDirtyBit(); // tree is clean after saving
|
||||
printf("DONE saving voxels to file...\n");
|
||||
}
|
||||
|
||||
return isStillRunning(); // keep running till they terminate us
|
||||
}
|
33
voxel-server/src/VoxelPersistThread.h
Normal file
33
voxel-server/src/VoxelPersistThread.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// VoxelPersistThread.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 voxel persistence
|
||||
//
|
||||
|
||||
#ifndef __voxel_server__VoxelPersistThread__
|
||||
#define __voxel_server__VoxelPersistThread__
|
||||
|
||||
#include <GenericThread.h>
|
||||
#include <NetworkPacket.h>
|
||||
#include <VoxelTree.h>
|
||||
|
||||
/// Generalized threaded processor for handling received inbound packets.
|
||||
class VoxelPersistThread : public virtual GenericThread {
|
||||
public:
|
||||
static const int DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds
|
||||
|
||||
VoxelPersistThread(VoxelTree* tree, const char* filename, int persistInterval = DEFAULT_PERSIST_INTERVAL);
|
||||
protected:
|
||||
/// Implements generic processing behavior for this thread.
|
||||
virtual bool process();
|
||||
private:
|
||||
VoxelTree* _tree;
|
||||
const char* _filename;
|
||||
int _persistInterval;
|
||||
};
|
||||
|
||||
#endif // __voxel_server__VoxelPersistThread__
|
303
voxel-server/src/VoxelSendThread.cpp
Normal file
303
voxel-server/src/VoxelSendThread.cpp
Normal 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);
|
||||
}
|
||||
|
37
voxel-server/src/VoxelSendThread.h
Normal file
37
voxel-server/src/VoxelSendThread.h
Normal 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__
|
58
voxel-server/src/VoxelServer.h
Normal file
58
voxel-server/src/VoxelServer.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
// VoxelServer.h
|
||||
// voxel-server
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 8/21/13
|
||||
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef __voxel_server__VoxelServer__
|
||||
#define __voxel_server__VoxelServer__
|
||||
|
||||
#include <SharedUtil.h>
|
||||
#include <NodeList.h> // for MAX_PACKET_SIZE
|
||||
#include <EnvironmentData.h>
|
||||
#include <JurisdictionSender.h>
|
||||
#include <VoxelTree.h>
|
||||
|
||||
#include "VoxelServerPacketProcessor.h"
|
||||
|
||||
|
||||
const int MAX_FILENAME_LENGTH = 1024;
|
||||
const int VOXEL_LISTEN_PORT = 40106;
|
||||
const int VOXEL_SIZE_BYTES = 3 + (3 * sizeof(float));
|
||||
const int VOXELS_PER_PACKET = (MAX_PACKET_SIZE - 1) / VOXEL_SIZE_BYTES;
|
||||
const int MIN_BRIGHTNESS = 64;
|
||||
const float DEATH_STAR_RADIUS = 4.0;
|
||||
const float MAX_CUBE = 0.05f;
|
||||
const int VOXEL_SEND_INTERVAL_USECS = 17 * 1000; // approximately 60fps
|
||||
const int SENDING_TIME_TO_SPARE = 5 * 1000; // usec of sending interval to spare for calculating voxels
|
||||
const int INTERVALS_PER_SECOND = 1000 * 1000 / VOXEL_SEND_INTERVAL_USECS;
|
||||
const int MAX_VOXEL_TREE_DEPTH_LEVELS = 4;
|
||||
const int ENVIRONMENT_SEND_INTERVAL_USECS = 1000000;
|
||||
|
||||
extern const char* LOCAL_VOXELS_PERSIST_FILE;
|
||||
extern const char* VOXELS_PERSIST_FILE;
|
||||
extern char voxelPersistFilename[MAX_FILENAME_LENGTH];
|
||||
extern int PACKETS_PER_CLIENT_PER_INTERVAL;
|
||||
|
||||
extern VoxelTree serverTree; // this IS a reaveraging tree
|
||||
extern bool wantVoxelPersist;
|
||||
extern bool wantLocalDomain;
|
||||
extern bool debugVoxelSending;
|
||||
extern bool shouldShowAnimationDebug;
|
||||
extern bool displayVoxelStats;
|
||||
extern bool debugVoxelReceiving;
|
||||
extern bool sendEnvironments;
|
||||
extern bool sendMinimalEnvironment;
|
||||
extern bool dumpVoxelsOnMove;
|
||||
extern EnvironmentData environmentData[3];
|
||||
extern int receivedPacketCount;
|
||||
extern JurisdictionMap* jurisdiction;
|
||||
extern JurisdictionSender* jurisdictionSender;
|
||||
extern VoxelServerPacketProcessor* voxelServerPacketProcessor;
|
||||
extern pthread_mutex_t treeLock;
|
||||
|
||||
|
||||
|
||||
#endif // __voxel_server__VoxelServer__
|
117
voxel-server/src/VoxelServerPacketProcessor.cpp
Normal file
117
voxel-server/src/VoxelServerPacketProcessor.cpp
Normal file
|
@ -0,0 +1,117 @@
|
|||
//
|
||||
// VoxelServerPacketProcessor.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 network packet processor for the voxel-server
|
||||
//
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "VoxelServer.h"
|
||||
#include "VoxelServerPacketProcessor.h"
|
||||
|
||||
|
||||
void VoxelServerPacketProcessor::processPacket(sockaddr& senderAddress, unsigned char* packetData, ssize_t packetLength) {
|
||||
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(packetData);
|
||||
|
||||
if (packetData[0] == PACKET_TYPE_SET_VOXEL || packetData[0] == PACKET_TYPE_SET_VOXEL_DESTRUCTIVE) {
|
||||
bool destructive = (packetData[0] == PACKET_TYPE_SET_VOXEL_DESTRUCTIVE);
|
||||
PerformanceWarning warn(::shouldShowAnimationDebug,
|
||||
destructive ? "PACKET_TYPE_SET_VOXEL_DESTRUCTIVE" : "PACKET_TYPE_SET_VOXEL",
|
||||
::shouldShowAnimationDebug);
|
||||
|
||||
::receivedPacketCount++;
|
||||
|
||||
unsigned short int itemNumber = (*((unsigned short int*)(packetData + numBytesPacketHeader)));
|
||||
if (::shouldShowAnimationDebug) {
|
||||
printf("got %s - command from client receivedBytes=%ld itemNumber=%d\n",
|
||||
destructive ? "PACKET_TYPE_SET_VOXEL_DESTRUCTIVE" : "PACKET_TYPE_SET_VOXEL",
|
||||
packetLength, itemNumber);
|
||||
}
|
||||
|
||||
if (::debugVoxelReceiving) {
|
||||
printf("got %s - %d command from client receivedBytes=%ld itemNumber=%d\n",
|
||||
destructive ? "PACKET_TYPE_SET_VOXEL_DESTRUCTIVE" : "PACKET_TYPE_SET_VOXEL",
|
||||
::receivedPacketCount, packetLength, itemNumber);
|
||||
}
|
||||
int atByte = numBytesPacketHeader + sizeof(itemNumber);
|
||||
unsigned char* voxelData = (unsigned char*)&packetData[atByte];
|
||||
while (atByte < packetLength) {
|
||||
unsigned char octets = (unsigned char)*voxelData;
|
||||
const int COLOR_SIZE_IN_BYTES = 3;
|
||||
int voxelDataSize = bytesRequiredForCodeLength(octets) + COLOR_SIZE_IN_BYTES;
|
||||
int voxelCodeSize = bytesRequiredForCodeLength(octets);
|
||||
|
||||
if (::shouldShowAnimationDebug) {
|
||||
int red = voxelData[voxelCodeSize + 0];
|
||||
int green = voxelData[voxelCodeSize + 1];
|
||||
int blue = voxelData[voxelCodeSize + 2];
|
||||
|
||||
float* vertices = firstVertexForCode(voxelData);
|
||||
printf("inserting voxel: %f,%f,%f r=%d,g=%d,b=%d\n", vertices[0], vertices[1], vertices[2], red, green, blue);
|
||||
delete[] vertices;
|
||||
}
|
||||
|
||||
serverTree.readCodeColorBufferToTree(voxelData, destructive);
|
||||
// skip to next
|
||||
voxelData += voxelDataSize;
|
||||
atByte += voxelDataSize;
|
||||
}
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
Node* node = NodeList::getInstance()->nodeWithAddress(&senderAddress);
|
||||
if (node) {
|
||||
node->setLastHeardMicrostamp(usecTimestampNow());
|
||||
}
|
||||
|
||||
} else if (packetData[0] == PACKET_TYPE_ERASE_VOXEL) {
|
||||
|
||||
// Send these bits off to the VoxelTree class to process them
|
||||
pthread_mutex_lock(&::treeLock);
|
||||
::serverTree.processRemoveVoxelBitstream((unsigned char*)packetData, packetLength);
|
||||
pthread_mutex_unlock(&::treeLock);
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
Node* node = NodeList::getInstance()->nodeWithAddress(&senderAddress);
|
||||
if (node) {
|
||||
node->setLastHeardMicrostamp(usecTimestampNow());
|
||||
}
|
||||
} else if (packetData[0] == PACKET_TYPE_Z_COMMAND) {
|
||||
|
||||
// the Z command is a special command that allows the sender to send the voxel server high level semantic
|
||||
// requests, like erase all, or add sphere scene
|
||||
|
||||
char* command = (char*) &packetData[numBytesPacketHeader]; // start of the command
|
||||
int commandLength = strlen(command); // commands are null terminated strings
|
||||
int totalLength = numBytesPacketHeader + commandLength + 1; // 1 for null termination
|
||||
printf("got Z message len(%ld)= %s\n", packetLength, command);
|
||||
bool rebroadcast = true; // by default rebroadcast
|
||||
|
||||
while (totalLength <= packetLength) {
|
||||
if (strcmp(command, TEST_COMMAND) == 0) {
|
||||
printf("got Z message == a message, nothing to do, just report\n");
|
||||
}
|
||||
totalLength += commandLength + 1; // 1 for null termination
|
||||
}
|
||||
|
||||
if (rebroadcast) {
|
||||
// Now send this to the connected nodes so they can also process these messages
|
||||
printf("rebroadcasting Z message to connected nodes... nodeList.broadcastToNodes()\n");
|
||||
NodeList::getInstance()->broadcastToNodes(packetData, packetLength, &NODE_TYPE_AGENT, 1);
|
||||
}
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
Node* node = NodeList::getInstance()->nodeWithAddress(&senderAddress);
|
||||
if (node) {
|
||||
node->setLastHeardMicrostamp(usecTimestampNow());
|
||||
}
|
||||
} else {
|
||||
printf("unknown packet ignored... packetData[0]=%c\n", packetData[0]);
|
||||
}
|
||||
}
|
||||
|
22
voxel-server/src/VoxelServerPacketProcessor.h
Normal file
22
voxel-server/src/VoxelServerPacketProcessor.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// VoxelServerPacketProcessor.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 network packet processor for the voxel-server
|
||||
//
|
||||
|
||||
#ifndef __voxel_server__VoxelServerPacketProcessor__
|
||||
#define __voxel_server__VoxelServerPacketProcessor__
|
||||
|
||||
#include <ReceivedPacketProcessor.h>
|
||||
|
||||
/// Handles processing of incoming network packets for the voxel-server. As with other ReceivedPacketProcessor classes
|
||||
/// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket()
|
||||
class VoxelServerPacketProcessor : public ReceivedPacketProcessor {
|
||||
protected:
|
||||
virtual void processPacket(sockaddr& senderAddress, unsigned char* packetData, ssize_t packetLength);
|
||||
};
|
||||
#endif // __voxel_server__VoxelServerPacketProcessor__
|
|
@ -10,6 +10,7 @@
|
|||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
#include <OctalCode.h>
|
||||
#include <NodeList.h>
|
||||
#include <NodeTypes.h>
|
||||
|
@ -20,9 +21,13 @@
|
|||
#include <PacketHeaders.h>
|
||||
#include <SceneUtils.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include <JurisdictionSender.h>
|
||||
|
||||
#include "NodeWatcher.h"
|
||||
#include "VoxelPersistThread.h"
|
||||
#include "VoxelSendThread.h"
|
||||
#include "VoxelServerPacketProcessor.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Syssocket.h"
|
||||
#include "Systime.h"
|
||||
|
@ -32,37 +37,15 @@
|
|||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
#include "VoxelServer.h"
|
||||
|
||||
const char* LOCAL_VOXELS_PERSIST_FILE = "resources/voxels.svo";
|
||||
const char* VOXELS_PERSIST_FILE = "/etc/highfidelity/voxel-server/resources/voxels.svo";
|
||||
const int MAX_FILENAME_LENGTH = 1024;
|
||||
char voxelPersistFilename[MAX_FILENAME_LENGTH];
|
||||
const int VOXEL_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds
|
||||
|
||||
const int VOXEL_LISTEN_PORT = 40106;
|
||||
|
||||
|
||||
const int VOXEL_SIZE_BYTES = 3 + (3 * sizeof(float));
|
||||
const int VOXELS_PER_PACKET = (MAX_PACKET_SIZE - 1) / VOXEL_SIZE_BYTES;
|
||||
|
||||
const int MIN_BRIGHTNESS = 64;
|
||||
const float DEATH_STAR_RADIUS = 4.0;
|
||||
const float MAX_CUBE = 0.05f;
|
||||
|
||||
const int VOXEL_SEND_INTERVAL_USECS = 17 * 1000; // approximately 60fps
|
||||
int PACKETS_PER_CLIENT_PER_INTERVAL = 10;
|
||||
const int SENDING_TIME_TO_SPARE = 5 * 1000; // usec of sending interval to spare for calculating voxels
|
||||
const int INTERVALS_PER_SECOND = 1000 * 1000 / VOXEL_SEND_INTERVAL_USECS;
|
||||
|
||||
const int MAX_VOXEL_TREE_DEPTH_LEVELS = 4;
|
||||
|
||||
const int ENVIRONMENT_SEND_INTERVAL_USECS = 1000000;
|
||||
|
||||
VoxelTree serverTree(true); // this IS a reaveraging tree
|
||||
bool wantVoxelPersist = true;
|
||||
bool wantLocalDomain = false;
|
||||
|
||||
|
||||
bool wantColorRandomizer = false;
|
||||
bool debugVoxelSending = false;
|
||||
bool shouldShowAnimationDebug = false;
|
||||
bool displayVoxelStats = false;
|
||||
|
@ -70,374 +53,14 @@ bool debugVoxelReceiving = false;
|
|||
bool sendEnvironments = true;
|
||||
bool sendMinimalEnvironment = false;
|
||||
bool dumpVoxelsOnMove = false;
|
||||
|
||||
EnvironmentData environmentData[3];
|
||||
|
||||
int receivedPacketCount = 0;
|
||||
JurisdictionMap* jurisdiction = NULL;
|
||||
JurisdictionSender* jurisdictionSender = NULL;
|
||||
|
||||
void randomlyFillVoxelTree(int levelsToGo, VoxelNode *currentRootNode) {
|
||||
// randomly generate children for this node
|
||||
// the first level of the tree (where levelsToGo = MAX_VOXEL_TREE_DEPTH_LEVELS) has all 8
|
||||
if (levelsToGo > 0) {
|
||||
|
||||
bool createdChildren = false;
|
||||
createdChildren = false;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (true) {
|
||||
// create a new VoxelNode to put here
|
||||
currentRootNode->addChildAtIndex(i);
|
||||
randomlyFillVoxelTree(levelsToGo - 1, currentRootNode->getChildAtIndex(i));
|
||||
createdChildren = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!createdChildren) {
|
||||
// we didn't create any children for this node, making it a leaf
|
||||
// give it a random color
|
||||
currentRootNode->setRandomColor(MIN_BRIGHTNESS);
|
||||
} else {
|
||||
// set the color value for this node
|
||||
currentRootNode->setColorFromAverageOfChildren();
|
||||
}
|
||||
} else {
|
||||
// this is a leaf node, just give it a color
|
||||
currentRootNode->setRandomColor(MIN_BRIGHTNESS);
|
||||
}
|
||||
}
|
||||
|
||||
void eraseVoxelTreeAndCleanupNodeVisitData() {
|
||||
|
||||
// As our tree to erase all it's voxels
|
||||
::serverTree.eraseAllVoxels();
|
||||
// enumerate the nodes clean up their marker nodes
|
||||
for (NodeList::iterator node = NodeList::getInstance()->begin(); node != NodeList::getInstance()->end(); node++) {
|
||||
VoxelNodeData* nodeData = (VoxelNodeData*) node->getLinkedData();
|
||||
if (nodeData) {
|
||||
// clean up the node visit data
|
||||
nodeData->nodeBag.deleteAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
uint64_t lastPersistVoxels = 0;
|
||||
void persistVoxelsWhenDirty() {
|
||||
uint64_t now = usecTimestampNow();
|
||||
if (::lastPersistVoxels == 0) {
|
||||
::lastPersistVoxels = now;
|
||||
}
|
||||
int sinceLastTime = (now - ::lastPersistVoxels) / 1000;
|
||||
|
||||
// check the dirty bit and persist here...
|
||||
if (::wantVoxelPersist && ::serverTree.isDirty() && sinceLastTime > VOXEL_PERSIST_INTERVAL) {
|
||||
{
|
||||
PerformanceWarning warn(::shouldShowAnimationDebug,
|
||||
"persistVoxelsWhenDirty() - writeToSVOFile()", ::shouldShowAnimationDebug);
|
||||
|
||||
printf("saving voxels to file...\n");
|
||||
serverTree.writeToSVOFile(::voxelPersistFilename);
|
||||
serverTree.clearDirtyBit(); // tree is clean after saving
|
||||
printf("DONE saving voxels to file...\n");
|
||||
}
|
||||
::lastPersistVoxels = usecTimestampNow();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -509,10 +132,10 @@ int main(int argc, const char * argv[]) {
|
|||
|
||||
NodeList* nodeList = NodeList::createInstance(NODE_TYPE_VOXEL_SERVER, listenPort);
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
|
||||
const NODE_TYPE nodeTypes[] = { NODE_TYPE_AGENT, NODE_TYPE_ANIMATION_SERVER };
|
||||
NodeList::getInstance()->setNodeTypesOfInterest(&nodeTypes[0], sizeof(nodeTypes));
|
||||
|
||||
// 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";
|
||||
::wantLocalDomain = cmdOptionExists(argc, argv,local);
|
||||
|
@ -547,10 +170,6 @@ int main(int argc, const char * argv[]) {
|
|||
::shouldShowAnimationDebug = cmdOptionExists(argc, argv, WANT_ANIMATION_DEBUG);
|
||||
printf("shouldShowAnimationDebug=%s\n", debug::valueOf(::shouldShowAnimationDebug));
|
||||
|
||||
const char* WANT_COLOR_RANDOMIZER = "--wantColorRandomizer";
|
||||
::wantColorRandomizer = cmdOptionExists(argc, argv, WANT_COLOR_RANDOMIZER);
|
||||
printf("wantColorRandomizer=%s\n", debug::valueOf(::wantColorRandomizer));
|
||||
|
||||
// By default we will voxel persist, if you want to disable this, then pass in this parameter
|
||||
const char* NO_VOXEL_PERSIST = "--NoVoxelPersist";
|
||||
if (cmdOptionExists(argc, argv, NO_VOXEL_PERSIST)) {
|
||||
|
@ -558,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) {
|
||||
|
||||
|
@ -589,6 +208,12 @@ int main(int argc, const char * argv[]) {
|
|||
unsigned long internalNodeCount = ::serverTree.rootNode->getSubTreeInternalNodeCount();
|
||||
unsigned long leafNodeCount = ::serverTree.rootNode->getSubTreeLeafNodeCount();
|
||||
printf("Nodes after loading scene %lu nodes %lu internal %lu leaves\n", nodeCount, internalNodeCount, leafNodeCount);
|
||||
|
||||
// now set up VoxelPersistThread
|
||||
::voxelPersistThread = new VoxelPersistThread(&::serverTree, ::voxelPersistFilename);
|
||||
if (::voxelPersistThread) {
|
||||
::voxelPersistThread->initialize(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if the user passed in a command line option for loading an old style local
|
||||
|
@ -610,32 +235,6 @@ int main(int argc, const char * argv[]) {
|
|||
printf("packetsPerSecond=%s PACKETS_PER_CLIENT_PER_INTERVAL=%d\n", packetsPerSecond, PACKETS_PER_CLIENT_PER_INTERVAL);
|
||||
}
|
||||
|
||||
const char* ADD_RANDOM_VOXELS = "--AddRandomVoxels";
|
||||
if (cmdOptionExists(argc, argv, ADD_RANDOM_VOXELS)) {
|
||||
// create an octal code buffer and load it with 0 so that the recursive tree fill can give
|
||||
// octal codes to the tree nodes that it is creating
|
||||
randomlyFillVoxelTree(MAX_VOXEL_TREE_DEPTH_LEVELS, serverTree.rootNode);
|
||||
}
|
||||
|
||||
const char* ADD_SCENE = "--AddScene";
|
||||
bool addScene = cmdOptionExists(argc, argv, ADD_SCENE);
|
||||
const char* NO_ADD_SCENE = "--NoAddScene";
|
||||
bool noAddScene = cmdOptionExists(argc, argv, NO_ADD_SCENE);
|
||||
if (addScene && noAddScene) {
|
||||
printf("WARNING! --AddScene and --NoAddScene are mutually exclusive. We will honor --NoAddScene\n");
|
||||
}
|
||||
|
||||
// We will add a scene if...
|
||||
// 1) we attempted to load a persistant file and it wasn't there
|
||||
// 2) you asked us to add a scene
|
||||
// HOWEVER -- we will NEVER add a scene if you explicitly tell us not to!
|
||||
//
|
||||
// TEMPORARILY DISABLED!!!
|
||||
bool actuallyAddScene = false; // !noAddScene && (addScene || (::wantVoxelPersist && !persistantFileRead));
|
||||
if (actuallyAddScene) {
|
||||
addSphereScene(&serverTree);
|
||||
}
|
||||
|
||||
// for now, initialize the environments with fixed values
|
||||
environmentData[1].setID(1);
|
||||
environmentData[1].setGravity(1.0f);
|
||||
|
@ -648,14 +247,11 @@ 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 nodePublicAddress;
|
||||
sockaddr senderAddress;
|
||||
|
||||
unsigned char *packetData = new unsigned char[MAX_PACKET_SIZE];
|
||||
ssize_t receivedBytes;
|
||||
ssize_t packetLength;
|
||||
|
||||
timeval lastDomainServerCheckIn = {};
|
||||
|
||||
|
@ -664,6 +260,12 @@ int main(int argc, const char * argv[]) {
|
|||
if (::jurisdictionSender) {
|
||||
::jurisdictionSender->initialize(true);
|
||||
}
|
||||
|
||||
// set up our VoxelServerPacketProcessor
|
||||
::voxelServerPacketProcessor = new VoxelServerPacketProcessor();
|
||||
if (::voxelServerPacketProcessor) {
|
||||
::voxelServerPacketProcessor->initialize(true);
|
||||
}
|
||||
|
||||
// loop to send to nodes requesting data
|
||||
while (true) {
|
||||
|
@ -674,173 +276,63 @@ int main(int argc, const char * argv[]) {
|
|||
NodeList::getInstance()->sendDomainServerCheckIn();
|
||||
}
|
||||
|
||||
// check to see if we need to persist our voxel state
|
||||
persistVoxelsWhenDirty();
|
||||
|
||||
if (nodeList->getNodeSocket()->receive(&nodePublicAddress, packetData, &receivedBytes) &&
|
||||
if (nodeList->getNodeSocket()->receive(&senderAddress, packetData, &packetLength) &&
|
||||
packetVersionMatch(packetData)) {
|
||||
|
||||
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(packetData);
|
||||
|
||||
if (packetData[0] == PACKET_TYPE_SET_VOXEL || packetData[0] == PACKET_TYPE_SET_VOXEL_DESTRUCTIVE) {
|
||||
bool destructive = (packetData[0] == PACKET_TYPE_SET_VOXEL_DESTRUCTIVE);
|
||||
PerformanceWarning warn(::shouldShowAnimationDebug,
|
||||
destructive ? "PACKET_TYPE_SET_VOXEL_DESTRUCTIVE" : "PACKET_TYPE_SET_VOXEL",
|
||||
::shouldShowAnimationDebug);
|
||||
|
||||
::receivedPacketCount++;
|
||||
|
||||
unsigned short int itemNumber = (*((unsigned short int*)(packetData + numBytesPacketHeader)));
|
||||
if (::shouldShowAnimationDebug) {
|
||||
printf("got %s - command from client receivedBytes=%ld itemNumber=%d\n",
|
||||
destructive ? "PACKET_TYPE_SET_VOXEL_DESTRUCTIVE" : "PACKET_TYPE_SET_VOXEL",
|
||||
receivedBytes,itemNumber);
|
||||
}
|
||||
|
||||
if (::debugVoxelReceiving) {
|
||||
printf("got %s - %d command from client receivedBytes=%ld itemNumber=%d\n",
|
||||
destructive ? "PACKET_TYPE_SET_VOXEL_DESTRUCTIVE" : "PACKET_TYPE_SET_VOXEL",
|
||||
::receivedPacketCount, receivedBytes,itemNumber);
|
||||
}
|
||||
int atByte = numBytesPacketHeader + sizeof(itemNumber);
|
||||
unsigned char* voxelData = (unsigned char*)&packetData[atByte];
|
||||
while (atByte < receivedBytes) {
|
||||
unsigned char octets = (unsigned char)*voxelData;
|
||||
const int COLOR_SIZE_IN_BYTES = 3;
|
||||
int voxelDataSize = bytesRequiredForCodeLength(octets) + COLOR_SIZE_IN_BYTES;
|
||||
int voxelCodeSize = bytesRequiredForCodeLength(octets);
|
||||
|
||||
// color randomization on insert
|
||||
int colorRandomizer = ::wantColorRandomizer ? randIntInRange (-50, 50) : 0;
|
||||
int red = voxelData[voxelCodeSize + 0];
|
||||
int green = voxelData[voxelCodeSize + 1];
|
||||
int blue = voxelData[voxelCodeSize + 2];
|
||||
|
||||
if (::shouldShowAnimationDebug) {
|
||||
printf("insert voxels - wantColorRandomizer=%s old r=%d,g=%d,b=%d \n",
|
||||
(::wantColorRandomizer?"yes":"no"),red,green,blue);
|
||||
}
|
||||
|
||||
red = std::max(0, std::min(255, red + colorRandomizer));
|
||||
green = std::max(0, std::min(255, green + colorRandomizer));
|
||||
blue = std::max(0, std::min(255, blue + colorRandomizer));
|
||||
|
||||
if (::shouldShowAnimationDebug) {
|
||||
printf("insert voxels - wantColorRandomizer=%s NEW r=%d,g=%d,b=%d \n",
|
||||
(::wantColorRandomizer?"yes":"no"),red,green,blue);
|
||||
}
|
||||
voxelData[voxelCodeSize + 0] = red;
|
||||
voxelData[voxelCodeSize + 1] = green;
|
||||
voxelData[voxelCodeSize + 2] = blue;
|
||||
|
||||
if (::shouldShowAnimationDebug) {
|
||||
float* vertices = firstVertexForCode(voxelData);
|
||||
printf("inserting voxel at: %f,%f,%f\n", vertices[0], vertices[1], vertices[2]);
|
||||
delete []vertices;
|
||||
}
|
||||
|
||||
serverTree.readCodeColorBufferToTree(voxelData, destructive);
|
||||
// skip to next
|
||||
voxelData += voxelDataSize;
|
||||
atByte += voxelDataSize;
|
||||
}
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
Node* node = NodeList::getInstance()->nodeWithAddress(&nodePublicAddress);
|
||||
if (node) {
|
||||
node->setLastHeardMicrostamp(usecTimestampNow());
|
||||
}
|
||||
|
||||
} else if (packetData[0] == PACKET_TYPE_ERASE_VOXEL) {
|
||||
|
||||
// Send these bits off to the VoxelTree class to process them
|
||||
pthread_mutex_lock(&::treeLock);
|
||||
serverTree.processRemoveVoxelBitstream((unsigned char*)packetData, receivedBytes);
|
||||
pthread_mutex_unlock(&::treeLock);
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
Node* node = NodeList::getInstance()->nodeWithAddress(&nodePublicAddress);
|
||||
if (node) {
|
||||
node->setLastHeardMicrostamp(usecTimestampNow());
|
||||
}
|
||||
} else if (packetData[0] == PACKET_TYPE_Z_COMMAND) {
|
||||
|
||||
// the Z command is a special command that allows the sender to send the voxel server high level semantic
|
||||
// requests, like erase all, or add sphere scene
|
||||
|
||||
char* command = (char*) &packetData[numBytesPacketHeader]; // start of the command
|
||||
int commandLength = strlen(command); // commands are null terminated strings
|
||||
int totalLength = numBytesPacketHeader + commandLength + 1; // 1 for null termination
|
||||
printf("got Z message len(%ld)= %s\n", receivedBytes, command);
|
||||
bool rebroadcast = true; // by default rebroadcast
|
||||
|
||||
while (totalLength <= receivedBytes) {
|
||||
if (strcmp(command, ERASE_ALL_COMMAND) == 0) {
|
||||
printf("got Z message == erase all\n");
|
||||
eraseVoxelTreeAndCleanupNodeVisitData();
|
||||
rebroadcast = false;
|
||||
}
|
||||
if (strcmp(command, ADD_SCENE_COMMAND) == 0) {
|
||||
printf("got Z message == add scene\n");
|
||||
addSphereScene(&serverTree);
|
||||
rebroadcast = false;
|
||||
}
|
||||
if (strcmp(command, TEST_COMMAND) == 0) {
|
||||
printf("got Z message == a message, nothing to do, just report\n");
|
||||
}
|
||||
totalLength += commandLength + 1; // 1 for null termination
|
||||
}
|
||||
|
||||
if (rebroadcast) {
|
||||
// Now send this to the connected nodes so they can also process these messages
|
||||
printf("rebroadcasting Z message to connected nodes... nodeList.broadcastToNodes()\n");
|
||||
nodeList->broadcastToNodes(packetData, receivedBytes, &NODE_TYPE_AGENT, 1);
|
||||
}
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
Node* node = NodeList::getInstance()->nodeWithAddress(&nodePublicAddress);
|
||||
if (node) {
|
||||
node->setLastHeardMicrostamp(usecTimestampNow());
|
||||
}
|
||||
} else if (packetData[0] == PACKET_TYPE_HEAD_DATA) {
|
||||
if (packetData[0] == PACKET_TYPE_HEAD_DATA) {
|
||||
// If we got a PACKET_TYPE_HEAD_DATA, then we're talking to an NODE_TYPE_AVATAR, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
|
||||
uint16_t nodeID = 0;
|
||||
unpackNodeId(packetData + numBytesPacketHeader, &nodeID);
|
||||
Node* node = nodeList->addOrUpdateNode(&nodePublicAddress,
|
||||
&nodePublicAddress,
|
||||
Node* node = NodeList::getInstance()->addOrUpdateNode(&senderAddress,
|
||||
&senderAddress,
|
||||
NODE_TYPE_AGENT,
|
||||
nodeID);
|
||||
|
||||
nodeList->updateNodeWithData(node, packetData, receivedBytes);
|
||||
|
||||
NodeList::getInstance()->updateNodeWithData(node, packetData, packetLength);
|
||||
} else if (packetData[0] == PACKET_TYPE_PING) {
|
||||
// If the packet is a ping, let processNodeData handle it.
|
||||
nodeList->processNodeData(&nodePublicAddress, packetData, receivedBytes);
|
||||
NodeList::getInstance()->processNodeData(&senderAddress, packetData, packetLength);
|
||||
} else if (packetData[0] == PACKET_TYPE_DOMAIN) {
|
||||
nodeList->processNodeData(&nodePublicAddress, packetData, receivedBytes);
|
||||
NodeList::getInstance()->processNodeData(&senderAddress, packetData, packetLength);
|
||||
} else if (packetData[0] == PACKET_TYPE_VOXEL_JURISDICTION_REQUEST) {
|
||||
if (::jurisdictionSender) {
|
||||
jurisdictionSender->queueReceivedPacket(nodePublicAddress, packetData, receivedBytes);
|
||||
::jurisdictionSender->queueReceivedPacket(senderAddress, packetData, packetLength);
|
||||
}
|
||||
} else if (::voxelServerPacketProcessor) {
|
||||
::voxelServerPacketProcessor->queueReceivedPacket(senderAddress, packetData, packetLength);
|
||||
} else {
|
||||
printf("unknown packet ignored... packetData[0]=%c\n", packetData[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pthread_join(sendVoxelThread, NULL);
|
||||
pthread_mutex_destroy(&::treeLock);
|
||||
|
||||
if (::jurisdiction) {
|
||||
delete ::jurisdiction;
|
||||
}
|
||||
|
||||
if (::jurisdictionSender) {
|
||||
jurisdictionSender->terminate();
|
||||
::jurisdictionSender->terminate();
|
||||
delete ::jurisdictionSender;
|
||||
}
|
||||
|
||||
if (::voxelServerPacketProcessor) {
|
||||
::voxelServerPacketProcessor->terminate();
|
||||
delete ::voxelServerPacketProcessor;
|
||||
}
|
||||
|
||||
if (::voxelPersistThread) {
|
||||
::voxelPersistThread->terminate();
|
||||
delete ::voxelPersistThread;
|
||||
}
|
||||
|
||||
// tell our NodeList we're done with notifications
|
||||
nodeList->removeHook(&nodeWatcher);
|
||||
|
||||
pthread_mutex_destroy(&::treeLock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue