local K=require(script.Parent.GenerationConfig)local V=require(script.Parent.MarchTables)local S=require(script.Parent.createTriangle)local e=require(script.Parent.TriStorage)local I=require(script.Parent.CellStyles)local X if K.oreCounterEnabled then local K=game.Players.LocalPlayer:WaitForChild("\080\108\097\121\101\114\071\117\105")X=K.ScreenGui.Ores end local W=V.triangulation local T=V.cornerIndexAFromEdge local v=V.cornerIndexBFromEdge local O=K.worldScale local w=K.cellsPerAxis local a=K.isoLevel local function B(K,V,e)return S(K*O,V*O,e*O)end local function o(K,V)return(Vector3.new(K.x,K.y,K.z)):lerp(Vector3.new(V.x,V.y,V.z),.5)end local p=Instance.new("\077\111\100\101\108",workspace)p.Name="\077\097\114\099\104\105\110\103\067\117\098\101\115\087\111\114\108\100"local f={}function f.new(K,V,S,e)local I={x=K;y=V;z=S,w=e;tris={}}setmetatable(I,{__index=f})return I end function f.destroy(S,V)if S.w~=0 and S.cellType~="\119\111\114\108\100\066\111\114\100\101\114"then S.w=0 if K.oreCounterEnabled and X:FindFirstChild(S.cellType)then X[S.cellType].Quantity.Text=tonumber(X[S.cellType].Quantity.Text)+1 end if not V then S:updateAdjacent()end end end function f.updateAdjacent(K)for K,V in pairs(K:getAdjacent())do V:update()end end function f.getAdjacent(V,K)local S=V.grid local e={}if not K then for K=V.x-1,V.x+1,1 do for I=V.y-1,V.y+1,1 do for V=V.z-1,V.y+1,1 do table.insert(e,S[K][I][V])end end end else local K,I,X=V.x,V.y,V.z table.insert(e,S[K][I][X])table.insert(e,S[K+1][I][X])table.insert(e,S[K][I][X+1])table.insert(e,S[K][I+1][X])table.insert(e,S[K-1][I][X])table.insert(e,S[K][I][X-1])table.insert(e,S[K][I-1][X])end return e end function f.getCellsInRadius(V,K)local S=V.grid local e={}for I=V.x-K,V.x+K,1 do for X=V.y-K,V.y+K,1 do for W=V.z-K,V.z+K,1 do if S[I][X][W]and(Vector3.new(V.x,V.y,V.z)-Vector3.new(I,X,W)).magnitude<=K then table.insert(e,S[I][X][W])end end end end return e end function f.getCellsInRange(V,K)local S=V.grid local e={}for I=V.x-K,V.x+K,1 do for X=V.y-K,V.y+K,1 do for K=V.z-K,V.z+K,1 do table.insert(e,S[I][X][K])end end end return e end function f.update(K)local V=K.grid local S=K local X=S.w if S.x>=w or S.y>=w or S.z>=w then return end local O={V[S.x][S.y][S.z];V[S.x+1][S.y][S.z],V[S.x+1][S.y][S.z+1],V[S.x][S.y][S.z+1];V[S.x][S.y+1][S.z];V[S.x+1][S.y+1][S.z],V[S.x+1][S.y+1][S.z+1];V[S.x][S.y+1][S.z+1]}local f=0 for K=0,7,1 do if O[K+1]and O[K+1].w<a then f=bit32.bor(f,bit32.lshift(1,K))end end f=f+1 if S.lastCubeIndex and S.lastCubeIndex==f then return end S.lastCubeIndex=f local n={}local C=1 while W[f][C]~=-1 do local K=T[W[f][C]+1]local V=v[W[f][C]+1]local e=T[W[f][C+1]+1]local X=v[W[f][C+1]+1]local w=T[W[f][C+2]+1]local a=v[W[f][C+2]+1]local p,x=B(o(O[K+1],O[V+1]),o(O[e+1],O[X+1]),o(O[w+1],O[a+1]))local J=I[S.cellType]for K,V in pairs(J)do if K~="\067\104\105\108\100\114\101\110"and K~="\077\097\116\101\114\105\097\108\083\116\114\101\110\103\116\104"then if not J.MaterialStrength or K~="\077\097\116\101\114\105\097\108"then p[K]=V x[K]=V end end end if J.MaterialStrength then local K=p:Clone()K.Transparency=J.MaterialStrength K.Material=J.Material table.insert(n,K)local V=x:Clone()V.Transparency=J.MaterialStrength V.Material=J.Material table.insert(n,V)p.Transparency=0 x.Transparency=0 end if J.Children then for K,V in pairs(J.Children)do local S=Instance.new(V.class)for K,V in pairs(V.props)do S[K]=V end S.Parent=p;(S:Clone()).Parent=x end end C=C+3 table.insert(n,p)table.insert(n,x)end for K,V in pairs(K.tris)do e.recycle(V)end for K,V in pairs(n)do V.Parent=p end K.tris=n end return f
local GenerationConfig = require(script.Parent.GenerationConfig)
local MarchTables = require(script.Parent.MarchTables)
local createTriangle = require(script.Parent.createTriangle)
local TriStorage = require(script.Parent.TriStorage)
local CellStyles = require(script.Parent.CellStyles)

