You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

739 lines
32 KiB

  1. /*******************************************************************************************
  2. *
  3. * raylib [text] example - Draw 2D text in 3D
  4. *
  5. * Draw a 2D text in 3D space, each letter is drawn in a quad (or 2 quads if backface is set)
  6. * where the texture coodinates of each quad map to the texture coordinates of the glyphs
  7. * inside the font texture.
  8. * A more efficient approach, i believe, would be to render the text in a render texture and
  9. * map that texture to a plane and render that, or maybe a shader but my method allows more
  10. * flexibility...for example to change position of each letter individually to make somethink
  11. * like a wavy text effect.
  12. *
  13. * Special thanks to:
  14. * @Nighten for the DrawTextStyle() code https://github.com/NightenDushi/Raylib_DrawTextStyle
  15. * Chris Camacho (codifies - http://bedroomcoders.co.uk/) for the alpha discard shader
  16. *
  17. * This example has been created using raylib 3.5 (www.raylib.com)
  18. * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
  19. *
  20. * Example contributed by Vlad Adrian (@Demizdor) and reviewed by Ramon Santamaria (@raysan5)
  21. *
  22. * Copyright (C) 2021 Vlad Adrian (@Demizdor - https://github.com/Demizdor)
  23. *
  24. ********************************************************************************************/
  25. #include "raylib.h"
  26. #include "rlgl.h"
  27. #include <stddef.h> // Required for: NULL
  28. #include <math.h> // Required for: sinf()
  29. // To make it work with the older RLGL module just comment the line below
  30. #define RAYLIB_NEW_RLGL
  31. //--------------------------------------------------------------------------------------
  32. // Globals
  33. //--------------------------------------------------------------------------------------
  34. #define LETTER_BOUNDRY_SIZE 0.25f
  35. #define TEXT_MAX_LAYERS 32
  36. #define LETTER_BOUNDRY_COLOR VIOLET
  37. bool SHOW_LETTER_BOUNDRY = false;
  38. bool SHOW_TEXT_BOUNDRY = false;
  39. //--------------------------------------------------------------------------------------
  40. // Data Types definition
  41. //--------------------------------------------------------------------------------------
  42. // Configuration structure for waving the text
  43. typedef struct {
  44. Vector3 waveRange;
  45. Vector3 waveSpeed;
  46. Vector3 waveOffset;
  47. } WaveTextConfig;
  48. //--------------------------------------------------------------------------------------
  49. // Module Functions Declaration
  50. //--------------------------------------------------------------------------------------
  51. // Draw a codepoint in 3D space
  52. void DrawTextCodepoint3D(Font font, int codepoint, Vector3 position, float fontSize, bool backface, Color tint);
  53. // Draw a 2D text in 3D space
  54. void DrawText3D(Font font, const char *text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint);
  55. // Measure a text in 3D. For some reason `MeasureTextEx()` just doesn't seem to work so i had to use this instead.
  56. Vector3 MeasureText3D(Font font, const char* text, float fontSize, float fontSpacing, float lineSpacing);
  57. // Draw a 2D text in 3D space and wave the parts that start with `~~` and end with `~~`.
  58. // This is a modified version of the original code by @Nighten found here https://github.com/NightenDushi/Raylib_DrawTextStyle
  59. void DrawTextWave3D(Font font, const char *text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, WaveTextConfig* config, float time, Color tint);
  60. // Measure a text in 3D ignoring the `~~` chars.
  61. Vector3 MeasureTextWave3D(Font font, const char* text, float fontSize, float fontSpacing, float lineSpacing);
  62. // Generates a nice color with a random hue
  63. Color GenerateRandomColor(float s, float v);
  64. //------------------------------------------------------------------------------------
  65. // Program main entry point
  66. //------------------------------------------------------------------------------------
  67. int main(void)
  68. {
  69. // Initialization
  70. //--------------------------------------------------------------------------------------
  71. const int screenWidth = 800;
  72. const int screenHeight = 450;
  73. SetConfigFlags(FLAG_MSAA_4X_HINT|FLAG_VSYNC_HINT);
  74. InitWindow(screenWidth, screenHeight, "raylib [text] example - draw 2D text in 3D");
  75. bool spin = true; // Spin the camera?
  76. bool multicolor = false; // Multicolor mode
  77. // Define the camera to look into our 3d world
  78. Camera3D camera = { 0 };
  79. camera.position = (Vector3){ -10.0f, 15.0f, -10.0f }; // Camera position
  80. camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Camera looking at point
  81. camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target)
  82. camera.fovy = 45.0f; // Camera field-of-view Y
  83. camera.projection = CAMERA_PERSPECTIVE; // Camera mode type
  84. SetCameraMode(camera, CAMERA_ORBITAL);
  85. Vector3 cubePosition = { 0.0f, 1.0f, 0.0f };
  86. Vector3 cubeSize = { 2.0f, 2.0f, 2.0f };
  87. SetTargetFPS(60); // Set our game to run at 60 frames-per-second
  88. // Use the default font
  89. Font font = GetFontDefault();
  90. float fontSize = 8.0f;
  91. float fontSpacing = 0.5f;
  92. float lineSpacing = -1.0f;
  93. // Set the text (using markdown!)
  94. char text[64] = "Hello ~~World~~ in 3D!";
  95. Vector3 tbox = {0};
  96. int layers = 1;
  97. int quads = 0;
  98. float layerDistance = 0.01f;
  99. WaveTextConfig wcfg;
  100. wcfg.waveSpeed.x = wcfg.waveSpeed.y = 3.0f; wcfg.waveSpeed.z = 0.5f;
  101. wcfg.waveOffset.x = wcfg.waveOffset.y = wcfg.waveOffset.z = 0.35f;
  102. wcfg.waveRange.x = wcfg.waveRange.y = wcfg.waveRange.z = 0.45f;
  103. float time = 0.0f;
  104. // Setup a light and dark color
  105. Color light = MAROON;
  106. Color dark = RED;
  107. // Load the alpha discard shader
  108. Shader alphaDiscard = LoadShader(NULL, "resources/shaders/glsl330/alpha_discard.fs");
  109. // Array filled with multiple random colors (when multicolor mode is set)
  110. Color multi[TEXT_MAX_LAYERS] = {0};
  111. //--------------------------------------------------------------------------------------
  112. // Main game loop
  113. while (!WindowShouldClose()) // Detect window close button or ESC key
  114. {
  115. // Update
  116. //----------------------------------------------------------------------------------
  117. // Handle font files dropped
  118. if (IsFileDropped())
  119. {
  120. int count = 0;
  121. char **droppedFiles = GetDroppedFiles(&count);
  122. // NOTE: We only support first ttf file dropped
  123. if (IsFileExtension(droppedFiles[0], ".ttf"))
  124. {
  125. UnloadFont(font);
  126. font = LoadFontEx(droppedFiles[0], fontSize, 0, 0);
  127. }
  128. else if (IsFileExtension(droppedFiles[0], ".fnt"))
  129. {
  130. UnloadFont(font);
  131. font = LoadFont(droppedFiles[0]);
  132. fontSize = font.baseSize;
  133. }
  134. ClearDroppedFiles();
  135. }
  136. // Handle Events
  137. if (IsKeyPressed(KEY_F1)) SHOW_LETTER_BOUNDRY = !SHOW_LETTER_BOUNDRY;
  138. if (IsKeyPressed(KEY_F2)) SHOW_TEXT_BOUNDRY = !SHOW_TEXT_BOUNDRY;
  139. if (IsKeyPressed(KEY_F3))
  140. {
  141. // Handle camera change
  142. spin = !spin;
  143. // we need to reset the camera when changing modes
  144. camera = (Camera3D){ 0 };
  145. camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Camera looking at point
  146. camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target)
  147. camera.fovy = 45.0f; // Camera field-of-view Y
  148. camera.projection = CAMERA_PERSPECTIVE; // Camera mode type
  149. if (spin)
  150. {
  151. camera.position = (Vector3){ -10.0f, 15.0f, -10.0f }; // Camera position
  152. SetCameraMode(camera, CAMERA_ORBITAL);
  153. }
  154. else
  155. {
  156. camera.position = (Vector3){ 10.0f, 10.0f, -10.0f }; // Camera position
  157. SetCameraMode(camera, CAMERA_FREE);
  158. }
  159. }
  160. // Handle clicking the cube
  161. if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
  162. {
  163. Ray ray = GetMouseRay(GetMousePosition(), camera);
  164. // Check collision between ray and box
  165. RayCollision collision = GetRayCollisionBox(ray,
  166. (BoundingBox){(Vector3){ cubePosition.x - cubeSize.x/2, cubePosition.y - cubeSize.y/2, cubePosition.z - cubeSize.z/2 },
  167. (Vector3){ cubePosition.x + cubeSize.x/2, cubePosition.y + cubeSize.y/2, cubePosition.z + cubeSize.z/2 }});
  168. if (collision.hit)
  169. {
  170. // Generate new random colors
  171. light = GenerateRandomColor(0.5f, 0.78f);
  172. dark = GenerateRandomColor(0.4f, 0.58f);
  173. }
  174. }
  175. // Handle text layers changes
  176. if (IsKeyPressed(KEY_HOME)) { if (layers > 1) --layers; }
  177. else if (IsKeyPressed(KEY_END)) { if (layers < TEXT_MAX_LAYERS) ++layers; }
  178. // Handle text changes
  179. if (IsKeyPressed(KEY_LEFT)) fontSize -= 0.5f;
  180. else if (IsKeyPressed(KEY_RIGHT)) fontSize += 0.5f;
  181. else if (IsKeyPressed(KEY_UP)) fontSpacing -= 0.1f;
  182. else if (IsKeyPressed(KEY_DOWN)) fontSpacing += 0.1f;
  183. else if (IsKeyPressed(KEY_PAGE_UP)) lineSpacing -= 0.1f;
  184. else if (IsKeyPressed(KEY_PAGE_DOWN)) lineSpacing += 0.1f;
  185. else if (IsKeyDown(KEY_INSERT)) layerDistance -= 0.001f;
  186. else if (IsKeyDown(KEY_DELETE)) layerDistance += 0.001f;
  187. else if (IsKeyPressed(KEY_TAB))
  188. {
  189. multicolor = !multicolor; // Enable /disable multicolor mode
  190. if (multicolor)
  191. {
  192. // Fill color array with random colors
  193. for (int i = 0; i < TEXT_MAX_LAYERS; ++i)
  194. {
  195. multi[i] = GenerateRandomColor(0.5f, 0.8f);
  196. multi[i].a = GetRandomValue(0, 255);
  197. }
  198. }
  199. }
  200. // Handle text input
  201. int ch = GetCharPressed();
  202. if (IsKeyPressed(KEY_BACKSPACE))
  203. {
  204. // Remove last char
  205. int len = TextLength(text);
  206. if (len > 0) text[len - 1] = '\0';
  207. }
  208. else if (IsKeyPressed(KEY_ENTER))
  209. {
  210. // handle newline
  211. int len = TextLength(text);
  212. if (len < sizeof(text) - 1)
  213. {
  214. text[len] = '\n';
  215. text[len+1] ='\0';
  216. }
  217. }
  218. else
  219. {
  220. // append only printable chars
  221. int len = TextLength(text);
  222. if (len < sizeof(text) - 1)
  223. {
  224. text[len] = ch;
  225. text[len+1] ='\0';
  226. }
  227. }
  228. // Measure 3D text so we can center it
  229. tbox = MeasureTextWave3D(font, text, fontSize, fontSpacing, lineSpacing);
  230. UpdateCamera(&camera); // Update camera
  231. quads = 0; // Reset quad counter
  232. time += GetFrameTime(); // Update timer needed by `DrawTextWave3D()`
  233. //----------------------------------------------------------------------------------
  234. // Draw
  235. //----------------------------------------------------------------------------------
  236. BeginDrawing();
  237. ClearBackground(RAYWHITE);
  238. BeginMode3D(camera);
  239. DrawCubeV(cubePosition, cubeSize, dark);
  240. DrawCubeWires(cubePosition, 2.1f, 2.1f, 2.1f, light);
  241. DrawGrid(10, 2.0f);
  242. // Use a shader to handle the depth buffer issue with transparent textures
  243. // NOTE: more info at https://bedroomcoders.co.uk/raylib-billboards-advanced-use/
  244. BeginShaderMode(alphaDiscard);
  245. // Draw the 3D text above the red cube
  246. rlPushMatrix();
  247. rlRotatef(90.0f, 1.0f, 0.0f, 0.0f);
  248. rlRotatef(90.0f, 0.0f, 0.0f, -1.0f);
  249. for (int i = 0; i < layers; ++i)
  250. {
  251. Color clr = light;
  252. if (multicolor) clr = multi[i];
  253. DrawTextWave3D(font, text, (Vector3){ -tbox.x/2.0f, layerDistance*i, -4.5f }, fontSize, fontSpacing, lineSpacing, true, &wcfg, time, clr);
  254. }
  255. // Draw the text boundry if set
  256. if (SHOW_TEXT_BOUNDRY) DrawCubeWiresV((Vector3){ 0.0f, 0.0f, -4.5f + tbox.z/2 }, tbox, dark);
  257. rlPopMatrix();
  258. // Don't draw the letter boundries for the 3D text below
  259. bool slb = SHOW_LETTER_BOUNDRY;
  260. SHOW_LETTER_BOUNDRY = false;
  261. // Draw 3D options (use default font)
  262. //-------------------------------------------------------------------------
  263. rlPushMatrix();
  264. rlRotatef(180.0f, 0.0f, 1.0f, 0.0f);
  265. char *opt = (char *)TextFormat("< SIZE: %2.1f >", fontSize);
  266. quads += TextLength(opt);
  267. Vector3 m = MeasureText3D(GetFontDefault(), opt, 8.0f, 1.0f, 0.0f);
  268. Vector3 pos = { -m.x/2.0f, 0.01f, 2.0f};
  269. DrawText3D(GetFontDefault(), opt, pos, 8.0f, 1.0f, 0.0f, false, BLUE);
  270. pos.z += 0.5f + m.z;
  271. opt = (char *)TextFormat("< SPACING: %2.1f >", fontSpacing);
  272. quads += TextLength(opt);
  273. m = MeasureText3D(GetFontDefault(), opt, 8.0f, 1.0f, 0.0f);
  274. pos.x = -m.x/2.0f;
  275. DrawText3D(GetFontDefault(), opt, pos, 8.0f, 1.0f, 0.0f, false, BLUE);
  276. pos.z += 0.5f + m.z;
  277. opt = (char *)TextFormat("< LINE: %2.1f >", lineSpacing);
  278. quads += TextLength(opt);
  279. m = MeasureText3D(GetFontDefault(), opt, 8.0f, 1.0f, 0.0f);
  280. pos.x = -m.x/2.0f;
  281. DrawText3D(GetFontDefault(), opt, pos, 8.0f, 1.0f, 0.0f, false, BLUE);
  282. pos.z += 1.0f + m.z;
  283. opt = (char *)TextFormat("< LBOX: %3s >", slb? "ON" : "OFF");
  284. quads += TextLength(opt);
  285. m = MeasureText3D(GetFontDefault(), opt, 8.0f, 1.0f, 0.0f);
  286. pos.x = -m.x/2.0f;
  287. DrawText3D(GetFontDefault(), opt, pos, 8.0f, 1.0f, 0.0f, false, RED);
  288. pos.z += 0.5f + m.z;
  289. opt = (char *)TextFormat("< TBOX: %3s >", SHOW_TEXT_BOUNDRY? "ON" : "OFF");
  290. quads += TextLength(opt);
  291. m = MeasureText3D(GetFontDefault(), opt, 8.0f, 1.0f, 0.0f);
  292. pos.x = -m.x/2.0f;
  293. DrawText3D(GetFontDefault(), opt, pos, 8.0f, 1.0f, 0.0f, false, RED);
  294. pos.z += 0.5f + m.z;
  295. opt = (char *)TextFormat("< LAYER DISTANCE: %.3f >", layerDistance);
  296. quads += TextLength(opt);
  297. m = MeasureText3D(GetFontDefault(), opt, 8.0f, 1.0f, 0.0f);
  298. pos.x = -m.x/2.0f;
  299. DrawText3D(GetFontDefault(), opt, pos, 8.0f, 1.0f, 0.0f, false, DARKPURPLE);
  300. rlPopMatrix();
  301. //-------------------------------------------------------------------------
  302. // Draw 3D info text (use default font)
  303. //-------------------------------------------------------------------------
  304. opt = "All the text displayed here is in 3D";
  305. quads += 36;
  306. m = MeasureText3D(GetFontDefault(), opt, 10.0f, 0.5f, 0.0f);
  307. pos = (Vector3){-m.x/2.0f, 0.01f, 2.0f};
  308. DrawText3D(GetFontDefault(), opt, pos, 10.0f, 0.5f, 0.0f, false, DARKBLUE);
  309. pos.z += 1.5f + m.z;
  310. opt = "press [Left]/[Right] to change the font size";
  311. quads += 44;
  312. m = MeasureText3D(GetFontDefault(), opt, 6.0f, 0.5f, 0.0f);
  313. pos.x = -m.x/2.0f;
  314. DrawText3D(GetFontDefault(), opt, pos, 6.0f, 0.5f, 0.0f, false, DARKBLUE);
  315. pos.z += 0.5f + m.z;
  316. opt = "press [Up]/[Down] to change the font spacing";
  317. quads += 44;
  318. m = MeasureText3D(GetFontDefault(), opt, 6.0f, 0.5f, 0.0f);
  319. pos.x = -m.x/2.0f;
  320. DrawText3D(GetFontDefault(), opt, pos, 6.0f, 0.5f, 0.0f, false, DARKBLUE);
  321. pos.z += 0.5f + m.z;
  322. opt = "press [PgUp]/[PgDown] to change the line spacing";
  323. quads += 48;
  324. m = MeasureText3D(GetFontDefault(), opt, 6.0f, 0.5f, 0.0f);
  325. pos.x = -m.x/2.0f;
  326. DrawText3D(GetFontDefault(), opt, pos, 6.0f, 0.5f, 0.0f, false, DARKBLUE);
  327. pos.z += 0.5f + m.z;
  328. opt = "press [F1] to toggle the letter boundry";
  329. quads += 39;
  330. m = MeasureText3D(GetFontDefault(), opt, 6.0f, 0.5f, 0.0f);
  331. pos.x = -m.x/2.0f;
  332. DrawText3D(GetFontDefault(), opt, pos, 6.0f, 0.5f, 0.0f, false, DARKBLUE);
  333. pos.z += 0.5f + m.z;
  334. opt = "press [F2] to toggle the text boundry";
  335. quads += 37;
  336. m = MeasureText3D(GetFontDefault(), opt, 6.0f, 0.5f, 0.0f);
  337. pos.x = -m.x/2.0f;
  338. DrawText3D(GetFontDefault(), opt, pos, 6.0f, 0.5f, 0.0f, false, DARKBLUE);
  339. //-------------------------------------------------------------------------
  340. SHOW_LETTER_BOUNDRY = slb;
  341. EndShaderMode();
  342. EndMode3D();
  343. // Draw 2D info text & stats
  344. //-------------------------------------------------------------------------
  345. DrawText("Drag & drop a font file to change the font!\nType something, see what happens!\n\n"
  346. "Press [F3] to toggle the camera", 10, 35, 10, BLACK);
  347. quads += TextLength(text)*2*layers;
  348. char *tmp = (char *)TextFormat("%2i layer(s) | %s camera | %4i quads (%4i verts)", layers, spin? "ORBITAL" : "FREE", quads, quads*4);
  349. int width = MeasureText(tmp, 10);
  350. DrawText(tmp, screenWidth - 20 - width, 10, 10, DARKGREEN);
  351. tmp = "[Home]/[End] to add/remove 3D text layers";
  352. width = MeasureText(tmp, 10);
  353. DrawText(tmp, screenWidth - 20 - width, 25, 10, DARKGRAY);
  354. tmp = "[Insert]/[Delete] to increase/decrease distance between layers";
  355. width = MeasureText(tmp, 10);
  356. DrawText(tmp, screenWidth - 20 - width, 40, 10, DARKGRAY);
  357. tmp = "click the [CUBE] for a random color";
  358. width = MeasureText(tmp, 10);
  359. DrawText(tmp, screenWidth - 20 - width, 55, 10, DARKGRAY);
  360. tmp = "[Tab] to toggle multicolor mode";
  361. width = MeasureText(tmp, 10);
  362. DrawText(tmp, screenWidth - 20 - width, 70, 10, DARKGRAY);
  363. //-------------------------------------------------------------------------
  364. DrawFPS(10, 10);
  365. EndDrawing();
  366. //----------------------------------------------------------------------------------
  367. }
  368. // De-Initialization
  369. //--------------------------------------------------------------------------------------
  370. UnloadFont(font);
  371. CloseWindow(); // Close window and OpenGL context
  372. //--------------------------------------------------------------------------------------
  373. return 0;
  374. }
  375. //--------------------------------------------------------------------------------------
  376. // Module Functions Definitions
  377. //--------------------------------------------------------------------------------------
  378. // Draw codepoint at specified position in 3D space
  379. void DrawTextCodepoint3D(Font font, int codepoint, Vector3 position, float fontSize, bool backface, Color tint)
  380. {
  381. // Character index position in sprite font
  382. // NOTE: In case a codepoint is not available in the font, index returned points to '?'
  383. int index = GetGlyphIndex(font, codepoint);
  384. float scale = fontSize/(float)font.baseSize;
  385. // Character destination rectangle on screen
  386. // NOTE: We consider charsPadding on drawing
  387. position.x += (float)(font.glyphs[index].offsetX - font.glyphPadding)/(float)font.baseSize*scale;
  388. position.z += (float)(font.glyphs[index].offsetY - font.glyphPadding)/(float)font.baseSize*scale;
  389. // Character source rectangle from font texture atlas
  390. // NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects
  391. Rectangle srcRec = { font.recs[index].x - (float)font.glyphPadding, font.recs[index].y - (float)font.glyphPadding,
  392. font.recs[index].width + 2.0f*font.glyphPadding, font.recs[index].height + 2.0f*font.glyphPadding };
  393. float width = (float)(font.recs[index].width + 2.0f*font.glyphPadding)/(float)font.baseSize*scale;
  394. float height = (float)(font.recs[index].height + 2.0f*font.glyphPadding)/(float)font.baseSize*scale;
  395. if (font.texture.id > 0)
  396. {
  397. const float x = 0.0f;
  398. const float y = 0.0f;
  399. const float z = 0.0f;
  400. // normalized texture coordinates of the glyph inside the font texture (0.0f -> 1.0f)
  401. const float tx = srcRec.x/font.texture.width;
  402. const float ty = srcRec.y/font.texture.height;
  403. const float tw = (srcRec.x+srcRec.width)/font.texture.width;
  404. const float th = (srcRec.y+srcRec.height)/font.texture.height;
  405. if (SHOW_LETTER_BOUNDRY) DrawCubeWiresV((Vector3){ position.x + width/2, position.y, position.z + height/2}, (Vector3){ width, LETTER_BOUNDRY_SIZE, height }, LETTER_BOUNDRY_COLOR);
  406. rlCheckRenderBatchLimit(4 + 4*backface);
  407. rlSetTexture(font.texture.id);
  408. rlPushMatrix();
  409. rlTranslatef(position.x, position.y, position.z);
  410. rlBegin(RL_QUADS);
  411. rlColor4ub(tint.r, tint.g, tint.b, tint.a);
  412. // Front Face
  413. rlNormal3f(0.0f, 1.0f, 0.0f); // Normal Pointing Up
  414. rlTexCoord2f(tx, ty); rlVertex3f(x, y, z); // Top Left Of The Texture and Quad
  415. rlTexCoord2f(tx, th); rlVertex3f(x, y, z + height); // Bottom Left Of The Texture and Quad
  416. rlTexCoord2f(tw, th); rlVertex3f(x + width, y, z + height); // Bottom Right Of The Texture and Quad
  417. rlTexCoord2f(tw, ty); rlVertex3f(x + width, y, z); // Top Right Of The Texture and Quad
  418. if (backface)
  419. {
  420. // Back Face
  421. rlNormal3f(0.0f, -1.0f, 0.0f); // Normal Pointing Down
  422. rlTexCoord2f(tx, ty); rlVertex3f(x, y, z); // Top Right Of The Texture and Quad
  423. rlTexCoord2f(tw, ty); rlVertex3f(x + width, y, z); // Top Left Of The Texture and Quad
  424. rlTexCoord2f(tw, th); rlVertex3f(x + width, y, z + height); // Bottom Left Of The Texture and Quad
  425. rlTexCoord2f(tx, th); rlVertex3f(x, y, z + height); // Bottom Right Of The Texture and Quad
  426. }
  427. rlEnd();
  428. rlPopMatrix();
  429. rlSetTexture(0);
  430. }
  431. }
  432. void DrawText3D(Font font, const char *text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint)
  433. {
  434. int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop
  435. float textOffsetY = 0.0f; // Offset between lines (on line break '\n')
  436. float textOffsetX = 0.0f; // Offset X to next character to draw
  437. float scale = fontSize/(float)font.baseSize;
  438. for (int i = 0; i < length;)
  439. {
  440. // Get next codepoint from byte string and glyph index in font
  441. int codepointByteCount = 0;
  442. int codepoint = GetCodepoint(&text[i], &codepointByteCount);
  443. int index = GetGlyphIndex(font, codepoint);
  444. // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
  445. // but we need to draw all of the bad bytes using the '?' symbol moving one byte
  446. if (codepoint == 0x3f) codepointByteCount = 1;
  447. if (codepoint == '\n')
  448. {
  449. // NOTE: Fixed line spacing of 1.5 line-height
  450. // TODO: Support custom line spacing defined by user
  451. textOffsetY += scale + lineSpacing/(float)font.baseSize*scale;
  452. textOffsetX = 0.0f;
  453. }
  454. else
  455. {
  456. if ((codepoint != ' ') && (codepoint != '\t'))
  457. {
  458. DrawTextCodepoint3D(font, codepoint, (Vector3){ position.x + textOffsetX, position.y, position.z + textOffsetY }, fontSize, backface, tint);
  459. }
  460. if (font.glyphs[index].advanceX == 0) textOffsetX += (float)(font.recs[index].width + fontSpacing)/(float)font.baseSize*scale;
  461. else textOffsetX += (float)(font.glyphs[index].advanceX + fontSpacing)/(float)font.baseSize*scale;
  462. }
  463. i += codepointByteCount; // Move text bytes counter to next codepoint
  464. }
  465. }
  466. Vector3 MeasureText3D(Font font, const char* text, float fontSize, float fontSpacing, float lineSpacing)
  467. {
  468. int len = TextLength(text);
  469. int tempLen = 0; // Used to count longer text line num chars
  470. int lenCounter = 0;
  471. float tempTextWidth = 0.0f; // Used to count longer text line width
  472. float scale = fontSize/(float)font.baseSize;
  473. float textHeight = scale;
  474. float textWidth = 0.0f;
  475. int letter = 0; // Current character
  476. int index = 0; // Index position in sprite font
  477. for (int i = 0; i < len; i++)
  478. {
  479. lenCounter++;
  480. int next = 0;
  481. letter = GetCodepoint(&text[i], &next);
  482. index = GetGlyphIndex(font, letter);
  483. // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
  484. // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1
  485. if (letter == 0x3f) next = 1;
  486. i += next - 1;
  487. if (letter != '\n')
  488. {
  489. if (font.glyphs[index].advanceX != 0) textWidth += (font.glyphs[index].advanceX+fontSpacing)/(float)font.baseSize*scale;
  490. else textWidth += (font.recs[index].width + font.glyphs[index].offsetX)/(float)font.baseSize*scale;
  491. }
  492. else
  493. {
  494. if (tempTextWidth < textWidth) tempTextWidth = textWidth;
  495. lenCounter = 0;
  496. textWidth = 0.0f;
  497. textHeight += scale + lineSpacing/(float)font.baseSize*scale;
  498. }
  499. if (tempLen < lenCounter) tempLen = lenCounter;
  500. }
  501. if (tempTextWidth < textWidth) tempTextWidth = textWidth;
  502. Vector3 vec = { 0 };
  503. vec.x = tempTextWidth + (float)((tempLen - 1)*fontSpacing/(float)font.baseSize*scale); // Adds chars spacing to measure
  504. vec.y = 0.25f;
  505. vec.z = textHeight;
  506. return vec;
  507. }
  508. void DrawTextWave3D(Font font, const char *text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, WaveTextConfig* config, float time, Color tint)
  509. {
  510. int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop
  511. float textOffsetY = 0.0f; // Offset between lines (on line break '\n')
  512. float textOffsetX = 0.0f; // Offset X to next character to draw
  513. float scale = fontSize/(float)font.baseSize;
  514. bool wave = false;
  515. for (int i = 0, k = 0; i < length; ++k)
  516. {
  517. // Get next codepoint from byte string and glyph index in font
  518. int codepointByteCount = 0;
  519. int codepoint = GetCodepoint(&text[i], &codepointByteCount);
  520. int index = GetGlyphIndex(font, codepoint);
  521. // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
  522. // but we need to draw all of the bad bytes using the '?' symbol moving one byte
  523. if (codepoint == 0x3f) codepointByteCount = 1;
  524. if (codepoint == '\n')
  525. {
  526. // NOTE: Fixed line spacing of 1.5 line-height
  527. // TODO: Support custom line spacing defined by user
  528. textOffsetY += scale + lineSpacing/(float)font.baseSize*scale;
  529. textOffsetX = 0.0f;
  530. k = 0;
  531. }
  532. else if (codepoint == '~')
  533. {
  534. if (GetCodepoint(&text[i+1], &codepointByteCount) == '~')
  535. {
  536. codepointByteCount += 1;
  537. wave = !wave;
  538. }
  539. }
  540. else
  541. {
  542. if ((codepoint != ' ') && (codepoint != '\t'))
  543. {
  544. Vector3 pos = position;
  545. if (wave) // Apply the wave effect
  546. {
  547. pos.x += sinf(time*config->waveSpeed.x-k*config->waveOffset.x)*config->waveRange.x;
  548. pos.y += sinf(time*config->waveSpeed.y-k*config->waveOffset.y)*config->waveRange.y;
  549. pos.z += sinf(time*config->waveSpeed.z-k*config->waveOffset.z)*config->waveRange.z;
  550. }
  551. DrawTextCodepoint3D(font, codepoint, (Vector3){ pos.x + textOffsetX, pos.y, pos.z + textOffsetY }, fontSize, backface, tint);
  552. }
  553. if (font.glyphs[index].advanceX == 0) textOffsetX += (float)(font.recs[index].width + fontSpacing)/(float)font.baseSize*scale;
  554. else textOffsetX += (float)(font.glyphs[index].advanceX + fontSpacing)/(float)font.baseSize*scale;
  555. }
  556. i += codepointByteCount; // Move text bytes counter to next codepoint
  557. }
  558. }
  559. Vector3 MeasureTextWave3D(Font font, const char* text, float fontSize, float fontSpacing, float lineSpacing)
  560. {
  561. int len = TextLength(text);
  562. int tempLen = 0; // Used to count longer text line num chars
  563. int lenCounter = 0;
  564. float tempTextWidth = 0.0f; // Used to count longer text line width
  565. float scale = fontSize/(float)font.baseSize;
  566. float textHeight = scale;
  567. float textWidth = 0.0f;
  568. int letter = 0; // Current character
  569. int index = 0; // Index position in sprite font
  570. for (int i = 0; i < len; i++)
  571. {
  572. lenCounter++;
  573. int next = 0;
  574. letter = GetCodepoint(&text[i], &next);
  575. index = GetGlyphIndex(font, letter);
  576. // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
  577. // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1
  578. if (letter == 0x3f) next = 1;
  579. i += next - 1;
  580. if (letter != '\n')
  581. {
  582. if (letter == '~' && GetCodepoint(&text[i+1], &next) == '~')
  583. {
  584. i++;
  585. }
  586. else
  587. {
  588. if (font.glyphs[index].advanceX != 0) textWidth += (font.glyphs[index].advanceX+fontSpacing)/(float)font.baseSize*scale;
  589. else textWidth += (font.recs[index].width + font.glyphs[index].offsetX)/(float)font.baseSize*scale;
  590. }
  591. }
  592. else
  593. {
  594. if (tempTextWidth < textWidth) tempTextWidth = textWidth;
  595. lenCounter = 0;
  596. textWidth = 0.0f;
  597. textHeight += scale + lineSpacing/(float)font.baseSize*scale;
  598. }
  599. if (tempLen < lenCounter) tempLen = lenCounter;
  600. }
  601. if (tempTextWidth < textWidth) tempTextWidth = textWidth;
  602. Vector3 vec = { 0 };
  603. vec.x = tempTextWidth + (float)((tempLen - 1)*fontSpacing/(float)font.baseSize*scale); // Adds chars spacing to measure
  604. vec.y = 0.25f;
  605. vec.z = textHeight;
  606. return vec;
  607. }
  608. Color GenerateRandomColor(float s, float v)
  609. {
  610. const float Phi = 0.618033988749895f; // Golden ratio conjugate
  611. float h = GetRandomValue(0, 360);
  612. h = fmodf((h + h*Phi), 360.0f);
  613. return ColorFromHSV(h, s, v);
  614. }