Edit 2023:
We now have the Model.Scale property! As well as Model:ScaleTo(scale) and Model:GetScale().
I’m trying to scale trees to give more foliage variation in my game. The code I’m using is shown below. It works fine. But is there a better way? Is this the best way?
Considering I’m trying to run this function on 10,000 items as quickly as possible, I’m trying to figure out if there’s any way to speed this up.
local function ScaleModel(model, scale)
local primary = model.PrimaryPart
local primaryCf = primary.CFrame
for _,v in pairs(model:GetDescendants()) do
if (v:IsA("BasePart")) then
v.Size = (v.Size * scale)
if (v ~= primary) then
v.CFrame = (primaryCf + (primaryCf:inverse() * v.Position * scale))
end
end
end
return model
end
The models I’m scaling usually have no more than 2 or 3 parts, and are all MeshParts.
You think you can round scale to 0.05 increments? If you do that, then you can memoize copies of models with a given scale, and then clone those memoized copies instead.
That’s a good idea. Keeping 0.05 increments might be hard, but I could do something similar with the scalar value. Just lock the scalar to a certain step and keep a lookup table of foliage generated at that given scale.
By rounding my scale to the nearest 0.1 and caching the results, it has improved performance by +30%. Higher memory usage of course, but nothing crazy.
Server is generating about 13,000 items in 1.4 seconds consistently.
Before. Each foliage prefab has a different ‘transformation’ function that applies the object to a given position (i.e. rocks and trees need to be placed/rotated differently). All transformations are done before parenting. Final parenting of the whole folder is last, and thus allows server to replicate all items to the client however it sees best.
CFrame operations can be batched, which appears to speed things up around 2x for me. I’ve found that grouping operations into batches of ~1000 offers the greatest speedup on my machine. To multiply a tuple of Vector3s by another CFrame, just do cf:PointToWorldSpace(…).
Also, you can store the scale and the object space transformation as a single CFrame, since CFrames by themselves aren’t required to be orthogonal:
local mPts,mSizes
local cf = primaryCf:inverse()
local ScaleMatrix = CFrame.new(0,0,0,scale,0,0,0,scale,0,0,0,scale)
cf=cf*ScaleMatrix
mPts = {cf:PointToWorldSpace(unpack(mPts))}
mSizes = {ScaleMatrix:VectorToWorldSpace(unpack(mSizes))}
Where mPts and mSizes are defined appropriately.
Edit: On second thought, this might not meet your needs if you’re only transforming 2-3 parts per model. It’s worth a try, though.
I feel like GetChildren would be faster than GetDescendants, although if it is, probably not enough to impact performance.
Maybe only scale half of the models? If variation is what you want, do you really need to scale every single one of them?
Also, you could maybe make a variable for primaryCf:inverse() as well. Means it would be called/calculated 2 to 3 times less often. Optionally use pointToObjectSpace like suremark suggested (not sure which would be faster).
And finally, do you really need to return the model? I mean, if you’re passing it in the function, I imagine you wouldn’t need another reference to it.
It was on the old wiki I discovered it last week and have been trying to think of some useful applications for it that aren’t already handled by the ROBLOX engine, but you’d be the first to give me such an idea.
By the way, it works for all of the ToWorldSpace and ToObjectSpace methods, i.e. those plus the ones for points and vectors.
All the parts from my model have scaled, but their positions appear to remain the same. As a result, there are gaps between all my parts (I am scaling down). I do have welds connecting some parts together since it is a moving object. Solutions would be appreciated.
local function scaleModel(model, scale)
local PrimaryPart = model.PrimaryPart
local PrimaryPartCFrame = model:GetPrimaryPartCFrame()
--Destroy welds
for _,object in pairs(model:GetDescendants()) do
if object:IsA('BasePart') then
for _,object in pairs(object:GetDescendants()) do
if object:IsA('Weld') or object:IsA('ManualWeld') or object:IsA('WeldConstraint') then
pcall(function()
object.Part0.Anchored = true
object.Part1.Anchored = true
end)
object:Destroy()
end
end
end
end
--Scale BaseParts
for _,object in pairs(model:GetDescendants()) do
if object:IsA('BasePart') then
object.Size = object.Size*scale
local distance = (object.Position - PrimaryPartCFrame.p)
local rotation = (object.CFrame - object.Position)
object.CFrame = (CFrame.new(PrimaryPartCFrame.p + distance*scale) * rotation)
end
end
end
I asked @KoxuVBC in a private message for a solution where the parts of the model are not anchored. He then gave me this advise:
This is my implementation, which works. I want to share it with you. Maybe even someone has a suggestion for improvement.
local function scaleModel(model, scale)
local PrimaryPart = model.PrimaryPart
local PrimaryPartCFrame = model:GetPrimaryPartCFrame()
--Destroy welds
for _,object in pairs(model:GetDescendants()) do
if object:IsA('Weld') or object:IsA('ManualWeld') or object:IsA('WeldConstraint') then
object:Destroy()
end
end
for _,object in pairs(model:GetDescendants()) do
if object:IsA('BasePart') then
for _,object2 in pairs(model:GetDescendants()) do
if object2:IsA('BasePart') then
if object ~= object2 then
local constraint = Instance.new("WeldConstraint")
constraint.Part0 = object
constraint.Part1 = object2
constraint.Parent = object
end
end
end
end
end
--Scale BaseParts
for _,object in pairs(model:GetDescendants()) do
if object:IsA('BasePart') then
object.Size = object.Size*scale
local distance = (object.Position - PrimaryPartCFrame.p)
local rotation = (object.CFrame - object.Position)
object.CFrame = (CFrame.new(PrimaryPartCFrame.p + distance*scale) * rotation)
end
end