local z=require(script.Parent.DefaultColorset)local U=require(script.Parent.Utils)local Q={TileSide=20;HugeTileSide=40;TileHeight=5;HeightRange=400;SlopeLength=3;WaterSlopeLength=3;SlopesEnabled=true,water_plane_y_offset=-0.5,water_plane_thickness=.1,TreesEnabled=true;Colorset=z;status="\073\100\108\101"}local M=function()local z={}return setmetatable(z,{__index=Q})end local function v(z,U)if U-z>=.001 then return 1 end return 0 end local function n(z,U,Q)local M=z[U][Q]local n=0 local h=0 if z[U-1]then if z[U-1][Q]then n=n+4*v(M,z[U-1][Q])end if z[U-1][Q+1]then h=h+4*v(M,z[U-1][Q+1])end if z[U-1][Q-1]then h=h+2*v(M,z[U-1][Q-1])end end if z[U+1]then if z[U+1][Q]then n=n+8*v(M,z[U+1][Q])end if z[U+1][Q+1]then h=h+8*v(M,z[U+1][Q+1])end if z[U+1][Q-1]then h=h+1*v(M,z[U+1][Q-1])end end if z[U]then if z[U][Q-1]then n=n+1*v(M,z[U][Q-1])end if z[U][Q+1]then n=n+2*v(M,z[U][Q+1])end end return n,h end local function h(z,U,Q)local M=z.rivers[U][Q]local v={}local n=z:get_water_surface_level(U+1,Q)if n and n-M<-0.001 then v[3]=M-n end n=z:get_water_surface_level(U-1,Q)if n and n-M<-0.001 then v[1]=M-n end n=z:get_water_surface_level(U,Q+1)if n and n-M<-0.001 then v[2]=M-n end n=z:get_water_surface_level(U,Q-1)if n and n-M<-0.001 then v[0]=M-n end return v end function Q.apply_water_visual_properties(U,Q)if U.Colorset.colors.water then Q.Color=U.Colorset.colors.water else Q.Color=z.colors.water end Q.TopSurface=Enum.SurfaceType.Smooth Q.BottomSurface=Enum.SurfaceType.Smooth Q.CastShadow=false Q.Transparency=.35 Q.Reflectance=.5 Q.Material=Enum.Material.Glass end function Q.apply_water_properties(z,U)z:apply_water_visual_properties(U)U.CanCollide=false U.Anchored=true end function Q.generate_water_plane(z,U,Q,M,v,n,h)n=n or 0 h=h or 0 local e=Instance.new("\077\111\100\101\108")e.Name="\087\097\116\101\114\080\108\097\110\101"e.Parent=U local a=M*z.HugeTileSide local b=v*z.HugeTileSide local y,A local i,Z y=math.ceil(a/2048)A=math.ceil(b/2048)i=a/y Z=b/A local J=Instance.new("\080\097\114\116")J.Name="\087\097\116\101\114\080\108\097\110\101\083\101\103\109\101\110\116"J.Size=Vector3.new(i,z.water_plane_thickness,Z)z:apply_water_properties(J)local o=Q*z.HeightRange+z.water_plane_y_offset for U=1,y,1 do for Q=1,A,1 do local M=(i*(U-.5)-z.HugeTileSide)+n local v=(Z*(Q-.5)-z.HugeTileSide)+h local a=J:Clone()a.Position=Vector3.new(M,o,v)a.Parent=e end end J:Destroy()end function Q.UpdateStatus(z,U)z.status=U if U~="\073\100\108\101 \040\070\105\110\105\115\104\101\100\041"and U~="\073\100\108\101"then wait()end end function Q.Build(z,Q,M,v,e)if Q.status~="\073\110\105\116\105\097\108\105\122\101\100"then error("\070\097\105\108\101\100 \116\111 \098\117\105\108\100 \097 \109\097\112\058 \084\101\114\114\097\105\110\068\101\115\099\114\105\112\116\111\114 \105\115\110\'\116 \105\110\105\116\105\097\108\105\122\101\100\033")end if not rawget(z,"\067\111\108\111\114\115\101\116")then print("\078\111 \099\117\115\116\111\109 \099\111\108\111\114\115\101\116 \112\097\115\115\101\100\044 \103\111\105\110\103 \116\111 \117\115\101 \097 \100\101\102\097\117\108\116 \111\110\101")elseif not z.Colorset.colors.water then warn("\078\111 \119\097\116\101\114 \099\111\108\111\114 \115\112\101\099\105\102\105\101\100 \105\110 \099\117\114\114\101\110\116 \099\111\108\111\114\115\101\116\044 \103\111\105\110\103 \116\111 \117\115\101 \097 \100\101\102\097\117\108\116 \111\110\101 \040\099\111\108\111\114\041")end if not M then warn("\078\111 \119\111\114\108\100 \099\111\110\116\097\105\110\101\114 \115\112\101\099\105\102\105\101\100\044 \099\114\101\097\116\105\110\103 \111\110\101 \105\110 \103\097\109\101\046\087\111\114\107\115\112\097\099\101")M=Instance.new("\077\111\100\101\108")M.Name="\087\111\114\108\100"M.Parent=workspace end local a=1/(z.HeightRange/z.TileHeight)local b=Random.new(0)local y=v or 0 local A=e or 0 local i=Q.Heightmap local Z=Q.lakes local J=Q.rivers local o=Q.trees local j=Q.water_level local W=Q.width local D=Q.height z:UpdateStatus("\066\117\105\108\100\105\110\103 \104\117\103\101 \116\105\108\101\115")local K={}for z=0,z.HeightRange/z.TileHeight,1 do K[z]={}for Q=1,W,1 do K[z][Q]={}local M for v=1,D,1 do if U.round(i[Q][v]/a)>=z then if not M then M=1 else M=M+1 end elseif M then K[z][Q][v-M]={v-1}M=nil end if M and v==D then K[z][Q][(v-M)+1]={v}end end end end for z=0,z.HeightRange/z.TileHeight,1 do for U=1,W-1,1 do for Q,M in pairs(K[z][U])do for U=U+1,W,1 do local v=K[z][U][Q]if v then if M[1]==v[1]then M[2]=M[2]and M[2]+1 or 2 K[z][U][Q]=nil else break end else break end end end end end local N=0 for U=0,z.HeightRange/z.TileHeight,1 do local Q=z.Colorset:pickColor(U*a)for v=1,W,1 do for n,h in pairs(K[U][v])do local e=(h[2]or 1)*z.HugeTileSide local a=((h[1]-n)+1)*z.HugeTileSide local b=(U-.5)*z.TileHeight local i=math.ceil(e/2048)local Z=math.ceil(a/2048)e=e/i a=a/Z for U=1,i,1 do local h=(((v-1)*z.HugeTileSide+e/2)+e*(U-1))+y for U=1,Z,1 do N=N+1 local v=(((n-1)*z.HugeTileSide+a/2)+a*(U-1))+A local y=Instance.new("\080\097\114\116")y.Size=Vector3.new(e,z.TileHeight,a)y.Anchored=true y.CFrame=CFrame.new(h,b,v)y.Color=Q y.Parent=M y.TopSurface=Enum.SurfaceType.Smooth end end end end end print("\067\111\117\110\116\058 "..(tostring(N)..(" \105\110\115\116\101\097\100 \111\102 "..(tostring(W*D)..("\044 \099\111\109\112\114\101\115\115\105\111\110 \114\097\116\101\058 "..(tostring((1-N/(W*D))*100).."\037"))))))if z.SlopesEnabled then local U=0 z:UpdateStatus("\066\117\105\108\100\105\110\103 \115\108\111\112\101\115 \049\047\050")local Q={}for z=1,W,1 do Q[z]={}local U local M local v for h=1,D,1 do local e=n(i,z,h)e=math.floor(e/4)local a=i[z][h]if e~=0 then if not U then U=e M=a v=1 elseif a~=M or e~=U then Q[z][h-v]={h-1,U;M}U=e M=a v=1 else v=v+1 end elseif U then Q[z][h-v]={h-1;U,M}U=nil end if U and h==D then Q[z][(h-v)+1]={h;U,M}end end end for v=1,W,1 do for Q,n in pairs(Q[v])do U=U+1 local h=z.Colorset:pickColor(n[3])local e=n[2]local a=z.HugeTileSide*((n[1]-Q)+1)local b=z.TileHeight local i=z.SlopeLength local Z=Vector3.new(a,b,i)local J local o=n[3]*z.HeightRange+z.TileHeight/2 local j=((Q-1)*z.HugeTileSide+a/2)+A local W=Instance.new("\087\101\100\103\101\080\097\114\116")W.Anchored=true W.Size=Z if e==2 or e==3 then W.Rotation=Vector3.new(0,90,0)J=(v*z.HugeTileSide-i/2)+y else W.Rotation=Vector3.new(0,-90,0)J=((v-1)*z.HugeTileSide+i/2)+y end W.Position=Vector3.new(J,o,j)W.Color=h W.Parent=M if e==3 then U=U+1 local Q=Instance.new("\087\101\100\103\101\080\097\114\116")Q.Anchored=true Q.Rotation=Vector3.new(0,-90,0)Q.Size=Z local n=((v-1)*z.HugeTileSide+i/2)+y Q.Position=Vector3.new(n,o,j)Q.Color=h Q.Parent=M end end end z:UpdateStatus("\066\117\105\108\100\105\110\103 \115\108\111\112\101\115 \050\047\050")local v={}for z=1,D,1 do v[z]={}local U local Q local M for h=1,W,1 do local e=n(i,h,z)e=e%4 local a=i[h][z]if e~=0 then if not U then U=e Q=a M=1 elseif a~=Q or e~=U then v[z][h-M]={h-1,U;Q}U=e Q=a M=1 else M=M+1 end elseif U then v[z][h-M]={h-1;U;Q}U=nil end if U and h==W then v[z][(h-M)+1]={h;U;Q}end end end for Q=1,D,1 do for v,n in pairs(v[Q])do local h=n[2]U=U+1 local e=z.HugeTileSide*((n[1]-v)+1)local a=z.TileHeight local b=z.SlopeLength local i=Vector3.new(e,a,b)local Z=((v-1)*z.HugeTileSide+e/2)+y local J=n[3]*z.HeightRange+z.TileHeight/2 local o local j=Instance.new("\087\101\100\103\101\080\097\114\116")if h==2 or h==3 then j.Rotation=Vector3.new(0,0,0)o=(Q*z.HugeTileSide-b/2)+A else j.Rotation=Vector3.new(0,180,0)o=((Q-1)*z.HugeTileSide+b/2)+A end j.Anchored=true j.Position=Vector3.new(Z,J,o)j.Color=z.Colorset:pickColor(n[3])j.Size=i j.Parent=M if h==3 then U=U+1 local v=((Q-1)*z.HugeTileSide+b/2)+A local h=Instance.new("\087\101\100\103\101\080\097\114\116")h.Anchored=true h.Rotation=Vector3.new(0,180,0)h.Size=i h.Position=Vector3.new(Z,J,v)h.Color=z.Colorset:pickColor(n[3])h.Parent=M end end end print("\083\108\111\112\101\115\058 "..(tostring(U)..("\044 \084\111\116\097\108\058 "..tostring(U+N))))end z:generate_water_plane(M,j,W,D,y,A)z:UpdateStatus("\067\114\101\097\116\105\110\103 \108\097\107\101\115")local p=0 if Z then for U=1,W,1 do if Z[U]then local Q local v local n=U*z.HugeTileSide+y for h=1,D,1 do local e=Z[U][h]if e then if not Q then Q=1 v=e else Q=Q+1 end elseif Q then p=p+1 local U=(h-Q)*z.HugeTileSide+A z:generate_water_plane(M,v,1,Q,n,U)Q=nil end if Q and h==D then p=p+1 local U=((h-Q)+1)*z.HugeTileSide+A z:generate_water_plane(M,v,1,Q,n,U)end end end end print("\071\101\110\101\114\097\116\101\100 \108\097\107\101 \119\097\116\101\114 \112\108\097\110\101\115\058",p)end z:UpdateStatus("\067\114\101\097\116\105\110\103 \114\105\118\101\114\115")for v=1,W,1 do if J[v]then for n=1,D,1 do local e=J[v][n]if e and e-j>.001 then z:generate_water_plane(M,e,1,1,v*z.HugeTileSide+y,n*z.HugeTileSide+A)local b=h(Q,v,n)for Q,h in pairs(b)do local b=Instance.new("\087\101\100\103\101\080\097\114\116")b.Anchored=true b.Size=Vector3.new(z.HugeTileSide,z.TileHeight,z.WaterSlopeLength)b.Orientation=Vector3.new(0,90*Q,0)local i=y local Z=((e*z.HeightRange-b.Size.Y/2)+z.water_plane_y_offset)+z.water_plane_thickness/2 local J=A if Q==0 then i=i+(v-.5)*z.HugeTileSide J=(J+(n-1)*z.HugeTileSide)-b.Size.Z/2 elseif Q==1 then i=(i+(v-1)*z.HugeTileSide)-b.Size.Z/2 J=J+(n-.5)*z.HugeTileSide elseif Q==2 then i=i+(v-.5)*z.HugeTileSide J=(J+n*z.HugeTileSide)+b.Size.Z/2 elseif Q==3 then i=(i+v*z.HugeTileSide)+b.Size.Z/2 J=J+(n-.5)*z.HugeTileSide end b.Position=Vector3.new(i,Z,J)b.CanCollide=false z:apply_water_properties(b)b.Parent=M local o=U.round(h/a)if o>1 then local U=Instance.new("\080\097\114\116")U.Anchored=true U.Size=Vector3.new(z.HugeTileSide,z.TileHeight*(o-1),z.WaterSlopeLength)U.Orientation=b.Orientation local Q=b.Position.X local v=(b.Position.Y-b.Size.Y/2)-U.Size.Y/2 local n=b.Position.Z U.Position=Vector3.new(Q,v,n)z:apply_water_properties(U)U.CanCollide=false U.Parent=M end end end end end end local R=2 if z.TreesEnabled then z:UpdateStatus("\067\114\101\097\116\105\110\103 \116\114\101\101\115")local Q=z.tree_meshes if not Q then if script.Parent:FindFirstChild("\077\101\115\104\101\115")then Q=script.Parent.Meshes.Trees:GetChildren()else Q={}end end if#Q==0 then warn("\078\111 \116\114\101\101 \109\101\115\104\101\115 \102\111\117\110\100\033 \084\114\101\101\115 \119\105\108\108 \110\111\116 \098\101 \103\101\110\101\114\097\116\101\100\046")end if type(o)=="\116\097\098\108\101"and#Q>0 then for M,v in pairs(o)do local n=math.ceil(M/R)for v,h in pairs(v)do local e=math.ceil(v/R)if h then local h=(M-.5)*z.TileSide+y local a=i[n][e]*z.HeightRange local Z=(v-.5)*z.TileSide+A local J=Vector3.new(h,a,Z)local o=b:NextInteger(1,#Q)local j=Q[o]:Clone()local W=b:NextInteger(1,360)local D=U.turn_vector2(Vector2.new(j.Position.X,j.Position.Z),W)local K=Vector3.new(D.X,j.Position.Y,D.Y)j.Orientation=Vector3.new(0,-W,0)j.Position=K+J j.Anchored=true j.Parent=workspace end end end end end z:UpdateStatus("\073\100\108\101 \040\070\105\110\105\115\104\101\100\041")return M end return setmetatable({new=M},{__newindex=function()error("\084\101\114\114\097\105\110\066\117\105\108\100\101\114\058 \065\116\116\101\109\112\116 \116\111 \101\100\105\116 \097 \114\101\097\100-\111\110\108\121 \119\114\097\112\112\101\114\033 \077\097\107\101 \115\117\114\101 \116\104\097\116 \121\111\117 \099\114\101\097\116\101 \097 \110\101\119 \111\098\106\101\099\116 \102\105\114\115\116")end})
local DefaultColorset = require(script.Parent.DefaultColorset)
local Utils = require(script.Parent.Utils)

local TerrainBuilderBase = {
	 TileSide = 20
	,HugeTileSide = 40
	,TileHeight = 5
	,HeightRange = 400

	,SlopeLength = 3
	,WaterSlopeLength = 3
	,SlopesEnabled = true

	,water_plane_y_offset  = -0.5
	,water_plane_thickness = 0.1

	,TreesEnabled = true

	,Colorset = DefaultColorset
	,status = "Idle"
}

local object_constructor = function()
	local object = {}

	return setmetatable(object, {__index = TerrainBuilderBase})
end

local function compare(v1,v2)
	--should'nt be compared directly because of the way doubles behave
	if v2 - v1 >= 0.001 then
		return 1
	end

	return 0
end

local function HashSurroundings(Heightmap,x,y)
	local noise_val = Heightmap[x][y]
	local side_hash = 0

	--[[
	c2         s2      c3
  	           X
	s3 <-------|--------Y s4
	           V
	c1         s1      c4
	--]]
	local corner_hash = 0b0000

	if Heightmap[x - 1] then
		if Heightmap[x - 1][y] then
			side_hash = side_hash + 0b0100 * compare(noise_val,Heightmap[x-1][y])
		end

		if Heightmap[x - 1][y + 1] then
			corner_hash = corner_hash + 0b0100 * compare(noise_val,Heightmap[x-1][y+1])
		end

		if Heightmap[x - 1][y - 1] then
			corner_hash = corner_hash + 0b0010 * compare(noise_val,Heightmap[x-1][y-1])
		end
	end
	if Heightmap[x + 1] then
		if Heightmap[x + 1][y] then
			side_hash = side_hash + 0b1000 * compare(noise_val,Heightmap[x+1][y])
		end

		if Heightmap[x + 1][y + 1] then
			corner_hash = corner_hash + 0b1000 * compare(noise_val,Heightmap[x+1][y+1])
		end

		if Heightmap[x + 1][y - 1] then
			corner_hash = corner_hash + 0b0001 * compare(noise_val,Heightmap[x+1][y-1])
		end
	end

	if Heightmap[x] then
		if Heightmap[x][y - 1] then
			side_hash = side_hash + 0b0001 * compare(noise_val,Heightmap[x][y-1])
		end
		if Heightmap[x][y + 1] then
			side_hash = side_hash + 0b0010 * compare(noise_val,Heightmap[x][y+1])
		end
	end

	return side_hash, corner_hash
