Skip to content

Commit

Permalink
docs improved
Browse files Browse the repository at this point in the history
Signed-off-by: Amith <amitharun3@gmail.com>
  • Loading branch information
amithm3 committed Oct 13, 2022
1 parent 4512516 commit f73eccf
Show file tree
Hide file tree
Showing 15 changed files with 185 additions and 159 deletions.
68 changes: 54 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
- ![hot nebula](snaps/hot_nebula.png)
- island
- ![island](snaps/island.png)
- island zoom
- ![island zoom](snaps/island_zoom.png)
- land
- ![land](snaps/land.png)
- marble fractal
Expand All @@ -52,28 +50,70 @@
- [INSTALLATION](docs/INSTALL.md) document.

## Usage
Show users how to use the software.
Be specific.
Use appropriate formatting when showing code snippets.
- [EXAMPLE](main.py)
- Noise functions
- >**NPerlin**(<br>
seed: int = None,<br>
frequency: Union[int, tuple[int, ...]] = 8,<br>
waveLength: Union[float, tuple[float]] = 128,<br>
warp: Union['Warp', tuple['Warp']] = None,<br>
_range: tuple[float, float] = None<br>
)
- >**Noise**(<br>
seed: int = None,<br>
frequency: frequencyHint = 8,<br>
waveLength: waveLengthHint = 128,<br>
warp: warpHint = None,<br>
_range: rangeHint = None,<br>
octaves: int = 8,<br>
persistence: float = 0.5,<br>
lacunarity: float = 2<br>
)
- :param **seed**: seed for prng values, default random value
- :param **frequency**: number of random values in one unit respect to dimension, default 8
- :param **waveLength**: length of one unit respect to dimension, default 128
- :param **warp**: the interpolation function used between random value nodes, default selectionTools.Warp.improved()
- :param **_range**: bound for noise values, output will be within the give range, default (0, 1)
- :param **fwm**: **key word only** - frequency, waveLength multiplier
- :param **octaves**: number(s) of additive overlapping noise wave(s), default 8
- :param **lacunarity**: frequency multiplier for successive noise octave, default 2
- :param **persistence**: amplitude modulator for successive noise octave, default 0.5
- >**perlinGenerator**(<br>
noise: 'NPerlin',<br>
*lineSpace: Union[tuple[float, float, float], tuple[float, float]])<br>
)
- generates noise values from given noise instance for given line space
- :param **noise**: the noise instance to use for generating noise values
- :param **lineSpace**: (start, stop) | (start, stop, resolution) for each dimension,
- _start_: minimum value for nth dimension coordinate
- _stop_: maximum value for nth dimension coordinate
- _resolution_: number of coordinates between start and stop (both included)
- :return: tuple of noise values and coordinate mesh for each nth dimension of n-dimension depth

## How to test the software
If the software includes automated tests, detail how to run those tests.
- [Tests](tests)
- To test Logical consistency run [testLogic](tests/testLogic.py)
- To test Profile Benchmarking run [testProfile](tests/testProfile.py)
- To test Visuals run [testVisuals](tests/testVisuals.py)
- To test Colors run [testCol](tests/testCol.py)

## Known issues
Document any known significant shortcomings with the software.
- **_`No Known Bugs`_**
- **_`NPerlin.findBounds is bottleneck`_**

## Getting help
Instruct users how to get help with this software; this might include links to an issue tracker, wiki, mailing list, etc.
- Check [main.py](main.py) for usage
- Check [docs](docs)
- Check Usage Section

If you have questions, concerns, bug reports, etc, please file an [issue]() in this repository's Issue Tracker or
open a [discussion]() in this repository's Discussion section.

**Example**
If you have questions, concerns, bug reports, etc, please file an issue in this repository's Issue Tracker.

## Getting involved
This section should detail why people should get involved and describe key areas you are
currently focusing on; e.g., trying to get feedback on features, fixing certain bugs, building
important pieces, etc.
- [Fork]() the repository and issue a [PR]() to contribute

General instructions on _how_ to contribute should be stated with a link to [CONTRIBUTING](docs/CONTRIBUTING.md).
General instructions on _how_ to contribute [CONTRIBUTING](docs/CONTRIBUTING.md).

