Help with this bone scale code

I am trying to scale a mesh with bones.
If I use a scale of (.7,.7,.7) I would get bones scaled to (.7,.7,7) and that is correct
but if I wanted to do (.7,.5,.7) I get bones scaled to (.7,.7,.7) which is incorrect. It doesnt seem to respect all axis while scaling, and I don’t know enough about the code or how it works to determine how to get the desired results.

I appreciate any help, thanks.

function ScaleMesh(scale,root)
	for _,part in pairs(root:GetDescendants()) do
		if part:IsA("Bone") then
			local bone = part
			-- move bone local translation to part space, scale, then move back to bone parent space

			local parentCFrame = bone.Parent.CFrame
			local boneParentToPartCFrame = root.CFrame:Inverse() * parentCFrame
			boneParentToPartCFrame = boneParentToPartCFrame - boneParentToPartCFrame.Position --rotation only
			local pivotOffsetInPartSpace = (boneParentToPartCFrame * bone.Position) * scale
			bone.Position = boneParentToPartCFrame:inverse() * pivotOffsetInPartSpace

		elseif part:IsA("Attachment") then
			part.Position = part.Position * scale
		end
	end
	root.Size = root.Size * scale
end

You can get the X, Y and Z components of CFrame values like this:

CFrame.X
CFrame.Y
CFrame.Z

Sorry to be a noob, but where would I apply them in the original code? I don’t quite understand the workings of the function, its not my original work.

I don’t really get why the function is getting the cframe to each bone’s parent. I would think it logical to just scale the mesh first, then get the bone’s crfame in relation to the mesh, then scale the bone, then put it back to its original scope. Maybe I just don’t quite understand how the cframes of bone hierarchies work. Since they are structured in a tree, does each bone cframe live in the scope of its parent bone?

can you just do bone.Position = bone.Position * scale

Funny you ask that, I am trying that right now, typed up a recursive function, that goes through the children in order of their parenting and does just this, I will update if that works.

Ok that didn’t work. Not really sure why…

so you are trying to make the positions of each bone “scale” right, can i see what you have written so far

I have this in a server script, as a child of the model holding the meshpart

function ScaleMesh(mesh,scale)
	local function DoScale(part,scale)
		if part:IsA("MeshPart") then
			part.Size = part.Size*scale
		elseif part:IsA("Bone") then
			part.Position = part.Position * scale 
		end
		for _,i in pairs(part:GetChildren()) do
			DoScale(i,scale)
		end
	end
	DoScale(mesh,scale)
end

ScaleMesh(script.Parent,Vector3.new(1,.5,1))

that looks quite messy

function ScaleMesh(mesh,scale)
     mesh.Size = mesh.Size * scale
     for _,child in pairs(mesh:GetDescendants()) do
          if child:IsA("Bone") then
               child.Position = child.Position * scale
          end
     end
end

this should have the same effect

Even with the less messy code, it just doesn’t seem to scale correctly.
If I use the scale tool in studio, it scales the mesh and the bones perfectly.
How does the scale tool do this? Is there any code for it?

i have just ran my code , it works perfectly fine…

Here is an image of my results.
On the left is the original
Middle is what result I am getting
Right is scaling with the scale tool in studio, so its what it should look like
image
Edit: This is using a scale of 1,.5,1

weird… it worked for me

The bones in my mesh are organized as such…
image

Doesn’t work, perhaps you need to redefine it.

Just as I was about to fall asleep, I realized, when scaling the bones need to change ‘look direction’ based on the new dimensions of the mesh, when scaling in a non uniform way.

I will mess with some code today, unless someone else knows this already, but just for simplicity, I was thinking of this in bed in a 2d perspective.
If a mesh, is 1,2 and it contains a bone that is looking 1,1 if we ‘squash’ the mesh to be 1,1, we need to find the ‘ratio’? of the new mesh compared to the old one so … r=(1/1,1/2) then we multiply this by the bone’s look direction, so look = 1r.X, 1r.Y … which would be 1*(1/1), 1*(1/2) which gives us a bone lookvector of 1,.5, or 2,1. From what I can imagine in my head, this would be a bone who’s look vector is basically squished, appropriately with the mesh scale. If anyone wants to provide any math on this, or other input I would appreciate it.

