Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --!optimize 2
- --!native
- local RepStorage = game:FindService("ReplicatedStorage")
- local chunkMeshTime = {0,0}
- local chunkBuildTime = {0,0}
- local bench = table.create(4,0)
- local CHUNK_REF = script.Chunk
- local CHUNK_FOLDER = workspace.ChunkFolder
- local BLOCK_DATA = require(RepStorage.BlockData)
- local STRUCTURE_DATA = require(RepStorage.StructureData)
- local NOISE_SEED = os.clock()
- local Vector3Half = Vector3.one/2
- local VERT_OFFS_1 = Vector3.new(-0.5, -0.5, -0.5)
- local VERT_OFFS_2 = Vector3.new( 0.5, -0.5, -0.5)
- local VERT_OFFS_3 = Vector3.new(-0.5, -0.5, 0.5)
- local VERT_OFFS_4 = Vector3.new( 0.5, -0.5, 0.5)
- local FRONT_FACE = {4,2,1,1,3,4}
- local BACK_FACE = {3,1,2,2,4,3}
- local TOP_FACE = {1,3,4,4,2,1}
- local BOTTOM_FACE = {1,2,4,4,3,1}
- local RIGHT_FACE = {3,1,2,2,4,3}
- local LEFT_FACE = {4,2,1,1,3,4}
- local module = {}
- local chunks = {}
- --config
- local CHUNK_WIDTH = 16
- local OCTAVES = 5 -- how much noise maps to sum, default 4
- local PERSISTANCE = 0.325 -- lower = later octaves height matter less to the final result, default 0.33
- local LUCANARITY = 3 -- lower = later octaves get scaled smaller, default 2.74
- local NOISE_WIDTH = 780 --default 150
- local NOISE_HEIGHT = 450 -- default 80
- local OCEAN_HEIGHT = 2
- local TREE_CHANCE = 200
- --index constants
- local IDX_VOXELS = 1
- local IDX_EDITMESH = 2
- local IDX_CHUNKPOS = 3
- local IDX_COLORMIN = 1
- local IDX_COLORMAX = 2
- --
- local POS_LIMIT = CHUNK_WIDTH-1
- local CHUNK_WIDTH_SQUARED = CHUNK_WIDTH^2
- local CHUNK_WIDTH_CUBED = CHUNK_WIDTH^3
- local CHUNK_VECTOR = Vector3.new(CHUNK_WIDTH, CHUNK_WIDTH, CHUNK_WIDTH)
- local function RevealChunk(chunk, color)
- local part = Instance.new("Part")
- part.Transparency = 1
- part.Anchored = true
- part.CanCollide = false
- part.CanQuery = false
- part.Size = CHUNK_VECTOR
- part.Position = chunk[IDX_CHUNKPOS] * CHUNK_WIDTH + (CHUNK_VECTOR - Vector3.one)/2
- local box = Instance.new("SelectionBox")
- box.Adornee = part
- box.Parent = part
- if color then
- box.Color = color
- end
- part.Parent = workspace
- end
- local function Flatten(x,y,z)
- return x + (y * CHUNK_WIDTH) + (z * CHUNK_WIDTH_SQUARED)
- end
- local function IsBufferEmpty(buff)
- return string.rep("\0", buffer.len(buff)) == buffer.tostring(buff)
- end
- local function GetBufferSize(buff)
- local count = 0
- for i = 0, buffer.len(buff)-1, 4 do
- local data = buffer.readu32(buff, i)
- if bit32.btest(data, 0xFF) then count += 1 end
- if bit32.btest(data, 0xFF00) then count += 1 end
- if bit32.btest(data, 0xFF0000) then count += 1 end
- if bit32.btest(data, 0xFF000000) then count += 1 end
- end
- return count
- end
- local function DrawFace(editMesh, color, p1,p2,p3,p4, map)
- local verts = {
- editMesh:AddVertex(p1),
- editMesh:AddVertex(p2),
- editMesh:AddVertex(p3),
- editMesh:AddVertex(p4)
- }
- for i,v in verts do
- editMesh:SetVertexColor(v, color)
- end
- editMesh:AddTriangle(verts[map[1]], verts[map[2]], verts[map[3]])
- editMesh:AddTriangle(verts[map[4]], verts[map[5]], verts[map[6]])
- end
- local function GenerateNoise(Z,X)
- local noise = 0
- local amplitude = 1
- for _ = 1, OCTAVES do
- noise += math.noise(X / NOISE_WIDTH, Z / NOISE_WIDTH, NOISE_SEED) * amplitude
- X *= LUCANARITY
- Z *= LUCANARITY
- amplitude *= PERSISTANCE
- end
- return noise >= 0 and noise^1.25 or -(math.abs(noise)^0.5)
- end
- local function GetChunkNeighbors(chunkPos)
- return {
- chunks[chunkPos + Vector3.xAxis],
- chunks[chunkPos - Vector3.xAxis],
- chunks[chunkPos + Vector3.yAxis],
- chunks[chunkPos - Vector3.yAxis],
- chunks[chunkPos + Vector3.zAxis],
- chunks[chunkPos - Vector3.zAxis]
- }
- end
- local function RemoveChunk(pos)
- local chunk = chunks[pos][IDX_EDITMESH].Parent:Destroy()
- chunks[pos] = nil
- end
- local function AddChunk(chunkPos)
- local mesh = CHUNK_REF:Clone()
- mesh.Position = chunkPos * CHUNK_WIDTH
- local chunk = {
- buffer.create(CHUNK_WIDTH_CUBED),
- Instance.new("EditableMesh", mesh),
- chunkPos
- }
- chunks[chunkPos] = chunk
- mesh.Parent = CHUNK_FOLDER
- return chunk
- end
- local function GetChunksInRange(centerPos, range, createChunks)
- local rangeVector = Vector3.new(range, range, range)
- local minPos = (centerPos - rangeVector) // CHUNK_WIDTH
- local maxPos = (centerPos + rangeVector) // CHUNK_WIDTH + Vector3.one
- local touchingChunks = {}
- for z = minPos.Z, maxPos.Z do
- for y = minPos.Y, maxPos.Y do
- for x = minPos.X, maxPos.X do
- local chunkPos = Vector3.new(x,y,z)
- local chunkMin = chunkPos * CHUNK_WIDTH
- local chunkMax = chunkMin + CHUNK_VECTOR
- local closestPoint = chunkMin:Max(centerPos:Min(chunkMax))
- if (closestPoint - centerPos).Magnitude <= range then
- local chunk = chunks[chunkPos]
- if not chunk then
- if not createChunks then
- continue
- end
- chunk = AddChunk(chunkPos)
- end
- table.insert(touchingChunks, chunk)
- end
- end
- end
- end
- return touchingChunks
- end
- local function GetChunksInBounds(centerPos, size, createChunks)
- size //= 2
- local minPos = (centerPos - size) // CHUNK_WIDTH
- local maxPos = (centerPos + size) // CHUNK_WIDTH + Vector3.one
- local touchingChunks = {}
- for z = minPos.Z, maxPos.Z do
- for y = minPos.Y, maxPos.Y do
- for x = minPos.X, maxPos.X do
- local chunkPos = Vector3.new(x,y,z)
- local chunkMin = chunkPos * CHUNK_WIDTH
- local chunkMax = chunkMin + CHUNK_VECTOR
- local closestPoint = chunkMin:Max(centerPos:Min(chunkMax))
- local delta = (closestPoint - centerPos)
- if delta.X <= size.X and delta.Y <= size.Z and delta.Z <= size.Z then
- local chunk = chunks[chunkPos]
- if not chunk then
- if not createChunks then
- continue
- end
- chunk = AddChunk(chunkPos)
- end
- table.insert(touchingChunks, chunk)
- end
- end
- end
- end
- return touchingChunks
- end
- local function UpdateChunk(chunk)
- if not chunk then return end
- local clock = os.clock()
- local oldEditMesh = chunk[IDX_EDITMESH]
- local replaceEditMesh = #oldEditMesh:GetVertices() ~= 0
- local editMesh = replaceEditMesh and Instance.new("EditableMesh") or oldEditMesh
- local voxels = chunk[IDX_VOXELS]
- local chunkPos = chunk[IDX_CHUNKPOS]
- local chunkNeighbors = GetChunkNeighbors(chunkPos)
- local reserved = {}
- if not IsBufferEmpty(voxels) then
- for Z = 0, POS_LIMIT do
- local Zindex = Z * CHUNK_WIDTH_SQUARED
- for Y = 0, POS_LIMIT do
- local Yindex = Y * CHUNK_WIDTH
- local YZindex = Zindex + Yindex
- for X = 0, POS_LIMIT do
- local Xindex = X
- local gridIndex = YZindex + Xindex
- local blockID = buffer.readu8(voxels, gridIndex)
- if blockID == 0 then continue end -- skip air
- local centerPos = Vector3.new(X,Y,Z)
- local blockTypeData = BLOCK_DATA[blockID]
- local colorMin = blockTypeData[IDX_COLORMIN]
- local colorMax = blockTypeData[IDX_COLORMAX]
- math.randomseed((chunkPos.X*4.3 + chunkPos.Y*7.2 + chunkPos.Z*1.9)*CHUNK_WIDTH + gridIndex)
- local color = colorMax and colorMin:Lerp(colorMax, math.random()) or colorMin
- local p1 = centerPos + VERT_OFFS_1
- local p2 = centerPos + VERT_OFFS_2
- local p3 = centerPos + VERT_OFFS_3
- local p4 = centerPos + VERT_OFFS_4
- local p5 = centerPos - VERT_OFFS_4
- local p6 = centerPos - VERT_OFFS_3
- local p7 = centerPos - VERT_OFFS_2
- local p8 = centerPos - VERT_OFFS_1
- --front face
- local drawFace
- if X == POS_LIMIT then
- local chunkNeighbor = chunkNeighbors[1]
- if not chunkNeighbor or buffer.readu8(chunkNeighbor[IDX_VOXELS], YZindex) == 0 then
- drawFace = true
- end
- elseif buffer.readu8(voxels, gridIndex+1) == 0 then
- drawFace = true
- end
- if drawFace then
- DrawFace(editMesh, color, p2,p4,p6,p8, FRONT_FACE)
- end
- --
- --back face
- local drawFace
- if X == 0 then
- local chunkNeighbor = chunkNeighbors[2]
- if not chunkNeighbor or buffer.readu8(chunkNeighbor[IDX_VOXELS], POS_LIMIT+YZindex) == 0 then
- drawFace = true
- end
- elseif buffer.readu8(voxels, gridIndex-1) == 0 then
- drawFace = true
- end
- if drawFace then
- DrawFace(editMesh, color, p1,p3,p5,p7, BACK_FACE)
- end
- --
- --top face
- local drawFace
- if Y == POS_LIMIT then
- local chunkNeighbor = chunkNeighbors[3]
- if not chunkNeighbor or buffer.readu8(chunkNeighbor[IDX_VOXELS], Xindex + Zindex) == 0 then
- drawFace = true
- end
- elseif buffer.readu8(voxels, Xindex + (Yindex+CHUNK_WIDTH) + Zindex) == 0 then
- drawFace = true
- end
- if drawFace then
- DrawFace(editMesh, color, p5,p6,p7,p8, TOP_FACE)
- end
- --
- --bottom face
- local drawFace
- if Y == 0 then
- local chunkNeighbor = chunkNeighbors[4]
- if not chunkNeighbor or buffer.readu8(chunkNeighbor[IDX_VOXELS], Xindex + (POS_LIMIT*CHUNK_WIDTH) + Zindex) == 0 then
- drawFace = true
- end
- elseif buffer.readu8(voxels, Xindex + (Yindex-CHUNK_WIDTH) + Zindex) == 0 then
- drawFace = true
- end
- if drawFace then
- DrawFace(editMesh, color, p1,p2,p3,p4, BOTTOM_FACE)
- end
- --
- --right face
- local drawFace
- if Z == POS_LIMIT then
- local chunkNeighbor = chunkNeighbors[5]
- if not chunkNeighbor or buffer.readu8(chunkNeighbor[IDX_VOXELS], Xindex + Yindex) == 0 then
- drawFace = true
- end
- elseif buffer.readu8(voxels, Xindex + Yindex + (Zindex+CHUNK_WIDTH_SQUARED)) == 0 then
- drawFace = true
- end
- if drawFace then
- DrawFace(editMesh, color, p3,p4,p7,p8, RIGHT_FACE)
- end
- --
- --left face
- local drawFace
- if Z == 0 then
- local chunkNeighbor = chunkNeighbors[6]
- if not chunkNeighbor or buffer.readu8(chunkNeighbor[IDX_VOXELS], Xindex + Yindex + (POS_LIMIT*CHUNK_WIDTH_SQUARED)) == 0 then
- drawFace = true
- end
- elseif buffer.readu8(voxels, Xindex + Yindex + (Zindex-CHUNK_WIDTH_SQUARED)) == 0 then
- drawFace = true
- end
- if drawFace then
- DrawFace(editMesh, color, p1,p2,p5,p6, LEFT_FACE)
- end
- --
- end
- end
- end
- end
- if replaceEditMesh then
- chunk[IDX_EDITMESH] = editMesh
- editMesh.Parent = oldEditMesh.Parent
- oldEditMesh:Destroy()
- end
- chunkMeshTime[1] += os.clock() - clock
- chunkMeshTime[2] += 1
- end
- local function GenerateStructure(at, strucID, createChunks, writeAir)
- local structure = STRUCTURE_DATA[strucID]
- local size = structure[1]
- local data = structure[2]
- local sizeX = size.X
- local sizeY = size.Y
- local sizeZ = size.Z
- local sizeXY = sizeX*sizeY
- for Z = 0, sizeZ-1 do
- local Zindex = Z * sizeXY
- for Y = 0, sizeY-1 do
- local YZindex = Zindex + (Y * sizeX)
- for X = 0, sizeX-1 do
- local blockID = buffer.readu8(data, YZindex+X)
- if blockID == 0 and not writeAir then
- continue
- end
- local vectorPos = at+Vector3.new(X,Y,Z)
- local chunkPos = vectorPos // CHUNK_WIDTH
- local chunk = chunks[chunkPos]
- if not chunk then
- if not createChunks then
- continue
- end
- chunk = AddChunk(chunkPos)
- end
- local offset = vectorPos-(chunkPos*CHUNK_WIDTH)
- buffer.writeu8(chunk[IDX_VOXELS], Flatten(offset.X, offset.Y, offset.Z), blockID)
- end
- end
- end
- end
- local function GetVoxelType(voxelY, noiseHeight)
- if voxelY < noiseHeight-55 then
- return 9
- elseif voxelY < noiseHeight-10 then
- return 3 --stone
- elseif voxelY < noiseHeight-1.5 then
- return 5 --dirt
- elseif noiseHeight <= OCEAN_HEIGHT then
- return 2 --water
- elseif noiseHeight < OCEAN_HEIGHT + 1.8 then
- return 4 --sand
- elseif voxelY > (NOISE_HEIGHT*0.55) and voxelY > noiseHeight-3 then
- return 6 --snow
- else
- return 1 --grass
- end
- end
- local function GenerateChunk(x,y,z)
- local clock = os.clock()
- local trueChunkX = x*CHUNK_WIDTH
- local trueChunkY = y*CHUNK_WIDTH
- local trueChunkZ = z*CHUNK_WIDTH
- local chunkPos = Vector3.new(x,y,z)
- local maxY = trueChunkY+POS_LIMIT
- local chunk = chunks[chunkPos]
- local voxels = chunk and chunk[IDX_VOXELS]
- for Z = 0, POS_LIMIT do
- local trueVoxelZ = trueChunkZ+Z
- local indexZ = Z*CHUNK_WIDTH_SQUARED
- for X = 0, POS_LIMIT do
- local trueVoxelX = trueChunkX+X
- local noise = GenerateNoise(trueVoxelZ, trueVoxelX)
- local noiseHeight = math.max(noise * NOISE_HEIGHT, OCEAN_HEIGHT)
- if noiseHeight >= trueChunkY then
- if not chunk then
- chunk = AddChunk(chunkPos)
- voxels = chunk[IDX_VOXELS]
- end
- local maxHeight = math.min(math.floor(noiseHeight), maxY) - trueChunkY
- local voxelType
- for Y = 0, maxHeight do
- voxelType = GetVoxelType(trueChunkY + Y, noiseHeight)
- buffer.writeu8(voxels, X + (Y*CHUNK_WIDTH) + indexZ, voxelType)
- end
- if voxelType == 1 and math.random(TREE_CHANCE) == 1 then
- GenerateStructure(Vector3.new(trueVoxelX-2, trueChunkY+maxHeight+1, trueVoxelZ-2), 2, true)
- end
- end
- end
- end
- chunkBuildTime[1] += os.clock() - clock
- chunkBuildTime[2] += 1
- return chunk
- end
- --
- module.RemoveVoxelsInRange = function(from, range)
- local radius = range/2
- local radiusSquared = radius^2
- local radiusFloored = math.floor(radius)
- for Z = -radiusFloored, radiusFloored do
- local zSquared = Z^2
- local yRange = math.floor(math.sqrt(radiusSquared - zSquared))
- for Y = -yRange, yRange do
- local xRange = math.floor(math.sqrt(radiusSquared - (zSquared + Y^2)))
- for X = -xRange, xRange do
- local vectorPos = from+Vector3.new(X,Y,Z)
- local chunkPos = vectorPos // CHUNK_WIDTH
- local chunk = chunks[chunkPos]
- if chunk then
- local offset = vectorPos-(chunkPos*CHUNK_WIDTH)
- buffer.writeu8(chunk[IDX_VOXELS], Flatten(offset.X, offset.Y, offset.Z), 0)
- end
- end
- end
- end
- for _, chunk in GetChunksInRange(from, radius+.5) do
- if IsBufferEmpty(chunk[IDX_VOXELS]) then
- RemoveChunk(chunk[IDX_CHUNKPOS])
- continue
- end
- --RevealChunk(chunk)
- task.defer(UpdateChunk, chunk)
- end
- end
- module.AddVoxelsInRange = function(from, range, blockID)
- local radius = range/2
- local radiusSquared = radius^2
- local radiusFloored = math.floor(radius)
- local touchingChunks = GetChunksInRange(from, radius+.5, true)
- for Z = -radiusFloored, radiusFloored do
- local zSquared = Z^2
- local yRange = math.floor(math.sqrt(radiusSquared - zSquared))
- for Y = -yRange, yRange do
- local xRange = math.floor(math.sqrt(radiusSquared - (zSquared + Y^2)))
- for X = -xRange, xRange do
- local vectorPos = from+Vector3.new(X,Y,Z)
- local chunkPos = vectorPos // CHUNK_WIDTH
- local chunk = chunks[chunkPos]
- local offset = vectorPos-(chunkPos*CHUNK_WIDTH)
- buffer.writeu8(chunk[IDX_VOXELS], Flatten(offset.X, offset.Y, offset.Z), blockID)
- end
- end
- end
- for _, chunk in touchingChunks do
- --RevealChunk(chunk)
- task.defer(UpdateChunk, chunk)
- end
- end
- module.GetStats = function()
- local chunkCount = 0
- local voxelCount = 0
- for _, chunk in chunks do
- chunkCount += 1
- end
- return {
- chunkAmount = chunkCount,
- voxelAmount = voxelCount,
- averageChunkMeshTime = `{math.floor((chunkMeshTime[1]/chunkMeshTime[2])*10000000)/10000} ms`,
- averageChunkBuildTime = `{math.floor((chunkBuildTime[1]/chunkBuildTime[2])*10000000)/10000} ms`,
- bench
- }
- end
- module.AddVoxel = function(pos, ID)
- local chunkPos = pos // CHUNK_WIDTH
- local chunk = chunks[chunkPos] or AddChunk(chunkPos)
- local offset = pos-(chunkPos*CHUNK_WIDTH)
- buffer.writeu8(chunk[IDX_VOXELS], Flatten(offset.X, offset.Y, offset.Z), ID)
- UpdateChunk(chunk)
- end
- module.RemoveVoxel = function(pos)
- local chunkPos = pos // CHUNK_WIDTH
- local chunk = chunks[chunkPos]
- if chunk then
- local offset = pos-(chunkPos*CHUNK_WIDTH)
- buffer.writeu8(chunk[IDX_VOXELS], Flatten(offset.X, offset.Y, offset.Z), 0)
- UpdateChunk(chunk)
- end
- end
- module.PlaceStructure = function(at, strucID)
- local size = STRUCTURE_DATA[strucID][1]
- local touchingChunks = GetChunksInBounds(at + size//2, size+Vector3.one, true)
- GenerateStructure(at, strucID)
- for _, chunk in touchingChunks do
- --RevealChunk(chunk)
- task.defer(UpdateChunk, chunk)
- end
- end
- module.Raycast = function(from, dir, res, inside)
- local length = dir.Magnitude
- local unit = dir/length
- local step = unit*res
- for i = 0,length/res do
- local halfFrom = from + Vector3Half
- local pos = (halfFrom + (step*i)) // 1
- local chunkPos = pos // CHUNK_WIDTH
- local chunk = chunks[chunkPos]
- if chunk then
- local offset = pos-(chunkPos*CHUNK_WIDTH)
- if buffer.readu8(chunk[IDX_VOXELS], Flatten(offset.X, offset.Y, offset.Z)) ~= 0 then
- return inside and pos or (halfFrom + (step*(i-1))) // 1
- end
- end
- end
- end
- module.RevealChunksInRange = function(at, range)
- for _, chunk in GetChunksInRange(at, range) do
- RevealChunk(chunk)
- end
- end
- module.Chunks = chunks
- module.UpdateChunk = UpdateChunk
- module.GenerateChunk = GenerateChunk
- return module
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement