Last active
September 19, 2021 00:07
-
-
Save nothke/9cd2baaa48387eefec8a13f45d48980f to your computer and use it in GitHub Desktop.
Font experiment, loading from MBFont
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "pch.h" | |
#include "noclip_controller.h" | |
struct FChar | |
{ | |
char id; | |
int x, y; | |
int width, height; | |
int xoffset, yoffset; | |
int xadvance; | |
}; | |
class Font | |
{ | |
bool inited{ false }; | |
public: | |
std::unordered_map<char, FChar> chars; | |
int scaleW{ 256 }; | |
int scaleH{ 256 }; | |
int base{ 0 }; | |
int lineHeight{ 0 }; | |
private: | |
inline int GetNum(const std::string& word, const std::string& prefix) | |
{ | |
int i = atoi(word.substr(prefix.size()).c_str()); | |
//spdlog::info("{} {}", prefix, i); | |
return i; | |
} | |
public: | |
bool IsValid() { return inited; } | |
void BuildFromBMFontFile(const std::string& path) | |
{ | |
namespace fs = std::filesystem; | |
if (fs::exists(path)) | |
{ | |
std::ifstream stream(path); | |
if (stream.is_open()) | |
{ | |
// LINES | |
int l = 0; | |
for (std::string line; std::getline(stream, line); ) | |
{ | |
if (l == 1) // "commmon" line | |
{ | |
std::stringstream lineStream(line); | |
std::string word; | |
spdlog::info("{}", line); | |
while (std::getline(lineStream, word, ' ')) | |
{ | |
if (word._Starts_with("lineHeight=")) | |
lineHeight = GetNum(word, "lineHeight="); | |
else if (word._Starts_with("base=")) | |
base = GetNum(word, "base="); | |
else if (word._Starts_with("scaleW=")) | |
scaleW = GetNum(word, "scaleW="); | |
else if (word._Starts_with("scaleH=")) | |
scaleH = GetNum(word, "scaleH="); | |
} | |
} | |
else if (l == 3) // "chars" line | |
{ | |
size_t charCount = GetNum(line, "chars count="); | |
chars.reserve(charCount); | |
} | |
if (l >= 4) // "char" lines start on fifth line | |
{ | |
FChar fchar; | |
// WORDS | |
std::stringstream lineStream(line); | |
std::string word; | |
while (std::getline(lineStream, word, ' ')) | |
{ | |
if (word._Starts_with("id=")) | |
{ | |
int intChar = (GetNum(word, "id=")); | |
if (intChar <= MAXCHAR) | |
fchar.id = static_cast<char>(intChar); | |
else | |
{ | |
spdlog::warn("Attempting to load char {}, which is not an ASCII character", intChar); | |
continue; // continue if char is more than 127 | |
} | |
} | |
else if (word._Starts_with("x=")) | |
fchar.x = GetNum(word, "x="); | |
else if (word._Starts_with("y=")) | |
fchar.y = GetNum(word, "y="); | |
else if (word._Starts_with("width=")) | |
fchar.width = GetNum(word, "width="); | |
else if (word._Starts_with("height=")) | |
fchar.height = GetNum(word, "height="); | |
else if (word._Starts_with("xoffset=")) | |
fchar.xoffset = GetNum(word, "xoffset="); | |
else if (word._Starts_with("yoffset=")) | |
fchar.yoffset = GetNum(word, "yoffset="); | |
else if (word._Starts_with("xadvance=")) | |
fchar.xadvance = GetNum(word, "xadvance="); | |
} | |
chars.insert({ fchar.id, fchar }); | |
} | |
l++; | |
} | |
} | |
inited = true; | |
} | |
else | |
{ | |
spdlog::error("Font file {} doesn't exist", path); | |
} | |
} | |
}; | |
class TextMesh | |
{ | |
private: | |
struct CharLocation | |
{ | |
FChar* fchar; | |
int x, y, line; | |
}; | |
std::vector<CharLocation> charLocs; | |
public: | |
std::vector<Vertex> vertices; | |
std::vector<unsigned int> indices; | |
Font& font; | |
enum class HorizontalAlignment | |
{ | |
Left, | |
Center, | |
Right, | |
}; | |
enum class VerticalAlignment | |
{ | |
Top, | |
Middle, | |
Bottom | |
}; | |
HorizontalAlignment horizontalAlignment{ HorizontalAlignment::Left }; | |
VerticalAlignment verticalAlignment{ VerticalAlignment::Top }; | |
TextMesh(Font& font) | |
: font(font) {} | |
private: | |
void AddQuad(float x, float y, float width, float height, | |
float uvx, float uvy, float uvwidth, float uvheight) | |
{ | |
vertices.push_back( | |
Vertex{ | |
x, y + height, 0, | |
uvx, uvy + uvheight | |
}); | |
vertices.push_back( | |
Vertex{ | |
x + width, y + height, 0, | |
uvx + uvwidth, uvy + uvheight | |
}); | |
vertices.push_back( | |
Vertex{ | |
x, y, 0, | |
uvx, uvy | |
}); | |
vertices.push_back( | |
Vertex{ | |
x + width, y, 0, | |
uvx + uvwidth, uvy | |
}); | |
auto size = vertices.size(); | |
indices.push_back(size + 0); | |
indices.push_back(size + 2); | |
indices.push_back(size + 1); | |
indices.push_back(size + 1); | |
indices.push_back(size + 2); | |
indices.push_back(size + 3); | |
} | |
public: | |
void SetText(const std::string& text) | |
{ | |
charLocs.clear(); | |
vertices.clear(); | |
indices.clear(); | |
size_t size = text.size(); | |
charLocs.reserve(size); | |
vertices.reserve(size * 4); | |
vertices.reserve(size * 6); | |
const float scale = 9.0f / font.scaleH; | |
AddQuad(-1, -1, 1, 1, 0, 0, 0, 0); | |
//AddQuad(-1, -1, 1, 1, 0, 0, 0, 0); | |
std::vector<float> lineWidths; | |
int boundsHeight = 0; | |
int curLine = 0; | |
// First pass, find chars and calculate bounds | |
{ | |
int curx = 0; | |
int cury = 0; | |
for (size_t c = 0; c < size; c++) | |
{ | |
if (text[c] == '\n') | |
{ | |
cury += font.lineHeight; | |
curLine++; | |
lineWidths.push_back(static_cast<float>(curx)); | |
curx = 0; | |
continue; | |
} | |
FChar& fchar = font.chars[text[c]]; | |
charLocs.push_back(CharLocation{ &fchar, curx, cury, curLine }); | |
curx += fchar.xadvance; | |
} | |
lineWidths.push_back(static_cast<float>(curx)); | |
boundsHeight = cury + font.lineHeight; | |
} | |
spdlog::warn("Lines: {}", lineWidths.size()); | |
for (size_t i = 0; i < lineWidths.size(); i++) | |
{ | |
if (horizontalAlignment == HorizontalAlignment::Left) | |
lineWidths[i] = 0; | |
else if (horizontalAlignment == HorizontalAlignment::Center) | |
lineWidths[i] *= -0.5f; | |
// For right do nothing | |
} | |
float vertOff = 0; | |
if (verticalAlignment == VerticalAlignment::Middle) | |
vertOff = boundsHeight * 0.5f; | |
else if (verticalAlignment == VerticalAlignment::Bottom) | |
vertOff = static_cast<float>(boundsHeight); | |
// Second pass, place quads | |
for (auto& charLoc : charLocs) | |
{ | |
const auto& fchar = *charLoc.fchar; | |
const float lineOffset = lineWidths[charLoc.line]; | |
const float | |
w = fchar.width * scale, | |
h = fchar.height * scale, | |
x = (charLoc.x + lineOffset) * scale, | |
y = (-fchar.yoffset - fchar.height + font.base - charLoc.y + vertOff) * scale; | |
const float | |
uvx = static_cast<float>(fchar.x) / font.scaleW, | |
uvy = 1.0f - ((fchar.y + fchar.height) / static_cast<float>(font.scaleH)), | |
uvw = fchar.width / static_cast<float>(font.scaleW), | |
uvh = fchar.height / static_cast<float>(font.scaleH); | |
AddQuad(x, y, w, h, uvx, uvy, uvw, uvh); | |
} | |
} | |
}; | |
int main() | |
{ | |
using namespace Conversion; | |
Engine e; | |
e.StartWindow(); | |
e.app.SetIcon("res/inconsolata.png"); | |
e.InitAll(); | |
e.assets.LoadAll("res/"); | |
Font font; | |
font.BuildFromBMFontFile("res/inconsolata.fnt"); | |
Texture fontTexture = e.assets.GetTexture("inconsolata"); | |
TextMesh text = TextMesh(font); | |
text.horizontalAlignment = TextMesh::HorizontalAlignment::Center; | |
text.verticalAlignment = TextMesh::VerticalAlignment::Middle; | |
text.SetText("Oooh..\nIs that...\nAlignment!?\nCooool!"); | |
Mesh mesh = Mesh(text.vertices, text.indices, false); | |
spdlog::info("Vertices: {}", text.vertices.size()); | |
auto shader = e.assets.GetShader("unlit_texture"); | |
Material* mat = new Material("null", shader); | |
mat->AddTexture("_Texture", fontTexture); | |
e.CreateScene(32); | |
auto& textGO = e.scene->Create(mesh, *mat); | |
//textGO.transform.position = { 0, 0, 0 }; | |
//NoClipController noclip = NoClipController(e.camera, e); | |
//noclip.cameraSpeedInput = 10; | |
while (e.IsRunning()) | |
{ | |
Renderer::Clear(toFloatColor({ 212, 97, 196 })); | |
//noclip.DefaultUpdate(); | |
e.scene->Draw(); | |
e.Render(); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment