Skip to content

Instantly share code, notes, and snippets.

@nothke
Last active September 19, 2021 00:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nothke/9cd2baaa48387eefec8a13f45d48980f to your computer and use it in GitHub Desktop.
Save nothke/9cd2baaa48387eefec8a13f45d48980f to your computer and use it in GitHub Desktop.
Font experiment, loading from MBFont
#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