diff --git a/cmake/modules/FindOpenNI.cmake b/cmake/modules/FindOpenNI.cmake new file mode 100644 index 0000000000..111a33dfbe --- /dev/null +++ b/cmake/modules/FindOpenNI.cmake @@ -0,0 +1,44 @@ +# Find the OpenNI library +# +# You must provide an OPENNI_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# OPENNI_FOUND - system found OpenNI +# OPENNI_INCLUDE_DIRS - the OpenNI include directory +# OPENNI_LIBRARIES - Link this to use OpenNI +# +# Created on 6/28/2013 by Andrzej Kapolka +# Copyright (c) 2013 High Fidelity +# + +if (OPENNI_LIBRARIES AND OPENNI_INCLUDE_DIRS) + # in cache already + set(OPENNI_FOUND TRUE) +else (OPENNI_LIBRARIES AND OPENNI_INCLUDE_DIRS) + find_path(OPENNI_INCLUDE_DIRS XnOpenNI.h /usr/include/ni) + + if (APPLE) + find_library(OPENNI_LIBRARIES libOpenNI.dylib /usr/lib) + elseif (UNIX) + find_library(OPENNI_LIBRARIES libOpenNI.so /usr/lib) + endif () + + if (OPENNI_INCLUDE_DIRS AND OPENNI_LIBRARIES) + set(OPENNI_FOUND TRUE) + endif (OPENNI_INCLUDE_DIRS AND OPENNI_LIBRARIES) + + if (OPENNI_FOUND) + if (NOT OPENNI_FIND_QUIETLY) + message(STATUS "Found OpenNI: ${OPENNI_LIBRARIES}") + endif (NOT OPENNI_FIND_QUIETLY) + else (OPENNI_FOUND) + if (OPENNI_FIND_REQUIRED) + message(FATAL_ERROR "Could not find OpenNI") + endif (OPENNI_FIND_REQUIRED) + endif (OPENNI_FOUND) + + # show the OPENNI_INCLUDE_DIRS and OPENNI_LIBRARIES variables only in the advanced view + mark_as_advanced(OPENNI_INCLUDE_DIRS OPENNI_LIBRARIES) + +endif (OPENNI_LIBRARIES AND OPENNI_INCLUDE_DIRS) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 5de01a50bc..339e7a77b7 100755 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -65,7 +65,7 @@ if (APPLE) endif (APPLE) -find_package(Qt4 REQUIRED QtCore QtGui QtNetwork QtOpenGL QtWebKit) +find_package(Qt4 REQUIRED QtCore QtGui QtNetwork QtOpenGL QtWebKit QtSvg) include(${QT_USE_FILE}) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${QT_QTGUI_INCLUDE_DIR}") @@ -95,6 +95,15 @@ find_package(Leap) find_package(OpenCV) find_package(ZLIB) find_package(UVCCameraControl) +find_package(OpenNI) + +# let the source know that we have OpenNI/NITE for Kinect +if (OPENNI_FOUND) + add_definitions(-DHAVE_OPENNI) + include_directories(SYSTEM ${OPENNI_INCLUDE_DIRS}) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${OPENNI_INCLUDE_DIRS}") + target_link_libraries(${TARGET_NAME} ${OPENNI_LIBRARIES}) +endif (OPENNI_FOUND) # include headers for interface and InterfaceConfig. include_directories( diff --git a/interface/resources/images/hifi-interface-tools.svg b/interface/resources/images/hifi-interface-tools.svg new file mode 100644 index 0000000000..311514581f --- /dev/null +++ b/interface/resources/images/hifi-interface-tools.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dc97742ea9..be93d25af9 100755 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -8,6 +8,7 @@ #include #include +#include #ifdef _WIN32 #include "Syssocket.h" @@ -63,6 +64,7 @@ #include "Util.h" #include "renderer/ProgramObject.h" #include "ui/TextRenderer.h" +#include "Swatch.h" #include "fvupdater.h" using namespace std; @@ -198,7 +200,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _packetCount(0), _packetsPerSecond(0), _bytesPerSecond(0), - _bytesCount(0) + _bytesCount(0), + _swatch(NULL) { _applicationStartupTime = startup_time; _window->setWindowTitle("Interface"); @@ -274,7 +277,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : FvUpdater::sharedUpdater()->SetFeedURL("https://s3-us-west-1.amazonaws.com/highfidelity/appcast.xml"); FvUpdater::sharedUpdater()->CheckForUpdatesSilent(); #endif - + initMenu(); QRect available = desktop()->availableGeometry(); @@ -483,30 +486,41 @@ void Application::resizeGL(int width, int height) { glLoadIdentity(); } -void Application::broadcastToNodes(unsigned char* data, size_t bytes, const char type) { +void Application::controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes, + const char* nodeTypes, int numNodeTypes) { + Application* self = getInstance(); + for (int i = 0; i < numNodeTypes; ++i) { - int n = NodeList::getInstance()->broadcastToNodes(data, bytes, &type, 1); + // Intercept data to voxel server when voxels are disabled + if (nodeTypes[i] == NODE_TYPE_VOXEL_SERVER && ! self->_renderVoxels->isChecked()) { + continue; + } - BandwidthMeter::ChannelIndex channel; - switch (type) { - case NODE_TYPE_AGENT: - case NODE_TYPE_AVATAR_MIXER: - channel = BandwidthMeter::AVATARS; - break; - case NODE_TYPE_VOXEL_SERVER: - channel = BandwidthMeter::VOXELS; - break; - default: - return; + // Perform the broadcast for one type + int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(broadcastData, dataBytes, & nodeTypes[i], 1); + + // Feed number of bytes to corresponding channel of the bandwidth meter, if any (done otherwise) + BandwidthMeter::ChannelIndex channel; + switch (nodeTypes[i]) { + case NODE_TYPE_AGENT: + case NODE_TYPE_AVATAR_MIXER: + channel = BandwidthMeter::AVATARS; + break; + case NODE_TYPE_VOXEL_SERVER: + channel = BandwidthMeter::VOXELS; + break; + default: + continue; + } + self->_bandwidthMeter.outputStream(channel).updateValue(nReceivingNodes * dataBytes); } - getInstance()->_bandwidthMeter.outputStream(channel).updateValue(n * bytes); } void Application::sendVoxelServerAddScene() { char message[100]; sprintf(message,"%c%s",'Z',"add scene"); int messageSize = strlen(message) + 1; - broadcastToNodes((unsigned char*)message, messageSize, NODE_TYPE_VOXEL_SERVER); + controlledBroadcastToNodes((unsigned char*)message, messageSize, & NODE_TYPE_VOXEL_SERVER, 1); } void Application::keyPressEvent(QKeyEvent* event) { @@ -716,7 +730,16 @@ void Application::keyPressEvent(QKeyEvent* event) { deleteVoxelUnderCursor(); } break; - + case Qt::Key_1: + case Qt::Key_2: + case Qt::Key_3: + case Qt::Key_4: + case Qt::Key_5: + case Qt::Key_6: + case Qt::Key_7: + case Qt::Key_8: + _swatch.handleEvent(event->key(), _eyedropperMode->isChecked()); + break; default: event->ignore(); break; @@ -877,16 +900,16 @@ void Application::wheelEvent(QWheelEvent* event) { } } -void sendPingPackets() { +void Application::sendPingPackets() { char nodeTypesOfInterest[] = {NODE_TYPE_VOXEL_SERVER, NODE_TYPE_AUDIO_MIXER, NODE_TYPE_AVATAR_MIXER}; long long currentTime = usecTimestampNow(); - char pingPacket[1 + sizeof(currentTime)]; + unsigned char pingPacket[1 + sizeof(currentTime)]; pingPacket[0] = PACKET_HEADER_PING; memcpy(&pingPacket[1], ¤tTime, sizeof(currentTime)); - NodeList::getInstance()->broadcastToNodes((unsigned char*)pingPacket, 1 + sizeof(currentTime), nodeTypesOfInterest, 3); - + getInstance()->controlledBroadcastToNodes(pingPacket, 1 + sizeof(currentTime), + nodeTypesOfInterest, sizeof(nodeTypesOfInterest)); } // Every second, check the frame rates and other stuff @@ -944,14 +967,17 @@ void Application::idle() { // Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time we ran if (diffclock(&_lastTimeIdle, &check) > IDLE_SIMULATE_MSECS) { - // We call processEvents() here because the idle timer takes priority over - // event handling in Qt, so when the framerate gets low events will pile up - // unless we handle them here. - // NOTE - this is commented out for now - causing event processing issues reported by Philip and Ryan - // birarda - July 3rd - - // processEvents(); + // If we're using multi-touch look, immediately process any + // touch events, and no other events. + // This is necessary because id the idle() call takes longer than the + // interval between idle() calls, the event loop never gets to run, + // and touch events get delayed. + if (_touchLook->isChecked()) { + sendPostedEvents(NULL, QEvent::TouchBegin); + sendPostedEvents(NULL, QEvent::TouchUpdate); + sendPostedEvents(NULL, QEvent::TouchEnd); + } update(1.0f / _fps); @@ -987,7 +1013,7 @@ void Application::sendAvatarVoxelURLMessage(const QUrl& url) { message.append((const char*)&ownerID, sizeof(ownerID)); message.append(url.toEncoded()); - broadcastToNodes((unsigned char*)message.data(), message.size(), NODE_TYPE_AVATAR_MIXER); + controlledBroadcastToNodes((unsigned char*)message.data(), message.size(), & NODE_TYPE_AVATAR_MIXER, 1); } void Application::processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataBytes) { @@ -1224,7 +1250,7 @@ void Application::sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail int sizeOut; if (createVoxelEditMessage(header, 0, 1, &detail, bufferOut, sizeOut)){ - Application::broadcastToNodes(bufferOut, sizeOut, NODE_TYPE_VOXEL_SERVER); + Application::controlledBroadcastToNodes(bufferOut, sizeOut, & NODE_TYPE_VOXEL_SERVER, 1); delete[] bufferOut; } } @@ -1242,7 +1268,11 @@ void Application::decreaseVoxelSize() { void Application::increaseVoxelSize() { _mouseVoxelScale *= 2; } - + +void Application::resetSwatchColors() { + _swatch.reset(); +} + static QIcon createSwatchIcon(const QColor& color) { QPixmap map(16, 16); map.fill(color); @@ -1305,7 +1335,7 @@ bool Application::sendVoxelsOperation(VoxelNode* node, int level, void* extraDat // 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) { - broadcastToNodes(args->messageBuffer, args->bufferInUse, NODE_TYPE_VOXEL_SERVER); + controlledBroadcastToNodes(args->messageBuffer, args->bufferInUse, & NODE_TYPE_VOXEL_SERVER, 1); args->bufferInUse = sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int); // reset } @@ -1337,8 +1367,10 @@ void Application::exportVoxels() { void Application::importVoxels() { QString desktopLocation = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation); - QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Import Voxels"), desktopLocation, - tr("Sparse Voxel Octree Files, Square PNG (*.svo *.png)")); + QString fileNameString = QFileDialog::getOpenFileName( + _glWidget, tr("Import Voxels"), desktopLocation, + tr("Sparse Voxel Octree Files, Square PNG, Schematic Files (*.svo *.png *.schematic)")); + QByteArray fileNameAscii = fileNameString.toAscii(); const char* fileName = fileNameAscii.data(); @@ -1359,8 +1391,10 @@ void Application::importVoxels() { } importVoxels.readFromSquareARGB32Pixels(pixels, pngImage.height()); - } else { + } else if (fileNameString.endsWith(".svo", Qt::CaseInsensitive)) { importVoxels.readFromSVOFile(fileName); + } else { + importVoxels.readFromSchematicFile(fileName); } VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); @@ -1386,7 +1420,7 @@ void Application::importVoxels() { // 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))) { - broadcastToNodes(args.messageBuffer, args.bufferInUse, NODE_TYPE_VOXEL_SERVER); + controlledBroadcastToNodes(args.messageBuffer, args.bufferInUse, & NODE_TYPE_VOXEL_SERVER, 1); } if (calculatedOctCode) { @@ -1438,7 +1472,7 @@ void Application::pasteVoxels() { // 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))) { - broadcastToNodes(args.messageBuffer, args.bufferInUse, NODE_TYPE_VOXEL_SERVER); + controlledBroadcastToNodes(args.messageBuffer, args.bufferInUse, & NODE_TYPE_VOXEL_SERVER, 1); } if (calculatedOctCode) { @@ -1480,12 +1514,13 @@ void Application::initMenu() { (_testPing = optionsMenu->addAction("Test Ping"))->setCheckable(true); _testPing->setChecked(true); (_fullScreenMode = optionsMenu->addAction("Fullscreen", this, SLOT(setFullscreen(bool)), Qt::Key_F))->setCheckable(true); - optionsMenu->addAction("Webcam", &_webcam, SLOT(setEnabled(bool)))->setCheckable(true); + optionsMenu->addAction("Webcam", &_webcam, SLOT(setEnabled(bool)))->setCheckable(true); + optionsMenu->addAction("Go Home", this, SLOT(goHome())); QMenu* renderMenu = menuBar->addMenu("Render"); (_renderVoxels = renderMenu->addAction("Voxels"))->setCheckable(true); _renderVoxels->setChecked(true); - _renderVoxels->setShortcut(Qt::Key_V); + _renderVoxels->setShortcut(Qt::SHIFT | Qt::Key_V); (_renderVoxelTextures = renderMenu->addAction("Voxel Textures"))->setCheckable(true); (_renderStarsOn = renderMenu->addAction("Stars"))->setCheckable(true); _renderStarsOn->setChecked(true); @@ -1515,6 +1550,8 @@ void Application::initMenu() { (_logOn = toolsMenu->addAction("Log"))->setCheckable(true); _logOn->setChecked(false); _logOn->setShortcut(Qt::CTRL | Qt::Key_L); + (_oscilloscopeOn = toolsMenu->addAction("Audio Oscilloscope"))->setCheckable(true); + _oscilloscopeOn->setChecked(true); (_bandwidthDisplayOn = toolsMenu->addAction("Bandwidth Display"))->setCheckable(true); _bandwidthDisplayOn->setChecked(true); toolsMenu->addAction("Bandwidth Details", this, SLOT(bandwidthDetails())); @@ -1525,36 +1562,39 @@ void Application::initMenu() { _voxelModeActions->setExclusive(false); // exclusivity implies one is always checked (_addVoxelMode = voxelMenu->addAction( - "Add Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::CTRL | Qt::Key_A))->setCheckable(true); + "Add Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::Key_V))->setCheckable(true); _voxelModeActions->addAction(_addVoxelMode); (_deleteVoxelMode = voxelMenu->addAction( - "Delete Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::CTRL | Qt::Key_D))->setCheckable(true); + "Delete Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::Key_R))->setCheckable(true); _voxelModeActions->addAction(_deleteVoxelMode); (_colorVoxelMode = voxelMenu->addAction( - "Color Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::CTRL | Qt::Key_B))->setCheckable(true); + "Color Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::Key_B))->setCheckable(true); _voxelModeActions->addAction(_colorVoxelMode); (_selectVoxelMode = voxelMenu->addAction( - "Select Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::CTRL | Qt::Key_S))->setCheckable(true); + "Select Voxel Mode", this, SLOT(updateVoxelModeActions()), Qt::Key_O))->setCheckable(true); _voxelModeActions->addAction(_selectVoxelMode); (_eyedropperMode = voxelMenu->addAction( - "Get Color Mode", this, SLOT(updateVoxelModeActions()), Qt::CTRL | Qt::Key_G))->setCheckable(true); + "Get Color Mode", this, SLOT(updateVoxelModeActions()), Qt::Key_G))->setCheckable(true); _voxelModeActions->addAction(_eyedropperMode); - + voxelMenu->addAction("Decrease Voxel Size", this, SLOT(decreaseVoxelSize()), QKeySequence::ZoomOut); voxelMenu->addAction("Increase Voxel Size", this, SLOT(increaseVoxelSize()), QKeySequence::ZoomIn); - + voxelMenu->addAction("Reset Swatch Colors", this, SLOT(resetSwatchColors())); + _voxelPaintColor = voxelMenu->addAction("Voxel Paint Color", this, SLOT(chooseVoxelPaintColor()), Qt::META | Qt::Key_C); + _swatch.setAction(_voxelPaintColor); + 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); + 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* debugMenu = menuBar->addMenu("Debug"); @@ -1680,6 +1720,14 @@ void Application::init() { printLog("Loaded settings.\n"); sendAvatarVoxelURLMessage(_myAvatar.getVoxels()->getVoxelURL()); + + _palette.init(_glWidget->width(), _glWidget->height()); + _palette.addAction(_addVoxelMode, 0, 0); + _palette.addAction(_deleteVoxelMode, 0, 1); + _palette.addTool(&_swatch); + _palette.addAction(_colorVoxelMode, 0, 2); + _palette.addAction(_eyedropperMode, 0, 3); + _palette.addAction(_selectVoxelMode, 0, 4); } const float MAX_AVATAR_EDIT_VELOCITY = 1.0f; @@ -1912,7 +1960,7 @@ void Application::update(float deltaTime) { if (_bandwidthDialog) { _bandwidthDialog->update(); } - + // Update audio stats for procedural sounds #ifndef _WIN32 _audio.setLastAcceleration(_myAvatar.getThrust()); @@ -1923,11 +1971,11 @@ void Application::update(float deltaTime) { void Application::updateAvatar(float deltaTime) { - // Update my avatar's head position from gyros and/or webcam - _myAvatar.updateHeadFromGyrosAndOrWebcam(_gyroLook->isChecked(), - glm::vec3(_headCameraPitchYawScale, - _headCameraPitchYawScale, - _headCameraPitchYawScale)); + // Update my avatar's state from gyros and/or webcam + _myAvatar.updateFromGyrosAndOrWebcam(_gyroLook->isChecked(), + glm::vec3(_headCameraPitchYawScale, + _headCameraPitchYawScale, + _headCameraPitchYawScale)); if (_serialHeadSensor.isActive()) { @@ -1989,9 +2037,10 @@ void Application::updateAvatar(float deltaTime) { endOfBroadcastStringWrite += packNodeId(endOfBroadcastStringWrite, nodeList->getOwnerID()); endOfBroadcastStringWrite += _myAvatar.getBroadcastData(endOfBroadcastStringWrite); - - broadcastToNodes(broadcastString, endOfBroadcastStringWrite - broadcastString, NODE_TYPE_VOXEL_SERVER); - broadcastToNodes(broadcastString, endOfBroadcastStringWrite - broadcastString, NODE_TYPE_AVATAR_MIXER); + + const char nodeTypesOfInterest[] = { NODE_TYPE_VOXEL_SERVER, NODE_TYPE_AVATAR_MIXER }; + controlledBroadcastToNodes(broadcastString, endOfBroadcastStringWrite - broadcastString, + nodeTypesOfInterest, sizeof(nodeTypesOfInterest)); // once in a while, send my voxel url const float AVATAR_VOXEL_URL_SEND_INTERVAL = 1.0f; // seconds @@ -2350,8 +2399,9 @@ void Application::displayOverlay() { #ifndef _WIN32 _audio.render(_glWidget->width(), _glWidget->height()); - _audioScope.render(20, _glWidget->height() - 200); - //_audio.renderEchoCompare(); // PER: Will turn back on to further test echo + if (_oscilloscopeOn->isChecked()) { + _audioScope.render(20, _glWidget->height() - 200); + } #endif //noiseTest(_glWidget->width(), _glWidget->height()); @@ -2431,7 +2481,52 @@ void Application::displayOverlay() { // render the webcam input frame _webcam.renderPreview(_glWidget->width(), _glWidget->height()); - + + _palette.render(_glWidget->width(), _glWidget->height()); + + if (_eyedropperMode->isChecked() && _voxelPaintColor->data().value() != _swatch.getColor()) { + QColor color = _voxelPaintColor->data().value(); + TextRenderer textRenderer(SANS_FONT_FAMILY, 11, 50); + const char line1[] = "Assign this color to a swatch"; + const char line2[] = "by choosing a key from 1 to 8."; + + int left = (_glWidget->width() - POPUP_WIDTH - 2 * POPUP_MARGIN) / 2; + int top = _glWidget->height() / 40; + + glBegin(GL_POLYGON); + glColor3f(0.0f, 0.0f, 0.0f); + for (double a = M_PI; a < 1.5f * M_PI; a += POPUP_STEP) { + glVertex2f(left + POPUP_MARGIN * cos(a) , top + POPUP_MARGIN * sin(a)); + } + for (double a = 1.5f * M_PI; a < 2.0f * M_PI; a += POPUP_STEP) { + glVertex2f(left + POPUP_WIDTH + POPUP_MARGIN * cos(a), top + POPUP_MARGIN * sin(a)); + } + for (double a = 0.0f; a < 0.5f * M_PI; a += POPUP_STEP) { + glVertex2f(left + POPUP_WIDTH + POPUP_MARGIN * cos(a), top + POPUP_HEIGHT + POPUP_MARGIN * sin(a)); + } + for (double a = 0.5f * M_PI; a < 1.0f * M_PI; a += POPUP_STEP) { + glVertex2f(left + POPUP_MARGIN * cos(a) , top + POPUP_HEIGHT + POPUP_MARGIN * sin(a)); + } + glEnd(); + + glBegin(GL_QUADS); + glColor3f(color.redF(), + color.greenF(), + color.blueF()); + glVertex2f(left , top); + glVertex2f(left + SWATCH_WIDTH, top); + glVertex2f(left + SWATCH_WIDTH, top + SWATCH_HEIGHT); + glVertex2f(left , top + SWATCH_HEIGHT); + glEnd(); + + glColor3f(1.0f, 1.0f, 1.0f); + textRenderer.draw(left + SWATCH_WIDTH + POPUP_MARGIN, top + FIRST_LINE_OFFSET , line1); + textRenderer.draw(left + SWATCH_WIDTH + POPUP_MARGIN, top + SECOND_LINE_OFFSET, line2); + } + else { + _swatch.checkColor(); + } + glPopMatrix(); } @@ -2953,6 +3048,7 @@ void Application::eyedropperVoxelUnderCursor() { } void Application::goHome() { + printLog("Going Home!\n"); _myAvatar.setPosition(START_LOCATION); } @@ -3127,7 +3223,8 @@ void Application::loadSettings(QSettings* settings) { settings->endGroup(); scanMenuBar(&Application::loadAction, settings); - getAvatar()->loadData(settings); + getAvatar()->loadData(settings); + _swatch.loadData(settings); } @@ -3149,6 +3246,7 @@ void Application::saveSettings(QSettings* settings) { scanMenuBar(&Application::saveAction, settings); getAvatar()->saveData(settings); + _swatch.saveData(settings); } void Application::importSettings() { diff --git a/interface/src/Application.h b/interface/src/Application.h index fe53c6edfa..b9ca8919b5 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -39,6 +39,8 @@ #include "Webcam.h" #include "renderer/GeometryCache.h" #include "ui/ChatEntry.h" +#include "ToolsPalette.h" +#include "Swatch.h" class QAction; class QActionGroup; @@ -140,6 +142,7 @@ private slots: void updateVoxelModeActions(); void decreaseVoxelSize(); void increaseVoxelSize(); + void resetSwatchColors(); void chooseVoxelPaintColor(); void loadSettings(QSettings* set = NULL); void saveSettings(QSettings* set = NULL); @@ -159,15 +162,19 @@ private slots: void renderCoverageMapsV2Recursively(CoverageMapV2* map); glm::vec2 getScaledScreenPoint(glm::vec2 projectedPoint); + void goHome(); private: - static void broadcastToNodes(unsigned char* data, size_t bytes, const char type); + static void controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes, + const char* nodeTypes, int numNodeTypes); + static void sendVoxelServerAddScene(); static bool sendVoxelsOperation(VoxelNode* node, int level, void* extraData); + static void sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail); static void sendAvatarVoxelURLMessage(const QUrl& url); static void processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataBytes); - static void sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail); + static void sendPingPackets(); void initMenu(); void updateFrustumRenderModeAction(); @@ -191,7 +198,6 @@ private: void maybeEditVoxelUnderCursor(); void deleteVoxelUnderCursor(); void eyedropperVoxelUnderCursor(); - void goHome(); void resetSensors(); void setMenuShortcutsEnabled(bool enabled); @@ -236,6 +242,7 @@ private: QAction* _manualFirstPerson; // Whether to force first-person mode QAction* _manualThirdPerson; // Whether to force third-person mode QAction* _logOn; // Whether to show on-screen log + QAction* _oscilloscopeOn; // Whether to show the oscilloscope QAction* _bandwidthDisplayOn; // Whether to show on-screen bandwidth bars QActionGroup* _voxelModeActions; // The group of voxel edit mode actions QAction* _addVoxelMode; // Whether add voxel mode is enabled @@ -370,6 +377,8 @@ private: int _bytesPerSecond; int _bytesCount; + ToolsPalette _palette; + Swatch _swatch; }; #endif /* defined(__interface__Application__) */ diff --git a/interface/src/Avatar.cpp b/interface/src/Avatar.cpp index 2c94b134f3..c2c850c923 100755 --- a/interface/src/Avatar.cpp +++ b/interface/src/Avatar.cpp @@ -148,121 +148,121 @@ void Avatar::initializeBodyBalls() { _bodyBall[ BODY_BALL_HEAD_BASE ].radius = 0.07; _bodyBall[ BODY_BALL_LEFT_COLLAR ].radius = 0.04; _bodyBall[ BODY_BALL_LEFT_SHOULDER ].radius = 0.03; - _bodyBall[ BODY_BALL_LEFT_ELBOW ].radius = 0.02; + _bodyBall[ BODY_BALL_LEFT_ELBOW ].radius = 0.02; _bodyBall[ BODY_BALL_LEFT_WRIST ].radius = 0.02; _bodyBall[ BODY_BALL_LEFT_FINGERTIPS ].radius = 0.01; _bodyBall[ BODY_BALL_RIGHT_COLLAR ].radius = 0.04; - _bodyBall[ BODY_BALL_RIGHT_SHOULDER ].radius = 0.03; - _bodyBall[ BODY_BALL_RIGHT_ELBOW ].radius = 0.02; + _bodyBall[ BODY_BALL_RIGHT_SHOULDER ].radius = 0.03; + _bodyBall[ BODY_BALL_RIGHT_ELBOW ].radius = 0.02; _bodyBall[ BODY_BALL_RIGHT_WRIST ].radius = 0.02; _bodyBall[ BODY_BALL_RIGHT_FINGERTIPS ].radius = 0.01; - _bodyBall[ BODY_BALL_LEFT_HIP ].radius = 0.04; + _bodyBall[ BODY_BALL_LEFT_HIP ].radius = 0.04; //_bodyBall[ BODY_BALL_LEFT_MID_THIGH ].radius = 0.03; - _bodyBall[ BODY_BALL_LEFT_KNEE ].radius = 0.025; - _bodyBall[ BODY_BALL_LEFT_HEEL ].radius = 0.025; - _bodyBall[ BODY_BALL_LEFT_TOES ].radius = 0.025; - _bodyBall[ BODY_BALL_RIGHT_HIP ].radius = 0.04; - _bodyBall[ BODY_BALL_RIGHT_KNEE ].radius = 0.025; - _bodyBall[ BODY_BALL_RIGHT_HEEL ].radius = 0.025; - _bodyBall[ BODY_BALL_RIGHT_TOES ].radius = 0.025; + _bodyBall[ BODY_BALL_LEFT_KNEE ].radius = 0.025; + _bodyBall[ BODY_BALL_LEFT_HEEL ].radius = 0.025; + _bodyBall[ BODY_BALL_LEFT_TOES ].radius = 0.025; + _bodyBall[ BODY_BALL_RIGHT_HIP ].radius = 0.04; + _bodyBall[ BODY_BALL_RIGHT_KNEE ].radius = 0.025; + _bodyBall[ BODY_BALL_RIGHT_HEEL ].radius = 0.025; + _bodyBall[ BODY_BALL_RIGHT_TOES ].radius = 0.025; // specify the parent joint for each ball - _bodyBall[ BODY_BALL_PELVIS ].parentJoint = AVATAR_JOINT_PELVIS; + _bodyBall[ BODY_BALL_PELVIS ].parentJoint = AVATAR_JOINT_PELVIS; _bodyBall[ BODY_BALL_TORSO ].parentJoint = AVATAR_JOINT_TORSO; - _bodyBall[ BODY_BALL_CHEST ].parentJoint = AVATAR_JOINT_CHEST; - _bodyBall[ BODY_BALL_NECK_BASE ].parentJoint = AVATAR_JOINT_NECK_BASE; + _bodyBall[ BODY_BALL_CHEST ].parentJoint = AVATAR_JOINT_CHEST; + _bodyBall[ BODY_BALL_NECK_BASE ].parentJoint = AVATAR_JOINT_NECK_BASE; _bodyBall[ BODY_BALL_HEAD_BASE ].parentJoint = AVATAR_JOINT_HEAD_BASE; _bodyBall[ BODY_BALL_HEAD_TOP ].parentJoint = AVATAR_JOINT_HEAD_TOP; _bodyBall[ BODY_BALL_LEFT_COLLAR ].parentJoint = AVATAR_JOINT_LEFT_COLLAR; _bodyBall[ BODY_BALL_LEFT_SHOULDER ].parentJoint = AVATAR_JOINT_LEFT_SHOULDER; - _bodyBall[ BODY_BALL_LEFT_ELBOW ].parentJoint = AVATAR_JOINT_LEFT_ELBOW; - _bodyBall[ BODY_BALL_LEFT_WRIST ].parentJoint = AVATAR_JOINT_LEFT_WRIST; + _bodyBall[ BODY_BALL_LEFT_ELBOW ].parentJoint = AVATAR_JOINT_LEFT_ELBOW; + _bodyBall[ BODY_BALL_LEFT_WRIST ].parentJoint = AVATAR_JOINT_LEFT_WRIST; _bodyBall[ BODY_BALL_LEFT_FINGERTIPS ].parentJoint = AVATAR_JOINT_LEFT_FINGERTIPS; _bodyBall[ BODY_BALL_RIGHT_COLLAR ].parentJoint = AVATAR_JOINT_RIGHT_COLLAR; - _bodyBall[ BODY_BALL_RIGHT_SHOULDER ].parentJoint = AVATAR_JOINT_RIGHT_SHOULDER; - _bodyBall[ BODY_BALL_RIGHT_ELBOW ].parentJoint = AVATAR_JOINT_RIGHT_ELBOW; + _bodyBall[ BODY_BALL_RIGHT_SHOULDER ].parentJoint = AVATAR_JOINT_RIGHT_SHOULDER; + _bodyBall[ BODY_BALL_RIGHT_ELBOW ].parentJoint = AVATAR_JOINT_RIGHT_ELBOW; _bodyBall[ BODY_BALL_RIGHT_WRIST ].parentJoint = AVATAR_JOINT_RIGHT_WRIST; _bodyBall[ BODY_BALL_RIGHT_FINGERTIPS ].parentJoint = AVATAR_JOINT_RIGHT_FINGERTIPS; - _bodyBall[ BODY_BALL_LEFT_HIP ].parentJoint = AVATAR_JOINT_LEFT_HIP; - _bodyBall[ BODY_BALL_LEFT_KNEE ].parentJoint = AVATAR_JOINT_LEFT_KNEE; - _bodyBall[ BODY_BALL_LEFT_HEEL ].parentJoint = AVATAR_JOINT_LEFT_HEEL; - _bodyBall[ BODY_BALL_LEFT_TOES ].parentJoint = AVATAR_JOINT_LEFT_TOES; - _bodyBall[ BODY_BALL_RIGHT_HIP ].parentJoint = AVATAR_JOINT_RIGHT_HIP; - _bodyBall[ BODY_BALL_RIGHT_KNEE ].parentJoint = AVATAR_JOINT_RIGHT_KNEE; - _bodyBall[ BODY_BALL_RIGHT_HEEL ].parentJoint = AVATAR_JOINT_RIGHT_HEEL; - _bodyBall[ BODY_BALL_RIGHT_TOES ].parentJoint = AVATAR_JOINT_RIGHT_TOES; + _bodyBall[ BODY_BALL_LEFT_HIP ].parentJoint = AVATAR_JOINT_LEFT_HIP; + _bodyBall[ BODY_BALL_LEFT_KNEE ].parentJoint = AVATAR_JOINT_LEFT_KNEE; + _bodyBall[ BODY_BALL_LEFT_HEEL ].parentJoint = AVATAR_JOINT_LEFT_HEEL; + _bodyBall[ BODY_BALL_LEFT_TOES ].parentJoint = AVATAR_JOINT_LEFT_TOES; + _bodyBall[ BODY_BALL_RIGHT_HIP ].parentJoint = AVATAR_JOINT_RIGHT_HIP; + _bodyBall[ BODY_BALL_RIGHT_KNEE ].parentJoint = AVATAR_JOINT_RIGHT_KNEE; + _bodyBall[ BODY_BALL_RIGHT_HEEL ].parentJoint = AVATAR_JOINT_RIGHT_HEEL; + _bodyBall[ BODY_BALL_RIGHT_TOES ].parentJoint = AVATAR_JOINT_RIGHT_TOES; - //_bodyBall[ BODY_BALL_LEFT_MID_THIGH].parentJoint = AVATAR_JOINT_LEFT_HIP; + //_bodyBall[ BODY_BALL_LEFT_MID_THIGH ].parentJoint = AVATAR_JOINT_LEFT_HIP; // specify the parent offset for each ball - _bodyBall[ BODY_BALL_PELVIS ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_PELVIS ].parentOffset = glm::vec3(0.0, 0.0, 0.0); _bodyBall[ BODY_BALL_TORSO ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_CHEST ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_NECK_BASE ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_CHEST ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_NECK_BASE ].parentOffset = glm::vec3(0.0, 0.0, 0.0); _bodyBall[ BODY_BALL_HEAD_BASE ].parentOffset = glm::vec3(0.0, 0.0, 0.0); _bodyBall[ BODY_BALL_HEAD_TOP ].parentOffset = glm::vec3(0.0, 0.0, 0.0); _bodyBall[ BODY_BALL_LEFT_COLLAR ].parentOffset = glm::vec3(0.0, 0.0, 0.0); _bodyBall[ BODY_BALL_LEFT_SHOULDER ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_LEFT_ELBOW ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_LEFT_WRIST ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_LEFT_ELBOW ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_LEFT_WRIST ].parentOffset = glm::vec3(0.0, 0.0, 0.0); _bodyBall[ BODY_BALL_LEFT_FINGERTIPS ].parentOffset = glm::vec3(0.0, 0.0, 0.0); _bodyBall[ BODY_BALL_RIGHT_COLLAR ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_RIGHT_SHOULDER ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_RIGHT_ELBOW ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_RIGHT_SHOULDER ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_RIGHT_ELBOW ].parentOffset = glm::vec3(0.0, 0.0, 0.0); _bodyBall[ BODY_BALL_RIGHT_WRIST ].parentOffset = glm::vec3(0.0, 0.0, 0.0); _bodyBall[ BODY_BALL_RIGHT_FINGERTIPS ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_LEFT_HIP ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_LEFT_KNEE ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_LEFT_HEEL ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_LEFT_TOES ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_RIGHT_HIP ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_RIGHT_KNEE ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_RIGHT_HEEL ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - _bodyBall[ BODY_BALL_RIGHT_TOES ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_LEFT_HIP ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_LEFT_KNEE ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_LEFT_HEEL ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_LEFT_TOES ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_RIGHT_HIP ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_RIGHT_KNEE ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_RIGHT_HEEL ].parentOffset = glm::vec3(0.0, 0.0, 0.0); + _bodyBall[ BODY_BALL_RIGHT_TOES ].parentOffset = glm::vec3(0.0, 0.0, 0.0); - //_bodyBall[ BODY_BALL_LEFT_MID_THIGH].parentOffset = glm::vec3(-0.1, -0.1, 0.0); + //_bodyBall[ BODY_BALL_LEFT_MID_THIGH ].parentOffset = glm::vec3(-0.1, -0.1, 0.0); // specify the parent BALL for each ball - _bodyBall[ BODY_BALL_PELVIS ].parentBall = BODY_BALL_NULL; + _bodyBall[ BODY_BALL_PELVIS ].parentBall = BODY_BALL_NULL; _bodyBall[ BODY_BALL_TORSO ].parentBall = BODY_BALL_PELVIS; - _bodyBall[ BODY_BALL_CHEST ].parentBall = BODY_BALL_TORSO; - _bodyBall[ BODY_BALL_NECK_BASE ].parentBall = BODY_BALL_CHEST; + _bodyBall[ BODY_BALL_CHEST ].parentBall = BODY_BALL_TORSO; + _bodyBall[ BODY_BALL_NECK_BASE ].parentBall = BODY_BALL_CHEST; _bodyBall[ BODY_BALL_HEAD_BASE ].parentBall = BODY_BALL_NECK_BASE; _bodyBall[ BODY_BALL_HEAD_TOP ].parentBall = BODY_BALL_HEAD_BASE; _bodyBall[ BODY_BALL_LEFT_COLLAR ].parentBall = BODY_BALL_CHEST; _bodyBall[ BODY_BALL_LEFT_SHOULDER ].parentBall = BODY_BALL_LEFT_COLLAR; - _bodyBall[ BODY_BALL_LEFT_ELBOW ].parentBall = BODY_BALL_LEFT_SHOULDER; - _bodyBall[ BODY_BALL_LEFT_WRIST ].parentBall = BODY_BALL_LEFT_ELBOW; + _bodyBall[ BODY_BALL_LEFT_ELBOW ].parentBall = BODY_BALL_LEFT_SHOULDER; + _bodyBall[ BODY_BALL_LEFT_WRIST ].parentBall = BODY_BALL_LEFT_ELBOW; _bodyBall[ BODY_BALL_LEFT_FINGERTIPS ].parentBall = BODY_BALL_LEFT_WRIST; _bodyBall[ BODY_BALL_RIGHT_COLLAR ].parentBall = BODY_BALL_CHEST; - _bodyBall[ BODY_BALL_RIGHT_SHOULDER ].parentBall = BODY_BALL_RIGHT_COLLAR; - _bodyBall[ BODY_BALL_RIGHT_ELBOW ].parentBall = BODY_BALL_RIGHT_SHOULDER; + _bodyBall[ BODY_BALL_RIGHT_SHOULDER ].parentBall = BODY_BALL_RIGHT_COLLAR; + _bodyBall[ BODY_BALL_RIGHT_ELBOW ].parentBall = BODY_BALL_RIGHT_SHOULDER; _bodyBall[ BODY_BALL_RIGHT_WRIST ].parentBall = BODY_BALL_RIGHT_ELBOW; _bodyBall[ BODY_BALL_RIGHT_FINGERTIPS ].parentBall = BODY_BALL_RIGHT_WRIST; - _bodyBall[ BODY_BALL_LEFT_HIP ].parentBall = BODY_BALL_PELVIS; + _bodyBall[ BODY_BALL_LEFT_HIP ].parentBall = BODY_BALL_PELVIS; - //_bodyBall[ BODY_BALL_LEFT_MID_THIGH ].parentBall = BODY_BALL_LEFT_HIP; + //_bodyBall[ BODY_BALL_LEFT_MID_THIGH ].parentBall = BODY_BALL_LEFT_HIP; - // _bodyBall[ BODY_BALL_LEFT_KNEE ].parentBall = BODY_BALL_LEFT_MID_THIGH; - _bodyBall[ BODY_BALL_LEFT_KNEE ].parentBall = BODY_BALL_LEFT_HIP; + //_bodyBall[ BODY_BALL_LEFT_KNEE ].parentBall = BODY_BALL_LEFT_MID_THIGH; + _bodyBall[ BODY_BALL_LEFT_KNEE ].parentBall = BODY_BALL_LEFT_HIP; - _bodyBall[ BODY_BALL_LEFT_HEEL ].parentBall = BODY_BALL_LEFT_KNEE; - _bodyBall[ BODY_BALL_LEFT_TOES ].parentBall = BODY_BALL_LEFT_HEEL; - _bodyBall[ BODY_BALL_RIGHT_HIP ].parentBall = BODY_BALL_PELVIS; - _bodyBall[ BODY_BALL_RIGHT_KNEE ].parentBall = BODY_BALL_RIGHT_HIP; - _bodyBall[ BODY_BALL_RIGHT_HEEL ].parentBall = BODY_BALL_RIGHT_KNEE; - _bodyBall[ BODY_BALL_RIGHT_TOES ].parentBall = BODY_BALL_RIGHT_HEEL; + _bodyBall[ BODY_BALL_LEFT_HEEL ].parentBall = BODY_BALL_LEFT_KNEE; + _bodyBall[ BODY_BALL_LEFT_TOES ].parentBall = BODY_BALL_LEFT_HEEL; + _bodyBall[ BODY_BALL_RIGHT_HIP ].parentBall = BODY_BALL_PELVIS; + _bodyBall[ BODY_BALL_RIGHT_KNEE ].parentBall = BODY_BALL_RIGHT_HIP; + _bodyBall[ BODY_BALL_RIGHT_HEEL ].parentBall = BODY_BALL_RIGHT_KNEE; + _bodyBall[ BODY_BALL_RIGHT_TOES ].parentBall = BODY_BALL_RIGHT_HEEL; /* // to aid in hand-shaking and hand-holding, the right hand is not collidable - _bodyBall[ BODY_BALL_RIGHT_ELBOW ].isCollidable = false; - _bodyBall[ BODY_BALL_RIGHT_WRIST ].isCollidable = false; - _bodyBall[ BODY_BALL_RIGHT_FINGERTIPS].isCollidable = false; + _bodyBall[ BODY_BALL_RIGHT_ELBOW ].isCollidable = false; + _bodyBall[ BODY_BALL_RIGHT_WRIST ].isCollidable = false; + _bodyBall[ BODY_BALL_RIGHT_FINGERTIPS ].isCollidable = false; */ } @@ -285,24 +285,37 @@ void Avatar::reset() { } // Update avatar head rotation with sensor data -void Avatar::updateHeadFromGyrosAndOrWebcam(bool gyroLook, const glm::vec3& amplifyAngle) { - +void Avatar::updateFromGyrosAndOrWebcam(bool gyroLook, const glm::vec3& amplifyAngle) { SerialInterface* gyros = Application::getInstance()->getSerialHeadSensor(); Webcam* webcam = Application::getInstance()->getWebcam(); glm::vec3 estimatedPosition, estimatedRotation; if (gyros->isActive()) { - if (webcam->isActive()) { - estimatedPosition = webcam->getEstimatedPosition(); - } estimatedRotation = gyros->getEstimatedRotation(); - + } else if (webcam->isActive()) { - estimatedPosition = webcam->getEstimatedPosition(); estimatedRotation = webcam->getEstimatedRotation(); } else { return; } + if (webcam->isActive()) { + estimatedPosition = webcam->getEstimatedPosition(); + + // compute and store the joint rotations + const JointVector& joints = webcam->getEstimatedJoints(); + _joints.clear(); + for (int i = 0; i < NUM_AVATAR_JOINTS; i++) { + if (joints.size() > i && joints[i].isValid) { + JointData data = { i, joints[i].rotation }; + _joints.push_back(data); + + if (i == AVATAR_JOINT_CHEST) { + // if we have a chest rotation, don't apply lean based on head + estimatedPosition = glm::vec3(); + } + } + } + } _head.setPitch(estimatedRotation.x * amplifyAngle.x); _head.setYaw(estimatedRotation.y * amplifyAngle.y); _head.setRoll(estimatedRotation.z * amplifyAngle.z); @@ -485,9 +498,18 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { _skeleton.joint[AVATAR_JOINT_TORSO].rotation = glm::quat(glm::radians(glm::vec3( _head.getLeanForward(), 0.0f, _head.getLeanSideways()))); - // update avatar skeleton - _skeleton.update(deltaTime, getOrientation(), _position); + // apply joint data (if any) to skeleton + bool enableHandMovement = true; + for (vector::iterator it = _joints.begin(); it != _joints.end(); it++) { + _skeleton.joint[it->jointID].rotation = it->rotation; + + // disable hand movement if we have joint info for the right wrist + enableHandMovement &= (it->jointID != AVATAR_JOINT_RIGHT_WRIST); + } + // update avatar skeleton + _skeleton.update(deltaTime, getOrientation(), _position); + //determine the lengths of the body springs now that we have updated the skeleton at least once if (!_ballSpringsInitialized) { for (int b = 0; b < NUM_AVATAR_BODY_BALLS; b++) { @@ -518,7 +540,7 @@ void Avatar::simulate(float deltaTime, Transmitter* transmitter) { } //update the movement of the hand and process handshaking with other avatars... - updateHandMovementAndTouching(deltaTime); + updateHandMovementAndTouching(deltaTime, enableHandMovement); _avatarTouch.simulate(deltaTime); // apply gravity and collision with the ground/floor @@ -698,7 +720,7 @@ void Avatar::setOrientation(const glm::quat& orientation) { _bodyRoll = eulerAngles.z; } -void Avatar::updateHandMovementAndTouching(float deltaTime) { +void Avatar::updateHandMovementAndTouching(float deltaTime, bool enableHandMovement) { glm::quat orientation = getOrientation(); @@ -707,12 +729,14 @@ void Avatar::updateHandMovementAndTouching(float deltaTime) { glm::vec3 up = orientation * IDENTITY_UP; glm::vec3 front = orientation * IDENTITY_FRONT; - glm::vec3 transformedHandMovement - = right * _movedHandOffset.x * 2.0f - + up * -_movedHandOffset.y * 2.0f - + front * -_movedHandOffset.y * 2.0f; + if (enableHandMovement) { + glm::vec3 transformedHandMovement = + right * _movedHandOffset.x * 2.0f + + up * -_movedHandOffset.y * 2.0f + + front * -_movedHandOffset.y * 2.0f; - _skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position += transformedHandMovement; + _skeleton.joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].position += transformedHandMovement; + } if (isMyAvatar()) { _avatarTouch.setMyBodyPosition(_position); @@ -803,7 +827,9 @@ void Avatar::updateHandMovementAndTouching(float deltaTime) { //constrain right arm length and re-adjust elbow position as it bends // NOTE - the following must be called on all avatars - not just _isMine - updateArmIKAndConstraints(deltaTime); + if (enableHandMovement) { + updateArmIKAndConstraints(deltaTime); + } //Set right hand position and state to be transmitted, and also tell AvatarTouch about it if (isMyAvatar()) { diff --git a/interface/src/Avatar.h b/interface/src/Avatar.h index 8b6c79bd7c..654cf4b8fe 100755 --- a/interface/src/Avatar.h +++ b/interface/src/Avatar.h @@ -87,7 +87,7 @@ public: void reset(); void simulate(float deltaTime, Transmitter* transmitter); void updateThrust(float deltaTime, Transmitter * transmitter); - void updateHeadFromGyrosAndOrWebcam(bool gyroLook, const glm::vec3& amplifyAngles); + void updateFromGyrosAndOrWebcam(bool gyroLook, const glm::vec3& amplifyAngles); void updateFromMouse(int mouseX, int mouseY, int screenWidth, int screenHeight); void updateFromTouch(float touchAvgDistX, float touchAvgDistY); void addBodyYaw(float y) {_bodyYaw += y;}; @@ -151,10 +151,6 @@ public: // 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(); static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2); @@ -229,7 +225,7 @@ private: void updateBodyBalls( float deltaTime ); void calculateBoneLengths(); void readSensors(); - void updateHandMovementAndTouching(float deltaTime); + void updateHandMovementAndTouching(float deltaTime, bool enableHandMovement); void updateAvatarCollisions(float deltaTime); void updateArmIKAndConstraints( float deltaTime ); void updateCollisionWithSphere( glm::vec3 position, float radius, float deltaTime ); diff --git a/interface/src/BandwidthMeter.cpp b/interface/src/BandwidthMeter.cpp index ba89864807..5e0d63d6c5 100644 --- a/interface/src/BandwidthMeter.cpp +++ b/interface/src/BandwidthMeter.cpp @@ -68,6 +68,12 @@ void BandwidthMeter::Stream::updateValue(double amount) { timeval now; gettimeofday(& now, NULL); double dt = diffclock(& _prevTime, & now); + + // Ignore this value when timer imprecision yields dt = 0 + if (dt == 0.0) { + return; + } + memcpy(& _prevTime, & now, sizeof(timeval)); // Compute approximate average @@ -225,6 +231,13 @@ void BandwidthMeter::render(int screenWidth, int screenHeight) { textYlowerLine, fmtBuf); glPopMatrix(); + + // After rendering, indicate that no data has been sent/received since the last feed. + // This way, the meters fall when not continuously fed. + for (int i = 0; i < N_CHANNELS; ++i) { + inputStream(ChannelIndex(i)).updateValue(0); + outputStream(ChannelIndex(i)).updateValue(0); + } } diff --git a/interface/src/Head.cpp b/interface/src/Head.cpp index 6a6643352a..6bf6c65341 100644 --- a/interface/src/Head.cpp +++ b/interface/src/Head.cpp @@ -189,12 +189,13 @@ void Head::simulate(float deltaTime, bool isMine) { const float FULLY_CLOSED = 1.0f; if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) { // no blinking when brows are raised; blink less with increasing loudness - const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.75f; - if (forceBlink || (_browAudioLift < EPSILON && shouldDo( - sqrtf(_averageLoudness) * ROOT_LOUDNESS_TO_BLINK_INTERVAL, deltaTime))) { + const float BASE_BLINK_RATE = 15.0f / 60.0f; + const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; + if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(_averageLoudness) * + ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { _leftEyeBlinkVelocity = BLINK_SPEED; _rightEyeBlinkVelocity = BLINK_SPEED; - } + } } else { _leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); _rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); @@ -222,11 +223,11 @@ void Head::simulate(float deltaTime, bool isMine) { if (isMine && _cameraFollowsHead) { // If we are using gyros and using gyroLook, have the camera follow head but with a null region // to create stable rendering view with small head movements. - const float CAMERA_FOLLOW_HEAD_RATE_START = 0.05f; - const float CAMERA_FOLLOW_HEAD_RATE_MAX = 0.25f; - const float CAMERA_FOLLOW_HEAD_RATE_RAMP_RATE = 1.5f; - const float CAMERA_STOP_TOLERANCE_DEGREES = 0.25f; - const float CAMERA_START_TOLERANCE_DEGREES = 15.0f; + const float CAMERA_FOLLOW_HEAD_RATE_START = 0.01f; + const float CAMERA_FOLLOW_HEAD_RATE_MAX = 0.5f; + const float CAMERA_FOLLOW_HEAD_RATE_RAMP_RATE = 1.05f; + const float CAMERA_STOP_TOLERANCE_DEGREES = 0.1f; + const float CAMERA_START_TOLERANCE_DEGREES = 2.0f; float cameraHeadAngleDifference = glm::length(glm::vec2(_pitch - _cameraPitch, _yaw - _cameraYaw)); if (_isCameraMoving) { _cameraFollowHeadRate = glm::clamp(_cameraFollowHeadRate * CAMERA_FOLLOW_HEAD_RATE_RAMP_RATE, diff --git a/interface/src/Skeleton.cpp b/interface/src/Skeleton.cpp index 1167194534..0d166abfa1 100644 --- a/interface/src/Skeleton.cpp +++ b/interface/src/Skeleton.cpp @@ -6,6 +6,7 @@ #include "Skeleton.h" #include "Util.h" +#include "world.h" const float BODY_SPRING_DEFAULT_TIGHTNESS = 1000.0f; const float FLOATING_HEIGHT = 0.13f; @@ -18,12 +19,21 @@ void Skeleton::initialize() { for (int b = 0; b < NUM_AVATAR_JOINTS; b++) { joint[b].parent = AVATAR_JOINT_NULL; joint[b].position = glm::vec3(0.0, 0.0, 0.0); - joint[b].defaultPosePosition = glm::vec3(0.0, 0.0, 0.0); joint[b].rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); joint[b].length = 0.0; joint[b].bindRadius = 1.0f / 8; } + // put the arms at the side + joint[AVATAR_JOINT_LEFT_ELBOW].rotation = glm::quat(glm::vec3(0.0f, 0.0f, PIf * 0.5f)); + joint[AVATAR_JOINT_RIGHT_ELBOW].rotation = glm::quat(glm::vec3(0.0f, 0.0f, -PIf * 0.5f)); + + // bend the knees + joint[AVATAR_JOINT_LEFT_KNEE].rotation = joint[AVATAR_JOINT_RIGHT_KNEE].rotation = + glm::quat(glm::vec3(PIf / 8.0f, 0.0f, 0.0f)); + joint[AVATAR_JOINT_LEFT_HEEL].rotation = joint[AVATAR_JOINT_RIGHT_HEEL].rotation = + glm::quat(glm::vec3(-PIf / 4.0f, 0.0f, 0.0f)); + // specify the parental hierarchy joint[ AVATAR_JOINT_PELVIS ].parent = AVATAR_JOINT_NULL; joint[ AVATAR_JOINT_TORSO ].parent = AVATAR_JOINT_PELVIS; @@ -80,39 +90,9 @@ void Skeleton::initialize() { joint[ AVATAR_JOINT_RIGHT_HEEL ].bindPosePosition = glm::vec3( 0.00, -0.23, 0.00 ); joint[ AVATAR_JOINT_RIGHT_TOES ].bindPosePosition = glm::vec3( 0.00, 0.00, -0.06 ); - // specify the default pose position - joint[ AVATAR_JOINT_PELVIS ].defaultPosePosition = glm::vec3( 0.0, 0.0, 0.0 ); - joint[ AVATAR_JOINT_TORSO ].defaultPosePosition = glm::vec3( 0.0, 0.09, -0.01 ); - joint[ AVATAR_JOINT_CHEST ].defaultPosePosition = glm::vec3( 0.0, 0.09, -0.01 ); - joint[ AVATAR_JOINT_NECK_BASE ].defaultPosePosition = glm::vec3( 0.0, 0.14, 0.01 ); - joint[ AVATAR_JOINT_HEAD_BASE ].defaultPosePosition = glm::vec3( 0.0, 0.04, 0.00 ); - joint[ AVATAR_JOINT_HEAD_TOP ].defaultPosePosition = glm::vec3( 0.0, 0.04, 0.00 ); - - joint[ AVATAR_JOINT_LEFT_COLLAR ].defaultPosePosition = glm::vec3( -0.06, 0.04, 0.01 ); - joint[ AVATAR_JOINT_LEFT_SHOULDER ].defaultPosePosition = glm::vec3( -0.05, 0.0, 0.01 ); - joint[ AVATAR_JOINT_LEFT_ELBOW ].defaultPosePosition = glm::vec3( 0.0, -0.16, 0.0 ); - joint[ AVATAR_JOINT_LEFT_WRIST ].defaultPosePosition = glm::vec3( 0.0, -0.117, 0.0 ); - joint[ AVATAR_JOINT_LEFT_FINGERTIPS ].defaultPosePosition = glm::vec3( 0.0, -0.1, 0.0 ); - - joint[ AVATAR_JOINT_RIGHT_COLLAR ].defaultPosePosition = glm::vec3( 0.06, 0.04, 0.01 ); - joint[ AVATAR_JOINT_RIGHT_SHOULDER ].defaultPosePosition = glm::vec3( 0.05, 0.0, 0.01 ); - joint[ AVATAR_JOINT_RIGHT_ELBOW ].defaultPosePosition = glm::vec3( 0.0, -0.16, 0.0 ); - joint[ AVATAR_JOINT_RIGHT_WRIST ].defaultPosePosition = glm::vec3( 0.0, -0.117, 0.0 ); - joint[ AVATAR_JOINT_RIGHT_FINGERTIPS ].defaultPosePosition = glm::vec3( 0.0, -0.1, 0.0 ); - - joint[ AVATAR_JOINT_LEFT_HIP ].defaultPosePosition = glm::vec3( -0.05, 0.0, 0.02 ); - joint[ AVATAR_JOINT_LEFT_KNEE ].defaultPosePosition = glm::vec3( 0.01, -0.25, -0.03 ); - joint[ AVATAR_JOINT_LEFT_HEEL ].defaultPosePosition = glm::vec3( 0.01, -0.22, 0.08 ); - joint[ AVATAR_JOINT_LEFT_TOES ].defaultPosePosition = glm::vec3( 0.00, -0.03, -0.05 ); - - joint[ AVATAR_JOINT_RIGHT_HIP ].defaultPosePosition = glm::vec3( 0.05, 0.0, 0.02 ); - joint[ AVATAR_JOINT_RIGHT_KNEE ].defaultPosePosition = glm::vec3( -0.01, -0.25, -0.03 ); - joint[ AVATAR_JOINT_RIGHT_HEEL ].defaultPosePosition = glm::vec3( -0.01, -0.22, 0.08 ); - joint[ AVATAR_JOINT_RIGHT_TOES ].defaultPosePosition = glm::vec3( 0.00, -0.03, -0.05 ); - // calculate bone length, absolute bind positions/rotations for (int b = 0; b < NUM_AVATAR_JOINTS; b++) { - joint[b].length = glm::length(joint[b].defaultPosePosition); + joint[b].length = glm::length(joint[b].bindPosePosition); if (joint[b].parent == AVATAR_JOINT_NULL) { joint[b].absoluteBindPosePosition = joint[b].bindPosePosition; @@ -122,7 +102,7 @@ void Skeleton::initialize() { joint[b].bindPosePosition; glm::vec3 parentDirection = joint[ joint[b].parent ].absoluteBindPoseRotation * JOINT_DIRECTION; joint[b].absoluteBindPoseRotation = rotationBetween(parentDirection, joint[b].bindPosePosition) * - joint[ joint[b].parent ].absoluteBindPoseRotation; + joint[ joint[b].parent ].absoluteBindPoseRotation; } } } @@ -140,7 +120,7 @@ void Skeleton::update(float deltaTime, const glm::quat& orientation, glm::vec3 p joint[b].position = joint[ joint[b].parent ].position; } - glm::vec3 rotatedJointVector = joint[b].absoluteRotation * joint[b].defaultPosePosition; + glm::vec3 rotatedJointVector = joint[b].absoluteRotation * joint[b].bindPosePosition; joint[b].position += rotatedJointVector; } } diff --git a/interface/src/Skeleton.h b/interface/src/Skeleton.h index bb953fe947..8d99a80fa6 100644 --- a/interface/src/Skeleton.h +++ b/interface/src/Skeleton.h @@ -63,7 +63,6 @@ public: { AvatarJointID parent; // which joint is this joint connected to? glm::vec3 position; // the position at the "end" of the joint - in global space - glm::vec3 defaultPosePosition; // the parent relative position when the avatar is in the default pose glm::vec3 bindPosePosition; // the parent relative position when the avatar is in the "T-pose" glm::vec3 absoluteBindPosePosition; // the absolute position when the avatar is in the "T-pose" glm::quat absoluteBindPoseRotation; // the absolute rotation when the avatar is in the "T-pose" diff --git a/interface/src/Swatch.cpp b/interface/src/Swatch.cpp new file mode 100644 index 0000000000..ea8516e9a8 --- /dev/null +++ b/interface/src/Swatch.cpp @@ -0,0 +1,169 @@ +#include "Swatch.h" +#include + +Swatch::Swatch(QAction* action) : + Tool(action, 0, -1, -1), + _textRenderer(MONO_FONT_FAMILY, 10, 100), + _selected(1) { +} + +void Swatch::reset() { + for (int i = 0; i < 8; ++i) { + _colors[i].setRgb(colorBase[i][0], + colorBase[i][1], + colorBase[i][2]); + } +} + +QColor Swatch::getColor() { + return _colors[_selected - 1]; +} + +void Swatch::checkColor() { + if (_action->data().value() == _colors[_selected - 1]) { + return; + } + + QPixmap map(16, 16); + map.fill(_colors[_selected - 1]); + _action->setData(_colors[_selected - 1]) ; + _action->setIcon(map); +} + +void Swatch::saveData(QSettings* settings) { + settings->beginGroup("Swatch"); + + for (int i(0); i < SWATCH_SIZE; ++i) { + QString rx("R1"), gx("G1"), bx("B1"); + rx[1] = '1' + i; + gx[1] = rx[1]; + bx[1] = rx[1]; + settings->setValue(rx, _colors[i].red()); + settings->setValue(gx, _colors[i].green()); + settings->setValue(bx, _colors[i].blue()); + } + + settings->endGroup(); +} + +void Swatch::loadData(QSettings* settings) { + settings->beginGroup("Swatch"); + + for (int i = 0; i < SWATCH_SIZE; ++i) { + QString rx("R1"), gx("G1"), bx("B1"); + rx[1] = '1' + i; + gx[1] = rx[1]; + bx[1] = rx[1]; + _colors[i].setRgb(settings->value(rx, colorBase[i][0]).toInt(), + settings->value(gx, colorBase[i][1]).toInt(), + settings->value(bx, colorBase[i][2]).toInt()); + } + + settings->endGroup(); + + checkColor(); +} + +void Swatch::handleEvent(int key, bool getColor) { + int next(0); + + switch (key) { + case Qt::Key_1: + next = 1; + break; + case Qt::Key_2: + next = 2; + break; + case Qt::Key_3: + next = 3; + break; + case Qt::Key_4: + next = 4; + break; + case Qt::Key_5: + next = 5; + break; + case Qt::Key_6: + next = 6; + break; + case Qt::Key_7: + next = 7; + break; + case Qt::Key_8: + next = 8; + break; + default: + break; + } + + if (getColor) { + if (_action->data().value() != _colors[_selected - 1]) { + _selected = next; + _colors[_selected - 1] = _action->data().value(); + } + } else { + _selected = next; + QPixmap map(16, 16); + map.fill(_colors[_selected - 1]); + _action->setData(_colors[_selected - 1]) ; + _action->setIcon(map); + } +} + +void Swatch::render(int width, int height) { + char str[2]; + int margin = 0.10f * height; + height = 0.75f * height; + + glBegin(GL_QUADS); + glColor3f(0.0f, 0.0f, 0.0f); + glVertex2f(0, 8 * (height - margin) + margin); + glVertex2f(width, 8 * (height - margin) + margin); + glVertex2f(width, 0); + glVertex2f(0, 0); + glEnd(); + + for (unsigned int i = 0; i < SWATCH_SIZE; ++i) { + glBegin(GL_QUADS); + glColor3f(_colors[i].redF(), + _colors[i].greenF(), + _colors[i].blueF()); + glVertex2f(margin , (i + 1) * (height - margin)); + glVertex2f(width - margin, (i + 1) * (height - margin)); + glVertex2f(width - margin, i * (height - margin) + margin); + glVertex2f(margin , i * (height - margin) + margin); + glEnd(); + + if (_colors[i].lightness() < 50) { + glBegin(GL_LINES); + glColor3f(1.0f, 1.0f, 1.0f); + glVertex2f(margin , (i + 1) * (height - margin)); + glVertex2f(width - margin, (i + 1) * (height - margin)); + + glVertex2f(width - margin, (i + 1) * (height - margin)); + glVertex2f(width - margin, i * (height - margin) + margin); + + glVertex2f(width - margin, i * (height - margin) + margin); + glVertex2f(margin , i * (height - margin) + margin); + + glVertex2f(margin , i * (height - margin) + margin); + glVertex2f(margin , (i + 1) * (height - margin)); + glEnd(); + } else { + glColor3f(0.0f, 0.0f, 0.0f); + } + + if (_selected == i + 1) { + glBegin(GL_TRIANGLES); + glVertex2f(margin , (i + 1) * (height - margin) - margin); + glVertex2f(width/4 - margin, i * (height - margin) + height / 2.0f); + glVertex2f(margin , i * (height - margin) + margin + margin); + glEnd(); + } + + sprintf(str, "%d", i + 1); + _textRenderer.draw(3 * width/4, (i + 1) * (height - margin) - 0.2f * height, str); + } + + glTranslated(0, 8 * (height - margin) + margin + 0.075f * height, 0); +} diff --git a/interface/src/Swatch.h b/interface/src/Swatch.h new file mode 100644 index 0000000000..e167cd05c4 --- /dev/null +++ b/interface/src/Swatch.h @@ -0,0 +1,43 @@ +// +// Swatch.h +// interface +// +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__Swatch__ +#define __interface__Swatch__ + +#include "Tool.h" +#include "ui/TextRenderer.h" + +static const int SWATCH_SIZE = 8; +static const int colorBase[8][3] = {{237, 175, 0}, + {61, 211, 72}, + {51, 204, 204}, + {63, 169, 245}, + {193, 99, 122}, + {255, 54, 69}, + {124, 36, 36}, + {63, 35, 19}}; + +class Swatch : public Tool { +public: + Swatch(QAction* action); + + QColor getColor(); + void checkColor(); + void saveData(QSettings* settings); + void loadData(QSettings* settings); + void reset(); + + void render(int width, int height); + void handleEvent(int key, bool getColor); + +private: + TextRenderer _textRenderer; + QColor _colors[SWATCH_SIZE]; + int _selected; +}; + +#endif /* defined(__interface__Swatch__) */ diff --git a/interface/src/Tool.cpp b/interface/src/Tool.cpp new file mode 100644 index 0000000000..ccb42381dd --- /dev/null +++ b/interface/src/Tool.cpp @@ -0,0 +1,51 @@ +#include "Tool.h" + +#include +#include +#include + +Tool::Tool(QAction *action, GLuint texture, int x, int y) : + _action(action), + _texture(texture), + _x(x), + _y(y) { +} + +void Tool::setAction(QAction* action) { + _action = action; +} + +bool Tool::isActive() { + return _action->isChecked(); +} + +void Tool::render(int width, int height) { + glEnable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, _texture); + + + if (_action == 0 || _action->isChecked()) { + glColor3f(1.0f, 1.0f, 1.0f); // reset gl color + } else { + glColor3f(0.3f, 0.3f, 0.3f); + } + + glBegin(GL_QUADS); + glTexCoord2f( _x / NUM_TOOLS_COLS, 1.0f - (_y + 1) / NUM_TOOLS_ROWS); + glVertex2f(0 , height); + + glTexCoord2f((_x + 1) / NUM_TOOLS_COLS, 1.0f - (_y + 1) / NUM_TOOLS_ROWS); + glVertex2f(width, height); + + glTexCoord2f((_x + 1) / NUM_TOOLS_COLS, 1.0f - _y / NUM_TOOLS_ROWS); + glVertex2f(width, 0); + + glTexCoord2f( _x / NUM_TOOLS_COLS, 1.0f - _y / NUM_TOOLS_ROWS); + glVertex2f(0 , 0); + glEnd(); + + glDisable(GL_TEXTURE_2D); + + glTranslated(0, 1.10f * height, 0); +} diff --git a/interface/src/Tool.h b/interface/src/Tool.h new file mode 100644 index 0000000000..f0e1bae4b0 --- /dev/null +++ b/interface/src/Tool.h @@ -0,0 +1,55 @@ +// +// Tool.h +// interface +// +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__Tool__ +#define __interface__Tool__ + +#include + +#include "InterfaceConfig.h" +#include "Util.h" + +class QAction; + + +// Number of rows and columns in the SVG file for the tool palette +static const int NUM_TOOLS_ROWS = 10; +static const int NUM_TOOLS_COLS = 2; +static const int SWATCHS_TOOLS_COUNT = 13; // 8 swatch + 5 tools + +static const int WIDTH_MIN = 47; // Minimal tools width +static const float TOOLS_RATIO = 40.0f / 60.0f; // ratio height/width of tools icons +static const float PAL_SCREEN_RATIO = 3.0f / 100.0f; // Percentage of the screeen width the palette is going to occupy + +// Swatch popup consts +static const float POPUP_STEP = 0.05f; +static const float POPUP_MARGIN = 10.0f; +static const int POPUP_WIDTH = 280; +static const int POPUP_HEIGHT = 30; +static const int SWATCH_WIDTH = 64; +static const int SWATCH_HEIGHT = 30; +static const int FIRST_LINE_OFFSET = 12; +static const int SECOND_LINE_OFFSET = 28; + +class Tool { +public: + Tool(QAction* action, GLuint texture, int x, int y); + + void setAction(QAction* action); + bool isActive(); + virtual void render(int width, int height); + +protected: + QAction* _action; + GLuint _texture; + + // position in the SVG grid + double _x; + double _y; +}; + +#endif /* defined(__interface__Tool__) */ diff --git a/interface/src/ToolsPalette.cpp b/interface/src/ToolsPalette.cpp new file mode 100644 index 0000000000..341a128f8a --- /dev/null +++ b/interface/src/ToolsPalette.cpp @@ -0,0 +1,79 @@ +#include "ToolsPalette.h" + +#include +#include +#include +#include + +void ToolsPalette::init(int screenWidth, int screenHeight) { + _width = (PAL_SCREEN_RATIO * screenWidth < WIDTH_MIN) ? WIDTH_MIN : PAL_SCREEN_RATIO * screenWidth; + _height = TOOLS_RATIO * _width; + + _left = screenWidth / 150; + _top = (screenHeight - SWATCHS_TOOLS_COUNT * _height) / 2; + + // Load SVG + switchToResourcesParentIfRequired(); + QSvgRenderer renderer(QString("./resources/images/hifi-interface-tools.svg")); + + // Prepare a QImage with desired characteritisc + QImage image(NUM_TOOLS_COLS * _width, NUM_TOOLS_ROWS * _height, QImage::Format_ARGB32); + + // Get QPainter that paints to the image + QPainter painter(&image); + renderer.render(&painter); + + //get the OpenGL-friendly image + _textureImage = QGLWidget::convertToGLFormat(image); + + glGenTextures(1, &_textureID); + glBindTexture(GL_TEXTURE_2D, _textureID); + + //generate the texture + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + _textureImage.width(), + _textureImage.height(), + 0, GL_RGBA, GL_UNSIGNED_BYTE, + _textureImage.bits()); + + //texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +} + + +void ToolsPalette::addAction(QAction* action, int x, int y) { + Tool* tmp = new Tool(action, _textureID, x, y); + _tools.push_back(tmp); +} + +void ToolsPalette::addTool(Tool* tool) { + _tools.push_back(tool); +} + +void ToolsPalette::render(int screenWidth, int screenHeight) { + _width = (PAL_SCREEN_RATIO * screenWidth < WIDTH_MIN) ? WIDTH_MIN : PAL_SCREEN_RATIO * screenWidth; + _height = TOOLS_RATIO * _width; + + _left = screenWidth / 150; + _top = (screenHeight - SWATCHS_TOOLS_COUNT * _height) / 2; + + glPushMatrix(); + glTranslated(_left, _top, 0); + + bool show = false; + for (unsigned int i = 0; i < _tools.size(); ++i) { + if (_tools[i]->isActive()) { + show = true; + break; + } + } + + if (show) { + for (unsigned int i = 0; i < _tools.size(); ++i) { + _tools[i]->render(_width, _height); + } + } + + glPopMatrix(); +} diff --git a/interface/src/ToolsPalette.h b/interface/src/ToolsPalette.h new file mode 100644 index 0000000000..0b4ee95524 --- /dev/null +++ b/interface/src/ToolsPalette.h @@ -0,0 +1,35 @@ +// +// ToolsPalette.h +// interface +// +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__ToolsPalette__ +#define __interface__ToolsPalette__ + +#include "Tool.h" +#include "Swatch.h" + +#include + +class ToolsPalette { +public: + void init(int screenWidth, int screenHeight); + void addAction(QAction* action, int x, int y); + void addTool(Tool* tool); + void render(int screenWidth, int screenHeight); + +private: + QImage _textureImage; + GLuint _textureID; + std::vector _tools; + + int _top; + int _left; + + int _width; + int _height; +}; + +#endif /* defined(__interface__ToolsPalette__) */ diff --git a/interface/src/Util.h b/interface/src/Util.h index 24cbad9e68..2005b76438 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -25,7 +25,6 @@ // the standard mono font family #define MONO_FONT_FAMILY "Courier" - void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * fwd, glm::vec3 * left, glm::vec3 * up); float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos); diff --git a/interface/src/Webcam.cpp b/interface/src/Webcam.cpp index 1b370f6c4b..6c7006c5f4 100644 --- a/interface/src/Webcam.cpp +++ b/interface/src/Webcam.cpp @@ -21,11 +21,16 @@ using namespace cv; using namespace std; -// register OpenCV matrix type with Qt metatype system +#ifdef HAVE_OPENNI +using namespace xn; +#endif + +// register types with Qt metatype system +int jointVectorMetaType = qRegisterMetaType("JointVector"); int matMetaType = qRegisterMetaType("cv::Mat"); int rotatedRectMetaType = qRegisterMetaType("cv::RotatedRect"); -Webcam::Webcam() : _enabled(false), _active(false), _frameTextureID(0) { +Webcam::Webcam() : _enabled(false), _active(false), _frameTextureID(0), _depthTextureID(0) { // the grabber simply runs as fast as possible _grabber = new FrameGrabber(); _grabber->moveToThread(&_grabberThread); @@ -79,9 +84,45 @@ void Webcam::renderPreview(int screenWidth, int screenHeight) { glTexCoord2f(0, 1); glVertex2f(left, top + PREVIEW_HEIGHT); glEnd(); - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); + if (_depthTextureID != 0) { + glBindTexture(GL_TEXTURE_2D, _depthTextureID); + glBegin(GL_QUADS); + int depthPreviewWidth = _depthWidth * PREVIEW_HEIGHT / _depthHeight; + int depthLeft = screenWidth - depthPreviewWidth - 10; + glTexCoord2f(0, 0); + glVertex2f(depthLeft, top - PREVIEW_HEIGHT); + glTexCoord2f(1, 0); + glVertex2f(depthLeft + depthPreviewWidth, top - PREVIEW_HEIGHT); + glTexCoord2f(1, 1); + glVertex2f(depthLeft + depthPreviewWidth, top); + glTexCoord2f(0, 1); + glVertex2f(depthLeft, top); + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + if (!_joints.isEmpty()) { + glColor3f(1.0f, 0.0f, 0.0f); + glPointSize(4.0f); + glBegin(GL_POINTS); + float projectedScale = PREVIEW_HEIGHT / (float)_depthHeight; + foreach (const Joint& joint, _joints) { + if (joint.isValid) { + glVertex2f(depthLeft + joint.projected.x * projectedScale, + top - PREVIEW_HEIGHT + joint.projected.y * projectedScale); + } + } + glEnd(); + glPointSize(1.0f); + } + } else { + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + } + + glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_LINE_LOOP); Point2f facePoints[4]; _faceRect.points(facePoints); @@ -107,26 +148,45 @@ Webcam::~Webcam() { delete _grabber; } -void Webcam::setFrame(const Mat& frame, const RotatedRect& faceRect) { +void Webcam::setFrame(const Mat& frame, int format, const Mat& depth, const RotatedRect& faceRect, const JointVector& joints) { IplImage image = frame; glPixelStorei(GL_UNPACK_ROW_LENGTH, image.widthStep / 3); if (_frameTextureID == 0) { glGenTextures(1, &_frameTextureID); glBindTexture(GL_TEXTURE_2D, _frameTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _frameWidth = image.width, _frameHeight = image.height, 0, GL_BGR, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _frameWidth = image.width, _frameHeight = image.height, 0, format, GL_UNSIGNED_BYTE, image.imageData); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - printLog("Capturing webcam at %dx%d.\n", _frameWidth, _frameHeight); + printLog("Capturing video at %dx%d.\n", _frameWidth, _frameHeight); } else { glBindTexture(GL_TEXTURE_2D, _frameTextureID); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _frameWidth, _frameHeight, GL_BGR, GL_UNSIGNED_BYTE, image.imageData); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _frameWidth, _frameHeight, format, GL_UNSIGNED_BYTE, image.imageData); + } + + if (!depth.empty()) { + IplImage depthImage = depth; + glPixelStorei(GL_UNPACK_ROW_LENGTH, depthImage.widthStep); + if (_depthTextureID == 0) { + glGenTextures(1, &_depthTextureID); + glBindTexture(GL_TEXTURE_2D, _depthTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, _depthWidth = depthImage.width, _depthHeight = depthImage.height, 0, + GL_LUMINANCE, GL_UNSIGNED_BYTE, depthImage.imageData); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + printLog("Capturing depth at %dx%d.\n", _depthWidth, _depthHeight); + + } else { + glBindTexture(GL_TEXTURE_2D, _depthTextureID); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _depthWidth, _depthHeight, GL_LUMINANCE, + GL_UNSIGNED_BYTE, depthImage.imageData); + } } glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glBindTexture(GL_TEXTURE_2D, 0); - // store our face rect, update our frame count for fps computation + // store our face rect and joints, update our frame count for fps computation _faceRect = faceRect; + _joints = joints; _frameCount++; const int MAX_FPS = 60; @@ -140,33 +200,60 @@ void Webcam::setFrame(const Mat& frame, const RotatedRect& faceRect) { } _lastFrameTimestamp = now; - // roll is just the angle of the face rect (correcting for 180 degree rotations) - float roll = faceRect.angle; - if (roll < -90.0f) { - roll += 180.0f; + // see if we have joint data + if (!_joints.isEmpty()) { + _estimatedJoints.resize(NUM_AVATAR_JOINTS); + glm::vec3 origin; + if (_joints[AVATAR_JOINT_LEFT_HIP].isValid && _joints[AVATAR_JOINT_RIGHT_HIP].isValid) { + origin = glm::mix(_joints[AVATAR_JOINT_LEFT_HIP].position, _joints[AVATAR_JOINT_RIGHT_HIP].position, 0.5f); + + } else if (_joints[AVATAR_JOINT_TORSO].isValid) { + const glm::vec3 TORSO_TO_PELVIS = glm::vec3(0.0f, -0.09f, -0.01f); + origin = _joints[AVATAR_JOINT_TORSO].position + TORSO_TO_PELVIS; + } + for (int i = 0; i < NUM_AVATAR_JOINTS; i++) { + if (!_joints[i].isValid) { + continue; + } + const float JOINT_SMOOTHING = 0.9f; + _estimatedJoints[i].isValid = true; + _estimatedJoints[i].position = glm::mix(_joints[i].position - origin, + _estimatedJoints[i].position, JOINT_SMOOTHING); + _estimatedJoints[i].rotation = safeMix(_joints[i].rotation, + _estimatedJoints[i].rotation, JOINT_SMOOTHING); + } + _estimatedRotation = safeEulerAngles(_estimatedJoints[AVATAR_JOINT_HEAD_BASE].rotation); + _estimatedPosition = _estimatedJoints[AVATAR_JOINT_HEAD_BASE].position; - } else if (roll > 90.0f) { - roll -= 180.0f; - } - const float ROTATION_SMOOTHING = 0.95f; - _estimatedRotation.z = glm::mix(roll, _estimatedRotation.z, ROTATION_SMOOTHING); - - // determine position based on translation and scaling of the face rect - if (_initialFaceRect.size.area() == 0) { - _initialFaceRect = faceRect; - _estimatedPosition = glm::vec3(); - } else { - float proportion = sqrtf(_initialFaceRect.size.area() / (float)faceRect.size.area()); - const float DISTANCE_TO_CAMERA = 0.333f; - const float POSITION_SCALE = 0.5f; - float z = DISTANCE_TO_CAMERA * proportion - DISTANCE_TO_CAMERA; - glm::vec3 position = glm::vec3( - (faceRect.center.x - _initialFaceRect.center.x) * proportion * POSITION_SCALE / _frameWidth, - (faceRect.center.y - _initialFaceRect.center.y) * proportion * POSITION_SCALE / _frameWidth, - z); - const float POSITION_SMOOTHING = 0.95f; - _estimatedPosition = glm::mix(position, _estimatedPosition, POSITION_SMOOTHING); + // roll is just the angle of the face rect (correcting for 180 degree rotations) + float roll = faceRect.angle; + if (roll < -90.0f) { + roll += 180.0f; + + } else if (roll > 90.0f) { + roll -= 180.0f; + } + const float ROTATION_SMOOTHING = 0.95f; + _estimatedRotation.z = glm::mix(roll, _estimatedRotation.z, ROTATION_SMOOTHING); + + // determine position based on translation and scaling of the face rect + if (_initialFaceRect.size.area() == 0) { + _initialFaceRect = faceRect; + _estimatedPosition = glm::vec3(); + + } else { + float proportion = sqrtf(_initialFaceRect.size.area() / (float)faceRect.size.area()); + const float DISTANCE_TO_CAMERA = 0.333f; + const float POSITION_SCALE = 0.5f; + float z = DISTANCE_TO_CAMERA * proportion - DISTANCE_TO_CAMERA; + glm::vec3 position = glm::vec3( + (faceRect.center.x - _initialFaceRect.center.x) * proportion * POSITION_SCALE / _frameWidth, + (faceRect.center.y - _initialFaceRect.center.y) * proportion * POSITION_SCALE / _frameWidth, + z); + const float POSITION_SMOOTHING = 0.95f; + _estimatedPosition = glm::mix(position, _estimatedPosition, POSITION_SMOOTHING); + } } // note that we have data @@ -176,7 +263,7 @@ void Webcam::setFrame(const Mat& frame, const RotatedRect& faceRect) { QTimer::singleShot(qMax((int)remaining / 1000, 0), _grabber, SLOT(grabFrame())); } -FrameGrabber::FrameGrabber() : _capture(0), _searchWindow(0, 0, 0, 0) { +FrameGrabber::FrameGrabber() : _initialized(false), _capture(0), _searchWindow(0, 0, 0, 0) { } FrameGrabber::~FrameGrabber() { @@ -185,52 +272,170 @@ FrameGrabber::~FrameGrabber() { } } +#ifdef HAVE_OPENNI +static AvatarJointID xnToAvatarJoint(XnSkeletonJoint joint) { + switch (joint) { + case XN_SKEL_HEAD: return AVATAR_JOINT_HEAD_TOP; + case XN_SKEL_NECK: return AVATAR_JOINT_HEAD_BASE; + case XN_SKEL_TORSO: return AVATAR_JOINT_CHEST; + + case XN_SKEL_LEFT_SHOULDER: return AVATAR_JOINT_RIGHT_ELBOW; + case XN_SKEL_LEFT_ELBOW: return AVATAR_JOINT_RIGHT_WRIST; + + case XN_SKEL_RIGHT_SHOULDER: return AVATAR_JOINT_LEFT_ELBOW; + case XN_SKEL_RIGHT_ELBOW: return AVATAR_JOINT_LEFT_WRIST; + + case XN_SKEL_LEFT_HIP: return AVATAR_JOINT_RIGHT_KNEE; + case XN_SKEL_LEFT_KNEE: return AVATAR_JOINT_RIGHT_HEEL; + case XN_SKEL_LEFT_FOOT: return AVATAR_JOINT_RIGHT_TOES; + + case XN_SKEL_RIGHT_HIP: return AVATAR_JOINT_LEFT_KNEE; + case XN_SKEL_RIGHT_KNEE: return AVATAR_JOINT_LEFT_HEEL; + case XN_SKEL_RIGHT_FOOT: return AVATAR_JOINT_LEFT_TOES; + + default: return AVATAR_JOINT_NULL; + } +} + +static int getParentJoint(XnSkeletonJoint joint) { + switch (joint) { + case XN_SKEL_HEAD: return XN_SKEL_NECK; + case XN_SKEL_TORSO: return -1; + + case XN_SKEL_LEFT_ELBOW: return XN_SKEL_LEFT_SHOULDER; + case XN_SKEL_LEFT_HAND: return XN_SKEL_LEFT_ELBOW; + + case XN_SKEL_RIGHT_ELBOW: return XN_SKEL_RIGHT_SHOULDER; + case XN_SKEL_RIGHT_HAND: return XN_SKEL_RIGHT_ELBOW; + + case XN_SKEL_LEFT_KNEE: return XN_SKEL_LEFT_HIP; + case XN_SKEL_LEFT_FOOT: return XN_SKEL_LEFT_KNEE; + + case XN_SKEL_RIGHT_KNEE: return XN_SKEL_RIGHT_HIP; + case XN_SKEL_RIGHT_FOOT: return XN_SKEL_RIGHT_KNEE; + + default: return XN_SKEL_TORSO; + } +} + +static glm::vec3 xnToGLM(const XnVector3D& vector, bool flip = false) { + return glm::vec3(vector.X * (flip ? -1 : 1), vector.Y, vector.Z); +} + +static glm::quat xnToGLM(const XnMatrix3X3& matrix) { + glm::quat rotation = glm::quat_cast(glm::mat3( + matrix.elements[0], matrix.elements[1], matrix.elements[2], + matrix.elements[3], matrix.elements[4], matrix.elements[5], + matrix.elements[6], matrix.elements[7], matrix.elements[8])); + return glm::quat(rotation.w, -rotation.x, rotation.y, rotation.z); +} + +static void XN_CALLBACK_TYPE newUser(UserGenerator& generator, XnUserID id, void* cookie) { + printLog("Found user %d.\n", id); + generator.GetSkeletonCap().RequestCalibration(id, false); +} + +static void XN_CALLBACK_TYPE lostUser(UserGenerator& generator, XnUserID id, void* cookie) { + printLog("Lost user %d.\n", id); +} + +static void XN_CALLBACK_TYPE calibrationStarted(SkeletonCapability& capability, XnUserID id, void* cookie) { + printLog("Calibration started for user %d.\n", id); +} + +static void XN_CALLBACK_TYPE calibrationCompleted(SkeletonCapability& capability, + XnUserID id, XnCalibrationStatus status, void* cookie) { + if (status == XN_CALIBRATION_STATUS_OK) { + printLog("Calibration completed for user %d.\n", id); + capability.StartTracking(id); + + } else { + printLog("Calibration failed to user %d.\n", id); + capability.RequestCalibration(id, true); + } +} +#endif + void FrameGrabber::reset() { _searchWindow = cv::Rect(0, 0, 0, 0); + +#ifdef HAVE_OPENNI + if (_userGenerator.IsValid() && _userGenerator.GetSkeletonCap().IsTracking(_userID)) { + _userGenerator.GetSkeletonCap().RequestCalibration(_userID, true); + } +#endif } void FrameGrabber::grabFrame() { - if (_capture == 0) { - if ((_capture = cvCaptureFromCAM(-1)) == 0) { - printLog("Failed to open webcam.\n"); - return; - } - const int IDEAL_FRAME_WIDTH = 320; - const int IDEAL_FRAME_HEIGHT = 240; - cvSetCaptureProperty(_capture, CV_CAP_PROP_FRAME_WIDTH, IDEAL_FRAME_WIDTH); - cvSetCaptureProperty(_capture, CV_CAP_PROP_FRAME_HEIGHT, IDEAL_FRAME_HEIGHT); + if (!(_initialized || init())) { + return; + } + int format = GL_BGR; + Mat frame; + JointVector joints; + +#ifdef HAVE_OPENNI + if (_depthGenerator.IsValid()) { + _xnContext.WaitAnyUpdateAll(); + frame = Mat(_imageMetaData.YRes(), _imageMetaData.XRes(), CV_8UC3, (void*)_imageGenerator.GetImageMap()); + format = GL_RGB; -#ifdef __APPLE__ - configureCamera(0x5ac, 0x8510, false, 0.975, 0.5, 1.0, 0.5, true, 0.5); -#else - cvSetCaptureProperty(_capture, CV_CAP_PROP_EXPOSURE, 0.5); - cvSetCaptureProperty(_capture, CV_CAP_PROP_CONTRAST, 0.5); - cvSetCaptureProperty(_capture, CV_CAP_PROP_SATURATION, 0.5); - cvSetCaptureProperty(_capture, CV_CAP_PROP_BRIGHTNESS, 0.5); - cvSetCaptureProperty(_capture, CV_CAP_PROP_HUE, 0.5); - cvSetCaptureProperty(_capture, CV_CAP_PROP_GAIN, 0.5); -#endif - - switchToResourcesParentIfRequired(); - if (!_faceCascade.load("resources/haarcascades/haarcascade_frontalface_alt.xml")) { - printLog("Failed to load Haar cascade for face tracking.\n"); + Mat depth = Mat(_depthMetaData.YRes(), _depthMetaData.XRes(), CV_16UC1, (void*)_depthGenerator.GetDepthMap()); + const double EIGHT_BIT_MAX = 255; + const double ELEVEN_BIT_MAX = 2047; + depth.convertTo(_grayDepthFrame, CV_8UC1, EIGHT_BIT_MAX / ELEVEN_BIT_MAX); + + _userID = 0; + XnUInt16 userCount = 1; + _userGenerator.GetUsers(&_userID, userCount); + if (userCount > 0 && _userGenerator.GetSkeletonCap().IsTracking(_userID)) { + joints.resize(NUM_AVATAR_JOINTS); + const int MAX_ACTIVE_JOINTS = 16; + XnSkeletonJoint activeJoints[MAX_ACTIVE_JOINTS]; + XnUInt16 activeJointCount = MAX_ACTIVE_JOINTS; + _userGenerator.GetSkeletonCap().EnumerateActiveJoints(activeJoints, activeJointCount); + XnSkeletonJointTransformation transform; + for (int i = 0; i < activeJointCount; i++) { + AvatarJointID avatarJoint = xnToAvatarJoint(activeJoints[i]); + if (avatarJoint == AVATAR_JOINT_NULL) { + continue; + } + _userGenerator.GetSkeletonCap().GetSkeletonJoint(_userID, activeJoints[i], transform); + XnVector3D projected; + _depthGenerator.ConvertRealWorldToProjective(1, &transform.position.position, &projected); + glm::quat rotation = xnToGLM(transform.orientation.orientation); + int parentJoint = getParentJoint(activeJoints[i]); + if (parentJoint != -1) { + XnSkeletonJointOrientation parentOrientation; + _userGenerator.GetSkeletonCap().GetSkeletonJointOrientation( + _userID, (XnSkeletonJoint)parentJoint, parentOrientation); + rotation = glm::inverse(xnToGLM(parentOrientation.orientation)) * rotation; + } + const float METERS_PER_MM = 1.0f / 1000.0f; + joints[avatarJoint] = Joint(xnToGLM(transform.position.position, true) * METERS_PER_MM, + rotation, xnToGLM(projected)); + } } } - IplImage* image = cvQueryFrame(_capture); - if (image == 0) { - // try again later - QMetaObject::invokeMethod(this, "grabFrame", Qt::QueuedConnection); - return; - } - // make sure it's in the format we expect - if (image->nChannels != 3 || image->depth != IPL_DEPTH_8U || image->dataOrder != IPL_DATA_ORDER_PIXEL || - image->origin != 0) { - printLog("Invalid webcam image format.\n"); - return; +#endif + + if (frame.empty()) { + IplImage* image = cvQueryFrame(_capture); + if (image == 0) { + // try again later + QMetaObject::invokeMethod(this, "grabFrame", Qt::QueuedConnection); + return; + } + // make sure it's in the format we expect + if (image->nChannels != 3 || image->depth != IPL_DEPTH_8U || image->dataOrder != IPL_DATA_ORDER_PIXEL || + image->origin != 0) { + printLog("Invalid webcam image format.\n"); + return; + } + frame = image; } // if we don't have a search window (yet), try using the face cascade - Mat frame = image; int channels = 0; float ranges[] = { 0, 180 }; const float* range = ranges; @@ -239,7 +444,7 @@ void FrameGrabber::grabFrame() { _faceCascade.detectMultiScale(frame, faces, 1.1, 6); if (!faces.empty()) { _searchWindow = faces.front(); - updateHSVFrame(frame); + updateHSVFrame(frame, format); Mat faceHsv(_hsvFrame, _searchWindow); Mat faceMask(_mask, _searchWindow); @@ -252,7 +457,7 @@ void FrameGrabber::grabFrame() { } RotatedRect faceRect; if (_searchWindow.area() > 0) { - updateHSVFrame(frame); + updateHSVFrame(frame, format); calcBackProject(&_hsvFrame, 1, &channels, _histogram, _backProject, &range); bitwise_and(_backProject, _mask, _backProject); @@ -261,10 +466,74 @@ void FrameGrabber::grabFrame() { _searchWindow = faceRect.boundingRect(); } QMetaObject::invokeMethod(Application::getInstance()->getWebcam(), "setFrame", - Q_ARG(cv::Mat, frame), Q_ARG(cv::RotatedRect, faceRect)); + Q_ARG(cv::Mat, frame), Q_ARG(int, format), Q_ARG(cv::Mat, _grayDepthFrame), + Q_ARG(cv::RotatedRect, faceRect), Q_ARG(JointVector, joints)); } -void FrameGrabber::updateHSVFrame(const Mat& frame) { - cvtColor(frame, _hsvFrame, CV_BGR2HSV); +bool FrameGrabber::init() { + _initialized = true; + + // load our face cascade + switchToResourcesParentIfRequired(); + if (!_faceCascade.load("resources/haarcascades/haarcascade_frontalface_alt.xml")) { + printLog("Failed to load Haar cascade for face tracking.\n"); + return false; + } + + // first try for a Kinect +#ifdef HAVE_OPENNI + _xnContext.Init(); + if (_depthGenerator.Create(_xnContext) == XN_STATUS_OK && _imageGenerator.Create(_xnContext) == XN_STATUS_OK && + _userGenerator.Create(_xnContext) == XN_STATUS_OK && + _userGenerator.IsCapabilitySupported(XN_CAPABILITY_SKELETON)) { + _depthGenerator.GetMetaData(_depthMetaData); + _imageGenerator.SetPixelFormat(XN_PIXEL_FORMAT_RGB24); + _imageGenerator.GetMetaData(_imageMetaData); + + XnCallbackHandle userCallbacks, calibrationStartCallback, calibrationCompleteCallback; + _userGenerator.RegisterUserCallbacks(newUser, lostUser, 0, userCallbacks); + _userGenerator.GetSkeletonCap().RegisterToCalibrationStart(calibrationStarted, 0, calibrationStartCallback); + _userGenerator.GetSkeletonCap().RegisterToCalibrationComplete(calibrationCompleted, 0, calibrationCompleteCallback); + + _userGenerator.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_UPPER); + + _xnContext.StartGeneratingAll(); + return true; + } +#endif + + // next, an ordinary webcam + if ((_capture = cvCaptureFromCAM(-1)) == 0) { + printLog("Failed to open webcam.\n"); + return false; + } + const int IDEAL_FRAME_WIDTH = 320; + const int IDEAL_FRAME_HEIGHT = 240; + cvSetCaptureProperty(_capture, CV_CAP_PROP_FRAME_WIDTH, IDEAL_FRAME_WIDTH); + cvSetCaptureProperty(_capture, CV_CAP_PROP_FRAME_HEIGHT, IDEAL_FRAME_HEIGHT); + +#ifdef __APPLE__ + configureCamera(0x5ac, 0x8510, false, 0.975, 0.5, 1.0, 0.5, true, 0.5); +#else + cvSetCaptureProperty(_capture, CV_CAP_PROP_EXPOSURE, 0.5); + cvSetCaptureProperty(_capture, CV_CAP_PROP_CONTRAST, 0.5); + cvSetCaptureProperty(_capture, CV_CAP_PROP_SATURATION, 0.5); + cvSetCaptureProperty(_capture, CV_CAP_PROP_BRIGHTNESS, 0.5); + cvSetCaptureProperty(_capture, CV_CAP_PROP_HUE, 0.5); + cvSetCaptureProperty(_capture, CV_CAP_PROP_GAIN, 0.5); +#endif + + return true; +} + +void FrameGrabber::updateHSVFrame(const Mat& frame, int format) { + cvtColor(frame, _hsvFrame, format == GL_RGB ? CV_RGB2HSV : CV_BGR2HSV); inRange(_hsvFrame, Scalar(0, 55, 65), Scalar(180, 256, 256), _mask); } + +Joint::Joint(const glm::vec3& position, const glm::quat& rotation, const glm::vec3& projected) : + isValid(true), position(position), rotation(rotation), projected(projected) { +} + +Joint::Joint() : isValid(false) { +} diff --git a/interface/src/Webcam.h b/interface/src/Webcam.h index 4bf6dea53e..f0910c7bce 100644 --- a/interface/src/Webcam.h +++ b/interface/src/Webcam.h @@ -12,11 +12,17 @@ #include #include #include +#include #include +#include #include +#ifdef HAVE_OPENNI + #include +#endif + #include "InterfaceConfig.h" class QImage; @@ -24,6 +30,9 @@ class QImage; struct CvCapture; class FrameGrabber; +class Joint; + +typedef QVector JointVector; class Webcam : public QObject { Q_OBJECT @@ -36,6 +45,7 @@ public: const bool isActive() const { return _active; } const glm::vec3& getEstimatedPosition() const { return _estimatedPosition; } const glm::vec3& getEstimatedRotation() const { return _estimatedRotation; } + const JointVector& getEstimatedJoints() const { return _estimatedJoints; } void reset(); void renderPreview(int screenWidth, int screenHeight); @@ -43,7 +53,8 @@ public: public slots: void setEnabled(bool enabled); - void setFrame(const cv::Mat& image, const cv::RotatedRect& faceRect); + void setFrame(const cv::Mat& video, int format, const cv::Mat& depth, + const cv::RotatedRect& faceRect, const JointVector& joints); private: @@ -54,9 +65,13 @@ private: bool _active; int _frameWidth; int _frameHeight; + int _depthWidth; + int _depthHeight; GLuint _frameTextureID; + GLuint _depthTextureID; cv::RotatedRect _faceRect; cv::RotatedRect _initialFaceRect; + JointVector _joints; long long _startTimestamp; int _frameCount; @@ -65,6 +80,7 @@ private: glm::vec3 _estimatedPosition; glm::vec3 _estimatedRotation; + JointVector _estimatedJoints; }; class FrameGrabber : public QObject { @@ -82,8 +98,10 @@ public slots: private: - void updateHSVFrame(const cv::Mat& frame); + bool init(); + void updateHSVFrame(const cv::Mat& frame, int format); + bool _initialized; CvCapture* _capture; cv::CascadeClassifier _faceCascade; cv::Mat _hsvFrame; @@ -91,8 +109,32 @@ private: cv::SparseMat _histogram; cv::Mat _backProject; cv::Rect _searchWindow; + cv::Mat _grayDepthFrame; + +#ifdef HAVE_OPENNI + xn::Context _xnContext; + xn::DepthGenerator _depthGenerator; + xn::ImageGenerator _imageGenerator; + xn::UserGenerator _userGenerator; + xn::DepthMetaData _depthMetaData; + xn::ImageMetaData _imageMetaData; + XnUserID _userID; +#endif }; +class Joint { +public: + + Joint(const glm::vec3& position, const glm::quat& rotation, const glm::vec3& projected); + Joint(); + + bool isValid; + glm::vec3 position; + glm::quat rotation; + glm::vec3 projected; +}; + +Q_DECLARE_METATYPE(JointVector) Q_DECLARE_METATYPE(cv::Mat) Q_DECLARE_METATYPE(cv::RotatedRect) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9779d0a1f5..1bef9026e7 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -152,6 +152,13 @@ int AvatarData::getBroadcastData(unsigned char* destinationBuffer) { } } + // skeleton joints + *destinationBuffer++ = (unsigned char)_joints.size(); + for (vector::iterator it = _joints.begin(); it != _joints.end(); it++) { + *destinationBuffer++ = (unsigned char)it->jointID; + destinationBuffer += packOrientationQuatToBytes(destinationBuffer, it->rotation); + } + return destinationBuffer - bufferStart; } @@ -263,6 +270,16 @@ int AvatarData::parseData(unsigned char* sourceBuffer, int numBytes) { _handData->setFingerRoots(fingerRoots); } + // skeleton joints + if (sourceBuffer - startPosition < numBytes) // safety check + { + _joints.resize(*sourceBuffer++); + for (vector::iterator it = _joints.begin(); it != _joints.end(); it++) { + it->jointID = *sourceBuffer++; + sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, it->rotation); + } + } + return sourceBuffer - startPosition; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 5d24199b9b..f65cf7bcb5 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -36,6 +37,8 @@ enum KeyState DELETE_KEY_DOWN }; +class JointData; + class AvatarData : public NodeData { public: AvatarData(Node* owningNode = NULL); @@ -132,14 +135,23 @@ protected: bool _wantDelta; bool _wantOcclusionCulling; + std::vector _joints; + HeadData* _headData; HandData* _handData; + private: // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); AvatarData& operator= (const AvatarData&); }; +class JointData { +public: + + int jointID; + glm::quat rotation; +}; // These pack/unpack functions are designed to start specific known types in as efficient a manner // as possible. Taking advantage of the known characteristics of the semantic types. diff --git a/libraries/shared/src/SimpleMovingAverage.cpp b/libraries/shared/src/SimpleMovingAverage.cpp index df41433618..5a55486216 100644 --- a/libraries/shared/src/SimpleMovingAverage.cpp +++ b/libraries/shared/src/SimpleMovingAverage.cpp @@ -22,7 +22,7 @@ int SimpleMovingAverage::updateAverage(float sample) { if (_numSamples > 0) { _average = (ONE_MINUS_WEIGHTING * _average) + (WEIGHTING * sample); - float eventDelta = (usecTimestampNow() - _lastEventTimestamp) / 1000000; + float eventDelta = (usecTimestampNow() - _lastEventTimestamp) / 1000000.0f; if (_numSamples > 1) { _eventDeltaAverage = (ONE_MINUS_WEIGHTING * _eventDeltaAverage) + @@ -46,7 +46,7 @@ void SimpleMovingAverage::reset() { float SimpleMovingAverage::getEventDeltaAverage() { return (ONE_MINUS_WEIGHTING * _eventDeltaAverage) + - (WEIGHTING * ((usecTimestampNow() - _lastEventTimestamp) / 1000000)); + (WEIGHTING * ((usecTimestampNow() - _lastEventTimestamp) / 1000000.0f)); } float SimpleMovingAverage::getAverageSampleValuePerSecond() { diff --git a/libraries/voxels/CMakeLists.txt b/libraries/voxels/CMakeLists.txt index d873320064..861001ed35 100644 --- a/libraries/voxels/CMakeLists.txt +++ b/libraries/voxels/CMakeLists.txt @@ -15,4 +15,9 @@ include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} ${ROOT_DIR}) include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) \ No newline at end of file +link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) + +# link ZLIB +find_package(ZLIB) +include_directories(${ZLIB_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) diff --git a/libraries/voxels/src/Tags.cpp b/libraries/voxels/src/Tags.cpp new file mode 100644 index 0000000000..0cbfa1a37c --- /dev/null +++ b/libraries/voxels/src/Tags.cpp @@ -0,0 +1,245 @@ +// +// Tags.h +// hifi +// +// Created by Clement Brisset on 7/3/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include "Tags.h" +#include + +#include +#include + +#include + +Tag::Tag(int tagId, std::stringstream &ss) : _tagId(tagId) { + int size = ss.get() << 8 | ss.get(); + + _name.clear(); + for (int i = 0; i < size; ++i) { _name += ss.get(); + } +} + +Tag* Tag::readTag(int tagId, std::stringstream &ss) { + + switch (tagId) { + case TAG_Byte: + return new TagByte(ss); + case TAG_Short: + return new TagShort(ss); + case TAG_Int: + return new TagInt(ss); + case TAG_Long: + return new TagLong(ss); + case TAG_Float: + return new TagFloat(ss); + case TAG_Double: + return new TagDouble(ss); + case TAG_Byte_Array: + return new TagByteArray(ss); + case TAG_String: + return new TagString(ss); + case TAG_List: + return new TagList(ss); + case TAG_Compound: + return new TagCompound(ss); + case TAG_Int_Array: + return new TagIntArray(ss); + default: + return NULL; + } +} + +TagByte::TagByte(std::stringstream &ss) : Tag(TAG_Byte, ss) { + _data = ss.get(); +} + +TagShort::TagShort(std::stringstream &ss) : Tag(TAG_Short, ss) { + _data = ss.get() << 8 | ss.get(); +} + +TagInt::TagInt(std::stringstream &ss) : Tag(TAG_Int, ss) { + _data = ss.get() << 24 | ss.get() << 16 | ss.get() << 8 | ss.get(); +} + +TagLong::TagLong(std::stringstream &ss) : Tag(TAG_Long, ss) { + _data = (((int64_t) ss.get()) << 56 | ((int64_t) ss.get()) << 48 + |((int64_t) ss.get()) << 40 | ((int64_t) ss.get()) << 32 + | ss.get() << 24 | ss.get() << 16 + | ss.get() << 8 | ss.get()); +} + +// We don't need Float and double, so we just ignore the bytes +TagFloat::TagFloat(std::stringstream &ss) : Tag(TAG_Float, ss) { + ss.seekg(4, ss.cur); +} + +TagDouble::TagDouble(std::stringstream &ss) : Tag(TAG_Double, ss) { + ss.seekg(8, ss.cur); +} + +TagByteArray::TagByteArray(std::stringstream &ss) : Tag(TAG_Byte_Array, ss) { + _size = ss.get() << 24 | ss.get() << 16 | ss.get() << 8 | ss.get(); + + _data = new char[_size]; + for (int i = 0; i < _size; ++i) { + _data[i] = ss.get(); + } +} + +TagString::TagString(std::stringstream &ss) : Tag(TAG_String, ss) { + _size = ss.get() << 8 | ss.get(); + + for (int i = 0; i < _size; ++i) { + _data += ss.get(); + } +} + +TagList::TagList(std::stringstream &ss) : + Tag(TAG_List, ss) { + _tagId = ss.get(); + _size = ss.get() << 24 | ss.get() << 16 | ss.get() << 8 | ss.get(); + + for (int i = 0; i < _size; ++i) { + ss.putback(0); + ss.putback(0); + _data.push_back(readTag(_tagId, ss)); + } +} + +TagCompound::TagCompound(std::stringstream &ss) : + Tag(TAG_Compound, ss), + _size(0), + _width(0), + _length(0), + _height(0), + _blocksId(NULL), + _blocksData(NULL) +{ + int tagId; + + while (TAG_End != (tagId = ss.get())) { + _data.push_back(readTag(tagId, ss)); + ++_size; + + if (NULL == _data.back()) { + _blocksId = NULL; + _blocksData = NULL; + return; + } else if (TAG_Short == tagId) { + if ("Width" == _data.back()->getName()) { + _width = ((TagShort*) _data.back())->getData(); + } else if ("Height" == _data.back()->getName()) { + _height = ((TagShort*) _data.back())->getData(); + } else if ("Length" == _data.back()->getName()) { + _length = ((TagShort*) _data.back())->getData(); + } + } else if (TAG_Byte_Array == tagId) { + if ("Blocks" == _data.back()->getName()) { + _blocksId = ((TagByteArray*) _data.back())->getData(); + } else if ("Data" == _data.back()->getName()) { + _blocksData = ((TagByteArray*) _data.back())->getData(); + } + } + } +} + +TagIntArray::TagIntArray(std::stringstream &ss) : Tag(TAG_Int_Array, ss) { + _size = ss.get() << 24 | ss.get() << 16 | ss.get() << 8 | ss.get(); + + _data = new int[_size]; + for (int i = 0; i < _size; ++i) { + _data[i] = ss.get(); + } +} + +int retrieveData(std::string filename, std::stringstream &ss) { + std::ifstream file(filename.c_str(), std::ios::binary); + + int type = file.peek(); + if (type == 0x0A) { + ss.flush(); + ss << file; + return 0; + } + if (type == 0x1F) { + return ungzip(file, ss); + } + + return 1; +} + +int ungzip(std::ifstream &file, std::stringstream &ss) { + std::string gzipedBytes; + gzipedBytes.clear(); + ss.flush(); + + while (!file.eof()) { + gzipedBytes += (char) file.get(); + } + file.close(); + + if (gzipedBytes.size() == 0) { + ss << gzipedBytes; + return 0; + } + + unsigned int full_length = gzipedBytes.size(); + unsigned int half_length = gzipedBytes.size()/2; + unsigned int uncompLength = full_length; + + char* uncomp = (char*) calloc(sizeof(char), uncompLength); + + z_stream strm; + strm.next_in = (Bytef *) gzipedBytes.c_str(); + strm.avail_in = full_length; + strm.total_out = 0; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + + bool done = false; + + if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK) { + free(uncomp); + return 1; + } + + while (!done) { + // If our output buffer is too small + if (strm.total_out >= uncompLength) { + // Increase size of output buffer + char* uncomp2 = (char*) calloc(sizeof(char), uncompLength + half_length); + memcpy(uncomp2, uncomp, uncompLength); + uncompLength += half_length; + free(uncomp); + uncomp = uncomp2; + } + + strm.next_out = (Bytef *) (uncomp + strm.total_out); + strm.avail_out = uncompLength - strm.total_out; + + // Inflate another chunk. + int err = inflate (&strm, Z_SYNC_FLUSH); + if (err == Z_STREAM_END) { + done = true; + } else if (err != Z_OK) { + break; + } + } + + if (inflateEnd (&strm) != Z_OK) { + free(uncomp); + return 1; + } + + for (size_t i = 0; i < strm.total_out; ++i) { + ss << uncomp[i]; + } + free(uncomp); + + return 0; +} + + diff --git a/libraries/voxels/src/Tags.h b/libraries/voxels/src/Tags.h new file mode 100644 index 0000000000..a005095142 --- /dev/null +++ b/libraries/voxels/src/Tags.h @@ -0,0 +1,175 @@ +// +// Tags.h +// hifi +// +// Created by Clement Brisset on 7/3/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__Tags__ +#define __hifi__Tags__ + +#include +#include + +#include +#include +#include + +#define TAG_End 0 +#define TAG_Byte 1 +#define TAG_Short 2 +#define TAG_Int 3 +#define TAG_Long 4 +#define TAG_Float 5 +#define TAG_Double 6 +#define TAG_Byte_Array 7 +#define TAG_String 8 +#define TAG_List 9 +#define TAG_Compound 10 +#define TAG_Int_Array 11 + +int retrieveData(std::string filename, std::stringstream &ss); +int ungzip(std::ifstream &file, std::stringstream &ss); + +class Tag { +public: + Tag(int tagId, std::stringstream &ss); + + int getTagId() const {return _tagId;} + std::string getName () const {return _name; } + + static Tag* readTag(int tagId, std::stringstream &ss); + +protected: + int _tagId; + std::string _name; +}; + +class TagByte : public Tag { +public: + TagByte(std::stringstream &ss); + + int8_t getData() const {return _data;} + +private: + int8_t _data; +}; + +class TagShort : public Tag { +public: + TagShort(std::stringstream &ss); + + int16_t getData() const {return _data;} + +private: + int16_t _data; +}; + +class TagInt : public Tag { +public: + TagInt(std::stringstream &ss); + + int32_t getData() const {return _data;} + +private: + int32_t _data; +}; + +class TagLong : public Tag { +public: + TagLong(std::stringstream &ss); + + int64_t getData() const {return _data;} + +private: + int64_t _data; +}; + +class TagFloat : public Tag { +public: + TagFloat(std::stringstream &ss); +}; + +class TagDouble : public Tag { +public: + TagDouble(std::stringstream &ss); +}; + +class TagByteArray : public Tag { +public: + TagByteArray(std::stringstream &ss); + + int getSize() const {return _size;} + char* getData() const {return _data;} + +private: + int _size; + char* _data; +}; + +class TagString : public Tag { +public: + TagString(std::stringstream &ss); + + int getSize() const {return _size;} + std::string getData() const {return _data;} + +private: + int _size; + std::string _data; +}; + +class TagList : public Tag { +public: + TagList(std::stringstream &ss); + + int getTagId() const {return _tagId;} + int getSize () const {return _size; } + std::list getData () const {return _data; } + +private: + int _tagId; + int _size; + std::list _data; +}; + +class TagCompound : public Tag { +public: + TagCompound(std::stringstream &ss); + + int getSize () const {return _size; } + std::list getData () const {return _data; } + + int getWidth () const {return _width; } + int getLength () const {return _length; } + int getHeight () const {return _height; } + char* getBlocksId () const {return _blocksId; } + char* getBlocksData() const {return _blocksData;} + +private: + int _size; + std::list _data; + + // Specific to schematics file + int _width; + int _length; + int _height; + char* _blocksData; + char* _blocksId; +}; + +class TagIntArray : public Tag { +public: + TagIntArray(std::stringstream &ss); + ~TagIntArray() {delete _data;} + + int getSize() const {return _size;} + int* getData() const {return _data;} + +private: + int _size; + int* _data; +}; + +#endif /* defined(__hifi__Tags__) */ diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 70e88bd7b5..dbd13e236a 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -24,6 +24,7 @@ #include "VoxelConstants.h" #include "CoverageMap.h" #include "SquarePixelMap.h" +#include "Tags.h" #include @@ -138,7 +139,7 @@ void VoxelTree::recurseNodeWithOperation(VoxelNode* node, int& level, RecurseVox // Recurses voxel tree calling the RecurseVoxelTreeOperation function for each node. // stops recursion if operation function returns false. -void VoxelTree::recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation, +void VoxelTree::recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation, const glm::vec3& point, void* extraData) { int level = 0; @@ -159,15 +160,15 @@ void VoxelTree::recurseNodeWithOperationDistanceSorted(VoxelNode* node, int& lev VoxelNode* childNode = node->getChildAtIndex(i); if (childNode) { // chance to optimize, doesn't need to be actual distance!! Could be distance squared - float distanceSquared = childNode->distanceSquareToPoint(point); + float distanceSquared = childNode->distanceSquareToPoint(point); //printLog("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance); //childNode->printDebugDetails(""); currentCount = insertIntoSortedArrays((void*)childNode, distanceSquared, i, - (void**)&sortedChildren, (float*)&distancesToChildren, + (void**)&sortedChildren, (float*)&distancesToChildren, (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); } } - + for (int i = 0; i < currentCount; i++) { VoxelNode* childNode = sortedChildren[i]; if (childNode) { @@ -182,20 +183,20 @@ void VoxelTree::recurseNodeWithOperationDistanceSorted(VoxelNode* node, int& lev } -VoxelNode* VoxelTree::nodeForOctalCode(VoxelNode* ancestorNode, +VoxelNode* VoxelTree::nodeForOctalCode(VoxelNode* ancestorNode, unsigned char* needleCode, VoxelNode** parentOfFoundNode) const { // find the appropriate branch index based on this ancestorNode if (*needleCode > 0) { int branchForNeedle = branchIndexWithDescendant(ancestorNode->getOctalCode(), needleCode); VoxelNode* childNode = ancestorNode->getChildAtIndex(branchForNeedle); - + if (childNode) { if (*childNode->getOctalCode() == *needleCode) { - - // If the caller asked for the parent, then give them that too... - if (parentOfFoundNode) { - *parentOfFoundNode = ancestorNode; - } + + // If the caller asked for the parent, then give them that too... + if (parentOfFoundNode) { + *parentOfFoundNode = ancestorNode; + } // the fact that the number of sections is equivalent does not always guarantee // that this is the same node, however due to the recursive traversal // we know that this is our node @@ -206,7 +207,7 @@ VoxelNode* VoxelTree::nodeForOctalCode(VoxelNode* ancestorNode, } } } - + // we've been given a code we don't have a node for // return this node as the last created parent return ancestorNode; @@ -236,7 +237,7 @@ VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, unsigned char } } -int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, int bytesLeftToRead, +int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, int bytesLeftToRead, bool includeColor, bool includeExistsBits) { // give this destination node the child mask from the packet const unsigned char ALL_CHILDREN_ASSUMED_TO_EXIST = 0xFF; @@ -273,22 +274,22 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, if (!nodeWasDirty && nodeIsDirty) { _nodesChangedFromBitstream++; } - this->voxelsColored++; - this->voxelsColoredStats.updateAverage(1); + this->voxelsColored++; + this->voxelsColoredStats.updateAverage(1); } } // give this destination node the child mask from the packet - unsigned char childrenInTreeMask = includeExistsBits ? *(nodeData + bytesRead) : ALL_CHILDREN_ASSUMED_TO_EXIST; + unsigned char childrenInTreeMask = includeExistsBits ? *(nodeData + bytesRead) : ALL_CHILDREN_ASSUMED_TO_EXIST; unsigned char childMask = *(nodeData + bytesRead + (includeExistsBits ? sizeof(childrenInTreeMask) : 0)); int childIndex = 0; bytesRead += includeExistsBits ? sizeof(childrenInTreeMask) + sizeof(childMask) : sizeof(childMask); - + while (bytesLeftToRead - bytesRead > 0 && childIndex < NUMBER_OF_CHILDREN) { // check the exists mask to see if we have a child to traverse into - - if (oneAtBit(childMask, childIndex)) { + + if (oneAtBit(childMask, childIndex)) { if (!destinationNode->getChildAtIndex(childIndex)) { // add a child at that index, if it doesn't exist bool nodeWasDirty = destinationNode->isDirty(); @@ -303,14 +304,14 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, this->voxelsCreated++; this->voxelsCreatedStats.updateAverage(this->voxelsCreated); } - + // tell the child to read the subsequent data bytesRead += readNodeData(destinationNode->getChildAtIndex(childIndex), nodeData + bytesRead, bytesLeftToRead - bytesRead, includeColor, includeExistsBits); } childIndex++; } - + if (includeExistsBits) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { @@ -321,21 +322,21 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, destinationNode->safeDeepDeleteChildAtIndex(i, stagedForDeletion); _isDirty = true; // by definition! } - } + } } return bytesRead; } -void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int bufferSizeBytes, +void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int bufferSizeBytes, 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 @@ -360,8 +361,8 @@ void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int int octalCodeBytes = bytesRequiredForCodeLength(*bitstreamAt); int theseBytesRead = 0; theseBytesRead += octalCodeBytes; - theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes, - bufferSizeBytes - (bytesRead + octalCodeBytes), includeColor, includeExistsBits); + theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes, + bufferSizeBytes - (bytesRead + octalCodeBytes), includeColor, includeExistsBits); // skip bitstream to new startPoint bitstreamAt += theseBytesRead; @@ -401,7 +402,7 @@ void VoxelTree::deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool stage, b args.lengthOfCode = numberOfThreeBitSectionsInCode(codeBuffer); args.deleteLastChild = false; args.pathChanged = false; - + VoxelNode* node = rootNode; deleteVoxelCodeFromTreeRecursion(node, &args); } @@ -411,11 +412,11 @@ void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraDat int lengthOfNodeCode = numberOfThreeBitSectionsInCode(node->getOctalCode()); - // Since we traverse the tree in code order, we know that if our code + // Since we traverse the tree in code order, we know that if our code // matches, then we've reached our target node. if (lengthOfNodeCode == args->lengthOfCode) { // we've reached our target, depending on how we're called we may be able to operate on it - // if we're in "stage" mode, then we can could have the node staged, otherwise we can't really delete + // if we're in "stage" mode, then we can could have the node staged, otherwise we can't really delete // it here, we need to recurse up, and delete it there. So we handle these cases the same to keep // the logic consistent. args->deleteLastChild = true; @@ -425,9 +426,9 @@ void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraDat // Ok, we know we haven't reached our target node yet, so keep looking int childIndex = branchIndexWithDescendant(node->getOctalCode(), args->codeBuffer); VoxelNode* childNode = node->getChildAtIndex(childIndex); - + // If there is no child at the target location, and the current parent node is a colored leaf, - // then it means we were asked to delete a child out of a larger leaf voxel. + // then it means we were asked to delete a child out of a larger leaf voxel. // We support this by breaking up the parent voxel into smaller pieces. if (!childNode && node->isLeaf() && node->isColored()) { // we need to break up ancestors until we get to the right level @@ -443,7 +444,7 @@ void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraDat } } int lengthOfancestorNode = numberOfThreeBitSectionsInCode(ancestorNode->getOctalCode()); - + // If we've reached the parent of the target, then stop breaking up children if (lengthOfancestorNode == (args->lengthOfCode - 1)) { break; @@ -456,11 +457,11 @@ void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraDat } _isDirty = true; args->pathChanged = true; - + // ends recursion, unwinds up stack return; } - + // if we don't have a child and we reach this point, then we actually know that the parent // isn't a colored leaf, and the child branch doesn't exist, so there's nothing to do below and // we can safely return, ending the recursion and unwinding @@ -483,12 +484,12 @@ void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraDat // track our tree dirtiness _isDirty = true; - + // track that path has changed args->pathChanged = true; // If we're in collapseEmptyTrees mode, and this was the last child of this node, then we also want - // to delete this node. This will collapse the empty tree above us. + // to delete this node. This will collapse the empty tree above us. if (args->collapseEmptyTrees && node->getChildCount() == 0) { // Can't delete the root this way. if (node == rootNode) { @@ -498,7 +499,7 @@ void VoxelTree::deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraDat args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything } } - + // If the lower level did some work, then we need to let this node know, so it can // do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc if (args->pathChanged) { @@ -528,9 +529,9 @@ void VoxelTree::readCodeColorBufferToTree(unsigned char* codeColorBuffer, bool d args.destructive = destructive; args.pathChanged = false; - + VoxelNode* node = rootNode; - + readCodeColorBufferToTreeRecursion(node, &args); } @@ -540,7 +541,7 @@ void VoxelTree::readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraD int lengthOfNodeCode = numberOfThreeBitSectionsInCode(node->getOctalCode()); - // Since we traverse the tree in code order, we know that if our code + // Since we traverse the tree in code order, we know that if our code // matches, then we've reached our target node. if (lengthOfNodeCode == args->lengthOfCode) { // we've reached our target -- we might have found our node, but that node might have children. @@ -556,7 +557,7 @@ void VoxelTree::readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraD printLog("WARNING! operation would require deleting children, add Voxel ignored!\n "); } } - + // If we get here, then it means, we either had a true leaf to begin with, or we were in // destructive mode and we deleted all the child trees. So we can color. if (node->isLeaf()) { @@ -567,7 +568,7 @@ void VoxelTree::readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraD memcpy(newColor, args->codeColorBuffer + octalCodeBytes, SIZE_OF_COLOR_DATA); newColor[SIZE_OF_COLOR_DATA] = 1; node->setColor(newColor); - + // It's possible we just reset the node to it's exact same color, in // which case we don't consider this to be dirty... if (node->isDirty()) { @@ -583,7 +584,7 @@ void VoxelTree::readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraD // Ok, we know we haven't reached our target node yet, so keep looking int childIndex = branchIndexWithDescendant(node->getOctalCode(), args->codeColorBuffer); VoxelNode* childNode = node->getChildAtIndex(childIndex); - + // If the branch we need to traverse does not exist, then create it on the way down... if (!childNode) { childNode = node->addChildAtIndex(childIndex); @@ -593,7 +594,7 @@ void VoxelTree::readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraD readCodeColorBufferToTreeRecursion(childNode, args); // Unwinding... - + // If the lower level did some work, then we need to let this node know, so it can // do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc if (args->pathChanged) { @@ -602,18 +603,18 @@ void VoxelTree::readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraD } void VoxelTree::processRemoveVoxelBitstream(unsigned char * bitstream, int bufferSizeBytes) { - //unsigned short int itemNumber = (*((unsigned short int*)&bitstream[sizeof(PACKET_HEADER)])); - int atByte = sizeof(short int) + sizeof(PACKET_HEADER); - unsigned char* voxelCode = (unsigned char*)&bitstream[atByte]; - while (atByte < bufferSizeBytes) { - int codeLength = numberOfThreeBitSectionsInCode(voxelCode); - int voxelDataSize = bytesRequiredForCodeLength(codeLength) + SIZE_OF_COLOR_DATA; + //unsigned short int itemNumber = (*((unsigned short int*)&bitstream[sizeof(PACKET_HEADER)])); + int atByte = sizeof(short int) + sizeof(PACKET_HEADER); + unsigned char* voxelCode = (unsigned char*)&bitstream[atByte]; + while (atByte < bufferSizeBytes) { + int codeLength = numberOfThreeBitSectionsInCode(voxelCode); + int voxelDataSize = bytesRequiredForCodeLength(codeLength) + SIZE_OF_COLOR_DATA; - deleteVoxelCodeFromTree(voxelCode, ACTUALLY_DELETE, COLLAPSE_EMPTY_TREE); + deleteVoxelCodeFromTree(voxelCode, ACTUALLY_DELETE, COLLAPSE_EMPTY_TREE); - voxelCode+=voxelDataSize; - atByte+=voxelDataSize; - } + voxelCode+=voxelDataSize; + atByte+=voxelDataSize; + } } void VoxelTree::printTreeForDebugging(VoxelNode *startNode) { @@ -659,7 +660,7 @@ void VoxelTree::printTreeForDebugging(VoxelNode *startNode) { printTreeForDebugging(startNode->getChildAtIndex(l)); } } - } + } } // Note: this is an expensive call. Don't call it unless you really need to reaverage the entire tree (from startNode) @@ -690,15 +691,15 @@ void VoxelTree::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) { char octets; unsigned int lengthInBytes; - + int totalBytesRead = 0; if(file.is_open()) { - printLog("loading file...\n"); + printLog("loading file...\n"); bool bail = false; while (!file.eof() && !bail) { file.get(octets); totalBytesRead++; - lengthInBytes = bytesRequiredForCodeLength(octets) - 1; + lengthInBytes = bytesRequiredForCodeLength(octets) - 1; unsigned char * voxelData = new unsigned char[lengthInBytes + 1 + 3]; voxelData[0]=octets; char byte; @@ -726,7 +727,7 @@ void VoxelTree::loadVoxelsFile(const char* fileName, bool wantColorRandomizer) { voxelData[lengthInBytes+2] = std::max(0,std::min(255,green + colorRandomizer)); voxelData[lengthInBytes+3] = std::max(0,std::min(255,blue + colorRandomizer)); printLog("voxel color after rand red:%d, green:%d, blue:%d\n", - voxelData[lengthInBytes+1], voxelData[lengthInBytes+2], voxelData[lengthInBytes+3]); + voxelData[lengthInBytes+1], voxelData[lengthInBytes+2], voxelData[lengthInBytes+3]); //printVoxelCode(voxelData); this->readCodeColorBufferToTree(voxelData); @@ -746,7 +747,7 @@ VoxelNode* VoxelTree::getVoxelAt(float x, float y, float z, float s) const { return node; } -void VoxelTree::createVoxel(float x, float y, float z, float s, +void VoxelTree::createVoxel(float x, float y, float z, float s, unsigned char red, unsigned char green, unsigned char blue, bool destructive) { unsigned char* voxelData = pointToVoxel(x,y,z,s,red,green,blue); this->readCodeColorBufferToTree(voxelData, destructive); @@ -766,13 +767,13 @@ void VoxelTree::createLine(glm::vec3 point1, glm::vec3 point2, float unitSize, r } } -void VoxelTree::createSphere(float radius, float xc, float yc, float zc, float voxelSize, - bool solid, creationMode mode, bool destructive, bool debug) { - +void VoxelTree::createSphere(float radius, float xc, float yc, float zc, float voxelSize, + bool solid, creationMode mode, bool destructive, bool debug) { + bool wantColorRandomizer = (mode == RANDOM); bool wantNaturalSurface = (mode == NATURAL); bool wantNaturalColor = (mode == NATURAL); - + // About the color of the sphere... we're going to make this sphere be a mixture of two colors // in NATURAL mode, those colors will be green dominant and blue dominant. In GRADIENT mode we // will randomly pick which color family red, green, or blue to be dominant. In RANDOM mode we @@ -797,7 +798,7 @@ void VoxelTree::createSphere(float radius, float xc, float yc, float zc, float v g2 = (dominantColor2 == 2) ? randIntInRange(200, 255) : randIntInRange(40, 100); b2 = (dominantColor2 == 3) ? randIntInRange(200, 255) : randIntInRange(40, 100); } - + // We initialize our rgb to be either "grey" in case of randomized surface, or // the average of the gradient, in the case of the gradient sphere. unsigned char red = wantColorRandomizer ? 128 : (r1 + r2) / 2; // average of the colors @@ -805,7 +806,7 @@ void VoxelTree::createSphere(float radius, float xc, float yc, float zc, float v unsigned char blue = wantColorRandomizer ? 128 : (b1 + b2) / 2; // I want to do something smart like make these inside circles with bigger voxels, but this doesn't seem to work. - float thisVoxelSize = voxelSize; // radius / 2.0f; + float thisVoxelSize = voxelSize; // radius / 2.0f; float thisRadius = 0.0; if (!solid) { thisRadius = radius; // just the outer surface @@ -818,63 +819,63 @@ void VoxelTree::createSphere(float radius, float xc, float yc, float zc, float v while (!lastLayer) { lastLayer = (thisRadius + (voxelSize * 2.0) >= radius); - // We want to make sure that as we "sweep" through our angles we use a delta angle that voxelSize + // We want to make sure that as we "sweep" through our angles we use a delta angle that voxelSize // small enough to not skip any voxels we can calculate theta from our desired arc length // lenArc = ndeg/360deg * 2pi*R ---> lenArc = theta/2pi * 2pi*R - // lenArc = theta*R ---> theta = lenArc/R ---> theta = g/r + // lenArc = theta*R ---> theta = lenArc/R ---> theta = g/r float angleDelta = (thisVoxelSize / thisRadius); if (debug) { int percentComplete = 100 * (thisRadius/radius); - printLog("percentComplete=%d\n",percentComplete); + printLog("percentComplete=%d\n",percentComplete); } - + for (float theta=0.0; theta <= 2 * M_PI; theta += angleDelta) { for (float phi=0.0; phi <= M_PI; phi += angleDelta) { bool naturalSurfaceRendered = false; float x = xc + thisRadius * cos(theta) * sin(phi); float y = yc + thisRadius * sin(theta) * sin(phi); float z = zc + thisRadius * cos(phi); - - // if we're on the outer radius, then we do a couple of things differently. - // 1) If we're in NATURAL mode we will actually draw voxels from our surface outward (from the surface) up + + // if we're on the outer radius, then we do a couple of things differently. + // 1) If we're in NATURAL mode we will actually draw voxels from our surface outward (from the surface) up // some random height. This will give our sphere some contours. // 2) In all modes, we will use our "outer" color to draw the voxels. Otherwise we will use the average color if (lastLayer) { - if (false && debug) { + if (false && debug) { printLog("adding candy shell: theta=%f phi=%f thisRadius=%f radius=%f\n", - theta, phi, thisRadius,radius); + theta, phi, thisRadius,radius); } switch (mode) { - case RANDOM: { - red = randomColorValue(165); - green = randomColorValue(165); - blue = randomColorValue(165); - } break; - case GRADIENT: { - float gradient = (phi / M_PI); - red = r1 + ((r2 - r1) * gradient); - green = g1 + ((g2 - g1) * gradient); - blue = b1 + ((b2 - b1) * gradient); - } break; - case NATURAL: { - glm::vec3 position = glm::vec3(theta,phi,radius); - float perlin = glm::perlin(position) + .25f * glm::perlin(position * 4.f) - + .125f * glm::perlin(position * 16.f); - float gradient = (1.0f + perlin)/ 2.0f; - red = (unsigned char)std::min(255, std::max(0, (int)(r1 + ((r2 - r1) * gradient)))); - green = (unsigned char)std::min(255, std::max(0, (int)(g1 + ((g2 - g1) * gradient)))); - blue = (unsigned char)std::min(255, std::max(0, (int)(b1 + ((b2 - b1) * gradient)))); - if (debug) { - printLog("perlin=%f gradient=%f color=(%d,%d,%d)\n",perlin, gradient, red, green, blue); - } + case RANDOM: { + red = randomColorValue(165); + green = randomColorValue(165); + blue = randomColorValue(165); + } break; + case GRADIENT: { + float gradient = (phi / M_PI); + red = r1 + ((r2 - r1) * gradient); + green = g1 + ((g2 - g1) * gradient); + blue = b1 + ((b2 - b1) * gradient); + } break; + case NATURAL: { + glm::vec3 position = glm::vec3(theta,phi,radius); + float perlin = glm::perlin(position) + .25f * glm::perlin(position * 4.f) + + .125f * glm::perlin(position * 16.f); + float gradient = (1.0f + perlin)/ 2.0f; + red = (unsigned char)std::min(255, std::max(0, (int)(r1 + ((r2 - r1) * gradient)))); + green = (unsigned char)std::min(255, std::max(0, (int)(g1 + ((g2 - g1) * gradient)))); + blue = (unsigned char)std::min(255, std::max(0, (int)(b1 + ((b2 - b1) * gradient)))); + if (debug) { + printLog("perlin=%f gradient=%f color=(%d,%d,%d)\n",perlin, gradient, red, green, blue); + } } break; } if (wantNaturalSurface) { // for natural surfaces, we will render up to 16 voxel's above the surface of the sphere glm::vec3 position = glm::vec3(theta,phi,radius); - float perlin = glm::perlin(position) + .25f * glm::perlin(position * 4.f) - + .125f * glm::perlin(position * 16.f); + float perlin = glm::perlin(position) + .25f * glm::perlin(position * 4.f) + + .125f * glm::perlin(position * 16.f); float gradient = (1.0f + perlin)/ 2.0f; int height = (4 * gradient)+1; // make it at least 4 thick, so we get some averaging @@ -903,8 +904,8 @@ int VoxelTree::searchForColoredNodes(int maxSearchLevel, VoxelNode* node, const // call the recursive version, this will add all found colored node roots to the bag int currentSearchLevel = 0; - - int levelReached = searchForColoredNodesRecursion(maxSearchLevel, currentSearchLevel, rootNode, + + int levelReached = searchForColoredNodesRecursion(maxSearchLevel, currentSearchLevel, rootNode, viewFrustum, bag, deltaViewFrustum, lastViewFrustum); return levelReached; } @@ -958,7 +959,7 @@ public: bool findSpherePenetrationOp(VoxelNode* node, int level, void* extraData) { SphereArgs* args = static_cast(extraData); - + // coarse check against bounds const AABox& box = node->getAABox(); if (!box.expandedContains(args->center, args->radius)) { @@ -972,7 +973,7 @@ bool findSpherePenetrationOp(VoxelNode* node, int level, void* extraData) { if (box.findSpherePenetration(args->center, args->radius, nodePenetration)) { args->penetration = addPenetrations(args->penetration, nodePenetration * (float)TREE_SCALE); args->found = true; - } + } } return false; } @@ -995,7 +996,7 @@ public: bool findCapsulePenetrationOp(VoxelNode* node, int level, void* extraData) { CapsuleArgs* args = static_cast(extraData); - + // coarse check against bounds const AABox& box = node->getAABox(); if (!box.expandedIntersectsSegment(args->start, args->end, args->radius)) { @@ -1009,7 +1010,7 @@ bool findCapsulePenetrationOp(VoxelNode* node, int level, void* extraData) { if (box.findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) { args->penetration = addPenetrations(args->penetration, nodePenetration * (float)TREE_SCALE); args->found = true; - } + } } return false; } @@ -1021,36 +1022,36 @@ bool VoxelTree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& return args.found; } -int VoxelTree::searchForColoredNodesRecursion(int maxSearchLevel, int& currentSearchLevel, +int VoxelTree::searchForColoredNodesRecursion(int maxSearchLevel, int& currentSearchLevel, VoxelNode* node, const ViewFrustum& viewFrustum, VoxelNodeBag& bag, bool deltaViewFrustum, const ViewFrustum* lastViewFrustum) { - // Keep track of how deep we've searched. + // Keep track of how deep we've searched. currentSearchLevel++; // If we've passed our max Search Level, then stop searching. return last level searched if (currentSearchLevel > maxSearchLevel) { return currentSearchLevel-1; - } + } // If we're at a node that is out of view, then we can return, because no nodes below us will be in view! if (!node->isInView(viewFrustum)) { return currentSearchLevel; } - + // Ok, this is a little tricky, each child may have been deeper than the others, so we need to track // how deep each child went. And we actually return the maximum of each child. We use these variables below // when we recurse the children. int thisLevel = currentSearchLevel; int maxChildLevel = thisLevel; - + VoxelNode* inViewChildren[NUMBER_OF_CHILDREN]; float distancesToChildren[NUMBER_OF_CHILDREN]; int positionOfChildren[NUMBER_OF_CHILDREN]; int inViewCount = 0; int inViewNotLeafCount = 0; int inViewWithColorCount = 0; - + // for each child node, check to see if they exist, are colored, and in view, and if so // add them to our distance ordered array of children for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { @@ -1060,22 +1061,22 @@ int VoxelTree::searchForColoredNodesRecursion(int maxSearchLevel, int& currentSe bool childIsLeaf = (childNode && childNode->isLeaf()); if (childIsInView) { - + // track children in view as existing and not a leaf if (!childIsLeaf) { inViewNotLeafCount++; } - + // track children with actual color if (childIsColored) { inViewWithColorCount++; } - + float distance = childNode->distanceToCamera(viewFrustum); - + if (distance < boundaryDistanceForRenderLevel(*childNode->getOctalCode() + 1)) { - inViewCount = insertIntoSortedArrays((void*)childNode, distance, i, - (void**)&inViewChildren, (float*)&distancesToChildren, + inViewCount = insertIntoSortedArrays((void*)childNode, distance, i, + (void**)&inViewChildren, (float*)&distancesToChildren, (int*)&positionOfChildren, inViewCount, NUMBER_OF_CHILDREN); } } @@ -1091,7 +1092,7 @@ int VoxelTree::searchForColoredNodesRecursion(int maxSearchLevel, int& currentSe } else { // at this point, we need to iterate the children who are in view, even if not colored // and we need to determine if there's a deeper tree below them that we care about. We will iterate - // these based on which tree is closer. + // these based on which tree is closer. for (int i = 0; i < inViewCount; i++) { VoxelNode* childNode = inViewChildren[i]; thisLevel = currentSearchLevel; // reset this, since the children will munge it up @@ -1103,8 +1104,8 @@ int VoxelTree::searchForColoredNodesRecursion(int maxSearchLevel, int& currentSe return maxChildLevel; } -int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, - EncodeBitstreamParams& params) const { +int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, + EncodeBitstreamParams& params) const { // How many bytes have we written so far at this level; int bytesWritten = 0; @@ -1130,10 +1131,10 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, codeLength = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(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 + availableBytes -= codeLength; // keep track or remaining space int currentEncodeLevel = 0; int childBytesWritten = encodeTreeBitstreamRecursion(node, outputBuffer, availableBytes, bag, params, currentEncodeLevel); @@ -1149,7 +1150,7 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, // if we wrote child bytes, then return our result of all bytes written if (childBytesWritten) { - bytesWritten += childBytesWritten; + bytesWritten += childBytesWritten; } else { // otherwise... if we didn't write any child bytes, then pretend like we also didn't write our octal code bytesWritten = 0; @@ -1157,7 +1158,7 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, return bytesWritten; } -int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, +int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag, EncodeBitstreamParams& params, int& currentEncodeLevel) const { // How many bytes have we written so far at this level; @@ -1189,8 +1190,8 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp if (!node->isInView(*params.viewFrustum)) { return bytesAtThisLevel; } - - + + // If the user also asked for occlusion culling, check if this node is occluded, but only if it's not a leaf. // leaf occlusion is handled down below when we check child nodes if (params.wantOcclusionCulling && !node->isLeaf()) { @@ -1212,7 +1213,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp //args->subtreeVoxelsSkipped += (subArgs.voxelsTouched - 1); //args->totalVoxels += (subArgs.voxelsTouched - 1); - + return bytesAtThisLevel; } } else { @@ -1223,7 +1224,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp } } } - + bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more! // At any given point in writing the bitstream, the largest minimum we might need to flesh out the current level @@ -1238,7 +1239,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp const int BYTES_PER_COLOR = 3; const int CHILD_TREE_EXISTS_BYTES = sizeof(childrenExistInTreeBits) + sizeof(childrenExistInPacketBits); const int MAX_LEVEL_BYTES = CHILD_COLOR_MASK_BYTES + NUMBER_OF_CHILDREN * BYTES_PER_COLOR + CHILD_TREE_EXISTS_BYTES; - + // Make our local buffer large enough to handle writing at this level in case we need to. unsigned char thisLevelBuffer[MAX_LEVEL_BYTES]; unsigned char* writeToThisLevelBuffer = &thisLevelBuffer[0]; @@ -1263,14 +1264,14 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp if (params.wantOcclusionCulling) { if (childNode) { // chance to optimize, doesn't need to be actual distance!! Could be distance squared - //float distanceSquared = childNode->distanceSquareToPoint(point); + //float distanceSquared = childNode->distanceSquareToPoint(point); //printLog("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance); //childNode->printDebugDetails(""); float distance = params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; currentCount = insertIntoSortedArrays((void*)childNode, distance, i, - (void**)&sortedChildren, (float*)&distancesToChildren, + (void**)&sortedChildren, (float*)&distancesToChildren, (int*)&indexOfChildren, currentCount, NUMBER_OF_CHILDREN); } } else { @@ -1288,7 +1289,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp int originalIndex = indexOfChildren[i]; bool childIsInView = (childNode && (!params.viewFrustum || childNode->isInView(*params.viewFrustum))); - + if (childIsInView) { // Before we determine consider this further, let's see if it's in our LOD scope... float distance = distancesToChildren[i]; // params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; @@ -1296,7 +1297,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp if (distance < boundaryDistance) { inViewCount++; - + // track children in view as existing and not a leaf, if they're a leaf, // we don't care about recursing deeper on them, and we don't consider their // subtree to exist @@ -1314,13 +1315,13 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp AABox voxelBox = childNode->getAABox(); voxelBox.scale(TREE_SCALE); VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon( - params.viewFrustum->getProjectedPolygon(voxelBox)); - + params.viewFrustum->getProjectedPolygon(voxelBox)); + // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion // culling and proceed as normal if (voxelPolygon->getAllInView()) { CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, true); - + // In all cases where the shadow wasn't stored, we need to free our own memory. // In the case where it is stored, the CoverageMap will free memory for us later. if (result != STORED) { @@ -1339,6 +1340,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp bool childWasInView = (childNode && params.deltaViewFrustum && + (params.lastViewFrustum && ViewFrustum::INSIDE == childNode->inFrustum(*params.lastViewFrustum))); // There are two types of nodes for which we want to send colors: @@ -1409,31 +1411,31 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // We only need to keep digging, if there is at least one child that is inView, and not a leaf. keepDiggingDeeper = (inViewNotLeafCount > 0); - + // If we have enough room to copy our local results into the buffer, then do so... if (availableBytes >= bytesAtThisLevel) { memcpy(outputBuffer, &thisLevelBuffer[0], bytesAtThisLevel); - + outputBuffer += bytesAtThisLevel; availableBytes -= bytesAtThisLevel; } else { bag.insert(node); return 0; } - + if (keepDiggingDeeper) { // at this point, we need to iterate the children who are in view, even if not colored - // and we need to determine if there's a deeper tree below them that we care about. + // and we need to determine if there's a deeper tree below them that we care about. // - // Since this recursive function assumes we're already writing, we know we've already written our + // Since this recursive function assumes we're already writing, we know we've already written our // childrenExistInPacketBits. But... we don't really know how big the child tree will be. And we don't know if // we'll have room in our buffer to actually write all these child trees. What we kinda would like to do is - // write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they + // write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they // write something, we keep them in the bits, if they don't, we take them out. // // we know the last thing we wrote to the outputBuffer was our childrenExistInPacketBits. Let's remember where that was! unsigned char* childExistsPlaceHolder = outputBuffer-sizeof(childrenExistInPacketBits); - + // we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the // final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes, // and then later reshuffle these sections of our output buffer back into normal order. This allows us to make @@ -1450,19 +1452,19 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp int originalIndex = indexOfChildren[indexByDistance]; if (oneAtBit(childrenExistInPacketBits, originalIndex)) { - + int thisLevel = currentEncodeLevel; - // remember this for reshuffling + // remember this for reshuffling recursiveSliceStarts[originalIndex] = outputBuffer; - - int childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, outputBuffer, availableBytes, bag, + + int childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, outputBuffer, availableBytes, bag, params, thisLevel); - // remember this for reshuffling + // remember this for reshuffling recursiveSliceSizes[originalIndex] = childTreeBytesOut; allSlicesSize += childTreeBytesOut; - + // if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space, // basically, the children below don't contain any info. @@ -1470,9 +1472,9 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // byte and the child exist byte. // assert(childTreeBytesOut != 1); - + // if the child tree wrote just 2 bytes, then it means: it had no colors and no child nodes, because... - // if it had colors it would write 1 byte for the color mask, + // if it had colors it would write 1 byte for the color mask, // and at least a color's worth of bytes for the node of colors. // if it had child trees (with something in them) then it would have the 1 byte for child mask // and some number of bytes of lower children... @@ -1502,9 +1504,9 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // reshuffle here... if (params.wantOcclusionCulling) { unsigned char tempReshuffleBuffer[MAX_VOXEL_PACKET_SIZE]; - + unsigned char* tempBufferTo = &tempReshuffleBuffer[0]; // this is our temporary destination - + // iterate through our childrenExistInPacketBits, these will be the sections of the packet that we copied subTree // details into. Unfortunately, they're in distance sorted order, not original index order. we need to put them // back into original distance order @@ -1517,32 +1519,32 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp tempBufferTo += thisSliceSize; } } - + // now that all slices are back in the correct order, copy them to the correct output buffer memcpy(firstRecursiveSlice, &tempReshuffleBuffer[0], allSlicesSize); } - - + + } // end keepDiggingDeeper - + return bytesAtThisLevel; } 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); - - // get file length.... + printLog("loading file %s...\n", fileName); + + // get file length.... unsigned long fileLength = file.tellg(); file.seekg( 0, std::ios::beg ); - + // read the entire file into a buffer, WHAT!? Why not. unsigned char* entireFile = new unsigned char[fileLength]; file.read((char*)entireFile, fileLength); readBitstreamToTree(entireFile, fileLength, WANT_COLOR, NO_EXISTS_BITS); delete[] entireFile; - + file.close(); return true; } @@ -1555,12 +1557,91 @@ bool VoxelTree::readFromSquareARGB32Pixels(const uint32_t* pixels, int dimension return true; } +bool VoxelTree::readFromSchematicFile(const char *fileName) { + std::stringstream ss; + int err = retrieveData(fileName, ss); + if (err && ss.get() != TAG_Compound) { + printLog("[ERROR] Invalid schematic file.\n"); + return false; + } + + ss.get(); + TagCompound schematics(ss); + if (!schematics.getBlocksId() || !schematics.getBlocksData()) { + printLog("[ERROR] Invalid schematic file.\n"); + return false; + } + + int max = (schematics.getWidth() > schematics.getLength()) ? schematics.getWidth() : schematics.getLength(); + max = (max > schematics.getHeight()) ? max : schematics.getHeight(); + + int scale = 1; + while (max > scale) {scale *= 2;} + float size = 1.0f / scale; + + int create = 1; + int red = 128, green = 128, blue = 128; + int count = 0; + + for (int y = 0; y < schematics.getHeight(); ++y) { + for (int z = 0; z < schematics.getLength(); ++z) { + for (int x = 0; x < schematics.getWidth(); ++x) { + int pos = ((y * schematics.getLength()) + z) * schematics.getWidth() + x; + int id = schematics.getBlocksId()[pos]; + int data = schematics.getBlocksData()[pos]; + + create = 1; + computeBlockColor(id, data, red, green, blue, create); + + switch (create) { + case 1: + createVoxel(size * x, size * y, size * z, size, red, green, blue, true); + ++count; + break; + case 2: + switch (data) { + case 0: + createVoxel(size * x + size / 2, size * y + size / 2, size * z , size / 2, red, green, blue, true); + createVoxel(size * x + size / 2, size * y + size / 2, size * z + size / 2, size / 2, red, green, blue, true); + break; + case 1: + createVoxel(size * x , size * y + size / 2, size * z , size / 2, red, green, blue, true); + createVoxel(size * x , size * y + size / 2, size * z + size / 2, size / 2, red, green, blue, true); + break; + case 2: + createVoxel(size * x , size * y + size / 2, size * z + size / 2, size / 2, red, green, blue, true); + createVoxel(size * x + size / 2, size * y + size / 2, size * z + size / 2, size / 2, red, green, blue, true); + break; + case 3: + createVoxel(size * x , size * y + size / 2, size * z , size / 2, red, green, blue, true); + createVoxel(size * x + size / 2, size * y + size / 2, size * z , size / 2, red, green, blue, true); + break; + } + count += 2; + // There's no break on purpose. + case 3: + createVoxel(size * x , size * y, size * z , size / 2, red, green, blue, true); + createVoxel(size * x + size / 2, size * y, size * z , size / 2, red, green, blue, true); + createVoxel(size * x , size * y, size * z + size / 2, size / 2, red, green, blue, true); + createVoxel(size * x + size / 2, size * y, size * z + size / 2, size / 2, red, green, blue, true); + count += 4; + break; + } + } + } + } + + printLog("Created %d voxels from minecraft import.\n", count); + + return true; +} + void VoxelTree::writeToSVOFile(const char* fileName, VoxelNode* node) const { std::ofstream file(fileName, std::ios::out|std::ios::binary); if(file.is_open()) { - printLog("saving to file %s...\n", fileName); + printLog("saving to file %s...\n", fileName); VoxelNodeBag nodeBag; // If we were given a specific node, start from there, otherwise start from root @@ -1572,13 +1653,13 @@ void VoxelTree::writeToSVOFile(const char* fileName, VoxelNode* node) const { 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(); EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); bytesWritten = encodeTreeBitstream(subTree, &outputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeBag, params); - + file.write((const char*)&outputBuffer[0], bytesWritten); } } @@ -1606,10 +1687,10 @@ void VoxelTree::copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinat 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 EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS, chopLevels); bytesWritten = encodeTreeBitstream(subTree, &outputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeBag, params); @@ -1623,19 +1704,171 @@ void VoxelTree::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destin 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 EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); bytesWritten = sourceTree->encodeTreeBitstream(subTree, &outputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeBag, params); - + // ask destination tree to read the bitstream readBitstreamToTree(&outputBuffer[0], bytesWritten, WANT_COLOR, NO_EXISTS_BITS, destinationNode); } } +void VoxelTree::computeBlockColor(int id, int data, int& red, int& green, int& blue, int& create) { + + switch (id) { + case 1: + case 14: + case 15: + case 16: + case 21: + case 56: + case 73: + case 74: + case 97: + case 129: red = 128; green = 128; blue = 128; break; + case 2: red = 77; green = 117; blue = 66; break; + case 3: + case 60: red = 116; green = 83; blue = 56; break; + case 4: red = 71; green = 71; blue = 71; break; + case 5: + case 125: red = 133; green = 94; blue = 62; break; + case 7: red = 35; green = 35; blue = 35; break; + case 8: + case 9: red = 100; green = 109; blue = 185; break; + case 10: + case 11: red = 192; green = 64; blue = 8; break; + case 12: red = 209; green = 199; blue = 155; break; + case 13: red = 96; green = 94; blue = 93; break; + case 17: red = 71; green = 56; blue = 35; break; + case 18: red = 76; green = 104; blue = 64; break; + case 19: red = 119; green = 119; blue = 37; break; + case 22: red = 22; green = 44; blue = 86; break; + case 23: + case 29: + case 33: + case 61: + case 62: + case 158: red = 61; green = 61; blue = 61; break; + case 24: red = 209; green = 202; blue = 156; break; + case 25: + case 58: + case 84: + case 137: red = 57; green = 38; blue = 25; break; + case 35: + switch (data) { + case 0: red = 234; green = 234; blue = 234; break; + case 1: red = 224; green = 140; blue = 84; break; + case 2: red = 185; green = 90; blue = 194; break; + case 3: red = 124; green = 152; blue = 208; break; + case 4: red = 165; green = 154; blue = 35; break; + case 5: red = 70; green = 187; blue = 61; break; + case 6: red = 206; green = 124; blue = 145; break; + case 7: red = 66; green = 66; blue = 66; break; + case 8: red = 170; green = 176; blue = 176; break; + case 9: red = 45; green = 108; blue = 35; break; + case 10: red = 130; green = 62; blue = 8; break; + case 11: red = 43; green = 51; blue = 29; break; + case 12: red = 73; green = 47; blue = 29; break; + case 13: red = 57; green = 76; blue = 36; break; + case 14: red = 165; green = 58; blue = 53; break; + case 15: red = 24; green = 24; blue = 24; break; + default: + create = 0; + break; + } + break; + case 41: red = 239; green = 238; blue = 105; break; + case 42: red = 146; green = 146; blue = 146; break; + case 43: + case 98: red = 161; green = 161; blue = 161; break; + case 44: + create = 3; + + switch (data) { + case 0: red = 161; green = 161; blue = 161; break; + case 1: red = 209; green = 202; blue = 156; break; + case 2: red = 133; green = 94; blue = 62; break; + case 3: red = 71; green = 71; blue = 71; break; + case 4: red = 121; green = 67; blue = 53; break; + case 5: red = 161; green = 161; blue = 161; break; + case 6: red = 45; green = 22; blue = 26; break; + case 7: red = 195; green = 192; blue = 185; break; + default: + create = 0; + break; + } + break; + case 45: red = 121; green = 67; blue = 53; break; + case 46: red = 118; green = 36; blue = 13; break; + case 47: red = 155; green = 127; blue = 76; break; + case 48: red = 61; green = 79; blue = 61; break; + case 49: red = 52; green = 41; blue = 74; break; + case 52: red = 12; green = 66; blue = 71; break; + case 53: + case 67: + case 108: + case 109: + case 114: + case 128: + case 134: + case 135: + case 136: + case 156: + create = 2; + + switch (id) { + case 53: + case 134: + case 135: + case 136: red = 133; green = 94; blue = 62; break; + case 67: red = 71; green = 71; blue = 71; break; + case 108: red = 121; green = 67; blue = 53; break; + case 109: red = 161; green = 161; blue = 161; break; + case 114: red = 45; green = 22; blue = 26; break; + case 128: red = 209; green = 202; blue = 156; break; + case 156: red = 195; green = 192; blue = 185; break; + default: + create = 0; + break; + } + break; + case 54: + case 95: + case 146: red = 155; green = 105; blue = 32; break; + case 57: red = 145; green = 219; blue = 215; break; + case 79: red = 142; green = 162; blue = 195; break; + case 80: red = 255; green = 255; blue = 255; break; + case 81: red = 8; green = 64; blue = 15; break; + case 82: red = 150; green = 155; blue = 166; break; + case 86: + case 91: red = 179; green = 108; blue = 17; break; + case 87: + case 153: red = 91; green = 31; blue = 30; break; + case 88: red = 68; green = 49; blue = 38; break; + case 89: red = 180; green = 134; blue = 65; break; + case 103: red = 141; green = 143; blue = 36; break; + case 110: red = 103; green = 92; blue = 95; break; + case 112: red = 45; green = 22; blue = 26; break; + case 121: red = 183; green = 178; blue = 129; break; + case 123: red = 101; green = 59; blue = 31; break; + case 124: red = 213; green = 178; blue = 123; break; + case 130: red = 38; green = 54; blue = 56; break; + case 133: red = 53; green = 84; blue = 85; break; + case 152: red = 131; green = 22; blue = 7; break; + case 155: red = 195; green = 192; blue = 185; break; + case 159: red = 195; green = 165; blue = 150; break; + case 170: red = 168; green = 139; blue = 15; break; + case 172: red = 140; green = 86; blue = 61; break; + case 173: red = 9; green = 9; blue = 9; break; + default: + create = 0; + break; + } +} diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index a6d1000301..a52e8cf491 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -137,6 +137,8 @@ public: bool readFromSVOFile(const char* filename); // reads voxels from square image with alpha as a Y-axis bool readFromSquareARGB32Pixels(const uint32_t* pixels, int dimension); + bool readFromSchematicFile(const char* filename); + void computeBlockColor(int id, int data, int& r, int& g, int& b, int& create); unsigned long getVoxelCount();