-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathVisualTextModel.h
More file actions
317 lines (267 loc) · 14.3 KB
/
VisualTextModel.h
File metadata and controls
317 lines (267 loc) · 14.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/*!
* \file
*
* Declares a class to hold vertices of the quads that are the backing for a sequence of text
* characters. This is for use in VisualModel-derived classes. Within the backend, the
* VisualTextModel classes are used directly.
*
* There is a hierarchy of implementation files and a base class underlying this, but in client
* code, you just use a VisualTextModel<glver>.
*
* This implementation class adds OpenGL function calls to the base class to make a finished
* VisualTextModel class.
*
* \author Seb James
* \date Oct 2020 - Mar 2026
*/
#pragma once
#include <mplot/VisualTextModelBase.h>
#if defined __gl3_h_ || defined __gl_h_
// GL headers have been externally included
#else
# error "GL headers should have been included already"
#endif
#include <mplot/gl/util_mx.h>
#include <mplot/VisualFace.h>
#include <mplot/VisualResources.h>
namespace mplot
{
//! Forward declaration of a VisualBase class
template <int> class VisualBase;
/*!
* Implementation of a separate data-containing model which is used to render text. It is
* intended that this could comprise part of a mplot::Visual or a mplot::VisualModel. It has its
* own render call. Multicontext-safe GL version (GLAD).
*/
template <int glver = mplot::gl::version_4_1>
class VisualTextModel : public mplot::VisualTextModelBase<glver>
{
public:
VisualTextModel (mplot::TextFeatures _tfeatures)
: VisualTextModelBase<glver>::VisualTextModelBase (_tfeatures) {}
~VisualTextModel()
{
if (this->vbos != nullptr) {
this->get_glfn(this->parentVis)->DeleteBuffers (this->numVBO, this->vbos.get());
this->get_glfn(this->parentVis)->DeleteVertexArrays (1, &this->vao);
}
}
//! Render the VisualTextModel
void render() final
{
if (this->hide == true) { return; }
GLint prev_shader;
GLuint tshaderprog = this->get_tprog (this->parentVis);
auto _glfn = this->get_glfn (this->parentVis);
_glfn->GetIntegerv (GL_CURRENT_PROGRAM, &prev_shader);
// Ensure the correct program is in play for this VisualModel
_glfn->UseProgram (tshaderprog);
// Set uniforms
GLint loc_tc = _glfn->GetUniformLocation (tshaderprog, static_cast<const GLchar*>("textColor"));
if (loc_tc != -1) { _glfn->Uniform3f (loc_tc, this->clr_text[0], this->clr_text[1], this->clr_text[2]); }
GLint loc_a = _glfn->GetUniformLocation (tshaderprog, static_cast<const GLchar*>("alpha"));
if (loc_a != -1) { _glfn->Uniform1f (loc_a, this->alpha); }
GLint loc_v = _glfn->GetUniformLocation (tshaderprog, static_cast<const GLchar*>("v_matrix"));
if (loc_v != -1) { _glfn->UniformMatrix4fv (loc_v, 1, GL_FALSE, this->scenematrix.arr.data()); }
GLint loc_m = _glfn->GetUniformLocation (tshaderprog, static_cast<const GLchar*>("m_matrix"));
if (loc_m != -1) { _glfn->UniformMatrix4fv (loc_m, 1, GL_FALSE, this->viewmatrix.arr.data()); }
_glfn->ActiveTexture (GL_TEXTURE0);
// It is only necessary to bind the vertex array object before rendering
_glfn->BindVertexArray (this->vao);
// We have a max of (2^32)-1 characters. Should be enough.
for (unsigned int i = 0U; i < this->quads.size(); ++i) {
// Bind the right texture for the quad.
_glfn->BindTexture (GL_TEXTURE_2D, this->quad_ids[i]);
// This is 'draw a subset of the elements from the vertex array
// object'. You say how many indices to draw and which base *vertex* you
// start from. In my scheme, I have 4 vertices for each two triangles
// that are constructed. Thus, I draw 6 indices, but increment the base
// vertex by 4 for each letter.
_glfn->DrawElementsBaseVertex (GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0, 4*i);
}
_glfn->BindVertexArray(0);
_glfn->UseProgram (prev_shader);
mplot::gl::Util::checkError (__FILE__, __LINE__, _glfn);
}
//! Compute the geometry for a sample text.
mplot::TextGeometry getTextGeometry (const std::string& _txt) final
{
mplot::TextGeometry geom;
if (!this->get_glfn) { return geom; }
if (this->face == nullptr) {
this->face = VisualResources<glver>::i().getVisualFace (this->tfeatures, this->parentVis,
this->get_glfn(this->parentVis));
}
// First convert string from ASCII/UTF-8 into Unicode.
std::basic_string<char32_t> utxt = mplot::unicode::fromUtf8(_txt);
for (std::basic_string<char32_t>::const_iterator c = utxt.begin(); c != utxt.end(); c++) {
mplot::visgl::CharInfo ci = this->face->glchars[*c];
float drop = (ci.size.y() - ci.bearing.y()) * this->fontscale;
geom.max_drop = (drop > geom.max_drop) ? drop : geom.max_drop;
float bearingy = ci.bearing.y() * this->fontscale;
geom.max_bearingy = (bearingy > geom.max_bearingy) ? bearingy : geom.max_bearingy;
geom.total_advance += ((ci.advance>>6)*this->fontscale);
}
return geom;
}
//! Return the geometry for the stored txt
mplot::TextGeometry getTextGeometry() final
{
mplot::TextGeometry geom;
if (!this->get_glfn) { return geom; }
if (this->face == nullptr) {
this->face = VisualResources<glver>::i().getVisualFace (this->tfeatures, this->parentVis,
this->get_glfn(this->parentVis));
}
for (std::basic_string<char32_t>::const_iterator c = this->txt.begin(); c != this->txt.end(); c++) {
mplot::visgl::CharInfo ci = this->face->glchars[*c];
float drop = (ci.size.y() - ci.bearing.y()) * this->fontscale;
geom.max_drop = (drop > geom.max_drop) ? drop : geom.max_drop;
float bearingy = ci.bearing.y() * this->fontscale;
geom.max_bearingy = (bearingy > geom.max_bearingy) ? bearingy : geom.max_bearingy;
geom.total_advance += ((ci.advance>>6)*this->fontscale);
}
return geom;
}
//! For some reason, I can't place these setupText functions in the base class. Compiler
//! gets confused wtih std::string aka std::__cxx11::basic_string<char> and
//! std::__cxx11::basic_string<char32_t>
//!{
//! Set up a new text at a given position, with the given colour.
void setupText (const std::string& _txt,
const sm::vec<float> _offset, std::array<float, 3> _clr = {0,0,0})
{
this->viewmatrix.translate (_offset);
this->clr_text = _clr;
this->setupText (_txt);
}
//! Set up a new text at a given position, with the given colour and a pre-rotation
void setupText (const std::string& _txt,
const sm::quaternion<float>& _rotation, const sm::vec<float> _offset,
std::array<float, 3> _clr = {0,0,0})
{
this->viewmatrix.translate (_offset);
this->viewmatrix.rotate (_rotation);
this->clr_text = _clr;
this->setupText (_txt);
}
void setupText (const std::string& _txt)
{
// Convert std::string _txt to std::basic_string<uchar32_t> text and call the other setupText
this->setupText (mplot::unicode::fromUtf8 (_txt));
}
//!}
//! With the given text and font size information, create the quads for the text.
void setupText (const std::basic_string<char32_t>& _txt)
{
if (this->face == nullptr) {
this->face = VisualResources<glver>::i().getVisualFace (this->tfeatures, this->parentVis,
this->get_glfn(this->parentVis));
}
this->txt = _txt;
// With glyph information from txt, set up this->quads.
this->quads.clear();
this->quad_ids.clear();
// Our string of letters starts at this location
float letter_pos = 0.0f;
float letter_y = 0.0f;
float text_epsilon = 0.0f;
for (std::basic_string<char32_t>::const_iterator c = this->txt.begin(); c != this->txt.end(); c++) {
if (*c == '\n') {
// Skip newline, but add a y offset and reset letter_pos
letter_pos = 0.0f;
mplot::visgl::CharInfo ch = this->face->glchars['h'];
letter_y += this->line_spacing * -ch.size.y() * this->fontscale;
continue;
}
// Add a quad to this->quads
mplot::visgl::CharInfo ci = this->face->glchars[*c];
float xpos = letter_pos + ci.bearing.x() * this->fontscale;
float ypos = letter_y - (ci.size.y() - ci.bearing.y()) * this->fontscale;
float w = ci.size.x() * this->fontscale;
float h = ci.size.y() * this->fontscale;
// Update extents
if (xpos < this->extents[0]) { this->extents[0] = xpos; } // left
if (xpos+w > this->extents[1]) { this->extents[1] = xpos+w; } // right
if (ypos < this->extents[2]) { this->extents[2] = ypos; } // bottom
if (ypos+h > this->extents[3]) { this->extents[3] = ypos+h; } // top
// What's the order of the vertices for the quads? It is:
// Bottom left, Top left, top right, bottom right.
std::array<float,12> tbox = { xpos, ypos, text_epsilon,
xpos, ypos+h, text_epsilon,
xpos+w, ypos+h, text_epsilon,
xpos+w, ypos, text_epsilon };
text_epsilon -= 10.0f * std::numeric_limits<float>::epsilon();
if constexpr (mplot::VisualTextModelBase<glver>::debug_textquads == true) {
std::cout << "Text box added as quad from\n("
<< tbox[0] << "," << tbox[1] << "," << tbox[2]
<< ") to (" << tbox[3] << "," << tbox[4] << "," << tbox[5]
<< ") to (" << tbox[6] << "," << tbox[7] << "," << tbox[8]
<< ") to (" << tbox[9] << "," << tbox[10] << "," << tbox[11]
<< "). w="<<w<<", h="<<h<<"\n";
std::cout << "Texture ID for that character is: " << ci.textureID << std::endl;
}
this->quads.push_back (tbox);
this->quad_ids.push_back (ci.textureID);
// The value in ci.advance has to be divided by 64 to bring it into the
// same units as the ci.size and ci.bearing values.
letter_pos += ((ci.advance>>6)*this->fontscale);
}
// Ensure we've cleared out vertex info
this->vertexPositions.clear();
this->vertexNormals.clear();
this->vertexColors.clear();
this->vertexTextures.clear();
this->indices.clear();
this->initializeVertices();
this->postVertexInit();
}
protected:
//! Common code to call after the vertices have been set up.
void postVertexInit() final
{
auto _glfn = this->get_glfn (this->parentVis);
if (this->vbos == nullptr) {
// Create vertex array object
_glfn->GenVertexArrays (1, &this->vao); // Safe for OpenGL 4.4-
}
_glfn->BindVertexArray (this->vao);
if (this->vbos == nullptr) {
// Create the vertex buffer objects
this->vbos = std::make_unique<GLuint[]>(this->numVBO);
_glfn->GenBuffers (this->numVBO, this->vbos.get()); // OpenGL 4.4- safe
}
// Set up the indices buffer - bind and buffer the data in this->indices
_glfn->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->vbos[this->idxVBO]);
//std::cout << "indices.size(): " << this->indices.size() << std::endl;
std::size_t sz = this->indices.size() * sizeof(GLuint);
_glfn->BufferData(GL_ELEMENT_ARRAY_BUFFER, sz, this->indices.data(), GL_STATIC_DRAW);
// Binds data from the "C++ world" to the OpenGL shader world for
// "position", "normalin" and "color"
// (bind, buffer and set vertex array object attribute)
this->setupVBO (this->vbos[this->posnVBO], this->vertexPositions, visgl::posnLoc);
this->setupVBO (this->vbos[this->normVBO], this->vertexNormals, visgl::normLoc);
this->setupVBO (this->vbos[this->colVBO], this->vertexColors, visgl::colLoc);
this->setupVBO (this->vbos[this->textureVBO], this->vertexTextures, visgl::textureLoc);
// Possibly release (unbind) the vertex buffers, but have to unbind vertex
// array object first.
_glfn->BindVertexArray(0); // carefully unbind
}
public:
//! Get the GladGLContext function pointer
std::function<GladGLContext*(mplot::VisualBase<glver>*)> get_glfn;
protected:
//! A face for this text. The face is specfied by tfeatures.font
mplot::visgl::VisualFace* face = nullptr;
//! Set up a vertex buffer object - bind, buffer and set vertex array object attribute
void setupVBO (GLuint& buf, std::vector<float>& dat, unsigned int bufferAttribPosition) final
{
std::size_t sz = dat.size() * sizeof(float);
auto _glfn = this->get_glfn (this->parentVis);
_glfn->BindBuffer (GL_ARRAY_BUFFER, buf);
_glfn->BufferData (GL_ARRAY_BUFFER, sz, dat.data(), GL_STATIC_DRAW);
_glfn->VertexAttribPointer (bufferAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, (void*)(0));
_glfn->EnableVertexAttribArray (bufferAttribPosition);
}
};
} // namespace mplot