local OresCounter
if GenerationConfig.oreCounterEnabled then
	local PlayerGui = game.Players.LocalPlayer:WaitForChild("PlayerGui")
	OresCounter = PlayerGui.ScreenGui.Ores
end

--Marching cubes tables
local TRIANGULATION = MarchTables.triangulation
local CORNER_INDEX_A_FROM_EDGE = MarchTables.cornerIndexAFromEdge
local CORNER_INDEX_B_FROM_EDGE = MarchTables.cornerIndexBFromEdge

local WORLD_SCALE = GenerationConfig.worldScale
local CELLS_PER_AXIS = GenerationConfig.cellsPerAxis
local ISO_LEVEL = GenerationConfig.isoLevel

--Helper functions
local function renderTriangle(posA, posB, posC) 
	return createTriangle(posA*WORLD_SCALE, posB*WORLD_SCALE, posC*WORLD_SCALE)
end

local function interpolateVerts(v1,  v2) 
    return Vector3.new(v1.x, v1.y, v1.z):lerp(Vector3.new(v2.x, v2.y, v2.z), 0.5)
end

local worldModel = Instance.new("Model", workspace)
worldModel.Name = "MarchingCubesWorld"

--Grid cell class
local GridCell = {}

function GridCell.new(x,y,z, w)
	local cell = {
		x = x,
		y = y,
		z = z,
		w = w,
		tris = {},
	}
	
	setmetatable(cell, {__index = GridCell})
	
	return cell
end

function GridCell:destroy(dontUpdate)
	if self.w ~= 0 and self.cellType ~= "worldBorder" then
		self.w = 0
		
		if GenerationConfig.oreCounterEnabled and OresCounter:FindFirstChild(self.cellType) then
			OresCounter[self.cellType].Quantity.Text = tonumber(OresCounter[self.cellType].Quantity.Text) + 1
		end
		
		if not dontUpdate then
			self:updateAdjacent()
		end
	end
end

function GridCell:updateAdjacent()
	for _, cell in pairs(self:getAdjacent()) do
		cell:update()
	end
end

function GridCell:getAdjacent(facesOnly)
	local grid = self.grid
	local adjacent = {}
	
	if not facesOnly then
		for x = self.x-1, self.x+1 do
			for y = self.y-1, self.y+1 do
				for z = self.z-1, self.y+1 do
					table.insert(adjacent, grid[x][y][z])
				end
			end
		end
	else
		local x,y,z = self.x, self.y, self.z
		
		table.insert(adjacent, grid[x][y][z])
		table.insert(adjacent, grid[x+1][y][z])
		table.insert(adjacent, grid[x][y][z+1])
		table.insert(adjacent, grid[x][y+1][z])
		table.insert(adjacent, grid[x-1][y][z])
		table.insert(adjacent, grid[x][y][z-1])
		table.insert(adjacent, grid[x][y-1][z])
	end
	
	return adjacent
end

function GridCell:getCellsInRadius(radius)
	local grid = self.grid
	local cells = {}
	
	for x = self.x - radius, self.x + radius do
		for y = self.y - radius, self.y + radius do
			for z = self.z - radius, self.z + radius do
				if grid[x][y][z] and (Vector3.new(self.x, self.y, self.z) - Vector3.new(x, y, z)).magnitude <= radius then
					table.insert(cells, grid[x][y][z])
				end
			end
		end
	end
	
	return cells
end

function GridCell:getCellsInRange(range)
	local grid = self.grid
	local cells = {}
	
	for x = self.x - range, self.x + range do
		for y = self.y - range, self.y + range do
			for z = self.z - range, self.z + range do
				table.insert(cells, grid[x][y][z])
			end
		end
	end
	
	return cells
end

function GridCell:update()
	local grid = self.grid	
	local cell = self
	local w = cell.w
	
	if cell.x >= CELLS_PER_AXIS or cell.y >= CELLS_PER_AXIS or cell.z >= CELLS_PER_AXIS then
       return
    end
	
    local cubeCorners = {
		grid[cell.x][cell.y][cell.z],
        grid[cell.x+1][cell.y][cell.z],
        grid[cell.x+1][cell.y][cell.z+1],
        grid[cell.x][cell.y][cell.z+1],
        grid[cell.x][cell.y+1][cell.z],
        grid[cell.x+1][cell.y+1][cell.z],
        grid[cell.x+1][cell.y+1][cell.z+1],
        grid[cell.x][cell.y+1][cell.z+1]
    }

    local cubeIndex = 0;
	for i = 0, 7 do
		if cubeCorners[i+1] and cubeCorners[i+1].w < ISO_LEVEL then
			cubeIndex = bit32.bor(cubeIndex, bit32.lshift(1, i))
		end
	end
	
	cubeIndex = cubeIndex + 1
	
	if cell.lastCubeIndex and cell.lastCubeIndex == cubeIndex then
		return
	end
	
	cell.lastCubeIndex = cubeIndex
	
	local newTris = {}
	
	local i = 1
	while TRIANGULATION[cubeIndex][i] ~= -1 do
        local a0 = CORNER_INDEX_A_FROM_EDGE[TRIANGULATION[cubeIndex][i]+1]
        local b0 = CORNER_INDEX_B_FROM_EDGE[TRIANGULATION[cubeIndex][i]+1]

        local a1 = CORNER_INDEX_A_FROM_EDGE[TRIANGULATION[cubeIndex][i+1]+1]
        local b1 = CORNER_INDEX_B_FROM_EDGE[TRIANGULATION[cubeIndex][i+1]+1]

        local a2 = CORNER_INDEX_A_FROM_EDGE[TRIANGULATION[cubeIndex][i+2]+1]
        local b2 = CORNER_INDEX_B_FROM_EDGE[TRIANGULATION[cubeIndex][i+2]+1]
		
		local t1, t2 = renderTriangle(
			interpolateVerts(cubeCorners[a0+1], cubeCorners[b0+1]),
			interpolateVerts(cubeCorners[a1+1], cubeCorners[b1+1]),
			interpolateVerts(cubeCorners[a2+1], cubeCorners[b2+1])
		)
		
		local style = CellStyles[cell.cellType]
		for prop, val in pairs(style) do
			if prop ~= "Children" and prop ~= "MaterialStrength" then
				if not style.MaterialStrength or (prop ~= "Material")  then
					t1[prop] = val
					t2[prop] = val
				end
			end
		end
		
		--Material strength affects how visible the material is by making a translucent copy that layers on top of a smooth, opaque part
		if style.MaterialStrength then
			local base1 = t1:Clone()
			base1.Transparency = style.MaterialStrength
			base1.Material = style.Material
			table.insert(newTris, base1)
			
			local base2 = t2:Clone()
			base2.Transparency = style.MaterialStrength
			base2.Material = style.Material
			table.insert(newTris, base2)
			
			t1.Transparency = 0
			t2.Transparency = 0
		end
		
		if style.Children then
			for _, childData in pairs(style.Children) do
				local child = Instance.new(childData.class)
				for prop, value in pairs(childData.props) do
					child[prop] = value
				end
				child.Parent = t1
				child:Clone().Parent = t2
			end
		end
		
		i = i + 3
		
		table.insert(newTris, t1)
		table.insert(newTris, t2)
    end
	
	--clear old tris
	for _, tri in pairs(self.tris) do
		TriStorage.recycle(tri)
	end
	
	--render new tris
	for _, tri in pairs(newTris) do
		tri.Parent = worldModel
	end
	
	self.tris = newTris
end

return GridCell