Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Billboard Functions

Camera-facing quads for sprites in 3D space.

Billboard Modes

ModeNameDescription
1SphericalAlways faces camera (all axes)
2Cylindrical YRotates around Y axis only (trees, NPCs)
3Cylindrical XRotates around X axis only
4Cylindrical ZRotates around Z axis only

Functions

draw_billboard

Draws a camera-facing quad using the bound texture.

Signature:

#![allow(unused)]
fn main() {
fn draw_billboard(w: f32, h: f32, mode: u32, color: u32)
}

Parameters:

NameTypeDescription
wf32Width in world units
hf32Height in world units
modeu32Billboard mode (1-4)
coloru32Tint color as 0xRRGGBBAA

Example:

#![allow(unused)]
fn main() {
fn render() {
    texture_bind(tree_sprite);

    // Trees with cylindrical Y billboards
    for tree in &trees {
        push_identity();
        push_translate(tree.x, tree.y, tree.z);
        draw_billboard(2.0, 4.0, 2, 0xFFFFFFFF);
    }

    // Particles with spherical billboards
    texture_bind(particle_sprite);
    blend_mode(2); // Additive
    for particle in &particles {
        push_identity();
        push_translate(particle.x, particle.y, particle.z);
        draw_billboard(0.5, 0.5, 1, particle.color);
    }
}
}

draw_billboard_region

Draws a billboard using a texture region (sprite sheet).

Signature:

#![allow(unused)]
fn main() {
fn draw_billboard_region(
    w: f32, h: f32,
    src_x: f32, src_y: f32, src_w: f32, src_h: f32,
    mode: u32,
    color: u32
)
}

Parameters:

NameTypeDescription
w, hf32Size in world units
src_x, src_yf32Source position in texture (pixels)
src_w, src_hf32Source size in texture (pixels)
modeu32Billboard mode (1-4)
coloru32Tint color

Example:

#![allow(unused)]
fn main() {
fn render() {
    texture_bind(enemy_sheet);

    // Animated enemy sprite
    let frame = ((elapsed_time() * 8.0) as u32) % 4;
    push_identity();
    push_translate(enemy.x, enemy.y + 1.0, enemy.z);
    draw_billboard_region(
        2.0, 2.0,                              // Size
        (frame * 32) as f32, 0.0, 32.0, 32.0,  // Animation frame
        2,                                      // Cylindrical Y
        0xFFFFFFFF
    );
}
}

Use Cases

Trees and Vegetation

#![allow(unused)]
fn main() {
fn render() {
    texture_bind(vegetation_atlas);
    blend_mode(1); // Alpha blend for transparency
    cull_mode(0);  // Double-sided

    for tree in &trees {
        push_identity();
        push_translate(tree.x, tree.height * 0.5, tree.z);

        // Different tree types from atlas
        let src_x = (tree.type_id * 64) as f32;
        draw_billboard_region(
            tree.width, tree.height,
            src_x, 0.0, 64.0, 128.0,
            2, // Cylindrical Y - always upright
            0xFFFFFFFF
        );
    }
}
}

Particle Effects

#![allow(unused)]
fn main() {
fn render() {
    texture_bind(particle_texture);
    blend_mode(2); // Additive for glow
    depth_test(1);

    for particle in &particles {
        push_identity();
        push_translate(particle.x, particle.y, particle.z);

        // Spherical billboard - faces camera completely
        let alpha = (particle.life * 255.0) as u32;
        let color = (particle.color & 0xFFFFFF00) | alpha;
        draw_billboard(particle.size, particle.size, 1, color);
    }
}
}

NPCs and Enemies

#![allow(unused)]
fn main() {
fn render() {
    texture_bind(npc_sheet);
    blend_mode(1);

    for npc in &npcs {
        push_identity();
        push_translate(npc.x, npc.y + 1.0, npc.z);

        // Select animation frame based on direction and state
        let frame = get_npc_frame(npc);
        draw_billboard_region(
            2.0, 2.0,
            (frame % 4 * 32) as f32,
            (frame / 4 * 32) as f32,
            32.0, 32.0,
            2, // Cylindrical Y
            0xFFFFFFFF
        );
    }
}
}

Health Bars Above Enemies

#![allow(unused)]
fn main() {
fn render() {
    // Draw enemies first
    for enemy in &enemies {
        draw_enemy(enemy);
    }

    // Then draw health bars as billboards
    depth_test(0); // On top of everything
    texture_bind(0); // No texture (solid color)

    for enemy in &enemies {
        if enemy.health < enemy.max_health {
            push_identity();
            push_translate(enemy.x, enemy.y + 2.5, enemy.z);

            // Background
            draw_billboard(1.0, 0.1, 1, 0x333333FF);

            // Health fill
            let ratio = enemy.health / enemy.max_health;
            push_scale(ratio, 1.0, 1.0);
            draw_billboard(1.0, 0.1, 1, 0x00FF00FF);
        }
    }

    depth_test(1);
}
}

Complete Example

#![allow(unused)]
fn main() {
static mut TREE_TEX: u32 = 0;
static mut PARTICLE_TEX: u32 = 0;

struct Particle {
    x: f32, y: f32, z: f32,
    vx: f32, vy: f32, vz: f32,
    life: f32,
    size: f32,
}

static mut PARTICLES: [Particle; 100] = [Particle {
    x: 0.0, y: 0.0, z: 0.0,
    vx: 0.0, vy: 0.0, vz: 0.0,
    life: 0.0, size: 0.0,
}; 100];

fn init() {
    unsafe {
        TREE_TEX = rom_texture(b"tree".as_ptr(), 4);
        PARTICLE_TEX = rom_texture(b"spark".as_ptr(), 5);
    }
}

fn update() {
    unsafe {
        let dt = delta_time();
        for p in &mut PARTICLES {
            if p.life > 0.0 {
                p.x += p.vx * dt;
                p.y += p.vy * dt;
                p.z += p.vz * dt;
                p.vy -= 5.0 * dt; // Gravity
                p.life -= dt;
            }
        }
    }
}

fn render() {
    unsafe {
        // Trees - cylindrical billboards
        texture_bind(TREE_TEX);
        blend_mode(1);
        cull_mode(0);

        push_identity();
        push_translate(5.0, 2.0, -5.0);
        draw_billboard(2.0, 4.0, 2, 0xFFFFFFFF);

        push_identity();
        push_translate(-3.0, 1.5, -8.0);
        draw_billboard(1.5, 3.0, 2, 0xFFFFFFFF);

        // Particles - spherical billboards
        texture_bind(PARTICLE_TEX);
        blend_mode(2); // Additive

        for p in &PARTICLES {
            if p.life > 0.0 {
                push_identity();
                push_translate(p.x, p.y, p.z);
                let alpha = (p.life.min(1.0) * 255.0) as u32;
                draw_billboard(p.size, p.size, 1, 0xFFAA00FF & (0xFFFFFF00 | alpha));
            }
        }

        blend_mode(0);
        cull_mode(1);
    }
}
}

See Also: Textures, Transforms