Trait TileSetVirtual

Source
pub trait TileSetVirtual<EdgeData>
where EdgeData: Clone + PartialEq + Debug,
{ // Required methods fn build_tile_set(&mut self) -> Result<(), GridError>; fn judge_possibility( &self, neighbor_possibilities: &[Vec<TileId>], candidate: TileId, ) -> bool; fn get_tile(&self, tile_id: TileId) -> Option<&Tile<EdgeData>>; fn get_tile_count(&self) -> usize; fn get_all_tile_ids(&self) -> Vec<TileId> ; }
Expand description

瓷砖集虚函数特性 - 仅包含C++的两个虚函数

这个trait专门提取了原C++代码中的两个纯虚函数,实现了与原C++设计的完全对应:

  • virtual void buildTileSet() = 0;
  • virtual bool judgePossibility(...) = 0;

§设计理念

§职责分离

将虚函数逻辑从数据管理中分离出来,带来以下好处:

  1. 清晰的接口:只包含需要自定义实现的方法
  2. 类型安全:编译时确保所有必要方法都被实现
  3. 测试友好:可以独立模拟和测试虚函数逻辑

§与原C++的一致性

C++虚函数Rust trait方法功能
buildTileSet()build_tile_set()构建瓷砖集
judgePossibility(...)judge_possibility(...)判断瓷砖可能性

§泛型参数

EdgeData 类型参数表示瓷砖边的数据类型,需要满足:

  • Clone:支持复制操作
  • PartialEq:支持相等性比较
  • Debug:支持调试输出

§实现示例

use rlwfc::{TileSetVirtual, TileSet, TileId, Tile, GridError};

struct SimpleTileSet {
    tiles: TileSet<&'static str>,
}

impl TileSetVirtual<&'static str> for SimpleTileSet {
    fn build_tile_set(&mut self) -> Result<(), GridError> {
        // 清空现有瓷砖
        self.tiles.clear();
         
        // 添加具体的瓷砖
        self.tiles.add_tile(vec!["A", "B", "C", "D"], 10);
        self.tiles.add_tile(vec!["B", "A", "D", "C"], 15);
        Ok(())
    }

    fn judge_possibility(
        &self,
        neighbor_possibilities: &[Vec<TileId>],
        candidate: TileId
    ) -> bool {
        // 实现具体的约束判断逻辑
        if let Some(_tile) = self.tiles.get_tile(candidate) {
            // 检查候选瓷砖是否与邻居兼容
            // 这里应该实现具体的兼容性检查逻辑
            !neighbor_possibilities.is_empty()
        } else {
            false
        }
    }
     
    fn get_tile(&self, tile_id: TileId) -> Option<&Tile<&'static str>> {
        self.tiles.get_tile(tile_id)
    }
     
    fn get_tile_count(&self) -> usize {
        self.tiles.get_tile_count()
    }
     
    fn get_all_tile_ids(&self) -> Vec<TileId> {
        self.tiles.get_all_tile_ids()
    }
}

Required Methods§

Source

fn build_tile_set(&mut self) -> Result<(), GridError>

构建瓷砖集 - 对应C++的buildTileSet()虚函数

这个方法负责初始化和填充瓷砖集合。具体的实现由各种不同的瓷砖集类型决定。

§实现要求

实现者应该在此方法中:

  1. 清理现有状态:清空或重置瓷砖集合
  2. 创建瓷砖:添加所有需要的瓷砖到集合中
  3. 设置属性:配置每个瓷砖的权重、边数据等
  4. 验证完整性:确保瓷砖集合的一致性和完整性
§调用时机

这个方法通常在以下时机被调用:

  • WFC系统初始化时
  • 重新开始新的生成过程时
  • 动态改变瓷砖集配置时
§示例实现
fn build_tile_set(&mut self) -> Result<(), rlwfc::GridError> {
    // 1. 清理现有瓷砖
    self.tiles.clear();
     
    // 2. 添加基础瓷砖
    self.tiles.add_tile(vec!["grass", "grass", "grass", "grass"], 50);
    self.tiles.add_tile(vec!["water", "water", "water", "water"], 30);
     
    // 3. 添加过渡瓷砖
    self.tiles.add_tile(vec!["grass", "water", "grass", "water"], 20);
     
    // 4. 可选:添加验证逻辑
    debug_assert!(!self.tiles.is_empty());
    Ok(())
}
Source

fn judge_possibility( &self, neighbor_possibilities: &[Vec<TileId>], candidate: TileId, ) -> bool

判断瓷砖可能性 - 对应C++的judgePossibility()虚函数

这是WFC算法的核心约束判断方法。它决定了在给定邻居约束的情况下, 某个候选瓷砖是否可以放置在当前位置。

§⚠️ 重要:利用边数据顺序约定

由于瓷砖的边数据严格按照 neighbors() 返回顺序排列, 本方法可以直接通过索引访问对应方向的边数据,实现高效的兼容性检查。

§索引到方向的直接映射
neighbor_possibilities[0] <-> candidate_tile.edges[0] (北方向)
neighbor_possibilities[1] <-> candidate_tile.edges[1] (西方向)
neighbor_possibilities[2] <-> candidate_tile.edges[2] (南方向)
neighbor_possibilities[3] <-> candidate_tile.edges[3] (东方向)
§高效实现模式
fn judge_possibility(
    &self,
    neighbor_possibilities: &[Vec<TileId>],
    candidate: TileId
) -> bool {
    let Some(candidate_tile) = self.get_tile(candidate) else {
        return false;
    };
     
    for (direction_index, neighbor_tiles) in neighbor_possibilities.iter().enumerate() {
        // 🎯 直接获取候选瓷砖在该方向的边数据
        let candidate_edge = &candidate_tile.edges[direction_index];
         
        // 检查与该方向所有可能邻居的兼容性
        let is_compatible = neighbor_tiles.iter().any(|&neighbor_id| {
            if let Some(neighbor_tile) = self.get_tile(neighbor_id) {
                // 获取邻居瓷砖相对方向的边数据
                let opposite_index = match direction_index {
                    0 => 2,  // 北 ↔ 南
                    1 => 3,  // 西 ↔ 东
                    2 => 0,  // 南 ↔ 北  
                    3 => 1,  // 东 ↔ 西
                    _ => return false,
                };
                let neighbor_edge = &neighbor_tile.edges[opposite_index];
                 
                // 边兼容性检查(具体规则由应用定义)
                candidate_edge == neighbor_edge
            } else {
                false
            }
        });
         
        if !is_compatible {
            return false;
        }
    }
    true
}
§性能优势

通过边数据顺序约定,该方法获得了显著的性能优势:

  1. 零成本索引映射:无需运行时的方向转换或查找表
  2. O(1) 边数据访问:直接数组索引,最高效的访问方式
  3. 缓存友好:连续的内存访问模式,提高CPU缓存命中率
  4. 编译时优化:编译器可以更好地优化索引访问代码
§参数
  • neighbor_possibilities - 邻居单元格的可能瓷砖列表数组

    • 每个元素是一个邻居的可能瓷砖ID列表
    • 数组的顺序对应方向顺序:[北, 西, 南, 东]
    • 空列表表示该方向没有邻居或邻居未确定
  • candidate - 候选瓷砖的ID

§返回值
  • true - 该瓷砖在当前邻居约束下是可能的
  • false - 该瓷砖与邻居约束冲突,不能放置
§错误情况

实现者应该处理以下错误情况:

  • 候选瓷砖ID无效(不存在对应的瓷砖)
  • 邻居瓷砖ID无效
  • 边数据索引越界(瓷砖边数量不足)
§算法逻辑

典型的实现流程:

  1. 验证候选瓷砖:确认候选瓷砖存在且有效
  2. 遍历方向约束:检查每个方向的邻居约束
  3. 获取边数据:直接通过索引获取对应方向的边数据
  4. 兼容性检查:验证候选瓷砖的边与邻居瓷砖的边是否兼容
  5. 返回结果:所有方向都兼容则返回true,否则返回false
§性能考虑

这个方法在WFC算法中会被频繁调用,因此性能很重要:

  • 考虑缓存计算结果
  • 优先检查最容易失败的约束
  • 使用快速的边比较算法
  • 利用边数据顺序约定避免额外的映射开销
Source

fn get_tile(&self, tile_id: TileId) -> Option<&Tile<EdgeData>>

获取指定ID的瓷砖

Source

fn get_tile_count(&self) -> usize

获取瓷砖总数

Source

fn get_all_tile_ids(&self) -> Vec<TileId>

获取所有瓷砖ID列表

Implementors§