diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 85d4749b27..7cca9d3ef5 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1036,6 +1036,13 @@ void OctreeServer::readConfiguration() { strcpy(_persistFilename, qPrintable(persistFilename)); qDebug("persistFilename=%s", _persistFilename); + QString persistAsFileType; + if (!readOptionString(QString("persistAsFileType"), settingsSectionObject, persistAsFileType)) { + persistAsFileType = "svo"; + } + _persistAsFileType = persistAsFileType; + qDebug() << "persistAsFileType=" << _persistAsFileType; + _persistInterval = OctreePersistThread::DEFAULT_PERSIST_INTERVAL; readOptionInt(QString("persistInterval"), settingsSectionObject, _persistInterval); qDebug() << "persistInterval=" << _persistInterval; @@ -1131,7 +1138,7 @@ void OctreeServer::run() { // now set up PersistThread _persistThread = new OctreePersistThread(_tree, _persistFilename, _persistInterval, - _wantBackup, _settings, _debugTimestampNow); + _wantBackup, _settings, _debugTimestampNow, _persistAsFileType); if (_persistThread) { _persistThread->initialize(true); } diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 0f90c2941e..41cd3259cf 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -157,6 +157,7 @@ protected: QString _statusHost; char _persistFilename[MAX_FILENAME_LENGTH]; + QString _persistAsFileType; int _packetsPerClientPerInterval; int _packetsTotalPerInterval; Octree* _tree; // this IS a reaveraging tree diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index fdaede8c44..9af44f6a1b 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -325,6 +325,24 @@ "default": "resources/models.svo", "advanced": true }, + { + "name": "persistAsFileType", + "label": "File format for entity server's persistent data", + "help": "This defines how the entity server will save entities to disk.", + "default": "svo", + "type": "select", + "options": [ + { + "value": "svo", + "label": "Entity server persists data as SVO" + }, + { + "value": "json", + "label": "Entity server persists data as JSON" + } + ], + "advanced": true + }, { "name": "persistInterval", "label": "Save Check Interval", diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 184d1bf940..147162384b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -891,9 +891,10 @@ bool Application::event(QEvent* event) { DependencyManager::get()->handleLookupString(fileEvent->url().toString()); } else if (url.path().toLower().endsWith(SVO_EXTENSION)) { emit svoImportRequested(url.url()); + } else if (url.path().toLower().endsWith(JS_EXTENSION)) { + askToLoadScript(url.toString()); } } - return false; } @@ -1454,31 +1455,43 @@ void Application::wheelEvent(QWheelEvent* event) { void Application::dropEvent(QDropEvent *event) { QString snapshotPath; const QMimeData *mimeData = event->mimeData(); + bool atLeastOneFileAccepted = false; foreach (QUrl url, mimeData->urls()) { auto lower = url.path().toLower(); if (lower.endsWith(SNAPSHOT_EXTENSION)) { snapshotPath = url.toLocalFile(); - break; + + + SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath); + if (snapshotData) { + if (!snapshotData->getDomain().isEmpty()) { + DependencyManager::get()->getDomainHandler().setHostnameAndPort(snapshotData->getDomain()); + } + + _myAvatar->setPosition(snapshotData->getLocation()); + _myAvatar->setOrientation(snapshotData->getOrientation()); + atLeastOneFileAccepted = true; + break; // don't process further files + } else { + QMessageBox msgBox; + msgBox.setText("No location details were found in the file " + + snapshotPath + ", try dragging in an authentic Hifi snapshot."); + + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + } } else if (lower.endsWith(SVO_EXTENSION)) { emit svoImportRequested(url.url()); event->acceptProposedAction(); - return; + atLeastOneFileAccepted = true; + } else if (lower.endsWith(JS_EXTENSION)) { + askToLoadScript(url.url()); + atLeastOneFileAccepted = true; } } - - SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath); - if (snapshotData) { - if (!snapshotData->getDomain().isEmpty()) { - DependencyManager::get()->getDomainHandler().setHostnameAndPort(snapshotData->getDomain()); - } - - _myAvatar->setPosition(snapshotData->getLocation()); - _myAvatar->setOrientation(snapshotData->getOrientation()); - } else { - QMessageBox msgBox; - msgBox.setText("No location details were found in this JPG, try dragging in an authentic Hifi snapshot."); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.exec(); + + if (atLeastOneFileAccepted) { + event->acceptProposedAction(); } } @@ -1798,7 +1811,7 @@ bool Application::importEntities(const QString& urlOrFilename) { url = QUrl::fromLocalFile(urlOrFilename); } - bool success = _entityClipboard.readFromSVOURL(url.toString()); + bool success = _entityClipboard.readFromURL(url.toString()); if (success) { _entityClipboard.reaverageOctreeElements(); } @@ -2795,7 +2808,31 @@ QImage Application::renderAvatarBillboard() { return image; } +// FIXME, preprocessor guard this check to occur only in DEBUG builds +static QThread * activeRenderingThread = nullptr; + +ViewFrustum* Application::getViewFrustum() { +#ifdef DEBUG + if (QThread::currentThread() == activeRenderingThread) { + // FIXME, should this be an assert? + qWarning() << "Calling Application::getViewFrustum() from the active rendering thread, did you mean Application::getDisplayViewFrustum()?"; + } +#endif + return &_viewFrustum; +} + +ViewFrustum* Application::getDisplayViewFrustum() { +#ifdef DEBUG + if (QThread::currentThread() != activeRenderingThread) { + // FIXME, should this be an assert? + qWarning() << "Calling Application::getDisplayViewFrustum() from outside the active rendering thread or outside rendering, did you mean Application::getViewFrustum()?"; + } +#endif + return &_displayViewFrustum; +} + void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs::RenderSide renderSide) { + activeRenderingThread = QThread::currentThread(); PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("display"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()"); @@ -3020,6 +3057,7 @@ void Application::displaySide(Camera& theCamera, bool selfAvatarOnly, RenderArgs glClear(GL_DEPTH_BUFFER_BIT); _overlays.renderWorld(true); } + activeRenderingThread = nullptr; } void Application::updateUntranslatedViewMatrix(const glm::vec3& viewMatrixTranslation) { @@ -3112,17 +3150,25 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { _myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale()); } else { // HEAD zoom level - _mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees - if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead()->getFaceModel().isActive()) { - // as a hack until we have a better way of dealing with coordinate precision issues, reposition the - // face/body so that the average eye position lies at the origin - eyeRelativeCamera = true; - _mirrorCamera.setPosition(_myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale()); + // FIXME note that the positioing of the camera relative to the avatar can suffer limited + // precision as the user's position moves further away from the origin. Thus at + // /1e7,1e7,1e7 (well outside the buildable volume) the mirror camera veers and sways + // wildly as you rotate your avatar because the floating point values are becoming + // larger, squeezing out the available digits of precision you have available at the + // human scale for camera positioning. - } else { - _mirrorCamera.setPosition(_myAvatar->getHead()->getEyePosition() + - _myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale()); - } + // Previously there was a hack to correct this using the mechanism of repositioning + // the avatar at the origin of the world for the purposes of rendering the mirror, + // but it resulted in failing to render the avatar's head model in the mirror view + // when in first person mode. Presumably this was because of some missed culling logic + // that was not accounted for in the hack. + + // This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further + // investigated in order to adapt the technique while fixing the head rendering issue, + // but the complexity of the hack suggests that a better approach + _mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees + _mirrorCamera.setPosition(_myAvatar->getHead()->getEyePosition() + + _myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale()); } _mirrorCamera.setAspectRatio((float)region.width() / region.height()); @@ -3149,58 +3195,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { // render rear mirror view glPushMatrix(); - if (eyeRelativeCamera) { - // save absolute translations - glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation(); - glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation(); - - // get the neck position so we can translate the face relative to it - glm::vec3 neckPosition; - _myAvatar->getSkeletonModel().setTranslation(glm::vec3()); - _myAvatar->getSkeletonModel().getNeckPosition(neckPosition); - - // get the eye position relative to the body - glm::vec3 eyePosition = _myAvatar->getHead()->getEyePosition(); - float eyeHeight = eyePosition.y - _myAvatar->getPosition().y; - - // set the translation of the face relative to the neck position - _myAvatar->getHead()->getFaceModel().setTranslation(neckPosition - glm::vec3(0, eyeHeight, 0)); - - // translate the neck relative to the face - _myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() - - neckPosition); - - // update the attachments to match - QVector absoluteAttachmentTranslations; - glm::vec3 delta = _myAvatar->getSkeletonModel().getTranslation() - absoluteSkeletonTranslation; - foreach (Model* attachment, _myAvatar->getAttachmentModels()) { - absoluteAttachmentTranslations.append(attachment->getTranslation()); - attachment->setTranslation(attachment->getTranslation() + delta); - } - - // and lo, even the shadow matrices - glm::mat4 savedShadowMatrices[CASCADED_SHADOW_MATRIX_COUNT]; - for (int i = 0; i < CASCADED_SHADOW_MATRIX_COUNT; i++) { - savedShadowMatrices[i] = _shadowMatrices[i]; - _shadowMatrices[i] = glm::transpose(glm::transpose(_shadowMatrices[i]) * glm::translate(-delta)); - } - - displaySide(_mirrorCamera, true); - - // restore absolute translations - _myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation); - _myAvatar->getHead()->getFaceModel().setTranslation(absoluteFaceTranslation); - for (int i = 0; i < absoluteAttachmentTranslations.size(); i++) { - _myAvatar->getAttachmentModels().at(i)->setTranslation(absoluteAttachmentTranslations.at(i)); - } - - // restore the shadow matrices - for (int i = 0; i < CASCADED_SHADOW_MATRIX_COUNT; i++) { - _shadowMatrices[i] = savedShadowMatrices[i]; - } - } else { - displaySide(_mirrorCamera, true); - } + displaySide(_mirrorCamera, true); glPopMatrix(); if (!billboard) { @@ -3601,6 +3596,19 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri workerThread->start(); } +void Application::askToLoadScript(const QString& scriptFilenameOrURL) { + QMessageBox::StandardButton reply; + QString message = "Would you like to run this script:\n" + scriptFilenameOrURL; + reply = QMessageBox::question(getWindow(), "Run Script", message, QMessageBox::Yes|QMessageBox::No); + + if (reply == QMessageBox::Yes) { + qDebug() << "Chose to run the script: " << scriptFilenameOrURL; + loadScript(scriptFilenameOrURL); + } else { + qDebug() << "Declined to run the script: " << scriptFilenameOrURL; + } +} + ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded, bool loadScriptFromEditor, bool activateMainWindow) { diff --git a/interface/src/Application.h b/interface/src/Application.h index b013692393..49fdde5fb2 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -95,6 +95,7 @@ static const float NODE_KILLED_BLUE = 0.0f; static const QString SNAPSHOT_EXTENSION = ".jpg"; static const QString SVO_EXTENSION = ".svo"; +static const QString JS_EXTENSION = ".js"; static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees static const float BILLBOARD_DISTANCE = 5.56f; // meters @@ -174,8 +175,12 @@ public: bool isThrottleRendering() const { return _glWidget->isThrottleRendering(); } Camera* getCamera() { return &_myCamera; } - ViewFrustum* getViewFrustum() { return &_viewFrustum; } - ViewFrustum* getDisplayViewFrustum() { return &_displayViewFrustum; } + // Represents the current view frustum of the avatar. + ViewFrustum* getViewFrustum(); + // Represents the view frustum of the current rendering pass, + // which might be different from the viewFrustum, i.e. shadowmap + // passes, mirror window passes, etc + ViewFrustum* getDisplayViewFrustum(); ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; } const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; } EntityTreeRenderer* getEntities() { return &_entities; } @@ -336,6 +341,7 @@ public slots: void loadDialog(); void loadScriptURLDialog(); void toggleLogDialog(); + void askToLoadScript(const QString& scriptFilenameOrURL); ScriptEngine* loadScript(const QString& scriptFilename = QString(), bool isUserLoaded = true, bool loadScriptFromEditor = false, bool activateMainWindow = false); void scriptFinished(const QString& scriptName); diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 4587fca0f4..c615083335 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -168,10 +168,10 @@ void GLCanvas::wheelEvent(QWheelEvent* event) { } void GLCanvas::dragEnterEvent(QDragEnterEvent* event) { - const QMimeData *mimeData = event->mimeData(); + const QMimeData* mimeData = event->mimeData(); foreach (QUrl url, mimeData->urls()) { auto lower = url.path().toLower(); - if (lower.endsWith(SNAPSHOT_EXTENSION) || lower.endsWith(SVO_EXTENSION)) { + if (lower.endsWith(SNAPSHOT_EXTENSION) || lower.endsWith(SVO_EXTENSION) || lower.endsWith(JS_EXTENSION)) { event->acceptProposedAction(); break; } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 75d77b780a..431b6e0d37 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -338,7 +338,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode, bool // simple frustum check float boundingRadius = getBillboardSize(); ViewFrustum* frustum = (renderMode == Avatar::SHADOW_RENDER_MODE) ? - Application::getInstance()->getShadowViewFrustum() : Application::getInstance()->getViewFrustum(); + Application::getInstance()->getShadowViewFrustum() : Application::getInstance()->getDisplayViewFrustum(); if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) { return; } @@ -1013,16 +1013,18 @@ float Avatar::getSkeletonHeight() const { } float Avatar::getHeadHeight() const { - Extents extents = getHead()->getFaceModel().getBindExtents(); + Extents extents = getHead()->getFaceModel().getMeshExtents(); if (!extents.isEmpty()) { return extents.maximum.y - extents.minimum.y; } + + extents = _skeletonModel.getMeshExtents(); glm::vec3 neckPosition; - glm::vec3 headPosition; - if (_skeletonModel.getNeckPosition(neckPosition) && _skeletonModel.getHeadPosition(headPosition)) { - return glm::distance(neckPosition, headPosition); + if (!extents.isEmpty() && _skeletonModel.getNeckPosition(neckPosition)) { + return extents.maximum.y / 2.0f - neckPosition.y + _position.y; } - const float DEFAULT_HEAD_HEIGHT = 0.1f; + + const float DEFAULT_HEAD_HEIGHT = 0.25f; return DEFAULT_HEAD_HEIGHT; } diff --git a/interface/src/ui/DataWebPage.cpp b/interface/src/ui/DataWebPage.cpp index bec2c98f55..c24e34fb64 100644 --- a/interface/src/ui/DataWebPage.cpp +++ b/interface/src/ui/DataWebPage.cpp @@ -37,6 +37,9 @@ bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkReques if (request.url().path().toLower().endsWith(SVO_EXTENSION)) { Application::getInstance()->importSVOFromURL(request.url()); return false; + } else if (request.url().path().toLower().endsWith(JS_EXTENSION)) { + Application::getInstance()->askToLoadScript(request.url().toString()); + return false; } return true; } else { diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index c7b350f100..158deb00ff 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -122,7 +122,7 @@ void Overlays::renderWorld(bool drawFront, RenderArgs::RenderMode renderMode, Re float myAvatarScale = 1.0f; auto lodManager = DependencyManager::get(); - RenderArgs args = { NULL, Application::getInstance()->getViewFrustum(), + RenderArgs args = { NULL, Application::getInstance()->getDisplayViewFrustum(), lodManager->getOctreeSizeScale(), lodManager->getBoundaryLevelAdjust(), renderMode, renderSide, diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c083fbc085..0dcab2c81b 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -812,7 +812,7 @@ void AudioClient::handleAudioInput() { } emit inputReceived(QByteArray(reinterpret_cast(networkAudioSamples), - AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL)); + AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * sizeof(AudioConstants::AudioSample))); } else { // our input loudness is 0, since we're muted diff --git a/libraries/avatars/src/Player.cpp b/libraries/avatars/src/Player.cpp index 2b92acb189..b5cca61461 100644 --- a/libraries/avatars/src/Player.cpp +++ b/libraries/avatars/src/Player.cpp @@ -169,6 +169,8 @@ void Player::setupAudioThread() { _audioThread->setObjectName("Player Audio Thread"); _options.position = _avatar->getPosition(); _options.orientation = _avatar->getOrientation(); + _options.stereo = _recording->numberAudioChannel() == 2; + _injector.reset(new AudioInjector(_recording->getAudioData(), _options), &QObject::deleteLater); _injector->moveToThread(_audioThread); _audioThread->start(); @@ -309,7 +311,7 @@ void Player::setCurrentFrame(int currentFrame) { if (isPlaying()) { _timer.start(); - setAudionInjectorPosition(); + setAudioInjectorPosition(); } else { _pausedFrame = _currentFrame; } @@ -349,15 +351,7 @@ void Player::setCurrentTime(int currentTime) { } } - _currentFrame = lowestBound; - _timerOffset = _recording->getFrameTimestamp(lowestBound); - - if (isPlaying()) { - _timer.start(); - setAudionInjectorPosition(); - } else { - _pausedFrame = lowestBound; - } + setCurrentFrame(lowestBound); } void Player::setVolume(float volume) { @@ -372,11 +366,9 @@ void Player::setAudioOffset(int audioOffset) { _audioOffset = audioOffset; } -void Player::setAudionInjectorPosition() { +void Player::setAudioInjectorPosition() { int MSEC_PER_SEC = 1000; - int SAMPLE_SIZE = 2; // 16 bits - int CHANNEL_COUNT = 1; - int FRAME_SIZE = SAMPLE_SIZE * CHANNEL_COUNT; + int FRAME_SIZE = sizeof(AudioConstants::AudioSample) * _recording->numberAudioChannel(); int currentAudioFrame = elapsed() * FRAME_SIZE * (AudioConstants::SAMPLE_RATE / MSEC_PER_SEC); _injector->setCurrentSendPosition(currentAudioFrame); } diff --git a/libraries/avatars/src/Player.h b/libraries/avatars/src/Player.h index 7ed76fa495..96f3cbc268 100644 --- a/libraries/avatars/src/Player.h +++ b/libraries/avatars/src/Player.h @@ -61,7 +61,7 @@ private: void setupAudioThread(); void cleanupAudioThread(); void loopRecording(); - void setAudionInjectorPosition(); + void setAudioInjectorPosition(); bool computeCurrentFrame(); AvatarData* _avatar; diff --git a/libraries/avatars/src/Recording.cpp b/libraries/avatars/src/Recording.cpp index 7fa8b79bda..6901e5e10d 100644 --- a/libraries/avatars/src/Recording.cpp +++ b/libraries/avatars/src/Recording.cpp @@ -65,6 +65,15 @@ const RecordingFrame& Recording::getFrame(int i) const { return _frames[i]; } + +int Recording::numberAudioChannel() const { + // Check for stereo audio + int MSEC_PER_SEC = 1000; + int channelLength = (getLength() / MSEC_PER_SEC) * + AudioConstants::SAMPLE_RATE * sizeof(AudioConstants::AudioSample); + return glm::round((float)channelLength / (float)getAudioData().size()); +} + void Recording::addFrame(int timestamp, RecordingFrame &frame) { _timestamps << timestamp; _frames << frame; diff --git a/libraries/avatars/src/Recording.h b/libraries/avatars/src/Recording.h index d1da77560c..49d12ec5b5 100644 --- a/libraries/avatars/src/Recording.h +++ b/libraries/avatars/src/Recording.h @@ -56,6 +56,7 @@ public: qint32 getFrameTimestamp(int i) const; const RecordingFrame& getFrame(int i) const; const QByteArray& getAudioData() const { return _audioData; } + int numberAudioChannel() const; protected: void addFrame(int timestamp, RecordingFrame& frame); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 75c9f07881..7e57323d68 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -266,4 +266,53 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo, precisionPicking); } +bool RenderableModelEntityItem::isReadyToComputeShape() { + if (_collisionModelURL == "") { + // no model url, so we're ready to compute a shape. + return true; + } + if (! _collisionNetworkGeometry.isNull() && _collisionNetworkGeometry->isLoadedWithTextures()) { + // we have a _collisionModelURL AND a _collisionNetworkGeometry AND it's fully loaded. + return true; + } + + if (_collisionNetworkGeometry.isNull()) { + // we have a _collisionModelURL but we don't yet have a _collisionNetworkGeometry. + _collisionNetworkGeometry = + DependencyManager::get()->getGeometry(_collisionModelURL, QUrl(), false, false); + + if (! _collisionNetworkGeometry.isNull() && _collisionNetworkGeometry->isLoadedWithTextures()) { + // shortcut in case it's already loaded. + return true; + } + } + + // the model is still being downloaded. + return false; +} + +void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { + if (_collisionModelURL == "") { + info.setParams(getShapeType(), 0.5f * getDimensions()); + } else { + const FBXGeometry& fbxGeometry = _collisionNetworkGeometry->getFBXGeometry(); + + _points.clear(); + foreach (const FBXMesh& mesh, fbxGeometry.meshes) { + _points << mesh.vertices; + } + + info.setParams(getShapeType(), 0.5f * getDimensions(), NULL, _collisionModelURL); + info.setConvexHull(_points); + } +} + +ShapeType RenderableModelEntityItem::getShapeType() const { + // XXX make hull an option in edit.js ? + if (_collisionModelURL != "") { + return SHAPE_TYPE_CONVEX_HULL; + } else { + return _shapeType; + } +} diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 65cded0207..6d50a52a95 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -30,7 +30,8 @@ public: _needsInitialSimulation(true), _needsModelReload(true), _myRenderer(NULL), - _originalTexturesRead(false) { } + _originalTexturesRead(false), + _collisionNetworkGeometry(QSharedPointer()) { } virtual ~RenderableModelEntityItem(); @@ -52,6 +53,10 @@ public: bool needsToCallUpdate() const; + bool isReadyToComputeShape(); + void computeShapeInfo(ShapeInfo& info); + ShapeType getShapeType() const; + private: void remapTextures(); @@ -62,6 +67,9 @@ private: QString _currentTextures; QStringList _originalTextures; bool _originalTexturesRead; + + QSharedPointer _collisionNetworkGeometry; + QVector _points; }; #endif // hifi_RenderableModelEntityItem_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 4f74438a45..5df0bec562 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1002,7 +1002,7 @@ float EntityItem::getRadius() const { return 0.5f * glm::length(_dimensions); } -void EntityItem::computeShapeInfo(ShapeInfo& info) const { +void EntityItem::computeShapeInfo(ShapeInfo& info) { info.setParams(getShapeType(), 0.5f * getDimensions()); } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 5193aa4490..03e6868cc8 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -43,7 +43,7 @@ class EntityTreeElementExtraEncodeData; /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate /// one directly, instead you must only construct one of it's derived classes with additional features. -class EntityItem { +class EntityItem { friend class EntityTreeElement; public: enum EntityDirtyFlags { @@ -256,7 +256,9 @@ public: virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); } virtual bool containsInDomainUnits(const glm::vec3& point) const { return getAABoxInDomainUnits().contains(point); } - virtual void computeShapeInfo(ShapeInfo& info) const; + + virtual bool isReadyToComputeShape() { return true; } + virtual void computeShapeInfo(ShapeInfo& info); /// return preferred shape type (actual physical shape may differ) virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } @@ -295,7 +297,6 @@ public: static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; } static bool getSendPhysicsUpdates() { return _sendPhysicsUpdates; } - protected: static bool _sendPhysicsUpdates; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 2b8e82d28f..dbb3cfa338 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -176,7 +176,8 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) { _lastEdited = usecTime > _created ? usecTime : _created; } -const char* shapeTypeNames[] = {"none", "box", "sphere"}; +const char* shapeTypeNames[] = {"none", "box", "sphere", "ellipsoid", "convex-hull", "plane", "compound", "capsule-x", + "capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"}; QHash stringToShapeTypeLookup; @@ -184,10 +185,22 @@ void buildStringToShapeTypeLookup() { stringToShapeTypeLookup["none"] = SHAPE_TYPE_NONE; stringToShapeTypeLookup["box"] = SHAPE_TYPE_BOX; stringToShapeTypeLookup["sphere"] = SHAPE_TYPE_SPHERE; + stringToShapeTypeLookup["ellipsoid"] = SHAPE_TYPE_ELLIPSOID; + stringToShapeTypeLookup["convex-hull"] = SHAPE_TYPE_CONVEX_HULL; + stringToShapeTypeLookup["plane"] = SHAPE_TYPE_PLANE; + stringToShapeTypeLookup["compound"] = SHAPE_TYPE_COMPOUND; + stringToShapeTypeLookup["capsule-x"] = SHAPE_TYPE_CAPSULE_X; + stringToShapeTypeLookup["capsule-y"] = SHAPE_TYPE_CAPSULE_Y; + stringToShapeTypeLookup["capsule-z"] = SHAPE_TYPE_CAPSULE_Z; + stringToShapeTypeLookup["cylinder-x"] = SHAPE_TYPE_CYLINDER_X; + stringToShapeTypeLookup["cylinder-y"] = SHAPE_TYPE_CYLINDER_Y; + stringToShapeTypeLookup["cylinder-z"] = SHAPE_TYPE_CYLINDER_Z; } QString EntityItemProperties::getShapeTypeAsString() const { - return QString(shapeTypeNames[_shapeType]); + if (_shapeType < sizeof(shapeTypeNames) / sizeof(char *)) + return QString(shapeTypeNames[_shapeType]); + return QString("none"); } void EntityItemProperties::setShapeTypeFromString(const QString& shapeName) { diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 2585b5d33e..5624e0765b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "EntityScriptingInterface.h" #include "EntityTree.h" #include "LightEntityItem.h" diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 9be004828b..3ccff46a04 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -10,13 +10,18 @@ // #include +#include +#include #include "EntityTree.h" #include "EntitySimulation.h" +#include "VariantMapToScriptValue.h" #include "AddEntityOperator.h" #include "MovingEntitiesOperator.h" #include "UpdateEntityOperator.h" +#include "QVariantGLM.h" +#include "RecurseOctreeToMapOperator.h" EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage), @@ -466,7 +471,7 @@ bool EntityTree::findNearPointOperation(OctreeElement* element, void* extraData) return true; // keep searching in case children have closer entities } - // if this element doesn't contain the point, then none of it's children can contain the point, so stop searching + // if this element doesn't contain the point, then none of its children can contain the point, so stop searching return false; } @@ -1080,3 +1085,41 @@ bool EntityTree::sendEntitiesOperation(OctreeElement* element, void* extraData) return true; } +bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElement* element) { + entityDescription["Entities"] = QVariantList(); + QScriptEngine scriptEngine; + RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine); + recurseTreeWithOperator(&theOperator); + return true; +} + +bool EntityTree::readFromMap(QVariantMap& map) { + // map will have a top-level list keyed as "Entities". This will be extracted + // and iterated over. Each member of this list is converted to a QVariantMap, then + // to a QScriptValue, and then to EntityItemProperties. These properties are used + // to add the new entity to the EnitytTree. + QVariantList entitiesQList = map["Entities"].toList(); + QScriptEngine scriptEngine; + + foreach (QVariant entityVariant, entitiesQList) { + // QVariantMap --> QScriptValue --> EntityItemProperties --> Entity + QVariantMap entityMap = entityVariant.toMap(); + QScriptValue entityScriptValue = variantMapToScriptValue(entityMap, scriptEngine); + EntityItemProperties properties; + EntityItemPropertiesFromScriptValue(entityScriptValue, properties); + + EntityItemID entityItemID; + if (entityMap.contains("id")) { + entityItemID = EntityItemID(QUuid(entityMap["id"].toString())); + } else { + entityItemID = EntityItemID(QUuid::createUuid()); + } + + EntityItem* entity = addEntity(entityItemID, properties); + if (!entity) { + qDebug() << "adding Entity failed:" << entityItemID << entity->getType(); + } + } + + return true; +} diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 75a5daab4e..e1521ebd50 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -164,6 +164,9 @@ public: bool wantEditLogging() const { return _wantEditLogging; } void setWantEditLogging(bool value) { _wantEditLogging = value; } + bool writeToMap(QVariantMap& entityDescription, OctreeElement* element); + bool readFromMap(QVariantMap& entityDescription); + signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index d2a2714982..ce009988b1 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -17,6 +17,7 @@ #include "EntityTree.h" #include "EntityTreeElement.h" #include "ModelEntityItem.h" +#include "ResourceCache.h" const QString ModelEntityItem::DEFAULT_MODEL_URL = QString(""); const QString ModelEntityItem::DEFAULT_COLLISION_MODEL_URL = QString(""); diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.cpp b/libraries/entities/src/RecurseOctreeToMapOperator.cpp new file mode 100644 index 0000000000..afe28e17e0 --- /dev/null +++ b/libraries/entities/src/RecurseOctreeToMapOperator.cpp @@ -0,0 +1,54 @@ +// +// RecurseOctreeToMapOperator.cpp +// libraries/entities/src +// +// Created by Seth Alves on 3/16/15. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "RecurseOctreeToMapOperator.h" + + +RecurseOctreeToMapOperator::RecurseOctreeToMapOperator(QVariantMap& map, OctreeElement *top, QScriptEngine *engine) : + RecurseOctreeOperator(), + _map(map), + _top(top), + _engine(engine) +{ + // if some element "top" was given, only save information for that element and it's children. + if (_top) { + _withinTop = false; + } else { + // top was NULL, export entire tree. + _withinTop = true; + } +}; + +bool RecurseOctreeToMapOperator::preRecursion(OctreeElement* element) { + if (element == _top) { + _withinTop = true; + } + return true; +} + +bool RecurseOctreeToMapOperator::postRecursion(OctreeElement* element) { + + EntityTreeElement* entityTreeElement = static_cast(element); + const QList& entities = entityTreeElement->getEntities(); + + QVariantList entitiesQList = qvariant_cast(_map["Entities"]); + + foreach (EntityItem* entityItem, entities) { + EntityItemProperties properties = entityItem->getProperties(); + QScriptValue qScriptValues = EntityItemPropertiesToScriptValue(_engine, properties); + entitiesQList << qScriptValues.toVariant(); + } + _map["Entities"] = entitiesQList; + if (element == _top) { + _withinTop = false; + } + return true; +} diff --git a/libraries/entities/src/RecurseOctreeToMapOperator.h b/libraries/entities/src/RecurseOctreeToMapOperator.h new file mode 100644 index 0000000000..6bd44f3cbf --- /dev/null +++ b/libraries/entities/src/RecurseOctreeToMapOperator.h @@ -0,0 +1,24 @@ +// +// RecurseOctreeToMapOperator.h +// libraries/entities/src +// +// Created by Seth Alves on 3/16/15. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "EntityTree.h" + +class RecurseOctreeToMapOperator : public RecurseOctreeOperator { +public: + RecurseOctreeToMapOperator(QVariantMap& map, OctreeElement *top, QScriptEngine *engine); + bool preRecursion(OctreeElement* element); + bool postRecursion(OctreeElement* element); + private: + QVariantMap& _map; + OctreeElement *_top; + QScriptEngine *_engine; + bool _withinTop; +}; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index dfb0d6c150..b574eb1aeb 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -48,14 +48,26 @@ void ResourceCache::refresh(const QUrl& url) { } } -QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) { - +QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, + bool delayLoad, void* extra, bool block) { if (QThread::currentThread() != thread()) { - QSharedPointer result; - QMetaObject::invokeMethod(this, "getResource", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(QSharedPointer, result), Q_ARG(const QUrl&, url), Q_ARG(const QUrl&, fallback), - Q_ARG(bool, delayLoad), Q_ARG(void*, extra)); - return result; + // This will re-call this method in the main thread. If block is true and the main thread + // is waiting on a lock, we'll deadlock here. + if (block) { + QSharedPointer result; + QMetaObject::invokeMethod(this, "getResource", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QSharedPointer, result), Q_ARG(const QUrl&, url), + Q_ARG(const QUrl&, fallback), Q_ARG(bool, delayLoad), Q_ARG(void*, extra)); + return result; + } else { + // Queue the re-invocation of this method, but if the main thread is blocked, don't wait. The + // return value may be NULL -- it's expected that this will be called again later, in order + // to receive the actual Resource. + QMetaObject::invokeMethod(this, "getResource", Qt::QueuedConnection, + Q_ARG(const QUrl&, url), + Q_ARG(const QUrl&, fallback), Q_ARG(bool, delayLoad), Q_ARG(void*, extra)); + return _resources.value(url); + } } if (!url.isValid() && !url.isEmpty() && fallback.isValid()) { diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 11b091a9e3..d4aa9a7fd9 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -89,7 +89,7 @@ protected: /// \param delayLoad if true, don't load the resource immediately; wait until load is first requested /// \param extra extra data to pass to the creator, if appropriate Q_INVOKABLE QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), - bool delayLoad = false, void* extra = NULL); + bool delayLoad = false, void* extra = NULL, bool block = true); /// Creates a new resource. virtual QSharedPointer createResource(const QUrl& url, diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 2b29529f4e..85ea09b5ef 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -27,6 +27,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -35,6 +39,7 @@ #include #include #include +#include #include "CoverageMap.h" #include "OctreeConstants.h" @@ -42,6 +47,9 @@ #include "Octree.h" #include "ViewFrustum.h" + +QVector PERSIST_EXTENSIONS = {"svo", "json"}; + float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) { return voxelSizeScale / powf(2, renderLevel); } @@ -1841,21 +1849,22 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, return bytesAtThisLevel; } -bool Octree::readFromSVOFile(const char* fileName) { +bool Octree::readFromFile(const char* fileName) { bool fileOk = false; - QFile file(fileName); + QString qFileName = findMostRecentFileExtension(fileName, PERSIST_EXTENSIONS); + QFile file(qFileName); fileOk = file.open(QIODevice::ReadOnly); if(fileOk) { QDataStream fileInputStream(&file); - QFileInfo fileInfo(fileName); + QFileInfo fileInfo(qFileName); unsigned long fileLength = fileInfo.size(); emit importSize(1.0f, 1.0f, 1.0f); emit importProgress(0); - qDebug("Loading file %s...", fileName); + qDebug() << "Loading file" << qFileName << "..."; fileOk = readFromStream(fileLength, fileInputStream); @@ -1866,14 +1875,14 @@ bool Octree::readFromSVOFile(const char* fileName) { return fileOk; } -bool Octree::readFromSVOURL(const QString& urlString) { +bool Octree::readFromURL(const QString& urlString) { bool readOk = false; // determine if this is a local file or a network resource QUrl url(urlString); if (url.isLocalFile()) { - readOk = readFromSVOFile(qPrintable(url.toLocalFile())); + readOk = readFromFile(qPrintable(url.toLocalFile())); } else { QNetworkRequest request; request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); @@ -1899,6 +1908,23 @@ bool Octree::readFromSVOURL(const QString& urlString) { bool Octree::readFromStream(unsigned long streamLength, QDataStream& inputStream) { + + // decide if this is SVO or JSON + QIODevice *device = inputStream.device(); + char firstChar; + device->getChar(&firstChar); + device->ungetChar(firstChar); + + if (firstChar == (char) PacketTypeEntityData) { + return readSVOFromStream(streamLength, inputStream); + } else { + return readJSONFromStream(streamLength, inputStream); + } +} + + +bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStream) { + bool fileOk = false; PacketVersion gotVersion = 0; @@ -2026,6 +2052,53 @@ bool Octree::readFromStream(unsigned long streamLength, QDataStream& inputStream return fileOk; } +bool Octree::readJSONFromStream(unsigned long streamLength, QDataStream& inputStream) { + char *rawData = new char[streamLength]; + inputStream.readRawData(rawData, streamLength); + QJsonDocument d = QJsonDocument::fromJson(rawData); + QVariant v = d.toVariant(); + QVariantMap m = v.toMap(); + readFromMap(m); + delete rawData; + return true; +} + +void Octree::writeToFile(const char* fileName, OctreeElement* element, QString persistAsFileType) { + // make the sure file extension makes sense + QString qFileName = fileNameWithoutExtension(QString(fileName), PERSIST_EXTENSIONS) + "." + persistAsFileType; + QByteArray byteArray = qFileName.toUtf8(); + const char* cFileName = byteArray.constData(); + + if (persistAsFileType == "svo") { + writeToSVOFile(fileName, element); + } else if (persistAsFileType == "json") { + writeToJSONFile(cFileName, element); + } else { + qDebug() << "unable to write octree to file of type" << persistAsFileType; + } +} + +void Octree::writeToJSONFile(const char* fileName, OctreeElement* element) { + QFile persistFile(fileName); + QVariantMap entityDescription; + + qDebug("Saving to file %s...", fileName); + + OctreeElement* top; + if (element) { + top = element; + } else { + top = _rootElement; + } + + bool entityDescriptionSuccess = writeToMap(entityDescription, top); + if (entityDescriptionSuccess && persistFile.open(QIODevice::WriteOnly)) { + persistFile.write(QJsonDocument::fromVariant(entityDescription).toJson()); + } else { + qCritical("Could not write to JSON description of entities."); + } +} + void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) { std::ofstream file(fileName, std::ios::out|std::ios::binary); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 672a3b63d5..21c3efc01d 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -35,6 +35,9 @@ class Shape; #include #include + +extern QVector PERSIST_EXTENSIONS; + /// derive from this class to use the Octree::recurseTreeWithOperator() method class RecurseOctreeOperator { public: @@ -324,13 +327,19 @@ public: // Note: this assumes the fileFormat is the HIO individual voxels code files void loadOctreeFile(const char* fileName, bool wantColorRandomizer); - // these will read/write files that match the wireformat, excluding the 'V' leading + // Octree exporters + void writeToFile(const char* filename, OctreeElement* element = NULL, QString persistAsFileType = "svo"); + void writeToJSONFile(const char* filename, OctreeElement* element = NULL); void writeToSVOFile(const char* filename, OctreeElement* element = NULL); + virtual bool writeToMap(QVariantMap& entityDescription, OctreeElement* element) = 0; - bool readFromSVOFile(const char* filename); - bool readFromSVOURL(const QString& url); // will support file urls as well... + // Octree importers + bool readFromFile(const char* filename); + bool readFromURL(const QString& url); // will support file urls as well... bool readFromStream(unsigned long streamLength, QDataStream& inputStream); - + bool readSVOFromStream(unsigned long streamLength, QDataStream& inputStream); + bool readJSONFromStream(unsigned long streamLength, QDataStream& inputStream); + virtual bool readFromMap(QVariantMap& entityDescription) = 0; unsigned long getOctreeElementsCount(); diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 2a563dc50a..02f46ee64b 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -20,16 +20,19 @@ #include #include #include +#include #include #include +#include #include "OctreePersistThread.h" const int OctreePersistThread::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds OctreePersistThread::OctreePersistThread(Octree* tree, const QString& filename, int persistInterval, - bool wantBackup, const QJsonObject& settings, bool debugTimestampNow) : + bool wantBackup, const QJsonObject& settings, bool debugTimestampNow, + QString persistAsFileType) : _tree(tree), _filename(filename), _persistInterval(persistInterval), @@ -38,9 +41,14 @@ OctreePersistThread::OctreePersistThread(Octree* tree, const QString& filename, _lastCheck(0), _wantBackup(wantBackup), _debugTimestampNow(debugTimestampNow), - _lastTimeDebug(0) + _lastTimeDebug(0), + _persistAsFileType(persistAsFileType) { parseSettings(settings); + + // in case the persist filename has an extension that doesn't match the file type + QString sansExt = fileNameWithoutExtension(_filename, PERSIST_EXTENSIONS); + _filename = sansExt + "." + _persistAsFileType; } void OctreePersistThread::parseSettings(const QJsonObject& settings) { @@ -140,7 +148,7 @@ bool OctreePersistThread::process() { qDebug() << "Loading Octree... lock file removed:" << lockFileName; } - persistantFileRead = _tree->readFromSVOFile(_filename.toLocal8Bit().constData()); + persistantFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit())); _tree->pruneTree(); } _tree->unlock(); @@ -242,9 +250,7 @@ void OctreePersistThread::persist() { if(lockFile.is_open()) { qDebug() << "saving Octree lock file created at:" << lockFileName; - qDebug() << "saving Octree to file " << _filename << "..."; - - _tree->writeToSVOFile(qPrintable(_filename)); + _tree->writeToFile(qPrintable(_filename), NULL, _persistAsFileType); time(&_lastPersistTime); _tree->clearDirtyBit(); // tree is clean after saving qDebug() << "DONE saving Octree to file..."; @@ -346,8 +352,8 @@ void OctreePersistThread::rollOldBackupVersions(const BackupRule& rule) { QString backupExtensionNplusOne = rule.extensionFormat; backupExtensionN.replace(QString("%N"), QString::number(n)); backupExtensionNplusOne.replace(QString("%N"), QString::number(n+1)); - - QString backupFilenameN = _filename + backupExtensionN; + + QString backupFilenameN = findMostRecentFileExtension(_filename, PERSIST_EXTENSIONS) + backupExtensionN; QString backupFilenameNplusOne = _filename + backupExtensionNplusOne; QFile backupFileN(backupFilenameN); diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index 374de79f0a..e756c13f59 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -34,8 +34,8 @@ public: static const int DEFAULT_PERSIST_INTERVAL; OctreePersistThread(Octree* tree, const QString& filename, int persistInterval = DEFAULT_PERSIST_INTERVAL, - bool wantBackup = false, const QJsonObject& settings = QJsonObject(), - bool debugTimestampNow = false); + bool wantBackup = false, const QJsonObject& settings = QJsonObject(), + bool debugTimestampNow = false, QString persistAsFileType="svo"); bool isInitialLoadComplete() const { return _initialLoadComplete; } quint64 getLoadElapsedTime() const { return _loadTimeUSecs; } @@ -72,6 +72,8 @@ private: bool _debugTimestampNow; quint64 _lastTimeDebug; + + QString _persistAsFileType; }; #endif // hifi_OctreePersistThread_h diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 3e2fabfd89..c7b3d60e41 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -63,23 +63,26 @@ void PhysicsEngine::addEntityInternal(EntityItem* entity) { assert(entity); void* physicsInfo = entity->getPhysicsInfo(); if (!physicsInfo) { - ShapeInfo shapeInfo; - entity->computeShapeInfo(shapeInfo); - btCollisionShape* shape = _shapeManager.getShape(shapeInfo); - if (shape) { - EntityMotionState* motionState = new EntityMotionState(entity); - entity->setPhysicsInfo(static_cast(motionState)); - _entityMotionStates.insert(motionState); - addObject(shapeInfo, shape, motionState); - } else if (entity->isMoving()) { - EntityMotionState* motionState = new EntityMotionState(entity); - entity->setPhysicsInfo(static_cast(motionState)); - _entityMotionStates.insert(motionState); + qDebug() << "PhysicsEngine::addEntityInternal(" << entity; + if (entity->isReadyToComputeShape()) { + ShapeInfo shapeInfo; + entity->computeShapeInfo(shapeInfo); + btCollisionShape* shape = _shapeManager.getShape(shapeInfo); + if (shape) { + EntityMotionState* motionState = new EntityMotionState(entity); + entity->setPhysicsInfo(static_cast(motionState)); + _entityMotionStates.insert(motionState); + addObject(shapeInfo, shape, motionState); + } else if (entity->isMoving()) { + EntityMotionState* motionState = new EntityMotionState(entity); + entity->setPhysicsInfo(static_cast(motionState)); + _entityMotionStates.insert(motionState); - motionState->setKinematic(true, _numSubsteps); - _nonPhysicalKinematicObjects.insert(motionState); - // We failed to add the entity to the simulation. Probably because we couldn't create a shape for it. - //qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine"; + motionState->setKinematic(true, _numSubsteps); + _nonPhysicalKinematicObjects.insert(motionState); + // We failed to add the entity to the simulation. Probably because we couldn't create a shape for it. + //qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine"; + } } } } @@ -322,16 +325,21 @@ void PhysicsEngine::stepSimulation() { // // TODO: untangle these lock sequences. _entityTree->lockForWrite(); - _avatarData->lockForWrite(); + lock(); _dynamicsWorld->synchronizeMotionStates(); - if (_avatarData->isPhysicsEnabled()) { + _avatarData->lockForRead(); + bool avatarHasPhysicsEnabled = _avatarData->isPhysicsEnabled(); + _avatarData->unlock(); + if (avatarHasPhysicsEnabled) { const btTransform& avatarTransform = _avatarGhostObject->getWorldTransform(); glm::quat rotation = bulletToGLM(avatarTransform.getRotation()); glm::vec3 offset = rotation * _avatarShapeLocalOffset; + _avatarData->lockForWrite(); _avatarData->setOrientation(rotation); _avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset); + _avatarData->unlock(); } unlock(); diff --git a/libraries/physics/src/ShapeInfoUtil.cpp b/libraries/physics/src/ShapeInfoUtil.cpp index e98bb07052..116be984b9 100644 --- a/libraries/physics/src/ShapeInfoUtil.cpp +++ b/libraries/physics/src/ShapeInfoUtil.cpp @@ -26,6 +26,9 @@ int ShapeInfoUtil::toBulletShapeType(int shapeInfoType) { case SHAPE_TYPE_CAPSULE_Y: bulletShapeType = CAPSULE_SHAPE_PROXYTYPE; break; + case SHAPE_TYPE_CONVEX_HULL: + bulletShapeType = CONVEX_HULL_SHAPE_PROXYTYPE; + break; } return bulletShapeType; } @@ -42,6 +45,9 @@ int ShapeInfoUtil::fromBulletShapeType(int bulletShapeType) { case CAPSULE_SHAPE_PROXYTYPE: shapeInfoType = SHAPE_TYPE_CAPSULE_Y; break; + case CONVEX_HULL_SHAPE_PROXYTYPE: + shapeInfoType = SHAPE_TYPE_CONVEX_HULL; + break; } return shapeInfoType; } @@ -60,8 +66,21 @@ void ShapeInfoUtil::collectInfoFromShape(const btCollisionShape* shape, ShapeInf info.setSphere(sphereShape->getRadius()); } break; - default: + case SHAPE_TYPE_CONVEX_HULL: { + const btConvexHullShape* convexHullShape = static_cast(shape); + const int numPoints = convexHullShape->getNumPoints(); + const btVector3* btPoints = convexHullShape->getUnscaledPoints(); + QVector points; + for (int i = 0; i < numPoints; i++) { + glm::vec3 point(btPoints->getX(), btPoints->getY(), btPoints->getZ()); + points << point; + } + info.setConvexHull(points); + } + break; + default: { info.clear(); + } break; } } else { @@ -88,6 +107,15 @@ btCollisionShape* ShapeInfoUtil::createShapeFromInfo(const ShapeInfo& info) { shape = new btCapsuleShape(radius, height); } break; + case SHAPE_TYPE_CONVEX_HULL: { + shape = new btConvexHullShape(); + const QVector& points = info.getPoints(); + foreach (glm::vec3 point, points) { + btVector3 btPoint(point[0], point[1], point[2]); + static_cast(shape)->addPoint(btPoint); + } + } + break; } return shape; } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index b80cc04b3f..29f76291ea 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1770,8 +1770,8 @@ void GeometryCache::renderLine(const glm::vec2& p1, const glm::vec2& p2, } -QSharedPointer GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) { - return getResource(url, fallback, delayLoad).staticCast(); +QSharedPointer GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad, bool block) { + return getResource(url, fallback, delayLoad, NULL, block).staticCast(); } QSharedPointer GeometryCache::createResource(const QUrl& url, diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index ba7c16bc10..a64d041fc1 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -21,8 +21,8 @@ #include #include -#include -#include +#include "FBXReader.h" +#include "OBJReader.h" #include @@ -203,7 +203,8 @@ public: /// Loads geometry from the specified URL. /// \param fallback a fallback URL to load if the desired one is unavailable /// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested - QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); + QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), + bool delayLoad = false, bool block = true); protected: diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index be2e7b4aec..d5e15ed2c8 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include "PhysicsEntity.h" #include #include #include @@ -1101,6 +1101,14 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo } } +void Model::setCollisionModelURL(const QUrl& url, const QUrl& fallback, bool delayLoad) { + if (_collisionUrl == url) { + return; + } + _collisionUrl = url; + _collisionGeometry = DependencyManager::get()->getGeometry(url, fallback, delayLoad); +} + bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return false; diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 453d721962..f95c87e99f 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include "PhysicsEntity.h" #include #include "AnimationHandle.h" @@ -106,7 +106,10 @@ public: /// \param delayLoad if true, don't load the model immediately; wait until actually requested Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(), bool retainCurrent = false, bool delayLoad = false); - + + // Set the model to use for collisions + Q_INVOKABLE void setCollisionModelURL(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); + const QUrl& getURL() const { return _url; } /// Sets the distance parameter used for LOD computations. @@ -287,11 +290,14 @@ private: float _lodDistance; float _lodHysteresis; float _nextLODHysteresis; + + QSharedPointer _collisionGeometry; float _pupilDilation; QVector _blendshapeCoefficients; QUrl _url; + QUrl _collisionUrl; gpu::Buffers _blendedVertexBuffers; std::vector _transforms; diff --git a/libraries/physics/src/PhysicsEntity.cpp b/libraries/render-utils/src/PhysicsEntity.cpp similarity index 100% rename from libraries/physics/src/PhysicsEntity.cpp rename to libraries/render-utils/src/PhysicsEntity.cpp diff --git a/libraries/physics/src/PhysicsEntity.h b/libraries/render-utils/src/PhysicsEntity.h similarity index 100% rename from libraries/physics/src/PhysicsEntity.h rename to libraries/render-utils/src/PhysicsEntity.h diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 0b99f22228..545183e8f5 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -11,6 +11,9 @@ #include #include +#include +#include +#include #include "PathUtils.h" @@ -23,3 +26,30 @@ QString& PathUtils::resourcesPath() { #endif return staticResourcePath; } + + +QString fileNameWithoutExtension(const QString& fileName, const QVector possibleExtensions) { + foreach (const QString possibleExtension, possibleExtensions) { + if (fileName.endsWith(possibleExtension) || + fileName.endsWith(possibleExtension.toUpper()) || + fileName.endsWith(possibleExtension.toLower())) { + return fileName.left(fileName.count() - possibleExtension.count() - 1); + } + } + return fileName; +} + +QString findMostRecentFileExtension(const QString& originalFileName, QVector possibleExtensions) { + QString sansExt = fileNameWithoutExtension(originalFileName, possibleExtensions); + QString newestFileName = originalFileName; + QDateTime newestTime = QDateTime::fromMSecsSinceEpoch(0); + foreach (QString possibleExtension, possibleExtensions) { + QString fileName = sansExt + "." + possibleExtension; + QFileInfo fileInfo(fileName); + if (fileInfo.exists() && fileInfo.lastModified() > newestTime) { + newestFileName = fileName; + newestTime = fileInfo.lastModified(); + } + } + return newestFileName; +} diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 71cabc727d..6b6893574b 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -19,4 +19,7 @@ namespace PathUtils { QString& resourcesPath(); } -#endif // hifi_PathUtils_h \ No newline at end of file +QString fileNameWithoutExtension(const QString& fileName, const QVector possibleExtensions); +QString findMostRecentFileExtension(const QString& originalFileName, QVector possibleExtensions); + +#endif // hifi_PathUtils_h diff --git a/libraries/shared/src/QVariantGLM.cpp b/libraries/shared/src/QVariantGLM.cpp new file mode 100644 index 0000000000..aa8fa40593 --- /dev/null +++ b/libraries/shared/src/QVariantGLM.cpp @@ -0,0 +1,48 @@ +// +// QVariantGLM.cpp +// libraries/shared/src +// +// Created by Seth Alves on 3/9/15. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "QVariantGLM.h" +#include "OctalCode.h" + +QVariantList glmToQList(const glm::vec3& g) { + return QVariantList() << g[0] << g[1] << g[2]; +} + +QVariantList glmToQList(const glm::quat& g) { + return QVariantList() << g.x << g.y << g.z << g.w; +} + +QVariantList rgbColorToQList(rgbColor& v) { + return QVariantList() << (int)(v[0]) << (int)(v[1]) << (int)(v[2]); +} + + + +glm::vec3 qListToGlmVec3(const QVariant q) { + QVariantList qList = q.toList(); + return glm::vec3(qList[RED_INDEX].toFloat(), qList[GREEN_INDEX].toFloat(), qList[BLUE_INDEX].toFloat()); +} + +glm::quat qListToGlmQuat(const QVariant q) { + QVariantList qList = q.toList(); + float x = qList[0].toFloat(); + float y = qList[1].toFloat(); + float z = qList[2].toFloat(); + float w = qList[3].toFloat(); + return glm::quat(w, x, y, z); +} + +void qListtoRgbColor(const QVariant q, rgbColor returnValue) { + QVariantList qList = q.toList(); + returnValue[RED_INDEX] = qList[RED_INDEX].toInt(); + returnValue[GREEN_INDEX] = qList[GREEN_INDEX].toInt(); + returnValue[BLUE_INDEX] = qList[BLUE_INDEX].toInt(); +} diff --git a/libraries/shared/src/QVariantGLM.h b/libraries/shared/src/QVariantGLM.h new file mode 100644 index 0000000000..4cc1d038a1 --- /dev/null +++ b/libraries/shared/src/QVariantGLM.h @@ -0,0 +1,26 @@ +// +// QVariantGLM.h +// libraries/shared/src +// +// Created by Seth Alves on 3/9/15. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include + +#include "SharedUtil.h" + +QVariantList glmToQList(const glm::vec3& g); +QVariantList glmToQList(const glm::quat& g); +QVariantList rgbColorToQList(rgbColor& v); + +glm::vec3 qListToGlmVec3(const QVariant q); +glm::quat qListToGlmQuat(const QVariant q); +void qListtoRgbColor(const QVariant q, rgbColor returnValue); diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 8b2a3e3a73..d92decad2a 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -22,7 +22,7 @@ void ShapeInfo::clear() { _externalData = NULL; } -void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QVector* data) { +void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QVector* data, QString url) { _type = type; switch(type) { case SHAPE_TYPE_NONE: @@ -37,8 +37,13 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QVector< _halfExtents = glm::vec3(radius); break; } + case SHAPE_TYPE_CONVEX_HULL: + _url = QUrl(url); + _halfExtents = halfExtents; + break; default: _halfExtents = halfExtents; + break; } _externalData = data; } @@ -61,6 +66,11 @@ void ShapeInfo::setEllipsoid(const glm::vec3& halfExtents) { _doubleHashKey.clear(); } +void ShapeInfo::setConvexHull(const QVector& points) { + _type = SHAPE_TYPE_CONVEX_HULL; + _points = points; +} + void ShapeInfo::setCapsuleY(float radius, float halfHeight) { _type = SHAPE_TYPE_CAPSULE_Y; _halfExtents = glm::vec3(radius, halfHeight, radius); @@ -130,21 +140,30 @@ const DoubleHashKey& ShapeInfo::getHash() const { thisPtr->_doubleHashKey.setHash(hash); // compute hash2 - hash = _doubleHashKey.getHash2(); - for (int i = 0; i < numData; ++i) { - tmpData = (*data)[i]; - for (int j = 0; j < 3; ++j) { - // NOTE: 0.49f is used to bump the float up almost half a millimeter - // so the cast to int produces a round() effect rather than a floor() - uint32_t floatHash = - DoubleHashKey::hashFunction2((uint32_t)(tmpData[j] * MILLIMETERS_PER_METER + copysignf(1.0f, tmpData[j]) * 0.49f)); - hash += ~(floatHash << 17); - hash ^= (floatHash >> 11); - hash += (floatHash << 4); - hash ^= (floatHash >> 7); - hash += ~(floatHash << 10); - hash = (hash << 16) | (hash >> 16); + + QString url = _url.toString(); + + if (url == "") { + hash = _doubleHashKey.getHash2(); + for (int i = 0; i < numData; ++i) { + tmpData = (*data)[i]; + for (int j = 0; j < 3; ++j) { + // NOTE: 0.49f is used to bump the float up almost half a millimeter + // so the cast to int produces a round() effect rather than a floor() + uint32_t floatHash = + DoubleHashKey::hashFunction2((uint32_t)(tmpData[j] * MILLIMETERS_PER_METER + copysignf(1.0f, tmpData[j]) * 0.49f)); + hash += ~(floatHash << 17); + hash ^= (floatHash >> 11); + hash += (floatHash << 4); + hash ^= (floatHash >> 7); + hash += ~(floatHash << 10); + hash = (hash << 16) | (hash >> 16); + } } + } else { + QByteArray baUrl = url.toLocal8Bit(); + const char *cUrl = baUrl.data(); + hash = qChecksum(cUrl, baUrl.count()); } thisPtr->_doubleHashKey.setHash2(hash); } else { diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index b6dc88e4b7..ff0b5ca8ff 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -13,6 +13,8 @@ #define hifi_ShapeInfo_h #include +#include +#include #include #include "DoubleHashKey.h" @@ -22,7 +24,7 @@ enum ShapeType { SHAPE_TYPE_BOX, SHAPE_TYPE_SPHERE, SHAPE_TYPE_ELLIPSOID, - SHAPE_TYPE_HULL, + SHAPE_TYPE_CONVEX_HULL, SHAPE_TYPE_PLANE, SHAPE_TYPE_COMPOUND, SHAPE_TYPE_CAPSULE_X, @@ -34,14 +36,15 @@ enum ShapeType { }; class ShapeInfo { + public: void clear(); - void setParams(ShapeType type, const glm::vec3& halfExtents, QVector* data = NULL); + void setParams(ShapeType type, const glm::vec3& halfExtents, QVector* data = NULL, QString url=""); void setBox(const glm::vec3& halfExtents); void setSphere(float radius); void setEllipsoid(const glm::vec3& halfExtents); - //void setHull(); // TODO: implement this + void setConvexHull(const QVector& points); void setCapsuleY(float radius, float halfHeight); const int getType() const { return _type; } @@ -51,6 +54,11 @@ public: void setData(const QVector* data) { _externalData = data; } const QVector* getData() const { return _externalData; } + const QVector& getPoints() const { return _points; } + + void clearPoints () { _points.clear(); } + void appendToPoints (const QVector& newPoints) { _points << newPoints; } + float computeVolume() const; const DoubleHashKey& getHash() const; @@ -60,6 +68,8 @@ protected: glm::vec3 _halfExtents = glm::vec3(0.0f); DoubleHashKey _doubleHashKey; const QVector* _externalData = NULL; + QVector _points; // points for convex collision hull + QUrl _url; // url for model of convex collision hull }; #endif // hifi_ShapeInfo_h diff --git a/libraries/shared/src/VariantMapToScriptValue.cpp b/libraries/shared/src/VariantMapToScriptValue.cpp new file mode 100644 index 0000000000..6fa3fd04e6 --- /dev/null +++ b/libraries/shared/src/VariantMapToScriptValue.cpp @@ -0,0 +1,47 @@ +// +// VariantMapToScriptValue.cpp +// libraries/shared/src/ +// +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include "VariantMapToScriptValue.h" + +QScriptValue variantMapToScriptValue(QVariantMap& variantMap, QScriptEngine& scriptEngine) { + QScriptValue scriptValue = scriptEngine.newObject(); + + for (QVariantMap::const_iterator iter = variantMap.begin(); iter != variantMap.end(); ++iter) { + QString key = iter.key(); + QVariant qValue = iter.value(); + + switch(qValue.type()) { + case QVariant::Bool: + scriptValue.setProperty(key, qValue.toBool()); + break; + case QVariant::Int: + scriptValue.setProperty(key, qValue.toInt()); + break; + case QVariant::Double: + scriptValue.setProperty(key, qValue.toDouble()); + break; + case QVariant::String: { + scriptValue.setProperty(key, scriptEngine.newVariant(qValue)); + break; + } + case QVariant::Map: { + QVariantMap childMap = qValue.toMap(); + scriptValue.setProperty(key, variantMapToScriptValue(childMap, scriptEngine)); + break; + } + default: + qDebug() << "unhandled QScript type" << qValue.type(); + } + } + + return scriptValue; +} diff --git a/libraries/shared/src/VariantMapToScriptValue.h b/libraries/shared/src/VariantMapToScriptValue.h new file mode 100644 index 0000000000..503f8c6490 --- /dev/null +++ b/libraries/shared/src/VariantMapToScriptValue.h @@ -0,0 +1,16 @@ +// +// VariantMapToScriptValue.h +// libraries/shared/src/ +// +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +QScriptValue variantMapToScriptValue(QVariantMap& variantMap, QScriptEngine& scriptEngine);