// // Copyright (c) 2008-2013 the Urho3D project. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include "Precompiled.h" #include "Camera.h" #include "Context.h" #include "Font.h" #include "Geometry.h" #include "Log.h" #include "Material.h" #include "Node.h" #include "ResourceCache.h" #include "Technique.h" #include "Text.h" #include "Text3D.h" #include "VertexBuffer.h" namespace Urho3D { extern const char* horizontalAlignments[]; extern const char* verticalAlignments[]; extern const char* textEffects[]; extern const char* GEOMETRY_CATEGORY; static const float TEXT_SCALING = 1.0f / 128.0f; static const float DEFAULT_EFFECT_DEPTH_BIAS = 0.1f; Text3D::Text3D(Context* context) : Drawable(context, DRAWABLE_GEOMETRY), text_(context), vertexBuffer_(new VertexBuffer(context_)), customWorldTransform_(Matrix3x4::IDENTITY), faceCamera_(false), textDirty_(true), geometryDirty_(true) { text_.SetEffectDepthBias(DEFAULT_EFFECT_DEPTH_BIAS); } Text3D::~Text3D() { } void Text3D::RegisterObject(Context* context) { context->RegisterFactory(GEOMETRY_CATEGORY); ACCESSOR_ATTRIBUTE(Text3D, VAR_BOOL, "Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT); ACCESSOR_ATTRIBUTE(Text3D, VAR_RESOURCEREF, "Font", GetFontAttr, SetFontAttr, ResourceRef, ResourceRef(Font::GetTypeStatic()), AM_DEFAULT); ACCESSOR_ATTRIBUTE(Text3D, VAR_RESOURCEREF, "Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()), AM_DEFAULT); ATTRIBUTE(Text3D, VAR_INT, "Font Size", text_.fontSize_, DEFAULT_FONT_SIZE, AM_DEFAULT); ATTRIBUTE(Text3D, VAR_STRING, "Text", text_.text_, String::EMPTY, AM_DEFAULT); ENUM_ATTRIBUTE(Text3D, "Text Alignment", text_.textAlignment_, horizontalAlignments, HA_LEFT, AM_DEFAULT); ATTRIBUTE(Text3D, VAR_FLOAT, "Row Spacing", text_.rowSpacing_, 1.0f, AM_DEFAULT); ATTRIBUTE(Text3D, VAR_BOOL, "Word Wrap", text_.wordWrap_, false, AM_DEFAULT); ENUM_ATTRIBUTE(Text3D, "Text Effect", text_.textEffect_, textEffects, TE_NONE, AM_DEFAULT); REF_ACCESSOR_ATTRIBUTE(Text3D, VAR_COLOR, "Effect Color", GetEffectColor, SetEffectColor, Color, Color::BLACK, AM_DEFAULT); ATTRIBUTE(Text3D, VAR_FLOAT, "Effect Depth Bias", text_.effectDepthBias_, DEFAULT_EFFECT_DEPTH_BIAS, AM_DEFAULT); ACCESSOR_ATTRIBUTE(Text3D, VAR_INT, "Width", GetWidth, SetWidth, int, 0, AM_DEFAULT); ENUM_ACCESSOR_ATTRIBUTE(Text3D, "Horiz Alignment", GetHorizontalAlignment, SetHorizontalAlignment, HorizontalAlignment, horizontalAlignments, HA_LEFT, AM_DEFAULT); ENUM_ACCESSOR_ATTRIBUTE(Text3D, "Vert Alignment", GetVerticalAlignment, SetVerticalAlignment, VerticalAlignment, verticalAlignments, VA_TOP, AM_DEFAULT); REF_ACCESSOR_ATTRIBUTE(Text3D, VAR_COLOR, "Color", GetColorAttr, SetColor, Color, Color::WHITE, AM_DEFAULT); ATTRIBUTE(Text3D, VAR_COLOR, "Top Left Color", text_.color_[0], Color::WHITE, AM_DEFAULT); ATTRIBUTE(Text3D, VAR_COLOR, "Top Right Color", text_.color_[1], Color::WHITE, AM_DEFAULT); ATTRIBUTE(Text3D, VAR_COLOR, "Bottom Left Color", text_.color_[2], Color::WHITE, AM_DEFAULT); ATTRIBUTE(Text3D, VAR_COLOR, "Bottom Right Color", text_.color_[3], Color::WHITE, AM_DEFAULT); ACCESSOR_ATTRIBUTE(Text3D, VAR_BOOL, "Can Be Occluded", IsOccludee, SetOccludee, bool, true, AM_DEFAULT); ACCESSOR_ATTRIBUTE(Text3D, VAR_BOOL, "Face Camera", GetFaceCamera, SetFaceCamera, bool, false, AM_DEFAULT); ACCESSOR_ATTRIBUTE(Text3D, VAR_FLOAT, "Draw Distance", GetDrawDistance, SetDrawDistance, float, 0.0f, AM_DEFAULT); COPY_BASE_ATTRIBUTES(Text3D, Drawable); } void Text3D::ApplyAttributes() { text_.ApplyAttributes(); MarkTextDirty(); UpdateTextBatches(); UpdateTextMaterials(); } void Text3D::UpdateBatches(const FrameInfo& frame) { const Matrix3x4& worldTransform = node_->GetWorldTransform(); distance_ = frame.camera_->GetDistance(GetWorldBoundingBox().Center()); if (faceCamera_) { customWorldTransform_ = Matrix3x4(node_->GetWorldPosition(), frame.camera_->GetNode()->GetWorldRotation(), node_->GetWorldScale()); worldBoundingBoxDirty_ = true; } for (unsigned i = 0; i < batches_.Size(); ++i) { batches_[i].distance_ = distance_; batches_[i].worldTransform_ = faceCamera_ ? &customWorldTransform_ : &worldTransform; } } void Text3D::UpdateGeometry(const FrameInfo& frame) { if (geometryDirty_) { for (unsigned i = 0; i < batches_.Size(); ++i) { Geometry* geometry = geometries_[i]; geometry->SetDrawRange(TRIANGLE_LIST, 0, 0, uiBatches_[i].vertexStart_, (uiBatches_[i].vertexEnd_ - uiBatches_[i].vertexStart_) / UI_VERTEX_SIZE); } } if ((geometryDirty_ || vertexBuffer_->IsDataLost()) && uiVertexData_.Size()) { unsigned vertexCount = uiVertexData_.Size() / UI_VERTEX_SIZE; if (vertexBuffer_->GetVertexCount() != vertexCount) vertexBuffer_->SetSize(vertexCount, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1); vertexBuffer_->SetData(&uiVertexData_[0]); } geometryDirty_ = false; } UpdateGeometryType Text3D::GetUpdateGeometryType() { if (geometryDirty_ || vertexBuffer_->IsDataLost()) return UPDATE_MAIN_THREAD; else return UPDATE_NONE; } void Text3D::SetMaterial(Material* material) { material_ = material; UpdateTextMaterials(true); } bool Text3D::SetFont(const String& fontName, int size) { bool success = text_.SetFont(fontName, size); // Changing font requires materials to be re-evaluated. Material evaluation can not be done in worker threads, // so UI batches must be brought up-to-date immediately MarkTextDirty(); UpdateTextBatches(); UpdateTextMaterials(); return success; } bool Text3D::SetFont(Font* font, int size) { bool success = text_.SetFont(font, size); MarkTextDirty(); UpdateTextBatches(); UpdateTextMaterials(); return success; } void Text3D::SetText(const String& text) { text_.SetText(text); // Changing text requires materials to be re-evaluated, in case the font is multi-page MarkTextDirty(); UpdateTextBatches(); UpdateTextMaterials(); } void Text3D::SetAlignment(HorizontalAlignment hAlign, VerticalAlignment vAlign) { text_.SetAlignment(hAlign, vAlign); MarkTextDirty(); } void Text3D::SetHorizontalAlignment(HorizontalAlignment align) { text_.SetHorizontalAlignment(align); MarkTextDirty(); } void Text3D::SetVerticalAlignment(VerticalAlignment align) { text_.SetVerticalAlignment(align); MarkTextDirty(); } void Text3D::SetTextAlignment(HorizontalAlignment align) { text_.SetTextAlignment(align); MarkTextDirty(); } void Text3D::SetRowSpacing(float spacing) { text_.SetRowSpacing(spacing); MarkTextDirty(); } void Text3D::SetWordwrap(bool enable) { text_.SetWordwrap(enable); MarkTextDirty(); } void Text3D::SetTextEffect(TextEffect textEffect) { text_.SetTextEffect(textEffect); MarkTextDirty(); } void Text3D::SetEffectColor(const Color& effectColor) { text_.SetEffectColor(effectColor); MarkTextDirty(); } void Text3D::SetEffectDepthBias(float bias) { text_.SetEffectDepthBias(bias); MarkTextDirty(); } void Text3D::SetWidth(int width) { text_.SetMinWidth(width); text_.SetWidth(width); MarkTextDirty(); } void Text3D::SetColor(const Color& color) { text_.SetColor(color); MarkTextDirty(); } void Text3D::SetColor(Corner corner, const Color& color) { text_.SetColor(corner, color); MarkTextDirty(); } void Text3D::SetOpacity(float opacity) { text_.SetOpacity(opacity); MarkTextDirty(); } void Text3D::SetFaceCamera(bool enable) { if (enable != faceCamera_) { faceCamera_ = enable; // Bounding box must be recalculated OnMarkedDirty(node_); } } Material* Text3D::GetMaterial() const { return material_; } Font* Text3D::GetFont() const { return text_.GetFont(); } int Text3D::GetFontSize() const { return text_.GetFontSize(); } const String& Text3D::GetText() const { return text_.GetText(); } HorizontalAlignment Text3D::GetHorizontalAlignment() const { return text_.GetHorizontalAlignment(); } VerticalAlignment Text3D::GetVerticalAlignment() const { return text_.GetVerticalAlignment(); } HorizontalAlignment Text3D::GetTextAlignment() const { return text_.GetTextAlignment(); } float Text3D::GetRowSpacing() const { return text_.GetRowSpacing(); } bool Text3D::GetWordwrap() const { return text_.GetWordwrap(); } TextEffect Text3D::GetTextEffect() const { return text_.GetTextEffect(); } const Color& Text3D::GetEffectColor() const { return text_.GetEffectColor(); } float Text3D::GetEffectDepthBias() const { return text_.GetEffectDepthBias(); } int Text3D::GetWidth() const { return text_.GetWidth(); } int Text3D::GetRowHeight() const { return text_.GetRowHeight(); } unsigned Text3D::GetNumRows() const { return text_.GetNumRows(); } const PODVector& Text3D::GetRowWidths() const { return text_.GetRowWidths(); } const Color& Text3D::GetColor(Corner corner) const { return text_.GetColor(corner); } float Text3D::GetOpacity() const { return text_.GetOpacity(); } void Text3D::OnNodeSet(Node* node) { Drawable::OnNodeSet(node); if (node) customWorldTransform_ = node->GetWorldTransform(); } void Text3D::OnWorldBoundingBoxUpdate() { if (textDirty_) UpdateTextBatches(); // In face camera mode, use the last camera rotation to build the world bounding box worldBoundingBox_ = boundingBox_.Transformed(faceCamera_ ? Matrix3x4(node_->GetWorldPosition(), customWorldTransform_.Rotation(), node_->GetWorldScale()) : node_->GetWorldTransform()); } void Text3D::MarkTextDirty() { textDirty_ = true; OnMarkedDirty(node_); MarkNetworkUpdate(); } void Text3D::SetMaterialAttr(ResourceRef value) { ResourceCache* cache = GetSubsystem(); SetMaterial(cache->GetResource(value.id_)); } void Text3D::SetFontAttr(ResourceRef value) { ResourceCache* cache = GetSubsystem(); text_.font_ = cache->GetResource(value.id_); } ResourceRef Text3D::GetMaterialAttr() const { return GetResourceRef(material_, Material::GetTypeStatic()); } ResourceRef Text3D::GetFontAttr() const { return GetResourceRef(text_.font_, Font::GetTypeStatic()); } void Text3D::UpdateTextBatches() { uiBatches_.Clear(); uiVertexData_.Clear(); text_.GetBatches(uiBatches_, uiVertexData_, IntRect::ZERO); Vector3 offset(Vector3::ZERO); switch (text_.GetHorizontalAlignment()) { case HA_LEFT: break; case HA_CENTER: offset.x_ -= (float)text_.GetWidth() * 0.5f; break; case HA_RIGHT: offset.x_ -= (float)text_.GetWidth(); break; } switch (text_.GetVerticalAlignment()) { case VA_TOP: break; case VA_CENTER: offset.y_ -= (float)text_.GetHeight() * 0.5f; break; case VA_BOTTOM: offset.y_ -= (float)text_.GetHeight(); break; } boundingBox_.defined_ = false; boundingBox_.min_ = boundingBox_.max_ = Vector3::ZERO; for (unsigned i = 0; i < uiVertexData_.Size(); i += UI_VERTEX_SIZE) { Vector3& position = *(reinterpret_cast(&uiVertexData_[i])); position += offset; position *= TEXT_SCALING; position.y_ = -position.y_; boundingBox_.Merge(position); } textDirty_ = false; geometryDirty_ = true; } void Text3D::UpdateTextMaterials(bool forceUpdate) { batches_.Resize(uiBatches_.Size()); geometries_.Resize(uiBatches_.Size()); for (unsigned i = 0; i < batches_.Size(); ++i) { if (!geometries_[i]) { Geometry* geometry = new Geometry(context_); geometry->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_COLOR | MASK_TEXCOORD1); batches_[i].geometry_ = geometries_[i] = geometry; } if (!batches_[i].material_ || forceUpdate) { // If material not defined, create a reasonable default from scratch if (!material_) { Material* material = new Material(context_); Technique* tech = new Technique(context_); Pass* pass = tech->CreatePass(PASS_ALPHA); pass->SetVertexShader("Basic_DiffVCol"); pass->SetPixelShader("Basic_AlphaVCol"); pass->SetBlendMode(BLEND_ALPHA); material->SetTechnique(0, tech); material->SetCullMode(CULL_NONE); batches_[i].material_ = material; } else batches_[i].material_ = material_->Clone(); } Material* material = batches_[i].material_; material->SetTexture(TU_DIFFUSE, uiBatches_[i].texture_); } } }