From 986dfa6cbf15f1ba2f536df56fba3b4a8678af07 Mon Sep 17 00:00:00 2001 From: mcmlxxix <> Date: Fri, 24 Aug 2012 22:50:52 +0000 Subject: [PATCH] new Map object (no more TerrainGenerator object). self-normalizing, self-range finding, self-profiling, self-impregnating --- exec/load/mapgenerator.js | 430 +++++++++++++++++++++++++++----------- 1 file changed, 305 insertions(+), 125 deletions(-) diff --git a/exec/load/mapgenerator.js b/exec/load/mapgenerator.js index 3b8273a35b..4564adafe0 100644 --- a/exec/load/mapgenerator.js +++ b/exec/load/mapgenerator.js @@ -5,52 +5,58 @@ USAGE: -var generator = new TerrainGenerator(); -var map = generator.generate( - <width>, - <height>, - <hills> -); +var map = new Map(width,height); width = the width 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: -TerrainGenerator.base = the minimum elevation of the map -TerrainGenerator.max_radius = the largest any single hill can be -TerrainGenerator.min_radius = the smallest any single hill can be -TerrainGenerator.peak = the scale of the map (max elevation relative to base) +Map.base = the minimum elevation of the map +Map.peak = the scale of the map (max elevation relative to base) +Map.maxRadius = the largest any single hill can be +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 - (tends to cluster terrain in the center of the map) -TerrainGenerator.lake_mode = default "poke" direction is downward instead of upward -TerrainGenerator.border_mode = make a single line half-step border around "poke" +Map.island = [true|false] cluster features towards the center of the map +Map.lake = [true|false] invert feature changes (lower elevation instead of increase elevation) +Map.border = [true|false] add a ring around the edge of a feature METHODS: -TerrainGenerator.generate(width,height,hills) ---must supply width and height ---hills determines whether the generator will generate randomized terrain for you ---if hills is not specified, a "blank slate" will be returned, which - can be manipulated via poke() and normalize() +Map.xSection(y,width,height) +--y is the vertical 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.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) ---must supply a map (2D integer array) +Map.poke(xoff,yoff,direction) --xoff, yoff are the position on the map to change 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) ---must supply a map (2D integer array) +Map.normalize(base,peak) --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: -var generator = new TerrainGenerator(); -var map = generator.generate(20,10,10); +var m = new Map(20,10); +m.randomize(0,10,100); 23333344678963322111 33344444677763332111 @@ -69,66 +75,258 @@ Math.sq = function(num) { return num*num; } -function TerrainGenerator() { +function Map(width,height) { - /* defaults */ - this.base = 0; - this.peak = 9; - this.min_radius=15; - this.max_radius=20; + /* protected map properties */ + var properties = { + min:undefined, + max:undefined, + mean:undefined, + range:undefined, + width:0, + height:0 + }; - this.border_mode=false; - this.island_mode=false; - this.lake_mode=false; + /* map generation modes */ + var modes = { + 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 */ - this.generate = function(width,height,hills) { + this.randomize = function(base,peak,hills) { - if(width == undefined || height == undefined) - throw("Terrain.generate() requires, at minimum, width and height arguments"); - - /* populate matrix with base elevation values */ - var map = getMap(width,height,this.base); + if(!hills) + hills = settings.hills; + + /* generate specified number of "hills" */ + for(var h=0;h<hills;h++) + this.poke(); - if(hills > 0) { - /* generate specified number of "hills" */ - for(var h=0;h<hills;h++) - map = this.poke(map); + /* normalize elevation values to fit between base elevation and scale */ + this.normalize(base,peak); + + 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 */ - map = this.normalize(map); + for(var x=0;x<this.length;x++) { + 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 from an optionally specified x,y position in a given direction */ - this.poke = function(map,xoff,yoff,radius,direction) { - /* make a copy of the original map */ - var copy = copyMap(map); - + this.poke = function(xoff,yoff,radius,direction) { + /* pick a hill radius between min and max */ 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 */ if(yoff == undefined) { - if(this.island_mode) - xoff = random(copy.length); + if(settings.mode&modes.island) + xoff = random(this.length); else - xoff = random(copy.length+(2*radius))-radius; + xoff = random(this.length+(2*radius))-radius; } if(yoff == undefined) { - if(this.island_mode) - yoff = random(copy[0].length); + if(settings.mode&modes.island) + yoff = random(this[0].length); else - yoff = random(copy[0].length+(2*radius))-radius; + yoff = random(this[0].length+(2*radius))-radius; } /* default elevation change */ if(direction == undefined) { - if(this.lake_mode) + if(settings.mode&modes.lake) direction = -1; else direction = 1; @@ -138,80 +336,62 @@ function TerrainGenerator() { var halfRow=Math.round(Math.sqrt(Math.sq(radius)-Math.sq(y))); for(var x=-halfRow;x<halfRow;x++) { //var h = Math.sq(radius) - (Math.sq(x) + Math.sq(y)); - if(copy[xoff+x] && copy[xoff+x][yoff+y] !== undefined) - copy[xoff+x][yoff+y] += direction; - if(this.border_mode) { - if(copy[xoff-x] && copy[xoff-x][yoff+y] !== undefined) - copy[xoff-x][yoff+y] += direction; + if(this[xoff+x] && this[xoff+x][yoff+y] !== undefined) + this[xoff+x][yoff+y] += direction; + if(settings.mode&modes.border) { + if(this[xoff-x] && this[xoff-x][yoff+y] !== undefined) + 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 */ - var copy = copyMap(map); + /* private range finding function */ + function setRange(map) { var min=undefined; var max=undefined; - - if(base == undefined) - base = this.base; - if(peak == undefined) - peak = this.peak; + var mean=undefined; + var total=0; /* iterate map and find the min and max values */ - for(var x=0;x<copy.length;x++) { - for(var y=0;y<copy[x].length;y++) { - if(min==undefined || copy[x][y] < min) - min = copy[x][y]; - if(max==undefined || copy[x][y] > max) - max = copy[x][y]; + for(var x=0;x<map.length;x++) { + for(var y=0;y<map[x].length;y++) { + if(min==undefined || map[x][y] < min) + min = map[x][y]; + if(max==undefined || map[x][y] > max) + max = map[x][y]; + total+=map[x][y]; } - } + } - /* iterate map again and adjust values to fit scale */ - var range = Math.abs(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; - } - } + mean = total / (map[0].length * map.length); + range = max - min; - return copy; - } - - /* create a 2d array of given dimensions */ - 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; + properties.min = min; + properties.max = max; + properties.mean = mean; + properties.range = range; } -} \ No newline at end of file + + /* initialize map object */ + this.init(width,height); +} +Map.prototype = new Array(); -- GitLab