sdl_runtime_compiler git · main
SDL3 game for running and compiling code at runtime
C++ 72.3% C 26.2%git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gzsrc/text/token.cpp raw
#include <SDL3/SDL.h>
#include "entities/camera.hpp"
#include "entities/world.hpp"
#include "rendering/gl_renderer.hpp"
#include "systems/cpp_highlight_system.hpp"
#include "text/token.hpp"
#include "utils/globals.hpp"
namespace
Nimbin
{
namespace Text
{
static SDL_FColor
aiGlintColor(Token::Ai::State s)
{
switch (s) {
case Token::Ai::State::Attack: return {1.0f, 0.85f, 0.75f, 1.0f};
case Token::Ai::State::Flee: return {1.0f, 1.0f, 0.80f, 1.0f};
case Token::Ai::State::Stunned: return {0.85f, 0.85f, 0.95f, 1.0f};
case Token::Ai::State::Idle: return {1.0f, 1.0f, 1.0f, 1.0f};
}
return {1.0f, 1.0f, 1.0f, 1.0f};
}
static Vec3 v_norm(const Vec3& v)
{
double l = SDL_sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
return (l < 1e-9) ? Vec3{0,0,1} : Vec3{v.x/l, v.y/l, v.z/l};
}
// Camera-facing filled disc (triangle fan)
static void
append_disc(DynArray<float>& v, DynArray<int>& idx,
const Vec3& c, const Vec3& right, const Vec3& up,
const Vec3& camN, double radius)
{
constexpr int SEG = 10;
const int base = static_cast<int>(v.size()) / 9;
auto push = [&](const Vec3& p) {
v.push_back((float)p.x); v.push_back((float)p.y); v.push_back((float)p.z);
v.push_back((float)camN.x); v.push_back((float)camN.y); v.push_back((float)camN.z);
v.push_back(1.0f); v.push_back(0.0f); v.push_back(0.0f);
};
push(c);
for (int i = 0; i < SEG; i++) {
double ang = static_cast<double>(i) * (2.0 * SDL_PI_D / SEG);
double dx = SDL_cos(ang) * radius, dy = SDL_sin(ang) * radius;
push(Vec3{ c.x + right.x*dx + up.x*dy,
c.y + right.y*dx + up.y*dy,
c.z + right.z*dx + up.z*dy });
}
for (int i = 0; i < SEG; i++) {
idx.push_back(base + 0);
idx.push_back(base + 1 + i);
idx.push_back(base + 1 + ((i + 1) % SEG));
}
}
// Fast integer hash → float in [0,1). Used for all per-sparkle randomness.
static double hash01(uint32_t x)
{
x ^= x >> 16; x *= 0x7feb352dU;
x ^= x >> 15; x *= 0x846ca68bU;
x ^= x >> 16;
return (x & 0x00ffffffU) / static_cast<double>(0x01000000);
}
static SDL_FColor mix_col(SDL_FColor a, SDL_FColor b, float k)
{
return { a.r + (b.r - a.r) * k,
a.g + (b.g - a.g) * k,
a.b + (b.b - a.b) * k, 1.0f };
}
// A whitespace token's text is a run of spaces or a tab. We keep the real text
// (" " / "\t") for serialization, but build the MESH from an ASCII ghost glyph
// so the token is visible as a tangible thing when it's loose in the world.
// (The mesh path is single-byte, so the ghost must be ASCII — not "·"/"→".)
static bool
text_Is_Whitespace(const std::string& t, bool& has_tab)
{
if (t.empty()) return false;
has_tab = false;
for (char c : t) {
if (c == '\t') has_tab = true;
else if (c != ' ') return false;
}
return true;
}
static std::string
ghost_Glyph(const std::string& t)
{
return (t.find('\t') != std::string::npos) ? ">" : ".";
}
namespace
Token
{
Data::Data(const std::string& txt,
float dpth, float scle,
Vec3 position, SDL_FColor colr)
: GameObject(position, ObjectType::Text,
CollisionShape{}, RigidBody{PhysicsMode::Hover},
true, true, true),
text(txt), color(colr), depth(dpth), scale(scle)
{
bool has_tab = false;
if (text_Is_Whitespace(text, has_tab)) {
is_tab = has_tab;
is_space = !has_tab;
}
single_color = Text::Token::get_Color(CppHighlight::classify_Word(text));
context_color = single_color; // until a lexer overrides
if (is_Whitespace()) {
// Faint glyph that glows when loose in the world (bloomed ghost).
single_color = SDL_FColor{ 0.45f, 0.55f, 0.70f, 1.0f };
context_color = single_color;
glow_enabled = true;
glow_strength = 0.6f;
}
// `color` (Static slot) already initialised to `colr` in the member init list.
rebuild_Mesh();
body.hover_phase = Random::unit() * SDL_PI_D * 2.0;
ai.aggressiveness = Random::unit();
ai.wiggle_phase = Random::unit() * SDL_PI_D * 2.0;
ai.wiggle_freq = 3.0 + Random::unit() * 4.0;
ai.speed = 2.5 + Random::unit() * 2.5;
}
Data::~Data()
{
if (gpu_vbo || gpu_ebo)
GlRenderer::free_MeshBuffers(gpu_vbo, gpu_ebo);
if (gpu_vbo_sparkle || gpu_ebo_sparkle)
GlRenderer::free_MeshBuffers(gpu_vbo_sparkle, gpu_ebo_sparkle);
}
void
Data::set_Color(SDL_FColor c) // the Static fixed colour
{
color = c;
}
void
Data::set_Context(SDL_FColor c) // lexer result inside a document
{
context_color = c;
has_context = true;
}
void
Data::clear_Context() // leaving a document → fall back to single
{
has_context = false;
}
void
Data::set_Look(SDL_FColor c, float new_depth)
{
if (c.r == -1.0f) single_color = Text::Token::get_Color(CppHighlight::classify_Word(text));
else if (c.g == -1.0f) { /* keep current colours */ }
else color = c; // explicit colour → Static slot
set_Depth(new_depth);
}
void
Data::recolor() // recompute the tokenized (single) colour
{
single_color = Text::Token::get_Color(CppHighlight::classify_Word(text));
if (!has_context) context_color = single_color;
}
void
Data::retext(const std::string& s) // text changed → new single colour + new mesh
{
if (text == s) return;
text = s;
bool has_tab = false;
const bool ws = text_Is_Whitespace(text, has_tab);
is_tab = ws && has_tab;
is_space = ws && !has_tab;
single_color = Text::Token::get_Color(CppHighlight::classify_Word(text));
if (!has_context) context_color = single_color;
rebuild_Mesh();
}
void
Data::set_Depth(float new_depth)
{
depth = new_depth;
shape.half_extents.z = (double)depth * 0.5;
}
Vec3
Data::words_centroid(const DynArray<TokenPtr>& words)
{
Vec3 c{};
double n = 0.0;
for (const TokenPtr& w : words) {
if (!w) continue;
c += w->pos;
n += 1.0;
}
if (n > 0.0) c = c / n;
return c;
}
Vec3
Data::get_Distance(Vec3 position) const
{
return pos - position;
}
void
Data::rebuild_Mesh()
{
// SPIKE("rebuild_mesh");
// Whitespace keeps its real text (" "/"\t") for serialization, but renders
// an ASCII ghost glyph so it's a visible thing when loose in the world.
const std::string build_str = is_Whitespace() ? ghost_Glyph(text) : text;
loaded = mesh.build(font_path, build_str, 64, depth, scale);
if (!loaded) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Text::Token::Data: mesh build failed for \"%s\"", text.c_str());
return;
}
float minX = 1e9f, maxX = -1e9f;
float minY = 1e9f, maxY = -1e9f;
for (const Vec3& v : mesh.verts) {
float vx = static_cast<float>(v.x);
float vy = static_cast<float>(v.y);
if (vx < minX) minX = vx;
if (vx > maxX) maxX = vx;
if (vy < minY) minY = vy;
if (vy > maxY) maxY = vy;
}
half_extent = (maxX - minX) * 0.5f;
half_extent_y = (maxY - minY) * 0.5f;
shape.half_extents = {
static_cast<double>(half_extent),
static_cast<double>(half_extent_y),
static_cast<double>(depth) * 0.5
};
packed_verts. clear();
packed_indices.clear();
const size_t nv = mesh.verts.size();
packed_verts.reserve(nv * 9);
for (size_t i = 0; i < nv; i++) {
const Vec3& v = mesh.verts[i];
const Vec3& n = mesh.vertex_normals[i];
const float f = (i < mesh.is_front.size()) ? mesh.is_front[i] : 0.0f;
packed_verts.push_back((float)v.x); packed_verts.push_back((float)v.y); packed_verts.push_back((float)v.z);
packed_verts.push_back((float)n.x); packed_verts.push_back((float)n.y); packed_verts.push_back((float)n.z);
packed_verts.push_back(f); packed_verts.push_back(0.0f); packed_verts.push_back(0.0f);
}
packed_indices.reserve(mesh.tris.size() * 3);
for (const MeshTri& t : mesh.tris) {
packed_indices.push_back(t.a);
packed_indices.push_back(t.b);
packed_indices.push_back(t.c);
}
gpu_dirty = true;
}
void
Data::update(const Vec3& camera_pos, const double view_mat[9], float carry_dist)
{
float fx = -static_cast<float>(view_mat[6]);
float fy = -static_cast<float>(view_mat[7]);
float fz = -static_cast<float>(view_mat[8]);
pos.x = camera_pos.x + static_cast<double>(fx * carry_dist);
pos.y = camera_pos.y + static_cast<double>(fy * carry_dist);
pos.z = camera_pos.z + static_cast<double>(fz * carry_dist);
yaw = SDL_atan2f(-fx, -fz);
}
void
Data::draw(GlRenderer& gl, const Camera& camera, const Light& light) const
{
if (!loaded || packed_indices.empty()) return;
Mat4 proj = gl.get_Projection();
Mat4 view = buildViewMat4(camera);
Mat4 model_rot = buildModelMat4(pos, yaw, pitch);
Mat4 model = model_rot;
model.m[8] *= depth;
model.m[9] *= depth;
model.m[10] *= depth;
Mat4 mvp = Mat4::mul(proj, Mat4::mul(view, model));
const SDL_FColor ac = get_ActiveColor();
gl.set_Wireframe(false);
gl.set_Light (light);
gl.set_MVP (model, mvp);
gl.set_NormalMat(model_rot);
gl.set_FaceShade(ac.r, ac.g, ac.b, back_dim, front_tint);
if (glow_enabled && glow_strength > 0.0f)
gl.set_Emissive(ac.r, ac.g, ac.b, glow_strength);
else
gl.set_Emissive(0.0f, 0.0f, 0.0f, 0.0f);
gl.draw_MeshCached(gpu_vbo, gpu_ebo, gpu_index_count, gpu_dirty,
packed_verts.data(), (int)packed_verts.size() / 9,
packed_indices.data(), (int)packed_indices.size());
gpu_dirty = false;
gl.set_Emissive (0.0f, 0.0f, 0.0f, 0.0f);
gl.set_FaceShade(0.0f, 0.0f, 0.0f, 1.0f, false);
// ── AI sparkle tail ───────────────────────────────────────────────────
if (ai.state == Ai::State::Idle) return;
const Uint64 now = SDL_GetTicks();
// Sample the word's path into a history ring → the trailing tail.
if (sparkle_trail_count == 0) {
for (int i = 0; i < SPARKLE_TRAIL; i++) sparkle_trail[i] = pos;
sparkle_trail_count = SPARKLE_TRAIL;
sparkle_last_ms = now;
}
else if (now - sparkle_last_ms >= 33) { // ~30 Hz
for (int i = SPARKLE_TRAIL - 1; i > 0; --i)
sparkle_trail[i] = sparkle_trail[i - 1];
sparkle_trail[0] = pos;
sparkle_last_ms = now;
}
const double t = static_cast<double>(now) * 0.001;
const Vec3 fwd = v_norm(camera.fwd);
const Vec3 right = v_norm(camera.right);
const Vec3 up = v_norm(Vec3{ right.y*fwd.z - right.z*fwd.y,
right.z*fwd.x - right.x*fwd.z,
right.x*fwd.y - right.y*fwd.x });
const Vec3 camN = { -fwd.x, -fwd.y, -fwd.z };
const double sz = SDL_max(half_extent, 0.25);
const uint32_t wseed = static_cast<uint32_t>(body.hover_phase * 10037.0) | 1u;
static const SDL_FColor PAL[] = {
{1.00f, 0.72f, 0.10f, 1.0f}, // gold
{1.00f, 0.25f, 0.65f, 1.0f}, // magenta-pink
{0.20f, 0.80f, 1.00f, 1.0f}, // cyan
{0.45f, 1.00f, 0.30f, 1.0f}, // lime
{1.00f, 0.45f, 0.10f, 1.0f}, // orange
{0.65f, 0.40f, 1.00f, 1.0f}, // violet
};
constexpr int PALN = sizeof(PAL) / sizeof(PAL[0]);
DynArray<float> sv[PALN];
DynArray<int> si[PALN];
constexpr int PER_NODE = 2;
for (int j = 0; j < sparkle_trail_count; ++j) {
const double age = (sparkle_trail_count > 1)
? double(j) / double(sparkle_trail_count - 1) : 0.0;
const Vec3& node = sparkle_trail[j];
for (int s = 0; s < PER_NODE; ++s) {
uint32_t h = wseed ^ (uint32_t(j) * 2654435761u) ^ (uint32_t(s) * 40503u);
if (hash01(h) < 0.6) continue; // sparser → calmer
double phase = hash01(h ^ 0xA5A5u) * 6.28318;
double tw = SDL_pow(SDL_max(0.0, SDL_sin(t * 1.6 + phase)), 3.0); // slower, softer
if (tw < 0.04) continue;
double spread = sz * (0.6 + age * 1.4); // older = wider scatter
double jx = (hash01(h ^ 0x1111u) - 0.5) * spread;
double jy = (hash01(h ^ 0x2222u) - 0.5) * spread;
// gentle independent wander so each sparkle drifts in place
double wamp = sz * 0.22; // how far it roams
double fx = 0.6 + hash01(h ^ 0x7777u) * 0.8; // per-sparkle drift rates
double fy = 0.6 + hash01(h ^ 0x8888u) * 0.8;
jx += SDL_sin(t * fx + hash01(h ^ 0x5555u) * 6.28318) * wamp;
jy += SDL_cos(t * fy + hash01(h ^ 0x6666u) * 6.28318) * wamp;
Vec3 ctr = { node.x + right.x*jx + up.x*jy + camN.x*0.03,
node.y + right.y*jx + up.y*jy + camN.y*0.03,
node.z + right.z*jx + up.z*jy + camN.z*0.03 };
double size_rng = 0.06 + 0.05 * hash01(h ^ 0x3333u); // smaller + varied
double outer = size_rng * sz * tw * (1.0 - age * 0.6);
if (outer < 1e-4) continue;
int c = int(hash01(h ^ 0x4444u) * PALN) % PALN;
append_disc(sv[c], si[c], ctr, right, up, camN, static_cast<float>(outer));
}
}
Mat4 ident = buildModelMat4(Vec3{0,0,0}, 0.0f, 0.0f);
gl.set_MVP (ident, Mat4::mul(proj, view));
gl.set_NormalMat(ident);
const SDL_FColor glint = aiGlintColor(ai.state);
for (int c = 0; c < PALN; ++c) {
if (si[c].empty()) continue;
SDL_FColor g = mix_col(PAL[c], glint, 0.15f); // keep only a hint of state
gl.set_FaceShade (0.0f, 0.0f, 0.0f, 1.0f, false); // no lit term → no white-out
gl.set_Emissive (g.r, g.g, g.b, 0.85f); // pure colour, below clip
gl.draw_MeshCached(gpu_vbo_sparkle, gpu_ebo_sparkle,
gpu_sparkle_index_count, /*dirty=*/true,
sv[c].data(), (int)sv[c].size() / 9,
si[c].data(), (int)si[c].size());
}
gl.set_Emissive (0.0f, 0.0f, 0.0f, 0.0f);
gl.set_FaceShade(0.0f, 0.0f, 0.0f, 1.0f, false);
}
} // namespace Token
} // namespace Text
} // namespace Nimbin