Skip to content

Instantly share code, notes, and snippets.

@NoRaincheck
Created January 26, 2026 19:12
Show Gist options
  • Select an option

  • Save NoRaincheck/1f3c302cc7ca382e6e19bae2e2383ed4 to your computer and use it in GitHub Desktop.

Select an option

Save NoRaincheck/1f3c302cc7ca382e6e19bae2e2383ed4 to your computer and use it in GitHub Desktop.
"""
FFT Terrain Generator
Generates 2D fractal noise terrain using Fast Fourier Transforms.
Based on Paul Bourke's "Frequency Synthesis of Landscapes (and clouds)" (1997).
"""
import argparse
import numpy as np
from PIL import Image
def generate_white_noise(size: int, rng: np.random.RandomState) -> np.ndarray:
"""Generate a 2D array of white noise (random values)."""
return rng.random((size, size))
def create_frequency_filter(size: int, roughness: float) -> np.ndarray:
"""
Create a frequency filter mask for 1/f^r scaling.
The filter applies 1/f^r scaling where:
- f is the frequency (distance from center in frequency space)
- r is the roughness factor
Args:
size: Size of the 2D array (must be square)
roughness: The roughness exponent (typically 2.0-3.0 for natural terrain)
Returns:
2D array of filter values
"""
# Create frequency coordinates centered at (0, 0)
freq_x = np.fft.fftfreq(size)
freq_y = np.fft.fftfreq(size)
# Create 2D frequency grid
fx, fy = np.meshgrid(freq_x, freq_y)
# Calculate frequency magnitude (distance from DC component)
frequency = np.sqrt(fx**2 + fy**2)
# Avoid division by zero at DC component
frequency[0, 0] = 1.0
# Apply 1/f^r filter
filter_mask = 1.0 / (frequency ** roughness)
# Set DC component to 0 to center the output around zero mean
filter_mask[0, 0] = 0.0
return filter_mask
def generate_fft_terrain(size: int, roughness: float, seed: int | None = None) -> np.ndarray:
"""
Generate fractal terrain using FFT noise synthesis.
Algorithm:
1. Generate white noise
2. Apply 2D FFT
3. Scale by frequency filter (1/f^r)
4. Apply inverse FFT
Args:
size: Size of the terrain (size x size pixels)
roughness: Roughness factor (typically 2.0-3.0)
seed: Optional random seed for reproducibility
Returns:
2D array of terrain heights (normalized to 0-1)
"""
rng = np.random.RandomState(seed)
# Step 1: Generate white noise
noise = generate_white_noise(size, rng)
# Step 2: Apply 2D FFT
fft_result = np.fft.fft2(noise)
# Step 3: Create and apply frequency filter
frequency_filter = create_frequency_filter(size, roughness)
filtered_fft = fft_result * frequency_filter
# Step 4: Apply inverse FFT and take real part
terrain = np.fft.ifft2(filtered_fft).real
# Normalize to 0-1 range
terrain = (terrain - terrain.min()) / (terrain.max() - terrain.min())
return terrain
def save_terrain_image(terrain: np.ndarray, filename: str) -> None:
"""
Save terrain data as a grayscale PNG image.
Args:
terrain: 2D array of terrain heights (normalized to 0-1)
filename: Output filename
"""
# Convert to 8-bit grayscale (0-255)
terrain_uint8 = (terrain * 255).astype(np.uint8)
# Create PIL image and save
image = Image.fromarray(terrain_uint8, mode='L')
image.save(filename)
print(f"Saved terrain image to: {filename}")
def main():
parser = argparse.ArgumentParser(
description="Generate fractal terrain using FFT noise synthesis"
)
parser.add_argument(
"-s", "--size",
type=int,
default=512,
help="Size of the terrain (must be power of 2, default: 512)"
)
parser.add_argument(
"-r", "--roughness",
type=float,
default=2.4,
help="Roughness factor (typically 2.0-3.0, default: 2.4)"
)
parser.add_argument(
"-o", "--output",
type=str,
default="terrain.png",
help="Output filename (default: terrain.png)"
)
parser.add_argument(
"--seed",
type=int,
default=None,
help="Random seed for reproducibility"
)
args = parser.parse_args()
# Validate size is power of 2
if args.size & (args.size - 1) != 0 or args.size <= 0:
print(f"Warning: Size {args.size} is not a power of 2. FFT works best with powers of 2.")
print(f"Generating {args.size}x{args.size} terrain with roughness={args.roughness}")
# Generate terrain
terrain = generate_fft_terrain(args.size, args.roughness, args.seed)
# Save image
save_terrain_image(terrain, args.output)
print("Done!")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment