mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-04-06 10:33:35 +02:00
428 lines
16 KiB
C++
428 lines
16 KiB
C++
//
|
|
// RenderableTextEntityItem.cpp
|
|
// interface/src
|
|
//
|
|
// Created by Brad Hefta-Gaub on 8/6/14.
|
|
// Copyright 2014 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 "RenderableTextEntityItem.h"
|
|
|
|
#include <TextEntityItem.h>
|
|
#include <GeometryCache.h>
|
|
#include <PerfStat.h>
|
|
#include <Transform.h>
|
|
#include <TextRenderer3D.h>
|
|
|
|
#include "GLMHelpers.h"
|
|
|
|
#include "DeferredLightingEffect.h"
|
|
#include "RenderPipelines.h"
|
|
|
|
using namespace render;
|
|
using namespace render::entities;
|
|
|
|
static const int FIXED_FONT_POINT_SIZE = 40;
|
|
const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 92.0f; // Determined through experimentation to fit font to line height.
|
|
const float LINE_SCALE_RATIO = 1.2f;
|
|
|
|
TextEntityRenderer::TextEntityRenderer(const EntityItemPointer& entity) :
|
|
Parent(entity),
|
|
_textRenderer(TextRenderer3D::getInstance(ROBOTO_FONT_FAMILY)) {
|
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
|
if (geometryCache) {
|
|
_geometryID = geometryCache->allocateID();
|
|
}
|
|
_material->setCullFaceMode(graphics::MaterialKey::CullFaceMode::CULL_NONE);
|
|
addMaterial(graphics::MaterialLayer(_material, 0), "0");
|
|
}
|
|
|
|
TextEntityRenderer::~TextEntityRenderer() {
|
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
|
if (_geometryID && geometryCache) {
|
|
geometryCache->releaseID(_geometryID);
|
|
}
|
|
}
|
|
|
|
bool TextEntityRenderer::needsRenderUpdate() const {
|
|
return needsRenderUpdateFromMaterials() || Parent::needsRenderUpdate();
|
|
}
|
|
|
|
void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
|
|
void* key = (void*)this;
|
|
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] {
|
|
withWriteLock([&] {
|
|
_dimensions = entity->getScaledDimensions();
|
|
_renderTransform = getModelTransform();
|
|
_renderTransform.postScale(_dimensions);
|
|
});
|
|
});
|
|
}
|
|
|
|
void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
|
|
_pulseProperties = entity->getPulseProperties();
|
|
_text = entity->getText();
|
|
_lineHeight = entity->getLineHeight();
|
|
_textColor = toGlm(entity->getTextColor());
|
|
_textAlpha = entity->getTextAlpha();
|
|
_leftMargin = entity->getLeftMargin();
|
|
_rightMargin = entity->getRightMargin();
|
|
_topMargin = entity->getTopMargin();
|
|
_bottomMargin = entity->getBottomMargin();
|
|
_font = entity->getFont();
|
|
_effect = entity->getTextEffect();
|
|
_effectColor = toGlm(entity->getTextEffectColor());
|
|
_effectThickness = entity->getTextEffectThickness();
|
|
_alignment = entity->getAlignment();
|
|
_verticalAlignment = entity->getVerticalAlignment();
|
|
|
|
bool materialChanged = false;
|
|
glm::vec3 color = toGlm(entity->getBackgroundColor());
|
|
if (_backgroundColor != color) {
|
|
_backgroundColor = color;
|
|
_material->setAlbedo(color);
|
|
materialChanged = true;
|
|
}
|
|
|
|
float alpha = entity->getBackgroundAlpha();
|
|
if (_backgroundAlpha != alpha) {
|
|
_backgroundAlpha = alpha;
|
|
_material->setOpacity(alpha);
|
|
materialChanged = true;
|
|
}
|
|
|
|
auto unlit = entity->getUnlit();
|
|
if (_unlit != unlit) {
|
|
_unlit = unlit;
|
|
_material->setUnlit(_unlit);
|
|
materialChanged = true;
|
|
}
|
|
|
|
updateMaterials(materialChanged);
|
|
|
|
updateTextRenderItem();
|
|
}
|
|
|
|
bool TextEntityRenderer::isTransparent() const {
|
|
bool backgroundTransparent = _backgroundAlpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE;
|
|
return backgroundTransparent || Parent::isTransparent() || materialsTransparent();
|
|
}
|
|
|
|
bool TextEntityRenderer::isTextTransparent() const {
|
|
return Parent::isTransparent() || _textAlpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE;
|
|
}
|
|
|
|
Item::Bound TextEntityRenderer::getBound(RenderArgs* args) {
|
|
return Parent::getMaterialBound(args);
|
|
}
|
|
|
|
ItemKey TextEntityRenderer::getKey() {
|
|
return ItemKey::Builder(Parent::getKey()).withMetaCullGroup();
|
|
}
|
|
|
|
ShapeKey TextEntityRenderer::getShapeKey() {
|
|
auto builder = render::ShapeKey::Builder().withDepthBias();
|
|
updateShapeKeyBuilderFromMaterials(builder);
|
|
return builder.build();
|
|
}
|
|
|
|
uint32_t TextEntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) const {
|
|
auto parentSubs = Parent::metaFetchMetaSubItems(subItems);
|
|
if (Item::isValidID(_textRenderID)) {
|
|
subItems.emplace_back(_textRenderID);
|
|
return parentSubs + 1;
|
|
}
|
|
return parentSubs;
|
|
}
|
|
|
|
void TextEntityRenderer::doRender(RenderArgs* args) {
|
|
PerformanceTimer perfTimer("RenderableTextEntityItem::render");
|
|
Q_ASSERT(args->_batch);
|
|
|
|
graphics::MultiMaterial materials;
|
|
{
|
|
std::lock_guard<std::mutex> lock(_materialsLock);
|
|
materials = _materials["0"];
|
|
}
|
|
|
|
glm::vec4 backgroundColor = materials.getColor();
|
|
backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created);
|
|
|
|
if (backgroundColor.a <= 0.0f) {
|
|
return;
|
|
}
|
|
|
|
gpu::Batch& batch = *args->_batch;
|
|
|
|
bool transparent;
|
|
Transform transform;
|
|
withReadLock([&] {
|
|
transparent = isTransparent();
|
|
transform = _renderTransform;
|
|
});
|
|
|
|
bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
|
|
transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode,
|
|
usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
|
|
batch.setModelTransform(transform);
|
|
|
|
Pipeline pipelineType = getPipelineType(materials);
|
|
if (pipelineType == Pipeline::PROCEDURAL) {
|
|
auto procedural = std::static_pointer_cast<graphics::ProceduralMaterial>(materials.top().material);
|
|
transparent |= procedural->isFading();
|
|
procedural->prepare(batch, transform.getTranslation(), transform.getScale(), transform.getRotation(), _created, ProceduralProgramKey(transparent));
|
|
} else if (pipelineType == Pipeline::MATERIAL) {
|
|
if (RenderPipelines::bindMaterials(materials, batch, args->_renderMode, args->_enableTexturing)) {
|
|
args->_details._materialSwitches++;
|
|
}
|
|
}
|
|
|
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
|
if (pipelineType == Pipeline::SIMPLE || pipelineType == Pipeline::MIRROR) {
|
|
geometryCache->renderQuad(batch, glm::vec2(-0.5f), glm::vec2(0.5f), backgroundColor, _geometryID);
|
|
} else {
|
|
geometryCache->renderQuad(batch, glm::vec2(-0.5f), glm::vec2(0.5f), glm::vec2(0.0f), glm::vec2(1.0f), backgroundColor, _geometryID);
|
|
}
|
|
|
|
const int TRIANGLES_PER_QUAD = 2;
|
|
args->_details._trianglesRendered += TRIANGLES_PER_QUAD;
|
|
}
|
|
|
|
QSizeF TextEntityRenderer::textSize(const QString& text) const {
|
|
auto extents = _textRenderer->computeExtent(text);
|
|
extents.y *= 2.0f;
|
|
|
|
float maxHeight = (float)_textRenderer->computeExtent("Xy").y * LINE_SCALE_RATIO;
|
|
float pointToWorldScale = (maxHeight / FIXED_FONT_SCALING_RATIO) * _lineHeight;
|
|
|
|
return QSizeF(extents.x, extents.y) * pointToWorldScale;
|
|
}
|
|
|
|
void TextEntityRenderer::onAddToSceneTyped(const TypedEntityPointer& entity) {
|
|
Parent::onAddToSceneTyped(entity);
|
|
_textPayload = std::make_shared<TextPayload>(entity->getID(), _textRenderer);
|
|
_textRenderID = AbstractViewStateInterface::instance()->getMain3DScene()->allocateID();
|
|
auto renderPayload = std::make_shared<TextPayload::Payload>(_textPayload);
|
|
render::Transaction transaction;
|
|
transaction.resetItem(_textRenderID, renderPayload);
|
|
AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction);
|
|
}
|
|
|
|
void TextEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) {
|
|
Parent::onRemoveFromSceneTyped(entity);
|
|
render::Transaction transaction;
|
|
transaction.removeItem(_textRenderID);
|
|
AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction);
|
|
_textPayload.reset();
|
|
}
|
|
|
|
void TextEntityRenderer::updateTextRenderItem() const {
|
|
render::Transaction transaction;
|
|
transaction.updateItem(_textRenderID);
|
|
AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction);
|
|
}
|
|
|
|
entities::TextPayload::TextPayload(const QUuid& entityID, const std::weak_ptr<TextRenderer3D>& textRenderer) :
|
|
_entityID(entityID), _textRenderer(textRenderer) {
|
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
|
if (geometryCache) {
|
|
_geometryID = geometryCache->allocateID();
|
|
}
|
|
}
|
|
|
|
entities::TextPayload::~TextPayload() {
|
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
|
if (_geometryID && geometryCache) {
|
|
geometryCache->releaseID(_geometryID);
|
|
}
|
|
}
|
|
|
|
ItemKey entities::TextPayload::getKey() const {
|
|
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
|
if (entityTreeRenderer) {
|
|
auto renderable = entityTreeRenderer->renderableForEntityId(_entityID);
|
|
if (renderable) {
|
|
auto textRenderable = std::static_pointer_cast<TextEntityRenderer>(renderable);
|
|
|
|
// 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();
|
|
}
|
|
|
|
if (textRenderable->_mirrorMode == MirrorMode::MIRROR || (textRenderable->_mirrorMode == MirrorMode::PORTAL && !textRenderable->_portalExitID.isNull())) {
|
|
builder.withMirror();
|
|
}
|
|
|
|
return builder;
|
|
}
|
|
}
|
|
return ItemKey::Builder::opaqueShape();
|
|
}
|
|
|
|
Item::Bound entities::TextPayload::getBound(RenderArgs* args) const {
|
|
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
|
if (entityTreeRenderer) {
|
|
auto renderable = entityTreeRenderer->renderableForEntityId(_entityID);
|
|
if (renderable) {
|
|
return std::static_pointer_cast<TextEntityRenderer>(renderable)->getBound(args);
|
|
}
|
|
}
|
|
return Item::Bound();
|
|
}
|
|
|
|
ShapeKey entities::TextPayload::getShapeKey() const {
|
|
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
|
if (entityTreeRenderer) {
|
|
auto renderable = entityTreeRenderer->renderableForEntityId(_entityID);
|
|
if (renderable) {
|
|
auto textRenderable = std::static_pointer_cast<TextEntityRenderer>(renderable);
|
|
|
|
auto builder = render::ShapeKey::Builder().withOwnPipeline();
|
|
if (textRenderable->isTextTransparent()) {
|
|
builder.withTranslucent();
|
|
}
|
|
if (textRenderable->_unlit) {
|
|
builder.withUnlit();
|
|
}
|
|
if (textRenderable->_primitiveMode == PrimitiveMode::LINES) {
|
|
builder.withWireframe();
|
|
}
|
|
return builder.build();
|
|
}
|
|
}
|
|
return ShapeKey::Builder::invalid();
|
|
}
|
|
|
|
bool entities::TextPayload::passesZoneOcclusionTest(const std::unordered_set<QUuid>& containingZones) const {
|
|
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
|
if (entityTreeRenderer) {
|
|
auto renderable = entityTreeRenderer->renderableForEntityId(_entityID);
|
|
if (renderable) {
|
|
return std::static_pointer_cast<TextEntityRenderer>(renderable)->passesZoneOcclusionTest(containingZones);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ItemID entities::TextPayload::computeMirrorView(ViewFrustum& viewFrustum) const {
|
|
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
|
if (entityTreeRenderer) {
|
|
auto renderable = entityTreeRenderer->renderableForEntityId(_entityID);
|
|
if (renderable) {
|
|
return renderable->computeMirrorView(viewFrustum);
|
|
}
|
|
}
|
|
return Item::INVALID_ITEM_ID;
|
|
}
|
|
|
|
void entities::TextPayload::render(RenderArgs* args) {
|
|
PerformanceTimer perfTimer("TextPayload::render");
|
|
Q_ASSERT(args->_batch);
|
|
gpu::Batch& batch = *args->_batch;
|
|
|
|
auto textRenderer = _textRenderer.lock();
|
|
if (!textRenderer) {
|
|
return;
|
|
}
|
|
|
|
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
|
if (!entityTreeRenderer) {
|
|
return;
|
|
}
|
|
auto renderable = entityTreeRenderer->renderableForEntityId(_entityID);
|
|
if (!renderable) {
|
|
return;
|
|
}
|
|
auto textRenderable = std::static_pointer_cast<TextEntityRenderer>(renderable);
|
|
|
|
Transform transform;
|
|
glm::vec3 dimensions;
|
|
|
|
glm::vec4 textColor;
|
|
bool mirror;
|
|
textRenderable->withReadLock([&] {
|
|
transform = textRenderable->_renderTransform;
|
|
dimensions = textRenderable->_dimensions;
|
|
|
|
float fadeRatio = textRenderable->_isFading ? Interpolate::calculateFadeRatio(textRenderable->_fadeStartTime) : 1.0f;
|
|
textColor = glm::vec4(textRenderable->_textColor, fadeRatio * textRenderable->_textAlpha);
|
|
|
|
mirror = textRenderable->_mirrorMode == MirrorMode::MIRROR || (textRenderable->_mirrorMode == MirrorMode::PORTAL && !textRenderable->_portalExitID.isNull());
|
|
});
|
|
|
|
bool forward = textRenderable->_renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD;
|
|
|
|
textColor = EntityRenderer::calculatePulseColor(textColor, textRenderable->_pulseProperties, textRenderable->_created);
|
|
glm::vec3 effectColor = EntityRenderer::calculatePulseColor(textRenderable->_effectColor, textRenderable->_pulseProperties, textRenderable->_created);
|
|
|
|
if (textColor.a <= 0.0f) {
|
|
return;
|
|
}
|
|
|
|
bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0;
|
|
transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), textRenderable->_billboardMode,
|
|
usePrimaryFrustum ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition()));
|
|
|
|
float scale = textRenderable->_lineHeight / textRenderer->getFontSize();
|
|
transform.postTranslate(glm::vec3(-0.5, 0.5, 1.0f + EPSILON / dimensions.z));
|
|
transform.setScale(scale);
|
|
batch.setModelTransform(transform);
|
|
|
|
glm::vec2 bounds = glm::vec2(dimensions.x - (textRenderable->_leftMargin + textRenderable->_rightMargin), dimensions.y - (textRenderable->_topMargin + textRenderable->_bottomMargin));
|
|
textRenderer->draw(batch, textRenderable->_font, { textRenderable->_text, textColor, effectColor, { textRenderable->_leftMargin / scale, -textRenderable->_topMargin / scale },
|
|
bounds / scale, scale, textRenderable->_effectThickness, textRenderable->_effect, textRenderable->_alignment, textRenderable->_verticalAlignment, textRenderable->_unlit, forward, mirror });
|
|
}
|
|
|
|
namespace render {
|
|
template <> const ItemKey payloadGetKey(const TextPayload::Pointer& payload) {
|
|
if (payload) {
|
|
return payload->getKey();
|
|
}
|
|
return ItemKey::Builder::opaqueShape();
|
|
}
|
|
|
|
template <> const Item::Bound payloadGetBound(const TextPayload::Pointer& payload, RenderArgs* args) {
|
|
if (payload) {
|
|
return payload->getBound(args);
|
|
}
|
|
return Item::Bound();
|
|
}
|
|
|
|
template <> const ShapeKey shapeGetShapeKey(const TextPayload::Pointer& payload) {
|
|
if (payload) {
|
|
return payload->getShapeKey();
|
|
}
|
|
return ShapeKey::Builder::invalid();
|
|
}
|
|
|
|
template <> void payloadRender(const TextPayload::Pointer& payload, RenderArgs* args) {
|
|
return payload->render(args);
|
|
}
|
|
|
|
template <> bool payloadPassesZoneOcclusionTest(const entities::TextPayload::Pointer& payload, const std::unordered_set<QUuid>& containingZones) {
|
|
if (payload) {
|
|
return payload->passesZoneOcclusionTest(containingZones);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <> ItemID payloadComputeMirrorView(const entities::TextPayload::Pointer& payload, ViewFrustum& viewFrustum) {
|
|
if (payload) {
|
|
return payload->computeMirrorView(viewFrustum);
|
|
}
|
|
return Item::INVALID_ITEM_ID;
|
|
}
|
|
|
|
}
|