mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-04-07 10:02:24 +02:00
supporting text effects + fonts on text entities
This commit is contained in:
parent
6691f68428
commit
078ca7edea
28 changed files with 622 additions and 302 deletions
|
@ -9,18 +9,10 @@
|
|||
#include "QmlOverlay.h"
|
||||
|
||||
#include <QQuickItem>
|
||||
#include <QThread>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <TextureCache.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "text/FontFamilies.h"
|
||||
|
||||
QmlOverlay::QmlOverlay(const QUrl& url) {
|
||||
buildQmlElement(url);
|
||||
|
|
|
@ -9,10 +9,6 @@
|
|||
#ifndef hifi_QmlOverlay_h
|
||||
#define hifi_QmlOverlay_h
|
||||
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
#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
|
||||
|
|
|
@ -11,18 +11,9 @@
|
|||
#include "TextOverlay.h"
|
||||
|
||||
#include <QQuickItem>
|
||||
#include <QFontMetrics>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <TextureCache.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#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);
|
||||
|
|
|
@ -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<BlendshapeOffset>& blendshapeOffsets,
|
||||
const QVector<int>& 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<GeometryCache>();
|
||||
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<TextEntityRenderer>(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<TextEntityRenderer>(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 {
|
||||
|
|
|
@ -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> _textPayload;
|
||||
|
|
|
@ -299,6 +299,24 @@ void EntityItemProperties::setAvatarPriorityFromString(const QString& mode) {
|
|||
}
|
||||
}
|
||||
|
||||
inline void addTextEffect(QHash<QString, TextEffect>& lookup, TextEffect effect) { lookup[TextEffectHelpers::getNameForTextEffect(effect)] = effect; }
|
||||
const QHash<QString, TextEffect> stringToTextEffectLookup = [] {
|
||||
QHash<QString, TextEffect> 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 - <code>true</code> 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: <code>Courier</code,
|
||||
* <code>Inconsolata</code>, <code>Roboto</code>, <code>Timeless</code>, 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 <code>0.0</code> – <code>0.5</code>.
|
||||
* @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera.
|
||||
* @property {boolean} faceCamera - <code>true</code> if <code>billboardMode</code> is <code>"yaw"</code>, <code>false</code>
|
||||
* if it isn't. Setting this property to <code>false</code> sets the <code>billboardMode</code> to <code>"none"</code>.
|
||||
|
@ -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<QString> 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);
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <OctreeConstants.h>
|
||||
#include <ShapeInfo.h>
|
||||
#include <ColorUtils.h>
|
||||
#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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<QString>([&] {
|
||||
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<QString>([&] {
|
||||
return _font;
|
||||
});
|
||||
}
|
||||
|
||||
void TextEntityItem::setTextEffect(TextEffect value) {
|
||||
withWriteLock([&] {
|
||||
_effect = value;
|
||||
});
|
||||
}
|
||||
|
||||
TextEffect TextEntityItem::getTextEffect() const {
|
||||
return resultWithReadLock<TextEffect>([&] {
|
||||
return _effect;
|
||||
});
|
||||
}
|
||||
|
||||
void TextEntityItem::setTextEffectColor(const glm::u8vec3& value) {
|
||||
withWriteLock([&] {
|
||||
_effectColor = value;
|
||||
});
|
||||
}
|
||||
|
||||
glm::u8vec3 TextEntityItem::getTextEffectColor() const {
|
||||
return resultWithReadLock<glm::u8vec3>([&] {
|
||||
return _effectColor;
|
||||
});
|
||||
}
|
||||
|
||||
void TextEntityItem::setTextEffectThickness(float value) {
|
||||
withWriteLock([&] {
|
||||
_effectThickness = value;
|
||||
});
|
||||
}
|
||||
|
||||
float TextEntityItem::getTextEffectThickness() const {
|
||||
return resultWithReadLock<float>([&] {
|
||||
return _effectThickness;
|
||||
});
|
||||
}
|
||||
|
||||
PulsePropertyGroup TextEntityItem::getPulseProperties() const {
|
||||
return resultWithReadLock<PulsePropertyGroup>([&] {
|
||||
return _pulseProperties;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -273,6 +273,7 @@ enum class EntityVersion : PacketVersion {
|
|||
PrivateUserData,
|
||||
TextUnlit,
|
||||
ShadowBiasAndDistance,
|
||||
TextEntityFonts,
|
||||
|
||||
// Add new versions above here
|
||||
NUM_PACKET_TYPE,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -10,45 +10,19 @@
|
|||
//
|
||||
|
||||
#include "TextRenderer3D.h"
|
||||
#include <StreamHelpers.h>
|
||||
|
||||
#include <gpu/Shader.h>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QImage>
|
||||
#include <QStringList>
|
||||
#include <QFile>
|
||||
|
||||
#include <shaders/Shaders.h>
|
||||
#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);
|
||||
}
|
||||
}
|
|
@ -14,48 +14,32 @@
|
|||
|
||||
#include <memory>
|
||||
#include <glm/glm.hpp>
|
||||
#include <QColor>
|
||||
#include <gpu/Forward.h>
|
||||
|
||||
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;
|
||||
Font::DrawInfo _drawInfo;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_TextRenderer3D_h
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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@>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <QFile>
|
||||
#include <QImage>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include <ColorUtils.h>
|
||||
|
||||
|
@ -13,6 +14,8 @@
|
|||
#include "FontFamilies.h"
|
||||
#include "../StencilMaskPass.h"
|
||||
|
||||
#include "NetworkAccessManager.h"
|
||||
|
||||
static std::mutex fontMutex;
|
||||
|
||||
std::map<std::tuple<bool, bool, bool>, 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<QString, Font::Pointer> LOADED_FONTS;
|
||||
|
||||
Font::Pointer Font::load(QIODevice& fontFile) {
|
||||
Pointer font = std::make_shared<Font>();
|
||||
font->read(fontFile);
|
||||
return font;
|
||||
}
|
||||
|
||||
static QHash<QString, Font::Pointer> LOADED_FONTS;
|
||||
|
||||
Font::Pointer Font::load(const QString& family) {
|
||||
std::lock_guard<std::mutex> 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<Font>();
|
||||
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<QNetworkReply*>(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<gpu::Stream::Format>();
|
||||
_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<gpu::Buffer>();
|
||||
drawInfo.indicesBuffer = std::make_shared<gpu::Buffer>();
|
||||
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<gpu::Buffer>(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<gpu::Buffer>(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);
|
||||
}
|
||||
|
|
|
@ -10,12 +10,16 @@
|
|||
#ifndef hifi_Font_h
|
||||
#define hifi_Font_h
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "Glyph.h"
|
||||
#include "EffectType.h"
|
||||
#include "TextEffect.h"
|
||||
#include <gpu/Batch.h>
|
||||
#include <gpu/Pipeline.h>
|
||||
|
||||
class Font {
|
||||
class Font : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Pointer = std::shared_ptr<Font>;
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
17
libraries/shared/src/FontFamilies.h
Normal file
17
libraries/shared/src/FontFamilies.h
Normal file
|
@ -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
|
26
libraries/shared/src/TextEffect.cpp
Normal file
26
libraries/shared/src/TextEffect.cpp
Normal file
|
@ -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];
|
||||
}
|
42
libraries/shared/src/TextEffect.h
Normal file
42
libraries/shared/src/TextEffect.h
Normal file
|
@ -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
|
||||
* <p>A {@link Entities.EntityProperties-Text|Text} entity may use one of the following effects:</p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>Value</th><th>Description</th></tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr><td><code>"none"</code></td><td>No effect.</td></tr>
|
||||
* <tr><td><code>"outline"</code></td><td>An outline effect.</td></tr>
|
||||
* <tr><td><code>"outlineFill"</code></td><td>An outline effect, with fill.</td></tr>
|
||||
* <tr><td><code>"shadow"</code></td><td>A shadow effect.</td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @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
|
Loading…
Reference in a new issue