← 日志列表 / All journal entries

用 Space Colonization 生成聚落道路

Using Space Colonization for Settlement Roads

Space Colonization从自然生长到受约束路网

Space Colonization From Natural Growth to Constrained Road Networks

pcgtown1_SpaceColonization_p1


原始 Space Colonization 用于道路时的问题

Space Colonization 可以把一组目标点连成分枝结构。把它直接用在聚落道路上,会出现几个问题。

多个生长点可能会同时受到同一片 attractor 的影响。几条支路会一起往一个方向长。结果就是平行道路、拥挤道路和重复道路。只看平均吸引方向,系统不知道前面是不是已经有别的路。

还有一个问题。方向看起来没问题,新点的位置还是可能离别的生长点太近。局部就会出现挤压,也会出现很多短支路。

原始结果里只有线和连接关系。后面的道路系统还需要道路层级。历史节点如果一直保留生长能力,网络内部也会一直长出新支路。结果会越来越碎。

所以这里要做的事很直接。保留 Space Colonization 的生长方式,再给它加上适合道路的限制。

为道路增加几层约束

我保留了 attractor 驱动生长这部分。然后加了几层限制。

先处理 attractor。这里把 attractor 看成还没有被道路覆盖的空间。上游会把每个聚落区域的 town_area 转成 attractors_count。Scatter 再按照这个数值生成 attractor。面积大的区域会得到更多点。面积小的区域会得到更少点。

每个活动 seed 会查找附近的 grp_attractor。然后把指向这些点的单位向量加在一起。结果就是这一次的生长方向。

生成新点之前,我又加了两次检查。

第一层检查方向。当前 seed 会查找附近的 grp_seed。如果另一个 seed 已经在候选方向前面,而且夹角小于大约 50 度,这一次就不再生长。这样可以少生成几条朝同一区域一起延伸的道路。

第二层检查位置。系统先算出下一步的新位置,再看这个位置附近有没有别的活动 seed。现在的 step_size 是 22。碰撞距离是 step_size - 0.1。这样 parent 不会被当成碰撞对象,新点也不会直接落到别的活动 seed 旁边。

几个距离也都跟着 step size 走。邻域半径和 kill radius 大约是一个 step。attractor 的搜索范围大约是两个 step。neighbour_rad 直接引用道路的 Resample 长度。kill_rad 使用 step_size * 1.05

道路层级也在生长时一起写入。初始道路是 road_level = 1。后面的 branch 会根据 parent 的 seed_level 继续写新的层级。这里的 parent-child 关系只是项目里的分类规则。后面的道路宽度、路口和材质可以直接读取这些数据。

grp_seed 只保存现在还能继续生长的点。每个 seed 参加三轮计算后,就会退出这个组。这个点还留在道路里,只是不再继续生长。

这些约束改变了什么

这些限制加进去以后,结果开始变得稳定。

Attractor 数量跟聚落面积绑定以后,大区域通常会保留更强的道路扩展需求。小区域不会被切得太碎。

方向检查减少了几条支路一起往同一个方向长的情况。位置检查又减少了活动 seed 挤在一起的情况。系统现在不只看“往哪里长”。它也会看“前面是不是已经有别的生长点”和“下一步的位置还能不能放下新点”。

几个半径跟着 step size 走以后,调整道路段长时,其他判断也会保持大致相同的尺度关系。

Active frontier 也控制了生长范围。旧节点退出 grp_seed 后,还留在道路网络里。它不会一直继续生成新的 branch。

道路层级也会跟着拓扑一起留下。最后得到的不只是 Polyline。后面的道路宽度、路口生成和 UE 流程都可以继续使用这些数据。

当前实现的边界

这套实现还有几个限制。

50 度的方向阈值、三轮 seed 生命周期、几个半径之间的比例,都是项目里的经验参数。它们适合现在这套输入。换一套聚落尺度和道路密度以后,可能还要重新调。

现在的方向检查和位置碰撞都只查询 grp_seed。旧节点退出 active frontier 后,就不会再参加后面的碰撞检测。它还在道路几何里,只是不再参与这部分计算。

所以现在主要解决的是活动生长点之间的冲突。它不能保证新道路不会靠近以前生成的道路。

