Skip to content
Snippets Groups Projects
Commit 986dfa6c authored by mcmlxxix's avatar mcmlxxix
Browse files

new Map object (no more TerrainGenerator object).

self-normalizing, self-range finding, self-profiling, self-impregnating
parent bc16f576
No related branches found
No related tags found
No related merge requests found
...@@ -5,52 +5,58 @@ ...@@ -5,52 +5,58 @@
USAGE: USAGE:
var generator = new TerrainGenerator(); var map = new Map(width,height);
var map = generator.generate(
<width>,
<height>,
<hills>
);
width = the width of the map array width = the width of the map array
height = the height of the map array height = the height of the map array
hills = the number of hills to generate
PROPERTIES:
Map.min = the lowest elevation on the map
Map.max = the highest elevation on the map
Map.mean = the average elevation on the map
Map.range = the difference between the max and min elevations
SETTINGS: SETTINGS:
TerrainGenerator.base = the minimum elevation of the map Map.base = the minimum elevation of the map
TerrainGenerator.max_radius = the largest any single hill can be Map.peak = the scale of the map (max elevation relative to base)
TerrainGenerator.min_radius = the smallest any single hill can be Map.maxRadius = the largest any single hill can be
TerrainGenerator.peak = the scale of the map (max elevation relative to base) Map.minRadius = the smallest any single hill can be
Map.hills = number of times to "poke" the map (generate elevation features)
MODES:
TerrainGenerator.island_mode = force poke center-points to be within map boundaries Map.island = [true|false] cluster features towards the center of the map
(tends to cluster terrain in the center of the map) Map.lake = [true|false] invert feature changes (lower elevation instead of increase elevation)
TerrainGenerator.lake_mode = default "poke" direction is downward instead of upward Map.border = [true|false] add a ring around the edge of a feature
TerrainGenerator.border_mode = make a single line half-step border around "poke"
METHODS: METHODS:
TerrainGenerator.generate(width,height,hills) Map.xSection(y,width,height)
--must supply width and height --y is the vertical offset of the map slice to create (a side profile view of the map at a given line)
--hills determines whether the generator will generate randomized terrain for you --width and height are the dimensions of the matrix returned
--if hills is not specified, a "blank slate" will be returned, which
can be manipulated via poke() and normalize() Map.ySection(x,width,height)
--x is the horizontal offset of the map slice to create (a side profile view of the map at a given line)
--width and height are the dimensions of the matrix returned
Map.randomize(base,peak,hill)
--if arguments are not specified, Map will use default values
TerrainGenerator.poke(map,xoff,yoff,direction) Map.poke(xoff,yoff,direction)
--must supply a map (2D integer array)
--xoff, yoff are the position on the map to change elevation (optional) --xoff, yoff are the position on the map to change elevation (optional)
--direction is the direction in which to change the elevation (optional) --direction is the direction in which to change the elevation (optional)
--min_radius and max_radius affect the outcome of the "poke" NOTE: Map.minRadius and maxRadius affect the outcome of the "poke"
TerrainGenerator.normalize(map,base,scale) Map.normalize(base,peak)
--must supply a map (2D integer array)
--base is the lowest acceptable elevation value (optional) --base is the lowest acceptable elevation value (optional)
--scale is the highest acceptable elevation value (optional) --peak is the highest acceptable elevation value (optional)
EXAMPLE: EXAMPLE:
var generator = new TerrainGenerator(); var m = new Map(20,10);
var map = generator.generate(20,10,10); m.randomize(0,10,100);
23333344678963322111 23333344678963322111
33344444677763332111 33344444677763332111
...@@ -69,66 +75,258 @@ Math.sq = function(num) { ...@@ -69,66 +75,258 @@ Math.sq = function(num) {
return num*num; return num*num;
} }
function TerrainGenerator() { function Map(width,height) {
/* defaults */ /* protected map properties */
this.base = 0; var properties = {
this.peak = 9; min:undefined,
this.min_radius=15; max:undefined,
this.max_radius=20; mean:undefined,
range:undefined,
width:0,
height:0
};
this.border_mode=false; /* map generation modes */
this.island_mode=false; var modes = {
this.lake_mode=false; island:(1<<0),
lake:(1<<1),
border:(1<<2)
};
/* map generation settings */
var settings = {
minRadius:10,
maxRadius:20,
hills:100,
mode:0
};
this.__defineGetter__("width",function() {
return properties.width;
});
this.__defineGetter__("height",function() {
return properties.height;
});
this.__defineGetter__("min",function() {
if(isNaN(properties.min))
setRange(this);
return properties.min;
});
this.__defineGetter__("max",function() {
if(isNaN(properties.max))
setRange(this);
return properties.max;
});
this.__defineGetter__("mean",function() {
if(isNaN(properties.mean))
setRange(this);
return properties.mean;
});
this.__defineGetter__("range",function() {
if(isNaN(properties.range))
setRange(this);
return properties.range;
});
this.__defineGetter__("minRadius",function() {
return settings.minRadius;
});
this.__defineGetter__("maxRadius",function() {
return settings.maxRadius;
});
this.__defineSetter__("minRadius",function(r) {
if(isNaN(r) || r > settings.maxRadius)
return false;
settings.minRadius = r;
return true;
});
this.__defineSetter__("maxRadius",function(r) {
if(isNaN(r) || r < settings.minRadius)
return false;
settings.maxRadius = r;
return true;
});
this.__defineGetter__("island",function() {
return properties.mode&modes.island;
});
this.__defineSetter__("island",function(bool) {
if(bool) {
if(properties.mode&modes.island)
return false;
properties.mode|=modes.island;
}
else {
if(!(properties.mode&modes.island))
return false;
properties.mode&=~modes.island;
}
});
this.__defineGetter__("lake",function() {
return properties.mode&modes.lake;
});
this.__defineSetter__("lake",function(bool) {
if(bool) {
if(properties.mode&modes.lake)
return false;
properties.mode|=modes.lake;
}
else {
if(!(properties.mode&modes.lake))
return false;
properties.mode&=~modes.lake;
}
});
this.__defineGetter__("border",function() {
return properties.mode&modes.lake;
});
this.__defineSetter__("border",function(bool) {
if(bool) {
if(properties.mode&modes.border)
return false;
properties.mode|=modes.border;
}
else {
if(!(properties.mode&modes.border))
return false;
properties.mode&=~modes.border;
}
});
this.__defineGetter__("hills",function() {
return settings.hills;
});
this.__defineSetter__("hills",function(hills) {
if(isNaN(hills))
return false;
settings.hills = hills;
return true;
});
/* return a cross section (horizontal) */
this.xSection = function(y,width,height) {
var section = [];
for(var x=0;x<this.length;x++) {
section[x]=[];
var a = this[x][y];
var b = a-this.min;
var c = b/this.range;
var d = c*(height-2);
log(format("a:%f,b:%f,c:%f,d:%f",a,b,c,d));
var i=0;
for(;i<t;i++)
section[x].unshift(1);
for(;i<height;i++)
section[x].unshift(0);
}
return section;
}
/* return a cross section (vertical) */
this.ySection = function(x,width,height) {
var section = [];
for(var y=0;y<this[x].length;y++) {
section[y]=[];
var a = this[x][y];
if(a == undefined)
throw("Invalid elevation data '" + a + "' at " + x + ":" + y);
var b = a-this.min;
var c = b/this.range;
var d = c*(height-2);
log(format("a:%f,b:%f,c:%f,d:%f",a,b,c,d));
var i=0;
for(;i<t;i++)
section[y].unshift(1);
for(;i<height;i++)
section[y].unshift(0);
}
return section.reverse();
}
/* generate a land object of specified width, height, base elevation */ /* generate a land object of specified width, height, base elevation */
this.generate = function(width,height,hills) { this.randomize = function(base,peak,hills) {
if(width == undefined || height == undefined) if(!hills)
throw("Terrain.generate() requires, at minimum, width and height arguments"); hills = settings.hills;
/* populate matrix with base elevation values */ /* generate specified number of "hills" */
var map = getMap(width,height,this.base); for(var h=0;h<hills;h++)
this.poke();
if(hills > 0) { /* normalize elevation values to fit between base elevation and scale */
/* generate specified number of "hills" */ this.normalize(base,peak);
for(var h=0;h<hills;h++)
map = this.poke(map); return true;
}
/* wipe map clear */
this.clear = function() {
for(var x=0;x<properties.width;x++) {
for(var y=0;y<properties.height;y++) {
this[x][y]=0;
}
}
}
/* normalize elevation data between a base and peak value */
this.normalize = function(base,peak) {
if(base == undefined)
base = 0;
if(peak == undefined)
peak = 1;
/* iterate map again and adjust values to fit scale */
if(this.range == 0)
return false;
/* normalize elevation values to fit between base elevation and scale */ for(var x=0;x<this.length;x++) {
map = this.normalize(map); for(var y=0;y<this[x].length;y++) {
var a = this[x][y];
if(a == undefined)
throw("Invalid elevation data '" + a + "' at " + x + ":" + y);
var b = a-this.min;
var c = b/this.range;
var d = c * peak;
var e = d + base;
this[x][y] = e;
}
} }
return map; setRange(this);
return true;
} }
/* generate a randomized feature on a map starting /* generate a randomized feature on a map starting
from an optionally specified x,y position in a given direction */ from an optionally specified x,y position in a given direction */
this.poke = function(map,xoff,yoff,radius,direction) { this.poke = function(xoff,yoff,radius,direction) {
/* make a copy of the original map */
var copy = copyMap(map);
/* pick a hill radius between min and max */ /* pick a hill radius between min and max */
if(radius == undefined) if(radius == undefined)
radius = random(this.max_radius-this.min_radius)+this.min_radius; radius = random(settings.maxRadius-settings.minRadius)+settings.minRadius;
/* pick a random starting position */ /* pick a random starting position */
if(yoff == undefined) { if(yoff == undefined) {
if(this.island_mode) if(settings.mode&modes.island)
xoff = random(copy.length); xoff = random(this.length);
else else
xoff = random(copy.length+(2*radius))-radius; xoff = random(this.length+(2*radius))-radius;
} }
if(yoff == undefined) { if(yoff == undefined) {
if(this.island_mode) if(settings.mode&modes.island)
yoff = random(copy[0].length); yoff = random(this[0].length);
else else
yoff = random(copy[0].length+(2*radius))-radius; yoff = random(this[0].length+(2*radius))-radius;
} }
/* default elevation change */ /* default elevation change */
if(direction == undefined) { if(direction == undefined) {
if(this.lake_mode) if(settings.mode&modes.lake)
direction = -1; direction = -1;
else else
direction = 1; direction = 1;
...@@ -138,80 +336,62 @@ function TerrainGenerator() { ...@@ -138,80 +336,62 @@ function TerrainGenerator() {
var halfRow=Math.round(Math.sqrt(Math.sq(radius)-Math.sq(y))); var halfRow=Math.round(Math.sqrt(Math.sq(radius)-Math.sq(y)));
for(var x=-halfRow;x<halfRow;x++) { for(var x=-halfRow;x<halfRow;x++) {
//var h = Math.sq(radius) - (Math.sq(x) + Math.sq(y)); //var h = Math.sq(radius) - (Math.sq(x) + Math.sq(y));
if(copy[xoff+x] && copy[xoff+x][yoff+y] !== undefined) if(this[xoff+x] && this[xoff+x][yoff+y] !== undefined)
copy[xoff+x][yoff+y] += direction; this[xoff+x][yoff+y] += direction;
if(this.border_mode) { if(settings.mode&modes.border) {
if(copy[xoff-x] && copy[xoff-x][yoff+y] !== undefined) if(this[xoff-x] && this[xoff-x][yoff+y] !== undefined)
copy[xoff-x][yoff+y] += direction; this[xoff-x][yoff+y] += direction;
} }
} }
} }
return true;
}
/* initialize map */
this.init = function(width,height) {
if(isNaN(width) || isNaN(height))
throw("Map() width and height arguments must be integers > 0");
if(this.length > 0)
this.splice(0,this.length);
for(var x=0;x<width;x++) {
this.push([]);
for(var y=0;y<height;y++) {
this[x][y]=0;
}
}
return copy; properties.width = width;
properties.height = height;
} }
/* normalize land within a given range */
this.normalize = function(map,base,peak) {
/* make a copy of the original map */ /* private range finding function */
var copy = copyMap(map); function setRange(map) {
var min=undefined; var min=undefined;
var max=undefined; var max=undefined;
var mean=undefined;
if(base == undefined) var total=0;
base = this.base;
if(peak == undefined)
peak = this.peak;
/* iterate map and find the min and max values */ /* iterate map and find the min and max values */
for(var x=0;x<copy.length;x++) { for(var x=0;x<map.length;x++) {
for(var y=0;y<copy[x].length;y++) { for(var y=0;y<map[x].length;y++) {
if(min==undefined || copy[x][y] < min) if(min==undefined || map[x][y] < min)
min = copy[x][y]; min = map[x][y];
if(max==undefined || copy[x][y] > max) if(max==undefined || map[x][y] > max)
max = copy[x][y]; max = map[x][y];
total+=map[x][y];
} }
} }
/* iterate map again and adjust values to fit scale */ mean = total / (map[0].length * map.length);
var range = Math.abs(max - min); range = max - min;
if(range == 0)
return copy;
for(var x=0;x<copy.length;x++) {
for(var y=0;y<copy[x].length;y++) {
var a = copy[x][y]-min;
var b = a/range;
var c = b * peak;
var d = c + base;
if(d < base)
log("invalid elevation: " + d);
copy[x][y] = d;
}
}
return copy; properties.min = min;
} properties.max = max;
properties.mean = mean;
/* create a 2d array of given dimensions */ properties.range = range;
function getMap(width,height,base) {
var array = [];
for(var x=0;x<width;x++) {
array[x] = [];
for(var y=0;y<height;y++)
array[x][y] = base;
}
return array;
}
/* copy a map */
function copyMap(map) {
var copy = [];
for(var x=0;x<map.length;x++) {
copy[x] = [];
for(var y=0;y<map[x].length;y++)
copy[x][y] = map[x][y];
}
return copy;
} }
}
\ No newline at end of file /* initialize map object */
this.init(width,height);
}
Map.prototype = new Array();
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment