diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index ad337a6361..247a42428a 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -189,15 +189,17 @@ Windows.ScrollingWindow { var grabbable = MenuInterface.isOptionChecked("Create Entities As Grabbable (except Zones, Particles, and Lights)"); if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { - var name = assetProxyModel.data(treeView.selection.currentIndex); - var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; - var textures = JSON.stringify({ "tex.picture": defaultURL}); - var shapeType = "box"; - var dynamic = false; - var collisionless = true; - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); - var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); - Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, grabbable, position, gravity); + Entities.addEntity({ + type: "Image", + name: assetProxyModel.data(treeView.selection.currentIndex), + imageURL: defaultURL, + keepAspectRatio: false, + dynamic: false, + collisionless: true, + grabbable: grabbable, + position: Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))), + gravity: Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0) + }); } else { var SHAPE_TYPE_NONE = 0; var SHAPE_TYPE_SIMPLE_HULL = 1; diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index f665032b01..b5374b2fe0 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -189,15 +189,17 @@ Rectangle { var grabbable = MenuInterface.isOptionChecked("Create Entities As Grabbable (except Zones, Particles, and Lights)"); if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { - var name = assetProxyModel.data(treeView.selection.currentIndex); - var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; - var textures = JSON.stringify({ "tex.picture": defaultURL}); - var shapeType = "box"; - var dynamic = false; - var collisionless = true; - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); - var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); - Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, grabbable, position, gravity); + Entities.addEntity({ + type: "Image", + name: assetProxyModel.data(treeView.selection.currentIndex), + imageURL: defaultURL, + keepAspectRatio: false, + dynamic: false, + collisionless: true, + grabbable: grabbable, + position: Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))), + gravity: Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0) + }); } else { var SHAPE_TYPE_NONE = 0; var SHAPE_TYPE_SIMPLE_HULL = 1; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 21af28ffcb..46b25a8f03 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2367,6 +2367,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); + EntityTreeRenderer::setGetAvatarUpOperator([] { + return DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; + }); + // Preload Tablet sounds DependencyManager::get()->preloadSounds(); DependencyManager::get()->createKeyboard(); @@ -7623,16 +7627,13 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) { EntityItemProperties properties; - properties.setType(EntityTypes::Model); properties.setName(mapping.right(mapping.length() - 1)); if (filePath.toLower().endsWith(PNG_EXTENSION) || filePath.toLower().endsWith(JPG_EXTENSION)) { - QJsonObject textures { - {"tex.picture", QString("atp:" + mapping) } - }; - properties.setModelURL("https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"); - properties.setTextures(QJsonDocument(textures).toJson(QJsonDocument::Compact)); - properties.setShapeType(SHAPE_TYPE_BOX); + properties.setType(EntityTypes::Image); + properties.setImageURL(QString("atp:" + mapping)); + properties.setKeepAspectRatio(false); } else { + properties.setType(EntityTypes::Model); properties.setModelURL("atp:" + mapping); properties.setShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); } diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index e24e3b3ed8..d3d0c873a4 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -82,18 +82,21 @@ void Image3DOverlay::render(RenderArgs* args) { float imageHeight = _texture->getHeight(); QRect fromImage; - if (_fromImage.isNull()) { + if (_fromImage.width() <= 0) { fromImage.setX(0); - fromImage.setY(0); fromImage.setWidth(imageWidth); - fromImage.setHeight(imageHeight); } else { float scaleX = imageWidth / _texture->getOriginalWidth(); - float scaleY = imageHeight / _texture->getOriginalHeight(); - fromImage.setX(scaleX * _fromImage.x()); - fromImage.setY(scaleY * _fromImage.y()); fromImage.setWidth(scaleX * _fromImage.width()); + } + + if (_fromImage.height() <= 0) { + fromImage.setY(0); + fromImage.setHeight(imageHeight); + } else { + float scaleY = imageHeight / _texture->getOriginalHeight(); + fromImage.setY(scaleY * _fromImage.y()); fromImage.setHeight(scaleY * _fromImage.height()); } @@ -247,9 +250,6 @@ QVariant Image3DOverlay::getProperty(const QString& property) { if (property == "subImage") { return _fromImage; } - if (property == "offsetPosition") { - return vec3toVariant(getOffsetPosition()); - } if (property == "emissive") { return _emissive; } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 980ff8834c..5fb94dd323 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -41,6 +41,7 @@ #include std::function EntityTreeRenderer::_entitiesShouldFadeFunction = []() { return true; }; +std::function EntityTreeRenderer::_getAvatarUpOperator = []() { return Vectors::UP; }; QString resolveScriptURL(const QString& scriptUrl) { auto normalizedScriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 4ba1a0060b..243dfd9813 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -117,6 +117,9 @@ public: // Access the workload Space workload::SpacePointer getWorkloadSpace() const { return _space; } + static void setGetAvatarUpOperator(std::function getAvatarUpOperator) { _getAvatarUpOperator = getAvatarUpOperator; } + static glm::vec3 getAvatarUp() { return _getAvatarUpOperator(); } + signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); @@ -262,6 +265,8 @@ private: mutable std::mutex _spaceLock; workload::SpacePointer _space{ new workload::Space() }; workload::Transaction::Updates _spaceUpdates; + + static std::function _getAvatarUpOperator; }; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index aa335bb7d5..92387dafa6 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -25,6 +25,8 @@ #include "RenderableWebEntityItem.h" #include "RenderableZoneEntityItem.h" #include "RenderableMaterialEntityItem.h" +#include "RenderableImageEntityItem.h" +#include "RenderableGridEntityItem.h" using namespace render; @@ -214,22 +216,39 @@ EntityRenderer::Pointer EntityRenderer::addToScene(EntityTreeRenderer& renderer, using Type = EntityTypes::EntityType_t; auto type = entity->getType(); switch (type) { - case Type::Light: - result = make_renderer(entity); - break; - case Type::Line: - result = make_renderer(entity); + case Type::Shape: + case Type::Box: + case Type::Sphere: + result = make_renderer(entity); break; case Type::Model: result = make_renderer(entity); break; + case Type::Text: + result = make_renderer(entity); + break; + + case Type::Image: + result = make_renderer(entity); + break; + + case Type::Web: + if (!nsightActive()) { + result = make_renderer(entity); + } + break; + case Type::ParticleEffect: result = make_renderer(entity); break; + case Type::Line: + result = make_renderer(entity); + break; + case Type::PolyLine: result = make_renderer(entity); break; @@ -238,20 +257,12 @@ EntityRenderer::Pointer EntityRenderer::addToScene(EntityTreeRenderer& renderer, result = make_renderer(entity); break; - case Type::Shape: - case Type::Box: - case Type::Sphere: - result = make_renderer(entity); + case Type::Grid: + result = make_renderer(entity); break; - case Type::Text: - result = make_renderer(entity); - break; - - case Type::Web: - if (!nsightActive()) { - result = make_renderer(entity); - } + case Type::Light: + result = make_renderer(entity); break; case Type::Zone: diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp new file mode 100644 index 0000000000..bd42a722fb --- /dev/null +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp @@ -0,0 +1,51 @@ +// +// Created by Sam Gondelman on 11/29/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 "RenderableGridEntityItem.h" + +using namespace render; +using namespace render::entities; + +GridEntityRenderer::GridEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { +} + +bool GridEntityRenderer::needsRenderUpdate() const { + return Parent::needsRenderUpdate(); +} + +bool GridEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { + return false; +} + +void GridEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { + +} + +ItemKey GridEntityRenderer::getKey() { + ItemKey::Builder builder; + builder.withTypeShape().withTypeMeta().withTagBits(getTagMask()); + + withReadLock([&] { + if (isTransparent()) { + builder.withTransparent(); + } else if (_canCastShadow) { + builder.withShadowCaster(); + } + }); + + return builder.build(); +} + +ShapeKey GridEntityRenderer::getShapeKey() { + ShapeKey::Builder builder; + return builder.build(); +} + +void GridEntityRenderer::doRender(RenderArgs* args) { + +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.h b/libraries/entities-renderer/src/RenderableGridEntityItem.h new file mode 100644 index 0000000000..09c0de1d0d --- /dev/null +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.h @@ -0,0 +1,36 @@ +// +// Created by Sam Gondelman on 11/29/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_RenderableGridEntityItem_h +#define hifi_RenderableGridEntityItem_h + +#include "RenderableEntityItem.h" + +#include + +namespace render { namespace entities { + +class GridEntityRenderer : public TypedEntityRenderer { + using Parent = TypedEntityRenderer; + using Pointer = std::shared_ptr; +public: + GridEntityRenderer(const EntityItemPointer& entity); + +protected: + ItemKey getKey() override; + ShapeKey getShapeKey() override; + +private: + virtual bool needsRenderUpdate() const override; + 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; +}; + +} } +#endif // hifi_RenderableGridEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp new file mode 100644 index 0000000000..7c5b7fc0da --- /dev/null +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp @@ -0,0 +1,218 @@ +// +// Created by Sam Gondelman on 11/29/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 "RenderableImageEntityItem.h" + +#include +#include + +using namespace render; +using namespace render::entities; + +ImageEntityRenderer::ImageEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { + _geometryId = DependencyManager::get()->allocateID(); +} + +ImageEntityRenderer::~ImageEntityRenderer() { + auto geometryCache = DependencyManager::get(); + if (geometryCache) { + geometryCache->releaseID(_geometryId); + } +} + +bool ImageEntityRenderer::isTransparent() const { + return Parent::isTransparent() || (_textureIsLoaded && _texture->getGPUTexture() && _texture->getGPUTexture()->getUsage().isAlpha()) || _alpha < 1.0f; +} + +bool ImageEntityRenderer::needsRenderUpdate() const { + bool textureLoadedChanged = resultWithReadLock([&] { + return (!_textureIsLoaded && _texture && _texture->isLoaded()); + }); + + if (textureLoadedChanged) { + return true; + } + + return Parent::needsRenderUpdate(); +} + +bool ImageEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { + bool needsUpdate = resultWithReadLock([&] { + if (_imageURL != entity->getImageURL()) { + return true; + } + + if (_emissive != entity->getEmissive()) { + return true; + } + + if (_keepAspectRatio != entity->getKeepAspectRatio()) { + return true; + } + + if (_billboardMode != entity->getBillboardMode()) { + return true; + } + + if (_subImage != entity->getSubImage()) { + return true; + } + + if (_color != entity->getColor()) { + return true; + } + + if (_alpha != entity->getAlpha()) { + return true; + } + + return false; + }); + + return needsUpdate; +} + +void ImageEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { + withWriteLock([&] { + auto imageURL = entity->getImageURL(); + if (_imageURL != imageURL) { + _imageURL = imageURL; + if (imageURL.isEmpty()) { + _texture.reset(); + } else { + _texture = DependencyManager::get()->getTexture(_imageURL); + } + _textureIsLoaded = false; + } + + _emissive = entity->getEmissive(); + _keepAspectRatio = entity->getKeepAspectRatio(); + _billboardMode = entity->getBillboardMode(); + _subImage = entity->getSubImage(); + + _color = entity->getColor(); + _alpha = entity->getAlpha(); + + if (!_textureIsLoaded && _texture && _texture->isLoaded()) { + _textureIsLoaded = true; + } + }); + + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() { + withWriteLock([&] { + _dimensions = entity->getScaledDimensions(); + updateModelTransformAndBound(); + _renderTransform = getModelTransform(); + }); + }); +} + +ShapeKey ImageEntityRenderer::getShapeKey() { + auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias(); + if (isTransparent()) { + builder.withTranslucent(); + } + + withReadLock([&] { + if (_emissive) { + builder.withUnlit(); + } + }); + + return builder.build(); +} + +void ImageEntityRenderer::doRender(RenderArgs* args) { + NetworkTexturePointer texture; + QRect subImage; + glm::u8vec3 color; + glm::vec3 dimensions; + Transform transform; + withReadLock([&] { + texture = _texture; + subImage = _subImage; + color = _color; + dimensions = _dimensions; + transform = _renderTransform; + }); + + if (!_visible || !texture || !texture->isLoaded()) { + return; + } + + Q_ASSERT(args->_batch); + gpu::Batch* batch = args->_batch; + + if (_billboardMode == BillboardMode::YAW) { + //rotate about vertical to face the camera + glm::vec3 dPosition = args->getViewFrustum().getPosition() - transform.getTranslation(); + // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees + float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); + glm::quat orientation = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); + transform.setRotation(orientation); + } else if (_billboardMode == BillboardMode::FULL) { + glm::vec3 billboardPos = transform.getTranslation(); + glm::vec3 cameraPos = args->getViewFrustum().getPosition(); + // use the referencial from the avatar, y isn't always up + glm::vec3 avatarUP = EntityTreeRenderer::getAvatarUp(); + // check to see if glm::lookAt will work / using glm::lookAt variable name + glm::highp_vec3 s(glm::cross(billboardPos - cameraPos, avatarUP)); + + // make sure s is not NaN for any component + if (glm::length2(s) > 0.0f) { + glm::quat rotation(conjugate(toQuat(glm::lookAt(cameraPos, billboardPos, avatarUP)))); + transform.setRotation(rotation); + } + } + transform.postScale(dimensions); + + batch->setModelTransform(transform); + batch->setResourceTexture(0, texture->getGPUTexture()); + + float imageWidth = texture->getWidth(); + float imageHeight = texture->getHeight(); + + QRect fromImage; + if (subImage.width() <= 0) { + fromImage.setX(0); + fromImage.setWidth(imageWidth); + } else { + float scaleX = imageWidth / texture->getOriginalWidth(); + fromImage.setX(scaleX * subImage.x()); + fromImage.setWidth(scaleX * subImage.width()); + } + + if (subImage.height() <= 0) { + fromImage.setY(0); + fromImage.setHeight(imageHeight); + } else { + float scaleY = imageHeight / texture->getOriginalHeight(); + fromImage.setY(scaleY * subImage.y()); + fromImage.setHeight(scaleY * subImage.height()); + } + + float maxSize = glm::max(fromImage.width(), fromImage.height()); + float x = _keepAspectRatio ? fromImage.width() / (2.0f * maxSize) : 0.5f; + float y = _keepAspectRatio ? -fromImage.height() / (2.0f * maxSize) : -0.5f; + + glm::vec2 topLeft(-x, -y); + glm::vec2 bottomRight(x, y); + glm::vec2 texCoordTopLeft((fromImage.x() + 0.5f) / imageWidth, (fromImage.y() + 0.5f) / imageHeight); + glm::vec2 texCoordBottomRight((fromImage.x() + fromImage.width() - 0.5f) / imageWidth, + (fromImage.y() + fromImage.height() - 0.5f) / imageHeight); + + glm::vec4 imageColor(toGlm(color), _alpha); + + DependencyManager::get()->renderQuad( + *batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, + imageColor, _geometryId + ); + + batch->setResourceTexture(0, nullptr); +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.h b/libraries/entities-renderer/src/RenderableImageEntityItem.h new file mode 100644 index 0000000000..669db13a22 --- /dev/null +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.h @@ -0,0 +1,56 @@ +// +// Created by Sam Gondelman on 11/29/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_RenderableImageEntityItem_h +#define hifi_RenderableImageEntityItem_h + +#include "RenderableEntityItem.h" + +#include + +namespace render { + namespace entities { + + class ImageEntityRenderer : public TypedEntityRenderer { + using Parent = TypedEntityRenderer; + using Pointer = std::shared_ptr; + public: + ImageEntityRenderer(const EntityItemPointer& entity); + ~ImageEntityRenderer(); + + protected: + ShapeKey getShapeKey() override; + + bool isTransparent() const override; + + private: + virtual bool needsRenderUpdate() const override; + 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; + + QString _imageURL; + NetworkTexturePointer _texture; + bool _textureIsLoaded { false }; + + bool _emissive; + bool _keepAspectRatio; + BillboardMode _billboardMode; + QRect _subImage; + + glm::u8vec3 _color; + float _alpha; + + glm::vec3 _dimensions; + + int _geometryId { 0 }; + }; + + } +} +#endif // hifi_RenderableImageEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 2b1d70f4d0..62aaf6f3a8 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -83,34 +83,6 @@ void RenderableModelEntityItem::setUnscaledDimensions(const glm::vec3& value) { } } -QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures) { - // If textures are unset, revert to original textures - if (textures.isEmpty()) { - return defaultTextures; - } - - // Legacy: a ,\n-delimited list of filename:"texturepath" - if (*textures.cbegin() != '{') { - textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; - } - - QJsonParseError error; - QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error); - // If textures are invalid, revert to original textures - if (error.error != QJsonParseError::NoError) { - qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << textures; - return defaultTextures; - } - - QVariantMap texturesMap = texturesJson.toVariant().toMap(); - // If textures are unset, revert to original textures - if (texturesMap.isEmpty()) { - return defaultTextures; - } - - return texturesJson.toVariant().toMap(); -} - void RenderableModelEntityItem::doInitialModelSimulation() { DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__); ModelPointer model = getModel(); diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index ce9e7ab764..6e281081d2 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include "GLMHelpers.h" @@ -59,7 +58,7 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return true; } - if (_faceCamera != entity->getFaceCamera()) { + if (_billboardMode != entity->getBillboardMode()) { return true; } return false; @@ -79,7 +78,7 @@ void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { _textColor = toGlm(entity->getTextColor()); _backgroundColor = toGlm(entity->getBackgroundColor()); - _faceCamera = entity->getFaceCamera(); + _billboardMode = entity->getBillboardMode(); _lineHeight = entity->getLineHeight(); _text = entity->getText(); } @@ -110,13 +109,26 @@ void TextEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; auto transformToTopLeft = modelTransform; - if (_faceCamera) { + if (_billboardMode == BillboardMode::YAW) { //rotate about vertical to face the camera glm::vec3 dPosition = args->getViewFrustum().getPosition() - modelTransform.getTranslation(); // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); glm::quat orientation = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); transformToTopLeft.setRotation(orientation); + } else if (_billboardMode == BillboardMode::FULL) { + glm::vec3 billboardPos = transformToTopLeft.getTranslation(); + glm::vec3 cameraPos = args->getViewFrustum().getPosition(); + // use the referencial from the avatar, y isn't always up + glm::vec3 avatarUP = EntityTreeRenderer::getAvatarUp(); + // check to see if glm::lookAt will work / using glm::lookAt variable name + glm::highp_vec3 s(glm::cross(billboardPos - cameraPos, avatarUP)); + + // make sure s is not NaN for any component + if (glm::length2(s) > 0.0f) { + glm::quat rotation(conjugate(toQuat(glm::lookAt(cameraPos, billboardPos, avatarUP)))); + transformToTopLeft.setRotation(rotation); + } } transformToTopLeft.postTranslate(dimensions * glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left transformToTopLeft.setScale(1.0f); // Use a scale of one so that the text is not deformed diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index ac7f2b620f..a368f280c5 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -32,7 +32,7 @@ private: virtual void doRender(RenderArgs* args) override; int _geometryID{ 0 }; std::shared_ptr _textRenderer; - bool _faceCamera; + BillboardMode _billboardMode; glm::vec3 _dimensions; glm::vec3 _textColor; glm::vec3 _backgroundColor; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 8970aed01a..f24b14d3b7 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -297,6 +297,33 @@ void EntityItemProperties::setMaterialMappingModeFromString(const QString& mater } } +QHash stringToBillboardModeLookup; + +void addBillboardMode(BillboardMode mode) { + stringToBillboardModeLookup[BillboardModeHelpers::getNameForBillboardMode(mode)] = mode; +} + +void buildStringToBillboardModeLookup() { + addBillboardMode(BillboardMode::NONE); + addBillboardMode(BillboardMode::YAW); + addBillboardMode(BillboardMode::FULL); +} + +QString EntityItemProperties::getBillboardModeAsString() const { + return BillboardModeHelpers::getNameForBillboardMode(_billboardMode); +} + +void EntityItemProperties::setBillboardModeFromString(const QString& materialMappingMode) { + if (stringToBillboardModeLookup.empty()) { + buildStringToBillboardModeLookup(); + } + auto billboardModeItr = stringToBillboardModeLookup.find(materialMappingMode.toLower()); + if (billboardModeItr != stringToBillboardModeLookup.end()) { + _billboardMode = billboardModeItr.value(); + _billboardModeChanged = true; + } +} + EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; @@ -383,6 +410,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_SPIN_FINISH, spinFinish); CHECK_PROPERTY_CHANGE(PROP_PARTICLE_ROTATE_WITH_ENTITY, rotateWithEntity); + CHECK_PROPERTY_CHANGE(PROP_IMAGE_URL, imageURL); + CHECK_PROPERTY_CHANGE(PROP_EMISSIVE, emissive); + CHECK_PROPERTY_CHANGE(PROP_KEEP_ASPECT_RATIO, keepAspectRatio); + CHECK_PROPERTY_CHANGE(PROP_SUB_IMAGE, subImage); + // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); CHECK_PROPERTY_CHANGE(PROP_ITEM_DESCRIPTION, itemDescription); @@ -412,7 +444,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints); CHECK_PROPERTY_CHANGE(PROP_HREF, href); CHECK_PROPERTY_CHANGE(PROP_DESCRIPTION, description); - CHECK_PROPERTY_CHANGE(PROP_FACE_CAMERA, faceCamera); + CHECK_PROPERTY_CHANGE(PROP_BILLBOARD_MODE, billboardMode); CHECK_PROPERTY_CHANGE(PROP_ACTION_DATA, actionData); CHECK_PROPERTY_CHANGE(PROP_NORMALS, normals); CHECK_PROPERTY_CHANGE(PROP_STROKE_COLORS, strokeColors); @@ -642,18 +674,20 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * * @see The different entity types have additional properties as follows: * @see {@link Entities.EntityProperties-Box|EntityProperties-Box} - * @see {@link Entities.EntityProperties-Light|EntityProperties-Light} - * @see {@link Entities.EntityProperties-Line|EntityProperties-Line} - * @see {@link Entities.EntityProperties-Material|EntityProperties-Material} + * @see {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} + * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} * @see {@link Entities.EntityProperties-Model|EntityProperties-Model} + * @see {@link Entities.EntityProperties-Text|EntityProperties-Text} + * @see {@link Entities.EntityProperties-Image|EntityProperties-Image} + * @see {@link Entities.EntityProperties-Web|EntityProperties-Web} * @see {@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect} + * @see {@link Entities.EntityProperties-Line|EntityProperties-Line} * @see {@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine} * @see {@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox} - * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} - * @see {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} - * @see {@link Entities.EntityProperties-Text|EntityProperties-Text} - * @see {@link Entities.EntityProperties-Web|EntityProperties-Web} + * @see {@link Entities.EntityProperties-Grid|EntityProperties-Grid} + * @see {@link Entities.EntityProperties-Light|EntityProperties-Light} * @see {@link Entities.EntityProperties-Zone|EntityProperties-Zone} + * @see {@link Entities.EntityProperties-Material|EntityProperties-Material} */ /**jsdoc @@ -786,8 +820,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * {@link Entities.EntityProperties|naturalDimensions}. * @property {Color} color=255,255,255 - Currently not used. * @property {string} modelURL="" - The URL of the FBX of OBJ model. Baked FBX models' URLs end in ".baked.fbx".
- * Note: If the name ends with "default-image-model.fbx" then the entity is considered to be an "Image" - * entity, in which case the textures property should be set per the example. * @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the * model's original textures. Use a texture name from the originalTextures property to override that texture. * Only the texture names and URLs to be overridden need be specified; original textures are used where there are no @@ -833,24 +865,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * dimensions: { x: 0.0945, y: 0.0921, z: 0.0423 }, * lifetime: 300 // Delete after 5 minutes. * }); - * @example Create an "Image" entity like you can in the Create app. - * 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"; - * var entity = Entities.addEntity({ - * type: "Model", - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -3 })), - * rotation: MyAvatar.orientation, - * dimensions: { - * x: 0.5385, - * y: 0.2819, - * z: 0.0092 - * }, - * shapeType: "box", - * collisionless: true, - * modelURL: IMAGE_MODEL, - * textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }), - * lifetime: 300 // Delete after 5 minutes - * }); */ /**jsdoc @@ -1091,8 +1105,10 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} lineHeight=0.1 - The height of each line of text (thus determining the font size). * @property {Color} textColor=255,255,255 - The color of the text. * @property {Color} backgroundColor=0,0,0 - The color of the background rectangle. - * @property {boolean} faceCamera=false - If true, the entity is oriented to face each user's camera (i.e., it - * differs for each user present). + * @property {BillboardMode} billboardMode="none" - If "none", the entity is not billboarded. If "yaw", the entity will be + * oriented to follow your camera around the y-axis. If "full" the entity will be oriented to face your camera. The following deprecated + * behavior is also supported: you can also set "faceCamera" to true to set billboardMode to "yaw", and you can set + * "isFacingAvatar" to true to set billboardMode to "full". Setting either to false sets the mode to "none" * @example Create a text entity. * var text = Entities.addEntity({ * type: "Text", @@ -1100,7 +1116,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * dimensions: { x: 0.6, y: 0.3, z: 0.01 }, * lineHeight: 0.12, * text: "Hello\nthere!", - * faceCamera: true, + * billboardMode: "yaw", * lifetime: 300 // Delete after 5 minutes. * }); */ @@ -1211,6 +1227,32 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * }); */ +/**jsdoc + * The "Image" {@link Entities.EntityType|EntityType} displays an image on a 2D rectangle in the domain. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Image + * @property {string} imageURL="" - The URL of the image to use. + * @property {boolean} emissive=false - Whether or not the image should be emissive (unlit). + * @property {boolean} keepAspectRatio=true - Whether or not the image should maintain its aspect ratio. + * @property {BillboardMode} billboardMode="none" - If "none", the entity is not billboarded. If "yaw", the entity will be + * oriented to follow your camera around the y-axis. If "full" the entity will be oriented to face your camera. The following deprecated + * behavior is also supported: you can also set "faceCamera" to true to set billboardMode to "yaw", and you can set + * "isFacingAvatar" to true to set billboardMode to "full". Setting either to false sets the mode to "none" + * @property {Rect} subImage={ x: 0, y: 0, width: -1, height: -1 } - The portion of the image to display. If width or height are -1, defaults to + * the full image in that dimension. + * @property {Color} color=255,255,255 - The color of image. + * @property {number} alpha=1 - The alpha of the image. + * @example Create a image entity. + * var image = Entities.addEntity({ + * type: "Image", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.6, y: 0.3, z: 0.01 }, + * imageURL: "https://images.pexels.com/photos/1020315/pexels-photo-1020315.jpeg", + * billboardMode: "yaw", + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime, bool strictSemantics, EntityPsuedoPropertyFlags psueudoPropertyFlags) const { @@ -1401,7 +1443,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_HEIGHT, lineHeight); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_TYPED(PROP_TEXT_COLOR, textColor, getTextColor(), u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_TYPED(PROP_BACKGROUND_COLOR, backgroundColor, getBackgroundColor(), u8vec3Color); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FACE_CAMERA, faceCamera); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); } // Zones only @@ -1482,6 +1524,26 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_DATA, materialData); } + // Image only + if (_type == EntityTypes::Image) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMISSIVE, emissive); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_KEEP_ASPECT_RATIO, keepAspectRatio); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SUB_IMAGE, subImage); + + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + + // Handle conversions to old 'textures' property from "imageURL" + if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(PROP_IMAGE_URL)) && + (!skipDefaults || defaultEntityProperties._imageURL != _imageURL)) { + QScriptValue textures = engine->newObject(); + textures.setProperty("tex.picture", _imageURL); + properties.setProperty("textures", textures); + } + } + /**jsdoc * The axis-aligned bounding box of an entity. * @typedef {object} Entities.BoundingBox @@ -1570,6 +1632,13 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID())); } + if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::FaceCamera)) { + properties.setProperty("faceCamera", convertScriptValue(engine, getBillboardMode() == BillboardMode::YAW)); + } + if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::IsFacingAvatar)) { + properties.setProperty("isFacingAvatar", convertScriptValue(engine, getBillboardMode() == BillboardMode::FULL)); + } + // FIXME - I don't think these properties are supported any more //COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); @@ -1698,13 +1767,17 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints); COPY_PROPERTY_FROM_QSCRIPTVALUE(href, QString, setHref); COPY_PROPERTY_FROM_QSCRIPTVALUE(description, QString, setDescription); - COPY_PROPERTY_FROM_QSCRIPTVALUE(faceCamera, bool, setFaceCamera); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(billboardMode, BillboardMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(actionData, QByteArray, setActionData); COPY_PROPERTY_FROM_QSCRIPTVALUE(normals, qVectorVec3, setNormals); COPY_PROPERTY_FROM_QSCRIPTVALUE(strokeColors, qVectorVec3, setStrokeColors); COPY_PROPERTY_FROM_QSCRIPTVALUE(strokeWidths, qVectorFloat, setStrokeWidths); COPY_PROPERTY_FROM_QSCRIPTVALUE(isUVModeStretch, bool, setIsUVModeStretch); + COPY_PROPERTY_FROM_QSCRIPTVALUE(imageURL, QString, setImageURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(emissive, bool, setEmissive); + COPY_PROPERTY_FROM_QSCRIPTVALUE(keepAspectRatio, bool, setKeepAspectRatio); + COPY_PROPERTY_FROM_QSCRIPTVALUE(subImage, QRect, setSubImage); if (!honorReadOnly) { // this is used by the json reader to set things that we don't want javascript to able to affect. @@ -1768,6 +1841,47 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneAvatarEntity, bool, setCloneAvatarEntity); COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneOriginID, QUuid, setCloneOriginID); + // Handle conversions from old 'textures' property to "imageURL" + { + QScriptValue V = object.property("textures"); + if (V.isValid()) { + bool isValid = false; + QString textures = QString_convertFromScriptValue(V, isValid); + if (isValid) { + QVariantMap texturesMap = parseTexturesToMap(textures, QVariantMap()); + auto texPicture = texturesMap.find("tex.picture"); + if (texPicture != texturesMap.end()) { + auto imageURL = texPicture.value().toString(); + if (_defaultSettings || imageURL != _imageURL) { + setImageURL(imageURL); + } + } + } + } + } + + // Handle old "faceCamera" and "isFacingAvatar" props + { + QScriptValue P = object.property("faceCamera"); + if (P.isValid()) { + bool newValue = P.toVariant().toBool(); + bool oldValue = getBillboardMode() == BillboardMode::YAW; + if (_defaultSettings || newValue != oldValue) { + setBillboardMode(newValue ? BillboardMode::YAW : BillboardMode::NONE); + } + } + } + { + QScriptValue P = object.property("isFacingAvatar"); + if (P.isValid()) { + bool newValue = P.toVariant().toBool(); + bool oldValue = getBillboardMode() == BillboardMode::FULL; + if (_defaultSettings || newValue != oldValue) { + setBillboardMode(newValue ? BillboardMode::FULL : BillboardMode::NONE); + } + } + } + _lastEdited = usecTimestampNow(); } @@ -1844,6 +1958,11 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(spinFinish); COPY_PROPERTY_IF_CHANGED(rotateWithEntity); + COPY_PROPERTY_IF_CHANGED(imageURL); + COPY_PROPERTY_IF_CHANGED(emissive); + COPY_PROPERTY_IF_CHANGED(keepAspectRatio); + COPY_PROPERTY_IF_CHANGED(subImage); + // Certifiable Properties COPY_PROPERTY_IF_CHANGED(itemName); COPY_PROPERTY_IF_CHANGED(itemDescription); @@ -1874,7 +1993,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(linePoints); COPY_PROPERTY_IF_CHANGED(href); COPY_PROPERTY_IF_CHANGED(description); - COPY_PROPERTY_IF_CHANGED(faceCamera); + COPY_PROPERTY_IF_CHANGED(billboardMode); COPY_PROPERTY_IF_CHANGED(actionData); COPY_PROPERTY_IF_CHANGED(normals); COPY_PROPERTY_IF_CHANGED(strokeColors); @@ -2092,7 +2211,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString); ADD_PROPERTY_TO_MAP(PROP_DESCRIPTION, Description, description, QString); - ADD_PROPERTY_TO_MAP(PROP_FACE_CAMERA, FaceCamera, faceCamera, bool); + ADD_PROPERTY_TO_MAP(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode); ADD_PROPERTY_TO_MAP(PROP_ACTION_DATA, ActionData, actionData, QByteArray); ADD_PROPERTY_TO_MAP(PROP_NORMALS, Normals, normals, QVector); ADD_PROPERTY_TO_MAP(PROP_STROKE_COLORS, StrokeColors, strokeColors, QVector); @@ -2198,6 +2317,11 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET, Grab, grab, EquippableIndicatorOffset, equippableIndicatorOffset); + ADD_PROPERTY_TO_MAP(PROP_IMAGE_URL, ImageURL, imageURL, QString); + ADD_PROPERTY_TO_MAP(PROP_EMISSIVE, Emissive, emissive, bool); + ADD_PROPERTY_TO_MAP(PROP_KEEP_ASPECT_RATIO, KeepAspectRatio, keepAspectRatio, bool); + ADD_PROPERTY_TO_MAP(PROP_SUB_IMAGE, SubImage, subImage, QRect); + // FIXME - these are not yet handled //ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64); @@ -2361,7 +2485,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight()); APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, properties.getTextColor()); APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, properties.getBackgroundColor()); - APPEND_ENTITY_PROPERTY(PROP_FACE_CAMERA, properties.getFaceCamera()); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); } if (properties.getType() == EntityTypes::Model) { @@ -2519,6 +2643,18 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_MATERIAL_DATA, properties.getMaterialData()); } + // Image + if (properties.getType() == EntityTypes::Image) { + APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); + APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, properties.getEmissive()); + APPEND_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, properties.getKeepAspectRatio()); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); + APPEND_ENTITY_PROPERTY(PROP_SUB_IMAGE, properties.getSubImage()); + + APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); + } + APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); @@ -2764,7 +2900,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_COLOR, u8vec3Color, setTextColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_COLOR, u8vec3Color, setBackgroundColor); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FACE_CAMERA, bool, setFaceCamera); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); } if (properties.getType() == EntityTypes::Model) { @@ -2914,6 +3050,18 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_DATA, QString, setMaterialData); } + // Image + if (properties.getType() == EntityTypes::Image) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMISSIVE, bool, setEmissive); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEEP_ASPECT_RATIO, bool, setKeepAspectRatio); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SUB_IMAGE, QRect, setSubImage); + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); + } + 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); @@ -3190,7 +3338,7 @@ void EntityItemProperties::markAllChanged() { _hrefChanged = true; _descriptionChanged = true; - _faceCameraChanged = true; + _billboardModeChanged = true; _actionDataChanged = true; _normalsChanged = true; @@ -3240,6 +3388,11 @@ void EntityItemProperties::markAllChanged() { _cloneOriginIDChanged = true; _isVisibleInSecondaryCameraChanged = true; + + _imageURLChanged = true; + _emissiveChanged = true; + _keepAspectRatioChanged = true; + _subImageChanged = true; } // The minimum bounding box for the entity. @@ -3776,6 +3929,23 @@ QList EntityItemProperties::listChangedProperties() { out += "cloneOriginID"; } + if (imageURLChanged()) { + out += "imageURL"; + } + if (emissiveChanged()) { + out += "emissive"; + } + if (keepAspectRatioChanged()) { + out += "keepAspectRatio"; + } + if (subImageChanged()) { + out += "subImage"; + } + + if (billboardModeChanged()) { + out += "billboardMode"; + } + getAnimation().listChangedProperties(out); getKeyLight().listChangedProperties(out); getAmbientLight().listChangedProperties(out); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 4d45f280e2..a34af703fb 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -48,6 +48,7 @@ #include "ZoneEntityItem.h" #include "MaterialMappingMode.h" +#include "BillboardMode.h" const quint64 UNKNOWN_CREATED_TIME = 0; @@ -68,18 +69,20 @@ using u8vec3Color = glm::u8vec3; class EntityItemProperties { // 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 ModelEntityItem; + friend class TextEntityItem; + friend class ImageEntityItem; + friend class WebEntityItem; + friend class ParticleEffectEntityItem; + friend class LineEntityItem; + friend class PolyLineEntityItem; + friend class PolyVoxEntityItem; + friend class GridEntityItem; + friend class LightEntityItem; + friend class ZoneEntityItem; friend class MaterialEntityItem; public: EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()); @@ -213,7 +216,7 @@ public: DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, ""); DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString, ""); - DEFINE_PROPERTY(PROP_FACE_CAMERA, FaceCamera, faceCamera, bool, TextEntityItem::DEFAULT_FACE_CAMERA); + DEFINE_PROPERTY_REF_ENUM(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode, BillboardMode::NONE); DEFINE_PROPERTY_REF(PROP_ACTION_DATA, ActionData, actionData, QByteArray, QByteArray()); DEFINE_PROPERTY(PROP_NORMALS, Normals, normals, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); DEFINE_PROPERTY(PROP_STROKE_COLORS, StrokeColors, strokeColors, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); @@ -250,6 +253,11 @@ public: DEFINE_PROPERTY(PROP_SPIN_FINISH, SpinFinish, spinFinish, float, particle::DEFAULT_SPIN_FINISH); DEFINE_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, bool, particle::DEFAULT_ROTATE_WITH_ENTITY); + DEFINE_PROPERTY_REF(PROP_IMAGE_URL, ImageURL, imageURL, QString, ""); + DEFINE_PROPERTY_REF(PROP_EMISSIVE, Emissive, emissive, bool, false); + DEFINE_PROPERTY_REF(PROP_KEEP_ASPECT_RATIO, KeepAspectRatio, keepAspectRatio, bool, true); + DEFINE_PROPERTY_REF(PROP_SUB_IMAGE, SubImage, subImage, QRect, QRect()); + // 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 d3a2dc6cec..225b77bd97 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -122,6 +122,8 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) {return qVectorBoolToScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) { return qVectorFloatToScriptValue(e, v); } +inline QScriptValue convertScriptValue(QScriptEngine* e, const QRect& v) { return qRectToScriptValue(e, v); } + inline QScriptValue convertScriptValue(QScriptEngine* e, const QByteArray& v) { QByteArray b64 = v.toBase64(); return QScriptValue(QString(b64)); @@ -323,6 +325,13 @@ inline glm::quat quat_convertFromScriptValue(const QScriptValue& v, bool& isVali return glm::quat(); } +inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) { + isValid = true; + QRect rect; + qRectFromScriptValue(v, rect); + return rect; +} + #define COPY_PROPERTY_IF_CHANGED(P) \ { \ if (other._##P##Changed) { \ diff --git a/libraries/entities/src/EntityPropertyFlags.cpp b/libraries/entities/src/EntityPropertyFlags.cpp index c077b153b8..d4aeac3a78 100644 --- a/libraries/entities/src/EntityPropertyFlags.cpp +++ b/libraries/entities/src/EntityPropertyFlags.cpp @@ -67,7 +67,7 @@ QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f) { result = f.getHasProperty(PROP_LINE_POINTS) ? result + "linePoints " : result; result = f.getHasProperty(PROP_HREF) ? result + "href " : result; result = f.getHasProperty(PROP_DESCRIPTION) ? result + "description " : result; - result = f.getHasProperty(PROP_FACE_CAMERA) ? result + "faceCamera " : result; + result = f.getHasProperty(PROP_BILLBOARD_MODE) ? result + "billboardMode " : result; result = f.getHasProperty(PROP_SCRIPT_TIMESTAMP) ? result + "scriptTimestamp " : result; result = f.getHasProperty(PROP_ACTION_DATA) ? result + "actionData " : result; result = f.getHasProperty(PROP_X_TEXTURE_URL) ? result + "xTextureUrl " : result; diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index d2f687fbd3..803d391e11 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -99,7 +99,7 @@ enum EntityPropertyList { PROP_HREF, PROP_DESCRIPTION, // 61 - PROP_FACE_CAMERA, + PROP_BILLBOARD_MODE, PROP_SCRIPT_TIMESTAMP, PROP_ACTION_DATA, @@ -275,6 +275,9 @@ enum EntityPropertyList { PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE, PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET, + PROP_EMISSIVE, + PROP_SUB_IMAGE, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, @@ -308,6 +311,11 @@ enum EntityPropertyList { // other properties which will never overlap with each other. PROP_EMITTING_PARTICLES = PROP_ANIMATION_PLAYING, + // Aliases/Piggyback properties for Image. These properties intentionally reuse the enum values for + // other properties which will never overlap with each other. + PROP_IMAGE_URL = PROP_MODEL_URL, + PROP_KEEP_ASPECT_RATIO = PROP_ANIMATION_PLAYING, + // WARNING!!! DO NOT ADD PROPS_xxx here unless you really really meant to.... Add them UP above }; diff --git a/libraries/entities/src/EntityPsuedoPropertyFlags.h b/libraries/entities/src/EntityPsuedoPropertyFlags.h index 0b051a4c74..cb5f4ebfba 100644 --- a/libraries/entities/src/EntityPsuedoPropertyFlags.h +++ b/libraries/entities/src/EntityPsuedoPropertyFlags.h @@ -32,6 +32,8 @@ namespace EntityPsuedoPropertyFlag { RenderInfo, ClientOnly, OwningAvatarID, + FaceCamera, + IsFacingAvatar, NumFlags }; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 3d68a27bc9..8ad1efd36e 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -681,6 +681,10 @@ QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScri psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ClientOnly); } else if (extendedPropertyString == "owningAvatarID") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::OwningAvatarID); + } else if (extendedPropertyString == "faceCamera") { + psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FaceCamera); + } else if (extendedPropertyString == "isFacingAvatar") { + psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::IsFacingAvatar); } }; diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 9611063f8b..e511af83b0 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -20,38 +20,42 @@ #include "EntityItemProperties.h" #include "EntitiesLogging.h" -#include "LightEntityItem.h" +#include "ShapeEntityItem.h" #include "ModelEntityItem.h" #include "ParticleEffectEntityItem.h" #include "TextEntityItem.h" +#include "ImageEntityItem.h" #include "WebEntityItem.h" -#include "ZoneEntityItem.h" #include "LineEntityItem.h" -#include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" -#include "ShapeEntityItem.h" +#include "PolyVoxEntityItem.h" +#include "GridEntityItem.h" +#include "LightEntityItem.h" +#include "ZoneEntityItem.h" #include "MaterialEntityItem.h" QMap EntityTypes::_typeToNameMap; QMap EntityTypes::_nameToTypeMap; -EntityTypeFactory EntityTypes::_factories[EntityTypes::LAST + 1]; +EntityTypeFactory EntityTypes::_factories[EntityTypes::NUM_TYPES]; bool EntityTypes::_factoriesInitialized = false; const QString ENTITY_TYPE_NAME_UNKNOWN = "Unknown"; // Register Entity the default implementations of entity types here... -REGISTER_ENTITY_TYPE(Model) -REGISTER_ENTITY_TYPE(Web) -REGISTER_ENTITY_TYPE(Light) -REGISTER_ENTITY_TYPE(Text) -REGISTER_ENTITY_TYPE(ParticleEffect) -REGISTER_ENTITY_TYPE(Zone) -REGISTER_ENTITY_TYPE(Line) -REGISTER_ENTITY_TYPE(PolyVox) -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(Shape) +REGISTER_ENTITY_TYPE(Model) +REGISTER_ENTITY_TYPE(Text) +REGISTER_ENTITY_TYPE(Image) +REGISTER_ENTITY_TYPE(Web) +REGISTER_ENTITY_TYPE(ParticleEffect) +REGISTER_ENTITY_TYPE(Line) +REGISTER_ENTITY_TYPE(PolyLine) +REGISTER_ENTITY_TYPE(PolyVox) +REGISTER_ENTITY_TYPE(Grid) +REGISTER_ENTITY_TYPE(Light) +REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Material) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { @@ -80,7 +84,7 @@ bool EntityTypes::registerEntityType(EntityType entityType, const char* name, En memset(&_factories,0,sizeof(_factories)); _factoriesInitialized = true; } - if (entityType >= 0 && entityType <= LAST) { + if (entityType >= 0 && entityType < NUM_TYPES) { _factories[entityType] = factoryMethod; return true; } @@ -91,7 +95,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const const EntityItemProperties& properties) { EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; - if (entityType >= 0 && entityType <= LAST) { + if (entityType >= 0 && entityType < NUM_TYPES) { factory = _factories[entityType]; } if (factory) { diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 954bdf8f17..604f2fe905 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -49,25 +49,6 @@ public: * "Cube". If an entity of type Shape or Sphere has its shape set * to "Cube" then its type will be reported as "Box". * {@link Entities.EntityProperties-Box|EntityProperties-Box} - * "Light"A local lighting effect. - * {@link Entities.EntityProperties-Light|EntityProperties-Light} - * "Line"A sequence of one or more simple straight lines. - * {@link Entities.EntityProperties-Line|EntityProperties-Line} - * "Material"Modifies the existing materials on Model entities, Shape entities (albedo - * only), {@link Overlays.OverlayType|model overlays}, and avatars. - * {@link Entities.EntityProperties-Material|EntityProperties-Material} - * "Model"A mesh model from an FBX or OBJ file. - * {@link Entities.EntityProperties-Model|EntityProperties-Model} - * "ParticleEffect"A particle system that can be used to simulate things such as fire, - * smoke, snow, magic spells, etc. - * {@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect} - * "PolyLine"A sequence of one or more textured straight lines. - * {@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine} - * "PolyVox"A set of textured voxels. - * {@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox} - * "Shape"A basic entity such as a cube. - * See also, the "Box" and "Sphere" entity types. - * {@link Entities.EntityProperties-Shape|EntityProperties-Shape} * "Sphere"A sphere. This is a synonym of "Shape" for the case * where the entity's shape property value is "Sphere".
* If an entity is created with its type @@ -75,32 +56,57 @@ public: * "Sphere". If an entity of type Box or Shape has its shape set * to "Sphere" then its type will be reported as "Sphere". * {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} + * "Shape"A basic entity such as a cube. + * See also, the "Box" and "Sphere" entity types. + * {@link Entities.EntityProperties-Shape|EntityProperties-Shape} + * "Model"A mesh model from an FBX or OBJ file. + * {@link Entities.EntityProperties-Model|EntityProperties-Model} * "Text"A pane of text oriented in space. * {@link Entities.EntityProperties-Text|EntityProperties-Text} + * "Image"An image oriented in space. + * {@link Entities.EntityProperties-Image|EntityProperties-Image} * "Web"A browsable Web page. * {@link Entities.EntityProperties-Web|EntityProperties-Web} + * "ParticleEffect"A particle system that can be used to simulate things such as fire, + * smoke, snow, magic spells, etc. + * {@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect} + * "Line"A sequence of one or more simple straight lines. + * {@link Entities.EntityProperties-Line|EntityProperties-Line} + * "PolyLine"A sequence of one or more textured straight lines. + * {@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine} + * "PolyVox"A set of textured voxels. + * {@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox} + * "Grid"A grid. + * {@link Entities.EntityProperties-Grid|EntityProperties-Grid} + * "Light"A local lighting effect. + * {@link Entities.EntityProperties-Light|EntityProperties-Light} * "Zone"A volume of lighting effects and avatar permissions. * {@link Entities.EntityProperties-Zone|EntityProperties-Zone} + * "Material"Modifies the existing materials on Model entities, Shape entities, + * {@link Overlays.OverlayType|model overlays}, and avatars. + * {@link Entities.EntityProperties-Material|EntityProperties-Material} * * * @typedef {string} Entities.EntityType */ typedef enum EntityType_t { Unknown, - Model, Box, Sphere, - Light, - Text, - ParticleEffect, - Zone, - Web, - Line, - PolyVox, - PolyLine, Shape, + Model, + Text, + Image, + Web, + ParticleEffect, + Line, + PolyLine, + PolyVox, + Grid, + Light, + Zone, Material, - LAST = Material + NUM_TYPES } EntityType; static const QString& getEntityTypeName(EntityType entityType); @@ -112,7 +118,7 @@ public: private: static QMap _typeToNameMap; static QMap _nameToTypeMap; - static EntityTypeFactory _factories[LAST + 1]; + static EntityTypeFactory _factories[NUM_TYPES]; static bool _factoriesInitialized; }; diff --git a/libraries/entities/src/GridEntityItem.cpp b/libraries/entities/src/GridEntityItem.cpp new file mode 100644 index 0000000000..cc59135e8d --- /dev/null +++ b/libraries/entities/src/GridEntityItem.cpp @@ -0,0 +1,154 @@ +// +// Created by Sam Gondelman on 11/29/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 "GridEntityItem.h" + +#include "EntityItemProperties.h" + +EntityItemPointer GridEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + Pointer entity(new GridEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); + entity->setProperties(properties); + return entity; +} + +// our non-pure virtual subclass for now... +GridEntityItem::GridEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _type = EntityTypes::Grid; +} + +EntityItemProperties GridEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { + EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class + + //COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getGridURL); + //COPY_ENTITY_PROPERTY_TO_PROPERTIES(emissive, getEmissive); + //COPY_ENTITY_PROPERTY_TO_PROPERTIES(keepAspectRatio, getKeepAspectRatio); + //COPY_ENTITY_PROPERTY_TO_PROPERTIES(faceCamera, getFaceCamera); + //COPY_ENTITY_PROPERTY_TO_PROPERTIES(subGrid, getSubGrid); + + return properties; +} + +bool GridEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + //SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setGridURL); + //SET_ENTITY_PROPERTY_FROM_PROPERTIES(emissive, setEmissive); + //SET_ENTITY_PROPERTY_FROM_PROPERTIES(keepAspectRatio, setKeepAspectRatio); + //SET_ENTITY_PROPERTY_FROM_PROPERTIES(faceCamera, setFaceCamera); + //SET_ENTITY_PROPERTY_FROM_PROPERTIES(subGrid, setSubGrid); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "GridEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int GridEntityItem::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_IMAGE_URL, QString, setGridURL); + //READ_ENTITY_PROPERTY(PROP_EMISSIVE, bool, setEmissive); + //READ_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, bool, setKeepAspectRatio); + //READ_ENTITY_PROPERTY(PROP_FACE_CAMERA, bool, setFaceCamera); + //READ_ENTITY_PROPERTY(PROP_SUB_IMAGE, QRect, setSubGrid); + + return bytesRead; +} + +EntityPropertyFlags GridEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + + //requestedProperties += PROP_IMAGE_URL; + //requestedProperties += PROP_EMISSIVE; + //requestedProperties += PROP_KEEP_ASPECT_RATIO; + //requestedProperties += PROP_FACE_CAMERA; + //requestedProperties += PROP_SUB_IMAGE; + + return requestedProperties; +} + +void GridEntityItem::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_IMAGE_URL, getGridURL()); + //APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, getEmissive()); + //APPEND_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, getKeepAspectRatio()); + //APPEND_ENTITY_PROPERTY(PROP_FACE_CAMERA, getFaceCamera()); + //APPEND_ENTITY_PROPERTY(PROP_SUB_IMAGE, getSubGrid()); +} + +bool GridEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + OctreeElementPointer& element, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + return false; +} + +bool GridEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } + return false; +} \ No newline at end of file diff --git a/libraries/entities/src/GridEntityItem.h b/libraries/entities/src/GridEntityItem.h new file mode 100644 index 0000000000..4704d96f3d --- /dev/null +++ b/libraries/entities/src/GridEntityItem.h @@ -0,0 +1,56 @@ +// +// Created by Sam Gondelman on 11/29/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_GridEntityItem_h +#define hifi_GridEntityItem_h + +#include "EntityItem.h" + +class GridEntityItem : public EntityItem { + using Pointer = std::shared_ptr; +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + GridEntityItem(const EntityItemID& entityItemID); + + ALLOW_INSTANTIATION // This class can be instantiated + + // methods for getting/setting all properties of an entity + EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; + bool setProperties(const EntityItemProperties& properties) override; + + EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + virtual bool supportsDetailedIntersection() const override { return true; } + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; + +protected: + +}; + +#endif // hifi_GridEntityItem_h diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp new file mode 100644 index 0000000000..98817a63ba --- /dev/null +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -0,0 +1,269 @@ +// +// Created by Sam Gondelman on 11/29/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 "ImageEntityItem.h" + +#include "EntityItemProperties.h" + +EntityItemPointer ImageEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + Pointer entity(new ImageEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); + entity->setProperties(properties); + return entity; +} + +// our non-pure virtual subclass for now... +ImageEntityItem::ImageEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _type = EntityTypes::Image; +} + +void ImageEntityItem::setUnscaledDimensions(const glm::vec3& value) { + const float IMAGE_ENTITY_ITEM_FIXED_DEPTH = 0.01f; + // NOTE: Image Entities always have a "depth" of 1cm. + EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, IMAGE_ENTITY_ITEM_FIXED_DEPTH)); +} + +EntityItemProperties ImageEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { + EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emissive, getEmissive); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(keepAspectRatio, getKeepAspectRatio); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(subImage, getSubImage); + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + + return properties; +} + +bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setImageURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emissive, setEmissive); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(keepAspectRatio, setKeepAspectRatio); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(subImage, setSubImage); + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "ImageEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int ImageEntityItem::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_IMAGE_URL, QString, setImageURL); + READ_ENTITY_PROPERTY(PROP_EMISSIVE, bool, setEmissive); + READ_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, bool, setKeepAspectRatio); + READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); + READ_ENTITY_PROPERTY(PROP_SUB_IMAGE, QRect, setSubImage); + + READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + + return bytesRead; +} + +EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + + requestedProperties += PROP_IMAGE_URL; + requestedProperties += PROP_EMISSIVE; + requestedProperties += PROP_KEEP_ASPECT_RATIO; + requestedProperties += PROP_BILLBOARD_MODE; + requestedProperties += PROP_SUB_IMAGE; + + requestedProperties += PROP_COLOR; + requestedProperties += PROP_ALPHA; + + return requestedProperties; +} + +void ImageEntityItem::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_IMAGE_URL, getImageURL()); + APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, getEmissive()); + APPEND_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, getKeepAspectRatio()); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); + APPEND_ENTITY_PROPERTY(PROP_SUB_IMAGE, getSubImage()); + + APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); +} + +bool ImageEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + OctreeElementPointer& element, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + return false; +} + +bool ImageEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } + return false; +} + +QString ImageEntityItem::getImageURL() const { + QString result; + withReadLock([&] { + result = _imageURL; + }); + return result; +} + +void ImageEntityItem::setImageURL(const QString& url) { + withWriteLock([&] { + _imageURL = url; + }); +} + +bool ImageEntityItem::getEmissive() const { + bool result; + withReadLock([&] { + result = _emissive; + }); + return result; +} + +void ImageEntityItem::setEmissive(bool emissive) { + withWriteLock([&] { + _emissive = emissive; + }); +} + +bool ImageEntityItem::getKeepAspectRatio() const { + bool result; + withReadLock([&] { + result = _keepAspectRatio; + }); + return result; +} + +void ImageEntityItem::setKeepAspectRatio(bool keepAspectRatio) { + withWriteLock([&] { + _keepAspectRatio = keepAspectRatio; + }); +} + +BillboardMode ImageEntityItem::getBillboardMode() const { + BillboardMode result; + withReadLock([&] { + result = _billboardMode; + }); + return result; +} + +void ImageEntityItem::setBillboardMode(BillboardMode value) { + withWriteLock([&] { + _billboardMode = value; + }); +} + +QRect ImageEntityItem::getSubImage() const { + QRect result; + withReadLock([&] { + result = _subImage; + }); + return result; +} + +void ImageEntityItem::setSubImage(const QRect& subImage) { + withWriteLock([&] { + _subImage = subImage; + }); +} + +void ImageEntityItem::setColor(const glm::u8vec3& color) { + withWriteLock([&] { + _color = color; + }); +} + +glm::u8vec3 ImageEntityItem::getColor() const { + return resultWithReadLock([&] { + return _color; + }); +} + +void ImageEntityItem::setAlpha(float alpha) { + withWriteLock([&] { + _alpha = alpha; + }); +} + +float ImageEntityItem::getAlpha() const { + return resultWithReadLock([&] { + return _alpha; + }); +} \ No newline at end of file diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h new file mode 100644 index 0000000000..228f86ca03 --- /dev/null +++ b/libraries/entities/src/ImageEntityItem.h @@ -0,0 +1,86 @@ +// +// Created by Sam Gondelman on 11/29/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_ImageEntityItem_h +#define hifi_ImageEntityItem_h + +#include "EntityItem.h" + +class ImageEntityItem : public EntityItem { + using Pointer = std::shared_ptr; +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + ImageEntityItem(const EntityItemID& entityItemID); + + ALLOW_INSTANTIATION // This class can be instantiated + + virtual void setUnscaledDimensions(const glm::vec3& value) override; + + // methods for getting/setting all properties of an entity + EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; + bool setProperties(const EntityItemProperties& properties) override; + + EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + virtual bool supportsDetailedIntersection() const override { return true; } + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; + + void setImageURL(const QString& imageUrl); + QString getImageURL() const; + + void setEmissive(bool emissive); + bool getEmissive() const; + + void setKeepAspectRatio(bool keepAspectRatio); + bool getKeepAspectRatio() const; + + void setBillboardMode(BillboardMode value); + BillboardMode getBillboardMode() const; + + void setSubImage(const QRect& subImage); + QRect getSubImage() const; + + void setColor(const glm::u8vec3& color); + glm::u8vec3 getColor() const; + + void setAlpha(float alpha); + float getAlpha() const; + +protected: + QString _imageURL; + bool _emissive { false }; + bool _keepAspectRatio { true }; + BillboardMode _billboardMode; + QRect _subImage; + + glm::u8vec3 _color; + float _alpha; +}; + +#endif // hifi_ImageEntityItem_h diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 8dd4877ce2..b178ce33c3 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -27,7 +27,6 @@ const QString TextEntityItem::DEFAULT_TEXT(""); const float TextEntityItem::DEFAULT_LINE_HEIGHT = 0.1f; const glm::u8vec3 TextEntityItem::DEFAULT_TEXT_COLOR = { 255, 255, 255 }; const glm::u8vec3 TextEntityItem::DEFAULT_BACKGROUND_COLOR = { 0, 0, 0}; -const bool TextEntityItem::DEFAULT_FACE_CAMERA = false; EntityItemPointer TextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new TextEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); @@ -39,9 +38,8 @@ TextEntityItem::TextEntityItem(const EntityItemID& entityItemID) : EntityItem(en _type = EntityTypes::Text; } -const float TEXT_ENTITY_ITEM_FIXED_DEPTH = 0.01f; - void TextEntityItem::setUnscaledDimensions(const glm::vec3& value) { + const float TEXT_ENTITY_ITEM_FIXED_DEPTH = 0.01f; // NOTE: Text Entities always have a "depth" of 1cm. EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, TEXT_ENTITY_ITEM_FIXED_DEPTH)); } @@ -53,7 +51,7 @@ EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& de COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineHeight, getLineHeight); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textColor, getTextColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundColor, getBackgroundColor); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(faceCamera, getFaceCamera); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); return properties; } @@ -65,7 +63,7 @@ bool TextEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineHeight, setLineHeight); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textColor, setTextColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundColor, setBackgroundColor); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(faceCamera, setFaceCamera); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); if (somethingChanged) { bool wantDebug = false; @@ -93,7 +91,7 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_LINE_HEIGHT, float, setLineHeight); READ_ENTITY_PROPERTY(PROP_TEXT_COLOR, glm::u8vec3, setTextColor); READ_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, glm::u8vec3, setBackgroundColor); - READ_ENTITY_PROPERTY(PROP_FACE_CAMERA, bool, setFaceCamera); + READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); return bytesRead; } @@ -104,7 +102,7 @@ EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_LINE_HEIGHT; requestedProperties += PROP_TEXT_COLOR; requestedProperties += PROP_BACKGROUND_COLOR; - requestedProperties += PROP_FACE_CAMERA; + requestedProperties += PROP_BILLBOARD_MODE; return requestedProperties; } @@ -122,7 +120,7 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, getLineHeight()); APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, getTextColor()); APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, getBackgroundColor()); - APPEND_ENTITY_PROPERTY(PROP_FACE_CAMERA, getFaceCamera()); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); } @@ -230,17 +228,17 @@ glm::u8vec3 TextEntityItem::getBackgroundColor() const { }); } -bool TextEntityItem::getFaceCamera() const { - bool result; +BillboardMode TextEntityItem::getBillboardMode() const { + BillboardMode result; withReadLock([&] { - result = _faceCamera; + result = _billboardMode; }); return result; } -void TextEntityItem::setFaceCamera(bool value) { +void TextEntityItem::setBillboardMode(BillboardMode value) { withWriteLock([&] { - _faceCamera = value; + _billboardMode = value; }); } diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 357697fdec..9abef4d198 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -71,16 +71,15 @@ public: glm::u8vec3 getBackgroundColor() const; void setBackgroundColor(const glm::u8vec3& value); - static const bool DEFAULT_FACE_CAMERA; - bool getFaceCamera() const; - void setFaceCamera(bool value); + BillboardMode getBillboardMode() const; + void setBillboardMode(BillboardMode value); private: QString _text; float _lineHeight; glm::u8vec3 _textColor; glm::u8vec3 _backgroundColor; - bool _faceCamera; + BillboardMode _billboardMode; }; #endif // hifi_TextEntityItem_h diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 9e3ecdfb39..3325faa95c 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -33,7 +33,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::CleanupProperties); + return static_cast(EntityVersion::ImageEntities); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 800ce7bbe7..421c9a96c1 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -245,7 +245,8 @@ enum class EntityVersion : PacketVersion { GrabProperties, ScriptGlmVectors, FixedLightSerialization, - CleanupProperties + CleanupProperties, + ImageEntities }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 88e83c01c8..6c0bba5ec6 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -546,6 +546,17 @@ bool OctreePacketData::appendValue(const AACube& aaCube) { return success; } +bool OctreePacketData::appendValue(const QRect& value) { + const unsigned char* data = (const unsigned char*)&value; + int length = sizeof(QRect); + bool success = append(data, length); + if (success) { + _bytesOfValues += length; + _totalBytesOfValues += length; + } + return success; +} + bool OctreePacketData::appendPosition(const glm::vec3& value) { const unsigned char* data = (const unsigned char*)&value; int length = sizeof(value); @@ -804,3 +815,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, AACube result = AACube(cube.corner, cube.scale); return sizeof(aaCubeData); } + +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QRect& result) { + memcpy(&result, dataBytes, sizeof(result)); + return sizeof(result); +} \ No newline at end of file diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 46726d83a6..bd1abf8744 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -34,6 +34,7 @@ #include #include "MaterialMappingMode.h" +#include "BillboardMode.h" #include "OctreeConstants.h" #include "OctreeElement.h" @@ -197,6 +198,9 @@ public: /// appends an AACube value to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const AACube& aaCube); + /// appends an QRect value to the end of the stream, may fail if new data stream is too long to fit in packet + bool appendValue(const QRect& rect); + /// appends a position to the end of the stream, may fail if new data stream is too long to fit in packet bool appendPosition(const glm::vec3& value); @@ -258,6 +262,7 @@ public: 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, BillboardMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result); @@ -269,6 +274,7 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, QVector& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result); static int unpackDataFromBytes(const unsigned char* dataBytes, AACube& result); + static int unpackDataFromBytes(const unsigned char* dataBytes, QRect& result); private: /// appends raw bytes, might fail if byte would cause packet to be too large diff --git a/libraries/shared/src/BillboardMode.cpp b/libraries/shared/src/BillboardMode.cpp new file mode 100644 index 0000000000..56251f53f2 --- /dev/null +++ b/libraries/shared/src/BillboardMode.cpp @@ -0,0 +1,25 @@ +// +// Created by Sam Gondelman on 11/30/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 "BillboardMode.h" + +const char* billboardModeNames[] = { + "none", + "yaw", + "full" +}; + +static const size_t MATERIAL_MODE_NAMES = (sizeof(billboardModeNames) / sizeof((billboardModeNames)[0])); + +QString BillboardModeHelpers::getNameForBillboardMode(BillboardMode mode) { + if (((int)mode <= 0) || ((int)mode >= (int)MATERIAL_MODE_NAMES)) { + mode = (BillboardMode)0; + } + + return billboardModeNames[(int)mode]; +} \ No newline at end of file diff --git a/libraries/shared/src/BillboardMode.h b/libraries/shared/src/BillboardMode.h new file mode 100644 index 0000000000..7f3e79d453 --- /dev/null +++ b/libraries/shared/src/BillboardMode.h @@ -0,0 +1,26 @@ +// +// Created by Sam Gondelman on 11/30/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_BillboardMode_h +#define hifi_BillboardMode_h + +#include "QString" + +enum class BillboardMode { + NONE = 0, + YAW, + FULL +}; + +class BillboardModeHelpers { +public: + static QString getNameForBillboardMode(BillboardMode mode); +}; + +#endif // hifi_BillboardMode_h + diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index dc84afff93..5394a0f448 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -24,6 +24,7 @@ #include #include #include +#include int vec2MetaTypeId = qRegisterMetaType(); int u8vec3MetaTypeId = qRegisterMetaType(); @@ -1245,3 +1246,31 @@ void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector result << meshFace; } } + +QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures) { + // If textures are unset, revert to original textures + if (textures.isEmpty()) { + return defaultTextures; + } + + // Legacy: a ,\n-delimited list of filename:"texturepath" + if (*textures.cbegin() != '{') { + textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; + } + + QJsonParseError error; + QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error); + // If textures are invalid, revert to original textures + if (error.error != QJsonParseError::NoError) { + qWarning() << "Could not evaluate textures property value:" << textures; + return defaultTextures; + } + + QVariantMap texturesMap = texturesJson.toVariant().toMap(); + // If textures are unset, revert to original textures + if (texturesMap.isEmpty()) { + return defaultTextures; + } + + return texturesJson.toVariant().toMap(); +} \ No newline at end of file diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index ed637fe771..9d5bca6b9f 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -705,5 +705,7 @@ void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResul QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector& vector); void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector& result); +QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures); + #endif // hifi_RegisteredMetaTypes_h diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 077d50ddde..8d5271093e 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -42,7 +42,6 @@ var TITLE_OFFSET = 60; var CREATE_TOOLS_WIDTH = 490; var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; -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"; var createToolsWindow = new CreateWindow( @@ -398,8 +397,8 @@ const DEFAULT_ENTITY_PROPERTIES = { }, shapeType: "box", collisionless: true, - modelURL: IMAGE_MODEL, - textures: JSON.stringify({ "tex.picture": "" }) + keepAspectRatio: false, + imageURL: DEFAULT_IMAGE }, Web: { dimensions: { @@ -495,9 +494,6 @@ var toolBar = (function () { var type = requestedProperties.type; if (type === "Box" || type === "Sphere") { applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); - } else if (type === "Image") { - requestedProperties.type = "Model"; - applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Image); } else { applyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); } @@ -515,7 +511,7 @@ var toolBar = (function () { } direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); - var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web", "Material"]; + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web", "Material"]; if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box prior to creating it. diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 84ad59df36..a0520cda4f 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -11,7 +11,6 @@ const DESCENDING_SORT = -1; const ASCENDING_STRING = '▴'; const DESCENDING_STRING = '▾'; const BYTES_PER_MEGABYTE = 1024 * 1024; -const IMAGE_MODEL_NAME = 'default-image-model.fbx'; const COLLAPSE_EXTRA_INFO = "E"; const EXPAND_EXTRA_INFO = "D"; const FILTER_IN_VIEW_ATTRIBUTE = "pressed"; @@ -625,9 +624,6 @@ function loaded() { entityData.forEach(function(entity) { let type = entity.type; let filename = getFilename(entity.url); - if (filename === IMAGE_MODEL_NAME) { - type = "Image"; - } let entityData = { id: entity.id, diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 78e3cd4dc8..da16f6c504 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -471,7 +471,7 @@ const GROUPS = [ label: "Image", type: "string", placeholder: "URL", - propertyID: "image", + propertyID: "imageURL", }, ] }, @@ -1783,14 +1783,6 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen updateProperty(propertyName, propertyValue, isParticleProperty); } -function createImageURLUpdateFunction(property) { - return function () { - let newTextures = JSON.stringify({ "tex.picture": this.value }); - updateProperty(property.name, newTextures, property.isParticleProperty); - }; -} - - /** * PROPERTY ELEMENT CREATION FUNCTIONS */ @@ -3035,17 +3027,6 @@ function loaded() { // the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + selectedEntityProperties.id + '"'; - // HTML workaround since image is not yet a separate entity type - let IMAGE_MODEL_NAME = 'default-image-model.fbx'; - if (selectedEntityProperties.type === "Model") { - let urlParts = selectedEntityProperties.modelURL.split('/'); - let propsFilename = urlParts[urlParts.length - 1]; - - if (propsFilename === IMAGE_MODEL_NAME) { - selectedEntityProperties.type = "Image"; - } - } - showGroupsForType(selectedEntityProperties.type); for (let propertyID in properties) { @@ -3171,10 +3152,7 @@ function loaded() { updateVisibleSpaceModeProperties(); - if (selectedEntityProperties.type === "Image") { - let imageLink = JSON.parse(selectedEntityProperties.textures)["tex.picture"]; - getPropertyInputElement("image").value = imageLink; - } else if (selectedEntityProperties.type === "Material") { + if (selectedEntityProperties.type === "Material") { let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); @@ -3340,8 +3318,6 @@ function loaded() { } }); - getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction(properties['textures'])); - // Collapsible sections let elCollapsible = document.getElementsByClassName("collapse-icon"); diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index eeb16fd60d..edc6bc3845 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -164,7 +164,7 @@ EntityListTool = function(shouldUseEditTabletApp) { var cameraPosition = Camera.position; PROFILE("getMultipleProperties", function () { var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked', - 'visible', 'renderInfo', 'modelURL', 'materialURL', 'script']); + 'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script']); for (var i = 0; i < multipleProperties.length; i++) { var properties = multipleProperties[i]; @@ -174,6 +174,8 @@ EntityListTool = function(shouldUseEditTabletApp) { url = properties.modelURL; } else if (properties.type === "Material") { url = properties.materialURL; + } else if (properties.type === "Image") { + url = properties.imageURL; } entities.push({ id: ids[i],