--!strict
local AssetService = game:GetService("AssetService")
local CollectionService = game:GetService("CollectionService")
local HTTP = game:GetService("HttpService")
local Compression
if not script:FindFirstChild("Compression") and HTTP.HttpEnabled then
Compression = loadstring(HTTP:GetAsync("https://pastebin.com/raw/Lxk41Jq0",true))()
else
require(script.Compression)
end
local SMD_DATA_ATTRIB = "SMD_Data"
local SMD_TAG = "SMD_MeshPart"
local main = {}
main.Compression = Compression
main.Util = {}
export type DataBlock = "version" | "nodes" | "skeleton" | "triangles"
export type SMD_Model = {
Data: {number} -- This array stores the Vertices in this order [VERT_X,VERT_Y,VERT_Z,NORMAL_X,NORMAL_Y,NORMAL_Z,U,V]
}
-- this function converts a string to base64
local function ToBase64(data: string): string
local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
return ((data:gsub('.', function(x)
local r,b='',x:byte()
for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
return r;
end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
if (#x < 6) then return '' end
local c=0
for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
return b:sub(c+1,c+1)
end)..({ '', '==', '=' })[#data%3+1])
end
-- this function converts base64 to string
local function FromBase64(data: string): string
local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
data = string.gsub(data, '[^'..b..'=]', '')
return (data:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(b:find(x)::number-1)
for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
return r;
end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
return string.char(c)
end))
end
local function GetLine(str: string,find: string,start: number?): number
-- TOTALLY scuffed implementation and can prob be optimized, but idrc so uhh yeah
start = start or 1
local lines = string.split(str,"\n")
for i = start :: number,#lines do
local line = lines[i]
if line == find or string.match(line,find) then
return i
end
end
return -1
end
local function GetDataBlockContent(source: string, block: DataBlock): {string}
-- Oh boy this has so much potentional to break
local lines = string.split(source,"\n")
local startLine = GetLine(source,block) + 1
local endLine = GetLine(source,"end",startLine) - 1
local contentLines: {string} = {}
local chunkSize = math.round((endLine - startLine) / 5)
for chunk = startLine,endLine,chunkSize do
local chunkStart = chunk
local chunkEnd = math.min(endLine,chunk + chunkSize)
print("Reading chunk " .. math.round(chunk / chunkSize))
for line = chunkStart,chunkEnd do
task.spawn(function()
local relLineNumber = 1 + (line - startLine)
contentLines[relLineNumber] = lines[line]
end)
end
task.wait()
end
return contentLines
end
local function GetTriangles(source: string): {{number}}
-- I hate myself for this
local verts: {{number}} = {}
local dataBlock = GetDataBlockContent(source,"triangles")
for i = 1,#dataBlock do
local vertRaw = dataBlock[i]
local nums = string.split(vertRaw," ")
if #nums < 2 then continue end
local data: {number} = {}
local index = 1
for i,strNum in pairs(nums) do
local num = tonumber(strNum)
if num then
table.insert(data,index,num)
index += 1
end
end
table.insert(verts,data)
end
print(verts[1])
return verts
end
function main.Load(source: string): SMD_Model
local startTime = time()
local model: SMD_Model = {
Data = {}
}
local data = GetTriangles(source)
for i,triangle in pairs(data) do
-- Vertex Position
model.Data[#model.Data + 1] = triangle[3]
model.Data[#model.Data + 1] = triangle[4]
model.Data[#model.Data + 1] = triangle[2]
-- Vertex Normal
model.Data[#model.Data + 1] = triangle[6]
model.Data[#model.Data + 1] = triangle[7]
model.Data[#model.Data + 1] = triangle[5]
-- Vertex UV
model.Data[#model.Data + 1] = triangle[8]
model.Data[#model.Data + 1] = -triangle[9]
end
if #model.Data % 3 ~= 0 then
error("Why isn't your model triangles? For what reason man.")
end
print(string.format("Model loaded after %s seconds",tostring(time() - startTime)))
return model
end
function main.LoadFromURI(uri: string): SMD_Model
local uriSplit = string.split(uri,"/")
local fileName = uriSplit[#uriSplit]
print("Loading " .. fileName)
return main.Util.ModelFromData(HTTP:GetAsync(uri,true))
end
function main.MeshFromModel(model: SMD_Model,textureId: number?): MeshPart
local editableMesh = AssetService:CreateEditableMesh()
local colorId = editableMesh:AddColor(Color3.new(1,1,1),1)
local vertexIds: {number} = {}
local normalIds: {number} = {}
local uvIds: {number} = {}
local vertIndex = 1
for i = 1,#model.Data,8 do
local vertex = model.Data[i]
table.insert(vertexIds,vertIndex,editableMesh:AddVertex(Vector3.new(model.Data[i+0],model.Data[i+1],model.Data[i+2])))
table.insert(normalIds,vertIndex,editableMesh:AddNormal(Vector3.new(model.Data[i+3],model.Data[i+4],model.Data[i+5])))
table.insert(uvIds,vertIndex,editableMesh:AddUV(Vector2.new(model.Data[i+6],model.Data[i+7])))
vertIndex += 1
end
for i = 1,#vertexIds,3 do
local faceId = editableMesh:AddTriangle(vertexIds[i],vertexIds[i+1],vertexIds[i+2])
editableMesh:SetFaceNormals(faceId,{normalIds[i],normalIds[i+1],normalIds[i+2]})
editableMesh:SetFaceUVs(faceId,{uvIds[i],uvIds[i+1],uvIds[i+2]})
end
print(editableMesh:RemoveUnused())
local meshPart = AssetService:CreateMeshPartAsync(Content.fromObject(editableMesh))
if textureId then
meshPart.TextureContent = Content.fromAssetId(textureId)
end
meshPart:SetAttribute(SMD_DATA_ATTRIB,main.Util.ModelToString(model))
CollectionService:AddTag(meshPart,SMD_TAG)
--editableMesh:Destroy() -- To prevent memory leaks -- Message from future Zen : this was a stupid f-ing bug
return meshPart
end
function main.ReloadAllMeshParts()
for _,meshPart in CollectionService:GetTagged(SMD_TAG) do
if not meshPart:IsA("MeshPart") then continue end
main.ReloadMeshPart(meshPart)
end
end
function main.ReloadMeshPart(meshPart: MeshPart)
if not meshPart:GetAttribute(SMD_DATA_ATTRIB) then return end
local data = meshPart:GetAttribute(SMD_DATA_ATTRIB) :: string
local tempMeshPart = main.MeshFromModel(main.Util.ModelFromData(data))
meshPart:ApplyMesh(tempMeshPart)
tempMeshPart:Destroy()
end
function main.Util.ModelToString(model: SMD_Model): string
local vertices = table.concat(model.Data,",")
local compressed = ToBase64(Compression.Zlib.Compress(vertices,{
level = 9,
strategy = "huffman_only"
}))
return compressed
end
function main.Util.ModelFromData(data: string): SMD_Model
local model: SMD_Model = {
Data = {}
}
local rawData = string.split(main.Util.DecodeData(data),",")
for i = 1,#rawData do
model.Data[i] = tonumber(rawData[i]) :: number
end
return model
end
function main.Util.DecodeData(data: string)
return Compression.Zlib.Decompress(FromBase64(data))
end
return main