So apparently for this to work, the bone’s cframe needs to be translated to the mesh’s space, so that scaling the cframe’s lookvector by x or y or z, will be in line with the mesh, and not whatever local rotation the bone is in.

So, unless I am mistaken, each bone’s position is in reference to its parent position, such as the first bone, bone1, is for example… 0,1,0 (relative to the mesh) then bone2 might be 0,.1,0 (relative to bone1) and the next 0,.1,0 (relative to bone2)

So to correctly put these in the mesh’s space and rotate them correctly, I am thinking each cframe needs to be translated to world coordinates, then translated to part (the mesh) coordinates
Then I need to create the new cframe based Cframe.LookAt, using the cframes position scaled, and lookdireciton multiplied by the ratio of the mesh’s scale. Then put back into world space, then back into the bone’s parent’s space?

Does this sound correct?

I was wrong in thinking the cframe’s rotation has anything to do with scaling the bones. Apparently the cframe does not look towards the parent. And when scaling the cframe rotations do not ever change. So it’s mainly a matter of scaling position in relation to the mesh’s object space, and applying those positions in order.
However, I have to give God the credit on this one. I thought I had it all worked out in my head, but then when I went to type it up, I typed up the tree traversal backwards, which worked. So had God not had me type it totally backwards, I would still be trying other things.

Here is the code, I cheat a little with .WorldCFrame, and .WorldPosition, since bones are derived from Attachments.

function ScaleMesh(mesh,scale)
	local nodeTree = {}
	local function BuildTree(tree,mesh,inst,scale)
		if inst:IsA("Bone") or inst:IsA("Attachment") then
			local cf = mesh.CFrame:ToObjectSpace(inst.WorldCFrame)
			cf = mesh.CFrame:ToWorldSpace((cf - cf.Position) + (cf.Position * scale))
			table.insert(tree,{Pos = cf.Position,Inst = inst})		
		end
		for _,i in pairs(inst:GetChildren()) do
			BuildTree(tree,mesh,i,scale)
		end
	end
	BuildTree(nodeTree,mesh,mesh,scale)
	for n = 1,#nodeTree do
		nodeTree[n].Inst.WorldPosition = nodeTree[n].Pos
	end
	mesh.Size = mesh.Size * scale
end
ScaleMesh(MyMesh,Vector3.new(1,.5,1))

If anyone knows of a cleaner or better way to do this, please let me know. But as for now, this works for what I need. Thank you to everyone who replied and gave input. I appreciate it.
image

@SelDraken Sorry there was a bug in the original script.
It seems you have a working script, but I don’t see the code for “BuildTree”

Here is the corrected version of the original script:

function ScaleMesh(scaleVector3, meshPart)
	for _,descendant in pairs(meshPart:GetDescendants()) do
		if descendant:IsA("Bone") then
			local bone = descendant
			-- move bone local translation to part space, scale xyz in part space, then move back to bone parent space
			local parentCFrame
			if (bone.Parent:IsA("Bone")) then -- parent can be either the MeshPart or another Bone
				parentCFrame = bone.Parent.WorldCFrame
			else
				parentCFrame = bone.Parent.CFrame
			end
			local parentInPartCFrame = meshPart.CFrame:Inverse() * parentCFrame
			local parentInPartRotationCFrame = parentInPartCFrame - parentInPartCFrame.Position --rotation only
			local pivotOffsetInPartSpace = parentInPartRotationCFrame * bone.Position 
			local scaledPivotOffsetInPartSpace = pivotOffsetInPartSpace * scaleVector3
			local partToParentRotationCFrame = parentInPartRotationCFrame:inverse()  
			bone.Position = partToParentRotationCFrame * scaledPivotOffsetInPartSpace
		elseif descendant:IsA("Attachment") then
			-- attachments are always directly parented to the MeshPart
			local attachment = descendant
			attachment.Position = attachment.Position * scaleVector3
		end
	end
	meshPart.Size = meshPart.Size * scaleVector3
end

ScaleMesh(Vector3.new(2,1,2), workspace.S1.Lola_Geo)
3 Likes