From 907b604fc9c25c0122cf1a65534d72bac0c0df1d Mon Sep 17 00:00:00 2001
From: Dominic Masters <dominic@domsplace.com>
Date: Fri, 1 Dec 2023 00:44:35 -0600
Subject: [PATCH] Added truetype, with block support.

---
 src/dawn/display/CMakeLists.txt              |   2 +-
 src/dawn/display/font/CMakeLists.txt         |   9 ++
 src/dawn/display/font/TrueTypeCharacter.hpp  |  15 +++
 src/dawn/display/font/TrueTypeTexture.cpp    | 135 +++++++++++++++++++
 src/dawn/display/font/TrueTypeTexture.hpp    |  46 +++++++
 src/dawn/display/mesh/CMakeLists.txt         |   1 +
 src/dawn/display/mesh/QuadMesh.cpp           |  39 ++++++
 src/dawn/display/mesh/QuadMesh.hpp           |  32 +++++
 src/dawn/save/SaveManager.cpp                |   3 +-
 src/dawnhelloworld/scene/HelloWorldScene.cpp |  42 +++++-
 10 files changed, 318 insertions(+), 6 deletions(-)
 create mode 100644 src/dawn/display/font/CMakeLists.txt
 create mode 100644 src/dawn/display/font/TrueTypeCharacter.hpp
 create mode 100644 src/dawn/display/font/TrueTypeTexture.cpp
 create mode 100644 src/dawn/display/font/TrueTypeTexture.hpp
 create mode 100644 src/dawn/display/mesh/QuadMesh.cpp
 create mode 100644 src/dawn/display/mesh/QuadMesh.hpp

diff --git a/src/dawn/display/CMakeLists.txt b/src/dawn/display/CMakeLists.txt
index c1475a70..c33affdc 100644
--- a/src/dawn/display/CMakeLists.txt
+++ b/src/dawn/display/CMakeLists.txt
@@ -12,6 +12,6 @@ target_sources(${DAWN_TARGET_NAME}
 )
 
 # Subdirs
-# add_subdirectory(font)
+add_subdirectory(font)
 add_subdirectory(mesh)
 add_subdirectory(shader)
