OverviewFilesCommitsIssuesDocsReleasesPeers

terrain-lookdev / dump_erosion_heightfield.cpp

106 lines · cpp ref:
// ============================================================================
// EROSION HEIGHTFIELD DUMPER — our generator's surface, as a raw grid, so the
// SAME spectrum probe can be run on it as on terrain-diffusion's output.
//
// WHY IT INCLUDES A TEST FILE
//
// The CPU wrapper (map_dc_erosion's terrain layers + erosion_node_eval + the
// detail layer + the y-mapping) is not reachable outside a running engine;
// test_erosion_parity.cpp already reconstructs it, with every constant
// annotated by file and line, and that reconstruction is GUARDED green by the
// parity oracle against the GPU. Copying it here would create a fifth copy of
// the thing #45 spent a session reducing to one, and it would drift silently.
//
// So this file includes that translation unit and calls its `cpu_wrapper` and
// `find_surface` directly. `main` is renamed out of the way. If the parity
// oracle's reconstruction changes, this dumper changes with it -- which is the
// property we want.
//
// Build (no engine, no godot-cpp, no scons, nothing into bin/):
//   g++ -O2 -std=c++17 -I ../proc-nodes/src/shared -I ../dc-terrain \
//       -o /tmp/dump_erosion dump_erosion_heightfield.cpp -lvulkan
//
// Run (from the dc-terrain root, so the default --inc path resolves):
//   /tmp/dump_erosion --n 512 --metres-per-px 30 --out /tmp/erosion.f32
//
// Output: N*N float32 little-endian surface heights in metres, row-major.
// A column with no surface writes NAN and is counted in the summary.
// ============================================================================
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>

#define main parity_main_unused
#include "tests/test_erosion_parity.cpp"
#undef main

int main(int argc, char** argv) {
	int   n   = 512;
	float mpp = 30.0f;      // metres per pixel — match the diffusion tile
	float ox  = 0.0f, oz = 0.0f;
	const char* out = "/tmp/erosion.f32";
	float voxel = -1.0f;    // default: band-limit to the sampling grid

	for (int i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "--n") && i + 1 < argc)              n   = atoi(argv[++i]);
		else if (!strcmp(argv[i], "--metres-per-px") && i+1<argc) mpp = (float)atof(argv[++i]);
		else if (!strcmp(argv[i], "--ox") && i + 1 < argc)        ox  = (float)atof(argv[++i]);
		else if (!strcmp(argv[i], "--oz") && i + 1 < argc)        oz  = (float)atof(argv[++i]);
		else if (!strcmp(argv[i], "--out") && i + 1 < argc)       out = argv[++i];
		else if (!strcmp(argv[i], "--voxel") && i + 1 < argc)   voxel = (float)atof(argv[++i]);
		else { fprintf(stderr, "unknown arg %s\n", argv[i]); return 2; }
	}

	// BAND-LIMIT THE GENERATOR TO THE SAMPLING GRID. The erosion filter runs
	// octaves down to the voxel size; sampling that on a 30 m grid would alias
	// fine octaves into low bins and corrupt exactly the spectrum we are
	// measuring. The filter already has Nyquist octave clamping keyed to
	// MAP_VOXEL_SIZE (erosion_filter.inc nyquist_octaves), so setting it to
	// 2*pixel makes the generator itself band-limited -- the same thing it does
	// at a coarse LOD. Override with --voxel to sample it unclamped on purpose.
	MAP_VOXEL_SIZE = (voxel > 0.0f) ? voxel : 2.0f * mpp;

	printf("erosion heightfield: %dx%d px @ %g m/px = %.2f km across\n",
	       n, n, mpp, n * mpp / 1000.0f);
	printf("  origin (%g, %g) m   voxel size %g m   detail scale %g\n",
	       ox, oz, MAP_VOXEL_SIZE, MAP_DETAIL_SCALE);

	std::vector<float> h((size_t)n * n);
	int misses = 0;
	double lo = 1e30, hi = -1e30, sum = 0.0;

	for (int j = 0; j < n; j++) {
		for (int i = 0; i < n; i++) {
			float x = ox + (float)i * mpp;
			float z = oz + (float)j * mpp;
			float y;
			if (find_surface(cpu_wrapper, x, z, y)) {
				h[(size_t)j * n + i] = y;
				if (y < lo) lo = y;
				if (y > hi) hi = y;
				sum += y;
			} else {
				h[(size_t)j * n + i] = NAN;
				misses++;
			}
		}
		if ((j & 63) == 0) { printf("\r  row %d/%d", j, n); fflush(stdout); }
	}
	printf("\r  done                \n");

	long hits = (long)n * n - misses;
	printf("  surface found in %ld of %d columns (%d misses)\n", hits, n * n, misses);
	if (hits > 0)
		printf("  height  min %.1f m  max %.1f m  mean %.1f m\n",
		       lo, hi, sum / (double)hits);

	FILE* f = fopen(out, "wb");
	if (!f) { perror("fopen"); return 1; }
	fwrite(h.data(), sizeof(float), h.size(), f);
	fclose(f);
	printf("  wrote %zu floats to %s\n", h.size(), out);
	return misses == n * n ? 1 : 0;
}