#map #quake #geometry #gamedev #brushes

shalrath

一款锈迹斑斑、带刺、能感知热量的 Quake 地图解析器

11 个版本

0.2.6 2024 年 4 月 2 日
0.2.5 2022 年 1 月 15 日
0.2.1 2021 年 10 月 26 日
0.1.4 2021 年 10 月 24 日

#209解析器实现

Download history 4/week @ 2024-05-19 4/week @ 2024-05-26 4/week @ 2024-06-09 1/week @ 2024-06-16

每月 472 次下载
用于 shambler

MIT/Apache 许可协议

66KB
1.5K SLoC

Shalrath — License 最新版本 文档

一款锈迹斑斑、带刺、能感知热量的 quake 地图解析器

shalrath 是一个 Quake map 文件的 Rust 表示形式,[nom] 解析器和字符串序列化器。

它完全用 Rust 编写,并通过 #![forbid(unsafe_code)] 在整个代码库中强制使用安全代码。

Rust 表示形式

Rust 表示形式位于 repr 模块中,它是一组结构体,用于表示 map 文件的内容。

整体类结构(为简单起见省略了一些更具体的内部类型)看起来像这样

Map
└ Entity (1..*)
  ├ Properties (1..1)
  │ └ Property (0..*)
  └ Brushes (1..1)
    └ Brush (0..*)
      └ BrushPlane (4..*)

Entity 是一个可以包含 PropertyBrush 的游戏对象。

Property是存储为String的键值对。

Brush是通过一组TexturePlane(具有相关纹理映射数据的3D平面)的交集定义的凸形状。

在任何给定的map中,至少必须存在一个Entity(称为worldspawn)来表示其所有结构性的Brush。结构性的Brush是静态几何体,没有相关的行为。

在Quake术语中,通过为实体分配一个classname属性来赋予它们行为,该属性被游戏代码用来分配一个actor类,该类读取附加到对象的其他属性。

这些实体被分为两个类别

  • Point Entities是没有Brush但有EntityEntity
    • 这些用于表示玩家、敌人或物品拾取等演员。
  • Brush Entities是既有Entityclassname,又有BrushEntity
    • 这些用于表示移动门、电梯等特殊世界几何体。

但这只是为了提供背景信息,更多内容可以在地图文件规范中找到。

最终,你在将数据解析到Rust表示形式后做什么取决于你项目的需求。为此,命名字段的成员是公开的,通过Deref将集合包装器如PropertiesBrushes公开。

解析

map文件解析为AST的最简单方法是使用FromStr特质。

use shalrath::repr::*;

let map =
    "{\"classname\" \"worldspawn\"\n{\n( 0 1 2 ) ( 3 4 5 ) ( 6 7 8 ) TEXTURE 0 0 0 1 1\n}\n}"
        .parse::<Map>()
        .expect("Failed to parse map");

assert_eq!(
    map,
    Map::new(vec![Entity {
        properties: Properties::new(vec![Property {
            key: "classname".into(),
            value: "worldspawn".into()
        }]),
        brushes: Brushes::new(vec![Brush::new(vec![
            BrushPlane {
                plane: Triangle {
                    v0: Point {
                        x: 0.0,
                        y: 1.0,
                        z: 2.0
                    },
                    v1: Point {
                        x: 3.0,
                        y: 4.0,
                        z: 5.0
                    },
                    v2: Point {
                        x: 6.0,
                        y: 7.0,
                        z: 8.0
                    },
                },
                texture: "TEXTURE".into(),
                texture_offset: TextureOffset::Standard { u: 0.0, v: 0.0 },
                angle: 0.0,
                scale_x: 1.0,
                scale_y: 1.0,
                extension: Extension::Standard,
            }
        ])])
    }])
)

对于更低级别的替代方案,parser模块包含由FromStr实现使用的[nom]函数,可以用于将明文数据解析为单个AST结构。

其中,parse_map 是主要的入口点,相当于 str::parse::<Map>()

use shalrath::parser::repr::parse_map;

let map_string = include_str!("../test_data/abstract-test.map");
let (_, map_ast) = parse_map(map_string).expect("Failed to parse map");
println!("{:#?}", map_ast);

字符串序列化

Rust 表示可以序列化为基于文本的 map 表示形式,通过 DisplayToString 特性实现。

use shalrath::repr::Map;

let map_string = include_str!("../test_data/abstract-test.map");
let map_ast = map_string.parse::<Map>().expect("Failed to parse map file");
let serialized_map_string = map_ast.to_string();
println!("{}", serialized_map_string);

此外,将结果字符串重新解析为相应的抽象语法树(AST)是一个无损操作,并且作为 shalrath 集成测试的标准部分。

use shalrath::repr::Map;

let map_string = include_str!("../test_data/abstract-test.map");
let map_ast = map_string.parse::<Map>().expect("Failed to parse map file");
let serialized_map_string = map_ast.to_string();
let roundtrip_map_ast = serialized_map_string.parse::<Map>().expect("Failed to parse map file");
assert_eq!(map_ast, roundtrip_map_ast);

格式支持

Quake 1 基础地图格式存在多个变体,它们保留了相同的核心结构,但修改了刷平面编码方式。

shalrath 通过按 UV 格式(由 TextureOffset 枚举表示)进行分类来支持这些格式。

UV 格式 备注
标准 面根据最接近的世界 X/Y/Z 平面投影纹理。
Valve 面根据自定义的 U/V 轴投影纹理,允许对弯曲表面进行倾斜和更精确的纹理处理。

...以及刷平面扩展数据,由 Extension 枚举表示

刷平面扩展 备注
标准 刷平面不包含额外数据。
Hexen 2 刷平面包含一个额外的数值,其用途尚不清楚。
Quake 2 刷平面包含 content_flagssurface_flags 位掩码,以及一个浮点数 value
Daikatana 刷平面包含三个未知值,以及浮点数 RGB 值。

其他格式如 Quake 3Daikatana 存在,但实质上是上述格式的变体,并且解析器将透明地处理它们。

Serde 支持

对于需要从非 map 格式进行序列化和反序列化的情况,shalrath 包含 serde::Serializeserde::Deserialize 属性的所有 repr 模块类型。

可以通过将 serde 特性标志应用于 Cargo.toml 中的 shalrath 依赖项来启用这些功能。

流式传输

目前,shalrath 仅实现了期望完整输入数据集的 complete 解析器。

计划实现streaming实现,但目前仍在进行进一步研究。

依赖项

~1.2–2MB
~41K SLoC