更新植物系统、地形和水系统
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1512,7 +1512,11 @@ export const generateTerrain = async (
|
||||
desertContext,
|
||||
isDesertScene,
|
||||
sceneType: sceneConfig?.name,
|
||||
heightMap
|
||||
heightMap,
|
||||
// 山地场景专用数据
|
||||
mountainStreamMap: mountainStreamMap ?? undefined,
|
||||
mountainRockContext: mountainRockContext ?? undefined,
|
||||
terrainHeightMap: terrainHeightMap ?? undefined,
|
||||
};
|
||||
|
||||
const newPlantVoxels = await generateVegetation(vegContext);
|
||||
|
||||
@@ -1286,8 +1286,8 @@ export const generateMountainStream = (
|
||||
if (isBlocked || isUphill) {
|
||||
// 考虑生成支流
|
||||
if (tributaryCount < MAX_TRIBUTARIES && stepsSinceLastTributary >= TRIBUTARY_COOLDOWN) {
|
||||
// 【修改】支流宽度:主流的40%,最小3微体素(更窄)
|
||||
const tributaryWidth = Math.max(3, Math.floor(currentWidth * 0.4));
|
||||
// 【修改】支流宽度:与主流一致(6微体素)
|
||||
const tributaryWidth = 6;
|
||||
|
||||
// 【新增】随机选择支流方向(左或右)
|
||||
const isLeftTributary = rng() > 0.5;
|
||||
@@ -1304,8 +1304,8 @@ export const generateMountainStream = (
|
||||
{ lx: currentPos.lx, lz: currentPos.lz },
|
||||
currentHeight,
|
||||
tributaryWidth,
|
||||
tributaryDir, // 【修改】直接传入确定的方向
|
||||
6 + Math.floor(rng() * 5), // 【修改】长度:6-10格(稍微短一点)
|
||||
tributaryDir,
|
||||
visitedPositions, // 传入主流已访问位置,避免支流与主流相交
|
||||
grid,
|
||||
getHeight,
|
||||
mapSize
|
||||
@@ -1459,80 +1459,264 @@ export const generateMountainStream = (
|
||||
|
||||
// 6. 生成支流函数(需要在主循环之前定义)
|
||||
/**
|
||||
* 生成支流(正交90度方向,短路径)
|
||||
* 生成支流 - 增强版:支持转向,延伸到边界或盆地
|
||||
*
|
||||
* 特性:
|
||||
* 1. 遇到障碍/上坡时可以转向(类似主流)
|
||||
* 2. 一直延伸直到到达地图边界或陷入盆地
|
||||
* 3. 避免与主流路径相交
|
||||
*/
|
||||
function generateTributary(
|
||||
startPos: { lx: number; lz: number },
|
||||
startHeight: number,
|
||||
tributaryWidth: number,
|
||||
tributaryDirection: Direction, // 【修改】直接传入方向,不再随机
|
||||
maxLength: number,
|
||||
tributaryDirection: Direction,
|
||||
mainPathVisited: Set<string>, // 主流已访问的位置,用于避免相交
|
||||
grid: Uint8Array,
|
||||
getHeight: (lx: number, lz: number) => number,
|
||||
mapSize: number
|
||||
): RiverNode[] {
|
||||
const tributary: RiverNode[] = [];
|
||||
const tributaryVisited = new Set<string>(); // 支流自己的访问记录
|
||||
const posKey = (lx: number, lz: number) => `${lx}|${lz}`;
|
||||
|
||||
let currentPos = { lx: startPos.lx, lz: startPos.lz };
|
||||
let currentHeight = startHeight;
|
||||
let currentWidth = tributaryWidth; // 【修改】直接使用传入的宽度,不再取最小值
|
||||
let currentWidth = tributaryWidth;
|
||||
let currentDir = tributaryDirection;
|
||||
let accumulatedDrop = 0;
|
||||
|
||||
// 【修复】先添加分叉点(起点),确保与主流连接
|
||||
tributary.push({
|
||||
lx: startPos.lx,
|
||||
lz: startPos.lz,
|
||||
height: startHeight,
|
||||
width: currentWidth,
|
||||
direction: tributaryDirection,
|
||||
accumulatedDrop: 0,
|
||||
});
|
||||
const MAX_TRIBUTARY_STEPS = mapSize * 2; // 防止无限循环
|
||||
|
||||
for (let step = 0; step < maxLength; step++) {
|
||||
// 【修复】不再把分叉点作为支流起点,而是从分叉点的下一格开始
|
||||
// 这样可以避免:1) 分叉点被重复渲染 2) 在分叉点生成不合理的瀑布
|
||||
|
||||
// 计算支流的第一个实际位置(分叉点的下一格)
|
||||
const dirVec = getDirectionVector(tributaryDirection);
|
||||
const nextLx = currentPos.lx + dirVec.dx;
|
||||
const nextLz = currentPos.lz + dirVec.dz;
|
||||
const firstLx = startPos.lx + dirVec.dx;
|
||||
const firstLz = startPos.lz + dirVec.dz;
|
||||
|
||||
// 边界检查
|
||||
if (nextLx < 0 || nextLx >= mapSize || nextLz < 0 || nextLz >= mapSize) {
|
||||
break;
|
||||
if (firstLx < 0 || firstLx >= mapSize || firstLz < 0 || firstLz >= mapSize) {
|
||||
console.log(`[支流生成] 支流第一格越界,丢弃`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 障碍物检查
|
||||
const nextGrid = grid[nextLz * mapSize + nextLx];
|
||||
if (nextGrid === 1) {
|
||||
break; // 遇到石头停止
|
||||
if (grid[firstLz * mapSize + firstLx] === 1) {
|
||||
console.log(`[支流生成] 支流第一格有障碍物,丢弃`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 与主流相交检查
|
||||
if (mainPathVisited.has(posKey(firstLx, firstLz))) {
|
||||
console.log(`[支流生成] 支流第一格与主流重叠,丢弃`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 获取第一格的高度
|
||||
const firstHeight = getHeight(firstLx, firstLz);
|
||||
|
||||
// 上坡检查
|
||||
if (firstHeight > startHeight) {
|
||||
console.log(`[支流生成] 支流第一格上坡,丢弃`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 初始化当前位置为第一格
|
||||
let currentPos = { lx: firstLx, lz: firstLz };
|
||||
let currentHeight = firstHeight;
|
||||
|
||||
// 计算初始累计下降
|
||||
if (firstHeight < startHeight) {
|
||||
accumulatedDrop = startHeight - firstHeight;
|
||||
}
|
||||
|
||||
// 添加第一个节点(不是分叉点,而是分叉点的下一格)
|
||||
tributary.push({
|
||||
lx: firstLx,
|
||||
lz: firstLz,
|
||||
height: currentHeight,
|
||||
width: currentWidth,
|
||||
direction: currentDir,
|
||||
accumulatedDrop: accumulatedDrop,
|
||||
});
|
||||
tributaryVisited.add(posKey(firstLx, firstLz));
|
||||
// 也标记分叉点为已访问,防止支流回头
|
||||
tributaryVisited.add(posKey(startPos.lx, startPos.lz));
|
||||
|
||||
// 辅助函数:支流的转向选择(类似主流,但增加避开主流路径的检查)
|
||||
const chooseTributaryTurn = (
|
||||
pos: { lx: number; lz: number },
|
||||
curDir: Direction,
|
||||
curH: number
|
||||
): Direction | null => {
|
||||
const options: Array<{ dir: Direction; score: number }> = [];
|
||||
const leftDir = rotate90(curDir, false);
|
||||
const rightDir = rotate90(curDir, true);
|
||||
|
||||
for (const testDir of [leftDir, rightDir]) {
|
||||
const vec = getDirectionVector(testDir);
|
||||
const nextLx = pos.lx + vec.dx;
|
||||
const nextLz = pos.lz + vec.dz;
|
||||
|
||||
// 越界检查 - 边界外意味着到达边缘,这是好的
|
||||
if (nextLx < 0 || nextLx >= mapSize || nextLz < 0 || nextLz >= mapSize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 障碍物检查
|
||||
if (grid[nextLz * mapSize + nextLx] === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 避免与主流相交(关键!)
|
||||
if (mainPathVisited.has(posKey(nextLx, nextLz))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 避免支流自身循环
|
||||
if (tributaryVisited.has(posKey(nextLx, nextLz))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const nextHeight = getHeight(nextLx, nextLz);
|
||||
const heightDiff = curH - nextHeight;
|
||||
|
||||
// 上坡停止
|
||||
if (nextHeight > currentHeight) {
|
||||
// 禁止上坡
|
||||
if (heightDiff < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let score = 0;
|
||||
// 下坡优先
|
||||
if (heightDiff > 0) {
|
||||
score += heightDiff * 20;
|
||||
} else {
|
||||
score += 5;
|
||||
}
|
||||
// 添加随机性
|
||||
score += rng() * 5;
|
||||
|
||||
options.push({ dir: testDir, score });
|
||||
}
|
||||
|
||||
if (options.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
options.sort((a, b) => b.score - a.score);
|
||||
return options[0].dir;
|
||||
};
|
||||
|
||||
// 辅助函数:检查是否在盆地中
|
||||
const isTributaryInBasin = (pos: { lx: number; lz: number }, curH: number): boolean => {
|
||||
const checkDirs = [
|
||||
{ dx: 1, dz: 0 },
|
||||
{ dx: -1, dz: 0 },
|
||||
{ dx: 0, dz: 1 },
|
||||
{ dx: 0, dz: -1 },
|
||||
];
|
||||
|
||||
for (const dir of checkDirs) {
|
||||
const nx = pos.lx + dir.dx;
|
||||
const nz = pos.lz + dir.dz;
|
||||
|
||||
// 边界外不算障碍
|
||||
if (nx < 0 || nx >= mapSize || nz < 0 || nz >= mapSize) {
|
||||
return false; // 可以流出边界,不是盆地
|
||||
}
|
||||
|
||||
// 如果有一个方向是下坡或同高且无障碍,则不是盆地
|
||||
if (grid[nz * mapSize + nx] === 0) {
|
||||
const neighborH = getHeight(nx, nz);
|
||||
if (neighborH <= curH && !mainPathVisited.has(posKey(nx, nz)) && !tributaryVisited.has(posKey(nx, nz))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true; // 四周都是高地或障碍或已访问,是盆地
|
||||
};
|
||||
|
||||
// 主循环:延伸支流
|
||||
for (let step = 0; step < MAX_TRIBUTARY_STEPS; step++) {
|
||||
const dirVec = getDirectionVector(currentDir);
|
||||
const nextLx = currentPos.lx + dirVec.dx;
|
||||
const nextLz = currentPos.lz + dirVec.dz;
|
||||
|
||||
// 边界检查 - 到达边界是成功结束
|
||||
if (nextLx < 0 || nextLx >= mapSize || nextLz < 0 || nextLz >= mapSize) {
|
||||
console.log(`[支流生成] 支流到达地图边界 (${currentPos.lx}, ${currentPos.lz}),长度: ${tributary.length}`);
|
||||
break;
|
||||
}
|
||||
|
||||
// 更新位置
|
||||
currentPos = { lx: nextLx, lz: nextLz };
|
||||
// 检查是否会与主流相交
|
||||
const wouldIntersectMain = mainPathVisited.has(posKey(nextLx, nextLz));
|
||||
|
||||
// 高度下降处理(支流宽度固定)
|
||||
// 障碍物检查
|
||||
const isBlocked = grid[nextLz * mapSize + nextLx] === 1;
|
||||
|
||||
// 高度检查
|
||||
const nextHeight = getHeight(nextLx, nextLz);
|
||||
const isUphill = nextHeight > currentHeight;
|
||||
|
||||
// 访问检查
|
||||
const alreadyVisited = tributaryVisited.has(posKey(nextLx, nextLz));
|
||||
|
||||
// 碰撞处理:遇到障碍、上坡、主流路径或已访问
|
||||
if (isBlocked || isUphill || wouldIntersectMain || alreadyVisited) {
|
||||
// 尝试转向
|
||||
const newDir = chooseTributaryTurn(currentPos, currentDir, currentHeight);
|
||||
|
||||
if (newDir === null) {
|
||||
// 无路可走,检查是否在盆地中
|
||||
if (isTributaryInBasin(currentPos, currentHeight)) {
|
||||
console.log(`[支流生成] 支流陷入盆地 (${currentPos.lx}, ${currentPos.lz}),长度: ${tributary.length}`);
|
||||
} else {
|
||||
console.log(`[支流生成] 支流无路可走 (${currentPos.lx}, ${currentPos.lz}),长度: ${tributary.length}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
currentDir = newDir;
|
||||
continue; // 转向后重新尝试
|
||||
}
|
||||
|
||||
// 可以前进
|
||||
currentPos = { lx: nextLx, lz: nextLz };
|
||||
tributaryVisited.add(posKey(nextLx, nextLz));
|
||||
|
||||
// 高度下降处理
|
||||
if (nextHeight < currentHeight) {
|
||||
const drop = currentHeight - nextHeight;
|
||||
accumulatedDrop += drop;
|
||||
currentHeight = nextHeight;
|
||||
}
|
||||
|
||||
// 【修改】支流宽度保持固定
|
||||
// 记录节点
|
||||
tributary.push({
|
||||
lx: currentPos.lx,
|
||||
lz: currentPos.lz,
|
||||
height: currentHeight,
|
||||
width: currentWidth, // 使用固定宽度
|
||||
direction: tributaryDirection,
|
||||
width: currentWidth,
|
||||
direction: currentDir,
|
||||
accumulatedDrop: accumulatedDrop,
|
||||
});
|
||||
|
||||
// 检查是否到达边缘
|
||||
const atEdge = (
|
||||
currentPos.lx === 0 ||
|
||||
currentPos.lx === mapSize - 1 ||
|
||||
currentPos.lz === 0 ||
|
||||
currentPos.lz === mapSize - 1
|
||||
);
|
||||
|
||||
if (atEdge) {
|
||||
console.log(`[支流生成] 支流到达地图边缘 (${currentPos.lx}, ${currentPos.lz}),长度: ${tributary.length}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 【新增】如果支流太短(< 3个节点),返回空数组
|
||||
// 如果支流太短(< 3个节点),返回空数组
|
||||
if (tributary.length < 3) {
|
||||
console.log(`[支流生成] 支流过短 (${tributary.length} < 3),丢弃`);
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user