Skip to content

Commit f8e35ab

Browse files
committed
Add initial files
0 parents  commit f8e35ab

File tree

3 files changed

+235
-0
lines changed

3 files changed

+235
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.toml
2+
*.project.json

src/QubitNoise.lua

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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

tests/SmallTest.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--[[
2+
Noise test #1
3+
4+
Ensuring the noise library works.
5+
]]
6+
7+
local Noise;
8+
-- TODO: complete test

0 commit comments

Comments
 (0)