Issues (838)

tcod/noise.py (1 issue)

1
"""
2
The :any:`Noise.sample_mgrid` and :any:`Noise.sample_ogrid` methods are
3
multi-threaded operations when the Python runtime supports OpenMP.
4
Even when single threaded these methods will perform much better than
5
multiple calls to :any:`Noise.get_point`.
6
7
Example::
8
9
    import numpy as np
10
    import tcod
11
    import tcod.noise
12
13
    noise = tcod.noise.Noise(
14
        dimensions=2,
15
        algorithm=tcod.NOISE_SIMPLEX,
16
        implementation=tcod.noise.TURBULENCE,
17
        hurst=0.5,
18
        lacunarity=2.0,
19
        octaves=4,
20
        seed=None,
21
        )
22
23
    # Create a 5x5 open multi-dimensional mesh-grid.
24
    ogrid = [np.arange(5, dtype=np.float32),
25
             np.arange(5, dtype=np.float32)]
26
    print(ogrid)
27
28
    # Scale the grid.
29
    ogrid[0] *= 0.25
30
    ogrid[1] *= 0.25
31
32
    # Return the sampled noise from this grid of points.
33
    samples = noise.sample_ogrid(ogrid)
34
    print(samples)
35
"""
36
from __future__ import absolute_import
37
38
import numpy as np
39
40
from tcod.libtcod import ffi, lib
41
import tcod.libtcod
42
43
"""Noise implementation constants"""
0 ignored issues
show
This string statement has no effect and could be removed.
Loading history...
44
SIMPLE = 0
45
FBM = 1
46
TURBULENCE = 2
47
48
class Noise(object):
49
    """
50
51
    The ``hurst`` exponent describes the raggedness of the resultant noise,
52
    with a higher value leading to a smoother noise.
53
    Not used with tcod.noise.SIMPLE.
54
55
    ``lacunarity`` is a multiplier that determines how fast the noise
56
    frequency increases for each successive octave.
57
    Not used with tcod.noise.SIMPLE.
58
59
    Args:
60
        dimensions (int): Must be from 1 to 4.
61
        algorithm (int): Defaults to NOISE_SIMPLEX
62
        implementation (int): Defaults to tcod.noise.SIMPLE
63
        hurst (float): The hurst exponent.  Should be in the 0.0-1.0 range.
64
        lacunarity (float): The noise lacunarity.
65
        octaves (float): The level of detail on fBm and turbulence
66
                         implementations.
67
        seed (Optional[Random]): A Random instance, or None.
68
69
    Attributes:
70
        noise_c (CData): A cffi pointer to a TCOD_noise_t object.
71
    """
72
73
    def __init__(self, dimensions, algorithm=2, implementation=SIMPLE,
74
                 hurst=0.5, lacunarity=2.0, octaves=4, seed=None):
75
        if not 0 < dimensions <= 4:
76
            raise ValueError('dimensions must be in range 0 < n <= 4, got %r' %
77
                             (dimensions,))
78
        self._random = seed
79
        _random_c = seed.random_c if seed else ffi.NULL
80
        self._algorithm = algorithm
81
        self.noise_c = ffi.gc(
82
            ffi.cast(
83
                'perlin_data_t*',
84
                lib.TCOD_noise_new(dimensions, hurst, lacunarity,
85
                                   _random_c),
86
                ),
87
            lib.TCOD_noise_delete)
88
        self._tdl_noise_c = ffi.new('TDLNoise*', (self.noise_c,
89
                                                  dimensions,
90
                                                  0,
91
                                                  octaves))
92
        self.implementation = implementation # sanity check
93
94
    @property
95
    def dimensions(self):
96
        return self._tdl_noise_c.dimensions
97
98
    @property
99
    def dimentions(self): # deprecated
100
        return self.dimensions
101
102
    @property
103
    def algorithm(self):
104
        return self.noise_c.noise_type
105
    @algorithm.setter
106
    def algorithm(self, value):
107
        lib.TCOD_noise_set_type(self.noise_c, value)
108
109
    @property
110
    def implementation(self):
111
        return self._tdl_noise_c.implementation
112
    @implementation.setter
113
    def implementation(self, value):
114
        if not 0 <= value < 3:
115
            raise ValueError('%r is not a valid implementation. ' % (value,))
116
        self._tdl_noise_c.implementation = value
117
118
    @property
119
    def hurst(self):
120
        return self.noise_c.H
121
122
    @property
123
    def lacunarity(self):
124
        return self.noise_c.lacunarity
125
126
    @property
127
    def octaves(self):
128
        return self._tdl_noise_c.octaves
129
    @octaves.setter
130
    def octaves(self, value):
131
        self._tdl_noise_c.octaves = value
132
133
    def get_point(self, x=0, y=0, z=0, w=0):
134
        """Return the noise value at the (x, y, z, w) point.
135
136
        Args:
137
            x (float): The position on the 1st axis.
138
            y (float): The position on the 2nd axis.
139
            z (float): The position on the 3rd axis.
140
            w (float): The position on the 4th axis.
141
        """
142
        return lib.NoiseGetSample(self._tdl_noise_c, (x, y, z, w))
143
144
    def sample_mgrid(self, mgrid):
145
        """Sample a mesh-grid array and return the result.
146
147
        The :any:`sample_ogrid` method performs better as there is a lot of
148
        overhead when working with large mesh-grids.
149
150
        Args:
151
            mgrid (numpy.ndarray): A mesh-grid array of points to sample.
152
                A contiguous array of type `numpy.float32` is preferred.
153
154
        Returns:
155
            numpy.ndarray: An array of sampled points.
156
157
                This array has the shape: ``mgrid.shape[:-1]``.
158
                The ``dtype`` is `numpy.float32`.
159
        """
160
        mgrid = np.ascontiguousarray(mgrid, np.float32)
161
        if mgrid.shape[0] != self.dimensions:
162
            raise ValueError('mgrid.shape[0] must equal self.dimensions, '
163
                             '%r[0] != %r' % (mgrid.shape, self.dimensions))
164
        out = np.ndarray(mgrid.shape[1:], np.float32)
165
        if mgrid.shape[1:] != out.shape:
166
            raise ValueError('mgrid.shape[1:] must equal out.shape, '
167
                             '%r[1:] != %r' % (mgrid.shape, out.shape))
168
        lib.NoiseSampleMeshGrid(self._tdl_noise_c, out.size,
169
                                ffi.cast('float*', mgrid.ctypes.data),
170
                                ffi.cast('float*', out.ctypes.data))
171
        return out
172
173
    def sample_ogrid(self, ogrid):
174
        """Sample an open mesh-grid array and return the result.
175
176
        Args
177
            ogrid (Sequence[Sequence[float]]): An open mesh-grid.
178
179
        Returns:
180
            numpy.ndarray: An array of sampled points.
181
182
                The ``shape`` is based on the lengths of the open mesh-grid
183
                arrays.
184
                The ``dtype`` is `numpy.float32`.
185
        """
186
        if len(ogrid) != self.dimensions:
187
            raise ValueError('len(ogrid) must equal self.dimensions, '
188
                             '%r != %r' % (len(ogrid), self.dimensions))
189
        ogrids = [np.ascontiguousarray(array, np.float32) for array in ogrid]
190
        out = np.ndarray([array.size for array in ogrids], np.float32)
191
        lib.NoiseSampleOpenMeshGrid(
192
            self._tdl_noise_c,
193
            len(ogrids),
194
            out.shape,
195
            [ffi.cast('float*', array.ctypes.data) for array in ogrids],
196
            ffi.cast('float*', out.ctypes.data),
197
            )
198
        return out
199
200
    def __getstate__(self):
201
        state = self.__dict__.copy()
202
        if self.dimensions < 4 and self.noise_c.waveletTileData == ffi.NULL:
203
            # Trigger a side effect of wavelet, so that copies will be synced.
204
            saved_algo = self.algorithm
205
            self.algorithm = tcod.libtcod.NOISE_WAVELET
206
            self.get_point()
207
            self.algorithm = saved_algo
208
209
        waveletTileData = None
210
        if self.noise_c.waveletTileData != ffi.NULL:
211
            waveletTileData = list(self.noise_c.waveletTileData[0:32*32*32])
212
            state['_waveletTileData'] = waveletTileData
213
214
        state['noise_c'] = {
215
            'ndim': self.noise_c.ndim,
216
            'map': list(self.noise_c.map),
217
            'buffer': [list(sub_buffer) for sub_buffer in self.noise_c.buffer],
218
            'H': self.noise_c.H,
219
            'lacunarity': self.noise_c.lacunarity,
220
            'exponent': list(self.noise_c.exponent),
221
            'waveletTileData': waveletTileData,
222
            'noise_type': self.noise_c.noise_type,
223
            }
224
        state['_tdl_noise_c'] = {
225
            'dimensions': self._tdl_noise_c.dimensions,
226
            'implementation': self._tdl_noise_c.implementation,
227
            'octaves': self._tdl_noise_c.octaves,
228
            }
229
        return state
230
231
    def __setstate__(self, state):
232
        if isinstance(state, tuple): # deprecated format
233
            return self._setstate_old(state)
234
        # unpack wavelet tile data if it exists
235
        if '_waveletTileData' in state:
236
            state['_waveletTileData'] = ffi.new('float[]',
237
                                                state['_waveletTileData'])
238
            state['noise_c']['waveletTileData'] = state['_waveletTileData']
239
        else:
240
            state['noise_c']['waveletTileData'] = ffi.NULL
241
242
        # unpack perlin_data_t and link to Random instance
243
        state['noise_c']['rand'] = state['_random'].random_c
244
        state['noise_c'] = ffi.new('perlin_data_t*', state['noise_c'])
245
246
        # unpack TDLNoise and link to libtcod noise
247
        state['_tdl_noise_c']['noise'] = state['noise_c']
248
        state['_tdl_noise_c'] = ffi.new('TDLNoise*', state['_tdl_noise_c'])
249
        self.__dict__.update(state)
250
251
    def _setstate_old(self, state):
252
        self._random = state[0]
253
        self.noise_c = ffi.new('perlin_data_t*')
254
        self.noise_c.ndim = state[3]
255
        ffi.buffer(self.noise_c.map)[:] = state[4]
256
        ffi.buffer(self.noise_c.buffer)[:] = state[5]
257
        self.noise_c.H = state[6]
258
        self.noise_c.lacunarity = state[7]
259
        ffi.buffer(self.noise_c.exponent)[:] = state[8]
260
        if state[9]:
261
            # high change of this being prematurely garbage collected!
262
            self.__waveletTileData = ffi.new('float[]', 32*32*32)
263
            ffi.buffer(self.__waveletTileData)[:] = state[9]
264
        self.noise_c.noise_type = state[10]
265
        self._tdl_noise_c = ffi.new('TDLNoise*',
266
                                    (self.noise_c, self.noise_c.ndim,
267
                                     state[1], state[2]))
268