From bb009b049993c84cc4f84c4d7f7c3bf0c42ea742 Mon Sep 17 00:00:00 2001
From: pawelw
Date: Sat, 7 Nov 2026 12:36:17 +0100
Subject: [PATCH] Text displays: draw opaque characters in the opaque pass
(#201)
* Text displays: accept characters above 127, in windows-1250 or utf-8
A text display (sub-model of type "text") looks up the sub-model of each character
in a 256 entry table, and indexed it with a plain char. With char being signed, any
character above 127 gave a negative index: reading the text went outside the table,
and so did filling the table if a character sub-model had such a name. In effect the
displays could only show ASCII, and a text with national characters could crash.
The lookup is now keyed by unicode code point. Both the displayed text and the names
of the character sub-models are decoded as utf-8 if they are valid utf-8, and as
windows-1250 otherwise, so legacy (ansi) sceneries and models work next to utf-8
ones, and the two can be mixed. A short ansi text can be valid utf-8 by accident
(two upper case accented letters in a row will do); when both readings are possible
the one which finds more of its characters in the model is used.
The rest is unchanged: the first character of the text still isn't displayed, the
characters are still drawn in the translucent pass only. A missing text pointer no
longer gets dereferenced.
* Text displays: draw opaque characters in the opaque pass
The characters of a text display were only laid out in the translucent pass, so a
display had to be made of translucent sub-models to show anything. Those don't write
depth: overhead wires and other translucent geometry drawn later showed through the
display, and translucent geometry in front of it could end up painted over.
The opaque pass now lays the characters out the same way, so a display built from
opaque sub-models is drawn with depth like any other opaque geometry (and casts
shadows). Translucent characters keep working as before.
A model with a text display shows the text of its own instance, so such models are
excluded from the GPU-instanced opaque path.
* Text displays: prepare the characters once for each text
The sequence of character sub-models was cached for the last text only, so
displays of several instances drawn in turn decoded their texts again on
every draw. It is now kept for each text the display shows; the texts of the
instances don't change, so a draw costs a single lookup.
Also the comments added in the renderers are now in English.
* Text displays: move the text decoding to utilities
utf8_to_utf32() and win1250_to_utf32(), with the windows-1250 table, now sit
next to win1250_to_utf8(). The existing function maps only the Polish letters
and passes other bytes above 127 through, so it can't serve the displays.
* Text displays: address the SonarCloud findings
std::array instead of the C-style arrays, the utf-8 sequence length worked
out in a function of its own instead of nested conditional operators,
std::byte for the bit operations, std::ranges::count_if and contains().
The character loop of the renderers moved to Render_Alpha_text(), which
takes the nesting back to the limit, and the static pasText is accessed
through the class.
* Text displays: move the opaque character loop to Render_text()
The same as for the translucent pass: the loop sits in a function of its
own and the static pasText is accessed through the class, which keeps the
nesting within the limit.
* Text displays: access the static iAlpha through the class in the opaque pass
* Text displays: transparent lookup of the text layouts, init-statement for the lookup result
Two SonarCloud findings left in the code of #198: the map of the text layouts
gets a transparent hasher with std::equal_to<>, and the result of the lookup
is declared in the if statement.
---
model/AnimModel.cpp | 2 ++
model/Model3d.cpp | 3 +--
model/Model3d.h | 9 ++++++++-
rendering/opengl33renderer.cpp | 29 +++++++++++++++++++++++++++--
rendering/opengl33renderer.h | 1 +
rendering/openglrenderer.cpp | 25 +++++++++++++++++++++++--
rendering/openglrenderer.h | 3 +++
7 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/model/AnimModel.cpp b/model/AnimModel.cpp
index cafb3a93e..52a879ac2 100644
--- a/model/AnimModel.cpp
+++ b/model/AnimModel.cpp
@@ -505,6 +505,8 @@ bool anim_type_shared_by_instances( TAnimType a ) {
// instanced path (one shared submodel tree across all instances) cannot serve.
bool submodel_tree_blocks_instancing( TSubModel const *Sub ) {
if( Sub == nullptr ) { return false; }
+ // a text display shows the text of its instance, a tree shared by all instances can't do that
+ if( Sub->eType == TP_TEXT ) { return true; }
if( anim_type_unsafe_for_instancing( Sub->b_Anim ) ) { return true; }
if( ( ( Sub->iFlags & 0x4000 ) != 0 ) && ( false == anim_type_shared_by_instances( Sub->b_Anim ) ) ) { return true; }
if( submodel_tree_blocks_instancing( Sub->Child ) ) { return true; }
diff --git a/model/Model3d.cpp b/model/Model3d.cpp
index 66fc08c94..44f5f6255 100644
--- a/model/Model3d.cpp
+++ b/model/Model3d.cpp
@@ -898,8 +898,7 @@ std::vector const &TSubModel::text_letters(std::string const &Text)
{
// the sequence is prepared once for each text the display shows. the texts of the model instances don't change,
// so drawing a display afterwards costs a single lookup, however many instances and passes take turns
- auto const prepared{m_textlayouts.find(Text)};
- if (prepared != m_textlayouts.end())
+ if (auto const prepared{m_textlayouts.find(Text)}; prepared != m_textlayouts.end())
{
return prepared->second;
}
diff --git a/model/Model3d.h b/model/Model3d.h
index 27565c206..a9ef8af12 100644
--- a/model/Model3d.h
+++ b/model/Model3d.h
@@ -15,6 +15,8 @@ http://mozilla.org/MPL/2.0/.
#include "model/material.h"
#include "gl/query.h"
+#include
+
#define EU07_USE_GEOMETRYINDEXING
// Ra: specjalne typy submodeli, poza tym GL_TRIANGLES itp.
@@ -159,7 +161,12 @@ public: // chwilowo
// text display (TP_TEXT) data, filled on first use
std::unordered_map m_letters; // sub-models of the available characters, by unicode code point
bool m_lettersready { false }; // whether the character lookup was built yet
- std::unordered_map> m_textlayouts; // sub-models of consecutive characters, for each text shown
+ // transparent hasher, so the text layouts can be looked up with any string type without a temporary std::string
+ struct text_hash {
+ using is_transparent = void;
+ std::size_t operator()( std::string_view const Text ) const noexcept { return std::hash{}( Text ); }
+ };
+ std::unordered_map, text_hash, std::equal_to<>> m_textlayouts; // sub-models of consecutive characters, for each text shown
TSubModel *Parent{ nullptr }; // nadrzędny, np. do wymnażania macierzy
int iVisible { 1 }; // roboczy stan widoczności
float fVisible { 1.f }; // visibility level
diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp
index 33346fad2..c24f750c8 100644
--- a/rendering/opengl33renderer.cpp
+++ b/rendering/opengl33renderer.cpp
@@ -4152,6 +4152,23 @@ bool opengl33_renderer::Render(TModel3d *Model, material_data const *Material, f
return result;
}
+// opaque characters of a text display, laid out in a row the same way as the translucent ones
+void opengl33_renderer::Render_text(TSubModel *Submodel)
+{
+ if (TSubModel::pasText == nullptr)
+ {
+ return;
+ }
+ ::glPushMatrix();
+ for (auto *p : Submodel->text_letters(*TSubModel::pasText))
+ {
+ Render(p);
+ if (p->fMatrix)
+ ::glMultMatrixf(p->fMatrix->readArray()); // move on to the place of the next character
+ }
+ ::glPopMatrix();
+}
+
void opengl33_renderer::Render(TSubModel *Submodel)
{
glDebug("Render TSubModel");
@@ -4302,9 +4319,17 @@ void opengl33_renderer::Render(TSubModel *Submodel)
}
}
}
- if (Submodel->Child != nullptr)
- if (Submodel->iAlpha & Submodel->iFlags & 0x001F0000)
+ if (Submodel->Child != nullptr && (TSubModel::iAlpha & Submodel->iFlags & 0x001F0000))
+ {
+ if (Submodel->eType == TP_TEXT)
+ {
+ Render_text(Submodel);
+ }
+ else
+ {
Render(Submodel->Child);
+ }
+ }
if (Submodel->iFlags & 0xC000)
{
diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h
index b472849bc..1b4897386 100644
--- a/rendering/opengl33renderer.h
+++ b/rendering/opengl33renderer.h
@@ -314,6 +314,7 @@ class opengl33_renderer : public gfx_renderer {
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle);
bool Render(TModel3d *Model, material_data const *Material, float const Squaredistance);
void Render(TSubModel *Submodel);
+ void Render_text(TSubModel *Submodel); // opaque characters of a text display
void Render(TTrack *Track);
void Render(scene::basic_cell::path_sequence::const_iterator First, scene::basic_cell::path_sequence::const_iterator Last);
// renders the per-track sleeper instances (TTrack::m_sleeper_local_transforms) via GPU instancing.
diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp
index ef54f32c9..180dba771 100644
--- a/rendering/openglrenderer.cpp
+++ b/rendering/openglrenderer.cpp
@@ -2794,6 +2794,21 @@ opengl_renderer::Render( TModel3d *Model, material_data const *Material, float c
return result;
}
+// opaque characters of a text display, laid out in a row the same way as the translucent ones
+void
+opengl_renderer::Render_text( TSubModel *Submodel ) {
+
+ if( TSubModel::pasText == nullptr ) { return; }
+
+ ::glPushMatrix();
+ for( auto *p : Submodel->text_letters( *TSubModel::pasText ) ) {
+ Render( p );
+ if( p->fMatrix )
+ ::glMultMatrixf( p->fMatrix->readArray() ); // move on to the place of the next character
+ }
+ ::glPopMatrix();
+}
+
void
opengl_renderer::Render( TSubModel *Submodel ) {
@@ -3112,9 +3127,15 @@ opengl_renderer::Render( TSubModel *Submodel ) {
}
}
}
- if( Submodel->Child != nullptr )
- if( Submodel->iAlpha & Submodel->iFlags & 0x001F0000 )
+ if( Submodel->Child != nullptr
+ && ( TSubModel::iAlpha & Submodel->iFlags & 0x001F0000 ) ) {
+ if( Submodel->eType == TP_TEXT ) {
+ Render_text( Submodel );
+ }
+ else {
Render( Submodel->Child );
+ }
+ }
if( Submodel->iFlags & 0xC000 )
::glPopMatrix();
diff --git a/rendering/openglrenderer.h b/rendering/openglrenderer.h
index 9779388c7..3a3538a6c 100644
--- a/rendering/openglrenderer.h
+++ b/rendering/openglrenderer.h
@@ -237,6 +237,9 @@ private:
Render( TModel3d *Model, material_data const *Material, float const Squaredistance );
void
Render( TSubModel *Submodel );
+ // opaque characters of a text display
+ void
+ Render_text( TSubModel *Submodel );
void
Render( TTrack *Track );
void