This commit is contained in:
atlante45 2013-08-30 15:55:04 -07:00
commit 1bc1998023
36 changed files with 3044 additions and 2350 deletions

111
README.md
View file

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

View file

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

View file

@ -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() {

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View 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

View file

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

View file

@ -15,7 +15,7 @@
#include "ProgramObject.h"
#include "RenderUtil.h"
GlowEffect::GlowEffect() : _renderMode(DIFFUSE_ADD_MODE), _isOddFrame(false) {
GlowEffect::GlowEffect() : _renderMode(DIFFUSE_ADD_MODE), _isOddFrame(false), _intensity(0.0f) {
}
QOpenGLFramebufferObject* GlowEffect::getFreeFramebufferObject() const {
@ -70,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();
}

View file

@ -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__) */

View file

@ -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();
}

View file

@ -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__) */

View file

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

View file

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

View file

@ -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__) */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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__

View file

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

View file

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

View file

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

View 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]);
}
}

View 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__

View file

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