| … | … | @@ -0,0 +1,80 @@ |
| 1 | # terrain-lookdev | |
| 2 | ||
| 3 | Tooling for hyper-games #46 — comparing our erosion filter against | |
| 4 | terrain-diffusion, and looking at either while turning its knobs. | |
| 5 | ||
| 6 | Nothing here lives in a shared repo, and nothing here builds into any repo's | |
| 7 | `bin/`. Everything compiles standalone with `g++` into `/tmp`. | |
| 8 | ||
| 9 | ## The rule this tooling exists to obey | |
| 10 | ||
| 11 | > Before believing a check, name the artifact it examined, and confirm it is | |
| 12 | > the one that ships. — hyper-games #44 §7 | |
| 13 | ||
| 14 | So none of it reimplements the terrain. `erosion_render` compiles | |
| 15 | `proc-nodes/src/shared/erosion_filter.inc` off disk and gets its wrapper from | |
| 16 | `erosion_wrapper_glsl.h` — the same function `test_erosion_parity.cpp` calls — | |
| 17 | and it fills its parameters from `erosion_wrapper_params_from_map()` rather than | |
| 18 | copying constants. It hand-copied them once and was 7.3 km out within hours, | |
| 19 | because `MAP_FLOOR_Y` moved in dc-terrain `156b2e4` the same morning. | |
| 20 | ||
| 21 | ## Files | |
| 22 | ||
| 23 | | file | what | | |
| 24 | |---|---| | |
| 25 | | `erosion_render.cpp` | the map's surface as a heightfield, on the GPU. 0.94 s for 512² where the CPU dumper takes 13.5 min. **Run `--verify` before believing any picture it makes.** | | |
| 26 | | `dump_erosion_heightfield.cpp` | the CPU equivalent. Slow, and kept because it goes through a different code path — it is the cross-check, not the workhorse. | | |
| 27 | | `spectrum_probe.py` | radially-averaged power spectrum. Self-tests first and refuses to print a terrain number until they pass. | | |
| 28 | | `compare_bands.py` | per-octave β for both generators on the same axes. #46 §4.3. | | |
| 29 | | `server.py` + `index.html` | the lookdev app: 2D and 3D, both generators side by side at matched scale. | | |
| 30 | ||
| 31 | ## Build and run | |
| 32 | ||
| 33 | ```bash | |
| 34 | # renderer — from the dc-terrain root, so the default --inc resolves | |
| 35 | cd ../dc-terrain | |
| 36 | g++ -O2 -std=c++17 -I ../proc-nodes/src/shared -I . \ | |
| 37 | -o /tmp/erosion_render ../terrain-lookdev/erosion_render.cpp -lvulkan | |
| 38 | /tmp/erosion_render --verify # MUST pass. 9 columns, worst delta < 0.01 m | |
| 39 | ||
| 40 | # the diffusion service (leave running) | |
| 41 | cd ../terrain-diffusion | |
| 42 | .venv/bin/python -m terrain_diffusion.inference.api \ | |
| 43 | xandergos/terrain-diffusion-30m --seed 1234 --no-compile \ | |
| 44 | --host 127.0.0.1 --port 8817 | |
| 45 | ||
| 46 | # the app | |
| 47 | .venv/bin/python ../terrain-lookdev/server.py --port 8818 | |
| 48 | ``` | |
| 49 | ||
| 50 | Then <http://127.0.0.1:8818>. | |
| 51 | ||
| 52 | Two traps in the diffusion service, both cost time once: `python -m | |
| 53 | terrain_diffusion` is broken — its `__main__` imports the training module, which | |
| 54 | imports `wandb` — so invoke `terrain_diffusion.inference.api` directly; and it | |
| 55 | blocks on an interactive prompt for WorldClim base data, so put | |
| 56 | `wc2.1_10m_bio_{1,4,12,15}.tif` in `terrain-diffusion/data/global/` first. | |
| 57 | ||
| 58 | ## Using it | |
| 59 | ||
| 60 | Both panels are shaded by the same code, so a difference on screen is a | |
| 61 | difference in the terrain rather than in the renderer. The diffusion panel is | |
| 62 | always at its native 30 m/px: that API's `scale` knob is bilinear (#46 §4.1) and | |
| 63 | would only blur. | |
| 64 | ||
| 65 | - **2D / 3D** tabs. 3D is a different view of the same heightfield from the same | |
| 66 | endpoint, so the tabs cannot disagree about the terrain. | |
| 67 | - Vertical exaggeration in 3D is an explicit multiplier on the true | |
| 68 | height-to-width ratio, never an auto-fit — an auto-fit would flatter whichever | |
| 69 | generator has less relief. | |
| 70 | - Any control can be set from the URL (`?mode=3d&mpp=30&en_gain=0.4&…`), which is | |
| 71 | how the views get linked into an issue. | |
| 72 | - `voxel_size` band-limits the generator the way a coarse LOD does. At 30 m/px | |
| 73 | leave it near 60, or you are looking at aliasing. | |
| 74 | ||
| 75 | ## What it was built to answer | |
| 76 | ||
| 77 | #46 §4 measured that the two generators differ in roughness — diffusion's β | |
| 78 | steepens 4.18 → 5.10 across the octaves where ours is flat at 3.58 → 3.78 — and | |
| 79 | said nothing about which is better to look at. That is aesthetic, and this is the | |
| 80 | only instrument that can settle it. |
| … | … | @@ -0,0 +1,88 @@ |
| 1 | #!/usr/bin/env python3 | |
| 2 | """ | |
| 3 | BAND COMPARISON — terrain-diffusion's spectrum against our erosion filter's, | |
| 4 | on the same physical axes. | |
| 5 | ||
| 6 | hyper-games #46 §3, probe B. Both fields are sampled over the same physical | |
| 7 | width at the same metres-per-pixel, so a given radial bin k is the same | |
| 8 | wavelength in both and the two spectra can be read against each other directly. | |
| 9 | ||
| 10 | The instrument is the same one, and its self-test runs first. | |
| 11 | """ | |
| 12 | import argparse, sys | |
| 13 | import numpy as np | |
| 14 | sys.path.insert(0, '/storage/projects/code/godot/terrain-lookdev') | |
| 15 | from spectrum_probe import fetch_tile, radial_power_spectrum, selftest | |
| 16 | ||
| 17 | ||
| 18 | def octave_table(name, field, mpp, kmin, kmax): | |
| 19 | n = field.shape[0] | |
| 20 | width = n * mpp | |
| 21 | k, p = radial_power_spectrum(field) | |
| 22 | rows = [] | |
| 23 | lo = kmin | |
| 24 | while lo * 2 <= k.max(): | |
| 25 | hi = min(lo * 2, int(k.max())) | |
| 26 | m = (k > lo) & (k <= hi) & (p > 0) | |
| 27 | if m.sum() >= 3: | |
| 28 | # local power-law slope inside this octave: the honest measure of | |
| 29 | # "how rough is it HERE", with no extrapolation from a lower band. | |
| 30 | s, _ = np.polyfit(np.log10(k[m]), np.log10(p[m]), 1) | |
| 31 | rows.append((width / hi, width / lo, -s, m.sum())) | |
| 32 | lo = hi | |
| 33 | return k, p, rows | |
| 34 | ||
| 35 | ||
| 36 | def main(): | |
| 37 | ap = argparse.ArgumentParser() | |
| 38 | ap.add_argument("--url", default="http://127.0.0.1:8817") | |
| 39 | ap.add_argument("--erosion", default="/tmp/erosion-512.f32") | |
| 40 | ap.add_argument("--n", type=int, default=512) | |
| 41 | ap.add_argument("--mpp", type=float, default=30.0) | |
| 42 | ap.add_argument("--i1", type=int, default=0) | |
| 43 | ap.add_argument("--j1", type=int, default=2048) | |
| 44 | args = ap.parse_args() | |
| 45 | ||
| 46 | print("INSTRUMENT SELF-TEST") | |
| 47 | if not selftest(): | |
| 48 | print(" FAILED — no number below is meaningful.") | |
| 49 | return 2 | |
| 50 | print(" all pass\n") | |
| 51 | ||
| 52 | n, mpp = args.n, args.mpp | |
| 53 | print(f"Both fields: {n}x{n} px @ {mpp:g} m/px = {n*mpp/1000:.2f} km across, " | |
| 54 | f"Nyquist lambda = {2*mpp:g} m\n") | |
| 55 | ||
| 56 | ero = np.fromfile(args.erosion, dtype="<f4").reshape(n, n).astype(np.float64) | |
| 57 | nan = int(np.isnan(ero).sum()) | |
| 58 | if nan: | |
| 59 | print(f" NOTE: {nan} NaN cells in the erosion field; filled with the mean.") | |
| 60 | ero = np.where(np.isnan(ero), np.nanmean(ero), ero) | |
| 61 | ||
| 62 | dif, _ = fetch_tile(args.url, args.i1, args.j1, n, 1) | |
| 63 | ||
| 64 | fields = [("terrain-diffusion (30m model, seed 1234)", dif), | |
| 65 | ("our erosion filter (map_dc_erosion, band-limited)", ero)] | |
| 66 | ||
| 67 | for label, f in fields: | |
| 68 | k, p, rows = octave_table(label, f, mpp, 4, n // 2) | |
| 69 | print(f"{label}") | |
| 70 | print(f" relief: min {f.min():.0f} m max {f.max():.0f} m " | |
| 71 | f"sd {f.std():.0f} m") | |
| 72 | print(f" {'octave (wavelength)':>26} {'local beta':>11} {'bins':>6}") | |
| 73 | for w_lo, w_hi, beta, nb in rows: | |
| 74 | unit = (f"{w_lo/1000:5.2f}-{w_hi/1000:5.2f} km" if w_hi >= 1000 | |
| 75 | else f"{w_lo:6.0f}-{w_hi:6.0f} m ") | |
| 76 | print(f" {unit:>26} {beta:11.2f} {nb:6d}") | |
| 77 | print() | |
| 78 | ||
| 79 | print("READING IT: beta is the local 2-D radial power-law exponent inside") | |
| 80 | print("that octave alone -- no extrapolation. Higher beta = smoother, i.e.") | |
| 81 | print("less energy at the fine end of the octave than the coarse end.") | |
| 82 | print("A self-similar generator holds beta constant down the column.") | |
| 83 | print("Subtract 1 for the radially-INTEGRATED convention some papers use.") | |
| 84 | return 0 | |
| 85 | ||
| 86 | ||
| 87 | if __name__ == "__main__": | |
| 88 | sys.exit(main()) |
| … | … | @@ -0,0 +1,105 @@ |
| 1 | // ============================================================================ | |
| 2 | // EROSION HEIGHTFIELD DUMPER — our generator's surface, as a raw grid, so the | |
| 3 | // SAME spectrum probe can be run on it as on terrain-diffusion's output. | |
| 4 | // | |
| 5 | // WHY IT INCLUDES A TEST FILE | |
| 6 | // | |
| 7 | // The CPU wrapper (map_dc_erosion's terrain layers + erosion_node_eval + the | |
| 8 | // detail layer + the y-mapping) is not reachable outside a running engine; | |
| 9 | // test_erosion_parity.cpp already reconstructs it, with every constant | |
| 10 | // annotated by file and line, and that reconstruction is GUARDED green by the | |
| 11 | // parity oracle against the GPU. Copying it here would create a fifth copy of | |
| 12 | // the thing #45 spent a session reducing to one, and it would drift silently. | |
| 13 | // | |
| 14 | // So this file includes that translation unit and calls its `cpu_wrapper` and | |
| 15 | // `find_surface` directly. `main` is renamed out of the way. If the parity | |
| 16 | // oracle's reconstruction changes, this dumper changes with it -- which is the | |
| 17 | // property we want. | |
| 18 | // | |
| 19 | // Build (no engine, no godot-cpp, no scons, nothing into bin/): | |
| 20 | // g++ -O2 -std=c++17 -I ../proc-nodes/src/shared -I ../dc-terrain \ | |
| 21 | // -o /tmp/dump_erosion dump_erosion_heightfield.cpp -lvulkan | |
| 22 | // | |
| 23 | // Run (from the dc-terrain root, so the default --inc path resolves): | |
| 24 | // /tmp/dump_erosion --n 512 --metres-per-px 30 --out /tmp/erosion.f32 | |
| 25 | // | |
| 26 | // Output: N*N float32 little-endian surface heights in metres, row-major. | |
| 27 | // A column with no surface writes NAN and is counted in the summary. | |
| 28 | // ============================================================================ | |
| 29 | #include <cstdio> | |
| 30 | #include <cstdlib> | |
| 31 | #include <cstring> | |
| 32 | #include <cmath> | |
| 33 | #include <vector> | |
| 34 | ||
| 35 | #define main parity_main_unused | |
| 36 | #include "tests/test_erosion_parity.cpp" | |
| 37 | #undef main | |
| 38 | ||
| 39 | int main(int argc, char** argv) { | |
| 40 | int n = 512; | |
| 41 | float mpp = 30.0f; // metres per pixel — match the diffusion tile | |
| 42 | float ox = 0.0f, oz = 0.0f; | |
| 43 | const char* out = "/tmp/erosion.f32"; | |
| 44 | float voxel = -1.0f; // default: band-limit to the sampling grid | |
| 45 | ||
| 46 | for (int i = 1; i < argc; i++) { | |
| 47 | if (!strcmp(argv[i], "--n") && i + 1 < argc) n = atoi(argv[++i]); | |
| 48 | else if (!strcmp(argv[i], "--metres-per-px") && i+1<argc) mpp = (float)atof(argv[++i]); | |
| 49 | else if (!strcmp(argv[i], "--ox") && i + 1 < argc) ox = (float)atof(argv[++i]); | |
| 50 | else if (!strcmp(argv[i], "--oz") && i + 1 < argc) oz = (float)atof(argv[++i]); | |
| 51 | else if (!strcmp(argv[i], "--out") && i + 1 < argc) out = argv[++i]; | |
| 52 | else if (!strcmp(argv[i], "--voxel") && i + 1 < argc) voxel = (float)atof(argv[++i]); | |
| 53 | else { fprintf(stderr, "unknown arg %s\n", argv[i]); return 2; } | |
| 54 | } | |
| 55 | ||
| 56 | // BAND-LIMIT THE GENERATOR TO THE SAMPLING GRID. The erosion filter runs | |
| 57 | // octaves down to the voxel size; sampling that on a 30 m grid would alias | |
| 58 | // fine octaves into low bins and corrupt exactly the spectrum we are | |
| 59 | // measuring. The filter already has Nyquist octave clamping keyed to | |
| 60 | // MAP_VOXEL_SIZE (erosion_filter.inc nyquist_octaves), so setting it to | |
| 61 | // 2*pixel makes the generator itself band-limited -- the same thing it does | |
| 62 | // at a coarse LOD. Override with --voxel to sample it unclamped on purpose. | |
| 63 | MAP_VOXEL_SIZE = (voxel > 0.0f) ? voxel : 2.0f * mpp; | |
| 64 | ||
| 65 | printf("erosion heightfield: %dx%d px @ %g m/px = %.2f km across\n", | |
| 66 | n, n, mpp, n * mpp / 1000.0f); | |
| 67 | printf(" origin (%g, %g) m voxel size %g m detail scale %g\n", | |
| 68 | ox, oz, MAP_VOXEL_SIZE, MAP_DETAIL_SCALE); | |
| 69 | ||
| 70 | std::vector<float> h((size_t)n * n); | |
| 71 | int misses = 0; | |
| 72 | double lo = 1e30, hi = -1e30, sum = 0.0; | |
| 73 | ||
| 74 | for (int j = 0; j < n; j++) { | |
| 75 | for (int i = 0; i < n; i++) { | |
| 76 | float x = ox + (float)i * mpp; | |
| 77 | float z = oz + (float)j * mpp; | |
| 78 | float y; | |
| 79 | if (find_surface(cpu_wrapper, x, z, y)) { | |
| 80 | h[(size_t)j * n + i] = y; | |
| 81 | if (y < lo) lo = y; | |
| 82 | if (y > hi) hi = y; | |
| 83 | sum += y; | |
| 84 | } else { | |
| 85 | h[(size_t)j * n + i] = NAN; | |
| 86 | misses++; | |
| 87 | } | |
| 88 | } | |
| 89 | if ((j & 63) == 0) { printf("\r row %d/%d", j, n); fflush(stdout); } | |
| 90 | } | |
| 91 | printf("\r done \n"); | |
| 92 | ||
| 93 | long hits = (long)n * n - misses; | |
| 94 | printf(" surface found in %ld of %d columns (%d misses)\n", hits, n * n, misses); | |
| 95 | if (hits > 0) | |
| 96 | printf(" height min %.1f m max %.1f m mean %.1f m\n", | |
| 97 | lo, hi, sum / (double)hits); | |
| 98 | ||
| 99 | FILE* f = fopen(out, "wb"); | |
| 100 | if (!f) { perror("fopen"); return 1; } | |
| 101 | fwrite(h.data(), sizeof(float), h.size(), f); | |
| 102 | fclose(f); | |
| 103 | printf(" wrote %zu floats to %s\n", h.size(), out); | |
| 104 | return misses == n * n ? 1 : 0; | |
| 105 | } |
| … | … | @@ -0,0 +1,347 @@ |
| 1 | // ============================================================================ | |
| 2 | // EROSION RENDERER — the map's surface as a heightfield, on the GPU, from the | |
| 3 | // same erosion_filter.inc and the same wrapper text the parity oracle guards. | |
| 4 | // | |
| 5 | // WHY IT EXISTS | |
| 6 | // | |
| 7 | // The CPU dumper takes 13.5 minutes for a 512^2 tile. That is fine for a | |
| 8 | // measurement and useless for looking at something while turning a knob. This | |
| 9 | // does the same job on the GPU. | |
| 10 | // | |
| 11 | // WHAT IT SHARES, AND WHAT IT DOES NOT | |
| 12 | // | |
| 13 | // SHARED, and this is the point: | |
| 14 | // - erosion_filter.inc, read off disk at run time and compiled by glslc. | |
| 15 | // - the map WRAPPER, from proc-nodes/src/shared/erosion_wrapper_glsl.h -- | |
| 16 | // the same function test_erosion_parity.cpp calls, which is why adding | |
| 17 | // this second GPU consumer did not create a third copy of the wrapper. | |
| 18 | // - the Vulkan setup, by including the oracle's translation unit. | |
| 19 | // | |
| 20 | // NOT SHARED: | |
| 21 | // - the root finder. find_surface() is C++ in the oracle; the scan-and- | |
| 22 | // bisect below is GLSL, because doing it per column inside the shader is | |
| 23 | // the whole speed win. Two implementations of a generic root finder is a | |
| 24 | // real duplication, but a much smaller one than the wrapper, and it | |
| 25 | // fails LOUDLY: --verify checks this renderer against the oracle's own | |
| 26 | // published surface heights, and those came from the C++ finder. | |
| 27 | // | |
| 28 | // VERIFY BEFORE BELIEVING A PICTURE (#45 section 5 -- instruments are layers) | |
| 29 | // | |
| 30 | // erosion_render --verify | |
| 31 | // | |
| 32 | // evaluates the nine columns the oracle prints and compares. The oracle's | |
| 33 | // values at MAP defaults are, from a green run: | |
| 34 | // (-10000,-10000) 5430.60 (0,0) -1059.81 (10000,10000) -5038.45 | |
| 35 | // A picture from a renderer that has not passed --verify is decoration. | |
| 36 | // | |
| 37 | // Build (no engine, no godot-cpp, no scons, nothing into bin/): | |
| 38 | // cd dc-terrain | |
| 39 | // g++ -O2 -std=c++17 -I ../proc-nodes/src/shared -I . \ | |
| 40 | // -o /tmp/erosion_render ../terrain-lookdev/erosion_render.cpp -lvulkan | |
| 41 | // | |
| 42 | // Run (from the dc-terrain root, so the default --inc path resolves): | |
| 43 | // /tmp/erosion_render --n 512 --mpp 30 --out /tmp/h.f32 | |
| 44 | // /tmp/erosion_render --verify | |
| 45 | // | |
| 46 | // Output: N*N float32 LE surface heights in metres, row-major (z outer, x | |
| 47 | // inner). Columns with no surface are -3.0e38. | |
| 48 | // ============================================================================ | |
| 49 | #include <cstdio> | |
| 50 | #include <cstdlib> | |
| 51 | #include <cstring> | |
| 52 | #include <cmath> | |
| 53 | #include <string> | |
| 54 | #include <vector> | |
| 55 | ||
| 56 | #define main parity_main_unused | |
| 57 | #include "tests/test_erosion_parity.cpp" | |
| 58 | #undef main | |
| 59 | ||
| 60 | static const float NO_SURFACE = -3.0e38f; | |
| 61 | ||
| 62 | struct PushC { | |
| 63 | uint32_t w, h; | |
| 64 | float ox, oz, mpp; | |
| 65 | uint32_t row0; | |
| 66 | }; | |
| 67 | ||
| 68 | // The root finder, in GLSL. Deliberately the same shape as find_surface() in | |
| 69 | // test_erosion_parity.cpp: a top-down scan for a sign change, then bisection | |
| 70 | // inside the bracket that found it. Same STEPS and same bisection count, so | |
| 71 | // --verify is comparing the FIELD and not two different search policies. | |
| 72 | static std::string build_render_shader(const std::string& inc, | |
| 73 | const ErosionWrapperParams& wp, | |
| 74 | int steps, int bisect) { | |
| 75 | std::string s = | |
| 76 | "#version 450\n" | |
| 77 | "layout(local_size_x = 8, local_size_y = 8) in;\n" | |
| 78 | "layout(set=0, binding=0) writeonly buffer OutH { float heights[]; };\n" | |
| 79 | "layout(push_constant) uniform PC {\n" | |
| 80 | " uint w; uint h; float ox; float oz; float mpp; uint row0;\n" | |
| 81 | "} pc;\n" | |
| 82 | "\n"; | |
| 83 | s += inc; | |
| 84 | s += "\n"; | |
| 85 | s += erosion_wrapper_glsl(wp); | |
| 86 | ||
| 87 | char tail[2048]; | |
| 88 | snprintf(tail, sizeof(tail), | |
| 89 | "const int SCAN_STEPS = %d;\n" | |
| 90 | "const int BISECT = %d;\n" | |
| 91 | "void main() {\n" | |
| 92 | " uint gx = gl_GlobalInvocationID.x;\n" | |
| 93 | " uint gy = gl_GlobalInvocationID.y;\n" | |
| 94 | " if (gx >= pc.w || gy >= pc.h) return;\n" | |
| 95 | " float x = pc.ox + float(gx) * pc.mpp;\n" | |
| 96 | " float z = pc.oz + float(pc.row0 + gy) * pc.mpp;\n" | |
| 97 | " float lo = M_FLOOR_Y + 1.0;\n" | |
| 98 | " float hi = M_CEIL_Y - 1.0;\n" | |
| 99 | " float prev_y = hi;\n" | |
| 100 | " float prev_d = gpu_wrapper(x, hi, z);\n" | |
| 101 | " float found = %.9g;\n" | |
| 102 | " for (int i = 1; i <= SCAN_STEPS; i++) {\n" | |
| 103 | " float y = hi + (lo - hi) * float(i) / float(SCAN_STEPS);\n" | |
| 104 | " float d = gpu_wrapper(x, y, z);\n" | |
| 105 | " if ((prev_d < 0.0) != (d < 0.0)) {\n" | |
| 106 | " float a = y, b = prev_y;\n" | |
| 107 | " for (int k = 0; k < BISECT; k++) {\n" | |
| 108 | " float m = 0.5 * (a + b);\n" | |
| 109 | " if ((gpu_wrapper(x, m, z) < 0.0) == (prev_d < 0.0)) b = m;\n" | |
| 110 | " else a = m;\n" | |
| 111 | " }\n" | |
| 112 | " found = 0.5 * (a + b);\n" | |
| 113 | " break;\n" | |
| 114 | " }\n" | |
| 115 | " prev_y = y; prev_d = d;\n" | |
| 116 | " }\n" | |
| 117 | " heights[gy * pc.w + gx] = found;\n" | |
| 118 | "}\n", | |
| 119 | steps, bisect, (double)NO_SURFACE); | |
| 120 | s += tail; | |
| 121 | return s; | |
| 122 | } | |
| 123 | ||
| 124 | // --------------------------------------------------------------------------- | |
| 125 | struct Renderer { | |
| 126 | Ctx c{}; | |
| 127 | VkShaderModule mod = VK_NULL_HANDLE; | |
| 128 | VkDescriptorSetLayout dsl = VK_NULL_HANDLE; | |
| 129 | VkPipelineLayout playout = VK_NULL_HANDLE; | |
| 130 | VkPipeline pipe = VK_NULL_HANDLE; | |
| 131 | VkDescriptorPool dpool = VK_NULL_HANDLE; | |
| 132 | VkDescriptorSet dset = VK_NULL_HANDLE; | |
| 133 | ||
| 134 | void build(const std::string& src) { | |
| 135 | init_vulkan(c); | |
| 136 | std::vector<uint32_t> spv = compile_glsl(src, "erosion render"); | |
| 137 | ||
| 138 | VkShaderModuleCreateInfo smi{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; | |
| 139 | smi.codeSize = spv.size() * 4; smi.pCode = spv.data(); | |
| 140 | VK(vkCreateShaderModule(c.dev, &smi, nullptr, &mod)); | |
| 141 | ||
| 142 | VkDescriptorSetLayoutBinding bind{}; | |
| 143 | bind.binding = 0; | |
| 144 | bind.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; | |
| 145 | bind.descriptorCount = 1; | |
| 146 | bind.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; | |
| 147 | VkDescriptorSetLayoutCreateInfo dli{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; | |
| 148 | dli.bindingCount = 1; dli.pBindings = &bind; | |
| 149 | VK(vkCreateDescriptorSetLayout(c.dev, &dli, nullptr, &dsl)); | |
| 150 | ||
| 151 | VkPushConstantRange pcr{VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(PushC)}; | |
| 152 | VkPipelineLayoutCreateInfo pli{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; | |
| 153 | pli.setLayoutCount = 1; pli.pSetLayouts = &dsl; | |
| 154 | pli.pushConstantRangeCount = 1; pli.pPushConstantRanges = &pcr; | |
| 155 | VK(vkCreatePipelineLayout(c.dev, &pli, nullptr, &playout)); | |
| 156 | ||
| 157 | VkComputePipelineCreateInfo cpi{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; | |
| 158 | cpi.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; | |
| 159 | cpi.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; | |
| 160 | cpi.stage.module = mod; cpi.stage.pName = "main"; | |
| 161 | cpi.layout = playout; | |
| 162 | VK(vkCreateComputePipelines(c.dev, VK_NULL_HANDLE, 1, &cpi, nullptr, &pipe)); | |
| 163 | ||
| 164 | VkDescriptorPoolSize ps{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}; | |
| 165 | VkDescriptorPoolCreateInfo dpi{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; | |
| 166 | dpi.maxSets = 1; dpi.poolSizeCount = 1; dpi.pPoolSizes = &ps; | |
| 167 | VK(vkCreateDescriptorPool(c.dev, &dpi, nullptr, &dpool)); | |
| 168 | VkDescriptorSetAllocateInfo dai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; | |
| 169 | dai.descriptorPool = dpool; dai.descriptorSetCount = 1; dai.pSetLayouts = &dsl; | |
| 170 | VK(vkAllocateDescriptorSets(c.dev, &dai, &dset)); | |
| 171 | } | |
| 172 | ||
| 173 | // One band of rows per submit. A single dispatch over a whole tile is | |
| 174 | // hundreds of millions of erosion evaluations, and a compute submit that | |
| 175 | // long on the DISPLAY gpu trips the driver's watchdog and takes the | |
| 176 | // desktop with it. Banding keeps each submit short. | |
| 177 | void render(float ox, float oz, uint32_t w, uint32_t h, float mpp, | |
| 178 | uint32_t band, std::vector<float>& out) { | |
| 179 | out.assign((size_t)w * h, NO_SURFACE); | |
| 180 | Buf buf = make_host_buf(c, (size_t)w * band * sizeof(float)); | |
| 181 | ||
| 182 | VkDescriptorBufferInfo bi{buf.buf, 0, VK_WHOLE_SIZE}; | |
| 183 | VkWriteDescriptorSet wr{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; | |
| 184 | wr.dstSet = dset; wr.dstBinding = 0; wr.descriptorCount = 1; | |
| 185 | wr.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; | |
| 186 | wr.pBufferInfo = &bi; | |
| 187 | vkUpdateDescriptorSets(c.dev, 1, &wr, 0, nullptr); | |
| 188 | ||
| 189 | for (uint32_t row0 = 0; row0 < h; row0 += band) { | |
| 190 | uint32_t rows = (row0 + band <= h) ? band : (h - row0); | |
| 191 | PushC pc{w, rows, ox, oz, mpp, row0}; | |
| 192 | ||
| 193 | VkCommandBufferAllocateInfo cbi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; | |
| 194 | cbi.commandPool = c.cmdpool; cbi.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; | |
| 195 | cbi.commandBufferCount = 1; | |
| 196 | VkCommandBuffer cmd; | |
| 197 | VK(vkAllocateCommandBuffers(c.dev, &cbi, &cmd)); | |
| 198 | VkCommandBufferBeginInfo cbb{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; | |
| 199 | cbb.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; | |
| 200 | VK(vkBeginCommandBuffer(cmd, &cbb)); | |
| 201 | vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipe); | |
| 202 | vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, playout, | |
| 203 | 0, 1, &dset, 0, nullptr); | |
| 204 | vkCmdPushConstants(cmd, playout, VK_SHADER_STAGE_COMPUTE_BIT, 0, | |
| 205 | sizeof(PushC), &pc); | |
| 206 | vkCmdDispatch(cmd, (w + 7) / 8, (rows + 7) / 8, 1); | |
| 207 | VK(vkEndCommandBuffer(cmd)); | |
| 208 | VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO}; | |
| 209 | si.commandBufferCount = 1; si.pCommandBuffers = &cmd; | |
| 210 | VK(vkQueueSubmit(c.queue, 1, &si, VK_NULL_HANDLE)); | |
| 211 | VK(vkQueueWaitIdle(c.queue)); | |
| 212 | vkFreeCommandBuffers(c.dev, c.cmdpool, 1, &cmd); | |
| 213 | ||
| 214 | memcpy(out.data() + (size_t)row0 * w, buf.mapped, | |
| 215 | (size_t)rows * w * sizeof(float)); | |
| 216 | } | |
| 217 | } | |
| 218 | }; | |
| 219 | ||
| 220 | // --------------------------------------------------------------------------- | |
| 221 | static void set_param(ErosionWrapperParams& p, const char* k, const char* v) { | |
| 222 | float f = (float)atof(v); | |
| 223 | int i = atoi(v); | |
| 224 | if (!strcmp(k, "floor_y")) p.floor_y = f; | |
| 225 | else if (!strcmp(k, "ceiling_y")) p.ceiling_y = f; | |
| 226 | else if (!strcmp(k, "terrain_height")) p.terrain_height = f; | |
| 227 | else if (!strcmp(k, "mountain_scale")) p.mountain_scale = f; | |
| 228 | else if (!strcmp(k, "mountain_height")) p.mountain_height = f; | |
| 229 | else if (!strcmp(k, "mountain_gain")) p.mountain_gain = f; | |
| 230 | else if (!strcmp(k, "mountain_variance")) p.mountain_variance = f; | |
| 231 | else if (!strcmp(k, "mountain_octaves")) p.mountain_octaves = i; | |
| 232 | else if (!strcmp(k, "relief_scale")) p.relief_scale = f; | |
| 233 | else if (!strcmp(k, "relief_floor")) p.relief_floor = f; | |
| 234 | else if (!strcmp(k, "detail_scale")) p.detail_scale = f; | |
| 235 | else if (!strcmp(k, "detail_height")) p.detail_height = f; | |
| 236 | else if (!strcmp(k, "detail_gain")) p.detail_gain = f; | |
| 237 | else if (!strcmp(k, "detail_octaves")) p.detail_octaves = i; | |
| 238 | else if (!strcmp(k, "noise_lacunarity")) p.noise_lacunarity = f; | |
| 239 | else if (!strcmp(k, "voxel_size")) p.voxel_size = f; | |
| 240 | else if (!strcmp(k, "gn_scale")) p.gn_scale = f; | |
| 241 | else if (!strcmp(k, "gn_gain")) p.gn_gain = f; | |
| 242 | else if (!strcmp(k, "gn_detail")) p.gn_detail = i; | |
| 243 | else if (!strcmp(k, "en_height_scale")) p.en_height_scale = f; | |
| 244 | else if (!strcmp(k, "en_strength")) p.en_strength = f; | |
| 245 | else if (!strcmp(k, "en_cell_scale")) p.en_cell_scale = f; | |
| 246 | else if (!strcmp(k, "en_gain")) p.en_gain = f; | |
| 247 | else if (!strcmp(k, "en_octaves")) p.en_octaves = i; | |
| 248 | else if (!strcmp(k, "en_erosion_scale")) p.en_erosion_scale = f; | |
| 249 | else { fprintf(stderr, "unknown parameter '%s'\n", k); exit(2); } | |
| 250 | } | |
| 251 | ||
| 252 | int main(int argc, char** argv) { | |
| 253 | const char* inc_path = "../proc-nodes/src/shared/erosion_filter.inc"; | |
| 254 | const char* out_path = "/tmp/h.f32"; | |
| 255 | int n = 512, steps = 512, bisect = 60, band = 32; | |
| 256 | float mpp = 30.0f, ox = 0.0f, oz = 0.0f; | |
| 257 | bool verify = false; | |
| 258 | ||
| 259 | // NOT ErosionWrapperParams{} -- the struct's own defaults are a convenience | |
| 260 | // and are not authoritative. The map's values come from the oracle's single | |
| 261 | // mapping, so a constant that moves there moves here in the same commit. | |
| 262 | // Hand-copying them instead is what put this renderer 7.3 km out on its | |
| 263 | // first run, and the --verify gate is the only reason that was noticed. | |
| 264 | ErosionWrapperParams wp = erosion_wrapper_params_from_map(); | |
| 265 | ||
| 266 | for (int i = 1; i < argc; i++) { | |
| 267 | const char* a = argv[i]; | |
| 268 | if (!strcmp(a, "--inc") && i+1 < argc) inc_path = argv[++i]; | |
| 269 | else if (!strcmp(a, "--out") && i+1 < argc) out_path = argv[++i]; | |
| 270 | else if (!strcmp(a, "--n") && i+1 < argc) n = atoi(argv[++i]); | |
| 271 | else if (!strcmp(a, "--mpp") && i+1 < argc) mpp = (float)atof(argv[++i]); | |
| 272 | else if (!strcmp(a, "--ox") && i+1 < argc) ox = (float)atof(argv[++i]); | |
| 273 | else if (!strcmp(a, "--oz") && i+1 < argc) oz = (float)atof(argv[++i]); | |
| 274 | else if (!strcmp(a, "--steps") && i+1 < argc) steps = atoi(argv[++i]); | |
| 275 | else if (!strcmp(a, "--bisect") && i+1 < argc) bisect = atoi(argv[++i]); | |
| 276 | else if (!strcmp(a, "--band") && i+1 < argc) band = atoi(argv[++i]); | |
| 277 | else if (!strcmp(a, "--verify")) verify = true; | |
| 278 | else if (!strcmp(a, "--set") && i+2 < argc) { set_param(wp, argv[i+1], argv[i+2]); i += 2; } | |
| 279 | else { fprintf(stderr, "unknown arg %s\n", a); return 2; } | |
| 280 | } | |
| 281 | ||
| 282 | std::string inc = read_file(inc_path); | |
| 283 | std::string src = build_render_shader(inc, wp, steps, bisect); | |
| 284 | ||
| 285 | Renderer r; | |
| 286 | r.build(src); | |
| 287 | ||
| 288 | if (verify) { | |
| 289 | // The oracle's own columns, at MAP defaults, from a green run. | |
| 290 | struct { float x, z, expect; } cols[] = { | |
| 291 | {-10000.0f, -10000.0f, 5430.60f}, | |
| 292 | {-10000.0f, 0.0f, 4179.92f}, | |
| 293 | {-10000.0f, 10000.0f, -353.49f}, | |
| 294 | { 0.0f, -10000.0f, 4299.36f}, | |
| 295 | { 0.0f, 0.0f, -1059.81f}, | |
| 296 | { 0.0f, 10000.0f, -5454.78f}, | |
| 297 | { 10000.0f, -10000.0f, 215.53f}, | |
| 298 | { 10000.0f, 0.0f, -4136.03f}, | |
| 299 | { 10000.0f, 10000.0f, -5038.45f}, | |
| 300 | }; | |
| 301 | printf("VERIFY against test_erosion_parity's published columns\n"); | |
| 302 | printf(" scan steps %d, bisect %d (the oracle uses 512 / 60)\n\n", steps, bisect); | |
| 303 | printf(" %11s %11s %13s %13s %12s\n", "x", "z", "oracle", "renderer", "delta"); | |
| 304 | double worst = 0.0; | |
| 305 | for (auto& col : cols) { | |
| 306 | std::vector<float> one; | |
| 307 | r.render(col.x, col.z, 1, 1, 1.0f, 1, one); | |
| 308 | double d = (double)one[0] - (double)col.expect; | |
| 309 | if (fabs(d) > worst) worst = fabs(d); | |
| 310 | printf(" %11.1f %11.1f %13.2f %13.2f %12.4f\n", | |
| 311 | col.x, col.z, col.expect, one[0], d); | |
| 312 | } | |
| 313 | printf("\n worst |delta| : %.4f m\n", worst); | |
| 314 | // The oracle prints 2 decimals, so agreement can only be shown to | |
| 315 | // about half a centimetre. Anything above that is a real difference. | |
| 316 | bool ok = worst < 0.01; | |
| 317 | printf(" VERDICT: %s\n", ok ? "renderer MATCHES the oracle." | |
| 318 | : "renderer DISAGREES with the oracle. Do not " | |
| 319 | "trust any picture it makes."); | |
| 320 | return ok ? 0 : 1; | |
| 321 | } | |
| 322 | ||
| 323 | printf("erosion render: %dx%d px @ %g m/px = %.2f km across\n", | |
| 324 | n, n, mpp, n * mpp / 1000.0f); | |
| 325 | printf(" origin (%g, %g) m voxel %g scan %d/%d band %d rows\n", | |
| 326 | ox, oz, wp.voxel_size, steps, bisect, band); | |
| 327 | ||
| 328 | std::vector<float> h; | |
| 329 | r.render(ox, oz, (uint32_t)n, (uint32_t)n, mpp, (uint32_t)band, h); | |
| 330 | ||
| 331 | long hits = 0; double lo = 1e30, hi = -1e30, sum = 0; | |
| 332 | for (float v : h) { | |
| 333 | if (v == NO_SURFACE) continue; | |
| 334 | hits++; sum += v; | |
| 335 | if (v < lo) lo = v; | |
| 336 | if (v > hi) hi = v; | |
| 337 | } | |
| 338 | printf(" surface found in %ld of %d columns\n", hits, n * n); | |
| 339 | if (hits) printf(" height min %.1f m max %.1f m mean %.1f m\n", lo, hi, sum / hits); | |
| 340 | ||
| 341 | FILE* f = fopen(out_path, "wb"); | |
| 342 | if (!f) { perror("fopen"); return 1; } | |
| 343 | fwrite(h.data(), sizeof(float), h.size(), f); | |
| 344 | fclose(f); | |
| 345 | printf(" wrote %zu floats to %s\n", h.size(), out_path); | |
| 346 | return hits > 0 ? 0 : 1; | |
| 347 | } |
| … | … | @@ -0,0 +1,480 @@ |
| 1 | <!doctype html> | |
| 2 | <meta charset="utf-8"> | |
| 3 | <title>Terrain Lookdev</title> | |
| 4 | <style> | |
| 5 | :root{ | |
| 6 | --bg:#12161a; --panel:#191f25; --edge:#28313a; --edge2:#3a4652; | |
| 7 | --ink:#e2e8e6; --dim:#8c9aa1; --accent:#74c2cd; --warn:#d7a45c; | |
| 8 | font-family:"IBM Plex Sans",system-ui,sans-serif; | |
| 9 | } | |
| 10 | *{box-sizing:border-box} | |
| 11 | body{margin:0;background:var(--bg);color:var(--ink);font-size:13px;height:100vh; | |
| 12 | display:grid;grid-template-columns:290px 1fr;overflow:hidden} | |
| 13 | h1{font-size:13px;margin:0 0 2px;letter-spacing:.02em} | |
| 14 | .sub{color:var(--dim);font-size:11px;margin:0 0 14px;line-height:1.45} | |
| 15 | ||
| 16 | aside{background:var(--panel);border-right:1px solid var(--edge); | |
| 17 | padding:14px;overflow-y:auto} | |
| 18 | fieldset{border:0;border-top:1px solid var(--edge);margin:14px 0 0;padding:12px 0 0} | |
| 19 | legend{font-family:"IBM Plex Mono",ui-monospace,monospace;font-size:10px; | |
| 20 | letter-spacing:.12em;text-transform:uppercase;color:var(--dim);padding:0} | |
| 21 | label{display:grid;grid-template-columns:1fr auto;gap:2px 8px;margin:9px 0; | |
| 22 | font-size:11.5px;align-items:center} | |
| 23 | label .v{font-family:"IBM Plex Mono",ui-monospace,monospace;color:var(--accent); | |
| 24 | font-size:11px;font-variant-numeric:tabular-nums} | |
| 25 | input[type=range]{grid-column:1/-1;width:100%;accent-color:var(--accent);height:16px} | |
| 26 | input[type=number],select{background:#0e1216;color:var(--ink);border:1px solid var(--edge2); | |
| 27 | border-radius:3px;padding:3px 5px;font:inherit;font-size:11.5px;width:100%} | |
| 28 | button{background:#22303a;color:var(--ink);border:1px solid var(--edge2);border-radius:3px; | |
| 29 | padding:6px 10px;font:inherit;font-size:11.5px;cursor:pointer} | |
| 30 | button:hover{border-color:var(--accent)} | |
| 31 | button.primary{background:var(--accent);color:#0d1417;border-color:var(--accent);font-weight:600} | |
| 32 | .row{display:flex;gap:6px;margin:8px 0} | |
| 33 | .row>*{flex:1} | |
| 34 | ||
| 35 | main{display:flex;flex-direction:column;overflow:hidden} | |
| 36 | .bar{display:flex;gap:14px;align-items:center;padding:9px 14px; | |
| 37 | border-bottom:1px solid var(--edge);background:var(--panel);flex-wrap:wrap} | |
| 38 | .bar .grow{flex:1} | |
| 39 | .views{display:flex;gap:0;border:1px solid var(--edge2);border-radius:3px;overflow:hidden} | |
| 40 | .views button{border:0;border-radius:0;border-right:1px solid var(--edge2);padding:5px 11px} | |
| 41 | .views button:last-child{border-right:0} | |
| 42 | .views button[aria-pressed=true]{background:var(--accent);color:#0d1417;font-weight:600} | |
| 43 | ||
| 44 | .panes{flex:1;display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--edge); | |
| 45 | overflow:hidden;min-height:0} | |
| 46 | .pane{background:#0d1114;display:flex;flex-direction:column;min-height:0;position:relative} | |
| 47 | .pane h2{margin:0;padding:7px 12px;font-size:11px;font-weight:600;letter-spacing:.02em; | |
| 48 | border-bottom:1px solid var(--edge);display:flex;gap:10px;align-items:baseline} | |
| 49 | .pane h2 .tag{font-family:"IBM Plex Mono",ui-monospace,monospace;font-size:9.5px; | |
| 50 | letter-spacing:.1em;text-transform:uppercase;color:var(--dim)} | |
| 51 | .imgwrap{flex:1;display:flex;align-items:center;justify-content:center; | |
| 52 | overflow:hidden;padding:10px;min-height:0} | |
| 53 | .imgwrap img{width:100%;height:100%;object-fit:contain;image-rendering:pixelated; | |
| 54 | border:1px solid var(--edge);display:block} | |
| 55 | .imgwrap canvas{width:100%;height:100%;border:1px solid var(--edge);display:block; | |
| 56 | cursor:grab;touch-action:none} | |
| 57 | .imgwrap canvas:active{cursor:grabbing} | |
| 58 | .stats{font-family:"IBM Plex Mono",ui-monospace,monospace;font-size:10.5px;color:var(--dim); | |
| 59 | padding:6px 12px;border-top:1px solid var(--edge);white-space:pre-wrap; | |
| 60 | font-variant-numeric:tabular-nums;min-height:26px} | |
| 61 | .busy{position:absolute;inset:0;display:grid;place-items:center;background:#0d1114cc; | |
| 62 | font-family:"IBM Plex Mono",monospace;font-size:11px;color:var(--accent)} | |
| 63 | /* display:grid above beats the hidden attribute's UA display:none, so the | |
| 64 | overlay would never go away. State lives on the attribute; say so here. */ | |
| 65 | .busy[hidden]{display:none} | |
| 66 | img[hidden],canvas[hidden]{display:none} | |
| 67 | .err{color:var(--warn)} | |
| 68 | .note{font-size:10.5px;color:var(--dim);line-height:1.5;margin-top:12px; | |
| 69 | border-top:1px solid var(--edge);padding-top:10px} | |
| 70 | kbd{font-family:"IBM Plex Mono",monospace;background:#0e1216;border:1px solid var(--edge2); | |
| 71 | border-radius:2px;padding:0 4px;font-size:10px} | |
| 72 | </style> | |
| 73 | ||
| 74 | <aside> | |
| 75 | <h1>Terrain Lookdev</h1> | |
| 76 | <p class="sub">hyper-games #46 §4 measured that these two differ in roughness. | |
| 77 | It cannot say which is better to look at. That is what this is for.</p> | |
| 78 | ||
| 79 | <div class="row"> | |
| 80 | <button class="primary" id="render">Render</button> | |
| 81 | <button id="reset">Reset</button> | |
| 82 | </div> | |
| 83 | <label>Auto-render on change <input type="checkbox" id="auto" checked style="width:auto"></label> | |
| 84 | ||
| 85 | <fieldset><legend>View</legend> | |
| 86 | <label>Tile size (px)<span class="v" id="v_n">384</span> | |
| 87 | <input type="range" id="n" min="128" max="768" step="64" value="384"></label> | |
| 88 | <label>Metres per pixel<span class="v" id="v_mpp">30</span> | |
| 89 | <input type="range" id="mpp" min="2" max="200" step="1" value="30"></label> | |
| 90 | <label>Scan steps (quality)<span class="v" id="v_steps">256</span> | |
| 91 | <input type="range" id="steps" min="64" max="768" step="32" value="256"></label> | |
| 92 | <label>Erosion origin X (m)<input type="number" id="ox" value="0" step="1000"></label> | |
| 93 | <label>Erosion origin Z (m)<input type="number" id="oz" value="0" step="1000"></label> | |
| 94 | <label>Diffusion i1 (px)<input type="number" id="i1" value="0" step="256"></label> | |
| 95 | <label>Diffusion j1 (px)<input type="number" id="j1" value="2048" step="256"></label> | |
| 96 | <label id="exagrow" hidden>3D vertical exaggeration<span class="v" id="v_exag">1</span> | |
| 97 | <input type="range" id="exag" min="0.2" max="6" step="0.1" value="1"></label> | |
| 98 | </fieldset> | |
| 99 | ||
| 100 | <fieldset><legend>Mountain layer</legend> | |
| 101 | <div id="grp_mountain"></div> | |
| 102 | </fieldset> | |
| 103 | <fieldset><legend>Erosion filter</legend> | |
| 104 | <div id="grp_erosion"></div> | |
| 105 | </fieldset> | |
| 106 | <fieldset><legend>Detail + base noise</legend> | |
| 107 | <div id="grp_detail"></div> | |
| 108 | </fieldset> | |
| 109 | ||
| 110 | <p class="note">Both panels are shaded by the same code, so a difference on | |
| 111 | screen is a difference in the terrain and not in the renderer. | |
| 112 | Diffusion is always at its native 30 m/px — its <code>scale</code> knob | |
| 113 | is bilinear (#46 §4.1) and would only blur. | |
| 114 | Drag on a panel to pan. <kbd>R</kbd> renders.</p> | |
| 115 | </aside> | |
| 116 | ||
| 117 | <main> | |
| 118 | <div class="bar"> | |
| 119 | <div class="views" id="modes"> | |
| 120 | <button data-m="2d" aria-pressed="true">2D</button> | |
| 121 | <button data-m="3d" aria-pressed="false">3D</button> | |
| 122 | </div> | |
| 123 | <div class="views" id="views"> | |
| 124 | <button data-v="shaded-height" aria-pressed="true">Shaded height</button> | |
| 125 | <button data-v="shade" aria-pressed="false">Hillshade</button> | |
| 126 | <button data-v="slope" aria-pressed="false">Slope</button> | |
| 127 | <button data-v="height" aria-pressed="false">Height</button> | |
| 128 | </div> | |
| 129 | <div class="grow"></div> | |
| 130 | <div class="stats" id="scale" style="border:0;padding:0"></div> | |
| 131 | </div> | |
| 132 | ||
| 133 | <div class="panes"> | |
| 134 | <section class="pane"> | |
| 135 | <h2>Our erosion filter <span class="tag">erosion_filter.inc · gpu</span></h2> | |
| 136 | <div class="imgwrap"><img id="img_e" alt="erosion render"> | |
| 137 | <canvas id="cv_e" hidden></canvas></div> | |
| 138 | <div class="stats" id="st_e">—</div> | |
| 139 | <div class="busy" id="busy_e" hidden>rendering…</div> | |
| 140 | </section> | |
| 141 | <section class="pane"> | |
| 142 | <h2>terrain-diffusion <span class="tag">30 m model · seed 1234</span></h2> | |
| 143 | <div class="imgwrap"><img id="img_d" alt="diffusion tile"> | |
| 144 | <canvas id="cv_d" hidden></canvas></div> | |
| 145 | <div class="stats" id="st_d">—</div> | |
| 146 | <div class="busy" id="busy_d" hidden>fetching…</div> | |
| 147 | </section> | |
| 148 | </div> | |
| 149 | </main> | |
| 150 | ||
| 151 | <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.160.0/three.min.js"></script> | |
| 152 | <script> | |
| 153 | const GROUPS = { | |
| 154 | mountain: [["terrain_height",60000,1000,200000,500],["mountain_scale",1,0.05,8,0.05], | |
| 155 | ["mountain_height",0.55,0,2,0.01],["mountain_gain",0.68,0.2,0.9,0.01], | |
| 156 | ["mountain_variance",0,0,1,0.01],["mountain_octaves",8,1,12,1], | |
| 157 | ["relief_scale",0.5,0,8,0.05],["relief_floor",0.08,0,1,0.01]], | |
| 158 | erosion: [["en_strength",0.15,0,1.5,0.005],["en_erosion_scale",0.15,0.002,0.5,0.002], | |
| 159 | ["en_cell_scale",0.7,0.05,4,0.01],["en_gain",0.5,0.2,0.9,0.01], | |
| 160 | ["en_octaves",7,1,12,1],["en_height_scale",5,0.1,40,0.1]], | |
| 161 | detail: [["detail_scale",600,0,5000,10],["detail_height",30,0,500,1], | |
| 162 | ["detail_gain",0.5,0.2,0.9,0.01],["detail_octaves",4,1,10,1], | |
| 163 | ["noise_lacunarity",1.87,1.2,3,0.01],["gn_scale",3,0.1,40,0.1], | |
| 164 | ["gn_gain",0.1,0,0.9,0.01],["gn_detail",3,1,8,1], | |
| 165 | ["voxel_size",1,0.25,512,0.25]] | |
| 166 | }; | |
| 167 | const $ = id => document.getElementById(id); | |
| 168 | let view = "shaded-height"; | |
| 169 | let mode = "2d"; | |
| 170 | ||
| 171 | for (const [grp, items] of Object.entries(GROUPS)) { | |
| 172 | const host = $("grp_" + grp); | |
| 173 | for (const [key, def, lo, hi, step] of items) { | |
| 174 | const l = document.createElement("label"); | |
| 175 | l.innerHTML = `${key.replace(/_/g," ")}<span class="v" id="v_${key}">${def}</span> | |
| 176 | <input type="range" id="${key}" min="${lo}" max="${hi}" step="${step}" value="${def}">`; | |
| 177 | host.appendChild(l); | |
| 178 | } | |
| 179 | } | |
| 180 | ||
| 181 | function knobParams() { | |
| 182 | const p = new URLSearchParams(); | |
| 183 | for (const items of Object.values(GROUPS)) | |
| 184 | for (const [key] of items) p.set(key, $(key).value); | |
| 185 | return p; | |
| 186 | } | |
| 187 | ||
| 188 | function syncLabels() { | |
| 189 | for (const id of ["n","mpp","steps","exag"]) $("v_"+id).textContent = $(id).value; | |
| 190 | for (const items of Object.values(GROUPS)) | |
| 191 | for (const [key] of items) $("v_"+key).textContent = $(key).value; | |
| 192 | const n = +$("n").value, mpp = +$("mpp").value; | |
| 193 | $("scale").textContent = | |
| 194 | `erosion ${(n*mpp/1000).toFixed(2)} km @ ${mpp} m/px · ` + | |
| 195 | `diffusion ${(n*30/1000).toFixed(2)} km @ 30 m/px` + | |
| 196 | (mpp !== 30 ? " · SCALES DIFFER" : ""); | |
| 197 | $("scale").className = "stats" + (mpp !== 30 ? " err" : ""); | |
| 198 | } | |
| 199 | ||
| 200 | async function load(which, url, imgId, stId, busyId) { | |
| 201 | $(busyId).hidden = false; | |
| 202 | const t0 = performance.now(); | |
| 203 | try { | |
| 204 | const r = await fetch(url); | |
| 205 | if (!r.ok) throw new Error(await r.text()); | |
| 206 | const b = await r.blob(); | |
| 207 | const old = $(imgId).src; | |
| 208 | $(imgId).src = URL.createObjectURL(b); | |
| 209 | if (old.startsWith("blob:")) URL.revokeObjectURL(old); | |
| 210 | const h = r.headers; | |
| 211 | const ms = (performance.now() - t0).toFixed(0); | |
| 212 | let s = `min ${h.get("X-Min")} max ${h.get("X-Max")} ` + | |
| 213 | `mean ${h.get("X-Mean")} sd ${h.get("X-Sd")} m ${ms} ms`; | |
| 214 | if (h.get("X-Climate")) s += `\n${h.get("X-Climate")}`; | |
| 215 | $(stId).textContent = s; | |
| 216 | $(stId).classList.remove("err"); | |
| 217 | } catch (e) { | |
| 218 | $(stId).textContent = String(e.message || e); | |
| 219 | $(stId).classList.add("err"); | |
| 220 | } finally { | |
| 221 | $(busyId).hidden = true; | |
| 222 | } | |
| 223 | } | |
| 224 | ||
| 225 | // --------------------------------------------------------------------------- | |
| 226 | // 3D. A different VIEW of the same heightfield the 2D panel shows -- same | |
| 227 | // endpoint, same render path -- so the two tabs can never disagree about the | |
| 228 | // terrain. Shading is a single directional light placed at the same azimuth | |
| 229 | // and altitude as the 2D hillshade (315 deg / 45 deg), for the same reason the | |
| 230 | // two panels share one shader: a difference on screen should be the terrain. | |
| 231 | // --------------------------------------------------------------------------- | |
| 232 | const V3 = {}; // per-pane viewer state | |
| 233 | ||
| 234 | function makeViewer(canvasId) { | |
| 235 | const canvas = $(canvasId); | |
| 236 | const renderer = new THREE.WebGLRenderer({canvas, antialias: true}); | |
| 237 | renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); | |
| 238 | const scene = new THREE.Scene(); | |
| 239 | scene.background = new THREE.Color(0x0d1114); | |
| 240 | ||
| 241 | const camera = new THREE.PerspectiveCamera(42, 1, 0.1, 100); | |
| 242 | const sun = new THREE.DirectionalLight(0xfff2e0, 2.1); | |
| 243 | // azimuth 315 (NW), altitude 45 -- matching hillshade() in server.py | |
| 244 | sun.position.set(-0.707 * 0.707, 0.707, -0.707 * 0.707).normalize(); | |
| 245 | scene.add(sun); | |
| 246 | scene.add(new THREE.HemisphereLight(0x93a8c4, 0x201a14, 0.55)); | |
| 247 | ||
| 248 | const st = {renderer, scene, camera, mesh: null, | |
| 249 | yaw: -0.7, pitch: 0.62, dist: 2.4, dirty: true}; | |
| 250 | V3[canvasId] = st; | |
| 251 | ||
| 252 | let down = false, sx = 0, sy = 0, y0 = 0, p0 = 0; | |
| 253 | canvas.addEventListener("pointerdown", e => { | |
| 254 | down = true; sx = e.clientX; sy = e.clientY; y0 = st.yaw; p0 = st.pitch; | |
| 255 | canvas.setPointerCapture(e.pointerId); | |
| 256 | }); | |
| 257 | canvas.addEventListener("pointermove", e => { | |
| 258 | if (!down) return; | |
| 259 | st.yaw = y0 - (e.clientX - sx) * 0.006; | |
| 260 | st.pitch = Math.max(0.05, Math.min(1.5, p0 + (e.clientY - sy) * 0.005)); | |
| 261 | st.dirty = true; | |
| 262 | }); | |
| 263 | canvas.addEventListener("pointerup", () => down = false); | |
| 264 | canvas.addEventListener("wheel", e => { | |
| 265 | e.preventDefault(); | |
| 266 | st.dist = Math.max(0.6, Math.min(8, st.dist * (1 + Math.sign(e.deltaY) * 0.12))); | |
| 267 | st.dirty = true; | |
| 268 | }, {passive: false}); | |
| 269 | return st; | |
| 270 | } | |
| 271 | ||
| 272 | // One shared frame loop. Only redraws panes marked dirty, so an idle 3D tab | |
| 273 | // costs nothing. | |
| 274 | function frame() { | |
| 275 | for (const [id, st] of Object.entries(V3)) { | |
| 276 | const cv = $(id); | |
| 277 | if (cv.hidden || !st.mesh) continue; | |
| 278 | const w = cv.clientWidth | 0, h = cv.clientHeight | 0; | |
| 279 | if (!w || !h) continue; | |
| 280 | if (cv.width !== w * st.renderer.getPixelRatio() || | |
| 281 | cv.height !== h * st.renderer.getPixelRatio()) { | |
| 282 | st.renderer.setSize(w, h, false); | |
| 283 | st.camera.aspect = w / h; | |
| 284 | st.camera.updateProjectionMatrix(); | |
| 285 | st.dirty = true; | |
| 286 | } | |
| 287 | if (!st.dirty) continue; | |
| 288 | const d = st.dist, cp = Math.cos(st.pitch); | |
| 289 | st.camera.position.set(Math.sin(st.yaw) * cp * d, Math.sin(st.pitch) * d, | |
| 290 | Math.cos(st.yaw) * cp * d); | |
| 291 | st.camera.lookAt(0, 0, 0); | |
| 292 | st.renderer.render(st.scene, st.camera); | |
| 293 | st.dirty = false; | |
| 294 | } | |
| 295 | requestAnimationFrame(frame); | |
| 296 | } | |
| 297 | requestAnimationFrame(frame); | |
| 298 | ||
| 299 | function buildMesh(st, heights, n, mpp, lo, hi, exag) { | |
| 300 | if (st.mesh) { | |
| 301 | st.scene.remove(st.mesh); | |
| 302 | st.mesh.geometry.dispose(); | |
| 303 | st.mesh.material.dispose(); | |
| 304 | } | |
| 305 | // The tile is normalised to 1 unit wide, so the two panes stay comparable | |
| 306 | // even when their metres-per-pixel differ. Height keeps its TRUE ratio to | |
| 307 | // the ground extent -- exaggeration is an explicit, labelled multiplier and | |
| 308 | // never a silent auto-fit, or the 3D view would flatter whichever generator | |
| 309 | // has less relief. | |
| 310 | const widthM = n * mpp; | |
| 311 | const g = new THREE.PlaneGeometry(1, 1, n - 1, n - 1); | |
| 312 | g.rotateX(-Math.PI / 2); | |
| 313 | const pos = g.attributes.position; | |
| 314 | const col = new Float32Array(n * n * 3); | |
| 315 | const span = Math.max(hi - lo, 1e-6); | |
| 316 | for (let i = 0; i < n * n; i++) { | |
| 317 | const y = heights[i]; | |
| 318 | pos.array[i * 3 + 1] = (y / widthM) * exag; | |
| 319 | const t = (y - lo) / span; | |
| 320 | col[i * 3 + 0] = 0.20 + 0.75 * t; | |
| 321 | col[i * 3 + 1] = 0.30 + 0.55 * t; | |
| 322 | col[i * 3 + 2] = 0.38 + 0.22 * (1 - t); | |
| 323 | } | |
| 324 | // Recentre so orbiting is about the terrain, not about y = 0. | |
| 325 | const mid = ((lo + hi) * 0.5 / widthM) * exag; | |
| 326 | for (let i = 0; i < n * n; i++) pos.array[i * 3 + 1] -= mid; | |
| 327 | pos.needsUpdate = true; | |
| 328 | g.setAttribute("color", new THREE.BufferAttribute(col, 3)); | |
| 329 | g.computeVertexNormals(); | |
| 330 | ||
| 331 | const m = new THREE.MeshStandardMaterial({vertexColors: true, roughness: 0.95, | |
| 332 | metalness: 0.0, flatShading: false}); | |
| 333 | st.mesh = new THREE.Mesh(g, m); | |
| 334 | st.scene.add(st.mesh); | |
| 335 | st.dirty = true; | |
| 336 | } | |
| 337 | ||
| 338 | async function load3d(url, canvasId, stId, busyId) { | |
| 339 | $(busyId).hidden = false; | |
| 340 | const t0 = performance.now(); | |
| 341 | try { | |
| 342 | const r = await fetch(url); | |
| 343 | if (!r.ok) throw new Error(await r.text()); | |
| 344 | const buf = await r.arrayBuffer(); | |
| 345 | const h = r.headers; | |
| 346 | const n = +h.get("X-N"), mpp = +h.get("X-Mpp"); | |
| 347 | const lo = +h.get("X-Min"), hi = +h.get("X-Max"); | |
| 348 | const heights = new Float32Array(buf); | |
| 349 | if (heights.length !== n * n) throw new Error(`expected ${n*n} floats, got ${heights.length}`); | |
| 350 | const st = V3[canvasId] || makeViewer(canvasId); | |
| 351 | buildMesh(st, heights, n, mpp, lo, hi, +$("exag").value); | |
| 352 | const ms = (performance.now() - t0).toFixed(0); | |
| 353 | let s = `min ${h.get("X-Min")} max ${h.get("X-Max")} mean ${h.get("X-Mean")} ` + | |
| 354 | `sd ${h.get("X-Sd")} m ${n}x${n} @ ${mpp} m/px ${ms} ms`; | |
| 355 | if (+h.get("X-Holes")) s += ` ${h.get("X-Holes")} holes filled`; | |
| 356 | if (h.get("X-Climate")) s += `\n${h.get("X-Climate")}`; | |
| 357 | $(stId).textContent = s; | |
| 358 | $(stId).classList.remove("err"); | |
| 359 | } catch (e) { | |
| 360 | $(stId).textContent = String(e.message || e); | |
| 361 | $(stId).classList.add("err"); | |
| 362 | } finally { | |
| 363 | $(busyId).hidden = true; | |
| 364 | } | |
| 365 | } | |
| 366 | ||
| 367 | function erosionQuery() { | |
| 368 | const p = knobParams(); | |
| 369 | p.set("n", $("n").value); p.set("mpp", $("mpp").value); | |
| 370 | p.set("steps", $("steps").value); | |
| 371 | p.set("ox", $("ox").value); p.set("oz", $("oz").value); | |
| 372 | return p; | |
| 373 | } | |
| 374 | ||
| 375 | function render() { | |
| 376 | syncLabels(); | |
| 377 | const p = erosionQuery(); | |
| 378 | const q = new URLSearchParams({n: $("n").value, i1: $("i1").value, j1: $("j1").value}); | |
| 379 | ||
| 380 | if (mode === "3d") { | |
| 381 | load3d("/api/erosion.bin?" + p, "cv_e", "st_e", "busy_e"); | |
| 382 | load3d("/api/diffusion.bin?" + q, "cv_d", "st_d", "busy_d"); | |
| 383 | } else { | |
| 384 | p.set("view", view); q.set("view", view); | |
| 385 | load("e", "/api/erosion.png?" + p, "img_e", "st_e", "busy_e"); | |
| 386 | load("d", "/api/diffusion.png?" + q, "img_d", "st_d", "busy_d"); | |
| 387 | } | |
| 388 | } | |
| 389 | ||
| 390 | let timer = null; | |
| 391 | function schedule() { | |
| 392 | syncLabels(); | |
| 393 | if (!$("auto").checked) return; | |
| 394 | clearTimeout(timer); | |
| 395 | timer = setTimeout(render, 250); | |
| 396 | } | |
| 397 | ||
| 398 | document.addEventListener("input", e => { | |
| 399 | if (e.target.type === "range" || e.target.type === "number") schedule(); | |
| 400 | }); | |
| 401 | $("render").onclick = render; | |
| 402 | $("reset").onclick = () => { | |
| 403 | for (const items of Object.values(GROUPS)) | |
| 404 | for (const [key, def] of items) $(key).value = def; | |
| 405 | render(); | |
| 406 | }; | |
| 407 | $("views").onclick = e => { | |
| 408 | const b = e.target.closest("button"); if (!b) return; | |
| 409 | view = b.dataset.v; | |
| 410 | for (const x of $("views").children) x.setAttribute("aria-pressed", x === b); | |
| 411 | render(); | |
| 412 | }; | |
| 413 | $("modes").onclick = e => { | |
| 414 | const b = e.target.closest("button"); if (!b) return; | |
| 415 | mode = b.dataset.m; | |
| 416 | for (const x of $("modes").children) x.setAttribute("aria-pressed", x === b); | |
| 417 | const three = mode === "3d"; | |
| 418 | $("views").style.display = three ? "none" : ""; | |
| 419 | $("exagrow").hidden = !three; | |
| 420 | for (const [img, cv] of [["img_e","cv_e"], ["img_d","cv_d"]]) { | |
| 421 | $(img).hidden = three; | |
| 422 | $(cv).hidden = !three; | |
| 423 | } | |
| 424 | if (three && !V3.cv_e) { makeViewer("cv_e"); makeViewer("cv_d"); } | |
| 425 | render(); | |
| 426 | }; | |
| 427 | ||
| 428 | // Exaggeration only reshapes the existing mesh; no refetch. | |
| 429 | $("exag").addEventListener("input", () => { | |
| 430 | $("v_exag").textContent = $("exag").value; | |
| 431 | if (mode === "3d") render(); | |
| 432 | }); | |
| 433 | ||
| 434 | document.addEventListener("keydown", e => { | |
| 435 | if (e.key === "r" && !/INPUT|SELECT/.test(document.activeElement.tagName)) render(); | |
| 436 | }); | |
| 437 | ||
| 438 | // Drag to pan. Erosion moves in metres, diffusion in its own pixels; both move | |
| 439 | // by the same number of PIXELS so the two views stay in step visually. | |
| 440 | for (const [imgId, xId, yId, unit] of | |
| 441 | [["img_e","ox","oz",() => +$("mpp").value], ["img_d","i1","j1",() => 1]]) { | |
| 442 | let sx = 0, sy = 0, x0 = 0, y0 = 0, down = false; | |
| 443 | $(imgId).addEventListener("pointerdown", e => { | |
| 444 | down = true; sx = e.clientX; sy = e.clientY; | |
| 445 | x0 = +$(xId).value; y0 = +$(yId).value; | |
| 446 | $(imgId).setPointerCapture(e.pointerId); | |
| 447 | }); | |
| 448 | $(imgId).addEventListener("pointermove", e => { | |
| 449 | if (!down) return; | |
| 450 | const px = ($(imgId).naturalWidth || 1) / ($(imgId).clientWidth || 1); | |
| 451 | const u = unit(); | |
| 452 | $(xId).value = Math.round(x0 - (e.clientX - sx) * px * u); | |
| 453 | $(yId).value = Math.round(y0 - (e.clientY - sy) * px * u); | |
| 454 | }); | |
| 455 | $(imgId).addEventListener("pointerup", () => { if (down) { down = false; render(); } }); | |
| 456 | } | |
| 457 | ||
| 458 | // URL-driven initial state, so a view can be linked to and so headless | |
| 459 | // verification can reach the 3D tab without a click. | |
| 460 | // ?mode=3d&view=slope&ox=...&oz=...&i1=...&j1=...&n=...&mpp=... plus any knob | |
| 461 | (function fromUrl() { | |
| 462 | const q = new URLSearchParams(location.search); | |
| 463 | for (const [k, v] of q) { | |
| 464 | if (k === "mode" || k === "view") continue; | |
| 465 | const el = $(k); | |
| 466 | if (el && (el.type === "range" || el.type === "number")) el.value = v; | |
| 467 | } | |
| 468 | if (q.get("view")) { | |
| 469 | const b = [...$("views").children].find(x => x.dataset.v === q.get("view")); | |
| 470 | if (b) { view = b.dataset.v; | |
| 471 | for (const x of $("views").children) x.setAttribute("aria-pressed", x === b); } | |
| 472 | } | |
| 473 | if (q.get("mode") === "3d") { | |
| 474 | const b = [...$("modes").children].find(x => x.dataset.m === "3d"); | |
| 475 | if (b) b.click(); // click() runs the full tab handler, incl. render() | |
| 476 | return true; | |
| 477 | } | |
| 478 | return false; | |
| 479 | })() || (syncLabels(), render()); | |
| 480 | </script> |
| … | … | @@ -0,0 +1,323 @@ |
| 1 | #!/usr/bin/env python3 | |
| 2 | """ | |
| 3 | LOOKDEV SERVER — our erosion filter and terrain-diffusion, side by side, at a | |
| 4 | matched scale, with the knobs live. | |
| 5 | ||
| 6 | hyper-games #46. §4 measured that the two generators differ in roughness and | |
| 7 | said nothing about which is better to look at. That is the open question and it | |
| 8 | is aesthetic, so the only instrument that can answer it is a pair of eyes with | |
| 9 | both images at the same scale. | |
| 10 | ||
| 11 | EROSION panel -> shells out to erosion_render, which compiles | |
| 12 | erosion_filter.inc off disk and the wrapper from | |
| 13 | erosion_wrapper_glsl.h. Nothing is reimplemented here. | |
| 14 | DIFFUSION panel -> proxies the terrain_diffusion API. | |
| 15 | ||
| 16 | Both are shaded by the SAME code below, because otherwise you are comparing two | |
| 17 | renderers rather than two terrains. | |
| 18 | ||
| 19 | python server.py --port 8818 | |
| 20 | """ | |
| 21 | import argparse, hashlib, json, os, subprocess, sys, urllib.request | |
| 22 | from collections import OrderedDict | |
| 23 | ||
| 24 | import numpy as np | |
| 25 | from flask import Flask, Response, request, send_from_directory | |
| 26 | ||
| 27 | HERE = os.path.dirname(os.path.abspath(__file__)) | |
| 28 | DC_TERRAIN = os.path.abspath(os.path.join(HERE, "..", "dc-terrain")) | |
| 29 | INC = os.path.abspath(os.path.join(HERE, "..", "proc-nodes", "src", "shared", | |
| 30 | "erosion_filter.inc")) | |
| 31 | ||
| 32 | app = Flask(__name__, static_folder=None) | |
| 33 | ||
| 34 | # Every knob the renderer will accept, with its type and a sane range. The | |
| 35 | # server shells out, so this allowlist is also the injection boundary: a key | |
| 36 | # not in here never reaches argv, and a value is always re-emitted from a | |
| 37 | # parsed float/int, never passed through as text. | |
| 38 | KNOBS = { | |
| 39 | "terrain_height": ("f", 1000.0, 200000.0), | |
| 40 | "mountain_scale": ("f", 0.05, 20.0), | |
| 41 | "mountain_height": ("f", 0.0, 2.0), | |
| 42 | "mountain_gain": ("f", 0.2, 0.9), | |
| 43 | "mountain_variance": ("f", 0.0, 1.0), | |
| 44 | "mountain_octaves": ("i", 1, 12), | |
| 45 | "relief_scale": ("f", 0.0, 8.0), | |
| 46 | "relief_floor": ("f", 0.0, 1.0), | |
| 47 | "detail_scale": ("f", 0.0, 5000.0), | |
| 48 | "detail_height": ("f", 0.0, 500.0), | |
| 49 | "detail_gain": ("f", 0.2, 0.9), | |
| 50 | "detail_octaves": ("i", 1, 10), | |
| 51 | "noise_lacunarity": ("f", 1.2, 3.0), | |
| 52 | "voxel_size": ("f", 0.25, 512.0), | |
| 53 | "gn_scale": ("f", 0.1, 40.0), | |
| 54 | "gn_gain": ("f", 0.0, 0.9), | |
| 55 | "gn_detail": ("i", 1, 8), | |
| 56 | "en_height_scale": ("f", 0.1, 40.0), | |
| 57 | "en_strength": ("f", 0.0, 1.5), | |
| 58 | "en_cell_scale": ("f", 0.05, 4.0), | |
| 59 | "en_gain": ("f", 0.2, 0.9), | |
| 60 | "en_octaves": ("i", 1, 12), | |
| 61 | "en_erosion_scale": ("f", 0.002, 0.5), | |
| 62 | } | |
| 63 | ||
| 64 | NO_SURFACE = -3.0e38 | |
| 65 | _cache = OrderedDict() | |
| 66 | CACHE_MAX = 64 | |
| 67 | ||
| 68 | ||
| 69 | def _clamp(v, lo, hi): | |
| 70 | return lo if v < lo else (hi if v > hi else v) | |
| 71 | ||
| 72 | ||
| 73 | def parse_knobs(args): | |
| 74 | """Allowlist + clamp. Nothing else is ever put on the command line.""" | |
| 75 | out = {} | |
| 76 | for k, (kind, lo, hi) in KNOBS.items(): | |
| 77 | raw = args.get(k) | |
| 78 | if raw is None or raw == "": | |
| 79 | continue | |
| 80 | try: | |
| 81 | v = float(raw) | |
| 82 | except ValueError: | |
| 83 | raise ValueError(f"{k}: not a number") | |
| 84 | if v != v or v in (float("inf"), float("-inf")): | |
| 85 | raise ValueError(f"{k}: not finite") | |
| 86 | v = _clamp(v, lo, hi) | |
| 87 | out[k] = int(round(v)) if kind == "i" else v | |
| 88 | return out | |
| 89 | ||
| 90 | ||
| 91 | def render_erosion(ox, oz, n, mpp, knobs, steps, bisect): | |
| 92 | key = hashlib.sha1(json.dumps( | |
| 93 | [ox, oz, n, mpp, sorted(knobs.items()), steps, bisect]).encode()).hexdigest() | |
| 94 | if key in _cache: | |
| 95 | _cache.move_to_end(key) | |
| 96 | return _cache[key] | |
| 97 | ||
| 98 | out = f"/tmp/lookdev-{key}.f32" | |
| 99 | cmd = ["/tmp/erosion_render", "--inc", INC, "--out", out, | |
| 100 | "--n", str(int(n)), "--mpp", repr(float(mpp)), | |
| 101 | "--ox", repr(float(ox)), "--oz", repr(float(oz)), | |
| 102 | "--steps", str(int(steps)), "--bisect", str(int(bisect))] | |
| 103 | for k, v in knobs.items(): | |
| 104 | cmd += ["--set", k, repr(v)] | |
| 105 | p = subprocess.run(cmd, cwd=DC_TERRAIN, capture_output=True, text=True, timeout=300) | |
| 106 | if p.returncode != 0: | |
| 107 | raise RuntimeError((p.stderr or p.stdout or "render failed").strip()[:500]) | |
| 108 | ||
| 109 | h = np.fromfile(out, dtype="<f4").reshape(int(n), int(n)).astype(np.float64) | |
| 110 | os.unlink(out) | |
| 111 | h = np.where(h <= NO_SURFACE / 2, np.nan, h) | |
| 112 | ||
| 113 | _cache[key] = h | |
| 114 | while len(_cache) > CACHE_MAX: | |
| 115 | _cache.popitem(last=False) | |
| 116 | return h | |
| 117 | ||
| 118 | ||
| 119 | def fetch_diffusion(url, i1, j1, n): | |
| 120 | q = f"{url}/terrain?i1={int(i1)}&j1={int(j1)}&i2={int(i1)+int(n)}&j2={int(j1)+int(n)}&scale=1" | |
| 121 | with urllib.request.urlopen(q, timeout=600) as r: | |
| 122 | hh = int(r.headers["X-Height"]); ww = int(r.headers["X-Width"]) | |
| 123 | body = r.read() | |
| 124 | nelev = hh * ww * 2 | |
| 125 | elev = np.frombuffer(body[:nelev], dtype="<i2").reshape(hh, ww).astype(np.float64) | |
| 126 | climate = None | |
| 127 | if len(body) > nelev: | |
| 128 | climate = np.frombuffer(body[nelev:], dtype="<f4").reshape(hh, ww, 4) | |
| 129 | return elev, climate | |
| 130 | ||
| 131 | ||
| 132 | # --------------------------------------------------------------------------- | |
| 133 | # Shading. ONE implementation, used for both panels — the whole point is that | |
| 134 | # a difference on screen is a difference in the terrain. | |
| 135 | # --------------------------------------------------------------------------- | |
| 136 | def hillshade(h, mpp, azimuth=315.0, altitude=45.0, exaggeration=1.0): | |
| 137 | hh = np.nan_to_num(h, nan=float(np.nanmean(h)) if np.isfinite(h).any() else 0.0) | |
| 138 | gy, gx = np.gradient(hh * exaggeration, mpp) | |
| 139 | slope = np.arctan(np.hypot(gx, gy)) | |
| 140 | aspect = np.arctan2(-gy, gx) | |
| 141 | az = np.radians(360.0 - azimuth + 90.0) | |
| 142 | alt = np.radians(altitude) | |
| 143 | shade = (np.sin(alt) * np.cos(slope) + | |
| 144 | np.cos(alt) * np.sin(slope) * np.cos(az - aspect)) | |
| 145 | return np.clip(shade, 0.0, 1.0) | |
| 146 | ||
| 147 | ||
| 148 | def slope_map(h, mpp): | |
| 149 | hh = np.nan_to_num(h, nan=float(np.nanmean(h)) if np.isfinite(h).any() else 0.0) | |
| 150 | gy, gx = np.gradient(hh, mpp) | |
| 151 | return np.degrees(np.arctan(np.hypot(gx, gy))) | |
| 152 | ||
| 153 | ||
| 154 | def to_png(rgb): | |
| 155 | from PIL import Image | |
| 156 | import io | |
| 157 | im = Image.fromarray(rgb, mode="RGB") | |
| 158 | buf = io.BytesIO() | |
| 159 | im.save(buf, format="PNG", optimize=False, compress_level=1) | |
| 160 | return buf.getvalue() | |
| 161 | ||
| 162 | ||
| 163 | def colourise(h, mpp, view, vmin=None, vmax=None): | |
| 164 | """view: shade | height | slope | shaded-height""" | |
| 165 | finite = h[np.isfinite(h)] | |
| 166 | lo = float(vmin) if vmin is not None else (float(finite.min()) if finite.size else 0.0) | |
| 167 | hi = float(vmax) if vmax is not None else (float(finite.max()) if finite.size else 1.0) | |
| 168 | if hi <= lo: | |
| 169 | hi = lo + 1.0 | |
| 170 | ||
| 171 | if view == "slope": | |
| 172 | s = np.clip(slope_map(h, mpp) / 60.0, 0, 1) | |
| 173 | rgb = np.stack([s, 1.0 - s * 0.65, 1.0 - s], axis=-1) | |
| 174 | elif view == "height": | |
| 175 | t = np.clip((h - lo) / (hi - lo), 0, 1) | |
| 176 | t = np.nan_to_num(t) | |
| 177 | rgb = np.stack([0.15 + 0.85 * t, 0.25 + 0.6 * t, 0.35 + 0.3 * (1 - t)], axis=-1) | |
| 178 | else: | |
| 179 | sh = hillshade(h, mpp) | |
| 180 | if view == "shaded-height": | |
| 181 | t = np.nan_to_num(np.clip((h - lo) / (hi - lo), 0, 1)) | |
| 182 | base = np.stack([0.20 + 0.75 * t, 0.30 + 0.55 * t, | |
| 183 | 0.38 + 0.22 * (1 - t)], axis=-1) | |
| 184 | rgb = base * (0.35 + 0.65 * sh[..., None]) | |
| 185 | else: | |
| 186 | rgb = np.repeat(sh[..., None], 3, axis=-1) | |
| 187 | ||
| 188 | rgb = np.where(np.isfinite(h)[..., None], rgb, 0.0) | |
| 189 | return to_png((np.clip(rgb, 0, 1) * 255).astype(np.uint8)), lo, hi | |
| 190 | ||
| 191 | ||
| 192 | def stats_headers(resp, h, lo, hi, extra=None): | |
| 193 | finite = h[np.isfinite(h)] | |
| 194 | resp.headers["X-Min"] = f"{finite.min():.1f}" if finite.size else "nan" | |
| 195 | resp.headers["X-Max"] = f"{finite.max():.1f}" if finite.size else "nan" | |
| 196 | resp.headers["X-Mean"] = f"{finite.mean():.1f}" if finite.size else "nan" | |
| 197 | resp.headers["X-Sd"] = f"{finite.std():.1f}" if finite.size else "nan" | |
| 198 | resp.headers["X-Ramp"] = f"{lo:.1f},{hi:.1f}" | |
| 199 | if extra: | |
| 200 | for k, v in extra.items(): | |
| 201 | resp.headers[k] = v | |
| 202 | return resp | |
| 203 | ||
| 204 | ||
| 205 | # --------------------------------------------------------------------------- | |
| 206 | @app.get("/") | |
| 207 | def index(): | |
| 208 | return send_from_directory(HERE, "index.html") | |
| 209 | ||
| 210 | ||
| 211 | @app.get("/api/erosion.png") | |
| 212 | def api_erosion(): | |
| 213 | try: | |
| 214 | a = request.args | |
| 215 | n = int(_clamp(float(a.get("n", 384)), 64, 1024)) | |
| 216 | mpp = float(_clamp(float(a.get("mpp", 30)), 0.05, 20000)) | |
| 217 | ox = float(a.get("ox", 0)); oz = float(a.get("oz", 0)) | |
| 218 | steps = int(_clamp(float(a.get("steps", 256)), 32, 1024)) | |
| 219 | knobs = parse_knobs(a) | |
| 220 | h = render_erosion(ox, oz, n, mpp, knobs, steps, 40) | |
| 221 | png, lo, hi = colourise(h, mpp, a.get("view", "shaded-height")) | |
| 222 | return stats_headers(Response(png, mimetype="image/png"), h, lo, hi, | |
| 223 | {"X-Source": "erosion_render"}) | |
| 224 | except Exception as e: | |
| 225 | return Response(str(e), status=400, mimetype="text/plain") | |
| 226 | ||
| 227 | ||
| 228 | @app.get("/api/diffusion.png") | |
| 229 | def api_diffusion(): | |
| 230 | try: | |
| 231 | a = request.args | |
| 232 | n = int(_clamp(float(a.get("n", 384)), 64, 1024)) | |
| 233 | i1 = int(float(a.get("i1", 0))); j1 = int(float(a.get("j1", 2048))) | |
| 234 | h, climate = fetch_diffusion(app.config["TD_URL"], i1, j1, n) | |
| 235 | png, lo, hi = colourise(h, 30.0, a.get("view", "shaded-height")) | |
| 236 | extra = {"X-Source": "terrain-diffusion", "X-Mpp": "30"} | |
| 237 | if climate is not None: | |
| 238 | extra["X-Climate"] = (f"{climate[...,0].mean():.1f}C," | |
| 239 | f"{climate[...,1].mean():.0f}," | |
| 240 | f"{climate[...,2].mean():.0f}mm," | |
| 241 | f"{climate[...,3].mean():.0f}%") | |
| 242 | return stats_headers(Response(png, mimetype="image/png"), h, lo, hi, extra) | |
| 243 | except Exception as e: | |
| 244 | return Response(str(e), status=400, mimetype="text/plain") | |
| 245 | ||
| 246 | ||
| 247 | # --------------------------------------------------------------------------- | |
| 248 | # Raw heightfields, for the 3D tab. Same render path as the PNG endpoints -- | |
| 249 | # the 3D view must be a different VIEW of the same field, never a different | |
| 250 | # field, or it stops being evidence about the thing on the left. | |
| 251 | # --------------------------------------------------------------------------- | |
| 252 | def _raw_response(h, mpp, extra=None): | |
| 253 | finite = h[np.isfinite(h)] | |
| 254 | lo = float(finite.min()) if finite.size else 0.0 | |
| 255 | hi = float(finite.max()) if finite.size else 1.0 | |
| 256 | # NaN does not survive a Float32Array cleanly in every browser path, so | |
| 257 | # holes are sent as the floor and flagged by count. | |
| 258 | filled = np.where(np.isfinite(h), h, lo).astype("<f4") | |
| 259 | resp = Response(filled.tobytes(), mimetype="application/octet-stream") | |
| 260 | resp.headers["X-N"] = str(h.shape[0]) | |
| 261 | resp.headers["X-Mpp"] = repr(float(mpp)) | |
| 262 | resp.headers["X-Min"] = f"{lo:.3f}" | |
| 263 | resp.headers["X-Max"] = f"{hi:.3f}" | |
| 264 | resp.headers["X-Mean"] = f"{finite.mean():.1f}" if finite.size else "nan" | |
| 265 | resp.headers["X-Sd"] = f"{finite.std():.1f}" if finite.size else "nan" | |
| 266 | resp.headers["X-Holes"] = str(int((~np.isfinite(h)).sum())) | |
| 267 | if extra: | |
| 268 | for k, v in extra.items(): | |
| 269 | resp.headers[k] = v | |
| 270 | return resp | |
| 271 | ||
| 272 | ||
| 273 | @app.get("/api/erosion.bin") | |
| 274 | def api_erosion_bin(): | |
| 275 | try: | |
| 276 | a = request.args | |
| 277 | n = int(_clamp(float(a.get("n", 384)), 64, 1024)) | |
| 278 | mpp = float(_clamp(float(a.get("mpp", 30)), 0.05, 20000)) | |
| 279 | ox = float(a.get("ox", 0)); oz = float(a.get("oz", 0)) | |
| 280 | steps = int(_clamp(float(a.get("steps", 256)), 32, 1024)) | |
| 281 | h = render_erosion(ox, oz, n, mpp, parse_knobs(a), steps, 40) | |
| 282 | return _raw_response(h, mpp, {"X-Source": "erosion_render"}) | |
| 283 | except Exception as e: | |
| 284 | return Response(str(e), status=400, mimetype="text/plain") | |
| 285 | ||
| 286 | ||
| 287 | @app.get("/api/diffusion.bin") | |
| 288 | def api_diffusion_bin(): | |
| 289 | try: | |
| 290 | a = request.args | |
| 291 | n = int(_clamp(float(a.get("n", 384)), 64, 1024)) | |
| 292 | i1 = int(float(a.get("i1", 0))); j1 = int(float(a.get("j1", 2048))) | |
| 293 | h, climate = fetch_diffusion(app.config["TD_URL"], i1, j1, n) | |
| 294 | extra = {"X-Source": "terrain-diffusion"} | |
| 295 | if climate is not None: | |
| 296 | extra["X-Climate"] = (f"{climate[...,0].mean():.1f}C," | |
| 297 | f"{climate[...,1].mean():.0f}," | |
| 298 | f"{climate[...,2].mean():.0f}mm," | |
| 299 | f"{climate[...,3].mean():.0f}%") | |
| 300 | return _raw_response(h, 30.0, extra) | |
| 301 | except Exception as e: | |
| 302 | return Response(str(e), status=400, mimetype="text/plain") | |
| 303 | ||
| 304 | ||
| 305 | @app.get("/api/knobs") | |
| 306 | def api_knobs(): | |
| 307 | return Response(json.dumps({k: {"kind": v[0], "min": v[1], "max": v[2]} | |
| 308 | for k, v in KNOBS.items()}), | |
| 309 | mimetype="application/json") | |
| 310 | ||
| 311 | ||
| 312 | if __name__ == "__main__": | |
| 313 | ap = argparse.ArgumentParser() | |
| 314 | ap.add_argument("--port", type=int, default=8818) | |
| 315 | ap.add_argument("--host", default="127.0.0.1") | |
| 316 | ap.add_argument("--td-url", default="http://127.0.0.1:8817") | |
| 317 | args = ap.parse_args() | |
| 318 | if not os.path.exists("/tmp/erosion_render"): | |
| 319 | sys.exit("build /tmp/erosion_render first — see erosion_render.cpp's header, " | |
| 320 | "and run it with --verify before believing any picture it makes.") | |
| 321 | app.config["TD_URL"] = args.td_url | |
| 322 | print(f"lookdev on http://{args.host}:{args.port} (diffusion at {args.td_url})") | |
| 323 | app.run(host=args.host, port=args.port, threaded=True) |
| … | … | @@ -0,0 +1,1790 @@ |
| 1 | scale,k,power | |
| 2 | 1,1,5.2687833258e+07 | |
| 3 | 1,2,4.9792994088e+07 | |
| 4 | 1,3,2.4994074186e+07 | |
| 5 | 1,4,1.3073906787e+07 | |
| 6 | 1,5,3.8223782999e+06 | |
| 7 | 1,6,1.5221426949e+06 | |
| 8 | 1,7,1.1774009313e+06 | |
| 9 | 1,8,1.0481788303e+06 | |
| 10 | 1,9,8.8397091917e+05 | |
| 11 | 1,10,4.9330667115e+05 | |
| 12 | 1,11,2.4753444462e+05 | |
| 13 | 1,12,2.3414011349e+05 | |
| 14 | 1,13,1.3515903262e+05 | |
| 15 | 1,14,1.0027913770e+05 | |
| 16 | 1,15,8.3561658310e+04 | |
| 17 | 1,16,1.0234258655e+05 | |
| 18 | 1,17,6.7220925381e+04 | |
| 19 | 1,18,3.6412352785e+04 | |
| 20 | 1,19,3.8097956857e+04 | |
| 21 | 1,20,3.4141851727e+04 | |
| 22 | 1,21,2.7769109860e+04 | |
| 23 | 1,22,1.9993490346e+04 | |
| 24 | 1,23,1.8149261896e+04 | |
| 25 | 1,24,1.9175413147e+04 | |
| 26 | 1,25,1.4034288300e+04 | |
| 27 | 1,26,9.5102824381e+03 | |
| 28 | 1,27,8.3137646973e+03 | |
| 29 | 1,28,8.1425644040e+03 | |
| 30 | 1,29,6.7442045927e+03 | |
| 31 | 1,30,5.8814905915e+03 | |
| 32 | 1,31,4.7546694406e+03 | |
| 33 | 1,32,4.0229979156e+03 | |
| 34 | 1,33,3.9487426108e+03 | |
| 35 | 1,34,4.1570255193e+03 | |
| 36 | 1,35,3.7199821331e+03 | |
| 37 | 1,36,2.6562678364e+03 | |
| 38 | 1,37,2.8272429221e+03 | |
| 39 | 1,38,2.3850339022e+03 | |
| 40 | 1,39,1.8118568494e+03 | |
| 41 | 1,40,1.7575387586e+03 | |
| 42 | 1,41,1.3962198347e+03 | |
| 43 | 1,42,1.1561875305e+03 | |
| 44 | 1,43,1.1117639269e+03 | |
| 45 | 1,44,1.1071405852e+03 | |
| 46 | 1,45,1.0362781210e+03 | |
| 47 | 1,46,8.6217025882e+02 | |
| 48 | 1,47,6.6687478556e+02 | |
| 49 | 1,48,5.8467291901e+02 | |
| 50 | 1,49,6.3405500638e+02 | |
| 51 | 1,50,5.7214327263e+02 | |
| 52 | 1,51,4.4839527442e+02 | |
| 53 | 1,52,4.7666999581e+02 | |
| 54 | 1,53,4.0777940116e+02 | |
| 55 | 1,54,3.9244889453e+02 | |
| 56 | 1,55,4.0144401461e+02 | |
| 57 | 1,56,3.1566351086e+02 | |
| 58 | 1,57,3.3433898004e+02 | |
| 59 | 1,58,3.3609232811e+02 | |
| 60 | 1,59,2.7267855555e+02 | |
| 61 | 1,60,2.5126925300e+02 | |
| 62 | 1,61,2.5824107656e+02 | |
| 63 | 1,62,2.2593401362e+02 | |
| 64 | 1,63,2.3576729224e+02 | |
| 65 | 1,64,2.0198025354e+02 | |
| 66 | 1,65,1.5516853902e+02 | |
| 67 | 1,66,1.3398117366e+02 | |
| 68 | 1,67,1.4050731068e+02 | |
| 69 | 1,68,1.2561808461e+02 | |
| 70 | 1,69,1.1484988780e+02 | |
| 71 | 1,70,1.0666335412e+02 | |
| 72 | 1,71,1.1114547981e+02 | |
| 73 | 1,72,1.0678057710e+02 | |
| 74 | 1,73,8.2328043430e+01 | |
| 75 | 1,74,8.3427317503e+01 | |
| 76 | 1,75,8.2349175262e+01 | |
| 77 | 1,76,8.7829840748e+01 | |
| 78 | 1,77,8.4571852159e+01 | |
| 79 | 1,78,7.0124647557e+01 | |
| 80 | 1,79,6.5373643130e+01 | |
| 81 | 1,80,5.8169906416e+01 | |
| 82 | 1,81,6.1301571861e+01 | |
| 83 | 1,82,5.5534280142e+01 | |
| 84 | 1,83,4.5679308632e+01 | |
| 85 | 1,84,4.2513125405e+01 | |
| 86 | 1,85,4.2915489843e+01 | |
| 87 | 1,86,3.9262418571e+01 | |
| 88 | 1,87,3.5789968299e+01 | |
| 89 | 1,88,3.8544810429e+01 | |
| 90 | 1,89,3.5704052761e+01 | |
| 91 | 1,90,4.0646370237e+01 | |
| 92 | 1,91,3.6495862290e+01 | |
| 93 | 1,92,3.1642073968e+01 | |
| 94 | 1,93,2.9887959528e+01 | |
| 95 | 1,94,2.5482015399e+01 | |
| 96 | 1,95,2.2125156043e+01 | |
| 97 | 1,96,2.2614542629e+01 | |
| 98 | 1,97,2.3867592979e+01 | |
| 99 | 1,98,2.0453533046e+01 | |
| 100 | 1,99,2.0417273067e+01 | |
| 101 | 1,100,1.8191242102e+01 | |
| 102 | 1,101,1.7617264131e+01 | |
| 103 | 1,102,1.7742051416e+01 | |
| 104 | 1,103,1.8988704915e+01 | |
| 105 | 1,104,1.7278825237e+01 | |
| 106 | 1,105,1.4154413566e+01 | |
| 107 | 1,106,1.3173121949e+01 | |
| 108 | 1,107,1.3074501049e+01 | |
| 109 | 1,108,1.2766463483e+01 | |
| 110 | 1,109,1.2462716364e+01 | |
| 111 | 1,110,1.2002103033e+01 | |
| 112 | 1,111,1.1330812292e+01 | |
| 113 | 1,112,1.0774347534e+01 | |
| 114 | 1,113,1.1275903020e+01 | |
| 115 | 1,114,1.0145344722e+01 | |
| 116 | 1,115,9.1570601635e+00 | |
| 117 | 1,116,8.2444195096e+00 | |
| 118 | 1,117,8.2688823331e+00 | |
| 119 | 1,118,8.4460148617e+00 | |
| 120 | 1,119,7.6839847172e+00 | |
| 121 | 1,120,7.2306187968e+00 | |
| 122 | 1,121,6.4465775578e+00 | |
| 123 | 1,122,6.2603653766e+00 | |
| 124 | 1,123,6.6260113499e+00 | |
| 125 | 1,124,6.0190351255e+00 | |
| 126 | 1,125,5.9843175748e+00 | |
| 127 | 1,126,5.6740750529e+00 | |
| 128 | 1,127,5.0135757834e+00 | |
| 129 | 1,128,4.8644454596e+00 | |
| 130 | 1,129,4.8526750560e+00 | |
| 131 | 1,130,4.5620389406e+00 | |
| 132 | 1,131,4.4145968392e+00 | |
| 133 | 1,132,4.0426044754e+00 | |
| 134 | 1,133,3.9268719385e+00 | |
| 135 | 1,134,3.8868830014e+00 | |
| 136 | 1,135,3.6983613563e+00 | |
| 137 | 1,136,3.4705924557e+00 | |
| 138 | 1,137,3.1742219327e+00 | |
| 139 | 1,138,2.9746865547e+00 | |
| 140 | 1,139,2.9294360511e+00 | |
| 141 | 1,140,2.6058619013e+00 | |
| 142 | 1,141,2.7924095657e+00 | |
| 143 | 1,142,2.5458609160e+00 | |
| 144 | 1,143,2.2988727260e+00 | |
| 145 | 1,144,2.2752195413e+00 | |
| 146 | 1,145,2.3071075527e+00 | |
| 147 | 1,146,2.2551078163e+00 | |
| 148 | 1,147,1.9741853072e+00 | |
| 149 | 1,148,1.8820117796e+00 | |
| 150 | 1,149,1.6429593995e+00 | |
| 151 | 1,150,1.6530976801e+00 | |
| 152 | 1,151,1.6656089236e+00 | |
| 153 | 1,152,1.5299707318e+00 | |
| 154 | 1,153,1.4341580145e+00 | |
| 155 | 1,154,1.3485144415e+00 | |
| 156 | 1,155,1.3268882723e+00 | |
| 157 | 1,156,1.3831834555e+00 | |
| 158 | 1,157,1.2972350432e+00 | |
| 159 | 1,158,1.1967101713e+00 | |
| 160 | 1,159,1.1913516369e+00 | |
| 161 | 1,160,1.0938427935e+00 | |
| 162 | 1,161,1.0661608946e+00 | |
| 163 | 1,162,1.0889176596e+00 | |
| 164 | 1,163,1.0138837064e+00 | |
| 165 | 1,164,1.0496260983e+00 | |
| 166 | 1,165,9.9669911585e-01 | |
| 167 | 1,166,8.5780262940e-01 | |
| 168 | 1,167,7.8865817309e-01 | |
| 169 | 1,168,8.4656878721e-01 | |
| 170 | 1,169,8.8039326575e-01 | |
| 171 | 1,170,7.9223811849e-01 | |
| 172 | 1,171,7.4477128169e-01 | |
| 173 | 1,172,7.0646978254e-01 | |
| 174 | 1,173,6.9546165342e-01 | |
| 175 | 1,174,6.9947167385e-01 | |
| 176 | 1,175,6.5800173611e-01 | |
| 177 | 1,176,6.2984747056e-01 | |
| 178 | 1,177,6.4941050695e-01 | |
| 179 | 1,178,6.2412391717e-01 | |
| 180 | 1,179,6.0823983302e-01 | |
| 181 | 1,180,6.0524767207e-01 | |
| 182 | 1,181,6.0732426540e-01 | |
| 183 | 1,182,6.3569603332e-01 | |
| 184 | 1,183,5.1618500071e-01 | |
| 185 | 1,184,4.6161517919e-01 | |
| 186 | 1,185,4.8688682435e-01 | |
| 187 | 1,186,4.7564408414e-01 | |
| 188 | 1,187,4.7866245270e-01 | |
| 189 | 1,188,4.7029531967e-01 | |
| 190 | 1,189,4.7544067263e-01 | |
| 191 | 1,190,4.3724810498e-01 | |
| 192 | 1,191,4.2926133774e-01 | |
| 193 | 1,192,4.3560845052e-01 | |
| 194 | 1,193,4.2026994512e-01 | |
| 195 | 1,194,3.7676942401e-01 | |
| 196 | 1,195,3.6714027906e-01 | |
| 197 | 1,196,3.3901654094e-01 | |
| 198 | 1,197,3.6785441508e-01 | |
| 199 | 1,198,3.6950833294e-01 | |
| 200 | 1,199,3.3445071732e-01 | |
| 201 | 1,200,3.3068534266e-01 | |
| 202 | 1,201,3.1117622883e-01 | |
| 203 | 1,202,3.0180124879e-01 | |
| 204 | 1,203,2.8834712870e-01 | |
| 205 | 1,204,2.7279765269e-01 | |
| 206 | 1,205,2.7939084984e-01 | |
| 207 | 1,206,2.7225105441e-01 | |
| 208 | 1,207,2.7006778354e-01 | |
| 209 | 1,208,2.7420656439e-01 | |
| 210 | 1,209,2.5326310668e-01 | |
| 211 | 1,210,2.2829225551e-01 | |
| 212 | 1,211,2.1756406706e-01 | |
| 213 | 1,212,2.3979176884e-01 | |
| 214 | 1,213,2.5424816224e-01 | |
| 215 | 1,214,2.2468795794e-01 | |
| 216 | 1,215,2.1032978086e-01 | |
| 217 | 1,216,2.0988521801e-01 | |
| 218 | 1,217,1.9207200320e-01 | |
| 219 | 1,218,1.8761875015e-01 | |
| 220 | 1,219,1.8139671682e-01 | |
| 221 | 1,220,1.8088781967e-01 | |
| 222 | 1,221,1.7063957385e-01 | |
| 223 | 1,222,1.7281823930e-01 | |
| 224 | 1,223,1.7140492569e-01 | |
| 225 | 1,224,1.5976378874e-01 | |
| 226 | 1,225,1.5632892835e-01 | |
| 227 | 1,226,1.5384229373e-01 | |
| 228 | 1,227,1.6326986864e-01 | |
| 229 | 1,228,1.4819962601e-01 | |
| 230 | 1,229,1.5443740533e-01 | |
| 231 | 1,230,1.4351063695e-01 | |
| 232 | 1,231,1.3776671969e-01 | |
| 233 | 1,232,1.2795128482e-01 | |
| 234 | 1,233,1.2641826438e-01 | |
| 235 | 1,234,1.2691971036e-01 | |
| 236 | 1,235,1.2848041836e-01 | |
| 237 | 1,236,1.2979685283e-01 | |
| 238 | 1,237,1.3405718112e-01 | |
| 239 | 1,238,1.3051965589e-01 | |
| 240 | 1,239,1.2209519618e-01 | |
| 241 | 1,240,1.2137005773e-01 | |
| 242 | 1,241,1.1253233721e-01 | |
| 243 | 1,242,1.0531723682e-01 | |
| 244 | 1,243,1.0816322317e-01 | |
| 245 | 1,244,1.0929459925e-01 | |
| 246 | 1,245,1.1437495588e-01 | |
| 247 | 1,246,1.0866405815e-01 | |
| 248 | 1,247,1.1293691862e-01 | |
| 249 | 1,248,1.1170492018e-01 | |
| 250 | 1,249,1.0992064311e-01 | |
| 251 | 1,250,1.0175068551e-01 | |
| 252 | 1,251,9.9210369964e-02 | |
| 253 | 1,252,9.6174696952e-02 | |
| 254 | 1,253,9.2776193557e-02 | |
| 255 | 1,254,9.3449644023e-02 | |
| 256 | 1,255,9.1796255959e-02 | |
| 257 | 2,1,2.1166750661e+08 | |
| 258 | 2,2,1.9965013098e+08 | |
| 259 | 2,3,1.0003484704e+08 | |
| 260 | 2,4,5.2351756758e+07 | |
| 261 | 2,5,1.5301537449e+07 | |
| 262 | 2,6,6.0936100083e+06 | |
| 263 | 2,7,4.7122566132e+06 | |
| 264 | 2,8,4.1918494243e+06 | |
| 265 | 2,9,3.5335311491e+06 | |
| 266 | 2,10,1.9708962195e+06 | |
| 267 | 2,11,9.8828442631e+05 | |
| 268 | 2,12,9.3459774178e+05 | |
| 269 | 2,13,5.3902309248e+05 | |
| 270 | 2,14,3.9952900304e+05 | |
| 271 | 2,15,3.3234072791e+05 | |
| 272 | 2,16,4.0705367202e+05 | |
| 273 | 2,17,2.6701327262e+05 | |
| 274 | 2,18,1.4454576073e+05 | |
| 275 | 2,19,1.5112876821e+05 | |
| 276 | 2,20,1.3520619251e+05 | |
| 277 | 2,21,1.0985089396e+05 | |
| 278 | 2,22,7.9017104738e+04 | |
| 279 | 2,23,7.1614812368e+04 | |
| 280 | 2,24,7.5542533381e+04 | |
| 281 | 2,25,5.5254239990e+04 | |
| 282 | 2,26,3.7367321531e+04 | |
| 283 | 2,27,3.2619298705e+04 | |
| 284 | 2,28,3.1902034365e+04 | |
| 285 | 2,29,2.6372185781e+04 | |
| 286 | 2,30,2.2970852615e+04 | |
| 287 | 2,31,1.8536923535e+04 | |
| 288 | 2,32,1.5648613382e+04 | |
| 289 | 2,33,1.5331685794e+04 | |
| 290 | 2,34,1.6112757627e+04 | |
| 291 | 2,35,1.4394929759e+04 | |
| 292 | 2,36,1.0263388692e+04 | |
| 293 | 2,37,1.0890923897e+04 | |
| 294 | 2,38,9.1652598708e+03 | |
| 295 | 2,39,6.9507049682e+03 | |
| 296 | 2,40,6.7268721450e+03 | |
| 297 | 2,41,5.3275089853e+03 | |
| 298 | 2,42,4.3976603864e+03 | |
| 299 | 2,43,4.2231797605e+03 | |
| 300 | 2,44,4.1992166367e+03 | |
| 301 | 2,45,3.9226064030e+03 | |
| 302 | 2,46,3.2503349463e+03 | |
| 303 | 2,47,2.5071053558e+03 | |
| 304 | 2,48,2.1925360050e+03 | |
| 305 | 2,49,2.3715806373e+03 | |
| 306 | 2,50,2.1326575362e+03 | |
| 307 | 2,51,1.6681111325e+03 | |
| 308 | 2,52,1.7680891385e+03 | |
| 309 | 2,53,1.5072124487e+03 | |
| 310 | 2,54,1.4447243693e+03 | |
| 311 | 2,55,1.4726346392e+03 | |
| 312 | 2,56,1.1549215001e+03 | |
| 313 | 2,57,1.2184421611e+03 | |
| 314 | 2,58,1.2238107274e+03 | |
| 315 | 2,59,9.9075853696e+02 | |
| 316 | 2,60,9.0817443827e+02 | |
| 317 | 2,61,9.2757503323e+02 | |
| 318 | 2,62,8.1084500667e+02 | |
| 319 | 2,63,8.4318622020e+02 | |
| 320 | 2,64,7.1928933548e+02 | |
| 321 | 2,65,5.5081444478e+02 | |
| 322 | 2,66,4.7420688757e+02 | |
| 323 | 2,67,4.9575454669e+02 | |
| 324 | 2,68,4.4099841327e+02 | |
| 325 | 2,69,4.0164590367e+02 | |
| 326 | 2,70,3.7090583603e+02 | |
| 327 | 2,71,3.8518980932e+02 | |
| 328 | 2,72,3.6895678109e+02 | |
| 329 | 2,73,2.8262197159e+02 | |
| 330 | 2,74,2.8577945310e+02 | |
| 331 | 2,75,2.8120083249e+02 | |
| 332 | 2,76,2.9865563595e+02 | |
| 333 | 2,77,2.8593931811e+02 | |
| 334 | 2,78,2.3644081940e+02 | |
| 335 | 2,79,2.1916439580e+02 | |
| 336 | 2,80,1.9371415858e+02 | |
| 337 | 2,81,2.0302234417e+02 | |
| 338 | 2,82,1.8318956780e+02 | |
| 339 | 2,83,1.5005815018e+02 | |
| 340 | 2,84,1.3921790873e+02 | |
| 341 | 2,85,1.4017198091e+02 | |
| 342 | 2,86,1.2759732322e+02 | |
| 343 | 2,87,1.1553624192e+02 | |
| 344 | 2,88,1.2395079820e+02 | |
| 345 | 2,89,1.1416693798e+02 | |
| 346 | 2,90,1.2889687236e+02 | |
| 347 | 2,91,1.1479558217e+02 | |
| 348 | 2,92,9.9488953451e+01 | |
| 349 | 2,93,9.3317958092e+01 | |
| 350 | 2,94,7.9156590292e+01 | |
| 351 | 2,95,6.8698125448e+01 | |
| 352 | 2,96,6.9751602617e+01 | |
| 353 | 2,97,7.3039665469e+01 | |
| 354 | 2,98,6.2310738550e+01 | |
| 355 | 2,99,6.1794001959e+01 | |
| 356 | 2,100,5.4424632028e+01 | |
| 357 | 2,101,5.2488172004e+01 | |
| 358 | 2,102,5.2689814158e+01 | |
| 359 | 2,103,5.6016915792e+01 | |
| 360 | 2,104,5.0718406045e+01 | |
| 361 | 2,105,4.1078182701e+01 | |
| 362 | 2,106,3.8088832093e+01 | |
| 363 | 2,107,3.7813824960e+01 | |
| 364 | 2,108,3.6512776620e+01 | |
| 365 | 2,109,3.5401542607e+01 | |
| 366 | 2,110,3.3941266789e+01 | |
| 367 | 2,111,3.1730285565e+01 | |
| 368 | 2,112,2.9821707986e+01 | |
| 369 | 2,113,3.1046226097e+01 | |
| 370 | 2,114,2.8104029138e+01 | |
| 371 | 2,115,2.5000876138e+01 | |
| 372 | 2,116,2.2396637125e+01 | |
| 373 | 2,117,2.2232415741e+01 | |
| 374 | 2,118,2.2612317332e+01 | |
| 375 | 2,119,2.0470474723e+01 | |
| 376 | 2,120,1.9099773706e+01 | |
| 377 | 2,121,1.6853108298e+01 | |
| 378 | 2,122,1.6272054720e+01 | |
| 379 | 2,123,1.7058096499e+01 | |
| 380 | 2,124,1.5472950690e+01 | |
| 381 | 2,125,1.5403553380e+01 | |
| 382 | 2,126,1.4453581010e+01 | |
| 383 | 2,127,1.2609190201e+01 | |
| 384 | 2,128,1.2034511080e+01 | |
| 385 | 2,129,1.1970297420e+01 | |
| 386 | 2,130,1.1250772968e+01 | |
| 387 | 2,131,1.0728105845e+01 | |
| 388 | 2,132,9.7282845723e+00 | |
| 389 | 2,133,9.4179943151e+00 | |
| 390 | 2,134,9.3346488432e+00 | |
| 391 | 2,135,8.8295857024e+00 | |
| 392 | 2,136,8.0773773794e+00 | |
| 393 | 2,137,7.2925151974e+00 | |
| 394 | 2,138,6.7977696813e+00 | |
| 395 | 2,139,6.6367369399e+00 | |
| 396 | 2,140,5.8534850025e+00 | |
| 397 | 2,141,6.2257969102e+00 | |
| 398 | 2,142,5.6486971687e+00 | |
| 399 | 2,143,5.0391004791e+00 | |
| 400 | 2,144,4.9713459201e+00 | |
| 401 | 2,145,4.9811082466e+00 | |
| 402 | 2,146,4.8008138145e+00 | |
| 403 | 2,147,4.1887372143e+00 | |
| 404 | 2,148,4.0063145844e+00 | |
| 405 | 2,149,3.4164984293e+00 | |
| 406 | 2,150,3.4382777938e+00 | |
| 407 | 2,151,3.4658307601e+00 | |
| 408 | 2,152,3.1500350956e+00 | |
| 409 | 2,153,2.9234982410e+00 | |
| 410 | 2,154,2.7104361732e+00 | |
| 411 | 2,155,2.6303030154e+00 | |
| 412 | 2,156,2.6996310033e+00 | |
| 413 | 2,157,2.4901206381e+00 | |
| 414 | 2,158,2.2730482136e+00 | |
| 415 | 2,159,2.2689655956e+00 | |
| 416 | 2,160,2.0560610219e+00 | |
| 417 | 2,161,1.9817070751e+00 | |
| 418 | 2,162,2.0025138191e+00 | |
| 419 | 2,163,1.8770484263e+00 | |
| 420 | 2,164,1.9021785855e+00 | |
| 421 | 2,165,1.7865330706e+00 | |
| 422 | 2,166,1.5248138231e+00 | |
| 423 | 2,167,1.3862624160e+00 | |
| 424 | 2,168,1.4423262417e+00 | |
| 425 | 2,169,1.4964447765e+00 | |
| 426 | 2,170,1.3324949886e+00 | |
| 427 | 2,171,1.2638609925e+00 | |
| 428 | 2,172,1.1895081404e+00 | |
| 429 | 2,173,1.1545715858e+00 | |
| 430 | 2,174,1.1512565745e+00 | |
| 431 | 2,175,1.0686751319e+00 | |
| 432 | 2,176,1.0167130208e+00 | |
| 433 | 2,177,1.0301844032e+00 | |
| 434 | 2,178,9.7364401270e-01 | |
| 435 | 2,179,9.4296041670e-01 | |
| 436 | 2,180,9.4689342088e-01 | |
| 437 | 2,181,9.1724741490e-01 | |
| 438 | 2,182,9.4962846795e-01 | |
| 439 | 2,183,7.7459698834e-01 | |
| 440 | 2,184,6.7952452942e-01 | |
| 441 | 2,185,7.0413529560e-01 | |
| 442 | 2,186,6.8890538587e-01 | |
| 443 | 2,187,6.6710770608e-01 | |
| 444 | 2,188,6.7212112845e-01 | |
| 445 | 2,189,6.7815489839e-01 | |
| 446 | 2,190,6.0349342381e-01 | |
| 447 | 2,191,5.7776203687e-01 | |
| 448 | 2,192,5.7915741129e-01 | |
| 449 | 2,193,5.5227466710e-01 | |
| 450 | 2,194,4.7400673653e-01 | |
| 451 | 2,195,4.6713881543e-01 | |
| 452 | 2,196,4.3215900281e-01 | |
| 453 | 2,197,4.5746089728e-01 | |
| 454 | 2,198,4.4698837220e-01 | |
| 455 | 2,199,3.9383728443e-01 | |
| 456 | 2,200,3.9028905895e-01 | |
| 457 | 2,201,3.6455352268e-01 | |
| 458 | 2,202,3.4923710159e-01 | |
| 459 | 2,203,3.3607933243e-01 | |
| 460 | 2,204,3.1294757459e-01 | |
| 461 | 2,205,3.1905091970e-01 | |
| 462 | 2,206,3.0567875689e-01 | |
| 463 | 2,207,2.9285048404e-01 | |
| 464 | 2,208,2.9243226640e-01 | |
| 465 | 2,209,2.7286663901e-01 | |
| 466 | 2,210,2.3812255317e-01 | |
| 467 | 2,211,2.2470368028e-01 | |
| 468 | 2,212,2.4280166992e-01 | |
| 469 | 2,213,2.4986546853e-01 | |
| 470 | 2,214,2.1254742945e-01 | |
| 471 | 2,215,1.9933915603e-01 | |
| 472 | 2,216,1.9978405567e-01 | |
| 473 | 2,217,1.8421198728e-01 | |
| 474 | 2,218,1.8118760764e-01 | |
| 475 | 2,219,1.7543553892e-01 | |
| 476 | 2,220,1.7553604066e-01 | |
| 477 | 2,221,1.6215593273e-01 | |
| 478 | 2,222,1.5616074965e-01 | |
| 479 | 2,223,1.5783401215e-01 | |
| 480 | 2,224,1.4578227737e-01 | |
| 481 | 2,225,1.3859739617e-01 | |
| 482 | 2,226,1.3378781539e-01 | |
| 483 | 2,227,1.3594866134e-01 | |
| 484 | 2,228,1.1868764224e-01 | |
| 485 | 2,229,1.2660184320e-01 | |
| 486 | 2,230,1.1586191996e-01 | |
| 487 | 2,231,1.0798440196e-01 | |
| 488 | 2,232,1.0367983463e-01 | |
| 489 | 2,233,1.0265450487e-01 | |
| 490 | 2,234,1.0200490437e-01 | |
| 491 | 2,235,1.0080538032e-01 | |
| 492 | 2,236,9.6921190467e-02 | |
| 493 | 2,237,9.7637375188e-02 | |
| 494 | 2,238,1.0016940953e-01 | |
| 495 | 2,239,9.3430736715e-02 | |
| 496 | 2,240,8.5240878376e-02 | |
| 497 | 2,241,7.7028960356e-02 | |
| 498 | 2,242,7.0937331919e-02 | |
| 499 | 2,243,6.9616802923e-02 | |
| 500 | 2,244,7.6174869319e-02 | |
| 501 | 2,245,7.9457414530e-02 | |
| 502 | 2,246,6.8759904135e-02 | |
| 503 | 2,247,6.8978486042e-02 | |
| 504 | 2,248,6.7173378321e-02 | |
| 505 | 2,249,6.5340945649e-02 | |
| 506 | 2,250,6.2883919499e-02 | |
| 507 | 2,251,6.3748426001e-02 | |
| 508 | 2,252,5.9001676326e-02 | |
| 509 | 2,253,6.0395213626e-02 | |
| 510 | 2,254,5.9355367692e-02 | |
| 511 | 2,255,5.4500158346e-02 | |
| 512 | 2,256,5.1718965475e-02 | |
| 513 | 2,257,5.2215230984e-02 | |
| 514 | 2,258,4.9061080738e-02 | |
| 515 | 2,259,4.7318256433e-02 | |
| 516 | 2,260,4.7323267536e-02 | |
| 517 | 2,261,4.5747619399e-02 | |
| 518 | 2,262,4.8039840867e-02 | |
| 519 | 2,263,4.9807224594e-02 | |
| 520 | 2,264,4.8680920925e-02 | |
| 521 | 2,265,4.4984455688e-02 | |
| 522 | 2,266,4.4766922998e-02 | |
| 523 | 2,267,4.8585485921e-02 | |
| 524 | 2,268,4.6609465828e-02 | |
| 525 | 2,269,4.2740603576e-02 | |
| 526 | 2,270,4.3165717866e-02 | |
| 527 | 2,271,4.0791822952e-02 | |
| 528 | 2,272,4.0842391458e-02 | |
| 529 | 2,273,3.9466634141e-02 | |
| 530 | 2,274,3.9755914004e-02 | |
| 531 | 2,275,3.5402762676e-02 | |
| 532 | 2,276,3.7042969219e-02 | |
| 533 | 2,277,3.8554056112e-02 | |
| 534 | 2,278,4.1308534766e-02 | |
| 535 | 2,279,3.7472918863e-02 | |
| 536 | 2,280,3.4808034909e-02 | |
| 537 | 2,281,3.4550059621e-02 | |
| 538 | 2,282,3.3208044683e-02 | |
| 539 | 2,283,3.4952787751e-02 | |
| 540 | 2,284,3.3595796644e-02 | |
| 541 | 2,285,3.2172252964e-02 | |
| 542 | 2,286,3.2729763796e-02 | |
| 543 | 2,287,3.1329104366e-02 | |
| 544 | 2,288,3.3575765285e-02 | |
| 545 | 2,289,3.5448577491e-02 | |
| 546 | 2,290,3.3997431301e-02 | |
| 547 | 2,291,3.2930170654e-02 | |
| 548 | 2,292,3.2976022281e-02 | |
| 549 | 2,293,3.5132527650e-02 | |
| 550 | 2,294,3.5088106628e-02 | |
| 551 | 2,295,3.2785855301e-02 | |
| 552 | 2,296,3.0302161601e-02 | |
| 553 | 2,297,3.0400711133e-02 | |
| 554 | 2,298,3.2077132309e-02 | |
| 555 | 2,299,3.3464837798e-02 | |
| 556 | 2,300,3.5619135464e-02 | |
| 557 | 2,301,3.1483126782e-02 | |
| 558 | 2,302,2.7745855453e-02 | |
| 559 | 2,303,2.8398321111e-02 | |
| 560 | 2,304,2.9635044217e-02 | |
| 561 | 2,305,3.0842041736e-02 | |
| 562 | 2,306,3.2843008162e-02 | |
| 563 | 2,307,3.1011084174e-02 | |
| 564 | 2,308,3.1438384931e-02 | |
| 565 | 2,309,3.1197820159e-02 | |
| 566 | 2,310,3.0164917355e-02 | |
| 567 | 2,311,3.1534390969e-02 | |
| 568 | 2,312,2.9570248565e-02 | |
| 569 | 2,313,3.0209312279e-02 | |
| 570 | 2,314,3.3076318239e-02 | |
| 571 | 2,315,3.0284751612e-02 | |
| 572 | 2,316,2.8306866909e-02 | |
| 573 | 2,317,3.2842882270e-02 | |
| 574 | 2,318,3.0171094665e-02 | |
| 575 | 2,319,2.8259205782e-02 | |
| 576 | 2,320,3.0436313415e-02 | |
| 577 | 2,321,2.7993716174e-02 | |
| 578 | 2,322,2.7472081222e-02 | |
| 579 | 2,323,3.0850171124e-02 | |
| 580 | 2,324,3.0473955196e-02 | |
| 581 | 2,325,2.8617354506e-02 | |
| 582 | 2,326,2.8221787731e-02 | |
| 583 | 2,327,2.8565119382e-02 | |
| 584 | 2,328,3.1464711160e-02 | |
| 585 | 2,329,3.0352831780e-02 | |
| 586 | 2,330,2.9335153896e-02 | |
| 587 | 2,331,2.8882465215e-02 | |
| 588 | 2,332,3.1550298599e-02 | |
| 589 | 2,333,3.0365159720e-02 | |
| 590 | 2,334,2.8147231381e-02 | |
| 591 | 2,335,2.5886812773e-02 | |
| 592 | 2,336,2.6880814894e-02 | |
| 593 | 2,337,2.8177857914e-02 | |
| 594 | 2,338,2.7713262493e-02 | |
| 595 | 2,339,2.8198079711e-02 | |
| 596 | 2,340,2.7870888471e-02 | |
| 597 | 2,341,2.7172702968e-02 | |
| 598 | 2,342,2.5689449622e-02 | |
| 599 | 2,343,2.5676703819e-02 | |
| 600 | 2,344,2.6794589249e-02 | |
| 601 | 2,345,2.5674701906e-02 | |
| 602 | 2,346,2.5704760034e-02 | |
| 603 | 2,347,2.7069945536e-02 | |
| 604 | 2,348,2.6014199887e-02 | |
| 605 | 2,349,2.8265524347e-02 | |
| 606 | 2,350,2.9711618212e-02 | |
| 607 | 2,351,2.8050792098e-02 | |
| 608 | 2,352,2.7764322488e-02 | |
| 609 | 2,353,2.6992552784e-02 | |
| 610 | 2,354,2.5430685037e-02 | |
| 611 | 2,355,2.5634756364e-02 | |
| 612 | 2,356,2.4990882885e-02 | |
| 613 | 2,357,2.5323572414e-02 | |
| 614 | 2,358,2.6877895828e-02 | |
| 615 | 2,359,2.8100644264e-02 | |
| 616 | 2,360,2.6722160687e-02 | |
| 617 | 2,361,2.9133243715e-02 | |
| 618 | 2,362,2.8054182477e-02 | |
| 619 | 2,363,2.6844035354e-02 | |
| 620 | 2,364,2.5823786623e-02 | |
| 621 | 2,365,2.7933172379e-02 | |
| 622 | 2,366,2.8319625104e-02 | |
| 623 | 2,367,2.7837864497e-02 | |
| 624 | 2,368,2.7302581372e-02 | |
| 625 | 2,369,2.6336825275e-02 | |
| 626 | 2,370,2.8771423634e-02 | |
| 627 | 2,371,2.7254742795e-02 | |
| 628 | 2,372,2.5337675033e-02 | |
| 629 | 2,373,2.6433038904e-02 | |
| 630 | 2,374,2.6823446490e-02 | |
| 631 | 2,375,2.6783471305e-02 | |
| 632 | 2,376,2.7726577140e-02 | |
| 633 | 2,377,2.8575114858e-02 | |
| 634 | 2,378,2.7310870203e-02 | |
| 635 | 2,379,2.5317249497e-02 | |
| 636 | 2,380,2.4753216890e-02 | |
| 637 | 2,381,2.6012883428e-02 | |
| 638 | 2,382,2.7712538915e-02 | |
| 639 | 2,383,2.8232118792e-02 | |
| 640 | 2,384,2.7972044305e-02 | |
| 641 | 2,385,2.8202057330e-02 | |
| 642 | 2,386,2.6499561611e-02 | |
| 643 | 2,387,2.9658677827e-02 | |
| 644 | 2,388,2.7619496688e-02 | |
| 645 | 2,389,2.5937621728e-02 | |
| 646 | 2,390,2.6699641586e-02 | |
| 647 | 2,391,2.4727247534e-02 | |
| 648 | 2,392,2.5694193653e-02 | |
| 649 | 2,393,2.4458392686e-02 | |
| 650 | 2,394,2.7447700934e-02 | |
| 651 | 2,395,2.9188800935e-02 | |
| 652 | 2,396,2.4633424659e-02 | |
| 653 | 2,397,2.4959469548e-02 | |
| 654 | 2,398,2.4999526430e-02 | |
| 655 | 2,399,2.5372175861e-02 | |
| 656 | 2,400,2.4321316854e-02 | |
| 657 | 2,401,2.3875470538e-02 | |
| 658 | 2,402,2.4929390663e-02 | |
| 659 | 2,403,2.4587578760e-02 | |
| 660 | 2,404,2.6099280918e-02 | |
| 661 | 2,405,2.5924712812e-02 | |
| 662 | 2,406,2.4808739398e-02 | |
| 663 | 2,407,2.4839591277e-02 | |
| 664 | 2,408,2.4431588397e-02 | |
| 665 | 2,409,2.5074770561e-02 | |
| 666 | 2,410,2.4606007232e-02 | |
| 667 | 2,411,2.3429246557e-02 | |
| 668 | 2,412,2.3091252328e-02 | |
| 669 | 2,413,2.3925847921e-02 | |
| 670 | 2,414,2.3692456124e-02 | |
| 671 | 2,415,2.3666042278e-02 | |
| 672 | 2,416,2.2725566595e-02 | |
| 673 | 2,417,2.1207708222e-02 | |
| 674 | 2,418,2.2694509659e-02 | |
| 675 | 2,419,2.3407455362e-02 | |
| 676 | 2,420,2.2895790050e-02 | |
| 677 | 2,421,2.3393704871e-02 | |
| 678 | 2,422,2.3689799477e-02 | |
| 679 | 2,423,2.3804880768e-02 | |
| 680 | 2,424,2.0811193897e-02 | |
| 681 | 2,425,1.9788122244e-02 | |
| 682 | 2,426,1.9883499968e-02 | |
| 683 | 2,427,2.0719372616e-02 | |
| 684 | 2,428,1.9457503792e-02 | |
| 685 | 2,429,1.8559705983e-02 | |
| 686 | 2,430,2.0754765636e-02 | |
| 687 | 2,431,2.0853984730e-02 | |
| 688 | 2,432,1.8939436402e-02 | |
| 689 | 2,433,1.9195893277e-02 | |
| 690 | 2,434,2.0232873916e-02 | |
| 691 | 2,435,2.0352535461e-02 | |
| 692 | 2,436,1.9613599324e-02 | |
| 693 | 2,437,1.9996788509e-02 | |
| 694 | 2,438,2.0764963654e-02 | |
| 695 | 2,439,1.7847661082e-02 | |
| 696 | 2,440,1.8557645551e-02 | |
| 697 | 2,441,1.8416080372e-02 | |
| 698 | 2,442,1.7977264592e-02 | |
| 699 | 2,443,1.7295445534e-02 | |
| 700 | 2,444,1.6889339416e-02 | |
| 701 | 2,445,1.7620188919e-02 | |
| 702 | 2,446,1.7000626476e-02 | |
| 703 | 2,447,1.6607322258e-02 | |
| 704 | 2,448,1.7758860921e-02 | |
| 705 | 2,449,1.8099685281e-02 | |
| 706 | 2,450,1.7484058117e-02 | |
| 707 | 2,451,1.5708037902e-02 | |
| 708 | 2,452,1.6482884825e-02 | |
| 709 | 2,453,1.6638442843e-02 | |
| 710 | 2,454,1.5568421536e-02 | |
| 711 | 2,455,1.5413506424e-02 | |
| 712 | 2,456,1.5018994834e-02 | |
| 713 | 2,457,1.5304492354e-02 | |
| 714 | 2,458,1.3963315621e-02 | |
| 715 | 2,459,1.5215812161e-02 | |
| 716 | 2,460,1.5289013495e-02 | |
| 717 | 2,461,1.4331326052e-02 | |
| 718 | 2,462,1.5940948636e-02 | |
| 719 | 2,463,1.4925809782e-02 | |
| 720 | 2,464,1.3602279753e-02 | |
| 721 | 2,465,1.3966262765e-02 | |
| 722 | 2,466,1.4595484584e-02 | |
| 723 | 2,467,1.4365708459e-02 | |
| 724 | 2,468,1.3805158446e-02 | |
| 725 | 2,469,1.3472443740e-02 | |
| 726 | 2,470,1.3607450216e-02 | |
| 727 | 2,471,1.4051787042e-02 | |
| 728 | 2,472,1.3737747227e-02 | |
| 729 | 2,473,1.3338473671e-02 | |
| 730 | 2,474,1.2793768721e-02 | |
| 731 | 2,475,1.3604357985e-02 | |
| 732 | 2,476,1.3611627263e-02 | |
| 733 | 2,477,1.4769810798e-02 | |
| 734 | 2,478,1.3465458426e-02 | |
| 735 | 2,479,1.2908418232e-02 | |
| 736 | 2,480,1.3154417561e-02 | |
| 737 | 2,481,1.2557361016e-02 | |
| 738 | 2,482,1.1931601623e-02 | |
| 739 | 2,483,1.2204970812e-02 | |
| 740 | 2,484,1.2764471992e-02 | |
| 741 | 2,485,1.2962844931e-02 | |
| 742 | 2,486,1.3121416244e-02 | |
| 743 | 2,487,1.2693539821e-02 | |
| 744 | 2,488,1.2904801778e-02 | |
| 745 | 2,489,1.2166050483e-02 | |
| 746 | 2,490,1.2262270641e-02 | |
| 747 | 2,491,1.2010828554e-02 | |
| 748 | 2,492,1.1648179331e-02 | |
| 749 | 2,493,1.2259260451e-02 | |
| 750 | 2,494,1.2268628262e-02 | |
| 751 | 2,495,1.2409777856e-02 | |
| 752 | 2,496,1.1833814722e-02 | |
| 753 | 2,497,1.1173233881e-02 | |
| 754 | 2,498,1.1920641562e-02 | |
| 755 | 2,499,1.1943322300e-02 | |
| 756 | 2,500,1.1337419422e-02 | |
| 757 | 2,501,1.1822188890e-02 | |
| 758 | 2,502,1.1753550669e-02 | |
| 759 | 2,503,1.2147537357e-02 | |
| 760 | 2,504,1.1933785462e-02 | |
| 761 | 2,505,1.1911833896e-02 | |
| 762 | 2,506,1.1619371078e-02 | |
| 763 | 2,507,1.2048163361e-02 | |
| 764 | 2,508,1.2211042789e-02 | |
| 765 | 2,509,1.1675572368e-02 | |
| 766 | 2,510,1.0930087045e-02 | |
| 767 | 2,511,1.1431282353e-02 | |
| 768 | 4,1,8.4853048427e+08 | |
| 769 | 4,2,7.9960613877e+08 | |
| 770 | 4,3,4.0031692795e+08 | |
| 771 | 4,4,2.0957015415e+08 | |
| 772 | 4,5,6.1254905679e+07 | |
| 773 | 4,6,2.4400059959e+07 | |
| 774 | 4,7,1.8870345320e+07 | |
| 775 | 4,8,1.6784382939e+07 | |
| 776 | 4,9,1.4148484712e+07 | |
| 777 | 4,10,7.8916755600e+06 | |
| 778 | 4,11,3.9575976267e+06 | |
| 779 | 4,12,3.7437562795e+06 | |
| 780 | 4,13,2.1592686768e+06 | |
| 781 | 4,14,1.6005388834e+06 | |
| 782 | 4,15,1.3309473729e+06 | |
| 783 | 4,16,1.6307276212e+06 | |
| 784 | 4,17,1.0694861062e+06 | |
| 785 | 4,18,5.7914856320e+05 | |
| 786 | 4,19,6.0568642424e+05 | |
| 787 | 4,20,5.4176946754e+05 | |
| 788 | 4,21,4.4029035819e+05 | |
| 789 | 4,22,3.1671321525e+05 | |
| 790 | 4,23,2.8705000626e+05 | |
| 791 | 4,24,3.0281956071e+05 | |
| 792 | 4,25,2.2152237074e+05 | |
| 793 | 4,26,1.4988064393e+05 | |
| 794 | 4,27,1.3081378030e+05 | |
| 795 | 4,28,1.2796964364e+05 | |
| 796 | 4,29,1.0577161601e+05 | |
| 797 | 4,30,9.2151589193e+04 | |
| 798 | 4,31,7.4393141760e+04 | |
| 799 | 4,32,6.2802964473e+04 | |
| 800 | 4,33,6.1543581646e+04 | |
| 801 | 4,34,6.4702970299e+04 | |
| 802 | 4,35,5.7796580979e+04 | |
| 803 | 4,36,4.1204250714e+04 | |
| 804 | 4,37,4.3754202520e+04 | |
| 805 | 4,38,3.6827336016e+04 | |
| 806 | 4,39,2.7915914118e+04 | |
| 807 | 4,40,2.7025266623e+04 | |
| 808 | 4,41,2.1412766798e+04 | |
| 809 | 4,42,1.7682176003e+04 | |
| 810 | 4,43,1.6984014484e+04 | |
| 811 | 4,44,1.6883775557e+04 | |
| 812 | 4,45,1.5771176496e+04 | |
| 813 | 4,46,1.3077817147e+04 | |
| 814 | 4,47,1.0091093854e+04 | |
| 815 | 4,48,8.8257586866e+03 | |
| 816 | 4,49,9.5490698304e+03 | |
| 817 | 4,50,8.5912037274e+03 | |
| 818 | 4,51,6.7187284625e+03 | |
| 819 | 4,52,7.1218824072e+03 | |
| 820 | 4,53,6.0749720629e+03 | |
| 821 | 4,54,5.8270884591e+03 | |
| 822 | 4,55,5.9441781137e+03 | |
| 823 | 4,56,4.6588009450e+03 | |
| 824 | 4,57,4.9180581425e+03 | |
| 825 | 4,58,4.9381308076e+03 | |
| 826 | 4,59,3.9981387808e+03 | |
| 827 | 4,60,3.6687980002e+03 | |
| 828 | 4,61,3.7473114501e+03 | |
| 829 | 4,62,3.2736640443e+03 | |
| 830 | 4,63,3.4075266763e+03 | |
| 831 | 4,64,2.9075398938e+03 | |
| 832 | 4,65,2.2264210439e+03 | |
| 833 | 4,66,1.9180354256e+03 | |
| 834 | 4,67,2.0057380653e+03 | |
| 835 | 4,68,1.7857883092e+03 | |
| 836 | 4,69,1.6279373282e+03 | |
| 837 | 4,70,1.5038455357e+03 | |
| 838 | 4,71,1.5627518518e+03 | |
| 839 | 4,72,1.4975294868e+03 | |
| 840 | 4,73,1.1477325518e+03 | |
| 841 | 4,74,1.1592034097e+03 | |
| 842 | 4,75,1.1412547139e+03 | |
| 843 | 4,76,1.2122027527e+03 | |
| 844 | 4,77,1.1613621267e+03 | |
| 845 | 4,78,9.5886509891e+02 | |
| 846 | 4,79,8.9017513669e+02 | |
| 847 | 4,80,7.8808228020e+02 | |
| 848 | 4,81,8.2728328829e+02 | |
| 849 | 4,82,7.4688204244e+02 | |
| 850 | 4,83,6.1130327220e+02 | |
| 851 | 4,84,5.6717582248e+02 | |
| 852 | 4,85,5.7031125152e+02 | |
| 853 | 4,86,5.1905911969e+02 | |
| 854 | 4,87,4.6988432496e+02 | |
| 855 | 4,88,5.0467378531e+02 | |
| 856 | 4,89,4.6511863642e+02 | |
| 857 | 4,90,5.2577112138e+02 | |
| 858 | 4,91,4.6905695435e+02 | |
| 859 | 4,92,4.0642635228e+02 | |
| 860 | 4,93,3.8200578498e+02 | |
| 861 | 4,94,3.2404977590e+02 | |
| 862 | 4,95,2.8025576308e+02 | |
| 863 | 4,96,2.8497799181e+02 | |
| 864 | 4,97,2.9890091705e+02 | |
| 865 | 4,98,2.5513532050e+02 | |
| 866 | 4,99,2.5292252921e+02 | |
| 867 | 4,100,2.2306213973e+02 | |
| 868 | 4,101,2.1548522247e+02 | |
| 869 | 4,102,2.1656886873e+02 | |
| 870 | 4,103,2.3003911530e+02 | |
| 871 | 4,104,2.0826110459e+02 | |
| 872 | 4,105,1.6898063495e+02 | |
| 873 | 4,106,1.5693796339e+02 | |
| 874 | 4,107,1.5571626145e+02 | |
| 875 | 4,108,1.4991633145e+02 | |
| 876 | 4,109,1.4578930932e+02 | |
| 877 | 4,110,1.4006551813e+02 | |
| 878 | 4,111,1.3134803571e+02 | |
| 879 | 4,112,1.2341941987e+02 | |
| 880 | 4,113,1.2807004182e+02 | |
| 881 | 4,114,1.1549869642e+02 | |
| 882 | 4,115,1.0314709571e+02 | |
| 883 | 4,116,9.2822067237e+01 | |
| 884 | 4,117,9.2443235928e+01 | |
| 885 | 4,118,9.3434087036e+01 | |
| 886 | 4,119,8.4693793545e+01 | |
| 887 | 4,120,7.9297507424e+01 | |
| 888 | 4,121,6.9952279333e+01 | |
| 889 | 4,122,6.7762191526e+01 | |
| 890 | 4,123,7.1122217338e+01 | |
| 891 | 4,124,6.4460086281e+01 | |
| 892 | 4,125,6.3966000330e+01 | |
| 893 | 4,126,5.9977094872e+01 | |
| 894 | 4,127,5.2694682215e+01 | |
| 895 | 4,128,5.0178944407e+01 | |
| 896 | 4,129,4.9818200650e+01 | |
| 897 | 4,130,4.6859902101e+01 | |
| 898 | 4,131,4.4881669911e+01 | |
| 899 | 4,132,4.0782017491e+01 | |
| 900 | 4,133,3.9285110475e+01 | |
| 901 | 4,134,3.8793388526e+01 | |
| 902 | 4,135,3.6773925632e+01 | |
| 903 | 4,136,3.3914995970e+01 | |
| 904 | 4,137,3.0829205527e+01 | |
| 905 | 4,138,2.8607745394e+01 | |
| 906 | 4,139,2.7864754806e+01 | |
| 907 | 4,140,2.4770055340e+01 | |
| 908 | 4,141,2.6316306296e+01 | |
| 909 | 4,142,2.3862904270e+01 | |
| 910 | 4,143,2.1139466683e+01 | |
| 911 | 4,144,2.0951317337e+01 | |
| 912 | 4,145,2.1136046628e+01 | |
| 913 | 4,146,2.0376454970e+01 | |
| 914 | 4,147,1.7658408459e+01 | |
| 915 | 4,148,1.6856011745e+01 | |
| 916 | 4,149,1.4543431703e+01 | |
| 917 | 4,150,1.4631018058e+01 | |
| 918 | 4,151,1.4666505599e+01 | |
| 919 | 4,152,1.3333930337e+01 | |
| 920 | 4,153,1.2282396395e+01 | |
| 921 | 4,154,1.1537953370e+01 | |
| 922 | 4,155,1.1210180321e+01 | |
| 923 | 4,156,1.1387503453e+01 | |
| 924 | 4,157,1.0575740555e+01 | |
| 925 | 4,158,9.6976913699e+00 | |
| 926 | 4,159,9.6437844763e+00 | |
| 927 | 4,160,8.7114852109e+00 | |
| 928 | 4,161,8.3975006234e+00 | |
| 929 | 4,162,8.4518602039e+00 | |
| 930 | 4,163,7.9887242739e+00 | |
| 931 | 4,164,8.2424988191e+00 | |
| 932 | 4,165,7.7109157205e+00 | |
| 933 | 4,166,6.5203704245e+00 | |
| 934 | 4,167,5.9485498489e+00 | |
| 935 | 4,168,6.2398200717e+00 | |
| 936 | 4,169,6.4761019847e+00 | |
| 937 | 4,170,5.7679434956e+00 | |
| 938 | 4,171,5.4497367819e+00 | |
| 939 | 4,172,5.1109396458e+00 | |
| 940 | 4,173,4.9878840148e+00 | |
| 941 | 4,174,4.9407753818e+00 | |
| 942 | 4,175,4.5815082601e+00 | |
| 943 | 4,176,4.3610851080e+00 | |
| 944 | 4,177,4.4122558220e+00 | |
| 945 | 4,178,4.2271332125e+00 | |
| 946 | 4,179,4.0925955563e+00 | |
| 947 | 4,180,4.0515909876e+00 | |
| 948 | 4,181,3.9514934928e+00 | |
| 949 | 4,182,4.1055066479e+00 | |
| 950 | 4,183,3.3332811881e+00 | |
| 951 | 4,184,2.9641322211e+00 | |
| 952 | 4,185,3.0402786163e+00 | |
| 953 | 4,186,2.9438292595e+00 | |
| 954 | 4,187,2.9257917054e+00 | |
| 955 | 4,188,2.8973835447e+00 | |
| 956 | 4,189,2.9110510212e+00 | |
| 957 | 4,190,2.6131662939e+00 | |
| 958 | 4,191,2.5432733809e+00 | |
| 959 | 4,192,2.5152822278e+00 | |
| 960 | 4,193,2.3816322469e+00 | |
| 961 | 4,194,2.0440223664e+00 | |
| 962 | 4,195,2.0108650143e+00 | |
| 963 | 4,196,1.8570516508e+00 | |
| 964 | 4,197,1.9621578573e+00 | |
| 965 | 4,198,1.9416222596e+00 | |
| 966 | 4,199,1.7385390143e+00 | |
| 967 | 4,200,1.7359643002e+00 | |
| 968 | 4,201,1.6367820829e+00 | |
| 969 | 4,202,1.5110685281e+00 | |
| 970 | 4,203,1.4607468577e+00 | |
| 971 | 4,204,1.3435162901e+00 | |
| 972 | 4,205,1.3874271416e+00 | |
| 973 | 4,206,1.3549813059e+00 | |
| 974 | 4,207,1.3017354570e+00 | |
| 975 | 4,208,1.3121726672e+00 | |
| 976 | 4,209,1.1918856756e+00 | |
| 977 | 4,210,1.0318277697e+00 | |
| 978 | 4,211,9.8296168567e-01 | |
| 979 | 4,212,1.0792466546e+00 | |
| 980 | 4,213,1.1037738072e+00 | |
| 981 | 4,214,9.5486269644e-01 | |
| 982 | 4,215,8.6447595922e-01 | |
| 983 | 4,216,8.4268088492e-01 | |
| 984 | 4,217,7.9288689336e-01 | |
| 985 | 4,218,7.7252122393e-01 | |
| 986 | 4,219,7.4854413116e-01 | |
| 987 | 4,220,7.4753502601e-01 | |
| 988 | 4,221,6.8348967814e-01 | |
| 989 | 4,222,6.9298423832e-01 | |
| 990 | 4,223,6.8223978342e-01 | |
| 991 | 4,224,6.2272618688e-01 | |
| 992 | 4,225,5.9371642639e-01 | |
| 993 | 4,226,5.7540579055e-01 | |
| 994 | 4,227,5.9801631365e-01 | |
| 995 | 4,228,5.2374680041e-01 | |
| 996 | 4,229,5.3503542646e-01 | |
| 997 | 4,230,4.9542819695e-01 | |
| 998 | 4,231,4.6451444951e-01 | |
| 999 | 4,232,4.3435965439e-01 | |
| 1000 | 4,233,4.3706717259e-01 | |
| 1001 | 4,234,4.3347944059e-01 | |
| 1002 | 4,235,4.2750045265e-01 | |
| 1003 | 4,236,4.1580673320e-01 | |
| 1004 | 4,237,4.1482159384e-01 | |
| 1005 | 4,238,4.0163303293e-01 | |
| 1006 | 4,239,3.7368978101e-01 | |
| 1007 | 4,240,3.5412255960e-01 | |
| 1008 | 4,241,3.2972592273e-01 | |
| 1009 | 4,242,3.0397892270e-01 | |
| 1010 | 4,243,2.9500239886e-01 | |
| 1011 | 4,244,3.1184526703e-01 | |
| 1012 | 4,245,3.2538618343e-01 | |
| 1013 | 4,246,2.8510384273e-01 | |
| 1014 | 4,247,2.9212993537e-01 | |
| 1015 | 4,248,2.9659728999e-01 | |
| 1016 | 4,249,2.9112699363e-01 | |
| 1017 | 4,250,2.6242625514e-01 | |
| 1018 | 4,251,2.5617054101e-01 | |
| 1019 | 4,252,2.5085092129e-01 | |
| 1020 | 4,253,2.4332254784e-01 | |
| 1021 | 4,254,2.3149222287e-01 | |
| 1022 | 4,255,2.1531942083e-01 | |
| 1023 | 4,256,1.9850258000e-01 | |
| 1024 | 4,257,1.9808156363e-01 | |
| 1025 | 4,258,1.8423707181e-01 | |
| 1026 | 4,259,1.9322558968e-01 | |
| 1027 | 4,260,1.8510121328e-01 | |
| 1028 | 4,261,1.8113515226e-01 | |
| 1029 | 4,262,1.9251151922e-01 | |
| 1030 | 4,263,2.0357788023e-01 | |
| 1031 | 4,264,1.9350586510e-01 | |
| 1032 | 4,265,1.8324996501e-01 | |
| 1033 | 4,266,1.8258075407e-01 | |
| 1034 | 4,267,1.8549012616e-01 | |
| 1035 | 4,268,1.7781640460e-01 | |
| 1036 | 4,269,1.6921271465e-01 | |
| 1037 | 4,270,1.6365072929e-01 | |
| 1038 | 4,271,1.5565727844e-01 | |
| 1039 | 4,272,1.5333562587e-01 | |
| 1040 | 4,273,1.5521345597e-01 | |
| 1041 | 4,274,1.5702623274e-01 | |
| 1042 | 4,275,1.4375905612e-01 | |
| 1043 | 4,276,1.5636500729e-01 | |
| 1044 | 4,277,1.5158802343e-01 | |
| 1045 | 4,278,1.5981014400e-01 | |
| 1046 | 4,279,1.4794625879e-01 | |
| 1047 | 4,280,1.4165869243e-01 | |
| 1048 | 4,281,1.3584856917e-01 | |
| 1049 | 4,282,1.3343407431e-01 | |
| 1050 | 4,283,1.3949191602e-01 | |
| 1051 | 4,284,1.3149667696e-01 | |
| 1052 | 4,285,1.2512731563e-01 | |
| 1053 | 4,286,1.3137740373e-01 | |
| 1054 | 4,287,1.2350517002e-01 | |
| 1055 | 4,288,1.2956800046e-01 | |
| 1056 | 4,289,1.3985529203e-01 | |
| 1057 | 4,290,1.3926338474e-01 | |
| 1058 | 4,291,1.3125982110e-01 | |
| 1059 | 4,292,1.3463502230e-01 | |
| 1060 | 4,293,1.4138281905e-01 | |
| 1061 | 4,294,1.3522615857e-01 | |
| 1062 | 4,295,1.2767154094e-01 | |
| 1063 | 4,296,1.3136616839e-01 | |
| 1064 | 4,297,1.3219938404e-01 | |
| 1065 | 4,298,1.3117897925e-01 | |
| 1066 | 4,299,1.2879817932e-01 | |
| 1067 | 4,300,1.4252408696e-01 | |
| 1068 | 4,301,1.3096281003e-01 | |
| 1069 | 4,302,1.1914667140e-01 | |
| 1070 | 4,303,1.1833711113e-01 | |
| 1071 | 4,304,1.2230279776e-01 | |
| 1072 | 4,305,1.2210515401e-01 | |
| 1073 | 4,306,1.2776663054e-01 | |
| 1074 | 4,307,1.2688858913e-01 | |
| 1075 | 4,308,1.3511292602e-01 | |
| 1076 | 4,309,1.2337543768e-01 | |
| 1077 | 4,310,1.2044439982e-01 | |
| 1078 | 4,311,1.2828932254e-01 | |
| 1079 | 4,312,1.1854362913e-01 | |
| 1080 | 4,313,1.1236404717e-01 | |
| 1081 | 4,314,1.2017164469e-01 | |
| 1082 | 4,315,1.1990352057e-01 | |
| 1083 | 4,316,1.1444759221e-01 | |
| 1084 | 4,317,1.2133803345e-01 | |
| 1085 | 4,318,1.1298109591e-01 | |
| 1086 | 4,319,1.1684457997e-01 | |
| 1087 | 4,320,1.2541840901e-01 | |
| 1088 | 4,321,1.2113273536e-01 | |
| 1089 | 4,322,1.1560240203e-01 | |
| 1090 | 4,323,1.2852914428e-01 | |
| 1091 | 4,324,1.2449983257e-01 | |
| 1092 | 4,325,1.1455026990e-01 | |
| 1093 | 4,326,1.1715719265e-01 | |
| 1094 | 4,327,1.1554492137e-01 | |
| 1095 | 4,328,1.2852031449e-01 | |
| 1096 | 4,329,1.2247935372e-01 | |
| 1097 | 4,330,1.2260173101e-01 | |
| 1098 | 4,331,1.2443686118e-01 | |
| 1099 | 4,332,1.2855068928e-01 | |
| 1100 | 4,333,1.2064726607e-01 | |
| 1101 | 4,334,1.2221342865e-01 | |
| 1102 | 4,335,1.1570163013e-01 | |
| 1103 | 4,336,1.1314407280e-01 | |
| 1104 | 4,337,1.1403667458e-01 | |
| 1105 | 4,338,1.1508643939e-01 | |
| 1106 | 4,339,1.1261728141e-01 | |
| 1107 | 4,340,1.1220223505e-01 | |
| 1108 | 4,341,1.0544879996e-01 | |
| 1109 | 4,342,9.9007674776e-02 | |
| 1110 | 4,343,1.0668579071e-01 | |
| 1111 | 4,344,1.2027152935e-01 | |
| 1112 | 4,345,1.1490555290e-01 | |
| 1113 | 4,346,1.1338243958e-01 | |
| 1114 | 4,347,1.1396814043e-01 | |
| 1115 | 4,348,1.1617707164e-01 | |
| 1116 | 4,349,1.2293559220e-01 | |
| 1117 | 4,350,1.2548203979e-01 | |
| 1118 | 4,351,1.2182780322e-01 | |
| 1119 | 4,352,1.1654383698e-01 | |
| 1120 | 4,353,1.1804692949e-01 | |
| 1121 | 4,354,1.1827609387e-01 | |
| 1122 | 4,355,1.1925159732e-01 | |
| 1123 | 4,356,1.1633073608e-01 | |
| 1124 | 4,357,1.2093420444e-01 | |
| 1125 | 4,358,1.2655190542e-01 | |
| 1126 | 4,359,1.2356264726e-01 | |
| 1127 | 4,360,1.1928583453e-01 | |
| 1128 | 4,361,1.2891880681e-01 | |
| 1129 | 4,362,1.2252937978e-01 | |
| 1130 | 4,363,1.1576336778e-01 | |
| 1131 | 4,364,1.1248235143e-01 | |
| 1132 | 4,365,1.2645391328e-01 | |
| 1133 | 4,366,1.3622715378e-01 | |
| 1134 | 4,367,1.2947826408e-01 | |
| 1135 | 4,368,1.2449556811e-01 | |
| 1136 | 4,369,1.2181614202e-01 | |
| 1137 | 4,370,1.4885506985e-01 | |
| 1138 | 4,371,1.3454786248e-01 | |
| 1139 | 4,372,1.2540223133e-01 | |
| 1140 | 4,373,1.2317631965e-01 | |
| 1141 | 4,374,1.2992469307e-01 | |
| 1142 | 4,375,1.3220684484e-01 | |
| 1143 | 4,376,1.5063438043e-01 | |
| 1144 | 4,377,1.5170516642e-01 | |
| 1145 | 4,378,1.4760136947e-01 | |
| 1146 | 4,379,1.3615624860e-01 | |
| 1147 | 4,380,1.3211379128e-01 | |
| 1148 | 4,381,1.3540380871e-01 | |
| 1149 | 4,382,1.4799288074e-01 | |
| 1150 | 4,383,1.5462017725e-01 | |
| 1151 | 4,384,1.4614448812e-01 | |
| 1152 | 4,385,1.5311919451e-01 | |
| 1153 | 4,386,1.4348558291e-01 | |
| 1154 | 4,387,1.6466829425e-01 | |
| 1155 | 4,388,1.5650742741e-01 | |
| 1156 | 4,389,1.4739171860e-01 | |
| 1157 | 4,390,1.4070691287e-01 | |
| 1158 | 4,391,1.2999011222e-01 | |
| 1159 | 4,392,1.4367624091e-01 | |
| 1160 | 4,393,1.3353175695e-01 | |
| 1161 | 4,394,1.5574378080e-01 | |
| 1162 | 4,395,1.7090538025e-01 | |
| 1163 | 4,396,1.4605588000e-01 | |
| 1164 | 4,397,1.5740805388e-01 | |
| 1165 | 4,398,1.5654782514e-01 | |
| 1166 | 4,399,1.5427118194e-01 | |
| 1167 | 4,400,1.4493674421e-01 | |
| 1168 | 4,401,1.3861507894e-01 | |
| 1169 | 4,402,1.4819233158e-01 | |
| 1170 | 4,403,1.3790975102e-01 | |
| 1171 | 4,404,1.6092745928e-01 | |
| 1172 | 4,405,1.6167387675e-01 | |
| 1173 | 4,406,1.5713257968e-01 | |
| 1174 | 4,407,1.6299402290e-01 | |
| 1175 | 4,408,1.5932553409e-01 | |
| 1176 | 4,409,1.7758512851e-01 | |
| 1177 | 4,410,1.5826826473e-01 | |
| 1178 | 4,411,1.4249542059e-01 | |
| 1179 | 4,412,1.3706058487e-01 | |
| 1180 | 4,413,1.4284256231e-01 | |
| 1181 | 4,414,1.4133345112e-01 | |
| 1182 | 4,415,1.4735780304e-01 | |
| 1183 | 4,416,1.4291656499e-01 | |
| 1184 | 4,417,1.3561507816e-01 | |
| 1185 | 4,418,1.4448368580e-01 | |
| 1186 | 4,419,1.4243086132e-01 | |
| 1187 | 4,420,1.4750404623e-01 | |
| 1188 | 4,421,1.5273857729e-01 | |
| 1189 | 4,422,1.6880322802e-01 | |
| 1190 | 4,423,1.7230754893e-01 | |
| 1191 | 4,424,1.5957861351e-01 | |
| 1192 | 4,425,1.5547604369e-01 | |
| 1193 | 4,426,1.3831926879e-01 | |
| 1194 | 4,427,1.3818037287e-01 | |
| 1195 | 4,428,1.2509696847e-01 | |
| 1196 | 4,429,1.1650446117e-01 | |
| 1197 | 4,430,1.4857380186e-01 | |
| 1198 | 4,431,1.5923978795e-01 | |
| 1199 | 4,432,1.3646499278e-01 | |
| 1200 | 4,433,1.3103644023e-01 | |
| 1201 | 4,434,1.5433085905e-01 | |
| 1202 | 4,435,1.5468324088e-01 | |
| 1203 | 4,436,1.3930533602e-01 | |
| 1204 | 4,437,1.3632216241e-01 | |
| 1205 | 4,438,1.4891918687e-01 | |
| 1206 | 4,439,1.2533477101e-01 | |
| 1207 | 4,440,1.3022980339e-01 | |
| 1208 | 4,441,1.3892393235e-01 | |
| 1209 | 4,442,1.3528889336e-01 | |
| 1210 | 4,443,1.2132263340e-01 | |
| 1211 | 4,444,1.1223105715e-01 | |
| 1212 | 4,445,1.2067818606e-01 | |
| 1213 | 4,446,1.1055886767e-01 | |
| 1214 | 4,447,1.0731782738e-01 | |
| 1215 | 4,448,1.2238150436e-01 | |
| 1216 | 4,449,1.3892252503e-01 | |
| 1217 | 4,450,1.2948000335e-01 | |
| 1218 | 4,451,1.2322807622e-01 | |
| 1219 | 4,452,1.2827441588e-01 | |
| 1220 | 4,453,1.2904605739e-01 | |
| 1221 | 4,454,1.1685709932e-01 | |
| 1222 | 4,455,1.2582243843e-01 | |
| 1223 | 4,456,1.2033660112e-01 | |
| 1224 | 4,457,1.1793111500e-01 | |
| 1225 | 4,458,1.1147193372e-01 | |
| 1226 | 4,459,1.1891554237e-01 | |
| 1227 | 4,460,1.1667369427e-01 | |
| 1228 | 4,461,1.0560863453e-01 | |
| 1229 | 4,462,1.3254410600e-01 | |
| 1230 | 4,463,1.2012513214e-01 | |
| 1231 | 4,464,8.4612120958e-02 | |
| 1232 | 4,465,9.5552334428e-02 | |
| 1233 | 4,466,1.0453496610e-01 | |
| 1234 | 4,467,1.0893590305e-01 | |
| 1235 | 4,468,9.3557562193e-02 | |
| 1236 | 4,469,9.5983847286e-02 | |
| 1237 | 4,470,9.6039874579e-02 | |
| 1238 | 4,471,9.3144445728e-02 | |
| 1239 | 4,472,9.7948144410e-02 | |
| 1240 | 4,473,1.0197503558e-01 | |
| 1241 | 4,474,1.0491583461e-01 | |
| 1242 | 4,475,9.0675014108e-02 | |
| 1243 | 4,476,9.7183356272e-02 | |
| 1244 | 4,477,1.1306437699e-01 | |
| 1245 | 4,478,9.9941530495e-02 | |
| 1246 | 4,479,9.3676932773e-02 | |
| 1247 | 4,480,8.1143400480e-02 | |
| 1248 | 4,481,7.0322182035e-02 | |
| 1249 | 4,482,6.8192487786e-02 | |
| 1250 | 4,483,7.0836280753e-02 | |
| 1251 | 4,484,7.4707945641e-02 | |
| 1252 | 4,485,7.6332534732e-02 | |
| 1253 | 4,486,7.1285815449e-02 | |
| 1254 | 4,487,7.4629568629e-02 | |
| 1255 | 4,488,8.2669038130e-02 | |
| 1256 | 4,489,6.6498300038e-02 | |
| 1257 | 4,490,5.2496378625e-02 | |
| 1258 | 4,491,5.2315886654e-02 | |
| 1259 | 4,492,6.0887108650e-02 | |
| 1260 | 4,493,6.2115281236e-02 | |
| 1261 | 4,494,4.6759102643e-02 | |
| 1262 | 4,495,4.8779049902e-02 | |
| 1263 | 4,496,5.7222016741e-02 | |
| 1264 | 4,497,4.4765027598e-02 | |
| 1265 | 4,498,3.9545979833e-02 | |
| 1266 | 4,499,3.2732356784e-02 | |
| 1267 | 4,500,2.9626344531e-02 | |
| 1268 | 4,501,3.1291029697e-02 | |
| 1269 | 4,502,3.0815946740e-02 | |
| 1270 | 4,503,3.5675800874e-02 | |
| 1271 | 4,504,3.0719354503e-02 | |
| 1272 | 4,505,2.3439843673e-02 | |
| 1273 | 4,506,1.6505652456e-02 | |
| 1274 | 4,507,1.6056859289e-02 | |
| 1275 | 4,508,1.8100674077e-02 | |
| 1276 | 4,509,1.5643826879e-02 | |
| 1277 | 4,510,1.3106491226e-02 | |
| 1278 | 4,511,1.2349250514e-02 | |
| 1279 | 4,512,1.1715775128e-02 | |
| 1280 | 4,513,1.2192772211e-02 | |
| 1281 | 4,514,1.2144886779e-02 | |
| 1282 | 4,515,1.4337259769e-02 | |
| 1283 | 4,516,1.6432012002e-02 | |
| 1284 | 4,517,1.5126511681e-02 | |
| 1285 | 4,518,1.6380691534e-02 | |
| 1286 | 4,519,2.2044263520e-02 | |
| 1287 | 4,520,2.7090108298e-02 | |
| 1288 | 4,521,2.9134256167e-02 | |
| 1289 | 4,522,2.6389535579e-02 | |
| 1290 | 4,523,2.7932703950e-02 | |
| 1291 | 4,524,2.6453341273e-02 | |
| 1292 | 4,525,2.8208368841e-02 | |
| 1293 | 4,526,3.2141018938e-02 | |
| 1294 | 4,527,3.5669125966e-02 | |
| 1295 | 4,528,4.4784822221e-02 | |
| 1296 | 4,529,3.9317834651e-02 | |
| 1297 | 4,530,3.4385482618e-02 | |
| 1298 | 4,531,4.3655124041e-02 | |
| 1299 | 4,532,4.1344122500e-02 | |
| 1300 | 4,533,3.4301894450e-02 | |
| 1301 | 4,534,3.4942635447e-02 | |
| 1302 | 4,535,4.3320909447e-02 | |
| 1303 | 4,536,5.0988474093e-02 | |
| 1304 | 4,537,4.9064383467e-02 | |
| 1305 | 4,538,4.3165658169e-02 | |
| 1306 | 4,539,4.9292828038e-02 | |
| 1307 | 4,540,4.6785282399e-02 | |
| 1308 | 4,541,4.3403940809e-02 | |
| 1309 | 4,542,3.8169372768e-02 | |
| 1310 | 4,543,3.7175718681e-02 | |
| 1311 | 4,544,4.1323283404e-02 | |
| 1312 | 4,545,4.8361760429e-02 | |
| 1313 | 4,546,5.1012066796e-02 | |
| 1314 | 4,547,5.4908224861e-02 | |
| 1315 | 4,548,4.9478924582e-02 | |
| 1316 | 4,549,4.6656873688e-02 | |
| 1317 | 4,550,4.9833224461e-02 | |
| 1318 | 4,551,4.6510684810e-02 | |
| 1319 | 4,552,4.5098044441e-02 | |
| 1320 | 4,553,4.3529749715e-02 | |
| 1321 | 4,554,4.5223658293e-02 | |
| 1322 | 4,555,4.2768765161e-02 | |
| 1323 | 4,556,4.3937275908e-02 | |
| 1324 | 4,557,4.3080528331e-02 | |
| 1325 | 4,558,4.0160699384e-02 | |
| 1326 | 4,559,3.7022188334e-02 | |
| 1327 | 4,560,3.5880524351e-02 | |
| 1328 | 4,561,4.9373023906e-02 | |
| 1329 | 4,562,5.2018884591e-02 | |
| 1330 | 4,563,4.3414987334e-02 | |
| 1331 | 4,564,4.2235792918e-02 | |
| 1332 | 4,565,4.0099365823e-02 | |
| 1333 | 4,566,4.0147280884e-02 | |
| 1334 | 4,567,4.2092120071e-02 | |
| 1335 | 4,568,4.2108491691e-02 | |
| 1336 | 4,569,4.1429208343e-02 | |
| 1337 | 4,570,3.9550925507e-02 | |
| 1338 | 4,571,4.2628705220e-02 | |
| 1339 | 4,572,3.9616273784e-02 | |
| 1340 | 4,573,3.8077927718e-02 | |
| 1341 | 4,574,4.0489447412e-02 | |
| 1342 | 4,575,4.0239746194e-02 | |
| 1343 | 4,576,3.4218636597e-02 | |
| 1344 | 4,577,3.1972218515e-02 | |
| 1345 | 4,578,3.4613452112e-02 | |
| 1346 | 4,579,3.3560135882e-02 | |
| 1347 | 4,580,3.1436339772e-02 | |
| 1348 | 4,581,3.1046720187e-02 | |
| 1349 | 4,582,3.5339249842e-02 | |
| 1350 | 4,583,3.6126641188e-02 | |
| 1351 | 4,584,3.2385690343e-02 | |
| 1352 | 4,585,3.3632317277e-02 | |
| 1353 | 4,586,3.7119193993e-02 | |
| 1354 | 4,587,3.5985224626e-02 | |
| 1355 | 4,588,3.4042625620e-02 | |
| 1356 | 4,589,3.3317686417e-02 | |
| 1357 | 4,590,3.5258368798e-02 | |
| 1358 | 4,591,3.0532787592e-02 | |
| 1359 | 4,592,3.0847176128e-02 | |
| 1360 | 4,593,3.1188331461e-02 | |
| 1361 | 4,594,3.0071025434e-02 | |
| 1362 | 4,595,2.9854930965e-02 | |
| 1363 | 4,596,2.9875207071e-02 | |
| 1364 | 4,597,2.8535075018e-02 | |
| 1365 | 4,598,2.7960691470e-02 | |
| 1366 | 4,599,3.0193752733e-02 | |
| 1367 | 4,600,3.0660497760e-02 | |
| 1368 | 4,601,3.0169830026e-02 | |
| 1369 | 4,602,3.1781143114e-02 | |
| 1370 | 4,603,2.9647039340e-02 | |
| 1371 | 4,604,2.8793376746e-02 | |
| 1372 | 4,605,2.9199719992e-02 | |
| 1373 | 4,606,2.7027129405e-02 | |
| 1374 | 4,607,2.5588631439e-02 | |
| 1375 | 4,608,2.5525352330e-02 | |
| 1376 | 4,609,2.5750615101e-02 | |
| 1377 | 4,610,2.4576426451e-02 | |
| 1378 | 4,611,2.6433109172e-02 | |
| 1379 | 4,612,2.4861196318e-02 | |
| 1380 | 4,613,2.5178765085e-02 | |
| 1381 | 4,614,2.6369235602e-02 | |
| 1382 | 4,615,2.7295611685e-02 | |
| 1383 | 4,616,2.5393595295e-02 | |
| 1384 | 4,617,2.4846997540e-02 | |
| 1385 | 4,618,2.3523877568e-02 | |
| 1386 | 4,619,2.3865716014e-02 | |
| 1387 | 4,620,2.3607092693e-02 | |
| 1388 | 4,621,2.3173079673e-02 | |
| 1389 | 4,622,2.2870362064e-02 | |
| 1390 | 4,623,2.2935186581e-02 | |
| 1391 | 4,624,2.3251397515e-02 | |
| 1392 | 4,625,2.2461881167e-02 | |
| 1393 | 4,626,2.2734259666e-02 | |
| 1394 | 4,627,2.0751992490e-02 | |
| 1395 | 4,628,2.0881479932e-02 | |
| 1396 | 4,629,2.2186319684e-02 | |
| 1397 | 4,630,2.1381061897e-02 | |
| 1398 | 4,631,1.9796686280e-02 | |
| 1399 | 4,632,1.9247880543e-02 | |
| 1400 | 4,633,1.9319138546e-02 | |
| 1401 | 4,634,1.9837207342e-02 | |
| 1402 | 4,635,2.0320120506e-02 | |
| 1403 | 4,636,1.8708202072e-02 | |
| 1404 | 4,637,1.9949554364e-02 | |
| 1405 | 4,638,2.0390156239e-02 | |
| 1406 | 4,639,2.0384205611e-02 | |
| 1407 | 4,640,1.8841214942e-02 | |
| 1408 | 4,641,1.9449888588e-02 | |
| 1409 | 4,642,1.9641104665e-02 | |
| 1410 | 4,643,1.8753032223e-02 | |
| 1411 | 4,644,1.8165180176e-02 | |
| 1412 | 4,645,1.7921483229e-02 | |
| 1413 | 4,646,1.7972500389e-02 | |
| 1414 | 4,647,1.8135798283e-02 | |
| 1415 | 4,648,1.8193484569e-02 | |
| 1416 | 4,649,1.6732986071e-02 | |
| 1417 | 4,650,1.7293955510e-02 | |
| 1418 | 4,651,1.6825977719e-02 | |
| 1419 | 4,652,1.6432208950e-02 | |
| 1420 | 4,653,1.7882645972e-02 | |
| 1421 | 4,654,1.7027771416e-02 | |
| 1422 | 4,655,1.6454664371e-02 | |
| 1423 | 4,656,1.5268058866e-02 | |
| 1424 | 4,657,1.4824277078e-02 | |
| 1425 | 4,658,1.5292700971e-02 | |
| 1426 | 4,659,1.5309325631e-02 | |
| 1427 | 4,660,1.4661478172e-02 | |
| 1428 | 4,661,1.5835390108e-02 | |
| 1429 | 4,662,1.6267494482e-02 | |
| 1430 | 4,663,1.5279071279e-02 | |
| 1431 | 4,664,1.4641992271e-02 | |
| 1432 | 4,665,1.4840627859e-02 | |
| 1433 | 4,666,1.5221611952e-02 | |
| 1434 | 4,667,1.4682409605e-02 | |
| 1435 | 4,668,1.5157521564e-02 | |
| 1436 | 4,669,1.5608609974e-02 | |
| 1437 | 4,670,1.4754114742e-02 | |
| 1438 | 4,671,1.4805825595e-02 | |
| 1439 | 4,672,1.5472930245e-02 | |
| 1440 | 4,673,1.4777227744e-02 | |
| 1441 | 4,674,1.4750500922e-02 | |
| 1442 | 4,675,1.4766873512e-02 | |
| 1443 | 4,676,1.4697552788e-02 | |
| 1444 | 4,677,1.4533999049e-02 | |
| 1445 | 4,678,1.4150486481e-02 | |
| 1446 | 4,679,1.4085005856e-02 | |
| 1447 | 4,680,1.4329058082e-02 | |
| 1448 | 4,681,1.3709496823e-02 | |
| 1449 | 4,682,1.3783401299e-02 | |
| 1450 | 4,683,1.3619479856e-02 | |
| 1451 | 4,684,1.3693872088e-02 | |
| 1452 | 4,685,1.3082639245e-02 | |
| 1453 | 4,686,1.2962200776e-02 | |
| 1454 | 4,687,1.2718909722e-02 | |
| 1455 | 4,688,1.2849389781e-02 | |
| 1456 | 4,689,1.2455668055e-02 | |
| 1457 | 4,690,1.2500124749e-02 | |
| 1458 | 4,691,1.3242194675e-02 | |
| 1459 | 4,692,1.2926867300e-02 | |
| 1460 | 4,693,1.3191132313e-02 | |
| 1461 | 4,694,1.2235259107e-02 | |
| 1462 | 4,695,1.2511094917e-02 | |
| 1463 | 4,696,1.2499307627e-02 | |
| 1464 | 4,697,1.2957796726e-02 | |
| 1465 | 4,698,1.3370920183e-02 | |
| 1466 | 4,699,1.3053124711e-02 | |
| 1467 | 4,700,1.2418703401e-02 | |
| 1468 | 4,701,1.3097691533e-02 | |
| 1469 | 4,702,1.3250662297e-02 | |
| 1470 | 4,703,1.3363229938e-02 | |
| 1471 | 4,704,1.2820392163e-02 | |
| 1472 | 4,705,1.2962340138e-02 | |
| 1473 | 4,706,1.2844617424e-02 | |
| 1474 | 4,707,1.2856173409e-02 | |
| 1475 | 4,708,1.2241710397e-02 | |
| 1476 | 4,709,1.1670855921e-02 | |
| 1477 | 4,710,1.2110002791e-02 | |
| 1478 | 4,711,1.2416555593e-02 | |
| 1479 | 4,712,1.2534110102e-02 | |
| 1480 | 4,713,1.1978701332e-02 | |
| 1481 | 4,714,1.2030211201e-02 | |
| 1482 | 4,715,1.1870684558e-02 | |
| 1483 | 4,716,1.1545909669e-02 | |
| 1484 | 4,717,1.2377153324e-02 | |
| 1485 | 4,718,1.2599264661e-02 | |
| 1486 | 4,719,1.2743746439e-02 | |
| 1487 | 4,720,1.2158684551e-02 | |
| 1488 | 4,721,1.2050572408e-02 | |
| 1489 | 4,722,1.2284453418e-02 | |
| 1490 | 4,723,1.2503904754e-02 | |
| 1491 | 4,724,1.2861248383e-02 | |
| 1492 | 4,725,1.2894452099e-02 | |
| 1493 | 4,726,1.2501667746e-02 | |
| 1494 | 4,727,1.2541907152e-02 | |
| 1495 | 4,728,1.2111373581e-02 | |
| 1496 | 4,729,1.2328531488e-02 | |
| 1497 | 4,730,1.3208864573e-02 | |
| 1498 | 4,731,1.2933043359e-02 | |
| 1499 | 4,732,1.1955245898e-02 | |
| 1500 | 4,733,1.1938925418e-02 | |
| 1501 | 4,734,1.2121521542e-02 | |
| 1502 | 4,735,1.2237616133e-02 | |
| 1503 | 4,736,1.2424290979e-02 | |
| 1504 | 4,737,1.2660531548e-02 | |
| 1505 | 4,738,1.2740531747e-02 | |
| 1506 | 4,739,1.2692464313e-02 | |
| 1507 | 4,740,1.2604660756e-02 | |
| 1508 | 4,741,1.2094232387e-02 | |
| 1509 | 4,742,1.2109487120e-02 | |
| 1510 | 4,743,1.2216045932e-02 | |
| 1511 | 4,744,1.2061935563e-02 | |
| 1512 | 4,745,1.1877216860e-02 | |
| 1513 | 4,746,1.1911263455e-02 | |
| 1514 | 4,747,1.2101043471e-02 | |
| 1515 | 4,748,1.2273359374e-02 | |
| 1516 | 4,749,1.1891691649e-02 | |
| 1517 | 4,750,1.2282258237e-02 | |
| 1518 | 4,751,1.2518553720e-02 | |
| 1519 | 4,752,1.2147606252e-02 | |
| 1520 | 4,753,1.1957464305e-02 | |
| 1521 | 4,754,1.2161305419e-02 | |
| 1522 | 4,755,1.1514901831e-02 | |
| 1523 | 4,756,1.1756803728e-02 | |
| 1524 | 4,757,1.2052379605e-02 | |
| 1525 | 4,758,1.2006260360e-02 | |
| 1526 | 4,759,1.2351259466e-02 | |
| 1527 | 4,760,1.2399938642e-02 | |
| 1528 | 4,761,1.2066846784e-02 | |
| 1529 | 4,762,1.2065621923e-02 | |
| 1530 | 4,763,1.2064770320e-02 | |
| 1531 | 4,764,1.1780900367e-02 | |
| 1532 | 4,765,1.1577488413e-02 | |
| 1533 | 4,766,1.1916424970e-02 | |
| 1534 | 4,767,1.2245708653e-02 | |
| 1535 | 4,768,1.2171984030e-02 | |
| 1536 | 4,769,1.1971697863e-02 | |
| 1537 | 4,770,1.1571421091e-02 | |
| 1538 | 4,771,1.1934978158e-02 | |
| 1539 | 4,772,1.1889686863e-02 | |
| 1540 | 4,773,1.2089811304e-02 | |
| 1541 | 4,774,1.1968453218e-02 | |
| 1542 | 4,775,1.2182572550e-02 | |
| 1543 | 4,776,1.2107396322e-02 | |
| 1544 | 4,777,1.2249139537e-02 | |
| 1545 | 4,778,1.2174387585e-02 | |
| 1546 | 4,779,1.2333183521e-02 | |
| 1547 | 4,780,1.1708776451e-02 | |
| 1548 | 4,781,1.1113482393e-02 | |
| 1549 | 4,782,1.1517278740e-02 | |
| 1550 | 4,783,1.1609946481e-02 | |
| 1551 | 4,784,1.1527033729e-02 | |
| 1552 | 4,785,1.1477508175e-02 | |
| 1553 | 4,786,1.2413135700e-02 | |
| 1554 | 4,787,1.2691469016e-02 | |
| 1555 | 4,788,1.1837381836e-02 | |
| 1556 | 4,789,1.2011118067e-02 | |
| 1557 | 4,790,1.2154146760e-02 | |
| 1558 | 4,791,1.1725814948e-02 | |
| 1559 | 4,792,1.2214511057e-02 | |
| 1560 | 4,793,1.2459886841e-02 | |
| 1561 | 4,794,1.1968900519e-02 | |
| 1562 | 4,795,1.1889993188e-02 | |
| 1563 | 4,796,1.2158410877e-02 | |
| 1564 | 4,797,1.1955944441e-02 | |
| 1565 | 4,798,1.2007131757e-02 | |
| 1566 | 4,799,1.2309650921e-02 | |
| 1567 | 4,800,1.1633678689e-02 | |
| 1568 | 4,801,1.1490816692e-02 | |
| 1569 | 4,802,1.1889626880e-02 | |
| 1570 | 4,803,1.2057644915e-02 | |
| 1571 | 4,804,1.1655270436e-02 | |
| 1572 | 4,805,1.1232683392e-02 | |
| 1573 | 4,806,1.1727129672e-02 | |
| 1574 | 4,807,1.2159075413e-02 | |
| 1575 | 4,808,1.2140407134e-02 | |
| 1576 | 4,809,1.2497465644e-02 | |
| 1577 | 4,810,1.2694778412e-02 | |
| 1578 | 4,811,1.2117974577e-02 | |
| 1579 | 4,812,1.2324749939e-02 | |
| 1580 | 4,813,1.2240001229e-02 | |
| 1581 | 4,814,1.2269341269e-02 | |
| 1582 | 4,815,1.1340618561e-02 | |
| 1583 | 4,816,1.1398024104e-02 | |
| 1584 | 4,817,1.2099339054e-02 | |
| 1585 | 4,818,1.2037737593e-02 | |
| 1586 | 4,819,1.1632917206e-02 | |
| 1587 | 4,820,1.1607521821e-02 | |
| 1588 | 4,821,1.2135129831e-02 | |
| 1589 | 4,822,1.2070125037e-02 | |
| 1590 | 4,823,1.1770510297e-02 | |
| 1591 | 4,824,1.1988897696e-02 | |
| 1592 | 4,825,1.2326459073e-02 | |
| 1593 | 4,826,1.2220103129e-02 | |
| 1594 | 4,827,1.1810311316e-02 | |
| 1595 | 4,828,1.2038657336e-02 | |
| 1596 | 4,829,1.2651884741e-02 | |
| 1597 | 4,830,1.2268782380e-02 | |
| 1598 | 4,831,1.1654235633e-02 | |
| 1599 | 4,832,1.1475454405e-02 | |
| 1600 | 4,833,1.2713530520e-02 | |
| 1601 | 4,834,1.2485586980e-02 | |
| 1602 | 4,835,1.1908763390e-02 | |
| 1603 | 4,836,1.2051082839e-02 | |
| 1604 | 4,837,1.2101963241e-02 | |
| 1605 | 4,838,1.1684219609e-02 | |
| 1606 | 4,839,1.1672237693e-02 | |
| 1607 | 4,840,1.1739167339e-02 | |
| 1608 | 4,841,1.2106088273e-02 | |
| 1609 | 4,842,1.1885793378e-02 | |
| 1610 | 4,843,1.1736726213e-02 | |
| 1611 | 4,844,1.1838398200e-02 | |
| 1612 | 4,845,1.2370411714e-02 | |
| 1613 | 4,846,1.2169103355e-02 | |
| 1614 | 4,847,1.2257939143e-02 | |
| 1615 | 4,848,1.1773859302e-02 | |
| 1616 | 4,849,1.1671826094e-02 | |
| 1617 | 4,850,1.1848337464e-02 | |
| 1618 | 4,851,1.1903929697e-02 | |
| 1619 | 4,852,1.2309024012e-02 | |
| 1620 | 4,853,1.1562406503e-02 | |
| 1621 | 4,854,1.1853007156e-02 | |
| 1622 | 4,855,1.2013282502e-02 | |
| 1623 | 4,856,1.2262692011e-02 | |
| 1624 | 4,857,1.2025637342e-02 | |
| 1625 | 4,858,1.1721408136e-02 | |
| 1626 | 4,859,1.1720114948e-02 | |
| 1627 | 4,860,1.2127601570e-02 | |
| 1628 | 4,861,1.2493618269e-02 | |
| 1629 | 4,862,1.2387474453e-02 | |
| 1630 | 4,863,1.2683601779e-02 | |
| 1631 | 4,864,1.2495291915e-02 | |
| 1632 | 4,865,1.2051295944e-02 | |
| 1633 | 4,866,1.1946767542e-02 | |
| 1634 | 4,867,1.2044966540e-02 | |
| 1635 | 4,868,1.1874921683e-02 | |
| 1636 | 4,869,1.1831712554e-02 | |
| 1637 | 4,870,1.1966174047e-02 | |
| 1638 | 4,871,1.2361409210e-02 | |
| 1639 | 4,872,1.1802226435e-02 | |
| 1640 | 4,873,1.1326668284e-02 | |
| 1641 | 4,874,1.1855271752e-02 | |
| 1642 | 4,875,1.2220130289e-02 | |
| 1643 | 4,876,1.2190422612e-02 | |
| 1644 | 4,877,1.2215031595e-02 | |
| 1645 | 4,878,1.2307520494e-02 | |
| 1646 | 4,879,1.1417835913e-02 | |
| 1647 | 4,880,1.1867183279e-02 | |
| 1648 | 4,881,1.2159596804e-02 | |
| 1649 | 4,882,1.2379887936e-02 | |
| 1650 | 4,883,1.2426964809e-02 | |
| 1651 | 4,884,1.2497559597e-02 | |
| 1652 | 4,885,1.2176647674e-02 | |
| 1653 | 4,886,1.2272305591e-02 | |
| 1654 | 4,887,1.2338651937e-02 | |
| 1655 | 4,888,1.2030327242e-02 | |
| 1656 | 4,889,1.1645677825e-02 | |
| 1657 | 4,890,1.1468267542e-02 | |
| 1658 | 4,891,1.1504761977e-02 | |
| 1659 | 4,892,1.1766738266e-02 | |
| 1660 | 4,893,1.2049132720e-02 | |
| 1661 | 4,894,1.2371364233e-02 | |
| 1662 | 4,895,1.2539544793e-02 | |
| 1663 | 4,896,1.2216130501e-02 | |
| 1664 | 4,897,1.2103025226e-02 | |
| 1665 | 4,898,1.1515081757e-02 | |
| 1666 | 4,899,1.2243695130e-02 | |
| 1667 | 4,900,1.1999541697e-02 | |
| 1668 | 4,901,1.1950781739e-02 | |
| 1669 | 4,902,1.1771031283e-02 | |
| 1670 | 4,903,1.2360275059e-02 | |
| 1671 | 4,904,1.2630630571e-02 | |
| 1672 | 4,905,1.2263978544e-02 | |
| 1673 | 4,906,1.1521488882e-02 | |
| 1674 | 4,907,1.1623790120e-02 | |
| 1675 | 4,908,1.1600942150e-02 | |
| 1676 | 4,909,1.1815807976e-02 | |
| 1677 | 4,910,1.2168673170e-02 | |
| 1678 | 4,911,1.1817163554e-02 | |
| 1679 | 4,912,1.2005286695e-02 | |
| 1680 | 4,913,1.2360930484e-02 | |
| 1681 | 4,914,1.2378002665e-02 | |
| 1682 | 4,915,1.2210055945e-02 | |
| 1683 | 4,916,1.2359893436e-02 | |
| 1684 | 4,917,1.2082780373e-02 | |
| 1685 | 4,918,1.2012871964e-02 | |
| 1686 | 4,919,1.1864462033e-02 | |
| 1687 | 4,920,1.1815434142e-02 | |
| 1688 | 4,921,1.2116039304e-02 | |
| 1689 | 4,922,1.1792357270e-02 | |
| 1690 | 4,923,1.2162142681e-02 | |
| 1691 | 4,924,1.2083621684e-02 | |
| 1692 | 4,925,1.1996117334e-02 | |
| 1693 | 4,926,1.2194611165e-02 | |
| 1694 | 4,927,1.1512122024e-02 | |
| 1695 | 4,928,1.1323371724e-02 | |
| 1696 | 4,929,1.1511579192e-02 | |
| 1697 | 4,930,1.2093011714e-02 | |
| 1698 | 4,931,1.2327629295e-02 | |
| 1699 | 4,932,1.2295789683e-02 | |
| 1700 | 4,933,1.1807477230e-02 | |
| 1701 | 4,934,1.1804489261e-02 | |
| 1702 | 4,935,1.1740872603e-02 | |
| 1703 | 4,936,1.1740505265e-02 | |
| 1704 | 4,937,1.1811183927e-02 | |
| 1705 | 4,938,1.1309921547e-02 | |
| 1706 | 4,939,1.2408857535e-02 | |
| 1707 | 4,940,1.2743247366e-02 | |
| 1708 | 4,941,1.2375062727e-02 | |
| 1709 | 4,942,1.2232486003e-02 | |
| 1710 | 4,943,1.1935603743e-02 | |
| 1711 | 4,944,1.1967547304e-02 | |
| 1712 | 4,945,1.1813159848e-02 | |
| 1713 | 4,946,1.1908292699e-02 | |
| 1714 | 4,947,1.1964120065e-02 | |
| 1715 | 4,948,1.2408606077e-02 | |
| 1716 | 4,949,1.1783475082e-02 | |
| 1717 | 4,950,1.2282742241e-02 | |
| 1718 | 4,951,1.2064106170e-02 | |
| 1719 | 4,952,1.1617285407e-02 | |
| 1720 | 4,953,1.1855528120e-02 | |
| 1721 | 4,954,1.1998040768e-02 | |
| 1722 | 4,955,1.2339363736e-02 | |
| 1723 | 4,956,1.1696707116e-02 | |
| 1724 | 4,957,1.2142379731e-02 | |
| 1725 | 4,958,1.1740788841e-02 | |
| 1726 | 4,959,1.1955450113e-02 | |
| 1727 | 4,960,1.2111338829e-02 | |
| 1728 | 4,961,1.2042820872e-02 | |
| 1729 | 4,962,1.2014342301e-02 | |
| 1730 | 4,963,1.1803992625e-02 | |
| 1731 | 4,964,1.1245127269e-02 | |
| 1732 | 4,965,1.1950241897e-02 | |
| 1733 | 4,966,1.2191348034e-02 | |
| 1734 | 4,967,1.2128977707e-02 | |
| 1735 | 4,968,1.1472326439e-02 | |
| 1736 | 4,969,1.1641403712e-02 | |
| 1737 | 4,970,1.1926753426e-02 | |
| 1738 | 4,971,1.1893717787e-02 | |
| 1739 | 4,972,1.1835133417e-02 | |
| 1740 | 4,973,1.1787567739e-02 | |
| 1741 | 4,974,1.2125769323e-02 | |
| 1742 | 4,975,1.2102172128e-02 | |
| 1743 | 4,976,1.1650742912e-02 | |
| 1744 | 4,977,1.1407346286e-02 | |
| 1745 | 4,978,1.1456264525e-02 | |
| 1746 | 4,979,1.1228826749e-02 | |
| 1747 | 4,980,1.1342672895e-02 | |
| 1748 | 4,981,1.1464701940e-02 | |
| 1749 | 4,982,1.1380117968e-02 | |
| 1750 | 4,983,1.1569835335e-02 | |
| 1751 | 4,984,1.1715668377e-02 | |
| 1752 | 4,985,1.1822589583e-02 | |
| 1753 | 4,986,1.1094127334e-02 | |
| 1754 | 4,987,1.1278715156e-02 | |
| 1755 | 4,988,1.1498840959e-02 | |
| 1756 | 4,989,1.1378299459e-02 | |
| 1757 | 4,990,1.1371427600e-02 | |
| 1758 | 4,991,1.1498856623e-02 | |
| 1759 | 4,992,1.1736400968e-02 | |
| 1760 | 4,993,1.1519346451e-02 | |
| 1761 | 4,994,1.1634763814e-02 | |
| 1762 | 4,995,1.1594505937e-02 | |
| 1763 | 4,996,1.1827741545e-02 | |
| 1764 | 4,997,1.2013900469e-02 | |
| 1765 | 4,998,1.1972761675e-02 | |
| 1766 | 4,999,1.1557043803e-02 | |
| 1767 | 4,1000,1.1712791018e-02 | |
| 1768 | 4,1001,1.1347747768e-02 | |
| 1769 | 4,1002,1.1528811308e-02 | |
| 1770 | 4,1003,1.1524953245e-02 | |
| 1771 | 4,1004,1.1914997487e-02 | |
| 1772 | 4,1005,1.1736986107e-02 | |
| 1773 | 4,1006,1.1391134196e-02 | |
| 1774 | 4,1007,1.1414629042e-02 | |
| 1775 | 4,1008,1.1812000042e-02 | |
| 1776 | 4,1009,1.1924803034e-02 | |
| 1777 | 4,1010,1.1976769542e-02 | |
| 1778 | 4,1011,1.1948932732e-02 | |
| 1779 | 4,1012,1.1738695446e-02 | |
| 1780 | 4,1013,1.1689225338e-02 | |
| 1781 | 4,1014,1.1998664811e-02 | |
| 1782 | 4,1015,1.2231890357e-02 | |
| 1783 | 4,1016,1.1833448861e-02 | |
| 1784 | 4,1017,1.1516570116e-02 | |
| 1785 | 4,1018,1.1793588731e-02 | |
| 1786 | 4,1019,1.2191044314e-02 | |
| 1787 | 4,1020,1.1880566419e-02 | |
| 1788 | 4,1021,1.1844599836e-02 | |
| 1789 | 4,1022,1.2179639429e-02 | |
| 1790 | 4,1023,1.2333189289e-02 |
| … | … | @@ -0,0 +1,316 @@ |
| 1 | #!/usr/bin/env python3 | |
| 2 | """ | |
| 3 | SPECTRUM PROBE — where does genuine detail stop in terrain-diffusion output? | |
| 4 | ||
| 5 | WHY THIS EXISTS | |
| 6 | ||
| 7 | hyper-games #46 §3. The band split between a learned coarse generator and our | |
| 8 | erosion filter is a decision about a wavelength, and it is only sound if we know | |
| 9 | the finest wavelength at which the model puts real energy. | |
| 10 | ||
| 11 | WHAT IT MEASURES | |
| 12 | ||
| 13 | The radially-averaged power spectrum of an elevation tile, P(k), fitted to a | |
| 14 | power law P(k) ~ k^-beta over a mid band, plus the residual of the top octave | |
| 15 | against that fit. A generator that produces genuine detail to its own Nyquist | |
| 16 | holds the power law all the way up. One that upsamples falls off a cliff. | |
| 17 | ||
| 18 | THE INSTRUMENT IS A LAYER TOO (#45 section 5) | |
| 19 | ||
| 20 | Every number below is worthless if the spectrum code is wrong, and a broken | |
| 21 | ruler reports confidently. So --selftest runs FIRST and must pass: | |
| 22 | ||
| 23 | - white noise -> beta ~ 0 (flat spectrum), the null case | |
| 24 | - a single sinusoid -> one sharp peak at the known bin, and nowhere else | |
| 25 | ||
| 26 | If either fails, no terrain number is printed at all. | |
| 27 | ||
| 28 | USAGE | |
| 29 | ||
| 30 | python spectrum_probe.py --selftest | |
| 31 | python spectrum_probe.py --url http://127.0.0.1:8817 --size 512 --scale 1 | |
| 32 | python spectrum_probe.py --url http://127.0.0.1:8817 --compare 1 2 | |
| 33 | """ | |
| 34 | import argparse | |
| 35 | import sys | |
| 36 | ||
| 37 | import numpy as np | |
| 38 | ||
| 39 | ||
| 40 | # --------------------------------------------------------------------------- | |
| 41 | # The instrument. | |
| 42 | # --------------------------------------------------------------------------- | |
| 43 | ||
| 44 | def radial_power_spectrum(field): | |
| 45 | """ | |
| 46 | Radially-averaged power spectrum of a 2-D real field. | |
| 47 | ||
| 48 | Returns (k, power) where k is in cycles per field-width (so k=1 is one | |
| 49 | cycle across the whole tile) and power is the mean |FFT|^2 in that | |
| 50 | annulus. Bin k covers wavelength lambda = N / k pixels. | |
| 51 | ||
| 52 | A Hann window is applied in both axes: the tile is a crop of an unbounded | |
| 53 | field, so its edges do not match, and without a window that discontinuity | |
| 54 | leaks broadband energy across every bin -- which would look exactly like | |
| 55 | the "genuine high-frequency detail" this probe exists to detect. | |
| 56 | """ | |
| 57 | a = np.asarray(field, dtype=np.float64) | |
| 58 | if a.ndim != 2 or a.shape[0] != a.shape[1]: | |
| 59 | raise ValueError(f"expected a square 2-D field, got {a.shape}") | |
| 60 | n = a.shape[0] | |
| 61 | ||
| 62 | # Remove mean and best-fit plane. A tilt is a k=0/k=1 feature and would | |
| 63 | # otherwise dominate every low bin. | |
| 64 | yy, xx = np.mgrid[0:n, 0:n].astype(np.float64) | |
| 65 | A = np.column_stack([np.ones(n * n), xx.ravel(), yy.ravel()]) | |
| 66 | coef, *_ = np.linalg.lstsq(A, a.ravel(), rcond=None) | |
| 67 | a = a - (A @ coef).reshape(n, n) | |
| 68 | ||
| 69 | w = np.hanning(n) | |
| 70 | a = a * w[:, None] * w[None, :] | |
| 71 | ||
| 72 | f = np.fft.fftshift(np.fft.fft2(a)) | |
| 73 | power = (np.abs(f) ** 2) / (n * n) | |
| 74 | ||
| 75 | cy = cx = n // 2 | |
| 76 | yy, xx = np.mgrid[0:n, 0:n] | |
| 77 | r = np.sqrt((yy - cy) ** 2 + (xx - cx) ** 2) | |
| 78 | rint = r.astype(np.int64) | |
| 79 | ||
| 80 | nbins = n // 2 | |
| 81 | tot = np.bincount(rint.ravel(), weights=power.ravel(), minlength=nbins + 1) | |
| 82 | cnt = np.bincount(rint.ravel(), minlength=nbins + 1) | |
| 83 | with np.errstate(invalid="ignore", divide="ignore"): | |
| 84 | radial = tot / np.maximum(cnt, 1) | |
| 85 | ||
| 86 | k = np.arange(1, nbins) # drop k=0 (DC, removed anyway) | |
| 87 | return k, radial[1:nbins] | |
| 88 | ||
| 89 | ||
| 90 | def fit_power_law(k, p, kmin, kmax): | |
| 91 | """Least-squares fit of log10(P) = c - beta*log10(k) over [kmin, kmax].""" | |
| 92 | m = (k >= kmin) & (k <= kmax) & (p > 0) | |
| 93 | if m.sum() < 4: | |
| 94 | raise ValueError(f"only {m.sum()} usable bins in [{kmin},{kmax}]") | |
| 95 | lk, lp = np.log10(k[m]), np.log10(p[m]) | |
| 96 | slope, intercept = np.polyfit(lk, lp, 1) | |
| 97 | pred = intercept + slope * lk | |
| 98 | ss_res = float(np.sum((lp - pred) ** 2)) | |
| 99 | ss_tot = float(np.sum((lp - lp.mean()) ** 2)) | |
| 100 | r2 = 1.0 - ss_res / ss_tot if ss_tot > 0 else float("nan") | |
| 101 | return -slope, intercept, r2 | |
| 102 | ||
| 103 | ||
| 104 | def top_octave_residual(k, p, kmin, kmax): | |
| 105 | """ | |
| 106 | How far the top octave sits below the power law fitted BELOW it, in dB. | |
| 107 | ||
| 108 | Fit over [kmin, kmax], extrapolate into (kmax, nyquist], and report the | |
| 109 | median dB shortfall. ~0 dB means the power law continues -- genuine | |
| 110 | detail. A large negative number means the energy is not there. | |
| 111 | """ | |
| 112 | beta, intercept, r2 = fit_power_law(k, p, kmin, kmax) | |
| 113 | m = (k > kmax) & (p > 0) | |
| 114 | if m.sum() < 3: | |
| 115 | return beta, r2, float("nan"), 0 | |
| 116 | pred_log = intercept - beta * np.log10(k[m]) | |
| 117 | resid_db = 10.0 * (np.log10(p[m]) - pred_log) | |
| 118 | return beta, r2, float(np.median(resid_db)), int(m.sum()) | |
| 119 | ||
| 120 | ||
| 121 | # --------------------------------------------------------------------------- | |
| 122 | # Self-test. Nothing downstream runs unless this passes. | |
| 123 | # --------------------------------------------------------------------------- | |
| 124 | ||
| 125 | def selftest(n=512, verbose=True): | |
| 126 | ok = True | |
| 127 | ||
| 128 | # 1. White noise -> flat spectrum. | |
| 129 | rng = np.random.default_rng(20260831) | |
| 130 | k, p = radial_power_spectrum(rng.standard_normal((n, n))) | |
| 131 | beta, _, r2 = fit_power_law(k, p, 8, n // 2 - 4) | |
| 132 | passed = abs(beta) < 0.10 | |
| 133 | ok &= passed | |
| 134 | if verbose: | |
| 135 | print(f" [{'PASS' if passed else 'FAIL'}] white noise " | |
| 136 | f"beta = {beta:+.4f} (expect 0.000 +/- 0.100)") | |
| 137 | ||
| 138 | # 2. Single sinusoid at a known wavelength -> one peak, right bin. | |
| 139 | lam = 32.0 # pixels per cycle | |
| 140 | expect = int(round(n / lam)) # -> bin 16 | |
| 141 | xx = np.arange(n) | |
| 142 | field = np.sin(2 * np.pi * xx / lam)[None, :] * np.ones((n, 1)) | |
| 143 | k, p = radial_power_spectrum(field) | |
| 144 | peak = int(k[np.argmax(p)]) | |
| 145 | passed = peak == expect | |
| 146 | ok &= passed | |
| 147 | if verbose: | |
| 148 | print(f" [{'PASS' if passed else 'FAIL'}] sinusoid lam={lam:.0f}px " | |
| 149 | f"peak at k = {peak} (expect {expect})") | |
| 150 | ||
| 151 | # 3a. Leakage check, on the UN-AVERAGED 2-D spectrum. A 1-D sinusoid puts | |
| 152 | # all its energy in two bins; everything else is windowing leakage and | |
| 153 | # must sit far below. This is the honest test of the FFT + window. | |
| 154 | ff = np.fft.fftshift(np.fft.fft2( | |
| 155 | (field - field.mean()) * np.hanning(n)[:, None] * np.hanning(n)[None, :])) | |
| 156 | pw = np.abs(ff) ** 2 | |
| 157 | cy = cx = n // 2 | |
| 158 | yy2, xx2 = np.mgrid[0:n, 0:n] | |
| 159 | near = (np.abs(xx2 - cx) <= expect + 2) & (np.abs(xx2 - cx) >= expect - 2) & \ | |
| 160 | (np.abs(yy2 - cy) <= 2) | |
| 161 | far = ~near | |
| 162 | ratio_db = 10 * np.log10(pw[near].max() / pw[far].max()) | |
| 163 | passed = ratio_db > 30 | |
| 164 | ok &= passed | |
| 165 | if verbose: | |
| 166 | print(f" [{'PASS' if passed else 'FAIL'}] leakage (2-D) " | |
| 167 | f"peak is {ratio_db:.1f} dB above off-axis (expect > 30.0)") | |
| 168 | ||
| 169 | # 3b. Purity through the RADIAL path, with an isotropic signal. Radial | |
| 170 | # averaging divides an annulus's energy by its pixel count, so it | |
| 171 | # dilutes a directional peak by ~10*log10(2*pi*r/2) -- about 17 dB at | |
| 172 | # r=16 -- by construction, not by fault. The instrument is built for | |
| 173 | # isotropic fields, so the check uses one: energy on a ring at k=16 | |
| 174 | # and nowhere else. | |
| 175 | kx = np.fft.fftfreq(n) * n | |
| 176 | KX, KY = np.meshgrid(kx, kx) | |
| 177 | KR = np.sqrt(KX ** 2 + KY ** 2) | |
| 178 | ring = (np.abs(KR - expect) < 0.5).astype(np.float64) | |
| 179 | ph = rng.uniform(0, 2 * np.pi, (n, n)) | |
| 180 | field_iso = np.real(np.fft.ifft2(ring * np.exp(1j * ph))) | |
| 181 | k, p = radial_power_spectrum(field_iso) | |
| 182 | peak_iso = int(k[np.argmax(p)]) | |
| 183 | mask = np.abs(k - expect) > 2 | |
| 184 | ratio_db = 10 * np.log10(p.max() / p[mask].max()) | |
| 185 | passed = (peak_iso == expect) and ratio_db > 30 | |
| 186 | ok &= passed | |
| 187 | if verbose: | |
| 188 | print(f" [{'PASS' if passed else 'FAIL'}] ring k={expect} isotropic " | |
| 189 | f"peak at k = {peak_iso}, {ratio_db:.1f} dB above next " | |
| 190 | f"(expect {expect}, > 30.0)") | |
| 191 | ||
| 192 | # 4. Known power law in -> same power law out. | |
| 193 | for target in (1.0, 2.0): | |
| 194 | f = np.fft.fftfreq(n) * n | |
| 195 | kx, ky = np.meshgrid(f, f) | |
| 196 | kr = np.sqrt(kx ** 2 + ky ** 2) | |
| 197 | kr[0, 0] = 1.0 | |
| 198 | amp = kr ** (-target / 2.0) | |
| 199 | ph = rng.uniform(0, 2 * np.pi, (n, n)) | |
| 200 | spec = amp * np.exp(1j * ph) | |
| 201 | field = np.real(np.fft.ifft2(spec)) | |
| 202 | k, p = radial_power_spectrum(field) | |
| 203 | beta, _, r2 = fit_power_law(k, p, 8, n // 4) | |
| 204 | passed = abs(beta - target) < 0.20 | |
| 205 | ok &= passed | |
| 206 | if verbose: | |
| 207 | print(f" [{'PASS' if passed else 'FAIL'}] synthetic k^-{target:.0f} " | |
| 208 | f"beta = {beta:.3f} (expect {target:.3f} +/- 0.200), R2 = {r2:.3f}") | |
| 209 | ||
| 210 | return ok | |
| 211 | ||
| 212 | ||
| 213 | # --------------------------------------------------------------------------- | |
| 214 | # The terrain fetch. | |
| 215 | # --------------------------------------------------------------------------- | |
| 216 | ||
| 217 | def fetch_tile(url, i1, j1, size, scale, timeout=1800): | |
| 218 | import urllib.request | |
| 219 | q = (f"{url}/terrain?i1={i1}&j1={j1}&i2={i1 + size}&j2={j1 + size}" | |
| 220 | f"&scale={scale}") | |
| 221 | with urllib.request.urlopen(q, timeout=timeout) as r: | |
| 222 | h = int(r.headers["X-Height"]) | |
| 223 | w = int(r.headers["X-Width"]) | |
| 224 | body = r.read() | |
| 225 | nelev = h * w * 2 | |
| 226 | elev = np.frombuffer(body[:nelev], dtype="<i2").reshape(h, w).astype(np.float64) | |
| 227 | climate = None | |
| 228 | if len(body) > nelev: | |
| 229 | climate = np.frombuffer(body[nelev:], dtype="<f4").reshape(h, w, 4) | |
| 230 | return elev, climate | |
| 231 | ||
| 232 | ||
| 233 | def describe(name, elev, px_metres, kmin, kmax): | |
| 234 | k, p = radial_power_spectrum(elev) | |
| 235 | n = elev.shape[0] | |
| 236 | beta, r2, resid_db, nbins = top_octave_residual(k, p, kmin, kmax) | |
| 237 | ||
| 238 | print(f"\n {name}") | |
| 239 | print(f" tile {n} x {n} px @ {px_metres:g} m/px " | |
| 240 | f"= {n * px_metres / 1000:.1f} km across") | |
| 241 | print(f" elevation min {elev.min():.0f} m max {elev.max():.0f} m " | |
| 242 | f"mean {elev.mean():.0f} m sd {elev.std():.0f} m") | |
| 243 | print(f" fit band k = {kmin}..{kmax} " | |
| 244 | f"(lambda {n * px_metres / kmax:.0f} m .. {n * px_metres / kmin / 1000:.1f} km)") | |
| 245 | print(f" power law P(k) ~ k^-{beta:.3f} R2 = {r2:.4f}") | |
| 246 | print(f" above k={kmax} {resid_db:+.1f} dB vs the fit, " | |
| 247 | f"median over {nbins} bins") | |
| 248 | print(f" Nyquist lambda = {2 * px_metres:g} m") | |
| 249 | return k, p, beta, resid_db | |
| 250 | ||
| 251 | ||
| 252 | def main(): | |
| 253 | ap = argparse.ArgumentParser() | |
| 254 | ap.add_argument("--url", default="http://127.0.0.1:8817") | |
| 255 | ap.add_argument("--size", type=int, default=512) | |
| 256 | ap.add_argument("--i1", type=int, default=0) | |
| 257 | ap.add_argument("--j1", type=int, default=0) | |
| 258 | ap.add_argument("--native-metres", type=float, default=30.0, | |
| 259 | help="metres per pixel at scale=1 for the loaded model") | |
| 260 | ap.add_argument("--compare", type=int, nargs="*", default=[1], | |
| 261 | help="scale factors to fetch and compare") | |
| 262 | ap.add_argument("--selftest", action="store_true", | |
| 263 | help="run the instrument checks and exit") | |
| 264 | ap.add_argument("--csv", default=None, help="write spectra to this CSV") | |
| 265 | args = ap.parse_args() | |
| 266 | ||
| 267 | print("INSTRUMENT SELF-TEST") | |
| 268 | if not selftest(): | |
| 269 | print("\n FAILED. The ruler is broken; no terrain number is meaningful.") | |
| 270 | return 2 | |
| 271 | print(" all pass\n") | |
| 272 | if args.selftest: | |
| 273 | return 0 | |
| 274 | ||
| 275 | n = args.size | |
| 276 | kmin, kmax = 6, n // 8 # fit below the top two octaves, extrapolate up | |
| 277 | ||
| 278 | print(f"TERRAIN {args.url} region ({args.i1},{args.j1}) size {n}") | |
| 279 | results = {} | |
| 280 | for s in args.compare: | |
| 281 | # Same WORLD region at every scale: coordinates are in target pixels. | |
| 282 | # Same WORLD footprint at every scale: n native px wide. At scale s | |
| 283 | # that is n*s target px starting at i1*s. Do NOT crop to n -- cropping | |
| 284 | # would compare a smaller region, not a finer sampling of the same one. | |
| 285 | elev, climate = fetch_tile(args.url, args.i1 * s, args.j1 * s, n * s, s) | |
| 286 | elev_c = elev | |
| 287 | px = args.native_metres / s | |
| 288 | # kmin/kmax stay FIXED across scales. n*px is the same physical width | |
| 289 | # for every scale, so a given k is the same physical wavelength -- the | |
| 290 | # fit band is therefore identical and the residual above it asks | |
| 291 | # exactly the question: does a finer scale add energy up there? | |
| 292 | k, p, beta, resid = describe( | |
| 293 | f"scale={s} ({px:g} m/px)", elev_c, px, kmin, kmax) | |
| 294 | results[s] = (k, p, beta, resid) | |
| 295 | if climate is not None: | |
| 296 | c = climate | |
| 297 | print(f" climate temp {c[...,0].mean():.1f} C " | |
| 298 | f"t_season {c[...,1].mean():.0f} " | |
| 299 | f"precip {c[...,2].mean():.0f} mm/yr " | |
| 300 | f"p_cv {c[...,3].mean():.0f}%") | |
| 301 | ||
| 302 | if args.csv: | |
| 303 | import csv | |
| 304 | with open(args.csv, "w", newline="") as fh: | |
| 305 | wr = csv.writer(fh) | |
| 306 | wr.writerow(["scale", "k", "power"]) | |
| 307 | for s, (k, p, _, _) in results.items(): | |
| 308 | for kk, pp in zip(k, p): | |
| 309 | wr.writerow([s, int(kk), f"{pp:.10e}"]) | |
| 310 | print(f"\n spectra written to {args.csv}") | |
| 311 | ||
| 312 | return 0 | |
| 313 | ||
| 314 | ||
| 315 | if __name__ == "__main__": | |
| 316 | sys.exit(main()) |