mirror of
https://github.com/Cubitect/cubiomes.git
synced 2025-08-03 09:46:41 -04:00

* added basic support for cmake (#127) * renamed Winter Drop version from MC_1_21_3 to MC_1_21_WD * updated world spawn location for 1.21.2 (cubiomes-viewer #340) * tweaked mc version to text conversion (#128) * removed properties field in structure config and added dimension field instead * moved biome tree selection back to biomenoise.c as it's slightly faster and avoids globals
545 lines
15 KiB
C
545 lines
15 KiB
C
#include "finders.h"
|
|
#include "util.h"
|
|
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <float.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
|
|
static uint32_t hash32(uint32_t x)
|
|
{
|
|
x ^= x >> 15;
|
|
x *= 0xd168aaad;
|
|
x ^= x >> 15;
|
|
x *= 0xaf723597;
|
|
x ^= x >> 15;
|
|
return x;
|
|
}
|
|
|
|
static double now()
|
|
{
|
|
struct timespec t;
|
|
clock_gettime(CLOCK_MONOTONIC, &t);
|
|
return t.tv_sec + t.tv_nsec * 1e-9;
|
|
}
|
|
|
|
/* Runs a performance test using function f(). The function should take a
|
|
* number of trial runs and a data argument [dat], and return an arbitrary
|
|
* consumable value (useful to avoid ommission by optimization).
|
|
* The minimum and mean times for a trial run will be stored in tmin and tavg.
|
|
* The benchmark aims to take just over one second.
|
|
* Returned is the total number of trials performed.
|
|
*/
|
|
int64_t
|
|
benchmark(int64_t (*f)(int64_t n, void*), void *dat, double *tmin, double *tavg)
|
|
{
|
|
const double maxt = 1.0;
|
|
const double mintt = 1e-2;
|
|
int64_t cnt = 0;
|
|
int64_t ntt = 1;
|
|
int64_t consume = 0;
|
|
double t, _tavg = 0, _tmin = DBL_MAX;
|
|
|
|
for (ntt = 1; ; ntt *= 2)
|
|
{
|
|
t = -now();
|
|
consume ^= f(ntt, dat);
|
|
t += now();
|
|
if (t >= mintt)
|
|
break;
|
|
}
|
|
do
|
|
{
|
|
t = -now();
|
|
consume ^= f(ntt, dat);
|
|
t += now();
|
|
cnt++;
|
|
_tavg += t;
|
|
if (t < _tmin)
|
|
_tmin = t;
|
|
}
|
|
while (_tavg < maxt);
|
|
|
|
cnt *= ntt;
|
|
if (tmin) *tmin = _tmin / ntt;
|
|
if (tavg) *tavg = _tavg / cnt;
|
|
|
|
static volatile int64_t v_consume;
|
|
v_consume ^= consume;
|
|
return cnt;
|
|
}
|
|
|
|
uint32_t getRef(int mc, int dim, int bits, int scale, int spread, const char *path)
|
|
{
|
|
Generator g;
|
|
setupGenerator(&g, mc, 0);
|
|
|
|
FILE *fp = NULL;
|
|
if (path) fp = fopen(path, "w");
|
|
|
|
int r = 1 << (bits-1);
|
|
int h = 0;
|
|
int x, z;
|
|
for (x = -r; x < r; x++)
|
|
{
|
|
for (z = -r; z < r; z++)
|
|
{
|
|
int64_t s = (int64_t)( (z << bits) ^ x );
|
|
applySeed(&g, dim, s);
|
|
int y = (int)((hash32((int)s) & 0x7fffffff) % 384 - 64) >> 2;
|
|
int id = getBiomeAt(&g, scale, x, y, z);
|
|
h ^= hash32( (int) s ^ (id << 2*bits) );
|
|
if (fp)
|
|
//fprintf(fp, "%5d%6d%4d\n", x*spread, z*spread, id);
|
|
fprintf(fp, "%2ld @ %2d %4d %2d - %4d %08x\n", s, x, y, z, id, h);
|
|
}
|
|
}
|
|
if (fp && fp != stdout)
|
|
fclose(fp);
|
|
return h;
|
|
}
|
|
|
|
int testBiomeGen1x1(const int *mc, const uint32_t *expect, int dim, int bits, int spread, int cnt)
|
|
{
|
|
int test;
|
|
uint32_t h;
|
|
int ok = 1;
|
|
|
|
for (test = 0; test < cnt; test++)
|
|
{
|
|
printf(" [%*d/%*d] MC %-6s dim=%-2d: expecting %08x ... ",
|
|
1+(cnt>9), test+1, 1+(cnt>9), cnt, mc2str(mc[test]), dim, expect[test]);
|
|
fflush(stdout);
|
|
|
|
double t = -now();
|
|
h = getRef(mc[test], dim, bits, 4, spread, NULL);
|
|
t += now();
|
|
printf("got %08x %s\e[0m (%ld msec)\n",
|
|
h, h == expect[test] ? "\e[1;92mOK" : "\e[1;91mFAILED",
|
|
(long)(t*1e3));
|
|
ok &= (h == expect[test]);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
uint32_t testAreas(int mc, int dim, int scale)
|
|
{
|
|
Generator g;
|
|
setupGenerator(&g, mc, 0);
|
|
|
|
SurfaceNoise sn;
|
|
|
|
double t = -now();
|
|
uint32_t hash = 0;
|
|
uint64_t s;
|
|
for (s = 0; s < 1000; s++)
|
|
{
|
|
int d = 40000;
|
|
int x = hash32(s << 5) % d - d/2;
|
|
int y = ((int)(hash32(s << 7) % 384) - 64);
|
|
int z = hash32(s << 9) % d - d/2;
|
|
int w = 1 + hash32(s << 11) % 128;
|
|
int h = 1 + hash32(s << 13) % 128;
|
|
|
|
applySeed(&g, dim, s);
|
|
Range r = {scale, x, z, w, h, y, 1};
|
|
int *ids = allocCache(&g, r);
|
|
genBiomes(&g, ids, r);
|
|
/*
|
|
float *surf = malloc(4 * w * h);
|
|
initSurfaceNoise(&sn, dim, s);
|
|
mapApproxHeight(surf, 0, &g, &sn, x, z, w, h);
|
|
for (int i = 0; i < w*h; i++)
|
|
ids[i] = (int) surf[i];
|
|
free(surf);
|
|
*/
|
|
|
|
int i = 0;
|
|
hash = 0;
|
|
for (i = 0; i < w*h; i++)
|
|
hash = hash32(hash ^ hash32(ids[i] + (i << 17)));
|
|
free(ids);
|
|
}
|
|
t += now();
|
|
printf(" MC %-6s dim %-2d @ 1:%-3d - %08x [%ld msec]\n",
|
|
mc2str(mc), dim, scale, hash, (long)(t*1e3));
|
|
return hash;
|
|
}
|
|
|
|
|
|
|
|
|
|
int testGeneration()
|
|
{
|
|
const int mc_vers[] = {
|
|
MC_1_20, MC_1_19, MC_1_19_2, MC_1_18,
|
|
MC_1_16, MC_1_15, MC_1_13, MC_1_12, MC_1_9, MC_1_7,
|
|
MC_1_6, MC_1_2, MC_1_1, MC_1_0, MC_B1_8,
|
|
};
|
|
const uint32_t b6_hashes[] = {
|
|
0x0f8888ab, 0x391c36ec, 0xea3e8c1c, 0xade7f891,
|
|
0xde9a6574, 0x3a568a6d, 0x96c97323, 0xbc75e996, 0xe27a45a2, 0xbc75e996,
|
|
0x15b47206, 0x2d7e0fed, 0x5cbf4709, 0xbd794adb, 0x00000000,
|
|
};
|
|
const int testcnt = sizeof(mc_vers) / sizeof(int);
|
|
|
|
printf("Testing 1x1 biome generation (quick):\n");
|
|
if (!testBiomeGen1x1(mc_vers, b6_hashes, 0, 6, 1, testcnt))
|
|
return -1;
|
|
|
|
//Generator g;
|
|
//setupGenerator(&g, MC_1_18, 0);
|
|
//applySeed(&g, 0, 1234);
|
|
//Pos p = getSpawn(&g);
|
|
//printf("%d %d\n", p.x, p.z);
|
|
|
|
//StrongholdIter sh;
|
|
//initFirstStronghold(&sh, g.mc, g.seed);
|
|
//while (nextStronghold(&sh, &g) > 0)
|
|
// printf("Stronghold #: (%6d, %6d)\n", sh.pos.x, sh.pos.z);
|
|
|
|
//printf("Area generation tests:\n");
|
|
//testAreas(MC_1_19, 0, 4);
|
|
//testAreas(MC_1_18, 0, 4);
|
|
//testAreas(MC_1_17, 0, 4);
|
|
|
|
const uint32_t b10_hashes[] = {
|
|
0x00000000, 0x00000000, 0x00000000,
|
|
0xfdede71d, 0xca8005d7, 0x399f7cc8, 0xb3363967, 0x17e5592f, 0xb3363967,
|
|
0xa52e377c, 0xdb1df71d, 0x58e86947, 0xe1e89cc3, 0x00000000,
|
|
};
|
|
printf("Testing 1x1 biome generation (thorough):\n");
|
|
if (!testBiomeGen1x1(mc_vers, b10_hashes, 0, 10, 1, testcnt))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int k_tot;
|
|
struct _f_para { double v; double *buf; int x, z, w, h; };
|
|
int _f1(void *data, int x, int z, double v)
|
|
{
|
|
struct _f_para d = *(struct _f_para*) data;
|
|
d.buf[(x-d.x)*d.w + (z-d.z)] = d.v;
|
|
k_tot++;
|
|
return 0;
|
|
}
|
|
|
|
void testNoiseRangeFinder()
|
|
{
|
|
int n = 100;
|
|
unsigned char *pix = (unsigned char*) malloc(n*n*3);
|
|
double *buf = (double*) malloc(n*n*8);
|
|
double bmin, bmax;
|
|
int i, j;
|
|
|
|
int64_t seed = 0, seed_max = 200;
|
|
int bad = 0;
|
|
for (seed = 0; seed < seed_max; seed++)
|
|
{
|
|
//printf("%ld\n", seed);
|
|
Generator g;
|
|
setupGenerator(&g, MC_1_18, 0);
|
|
applySeed(&g, 0, seed);
|
|
|
|
int x = 100, z = -100;
|
|
bmin = 99999, bmax = -99999;
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
for (j = 0; j < n; j++)
|
|
{
|
|
double t = sampleDoublePerlin(&g.bn.climate[NP_HUMIDITY], x+i, 0, z+j);
|
|
if (t < bmin) bmin = t;
|
|
if (t > bmax) bmax = t;
|
|
buf[i*n+j] = t;
|
|
}
|
|
}
|
|
|
|
struct _f_para f_p = {-1e9, buf, x, z, n, n };
|
|
double tmin, tmax;
|
|
int k = k_tot;
|
|
getParaRange(&g.bn.climate[NP_HUMIDITY], &tmin, &tmax, x, z, n, n, &f_p, _f1);
|
|
if (fabs(tmin-bmin*1e4)>.01||fabs(tmax-bmax*1e4)>.01)
|
|
{
|
|
printf("=========================== BAD ============================\n");
|
|
printf("seed:%-8ld temp = [%g %g], best = [%g %g]\n",
|
|
seed, tmin, tmax, bmin * 10000, bmax * 10000);
|
|
bad++;
|
|
}
|
|
//printf("wh:%d (%d) -> k:%d\n", 2*n, n*n, k_tot - k);
|
|
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
for (j = 0; j < n; j++)
|
|
{
|
|
unsigned char *p = pix + 3*(i*n+j);
|
|
double b = buf[i*n+j];
|
|
if (b == bmin || b == bmax) {
|
|
p[1] = 0xff; p[0] = p[2] = 0;
|
|
continue;
|
|
}
|
|
if (b == -1e9) {
|
|
p[0] = 0xff; p[1] = p[2] = 0;
|
|
continue;
|
|
}
|
|
if (b == -1e9+1) {
|
|
p[2] = 0xff; p[0] = p[1] = 0;
|
|
continue;
|
|
}
|
|
p[0] = p[1] = p[2] = (unsigned char)
|
|
((b - bmin) / (bmax - bmin) * 256);
|
|
}
|
|
}
|
|
if (bad >= 10) break;
|
|
}
|
|
|
|
printf("bad:%d k_tot: %d / %ld ~ %g : %d\n", bad, k_tot, seed, k_tot / (double)seed, n*n);
|
|
savePPM("img.ppm", pix, n, n);
|
|
}
|
|
|
|
|
|
int64_t bbounds[256][6][2]; // [biome][np][min/max]
|
|
|
|
int _f2(void *data, int x, int z, double v)
|
|
{
|
|
int64_t np[6];
|
|
Generator *g = (Generator*) data;
|
|
int id = sampleBiomeNoise(&g->bn, np, x, -64+rand()%384, z, 0, SAMPLE_NO_SHIFT);
|
|
int i;
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
if (np[i] < bbounds[id][i][0]) bbounds[id][i][0] = np[i];
|
|
if (np[i] > bbounds[id][i][1]) bbounds[id][i][1] = np[i];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void findBiomeParaBounds()
|
|
{
|
|
int i, j;
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
for (j = 0; j < 6; j++)
|
|
{
|
|
bbounds[i][j][0] = +1e8;
|
|
bbounds[i][j][1] = -1e8;
|
|
}
|
|
}
|
|
|
|
Generator g;
|
|
setupGenerator(&g, MC_1_21, 0);
|
|
int64_t s;
|
|
int r = 1000;
|
|
for (s = 0; s < 20000; s++)
|
|
{
|
|
int64_t seed = ((int64_t)hash32(s) << 32) ^ hash32(rand());
|
|
applySeed(&g, 0, seed);
|
|
double tmin, tmax;
|
|
int x = rand() % 10000 - 5000;
|
|
int z = rand() % 10000 - 5000;
|
|
getParaRange(&g.bn.climate[NP_TEMPERATURE], &tmin, &tmax, x-r, z-r, r, r, &g, _f2);
|
|
getParaRange(&g.bn.climate[NP_HUMIDITY], &tmin, &tmax, x-r, z-r, r, r, &g, _f2);
|
|
getParaRange(&g.bn.climate[NP_EROSION], &tmin, &tmax, x-r, z-r, r, r, &g, _f2);
|
|
getParaRange(&g.bn.climate[NP_CONTINENTALNESS], &tmin, &tmax, x-r, z-r, r, r, &g, _f2);
|
|
getParaRange(&g.bn.climate[NP_WEIRDNESS], &tmin, &tmax, x-r, z-r, r, r, &g, _f2);
|
|
if (s % 1000 == 999)
|
|
printf(".\n");
|
|
}
|
|
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
if (!isOverworld(MC_1_21, i))
|
|
continue;
|
|
|
|
printf("{%-24s", biome2str(MC_1_21, i));
|
|
for (j = 0; j < 6; j++)
|
|
{
|
|
printf(", %6ld,%6ld", bbounds[i][j][0], bbounds[i][j][1]);
|
|
}
|
|
printf("},\n");
|
|
}
|
|
}
|
|
|
|
static void canGenerateTest(int mc, int layerId)
|
|
{
|
|
Generator g;
|
|
setupGenerator(&g, mc, 0);
|
|
int ids[0x1000];
|
|
int idcnt[256] = {};
|
|
int i;
|
|
uint64_t seed;
|
|
Layer *layer = g.ls.layers + layerId;
|
|
|
|
for (seed = 0; seed < 1e6; seed++)
|
|
{
|
|
applySeed(&g, DIM_OVERWORLD, seed);
|
|
genArea(layer, ids, 0, 0, 1, 1);
|
|
int id = ids[0];
|
|
idcnt[id]++;
|
|
}
|
|
int ok = 1;
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
int cnt = idcnt[i];
|
|
int can = canBiomeGenerate(layerId, mc, 0, i);
|
|
if (cnt == 0 && can == 0)
|
|
continue;
|
|
if (cnt != 0 && can == 1)
|
|
continue;
|
|
ok = 0;
|
|
printf("can:%d, cnt:%d (%s)\n", can, cnt, biome2str(mc, i));
|
|
}
|
|
printf("canBiomesGenerate() for MC %-4s, layer (%d) %s!\n",
|
|
mc2str(mc), layerId, ok ? "PASSED" : "FAILED");
|
|
}
|
|
|
|
void testCanBiomesGenerate()
|
|
{
|
|
canGenerateTest(MC_B1_8, L_BIOME_256);
|
|
canGenerateTest(MC_B1_8, L_ZOOM_64);
|
|
canGenerateTest(MC_B1_8, L_ZOOM_16);
|
|
canGenerateTest(MC_B1_8, L_RIVER_MIX_4);
|
|
canGenerateTest(MC_1_0, L_BIOME_256);
|
|
canGenerateTest(MC_1_0, L_ZOOM_64);
|
|
canGenerateTest(MC_1_0, L_ZOOM_16);
|
|
canGenerateTest(MC_1_0, L_RIVER_MIX_4);
|
|
canGenerateTest(MC_1_6, L_BIOME_256);
|
|
canGenerateTest(MC_1_6, L_HILLS_64);
|
|
canGenerateTest(MC_1_6, L_SWAMP_RIVER_16);
|
|
canGenerateTest(MC_1_6, L_RIVER_MIX_4);
|
|
canGenerateTest(MC_1_7, L_BIOME_256);
|
|
canGenerateTest(MC_1_7, L_SUNFLOWER_64);
|
|
canGenerateTest(MC_1_7, L_SHORE_16);
|
|
canGenerateTest(MC_1_7, L_RIVER_MIX_4);
|
|
canGenerateTest(MC_1_17, L_BAMBOO_256);
|
|
canGenerateTest(MC_1_17, L_SUNFLOWER_64);
|
|
canGenerateTest(MC_1_17, L_SHORE_16);
|
|
canGenerateTest(MC_1_17, L_OCEAN_MIX_4);
|
|
canGenerateTest(MC_1_17, L_OCEAN_TEMP_256);
|
|
}
|
|
|
|
|
|
void findStructures(int structureType, int mc, int dim, uint64_t seed,
|
|
int x0, int z0, int x1, int z1)
|
|
{
|
|
// set up a biome generator
|
|
Generator g;
|
|
setupGenerator(&g, mc, 0);
|
|
applySeed(&g, dim, seed);
|
|
|
|
// ignore this if you are not looking for end cities
|
|
SurfaceNoise sn;
|
|
if (structureType == End_City)
|
|
initSurfaceNoise(&sn, DIM_END, seed);
|
|
|
|
StructureConfig sconf;
|
|
if (!getStructureConfig(structureType, mc, &sconf))
|
|
return; // bad version or structure
|
|
|
|
// segment area into structure regions
|
|
double blocksPerRegion = sconf.regionSize * 16.0;
|
|
int rx0 = (int) floor(x0 / blocksPerRegion);
|
|
int rz0 = (int) floor(z0 / blocksPerRegion);
|
|
int rx1 = (int) ceil(x1 / blocksPerRegion);
|
|
int rz1 = (int) ceil(z1 / blocksPerRegion);
|
|
int i, j;
|
|
|
|
for (j = rz0; j <= rz1; j++)
|
|
{
|
|
for (i = rx0; i <= rx1; i++)
|
|
{ // check the structure generation attempt in region (i, j)
|
|
Pos pos;
|
|
if (!getStructurePos(structureType, mc, seed, i, j, &pos))
|
|
continue; // this region is not suitable
|
|
if (pos.x < x0 || pos.x > x1 || pos.z < z0 || pos.z > z1)
|
|
continue; // structure is outside the specified area
|
|
if (!isViableStructurePos(structureType, &g, pos.x, pos.z, 0))
|
|
continue; // biomes are not viable
|
|
if (structureType == End_City)
|
|
{ // end cities have a dedicated terrain checker
|
|
if (!isViableEndCityTerrain(&g, &sn, pos.x, pos.z))
|
|
continue;
|
|
}
|
|
else if (mc >= MC_1_18)
|
|
{ // some structures in 1.18+ depend on the terrain
|
|
if (!isViableStructureTerrain(structureType, &g, pos.x, pos.z))
|
|
continue;
|
|
}
|
|
|
|
int id = getBiomeAt(&g, 4, pos.x>>2, 320>>2, pos.z>>2);
|
|
StructureVariant sv;
|
|
getVariant(&sv, structureType, mc, seed, pos.x, pos.z, id);
|
|
int x = pos.x + sv.x;
|
|
int z = pos.z + sv.z;
|
|
printf("%d, %d : [%d %d %d] - [%d %d %d]\n", pos.x, pos.z,
|
|
x, sv.y, z, x+sv.sx, sv.y+sv.sy, z+sv.sz
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
int getStructureConfig_override(int stype, int mc, StructureConfig *sconf)
|
|
{
|
|
return getStructureConfig(stype, mc, sconf);
|
|
}
|
|
|
|
|
|
|
|
int main()
|
|
{
|
|
/*
|
|
int mc = MC_1_21;
|
|
uint64_t seed = 2;
|
|
|
|
double t0 = 0, t1 = 0;
|
|
t0 -= now();
|
|
|
|
EndNoise en;
|
|
setEndSeed(&en, mc, seed);
|
|
|
|
SurfaceNoise sn;
|
|
initSurfaceNoise(&sn, DIM_END, seed);
|
|
|
|
Pos src[20];
|
|
getFixedEndGateways(mc, seed, src);
|
|
|
|
t0 += now();
|
|
t1 -= now();
|
|
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
Pos dst = getLinkedGatewayPos(&en, &sn, seed, src[i]);
|
|
printf("%d %d -> %d %d\n", src[i].x, src[i].z, dst.x, dst.z);
|
|
}
|
|
t1 += now();
|
|
|
|
printf("Time: %g -> %g sec\n", t0, t1);
|
|
*/
|
|
|
|
//findStructures(Trial_Chambers, MC_1_21, 0, 1, 3056, 3440, 3056, 3440);
|
|
|
|
//endHeight(MC_1_21, 1, 80>>1, 1216>>1, 32, 32, 2);
|
|
|
|
//endHeight(MC_1_15, 1, 370704, 96, 32, 32, 1);
|
|
|
|
//testAreas(MC_1_21, 1, 1);
|
|
//testAreas(MC_1_21, 0, 4);
|
|
//testAreas(mc, 0, 16);
|
|
//testAreas(mc, 0, 256);
|
|
//testCanBiomesGenerate();
|
|
//testGeneration();
|
|
//findBiomeParaBounds();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|