Created
April 28, 2019 11:58
-
-
Save felixjones/02a20c832b5cda4967790f7c2aed8765 to your computer and use it in GitHub Desktop.
C++ Mode 1 3D perspective camera for Game Boy Advance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
really cool :)