1
0
forked from 0ad/0ad

# Updates to random map generator.

- Added a direct addToClass/removeFromClass API for use in noise-based
maps.
- Raised the sea level in the game world so water can be deeper, and set
it to 0 in script coordinates.
- Moved some class-related utility functions to rmlibrary.js.
- Modified sample maps in light of these changes.
- Committed noise class which I forgot last time.

This was SVN commit r4659.
This commit is contained in:
Matei 2006-11-27 00:20:58 +00:00
parent aa405024f2
commit 60befbd1cf
10 changed files with 328 additions and 105 deletions

View File

@ -31,24 +31,10 @@ const oRockMedium = "geology/stone_granite_med.xml";
const oBushMedium = "props/flora/bush_tempe_me.xml";
const oBushSmall = "props/flora/bush_tempe_sm.xml";
// some utility functions to save typing
function paintClass(cl) {
return new TileClassPainter(cl);
}
function avoidClasses(/*class1, dist1, class2, dist2, etc*/) {
var ar = new Array(arguments.length/2);
for(var i=0; i<arguments.length/2; i++) {
ar[i] = new AvoidTileClassConstraint(arguments[2*i], arguments[2*i+1]);
}
return ar;
}
// initialize map
println("Initializing map...");
init(SIZE, tGrass, 8);
init(SIZE, tGrass, 3);
// create tile classes
@ -81,7 +67,7 @@ for(i=1; i<=NUM_PLAYERS; i++) {
// some constants
radius = 17;
cliffRadius = 2;
elevation = 20;
elevation = 15;
// get the x and y in tiles
fx = fractionToTiles(playerX[i]);
@ -193,7 +179,7 @@ terrainPainter = new LayeredPainter(
[1,1], // widths
[tShoreBlend, tShore, tWater] // terrains
);
elevationPainter = new SmoothElevationPainter(ELEVATION_SET, -2, 3);
elevationPainter = new SmoothElevationPainter(ELEVATION_SET, -7, 3);
createAreas(placer, [terrainPainter, elevationPainter, paintClass(clWater)],
avoidClasses(clPlayer, 2, clWater, 20),
round(1.3 * NUM_PLAYERS)
@ -215,7 +201,7 @@ terrainPainter = new LayeredPainter(
[3], // widths
[tCliff, [tGrass,tGrass,tGrassDirt75]] // terrains
);
elevationPainter = new SmoothElevationPainter(ELEVATION_SET, 17, 2);
elevationPainter = new SmoothElevationPainter(ELEVATION_SET, 12, 2);
createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill)],
avoidClasses(clPlayer, 2, clWater, 5, clHill, 15),
2 * NUM_PLAYERS

View File