end

local function GetSurroundings_Water(TerrainDescriptor,x,y)
	local noise_val = TerrainDescriptor.rivers[x][y]
	local sides = {}

	local compare_val = TerrainDescriptor:get_water_surface_level(x+1,y)
	if compare_val and compare_val - noise_val < -0.001 then
		sides[3] = noise_val-compare_val
	end

	compare_val = TerrainDescriptor:get_water_surface_level(x-1,y)
	if compare_val and compare_val - noise_val < -0.001 then
		sides[1] = noise_val-compare_val
	end

	compare_val = TerrainDescriptor:get_water_surface_level(x,y+1)
	if compare_val and compare_val - noise_val < -0.001 then
		sides[2] = noise_val-compare_val
	end

	compare_val = TerrainDescriptor:get_water_surface_level(x,y-1)
	if compare_val and compare_val - noise_val < -0.001 then
		sides[0] = noise_val-compare_val
	end

	return sides
end

function TerrainBuilderBase.apply_water_visual_properties(self,part)
	if self.Colorset.colors.water then
		part.Color = self.Colorset.colors.water
	else
		part.Color = DefaultColorset.colors.water
	end

	part.TopSurface = Enum.SurfaceType.Smooth
	part.BottomSurface = Enum.SurfaceType.Smooth
	part.CastShadow = false
	part.Transparency = 0.35
	part.Reflectance = 0.5
	part.Material = Enum.Material.Glass
