feat(procgen): 添加程序化生成工具包 (#331)

- 添加噪声函数 (Perlin, Simplex, Worley, FBM)
- 添加种子随机数生成器 (SeededRandom)
- 添加加权随机选择和洗牌工具
- 添加蓝图节点 (SampleNoise2D, SeededRandom, WeightedPick 等)
This commit is contained in:
YHH
2025-12-25 14:33:19 +08:00
committed by GitHub
parent 25936c19e9
commit 275124b66c
18 changed files with 2108 additions and 0 deletions

View File

@@ -0,0 +1,214 @@
/**
* @zh 分形布朗运动 (FBM)
* @en Fractal Brownian Motion (FBM)
*
* @zh 通过叠加多层噪声创建更自然的效果
* @en Creates more natural effects by layering multiple noise octaves
*/
/**
* @zh 噪声函数接口
* @en Noise function interface
*/
export interface INoise2D {
noise2D(x: number, y: number): number;
}
/**
* @zh 噪声函数接口 (3D)
* @en Noise function interface (3D)
*/
export interface INoise3D {
noise3D(x: number, y: number, z: number): number;
}
/**
* @zh FBM 配置
* @en FBM configuration
*/
export interface FBMConfig {
/**
* @zh 八度数(层数)
* @en Number of octaves (layers)
*/
octaves: number;
/**
* @zh 频率倍增因子
* @en Frequency multiplier per octave
*/
lacunarity: number;
/**
* @zh 振幅衰减因子
* @en Amplitude decay per octave
*/
persistence: number;
/**
* @zh 初始频率
* @en Initial frequency
*/
frequency: number;
/**
* @zh 初始振幅
* @en Initial amplitude
*/
amplitude: number;
}
const DEFAULT_CONFIG: FBMConfig = {
octaves: 6,
lacunarity: 2.0,
persistence: 0.5,
frequency: 1.0,
amplitude: 1.0
};
/**
* @zh FBM 噪声生成器
* @en FBM noise generator
*/
export class FBM {
private readonly _noise: INoise2D & Partial<INoise3D>;
private readonly _config: FBMConfig;
/**
* @zh 创建 FBM 噪声生成器
* @en Create FBM noise generator
*
* @param noise - @zh 基础噪声函数 @en Base noise function
* @param config - @zh 配置 @en Configuration
*/
constructor(noise: INoise2D & Partial<INoise3D>, config?: Partial<FBMConfig>) {
this._noise = noise;
this._config = { ...DEFAULT_CONFIG, ...config };
}
/**
* @zh 2D FBM 噪声
* @en 2D FBM noise
*
* @param x - @zh X 坐标 @en X coordinate
* @param y - @zh Y 坐标 @en Y coordinate
* @returns @zh 噪声值 @en Noise value
*/
noise2D(x: number, y: number): number {
let value = 0;
let frequency = this._config.frequency;
let amplitude = this._config.amplitude;
let maxValue = 0;
for (let i = 0; i < this._config.octaves; i++) {
value += this._noise.noise2D(x * frequency, y * frequency) * amplitude;
maxValue += amplitude;
amplitude *= this._config.persistence;
frequency *= this._config.lacunarity;
}
return value / maxValue;
}
/**
* @zh 3D FBM 噪声
* @en 3D FBM noise
*
* @param x - @zh X 坐标 @en X coordinate
* @param y - @zh Y 坐标 @en Y coordinate
* @param z - @zh Z 坐标 @en Z coordinate
* @returns @zh 噪声值 @en Noise value
*/
noise3D(x: number, y: number, z: number): number {
if (!this._noise.noise3D) {
throw new Error('Base noise does not support 3D');
}
let value = 0;
let frequency = this._config.frequency;
let amplitude = this._config.amplitude;
let maxValue = 0;
for (let i = 0; i < this._config.octaves; i++) {
value += this._noise.noise3D(x * frequency, y * frequency, z * frequency) * amplitude;
maxValue += amplitude;
amplitude *= this._config.persistence;
frequency *= this._config.lacunarity;
}
return value / maxValue;
}
/**
* @zh Ridged FBM脊状适合山脉
* @en Ridged FBM (suitable for mountains)
*/
ridged2D(x: number, y: number): number {
let value = 0;
let frequency = this._config.frequency;
let amplitude = this._config.amplitude;
let weight = 1;
for (let i = 0; i < this._config.octaves; i++) {
let signal = this._noise.noise2D(x * frequency, y * frequency);
signal = 1 - Math.abs(signal);
signal *= signal;
signal *= weight;
weight = Math.max(0, Math.min(1, signal * 2));
value += signal * amplitude;
frequency *= this._config.lacunarity;
amplitude *= this._config.persistence;
}
return value;
}
/**
* @zh Turbulence湍流使用绝对值
* @en Turbulence (using absolute value)
*/
turbulence2D(x: number, y: number): number {
let value = 0;
let frequency = this._config.frequency;
let amplitude = this._config.amplitude;
let maxValue = 0;
for (let i = 0; i < this._config.octaves; i++) {
value += Math.abs(this._noise.noise2D(x * frequency, y * frequency)) * amplitude;
maxValue += amplitude;
amplitude *= this._config.persistence;
frequency *= this._config.lacunarity;
}
return value / maxValue;
}
/**
* @zh Billowed膨胀适合云朵
* @en Billowed (suitable for clouds)
*/
billowed2D(x: number, y: number): number {
let value = 0;
let frequency = this._config.frequency;
let amplitude = this._config.amplitude;
let maxValue = 0;
for (let i = 0; i < this._config.octaves; i++) {
const n = this._noise.noise2D(x * frequency, y * frequency);
value += (Math.abs(n) * 2 - 1) * amplitude;
maxValue += amplitude;
amplitude *= this._config.persistence;
frequency *= this._config.lacunarity;
}
return value / maxValue;
}
}
/**
* @zh 创建 FBM 噪声生成器
* @en Create FBM noise generator
*/
export function createFBM(noise: INoise2D & Partial<INoise3D>, config?: Partial<FBMConfig>): FBM {
return new FBM(noise, config);
}