OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / test / test_density.cpp

246 lines · cpp ref:
// Standalone tests for DCDensity — no Godot dependencies.
// Build: g++ -std=c++17 -O2 -I src test/test_density.cpp src/dc_density.cpp -o test/test_density -lm

#include "dc_density.h"
#include "dc_noise.h"

#include <cmath>
#include <cstdio>
#include <cstdlib>

static int g_pass = 0;
static int g_fail = 0;

static void check(bool cond, const char* name, const char* detail = nullptr) {
    if (cond) {
        printf("  PASS  %s\n", name);
        g_pass++;
    } else {
        printf("  FAIL  %s", name);
        if (detail) printf("  (%s)", detail);
        printf("\n");
        g_fail++;
    }
}

// -------------------------------------------------------------------
// Noise sanity checks
// -------------------------------------------------------------------
static void test_noise_basic() {
    printf("\n--- SimplexNoise basic ---\n");

    SimplexNoise n(42);
    float v = n.noise3(1.5f, 2.7f, 3.3f);
    check(v >= -1.0f && v <= 1.0f, "noise3 in [-1,1]");

    // Deterministic: same seed + coords = same value
    SimplexNoise n2(42);
    float v2 = n2.noise3(1.5f, 2.7f, 3.3f);
    check(v == v2, "noise3 deterministic");

    // Different seed = different value (avoid integer coords where simplex = 0)
    SimplexNoise n3(99);
    float v3 = n3.noise3(1.5f, 2.7f, 3.3f);
    check(v != v3, "different seed different value");

    // fbm also in range
    float f = n.fbm3(5.0f, 5.0f, 5.0f, 3);
    check(f >= -1.5f && f <= 1.5f, "fbm3 in reasonable range");
}

// -------------------------------------------------------------------
// Ground plane test
// -------------------------------------------------------------------
static void test_ground_plane() {
    printf("\n--- Ground plane ---\n");

    DCDensity den;
    DensityConfig cfg;
    den.configure(cfg);

    // Deep below ground should be solid (positive)
    float below = den.evaluate(0.0f, -10.0f, 0.0f);
    check(below > 0.0f, "evaluate(0,-10,0) > 0 (solid below ground)");

    // High above should be air (negative)
    float above = den.evaluate(0.0f, 50.0f, 0.0f);
    check(above < 0.0f, "evaluate(0,50,0) < 0 (air above sky)");
}

// -------------------------------------------------------------------
// Zero noise: only ground bias remains
// -------------------------------------------------------------------
static void test_zero_noise() {
    printf("\n--- Zero noise (ground bias only) ---\n");

    DCDensity den;
    DensityConfig cfg;
    cfg.primary_strength = 0.0f;
    cfg.tunnel_strength = 0.0f;
    cfg.ridge_strength = 0.0f;
    cfg.warp_strength = 0.0f;
    cfg.detail_strength = 0.0f;
    cfg.floor_strength = 0.0f;
    cfg.solid_floor_depth = 1000.0f;  // disable height clamping
    cfg.sky_height = 1000.0f;
    cfg.flat_center_radius = 0.0f;    // disable flat spawn
    den.configure(cfg);

    float y_test = -5.0f;
    float expected = -y_test * cfg.ground_bias; // = 5 * 0.04 = 0.2
    float actual = den.evaluate(5.0f, y_test, 5.0f);
    float err = std::fabs(actual - expected);
    char buf[128];
    snprintf(buf, sizeof(buf), "expected %.4f, got %.4f, err %.6f", expected, actual, err);
    check(err < 0.001f, "zero noise = -y * ground_bias", buf);
}

// -------------------------------------------------------------------
// Height clamping
// -------------------------------------------------------------------
static void test_height_clamping() {
    printf("\n--- Height clamping ---\n");

    DCDensity den;
    DensityConfig cfg;
    den.configure(cfg);

    // Well below solid_floor_depth (default 30) => very positive
    float deep = den.evaluate(0.0f, -35.0f, 0.0f);
    check(deep > 0.5f, "evaluate(0,-35,0) >> 0 (below solid floor)");

    // Well above sky_height (default 25) => very negative
    float high = den.evaluate(0.0f, 30.0f, 0.0f);
    check(high < -0.5f, "evaluate(0,30,0) << 0 (above sky height)");
}

