UNPKG

@awayjs/stage

Version:
255 lines (207 loc) 6.33 kB
/** * Implement Turbulence for noise * Port from Ruffle/Rust * @see https://github.com/ruffle-rs/ruffle/blob/04d80e5e4e5a3023117e0410378fc623a8bfbbf5/core/src/bitmap/turbulence.rs */ const RAND_M: i64 = 2147483647; // 2**31 - 1 const RAND_A: i64 = 16807; // 7**5; primitive root of m const RAND_Q: i64 = 127773; // m / a const RAND_R: i64 = 2836; // m % a const B_SIZE: ui16 = 0x100; const BM: i32 = 0xff; const PERLIN_N: i32 = 0x1000; class SeededRandom { public seed: number = 0; constructor(seed: number) { if (seed <= 0) { seed = -(seed % (RAND_M - 1)) + 1; } if (seed > RAND_M - 1) { seed = RAND_M - 1; } this.seed = seed; } public next (): i16 { let result = RAND_A * (this.seed % RAND_Q) - RAND_R * (this.seed / RAND_Q | 0); if (result <= 0) { result += RAND_M; } return result; } } function curve (t: number): number { return t * t * (3. - 2. * t); } function lerp (t: number, a: number, b: number): number { return a + t * (b - a); } export interface StitchInfo { width: i32, // How much to subtract to wrap for stitching. height: i32, wrap_x: i32, // Minimum value to wrap. wrap_y: i32, } export class Turbulence { constructor( public gradient: Array<Array<[number, number]>>, public lattice_selector: any ) {} public noise2( color_channel: ui8, vec: [ui16, ui16], stitch_info?: StitchInfo ): number { const t = vec[0] + PERLIN_N; let bx0 = t | 0; let bx1 = bx0 + 1; const rx0 = t - (t | 0); const rx1 = rx0 - 1.0; const t2 = vec[1] + PERLIN_N; let by0 = t2 | 0; let by1 = by0 + 1; const ry0 = t2 - (t2 | 0); const ry1 = ry0 - 1.0; // If stitching, adjust lattice points accordingly. if (stitch_info) { if (bx0 >= stitch_info.wrap_x) { bx0 -= stitch_info.width; } if (bx1 >= stitch_info.wrap_x) { bx1 -= stitch_info.width; } if (by0 >= stitch_info.wrap_y) { by0 -= stitch_info.height; } if (by1 >= stitch_info.wrap_y) { by1 -= stitch_info.height; } } bx0 &= BM; bx1 &= BM; by0 &= BM; by1 &= BM; const i = this.lattice_selector[bx0]; const j = this.lattice_selector[bx1]; const b00 = this.lattice_selector[(i + by0)]; const b10 = this.lattice_selector[(j + by0)]; const b01 = this.lattice_selector[(i + by1)]; const b11 = this.lattice_selector[(j + by1)]; const sx = curve(rx0); const sy = curve(ry0); const q = this.gradient[color_channel][b00]; const u = rx0 * q[0] + ry0 * q[1]; const q2 = this.gradient[color_channel][b10]; const v = rx1 * q2[0] + ry0 * q2[1]; const a = lerp(sx, u, v); const q3 = this.gradient[color_channel][b01]; const u2 = rx0 * q3[0] + ry1 * q3[1]; const q4 = this.gradient[color_channel][b11]; const v2 = rx1 * q4[0] + ry1 * q4[1]; const b = lerp(sx, u2, v2); return lerp(sy, a, b); } public turbulence( color_channel: ui8, point: [number, number], base_freq: [number, number], num_octaves: ui8, fractal_sum: boolean, do_stitching: boolean, tile_pos: [number, number], tile_size: [number, number], octave_offsets: Array<[number, number]> = null, ): number { let stitch_info: StitchInfo = null; // Not stitching when None. // Adjust the base frequencies if necessary for stitching. if (do_stitching) { // When stitching tiled turbulence, the frequencies must be adjusted // so that the tile borders will be continuous. if (base_freq[0] !== 0.0) { const lo_freq = Math.floor(tile_size[0] * base_freq[0]) / tile_size[0]; const hi_freq = Math.ceil(tile_size[0] * base_freq[0]) / tile_size[0]; if (base_freq[0] / lo_freq < hi_freq / base_freq[0]) { base_freq[0] = lo_freq; } else { base_freq[0] = hi_freq; } } if (base_freq[1] !== 0.0) { const lo_freq = Math.floor(tile_size[1] * base_freq[1]) / tile_size[1]; const hi_freq = Math.ceil(tile_size[1] * base_freq[1]) / tile_size[1]; if (base_freq[1] / lo_freq < hi_freq / base_freq[1]) { base_freq[1] = lo_freq; } else { base_freq[1] = hi_freq; } } // Set up initial stitch values. const w = (tile_size[0] * base_freq[0] + 0.5) | 0; const h = (tile_size[1] * base_freq[1] + 0.5) | 0; stitch_info = { width: w, height: h, wrap_x: (tile_pos[0] * base_freq[0]) | 0 + PERLIN_N + w, wrap_y: (tile_pos[1] * base_freq[1]) | 0 + PERLIN_N + h, }; } let sum = 0.0; let ratio = 1.0; const nullOffset = [0,0]; for (let octave = 0; octave < num_octaves; octave++) { const offset = octave_offsets ? octave_offsets[octave] : nullOffset; const vec: [number, number] = [ (point[0] + offset[0]) * base_freq[0] * ratio, (point[1] + offset[1]) * base_freq[1] * ratio ]; const noise = this.noise2(color_channel, vec, stitch_info); sum += (fractal_sum ? noise : Math.abs(noise)) / ratio; ratio *= 2.0; if (stitch_info) { stitch_info.width *= 2; stitch_info.wrap_x = 2 * stitch_info.wrap_x - PERLIN_N; stitch_info.height *= 2; stitch_info.wrap_y = 2 * stitch_info.wrap_y - PERLIN_N; } } return sum; } public static fromSeed(seed: i64): Turbulence { let s: f64; const lattice_selector: Array<i32> = Array.from({ length: B_SIZE * 2 + 2 }, () => 0); const gradient: Array<Array<[number, number]>> = []; const rnd = new SeededRandom(seed); for (let k = 0; k < 4; k++) { gradient[k] = Array.from({ length: B_SIZE * 2 + 2 }, () => [0,0]); for (let i = 0; i < B_SIZE; i++) { lattice_selector[i] = i; for (let j = 0; j < 2; j++) { seed = rnd.next(); rnd.seed = seed; gradient[k][i][j] = (seed % (B_SIZE + B_SIZE) - B_SIZE) / B_SIZE; } s = Math.sqrt( gradient[k][i][0] * gradient[k][i][0] + gradient[k][i][1] * gradient[k][i][1], ); gradient[k][i][0] /= s; gradient[k][i][1] /= s; } } for (let i = B_SIZE - 1; i >= 0; i--) { const k = lattice_selector[i]; seed = rnd.next(); rnd.seed = seed; const j = seed % B_SIZE; lattice_selector[i] = lattice_selector[j]; lattice_selector[j] = k; } for (let i = 0; i < B_SIZE + 2; i++) { lattice_selector[B_SIZE + i] = lattice_selector[i]; for (let k = 0; k < 4; k++) { for (let j = 0; j < 2; j++) { gradient[k][B_SIZE + i][j] = gradient[k][i][j]; } } } return new Turbulence(gradient, lattice_selector); } }