While using EditableMesh I created a sphere of size 10,000, you may notice this already that this surpasses the size limit of 2048. I did this by creating many (100) meshparts with an EditableMesh attached to each to then line up to form its surface.
As a serverscript, when ran (F8) it works perfect, however, as a server script when you actually test/play it, it has a strange issue where all meshparts are created with their EditableMesh’s with no errors printed but you can’t see any of the mesh.
As a local script it just reaches the memory budget limit of 8 EditableMesh’s.
local AssetService = game:GetService("AssetService")
local POSITION = Vector3.new(0, 0, 0)
local RADIUS = 5250
local SUBDIVISIONS = 7.5
local HEIGHT_INTENSITY = 500
local TEX_SIZE = 1024
local NUM_REGIONS = 250
math.randomseed(os.time() ^ 2)
local NOISE_SEED = Vector3.new(math.random(-1e5, 1e5), math.random(-1e5, 1e5), math.random(-1e5, 1e5))
local PI = math.pi
local PI_2 = math.pi * 2
local function getFibonacciSpherePoints(samples)
local points = {}
local phi = math.pi * (3 - math.sqrt(5))
for i = 0, samples - 1 do
local y = 1 - (i / (samples - 1)) * 2
local radius = math.sqrt(1 - y * y)
local theta = phi * i
table.insert(points, Vector3.new(math.cos(theta) * radius, y, math.sin(theta) * radius))
end
return points
end
local function generateBadlandsPalette()
return {
Color3.fromRGB(89, 52, 39),
Color3.fromRGB(161, 85, 59),
Color3.fromRGB(209, 178, 129),
Color3.fromRGB(124, 126, 110),
Color3.fromRGB(143, 89, 102),
Color3.fromRGB(230, 214, 186),
Color3.fromRGB(74, 65, 62),
Color3.fromRGB(189, 135, 85),
}
end
local function fbm(p, scale, octaves)
local total, freq, amp, maxVal = 0, scale, 1, 0
for i = 1, octaves do
total += (math.noise(p.X * freq + NOISE_SEED.X, p.Y * freq + NOISE_SEED.Y, p.Z * freq + NOISE_SEED.Z) + 1) * 0.5 * amp
maxVal += amp
amp *= 0.5
freq *= 2
end
return total / maxVal
end
local function ridgedNoise(p, scale, octaves)
local total, freq, amp, maxVal = 0, scale, 1, 0
for i = 1, octaves do
local n = math.noise(p.X * freq + NOISE_SEED.X, p.Y * freq + NOISE_SEED.Y, p.Z * freq + NOISE_SEED.Z)
n = 1 - math.abs(n)
total += n * n * amp
maxVal += amp
amp *= 0.5
freq *= 2
end
return total / maxVal
end
local function terraced(v, bands)
return (math.floor(v * bands) + math.pow(math.fmod(v * bands, 1), 3)) / bands
end
local function getCombinedHeight(u)
local base = fbm(u, 2.0, 8)
local ridges = ridgedNoise(u, 3, 8)
local mesaBaseNoise = fbm(u, 1.5, 7)
local mesas = terraced(mesaBaseNoise, 12)
local grit = fbm(u, 25, 4) * 0.03
local finalHeight = math.max(mesas * 0.6, ridges * base)
return finalHeight + grit
end
local function bakeECHOTexture(palette)
local buf = buffer.create(TEX_SIZE * TEX_SIZE * 4)
local numColors = #palette
for y = 0, TEX_SIZE - 1 do
local lat = (0.5 - (y / (TEX_SIZE - 1))) * PI
local sinLat, cosLat = math.sin(lat), math.cos(lat)
for x = 0, TEX_SIZE - 1 do
local lon = ((x / (TEX_SIZE - 1)) - 0.5) * PI_2
local u = Vector3.new(math.cos(lon) * cosLat, sinLat, math.sin(lon) * cosLat)
local h = getCombinedHeight(u)
local stripeNoise = math.noise(u.X * 5, u.Y * 2, u.Z * 5) * 0.05
local stripeValue = (h + stripeNoise) * 25
local index = math.floor(stripeValue % numColors) + 1
local nextIndex = (index % numColors) + 1
local fraction = stripeValue % 1
fraction = math.pow(fraction, 0.5)
local color = palette[index]:Lerp(palette[nextIndex], fraction)
local grain = (math.noise(u.X*200, u.Y*200, u.Z*200) * 0.1) + 0.9
if h < 0.1 then color = color:Lerp(Color3.new(0,0,0), 0.2) end
local offset = (y * TEX_SIZE + x) * 4
buffer.writeu8(buf, offset, math.clamp(color.R * 255 * grain, 0, 255))
buffer.writeu8(buf, offset + 1, math.clamp(color.G * 255 * grain, 0, 255))
buffer.writeu8(buf, offset + 2, math.clamp(color.B * 255 * grain, 0, 255))
buffer.writeu8(buf, offset + 3, 255)
end
if y % 64 == 0 then task.wait() end
end
local editImage = AssetService:CreateEditableImage({Size = Vector2.new(TEX_SIZE, TEX_SIZE)})
editImage:WritePixelsBuffer(Vector2.zero, Vector2.new(TEX_SIZE, TEX_SIZE), buf)
return Content.fromObject(editImage)
end
local function generateMeshes(textureContent)
local TAU = (1 + math.sqrt(5)) / 2
local rawVerts = {
Vector3.new(-1,TAU,0).Unit, Vector3.new(1,TAU,0).Unit, Vector3.new(-1,-TAU,0).Unit, Vector3.new(1,-TAU,0).Unit,
Vector3.new(0,-1,TAU).Unit, Vector3.new(0,1,TAU).Unit, Vector3.new(0,-1,-TAU).Unit, Vector3.new(0,1,-TAU).Unit,
Vector3.new(TAU,0,-1).Unit, Vector3.new(TAU,0,1).Unit, Vector3.new(-TAU,0,-1).Unit, Vector3.new(-TAU,0,1).Unit
}
local faces = {
{1,12,6},{1,6,2},{1,2,8},{1,8,11},{1,11,12},{2,6,10},{6,12,5},{12,11,3},
{11,8,7},{8,2,9},{4,10,5},{4,5,3},{4,3,7},{4,7,9},{4,9,10},{5,10,6},
{3,5,12},{7,3,11},{9,7,8},{10,9,2}
}
local midpoints = {}
local function getMid(v1, v2)
local minI, maxI = math.min(v1, v2), math.max(v1, v2)
local key = bit32.bor(bit32.lshift(minI, 16), maxI)
if midpoints[key] then return midpoints[key] end
local idx = #rawVerts + 1
rawVerts[idx] = (rawVerts[minI] + rawVerts[maxI]).Unit
midpoints[key] = idx
return idx
end
for _ = 1, SUBDIVISIONS do
local newFaces = {}
for _, f in ipairs(faces) do
local ab, bc, ca = getMid(f[1], f[2]), getMid(f[2], f[3]), getMid(f[3], f[1])
table.insert(newFaces, {f[1], ab, ca})
table.insert(newFaces, {f[2], bc, ab})
table.insert(newFaces, {f[3], ca, bc})
table.insert(newFaces, {ab, bc, ca})
end
faces = newFaces
end
local regionCenters = getFibonacciSpherePoints(NUM_REGIONS)
local regionFaces = table.create(NUM_REGIONS)
for i = 1, NUM_REGIONS do regionFaces[i] = {} end
for _, f in ipairs(faces) do
local centroidUnit = ((rawVerts[f[1]] + rawVerts[f[2]] + rawVerts[f[3]]) / 3).Unit
local bestDist, bestRegion = math.huge, 1
for i, center in ipairs(regionCenters) do
local dist = (centroidUnit - center).Magnitude
if dist < bestDist then bestDist, bestRegion = dist, i end
end
table.insert(regionFaces[bestRegion], f)
end
local planetModel = Instance.new("Model", workspace)
planetModel.Name = "Badlands_Planet"
for regionId = 1, NUM_REGIONS do
local em = AssetService:CreateEditableMesh()
local vertexMap = {}
for i, f in ipairs(regionFaces[regionId]) do
local localFaceIds, uvs = {}, {}
for j = 1, 3 do
local vIdx = f[j]
local rawPos = rawVerts[vIdx]
local uv = Vector2.new(0.5 + math.atan2(rawPos.Z, rawPos.X) / PI_2, 0.5 - math.asin(rawPos.Y) / PI)
uvs[j] = uv
if not vertexMap[vIdx] then
local h = getCombinedHeight(rawPos)
local displacement = (h - 0.2) * HEIGHT_INTENSITY
local r = RADIUS + displacement
vertexMap[vIdx] = em:AddVertex(rawPos * r)
end
localFaceIds[j] = vertexMap[vIdx]
end
if math.abs(uvs[1].X - uvs[2].X) > 0.5 or math.abs(uvs[1].X - uvs[3].X) > 0.5 then
for j = 1, 3 do if uvs[j].X < 0.5 then uvs[j] += Vector2.new(1, 0) end end
end
local faceId = em:AddTriangle(localFaceIds[1], localFaceIds[2], localFaceIds[3])
em:SetFaceUVs(faceId, {em:AddUV(uvs[1]), em:AddUV(uvs[2]), em:AddUV(uvs[3])})
if i % 1000 == 0 then task.wait() end
end
local success, mp = pcall(function() return AssetService:CreateMeshPartAsync(Content.fromObject(em)) end)
if success and mp then
mp.Name = "Region_" .. regionId
mp.Position = POSITION
mp.Anchored = true
mp.Material = Enum.Material.Basalt
mp.TextureContent = textureContent
mp.Parent = planetModel
end
task.wait()
end
end
task.spawn(function()
local palette = generateBadlandsPalette()
local tex = bakeECHOTexture(palette)
generateMeshes(tex)
end)