end

function TerrainBuilderBase.apply_water_properties(self,part)
	self:apply_water_visual_properties(part)

	part.CanCollide = false
	part.Anchored = true
end

function TerrainBuilderBase.generate_water_plane(self,container,level,width,height,offsetX,offsetY)
	offsetX = offsetX or 0
	offsetY = offsetY or 0

	local water_plane_group = Instance.new("Model")
	water_plane_group.Name = "WaterPlane"
	water_plane_group.Parent = container

	-- Because of 2048x2048x2048 size cap
	-- water plane should be split on parts
	local water_width = width * self.HugeTileSide
	local water_height = height * self.HugeTileSide

	local countX, countY
	--Size of the part of water plane
	local part_sizeX,part_sizeY

	countX = math.ceil(water_width/2048)
	countY = math.ceil(water_height/2048)

	part_sizeX = water_width / countX
	part_sizeY = water_height / countY

	local water_plane = Instance.new("Part")
	water_plane.Name = "WaterPlaneSegment"
	water_plane.Size = Vector3.new(part_sizeX, self.water_plane_thickness, part_sizeY)

	self:apply_water_properties(water_plane)

	local posY = level * self.HeightRange + self.water_plane_y_offset

	for x=1,countX do
		for y=1,countY do
			local posX = part_sizeX * (x-0.5) - self.HugeTileSide + offsetX
			local posZ = part_sizeY * (y-0.5) - self.HugeTileSide + offsetY

			local segment = water_plane:Clone()
			segment.Position = Vector3.new(posX, posY, posZ)
			segment.Parent = water_plane_group
		end
	end

	water_plane:Destroy()
