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