我正在开发一个 iOS 版本的 Game Of Life。该应用程序的核心是工作,因为它是一个具有所有生活游戏规则的工作元胞自动机。它可以制造滑翔机、宇宙飞船,并且功能完美。如果您不知道 Game of Life,请在 youtube 上观看。
所以,我想用这个版本做的是“平滑”每个单元格。因此,不是每个细胞都是一个孤立的网格,而是所有活着的细胞将被绘制成一个连续的 Blob 。
我已经让这个以离散的形式工作了。我的意思是我为每个单元格制定了一组规则,其中单元格绘制选定的曲线/矩形形状,以便所有单元格组合在一起。
看起来像:
例如,右侧 blob 的顶部单元格下方有一个活单元格,并且知道其他单元格,因此它有 3 个直边和一个弯曲的顶部。确定单元格形状的规则是“单元格”类中的逻辑。这会产生我想要的几何效果。但是,因为每个单元格都有不同的 CGPath,所以它是不可动画的。理想情况下,在这张图片中会有两个 CGPath。
问题就在这里。我现在正在研究一种基于给定世代中存活的细胞来组成矢量路径的方法。我相信,这个过程将涉及让单元格的输出坐标点,然后由某些过程采用这些坐标点来创建路径。然而,这些点的顺序很重要。
例如:
在此,1) 代表一组“活的”“细胞”。 2) 是我生成积分的方式。基本上使用我用来从第一种技术创建弯曲路径的相同逻辑。它可能需要稍微改变,因为有些点不需要,因为它们与单元格重叠。 3)将是理想的矢量形状。它们是两条矢量路径,因为单元格上没有邻居。
所以我正在考虑的算法是如何迭代这些点,为所有相互相邻的单元格集创建离散路径。我正在研究从左到右与从上到下的扫描方式。或者总是选择最接近当前点的点,同时仍然是一组活细胞的一部分。当它到达第一个点时,它将跳到不在该组活细胞中的最近点。在这一点上只是理论化。
最佳答案
为每个活细胞创建相邻活细胞的位掩码。假设您使用值 1、2、4 和 8 的位表示北、东、南和西。您将获得一组 16 个图 block :
如果您只想显示带有圆形轮廓的单元格,您可以快速将适当的图 block 复制到 Canvas 或位图窗口。这应该相当快,因为图 block 已经呈现。
如果您确实需要曲线形式的轮廓,请将深红色线条创建为每个图 block 的曲线。请注意,方 block 0101
和 1010
由两条曲线组成,方 block 1111
没有曲线,方 block 0000
是一条闭合曲线。
将当前单元格具有偏移量的曲线段移动到列表中。所有曲线(0000
除外)都有松散的末端;这些端点始终位于单元格边界交叉点上。
现在您可以创建路径了。创建一个查找表,可能是所有端点的散列。每个端点应该有两个相连的曲线段。现在从列表中选择一条曲线段。它的起点是曲线的起点。现在查找段的终点并转到下一个段。删除访问过的段并将它们添加到路径中。继续,直到你到达 曲线的起点。将曲线添加到列表中。当列表中仍有曲线段时重复。
您可能应该立即将没有邻居的单元格 (0000
) 放入最终的曲线列表中。您还必须将 0101
和 1010
单元格的两条线段视为单独的线段。
编辑:我用 C 拼凑了一个概念验证示例,它采用随机生成的单元格网格并创建平滑渲染的 PostScript 文件。
恐怕命名法有点不一致,但基本上有四种分段类型:闭合圆、尖刺(或 Nose )、四分之一圆和直壁。它们以顺时针方式呈现,因此封闭的形状始终位于曲线的右侧。
PS 渲染 - stype
的 path
字段 - 由四分之一圆组成(NE
,SE
, SW
, NW
), 单元格大小的直线(N1
, E1
, S1
, W1
) 和一半像元大小的直线 (N2
, E2
, S2
,W2
)。当然,您可以将它们渲染为直线和三次贝塞尔曲线序列,而不是使用 PS 命令。
每个段还存储单元格的开始和结束角与 NE
、SE
、SW
和 NW 的位置
枚举。
除了单元格之外,还有一个线段列表和角点网格,用于存储哪些线段附加到角点。一个角只能没有或有两个线段。
该示例的大小在编译时是固定的,以使 C 程序员的工作更轻松。开始了:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define N 24
#define die(...) exit((fprintf(stderr, __VA_ARGS__), 1))
enum {NW, NE, SE, SW, NCORNER};
static const int dx[NCORNER] = {0, 1, 1, 0};
static const int dy[NCORNER] = {0, 0, 1, 1};
struct stype {
const char *name;
int start;
int end;
const char *path;
};
#define STYPE(X) \
X(None, -1, -1, "") \
X(WallN, NW, NE, "E1") \
X(WallE, NE, SE, "S1") \
X(WallS, SE, SW, "W1") \
X(WallW, SW, NW, "N1") \
X(QuarterNE, SE, NW, "W2 SW N2") \
X(QuarterSE, SW, NE, "N2 NW E2") \
X(QuarterSW, NW, SE, "E2 NE S2") \
X(QuarterNW, NE, SW, "S2 SE W2") \
X(SpikeN, NE, NW, "S2 SE SW N2") \
X(SpikeE, SE, NE, "W2 SW NW E2") \
X(SpikeS, SW, SE, "N2 NW NE S2") \
X(SpikeW, NW, SW, "E2 NE SE W2") \
X(Circle, NW, NW, "MM")
#define STYPE_STRUCT(Name, S1, S2, P) {#Name, S1, S2, P},
#define STYPE_ENUM(Name, S1, S2, P) Name,
enum {STYPE(STYPE_ENUM) MAX_STYPE};
static const struct stype stype[MAX_STYPE] = { STYPE(STYPE_STRUCT)};
int ctype[16][3] = {
/* 0 */ {Circle, None},
/* 1 */ {SpikeN, None},
/* 2 */ {SpikeE, None},
/* 3 */ {QuarterNE, None},
/* 4 */ {SpikeS, None},
/* 5 */ {WallW, WallE, None},
/* 6 */ {QuarterSE, None},
/* 7 */ {WallW, None},
/* 8 */ {SpikeW, None},
/* 9 */ {QuarterNW, None},
/* 10 */ {WallN, WallS, None},
/* 11 */ {WallS, None},
/* 12 */ {QuarterSW, None},
/* 13 */ {WallE, None},
/* 14 */ {WallN, None},
/* 15 */ { None},
};
struct seg {
int type;
int x;
int y;
};
struct pt {
int seg1;
int seg2;
};
int alive(int cell[N][N], int x, int y)
{
if (x < 0 || x >= N) return 0;
if (y < 0 || y >= N) return 0;
return cell[y][x];
}
void addpt(struct pt *pt, int seg)
{
if (pt->seg1 < 0) {
pt->seg1 = seg;
} else if (pt->seg2 < 0) {
pt->seg2 = seg;
} else {
die("Too many segments for point.\n");
}
}
int main(void)
{
int cell[N][N];
int count = 0;
int i, x, y;
for (y = 0; y < N; y++) {
for (x = 0; x < N; x++) {
int r = 1 + abs(N/2 - x) + abs(N/2 - y);
cell[y][x] = (rand() / 4 < RAND_MAX / r);
if (cell[y][x]) count++;
}
}
/* Create line segments */
struct seg seg[2 * count];
int nseg = 0;
for (y = 0; y < N; y++) {
for (x = 0; x < N; x++) {
int ix = 0;
if (cell[y][x] == 0) continue;
if (alive(cell, x, y - 1)) ix |= 1;
if (alive(cell, x + 1, y)) ix |= 2;
if (alive(cell, x, y + 1)) ix |= 4;
if (alive(cell, x - 1, y)) ix |= 8;
int *p = ctype[ix];
while (*p != None) {
if (nseg >= 2 * count) die("Segment overflow\n");
seg[nseg].x = x;
seg[nseg].y = y;
seg[nseg].type = *p++;
nseg++;
}
}
}
/* determine start and end points of segments */
struct pt pt[N + 1][N + 1];
memset(pt, -1, sizeof(pt));
for (i = 0; i < nseg; i++) {
int tp = seg[i].type;
int s = stype[tp].start;
int e = stype[tp].end;
x = seg[i].x;
y = seg[i].y;
addpt(&pt[y + dy[s]][x + dx[s]], i);
addpt(&pt[y + dy[e]][x + dx[e]], i);
}
/* set up PostScript header */
puts("%!PS-Adobe 3.0");
puts("/A 10 def");
puts("/A2 A 2 mul def");
puts("/C { rcurveto } def");
puts("/L { rlineto } def");
puts("/START { newpath exch A2 mul exch A2 mul moveto } bind def");
puts("/END { closepath stroke } bind def");
puts("/MM { A 0 rmoveto NE SE SW NW } bind def");
puts("/NW { 0 A neg 0 A neg A A neg C } bind def");
puts("/NE { A 0 A 0 A A C } bind def");
puts("/SE { 0 A 0 A A neg A C } bind def");
puts("/SW { A neg 0 A neg 0 A neg A neg C } bind def");
puts("/N1 { 0 A2 neg L } bind def");
puts("/E1 { A2 0 L } bind def");
puts("/S1 { 0 A2 L } bind def");
puts("/W1 { A2 neg 0 L } bind def");
puts("/N2 { 0 A neg L } bind def");
puts("/E2 { A 0 L } bind def");
puts("/S2 { 0 A L } bind def");
puts("/W2 { A neg 0 L } bind def");
puts("57 180 translate");
/* walk segments */
for (i = 0; i < nseg; i++) {
struct seg *s = seg + i;
if (s->type == None) continue;
int x0 = s->x + dx[stype[s->type].start];
int y0 = s->y + dy[stype[s->type].start];
int j = i;
x = s->x + dx[stype[s->type].end];
y = s->y + dy[stype[s->type].end];
printf("%d %d START", x0, y0);
for (;;) {
printf(" %s", stype[s->type].path);
s->type = None;
if (x == x0 && y == y0) break;
if (pt[y][x].seg1 == j) {
j = pt[y][x].seg2;
} else {
j = pt[y][x].seg1;
}
s = seg + j;
x = s->x + dx[stype[s->type].end];
y = s->y + dy[stype[s->type].end];
}
puts(" END");
}
puts("showpage");
return 0;
}
关于objective-c - 将元胞自动机状态简化为一组向量路径,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33705810/