\ No newline at end of file
diff --git a/src/dawn/display/font/CMakeLists.txt b/src/dawn/display/font/CMakeLists.txt
new file mode 100644
index 00000000..42404b54
--- /dev/null
+++ b/src/dawn/display/font/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright (c) 2023 Dominic Masters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+target_sources(${DAWN_TARGET_NAME}
+  PRIVATE
+    TrueTypeTexture.cpp
+)
\ No newline at end of file
diff --git a/src/dawn/display/font/TrueTypeCharacter.hpp b/src/dawn/display/font/TrueTypeCharacter.hpp
new file mode 100644
index 00000000..c1d8ca86
--- /dev/null
+++ b/src/dawn/display/font/TrueTypeCharacter.hpp
@@ -0,0 +1,15 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "dawnlibs.hpp"
+
+namespace Dawn {
+  struct TrueTypeCharacter {
+    glm::vec2 advance;
+    glm::vec2 size;
+    glm::vec4 quad;
+  };
+}
\ No newline at end of file
diff --git a/src/dawn/display/font/TrueTypeTexture.cpp b/src/dawn/display/font/TrueTypeTexture.cpp
new file mode 100644
index 00000000..7e059a08
--- /dev/null
+++ b/src/dawn/display/font/TrueTypeTexture.cpp
@@ -0,0 +1,135 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "TrueTypeTexture.hpp"
+#include "assert/assert.hpp"
+#include "util/Math.hpp"
+
+using namespace Dawn;
+
+TrueTypeTexture::TrueTypeTexture(
+  FT_Face face,
+  uint32_t fontSize,
+  uint8_t style
+)  :
+  face(face),
+  fontSize(fontSize),
+  style(style)
+{
+  assertTrue(fontSize < 256, "Font size cannot be greater than 256");
+
+  texture = std::make_shared<Texture>();
+
+  // Set freetype font size prior to baking.
+  if(FT_Set_Pixel_Sizes(face, 0, fontSize)) {
+    assertUnreachable("Failed to set font size");
+  }
+  
+  // Set the texture size
+  texture->setSize(
+    4096,
+    4096,
+    TextureFormat::R,
+    TextureDataFormat::UNSIGNED_BYTE
+  );
+
+  // Texture buffer
+  uint8_t *buffer = new uint8_t[texture->getWidth() * texture->getHeight()];
+  // Fill with zeros
+  std::memset(buffer, 0, texture->getWidth() * texture->getHeight());
+
+  size_t offset = 0;
+  struct TrueTypeCharacter info;
+  int32_t textureX = 0, textureY = 0;
+  int32_t rowHeight = 0;
+
+  // Character sets
+  std::vector<wchar_t> characterBlocks;
+  // Latin
+  for(wchar_t c = 0x0020; c < 0x007F; c++) characterBlocks.push_back(c);
+  // Latin-1 Supplement
+  for(wchar_t c = 0x00A0; c < 0x00FF; c++) characterBlocks.push_back(c);
+  // Latin Extended-A
+  for(wchar_t c = 0x0100; c < 0x017F; c++) characterBlocks.push_back(c);
+  // Latin Extended-B
+  for(wchar_t c = 0x0180; c < 0x024F; c++) characterBlocks.push_back(c);
+  // Hiragana
+  for(wchar_t c = 0x3040; c < 0x309F; c++) characterBlocks.push_back(c);
+  // Katakana
+  for(wchar_t c = 0x30A0; c < 0x30FF; c++) characterBlocks.push_back(c);
+
+  // For each character in the character set
+  for(wchar_t c : characterBlocks) {
+    // Load the character
+    if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
+      assertUnreachable("Failed to load character (1)");
+    }
+
+    // Store the character information
+    info.advance.x = (float_t)(face->glyph->advance.x >> 6);
+    info.advance.y = (float_t)(face->glyph->advance.y >> 6);
+    info.size = glm::vec2(face->glyph->bitmap.width, face->glyph->bitmap.rows);
+
+    // Determine the texture position
+    if(textureX + face->glyph->bitmap.width >= texture->getWidth()) {
+      textureX = 0;
+      textureY += rowHeight + 1;// Tiny gap between rows
+      rowHeight = face->glyph->bitmap.rows;
+    } else {
+      rowHeight = Math::max<int32_t>(rowHeight, face->glyph->bitmap.rows);
+    }
+
+    // Set the quad positions
+    info.quad = glm::vec4(
+      textureX,
+      textureY,
+      textureX + face->glyph->bitmap.width,
+      textureY + face->glyph->bitmap.rows
+    ) / glm::vec4(
+      texture->getWidth(),
+      texture->getHeight(),
+      texture->getWidth(),
+      texture->getHeight()
+    );
+
+    // Store the cached character data.
+    this->characterData[c] = info;
+
+    // Determine pixel offset.
+    offset = textureX + (textureY * texture->getWidth());
+    assertTrue(
+      offset + (face->glyph->bitmap.rows * texture->getWidth()) <=
+      texture->getWidth() * texture->getHeight(),
+      "Font texture buffer overflow will occur."
+    );
+
+    // Buffer pixels, we have to do this one row at a time due to the 
+    // differences in width between the glyph and the texture.
+    const size_t countPerRow = face->glyph->bitmap.width;
+    int32_t i = 0;
+    while(i != face->glyph->bitmap.rows) {
+      std::memcpy(
+        buffer + offset + (i * texture->getWidth()),
+        face->glyph->bitmap.buffer + (i * countPerRow),
+        countPerRow
+      );
+      i++;
+    }
+
+    // Increment textureX
+    textureX += face->glyph->bitmap.width + 1;// I add a tiny gap between chars
+  }
+
+  this->texture->buffer(buffer);
+  delete[] buffer;
+}
+
+struct TrueTypeCharacter TrueTypeTexture::getCharacterData(wchar_t c) {
+  return this->characterData[c];
+}
+
+TrueTypeTexture::~TrueTypeTexture() {
+  FT_Done_Face(this->face);
+}
\ No newline at end of file
diff --git a/src/dawn/display/font/TrueTypeTexture.hpp b/src/dawn/display/font/TrueTypeTexture.hpp
new file mode 100644
index 00000000..a5feb92a
--- /dev/null
+++ b/src/dawn/display/font/TrueTypeTexture.hpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "display/Texture.hpp"
+#include "TrueTypeCharacter.hpp"
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+namespace Dawn {
+  class TrueTypeTexture final {
+    public:
+      FT_Face face;
+      std::shared_ptr<Texture> texture;
+      uint32_t fontSize;
+      uint8_t style;
+      std::unordered_map<wchar_t, struct TrueTypeCharacter> characterData;
+
+      /**
+       * Construct a new New True Type Face Texture object
+       * 
+       * @param face The freetype face object.
+       * @param style Style that this font has, used for locking.
+       */
+      TrueTypeTexture(
+        FT_Face face,
+        uint32_t fontSize,
+        uint8_t style
+      );
+
+      /**
+       * Returns the character data for the given character.
+       * 
+       * @param c Character to get data for.
+       * @return The Character data for the given character.
+       */
+      struct TrueTypeCharacter getCharacterData(wchar_t c);
+
+      /**
+       * Destroys this true type face texture.
+       */
+      ~TrueTypeTexture();
+  };
+}
\ No newline at end of file
diff --git a/src/dawn/display/mesh/CMakeLists.txt b/src/dawn/display/mesh/CMakeLists.txt
index 6937e8e9..741a6ec2 100644
--- a/src/dawn/display/mesh/CMakeLists.txt
+++ b/src/dawn/display/mesh/CMakeLists.txt
@@ -7,4 +7,5 @@
 target_sources(${DAWN_TARGET_NAME}
   PRIVATE
     CubeMesh.cpp
+    QuadMesh.cpp
 )
\ No newline at end of file
diff --git a/src/dawn/display/mesh/QuadMesh.cpp b/src/dawn/display/mesh/QuadMesh.cpp
new file mode 100644
index 00000000..8fb6e0d4
--- /dev/null
+++ b/src/dawn/display/mesh/QuadMesh.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "QuadMesh.hpp"
+
+using namespace Dawn;
+
+void QuadMesh::buffer(
+  const std::shared_ptr<Mesh> mesh,
+  const glm::vec4 positions,
+  const glm::vec4 coordinates,
+  const int32_t verticeStart,
+  const int32_t indiceStart
+) {
+  glm::vec3 vertices[QUAD_VERTICE_COUNT] = {
+    glm::vec3(positions.x, positions.y, 0),
+    glm::vec3(positions.z, positions.y, 0),
+    glm::vec3(positions.x, positions.w, 0),
+    glm::vec3(positions.z, positions.w, 0)
+  };
+
+  glm::vec2 coords[QUAD_VERTICE_COUNT] = {
+    glm::vec2(coordinates.x, coordinates.y),
+    glm::vec2(coordinates.z, coordinates.y),
+    glm::vec2(coordinates.x, coordinates.w),
+    glm::vec2(coordinates.z, coordinates.w)
+  };
+
+  int32_t indices[QUAD_INDICE_COUNT] = {
+    verticeStart, verticeStart + 1, verticeStart + 3,
+    verticeStart, verticeStart + 2, verticeStart + 3
+  };
+
+  mesh->bufferPositions(verticeStart, vertices, QUAD_VERTICE_COUNT);
+  mesh->bufferCoordinates(verticeStart, coords, QUAD_VERTICE_COUNT);
+  mesh->bufferIndices(indiceStart, indices, QUAD_INDICE_COUNT);
+}
\ No newline at end of file
diff --git a/src/dawn/display/mesh/QuadMesh.hpp b/src/dawn/display/mesh/QuadMesh.hpp
new file mode 100644
index 00000000..bbe15ecb
--- /dev/null
+++ b/src/dawn/display/mesh/QuadMesh.hpp
@@ -0,0 +1,32 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "display/mesh/Mesh.hpp"
+
+#define QUAD_VERTICE_COUNT 4
+#define QUAD_INDICE_COUNT 6
+
+namespace Dawn {
+  class QuadMesh {
+    public:
+      /**
+       * Buffers quad mesh vertices and indices into the given mesh.
+       * 
+       * @param mesh The mesh to buffer into.
+       * @param positions The positions of the vertices.
+       * @param coordinates The coordinates of the vertices.
+       * @param verticeStart The starting index of the vertices.
+       * @param indiceStart The starting index of the indices.
+       */
+      static void buffer(
+        const std::shared_ptr<Mesh> mesh,
+        const glm::vec4 positions,
+        const glm::vec4 coordinates,
+        const int32_t verticeStart,
+        const int32_t indiceStart
+      );
+  };
+}
\ No newline at end of file
diff --git a/src/dawn/save/SaveManager.cpp b/src/dawn/save/SaveManager.cpp
index d9c407c7..8e1fb021 100644
--- a/src/dawn/save/SaveManager.cpp
+++ b/src/dawn/save/SaveManager.cpp
@@ -11,5 +11,4 @@ using namespace Dawn;
 void SaveManager::init(std::shared_ptr<Game> game) {
   assertNotNull(game, "Game instance cannot be null!");
   this->game = game;
-}
-
+}
\ No newline at end of file
diff --git a/src/dawnhelloworld/scene/HelloWorldScene.cpp b/src/dawnhelloworld/scene/HelloWorldScene.cpp
index 601488a1..a4b6aa7b 100644
--- a/src/dawnhelloworld/scene/HelloWorldScene.cpp
+++ b/src/dawnhelloworld/scene/HelloWorldScene.cpp
@@ -6,15 +6,51 @@
 #include "scene/SceneList.hpp"
 #include "component/display/Camera.hpp"
 #include "prefab/SimpleSpinningCube.hpp"
