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",
|
"description": "A token from the Guard - sign of respect",
|
||||||
"icon": "resources/fire2.png"
|
"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",
|
"type": "Line",
|
||||||
"speaker": "Ghost",
|
"speaker": "Ghost",
|
||||||
"portrait": "resources/ghost_avatar.png",
|
"portrait": "resources/ghost_avatar.png",
|
||||||
"text": "You finally came here.",
|
"text": "Наконец-то ты пришел.",
|
||||||
"next": "line_2"
|
"next": "line_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "line_2",
|
"id": "line_2",
|
||||||
"type": "Line",
|
"type": "Line",
|
||||||
"speaker": "Hero",
|
"speaker": "Hero",
|
||||||
"portrait": "",
|
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
|
||||||
"text": "I need answers.",
|
"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"
|
"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 <array>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace ZL {
|
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()
|
TextRenderer::~TextRenderer()
|
||||||
{
|
{
|
||||||
glyphs.clear();
|
glyphs.clear();
|
||||||
@ -87,6 +105,8 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
|||||||
return false;
|
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);
|
FT_Set_Pixel_Sizes(face, 0, pixelSize);
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
@ -101,7 +121,7 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
|||||||
unsigned int advance = 0;
|
unsigned int advance = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::pair<char, GlyphBitmap>> glyphList;
|
std::vector<std::pair<uint32_t, GlyphBitmap>> glyphList;
|
||||||
glyphList.reserve(128 - 32);
|
glyphList.reserve(128 - 32);
|
||||||
|
|
||||||
int maxGlyphHeight = 0;
|
int maxGlyphHeight = 0;
|
||||||
@ -114,7 +134,7 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
|||||||
gb.height = 0;
|
gb.height = 0;
|
||||||
gb.bearing = { 0.f, 0.f };
|
gb.bearing = { 0.f, 0.f };
|
||||||
gb.advance = 0;
|
gb.advance = 0;
|
||||||
glyphList.emplace_back((char)c, std::move(gb));
|
glyphList.emplace_back((uint32_t)c, std::move(gb));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +150,51 @@ bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const s
|
|||||||
maxGlyphHeight = max(maxGlyphHeight, gb.height);
|
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);
|
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;
|
rowHeight = 0;
|
||||||
|
|
||||||
for (auto &p : glyphList) {
|
for (auto &p : glyphList) {
|
||||||
char ch = p.first;
|
uint32_t ch = p.first;
|
||||||
GlyphBitmap &gb = p.second;
|
GlyphBitmap &gb = p.second;
|
||||||
|
|
||||||
if (gb.width == 0 || gb.height == 0) {
|
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 maxH = 0.0f;
|
||||||
|
|
||||||
float maxLineWidth = 0.0f;
|
float maxLineWidth = 0.0f;
|
||||||
for (char ch : text) {
|
size_t textPos = 0;
|
||||||
if (ch == '\n') {
|
while (textPos < text.size()) {
|
||||||
|
uint32_t cp = nextUtf8Codepoint(text, textPos);
|
||||||
|
if (cp == '\n') {
|
||||||
maxLineWidth = max(maxLineWidth, penX);
|
maxLineWidth = max(maxLineWidth, penX);
|
||||||
penX = 0.0f;
|
penX = 0.0f;
|
||||||
penY -= lineHeight * scale;
|
penY -= lineHeight * scale;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto git = glyphs.find(ch);
|
auto git = glyphs.find(cp);
|
||||||
if (git == glyphs.end()) continue;
|
if (git == glyphs.end()) continue;
|
||||||
const GlyphInfo& g = git->second;
|
const GlyphInfo& g = git->second;
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ private:
|
|||||||
|
|
||||||
Renderer* r = nullptr;
|
Renderer* r = nullptr;
|
||||||
|
|
||||||
std::unordered_map<char, GlyphInfo> glyphs;
|
std::unordered_map<uint32_t, GlyphInfo> glyphs;
|
||||||
|
|
||||||
// OpenGL objects for a dynamic quad
|
// OpenGL objects for a dynamic quad
|
||||||
//unsigned int vao = 0;
|
//unsigned int vao = 0;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user