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)
}

Parameters:

NameTypeDescription
wf32Width in world units
hf32Height in world units
modeu32Billboard mode (1-4)

Note: Use set_color(0xRRGGBBAA) before calling to set the billboard tint.

Example:

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

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

    // 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);
        set_color(particle.color);
        draw_billboard(0.5, 0.5, 1);
    }
}
}

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
)
}

Parameters:

NameTypeDescription
w, hf32Size in world units
src_x, src_yf32UV origin in texture (0.0-1.0)
src_w, src_hf32UV size in texture (0.0-1.0)
modeu32Billboard mode (1-4)

Note: Use set_color(0xRRGGBBAA) before calling to set the billboard tint.

Example:

#![allow(unused)]
fn main() {
// Sprite sheet: 128x32 pixels, 4 frames of 32x32 each
const FRAME_UV_W: f32 = 32.0 / 128.0;  // 0.25
const FRAME_UV_H: f32 = 32.0 / 32.0;   // 1.0

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);
    set_color(0xFFFFFFFF);
    draw_billboard_region(
        2.0, 2.0,                                    // Size
        frame as f32 * FRAME_UV_W, 0.0, FRAME_UV_W, FRAME_UV_H,  // UV coords
        2                                            // Cylindrical Y
    );
}
}

Use Cases

Trees and Vegetation

#![allow(unused)]
fn main() {
// Vegetation atlas: 256x128 pixels, 4 tree types of 64x128 each
const TREE_UV_W: f32 = 64.0 / 256.0;   // 0.25
const TREE_UV_H: f32 = 128.0 / 128.0;  // 1.0

fn render() {
    texture_bind(vegetation_atlas);
    blend_mode(1); // Alpha blend for transparency
    cull_mode(0);  // Double-sided
    set_color(0xFFFFFFFF);

    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 as f32 * TREE_UV_W;
        draw_billboard_region(
            tree.width, tree.height,
            src_x, 0.0, TREE_UV_W, TREE_UV_H,
            2  // Cylindrical Y - always upright
        );
    }
}
}

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;
        set_color(color);
        draw_billboard(particle.size, particle.size, 1);
    }
}
}

NPCs and Enemies

#![allow(unused)]
fn main() {
// NPC sheet: 128x128 pixels, 4x4 grid of 32x32 frames
const FRAME_UV_SIZE: f32 = 32.0 / 128.0;  // 0.25

fn render() {
    texture_bind(npc_sheet);
    blend_mode(1);
    set_color(0xFFFFFFFF);

    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) as f32 * FRAME_UV_SIZE,
            (frame / 4) as f32 * FRAME_UV_SIZE,
            FRAME_UV_SIZE, FRAME_UV_SIZE,
            2  // Cylindrical Y
        );
    }
}
}

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
            set_color(0x333333FF);
            draw_billboard(1.0, 0.1, 1);

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

    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);
        set_color(0xFFFFFFFF);

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

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

        // 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;
                set_color(0xFFAA00FF & (0xFFFFFF00 | alpha));
                draw_billboard(p.size, p.size, 1);
            }
        }

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

See Also: Textures, Transforms