Billboard Functions
Camera-facing quads for sprites in 3D space.
Billboard Modes
| Mode | Name | Description |
|---|---|---|
| 1 | Spherical | Always faces camera (all axes) |
| 2 | Cylindrical Y | Rotates around Y axis only (trees, NPCs) |
| 3 | Cylindrical X | Rotates around X axis only |
| 4 | Cylindrical Z | Rotates 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:
| Name | Type | Description |
|---|---|---|
| w | f32 | Width in world units |
| h | f32 | Height in world units |
| mode | u32 | Billboard 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:
| Name | Type | Description |
|---|---|---|
| w, h | f32 | Size in world units |
| src_x, src_y | f32 | UV origin in texture (0.0-1.0) |
| src_w, src_h | f32 | UV size in texture (0.0-1.0) |
| mode | u32 | Billboard 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