createSnakeWithBounds static method
创建带边界回折的蛇形排列
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;
}