add gs
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
-- AnimationManifest.txt
|
||||
-- V 1.0.3
|
||||
|
||||
-- Exports multiple frames
|
||||
-- https://spocktopus.roblox.local/Internals_of_3D_Animated_Thumbnails
|
||||
|
||||
local baseUrl, characterAppearanceUrl, animationUrl = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
local BundleLoader = require(ThumbnailGenerator:GetThumbnailModule("BundleLoader"))
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local FRAME_RATE = 60
|
||||
local originalJointCFramesMap = {}
|
||||
local originalPartsCFramesMap = {}
|
||||
|
||||
local function getOriginalJointCFrames(object)
|
||||
for _, child in pairs(object:GetChildren()) do
|
||||
if child:IsA("Motor6D") then
|
||||
originalJointCFramesMap[child] = child.C1
|
||||
end
|
||||
if child:IsA("BasePart") then
|
||||
originalPartsCFramesMap[child] = child.CFrame
|
||||
end
|
||||
getOriginalJointCFrames(child)
|
||||
end
|
||||
end
|
||||
|
||||
local animationObjects = game:GetObjects(animationUrl)
|
||||
local animations = {}
|
||||
local rotateCharacter = false
|
||||
|
||||
local function getAnimations(model)
|
||||
for _, child in pairs(model:GetChildren()) do
|
||||
if child:IsA("StringValue") and child.Name == "swim" then
|
||||
rotateCharacter = true
|
||||
elseif child:IsA("StringValue") and child.Name == "swimidle" then
|
||||
-- Don't include swimidle if we are thumnailing the swim animation
|
||||
if child.Parent and child.Parent:FindFirstChild("swim") then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if child:IsA("Animation") then
|
||||
table.insert(animations, child)
|
||||
else
|
||||
getAnimations(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, animationModel in pairs(animationObjects) do
|
||||
if animationModel:IsA("Animation") then
|
||||
table.insert(animations, animationModel)
|
||||
else
|
||||
getAnimations(animationModel)
|
||||
end
|
||||
end
|
||||
|
||||
local bundleId
|
||||
|
||||
if #animations == 1 then
|
||||
local anim = animations[1]
|
||||
local bundleIdValue = anim:FindFirstChild("ThumbnailBundleId")
|
||||
if bundleIdValue and bundleIdValue:IsA("NumberValue") then
|
||||
bundleId = bundleIdValue.Value
|
||||
end
|
||||
end
|
||||
|
||||
local character
|
||||
|
||||
if bundleId then
|
||||
-- Thumbnail with the loaded bundle
|
||||
character = BundleLoader.LoadBundleCharacter(baseUrl, bundleId)
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("BundleCharacterLoaded")
|
||||
else
|
||||
-- Use characterAppearanceUrl
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
character = player.Character
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
|
||||
end
|
||||
|
||||
local humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
getOriginalJointCFrames(character)
|
||||
|
||||
local animator = humanoid:FindFirstChild("Animator")
|
||||
if not animator then
|
||||
animator = Instance.new("Animator")
|
||||
animator.Parent = humanoid
|
||||
end
|
||||
|
||||
if rotateCharacter then
|
||||
local rootPart = character:FindFirstChild("HumanoidRootPart")
|
||||
if rootPart then
|
||||
rootPart.CFrame = rootPart.CFrame * CFrame.Angles(math.rad(-90), 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local animationId = ""
|
||||
local bestWeight = -1
|
||||
for _, anim in pairs(animations) do
|
||||
local weight = anim:FindFirstChild("Weight")
|
||||
if weight then
|
||||
if weight.Value > bestWeight then
|
||||
bestWeight = weight.Value
|
||||
animationId = anim.AnimationId
|
||||
end
|
||||
else
|
||||
animationId = anim.AnimationId
|
||||
end
|
||||
end
|
||||
|
||||
local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider")
|
||||
local keyframeSequence = KeyframeSequenceProvider:GetKeyframeSequence(animationId)
|
||||
|
||||
local animationObj = Instance.new("Animation")
|
||||
animationObj.AnimationId = KeyframeSequenceProvider:RegisterActiveKeyframeSequence(keyframeSequence)
|
||||
|
||||
for _, track in pairs(humanoid:GetPlayingAnimationTracks()) do
|
||||
track:Stop(0)
|
||||
end
|
||||
|
||||
local track = humanoid:LoadAnimation(animationObj)
|
||||
track:Play(0)
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("AnimationsLoaded")
|
||||
|
||||
local finished = false
|
||||
|
||||
local function stepNextFrame()
|
||||
local delta = 1/FRAME_RATE
|
||||
|
||||
if track.TimePosition + delta > track.Length then
|
||||
delta = track.Length - track.TimePosition
|
||||
end
|
||||
|
||||
if delta <= 0.005 or track.TimePosition >= track.Length or finished then
|
||||
return false
|
||||
end
|
||||
|
||||
local preStepTimePosition = track.TimePosition
|
||||
animator:StepAnimations(delta)
|
||||
|
||||
if track.TimePosition <= preStepTimePosition then
|
||||
finished = true
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local math_floor = math.floor
|
||||
local math_sin = math.sin
|
||||
local math_cos = math.cos
|
||||
local math_min = math.min
|
||||
local math_max = math.max
|
||||
local function round(number)
|
||||
return math_floor(number*10000 + 0.5)/10000
|
||||
end
|
||||
|
||||
local function vector3ToTable(vector)
|
||||
return {
|
||||
x = round(vector.X),
|
||||
y = round(vector.Y),
|
||||
z = round(vector.Z)
|
||||
}
|
||||
end
|
||||
|
||||
local animationData = {}
|
||||
|
||||
while stepNextFrame() do
|
||||
local frameAnimationData = {}
|
||||
|
||||
for _, obj in pairs(character:GetChildren()) do
|
||||
if obj:IsA("BasePart") or obj:IsA("Accoutrement") then
|
||||
local part = obj
|
||||
if obj:IsA("Accoutrement") then
|
||||
part = obj:FindFirstChild("Handle")
|
||||
end
|
||||
if part and part.Name ~= "HumanoidRootPart" then
|
||||
local posAndRotation = {}
|
||||
posAndRotation["Position"] = vector3ToTable(part.Position)
|
||||
local axis, angle = part.CFrame:toAxisAngle()
|
||||
local halfAngle = angle/2
|
||||
posAndRotation["Rotation"] = {
|
||||
x = round(math_sin(halfAngle)*axis.X),
|
||||
y = round(math_sin(halfAngle)*axis.Y),
|
||||
z = round(math_sin(halfAngle)*axis.Z),
|
||||
w = round(math_cos(halfAngle))
|
||||
}
|
||||
|
||||
frameAnimationData[obj.Name] = posAndRotation
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(animationData, frameAnimationData)
|
||||
end
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("AnimationDataCollected")
|
||||
|
||||
-- Take note of finishing CFrames for the animation
|
||||
-- The CameraResult position will be transformed based on this
|
||||
local animatedPartPositionsMap = {}
|
||||
|
||||
for part, _ in pairs(originalPartsCFramesMap) do
|
||||
animatedPartPositionsMap[part] = part.Position
|
||||
end
|
||||
|
||||
-- Restore original CFrames
|
||||
for motor, origC1 in pairs(originalJointCFramesMap) do
|
||||
motor.C1 = origC1
|
||||
end
|
||||
|
||||
for part, cframe in pairs(originalPartsCFramesMap) do
|
||||
part.Anchored = true
|
||||
part.CFrame = cframe
|
||||
end
|
||||
|
||||
local partsArray = {}
|
||||
for _, obj in pairs(character:GetChildren()) do
|
||||
if obj:IsA("BasePart") or obj:IsA("Accoutrement") then
|
||||
if obj.Name ~= "HumanoidRootPart" then
|
||||
table.insert(partsArray, obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local string_sub = string.sub
|
||||
local string_len = string.len
|
||||
local function strEndsWith(str, val)
|
||||
return string_sub(str, -string_len(val)) == val
|
||||
end
|
||||
|
||||
local function replaceChar(pos, str, r)
|
||||
return str:sub(1, pos - 1) ..r.. str:sub(pos + 1)
|
||||
end
|
||||
|
||||
game:GetService("Selection"):Set(partsArray)
|
||||
local objsStrOutput, requestedUrls = ThumbnailGenerator:Click("SplitObjs", 0, 0, true)
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ObjFilesGenerated")
|
||||
|
||||
local decodedObjsStrOutput = game:GetService("HttpService"):JSONDecode(objsStrOutput)
|
||||
local partObjsResult = {}
|
||||
local textures = {}
|
||||
local cameraResult = nil
|
||||
|
||||
local totalAABB = {
|
||||
["min"] = {},
|
||||
["max"] = {}
|
||||
}
|
||||
|
||||
local function addToTotalAABB(partName, partAABB)
|
||||
local part = character:FindFirstChild(partName)
|
||||
if part and animatedPartPositionsMap[part] then
|
||||
local currentPosition = part.Position
|
||||
local animatedPosition = animatedPartPositionsMap[part]
|
||||
local offset = animatedPosition - currentPosition
|
||||
|
||||
local minJSON = partAABB["min"]
|
||||
local currentMin = Vector3.new(minJSON.x, minJSON.y, minJSON.z)
|
||||
currentMin = Vector3.new(currentMin.X, currentMin.Y + offset.Y, currentMin.Z)
|
||||
partAABB["min"] = vector3ToTable(currentMin)
|
||||
|
||||
local maxJSON = partAABB["min"]
|
||||
local currentMax = Vector3.new(maxJSON.x, maxJSON.y, maxJSON.z)
|
||||
currentMax = Vector3.new(currentMax.X, currentMax.Y + offset.Y, currentMax.Z)
|
||||
partAABB["min"] = vector3ToTable(currentMax)
|
||||
end
|
||||
|
||||
for xyzkey, val in pairs(partAABB["min"]) do
|
||||
if not totalAABB["min"][xyzkey] then
|
||||
totalAABB["min"][xyzkey] = val
|
||||
else
|
||||
totalAABB["min"][xyzkey] = math_min(totalAABB["min"][xyzkey], val)
|
||||
end
|
||||
end
|
||||
|
||||
for xyzkey, val in pairs(partAABB["max"]) do
|
||||
if not totalAABB["max"][xyzkey] then
|
||||
totalAABB["max"][xyzkey] = val
|
||||
else
|
||||
totalAABB["max"][xyzkey] = math_max(totalAABB["max"][xyzkey], val)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function resolveCameraResult(partName, cameraJSON)
|
||||
local part = character:FindFirstChild(partName)
|
||||
if part and animatedPartPositionsMap[part] then
|
||||
local currentPosition = part.Position
|
||||
local animatedPosition = animatedPartPositionsMap[part]
|
||||
local offset = animatedPosition - currentPosition
|
||||
|
||||
local positionJSON = cameraJSON["position"]
|
||||
local cameraPosition = Vector3.new(positionJSON.x, positionJSON.y, positionJSON.z)
|
||||
cameraPosition = cameraPosition + offset
|
||||
cameraJSON["position"] = vector3ToTable(cameraPosition)
|
||||
end
|
||||
|
||||
if partName == "Head" then
|
||||
cameraResult = cameraJSON
|
||||
elseif cameraResult == nil then -- Fallback if Head doesn't exist for some reason
|
||||
cameraResult = cameraJSON
|
||||
end
|
||||
end
|
||||
|
||||
-- Process the SplitObjs output to consolidate it for the animation output.
|
||||
-- The data for common textures is mapped to the same output in the textures table.
|
||||
-- The Camera and AABB fields are consolidated into global fields.
|
||||
for key, val in pairs(decodedObjsStrOutput) do
|
||||
local decodedPartObj = game:GetService("HttpService"):JSONDecode(val)
|
||||
if decodedPartObj["files"] then
|
||||
local files = decodedPartObj["files"]
|
||||
for fileName, fileInfo in pairs(files) do
|
||||
local fileContent = fileInfo.content
|
||||
if strEndsWith(fileName, ".png") then
|
||||
local newFileName = fileName
|
||||
while textures[newFileName] and textures[newFileName].content ~= fileContent do
|
||||
local charsFromEnd = string_len("Tex.png")
|
||||
local replacePos = string_len(newFileName) - charsFromEnd
|
||||
local newNumber = tostring(tonumber(newFileName:sub(replacePos, replacePos)) + 1)
|
||||
newFileName = replaceChar(replacePos, newFileName, newNumber)
|
||||
end
|
||||
textures[newFileName] = {
|
||||
content = fileContent
|
||||
}
|
||||
files["texture"] = newFileName
|
||||
files[fileName] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Calculate total aabb
|
||||
if decodedPartObj["AABB"] then
|
||||
addToTotalAABB(key, decodedPartObj["AABB"])
|
||||
decodedPartObj["AABB"] = nil
|
||||
end
|
||||
|
||||
-- Resolve overall Camera JSON.
|
||||
if decodedPartObj["camera"] then
|
||||
resolveCameraResult(key, decodedPartObj["camera"])
|
||||
decodedPartObj["camera"] = nil
|
||||
end
|
||||
partObjsResult[key] = decodedPartObj
|
||||
end
|
||||
|
||||
-- Special camera position and direction for rotated character
|
||||
if rotateCharacter then
|
||||
local rootPart = character:FindFirstChild("HumanoidRootPart")
|
||||
if rootPart then
|
||||
local cameraOffset = Vector3.new(6, 5, 7) * .7
|
||||
local cameraPosition = Vector3.new(rootPart.Position.X, rootPart.Position.Y, rootPart.Position.Z) - cameraOffset
|
||||
local cameraDirection = (rootPart.Position - cameraPosition).unit
|
||||
cameraResult["position"] = vector3ToTable(cameraPosition)
|
||||
cameraResult["direction"] = vector3ToTable(cameraDirection)
|
||||
end
|
||||
end
|
||||
|
||||
local resultData = {
|
||||
Frames = animationData,
|
||||
Camera = cameraResult,
|
||||
AABB = totalAABB,
|
||||
PartObjs = partObjsResult,
|
||||
Textures = textures
|
||||
}
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ResultFinalized")
|
||||
return game:GetService("HttpService"):JSONEncode(resultData), requestedUrls
|
||||
@@ -0,0 +1,178 @@
|
||||
-- AnimationSilhouette.lua
|
||||
-- Generates a Silhouette of a character doing the animation in the color requested
|
||||
|
||||
local assetUrl, baseUrl, x, y, silhouetteColor = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
local BundleLoader = require(ThumbnailGenerator:GetThumbnailModule("BundleLoader"))
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local emoteAnim = game:GetObjects(assetUrl)[1]
|
||||
|
||||
local bundleId = 401 -- Default is Alexandra Ninniflip
|
||||
local bundleIdValue = emoteAnim:FindFirstChild("ThumbnailBundleId")
|
||||
if bundleIdValue and bundleIdValue:IsA("NumberValue") then
|
||||
bundleId = bundleIdValue.Value
|
||||
end
|
||||
|
||||
-- Default keyframe to use in thumbnail is middle keyframe
|
||||
local thumbnailKeyframeNumber
|
||||
local thumbnailKeyframeValue = emoteAnim:FindFirstChild("ThumbnailKeyframe")
|
||||
if thumbnailKeyframeValue and thumbnailKeyframeValue:IsA("NumberValue") then
|
||||
thumbnailKeyframeNumber = thumbnailKeyframeValue.Value
|
||||
end
|
||||
|
||||
local thumbnailZoom = 1
|
||||
local thumbnailZoomValue = emoteAnim:FindFirstChild("ThumbnailZoom")
|
||||
if thumbnailZoomValue and thumbnailZoomValue:IsA("NumberValue") then
|
||||
thumbnailZoom = thumbnailZoomValue.Value
|
||||
end
|
||||
|
||||
local fieldOfView = 20
|
||||
local fieldOfViewValue = emoteAnim:FindFirstChild("ThumbnailFieldOfView")
|
||||
if fieldOfViewValue and fieldOfViewValue:IsA("NumberValue") then
|
||||
fieldOfView = fieldOfViewValue.Value
|
||||
end
|
||||
|
||||
local verticalOffset = 0
|
||||
local verticalOffsetValue = emoteAnim:FindFirstChild("ThumbnailVerticalOffset")
|
||||
if verticalOffsetValue and verticalOffsetValue:IsA("NumberValue") then
|
||||
verticalOffset = verticalOffsetValue.Value
|
||||
end
|
||||
|
||||
local horizontalOffset = 0
|
||||
local horizontalOffsetValue = emoteAnim:FindFirstChild("ThumbnailHorizontalOffset")
|
||||
if horizontalOffsetValue and horizontalOffsetValue:IsA("NumberValue") then
|
||||
horizontalOffset = horizontalOffsetValue.Value
|
||||
end
|
||||
|
||||
local rotationDegrees = 0
|
||||
local thumbnailRotationValue = emoteAnim:FindFirstChild("ThumbnailCharacterRotation")
|
||||
if thumbnailRotationValue and thumbnailRotationValue:IsA("NumberValue") then
|
||||
rotationDegrees = thumbnailRotationValue.Value
|
||||
end
|
||||
|
||||
local bundleCharacter = BundleLoader.LoadBundleCharacter(baseUrl, bundleId)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("BundleCharacterLoaded")
|
||||
|
||||
local r, g, b = unpack(silhouetteColor:split("/"))
|
||||
local silhouetteColor3 = Color3.fromRGB(tonumber(r), tonumber(g), tonumber(b))
|
||||
|
||||
local overrideColor3Value = emoteAnim:FindFirstChild("ThumbnailSilhouetteColor")
|
||||
if overrideColor3Value and overrideColor3Value:IsA("Color3Value") then
|
||||
silhouetteColor3 = overrideColor3Value.Value
|
||||
end
|
||||
|
||||
local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider")
|
||||
|
||||
local kfs = KeyframeSequenceProvider:GetKeyframeSequence(emoteAnim.AnimationId)
|
||||
local emoteKeyframes = kfs:GetKeyframes()
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("KeyframesLoaded")
|
||||
|
||||
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 applyPose(character, poseKeyframe)
|
||||
local function recurApplyPoses(parentPose, poseObject)
|
||||
if parentPose then
|
||||
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
|
||||
joint.C1 = joint.C1 * poseObject.CFrame:inverse()
|
||||
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
|
||||
|
||||
local thumbnailKeyframe
|
||||
if thumbnailKeyframeNumber then
|
||||
-- Check that the index provided as the keyframe number is valid
|
||||
if thumbnailKeyframeNumber > 0 and thumbnailKeyframeNumber <= #emoteKeyframes then
|
||||
thumbnailKeyframe = emoteKeyframes[thumbnailKeyframeNumber]
|
||||
else
|
||||
thumbnailKeyframe = emoteKeyframes[math.ceil(#emoteKeyframes/2)]
|
||||
end
|
||||
else
|
||||
thumbnailKeyframe = emoteKeyframes[math.ceil(#emoteKeyframes/2)]
|
||||
end
|
||||
|
||||
if rotationDegrees ~= 0 then
|
||||
local rootPose = thumbnailKeyframe:GetPoses()[1]
|
||||
if rootPose then
|
||||
local upperTorsoPose = rootPose:GetSubPoses()[1]
|
||||
if upperTorsoPose then
|
||||
upperTorsoPose.CFrame = upperTorsoPose.CFrame * CFrame.Angles(0, math.rad(rotationDegrees), 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
applyPose(bundleCharacter, thumbnailKeyframe)
|
||||
|
||||
local function getCameraOffset(fov, extentsSize)
|
||||
local xSize, ySize, zSize = extentsSize.X, extentsSize.Y, extentsSize.Z
|
||||
|
||||
local maxSize = math.sqrt(xSize^2 + ySize^2 + zSize^2)
|
||||
local fovMultiplier = 1 / math.tan(math.rad(fov) / 2)
|
||||
|
||||
local halfSize = maxSize / 2
|
||||
return halfSize * fovMultiplier
|
||||
end
|
||||
|
||||
local function zoomExtents(model, lookVector, thumbnailCamera)
|
||||
local modelCFrame = model:GetModelCFrame()
|
||||
|
||||
local position = modelCFrame.p
|
||||
position = position + Vector3.new(horizontalOffset, -verticalOffset, 0)
|
||||
|
||||
local extentsSize = model:GetExtentsSize()
|
||||
local cameraOffset = getCameraOffset(thumbnailCamera.FieldOfView, extentsSize)
|
||||
|
||||
local zoomFactor = 1 / thumbnailZoom
|
||||
cameraOffset = cameraOffset * zoomFactor
|
||||
|
||||
local cameraRotation = thumbnailCamera.CFrame - thumbnailCamera.CFrame.p
|
||||
thumbnailCamera.CFrame = cameraRotation + position + (lookVector * cameraOffset)
|
||||
end
|
||||
|
||||
local function createThumbnailCamera(model)
|
||||
local modelCFrame = model:GetModelCFrame()
|
||||
local lookVector = modelCFrame.lookVector
|
||||
|
||||
local humanoidRootPart = model:FindFirstChild("HumanoidRootPart")
|
||||
if humanoidRootPart then
|
||||
lookVector = humanoidRootPart.CFrame.lookVector
|
||||
end
|
||||
|
||||
local thumbnailCamera = Instance.new("Camera")
|
||||
thumbnailCamera.Name = "ThumbnailCamera"
|
||||
thumbnailCamera.Parent = model
|
||||
|
||||
thumbnailCamera.FieldOfView = fieldOfView
|
||||
thumbnailCamera.CFrame = CFrame.new(modelCFrame.p + (lookVector * 5), modelCFrame.p)
|
||||
thumbnailCamera.Focus = modelCFrame
|
||||
|
||||
zoomExtents(model, lookVector, thumbnailCamera)
|
||||
end
|
||||
|
||||
createThumbnailCamera(bundleCharacter)
|
||||
|
||||
local result, requestedUrls = game:GetService("ThumbnailGenerator"):ClickSilhouette(x, y, silhouetteColor3)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,31 @@
|
||||
-- Avatar v1.0.2
|
||||
-- This is the thumbnail script for R6 avatars. Straight up and down, with the right arm out if they have a gear.
|
||||
|
||||
local characterAppearanceUrl, baseUrl, fileExtension, x, y = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
|
||||
|
||||
-- Raise up the character's arm if they have gear.
|
||||
if player.Character then
|
||||
for _, child in pairs(player.Character:GetChildren()) do
|
||||
if child:IsA("Tool") then
|
||||
player.Character.Torso["Right Shoulder"].CurrentAngle = math.rad(90)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,130 @@
|
||||
-- AvatarAnimation V 1.0.3
|
||||
-- Creates a thumbnail from the middle Keyframe of an animation.
|
||||
|
||||
-- Example arguments:
|
||||
-- https://www.sitetest3.robloxlabs.com/Asset/AvatarAccoutrements.ashx?AvatarHash=80dcfa39a8e90f690d2be0e56239abbe&AssetIDs=42900214,32357663,32357631,32357619,32357584,32357558&ResolvedAvatarType=R15,
|
||||
-- http://www.sitetest3.robloxlabs.com/,
|
||||
-- Png,
|
||||
-- 600,
|
||||
-- 600,
|
||||
-- http://www.sitetest3.robloxlabs.com/Asset/?hash=ca005a9e83ac95bd5068029afdc65496
|
||||
|
||||
local characterAppearanceUrl, baseUrl, fileExtension, x, y, animationUrl = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
|
||||
|
||||
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 applyR15Pose(character, poseKeyframe)
|
||||
local function recurApplyPoses(parentPose, poseObject)
|
||||
if parentPose then
|
||||
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
|
||||
joint.C1 = joint.C1 * poseObject.CFrame:inverse()
|
||||
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
|
||||
|
||||
local animationObjects = game:GetObjects(animationUrl)
|
||||
|
||||
local poseAnimation = nil
|
||||
local animations = {}
|
||||
local rotateCharacter = false
|
||||
local thumbnailCamera = nil
|
||||
|
||||
local function getAnimations(model)
|
||||
for _, child in pairs(model:GetChildren()) do
|
||||
if child:IsA("Animation") then
|
||||
if string.lower(model.Name) == "pose" then
|
||||
poseAnimation = child
|
||||
else
|
||||
table.insert(animations, child)
|
||||
end
|
||||
else
|
||||
getAnimations(child)
|
||||
end
|
||||
|
||||
if child:IsA("Camera") and child.Name == "ThumbnailCamera" then
|
||||
thumbnailCamera = child:Clone()
|
||||
end
|
||||
|
||||
if child:IsA("StringValue") and child.Name == "swim" then
|
||||
rotateCharacter = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, animationModel in pairs(animationObjects) do
|
||||
getAnimations(animationModel)
|
||||
end
|
||||
|
||||
local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider")
|
||||
local keyframes = {}
|
||||
|
||||
if poseAnimation then
|
||||
local kfs = KeyframeSequenceProvider:GetKeyframeSequence(poseAnimation.AnimationId)
|
||||
local animKeyframes = kfs:GetKeyframes()
|
||||
for _, keyframe in pairs(animKeyframes) do
|
||||
table.insert(keyframes, keyframe)
|
||||
end
|
||||
else
|
||||
for _, animation in pairs(animations) do
|
||||
local kfs = KeyframeSequenceProvider:GetKeyframeSequence(animation.AnimationId)
|
||||
local animKeyframes = kfs:GetKeyframes()
|
||||
for _, keyframe in pairs(animKeyframes) do
|
||||
table.insert(keyframes, keyframe)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("AnimationsLoaded")
|
||||
|
||||
local keyframe = keyframes[math.max(1, math.floor(#keyframes/2))]
|
||||
applyR15Pose(player.Character, keyframe)
|
||||
|
||||
if rotateCharacter then
|
||||
local rootPart = player.Character:FindFirstChild("HumanoidRootPart")
|
||||
if rootPart then
|
||||
rootPart.CFrame = rootPart.CFrame * CFrame.Angles(math.rad(-90), 0, 0)
|
||||
if not thumbnailCamera then
|
||||
local camera = Instance.new("Camera")
|
||||
camera.Name = "ThumbnailCamera"
|
||||
|
||||
local rotatedCameraOffset = Vector3.new(6, 5, 7) * .7
|
||||
camera.CFrame = CFrame.new(Vector3.new(rootPart.Position.X, rootPart.Position.Y, rootPart.Position.Z) - rotatedCameraOffset, rootPart.Position)
|
||||
camera.Parent = player.Character
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if thumbnailCamera then
|
||||
thumbnailCamera.Parent = player.Character
|
||||
end
|
||||
|
||||
local result, requestedUrls = game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,172 @@
|
||||
-- Avatar_R15_Action v1.1.1
|
||||
-- For R6, this generates the normal with/without gear pose. For R15 it positions their body in an action pose.
|
||||
local baseUrl, characterAppearanceUrl, fileExtension, x, y = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
|
||||
|
||||
local poseAnimationId = "http://www.roblox.com/asset/?id=532421348"
|
||||
|
||||
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 applyKeyframe(character, poseKeyframe)
|
||||
local function recurApplyPoses(parentPose, poseObject)
|
||||
if parentPose then
|
||||
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
|
||||
if joint and poseObject.Weight ~= 0 then
|
||||
joint.C1 = poseObject.CFrame:inverse() + joint.C1.p
|
||||
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
|
||||
|
||||
local function applyR15Pose(character)
|
||||
local poseKeyframSequence = game:GetService("KeyframeSequenceProvider"):GetKeyframeSequence(poseAnimationId)
|
||||
local poseKeyframe = poseKeyframSequence:GetKeyframes()[1]
|
||||
|
||||
applyKeyframe(character, poseKeyframe)
|
||||
end
|
||||
|
||||
local function findAttachmentsRecur(parent, resultTable, returnDictionary)
|
||||
for _, obj in pairs(parent:GetChildren()) do
|
||||
if obj:IsA("Attachment") then
|
||||
if returnDictionary then
|
||||
resultTable[obj.Name] = obj
|
||||
else
|
||||
resultTable[#resultTable + 1] = obj
|
||||
end
|
||||
elseif not obj:IsA("Tool") and not obj:IsA("Accoutrement") then -- Leave out tools and accoutrements in the character
|
||||
findAttachmentsRecur(obj, resultTable, returnDictionary)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function findAttachmentsInTool(tool)
|
||||
local attachments = {}
|
||||
findAttachmentsRecur(tool, attachments, false)
|
||||
return attachments
|
||||
end
|
||||
|
||||
local function findAttachmentsInCharacter(character)
|
||||
local attachments = {}
|
||||
findAttachmentsRecur(character, attachments, true)
|
||||
return attachments
|
||||
end
|
||||
|
||||
local function weldAttachments(attach1, attach2)
|
||||
local weld = Instance.new("Weld")
|
||||
weld.Part0 = attach1.Parent
|
||||
weld.Part1 = attach2.Parent
|
||||
weld.C0 = attach1.CFrame
|
||||
weld.C1 = attach2.CFrame
|
||||
weld.Parent = attach1.Parent
|
||||
return weld
|
||||
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
|
||||
|
||||
local function doR15ToolPose(character, humanoid, tool)
|
||||
local characterAttachments = findAttachmentsInCharacter(character)
|
||||
local toolAttachments = findAttachmentsInTool(tool)
|
||||
local foundAttachments = false
|
||||
-- If matching attachments exist in the gear then weld them and do the "action" R15 pose.
|
||||
-- Otherwise keep the R15 in the T-Pose position and just raise the arm.
|
||||
for _, attachment in pairs(toolAttachments) do
|
||||
local matchingAttachment = characterAttachments[attachment.Name]
|
||||
if matchingAttachment then
|
||||
foundAttachments = true
|
||||
weldAttachments(matchingAttachment, attachment)
|
||||
end
|
||||
end
|
||||
|
||||
if foundAttachments then
|
||||
tool.Parent = character
|
||||
applyR15Pose(character)
|
||||
|
||||
local toolPose = tool:FindFirstChild("ThumbnailPose")
|
||||
if toolPose and toolPose:IsA("Keyframe") then
|
||||
applyKeyframe(character, toolPose)
|
||||
end
|
||||
else
|
||||
tool.Parent = nil
|
||||
local rightShoulderJoint = getJointBetween(character.UpperTorso, character.RightUpperArm)
|
||||
if rightShoulderJoint then
|
||||
rightShoulderJoint.C1 = rightShoulderJoint.C1 * CFrame.new(0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 1, 0):inverse()
|
||||
end
|
||||
if tool:FindFirstChild("Handle") then
|
||||
local attachment = findFirstMatchingAttachment(character, "RightGripAttachment")
|
||||
if attachment then
|
||||
tool.Handle.CFrame = attachment.Parent.CFrame * attachment.CFrame * tool.Grip:inverse()
|
||||
end
|
||||
end
|
||||
humanoid:EquipTool(tool)
|
||||
end
|
||||
end
|
||||
|
||||
local character = player.Character
|
||||
if character then
|
||||
local tool = character:FindFirstChildOfClass("Tool")
|
||||
local humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
local animateScript = character:FindFirstChild("Animate")
|
||||
if animateScript then
|
||||
local equippedPoseValue = animateScript:FindFirstChild("Pose") or animateScript:FindFirstChild("pose")
|
||||
if equippedPoseValue then
|
||||
local poseAnim = equippedPoseValue:FindFirstChildOfClass("Animation")
|
||||
if poseAnim then
|
||||
poseAnimationId = poseAnim.AnimationId
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if humanoid then
|
||||
if humanoid.RigType == Enum.HumanoidRigType.R6 then
|
||||
if tool then
|
||||
character.Torso["Right Shoulder"].CurrentAngle = math.rad(90)
|
||||
end
|
||||
elseif humanoid.RigType == Enum.HumanoidRigType.R15 then
|
||||
if tool then
|
||||
doR15ToolPose(character, humanoid, tool)
|
||||
else
|
||||
applyR15Pose(character)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,56 @@
|
||||
-- Avatar_R15_Standard v1.0.2
|
||||
-- Pose R6 characters in the normal way. For R15, have them in the same pose, and raise their arm up if they have gear.
|
||||
-- Sample params:
|
||||
-- baseUrl: "http://www.roblox.com/"
|
||||
-- characterAppearanceUrl: "http://www.roblox.com/Asset/AvatarAccoutrements.ashx?AvatarHash=98925edb8aa60e39ba8a4f0bf8b71d6f&AssetIDs=3372792,9255011,20418682,68258723,158066137,232503325,244097060,248286896,264611665,376530220,376531012,376531300,376531703,376532000,624157131&ResolvedAvatarType=R15&Height=1&Width=0.75&Head=0.95&Depth=0.88"
|
||||
-- fileExtension: "Png"
|
||||
-- x: 1260
|
||||
-- y: 1260
|
||||
|
||||
local baseUrl, characterAppearanceUrl, fileExtension, x, y = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService('ThumbnailGenerator')
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
|
||||
|
||||
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 doR15ToolPose(rig)
|
||||
local rightShoulderJoint = getJointBetween(rig.UpperTorso, rig.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
|
||||
|
||||
-- Raise right arm up to hold gear.
|
||||
local character = player.Character
|
||||
if character then
|
||||
if character:FindFirstChildOfClass("Tool") then
|
||||
local humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
if humanoid then
|
||||
if humanoid.RigType == Enum.HumanoidRigType.R6 then
|
||||
character.Torso['Right Shoulder'].CurrentAngle = math.rad(90)
|
||||
elseif humanoid.RigType == Enum.HumanoidRigType.R15 then
|
||||
doR15ToolPose(character)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,273 @@
|
||||
-- BodyPart v1.0.6
|
||||
-- See http://wiki.roblox.com/index.php?title=R15_Compatibility_Guide#Package_Parts for details on how body parts work with R15
|
||||
|
||||
local assetUrl, baseUrl, fileExtension, x, y, R6RigUrl, customUrl = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
local DFFlagBodyPartFocusInThreeDThumbnails = settings():GetFFlag("BodyPartFocusInThreeDThumbnails")
|
||||
|
||||
local CreateExtentsMinMax
|
||||
local MannequinUtility
|
||||
local ScaleUtility
|
||||
|
||||
if DFFlagBodyPartFocusInThreeDThumbnails then
|
||||
CreateExtentsMinMax = require(ThumbnailGenerator:GetThumbnailModule("CreateExtentsMinMax"))
|
||||
MannequinUtility = require(ThumbnailGenerator:GetThumbnailModule("MannequinUtility"))
|
||||
ScaleUtility = require(ThumbnailGenerator:GetThumbnailModule("ScaleUtility"))
|
||||
end
|
||||
|
||||
pcall(function() game:GetService('ContentProvider'):SetBaseUrl(baseUrl) end)
|
||||
game:GetService('ScriptContext').ScriptsDisabled = true
|
||||
|
||||
local objects = game:GetObjects(assetUrl)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("BodyPartLoaded")
|
||||
|
||||
local useR15 = false
|
||||
local useR15NewNames = false
|
||||
local bodyPartProportion = "Classic"
|
||||
local floatMax = math.huge
|
||||
|
||||
if DFFlagBodyPartFocusInThreeDThumbnails then
|
||||
for _, object in pairs(objects) do
|
||||
if object:IsA("Folder") then
|
||||
if object.Name == "R15" then
|
||||
useR15 = true
|
||||
elseif object.Name == "R15ArtistIntent" then
|
||||
useR15 = true
|
||||
useR15NewNames = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
bodyPartProportion = ScaleUtility.GetObjectsScaleType(objects)
|
||||
else
|
||||
for _, object in pairs(objects) do
|
||||
if object:IsA("Folder") and (object.Name == "R15" or object.Name == "R15ArtistIntent") then
|
||||
useR15 = true
|
||||
|
||||
-- Check to see if there are native scale parts in this object
|
||||
local partScaleType = object:FindFirstChild("AvatarPartScaleType", true)
|
||||
if partScaleType then
|
||||
bodyPartProportion = partScaleType.Value
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local mannequin
|
||||
if DFFlagBodyPartFocusInThreeDThumbnails then
|
||||
if useR15 then
|
||||
mannequin = MannequinUtility.LoadMannequinForScaleType(bodyPartProportion)
|
||||
else
|
||||
mannequin = MannequinUtility.LoadR6Mannequin()
|
||||
end
|
||||
else
|
||||
local R15RigUrl = "http://www.roblox.com/asset/?id=516159357"
|
||||
for _, object in pairs(objects) do
|
||||
if object:IsA("Folder") and object.Name == "R15ArtistIntent" then
|
||||
useR15NewNames = true
|
||||
if bodyPartProportion == "Classic" then
|
||||
R15RigUrl = "http://www.roblox.com/asset/?id=1664543044"
|
||||
else
|
||||
R15RigUrl = "http://www.roblox.com/asset/?id=2337256345"
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if useR15 then
|
||||
mannequin = game:GetObjects(R15RigUrl)[1]
|
||||
else
|
||||
mannequin = game:GetObjects(R6RigUrl)[1]
|
||||
end
|
||||
|
||||
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
||||
mannequin.Parent = workspace
|
||||
end
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
|
||||
|
||||
game:GetObjects(customUrl)[1].Parent = mannequin
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("CustomUrlLoaded")
|
||||
|
||||
local function addFolderChildren(folder, focusPartNamesOut, focusPartsOut)
|
||||
for _, child in pairs(folder:GetChildren()) do
|
||||
local existingBodyPart = mannequin:FindFirstChild(child.Name)
|
||||
if existingBodyPart then
|
||||
existingBodyPart:Destroy()
|
||||
end
|
||||
child.Parent = mannequin
|
||||
table.insert(focusPartNamesOut, child.name)
|
||||
table.insert(focusPartsOut, child)
|
||||
end
|
||||
end
|
||||
|
||||
local r15FolderName = "R15"
|
||||
if (useR15 and useR15NewNames) then
|
||||
r15FolderName = "R15ArtistIntent"
|
||||
end
|
||||
|
||||
local focusParts = {}
|
||||
local focusPartNames = {}
|
||||
|
||||
for _, object in pairs(objects) do
|
||||
if useR15 and object:IsA("Folder") and object.Name == r15FolderName then
|
||||
addFolderChildren(object, focusPartNames, focusParts)
|
||||
elseif not useR15 and object:IsA("Folder") and object.Name == "R6" then
|
||||
addFolderChildren(object, focusPartNames, focusParts)
|
||||
elseif not (object:IsA("Folder") and string.find(object.Name, "R15")) then -- There will now be MULTIPLE R15 Folders. Ignore the ones we didn't search for.
|
||||
object.Parent = mannequin
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
if useR15 then
|
||||
if DFFlagBodyPartFocusInThreeDThumbnails then
|
||||
local humanoid = mannequin:FindFirstChild("Humanoid")
|
||||
if humanoid then
|
||||
ScaleUtility.CreateProportionScaleValues(humanoid, bodyPartProportion)
|
||||
humanoid:BuildRigFromAttachments()
|
||||
end
|
||||
else
|
||||
-- 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)
|
||||
local humanoid = mannequin:WaitForChild("Humanoid")
|
||||
if humanoid then
|
||||
local typeObject = humanoid:FindFirstChild("BodyTypeScale")
|
||||
|
||||
if typeObject == nil then
|
||||
typeObject = Instance.new("NumberValue")
|
||||
typeObject.Name = "BodyTypeScale"
|
||||
typeObject.Value = 0
|
||||
typeObject.Parent = humanoid
|
||||
end
|
||||
|
||||
local proportionObject = humanoid:FindFirstChild("BodyProportionScale")
|
||||
if proportionObject == nil then
|
||||
proportionObject = Instance.new("NumberValue")
|
||||
proportionObject.Name = "BodyProportionScale"
|
||||
proportionObject.Value = 0
|
||||
proportionObject.Parent = humanoid
|
||||
end
|
||||
|
||||
if bodyPartProportion == "ProportionsNormal" then
|
||||
typeObject.Value = 1
|
||||
proportionObject.Value = 0
|
||||
elseif bodyPartProportion == "ProportionsSlender" then
|
||||
typeObject.Value = 1
|
||||
proportionObject.Value = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function addToBounds(cornerPosition, focusExtentsOut)
|
||||
focusExtentsOut["minx"] = math.min(focusExtentsOut["minx"], cornerPosition.x)
|
||||
focusExtentsOut["miny"] = math.min(focusExtentsOut["miny"], cornerPosition.y)
|
||||
focusExtentsOut["minz"] = math.min(focusExtentsOut["minz"], cornerPosition.z)
|
||||
focusExtentsOut["maxx"] = math.max(focusExtentsOut["maxx"], cornerPosition.x)
|
||||
focusExtentsOut["maxy"] = math.max(focusExtentsOut["maxy"], cornerPosition.y)
|
||||
focusExtentsOut["maxz"] = math.max(focusExtentsOut["maxz"], cornerPosition.z)
|
||||
end
|
||||
|
||||
local function addCornerToBounds(partCFrame, cornerSelect, halfPartSize, focusExtentsOut)
|
||||
local cornerPositionLocal = cornerSelect * halfPartSize
|
||||
local cornerPositionWorld = partCFrame * cornerPositionLocal
|
||||
addToBounds(cornerPositionWorld, focusExtentsOut)
|
||||
end
|
||||
|
||||
local extentsMinMax
|
||||
local shouldCrop = false
|
||||
|
||||
if DFFlagBodyPartFocusInThreeDThumbnails then
|
||||
extentsMinMax = CreateExtentsMinMax(focusParts)
|
||||
shouldCrop = #focusParts > 0
|
||||
else
|
||||
local focusOnExtents = { minx = floatMax, miny = floatMax, minz = floatMax, maxx = -floatMax, maxy = -floatMax, maxz = -floatMax }
|
||||
|
||||
local FFlagThumbnailSupportFocusOnPart = settings():GetFFlag("ThumbnailSupportFocusOnPart")
|
||||
if FFlagThumbnailSupportFocusOnPart and string.lower(fileExtension) == "png" then
|
||||
-- expand focusOnExtents to bound all the part(s) in the focusPartNames table
|
||||
if #focusPartNames > 0 then
|
||||
for _, focusPartName in pairs(focusPartNames) do
|
||||
local focusPart = mannequin:FindFirstChild(focusPartName, --[[recursive = ]] true)
|
||||
if focusPart then
|
||||
local partPosition = focusPart.Position
|
||||
local partRotation = focusPart.Rotation
|
||||
local halfPartSize = focusPart.Size / 2.0
|
||||
local partCFrame = CFrame.Angles(math.rad(partRotation.x), math.rad(partRotation.y), math.rad(partRotation.z)) + partPosition
|
||||
addCornerToBounds(partCFrame, Vector3.new( 1, 1, 1), halfPartSize, focusOnExtents)
|
||||
addCornerToBounds(partCFrame, Vector3.new( 1, 1,-1), halfPartSize, focusOnExtents)
|
||||
addCornerToBounds(partCFrame, Vector3.new( 1,-1, 1), halfPartSize, focusOnExtents)
|
||||
addCornerToBounds(partCFrame, Vector3.new( 1,-1,-1), halfPartSize, focusOnExtents)
|
||||
addCornerToBounds(partCFrame, Vector3.new(-1, 1, 1), halfPartSize, focusOnExtents)
|
||||
addCornerToBounds(partCFrame, Vector3.new(-1, 1,-1), halfPartSize, focusOnExtents)
|
||||
addCornerToBounds(partCFrame, Vector3.new(-1,-1, 1), halfPartSize, focusOnExtents)
|
||||
addCornerToBounds(partCFrame, Vector3.new(-1,-1,-1), halfPartSize, focusOnExtents)
|
||||
shouldCrop = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
extentsMinMax = {
|
||||
Vector3.new(focusOnExtents["minx"], focusOnExtents["miny"], focusOnExtents["minz"]),
|
||||
Vector3.new(focusOnExtents["maxx"], focusOnExtents["maxy"], focusOnExtents["maxz"])
|
||||
}
|
||||
end
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop = ]] shouldCrop, extentsMinMax)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,75 @@
|
||||
-- Closeup v1.0.3
|
||||
-- Used for avatar closeup
|
||||
local baseUrl, characterAppearanceUrl, fileExtension, x, y, quadratic, baseHatZoom, maxHatZoom, cameraOffsetX, cameraOffsetY = ...
|
||||
|
||||
local FFlagOnlyCheckHeadAccessoryInHeadShot = game:DefineFastFlag("OnlyCheckHeadAccessoryInHeadShot", false)
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService('ContentProvider'):SetBaseUrl(baseUrl) end)
|
||||
game:GetService('ScriptContext').ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("PlayerCharacterLoaded")
|
||||
|
||||
local headAttachments = {}
|
||||
if FFlagOnlyCheckHeadAccessoryInHeadShot then
|
||||
if player.Character:FindFirstChild("Head") then
|
||||
for _,child in pairs(player.Character.Head:GetChildren()) do
|
||||
if child:IsA("Attachment") then
|
||||
headAttachments[child.Name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local maxDimension = 0
|
||||
|
||||
if player.Character then
|
||||
-- Remove gear
|
||||
for _, child in pairs(player.Character:GetChildren()) do
|
||||
if child:IsA("Tool") then
|
||||
child:Destroy()
|
||||
elseif child:IsA("Accoutrement") then
|
||||
local handle = child:FindFirstChild("Handle")
|
||||
if handle then
|
||||
local attachment = handle:FindFirstChildWhichIsA("Attachment")
|
||||
--legacy hat does not have attachment in it and should be considered when zoom out camera
|
||||
if not FFlagOnlyCheckHeadAccessoryInHeadShot or not attachment or headAttachments[attachment.Name] then
|
||||
local size = handle.Size / 2 + handle.Position - player.Character.Head.Position
|
||||
local xy = Vector2.new(size.x, size.y)
|
||||
if xy.magnitude > maxDimension then
|
||||
maxDimension = xy.magnitude
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Setup Camera
|
||||
local maxHatOffset = 0.5 -- Maximum amount to move camera upward to accomodate large hats
|
||||
maxDimension = math.min(1, maxDimension / 3) -- Confine maxdimension to specific bounds
|
||||
|
||||
if quadratic then
|
||||
maxDimension = maxDimension * maxDimension -- Zoom out on quadratic interpolation
|
||||
end
|
||||
|
||||
local viewOffset = player.Character.Head.CFrame * CFrame.new(cameraOffsetX, cameraOffsetY + maxHatOffset * maxDimension, 0.1) -- View vector offset from head
|
||||
local positionOffset = player.Character.Head.CFrame + (CFrame.Angles(0, -math.pi / 16, 0).lookVector.unit * 3) -- Position vector offset from head
|
||||
|
||||
local camera = Instance.new("Camera", player.Character)
|
||||
camera.Name = "ThumbnailCamera"
|
||||
camera.CameraType = Enum.CameraType.Scriptable
|
||||
camera.CoordinateFrame = CFrame.new(positionOffset.p, viewOffset.p)
|
||||
camera.FieldOfView = baseHatZoom + (maxHatZoom - baseHatZoom) * maxDimension
|
||||
end
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,28 @@
|
||||
-- Decal v1.0.2
|
||||
-- Used for faces and decals
|
||||
|
||||
local assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService('ScriptContext').ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local decal = game:GetObjects(assetUrl)[1]
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("DecalLoaded")
|
||||
|
||||
local image, requestedUrls
|
||||
local success = pcall(function()
|
||||
image, requestedUrls = ThumbnailGenerator:ClickTexture(decal.Texture, fileExtension, x, y)
|
||||
end)
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("TextureGenerated")
|
||||
|
||||
if success then
|
||||
return image, requestedUrls
|
||||
end
|
||||
|
||||
-- if we fail return the hourglass, since we're probably in moderation.
|
||||
return "/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAX8P/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABBQJ//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAGPwJ//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIAAwAAABAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPxB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPxB//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxB//9k=", {decal.Texture}
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Gear v1.0.3
|
||||
|
||||
local assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
for _, object in pairs(game:GetObjects(assetUrl)) do
|
||||
object.Parent = workspace
|
||||
end
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop =]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,73 @@
|
||||
-- Hat v1.1.0
|
||||
|
||||
local assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
local DFFlagHatThumbnailMannequins = settings():GetFFlag("HatThumbnailMannequins")
|
||||
|
||||
-- Modules
|
||||
local CreateExtentsMinMax
|
||||
local MannequinUtility
|
||||
local ScaleUtility
|
||||
|
||||
if DFFlagHatThumbnailMannequins then
|
||||
CreateExtentsMinMax = require(ThumbnailGenerator:GetThumbnailModule("CreateExtentsMinMax"))
|
||||
MannequinUtility = require(ThumbnailGenerator:GetThumbnailModule("MannequinUtility"))
|
||||
ScaleUtility = require(ThumbnailGenerator:GetThumbnailModule("ScaleUtility"))
|
||||
end
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local accoutrement = game:GetObjects(assetUrl)[1]
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
|
||||
|
||||
local handle
|
||||
|
||||
if DFFlagHatThumbnailMannequins then
|
||||
local accoutrementScaleType = ScaleUtility.GetScaleTypeForAccessory(accoutrement)
|
||||
local mannequin = MannequinUtility.LoadMannequinForScaleType(accoutrementScaleType)
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
|
||||
|
||||
-- Rotate mannequin for back accessories
|
||||
handle = accoutrement:FindFirstChild("Handle")
|
||||
if handle and handle:FindFirstChild("BodyBackAttachment") then
|
||||
MannequinUtility.RotateMannequin(mannequin, CFrame.Angles(0, math.pi, 0))
|
||||
end
|
||||
|
||||
-- Scale mannequin based on accoutrement scale type
|
||||
local humanoid = mannequin:FindFirstChild("Humanoid")
|
||||
if humanoid then
|
||||
ScaleUtility.CreateProportionScaleValues(humanoid, accoutrementScaleType)
|
||||
humanoid:BuildRigFromAttachments()
|
||||
end
|
||||
|
||||
accoutrement.Parent = mannequin
|
||||
else
|
||||
accoutrement.Parent = workspace
|
||||
end
|
||||
|
||||
local focusParts = {}
|
||||
local extentsMinMax
|
||||
|
||||
if DFFlagHatThumbnailMannequins then
|
||||
if handle then
|
||||
focusParts[#focusParts + 1] = handle
|
||||
|
||||
local connectedParts = handle:GetConnectedParts()
|
||||
for _, part in pairs(connectedParts) do
|
||||
focusParts[#focusParts + 1] = part
|
||||
end
|
||||
end
|
||||
|
||||
extentsMinMax = CreateExtentsMinMax(focusParts)
|
||||
end
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop =]] true, extentsMinMax)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,143 @@
|
||||
-- Head v1.2.0
|
||||
|
||||
local assetUrl, fileExtension, x, y, baseUrl, mannequinId = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
local DFFlagHeadThumbnailMannequins = settings():GetFFlag("HeadThumbnailMannequins")
|
||||
|
||||
-- Modules
|
||||
local CreateExtentsMinMax
|
||||
local MannequinUtility
|
||||
local ScaleUtility
|
||||
|
||||
if DFFlagHeadThumbnailMannequins then
|
||||
CreateExtentsMinMax = require(ThumbnailGenerator:GetThumbnailModule("CreateExtentsMinMax"))
|
||||
MannequinUtility = require(ThumbnailGenerator:GetThumbnailModule("MannequinUtility"))
|
||||
ScaleUtility = require(ThumbnailGenerator:GetThumbnailModule("ScaleUtility"))
|
||||
end
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local objects = game:GetObjects(assetUrl)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
|
||||
|
||||
local headScaleType
|
||||
local mannequin
|
||||
|
||||
if DFFlagHeadThumbnailMannequins then
|
||||
headScaleType = ScaleUtility.GetObjectsScaleType(objects)
|
||||
mannequin = MannequinUtility.LoadMannequinForScaleType(headScaleType)
|
||||
else
|
||||
mannequin = game:GetObjects(baseUrl.. "/asset/?id=" .. tostring(mannequinId))[1]
|
||||
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
||||
mannequin.Parent = workspace
|
||||
end
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
|
||||
|
||||
local function addFaceDecal(head)
|
||||
if head:FindFirstChild("face") then
|
||||
return
|
||||
end
|
||||
|
||||
local face = Instance.new("Decal")
|
||||
face.Name = "face"
|
||||
face.Texture = "rbxasset://textures/face.png"
|
||||
face.Parent = head
|
||||
end
|
||||
|
||||
local function replaceMannequinMeshPartHead(meshPartHead, meshHead)
|
||||
local newHead = Instance.new("Part")
|
||||
newHead.Size = meshPartHead.Size
|
||||
newHead.CFrame = meshPartHead.CFrame
|
||||
newHead.Color = meshPartHead.Color
|
||||
newHead.Name = "Head"
|
||||
|
||||
addFaceDecal(newHead)
|
||||
|
||||
local copiedAttachments = false
|
||||
for _, child in pairs(meshHead:GetChildren()) do
|
||||
if child:IsA("Vector3Value") and string.find(child.Name, "Attachment") then
|
||||
copiedAttachments = true
|
||||
|
||||
local newAttachment = Instance.new("Attachment")
|
||||
newAttachment.Name = child.Name
|
||||
newAttachment.Position = child.Value
|
||||
newAttachment.Parent = newHead
|
||||
end
|
||||
end
|
||||
|
||||
if not copiedAttachments then
|
||||
for _, child in pairs(meshPartHead:GetChildren()) do
|
||||
child.Parent = newHead
|
||||
end
|
||||
end
|
||||
|
||||
meshPartHead:Destroy()
|
||||
newHead.Parent = mannequin
|
||||
end
|
||||
|
||||
local function replaceMannequinHeadWithMeshHead()
|
||||
for _, obj in pairs(objects) do
|
||||
if obj:IsA("Folder") and obj.Name == "R15ArtistIntent" then
|
||||
local head = obj.Head
|
||||
addFaceDecal(head)
|
||||
mannequin.Head:Destroy()
|
||||
head.Parent = mannequin
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local headObject = objects[1]
|
||||
if headObject:IsA("Folder") then
|
||||
replaceMannequinHeadWithMeshHead()
|
||||
else
|
||||
if DFFlagHeadThumbnailMannequins then
|
||||
if mannequin.Head:IsA("MeshPart") then
|
||||
replaceMannequinMeshPartHead(mannequin.Head, headObject)
|
||||
end
|
||||
else
|
||||
mannequin.Head.BrickColor = BrickColor.Gray()
|
||||
end
|
||||
|
||||
if mannequin.Head:FindFirstChild("Mesh") then
|
||||
mannequin.Head.Mesh:Destroy()
|
||||
end
|
||||
headObject.Parent = mannequin.Head
|
||||
end
|
||||
|
||||
if DFFlagHeadThumbnailMannequins then
|
||||
-- Scale mannequin based on the scale type of the Head
|
||||
local humanoid = mannequin:FindFirstChild("Humanoid")
|
||||
if humanoid then
|
||||
ScaleUtility.CreateProportionScaleValues(humanoid, headScaleType)
|
||||
humanoid:BuildRigFromAttachments()
|
||||
end
|
||||
else
|
||||
for _, child in pairs(mannequin:GetChildren()) do
|
||||
if child:IsA("BasePart") and child.Name ~= "Head" then
|
||||
child:Destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local shouldCrop = false
|
||||
local extentsMinMax
|
||||
|
||||
if DFFlagHeadThumbnailMannequins then
|
||||
local focusParts = {
|
||||
mannequin:FindFirstChild("Head")
|
||||
}
|
||||
|
||||
shouldCrop = #focusParts > 0
|
||||
extentsMinMax = CreateExtentsMinMax(focusParts)
|
||||
end
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, shouldCrop, extentsMinMax)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,37 @@
|
||||
imageId, baseUrl, fileExtension, x, y = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
game:GetService('StarterGui'):SetCoreGuiEnabled(Enum.CoreGuiType.All, false);
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local IsSuccess, actualImageId = pcall(function()
|
||||
local Asset = game:GetObjects(baseUrl.."/asset/?id="..tostring(imageId))[1]
|
||||
if Asset:IsA("Decal") then
|
||||
return Asset.Texture
|
||||
end
|
||||
for _, child in pairs(Asset:GetDescendants()) do
|
||||
if child:IsA("Decal") then
|
||||
return child.Texture
|
||||
end
|
||||
end
|
||||
end)
|
||||
local AssetURL
|
||||
if not IsSuccess then
|
||||
actualImageId = imageId
|
||||
AssetURL = baseUrl.."/asset/?id="..tostring(imageId)
|
||||
else
|
||||
AssetURL = actualImageId
|
||||
end
|
||||
|
||||
local success, result = pcall(function()
|
||||
return ThumbnailGenerator:ClickTexture(AssetURL, fileExtension, x, y)
|
||||
end)
|
||||
if not success then
|
||||
result = "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAQAAABecRxxAAAEnklEQVR42u3UMQEAAAjDMOZf9JCAABIJPZp2gKdiAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYABiAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAAIoABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYABiAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAABgAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAYABiAAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAGAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABwW1Dy/i5f+lAuAAAAAElFTkSuQmCC" -- 1x1 transparent pixel
|
||||
end
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,22 @@
|
||||
-- Mesh v1.0.2
|
||||
|
||||
local assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local part = Instance.new("Part")
|
||||
part.Parent = workspace
|
||||
|
||||
local specialMesh = Instance.new("SpecialMesh")
|
||||
specialMesh.MeshId = assetUrl
|
||||
specialMesh.Parent = part
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop = ]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,47 @@
|
||||
-- MeshPart v1.0.2
|
||||
|
||||
local assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
game:DefineFastFlag("OnlyAllowMeshParts", false)
|
||||
|
||||
for _, object in pairs(game:GetObjects(assetUrl)) do
|
||||
if game:GetFastFlag("OnlyAllowMeshParts") then
|
||||
if object:IsA("MeshPart") and #object:GetChildren() == 0 then
|
||||
pcall(function() object.Parent = workspace end)
|
||||
break
|
||||
end
|
||||
else
|
||||
if object:IsA("Sky") then
|
||||
local resultValues = nil
|
||||
local success = pcall(function() resultValues = {ThumbnailGenerator:ClickTexture(object.SkyboxFt, fileExtension, x, y)} end)
|
||||
if success then
|
||||
return unpack(resultValues)
|
||||
else
|
||||
object.Parent = game:GetService("Lighting")
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] false)
|
||||
end
|
||||
elseif object:IsA("LuaSourceContainer") then
|
||||
return ThumbnailGenerator:ClickTexture(baseUrl.. "Thumbs/Script.png", fileExtension, x, y)
|
||||
elseif object:IsA("SpecialMesh") then
|
||||
local part = Instance.new("Part")
|
||||
part.Parent = workspace
|
||||
object.Parent = part
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
else
|
||||
pcall(function() object.Parent = workspace end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,39 @@
|
||||
-- Model v1.0.2
|
||||
|
||||
local assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
for _, object in pairs(game:GetObjects(assetUrl)) do
|
||||
if object:IsA("Sky") then
|
||||
local resultValues = nil
|
||||
local success = pcall(function() resultValues = {ThumbnailGenerator:ClickTexture(object.SkyboxFt, fileExtension, x, y)} end)
|
||||
if success then
|
||||
return unpack(resultValues)
|
||||
else
|
||||
object.Parent = game:GetService("Lighting")
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] false)
|
||||
end
|
||||
elseif object:IsA("LuaSourceContainer") then
|
||||
return ThumbnailGenerator:ClickTexture(baseUrl.. "/static/img/LuaThumbnail.png", fileExtension, x, y)
|
||||
elseif object:IsA("SpecialMesh") then
|
||||
local part = Instance.new("Part")
|
||||
part.Parent = workspace
|
||||
object.Parent = part
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
else
|
||||
pcall(function() object.Parent = workspace end)
|
||||
end
|
||||
end
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,361 @@
|
||||
-- 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
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
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
|
||||
@@ -0,0 +1,39 @@
|
||||
-- Pants v1.0.2
|
||||
|
||||
local assetUrl, fileExtension, x, y, baseUrl, mannequinId = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local mannequin = game:GetObjects(baseUrl.. "/asset/?id=" .. tostring(mannequinId))[1]
|
||||
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
||||
mannequin.Parent = workspace
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
|
||||
|
||||
local pants = game:GetObjects(assetUrl)[1]
|
||||
pants.Parent = mannequin
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
local DFFlagThrowErrorWhenRequestedURLFailed = settings():GetFFlag("ThrowErrorWhenRequestedURLFailed")
|
||||
if DFFlagThrowErrorWhenRequestedURLFailed then
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
local failedRequests = ContentProvider:GetFailedRequests()
|
||||
if #failedRequests > 0 then
|
||||
local failedRequestString = "Asset failed to be requested:"
|
||||
for _,failedString in pairs(failedRequests) do
|
||||
failedRequestString = failedRequestString.." "..failedString
|
||||
end
|
||||
error(failedRequestString)
|
||||
end
|
||||
end
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,28 @@
|
||||
-- Place v1.0.2
|
||||
|
||||
local assetUrl, fileExtension, x, y, baseUrl, universeId = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
if universeId ~= nil then
|
||||
pcall(function() game:SetUniverseId(universeId) end)
|
||||
end
|
||||
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("StarterGui").ShowDevelopmentGui = false
|
||||
|
||||
game:Load(assetUrl)
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("GameLoaded")
|
||||
|
||||
-- Do this after again loading the place file to ensure that these values aren't changed when the place file is loaded.
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("StarterGui").ShowDevelopmentGui = false
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] false)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Place Validation v1.0.0
|
||||
|
||||
assetUrl, baseUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("StarterGui").ShowDevelopmentGui = false
|
||||
|
||||
local success, message = pcall(function()
|
||||
game:Load(assetUrl)
|
||||
end)
|
||||
if not success then
|
||||
return message
|
||||
end
|
||||
|
||||
-- Do this after again loading the place file to ensure that these values aren't changed when the place file is loaded.
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("StarterGui").ShowDevelopmentGui = false
|
||||
|
||||
return true
|
||||
@@ -0,0 +1,39 @@
|
||||
-- Shirt v1.0.2
|
||||
|
||||
local assetUrl, fileExtension, x, y, baseUrl, mannequinId = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailScriptStarted")
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("UserInputService").MouseIconEnabled = false
|
||||
|
||||
local mannequin = game:GetObjects(baseUrl.. "/asset/?id=" .. tostring(mannequinId))[1]
|
||||
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
||||
mannequin.Parent = workspace
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("MannequinLoaded")
|
||||
|
||||
local shirt = game:GetObjects(assetUrl)[1]
|
||||
shirt.Parent = mannequin
|
||||
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ObjectsLoaded")
|
||||
|
||||
local result, requestedUrls = ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
ThumbnailGenerator:AddProfilingCheckpoint("ThumbnailGenerated")
|
||||
|
||||
local DFFlagThrowErrorWhenRequestedURLFailed = settings():GetFFlag("ThrowErrorWhenRequestedURLFailed")
|
||||
if DFFlagThrowErrorWhenRequestedURLFailed then
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
local failedRequests = ContentProvider:GetFailedRequests()
|
||||
if #failedRequests > 0 then
|
||||
local failedRequestString = "Asset failed to be requested:"
|
||||
for _,failedString in pairs(failedRequests) do
|
||||
failedRequestString = failedRequestString.." "..failedString
|
||||
end
|
||||
error(failedRequestString)
|
||||
end
|
||||
end
|
||||
|
||||
return result, requestedUrls
|
||||
@@ -0,0 +1,128 @@
|
||||
local BundleLoader = {}
|
||||
|
||||
local AssetService = game:GetService("AssetService")
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
|
||||
local MannequinUtility = require(ThumbnailGenerator:GetThumbnailModule("MannequinUtility"))
|
||||
local ScaleUtility = require(ThumbnailGenerator:GetThumbnailModule("ScaleUtility"))
|
||||
|
||||
local ARTIST_INTENT_FOLDER = "R15ArtistIntent"
|
||||
local ASSET_URL = "asset/?id="
|
||||
|
||||
local function constructAssetUrl(baseUrl, assetId)
|
||||
return baseUrl ..ASSET_URL.. tostring(assetId)
|
||||
end
|
||||
|
||||
function BundleLoader.LoadBundleAssets(baseUrl, bundleId)
|
||||
local bundleInfo = AssetService:GetBundleDetailsSync(bundleId)
|
||||
|
||||
local contentIdsList = {}
|
||||
for _, itemInfo in pairs(bundleInfo.Items) do
|
||||
if itemInfo.Type == "Asset" then
|
||||
local assetId = itemInfo.Id
|
||||
local assetUrl = constructAssetUrl(baseUrl, assetId)
|
||||
|
||||
contentIdsList[#contentIdsList + 1] = assetUrl
|
||||
end
|
||||
end
|
||||
|
||||
local objectsList = game:GetObjectsList(contentIdsList)
|
||||
|
||||
local results = {}
|
||||
for _, objects in pairs(objectsList) do
|
||||
local assetFolder = Instance.new("Folder")
|
||||
|
||||
for _, object in pairs(objects) do
|
||||
object.Parent = assetFolder
|
||||
end
|
||||
|
||||
results[#results + 1] = assetFolder
|
||||
end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
local function addPartsToCharacter(character, folder)
|
||||
for _, part in pairs(folder:GetChildren()) do
|
||||
local existingPart = character:FindFirstChild(part.Name)
|
||||
part.Parent = character
|
||||
|
||||
if existingPart then
|
||||
existingPart:Destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function addMeshHeadToCharacter(character, mesh)
|
||||
local head = character:FindFirstChild("Head")
|
||||
if not head then
|
||||
return
|
||||
end
|
||||
|
||||
local existingMesh = head:FindFirstChild("Mesh")
|
||||
if existingMesh then
|
||||
existingMesh:Destroy()
|
||||
end
|
||||
|
||||
for _, child in pairs(mesh:GetChildren()) do
|
||||
if child:IsA("Vector3Value") and string.find(child.Name, "Attachment") then
|
||||
local attachment = head:FindFirstChild(child.Name)
|
||||
|
||||
if not attachment then
|
||||
attachment = Instance.new("Attachment")
|
||||
end
|
||||
attachment.Name = child.Name
|
||||
attachment.Position = child.Value
|
||||
attachment.Parent = head
|
||||
end
|
||||
end
|
||||
|
||||
mesh.Parent = head
|
||||
end
|
||||
|
||||
local function addFaceToCharacter(character, face)
|
||||
local head = character:FindFirstChild("Head")
|
||||
if not head then
|
||||
return
|
||||
end
|
||||
|
||||
local existingFace = head:FindFirstChild("face")
|
||||
if existingFace then
|
||||
existingFace:Destroy()
|
||||
end
|
||||
|
||||
face.Parent = head
|
||||
end
|
||||
|
||||
function BundleLoader.LoadBundleCharacter(baseUrl, bundleId)
|
||||
local bundleAssets = BundleLoader.LoadBundleAssets(baseUrl, bundleId)
|
||||
local character = MannequinUtility.LoadR15Mannequin()
|
||||
|
||||
local scaleType = ScaleUtility.GetObjectsScaleType(bundleAssets)
|
||||
|
||||
for _, loadedAsset in pairs(bundleAssets) do
|
||||
for _, item in pairs(loadedAsset:GetChildren()) do
|
||||
if item:IsA("Folder") and item.Name == ARTIST_INTENT_FOLDER then
|
||||
addPartsToCharacter(character, item)
|
||||
elseif not item:IsA("Folder") then
|
||||
if item:IsA("DataModelMesh") then
|
||||
addMeshHeadToCharacter(character, item)
|
||||
elseif item:IsA("Decal") then
|
||||
addFaceToCharacter(character, item)
|
||||
else
|
||||
item.Parent = character
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
if humanoid then
|
||||
ScaleUtility.CreateProportionScaleValues(humanoid, scaleType)
|
||||
humanoid:BuildRigFromAttachments()
|
||||
end
|
||||
|
||||
return character
|
||||
end
|
||||
|
||||
return BundleLoader
|
||||
@@ -0,0 +1,75 @@
|
||||
-- Utility function for focusing on a selection of parts in a thumbnail
|
||||
|
||||
local FLOAT_MAX = math.huge
|
||||
|
||||
-- 10% tolerance for meshes that are bigger than the part which contains them
|
||||
local MESH_SIZE_TOLERANCE_MULTIPLIER = 1.1
|
||||
|
||||
local function addToBounds(cornerPosition, focusExtentsOut)
|
||||
focusExtentsOut["minx"] = math.min(focusExtentsOut["minx"], cornerPosition.x)
|
||||
focusExtentsOut["miny"] = math.min(focusExtentsOut["miny"], cornerPosition.y)
|
||||
focusExtentsOut["minz"] = math.min(focusExtentsOut["minz"], cornerPosition.z)
|
||||
focusExtentsOut["maxx"] = math.max(focusExtentsOut["maxx"], cornerPosition.x)
|
||||
focusExtentsOut["maxy"] = math.max(focusExtentsOut["maxy"], cornerPosition.y)
|
||||
focusExtentsOut["maxz"] = math.max(focusExtentsOut["maxz"], cornerPosition.z)
|
||||
end
|
||||
|
||||
local function addCornerToBounds(partCFrame, cornerSelect, halfPartSize, focusExtentsOut)
|
||||
local cornerPositionLocal = cornerSelect * halfPartSize
|
||||
local cornerPositionWorld = partCFrame * cornerPositionLocal
|
||||
addToBounds(cornerPositionWorld, focusExtentsOut)
|
||||
end
|
||||
|
||||
-- Adds a tolerance for meshes in parts
|
||||
local function getPartSizeBounds(part)
|
||||
local mesh = part:FindFirstChildWhichIsA("DataModelMesh")
|
||||
if not mesh then
|
||||
return part.Size
|
||||
end
|
||||
|
||||
return part.Size * MESH_SIZE_TOLERANCE_MULTIPLIER
|
||||
end
|
||||
|
||||
local CORNERS = {
|
||||
Vector3.new( 1, 1, 1),
|
||||
Vector3.new( 1, 1, -1),
|
||||
Vector3.new( 1, -1, 1),
|
||||
Vector3.new( 1, -1, -1),
|
||||
Vector3.new(-1, 1, 1),
|
||||
Vector3.new(-1, 1, -1),
|
||||
Vector3.new(-1, -1, 1),
|
||||
Vector3.new(-1, -1, -1),
|
||||
}
|
||||
|
||||
local function CreateExtentsMinMax(focusParts)
|
||||
local focusOnExtents = {
|
||||
minx = FLOAT_MAX,
|
||||
miny = FLOAT_MAX,
|
||||
minz = FLOAT_MAX,
|
||||
maxx = -FLOAT_MAX,
|
||||
maxy = -FLOAT_MAX,
|
||||
maxz = -FLOAT_MAX
|
||||
}
|
||||
|
||||
-- Expand focusOnExtents to bound all the parts in the focusParts table
|
||||
for _, focusPart in ipairs(focusParts) do
|
||||
if focusPart:IsA("BasePart") then
|
||||
local partSize = getPartSizeBounds(focusPart)
|
||||
local halfPartSize = partSize * 0.5
|
||||
local partCFrame = focusPart.CFrame
|
||||
|
||||
for _, corner in ipairs(CORNERS) do
|
||||
addCornerToBounds(partCFrame, corner, halfPartSize, focusOnExtents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local extentsMinMax = {
|
||||
Vector3.new(focusOnExtents["minx"], focusOnExtents["miny"], focusOnExtents["minz"]),
|
||||
Vector3.new(focusOnExtents["maxx"], focusOnExtents["maxy"], focusOnExtents["maxz"])
|
||||
}
|
||||
|
||||
return extentsMinMax
|
||||
end
|
||||
|
||||
return CreateExtentsMinMax
|
||||
@@ -0,0 +1,51 @@
|
||||
-- Utility module for functions related to loading mannequins for thumbnails
|
||||
|
||||
local MannequinUtility = {}
|
||||
|
||||
local InsertService = game:GetService("InsertService")
|
||||
|
||||
local R6_MANNEQUIN_CONTENT_ID = "rbxasset://models/Thumbnails/Mannequins/R6.rbxmx"
|
||||
local R15_MANNEQUIN_CONTENT_ID = "rbxasset://models/Thumbnails/Mannequins/R15.rbxm"
|
||||
local RTHRO_MANNEQUIN_CONTENT_ID = "rbxasset://models/Thumbnails/Mannequins/Rthro.rbxm"
|
||||
|
||||
local function loadMannequin(contentId)
|
||||
local mannequin = InsertService:LoadLocalAsset(contentId)
|
||||
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
||||
mannequin.Parent = workspace
|
||||
|
||||
return mannequin
|
||||
end
|
||||
|
||||
function MannequinUtility.LoadR15Mannequin()
|
||||
return loadMannequin(R15_MANNEQUIN_CONTENT_ID)
|
||||
end
|
||||
|
||||
function MannequinUtility.LoadR6Mannequin()
|
||||
return loadMannequin(R6_MANNEQUIN_CONTENT_ID)
|
||||
end
|
||||
|
||||
function MannequinUtility.LoadRthroMannequin()
|
||||
return loadMannequin(RTHRO_MANNEQUIN_CONTENT_ID)
|
||||
end
|
||||
|
||||
function MannequinUtility.LoadMannequinForScaleType(scaleType)
|
||||
if scaleType == "Classic" then
|
||||
return MannequinUtility.LoadR15Mannequin()
|
||||
else
|
||||
return MannequinUtility.LoadRthroMannequin()
|
||||
end
|
||||
end
|
||||
|
||||
function MannequinUtility.RotateMannequin(mannequin, cframe)
|
||||
local humanoidRootPart = mannequin:FindFirstChild("HumanoidRootPart")
|
||||
if not humanoidRootPart then
|
||||
return
|
||||
end
|
||||
|
||||
local rootRigAttachment = humanoidRootPart:FindFirstChild("RootRigAttachment")
|
||||
if rootRigAttachment then
|
||||
rootRigAttachment.CFrame = rootRigAttachment.CFrame * cframe
|
||||
end
|
||||
end
|
||||
|
||||
return MannequinUtility
|
||||
@@ -0,0 +1,63 @@
|
||||
-- Utility module for managing the scale of mannequins and items in thumbnails
|
||||
|
||||
local ScaleUtility = {}
|
||||
|
||||
local CLASSIC_SCALE = "Classic"
|
||||
local RTHRO_NORMAL = "ProportionsNormal"
|
||||
local RTHRO_SLENDER = "ProportionsSlender"
|
||||
|
||||
local function getPartScaleType(part)
|
||||
local value = part:FindFirstChild("AvatarPartScaleType")
|
||||
if value then
|
||||
return value.Value
|
||||
end
|
||||
|
||||
return CLASSIC_SCALE
|
||||
end
|
||||
|
||||
function ScaleUtility.GetScaleTypeForAccessory(accessory)
|
||||
local handle = accessory:FindFirstChild("Handle")
|
||||
if not handle then
|
||||
return CLASSIC_SCALE
|
||||
end
|
||||
|
||||
return getPartScaleType(handle)
|
||||
end
|
||||
|
||||
function ScaleUtility.GetObjectsScaleType(objects)
|
||||
for _, object in pairs(objects) do
|
||||
local partScaleType = object:FindFirstChild("AvatarPartScaleType", --[[ recursive = ]] true)
|
||||
if partScaleType then
|
||||
return partScaleType.Value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function getOrCreateScaleValue(humanoid, name, default)
|
||||
local scaleValue = humanoid:FindFirstChild(name)
|
||||
if scaleValue then
|
||||
return scaleValue
|
||||
end
|
||||
|
||||
scaleValue = Instance.new("NumberValue")
|
||||
scaleValue.Name = name
|
||||
scaleValue.Value = default
|
||||
scaleValue.Parent = humanoid
|
||||
|
||||
return scaleValue
|
||||
end
|
||||
|
||||
function ScaleUtility.CreateProportionScaleValues(humanoid, scaleType)
|
||||
local bodyTypeValue = getOrCreateScaleValue(humanoid, "BodyTypeScale", 0)
|
||||
local bodyProportionValue = getOrCreateScaleValue(humanoid, "BodyProportionScale", 0)
|
||||
|
||||
if scaleType == RTHRO_NORMAL then
|
||||
bodyTypeValue.Value = 1
|
||||
bodyProportionValue.Value = 0
|
||||
elseif scaleType == RTHRO_SLENDER then
|
||||
bodyTypeValue.Value = 1
|
||||
bodyProportionValue.Value = 1
|
||||
end
|
||||
end
|
||||
|
||||
return ScaleUtility
|
||||
Reference in New Issue
Block a user