diff --git a/.gitignore b/.gitignore index 4deb427..3fd7b4c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ env *.egg *.egg-info .coverage +.vscode +.venv* diff --git a/CHANGES.txt b/CHANGES.txt index 4608a73..cf4a1b7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,10 @@ +2.0 -- September 27, 2025 + + - Renewed the library after 7 years when the original author abandoned this project. + - Added typing annotation + - Added batch calculation (will be output as np.ndarray) + (Thanks to: 月と猫 - LunaNeko) + 1.2.3 -- November 12, 2018 - Fix linking issue with inline functions on some platforms (e.g., Gentoo) diff --git a/README.txt b/README.md similarity index 88% rename from README.txt rename to README.md index b975ff3..d56c171 100644 --- a/README.txt +++ b/README.md @@ -1,6 +1,14 @@ +The following message is via `月と猫 - LunaNeko`: + +## NOISE + +- This is a TOTAL REMASTER version of the original library written in 2008 via `Casey Duncan` . + +## Original Readme.txt: + Native-code and shader implementations of Perlin noise for Python -By Casey Duncan +By Casey Duncan This package is designed to give you simple to use, fast functions for generating Perlin noise in your Python programs. Perlin noise is famously @@ -32,7 +40,9 @@ convenient generation of turbulent noise in shaders are also included. Installation uses the standard Python distutils regime: +``` python setup.py install +``` This will compile and install the noise package into your Python site packages. @@ -40,8 +50,10 @@ packages. The functions and their signatures are documented in their respective docstrings. Use the Python help() function to read them. ->>> import noise ->>> help(noise) +``` +import noise +help(noise) +``` The examples directory contains sample programs using the noise functions. @@ -51,3 +63,5 @@ to my email above. ---- Blue planet texture used for atmosphere example courtesy NASA + + diff --git a/__init__.py b/__init__.py index f4ae4f8..aebcbe3 100644 --- a/__init__.py +++ b/__init__.py @@ -1,19 +1,352 @@ -"""Noise functions for procedural generation of content +""" +Noise functions for procedural generation of content Contains native code implementations of Perlin improved noise (with fBm capabilities) and Perlin simplex noise. Also contains a fast "fake noise" implementation in GLSL for execution in shaders. -Copyright (c) 2008, Casey Duncan (casey dot duncan at gmail dot com) +Copyright (c) 2008, Casey Duncan (casey.duncan@gmail.com) + +---- +Here is the information of `月と猫 - LunaNeko`: + +I found that this library is a legacy library and REALLY +need to be remastered for it doesn't have ANY of typing +checking in its functions. + +This library is a REMASTERED version of the original library. +Especially for Python calling and type checking. + """ -__version__ = "1.2.3" +__version__ = "2.0" + +import numpy as np +from numpy.typing import NDArray +from typing import Callable, Optional from . import _perlin, _simplex -snoise2 = _simplex.noise2 -snoise3 = _simplex.noise3 -snoise4 = _simplex.noise4 -pnoise1 = _perlin.noise1 -pnoise2 = _perlin.noise2 -pnoise3 = _perlin.noise3 +def snoise2( + x: float, y: float, + octaves: int = 1, + persistence: float = 0.5, + lacunarity: float = 2.0, + repeatx: Optional[float] = None, repeaty: Optional[float] = None, + base: float = 0.0 + ) -> float: + """Generate 2D simplex noise value for specified coordinate. + + Args: + x: X coordinate. + y: Y coordinate. + octaves: Specifies the number of passes, defaults to 1 (simple noise). + persistence: Specifies the amplitude of each successive octave relative + to the one below it. Defaults to 0.5 (each higher octave's amplitude + is halved). Note the amplitude of the first pass is always 1.0. + lacunarity: Specifies the frequency of each successive octave relative + to the one below it, similar to persistence. Defaults to 2.0. + repeatx: Specifies the interval along x axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + repeaty: Specifies the interval along y axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + base: Specifies a fixed offset for the noise coordinates. Useful for + generating different noise textures with the same repeat interval. + + Returns: + Simplex noise value for the specified 2D coordinate. + """ + return _simplex.noise2(x, y, octaves, persistence, lacunarity, repeatx, repeaty, base) + +def snoise3( + x: float, y: float, z: float, + octaves: int = 1, + persistence: float = 0.5, + lacunarity: float = 2.0 + ) -> float: + """Generate 3D simplex noise value for specified coordinate. + + Args: + x: X coordinate. + y: Y coordinate. + z: Z coordinate. + octaves: Specifies the number of passes, defaults to 1 (simple noise). + persistence: Specifies the amplitude of each successive octave relative + to the one below it. Defaults to 0.5 (each higher octave's amplitude + is halved). Note the amplitude of the first pass is always 1.0. + lacunarity: Specifies the frequency of each successive octave relative + to the one below it, similar to persistence. Defaults to 2.0. + + Returns: + Simplex noise value for the specified 3D coordinate. + """ + return _simplex.noise3(x, y, z, octaves, persistence, lacunarity) + +def snoise4( + x: float, y: float, z: float, w: float, + octaves: int = 1, + persistence: float = 0.5, + lacunarity: float = 2.0 + ) -> float: + """Generate 4D simplex noise value for specified coordinate. + + Args: + x: X coordinate. + y: Y coordinate. + z: Z coordinate. + w: W coordinate. + octaves: Specifies the number of passes, defaults to 1 (simple noise). + persistence: Specifies the amplitude of each successive octave relative + to the one below it. Defaults to 0.5 (each higher octave's amplitude + is halved). Note the amplitude of the first pass is always 1.0. + lacunarity: Specifies the frequency of each successive octave relative + to the one below it, similar to persistence. Defaults to 2.0. + + Returns: + Simplex noise value for the specified 4D coordinate. + """ + return _simplex.noise4(x, y, z, w, octaves, persistence, lacunarity) + +def pnoise1( + x: float, + octaves: int = 1, + persistence: float = 0.5, + lacunarity: float = 2.0, + repeat: int = 1024, + base: int = 0 + ) -> float: + """Generate 1D perlin improved noise value for specified coordinate. + + Args: + x: X coordinate. + octaves: Specifies the number of passes for generating fBm noise, + defaults to 1 (simple noise). + persistence: Specifies the amplitude of each successive octave relative + to the one below it. Defaults to 0.5 (each higher octave's amplitude + is halved). Note the amplitude of the first pass is always 1.0. + lacunarity: Specifies the frequency of each successive octave relative + to the one below it, similar to persistence. Defaults to 2.0. + repeat: Specifies the interval along axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + base: Specifies a fixed offset for the input coordinates. Useful for + generating different noise textures with the same repeat interval. + + Returns: + Perlin "improved" noise value for the specified coordinate. + """ + return _perlin.noise1(x, octaves, persistence, lacunarity, repeat, base) + +def pnoise2( + x: float, y: float, + octaves: int = 1, + persistence: float = 0.5, + lacunarity: float = 2.0, + repeatx: float = 1024.0, repeaty: float = 1024.0, + base: int = 0 + ) -> float: + """Generate 2D perlin improved noise value for specified coordinate. + + Args: + x: X coordinate. + y: Y coordinate. + octaves: Specifies the number of passes for generating fBm noise, + defaults to 1 (simple noise). + persistence: Specifies the amplitude of each successive octave relative + to the one below it. Defaults to 0.5 (each higher octave's amplitude + is halved). Note the amplitude of the first pass is always 1.0. + lacunarity: Specifies the frequency of each successive octave relative + to the one below it, similar to persistence. Defaults to 2.0. + repeatx: Specifies the interval along x axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + repeaty: Specifies the interval along y axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + base: Specifies a fixed offset for the input coordinates. Useful for + generating different noise textures with the same repeat interval. + + Returns: + Perlin "improved" noise value for the specified coordinate. + """ + return _perlin.noise2(x, y, octaves, persistence, lacunarity, repeatx, repeaty, base) + +def pnoise3( + x: float, y: float, z: float, + octaves: int = 1, persistence: float = 0.5, + lacunarity: float = 2.0, + repeatx: int = 1024, repeaty: int = 1024, repeatz: int = 1024, + base: int = 0 + ) -> float: + """Generate 3D perlin improved noise value for specified coordinate. + + Args: + x: X coordinate. + y: Y coordinate. + z: Z coordinate. + octaves: Specifies the number of passes for generating fBm noise, + defaults to 1 (simple noise). + persistence: Specifies the amplitude of each successive octave relative + to the one below it. Defaults to 0.5 (each higher octave's amplitude + is halved). Note the amplitude of the first pass is always 1.0. + lacunarity: Specifies the frequency of each successive octave relative + to the one below it, similar to persistence. Defaults to 2.0. + repeatx: Specifies the interval along x axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + repeaty: Specifies the interval along y axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + repeatz: Specifies the interval along z axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + base: Specifies a fixed offset for the input coordinates. Useful for + generating different noise textures with the same repeat interval. + + Returns: + Perlin "improved" noise value for the specified coordinate. + """ + return _perlin.noise3(x, y, z, octaves, persistence, lacunarity, repeatx, repeaty, repeatz, base) + +# Added my homebrew batch calculating function. +def batch_pnoise2( + min_x: float, min_y: float, + max_x: float, max_y: float, + repeat_x: float = 1024.0, repeat_y: float = 1024.0, + base: float = 0.0, + resolution: float = 30.0, + callback: Optional[Callable] = None + ) -> NDArray[np.float32]: + """Generate a 2D array of Perlin noise values. + + Args: + min_x: Minimum X coordinate value. + min_y: Minimum Y coordinate value. + max_x: Maximum X coordinate value. + max_y: Maximum Y coordinate value. + repeat_x: Specifies the interval along x axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + repeat_y: Specifies the interval along y axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + base: Specifies a fixed offset for the input coordinates. Useful for + generating different noise textures with the same repeat interval. + resolution: Number of samples per unit. + callback: Optional callback function for acquiring the progress of noise generating. + + Returns: + 2D numpy array of Perlin noise values. + """ + return _perlin.batch_noise2( + min_x, min_y, + max_x, max_y, + repeat_x, repeat_y, + base, + resolution, + callback + ) + +def batch_pnoise3( + min_x: float, min_y: float, min_z: float, + max_x: float, max_y: float, max_z: float, + repeat_x: float = 1024.0, repeat_y: float = 1024.0, repeat_z: float = 1024.0, + base: float = 0.0, + resolution: float = 30.0, + callback: Optional[Callable] = None + ) -> NDArray[np.float32]: + """Generate a 2D array of Perlin noise values. + + Args: + min_x: Minimum X coordinate value. + min_y: Minimum Y coordinate value. + max_x: Maximum X coordinate value. + max_y: Maximum Y coordinate value. + repeat_x: Specifies the interval along x axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + repeat_y: Specifies the interval along y axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + base: Specifies a fixed offset for the input coordinates. Useful for + generating different noise textures with the same repeat interval. + resolution: Number of samples per unit. + callback: Optional callback function for acquiring the progress of noise generating. + + Returns: + 3D numpy array of Perlin noise values. + """ + return _perlin.batch_noise3( + min_x, min_y, min_z, + max_x, max_y, max_z, + repeat_x, repeat_y, repeat_z, + base, + resolution, + callback + ) + +def batch_snoise2( + min_x: float, min_y: float, + max_x: float, max_y: float, + repeat_x: Optional[float] = None, repeat_y: Optional[float] = None, + base: float = 0.0, + resolution: float = 30.0, + callback: Optional[Callable] = None + ) -> NDArray[np.float32]: + """Generate a 2D array of Simplex noise values. + + Args: + min_x: Minimum X coordinate value. + min_y: Minimum Y coordinate value. + max_x: Maximum X coordinate value. + max_y: Maximum Y coordinate value. + repeat_x: Specifies the interval along x axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + repeat_y: Specifies the interval along y axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + base: Specifies a fixed offset for the input coordinates. Useful for + generating different noise textures with the same repeat interval. + resolution: Number of samples per unit. + callback: Optional callback function for acquiring the progress of noise generating. + + Returns: + 2D numpy array of Simplex noise values. + """ + return _simplex.batch_noise2( + min_x, min_y, + max_x, max_y, + repeat_x, repeat_y, + base, + resolution, + callback + ) + +def batch_snoise3( + min_x: float, min_y: float, min_z: float, + max_x: float, max_y: float, max_z: float, + repeat_x: float = 1024.0, repeat_y: float = 1024.0, repeat_z: float = 1024.0, + base: float = 0.0, + resolution: float = 30.0, + callback: Optional[Callable] = None + ) -> NDArray[np.float32]: + """Generate a 3D array of Simplex noise values. + + Args: + min_x: Minimum X coordinate value. + min_y: Minimum Y coordinate value. + min_z: Minimum Z coordinate value. + max_x: Maximum X coordinate value. + max_y: Maximum Y coordinate value. + max_z: Maximum Z coordinate value. + repeat_x: Specifies the interval along x axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + repeat_y: Specifies the interval along y axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + repeat_z: Specifies the interval along z axis when the noise values repeat. + This can be used as the tile size for creating tileable textures. + base: Specifies a fixed offset for the input coordinates. Useful for + generating different noise textures with the same repeat interval. + resolution: Number of samples per unit. + callback: Optional callback function for acquiring the progress of noise generating. + + Returns: + 3D numpy array of Simplex noise values. + """ + return _simplex.batch_noise3( + min_x, min_y, min_z, + max_x, max_y, max_z, + repeat_x, repeat_y, repeat_z, + base, + resolution, + callback + ) diff --git a/_perlin.c b/_perlin.c index ee61527..de05c50 100644 --- a/_perlin.c +++ b/_perlin.c @@ -7,6 +7,8 @@ #include #include "_noise.h" +#include "numpy/arrayobject.h" + #ifdef _MSC_VER #define inline __inline #endif @@ -246,6 +248,209 @@ py_noise3(PyObject *self, PyObject *args, PyObject *kwargs) } } +/** + * def batch_pnoise2( + * min_x: float, min_y: float, + * max_x: float, max_y: float, + * repeat_x: float, repeat_y: float, + * base: float, resolution: float, + * callback: Optional[Callable] = None + * ) -> np.ndarray[np.float32] + * + */ +static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwargs) { + + // Prepare parameters. + float min_x, min_y, max_x, max_y; + float repeat_x = 1024.0f, repeat_y = 1024.0f; + float base = 0.0f; + float resolution = 30.0f; // 30 units. + PyObject* callback = NULL; + + static char* kwlist[] = { + "min_x", "min_y", + "max_x", "max_y", + "repeat_x", "repeat_y", + "base", "resolution", + "callback", + NULL + }; + + // Parse parameter. + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ffff|ffffO:batch_noise2", kwlist, + &min_x, &min_y, + &max_x, &max_y, + &repeat_x, &repeat_y, + &base, &resolution, + &callback) + ) { + return NULL; + } + + // Validate the callback function. + if (callback && callback != Py_None && !PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "callback must be callable or None"); + return NULL; + } + + // Calculate grid dimensions + int width = (int)((max_x - min_x) / resolution) + 1; + int height = (int)((max_y - min_y) / resolution) + 1; + + if (width <= 0 || height <= 0) { + PyErr_SetString(PyExc_ValueError, "Invalid grid dimensions. I meant, min should be smaller than max."); + return NULL; + } + + // Create a numpy ndarray. + npy_intp dims[2] = {height, width}; + PyArrayObject* result_array = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_FLOAT32); + if (!result_array) { + PySys_WriteStdout("WARNING: No array created.\n"); + return NULL; + } + float* data = (float*)PyArray_DATA(result_array); + + float step_x = width / resolution; + float step_y = height / resolution; + + // progress bar for now. + float total = step_x * step_y; + float now = 0.0f; + + // Iter. + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + float x = min_x + i * step_x; + float y = min_y + j * step_y; + float val = noise2(x, y, repeat_x, repeat_y, base); + data[j * width + i] = val; + } + + // Callback, once per row. + if (callback && callback != Py_None) { + double progress = (double)(j + 1) / (double)height; // in range [0, 1] + PyObject* arg = Py_BuildValue("(d)", progress); // tuple with one float + if (!arg) { + Py_DECREF(result_array); + PySys_WriteStderr("Error in building callback arg.\n"); + return NULL; + } + PyObject* res = PyObject_CallObject(callback, arg); + Py_DECREF(arg); + + if (!res) { // exception occurred in Python + Py_DECREF(result_array); + PySys_WriteStderr("Error in calling callback func.\n"); + return NULL; + } + Py_DECREF(res); + } + } + + return (PyObject*)result_array; +} + + +static PyObject* py_batch_noise3(PyObject* self, PyObject* args, PyObject* kwargs) { + + // Prepare parameters. + float min_x, min_y, min_z, max_x, max_y, max_z; + float repeat_x = 1024.0f, repeat_y = 1024.0f, repeat_z = 1024.0f; + float base = 0.0f; + float resolution = 30.0f; // 30 units. + PyObject* callback = NULL; + + static char* kwlist[] = { + "min_x", "min_y", "min_z", + "max_x", "max_y", "max_z", + "repeat_x", "repeat_y", "repeat_z", + "base", "resolution", + "callback", + NULL + }; + + // Parse parameter. + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ffffff|fffffO:batch_noise2", kwlist, + &min_x, &min_y, &min_z, + &max_x, &max_y, &max_z, + &repeat_x, &repeat_y, &repeat_z, + &base, &resolution, + &callback) + ) { + return NULL; + } + + // Validate the callback function. + if (callback && callback != Py_None && !PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "callback must be callable or None"); + return NULL; + } + + // Calculate grid dimensions + int width = (int)((max_x - min_x) / resolution) + 1; + int height = (int)((max_y - min_y) / resolution) + 1; + int alt = (int)((max_z - min_z) / resolution) + 1; + + if (width <= 0 || height <= 0) { + PyErr_SetString(PyExc_ValueError, "Invalid grid dimensions. I meant, min should be smaller than max."); + return NULL; + } + + // Create a numpy ndarray. + npy_intp dims[3] = {height, width, alt}; + PyArrayObject* result_array = (PyArrayObject*)PyArray_SimpleNew(3, dims, NPY_FLOAT32); + if (!result_array) { + PySys_WriteStdout("WARNING: No array created.\n"); + return NULL; + } + float* data = (float*)PyArray_DATA(result_array); + + float step_x = width / resolution; + float step_y = height / resolution; + float step_z = alt / resolution; + + // progress bar for now. + float total = step_x * step_y * step_z; + float now = 0.0f; + + // Iter. + for (int k = 0; k < alt; k++) { + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + float x = min_x + i * step_x; + float y = min_y + j * step_y; + float z = min_z + k * step_z; + float val = noise3(x, y, z, repeat_x, repeat_y, repeat_z, base); + data[k * height * width + j * width + i] = val; // HEREs + } + } + + // Callback, once per layer. + if (callback && callback != Py_None) { + double progress = (double)(k + 1) / (double)height; // in range [0, 1] + PyObject* arg = Py_BuildValue("(d)", progress); // tuple with one float + if (!arg) { + Py_DECREF(result_array); + PySys_WriteStderr("Error in building callback arg.\n"); + return NULL; + } + PyObject* res = PyObject_CallObject(callback, arg); + Py_DECREF(arg); + + if (!res) { // exception occurred in Python + Py_DECREF(result_array); + PySys_WriteStderr("Error in calling callback func.\n"); + return NULL; + } + Py_DECREF(res); + } + } + + return (PyObject*)result_array; +} + + static PyMethodDef perlin_functions[] = { {"noise1", (PyCFunction) py_noise1, METH_VARARGS | METH_KEYWORDS, "noise1(x, octaves=1, persistence=0.5, lacunarity=2.0, repeat=1024, base=0.0)\n\n" @@ -269,6 +474,35 @@ static PyMethodDef perlin_functions[] = { "tileable textures\n\n" "base -- specifies a fixed offset for the input coordinates. Useful for\n" "generating different noise textures with the same repeat interval"}, + {"batch_noise2", (PyCFunction)py_batch_noise2, METH_VARARGS | METH_KEYWORDS, + "batch_pnoise2(\n" + "min_x: float, min_y: float, max_x: float, max_y: float, " + "repeat_x: float = 1024.0, repeat_y: float = 1024.0, base: float = 0.0, \n" + "resolution: float = 30.0\n" + ")\n\n" + "Generate a 2D array of Perlin noise values.\n\n" + "min_x, min_y -- minimum coordinate values.\n" + "max_x, max_y -- maximum coordinate values.\n" + "repeat_x, repeat_y, base -- (see noise3 for more info)\n" + "resolution -- number of samples per unit.\n" + "for acquiring the progress of noise generating." + }, + {"batch_noise3", (PyCFunction)py_batch_noise3, METH_VARARGS | METH_KEYWORDS, + "batch_pnoise3(\n" + "min_x: float, min_y: float, min_z: float,\n" + "max_x: float, max_y: float, max_z: float,\n" + "repeat_x: float = 1024.0, repeat_y: float = 1024.0, repeat_z: float = 1024.0,\n" + "base: float = 0.0,\n" + "resolution: float = 30.0,\n" + "callback: Optional[Callable] = None\n" + ")\n\n" + "Generate a 2D array of Perlin noise values.\n\n" + "min_x, min_y, min_z -- minimum coordinate values.\n" + "max_x, max_y, max_z -- maximum coordinate values.\n" + "repeat_x, repeat_y, repeat_z, base -- (see noise3 for more info)\n" + "resolution -- number of samples per unit.\n" + "for acquiring the progress of noise generating." + }, {NULL} }; @@ -291,6 +525,7 @@ static struct PyModuleDef moduledef = { PyObject * PyInit__perlin(void) { + import_array(); return PyModule_Create(&moduledef); } @@ -299,7 +534,7 @@ PyInit__perlin(void) void init_perlin(void) { - Py_InitModule3("_perlin", perlin_functions, module_doc); + PyErr_SetString(PyExc_SystemError, "Version for Python 2.0 in this lib is DEPRECATED. Please upgrade your Python to Python3."); } #endif diff --git a/_simplex.c b/_simplex.c index 41044fc..efb6198 100644 --- a/_simplex.c +++ b/_simplex.c @@ -7,6 +7,9 @@ #include #include "_noise.h" +// For numpy array support +#include "numpy/arrayobject.h" + // 2D simplex skew factors #define F2 0.3660254037844386f // 0.5 * (sqrt(3.0) - 1.0) #define G2 0.21132486540518713f // (3.0 - sqrt(3.0)) / 6.0 @@ -14,7 +17,7 @@ float noise2(float x, float y) { - int i1, j1, I, J, c; + int i1, j1, ii, jj, c; float s = (x + y) * F2; float i = floorf(x + s); float j = floorf(y + s); @@ -27,19 +30,19 @@ noise2(float x, float y) xx[0] = x - (i - t); yy[0] = y - (j - t); - i1 = xx[0] > yy[0]; - j1 = xx[0] <= yy[0]; + i1 = (xx[0] > yy[0]) ? 1 : 0; + j1 = (xx[0] <= yy[0]) ? 1 : 0; xx[2] = xx[0] + G2 * 2.0f - 1.0f; yy[2] = yy[0] + G2 * 2.0f - 1.0f; - xx[1] = xx[0] - i1 + G2; - yy[1] = yy[0] - j1 + G2; + xx[1] = xx[0] - (float)i1 + G2; + yy[1] = yy[0] - (float)j1 + G2; - I = (int) i & 255; - J = (int) j & 255; - g[0] = PERM[I + PERM[J]] % 12; - g[1] = PERM[I + i1 + PERM[J + j1]] % 12; - g[2] = PERM[I + 1 + PERM[J + 1]] % 12; + ii = ((int) i) & 255; + jj = ((int) j) & 255; + g[0] = PERM[ii + PERM[jj]] % 12; + g[1] = PERM[ii + i1 + PERM[jj + j1]] % 12; + g[2] = PERM[ii + 1 + PERM[jj + 1]] % 12; for (c = 0; c <= 2; c++) f[c] = 0.5f - xx[c]*xx[c] - yy[c]*yy[c]; @@ -53,7 +56,7 @@ noise2(float x, float y) #define dot3(v1, v2) ((v1)[0]*(v2)[0] + (v1)[1]*(v2)[1] + (v1)[2]*(v2)[2]) -#define ASSIGN(a, v0, v1, v2) (a)[0] = v0; (a)[1] = v1; (a)[2] = v2; +#define ASSIGN(a, v0, v1, v2) {(a)[0] = (float)(v0); (a)[1] = (float)(v1); (a)[2] = (float)(v2);} #define F3 (1.0f / 3.0f) #define G3 (1.0f / 6.0f) @@ -61,7 +64,7 @@ noise2(float x, float y) float noise3(float x, float y, float z) { - int c, o1[3], o2[3], g[4], I, J, K; + int c, o1[3], o2[3], g[4], ii, jj, kk; float f[4], noise[4] = {0.0f, 0.0f, 0.0f, 0.0f}; float s = (x + y + z) * F3; float i = floorf(x + s); @@ -101,17 +104,17 @@ noise3(float x, float y, float z) for (c = 0; c <= 2; c++) { pos[3][c] = pos[0][c] - 1.0f + 3.0f * G3; - pos[2][c] = pos[0][c] - o2[c] + 2.0f * G3; - pos[1][c] = pos[0][c] - o1[c] + G3; + pos[2][c] = pos[0][c] - (float)o2[c] + 2.0f * G3; + pos[1][c] = pos[0][c] - (float)o1[c] + G3; } - I = (int) i & 255; - J = (int) j & 255; - K = (int) k & 255; - g[0] = PERM[I + PERM[J + PERM[K]]] % 12; - g[1] = PERM[I + o1[0] + PERM[J + o1[1] + PERM[o1[2] + K]]] % 12; - g[2] = PERM[I + o2[0] + PERM[J + o2[1] + PERM[o2[2] + K]]] % 12; - g[3] = PERM[I + 1 + PERM[J + 1 + PERM[K + 1]]] % 12; + ii = ((int) i) & 255; + jj = ((int) j) & 255; + kk = ((int) k) & 255; + g[0] = PERM[ii + PERM[jj + PERM[kk]]] % 12; + g[1] = PERM[ii + o1[0] + PERM[jj + o1[1] + PERM[o1[2] + kk]]] % 12; + g[2] = PERM[ii + o2[0] + PERM[jj + o2[1] + PERM[o2[2] + kk]]] % 12; + g[3] = PERM[ii + 1 + PERM[jj + 1 + PERM[kk + 1]]] % 12; for (c = 0; c <= 3; c++) { f[c] = 0.6f - pos[c][0]*pos[c][0] - pos[c][1]*pos[c][1] - pos[c][2]*pos[c][2]; @@ -164,46 +167,46 @@ noise4(float x, float y, float z, float w) { float z0 = z - (k - t); float w0 = w - (l - t); - int c = (x0 > y0)*32 + (x0 > z0)*16 + (y0 > z0)*8 + (x0 > w0)*4 + (y0 > w0)*2 + (z0 > w0); - int i1 = SIMPLEX[c][0]>=3; - int j1 = SIMPLEX[c][1]>=3; - int k1 = SIMPLEX[c][2]>=3; - int l1 = SIMPLEX[c][3]>=3; - int i2 = SIMPLEX[c][0]>=2; - int j2 = SIMPLEX[c][1]>=2; - int k2 = SIMPLEX[c][2]>=2; - int l2 = SIMPLEX[c][3]>=2; - int i3 = SIMPLEX[c][0]>=1; - int j3 = SIMPLEX[c][1]>=1; - int k3 = SIMPLEX[c][2]>=1; - int l3 = SIMPLEX[c][3]>=1; - - float x1 = x0 - i1 + G4; - float y1 = y0 - j1 + G4; - float z1 = z0 - k1 + G4; - float w1 = w0 - l1 + G4; - float x2 = x0 - i2 + 2.0f*G4; - float y2 = y0 - j2 + 2.0f*G4; - float z2 = z0 - k2 + 2.0f*G4; - float w2 = w0 - l2 + 2.0f*G4; - float x3 = x0 - i3 + 3.0f*G4; - float y3 = y0 - j3 + 3.0f*G4; - float z3 = z0 - k3 + 3.0f*G4; - float w3 = w0 - l3 + 3.0f*G4; + int c = ((x0 > y0)*32 + (x0 > z0)*16 + (y0 > z0)*8 + (x0 > w0)*4 + (y0 > w0)*2 + (z0 > w0)); + int i1 = (SIMPLEX[c][0]>=3) ? 1 : 0; + int j1 = (SIMPLEX[c][1]>=3) ? 1 : 0; + int k1 = (SIMPLEX[c][2]>=3) ? 1 : 0; + int l1 = (SIMPLEX[c][3]>=3) ? 1 : 0; + int i2 = (SIMPLEX[c][0]>=2) ? 1 : 0; + int j2 = (SIMPLEX[c][1]>=2) ? 1 : 0; + int k2 = (SIMPLEX[c][2]>=2) ? 1 : 0; + int l2 = (SIMPLEX[c][3]>=2) ? 1 : 0; + int i3 = (SIMPLEX[c][0]>=1) ? 1 : 0; + int j3 = (SIMPLEX[c][1]>=1) ? 1 : 0; + int k3 = (SIMPLEX[c][2]>=1) ? 1 : 0; + int l3 = (SIMPLEX[c][3]>=1) ? 1 : 0; + + float x1 = x0 - (float)i1 + G4; + float y1 = y0 - (float)j1 + G4; + float z1 = z0 - (float)k1 + G4; + float w1 = w0 - (float)l1 + G4; + float x2 = x0 - (float)i2 + 2.0f*G4; + float y2 = y0 - (float)j2 + 2.0f*G4; + float z2 = z0 - (float)k2 + 2.0f*G4; + float w2 = w0 - (float)l2 + 2.0f*G4; + float x3 = x0 - (float)i3 + 3.0f*G4; + float y3 = y0 - (float)j3 + 3.0f*G4; + float z3 = z0 - (float)k3 + 3.0f*G4; + float w3 = w0 - (float)l3 + 3.0f*G4; float x4 = x0 - 1.0f + 4.0f*G4; float y4 = y0 - 1.0f + 4.0f*G4; float z4 = z0 - 1.0f + 4.0f*G4; float w4 = w0 - 1.0f + 4.0f*G4; - int I = (int)i & 255; - int J = (int)j & 255; - int K = (int)k & 255; - int L = (int)l & 255; - int gi0 = PERM[I + PERM[J + PERM[K + PERM[L]]]] & 0x1f; - int gi1 = PERM[I + i1 + PERM[J + j1 + PERM[K + k1 + PERM[L + l1]]]] & 0x1f; - int gi2 = PERM[I + i2 + PERM[J + j2 + PERM[K + k2 + PERM[L + l2]]]] & 0x1f; - int gi3 = PERM[I + i3 + PERM[J + j3 + PERM[K + k3 + PERM[L + l3]]]] & 0x1f; - int gi4 = PERM[I + 1 + PERM[J + 1 + PERM[K + 1 + PERM[L + 1]]]] & 0x1f; + int ii = ((int)i) & 255; + int jj = ((int)j) & 255; + int kk = ((int)k) & 255; + int ll = ((int)l) & 255; + int gi0 = PERM[ii + PERM[jj + PERM[kk + PERM[ll]]]] & 0x1f; + int gi1 = PERM[ii + i1 + PERM[jj + j1 + PERM[kk + k1 + PERM[ll + l1]]]] & 0x1f; + int gi2 = PERM[ii + i2 + PERM[jj + j2 + PERM[kk + k2 + PERM[ll + l2]]]] & 0x1f; + int gi3 = PERM[ii + i3 + PERM[jj + j3 + PERM[kk + k3 + PERM[ll + l3]]]] & 0x1f; + int gi4 = PERM[ii + 1 + PERM[jj + 1 + PERM[kk + 1 + PERM[ll + 1]]]] & 0x1f; float t0, t1, t2, t3, t4; t0 = 0.6f - x0*x0 - y0*y0 - z0*z0 - w0*w0; @@ -232,7 +235,7 @@ noise4(float x, float y, float z, float w) { noise[4] = t4 * t4 * dot4(GRAD4[gi4], x4, y4, z4, w4); } - return 27.0 * (noise[0] + noise[1] + noise[2] + noise[3] + noise[4]); + return 27.0f * (noise[0] + noise[1] + noise[2] + noise[3] + noise[4]); } static inline float @@ -293,8 +296,8 @@ py_noise2(PyObject *self, PyObject *args, PyObject *kwargs) } else { // Tiled noise float w = z; if (repeaty != FLT_MAX) { - float yf = y * 2.0 / repeaty; - float yr = repeaty * M_1_PI * 0.5; + float yf = y * 2.0f / repeaty; + float yr = repeaty * (float)M_1_PI * 0.5f; float vy = fast_sin(yf); float vyz = fast_cos(yf); y = vy * yr; @@ -305,8 +308,8 @@ py_noise2(PyObject *self, PyObject *args, PyObject *kwargs) } } if (repeatx != FLT_MAX) { - float xf = x * 2.0 / repeatx; - float xr = repeatx * M_1_PI * 0.5; + float xf = x * 2.0f / repeatx; + float xr = repeatx * (float)M_1_PI * 0.5f; float vx = fast_sin(xf); float vxz = fast_cos(xf); x = vx * xr; @@ -373,6 +376,243 @@ py_noise4(PyObject *self, PyObject *args, PyObject *kwargs) } } +/** + * def batch_snoise2( + * min_x: float, min_y: float, + * max_x: float, max_y: float, + * repeat_x: float, repeat_y: float, + * base: float, resolution: float, + * callback: Optional[Callable] = None + * ) -> np.ndarray[np.float32] + * + */ +static PyObject* py_batch_snoise2(PyObject* self, PyObject* args, PyObject* kwargs) { + + // Prepare parameters. + float min_x, min_y, max_x, max_y; + float repeat_x = FLT_MAX, repeat_y = FLT_MAX; + float base = 0.0f; + float resolution = 30.0f; // 30 units. + PyObject* callback = NULL; + + static char* kwlist[] = { + "min_x", "min_y", + "max_x", "max_y", + "repeat_x", "repeat_y", + "base", "resolution", + "callback", + NULL + }; + + // Parse parameter. + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ffff|ffffO:batch_noise2", kwlist, + &min_x, &min_y, + &max_x, &max_y, + &repeat_x, &repeat_y, + &base, &resolution, + &callback) + ) { + return NULL; + } + + // Validate the callback function. + if (callback && callback != Py_None && !PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "callback must be callable or None"); + return NULL; + } + + // Calculate grid dimensions + int width = (int)((max_x - min_x) / resolution) + 1; + int height = (int)((max_y - min_y) / resolution) + 1; + + if (width <= 0 || height <= 0) { + PyErr_SetString(PyExc_ValueError, "Invalid grid dimensions. I meant, min should be smaller than max."); + return NULL; + } + + // Create a numpy ndarray. + npy_intp dims[2] = {height, width}; + PyArrayObject* result_array = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_FLOAT32); + if (!result_array) { + PySys_WriteStdout("WARNING: No array created.\n"); + return NULL; + } + float* data = (float*)PyArray_DATA(result_array); + + float step_x = (max_x - min_x) / (float)(width - 1); + float step_y = (max_y - min_y) / (float)(height - 1); + + // Iter. + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + float x = min_x + i * step_x; + float y = min_y + j * step_y; + + // Use the same logic as in py_noise2 for tiled vs non-tiled noise + float val; + if (repeat_x == FLT_MAX && repeat_y == FLT_MAX) { + // Flat noise, no tiling + val = noise2(x, y); + } else { + // Tiled noise - use the same approach as py_noise2 + float z = base; + float w = base; + if (repeat_y != FLT_MAX) { + float yf = y * 2.0f / repeat_y; + float yr = repeat_y * (float)M_1_PI * 0.5f; + float vy = fast_sin(yf); + float vyz = fast_cos(yf); + y = vy * yr; + w += vyz * yr; + } + if (repeat_x != FLT_MAX) { + float xf = x * 2.0f / repeat_x; + float xr = repeat_x * (float)M_1_PI * 0.5f; + float vx = fast_sin(xf); + float vxz = fast_cos(xf); + x = vx * xr; + z += vxz * xr; + } + + if (repeat_x == FLT_MAX || repeat_y == FLT_MAX) { + val = fbm_noise3(x, y, z, 1, 0.5f, 2.0f); + } else { + val = fbm_noise4(x, y, z, w, 1, 0.5f, 2.0f); + } + } + + data[j * width + i] = val; + } + + // Callback, once per row. + if (callback && callback != Py_None) { + double progress = (double)(j + 1) / (double)height; // in range [0, 1] + PyObject* arg = Py_BuildValue("(d)", progress); // tuple with one float + if (!arg) { + Py_DECREF(result_array); + PySys_WriteStderr("Error in building callback arg.\n"); + return NULL; + } + PyObject* res = PyObject_CallObject(callback, arg); + Py_DECREF(arg); + + if (!res) { // exception occurred in Python + Py_DECREF(result_array); + PySys_WriteStderr("Error in calling callback func.\n"); + return NULL; + } + Py_DECREF(res); + } + } + + return (PyObject*)result_array; +} + +/** + * def batch_snoise3( + * min_x: float, min_y: float, min_z: float, + * max_x: float, max_y: float, max_z: float, + * repeat_x: float, repeat_y: float, repeat_z: float, + * base: float, resolution: float, + * callback: Optional[Callable] = None + * ) -> np.ndarray[np.float32] + * + */ +static PyObject* py_batch_snoise3(PyObject* self, PyObject* args, PyObject* kwargs) { + + // Prepare parameters. + float min_x, min_y, min_z, max_x, max_y, max_z; + float repeat_x = 1024.0f, repeat_y = 1024.0f, repeat_z = 1024.0f; + float base = 0.0f; + float resolution = 30.0f; // 30 units. + PyObject* callback = NULL; + + static char* kwlist[] = { + "min_x", "min_y", "min_z", + "max_x", "max_y", "max_z", + "repeat_x", "repeat_y", "repeat_z", + "base", "resolution", + "callback", + NULL + }; + + // Parse parameter. + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ffffff|fffffO:batch_noise3", kwlist, + &min_x, &min_y, &min_z, + &max_x, &max_y, &max_z, + &repeat_x, &repeat_y, &repeat_z, + &base, &resolution, + &callback) + ) { + return NULL; + } + + // Validate the callback function. + if (callback && callback != Py_None && !PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "callback must be callable or None"); + return NULL; + } + + // Calculate grid dimensions + int width = (int)((max_x - min_x) / resolution) + 1; + int height = (int)((max_y - min_y) / resolution) + 1; + int depth = (int)((max_z - min_z) / resolution) + 1; + + if (width <= 0 || height <= 0 || depth <= 0) { + PyErr_SetString(PyExc_ValueError, "Invalid grid dimensions. I meant, min should be smaller than max."); + return NULL; + } + + // Create a numpy ndarray. + npy_intp dims[3] = {depth, height, width}; + PyArrayObject* result_array = (PyArrayObject*)PyArray_SimpleNew(3, dims, NPY_FLOAT32); + if (!result_array) { + PySys_WriteStdout("WARNING: No array created.\n"); + return NULL; + } + float* data = (float*)PyArray_DATA(result_array); + + float step_x = (max_x - min_x) / (float)(width - 1); + float step_y = (max_y - min_y) / (float)(height - 1); + float step_z = (max_z - min_z) / (float)(depth - 1); + + // Iter. + for (int k = 0; k < depth; k++) { + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + float x = min_x + i * step_x; + float y = min_y + j * step_y; + float z = min_z + k * step_z; + float val = noise3(x, y, z); + data[k * height * width + j * width + i] = val; + } + } + + // Callback, once per layer. + if (callback && callback != Py_None) { + double progress = (double)(k + 1) / (double)depth; // in range [0, 1] + PyObject* arg = Py_BuildValue("(d)", progress); // tuple with one float + if (!arg) { + Py_DECREF(result_array); + PySys_WriteStderr("Error in building callback arg.\n"); + return NULL; + } + PyObject* res = PyObject_CallObject(callback, arg); + Py_DECREF(arg); + + if (!res) { // exception occurred in Python + Py_DECREF(result_array); + PySys_WriteStderr("Error in calling callback func.\n"); + return NULL; + } + Py_DECREF(res); + } + } + + return (PyObject*)result_array; +} + + static PyMethodDef simplex_functions[] = { {"noise2", (PyCFunction)py_noise2, METH_VARARGS | METH_KEYWORDS, "noise2(x, y, octaves=1, persistence=0.5, lacunarity=2.0, repeatx=None, repeaty=None, base=0.0) " @@ -406,6 +646,35 @@ static PyMethodDef simplex_functions[] = { "is halved). Note the amplitude of the first pass is always 1.0.\n\n" "lacunarity -- specifies the frequency of each successive octave relative\n" "to the one below it, similar to persistence. Defaults to 2.0."}, + {"batch_noise2", (PyCFunction)py_batch_snoise2, METH_VARARGS | METH_KEYWORDS, + "batch_snoise2(\n" + "min_x: float, min_y: float, max_x: float, max_y: float, " + "repeat_x: float = None, repeat_y: float = None, base: float = 0.0, \n" + "resolution: float = 30.0, callback: Optional[Callable] = None\n" + ")\n\n" + "Generate a 2D array of Simplex noise values.\n\n" + "min_x, min_y -- minimum coordinate values.\n" + "max_x, max_y -- maximum coordinate values.\n" + "repeat_x, repeat_y, base -- (see noise2 for more info)\n" + "resolution -- number of samples per unit.\n" + "callback -- optional callback function for acquiring the progress of noise generating." + }, + {"batch_noise3", (PyCFunction)py_batch_snoise3, METH_VARARGS | METH_KEYWORDS, + "batch_snoise3(\n" + "min_x: float, min_y: float, min_z: float,\n" + "max_x: float, max_y: float, max_z: float,\n" + "repeat_x: float = 1024.0, repeat_y: float = 1024.0, repeat_z: float = 1024.0,\n" + "base: float = 0.0,\n" + "resolution: float = 30.0,\n" + "callback: Optional[Callable] = None\n" + ")\n\n" + "Generate a 3D array of Simplex noise values.\n\n" + "min_x, min_y, min_z -- minimum coordinate values.\n" + "max_x, max_y, max_z -- maximum coordinate values.\n" + "repeat_x, repeat_y, repeat_z, base -- (see noise3 for more info)\n" + "resolution -- number of samples per unit.\n" + "callback -- optional callback function for acquiring the progress of noise generating." + }, {NULL} }; @@ -428,6 +697,7 @@ static struct PyModuleDef moduledef = { PyObject * PyInit__simplex(void) { + import_array(); return PyModule_Create(&moduledef); } @@ -436,7 +706,7 @@ PyInit__simplex(void) void init_simplex(void) { - Py_InitModule3("_simplex", simplex_functions, module_doc); + PyErr_SetString(PyExc_SystemError, "Version for Python 2.0 in this lib is DEPRECATED. Please upgrade your Python to Python3."); } -#endif +#endif \ No newline at end of file diff --git a/examples/2d_batch_generation.py b/examples/2d_batch_generation.py new file mode 100644 index 0000000..6bc51d9 --- /dev/null +++ b/examples/2d_batch_generation.py @@ -0,0 +1,37 @@ +import noise +import numpy as np + +import threading as th +from tqdm import tqdm + +print("Hello world!") + +def fetch_progress(arg: float) -> float: + global total_steps + steps_done = int(arg * total_steps) + bar.n = steps_done + bar.refresh() + return arg + +def worker(): + noise.batch_snoise3( + 0.0, 0.0, 0.0, + 1000.0, 1000.0, 1000.0, + 1024.0, 1024.0, 1024.0, + 2.0, 30.0, + fetch_progress + ) + +# total steps, for tqdm. +total_width: int = int(1000 / 30) + 1 +total_height: int = int(1000 / 30) + 1 +total_steps: int = total_width * total_height +bar = tqdm(range(0, total_steps), desc="Generating Perlin noise...") + +working_thread = th.Thread(target=worker) +working_thread.start() +working_thread.join() + +bar.close() + +print("Finished.") diff --git a/perlin.py b/perlin.py index 74b425c..c582438 100644 --- a/perlin.py +++ b/perlin.py @@ -1,4 +1,4 @@ -# Copyright (c) 2008, Casey Duncan (casey dot duncan at gmail dot com) +# Copyright (c) 2008, Casey Duncan (casey.duncan@gmail.com) # see LICENSE.txt for details """Perlin noise -- pure python implementation""" diff --git a/setup.py b/setup.py index ccfafdf..75b3fd2 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,17 @@ except ImportError: from distutils.core import setup, Extension +# Get the include path of numpy. +try: + import numpy + include_dirs = [numpy.get_include()] +except ImportError: + raise ImportError( + "No NumPy detected. You should do: \n" \ + " pip install numpy \n " \ + "to install a numpy lib at first. If the same problem happened again, check for setup.py in line 10 to change " + "the include dir of numpy manually.") + if sys.platform != 'win32': compile_args = ['-funroll-loops'] else: @@ -12,8 +23,8 @@ setup( name='noise', - version='1.2.3', - description='Perlin noise for Python', + version='2.0', + description='Perlin noise for Python (Remastered version)', long_description='''\ Perlin noise is ubiquitous in modern CGI. Used for procedural texturing, animation, and enhancing realism, Perlin noise has been called the "salt" of @@ -30,6 +41,8 @@ motion) noise by combining multiple octaves of Perlin noise. Shader functions for convenient generation of turbulent noise are also included. +- 2.0 Remastered the whole library. + - 1.2.3 Fixed linker bug (Gentoo), lacunarity param for snoise4 - 1.2.2 AppVeyor support for Windows builds @@ -61,9 +74,11 @@ ext_modules=[ Extension('noise._simplex', ['_simplex.c'], extra_compile_args=compile_args, + include_dirs=include_dirs, ), Extension('noise._perlin', ['_perlin.c'], extra_compile_args=compile_args, - ) + include_dirs=include_dirs, + ), ], -) +) \ No newline at end of file