Skip to content

About

A perlin noise shard written for Crystal. All credit goes to Spooner who wrote the original implementation.

Resources

Stars

8 stars

Watchers

1 watching

Forks

Latest commit

 

History

28 Commits

Folders and files

Repository files navigation

perlin_noise

CI Docs Crystal Version License: MIT

Fast, zero-allocation procedural noise library for Crystal implementing the Sol-Vin Two-Tier Engine & Declarative Builder (core/ + dsl/) architecture.

Features

  • Tier 1 Zero-Allocation Core Engines (PerlinNoise::Core):
    • Improved Gradient Perlin Noise (PerlinEngine): 1D, 2D, 3D, and 4D sampling, periodic/tileable noise (sample_periodic_2d/3d), and exact analytic partial derivatives (sample_with_derivative_2d/3d).
    • Simplex Noise (SimplexEngine & SimplexNoise): 1D, 2D, 3D, and 4D isotropic simplex noise.
    • Smoothed Value Noise (ValueEngine): 1D, 2D, and 3D integer-lattice smoothed value noise.
    • Worley / Cellular Noise (WorleyEngine): 2D and 3D Voronoi/cellular noise with Euclidean, EuclideanSquared, Manhattan, and Chebyshev metrics and F1, F2, F2MinusF1, F1PlusF2, F1TimesF2, and CellValue feature outputs.
    • Multi-Octave Fractals (FractalSampler): FBM (Fractional Brownian Motion), Billow, Ridged (Ridged Multifractal), and Turbulence with configurable octaves, persistence, and lacunarity.
    • Domain Warping (DomainWarp): Single- and multi-stage iterative coordinate warping in 2D and 3D.
    • Batch Heightmaps & Volumes (NoiseMap2D & NoiseMap3D): Planar maps, 100% seamless 4D-torus tileable textures, equirectangular spherical planetary maps, Sobel normal maps (#to_normal_map), terracing (#terrace!), and ASCII preview rendering (#to_ascii).
  • Tier 2 Declarative DSL (PerlinNoise::DSL):
    • PerlinNoise.build / SimplexNoise.build block configuration DSL.
    • PerlinNoise.pipeline multi-layer procedural graph with blend, select_layer, terrace, scale_bias, curve, clamp, abs, and invert.
    • PerlinNoise.biome_map declarative 2D elevation $\times$ moisture biome classifier.

Installation

Add this to your application's shard.yml:

dependencies:
  perlin_noise:
    github: sol-vin/perlin_noise

Then run:

shards install

Quick Start & Classic API

require "perlin_noise"

p = PerlinNoise.new(1234) # Seed the generator

# Configure step (scale), persistence, octaves, and coordinate offsets
p.step = 0.4        # Default is 0.123456 (aliases: scale, frequency)
p.persistence = 0.4 # Default is 0.2 (legacy alias `persistance` also supported)
p.octaves = 4.0     # Default is 1.0 (alias: octave)

# Continuous 1D, 2D, 3D, and 4D noise in [-1.0, 1.0] (accepts Int or Float)
p.noise(0)             # 1D
p.noise(1.5, 2.75)     # 2D
p.noise(0, 1, 2)       # 3D
p.noise_4d(0, 1, 2, 3) # 4D

# Normalized noise in [0.0, 1.0]
p.normalize_noise(10, 20)

# Heightmap helper
p.height(0, 0, 10)     # 2D integer height in 0..10
p.height(0, 0, 5..25)  # 2D integer height in 5..25

# Deterministic integers & PRNG integers
p.int(0, 10, 20)           # 1D in [10, 20)
p.int(0, 1, 10, 20)        # 2D in [10, 20)
p.int(0, 1, 2, 10..20)     # 3D in 10..20
p.prng_int(0, 1, 2, 1, 50) # 3D PRNG-seeded integer

# Deterministic floats
p.float(0, 0.0, 1.0)
p.float(0, 1, 0.0, 5.0)
p.float(0, 1, 2, 1.0..10.0)

# Deterministic booleans
p.bool(0, 1, 2, 1, 100)              # 1 out of 100 chance of being true
p.bool(10, 20, probability: 0.25)    # 25% probability of being true

# Deterministic collection & weighted item selection
items = [1, 2, 3, "Bacon", "Taco", "Cat"]
p.item(0, 1, 2, items)
p.prng_item(0, 1, 2, items)

loot = {:common => 70, :rare => 25, :legendary => 5}
p.weighted_item(12, 34, loot) # => :common | :rare | :legendary

Declarative Builder DSL (PerlinNoise.build)

terrain = PerlinNoise.build do
  seed 1337
  algorithm :simplex          # :perlin | :simplex | :value | :worley
  fractal :ridged             # :none | :fbm | :billow | :ridged | :turbulence
  octaves 6
  persistence 0.5
  lacunarity 2.0
  scale 0.015
  offset x: 100.0, y: -50.0, z: 0.0
  interpolation :quintic      # :linear | :cosine | :hermite | :quintic
  domain_warp amplitude: 8.0, frequency: 0.04, octaves: 2, iterations: 2
  output_range 0.0..255.0
end

elevation = terrain.noise(42.5, 19.2)

Multi-Layer Procedural Pipeline DSL (PerlinNoise.pipeline)

Combine multiple noise generators and post-processing modifiers into a single composable pipeline:

world = PerlinNoise.pipeline(seed: 2026) do
  layer :continents do
    algorithm :simplex
    fractal :fbm
    octaves 4
    scale 0.008
  end

  layer :mountains do
    algorithm :perlin
    fractal :ridged
    octaves 6
    scale 0.03
  end

  layer : tectonic_mask do
    algorithm :worley
    worley metric: :euclidean, return_type: :f2_minus_f1, jitter: 0.85
    scale 0.015
  end

  select_layer control: :tectonic_mask, low: :continents, high: :mountains, threshold: 0.1, falloff: 0.15
  terrace 8, smoothness: 0.25
  clamp -1.0..1.0
end

height_val = world.noise(120.0, 85.0)
map = world.map_2d(64, 32)
puts map.to_ascii

Declarative Biome Classification (PerlinNoise.biome_map)

biomes = PerlinNoise.biome_map(seed: 42) do
  elevation do
    algorithm :simplex
    fractal :fbm
    octaves 5
    scale 0.01
  end

  moisture do
    algorithm :perlin
    fractal :fbm
    octaves 3
    scale 0.02
  end

  rule :deep_ocean, elevation: -1.0..-0.35
  rule :ocean,      elevation: -0.35..0.0
  rule :beach,      elevation: 0.0..0.08
  rule :snow_peak,  elevation: 0.65..1.0, moisture: 0.0..1.0
  rule :tundra,     elevation: 0.65..1.0, moisture: -1.0..0.0
  rule :rainforest, elevation: 0.08..0.65, moisture: 0.3..1.0
  rule :desert,     elevation: 0.08..0.65, moisture: -1.0..-0.3
  fallback :grassland
end

biomes.classify(100, 200) # => :rainforest

Seamless Tileable, Spherical & Normal Maps (NoiseMap2D)

gen = PerlinNoise.build(seed: 77) do
  algorithm :perlin
  fractal :fbm
  octaves 4
  scale 0.05
end

# 1. Planar 2D heightmap
planar = gen.map_2d(128, 128)

# 2. 100% seamless X/Y tileable texture (via 4D torus projection)
tileable = gen.tileable_map_2d(128, 128, frequency: 4.0)

# 3. Seamless equirectangular spherical planetary map
planet = gen.sphere_map_2d(256, 128)

# Export grayscale bytes, RGB tangent-space normal maps, or ASCII preview
bytes   = tileable.to_bytes
normals = tileable.to_normal_map(bump_height: 2.5)
puts gen.map_2d(40, 12).to_ascii

Analytic Derivatives & Periodic Noise

gen = PerlinNoise.new(42)

# Exact 2D/3D value + partial derivatives {value, dx, dy[, dz]}
d2 = gen.derivative(12.3, 45.6)
d3 = gen.derivative(12.3, 45.6, 7.8)

# Periodic Perlin noise repeating every (px, py) lattice cells
v0 = gen.periodic_noise(1.5, 2.5, 16, 16)
v1 = gen.periodic_noise(17.5, 18.5, 16, 16) # == v0

Contributing

  1. Fork it (https://github.com/sol-vin/perlin_noise/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Run tests and formatter (crystal spec && crystal tool format --check)
  4. Commit your changes (git commit -am 'Add some feature')
  5. Push to the branch (git push origin my-new-feature)
  6. Create a new Pull Request

Contributors

  • sol-vin Ian Rash - creator, maintainer
  • Spooner Bil Bas - author of the original ruby-perlin gem that inspired the initial 2016 version

About

A perlin noise shard written for Crystal. All credit goes to Spooner who wrote the original implementation.

Resources

Stars

8 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages