{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%reload_ext autoreload\n", "%autoreload 2\n", "\n", "import itertools\n", "import numpy as np\n", "from scipy import spatial\n", "from pythonworley import noisecoords, worley\n", "import pylab as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generate Worley noise centers and Voronoi tesselation\n", "
\n", "\n", "Worley noise and Voronoi tesselation work on any random centers. However, random centers placed at regular grid give us an easy way to make a pattern that can be seamlessly tiled in any direction." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Set grid shape for randomly seeded gradients\n", "shape = (8,4)\n", "\n", "# Generate grid noise and set flag boundary=True \n", "# to pad it with periodic boundary points\n", "noise = noisecoords(*shape, boundary=True, seed=0)\n", "\n", "# Fltten X, Y coordinates and generate Voronoi tesselation\n", "coords = noise.reshape(2,-1).T\n", "vor = spatial.Voronoi(coords)\n", "vert = vor.vertices\n", "edge = vor.ridge_vertices\n", "face = vor.regions\n", "\n", "\n", "plt.figure(figsize=(12,6), facecolor=\"grey\")\n", "\n", "# Fill faces with random colors\n", "rand = np.random.uniform(0.2, 0.8, len(face))\n", "color = plt.get_cmap(\"Greys\")(rand)\n", "for i, f in enumerate(face):\n", " if len(f) and min(f) > 0:\n", " v = vert[f]\n", " plt.fill(v[:,0], v[:,1], c=color[i])\n", "\n", "# Draw edges\n", "for e in edge:\n", " if min(e) > 0:\n", " v = vert[e]\n", " plt.plot(v[:,0], v[:,1], c=\"black\", lw=12)\n", "\n", "# Plot centers\n", "plt.scatter(*noise, c=\"black\", s=200)\n", "\n", "# Set xlim and ylim to hide periodic boundary padding points\n", "plt.xlim(0, shape[0])\n", "plt.ylim(0, shape[1])\n", "plt.axis('off')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generate cellular noise\n", "
\n", "\n", "Worley noise produces cellular noise pattern when colored dark to light as the distance to noise centers increases." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Set grid shape for randomly seeded gradients\n", "shape = (4,4)\n", "\n", "# Set density - output shape will be dens * shape = (128,128)\n", "dens = 64\n", "\n", "# Generate noise and centers\n", "w, c = worley(shape, dens=dens, seed=0)\n", "\n", "# Worley noise is an array of distances to the Nth closests neighbour center.\n", "# Select the first (the smallest). Then transpose, because plt.imshow treats axis 0 as the \"Y\".\n", "w = w[0].T\n", "\n", "# Test that noise tiles seamlessly\n", "w = np.concatenate([w] * 2, axis=1)\n", "\n", "plt.figure(figsize=(12,6))\n", "plt.imshow(w, cmap=plt.get_cmap('Greys_r'))\n", "plt.plot([256,256], [0,256], '--k', lw=3)\n", "# plt.scatter(*c, c= \"r\")\n", "plt.axis('off')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generate bubble pettern\n", "
\n", "\n", "Worley noise produces bubble pattern when colored light to dark as the distance to noise centers increases." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dens = 64\n", "shape = (8,4)\n", "w, c = worley(shape, dens=dens, seed=0)\n", "w = w[0].T\n", "\n", "plt.figure(figsize=(12,6))\n", "plt.imshow(w, cmap=plt.get_cmap('Greys'))\n", "# plt.scatter(*c, c= \"r\")\n", "plt.axis('off')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generate cobblestone pattern\n", "
\n", "\n", "Worley noise produces ccobblestone pavement pattern when taking the difference between the smallest and the second smallest distances from the Worley noise array." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dens = 64\n", "shape = (8,4)\n", "w, c = worley(shape, dens=dens, seed=0)\n", "w = w[1].T - w[0].T\n", "\n", "plt.figure(figsize=(12,6))\n", "plt.imshow(w, cmap=plt.get_cmap('Greys_r'))\n", "# plt.scatter(*c, c= \"r\")\n", "plt.axis('off')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Procedural star field\n", "
\n", "\n", "* When generating a random star field, a problem is how keep stars apart. Unfortunately random placememts tends to put some stars extremely close to each other.\n", "
\n", "\n", "* An elegant solution is to place stars based on the grid noise. Though we do not use Worley noise in this example, the grid noise is similar to that we used to generate Worley noise centers above." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "shape = (20,10)\n", "\n", "# Make rectangular grid\n", "x, y = np.arange(shape[0]), np.arange(shape[1])\n", "x, y = np.meshgrid(x, y, indexing=\"ij\")\n", "\n", "# Generate noise: random displacements r at random angles phi\n", "np.random.seed(0)\n", "phi = np.random.uniform(0, 2 * np.pi, x.shape)\n", "r = np.random.uniform(0, 0.5, x.shape)\n", "\n", "# Shrink star size to keep it inside its cell.\n", "# Alse, we want more small stars - for the background effect.\n", "# To do that we rescale displacements: r -> 1/2 - 0.001 / r.\n", "r = np.clip(0.5 - 1e-3 / r, 0, None)\n", "size = 200 * (0.5 - r) - 0.4\n", "\n", "# Convert r and phi to cartesian coordinates using Euler formula.\n", "z = r * np.exp(1j * phi)\n", "dx, dy = z.real, z.imag\n", "x, y = x + dx, y + dy\n", "\n", "plt.figure(figsize=(12,6), facecolor=\"black\")\n", "plt.scatter(x, y, c=\"white\", s=size)\n", "plt.axis('off')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Procedural cityline\n", "
\n", "\n", "Again we do not use Worley noise itself in this example. Instead we generate a random cityline based on the grid noise which is similar to that we used to generate Worley noise centers above." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Function to plot a building based its randomly generated center, width, and height\n", "def plot_building(center, width, height, floor_color, window_color, floor=3, basement=0):\n", " nfloor = int(height)\n", " if nfloor > floor:\n", " colors = [[window_color, floor_color] for i in range(nfloor - 1)]\n", " colors = [floor_color] * 2 + list(itertools.chain(*colors))\n", " heights = floor * np.arange(1, 1 + 2 * nfloor)[::-1] + basement\n", " centers = np.ones((2 * nfloor)) * center\n", " plt.bar(centers, heights, width=width, color=colors)\n", "\n", "\n", "nblock = 6 # Number of blocks per line\n", "nline = 3 # Number of lines\n", "w = 20 # Average block width\n", "\n", "# Generate grid noise\n", "np.random.seed(0)\n", "rand = np.random.uniform(0, w, (nline, 3, nblock))\n", "\n", "# Plot blocks line by line\n", "plt.figure(figsize=(18,6), facecolor=\"w\")\n", "for i in range(nline):\n", " darkness = (i + np.arange(2) + 1) / (nline + 1)\n", " floor_color, window_color = plt.get_cmap(\"Greys\")(darkness)\n", " center, width, height = rand[i]\n", " center += 3 * w * np.arange(nblock) + w * i\n", " width += w\n", " for j in range(nblock):\n", " plot_building(center[j], width[j], height[j], \n", " floor_color, window_color, basement=i)\n", "plt.axis('off')\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.17" } }, "nbformat": 4, "nbformat_minor": 1 }