Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --!optimize 2
- --!native
- local RepStorage = game:FindService("ReplicatedStorage")
- local chunkReference = script.Chunk
- local chunkFolder = workspace.ChunkFolder
- local faceDrawFunctions = require(script.FaceDrawFunctions)
- local blockData = require(RepStorage.BlockData)
- local structureData = require(RepStorage.StructureData)
- local DrawFront, DrawBack, DrawTop, DrawBottom, DrawRight, DrawLeft = unpack(faceDrawFunctions)
- 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 chunks = {}
- --config
- local CHUNK_WIDTH = 16
- local OCTAVES = 5 -- default 4
- local PERSISTANCE = 0.325 -- default 0.33
- local LUCANARITY = 3 -- default 2.74
- local NOISE_WIDTH = 780 --default 300
- local NOISE_HEIGHT = 450 -- default 155
- 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 Flatten(x: number , y: number, z: number)
- return x + (y * CHUNK_WIDTH) + (z * CHUNK_WIDTH_SQUARED)
- end
- local function IsBufferEmpty(buff: buffer)
- return string.rep("\0", buffer.len(buff)) == buffer.tostring(buff)
- end
- local function GetBufferSize(buff: buffer)
- 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 --idk if this is faster than just reading each byte individually but we ball
- return count
- end
- local function GenerateNoise(Z: number, X: number)
- 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: Vector3)
- 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(chunkPos: Vector3)
- local chunk = chunks[chunkPos][IDX_EDITMESH].Parent:Destroy()
- chunks[chunkPos] = nil
- end
- local function AddChunk(chunkPos: Vector3)
- local mesh = Instance.fromExisting(chunkReference)
- local chunk = {
- buffer.create(CHUNK_WIDTH_CUBED),
- Instance.new("EditableMesh", mesh),
- chunkPos
- }
- chunks[chunkPos] = chunk
- mesh.Position = chunkPos * CHUNK_WIDTH
- mesh.Parent = chunkFolder
- return chunk
- end
- local function GetChunksInRange(centerPos: Vector3, radius: number, createChunks: boolean?)
- local radiusSquared = radius^2
- local radiusVector = Vector3.new(radius, radius, radius)
- local chunkRadius = radius // CHUNK_WIDTH
- local minPos = (centerPos - radiusVector) // CHUNK_WIDTH
- local maxPos = (centerPos + radiusVector) // CHUNK_WIDTH --+ Vector3.one
- local touchingChunks = {}
- local count = 0
- 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 difference = Vector3.new(
- math.clamp(centerPos.X, chunkMin.X, chunkMax.X),
- math.clamp(centerPos.Y, chunkMin.Y, chunkMax.Y),
- math.clamp(centerPos.Z, chunkMin.Z, chunkMax.Z)
- ) - centerPos
- if (difference.X^2 + difference.Y^2 + difference.Z^2) <= radiusSquared then
- local chunk = chunks[chunkPos]
- if not chunk then
- if not createChunks then
- continue
- end
- chunk = AddChunk(chunkPos)
- end
- count += 1
- touchingChunks[count] = chunk
- end
- end
- end
- end
- return touchingChunks
- end
- local function GetChunksInBounds(centerPos: Vector3, size: Vector3, createChunks: boolean?)
- size //= 2
- local minPos = (centerPos - size) // CHUNK_WIDTH
- local maxPos = (centerPos + size) // CHUNK_WIDTH
- local touchingChunks = {}
- local count = 0
- 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 chunk = chunks[chunkPos]
- if not chunk then
- if not createChunks then
- continue
- end
- chunk = AddChunk(chunkPos)
- end
- count += 1
- touchingChunks[count] = chunk
- end
- end
- end
- return touchingChunks
- end
- local function UpdateChunk(chunk)
- if not chunk then return end
- local oldEditMesh = chunk[IDX_EDITMESH]
- local replaceEditMesh = #oldEditMesh:GetVertices() ~= 0
- local editMesh = if replaceEditMesh then Instance.new("EditableMesh") else oldEditMesh
- local voxels = chunk[IDX_VOXELS]
- local chunkPos = chunk[IDX_CHUNKPOS]
- local frontNeighbor, backNeighbor, topNeighbor, bottomNeighbor, rightNeighbor, leftNeighbor = GetChunkNeighbors(chunkPos)
- 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 = blockData[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 = if colorMax then colorMin:Lerp(colorMax, math.random()) else 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
- if not frontNeighbor or buffer.readu8(frontNeighbor[IDX_VOXELS], YZindex) == 0 then
- drawFace = true
- end
- elseif buffer.readu8(voxels, gridIndex+1) == 0 then
- drawFace = true
- end
- if drawFace then
- DrawFront(editMesh, color, p2,p4,p6,p8)
- end
- --
- --back face
- local drawFace
- if X == 0 then
- if not backNeighbor or buffer.readu8(backNeighbor[IDX_VOXELS], POS_LIMIT+YZindex) == 0 then
- drawFace = true
- end
- elseif buffer.readu8(voxels, gridIndex-1) == 0 then
- drawFace = true
- end
- if drawFace then
- DrawBack(editMesh, color, p1,p3,p5,p7)
- end
- --
- --top face
- local drawFace
- if Y == POS_LIMIT then
- if not topNeighbor or buffer.readu8(topNeighbor[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
- DrawTop(editMesh, color, p5,p6,p7,p8)
- end
- --
- --bottom face
- local drawFace
- if Y == 0 then
- if not bottomNeighbor or buffer.readu8(bottomNeighbor[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
- DrawBottom(editMesh, color, p1,p2,p3,p4)
- end
- --
- --right face
- local drawFace
- if Z == POS_LIMIT then
- if not rightNeighbor or buffer.readu8(rightNeighbor[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
- DrawRight(editMesh, color, p3,p4,p7,p8)
- end
- --
- --left face
- local drawFace
- if Z == 0 then
- if not leftNeighbor or buffer.readu8(leftNeighbor[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
- DrawLeft(editMesh, color, p1,p2,p5,p6)
- end
- --
- end
- end
- end
- end
- if replaceEditMesh then
- chunk[IDX_EDITMESH] = editMesh
- editMesh.Parent = oldEditMesh.Parent
- oldEditMesh:Destroy()
- end
- end
- local function GenerateStructure(at: Vector3, structureID: number, createChunks: boolean?, writeAir: boolean?)
- local structure = structureData[structureID]
- 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: number, noiseHeight: number)
- if voxelY < noiseHeight-105 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: number, y: number, z: number)
- 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
- return chunk
- end
- --
- local module = {}
- module.RemoveVoxelsInRange = function(centerPos: Vector3, range: number)
- 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 = centerPos+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(centerPos, radius+1) do
- if IsBufferEmpty(chunk[IDX_VOXELS]) then
- RemoveChunk(chunk[IDX_CHUNKPOS])
- else
- --RevealChunk(chunk)
- task.defer(UpdateChunk, chunk)
- end
- end
- end
- module.AddVoxelsInRange = function(centerPos: Vector3, range: number, blockID: number)
- local radius = range/2
- local radiusSquared = radius^2
- local radiusFloored = math.floor(radius)
- local touchingChunks = GetChunksInRange(centerPos, radius+1, 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 = centerPos+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.AddVoxel = function(pos: Vector3, ID: number)
- 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: Vector3)
- 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: Vector3, strucID: number)
- local size = structureData[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: Vector3, dir: Vector3, res: number, inside: boolean?)
- local length = (dir.X^2 + dir.Y^2 + dir.Z^2)^0.5
- 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 if inside then pos else (halfFrom + (step*(i-1))) // 1
- end
- end
- end
- end
- module.ClearChunks = function()
- chunkFolder:ClearAllChildren()
- table.clear(chunks)
- end
- module.Chunks = chunks
- module.UpdateChunk = UpdateChunk
- module.GenerateChunk = GenerateChunk
- return module
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement