体素气体模拟
模拟粒度:EBS
pV=nRT 不在方块级模拟气体,而是把世界切成 EBS(Extended Block Storage) 粒度——即 Minecraft 1.7.10 区块的 16×16×16 存储段。每个 EBS 对应原生层的一个句柄,由 EBSGasTracker(pvnrt/core/world/EBSGasTracker.java)跟踪。
| 类 | 职责 |
|---|---|
GasManager |
世界级入口,持有 NativeGasSimulator,提供 getVoxelStatus / getVoxelGas / getVoxelVolume / addGas / populate |
EBSGasTracker |
单个 EBS 的占用状态、遮挡掩码、patch 生成、存档读写 |
GasDataManager |
实现 ChunkAPI 的 DataManager + SubChunkDataManager,负责把气体数据写进区块 NBT |
EBSExt / WorldExt |
挂在原版 ExtendedBlockStorage / World 上的 @Unique 字段接口 |
HeightMap |
辅助结构,记录列高供 flood-fill 使用 |
GasManager 的常量 TICK_RATE = 20(pvnrt/core/world/GasManager.java:20)是固定的 20 tick 心跳。
Voxel 位打包
Voxel(pvnrt/rust/Voxel.java)把每个体素的状态压进一个 int,位布局定义在 VoxelLike(pvnrt/rust/VoxelLike.java:5-11):
| 位 | 掩码 | 含义 |
|---|---|---|
| 0-11 | 0b1111_1111_1111 |
气体数据指针(12 bit) |
| 12 | 0b1 << 12 |
是否持有气体指针 |
| 13 | 0b1 << 13 |
是否为世界大气体素 |
| 14-19 | 0b11_1111 << 14 |
遮挡掩码(6 bit,对应 6 个 ForgeDirection 面) |
| 20-23 | 0b1111 << 20 |
打包体积(4 bit) |
⚠️ 仓库
CLAUDE.md称体素数据「packed into 16-bit integers(status + occlusion + pointer fields)」,与源码不符——Voxel.data是 32 位int,且没有OCCLUDED/ACTIVE/IDLE/WORLD_ATMOSPHERE状态枚举。GasManager.getVoxelStatus()返回的是原生层format!("Voxel @ {x},{y},{z}: {voxel}")(gas-sim/src/jni.rs:156)拼出的调试字符串。
遮挡
EBSGasTracker 用 6 位掩码记录每个体素哪些面可传导气体。遮挡计算见 EBSGasTracker.java:266 起:原版 block.isOpaqueCube() 为真即判 FULLY_OCCLUDED(常量 0b11_1111,EBSGasTracker.java:29);FilledBlock 类方块按填充比例 <= 0.00001 也判全遮挡。GasAwareBlock(pvnrt/block/GasAwareBlock.java)是让方块参与气体遮挡判定的接口。
populate 流程
EBSGasTracker.populate(...) 逐体素判断是否需要把气体数据分配进原生模拟器:全遮挡的体素直接 patches.addPatch(x, y, z, false, FULLY_OCCLUDED, 0) 跳过(EBSGasTracker.java:203-204)。patch 通过 VoxelPatchBuffer 以 ByteBuffer 直接内存块传给 Rust 侧。另有 save() / load(byte[]) 用于 NBT 序列化。