@ -1,7 +1,30 @@
const SIZE = 160;
const SEA_LEVEL = 5;
init(SIZE, "ocean_medit_rock_shallow", 0);
const sand = "beach_medit_dry";
const grass1 = "grass_temperate_a";
const grass2 = "grass_mediterranean_green_flowers";
const forestFloor = "forrestfloor";
const dirt1 = "grass_sand_75";
const dirt2 = "grass_sand_50";
const dirt3 = "dirt_brown_e";
const cliffBase = "cliff base a";
const cliffBeach = "beech_cliff_a_75";
const cliff = "cliff_face3";
const oTree = "flora_tree_oak";
const oGrass = "props/flora/grass_soft_small.xml"
const oMine = "geology_stone_light";
// Initialize
init(SIZE, grass1, 0);
// Create classes
clImpassable = createTileClass();
clRock = createTileClass();
// Paint elevation
noise0 = new Noise2D(4 * SIZE/128.0);
noise1 = new Noise2D(8 * SIZE/128.0);
@ -9,8 +32,6 @@ noise2 = new Noise2D(11 * SIZE/128.0);
noise3 = new Noise2D(30 * SIZE/128.0);
noise4 = new Noise2D(60 * SIZE/128.0);
// Paint elevation
for(ix=0; ix<SIZE+1; ix++) {
for(iy=0; iy<SIZE+1; iy++) {
x = ix / (SIZE + 1.0);
@ -23,35 +44,21 @@ for(ix=0; ix<SIZE+1; ix++) {
if(n < T) {
// Tile is underwater - just scale the height down a bit
h = Math.max(-60 * (T-n)/T, -30);
h = Math.max(-50 * (T-n)/T, -8);
}
else {
// Tile is above water - add some land noise depending on how far
// we are from the shoreline
u = 25*noise1.eval(x, y) + 12*noise2.eval(x,y) + 8 * noise3.eval(x,y) - 11;
// Tile is above water - add some land noise depending on how far we are from the shoreline
u = 27*noise1.eval(x, y) + 14*noise2.eval(x,y) + 9 * noise3.eval(x,y) - 14;
h = 8*(n-T) + Math.max(0, lerp(0, u, Math.min(.1, n-T)*10));
h += 0.4*noise4.eval(x, y);
}
h += SEA_LEVEL;
setHeight(ix, iy, h);
}
}
// Paint terrains
sand = "beach_medit_dry";
grass1 = "grass_temperate_a";
grass2 = "grass_mediterranean_green_flowers";
forestFloor = "forrestfloor";
dirt1 = "grass_sand_75";
dirt2 = "grass_sand_50";
dirt3 = "dirt_brown_e";
cliffBase = "cliff base a";
cliffBeach = "beech_cliff_a_75";
cliff = "cliff_face3";
for(ix=0; ix<SIZE; ix++) {
for(iy=0; iy<SIZE; iy++) {
h00 = getHeight(ix, iy);
@ -60,20 +67,21 @@ for(ix=0; ix<SIZE; ix++) {
h11 = getHeight(ix+1, iy+1);
maxH = Math.max(h00, h01, h10, h11);
minH = Math.min(h00, h01, h10, h11);
if(maxH <= SEA_LEVEL) {
if(maxH <= 0) {
setTexture(ix, iy, sand);
addToClass(ix, iy, clImpassable);
}
else if(maxH - minH > 3.2 && minH >= SEA_LEVEL) {
else if(maxH - minH > 3.2) {
setTexture(ix, iy, cliff);
}
else if(maxH - minH > 2.7 && minH < SEA_LEVEL) {
setTexture(ix, iy, cliffBeach);
addToClass(ix, iy, clImpassable);
}
else if(maxH - minH > 2.7) {
setTexture(ix, iy, cliffBase);
addToClass(ix, iy, clImpassable);
}
else if(minH <= SEA_LEVEL) {
else if(minH <= 0) {
setTexture(ix, iy, sand);
addToClass(ix, iy, clImpassable);
}
else {
setTexture(ix, iy, grass1);
@ -81,12 +89,10 @@ for(ix=0; ix<SIZE; ix++) {
}
}
oTree = "flora_tree_oak";
oGrass = "props/flora/grass_soft_small.xml"
// Paint forest and dirt
forestNoise1 = new Noise2D(20 * SIZE/128.0);
forestNoise2 = new Noise2D(40 * SIZE/128.0);
dirtNoise = new Noise2D(80 * SIZE/128.0);
for(ix=0; ix<SIZE; ix++) {
@ -99,7 +105,7 @@ for(ix=0; ix<SIZE; ix++) {
h11 = getHeight(ix+1, iy+1);
maxH = Math.max(h00, h01, h10, h11);
minH = Math.min(h00, h01, h10, h11);
if(maxH - minH < 1.7 && minH > SEA_LEVEL) {
if(maxH - minH < 1.7 && minH > 0) {
fn = (forestNoise1.eval(x,y) + .5*forestNoise1.eval(x,y)) / 1.5;
if(minH > .5 && fn < .38 && dirtNoise.eval(x,y) > .55) {
@ -113,49 +119,19 @@ for(ix=0; ix<SIZE; ix++) {
if(fn > .6 && randFloat() < (.3 + .7 * Math.min(fn-.6, .1) / .1) ) {
placeObject(oTree, 0, ix+.4+.2*randFloat(), iy+.4+.2*randFloat(), randFloat()*2*Math.PI);
addToClass(ix, iy, clImpassable);
if(randFloat() < .7) {
setTexture(ix, iy, forestFloor);
}
}
else if(getTexture(ix, iy)==grass1 && maxH-minH < 1 && randFloat() < .13) {
placeObject(oGrass, 0, ix+.3+.4*randFloat(), iy+.3+.4*randFloat(), randFloat()*2*Math.PI);
}
else if(getTexture(ix, iy)==grass1 && randFloat() < .03) {
placeObject(oTree, 0, ix+.3+.4*randFloat(), iy+.3+.4*randFloat(), randFloat()*2*Math.PI);
}
/*else if(getTerrain(ix, iy)==grass && fn > .5 && randFloat() < .04) {
placeBushClump(ix+.5, iy+.5);
}
else if(randFloat() < .04 && (maxH < .5 || randFloat() < .2)) {
placeRockClump(ix+.5, iy+.5);
}
else if(getTerrain(ix, iy)==grass && maxH-minH < .4 && randFloat() < .1) {
addSprite(new Sprite(makeGrass(),
ix+.3+.4*randFloat(), iy+.3+.4*randFloat(), STATIC));
}
else if(randFloat() < .015) {
addSprite(new Sprite(makeSmallRock(),
ix+randFloat(), iy+randFloat(), STATIC));
}
else if(getTerrain(ix, iy)==grass && randFloat() < .03) {
var t = new Sprite(makeOak(),
ix+.3+.4*randFloat(), iy+.3+4*randFloat(), STATIC);
t.maxY = 4;
t.collidable = true;
addSprite(t);
}*/
}
/*if(maxH < .5 && minH < -0.3 && minH > -2 && randFloat() < .007) {
var l = new Sprite(waterLog, ix+randFloat(), iy+randFloat(), STATIC);
l.floating = true;
addSprite(l);
}
else if(maxH < 0 && minH > -3 && randFloat() < .04) {
var l = new Sprite(lillies, ix+randFloat(), iy+randFloat(), STATIC);
l.floating = true;
addSprite(l);
}*/
}
}
println("Creating mines...");
group = new SimpleGroup([new SimpleObject(oMine, 3,4, 0,2)], true, clRock);
createObjectGroups(group, 0,
new AvoidTileClassConstraint(clImpassable, 2, clRock, 13),
12, 100
);

View File

@ -71,6 +71,10 @@ function round(x) {
return Math.round(x);
}
function lerp(a, b, t) {
return a + (b-a) * t;
}
function println(x) {
print(x);
print("\n");
@ -251,6 +255,18 @@ function SimpleGroup(elements, avoidSelf, tileClass, x, y) {
this.y = x!=undefined ? y : -1;
}
function lerp(a, b, t) {
return a + (b-a) * t;
}
// Utility functions for classes
// Create a painter for the given class
function paintClass(cl) {
return new TileClassPainter(cl);
}
// Create an avoid constraint for the given classes by the given distances
function avoidClasses(/*class1, dist1, class2, dist2, etc*/) {
var ar = new Array(arguments.length/2);
for(var i=0; i<arguments.length/2; i++) {
ar[i] = new AvoidTileClassConstraint(arguments[2*i], arguments[2*i+1]);
}
return ar;
}

View File

@ -29,6 +29,8 @@ JSFunctionSpec globalFunctions[] = {
{"createArea", createArea, 3},
{"createObjectGroup", createObjectGroup, 3},
{"createTileClass", createTileClass, 0},
{"addToClass", addToClass, 0},
{"removeFromClass", removeFromClass, 0},
{0, 0, 0}
};
@ -367,6 +369,42 @@ JSBool createTileClass(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, js
return JS_TRUE;
}
JSBool addToClass(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
CheckInit(true, cx, __FUNCTION__);
ValidateArgs("iii", cx, argc, argv, __FUNCTION__);
int x = JSVAL_TO_INT(argv[0]);
int y = JSVAL_TO_INT(argv[1]);
int classIndex = JSVAL_TO_INT(argv[2]) - 1;
if(!theMap->validClass(classIndex) || !theMap->validT(x, y)) {
JS_ReportError(cx, "addToClass: Invalid arguments for addToClass(x, y, class)");
}
theMap->tileClasses[classIndex]->add(x, y);
return JS_TRUE;
}
JSBool removeFromClass(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
CheckInit(true, cx, __FUNCTION__);
ValidateArgs("iii", cx, argc, argv, __FUNCTION__);
int x = JSVAL_TO_INT(argv[0]);
int y = JSVAL_TO_INT(argv[1]);
int classIndex = JSVAL_TO_INT(argv[2]) - 1;
if(!theMap->validClass(classIndex) || !theMap->validT(x, y)) {
JS_ReportError(cx, "addToClass: Invalid arguments for removeFromClass(x, y, class)");
}
theMap->tileClasses[classIndex]->remove(x, y);
return JS_TRUE;
}
JSBool getMapSize(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
CheckInit(true, cx, __FUNCTION__);

View File

@ -33,5 +33,7 @@ JSBool createObjectGroup(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
// Tile class functions
JSBool createTileClass(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
JSBool addToClass(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
JSBool removeFromClass(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
#endif

View File

@ -219,14 +219,6 @@ int Map::getId(string texture) {
}
}
bool Map::validT(int x, int y) {
return x>=0 && y>=0 && x<size && y<size;
}
bool Map::validH(int x, int y) {
return x>=0 && y>=0 && x<size+1 && y<size+1;
}
string Map::getTexture(int x, int y) {
if(!validT(x,y)) JS_ReportError(cx, "getTexture: invalid tile position");
return idToName[texture[x][y]];

View File

@ -29,8 +29,10 @@ public:
int getId(std::string texture);
bool validT(int x, int y);
bool validH(int x, int y);
bool validT(int x, int y) { return x>=0 && y>=0 && x<size && y<size; }
bool validH(int x, int y) { return x>=0 && y>=0 && x<=size && y<=size; }
bool validClass(int c) { return c>=0 && c<(int)tileClasses.size(); }
std::string getTexture(int x, int y);
void setTexture(int x, int y, const std::string& texture);

View File

@ -0,0 +1,152 @@
#include "stdafx.h"
#include "Noise.h"
#include <cmath>
#include "random.h"
using namespace std;
namespace
{
/// Utility function used in both noises as an ease curve
float easeCurve(float t)
{
return t*t*t*(t*(t*6-15)+10);
}
}
Noise2D::Noise2D(int f)
{
freq = f;
grads = new CVector2D_Maths*[freq];
for(int i=0; i<freq; i++)
{
grads[i] = new CVector2D_Maths[freq];
for(int j=0; j<freq; j++)
{
float a = RandFloat() * 2 * PI;
grads[i][j] = CVector2D_Maths(cos(a), sin(a));
}
}
}
Noise2D::~ Noise2D()
{
for(int i=0; i<freq; i++)
{
delete[] grads[i];
}
delete[] grads;
}
float Noise2D::operator()(float x, float y)
{
x *= freq;
y *= freq;
int ix = (int)floor(x);
int iy = (int)floor(y);
float fx = x - ix;
float fy = y - iy;
ix %= freq; if(ix<0) ix += freq;
iy %= freq; if(iy<0) iy += freq;
int ix1 = (ix+1) % freq;
int iy1 = (iy+1) % freq;
float s = grads[ix][iy].Dot(CVector2D_Maths(fx, fy));
float t = grads[ix1][iy].Dot(CVector2D_Maths(fx-1, fy));
float u = grads[ix][iy1].Dot(CVector2D_Maths(fx, fy-1));
float v = grads[ix1][iy1].Dot(CVector2D_Maths(fx-1, fy-1));
float ex = easeCurve(fx);
float ey = easeCurve(fy);
float a = s + ex*(t-s);
float b = u + ex*(v-u);
return (a + ey*(b-a)) * .5 + .5;
}
Noise3D::Noise3D(int f, int v) : freq(f), vfreq(v)
{
grads = new CVector3D**[freq];
for(int i=0; i<freq; i++)
{
grads[i] = new CVector3D*[freq];
for(int j=0; j<freq; j++)
{
grads[i][j] = new CVector3D[vfreq];
for(int k=0; k<vfreq; k++)
{
CVector3D v;
do {
v = CVector3D(2*RandFloat()-1, 2*RandFloat()-1, 2*RandFloat()-1);
}
while(v.LengthSquared() > 1 || v.LengthSquared() < 0.1);
v.Normalize();
grads[i][j][k] = CVector3D(v.X, v.Y, v.Z);
}
}
}
}
Noise3D::~ Noise3D()
{
for(int i=0; i<freq; i++)
{
for(int j=0; j<freq; j++)
{
delete[] grads[i][j];
}
delete[] grads[i];
}
delete[] grads;
}
float Noise3D::operator()(float x, float y, float z)
{
x *= freq;
y *= freq;
z *= vfreq;
int ix = (int)floor(x);
int iy = (int)floor(y);
int iz = (int)floor(z);
float fx = x - ix;
float fy = y - iy;
float fz = z - iz;
ix %= freq; if(ix<0) ix += freq;
iy %= freq; if(iy<0) iy += freq;
iz %= vfreq; if(iz<0) iz += vfreq;
int ix1 = (ix+1) % freq;
int iy1 = (iy+1) % freq;
int iz1 = (iz+1) % vfreq;
float s0 = grads[ix][iy][iz].Dot(CVector3D(fx, fy, fz));
float t0 = grads[ix1][iy][iz].Dot(CVector3D(fx-1, fy, fz));
float u0 = grads[ix][iy1][iz].Dot(CVector3D(fx, fy-1, fz));
float v0 = grads[ix1][iy1][iz].Dot(CVector3D(fx-1, fy-1, fz));
float s1 = grads[ix][iy][iz1].Dot(CVector3D(fx, fy, fz-1));
float t1 = grads[ix1][iy][iz1].Dot(CVector3D(fx-1, fy, fz-1));
float u1 = grads[ix][iy1][iz1].Dot(CVector3D(fx, fy-1, fz-1));
float v1 = grads[ix1][iy1][iz1].Dot(CVector3D(fx-1, fy-1, fz-1));
float ex = easeCurve(fx);
float ey = easeCurve(fy);
float ez = easeCurve(fz);
float a0 = s0 + ex*(t0-s0);
float b0 = u0 + ex*(v0-u0);
float c0 = a0 + ey*(b0-a0);
float a1 = s1 + ex*(t1-s1);
float b1 = u1 + ex*(v1-u1);
float c1 = a1 + ey*(b1-a1);
return (c0 + ez*(c1-c0)) * .5 + .5;
}

View File

@ -0,0 +1,54 @@
///////////////////////////////////////////////////////////////////////////////
//
// Name: Noise.h
// Author: Matei Zaharia
// Contact: matei@wildfiregames.com
//
// Description: 2D and 3D seamless Perlin noise classes. Not optimized for speed yet.
//
// Based on http://www.cs.cmu.edu/~mzucker/code/perlin-noise-math-faq.html
// and http://mrl.nyu.edu/~perlin/paper445.pdf.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef NOISE_H
#define NOISE_H
#include "Vector2D.h"
#include "Vector3D.h"
#include "MathUtil.h"
class Noise2D
{
/// Frequency in X and Y
int freq;
/// freq*freq random gradient vectors in the unit cube
CVector2D_Maths** grads;
public:
Noise2D(int freq);
~Noise2D();
/// Evaluate the noise function at a given point
float operator() (float x, float y);
};
class Noise3D
{
/// Frequency in X and Y
int freq;
/// Frequency in Z (vertical frequency)
int vfreq;
/// freq*freq*vfreq random gradient vectors in the unit cube
CVector3D*** grads;
public:
Noise3D(int freq, int vfreq);
~Noise3D();
/// Evaluate the noise function at a given point
float operator() (float x, float y, float z);
};
#endif

View File

@ -4,11 +4,15 @@
#include "map.h"
#include "object.h"
#include "pmp_file.h"
#include <iomanip>
using namespace std;
// Sea level elevation
const float SEA_LEVEL = 20.0f;
void OutputObject(Map* m, Object* e, ostringstream& xml) {
float height = m->getExactHeight(e->x, e->y);
float height = m->getExactHeight(e->x, e->y) + SEA_LEVEL;
if(e->isEntity()) {
xml << "\
@ -52,7 +56,6 @@ void OutputXml(Map* m, FILE* f) {
ostringstream xml;
xml << "\
<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n\
<!DOCTYPE Scenario SYSTEM \"/maps/scenario_v4.dtd\">\n\
<Scenario>\n\
<Environment>\n\
<SunColour r=\"1\" g=\"1\" b=\"1\" />\n\
@ -60,6 +63,7 @@ void OutputXml(Map* m, FILE* f) {
<SunRotation angle=\"4.712389\" />\n\
<TerrainAmbientColour r=\"0.5\" g=\"0.5\" b=\"0.5\" />\n\
<UnitsAmbientColour r=\"0.52\" g=\"0.52\" b=\"0.52\" />\n\
<Water><WaterBody><Height>" << SEA_LEVEL-0.1f << "</Height></WaterBody></Water>\n\
</Environment>\n\
<Entities>\n";
OutputObjects(xml, m, true); // print entities
@ -67,7 +71,8 @@ void OutputXml(Map* m, FILE* f) {
</Entities>\n\
<Nonentities>\n";
OutputObjects(xml, m, false); // print nonentities
xml << "</Nonentities>\n\
xml << "\
</Nonentities>\n\
</Scenario>\n";
fprintf(f, "%s", xml.str().c_str());
@ -117,7 +122,7 @@ struct PMP {
u16* heightmap = new u16[(size+1)*(size+1)];
for(int x=0; x<size+1; x++) {
for(int y=0; y<size+1; y++) {
int intHeight = (int) (m->height[x][y] * 256.0f / 0.35f);
int intHeight = (int) ((m->height[x][y]+SEA_LEVEL) * 256.0f / 0.35f);
if(intHeight > 0xFFFF) {
intHeight = 0xFFFF;
}