Working on dialog portraits
This commit is contained in:
parent
de2e18e38f
commit
eff7322140
@ -21,6 +21,28 @@
|
||||
"description": "A token from the Guard - sign of respect",
|
||||
"icon": "resources/fire2.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "npc_03_ghost",
|
||||
"name": "NPC Floating Ghost",
|
||||
"texturePath": "resources/w/ghost_skin001.png",
|
||||
"animationIdlePath": "resources/w/default_float001.txt",
|
||||
"animationWalkPath": "resources/w/default_float001.txt",
|
||||
"positionX": 0.0,
|
||||
"positionY": 0.0,
|
||||
"positionZ": -5.0,
|
||||
"walkSpeed": 1.5,
|
||||
"rotationSpeed": 8.0,
|
||||
"modelScale": 0.01,
|
||||
"modelCorrectionRotX": 0.0,
|
||||
"modelCorrectionRotY": 0.0,
|
||||
"modelCorrectionRotZ": 0.0,
|
||||
"gift": {
|
||||
"id": "ghost_essence",
|
||||
"name": "Ghost's Essence",
|
||||
"description": "A mysterious essence from the Ghost realm",
|
||||
"icon": "resources/fire2.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
resources/dialogue/portrait_frame.png
(Stored with Git LFS)
BIN
resources/dialogue/portrait_frame.png
(Stored with Git LFS)
Binary file not shown.
@ -9,15 +9,31 @@
|
||||
"type": "Line",
|
||||
"speaker": "Ghost",
|
||||
"portrait": "resources/ghost_avatar.png",
|
||||
"text": "You finally came here.",
|
||||
"text": "Наконец-то ты пришел.",
|
||||
"next": "line_2"
|
||||
},
|
||||
{
|
||||
"id": "line_2",
|
||||
"type": "Line",
|
||||
"speaker": "Hero",
|
||||
"portrait": "",
|
||||
"text": "I need answers.",
|
||||
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||
"text": "Ты сделан из дыма?",
|
||||
"next": "line_3"
|
||||
},
|
||||
{
|
||||
"id": "line_3",
|
||||
"type": "Line",
|
||||
"speaker": "Ghost",
|
||||
"portrait": "resources/ghost_avatar.png",
|
||||
"text": "Ты думаешь, это смешно?",
|
||||
"next": "line_4"
|
||||
},
|
||||
{
|
||||
"id": "line_4",
|
||||
"type": "Line",
|
||||
"speaker": "Hero",
|
||||
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||
"text": "Я думаю что ты пахнешь как выхлоп от Камаза.",
|
||||
"next": "end_1"
|
||||
},
|
||||
{
|
||||
|
||||
1
resources/symbols.txt
Normal file
1
resources/symbols.txt
Normal file
@ -0,0 +1 @@
|
||||
АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюяӨөҢңҮү
|
||||
BIN
resources/w/gg/gg2_s_podsvetkoy5.png
(Stored with Git LFS)
Normal file
BIN
resources/w/gg/gg2_s_podsvetkoy5.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -8,9 +8,27 @@
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
// Decode one UTF-8 codepoint from str at byte position i; advances i past the sequence.
|
||||
static uint32_t nextUtf8Codepoint(const std::string& str, size_t& i)
|
||||
{
|
||||
auto b = [&](size_t offset) { return static_cast<unsigned char>(str[i + offset]); };
|
||||
unsigned char c = b(0);
|
||||
uint32_t cp;
|
||||
size_t bytes;
|
||||
if (c < 0x80) { cp = c; bytes = 1; }
|
||||
else if (c < 0xE0) { cp = c & 0x1F; bytes = 2; }
|
||||
else if (c < 0xF0) { cp = c & 0x0F; bytes = 3; }
|
||||
else { cp = c & 0x07; bytes = 4; }
|
||||
for (size_t j = 1; j < bytes && i + j < str.size(); ++j)
|
||||
cp = (cp << 6) | (b(j) & 0x3F);
|
||||
i += bytes;
|
||||
return cp;
|
||||
}
|
||||
|
||||
TextRenderer::~TextRenderer()
|
||||
{
|
||||
glyphs.clear();
|
||||
@ -87,6 +105,8 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure we use the Unicode charmap so Cyrillic codepoints resolve correctly.
|
||||
FT_Select_Charmap(face, FT_ENCODING_UNICODE);
|
||||
FT_Set_Pixel_Sizes(face, 0, pixelSize);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
@ -101,7 +121,7 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
||||
unsigned int advance = 0;
|
||||
};
|
||||
|
||||
std::vector<std::pair<char, GlyphBitmap>> glyphList;
|
||||
std::vector<std::pair<uint32_t, GlyphBitmap>> glyphList;
|
||||
glyphList.reserve(128 - 32);
|
||||
|
||||
int maxGlyphHeight = 0;
|
||||
@ -114,7 +134,7 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
||||
gb.height = 0;
|
||||
gb.bearing = { 0.f, 0.f };
|
||||
gb.advance = 0;
|
||||
glyphList.emplace_back((char)c, std::move(gb));
|
||||
glyphList.emplace_back((uint32_t)c, std::move(gb));
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -130,7 +150,51 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
||||
maxGlyphHeight = max(maxGlyphHeight, gb.height);
|
||||
}
|
||||
|
||||
glyphList.emplace_back((char)c, std::move(gb));
|
||||
glyphList.emplace_back((uint32_t)c, std::move(gb));
|
||||
}
|
||||
|
||||
// Load extra codepoints from resources/symbols.txt (UTF-8 encoded).
|
||||
{
|
||||
std::vector<char> symbolsRaw;
|
||||
try {
|
||||
symbolsRaw = !zipfilename.empty()
|
||||
? readFileFromZIP("resources/symbols.txt", zipfilename)
|
||||
: readFile("resources/symbols.txt");
|
||||
}
|
||||
catch (...) {} // optional file — silently skip if missing
|
||||
|
||||
if (!symbolsRaw.empty()) {
|
||||
// Collect codepoints already loaded (ASCII 32-127) to skip duplicates.
|
||||
std::unordered_set<uint32_t> seen;
|
||||
for (auto& p : glyphList) seen.insert(p.first);
|
||||
|
||||
std::string symbolsStr(symbolsRaw.begin(), symbolsRaw.end());
|
||||
size_t pos = 0;
|
||||
while (pos < symbolsStr.size()) {
|
||||
uint32_t cp = nextUtf8Codepoint(symbolsStr, pos);
|
||||
if (cp < 32 || !seen.insert(cp).second) continue; // skip controls and duplicates
|
||||
|
||||
GlyphBitmap gb;
|
||||
if (FT_Load_Char(face, cp, FT_LOAD_RENDER)) {
|
||||
gb.width = 0; gb.height = 0;
|
||||
gb.bearing = { 0.f, 0.f };
|
||||
gb.advance = 0;
|
||||
} else {
|
||||
gb.width = face->glyph->bitmap.width;
|
||||
gb.height = face->glyph->bitmap.rows;
|
||||
gb.bearing = Eigen::Vector2f((float)face->glyph->bitmap_left,
|
||||
(float)face->glyph->bitmap_top);
|
||||
gb.advance = static_cast<unsigned int>(face->glyph->advance.x);
|
||||
size_t dataSize = static_cast<size_t>(gb.width) * static_cast<size_t>(gb.height);
|
||||
if (dataSize > 0) {
|
||||
gb.data.assign(face->glyph->bitmap.buffer,
|
||||
face->glyph->bitmap.buffer + dataSize);
|
||||
maxGlyphHeight = max(maxGlyphHeight, gb.height);
|
||||
}
|
||||
}
|
||||
glyphList.emplace_back(cp, std::move(gb));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lineHeight = std::max<float>(static_cast<float>(pixelSize) * 1.3f, static_cast<float>(maxGlyphHeight) * 1.25f);
|
||||
@ -185,7 +249,7 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
||||
rowHeight = 0;
|
||||
|
||||
for (auto &p : glyphList) {
|
||||
char ch = p.first;
|
||||
uint32_t ch = p.first;
|
||||
GlyphBitmap &gb = p.second;
|
||||
|
||||
if (gb.width == 0 || gb.height == 0) {
|
||||
@ -300,15 +364,17 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
|
||||
float maxH = 0.0f;
|
||||
|
||||
float maxLineWidth = 0.0f;
|
||||
for (char ch : text) {
|
||||
if (ch == '\n') {
|
||||
size_t textPos = 0;
|
||||
while (textPos < text.size()) {
|
||||
uint32_t cp = nextUtf8Codepoint(text, textPos);
|
||||
if (cp == '\n') {
|
||||
maxLineWidth = max(maxLineWidth, penX);
|
||||
penX = 0.0f;
|
||||
penY -= lineHeight * scale;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto git = glyphs.find(ch);
|
||||
auto git = glyphs.find(cp);
|
||||
if (git == glyphs.end()) continue;
|
||||
const GlyphInfo& g = git->second;
|
||||
|
||||
|
||||
@ -37,7 +37,7 @@ private:
|
||||
|
||||
Renderer* r = nullptr;
|
||||
|
||||
std::unordered_map<char, GlyphInfo> glyphs;
|
||||
std::unordered_map<uint32_t, GlyphInfo> glyphs;
|
||||
|
||||
// OpenGL objects for a dynamic quad
|
||||
//unsigned int vao = 0;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user