+#include "display/font/TrueTypeTexture.hpp"
+#include "component/display/material/SimpleTexturedMaterial.hpp"
+#include "display/mesh/QuadMesh.hpp"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
 
 using namespace Dawn;
 
+FT_Library fontLibrary;
+FT_Face face;
+std::shared_ptr<TrueTypeTexture> texture;
+
 void Dawn::helloWorldScene(Scene &s) {
-  std::cout << "Hello World Scene" << std::endl;
+  int32_t ret = FT_Init_FreeType(&fontLibrary);
+  assertTrue(ret == 0, "Failed to initialize FreeType library");
+  ret = FT_New_Face(fontLibrary,
+    // "/usr/share/fonts/TTF/arial.ttf",
+    "/home/yourwishes/Downloads/Noto_Sans_JP/static/NotoSansJP-Regular.ttf",
+    0,
+    &face
+  );
+  assertTrue(ret == 0, "Failed to load font face");
+  texture = std::make_shared<TrueTypeTexture>(face, 128, 0);
+
+  auto test = texture->getCharacterData(L'あ');
 
   auto cameraItem = s.createSceneItem();
   auto camera = cameraItem->addComponent<Camera>();
-  cameraItem->lookAt({ 5, 5, 5 }, { 0, 0, 0 }, { 0, 1, 0 });
+  // cameraItem->lookAt({ 5, 5, 5 }, { 0, 0, 0 }, { 0, 1, 0 });
+  cameraItem->lookAt({ 0, 0, 3 }, { 0, 0, 0 }, { 0, 1, 0 });
 
-  auto cubeItem = createSimpleSpinningCube(s);
+  auto quad = s.createSceneItem();
+  auto quadMesh = std::make_shared<Mesh>();
+  quadMesh->createBuffers(QUAD_VERTICE_COUNT, QUAD_INDICE_COUNT);
+  QuadMesh::buffer(
+    quadMesh,
+    glm::vec4(0, 0, test.size.x, test.size.y) / 128.0f,
+    test.quad,
+    0,
+    0
+  );
+  auto quadRenderer = quad->addComponent<MeshRenderer>();
+  quadRenderer->mesh = quadMesh;
+
+  auto quadMaterial = quad->addComponent<SimpleTexturedMaterial>();
+  quadMaterial->setTexture(texture->texture);
 }
\ No newline at end of file