更新植物系统、地形和水系统

This commit is contained in:
Rocky
2025-12-02 22:19:09 +08:00
parent 8cf5fb7397
commit bd0b00c37e
3 changed files with 2081 additions and 63 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1512,7 +1512,11 @@ export const generateTerrain = async (
desertContext, desertContext,
isDesertScene, isDesertScene,
sceneType: sceneConfig?.name, sceneType: sceneConfig?.name,
heightMap heightMap,
// 山地场景专用数据
mountainStreamMap: mountainStreamMap ?? undefined,
mountainRockContext: mountainRockContext ?? undefined,
terrainHeightMap: terrainHeightMap ?? undefined,
}; };
const newPlantVoxels = await generateVegetation(vegContext); const newPlantVoxels = await generateVegetation(vegContext);

View File

@@ -1286,8 +1286,8 @@ export const generateMountainStream = (
if (isBlocked || isUphill) { if (isBlocked || isUphill) {
// 考虑生成支流 // 考虑生成支流
if (tributaryCount < MAX_TRIBUTARIES && stepsSinceLastTributary >= TRIBUTARY_COOLDOWN) { if (tributaryCount < MAX_TRIBUTARIES && stepsSinceLastTributary >= TRIBUTARY_COOLDOWN) {
// 【修改】支流宽度:主流的40%最小3微体素更窄 // 【修改】支流宽度:主流一致6微体素
const tributaryWidth = Math.max(3, Math.floor(currentWidth * 0.4)); const tributaryWidth = 6;
// 【新增】随机选择支流方向(左或右) // 【新增】随机选择支流方向(左或右)
const isLeftTributary = rng() > 0.5; const isLeftTributary = rng() > 0.5;
@@ -1304,8 +1304,8 @@ export const generateMountainStream = (
{ lx: currentPos.lx, lz: currentPos.lz }, { lx: currentPos.lx, lz: currentPos.lz },
currentHeight, currentHeight,
tributaryWidth, tributaryWidth,
tributaryDir, // 【修改】直接传入确定的方向 tributaryDir,
6 + Math.floor(rng() * 5), // 【修改】长度6-10格稍微短一点 visitedPositions, // 传入主流已访问位置,避免支流与主流相交
grid, grid,
getHeight, getHeight,
mapSize mapSize
@@ -1459,80 +1459,264 @@ export const generateMountainStream = (
// 6. 生成支流函数(需要在主循环之前定义) // 6. 生成支流函数(需要在主循环之前定义)
/** /**
* 生成支流正交90度方向短路径 * 生成支流 - 增强版:支持转向,延伸到边界或盆地
*
* 特性:
* 1. 遇到障碍/上坡时可以转向(类似主流)
* 2. 一直延伸直到到达地图边界或陷入盆地
* 3. 避免与主流路径相交
*/ */
function generateTributary( function generateTributary(
startPos: { lx: number; lz: number }, startPos: { lx: number; lz: number },
startHeight: number, startHeight: number,
tributaryWidth: number, tributaryWidth: number,
tributaryDirection: Direction, // 【修改】直接传入方向,不再随机 tributaryDirection: Direction,
maxLength: number, mainPathVisited: Set<string>, // 主流已访问的位置,用于避免相交
grid: Uint8Array, grid: Uint8Array,
getHeight: (lx: number, lz: number) => number, getHeight: (lx: number, lz: number) => number,
mapSize: number mapSize: number
): RiverNode[] { ): RiverNode[] {
const tributary: 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 currentWidth = tributaryWidth;
let currentHeight = startHeight; let currentDir = tributaryDirection;
let currentWidth = tributaryWidth; // 【修改】直接使用传入的宽度,不再取最小值
let accumulatedDrop = 0; let accumulatedDrop = 0;
// 【修复】先添加分叉点(起点),确保与主流连接 const MAX_TRIBUTARY_STEPS = mapSize * 2; // 防止无限循环
tributary.push({
lx: startPos.lx,
lz: startPos.lz,
height: startHeight,
width: currentWidth,
direction: tributaryDirection,
accumulatedDrop: 0,
});
for (let step = 0; step < maxLength; step++) { // 【修复】不再把分叉点作为支流起点,而是从分叉点的下一格开始
const dirVec = getDirectionVector(tributaryDirection); // 这样可以避免1) 分叉点被重复渲染 2) 在分叉点生成不合理的瀑布
// 计算支流的第一个实际位置(分叉点的下一格)
const dirVec = getDirectionVector(tributaryDirection);
const firstLx = startPos.lx + dirVec.dx;
const firstLz = startPos.lz + dirVec.dz;
// 边界检查
if (firstLx < 0 || firstLx >= mapSize || firstLz < 0 || firstLz >= mapSize) {
console.log(`[支流生成] 支流第一格越界,丢弃`);
return [];
}
// 障碍物检查
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 (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 nextLx = currentPos.lx + dirVec.dx;
const nextLz = currentPos.lz + dirVec.dz; const nextLz = currentPos.lz + dirVec.dz;
// 边界检查 // 边界检查 - 到达边界是成功结束
if (nextLx < 0 || nextLx >= mapSize || nextLz < 0 || nextLz >= mapSize) { if (nextLx < 0 || nextLx >= mapSize || nextLz < 0 || nextLz >= mapSize) {
console.log(`[支流生成] 支流到达地图边界 (${currentPos.lx}, ${currentPos.lz}),长度: ${tributary.length}`);
break; break;
} }
// 检查是否会与主流相交
const wouldIntersectMain = mainPathVisited.has(posKey(nextLx, nextLz));
// 障碍物检查 // 障碍物检查
const nextGrid = grid[nextLz * mapSize + nextLx]; const isBlocked = grid[nextLz * mapSize + nextLx] === 1;
if (nextGrid === 1) {
break; // 遇到石头停止
}
// 高度检查
const nextHeight = getHeight(nextLx, nextLz); const nextHeight = getHeight(nextLx, nextLz);
const isUphill = nextHeight > currentHeight;
// 上坡停止 // 访问检查
if (nextHeight > currentHeight) { const alreadyVisited = tributaryVisited.has(posKey(nextLx, nextLz));
break;
// 碰撞处理:遇到障碍、上坡、主流路径或已访问
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 }; currentPos = { lx: nextLx, lz: nextLz };
tributaryVisited.add(posKey(nextLx, nextLz));
// 高度下降处理(支流宽度固定) // 高度下降处理
if (nextHeight < currentHeight) { if (nextHeight < currentHeight) {
const drop = currentHeight - nextHeight; const drop = currentHeight - nextHeight;
accumulatedDrop += drop; accumulatedDrop += drop;
currentHeight = nextHeight; currentHeight = nextHeight;
} }
// 【修改】支流宽度保持固定 // 记录节点
tributary.push({ tributary.push({
lx: currentPos.lx, lx: currentPos.lx,
lz: currentPos.lz, lz: currentPos.lz,
height: currentHeight, height: currentHeight,
width: currentWidth, // 使用固定宽度 width: currentWidth,
direction: tributaryDirection, direction: currentDir,
accumulatedDrop: accumulatedDrop, 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) { if (tributary.length < 3) {
console.log(`[支流生成] 支流过短 (${tributary.length} < 3),丢弃`); console.log(`[支流生成] 支流过短 (${tributary.length} < 3),丢弃`);
return []; return [];