1
+ --[[
2
+ License:
3
+ Copyright (C) 2021 QuantixDev
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ Description:
19
+ The Qubit Noise Library, is based off noise libraries that may be used in professional game development.
20
+ This noise library contains many features to create a smooth or noisy terrain generation or however you use it.
21
+ The functions are all documented for use and this module is open source as most of the code can be found anyway.
22
+ It was intented to be used in conjuction with my Terrain Generator, but I decided to open source due to the usefullness of
23
+ the module.
24
+
25
+ General Terminology:
26
+ Some terminology in this script may confuse you, I have compiled a list of the terms and their definitions below.
27
+ this serves to describe them and be a "dictionary" for those words:
28
+ Scale - How big the Noise will be (think of it as a 2D Texture)
29
+ Octaves - Combines multiple NoiseMaps to create a more natural looking Noise
30
+ Persistence - The multiplier that determines how quickly amplitudes decreases each octave.
31
+ Lacunarity - The multiplier that determines how quickly the frequency increases each octave.
32
+ Offset - Allows you to "scroll" through the noise, think of a webpage when you scroll through.
33
+ Seed - A value that lets you recreate the same noise, leaving it to tick() will create a semi-random noise value.
34
+ ]]
35
+
36
+ local RunService = game :GetService (" RunService" )
37
+
38
+ local MAX_NOISE_HEIGHT = - 100
39
+ local MIN_NOISE_HEIGHT = - MAX_NOISE_HEIGHT
40
+
41
+ local noiseDeliver = {}
42
+ local noise = {}
43
+
44
+ --[[
45
+ Normalises a value with min and max between 0-1
46
+ Returns <number> _normalisedValue
47
+ (<number> _value, <number> _min, <number> _max)
48
+ - _value is the value you're normalising.
49
+ - _min is the minimum number that value can be.
50
+ - _max is the maximum number that value can be.
51
+ ]]
52
+ local function normalise (_value , _min , _max )
53
+ return (_value - _min ) / (_max - _min );
54
+ end
55
+
56
+
57
+ --[[
58
+ @description Generates a 2DNoiseMap while simulataneously capable of being used
59
+ @usage generate2DPartialMap(noiseWidth, noiseHeight, seed, scale, octaves, persistence, lacunarity, offset)
60
+ @usage generate2DPartialMap(<dictionary> settings)
61
+ @returns <BindableEvent> noiseAdded [returns: <2D Partial NoiseMap>]
62
+ ]]
63
+ noise .generate2DPartialMap = function (_width , _height , _seed , _scale , _octaves , _persistence , _lacunarity , _offset , _normalise )
64
+ -- Allowing the entry of tables (im not proud of this)
65
+ if type (_width ) == " table" and _width .width then
66
+ _height = _width .height
67
+ _seed = _width .seed
68
+ _scale = _width .scale
69
+ _octaves = _width .octaves
70
+ _persistence = _width .persistence
71
+ _lacunarity = _width .lacunarity
72
+ _offset = _width .offset
73
+ _normalise = _width .normalise
74
+ _width = _width .width
75
+ end
76
+
77
+ -- applying defaults
78
+ _width = _width or 50
79
+ _height = _height or 50
80
+ _seed = _seed or 1
81
+ _scale = _scale or 25
82
+ _offset = _offset or Vector2 .new (0 , 0 )
83
+ _normalise = _normalise or false
84
+ _octaves = _octaves or 1
85
+ _lacunarity = _lacunarity or 1
86
+ _persistence = _persistence or 0.5
87
+
88
+ -- Type Checking
89
+ assert (typeof (_width ) == " number" , " Width must be a number, given: " .. typeof (_width ))
90
+ assert (typeof (_height ) == " number" , " Height must be a number, given: " .. typeof (_height ))
91
+ assert (typeof (_seed ) == " number" , " Seed must be a number, given: " .. typeof (_seed ))
92
+ assert (typeof (_scale ) == " number" and _scale > 0 , " Scale must be a number more than 0, given: " .. _scale )
93
+ assert (typeof (_octaves ) == " number" , " Octaves must be a number." )
94
+ assert (typeof (_persistence ) == " number" and _persistence >= 0 and _persistence <= 1 , " Persistence must be a number between 0 and 1." )
95
+ assert (typeof (_lacunarity ) == " number" and _lacunarity >= 1 , " Lacunarity must be a number more than or equal to 1." )
96
+ assert (typeof (_offset ) == " Vector2" , " Offset must be a Vector2." )
97
+
98
+ noiseDeliver [_seed ] = {}
99
+
100
+ local elementAdded , generationFinished = Instance .new (" BindableEvent" ), false
101
+ coroutine.wrap (function ()
102
+ local octaveOffsets = {}
103
+ local randomClass = Random .new (_seed )
104
+ local widthHalf = _width / 2
105
+ local heightHalf = _height / 2
106
+ noiseDeliver [_seed ][" MinNoise" ] = MIN_NOISE_HEIGHT
107
+ noiseDeliver [_seed ][" MaxNoise" ] = MAX_NOISE_HEIGHT
108
+
109
+ -- octaves, more octaves make for a more natural noise (essentially offsets the noise more and more)
110
+ for i = 0 , _octaves - 1 do
111
+ local xOffset = randomClass :NextInteger (- 10000 , 10000 ) + _offset .X
112
+ local yOffset = randomClass :NextInteger (- 10000 , 10000 ) + _offset .Y
113
+
114
+ table.insert (octaveOffsets , i , Vector2 .new (xOffset , yOffset ))
115
+ end
116
+
117
+ -- creating a noise map for the scale _width * _height
118
+ for x = 1 , _width do
119
+ noiseDeliver [_seed ][x ] = {}
120
+
121
+ for y = 1 , _height do
122
+ local frequency , amplitude , noiseHeight = 1 , 1 , 0
123
+ for ocI = 0 , _octaves - 1 do
124
+ local sampleX = (x - widthHalf ) / _scale * frequency + octaveOffsets [ocI ].x
125
+ local sampleY = (y - heightHalf ) / _scale * frequency + octaveOffsets [ocI ].y
126
+
127
+ -- using the built-in c noise function
128
+ local noiseValue = math .noise (sampleX , sampleY ) * 2 - 1
129
+ noiseHeight = noiseHeight + (noiseValue * amplitude )
130
+
131
+ -- applying the persistence and lacunarity values
132
+ amplitude = amplitude * _persistence
133
+ frequency = frequency * _lacunarity
134
+ end
135
+
136
+ -- getting the min and max
137
+ noiseDeliver [_seed ][" MinNoise" ] = math.min (noiseHeight , noiseDeliver [_seed ][" MinNoise" ])
138
+ noiseDeliver [_seed ][" MaxNoise" ] = math.max (noiseHeight , noiseDeliver [_seed ][" MaxNoise" ])
139
+ noiseDeliver [_seed ][x ][y ] = noiseHeight
140
+ end
141
+ end
142
+
143
+ -- another loop here, to clamp the noise values
144
+ for x = 1 , _width do
145
+ for y = 1 , _height do
146
+ if not noiseDeliver [_seed ][x ][y ] then
147
+ noiseDeliver [_seed ][x ][y ] = 1
148
+ end
149
+
150
+ if _normalise then
151
+ noiseDeliver [_seed ][x ][y ] = normalise (noiseDeliver [_seed ][x ][y ], noiseDeliver [_seed ][" MinNoise" ], noiseDeliver [_seed ][" MaxNoise" ])
152
+ end
153
+ elementAdded :Fire (x , y , noiseDeliver [_seed ][x ][y ])
154
+ end
155
+ end
156
+
157
+ generationFinished = true
158
+ end )()
159
+
160
+ return elementAdded .Event , generationFinished
161
+ end
162
+
163
+ --[[
164
+ @description Creates a 2D noiseMap which is a table full of 2D Noise Values (to save on calculations)
165
+ @usage generate2DNoiseMap(noiseWidth, noiseHeight, seed, scale, octaves, persistence, lacunarity, offset)
166
+ @usage generate2DNoiseMap(<dictionary> settings)
167
+ @returns <2D Dictionary> noiseMap[x][y]
168
+ ]]
169
+ noise .generate2DNoiseMap = function (...)
170
+ local args = {... }
171
+ local seed = args [3 ] or args [1 ].seed or 1
172
+
173
+ local onNoiseAdded , noiseGenerated = noise .generate2DPartialMap (... )
174
+ while not noiseGenerated and not noiseDeliver [seed ] do
175
+ onNoiseAdded :Wait ()
176
+ end
177
+
178
+ return noiseDeliver [seed ]
179
+ end
180
+
181
+ --[[
182
+ @description Generates a radial gradient around _center of values in a table.
183
+ @usage generateRadialGradient(width, length, center, doNormalisation)
184
+ @returns <2D Dictionary> or noisemap["min"] / noisemap["max"]
185
+ ]]
186
+ noise .generateRadialGradient = function (_width , _length , _doNormalisation )
187
+ local gradients = {}
188
+ gradients [" min" ] = MIN_NOISE_HEIGHT
189
+ gradients [" max" ] = MAX_NOISE_HEIGHT
190
+
191
+ local center = Vector3 .new (_width / 2 , 1 , _length / 2 )
192
+ local shouldNormalise = false
193
+ for _ = 0 , 1 do
194
+ for x = 1 , _width do
195
+ gradients [x ] = gradients [x ] or {}
196
+
197
+ for z = 1 , _length do
198
+ if not shouldNormalise then
199
+ local pos = Vector3 .new (x , 1 , z )
200
+ local dist = (pos - center ).magnitude * 0.005
201
+ local clr = dist % 2
202
+ clr = (clr > 1 ) and (2 - clr ) or clr
203
+
204
+ gradients [x ][z ] = math .clamp (math.abs ((clr - 1 )), 0 , 1 )
205
+ gradients [" min" ] = math.min (gradients [x ][z ], gradients [" min" ])
206
+ gradients [" max" ] = math.max (gradients [x ][z ], gradients [" max" ])
207
+ else
208
+ gradients [x ][z ] = normalise (gradients [x ][z ], gradients [" min" ], gradients [" max" ])
209
+ end
210
+ end
211
+
212
+ RunService .Heartbeat :Wait ()
213
+ end
214
+
215
+ if _doNormalisation and not shouldNormalise then
216
+ shouldNormalise = true
217
+ else
218
+ break
219
+ end
220
+ end
221
+
222
+ return gradients
223
+ end
224
+
225
+ return noise
0 commit comments