如何把现实城市搬进 Minecraft:Arnis 地理数据转世界流水线深度解析
【免费下载链接】arnisGenerate any location from the real world in Minecraft with a high level of detail.项目地址: https://gitcode.com/GitHub_Trending/ar/arnis
Arnis 是一个地理数据转 Minecraft 世界的转换引擎:读取 OpenStreetMap 矢量与数字高程数据,逐方块输出 Java 版、基岩版或 Luanti(Minetest)世界。引擎用 Rust 编写,附带 Tauri 桌面图形界面,也支持纯命令行模式。本文拆解它的坐标转换、元素处理流水线,以及大区域生成背后的工程取舍。
从椭球到方块网格:需要补上的三层差距
输入数据有两种形态:OpenStreetMap 矢量对象(道路是 way,建筑是闭合多边形,各带一组键值标签)和高程瓦片(提供地表高度)。但方块世界只认一件事:(x, y, z) 处放什么方块。中间隔着三层差距。
一是坐标系。经纬度在椭球面上,方块坐标在平面网格上,"一度"在不同纬度对应的米数不同。二是语义。highway=residential标签要变成有宽度、有材质的路,建筑多边形要拉伸成带墙和屋顶的立体。三是规模:按 1 格对应 1 米计算,10×10 km 的区域就有 10⁸ 量级的列,整世界一次性放内存对普通机器不可行。
Arnis 的做法是逐层解决:坐标模块管映射,元素流水线管语义,世界编辑器管写入。
坐标转换:一个转换器里的两种投影
整套坐标系集中在src/coordinate_system/,核心是transformation.rs里的CoordTransformer:输入是地理边界框与比例尺(每米对应几格),输出是世界边界框和逐点转换能力。内部有两条投影路径。
Local 模式做线性插值:用半正矢公式算出边界框的米数长宽,乘比例尺,再按点在框内的相对位置线性插值。速度快,小区域误差可控。WebMercator 模式改用球面墨卡托投影,以边界框中心为原点,保证 x 随东向增大、z 随北向减小,大区域和高纬度下形变明显更小。
世界边界框不是简单拉伸角点算出来的:WebMercator 模式把四个角都投影后取轴对齐包络,保证框内任意点都不会落到世界范围之外。图形界面里用户在地图上画一个矩形框选区域,就是靠这个模块换算出对应的方块范围。
元素流水线与世界写入:输入、处理、输出
主流程在main.rs:取数、解析、分派、写入。取数阶段用std::thread::scope并行跑三个任务——OpenStreetMap 查询、Overture 建筑数据、高程与地表覆盖——总耗时取决于最慢的一个,而不是三者之和。
解析阶段把原始 XML 变成带优先级的处理元素,基础类要素(比如道路)先于建筑处理。分派阶段在src/element_processing/,按要素类型一个文件一个职责:buildings.rs做拉伸与立面,highways.rs做中心线与宽度参数化,water_areas.rs、natural.rs分别管水体和植被。每个模块只负责自己那类"元素→方块"的实现。
写入阶段在src/world_editor/。common.rs维护 region、chunk、section 三层组织的世界数据结构,java.rs、bedrock.rs、luanti.rs各自序列化成对应格式。版本间方块差异靠映射表解决:bedrock_block_map.rs与luanti_block_map.rs分别把 Java 方块名翻译成基岩版和 Luanti 的名字,后者派生自 MC2MT 项目(LGPL,文件头保留了署名)。
开发者也可以完全不用图形界面。克隆仓库:
git clone https://gitcode.com/GitHub_Trending/ar/arnis编译后直接指定经纬度框生成:
cargo run --release -- --output-dir="<存档路径>" --bbox="min_lat,min_lng,max_lat,max_lng"大区域生成的工程细节
分块并行。src/tile.rs把世界切成 512×512 的瓦片——正好一个 Minecraft region,且按 region 边界对齐——每块四周再留 64 格光晕,避免跨界元素被裁掉。每块有自己的WorldEditor实例,处理完按严格边界合并回主世界,线程之间互不冲突。
泛洪填充预计算。多边形填充是热点。src/floodfill_cache.rs先把所有填充并行算完、结果缓存,主循环只做取用。缓存用位图存坐标:每列 1 bit,相比 HashSet 每条目约 24 字节,内存省约 200 倍;超大的闭合环直接放弃填充,防止单个森林多边形把内存吃光。
确定性与流式写盘。并行带来一个麻烦:同一元素可能在不同瓦片里被处理多次,随机值必须跨次一致。src/deterministic_rng.rs用元素的 OSM ID 给 ChaCha8Rng 播种,同一元素无论在哪、按什么顺序处理,拿到的"随机"颜色与样式都相同。同时WorldEditor支持流式写盘:region 序列化后交给后台线程落盘、从内存中移除,峰值内存取决于驻留 region 数,而不是世界总大小。
再配合 mimalloc 分配器和 90% CPU 上限的线程池,这些是普通桌面机也能完成中等规模城市生成的原因。
适用边界与展望
项目对自身规模边界说得比较直白:生成开始时,若换算面积超过 250 平方公里会打印警告——耗时与内存都会上一个量级,大查询还会给公共 OpenStreetMap 与高程服务带来真实负载。合理用法是先小区域验证效果,再逐步扩大。
功能完整度因平台而异:物品框招牌(路牌、象形图标、海报)只写入 Java 版世界,基岩版与 Luanti 输出的装饰细节更少;--mode提供完整生成、仅对象(平地面)、纯地形三档,便于单独测试地形或做对比。
演进方向可以从代码结构推断:元素流水线已按类型拆分,新增一类要素就是新增一个模块文件;投影抽象在 trait 后面,接入新投影不难。真要覆盖更大范围,路径是分布式生成或浏览器端服务——作者已推出对应的云端生成产品——而不是压榨单进程。
对想把"真实地理→方块世界"这件事做扎实的开发者,Arnis 给了一份可以通读的实现参考:问题切分干净,取舍写进代码注释,没有玄学。
【免费下载链接】arnisGenerate any location from the real world in Minecraft with a high level of detail.项目地址: https://gitcode.com/GitHub_Trending/ar/arnis
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考