我试图在程序上产生一些河流。
我有一个平面(没有高程概念)正方形网格作为基础,并希望在其上绘制分支结构,如图所示。
你能分享一下可以用来完成的步骤吗?
我不是在寻找最快的实现,因为没有实时生成,但更简单的实现将是首选。 Lua是我的语言,但任何事情都可以。
更多的事情:
答案 0 :(得分:1)
你的河流三角洲看起来很像一棵树。下面是一些使用turtle来绘制树的Python代码。
#您可以编辑此代码并在浏览器中直接运行! #尝试更改颜色或添加自己的形状。
import turtle
from random import randint
def tree(length,n, ps):
""" paints a branch of a tree with 2 smaller branches, like an Y"""
if length < (length/n):
return # escape the function
turtle.pensize(max(ps,1))
turtle.forward(length) # paint the thik branch of the tree
lb = 45+randint(-20,20)
turtle.left(lb) # rotate left for smaller "fork" branch
tree(length * 0.5*(1+randint(-20,20)/100),length/n,ps-1) # create a smaller branch with 1/2 the lenght of the parent branch
rb = 45+randint(-20,20)
turtle.right(lb+rb) # rotoate right for smaller "fork" branch
tree(length * 0.6,length/n,ps-1) # create second smaller branch
turtle.left(rb) # rotate back to original heading
rt = randint(-20,20)
turtle.right(rt)
tree(length * 0.45,length/n,ps-1)
turtle.left(rt)
turtle.backward(length) # move back to original position
return # leave the function, continue with calling program
turtle.left(90)
turtle.penup()
turtle.backward(250)
turtle.pendown()
tree(150,5,5)
答案 1 :(得分:1)
我认为生成河流是一种落后的方法,因为你需要根据它们的形状调整很多东西,这将很难。我会创建随机地形高度图并从中提取特征(如在现实世界中),这更容易和更接近现实。在最终地图中,您忽略高度并使用平面高度(如果您真的想要平面地图)。以下是您可以从高度图中提取的一些内容:
河流和湖泊
通过播种随机高海拔点并沿着它下坡到海平面或地图边缘。
植被或地面
从斜坡和海拔高度可以确定地面是沙子,泥土,岩石。如果有树木,灌木丛,草地或其他什么。
在此处查看 QA :random island generator
和一些河流概述:
调整地形的方式也会影响河流的形状(不需要只生成岛屿)。
种子也在为这种方法工作。
[Edit1]承诺使用C ++代码
这基本上生成随机高度图然后种子和下坡跟随河流(如果地形块下坡流动,湖泊会自动生成)。地形类型也是根据坡度和高度确定的。
//---------------------------------------------------------------------------
picture pic;
//---------------------------------------------------------------------------
void map_random(int _xs,int _ys)
{
// config
int h0=-1000,h1=3000; // [m] terrain elevation range
int h_water= 0; // [m] sea level
int h_sand=15; // [m] sand level
int h_evergreen=1500; // [m] evergreen level
int h_snow=2000; // [m] snow level
int h_rock=1800; // [m] mountine rock level
float a_rock=60.0; // [deg] mountine rock slope
float d_pixel=35.0; // [m] pixel size
int d_river_w=5; // [pixel] river max width
int d_river_l=150; // [pixel] river base length per width increase
bool _island=true;
// types
enum _cover_enum
{
_cover_none=0,
_cover_water, // sea
_cover_snow,
_covers,
_cover_shift=0,
_cover_mask=15,
};
DWORD _cover[_covers]=
{
// RRGGBB
0x00000000, // none
0x00003080, // watter (sea)
0x00EEEEEE, // snow
};
enum _terrain_enum
{
_terrain_dirt=0,
_terrain_sand,
_terrain_rock,
_terrain_water, // streams,rivers,lakes
_terrain_temp, // temp
_terrains,
_terrain_shift=4,
_terrain_mask=15,
};
DWORD _terrain[_terrains]=
{
// RRGGBB
0x00301510, // dirt
0x00EEC49A, // sand
0x006F6F6F, // rock
0x00006080, // water (streams,rivers,lakes)
0x00006080, // temp
};
enum _flora_enum
{
_flora_none=0,
_flora_grass,
_flora_hardwood,
_flora_evergreen,
_flora_deadwood,
_floras,
_flora_shift=8,
_flora_mask=15,
};
DWORD _flora[_floras]=
{
// RRGGBB
0x00000000, // none
0x007F7F3F, // grass
0x001FFF1F, // hardwood
0x00007F00, // evergreen
0x007F3F1F, // deadwood
};
// variables
float a,b,da; int c,t,f;
int x,y,z,xx,yy,mxs,mys,dx,dy,dx2,dy2,r,r2,ix,l;
int xh1,yh1; // topest hill position
int **ter=NULL,**typ=NULL;
Randomize();
// align resolution to power of 2
for (mxs=1;mxs+1<_xs;mxs<<=1); if (mxs<3) mxs=3;
for (mys=1;mys+1<_ys;mys<<=1); if (mys<3) mys=3;
ter=new int*[mys+1]; for (y=0;y<=mys;y++) ter[y]=new int[mxs+1];
typ=new int*[mys+1]; for (y=0;y<=mys;y++) typ[y]=new int[mxs+1];
// [Terrain]
for (;;)
{
// diamond & square random height map -> ter[][]
dx=mxs; dx2=dx>>1; r=(mxs+mys)<<1; // init step,half step and randomness
dy=mys; dy2=dy>>1; r2=r>>1;
// set corners values
if (_island)
{
t=-r2;
ter[ 0][ 0]=t;
ter[ 0][mxs]=t;
ter[mys][ 0]=t;
ter[mys][mxs]=t;
ter[dy2][dx2]=r+r; // top of central hill
}
else{
ter[ 0][ 0]=Random(r);
ter[ 0][mxs]=Random(r);
ter[mys][ 0]=Random(r);
ter[mys][mxs]=Random(r);
}
for (;dx2|dy2;dx=dx2,dx2>>=1,dy=dy2,dy2>>=1) // subdivide step until full image is filled
{
if (!dx) dx=1;
if (!dy) dy=1;
// diamond (skip first one for islands)
if ((!_island)||(dx!=mxs))
for (y=dy2,yy=mys-dy2;y<=yy;y+=dy)
for (x=dx2,xx=mxs-dx2;x<=xx;x+=dx)
ter[y][x]=((ter[y-dy2][x-dx2]+ter[y-dy2][x+dx2]+ter[y+dy2][x-dx2]+ter[y+dy2][x+dx2])>>2)+Random(r)-r2;
// square
for (y=dy2,yy=mys-dy2;y<=yy;y+=dy)
for (x=dx ,xx=mxs-dx ;x<=xx;x+=dx)
ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y-dy2][x]+ter[y+dy2][x])>>2)+Random(r)-r2;
for (y=dy ,yy=mys-dy ;y<=yy;y+=dy)
for (x=dx2,xx=mxs-dx2;x<=xx;x+=dx)
ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y-dy2][x]+ter[y+dy2][x])>>2)+Random(r)-r2;
for (x=dx2,xx=mxs-dx2;x<=xx;x+=dx)
{
y= 0; ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y+dy2][x])/3)+Random(r)-r2;
y=mys; ter[y][x]=((ter[y][x-dx2]+ter[y][x+dx2]+ter[y-dy2][x])/3)+Random(r)-r2;
}
for (y=dy2,yy=mys-dy2;y<=yy;y+=dy)
{
x= 0; ter[y][x]=((ter[y][x+dx2]+ter[y-dy2][x]+ter[y+dy2][x])/3)+Random(r)-r2;
x=mxs; ter[y][x]=((ter[y][x-dx2]+ter[y-dy2][x]+ter[y+dy2][x])/3)+Random(r)-r2;
}
if (_island)
{
// recompute middle position after first pass so there can be more central hills
if (dx==mxs) ter[dy2][dx2]=Random(r2);
// adjust border to underwatter
for (y=0;y<=mys;y+=dy2) { ter[y][0]=t; ter[y][mxs]=t; }
for (x=0;x<=mxs;x+=dx2) { ter[0][x]=t; ter[mys][x]=t; }
}
// adjust randomness
r>>=1; if (r<2) r=2; r2=r>>1;
}
// rescale to <h0,h1>
xx=ter[0][0]; yy=xx;
for (y=0;y<=mys;y++)
for (x=0;x<=mxs;x++)
{
z=ter[y][x];
if (xx>z) xx=z;
if (yy<z){ yy=z; xh1=x; yh1=y; }
}
for (y=0;y<=mys;y++)
for (x=0;x<=mxs;x++)
ter[y][x]=h0+(((ter[y][x]-xx)*(h1-h0))/(yy-xx));
// test for correctness
if (_island)
{
l=0;
for (x=0;x<=mxs;x++) { if (ter[0][x]>h_water) l++; if (ter[mys][x]>h_water) l++; }
for (y=0;y<=mys;y++) { if (ter[y][0]>h_water) l++; if (ter[y][mxs]>h_water) l++; }
if (l>1+((mxs+mys)>>3)) continue;
}
break;
}
// [Surface]
for (y=0;y<mys;y++)
for (x=0;x<mxs;x++)
{
z=ter[y][x];
// max slope [deg]
a=atan2(ter[y][x+1]-z,d_pixel);
b=atan2(ter[y+1][x]-z,d_pixel);
if (a<b) a=b; a*=180.0/M_PI;
c=_cover_none;
if (z<=h_water) c=_cover_water;
if (z>=h_snow ) c=_cover_snow;
t=_terrain_dirt;
if (z<=h_sand) t=_terrain_sand;
if (z>=h_rock) t=_terrain_rock;
if (a>=a_rock) t=_terrain_rock;
f=_flora_none;
if (t==_terrain_dirt)
{
r=Random(100);
if (r>10) f=_flora_grass;
if (r>50)
{
if (z>h_evergreen) f=_flora_evergreen;
else{
r=Random(h_evergreen);
if (r<=z) f=_flora_evergreen;
else f=_flora_hardwood;
}
}
if (r<5) f=_flora_deadwood;
}
typ[y][x]=(c<<_cover_shift)|(t<<_terrain_shift)|(f<<_flora_shift);
}
// [Rivers]
for (ix=10+Random(5),a=0.0,da=2.0*M_PI/float(ix);ix;ix--)
{
// random start around topest hill
a+=da*(0.75+(0.50*Random()));
for (l=0;l<10;l++)
{
b=Random(mxs>>3);
x=xh1; x+=float(b*cos(a));
y=yh1; y+=float(b*sin(a));
if ((x<1)||(x>=mxs)) continue;
if ((y<1)||(y>=mys)) continue;
if (typ[y][x]&0x00F==_cover_water) continue;
l=-1;
break;
} if (l>=0) continue; // safety check
for (l=0,r2=0;;)
{
// stop on map edge
if ((x<=0)||(x>=mxs-1)||(y<=0)||(y>=mys-1)) break;
// decode generated surface
r=typ[y][x];
c=(r>> _cover_shift)& _cover_mask;
t=(r>>_terrain_shift)&_terrain_mask;
f=(r>> _flora_shift)& _flora_mask;
// stop if reached sea
if (c==_cover_water) break;
// insert river dot radius = r2
dx=x-r2; if (dx<0) dx=0; dx2=x+r2; if (dx2>=mxs) dx2=mxs-1;
dy=y-r2; if (dy<0) dy=0; dy2=y+r2; if (dy2>=mys) dy2=mys-1;
for (yy=dy;yy<=dy2;yy++)
for (xx=dx;xx<=dx2;xx++)
if (((xx-x)*(xx-x))+((yy-y)*(yy-y))<=r2*r2)
if (((typ[yy][xx]>>_terrain_shift)&_terrain_mask)!=_terrain_water)
typ[yy][xx]=(typ[yy][xx]&0x00F)|(_terrain_temp<<_terrain_shift);
// step to smalest elevation neighbor
dx=x; dy=y; z=h1; typ[y][x]=(typ[y][x]&0x00F)|(_terrain_water<<_terrain_shift); xx=x; yy=y;
xx--; r=ter[yy][xx]; if ((z>=r)&&(((typ[yy][xx]>>_terrain_shift)&_terrain_mask)!=_terrain_water)) { z=r; dx=xx; dy=yy; }
yy--; r=ter[yy][xx]; if ((z>=r)&&(((typ[yy][xx]>>_terrain_shift)&_terrain_mask)!=_terrain_water)) { z=r; dx=xx; dy=yy; }
xx++; r=ter[yy][xx]; if ((z>=r)&&(((typ[yy][xx]>>_terrain_shift)&_terrain_mask)!=_terrain_water)) { z=r; dx=xx; dy=yy; }
xx++; r=ter[yy][xx]; if ((z>=r)&&(((typ[yy][xx]>>_terrain_shift)&_terrain_mask)!=_terrain_water)) { z=r; dx=xx; dy=yy; }
yy++; r=ter[yy][xx]; if ((z>=r)&&(((typ[yy][xx]>>_terrain_shift)&_terrain_mask)!=_terrain_water)) { z=r; dx=xx; dy=yy; }
yy++; r=ter[yy][xx]; if ((z>=r)&&(((typ[yy][xx]>>_terrain_shift)&_terrain_mask)!=_terrain_water)) { z=r; dx=xx; dy=yy; }
xx--; r=ter[yy][xx]; if ((z>=r)&&(((typ[yy][xx]>>_terrain_shift)&_terrain_mask)!=_terrain_water)) { z=r; dx=xx; dy=yy; }
xx--; r=ter[yy][xx]; if ((z>=r)&&(((typ[yy][xx]>>_terrain_shift)&_terrain_mask)!=_terrain_water)) { z=r; dx=xx; dy=yy; }
if ((dx==x)&&(dy==y))
{
// handle invalid path or need for a lake!!!
if (dx>mxs>>1) dx++; else dx--;
if (dy>mys>>1) dy++; else dy--;
}
x=dx; y=dy;
// increase river volume with length
l++; if (l>d_river_l*(r2+1)) { l=0; if (r2<d_river_w) r2++; }
}
// make merging of rivers possible
for (y=0;y<=mys;y++)
for (x=0;x<=mxs;x++)
if (((typ[y][x]>>_terrain_shift)&_terrain_mask)==_terrain_water)
typ[y][x]=(typ[y][x]&0x00F)|(_terrain_temp<<_terrain_shift);
}
for (y=0;y<=mys;y++)
for (x=0;x<=mxs;x++)
if (((typ[y][x]>>_terrain_shift)&_terrain_mask)==_terrain_temp)
typ[y][x]=(typ[y][x]&0x00F)|(_terrain_water<<_terrain_shift);
// [copy data] rewrite this part to suite your needs
for (y=1;y<_ys;y++)
for (x=1;x<_xs;x++)
{
float nx,ny,nz,x0,y0,z0,x1,y1,z1;
// (nx,ny,nz) = surface normal
nx=0.0; ny=0.0; nz=ter[y][x];
x0=-d_pixel; y0=0.0; z0=ter[y][x-1];
x1=0.0; y1=-d_pixel; z1=ter[y-1][x];
x0-=nx; x1-=nx;
y0-=ny; y1-=ny;
z0-=nz; z1-=nz;
nx=(y0*z1)-(z0*y1);
ny=(z0*x1)-(x0*z1);
nz=(x0*y1)-(y0*x1);
x0=1.0/sqrt((nx*nx)+(ny*ny)+(nz*nz));
nx*=x0;
ny*=x0;
nz*=x0;
// z = ambient light + normal shading
nz=(+0.7*nx)+(-0.7*ny)+(+0.7*nz);
if (nz<0.0) nz=0.0;
nz=255.0*(0.2+(0.8*nz)); z=nz;
// r = base color
r=typ[y][x];
c=(r>> _cover_shift)& _cover_mask;
t=(r>>_terrain_shift)&_terrain_mask;
f=(r>> _flora_shift)& _flora_mask;
r=_terrain[t];
if (c) r= _cover[c];
if (f){ if (c) r|=_flora[f]; else r=_flora[f]; };
// sea color is depending on depth not surface normal
if (c==_cover_water) z=256-((ter[y][x]<<7)/h0);
// apply lighting z to color r
yy=int(r>>16)&255; yy=(yy*z)>>8; if (yy>255) yy=255; r=(r&0x0000FFFF)|(yy<<16);
yy=int(r>> 8)&255; yy=(yy*z)>>8; if (yy>255) yy=255; r=(r&0x00FF00FF)|(yy<< 8);
yy=int(r )&255; yy=(yy*z)>>8; if (yy>255) yy=255; r=(r&0x00FFFF00)|(yy );
// set pixel to target image
pic.p[y][x].dd=r;
}
// free ter[][],typ[][]
for (y=0;y<=mys;y++) delete[] ter[y]; delete[] ter; ter=NULL;
for (y=0;y<=mys;y++) delete[] typ[y]; delete[] typ; typ=NULL;
}
//---------------------------------------------------------------------------
代码基于我的链接答案中的代码,但附加了功能(包括河流)。我使用自己的图片类图片,所以有些成员是:
xs,ys
图片大小(以像素为单位)p[y][x].dd
是(x,y)
位置的像素,为32位整数类型clear(color)
- 清除整个图片resize(xs,ys)
- 将图片大小调整为新分辨率bmp
- VCL封装了带Canvas访问权限的GDI Bitmap 您可以调整 Diamond&amp; Square 中的adjust randomness
来更改地形平滑度。高度限制和阈值也可以被篡改。
为了实现更多像河流一样的增长,在群集中种植更多的起始点,以便它们能够及时合并到单个或多个河流中。