end

function TerrainBuilderBase.UpdateStatus(self,string_status)
	self.status = string_status
	if string_status ~= "Idle (Finished)" and string_status ~= "Idle" then
		wait()
	end
end

-- self,Heightmap,rivers,lakes,trees,container,offsetX,offsetY,width,height
function TerrainBuilderBase.Build(self,TerrainDescriptor,container,offsetX,offsetY)
	if TerrainDescriptor.status ~= "Initialized" then
		error("Failed to build a map: TerrainDescriptor isn't initialized!")
	end

	if not rawget(self, "Colorset") then
		print("No custom colorset passed, going to use a default one")
	elseif not self.Colorset.colors.water then
		warn("No water color specified in current colorset, going to use a default one (color)")
	end

	if not container then
		warn("No world container specified, creating one in game.Workspace")
		container = Instance.new("Model")
		container.Name = "World"
		container.Parent = workspace
	end

	local tile_height_step = 1 / (self.HeightRange / self.TileHeight)

	local RandomGen = Random.new(0)

	local offsetX = offsetX or 0
	local offsetY = offsetY or 0

	local Heightmap = TerrainDescriptor.Heightmap
	local lakes = TerrainDescriptor.lakes
	local rivers = TerrainDescriptor.rivers
	local trees = TerrainDescriptor.trees

	local water_level = TerrainDescriptor.water_level

	local map_width = TerrainDescriptor.width
	local map_height = TerrainDescriptor.height

	-- | Optimizing large flat tiles |
	-- height = { x = {y1 = {y2,x_size}} }
	-- step1: make y-axis rows (rows should be interrupted only by tiles at lower height)
	-- step2: optimize rows into squares: merge near rows with equal y1 and y2, append x2 to each table

	-- | Test cases: |
	--The lowerst layer (at height 0) should be a whole square

	self:UpdateStatus("Building huge tiles")

	local ProcessedHeightmap = {}

	--Step 1
	for height = 0, self.HeightRange / self.TileHeight do

		ProcessedHeightmap[height] = {}

		for x = 1,map_width do

			ProcessedHeightmap[height][x] = {}
			local row_length

			for y=1,map_height do

				--Ah yes, I love comparing doubles that are expceted to be whole but in fact are fractional
				--(yep, "round" function is a MUST here)
				if Utils.round(Heightmap[x][y] / tile_height_step) >= height then

					if not row_length then
						row_length = 1
					else
						row_length = row_length + 1
					end

				elseif row_length then
					ProcessedHeightmap[height][x][y-row_length] = {y-1}

					row_length = nil
				end

				if row_length and y == map_height then
					--Row is interrupted by map edge
					ProcessedHeightmap[height][x][y-row_length+1] = {y}
				end

			end

		end
	end

	--Step 2
	for height = 0, self.HeightRange / self.TileHeight do
		for x = 1, map_width-1 do
			for y,row in pairs(ProcessedHeightmap[height][x]) do

				for x_merge = x+1, map_width do

					local next_val = ProcessedHeightmap[height][x_merge][y]

					if next_val then
						if row[1] == next_val[1] then
							row[2] = row[2] and row[2]+1 or 2
							ProcessedHeightmap[height][x_merge][y] = nil
						else
							break
						end
					else
						break
					end

				end

			end
		end
	end

	--Build huge tiles
	local count = 0

	for height = 0, self.HeightRange / self.TileHeight do
		local picked_color = self.Colorset:pickColor(height * tile_height_step)
		for x = 1, map_width do
			for y,row in pairs(ProcessedHeightmap[height][x]) do

				local XSize = (row[2] or 1) * self.HugeTileSide
				local YSize = (row[1]-y+1) * self.HugeTileSide

				local HeightPos = (height - 0.5) * self.TileHeight

				local X_Tiles = math.ceil(XSize/2048)
				local Y_Tiles = math.ceil(YSize/2048)

				XSize = XSize / X_Tiles
				YSize = YSize / Y_Tiles

				for x_tile = 1, X_Tiles do

					local XPos = (x-1) * self.HugeTileSide + XSize/2 + XSize * (x_tile-1) + offsetX

					for y_tile = 1,Y_Tiles do
						count = count + 1
						local YPos = (y-1) * self.HugeTileSide + YSize/2 + YSize * (y_tile-1) + offsetY

						local Tile = Instance.new("Part")
						Tile.Size = Vector3.new(XSize,self.TileHeight,YSize)
						Tile.Anchored = true
						Tile.CFrame = CFrame.new(XPos,HeightPos,YPos)
						Tile.Color = picked_color
						Tile.Parent = container
						Tile.TopSurface = Enum.SurfaceType.Smooth
					end
				end

			end
		end
	end

	print("Count: "..tostring(count).." instead of "..tostring(map_width*map_height)..", compression rate: "..tostring( (1-(count/(map_width*map_height)))*100 ).."%")

	if self.SlopesEnabled then
		local slope_count = 0

		self:UpdateStatus("Building slopes 1/2")
		local wedge_parts_y = {}

		for x = 1, map_width do
			wedge_parts_y[x] = {}

			local row_hash
			local row_height
			local row_length

			for y = 1, map_height do

				local hash = HashSurroundings(Heightmap,x,y)
				hash = math.floor(hash / 0b0100)

				local height = Heightmap[x][y]

				if hash ~= 0 then
					if not row_hash then
						row_hash = hash
						row_height = height
						row_length = 1
					elseif height ~= row_height or hash ~= row_hash then
						wedge_parts_y[x][y-row_length] = {y-1, row_hash, row_height}

						row_hash = hash
						row_height = height
						row_length = 1
					else
						row_length = row_length + 1
					end
				elseif row_hash then
					wedge_parts_y[x][y-row_length] = {y-1, row_hash, row_height}

					row_hash = nil
				end

				if row_hash and y == map_height then
					wedge_parts_y[x][y-row_length+1] = {y, row_hash, row_height}
				end

			end
		end


		for x=1, map_width do
			for y,row in pairs(wedge_parts_y[x]) do

				slope_count = slope_count + 1
				local picked_color = self.Colorset:pickColor(row[3])
				local hash = row[2]

				--Size
				local SizeX = self.HugeTileSide * (row[1] - y + 1)
				local SizeY = self.TileHeight
				local SizeZ = self.SlopeLength

				local SizeVector = Vector3.new(SizeX, SizeY, SizeZ)

				--Position of wedge in workspace
				local workspaceX
				local workspaceY = row[3] * self.HeightRange + self.TileHeight/2
				local workspaceZ = (y-1) * self.HugeTileSide + SizeX/2 + offsetY

				local wedge_part = Instance.new("WedgePart")
				wedge_part.Anchored = true
				wedge_part.Size = SizeVector

				if hash == 0b10 or hash == 0b11 then
					wedge_part.Rotation = Vector3.new(0,90,0)
					workspaceX = x * self.HugeTileSide - SizeZ/2 + offsetX
				else
					wedge_part.Rotation = Vector3.new(0,-90,0)
					workspaceX = (x-1) * self.HugeTileSide + SizeZ/2 + offsetX
				end

				wedge_part.Position = Vector3.new(workspaceX,workspaceY,workspaceZ)
				wedge_part.Color = picked_color
				wedge_part.Parent = container

				if hash == 0b11 then
					slope_count = slope_count + 1

					local wedge_part2 = Instance.new("WedgePart")
					wedge_part2.Anchored = true
					wedge_part2.Rotation = Vector3.new(0,-90,0)
					wedge_part2.Size = SizeVector
					local workspaceX = (x-1) * self.HugeTileSide + SizeZ/2 + offsetX
					wedge_part2.Position = Vector3.new(workspaceX,workspaceY,workspaceZ)
					wedge_part2.Color = picked_color
					wedge_part2.Parent = container
				end

			end
		end

		self:UpdateStatus("Building slopes 2/2")

		local wedge_parts_x = {}

		for y = 1,map_height do
			wedge_parts_x[y] = {}

			local row_hash
			local row_height
			local row_length

			for x = 1,map_width do

				local hash = HashSurroundings(Heightmap,x,y)
				hash = hash % 0b0100

				local height = Heightmap[x][y]

				if hash ~= 0 then
					if not row_hash then
						row_hash = hash
						row_height = height
						row_length = 1
					elseif height ~= row_height or hash ~= row_hash then
						wedge_parts_x[y][x-row_length] = {x-1,row_hash,row_height}

						row_hash = hash
						row_height = height
						row_length = 1
					else
						row_length = row_length + 1
					end
				elseif row_hash then
					wedge_parts_x[y][x-row_length] = {x-1,row_hash,row_height}

					row_hash = nil
				end

				if row_hash and x == map_width then
					wedge_parts_x[y][x-row_length+1] = {x,row_hash,row_height}
				end

			end
		end

		for y=1, map_height do
			for x,row in pairs(wedge_parts_x[y]) do

				local hash = row[2]
				slope_count = slope_count + 1

				--Size
				local SizeX = self.HugeTileSide * (row[1] - x + 1)
				local SizeY = self.TileHeight
				local SizeZ = self.SlopeLength
				local SizeVector = Vector3.new(SizeX,SizeY,SizeZ)

				--Position of wedge in workspace
				local workspaceX = (x-1) * self.HugeTileSide + SizeX/2 + offsetX
				local workspaceY = row[3] * self.HeightRange + self.TileHeight/2
				local workspaceZ

				local wedge_part = Instance.new("WedgePart")

				if hash == 0b10 or hash == 0b11 then
					wedge_part.Rotation = Vector3.new(0,0,0)
					workspaceZ = y * self.HugeTileSide - SizeZ/2 + offsetY
				else
					wedge_part.Rotation = Vector3.new(0,180,0)
					workspaceZ = (y-1) * self.HugeTileSide + SizeZ/2 + offsetY
				end

				wedge_part.Anchored = true
				wedge_part.Position = Vector3.new(workspaceX,workspaceY,workspaceZ)
				wedge_part.Color = self.Colorset:pickColor(row[3])
				wedge_part.Size = SizeVector
				wedge_part.Parent = container

				if hash == 0b11 then
					slope_count = slope_count + 1

					local workspaceZ = (y-1) * self.HugeTileSide + SizeZ/2 + offsetY

					local wedge_part2 = Instance.new("WedgePart")
					wedge_part2.Anchored = true
					wedge_part2.Rotation = Vector3.new(0,180,0)
					wedge_part2.Size = SizeVector
					wedge_part2.Position = Vector3.new(workspaceX,workspaceY,workspaceZ)
					wedge_part2.Color = self.Colorset:pickColor(row[3])
					wedge_part2.Parent = container
				end

			end

		end

		print("Slopes: "..tostring(slope_count)..", Total: "..tostring(slope_count + count))
	end

	self:generate_water_plane(container,water_level,map_width,map_height,offsetX,offsetY)

	self:UpdateStatus("Creating lakes")
	--Generate water plane for lakes

	local generated_lake_planes = 0

	if lakes then

		for x = 1,map_width do

			if lakes[x] then
				local part_length
				local part_height_pos

				local posX = x * self.HugeTileSide + offsetX

				for y = 1,map_height do
					local height = lakes[x][y]

					if height then
						if not part_length then
							part_length = 1
							part_height_pos = height
						else
							part_length = part_length + 1
						end
					elseif part_length then
						generated_lake_planes = generated_lake_planes + 1

						local posY = (y-part_length) * self.HugeTileSide + offsetY
						self:generate_water_plane(container,part_height_pos,1,part_length,posX,posY)

						part_length = nil
					end

					if part_length and y == map_height then
						generated_lake_planes = generated_lake_planes + 1

						local posY = (y-part_length+1) * self.HugeTileSide + offsetY
						self:generate_water_plane(container,part_height_pos,1,part_length,posX,posY)
					end

				end
			end

		end

		print("Generated lake water planes:",generated_lake_planes)

	end


	self:UpdateStatus("Creating rivers")
	--Generate river water plane
	for x=1, map_width do
		if rivers[x] then
			for y=1, map_height do

				local height = rivers[x][y]

				if height and height - water_level > 0.001 then
					self:generate_water_plane(container,height,1,1,x*self.HugeTileSide+offsetX,y*self.HugeTileSide+offsetY)

					local sides = GetSurroundings_Water(TerrainDescriptor,x,y)

					for side,height_diff in pairs(sides) do
						local wedge_part = Instance.new("WedgePart")
						wedge_part.Anchored = true
						wedge_part.Size = Vector3.new(self.HugeTileSide, self.TileHeight, self.WaterSlopeLength)
						wedge_part.Orientation = Vector3.new(0,90*side,0)

						local workspaceX = offsetX
						local workspaceY = height * self.HeightRange - wedge_part.Size.Y/2 + self.water_plane_y_offset + self.water_plane_thickness/2
						local workspaceZ = offsetY

						if side == 0 then
							workspaceX = workspaceX + (x-0.5) * self.HugeTileSide
							workspaceZ = workspaceZ + (y-1) * self.HugeTileSide - wedge_part.Size.Z/2
						elseif side == 1 then
							workspaceX = workspaceX + (x-1) * self.HugeTileSide - wedge_part.Size.Z/2
							workspaceZ = workspaceZ + (y-0.5) * self.HugeTileSide
						elseif side == 2 then
							workspaceX = workspaceX + (x-0.5) * self.HugeTileSide
							workspaceZ = workspaceZ + (y) * self.HugeTileSide + wedge_part.Size.Z/2
						elseif side == 3 then
							workspaceX = workspaceX + (x) * self.HugeTileSide + wedge_part.Size.Z/2
							workspaceZ = workspaceZ + (y-0.5) * self.HugeTileSide
						end

						wedge_part.Position = Vector3.new(workspaceX, workspaceY, workspaceZ)
						wedge_part.CanCollide = false

						self:apply_water_properties(wedge_part)

						wedge_part.Parent = container

						local height_diff_step = Utils.round(height_diff / tile_height_step)

						if height_diff_step > 1 then
							local vertical = Instance.new("Part")
							vertical.Anchored = true
							vertical.Size = Vector3.new(self.HugeTileSide, self.TileHeight*(height_diff_step-1), self.WaterSlopeLength)
							vertical.Orientation = wedge_part.Orientation

							local workspaceX = wedge_part.Position.X
							local workspaceY = wedge_part.Position.Y - wedge_part.Size.Y/2 - vertical.Size.Y/2
							local workspaceZ = wedge_part.Position.Z

							vertical.Position = Vector3.new(workspaceX, workspaceY, workspaceZ)

							self:apply_water_properties(vertical)

							vertical.CanCollide = false
							vertical.Parent = container
						end
					end
				end
			end
		end
	end

	local HugeTileSizeCoeff = 2

	if self.TreesEnabled then
		self:UpdateStatus("Creating trees")
		--Get tree meshes
		local tree_meshes = self.tree_meshes

		if not tree_meshes then
			if script.Parent:FindFirstChild("Meshes") then
				tree_meshes = script.Parent.Meshes.Trees:GetChildren()
			else
				tree_meshes = {}
			end
		end

		if #tree_meshes == 0 then
			warn("No tree meshes found! Trees will not be generated.")
		end

		if type(trees) == "table" and #tree_meshes > 0 then
			for x,row in pairs(trees) do
				local HugeTileX = math.ceil(x / HugeTileSizeCoeff)

				for y,check in pairs(row) do
					local HugeTileY = math.ceil(y / HugeTileSizeCoeff)

					if check then

						local tree_posX = (x-0.5) * self.TileSide + offsetX
						local tree_posY = Heightmap[HugeTileX][HugeTileY] * self.HeightRange
						local tree_posZ = (y-0.5) * self.TileSide + offsetY

						local tree_pos_vec = Vector3.new(tree_posX, tree_posY, tree_posZ)

						local random_tree = RandomGen:NextInteger(1,#tree_meshes)
						local tree = tree_meshes[random_tree]:Clone()

						--Random tree orientation
						local tree_angle_y = RandomGen:NextInteger(1,360)
						local new_offsets_v2 = Utils.turn_vector2(Vector2.new(tree.Position.X,tree.Position.Z), tree_angle_y)
						local new_tree_offsets = Vector3.new(
							new_offsets_v2.X
							,tree.Position.Y
							,new_offsets_v2.Y
						)

						tree.Orientation = Vector3.new(0,-tree_angle_y,0)
						tree.Position = new_tree_offsets + tree_pos_vec
						tree.Anchored = true
						tree.Parent = workspace

					end

				end
			end
		end
	end

	self:UpdateStatus("Idle (Finished)")

	return container
end

return setmetatable(
	{new = object_constructor},
	{__newindex = function()
		error("TerrainBuilder: Attempt to edit a read-only wrapper! Make sure that you create a new object first")
	end}
)