----

Expand Down
46 changes: 32 additions & 14 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from matplotlib import pyplot

from src import *

noise = Noise(
Expand All @@ -15,22 +13,42 @@


def main():
plot = 1
mul, res = 1, 4
colorMap = (
"#000",
"#00f",
"#0f0",
"#f00",
)
gradient = Gradient.scope(), Gradient.terrace(32)
colorGradient = LinearColorGradient(*colorMap, grad='i')
colorGradient = LinearColorGradient.earth(grad='i')
h, *coordsMesh = perlinGenerator(noise,
(0, noise.waveLength[0] * mul, noise.waveLength[0] * res),
(0, noise.waveLength[1] * mul, noise.waveLength[0] * res),
gradient=gradient)
h = colorGradient(h)
gradients = Gradient.none()
colorGradient = LinearColorGradient(*colorMap, grad='s').earth(grad='i') or LinearColorGradient.none()
h, coordsMesh = perlinGenerator(noise,
(0, noise.waveLength[0] * mul, noise.waveLength[0] * res),
(0, noise.waveLength[1] * mul, noise.waveLength[0] * res))
g = applyGrads(h, coordsMesh, gradients)
c = colorGradient(g)

fig, ax = pyplot.subplots()
ax.imshow(h, cmap="gray")
pyplot.show()
if plot == 1:
from matplotlib import pyplot
# ---matplotlib---
fig, ax = pyplot.subplots()
ax.imshow(c, cmap="gray")
pyplot.show()
elif plot == 2:
import plotly.graph_objects as go
from plotly.offline import offline
# -----plotly-----
marker_data = go.Surface(x=coordsMesh[0], y=coordsMesh[1], z=g)
fig = go.Figure(data=marker_data)
fig.update_layout(
scene=dict(zaxis=dict(nticks=4, range=[0, 2])),
width=700,
margin=dict(r=20, l=10, b=10, t=10))
fig.update_traces(contours_z=dict(
show=True, usecolormap=True,
highlightcolor='limegreen',
project_z=True))
offline.plot(fig)


if __name__ == '__main__':
Expand Down
Binary file added snaps/color_patch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified snaps/island.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed snaps/island_zoom.png
Binary file not shown.
Binary file modified snaps/land.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .nPerlin import NPerlin
from .noise import Noise
from .selectionTools import Warp, Gradient, LinearColorGradient
from .generator import perlinGenerator
from .generator import perlinGenerator, applyGrads

__all__ = ['NPerlin', 'Noise', 'Warp', 'Gradient', 'LinearColorGradient', 'perlinGenerator']
__all__ = ['NPerlin', 'Noise', 'Warp', 'Gradient', 'LinearColorGradient', 'perlinGenerator', 'applyGrads']
32 changes: 23 additions & 9 deletions src/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,35 @@


@cache
def meshgrid(*ls):
def meshgrid(*ls) -> "np.ndarray":
a = np.mgrid[tuple(slice(*(sl[0],
sl[1] + (step := (sl[1] - sl[0]) / ((sl[2] if len(sl) == 3 else sl[1] - sl[0]) - 1)),
step)) for sl in ls)]
return a.transpose(0, *range(1, a.ndim))
return a.transpose(0, *range(1, a.ndim))[::-1]


def perlinGenerator(noise: "NPerlin",
*lineSpace: Union[tuple[float, float, float], tuple[float, float]],
gradient: Union[tuple["Gradient", ...], "Gradient"] = None):
def applyGrads(h, coordsMesh, gradients: Union[tuple["Gradient", ...], "Gradient"] = None):
if gradients is None: gradients = ()
if not iterable(gradients): gradients = (gradients,)
for g in gradients: h = g(h, coordsMesh)
return h


def perlinGenerator(noise: 'NPerlin',
*lineSpace: Union[tuple[float, float, float], tuple[float, float]]) \
-> tuple['np.ndarray', 'np.ndarray']:
"""
generates noise values from given noise instance for given line space
:param noise: the noise instance to use for generating noise values
:param lineSpace: (start, stop) | (start, stop, resolution) for each dimension,
start: minimum value for nth dimension coordinate
stop: maximum value for nth dimension coordinate
resolution: number of coordinates between start and stop (both included)
:return: tuple of noise values and coordinate mesh for each nth dimension of n-dimension depth
"""
assert all([len(sl) in (2, 3) for sl in lineSpace]), \
"*lineSpace must be 'tuple' of float as (start, stop) or (start, stop, resolution)"
if gradient is None: gradient = ()
if not iterable(gradient): gradient = (gradient,)
coordsMesh = meshgrid(*lineSpace)
h = noise(*coordsMesh)
for g in gradient: h = g(h, *coordsMesh)
return h, *coordsMesh
return h, coordsMesh
2 changes: 1 addition & 1 deletion src/nPerlin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import numpy as np

from .tools import NTuple, NPrng, iterable, maxLen, findCorners
from .tools import NTuple, NPrng, findCorners
from .selectionTools import Warp

frequencyHint = Union[int, tuple[int, ...]]
Expand Down
82 changes: 46 additions & 36 deletions src/selectionTools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import numpy as np

from .tools import iterable, NTuple

try:
import numexpr as ne
except ImportError:
Expand Down Expand Up @@ -61,18 +59,18 @@ def __init__(self, foo: Callable[['np.ndarray', ...], 'np.ndarray'], _name):
self.__name = _name
self.__foo = foo

def __call__(self, a, *coordsMesh):
return self.__foo(a, *coordsMesh)
def __call__(self, a, coordsMesh):
return self.__foo(a, coordsMesh)

@classmethod
def woodSpread(cls, n=1) -> "Gradient":
return cls(lambda a, *cm: (np.sin(a * n * np.sqrt(np.sum(np.square(
return cls(lambda a, cm: (np.sin(a * n * np.sqrt(np.sum(np.square(
cm - np.max(cm, axis=tuple(i for i in range(1, len(cm) + 1)), keepdims=True) / 2), axis=0))) + 1) / 2,
"WoodSpread")

@classmethod
def wood(cls, n=1, m=16) -> "Gradient":
return cls(lambda a, *cm: (np.sin(m * a + n * np.sqrt(np.sum(np.square(
return cls(lambda a, cm: (np.sin(m * a + n * np.sqrt(np.sum(np.square(
cm - np.max(cm, axis=tuple(i for i in range(1, len(cm) + 1)), keepdims=True) / 2), axis=0))) + 1) / 2,
"Wood")

Expand All @@ -82,11 +80,11 @@ def gradient(a):
a = n * a
return a - np.floor(a)

return cls(lambda a, *_: gradient(a), "Ply")
return cls(lambda a, _: gradient(a), "Ply")

@classmethod
def terrace(cls, n=8) -> "Gradient":
return cls(lambda a, *_: np.int8(n * a) / n, "Terrace")
return cls(lambda a, _: np.int8(n * a) / n, "Terrace")

@classmethod
def terraceSmooth(cls, n=8) -> "Gradient":
Expand All @@ -95,58 +93,68 @@ def gradient(a):
a = np.where(a > i / n * a.max(), a, a - a / i)
return a

return cls(lambda a, *_: gradient(a), "TerraceSmooth")
return cls(lambda a, _: gradient(a), "TerraceSmooth")

@classmethod
def island(cls, n=16, m=1) -> "Gradient":
def island(cls, n=16, m=2) -> "Gradient":
scope = Gradient.scope(m)

def gradient(a):
for i in range(1, n + 1):
a = np.where(a > i / n * a.max(), a + a / i / 3, a)
return a

return cls(lambda a, *cm: gradient(scope(a, *cm)), "Island")
return cls(lambda a, cm: gradient(scope(a, cm)), "Island")

@classmethod
def marbleFractal(cls, n=0.5) -> "Gradient":
return cls(lambda a, *cm: (np.sin(np.sum(cm, axis=0) * a * n) + 1) / 2, "MarbleSpread")
return cls(lambda a, cm: (np.sin(np.sum(cm, axis=0) * a * n) + 1) / 2, "MarbleSpread")

@classmethod
def marble(cls, n=.5, m=32) -> "Gradient":
return cls(lambda a, *cm: (np.sin((np.sum(cm, axis=0) + a * m) * n) + 1) / 2, "Marble")
return cls(lambda a, cm: (np.sin((np.sum(cm, axis=0) + a * m) * n) + 1) / 2, "Marble")

@classmethod
def invert(cls) -> "Gradient":
return cls(lambda a, *_: a.max() - a, "Invert")
return cls(lambda a, _: a.max() - a, "Invert")

@classmethod
def scope(cls, m=1) -> "Gradient":
if not iterable(m): m = NTuple((m,))

def scope(cls, m=2) -> "Gradient":
def gradient(a, cm):
cm = [(c - c.max() / 2) / c.max() * 2 * m[i] for i, c in enumerate(cm)]
return a * np.where((b := np.sum(np.square(cm), axis=0) ** .5) > 1, 0, 1 - b)
cm -= (_max := cm.max(tuple(range(1, a.ndim + 1)), keepdims=True)) / 2
cm *= 2
cm /= _max
cmm = (cm ** 2).sum(0) / len(cm)
return a * (1 - cmm) ** m

return cls(lambda a, *cm: gradient(a, cm), "Scope")
return cls(lambda a, cm: gradient(a, cm), "Scope")

@classmethod
def none(cls) -> "Gradient":
return cls(lambda a, *_: a, "None")
return cls(lambda a, _: a, "None")


def hexToRGB(cols) -> "np.ndarray[np.ndarray]":
cols = list(cols)
for i, col in enumerate(cols):
col = col[1:]
assert (length := len(col)) in (3, 6, 9), \
assert (length := len(col)) in (3, 6), \
f"invalid color {col}"
length //= 3
col = [int('0x' + col[ch * length:ch * length + length] * (3 - length), 0) for ch in range(3)]
col = [int('0x' + col[ch * length:ch * length + length] * max(3 - length, 1), 0) for ch in range(3)]
cols[i] = col
return np.array(cols, dtype=np.int16)


def rgbToHex(cols) -> list[str]:
cols = list(cols)
for i, col in enumerate(cols):
assert len(col) == 3, \
f"invalid color {col}"
cols[i] = f'#{col[0]:02x}{col[1]:02x}{col[2]:02x}'
return cols


class LinearColorGradient:
def __init__(self, *cols: str, grad: str = 'i'):
self.cols = hexToRGB(cols)
Expand All @@ -155,21 +163,21 @@ def __init__(self, *cols: str, grad: str = 'i'):

def sGradient(self, a):
a = np.array(a)
_range = a.min(d := tuple(range(a.ndim))), a.max(d)
a = (a - _range[0]) / (_range[1] - _range[0])
s = a.flatten()
s.sort()
_as = s.argsort()
length = (len(s) - 1) / (len(self.cols) - 1)
x = np.zeros(a.shape + (3,))
pCol = self.cols[0]
pI = 0
x = np.zeros((np.prod(a.shape), 3))
for i, col in enumerate(self.cols[1:]):
ind = (s[int(i * length)] <= a) & (a <= s[int((i + 1) * length)])
_a = a[ind, None]
_range = _a.min(d := tuple(range(_a.ndim))), _a.max(d)
_a = (_a - _range[0]) / (_range[1] - _range[0])
x[ind] = _a * (col - pCol) + pCol
ind = slice(pI, pI := np.where(s[_as] == s[_as][int((i + 1) * length)])[0][-1])
_s = s[_as[ind], None]
_range = _s.min(d := tuple(range(_s.ndim))), _s.max(d)
_s = (_s - _range[0]) / (_range[1] - _range[0])
_s = np.nan_to_num(_s)
x[_as[ind]] = _s * (col - pCol) + pCol
pCol = col
return x.astype(np.uint8)
return x.reshape(*a.shape, -1).astype(np.uint8)

def iGradient(self, a):
a = np.array(a)
Expand All @@ -192,11 +200,13 @@ def __call__(self, a):
@classmethod
def earth(cls, **kwargs):
return cls(
"#006994",
"#003366",
"#006994",
"#f6d7b0",
"#1F6420",
"#4d8204",
"#1f6d04",
"#6b9b1e",
"#8dbf39",
"#b9d980",
"#977c53",
"#fff",
**kwargs
Expand Down
Loading

0 comments on commit f73eccf

Please sign in to comment.