Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/bench/tests/voxelgen.lua
2725 views
1
local function prequire(name) local success, result = pcall(require, name); return success and result end
2
local bench = script and require(script.Parent.bench_support) or prequire("bench_support") or require("../bench_support")
3
4
-- Based on voxel terrain generator by Stickmasterluke
5
6
local kSelectedBiomes = {
7
['Mountains'] = true,
8
['Canyons'] = true,
9
['Dunes'] = true,
10
['Arctic'] = true,
11
['Lavaflow'] = true,
12
['Hills'] = true,
13
['Plains'] = true,
14
['Marsh'] = true,
15
['Water'] = true,
16
}
17
18
---------Directly used in Generation---------
19
local masterSeed = 618033988
20
local mapWidth = 32
21
local mapHeight = 32
22
local biomeSize = 16
23
local generateCaves = true
24
local waterLevel = .48
25
local surfaceThickness = .018
26
local biomes = {}
27
---------------------------------------------
28
29
local rock = "Rock"
30
local snow = "Snow"
31
local ice = "Glacier"
32
local grass = "Grass"
33
local ground = "Ground"
34
local mud = "Mud"
35
local slate = "Slate"
36
local concrete = "Concrete"
37
local lava = "CrackedLava"
38
local basalt = "Basalt"
39
local air = "Air"
40
local sand = "Sand"
41
local sandstone = "Sandstone"
42
local water = "Water"
43
44
math.randomseed(6180339)
45
local theseed={}
46
for i=1,999 do
47
table.insert(theseed,math.random())
48
end
49
50
local function getPerlin(x,y,z,seed,scale,raw)
51
local seed = seed or 0
52
local scale = scale or 1
53
if not raw then
54
return math.noise(x/scale+(seed*17)+masterSeed,y/scale-masterSeed,z/scale-seed*seed)*.5 + .5 -- accounts for bleeding from interpolated line
55
else
56
return math.noise(x/scale+(seed*17)+masterSeed,y/scale-masterSeed,z/scale-seed*seed)
57
end
58
end
59
60
local function getNoise(x,y,z,seed1)
61
local x = x or 0
62
local y = y or 0
63
local z = z or 0
64
local seed1 = seed1 or 7
65
local wtf=x+y+z+seed1+masterSeed + (masterSeed-x)*(seed1+z) + (seed1-y)*(masterSeed+z) -- + x*(y+z) + z*(masterSeed+seed1) + seed1*(x+y) --x+y+z+seed1+masterSeed + x*y*masterSeed-y*z+(z+masterSeed)*x --((x+y)*(y-seed1)*seed1)-(x+z)*seed2+x*11+z*23-y*17
66
return theseed[(math.floor(wtf%(#theseed)))+1]
67
end
68
69
local function thresholdFilter(value, bottom, size)
70
if value <= bottom then
71
return 0
72
elseif value >= bottom+size then
73
return 1
74
else
75
return (value-bottom)/size
76
end
77
end
78
79
local function ridgedFilter(value) --absolute and flip for ridges. and normalize
80
return value<.5 and value*2 or 2-value*2
81
end
82
83
local function ridgedFlippedFilter(value) --unflipped
84
return value < .5 and 1-value*2 or value*2-1
85
end
86
87
local function advancedRidgedFilter(value, cutoff)
88
local cutoff = cutoff or .5
89
value = value - cutoff
90
return 1 - (value < 0 and -value or value) * 1/(1-cutoff)
91
end
92
93
local function fractalize(operation,x,y,z, operationCount, scale, offset, gain)
94
local operationCount = operationCount or 3
95
local scale = scale or .5
96
local offset = 0
97
local gain = gain or 1
98
local totalValue = 0
99
local totalScale = 0
100
for i=1, operationCount do
101
local thisScale = scale^(i-1)
102
totalScale = totalScale + thisScale
103
totalValue = totalValue + (offset + gain * operation(x,y,z,i))*thisScale
104
end
105
return totalValue/totalScale
106
end
107
108
local function mountainsOperation(x,y,z,i)
109
return ridgedFilter(getPerlin(x,y,z,100+i,(1/i)*160))
110
end
111
112
local canyonBandingMaterial = {rock,mud,sand,sand,sandstone,sandstone,sandstone,sandstone,sandstone,sandstone,}
113
114
local function findBiomeInfo(choiceBiome,x,y,z,verticalGradientTurbulence)
115
local choiceBiomeValue = .5
116
local choiceBiomeSurface = grass
117
local choiceBiomeFill = rock
118
if choiceBiome == 'City' then
119
choiceBiomeValue = .55
120
choiceBiomeSurface = concrete
121
choiceBiomeFill = slate
122
elseif choiceBiome == 'Water' then
123
choiceBiomeValue = .36+getPerlin(x,y,z,2,50)*.08
124
choiceBiomeSurface =
125
(1-verticalGradientTurbulence < .44 and slate)
126
or sand
127
elseif choiceBiome == 'Marsh' then
128
local preLedge = getPerlin(x+getPerlin(x,0,z,5,7,true)*10+getPerlin(x,0,z,6,30,true)*50,0,z+getPerlin(x,0,z,9,7,true)*10+getPerlin(x,0,z,10,30,true)*50,2,70) --could use some turbulence
129
local grassyLedge = thresholdFilter(preLedge,.65,0)
130
local largeGradient = getPerlin(x,y,z,4,100)
131
local smallGradient = getPerlin(x,y,z,3,20)
132
local smallGradientThreshold = thresholdFilter(smallGradient,.5,0)
133
choiceBiomeValue = waterLevel-.04
134
+preLedge*grassyLedge*.025
135
+largeGradient*.035
136
+smallGradient*.025
137
choiceBiomeSurface =
138
(grassyLedge >= 1 and grass)
139
or (1-verticalGradientTurbulence < waterLevel-.01 and mud)
140
or (1-verticalGradientTurbulence < waterLevel+.01 and ground)
141
or grass
142
choiceBiomeFill = slate
143
elseif choiceBiome == 'Plains' then
144
local rivulet = ridgedFlippedFilter(getPerlin(x+getPerlin(x,y,z,17,40)*25,0,z+getPerlin(x,y,z,19,40)*25,2,200))
145
local rivuletThreshold = thresholdFilter(rivulet,.01,0)
146
147
local rockMap = thresholdFilter(ridgedFlippedFilter(getPerlin(x,0,z,101,7)),.3,.7) --rocks
148
* thresholdFilter(getPerlin(x,0,z,102,50),.6,.05) --zoning
149
150
choiceBiomeValue = .5 --.51
151
+getPerlin(x,y,z,2,100)*.02 --.05
152
+rivulet*.05 --.02
153
+rockMap*.05 --.03
154
+rivuletThreshold*.005
155
156
local verticalGradient = 1-((y-1)/(mapHeight-1))
157
local surfaceGradient = verticalGradient*.5 + choiceBiomeValue*.5
158
local thinSurface = surfaceGradient > .5-surfaceThickness*.4 and surfaceGradient < .5+surfaceThickness*.4
159
choiceBiomeSurface =
160
(rockMap>0 and rock)
161
or (not thinSurface and mud)
162
or (thinSurface and rivuletThreshold <=0 and water)
163
or (1-verticalGradientTurbulence < waterLevel-.01 and sand)
164
or grass
165
choiceBiomeFill =
166
(rockMap>0 and rock)
167
or sandstone
168
elseif choiceBiome == 'Canyons' then
169
local canyonNoise = ridgedFlippedFilter(getPerlin(x,0,z,2,200))
170
local canyonNoiseTurbed = ridgedFlippedFilter(getPerlin(x+getPerlin(x,0,z,5,20,true)*20,0,z+getPerlin(x,0,z,9,20,true)*20,2,200))
171
local sandbank = thresholdFilter(canyonNoiseTurbed,0,.05)
172
local canyonTop = thresholdFilter(canyonNoiseTurbed,.125,0)
173
local mesaSlope = thresholdFilter(canyonNoise,.33,.12)
174
local mesaTop = thresholdFilter(canyonNoiseTurbed,.49,0)
175
choiceBiomeValue = .42
176
+getPerlin(x,y,z,2,70)*.05
177
+canyonNoise*.05
178
+sandbank*.04 --canyon bottom slope
179
+thresholdFilter(canyonNoiseTurbed,.05,0)*.08 --canyon cliff
180
+thresholdFilter(canyonNoiseTurbed,.05,.075)*.04 --canyon cliff top slope
181
+canyonTop*.01 --canyon cliff top ledge
182
183
+thresholdFilter(canyonNoiseTurbed,.0575,.2725)*.01 --plane slope
184
185
+mesaSlope*.06 --mesa slope
186
+thresholdFilter(canyonNoiseTurbed,.45,0)*.14 --mesa cliff
187
+thresholdFilter(canyonNoiseTurbed,.45,.04)*.025 --mesa cap
188
+mesaTop*.02 --mesa top ledge
189
choiceBiomeSurface =
190
(1-verticalGradientTurbulence < waterLevel+.015 and sand) --this for biome blending in to lakes
191
or (sandbank>0 and sandbank<1 and sand) --this for canyonbase sandbanks
192
--or (canyonTop>0 and canyonTop<=1 and mesaSlope<=0 and grass) --this for grassy canyon tops
193
--or (mesaTop>0 and mesaTop<=1 and grass) --this for grassy mesa tops
194
or sandstone
195
choiceBiomeFill = canyonBandingMaterial[math.ceil((1-getNoise(1,y,2))*10)]
196
elseif choiceBiome == 'Hills' then
197
local rivulet = ridgedFlippedFilter(getPerlin(x+getPerlin(x,y,z,17,20)*20,0,z+getPerlin(x,y,z,19,20)*20,2,200))^(1/2)
198
local largeHills = getPerlin(x,y,z,3,60)
199
choiceBiomeValue = .48
200
+largeHills*.05
201
+(.05
202
+largeHills*.1
203
+getPerlin(x,y,z,4,25)*.125)
204
*rivulet
205
local surfaceMaterialGradient = (1-verticalGradientTurbulence)*.9 + rivulet*.1
206
choiceBiomeSurface =
207
(surfaceMaterialGradient < waterLevel-.015 and mud)
208
or (surfaceMaterialGradient < waterLevel and ground)
209
or grass
210
choiceBiomeFill = slate
211
elseif choiceBiome == 'Dunes' then
212
local duneTurbulence = getPerlin(x,0,z,227,20)*24
213
local layer1 = ridgedFilter(getPerlin(x,0,z,201,40))
214
local layer2 = ridgedFilter(getPerlin(x/10+duneTurbulence,0,z+duneTurbulence,200,48))
215
choiceBiomeValue = .4+.1*(layer1 + layer2)
216
choiceBiomeSurface = sand
217
choiceBiomeFill = sandstone
218
elseif choiceBiome == 'Mountains' then
219
local rivulet = ridgedFlippedFilter(getPerlin(x+getPerlin(x,y,z,17,20)*20,0,z+getPerlin(x,y,z,19,20)*20,2,200))
220
choiceBiomeValue = -.4 --.3
221
+fractalize(mountainsOperation,x,y/20,z, 8, .65)*1.2
222
+rivulet*.2
223
choiceBiomeSurface =
224
(verticalGradientTurbulence < .275 and snow)
225
or (verticalGradientTurbulence < .35 and rock)
226
or (verticalGradientTurbulence < .4 and ground)
227
or (1-verticalGradientTurbulence < waterLevel and rock)
228
or (1-verticalGradientTurbulence < waterLevel+.01 and mud)
229
or (1-verticalGradientTurbulence < waterLevel+.015 and ground)
230
or grass
231
elseif choiceBiome == 'Lavaflow' then
232
local crackX = x+getPerlin(x,y*.25,z,21,8,true)*5
233
local crackY = y+getPerlin(x,y*.25,z,22,8,true)*5
234
local crackZ = z+getPerlin(x,y*.25,z,23,8,true)*5
235
local crack1 = ridgedFilter(getPerlin(crackX+getPerlin(x,y,z,22,30,true)*30,crackY,crackZ+getPerlin(x,y,z,24,30,true)*30,2,120))
236
local crack2 = ridgedFilter(getPerlin(crackX,crackY,crackZ,3,40))*(crack1*.25+.75)
237
local crack3 = ridgedFilter(getPerlin(crackX,crackY,crackZ,4,20))*(crack2*.25+.75)
238
239
local generalHills = thresholdFilter(getPerlin(x,y,z,9,40),.25,.5)*getPerlin(x,y,z,10,60)
240
241
local cracks = math.max(0,1-thresholdFilter(crack1,.975,0)-thresholdFilter(crack2,.925,0)-thresholdFilter(crack3,.9,0))
242
243
local spires = thresholdFilter(getPerlin(crackX/40,crackY/300,crackZ/30,123,1),.6,.4)
244
245
choiceBiomeValue = waterLevel+.02
246
+cracks*(.5+generalHills*.5)*.02
247
+generalHills*.05
248
+spires*.3
249
+((1-verticalGradientTurbulence > waterLevel+.01 or spires>0) and .04 or 0) --This lets it lip over water
250
251
choiceBiomeFill = (spires>0 and rock) or (cracks<1 and lava) or basalt
252
choiceBiomeSurface = (choiceBiomeFill == lava and 1-verticalGradientTurbulence < waterLevel and basalt) or choiceBiomeFill
253
elseif choiceBiome == 'Arctic' then
254
local preBoundary = getPerlin(x+getPerlin(x,0,z,5,8,true)*5,y/8,z+getPerlin(x,0,z,9,8,true)*5,2,20)
255
--local cliffs = thresholdFilter(preBoundary,.5,0)
256
local boundary = ridgedFilter(preBoundary)
257
local roughChunks = getPerlin(x,y/4,z,436,2)
258
local boundaryMask = thresholdFilter(boundary,.8,.1) --,.7,.25)
259
local boundaryTypeMask = getPerlin(x,0,z,6,74)-.5
260
local boundaryComp = 0
261
if boundaryTypeMask < 0 then --divergent
262
boundaryComp = (boundary > (1+boundaryTypeMask*.5) and -.17 or 0)
263
--* boundaryTypeMask*-2
264
else --convergent
265
boundaryComp = boundaryMask*.1*roughChunks
266
* boundaryTypeMask
267
end
268
choiceBiomeValue = .55
269
+boundary*.05*boundaryTypeMask --.1 --soft slope up or down to boundary
270
+boundaryComp --convergent/divergent effects
271
+getPerlin(x,0,z,123,25)*.025 --*cliffs --gentle rolling slopes
272
273
choiceBiomeSurface = (1-verticalGradientTurbulence < waterLevel-.1 and ice) or (boundaryMask>.6 and boundaryTypeMask>.1 and roughChunks>.5 and ice) or snow
274
choiceBiomeFill = ice
275
end
276
return choiceBiomeValue, choiceBiomeSurface, choiceBiomeFill
277
end
278
279
function findBiomeTransitionValue(biome,weight,value,averageValue)
280
if biome == 'Arctic' then
281
return (weight>.2 and 1 or 0)*value
282
elseif biome == 'Canyons' then
283
return (weight>.7 and 1 or 0)*value
284
elseif biome == 'Mountains' then
285
local weight = weight^3 --This improves the ease of mountains transitioning to other biomes
286
return averageValue*(1-weight)+value*weight
287
else
288
return averageValue*(1-weight)+value*weight
289
end
290
end
291
292
function generate()
293
local mapWidth = mapWidth
294
local biomeSize = biomeSize
295
local biomeBlendPercent = .25 --(biomeSize==50 or biomeSize == 100) and .5 or .25
296
local biomeBlendPercentInverse = 1-biomeBlendPercent
297
local biomeBlendDistortion = biomeBlendPercent
298
local smoothScale = .5/mapHeight
299
300
biomes = {}
301
for i,v in pairs(kSelectedBiomes) do
302
if v then
303
table.insert(biomes,i)
304
end
305
end
306
if #biomes<=0 then
307
table.insert(biomes,'Hills')
308
end
309
table.sort(biomes)
310
--local oMap = {}
311
--local mMap = {}
312
for x = 1, mapWidth do
313
local oMapX = {}
314
--oMap[x] = oMapX
315
local mMapX = {}
316
--mMap[x] = mMapX
317
for z = 1, mapWidth do
318
local biomeNoCave = false
319
local cellToBiomeX = x/biomeSize + getPerlin(x,0,z,233,biomeSize*.3)*.25 + getPerlin(x,0,z,235,biomeSize*.05)*.075
320
local cellToBiomeZ = z/biomeSize + getPerlin(x,0,z,234,biomeSize*.3)*.25 + getPerlin(x,0,z,236,biomeSize*.05)*.075
321
local closestDistance = 1000000
322
local biomePoints = {}
323
for vx=-1,1 do
324
for vz=-1,1 do
325
local gridPointX = math.floor(cellToBiomeX+vx+.5)
326
local gridPointZ = math.floor(cellToBiomeZ+vz+.5)
327
--local pointX, pointZ = getBiomePoint(gridPointX,gridPointZ)
328
local pointX = gridPointX+(getNoise(gridPointX,gridPointZ,53)-.5)*.75 --de-uniforming grid for vornonoi
329
local pointZ = gridPointZ+(getNoise(gridPointX,gridPointZ,73)-.5)*.75
330
331
local dist = math.sqrt((pointX-cellToBiomeX)^2 + (pointZ-cellToBiomeZ)^2)
332
if dist < closestDistance then
333
closestDistance = dist
334
end
335
table.insert(biomePoints,{
336
x = pointX,
337
z = pointZ,
338
dist = dist,
339
biomeNoise = getNoise(gridPointX,gridPointZ),
340
weight = 0
341
})
342
end
343
end
344
local weightTotal = 0
345
local weightPoints = {}
346
for _,point in pairs(biomePoints) do
347
local weight = point.dist == closestDistance and 1 or ((closestDistance / point.dist)-biomeBlendPercentInverse)/biomeBlendPercent
348
if weight > 0 then
349
local weight = weight^2.1 --this smooths the biome transition from linear to cubic InOut
350
weightTotal = weightTotal + weight
351
local biome = biomes[math.ceil(#biomes*(1-point.biomeNoise))] --inverting the noise so that it is limited as (0,1]. One less addition operation when finding a random list index
352
weightPoints[biome] = {
353
weight = weightPoints[biome] and weightPoints[biome].weight + weight or weight
354
}
355
end
356
end
357
for biome,info in pairs(weightPoints) do
358
info.weight = info.weight / weightTotal
359
if biome == 'Arctic' then --biomes that don't have caves that breach the surface
360
biomeNoCave = true
361
end
362
end
363
364
365
for y = 1, mapHeight do
366
local oMapY = oMapX[y] or {}
367
oMapX[y] = oMapY
368
local mMapY = mMapX[y] or {}
369
mMapX[y] = mMapY
370
371
--[[local oMapY = {}
372
oMapX[y] = oMapY
373
local mMapY = {}
374
mMapX[z] = mMapY]]
375
376
377
local verticalGradient = 1-((y-1)/(mapHeight-1))
378
local caves = 0
379
local verticalGradientTurbulence = verticalGradient*.9 + .1*getPerlin(x,y,z,107,15)
380
local choiceValue = 0
381
local choiceSurface = lava
382
local choiceFill = rock
383
384
if verticalGradient > .65 or verticalGradient < .1 then
385
--under surface of every biome; don't get biome data; waste of time.
386
choiceValue = .5
387
elseif #biomes == 1 then
388
choiceValue, choiceSurface, choiceFill = findBiomeInfo(biomes[1],x,y,z,verticalGradientTurbulence)
389
else
390
local averageValue = 0
391
--local findChoiceMaterial = -getNoise(x,y,z,19)
392
for biome,info in pairs(weightPoints) do
393
local biomeValue, biomeSurface, biomeFill = findBiomeInfo(biome,x,y,z,verticalGradientTurbulence)
394
info.biomeValue = biomeValue
395
info.biomeSurface = biomeSurface
396
info.biomeFill = biomeFill
397
local value = biomeValue * info.weight
398
averageValue = averageValue + value
399
--[[if findChoiceMaterial < 0 and findChoiceMaterial + weight >= 0 then
400
choiceMaterial = biomeMaterial
401
end
402
findChoiceMaterial = findChoiceMaterial + weight]]
403
end
404
for biome,info in pairs(weightPoints) do
405
local value = findBiomeTransitionValue(biome,info.weight,info.biomeValue,averageValue)
406
if value > choiceValue then
407
choiceValue = value
408
choiceSurface = info.biomeSurface
409
choiceFill = info.biomeFill
410
end
411
end
412
end
413
414
local preCaveComp = verticalGradient*.5 + choiceValue*.5
415
416
local surface = preCaveComp > .5-surfaceThickness and preCaveComp < .5+surfaceThickness
417
418
if generateCaves --user wants caves
419
and (not biomeNoCave or verticalGradient > .65) --biome allows caves or deep enough
420
and not (surface and (1-verticalGradient) < waterLevel+.005) --caves only breach surface above waterlevel
421
and not (surface and (1-verticalGradient) > waterLevel+.58) then --caves don't go too high so that they don't cut up mountain tops
422
local ridged2 = ridgedFilter(getPerlin(x,y,z,4,30))
423
local caves2 = thresholdFilter(ridged2,.84,.01)
424
local ridged3 = ridgedFilter(getPerlin(x,y,z,5,30))
425
local caves3 = thresholdFilter(ridged3,.84,.01)
426
local ridged4 = ridgedFilter(getPerlin(x,y,z,6,30))
427
local caves4 = thresholdFilter(ridged4,.84,.01)
428
local caveOpenings = (surface and 1 or 0) * thresholdFilter(getPerlin(x,0,z,143,62),.35,0) --.45
429
caves = caves2 * caves3 * caves4 - caveOpenings
430
caves = caves < 0 and 0 or caves > 1 and 1 or caves
431
end
432
433
local comp = preCaveComp - caves
434
435
local smoothedResult = thresholdFilter(comp,.5,smoothScale)
436
437
---below water level -above surface -no terrain
438
if 1-verticalGradient < waterLevel and preCaveComp <= .5 and smoothedResult <= 0 then
439
smoothedResult = 1
440
choiceSurface = water
441
choiceFill = water
442
surface = true
443
end
444
445
oMapY[z] = (y == 1 and 1) or smoothedResult
446
mMapY[z] = (y == 1 and lava) or (smoothedResult <= 0 and air) or (surface and choiceSurface) or choiceFill
447
end
448
end
449
450
-- local regionStart = Vector3.new(mapWidth*-2+(x-1)*4,mapHeight*-2,mapWidth*-2)
451
-- local regionEnd = Vector3.new(mapWidth*-2+x*4,mapHeight*2,mapWidth*2)
452
-- local mapRegion = Region3.new(regionStart, regionEnd)
453
-- terrain:WriteVoxels(mapRegion, 4, {mMapX}, {oMapX})
454
end
455
end
456
457
bench.runCode(generate, "voxelgen")
458
459