diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index c6336a3a4d..6cbfa76414 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -21,12 +21,7 @@ #include "AssetUtils.h" #include "ReceivedMessage.h" -namespace std { - template <> - struct hash { - size_t operator()(const QString& v) const { return qHash(v); } - }; -} +#include "RegisteredMetaTypes.h" struct AssetMeta { int bakeVersion { 0 }; diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index c1e275e4d3..f67be55b92 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -22,6 +22,8 @@ setup_memory_debugger() symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources" "resources") # link the shared hifi libraries +include_hifi_library_headers(gpu) +include_hifi_library_headers(graphics) link_hifi_libraries(embedded-webserver networking shared avatars) # find OpenSSL diff --git a/interface/resources/icons/create-icons/126-material-01.svg b/interface/resources/icons/create-icons/126-material-01.svg new file mode 100644 index 0000000000..9b6d92505f --- /dev/null +++ b/interface/resources/icons/create-icons/126-material-01.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index 899d604878..c79c05a601 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -144,6 +144,17 @@ TabView { editTabView.currentIndex = 4 } } + + NewEntityButton { + icon: "icons/create-icons/126-material-01.svg" + text: "MATERIAL" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newMaterialButton" } + }); + editTabView.currentIndex = 2 + } + } } HifiControls.Button { diff --git a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml new file mode 100644 index 0000000000..226c17e8e2 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml @@ -0,0 +1,174 @@ +// +// NewMaterialDialog.qml +// qml/hifi +// +// Created by Sam Gondelman on 1/17/18 +// Copyright 2018 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 as OriginalDialogs + +import "../../styles-uit" +import "../../controls-uit" +import "../dialogs" + +Rectangle { + id: newMaterialDialog + // width: parent.width + // height: parent.height + HifiConstants { id: hifi } + color: hifi.colors.baseGray; + signal sendToScript(var message); + property bool keyboardEnabled: false + property bool punctuationMode: false + property bool keyboardRasied: false + + function errorMessageBox(message) { + return desktop.messageBox({ + icon: hifi.icons.warning, + defaultButton: OriginalDialogs.StandardButton.Ok, + title: "Error", + text: message + }); + } + + Item { + id: column1 + anchors.rightMargin: 10 + anchors.leftMargin: 10 + anchors.bottomMargin: 10 + anchors.topMargin: 10 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: keyboard.top + + Text { + id: text1 + text: qsTr("Material URL") + color: "#ffffff" + font.pixelSize: 12 + } + + TextInput { + id: materialURL + height: 20 + text: qsTr("") + color: "white" + anchors.top: text1.bottom + anchors.topMargin: 5 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + font.pixelSize: 12 + + onAccepted: { + newMaterialDialog.keyboardEnabled = false; + } + + MouseArea { + anchors.fill: parent + onClicked: { + newMaterialDialog.keyboardEnabled = HMD.active + parent.focus = true; + parent.forceActiveFocus(); + materialURL.cursorPosition = materialURL.positionAt(mouseX, mouseY, TextInput.CursorBetweenCharaters); + } + } + } + + Rectangle { + id: textInputBox + color: "white" + anchors.fill: materialURL + opacity: 0.1 + } + + Row { + id: row1 + height: 400 + spacing: 30 + anchors.top: materialURL.bottom + anchors.topMargin: 5 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + Column { + id: column3 + height: 400 + spacing: 10 + + /*Text { + id: text3 + text: qsTr("Material Mode") + color: "#ffffff" + font.pixelSize: 12 + } + + ComboBox { + id: materialMappingMode + property var materialArray: ["UV space material", + "3D projected material"] + + width: 200 + z: 100 + transformOrigin: Item.Center + model: materialArray + }*/ + + Row { + id: row3 + width: 200 + height: 400 + spacing: 5 + + anchors.horizontalCenter: column3.horizontalCenter + anchors.horizontalCenterOffset: -20 + + Button { + id: button1 + text: qsTr("Add") + z: -1 + onClicked: { + newMaterialDialog.sendToScript({ + method: "newMaterialDialogAdd", + params: { + textInput: materialURL.text, + //comboBox: materialMappingMode.currentIndex + } + }); + } + } + + Button { + id: button2 + z: -1 + text: qsTr("Cancel") + onClicked: { + newMaterialDialog.sendToScript({method: "newMaterialDialogCancel"}) + } + } + } + } + } + } + + Keyboard { + id: keyboard + raised: parent.keyboardEnabled + numeric: parent.punctuationMode + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5a340f471e..3aa0f3d889 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1593,6 +1593,73 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } }); + EntityTree::setAddMaterialToEntityOperator([&](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + // try to find the renderable + auto renderable = getEntities()->renderableForEntityId(entityID); + if (renderable) { + renderable->addMaterial(material, parentMaterialName); + } + + // even if we don't find it, try to find the entity + auto entity = getEntities()->getEntity(entityID); + if (entity) { + entity->addMaterial(material, parentMaterialName); + return true; + } + return false; + }); + EntityTree::setRemoveMaterialFromEntityOperator([&](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + // try to find the renderable + auto renderable = getEntities()->renderableForEntityId(entityID); + if (renderable) { + renderable->removeMaterial(material, parentMaterialName); + } + + // even if we don't find it, try to find the entity + auto entity = getEntities()->getEntity(entityID); + if (entity) { + entity->removeMaterial(material, parentMaterialName); + return true; + } + return false; + }); + + EntityTree::setAddMaterialToAvatarOperator([](const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + auto avatarManager = DependencyManager::get(); + auto avatar = avatarManager->getAvatarBySessionID(avatarID); + if (avatar) { + avatar->addMaterial(material, parentMaterialName); + return true; + } + return false; + }); + EntityTree::setRemoveMaterialFromAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + auto avatarManager = DependencyManager::get(); + auto avatar = avatarManager->getAvatarBySessionID(avatarID); + if (avatar) { + avatar->removeMaterial(material, parentMaterialName); + return true; + } + return false; + }); + + EntityTree::setAddMaterialToOverlayOperator([&](const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + auto overlay = _overlays.getOverlay(overlayID); + if (overlay) { + overlay->addMaterial(material, parentMaterialName); + return true; + } + return false; + }); + EntityTree::setRemoveMaterialFromOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + auto overlay = _overlays.getOverlay(overlayID); + if (overlay) { + overlay->removeMaterial(material, parentMaterialName); + return true; + } + return false; + }); + // Keyboard focus handling for Web overlays. auto overlays = &(qApp->getOverlays()); connect(overlays, &Overlays::overlayDeleted, [=](const OverlayID& overlayID) { diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 2b2c1403ff..e95f13f8ff 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -83,6 +83,7 @@ void ModelOverlay::update(float deltatime) { auto modelOverlay = static_cast(&data); modelOverlay->setSubRenderItemIDs(newRenderItemIDs); }); + processMaterials(); } if (_visibleDirty) { _visibleDirty = false; @@ -108,6 +109,7 @@ void ModelOverlay::update(float deltatime) { bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { Volume3DOverlay::addToScene(overlay, scene, transaction); _model->addToScene(scene, transaction); + processMaterials(); return true; } @@ -632,3 +634,29 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { } return 0; } + +void ModelOverlay::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { + Overlay::addMaterial(material, parentMaterialName); + if (_model && _model->fetchRenderItemIDs().size() > 0) { + _model->addMaterial(material, parentMaterialName); + } +} + +void ModelOverlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { + Overlay::removeMaterial(material, parentMaterialName); + if (_model && _model->fetchRenderItemIDs().size() > 0) { + _model->removeMaterial(material, parentMaterialName); + } +} + +void ModelOverlay::processMaterials() { + assert(_model); + std::lock_guard lock(_materialsLock); + for (auto& shapeMaterialPair : _materials) { + auto material = shapeMaterialPair.second; + while (!material.empty()) { + _model->addMaterial(material.top(), shapeMaterialPair.first); + material.pop(); + } + } +} \ No newline at end of file diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 08c776c426..b38d5cd6d9 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -59,6 +59,9 @@ public: void setDrawInFront(bool drawInFront) override; void setDrawHUDLayer(bool drawHUDLayer) override; + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; + void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; + protected: Transform evalRenderTransform() override; @@ -110,6 +113,8 @@ private: bool _drawInFrontDirty { false }; bool _drawInHUDDirty { false }; + void processMaterials(); + }; #endif // hifi_ModelOverlay_h diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 3c952b8338..2c0c7c71b6 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -235,3 +235,13 @@ QVector qVectorOverlayIDFromScriptValue(const QScriptValue& array) { } return newVector; } + +void Overlay::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { + std::lock_guard lock(_materialsLock); + _materials[parentMaterialName].push(material); +} + +void Overlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { + std::lock_guard lock(_materialsLock); + _materials[parentMaterialName].remove(material); +} \ No newline at end of file diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index e9271f3c3f..f1be23ed39 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -91,6 +91,9 @@ public: unsigned int getStackOrder() const { return _stackOrder; } void setStackOrder(unsigned int stackOrder) { _stackOrder = stackOrder; } + virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); + virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); + protected: float updatePulse(); @@ -117,6 +120,9 @@ protected: static const xColor DEFAULT_OVERLAY_COLOR; static const float DEFAULT_ALPHA; + std::unordered_map _materials; + std::mutex _materialsLock; + private: OverlayID _overlayID; // only used for non-3d overlays }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index fb73e2a66b..55a40f3ae3 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -570,6 +570,7 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc } transaction.resetItem(_renderItemID, avatarPayloadPointer); _skeletonModel->addToScene(scene, transaction); + processMaterials(); for (auto& attachmentModel : _attachmentModels) { attachmentModel->addToScene(scene, transaction); } @@ -761,6 +762,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { if (_skeletonModel->isRenderable() && _skeletonModel->needsFixupInScene()) { _skeletonModel->removeFromScene(scene, transaction); _skeletonModel->addToScene(scene, transaction); + processMaterials(); canTryFade = true; _isAnimatingScale = true; } @@ -1760,3 +1762,31 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { return DEFAULT_AVATAR_EYE_HEIGHT; } } + +void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { + std::lock_guard lock(_materialsLock); + _materials[parentMaterialName].push(material); + if (_skeletonModel && _skeletonModel->fetchRenderItemIDs().size() > 0) { + _skeletonModel->addMaterial(material, parentMaterialName); + } +} + +void Avatar::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { + std::lock_guard lock(_materialsLock); + _materials[parentMaterialName].remove(material); + if (_skeletonModel && _skeletonModel->fetchRenderItemIDs().size() > 0) { + _skeletonModel->removeMaterial(material, parentMaterialName); + } +} + +void Avatar::processMaterials() { + assert(_skeletonModel); + std::lock_guard lock(_materialsLock); + for (auto& shapeMaterialPair : _materials) { + auto material = shapeMaterialPair.second; + while (!material.empty()) { + _skeletonModel->addMaterial(material.top(), shapeMaterialPair.first); + material.pop(); + } + } +} \ No newline at end of file diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index c2b404a925..b24fbeaef2 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -272,6 +272,9 @@ public: virtual void setAvatarEntityDataChanged(bool value) override; + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; + void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; + public slots: // FIXME - these should be migrated to use Pose data instead @@ -397,6 +400,11 @@ protected: float _displayNameAlpha { 1.0f }; ThreadSafeValueCache _unscaledEyeHeightCache { DEFAULT_AVATAR_EYE_HEIGHT }; + + std::unordered_map _materials; + std::mutex _materialsLock; + + void processMaterials(); }; #endif // hifi_Avatar_h diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index fc6d15cced..13fda28f40 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -1,3 +1,4 @@ set(TARGET_NAME avatars) setup_hifi_library(Network Script) -link_hifi_libraries(shared networking) +include_hifi_library_headers(gpu) +link_hifi_libraries(shared networking graphics) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 74888283df..1bbc8cc1a5 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2558,4 +2558,4 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value[EntityID] = binaryEntityProperties; } -} +} \ No newline at end of file diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index f24bd51bde..033756bfd2 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -54,6 +54,8 @@ #include "HeadData.h" #include "PathUtils.h" +#include + using AvatarSharedPointer = std::shared_ptr; using AvatarWeakPointer = std::weak_ptr; using AvatarHash = QHash; @@ -694,6 +696,9 @@ public: bool getIsReplicated() const { return _isReplicated; } + virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {} + virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {} + signals: void displayNameChanged(); void sessionDisplayNameChanged(); diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index aca2f4d35b..d3c9f3d4bd 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -25,6 +25,7 @@ #include "RenderableTextEntityItem.h" #include "RenderableWebEntityItem.h" #include "RenderableZoneEntityItem.h" +#include "RenderableMaterialEntityItem.h" using namespace render; @@ -145,6 +146,7 @@ EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity _needsRenderUpdate = true; emit requestRenderUpdate(); }); + _materials = entity->getMaterials(); } EntityRenderer::~EntityRenderer() { } @@ -252,6 +254,10 @@ EntityRenderer::Pointer EntityRenderer::addToScene(EntityTreeRenderer& renderer, result = make_renderer(entity); break; + case Type::Material: + result = make_renderer(entity); + break; + default: break; } @@ -395,3 +401,13 @@ void EntityRenderer::onRemoveFromScene(const EntityItemPointer& entity) { entity->deregisterChangeHandler(_changeHandlerId); QObject::disconnect(this, &EntityRenderer::requestRenderUpdate, this, nullptr); } + +void EntityRenderer::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { + std::lock_guard lock(_materialsLock); + _materials[parentMaterialName].push(material); +} + +void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { + std::lock_guard lock(_materialsLock); + _materials[parentMaterialName].remove(material); +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index f8685df5da..ce652a758c 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -54,12 +54,14 @@ public: const uint64_t& getUpdateTime() const { return _updateTime; } + virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); + virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); + protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); virtual void onRemoveFromScene(const EntityItemPointer& entity); -protected: EntityRenderer(const EntityItemPointer& entity); ~EntityRenderer(); @@ -130,6 +132,8 @@ protected: // Only touched on the rendering thread bool _renderUpdateQueued{ false }; + std::unordered_map _materials; + std::mutex _materialsLock; private: // The base class relies on comparing the model transform to the entity transform in order diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp new file mode 100644 index 0000000000..090891fe62 --- /dev/null +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -0,0 +1,269 @@ +// +// Created by Sam Gondelman on 1/18/2018 +// Copyright 2018 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 "RenderableMaterialEntityItem.h" + +#include "RenderPipelines.h" + +using namespace render; +using namespace render::entities; + +bool MaterialEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { + if (entity->getMaterial() != _drawMaterial) { + return true; + } + if (entity->getParentID() != _parentID || entity->getClientOnly() != _clientOnly || entity->getOwningAvatarID() != _owningAvatarID) { + return true; + } + if (entity->getMaterialMappingPos() != _materialMappingPos || entity->getMaterialMappingScale() != _materialMappingScale || entity->getMaterialMappingRot() != _materialMappingRot) { + return true; + } + return false; +} + +void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { + withWriteLock([&] { + _drawMaterial = entity->getMaterial(); + _parentID = entity->getParentID(); + _clientOnly = entity->getClientOnly(); + _owningAvatarID = entity->getOwningAvatarID(); + _materialMappingPos = entity->getMaterialMappingPos(); + _materialMappingScale = entity->getMaterialMappingScale(); + _materialMappingRot = entity->getMaterialMappingRot(); + _renderTransform = getModelTransform(); + const float MATERIAL_ENTITY_SCALE = 0.5f; + _renderTransform.postScale(MATERIAL_ENTITY_SCALE); + _renderTransform.postScale(ENTITY_ITEM_DEFAULT_DIMENSIONS); + }); +} + +ItemKey MaterialEntityRenderer::getKey() { + ItemKey::Builder builder; + builder.withTypeShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + + if (!_visible) { + builder.withInvisible(); + } + + if (_drawMaterial) { + auto matKey = _drawMaterial->getKey(); + if (matKey.isTranslucent()) { + builder.withTransparent(); + } + } + + return builder.build(); +} + +ShapeKey MaterialEntityRenderer::getShapeKey() { + graphics::MaterialKey drawMaterialKey; + if (_drawMaterial) { + drawMaterialKey = _drawMaterial->getKey(); + } + + bool isTranslucent = drawMaterialKey.isTranslucent(); + bool hasTangents = drawMaterialKey.isNormalMap(); + bool hasSpecular = drawMaterialKey.isMetallicMap(); + bool hasLightmap = drawMaterialKey.isLightmapMap(); + bool isUnlit = drawMaterialKey.isUnlit(); + + ShapeKey::Builder builder; + builder.withMaterial(); + + if (isTranslucent) { + builder.withTranslucent(); + } + if (hasTangents) { + builder.withTangents(); + } + if (hasSpecular) { + builder.withSpecular(); + } + if (hasLightmap) { + builder.withLightmap(); + } + if (isUnlit) { + builder.withUnlit(); + } + + return builder.build(); +} + +glm::vec3 MaterialEntityRenderer::getVertexPos(float phi, float theta) { + return glm::vec3(glm::sin(theta) * glm::cos(phi), glm::cos(theta), glm::sin(theta) * glm::sin(phi)); +} + +glm::vec3 MaterialEntityRenderer::getTangent(float phi, float theta) { + return glm::vec3(-glm::cos(theta) * glm::cos(phi), glm::sin(theta), -glm::cos(theta) * glm::sin(phi)); +} + +void MaterialEntityRenderer::addVertex(std::vector& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv) { + buffer.push_back(pos.x); buffer.push_back(pos.y); buffer.push_back(pos.z); + buffer.push_back(tan.x); buffer.push_back(tan.y); buffer.push_back(tan.z); + buffer.push_back(uv.x); buffer.push_back(uv.y); +} + +void MaterialEntityRenderer::addTriangleFan(std::vector& buffer, int stack, int step) { + float v1 = ((float)stack) / STACKS; + float theta1 = v1 * (float)M_PI; + glm::vec3 tip = getVertexPos(0, theta1); + float v2 = ((float)(stack + step)) / STACKS; + float theta2 = v2 * (float)M_PI; + for (int i = 0; i < SLICES; i++) { + float u1 = ((float)i) / SLICES; + float u2 = ((float)(i + step)) / SLICES; + float phi1 = u1 * M_PI_TIMES_2; + float phi2 = u2 * M_PI_TIMES_2; + /* (flipped for negative step) + p1 + / \ + / \ + / \ + p3 ------ p2 + */ + + glm::vec3 pos2 = getVertexPos(phi2, theta2); + glm::vec3 pos3 = getVertexPos(phi1, theta2); + + glm::vec3 tan1 = getTangent(0, theta1); + glm::vec3 tan2 = getTangent(phi2, theta2); + glm::vec3 tan3 = getTangent(phi1, theta2); + + glm::vec2 uv1 = glm::vec2((u1 + u2) / 2.0f, v1); + glm::vec2 uv2 = glm::vec2(u2, v2); + glm::vec2 uv3 = glm::vec2(u1, v2); + + addVertex(buffer, tip, tan1, uv1); + addVertex(buffer, pos2, tan2, uv2); + addVertex(buffer, pos3, tan3, uv3); + + _numVertices += 3; + } +} + +int MaterialEntityRenderer::_numVertices = 0; +std::shared_ptr MaterialEntityRenderer::_streamFormat = nullptr; +std::shared_ptr MaterialEntityRenderer::_stream = nullptr; +std::shared_ptr MaterialEntityRenderer::_verticesBuffer = nullptr; + +void MaterialEntityRenderer::generateMesh() { + _streamFormat = std::make_shared(); + _stream = std::make_shared(); + _verticesBuffer = std::make_shared(); + + const int NUM_POS_COORDS = 3; + const int NUM_TANGENT_COORDS = 3; + const int VERTEX_TANGENT_OFFSET = NUM_POS_COORDS * sizeof(float); + const int VERTEX_TEXCOORD_OFFSET = VERTEX_TANGENT_OFFSET + NUM_TANGENT_COORDS * sizeof(float); + + _streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); + _streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); + _streamFormat->setAttribute(gpu::Stream::TANGENT, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_TANGENT_OFFSET); + _streamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET); + + _stream->addBuffer(_verticesBuffer, 0, _streamFormat->getChannels().at(0)._stride); + + std::vector vertexBuffer; + + // Top + addTriangleFan(vertexBuffer, 0, 1); + + // Middle section + for (int j = 1; j < STACKS - 1; j++) { + float v1 = ((float)j) / STACKS; + float v2 = ((float)(j + 1)) / STACKS; + float theta1 = v1 * (float)M_PI; + float theta2 = v2 * (float)M_PI; + for (int i = 0; i < SLICES; i++) { + float u1 = ((float)i) / SLICES; + float u2 = ((float)(i + 1)) / SLICES; + float phi1 = u1 * M_PI_TIMES_2; + float phi2 = u2 * M_PI_TIMES_2; + + /* + p2 ---- p3 + | / | + | / | + | / | + p1 ---- p4 + */ + + glm::vec3 pos1 = getVertexPos(phi1, theta2); + glm::vec3 pos2 = getVertexPos(phi1, theta1); + glm::vec3 pos3 = getVertexPos(phi2, theta1); + glm::vec3 pos4 = getVertexPos(phi2, theta2); + + glm::vec3 tan1 = getTangent(phi1, theta2); + glm::vec3 tan2 = getTangent(phi1, theta1); + glm::vec3 tan3 = getTangent(phi2, theta1); + glm::vec3 tan4 = getTangent(phi2, theta2); + + glm::vec2 uv1 = glm::vec2(u1, v2); + glm::vec2 uv2 = glm::vec2(u1, v1); + glm::vec2 uv3 = glm::vec2(u2, v1); + glm::vec2 uv4 = glm::vec2(u2, v2); + + addVertex(vertexBuffer, pos1, tan1, uv1); + addVertex(vertexBuffer, pos2, tan2, uv2); + addVertex(vertexBuffer, pos3, tan3, uv3); + + addVertex(vertexBuffer, pos3, tan3, uv3); + addVertex(vertexBuffer, pos4, tan4, uv4); + addVertex(vertexBuffer, pos1, tan1, uv1); + + _numVertices += 6; + } + } + + // Bottom + addTriangleFan(vertexBuffer, STACKS, -1); + + _verticesBuffer->append(vertexBuffer.size() * sizeof(float), (gpu::Byte*) vertexBuffer.data()); +} + +void MaterialEntityRenderer::doRender(RenderArgs* args) { + PerformanceTimer perfTimer("RenderableMaterialEntityItem::render"); + Q_ASSERT(args->_batch); + gpu::Batch& batch = *args->_batch; + + // Don't render if our parent is set or our material is null + QUuid parentID; + Transform renderTransform; + graphics::MaterialPointer drawMaterial; + Transform textureTransform; + withReadLock([&] { + parentID = _clientOnly ? _owningAvatarID : _parentID; + renderTransform = _renderTransform; + drawMaterial = _drawMaterial; + textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0)); + textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot))); + textureTransform.setScale(glm::vec3(_materialMappingScale, 1)); + }); + if (!parentID.isNull() || !drawMaterial) { + return; + } + + batch.setModelTransform(renderTransform); + drawMaterial->setTextureTransforms(textureTransform); + + // bind the material + RenderPipelines::bindMaterial(drawMaterial, batch, args->_enableTexturing); + args->_details._materialSwitches++; + + // Draw! + if (_numVertices == 0) { + generateMesh(); + } + + batch.setInputFormat(_streamFormat); + batch.setInputStream(0, *_stream); + batch.draw(gpu::TRIANGLES, _numVertices, 0); + + const int NUM_VERTICES_PER_TRIANGLE = 3; + args->_details._trianglesRendered += _numVertices / NUM_VERTICES_PER_TRIANGLE; +} diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h new file mode 100644 index 0000000000..fef1a41138 --- /dev/null +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h @@ -0,0 +1,60 @@ +// +// Created by Sam Gondelman on 1/18/2018 +// Copyright 2018 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_RenderableMaterialEntityItem_h +#define hifi_RenderableMaterialEntityItem_h + +#include "RenderableEntityItem.h" + +#include + +class NetworkMaterial; + +namespace render { namespace entities { + +class MaterialEntityRenderer : public TypedEntityRenderer { + using Parent = TypedEntityRenderer; + using Pointer = std::shared_ptr; +public: + MaterialEntityRenderer(const EntityItemPointer& entity) : Parent(entity) {} + +private: + virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; + virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; + virtual void doRender(RenderArgs* args) override; + + ItemKey getKey() override; + ShapeKey getShapeKey() override; + + QUuid _parentID; + bool _clientOnly; + QUuid _owningAvatarID; + glm::vec2 _materialMappingPos; + glm::vec2 _materialMappingScale; + float _materialMappingRot; + Transform _renderTransform; + + std::shared_ptr _drawMaterial; + + static int _numVertices; + static std::shared_ptr _streamFormat; + static std::shared_ptr _stream; + static std::shared_ptr _verticesBuffer; + + void generateMesh(); + void addTriangleFan(std::vector& buffer, int stack, int step); + static glm::vec3 getVertexPos(float phi, float theta); + static glm::vec3 getTangent(float phi, float theta); + static void addVertex(std::vector& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv); + const int SLICES = 15; + const int STACKS = 9; + const float M_PI_TIMES_2 = 2.0f * (float)M_PI; +}; + +} } +#endif // hifi_RenderableMaterialEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 56e3f96014..83ec675adf 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1389,6 +1389,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce auto entityRenderer = static_cast(&data); entityRenderer->setSubRenderItemIDs(newRenderItemIDs); }); + processMaterials(); } } @@ -1475,3 +1476,28 @@ void ModelEntityRenderer::mapJoints(const TypedEntityPointer& entity, const QStr } } +void ModelEntityRenderer::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { + Parent::addMaterial(material, parentMaterialName); + if (_model && _model->fetchRenderItemIDs().size() > 0) { + _model->addMaterial(material, parentMaterialName); + } +} + +void ModelEntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { + Parent::removeMaterial(material, parentMaterialName); + if (_model && _model->fetchRenderItemIDs().size() > 0) { + _model->removeMaterial(material, parentMaterialName); + } +} + +void ModelEntityRenderer::processMaterials() { + assert(_model); + std::lock_guard lock(_materialsLock); + for (auto& shapeMaterialPair : _materials) { + auto material = shapeMaterialPair.second; + while (!material.empty()) { + _model->addMaterial(material.top(), shapeMaterialPair.first); + material.pop(); + } + } +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 33fc9910a0..25ae668a8e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -138,10 +138,14 @@ namespace render { namespace entities { class ModelEntityRenderer : public TypedEntityRenderer { using Parent = TypedEntityRenderer; friend class EntityRenderer; + Q_OBJECT public: ModelEntityRenderer(const EntityItemPointer& entity); + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override; + void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override; + protected: virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override; virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override; @@ -194,6 +198,8 @@ private: uint64_t _lastAnimated { 0 }; render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() }; + + void processMaterials(); }; } } // namespace diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 0c4f8cbd39..d561e8d5e8 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -54,8 +54,7 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin if (_lastUserData != entity->getUserData()) { return true; } - glm::vec4 newColor(toGlm(entity->getXColor()), entity->getLocalRenderAlpha()); - if (newColor != _color) { + if (_material != entity->getMaterial()) { return true; } @@ -78,7 +77,9 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce _procedural.setProceduralData(ProceduralData::parse(_lastUserData)); } - _color = vec4(toGlm(entity->getXColor()), entity->getLocalRenderAlpha()); + removeMaterial(_material, "0"); + _material = entity->getMaterial(); + addMaterial(graphics::MaterialLayer(_material, 0), "0"); _shape = entity->getShape(); _position = entity->getWorldPosition(); @@ -112,14 +113,13 @@ bool ShapeEntityRenderer::isTransparent() const { return Parent::isTransparent(); } - - void ShapeEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderableShapeEntityItem::render"); Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; + std::shared_ptr mat; auto geometryCache = DependencyManager::get(); GeometryCache::Shape geometryShape; bool proceduralRender = false; @@ -127,15 +127,22 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { withReadLock([&] { geometryShape = geometryCache->getShapeForEntityShape(_shape); batch.setModelTransform(_renderTransform); // use a transform with scale, rotation, registration point and translation - outColor = _color; - if (_procedural.isReady()) { - _procedural.prepare(batch, _position, _dimensions, _orientation); - outColor = _procedural.getColor(_color); - outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; - proceduralRender = true; + mat = _materials["0"].top().material; + if (mat) { + outColor = glm::vec4(mat->getAlbedo(), mat->getOpacity()); + if (_procedural.isReady()) { + _procedural.prepare(batch, _position, _dimensions, _orientation); + outColor = _procedural.getColor(outColor); + outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; + proceduralRender = true; + } } }); + if (!mat) { + return; + } + if (proceduralRender) { if (render::ShapeKey(args->_globalShapeKey).isWireframe()) { geometryCache->renderWireShape(batch, geometryShape, outColor); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 433cb41ad2..c913544255 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -34,7 +34,7 @@ private: QString _lastUserData; Transform _renderTransform; entity::Shape _shape { entity::Sphere }; - glm::vec4 _color; + std::shared_ptr _material; glm::vec3 _position; glm::vec3 _dimensions; glm::quat _orientation; diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index d6d9058e44..dca495ee03 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,4 +1,8 @@ set(TARGET_NAME entities) setup_hifi_library(Network Script) include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") -link_hifi_libraries(shared networking octree avatars graphics) +include_hifi_library_headers(fbx) +include_hifi_library_headers(gpu) +include_hifi_library_headers(image) +include_hifi_library_headers(ktx) +link_hifi_libraries(shared networking octree avatars graphics model-networking) \ No newline at end of file diff --git a/libraries/entities/src/DeleteEntityOperator.cpp b/libraries/entities/src/DeleteEntityOperator.cpp index cbd0ad7839..347d40ea49 100644 --- a/libraries/entities/src/DeleteEntityOperator.cpp +++ b/libraries/entities/src/DeleteEntityOperator.cpp @@ -93,7 +93,7 @@ bool DeleteEntityOperator::preRecursion(const OctreeElementPointer& element) { // and we can stop searching. if (entityTreeElement == details.containingElement) { EntityItemPointer theEntity = details.entity; - bool entityDeleted = entityTreeElement->removeEntityItem(theEntity); // remove it from the element + bool entityDeleted = entityTreeElement->removeEntityItem(theEntity, true); // remove it from the element assert(entityDeleted); (void)entityDeleted; // quite warning _tree->clearEntityMapEntry(details.entity->getEntityItemID()); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index ed13a46414..ec0bdbd2ae 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -60,14 +60,6 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : } EntityItem::~EntityItem() { - // clear out any left-over actions - EntityTreeElementPointer element = _element; // use local copy of _element for logic below - EntityTreePointer entityTree = element ? element->getTree() : nullptr; - EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; - if (simulation) { - clearActions(simulation); - } - // these pointers MUST be correct at delete, else we probably have a dangling backpointer // to this EntityItem in the corresponding data structure. assert(!_simulated); @@ -2937,3 +2929,32 @@ void EntityItem::retrieveMarketplacePublicKey() { networkReply->deleteLater(); }); } + +void EntityItem::preDelete() { + // clear out any left-over actions + EntityTreeElementPointer element = _element; // use local copy of _element for logic below + EntityTreePointer entityTree = element ? element->getTree() : nullptr; + EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; + if (simulation) { + clearActions(simulation); + } +} + +void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { + std::lock_guard lock(_materialsLock); + _materials[parentMaterialName].push(material); +} + +void EntityItem::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { + std::lock_guard lock(_materialsLock); + _materials[parentMaterialName].remove(material); +} + +std::unordered_map EntityItem::getMaterials() { + std::unordered_map toReturn; + { + std::lock_guard lock(_materialsLock); + toReturn = _materials; + } + return toReturn; +} \ No newline at end of file diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 5f84bcc311..b12417c496 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -36,6 +36,8 @@ #include "SimulationFlags.h" #include "EntityDynamicInterface.h" +#include "graphics/Material.h" + class EntitySimulation; class EntityTreeElement; class EntityTreeElementExtraEncodeData; @@ -49,7 +51,6 @@ typedef std::shared_ptr EntityTreeElementPointer; using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr; - #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; #define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() override { }; @@ -443,10 +444,10 @@ public: void scriptHasUnloaded() { _loadedScript = ""; _loadedScriptTimestamp = 0; } bool getClientOnly() const { return _clientOnly; } - void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; } + virtual void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; } // if this entity is client-only, which avatar is it associated with? QUuid getOwningAvatarID() const { return _owningAvatarID; } - void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } + virtual void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } virtual bool wantsHandControllerPointerEvents() const { return false; } virtual bool wantsKeyboardFocus() const { return false; } @@ -477,6 +478,13 @@ public: void setCauterized(bool value) { _cauterized = value; } bool getCauterized() const { return _cauterized; } + virtual void preDelete(); + virtual void postParentFixup() {} + + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); + void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); + std::unordered_map getMaterials(); + signals: void requestRenderUpdate(); @@ -631,6 +639,11 @@ protected: quint64 _lastUpdatedQueryAACubeTimestamp { 0 }; bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera + +private: + std::unordered_map _materials; + std::mutex _materialsLock; + }; #endif // hifi_EntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index e2a5ddf8b5..a2724c4cbf 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -115,6 +115,17 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_STATIC_MESH); } +QHash stringToMaterialMappingModeLookup; + +void addMaterialMappingMode(MaterialMappingMode mode) { + stringToMaterialMappingModeLookup[MaterialMappingModeHelpers::getNameForMaterialMappingMode(mode)] = mode; +} + +void buildStringToMaterialMappingModeLookup() { + addMaterialMappingMode(UV); + addMaterialMappingMode(PROJECTED); +} + QString getCollisionGroupAsString(uint8_t group) { switch (group) { case USER_COLLISION_GROUP_DYNAMIC: @@ -259,6 +270,21 @@ void EntityItemProperties::setSkyboxModeFromString(const QString& skyboxMode) { } } +QString EntityItemProperties::getMaterialMappingModeAsString() const { + return MaterialMappingModeHelpers::getNameForMaterialMappingMode(_materialMappingMode); +} + +void EntityItemProperties::setMaterialMappingModeFromString(const QString& materialMappingMode) { + if (stringToMaterialMappingModeLookup.empty()) { + buildStringToMaterialMappingModeLookup(); + } + auto materialMappingModeItr = stringToMaterialMappingModeLookup.find(materialMappingMode.toLower()); + if (materialMappingModeItr != stringToMaterialMappingModeLookup.end()) { + _materialMappingMode = materialMappingModeItr.value(); + _materialMappingModeChanged = true; + } +} + EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; @@ -329,6 +355,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_RADIUS_SPREAD, radiusSpread); CHECK_PROPERTY_CHANGE(PROP_RADIUS_START, radiusStart); CHECK_PROPERTY_CHANGE(PROP_RADIUS_FINISH, radiusFinish); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_URL, materialURL); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_MODE, materialMappingMode); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_PRIORITY, priority); + CHECK_PROPERTY_CHANGE(PROP_PARENT_MATERIAL_NAME, parentMaterialName); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_POS, materialMappingPos); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); @@ -625,6 +658,17 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); } + // Materials + if (_type == EntityTypes::Material) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_URL, materialURL); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_MATERIAL_MAPPING_MODE, materialMappingMode, getMaterialMappingModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_PRIORITY, priority); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_MATERIAL_NAME, parentMaterialName); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_POS, materialMappingPos); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); + } + if (!skipDefaults && !strictSemantics) { AABox aaBox = getAABox(); QScriptValue boundingBox = engine->newObject(); @@ -758,6 +802,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusStart, float, setRadiusStart); COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusFinish, float, setRadiusFinish); COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialURL, QString, setMaterialURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(materialMappingMode, MaterialMappingMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE(priority, quint16, setPriority); + COPY_PROPERTY_FROM_QSCRIPTVALUE(parentMaterialName, QString, setParentMaterialName); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingPos, glmVec2, setMaterialMappingPos); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingScale, glmVec2, setMaterialMappingScale); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -1112,6 +1163,14 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_RADIUS_START, RadiusStart, radiusStart, float); ADD_PROPERTY_TO_MAP(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_URL, MaterialURL, materialURL, QString); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_PRIORITY, Priority, priority, quint16); + ADD_PROPERTY_TO_MAP(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float); + // Certifiable Properties ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString); ADD_PROPERTY_TO_MAP(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString); @@ -1495,6 +1554,17 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } + // Materials + if (properties.getType() == EntityTypes::Material) { + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_URL, properties.getMaterialURL()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_MODE, (uint32_t)properties.getMaterialMappingMode()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, properties.getPriority()); + APPEND_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_NAME, properties.getParentMaterialName()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, properties.getMaterialMappingPos()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, properties.getMaterialMappingScale()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, properties.getMaterialMappingRot()); + } + APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); @@ -1851,6 +1921,17 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); } + // Materials + if (properties.getType() == EntityTypes::Material) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_URL, QString, setMaterialURL); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, setMaterialMappingMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_PRIORITY, quint16, setPriority); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_MATERIAL_NAME, QString, setParentMaterialName); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_POS, glmVec2, setMaterialMappingPos); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_SCALE, glmVec2, setMaterialMappingScale); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot); + } + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACTION_DATA, QByteArray, setActionData); @@ -2024,6 +2105,14 @@ void EntityItemProperties::markAllChanged() { //_alphaStartChanged = true; //_alphaFinishChanged = true; + _materialURLChanged = true; + _materialMappingModeChanged = true; + _priorityChanged = true; + _parentMaterialNameChanged = true; + _materialMappingPosChanged = true; + _materialMappingScaleChanged = true; + _materialMappingRotChanged = true; + // Certifiable Properties _itemNameChanged = true; _itemDescriptionChanged = true; @@ -2347,6 +2436,27 @@ QList EntityItemProperties::listChangedProperties() { if (radiusFinishChanged()) { out += "radiusFinish"; } + if (materialURLChanged()) { + out += "materialURL"; + } + if (materialMappingModeChanged()) { + out += "materialMappingMode"; + } + if (priorityChanged()) { + out += "priority"; + } + if (parentMaterialNameChanged()) { + out += "parentMaterialName"; + } + if (materialMappingPosChanged()) { + out += "materialMappingPos"; + } + if (materialMappingScaleChanged()) { + out += "materialMappingScale"; + } + if (materialMappingRotChanged()) { + out += "materialMappingRot"; + } // Certifiable Properties if (itemNameChanged()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 3e0770f386..36ca47291c 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -44,6 +44,8 @@ #include "TextEntityItem.h" #include "ZoneEntityItem.h" +#include "MaterialMappingMode.h" + const quint64 UNKNOWN_CREATED_TIME = 0; using ComponentPair = std::pair; @@ -58,19 +60,21 @@ const std::array COMPONENT_MODES = { { /// set of entity item properties via JavaScript hashes/QScriptValues /// all units for SI units (meter, second, radian, etc) class EntityItemProperties { - friend class EntityItem; // TODO: consider removing this friend relationship and use public methods - friend class ModelEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class BoxEntityItem; // TODO: consider removing this friend relationship and use public methods - 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 - friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class ShapeEntityItem; // TODO: consider removing this friend relationship and use public methods + // TODO: consider removing these friend relationship and use public methods + friend class EntityItem; + friend class ModelEntityItem; + friend class BoxEntityItem; + friend class SphereEntityItem; + friend class LightEntityItem; + friend class TextEntityItem; + friend class ParticleEffectEntityItem; + friend class ZoneEntityItem; + friend class WebEntityItem; + friend class LineEntityItem; + friend class PolyVoxEntityItem; + friend class PolyLineEntityItem; + friend class ShapeEntityItem; + friend class MaterialEntityItem; public: EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()); virtual ~EntityItemProperties() = default; @@ -218,6 +222,14 @@ public: DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube()); DEFINE_PROPERTY_REF(PROP_SHAPE, Shape, shape, QString, "Sphere"); + DEFINE_PROPERTY_REF(PROP_MATERIAL_URL, MaterialURL, materialURL, QString, ""); + DEFINE_PROPERTY_REF_ENUM(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode, UV); + DEFINE_PROPERTY_REF(PROP_MATERIAL_PRIORITY, Priority, priority, quint16, 0); + DEFINE_PROPERTY_REF(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString, "0"); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2, glm::vec2(0, 0)); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2, glm::vec2(1, 1)); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float, 0); + // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); DEFINE_PROPERTY_REF(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString, ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION); diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index f291973e52..51a23b6867 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -101,6 +101,7 @@ changedProperties += P; \ } +inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec2& v) { return vec2toScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec3& v) { return vec3toScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, float v) { return QScriptValue(v); } inline QScriptValue convertScriptValue(QScriptEngine* e, int v) { return QScriptValue(v); } @@ -183,6 +184,7 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu properties.setProperty(#P, V); \ } +typedef glm::vec2 glmVec2; typedef glm::vec3 glmVec3; typedef glm::quat glmQuat; typedef QVector qVectorVec3; @@ -221,6 +223,23 @@ inline QByteArray QByteArray_convertFromScriptValue(const QScriptValue& v, bool& return QByteArray::fromBase64(b64.toUtf8()); } +inline glmVec2 glmVec2_convertFromScriptValue(const QScriptValue& v, bool& isValid) { + isValid = false; /// assume it can't be converted + QScriptValue x = v.property("x"); + QScriptValue y = v.property("y"); + if (x.isValid() && y.isValid()) { + glm::vec4 newValue(0); + newValue.x = x.toVariant().toFloat(); + newValue.y = y.toVariant().toFloat(); + isValid = !glm::isnan(newValue.x) && + !glm::isnan(newValue.y); + if (isValid) { + return newValue; + } + } + return glm::vec2(0); +} + inline glmVec3 glmVec3_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = false; /// assume it can't be converted QScriptValue x = v.property("x"); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 90438ab01c..b65d5d1a3f 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -227,6 +227,14 @@ enum EntityPropertyList { PROP_LOCAL_DIMENSIONS, // only used to convert values to and from scripts + PROP_MATERIAL_URL, + PROP_MATERIAL_MAPPING_MODE, + PROP_MATERIAL_PRIORITY, + PROP_PARENT_MATERIAL_NAME, + PROP_MATERIAL_MAPPING_POS, + PROP_MATERIAL_MAPPING_SCALE, + PROP_MATERIAL_MAPPING_ROT, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 60bcc85575..ad0066af4a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1739,7 +1739,8 @@ void EntityTree::fixupNeedsParentFixups() { } }); entity->locationChanged(true); - } else if (getIsServer() && _avatarIDs.contains(entity->getParentID())) { + entity->postParentFixup(); + } else if (getIsServer() || _avatarIDs.contains(entity->getParentID())) { // this is a child of an avatar, which the entity server will never have // a SpatiallyNestable object for. Add it to a list for cleanup when the avatar leaves. if (!_childrenOfAvatars.contains(entity->getParentID())) { @@ -2385,3 +2386,51 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const { return entity->getJointNames(); } +std::function EntityTree::_addMaterialToEntityOperator = nullptr; +std::function EntityTree::_removeMaterialFromEntityOperator = nullptr; +std::function EntityTree::_addMaterialToAvatarOperator = nullptr; +std::function EntityTree::_removeMaterialFromAvatarOperator = nullptr; +std::function EntityTree::_addMaterialToOverlayOperator = nullptr; +std::function EntityTree::_removeMaterialFromOverlayOperator = nullptr; + +bool EntityTree::addMaterialToEntity(const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + if (_addMaterialToEntityOperator) { + return _addMaterialToEntityOperator(entityID, material, parentMaterialName); + } + return false; +} + +bool EntityTree::removeMaterialFromEntity(const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + if (_removeMaterialFromEntityOperator) { + return _removeMaterialFromEntityOperator(entityID, material, parentMaterialName); + } + return false; +} + +bool EntityTree::addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + if (_addMaterialToAvatarOperator) { + return _addMaterialToAvatarOperator(avatarID, material, parentMaterialName); + } + return false; +} + +bool EntityTree::removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + if (_removeMaterialFromAvatarOperator) { + return _removeMaterialFromAvatarOperator(avatarID, material, parentMaterialName); + } + return false; +} + +bool EntityTree::addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) { + if (_addMaterialToOverlayOperator) { + return _addMaterialToOverlayOperator(overlayID, material, parentMaterialName); + } + return false; +} + +bool EntityTree::removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) { + if (_removeMaterialFromOverlayOperator) { + return _removeMaterialFromOverlayOperator(overlayID, material, parentMaterialName); + } + return false; +} \ No newline at end of file diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 90fe342f57..1dea98ded6 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -280,6 +280,21 @@ public: void setMyAvatar(std::shared_ptr myAvatar) { _myAvatar = myAvatar; } + static void setAddMaterialToEntityOperator(std::function addMaterialToEntityOperator) { _addMaterialToEntityOperator = addMaterialToEntityOperator; } + static void setRemoveMaterialFromEntityOperator(std::function removeMaterialFromEntityOperator) { _removeMaterialFromEntityOperator = removeMaterialFromEntityOperator; } + static bool addMaterialToEntity(const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName); + static bool removeMaterialFromEntity(const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName); + + static void setAddMaterialToAvatarOperator(std::function addMaterialToAvatarOperator) { _addMaterialToAvatarOperator = addMaterialToAvatarOperator; } + static void setRemoveMaterialFromAvatarOperator(std::function removeMaterialFromAvatarOperator) { _removeMaterialFromAvatarOperator = removeMaterialFromAvatarOperator; } + static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName); + static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName); + + static void setAddMaterialToOverlayOperator(std::function addMaterialToOverlayOperator) { _addMaterialToOverlayOperator = addMaterialToOverlayOperator; } + static void setRemoveMaterialFromOverlayOperator(std::function removeMaterialFromOverlayOperator) { _removeMaterialFromOverlayOperator = removeMaterialFromOverlayOperator; } + static bool addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName); + static bool removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName); + signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); @@ -387,6 +402,13 @@ private: void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); std::shared_ptr _myAvatar{ nullptr }; + + static std::function _addMaterialToEntityOperator; + static std::function _removeMaterialFromEntityOperator; + static std::function _addMaterialToAvatarOperator; + static std::function _removeMaterialFromAvatarOperator; + static std::function _addMaterialToOverlayOperator; + static std::function _removeMaterialFromOverlayOperator; }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index c9e1ecc3f1..9e32bc3346 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -896,6 +896,7 @@ EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemI void EntityTreeElement::cleanupEntities() { withWriteLock([&] { foreach(EntityItemPointer entity, _entityItems) { + entity->preDelete(); // NOTE: only EntityTreeElement should ever be changing the value of entity->_element // NOTE: We explicitly don't delete the EntityItem here because since we only // access it by smart pointers, when we remove it from the _entityItems @@ -907,26 +908,10 @@ void EntityTreeElement::cleanupEntities() { bumpChangedContent(); } -bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) { - bool foundEntity = false; - withWriteLock([&] { - uint16_t numberOfEntities = _entityItems.size(); - for (uint16_t i = 0; i < numberOfEntities; i++) { - EntityItemPointer& entity = _entityItems[i]; - if (entity->getEntityItemID() == id) { - foundEntity = true; - // NOTE: only EntityTreeElement should ever be changing the value of entity->_element - entity->_element = NULL; - _entityItems.removeAt(i); - bumpChangedContent(); - break; - } - } - }); - return foundEntity; -} - -bool EntityTreeElement::removeEntityItem(EntityItemPointer entity) { +bool EntityTreeElement::removeEntityItem(EntityItemPointer entity, bool deletion) { + if (deletion) { + entity->preDelete(); + } int numEntries = 0; withWriteLock([&] { numEntries = _entityItems.removeAll(entity); diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index a524904c71..2313bde0c4 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -210,8 +210,7 @@ public: void getEntitiesInside(const AACube& box, QVector& foundEntities); void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities - bool removeEntityWithEntityItemID(const EntityItemID& id); - bool removeEntityItem(EntityItemPointer entity); + bool removeEntityItem(EntityItemPointer entity, bool deletion = false); bool containsEntityBounds(EntityItemPointer entity) const; bool bestFitEntityBounds(EntityItemPointer entity) const; diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index cb17c28fd7..307371c649 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -29,6 +29,7 @@ #include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" #include "ShapeEntityItem.h" +#include "MaterialEntityItem.h" QMap EntityTypes::_typeToNameMap; QMap EntityTypes::_nameToTypeMap; @@ -50,6 +51,7 @@ REGISTER_ENTITY_TYPE(PolyLine) REGISTER_ENTITY_TYPE(Shape) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) +REGISTER_ENTITY_TYPE(Material) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { QMap::iterator matchedTypeName = _typeToNameMap.find(entityType); diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 316bf2b813..62011c6e26 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -49,7 +49,8 @@ public: PolyVox, PolyLine, Shape, - LAST = Shape + Material, + LAST = Material } EntityType; static const QString& getEntityTypeName(EntityType entityType); diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp new file mode 100644 index 0000000000..44ef34a3a4 --- /dev/null +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -0,0 +1,339 @@ +// +// Created by Sam Gondelman on 1/12/18 +// Copyright 2018 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 "MaterialEntityItem.h" + +#include "EntityItemProperties.h" + +#include "QJsonDocument" +#include "QJsonArray" + +EntityItemPointer MaterialEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + Pointer entity(new MaterialEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); + entity->setProperties(properties); + // When you reload content, setProperties doesn't have any of the propertiesChanged flags set, so it won't trigger a material add + entity->removeMaterial(); + entity->applyMaterial(); + return entity; +} + +// our non-pure virtual subclass for now... +MaterialEntityItem::MaterialEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _type = EntityTypes::Material; +} + +EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { + EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialURL, getMaterialURL); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingMode, getMaterialMappingMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(priority, getPriority); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentMaterialName, getParentMaterialName); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingPos, getMaterialMappingPos); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingScale, getMaterialMappingScale); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingRot, getMaterialMappingRot); + return properties; +} + +bool MaterialEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialURL, setMaterialURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingMode, setMaterialMappingMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(priority, setPriority); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentMaterialName, setParentMaterialName); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingPos, setMaterialMappingPos); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingScale, setMaterialMappingScale); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingRot, setMaterialMappingRot); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "MaterialEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_MATERIAL_URL, QString, setMaterialURL); + READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, setMaterialMappingMode); + READ_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, quint16, setPriority); + READ_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_NAME, QString, setParentMaterialName); + READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, glm::vec2, setMaterialMappingPos); + READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, glm::vec2, setMaterialMappingScale); + READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot); + + return bytesRead; +} + + +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time +EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_MATERIAL_URL; + requestedProperties += PROP_MATERIAL_MAPPING_MODE; + requestedProperties += PROP_MATERIAL_PRIORITY; + requestedProperties += PROP_PARENT_MATERIAL_NAME; + requestedProperties += PROP_MATERIAL_MAPPING_POS; + requestedProperties += PROP_MATERIAL_MAPPING_SCALE; + requestedProperties += PROP_MATERIAL_MAPPING_ROT; + return requestedProperties; +} + +void MaterialEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_URL, getMaterialURL()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_MODE, (uint32_t)getMaterialMappingMode()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, getPriority()); + APPEND_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_NAME, getParentMaterialName()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, getMaterialMappingPos()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, getMaterialMappingScale()); + APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, getMaterialMappingRot()); +} + +void MaterialEntityItem::debugDump() const { + quint64 now = usecTimestampNow(); + qCDebug(entities) << " MATERIAL EntityItem id:" << getEntityItemID() << "---------------------------------------------"; + qCDebug(entities) << " name:" << _name; + qCDebug(entities) << " material url:" << _materialURL; + qCDebug(entities) << " current material name:" << _currentMaterialName.c_str(); + qCDebug(entities) << " material mapping mode:" << _materialMappingMode; + qCDebug(entities) << " priority:" << _priority; + qCDebug(entities) << " parent material name:" << _parentMaterialName; + qCDebug(entities) << " material mapping pos:" << _materialMappingPos; + qCDebug(entities) << " material mapping scale:" << _materialMappingRot; + qCDebug(entities) << " material mapping rot:" << _materialMappingScale; + qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << "MATERIAL EntityItem Ptr:" << this; +} + +void MaterialEntityItem::setUnscaledDimensions(const glm::vec3& value) { + EntityItem::setUnscaledDimensions(ENTITY_ITEM_DEFAULT_DIMENSIONS); +} + +std::shared_ptr MaterialEntityItem::getMaterial() const { + auto material = _parsedMaterials.networkMaterials.find(_currentMaterialName); + if (material != _parsedMaterials.networkMaterials.end()) { + return material->second; + } else { + return nullptr; + } +} + +void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool userDataChanged) { + bool usingUserData = materialURLString.startsWith("userData"); + if (_materialURL != materialURLString || (usingUserData && userDataChanged)) { + removeMaterial(); + _materialURL = materialURLString; + + if (materialURLString.contains("?")) { + auto split = materialURLString.split("?"); + _currentMaterialName = split.last().toStdString(); + } + + if (usingUserData) { + _parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8())); + + // Since our material changed, the current name might not be valid anymore, so we need to update + setCurrentMaterialName(_currentMaterialName); + applyMaterial(); + } else { + _networkMaterial = MaterialCache::instance().getMaterial(materialURLString); + auto onMaterialRequestFinished = [&](bool success) { + if (success) { + _parsedMaterials = _networkMaterial->parsedMaterials; + + setCurrentMaterialName(_currentMaterialName); + applyMaterial(); + } + }; + if (_networkMaterial) { + if (_networkMaterial->isLoaded()) { + onMaterialRequestFinished(!_networkMaterial->isFailed()); + } else { + connect(_networkMaterial.data(), &Resource::finished, this, onMaterialRequestFinished); + } + } + } + } +} + +void MaterialEntityItem::setCurrentMaterialName(const std::string& currentMaterialName) { + if (_parsedMaterials.networkMaterials.find(currentMaterialName) != _parsedMaterials.networkMaterials.end()) { + _currentMaterialName = currentMaterialName; + } else if (_parsedMaterials.names.size() > 0) { + _currentMaterialName = _parsedMaterials.names[0]; + } +} + +void MaterialEntityItem::setUserData(const QString& userData) { + if (_userData != userData) { + EntityItem::setUserData(userData); + if (_materialURL.startsWith("userData")) { + // Trigger material update when user data changes + setMaterialURL(_materialURL, true); + } + } +} + +void MaterialEntityItem::setMaterialMappingPos(const glm::vec2& materialMappingPos) { + if (_materialMappingPos != materialMappingPos) { + removeMaterial(); + _materialMappingPos = materialMappingPos; + applyMaterial(); + } +} + +void MaterialEntityItem::setMaterialMappingScale(const glm::vec2& materialMappingScale) { + if (_materialMappingScale != materialMappingScale) { + removeMaterial(); + _materialMappingScale = materialMappingScale; + applyMaterial(); + } +} + +void MaterialEntityItem::setMaterialMappingRot(const float& materialMappingRot) { + if (_materialMappingRot != materialMappingRot) { + removeMaterial(); + _materialMappingRot = materialMappingRot; + applyMaterial(); + } +} + +void MaterialEntityItem::setPriority(quint16 priority) { + if (_priority != priority) { + removeMaterial(); + _priority = priority; + applyMaterial(); + } +} + +void MaterialEntityItem::setParentMaterialName(const QString& parentMaterialName) { + if (_parentMaterialName != parentMaterialName) { + removeMaterial(); + _parentMaterialName = parentMaterialName; + applyMaterial(); + } +} + +void MaterialEntityItem::setParentID(const QUuid& parentID) { + if (getParentID() != parentID) { + removeMaterial(); + EntityItem::setParentID(parentID); + applyMaterial(); + } +} + +void MaterialEntityItem::setClientOnly(bool clientOnly) { + if (getClientOnly() != clientOnly) { + removeMaterial(); + EntityItem::setClientOnly(clientOnly); + applyMaterial(); + } +} + +void MaterialEntityItem::setOwningAvatarID(const QUuid& owningAvatarID) { + if (getOwningAvatarID() != owningAvatarID) { + removeMaterial(); + EntityItem::setOwningAvatarID(owningAvatarID); + applyMaterial(); + } +} + +void MaterialEntityItem::removeMaterial() { + graphics::MaterialPointer material = getMaterial(); + QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID(); + if (!material || parentID.isNull()) { + return; + } + + // Our parent could be an entity, an avatar, or an overlay + if (EntityTree::removeMaterialFromEntity(parentID, material, getParentMaterialName().toStdString())) { + return; + } + + if (EntityTree::removeMaterialFromAvatar(parentID, material, getParentMaterialName().toStdString())) { + return; + } + + if (EntityTree::removeMaterialFromOverlay(parentID, material, getParentMaterialName().toStdString())) { + return; + } + + // if a remove fails, our parent is gone, so we don't need to retry +} + +void MaterialEntityItem::applyMaterial() { + _retryApply = false; + graphics::MaterialPointer material = getMaterial(); + QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID(); + if (!material || parentID.isNull()) { + return; + } + Transform textureTransform; + textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0)); + textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot))); + textureTransform.setScale(glm::vec3(_materialMappingScale, 1)); + material->setTextureTransforms(textureTransform); + + graphics::MaterialLayer materialLayer = graphics::MaterialLayer(material, getPriority()); + + // Our parent could be an entity, an avatar, or an overlay + if (EntityTree::addMaterialToEntity(parentID, materialLayer, getParentMaterialName().toStdString())) { + return; + } + + if (EntityTree::addMaterialToAvatar(parentID, materialLayer, getParentMaterialName().toStdString())) { + return; + } + + if (EntityTree::addMaterialToOverlay(parentID, materialLayer, getParentMaterialName().toStdString())) { + return; + } + + // if we've reached this point, we couldn't find our parent, so we need to try again later + _retryApply = true; +} + +void MaterialEntityItem::postParentFixup() { + removeMaterial(); + applyMaterial(); +} + +void MaterialEntityItem::preDelete() { + EntityItem::preDelete(); + removeMaterial(); +} + +void MaterialEntityItem::update(const quint64& now) { + if (_retryApply) { + applyMaterial(); + } + + EntityItem::update(now); +} \ No newline at end of file diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h new file mode 100644 index 0000000000..f77077a782 --- /dev/null +++ b/libraries/entities/src/MaterialEntityItem.h @@ -0,0 +1,129 @@ +// +// Created by Sam Gondelman on 1/12/18 +// Copyright 2018 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_MaterialEntityItem_h +#define hifi_MaterialEntityItem_h + +#include "EntityItem.h" + +#include "MaterialMappingMode.h" +#include +#include + +class MaterialEntityItem : public EntityItem { + using Pointer = std::shared_ptr; +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + MaterialEntityItem(const EntityItemID& entityItemID); + + ALLOW_INSTANTIATION // This class can be instantiated + + void update(const quint64& now) override; + bool needsToCallUpdate() const override { return true; } + + // methods for getting/setting all properties of an entity + virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; + virtual bool setProperties(const EntityItemProperties& properties) override; + + // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + void debugDump() const override; + + virtual void setUnscaledDimensions(const glm::vec3& value) override; + + QString getMaterialURL() const { return _materialURL; } + void setMaterialURL(const QString& materialURLString, bool userDataChanged = false); + + void setCurrentMaterialName(const std::string& currentMaterialName); + + MaterialMappingMode getMaterialMappingMode() const { return _materialMappingMode; } + void setMaterialMappingMode(MaterialMappingMode mode) { _materialMappingMode = mode; } + + quint16 getPriority() const { return _priority; } + void setPriority(quint16 priority); + + QString getParentMaterialName() const { return _parentMaterialName; } + void setParentMaterialName(const QString& parentMaterialName); + + glm::vec2 getMaterialMappingPos() const { return _materialMappingPos; } + void setMaterialMappingPos(const glm::vec2& materialMappingPos); + glm::vec2 getMaterialMappingScale() const { return _materialMappingScale; } + void setMaterialMappingScale(const glm::vec2& materialMappingScale); + float getMaterialMappingRot() const { return _materialMappingRot; } + void setMaterialMappingRot(const float& materialMappingRot); + + std::shared_ptr getMaterial() const; + + void setUserData(const QString& userData) override; + void setParentID(const QUuid& parentID) override; + void setClientOnly(bool clientOnly) override; + void setOwningAvatarID(const QUuid& owningAvatarID) override; + + void applyMaterial(); + void removeMaterial(); + + void postParentFixup() override; + void preDelete() override; + +private: + // URL for this material. Currently, only JSON format is supported. Set to "userData" to use the user data to live edit a material. + // The following fields are supported in the JSON: + // materialVersion: a uint for the version of this network material (currently, only 1 is supported) + // materials, which is either an object or an array of objects, each with the following properties: + // strings: + // name (NOT YET USED), model (NOT YET USED, should use "hifi_pbr") + // floats: + // opacity, roughness, metallic, scattering + // bool: + // unlit + // colors (arrays of 3 floats 0-1. Optional fourth value in array can be a boolean isSRGB): + // emissive, albedo + // urls to textures: + // emissiveMap, albedoMap (set opacityMap = albedoMap for transparency), metallicMap or specularMap, roughnessMap or glossMap, + // normalMap or bumpMap, occlusionMap, lightmapMap (broken, FIXME), scatteringMap (only works if normal mapped) + QString _materialURL; + // Type of material. "uv" or "projected". NOT YET IMPLEMENTED, only UV is used + MaterialMappingMode _materialMappingMode { UV }; + // Priority for this material when applying it to its parent. Only the highest priority material will be used. Materials with the same priority are (essentially) randomly sorted. + // Base materials that come with models always have priority 0. + quint16 _priority { 0 }; + // An identifier for choosing a submesh or submeshes within a parent. If in the format "mat::", all submeshes with material name "" will be replaced. Otherwise, + // parentMaterialName will be parsed as an unsigned int (strings not starting with "mat::" will parse to 0), representing the mesh index to modify. + QString _parentMaterialName { "0" }; + // Offset position in UV-space of top left of material, (0, 0) to (1, 1) + glm::vec2 _materialMappingPos { 0, 0 }; + // How much to scale this material within its parent's UV-space + glm::vec2 _materialMappingScale { 1, 1 }; + // How much to rotate this material within its parent's UV-space (degrees) + float _materialMappingRot { 0 }; + + NetworkMaterialResourcePointer _networkMaterial; + NetworkMaterialResource::ParsedMaterials _parsedMaterials; + std::string _currentMaterialName; + + bool _retryApply { false }; + +}; + +#endif // hifi_MaterialEntityItem_h diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 5d33e4c047..bec7bc1906 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -721,4 +721,4 @@ bool ModelEntityItem::isAnimatingSomething() const { _animationProperties.getRunning() && (_animationProperties.getFPS() != 0.0f); }); -} +} \ No newline at end of file diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index cbcfcaaa1d..2425208a87 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -85,6 +85,7 @@ EntityItemPointer ShapeEntityItem::sphereFactory(const EntityItemID& entityID, c ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Shape; _volumeMultiplier *= PI / 6.0f; + _material = std::make_shared(); } EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { @@ -184,6 +185,7 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit void ShapeEntityItem::setColor(const rgbColor& value) { memcpy(_color, value, sizeof(rgbColor)); + _material->setAlbedo(glm::vec3(_color[0], _color[1], _color[2]) / 255.0f); } xColor ShapeEntityItem::getXColor() const { @@ -204,6 +206,11 @@ void ShapeEntityItem::setColor(const QColor& value) { setAlpha(value.alpha()); } +void ShapeEntityItem::setAlpha(float alpha) { + _alpha = alpha; + _material->setOpacity(alpha); +} + void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { const float MAX_FLAT_DIMENSION = 0.0001f; if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) { diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 84ce1ce57e..7ad1b3c1c2 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -75,7 +75,7 @@ public: void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); } float getAlpha() const { return _alpha; }; - void setAlpha(float alpha) { _alpha = alpha; } + void setAlpha(float alpha); const rgbColor& getColor() const { return _color; } void setColor(const rgbColor& value); @@ -101,6 +101,8 @@ public: virtual void computeShapeInfo(ShapeInfo& info) override; virtual ShapeType getShapeType() const override; + std::shared_ptr getMaterial() { return _material; } + protected: float _alpha { 1 }; @@ -111,6 +113,8 @@ protected: //! prior functionality where new or unsupported shapes are treated as //! ellipsoids. ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID }; + + std::shared_ptr _material; }; #endif // hifi_ShapeEntityItem_h diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp index ea5bd331c9..2300bc5098 100755 --- a/libraries/graphics/src/graphics/Material.cpp +++ b/libraries/graphics/src/graphics/Material.cpp @@ -12,6 +12,8 @@ #include "TextureMap.h" +#include + using namespace graphics; using namespace gpu; @@ -30,6 +32,7 @@ Material::Material() : } Material::Material(const Material& material) : + _name(material._name), _key(material._key), _textureMaps(material._textureMaps) { @@ -46,6 +49,8 @@ Material::Material(const Material& material) : Material& Material::operator= (const Material& material) { QMutexLocker locker(&_textureMapsMutex); + _name = material._name; + _key = (material._key); _textureMaps = (material._textureMaps); _hasCalculatedTextureInfo = false; @@ -222,3 +227,14 @@ bool Material::calculateMaterialInfo() const { } return _hasCalculatedTextureInfo; } + +void Material::setTextureTransforms(const Transform& transform) { + for (auto &textureMapItem : _textureMaps) { + if (textureMapItem.second) { + textureMapItem.second->setTextureTransform(transform); + } + } + for (int i = 0; i < NUM_TEXCOORD_TRANSFORMS; i++) { + _texMapArrayBuffer.edit()._texcoordTransforms[i] = transform.getMatrix(); + } +} \ No newline at end of file diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h index cfbfaa61ea..632cf99391 100755 --- a/libraries/graphics/src/graphics/Material.h +++ b/libraries/graphics/src/graphics/Material.h @@ -15,11 +15,14 @@ #include #include +#include #include #include +class Transform; + namespace graphics { class TextureMap; @@ -351,6 +354,15 @@ public: size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; } bool hasTextureInfo() const { return _hasCalculatedTextureInfo; } + void setTextureTransforms(const Transform& transform); + + const std::string& getName() { return _name; } + + void setModel(const std::string& model) { _model = model; } + +protected: + std::string _name { "" }; + private: mutable MaterialKey _key; mutable UniformBufferView _schemaBuffer; @@ -364,10 +376,46 @@ private: mutable bool _hasCalculatedTextureInfo { false }; bool calculateMaterialInfo() const; + std::string _model { "hifi_pbr" }; }; typedef std::shared_ptr< Material > MaterialPointer; +class MaterialLayer { +public: + MaterialLayer(MaterialPointer material, quint16 priority) : material(material), priority(priority) {} + + MaterialPointer material { nullptr }; + quint16 priority { 0 }; +}; + +class MaterialLayerCompare { +public: + bool operator() (MaterialLayer left, MaterialLayer right) { + return left.priority < right.priority; + } +}; + +class MultiMaterial : public std::priority_queue, MaterialLayerCompare> { +public: + bool remove(const MaterialPointer& value) { + auto it = c.begin(); + while (it != c.end()) { + if (it->material == value) { + break; + } + it++; + } + if (it != c.end()) { + c.erase(it); + std::make_heap(c.begin(), c.end(), comp); + return true; + } else { + return false; + } + } +}; + }; #endif diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp new file mode 100644 index 0000000000..8d9d6571f8 --- /dev/null +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -0,0 +1,208 @@ +// +// Created by Sam Gondelman on 2/9/2018 +// Copyright 2018 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 "MaterialCache.h" + +#include "QJsonObject" +#include "QJsonDocument" +#include "QJsonArray" + +NetworkMaterialResource::NetworkMaterialResource(const QUrl& url) : + Resource(url) {} + +void NetworkMaterialResource::downloadFinished(const QByteArray& data) { + parsedMaterials.reset(); + + if (_url.toString().contains(".json")) { + parsedMaterials = parseJSONMaterials(QJsonDocument::fromJson(data)); + } + + // TODO: parse other material types + + finishedLoading(true); +} + +bool NetworkMaterialResource::parseJSONColor(const QJsonValue& array, glm::vec3& color, bool& isSRGB) { + if (array.isArray()) { + QJsonArray colorArray = array.toArray(); + if (colorArray.size() >= 3 && colorArray[0].isDouble() && colorArray[1].isDouble() && colorArray[2].isDouble()) { + isSRGB = true; + if (colorArray.size() >= 4) { + if (colorArray[3].isBool()) { + isSRGB = colorArray[3].toBool(); + } + } + color = glm::vec3(colorArray[0].toDouble(), colorArray[1].toDouble(), colorArray[2].toDouble()); + return true; + } + } + return false; +} + +NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMaterials(const QJsonDocument& materialJSON) { + ParsedMaterials toReturn; + if (!materialJSON.isNull() && materialJSON.isObject()) { + QJsonObject materialJSONObject = materialJSON.object(); + for (auto& key : materialJSONObject.keys()) { + if (key == "materialVersion") { + auto value = materialJSONObject.value(key); + if (value.isDouble()) { + toReturn.version = (uint)value.toInt(); + } + } else if (key == "materials") { + auto materialsValue = materialJSONObject.value(key); + if (materialsValue.isArray()) { + QJsonArray materials = materialsValue.toArray(); + for (auto material : materials) { + if (!material.isNull() && material.isObject()) { + auto parsedMaterial = parseJSONMaterial(material.toObject()); + toReturn.networkMaterials[parsedMaterial.first] = parsedMaterial.second; + toReturn.names.push_back(parsedMaterial.first); + } + } + } else if (materialsValue.isObject()) { + auto parsedMaterial = parseJSONMaterial(materialsValue.toObject()); + toReturn.networkMaterials[parsedMaterial.first] = parsedMaterial.second; + toReturn.names.push_back(parsedMaterial.first); + } + } + } + } + + return toReturn; +} + +std::pair> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON) { + std::string name = ""; + std::shared_ptr material = std::make_shared(); + for (auto& key : materialJSON.keys()) { + if (key == "name") { + auto nameJSON = materialJSON.value(key); + if (nameJSON.isString()) { + name = nameJSON.toString().toStdString(); + } + } else if (key == "model") { + auto modelJSON = materialJSON.value(key); + if (modelJSON.isString()) { + material->setModel(modelJSON.toString().toStdString()); + } + } else if (key == "emissive") { + glm::vec3 color; + bool isSRGB; + bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB); + if (valid) { + material->setEmissive(color, isSRGB); + } + } else if (key == "opacity") { + auto value = materialJSON.value(key); + if (value.isDouble()) { + material->setOpacity(value.toDouble()); + } + } else if (key == "unlit") { + auto value = materialJSON.value(key); + if (value.isBool()) { + material->setUnlit(value.toBool()); + } + } else if (key == "albedo") { + glm::vec3 color; + bool isSRGB; + bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB); + if (valid) { + material->setAlbedo(color, isSRGB); + } + } else if (key == "roughness") { + auto value = materialJSON.value(key); + if (value.isDouble()) { + material->setRoughness(value.toDouble()); + } + } else if (key == "metallic") { + auto value = materialJSON.value(key); + if (value.isDouble()) { + material->setMetallic(value.toDouble()); + } + } else if (key == "scattering") { + auto value = materialJSON.value(key); + if (value.isDouble()) { + material->setScattering(value.toDouble()); + } + } else if (key == "emissiveMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + material->setEmissiveMap(value.toString()); + } + } else if (key == "albedoMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + bool useAlphaChannel = false; + auto opacityMap = materialJSON.find("opacityMap"); + if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == value.toString()) { + useAlphaChannel = true; + } + material->setAlbedoMap(value.toString(), useAlphaChannel); + } + } else if (key == "roughnessMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + material->setRoughnessMap(value.toString(), false); + } + } else if (key == "glossMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + material->setRoughnessMap(value.toString(), true); + } + } else if (key == "metallicMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + material->setMetallicMap(value.toString(), false); + } + } else if (key == "specularMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + material->setMetallicMap(value.toString(), true); + } + } else if (key == "normalMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + material->setNormalMap(value.toString(), false); + } + } else if (key == "bumpMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + material->setNormalMap(value.toString(), true); + } + } else if (key == "occlusionMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + material->setOcclusionMap(value.toString()); + } + } else if (key == "scatteringMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + material->setScatteringMap(value.toString()); + } + } else if (key == "lightMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + material->setLightmapMap(value.toString()); + } + } + } + return std::pair>(name, material); +} + +MaterialCache& MaterialCache::instance() { + static MaterialCache _instance; + return _instance; +} + +NetworkMaterialResourcePointer MaterialCache::getMaterial(const QUrl& url) { + return ResourceCache::getResource(url, QUrl(), nullptr).staticCast(); +} + +QSharedPointer MaterialCache::createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) { + return QSharedPointer(new NetworkMaterialResource(url), &Resource::deleter); +} \ No newline at end of file diff --git a/libraries/model-networking/src/model-networking/MaterialCache.h b/libraries/model-networking/src/model-networking/MaterialCache.h new file mode 100644 index 0000000000..468a12c677 --- /dev/null +++ b/libraries/model-networking/src/model-networking/MaterialCache.h @@ -0,0 +1,59 @@ +// +// Created by Sam Gondelman on 2/9/2018 +// Copyright 2018 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_MaterialCache_h +#define hifi_MaterialCache_h + +#include + +#include "glm/glm.hpp" + +#include "ModelCache.h" + +class NetworkMaterialResource : public Resource { +public: + NetworkMaterialResource(const QUrl& url); + + QString getType() const override { return "NetworkMaterial"; } + + virtual void downloadFinished(const QByteArray& data) override; + + typedef struct ParsedMaterials { + uint version { 1 }; + std::vector names; + std::unordered_map> networkMaterials; + + void reset() { + version = 1; + names.clear(); + networkMaterials.clear(); + } + + } ParsedMaterials; + + ParsedMaterials parsedMaterials; + + static ParsedMaterials parseJSONMaterials(const QJsonDocument& materialJSON); + static std::pair> parseJSONMaterial(const QJsonObject& materialJSON); + +private: + static bool parseJSONColor(const QJsonValue& array, glm::vec3& color, bool& isSRGB); +}; + +using NetworkMaterialResourcePointer = QSharedPointer; + +class MaterialCache : public ResourceCache { +public: + static MaterialCache& instance(); + + NetworkMaterialResourcePointer getMaterial(const QUrl& url); + +protected: + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) override; +}; + +#endif diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 07f7283bfa..d21e942581 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -425,7 +425,7 @@ bool Geometry::areTexturesLoaded() const { return true; } -const std::shared_ptr Geometry::getShapeMaterial(int partID) const { +const std::shared_ptr Geometry::getShapeMaterial(int partID) const { if ((partID >= 0) && (partID < (int)_meshParts->size())) { int materialID = _meshParts->at(partID)->materialID; if ((materialID >= 0) && (materialID < (int)_materials.size())) { @@ -491,6 +491,15 @@ void GeometryResourceWatcher::resourceRefreshed() { // _instance.reset(); } +NetworkMaterial::NetworkMaterial(const NetworkMaterial& m) : + Material(m), + _textures(m._textures), + _albedoTransform(m._albedoTransform), + _lightmapTransform(m._lightmapTransform), + _lightmapParams(m._lightmapParams), + _isOriginal(m._isOriginal) +{} + const QString NetworkMaterial::NO_TEXTURE = QString(); const QString& NetworkMaterial::getTextureName(MapChannel channel) { @@ -532,19 +541,85 @@ graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl } graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, image::TextureUsage::Type type, MapChannel channel) { - const auto texture = DependencyManager::get()->getTexture(url, type); - _textures[channel].texture = texture; + auto textureCache = DependencyManager::get(); + if (textureCache) { + auto texture = textureCache->getTexture(url, type); + _textures[channel].texture = texture; - auto map = std::make_shared(); - map->setTextureSource(texture->_textureSource); + auto map = std::make_shared(); + if (texture) { + map->setTextureSource(texture->_textureSource); + } - return map; + return map; + } + return nullptr; +} + +void NetworkMaterial::setAlbedoMap(const QString& url, bool useAlphaChannel) { + auto map = fetchTextureMap(QUrl(url), image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); + if (map) { + map->setUseAlphaChannel(useAlphaChannel); + setTextureMap(MapChannel::ALBEDO_MAP, map); + } +} + +void NetworkMaterial::setNormalMap(const QString& url, bool isBumpmap) { + auto map = fetchTextureMap(QUrl(url), isBumpmap ? image::TextureUsage::BUMP_TEXTURE : image::TextureUsage::NORMAL_TEXTURE, MapChannel::NORMAL_MAP); + if (map) { + setTextureMap(MapChannel::NORMAL_MAP, map); + } +} + +void NetworkMaterial::setRoughnessMap(const QString& url, bool isGloss) { + auto map = fetchTextureMap(QUrl(url), isGloss ? image::TextureUsage::GLOSS_TEXTURE : image::TextureUsage::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); + if (map) { + setTextureMap(MapChannel::ROUGHNESS_MAP, map); + } +} + +void NetworkMaterial::setMetallicMap(const QString& url, bool isSpecular) { + auto map = fetchTextureMap(QUrl(url), isSpecular ? image::TextureUsage::SPECULAR_TEXTURE : image::TextureUsage::METALLIC_TEXTURE, MapChannel::METALLIC_MAP); + if (map) { + setTextureMap(MapChannel::METALLIC_MAP, map); + } +} + +void NetworkMaterial::setOcclusionMap(const QString& url) { + auto map = fetchTextureMap(QUrl(url), image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); + if (map) { + setTextureMap(MapChannel::OCCLUSION_MAP, map); + } +} + +void NetworkMaterial::setEmissiveMap(const QString& url) { + auto map = fetchTextureMap(QUrl(url), image::TextureUsage::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); + if (map) { + setTextureMap(MapChannel::EMISSIVE_MAP, map); + } +} + +void NetworkMaterial::setScatteringMap(const QString& url) { + auto map = fetchTextureMap(QUrl(url), image::TextureUsage::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP); + if (map) { + setTextureMap(MapChannel::SCATTERING_MAP, map); + } +} + +void NetworkMaterial::setLightmapMap(const QString& url) { + auto map = fetchTextureMap(QUrl(url), image::TextureUsage::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); + if (map) { + //map->setTextureTransform(_lightmapTransform); + //map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y); + setTextureMap(MapChannel::LIGHTMAP_MAP, map); + } } NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl) : - graphics::Material(*material._material) + graphics::Material(*material._material), + _textures(MapChannel::NUM_MAP_CHANNELS) { - _textures = Textures(MapChannel::NUM_MAP_CHANNELS); + _name = material.name.toStdString(); if (!material.albedoTexture.filename.isEmpty()) { auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); _albedoTransform = material.albedoTexture.transform; @@ -653,6 +728,7 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { if (!occlusionName.isEmpty()) { auto url = textureMap.contains(occlusionName) ? textureMap[occlusionName].toUrl() : QUrl(); + // FIXME: we need to handle the occlusion map transform here auto map = fetchTextureMap(url, image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); setTextureMap(MapChannel::OCCLUSION_MAP, map); } diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index f650b3f2eb..bbb00d72eb 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -48,7 +48,7 @@ public: const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; } const GeometryMeshes& getMeshes() const { return *_meshes; } - const std::shared_ptr getShapeMaterial(int shapeID) const; + const std::shared_ptr getShapeMaterial(int shapeID) const; const QVariantMap getTextures() const; void setTextures(const QVariantMap& textureMap); @@ -131,7 +131,6 @@ private: Geometry::Pointer& _geometryRef; }; - /// Stores cached model geometries. class ModelCache : public ResourceCache, public Dependency { Q_OBJECT @@ -161,7 +160,18 @@ class NetworkMaterial : public graphics::Material { public: using MapChannel = graphics::Material::MapChannel; + NetworkMaterial() : _textures(MapChannel::NUM_MAP_CHANNELS) {} NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl); + NetworkMaterial(const NetworkMaterial& material); + + void setAlbedoMap(const QString& url, bool useAlphaChannel); + void setNormalMap(const QString& url, bool isBumpmap); + void setRoughnessMap(const QString& url, bool isGloss); + void setMetallicMap(const QString& url, bool isSpecular); + void setOcclusionMap(const QString& url); + void setEmissiveMap(const QString& url); + void setScatteringMap(const QString& url); + void setLightmapMap(const QString& url); protected: friend class Geometry; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index fb75123b43..3a29139ee7 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -283,6 +283,8 @@ QSharedPointer TextureCache::createResource(const QUrl& url, const QSh return QSharedPointer(texture, &Resource::deleter); } +int networkTexturePointerMetaTypeId = qRegisterMetaType>(); + NetworkTexture::NetworkTexture(const QUrl& url) : Resource(url), _type(), diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 74395d8948..ffa3150b43 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -163,7 +163,6 @@ public: NetworkTexturePointer getTexture(const QUrl& url, image::TextureUsage::Type type = image::TextureUsage::DEFAULT_TEXTURE, const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); - gpu::TexturePointer getTextureByHash(const std::string& hash); gpu::TexturePointer cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture); diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index 5d39583c9e..115e665b77 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -23,7 +23,7 @@ void ResourceRequest::send() { QMetaObject::invokeMethod(this, "send", Qt::QueuedConnection); return; } - + Q_ASSERT(_state == NotStarted); _state = InProgress; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 6aa3718d50..5c202fa70c 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::SoftEntities); + return static_cast(EntityVersion::MaterialEntities); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::RemovedJurisdictions); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5757cea496..670324c4b7 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -217,7 +217,8 @@ enum class EntityVersion : PacketVersion { OwnershipChallengeFix, ZoneLightInheritModes = 82, ZoneStageRemoved, - SoftEntities + SoftEntities, + MaterialEntities }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index a2aad33058..7108f9a4e4 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -381,6 +381,17 @@ bool OctreePacketData::appendValue(float value) { return success; } +bool OctreePacketData::appendValue(const glm::vec2& value) { + const unsigned char* data = (const unsigned char*)&value; + int length = sizeof(value); + bool success = append(data, length); + if (success) { + _bytesOfValues += length; + _totalBytesOfValues += length; + } + return success; +} + bool OctreePacketData::appendValue(const glm::vec3& value) { const unsigned char* data = (const unsigned char*)&value; int length = sizeof(value); diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index ef4e98798f..8d8f599fea 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -33,6 +33,8 @@ #include #include +#include "MaterialMappingMode.h" + #include "OctreeConstants.h" #include "OctreeElement.h" @@ -162,6 +164,9 @@ public: /// appends a float value to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(float value); + /// appends a vec2 to the end of the stream, may fail if new data stream is too long to fit in packet + bool appendValue(const glm::vec2& value); + /// appends a non-position vector to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const glm::vec3& value); @@ -248,6 +253,7 @@ public: static quint64 getTotalBytesOfColor() { return _totalBytesOfColor; } /// total bytes of color static int unpackDataFromBytes(const unsigned char* dataBytes, float& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, bool& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, quint64& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } @@ -257,6 +263,7 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, rgbColor& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::quat& result) { int bytes = unpackOrientationQuatFromBytes(dataBytes, result); return bytes; } static int unpackDataFromBytes(const unsigned char* dataBytes, ShapeType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, MaterialMappingMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, QString& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result); static int unpackDataFromBytes(const unsigned char* dataBytes, xColor& result); diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index 3d213840dd..41a5bf5faf 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -38,7 +38,7 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(const Transform _cauterizedTransform = renderTransform; } -void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { +void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) && _enableCauterization; if (useCauterizedMesh) { if (_cauterizedClusterBuffer) { @@ -46,7 +46,7 @@ void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::S } batch.setModelTransform(_cauterizedTransform); } else { - ModelMeshPartPayload::bindTransform(batch, locations, renderMode); + ModelMeshPartPayload::bindTransform(batch, renderMode); } } diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h index 2337632047..3c0f90fcb5 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.h +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h @@ -25,7 +25,7 @@ public: void updateTransformForCauterizedMesh(const Transform& renderTransform); - void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; + void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const override; void setEnableCauterization(bool enableCauterization) { _enableCauterization = enableCauterization; } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index a082a681c9..c57bb23f0a 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1318,7 +1318,6 @@ void GeometryCache::renderUnitQuad(gpu::Batch& batch, const glm::vec4& color, in renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color, id); } - void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, const glm::vec2& texCoordMinCorner, const glm::vec2& texCoordMaxCorner, const glm::vec4& color, int id) { diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index da11535396..07822c6103 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -16,6 +16,8 @@ #include "DeferredLightingEffect.h" +#include "RenderPipelines.h" + using namespace render; namespace render { @@ -47,7 +49,7 @@ template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderAr MeshPartPayload::MeshPartPayload(const std::shared_ptr& mesh, int partIndex, graphics::MaterialPointer material) { updateMeshPart(mesh, partIndex); - updateMaterial(material); + addMaterial(graphics::MaterialLayer(material, 0)); } void MeshPartPayload::updateMeshPart(const std::shared_ptr& drawMesh, int partIndex) { @@ -67,8 +69,12 @@ void MeshPartPayload::updateTransform(const Transform& transform, const Transfor _worldBound.transform(_drawTransform); } -void MeshPartPayload::updateMaterial(graphics::MaterialPointer drawMaterial) { - _drawMaterial = drawMaterial; +void MeshPartPayload::addMaterial(graphics::MaterialLayer material) { + _drawMaterials.push(material); +} + +void MeshPartPayload::removeMaterial(graphics::MaterialPointer material) { + _drawMaterials.remove(material); } void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { @@ -89,8 +95,8 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, builder.withSubMetaCulled(); } - if (_drawMaterial) { - auto matKey = _drawMaterial->getKey(); + if (_drawMaterials.top().material) { + auto matKey = _drawMaterials.top().material->getKey(); if (matKey.isTranslucent()) { builder.withTransparent(); } @@ -109,8 +115,8 @@ Item::Bound MeshPartPayload::getBound() const { ShapeKey MeshPartPayload::getShapeKey() const { graphics::MaterialKey drawMaterialKey; - if (_drawMaterial) { - drawMaterialKey = _drawMaterial->getKey(); + if (_drawMaterials.top().material) { + drawMaterialKey = _drawMaterials.top().material->getKey(); } ShapeKey::Builder builder; @@ -143,126 +149,7 @@ void MeshPartPayload::bindMesh(gpu::Batch& batch) { batch.setInputStream(0, _drawMesh->getVertexStream()); } -void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, bool enableTextures) const { - if (!_drawMaterial) { - return; - } - - auto textureCache = DependencyManager::get(); - - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::MATERIAL, _drawMaterial->getSchemaBuffer()); - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::TEXMAPARRAY, _drawMaterial->getTexMapArrayBuffer()); - - const auto& materialKey = _drawMaterial->getKey(); - const auto& textureMaps = _drawMaterial->getTextureMaps(); - - int numUnlit = 0; - if (materialKey.isUnlit()) { - numUnlit++; - } - - if (!enableTextures) { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); - return; - } - - // Albedo - if (materialKey.isAlbedoMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView()); - } else { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture()); - } - } - - // Roughness map - if (materialKey.isRoughnessMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); - } - } - - // Normal map - if (materialKey.isNormalMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); - } - } - - // Metallic map - if (materialKey.isMetallicMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); - } - } - - // Occlusion map - if (materialKey.isOcclusionMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); - } - } - - // Scattering map - if (materialKey.isScatteringMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, itr->second->getTextureView()); - - // texcoord are assumed to be the same has albedo - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); - } - } - - // Emissive / Lightmap - if (materialKey.isLightmapMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); - - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture()); - } - } else if (materialKey.isEmissiveMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); - - if (itr != textureMaps.end() && itr->second->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); - } else { - batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); - } - } -} - -void MeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { +void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { batch.setModelTransform(_drawTransform); } @@ -270,23 +157,21 @@ void MeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::Loca void MeshPartPayload::render(RenderArgs* args) { PerformanceTimer perfTimer("MeshPartPayload::render"); + if (!args) { + return; + } + gpu::Batch& batch = *(args->_batch); - auto locations = args->_shapePipeline->locations; - assert(locations); - // Bind the model transform and the skinCLusterMatrices if needed - bindTransform(batch, locations, args->_renderMode); + bindTransform(batch, args->_renderMode); //Bind the index buffer and vertex buffer and Blend shapes if needed bindMesh(batch); // apply material properties - bindMaterial(batch, locations, args->_enableTexturing); - - if (args) { - args->_details._materialSwitches++; - } + RenderPipelines::bindMaterial(_drawMaterials.top().material, batch, args->_enableTexturing); + args->_details._materialSwitches++; // Draw! { @@ -294,10 +179,8 @@ void MeshPartPayload::render(RenderArgs* args) { drawCall(batch); } - if (args) { - const int INDICES_PER_TRIANGLE = 3; - args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE; - } + const int INDICES_PER_TRIANGLE = 3; + args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE; } namespace render { @@ -379,7 +262,7 @@ void ModelMeshPartPayload::initCache(const ModelPointer& model) { auto networkMaterial = model->getGeometry()->getShapeMaterial(_shapeID); if (networkMaterial) { - _drawMaterial = networkMaterial; + addMaterial(graphics::MaterialLayer(networkMaterial, 0)); } } @@ -429,8 +312,8 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tag builder.withDeformed(); } - if (_drawMaterial) { - auto matKey = _drawMaterial->getKey(); + if (_drawMaterials.top().material) { + auto matKey = _drawMaterials.top().material->getKey(); if (matKey.isTranslucent()) { builder.withTransparent(); } @@ -460,8 +343,8 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe } graphics::MaterialKey drawMaterialKey; - if (_drawMaterial) { - drawMaterialKey = _drawMaterial->getKey(); + if (_drawMaterials.top().material) { + drawMaterialKey = _drawMaterials.top().material->getKey(); } bool isTranslucent = drawMaterialKey.isTranslucent(); @@ -520,7 +403,7 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { } } -void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { +void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const { if (_clusterBuffer) { batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer); } @@ -535,17 +418,14 @@ void ModelMeshPartPayload::render(RenderArgs* args) { } gpu::Batch& batch = *(args->_batch); - auto locations = args->_shapePipeline->locations; - assert(locations); - bindTransform(batch, locations, args->_renderMode); + bindTransform(batch, args->_renderMode); //Bind the index buffer and vertex buffer and Blend shapes if needed bindMesh(batch); // apply material properties - bindMaterial(batch, locations, args->_enableTexturing); - + RenderPipelines::bindMaterial(_drawMaterials.top().material, batch, args->_enableTexturing); args->_details._materialSwitches++; // Draw! diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 40efc67572..7d7b834fc1 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -17,7 +17,6 @@ #include #include -#include #include @@ -40,8 +39,6 @@ public: virtual void notifyLocationChanged() {} void updateTransform(const Transform& transform, const Transform& offsetTransform); - virtual void updateMaterial(graphics::MaterialPointer drawMaterial); - // Render Item interface virtual render::ItemKey getKey() const; virtual render::Item::Bound getBound() const; @@ -51,8 +48,7 @@ public: // ModelMeshPartPayload functions to perform render void drawCall(gpu::Batch& batch) const; virtual void bindMesh(gpu::Batch& batch); - virtual void bindMaterial(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, bool enableTextures) const; - virtual void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const; + virtual void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const; // Payload resource cached values Transform _drawTransform; @@ -65,13 +61,16 @@ public: mutable graphics::Box _worldBound; std::shared_ptr _drawMesh; - std::shared_ptr _drawMaterial; + graphics::MultiMaterial _drawMaterials; graphics::Mesh::Part _drawPart; size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; } - size_t getMaterialTextureSize() { return _drawMaterial ? _drawMaterial->getTextureSize() : 0; } - int getMaterialTextureCount() { return _drawMaterial ? _drawMaterial->getTextureCount() : 0; } - bool hasTextureInfo() const { return _drawMaterial ? _drawMaterial->hasTextureInfo() : false; } + size_t getMaterialTextureSize() { return _drawMaterials.top().material ? _drawMaterials.top().material->getTextureSize() : 0; } + int getMaterialTextureCount() { return _drawMaterials.top().material ? _drawMaterials.top().material->getTextureCount() : 0; } + bool hasTextureInfo() const { return _drawMaterials.top().material ? _drawMaterials.top().material->hasTextureInfo() : false; } + + void addMaterial(graphics::MaterialLayer material); + void removeMaterial(graphics::MaterialPointer material); protected: render::ItemKey _itemKey{ render::ItemKey::Builder::opaqueShape().build() }; @@ -113,7 +112,7 @@ public: // ModelMeshPartPayload functions to perform render void bindMesh(gpu::Batch& batch) override; - void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; + void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const override; void computeAdjustedLocalBound(const std::vector& clusterTransforms); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index bb8353c746..18308d8df5 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -832,6 +832,7 @@ void Model::removeFromScene(const render::ScenePointer& scene, render::Transacti _modelMeshRenderItemIDs.clear(); _modelMeshRenderItemsMap.clear(); _modelMeshRenderItems.clear(); + _modelMeshMaterialNames.clear(); _modelMeshRenderItemShapes.clear(); foreach(auto item, _collisionRenderItemsMap.keys()) { @@ -1460,6 +1461,7 @@ void Model::createVisibleRenderItemSet() { Q_ASSERT(_modelMeshRenderItems.isEmpty()); _modelMeshRenderItems.clear(); + _modelMeshMaterialNames.clear(); _modelMeshRenderItemShapes.clear(); Transform transform; @@ -1483,6 +1485,7 @@ void Model::createVisibleRenderItemSet() { int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); + _modelMeshMaterialNames.push_back(getGeometry()->getShapeMaterial(shapeID)->getName()); _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); shapeID++; } @@ -1528,6 +1531,77 @@ bool Model::isRenderable() const { return !_meshStates.empty() || (isLoaded() && _renderGeometry->getMeshes().empty()); } +std::vector Model::getMeshIDsFromMaterialID(QString parentMaterialName) { + // try to find all meshes with materials that match parentMaterialName as a string + // if none, return parentMaterialName as a uint + std::vector toReturn; + const QString MATERIAL_NAME_PREFIX = "mat::"; + if (parentMaterialName.startsWith(MATERIAL_NAME_PREFIX)) { + parentMaterialName.replace(0, MATERIAL_NAME_PREFIX.size(), QString("")); + for (unsigned int i = 0; i < (unsigned int)_modelMeshMaterialNames.size(); i++) { + if (_modelMeshMaterialNames[i] == parentMaterialName.toStdString()) { + toReturn.push_back(i); + } + } + } + + if (toReturn.empty()) { + toReturn.push_back(parentMaterialName.toUInt()); + } + + return toReturn; +} + +void Model::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { + std::vector shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); + render::Transaction transaction; + for (auto shapeID : shapeIDs) { + if (shapeID < _modelMeshRenderItemIDs.size()) { + auto itemID = _modelMeshRenderItemIDs[shapeID]; + bool visible = isVisible(); + uint8_t viewTagBits = getViewTagBits(); + bool layeredInFront = isLayeredInFront(); + bool layeredInHUD = isLayeredInHUD(); + bool wireframe = isWireframe(); + auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; + bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); + transaction.updateItem(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits, + invalidatePayloadShapeKey, wireframe](ModelMeshPartPayload& data) { + data.addMaterial(material); + // if the material changed, we might need to update our item key or shape key + data.updateKey(visible, layeredInFront || layeredInHUD, viewTagBits); + data.setShapeKey(invalidatePayloadShapeKey, wireframe); + }); + } + } + AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); +} + +void Model::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { + std::vector shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); + render::Transaction transaction; + for (auto shapeID : shapeIDs) { + if (shapeID < _modelMeshRenderItemIDs.size()) { + auto itemID = _modelMeshRenderItemIDs[shapeID]; + bool visible = isVisible(); + uint8_t viewTagBits = getViewTagBits(); + bool layeredInFront = isLayeredInFront(); + bool layeredInHUD = isLayeredInHUD(); + bool wireframe = isWireframe(); + auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; + bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); + transaction.updateItem(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits, + invalidatePayloadShapeKey, wireframe](ModelMeshPartPayload& data) { + data.removeMaterial(material); + // if the material changed, we might need to update our item key or shape key + data.updateKey(visible, layeredInFront || layeredInHUD, viewTagBits); + data.setShapeKey(invalidatePayloadShapeKey, wireframe); + }); + } + } + AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); +} + class CollisionRenderGeometry : public Geometry { public: CollisionRenderGeometry(graphics::MeshPointer mesh) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 57d2798a66..cfcf0a2ce6 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -320,6 +320,9 @@ public: void scaleToFit(); + void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); + void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); + public slots: void loadURLFinished(bool success); @@ -437,6 +440,7 @@ protected: render::ItemIDs _modelMeshRenderItemIDs; using ShapeInfo = struct { int meshIndex; }; std::vector _modelMeshRenderItemShapes; + std::vector _modelMeshMaterialNames; bool _addedToScene { false }; // has been added to scene bool _needsFixupInScene { true }; // needs to be removed/re-added to scene @@ -472,6 +476,8 @@ private: float _loadingPriority { 0.0f }; void calculateTextureInfo(); + + std::vector getMeshIDsFromMaterialID(QString parentMaterialName); }; Q_DECLARE_METATYPE(ModelPointer) diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index ad7409b731..3c80a2d14c 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -618,3 +618,125 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state) { ShapeKey::Filter::Builder().withSkinned().withFade(), skinFadeProgram, state); } + +#include "RenderPipelines.h" +#include + +void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures) { + if (!material) { + return; + } + + auto textureCache = DependencyManager::get(); + + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::MATERIAL, material->getSchemaBuffer()); + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::TEXMAPARRAY, material->getTexMapArrayBuffer()); + + const auto& materialKey = material->getKey(); + const auto& textureMaps = material->getTextureMaps(); + + int numUnlit = 0; + if (materialKey.isUnlit()) { + numUnlit++; + } + + if (!enableTextures) { + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); + return; + } + + // Albedo + if (materialKey.isAlbedoMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView()); + } else { + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture()); + } + } + + // Roughness map + if (materialKey.isRoughnessMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, itr->second->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); + } + } + + // Normal map + if (materialKey.isNormalMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, itr->second->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); + } + } + + // Metallic map + if (materialKey.isMetallicMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, itr->second->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); + } + } + + // Occlusion map + if (materialKey.isOcclusionMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, itr->second->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); + } + } + + // Scattering map + if (materialKey.isScatteringMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, itr->second->getTextureView()); + + // texcoord are assumed to be the same has albedo + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture()); + } + } + + // Emissive / Lightmap + if (materialKey.isLightmapMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); + + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture()); + } + } else if (materialKey.isEmissiveMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); + + if (itr != textureMaps.end() && itr->second->isDefined()) { + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView()); + } else { + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); + } + } +} diff --git a/libraries/render-utils/src/RenderPipelines.h b/libraries/render-utils/src/RenderPipelines.h new file mode 100644 index 0000000000..9b9ad2c001 --- /dev/null +++ b/libraries/render-utils/src/RenderPipelines.h @@ -0,0 +1,22 @@ +// +// RenderPipelines.h +// render-utils/src/ +// +// Created by Sam Gondelman on 2/15/18 +// Copyright 2018 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_RenderPipelines_h +#define hifi_RenderPipelines_h + +#include + +class RenderPipelines { +public: + static void bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures); +}; + + +#endif // hifi_RenderPipelines_h diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index f6ed00b493..92e22d86f6 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -179,4 +179,4 @@ const ShapePipelinePointer ShapePlumber::pickPipeline(RenderArgs* args, const Ke } return shapePipeline; -} +} \ No newline at end of file diff --git a/libraries/shared/src/MaterialMappingMode.cpp b/libraries/shared/src/MaterialMappingMode.cpp new file mode 100644 index 0000000000..1ddad178a2 --- /dev/null +++ b/libraries/shared/src/MaterialMappingMode.cpp @@ -0,0 +1,24 @@ +// +// Created by Sam Gondelman on 1/17/2018 +// Copyright 2018 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 "MaterialMappingMode.h" + +const char* materialMappingModeNames[] = { + "uv", + "projected" +}; + +static const size_t MATERIAL_MODE_NAMES = (sizeof(materialMappingModeNames) / sizeof((materialMappingModeNames)[0])); + +QString MaterialMappingModeHelpers::getNameForMaterialMappingMode(MaterialMappingMode mode) { + if (((int)mode <= 0) || ((int)mode >= (int)MATERIAL_MODE_NAMES)) { + mode = (MaterialMappingMode)0; + } + + return materialMappingModeNames[(int)mode]; +} \ No newline at end of file diff --git a/libraries/shared/src/MaterialMappingMode.h b/libraries/shared/src/MaterialMappingMode.h new file mode 100644 index 0000000000..848c2aa28c --- /dev/null +++ b/libraries/shared/src/MaterialMappingMode.h @@ -0,0 +1,25 @@ +// +// Created by Sam Gondelman on 1/12/18. +// Copyright 2018 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_MaterialMappingMode_h +#define hifi_MaterialMappingMode_h + +#include "QString" + +enum MaterialMappingMode { + UV = 0, + PROJECTED +}; + +class MaterialMappingModeHelpers { +public: + static QString getNameForMaterialMappingMode(MaterialMappingMode mode); +}; + +#endif // hifi_MaterialMappingMode_h + diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 25b2cec331..4dbbd190ff 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -256,6 +256,13 @@ namespace std { return result; } }; + + template <> + struct hash { + size_t operator()(const QString& a) const { + return qHash(a); + } + }; } enum ContactEventType { diff --git a/scripts/developer/tests/toolbarTest.js b/scripts/developer/tests/toolbarTest.js index e21fbd8e19..b713445927 100644 --- a/scripts/developer/tests/toolbarTest.js +++ b/scripts/developer/tests/toolbarTest.js @@ -11,7 +11,8 @@ var toolBar = (function() { newTextButton, newWebButton, newZoneButton, - newParticleButton + newParticleButton, + newMaterialButton var toolIconUrl = Script.resolvePath("../../system/assets/images/tools/"); @@ -89,6 +90,13 @@ var toolBar = (function() { visible: false }); + newMaterialButton = toolBar.addButton({ + objectName: "newMaterialButton", + imageURL: toolIconUrl + "material-01.svg", + alpha: 0.9, + visible: false + }); + that.setActive(false); newModelButton.clicked(); } @@ -109,8 +117,8 @@ var toolBar = (function() { newTextButton.writeProperty('visible', doShow); newWebButton.writeProperty('visible', doShow); newZoneButton.writeProperty('visible', doShow); - newModelButton.writeProperty('visible', doShow); newParticleButton.writeProperty('visible', doShow); + newMaterialButton.writeProperty('visible', doShow); }; initialize(); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 42952bbff0..0167b55810 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -251,8 +251,7 @@ var toolBar = (function () { // Align entity with Avatar orientation. properties.rotation = MyAvatar.orientation; - var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; - + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web", "Material"]; if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box prior to creating it. @@ -343,7 +342,7 @@ var toolBar = (function () { var buttonHandlers = {}; // only used to tablet mode - function addButton(name, image, handler) { + function addButton(name, handler) { buttonHandlers[name] = handler; } @@ -355,6 +354,9 @@ var toolBar = (function () { var SHAPE_TYPE_SPHERE = 5; var DYNAMIC_DEFAULT = false; + var MATERIAL_MODE_UV = 0; + var MATERIAL_MODE_PROJECTED = 1; + function handleNewModelDialogResult(result) { if (result) { var url = result.textInput; @@ -395,6 +397,30 @@ var toolBar = (function () { } } + function handleNewMaterialDialogResult(result) { + if (result) { + var materialURL = result.textInput; + //var materialMappingMode; + //switch (result.comboBox) { + // case MATERIAL_MODE_PROJECTED: + // materialMappingMode = "projected"; + // break; + // default: + // shapeType = "uv"; + //} + + var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; + if (materialURL) { + createNewEntity({ + type: "Material", + materialURL: materialURL, + //materialMappingMode: materialMappingMode, + priority: DEFAULT_LAYERED_MATERIAL_PRIORITY + }); + } + } + } + function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); tablet.popFromStack(); @@ -405,6 +431,9 @@ var toolBar = (function () { case "newEntityButtonClicked": buttonHandlers[message.params.buttonName](); break; + case "newMaterialDialogAdd": + handleNewMaterialDialogResult(message.params); + break; } } @@ -448,32 +477,22 @@ var toolBar = (function () { that.toggle(); }); - addButton("importEntitiesButton", "assets-01.svg", function() { + addButton("importEntitiesButton", function() { Window.browseChanged.connect(onFileOpenChanged); Window.browseAsync("Select Model to Import", "", "*.json"); }); - addButton("openAssetBrowserButton", "assets-01.svg", function() { + addButton("openAssetBrowserButton", function() { Window.showAssetServer(); }); - addButton("newModelButton", "model-01.svg", function () { - - var SHAPE_TYPES = []; - SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; - SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; - SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; - SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; - SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; - SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; - var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; - + addButton("newModelButton", function () { // tablet version of new-model dialog var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); tablet.pushOntoStack("hifi/tablet/NewModelDialog.qml"); }); - addButton("newCubeButton", "cube-01.svg", function () { + addButton("newCubeButton", function () { createNewEntity({ type: "Box", dimensions: DEFAULT_DIMENSIONS, @@ -485,7 +504,7 @@ var toolBar = (function () { }); }); - addButton("newSphereButton", "sphere-01.svg", function () { + addButton("newSphereButton", function () { createNewEntity({ type: "Sphere", dimensions: DEFAULT_DIMENSIONS, @@ -497,7 +516,7 @@ var toolBar = (function () { }); }); - addButton("newLightButton", "light-01.svg", function () { + addButton("newLightButton", function () { createNewEntity({ type: "Light", dimensions: DEFAULT_LIGHT_DIMENSIONS, @@ -516,7 +535,7 @@ var toolBar = (function () { }); }); - addButton("newTextButton", "text-01.svg", function () { + addButton("newTextButton", function () { createNewEntity({ type: "Text", dimensions: { @@ -539,7 +558,7 @@ var toolBar = (function () { }); }); - addButton("newImageButton", "web-01.svg", function () { + addButton("newImageButton", function () { var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; createNewEntity({ @@ -555,7 +574,7 @@ var toolBar = (function () { }); }); - addButton("newWebButton", "web-01.svg", function () { + addButton("newWebButton", function () { createNewEntity({ type: "Web", dimensions: { @@ -567,7 +586,7 @@ var toolBar = (function () { }); }); - addButton("newZoneButton", "zone-01.svg", function () { + addButton("newZoneButton", function () { createNewEntity({ type: "Zone", dimensions: { @@ -578,7 +597,7 @@ var toolBar = (function () { }); }); - addButton("newParticleButton", "particle-01.svg", function () { + addButton("newParticleButton", function () { createNewEntity({ type: "ParticleEffect", isEmitting: true, @@ -631,6 +650,12 @@ var toolBar = (function () { }); }); + addButton("newMaterialButton", function () { + // tablet version of new material dialog + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.pushOntoStack("hifi/tablet/NewMaterialDialog.qml"); + }); + that.setActive(false); } @@ -1999,7 +2024,7 @@ var PropertiesTool = function (opts) { ); } Entities.editEntity(selectionManager.selections[0], data.properties); - if (data.properties.name !== undefined || data.properties.modelURL !== undefined || + if (data.properties.name !== undefined || data.properties.modelURL !== undefined || data.properties.materialURL !== undefined || data.properties.visible !== undefined || data.properties.locked !== undefined) { entityListTool.sendUpdate(); } @@ -2010,7 +2035,7 @@ var PropertiesTool = function (opts) { parentSelectedEntities(); } else if (data.type === 'unparent') { unparentSelectedEntities(); - } else if (data.type === 'saveUserData'){ + } else if (data.type === 'saveUserData') { //the event bridge and json parsing handle our avatar id string differently. var actualID = data.id.split('"')[1]; Entities.editEntity(actualID, data.properties); diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 58acc317bd..7664ae8714 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -448,12 +448,10 @@ input[type=checkbox]:checked + label:hover { border: 1.5pt solid black; } -.shape-section, .light-section, .model-section, .web-section, .image-section, .hyperlink-section, .text-section, .zone-section { +.shape-section, .light-section, .model-section, .web-section, .image-section, .hyperlink-section, .text-section, .zone-section, .material-section { display: table; } - - #properties-list fieldset { position: relative; /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ @@ -571,6 +569,7 @@ hr { .physical-group[collapsed="true"] ~ .physical-group, .behavior-group[collapsed="true"] ~ .behavior-group, .model-group[collapsed="true"] ~ .model-group, +.material-group[collapsed="true"] ~ .material-group, .light-group[collapsed="true"] ~ .light-group { display: none !important; } @@ -645,14 +644,14 @@ hr { margin-left: 10px; } -.text label, .url label, .number label, .textarea label, .rgb label, .xyz label, .pyr label, .dropdown label, .gen label { +.text label, .url label, .number label, .textarea label, .xy label, .wh label, .rgb label, .xyz label,.pyr label, .dropdown label, .gen label { float: left; margin-left: 1px; margin-bottom: 3px; margin-top: -2px; } -.text legend, .url legend, .number legend, .textarea legend, .rgb legend, .xyz legend, .pyr legend, .dropdown legend, .gen legend { +.text legend, .url legend, .number legend, .textarea legend, .xy legend, .wh legend, .rgb legend, .xyz legend, .pyr legend, .dropdown legend, .gen legend { float: left; margin-left: 1px; margin-bottom: 3px; @@ -667,7 +666,7 @@ hr { clear: both; float: left; } -.xyz > div, .pyr > div, .gen > div { +.xy > div, .wh > div, .xyz > div, .pyr > div, .gen > div { clear: both; } @@ -841,6 +840,12 @@ div.refresh input[type="button"] { margin-right: -6px; } +.xy .tuple input { + padding-left: 25px; +} +.wh .tuple input { + padding-left: 45px; +} .rgb .tuple input { padding-left: 65px; } @@ -1387,7 +1392,7 @@ input#reset-to-natural-dimensions { } -#static-userdata{ +#static-userdata { display: none; z-index: 99; position: absolute; @@ -1398,7 +1403,7 @@ input#reset-to-natural-dimensions { background-color: #2e2e2e; } -#userdata-saved{ +#userdata-saved { margin-top:5px; font-size:16px; display:none; @@ -1455,6 +1460,9 @@ input#reset-to-natural-dimensions { order: 6; } +#properties-list.ShapeMenu #material, +#properties-list.BoxMenu #material, +#properties-list.SphereMenu #material, #properties-list.ShapeMenu #light, #properties-list.BoxMenu #light, #properties-list.SphereMenu #light, @@ -1494,6 +1502,7 @@ input#reset-to-natural-dimensions { } /* items to hide */ +#properties-list.ParticleEffectMenu #material, #properties-list.ParticleEffectMenu #base-color-section, #properties-list.ParticleEffectMenu #hyperlink, #properties-list.ParticleEffectMenu #light, @@ -1529,6 +1538,7 @@ input#reset-to-natural-dimensions { order: 7; } /* sections to hide */ +#properties-list.LightMenu #material, #properties-list.LightMenu #model, #properties-list.LightMenu #zone, #properties-list.LightMenu #text, @@ -1566,6 +1576,7 @@ input#reset-to-natural-dimensions { order: 7; } /* sections to hide */ +#properties-list.ModelMenu #material, #properties-list.ModelMenu #light, #properties-list.ModelMenu #zone, #properties-list.ModelMenu #text, @@ -1603,6 +1614,7 @@ input#reset-to-natural-dimensions { order: 7; } /* sections to hide */ +#properties-list.ZoneMenu #material, #properties-list.ZoneMenu #light, #properties-list.ZoneMenu #model, #properties-list.ZoneMenu #text, @@ -1640,6 +1652,7 @@ input#reset-to-natural-dimensions { order: 7; } /* sections to hide */ +#properties-list.ImageMenu #material, #properties-list.ImageMenu #light, #properties-list.ImageMenu #model, #properties-list.ImageMenu #zone, @@ -1677,6 +1690,7 @@ input#reset-to-natural-dimensions { order: 7; } /* sections to hide */ +#properties-list.WebMenu #material, #properties-list.WebMenu #light, #properties-list.WebMenu #model, #properties-list.WebMenu #zone, @@ -1715,6 +1729,7 @@ input#reset-to-natural-dimensions { order: 7; } /* sections to hide */ +#properties-list.TextMenu #material, #properties-list.TextMenu #light, #properties-list.TextMenu #model, #properties-list.TextMenu #zone, @@ -1728,6 +1743,40 @@ input#reset-to-natural-dimensions { display: none } +/* ----- Order of Menu items for Material ----- */ +#properties-list.MaterialMenu #general { + order: 1; +} +#properties-list.MaterialMenu #material { + order: 2; +} +#properties-list.MaterialMenu #spatial { + order: 3; +} +#properties-list.MaterialMenu #hyperlink { + order: 4; +} +#properties-list.MaterialMenu #behavior { + order: 5; +} + +/* sections to hide */ +#properties-list.MaterialMenu #physical, +#properties-list.MaterialMenu #collision-info, +#properties-list.MaterialMenu #model, +#properties-list.MaterialMenu #light, +#properties-list.MaterialMenu #zone, +#properties-list.MaterialMenu #text, +#properties-list.MaterialMenu #web, +#properties-list.MaterialMenu #image { + display: none; +} +/* items to hide */ +#properties-list.MaterialMenu #shape-list, +#properties-list.MaterialMenu #base-color-section { + display: none +} + /* Currently always hidden */ #properties-list #polyvox { diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 8f23d6d829..c53a2fa5bd 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -770,6 +770,71 @@ +
+ + MaterialM + +
+
+ + +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+
+ +
+
+ diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index b27c852974..41abc00cff 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -25,7 +25,8 @@ var ICON_FOR_TYPE = { Zone: "o", PolyVox: "", Multiple: "", - PolyLine: "" + PolyLine: "", + Material: "" }; var EDITOR_TIMEOUT_DURATION = 1500; @@ -33,6 +34,8 @@ var KEY_P = 80; // Key code for letter p used for Parenting hotkey. var colorPickers = []; var lastEntityID = null; +var MATERIAL_PREFIX_STRING = "mat::"; + function debugPrint(message) { EventBridge.emitWebEvent( JSON.stringify({ @@ -78,7 +81,6 @@ function disableProperties() { if ($('#userdata-editor').css('display') === "block" && elLocked.checked === true) { showStaticUserData(); } - } function showElements(els, show) { @@ -174,6 +176,17 @@ function createEmitGroupTextPropertyUpdateFunction(group, propertyName) { }; } +function createEmitVec2PropertyUpdateFunction(property, elX, elY) { + return function () { + var properties = {}; + properties[property] = { + x: elX.value, + y: elY.value + }; + updateProperties(properties); + }; +} + function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { return function() { var properties = {}; @@ -480,7 +493,6 @@ function bindAllNonJSONEditorElements() { } else { if ($('#userdata-editor').css('height') !== "0px") { saveJSONUserData(true); - } } }); @@ -506,6 +518,18 @@ function clearSelection() { } } +function showParentMaterialNameBox(number, elNumber, elString) { + if (number) { + $('#property-parent-material-id-number-container').show(); + $('#property-parent-material-id-string-container').hide(); + elString.value = ""; + } else { + $('#property-parent-material-id-string-container').show(); + $('#property-parent-material-id-number-container').hide(); + elNumber.value = 0; + } +} + function loaded() { openEventBridge(function() { @@ -628,6 +652,18 @@ function loaded() { var elModelTextures = document.getElementById("property-model-textures"); var elModelOriginalTextures = document.getElementById("property-model-original-textures"); + var elMaterialURL = document.getElementById("property-material-url"); + //var elMaterialMappingMode = document.getElementById("property-material-mapping-mode"); + var elPriority = document.getElementById("property-priority"); + var elParentMaterialNameString = document.getElementById("property-parent-material-id-string"); + var elParentMaterialNameNumber = document.getElementById("property-parent-material-id-number"); + var elParentMaterialNameCheckbox = document.getElementById("property-parent-material-id-checkbox"); + var elMaterialMappingPosX = document.getElementById("property-material-mapping-pos-x"); + var elMaterialMappingPosY = document.getElementById("property-material-mapping-pos-y"); + var elMaterialMappingScaleX = document.getElementById("property-material-mapping-scale-x"); + var elMaterialMappingScaleY = document.getElementById("property-material-mapping-scale-y"); + var elMaterialMappingRot = document.getElementById("property-material-mapping-rot"); + var elImageURL = document.getElementById("property-image-url"); var elWebSourceURL = document.getElementById("property-web-source-url"); @@ -1127,6 +1163,25 @@ function loaded() { elXTextureURL.value = properties.xTextureURL; elYTextureURL.value = properties.yTextureURL; elZTextureURL.value = properties.zTextureURL; + } else if (properties.type === "Material") { + elMaterialURL.value = properties.materialURL; + //elMaterialMappingMode.value = properties.materialMappingMode; + //setDropdownText(elMaterialMappingMode); + elPriority.value = properties.priority; + if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { + elParentMaterialNameString.value = properties.parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); + showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); + elParentMaterialNameCheckbox.checked = false; + } else { + elParentMaterialNameNumber.value = parseInt(properties.parentMaterialName); + showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); + elParentMaterialNameCheckbox.checked = true; + } + elMaterialMappingPosX.value = properties.materialMappingPos.x.toFixed(4); + elMaterialMappingPosY.value = properties.materialMappingPos.y.toFixed(4); + elMaterialMappingScaleX.value = properties.materialMappingScale.x.toFixed(4); + elMaterialMappingScaleY.value = properties.materialMappingScale.y.toFixed(4); + elMaterialMappingRot.value = properties.materialMappingRot.toFixed(2); } if (properties.locked) { @@ -1399,6 +1454,30 @@ function loaded() { elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); + elMaterialURL.addEventListener('change', createEmitTextPropertyUpdateFunction('materialURL')); + //elMaterialMappingMode.addEventListener('change', createEmitTextPropertyUpdateFunction('materialMappingMode')); + elPriority.addEventListener('change', createEmitNumberPropertyUpdateFunction('priority', 0)); + + elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); + elParentMaterialNameNumber.addEventListener('change', function () { updateProperty("parentMaterialName", this.value); }); + elParentMaterialNameCheckbox.addEventListener('change', function () { + if (this.checked) { + updateProperty("parentMaterialName", elParentMaterialNameNumber.value); + showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); + } else { + updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + elParentMaterialNameString.value); + showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); + } + }); + + var materialMappingPosChangeFunction = createEmitVec2PropertyUpdateFunction('materialMappingPos', elMaterialMappingPosX, elMaterialMappingPosY); + elMaterialMappingPosX.addEventListener('change', materialMappingPosChangeFunction); + elMaterialMappingPosY.addEventListener('change', materialMappingPosChangeFunction); + var materialMappingScaleChangeFunction = createEmitVec2PropertyUpdateFunction('materialMappingScale', elMaterialMappingScaleX, elMaterialMappingScaleY); + elMaterialMappingScaleX.addEventListener('change', materialMappingScaleChangeFunction); + elMaterialMappingScaleY.addEventListener('change', materialMappingScaleChangeFunction); + elMaterialMappingRot.addEventListener('change', createEmitNumberPropertyUpdateFunction('materialMappingRot', 2)); + elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text')); elTextFaceCamera.addEventListener('change', createEmitCheckedPropertyUpdateFunction('faceCamera')); elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight')); diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 9d9689000e..d53766ab4e 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -77,18 +77,24 @@ EntityListTool = function(opts) { var properties = Entities.getEntityProperties(id); if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { + var url = ""; + if (properties.type == "Model") { + url = properties.modelURL; + } else if (properties.type == "Material") { + url = properties.materialURL; + } entities.push({ id: id, name: properties.name, type: properties.type, - url: properties.type == "Model" ? properties.modelURL : "", + url: url, 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), - isBaked: properties.type == "Model" ? properties.modelURL.toLowerCase().endsWith(".baked.fbx") : false, + isBaked: properties.type == "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, drawCalls: valueIfDefined(properties.renderInfo.drawCalls), hasScript: properties.script !== "" });