diff --git a/CMakeLists.txt b/CMakeLists.txt index 47c4271815..fe7aad8939 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,4 +183,4 @@ endif () if (ANDROID OR DESKTOP_GVR) add_subdirectory(gvr-interface) -endif () \ No newline at end of file +endif () diff --git a/examples/controllers/hydra/toyball.js b/examples/controllers/hydra/toyball.js index 5306769e95..89085fad19 100644 --- a/examples/controllers/hydra/toyball.js +++ b/examples/controllers/hydra/toyball.js @@ -92,7 +92,6 @@ function checkControllerSide(whichSide) { Vec3.multiply(1.0 - AVERAGE_FACTOR, averageLinearVelocity[0])); linearVelocity = averageLinearVelocity[0]; angularVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getSpatialControlRawAngularVelocity(LEFT_TIP)); - angularVelocity = Vec3.multiply(180.0 / Math.PI, angularVelocity); } else { BUTTON_FWD = RIGHT_BUTTON_FWD; BUTTON_3 = RIGHT_BUTTON_3; @@ -104,7 +103,6 @@ function checkControllerSide(whichSide) { Vec3.multiply(1.0 - AVERAGE_FACTOR, averageLinearVelocity[1])); linearVelocity = averageLinearVelocity[1]; angularVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getSpatialControlRawAngularVelocity(RIGHT_TIP)); - angularVelocity = Vec3.multiply(180.0 / Math.PI, angularVelocity); handMessage = "RIGHT"; } diff --git a/examples/dice.js b/examples/dice.js index 1205f38ee1..b0021ecebb 100644 --- a/examples/dice.js +++ b/examples/dice.js @@ -53,7 +53,9 @@ function shootDice(position, velocity) { position: position, velocity: velocity, rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360), - angularVelocity: { x: Math.random() * 100, y: Math.random() * 100, z: Math.random() * 100 }, + // NOTE: angularVelocity is in radians/sec + var maxAngularSpeed = Math.PI; + angularVelocity: { x: Math.random() * maxAngularSpeed, y: Math.random() * maxAngularSpeed, z: Math.random() * maxAngularSpeed }, lifetime: LIFETIME, gravity: { x: 0, y: GRAVITY, z: 0 }, shapeType: "box", @@ -108,4 +110,4 @@ function scriptEnding() { Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); Controller.mousePressEvent.connect(mousePressEvent); -Script.scriptEnding.connect(scriptEnding); \ No newline at end of file +Script.scriptEnding.connect(scriptEnding); diff --git a/examples/editEntities.js b/examples/editEntities.js index a0f3cdc167..e6d4534b86 100644 --- a/examples/editEntities.js +++ b/examples/editEntities.js @@ -22,9 +22,7 @@ Script.include([ "libraries/progressDialog.js", "libraries/entitySelectionTool.js", - "libraries/ModelImporter.js", - "libraries/ExportMenu.js", "libraries/ToolTip.js", "libraries/entityPropertyDialogBox.js", @@ -35,7 +33,6 @@ Script.include([ var selectionDisplay = SelectionDisplay; var selectionManager = SelectionManager; -var modelImporter = new ModelImporter(); var entityPropertyDialogBox = EntityPropertyDialogBox; var cameraManager = new CameraManager(); @@ -420,8 +417,6 @@ var toolBar = (function () { }()); -var exportMenu = null; - function isLocked(properties) { // special case to lock the ground plane model in hq. if (location.hostname == "hq.highfidelity.io" && @@ -694,17 +689,16 @@ function setupModelMenus() { } Menu.addMenuItem({ menuName: "Edit", menuItemName: "Entity List...", shortcutKey: "CTRL+META+L", afterItem: "Models" }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Entity List..." }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L", - afterItem: "Paste Models", isCheckable: true, isChecked: true }); + afterItem: "Entity List...", isCheckable: true, isChecked: true }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S", afterItem: "Allow Selecting of Large Models", isCheckable: true, isChecked: true }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L", afterItem: "Allow Selecting of Small Models", isCheckable: true }); Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" }); - Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" }); - Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" }); + Menu.addMenuItem({ menuName: "File", menuItemName: "Export Entities", shortcutKey: "CTRL+META+E", afterItem: "Models" }); + Menu.addMenuItem({ menuName: "File", menuItemName: "Import Entities", shortcutKey: "CTRL+META+I", afterItem: "Export Entities" }); Menu.addMenuItem({ menuName: "View", menuItemName: MENU_AUTO_FOCUS_ON_SELECT, afterItem: MENU_INSPECT_TOOL_ENABLED, @@ -725,14 +719,13 @@ function cleanupModelMenus() { } Menu.removeMenuItem("Edit", "Entity List..."); - Menu.removeMenuItem("Edit", "Paste Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); Menu.removeMenuItem("Edit", "Allow Selecting of Lights"); Menu.removeSeparator("File", "Models"); - Menu.removeMenuItem("File", "Export Models"); - Menu.removeMenuItem("File", "Import Models"); + Menu.removeMenuItem("File", "Export Entities"); + Menu.removeMenuItem("File", "Import Entities"); Menu.removeMenuItem("View", MENU_INSPECT_TOOL_ENABLED); Menu.removeMenuItem("View", MENU_AUTO_FOCUS_ON_SELECT); @@ -747,11 +740,7 @@ Script.scriptEnding.connect(function() { toolBar.cleanup(); cleanupModelMenus(); tooltip.cleanup(); - modelImporter.cleanup(); selectionDisplay.cleanup(); - if (exportMenu) { - exportMenu.close(); - } Entities.setLightsArePickable(originalLightsArePickable); }); @@ -793,18 +782,41 @@ function handeMenuEvent(menuItem) { Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); } else if (menuItem == "Delete") { deleteSelectedEntities(); - } else if (menuItem == "Paste Models") { - modelImporter.paste(); - } else if (menuItem == "Export Models") { - if (!exportMenu) { - exportMenu = new ExportMenu({ - onClose: function () { - exportMenu = null; + } else if (menuItem == "Export Entities") { + if (!selectionManager.hasSelection()) { + Window.alert("No entities have been selected."); + } else { + var filename = "models__" + Window.location.hostname + "__.svo"; + filename = Window.save("Select where to save", filename, "*.svo") + if (filename) { + var success = Clipboard.exportEntities(filename, selectionManager.selections); + if (!success) { + Window.alert("Export failed."); } - }); + } + } + } else if (menuItem == "Import Entities") { + var filename = Window.browse("Select models to import", "", "*.svo") + if (filename) { + var success = Clipboard.importEntities(filename); + + if (success) { + var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE; + var direction = Quat.getFront(Camera.orientation); + var offset = Vec3.multiply(distance, direction); + var position = Vec3.sum(Camera.position, offset); + + position.x = Math.max(0, position.x); + position.y = Math.max(0, position.y); + position.z = Math.max(0, position.z); + + var pastedEntityIDs = Clipboard.pasteEntities(position); + + selectionManager.setSelections(pastedEntityIDs); + } else { + Window.alert("There was an error importing the entity file."); + } } - } else if (menuItem == "Import Models") { - modelImporter.doImport(); } else if (menuItem == "Entity List...") { entityListTool.toggleVisible(); } diff --git a/examples/example/entities/spotlightExample.js b/examples/example/entities/spotlightExample.js index 5eb5432f3f..80c89926a3 100644 --- a/examples/example/entities/spotlightExample.js +++ b/examples/example/entities/spotlightExample.js @@ -11,11 +11,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var DEGREES_TO_RADIANS = Math.PI / 180.0; + var lightProperties = { type: "Light", position: { x: 0, y: 0, z: 0 }, dimensions: { x: 1000, y: 1000, z: 1000 }, - angularVelocity: { x: 0, y: 10, z: 0 }, + angularVelocity: { x: 0, y: 10 * DEGREES_TO_RADIANS, z: 0 }, angularDamping: 0, isSpotlight: true, diff --git a/examples/hmdDefaults.js b/examples/hmdDefaults.js new file mode 100644 index 0000000000..0096b11777 --- /dev/null +++ b/examples/hmdDefaults.js @@ -0,0 +1,16 @@ +// +// hmdDefaults.js +// examples +// +// Created by David Rowe on 6 Mar 2015. +// Copyright 2015 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 +// + +Script.load("progress.js"); +Script.load("lobby.js"); +Script.load("notifications.js"); +Script.load("controllers/oculus/goTo.js"); +//Script.load("scripts.js"); // Not created yet diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index fd1a4e3821..cb49b86975 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -2,6 +2,10 @@ - \ No newline at end of file + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6484e364bc..6bb4a20095 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -141,7 +141,7 @@ using namespace std; static unsigned STARFIELD_NUM_STARS = 50000; static unsigned STARFIELD_SEED = 1; -const qint64 MAXIMUM_CACHE_SIZE = 10737418240; // 10GB +const qint64 MAXIMUM_CACHE_SIZE = 10 * BYTES_PER_GIGABYTES; // 10GB static QTimer* locationUpdateTimer = NULL; static QTimer* balanceUpdateTimer = NULL; @@ -151,7 +151,7 @@ static QTimer* billboardPacketTimer = NULL; static QTimer* checkFPStimer = NULL; static QTimer* idleTimer = NULL; -const QString CHECK_VERSION_URL = "https://highfidelity.io/latestVersion.xml"; +const QString CHECK_VERSION_URL = "https://highfidelity.com/latestVersion.xml"; const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion"; const QString DEFAULT_SCRIPTS_JS_URL = "http://s3.amazonaws.com/hifi-public/scripts/defaultScripts.js"; @@ -445,7 +445,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); networkAccessManager.setCache(cache); - + ResourceCache::setRequestLimit(3); _window->setCentralWidget(_glWidget); @@ -490,6 +490,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(nodeList.data(), SIGNAL(dataReceived(const quint8, const int)), bandwidthRecorder.data(), SLOT(updateInboundData(const quint8, const int))); + connect(&_myAvatar->getSkeletonModel(), &SkeletonModel::skeletonLoaded, + this, &Application::checkSkeleton, Qt::QueuedConnection); + // check first run... if (_firstRun.get()) { qDebug() << "This is a first run..."; @@ -772,13 +775,10 @@ void Application::paintGL() { DependencyManager::get()->render(); - { + if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) { PerformanceTimer perfTimer("renderOverlay"); - // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() _applicationOverlay.renderOverlay(true); - if (Menu::getInstance()->isOptionChecked(MenuOption::UserInterface)) { - _applicationOverlay.displayOverlayTexture(); - } + _applicationOverlay.displayOverlayTexture(); } } @@ -1633,10 +1633,50 @@ void Application::setActiveFaceTracker() { #endif } +bool Application::exportEntities(const QString& filename, const QVector& entityIDs) { + QVector entities; + + auto entityTree = _entities.getTree(); + EntityTree exportTree; + + glm::vec3 root(TREE_SCALE, TREE_SCALE, TREE_SCALE); + for (auto entityID : entityIDs) { + auto entityItem = entityTree->findEntityByEntityItemID(entityID); + if (!entityItem) { + continue; + } + + auto properties = entityItem->getProperties(); + auto position = properties.getPosition(); + + root.x = glm::min(root.x, position.x); + root.y = glm::min(root.y, position.y); + root.z = glm::min(root.z, position.z); + + entities << entityItem; + } + + if (entities.size() == 0) { + return false; + } + + for (auto entityItem : entities) { + auto properties = entityItem->getProperties(); + + properties.setPosition(properties.getPosition() - root); + exportTree.addEntity(entityItem->getEntityItemID(), properties); + } + + exportTree.writeToSVOFile(filename.toLocal8Bit().constData()); + + // restore the main window's active state + _window->activateWindow(); + return true; +} + bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale) { QVector entities; - _entities.getTree()->findEntities(AACube(glm::vec3(x / (float)TREE_SCALE, - y / (float)TREE_SCALE, z / (float)TREE_SCALE), scale / (float)TREE_SCALE), entities); + _entities.getTree()->findEntities(AACube(glm::vec3(x, y, z), scale), entities); if (entities.size() > 0) { glm::vec3 root(x, y, z); @@ -1683,8 +1723,8 @@ bool Application::importEntities(const QString& filename) { return success; } -void Application::pasteEntities(float x, float y, float z) { - _entityClipboard.sendEntities(&_entityEditSender, _entities.getTree(), x, y, z); +QVector Application::pasteEntities(float x, float y, float z) { + return _entityClipboard.sendEntities(&_entityEditSender, _entities.getTree(), x, y, z); } void Application::initDisplay() { @@ -2263,7 +2303,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - serverBounds.scale(TREE_SCALE); ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds); @@ -2327,7 +2366,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - serverBounds.scale(TREE_SCALE); ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds); if (serverFrustumLocation != ViewFrustum::OUTSIDE) { @@ -4006,3 +4044,19 @@ void Application::notifyPacketVersionMismatch() { msgBox.exec(); } } + +void Application::checkSkeleton() { + if (_myAvatar->getSkeletonModel().isActive() && !_myAvatar->getSkeletonModel().hasSkeleton()) { + qDebug() << "MyAvatar model has no skeleton"; + + QString message = "Your selected avatar body has no skeleton.\n\nThe default body will be loaded..."; + QMessageBox msgBox; + msgBox.setText(message); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + + _myAvatar->setSkeletonModelURL(DEFAULT_BODY_MODEL_URL); + _myAvatar->sendIdentityPacket(); + } +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 91a5f7547b..ce4bae45b9 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -323,7 +323,8 @@ public slots: void nodeKilled(SharedNodePointer node); void packetSent(quint64 length); - void pasteEntities(float x, float y, float z); + QVector pasteEntities(float x, float y, float z); + bool exportEntities(const QString& filename, const QVector& entityIDs); bool exportEntities(const QString& filename, float x, float y, float z, float scale); bool importEntities(const QString& filename); @@ -584,6 +585,8 @@ private: QTimer _settingsTimer; GLCanvas* _glWidget = new GLCanvas(); // our GLCanvas has a couple extra features + + void checkSkeleton(); }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 59d6734b2a..ea63b9879f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -436,6 +436,8 @@ Menu::Menu() { SLOT(disable(bool))); addActionToQMenuAndActionHash(networkMenu, MenuOption::CachesSize, 0, dialogsManager.data(), SLOT(cachesSizeDialog())); + addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0, + dialogsManager.data(), SLOT(toggleDiskCacheEditor())); QMenu* timingMenu = developerMenu->addMenu("Timing and Stats"); QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index fe1035d54b..3166e1fa37 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -125,7 +125,7 @@ namespace MenuOption { const QString BookmarkLocation = "Bookmark Location"; const QString Bookmarks = "Bookmarks"; const QString CascadedShadows = "Cascaded"; - const QString CachesSize = "Caches Size"; + const QString CachesSize = "RAM Caches Size"; const QString Chat = "Chat..."; const QString ChatCircling = "Chat Circling"; const QString CollideAsRagdoll = "Collide With Self (Ragdoll)"; @@ -143,6 +143,7 @@ namespace MenuOption { const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD"; const QString DisableLightEntities = "Disable Light Entities"; const QString DisableNackPackets = "Disable NACK Packets"; + const QString DiskCacheEditor = "Disk Cache Editor"; const QString DisplayHands = "Show Hand Info"; const QString DisplayHandTargets = "Show Hand Targets"; const QString DisplayModelBounds = "Display Model Bounds"; diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp index ad73d119a3..f1c5def149 100644 --- a/interface/src/ModelUploader.cpp +++ b/interface/src/ModelUploader.cpp @@ -177,6 +177,24 @@ bool ModelUploader::zip() { } QByteArray fbxContents = fbx.readAll(); FBXGeometry geometry = readFBX(fbxContents, QVariantHash()); + + #if 0 /// Temporarily remove this check until CtrlAltDavid can come up with a fix. + // Make sure that a skeleton model has a skeleton + if (_modelType == SKELETON_MODEL) { + if (geometry.rootJointIndex == -1) { + + QString message = "Your selected skeleton model has no skeleton.\n\nThe upload will be canceled."; + QMessageBox msgBox; + msgBox.setWindowTitle("Model Upload"); + msgBox.setText(message); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + + return false; + } + } + #endif // make sure we have some basic mappings populateBasicMapping(mapping, filename, geometry); @@ -311,8 +329,20 @@ void ModelUploader::populateBasicMapping(QVariantHash& mapping, QString filename mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); } - // mixamo blendshapes - if (!mapping.contains(BLENDSHAPE_FIELD) && geometry.applicationName == "mixamo.com") { + // mixamo blendshapes - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will + // be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file + bool likelyMixamoFile = geometry.applicationName == "mixamo.com" || + (geometry.blendshapeChannelNames.contains("Facial_Blends") && + geometry.blendshapeChannelNames.contains("BrowsDown_Right") && + geometry.blendshapeChannelNames.contains("MouthOpen") && + geometry.blendshapeChannelNames.contains("Blink_Left") && + geometry.blendshapeChannelNames.contains("Blink_Right") && + geometry.blendshapeChannelNames.contains("Squint_Right")); + +qDebug() << "likelyMixamoFile:" << likelyMixamoFile; +qDebug() << "geometry.blendshapeChannelNames:" << geometry.blendshapeChannelNames; + + if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) { QVariantHash blendshapes; blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0); blendshapes.insertMulti("BrowsD_R", QVariantList() << "BrowsDown_Right" << 1.0); diff --git a/interface/src/avatar/ModelReferential.cpp b/interface/src/avatar/ModelReferential.cpp index 5b72fddae7..b8acf78cd3 100644 --- a/interface/src/avatar/ModelReferential.cpp +++ b/interface/src/avatar/ModelReferential.cpp @@ -21,7 +21,6 @@ ModelReferential::ModelReferential(Referential* referential, EntityTree* tree, A { _translation = referential->getTranslation(); _rotation = referential->getRotation(); - _scale = referential->getScale(); unpackExtraData(reinterpret_cast(referential->getExtraData().data()), referential->getExtraData().size()); @@ -32,9 +31,9 @@ ModelReferential::ModelReferential(Referential* referential, EntityTree* tree, A const EntityItem* item = _tree->findEntityByID(_entityID); if (item != NULL) { - _refScale = item->getLargestDimension(); + _lastRefDimension = item->getDimensions(); _refRotation = item->getRotation(); - _refPosition = item->getPosition() * (float)TREE_SCALE; + _refPosition = item->getPosition(); update(); } } @@ -51,14 +50,13 @@ ModelReferential::ModelReferential(const QUuid& entityID, EntityTree* tree, Avat return; } - _refScale = item->getLargestDimension(); + _lastRefDimension = item->getDimensions(); _refRotation = item->getRotation(); - _refPosition = item->getPosition() * (float)TREE_SCALE; + _refPosition = item->getPosition(); glm::quat refInvRot = glm::inverse(_refRotation); - _scale = _avatar->getTargetScale() / _refScale; _rotation = refInvRot * _avatar->getOrientation(); - _translation = refInvRot * (avatar->getPosition() - _refPosition) / _refScale; + _translation = refInvRot * (avatar->getPosition() - _refPosition); } void ModelReferential::update() { @@ -68,9 +66,10 @@ void ModelReferential::update() { } bool somethingChanged = false; - if (item->getLargestDimension() != _refScale) { - _refScale = item->getLargestDimension(); - _avatar->setTargetScale(_refScale * _scale, true); + if (item->getDimensions() != _lastRefDimension) { + glm::vec3 oldDimension = _lastRefDimension; + _lastRefDimension = item->getDimensions(); + _translation *= _lastRefDimension / oldDimension; somethingChanged = true; } if (item->getRotation() != _refRotation) { @@ -80,7 +79,7 @@ void ModelReferential::update() { } if (item->getPosition() != _refPosition || somethingChanged) { _refPosition = item->getPosition(); - _avatar->setPosition(_refPosition * (float)TREE_SCALE + _refRotation * (_translation * _refScale), true); + _avatar->setPosition(_refPosition + _refRotation * _translation, true); } } @@ -108,7 +107,7 @@ JointReferential::JointReferential(Referential* referential, EntityTree* tree, A const EntityItem* item = _tree->findEntityByID(_entityID); const Model* model = getModel(item); if (!isValid() || model == NULL || _jointIndex >= (uint32_t)(model->getJointStateCount())) { - _refScale = item->getLargestDimension(); + _lastRefDimension = item->getDimensions(); model->getJointRotationInWorldFrame(_jointIndex, _refRotation); model->getJointPositionInWorldFrame(_jointIndex, _refPosition); } @@ -128,14 +127,14 @@ JointReferential::JointReferential(uint32_t jointIndex, const QUuid& entityID, E return; } - _refScale = item->getLargestDimension(); + _lastRefDimension = item->getDimensions(); model->getJointRotationInWorldFrame(_jointIndex, _refRotation); model->getJointPositionInWorldFrame(_jointIndex, _refPosition); glm::quat refInvRot = glm::inverse(_refRotation); - _scale = _avatar->getTargetScale() / _refScale; _rotation = refInvRot * _avatar->getOrientation(); - _translation = refInvRot * (avatar->getPosition() - _refPosition) / _refScale; + // BUG! _refPosition is in domain units, but avatar is in meters + _translation = refInvRot * (avatar->getPosition() - _refPosition); } void JointReferential::update() { @@ -146,9 +145,10 @@ void JointReferential::update() { } bool somethingChanged = false; - if (item->getLargestDimension() != _refScale) { - _refScale = item->getLargestDimension(); - _avatar->setTargetScale(_refScale * _scale, true); + if (item->getDimensions() != _lastRefDimension) { + glm::vec3 oldDimension = _lastRefDimension; + _lastRefDimension = item->getDimensions(); + _translation *= _lastRefDimension / oldDimension; somethingChanged = true; } if (item->getRotation() != _refRotation) { @@ -158,7 +158,7 @@ void JointReferential::update() { } if (item->getPosition() != _refPosition || somethingChanged) { model->getJointPositionInWorldFrame(_jointIndex, _refPosition); - _avatar->setPosition(_refPosition + _refRotation * (_translation * _refScale), true); + _avatar->setPosition(_refPosition + _refRotation * _translation, true); } } diff --git a/interface/src/avatar/ModelReferential.h b/interface/src/avatar/ModelReferential.h index adb5783de2..0b66acfac5 100644 --- a/interface/src/avatar/ModelReferential.h +++ b/interface/src/avatar/ModelReferential.h @@ -45,4 +45,4 @@ protected: uint32_t _jointIndex; }; -#endif // hifi_ModelReferential_h \ No newline at end of file +#endif // hifi_ModelReferential_h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9c83942439..c2a957dc72 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -195,6 +194,12 @@ void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("skeleton"); _skeletonModel.simulate(deltaTime); } + + if (!_skeletonModel.hasSkeleton()) { + // All the simulation that can be done has been done + return; + } + { PerformanceTimer perfTimer("attachments"); simulateAttachments(deltaTime); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index fa9846fd7d..fbb622b30a 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -81,6 +81,8 @@ void SkeletonModel::setJointStates(QVector states) { if (_enableShapes) { buildShapes(); } + + emit skeletonLoaded(); } const float PALM_PRIORITY = DEFAULT_PRIORITY; @@ -721,7 +723,8 @@ void SkeletonModel::buildShapes() { } const FBXGeometry& geometry = _geometry->getFBXGeometry(); - if (geometry.joints.isEmpty()) { + if (geometry.joints.isEmpty() || geometry.rootJointIndex == -1) { + // rootJointIndex == -1 if the avatar model has no skeleton return; } @@ -1006,3 +1009,6 @@ void SkeletonModel::renderJointCollisionShapes(float alpha) { glPopMatrix(); } +bool SkeletonModel::hasSkeleton() { + return isActive() ? _geometry->getFBXGeometry().rootJointIndex != -1 : false; +} diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index d28d1a8aef..74d0ed0324 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -123,7 +123,13 @@ public: void resetShapePositionsToDefaultPose(); // DEBUG method void renderRagdoll(); - + + bool hasSkeleton(); + +signals: + + void skeletonLoaded(); + protected: void buildShapes(); diff --git a/interface/src/octree/OctreeFade.cpp b/interface/src/octree/OctreeFade.cpp index 917bb67124..ad313bdb6d 100644 --- a/interface/src/octree/OctreeFade.cpp +++ b/interface/src/octree/OctreeFade.cpp @@ -42,7 +42,7 @@ void OctreeFade::render() { glDisable(GL_LIGHTING); glPushMatrix(); - glScalef(TREE_SCALE, TREE_SCALE, TREE_SCALE); + glScalef(1.0f, 1.0f, 1.0f); glTranslatef(voxelDetails.x + voxelDetails.s * 0.5f, voxelDetails.y + voxelDetails.s * 0.5f, voxelDetails.z + voxelDetails.s * 0.5f); diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 65a5c6f1a1..34aae97446 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -1,4 +1,4 @@ -// +//Merge branch 'master' of ssh://github.com/highfidelity/hifi into isentropic/ // OctreePacketProcessor.cpp // interface/src/octree // diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index 2411cc08eb..616fc5f141 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -14,6 +14,10 @@ ClipboardScriptingInterface::ClipboardScriptingInterface() { } +bool ClipboardScriptingInterface::exportEntities(const QString& filename, const QVector& entityIDs) { + return Application::getInstance()->exportEntities(filename, entityIDs); +} + bool ClipboardScriptingInterface::exportEntities(const QString& filename, float x, float y, float z, float s) { return Application::getInstance()->exportEntities(filename, x, y, z, s); } @@ -22,6 +26,6 @@ bool ClipboardScriptingInterface::importEntities(const QString& filename) { return Application::getInstance()->importEntities(filename); } -void ClipboardScriptingInterface::pasteEntities(float x, float y, float z, float s) { - Application::getInstance()->pasteEntities(x, y, z); +QVector ClipboardScriptingInterface::pasteEntities(glm::vec3 position) { + return Application::getInstance()->pasteEntities(position.x, position.y, position.z); } diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index bc270210dc..92204af60d 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -23,8 +23,9 @@ signals: public slots: bool importEntities(const QString& filename); + bool exportEntities(const QString& filename, const QVector& entityIDs); bool exportEntities(const QString& filename, float x, float y, float z, float s); - void pasteEntities(float x, float y, float z, float s); + QVector pasteEntities(glm::vec3 position); }; #endif // hifi_ClipboardScriptingInterface_h diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index c412f71579..701ceb0189 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -20,6 +20,7 @@ #include "AttachmentsDialog.h" #include "BandwidthDialog.h" #include "CachesSizeDialog.h" +#include "DiskCacheEditor.h" #include "HMDToolsDialog.h" #include "LodToolsDialog.h" #include "LoginDialog.h" @@ -37,6 +38,11 @@ void DialogsManager::toggleAddressBar() { } } +void DialogsManager::toggleDiskCacheEditor() { + maybeCreateDialog(_diskCacheEditor); + _diskCacheEditor->toggle(); +} + void DialogsManager::toggleLoginDialog() { maybeCreateDialog(_loginDialog); _loginDialog->toggleQAction(); @@ -61,7 +67,6 @@ void DialogsManager::octreeStatsDetails() { } void DialogsManager::cachesSizeDialog() { - qDebug() << "Caches size:" << _cachesSizeDialog.isNull(); if (!_cachesSizeDialog) { maybeCreateDialog(_cachesSizeDialog); diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index e2da71bbd8..897215cbff 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -24,8 +24,9 @@ class QAction; class AddressBarDialog; class AnimationsDialog; class AttachmentsDialog; -class CachesSizeDialog; class BandwidthDialog; +class CachesSizeDialog; +class DiskCacheEditor; class LodToolsDialog; class LoginDialog; class OctreeStatsDialog; @@ -45,6 +46,7 @@ public: public slots: void toggleAddressBar(); + void toggleDiskCacheEditor(); void toggleLoginDialog(); void showLoginDialog(); void octreeStatsDetails(); @@ -73,7 +75,7 @@ private: member = new T(parent); Q_CHECK_PTR(member); - if (_hmdToolsDialog) { + if (_hmdToolsDialog && member->windowHandle()) { _hmdToolsDialog->watchWindow(member->windowHandle()); } } @@ -84,6 +86,7 @@ private: QPointer _attachmentsDialog; QPointer _bandwidthDialog; QPointer _cachesSizeDialog; + QPointer _diskCacheEditor; QPointer _ircInfoBox; QPointer _hmdToolsDialog; QPointer _lodToolsDialog; diff --git a/interface/src/ui/DiskCacheEditor.cpp b/interface/src/ui/DiskCacheEditor.cpp new file mode 100644 index 0000000000..2f2b924e13 --- /dev/null +++ b/interface/src/ui/DiskCacheEditor.cpp @@ -0,0 +1,150 @@ +// +// DiskCacheEditor.cpp +// +// +// Created by Clement on 3/4/15. +// Copyright 2015 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 +#include +#include + +#include + +#include "DiskCacheEditor.h" + +DiskCacheEditor::DiskCacheEditor(QWidget* parent) : QObject(parent) { + +} + +QWindow* DiskCacheEditor::windowHandle() { + return (_dialog) ? _dialog->windowHandle() : nullptr; +} + +void DiskCacheEditor::toggle() { + qDebug() << "DiskCacheEditor::toggle()"; + if (!_dialog) { + makeDialog(); + } + + if (!_dialog->isActiveWindow()) { + _dialog->show(); + _dialog->raise(); + _dialog->activateWindow(); + } else { + _dialog->close(); + } +} + +void DiskCacheEditor::makeDialog() { + _dialog = new QDialog(static_cast(parent())); + Q_CHECK_PTR(_dialog); + _dialog->setAttribute(Qt::WA_DeleteOnClose); + _dialog->setWindowTitle("Disk Cache Editor"); + + QGridLayout* layout = new QGridLayout(_dialog); + Q_CHECK_PTR(layout); + _dialog->setLayout(layout); + + + QLabel* path = new QLabel("Path : ", _dialog); + Q_CHECK_PTR(path); + path->setAlignment(Qt::AlignRight); + layout->addWidget(path, 0, 0); + + QLabel* size = new QLabel("Current Size : ", _dialog); + Q_CHECK_PTR(size); + size->setAlignment(Qt::AlignRight); + layout->addWidget(size, 1, 0); + + QLabel* maxSize = new QLabel("Max Size : ", _dialog); + Q_CHECK_PTR(maxSize); + maxSize->setAlignment(Qt::AlignRight); + layout->addWidget(maxSize, 2, 0); + + + _path = new QLabel(_dialog); + Q_CHECK_PTR(_path); + _path->setAlignment(Qt::AlignLeft); + layout->addWidget(_path, 0, 1, 1, 3); + + _size = new QLabel(_dialog); + Q_CHECK_PTR(_size); + _size->setAlignment(Qt::AlignLeft); + layout->addWidget(_size, 1, 1, 1, 3); + + _maxSize = new QLabel(_dialog); + Q_CHECK_PTR(_maxSize); + _maxSize->setAlignment(Qt::AlignLeft); + layout->addWidget(_maxSize, 2, 1, 1, 3); + + refresh(); + + + QPushButton* refreshCacheButton = new QPushButton(_dialog); + Q_CHECK_PTR(refreshCacheButton); + refreshCacheButton->setText("Refresh"); + refreshCacheButton->setToolTip("Reload the cache stats."); + connect(refreshCacheButton, SIGNAL(clicked()), SLOT(refresh())); + layout->addWidget(refreshCacheButton, 3, 2); + + QPushButton* clearCacheButton = new QPushButton(_dialog); + Q_CHECK_PTR(clearCacheButton); + clearCacheButton->setText("Clear"); + clearCacheButton->setToolTip("Erases the entire content of the disk cache."); + connect(clearCacheButton, SIGNAL(clicked()), SLOT(clear())); + layout->addWidget(clearCacheButton, 3, 3); +} + +void DiskCacheEditor::refresh() { + static const std::function stringify = [](qint64 number) { + static const QStringList UNITS = QStringList() << "B" << "KB" << "MB" << "GB"; + static const qint64 CHUNK = 1024; + QString unit; + int i = 0; + for (i = 0; i < 4; ++i) { + if (number / CHUNK > 0) { + number /= CHUNK; + } else { + break; + } + } + return QString("%0 %1").arg(number).arg(UNITS[i]); + }; + QNetworkDiskCache* cache = qobject_cast(NetworkAccessManager::getInstance().cache()); + + if (_path) { + _path->setText(cache->cacheDirectory()); + } + if (_size) { + _size->setText(stringify(cache->cacheSize())); + } + if (_maxSize) { + _maxSize->setText(stringify(cache->maximumCacheSize())); + } +} + +void DiskCacheEditor::clear() { + QMessageBox::StandardButton buttonClicked = + QMessageBox::question(_dialog, "Clearing disk cache", + "You are about to erase all the content of the disk cache," + "are you sure you want to do that?"); + if (buttonClicked == QMessageBox::Yes) { + QNetworkDiskCache* cache = qobject_cast(NetworkAccessManager::getInstance().cache()); + if (cache) { + qDebug() << "DiskCacheEditor::clear(): Clearing disk cache."; + cache->clear(); + } + } + refresh(); +} diff --git a/interface/src/ui/DiskCacheEditor.h b/interface/src/ui/DiskCacheEditor.h new file mode 100644 index 0000000000..5d673c4285 --- /dev/null +++ b/interface/src/ui/DiskCacheEditor.h @@ -0,0 +1,46 @@ +// +// DiskCacheEditor.h +// +// +// Created by Clement on 3/4/15. +// Copyright 2015 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 +// + +#ifndef hifi_DiskCacheEditor_h +#define hifi_DiskCacheEditor_h + +#include +#include + +class QDialog; +class QLabel; +class QWindow; + +class DiskCacheEditor : public QObject { + Q_OBJECT + +public: + DiskCacheEditor(QWidget* parent = nullptr); + + QWindow* windowHandle(); + +public slots: + void toggle(); + +private slots: + void refresh(); + void clear(); + +private: + void makeDialog(); + + QPointer _dialog; + QPointer _path; + QPointer _size; + QPointer _maxSize; +}; + +#endif // hifi_DiskCacheEditor_h \ No newline at end of file diff --git a/interface/src/ui/NodeBounds.cpp b/interface/src/ui/NodeBounds.cpp index 85cbf72953..f0f4d432db 100644 --- a/interface/src/ui/NodeBounds.cpp +++ b/interface/src/ui/NodeBounds.cpp @@ -71,20 +71,19 @@ void NodeBounds::draw() { voxelDetailsForCode(rootCode, rootDetails); serverJurisdictions->unlock(); glm::vec3 location(rootDetails.x, rootDetails.y, rootDetails.z); - location *= (float)TREE_SCALE; - AACube serverBounds(location, rootDetails.s * TREE_SCALE); + AACube serverBounds(location, rootDetails.s); glm::vec3 center = serverBounds.getVertex(BOTTOM_RIGHT_NEAR) + ((serverBounds.getVertex(TOP_LEFT_FAR) - serverBounds.getVertex(BOTTOM_RIGHT_NEAR)) / 2.0f); const float ENTITY_NODE_SCALE = 0.99f; - float scaleFactor = rootDetails.s * TREE_SCALE; + float scaleFactor = rootDetails.s; // Scale by 0.92 - 1.00 depending on the scale of the node. This allows smaller nodes to scale in // a bit and not overlap larger nodes. - scaleFactor *= 0.92 + (rootDetails.s * 0.08); + scaleFactor *= 0.92f + (rootDetails.s * 0.08f); // Scale different node types slightly differently because it's common for them to overlap. if (nodeType == NodeType::EntityServer) { diff --git a/interface/src/ui/OctreeStatsDialog.cpp b/interface/src/ui/OctreeStatsDialog.cpp index d9c8124180..462811fc1c 100644 --- a/interface/src/ui/OctreeStatsDialog.cpp +++ b/interface/src/ui/OctreeStatsDialog.cpp @@ -273,7 +273,6 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - serverBounds.scale(TREE_SCALE); serverDetails << " jurisdiction: " << qPrintable(rootCodeHex) << " [" diff --git a/libraries/avatars/src/Referential.cpp b/libraries/avatars/src/Referential.cpp index d5203a0a9d..f5f1d47324 100644 --- a/libraries/avatars/src/Referential.cpp +++ b/libraries/avatars/src/Referential.cpp @@ -76,7 +76,6 @@ int Referential::pack(unsigned char* destinationBuffer) const { destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, _translation, 0); destinationBuffer += packOrientationQuatToBytes(destinationBuffer, _rotation); - destinationBuffer += packFloatScalarToSignedTwoByteFixed(destinationBuffer, _scale, 0); return destinationBuffer - startPosition; } @@ -91,7 +90,6 @@ int Referential::unpack(const unsigned char* sourceBuffer) { sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, _translation, 0); sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, _rotation); - sourceBuffer += unpackFloatScalarFromSignedTwoByteFixed((const int16_t*) sourceBuffer, &_scale, 0); return sourceBuffer - startPosition; } diff --git a/libraries/avatars/src/Referential.h b/libraries/avatars/src/Referential.h index d824cad42b..70edecda62 100644 --- a/libraries/avatars/src/Referential.h +++ b/libraries/avatars/src/Referential.h @@ -39,7 +39,6 @@ public: glm::vec3 getTranslation() const { return _translation; } glm::quat getRotation() const { return _rotation; } - float getScale() const {return _scale; } QByteArray getExtraData() const { return _extraDataBuffer; } virtual void update() {} @@ -62,14 +61,13 @@ protected: AvatarData* _avatar; QByteArray _extraDataBuffer; - glm::vec3 _refPosition; - glm::quat _refRotation; - float _refScale; + glm::vec3 _refPosition; // position of object in world-frame + glm::quat _refRotation; // rotation of object in world-frame + glm::vec3 _lastRefDimension; // dimension of object when _translation was last computed - glm::vec3 _translation; - glm::quat _rotation; - float _scale; + glm::vec3 _translation; // offset of avatar in object local-frame + glm::quat _rotation; // rotation of avatar in object local-frame }; -#endif // hifi_Referential_h \ No newline at end of file +#endif // hifi_Referential_h diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 25f4275222..a5e75f760b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -32,6 +32,7 @@ #include "RenderableModelEntityItem.h" #include "RenderableSphereEntityItem.h" #include "RenderableTextEntityItem.h" +#include "RenderableParticleEffectEntityItem.h" EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, @@ -53,6 +54,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory) + REGISTER_ENTITY_TYPE_WITH_FACTORY(ParticleEffect, RenderableParticleEffectEntityItem::factory) _currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID @@ -96,7 +98,7 @@ void EntityTreeRenderer::init() { // make sure our "last avatar position" is something other than our current position, so that on our // first chance, we'll check for enter/leave entity events. - _lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3(1.0f, 1.0f, 1.0f); + _lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE); connect(entityTree, &EntityTree::deletingEntity, this, &EntityTreeRenderer::deletingEntity); connect(entityTree, &EntityTree::addingEntity, this, &EntityTreeRenderer::checkAndCallPreload); @@ -276,10 +278,10 @@ void EntityTreeRenderer::update() { void EntityTreeRenderer::checkEnterLeaveEntities() { if (_tree && !_shuttingDown) { _tree->lockForWrite(); // so that our scripts can do edits if they want - glm::vec3 avatarPosition = _viewState->getAvatarPosition() / (float) TREE_SCALE; + glm::vec3 avatarPosition = _viewState->getAvatarPosition(); if (avatarPosition != _lastAvatarPosition) { - float radius = 1.0f / (float) TREE_SCALE; // for now, assume 1 meter radius + float radius = 1.0f; // for now, assume 1 meter radius QVector foundEntities; QVector entitiesContainingAvatar; @@ -341,7 +343,7 @@ void EntityTreeRenderer::leaveAllEntities() { // make sure our "last avatar position" is something other than our current position, so that on our // first chance, we'll check for enter/leave entity events. - _lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3(1.0f, 1.0f, 1.0f); + _lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE); _tree->unlock(); } } @@ -416,8 +418,8 @@ const Model* EntityTreeRenderer::getModelForEntityItem(const EntityItem* entityI } void EntityTreeRenderer::renderElementProxy(EntityTreeElement* entityTreeElement) { - glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float) TREE_SCALE; - float elementSize = entityTreeElement->getScale() * (float) TREE_SCALE; + glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter(); + float elementSize = entityTreeElement->getScale(); glPushMatrix(); glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z); DependencyManager::get()->renderWireCube(elementSize, glm::vec4(1.0f, 0.0f, 0.0f, 1.0f)); @@ -478,10 +480,6 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg AACube minCube = entity->getMinimumAACube(); AABox entityBox = entity->getAABox(); - maxCube.scale((float) TREE_SCALE); - minCube.scale((float) TREE_SCALE); - entityBox.scale((float) TREE_SCALE); - glm::vec3 maxCenter = maxCube.calcCenter(); glm::vec3 minCenter = minCube.calcCenter(); glm::vec3 entityBoxCenter = entityBox.calcCenter(); @@ -507,9 +505,9 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg glPopMatrix(); - glm::vec3 position = entity->getPosition() * (float) TREE_SCALE; - glm::vec3 center = entity->getCenter() * (float) TREE_SCALE; - glm::vec3 dimensions = entity->getDimensions() * (float) TREE_SCALE; + glm::vec3 position = entity->getPosition(); + glm::vec3 center = entity->getCenter(); + glm::vec3 dimensions = entity->getDimensions(); glm::quat rotation = entity->getRotation(); glPushMatrix(); @@ -549,8 +547,6 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) if (entityItem->isVisible()) { // render entityItem AABox entityBox = entityItem->getAABox(); - - entityBox.scale(TREE_SCALE); // TODO: some entity types (like lights) might want to be rendered even // when they are outside of the view frustum... diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index 7f2ba410d8..f7828806ab 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -25,9 +25,9 @@ EntityItem* RenderableBoxEntityItem::factory(const EntityItemID& entityID, const void RenderableBoxEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableBoxEntityItem::render"); assert(getType() == EntityTypes::Box); - glm::vec3 position = getPositionInMeters(); - glm::vec3 center = getCenter() * (float)TREE_SCALE; - glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; + glm::vec3 position = getPosition(); + glm::vec3 center = getCenter(); + glm::vec3 dimensions = getDimensions(); glm::quat rotation = getRotation(); const float MAX_COLOR = 255.0f; diff --git a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp index b5a2523158..838c9fd8c4 100644 --- a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp @@ -26,8 +26,8 @@ EntityItem* RenderableLightEntityItem::factory(const EntityItemID& entityID, con void RenderableLightEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableLightEntityItem::render"); assert(getType() == EntityTypes::Light); - glm::vec3 position = getPositionInMeters(); - glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; + glm::vec3 position = getPosition(); + glm::vec3 dimensions = getDimensions(); glm::quat rotation = getRotation(); float largestDiameter = glm::max(dimensions.x, dimensions.y, dimensions.z); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index ef6853c43a..75c9f07881 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -114,9 +114,9 @@ void RenderableModelEntityItem::render(RenderArgs* args) { bool drawAsModel = hasModel(); - glm::vec3 position = getPosition() * (float)TREE_SCALE; - float size = getSize() * (float)TREE_SCALE; - glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; + glm::vec3 position = getPosition(); + glm::vec3 dimensions = getDimensions(); + float size = glm::length(dimensions); if (drawAsModel) { remapTextures(); @@ -260,23 +260,10 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori if (!_model) { return true; } - - glm::vec3 originInMeters = origin * (float)TREE_SCALE; - QString extraInfo; - float localDistance; - //qDebug() << "RenderableModelEntityItem::findDetailedRayIntersection() precisionPicking:" << precisionPicking; - bool intersectsModel = _model->findRayIntersectionAgainstSubMeshes(originInMeters, direction, - localDistance, face, extraInfo, precisionPicking); - - if (intersectsModel) { - // NOTE: findRayIntersectionAgainstSubMeshes() does work in meters, but we're expected to return - // results in tree scale. - distance = localDistance / (float)TREE_SCALE; - } - - return intersectsModel; // we only got here if we intersected our non-aabox + QString extraInfo; + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo, precisionPicking); } diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp new file mode 100644 index 0000000000..b4dd5b4c64 --- /dev/null +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -0,0 +1,90 @@ +// +// RenderableParticleEffectEntityItem.cpp +// interface/src +// +// Created by Jason Rickwald on 3/2/15. +// +// 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 + +#include "RenderableParticleEffectEntityItem.h" + +EntityItem* RenderableParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return new RenderableParticleEffectEntityItem(entityID, properties); +} + +RenderableParticleEffectEntityItem::RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + ParticleEffectEntityItem(entityItemID, properties) { + _cacheID = DependencyManager::get()->allocateID(); +} + +void RenderableParticleEffectEntityItem::render(RenderArgs* args) { + PerformanceTimer perfTimer("RenderableParticleEffectEntityItem::render"); + assert(getType() == EntityTypes::ParticleEffect); + float pa_rad = getParticleRadius(); + + const float MAX_COLOR = 255.0f; + glm::vec4 paColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, + getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); + + // Right now we're just iterating over particles and rendering as a cross of four quads. + // This is pretty dumb, it was quick enough to code up. Really, there should be many + // rendering modes, including the all-important textured billboards. + + QVector* pointVec = new QVector(_paCount * VERTS_PER_PARTICLE); + quint32 paIter = _paHead; + while (_paLife[paIter] > 0.0f) { + int j = paIter * XYZ_STRIDE; + + pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2])); + pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2])); + pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2])); + pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2])); + + pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2])); + pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] + pa_rad, _paPosition[j + 2])); + pointVec->append(glm::vec3(_paPosition[j] - pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2])); + pointVec->append(glm::vec3(_paPosition[j] + pa_rad, _paPosition[j + 1] - pa_rad, _paPosition[j + 2])); + + pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] - pa_rad)); + pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] + pa_rad)); + pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] + pa_rad)); + pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] - pa_rad)); + + pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] + pa_rad)); + pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] + pa_rad, _paPosition[j + 2] - pa_rad)); + pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] - pa_rad)); + pointVec->append(glm::vec3(_paPosition[j], _paPosition[j + 1] - pa_rad, _paPosition[j + 2] + pa_rad)); + + paIter = (paIter + 1) % _maxParticles; + } + + DependencyManager::get()->updateVertices(_cacheID, *pointVec, paColor); + + glPushMatrix(); + glm::vec3 position = getPosition(); + glTranslatef(position.x, position.y, position.z); + glm::quat rotation = getRotation(); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + + glPushMatrix(); + glm::vec3 positionToCenter = getCenter() - position; + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + + DependencyManager::get()->renderVertices(gpu::QUADS, _cacheID); + glPopMatrix(); + glPopMatrix(); + + delete pointVec; +}; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h new file mode 100644 index 0000000000..74b29574c3 --- /dev/null +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -0,0 +1,28 @@ +// +// RenderableParticleEffectEntityItem.h +// interface/src/entities +// +// Created by Jason Rickwald on 3/2/15. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RenderableParticleEffectEntityItem_h +#define hifi_RenderableParticleEffectEntityItem_h + +#include + +class RenderableParticleEffectEntityItem : public ParticleEffectEntityItem { +public: + static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); + RenderableParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + virtual void render(RenderArgs* args); + +protected: + int _cacheID; + const int VERTS_PER_PARTICLE = 16; +}; + + +#endif // hifi_RenderableParticleEffectEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index 427ed20a8b..083a34c02f 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -26,9 +26,9 @@ EntityItem* RenderableSphereEntityItem::factory(const EntityItemID& entityID, co void RenderableSphereEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableSphereEntityItem::render"); assert(getType() == EntityTypes::Sphere); - glm::vec3 position = getPositionInMeters(); - glm::vec3 center = getCenterInMeters(); - glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; + glm::vec3 position = getPosition(); + glm::vec3 center = getCenter(); + glm::vec3 dimensions = getDimensions(); glm::quat rotation = getRotation(); const float MAX_COLOR = 255.0f; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index b1b3678dc9..3586a8c8c5 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -30,8 +30,8 @@ EntityItem* RenderableTextEntityItem::factory(const EntityItemID& entityID, cons void RenderableTextEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableTextEntityItem::render"); assert(getType() == EntityTypes::Text); - glm::vec3 position = getPositionInMeters(); - glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; + glm::vec3 position = getPosition(); + glm::vec3 dimensions = getDimensions(); glm::vec3 halfDimensions = dimensions / 2.0f; glm::quat rotation = getRotation(); float leftMargin = 0.1f; diff --git a/libraries/entities/src/AddEntityOperator.cpp b/libraries/entities/src/AddEntityOperator.cpp index 34ccc75030..09aa6af0cb 100644 --- a/libraries/entities/src/AddEntityOperator.cpp +++ b/libraries/entities/src/AddEntityOperator.cpp @@ -25,7 +25,7 @@ AddEntityOperator::AddEntityOperator(EntityTree* tree, { // caller must have verified existence of newEntity assert(_newEntity); - _newEntityBox = _newEntity->getMaximumAACube().clamp(0.0f, 1.0f); + _newEntityBox = _newEntity->getMaximumAACube().clamp(0.0f, (float)TREE_SCALE); } bool AddEntityOperator::preRecursion(OctreeElement* element) { diff --git a/libraries/entities/src/DeleteEntityOperator.cpp b/libraries/entities/src/DeleteEntityOperator.cpp index 698ef18936..d17e1c66d6 100644 --- a/libraries/entities/src/DeleteEntityOperator.cpp +++ b/libraries/entities/src/DeleteEntityOperator.cpp @@ -60,7 +60,7 @@ bool DeleteEntityOperator::subTreeContainsSomeEntitiesToDelete(OctreeElement* el // If we don't have an old entity, then we don't contain the entity, otherwise // check the bounds if (_entitiesToDelete.size() > 0) { - AACube elementCube = element->getAACube(); + const AACube& elementCube = element->getAACube(); foreach(const EntityToDeleteDetails& details, _entitiesToDelete) { if (elementCube.contains(details.cube)) { containsEntity = true; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index d6a3aab970..093f8cf84c 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -501,8 +501,12 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef EntityPropertyFlags propertyFlags = encodedPropertyFlags; dataAt += propertyFlags.getEncodedLength(); bytesRead += propertyFlags.getEncodedLength(); - - READ_ENTITY_PROPERTY_SETTER(PROP_POSITION, glm::vec3, updatePosition); + bool useMeters = (args.bitstreamVersion == VERSION_ENTITIES_USE_METERS_AND_RADIANS); + if (useMeters) { + READ_ENTITY_PROPERTY_SETTER(PROP_POSITION, glm::vec3, updatePosition); + } else { + READ_ENTITY_PROPERTY_SETTER(PROP_POSITION, glm::vec3, updatePositionInDomainUnits); + } // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { @@ -516,18 +520,31 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } } } else { - READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, setDimensions); + if (useMeters) { + READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, setDimensions); + } else { + READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, setDimensionsInDomainUnits); + } } READ_ENTITY_PROPERTY_QUAT_SETTER(PROP_ROTATION, updateRotation); READ_ENTITY_PROPERTY_SETTER(PROP_DENSITY, float, updateDensity); - READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocity); - READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravity); + if (useMeters) { + READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocity); + READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravity); + } else { + READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocityInDomainUnits); + READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits); + } READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping); READ_ENTITY_PROPERTY_SETTER(PROP_LIFETIME, float, updateLifetime); READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT, setScript); READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, _registrationPoint); - READ_ENTITY_PROPERTY_SETTER(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); + if (useMeters) { + READ_ENTITY_PROPERTY_SETTER(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); + } else { + READ_ENTITY_PROPERTY_SETTER(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); + } READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping); READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, _visible); READ_ENTITY_PROPERTY_SETTER(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); @@ -587,8 +604,7 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s } float EntityItem::computeMass() const { - // NOTE: we group the operations here in and attempt to reduce floating point error. - return ((_density * (_volumeMultiplier * _dimensions.x)) * _dimensions.y) * _dimensions.z; + return _density * _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z; } void EntityItem::setDensity(float density) { @@ -609,10 +625,7 @@ void EntityItem::setMass(float mass) { // we must protect the density range to help maintain stability of physics simulation // therefore this method might not accept the mass that is supplied. - // NOTE: when computing the volume we group the _volumeMultiplier (typically a very large number, due - // to the TREE_SCALE transformation) with the first dimension component (typically a very small number) - // in an attempt to reduce floating point error of the final result. - float volume = (_volumeMultiplier * _dimensions.x) * _dimensions.y * _dimensions.z; + float volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z; // compute new density const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3 @@ -674,41 +687,36 @@ void EntityItem::simulate(const quint64& now) { void EntityItem::simulateKinematicMotion(float timeElapsed) { if (hasAngularVelocity()) { // angular damping - glm::vec3 angularVelocity = getAngularVelocity(); if (_angularDamping > 0.0f) { - angularVelocity *= powf(1.0f - _angularDamping, timeElapsed); + _angularVelocity *= powf(1.0f - _angularDamping, timeElapsed); #ifdef WANT_DEBUG qDebug() << " angularDamping :" << _angularDamping; - qDebug() << " newAngularVelocity:" << angularVelocity; + qDebug() << " newAngularVelocity:" << _angularVelocity; #endif - setAngularVelocity(angularVelocity); } float angularSpeed = glm::length(_angularVelocity); - const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.1f; // + const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.0017453f; // 0.0017453 rad/sec = 0.1f degrees/sec if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) { if (angularSpeed > 0.0f) { _dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE; } - setAngularVelocity(ENTITY_ITEM_ZERO_VEC3); + _angularVelocity = ENTITY_ITEM_ZERO_VEC3; } else { - // NOTE: angularSpeed is currently in degrees/sec!!! - // TODO: Andrew to convert to radians/sec - glm::vec3 angularVelocity = glm::radians(_angularVelocity); // for improved agreement with the way Bullet integrates rotations we use an approximation // and break the integration into bullet-sized substeps glm::quat rotation = getRotation(); float dt = timeElapsed; while (dt > PHYSICS_ENGINE_FIXED_SUBSTEP) { - glm::quat dQ = computeBulletRotationStep(angularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP); + glm::quat dQ = computeBulletRotationStep(_angularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP); rotation = glm::normalize(dQ * rotation); dt -= PHYSICS_ENGINE_FIXED_SUBSTEP; } // NOTE: this final partial substep can drift away from a real Bullet simulation however // it only becomes significant for rapidly rotating objects // (e.g. around PI/4 radians per substep, or 7.5 rotations/sec at 60 substeps/sec). - glm::quat dQ = computeBulletRotationStep(angularVelocity, dt); + glm::quat dQ = computeBulletRotationStep(_angularVelocity, dt); rotation = glm::normalize(dQ * rotation); setRotation(rotation); @@ -753,7 +761,7 @@ void EntityItem::simulateKinematicMotion(float timeElapsed) { } float speed = glm::length(velocity); - const float EPSILON_LINEAR_VELOCITY_LENGTH = 0.001f / (float)TREE_SCALE; // 1mm/sec + const float EPSILON_LINEAR_VELOCITY_LENGTH = 0.001f; // 1mm/sec if (speed < EPSILON_LINEAR_VELOCITY_LENGTH) { setVelocity(ENTITY_ITEM_ZERO_VEC3); if (speed > 0.0f) { @@ -793,12 +801,12 @@ EntityItemProperties EntityItem::getProperties() const { properties._type = getType(); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPositionInMeters); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensionsInMeters); // NOTE: radius is obsolete + COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPosition); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensions); // NOTE: radius is obsolete COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getRotation); COPY_ENTITY_PROPERTY_TO_PROPERTIES(density, getDensity); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getVelocityInMeters); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravityInMeters); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getVelocity); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifetime, getLifetime); COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript); @@ -821,12 +829,12 @@ EntityItemProperties EntityItem::getProperties() const { bool EntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; - SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePositionInMeters); // this will call recalculate collision shape if needed - SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, updateDimensionsInMeters); // NOTE: radius is obsolete + SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePosition); // this will call recalculate collision shape if needed + SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, updateDimensions); // NOTE: radius is obsolete SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, updateRotation); SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, updateDensity); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocityInMeters); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravityInMeters); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, updateDamping); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime); SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); @@ -888,11 +896,6 @@ void EntityItem::recordCreationTime() { } -// TODO: is this really correct? how do we use size, does it need to handle rotation? -float EntityItem::getSize() const { - return glm::length(_dimensions); -} - // TODO: doesn't this need to handle rotation? glm::vec3 EntityItem::getCenter() const { return _position + (_dimensions * (glm::vec3(0.5f,0.5f,0.5f) - _registrationPoint)); @@ -951,8 +954,7 @@ AACube EntityItem::getMinimumAACube() const { return AACube(cornerOfCube, longestSide); } -AABox EntityItem::getAABox() const { - +AABox EntityItem::getAABox() const { // _position represents the position of the registration point. glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; @@ -967,6 +969,11 @@ AABox EntityItem::getAABox() const { return AABox(rotatedExtentsRelativeToRegistrationPoint); } +AABox EntityItem::getAABoxInDomainUnits() const { + AABox box = getAABox(); + box.scale(1.0f / (float)TREE_SCALE); + return box; +} // NOTE: This should only be used in cases of old bitstreams which only contain radius data // 0,0,0 --> maxDimension,maxDimension,maxDimension @@ -992,48 +999,41 @@ void EntityItem::setRadius(float value) { // ... cornerToCornerLength = sqrt(3 x maxDimension ^ 2) // ... radius = sqrt(3 x maxDimension ^ 2) / 2.0f; float EntityItem::getRadius() const { - float length = glm::length(_dimensions); - float radius = length / 2.0f; - return radius; + return 0.5f * glm::length(_dimensions); } void EntityItem::computeShapeInfo(ShapeInfo& info) const { - info.setParams(getShapeType(), 0.5f * getDimensionsInMeters()); + info.setParams(getShapeType(), 0.5f * getDimensions()); } const float MIN_POSITION_DELTA = 0.0001f; +const float MIN_DIMENSIONS_DELTA = 0.0005f; const float MIN_ALIGNMENT_DOT = 0.999999f; const float MIN_VELOCITY_DELTA = 0.01f; const float MIN_DAMPING_DELTA = 0.001f; const float MIN_GRAVITY_DELTA = 0.001f; const float MIN_SPIN_DELTA = 0.0003f; +void EntityItem::updatePositionInDomainUnits(const glm::vec3& value) { + glm::vec3 position = value * (float)TREE_SCALE; + updatePosition(position); +} + void EntityItem::updatePosition(const glm::vec3& value) { - if (glm::distance(_position, value) * (float)TREE_SCALE > MIN_POSITION_DELTA) { - _position = value; + if (glm::distance(_position, value) > MIN_POSITION_DELTA) { + _position = value; _dirtyFlags |= EntityItem::DIRTY_POSITION; } } -void EntityItem::updatePositionInMeters(const glm::vec3& value) { - glm::vec3 position = glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f); - if (glm::distance(_position, position) * (float)TREE_SCALE > MIN_POSITION_DELTA) { - _position = position; - _dirtyFlags |= EntityItem::DIRTY_POSITION; - } +void EntityItem::updateDimensionsInDomainUnits(const glm::vec3& value) { + glm::vec3 dimensions = value * (float)TREE_SCALE; + updateDimensions(dimensions); } void EntityItem::updateDimensions(const glm::vec3& value) { - if (_dimensions != value) { - _dimensions = glm::abs(value); - _dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS); - } -} - -void EntityItem::updateDimensionsInMeters(const glm::vec3& value) { - glm::vec3 dimensions = glm::abs(value) / (float) TREE_SCALE; - if (_dimensions != dimensions) { - _dimensions = dimensions; + if (glm::distance(_dimensions, value) > MIN_DIMENSIONS_DELTA) { + _dimensions = value; _dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS); } } @@ -1050,10 +1050,7 @@ void EntityItem::updateMass(float mass) { // we must protect the density range to help maintain stability of physics simulation // therefore this method might not accept the mass that is supplied. - // NOTE: when computing the volume we group the _volumeMultiplier (typically a very large number, due - // to the TREE_SCALE transformation) with the first dimension component (typically a very small number) - // in an attempt to reduce floating point error of the final result. - float volume = (_volumeMultiplier * _dimensions.x) * _dimensions.y * _dimensions.z; + float volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z; // compute new density float newDensity = _density; @@ -1072,24 +1069,17 @@ void EntityItem::updateMass(float mass) { } } -void EntityItem::updateVelocity(const glm::vec3& value) { - if (glm::distance(_velocity, value) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) { - if (glm::length(value) * (float)TREE_SCALE < MIN_VELOCITY_DELTA) { - _velocity = ENTITY_ITEM_ZERO_VEC3; - } else { - _velocity = value; - } - _dirtyFlags |= EntityItem::DIRTY_VELOCITY; - } +void EntityItem::updateVelocityInDomainUnits(const glm::vec3& value) { + glm::vec3 velocity = value * (float)TREE_SCALE; + updateVelocity(velocity); } -void EntityItem::updateVelocityInMeters(const glm::vec3& value) { - glm::vec3 velocity = value / (float) TREE_SCALE; - if (glm::distance(_velocity, velocity) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) { +void EntityItem::updateVelocity(const glm::vec3& value) { + if (glm::distance(_velocity, value) > MIN_VELOCITY_DELTA) { if (glm::length(value) < MIN_VELOCITY_DELTA) { _velocity = ENTITY_ITEM_ZERO_VEC3; } else { - _velocity = velocity; + _velocity = value; } _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } @@ -1102,17 +1092,14 @@ void EntityItem::updateDamping(float value) { } } -void EntityItem::updateGravity(const glm::vec3& value) { - if (glm::distance(_gravity, value) * (float)TREE_SCALE > MIN_GRAVITY_DELTA) { - _gravity = value; - _dirtyFlags |= EntityItem::DIRTY_VELOCITY; - } +void EntityItem::updateGravityInDomainUnits(const glm::vec3& value) { + glm::vec3 gravity = value * (float) TREE_SCALE; + updateGravity(gravity); } -void EntityItem::updateGravityInMeters(const glm::vec3& value) { - glm::vec3 gravity = value / (float) TREE_SCALE; - if ( glm::distance(_gravity, gravity) * (float)TREE_SCALE > MIN_GRAVITY_DELTA) { - _gravity = gravity; +void EntityItem::updateGravity(const glm::vec3& value) { + if ( glm::distance(_gravity, value) > MIN_GRAVITY_DELTA) { + _gravity = value; _dirtyFlags |= EntityItem::DIRTY_VELOCITY; } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index a26783fc7c..5193aa4490 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -37,7 +37,7 @@ class EntityTreeElementExtraEncodeData; #define debugTime(T, N) qPrintable(QString("%1 [ %2 ago]").arg(T, 16, 10).arg(formatUsecTime(N - T), 15)) #define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10)) -#define debugTreeVector(V) V << "[" << (V * (float)TREE_SCALE) << " in meters ]" +#define debugTreeVector(V) V << "[" << V << " in meters ]" /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available @@ -145,26 +145,27 @@ public: // attributes applicable to all entity types EntityTypes::EntityType getType() const { return _type; } - const glm::vec3& getPosition() const { return _position; } /// get position in domain scale units (0.0 - 1.0) - glm::vec3 getPositionInMeters() const { return _position * (float) TREE_SCALE; } /// get position in meters + glm::vec3 getPositionInDomainUnits() const { return _position / (float)TREE_SCALE; } /// get position in domain scale units (0.0 - 1.0) + const glm::vec3& getPosition() const { return _position; } /// get position in meters /// set position in domain scale units (0.0 - 1.0) - void setPosition(const glm::vec3& value) { _position = value; } - void setPositionInMeters(const glm::vec3& value) /// set position in meter units (0.0 - TREE_SCALE) - { setPosition(glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f)); } + void setPositionInDomainUnits(const glm::vec3& value) + { setPosition(glm::clamp(value, 0.0f, 1.0f) * (float)TREE_SCALE); } + void setPosition(const glm::vec3& value) { + _position = value; + } - glm::vec3 getCenter() const; /// calculates center of the entity in domain scale units (0.0 - 1.0) - glm::vec3 getCenterInMeters() const { return getCenter() * (float) TREE_SCALE; } + glm::vec3 getCenterInDomainUnits() const { return getCenter() / (float) TREE_SCALE; } + glm::vec3 getCenter() const; - const glm::vec3& getDimensions() const { return _dimensions; } /// get dimensions in domain scale units (0.0 - 1.0) - glm::vec3 getDimensionsInMeters() const { return _dimensions * (float) TREE_SCALE; } /// get dimensions in meters - float getLargestDimension() const { return glm::length(_dimensions); } /// get the largest possible dimension + glm::vec3 getDimensionsInDomainUnits() const { return _dimensions / (float)TREE_SCALE; } /// get dimensions in domain scale units (0.0 - 1.0) + const glm::vec3& getDimensions() const { return _dimensions; } /// get dimensions in meters - /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately - virtual void setDimensions(const glm::vec3& value) { _dimensions = value; } + /// set dimensions in domain scale units (0.0 - 1.0) + virtual void setDimensionsInDomainUnits(const glm::vec3& value) { _dimensions = glm::abs(value) * (float)TREE_SCALE; } - /// set dimensions in meter units (0.0 - TREE_SCALE) this will also reset radius appropriately - void setDimensionsInMeters(const glm::vec3& value) { setDimensions(value / (float) TREE_SCALE); } + /// set dimensions in meter units (0.0 - TREE_SCALE) + virtual void setDimensions(const glm::vec3& value) { _dimensions = glm::abs(value); } const glm::quat& getRotation() const { return _rotation; } void setRotation(const glm::quat& rotation) { _rotation = rotation; } @@ -181,16 +182,16 @@ public: float getDensity() const { return _density; } - const glm::vec3& getVelocity() const { return _velocity; } /// velocity in domain scale units (0.0-1.0) per second - glm::vec3 getVelocityInMeters() const { return _velocity * (float) TREE_SCALE; } /// get velocity in meters - void setVelocity(const glm::vec3& value) { _velocity = value; } /// velocity in domain scale units (0.0-1.0) per second - void setVelocityInMeters(const glm::vec3& value) { _velocity = value / (float) TREE_SCALE; } /// velocity in meters + glm::vec3 getVelocityInDomainUnits() const { return _velocity / (float)TREE_SCALE; } /// velocity in domain scale units (0.0-1.0) per second + const glm::vec3 getVelocity() const { return _velocity; } /// get velocity in meters + void setVelocityInDomainUnits(const glm::vec3& value) { _velocity = value * (float)TREE_SCALE; } /// velocity in domain scale units (0.0-1.0) per second + void setVelocity(const glm::vec3& value) { _velocity = value; } /// velocity in meters bool hasVelocity() const { return _velocity != ENTITY_ITEM_ZERO_VEC3; } - const glm::vec3& getGravity() const { return _gravity; } /// gravity in domain scale units (0.0-1.0) per second squared - glm::vec3 getGravityInMeters() const { return _gravity * (float) TREE_SCALE; } /// get gravity in meters - void setGravity(const glm::vec3& value) { _gravity = value; } /// gravity in domain scale units (0.0-1.0) per second squared - void setGravityInMeters(const glm::vec3& value) { _gravity = value / (float) TREE_SCALE; } /// gravity in meters + glm::vec3 getGravityInDomainUnits() const { return _gravity / (float)TREE_SCALE; } /// gravity in domain scale units (0.0-1.0) per second squared + const glm::vec3& getGravity() const { return _gravity; } /// get gravity in meters + void setGravityInDomainUnits(const glm::vec3& value) { _gravity = value * (float)TREE_SCALE; } /// gravity in domain scale units (0.0-1.0) per second squared + void setGravity(const glm::vec3& value) { _gravity = value; } /// gravity in meters bool hasGravity() const { return _gravity != ENTITY_ITEM_ZERO_VEC3; } float getDamping() const { return _damping; } @@ -212,10 +213,10 @@ public: quint64 getExpiry() const; // position, size, and bounds related helpers - float getSize() const; /// get maximum dimension in domain scale units (0.0 - 1.0) AACube getMaximumAACube() const; AACube getMinimumAACube() const; - AABox getAABox() const; /// axis aligned bounding box in domain scale units (0.0 - 1.0) + AABox getAABox() const; /// axis aligned bounding box in world-frame (meters) + AABox getAABoxInDomainUnits() const; /// axis aligned bounding box in domain scale units (0.0 - 1.0) const QString& getScript() const { return _script; } void setScript(const QString& value) { _script = value; } @@ -250,29 +251,31 @@ public: const QString& getUserData() const { return _userData; } void setUserData(const QString& value) { _userData = value; } - // TODO: We need to get rid of these users of getRadius()... + // TODO: get rid of users of getRadius()... float getRadius() const; 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; /// return preferred shape type (actual physical shape may differ) virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } // updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags + void updatePositionInDomainUnits(const glm::vec3& value); void updatePosition(const glm::vec3& value); - void updatePositionInMeters(const glm::vec3& value); + void updateDimensionsInDomainUnits(const glm::vec3& value); void updateDimensions(const glm::vec3& value); - void updateDimensionsInMeters(const glm::vec3& value); void updateRotation(const glm::quat& rotation); void updateDensity(float value); void updateMass(float value); + void updateVelocityInDomainUnits(const glm::vec3& value); void updateVelocity(const glm::vec3& value); - void updateVelocityInMeters(const glm::vec3& value); void updateDamping(float value); + void updateGravityInDomainUnits(const glm::vec3& value); void updateGravity(const glm::vec3& value); - void updateGravityInMeters(const glm::vec3& value); void updateAngularVelocity(const glm::vec3& value); + void updateAngularVelocityInDegrees(const glm::vec3& value) { updateAngularVelocity(glm::radians(value)); } void updateAngularDamping(float value); void updateIgnoreForCollisions(bool value); void updateCollisionsWillMove(bool value); @@ -318,11 +321,10 @@ protected: float _glowLevel; float _localRenderAlpha; float _density = ENTITY_ITEM_DEFAULT_DENSITY; // kg/m^3 - // NOTE: _volumeMultiplier is used to compute volume: - // volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z = m^3 - // DANGER: due to the size of TREE_SCALE the _volumeMultiplier is always a large number, and therefore - // will tend to introduce floating point error. We must keep this in mind when using it. - float _volumeMultiplier = (float)TREE_SCALE * (float)TREE_SCALE * (float)TREE_SCALE; + // NOTE: _volumeMultiplier is used to allow some mass properties code exist in the EntityItem base class + // rather than in all of the derived classes. If we ever collapse these classes to one we could do it a + // different way. + float _volumeMultiplier = 1.0f; glm::vec3 _velocity; glm::vec3 _gravity; float _damping; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index e53a6ede3d..3a33128c47 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -23,6 +23,7 @@ #include "EntityItemPropertiesDefaults.h" #include "ModelEntityItem.h" #include "TextEntityItem.h" +#include "ParticleEffectEntityItem.h" EntityItemProperties::EntityItemProperties() : @@ -61,6 +62,13 @@ EntityItemProperties::EntityItemProperties() : CONSTRUCT_PROPERTY(textColor, TextEntityItem::DEFAULT_TEXT_COLOR), CONSTRUCT_PROPERTY(backgroundColor, TextEntityItem::DEFAULT_BACKGROUND_COLOR), CONSTRUCT_PROPERTY(shapeType, SHAPE_TYPE_NONE), + CONSTRUCT_PROPERTY(maxParticles, ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES), + CONSTRUCT_PROPERTY(lifespan, ParticleEffectEntityItem::DEFAULT_LIFESPAN), + CONSTRUCT_PROPERTY(emitRate, ParticleEffectEntityItem::DEFAULT_EMIT_RATE), + CONSTRUCT_PROPERTY(emitDirection, ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION), + CONSTRUCT_PROPERTY(emitStrength, ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH), + CONSTRUCT_PROPERTY(localGravity, ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY), + CONSTRUCT_PROPERTY(particleRadius, ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS), _id(UNKNOWN_ENTITY_ID), _idSet(false), @@ -228,6 +236,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_TEXT_COLOR, textColor); CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_COLOR, backgroundColor); CHECK_PROPERTY_CHANGE(PROP_SHAPE_TYPE, shapeType); + CHECK_PROPERTY_CHANGE(PROP_MAX_PARTICLES, maxParticles); + CHECK_PROPERTY_CHANGE(PROP_LIFESPAN, lifespan); + CHECK_PROPERTY_CHANGE(PROP_EMIT_RATE, emitRate); + CHECK_PROPERTY_CHANGE(PROP_EMIT_DIRECTION, emitDirection); + CHECK_PROPERTY_CHANGE(PROP_EMIT_STRENGTH, emitStrength); + CHECK_PROPERTY_CHANGE(PROP_LOCAL_GRAVITY, localGravity); + CHECK_PROPERTY_CHANGE(PROP_PARTICLE_RADIUS, particleRadius); return changedProperties; } @@ -282,6 +297,13 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(textColor, getTextColor()); COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR_GETTER(backgroundColor, getBackgroundColor()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(shapeType, getShapeTypeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(maxParticles); + COPY_PROPERTY_TO_QSCRIPTVALUE(lifespan); + COPY_PROPERTY_TO_QSCRIPTVALUE(emitRate); + COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(emitDirection); + COPY_PROPERTY_TO_QSCRIPTVALUE(emitStrength); + COPY_PROPERTY_TO_QSCRIPTVALUE(localGravity); + COPY_PROPERTY_TO_QSCRIPTVALUE(particleRadius); // Sitting properties support QScriptValue sittingPoints = engine->newObject(); @@ -295,7 +317,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons sittingPoints.setProperty("length", _sittingPoints.size()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable - AABox aaBox = getAABoxInMeters(); + AABox aaBox = getAABox(); QScriptValue boundingBox = engine->newObject(); QScriptValue bottomRightNear = vec3toScriptValue(engine, aaBox.getCorner()); QScriptValue topFarLeft = vec3toScriptValue(engine, aaBox.calcTopFarLeft()); @@ -355,6 +377,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(textColor, setTextColor); COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(backgroundColor, setBackgroundColor); COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(shapeType, ShapeType); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(maxParticles, setMaxParticles); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(lifespan, setLifespan); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitRate, setEmitRate); + COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(emitDirection, setEmitDirection); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitStrength, setEmitStrength); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localGravity, setLocalGravity); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(particleRadius, setParticleRadius); _lastEdited = usecTimestampNow(); } @@ -528,6 +557,16 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_EXPONENT, appendValue, properties.getExponent()); APPEND_ENTITY_PROPERTY(PROP_CUTOFF, appendValue, properties.getCutoff()); } + + if (properties.getType() == EntityTypes::ParticleEffect) { + APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, appendValue, properties.getMaxParticles()); + APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, appendValue, properties.getLifespan()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, appendValue, properties.getEmitRate()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, appendValue, properties.getEmitDirection()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, appendValue, properties.getEmitStrength()); + APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, properties.getLocalGravity()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, properties.getParticleRadius()); + } } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -746,6 +785,16 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff); } + + if (properties.getType() == EntityTypes::ParticleEffect) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_PARTICLES, float, setMaxParticles); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFESPAN, float, setLifespan); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_RATE, float, setEmitRate); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_STRENGTH, float, setEmitStrength); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCAL_GRAVITY, float, setLocalGravity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius); + } return valid; } @@ -818,18 +867,20 @@ void EntityItemProperties::markAllChanged() { _textColorChanged = true; _backgroundColorChanged = true; _shapeTypeChanged = true; -} -AACube EntityItemProperties::getMaximumAACubeInTreeUnits() const { - AACube maxCube = getMaximumAACubeInMeters(); - maxCube.scale(1.0f / (float)TREE_SCALE); - return maxCube; + _maxParticlesChanged = true; + _lifespanChanged = true; + _emitRateChanged = true; + _emitDirectionChanged = true; + _emitStrengthChanged = true; + _localGravityChanged = true; + _particleRadiusChanged = true; } /// The maximum bounding cube for the entity, independent of it's rotation. /// This accounts for the registration point (upon which rotation occurs around). /// -AACube EntityItemProperties::getMaximumAACubeInMeters() const { +AACube EntityItemProperties::getMaximumAACube() const { // * we know that the position is the center of rotation glm::vec3 centerOfRotation = _position; // also where _registration point is @@ -853,7 +904,7 @@ AACube EntityItemProperties::getMaximumAACubeInMeters() const { } // The minimum bounding box for the entity. -AABox EntityItemProperties::getAABoxInMeters() const { +AABox EntityItemProperties::getAABox() const { // _position represents the position of the registration point. glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 9bf3c93c2b..b769261033 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -83,9 +83,18 @@ enum EntityPropertyList { PROP_ANIMATION_SETTINGS, PROP_USER_DATA, PROP_SHAPE_TYPE, + + // used by ParticleEffect entities + PROP_MAX_PARTICLES, + PROP_LIFESPAN, + PROP_EMIT_RATE, + PROP_EMIT_DIRECTION, + PROP_EMIT_STRENGTH, + PROP_LOCAL_GRAVITY, + PROP_PARTICLE_RADIUS, // NOTE: add new properties ABOVE this line and then modify PROP_LAST_ITEM below - PROP_LAST_ITEM = PROP_SHAPE_TYPE, + PROP_LAST_ITEM = PROP_PARTICLE_RADIUS, // These properties of TextEntity piggy back off of properties of ModelEntities, the type doesn't matter // since the derived class knows how to interpret it's own properties and knows the types it expects @@ -110,6 +119,7 @@ class EntityItemProperties { friend class SphereEntityItem; // TODO: consider removing this friend relationship and use public methods friend class LightEntityItem; // TODO: consider removing this friend relationship and use public methods friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods public: EntityItemProperties(); virtual ~EntityItemProperties(); @@ -129,9 +139,8 @@ public: /// used by EntityScriptingInterface to return EntityItemProperties for unknown models void setIsUnknownID() { _id = UNKNOWN_ENTITY_ID; _idSet = true; } - AACube getMaximumAACubeInTreeUnits() const; - AACube getMaximumAACubeInMeters() const; - AABox getAABoxInMeters() const; + AACube getMaximumAACube() const; + AABox getAABox() const; void debugDump() const; void setLastEdited(quint64 usecTime); @@ -177,6 +186,13 @@ public: DEFINE_PROPERTY_REF(PROP_TEXT_COLOR, TextColor, textColor, xColor); DEFINE_PROPERTY_REF(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, xColor); DEFINE_PROPERTY_REF_ENUM(PROP_SHAPE_TYPE, ShapeType, shapeType, ShapeType); + DEFINE_PROPERTY(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32); + DEFINE_PROPERTY(PROP_LIFESPAN, Lifespan, lifespan, float); + DEFINE_PROPERTY(PROP_EMIT_RATE, EmitRate, emitRate, float); + DEFINE_PROPERTY_REF(PROP_EMIT_DIRECTION, EmitDirection, emitDirection, glm::vec3); + DEFINE_PROPERTY(PROP_EMIT_STRENGTH, EmitStrength, emitStrength, float); + DEFINE_PROPERTY(PROP_LOCAL_GRAVITY, LocalGravity, localGravity, float); + DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float); public: float getMaxDimension() const { return glm::max(_dimensions.x, _dimensions.y, _dimensions.z); } @@ -296,6 +312,13 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, TextColor, textColor, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundColor, backgroundColor, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, ShapeType, shapeType, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, MaxParticles, maxParticles, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Lifespan, lifespan, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitRate, emitRate, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitDirection, emitDirection, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitStrength, emitStrength, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalGravity, localGravity, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParticleRadius, particleRadius, ""); debug << " last edited:" << properties.getLastEdited() << "\n"; debug << " edited ago:" << properties.getEditedAgo() << "\n"; diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 22beb3937e..b184d510b3 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -20,8 +20,6 @@ // creating a new one on the stack so we declare the ZERO_VEC3 constant as an optimization. const glm::vec3 ENTITY_ITEM_ZERO_VEC3(0.0f); -const glm::vec3 REGULAR_GRAVITY = glm::vec3(0, -9.8f / (float)TREE_SCALE, 0); - const bool ENTITY_ITEM_DEFAULT_LOCKED = false; const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString(""); @@ -37,7 +35,7 @@ const float ENTITY_ITEM_DEFAULT_LIFETIME = ENTITY_ITEM_IMMORTAL_LIFETIME; const glm::quat ENTITY_ITEM_DEFAULT_ROTATION; const float ENTITY_ITEM_DEFAULT_WIDTH = 0.1f; -const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(ENTITY_ITEM_DEFAULT_WIDTH) / (float)TREE_SCALE; +const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(ENTITY_ITEM_DEFAULT_WIDTH); const float ENTITY_ITEM_DEFAULT_VOLUME = ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH; const float ENTITY_ITEM_MAX_DENSITY = 10000.0f; // kg/m^3 density of silver diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 10ed98b0fa..5ef0db57ec 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -182,8 +182,7 @@ EntityItemID EntityScriptingInterface::findClosestEntity(const glm::vec3& center EntityItemID result(UNKNOWN_ENTITY_ID, UNKNOWN_ENTITY_TOKEN, false); if (_entityTree) { _entityTree->lockForRead(); - const EntityItem* closestEntity = _entityTree->findClosestEntity(center/(float)TREE_SCALE, - radius/(float)TREE_SCALE); + const EntityItem* closestEntity = _entityTree->findClosestEntity(center, radius); _entityTree->unlock(); if (closestEntity) { result.id = closestEntity->getID(); @@ -207,7 +206,7 @@ QVector EntityScriptingInterface::findEntities(const glm::vec3& ce if (_entityTree) { _entityTree->lockForRead(); QVector entities; - _entityTree->findEntities(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, entities); + _entityTree->findEntities(center, radius, entities); _entityTree->unlock(); foreach (const EntityItem* entity, entities) { diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index f7d6c55803..758cd6771a 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -87,7 +87,7 @@ void EntitySimulation::sortEntitiesThatMoved() { // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. PerformanceTimer perfTimer("sortingEntities"); MovingEntitiesOperator moveOperator(_entityTree); - AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f); + AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE); QSet::iterator itemItr = _entitiesToBeSorted.begin(); while (itemItr != _entitiesToBeSorted.end()) { EntityItem* entity = *itemItr; @@ -150,7 +150,7 @@ void EntitySimulation::entityChanged(EntityItem* entity) { bool wasRemoved = false; uint32_t dirtyFlags = entity->getDirtyFlags(); if (dirtyFlags & EntityItem::DIRTY_POSITION) { - AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), 1.0f); + AACube domainBounds(glm::vec3(0.0f,0.0f,0.0f), (float)TREE_SCALE); AACube newCube = entity->getMaximumAACube(); if (!domainBounds.touches(newCube)) { qDebug() << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 978ba43960..e952618c9f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -424,8 +424,7 @@ bool EntityTree::findNearPointOperation(OctreeElement* element, void* extraData) EntityTreeElement* entityTreeElement = static_cast(element); glm::vec3 penetration; - bool sphereIntersection = entityTreeElement->getAACube().findSpherePenetration(args->position, - args->targetRadius, penetration); + bool sphereIntersection = entityTreeElement->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); // If this entityTreeElement contains the point, then search it... if (sphereIntersection) { @@ -475,8 +474,7 @@ public: bool EntityTree::findInSphereOperation(OctreeElement* element, void* extraData) { FindAllNearPointArgs* args = static_cast(extraData); glm::vec3 penetration; - bool sphereIntersection = element->getAACube().findSpherePenetration(args->position, - args->targetRadius, penetration); + bool sphereIntersection = element->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); // If this element contains the point, then search it... if (sphereIntersection) { @@ -511,8 +509,7 @@ public: bool EntityTree::findInCubeOperation(OctreeElement* element, void* extraData) { FindEntitiesInCubeArgs* args = static_cast(extraData); - const AACube& elementCube = element->getAACube(); - if (elementCube.touches(args->_cube)) { + if (element->getAACube().touches(args->_cube)) { EntityTreeElement* entityTreeElement = static_cast(element); entityTreeElement->getEntities(args->_cube, args->_foundEntities); return true; @@ -1000,13 +997,17 @@ void EntityTree::pruneTree() { recurseTreeWithOperator(&theOperator); } -void EntityTree::sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z) { +QVector EntityTree::sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z) { SendEntitiesOperationArgs args; args.packetSender = packetSender; args.localTree = localTree; args.root = glm::vec3(x, y, z); + QVector newEntityIDs; + args.newEntityIDs = &newEntityIDs; recurseTreeWithOperation(sendEntitiesOperation, &args); packetSender->releaseQueuedMessages(); + + return newEntityIDs; } bool EntityTree::sendEntitiesOperation(OctreeElement* element, void* extraData) { @@ -1016,6 +1017,7 @@ bool EntityTree::sendEntitiesOperation(OctreeElement* element, void* extraData) const QList& entities = entityTreeElement->getEntities(); for (int i = 0; i < entities.size(); i++) { EntityItemID newID(NEW_ENTITY, EntityItemID::getNextCreatorTokenID(), false); + args->newEntityIDs->append(newID); EntityItemProperties properties = entities[i]->getProperties(); properties.setPosition(properties.getPosition() + args->root); properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d8b9b9f38f..226bfa873a 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -39,6 +39,7 @@ public: glm::vec3 root; EntityTree* localTree; EntityEditPacketSender* packetSender; + QVector* newEntityIDs; }; @@ -95,6 +96,8 @@ public: void deleteEntities(QSet entityIDs, bool force = false); void removeEntityFromSimulation(EntityItem* entity); + /// \param position point of query in world-frame (meters) + /// \param targetRadius radius of query (meters) const EntityItem* findClosestEntity(glm::vec3 position, float targetRadius); EntityItem* findEntityByID(const QUuid& id); EntityItem* findEntityByEntityItemID(const EntityItemID& entityID); @@ -103,14 +106,14 @@ public: /// finds all entities that touch a sphere - /// \param center the center of the sphere - /// \param radius the radius of the sphere + /// \param center the center of the sphere in world-frame (meters) + /// \param radius the radius of the sphere in world-frame (meters) /// \param foundEntities[out] vector of const EntityItem* /// \remark Side effect: any initial contents in foundEntities will be lost void findEntities(const glm::vec3& center, float radius, QVector& foundEntities); /// finds all entities that touch a cube - /// \param cube the query cube + /// \param cube the query cube in world-frame (meters) /// \param foundEntities[out] vector of non-const EntityItem* /// \remark Side effect: any initial contents in entities will be lost void findEntities(const AACube& cube, QVector& foundEntities); @@ -144,7 +147,7 @@ public: virtual void dumpTree(); virtual void pruneTree(); - void sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z); + QVector sendEntities(EntityEditPacketSender* packetSender, EntityTree* localTree, float x, float y, float z); void entityChanged(EntityItem* entity); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index ecd90e5942..223a4eb478 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -50,7 +50,7 @@ EntityTreeElement* EntityTreeElement::addChildAtIndex(int index) { void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) const { qDebug() << "EntityTreeElement::debugExtraEncodeData()... "; - qDebug() << " element:" << getAACube(); + qDebug() << " element:" << _cube; OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes @@ -159,7 +159,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params, Oct const bool wantDebug = false; if (wantDebug) { - qDebug() << "EntityTreeElement::elementEncodeComplete() element:" << getAACube(); + qDebug() << "EntityTreeElement::elementEncodeComplete() element:" << _cube; } OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; @@ -194,7 +194,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params, Oct = static_cast(extraEncodeData->value(childElement)); if (wantDebug) { - qDebug() << "checking child: " << childElement->getAACube(); + qDebug() << "checking child: " << childElement->_cube; qDebug() << " childElement->isLeaf():" << childElement->isLeaf(); qDebug() << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted; qDebug() << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted; @@ -215,7 +215,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params, Oct } if (wantDebug) { - qDebug() << "for this element: " << getAACube(); + qDebug() << "for this element: " << _cube; qDebug() << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted; qDebug() << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; } @@ -302,7 +302,6 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData // the entity may not be in view and then in view a frame later, let the client side handle it's view // frustum culling on rendering. AACube entityCube = entity->getMaximumAACube(); - entityCube.scale(TREE_SCALE); if (params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) { includeThisEntity = false; // out of view, don't include it } @@ -417,11 +416,11 @@ bool EntityTreeElement::bestFitEntityBounds(const EntityItem* entity) const { } bool EntityTreeElement::containsBounds(const EntityItemProperties& properties) const { - return containsBounds(properties.getMaximumAACubeInTreeUnits()); + return containsBounds(properties.getMaximumAACube()); } bool EntityTreeElement::bestFitBounds(const EntityItemProperties& properties) const { - return bestFitBounds(properties.getMaximumAACubeInTreeUnits()); + return bestFitBounds(properties.getMaximumAACube()); } bool EntityTreeElement::containsBounds(const AACube& bounds) const { @@ -441,14 +440,14 @@ bool EntityTreeElement::bestFitBounds(const AABox& bounds) const { } bool EntityTreeElement::containsBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const { - glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, 1.0f); - glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, 1.0f); + glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, (float)TREE_SCALE); + glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, (float)TREE_SCALE); return _cube.contains(clampedMin) && _cube.contains(clampedMax); } bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3& maxPoint) const { - glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, 1.0f); - glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, 1.0f); + glm::vec3 clampedMin = glm::clamp(minPoint, 0.0f, (float)TREE_SCALE); + glm::vec3 clampedMax = glm::clamp(maxPoint, 0.0f, (float)TREE_SCALE); if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) { @@ -823,9 +822,7 @@ bool EntityTreeElement::pruneChildren() { void EntityTreeElement::debugDump() { qDebug() << "EntityTreeElement..."; - AACube temp = getAACube(); - temp.scale((float)TREE_SCALE); - qDebug() << " cube:" << temp; + qDebug() << " cube:" << _cube; qDebug() << " has child elements:" << getChildCount(); if (_entityItems->size()) { qDebug() << " has entities:" << _entityItems->size(); diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index d66df84298..55e9512f53 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -23,6 +23,7 @@ #include "ModelEntityItem.h" #include "SphereEntityItem.h" #include "TextEntityItem.h" +#include "ParticleEffectEntityItem.h" QMap EntityTypes::_typeToNameMap; QMap EntityTypes::_nameToTypeMap; @@ -37,6 +38,7 @@ REGISTER_ENTITY_TYPE(Box) REGISTER_ENTITY_TYPE(Sphere) REGISTER_ENTITY_TYPE(Light) REGISTER_ENTITY_TYPE(Text) +REGISTER_ENTITY_TYPE(ParticleEffect) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 8ed407f11d..e1f8e876bb 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -35,7 +35,8 @@ public: Sphere, Light, Text, - LAST = Text + ParticleEffect, + LAST = ParticleEffect } EntityType; static const QString& getEntityTypeName(EntityType entityType); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index ed76b8c99f..f30d43c7d5 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -255,8 +255,8 @@ void ModelEntityItem::update(const quint64& now) { void ModelEntityItem::debugDump() const { qDebug() << "ModelEntityItem id:" << getEntityItemID(); qDebug() << " edited ago:" << getEditedAgo(); - qDebug() << " position:" << getPosition() * (float)TREE_SCALE; - qDebug() << " dimensions:" << getDimensions() * (float)TREE_SCALE; + qDebug() << " position:" << getPosition(); + qDebug() << " dimensions:" << getDimensions(); qDebug() << " model URL:" << getModelURL(); } diff --git a/libraries/entities/src/MovingEntitiesOperator.cpp b/libraries/entities/src/MovingEntitiesOperator.cpp index 48ba8e4ec2..7cace205e1 100644 --- a/libraries/entities/src/MovingEntitiesOperator.cpp +++ b/libraries/entities/src/MovingEntitiesOperator.cpp @@ -51,7 +51,7 @@ MovingEntitiesOperator::~MovingEntitiesOperator() { void MovingEntitiesOperator::addEntityToMoveList(EntityItem* entity, const AACube& newCube) { EntityTreeElement* oldContainingElement = _tree->getContainingElement(entity->getEntityItemID()); - AABox newCubeClamped = newCube.clamp(0.0f, 1.0f); + AABox newCubeClamped = newCube.clamp(0.0f, (float)TREE_SCALE); if (_wantDebug) { qDebug() << "MovingEntitiesOperator::addEntityToMoveList() -----------------------------"; @@ -114,7 +114,7 @@ bool MovingEntitiesOperator::shouldRecurseSubTree(OctreeElement* element) { // If we don't have an old entity, then we don't contain the entity, otherwise // check the bounds if (_entitiesToMove.size() > 0) { - AACube elementCube = element->getAACube(); + const AACube& elementCube = element->getAACube(); int detailIndex = 0; foreach(const EntityToMoveDetails& details, _entitiesToMove) { diff --git a/libraries/entities/src/MovingEntitiesOperator.h b/libraries/entities/src/MovingEntitiesOperator.h index fefda3328e..760b001081 100644 --- a/libraries/entities/src/MovingEntitiesOperator.h +++ b/libraries/entities/src/MovingEntitiesOperator.h @@ -15,11 +15,11 @@ class EntityToMoveDetails { public: EntityItem* entity; - AACube oldCube; - AACube newCube; - AABox newCubeClamped; + AACube oldCube; // meters + AACube newCube; // meters + AABox newCubeClamped; // meters EntityTreeElement* oldContainingElement; - AACube oldContainingElementCube; + AACube oldContainingElementCube; // meters bool oldFound; bool newFound; }; diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp new file mode 100644 index 0000000000..b902dd63b8 --- /dev/null +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -0,0 +1,512 @@ +// +// ParticleEffectEntityItem.cpp +// libraries/entities/src +// +// Some starter code for a particle simulation entity, which could ideally be used for a variety of effects. +// This is some really early and rough stuff here. It was enough for now to just get it up and running in the interface. +// +// Todo's and other notes: +// - The simulation should restart when the AnimationLoop's max frame is reached (or passed), but there doesn't seem +// to be a good way to set that max frame to something reasonable right now. +// - There seems to be a bug whereby entities on the edge of screen will just pop off or on. This is probably due +// to my lack of understanding of how entities in the octree are picked for rendering. I am updating the entity +// dimensions based on the bounds of the sim, but maybe I need to update a dirty flag or something. +// - This should support some kind of pre-roll of the simulation. +// - Just to get this out the door, I just did forward Euler integration. There are better ways. +// - Gravity always points along the Y axis. Support an actual gravity vector. +// - Add the ability to add arbitrary forces to the simulation. +// - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented). +// - Add drag. +// - Add some kind of support for collisions. +// - For simplicity, I'm currently just rendering each particle as a cross of four axis-aligned quads. Really, we'd +// want multiple render modes, including (the most important) textured billboards (always facing camera). Also, these +// should support animated textures. +// - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so +// there's no gaurantee that different clients will see simulations that look anything like the other. +// - MORE? +// +// Created by Jason Rickwald on 3/2/15. +// +// 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 "EntityTree.h" +#include "EntityTreeElement.h" +#include "ParticleEffectEntityItem.h" + +const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FRAME_INDEX = 0.0f; +const bool ParticleEffectEntityItem::DEFAULT_ANIMATION_IS_PLAYING = false; +const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FPS = 30.0f; +const quint32 ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES = 1000; +const float ParticleEffectEntityItem::DEFAULT_LIFESPAN = 3.0f; +const float ParticleEffectEntityItem::DEFAULT_EMIT_RATE = 15.0f; +const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION(0.0f, 1.0f, 0.0f); +const float ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH = 25.0f; +const float ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY = -9.8f; +const float ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS = 0.025f; + + +EntityItem* ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return new ParticleEffectEntityItem(entityID, properties); +} + +// our non-pure virtual subclass for now... +ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + EntityItem(entityItemID, properties) { + _type = EntityTypes::ParticleEffect; + _maxParticles = DEFAULT_MAX_PARTICLES; + _lifespan = DEFAULT_LIFESPAN; + _emitRate = DEFAULT_EMIT_RATE; + _emitDirection = DEFAULT_EMIT_DIRECTION; + _emitStrength = DEFAULT_EMIT_STRENGTH; + _localGravity = DEFAULT_LOCAL_GRAVITY; + _particleRadius = DEFAULT_PARTICLE_RADIUS; + setProperties(properties); + // this is a pretty dumb thing to do, and it should probably be changed to use a more dynamic + // data structure in the future. I'm just trying to get some code out the door for now (and it's + // at least time efficient (though not space efficient). + // Also, this being a real-time application, it's doubtful we'll ever have millions of particles + // to keep track of, so this really isn't all that bad. + _paLife = new float[_maxParticles]; + _paPosition = new float[_maxParticles * XYZ_STRIDE]; // x,y,z + _paVelocity = new float[_maxParticles * XYZ_STRIDE]; // x,y,z + _paXmax = _paYmax = _paZmax = 1.0f; + _paXmin = _paYmin = _paZmin = -1.0f; + _randSeed = (unsigned int) glm::abs(_lifespan + _emitRate + _localGravity + getPosition().x + getPosition().y + getPosition().z); + resetSimulation(); + _lastAnimated = usecTimestampNow(); +} + +ParticleEffectEntityItem::~ParticleEffectEntityItem() { + delete [] _paLife; + delete [] _paPosition; + delete [] _paVelocity; +} + +EntityItemProperties ParticleEffectEntityItem::getProperties() const { + EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationIsPlaying, getAnimationIsPlaying); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFrameIndex, getAnimationFrameIndex); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationFPS, getAnimationFPS); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(animationSettings, getAnimationSettings); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxParticles, getMaxParticles); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifespan, getLifespan); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitRate, getEmitRate); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitDirection, getEmitDirection); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitStrength, getEmitStrength); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(localGravity, getLocalGravity); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleRadius, getParticleRadius); + + return properties; +} + +bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationIsPlaying, setAnimationIsPlaying); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFrameIndex, setAnimationFrameIndex); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationFPS, setAnimationFPS); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(animationSettings, setAnimationSettings); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitRate, setEmitRate); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitDirection, setEmitDirection); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitStrength, setEmitStrength); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(localGravity, setLocalGravity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleRadius, setParticleRadius); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qDebug() << "ParticleEffectEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY_COLOR(PROP_COLOR, _color); + + // Because we're using AnimationLoop which will reset the frame index if you change it's running state + // we want to read these values in the order they appear in the buffer, but call our setters in an + // order that allows AnimationLoop to preserve the correct frame rate. + float animationFPS = getAnimationFPS(); + float animationFrameIndex = getAnimationFrameIndex(); + bool animationIsPlaying = getAnimationIsPlaying(); + READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, animationFPS); + READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, animationFrameIndex); + READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, animationIsPlaying); + + if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) { + if (animationIsPlaying != getAnimationIsPlaying()) { + setAnimationIsPlaying(animationIsPlaying); + } + } + if (propertyFlags.getHasProperty(PROP_ANIMATION_FPS)) { + setAnimationFPS(animationFPS); + } + if (propertyFlags.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) { + setAnimationFrameIndex(animationFrameIndex); + } + + READ_ENTITY_PROPERTY_STRING(PROP_ANIMATION_SETTINGS, setAnimationSettings); + READ_ENTITY_PROPERTY_SETTER(PROP_SHAPE_TYPE, ShapeType, updateShapeType); + READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, _maxParticles); + READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, _lifespan); + READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, _emitRate); + READ_ENTITY_PROPERTY_SETTER(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection); + READ_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, float, _emitStrength); + READ_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, float, _localGravity); + READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, _particleRadius); + + return bytesRead; +} + + +// TODO: eventually only include properties changed since the params.lastViewFrustumSent time +EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + + requestedProperties += PROP_COLOR; + requestedProperties += PROP_ANIMATION_FPS; + requestedProperties += PROP_ANIMATION_FRAME_INDEX; + requestedProperties += PROP_ANIMATION_PLAYING; + requestedProperties += PROP_ANIMATION_SETTINGS; + requestedProperties += PROP_SHAPE_TYPE; + requestedProperties += PROP_MAX_PARTICLES; + requestedProperties += PROP_LIFESPAN; + requestedProperties += PROP_EMIT_RATE; + requestedProperties += PROP_EMIT_DIRECTION; + requestedProperties += PROP_EMIT_STRENGTH; + requestedProperties += PROP_LOCAL_GRAVITY; + requestedProperties += PROP_PARTICLE_RADIUS; + + return requestedProperties; +} + +void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, getColor()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, getAnimationFPS()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, getAnimationFrameIndex()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, appendValue, getAnimationIsPlaying()); + APPEND_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, appendValue, getAnimationSettings()); + APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, appendValue, (uint32_t)getShapeType()); + APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, appendValue, getMaxParticles()); + APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, appendValue, getLifespan()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, appendValue, getEmitRate()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, appendValue, getEmitDirection()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, appendValue, getEmitStrength()); + APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, appendValue, getLocalGravity()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, getParticleRadius()); +} + +bool ParticleEffectEntityItem::isAnimatingSomething() const { + return getAnimationIsPlaying() && + getAnimationFPS() != 0.0f; +} + +bool ParticleEffectEntityItem::needsToCallUpdate() const { + return isAnimatingSomething() ? true : EntityItem::needsToCallUpdate(); +} + +void ParticleEffectEntityItem::update(const quint64& now) { + // only advance the frame index if we're playing + if (getAnimationIsPlaying()) { + float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND; + _lastAnimated = now; + float lastFrame = _animationLoop.getFrameIndex(); + _animationLoop.simulate(deltaTime); + float curFrame = _animationLoop.getFrameIndex(); + if (curFrame > lastFrame) { + stepSimulation(deltaTime); + } + else if (curFrame < lastFrame) { + // we looped around, so restart the sim and only sim up to the point + // since the beginning of the frame range. + resetSimulation(); + stepSimulation((curFrame - _animationLoop.getFirstFrame()) / _animationLoop.getFPS()); + } + } + else { + _lastAnimated = now; + } + + // update the dimensions + glm::vec3 dims; + dims.x = glm::max(glm::abs(_paXmin), glm::abs(_paXmax)) * 2.0f; + dims.y = glm::max(glm::abs(_paYmin), glm::abs(_paYmax)) * 2.0f; + dims.z = glm::max(glm::abs(_paZmin), glm::abs(_paZmax)) * 2.0f; + setDimensions(dims); + + EntityItem::update(now); // let our base class handle it's updates... +} + +void ParticleEffectEntityItem::debugDump() const { + quint64 now = usecTimestampNow(); + qDebug() << "PA EFFECT EntityItem id:" << getEntityItemID() << "---------------------------------------------"; + qDebug() << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; + qDebug() << " position:" << debugTreeVector(_position); + qDebug() << " dimensions:" << debugTreeVector(_dimensions); + qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now); +} + +void ParticleEffectEntityItem::updateShapeType(ShapeType type) { + if (type != _shapeType) { + _shapeType = type; + _dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS; + } +} + +void ParticleEffectEntityItem::setAnimationFrameIndex(float value) { +#ifdef WANT_DEBUG + if (isAnimatingSomething()) { + qDebug() << "ParticleEffectEntityItem::setAnimationFrameIndex()"; + qDebug() << " value:" << value; + qDebug() << " was:" << _animationLoop.getFrameIndex(); + } +#endif + _animationLoop.setFrameIndex(value); +} + +void ParticleEffectEntityItem::setAnimationSettings(const QString& value) { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + if (settingsMap.contains("fps")) { + float fps = settingsMap["fps"].toFloat(); + setAnimationFPS(fps); + } + + if (settingsMap.contains("frameIndex")) { + float frameIndex = settingsMap["frameIndex"].toFloat(); +#ifdef WANT_DEBUG + if (isAnimatingSomething()) { + qDebug() << "ParticleEffectEntityItem::setAnimationSettings() calling setAnimationFrameIndex()..."; + qDebug() << " settings:" << value; + qDebug() << " settingsMap[frameIndex]:" << settingsMap["frameIndex"]; + qDebug(" frameIndex: %20.5f", frameIndex); + } +#endif + + setAnimationFrameIndex(frameIndex); + } + + if (settingsMap.contains("running")) { + bool running = settingsMap["running"].toBool(); + if (running != getAnimationIsPlaying()) { + setAnimationIsPlaying(running); + } + } + + if (settingsMap.contains("firstFrame")) { + float firstFrame = settingsMap["firstFrame"].toFloat(); + setAnimationFirstFrame(firstFrame); + } + + if (settingsMap.contains("lastFrame")) { + float lastFrame = settingsMap["lastFrame"].toFloat(); + setAnimationLastFrame(lastFrame); + } + + if (settingsMap.contains("loop")) { + bool loop = settingsMap["loop"].toBool(); + setAnimationLoop(loop); + } + + if (settingsMap.contains("hold")) { + bool hold = settingsMap["hold"].toBool(); + setAnimationHold(hold); + } + + if (settingsMap.contains("startAutomatically")) { + bool startAutomatically = settingsMap["startAutomatically"].toBool(); + setAnimationStartAutomatically(startAutomatically); + } + + _animationSettings = value; + _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; +} + +void ParticleEffectEntityItem::setAnimationIsPlaying(bool value) { + _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; + _animationLoop.setRunning(value); +} + +void ParticleEffectEntityItem::setAnimationFPS(float value) { + _dirtyFlags |= EntityItem::DIRTY_UPDATEABLE; + _animationLoop.setFPS(value); +} + +QString ParticleEffectEntityItem::getAnimationSettings() const { + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, frameIndex, or running, those values will be parsed out and + // will over ride the regular animation settings + QString value = _animationSettings; + + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + + QVariant fpsValue(getAnimationFPS()); + settingsMap["fps"] = fpsValue; + + QVariant frameIndexValue(getAnimationFrameIndex()); + settingsMap["frameIndex"] = frameIndexValue; + + QVariant runningValue(getAnimationIsPlaying()); + settingsMap["running"] = runningValue; + + QVariant firstFrameValue(getAnimationFirstFrame()); + settingsMap["firstFrame"] = firstFrameValue; + + QVariant lastFrameValue(getAnimationLastFrame()); + settingsMap["lastFrame"] = lastFrameValue; + + QVariant loopValue(getAnimationLoop()); + settingsMap["loop"] = loopValue; + + QVariant holdValue(getAnimationHold()); + settingsMap["hold"] = holdValue; + + QVariant startAutomaticallyValue(getAnimationStartAutomatically()); + settingsMap["startAutomatically"] = startAutomaticallyValue; + + settingsAsJsonObject = QJsonObject::fromVariantMap(settingsMap); + QJsonDocument newDocument(settingsAsJsonObject); + QByteArray jsonByteArray = newDocument.toJson(QJsonDocument::Compact); + QString jsonByteString(jsonByteArray); + return jsonByteString; +} + +void ParticleEffectEntityItem::stepSimulation(float deltaTime) { + _paXmin = _paYmin = _paZmin = -1.0f; + _paXmax = _paYmax = _paZmax = 1.0f; + + // update particles + quint32 updateIter = _paHead; + while (_paLife[updateIter] > 0.0f) { + _paLife[updateIter] -= deltaTime; + if (_paLife[updateIter] <= 0.0f) { + _paLife[updateIter] = -1.0f; + _paHead = (_paHead + 1) % _maxParticles; + _paCount--; + } + else { + // DUMB FORWARD EULER just to get it done + int j = updateIter * XYZ_STRIDE; + _paPosition[j] += _paVelocity[j] * deltaTime; + _paPosition[j+1] += _paVelocity[j+1] * deltaTime; + _paPosition[j+2] += _paVelocity[j+2] * deltaTime; + + _paXmin = glm::min(_paXmin, _paPosition[j]); + _paYmin = glm::min(_paYmin, _paPosition[j+1]); + _paZmin = glm::min(_paZmin, _paPosition[j+2]); + _paXmax = glm::max(_paXmax, _paPosition[j]); + _paYmax = glm::max(_paYmax, _paPosition[j + 1]); + _paZmax = glm::max(_paZmax, _paPosition[j + 2]); + + // massless particles + _paVelocity[j + 1] += deltaTime * _localGravity; + } + updateIter = (updateIter + 1) % _maxParticles; + } + + // emit new particles + quint32 emitIdx = updateIter; + _partialEmit += ((float)_emitRate) * deltaTime; + quint32 birthed = (quint32)_partialEmit; + _partialEmit -= (float)birthed; + glm::vec3 randOffset; + + for (quint32 i = 0; i < birthed; i++) { + if (_paLife[emitIdx] < 0.0f) { + int j = emitIdx * XYZ_STRIDE; + _paLife[emitIdx] = _lifespan; + randOffset.x = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; + randOffset.y = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; + randOffset.z = (((double)rand() / (double)RAND_MAX) - 0.5) * 0.25 * _emitStrength; + _paVelocity[j] = (_emitDirection.x * _emitStrength) + randOffset.x; + _paVelocity[j + 1] = (_emitDirection.y * _emitStrength) + randOffset.y; + _paVelocity[j + 2] = (_emitDirection.z * _emitStrength) + randOffset.z; + + // DUMB FORWARD EULER just to get it done + _paPosition[j] += _paVelocity[j] * deltaTime; + _paPosition[j + 1] += _paVelocity[j + 1] * deltaTime; + _paPosition[j + 2] += _paVelocity[j + 2] * deltaTime; + + _paXmin = glm::min(_paXmin, _paPosition[j]); + _paYmin = glm::min(_paYmin, _paPosition[j + 1]); + _paZmin = glm::min(_paZmin, _paPosition[j + 2]); + _paXmax = glm::max(_paXmax, _paPosition[j]); + _paYmax = glm::max(_paYmax, _paPosition[j + 1]); + _paZmax = glm::max(_paZmax, _paPosition[j + 2]); + + // massless particles + // and simple gravity down + _paVelocity[j + 1] += deltaTime * _localGravity; + + emitIdx = (emitIdx + 1) % _maxParticles; + _paCount++; + } + else + break; + } +} + +void ParticleEffectEntityItem::resetSimulation() { + for (quint32 i = 0; i < _maxParticles; i++) { + quint32 j = i * XYZ_STRIDE; + _paLife[i] = -1.0f; + _paPosition[j] = 0.0f; + _paPosition[j+1] = 0.0f; + _paPosition[j+2] = 0.0f; + _paVelocity[j] = 0.0f; + _paVelocity[j+1] = 0.0f; + _paVelocity[j+2] = 0.0f; + } + _paCount = 0; + _paHead = 0; + _partialEmit = 0.0f; + + srand(_randSeed); +} + diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h new file mode 100644 index 0000000000..b00eb94685 --- /dev/null +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -0,0 +1,183 @@ +// +// ParticleEffectEntityItem.h +// libraries/entities/src +// +// Some starter code for a particle simulation entity, which could ideally be used for a variety of effects. +// This is some really early and rough stuff here. It was enough for now to just get it up and running in the interface. +// +// Todo's and other notes: +// - The simulation should restart when the AnimationLoop's max frame is reached (or passed), but there doesn't seem +// to be a good way to set that max frame to something reasonable right now. +// - There seems to be a bug whereby entities on the edge of screen will just pop off or on. This is probably due +// to my lack of understanding of how entities in the octree are picked for rendering. I am updating the entity +// dimensions based on the bounds of the sim, but maybe I need to update a dirty flag or something. +// - This should support some kind of pre-roll of the simulation. +// - Just to get this out the door, I just did forward Euler integration. There are better ways. +// - Gravity always points along the Y axis. Support an actual gravity vector. +// - Add the ability to add arbitrary forces to the simulation. +// - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented). +// - Add drag. +// - Add some kind of support for collisions. +// - For simplicity, I'm currently just rendering each particle as a cross of four axis-aligned quads. Really, we'd +// want multiple render modes, including (the most important) textured billboards (always facing camera). Also, these +// should support animated textures. +// - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so +// there's no gaurantee that different clients will see simulations that look anything like the other. +// - MORE? +// +// Created by Jason Rickwald on 3/2/15. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ParticleEffectEntityItem_h +#define hifi_ParticleEffectEntityItem_h + +#include +#include "EntityItem.h" + +class ParticleEffectEntityItem : public EntityItem { +public: + + static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + virtual ~ParticleEffectEntityItem(); + + ALLOW_INSTANTIATION // This class can be instantiated + + // methods for getting/setting all properties of this entity + virtual EntityItemProperties getProperties() const; + virtual bool setProperties(const EntityItemProperties& properties); + + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + + virtual void update(const quint64& now); + virtual bool needsToCallUpdate() const; + + const rgbColor& getColor() const { return _color; } + xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } + + void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } + void setColor(const xColor& value) { + _color[RED_INDEX] = value.red; + _color[GREEN_INDEX] = value.green; + _color[BLUE_INDEX] = value.blue; + } + + void updateShapeType(ShapeType type); + virtual ShapeType getShapeType() const { return _shapeType; } + + virtual void debugDump() const; + + static const float DEFAULT_ANIMATION_FRAME_INDEX; + void setAnimationFrameIndex(float value); + void setAnimationSettings(const QString& value); + + static const bool DEFAULT_ANIMATION_IS_PLAYING; + void setAnimationIsPlaying(bool value); + + static const float DEFAULT_ANIMATION_FPS; + void setAnimationFPS(float value); + + void setAnimationLoop(bool loop) { _animationLoop.setLoop(loop); } + bool getAnimationLoop() const { return _animationLoop.getLoop(); } + + void setAnimationHold(bool hold) { _animationLoop.setHold(hold); } + bool getAnimationHold() const { return _animationLoop.getHold(); } + + void setAnimationStartAutomatically(bool startAutomatically) { _animationLoop.setStartAutomatically(startAutomatically); } + bool getAnimationStartAutomatically() const { return _animationLoop.getStartAutomatically(); } + + void setAnimationFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); } + float getAnimationFirstFrame() const { return _animationLoop.getFirstFrame(); } + + void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } + float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } + + static const quint32 DEFAULT_MAX_PARTICLES; + void setMaxParticles(quint32 maxParticles) { _maxParticles = maxParticles; } + quint32 getMaxParticles() const { return _maxParticles; } + + static const float DEFAULT_LIFESPAN; + void setLifespan(float lifespan) { _lifespan = lifespan; } + float getLifespan() const { return _lifespan; } + + static const float DEFAULT_EMIT_RATE; + void setEmitRate(float emitRate) { _emitRate = emitRate; } + float getEmitRate() const { return _emitRate; } + + static const glm::vec3 DEFAULT_EMIT_DIRECTION; + void setEmitDirection(glm::vec3 emitDirection) { _emitDirection = emitDirection; } + const glm::vec3& getEmitDirection() const { return _emitDirection; } + + static const float DEFAULT_EMIT_STRENGTH; + void setEmitStrength(float emitStrength) { _emitStrength = emitStrength; } + float getEmitStrength() const { return _emitStrength; } + + static const float DEFAULT_LOCAL_GRAVITY; + void setLocalGravity(float localGravity) { _localGravity = localGravity; } + float getLocalGravity() const { return _localGravity; } + + static const float DEFAULT_PARTICLE_RADIUS; + void setParticleRadius(float particleRadius) { _particleRadius = particleRadius; } + float getParticleRadius() const { return _particleRadius; } + + bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); } + float getAnimationFrameIndex() const { return _animationLoop.getFrameIndex(); } + float getAnimationFPS() const { return _animationLoop.getFPS(); } + QString getAnimationSettings() const; + +protected: + + bool isAnimatingSomething() const; + void stepSimulation(float deltaTime); + void resetSimulation(); + + // the properties of this entity + rgbColor _color; + quint32 _maxParticles; + float _lifespan; + float _emitRate; + glm::vec3 _emitDirection; + float _emitStrength; + float _localGravity; + float _particleRadius; + quint64 _lastAnimated; + AnimationLoop _animationLoop; + QString _animationSettings; + ShapeType _shapeType = SHAPE_TYPE_NONE; + + // all the internals of running the particle sim + const quint32 XYZ_STRIDE = 3; + float* _paLife; + float* _paPosition; + float* _paVelocity; + float _partialEmit; + quint32 _paCount; + quint32 _paHead; + float _paXmin; + float _paXmax; + float _paYmin; + float _paYmax; + float _paZmin; + float _paZmax; + unsigned int _randSeed; + +}; + +#endif // hifi_ParticleEffectEntityItem_h + diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 7d5fb83a4e..483323ba4b 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -32,9 +32,6 @@ SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID, const Entit { _type = EntityTypes::Sphere; setProperties(properties); - // NOTE: _volumeMultiplier is used to compute volume: - // volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z - // The formula below looks funny because _dimension.xyz = diameter rather than radius. _volumeMultiplier *= PI / 6.0f; } @@ -117,7 +114,7 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons distance = glm::distance(origin,hitAt); return true; } - return false; + return false; } diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index ac9a080119..2b35ee9a59 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -40,10 +40,16 @@ TextEntityItem::TextEntityItem(const EntityItemID& entityItemID, const EntityIte setProperties(properties); } +const float TEXT_ENTITY_ITEM_FIXED_DEPTH = 0.01f; + void TextEntityItem::setDimensions(const glm::vec3& value) { // NOTE: Text Entities always have a "depth" of 1cm. - float fixedDepth = 0.01f / (float)TREE_SCALE; - _dimensions = glm::vec3(value.x, value.y, fixedDepth); + _dimensions = glm::vec3(value.x, value.y, TEXT_ENTITY_ITEM_FIXED_DEPTH); +} + +void TextEntityItem::setDimensionsInDomainUnits(const glm::vec3& value) { + // NOTE: Text Entities always have a "depth" of 1cm. + _dimensions = glm::vec3(value.x * (float)TREE_SCALE, value.y * (float)TREE_SCALE, TEXT_ENTITY_ITEM_FIXED_DEPTH); } EntityItemProperties TextEntityItem::getProperties() const { @@ -136,7 +142,7 @@ bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f); glm::vec3 normal = _rotation * UNROTATED_NORMAL; plane.setNormal(normal); - plane.setPoint(_position); // the position is definitely a point on our plane + plane.setPoint(getPosition()); // the position is definitely a point on our plane bool intersects = plane.findRayIntersection(rayInfo); diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index d57b5442d6..044975bdc8 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -24,6 +24,7 @@ public: /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately virtual void setDimensions(const glm::vec3& value); + virtual void setDimensionsInDomainUnits(const glm::vec3& value); virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } // methods for getting/setting all properties of an entity diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index 29ed430f7a..f93be2f563 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -46,7 +46,7 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, // which can handle all potential rotations? // the getMaximumAACube is the relaxed form. _oldEntityCube = _existingEntity->getMaximumAACube(); - _oldEntityBox = _oldEntityCube.clamp(0.0f, 1.0f); // clamp to domain bounds + _oldEntityBox = _oldEntityCube.clamp(0.0f, (float)TREE_SCALE); // clamp to domain bounds // If the old properties doesn't contain the properties required to calculate a bounding box, // get them from the existing entity. Registration point is required to correctly calculate @@ -59,8 +59,8 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, // get the old property value and set it in our properties in order for our bounds // calculations to work. if (_properties.containsPositionChange() && !_properties.containsDimensionsChange()) { - glm::vec3 oldDimensionsInMeters = _existingEntity->getDimensions() * (float)TREE_SCALE; - _properties.setDimensions(oldDimensionsInMeters); + glm::vec3 oldDimensions= _existingEntity->getDimensions(); + _properties.setDimensions(oldDimensions); if (_wantDebug) { qDebug() << " ** setting properties dimensions - had position change, no dimension change **"; @@ -68,8 +68,8 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, } if (!_properties.containsPositionChange() && _properties.containsDimensionsChange()) { - glm::vec3 oldPositionInMeters = _existingEntity->getPosition() * (float)TREE_SCALE; - _properties.setPosition(oldPositionInMeters); + glm::vec3 oldPosition= _existingEntity->getPosition(); + _properties.setPosition(oldPosition); if (_wantDebug) { qDebug() << " ** setting properties position - had dimensions change, no position change **"; @@ -114,7 +114,7 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, } } else { - _newEntityCube = _properties.getMaximumAACubeInTreeUnits(); + _newEntityCube = _properties.getMaximumAACube(); _removeOld = true; // our properties are going to move us, so remember this for later processing if (_wantDebug) { @@ -122,7 +122,7 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTree* tree, } } - _newEntityBox = _newEntityCube.clamp(0.0f, 1.0f); // clamp to domain bounds + _newEntityBox = _newEntityCube.clamp(0.0f, (float)TREE_SCALE); // clamp to domain bounds if (_wantDebug) { diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 72d153289a..cbbaae3d82 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1266,6 +1266,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, QVector humanIKJointIDs(humanIKJointNames.size()); QVariantHash blendshapeMappings = mapping.value("bs").toHash(); + QMultiHash blendshapeIndices; for (int i = 0;; i++) { QByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i]; @@ -1720,12 +1721,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } else if (object.properties.last() == "BlendShapeChannel") { QByteArray name = object.properties.at(1).toByteArray(); + name = name.left(name.indexOf('\0')); if (!blendshapeIndices.contains(name)) { // try everything after the dot name = name.mid(name.lastIndexOf('.') + 1); } QString id = getID(object.properties); + geometry.blendshapeChannelNames << name; foreach (const WeightedIndex& index, blendshapeIndices.values(name)) { blendshapeChannelIndices.insert(id, index); } diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 3f6f836950..6912dd730f 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -257,6 +257,8 @@ public: /// given a meshIndex this will return the name of the model that mesh belongs to if known QString getModelNameOfMesh(int meshIndex) const; + + QList blendshapeChannelNames; }; Q_DECLARE_METATYPE(FBXGeometry) diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index c9add10e5f..c7f1f4ec88 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -74,7 +74,7 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeEntityAddOrEdit: case PacketTypeEntityData: - return VERSION_ENTITIES_LIGHT_HAS_INTENSITY_AND_COLOR_PROPERTIES; + return VERSION_ENTITIES_USE_METERS_AND_RADIANS; case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index c89127058f..3133fd9649 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -129,6 +129,8 @@ const PacketVersion VERSION_ENTITIES_HAVE_USER_DATA = 6; const PacketVersion VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME = 7; const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SHAPE_TYPE = 8; const PacketVersion VERSION_ENTITIES_LIGHT_HAS_INTENSITY_AND_COLOR_PROPERTIES = 9; +const PacketVersion VERSION_ENTITIES_HAS_PARTICLES = 10; +const PacketVersion VERSION_ENTITIES_USE_METERS_AND_RADIANS = 11; const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1; #endif // hifi_PacketHeaders_h diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 7eadb0a3dd..658f32aba1 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -12,9 +12,10 @@ #include #include +#include +#include #include #include -#include #include @@ -314,13 +315,51 @@ void Resource::handleReplyTimeout() { "received" << _bytesReceived << "total" << _bytesTotal); } +void Resource::maybeRefresh() { + if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) { + QNetworkReply* reply = qobject_cast(sender()); + QVariant variant = reply->header(QNetworkRequest::LastModifiedHeader); + QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url); + if (variant.isValid() && variant.canConvert() && metaData.isValid()) { + QDateTime lastModified = variant.value(); + QDateTime lastModifiedOld = metaData.lastModified(); + if (lastModified.isValid() && lastModifiedOld.isValid() && + lastModifiedOld == lastModified) { + // We don't need to update, return + return; + } + } + qDebug() << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing."; + refresh(); + } +} + void Resource::makeRequest() { _reply = NetworkAccessManager::getInstance().get(_request); connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished())); - + + if (_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) { + // If the file as been updated since it was cached, refresh it + QNetworkRequest request(_request); + request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false); + QNetworkReply* reply = NetworkAccessManager::getInstance().head(request); + connect(reply, &QNetworkReply::finished, this, &Resource::maybeRefresh); + } else { + if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) { + QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url); + if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) { + // If the expiration date is NULL or in the past, + // put one far enough away that it won't be an issue. + metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100)); + NetworkAccessManager::getInstance().cache()->updateMetaData(metaData); + } + } + } + _replyTimer = new QTimer(this); connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout())); _replyTimer->setSingleShot(true); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 62fca71ea5..11b091a9e3 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -149,7 +149,7 @@ public: /// For loading resources, returns the load progress. float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; } - + /// Refreshes the resource. void refresh(); @@ -169,6 +169,10 @@ signals: protected slots: void attemptRequest(); + + /// Refreshes the resource if the last modified date on the network + /// is greater than the last modified date in the cache. + void maybeRefresh(); protected: diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index e925ea5aba..6b8f8c31e8 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -274,7 +274,7 @@ int Octree::readElementData(OctreeElement* destinationElement, const unsigned ch if (destinationElement->getScale() < SCALE_AT_DANGEROUSLY_DEEP_RECURSION) { qDebug() << "UNEXPECTED: readElementData() destination element is unreasonably small [" - << destinationElement->getScale() * (float)TREE_SCALE << " meters] " + << destinationElement->getScale() << " meters] " << " Discarding " << bytesAvailable << " remaining bytes."; return bytesAvailable; // assume we read the entire buffer... } @@ -706,7 +706,7 @@ bool findRayIntersectionOp(OctreeElement* element, void* extraData) { bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, Octree::lockType lockType, bool* accurateResult, bool precisionPicking) { - RayArgs args = { origin / (float)(TREE_SCALE), direction, element, distance, face, + RayArgs args = { origin, direction, element, distance, face, intersectedObject, false, precisionPicking}; distance = FLT_MAX; @@ -726,10 +726,6 @@ bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc recurseTreeWithOperation(findRayIntersectionOp, &args); - if (args.found) { - args.distance *= (float)(TREE_SCALE); // scale back up to meters - } - if (gotLock) { unlock(); } @@ -753,8 +749,7 @@ bool findSpherePenetrationOp(OctreeElement* element, void* extraData) { SphereArgs* args = static_cast(extraData); // coarse check against bounds - const AACube& box = element->getAACube(); - if (!box.expandedContains(args->center, args->radius)) { + if (!element->getAACube().expandedContains(args->center, args->radius)) { return false; } if (element->hasContent()) { @@ -762,7 +757,7 @@ bool findSpherePenetrationOp(OctreeElement* element, void* extraData) { if (element->findSpherePenetration(args->center, args->radius, elementPenetration, &args->penetratedObject)) { // NOTE: it is possible for this penetration accumulation algorithm to produce a // final penetration vector with zero length. - args->penetration = addPenetrations(args->penetration, elementPenetration * (float)(TREE_SCALE)); + args->penetration = addPenetrations(args->penetration, elementPenetration); args->found = true; } } @@ -776,8 +771,8 @@ bool Octree::findSpherePenetration(const glm::vec3& center, float radius, glm::v void** penetratedObject, Octree::lockType lockType, bool* accurateResult) { SphereArgs args = { - center / (float)(TREE_SCALE), - radius / (float)(TREE_SCALE), + center, + radius, penetration, false, NULL }; @@ -838,14 +833,13 @@ bool findCapsulePenetrationOp(OctreeElement* element, void* extraData) { CapsuleArgs* args = static_cast(extraData); // coarse check against bounds - const AACube& box = element->getAACube(); - if (!box.expandedIntersectsSegment(args->start, args->end, args->radius)) { + if (!element->getAACube().expandedIntersectsSegment(args->start, args->end, args->radius)) { return false; } if (element->hasContent()) { glm::vec3 nodePenetration; - if (box.findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) { - args->penetration = addPenetrations(args->penetration, nodePenetration * (float)(TREE_SCALE)); + if (element->getAACube().findCapsulePenetration(args->start, args->end, args->radius, nodePenetration)) { + args->penetration = addPenetrations(args->penetration, nodePenetration); args->found = true; } } @@ -872,8 +866,7 @@ bool findContentInCubeOp(OctreeElement* element, void* extraData) { ContentArgs* args = static_cast(extraData); // coarse check against bounds - AACube cube = element->getAACube(); - cube.scale(TREE_SCALE); + const AACube& cube = element->getAACube(); if (!cube.touches(args->cube)) { return false; } @@ -892,12 +885,7 @@ bool findContentInCubeOp(OctreeElement* element, void* extraData) { bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration, Octree::lockType lockType, bool* accurateResult) { - CapsuleArgs args = { - start / (float)(TREE_SCALE), - end / (float)(TREE_SCALE), - radius / (float)(TREE_SCALE), - penetration, - false }; + CapsuleArgs args = { start, end, radius, penetration, false }; penetration = glm::vec3(0.0f, 0.0f, 0.0f); bool gotLock = false; @@ -945,8 +933,7 @@ public: // Find the smallest colored voxel enclosing a point (if there is one) bool getElementEnclosingOperation(OctreeElement* element, void* extraData) { GetElementEnclosingArgs* args = static_cast(extraData); - AACube elementBox = element->getAACube(); - if (elementBox.contains(args->point)) { + if (element->getAACube().contains(args->point)) { if (element->hasContent() && element->isLeaf()) { // we've reached a solid leaf containing the point, return the element. args->element = element; @@ -1212,9 +1199,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, // If the user also asked for occlusion culling, check if this element is occluded, but only if it's not a leaf. // leaf occlusion is handled down below when we check child nodes if (params.wantOcclusionCulling && !element->isLeaf()) { - AACube voxelBox = element->getAACube(); - voxelBox.scale(TREE_SCALE); - OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon(params.viewFrustum->getProjectedPolygon(voxelBox)); + OctreeProjectedPolygon* voxelPolygon = + new OctreeProjectedPolygon(params.viewFrustum->getProjectedPolygon(element->getAACube())); // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion // culling and proceed as normal @@ -1365,10 +1351,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* element, if (params.wantOcclusionCulling && childElement->isLeaf()) { // Don't check occlusion here, just add them to our distance ordered array... - AACube voxelBox = childElement->getAACube(); - voxelBox.scale(TREE_SCALE); OctreeProjectedPolygon* voxelPolygon = new OctreeProjectedPolygon( - params.viewFrustum->getProjectedPolygon(voxelBox)); + params.viewFrustum->getProjectedPolygon(childElement->getAACube())); // In order to check occlusion culling, the shadow has to be "all in view" otherwise, we ignore occlusion // culling and proceed as normal @@ -2084,58 +2068,6 @@ bool Octree::countOctreeElementsOperation(OctreeElement* element, void* extraDat return true; // keep going } -void Octree::copySubTreeIntoNewTree(OctreeElement* startElement, Octree* destinationTree, bool rebaseToRoot) { - OctreeElementBag elementBag; - elementBag.insert(startElement); - int chopLevels = 0; - if (rebaseToRoot) { - chopLevels = numberOfThreeBitSectionsInCode(startElement->getOctalCode()); - } - - EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS, chopLevels); - OctreeElementExtraEncodeData extraEncodeData; - params.extraEncodeData = &extraEncodeData; - - OctreePacketData packetData; - - while (!elementBag.isEmpty()) { - OctreeElement* subTree = elementBag.extract(); - packetData.reset(); // reset the packet between usage - // ask our tree to write a bitsteam - encodeTreeBitstream(subTree, &packetData, elementBag, params); - // ask destination tree to read the bitstream - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS); - destinationTree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); - } -} - -void Octree::copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinationElement) { - OctreeElementBag elementBag; - // If we were given a specific element, start from there, otherwise start from root - elementBag.insert(sourceTree->_rootElement); - - OctreePacketData packetData; - - EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS); - OctreeElementExtraEncodeData extraEncodeData; - params.extraEncodeData = &extraEncodeData; - - while (!elementBag.isEmpty()) { - OctreeElement* subTree = elementBag.extract(); - - packetData.reset(); // reset between usage - - // ask our tree to write a bitsteam - sourceTree->encodeTreeBitstream(subTree, &packetData, elementBag, params); - - // ask destination tree to read the bitstream - bool wantImportProgress = true; - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationElement, - 0, SharedNodePointer(), wantImportProgress); - readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); - } -} - void Octree::cancelImport() { _stopImport = true; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 870e18983e..a2265e38ed 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -270,6 +270,9 @@ public: void recurseTreeWithOperation(RecurseOctreeOperation operation, void* extraData = NULL); void recurseTreeWithPostOperation(RecurseOctreeOperation operation, void* extraData = NULL); + /// \param operation type of operation + /// \param point point in world-frame (meters) + /// \param extraData hook for user data to be interpreted by special context void recurseTreeWithOperationDistanceSorted(RecurseOctreeOperation operation, const glm::vec3& point, void* extraData = NULL); @@ -308,8 +311,13 @@ public: bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); + /// \param cube query cube in world-frame (meters) + /// \param[out] cubes list of cubes (world-frame) of child elements that have content bool findContentInCube(const AACube& cube, CubeList& cubes); + /// \param point query point in world-frame (meters) + /// \param lockType how to lock the tree (Lock, TryLock, NoLock) + /// \param[out] accurateResult pointer to output result, will be set "true" or "false" if non-null OctreeElement* getElementEnclosingPoint(const glm::vec3& point, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); @@ -323,9 +331,6 @@ public: unsigned long getOctreeElementsCount(); - void copySubTreeIntoNewTree(OctreeElement* startElement, Octree* destinationTree, bool rebaseToRoot); - void copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinationElement); - bool getShouldReaverage() const { return _shouldReaverage; } void recurseElementWithOperation(OctreeElement* element, RecurseOctreeOperation operation, diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 93792678e2..c8564ee5cb 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -194,14 +194,14 @@ void OctreeElement::setShouldRender(bool shouldRender) { } void OctreeElement::calculateAACube() { - glm::vec3 corner; - // copy corner into cube - copyFirstVertexForCode(getOctalCode(),(float*)&corner); + glm::vec3 corner; + copyFirstVertexForCode(getOctalCode(), (float*)&corner); // this tells you the "size" of the voxel - float voxelScale = 1 / powf(2, numberOfThreeBitSectionsInCode(getOctalCode())); - _cube.setBox(corner,voxelScale); + float voxelScale = (float)TREE_SCALE / powf(2.0f, numberOfThreeBitSectionsInCode(getOctalCode())); + corner *= (float)TREE_SCALE; + _cube.setBox(corner, voxelScale); } void OctreeElement::deleteChildAtIndex(int childIndex) { @@ -1221,9 +1221,7 @@ float OctreeElement::getEnclosingRadius() const { } ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) const { - AACube cube = _cube; // use temporary cube so we can scale it - cube.scale(TREE_SCALE); - return viewFrustum.cubeInFrustum(cube); + return viewFrustum.cubeInFrustum(_cube); } // There are two types of nodes for which we want to "render" @@ -1257,14 +1255,13 @@ bool OctreeElement::calculateShouldRender(const ViewFrustum* viewFrustum, float // does as much math as possible in voxel scale and then scales up to TREE_SCALE at end float OctreeElement::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const { glm::vec3 furthestPoint; - viewFrustum.getFurthestPointFromCameraVoxelScale(getAACube(), furthestPoint); - glm::vec3 temp = viewFrustum.getPositionVoxelScale() - furthestPoint; - float distanceToFurthestPoint = sqrtf(glm::dot(temp, temp)); - return distanceToFurthestPoint * (float)TREE_SCALE; + viewFrustum.getFurthestPointFromCamera(_cube, furthestPoint); + glm::vec3 temp = viewFrustum.getPosition() - furthestPoint; + return sqrtf(glm::dot(temp, temp)); } float OctreeElement::distanceToCamera(const ViewFrustum& viewFrustum) const { - glm::vec3 center = _cube.calcCenter() * (float)TREE_SCALE; + glm::vec3 center = _cube.calcCenter(); glm::vec3 temp = viewFrustum.getPosition() - center; float distanceToVoxelCenter = sqrtf(glm::dot(temp, temp)); return distanceToVoxelCenter; @@ -1337,16 +1334,12 @@ bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3 keepSearching = true; // assume that we will continue searching after this. - AACube cube = getAACube(); float distanceToElementCube = std::numeric_limits::max(); float distanceToElementDetails = distance; BoxFace localFace; - AACube debugCube = cube; - debugCube.scale((float)TREE_SCALE); - // if the ray doesn't intersect with our cube, we can stop searching! - if (!cube.findRayIntersection(origin, direction, distanceToElementCube, localFace)) { + if (!_cube.findRayIntersection(origin, direction, distanceToElementCube, localFace)) { keepSearching = false; // no point in continuing to search return false; // we did not intersect } @@ -1358,7 +1351,7 @@ bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3 // if the distance to the element cube is not less than the current best distance, then it's not possible // for any details inside the cube to be closer so we don't need to consider them. - if (cube.contains(origin) || distanceToElementCube < distance) { + if (_cube.contains(origin) || distanceToElementCube < distance) { if (findDetailedRayIntersection(origin, direction, keepSearching, element, distanceToElementDetails, face, intersectedObject, precisionPicking, distanceToElementCube)) { @@ -1392,6 +1385,7 @@ bool OctreeElement::findDetailedRayIntersection(const glm::vec3& origin, const g bool OctreeElement::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const { + // center and radius are in meters, so we have to scale the _cube into world-frame return _cube.findSpherePenetration(center, radius, penetration); } @@ -1529,15 +1523,15 @@ int OctreeElement::getMyChildContaining(const AACube& cube) const { if (cubeScale > ourScale) { qDebug() << "UNEXPECTED -- OctreeElement::getMyChildContaining() -- (cubeScale > ourScale)"; qDebug() << " cube=" << cube; - qDebug() << " elements AACube=" << getAACube(); + qDebug() << " elements AACube=" << _cube; qDebug() << " cubeScale=" << cubeScale; qDebug() << " ourScale=" << ourScale; assert(false); } // Determine which of our children the minimum and maximum corners of the cube live in... - glm::vec3 cubeCornerMinimum = glm::clamp(cube.getCorner(), 0.0f, 1.0f); - glm::vec3 cubeCornerMaximum = glm::clamp(cube.calcTopFarLeft(), 0.0f, 1.0f); + glm::vec3 cubeCornerMinimum = glm::clamp(cube.getCorner(), 0.0f, (float)TREE_SCALE); + glm::vec3 cubeCornerMaximum = glm::clamp(cube.calcTopFarLeft(), 0.0f, (float)TREE_SCALE); if (_cube.contains(cubeCornerMinimum) && _cube.contains(cubeCornerMaximum)) { int childIndexCubeMinimum = getMyChildContainingPoint(cubeCornerMinimum); diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 8c83d9976e..e2229b2214 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -125,6 +125,10 @@ public: bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, bool precisionPicking, float distanceToElementCube); + /// \param center center of sphere in meters + /// \param radius radius of sphere in meters + /// \param[out] penetration pointing into cube from sphere + /// \param penetratedObject unused virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const; diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 8fe4fbd4d5..d47a324e07 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -101,7 +101,6 @@ void OctreeHeadlessViewer::queryOctree() { voxelDetailsForCode(rootCode, rootDetails); jurisdictions.unlock(); AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - serverBounds.scale(TREE_SCALE); ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds); @@ -170,7 +169,6 @@ void OctreeHeadlessViewer::queryOctree() { voxelDetailsForCode(rootCode, rootDetails); jurisdictions.unlock(); AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - serverBounds.scale(TREE_SCALE); ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds); if (serverFrustumLocation != ViewFrustum::OUTSIDE) { diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index 5bcf49a1a4..187f916d35 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -65,7 +65,8 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Shar unsigned int numBytesPacketHeader = numBytesForPacketHeader(dataByteArray); QUuid sourceUUID = uuidFromPacketHeader(dataByteArray); PacketType expectedType = getExpectedPacketType(); - PacketVersion expectedVersion = _tree->expectedVersion(); // TODO: would be better to read this from the packet! + // packetVersion is the second byte + PacketVersion packetVersion = dataByteArray[1]; if(command == expectedType) { PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram expected PacketType", showTimingDetails); @@ -117,7 +118,7 @@ void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const Shar if (sectionLength) { // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL, - sourceUUID, sourceNode, false, expectedVersion); + sourceUUID, sourceNode, false, packetVersion); _tree->lockForWrite(); OctreePacketData packetData(packetIsCompressed); packetData.loadFinalizedContent(dataAt, sectionLength); diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp index fac017218d..594dd8325e 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -837,33 +837,6 @@ void ViewFrustum::getFurthestPointFromCamera(const AACube& box, glm::vec3& furth } } -void ViewFrustum::getFurthestPointFromCameraVoxelScale(const AACube& box, glm::vec3& furthestPoint) const { - const glm::vec3& bottomNearRight = box.getCorner(); - float scale = box.getScale(); - float halfScale = scale * 0.5f; - - if (_positionVoxelScale.x < bottomNearRight.x + halfScale) { - // we are to the right of the center, so the left edge is furthest - furthestPoint.x = bottomNearRight.x + scale; - } else { - furthestPoint.x = bottomNearRight.x; - } - - if (_positionVoxelScale.y < bottomNearRight.y + halfScale) { - // we are below of the center, so the top edge is furthest - furthestPoint.y = bottomNearRight.y + scale; - } else { - furthestPoint.y = bottomNearRight.y; - } - - if (_positionVoxelScale.z < bottomNearRight.z + halfScale) { - // we are to the near side of the center, so the far side edge is furthest - furthestPoint.z = bottomNearRight.z + scale; - } else { - furthestPoint.z = bottomNearRight.z; - } -} - float ViewFrustum::distanceToCamera(const glm::vec3& point) const { glm::vec3 temp = getPosition() - point; float distanceToPoint = sqrtf(glm::dot(temp, temp)); diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index a74140b94f..3c8639d215 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -30,19 +30,18 @@ const float DEFAULT_KEYHOLE_RADIUS = 3.0f; const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f; const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f; const float DEFAULT_NEAR_CLIP = 0.08f; -const float DEFAULT_FAR_CLIP = TREE_SCALE; +const float DEFAULT_FAR_CLIP = (float)TREE_SCALE; class ViewFrustum { public: ViewFrustum(); // setters for camera attributes - void setPosition(const glm::vec3& p) { _position = p; _positionVoxelScale = (p / (float)TREE_SCALE); } + void setPosition(const glm::vec3& p) { _position = p; } void setOrientation(const glm::quat& orientationAsQuaternion); // getters for camera attributes const glm::vec3& getPosition() const { return _position; } - const glm::vec3& getPositionVoxelScale() const { return _positionVoxelScale; } const glm::quat& getOrientation() const { return _orientation; } const glm::vec3& getDirection() const { return _direction; } const glm::vec3& getUp() const { return _up; } @@ -119,9 +118,6 @@ public: OctreeProjectedPolygon getProjectedPolygon(const AACube& box) const; void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const; - // assumes box is in voxel scale, not TREE_SCALE, will scale view frustum's position accordingly - void getFurthestPointFromCameraVoxelScale(const AACube& box, glm::vec3& furthestPoint) const; - float distanceToCamera(const glm::vec3& point) const; void evalProjectionMatrix(glm::mat4& proj) const; @@ -135,8 +131,7 @@ private: void calculateOrthographic(); // camera location/orientation attributes - glm::vec3 _position = glm::vec3(0.0f); // the position in TREE_SCALE - glm::vec3 _positionVoxelScale = glm::vec3(0.0f); // the position in voxel scale + glm::vec3 _position = glm::vec3(0.0f); // the position in world-frame glm::quat _orientation = glm::quat(); // calculated for orientation diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 699b4cc386..cd0769255b 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -84,23 +84,22 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { // bypass const-ness so we can remember the substep const_cast(this)->_lastKinematicSubstep = substep; } - worldTrans.setOrigin(glmToBullet(_entity->getPositionInMeters() - ObjectMotionState::getWorldOffset())); + worldTrans.setOrigin(glmToBullet(_entity->getPosition() - ObjectMotionState::getWorldOffset())); worldTrans.setRotation(glmToBullet(_entity->getRotation())); } // This callback is invoked by the physics simulation at the end of each simulation frame... // iff the corresponding RigidBody is DYNAMIC and has moved. void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { - _entity->setPositionInMeters(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset()); + _entity->setPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset()); _entity->setRotation(bulletToGLM(worldTrans.getRotation())); glm::vec3 v; getVelocity(v); - _entity->setVelocityInMeters(v); + _entity->setVelocity(v); getAngularVelocity(v); - // DANGER! EntityItem stores angularVelocity in degrees/sec!!! - _entity->setAngularVelocity(glm::degrees(v)); + _entity->setAngularVelocity(v); _entity->setLastSimulated(usecTimestampNow()); @@ -119,7 +118,7 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) { if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) { if (flags & EntityItem::DIRTY_POSITION) { - _sentPosition = _entity->getPositionInMeters() - ObjectMotionState::getWorldOffset(); + _sentPosition = _entity->getPosition() - ObjectMotionState::getWorldOffset(); btTransform worldTrans; worldTrans.setOrigin(glmToBullet(_sentPosition)); @@ -156,14 +155,13 @@ void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) { void EntityMotionState::updateObjectVelocities() { if (_body) { - _sentVelocity = _entity->getVelocityInMeters(); + _sentVelocity = _entity->getVelocity(); setVelocity(_sentVelocity); - // DANGER! EntityItem stores angularVelocity in degrees/sec!!! - _sentAngularVelocity = glm::radians(_entity->getAngularVelocity()); + _sentAngularVelocity = _entity->getAngularVelocity(); setAngularVelocity(_sentAngularVelocity); - _sentAcceleration = _entity->getGravityInMeters(); + _sentAcceleration = _entity->getGravity(); setGravity(_sentAcceleration); _body->setActivationState(ACTIVE_TAG); @@ -219,8 +217,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setVelocity(_sentVelocity); _sentAcceleration = bulletToGLM(_body->getGravity()); properties.setGravity(_sentAcceleration); - // DANGER! EntityItem stores angularVelocity in degrees/sec!!! - properties.setAngularVelocity(glm::degrees(_sentAngularVelocity)); + properties.setAngularVelocity(_sentAngularVelocity); } // RELIABLE_SEND_HACK: count number of updates for entities at rest so we can stop sending them after some limit. diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 8823071132..6cb361a4a3 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -471,17 +471,17 @@ AABox AABox::clamp(float min, float max) const { return AABox(clampedCorner, clampedScale); } -AABox& AABox::operator += (const glm::vec3& point) { - _corner = glm::min(_corner, point); - _scale = glm::max(_scale, point - _corner); - - return (*this); -} - -AABox& AABox::operator += (const AABox& box) { - if (!box.isInvalid()) { - (*this) += box._corner; - _scale = glm::max(_scale, box.calcTopFarLeft() - _corner); - } - return (*this); -} +AABox& AABox::operator += (const glm::vec3& point) { + _corner = glm::min(_corner, point); + _scale = glm::max(_scale, point - _corner); + + return (*this); +} + +AABox& AABox::operator += (const AABox& box) { + if (!box.isInvalid()) { + (*this) += box._corner; + _scale = glm::max(_scale, box.calcTopFarLeft() - _corner); + } + return (*this); +} diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index d862957642..9beb0a85f2 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -72,9 +72,9 @@ public: AABox clamp(const glm::vec3& min, const glm::vec3& max) const; AABox clamp(float min, float max) const; - AABox& operator += (const glm::vec3& point); - AABox& operator += (const AABox& box); - + AABox& operator += (const glm::vec3& point); + AABox& operator += (const AABox& box); + bool isInvalid() const { return _corner == glm::vec3(std::numeric_limits::infinity()); } private: diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index 82bd471975..f26f103487 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -129,18 +129,17 @@ unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNu void voxelDetailsForCode(const unsigned char* octalCode, VoxelPositionSize& voxelPositionSize) { float output[3]; memset(&output[0], 0, 3 * sizeof(float)); - float currentScale = 1.0; + float currentScale = 1.0f; if (octalCode) { for (int i = 0; i < numberOfThreeBitSectionsInCode(octalCode); i++) { - currentScale *= 0.5; + currentScale *= 0.5f; int sectionIndex = sectionValue(octalCode + 1 + (BITS_IN_OCTAL * i / BITS_IN_BYTE), (BITS_IN_OCTAL * i) % BITS_IN_BYTE); for (int j = 0; j < BITS_IN_OCTAL; j++) { - output[j] += currentScale * (int)oneAtBit(sectionIndex, (BITS_IN_BYTE - BITS_IN_OCTAL) + j); + output[j] += currentScale * (float)oneAtBit(sectionIndex, (BITS_IN_BYTE - BITS_IN_OCTAL) + j); } - } } voxelPositionSize.x = output[0]; @@ -152,7 +151,7 @@ void voxelDetailsForCode(const unsigned char* octalCode, VoxelPositionSize& voxe void copyFirstVertexForCode(const unsigned char* octalCode, float* output) { memset(output, 0, 3 * sizeof(float)); - float currentScale = 0.5; + float currentScale = 0.5f; for (int i = 0; i < numberOfThreeBitSectionsInCode(octalCode); i++) { int sectionIndex = sectionValue(octalCode + 1 + (3 * i / 8), (3 * i) % 8); @@ -165,12 +164,6 @@ void copyFirstVertexForCode(const unsigned char* octalCode, float* output) { } } -float * firstVertexForCode(const unsigned char* octalCode) { - float * firstVertex = new float[3]; - copyFirstVertexForCode(octalCode, firstVertex); - return firstVertex; -} - OctalCodeComparison compareOctalCodes(const unsigned char* codeA, const unsigned char* codeB) { if (!codeA || !codeB) { return ILLEGAL_CODE; diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 07895ff67f..9229157c3d 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -43,9 +43,6 @@ const int CHECK_NODE_ONLY = -1; bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild = CHECK_NODE_ONLY); -// Note: copyFirstVertexForCode() is preferred because it doesn't allocate memory for the return -// but other than that these do the same thing. -float * firstVertexForCode(const unsigned char* octalCode); void copyFirstVertexForCode(const unsigned char* octalCode, float* output); struct VoxelPositionSize { diff --git a/tests/octree/src/ModelTests.cpp b/tests/octree/src/ModelTests.cpp index 00cb3901e5..e4309100af 100644 --- a/tests/octree/src/ModelTests.cpp +++ b/tests/octree/src/ModelTests.cpp @@ -45,12 +45,9 @@ void EntityTests::entityTreeTests(bool verbose) { entityID.isKnownID = false; // this is a temporary workaround to allow local tree entities to be added with known IDs EntityItemProperties properties; float oneMeter = 1.0f; - //float halfMeter = oneMeter / 2.0f; float halfOfDomain = TREE_SCALE * 0.5f; - glm::vec3 positionNearOriginInMeters(oneMeter, oneMeter, oneMeter); // when using properties, these are in meter not tree units - glm::vec3 positionAtCenterInMeters(halfOfDomain, halfOfDomain, halfOfDomain); - glm::vec3 positionNearOriginInTreeUnits = positionNearOriginInMeters / (float)TREE_SCALE; - glm::vec3 positionAtCenterInTreeUnits = positionAtCenterInMeters / (float)TREE_SCALE; + glm::vec3 positionNearOrigin(oneMeter, oneMeter, oneMeter); // when using properties, these are in meter not tree units + glm::vec3 positionAtCenter(halfOfDomain, halfOfDomain, halfOfDomain); { testsTaken++; @@ -59,28 +56,27 @@ void EntityTests::entityTreeTests(bool verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } - properties.setPosition(positionAtCenterInMeters); + properties.setPosition(positionAtCenter); // TODO: Fix these unit tests. - //properties.setRadius(halfMeter); //properties.setModelURL("http://s3.amazonaws.com/hifi-public/ozan/theater.fbx"); tree.addEntity(entityID, properties); - float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units - const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); + float targetRadius = oneMeter * 2.0f; + const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenter, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); - AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); + const AACube& elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" - << elementCube.getCorner().x * TREE_SCALE << "," - << elementCube.getCorner().y * TREE_SCALE << "," - << elementCube.getCorner().z * TREE_SCALE << ":" - << elementCube.getScale() * TREE_SCALE; + << elementCube.getCorner().x << "," + << elementCube.getCorner().y << "," + << elementCube.getCorner().z << ":" + << elementCube.getScale(); qDebug() << "elementCube.getScale()=" << elementCube.getScale(); //containingElement->printDebugDetails("containingElement"); } @@ -103,27 +99,27 @@ void EntityTests::entityTreeTests(bool verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } - glm::vec3 newPosition = positionNearOriginInMeters; + glm::vec3 newPosition = positionNearOrigin; properties.setPosition(newPosition); tree.updateEntity(entityID, properties, true); - float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units - const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOriginInTreeUnits, targetRadius); + float targetRadius = oneMeter * 2.0f; + const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOrigin, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); - AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); + const AACube& elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" - << elementCube.getCorner().x * TREE_SCALE << "," - << elementCube.getCorner().y * TREE_SCALE << "," - << elementCube.getCorner().z * TREE_SCALE << ":" - << elementCube.getScale() * TREE_SCALE; + << elementCube.getCorner().x << "," + << elementCube.getCorner().y << "," + << elementCube.getCorner().z << ":" + << elementCube.getScale(); //containingElement->printDebugDetails("containingElement"); } @@ -143,27 +139,27 @@ void EntityTests::entityTreeTests(bool verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } - glm::vec3 newPosition = positionAtCenterInMeters; + glm::vec3 newPosition = positionAtCenter; properties.setPosition(newPosition); tree.updateEntity(entityID, properties, true); - float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units - const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); + float targetRadius = oneMeter * 2.0f; + const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenter, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); - AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); + const AACube& elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" - << elementCube.getCorner().x * TREE_SCALE << "," - << elementCube.getCorner().y * TREE_SCALE << "," - << elementCube.getCorner().z * TREE_SCALE << ":" - << elementCube.getScale() * TREE_SCALE; + << elementCube.getCorner().x << "," + << elementCube.getCorner().y << "," + << elementCube.getCorner().z << ":" + << elementCube.getScale(); //containingElement->printDebugDetails("containingElement"); } @@ -184,11 +180,11 @@ void EntityTests::entityTreeTests(bool verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } - float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units + float targetRadius = oneMeter * 2.0f; quint64 start = usecTimestampNow(); const EntityItem* foundEntityByRadius = NULL; for (int i = 0; i < TEST_ITERATIONS; i++) { - foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); + foundEntityByRadius = tree.findClosestEntity(positionAtCenter, targetRadius); } quint64 end = usecTimestampNow(); @@ -262,13 +258,11 @@ void EntityTests::entityTreeTests(bool verbose) { float randomX = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); float randomY = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); float randomZ = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); - glm::vec3 randomPositionInMeters(randomX,randomY,randomZ); - glm::vec3 randomPositionInTreeUnits = randomPositionInMeters / (float)TREE_SCALE; + glm::vec3 randomPosition(randomX,randomY,randomZ); - properties.setPosition(randomPositionInMeters); + properties.setPosition(randomPosition); // TODO: fix these unit tests - //properties.setRadius(halfMeter); //properties.setModelURL("http://s3.amazonaws.com/hifi-public/ozan/theater.fbx"); if (extraVerbose) { @@ -287,14 +281,14 @@ void EntityTests::entityTreeTests(bool verbose) { } quint64 startFind = usecTimestampNow(); - float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units - const EntityItem* foundEntityByRadius = tree.findClosestEntity(randomPositionInTreeUnits, targetRadius); + float targetRadius = oneMeter * 2.0f; + const EntityItem* foundEntityByRadius = tree.findClosestEntity(randomPosition, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); quint64 endFind = usecTimestampNow(); totalElapsedFind += (endFind - startFind); EntityTreeElement* containingElement = tree.getContainingElement(entityID); - AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); + const AACube& elementCube = containingElement ? containingElement->getAACube() : AACube(); bool elementIsBestFit = containingElement->bestFitEntityBounds(foundEntityByID); @@ -303,10 +297,10 @@ void EntityTests::entityTreeTests(bool verbose) { qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" - << elementCube.getCorner().x * TREE_SCALE << "," - << elementCube.getCorner().y * TREE_SCALE << "," - << elementCube.getCorner().z * TREE_SCALE << ":" - << elementCube.getScale() * TREE_SCALE; + << elementCube.getCorner().x << "," + << elementCube.getCorner().y << "," + << elementCube.getCorner().z << ":" + << elementCube.getScale(); qDebug() << "elementCube.getScale()=" << elementCube.getScale(); //containingElement->printDebugDetails("containingElement"); qDebug() << "elementIsBestFit=" << elementIsBestFit;