diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 89d4767012..1c98f4e6f3 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 227bdadb97..f930424569 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -182,3 +182,16 @@ ViewFrustum Camera::toViewFrustum() const { loadViewFrustum(result); return result; } + +QVariantMap Camera::getViewFrustum() { + ViewFrustum frustum; + loadViewFrustum(frustum); + + QVariantMap result; + result["position"].setValue(frustum.getPosition()); + result["orientation"].setValue(frustum.getOrientation()); + result["projection"].setValue(frustum.getProjection()); + result["centerRadius"].setValue(frustum.getCenterRadius()); + + return result; +} diff --git a/interface/src/Camera.h b/interface/src/Camera.h index 46cad2efc8..792dcb4a40 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -42,6 +42,8 @@ class Camera : public QObject { Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) Q_PROPERTY(QString mode READ getModeString WRITE setModeString) Q_PROPERTY(QUuid cameraEntity READ getCameraEntity WRITE setCameraEntity) + Q_PROPERTY(QVariantMap frustum READ getViewFrustum CONSTANT) + public: Camera(); @@ -63,6 +65,8 @@ public: const glm::mat4& getProjection() const { return _projection; } void setProjection(const glm::mat4& projection); + QVariantMap getViewFrustum(); + public slots: QString getModeString() const; void setModeString(const QString& mode); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 0e40278824..588c6e2977 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -590,11 +590,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool renderInfo.setProperty("texturesSize", (int)getRenderInfoTextureSize()); // FIXME - theoretically the size of textures could be > max int renderInfo.setProperty("hasTransparent", getRenderInfoHasTransparent()); renderInfo.setProperty("drawCalls", getRenderInfoDrawCalls()); + renderInfo.setProperty("texturesCount", getRenderInfoTextureCount()); } - if (_type == EntityTypes::Model || _type == EntityTypes::PolyLine || _type == EntityTypes::ParticleEffect) { - renderInfo.setProperty("texturesCount", QScriptValue(_textureNames.count())); - } COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(renderInfo, renderInfo); // Gettable but not settable } @@ -771,7 +769,7 @@ void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags) { return EntityItemProperties::entityPropertyFlagsToScriptValue(engine, flags); QScriptValue result = engine->newObject(); - return result; + return result; } void EntityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags) { diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 12b9e2fa79..fe7fccaece 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -21,6 +21,7 @@ #include "EntityTree.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" +#include "QVariantGLM.h" #include "SimulationOwner.h" #include "ZoneEntityItem.h" @@ -566,6 +567,48 @@ QVector EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corn return result; } +QVector EntityScriptingInterface::findEntitiesInFrustum(QVariantMap frustum) const { + QVector result; + + const QString POSITION_PROPERTY = "position"; + bool positionOK = frustum.contains(POSITION_PROPERTY); + glm::vec3 position = positionOK ? qMapToGlmVec3(frustum[POSITION_PROPERTY]) : glm::vec3(); + + const QString ORIENTATION_PROPERTY = "orientation"; + bool orientationOK = frustum.contains(ORIENTATION_PROPERTY); + glm::quat orientation = orientationOK ? qMapToGlmQuat(frustum[ORIENTATION_PROPERTY]) : glm::quat(); + + const QString PROJECTION_PROPERTY = "projection"; + bool projectionOK = frustum.contains(PROJECTION_PROPERTY); + glm::mat4 projection = projectionOK ? qMapToGlmMat4(frustum[PROJECTION_PROPERTY]) : glm::mat4(); + + const QString CENTER_RADIUS_PROPERTY = "centerRadius"; + bool centerRadiusOK = frustum.contains(CENTER_RADIUS_PROPERTY); + float centerRadius = centerRadiusOK ? frustum[CENTER_RADIUS_PROPERTY].toFloat() : 0.0f; + + if (positionOK && orientationOK && projectionOK && centerRadiusOK) { + ViewFrustum viewFrustum; + viewFrustum.setPosition(position); + viewFrustum.setOrientation(orientation); + viewFrustum.setProjection(projection); + viewFrustum.setCenterRadius(centerRadius); + viewFrustum.calculate(); + + if (_entityTree) { + QVector entities; + _entityTree->withReadLock([&] { + _entityTree->findEntities(viewFrustum, entities); + }); + + foreach(EntityItemPointer entity, entities) { + result << entity->getEntityItemID(); + } + } + } + + return result; +} + RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index cb69cebe6b..d5934b1a8d 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -127,10 +127,19 @@ public slots: /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntities(const glm::vec3& center, float radius) const; - /// finds models within the search sphere specified by the center point and radius + /// finds models within the box specified by the corner and dimensions /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const; + /// finds models within the frustum + /// the frustum must have the following properties: + /// - position + /// - orientation + /// - projection + /// - centerRadius + /// this function will not find any models in script engine contexts which don't have access to models + Q_INVOKABLE QVector findEntitiesInFrustum(QVariantMap frustum) const; + /// If the scripting context has visible entities, this will determine a ray intersection, the results /// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate /// will be false. diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 4cdebc364c..89f469037e 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -688,6 +688,31 @@ void EntityTree::findEntities(const AABox& box, QVector& foun foundEntities.swap(args._foundEntities); } +class FindInFrustumArgs { +public: + ViewFrustum frustum; + QVector entities; +}; + +bool EntityTree::findInFrustumOperation(OctreeElementPointer element, void* extraData) { + FindInFrustumArgs* args = static_cast(extraData); + if (element->isInView(args->frustum)) { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + entityTreeElement->getEntities(args->frustum, args->entities); + return true; + } + return false; +} + +// NOTE: assumes caller has handled locking +void EntityTree::findEntities(const ViewFrustum& frustum, QVector& foundEntities) { + FindInFrustumArgs args = { frustum, QVector() }; + // NOTE: This should use recursion, since this is a spatial operation + recurseTreeWithOperation(findInFrustumOperation, &args); + // swap the two lists of entity pointers instead of copy + foundEntities.swap(args.entities); +} + EntityItemPointer EntityTree::findEntityByID(const QUuid& id) { EntityItemID entityID(id); return findEntityByEntityItemID(entityID); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 7dc999aac2..917b9333a5 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -153,6 +153,11 @@ public: /// \remark Side effect: any initial contents in entities will be lost void findEntities(const AABox& box, QVector& foundEntities); + /// finds all entities within a frustum + /// \parameter frustum the query frustum + /// \param foundEntities[out] vector of EntityItemPointer + void findEntities(const ViewFrustum& frustum, QVector& foundEntities); + void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); @@ -276,6 +281,7 @@ protected: static bool findInSphereOperation(OctreeElementPointer element, void* extraData); static bool findInCubeOperation(OctreeElementPointer element, void* extraData); static bool findInBoxOperation(OctreeElementPointer element, void* extraData); + static bool findInFrustumOperation(OctreeElementPointer element, void* extraData); static bool sendEntitiesOperation(OctreeElementPointer element, void* extraData); void notifyNewlyCreatedEntity(const EntityItem& newEntity, const SharedNodePointer& senderNode); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 657e0b286b..29274d2e72 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -796,6 +796,17 @@ void EntityTreeElement::getEntities(const AABox& box, QVector }); } +void EntityTreeElement::getEntities(const ViewFrustum& frustum, QVector& foundEntities) { + forEachEntity([&](EntityItemPointer entity) { + bool success; + AABox entityBox = entity->getAABox(success); + // FIXME - See FIXMEs for similar methods above. + if (!success || frustum.boxIntersectsFrustum(entityBox) || frustum.boxIntersectsKeyhole(entityBox)) { + foundEntities.push_back(entity); + } + }); +} + EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) const { EntityItemPointer foundEntity = NULL; withReadLock([&] { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 4875e258da..d92dfa52dc 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -194,6 +194,11 @@ public: /// \param entities[out] vector of non-const EntityItemPointer void getEntities(const AABox& box, QVector& foundEntities); + /// finds all entities that touch a frustum + /// \param frustum the query frustum + /// \param entities[out] vector of non-const EntityItemPointer + void getEntities(const ViewFrustum& frustum, QVector& foundEntities); + EntityItemPointer getEntityWithID(uint32_t id) const; EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; void getEntitiesInside(const AACube& box, QVector& foundEntities); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 3e891bffe2..50c0c869ff 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -77,6 +77,7 @@ void MeshPartPayload::updateMaterial(model::MaterialPointer drawMaterial) { bool MeshPartPayload::calculateMaterialSize() { bool allTextures = true; // assume we got this... _materialTextureSize = 0; + _materialTextureCount = 0; auto textureMaps = _drawMaterial->getTextureMaps(); for (auto const &textureMapItem : textureMaps) { auto textureMap = textureMapItem.second; @@ -88,6 +89,7 @@ bool MeshPartPayload::calculateMaterialSize() { //auto storedSize = texture->getStoredSize(); auto size = texture->getSize(); _materialTextureSize += size; + _materialTextureCount++; } else { allTextures = false; } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index f7ea77beba..3ecd8da03e 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -67,10 +67,12 @@ public: size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; } size_t getMaterialTextureSize() { return _materialTextureSize; } + int getMaterialTextureCount() { return _materialTextureCount; } bool calculateMaterialSize(); protected: size_t _materialTextureSize { 0 }; + int _materialTextureCount { 0 }; }; namespace render { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index ebf5cb4327..f6caa7c3d3 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -161,22 +161,33 @@ void Model::setOffset(const glm::vec3& offset) { _snappedToRegistrationPoint = false; } -size_t Model::getRenderInfoTextureSize() { - if (!_hasCalculatedTextureSize && isLoaded() && getGeometry()->areTexturesLoaded()) { +void Model::calculateTextureInfo() { + if (!_hasCalculatedTextureInfo && isLoaded() && getGeometry()->areTexturesLoaded() && !_modelMeshRenderItems.isEmpty()) { size_t textureSize = 0; + int textureCount = 0; bool allTexturesLoaded = true; foreach(auto renderItem, _modelMeshRenderItemsSet) { auto meshPart = renderItem.get(); bool allTexturesForThisMesh = meshPart->calculateMaterialSize(); allTexturesLoaded = allTexturesLoaded & allTexturesForThisMesh; textureSize += meshPart->getMaterialTextureSize(); + textureCount += meshPart->getMaterialTextureCount(); } _renderInfoTextureSize = textureSize; - _hasCalculatedTextureSize = allTexturesLoaded; // only do this once + _renderInfoTextureCount = textureCount; + _hasCalculatedTextureInfo = allTexturesLoaded; // only do this once } +} + +size_t Model::getRenderInfoTextureSize() { + calculateTextureInfo(); return _renderInfoTextureSize; } +int Model::getRenderInfoTextureCount() { + calculateTextureInfo(); + return _renderInfoTextureCount; +} void Model::updateRenderItems() { if (!_addedToScene) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 1b16892296..bd94fb706b 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -233,8 +233,8 @@ public: void setLoadingPriority(float priority) { _loadingPriority = priority; } size_t getRenderInfoVertexCount() const { return _renderInfoVertexCount; } - int getRenderInfoTextureCount() const { return _renderInfoTextureCount; } size_t getRenderInfoTextureSize(); + int getRenderInfoTextureCount(); int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; } bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; } @@ -409,13 +409,14 @@ protected: size_t _renderInfoVertexCount { 0 }; int _renderInfoTextureCount { 0 }; size_t _renderInfoTextureSize { 0 }; - bool _hasCalculatedTextureSize { false }; + bool _hasCalculatedTextureInfo { false }; int _renderInfoDrawCalls { 0 }; int _renderInfoHasTransparent { false }; private: float _loadingPriority { 0.0f }; + void calculateTextureInfo(); }; Q_DECLARE_METATYPE(ModelPointer) diff --git a/libraries/shared/src/QVariantGLM.cpp b/libraries/shared/src/QVariantGLM.cpp index 7a3ab92cca..b5b840c2ca 100644 --- a/libraries/shared/src/QVariantGLM.cpp +++ b/libraries/shared/src/QVariantGLM.cpp @@ -62,3 +62,48 @@ void qListtoRgbColor(const QVariant& q, rgbColor& returnValue) { returnValue[GREEN_INDEX] = qList[GREEN_INDEX].toInt(); returnValue[BLUE_INDEX] = qList[BLUE_INDEX].toInt(); } + + +glm::vec3 qMapToGlmVec3(const QVariant& q) { + QVariantMap qMap = q.toMap(); + if (qMap.contains("x") && qMap.contains("y") && qMap.contains("y")) { + return glm::vec3( + qMap["x"].toFloat(), + qMap["y"].toFloat(), + qMap["z"].toFloat() + ); + } else { + return glm::vec3(); + } +} + +glm::quat qMapToGlmQuat(const QVariant& q) { + QVariantMap qMap = q.toMap(); + if (qMap.contains("w") && qMap.contains("x") && qMap.contains("y") && qMap.contains("z")) { + return glm::quat( + qMap["w"].toFloat(), + qMap["x"].toFloat(), + qMap["y"].toFloat(), + qMap["z"].toFloat() + ); + } else { + return glm::quat(); + } +} + +glm::mat4 qMapToGlmMat4(const QVariant& q) { + QVariantMap qMap = q.toMap(); + if (qMap.contains("r0c0") && qMap.contains("r1c0") && qMap.contains("r2c0") && qMap.contains("r3c0") + && qMap.contains("r0c1") && qMap.contains("r1c1") && qMap.contains("r2c1") && qMap.contains("r3c1") + && qMap.contains("r0c2") && qMap.contains("r1c2") && qMap.contains("r2c2") && qMap.contains("r3c2") + && qMap.contains("r0c3") && qMap.contains("r1c3") && qMap.contains("r2c3") && qMap.contains("r3c3")) { + return glm::mat4( + qMap["r0c0"].toFloat(), qMap["r1c0"].toFloat(), qMap["r2c0"].toFloat(), qMap["r3c0"].toFloat(), + qMap["r0c1"].toFloat(), qMap["r1c1"].toFloat(), qMap["r2c1"].toFloat(), qMap["r3c1"].toFloat(), + qMap["r0c2"].toFloat(), qMap["r1c2"].toFloat(), qMap["r2c2"].toFloat(), qMap["r3c2"].toFloat(), + qMap["r0c3"].toFloat(), qMap["r1c3"].toFloat(), qMap["r2c3"].toFloat(), qMap["r3c3"].toFloat() + ); + } else { + return glm::mat4(); + } +} diff --git a/libraries/shared/src/QVariantGLM.h b/libraries/shared/src/QVariantGLM.h index 3a91110250..314889d5dd 100644 --- a/libraries/shared/src/QVariantGLM.h +++ b/libraries/shared/src/QVariantGLM.h @@ -27,3 +27,7 @@ QVariantMap glmToQMap(const glm::quat& glmQuat); glm::vec3 qListToGlmVec3(const QVariant& q); glm::quat qListToGlmQuat(const QVariant& q); void qListtoRgbColor(const QVariant& q, rgbColor& returnValue); + +glm::vec3 qMapToGlmVec3(const QVariant& q); +glm::quat qMapToGlmQuat(const QVariant& q); +glm::mat4 qMapToGlmMat4(const QVariant& q); diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 8a6c7786bf..a5807ff025 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -366,6 +366,10 @@ input[type=button]:disabled { background: linear-gradient(#575757 20%, #252525 100%); } +input[type=button][pressed=pressed] { + color: #00b4ef; +} + input[type=checkbox] { display: none; } @@ -861,13 +865,8 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { position: relative; /* New positioning context. */ } -#entity-list .glyph { - font-family: HiFi-Glyphs; - font-size: 14px; -} - #search-area { - padding-right: 148px; + padding-right: 168px; padding-bottom: 24px; } @@ -875,13 +874,23 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { width: 98%; } +#in-view { + position: absolute; + right: 126px; +} + #radius-and-unit { float: right; - margin-right: -148px; + margin-right: -168px; position: relative; top: -17px; } - +#radius-and-unit label { + margin-left: 2px; +} +#radius-and-unit input { + width: 120px; +} #entity-table-scroll { /* Height is set by JavaScript. */ @@ -896,6 +905,10 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { background-color: #1c1c1c; } +#entity-table-scroll .glyph { + font-family: HiFi-Glyphs; + font-size: 15px; +} #entity-table { margin-top: -28px; @@ -905,19 +918,6 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { background-color: #1c1c1c; } -#col-type { - width: 16%; -} -#col-name { - width: 34%; -} -#col-url { - width: 34%; -} -#col-locked, #col-visible { - width: 8%; -} - #entity-table thead tr, #entity-table thead tr th, #entity-table tfoot tr, #entity-table tfoot tr td { background: none; @@ -938,12 +938,22 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { top: 49px; left: 0; width: 100%; + word-wrap: nowrap; + white-space: nowrap; + overflow: hidden; } -#entity-table thead th { +.verticesCount, .texturesCount, .texturesSize, .drawCalls { + text-align: right; +} + +#entity-table th { + display: inline-block; box-sizing: border-box; - padding: 0 0 0 8px; + padding: 5px 0 0 0; vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; } #entity-table th:focus { @@ -952,17 +962,54 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { #entity-table th .glyph { position: relative; - left: 0; + left: 4px; +} +#entity-table th .glyph + .sort-order { + position: relative; + left: 4px; +} + +#entity-table th#entity-hasScript { + overflow: visible; +} + +#entity-table th#entity-hasScript .glyph { + text-transform: none; } #entity-table thead .sort-order { display: inline-block; width: 8px; margin: -5px 0 -3px 0; - text-align: right; vertical-align: middle; } +#entity-table th #info-toggle { + display: inline-block; + position: absolute; + left: initial; + right: 0; + width: 11px; + background-color: #1c1c1c; + z-index: 100; +} +#entity-table th #info-toggle span { + position: relative; + left: -2px; +} + +th#entity-hasTransparent .glyph { + font-weight: normal; + font-size: 24px !important; + margin: -6px; + position: relative; + top: -6px; +} +th#entity-hasTransparent .sort-order { + position: relative; + top: -4px; +} + #entity-table td { box-sizing: border-box; } @@ -971,6 +1018,11 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { text-align: center; padding: 0; } +#entity-table td.hasTransparent.glyph { + font-size: 22px; + position: relative; + top: -1px; +} #entity-table tfoot { box-sizing: border-box; @@ -984,6 +1036,82 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { width: 100%; } + +#col-type { + width: 16%; +} +#col-name { + width: 34%; +} +#col-url { + width: 34%; +} +#col-locked, #col-visible { + width: 9%; +} +#col-verticesCount, #col-texturesCount, #col-texturesSize, #col-hasTransparent, #col-drawCalls, #col-hasScript { + width: 0; +} + +.showExtraInfo #col-type { + width: 10%; +} +.showExtraInfo #col-name { + width: 19%; +} +.showExtraInfo #col-url { + width: 19%; +} +.showExtraInfo #col-locked, .showExtraInfo #col-visible { + width: 4%; +} +.showExtraInfo #col-verticesCount { + width: 8%; +} +.showExtraInfo #col-texturesCount { + width: 8%; +} +.showExtraInfo #col-texturesSize { + width: 10%; +} +.showExtraInfo #col-hasTransparent { + width: 4%; +} +.showExtraInfo #col-drawCalls { + width: 8%; +} +.showExtraInfo #col-hasScript { + width: 6%; +} + +th#entity-verticesCount, th#entity-texturesCount, th#entity-texturesSize, th#entity-hasTransparent, th#entity-drawCalls, +th#entity-hasScript { + display: none; +} + +.verticesCount, .texturesCount, .texturesSize, .hasTransparent, .drawCalls, .hasScript { + display: none; +} + +#entity-visible { + border: none; +} + +.showExtraInfo #entity-verticesCount, .showExtraInfo #entity-texturesCount, .showExtraInfo #entity-texturesSize, +.showExtraInfo #entity-hasTransparent, .showExtraInfo #entity-drawCalls, .showExtraInfo #entity-hasScript { + display: inline-block; +} + +.showExtraInfo .verticesCount, .showExtraInfo .texturesCount, .showExtraInfo .texturesSize, .showExtraInfo .hasTransparent, +.showExtraInfo .drawCalls, .showExtraInfo .hasScript { + display: table-cell; +} + +.showExtraInfo #entity-visible { + border-right: 1px solid #575757; +} + + #no-entities { display: none; position: absolute; @@ -1102,4 +1230,4 @@ input#reset-to-natural-dimensions { margin-top:5px; font-size:16px; display:none; -} \ No newline at end of file +} diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index b19a55b5a2..58dca4567f 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -29,6 +29,7 @@
Y +
@@ -37,39 +38,57 @@
- - - - - + + + + + + + + + + + - - - - - + + + + + + + + + + + - - - - - - + + + + + + + + + + + + - - - + + +
TypeNameFileTypeDNameFileVertsTextsText MBDrawsk
TypeName
URL
??
- No entities found within a 100 meter radius. Try moving to a different location and refreshing. + No entities found in view within a 100 meter radius. Try moving to a different location and refreshing.
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 8e5e190068..e9075da3eb 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -12,10 +12,12 @@ var currentSortColumn = 'type'; var currentSortOrder = 'des'; var entityList = null; var refreshEntityListTimer = null; -const ASCENDING_STRING = '▾'; -const DESCENDING_STRING = '▴'; +const ASCENDING_STRING = '▴'; +const DESCENDING_STRING = '▾'; const LOCKED_GLYPH = ""; const VISIBLE_GLYPH = ""; +const TRANSPARENCY_GLYPH = ""; +const SCRIPT_GLYPH = "k"; const DELETE = 46; // Key code for the delete key. const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities. @@ -33,10 +35,16 @@ function loaded() { elToggleLocked = document.getElementById("locked"); elToggleVisible = document.getElementById("visible"); elDelete = document.getElementById("delete"); - elTeleport = document.getElementById("teleport"); + elFilter = document.getElementById("filter"); + elInView = document.getElementById("in-view") elRadius = document.getElementById("radius"); + elTeleport = document.getElementById("teleport"); + elEntityTable = document.getElementById("entity-table"); + elInfoToggle = document.getElementById("info-toggle"); + elInfoToggleGlyph = elInfoToggle.firstChild; elFooter = document.getElementById("footer-text"); elNoEntitiesMessage = document.getElementById("no-entities"); + elNoEntitiesInView = document.getElementById("no-entities-in-view"); elNoEntitiesRadius = document.getElementById("no-entities-radius"); elEntityTableScroll = document.getElementById("entity-table-scroll"); @@ -55,7 +63,25 @@ function loaded() { document.getElementById("entity-visible").onclick = function () { setSortColumn('visible'); }; - + document.getElementById("entity-verticesCount").onclick = function () { + setSortColumn('verticesCount'); + }; + document.getElementById("entity-texturesCount").onclick = function () { + setSortColumn('texturesCount'); + }; + document.getElementById("entity-texturesSize").onclick = function () { + setSortColumn('texturesSize'); + }; + document.getElementById("entity-hasTransparent").onclick = function () { + setSortColumn('hasTransparent'); + }; + document.getElementById("entity-drawCalls").onclick = function () { + setSortColumn('drawCalls'); + }; + document.getElementById("entity-hasScript").onclick = function () { + setSortColumn('hasScript'); + }; + function onRowClicked(clickEvent) { var id = this.dataset.entityId; var selection = [this.dataset.entityId]; @@ -106,12 +132,29 @@ function loaded() { })); } - function addEntity(id, name, type, url, locked, visible) { + const BYTES_PER_MEGABYTE = 1024 * 1024; + + function decimalMegabytes(number) { + return number ? (number / BYTES_PER_MEGABYTE).toFixed(1) : ""; + } + + function displayIfNonZero(number) { + return number ? number : ""; + } + + function addEntity(id, name, type, url, locked, visible, verticesCount, texturesCount, texturesSize, hasTransparent, + drawCalls, hasScript) { + var urlParts = url.split('/'); var filename = urlParts[urlParts.length - 1]; if (entities[id] === undefined) { - entityList.add([{ id: id, name: name, type: type, url: filename, locked: locked, visible: visible }], + entityList.add([{ + id: id, name: name, type: type, url: filename, locked: locked, visible: visible, + verticesCount: displayIfNonZero(verticesCount), texturesCount: displayIfNonZero(texturesCount), + texturesSize: decimalMegabytes(texturesSize), hasTransparent: hasTransparent, + drawCalls: displayIfNonZero(drawCalls), hasScript: hasScript + }], function (items) { var currentElement = items[0].elm; var id = items[0]._values.id; @@ -148,7 +191,13 @@ function loaded() { type: document.querySelector('#entity-type .sort-order'), url: document.querySelector('#entity-url .sort-order'), locked: document.querySelector('#entity-locked .sort-order'), - visible: document.querySelector('#entity-visible .sort-order') + visible: document.querySelector('#entity-visible .sort-order'), + verticesCount: document.querySelector('#entity-verticesCount .sort-order'), + texturesCount: document.querySelector('#entity-texturesCount .sort-order'), + texturesSize: document.querySelector('#entity-texturesSize .sort-order'), + hasTransparent: document.querySelector('#entity-hasTransparent .sort-order'), + drawCalls: document.querySelector('#entity-drawCalls .sort-order'), + hasScript: document.querySelector('#entity-hasScript .sort-order'), } function setSortColumn(column) { if (currentSortColumn == column) { @@ -168,10 +217,23 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' })); } + function refreshFooter() { + if (selectedEntities.length > 1) { + elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; + } else if (selectedEntities.length === 1) { + elFooter.firstChild.nodeValue = "1 entity selected"; + } else if (entityList.visibleItems.length === 1) { + elFooter.firstChild.nodeValue = "1 entity found"; + } else { + elFooter.firstChild.nodeValue = entityList.visibleItems.length + " entities found"; + } + } + function refreshEntityListObject() { refreshEntityListTimer = null; entityList.sort(currentSortColumn, { order: currentSortOrder }); - entityList.search(document.getElementById("filter").value); + entityList.search(elFilter.value); + refreshFooter(); } function updateSelectedEntities(selectedEntities) { @@ -189,16 +251,6 @@ function loaded() { } } - if (selectedEntities.length > 1) { - elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; - } else if (selectedEntities.length === 1) { - elFooter.firstChild.nodeValue = "1 entity selected"; - } else if (entityList.visibleItems.length === 1) { - elFooter.firstChild.nodeValue = "1 entity found"; - } else { - elFooter.firstChild.nodeValue = entityList.visibleItems.length + " entities found"; - } - // HACK: Fixes the footer and header text sometimes not displaying after adding or deleting entities. // The problem appears to be a bug in the Qt HTML/CSS rendering (Qt 5.5). document.getElementById("radius").focus(); @@ -235,13 +287,29 @@ function loaded() { } }, false); + var isFilterInView = false; + var FILTER_IN_VIEW_ATTRIBUTE = "pressed"; + elNoEntitiesInView.style.display = "none"; + elInView.onclick = function () { + isFilterInView = !isFilterInView; + if (isFilterInView) { + elInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); + elNoEntitiesInView.style.display = "inline"; + } else { + elInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); + elNoEntitiesInView.style.display = "none"; + } + EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView })); + refreshEntities(); + } + elRadius.onchange = function () { elRadius.value = Math.max(elRadius.value, 0); EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value })); refreshEntities(); elNoEntitiesRadius.firstChild.nodeValue = elRadius.value; } - + if (window.EventBridge !== undefined) { EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); @@ -264,7 +332,11 @@ function loaded() { var id = newEntities[i].id; addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url, newEntities[i].locked ? LOCKED_GLYPH : null, - newEntities[i].visible ? VISIBLE_GLYPH : null); + newEntities[i].visible ? VISIBLE_GLYPH : null, + newEntities[i].verticesCount, newEntities[i].texturesCount, newEntities[i].texturesSize, + newEntities[i].hasTransparent ? TRANSPARENCY_GLYPH : null, + newEntities[i].drawCalls, + newEntities[i].hasScript ? SCRIPT_GLYPH : null); } updateSelectedEntities(data.selectedIDs); resize(); @@ -278,6 +350,7 @@ function loaded() { // Take up available window space elEntityTableScroll.style.height = window.innerHeight - 207; + var SCROLLABAR_WIDTH = 21; var tds = document.querySelectorAll("#entity-table-body tr:first-child td"); var ths = document.querySelectorAll("#entity-table thead th"); if (tds.length >= ths.length) { @@ -287,16 +360,53 @@ function loaded() { } } else { // Reasonable widths if nothing is displayed - var tableWidth = document.getElementById("entity-table").offsetWidth; - ths[0].width = 0.16 * tableWidth; - ths[1].width = 0.34 * tableWidth; - ths[2].width = 0.34 * tableWidth; - ths[3].width = 0.08 * tableWidth; - ths[4].width = 0.08 * tableWidth; + var tableWidth = document.getElementById("entity-table").offsetWidth - SCROLLABAR_WIDTH; + if (showExtraInfo) { + ths[0].width = 0.10 * tableWidth; + ths[1].width = 0.20 * tableWidth; + ths[2].width = 0.20 * tableWidth; + ths[3].width = 0.04 * tableWidth; + ths[4].width = 0.04 * tableWidth; + ths[5].width = 0.08 * tableWidth; + ths[6].width = 0.08 * tableWidth; + ths[7].width = 0.10 * tableWidth; + ths[8].width = 0.04 * tableWidth; + ths[9].width = 0.08 * tableWidth; + ths[10].width = 0.04 * tableWidth + SCROLLABAR_WIDTH; + } else { + ths[0].width = 0.16 * tableWidth; + ths[1].width = 0.34 * tableWidth; + ths[2].width = 0.34 * tableWidth; + ths[3].width = 0.08 * tableWidth; + ths[4].width = 0.08 * tableWidth; + } } }; window.onresize = resize; + elFilter.onchange = resize; + elFilter.onblur = refreshFooter; + + + var showExtraInfo = false; + var COLLAPSE_EXTRA_INFO = "E"; + var EXPAND_EXTRA_INFO = "D"; + + function toggleInfo(event) { + showExtraInfo = !showExtraInfo; + if (showExtraInfo) { + elEntityTable.className = "showExtraInfo"; + elInfoToggleGlyph.innerHTML = COLLAPSE_EXTRA_INFO; + } else { + elEntityTable.className = ""; + elInfoToggleGlyph.innerHTML = EXPAND_EXTRA_INFO; + } + resize(); + event.stopPropagation(); + } + elInfoToggle.addEventListener("click", toggleInfo, true); + + resize(); }); diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 448293a2ac..3e9d3b5648 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -9,7 +9,7 @@ EntityListTool = function(opts) { }); - + var filterInView = false; var searchRadius = 100; var visible = false; @@ -48,20 +48,41 @@ EntityListTool = function(opts) { webView.emitScriptEvent(JSON.stringify(data)); }; + function valueIfDefined(value) { + return value !== undefined ? value : ""; + } + that.sendUpdate = function() { var entities = []; - var ids = Entities.findEntities(MyAvatar.position, searchRadius); + + var ids; + if (filterInView) { + ids = Entities.findEntitiesInFrustum(Camera.frustum); + } else { + ids = Entities.findEntities(MyAvatar.position, searchRadius); + } + + var cameraPosition = Camera.position; for (var i = 0; i < ids.length; i++) { var id = ids[i]; var properties = Entities.getEntityProperties(id); - entities.push({ - id: id, - name: properties.name, - type: properties.type, - url: properties.type == "Model" ? properties.modelURL : "", - locked: properties.locked, - visible: properties.visible - }); + + if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { + entities.push({ + id: id, + name: properties.name, + type: properties.type, + url: properties.type == "Model" ? properties.modelURL : "", + locked: properties.locked, + visible: properties.visible, + verticesCount: valueIfDefined(properties.renderInfo.verticesCount), + texturesCount: valueIfDefined(properties.renderInfo.texturesCount), + texturesSize: valueIfDefined(properties.renderInfo.texturesSize), + hasTransparent: valueIfDefined(properties.renderInfo.hasTransparent), + drawCalls: valueIfDefined(properties.renderInfo.drawCalls), + hasScript: properties.script !== "" + }); + } } var selectedIDs = []; @@ -105,9 +126,10 @@ EntityListTool = function(opts) { toggleSelectedEntitiesLocked(); } else if (data.type == "toggleVisible") { toggleSelectedEntitiesVisible(); + } else if (data.type === "filterInView") { + filterInView = data.filterInView === true; } else if (data.type === "radius") { searchRadius = data.radius; - that.sendUpdate(); } });