Skip to content

Instantly share code, notes, and snippets.

@felixjones
Created April 28, 2019 11:58
Show Gist options
  • Save felixjones/02a20c832b5cda4967790f7c2aed8765 to your computer and use it in GitHub Desktop.
Save felixjones/02a20c832b5cda4967790f7c2aed8765 to your computer and use it in GitHub Desktop.
C++ Mode 1 3D perspective camera for Game Boy Advance
#include <gba.hpp>
#include <gba_debug.hpp>
#include <gba_nitro.hpp>
#include "res_assets.h"
#define EVER ;;
using namespace gba;
static camera3d camera; // 3D camera
static uint8 buffer = 0; // Double buffered
static video::background_affine::mat matrices[160 * 2]; // 2 buffers for holding matrices
static void IWRAM_ interrupt_handler( const irq::flags flags ) {
// DMA hblank copying 4 32bit chunks, resetting destination after each completion, repeating
constexpr auto matrixCopy = dma::control_hblank( sizeof( matrices[0] ) / 4 )
.transfer_32bit( true )
.destination_modifier( dma::modifier::reload )
.source_modifier( dma::modifier::increment )
.repeat( true );
if ( flags.vblank() ) {
dma::channel[3].submit( matrixCopy, &video::mode1.background_affine[0].matrix(), &matrices[buffer] );
buffer = 160 - buffer; // Swap buffers
// Calculate next 160 matrices from scanline 0
// The "_rough" version will transform every even line, but interpolate every odd line
// This is still quite slow, so IWRAM is needed
camera.calculate_matrices_rough( &matrices[buffer], 0, 160 );
}
}
int main( int argc, char * argv[] ) {
// Mode 1, Background 2 enabled
display::control = display::mode( 1 ).enable_layers( { 2 } ).disable_layers( { 0, 1, 3, 4 } );
auto assets = ( const nitro::archive * )res_assets;
if ( assets->is_good() == false ) {
debug::log.print( "Bad archive header" );
}
auto nclr = assets->open( "kyo.nclr" );
if ( !nclr ) {
debug::log.print( "File not found" );
} else {
auto nitroColor = ( const nitro::color * )assets->mmap( nclr );
if ( nitroColor->is_good() == false ) {
debug::log.print( "Bad color header" );
} else {
// Upload 16bit palette colors to VRAM
dma::channel[3].submit( dma::transfer( video::mode4.get_palette_address(), nitroColor->palette_start(), nitroColor->sizeof_palette() / 2 ) );
}
}
uint8 block = 0;
auto ncgr = assets->open( "kyo.ncgr" );
if ( !ncgr ) {
debug::log.print( "File not found" );
} else {
auto nitroCharacterGraphic = ( const nitro::character_graphic * )assets->mmap( ncgr );
if ( nitroCharacterGraphic->is_good() == false ) {
debug::log.print( "Bad character graphic header" );
} else {
// Upload 16bit character data to VRAM
dma::channel[3].submit( dma::transfer( &video::character_block[0], nitroCharacterGraphic->character_start(), nitroCharacterGraphic->sizeof_character() / 2 ) );
block = nitroCharacterGraphic->block_length();
}
}
video::tilemap_size size;
auto nscr = assets->open( "kyo.nscr" );
if ( !nscr ) {
debug::log.print( "File not found" );
} else {
auto nitroScreen = ( const nitro::screen * )assets->mmap( nscr );
if ( nitroScreen->is_good() == false ) {
debug::log.print( "Bad screen header" );
} else {
size = nitroScreen->tilemap_size();
if ( size.is_affine() ) {
// Upload 16bit character data to VRAM
dma::channel[3].submit( dma::transfer( &video::tilemap_block[block], nitroScreen->tiledata_start(), nitroScreen->sizeof_tiledata() / 2 ) );
} else {
// Copy 16bit character spans to VRAM
const auto stride = nitroScreen->stride();
auto count = nitroScreen->height() / 8;
auto source = nitroScreen->tiledata_start();
auto dest = &video::tilemap_block[block][0];
while ( count-- ) {
dma::channel[3].submit( dma::transfer( dest, source, stride ) );
source += stride;
dest += 32;
}
}
}
}
// Set up the first affine background (and only affine background of mode 1)
video::mode1.background_affine[0] = video::make_background_affine().tilemap_wrap( true ).tilemap_block( block ).tilemap_size( size );
// Set 3D camera to be 32 units up
camera.translation().y() = 32;
// IRQ Vblank
irq::interrupt.add( irq::vblank ).set_handler( interrupt_handler ).enable();
display::status = display::state().irq_on_vblank();
angle<int16> pitch, yaw;
const auto degreeOne = angle<int16>::from_degrees( 1 ); // Handy (one day this will be constexpr valid)
for ( EVER ) {
auto keys = io::keys.read_keys();
if ( keys.pad ) {
yaw += degreeOne * keys.pad.axis_x();
pitch -= degreeOne * keys.pad.axis_y();
camera.rotation() = mat3<fixed<int16>>::make_rotation( pitch, yaw ); // Sin LUT used
}
if ( keys.Select ) {
// Focal length = FOV
if ( keys.button.A ) camera.focal_length() -= 1;
if ( keys.button.B ) camera.focal_length() += 1;
} else if ( keys.button ) {
vec3<fixed<int16>> direction;
if ( keys.button.A ) direction.z() -= 1;
if ( keys.button.B ) direction.z() += 1;
if ( keys.button.L ) direction.x() += 1;
if ( keys.button.R ) direction.x() -= 1;
camera.transform().translate( direction );
// Limit camera height to 1..254
if ( camera.translation().y() < 1 ) camera.translation().y() = 1;
if ( camera.translation().y() > 254 ) camera.translation().y() = 254;
}
bios::VBlankIntrWait();
}
return 0;
}
@aknatnwastaken
Copy link

really cool :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment