createSnakeWithBounds static method

List<FreeHexagon> createSnakeWithBounds({
  1. required FreeHexagon start,
  2. required int count,
  3. required Rect bounds,
  4. double? size,
  5. double sizeProvider(
    1. int index
    )?,
  6. double gap = 0.0,
  7. String direction = 'right',
  8. bool clockwise = true,
})

创建带边界回折的蛇形排列

start 起始六边形(会被重新计算位置) count 六边形总数(不包括起始六边形) size 六边形大小(如果提供了 sizeProvider,此参数会被忽略) sizeProvider 为每个索引返回六边形大小的函数(索引从0开始) gap 间隙 bounds 边界矩形 direction 生成方向('up', 'down', 'left', 'right')

  • up: 向上生成,起始位置在底部中心
  • down: 向下生成,起始位置在顶部中心
  • left: 向左生成,起始位置在右侧中心
  • right: 向右生成,起始位置在左侧中心 clockwise 生成顺序:true=顺时针,false=逆时针
  • 决定第一个六边形连接第二个六边形的边

Implementation

static List<FreeHexagon> createSnakeWithBounds({
  required FreeHexagon start,
  required int count,
  required Rect bounds,
  double? size,
  double Function(int index)? sizeProvider,
  double gap = 0.0,
  String direction = 'right',
  bool clockwise = true,
}) {
  if (count <= 0) return [];

  // 获取六边形尺寸的辅助函数
  double getHexSize(int index) {
    if (sizeProvider != null) {
      return sizeProvider(index);
    }
    return size ?? start.size;
  }

  final firstHexSize = getHexSize(0);
  final result = <FreeHexagon>[];

  // 根据方向计算起始位置、扩展边界和初始边
  Offset startPosition;
  Rect effectiveBounds;
  int initialEdge;

  switch (direction) {
    case 'up':
      // 向上生成:起始位置在底部中心
      startPosition = Offset(
        bounds.left + bounds.width / 2,
        bounds.bottom - firstHexSize,
      );
      effectiveBounds = Rect.fromLTWH(
        bounds.left,
        -999999,
        bounds.width,
        999999 + bounds.bottom,
      );
      // 向上的初始边:顺时针=边0(右上),逆时针=边2(左上)
      initialEdge = clockwise ? 0 : 2;
      break;
    case 'down':
      // 向下生成:起始位置在顶部中心
      startPosition = Offset(
        bounds.left + bounds.width / 2,
        bounds.top + firstHexSize,
      );
      effectiveBounds = Rect.fromLTWH(
        bounds.left,
        bounds.top,
        bounds.width,
        999999,
      );
      // 向下的初始边:顺时针=边5(右下),逆时针=边3(左下)
      initialEdge = clockwise ? 5 : 3;
      break;
    case 'left':
      // 向左生成:起始位置在右侧中心
      startPosition = Offset(
        bounds.right - firstHexSize,
        bounds.top + bounds.height / 2,
      );
      effectiveBounds = Rect.fromLTWH(
        -999999,
        bounds.top,
        999999 + bounds.right,
        bounds.height,
      );
      // 向左的初始边:顺时针=边3(左下),逆时针=边2(左上)
      initialEdge = clockwise ? 3 : 2;
      break;
    case 'right':
    default:
      // 向右生成:起始位置在左侧中心
      startPosition = Offset(
        bounds.left + firstHexSize,
        bounds.top + bounds.height / 2,
      );
      effectiveBounds = Rect.fromLTWH(
        bounds.left,
        bounds.top,
        999999,
        bounds.height,
      );
      // 向右的初始边:顺时针=边5(右下),逆时针=边0(右上)
      initialEdge = clockwise ? 5 : 0;
      break;
  }

  // 使用计算出的起始位置创建新的起始六边形
  var currentHex = FreeHexagon(
    id: start.id,
    center: startPosition,
    size: firstHexSize,
    orientation: start.orientation,
  );

  // 将起始六边形添加到结果中
  result.add(currentHex);

  var currentDirection = initialEdge;

  for (var i = 0; i < count; i++) {
    // 获取下一个六边形的尺寸
    final nextHexSize = getHexSize(i + 1);

    // 直行:继续使用当前方向
    var useEdge = currentDirection;

    // 首先尝试直行
    var nextCenter = HexagonLayoutHelper.calculateAdjacentCenter(
      sourceHex: currentHex,
      sourceEdge: useEdge,
      targetSize: nextHexSize,
      gap: gap,
    );

    // 如果直行超出边界,尝试弯折
    if (!_isWithinBounds(nextCenter, nextHexSize, effectiveBounds)) {
      bool foundValidDirection = false;

      // 计算到各边界的距离,找出最近碰到的边界
      final distToLeft = currentHex.center.dx - effectiveBounds.left;
      final distToRight = effectiveBounds.right - currentHex.center.dx;
      final distToTop = currentHex.center.dy - effectiveBounds.top;
      final distToBottom = effectiveBounds.bottom - currentHex.center.dy;

      // 找到最近的边界(排除无限边界)
      double minDist = double.infinity;
      String nearestBoundary = '';

      if (distToLeft < minDist && distToLeft < 999999) {
        minDist = distToLeft;
        nearestBoundary = 'left';
      }
      if (distToRight < minDist && distToRight < 999999) {
        minDist = distToRight;
        nearestBoundary = 'right';
      }
      if (distToTop < minDist && distToTop < 999999) {
        minDist = distToTop;
        nearestBoundary = 'top';
      }
      if (distToBottom < minDist && distToBottom < 999999) {
        minDist = distToBottom;
        nearestBoundary = 'bottom';
      }

      // 根据碰到的边界类型决定弯折角度(光线反射原理)
      int bendOffset;
      if (nearestBoundary == 'left' || nearestBoundary == 'right') {
        // 碰到左右边界 → 水平反射 → 120度弯折
        bendOffset = 2;
      } else {
        // 碰到上下边界 → 垂直反射 → 60度弯折
        bendOffset = 1;
      }

      // 计算两个弯折候选方向
      final option1 = (useEdge + bendOffset) % 6; // 顺时针弯折
      final option2 = (useEdge - bendOffset + 6) % 6; // 逆时针弯折

      final center1 = HexagonLayoutHelper.calculateAdjacentCenter(
        sourceHex: currentHex,
        sourceEdge: option1,
        targetSize: nextHexSize,
        gap: gap,
      );

      final center2 = HexagonLayoutHelper.calculateAdjacentCenter(
        sourceHex: currentHex,
        sourceEdge: option2,
        targetSize: nextHexSize,
        gap: gap,
      );

      final inBounds1 = _isWithinBounds(
        center1,
        nextHexSize,
        effectiveBounds,
      );
      final inBounds2 = _isWithinBounds(
        center2,
        nextHexSize,
        effectiveBounds,
      );

      // 选择远离最近边界的方向
      if (inBounds1 && inBounds2) {
        // 两个方向都在边界内,选择离最近边界更远的那个
        double dist1ToNearBoundary, dist2ToNearBoundary;

        if (nearestBoundary == 'left') {
          dist1ToNearBoundary = center1.dx - effectiveBounds.left;
          dist2ToNearBoundary = center2.dx - effectiveBounds.left;
        } else if (nearestBoundary == 'right') {
          dist1ToNearBoundary = effectiveBounds.right - center1.dx;
          dist2ToNearBoundary = effectiveBounds.right - center2.dx;
        } else if (nearestBoundary == 'top') {
          dist1ToNearBoundary = center1.dy - effectiveBounds.top;
          dist2ToNearBoundary = center2.dy - effectiveBounds.top;
        } else {
          // bottom
          dist1ToNearBoundary = effectiveBounds.bottom - center1.dy;
          dist2ToNearBoundary = effectiveBounds.bottom - center2.dy;
        }

        // 选择离最近边界更远的方向
        if (dist1ToNearBoundary >= dist2ToNearBoundary) {
          useEdge = option1;
          nextCenter = center1;
        } else {
          useEdge = option2;
          nextCenter = center2;
        }
        foundValidDirection = true;
      } else if (inBounds1) {
        // 只有方向1在边界内
        useEdge = option1;
        nextCenter = center1;
        foundValidDirection = true;
      } else if (inBounds2) {
        // 只有方向2在边界内
        useEdge = option2;
        nextCenter = center2;
        foundValidDirection = true;
      }

      // 如果弯折方向都不行,尝试其他方向
      if (!foundValidDirection) {
        for (var offset = 1; offset <= 5; offset++) {
          if (offset == 3) continue; // 跳过对边(180度反向)
          if (offset == bendOffset || offset == (6 - bendOffset))
            continue; // 跳过已尝试的弯折方向

          final testOption = (useEdge + offset) % 6;
          final testCenter = HexagonLayoutHelper.calculateAdjacentCenter(
            sourceHex: currentHex,
            sourceEdge: testOption,
            targetSize: nextHexSize,
            gap: gap,
          );

          if (_isWithinBounds(testCenter, nextHexSize, effectiveBounds)) {
            useEdge = testOption;
            nextCenter = testCenter;
            foundValidDirection = true;
            break;
          }
        }
      }

      // 如果所有方向都超界,停止生成
      if (!foundValidDirection) {
        break;
      }
    }

    currentDirection = useEdge;

    final nextHex = FreeHexagon(
      id: '${start.id}_bounded_$i',
      center: nextCenter,
      size: nextHexSize,
      orientation: start.orientation,
    );

    result.add(nextHex);
    currentHex = nextHex;
  }
  return result;
}