1 // SuperTuxKart - a fun racing game with go-kart
2 // Copyright (C) 2014-2015 SuperTuxKart-Team
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 3
7 // of the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18 #ifndef SERVER_ONLY
19
20 #include "graphics/stk_text_billboard.hpp"
21 #include "graphics/sp/sp_base.hpp"
22 #include "graphics/central_settings.hpp"
23 #include "graphics/irr_driver.hpp"
24 #include "graphics/graphics_restrictions.hpp"
25
26 // ----------------------------------------------------------------------------
STKTextBillboard(const video::SColor & color_top,const video::SColor & color_bottom,ISceneNode * parent,ISceneManager * mgr,s32 id,const core::vector3df & position,const core::vector3df & scale)27 STKTextBillboard::STKTextBillboard(const video::SColor& color_top,
28 const video::SColor& color_bottom,
29 ISceneNode* parent, ISceneManager* mgr,
30 s32 id,
31 const core::vector3df& position,
32 const core::vector3df& scale)
33 : ISceneNode(parent, mgr, id, position,
34 core::vector3df(0.0f, 0.0f, 0.0f), scale)
35 {
36 using namespace SP;
37 m_color_top = color_top;
38 if (CVS->isDeferredEnabled() && CVS->isGLSL())
39 {
40 m_color_top.setRed(srgb255ToLinear(m_color_top.getRed()));
41 m_color_top.setGreen(srgb255ToLinear(m_color_top.getGreen()));
42 m_color_top.setBlue(srgb255ToLinear(m_color_top.getBlue()));
43 }
44 m_color_bottom = color_bottom;
45 if (CVS->isDeferredEnabled() && CVS->isGLSL())
46 {
47 video::SColorf tmp(m_color_bottom);
48 m_color_bottom.setRed(srgb255ToLinear(m_color_bottom.getRed()));
49 m_color_bottom.setGreen(srgb255ToLinear(m_color_bottom.getGreen()));
50 m_color_bottom.setBlue(srgb255ToLinear(m_color_bottom.getBlue()));
51 }
52 static_assert(sizeof(GLTB) == 20, "Wrong compiler padding");
53 } // STKTextBillboard
54
55 // ----------------------------------------------------------------------------
getDefaultScale(FontWithFace * face)56 float STKTextBillboard::getDefaultScale(FontWithFace* face)
57 {
58 return 1.0f / (float)face->getDPI();
59 } // getDefaultScale
60
61 // ----------------------------------------------------------------------------
updateAbsolutePosition()62 void STKTextBillboard::updateAbsolutePosition()
63 {
64 // Make billboard always face the camera
65 scene::ICameraSceneNode* curr_cam =
66 irr_driver->getSceneManager()->getActiveCamera();
67 if (!curr_cam) return;
68 core::quaternion q(curr_cam->getViewMatrix());
69 q.W = -q.W;
70
71 if (Parent)
72 {
73 // Override to not use the parent's rotation
74 core::vector3df wc = RelativeTranslation;
75 Parent->getAbsoluteTransformation().transformVect(wc);
76 AbsoluteTransformation.setTranslation(wc);
77 q.getMatrix(AbsoluteTransformation, wc);
78 }
79 else
80 {
81 q.getMatrix(AbsoluteTransformation, RelativeTranslation);
82 }
83 core::matrix4 m;
84 m.setScale(RelativeScale);
85 AbsoluteTransformation *= m;
86 if (CVS->isGLSL())
87 {
88 m_instanced_data =
89 SP::SPInstancedData(AbsoluteTransformation, 0, 0, 0, 0);
90 }
91 } // updateAbsolutePosition
92
93 // ----------------------------------------------------------------------------
init(const core::stringw & text,FontWithFace * face)94 void STKTextBillboard::init(const core::stringw& text, FontWithFace* face)
95 {
96 m_face = face;
97 m_text = text;
98 m_chars = new std::vector<STKTextBillboardChar>();
99 core::dimension2du size = face->getDimension(text);
100 face->drawText(text, core::rect<s32>(0, 0, size.Width, size.Height),
101 video::SColor(255,255,255,255), false, false, NULL, NULL, this);
102
103 const float scale = getDefaultScale(face);
104 float max_x = 0;
105 float min_y = 0;
106 float max_y = 0;
107 for (unsigned int i = 0; i < m_chars->size(); i++)
108 {
109 float char_x = (*m_chars)[i].m_dest_rect.LowerRightCorner.X;
110 if (char_x > max_x)
111 {
112 max_x = char_x;
113 }
114
115 float char_min_y = (*m_chars)[i].m_dest_rect.UpperLeftCorner.Y;
116 float char_max_y = (*m_chars)[i].m_dest_rect.LowerRightCorner.Y;
117 if (char_min_y < min_y)
118 {
119 min_y = char_min_y;
120 }
121 if (char_max_y > min_y)
122 {
123 max_y = char_max_y;
124 }
125 }
126 float scaled_center_x = (max_x / 2.0f) * scale;
127 float scaled_y = (max_y / 2.0f) * scale;
128
129 for (unsigned int i = 0; i < m_chars->size(); i++)
130 {
131 core::vector3df char_pos((*m_chars)[i].m_dest_rect.UpperLeftCorner.X,
132 (*m_chars)[i].m_dest_rect.UpperLeftCorner.Y, 0);
133 char_pos *= scale;
134
135 core::vector3df char_pos2((*m_chars)[i].m_dest_rect.LowerRightCorner.X,
136 (*m_chars)[i].m_dest_rect.LowerRightCorner.Y, 0);
137 char_pos2 *= scale;
138
139 float tex_width = (float)(*m_chars)[i].m_texture->getSize().Width;
140 float tex_height = (float)(*m_chars)[i].m_texture->getSize().Height;
141 using namespace MiniGLM;
142 std::array<GLTB, 4> triangle_strip =
143 {{
144 {
145 core::vector3df
146 (char_pos.X - scaled_center_x, char_pos.Y - scaled_y, 0),
147 m_color_bottom,
148 {
149 toFloat16((*m_chars)
150 [i].m_source_rect.UpperLeftCorner.X / tex_width),
151 toFloat16((*m_chars)
152 [i].m_source_rect.LowerRightCorner.Y / tex_height)
153 }
154 },
155
156 {
157 core::vector3df
158 (char_pos.X - scaled_center_x, char_pos2.Y - scaled_y, 0),
159 m_color_top,
160 {
161 toFloat16((*m_chars)
162 [i].m_source_rect.UpperLeftCorner.X / tex_width),
163 toFloat16((*m_chars)
164 [i].m_source_rect.UpperLeftCorner.Y / tex_height)
165 }
166 },
167
168 {
169 core::vector3df
170 (char_pos2.X - scaled_center_x, char_pos.Y - scaled_y, 0),
171 m_color_bottom,
172 {
173 toFloat16((*m_chars)
174 [i].m_source_rect.LowerRightCorner.X / tex_width),
175 toFloat16((*m_chars)
176 [i].m_source_rect.LowerRightCorner.Y / tex_height)
177 }
178 },
179
180 {
181 core::vector3df
182 (char_pos2.X - scaled_center_x, char_pos2.Y - scaled_y, 0),
183 m_color_top,
184 {
185 toFloat16((*m_chars)
186 [i].m_source_rect.LowerRightCorner.X / tex_width),
187 toFloat16((*m_chars)
188 [i].m_source_rect.UpperLeftCorner.Y / tex_height)
189 }
190 }
191 }};
192 m_gl_tbs[(*m_chars)[i].m_texture].push_back(triangle_strip);
193 }
194
195 glGenBuffers(1, &m_instanced_array);
196 glBindBuffer(GL_ARRAY_BUFFER, m_instanced_array);
197 glBufferData(GL_ARRAY_BUFFER,
198 12 /*position*/ + 16/*quaternion*/ + 8 /*scale*/, NULL,
199 GL_DYNAMIC_DRAW);
200 for (auto& p : m_gl_tbs)
201 {
202 glGenVertexArrays(1, &m_vao_vbos[p.first].first);
203 glGenBuffers(1, &m_vao_vbos[p.first].second);
204 glBindBuffer(GL_ARRAY_BUFFER, m_vao_vbos.at(p.first).second);
205 glBufferData(GL_ARRAY_BUFFER, m_gl_tbs.at(p.first).size() * 4 * 20,
206 m_gl_tbs.at(p.first).data(), GL_STATIC_DRAW);
207 glBindBuffer(GL_ARRAY_BUFFER, 0);
208 glBindVertexArray(m_vao_vbos.at(p.first).first);
209 glBindBuffer(GL_ARRAY_BUFFER, m_vao_vbos.at(p.first).second);
210
211 // Position
212 glEnableVertexAttribArray(0);
213 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 20, (void*)0);
214
215 // Vertex color
216 glEnableVertexAttribArray(2);
217 glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 20,
218 (void*)12);
219
220 // 1st texture coordinates
221 glEnableVertexAttribArray(3);
222 glVertexAttribPointer(3, 2, GL_HALF_FLOAT, GL_FALSE, 20, (void*)16);
223
224 glBindBuffer(GL_ARRAY_BUFFER, m_instanced_array);
225 // Origin
226 glEnableVertexAttribArray(8);
227 glVertexAttribPointer(8, 3, GL_FLOAT, GL_FALSE, 36, (void*)0);
228 glVertexAttribDivisorARB(8, 1);
229
230 // Rotation (quaternion in 4 32bit floats)
231 glEnableVertexAttribArray(9);
232 glVertexAttribPointer(9, 4, GL_FLOAT, GL_FALSE, 36, (void*)12);
233 glVertexAttribDivisorARB(9, 1);
234
235 // Scale (3 half floats and .w unused)
236 glEnableVertexAttribArray(10);
237 glVertexAttribPointer(10, 4, GL_HALF_FLOAT, GL_FALSE, 36, (void*)28);
238 glVertexAttribDivisorARB(10, 1);
239
240 glBindVertexArray(0);
241 glBindBuffer(GL_ARRAY_BUFFER, 0);
242 }
243
244 Vec3 min = Vec3( 999999.9f);
245 Vec3 max = Vec3(-999999.9f);
246 for (auto& p : m_gl_tbs)
247 {
248 for (auto& q : p.second)
249 {
250 for (auto& r : q)
251 {
252 Vec3 c(r.m_position.X, r.m_position.Y, r.m_position.Z);
253 min.min(c);
254 max.max(c);
255 }
256 }
257 }
258 m_bbox.MinEdge = min.toIrrVector();
259 m_bbox.MaxEdge = max.toIrrVector();
260
261 delete m_chars;
262 updateAbsolutePosition();
263 } // init
264
265 // ----------------------------------------------------------------------------
initLegacy(const core::stringw & text,FontWithFace * face)266 void STKTextBillboard::initLegacy(const core::stringw& text, FontWithFace* face)
267 {
268 m_face = face;
269 m_text = text;
270 m_chars = new std::vector<STKTextBillboardChar>();
271 core::dimension2du size = face->getDimension(text);
272 face->drawText(text, core::rect<s32>(0, 0, size.Width, size.Height),
273 video::SColor(255,255,255,255), false, false, NULL, NULL, this);
274
275 const float scale = getDefaultScale(face);
276 float max_x = 0;
277 float min_y = 0;
278 float max_y = 0;
279 for (unsigned int i = 0; i < m_chars->size(); i++)
280 {
281 float char_x = (*m_chars)[i].m_dest_rect.LowerRightCorner.X;
282 if (char_x > max_x)
283 {
284 max_x = char_x;
285 }
286
287 float char_min_y = (*m_chars)[i].m_dest_rect.UpperLeftCorner.Y;
288 float char_max_y = (*m_chars)[i].m_dest_rect.LowerRightCorner.Y;
289 if (char_min_y < min_y)
290 {
291 min_y = char_min_y;
292 }
293 if (char_max_y > min_y)
294 {
295 max_y = char_max_y;
296 }
297 }
298 float scaled_center_x = (max_x / 2.0f) * scale;
299 float scaled_y = (max_y / 2.0f) * scale;
300
301 std::unordered_map<video::ITexture*,
302 std::vector<std::array<irr::video::S3DVertex, 4> > > irr_tbs;
303 for (unsigned int i = 0; i < m_chars->size(); i++)
304 {
305 core::vector3df char_pos((*m_chars)[i].m_dest_rect.UpperLeftCorner.X,
306 (*m_chars)[i].m_dest_rect.UpperLeftCorner.Y, 0);
307 char_pos *= scale;
308
309 core::vector3df char_pos2((*m_chars)[i].m_dest_rect.LowerRightCorner.X,
310 (*m_chars)[i].m_dest_rect.LowerRightCorner.Y, 0);
311 char_pos2 *= scale;
312
313 float tex_width = (float)(*m_chars)[i].m_texture->getSize().Width;
314 float tex_height = (float)(*m_chars)[i].m_texture->getSize().Height;
315 std::array<irr::video::S3DVertex, 4> triangle =
316 {{
317 {
318 core::vector3df
319 (char_pos.X - scaled_center_x, char_pos.Y - scaled_y, 0),
320 core::vector3df(0.0f, 1.0f, 0.0f),
321 m_color_bottom,
322 core::vector2df
323 ((*m_chars)
324 [i].m_source_rect.UpperLeftCorner.X / tex_width,
325 (*m_chars)
326 [i].m_source_rect.LowerRightCorner.Y / tex_height)
327 },
328
329 {
330 core::vector3df
331 (char_pos2.X - scaled_center_x, char_pos.Y - scaled_y, 0),
332 core::vector3df(0.0f, 1.0f, 0.0f),
333 m_color_bottom,
334 core::vector2df
335 ((*m_chars)
336 [i].m_source_rect.LowerRightCorner.X / tex_width,
337 (*m_chars)
338 [i].m_source_rect.LowerRightCorner.Y / tex_height)
339 },
340
341 {
342 core::vector3df
343 (char_pos2.X - scaled_center_x, char_pos2.Y - scaled_y, 0),
344 core::vector3df(0.0f, 1.0f, 0.0f),
345
346 m_color_top,
347 core::vector2df
348 ((*m_chars)
349 [i].m_source_rect.LowerRightCorner.X / tex_width,
350 (*m_chars)
351 [i].m_source_rect.UpperLeftCorner.Y / tex_height)
352 },
353
354 {
355 core::vector3df
356 (char_pos.X - scaled_center_x, char_pos2.Y - scaled_y, 0),
357 core::vector3df(0.0f, 1.0f, 0.0f),
358 m_color_top,
359 core::vector2df
360 ((*m_chars)
361 [i].m_source_rect.UpperLeftCorner.X / tex_width,
362 (*m_chars)
363 [i].m_source_rect.UpperLeftCorner.Y / tex_height)
364 }
365 }};
366 irr_tbs[(*m_chars)[i].m_texture].push_back(triangle);
367 }
368
369 for (auto& p : irr_tbs)
370 {
371 scene::SMeshBuffer* buffer = new scene::SMeshBuffer();
372 buffer->getMaterial().setTexture(0, p.first);
373 buffer->getMaterial().MaterialType =
374 video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
375 buffer->getMaterial().Lighting = false;
376
377 std::vector<uint16_t> indices;
378 for (unsigned i = 0; i < p.second.size(); i++)
379 {
380 indices.push_back(4 * i + 2);
381 indices.push_back(4 * i + 1);
382 indices.push_back(4 * i + 0);
383 indices.push_back(4 * i + 3);
384 indices.push_back(4 * i + 2);
385 indices.push_back(4 * i + 0);
386 }
387 buffer->append(p.second.data(), p.second.size() * 4,
388 indices.data(), indices.size());
389 buffer->recalculateBoundingBox();
390 m_gl_mb[p.first] = buffer;
391 }
392
393 Vec3 min = Vec3( 999999.9f);
394 Vec3 max = Vec3(-999999.9f);
395 for (auto& p : irr_tbs)
396 {
397 for (auto& q : p.second)
398 {
399 for (auto& r : q)
400 {
401 Vec3 c(r.Pos.X, r.Pos.Y, r.Pos.Z);
402 min.min(c);
403 max.max(c);
404 }
405 }
406 }
407 m_bbox.MinEdge = min.toIrrVector();
408 m_bbox.MaxEdge = max.toIrrVector();
409
410 delete m_chars;
411 updateAbsolutePosition();
412
413 } // initLegacy
414
415 // ----------------------------------------------------------------------------
render()416 void STKTextBillboard::render()
417 {
418 video::IVideoDriver* driver = SceneManager->getVideoDriver();
419 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
420 for (auto& p : m_gl_mb)
421 {
422 driver->setMaterial(p.second->getMaterial());
423 driver->drawMeshBuffer(p.second);
424 }
425 } // render
426
427 // ----------------------------------------------------------------------------
OnRegisterSceneNode()428 void STKTextBillboard::OnRegisterSceneNode()
429 {
430 SceneManager->registerNodeForRendering(this, ESNRP_TRANSPARENT);
431 ISceneNode::OnRegisterSceneNode();
432 } // OnRegisterSceneNode
433
434 // ----------------------------------------------------------------------------
collectChar(video::ITexture * texture,const core::rect<float> & dest_rect,const core::rect<s32> & source_rect,const video::SColor * const colors)435 void STKTextBillboard::collectChar(video::ITexture* texture,
436 const core::rect<float>& dest_rect,
437 const core::rect<s32>& source_rect,
438 const video::SColor* const colors)
439 {
440 assert(m_chars != NULL);
441 m_chars->push_back(STKTextBillboardChar(texture, dest_rect, source_rect,
442 colors));
443 } // collectChar
444
445 // ----------------------------------------------------------------------------
reload()446 void STKTextBillboard::reload()
447 {
448 clearBuffer();
449 if (CVS->isGLSL())
450 init(m_text, m_face);
451 else
452 initLegacy(m_text, m_face);
453 } // reload
454
455 // ----------------------------------------------------------------------------
updateTextBillboard(core::list<scene::ISceneNode * > & List)456 static void updateTextBillboard(core::list<scene::ISceneNode*>& List)
457 {
458 core::list<scene::ISceneNode*>::Iterator I = List.begin(), E = List.end();
459 for (; I != E; ++I)
460 {
461 if (STKTextBillboard* tb = dynamic_cast<STKTextBillboard*>(*I))
462 tb->reload();
463 updateTextBillboard((*I)->getChildren());
464 }
465 } // updateTextBillboard
466
467 // ----------------------------------------------------------------------------
updateAllTextBillboards()468 void STKTextBillboard::updateAllTextBillboards()
469 {
470 updateTextBillboard(
471 irr_driver->getSceneManager()->getRootSceneNode()->getChildren());
472 } // updateAllTextBillboards
473
474 #endif // !SERVER_ONLY
475
476