From 47ab2f1b121cb92ac4b41161996d07f660c3705d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Sun, 7 Sep 2025 17:29:00 +0800 Subject: [PATCH 01/11] Initial commit for the new branch: batch_calculation. --- .gitignore | 2 + __init__.py | 5 +- _perlin.c | 115 ++++++++++++++++++++++++++++++++ examples/2d_batch_generation.py | 18 +++++ setup.py | 13 +++- 5 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 examples/2d_batch_generation.py diff --git a/.gitignore b/.gitignore index 4deb427..7d84f1e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ env *.egg *.egg-info .coverage +.vscode +.venv \ No newline at end of file diff --git a/__init__.py b/__init__.py index f4ae4f8..c57c23a 100644 --- a/__init__.py +++ b/__init__.py @@ -7,7 +7,7 @@ Copyright (c) 2008, Casey Duncan (casey dot duncan at gmail dot com) """ -__version__ = "1.2.3" +__version__ = "1.2.3_batch_calc_2" from . import _perlin, _simplex @@ -17,3 +17,6 @@ pnoise1 = _perlin.noise1 pnoise2 = _perlin.noise2 pnoise3 = _perlin.noise3 + +# Added my homebrew batch calculating function. +batch_pnoise2 = _perlin.batch_noise2 diff --git a/_perlin.c b/_perlin.c index ee61527..77cb227 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,105 @@ py_noise3(PyObject *self, PyObject *args, PyObject *kwargs) } } +/** + * Be like: + * def batch_noise2( + * 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] + * + * Yet, this is a single-thread calculating function. + * Once you want to combine the multiple results via multi threads, + * how to initialize the seamless mosaicking... I'm still thinking. + */ + +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; + } + + // Check for that 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); + int height = (int)((max_y - min_y) / resolution); + + 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) return NULL; + float* data = (float*)PyArray_DATA(result_array); + + float step_x = resolution; + float step_y = resolution; + + // 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 for each iter. + if (callback && callback != Py_None) { + // Same as this in python: + // >>> progress: float = (j + 1) / height + // >>> callback(progress) + double progress = (double)(j + 1) / (double)height; + PyObject* arg = Py_BuildValue("d", progress); + if (!arg) { + PyErr_Print(); + Py_DECREF(result_array); + return NULL; + } + PyObject* res = PyObject_CallObject(callback, arg); + Py_DECREF(arg); + if (!res) { + PyErr_Print(); + Py_DECREF(result_array); + 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 +370,20 @@ 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_noise2(\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, callback: Optional[Callable] = None\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" + "callback -- Optional. A callback function waiting for a float.\n" + "for acquiring the progress of noise generating." + }, {NULL} }; diff --git a/examples/2d_batch_generation.py b/examples/2d_batch_generation.py new file mode 100644 index 0000000..6d24f00 --- /dev/null +++ b/examples/2d_batch_generation.py @@ -0,0 +1,18 @@ +import noise +import numpy as np +from tqdm import tqdm + +def callback(n: float): + print(f"Now process: {n * 100:.4f}%") + +print("Hello world!") +p: np.ndarray[np.float32] = noise.batch_pnoise2( + 0, 0, 1000, 1000, 1024, 1024, 2, 30, callback +) +print("Finished.") + +for lines in p: + print(lines) + +pass + diff --git a/setup.py b/setup.py index ccfafdf..66ead3d 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,13 @@ except ImportError: from distutils.core import setup, Extension +# Get the include path of numpy. +try: + import numpy + include_dirs = [numpy.get_include()] +except ImportError: + include_dirs = [] + if sys.platform != 'win32': compile_args = ['-funroll-loops'] else: @@ -61,9 +68,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 From 46054a3e22b573d306638dbda6542fd562a0c7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Sun, 7 Sep 2025 17:52:44 +0800 Subject: [PATCH 02/11] Added a function for the batch generation of 2-dimensional Perlin Noise in C function that can be called in Python. And also, given a new testing example for batch generation of Perlin Noice. --- _perlin.c | 61 ++++++++++----------------------- examples/2d_batch_generation.py | 5 +-- 2 files changed, 20 insertions(+), 46 deletions(-) diff --git a/_perlin.c b/_perlin.c index 77cb227..2e0dd08 100644 --- a/_perlin.c +++ b/_perlin.c @@ -254,8 +254,7 @@ py_noise3(PyObject *self, PyObject *args, PyObject *kwargs) * 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 + * base: float, resolution: float * ) -> np.ndarray[np.float32] * * Yet, this is a single-thread calculating function. @@ -270,46 +269,43 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg 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; - } - - // Check for that callback function. - if (callback && callback != Py_None && !PyCallable_Check(callback)) { - PyErr_SetString(PyExc_TypeError, "callback must be callable or None"); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ffff|ffff:batch_noise2", kwlist, + &min_x, &min_y, &max_x, &max_y, &repeat_x, &repeat_y, &base, &resolution)) { return NULL; } // Calculate grid dimensions - int width = (int)((max_x - min_x) / resolution); - int height = (int)((max_y - min_y) / resolution); + 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; } + PySys_WriteStdout("Size: (%d, %d)\n", width, height); + // Create a numpy ndarray. npy_intp dims[2] = {height, width}; PyArrayObject* result_array = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_FLOAT32); - if (!result_array) return NULL; + if (!result_array) { + PySys_WriteStdout("WARNING: No array created.\n"); + return NULL; + } float* data = (float*)PyArray_DATA(result_array); - float step_x = resolution; - float step_y = resolution; + float step_x = width / resolution; + float step_y = height / resolution; // Iter. for (int j = 0; j < height; j++) { @@ -318,28 +314,6 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg float y = min_y + j * step_y; float val = noise2(x, y, repeat_x, repeat_y, base); data[j * width + i] = val; - } - - // Callback for each iter. - if (callback && callback != Py_None) { - // Same as this in python: - // >>> progress: float = (j + 1) / height - // >>> callback(progress) - double progress = (double)(j + 1) / (double)height; - PyObject* arg = Py_BuildValue("d", progress); - if (!arg) { - PyErr_Print(); - Py_DECREF(result_array); - return NULL; - } - PyObject* res = PyObject_CallObject(callback, arg); - Py_DECREF(arg); - if (!res) { - PyErr_Print(); - Py_DECREF(result_array); - return NULL; - } - Py_DECREF(res); } } @@ -374,15 +348,17 @@ static PyMethodDef perlin_functions[] = { "batch_noise2(\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, callback: Optional[Callable] = None\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" - "callback -- Optional. A callback function waiting for a float.\n" - "for acquiring the progress of noise generating." + "for acquiring the progress of noise generating.\n\n" + "Written via: 月と猫 - LunaNeko.\n" + "(I just want to generate this in C iteration rather than Python iteration \n" + "for the iteration in Python is too slow for me.)" }, {NULL} }; @@ -406,6 +382,7 @@ static struct PyModuleDef moduledef = { PyObject * PyInit__perlin(void) { + import_array(); return PyModule_Create(&moduledef); } diff --git a/examples/2d_batch_generation.py b/examples/2d_batch_generation.py index 6d24f00..957ff10 100644 --- a/examples/2d_batch_generation.py +++ b/examples/2d_batch_generation.py @@ -2,12 +2,9 @@ import numpy as np from tqdm import tqdm -def callback(n: float): - print(f"Now process: {n * 100:.4f}%") - print("Hello world!") p: np.ndarray[np.float32] = noise.batch_pnoise2( - 0, 0, 1000, 1000, 1024, 1024, 2, 30, callback + 0.0, 0.0, 1000.0, 1000.0, 1024.0, 1024.0, 2.0, 30.0 ) print("Finished.") From b55ec7735506eda90158efa9b5b362991028470d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Sun, 7 Sep 2025 17:52:44 +0800 Subject: [PATCH 03/11] Added a function for the batch generation of 2-dimensional Perlin Noise in C function that can be called in Python. And also, given a new testing example for batch generation of Perlin Noice. --- .gitignore | 3 +- _perlin.c | 61 ++++++++++----------------------- examples/2d_batch_generation.py | 8 +---- setup.py | 6 +++- 4 files changed, 27 insertions(+), 51 deletions(-) diff --git a/.gitignore b/.gitignore index 7d84f1e..3f52b50 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ env *.egg-info .coverage .vscode -.venv \ No newline at end of file +.venv +.venv3.11 \ No newline at end of file diff --git a/_perlin.c b/_perlin.c index 77cb227..2e0dd08 100644 --- a/_perlin.c +++ b/_perlin.c @@ -254,8 +254,7 @@ py_noise3(PyObject *self, PyObject *args, PyObject *kwargs) * 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 + * base: float, resolution: float * ) -> np.ndarray[np.float32] * * Yet, this is a single-thread calculating function. @@ -270,46 +269,43 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg 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; - } - - // Check for that callback function. - if (callback && callback != Py_None && !PyCallable_Check(callback)) { - PyErr_SetString(PyExc_TypeError, "callback must be callable or None"); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ffff|ffff:batch_noise2", kwlist, + &min_x, &min_y, &max_x, &max_y, &repeat_x, &repeat_y, &base, &resolution)) { return NULL; } // Calculate grid dimensions - int width = (int)((max_x - min_x) / resolution); - int height = (int)((max_y - min_y) / resolution); + 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; } + PySys_WriteStdout("Size: (%d, %d)\n", width, height); + // Create a numpy ndarray. npy_intp dims[2] = {height, width}; PyArrayObject* result_array = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_FLOAT32); - if (!result_array) return NULL; + if (!result_array) { + PySys_WriteStdout("WARNING: No array created.\n"); + return NULL; + } float* data = (float*)PyArray_DATA(result_array); - float step_x = resolution; - float step_y = resolution; + float step_x = width / resolution; + float step_y = height / resolution; // Iter. for (int j = 0; j < height; j++) { @@ -318,28 +314,6 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg float y = min_y + j * step_y; float val = noise2(x, y, repeat_x, repeat_y, base); data[j * width + i] = val; - } - - // Callback for each iter. - if (callback && callback != Py_None) { - // Same as this in python: - // >>> progress: float = (j + 1) / height - // >>> callback(progress) - double progress = (double)(j + 1) / (double)height; - PyObject* arg = Py_BuildValue("d", progress); - if (!arg) { - PyErr_Print(); - Py_DECREF(result_array); - return NULL; - } - PyObject* res = PyObject_CallObject(callback, arg); - Py_DECREF(arg); - if (!res) { - PyErr_Print(); - Py_DECREF(result_array); - return NULL; - } - Py_DECREF(res); } } @@ -374,15 +348,17 @@ static PyMethodDef perlin_functions[] = { "batch_noise2(\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, callback: Optional[Callable] = None\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" - "callback -- Optional. A callback function waiting for a float.\n" - "for acquiring the progress of noise generating." + "for acquiring the progress of noise generating.\n\n" + "Written via: 月と猫 - LunaNeko.\n" + "(I just want to generate this in C iteration rather than Python iteration \n" + "for the iteration in Python is too slow for me.)" }, {NULL} }; @@ -406,6 +382,7 @@ static struct PyModuleDef moduledef = { PyObject * PyInit__perlin(void) { + import_array(); return PyModule_Create(&moduledef); } diff --git a/examples/2d_batch_generation.py b/examples/2d_batch_generation.py index 6d24f00..fb297f1 100644 --- a/examples/2d_batch_generation.py +++ b/examples/2d_batch_generation.py @@ -1,14 +1,8 @@ import noise import numpy as np -from tqdm import tqdm - -def callback(n: float): - print(f"Now process: {n * 100:.4f}%") print("Hello world!") -p: np.ndarray[np.float32] = noise.batch_pnoise2( - 0, 0, 1000, 1000, 1024, 1024, 2, 30, callback -) +p: np.ndarray[np.float32] = noise.batch_pnoise2(0.0, 0.0, 1000.0, 1000.0, 1024.0, 1024.0, 2.0, 30.0) print("Finished.") for lines in p: diff --git a/setup.py b/setup.py index 66ead3d..2a43b61 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,11 @@ import numpy include_dirs = [numpy.get_include()] except ImportError: - include_dirs = [] + 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'] From ef2f1125c1d1b9fde9acb88d271e3d7de4795db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Sun, 7 Sep 2025 21:23:13 +0800 Subject: [PATCH 04/11] Modified `README.txt` into `README.md` and added my own message. --- README.txt => README.md | 15 ++++++++++++--- _perlin.c | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) rename README.txt => README.md (90%) diff --git a/README.txt b/README.md similarity index 90% rename from README.txt rename to README.md index b975ff3..0ee41a8 100644 --- a/README.txt +++ b/README.md @@ -1,6 +1,6 @@ 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 +32,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 +42,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 +55,8 @@ to my email above. ---- Blue planet texture used for atmosphere example courtesy NASA + +-------- + +The following message is via `月と猫 - LunaNeko`: +- I added a new function for this library for batch generating the noise. diff --git a/_perlin.c b/_perlin.c index 2e0dd08..3541d92 100644 --- a/_perlin.c +++ b/_perlin.c @@ -254,7 +254,8 @@ py_noise3(PyObject *self, PyObject *args, PyObject *kwargs) * min_x: float, min_y: float, * max_x: float, max_y: float, * repeat_x: float, repeat_y: float, - * base: float, resolution: float + * base: float, resolution: float, + * callback: Optional[Callable] = None * ) -> np.ndarray[np.float32] * * Yet, this is a single-thread calculating function. @@ -269,18 +270,22 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg 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|ffff:batch_noise2", kwlist, - &min_x, &min_y, &max_x, &max_y, &repeat_x, &repeat_y, &base, &resolution)) { + 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; } @@ -307,6 +312,10 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg 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++) { From 4cd7c4cdd7b69cca18f5b42ed23fd0e4ca72bd0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Sun, 7 Sep 2025 21:42:16 +0800 Subject: [PATCH 05/11] Light edit: Refresh the new testing .py file for dual threads. --- _perlin.c | 28 ++++++++++++++++++++++++-- examples/2d_batch_generation.py | 35 ++++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/_perlin.c b/_perlin.c index 3541d92..7be21ed 100644 --- a/_perlin.c +++ b/_perlin.c @@ -289,6 +289,12 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg 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; @@ -298,8 +304,6 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg return NULL; } - PySys_WriteStdout("Size: (%d, %d)\n", width, height); - // Create a numpy ndarray. npy_intp dims[2] = {height, width}; PyArrayObject* result_array = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_FLOAT32); @@ -323,6 +327,26 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg 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); } } diff --git a/examples/2d_batch_generation.py b/examples/2d_batch_generation.py index 4dd4dac..8afeaaf 100644 --- a/examples/2d_batch_generation.py +++ b/examples/2d_batch_generation.py @@ -1,14 +1,35 @@ import noise import numpy as np +import threading as th +from tqdm import tqdm + print("Hello world!") -p: np.ndarray[np.float32] = noise.batch_pnoise2( - 0.0, 0.0, 1000.0, 1000.0, 1024.0, 1024.0, 2.0, 30.0 -) -print("Finished.") -for lines in p: - print(lines) +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_pnoise2( + 0.0, 0.0, 1000.0, 1000.0, + 1024.0, 1024.0, 2.0, 30.0, + fetch_progress + ) -pass +# 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.") From 549e19dc36237f00f40c70452c77de3635eec679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Mon, 8 Sep 2025 00:30:19 +0800 Subject: [PATCH 06/11] Added the better interface in __init__.py. Also, added the function of 3-D batch generation. --- README.md | 11 ++- __init__.py | 274 ++++++++++++++++++++++++++++++++++++++++++++++++++-- _perlin.c | 136 +++++++++++++++++++++++--- perlin.py | 2 +- setup.py | 6 +- 5 files changed, 399 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 0ee41a8..61d1971 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ +The following message is via `月と猫 - LunaNeko`: + +## NOISE + +- This is a TOTAL REMASTER version of the original library written in 2008 via `Casey Duncan` . + 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 @@ -56,7 +62,4 @@ to my email above. Blue planet texture used for atmosphere example courtesy NASA --------- -The following message is via `月と猫 - LunaNeko`: -- I added a new function for this library for batch generating the noise. diff --git a/__init__.py b/__init__.py index c57c23a..5fc5110 100644 --- a/__init__.py +++ b/__init__.py @@ -1,22 +1,276 @@ -"""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_batch_calc_2" +__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. -batch_pnoise2 = _perlin.batch_noise2 +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: + 2D 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 + ) \ No newline at end of file diff --git a/_perlin.c b/_perlin.c index 7be21ed..7d4e8b0 100644 --- a/_perlin.c +++ b/_perlin.c @@ -249,8 +249,7 @@ py_noise3(PyObject *self, PyObject *args, PyObject *kwargs) } /** - * Be like: - * def batch_noise2( + * def batch_pnoise2( * min_x: float, min_y: float, * max_x: float, max_y: float, * repeat_x: float, repeat_y: float, @@ -258,11 +257,7 @@ py_noise3(PyObject *self, PyObject *args, PyObject *kwargs) * callback: Optional[Callable] = None * ) -> np.ndarray[np.float32] * - * Yet, this is a single-thread calculating function. - * Once you want to combine the multiple results via multi threads, - * how to initialize the seamless mosaicking... I'm still thinking. */ - static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwargs) { // Prepare parameters. @@ -283,7 +278,10 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg // 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, + &min_x, &min_y, + &max_x, &max_y, + &repeat_x, &repeat_y, + &base, &resolution, &callback) ) { return NULL; @@ -354,6 +352,105 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg } +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", + "base", "resolution", + "callback", + NULL + }; + + // Parse parameter. + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ffff|ffffO: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" @@ -378,7 +475,7 @@ static PyMethodDef perlin_functions[] = { "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_noise2(\n" + "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" @@ -388,10 +485,23 @@ static PyMethodDef perlin_functions[] = { "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.\n\n" - "Written via: 月と猫 - LunaNeko.\n" - "(I just want to generate this in C iteration rather than Python iteration \n" - "for the iteration in Python is too slow for me.)" + "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} }; @@ -424,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/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 2a43b61..75b3fd2 100644 --- a/setup.py +++ b/setup.py @@ -23,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 @@ -41,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 From edafbd95a4488dea21cc7cea841962165479d979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Mon, 8 Sep 2025 00:30:19 +0800 Subject: [PATCH 07/11] Added the better interface in __init__.py. Also, added the function of 3-D batch generation. (Modified: renew the testing .py file.) --- README.md | 11 +- __init__.py | 274 ++++++++++++++++++++++++++++++-- _perlin.c | 136 ++++++++++++++-- examples/2d_batch_generation.py | 8 +- perlin.py | 2 +- setup.py | 6 +- 6 files changed, 404 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 0ee41a8..61d1971 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ +The following message is via `月と猫 - LunaNeko`: + +## NOISE + +- This is a TOTAL REMASTER version of the original library written in 2008 via `Casey Duncan` . + 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 @@ -56,7 +62,4 @@ to my email above. Blue planet texture used for atmosphere example courtesy NASA --------- -The following message is via `月と猫 - LunaNeko`: -- I added a new function for this library for batch generating the noise. diff --git a/__init__.py b/__init__.py index c57c23a..5fc5110 100644 --- a/__init__.py +++ b/__init__.py @@ -1,22 +1,276 @@ -"""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_batch_calc_2" +__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. -batch_pnoise2 = _perlin.batch_noise2 +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: + 2D 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 + ) \ No newline at end of file diff --git a/_perlin.c b/_perlin.c index 7be21ed..7d4e8b0 100644 --- a/_perlin.c +++ b/_perlin.c @@ -249,8 +249,7 @@ py_noise3(PyObject *self, PyObject *args, PyObject *kwargs) } /** - * Be like: - * def batch_noise2( + * def batch_pnoise2( * min_x: float, min_y: float, * max_x: float, max_y: float, * repeat_x: float, repeat_y: float, @@ -258,11 +257,7 @@ py_noise3(PyObject *self, PyObject *args, PyObject *kwargs) * callback: Optional[Callable] = None * ) -> np.ndarray[np.float32] * - * Yet, this is a single-thread calculating function. - * Once you want to combine the multiple results via multi threads, - * how to initialize the seamless mosaicking... I'm still thinking. */ - static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwargs) { // Prepare parameters. @@ -283,7 +278,10 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg // 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, + &min_x, &min_y, + &max_x, &max_y, + &repeat_x, &repeat_y, + &base, &resolution, &callback) ) { return NULL; @@ -354,6 +352,105 @@ static PyObject* py_batch_noise2(PyObject* self, PyObject* args, PyObject* kwarg } +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", + "base", "resolution", + "callback", + NULL + }; + + // Parse parameter. + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ffff|ffffO: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" @@ -378,7 +475,7 @@ static PyMethodDef perlin_functions[] = { "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_noise2(\n" + "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" @@ -388,10 +485,23 @@ static PyMethodDef perlin_functions[] = { "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.\n\n" - "Written via: 月と猫 - LunaNeko.\n" - "(I just want to generate this in C iteration rather than Python iteration \n" - "for the iteration in Python is too slow for me.)" + "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} }; @@ -424,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/examples/2d_batch_generation.py b/examples/2d_batch_generation.py index 8afeaaf..bb5defb 100644 --- a/examples/2d_batch_generation.py +++ b/examples/2d_batch_generation.py @@ -14,9 +14,11 @@ def fetch_progress(arg: float) -> float: return arg def worker(): - noise.batch_pnoise2( - 0.0, 0.0, 1000.0, 1000.0, - 1024.0, 1024.0, 2.0, 30.0, + noise.batch_pnoise3( + 0.0, 0.0, 0.0, + 1000.0, 1000.0, 1000.0, + 1024.0, 1024.0, 1024.0, + 2.0, 30.0, fetch_progress ) 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 2a43b61..75b3fd2 100644 --- a/setup.py +++ b/setup.py @@ -23,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 @@ -41,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 From c672510b30dee08dbc2c6c0b19935532144e1394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Mon, 8 Sep 2025 00:34:00 +0800 Subject: [PATCH 08/11] Emergency fix: Fix the parameter of function batch_pnoise3() for python. --- _perlin.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_perlin.c b/_perlin.c index 7d4e8b0..de05c50 100644 --- a/_perlin.c +++ b/_perlin.c @@ -364,14 +364,14 @@ static PyObject* py_batch_noise3(PyObject* self, PyObject* args, PyObject* kwarg static char* kwlist[] = { "min_x", "min_y", "min_z", "max_x", "max_y", "max_z", - "repeat_x", "repeat_y", + "repeat_x", "repeat_y", "repeat_z", "base", "resolution", "callback", NULL }; // Parse parameter. - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ffff|ffffO:batch_noise2", kwlist, + 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, From 6e74066d66ba535f4a8ee2649a6304d9c07acd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Mon, 8 Sep 2025 00:35:02 +0800 Subject: [PATCH 09/11] Well, perfect now. --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 5fc5110..3521174 100644 --- a/__init__.py +++ b/__init__.py @@ -264,7 +264,7 @@ def batch_pnoise3( callback: Optional callback function for acquiring the progress of noise generating. Returns: - 2D numpy array of Perlin noise values. + 3D numpy array of Perlin noise values. """ return _perlin.batch_noise3( min_x, min_y, min_z, From 1343ac463449ffb07874df8c548522e38face87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Sat, 27 Sep 2025 20:21:37 +0800 Subject: [PATCH 10/11] Added the batch calculation for perlin noise and simplex noise. --- .gitignore | 3 +- __init__.py | 78 ++++++- _simplex.c | 396 +++++++++++++++++++++++++++----- examples/2d_batch_generation.py | 2 +- 4 files changed, 412 insertions(+), 67 deletions(-) diff --git a/.gitignore b/.gitignore index 3f52b50..3fd7b4c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,4 @@ env *.egg-info .coverage .vscode -.venv -.venv3.11 \ No newline at end of file +.venv* diff --git a/__init__.py b/__init__.py index 3521174..aebcbe3 100644 --- a/__init__.py +++ b/__init__.py @@ -273,4 +273,80 @@ def batch_pnoise3( base, resolution, callback - ) \ No newline at end of file + ) + +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/_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 index bb5defb..6bc51d9 100644 --- a/examples/2d_batch_generation.py +++ b/examples/2d_batch_generation.py @@ -14,7 +14,7 @@ def fetch_progress(arg: float) -> float: return arg def worker(): - noise.batch_pnoise3( + noise.batch_snoise3( 0.0, 0.0, 0.0, 1000.0, 1000.0, 1000.0, 1024.0, 1024.0, 1024.0, From 93f8bc292413ead81dcb03ff5d0145a163937598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E3=81=A8=E7=8C=AB=20-=20LunaNeko?= Date: Sat, 27 Sep 2025 20:30:49 +0800 Subject: [PATCH 11/11] Prepare to publish this out. --- CHANGES.txt | 7 +++++++ README.md | 2 ++ 2 files changed, 9 insertions(+) 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.md b/README.md index 61d1971..d56c171 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ The following message is via `月と猫 - LunaNeko`: - 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