Skip to main content

proof_engine/glyph/
mod.rs

1//! Glyph — the fundamental rendering primitive.
2//!
3//! Everything in Proof Engine is a Glyph: a single character rendered as a
4//! textured quad in 3D space with position, mass, charge, temperature, and entropy.
5
6pub mod batch;
7pub mod atlas;
8pub mod sdf_generator;
9pub mod sdf_atlas;
10pub mod sdf_batch;
11pub mod font_to_mesh;
12pub mod glyph_mesh;
13pub mod glyph_materials;
14pub mod glyph_3d_dissolve;
15
16use glam::{Vec2, Vec3, Vec4};
17use crate::math::MathFunction;
18
19/// Opaque handle to a Glyph in the scene.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub struct GlyphId(pub u32);
22
23/// Which render pass this glyph belongs to.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum RenderLayer {
26    Background,  // chaos field — rendered first, no depth write
27    World,       // environment elements
28    Entity,      // characters, enemies, bosses
29    Particle,    // particles (additive blend usually)
30    UI,          // UI elements (rendered without perspective)
31    Overlay,     // post-processing overlays (HUD flash, vignette)
32}
33
34/// How this glyph blends with the framebuffer.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum BlendMode {
37    Normal,    // standard alpha blend
38    Additive,  // add color to destination (for glow/particles)
39    Multiply,  // multiply with destination (for shadows/tints)
40    Screen,    // 1 - (1-src)(1-dst) (for bloom-like effects)
41}
42
43/// A single character rendered as a textured quad in 3D space.
44#[derive(Clone, Debug)]
45pub struct Glyph {
46    // ── Identity ─────────────────────────────────────────────────────────────
47    pub character: char,
48
49    // ── Spatial ──────────────────────────────────────────────────────────────
50    pub position: Vec3,
51    pub velocity: Vec3,
52    pub acceleration: Vec3,
53    pub rotation: f32,       // radians, around the facing axis
54    pub scale: Vec2,
55
56    // ── Visual ───────────────────────────────────────────────────────────────
57    pub color: Vec4,         // RGBA
58    pub emission: f32,       // how much light this glyph emits (0 = none, 1+ = glows)
59    pub glow_color: Vec3,    // color of emitted light
60    pub glow_radius: f32,    // how far the glow reaches in world units
61
62    // ── Mathematical properties (physically simulated) ───────────────────────
63    pub mass: f32,           // affects gravitational pull
64    pub charge: f32,         // EM interaction: + attracts -, like repels like
65    pub temperature: f32,    // affects color shift and jitter amount
66    pub entropy: f32,        // 0 = predictable, 1 = chaotic behavior
67
68    // ── Animation ────────────────────────────────────────────────────────────
69    pub life_function: Option<MathFunction>,  // drives this glyph's position/color
70    pub age: f32,                             // seconds since creation
71    pub lifetime: f32,                        // total lifespan (-1 = infinite)
72
73    // ── Rendering ────────────────────────────────────────────────────────────
74    pub layer: RenderLayer,
75    pub blend_mode: BlendMode,
76    pub visible: bool,
77}
78
79impl Default for Glyph {
80    fn default() -> Self {
81        Self {
82            character: ' ',
83            position: Vec3::ZERO,
84            velocity: Vec3::ZERO,
85            acceleration: Vec3::ZERO,
86            rotation: 0.0,
87            scale: Vec2::ONE,
88            color: Vec4::ONE,
89            emission: 0.0,
90            glow_color: Vec3::ONE,
91            glow_radius: 0.0,
92            mass: 1.0,
93            charge: 0.0,
94            temperature: 0.5,
95            entropy: 0.0,
96            life_function: None,
97            age: 0.0,
98            lifetime: -1.0,
99            layer: RenderLayer::World,
100            blend_mode: BlendMode::Normal,
101            visible: true,
102        }
103    }
104}
105
106impl Glyph {
107    /// Returns true if this glyph has expired and should be removed.
108    pub fn is_expired(&self) -> bool {
109        self.lifetime >= 0.0 && self.age >= self.lifetime
110    }
111}
112
113/// Pre-allocated pool of Glyphs for efficient batch rendering.
114#[allow(dead_code)]
115pub struct GlyphPool {
116    glyphs: Vec<Option<Glyph>>,
117    free_slots: Vec<u32>,
118    next_id: u32,
119}
120
121impl GlyphPool {
122    pub fn new(capacity: usize) -> Self {
123        Self {
124            glyphs: vec![None; capacity],
125            free_slots: (0..capacity as u32).rev().collect(),
126            next_id: 0,
127        }
128    }
129
130    pub fn spawn(&mut self, glyph: Glyph) -> GlyphId {
131        if let Some(slot) = self.free_slots.pop() {
132            self.glyphs[slot as usize] = Some(glyph);
133            GlyphId(slot)
134        } else {
135            // Expand if full
136            let id = self.glyphs.len() as u32;
137            self.glyphs.push(Some(glyph));
138            GlyphId(id)
139        }
140    }
141
142    pub fn remove(&mut self, id: GlyphId) {
143        if let Some(slot) = self.glyphs.get_mut(id.0 as usize) {
144            *slot = None;
145            self.free_slots.push(id.0);
146        }
147    }
148
149    /// Alias for `remove` — despawn a glyph by ID.
150    pub fn despawn(&mut self, id: GlyphId) {
151        self.remove(id);
152    }
153
154    pub fn get(&self, id: GlyphId) -> Option<&Glyph> {
155        self.glyphs.get(id.0 as usize)?.as_ref()
156    }
157
158    pub fn get_mut(&mut self, id: GlyphId) -> Option<&mut Glyph> {
159        self.glyphs.get_mut(id.0 as usize)?.as_mut()
160    }
161
162    pub fn iter(&self) -> impl Iterator<Item = (GlyphId, &Glyph)> {
163        self.glyphs.iter().enumerate().filter_map(|(i, g)| {
164            g.as_ref().map(|g| (GlyphId(i as u32), g))
165        })
166    }
167
168    pub fn iter_mut(&mut self) -> impl Iterator<Item = (GlyphId, &mut Glyph)> {
169        self.glyphs.iter_mut().enumerate().filter_map(|(i, g)| {
170            g.as_mut().map(|g| (GlyphId(i as u32), g))
171        })
172    }
173
174    /// Advance all glyphs by dt seconds. Remove expired ones.
175    pub fn tick(&mut self, dt: f32) {
176        for slot in self.glyphs.iter_mut() {
177            if let Some(ref mut g) = slot {
178                g.age += dt;
179                // Apply velocity
180                g.position += g.velocity * dt;
181                g.velocity += g.acceleration * dt;
182                // Expire check handled externally via is_expired()
183            }
184        }
185        // Remove expired
186        let mut to_remove = Vec::new();
187        for (i, slot) in self.glyphs.iter().enumerate() {
188            if let Some(ref g) = slot {
189                if g.is_expired() {
190                    to_remove.push(i as u32);
191                }
192            }
193        }
194        for id in to_remove {
195            self.glyphs[id as usize] = None;
196            self.free_slots.push(id);
197        }
198    }
199
200    pub fn count(&self) -> usize {
201        self.glyphs.iter().filter(|s| s.is_some()).count()
202    }
203}