// -------------------------------------------------------------------
// Flat spawn area
// -------------------------------------------------------------------
static void test_flat_spawn() {
    printf("\n--- Flat spawn area ---\n");

    DCDensity den;
    DensityConfig cfg;
    den.configure(cfg);

    // At origin, y=0, the flat spawn forces d toward -y*0.8 = 0.
    float at_origin = den.evaluate(0.0f, 0.0f, 0.0f);
    char buf[128];
    snprintf(buf, sizeof(buf), "value = %.4f", at_origin);
    check(std::fabs(at_origin) < 0.5f, "evaluate(0,0,0) ≈ 0 (surface at spawn)", buf);

    // Slightly below origin should be positive (solid ground for spawn)
    float below_spawn = den.evaluate(0.0f, -2.0f, 0.0f);
    check(below_spawn > 0.0f, "evaluate(0,-2,0) > 0 (solid below spawn)");

    // Slightly above origin should be negative (air above spawn)
    float above_spawn = den.evaluate(0.0f, 2.0f, 0.0f);
    check(above_spawn < 0.0f, "evaluate(0,2,0) < 0 (air above spawn)");
}

// -------------------------------------------------------------------
// Tunnel carving: verify tunnels produce negative density somewhere
// -------------------------------------------------------------------
static void test_tunnel_carving() {
    printf("\n--- Tunnel carving ---\n");

    // With strong tunnels and weak everything else, we should find
    // negative values inside solid regions (carved tunnels).
    DCDensity den;
    DensityConfig cfg;
    cfg.tunnel_strength = 3.0f;   // extra strong tunnels
    cfg.tunnel_radius = 0.4f;     // wider tunnels
    cfg.primary_strength = 0.5f;  // weaker primary
    cfg.ground_bias = 0.1f;       // stronger ground to ensure baseline solid
    cfg.flat_center_radius = 0.0f; // disable flat spawn
    den.configure(cfg);

    // Sample a grid at y=-5 (underground) looking for carved regions
    int found_carved = 0;
    int total = 0;
    for (int xi = -20; xi <= 20; xi += 2) {
        for (int zi = -20; zi <= 20; zi += 2) {
            float v = den.evaluate(static_cast<float>(xi), -5.0f, static_cast<float>(zi));
            total++;
            if (v < 0.0f) found_carved++;
        }
    }
    char buf[128];
    snprintf(buf, sizeof(buf), "found %d carved of %d samples", found_carved, total);
    check(found_carved > 0, "tunnels carve negative regions underground", buf);
}

// -------------------------------------------------------------------
// Different seeds produce different values
// -------------------------------------------------------------------
static void test_seed_change() {
    printf("\n--- Seed change ---\n");

    DCDensity den1, den2;
    DensityConfig cfg1, cfg2;
    cfg1.seed = 42;
    cfg2.seed = 123;
    den1.configure(cfg1);
    den2.configure(cfg2);

    float v1 = den1.evaluate(10.0f, -3.0f, 7.0f);
    float v2 = den2.evaluate(10.0f, -3.0f, 7.0f);
    char buf[128];
    snprintf(buf, sizeof(buf), "seed42=%.4f, seed123=%.4f", v1, v2);
    check(std::fabs(v1 - v2) > 0.001f, "different seeds produce different values", buf);
}

// -------------------------------------------------------------------
// Voronoi cells are generated
// -------------------------------------------------------------------
static void test_voronoi_effect() {
    printf("\n--- Voronoi effect ---\n");

    // With floor_strength > 0, Voronoi should modify density
    DCDensity den_with, den_without;
    DensityConfig cfg_with, cfg_without;

    cfg_with.floor_strength = 0.7f;
    cfg_without.floor_strength = 0.0f;
    // Disable flat spawn to not mask the effect
    cfg_with.flat_center_radius = 0.0f;
    cfg_without.flat_center_radius = 0.0f;

    den_with.configure(cfg_with);
    den_without.configure(cfg_without);

    // Sample at a point where Voronoi floor effect should be active
    // (below some floor height inside a cell)
    int differ = 0;
    for (int xi = -20; xi <= 20; xi += 5) {
        for (int zi = -20; zi <= 20; zi += 5) {
            float v1 = den_with.evaluate(static_cast<float>(xi), -5.0f, static_cast<float>(zi));
            float v2 = den_without.evaluate(static_cast<float>(xi), -5.0f, static_cast<float>(zi));
            if (std::fabs(v1 - v2) > 0.01f) differ++;
        }
    }
    char buf[128];
    snprintf(buf, sizeof(buf), "%d samples differ", differ);
    check(differ > 0, "Voronoi floor_strength changes density", buf);
}

// -------------------------------------------------------------------
// main
// -------------------------------------------------------------------
int main() {
    printf("=== DCDensity Tests ===\n");

    test_noise_basic();
    test_ground_plane();
    test_zero_noise();
    test_height_clamping();
    test_flat_spawn();
    test_tunnel_carving();
    test_seed_change();
    test_voronoi_effect();

    printf("\n=== Results: %d passed, %d failed ===\n", g_pass, g_fail);
    return g_fail > 0 ? 1 : 0;
}