-- ************************************************** -- Provide Moho with the name of this script object -- ************************************************** ScriptName = "AE_WaveInbetweener" -- ************************************************** -- General information about this script -- ************************************************** AE_WaveInbetweener = {} function AE_WaveInbetweener:Name() return 'Wave Inbetweener' end function AE_WaveInbetweener:Version() return '1.0' end function AE_WaveInbetweener:UILabel() return 'Wave Inbetweener' end function AE_WaveInbetweener:Creator() return 'Alexandra Evseeva' end function AE_WaveInbetweener:Description() return '' end function AE_WaveInbetweener:ColorizeIcon() return true end -- ************************************************** -- Is Relevant / Is Enabled -- ************************************************** function AE_WaveInbetweener:IsRelevant(moho) return true end function AE_WaveInbetweener:IsEnabled(moho) return true end AE_WaveInbetweener.fixStart = true -- ************************************************** -- The guts of this script -- ************************************************** function AE_WaveInbetweener:GetLongestChainChild(moho, skel, bone) if skel:CountBoneChildren(bone, true) == 0 then return nil, 0 end local foundChild, chainLength = nil, -1 for b = 0, skel:CountBones() - 1 do local nextChild = skel:Bone(b) if nextChild.fParent == skel:BoneID(bone) then local longestChild, longestLength = self:GetLongestChainChild(moho, skel, nextChild) if longestLength > chainLength then foundChild = nextChild chainLength = longestLength end end end AE_Utilities:Log(self.log, 'returning child '.. (foundChild and foundChild:Name() or 'nil')..' with '.. (chainLength+1)..' length chain\n') return foundChild, chainLength + 1 end function AE_WaveInbetweener:DebugExtremums(moho, startMatrix, finMatrix) --log found extrems for debug purpoces for i = 0, #self.extremums do extr = self.extremums[i] if extr then local startPos = LM.Vector2:new_local() local finPos = LM.Vector2:new_local() if extr.start then startPos:Set(extr.start) if startMatrix then startMatrix:Transform(startPos) end end if extr.fin then finPos:Set(extr.fin) if finMatrix then finMatrix:Transform(finPos) end end AE_Utilities:Log(self.log, 'extremum no. ', i, ': start ', startPos.x, ', ', startPos.y, ' end: ', finPos.x, ', ', finPos.y, ' dir: ', extr.dir, ' boneIDs: start ', extr.startBoneID, ' end ', extr.finBoneID, '\n') end end end function AE_WaveInbetweener:Run(moho) moho.document:SetDirty() moho.document:PrepUndo(nil) --self.log = "P:/test/moho.log" AE_Utilities:Log(self.log, "starting\n") -- get selected bone and find its chain, find start and finish frames local skel = moho:Skeleton() if not skel or moho:CountSelectedBones()~=1 then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "Select bone layer and one root bone") end self.boneChain = {} local rootBone = skel:Bone(skel:SelectedBoneID()) local nextBone = rootBone while nextBone do table.insert(self.boneChain, nextBone) AE_Utilities:Log(self.log, "ready to call for "..nextBone:Name().."\n") nextBone = self:GetLongestChainChild(moho, skel, nextBone) AE_Utilities:Log(self.log, "now nextBone is "..(nextBone and nextBone:Name() or 'nil').."\n") end if #self.boneChain < 4 then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "Need chain of at least 4 bones, only " .. #self.boneChain .. " found") end local finBone = self.boneChain[#self.boneChain] local startID = rootBone.fAnimAngle:GetClosestKeyID(moho.layerFrame) local startFrame = rootBone.fAnimAngle:GetKeyWhen(startID) local finFrame = rootBone.fAnimAngle:GetKeyWhen(startID + 1) if startFrame < 1 or not finFrame or finFrame <= startFrame then return LM.GUI.Alert(LM.GUI.ALERT_WARNING, "Set current frame between two keys of selected bone rotation") end -- create line matrix local startA = AE_Utilities:GetGlobalBonePos(moho, skel, rootBone, startFrame) local startB = LM.Vector2:new_local() startB:Set(finBone.fLength, 0) local startBoneMatrix = AE_Utilities:GetGlobalBoneMatrix(moho, skel, finBone, startFrame) startBoneMatrix:Transform(startB) local finA = AE_Utilities:GetGlobalBonePos(moho, skel, rootBone, finFrame) local finB = LM.Vector2:new_local() finB:Set(finBone.fLength, 0) local finBoneMatrix = AE_Utilities:GetGlobalBoneMatrix(moho, skel, finBone, finFrame) finBoneMatrix:Transform(finB) local startMatrix = LM.Matrix:new_local() startMatrix:Identity() startMatrix:Translate(startA.x, startA.y, 0) local startAB = startB - startA startMatrix:Rotate(LM.Z_AXIS, math.atan2(startAB.y, startAB.x)) startMatrix:Scale(startAB:Mag(), 1, 1) local inverseStartMatrix = LM.Matrix:new_local() inverseStartMatrix:Set(startMatrix) inverseStartMatrix:Invert() local finMatrix = LM.Matrix:new_local() finMatrix:Identity() finMatrix:Translate(finA.x, finA.y, 0) local finAB = finB - finA finMatrix:Rotate(LM.Z_AXIS, math.atan2(finAB.y, finAB.x)) finMatrix:Scale(finAB:Mag(), 1, 1) local inverseFinMatrix = LM.Matrix:new_local() inverseFinMatrix:Set(finMatrix) inverseFinMatrix:Invert() AE_Utilities:Log(self.log, "matrices created\n") -- for frame start: get each chain projection, find max abs Y ones (from start to end) and put them into table self.extremums = {} local prePos = LM.Vector2:new_local() local curDir = 0 for i, bone in pairs(self.boneChain) do if bone == rootBone then local nextPos = LM.Vector2:new_local() table.insert(self.extremums, {start=nextPos, dir=curDir, startBoneID=1}) else local pos = AE_Utilities:GetGlobalBonePos(moho, skel, bone, startFrame) inverseStartMatrix:Transform(pos) local distY = pos.y - prePos.y if curDir ~= 0 then if (distY < 0 and curDir > 0) or (distY > 0 and curDir < 0) then local nextPos = LM.Vector2:new_local() nextPos:Set(prePos) table.insert(self.extremums, {start=nextPos, dir=curDir, startBoneID=(i-1)}) end end prePos:Set(pos) curDir = (distY > 0) and 1 or -1 if self.extremums[1].dir == 0 then self.extremums[1].dir = - curDir end if i == #self.boneChain then local nextPos = LM.Vector2:new_local() nextPos:Set(1,0) table.insert(self.extremums, {start=nextPos, dir=curDir, startBoneID=i+1}) end end end self.numExtremums = #self.extremums AE_Utilities:Log(self.log, "start extrems found\n") -- for frame end: find max abs Y projections and put them to the same table (every finish must be greater x then start) prePos:Set(0,0) curDir = 0 local lastFoundIndex = -1 for i, bone in pairs(self.boneChain) do if bone == rootBone then local nextPos = LM.Vector2:new_local() self.extremums[0] = {fin=nextPos, dir=curDir, finBoneID=1} else local pos = AE_Utilities:GetGlobalBonePos(moho, skel, bone, finFrame) inverseFinMatrix:Transform(pos) if i == #self.boneChain then pos:Set(1,0) end local distY = pos.y - prePos.y if curDir ~= 0 then if (distY < 0 and curDir > 0) or (distY > 0 and curDir < 0) then -- find in self.extremums a suitable member: the last with x < prePos.x and dir==curDir local foundIndex = 0 for j=0, #self.extremums do local extr = self.extremums[j] if extr and extr.start then if extr.start.x <= prePos.x and extr.dir == curDir then foundIndex = j end if extr.start.x > prePos.x then break end end end local nextPos = LM.Vector2:new_local() nextPos:Set(prePos) AE_Utilities:Log(self.log, "found index ", foundIndex, " for ", bone:Name(), "\n") if foundIndex == 0 and not self.extremums[0] then self.extremums[0] = {fin=nextPos, dir=curDir, startBoneID=1, finBoneID=i} else if lastFoundIndex > 0 and (foundIndex - lastFoundIndex) > 1 then foundIndex = lastFoundIndex + 1 end while self.extremums[foundIndex].fin do foundIndex = foundIndex + 1 end AE_Utilities:Log(self.log, "now found index is ", foundIndex, "\n") self.extremums[foundIndex].fin = nextPos self.extremums[foundIndex].finBoneID = i end lastFoundIndex = foundIndex end end if i == #self.boneChain then local nextPos = LM.Vector2:new_local() nextPos:Set(1,0) for j=0, #self.extremums do local extr = self.extremums[j] if extr then if not extr.fin then extr.fin = nextPos extr.finBoneID = i break end end end end prePos:Set(pos) curDir = (distY > 0) and 1 or -1 if self.extremums[0].dir == 0 then self.extremums[0].dir = - curDir end end end AE_Utilities:Log(self.log, "fin extrems found\n") self:DebugExtremums(moho) AE_Utilities:Log(self.log, "\n") -- set extra start and end if needed if self.extremums[0] then self.numExtremums = self.numExtremums + 1 if not self.extremums[0].start then local extraStart = LM.Vector2:new_local() extraStart:Set(-self.extremums[2].start.x, - self.extremums[2].start.y) self.extremums[0].start = extraStart self.extremums[0].startBoneID = - self.extremums[1].startBoneID end end if not self.extremums[#self.extremums].fin then local extraFin = LM.Vector2:new_local() extraFin:Set( 2 - self.extremums[#self.extremums-2].fin.x, - self.extremums[#self.extremums - 2].fin.y) self.extremums[#self.extremums].fin = extraFin self.extremums[#self.extremums].finBoneID = #self.boneChain + #self.boneChain - self.extremums[#self.extremums - 1].finBoneID end AE_Utilities:Log(self.log, "extra extrems found\n") self:DebugExtremums(moho) AE_Utilities:Log(self.log, "\n") self:DebugExtremums(moho, startMatrix, finMatrix) --TODO: create mesh and a curve for start extremums --local preview = MOHO.MeshPreview(200, 200) local currentLayer = moho.layer local newLayer = moho:CreateNewLayer(MOHO.LT_VECTOR, true) newLayer:SetTimingOffset(currentLayer:TotalTimingOffset()) newLayer = moho:LayerAsVector(newLayer) local previewMesh = newLayer:Mesh() local p = 0 for i = 0, #self.extremums do extr = self.extremums[i] if extr then local startPos = LM.Vector2:new_local() startPos:Set(extr.start) startMatrix:Transform(startPos) if p == 0 then previewMesh:AddLonePoint(startPos, 0) else previewMesh:AppendPoint(startPos,0) end p = p + 1 end end local curve = previewMesh:Curve(0) local interp = MOHO.InterpSetting:new_local() interp:Reset() interp.interpMode = MOHO.INTERP_LINEAR p = 0 for i = 0, #self.extremums do extr = self.extremums[i] if extr then local point = curve:Point(p) point.fAnimPos:AddKey(startFrame) point.fAnimPos:SetKeyInterp(startFrame, interp) local finPos = LM.Vector2:new_local() finPos:Set(extr.fin) finMatrix:Transform(finPos) point.fAnimPos:SetValue(finFrame, finPos) p = p + 1 end end --TODO: Find bezier curvature size for each extremum -- find segment borders for start and finish frames (part of curve filled with real bones) local startSegmentA, startSegmentB, finSegmentA, finSegmentB = 1, #self.extremums, 0, #self.extremums - 1 for j = 0, #self.extremums do local extr = self.extremums[j] if extr then local i = j if not self.extremums[0] then i = j - 1 end if extr.start.x == 0 then startSegmentA = i end if extr.start.x == 1 then startSegmentB = i end if extr.fin.x == 0 then finSegmentA = i end if extr.fin.x == 1 then finSegmentB = i end end end AE_Utilities:Log(self.log, "found segments from ", startSegmentA, " to ", startSegmentB, " and from ", finSegmentA, " to ", finSegmentB, " \n") local returnFrame = moho.frame local boneAngles = {} for i,bone in pairs(self.boneChain) do table.insert(boneAngles,{}) end for f = startFrame, finFrame do moho:SetCurFrame(f) -- get a part of curve local k = (f - startFrame)/(finFrame - startFrame) local segmentStart = startSegmentA + k * (finSegmentA - startSegmentA) local segmentFin = startSegmentB + k * (finSegmentB - startSegmentB) -- get start and finish of curve segment local percentStart, fake1, fake2, percentFin = 0, 0, 0, 1 percentStart, fake1 = curve:GetSegmentRange(math.floor(segmentStart), percentStart, fake1 ) percentStart = percentStart + (fake1 - percentStart) * (segmentStart - math.floor(segmentStart)) local endSegment = math.floor(segmentFin) local endSegmentCrop = segmentFin - math.floor(segmentFin) if endSegment >= curve:CountSegments() then endSegment = curve:CountSegments() - 1 if endSegmentCrop == 0 then endSegmentCrop = 1 end end fake2, percentFin = curve:GetSegmentRange(endSegment, fake2, percentFin ) percentFin = fake2 + (percentFin - fake2) * endSegmentCrop -- get total length of bones (multiplied with scales) local boneTotalLength = 0 for i, bone in pairs(self.boneChain) do boneTotalLength = boneTotalLength + bone.fLength * bone.fScale end local boneCurrentLength = 0 for i, bone in pairs(self.boneChain) do -- find start end end positions for every bone local boneLength = bone.fScale * bone.fLength local startBonePercent = boneCurrentLength local finBonePercent = boneCurrentLength + (boneLength/boneTotalLength) local startPos = curve:GetPercentLocation(percentStart + startBonePercent * (percentFin - percentStart)) local finPos = curve:GetPercentLocation(percentStart + finBonePercent * (percentFin - percentStart)) -- find global angle local boneVector = finPos - startPos local globalAngle = math.atan2(boneVector.y, boneVector.x) -- convert global angle to bone local angle, then apply new value and update bone matrix local pos, angle = AE_Utilities:GetGlobalBonePRS(moho, skel, bone, moho.layerFrame) local angleDif = globalAngle - angle angleDif = AE_Utilities:CropAngle(angleDif) local newAngle = bone.fAnimAngle:GetValue(moho.layerFrame) + angleDif if f ~= startFrame and f ~= finFrame then bone.fAnimAngle:SetValue(moho.layerFrame, newAngle ) skel:UpdateBoneMatrix(skel:BoneID(bone)) end boneCurrentLength = finBonePercent boneAngles[i][f]= newAngle end end --TODO: cleanup rotation keyframes (try various methods) --TODO: and also remove strong changes near the first and last frames (have to calculate (but not apply) angles for first and last frames for it) -- now turned off because does not work as expected -- the only valid way to reduce keys is to set interpolation of extremly valued keys to follow existing motion curve for i, bone in pairs(self.boneChain) do local keys2delete = {} for f = startFrame + 1, finFrame - 1 do local average = (boneAngles[i][f-1] + boneAngles[i][f+1])/2 if math.abs(boneAngles[i][f] - average) < 0.001 then table.insert(keys2delete, f) end end for j, f in pairs(keys2delete) do --bone.fAnimAngle:DeleteKey(f) end end self.extremums = nil self.boneChain = nil moho:DeleteLayer(newLayer) moho:SetSelLayer(currentLayer) moho:SetCurFrame(returnFrame) AE_Utilities:Log(self.log, "now finish\n") end
Wave Inbetweener
Listed
Author: A.Evseeva
View Script
Script type: Button/Menu
Uploaded: Apr 19 2022, 00:03
Calculates frames between two keys of bone chain wavely motion
Selet a root bone of the chain and place current frame between two (rotation) keys -- start and end frames of motion segment. Keys must exist both in start and end frames for every bone of the chain.
This script, and all other scripts on this site are distributed as free software under the GNU General Public License 3.0 or later.
Downloads count: 659
Wave Inbetweener
Listed
Author: A.Evseeva
View Script
Script type: Button/Menu
Uploaded: Apr 19 2022, 00:03
Calculates frames between two keys of bone chain wavely motion
Selet a root bone of the chain and place current frame between two (rotation) keys -- start and end frames of motion segment. Keys must exist both in start and end frames for every bone of the chain.
This script, and all other scripts on this site are distributed as free software under the GNU General Public License 3.0 or later.
Downloads count: 659