Advertisement
DragRacer31

Terrain Generator with chunk loader, and LOD system

Sep 24th, 2019
663
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 11.50 KB | None | 0 0
  1. --[[
  2.     Please note several of the methods used in this are non-conventional; part of the challenge i gave myself
  3.     for this was to only use methods of my own making, without looking up how other people did it.
  4. --]]
  5.  
  6.  
  7. local Player = game.Players.LocalPlayer
  8. repeat wait() until game.ReplicatedStorage.Seed.Value~=0
  9. repeat wait() until Player.PlayerGui
  10. repeat wait() until workspace.CurrentCamera
  11.  
  12. Player.Chatted:connect(function(c)
  13.     if c:sub(1,3) == "ws/" then
  14.         local speed = tonumber(c:sub(4))
  15.         if speed and Player.Character then
  16.             Player.Character.Humanoid.WalkSpeed = speed
  17.         end
  18.     end
  19. end)
  20. workspace.CurrentCamera.FieldOfView = 50
  21.  
  22. wait(2)
  23.  
  24.  
  25. -- Stats gui is disabled by default, i enable it for studio testing
  26.  
  27. local StatsGui = Player.PlayerGui:WaitForChild("Stats")
  28. local Label = StatsGui.TextLabel
  29. Label.Parent = nil
  30.  
  31. QueueSizeGui = Label:clone()
  32. QueueSizeGui.Parent = StatsGui
  33.  
  34. QueueTimeGui = Label:clone()
  35. QueueTimeGui.Parent = StatsGui
  36.  
  37. QueueClearTimeGui = Label:clone()
  38. QueueClearTimeGui.Parent = StatsGui
  39.  
  40. SortTimeGui = Label:clone()
  41. SortTimeGui.Parent = StatsGui
  42.  
  43. TotalTimeGui = Label:clone()
  44. TotalTimeGui.Parent = StatsGui
  45.  
  46.  
  47. local randomCache = {}
  48. --[[
  49.     Terrain values are cached since they're somewhat expensive to generate, this leads to a
  50.     slow increase in RAM when travelling far distances, since I don't currently clear the cache.
  51. --]]
  52. function random2d(x,y,seed)
  53.     local cache = randomCache[seed]
  54.     if not cache then
  55.         cache = {}
  56.         randomCache[seed]=cache
  57.     end
  58.     local cacheX = cache[x]
  59.     if not cacheX then
  60.         cacheX = {}
  61.         cache[x]=cacheX
  62.     end
  63.     local value = cacheX[y]
  64.     if not value then
  65.         local seedMulti = Random.new(seed or 1):NextInteger(1,1000000)
  66.         local xRand = Random.new(x*seedMulti):NextInteger(1,1000000)
  67.         local yRand = Random.new(-y*seedMulti):NextInteger(1,1000000)
  68.         value = Random.new(xRand+yRand):NextNumber()
  69.         cacheX[y]=value
  70.     end
  71.     return value
  72. end
  73.  
  74. function slerp(a,b,c) -- sine interpolation
  75.     return a+(b-a)*((math.sin(math.pi*(c-.5))+1)/2)
  76. end
  77.  
  78. function lerp(a,b,c) -- linear interpolation
  79.     return a+(b-a)*c
  80. end
  81.  
  82. function squeeze(h,e)
  83.     --[[ this function is used to make mountains rarer and plains more common, it
  84.         squeezes height values towards 0.5, which is water level for this terrain generator
  85.     --]]
  86.     if h>=0.5 then
  87.         return 0.5+((((h-0.5)/0.5)^e)/2)
  88.     else
  89.         return 0.5-((((0.5-h)/0.5)^e)/2)
  90.     end
  91. end
  92.  
  93. function colerp(a,b,c) -- linear interpolation for colors
  94.     return Color3.new(a.r+(b.r-a.r)*c,a.g+(b.g-a.g)*c,a.b+(b.b-a.b)*c)
  95. end
  96.  
  97. function biterpolate(x,y,tl,tr,bl,br,lerp)
  98.     -- bilinear interpolation, gets the value of a point based on the values of the surrounding 4 points
  99.     if lerp then
  100.         local top = lerp(tl,tr,x)
  101.         local bottom = lerp(bl,br,x)
  102.         return lerp(top,bottom,y)
  103.     else
  104.         local top = tl+(tr-tl)*x
  105.         local bottom = bl+(br-bl)*x
  106.         return top+(bottom-top)*y
  107.     end
  108. end
  109.  
  110. function randomTerrain(x,y,scale,seed)
  111.     local xs,ys = math.floor(x/scale),math.floor(y/scale)
  112.     local tl = random2d(xs,ys,seed)
  113.     local tr = random2d(xs+1,ys,seed)
  114.     local bl = random2d(xs,ys+1,seed)
  115.     local br = random2d(xs+1,ys+1,seed)
  116.    
  117.     local h = biterpolate((x/scale)%1,(y/scale)%1,tl,tr,bl,br,slerp)
  118.     return h
  119. end
  120.  
  121. local nodes = {}
  122. local grid = {}
  123. local chunks = {}
  124. local chunklist = {}
  125. local seed = game.ReplicatedStorage.Seed.Value --123
  126. local chunkscale = 64*2 -- How wide the chunks are, in studs
  127.  
  128.  
  129. -- Layers is a list of the heightmaps layered to generate the terrain
  130. -- the first value is how wide the heightmap nodes are (inverse amplitude)
  131. -- the second value is the magnitude (how much the heightmap layer effects the height)
  132. -- generally the smaller the width the lower the magnitude
  133.  
  134. --[/[
  135. local Layers = {
  136. [1]={300,8,{}};
  137. [2]={100,3,{}};
  138. [3]={50,1.5,{}};
  139. [4]={25,.5,{}};
  140. [5]={10,.3,{}};
  141. [6]={5,.1,{}};
  142. [7]={3,.06,{}};
  143. [8]={1,.02,{}};
  144. }
  145. --]]
  146.  
  147. --[[
  148. local Layers = {
  149.     [1]={600,10};
  150.     [2]={300,8};
  151.     [3]={100,4};
  152.     [4]={50,2};
  153.     [5]={25,.75};
  154.     [6]={10,.5};
  155.     [7]={5,.2};
  156.     [8]={3,.1};
  157.     [9]={1,.05};
  158. }
  159. --]]
  160.  
  161.  
  162. local Recycler = {}
  163. local RecycleStorage = 0
  164.  
  165. function RecyclePart(Part)
  166.     --Part.CFrame = CFrame.new(0,-10000,0)
  167.     Part.Parent = nil
  168.     RecycleStorage = RecycleStorage+1
  169.     Recycler[RecycleStorage]=Part
  170. end
  171.  
  172. function ReusePart()
  173.     local Part = Recycler[RecycleStorage]
  174.     Recycler[RecycleStorage]=nil
  175.     return Part
  176. end
  177.  
  178. function SpawnChunk(X,Y,Resolution,NoCollide) -- Resolution is how many blocks wide to spawn the chunk
  179.     chunks[X]=chunks[X] or {}
  180.     if not chunks[X][Y] then
  181.         local numLayers = #Layers
  182.        
  183.         local realSeed = seed*numLayers
  184.        
  185.         local chunk = {
  186.             X=X;
  187.             Y=Y;
  188.             resolution=Resolution;
  189.             grid={};
  190.             parts={};
  191.             model=Instance.new("Model")
  192.         }
  193.         chunk.model.Name = "chunk "..X.."x"..Y.."y"
  194.        
  195.         local partScale = chunkscale/Resolution
  196.         local offsetX,offsetY = (X*chunkscale)+(partScale/2),(Y*chunkscale)+(partScale/2)
  197.        
  198.         for x = 1,Resolution do
  199.             for y = 1,Resolution do
  200.                 local pX,pY = offsetX+(partScale*(x-1)),offsetY+(partScale*(y-1)) -- Position of part
  201.                 local tX,tY = pX/4,pY/4
  202.                
  203.                 local rx,ry = tX+1000000,tY+1000000
  204.                 --[[
  205.                     1000000 added to chunk coordinates, because chunks below 0,0
  206.                     are mirrored and i was to lazy to fix it
  207.                 --]]
  208.                
  209.                 local height = 0
  210.                 local maxheight = 0
  211.                 for i = 1,numLayers do
  212.                     local Layer = Layers[i]
  213.                     local Size = Layer[1]
  214.                     local Power = Layer[2]
  215.                     if Size*8>=partScale then
  216.                         height=height+(randomTerrain(rx,ry,Size,realSeed+i)*Power)
  217.                         maxheight=maxheight+Power
  218.                     end
  219.                 end
  220.                 height=height/maxheight -- normalize height
  221.                
  222.                 height=squeeze(height,1.5+(math.abs((height-0.5)*2)*1.5))--*1.5))
  223.                
  224.                 local originalheight = height
  225.                 height = height*1500 -- height is multiplied in studs how tall the highest mountains should be/2
  226.                
  227.                 height=height-(1500/2) -- move height down by half so water level is at 0
  228.                 if height <= 0 then
  229.                     height = 0 -- set height to 0 if under 0, so water gets positioned correctly
  230.                 end
  231.                
  232.                
  233.                 local P = Instance.new("Part")
  234.                 P.Anchored = true
  235.                 P.TopSurface = "Smooth"
  236.                 P.Size = Vector3.new(partScale,height+2,partScale)
  237.                 P.CFrame = CFrame.new(pX,(height+2)/2,pY)
  238.                 P.CanCollide = not NoCollide
  239.                 if height <= 0 then -- water
  240.                     P.Color = Color3.new(0.0352, 0.5372, 0.8117)
  241.                     P.Material = "Foil"
  242.                 elseif height<=4 then -- sand
  243.                     P.Color = Color3.new(1,0.8,0.6)
  244.                     P.Material = "Sand"
  245.                 else -- grass
  246.                     local mul = height/600
  247.                     P.Color = colerp(Color3.new(0.2431, 1*.8, 0.2274*.8),Color3.new(1,1,1),mul) -- 58/255, 125/255, 21/255
  248.                     P.Material = "Grass"
  249.                 end
  250.                 P.Parent = chunk.model
  251.                 table.insert(chunk.parts,P)
  252.             end
  253.         end
  254.         chunks[X][Y]=chunk
  255.         chunklist[chunk]=true
  256.        
  257.         chunk.model.Parent = workspace
  258.        
  259.         return chunk
  260.     else
  261.         return chunks[X][Y]
  262.     end
  263. end
  264.  
  265. function RemoveChunk(X,Y)
  266.     local chunk = chunks[X] and chunks[X][Y]
  267.     if chunk then
  268.         if chunk.model then
  269.             chunk.model.Parent = nil
  270.         end
  271.         --[[for i,P in pairs(chunk.parts) do
  272.             RecyclePart(P)
  273.         end
  274.         chunk.parts = {}]]
  275.        
  276.         chunks[X][Y]=nil
  277.     end
  278. end
  279.  
  280. -- LODS are the ranges at which to load different detail levels
  281. -- size is how wide an area in chunks that LOD applies to
  282. -- resolution is the detail level of the LOD, how many blocks wide to load each chunk
  283. local LODS = {
  284.     {
  285.         size = 2;
  286.         resolution = 32;
  287.     };
  288.     {
  289.         size = 4;
  290.         resolution = 16;
  291.     };
  292.     {
  293.         size = 8;
  294.         resolution = 8;
  295.     };
  296.     {
  297.         size = 12;
  298.         resolution = 4;
  299.     };
  300.     --[/[
  301.     {
  302.         size = 24;
  303.         resolution = 2;
  304.     };
  305.     --]]
  306.     {
  307.         size = 48;
  308.         resolution = 1;
  309.     };
  310. }
  311.  
  312. local Queue = {}
  313. local QueueIndex = {}
  314.  
  315. function AddChunkToQueue(x,y)
  316.     local Row = QueueIndex[x]
  317.     if not Row or not Row[y] then
  318.         if not Row then
  319.             Row = {}
  320.             QueueIndex[x]=Row
  321.         end
  322.         Row[y]=true
  323.         table.insert(Queue,{x,y})
  324.     end
  325. end
  326.  
  327. local MaxSortTime = 0
  328. local MaxQueueTime = 0
  329. local MaxQueueClearTime = 0
  330. local MaxTotalTime = 0
  331. local LX,LY = 0,0
  332. game:GetService("RunService").RenderStepped:connect(function()
  333.     if Player.Character then
  334.         local Root = Player.Character.HumanoidRootPart
  335.         local X,Y = math.floor(Root.Position.X),math.floor(Root.Position.Z)
  336.        
  337.         local Root = Player.Character.HumanoidRootPart
  338.         local worldX,worldY = Root.Position.X/chunkscale,Root.Position.Z/chunkscale
  339.         local ChunkX,ChunkY = math.floor(worldX),math.floor(worldY)
  340.        
  341.         local chunksremoved = 0
  342.         local partsremoved = 0
  343.         local chunksadded = 0
  344.         local partsadded = 0
  345.         local start = tick()
  346.        
  347.         if LX~=X or LY~=Y then
  348.             LX,LY = X,Y
  349.             local t = tick()
  350.            
  351.             local renderDist = LODS[#LODS].size
  352.             for x = ChunkX-renderDist,ChunkX+renderDist do
  353.                 for y = ChunkY-renderDist,ChunkY+renderDist do
  354.                     local dist = (((x+.5)-worldX)^2+((y+0.5)-worldY)^2)^.5
  355.                     for i = 1,#LODS do
  356.                         local LOD = LODS[i]
  357.                         if dist<LOD.size*.9 then
  358.                             local chunk = chunks[x] and chunks[x][y]
  359.                             if (not chunk) or chunk.resolution~=LOD.resolution then
  360.                                 AddChunkToQueue(x,y)
  361.                             end
  362.                             break
  363.                         end
  364.                     end
  365.                 end
  366.             end
  367.            
  368.             for i,c in pairs(Queue) do
  369.                 c[3] = (((c[1]+.5)-worldX)^2+((c[2]+0.5)-worldY)^2)^.5
  370.             end
  371.            
  372.             table.sort(Queue,function(a,b)
  373.                 return a[3]<b[3]
  374.             end)
  375.            
  376.             local endLOD = LODS[#LODS]
  377.             local maxDist = endLOD.size
  378.             local removed = {}
  379.             for chunk in pairs(chunklist) do
  380.                 if chunk.X>ChunkX+renderDist or chunk.X<ChunkX-renderDist or chunk.Y>ChunkY+renderDist or chunk.Y<ChunkY-renderDist then
  381.                     RemoveChunk(chunk.X,chunk.Y)
  382.                     removed[chunk]=true
  383.                 end
  384.             end
  385.            
  386.             for chunk in pairs(removed) do
  387.                 chunklist[chunk]=nil
  388.             end
  389.            
  390.             local SortTime = tick()-t
  391.             if SortTime>MaxSortTime then
  392.                 MaxSortTime = SortTime
  393.             end
  394.         end
  395.        
  396.         -- Queue System
  397.        
  398.         local QueueRemoval = {}
  399.        
  400.         local t = tick()
  401.         for i,c in pairs(Queue) do
  402.             if (tick()-t)*1000>5 then break end -- Limits queue time to 5 milliseconds
  403.             QueueRemoval[c]=true
  404.             QueueIndex[c[1]][c[2]]=nil
  405.             local x,y,distance = c[1],c[2],c[3]
  406.             for i,LOD in pairs(LODS) do
  407.                 local LODres = LOD.resolution
  408.                 if distance <= LOD.size*.9 then
  409.                     local chunk = chunks[x] and chunks[x][y]
  410.                     if chunk and chunk.resolution~=LODres and (i==1 or distance>LODS[i-1].size*.9) then
  411.                         RemoveChunk(x,y)
  412.                         chunklist[chunk]=nil
  413.                        
  414.                         chunksremoved = chunksremoved+1
  415.                         partsremoved = partsremoved+(chunk.resolution^2)
  416.                        
  417.                         chunk = SpawnChunk(x,y,LODres,i>1)
  418.                         chunksadded = chunksadded+1
  419.                         partsadded = partsadded+(LODres^2)
  420.                     elseif not chunk then
  421.                         chunk = SpawnChunk(x,y,LODres,i>1)
  422.                         chunksadded = chunksadded+1
  423.                         partsadded = partsadded+(LODres^2)
  424.                     end
  425.                     break
  426.                 end
  427.             end
  428.         end
  429.         local QueueTime = tick()-t
  430.         if QueueTime>MaxQueueTime then
  431.             MaxQueueTime = QueueTime
  432.         end
  433.        
  434.         local t = tick()
  435.         local newQueue = {}
  436.         for i,c in pairs(Queue) do
  437.             if not QueueRemoval[c] then
  438.                 table.insert(newQueue,c)
  439.             end
  440.         end
  441.         Queue = newQueue
  442.         local ClearTime = tick()-t
  443.         if ClearTime>MaxQueueClearTime then
  444.             MaxQueueClearTime = ClearTime
  445.         end
  446.        
  447.         local TotalTime = tick()-start
  448.         if TotalTime>MaxTotalTime then
  449.             MaxTotalTime = TotalTime
  450.         end
  451.     end
  452.     QueueSizeGui.Text = "QueueSize: "..#Queue
  453.     QueueTimeGui.Text = "MaxQueueTime: "..math.ceil(MaxQueueTime*100000)/100
  454.     QueueClearTimeGui.Text = "MaxQueueClearTime: "..math.ceil(MaxQueueClearTime*100000)/100
  455.     SortTimeGui.Text = "MaxSortTime: "..math.ceil(MaxSortTime*100000)/100
  456.     TotalTimeGui.Text = "MaxTotalTime: "..math.ceil(MaxTotalTime*100000)/100
  457. end)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement