Need help with mouse offset in a tool

Moho allows users to write new tools and plugins. Discuss scripting ideas and problems here.

Moderators: Víctor Paredes, Belgarath, slowtiger

Post Reply
User avatar
Lukas
Posts: 1297
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Need help with mouse offset in a tool

Post by Lukas »

This problem is driving me nuts, and I'm probably missing something obvious... I'm hoping anyone can take a look and point me in the right direction.

When you drag a bone in a FK chain, it will rotate and scale it's parentbone as if it were IK. And rotate the selected bone as if its angle was independent. As you see in the GIF, when the parentbone that's being adjusted has a parentbone with an angle that's not 0 degrees the mouse is offset and the tool is broken.

The tool also works for entire chains if you set 'local singleMode = true' to false and drag a bone in a chain of multiple parents that are not set to ignore IK. But the problem doesn't happen in that case because it uses Mike's IKSolver function.

See code below GIF. The tool is dependant on FO_Utilities. Here's the testfile from the GIF: testfile


Image

Code: Select all

-- **************************************************
-- Provide Moho with the name of this script object
-- **************************************************

ScriptName = "LK_FakeIK"

-- **************************************************
-- General information about this script
-- **************************************************

LK_FakeIK = {}

function LK_FakeIK:Name()
	return "LK_FakeIK"
end

function LK_FakeIK:Version()
	return "0.0"
end

function LK_FakeIK:Description()
	return "LK_FakeIK"
end

function LK_FakeIK:Creator()
	return "Lukas Krepel, Frame Order"
end

function LK_FakeIK:UILabel()
	return "LK_FakeIK"
end

function LK_FakeIK:ColorizeIcon()
	return true
end


-- **************************************************
-- The guts of this script
-- **************************************************

function LK_FakeIK:IsEnabled(moho)
	return true
end

function LK_FakeIK:IsRelevant(moho)
		local skel = moho:Skeleton()
		if (skel == nil) then
			return false
		end
		return true
end


function LK_FakeIK:DoLayout(moho, layout)
	FO_Utilities:Divider(layout, "FakeIK", true)
end

function LK_FakeIK:UpdateWidgets(moho)
	-- *
end

function LK_FakeIK:OnMouseDown(moho, mouseEvent)
	self.bone = nil
	self.startBoneVec = nil
	self.mousePickedID = nil
	self.angleBones = {}

	moho.document:PrepMultiUndo()
	moho.document:SetDirty()

	self.skel = moho:Skeleton()
	-- *
	local layer = moho.layer -- * TODO Maybe use rig layer instead?
	self.mousePickedID = mouseEvent.view:PickBone(mouseEvent.pt, mouseEvent.vec, layer, true) -- * Pick exact.
	if self.mousePickedID == -1 then
		self.mousePickedID = mouseEvent.view:PickBone(mouseEvent.pt, mouseEvent.vec, layer, false) -- * Don't pick exact.
	end
	
	self.bone = self.skel:Bone(self.mousePickedID)
	local parentBone = self.skel:Bone(self.bone.fParent)
	if parentBone == nil or parentBone.fIgnoredByIK then
		return
	end

	skel:SelectNone()
	self.bone.fSelected = true

	self.startBoneVec = LM.Vector2:new_local()
	self.startBoneVec:Set(parentBone.fLength,0)

	self.matrix = LM.Matrix:new_local()
	self.matrix:Set(parentBone.fMovedMatrix)
	self.matrix:Transform(self.startBoneVec)

	while parentBone ~= nil  and not parentBone.fIgnoredByIK do
		table.insert(self.angleBones, parentBone)
		local singleMode = true------TODO
		if singleMode then
			parentBone = nil
		else
			parentBone = self.skel:Bone(parentBone.fParent)
		end
	end
	self.startAngle = self.bone.fAngle
	for i = 1, #self.angleBones do
		local angleBone = self.angleBones[i]
		angleBone.fTempAngle = angleBone.fAngle
		self.startAngle = self.startAngle + angleBone.fAngle
	end
end

function LK_FakeIK:OnMouseMoved(moho, mouseEvent)
	local parentBone = self.skel:Bone(self.bone.fParent)
	if parentBone == nil or parentBone.fIgnoredByIK then
		return
	end
	if self.bone ~= nil and moho.frame ~= 0 then
		bone = self.bone
		local offset = mouseEvent.vec - mouseEvent.startVec
		local parent = nil
		if (bone.fParent >= 0) then
			parent = self.skel:Bone(bone.fParent)
		end
		local fakeTargetVec = LM.Vector2:new_local()
		fakeTargetVec = self.startBoneVec + offset
		-- * SINGLE BONE:
		if #self.angleBones == 1 then
			local parentBone = self.angleBones[1]
			local parentBonePos = LM.Vector2:new_local()
			self.matrix:Transform(parentBonePos)
			local distance = FO_Utilities:Distance(moho, parentBonePos, fakeTargetVec)
			local newScale = distance/parentBone.fLength
			parentBone.fAnimScale:SetValue(moho.frame, newScale)
			-- * Calculate angle:
			local newAngle = math.atan2(fakeTargetVec.y - parentBonePos.y, fakeTargetVec.x - parentBonePos.x)
			-- * Set angle:
			local oldAngle = parentBone.fTempAngle
			-- * TODO get shortest route from oldAngle to newAngle and adjust newAngle accordingly, so the bones don't spin around.
			parentBone.fAnimAngle:SetValue(moho.frame, newAngle)
		else
		-- * BONE CHAIN:
			if parent ~= nil then
				self.skel:IKAngleSolver(bone.fParent, fakeTargetVec, 1, false, true) -- * M_Skeleton:IKAngleSolver(boneID, target, iterMultiplier, allowTwoBoneShortcut, allowBoneStretching)
				for i = 1, #self.angleBones do
					local parentBone = self.angleBones[i]
					parentBone.fAnimAngle:SetValue(moho.frame, parentBone.fAngle)
				end
			end
		end

		local newAngle = self.startAngle
		for i = 1, #self.angleBones do
			newAngle = newAngle - self.angleBones[i].fAnimAngle.value
		end
		bone.fAnimAngle:SetValue(moho.frame, newAngle)
	end
	MOHO.Redraw()
	moho.layer:UpdateCurFrame()
end

function LK_FakeIK:OnMouseUp(moho, mouseEvent)
	if moho.frame == 0 then
		return
	end
	FO_Utilities:PaintKeys(moho)
	moho:UpdateUI()
end

function LK_FakeIK:YoungestChild(skel, bone) -- * TODO UGLY FUNCTION
	for stupid = 0, 10 do
		for i = 0, self.skel:CountBones() - 1 do
			local b = skel:Bone(i)
			local parent = skel:Bone(b.fParent)
			if bone == parent then
				bone = b
			end
		end
	end
	return bone
end
User avatar
synthsin75
Posts: 9979
Joined: Mon Jan 14, 2008 11:20 pm
Location: Oklahoma
Contact:

Re: Need help with mouse offset in a tool

Post by synthsin75 »

Tried using on the keyed frame and immediately got an error for line 126, invalid operand.
You need self.skel on line 86.
User avatar
hayasidist
Posts: 3526
Joined: Wed Feb 16, 2011 8:12 pm
Location: Kent, England

Re: Need help with mouse offset in a tool

Post by hayasidist »

hmm -- bones -- yeah -- I had a lot of issues with bones when I was writing my "follow" script (gasp -- 2014!) -- I seemed to be forever playing with bone matrices.

http://www.mediafire.com/file/k0d5tdydz ... 0.zip/file [edit: link replaced to point at the most recent version]

There might be some nuggets in there to help -- I'll try to take another look at your script next week.

more about it and a link to the latest version (V520) here http://lostmarble.com/forum/viewtopic.php?t=25384 along with supporting files. Sadly, the kelleytown links are dead.
User avatar
Lukas
Posts: 1297
Joined: Fri Apr 09, 2010 9:00 am
Location: Netherlands
Contact:

Re: Need help with mouse offset in a tool

Post by Lukas »

synthsin75 wrote: Sat Jul 09, 2022 7:07 pm Tried using on the keyed frame and immediately got an error for line 126, invalid operand.
You need self.skel on line 86.
Ah, thanks, there was another tool that had set the skeleton globally.
hayasidist wrote: Sat Jul 09, 2022 8:58 pmThere might be some nuggets in there to help
Ha! Thank god for annotated code. Your "-- ** bone fPos is relative to parent's position and rotation unless no parent, in which case it's absolute" comment made something click. I had to rotate a vector before calculating the angle:

Code: Select all

-- * Calculate angle:
			local v2 = LM.Vector2:new_local()
			v2:Set(fakeTargetVec.x - parentBonePos.x, fakeTargetVec.y - parentBonePos.y)
			--
			local grandParentBone = self.skel:Bone(parentBone.fParent)
			if grandParentBone ~= nil then
				v2:Rotate(-grandParentBone.fAngle)
			end
			--
			newAngle = math.atan2(v2.y, v2.x)
One step closer to what I need! Thanks!

Now I need to figure out what angle to rotate it with, because using the grapdparentbone obviously doesn't work for a longer bone chain...
User avatar
hayasidist
Posts: 3526
Joined: Wed Feb 16, 2011 8:12 pm
Location: Kent, England

Re: Need help with mouse offset in a tool

Post by hayasidist »

Lukas wrote: Mon Jul 11, 2022 2:33 pm One step closer to what I need! Thanks!
Now I need to figure out what angle to rotate it with, because using the grapdparentbone obviously doesn't work for a longer bone chain...
It's been a while but I think that's what the bone transform matrices are all about.

In local function translateBones(mb, fCb)

Code: Select all

			boneVec:Set(bone.fPos)
--			HS_Test_harness:diagnosePrint (thisUUT, bone:Name(), "position wrt parent", boneVec.x, boneVec.y)

-- stuff about selecting / deselecting

				if (bone.fParent >= 0) then

					if (t == 0) then
						parent.fRestMatrix:Transform(boneVec)
					else
						parent.fMovedMatrix:Transform(boneVec)
					end
--					HS_Test_harness:diagnosePrint (thisUUT, "transformed wrt", parent:Name(), boneVec.x, boneVec.y)
				end
which I think does all the hard work of getting boneVec to be an absolute position (i.e. as though it had no parent chain)

On rotation, the code is not much help because I used the IK solver (in function moveBones(mk, fCk))... and it doesn't look as though the bone matrices will work on fAngle ... but you can probably get the bone's absolute angle ... and (delving deep into my archives I found this:

Code: Select all

	angle = bone.fAngle

	pAngle = 0
	while parent >= 0 do
--		local l = moho:Skeleton():Bone(parent).fAngle
--		self:diagnosePrint (thisUUT, "parent is", parent, "angle is", l, "=", math.deg(l))

		pAngle = pAngle + moho:Skeleton():Bone(parent).fAngle
		parent = moho:Skeleton():Bone(parent).fParent
--		self:diagnosePrint (thisUUT, "new parent is", parent)
	end

	local cum
	i, cum = math.modf ((pAngle + angle)/(2*math.pi)) -- lose excess 2pi
	cum = cum*2*math.pi

--	self:diagnosePrint (thisUUT, "cumulative angle is", math.deg(pAngle), "child angle real is", math.deg(cum))

(which I then used to calculate the bone tip position (here just in case you want it..)

Code: Select all

	length = bone.fLength
	scale = bone.fScale
	tip.x = scale * length * math.cos(cum) + vec.x
	tip.y = scale * length * math.sin(cum) + vec.y
Post Reply