blob: ced7c47f15c9e682e584521da63ec5ef9db572b9 [file] [log] [blame] [edit]
/*
32blit Voxel Terrain demo
by Jonathan Williamson (lowfatcode)
*/
#include <cstring>
#include "32blit.hpp"
#include "map.hpp"
using namespace blit;
Pen sky_colour = Pen(127, 182, 212, 255);
Pen colour_map_palette[256];
uint16_t *tiles[32][32];
std::vector<uint16_t *> free_tiles;
constexpr uint16_t tile_cache_size = 120;
uint16_t tile_cache[32 * 32 * tile_cache_size];
Vec3 position(64, 64, 100);
float angle = 0.0f;
float lean = 0.0f;
float pitch = 0.0f;
uint8_t visited[32][32];
uint32_t terrain_file;
uint8_t terrain_index = 1;
void load_map() {
//close_file(terrain_file);
//terrain_file = open_file(std::to_string(terrain_index) + ".map");
// load palette data
//if(read_file(terrain_file, 0, 768, (char *)buffer) == 768) {
for(uint16_t i = 0; i < 256; i++) {
colour_map_palette[i] = Pen(demo_map[i * 3 + 0], demo_map[i * 3 + 1], demo_map[i * 3 + 2]);
}
//}
for(uint8_t y = 0; y < 32; y++) {
for(uint8_t x = 0; x < 32; x++) {
tiles[x][y] = nullptr;
}
}
free_tiles.clear();
for(uint8_t i = 0; i < tile_cache_size; i++) {
free_tiles.push_back(&tile_cache[32 * 32 * i]);
}
}
void init() {
set_screen_mode(ScreenMode::lores);
load_map();
}
void load_tile(int16_t x, int16_t y) {
uint32_t tile_size = 32 * 32 * 2;
uint32_t offset = 768 + ((x + y * 32) * tile_size);
if(free_tiles.size() > 0) {
tiles[x][y] = free_tiles.back();
free_tiles.pop_back();
memcpy((char *)tiles[x][y], demo_map + offset, tile_size);
//read_file(terrain_file, offset, tile_size, (char *)tiles[x][y]);
}
}
uint16_t get_sample(int16_t x, int16_t y) {
// work out the tile coordinates for this sample
uint8_t tx = (x >> 5) & 0x1f;
uint8_t ty = (y >> 5) & 0x1f;
visited[tx][ty] = 1;
if(tiles[tx][ty] == nullptr) {
return 0;
}
// work out the coordinates (within the tile) for the sample
uint16_t sx = x & 0x1f;
uint16_t sy = y & 0x1f;
return tiles[tx][ty][sx + sy * 32];
}
float deg2rad(float d) {
return d * pi / 180.0f;
}
void draw_world(Vec3 position, float angle, float lean, float horizon, float near, float far) {
static int16_t height_buffer[160];
memset(visited, 0, 32 * 32);
// reset the height buffer to the bottom of the screen
for(auto i = 0; i < screen.bounds.w; i++) {
height_buffer[i] = screen.bounds.h;
}
// convert angle into radians
angle = deg2rad(angle);
float sina = sinf(angle);
float cosa = cosf(angle);
// starting z value
float z = near;
while(z < far) {
// calculate the left and right Points for the current sample span
Vec2 frustrum_left = Vec2(-cosa * z - sina * z, sina * z - cosa * z);
Vec2 frustrum_right = Vec2( cosa * z - sina * z, -sina * z - cosa * z);
// calculate the step size along the span for each screen column drawn
Vec2 sample_step((frustrum_right.x - frustrum_left.x) / screen.bounds.w, (frustrum_right.y - frustrum_left.y) / screen.bounds.w);
float lean_step = lean * 2.0f / screen.bounds.w;
// pre-multiply the fog alpha for this z-distance
uint8_t fog_blend = (z / far / 1.5f) * 255; // value for "amount of fog" from 0 to 255
Pen fog((sky_colour.r * fog_blend) >> 8, (sky_colour.g * fog_blend) >> 8, (sky_colour.b * fog_blend) >> 8);
fog_blend = 255 - fog_blend;
// Point being sampled, starts on the left of the view frustrum and is stepped forward each
// screen column drawn until it reaches the right of the view frustrum
Vec2 sample_point = frustrum_left + Vec2(position.x, position.y);
float sample_lean = -lean;
float invz = 1.0f / z * 100.0f;
// for each column on the screen...
for(uint16_t i = 0; i < screen.bounds.w; i++) {
uint16_t sample = get_sample(sample_point.x, sample_point.y);
uint8_t colour_index = sample >> 8;
// convert the height map sample into a y coordinate on screen
int16_t height = (position.z - (sample & 0xff)) * invz + float(horizon) + sample_lean;
// if the height is smaller (further up the screen) than our current height buffer
// value then we need to draw a new vertical strip
if(height < height_buffer[i]) {
// fetch the colour for this strip from the colour map
Pen colour = colour_map_palette[colour_index];
// blend terrain colour with pre-multiplied fog colour
colour.r = ((colour.r * fog_blend) >> 8) + fog.r;
colour.g = ((colour.g * fog_blend) >> 8) + fog.g;
colour.b = ((colour.b * fog_blend) >> 8) + fog.b;
// draw the vertical strip
screen.pen = colour;
screen.v_span(Point(i, height), height_buffer[i] - height);
// update the height buffer to save that we've draw this far
// up the screen
height_buffer[i] = height;
}
// move to the next sampling coordinate
sample_point += sample_step;
sample_lean += lean_step;
}
// move forward (into the distance) in increasingly large steps
z *= 1.025f;
}
}
void render(uint32_t time_ms) {
// draw the sky
screen.pen = sky_colour;
screen.clear();
uint32_t ms_start = now();
draw_world(
position, // player position
angle, // player direction
lean,
20.0f + pitch, // horizon position
3.0f, // near distance
300.0f // far distance
);
uint32_t ms_end = now();
for(uint8_t y = 0; y < 32; y++) {
for(uint8_t x = 0; x < 32; x++) {
if(tiles[x][y] != nullptr) {
screen.pen = Pen(255, 0, 0, 100);
}else{
screen.pen = Pen(0, 0, 0, 100);
}
screen.pixel(Point(x, y));
if(visited[x][y]) {
screen.pen = Pen(255, 255, 0, 100);
screen.pixel(Point(x, y));
}
}
}
// work out the tile coordinates for the player position
uint8_t tx = (int32_t(position.x) >> 5) & 0x1f;
uint8_t ty = (int32_t(position.y) >> 5) & 0x1f;
screen.pen = Pen(0, 255, 0);
screen.pixel(Point(tx, ty));
// draw FPS meter & watermark
screen.watermark();
screen.mask = nullptr;
screen.pen = Pen(255, 255, 255);
screen.text(std::to_string(ms_end - ms_start), minimal_font, Point(1, 230));
screen.pen = Pen(255, 0, 0);
for (int i = 0; i < uint16_t(ms_end - ms_start); i++) {
screen.pen = Pen(i * 5, 255 - (i * 5), 0);
screen.rectangle(Rect(i * 3 + 1, 237, 2, 2));
}
}
void update(uint32_t time_ms) {
static uint16_t tick = 0;
tick++;
// update angle of player based on joystick input
float old_angle = angle;
angle -= joystick.x * 2.0f;
lean = (angle - old_angle) * 10.0f;
if(buttons & DPAD_UP) {
position.z += 1.0f;
}
if(buttons & DPAD_DOWN) {
position.z -= 1.0f;
}
// move player location if joystick y axis is forward/backwards
position.x += sinf(deg2rad(angle)) * joystick.y;
position.y += cosf(deg2rad(angle)) * joystick.y;
pitch = joystick.y * 20.0f;
position.x = position.x < 0.0f ? 1023.0f : position.x;
position.y = position.y < 0.0f ? 1023.0f : position.y;
position.x = position.x > 1023.0f ? 0.0f : position.x;
position.y = position.y > 1023.0f ? 0.0f : position.y;
// remove any unused tiles from the cache
for(uint8_t y = 0; y < 32; y++) {
for(uint8_t x = 0; x < 32; x++) {
if(tiles[x][y] != nullptr && !visited[x][y]) {
free_tiles.push_back(tiles[x][y]);
tiles[x][y] = nullptr;
}
}
}
// load the next needed tile into the cache
for(uint8_t y = 0; y < 32; y++) {
for(uint8_t x = 0; x < 32; x++) {
if(tiles[x][y] == nullptr && visited[x][y]) {
load_tile(x, y);
//return; // quit out early if we load a new tile
}
}
}
if(buttons.released & A) {
terrain_index++;
if(terrain_index > 4) {
terrain_index = 1;
}
load_map();
}
}