1
0
forked from 0ad/0ad
0ad/binaries/data/mods/public/maps/random/rmgen/painter.js
2012-07-02 08:07:59 +00:00

438 lines
9.1 KiB
JavaScript

// Constants for using SmoothElevationPainter
const ELEVATION_SET = 0;
const ELEVATION_MODIFY = 1;
/////////////////////////////////////////////////////////////////////////////
// ElevationPainter
//
// Class for painting elevation over an area
//
// elevation: Target elevation/height to be painted
//
/////////////////////////////////////////////////////////////////////////////
function ElevationPainter(elevation)
{
this.elevation = elevation;
this.DX = [0, 1, 1, 0];
this.DZ = [0, 0, 1, 1];
}
ElevationPainter.prototype.paint = function(area)
{
var length = area.points.length;
var elevation = this.elevation;
for (var i=0; i < length; i++)
{
var pt = area.points[i];
for (var j=0; j < 4; j++)
{
if (g_Map.validT(pt.x + this.DX[j],pt.z + this.DZ[j]))
g_Map.height[pt.x + this.DX[j]][pt.z + this.DZ[j]] = elevation;
}
}
};
/////////////////////////////////////////////////////////////////////////////
// LayeredPainter
//
// Class for painting multiple layered terrains over an area
//
// terrainArray: Array of terrain painter objects
// widths: Array of widths for each layer
//
/////////////////////////////////////////////////////////////////////////////
function LayeredPainter(terrainArray, widths)
{
if (!(terrainArray instanceof Array))
{
throw("LayeredPainter: terrains must be an array!");
}
this.terrains = [];
for (var i = 0; i < terrainArray.length; ++i)
{
this.terrains.push(createTerrain(terrainArray[i]));
}
this.widths = widths;
}
LayeredPainter.prototype.paint = function(area)
{
var size = getMapSize();
var saw = new Array(size);
var dist = new Array(size);
// init typed arrays
for (var i = 0; i < size; ++i)
{
saw[i] = new Uint8Array(size); // bool / uint8
dist[i] = new Uint16Array(size); // uint16
}
// Point queue (implemented with array)
var pointQ = [];
// push edge points
var pts = area.points;
var length = pts.length;
var areaID = area.getID();
for (var i=0; i < length; i++)
{
var x = pts[i].x;
var z = pts[i].z;
for (var dx=-1; dx <= 1; dx++)
{
var nx = x+dx;
for (var dz=-1; dz <= 1; dz++)
{
var nz = z+dz;
if (g_Map.validT(nx, nz) && g_Map.area[nx][nz] != areaID && !saw[nx][nz])
{
saw[nx][nz] = 1;
dist[nx][nz] = 0;
pointQ.push(new PointXZ(nx, nz));
}
}
}
}
// do BFS inwards to find distances to edge
while (pointQ.length)
{
var pt = pointQ.shift(); // Pop queue
var px = pt.x;
var pz = pt.z;
var d = dist[px][pz];
// paint if in area
if (g_Map.area[px][pz] == areaID)
{
var w=0;
var i=0;
for (; i < this.widths.length; i++)
{
w += this.widths[i];
if (w >= d)
{
break;
}
}
this.terrains[i].place(px, pz);
}
// enqueue neighbours
for (var dx=-1; dx<=1; dx++)
{
var nx = px+dx;
for (var dz=-1; dz<=1; dz++)
{
var nz = pz+dz;
if (g_Map.validT(nx, nz) && g_Map.area[nx][nz] == areaID && !saw[nx][nz])
{
saw[nx][nz] = 1;
dist[nx][nz] = d+1;
pointQ.push(new PointXZ(nx, nz));
}
}
}
}
};
/////////////////////////////////////////////////////////////////////////////
// MultiPainter
//
// Class for applying multiple painters over an area
//
// painters: Array of painter objects
//
/////////////////////////////////////////////////////////////////////////////
function MultiPainter(painters)
{
this.painters = painters;
}
MultiPainter.prototype.paint = function(area)
{
for (var i=0; i < this.painters.length; i++)
{
this.painters[i].paint(area);
}
};
/////////////////////////////////////////////////////////////////////////////
// SmoothElevationPainter
//
// Class for painting elevation smoothly over an area
//
// type: Type of elevation modification
// ELEVATION_MODIFY = relative
// ELEVATION_SET = absolute
// elevation: Target elevation/height of area
// blendRadius: How steep the elevation change is
//
/////////////////////////////////////////////////////////////////////////////
function SmoothElevationPainter(type, elevation, blendRadius)
{
this.type = type;
this.elevation = elevation;
this.blendRadius = blendRadius;
if (type != ELEVATION_SET && type != ELEVATION_MODIFY)
{
throw("SmoothElevationPainter: invalid type '"+type+"'");
}
}
SmoothElevationPainter.prototype.checkInArea = function(areaID, x, z)
{
// Check given tile and its neighbors
return (
(g_Map.validT(x, z) && g_Map.area[x][z] == areaID)
|| (g_Map.validT(x-1, z) && g_Map.area[x-1][z] == areaID)
|| (g_Map.validT(x, z-1) && g_Map.area[x][z-1] == areaID)
|| (g_Map.validT(x-1, z-1) && g_Map.area[x-1][z-1] == areaID)
);
};
SmoothElevationPainter.prototype.paint = function(area)
{
var pointQ = [];
var pts = area.points;
var heightPts = [];
var mapSize = getMapSize()+1;
var saw = new Array(mapSize);
var dist = new Array(mapSize);
var gotHeightPt = new Array(mapSize);
var newHeight = new Array(mapSize);
// init typed arrays
for (var i = 0; i < mapSize; ++i)
{
saw[i] = new Uint8Array(mapSize); // bool / uint8
dist[i] = new Uint16Array(mapSize); // uint16
gotHeightPt[i] = new Uint8Array(mapSize); // bool / uint8
newHeight[i] = new Float32Array(mapSize); // float32
}
var length = pts.length;
var areaID = area.getID();
// get a list of all points
for (var i=0; i < length; i++)
{
var x = pts[i].x;
var z = pts[i].z;
for (var dx=-1; dx <= 2; dx++)
{
var nx = x+dx;
for (var dz=-1; dz <= 2; dz++)
{
var nz = z+dz;
if (g_Map.validH(nx, nz) && !gotHeightPt[nx][nz])
{
gotHeightPt[nx][nz] = 1;
heightPts.push(new PointXZ(nx, nz));
newHeight[nx][nz] = g_Map.height[nx][nz];
}
}
}
}
// push edge points
for (var i=0; i < length; i++)
{
var x = pts[i].x;
var z = pts[i].z;
for (var dx=-1; dx <= 2; dx++)
{
var nx = x+dx;
for (var dz=-1; dz <= 2; dz++)
{
var nz = z+dz;
if (g_Map.validH(nx, nz) && !this.checkInArea(areaID, nx, nz) && !saw[nx][nz])
{
saw[nx][nz]= 1;
dist[nx][nz] = 0;
pointQ.push(new PointXZ(nx, nz));
}
}
}
}
// do BFS inwards to find distances to edge
while(pointQ.length)
{
var pt = pointQ.shift();
var px = pt.x;
var pz = pt.z;
var d = dist[px][pz];
// paint if in area
if (g_Map.validH(px, pz) && this.checkInArea(areaID, px, pz))
{
if (d <= this.blendRadius)
{
var a = (d-1) / this.blendRadius;
if (this.type == ELEVATION_SET)
{
newHeight[px][pz] = a*this.elevation + (1-a)*g_Map.height[px][pz];
}
else
{ // type == MODIFY
newHeight[px][pz] += a*this.elevation;
}
}
else
{ // also happens when blendRadius == 0
if (this.type == ELEVATION_SET)
{
newHeight[px][pz] = this.elevation;
}
else
{ // type == MODIFY
newHeight[px][pz] += this.elevation;
}
}
}
// enqueue neighbours
for (var dx=-1; dx <= 1; dx++)
{
var nx = px+dx;
for (var dz=-1; dz <= 1; dz++)
{
var nz = pz+dz;
if (g_Map.validH(nx, nz) && this.checkInArea(areaID, nx, nz) && !saw[nx][nz])
{
saw[nx][nz] = 1;
dist[nx][nz] = d+1;
pointQ.push(new PointXZ(nx, nz));
}
}
}
}
length = heightPts.length;
// smooth everything out
for (var i = 0; i < length; ++i)
{
var pt = heightPts[i];
var px = pt.x;
var pz = pt.z;
if (this.checkInArea(areaID, px, pz))
{
var sum = 8 * newHeight[px][pz];
var count = 8;
for (var dx=-1; dx <= 1; dx++)
{
var nx = px+dx;
for (var dz=-1; dz <= 1; dz++)
{
var nz = pz+dz;
if (g_Map.validH(nx, nz))
{
sum += newHeight[nx][nz];
count++;
}
}
}
g_Map.height[px][pz] = sum/count;
}
}
};
/////////////////////////////////////////////////////////////////////////////
// TerrainPainter
//
// Class for painting a terrain over an area
//
// terrain: Terrain placer object
//
/////////////////////////////////////////////////////////////////////////////
function TerrainPainter(terrain)
{
this.terrain = createTerrain(terrain);
}
TerrainPainter.prototype.paint = function(area)
{
var length = area.points.length;
for (var i=0; i < length; i++)
{
var pt = area.points[i];
this.terrain.place(pt.x, pt.z);
}
};
/////////////////////////////////////////////////////////////////////////////
// TileClassPainter
//
// Class for painting tileClasses over an area
//
// tileClass: TileClass object
//
/////////////////////////////////////////////////////////////////////////////
function TileClassPainter(tileClass)
{
this.tileClass = tileClass;
}
TileClassPainter.prototype.paint = function(area)
{
var length = area.points.length;
for (var i=0; i < length; i++)
{
var pt = area.points[i];
this.tileClass.add(pt.x, pt.z);
}
};
/////////////////////////////////////////////////////////////////////////////
// TileClassUnPainter
//
// Class for unpainting tileClasses over an area
//
// tileClass: TileClass object
//
/////////////////////////////////////////////////////////////////////////////
function TileClassUnPainter(tileClass)
{
this.tileClass = tileClass;
}
TileClassUnPainter.prototype.paint = function(area)
{
var length = area.points.length;
for (var i=0; i < length; i++)
{
var pt = area.points[i];
this.tileClass.remove(pt.x, pt.z);
}
};