From 252ff19f2a014dc11727958d037776cc5d977249 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Sun, 17 Jul 2022 01:22:21 +0100 Subject: [PATCH] worldgen: add Perlin noise test --- CMakeLists.txt | 2 +- src/main.cpp | 104 +++++++++++++++++++++++++++++++++++++++++++++- worldgen/tests.py | 62 +++++++++++++++++++++++++-- 3 files changed, 162 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2522d19..861b2d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ fxconv_declare_converters(converters.py) add_executable(addin ${SOURCES} ${ASSETS}) target_compile_options(addin PRIVATE -Wall -Wextra -Wno-narrowing -Os -std=c++20) -target_link_libraries(addin LibProf::LibProf Gint::Gint -lsupc++) +target_link_libraries(addin LibProf::LibProf Gint::Gint -lnum -lsupc++) if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G) message(FATAL_ERROR "This game is not supported on fx-9860G!") diff --git a/src/main.cpp b/src/main.cpp index 94423ec..e49f9a6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include #include "render.h" #include "world.h" #include "graphics.h" @@ -33,16 +34,115 @@ static void renderGame(Camera *camera) renderText(2, hudY+3, "1 2 3 4 5 6 7 8 9"); } +// --== Perlin noise ==-- + +#include +using namespace libnum; +//using num = float; + +struct vec2 { + num x, y; +}; +static vec2 perlinGradients[16]; + +GCONSTRUCTOR +static void precomputeGradients(void) +{ + for(int i = 0; i < 16; i++) { + float alpha = 2 * 3.14159 * i / 16; + perlinGradients[i].x = num(cosf(alpha)); + perlinGradients[i].y = num(sinf(alpha)); + } +} + +static num smooth(num t) +{ + return t * t * t * (t * (6 * t - num(15)) + num(10)); +} + +static num lerp(num x, num y, num t) +{ + return x + t * (y - x); +} + +static num dot(vec2 *grid, int GRID_N, int ix, int iy, num fx, num fy) +{ + vec2 g = grid[iy * GRID_N + ix]; + return (fx - num(ix)) * g.x + (fy - num(iy)) * g.y; +} + +static uint8_t *perlinNoise(int N, int PERLIN_CELL_SIZE) +{ + uint8_t *noise = new uint8_t[N * N]; + if(!noise) + return nullptr; + + int GRID_N = (N / PERLIN_CELL_SIZE) + 1; + vec2 *grid = new vec2[GRID_N * GRID_N]; + if(!grid) { + delete[] noise; + return nullptr; + } + for(int i = 0; i < GRID_N * GRID_N; i++) + grid[i] = perlinGradients[rand() % 16]; + + for(int y = 0; y < N; y++) + for(int x = 0; x < N; x++) { + num fx = num(x) / PERLIN_CELL_SIZE; + num fy = num(y) / PERLIN_CELL_SIZE; + + int ix = (int)fx; + int iy = (int)fy; + + num d0 = dot(grid, GRID_N, ix, iy, fx, fy); + num d1 = dot(grid, GRID_N, ix+1, iy, fx, fy); + + num d2 = dot(grid, GRID_N, ix, iy+1, fx, fy); + num d3 = dot(grid, GRID_N, ix+1, iy+1, fx, fy); + + num rx = smooth(fx - num(ix)); + num ry = smooth(fy - num(iy)); + + num v = lerp(lerp(d0, d1, rx), lerp(d2, d3, rx), ry); + noise[y * N + x] = 128 + (int)(v * num(127)); + } + + delete[] grid; + return noise; +} + +#include +void perlinTest(void) +{ + uint8_t *noise; + int time = prof_exec({ noise = perlinNoise(128, 16); }); + dclear(C_RED); + + if(noise) + for(int y = 0; y < 128; y++) + for(int x = 0; x < 128; x++) { + int gray = noise[y * 128 + x] >> 3; + dpixel(x, y, C_RGB(gray, gray, gray)); + } + dprint(2, DHEIGHT-20, C_WHITE, "time = %d ms", time / 1000); + + dupdate(); + getkey(); +} + +// - + int main(void) { prof_init(); + srand(0xc0ffee); + + perlinTest(); World *world = Nooncraft::mkWorld(128, 128); if(!world) return 0; - srand(0xc0ffee); - renderClear(); renderText(0, 0, "GENERATING WORLD..."); renderUpdate(); diff --git a/worldgen/tests.py b/worldgen/tests.py index 1a9006c..6c39368 100755 --- a/worldgen/tests.py +++ b/worldgen/tests.py @@ -2,12 +2,13 @@ from PIL import Image import random +import math WIDTH = 128 HEIGHT = 128 BIOMES = 16 -PERLIN_CELL_SIZE = 8 +PERLIN_CELL_SIZE = 32 # Voronoi diagram by jump flooding def voronoi(seeds): @@ -38,7 +39,7 @@ def voronoi(seeds): return world -# Generate a biome center map but avoid the Voronoi algorithm +# --== Generate a biome center map with the jump-flood algorithm ==-- def random_color(): r = random.randint(0, 256) @@ -64,4 +65,59 @@ def show_biomes(biomes): img.show() -show_biomes(biomes) +# show_biomes(biomes) + +# --== Generate some Perlin noise ==-- + +pregradients = [] +for i in range(16): + alpha = 2 * math.pi * i / 16 + pregradients.append((math.cos(alpha), math.sin(alpha))) + +def random_grid(N): + N = WIDTH // PERLIN_CELL_SIZE + 1 + return [[random.choice(pregradients) for x in range(N)] for y in range(N)] + +def smooth(t): + return t * t * t * (t * (6 * t - 15) + 10) + +def interp(x, y, t): + return y * t + x * (1.0 - t) + +def dot(grid, ix, iy, fx, fy): + gx, gy = grid[iy][ix] + return (fx - ix) * gx + (fy - iy) * gy + +def perlin_at(grid, fx, fy): + ix = int(fx) + iy = int(fy) + + d0 = dot(grid, ix, iy, fx, fy) + d1 = dot(grid, ix+1, iy, fx, fy) + + d2 = dot(grid, ix, iy+1, fx, fy) + d3 = dot(grid, ix+1, iy+1, fx, fy) + + rx = smooth(fx - ix) + ry = smooth(fy - iy) + + return interp(interp(d0, d1, rx), interp(d2, d3, rx), ry) + +PERLIN_N = WIDTH // PERLIN_CELL_SIZE +grid = random_grid(PERLIN_N) + +def show_perlin(grid): + img = Image.new("RGBA", (WIDTH, HEIGHT)) + px = img.load() + + for y in range(HEIGHT): + for x in range(WIDTH): + fx, fy = x / PERLIN_CELL_SIZE, y / PERLIN_CELL_SIZE + noise = perlin_at(grid, fx, fy) + + gray = 128 + round(noise * 128) + px[x, y] = (gray, gray, gray) + + img.show() + +show_perlin(grid)