diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index d5da2073fb..b2984b35bb 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -63,12 +63,12 @@ if (APPLE) endif (APPLE) -find_package(Qt4 REQUIRED QtCore QtGui QtOpenGL) +find_package(Qt4 REQUIRED QtCore QtGui QtNetwork QtOpenGL) include(${QT_USE_FILE}) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${QT_QTGUI_INCLUDE_DIR}") # run qt moc on qt-enabled headers -qt4_wrap_cpp(INTERFACE_SRCS src/Application.h) +qt4_wrap_cpp(INTERFACE_SRCS src/Application.h src/AvatarVoxelSystem.h) # create the executable, make it a bundle on OS X add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS}) diff --git a/interface/resources/shaders/SkyFromAtmosphere.frag b/interface/resources/shaders/SkyFromAtmosphere.frag index 37d26ba40a..dbefc8270e 100644 --- a/interface/resources/shaders/SkyFromAtmosphere.frag +++ b/interface/resources/shaders/SkyFromAtmosphere.frag @@ -32,17 +32,75 @@ // Copyright (c) 2004 Sean O'Neil // +uniform vec3 v3CameraPos; // The camera's current position +uniform vec3 v3InvWavelength; // 1 / pow(wavelength, 4) for the red, green, and blue channels +uniform float fInnerRadius; // The inner (planetary) radius +uniform float fKrESun; // Kr * ESun +uniform float fKmESun; // Km * ESun +uniform float fKr4PI; // Kr * 4 * PI +uniform float fKm4PI; // Km * 4 * PI +uniform float fScale; // 1 / (fOuterRadius - fInnerRadius) +uniform float fScaleDepth; // The scale depth (i.e. the altitude at which the atmosphere's average density is found) +uniform float fScaleOverScaleDepth; // fScale / fScaleDepth + +const int nSamples = 2; +const float fSamples = 2.0; + uniform vec3 v3LightPos; uniform float g; uniform float g2; -varying vec3 v3Direction; +varying vec3 position; +float scale(float fCos) +{ + float x = 1.0 - fCos; + return fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); +} void main (void) { + // Get the ray from the camera to the vertex, and its length (which is the far point of the ray passing through the atmosphere) + vec3 v3Pos = position; + vec3 v3Ray = v3Pos - v3CameraPos; + float fFar = length(v3Ray); + v3Ray /= fFar; + + // Calculate the ray's starting position, then calculate its scattering offset + vec3 v3Start = v3CameraPos; + float fHeight = length(v3Start); + float fDepth = exp(fScaleOverScaleDepth * (fInnerRadius - fHeight)); + float fStartAngle = dot(v3Ray, v3Start) / fHeight; + float fStartOffset = fDepth * scale(fStartAngle); + + // Initialize the scattering loop variables + //gl_FrontColor = vec4(0.0, 0.0, 0.0, 0.0); + float fSampleLength = fFar / fSamples; + float fScaledLength = fSampleLength * fScale; + vec3 v3SampleRay = v3Ray * fSampleLength; + vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; + + // Now loop through the sample rays + vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); + for(int i=0; i #include +#include #include +#include #include +#include #include #include +#include #include #include #include +#include #include +#include #include #include +#include #include #include #include @@ -41,6 +48,7 @@ #include #include #include +#include #include "Application.h" #include "InterfaceConfig.h" @@ -52,6 +60,8 @@ using namespace std; +const bool TESTING_AVATAR_TOUCH = false; + // Starfield information static char STAR_FILE[] = "https://s3-us-west-1.amazonaws.com/highfidelity/stars.txt"; static char STAR_CACHE_FILE[] = "cachedStars.txt"; @@ -159,8 +169,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _window->setWindowTitle("Interface"); printLog("Interface Startup:\n"); - _voxels.setViewFrustum(&_viewFrustum); - unsigned int listenPort = AGENT_SOCKET_LISTEN_PORT; const char** constArgv = const_cast(argv); const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); @@ -290,22 +298,22 @@ void Application::paintGL() { if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setTightness (100.0f); - _myCamera.setTargetPosition(_myAvatar.getSpringyHeadPosition()); + _myCamera.setTargetPosition(_myAvatar.getBallPosition(AVATAR_JOINT_HEAD_BASE)); _myCamera.setTargetRotation(_myAvatar.getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); } else if (OculusManager::isConnected()) { _myCamera.setUpShift (0.0f); _myCamera.setDistance (0.0f); _myCamera.setTightness (100.0f); - _myCamera.setTargetPosition(_myAvatar.getHeadPosition()); + _myCamera.setTargetPosition(_myAvatar.getHeadJointPosition()); _myCamera.setTargetRotation(_myAvatar.getHead().getOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { - _myCamera.setTargetPosition(_myAvatar.getSpringyHeadPosition()); + _myCamera.setTargetPosition(_myAvatar.getBallPosition(AVATAR_JOINT_HEAD_BASE)); _myCamera.setTargetRotation(_myAvatar.getHead().getWorldAlignedOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { - _myCamera.setTargetPosition(_myAvatar.getHeadPosition()); + _myCamera.setTargetPosition(_myAvatar.getHeadJointPosition()); _myCamera.setTargetRotation(_myAvatar.getHead().getWorldAlignedOrientation()); } @@ -615,6 +623,12 @@ void Application::keyPressEvent(QKeyEvent* event) { } resizeGL(_glWidget->width(), _glWidget->height()); break; + case Qt::Key_Backspace: + case Qt::Key_Delete: + if (_selectVoxelMode->isChecked()) { + deleteVoxelUnderCursor(); + } + break; default: event->ignore(); @@ -832,7 +846,7 @@ void Application::idle() { _mouseVoxel.z += faceVector.z * _mouseVoxel.s; } } - } else if (_addVoxelMode->isChecked()) { + } else if (_addVoxelMode->isChecked() || _selectVoxelMode->isChecked()) { // place the voxel a fixed distance away float worldMouseVoxelScale = _mouseVoxelScale * TREE_SCALE; glm::vec3 pt = mouseRayOrigin + mouseRayDirection * (2.0f + worldMouseVoxelScale * 0.5f); @@ -846,7 +860,10 @@ void Application::idle() { // red indicates deletion _mouseVoxel.red = 255; _mouseVoxel.green = _mouseVoxel.blue = 0; - + } else if (_selectVoxelMode->isChecked()) { + // yellow indicates deletion + _mouseVoxel.red = _mouseVoxel.green = 255; + _mouseVoxel.blue = 0; } else { // _addVoxelMode->isChecked() || _colorVoxelMode->isChecked() QColor paintColor = _voxelPaintColor->data().value(); _mouseVoxel.red = paintColor.red(); @@ -910,24 +927,29 @@ void Application::idle() { _myAvatar.simulate(deltaTime, NULL); } - if (_myCamera.getMode() != CAMERA_MODE_MIRROR && !OculusManager::isConnected()) { - if (_manualFirstPerson) { - if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON ) { - _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); - _myCamera.setModeShiftRate(1.0f); - } - } else { - - if (_myAvatar.getIsNearInteractingOther()) { - if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) { + if (TESTING_AVATAR_TOUCH) { + if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) { + _myCamera.setMode(CAMERA_MODE_THIRD_PERSON); + _myCamera.setModeShiftRate(1.0f); + } + } else { + if (_myCamera.getMode() != CAMERA_MODE_MIRROR && !OculusManager::isConnected()) { + if (_manualFirstPerson) { + if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON ) { _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); _myCamera.setModeShiftRate(1.0f); } - } - else { - if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) { - _myCamera.setMode(CAMERA_MODE_THIRD_PERSON); - _myCamera.setModeShiftRate(1.0f); + } else { + if (_myAvatar.getIsNearInteractingOther()) { + if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) { + _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); + _myCamera.setModeShiftRate(1.0f); + } + } else { + if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) { + _myCamera.setMode(CAMERA_MODE_THIRD_PERSON); + _myCamera.setModeShiftRate(1.0f); + } } } } @@ -959,6 +981,32 @@ void Application::terminate() { } } +void Application::editPreferences() { + QDialog dialog(_glWidget); + dialog.setWindowTitle("Interface Preferences"); + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); + dialog.setLayout(layout); + + QFormLayout* form = new QFormLayout(); + layout->addLayout(form, 1); + + QLineEdit* avatarURL = new QLineEdit(_settings->value("avatarURL").toString()); + avatarURL->setMinimumWidth(400); + form->addRow("Avatar URL:", avatarURL); + + 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); + + if (dialog.exec() != QDialog::Accepted) { + return; + } + QUrl url(avatarURL->text()); + _settings->setValue("avatarURL", url); + _myAvatar.getVoxels()->loadVoxelsFromURL(url); +} + void Application::pair() { PairingHandler::sendPairRequest(); } @@ -1115,6 +1163,172 @@ void Application::chooseVoxelPaintColor() { _window->activateWindow(); } +<<<<<<< HEAD +======= +const int MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE = 1500; +struct SendVoxelsOperationArgs { + unsigned char* newBaseOctCode; + unsigned char messageBuffer[MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE]; + int bufferInUse; + +}; + +bool Application::sendVoxelsOperation(VoxelNode* node, void* extraData) { + SendVoxelsOperationArgs* args = (SendVoxelsOperationArgs*)extraData; + if (node->isColored()) { + unsigned char* nodeOctalCode = node->getOctalCode(); + + unsigned char* codeColorBuffer = NULL; + int codeLength = 0; + int bytesInCode = 0; + int codeAndColorLength; + + // If the newBase is NULL, then don't rebase + if (args->newBaseOctCode) { + codeColorBuffer = rebaseOctalCode(nodeOctalCode, args->newBaseOctCode, true); + codeLength = numberOfThreeBitSectionsInCode(codeColorBuffer); + bytesInCode = bytesRequiredForCodeLength(codeLength); + codeAndColorLength = bytesInCode + SIZE_OF_COLOR_DATA; + } else { + codeLength = numberOfThreeBitSectionsInCode(nodeOctalCode); + bytesInCode = bytesRequiredForCodeLength(codeLength); + codeAndColorLength = bytesInCode + SIZE_OF_COLOR_DATA; + codeColorBuffer = new unsigned char[codeAndColorLength]; + memcpy(codeColorBuffer, nodeOctalCode, bytesInCode); + } + + // copy the colors over + codeColorBuffer[bytesInCode + RED_INDEX ] = node->getColor()[RED_INDEX ]; + codeColorBuffer[bytesInCode + GREEN_INDEX] = node->getColor()[GREEN_INDEX]; + codeColorBuffer[bytesInCode + BLUE_INDEX ] = node->getColor()[BLUE_INDEX ]; + + // if we have room don't have room in the buffer, then send the previously generated message first + if (args->bufferInUse + codeAndColorLength > MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE) { + AgentList::getInstance()->broadcastToAgents(args->messageBuffer, args->bufferInUse, &AGENT_TYPE_VOXEL, 1); + args->bufferInUse = sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int); // reset + } + + // copy this node's code color details into our buffer. + memcpy(&args->messageBuffer[args->bufferInUse], codeColorBuffer, codeAndColorLength); + args->bufferInUse += codeAndColorLength; + } + return true; // keep going +} + +void Application::exportVoxels() { + QString desktopLocation = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation); + QString suggestedName = desktopLocation.append("/voxels.svo"); + + QString fileNameString = QFileDialog::getSaveFileName(_glWidget, tr("Export Voxels"), suggestedName, + tr("Sparse Voxel Octree Files (*.svo)")); + QByteArray fileNameAscii = fileNameString.toAscii(); + const char* fileName = fileNameAscii.data(); + VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + if (selectedNode) { + VoxelTree exportTree; + _voxels.copySubTreeIntoNewTree(selectedNode, &exportTree, true); + exportTree.writeToSVOFile(fileName); + } + + // restore the main window's active state + _window->activateWindow(); +} + +void Application::importVoxels() { + QString desktopLocation = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation); + QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Import Voxels"), desktopLocation, + tr("Sparse Voxel Octree Files (*.svo)")); + QByteArray fileNameAscii = fileNameString.toAscii(); + const char* fileName = fileNameAscii.data(); + + // Read the file into a tree + VoxelTree importVoxels; + importVoxels.readFromSVOFile(fileName); + + VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + + // Recurse the Import Voxels tree, where everything is root relative, and send all the colored voxels to + // the server as an set voxel message, this will also rebase the voxels to the new location + unsigned char* calculatedOctCode = NULL; + SendVoxelsOperationArgs args; + args.messageBuffer[0] = PACKET_HEADER_SET_VOXEL_DESTRUCTIVE; + unsigned short int* sequenceAt = (unsigned short int*)&args.messageBuffer[sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE)]; + *sequenceAt = 0; + args.bufferInUse = sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int); // set to command + sequence + + // we only need the selected voxel to get the newBaseOctCode, which we can actually calculate from the + // voxel size/position details. + if (selectedNode) { + args.newBaseOctCode = selectedNode->getOctalCode(); + } else { + args.newBaseOctCode = calculatedOctCode = pointToVoxel(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + } + + importVoxels.recurseTreeWithOperation(sendVoxelsOperation, &args); + + // If we have voxels left in the packet, then send the packet + if (args.bufferInUse > (sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int))) { + AgentList::getInstance()->broadcastToAgents(args.messageBuffer, args.bufferInUse, &AGENT_TYPE_VOXEL, 1); + } + + if (calculatedOctCode) { + delete calculatedOctCode; + } + + // restore the main window's active state + _window->activateWindow(); +} + +void Application::cutVoxels() { + copyVoxels(); + deleteVoxelUnderCursor(); +} + +void Application::copyVoxels() { + VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + if (selectedNode) { + // clear the clipboard first... + _clipboardTree.eraseAllVoxels(); + + // then copy onto it + _voxels.copySubTreeIntoNewTree(selectedNode, &_clipboardTree, true); + } +} + +void Application::pasteVoxels() { + unsigned char* calculatedOctCode = NULL; + VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + + // Recurse the clipboard tree, where everything is root relative, and send all the colored voxels to + // the server as an set voxel message, this will also rebase the voxels to the new location + SendVoxelsOperationArgs args; + args.messageBuffer[0] = PACKET_HEADER_SET_VOXEL_DESTRUCTIVE; + unsigned short int* sequenceAt = (unsigned short int*)&args.messageBuffer[sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE)]; + *sequenceAt = 0; + args.bufferInUse = sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int); // set to command + sequence + + // we only need the selected voxel to get the newBaseOctCode, which we can actually calculate from the + // voxel size/position details. If we don't have an actual selectedNode then use the mouseVoxel to create a + // target octalCode for where the user is pointing. + if (selectedNode) { + args.newBaseOctCode = selectedNode->getOctalCode(); + } else { + args.newBaseOctCode = calculatedOctCode = pointToVoxel(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + } + + _clipboardTree.recurseTreeWithOperation(sendVoxelsOperation, &args); + + // If we have voxels left in the packet, then send the packet + if (args.bufferInUse > (sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int))) { + AgentList::getInstance()->broadcastToAgents(args.messageBuffer, args.bufferInUse, &AGENT_TYPE_VOXEL, 1); + } + + if (calculatedOctCode) { + delete calculatedOctCode; + } +} + +>>>>>>> 82c1ee2062577f614cfde096f08adfc9e83e4f0f void Application::initMenu() { QMenuBar* menuBar = new QMenuBar(); _window->setMenuBar(menuBar); @@ -1122,6 +1336,9 @@ void Application::initMenu() { QMenu* fileMenu = menuBar->addMenu("File"); fileMenu->addAction("Quit", this, SLOT(quit()), Qt::CTRL | Qt::Key_Q); + QMenu* editMenu = menuBar->addMenu("Edit"); + editMenu->addAction("Preferences...", this, SLOT(editPreferences())); + QMenu* pairMenu = menuBar->addMenu("Pair"); pairMenu->addAction("Pair", this, SLOT(pair())); @@ -1162,9 +1379,7 @@ void Application::initMenu() { renderMenu->addAction("First Person", this, SLOT(setRenderFirstPerson(bool)), Qt::Key_P)->setCheckable(true); QMenu* toolsMenu = menuBar->addMenu("Tools"); - (_renderStatsOn = toolsMenu->addAction("Stats"))->setCheckable(true); - _renderStatsOn->setShortcut(Qt::Key_Slash); (_logOn = toolsMenu->addAction("Log"))->setCheckable(true); _logOn->setChecked(false); @@ -1173,26 +1388,40 @@ void Application::initMenu() { QMenu* voxelMenu = menuBar->addMenu("Voxels"); _voxelModeActions = new QActionGroup(this); _voxelModeActions->setExclusive(false); // exclusivity implies one is always checked + (_addVoxelMode = voxelMenu->addAction( - "Add Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::Key_1))->setCheckable(true); + "Add Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::CTRL | Qt::Key_A))->setCheckable(true); _voxelModeActions->addAction(_addVoxelMode); (_deleteVoxelMode = voxelMenu->addAction( - "Delete Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::Key_2))->setCheckable(true); + "Delete Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::CTRL | Qt::Key_D))->setCheckable(true); _voxelModeActions->addAction(_deleteVoxelMode); (_colorVoxelMode = voxelMenu->addAction( - "Color Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::Key_3))->setCheckable(true); + "Color Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::CTRL | Qt::Key_B))->setCheckable(true); _voxelModeActions->addAction(_colorVoxelMode); + (_selectVoxelMode = voxelMenu->addAction( + "Select Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::CTRL | Qt::Key_S))->setCheckable(true); + _voxelModeActions->addAction(_selectVoxelMode); + (_eyedropperMode = voxelMenu->addAction( + "Get Color Mode", this, SLOT(updateVoxelModeActions()), Qt::CTRL | Qt::Key_G))->setCheckable(true); + _voxelModeActions->addAction(_eyedropperMode); - voxelMenu->addAction("Place Voxel", this, SLOT(addVoxelInFrontOfAvatar()), Qt::Key_4); - voxelMenu->addAction("Decrease Voxel Size", this, SLOT(decreaseVoxelSize()), Qt::Key_5); - voxelMenu->addAction("Increase Voxel Size", this, SLOT(increaseVoxelSize()), Qt::Key_6); + voxelMenu->addAction("Place New Voxel", this, SLOT(addVoxelInFrontOfAvatar()), Qt::CTRL | Qt::Key_N); + voxelMenu->addAction("Decrease Voxel Size", this, SLOT(decreaseVoxelSize()), QKeySequence::ZoomOut); + voxelMenu->addAction("Increase Voxel Size", this, SLOT(increaseVoxelSize()), QKeySequence::ZoomIn); - _voxelPaintColor = voxelMenu->addAction("Voxel Paint Color", this, SLOT(chooseVoxelPaintColor()), Qt::Key_7); + _voxelPaintColor = voxelMenu->addAction("Voxel Paint Color", this, + SLOT(chooseVoxelPaintColor()), Qt::META | Qt::Key_C); QColor paintColor(128, 128, 128); _voxelPaintColor->setData(paintColor); _voxelPaintColor->setIcon(createSwatchIcon(paintColor)); (_destructiveAddVoxel = voxelMenu->addAction("Create Voxel is Destructive"))->setCheckable(true); + voxelMenu->addAction("Export Voxels", this, SLOT(exportVoxels()), Qt::CTRL | Qt::Key_E); + voxelMenu->addAction("Import Voxels", this, SLOT(importVoxels()), Qt::CTRL | Qt::Key_I); + voxelMenu->addAction("Cut Voxels", this, SLOT(cutVoxels()), Qt::CTRL | Qt::Key_X); + voxelMenu->addAction("Copy Voxels", this, SLOT(copyVoxels()), Qt::CTRL | Qt::Key_C); + voxelMenu->addAction("Paste Voxels", this, SLOT(pasteVoxels()), Qt::CTRL | Qt::Key_V); + QMenu* frustumMenu = menuBar->addMenu("Frustum"); (_frustumOn = frustumMenu->addAction("Display Frustum"))->setCheckable(true); _frustumOn->setShortcut(Qt::SHIFT | Qt::Key_F); @@ -1218,6 +1447,7 @@ void Application::initMenu() { debugMenu->addAction("Wants Res-In", this, SLOT(setWantsResIn(bool)))->setCheckable(true); debugMenu->addAction("Wants Monochrome", this, SLOT(setWantsMonochrome(bool)))->setCheckable(true); debugMenu->addAction("Wants View Delta Sending", this, SLOT(setWantsDelta(bool)))->setCheckable(true); +<<<<<<< HEAD QMenu* settingsMenu = menuBar->addMenu("Settings"); (_settingsAutosave = settingsMenu->addAction("Autosave", this, SLOT(setAutosave(bool))))->setCheckable(true); @@ -1226,6 +1456,11 @@ void Application::initMenu() { settingsMenu->addAction("Save settings", this, SLOT(saveSettings())); settingsMenu->addAction("Import settings", this, SLOT(importSettings())); settingsMenu->addAction("Export settings", this, SLOT(exportSettings())); +======= + + _networkAccessManager = new QNetworkAccessManager(this); + _settings = new QSettings("High Fidelity", "Interface", this); +>>>>>>> 82c1ee2062577f614cfde096f08adfc9e83e4f0f } void Application::updateFrustumRenderModeAction() { @@ -1260,8 +1495,6 @@ void Application::initDisplay() { void Application::init() { _voxels.init(); - _voxels.setViewerAvatar(&_myAvatar); - _voxels.setCamera(&_myCamera); _environment.init(); @@ -1272,11 +1505,14 @@ void Application::init() { _stars.readInput(STAR_FILE, STAR_CACHE_FILE, 0); + _myAvatar.init(); _myAvatar.setPosition(START_LOCATION); _myCamera.setMode(CAMERA_MODE_THIRD_PERSON ); _myCamera.setModeShiftRate(1.0f); _myAvatar.setDisplayingLookatVectors(false); + _myAvatar.getVoxels()->loadVoxelsFromURL(_settings->value("avatarURL").toUrl()); + QCursor::setPos(_headMouseX, _headMouseY); OculusManager::connect(); @@ -1392,7 +1628,7 @@ void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) { if (_cameraFrustum->isChecked()) { position = camera.getPosition(); } else { - position = _myAvatar.getHeadPosition(); + position = _myAvatar.getHeadJointPosition(); } float fov = camera.getFieldOfView(); @@ -1404,17 +1640,6 @@ void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) { glm::vec3 up = rotation * AVATAR_UP; glm::vec3 right = rotation * AVATAR_RIGHT; - /* - printf("position.x=%f, position.y=%f, position.z=%f\n", position.x, position.y, position.z); - printf("yaw=%f, pitch=%f, roll=%f\n", yaw,pitch,roll); - printf("direction.x=%f, direction.y=%f, direction.z=%f\n", direction.x, direction.y, direction.z); - printf("up.x=%f, up.y=%f, up.z=%f\n", up.x, up.y, up.z); - printf("right.x=%f, right.y=%f, right.z=%f\n", right.x, right.y, right.z); - printf("fov=%f\n", fov); - printf("nearClip=%f\n", nearClip); - printf("farClip=%f\n", farClip); - */ - // Set the viewFrustum up with the correct position and orientation of the camera viewFrustum.setPosition(position); viewFrustum.setOrientation(direction,up,right); @@ -1575,7 +1800,7 @@ void Application::displayOculus(Camera& whichCamera) { glPopMatrix(); } - + void Application::displaySide(Camera& whichCamera) { // transform by eye offset @@ -2015,7 +2240,6 @@ void Application::maybeEditVoxelUnderCursor() { //_myAvatar.getPosition() voxelInjector->setBearing(-1 * _myAvatar.getAbsoluteHeadYaw()); voxelInjector->setVolume (16 * pow (_mouseVoxel.s, 2) / .0000001); //255 is max, and also default value - // printf("mousevoxelscale is %f\n", _mouseVoxel.s); /* for (int i = 0; i < 22050; i++) { @@ -2066,6 +2290,8 @@ void Application::maybeEditVoxelUnderCursor() { } } else if (_deleteVoxelMode->isChecked()) { deleteVoxelUnderCursor(); + } else if (_eyedropperMode->isChecked()) { + eyedropperVoxelUnderCursor(); } } @@ -2081,7 +2307,7 @@ void Application::deleteVoxelUnderCursor() { for (int i = 0; i < 5000; i++) { voxelInjector->addSample(10000 * sin((i * 2 * PIE) / (500 * sin((i + 1) / 500.0)))); //FM 3 resonant pulse - // voxelInjector->addSample(20000 * sin((i) /((4 / _mouseVoxel.s) * sin((i)/(20 * _mouseVoxel.s / .001))))); //FM 2 comb filter + //voxelInjector->addSample(20000 * sin((i) /((4 / _mouseVoxel.s) * sin((i)/(20 * _mouseVoxel.s / .001))))); //FM 2 comb filter } AudioInjectionManager::threadInjector(voxelInjector); @@ -2090,6 +2316,20 @@ void Application::deleteVoxelUnderCursor() { _justEditedVoxel = true; } +void Application::eyedropperVoxelUnderCursor() { + VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + if (selectedNode && selectedNode->isColored()) { + QColor selectedColor(selectedNode->getColor()[RED_INDEX], + selectedNode->getColor()[GREEN_INDEX], + selectedNode->getColor()[BLUE_INDEX]); + + if (selectedColor.isValid()) { + _voxelPaintColor->setData(selectedColor); + _voxelPaintColor->setIcon(createSwatchIcon(selectedColor)); + } + } +} + void Application::goHome() { _myAvatar.setPosition(START_LOCATION); } @@ -2143,7 +2383,9 @@ QAction* Application::checkedVoxelModeAction() const { void Application::attachNewHeadToAgent(Agent* newAgent) { if (newAgent->getLinkedData() == NULL) { - newAgent->setLinkedData(new Avatar(newAgent)); + Avatar* newAvatar = new Avatar(newAgent); + newAvatar->init(); + newAgent->setLinkedData(newAvatar); } } diff --git a/interface/src/Application.h b/interface/src/Application.h index b1f1307b35..df30199e25 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -39,6 +39,8 @@ class QGLWidget; class QKeyEvent; class QMainWindow; class QMouseEvent; +class QNetworkAccessManager; +class QSettings; class QWheelEvent; class Agent; @@ -67,10 +69,67 @@ public: Avatar* getAvatar() { return &_myAvatar; } Camera* getCamera() { return &_myCamera; } + ViewFrustum* getViewFrustum() { return &_viewFrustum; } VoxelSystem* getVoxels() { return &_voxels; } QSettings* getSettings() { return &_settings; } Environment* getEnvironment() { return &_environment; } bool shouldEchoAudio() { return _echoAudioMode->isChecked(); } +<<<<<<< HEAD +======= + + QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; } + + /*! + @fn getSettingBool + @brief A function for getting boolean settings from the settings file. + @param settingName The desired setting to get the value for. + @param boolSetting The referenced variable where the setting will be stored. + @param defaultSetting The default setting to assign to boolSetting if this function fails to find the appropriate setting. Defaults to false. + */ + bool getSetting(const char* setting, bool &value, const bool defaultSetting = false) const; + + /*! + @fn getSettingFloat + @brief A function for getting float settings from the settings file. + @param settingName The desired setting to get the value for. + @param floatSetting The referenced variable where the setting will be stored. + @param defaultSetting The default setting to assign to boolSetting if this function fails to find the appropriate setting. Defaults to 0.0f. + */ + bool getSetting(const char* setting, float &value, const float defaultSetting = 0.0f) const; + + /*! + @fn getSettingVec3 + @brief A function for getting boolean settings from the settings file. + @param settingName The desired setting to get the value for. + @param vecSetting The referenced variable where the setting will be stored. + @param defaultSetting The default setting to assign to boolSetting if this function fails to find the appropriate setting. Defaults to <0.0f, 0.0f, 0.0f> + */ + bool getSetting(const char* setting, glm::vec3 &value, const glm::vec3& defaultSetting = glm::vec3(0.0f, 0.0f, 0.0f)) const; + + /*! + @fn setSettingBool + @brief A function for setting boolean setting values when saving the settings file. + @param settingName The desired setting to populate a value for. + @param boolSetting The value to set. + */ + void setSetting(const char* setting, const bool value); + + /*! + @fn setSettingFloat + @brief A function for setting boolean setting values when saving the settings file. + @param settingName The desired setting to populate a value for. + @param floatSetting The value to set. + */ + void setSetting(const char* setting, const float value); + + /*! + @fn setSettingVec3 + @brief A function for setting boolean setting values when saving the settings file. + @param settingName The desired setting to populate a value for. + @param vecSetting The value to set. + */ + void setSetting(const char* setting, const glm::vec3& value); +>>>>>>> 82c1ee2062577f614cfde096f08adfc9e83e4f0f private slots: @@ -78,6 +137,8 @@ private slots: void idle(); void terminate(); + void editPreferences(); + void pair(); void setHead(bool head); @@ -106,6 +167,7 @@ private slots: void decreaseVoxelSize(); void increaseVoxelSize(); void chooseVoxelPaintColor(); +<<<<<<< HEAD void setAutosave(bool wantsAutosave); void loadSettings(QSettings* set = NULL); @@ -113,7 +175,17 @@ private slots: void importSettings(); void exportSettings(); +======= + void exportVoxels(); + void importVoxels(); + void cutVoxels(); + void copyVoxels(); + void pasteVoxels(); + +>>>>>>> 82c1ee2062577f614cfde096f08adfc9e83e4f0f private: + + static bool sendVoxelsOperation(VoxelNode* node, void* extraData); void initMenu(); void updateFrustumRenderModeAction(); @@ -134,7 +206,7 @@ private: void shiftPaintingColor(); void maybeEditVoxelUnderCursor(); void deleteVoxelUnderCursor(); - + void eyedropperVoxelUnderCursor(); void goHome(); void resetSensors(); @@ -176,6 +248,8 @@ private: QAction* _addVoxelMode; // Whether add voxel mode is enabled QAction* _deleteVoxelMode; // Whether delete voxel mode is enabled QAction* _colorVoxelMode; // Whether color voxel mode is enabled + QAction* _selectVoxelMode; // Whether select voxel mode is enabled + QAction* _eyedropperMode; // Whether voxel color eyedropper mode is enabled QAction* _voxelPaintColor; // The color with which to paint voxels QAction* _destructiveAddVoxel; // when doing voxel editing do we want them to be destructive QAction* _frustumOn; // Whether or not to display the debug view frustum @@ -185,6 +259,9 @@ private: QAction* _frustumRenderModeAction; QAction* _settingsAutosave; // Whether settings are saved automatically + QNetworkAccessManager* _networkAccessManager; + QSettings* _settings; + SerialInterface _serialPort; bool _displayLevels; @@ -201,6 +278,8 @@ private: Stars _stars; VoxelSystem _voxels; + VoxelTree _clipboardTree; // if I copy/paste + QByteArray _voxelsFilename; bool _wantToKillLocalVoxels; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 97929a99ca..bf100d733b 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -116,7 +116,7 @@ int audioCallback (const void* inputBuffer, printLog("got output\n"); } - if (inputLeft) { + if (agentList && inputLeft) { // Measure the loudness of the signal from the microphone and store in audio object float loudness = 0; @@ -143,7 +143,7 @@ int audioCallback (const void* inputBuffer, unsigned char *currentPacketPtr = dataPacket + 1; // memcpy the three float positions - memcpy(currentPacketPtr, &interfaceAvatar->getHeadPosition(), sizeof(float) * 3); + memcpy(currentPacketPtr, &interfaceAvatar->getHeadJointPosition(), sizeof(float) * 3); currentPacketPtr += (sizeof(float) * 3); // tell the mixer not to add additional attenuation to our source diff --git a/interface/src/Avatar.cpp b/interface/src/Avatar.cpp index 57f2523248..c4a542d53b 100644 --- a/interface/src/Avatar.cpp +++ b/interface/src/Avatar.cpp @@ -44,7 +44,7 @@ const float HEAD_MAX_YAW = 85; const float HEAD_MIN_YAW = -85; const float PERIPERSONAL_RADIUS = 1.0f; const float AVATAR_BRAKING_STRENGTH = 40.0f; -const float JOINT_TOUCH_RANGE = 0.01f; +const float MOUSE_RAY_TOUCH_RANGE = 0.01f; const float FLOATING_HEIGHT = 0.13f; const bool USING_HEAD_LEAN = false; const float LEAN_SENSITIVITY = 0.15; @@ -55,7 +55,6 @@ const float SKIN_COLOR[] = {1.0, 0.84, 0.66}; const float DARK_SKIN_COLOR[] = {0.9, 0.78, 0.63}; const int NUM_BODY_CONE_SIDES = 9; - bool usingBigSphereCollisionTest = true; float chatMessageScale = 0.0015; @@ -64,7 +63,8 @@ float chatMessageHeight = 0.20; Avatar::Avatar(Agent* owningAgent) : AvatarData(owningAgent), _head(this), - _TEST_bigSphereRadius(0.4f), + _ballSpringsInitialized(false), + _TEST_bigSphereRadius(0.5f), _TEST_bigSpherePosition(5.0f, _TEST_bigSphereRadius, 5.0f), _mousePressed(false), _bodyPitchDelta(0.0f), @@ -87,7 +87,8 @@ Avatar::Avatar(Agent* owningAgent) : _mouseRayDirection(0.0f, 0.0f, 0.0f), _interactingOther(NULL), _cumulativeMouseYaw(0.0f), - _isMouseTurningRight(false) + _isMouseTurningRight(false), + _voxels(this) { // give the pointer to our head to inherited _headData variable from AvatarData @@ -101,10 +102,10 @@ Avatar::Avatar(Agent* owningAgent) : initializeBodyBalls(); - _height = _skeleton.getHeight() + _bodyBall[ AVATAR_JOINT_LEFT_HEEL ].radius + _bodyBall[ AVATAR_JOINT_HEAD_BASE ].radius; + _height = _skeleton.getHeight() + _bodyBall[ BODY_BALL_LEFT_HEEL ].radius + _bodyBall[ BODY_BALL_HEAD_BASE ].radius; _maxArmLength = _skeleton.getArmLength(); - _pelvisStandingHeight = _skeleton.getPelvisStandingHeight() + _bodyBall[ AVATAR_JOINT_LEFT_HEEL ].radius; - _pelvisFloatingHeight = _skeleton.getPelvisFloatingHeight() + _bodyBall[ AVATAR_JOINT_LEFT_HEEL ].radius; + _pelvisStandingHeight = _skeleton.getPelvisStandingHeight() + _bodyBall[ BODY_BALL_LEFT_HEEL ].radius; + _pelvisFloatingHeight = _skeleton.getPelvisFloatingHeight() + _bodyBall[ BODY_BALL_LEFT_HEEL ].radius; _avatarTouch.setReachableRadius(PERIPERSONAL_RADIUS); @@ -118,49 +119,142 @@ Avatar::Avatar(Agent* owningAgent) : void Avatar::initializeBodyBalls() { - for (int b=0; b (1.0f - range)) { _bodyBall[b].touchForce = (dot - (1.0f - range)) / range; @@ -557,11 +674,6 @@ void Avatar::updateHandMovementAndTouching(float deltaTime) { if (agent->getLinkedData() != NULL && agent->getType() == AGENT_TYPE_AVATAR) { Avatar *otherAvatar = (Avatar *)agent->getLinkedData(); - //Test: Show angle between your fwd vector and nearest avatar - //glm::vec3 vectorBetweenUs = otherAvatar->getJointPosition(AVATAR_JOINT_PELVIS) - - // getJointPosition(AVATAR_JOINT_PELVIS); - //printLog("Angle between: %f\n", angleBetween(vectorBetweenUs, getBodyFrontDirection())); - // test whether shoulders are close enough to allow for reaching to touch hands glm::vec3 v(_position - otherAvatar->_position); float distance = glm::length(v); @@ -579,7 +691,7 @@ void Avatar::updateHandMovementAndTouching(float deltaTime) { _avatarTouch.setHasInteractingOther(true); _avatarTouch.setYourBodyPosition(_interactingOther->_position); - _avatarTouch.setYourHandPosition(_interactingOther->_bodyBall[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position); + _avatarTouch.setYourHandPosition(_interactingOther->_bodyBall[ BODY_BALL_RIGHT_FINGERTIPS ].position); _avatarTouch.setYourOrientation (_interactingOther->getOrientation()); _avatarTouch.setYourHandState (_interactingOther->_handState); @@ -650,32 +762,29 @@ void Avatar::updateHandMovementAndTouching(float deltaTime) { } _avatarTouch.setMyHandState(_handState); - _avatarTouch.setMyHandPosition(_bodyBall[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position); + _avatarTouch.setMyHandPosition(_bodyBall[ BODY_BALL_RIGHT_FINGERTIPS ].position); } } void Avatar::updateCollisionWithSphere(glm::vec3 position, float radius, float deltaTime) { float myBodyApproximateBoundingRadius = 1.0f; glm::vec3 vectorFromMyBodyToBigSphere(_position - position); - bool jointCollision = false; float distanceToBigSphere = glm::length(vectorFromMyBodyToBigSphere); if (distanceToBigSphere < myBodyApproximateBoundingRadius + radius) { - for (int b = 0; b < NUM_AVATAR_JOINTS; b++) { - glm::vec3 vectorFromJointToBigSphereCenter(_bodyBall[b].position - position); - float distanceToBigSphereCenter = glm::length(vectorFromJointToBigSphereCenter); + for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) { + glm::vec3 vectorFromBallToBigSphereCenter(_bodyBall[b].position - position); + float distanceToBigSphereCenter = glm::length(vectorFromBallToBigSphereCenter); float combinedRadius = _bodyBall[b].radius + radius; if (distanceToBigSphereCenter < combinedRadius) { - jointCollision = true; if (distanceToBigSphereCenter > 0.0) { - glm::vec3 directionVector = vectorFromJointToBigSphereCenter / distanceToBigSphereCenter; + glm::vec3 directionVector = vectorFromBallToBigSphereCenter / distanceToBigSphereCenter; float penetration = 1.0 - (distanceToBigSphereCenter / combinedRadius); - glm::vec3 collisionForce = vectorFromJointToBigSphereCenter * penetration; - - _bodyBall[b].velocity += collisionForce * 0.0f * deltaTime; - _velocity += collisionForce * 40.0f * deltaTime; + glm::vec3 collisionForce = vectorFromBallToBigSphereCenter * penetration; + + _velocity += collisionForce * 40.0f * deltaTime; _bodyBall[b].position = position + directionVector * combinedRadius; } } @@ -729,7 +838,7 @@ void Avatar::updateAvatarCollisions(float deltaTime) { // Reset detector for nearest avatar _distanceToNearestAvatar = std::numeric_limits::max(); - //loop through all the other avatars for potential interactions... + // loop through all the other avatars for potential interactions... AgentList* agentList = AgentList::getInstance(); for (AgentList::iterator agent = agentList->begin(); agent != agentList->end(); agent++) { if (agent->getLinkedData() != NULL && agent->getType() == AGENT_TYPE_AVATAR) { @@ -739,7 +848,7 @@ void Avatar::updateAvatarCollisions(float deltaTime) { glm::vec3 vectorBetweenBoundingSpheres(_position - otherAvatar->_position); if (glm::length(vectorBetweenBoundingSpheres) < _height * ONE_HALF + otherAvatar->_height * ONE_HALF) { - //apply forces from collision + // apply forces from collision applyCollisionWithOtherAvatar(otherAvatar, deltaTime); } @@ -755,30 +864,30 @@ void Avatar::updateAvatarCollisions(float deltaTime) { } } -//detect collisions with other avatars and respond +// detect collisions with other avatars and respond void Avatar::applyCollisionWithOtherAvatar(Avatar * otherAvatar, float deltaTime) { glm::vec3 bodyPushForce = glm::vec3(0.0f, 0.0f, 0.0f); - // loop through the joints of each avatar to check for every possible collision - for (int b=1; b_bodyBall[o].isCollidable) { - glm::vec3 vectorBetweenJoints(_bodyBall[b].position - otherAvatar->_bodyBall[o].position); - float distanceBetweenJoints = glm::length(vectorBetweenJoints); + glm::vec3 vectorBetweenBalls(_bodyBall[b].position - otherAvatar->_bodyBall[o].position); + float distanceBetweenBalls = glm::length(vectorBetweenBalls); - if (distanceBetweenJoints > 0.0) { // to avoid divide by zero + if (distanceBetweenBalls > 0.0) { // to avoid divide by zero float combinedRadius = _bodyBall[b].radius + otherAvatar->_bodyBall[o].radius; // check for collision - if (distanceBetweenJoints < combinedRadius * COLLISION_RADIUS_SCALAR) { - glm::vec3 directionVector = vectorBetweenJoints / distanceBetweenJoints; + if (distanceBetweenBalls < combinedRadius * COLLISION_RADIUS_SCALAR) { + glm::vec3 directionVector = vectorBetweenBalls / distanceBetweenBalls; // push balls away from each other and apply friction - float penetration = 1.0f - (distanceBetweenJoints / (combinedRadius * COLLISION_RADIUS_SCALAR)); + float penetration = 1.0f - (distanceBetweenBalls / (combinedRadius * COLLISION_RADIUS_SCALAR)); glm::vec3 ballPushForce = directionVector * COLLISION_BALL_FORCE * penetration * deltaTime; bodyPushForce += directionVector * COLLISION_BODY_FORCE * penetration * deltaTime; @@ -793,7 +902,7 @@ void Avatar::applyCollisionWithOtherAvatar(Avatar * otherAvatar, float deltaTime } // b loop } // collidable - //apply force on the whole body + // apply force on the whole body _velocity += bodyPushForce; } @@ -833,7 +942,7 @@ void Avatar::render(bool lookingInMirror) { // render a simple round on the ground projected down from the avatar's position renderDiskShadow(_position, glm::vec3(0.0f, 1.0f, 0.0f), 0.1f, 0.2f); - //render body + // render body renderBody(lookingInMirror); // if this is my avatar, then render my interactions with the other avatar @@ -857,7 +966,7 @@ void Avatar::render(bool lookingInMirror) { } glPushMatrix(); - glm::vec3 chatPosition = _bodyBall[AVATAR_JOINT_HEAD_BASE].position + getBodyUpDirection() * chatMessageHeight; + glm::vec3 chatPosition = _bodyBall[BODY_BALL_HEAD_BASE].position + getBodyUpDirection() * chatMessageHeight; glTranslatef(chatPosition.x, chatPosition.y, chatPosition.z); glm::quat chatRotation = Application::getInstance()->getCamera()->getRotation(); glm::vec3 chatAxis = glm::axis(chatRotation); @@ -893,62 +1002,86 @@ void Avatar::render(bool lookingInMirror) { } void Avatar::resetBodyBalls() { - for (int b = 0; b < NUM_AVATAR_JOINTS; b++) { - _bodyBall[b].position = _skeleton.joint[b].position; + for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) { + + glm::vec3 targetPosition + = _skeleton.joint[_bodyBall[b].parentJoint].position + + _skeleton.joint[_bodyBall[b].parentJoint].rotation * _bodyBall[b].parentOffset; + + _bodyBall[b].position = targetPosition; // put ball on target position _bodyBall[b].velocity = glm::vec3(0.0f, 0.0f, 0.0f); } } void Avatar::updateBodyBalls(float deltaTime) { - // Check for a large repositioning, and re-initialize balls if this has happened + // Check for a large repositioning, and re-initialize balls if this has happened const float BEYOND_BODY_SPRING_RANGE = 2.f; - if (glm::length(_position - _bodyBall[AVATAR_JOINT_PELVIS].position) > BEYOND_BODY_SPRING_RANGE) { + if (glm::length(_position - _bodyBall[BODY_BALL_PELVIS].position) > BEYOND_BODY_SPRING_RANGE) { resetBodyBalls(); } - for (int b = 0; b < NUM_AVATAR_JOINTS; b++) { - glm::vec3 springVector(_bodyBall[b].position); - - if (_skeleton.joint[b].parent == AVATAR_JOINT_NULL) { - springVector -= _position; - } - else { - springVector -= _bodyBall[ _skeleton.joint[b].parent ].position; - } - - float length = glm::length(springVector); - - if (length > 0.0f) { // to avoid divide by zero - glm::vec3 springDirection = springVector / length; - - float force = (length - _skeleton.joint[b].length) * BODY_SPRING_FORCE * deltaTime; - _bodyBall[b].velocity -= springDirection * force; - - if (_skeleton.joint[b].parent != AVATAR_JOINT_NULL) { - _bodyBall[_skeleton.joint[b].parent].velocity += springDirection * force; + glm::quat orientation = getOrientation(); + glm::vec3 jointDirection = orientation * JOINT_DIRECTION; + for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) { + + glm::vec3 springVector; + float length = 0.0f; + if (_ballSpringsInitialized) { + + // apply spring forces + springVector = _bodyBall[b].position; + + if (b == BODY_BALL_PELVIS) { + springVector -= _position; + } else { + springVector -= _bodyBall[_bodyBall[b].parentBall].position; } + + length = glm::length(springVector); + + if (length > 0.0f) { // to avoid divide by zero + glm::vec3 springDirection = springVector / length; + + float force = (length - _skeleton.joint[b].length) * BODY_SPRING_FORCE * deltaTime; + _bodyBall[b].velocity -= springDirection * force; + + if (_bodyBall[b].parentBall != BODY_BALL_NULL) { + _bodyBall[_bodyBall[b].parentBall].velocity += springDirection * force; + } + } } // apply tightness force - (causing ball position to be close to skeleton joint position) - _bodyBall[b].velocity += (_skeleton.joint[b].position - _bodyBall[b].position) * _bodyBall[b].jointTightness * deltaTime; + glm::vec3 targetPosition + = _skeleton.joint[_bodyBall[b].parentJoint].position + + _skeleton.joint[_bodyBall[b].parentJoint].rotation * _bodyBall[b].parentOffset; + + _bodyBall[b].velocity += (targetPosition - _bodyBall[b].position) * _bodyBall[b].jointTightness * deltaTime; // apply decay float decay = 1.0 - BODY_SPRING_DECAY * deltaTime; if (decay > 0.0) { _bodyBall[b].velocity *= decay; - } - else { + } else { _bodyBall[b].velocity = glm::vec3(0.0f, 0.0f, 0.0f); } /* - //apply forces from touch... - if (_skeleton.joint[b].touchForce > 0.0) { - _skeleton.joint[b].springyVelocity += _mouseRayDirection * _skeleton.joint[b].touchForce * 0.7f; + // apply forces from touch... + if (_bodyBall[b].touchForce > 0.0) { + _bodyBall[b].velocity += _mouseRayDirection * _bodyBall[b].touchForce * 0.7f; } */ - //update position by velocity... + // update position by velocity... _bodyBall[b].position += _bodyBall[b].velocity * deltaTime; + + // update rotation + const float SMALL_SPRING_LENGTH = 0.001f; // too-small springs can change direction rapidly + if (_skeleton.joint[b].parent == AVATAR_JOINT_NULL || length < SMALL_SPRING_LENGTH) { + _bodyBall[b].rotation = orientation * _skeleton.joint[_bodyBall[b].parentJoint].absoluteBindPoseRotation; + } else { + _bodyBall[b].rotation = rotationBetween(jointDirection, springVector) * orientation; + } } } @@ -1009,9 +1142,12 @@ void Avatar::renderBody(bool lookingInMirror) { const float RENDER_OPAQUE_BEYOND = 1.0f; // Meters beyond which body is shown opaque const float RENDER_TRANSLUCENT_BEYOND = 0.5f; + // Render the body's voxels + _voxels.render(false); + // Render the body as balls and cones - for (int b = 0; b < NUM_AVATAR_JOINTS; b++) { - float distanceToCamera = glm::length(_cameraPosition - _skeleton.joint[b].position); + for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) { + float distanceToCamera = glm::length(_cameraPosition - _bodyBall[b].position); float alpha = lookingInMirror ? 1.0f : glm::clamp((distanceToCamera - RENDER_TRANSLUCENT_BEYOND) / (RENDER_OPAQUE_BEYOND - RENDER_TRANSLUCENT_BEYOND), 0.f, 1.f); @@ -1021,18 +1157,18 @@ void Avatar::renderBody(bool lookingInMirror) { } // Always render other people, and render myself when beyond threshold distance - if (b == AVATAR_JOINT_HEAD_BASE) { // the head is rendered as a special + if (b == BODY_BALL_HEAD_BASE) { // the head is rendered as a special if (lookingInMirror || _owningAgent || distanceToCamera > RENDER_OPAQUE_BEYOND * 0.5) { _head.render(lookingInMirror, _cameraPosition, alpha); } } else if (_owningAgent || distanceToCamera > RENDER_TRANSLUCENT_BEYOND - || b == AVATAR_JOINT_RIGHT_ELBOW - || b == AVATAR_JOINT_RIGHT_WRIST - || b == AVATAR_JOINT_RIGHT_FINGERTIPS ) { - // Render the sphere at the joint - if (_owningAgent || b == AVATAR_JOINT_RIGHT_ELBOW - || b == AVATAR_JOINT_RIGHT_WRIST - || b == AVATAR_JOINT_RIGHT_FINGERTIPS ) { + || b == BODY_BALL_RIGHT_ELBOW + || b == BODY_BALL_RIGHT_WRIST + || b == BODY_BALL_RIGHT_FINGERTIPS ) { + // Render the body ball sphere + if (_owningAgent || b == BODY_BALL_RIGHT_ELBOW + || b == BODY_BALL_RIGHT_WRIST + || b == BODY_BALL_RIGHT_FINGERTIPS ) { glColor3f(SKIN_COLOR[0] + _bodyBall[b].touchForce * 0.3f, SKIN_COLOR[1] - _bodyBall[b].touchForce * 0.2f, SKIN_COLOR[2] - _bodyBall[b].touchForce * 0.1f); @@ -1043,36 +1179,36 @@ void Avatar::renderBody(bool lookingInMirror) { alpha); } - if ((b != AVATAR_JOINT_HEAD_TOP ) - && (b != AVATAR_JOINT_HEAD_BASE )) { + if ((b != BODY_BALL_HEAD_TOP ) + && (b != BODY_BALL_HEAD_BASE )) { glPushMatrix(); glTranslatef(_bodyBall[b].position.x, _bodyBall[b].position.y, _bodyBall[b].position.z); glutSolidSphere(_bodyBall[b].radius, 20.0f, 20.0f); glPopMatrix(); } - // Render the cone connecting this joint to its parent - if (_skeleton.joint[b].parent != AVATAR_JOINT_NULL) { - if ((b != AVATAR_JOINT_HEAD_TOP ) - && (b != AVATAR_JOINT_HEAD_BASE ) - && (b != AVATAR_JOINT_PELVIS ) - && (b != AVATAR_JOINT_TORSO ) - && (b != AVATAR_JOINT_CHEST ) - && (b != AVATAR_JOINT_LEFT_COLLAR ) - && (b != AVATAR_JOINT_LEFT_SHOULDER ) - && (b != AVATAR_JOINT_RIGHT_COLLAR ) - && (b != AVATAR_JOINT_RIGHT_SHOULDER)) { + // Render the cone connecting this ball to its parent + if (_bodyBall[b].parentBall != BODY_BALL_NULL) { + if ((b != BODY_BALL_HEAD_TOP ) + && (b != BODY_BALL_HEAD_BASE ) + && (b != BODY_BALL_PELVIS ) + && (b != BODY_BALL_TORSO ) + && (b != BODY_BALL_CHEST ) + && (b != BODY_BALL_LEFT_COLLAR ) + && (b != BODY_BALL_LEFT_SHOULDER ) + && (b != BODY_BALL_RIGHT_COLLAR ) + && (b != BODY_BALL_RIGHT_SHOULDER)) { glColor3fv(DARK_SKIN_COLOR); - float r1 = _bodyBall[_skeleton.joint[b].parent ].radius * 0.8; - float r2 = _bodyBall[b ].radius * 0.8; - if (b == AVATAR_JOINT_HEAD_BASE) { + float r1 = _bodyBall[_bodyBall[b].parentBall ].radius * 0.8; + float r2 = _bodyBall[b].radius * 0.8; + if (b == BODY_BALL_HEAD_BASE) { r1 *= 0.5f; } renderJointConnectingCone ( - _bodyBall[_skeleton.joint[b].parent ].position, - _bodyBall[b ].position, r2, r2 + _bodyBall[_bodyBall[b].parentBall].position, + _bodyBall[b].position, r2, r2 ); } } @@ -1110,10 +1246,11 @@ void Avatar::setHeadFromGyros(glm::vec3* eulerAngles, glm::vec3* angularVelocity _head.setYaw (angles.x); _head.setPitch(angles.y); _head.setRoll (angles.z); - //printLog("Y/P/R: %3.1f, %3.1f, %3.1f\n", angles.x, angles.y, angles.z); + // printLog("Y/P/R: %3.1f, %3.1f, %3.1f\n", angles.x, angles.y, angles.z); } } +<<<<<<< HEAD void Avatar::loadData(QSettings* set) { set->beginGroup("Avatar"); @@ -1126,6 +1263,16 @@ void Avatar::loadData(QSettings* set) { _position.z = set->value("position_z", _position.z).toFloat(); set->endGroup(); +======= +void Avatar::getBodyBallTransform(AvatarJointID jointID, glm::vec3& position, glm::quat& rotation) const { + position = _bodyBall[jointID].position; + rotation = _bodyBall[jointID].rotation; +} + +void Avatar::writeAvatarDataToFile() { + Application::getInstance()->setSetting("avatarPos", _position); + Application::getInstance()->setSetting("avatarRotation", glm::vec3(_bodyYaw, _bodyPitch, _bodyRoll)); +>>>>>>> 82c1ee2062577f614cfde096f08adfc9e83e4f0f } void Avatar::saveData(QSettings* set) { diff --git a/interface/src/Avatar.h b/interface/src/Avatar.h index 5ff38215b9..645285eabb 100644 --- a/interface/src/Avatar.h +++ b/interface/src/Avatar.h @@ -14,6 +14,7 @@ #include #include "world.h" #include "AvatarTouch.h" +#include "AvatarVoxelSystem.h" #include "InterfaceConfig.h" #include "SerialInterface.h" #include "Balls.h" @@ -21,6 +22,40 @@ #include "Skeleton.h" #include "Transmitter.h" + +enum AvatarBodyBallID +{ + BODY_BALL_NULL = -1, + BODY_BALL_PELVIS, + BODY_BALL_TORSO, + BODY_BALL_CHEST, + BODY_BALL_NECK_BASE, + BODY_BALL_HEAD_BASE, + BODY_BALL_HEAD_TOP, + BODY_BALL_LEFT_COLLAR, + BODY_BALL_LEFT_SHOULDER, + BODY_BALL_LEFT_ELBOW, + BODY_BALL_LEFT_WRIST, + BODY_BALL_LEFT_FINGERTIPS, + BODY_BALL_RIGHT_COLLAR, + BODY_BALL_RIGHT_SHOULDER, + BODY_BALL_RIGHT_ELBOW, + BODY_BALL_RIGHT_WRIST, + BODY_BALL_RIGHT_FINGERTIPS, + BODY_BALL_LEFT_HIP, + BODY_BALL_LEFT_KNEE, + BODY_BALL_LEFT_HEEL, + BODY_BALL_LEFT_TOES, + BODY_BALL_RIGHT_HIP, + BODY_BALL_RIGHT_KNEE, + BODY_BALL_RIGHT_HEEL, + BODY_BALL_RIGHT_TOES, + +//TEST! +//BODY_BALL_LEFT_MID_THIGH, + NUM_AVATAR_BODY_BALLS +}; + enum DriveKeys { FWD = 0, @@ -47,6 +82,7 @@ public: Avatar(Agent* owningAgent = NULL); ~Avatar(); + void init(); void reset(); void simulate(float deltaTime, Transmitter* transmitter); void updateHeadFromGyros(float frametime, SerialInterface * serialInterface); @@ -65,19 +101,15 @@ public: void setOrientation (const glm::quat& orientation); //getters - - float getHeadYawRate () const { return _head.yawRate;} - float getBodyYaw () const { return _bodyYaw;} - bool getIsNearInteractingOther() const { return _avatarTouch.getAbleToReachOtherAvatar();} - const glm::vec3& getHeadPosition () const { return _skeleton.joint[ AVATAR_JOINT_HEAD_BASE ].position;} - const glm::vec3& getSpringyHeadPosition () const { return _bodyBall[ AVATAR_JOINT_HEAD_BASE ].position;} - const glm::vec3& getJointPosition (AvatarJointID j) const { return _bodyBall[j].position;} - - glm::vec3 getBodyRightDirection () const { return getOrientation() * AVATAR_RIGHT; } - glm::vec3 getBodyUpDirection () const { return getOrientation() * AVATAR_UP; } - glm::vec3 getBodyFrontDirection () const { return getOrientation() * AVATAR_FRONT; } - - + 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() * AVATAR_RIGHT; } + glm::vec3 getBodyUpDirection () const { return getOrientation() * AVATAR_UP; } + glm::vec3 getBodyFrontDirection () const { return getOrientation() * AVATAR_FRONT; } const glm::vec3& getVelocity () const { return _velocity;} float getSpeed () const { return _speed;} float getHeight () const { return _height;} @@ -88,6 +120,8 @@ public: glm::quat getOrientation () const; glm::quat getWorldAlignedOrientation() const; + 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]; }; @@ -96,9 +130,18 @@ public: void addThrust(glm::vec3 newThrust) { _thrust += newThrust; }; glm::vec3 getThrust() { return _thrust; }; +<<<<<<< HEAD // get/set avatar data void saveData(QSettings* set); void loadData(QSettings* set); +======= + // Get the position/rotation of a single body ball + void getBodyBallTransform(AvatarJointID jointID, glm::vec3& position, glm::quat& rotation) const; + + //read/write avatar data + void writeAvatarDataToFile(); + void readAvatarDataFromFile(); +>>>>>>> 82c1ee2062577f614cfde096f08adfc9e83e4f0f private: // privatize copy constructor and assignment operator to avoid copying @@ -107,16 +150,22 @@ private: struct AvatarBall { - glm::vec3 position; - glm::vec3 velocity; - float jointTightness; - float radius; - bool isCollidable; - float touchForce; + 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; Skeleton _skeleton; + bool _ballSpringsInitialized; float _TEST_bigSphereRadius; glm::vec3 _TEST_bigSpherePosition; bool _mousePressed; @@ -124,8 +173,7 @@ private: float _bodyYawDelta; float _bodyRollDelta; glm::vec3 _movedHandOffset; - glm::quat _rotation; // the rotation of the avatar body as a whole expressed as a quaternion - AvatarBall _bodyBall[ NUM_AVATAR_JOINTS ]; + AvatarBall _bodyBall[ NUM_AVATAR_BODY_BALLS ]; AvatarMode _mode; glm::vec3 _cameraPosition; glm::vec3 _handHoldingPosition; @@ -149,6 +197,8 @@ private: float _cumulativeMouseYaw; bool _isMouseTurningRight; + AvatarVoxelSystem _voxels; + // private methods... glm::vec3 caclulateAverageEyePosition() { return _head.caclulateAverageEyePosition(); } // get the position smack-dab between the eyes (for lookat) glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const; diff --git a/interface/src/AvatarVoxelSystem.cpp b/interface/src/AvatarVoxelSystem.cpp new file mode 100644 index 0000000000..2500c9b2d4 --- /dev/null +++ b/interface/src/AvatarVoxelSystem.cpp @@ -0,0 +1,261 @@ +// +// AvatarVoxelSystem.cpp +// interface +// +// Created by Andrzej Kapolka on 5/31/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. + +#include + +#include +#include + +#include + +#include "Application.h" +#include "Avatar.h" +#include "AvatarVoxelSystem.h" +#include "renderer/ProgramObject.h" + +const float AVATAR_TREE_SCALE = 1.0f; +const int MAX_VOXELS_PER_AVATAR = 2000; +const int BONE_ELEMENTS_PER_VOXEL = BONE_ELEMENTS_PER_VERTEX * VERTICES_PER_VOXEL; + +AvatarVoxelSystem::AvatarVoxelSystem(Avatar* avatar) : + VoxelSystem(AVATAR_TREE_SCALE, MAX_VOXELS_PER_AVATAR), + _avatar(avatar), _voxelReply(0) { +} + +AvatarVoxelSystem::~AvatarVoxelSystem() { + delete[] _readBoneIndicesArray; + delete[] _readBoneWeightsArray; + delete[] _writeBoneIndicesArray; + delete[] _writeBoneWeightsArray; +} + +ProgramObject* AvatarVoxelSystem::_skinProgram = 0; +int AvatarVoxelSystem::_boneMatricesLocation; +int AvatarVoxelSystem::_boneIndicesLocation; +int AvatarVoxelSystem::_boneWeightsLocation; + +void AvatarVoxelSystem::init() { + VoxelSystem::init(); + + // prep the data structures for incoming voxel data + _writeBoneIndicesArray = new GLubyte[BONE_ELEMENTS_PER_VOXEL * _maxVoxels]; + _readBoneIndicesArray = new GLubyte[BONE_ELEMENTS_PER_VOXEL * _maxVoxels]; + + _writeBoneWeightsArray = new GLfloat[BONE_ELEMENTS_PER_VOXEL * _maxVoxels]; + _readBoneWeightsArray = new GLfloat[BONE_ELEMENTS_PER_VOXEL * _maxVoxels]; + + // VBO for the boneIndicesArray + glGenBuffers(1, &_vboBoneIndicesID); + glBindBuffer(GL_ARRAY_BUFFER, _vboBoneIndicesID); + glBufferData(GL_ARRAY_BUFFER, BONE_ELEMENTS_PER_VOXEL * sizeof(GLubyte) * _maxVoxels, NULL, GL_DYNAMIC_DRAW); + + // VBO for the boneWeightsArray + glGenBuffers(1, &_vboBoneWeightsID); + glBindBuffer(GL_ARRAY_BUFFER, _vboBoneWeightsID); + glBufferData(GL_ARRAY_BUFFER, BONE_ELEMENTS_PER_VOXEL * sizeof(GLfloat) * _maxVoxels, NULL, GL_DYNAMIC_DRAW); + + // load our skin program if this is the first avatar system to initialize + if (_skinProgram != 0) { + return; + } + _skinProgram = new ProgramObject(); + _skinProgram->addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/skin_voxels.vert"); + _skinProgram->link(); + + _boneMatricesLocation = _skinProgram->uniformLocation("boneMatrices"); + _boneIndicesLocation = _skinProgram->attributeLocation("boneIndices"); + _boneWeightsLocation = _skinProgram->attributeLocation("boneWeights"); +} + +void AvatarVoxelSystem::removeOutOfView() { + // no-op for now +} + +void AvatarVoxelSystem::loadVoxelsFromURL(const QUrl& url) { + // cancel any current download + if (_voxelReply != 0) { + delete _voxelReply; + } + + killLocalVoxels(); + + // load the URL data asynchronously + if (!url.isValid()) { + return; + } + _voxelReply = Application::getInstance()->getNetworkAccessManager()->get(QNetworkRequest(url)); + connect(_voxelReply, SIGNAL(readyRead()), SLOT(readVoxelDataFromReply())); + connect(_voxelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleVoxelReplyError())); +} + +void AvatarVoxelSystem::updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex, + float voxelScale, const nodeColor& color) { + VoxelSystem::updateNodeInArrays(nodeIndex, startVertex, voxelScale, color); + + GLubyte* writeBoneIndicesAt = _writeBoneIndicesArray + (nodeIndex * BONE_ELEMENTS_PER_VOXEL); + GLfloat* writeBoneWeightsAt = _writeBoneWeightsArray + (nodeIndex * BONE_ELEMENTS_PER_VOXEL); + for (int i = 0; i < VERTICES_PER_VOXEL; i++) { + BoneIndices boneIndices; + glm::vec4 boneWeights; + computeBoneIndicesAndWeights(computeVoxelVertex(startVertex, voxelScale, i), boneIndices, boneWeights); + for (int j = 0; j < BONE_ELEMENTS_PER_VERTEX; j++) { + *(writeBoneIndicesAt + i * BONE_ELEMENTS_PER_VERTEX + j) = boneIndices[j]; + *(writeBoneWeightsAt + i * BONE_ELEMENTS_PER_VERTEX + j) = boneWeights[j]; + } + } +} + +void AvatarVoxelSystem::copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd) { + VoxelSystem::copyWrittenDataSegmentToReadArrays(segmentStart, segmentEnd); + + int segmentLength = (segmentEnd - segmentStart) + 1; + GLintptr segmentStartAt = segmentStart * BONE_ELEMENTS_PER_VOXEL * sizeof(GLubyte); + GLsizeiptr segmentSizeBytes = segmentLength * BONE_ELEMENTS_PER_VOXEL * sizeof(GLubyte); + GLubyte* readBoneIndicesAt = _readBoneIndicesArray + (segmentStart * BONE_ELEMENTS_PER_VOXEL); + GLubyte* writeBoneIndicesAt = _writeBoneIndicesArray + (segmentStart * BONE_ELEMENTS_PER_VOXEL); + memcpy(readBoneIndicesAt, writeBoneIndicesAt, segmentSizeBytes); + + segmentStartAt = segmentStart * BONE_ELEMENTS_PER_VOXEL * sizeof(GLfloat); + segmentSizeBytes = segmentLength * BONE_ELEMENTS_PER_VOXEL * sizeof(GLfloat); + GLfloat* readBoneWeightsAt = _readBoneWeightsArray + (segmentStart * BONE_ELEMENTS_PER_VOXEL); + GLfloat* writeBoneWeightsAt = _writeBoneWeightsArray + (segmentStart * BONE_ELEMENTS_PER_VOXEL); + memcpy(readBoneWeightsAt, writeBoneWeightsAt, segmentSizeBytes); +} + +void AvatarVoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) { + VoxelSystem::updateVBOSegment(segmentStart, segmentEnd); + + int segmentLength = (segmentEnd - segmentStart) + 1; + GLintptr segmentStartAt = segmentStart * BONE_ELEMENTS_PER_VOXEL * sizeof(GLubyte); + GLsizeiptr segmentSizeBytes = segmentLength * BONE_ELEMENTS_PER_VOXEL * sizeof(GLubyte); + GLubyte* readBoneIndicesFrom = _readBoneIndicesArray + (segmentStart * BONE_ELEMENTS_PER_VOXEL); + glBindBuffer(GL_ARRAY_BUFFER, _vboBoneIndicesID); + glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readBoneIndicesFrom); + + segmentStartAt = segmentStart * BONE_ELEMENTS_PER_VOXEL * sizeof(GLfloat); + segmentSizeBytes = segmentLength * BONE_ELEMENTS_PER_VOXEL * sizeof(GLfloat); + GLfloat* readBoneWeightsFrom = _readBoneWeightsArray + (segmentStart * BONE_ELEMENTS_PER_VOXEL); + glBindBuffer(GL_ARRAY_BUFFER, _vboBoneWeightsID); + glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readBoneWeightsFrom); +} + +void AvatarVoxelSystem::applyScaleAndBindProgram(bool texture) { + _skinProgram->bind(); + + // the base matrix includes centering and scale + QMatrix4x4 baseMatrix; + baseMatrix.scale(_treeScale); + baseMatrix.translate(-0.5f, -0.5f, -0.5f); + + // bone matrices include joint transforms + QMatrix4x4 boneMatrices[NUM_AVATAR_JOINTS]; + for (int i = 0; i < NUM_AVATAR_JOINTS; i++) { + glm::vec3 position; + glm::quat orientation; + _avatar->getBodyBallTransform((AvatarJointID)i, position, orientation); + boneMatrices[i].translate(position.x, position.y, position.z); + orientation = orientation * glm::inverse(_avatar->getSkeleton().joint[i].absoluteBindPoseRotation); + boneMatrices[i].rotate(QQuaternion(orientation.w, orientation.x, orientation.y, orientation.z)); + const glm::vec3& bindPosition = _avatar->getSkeleton().joint[i].absoluteBindPosePosition; + boneMatrices[i].translate(-bindPosition.x, -bindPosition.y, -bindPosition.z); + boneMatrices[i] *= baseMatrix; + } + _skinProgram->setUniformValueArray(_boneMatricesLocation, boneMatrices, NUM_AVATAR_JOINTS); + + glBindBuffer(GL_ARRAY_BUFFER, _vboBoneIndicesID); + glVertexAttribPointer(_boneIndicesLocation, BONE_ELEMENTS_PER_VERTEX, GL_UNSIGNED_BYTE, false, 0, 0); + _skinProgram->enableAttributeArray(_boneIndicesLocation); + + glBindBuffer(GL_ARRAY_BUFFER, _vboBoneWeightsID); + _skinProgram->setAttributeBuffer(_boneWeightsLocation, GL_FLOAT, 0, BONE_ELEMENTS_PER_VERTEX); + _skinProgram->enableAttributeArray(_boneWeightsLocation); +} + +void AvatarVoxelSystem::removeScaleAndReleaseProgram(bool texture) { + _skinProgram->release(); + _skinProgram->disableAttributeArray(_boneIndicesLocation); + _skinProgram->disableAttributeArray(_boneWeightsLocation); +} + +void AvatarVoxelSystem::readVoxelDataFromReply() { + // for now, just wait until we have the full business + if (!_voxelReply->isFinished()) { + return; + } + QByteArray entirety = _voxelReply->readAll(); + _voxelReply->deleteLater(); + _voxelReply = 0; + _tree->readBitstreamToTree((unsigned char*)entirety.data(), entirety.size(), WANT_COLOR, NO_EXISTS_BITS); + setupNewVoxelsForDrawing(); +} + +void AvatarVoxelSystem::handleVoxelReplyError() { + printLog("%s\n", _voxelReply->errorString().toAscii().constData()); + + _voxelReply->deleteLater(); + _voxelReply = 0; +} + +class IndexDistance { +public: + IndexDistance(GLubyte index = AVATAR_JOINT_PELVIS, float distance = FLT_MAX) : index(index), distance(distance) { } + + GLubyte index; + float distance; +}; + +void AvatarVoxelSystem::computeBoneIndicesAndWeights(const glm::vec3& vertex, BoneIndices& indices, glm::vec4& weights) const { + // transform into joint space + glm::vec3 jointVertex = (vertex - glm::vec3(0.5f, 0.5f, 0.5f)) * AVATAR_TREE_SCALE; + + // find the nearest four joints (TODO: use a better data structure for the pose positions to speed this up) + IndexDistance nearest[BONE_ELEMENTS_PER_VERTEX]; + const Skeleton& skeleton = _avatar->getSkeleton(); + for (int i = 0; i < NUM_AVATAR_JOINTS; i++) { + AvatarJointID parent = skeleton.joint[i].parent; + float distance = glm::length(computeVectorFromPointToSegment(jointVertex, + skeleton.joint[parent == AVATAR_JOINT_NULL ? i : parent].absoluteBindPosePosition, + skeleton.joint[i].absoluteBindPosePosition)); + if (distance > skeleton.joint[i].bindRadius) { + continue; + } + for (int j = 0; j < BONE_ELEMENTS_PER_VERTEX; j++) { + if (distance < nearest[j].distance) { + // move the rest of the indices down + for (int k = BONE_ELEMENTS_PER_VERTEX - 1; k > j; k--) { + nearest[k] = nearest[k - 1]; + } + nearest[j] = IndexDistance(i, distance); + break; + } + } + } + + // compute the weights based on inverse distance + float totalWeight = 0.0f; + for (int i = 0; i < BONE_ELEMENTS_PER_VERTEX; i++) { + indices[i] = nearest[i].index; + if (nearest[i].distance != FLT_MAX) { + weights[i] = 1.0f / glm::max(nearest[i].distance, EPSILON); + totalWeight += weights[i]; + + } else { + weights[i] = 0.0f; + } + } + + // if it's not attached to anything, consider it attached to the hip + if (totalWeight == 0.0f) { + weights[0] = 1.0f; + return; + } + + // ortherwise, normalize the weights + for (int i = 0; i < BONE_ELEMENTS_PER_VERTEX; i++) { + weights[i] /= totalWeight; + } +} diff --git a/interface/src/AvatarVoxelSystem.h b/interface/src/AvatarVoxelSystem.h new file mode 100644 index 0000000000..edeeadb17c --- /dev/null +++ b/interface/src/AvatarVoxelSystem.h @@ -0,0 +1,74 @@ +// +// AvatarVoxelSystem.h +// interface +// +// Created by Andrzej Kapolka on 5/31/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__AvatarVoxelSystem__ +#define __interface__AvatarVoxelSystem__ + +#include + +#include "VoxelSystem.h" + +const int BONE_ELEMENTS_PER_VERTEX = 4; +typedef GLubyte BoneIndices[BONE_ELEMENTS_PER_VERTEX]; + +class QNetworkReply; +class QUrl; + +class Avatar; + +class AvatarVoxelSystem : public QObject, public VoxelSystem { + Q_OBJECT + +public: + + AvatarVoxelSystem(Avatar* avatar); + virtual ~AvatarVoxelSystem(); + + virtual void init(); + + virtual void removeOutOfView(); + + void loadVoxelsFromURL(const QUrl& url); + +protected: + + virtual void updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex, + float voxelScale, const nodeColor& color); + virtual void copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd); + virtual void updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd); + virtual void applyScaleAndBindProgram(bool texture); + virtual void removeScaleAndReleaseProgram(bool texture); + +private slots: + + void readVoxelDataFromReply(); + void handleVoxelReplyError(); + +private: + + void computeBoneIndicesAndWeights(const glm::vec3& vertex, BoneIndices& indices, glm::vec4& weights) const; + + Avatar* _avatar; + + GLubyte* _readBoneIndicesArray; + GLfloat* _readBoneWeightsArray; + GLubyte* _writeBoneIndicesArray; + GLfloat* _writeBoneWeightsArray; + + GLuint _vboBoneIndicesID; + GLuint _vboBoneWeightsID; + + QNetworkReply* _voxelReply; + + static ProgramObject* _skinProgram; + static int _boneMatricesLocation; + static int _boneIndicesLocation; + static int _boneWeightsLocation; +}; + +#endif /* defined(__interface__AvatarVoxelSystem__) */ diff --git a/interface/src/Skeleton.cpp b/interface/src/Skeleton.cpp index 64a8645247..b3720c8869 100644 --- a/interface/src/Skeleton.cpp +++ b/interface/src/Skeleton.cpp @@ -5,6 +5,7 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #include "Skeleton.h" +#include "Util.h" const float BODY_SPRING_DEFAULT_TIGHTNESS = 1000.0f; const float FLOATING_HEIGHT = 0.13f; @@ -14,12 +15,13 @@ Skeleton::Skeleton() { void Skeleton::initialize() { - for (int b=0; b 179.99f) { // 180 degree rotation; must use another axis + axis = glm::cross(v1, glm::vec3(1.0f, 0.0f, 0.0f)); + float axisLength = glm::length(axis); + if (axisLength < EPSILON) { // parallel to x; y will work + axis = glm::normalize(glm::cross(v1, glm::vec3(0.0f, 1.0f, 0.0f))); + } else { + axis /= axisLength; + } + } else { + axis = glm::normalize(glm::cross(v1, v2)); + } + return glm::angleAxis(angle, axis); +} + // Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's // http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde, // https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) diff --git a/interface/src/Util.h b/interface/src/Util.h index 0823ac405b..0187d93da5 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -45,6 +45,8 @@ void drawVector(glm::vec3* vector); float angleBetween(const glm::vec3& v1, const glm::vec3& v2); +glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2); + glm::vec3 safeEulerAngles(const glm::quat& q); glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha); diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 5e1b72e5c4..7e179c0919 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -19,6 +19,7 @@ #include #include #include +#include "Application.h" #include "Log.h" #include "VoxelConstants.h" #include "InterfaceConfig.h" @@ -37,14 +38,15 @@ GLfloat identityNormals[] = { 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, -1,0,0, +1,0,0, +1,0,0, -1,0,0, -1,0,0, +1,0,0, +1,0,0, -1,0,0 }; -GLubyte identityIndices[] = { 0,2,1, 0,3,2, // Z- . +GLubyte identityIndices[] = { 0,2,1, 0,3,2, // Z- 8,9,13, 8,13,12, // Y- 16,23,19, 16,20,23, // X- 17,18,22, 17,22,21, // X+ 10,11,15, 10,15,14, // Y+ - 4,5,6, 4,6,7 }; // Z+ . + 4,5,6, 4,6,7 }; // Z+ -VoxelSystem::VoxelSystem() : AgentData(NULL) { +VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) : + AgentData(NULL), _treeScale(treeScale), _maxVoxels(maxVoxels) { _voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0; _writeRenderFullVBO = true; _readRenderFullVBO = true; @@ -70,6 +72,18 @@ void VoxelSystem::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) setupNewVoxelsForDrawing(); } +void VoxelSystem::writeToSVOFile(const char* filename, VoxelNode* node) const { + _tree->writeToSVOFile(filename, node); +} + +bool VoxelSystem::readFromSVOFile(const char* filename) { + bool result = _tree->readFromSVOFile(filename); + if (result) { + setupNewVoxelsForDrawing(); + } + return result; +} + long int VoxelSystem::getVoxelsCreated() { return _tree->voxelsCreated; } @@ -229,10 +243,8 @@ void VoxelSystem::cleanupRemovedVoxels() { } void VoxelSystem::copyWrittenDataToReadArraysFullVBOs() { - int bytesOfVertices = (_voxelsInWriteArrays * VERTEX_POINTS_PER_VOXEL) * sizeof(GLfloat); - int bytesOfColors = (_voxelsInWriteArrays * VERTEX_POINTS_PER_VOXEL) * sizeof(GLubyte); - memcpy(_readVerticesArray, _writeVerticesArray, bytesOfVertices); - memcpy(_readColorsArray, _writeColorsArray, bytesOfColors ); + copyWrittenDataSegmentToReadArrays(0, _voxelsInWriteArrays - 1); + _voxelsInReadArrays = _voxelsInWriteArrays; // clear our dirty flags @@ -259,47 +271,37 @@ void VoxelSystem::copyWrittenDataToReadArraysPartialVBOs() { if (!thisVoxelDirty) { // If we got here because because this voxel is NOT dirty, so the last dirty voxel was the one before // this one and so that's where the "segment" ends - segmentEnd = i - 1; + copyWrittenDataSegmentToReadArrays(segmentStart, i - 1); inSegment = false; - int segmentLength = (segmentEnd - segmentStart) + 1; - - GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); - GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); - GLfloat* readVerticesAt = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - GLfloat* writeVerticesAt = _writeVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - memcpy(readVerticesAt, writeVerticesAt, segmentSizeBytes); - - segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); - segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); - GLubyte* readColorsAt = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - GLubyte* writeColorsAt = _writeColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - memcpy(readColorsAt, writeColorsAt, segmentSizeBytes); } } } // if we got to the end of the array, and we're in an active dirty segment... if (inSegment) { - segmentEnd = _voxelsInWriteArrays - 1; - int segmentLength = (segmentEnd - segmentStart) + 1; - - GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); - GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); - GLfloat* readVerticesAt = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - GLfloat* writeVerticesAt = _writeVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - memcpy(readVerticesAt, writeVerticesAt, segmentSizeBytes); - - segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); - segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); - GLubyte* readColorsAt = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - GLubyte* writeColorsAt = _writeColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - memcpy(readColorsAt, writeColorsAt, segmentSizeBytes); + copyWrittenDataSegmentToReadArrays(segmentStart, _voxelsInWriteArrays - 1); } // update our length _voxelsInReadArrays = _voxelsInWriteArrays; } +void VoxelSystem::copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd) { + int segmentLength = (segmentEnd - segmentStart) + 1; + + GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); + GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); + GLfloat* readVerticesAt = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); + GLfloat* writeVerticesAt = _writeVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); + memcpy(readVerticesAt, writeVerticesAt, segmentSizeBytes); + + segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); + segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); + GLubyte* readColorsAt = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); + GLubyte* writeColorsAt = _writeColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); + memcpy(readColorsAt, writeColorsAt, segmentSizeBytes); +} + void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { PerformanceWarning warn(_renderWarningsOn, "copyWrittenDataToReadArrays()"); if (_voxelsDirty && _voxelsUpdated) { @@ -312,12 +314,11 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { } int VoxelSystem::newTreeToArrays(VoxelNode* node) { - assert(_viewFrustum); // you must set up _viewFrustum before calling this int voxelsUpdated = 0; bool shouldRender = false; // assume we don't need to render it // if it's colored, we might need to render it! if (node->isColored()) { - float distanceToNode = node->distanceToCamera(*_viewFrustum); + float distanceToNode = node->distanceToCamera(*Application::getInstance()->getViewFrustum()); float boundary = boundaryDistanceForRenderLevel(node->getLevel()); float childBoundary = boundaryDistanceForRenderLevel(node->getLevel() + 1); bool inBoundary = (distanceToNode <= boundary); @@ -352,7 +353,7 @@ int VoxelSystem::newTreeToArrays(VoxelNode* node) { int VoxelSystem::updateNodeInArraysAsFullVBO(VoxelNode* node) { // If we've run out of room, then just bail... - if (_voxelsInWriteArrays >= MAX_VOXELS_PER_SYSTEM) { + if (_voxelsInWriteArrays >= _maxVoxels) { return 0; } @@ -363,12 +364,7 @@ int VoxelSystem::updateNodeInArraysAsFullVBO(VoxelNode* node) { // populate the array with points for the 8 vertices // and RGB color for each added vertex - for (int j = 0; j < VERTEX_POINTS_PER_VOXEL; j++ ) { - GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL); - GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL); - *(writeVerticesAt+j) = startVertex[j % 3] + (identityVertices[j] * voxelScale); - *(writeColorsAt +j) = node->getColor()[j % 3]; - } + updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor()); node->setBufferIndex(nodeIndex); _writeVoxelDirtyArray[nodeIndex] = true; // just in case we switch to Partial mode _voxelsInWriteArrays++; // our know vertices in the arrays @@ -382,7 +378,7 @@ int VoxelSystem::updateNodeInArraysAsFullVBO(VoxelNode* node) { int VoxelSystem::updateNodeInArraysAsPartialVBO(VoxelNode* node) { // If we've run out of room, then just bail... - if (_voxelsInWriteArrays >= MAX_VOXELS_PER_SYSTEM) { + if (_voxelsInWriteArrays >= _maxVoxels) { return 0; } @@ -414,17 +410,31 @@ int VoxelSystem::updateNodeInArraysAsPartialVBO(VoxelNode* node) { // populate the array with points for the 8 vertices // and RGB color for each added vertex - for (int j = 0; j < VERTEX_POINTS_PER_VOXEL; j++ ) { - GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL); - GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL); - *(writeVerticesAt+j) = startVertex[j % 3] + (identityVertices[j] * voxelScale); - *(writeColorsAt +j) = node->getColor()[j % 3]; - } + updateNodeInArrays(nodeIndex, startVertex, voxelScale, node->getColor()); + return 1; // updated! } return 0; // not-updated } +void VoxelSystem::updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex, + float voxelScale, const nodeColor& color) { + for (int j = 0; j < VERTEX_POINTS_PER_VOXEL; j++ ) { + GLfloat* writeVerticesAt = _writeVerticesArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL); + GLubyte* writeColorsAt = _writeColorsArray + (nodeIndex * VERTEX_POINTS_PER_VOXEL); + *(writeVerticesAt+j) = startVertex[j % 3] + (identityVertices[j] * voxelScale); + *(writeColorsAt +j) = color[j % 3]; + } +} + +glm::vec3 VoxelSystem::computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const { + const float* identityVertex = identityVertices + index * 3; + return startVertex + glm::vec3(identityVertex[0], identityVertex[1], identityVertex[2]) * voxelScale; +} + +ProgramObject* VoxelSystem::_perlinModulateProgram = 0; +GLuint VoxelSystem::_permutationNormalTextureID = 0; + void VoxelSystem::init() { _renderWarningsOn = false; @@ -440,23 +450,23 @@ void VoxelSystem::init() { _unusedArraySpace = 0; // we will track individual dirty sections with these arrays of bools - _writeVoxelDirtyArray = new bool[MAX_VOXELS_PER_SYSTEM]; - memset(_writeVoxelDirtyArray, false, MAX_VOXELS_PER_SYSTEM * sizeof(bool)); - _readVoxelDirtyArray = new bool[MAX_VOXELS_PER_SYSTEM]; - memset(_readVoxelDirtyArray, false, MAX_VOXELS_PER_SYSTEM * sizeof(bool)); + _writeVoxelDirtyArray = new bool[_maxVoxels]; + memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); + _readVoxelDirtyArray = new bool[_maxVoxels]; + memset(_readVoxelDirtyArray, false, _maxVoxels * sizeof(bool)); // prep the data structures for incoming voxel data - _writeVerticesArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * MAX_VOXELS_PER_SYSTEM]; - _readVerticesArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * MAX_VOXELS_PER_SYSTEM]; + _writeVerticesArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * _maxVoxels]; + _readVerticesArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * _maxVoxels]; - _writeColorsArray = new GLubyte[VERTEX_POINTS_PER_VOXEL * MAX_VOXELS_PER_SYSTEM]; - _readColorsArray = new GLubyte[VERTEX_POINTS_PER_VOXEL * MAX_VOXELS_PER_SYSTEM]; + _writeColorsArray = new GLubyte[VERTEX_POINTS_PER_VOXEL * _maxVoxels]; + _readColorsArray = new GLubyte[VERTEX_POINTS_PER_VOXEL * _maxVoxels]; - GLuint* indicesArray = new GLuint[INDICES_PER_VOXEL * MAX_VOXELS_PER_SYSTEM]; + GLuint* indicesArray = new GLuint[INDICES_PER_VOXEL * _maxVoxels]; // populate the indicesArray // this will not change given new voxels, so we can set it all up now - for (int n = 0; n < MAX_VOXELS_PER_SYSTEM; n++) { + for (int n = 0; n < _maxVoxels; n++) { // fill the indices array int voxelIndexOffset = n * INDICES_PER_VOXEL; GLuint* currentIndicesPos = indicesArray + voxelIndexOffset; @@ -468,11 +478,11 @@ void VoxelSystem::init() { } } - GLfloat* normalsArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * MAX_VOXELS_PER_SYSTEM]; + GLfloat* normalsArray = new GLfloat[VERTEX_POINTS_PER_VOXEL * _maxVoxels]; GLfloat* normalsArrayEndPointer = normalsArray; // populate the normalsArray - for (int n = 0; n < MAX_VOXELS_PER_SYSTEM; n++) { + for (int n = 0; n < _maxVoxels; n++) { for (int i = 0; i < VERTEX_POINTS_PER_VOXEL; i++) { *(normalsArrayEndPointer++) = identityNormals[i]; } @@ -481,32 +491,35 @@ void VoxelSystem::init() { // VBO for the verticesArray glGenBuffers(1, &_vboVerticesID); glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID); - glBufferData(GL_ARRAY_BUFFER, VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat) * MAX_VOXELS_PER_SYSTEM, NULL, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat) * _maxVoxels, NULL, GL_DYNAMIC_DRAW); // VBO for the normalsArray glGenBuffers(1, &_vboNormalsID); glBindBuffer(GL_ARRAY_BUFFER, _vboNormalsID); glBufferData(GL_ARRAY_BUFFER, - VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat) * MAX_VOXELS_PER_SYSTEM, + VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat) * _maxVoxels, normalsArray, GL_STATIC_DRAW); // VBO for colorsArray glGenBuffers(1, &_vboColorsID); glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID); - glBufferData(GL_ARRAY_BUFFER, VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte) * MAX_VOXELS_PER_SYSTEM, NULL, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte) * _maxVoxels, NULL, GL_DYNAMIC_DRAW); // VBO for the indicesArray glGenBuffers(1, &_vboIndicesID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboIndicesID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, - INDICES_PER_VOXEL * sizeof(GLuint) * MAX_VOXELS_PER_SYSTEM, + INDICES_PER_VOXEL * sizeof(GLuint) * _maxVoxels, indicesArray, GL_STATIC_DRAW); // delete the indices and normals arrays that are no longer needed delete[] indicesArray; delete[] normalsArray; - // create our simple fragment shader + // create our simple fragment shader if we're the first system to init + if (_perlinModulateProgram != 0) { + return; + } switchToResourcesParentIfRequired(); _perlinModulateProgram = new ProgramObject(); _perlinModulateProgram->addShaderFromSourceFile(QGLShader::Vertex, "resources/shaders/perlin_modulate.vert"); @@ -539,20 +552,7 @@ void VoxelSystem::init() { } void VoxelSystem::updateFullVBOs() { - glBufferIndex segmentStart = 0; - glBufferIndex segmentEnd = _voxelsInReadArrays; - - int segmentLength = (segmentEnd - segmentStart) + 1; - GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); - GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); - GLfloat* readVerticesFrom = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID); - glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readVerticesFrom); - segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); - segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); - GLubyte* readColorsFrom = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID); - glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readColorsFrom); + updateVBOSegment(0, _voxelsInReadArrays); // consider the _readVoxelDirtyArray[] clean! memset(_readVoxelDirtyArray, false, _voxelsInReadArrays * sizeof(bool)); @@ -574,39 +574,17 @@ void VoxelSystem::updatePartialVBOs() { if (!thisVoxelDirty) { // If we got here because because this voxel is NOT dirty, so the last dirty voxel was the one before // this one and so that's where the "segment" ends - segmentEnd = i - 1; + updateVBOSegment(segmentStart, i - 1); inSegment = false; - int segmentLength = (segmentEnd - segmentStart) + 1; - GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); - GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); - GLfloat* readVerticesFrom = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID); - glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readVerticesFrom); - segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); - segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); - GLubyte* readColorsFrom = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID); - glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readColorsFrom); } _readVoxelDirtyArray[i] = false; // consider us clean! } } // if we got to the end of the array, and we're in an active dirty segment... - if (inSegment) { - segmentEnd = _voxelsInReadArrays - 1; + if (inSegment) { + updateVBOSegment(segmentStart, _voxelsInReadArrays - 1); inSegment = false; - int segmentLength = (segmentEnd - segmentStart) + 1; - GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); - GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); - GLfloat* readVerticesFrom = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID); - glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readVerticesFrom); - segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); - segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); - GLubyte* readColorsFrom = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); - glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID); - glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readColorsFrom); } } @@ -628,14 +606,28 @@ void VoxelSystem::updateVBOs() { _callsToTreesToArrays = 0; // clear it } +void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) { + int segmentLength = (segmentEnd - segmentStart) + 1; + GLintptr segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); + GLsizeiptr segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLfloat); + GLfloat* readVerticesFrom = _readVerticesArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); + glBindBuffer(GL_ARRAY_BUFFER, _vboVerticesID); + glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readVerticesFrom); + segmentStartAt = segmentStart * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); + segmentSizeBytes = segmentLength * VERTEX_POINTS_PER_VOXEL * sizeof(GLubyte); + GLubyte* readColorsFrom = _readColorsArray + (segmentStart * VERTEX_POINTS_PER_VOXEL); + glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID); + glBufferSubData(GL_ARRAY_BUFFER, segmentStartAt, segmentSizeBytes, readColorsFrom); +} + void VoxelSystem::render(bool texture) { PerformanceWarning warn(_renderWarningsOn, "render()"); // get the lock so that the update thread won't change anything pthread_mutex_lock(&_bufferWriteLock); - glPushMatrix(); updateVBOs(); + // tell OpenGL where to find vertex and color information glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); @@ -650,10 +642,7 @@ void VoxelSystem::render(bool texture) { glBindBuffer(GL_ARRAY_BUFFER, _vboColorsID); glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0); - if (texture) { - _perlinModulateProgram->bind(); - glBindTexture(GL_TEXTURE_2D, _permutationNormalTextureID); - } + applyScaleAndBindProgram(texture); // for performance, disable blending and enable backface culling glDisable(GL_BLEND); @@ -661,17 +650,13 @@ void VoxelSystem::render(bool texture) { // draw the number of voxels we have glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vboIndicesID); - glScalef(TREE_SCALE, TREE_SCALE, TREE_SCALE); glDrawRangeElementsEXT(GL_TRIANGLES, 0, VERTICES_PER_VOXEL * _voxelsInReadArrays - 1, 36 * _voxelsInReadArrays, GL_UNSIGNED_INT, 0); glEnable(GL_BLEND); glDisable(GL_CULL_FACE); - if (texture) { - _perlinModulateProgram->release(); - glBindTexture(GL_TEXTURE_2D, 0); - } + removeScaleAndReleaseProgram(texture); // deactivate vertex and color arrays after drawing glDisableClientState(GL_VERTEX_ARRAY); @@ -681,11 +666,28 @@ void VoxelSystem::render(bool texture) { // bind with 0 to switch back to normal operation glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + pthread_mutex_unlock(&_bufferWriteLock); +} +void VoxelSystem::applyScaleAndBindProgram(bool texture) { + glPushMatrix(); + glScalef(_treeScale, _treeScale, _treeScale); + + if (texture) { + _perlinModulateProgram->bind(); + glBindTexture(GL_TEXTURE_2D, _permutationNormalTextureID); + } +} + +void VoxelSystem::removeScaleAndReleaseProgram(bool texture) { // scale back down to 1 so heads aren't massive glPopMatrix(); - pthread_mutex_unlock(&_bufferWriteLock); + if (texture) { + _perlinModulateProgram->release(); + glBindTexture(GL_TEXTURE_2D, 0); + } } int VoxelSystem::_nodeCount = 0; @@ -855,7 +857,7 @@ bool VoxelSystem::removeOutOfViewOperation(VoxelNode* node, void* extraData) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { VoxelNode* childNode = node->getChildAtIndex(i); if (childNode) { - ViewFrustum::location inFrustum = childNode->inFrustum(*thisVoxelSystem->_viewFrustum); + ViewFrustum::location inFrustum = childNode->inFrustum(*Application::getInstance()->getViewFrustum()); switch (inFrustum) { case ViewFrustum::OUTSIDE: { args->nodesOutside++; @@ -907,9 +909,9 @@ bool VoxelSystem::hasViewChanged() { } // If our viewFrustum has changed since our _lastKnowViewFrustum - if (_viewFrustum && !_lastStableViewFrustum.matches(_viewFrustum)) { + if (!_lastStableViewFrustum.matches(Application::getInstance()->getViewFrustum())) { result = true; - _lastStableViewFrustum = *_viewFrustum; // save last stable + _lastStableViewFrustum = *Application::getInstance()->getViewFrustum(); // save last stable } return result; } @@ -1147,3 +1149,12 @@ void VoxelSystem::createSphere(float r,float xc, float yc, float zc, float s, bo _tree->createSphere(r, xc, yc, zc, s, solid, mode, destructive, debug); setupNewVoxelsForDrawing(); }; + +void VoxelSystem::copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinationTree, bool rebaseToRoot) { + _tree->copySubTreeIntoNewTree(startNode, destinationTree, rebaseToRoot); +} + +void VoxelSystem::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode) { + _tree->copyFromTreeIntoSubTree(sourceTree, destinationNode); +} + diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index 7bffb1d33c..c4aa33adfc 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -16,7 +16,6 @@ #include #include #include -#include "Avatar.h" #include "Camera.h" #include "Util.h" #include "world.h" @@ -27,23 +26,21 @@ const int NUM_CHILDREN = 8; class VoxelSystem : public AgentData { public: - VoxelSystem(); + VoxelSystem(float treeScale = TREE_SCALE, int maxVoxels = MAX_VOXELS_PER_SYSTEM); ~VoxelSystem(); int parseData(unsigned char* sourceBuffer, int numBytes); - void setViewFrustum(ViewFrustum* viewFrustum) { _viewFrustum = viewFrustum; }; - - void init(); + virtual void init(); void simulate(float deltaTime) { }; void render(bool texture); unsigned long getVoxelsUpdated() const {return _voxelsUpdated;}; unsigned long getVoxelsRendered() const {return _voxelsInReadArrays;}; - void setViewerAvatar(Avatar *newViewerAvatar) { _viewerAvatar = newViewerAvatar; }; - void setCamera(Camera* newCamera) { _camera = newCamera; }; void loadVoxelsFile(const char* fileName,bool wantColorRandomizer); + void writeToSVOFile(const char* filename, VoxelNode* node) const; + bool readFromSVOFile(const char* filename); long int getVoxelsCreated(); long int getVoxelsColored(); @@ -64,7 +61,7 @@ public: void setRenderPipelineWarnings(bool on) { _renderWarningsOn = on; }; bool getRenderPipelineWarnings() const { return _renderWarningsOn; }; - void removeOutOfView(); + virtual void removeOutOfView(); bool hasViewChanged(); bool isViewChanging(); @@ -83,6 +80,26 @@ public: void createLine(glm::vec3 point1, glm::vec3 point2, float unitSize, rgbColor color, bool destructive = false); void createSphere(float r,float xc, float yc, float zc, float s, bool solid, creationMode mode, bool destructive = false, bool debug = false); + + void copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinationTree, bool rebaseToRoot); + void copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode); + +protected: + float _treeScale; + int _maxVoxels; + VoxelTree* _tree; + + glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const; + + void setupNewVoxelsForDrawing(); + + virtual void updateNodeInArrays(glBufferIndex nodeIndex, const glm::vec3& startVertex, + float voxelScale, const nodeColor& color); + virtual void copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, glBufferIndex segmentEnd); + virtual void updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd); + virtual void applyScaleAndBindProgram(bool texture); + virtual void removeScaleAndReleaseProgram(bool texture); + private: // disallow copying of VoxelSystem objects VoxelSystem(const VoxelSystem&); @@ -110,13 +127,12 @@ private: void copyWrittenDataToReadArraysFullVBOs(); void copyWrittenDataToReadArraysPartialVBOs(); + void updateVBOs(); + // these are kinda hacks, used by getDistanceFromViewRangeOperation() probably shouldn't be here static float _maxDistance; static float _minDistance; - Avatar* _viewerAvatar; - Camera* _camera; - VoxelTree* _tree; GLfloat* _readVerticesArray; GLubyte* _readColorsArray; GLfloat* _writeVerticesArray; @@ -124,8 +140,8 @@ private: bool* _writeVoxelDirtyArray; bool* _readVoxelDirtyArray; unsigned long _voxelsUpdated; - unsigned long _voxelsInWriteArrays; unsigned long _voxelsInReadArrays; + unsigned long _voxelsInWriteArrays; unsigned long _unusedArraySpace; bool _writeRenderFullVBO; @@ -143,26 +159,21 @@ private: pthread_mutex_t _bufferWriteLock; pthread_mutex_t _treeLock; - ProgramObject* _perlinModulateProgram; - GLuint _permutationNormalTextureID; - - ViewFrustum* _viewFrustum; ViewFrustum _lastKnowViewFrustum; ViewFrustum _lastStableViewFrustum; int newTreeToArrays(VoxelNode *currentNode); void cleanupRemovedVoxels(); - void setupNewVoxelsForDrawing(); void copyWrittenDataToReadArrays(bool fullVBOs); + + void updateFullVBOs(); // all voxels in the VBO + void updatePartialVBOs(); // multiple segments, only dirty voxels bool _voxelsDirty; -public: - void updateVBOs(); - void updateFullVBOs(); // all voxels in the VBO - void updatePartialVBOs(); // multiple segments, only dirty voxels - + static ProgramObject* _perlinModulateProgram; + static GLuint _permutationNormalTextureID; }; #endif diff --git a/interface/src/main.cpp b/interface/src/main.cpp index ea915658af..8894e5bd7c 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -18,8 +18,9 @@ #include "Application.h" #include "Log.h" +#include + int main(int argc, const char * argv[]) { - timeval startup_time; gettimeofday(&startup_time, NULL); diff --git a/libraries/audio/src/AudioInjectionManager.cpp b/libraries/audio/src/AudioInjectionManager.cpp index f8fc9f742f..b49151616d 100644 --- a/libraries/audio/src/AudioInjectionManager.cpp +++ b/libraries/audio/src/AudioInjectionManager.cpp @@ -59,7 +59,6 @@ void* AudioInjectionManager::injectAudioViaThread(void* args) { // if we don't have an explicit destination socket then pull active socket for current audio mixer from agent list if (!_isDestinationSocketExplicit) { Agent* audioMixer = AgentList::getInstance()->soloAgentOfType(AGENT_TYPE_AUDIO_MIXER); - if (audioMixer) { _destinationSocket = *audioMixer->getActiveSocket(); } diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index d218639882..d51f058459 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -168,3 +168,88 @@ OctalCodeComparison compareOctalCodes(unsigned char* codeA, unsigned char* codeB return result; } + +char getOctalCodeSectionValue(unsigned char* octalCode, int section) { + int startAtByte = 1 + (BITS_IN_OCTAL * section / BITS_IN_BYTE); + char startIndexInByte = (BITS_IN_OCTAL * section) % BITS_IN_BYTE; + unsigned char* startByte = octalCode + startAtByte; + + return sectionValue(startByte, startIndexInByte); +} + +void setOctalCodeSectionValue(unsigned char* octalCode, int section, char sectionValue) { + int byteForSection = (BITS_IN_OCTAL * section / BITS_IN_BYTE); + unsigned char* byteAt = octalCode + 1 + byteForSection; + char bitInByte = (BITS_IN_OCTAL * section) % BITS_IN_BYTE; + char shiftBy = BITS_IN_BYTE - bitInByte - BITS_IN_OCTAL; + const unsigned char UNSHIFTED_MASK = 0x07; + unsigned char shiftedMask; + unsigned char shiftedValue; + if (shiftBy >=0) { + shiftedMask = UNSHIFTED_MASK << shiftBy; + shiftedValue = sectionValue << shiftBy; + } else { + shiftedMask = UNSHIFTED_MASK >> -shiftBy; + shiftedValue = sectionValue >> -shiftBy; + } + unsigned char oldValue = *byteAt & ~shiftedMask; + unsigned char newValue = oldValue | shiftedValue; + *byteAt = newValue; + + // If the requested section is partially in the byte, then we + // need to also set the portion of the section value in the next byte + // there's only two cases where this happens, if the bit in byte is + // 6, then it means that 1 extra bit lives in the next byte. If the + // bit in this byte is 7 then 2 extra bits live in the next byte. + const int FIRST_PARTIAL_BIT = 6; + if (bitInByte >= FIRST_PARTIAL_BIT) { + int bitsInFirstByte = BITS_IN_BYTE - bitInByte; + int bitsInSecondByte = BITS_IN_OCTAL - bitsInFirstByte; + shiftBy = BITS_IN_BYTE - bitsInSecondByte; + + shiftedMask = UNSHIFTED_MASK << shiftBy; + shiftedValue = sectionValue << shiftBy; + + oldValue = byteAt[1] & ~shiftedMask; + newValue = oldValue | shiftedValue; + byteAt[1] = newValue; + } +} + +unsigned char* chopOctalCode(unsigned char* originalOctalCode, int chopLevels) { + int codeLength = numberOfThreeBitSectionsInCode(originalOctalCode); + unsigned char* newCode = NULL; + if (codeLength > chopLevels) { + int newLength = codeLength - chopLevels; + newCode = new unsigned char[newLength+1]; + *newCode = newLength; // set the length byte + + for (int section = chopLevels; section < codeLength; section++) { + char sectionValue = getOctalCodeSectionValue(originalOctalCode, section); + setOctalCodeSectionValue(newCode, section - chopLevels, sectionValue); + } + } + return newCode; +} + +unsigned char* rebaseOctalCode(unsigned char* originalOctalCode, unsigned char* newParentOctalCode, bool includeColorSpace) { + int oldCodeLength = numberOfThreeBitSectionsInCode(originalOctalCode); + int newParentCodeLength = numberOfThreeBitSectionsInCode(newParentOctalCode); + int newCodeLength = newParentCodeLength + oldCodeLength; + int bufferLength = newCodeLength + (includeColorSpace ? SIZE_OF_COLOR_DATA : 0); + unsigned char* newCode = new unsigned char[bufferLength]; + *newCode = newCodeLength; // set the length byte + + // copy parent code section first + for (int sectionFromParent = 0; sectionFromParent < newParentCodeLength; sectionFromParent++) { + char sectionValue = getOctalCodeSectionValue(newParentOctalCode, sectionFromParent); + setOctalCodeSectionValue(newCode, sectionFromParent, sectionValue); + } + // copy original code section next + for (int sectionFromOriginal = 0; sectionFromOriginal < oldCodeLength; sectionFromOriginal++) { + char sectionValue = getOctalCodeSectionValue(originalOctalCode, sectionFromOriginal); + setOctalCodeSectionValue(newCode, sectionFromOriginal + newParentCodeLength, sectionValue); + } + return newCode; +} + diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index bf4a6ef699..761eac1953 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -11,12 +11,23 @@ #include +const int BITS_IN_BYTE = 8; +const int BITS_IN_OCTAL = 3; +const int NUMBER_OF_COLORS = 3; // RGB! +const int SIZE_OF_COLOR_DATA = NUMBER_OF_COLORS * sizeof(unsigned char); // size in bytes +const int RED_INDEX = 0; +const int GREEN_INDEX = 1; +const int BLUE_INDEX = 2; + void printOctalCode(unsigned char * octalCode); int bytesRequiredForCodeLength(unsigned char threeBitCodes); bool isDirectParentOfChild(unsigned char *parentOctalCode, unsigned char * childOctalCode); int branchIndexWithDescendant(unsigned char * ancestorOctalCode, unsigned char * descendantOctalCode); unsigned char * childOctalCode(unsigned char * parentOctalCode, char childNumber); - +int numberOfThreeBitSectionsInCode(unsigned char * octalCode); +unsigned char* chopOctalCode(unsigned char* originalOctalCode, int chopLevels); +unsigned char* rebaseOctalCode(unsigned char* originalOctalCode, unsigned char* newParentOctalCode, + bool includeColorSpace = false); // Note: copyFirstVertexForCode() is preferred because it doesn't allocate memory for the return // but other than that these do the same thing. diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index d117bac39f..466f45157e 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -71,7 +71,7 @@ struct VoxelDetail { unsigned char blue; }; -unsigned char* pointToVoxel(float x, float y, float z, float s, unsigned char r, unsigned char g, unsigned char b ); +unsigned char* pointToVoxel(float x, float y, float z, float s, unsigned char r = 0, unsigned char g = 0, unsigned char b = 0); bool createVoxelEditMessage(unsigned char command, short int sequence, int voxelCount, VoxelDetail* voxelDetails, unsigned char*& bufferOut, int& sizeOut); diff --git a/libraries/voxels/src/AABox.cpp b/libraries/voxels/src/AABox.cpp index b7580b243e..1b29c70b59 100644 --- a/libraries/voxels/src/AABox.cpp +++ b/libraries/voxels/src/AABox.cpp @@ -292,13 +292,16 @@ glm::vec3 AABox::getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& glm::vec4 diagonals[] = { secondAxisMinPlane + thirdAxisMaxPlane + offset, secondAxisMaxPlane + thirdAxisMaxPlane + offset }; + float minDistance = FLT_MAX; for (int i = 0; i < sizeof(diagonals) / sizeof(diagonals[0]); i++) { float divisor = glm::dot(direction, diagonals[i]); if (fabs(divisor) < EPSILON) { continue; // segment is parallel to diagonal plane } - float directionalDistance = -glm::dot(origin, diagonals[i]) / divisor; - return getClosestPointOnFace(glm::vec3(origin + direction * directionalDistance), face); + minDistance = glm::min(-glm::dot(origin, diagonals[i]) / divisor, minDistance); + } + if (minDistance != FLT_MAX) { + return getClosestPointOnFace(glm::vec3(origin + direction * minDistance), face); } } diff --git a/libraries/voxels/src/VoxelConstants.h b/libraries/voxels/src/VoxelConstants.h index 156a310476..5bf1345d73 100644 --- a/libraries/voxels/src/VoxelConstants.h +++ b/libraries/voxels/src/VoxelConstants.h @@ -13,6 +13,7 @@ #define __hifi_VoxelConstants_h__ #include +#include const int TREE_SCALE = 128; @@ -23,11 +24,12 @@ const int MAX_VOXELS_PER_SYSTEM = 200000; const int VERTICES_PER_VOXEL = 24; const int VERTEX_POINTS_PER_VOXEL = 3 * VERTICES_PER_VOXEL; const int INDICES_PER_VOXEL = 3 * 12; -const int COLOR_VALUES_PER_VOXEL = 3 * VERTICES_PER_VOXEL; +const int COLOR_VALUES_PER_VOXEL = NUMBER_OF_COLORS * VERTICES_PER_VOXEL; typedef unsigned long int glBufferIndex; const glBufferIndex GLBUFFER_INDEX_UNKNOWN = ULONG_MAX; const double SIXTY_FPS_IN_MILLISECONDS = 1000.0/60; const double VIEW_CULLING_RATE_IN_MILLISECONDS = 1000.0; // once a second is fine + #endif diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 39a7fceff6..49d79141de 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -53,7 +53,7 @@ VoxelTree::~VoxelTree() { // Recurses voxel tree calling the RecurseVoxelTreeOperation function for each node. // stops recursion if operation function returns false. void VoxelTree::recurseTreeWithOperation(RecurseVoxelTreeOperation operation, void* extraData) { - recurseNodeWithOperation(rootNode, operation,extraData); + recurseNodeWithOperation(rootNode, operation, extraData); } // Recurses voxel node with an operation function @@ -212,10 +212,15 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, } void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int bufferSizeBytes, - bool includeColor, bool includeExistsBits) { + bool includeColor, bool includeExistsBits, VoxelNode* destinationNode) { int bytesRead = 0; unsigned char* bitstreamAt = bitstream; + // If destination node is not included, set it to root + if (!destinationNode) { + destinationNode = rootNode; + } + _nodesChangedFromBitstream = 0; // Keep looping through the buffer calling readNodeData() this allows us to pack multiple root-relative Octal codes @@ -223,14 +228,14 @@ void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int // if there are more bytes after that, it's assumed to be another root relative tree while (bitstreamAt < bitstream + bufferSizeBytes) { - VoxelNode* bitstreamRootNode = nodeForOctalCode(rootNode, (unsigned char *)bitstreamAt, NULL); + VoxelNode* bitstreamRootNode = nodeForOctalCode(destinationNode, (unsigned char *)bitstreamAt, NULL); if (*bitstreamAt != *bitstreamRootNode->getOctalCode()) { // if the octal code returned is not on the same level as // the code being searched for, we have VoxelNodes to create // Note: we need to create this node relative to root, because we're assuming that the bitstream for the initial // octal code is always relative to root! - bitstreamRootNode = createMissingNode(rootNode, (unsigned char*) bitstreamAt); + bitstreamRootNode = createMissingNode(destinationNode, (unsigned char*) bitstreamAt); if (bitstreamRootNode->isDirty()) { _isDirty = true; _nodesChangedFromBitstream++; @@ -281,9 +286,9 @@ void VoxelTree::deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool stage, b } } - // If we're not a colored leaf, and we have no children, then delete ourselves - // This will collapse the empty tree above us. - if (collapseEmptyTrees && parentNode->getChildCount() == 0 && !parentNode->isColored()) { + // If we're in collapseEmptyTrees mode, and we're the last child of this parent, then delete the parent. + // This will collapse the empty tree above us. + if (collapseEmptyTrees && parentNode->getChildCount() == 0) { // Can't delete the root this way. if (parentNode != rootNode) { deleteVoxelCodeFromTree(parentNode->getOctalCode(), stage, collapseEmptyTrees); @@ -862,7 +867,7 @@ int VoxelTree::searchForColoredNodesRecursion(int maxSearchLevel, int& currentSe int VoxelTree::encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor, bool includeExistsBits, - bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const { + int chopLevels, bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const { // How many bytes have we written so far at this level; int bytesWritten = 0; @@ -873,16 +878,29 @@ int VoxelTree::encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, unsigned } // write the octal code - int codeLength = bytesRequiredForCodeLength(*node->getOctalCode()); - memcpy(outputBuffer,node->getOctalCode(),codeLength); - + int codeLength; + if (chopLevels) { + unsigned char* newCode = chopOctalCode(node->getOctalCode(), chopLevels); + if (newCode) { + codeLength = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(newCode)); + memcpy(outputBuffer, newCode, codeLength); + delete newCode; + } else { + codeLength = 1; // chopped to root! + *outputBuffer = 0; // root + } + } else { + codeLength = bytesRequiredForCodeLength(*node->getOctalCode()); + memcpy(outputBuffer, node->getOctalCode(), codeLength); + } + outputBuffer += codeLength; // move the pointer bytesWritten += codeLength; // keep track of byte count availableBytes -= codeLength; // keep track or remaining space int currentEncodeLevel = 0; int childBytesWritten = encodeTreeBitstreamRecursion(maxEncodeLevel, currentEncodeLevel, node, outputBuffer, availableBytes, - bag, viewFrustum, includeColor, includeExistsBits, + bag, viewFrustum, includeColor, includeExistsBits, chopLevels, deltaViewFrustum, lastViewFrustum); // if childBytesWritten == 1 then something went wrong... that's not possible @@ -907,7 +925,7 @@ int VoxelTree::encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, unsigned int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel, VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, const ViewFrustum* viewFrustum, bool includeColor, bool includeExistsBits, - bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const { + int chopLevels, bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const { // How many bytes have we written so far at this level; int bytesAtThisLevel = 0; @@ -1062,7 +1080,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco int thisLevel = currentEncodeLevel; int childTreeBytesOut = encodeTreeBitstreamRecursion(maxEncodeLevel, thisLevel, childNode, outputBuffer, availableBytes, bag, - viewFrustum, includeColor, includeExistsBits, + viewFrustum, includeColor, includeExistsBits, chopLevels, deltaViewFrustum, lastViewFrustum); // if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space, @@ -1105,7 +1123,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEnco return bytesAtThisLevel; } -bool VoxelTree::readFromFileV2(const char* fileName) { +bool VoxelTree::readFromSVOFile(const char* fileName) { std::ifstream file(fileName, std::ios::in|std::ios::binary|std::ios::ate); if(file.is_open()) { printLog("loading file %s...\n", fileName); @@ -1126,7 +1144,7 @@ bool VoxelTree::readFromFileV2(const char* fileName) { return false; } -void VoxelTree::writeToFileV2(const char* fileName) const { +void VoxelTree::writeToSVOFile(const char* fileName, VoxelNode* node) const { std::ofstream file(fileName, std::ios::out|std::ios::binary); @@ -1134,7 +1152,12 @@ void VoxelTree::writeToFileV2(const char* fileName) const { printLog("saving to file %s...\n", fileName); VoxelNodeBag nodeBag; - nodeBag.insert(rootNode); + // If we were given a specific node, start from there, otherwise start from root + if (node) { + nodeBag.insert(node); + } else { + nodeBag.insert(rootNode); + } static unsigned char outputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static int bytesWritten = 0; @@ -1160,3 +1183,47 @@ bool VoxelTree::countVoxelsOperation(VoxelNode* node, void* extraData) { (*(unsigned long*)extraData)++; return true; // keep going } + +void VoxelTree::copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinationTree, bool rebaseToRoot) { + VoxelNodeBag nodeBag; + nodeBag.insert(startNode); + int chopLevels = 0; + if (rebaseToRoot) { + chopLevels = numberOfThreeBitSectionsInCode(startNode->getOctalCode()); + } + + static unsigned char outputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static + int bytesWritten = 0; + + while (!nodeBag.isEmpty()) { + VoxelNode* subTree = nodeBag.extract(); + + // ask our tree to write a bitsteam + bytesWritten = encodeTreeBitstream(INT_MAX, subTree, &outputBuffer[0], + MAX_VOXEL_PACKET_SIZE - 1, nodeBag, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS, chopLevels); + + // ask destination tree to read the bitstream + destinationTree->readBitstreamToTree(&outputBuffer[0], bytesWritten, WANT_COLOR, NO_EXISTS_BITS); + } +} + +void VoxelTree::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode) { + VoxelNodeBag nodeBag; + // If we were given a specific node, start from there, otherwise start from root + nodeBag.insert(sourceTree->rootNode); + + static unsigned char outputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static + int bytesWritten = 0; + + while (!nodeBag.isEmpty()) { + VoxelNode* subTree = nodeBag.extract(); + + // ask our tree to write a bitsteam + bytesWritten = sourceTree->encodeTreeBitstream(INT_MAX, subTree, &outputBuffer[0], + MAX_VOXEL_PACKET_SIZE - 1, nodeBag, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); + + // ask destination tree to read the bitstream + readBitstreamToTree(&outputBuffer[0], bytesWritten, WANT_COLOR, NO_EXISTS_BITS, destinationNode); + } +} + diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index 0643b1038e..b63e6eb738 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -44,19 +44,20 @@ public: VoxelTree(bool shouldReaverage = false); ~VoxelTree(); - VoxelNode *rootNode; + VoxelNode* rootNode; int leavesWrittenToBitstream; void eraseAllVoxels(); - void processRemoveVoxelBitstream(unsigned char * bitstream, int bufferSizeBytes); - void readBitstreamToTree(unsigned char * bitstream, unsigned long int bufferSizeBytes, - bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS); - void readCodeColorBufferToTree(unsigned char *codeColorBuffer, bool destructive = false); - void deleteVoxelCodeFromTree(unsigned char *codeBuffer, bool stage = ACTUALLY_DELETE, + void processRemoveVoxelBitstream(unsigned char* bitstream, int bufferSizeBytes); + void readBitstreamToTree(unsigned char* bitstream, unsigned long int bufferSizeBytes, + bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS, + VoxelNode* destinationNode = NULL); + void readCodeColorBufferToTree(unsigned char* codeColorBuffer, bool destructive = false); + void deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool stage = ACTUALLY_DELETE, bool collapseEmptyTrees = DONT_COLLAPSE); - void printTreeForDebugging(VoxelNode *startNode); - void reaverageVoxelColors(VoxelNode *startNode); + void printTreeForDebugging(VoxelNode* startNode); + void reaverageVoxelColors(VoxelNode* startNode); void deleteVoxelAt(float x, float y, float z, float s, bool stage = false); VoxelNode* getVoxelAt(float x, float y, float z, float s) const; @@ -70,7 +71,7 @@ public: int encodeTreeBitstream(int maxEncodeLevel, VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, const ViewFrustum* viewFrustum, - bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS, + bool includeColor = WANT_COLOR, bool includeExistsBits = WANT_EXISTS_BITS, int chopLevels = 0, bool deltaViewFrustum = false, const ViewFrustum* lastViewFrustum = NULL) const; int searchForColoredNodes(int maxSearchLevel, VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag, @@ -91,16 +92,19 @@ public: void loadVoxelsFile(const char* fileName, bool wantColorRandomizer); // these will read/write files that match the wireformat, excluding the 'V' leading - void writeToFileV2(const char* filename) const; - bool readFromFileV2(const char* filename); + void writeToSVOFile(const char* filename, VoxelNode* node = NULL) const; + bool readFromSVOFile(const char* filename); unsigned long getVoxelCount(); + + void copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinationTree, bool rebaseToRoot); + void copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destinationNode); private: int encodeTreeBitstreamRecursion(int maxEncodeLevel, int& currentEncodeLevel, VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, - const ViewFrustum* viewFrustum, bool includeColor, bool includeExistsBits, - bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const; + const ViewFrustum* viewFrustum, bool includeColor, bool includeExistsBits, + int chopLevels, bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) const; int searchForColoredNodesRecursion(int maxSearchLevel, int& currentSearchLevel, VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag, diff --git a/voxel-edit/src/main.cpp b/voxel-edit/src/main.cpp index 222bf3f15b..9571e4adf5 100644 --- a/voxel-edit/src/main.cpp +++ b/voxel-edit/src/main.cpp @@ -92,7 +92,7 @@ int main(int argc, const char * argv[]) unsigned long nodeCount = myTree.getVoxelCount(); printf("Nodes after adding scenes: %ld nodes\n", nodeCount); - myTree.writeToFileV2("voxels.hio2"); + myTree.writeToSVOFile("voxels.svo"); } return 0; diff --git a/voxel-server/src/main.cpp b/voxel-server/src/main.cpp index 1a93021f09..9bf4a1dcc4 100644 --- a/voxel-server/src/main.cpp +++ b/voxel-server/src/main.cpp @@ -30,8 +30,8 @@ #include #endif -const char* LOCAL_VOXELS_PERSIST_FILE = "resources/voxels.hio2"; -const char* VOXELS_PERSIST_FILE = "/etc/highfidelity/voxel-server/resources/voxels.hio2"; +const char* LOCAL_VOXELS_PERSIST_FILE = "resources/voxels.svo"; +const char* VOXELS_PERSIST_FILE = "/etc/highfidelity/voxel-server/resources/voxels.svo"; const double VOXEL_PERSIST_INTERVAL = 1000.0 * 30; // every 30 seconds const int VOXEL_LISTEN_PORT = 40106; @@ -399,10 +399,10 @@ void persistVoxelsWhenDirty() { { PerformanceWarning warn(::shouldShowAnimationDebug, - "persistVoxelsWhenDirty() - writeToFileV2()", ::shouldShowAnimationDebug); + "persistVoxelsWhenDirty() - writeToSVOFile()", ::shouldShowAnimationDebug); printf("saving voxels to file...\n"); - randomTree.writeToFileV2(::wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE); + randomTree.writeToSVOFile(::wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE); randomTree.clearDirtyBit(); // tree is clean after saving printf("DONE saving voxels to file...\n"); } @@ -505,7 +505,7 @@ int main(int argc, const char * argv[]) { bool persistantFileRead = false; if (::wantVoxelPersist) { printf("loading voxels from file...\n"); - persistantFileRead = ::randomTree.readFromFileV2(::wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE); + persistantFileRead = ::randomTree.readFromSVOFile(::wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE); ::randomTree.clearDirtyBit(); // the tree is clean since we just loaded it printf("DONE loading voxels from file... fileRead=%s\n", debug::valueOf(persistantFileRead)); unsigned long nodeCount = ::randomTree.getVoxelCount(); @@ -517,7 +517,7 @@ int main(int argc, const char * argv[]) { const char* INPUT_FILE = "-i"; const char* voxelsFilename = getCmdOption(argc, argv, INPUT_FILE); if (voxelsFilename) { - randomTree.loadVoxelsFile(voxelsFilename,wantColorRandomizer); + randomTree.readFromSVOFile(voxelsFilename); } // Check to see if the user passed in a command line option for setting packet send rate