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 在 解析器实现
每月 472 次下载
用于 shambler
66KB
1.5K SLoC
Shalrath —

一款锈迹斑斑、带刺、能感知热量的 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
是一个可以包含 Property
和 Brush
的游戏对象。
Brush
是通过一组TexturePlane
(具有相关纹理映射数据的3D平面)的交集定义的凸形状。
在任何给定的map
中,至少必须存在一个Entity
(称为worldspawn
)来表示其所有结构性的Brush
。结构性的Brush
是静态几何体,没有相关的行为。
在Quake术语中,通过为实体分配一个classname
属性来赋予它们行为,该属性被游戏代码用来分配一个actor类,该类读取附加到对象的其他属性。
这些实体被分为两个类别
Point Entities
是没有Brush
但有Entity
的Entity
。- 这些用于表示玩家、敌人或物品拾取等演员。
Brush Entities
是既有Entity
的classname
,又有Brush
的Entity
。- 这些用于表示移动门、电梯等特殊世界几何体。
但这只是为了提供背景信息,更多内容可以在地图文件规范中找到。
最终,你在将数据解析到Rust表示形式后做什么取决于你项目的需求。为此,命名字段的成员是公开的,通过Deref
将集合包装器如Properties
和Brushes
公开。
解析
将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
表示形式,通过 Display
或 ToString
特性实现。
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_flags 和 surface_flags 位掩码,以及一个浮点数 value 。 |
Daikatana | 刷平面包含三个未知值,以及浮点数 RGB 值。 |
其他格式如 Quake 3
和 Daikatana
存在,但实质上是上述格式的变体,并且解析器将透明地处理它们。
Serde 支持
对于需要从非 map
格式进行序列化和反序列化的情况,shalrath
包含 serde::Serialize
和 serde::Deserialize
属性的所有 repr
模块类型。
可以通过将 serde
特性标志应用于 Cargo.toml
中的 shalrath
依赖项来启用这些功能。
流式传输
目前,shalrath
仅实现了期望完整输入数据集的 complete
解析器。
计划实现streaming
实现,但目前仍在进行进一步研究。
依赖项
~1.2–2MB
~41K SLoC