Skip to content

Commit

Permalink
Updated Perlin util
Browse files Browse the repository at this point in the history
  • Loading branch information
neki-dev committed Jul 12, 2024
1 parent c4a8aa7 commit 1d2a6bb
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 123 deletions.
2 changes: 1 addition & 1 deletion dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions dist/utils/perlin/const.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export declare const PERLIN_YWRAPB = 4;
export declare const PERLIN_YWRAP: number;
export declare const PERLIN_ZWRAPB = 8;
export declare const PERLIN_ZWRAP: number;
export declare const PERLIN_AMP_FALLOFF = 0.5;
export declare const PERLIN_AVG_POWER = 1.1;
9 changes: 8 additions & 1 deletion dist/utils/perlin/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
import type { PerlinParameters } from "./types";
export declare function generateNoise(parameters: PerlinParameters): number;
export declare class Perlin {
static generate({ x, y, seed, config }: PerlinParameters): number;
private static clamp;
private static scaledCosine;
private static smootherStep;
private static heightFalloff;
private static normalizeConfig;
}
4 changes: 3 additions & 1 deletion dist/utils/seed/index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export declare function generateSeed(size?: number): number[];
export declare class Seed {
static generate(size?: number): number[];
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "gen-biome",
"description": "Procedural generation of 2D maps with distinct biomes",
"version": "3.0.1",
"version": "3.0.2",
"keywords": [
"map",
"generation",
Expand Down
8 changes: 4 additions & 4 deletions src/generator/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { WorldGenerationParams } from "./types";
import { generateNoise } from "../utils/perlin";
import { generateSeed } from "../utils/seed";
import { Perlin } from "../utils/perlin";
import { Seed } from "../utils/seed";
import { World } from "../world";
import { WorldBiome } from "../world/biome";
import type { WorldConfig } from "../world/types";
Expand Down Expand Up @@ -37,13 +37,13 @@ export class WorldGenerator<T extends object> {
}

public generate(params?: WorldGenerationParams): World<T> {
const currentSeed = params?.seed ?? generateSeed(params?.seedSize);
const currentSeed = params?.seed ?? Seed.generate(params?.seedSize);
const matrix: T[][] = [];

for (let y = 0; y < this.config.height; y++) {
matrix[y] = [];
for (let x = 0; x < this.config.width; x++) {
const height = generateNoise({
const height = Perlin.generate({
config: this.config,
seed: currentSeed,
x: x + (params?.offsetX ?? 0),
Expand Down
9 changes: 9 additions & 0 deletions src/utils/perlin/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const PERLIN_YWRAPB = 4;
export const PERLIN_YWRAP = 1 << PERLIN_YWRAPB;

export const PERLIN_ZWRAPB = 8;
export const PERLIN_ZWRAP = 1 << PERLIN_ZWRAPB;

export const PERLIN_AMP_FALLOFF = 0.5;

export const PERLIN_AVG_POWER = 1.1;
219 changes: 116 additions & 103 deletions src/utils/perlin/index.ts
Original file line number Diff line number Diff line change
@@ -1,121 +1,134 @@
import type { WorldConfig } from "../../world/types";
import {
PERLIN_AMP_FALLOFF,
PERLIN_AVG_POWER,
PERLIN_YWRAP,
PERLIN_YWRAPB,
PERLIN_ZWRAP,
} from "./const";
import type { PerlinParameters } from "./types";

function clamp(
value: number | undefined,
defaultValue: number,
limit: [number, number] = [0, 1],
) {
return Math.max(limit[0], Math.min(limit[1], value ?? defaultValue));
}
export class Perlin {
public static generate({ x, y, seed, config }: PerlinParameters): number {
const {
frequencyChange,
borderSmoothness,
heightAveraging,
heightRedistribution,
falloff,
} = this.normalizeConfig(config);

const size = seed.length - 1;
const cx = (x / config.width) * frequencyChange;
const cy = (y / config.height) * frequencyChange;

let xi = Math.floor(cx);
let yi = Math.floor(cy);
let xf = cx - xi;
let yf = cy - yi;

let r = 0;
let ampl = 0.5;

for (let o = 0; o < borderSmoothness; o++) {
let of = xi + (yi << PERLIN_YWRAPB);

const rxf = this.scaledCosine(xf);
const ryf = this.scaledCosine(yf);

let n1 = seed[of & size];
n1 += rxf * (seed[(of + 1) & size] - n1);

let n2 = seed[(of + PERLIN_YWRAP) & size];
n2 += rxf * (seed[(of + PERLIN_YWRAP + 1) & size] - n2);

n1 += ryf * (n2 - n1);
r += n1 * ampl;
ampl *= PERLIN_AMP_FALLOFF;
of += PERLIN_ZWRAP;

xi <<= 1;
xf *= 2;
if (xf >= 1.0) {
xi++;
xf--;
}

yi <<= 1;
yf *= 2;
if (yf >= 1.0) {
yi++;
yf--;
}
}

function scaledCosine(i: number): number {
return 0.5 * (1.0 - Math.cos(i * Math.PI));
}
if (heightAveraging) {
if (r > 0.5) {
r **= (1.5 - r) / PERLIN_AVG_POWER;
} else if (r < 0.5) {
r **= (1.5 - r) * PERLIN_AVG_POWER;
}
}

function smootherStep(x: number) {
return (3 * x ** 2) - (2 * x ** 3);
}
r **= heightRedistribution;

function heightFalloff(offset: number, length: number, falloff: number) {
const radius = length / 2;
const distance = Math.abs(radius - offset);
const target = radius * (1 - falloff);
if (falloff > 0.0) {
r *=
this.heightFalloff(x, config.width, falloff) *
this.heightFalloff(y, config.height, falloff);
}

if (distance < target) {
return 1;
return r;
}

let x = ((distance - target) / radius) / (1 - target / radius);

x = Math.min(1, Math.max(0, x));

return 1 - smootherStep(x);
}
private static clamp(
value: number | undefined,
defaultValue: number,
limit: [number, number] = [0, 1]
) {
return Math.max(limit[0], Math.min(limit[1], value ?? defaultValue));
}

export function generateNoise(parameters: PerlinParameters): number {
const { x, y, seed, config } = parameters;

const frequency = Math.round(clamp(config.frequencyChange, 0.3) * 31 + 1);
const octaves = Math.round((1 - clamp(config.borderSmoothness, 0.5)) * 14 + 1);
const redistribution = 2.0 - clamp(config.heightRedistribution, 1.0, [0.5, 1.5]);
const falloff = clamp(config.falloff, 0.0, [0.0, 0.9]);
const averaging = config.heightAveraging ?? true;

const PERLIN_SIZE = seed.length - 1;
const PERLIN_YWRAPB = 4;
const PERLIN_YWRAP = 1 << PERLIN_YWRAPB;
const PERLIN_ZWRAPB = 8;
const PERLIN_ZWRAP = 1 << PERLIN_ZWRAPB;
const PERLIN_AMP_FALLOFF = 0.5;
const PERLIN_AVG_POWER = 1.1;

const cx = (x / config.width) * frequency;
const cy = (y / config.height) * frequency;

let xi = Math.floor(cx);
let yi = Math.floor(cy);
let xf = cx - xi;
let yf = cy - yi;
let rxf;
let ryf;

let r = 0;
let ampl = 0.5;

let n1;
let n2;
let n3;

for (let o = 0; o < octaves; o++) {
let of = xi + (yi << PERLIN_YWRAPB);

rxf = scaledCosine(xf);
ryf = scaledCosine(yf);

n1 = seed[of & PERLIN_SIZE];
n1 += rxf * (seed[(of + 1) & PERLIN_SIZE] - n1);
n2 = seed[(of + PERLIN_YWRAP) & PERLIN_SIZE];
n2 += rxf * (seed[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n2);
n1 += ryf * (n2 - n1);

of += PERLIN_ZWRAP;
n2 = seed[of & PERLIN_SIZE];
n2 += rxf * (seed[(of + 1) & PERLIN_SIZE] - n2);
n3 = seed[(of + PERLIN_YWRAP) & PERLIN_SIZE];
n3 += rxf * (seed[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n3);
n2 += ryf * (n3 - n2);

r += n1 * ampl;
ampl *= PERLIN_AMP_FALLOFF;

xi <<= 1;
xf *= 2;
if (xf >= 1.0) {
xi++;
xf--;
}
private static scaledCosine(i: number): number {
return 0.5 * (1.0 - Math.cos(i * Math.PI));
}

yi <<= 1;
yf *= 2;
if (yf >= 1.0) {
yi++;
yf--;
}
private static smootherStep(x: number) {
return 3 * x ** 2 - 2 * x ** 3;
}

if (averaging) {
if (r > 0.5) {
r **= (1.5 - r) / PERLIN_AVG_POWER;
} else if (r < 0.5) {
r **= (1.5 - r) * PERLIN_AVG_POWER;
private static heightFalloff(
offset: number,
length: number,
falloff: number
) {
const radius = length / 2;
const distance = Math.abs(radius - offset);
const target = radius * (1 - falloff);

if (distance < target) {
return 1;
}
}

r **= redistribution;
let x = (distance - target) / radius / (1 - target / radius);

if (falloff) {
r *= heightFalloff(x, config.width, falloff) * heightFalloff(y, config.height, falloff);
x = Math.min(1, Math.max(0, x));

return 1 - this.smootherStep(x);
}

return r;
private static normalizeConfig(config: WorldConfig) {
return {
frequencyChange: Math.round(
this.clamp(config.frequencyChange, 0.3) * 31 + 1
),
borderSmoothness: Math.round(
(1 - this.clamp(config.borderSmoothness, 0.5)) * 14 + 1
),
heightRedistribution:
2.0 - this.clamp(config.heightRedistribution, 1.0, [0.5, 1.5]),
falloff: this.clamp(config.falloff, 0.0, [0.0, 0.9]),
heightAveraging: config.heightAveraging ?? true,
};
}
}
18 changes: 10 additions & 8 deletions src/utils/seed/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
export function generateSeed(size: number = 512) {
const seed: number[] = [];

for (let i = 0; i < size; i++) {
seed.push(Math.random());
export class Seed {
public static generate(size: number = 512) {
const seed: number[] = [];

for (let i = 0; i < size; i++) {
seed.push(Math.random());
}

return seed;
}

return seed;
}
}
4 changes: 0 additions & 4 deletions src/world/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ export class World<T extends object> {
}

public replaceAt(point: WorldPoint, data: T): void {
if (point.y >= this.height || point.x >= this.width) {
throw Error(`Position [${point.x},${point.y}] is out of world bounds`);
}

this.matrix[point.y][point.x] = data;
}
}

0 comments on commit 1d2a6bb

Please sign in to comment.