360 lines
12 KiB
Lua
360 lines
12 KiB
Lua
-- Package v1.1.7
|
|
-- See http://wiki.roblox.com/index.php?title=R15_Compatibility_Guide#Package_Parts for details on how body parts work with R15
|
|
|
|
local assetUrls, baseUrl, fileExtension, x, y, R6RigUrl, customTextureUrls = ...
|
|
|
|
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
|
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
|
|
|
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
|
game:GetService("ScriptContext").ScriptsDisabled = true
|
|
|
|
local function split(str, delim)
|
|
local results = {}
|
|
local lastMatchEnd = 0
|
|
|
|
local matchStart, matchEnd = string.find(str, delim, --[[init = ]] 1, --[[plain = ]] true)
|
|
while matchStart and matchEnd do
|
|
if matchStart - lastMatchEnd > 1 then
|
|
table.insert(results, string.sub(str, lastMatchEnd + 1, matchStart - 1))
|
|
end
|
|
|
|
lastMatchEnd = matchEnd
|
|
matchStart, matchEnd = string.find(str, delim, --[[init = ]] lastMatchEnd + 1, --[[plain = ]] true)
|
|
end
|
|
|
|
if string.len(str) - lastMatchEnd > 1 then
|
|
table.insert(results, string.sub(str, lastMatchEnd + 1))
|
|
end
|
|
return results
|
|
end
|
|
|
|
local R15ArtistIntentAssets = {}
|
|
local R15Assets = {}
|
|
local R6Assets = {}
|
|
local bothAssets = {}
|
|
|
|
local useR15ArtistIntent = false
|
|
local useR15 = true
|
|
local poseAnimationId = nil
|
|
|
|
local poseValueFound = false
|
|
local function processR15Anim(animFolder)
|
|
local function processStrValue(strValue)
|
|
local animation = strValue:FindFirstChildOfClass("Animation")
|
|
if not animation then
|
|
return
|
|
end
|
|
|
|
-- By default the pose animation will be used for the thumbnail
|
|
-- If the pose animation doesn't exist then the idle will be used, otherwise the first animation will be used.
|
|
if string.lower(strValue.Name) == "pose" then
|
|
poseValueFound = true
|
|
poseAnimationId = animation.AnimationId
|
|
elseif not poseValueFound and string.lower(strValue.Name) == "idle" then
|
|
poseAnimationId = animation.AnimationId
|
|
elseif not poseAnimationId then
|
|
poseAnimationId = animation.AnimationId
|
|
end
|
|
end
|
|
|
|
|
|
for _, obj in pairs(animFolder:GetChildren()) do
|
|
if obj:IsA("StringValue") then
|
|
processStrValue(obj)
|
|
end
|
|
end
|
|
end
|
|
|
|
local assetUrlsList = split(assetUrls, ";")
|
|
|
|
for _, assetUrl in pairs(assetUrlsList) do
|
|
local currObjects = game:GetObjects(assetUrl)
|
|
|
|
for _, object in pairs(currObjects) do
|
|
if object:IsA("Folder") and object.Name == "R15ArtistIntent" then
|
|
for _, child in pairs(object:GetChildren()) do
|
|
table.insert(R15ArtistIntentAssets, child)
|
|
end
|
|
elseif object:IsA("Folder") and object.Name == "R15Fixed" then -- luacheck: ignore
|
|
-- Do nothing. We just don't want this to be dumped in bothAssets
|
|
elseif object:IsA("Folder") and object.Name == "R15" then
|
|
for _, child in pairs(object:GetChildren()) do
|
|
table.insert(R15Assets, child)
|
|
end
|
|
elseif object:IsA("Folder") and object.Name == "R6" then
|
|
for _, child in pairs(object:GetChildren()) do
|
|
table.insert(R6Assets, child)
|
|
end
|
|
elseif object:IsA("CharacterMesh") then
|
|
-- Legacy body part format using a CharacterMesh
|
|
table.insert(R6Assets, object)
|
|
elseif object:IsA("Folder") and object.Name == "R15Anim" then
|
|
processR15Anim(object)
|
|
else
|
|
table.insert(bothAssets, object)
|
|
end
|
|
end
|
|
end
|
|
|
|
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
|
|
|
|
-- if the package doesn't contain animations, use this pose.
|
|
poseAnimationId = poseAnimationId or "http://www.roblox.com/asset/?id=532421348"
|
|
|
|
-- Only use R6 if we found body parts that are only compatible with R15
|
|
if #R6Assets ~= 0 and #R15Assets == 0 and #R15ArtistIntentAssets == 0 then
|
|
useR15 = false
|
|
end
|
|
|
|
local R15RigUrl = "http://www.roblox.com/asset/?id=516159357"
|
|
if useR15 and #R15ArtistIntentAssets > 0 then
|
|
useR15ArtistIntent = true
|
|
R15RigUrl = "http://www.roblox.com/asset/?id=1664543044"
|
|
end
|
|
|
|
local mannequin
|
|
if useR15 then
|
|
mannequin = game:GetObjects(R15RigUrl)[1]
|
|
if (useR15ArtistIntent) then
|
|
for _, obj in pairs(R15ArtistIntentAssets) do
|
|
table.insert(bothAssets, obj)
|
|
end
|
|
else
|
|
for _,obj in pairs(R15Assets) do
|
|
table.insert(bothAssets, obj)
|
|
end
|
|
end
|
|
else
|
|
mannequin = game:GetObjects(R6RigUrl)[1]
|
|
for _,obj in pairs(R6Assets) do
|
|
table.insert(bothAssets, obj)
|
|
end
|
|
end
|
|
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
|
mannequin.Parent = workspace
|
|
|
|
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
|
|
|
|
local tool = nil
|
|
local accoutrements = {}
|
|
|
|
for _, currObject in pairs(bothAssets) do
|
|
if currObject:IsA("BasePart") then
|
|
local existingBodyPart = mannequin:FindFirstChild(currObject.Name)
|
|
if existingBodyPart ~= nil then
|
|
existingBodyPart:Destroy()
|
|
end
|
|
end
|
|
|
|
if currObject:IsA("Tool") then
|
|
if useR15 then
|
|
tool = currObject
|
|
else
|
|
mannequin.Torso["Right Shoulder"].CurrentAngle = math.rad(90)
|
|
currObject.Parent = mannequin
|
|
end
|
|
elseif currObject:IsA("DataModelMesh") then
|
|
local headMesh = mannequin.Head:FindFirstChild("Mesh")
|
|
if headMesh then
|
|
headMesh:Destroy()
|
|
end
|
|
currObject.Parent = mannequin.Head
|
|
elseif currObject:IsA("Decal") then
|
|
local face = mannequin.Head:FindFirstChild("face")
|
|
if face then
|
|
face:Destroy()
|
|
end
|
|
currObject.Parent = mannequin.Head
|
|
elseif currObject:IsA("Accoutrement") then
|
|
table.insert(accoutrements, currObject)
|
|
else
|
|
currObject.Parent = mannequin
|
|
end
|
|
end
|
|
|
|
local textureUrls = split(customTextureUrls, ";")
|
|
for _, url in pairs(textureUrls) do
|
|
local obj = game:GetObjects(url)[1]
|
|
if obj:IsA("Shirt") then
|
|
-- Don't add a texture Shirt if package already has a Shirt
|
|
if not mannequin:FindFirstChildOfClass("Shirt") then
|
|
obj.Parent = mannequin
|
|
end
|
|
elseif obj:IsA("Pants") then
|
|
-- Don't add a texture Pants if package already has a Pants
|
|
if not mannequin:FindFirstChildOfClass("Pants") then
|
|
obj.Parent = mannequin
|
|
end
|
|
else
|
|
obj.Parent = mannequin
|
|
end
|
|
end
|
|
|
|
ThumbnailGenerator:AddProfilingCheckpoint("CustomUrlsLoaded")
|
|
|
|
local function buildJoint(parentAttachment, partForJointAttachment)
|
|
local jointName = parentAttachment.Name:gsub("RigAttachment", "")
|
|
local motor = partForJointAttachment.Parent:FindFirstChild(jointName)
|
|
if not motor then
|
|
motor = Instance.new("Motor6D")
|
|
end
|
|
motor.Name = jointName
|
|
|
|
motor.Part0 = parentAttachment.Parent
|
|
motor.Part1 = partForJointAttachment.Parent
|
|
|
|
motor.C0 = parentAttachment.CFrame
|
|
motor.C1 = partForJointAttachment.CFrame
|
|
|
|
motor.Parent = partForJointAttachment.Parent
|
|
end
|
|
|
|
-- Builds an R15 rig from the attachments in the parts
|
|
local function buildRigFromAttachments(currentPart, lastPart)
|
|
local validSiblings = {}
|
|
for _, sibling in pairs(currentPart.Parent:GetChildren()) do
|
|
-- Don't find matching attachment in the current part being processed.
|
|
-- Don't visit the last part visited again, this would cause an infinite loop.
|
|
if sibling:IsA("BasePart") and sibling ~= currentPart and sibling ~= lastPart then
|
|
table.insert(validSiblings, sibling)
|
|
end
|
|
end
|
|
|
|
local function processRigAttachment(attachment)
|
|
for _, sibling in pairs(validSiblings) do
|
|
local matchingAttachment = sibling:FindFirstChild(attachment.Name)
|
|
if matchingAttachment then
|
|
buildJoint(attachment, matchingAttachment)
|
|
buildRigFromAttachments(matchingAttachment.Parent, currentPart)
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, object in pairs(currentPart:GetChildren()) do
|
|
if object:IsA("Attachment") and string.find(object.Name, "RigAttachment") then
|
|
processRigAttachment(object)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function getJointBetween(part0, part1)
|
|
for _, obj in pairs(part1:GetChildren()) do
|
|
if obj:IsA("Motor6D") and obj.Part0 == part0 then
|
|
return obj
|
|
end
|
|
end
|
|
end
|
|
|
|
local function applyR15ToolPose(rig)
|
|
local upperTorso = rig:FindFirstChild("UpperTorso")
|
|
local rightUpperArm = rig:FindFirstChild("RightUpperArm")
|
|
if upperTorso and rightUpperArm then
|
|
local rightShoulderJoint = getJointBetween(upperTorso, rightUpperArm)
|
|
if rightShoulderJoint then
|
|
rightShoulderJoint.C1 = rightShoulderJoint.C1 * CFrame.new(0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 1, 0):inverse()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Applies the middle keyframe of a pose to a given character.
|
|
local function applyPoseToCharacter(character, poseAnimId)
|
|
local poseKeyframSequence = game:GetService("KeyframeSequenceProvider"):GetKeyframeSequence(poseAnimId)
|
|
local keyframes = poseKeyframSequence:GetKeyframes()
|
|
local poseKeyframe = keyframes[math.max(1, math.floor(#keyframes/2))]
|
|
|
|
local function recurApplyPoses(parentPose, poseObject)
|
|
if parentPose then
|
|
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
|
|
if joint then
|
|
joint.C1 = joint.C1 * poseObject.CFrame:inverse()
|
|
end
|
|
end
|
|
|
|
for _, subPose in pairs(poseObject:GetSubPoses()) do
|
|
recurApplyPoses(poseObject, subPose)
|
|
end
|
|
end
|
|
|
|
for _, poseObj in pairs(poseKeyframe:GetPoses()) do
|
|
recurApplyPoses(nil, poseObj)
|
|
end
|
|
end
|
|
|
|
if useR15 then
|
|
-- Build R15 rig
|
|
local humanoidRootPart = mannequin:WaitForChild("HumanoidRootPart")
|
|
humanoidRootPart.CFrame = CFrame.new(Vector3.new(0, 5, 0)) * CFrame.Angles(0, math.pi, 0)
|
|
humanoidRootPart.Anchored = true
|
|
buildRigFromAttachments(humanoidRootPart)
|
|
|
|
if tool then
|
|
applyR15ToolPose(mannequin)
|
|
|
|
local hand = mannequin:FindFirstChild("RightHand")
|
|
local handle = tool:FindFirstChild("Handle")
|
|
if hand and handle then
|
|
local handGrip = hand:FindFirstChild("RightGripAttachment")
|
|
if handGrip then
|
|
handle.CFrame = hand.CFrame * handGrip.CFrame * tool.Grip:inverse()
|
|
end
|
|
end
|
|
tool.Parent = mannequin
|
|
elseif poseAnimationId then
|
|
applyPoseToCharacter(mannequin, poseAnimationId)
|
|
end
|
|
end
|
|
|
|
local function findFirstMatchingAttachment(model, name)
|
|
for _, child in pairs(model:GetChildren()) do
|
|
if child:IsA("Attachment") and child.Name == name then
|
|
return child
|
|
elseif not child:IsA("Accoutrement") and not child:IsA("Tool") then
|
|
local foundAttachment = findFirstMatchingAttachment(child, name)
|
|
if foundAttachment then
|
|
return foundAttachment
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, accoutrement in pairs(accoutrements) do
|
|
local handle = accoutrement:FindFirstChild("Handle")
|
|
if handle then
|
|
local accoutrementAttachment = handle:FindFirstChildOfClass("Attachment")
|
|
local characterAttachment = nil
|
|
if accoutrementAttachment then
|
|
characterAttachment = findFirstMatchingAttachment(mannequin, accoutrementAttachment.Name)
|
|
end
|
|
|
|
local attachmentPart
|
|
if characterAttachment then
|
|
attachmentPart = characterAttachment.Parent
|
|
else
|
|
attachmentPart = mannequin:FindFirstChild("Head")
|
|
end
|
|
|
|
local attachmentCFrame
|
|
if characterAttachment then
|
|
attachmentCFrame = characterAttachment.CFrame
|
|
else
|
|
attachmentCFrame = CFrame.new(0, 0.5, 0)
|
|
end
|
|
|
|
local hatCFrame
|
|
if accoutrementAttachment then
|
|
hatCFrame = accoutrementAttachment.CFrame
|
|
else
|
|
hatCFrame = accoutrement.AttachmentPoint
|
|
end
|
|
|
|
handle.CFrame = attachmentPart.CFrame * attachmentCFrame * hatCFrame:inverse()
|
|
handle.Anchored = true
|
|
handle.Parent = mannequin
|
|
end
|
|
end
|
|
|
|
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
|
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
|
|
|
return result, requestedUrls |