From 4222dfde4a78956af5915218d9e72e286b1078e0 Mon Sep 17 00:00:00 2001 From: Credence Date: Fri, 24 Jan 2025 03:52:22 +0100 Subject: [PATCH] client: make client biome impl equivalent to contracts (#2660) * client: make client biome impl equivalent to contracts * client: improve biome impl --- client/apps/game/src/three/managers/biome.ts | 160 ++++---- .../apps/game/src/utils/biome/fixed-point.ts | 352 ++++++++++++++++++ .../game/src/utils/biome/simplex-noise.ts | 157 ++++++++ client/apps/game/src/utils/biome/vec3.ts | 68 ++++ client/apps/game/src/utils/biome/vec4.ts | 71 ++++ 5 files changed, 741 insertions(+), 67 deletions(-) create mode 100644 client/apps/game/src/utils/biome/fixed-point.ts create mode 100644 client/apps/game/src/utils/biome/simplex-noise.ts create mode 100644 client/apps/game/src/utils/biome/vec3.ts create mode 100644 client/apps/game/src/utils/biome/vec4.ts diff --git a/client/apps/game/src/three/managers/biome.ts b/client/apps/game/src/three/managers/biome.ts index 701265245..887228b1d 100644 --- a/client/apps/game/src/three/managers/biome.ts +++ b/client/apps/game/src/three/managers/biome.ts @@ -1,28 +1,36 @@ -import { snoise } from "@dojoengine/utils"; +import { Fixed, FixedTrait } from "@/utils/biome/fixed-point"; +import { noise as snoise } from "@/utils/biome/simplex-noise"; +import { Vec3 } from "@/utils/biome/vec3"; import * as THREE from "three"; -const MAP_AMPLITUDE = 60; -const MOISTURE_OCTAVE = 2; -const ELEVATION_OCTAVES = [1, 0.25, 0.1]; -const ELEVATION_OCTAVES_SUM = ELEVATION_OCTAVES.reduce((a, b) => a + b, 0); - -export type BiomeType = - | "DeepOcean" - | "Ocean" - | "Beach" - | "Scorched" - | "Bare" - | "Tundra" - | "Snow" - | "TemperateDesert" - | "Shrubland" - | "Taiga" - | "Grassland" - | "TemperateDeciduousForest" - | "TemperateRainForest" - | "SubtropicalDesert" - | "TropicalSeasonalForest" - | "TropicalRainForest"; +const MAP_AMPLITUDE = FixedTrait.fromInt(60n); +const MOISTURE_OCTAVE = FixedTrait.fromInt(2n); +const ELEVATION_OCTAVES = [ + FixedTrait.fromInt(1n), // 1 + FixedTrait.fromRatio(1n, 4n), // 0.25 + FixedTrait.fromRatio(1n, 10n) // 0.1 +]; +const ELEVATION_OCTAVES_SUM = ELEVATION_OCTAVES.reduce((a, b) => a.add(b), FixedTrait.ZERO); + + +export enum BiomeType { + DeepOcean = "DeepOcean", + Ocean = "Ocean", + Beach = "Beach", + Scorched = "Scorched", + Bare = "Bare", + Tundra = "Tundra", + Snow = "Snow", + TemperateDesert = "TemperateDesert", + Shrubland = "Shrubland", + Taiga = "Taiga", + Grassland = "Grassland", + TemperateDeciduousForest = "TemperateDeciduousForest", + TemperateRainForest = "TemperateRainForest", + SubtropicalDesert = "SubtropicalDesert", + TropicalSeasonalForest = "TropicalSeasonalForest", + TropicalRainForest = "TropicalRainForest" +} export const BIOME_COLORS: Record = { DeepOcean: new THREE.Color("#4a6b63"), @@ -44,12 +52,12 @@ export const BIOME_COLORS: Record = { }; const LEVEL = { - DEEP_OCEAN: 0.25, - OCEAN: 0.5, - SAND: 0.53, - FOREST: 0.6, - DESERT: 0.72, - MOUNTAIN: 0.8, + DEEP_OCEAN: FixedTrait.fromRatio(25n, 100n), // 0.25 + OCEAN: FixedTrait.fromRatio(50n, 100n), // 0.5 + SAND: FixedTrait.fromRatio(53n, 100n), // 0.53 + FOREST: FixedTrait.fromRatio(60n, 100n), // 0.6 + DESERT: FixedTrait.fromRatio(72n, 100n), // 0.72 + MOUNTAIN: FixedTrait.fromRatio(80n, 100n), // 0.8 }; export class Biome { @@ -64,52 +72,70 @@ export class Biome { private calculateElevation( col: number, row: number, - mapAmplitude: number, - octaves: number[], - octavesSum: number, - ): number { - let elevation = 0; + mapAmplitude: Fixed, + octaves: Fixed[], + octavesSum: Fixed, + ): Fixed { + let elevation = FixedTrait.ZERO; + let _100 = FixedTrait.fromInt(100n); + let _2 = FixedTrait.fromInt(2n); for (const octave of octaves) { - const x = col / octave / mapAmplitude; - const z = row / octave / mapAmplitude; - const noise = ((snoise([x, 0, z]) + 1) * 100) / 2; - elevation += octave * Math.floor(noise); + let x = FixedTrait.fromInt(BigInt(col)).div(octave).div(mapAmplitude); + let z = FixedTrait.fromInt(BigInt(row)).div(octave).div(mapAmplitude); + + let sn = snoise(Vec3.new(x, FixedTrait.ZERO, z)); + const noise = sn.add(FixedTrait.ONE).mul(_100).div(_2); + elevation = elevation.add(octave.mul(noise.floor())); } - elevation = elevation / octavesSum; - return elevation / 100; + + return elevation.div(octavesSum).div(FixedTrait.fromInt(100n)); } - private calculateMoisture(col: number, row: number, mapAmplitude: number, moistureOctave: number): number { - const moistureX = (moistureOctave * col) / mapAmplitude; - const moistureZ = (moistureOctave * row) / mapAmplitude; - const noise = ((snoise([moistureX, 0, moistureZ]) + 1) * 100) / 2; - return Math.floor(noise) / 100; + + private calculateMoisture(col: number, row: number, mapAmplitude: Fixed, moistureOctave: Fixed): Fixed { + const moistureX = moistureOctave.mul(FixedTrait.fromInt(BigInt(col))).div(mapAmplitude); + const moistureZ = moistureOctave.mul(FixedTrait.fromInt(BigInt(row))).div(mapAmplitude); + const noise = snoise(Vec3.new(moistureX, FixedTrait.ZERO, moistureZ)).add(FixedTrait.ONE).mul(FixedTrait.fromInt(100n)).div(FixedTrait.fromInt(2n)); + return FixedTrait.floor(noise).div(FixedTrait.fromInt(100n)); } - private determineBiome(elevation: number, moisture: number, level: typeof LEVEL): BiomeType { - if (elevation < level.DEEP_OCEAN) return "DeepOcean"; - if (elevation < level.OCEAN) return "Ocean"; - if (elevation < level.SAND) return "Beach"; - if (elevation > level.MOUNTAIN) { - if (moisture < 0.1) return "Scorched"; - if (moisture < 0.4) return "Bare"; - if (moisture < 0.5) return "Tundra"; - return "Snow"; + private determineBiome(elevation: Fixed, moisture: Fixed, level: typeof LEVEL): BiomeType { + if (elevation.value < level.DEEP_OCEAN.value) return BiomeType.DeepOcean; + if (elevation.value < level.OCEAN.value) return BiomeType.Ocean; + if (elevation.value < level.SAND.value) return BiomeType.Beach; + + if (elevation.value > level.MOUNTAIN.value) { + if (moisture.value < FixedTrait.fromRatio(10n, 100n).value) return BiomeType.Scorched; + if (moisture.value < FixedTrait.fromRatio(40n, 100n).value) return BiomeType.Bare; + if (moisture.value < FixedTrait.fromRatio(50n, 100n).value) return BiomeType.Tundra; + return BiomeType.Snow; } - if (elevation > level.DESERT) { - if (moisture < 0.33) return "TemperateDesert"; - if (moisture < 0.66) return "Shrubland"; - return "Taiga"; + if (elevation.value > level.DESERT.value) { + if (moisture.value < FixedTrait.fromRatio(33n, 100n).value) return BiomeType.TemperateDesert; + if (moisture.value < FixedTrait.fromRatio(66n, 100n).value) return BiomeType.Shrubland; + return BiomeType.Taiga; } - if (elevation > level.FOREST) { - if (moisture < 0.16) return "TemperateDesert"; - if (moisture < 0.5) return "Grassland"; - if (moisture < 0.83) return "TemperateDeciduousForest"; - return "TemperateRainForest"; + if (elevation.value > level.FOREST.value) { + if (moisture.value < FixedTrait.fromRatio(16n, 100n).value) return BiomeType.TemperateDesert; + if (moisture.value < FixedTrait.fromRatio(50n, 100n).value) return BiomeType.Grassland; + if (moisture.value < FixedTrait.fromRatio(83n, 100n).value) return BiomeType.TemperateDeciduousForest; + return BiomeType.TemperateRainForest; } - if (moisture < 0.16) return "SubtropicalDesert"; - if (moisture < 0.33) return "Grassland"; - if (moisture < 0.66) return "TropicalSeasonalForest"; - return "TropicalRainForest"; + if (moisture.value < FixedTrait.fromRatio(16n, 100n).value) return BiomeType.SubtropicalDesert; + if (moisture.value < FixedTrait.fromRatio(33n, 100n).value) return BiomeType.Grassland; + if (moisture.value < FixedTrait.fromRatio(66n, 100n).value) return BiomeType.TropicalSeasonalForest; + return BiomeType.TropicalRainForest; } } + +// function testBiomeGeneration() { +// const biome = new Biome(); +// const start = 5000871265127650; +// const end = 5000871265127678; +// for (let i = start; i <= end; i++) { +// const result = biome.getBiome(i - 5, i + 10); +// console.log(`biome for ${i - 5} ${i + 10} is ${result} \n`); +// } +// } + +// testBiomeGeneration(); diff --git a/client/apps/game/src/utils/biome/fixed-point.ts b/client/apps/game/src/utils/biome/fixed-point.ts new file mode 100644 index 000000000..876c73504 --- /dev/null +++ b/client/apps/game/src/utils/biome/fixed-point.ts @@ -0,0 +1,352 @@ +/** + * TypeScript port of ABDK Math 64.64 Library plus modifications + * Original: https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol + * Original Copyright © 2019 by ABDK Consulting + * Original Author: Mikhail Vladimirov + * + * Modified by: Credence + * + * Modifications: + * - mul and div are handled differently from the original library, which may affect the precision. + */ + +// Constants +const MIN_64x64 = BigInt("0x80000000000000000000000000000000") * BigInt(-1); +const MAX_64x64 = BigInt("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); +const ONE_64x64 = BigInt(1) << BigInt(64); + +export class Fixed { + public value: bigint; + + constructor(value: bigint) { + this.value = value; + } + + + mul(other: Fixed): Fixed { + return FixedTrait.mul(this, other); + } + + div(other: Fixed): Fixed { + return FixedTrait.div(this, other); + } + + add(other: Fixed): Fixed { + return FixedTrait.add(this, other); + } + + sub(other: Fixed): Fixed { + return FixedTrait.sub(this, other); + } + + abs(): Fixed { + return FixedTrait.abs(this); + } + + sqrt(): Fixed { + return FixedTrait.sqrt(this); + } + + floor(): Fixed { + return FixedTrait.floor(this); + } + + mod(other: Fixed): Fixed { + return FixedTrait.mod(this, other); + } + + rem(other: Fixed): Fixed { + return FixedTrait.rem(this, other); + } + + neg(): Fixed { + return FixedTrait.neg(this); + } +} + +export class FixedTrait { + static ONE_64x64 = ONE_64x64; + + static ONE = new Fixed(ONE_64x64); + static ZERO = new Fixed(0n); + + + /** + * Convert a ratio to a fixed point number + */ + static fromRatio(numerator: bigint, denominator: bigint): Fixed { + return FixedTrait.divi(FixedTrait.fromInt(numerator), FixedTrait.fromInt(denominator)); + } + + /** + * Convert signed integer to 64.64 fixed point + */ + static fromInt(x: bigint): Fixed { + if (x < -0x8000000000000000n || x > 0x7FFFFFFFFFFFFFFFn) { + throw new Error("Input out of bounds"); + } + return new Fixed(x << 64n); + } + + /** + * Convert 64.64 fixed point to integer (rounding down) + */ + static toInt(x: Fixed): Fixed { + return new Fixed(x.value >> 64n); + } + + /** + * Convert unsigned integer to 64.64 fixed point + */ + static fromUInt(x: bigint): Fixed { + if (x > 0x7FFFFFFFFFFFFFFFn) { + throw new Error("Input out of bounds"); + } + return new Fixed(x << 64n); + } + + /** + * Convert 64.64 fixed point to unsigned integer (rounding down) + */ + static toUInt(x: Fixed): bigint { + if (x.value < 0n) { + throw new Error("Input must be positive"); + } + return x.value >> 64n; + } + + /** + * Add two 64.64 fixed point numbers + */ + static add(x: Fixed, y: Fixed): Fixed { + const result = x.value + y.value; + if (result < MIN_64x64 || result > MAX_64x64) { + throw new Error("Overflow add"); + } + return new Fixed(result); + } + + /** + * Subtract two 64.64 fixed point numbers + */ + static sub(x: Fixed, y: Fixed): Fixed { + const result = x.value - y.value; + if (result < MIN_64x64 || result > MAX_64x64) { + throw new Error("Overflow sub"); + } + return new Fixed(result); + } + + /** + * Multiply two 64.64 fixed point numbers (rounding down) + * Handles sign separately from magnitude for better precision + * + * Note: This is a replica of the way it is handled in the cubit starknet library + * e.g of difference: + * + * ABDKMath64x64: -3952873730080618200n * 24233599860844537324 = 5192914255895257994 + * cubit: -3952873730080618200n * 24233599860844537324 = -5192914255895257993 + */ + static mul(x: Fixed, y: Fixed): Fixed { + // Extract signs + const xNegative = x.value < 0n; + const yNegative = y.value < 0n; + + // Work with absolute values + const xAbs = xNegative ? -x.value : x.value; + const yAbs = yNegative ? -y.value : y.value; + + // Perform multiplication and scaling + const result = (xAbs * yAbs) >> 64n; + + // Apply combined sign + const finalResult = (xNegative !== yNegative) ? -result : result; + + if (finalResult < MIN_64x64 || finalResult > MAX_64x64) { + throw new Error("Overflow mul"); + } + return new Fixed(finalResult); + } + + /** + * Divide two 64.64 fixed point numbers (rounding down) + * Handles sign separately from magnitude for better precision + * + * Note: This is a modified version of the original div function. + * It handles overflow differently, which may affect the precision. + * + * This is a replica of the way it is handled in the cubit starknet library + * e.g of difference: + */ + static div(x: Fixed, y: Fixed): Fixed { + if (y.value === 0n) { + throw new Error("Division by zero"); + } + + // Extract signs + const xNegative = x.value < 0n; + const yNegative = y.value < 0n; + + // Work with absolute values + const xAbs = xNegative ? -x.value : x.value; + const yAbs = yNegative ? -y.value : y.value; + + // Perform division with scaling + const result = (xAbs << 64n) / yAbs; + + // Apply combined sign + const finalResult = (xNegative !== yNegative) ? -result : result; + + if (finalResult < MIN_64x64 || finalResult > MAX_64x64) { + throw new Error("Overflow div"); + } + return new Fixed(finalResult); + } + + static divi (x: Fixed, y: Fixed): Fixed { + if (y.value === 0n) { + throw new Error("Division by zero"); + } + + let negativeResult = false; + if (x.value < 0n) { + x.value = -x.value; // We rely on overflow behavior here + negativeResult = true; + } + if (y.value < 0n) { + y.value = -y.value; // We rely on overflow behavior here + negativeResult = !negativeResult; + } + const absoluteResult = FixedTrait.divuu(x, y); + if (negativeResult) { + if (absoluteResult.value <= 0x80000000000000000000000000000000n) { + return new Fixed(-absoluteResult.value); // We rely on overflow behavior here + } else { + throw new Error("Overflow divi"); + } + } + return absoluteResult; + } + + static divuu (x: Fixed, y: Fixed): Fixed { + if (y.value === 0n) { + throw new Error("Division by zero"); + } + + let result; + + if (x.value <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn) { + result = (x.value << 64n) / y.value; + } + else { + let msb = 192n; + let xc = x.value >> 192n; + if (xc >= 0x100000000n) { xc >>= 32n; msb += 32n; } + if (xc >= 0x10000n) { xc >>= 16n; msb += 16n; } + if (xc >= 0x100n) { xc >>= 8n; msb += 8n; } + if (xc >= 0x10n) { xc >>= 4n; msb += 4n; } + if (xc >= 0x4n) { xc >>= 2n; msb += 2n; } + if (xc >= 0x2n) msb += 1n; // No need to shift xc anymore + + result = (x.value << 255n - msb) / ((y.value - 1n >> msb - 191n) + 1n); + if (result > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn) { + throw new Error("Overflow divuu"); + } + + let hi = result * (y.value >> 128n); + let lo = result * (y.value & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn); + + let xh = x.value >> 192n; + let xl = x.value << 64n; + + if (xl < lo) xh -= 1n; + xl -= lo; // We rely on overflow behavior here + lo = hi << 128n; + if (xl < lo) xh -= 1n; + xl -= lo; // We rely on overflow behavior here + + result += xh == hi >> 128n ? xl / y.value : 1n; + } + + if (result > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn) { + throw new Error("Overflow divuu"); + } + return new Fixed(result); + } + + static divu (x: Fixed, y: Fixed): Fixed { + if (y.value === 0n) { + throw new Error("Division by zero"); + } + + const result = FixedTrait.divuu(x, y); + if (result.value > MAX_64x64) { + throw new Error("Overflow divu"); + } + return result; + } + + + /** + * Calculate absolute value + */ + static abs(x: Fixed): Fixed { + if (x.value === MIN_64x64) { + throw new Error("Overflow abs"); + } + return x.value < 0n ? new Fixed(-x.value) : x; + } + + /** + * Calculate square root (rounding down) + */ + static sqrt(x: Fixed): Fixed { + if (x.value < 0n) { + throw new Error("Input must be positive"); + } + + if (x.value === 0n) return new Fixed(0n); + + // Initial estimate using integer square root + let r = 1n; + let xx = x.value; + + // Binary search for the square root + if (xx >= (1n << 128n)) { xx >>= 128n; r <<= 64n; } + if (xx >= (1n << 64n)) { xx >>= 64n; r <<= 32n; } + if (xx >= (1n << 32n)) { xx >>= 32n; r <<= 16n; } + if (xx >= (1n << 16n)) { xx >>= 16n; r <<= 8n; } + if (xx >= (1n << 8n)) { xx >>= 8n; r <<= 4n; } + if (xx >= (1n << 4n)) { xx >>= 4n; r <<= 2n; } + if (xx >= (1n << 2n)) { r <<= 1n; } + + // Newton's method iterations + r = (r + x.value / r) >> 1n; + r = (r + x.value / r) >> 1n; + r = (r + x.value / r) >> 1n; + r = (r + x.value / r) >> 1n; + r = (r + x.value / r) >> 1n; + r = (r + x.value / r) >> 1n; + r = (r + x.value / r) >> 1n; + + const r1 = FixedTrait.div(x, new Fixed(r)); + return r < r1.value ? new Fixed(r) : r1; + } + + static floor(a: Fixed): Fixed { + return new Fixed((a.value >> 64n) * FixedTrait.ONE_64x64); + } + + static mod(a: Fixed, b: Fixed): Fixed { + return new Fixed(a.value % b.value); + } + + static rem(a: Fixed, b: Fixed): Fixed { + return new Fixed(a.value % b.value); + } + + static neg(a: Fixed): Fixed { + return new Fixed(-a.value); + } + +} diff --git a/client/apps/game/src/utils/biome/simplex-noise.ts b/client/apps/game/src/utils/biome/simplex-noise.ts new file mode 100644 index 000000000..99da62738 --- /dev/null +++ b/client/apps/game/src/utils/biome/simplex-noise.ts @@ -0,0 +1,157 @@ +import { Fixed, FixedTrait } from "./fixed-point"; +import { Vec3 } from "./vec3"; +import { Vec4 } from "./vec4"; + + + +function permute(x: Vec4): Vec4 { + let v34 = Vec4.splat(new Fixed(627189298506124754944n)); + let v1 = Vec4.splat(FixedTrait.ONE); + let v289 = Vec4.splat(new Fixed(5331109037302060417024n)); + return x.mul(v34).add(v1).mul(x).mod(v289); +} + +function taylor_inv_sqrt(r: Vec4): Vec4 { + let v1: Vec4 = Vec4.splat(new Fixed(33072114398950993631n)); // 1.79284291400159 + let v2: Vec4 = Vec4.splat(new Fixed(15748625904262413056n)); // 0.85373472095314 + return v1.minus(v2.mul(r)); +} + + + +function step(edge: Fixed, x: Fixed): Fixed { + if (x.value < edge.value) { + return FixedTrait.ZERO; + } else { + return FixedTrait.ONE; + } +} + + +function min(a: Fixed, b: Fixed): Fixed { + return a.value < b.value ? a : b; +} + +function max(a: Fixed, b: Fixed): Fixed { + return a.value > b.value ? a : b; +} + +export function noise(v: Vec3): Fixed { + let zero = new Fixed(0n); + let half = new Fixed(9223372036854775808n); // 0.5 + let one = FixedTrait.ONE; + + let Cx = new Fixed(3074457345618258602n); // 1 / 6 + let Cy = new Fixed(6148914691236517205n); // 1 / 3 + + // First corner + let i = v.add(Vec3.splat(v.dot(Vec3.splat(Cy)))).floor(); + let x0 = v.minus(i).add(Vec3.splat(i.dot(Vec3.splat(Cx)))); + + + // Other corners + let g = Vec3.new(step(x0.y, x0.x), step(x0.z, x0.y), step(x0.x, x0.z)); + let l = Vec3.splat(one).minus(g); + let i1 = Vec3.new(min(g.x, l.z), min(g.y, l.x), min(g.z, l.y)); + let i2 = Vec3.new(max(g.x, l.z), max(g.y, l.x), max(g.z, l.y)); + + let x1 = Vec3.new(x0.x.sub(i1.x).add(Cx), x0.y.sub(i1.y).add(Cx), x0.z.sub(i1.z).add(Cx)); + let x2 = Vec3.new(x0.x.sub(i2.x).add(Cy), x0.y.sub(i2.y).add(Cy), x0.z.sub(i2.z).add(Cy)); + let x3 = Vec3.new(x0.x.sub(half), x0.y.sub(half), x0.z.sub(half)); + + // Permutations + i = i.remScaler(new Fixed(5331109037302060417024n)); // 289 + let _p1 = permute(Vec4.new(i.z.add(zero), i.z.add(i1.z), i.z.add(i2.z), i.z.add(one))); + let _p2 = permute( + Vec4.new( + _p1.x.add(i.y).add(zero), _p1.y.add(i.y).add(i1.y), _p1.z.add(i.y).add(i2.y), _p1.w.add(i.y).add(one) + ) + ); + let p = permute( + Vec4.new( + _p2.x.add(i.x).add(zero), _p2.y.add(i.x).add(i1.x), _p2.z.add(i.x).add(i2.x), _p2.w.add(i.x).add(one) + ) + ); + + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + let ns_x = new Fixed(5270498306774157605n); // 2 / 7 + let ns_y = new Fixed(-17129119497016012214n); // -13 / 14 + let ns_z = new Fixed(2635249153387078803n); // 1 / 7 + + let j = p.remScaler(new Fixed(903890459611768029184n)); // 49 + let x_ = (j.mulScalar(ns_z)).floor(); + let y_ = (j.minus(x_.mulScalar(new Fixed(129127208515966861312n)))).floor(); // 7 + + let x = x_.mulScalar(ns_x).addScalar(ns_y); + let y = y_.mulScalar(ns_x).addScalar(ns_y); + let h = Vec4.splat(one).minus(x.abs()).minus(y.abs()); + + let b0 = Vec4.new(x.x, x.y, y.x, y.y); + let b1 = Vec4.new(x.z, x.w, y.z, y.w); + + // vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; + // vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; + let s0 = b0.floor().mulScalar(new Fixed(36893488147419103232n)).addScalar(one); + let s1 = b1.floor().mulScalar(new Fixed(36893488147419103232n)).addScalar(one); + + + let sh = Vec4.new( + step(h.x, zero).neg(), + step(h.y, zero).neg(), + step(h.z, zero).neg(), + step(h.w, zero).neg() + ); + + let a0 = Vec4.new( + b0.x.add(s0.x.mul(sh.x)), b0.z.add(s0.z.mul(sh.x)), b0.y.add(s0.y.mul(sh.y)), b0.w.add(s0.w.mul(sh.y)) + ); + let a1 = Vec4.new( + b1.x.add(s1.x.mul(sh.z)), b1.z.add(s1.z.mul(sh.z)), b1.y.add(s1.y.mul(sh.w)), b1.w.add(s1.w.mul(sh.w)) + ); + + + let p0 = Vec3.new(a0.x, a0.y, h.x); + let p1 = Vec3.new(a0.z, a0.w, h.y); + let p2 = Vec3.new(a1.x, a1.y, h.z); + let p3 = Vec3.new(a1.z, a1.w, h.w); + + + let norm = taylor_inv_sqrt(Vec4.new(p0.dot(p0), p1.dot(p1), p2.dot(p2), p3.dot(p3))); + p0 = Vec3.new(p0.x.mul(norm.x), p0.y.mul(norm.x), p0.z.mul(norm.x)); + p1 = Vec3.new(p1.x.mul(norm.y), p1.y.mul(norm.y), p1.z.mul(norm.y)); + p2 = Vec3.new(p2.x.mul(norm.z), p2.y.mul(norm.z), p2.z.mul(norm.z)); + p3 = Vec3.new(p3.x.mul(norm.w), p3.y.mul(norm.w), p3.z.mul(norm.w)); + + let m = Vec4.new( + max(half.sub(x0.dot(x0)), zero), + max(half.sub(x1.dot(x1)), zero), + max(half.sub(x2.dot(x2)), zero), + max(half.sub(x3.dot(x3)), zero) + ); + + // m = (m * m) * (m * m); + m = m.mul(m).mul(m.mul(m)); + + + let _105 = FixedTrait.fromInt(105n); + return _105.mul(m.dot(Vec4.new(p0.dot(x0), p1.dot(x1), p2.dot(x2), p3.dot(x3)))); + +} + + +export function noise_octaves(v: Vec3, octaves: bigint, persistence: Fixed): Fixed { + let s = FixedTrait.ONE; + let t = FixedTrait.ZERO; + let n = FixedTrait.ZERO; + + while (octaves > 0n) { + octaves -= 1n; + n = n.add(noise(v.div(Vec3.splat(s))).mul(s)); + t = t.add(s); + s = s.mul(persistence); + } + + return n.div(t); +} \ No newline at end of file diff --git a/client/apps/game/src/utils/biome/vec3.ts b/client/apps/game/src/utils/biome/vec3.ts new file mode 100644 index 000000000..1bda9ee16 --- /dev/null +++ b/client/apps/game/src/utils/biome/vec3.ts @@ -0,0 +1,68 @@ +import { Fixed } from "./fixed-point"; +export class Vec3 { + constructor( + public x: Fixed, + public y: Fixed, + public z: Fixed + ) {} + + static new(x: Fixed, y: Fixed, z: Fixed): Vec3 { + return new Vec3(x, y, z); + } + + static splat(v: Fixed): Vec3 { + return new Vec3(v, v, v); + } + + dot(other: Vec3): Fixed { + let x = this.x.mul(other.x); + let y = this.y.mul(other.y); + let z = this.z.mul(other.z); + return x.add(y).add(z); + } + + minus(other: Vec3): Vec3 { + return new Vec3(this.x.sub(other.x), this.y.sub(other.y), this.z.sub(other.z)); + } + + add(other: Vec3): Vec3 { + return new Vec3(this.x.add(other.x), this.y.add(other.y), this.z.add(other.z)); + } + + mul(other: Vec3): Vec3 { + return new Vec3(this.x.mul(other.x), this.y.mul(other.y), this.z.mul(other.z)); + } + + mod(other: Vec3): Vec3 { + return new Vec3(this.x.mod(other.x), this.y.mod(other.y), this.z.mod(other.z)); + } + + div(other: Vec3): Vec3 { + return new Vec3(this.x.div(other.x), this.y.div(other.y), this.z.div(other.z)); + } + + floor(): Vec3 { + return new Vec3(this.x.floor(), this.y.floor(), this.z.floor()); + } + + remScaler(scalar: Fixed): Vec3 { + return new Vec3(this.x.mod(scalar), this.y.mod(scalar), this.z.mod(scalar)); + } + + divScalar(scalar: Fixed): Vec3 { + return new Vec3(this.x.div(scalar), this.y.div(scalar), this.z.div(scalar)); + } + + mulScalar(scalar: Fixed): Vec3 { + return new Vec3(this.x.mul(scalar), this.y.mul(scalar), this.z.mul(scalar)); + } + + addScalar(scalar: Fixed): Vec3 { + return new Vec3(this.x.add(scalar), this.y.add(scalar), this.z.add(scalar)); + } + + abs(): Vec3 { + return new Vec3(this.x.abs(), this.y.abs(), this.z.abs()); + } +} + diff --git a/client/apps/game/src/utils/biome/vec4.ts b/client/apps/game/src/utils/biome/vec4.ts new file mode 100644 index 000000000..c0e15040b --- /dev/null +++ b/client/apps/game/src/utils/biome/vec4.ts @@ -0,0 +1,71 @@ +import { Fixed } from "./fixed-point"; + +export class Vec4 { + constructor( + public x: Fixed, + public y: Fixed, + public z: Fixed, + public w: Fixed + ) {} + + static new(x: Fixed, y: Fixed, z: Fixed, w: Fixed): Vec4 { + return new Vec4(x, y, z, w); + } + + static splat(v: Fixed): Vec4 { + return new Vec4(v, v, v, v); + } + + dot(other: Vec4): Fixed { + let x = this.x.mul(other.x); + let y = this.y.mul(other.y); + let z = this.z.mul(other.z); + let w = this.w.mul(other.w); + return x.add(y).add(z).add(w); + } + + minus(other: Vec4): Vec4 { + return new Vec4(this.x.sub(other.x), this.y.sub(other.y), this.z.sub(other.z), this.w.sub(other.w)); + } + + add(other: Vec4): Vec4 { + return new Vec4(this.x.add(other.x), this.y.add(other.y), this.z.add(other.z), this.w.add(other.w)); + } + + mul(other: Vec4): Vec4 { + return new Vec4(this.x.mul(other.x), this.y.mul(other.y), this.z.mul(other.z), this.w.mul(other.w)); + } + + mod(other: Vec4): Vec4 { + return new Vec4(this.x.mod(other.x), this.y.mod(other.y), this.z.mod(other.z), this.w.mod(other.w)); + } + + div(other: Vec4): Vec4 { + return new Vec4(this.x.div(other.x), this.y.div(other.y), this.z.div(other.z), this.w.div(other.w)); + } + + floor(): Vec4 { + return new Vec4(this.x.floor(), this.y.floor(), this.z.floor(), this.w.floor()); + } + + + remScaler(scalar: Fixed): Vec4 { + return new Vec4(this.x.mod(scalar), this.y.mod(scalar), this.z.mod(scalar), this.w.mod(scalar)); + } + + divScalar(scalar: Fixed): Vec4 { + return new Vec4(this.x.div(scalar), this.y.div(scalar), this.z.div(scalar), this.w.div(scalar)); + } + + mulScalar(scalar: Fixed): Vec4 { + return new Vec4(this.x.mul(scalar), this.y.mul(scalar), this.z.mul(scalar), this.w.mul(scalar)); + } + + addScalar(scalar: Fixed): Vec4 { + return new Vec4(this.x.add(scalar), this.y.add(scalar), this.z.add(scalar), this.w.add(scalar)); + } + + abs(): Vec4 { + return new Vec4(this.x.abs(), this.y.abs(), this.z.abs(), this.w.abs()); + } +}