|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T m
Length: 14026 (0x36ca) Types: TextFile Names: »mkmap.c«
└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987 └─⟦this⟧ »EUUGD18/X/Xconq/mkmap.c«
/* Copyright (c) 1987, 1988 Stanley T. Shebs, University of Utah. */ /* This program may be used, copied, modified, and redistributed freely */ /* for noncommercial purposes, so long as this notice remains intact. */ /* RCS $Header: mkmap.c,v 1.1 88/06/21 12:30:21 shebs Exp $ */ /* Terrain generation is based on fractal ideas, although this version */ /* is not directly derived from a published technique. */ /* Speed is important; most of the code has been integerized. */ /* Extra steps produce maps more suitable for conquest, including a way to */ /* control the amount of each type of terrain appearing in the result. */ /* The process is actually done for elevation and water separately, then */ /* the terrain type is dependent on both. */ /* (Note that this is no longer a separate program as in the first version.) */ #include "config.h" #include "misc.h" #include "period.h" #include "dir.h" #include "map.h" /* Values of altitude and moisture must be firmly bounded, so that */ /* histogramming doesn't take too much space. */ #define MAXALT 16384 /* Abstraction of 2D malloced array references. Names from Lisp... */ #define aref(m,x,y) ((m)[(x)+world.width*(y)]) #define aset(m,x,y,v) ((m)[(x)+world.width*(y)] = (v)) #define aadd(m,x,y,v) ((m)[(x)+world.width*(y)] += (v)) /* Values ultimately arriving from the command line. */ extern int givenwidth, givenheight, givenseen; /* Arrays here should contain only nonnegative values (for simplicity). */ /* They are potentially large and used only once, so we malloc and free... */ int *relief; /* pointer to array of elevations */ int *water; /* pointer to array of moisture levels */ int *aux; /* auxiliary array */ int *histo; /* histogram array */ int *alts; /* percentile for each elevation */ int *wets; /* percentile for level of moisture */ /* The main function looks a little strange in spots, since it is derived */ /* from a former standalone program (the mkmap of version 1). */ make_up_map() { int width, height; width = givenwidth; height = givenheight; if (Debug) printf("Going to make up a %dx%d map ...\n", width, height); /* Heuristic limit - algorithms get weird on small maps */ if (width < 9 || height < 9) { fprintf(stderr, "Cannot generate such a small map!\n"); exit(1); } world.width = width; world.height = height; world.scale = period.scale; world.known = givenseen; relief = (int *) malloc(world.width * world.height * sizeof(int)); aux = (int *) malloc(world.width * world.height * sizeof(int)); water = (int *) malloc(world.width * world.height * sizeof(int)); histo = (int *) malloc(MAXALT * sizeof(int)); alts = (int *) malloc(MAXALT * sizeof(int)); wets = (int *) malloc(MAXALT * sizeof(int)); /* Build a full relief map */ make_bumps(relief, period.altroughness); smooth_map(relief); add_curves(relief, period.altroughness); smooth_map(relief); limit_map(relief, MAXALT-1, 0); percentile(relief, alts); /* Build a "moisture relief" map */ make_bumps(water, period.wetroughness); smooth_map(water); smooth_map(water); limit_map(water, MAXALT-1, 0); percentile(water, wets); /* Put it all together */ compose_map(); free(relief); free(water); free(aux); free(histo); free(alts); free(wets); if (Debug) printf("... Done making up a map.\n"); } /* The main map generators produces bumps, of sizes and numbers dictated */ /* by the roughness. The size is a percentage of map dimensions, while */ /* number is set to approximately cover the entire map. Each bump is a */ /* hexagonal shape, and the elevation of interior points varies. */ make_bumps(map, roughness) int *map, roughness; { int blocks, i, scale, var, dx, dy, tries; int x0, y0, x1, y1, d0, d1, xt, yt, flag, x, y, dz, z, oz; int dn1, dn2, dn3, dn4, dn5, dn6, numer1, denom1, numer2, denom2; int farx[10], fary[10]; int peakx[10], peaky[10], peakz[10], peakf[10], numpeaks = 0, p; scale = max(1, ((100 - roughness) * min(world.width, world.height)) / 100); var = MAXALT/8 + (MAXALT/8 * roughness) / 100; blocks = (world.width * world.height) / (scale * scale); blocks *= 2; if (Debug) printf("Playing with blocks (%d)...\n", blocks); for (x = 0; x < world.width; ++x) { for (y = 0; y < world.height; ++y) { aset(map, x, y, MAXALT/2); } } for (i = 0; i < blocks; ++i) { flag = ((i == 0 || flip_coin()) ? 1 : -1); if (scale <= 1) { x = random(world.width) + world.width; y = random(world.height); z = var + random(var/2); aadd(map, wrap(x), y, flag * z); } else { dx = scale/2 + random(scale/2); dy = scale/2 + random(scale/2); /* All x values shifted, so as to avoid negatives */ x0 = random(world.width) + world.width; y0 = random(world.height - dy - 1); x1 = x0 + dx + 1; y1 = y0 + dy + 1; d0 = min(x1 - x0, y1 - y0) / 4; d0 = d0 + random(3*d0); d1 = min(x1 - x0, y1 - y0) / 4; d1 = d1 + random(3*d1); /* Cheap way to ensure most high points actually within hexagon */ xt = yt = -1; tries = 10; while (((xt-x0+yt-y0 < d0) || (x1-xt+y1-yt > d1)) && tries-- > 0) { xt = x0 + (random(x1 - x0) + random(x1 - x0)) / 2; yt = y0 + (random(y1 - y0) + random(y1 - y0)) / 2; } /* Compute distances from high point to hexagon corners */ dn1 = distance(xt, yt, x1-d1, y1); dn2 = distance(xt, yt, x1, y1-d1); dn3 = distance(xt, yt, x1, y0); dn4 = distance(xt, yt, x0+d0, y0); dn5 = distance(xt, yt, x0, y0+d0); dn6 = distance(xt, yt, x0, y1); for (y = y0; y <= y1; ++y) { for (x = x0; x <= x1; ++x) { if ((x-x0+y-y0 > d0) && (x1-x+y1-y > d1)) { dx = x - xt; dy = y - yt; if (dx > 0) { if (dy > 0) { numer1 = distance(x, y, x1, y1-d1); denom1 = dn2; numer2 = distance(x, y, x1-d1, y1); denom2 = dn1; } else if (dy > (0 - dx)) { numer1 = distance(x, y, x1, y0); denom1 = dn3; numer2 = distance(x, y, x1, y1-d1); denom2 = dn2; } else { numer1 = distance(x, y, x0+d0, y0); denom1 = dn4; numer2 = distance(x, y, x1, y0); denom2 = dn3; } } else { if (dy < 0) { numer1 = distance(x, y, x0, y0+d0); denom1 = dn5; numer2 = distance(x, y, x0+d0, y0); denom2 = dn4; } else if (dy < (0 - dx)) { numer1 = distance(x, y, x0, y1); denom1 = dn6; numer2 = distance(x, y, x0, y0+d0); denom2 = dn5; } else { numer1 = distance(x, y, x1-d1, y1); denom1 = dn1; numer2 = distance(x, y, x0, y1); denom2 = dn6; } } if (denom1 == 0) denom1 = 1; if (denom2 == 0) denom2 = 1; dz = (flag * ((var * numer1) / denom1 + (var * numer2) / denom2)); oz = aref(map, wrap(x), y); if (!between(0, dz + oz, MAXALT)) dz /= 2; aset(map, wrap(x), y, oz + dz + random(var/2)); } } } } /* Remember some high points for use in hitting untouched areas */ if (numpeaks < 10) { peakx[numpeaks] = wrap(xt); peaky[numpeaks] = yt; peakf[numpeaks] = flag; peakz[numpeaks] = var; farx[numpeaks] = wrap(xt+world.width/2); fary[numpeaks] = random(world.height); numpeaks++; } } /* This is to make smoothly varying terrain in formerly flat areas */ for (x = 0; x < world.width; ++x) { for (y = 0; y < world.height; ++y) { if (aref(map, x, y) == MAXALT/2) { z = 0; for (p = 0; p < numpeaks; ++p) { dz = ((peakz[p] * cyldist(x, y, farx[p], fary[p])) / (cyldist(peakx[p], peaky[p], farx[p], fary[p])+1)); dz -= (peakz[p] * scale) / world.width; z += peakf[p] * dz; } z /= numpeaks; aadd(map, x, y, z + random(var/4)); } } } limit_map(map, MAXALT-1, 0); } /* Returns shortest distance around the world (can be either direction). */ cyldist(x1, y1, x2, y2) int x1, y1, x2, y2; { int dist = distance(x1, y1, x2, y2), dist2; if ((dist2 = distance(x1+world.width, y1, x2, y2)) < dist) { dist = dist2; } else if ((dist2 = distance(x1, y1, x2+world.width, y2)) < dist) { dist = dist2; } return dist; } /* Exponent with *small* integer power. (used with Bezier curves) */ float expt(f, i) float f; int i; { float rslt = 1.0; while (i-- > 0) rslt = rslt * f; return rslt; } /* Combination of n objects taken i at a time. (used for Bezier) */ comb(n, i) int n, i; { if (i <= 0) return 1; else if (n <= 0) return 0; else return (comb(n-1, i) + comb(n-1,i-1)); } /* Put in random Bezier mountain chains. Number and size are scaled to */ /* the size of the map. To prevent criss-crossing from creating very */ /* high spots, just set the aux map to values, and only add in later. */ add_curves(map, roughness) int *map, roughness; { int i, j, n, x, y, flag, curves, mindim, var; float u, f, cpx[4], cpy[4], px[500], py[500]; curves = (world.width * world.height * roughness) / 50000; if (Debug) printf("Throwing curves (%d)...\n", curves); for (x = 0; x < world.width; ++x) { for (y = 0; y < world.height; ++y) { aset(aux, x, y, 0); } } mindim = min(500, min(world.width, world.height)); var = (MAXALT/8 * roughness) / 100; for (n = 0; n < curves; ++n) { cpx[0] = 1.0 * (random(world.width-2)+1); cpy[0] = 1.0 * (random(world.height-2)+1); for (j = 1; j < 4; ++j) { cpx[j] = cpx[0] + (random(mindim) - mindim/2); if (cpx[j] > world.width-1) cpx[j] = world.width-1; if (cpx[j] < 0) cpx[j] = 0; cpy[j] = cpy[0] + (random(mindim) - mindim/2); if (cpy[j] > world.height-1) cpy[j] = world.height-1; if (cpy[j] < 0) cpy[j] = 0; } for (i = 0; i < mindim; ++i) { u = (1.0 * i) / mindim; px[i] = py[i] = 0.0; for (j = 0; j < 4; ++j) { f = comb(3,j)*expt(u,j)*expt(1-u,3-j); px[i] += cpx[j]*f; py[i] += cpy[j]*f; } } flag = random(3) ? 1 : -1; for (i = 0; i < mindim; ++i) { x = (int) px[i]; y = (int) py[i]; aset(aux, x, y, flag * (MAXALT/8 + var)); } } add_maps(map, aux, map); limit_map(map, MAXALT-1, 0); } /* Ensure that map values stay within given range. */ limit_map(map, hi, lo) int *map, hi, lo; { int x, y, m; for (x = 0; x < world.width; ++x) { for (y = 0; y < world.height; ++y) { m = aref(map, x, y); aset(map, x, y, max(lo, min(m, hi))); } } } /* Form the point-wise sum of two maps into a third one. */ add_maps(m1, m2, rslt) int *m1, *m2, *rslt; { int x, y; for (x = 0; x < world.width; ++x) { for (y = 0; y < world.height; ++y) { aset(rslt, x, y, aref(m1, x, y) + aref(m2, x, y)); } } } /* Average out things to keep peaks from getting too sharp. */ /* The array "aux" is the buffer for this operation. */ /* The edges of the map remain unaltered. (dubious) */ smooth_map(map) int *map; { int x, y, nx, px, sum; if (Debug) printf("Smoothing...\n"); for (x = 0; x < world.width; ++x) { nx = wrap(x+1); px = wrap(x-1); for (y = 1; y < world.height-1; ++y) { sum = aref(map, x, y); sum += aref(map, x, y+1); sum += aref(map, nx, y); sum += aref(map, nx, y-1); sum += aref(map, x, y-1); sum += aref(map, px, y); sum += aref(map, px, y+1); aset(aux, x, y, sum / 7); } } for (x = 0; x < world.width; ++x) { for (y = 1; y < world.height-1; ++y) { aset(map, x, y, aref(aux, x, y)); } } } /* Terrain types are specified in terms of percentage cover on a map, so */ /* for instance the Earth is 70% sea. Since each of several types will have */ /* its own percentages (both for elevation and moisture), the simplest thing */ /* to do is to calculate the percentile for each elevation and moisture */ /* level, and save them all away. This would be a one-liner in APL... */ /* Percentile computation is inefficient, should be done incrementally */ /* somehow instead of with * and / */ percentile(map, percentiles) int *map, *percentiles; { int i, x, y, total; if (Debug) printf("Computing percentiles...\n"); for (i = 0; i < MAXALT; ++i) { histo[i] = 0; percentiles[i] = 0; } /* Make the basic histogram, but don't count the edges */ for (x = 0; x < world.width; ++x) { for (y = 1; y < world.height-1; ++y) { ++histo[aref(map, x, y)]; } } /* Integrate over the histogram */ for (i = 1; i < MAXALT; ++i) histo[i] += histo[i-1]; /* Total here should actually be the same as number of cells in the map */ total = histo[MAXALT-1]; /* Compute the percentile position */ for (i = 0; i < MAXALT; ++i) percentiles[i] = (100 * histo[i]) / total; } /* Final creation and output of the map. */ compose_map() { int x, y; if (Debug) printf("Assigning terrain types...\n"); allocate_map(); for (y = 0; y < world.height; ++y) { for (x = 0; x < world.width; ++x) { set_terrain_at(x, y, terrain(x, y)); set_unit_at(x, y, NULL); } } /* Add a border if specified */ if (period.edgeterrain >= 0) { for (x = 0; x < world.width; ++x) { set_terrain_at(x, 0, period.edgeterrain); set_terrain_at(x, world.height-1, period.edgeterrain); } } } /* Do final output of terrain types. This is basically a process of */ /* checking the percentile limits on each type against what is actually */ /* there. (could and maybe should be more efficient) */ /* Error checking important for period designers... */ terrain(x, y) int x, y; { int t, a = aref(relief, x, y), w = aref(water, x, y); for_all_terrain_types(t) { if (between(ttypes[t].minalt, alts[a], ttypes[t].maxalt) && between(ttypes[t].minwet, wets[w], ttypes[t].maxwet)) { return t; } } printf("Unknown terrain in percentiles alt %d, wet %d.\n", alts[a], wets[w]); /* Not great, but valid if nothing else */ return (max(0, period.defaultterrain)); }