From 078ca7edea471c40befd4323a1a7dae3f1b642c9 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Tue, 30 Jul 2019 09:59:45 -0700 Subject: [PATCH] supporting text effects + fonts on text entities --- interface/src/ui/overlays/QmlOverlay.cpp | 10 +- interface/src/ui/overlays/QmlOverlay.h | 6 +- interface/src/ui/overlays/TextOverlay.cpp | 15 +- .../src/avatars-renderer/Avatar.cpp | 28 +-- .../src/RenderableEntityItem.cpp | 23 +++ .../src/RenderableEntityItem.h | 1 + .../src/RenderableTextEntityItem.cpp | 73 +++++-- .../src/RenderableTextEntityItem.h | 5 + .../entities/src/EntityItemProperties.cpp | 67 ++++++ libraries/entities/src/EntityItemProperties.h | 6 + libraries/entities/src/EntityPropertyFlags.h | 4 + libraries/entities/src/TextEntityItem.cpp | 77 ++++++- libraries/entities/src/TextEntityItem.h | 19 ++ libraries/networking/src/udt/PacketHeaders.h | 1 + libraries/octree/src/OctreePacketData.h | 2 + libraries/render-utils/src/TextRenderer3D.cpp | 55 ++--- libraries/render-utils/src/TextRenderer3D.h | 40 ++-- libraries/render-utils/src/sdf_text3D.slf | 28 +-- libraries/render-utils/src/sdf_text3D.slh | 66 ++++-- libraries/render-utils/src/sdf_text3D.slv | 17 +- libraries/render-utils/src/text/EffectType.h | 15 -- libraries/render-utils/src/text/Font.cpp | 194 +++++++++++------- libraries/render-utils/src/text/Font.h | 55 +++-- .../render-utils/src/text/FontFamilies.h | 31 --- libraries/render-utils/src/text/Glyph.cpp | 1 + libraries/shared/src/FontFamilies.h | 17 ++ libraries/shared/src/TextEffect.cpp | 26 +++ libraries/shared/src/TextEffect.h | 42 ++++ 28 files changed, 622 insertions(+), 302 deletions(-) delete mode 100644 libraries/render-utils/src/text/EffectType.h delete mode 100644 libraries/render-utils/src/text/FontFamilies.h create mode 100644 libraries/shared/src/FontFamilies.h create mode 100644 libraries/shared/src/TextEffect.cpp create mode 100644 libraries/shared/src/TextEffect.h diff --git a/interface/src/ui/overlays/QmlOverlay.cpp b/interface/src/ui/overlays/QmlOverlay.cpp index f301a23d49..2afb29bb91 100644 --- a/interface/src/ui/overlays/QmlOverlay.cpp +++ b/interface/src/ui/overlays/QmlOverlay.cpp @@ -9,18 +9,10 @@ #include "QmlOverlay.h" #include +#include #include -#include -#include #include -#include -#include -#include -#include - -#include "Application.h" -#include "text/FontFamilies.h" QmlOverlay::QmlOverlay(const QUrl& url) { buildQmlElement(url); diff --git a/interface/src/ui/overlays/QmlOverlay.h b/interface/src/ui/overlays/QmlOverlay.h index 32badde28b..0d3c0982c3 100644 --- a/interface/src/ui/overlays/QmlOverlay.h +++ b/interface/src/ui/overlays/QmlOverlay.h @@ -9,10 +9,6 @@ #ifndef hifi_QmlOverlay_h #define hifi_QmlOverlay_h -#include -#include - -#include #include "Overlay2D.h" class QQuickItem; @@ -33,7 +29,7 @@ private: Q_INVOKABLE void buildQmlElement(const QUrl& url); protected: - QQuickItem* _qmlElement{ nullptr }; + QQuickItem* _qmlElement { nullptr }; }; #endif // hifi_QmlOverlay_h diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index 2f4353dae8..6760c918d3 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -11,18 +11,9 @@ #include "TextOverlay.h" #include +#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Application.h" -#include "text/FontFamilies.h" +#include "FontFamilies.h" QString const TextOverlay::TYPE = "text"; QUrl const TextOverlay::URL(QString("hifi/overlays/TextOverlay.qml")); @@ -44,7 +35,7 @@ QSizeF TextOverlay::textSize(const QString& text) const { ++lines; } } - QFont font(SANS_FONT_FAMILY); + QFont font(ROBOTO_FONT_FAMILY); font.setPixelSize(18); QFontMetrics fm(font); QSizeF result = QSizeF(fm.width(text), 18 * lines); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 5af98100dc..a7e4a0ae9f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -598,26 +598,6 @@ void Avatar::measureMotionDerivatives(float deltaTime) { } } -enum TextRendererType { - CHAT, - DISPLAYNAME -}; - -static TextRenderer3D* textRenderer(TextRendererType type) { - static TextRenderer3D* chatRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, -1, - false, SHADOW_EFFECT); - static TextRenderer3D* displayNameRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY); - - switch(type) { - case CHAT: - return chatRenderer; - case DISPLAYNAME: - return displayNameRenderer; - } - - return displayNameRenderer; -} - void Avatar::metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, const render::ItemIDs& subItemIDs) { render::Transaction transaction; @@ -1050,7 +1030,6 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const || (glm::dot(view.getDirection(), getDisplayNamePosition() - view.getPosition()) <= CLIP_DISTANCE)) { return; } - auto renderer = textRenderer(DISPLAYNAME); // optionally render timing stats for this avatar with the display name QString renderedDisplayName = _displayName; @@ -1065,7 +1044,8 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const } // Compute display name extent/position offset - const glm::vec2 extent = renderer->computeExtent(renderedDisplayName); + static TextRenderer3D* displayNameRenderer = TextRenderer3D::getInstance(ROBOTO_FONT_FAMILY); + const glm::vec2 extent = displayNameRenderer->computeExtent(renderedDisplayName); if (!glm::any(glm::isCompNull(extent, EPSILON))) { const QRect nameDynamicRect = QRect(0, 0, (int)extent.x, (int)extent.y); const int text_x = -nameDynamicRect.width() / 2; @@ -1104,11 +1084,11 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const QByteArray nameUTF8 = renderedDisplayName.toLocal8Bit(); // Render text slightly in front to avoid z-fighting - textTransform.postTranslate(glm::vec3(0.0f, 0.0f, SLIGHTLY_IN_FRONT * renderer->getFontSize())); + textTransform.postTranslate(glm::vec3(0.0f, 0.0f, SLIGHTLY_IN_FRONT * displayNameRenderer->getFontSize())); batch.setModelTransform(textTransform); { PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderText"); - renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor, glm::vec2(-1.0f), true, forward); + displayNameRenderer->draw(batch, text_x, -text_y, glm::vec2(-1.0f), nameUTF8.data(), textColor, true, forward); } } } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 11e369b532..5ebb0e2fd0 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -483,3 +483,26 @@ glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const Puls return result; } + +glm::vec3 EntityRenderer::calculatePulseColor(const glm::vec3& color, const PulsePropertyGroup& pulseProperties, quint64 start) { + if (pulseProperties.getPeriod() == 0.0f || (pulseProperties.getColorMode() == PulseMode::NONE && pulseProperties.getAlphaMode() == PulseMode::NONE)) { + return color; + } + + float t = ((float)(usecTimestampNow() - start)) / ((float)USECS_PER_SECOND); + float pulse = 0.5f * (cosf(t * (2.0f * (float)M_PI) / pulseProperties.getPeriod()) + 1.0f) * (pulseProperties.getMax() - pulseProperties.getMin()) + pulseProperties.getMin(); + float outPulse = (1.0f - pulse); + + glm::vec3 result = color; + if (pulseProperties.getColorMode() == PulseMode::IN_PHASE) { + result.r *= pulse; + result.g *= pulse; + result.b *= pulse; + } else if (pulseProperties.getColorMode() == PulseMode::OUT_PHASE) { + result.r *= outPulse; + result.g *= outPulse; + result.b *= outPulse; + } + + return result; +} diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 1db4cfdf53..7f06547cdc 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -61,6 +61,7 @@ public: virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); } static glm::vec4 calculatePulseColor(const glm::vec4& color, const PulsePropertyGroup& pulseProperties, quint64 start); + static glm::vec3 calculatePulseColor(const glm::vec3& color, const PulsePropertyGroup& pulseProperties, quint64 start); virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) const override; virtual Item::Bound getBound() override; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 0ead1de71a..d8099df0fe 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -30,7 +30,7 @@ const float LINE_SCALE_RATIO = 1.2f; TextEntityRenderer::TextEntityRenderer(const EntityItemPointer& entity) : Parent(entity), - _textRenderer(TextRenderer3D::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f)) { + _textRenderer(TextRenderer3D::getInstance(ROBOTO_FONT_FAMILY)) { auto geometryCache = DependencyManager::get(); if (geometryCache) { _geometryID = geometryCache->allocateID(); @@ -96,7 +96,6 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint if (_text != entity->getText()) { return true; } - if (_lineHeight != entity->getLineHeight()) { return true; } @@ -104,15 +103,12 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint if (_textColor != toGlm(entity->getTextColor())) { return true; } - if (_textAlpha != entity->getTextAlpha()) { return true; } - if (_backgroundColor != toGlm(entity->getBackgroundColor())) { return true; } - if (_backgroundAlpha != entity->getBackgroundAlpha()) { return true; } @@ -128,15 +124,12 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint if (_leftMargin != entity->getLeftMargin()) { return true; } - if (_rightMargin != entity->getRightMargin()) { return true; } - if (_topMargin != entity->getTopMargin()) { return true; } - if (_bottomMargin != entity->getBottomMargin()) { return true; } @@ -145,6 +138,20 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return true; } + if (_font != entity->getFont()) { + return true; + } + + if (_effect != entity->getTextEffect()) { + return true; + } + if (_effectColor != toGlm(entity->getTextEffectColor())) { + return true; + } + if (_effectThickness != entity->getTextEffectThickness()) { + return true; + } + if (_pulseProperties != entity->getPulseProperties()) { return true; } @@ -179,6 +186,10 @@ void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointe _topMargin = entity->getTopMargin(); _bottomMargin = entity->getBottomMargin(); _unlit = entity->getUnlit(); + _font = entity->getFont(); + _effect = entity->getTextEffect(); + _effectColor = toGlm(entity->getTextEffectColor()); + _effectThickness = entity->getTextEffectThickness(); updateTextRenderItem(); }); } @@ -282,7 +293,22 @@ ItemKey entities::TextPayload::getKey() const { auto renderable = entityTreeRenderer->renderableForEntityId(_entityID); if (renderable) { auto textRenderable = std::static_pointer_cast(renderable); - return ItemKey::Builder(textRenderable->getKey()).withoutMetaCullGroup().withSubMetaCulled(); + + // Similar to RenderableEntityItem::getKey() + ItemKey::Builder builder = ItemKey::Builder().withTypeShape().withTypeMeta().withTagBits(textRenderable->getTagMask()).withLayer(textRenderable->getHifiRenderLayer()); + builder.withSubMetaCulled(); + + if (textRenderable->isTextTransparent()) { + builder.withTransparent(); + } else if (textRenderable->_canCastShadow) { + builder.withShadowCaster(); + } + + if (!textRenderable->_visible) { + builder.withInvisible(); + } + + return builder; } } return ItemKey::Builder::opaqueShape(); @@ -342,27 +368,40 @@ void entities::TextPayload::render(RenderArgs* args) { } auto textRenderable = std::static_pointer_cast(renderable); - glm::vec4 textColor; Transform modelTransform; - BillboardMode billboardMode; - float lineHeight, leftMargin, rightMargin, topMargin, bottomMargin; - QString text; glm::vec3 dimensions; + BillboardMode billboardMode; + + QString text; + glm::vec4 textColor; + QString font; + TextEffect effect; + glm::vec3 effectColor; + float effectThickness; + float lineHeight, leftMargin, rightMargin, topMargin, bottomMargin; bool forward; textRenderable->withReadLock([&] { modelTransform = textRenderable->_renderTransform; + dimensions = textRenderable->_dimensions; billboardMode = textRenderable->_billboardMode; + + text = textRenderable->_text; + font = textRenderable->_font; + effect = textRenderable->_effect; + effectThickness = textRenderable->_effectThickness; + lineHeight = textRenderable->_lineHeight; leftMargin = textRenderable->_leftMargin; rightMargin = textRenderable->_rightMargin; topMargin = textRenderable->_topMargin; bottomMargin = textRenderable->_bottomMargin; - text = textRenderable->_text; - dimensions = textRenderable->_dimensions; float fadeRatio = textRenderable->_isFading ? Interpolate::calculateFadeRatio(textRenderable->_fadeStartTime) : 1.0f; textColor = glm::vec4(textRenderable->_textColor, fadeRatio * textRenderable->_textAlpha); textColor = EntityRenderer::calculatePulseColor(textColor, textRenderable->_pulseProperties, textRenderable->_created); + + effectColor = EntityRenderer::calculatePulseColor(textRenderable->_effectColor, textRenderable->_pulseProperties, textRenderable->_created); + forward = textRenderable->_renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD; }); @@ -378,7 +417,9 @@ void entities::TextPayload::render(RenderArgs* args) { batch.setModelTransform(modelTransform); glm::vec2 bounds = glm::vec2(dimensions.x - (leftMargin + rightMargin), dimensions.y - (topMargin + bottomMargin)); - textRenderer->draw(batch, leftMargin / scale, -topMargin / scale, text, textColor, bounds / scale, textRenderable->_unlit, forward); + textRenderer->draw(batch, leftMargin / scale, -topMargin / scale, bounds / scale, scale, + text, font, textColor, effectColor, effectThickness, effect, + textRenderable->_unlit, forward); } namespace render { diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index c62851a876..63cf3e6e9e 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -67,6 +67,11 @@ private: BillboardMode _billboardMode; glm::vec3 _dimensions; + QString _font { "" }; + TextEffect _effect { TextEffect::NO_EFFECT }; + glm::vec3 _effectColor { 0 }; + float _effectThickness { 0.0f }; + int _geometryID { 0 }; std::shared_ptr _textPayload; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index b89788040f..42ce276aca 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -299,6 +299,24 @@ void EntityItemProperties::setAvatarPriorityFromString(const QString& mode) { } } +inline void addTextEffect(QHash& lookup, TextEffect effect) { lookup[TextEffectHelpers::getNameForTextEffect(effect)] = effect; } +const QHash stringToTextEffectLookup = [] { + QHash toReturn; + addTextEffect(toReturn, TextEffect::NO_EFFECT); + addTextEffect(toReturn, TextEffect::OUTLINE_EFFECT); + addTextEffect(toReturn, TextEffect::OUTLINE_WITH_FILL_EFFECT); + addTextEffect(toReturn, TextEffect::SHADOW_EFFECT); + return toReturn; +}(); +QString EntityItemProperties::getTextEffectAsString() const { return TextEffectHelpers::getNameForTextEffect(_textEffect); } +void EntityItemProperties::setTextEffectFromString(const QString& effect) { + auto textEffectItr = stringToTextEffectLookup.find(effect.toLower()); + if (textEffectItr != stringToTextEffectLookup.end()) { + _textEffect = textEffectItr.value(); + _textEffectChanged = true; + } +} + QString getCollisionGroupAsString(uint16_t group) { switch (group) { case USER_COLLISION_GROUP_DYNAMIC: @@ -528,6 +546,10 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_TOP_MARGIN, topMargin); CHECK_PROPERTY_CHANGE(PROP_BOTTOM_MARGIN, bottomMargin); CHECK_PROPERTY_CHANGE(PROP_UNLIT, unlit); + CHECK_PROPERTY_CHANGE(PROP_FONT, font); + CHECK_PROPERTY_CHANGE(PROP_TEXT_EFFECT, textEffect); + CHECK_PROPERTY_CHANGE(PROP_TEXT_EFFECT_COLOR, textEffectColor); + CHECK_PROPERTY_CHANGE(PROP_TEXT_EFFECT_THICKNESS, textEffectThickness); // Zone changedProperties += _keyLight.getChangedProperties(); @@ -1287,6 +1309,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} bottomMargin=0.0 - The bottom margin, in meters. * @property {boolean} unlit=false - true if the entity should be unaffected by lighting. Otherwise, the text * is lit by the keylight and local lights. + * @property {string} font="" - The text is rendered with this font. Can be one of the following: CourierInconsolata, Roboto, Timeless, or a path to a .sdff file. + * @property {TextEffect} textEffect="none" - The effect that is applied to the text. + * @property {Color} textEffectColor=255,255,255 - The color of the effect. + * @property {number} textEffectThickness=0.2 - The magnitude of the text effect, range 0.00.5. * @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera. * @property {boolean} faceCamera - true if billboardMode is "yaw", false * if it isn't. Setting this property to false sets the billboardMode to "none". @@ -1727,6 +1754,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TOP_MARGIN, topMargin); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BOTTOM_MARGIN, bottomMargin); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_UNLIT, unlit); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FONT, font); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_TEXT_EFFECT, textEffect, getTextEffectAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_TEXT_EFFECT_COLOR, textEffectColor, u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT_EFFECT_THICKNESS, textEffectThickness); } // Zones only @@ -2103,6 +2134,10 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(topMargin, float, setTopMargin); COPY_PROPERTY_FROM_QSCRIPTVALUE(bottomMargin, float, setBottomMargin); COPY_PROPERTY_FROM_QSCRIPTVALUE(unlit, bool, setUnlit); + COPY_PROPERTY_FROM_QSCRIPTVALUE(font, QString, setFont); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(textEffect, TextEffect); + COPY_PROPERTY_FROM_QSCRIPTVALUE(textEffectColor, u8vec3Color, setTextEffectColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE(textEffectThickness, float, setTextEffectThickness); // Zone _keyLight.copyFromScriptValue(object, _defaultSettings); @@ -2387,6 +2422,10 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(topMargin); COPY_PROPERTY_IF_CHANGED(bottomMargin); COPY_PROPERTY_IF_CHANGED(unlit); + COPY_PROPERTY_IF_CHANGED(font); + COPY_PROPERTY_IF_CHANGED(textEffect); + COPY_PROPERTY_IF_CHANGED(textEffectColor); + COPY_PROPERTY_IF_CHANGED(textEffectThickness); // Zone _keyLight.merge(other._keyLight); @@ -2746,6 +2785,10 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_TOP_MARGIN, TopMargin, topMargin, float); ADD_PROPERTY_TO_MAP(PROP_BOTTOM_MARGIN, BottomMargin, bottomMargin, float); ADD_PROPERTY_TO_MAP(PROP_UNLIT, Unlit, unlit, bool); + ADD_PROPERTY_TO_MAP(PROP_FONT, Font, font, QString); + ADD_PROPERTY_TO_MAP(PROP_TEXT_EFFECT, TextEffect, textEffect, TextEffect); + ADD_PROPERTY_TO_MAP(PROP_TEXT_EFFECT_COLOR, TextEffectColor, textEffectColor, u8vec3Color); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_TEXT_EFFECT_THICKNESS, TextEffectThickness, textEffectThickness, float, 0.0, 0.5); // Zone { // Keylight @@ -3178,6 +3221,10 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_TOP_MARGIN, properties.getTopMargin()); APPEND_ENTITY_PROPERTY(PROP_BOTTOM_MARGIN, properties.getBottomMargin()); APPEND_ENTITY_PROPERTY(PROP_UNLIT, properties.getUnlit()); + APPEND_ENTITY_PROPERTY(PROP_FONT, properties.getFont()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT, (uint32_t)properties.getTextEffect()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_COLOR, properties.getTextEffectColor()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, properties.getTextEffectThickness()); } if (properties.getType() == EntityTypes::Zone) { @@ -3657,6 +3704,10 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TOP_MARGIN, float, setTopMargin); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BOTTOM_MARGIN, float, setBottomMargin); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_UNLIT, bool, setUnlit); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FONT, QString, setFont); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_EFFECT, TextEffect, setTextEffect); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_EFFECT_COLOR, u8vec3Color, setTextEffectColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_EFFECT_THICKNESS, float, setTextEffectThickness); } if (properties.getType() == EntityTypes::Zone) { @@ -4050,6 +4101,10 @@ void EntityItemProperties::markAllChanged() { _topMarginChanged = true; _bottomMarginChanged = true; _unlitChanged = true; + _fontChanged = true; + _textEffectChanged = true; + _textEffectColorChanged = true; + _textEffectThicknessChanged = true; // Zone _keyLight.markAllChanged(); @@ -4642,6 +4697,18 @@ QList EntityItemProperties::listChangedProperties() { if (unlitChanged()) { out += "unlit"; } + if (fontChanged()) { + out += "font"; + } + if (textEffectChanged()) { + out += "textEffect"; + } + if (textEffectColorChanged()) { + out += "textEffectColor"; + } + if (textEffectThicknessChanged()) { + out += "textEffectThickness"; + } // Zone getKeyLight().listChangedProperties(out); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 456ee3cdec..63d8183899 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -32,6 +32,7 @@ #include #include #include +#include "FontFamilies.h" #include "EntityItemID.h" #include "EntityItemPropertiesDefaults.h" @@ -64,6 +65,7 @@ #include "PrimitiveMode.h" #include "WebInputMode.h" #include "GizmoType.h" +#include "TextEffect.h" const quint64 UNKNOWN_CREATED_TIME = 0; @@ -315,6 +317,10 @@ public: DEFINE_PROPERTY_REF(PROP_TOP_MARGIN, TopMargin, topMargin, float, TextEntityItem::DEFAULT_MARGIN); DEFINE_PROPERTY_REF(PROP_BOTTOM_MARGIN, BottomMargin, bottomMargin, float, TextEntityItem::DEFAULT_MARGIN); DEFINE_PROPERTY_REF(PROP_UNLIT, Unlit, unlit, bool, false); + DEFINE_PROPERTY_REF(PROP_FONT, Font, font, QString, ROBOTO_FONT_FAMILY); + DEFINE_PROPERTY_REF_ENUM(PROP_TEXT_EFFECT, TextEffect, textEffect, TextEffect, TextEffect::NO_EFFECT); + DEFINE_PROPERTY_REF(PROP_TEXT_EFFECT_COLOR, TextEffectColor, textEffectColor, u8vec3Color, TextEntityItem::DEFAULT_TEXT_COLOR); + DEFINE_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, TextEffectThickness, textEffectThickness, float, TextEntityItem::DEFAULT_TEXT_EFFECT_THICKNESS); // Zone DEFINE_PROPERTY_GROUP(KeyLight, keyLight, KeyLightPropertyGroup); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 8d3833f364..d5af337a7d 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -244,6 +244,10 @@ enum EntityPropertyList { PROP_TOP_MARGIN = PROP_DERIVED_8, PROP_BOTTOM_MARGIN = PROP_DERIVED_9, PROP_UNLIT = PROP_DERIVED_10, + PROP_FONT = PROP_DERIVED_11, + PROP_TEXT_EFFECT = PROP_DERIVED_12, + PROP_TEXT_EFFECT_COLOR = PROP_DERIVED_13, + PROP_TEXT_EFFECT_THICKNESS = PROP_DERIVED_14, // Zone // Keylight diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 08200084f4..d237071a17 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -29,6 +29,7 @@ const glm::u8vec3 TextEntityItem::DEFAULT_TEXT_COLOR = { 255, 255, 255 }; const float TextEntityItem::DEFAULT_TEXT_ALPHA = 1.0f; const glm::u8vec3 TextEntityItem::DEFAULT_BACKGROUND_COLOR = { 0, 0, 0}; const float TextEntityItem::DEFAULT_MARGIN = 0.0f; +const float TextEntityItem::DEFAULT_TEXT_EFFECT_THICKNESS = 0.2f; EntityItemPointer TextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new TextEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); @@ -65,6 +66,10 @@ EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& de COPY_ENTITY_PROPERTY_TO_PROPERTIES(topMargin, getTopMargin); COPY_ENTITY_PROPERTY_TO_PROPERTIES(bottomMargin, getBottomMargin); COPY_ENTITY_PROPERTY_TO_PROPERTIES(unlit, getUnlit); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(font, getFont); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(textEffect, getTextEffect); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(textEffectColor, getTextEffectColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(textEffectThickness, getTextEffectThickness); return properties; } @@ -89,6 +94,10 @@ bool TextEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(topMargin, setTopMargin); SET_ENTITY_PROPERTY_FROM_PROPERTIES(bottomMargin, setBottomMargin); SET_ENTITY_PROPERTY_FROM_PROPERTIES(unlit, setUnlit); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(font, setFont); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffect, setTextEffect); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffectColor, setTextEffectColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffectThickness, setTextEffectThickness); if (somethingChanged) { bool wantDebug = false; @@ -132,6 +141,10 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_TOP_MARGIN, float, setTopMargin); READ_ENTITY_PROPERTY(PROP_BOTTOM_MARGIN, float, setBottomMargin); READ_ENTITY_PROPERTY(PROP_UNLIT, bool, setUnlit); + READ_ENTITY_PROPERTY(PROP_FONT, QString, setFont); + READ_ENTITY_PROPERTY(PROP_TEXT_EFFECT, TextEffect, setTextEffect); + READ_ENTITY_PROPERTY(PROP_TEXT_EFFECT_COLOR, glm::u8vec3, setTextEffectColor); + READ_ENTITY_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, float, setTextEffectThickness); return bytesRead; } @@ -153,6 +166,10 @@ EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_TOP_MARGIN; requestedProperties += PROP_BOTTOM_MARGIN; requestedProperties += PROP_UNLIT; + requestedProperties += PROP_FONT; + requestedProperties += PROP_TEXT_EFFECT; + requestedProperties += PROP_TEXT_EFFECT_COLOR; + requestedProperties += PROP_TEXT_EFFECT_THICKNESS; return requestedProperties; } @@ -184,6 +201,10 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_TOP_MARGIN, getTopMargin()); APPEND_ENTITY_PROPERTY(PROP_BOTTOM_MARGIN, getBottomMargin()); APPEND_ENTITY_PROPERTY(PROP_UNLIT, getUnlit()); + APPEND_ENTITY_PROPERTY(PROP_FONT, getFont()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT, (uint32_t)getTextEffect()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_COLOR, getTextEffectColor()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, getTextEffectThickness()); } glm::vec3 TextEntityItem::getRaycastDimensions() const { @@ -255,12 +276,10 @@ void TextEntityItem::setText(const QString& value) { }); } -QString TextEntityItem::getText() const { - QString result; - withReadLock([&] { - result = _text; +QString TextEntityItem::getText() const { + return resultWithReadLock([&] { + return _text; }); - return result; } void TextEntityItem::setLineHeight(float value) { @@ -399,6 +418,54 @@ bool TextEntityItem::getUnlit() const { }); } +void TextEntityItem::setFont(const QString& value) { + withWriteLock([&] { + _font = value; + }); +} + +QString TextEntityItem::getFont() const { + return resultWithReadLock([&] { + return _font; + }); +} + +void TextEntityItem::setTextEffect(TextEffect value) { + withWriteLock([&] { + _effect = value; + }); +} + +TextEffect TextEntityItem::getTextEffect() const { + return resultWithReadLock([&] { + return _effect; + }); +} + +void TextEntityItem::setTextEffectColor(const glm::u8vec3& value) { + withWriteLock([&] { + _effectColor = value; + }); +} + +glm::u8vec3 TextEntityItem::getTextEffectColor() const { + return resultWithReadLock([&] { + return _effectColor; + }); +} + +void TextEntityItem::setTextEffectThickness(float value) { + withWriteLock([&] { + _effectThickness = value; + }); +} + +float TextEntityItem::getTextEffectThickness() const { + return resultWithReadLock([&] { + return _effectThickness; + }); +} + PulsePropertyGroup TextEntityItem::getPulseProperties() const { return resultWithReadLock([&] { return _pulseProperties; diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index a962482cde..5e3f6d7c02 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -15,6 +15,7 @@ #include "EntityItem.h" #include "PulsePropertyGroup.h" +#include "TextEffect.h" class TextEntityItem : public EntityItem { public: @@ -100,6 +101,19 @@ public: bool getUnlit() const; void setUnlit(bool value); + void setFont(const QString& value); + QString getFont() const; + + TextEffect getTextEffect() const; + void setTextEffect(TextEffect value); + + glm::u8vec3 getTextEffectColor() const; + void setTextEffectColor(const glm::u8vec3& value); + + static const float DEFAULT_TEXT_EFFECT_THICKNESS; + float getTextEffectThickness() const; + void setTextEffectThickness(float value); + PulsePropertyGroup getPulseProperties() const; private: @@ -117,6 +131,11 @@ private: float _topMargin; float _bottomMargin; bool _unlit; + + QString _font; + TextEffect _effect; + glm::u8vec3 _effectColor; + float _effectThickness; }; #endif // hifi_TextEntityItem_h diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 2d184fe592..dc1c79e0eb 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -273,6 +273,7 @@ enum class EntityVersion : PacketVersion { PrivateUserData, TextUnlit, ShadowBiasAndDistance, + TextEntityFonts, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index a2fe886810..46e5de9bda 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -40,6 +40,7 @@ #include "WebInputMode.h" #include "PulseMode.h" #include "GizmoType.h" +#include "TextEffect.h" #include "OctreeConstants.h" #include "OctreeElement.h" @@ -273,6 +274,7 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, WebInputMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, PulseMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, GizmoType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, TextEffect& 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); diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 8c514df91d..264ff409a6 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -10,45 +10,19 @@ // #include "TextRenderer3D.h" -#include -#include - -#include -#include -#include -#include - -#include #include "text/Font.h" -#include "GLMHelpers.h" -#include "MatrixStack.h" -#include "RenderUtilsLogging.h" - -#include "GeometryCache.h" - -const float TextRenderer3D::DEFAULT_POINT_SIZE = 12; - -TextRenderer3D* TextRenderer3D::getInstance(const char* family, float pointSize, - bool bold, bool italic, EffectType effect, int effectThickness) { - return new TextRenderer3D(family, pointSize, false, italic, effect, effectThickness); +TextRenderer3D* TextRenderer3D::getInstance(const char* family) { + return new TextRenderer3D(family); } -TextRenderer3D::TextRenderer3D(const char* family, float pointSize, int weight, bool italic, - EffectType effect, int effectThickness) : - _effectType(effect), - _effectThickness(effectThickness), +TextRenderer3D::TextRenderer3D(const char* family) : + _family(family), _font(Font::load(family)) { if (!_font) { qWarning() << "Unable to load font with family " << family; - _font = Font::load("Courier"); - } - if (1 != _effectThickness) { - qWarning() << "Effect thickness not currently supported"; - } - if (NO_EFFECT != _effectType && OUTLINE_EFFECT != _effectType) { - qWarning() << "Effect type not currently supported"; + _font = Font::load(ROBOTO_FONT_FAMILY); } } @@ -66,12 +40,21 @@ float TextRenderer3D::getFontSize() const { return 0.0f; } -void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - const glm::vec2& bounds, bool unlit, bool forward) { - // The font does all the OpenGL work +void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, + const QString& str, const glm::vec4& color, bool unlit, bool forward) { if (_font) { - _color = color; - _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, unlit, forward); + _font->drawString(batch, _drawInfo, str, color, glm::vec3(0.0f), 0, TextEffect::NO_EFFECT, { x, y }, bounds, 1.0f, unlit, forward); } } +void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, float scale, + const QString& str, const QString& font, const glm::vec4& color, const glm::vec3& effectColor, + float effectThickness, TextEffect effect, bool unlit, bool forward) { + if (font != _family) { + _family = font; + _font = Font::load(_family); + } + if (_font) { + _font->drawString(batch, _drawInfo, str, color, effectColor, effectThickness, effect, { x, y }, bounds, scale, unlit, forward); + } +} \ No newline at end of file diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 8118aa883c..3a88ed555c 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -14,48 +14,32 @@ #include #include -#include -#include - -namespace gpu { -class Batch; -} -class Font; #include "text/Font.h" -#include "text/EffectType.h" -#include "text/FontFamilies.h" +#include "TextEffect.h" +#include "FontFamilies.h" -// TextRenderer3D is actually a fairly thin wrapper around a Font class -// defined in the cpp file. class TextRenderer3D { public: - static const float DEFAULT_POINT_SIZE; - - static TextRenderer3D* getInstance(const char* family, float pointSize = DEFAULT_POINT_SIZE, - bool bold = false, bool italic = false, EffectType effect = NO_EFFECT, int effectThickness = 1); + static TextRenderer3D* getInstance(const char* family); glm::vec2 computeExtent(const QString& str) const; float getFontSize() const; // Pixel size - void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - const glm::vec2& bounds, bool unlit, bool forward); + void draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, + const QString& str, const glm::vec4& color, bool unlit, bool forward); + void draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, float scale, + const QString& str, const QString& font, const glm::vec4& color, const glm::vec3& effectColor, + float effectThickness, TextEffect effect, bool unlit, bool forward); private: - TextRenderer3D(const char* family, float pointSize, int weight = -1, bool italic = false, - EffectType effect = NO_EFFECT, int effectThickness = 1); + TextRenderer3D(const char* family); - // the type of effect to apply - const EffectType _effectType; + QString _family; - // the thickness of the effect - const int _effectThickness; - - // text color - glm::vec4 _color; - Font::DrawInfo _drawInfo; std::shared_ptr _font; + Font::DrawInfo _drawInfo; + }; - #endif // hifi_TextRenderer3D_h diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index ac064e5c8f..c5bed1ecab 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -36,29 +36,29 @@ layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; <@endif@> layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; -layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; #define _texCoord0 _texCoord01.xy #define _texCoord1 _texCoord01.zw +layout(location=RENDER_UTILS_ATTR_FADE1) flat in vec4 _glyphBounds; // we're reusing the fade texcoord locations here void main() { - float alpha = evalSDFSuperSampled(_texCoord0); + vec4 color = evalSDFSuperSampled(_texCoord0, _glyphBounds); <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> - alpha *= _color.a; - if (alpha <= 0.0) { + color.a *= params.color.a; + if (color.a <= 0.0) { discard; } <@endif@> <@if HIFI_USE_UNLIT@> <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> - _fragColor0 = vec4(_color.rgb * isUnlitEnabled(), alpha); + _fragColor0 = vec4(color.rgb * isUnlitEnabled(), color.a); <@else@> packDeferredFragmentUnlit( normalize(_normalWS), - alpha, - _color.rgb); + color.a, + color.rgb); <@endif@> <@else@> <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> @@ -72,12 +72,12 @@ void main() { DEFAULT_OCCLUSION, fragPosition, normalize(_normalWS), - _color.rgb, + color.rgb, DEFAULT_FRESNEL, DEFAULT_METALLIC, DEFAULT_EMISSIVE, - DEFAULT_ROUGHNESS, alpha), - alpha); + DEFAULT_ROUGHNESS, color.a), + color.a); <@else@> _fragColor0 = vec4(evalSkyboxGlobalColor( cam._viewInverse, @@ -85,17 +85,17 @@ void main() { DEFAULT_OCCLUSION, fragPosition, normalize(_normalWS), - _color.rgb, + color.rgb, DEFAULT_FRESNEL, DEFAULT_METALLIC, DEFAULT_ROUGHNESS), - alpha); + color.a); <@endif@> <@else@> packDeferredFragment( normalize(_normalWS), - alpha, - _color.rgb, + color.a, + color.rgb, DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, diff --git a/libraries/render-utils/src/sdf_text3D.slh b/libraries/render-utils/src/sdf_text3D.slh index 3297596efd..76ace99182 100644 --- a/libraries/render-utils/src/sdf_text3D.slh +++ b/libraries/render-utils/src/sdf_text3D.slh @@ -14,11 +14,16 @@ <@if not SDF_TEXT3D_SLH@> <@def SDF_TEXT3D_SLH@> -LAYOUT(binding=0) uniform sampler2D Font; +LAYOUT(binding=0) uniform sampler2D fontTexture; struct TextParams { vec4 color; - vec4 outline; + + vec3 effectColor; + float effectThickness; + + int effect; + vec3 spare; }; LAYOUT(binding=0) uniform textParamsBuffer { @@ -29,32 +34,57 @@ LAYOUT(binding=0) uniform textParamsBuffer { #define TAA_TEXTURE_LOD_BIAS -3.0 -const float interiorCutoff = 0.8; -const float outlineExpansion = 0.2; +const float interiorCutoff = 0.5; const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS); -float evalSDF(vec2 texCoord) { - // retrieve signed distance - float sdf = textureLod(Font, texCoord, TAA_TEXTURE_LOD_BIAS).g; - sdf = mix(sdf, mix(sdf + outlineExpansion, 1.0 - sdf, float(sdf > interiorCutoff)), float(params.outline.x > 0.0)); +vec4 evalSDF(vec2 texCoord, vec4 glyphBounds) { + vec3 color = params.color.rgb; + float sdf = textureLod(fontTexture, texCoord, TAA_TEXTURE_LOD_BIAS).g; - // Rely on TAA for anti-aliasing - return step(0.5, sdf); + // Outline + if (params.effect == 1 || params.effect == 2) { + float outline = float(sdf < interiorCutoff); + color = mix(color, params.effectColor, outline); + + // with or without fill + sdf = mix(sdf, 0.0, float(params.effect == 1) * (1.0 - outline)); + + const float EPSILON = 0.00001; + sdf += mix(0.0, params.effectThickness - EPSILON, outline); + } else if (params.effect == 3) { // Shadow + // don't sample from outside of our glyph bounds + sdf *= mix(1.0, 0.0, float(clamp(texCoord, glyphBounds.xy, glyphBounds.xy + glyphBounds.zw) != texCoord)); + + if (sdf < interiorCutoff) { + color = params.effectColor; + const float DOUBLE_MAX_OFFSET_PIXELS = 20.0; // must match value in Font.cpp + // FIXME: TAA_TEXTURE_LOD_BIAS doesn't have any effect because we're only generating one mip, so here we need to use 0, but it should + // really match the LOD that we use in the textureLod call below + vec2 textureOffset = vec2(params.effectThickness * DOUBLE_MAX_OFFSET_PIXELS) / vec2(textureSize(fontTexture, 0/*int(TAA_TEXTURE_LOD_BIAS)*/)); + vec2 shadowTexCoords = texCoord - textureOffset; + sdf = textureLod(fontTexture, shadowTexCoords, TAA_TEXTURE_LOD_BIAS).g; + + // don't sample from outside of our glyph bounds + sdf *= mix(1.0, 0.0, float(clamp(shadowTexCoords, glyphBounds.xy, glyphBounds.xy + glyphBounds.zw) != shadowTexCoords)); + } + } + + return vec4(color, sdf); } -float evalSDFSuperSampled(vec2 texCoord) { +vec4 evalSDFSuperSampled(vec2 texCoord, vec4 glyphBounds) { vec2 dxTexCoord = dFdx(texCoord) * 0.5 * taaBias; vec2 dyTexCoord = dFdy(texCoord) * 0.5 * taaBias; // Perform 4x supersampling for anisotropic filtering - float a; - a = evalSDF(texCoord); - a += evalSDF(texCoord + dxTexCoord); - a += evalSDF(texCoord + dyTexCoord); - a += evalSDF(texCoord + dxTexCoord + dyTexCoord); - a *= 0.25; + vec4 color; + color = evalSDF(texCoord, glyphBounds); + color += evalSDF(texCoord + dxTexCoord, glyphBounds); + color += evalSDF(texCoord + dyTexCoord, glyphBounds); + color += evalSDF(texCoord + dxTexCoord + dyTexCoord, glyphBounds); + color *= 0.25; - return a; + return vec4(color.rgb, step(interiorCutoff, color.a)); } <@endfunc@> diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv index 731cbc2cad..a6cb43f038 100644 --- a/libraries/render-utils/src/sdf_text3D.slv +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -23,19 +23,28 @@ layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; <@endif@> layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; -layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_FADE1) flat out vec4 _glyphBounds; // we're reusing the fade texcoord locations here void main() { _texCoord01 = vec4(inTexCoord0.st, 0.0, 0.0); - _color = color_sRGBAToLinear(params.color); + _glyphBounds = inTexCoord1; + + vec4 position = inPosition; + // if we're in shadow mode, we need to move each subsequent quad slightly forward so it doesn't z-fight + // with the shadows of the letters before it + if (params.effect == 3) { // Shadow + const int VERTICES_PER_QUAD = 4; // must match value in Font.cpp + const float EPSILON = 0.001; + position.z += floor(gl_VertexID / VERTICES_PER_QUAD) * EPSILON; + } TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> - <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> + <$transformModelToEyeAndClipPos(cam, obj, position, _positionES, gl_Position)$> <@else@> - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToClipPos(cam, obj, position, gl_Position)$> <@endif@> const vec3 normal = vec3(0, 0, 1); diff --git a/libraries/render-utils/src/text/EffectType.h b/libraries/render-utils/src/text/EffectType.h deleted file mode 100644 index 63ec820036..0000000000 --- a/libraries/render-utils/src/text/EffectType.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/07/16 -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#pragma once -#ifndef hifi_EffectType_h -#define hifi_EffectType_h - -enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT }; - -#endif diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index eee6a7daea..f9ca9d4cae 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -13,6 +14,8 @@ #include "FontFamilies.h" #include "../StencilMaskPass.h" +#include "NetworkAccessManager.h" + static std::mutex fontMutex; std::map, gpu::PipelinePointer> Font::_pipelines; @@ -21,67 +24,90 @@ gpu::Stream::FormatPointer Font::_format; struct TextureVertex { glm::vec2 pos; glm::vec2 tex; + glm::vec4 bounds; TextureVertex() {} - TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {} + TextureVertex(const glm::vec2& pos, const glm::vec2& tex, const glm::vec4& bounds) : pos(pos), tex(tex), bounds(bounds) {} }; -static const int NUMBER_OF_INDICES_PER_QUAD = 6; // 1 quad = 2 triangles -static const int VERTICES_PER_QUAD = 4; // 1 quad = 4 vertices +static const int NUMBER_OF_INDICES_PER_QUAD = 6; // 1 quad = 2 triangles +static const int VERTICES_PER_QUAD = 4; // 1 quad = 4 vertices (must match value in sdf_text3D.slv) +const float DOUBLE_MAX_OFFSET_PIXELS = 20.0f; // must match value in sdf_text3D.slh struct QuadBuilder { TextureVertex vertices[VERTICES_PER_QUAD]; - QuadBuilder(const glm::vec2& min, const glm::vec2& size, - const glm::vec2& texMin, const glm::vec2& texSize) { + QuadBuilder(const Glyph& glyph, const glm::vec2& offset, float scale, bool enlargeForShadows) { + glm::vec2 min = offset + glm::vec2(glyph.offset.x, glyph.offset.y - glyph.size.y); + glm::vec2 size = glyph.size; + glm::vec2 texMin = glyph.texOffset; + glm::vec2 texSize = glyph.texSize; + + // We need the pre-adjustment bounds for clamping + glm::vec4 bounds = glm::vec4(texMin, texSize); + if (enlargeForShadows) { + glm::vec2 imageSize = glyph.size / glyph.texSize; + glm::vec2 sizeDelta = 0.5f * DOUBLE_MAX_OFFSET_PIXELS * scale * imageSize; + glm::vec2 oldSize = size; + size += sizeDelta; + min.y -= sizeDelta.y; + + texSize = texSize * (size / oldSize); + } + // min = bottomLeft vertices[0] = TextureVertex(min, - texMin + glm::vec2(0.0f, texSize.y)); + texMin + glm::vec2(0.0f, texSize.y), bounds); vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f), - texMin + texSize); + texMin + texSize, bounds); vertices[2] = TextureVertex(min + glm::vec2(0.0f, size.y), - texMin); + texMin, bounds); vertices[3] = TextureVertex(min + size, - texMin + glm::vec2(texSize.x, 0.0f)); + texMin + glm::vec2(texSize.x, 0.0f), bounds); } - QuadBuilder(const Glyph& glyph, const glm::vec2& offset) : - QuadBuilder(offset + glm::vec2(glyph.offset.x, glyph.offset.y - glyph.size.y), glyph.size, - glyph.texOffset, glyph.texSize) {} }; - - -static QHash LOADED_FONTS; - Font::Pointer Font::load(QIODevice& fontFile) { Pointer font = std::make_shared(); font->read(fontFile); return font; } +static QHash LOADED_FONTS; + Font::Pointer Font::load(const QString& family) { std::lock_guard lock(fontMutex); if (!LOADED_FONTS.contains(family)) { - - static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" }; - static const QString SDFF_INCONSOLATA_MEDIUM_FILENAME{ ":/InconsolataMedium.sdff" }; - static const QString SDFF_ROBOTO_FILENAME{ ":/Roboto.sdff" }; - static const QString SDFF_TIMELESS_FILENAME{ ":/Timeless.sdff" }; - QString loadFilename; - if (family == MONO_FONT_FAMILY) { - loadFilename = SDFF_COURIER_PRIME_FILENAME; + if (family == ROBOTO_FONT_FAMILY) { + loadFilename = ":/Roboto.sdff"; } else if (family == INCONSOLATA_FONT_FAMILY) { - loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME; - } else if (family == SANS_FONT_FAMILY) { - loadFilename = SDFF_ROBOTO_FILENAME; + loadFilename = ":/InconsolataMedium.sdff"; + } else if (family == COURIER_FONT_FAMILY) { + loadFilename = ":/CourierPrime.sdff"; + } else if (family == TIMELESS_FONT_FAMILY) { + loadFilename = ":/Timeless.sdff"; + } else if (family.startsWith("http")) { + auto loadingFont = std::make_shared(); + loadingFont->setLoaded(false); + LOADED_FONTS[family] = loadingFont; + + auto& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + networkRequest.setUrl(family); + + auto networkReply = networkAccessManager.get(networkRequest); + connect(networkReply, &QNetworkReply::finished, loadingFont.get(), &Font::handleFontNetworkReply); + } else if (!LOADED_FONTS.contains(ROBOTO_FONT_FAMILY)) { + // Unrecognized font and we haven't loaded Roboto yet + loadFilename = ":/Roboto.sdff"; } else { - if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) { - loadFilename = SDFF_TIMELESS_FILENAME; - } else { - LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY]; - } + // Unrecognized font but we've already loaded Roboto + LOADED_FONTS[family] = LOADED_FONTS[ROBOTO_FONT_FAMILY]; } if (!loadFilename.isEmpty()) { @@ -96,14 +122,24 @@ Font::Pointer Font::load(const QString& family) { return LOADED_FONTS[family]; } -Font::Font() { - static bool fontResourceInitComplete = false; - if (!fontResourceInitComplete) { - Q_INIT_RESOURCE(fonts); - fontResourceInitComplete = true; +void Font::handleFontNetworkReply() { + auto requestReply = qobject_cast(sender()); + + if (requestReply->error() == QNetworkReply::NoError) { + setLoaded(true); + read(*requestReply); + } else { + qDebug() << "Error downloading " << requestReply->url() << " - " << requestReply->errorString(); } } +Font::Font() { + static std::once_flag once; + std::call_once(once, []{ + Q_INIT_RESOURCE(fonts); + }); +} + // NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member const Glyph& Font::getGlyph(const QChar& c) const { if (!_glyphs.contains(c)) { @@ -139,7 +175,7 @@ glm::vec2 Font::computeTokenExtent(const QString& token) const { glm::vec2 Font::computeExtent(const QString& str) const { glm::vec2 extent = glm::vec2(0.0f, 0.0f); - QStringList lines{ splitLines(str) }; + QStringList lines = splitLines(str); if (!lines.empty()) { for(const auto& line : lines) { glm::vec2 tokenExtent = computeTokenExtent(line); @@ -154,7 +190,9 @@ void Font::read(QIODevice& in) { uint8_t header[4]; readStream(in, header); if (memcmp(header, "SDFF", 4)) { - qFatal("Bad SDFF file"); + qDebug() << "Bad SDFF file"; + _loaded = false; + return; } uint16_t version; @@ -191,7 +229,9 @@ void Font::read(QIODevice& in) { // read image data QImage image; if (!image.loadFromData(in.readAll(), "PNG")) { - qFatal("Failed to read SDFF image"); + qDebug() << "Failed to read SDFF image"; + _loaded = false; + return; } _glyphs.clear(); @@ -212,6 +252,9 @@ void Font::read(QIODevice& in) { formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::BGRA); } + // FIXME: We're forcing this to use only one mip, and then manually doing anisotropic filtering in the shader, + // and also calling textureLod. Shouldn't this just use anisotropic filtering and auto-generate mips? + // We should also use smoothstep for anti-aliasing, as explained here: https://github.com/libgdx/libgdx/wiki/Distance-field-fonts _texture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR)); _texture->setStoredMipFormat(formatMip); @@ -244,20 +287,21 @@ void Font::setupGPU() { } // Sanity checks - static const int OFFSET = offsetof(TextureVertex, tex); - assert(OFFSET == sizeof(glm::vec2)); - assert(sizeof(glm::vec2) == 2 * sizeof(float)); - assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2)); + static const int TEX_COORD_OFFSET = offsetof(TextureVertex, tex); + static const int TEX_BOUNDS_OFFSET = offsetof(TextureVertex, bounds); + assert(TEX_COORD_OFFSET == sizeof(glm::vec2)); + assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2) + sizeof(glm::vec4)); assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); // Setup rendering structures _format = std::make_shared(); _format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); - _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); + _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), TEX_COORD_OFFSET); + _format->setAttribute(gpu::Stream::TEXCOORD1, 0, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), TEX_BOUNDS_OFFSET); } } -void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds) { +void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds, float scale, bool enlargeForShadows) { drawInfo.verticesBuffer = std::make_shared(); drawInfo.indicesBuffer = std::make_shared(); drawInfo.indexCount = 0; @@ -267,6 +311,8 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm drawInfo.bounds = bounds; drawInfo.origin = origin; + float enlargedBoundsX = bounds.x - 0.5f * DOUBLE_MAX_OFFSET_PIXELS * float(enlargeForShadows); + // Top left of text glm::vec2 advance = origin; foreach(const QString& token, tokenizeForWrapping(str)) { @@ -274,7 +320,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm bool forceNewLine = false; // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > origin.x + bounds.x)) { + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > origin.x + enlargedBoundsX)) { // We are out of the x bound, force new line forceNewLine = true; } @@ -285,7 +331,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm if (isNewLine) { // No need to draw anything, go directly to next token continue; - } else if (computeExtent(token).x > bounds.x) { + } else if (computeExtent(token).x > enlargedBoundsX) { // token will never fit, stop drawing break; } @@ -301,10 +347,10 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm auto glyph = _glyphs[c]; quint16 verticesOffset = numVertices; - QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent)); + QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent), scale, enlargeForShadows); drawInfo.verticesBuffer->append(qd); - numVertices += 4; - + numVertices += VERTICES_PER_QUAD; + // Sam's recommended triangle slices // Triangle tri1 = { v0, v1, v3 }; // Triangle tri2 = { v1, v2, v3 }; @@ -331,7 +377,6 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm indices[5] = verticesOffset + 3; drawInfo.indicesBuffer->append(sizeof(indices), (const gpu::Byte*)indices); drawInfo.indexCount += NUMBER_OF_INDICES_PER_QUAD; - // Advance by glyph size advance.x += glyph.d; @@ -344,38 +389,49 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm } void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color, - EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds, bool unlit, bool forward) { - if (str == "") { + const glm::vec3& effectColor, float effectThickness, TextEffect effect, + const glm::vec2& origin, const glm::vec2& bounds, float scale, bool unlit, bool forward) { + if (!_loaded || str == "") { return; } - if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin) { - buildVertices(drawInfo, str, origin, bounds); + int textEffect = (int)effect; + const int SHADOW_EFFECT = (int)TextEffect::SHADOW_EFFECT; + + // If we're switching to or from shadow effect mode, we need to rebuild the vertices + if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin || + (drawInfo.params.effect != textEffect && (textEffect == SHADOW_EFFECT || drawInfo.params.effect == SHADOW_EFFECT)) || + (textEffect == SHADOW_EFFECT && scale != _scale)) { + _scale = scale; + buildVertices(drawInfo, str, origin, bounds, scale, textEffect == SHADOW_EFFECT); } setupGPU(); - struct GpuDrawParams { - glm::vec4 color; - glm::vec4 outline; - }; - - if (!drawInfo.paramsBuffer || drawInfo.params.color != color || drawInfo.params.effect != effectType) { + if (!drawInfo.paramsBuffer || drawInfo.params.color != color || drawInfo.params.effectColor != effectColor || + drawInfo.params.effectThickness != effectThickness || drawInfo.params.effect != textEffect) { drawInfo.params.color = color; - drawInfo.params.effect = effectType; - GpuDrawParams gpuDrawParams; + drawInfo.params.effectColor = effectColor; + drawInfo.params.effectThickness = effectThickness; + drawInfo.params.effect = textEffect; + + // need the gamma corrected color here + DrawParams gpuDrawParams; gpuDrawParams.color = ColorUtils::sRGBToLinearVec4(drawInfo.params.color); - gpuDrawParams.outline.x = (drawInfo.params.effect == OUTLINE_EFFECT) ? 1 : 0; - drawInfo.paramsBuffer = std::make_shared(sizeof(GpuDrawParams), nullptr); - drawInfo.paramsBuffer->setSubData(0, sizeof(GpuDrawParams), (const gpu::Byte*)&gpuDrawParams); + gpuDrawParams.effectColor = ColorUtils::sRGBToLinearVec3(drawInfo.params.effectColor); + gpuDrawParams.effectThickness = drawInfo.params.effectThickness; + gpuDrawParams.effect = drawInfo.params.effect; + if (!drawInfo.paramsBuffer) { + drawInfo.paramsBuffer = std::make_shared(sizeof(DrawParams), nullptr); + } + drawInfo.paramsBuffer->setSubData(0, sizeof(DrawParams), (const gpu::Byte*)&gpuDrawParams); } - // need the gamma corrected color here batch.setPipeline(_pipelines[std::make_tuple(color.a < 1.0f, unlit, forward)]); batch.setInputFormat(_format); batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride); batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); - batch.setUniformBuffer(0, drawInfo.paramsBuffer, 0, sizeof(GpuDrawParams)); + batch.setUniformBuffer(0, drawInfo.paramsBuffer, 0, sizeof(DrawParams)); batch.setIndexBuffer(gpu::UINT16, drawInfo.indicesBuffer, 0); batch.drawIndexed(gpu::TRIANGLES, drawInfo.indexCount, 0); } diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index be1e890e3d..c75f0f746f 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -10,12 +10,16 @@ #ifndef hifi_Font_h #define hifi_Font_h +#include + #include "Glyph.h" -#include "EffectType.h" +#include "TextEffect.h" #include #include -class Font { +class Font : public QObject { + Q_OBJECT + public: using Pointer = std::shared_ptr; @@ -24,14 +28,23 @@ public: void read(QIODevice& path); struct DrawParams { - vec4 color{ -1 }; - EffectType effect; + vec4 color { 0 }; + + vec3 effectColor { 0 }; + float effectThickness { 0 }; + + int effect { 0 }; + +#if defined(__clang__) + __attribute__((unused)) +#endif + vec3 _spare; }; struct DrawInfo { - gpu::BufferPointer verticesBuffer; - gpu::BufferPointer indicesBuffer; - gpu::BufferPointer paramsBuffer; + gpu::BufferPointer verticesBuffer { nullptr }; + gpu::BufferPointer indicesBuffer { nullptr }; + gpu::BufferPointer paramsBuffer { nullptr }; uint32_t indexCount; QString string; @@ -44,12 +57,18 @@ public: float getFontSize() const { return _fontSize; } // Render string to batch - void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, - const glm::vec4& color, EffectType effectType, - const glm::vec2& origin, const glm::vec2& bound, bool unlit, bool forward); + void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, const glm::vec4& color, + const glm::vec3& effectColor, float effectThickness, TextEffect effect, + const glm::vec2& origin, const glm::vec2& bound, float scale, bool unlit, bool forward); static Pointer load(const QString& family); + bool isLoaded() const { return _loaded; } + void setLoaded(bool loaded) { _loaded = loaded; } + +public slots: + void handleFontNetworkReply(); + private: static Pointer load(QIODevice& fontFile); QStringList tokenizeForWrapping(const QString& str) const; @@ -57,7 +76,7 @@ private: glm::vec2 computeTokenExtent(const QString& str) const; const Glyph& getGlyph(const QChar& c) const; - void buildVertices(DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds); + void buildVertices(DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds, float scale, bool enlargeForShadows); void setupGPU(); @@ -70,11 +89,15 @@ private: // Font characteristics QString _family; - float _fontSize = 0.0f; - float _leading = 0.0f; - float _ascent = 0.0f; - float _descent = 0.0f; - float _spaceWidth = 0.0f; + float _fontSize { 0.0f }; + float _leading { 0.0f }; + float _ascent { 0.0f }; + float _descent { 0.0f }; + float _spaceWidth { 0.0f }; + + float _scale { 0.0f }; + + bool _loaded { true }; gpu::TexturePointer _texture; gpu::BufferStreamPointer _stream; diff --git a/libraries/render-utils/src/text/FontFamilies.h b/libraries/render-utils/src/text/FontFamilies.h deleted file mode 100644 index 3c4186f5c4..0000000000 --- a/libraries/render-utils/src/text/FontFamilies.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/07/16 -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#pragma once -#ifndef hifi_FontFamilies_h -#define hifi_FontFamilies_h - -// the standard sans serif font family -#define SANS_FONT_FAMILY "Helvetica" - -// the standard sans serif font family -#define SERIF_FONT_FAMILY "Timeless" - -// the standard mono font family -#define MONO_FONT_FAMILY "Courier" - -// the Inconsolata font family -#ifdef Q_OS_WIN -#define INCONSOLATA_FONT_FAMILY "Fixedsys" -#define INCONSOLATA_FONT_WEIGHT QFont::Normal -#else -#define INCONSOLATA_FONT_FAMILY "Inconsolata" -#define INCONSOLATA_FONT_WEIGHT QFont::Bold -#endif - -#endif diff --git a/libraries/render-utils/src/text/Glyph.cpp b/libraries/render-utils/src/text/Glyph.cpp index 0354b1057c..ee3656bc00 100644 --- a/libraries/render-utils/src/text/Glyph.cpp +++ b/libraries/render-utils/src/text/Glyph.cpp @@ -18,5 +18,6 @@ void Glyph::read(QIODevice& in) { readStream(in, size); readStream(in, offset); readStream(in, d); + // texSize is divided by the image size later texSize = size; } diff --git a/libraries/shared/src/FontFamilies.h b/libraries/shared/src/FontFamilies.h new file mode 100644 index 0000000000..ba7af3ae7b --- /dev/null +++ b/libraries/shared/src/FontFamilies.h @@ -0,0 +1,17 @@ +// +// Created by Bradley Austin Davis on 2015/07/16 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_FontFamilies_h +#define hifi_FontFamilies_h + +#define ROBOTO_FONT_FAMILY "Roboto" +#define COURIER_FONT_FAMILY "Courier" +#define INCONSOLATA_FONT_FAMILY "Inconsolata" +#define TIMELESS_FONT_FAMILY "Timeless" + +#endif // hifi_FontFamilies_h diff --git a/libraries/shared/src/TextEffect.cpp b/libraries/shared/src/TextEffect.cpp new file mode 100644 index 0000000000..27ed1caa59 --- /dev/null +++ b/libraries/shared/src/TextEffect.cpp @@ -0,0 +1,26 @@ +// +// Created by Sam Gondelman on 7/21/2019 +// Copyright 2019 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 "TextEffect.h" + +const char* textEffectNames[] = { + "none", + "outline", + "outline fill", + "shadow" +}; + +static const size_t TEXT_EFFECT_NAMES = (sizeof(textEffectNames) / sizeof(textEffectNames[0])); + +QString TextEffectHelpers::getNameForTextEffect(TextEffect effect) { + if (((int)effect <= 0) || ((int)effect >= (int)TEXT_EFFECT_NAMES)) { + effect = (TextEffect)0; + } + + return textEffectNames[(int)effect]; +} \ No newline at end of file diff --git a/libraries/shared/src/TextEffect.h b/libraries/shared/src/TextEffect.h new file mode 100644 index 0000000000..91bd5ec60c --- /dev/null +++ b/libraries/shared/src/TextEffect.h @@ -0,0 +1,42 @@ +// +// Created by Bradley Austin Davis on 2015/07/16 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_TextEffect_h +#define hifi_TextEffect_h + +#include "QString" + +/**jsdoc + *

A {@link Entities.EntityProperties-Text|Text} entity may use one of the following effects:

+ * + * + * + * + * + * + * + * + * + * + *
ValueDescription
"none"No effect.
"outline"An outline effect.
"outlineFill"An outline effect, with fill.
"shadow"A shadow effect.
+ * @typedef {string} Entities.TextEffect + */ + +enum class TextEffect { + NO_EFFECT = 0, + OUTLINE_EFFECT, + OUTLINE_WITH_FILL_EFFECT, + SHADOW_EFFECT +}; + +class TextEffectHelpers { +public: + static QString getNameForTextEffect(TextEffect effect); +}; + +#endif // hifi_TextEffect_h \ No newline at end of file