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
Unused Code
introduced
by
![]() |
|||
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 |