后面如果要补这部分,可以把两个状态分开。一个 group 保存还能继续生长的 seed。另一个 group 保存全部道路节点。碰撞检查可以查完整道路。生长逻辑继续只查 active frontier。

这套系统也没有全局道路规划。它不会比较整条路线的长度。它也不会主动评估或优化整体连通性、绕行成本和中心性。

所以现在它更适合做一件事:

在已有道路骨架和聚落分布上,继续生成一层次级道路。

它解决的是局部生长问题。它没有解决整套路网规划问题。


pcgtown1_SpaceColonization_p2

Problems with the Original Space Colonization Approach

Space Colonization can connect target points into a branching structure. It does not work well for settlement roads without extra control.

Several growth points may react to the same group of attractors. Nearby branches may grow in the same direction. This can create parallel roads, crowded roads, and repeated roads. The average attraction direction does not tell the system if another branch is already growing into the same area.

There is another problem. The direction may look fine, but the new point may still be too close to another growth point. This can create local crowding and many short branches.

The original result also contains only lines and connections. The later road system still needs road levels. If all old points stay active, the inside of the network can keep producing new branches. The result becomes too fragmented.

The goal here is simple. I keep the basic Space Colonization growth process and add limits for road generation.

Adding Constraints for Road Growth

I keep the attractor-driven growth. Then I add several constraints.

First, I treat attractors as uncovered spatial demand. The upstream system converts each settlement area value, town_area, into attractors_count. Scatter then uses this value to create attractors. Larger settlement areas get more attractors. Smaller areas get fewer.

Each active seed searches nearby grp_attractor points. It adds the unit vectors toward those points. The result becomes the growth direction for that step.

Before creating a new point, I add two checks.

The first check is direction competition. The current seed searches nearby grp_seed points. If another active seed is already in front of the candidate direction, and the angle is smaller than about 50 degrees, the growth stops. This reduces cases where several nearby branches grow toward the same area.

The second check is candidate position collision. The system first calculates the next point position. Then it checks if another active seed is too close to that position. The current step_size is 22. The collision distance is step_size - 0.1. This prevents the parent from blocking its own child. It also keeps new points away from other active seeds.

Several distances also follow the same growth scale. The neighbor radius and kill radius are about one step. The attractor search radius is about two steps. neighbour_rad follows the road Resample length. kill_rad uses step_size * 1.05.

Road levels are also written during growth. The initial roads use road_level = 1. Later branches receive new levels from the parent seed_level. This parent-child rule is only a project rule for road classification. It gives the later road width, intersection, and material systems data they can use directly.

grp_seed only stores points that can still grow. Each seed leaves this group after three calculation rounds. The point stays in the road geometry, but it no longer keeps growing.

What These Constraints Change

These constraints make the results more stable.

When attractor count follows settlement area, larger areas usually keep stronger road expansion demand. Smaller areas are less likely to be divided too much.

The direction check reduces cases where several branches grow in the same direction. The position check reduces cases where active seeds become too crowded. The system no longer asks only where to grow. It also checks if another growth point is already ahead and if the next position has enough space.

The radius values also follow step_size. When the road segment length changes, the other checks keep roughly the same scale relationship.

The active frontier also limits where growth can continue. Old points stay in the road network after they leave grp_seed. They do not keep producing new branches.

Road levels also stay with the generated topology. The final result is not only a set of Polylines. The later road width, intersection, and Unreal Engine systems can keep using the same data.

Current Limits

This system still has clear limits.

The 50-degree angle threshold, the three-round seed lifetime, and the radius ratios are project-based values. They work with the current input. They may need to change for another settlement scale or road density.

The direction check and position collision only query grp_seed. Old points stop taking part in collision checks after they leave the active frontier. They still exist in the road geometry.

This means the current system mainly avoids conflicts between active growth points. It does not guarantee that a new road will stay away from all previously generated roads.

One possible improvement is to separate the two states. One group can store active seeds. Another group can store all road points. Collision checks can use the full road group. Growth can still use only the active frontier.

The system also has no global road planning. It does not compare full route lengths. It does not actively evaluate or optimize overall connectivity, detour cost, or network centrality.

So the current system is better suited to one task:

Extend secondary roads from an existing road skeleton and settlement distribution.

It solves local growth problems. It does not solve full road network planning.