AE_Utilities = {}

AE_Utilities.constructionTime = true

function AE_Utilities:Version()
	return "1.33"
end

function AE_Utilities:GetBezierValue(curve, pointNum, frame, prePoint, getModifiedPoints)
    local curvature = curve:GetCurvature(pointNum, frame)
    local weight = curve:GetWeight(pointNum, frame, prePoint)
    local offset = curve:GetOffset(pointNum, frame, prePoint)
    local pointToRange = function(p)
      if curve.fClosed then
         if p < 0 then p = curve:CountPoints()-1 end
         if p >= curve:CountPoints() then p = 0  end
      else
         if p < 0 then p = 0 end
         if p >= curve:CountPoints() then p = curve:CountPoints()-1 end    
      end
      return p    
    end
    local postPointNum = pointToRange(pointNum + 1)
    local prePointNum = pointToRange(pointNum - 1)
    local secondPointNum = postPointNum
    if prePoint then secondPointNum = prePointNum end
    if secondPointNum == pointNum then 
      if getModifiedPoints then 
        return curve:Point(pointNum).fPos
      else 
        return curve:Point(pointNum).fAnimPos:GetValue(frame)
      end
    end
    local N = curve:Point(postPointNum).fAnimPos:GetValue(frame) - curve:Point(prePointNum).fAnimPos:GetValue(frame)
    if getModifiedPoints then N = curve:Point(postPointNum).fPos - curve:Point(prePointNum).fPos end
    N:NormMe()
    if prePoint then N = N*-1 end
    local L = (curve:Point(secondPointNum).fAnimPos:GetValue(frame) - curve:Point(pointNum).fAnimPos:GetValue(frame)):Mag()
    if getModifiedPoints then L = (curve:Point(secondPointNum).fPos - curve:Point(pointNum).fPos):Mag() end
    L = L * curvature * weight
    local vec = N * L
    vec:Rotate(offset)
    if getModifiedPoints then 
      return curve:Point(pointNum).fPos + vec
    else
      return curve:Point(pointNum).fAnimPos:GetValue(frame) + vec
    end
end

function AE_Utilities:GetAngleBetween(vec1, vec2)
  	v1 = vec1:Norm()
		v2 = vec2:Norm()
		local angle = v1:Dot(v2)
		if (angle > 1.0) then
			angle = 0
		elseif (angle < -1.0) then
			angle = math.pi
		else
			angle = math.acos(angle)
		end
		local norm = v1:Cross(v2)
		if (norm.z < 0.0) then
			angle = -angle
		end
    return angle
end

function AE_Utilities:SumActionInfluences(moho, frame, derivedChannel, layer)
   local sum = 0
   local defaultValue = derivedChannel:GetValue(0)
   local parentSceleton = self:FindControllingSkeleton(moho, layer)
   if not parentSceleton then return 0 end
   
   for actID = 0, derivedChannel:CountActions()-1 do
       local actName = derivedChannel:ActionName(actID)
       local actChannel = moho:ChannelAsAnimVal(derivedChannel:Action(actID))
       local boneName = actName
       if string.sub(boneName, string.len(boneName)-1) == " 2" then boneName = string.sub(boneName, 1, string.len(boneName)-2) end
       local smartBone = parentSceleton:BoneByName(boneName)
       if smartBone and actName ~= layer:CurrentAction() then
          local boneTrack = smartBone.fAnimAngle:ActionByName(actName)
          if boneTrack then
            local curSmartboneValue = smartBone.fAnimAngle:GetValue(frame) -- bad for nested smartbones
			curSmartboneValue = smartBone.fAngle -- better for nested smartbones
            local smartBoneFrame, smartBoneSubFrame = self:FindSmartBoneFrame(curSmartboneValue, moho:ChannelAsAnimVal(boneTrack))
			if(smartBoneFrame>0 or smartBoneSubFrame ~= smartBoneFrame) then
              local nextVal = actChannel:GetValue(smartBoneFrame) - defaultValue
			  if smartBoneSubFrame == 0 and smartBoneSubFrame ~= smartBoneFrame then
				nextVal = (actChannel:GetValue(1) - defaultValue)*smartBoneSubFrame  
			  end
              sum = sum + nextVal 
            end      
          end
       end
   end
   return sum
end

function AE_Utilities:FindSmartBoneFrame(angle, smartBoneTrack, ignoreZeroFrame)
  --ignoreZeroFrame and startFrame added for KuzmaProduction; Remove if work incorrect

  local foundFrame = -1
  local foundSubFrame = -1
  local defAngle = smartBoneTrack:GetValue(0)
  if not ignoreZeroFrame and defAngle == angle then 
    return 0,0
  end
  local startFrame = 1
  if smartBoneTrack:Duration()>1 then startFrame = 2 end
  for f=startFrame, smartBoneTrack:Duration() do
    local curVal = smartBoneTrack:GetValue(f)
    local preVal = smartBoneTrack:GetValue(f-1)
    if ((curVal-angle)*(preVal-angle)) <= 0 then
       foundFrame = f-1
       foundSubFrame = f-1+(angle-preVal)/(curVal-preVal)
	   if foundSubFrame == f then foundFrame = f end
		--print("between ", preVal*180/math.pi, " and ", curVal*180/math.pi)
    end
    if f==smartBoneTrack:Duration() and foundFrame == -1 and math.abs(curVal-angle)<math.abs(preVal-angle) then 
      foundFrame = f 
      foundSubFrame = f
    end 
  end
  if smartBoneTrack:Duration() == 1 then return foundFrame, foundSubFrame end
  return foundFrame, foundSubFrame
end

function AE_Utilities:FindControllingSkeleton(moho, layer)
	local boneLayer = nil	
	if layer:LayerType() == MOHO.LT_BONE then
		boneLayer = moho:LayerAsBone(layer)
		return boneLayer:Skeleton(), boneLayer
	end
	local parent = layer

	while parent:Parent() do
		parent = parent:Parent()
		if parent:LayerType() == MOHO.LT_BONE then
			local boneLayer = moho:LayerAsBone(parent)
			if boneLayer and boneLayer:Skeleton():CountBones()>0 then
				return boneLayer:Skeleton(), boneLayer
			end
		end
	end
	if boneLayer then return boneLayer:Skeleton(), boneLayer end
	return nil
end

-----------------------------------------------------------------------------------
-------------------------- Added in version 1.1 -----------------------------------
-----------------------------------------------------------------------------------

function AE_Utilities:GetDerivedChannel(moho, chan, onlyMath)
  local chanType = chan:ChannelType()
  if chanType == MOHO.CHANNEL_VAL then return moho:ChannelAsAnimVal(chan), MOHO.CHANNEL_VAL 
  elseif chanType == MOHO.CHANNEL_VEC2 then return moho:ChannelAsAnimVec2(chan), MOHO.CHANNEL_VEC2
  elseif chanType == MOHO.CHANNEL_VEC3 then return moho:ChannelAsAnimVec3(chan), MOHO.CHANNEL_VEC3 
  elseif onlyMath then return nil   
  elseif chanType == MOHO.CHANNEL_COLOR then return moho:ChannelAsAnimColor(chan), MOHO.CHANNEL_COLOR   
  elseif chanType == MOHO.CHANNEL_BOOL then return moho:ChannelAsAnimBool(chan), MOHO.CHANNEL_BOOL 
  elseif chanType == MOHO.CHANNEL_STRING then return moho:ChannelAsAnimString(chan), MOHO.CHANNEL_STRING
  end
  return nil
end

function AE_Utilities:IsAncestor(parentLayer, childLayer)
	while childLayer do 
		if childLayer == parentLayer then return true end	
		childLayer = childLayer:Parent()
	end
	return false
end

function AE_Utilities:IterateLayerSubChannels(moho, layer)
	local GetCombinedChannel = function(layer)
		local maxChannel = layer:CountChannels()-1
		local chInfo = MOHO.MohoLayerChannel:new_local()
		for i=0, maxChannel do
			layer:GetChannelInfo(i, chInfo)
			if chInfo.channelID == CHANNEL_LAYER_ALL then
				return chInfo.subChannelCount, i
			end
		end
		return nil
	end
	local subChannelCount, combChannelNo = GetCombinedChannel(layer)
	if not subChannelCount then return end
	local iterateFn = function(condition, counter)
		counter = counter + 1
		if counter < subChannelCount then
			local channel = layer:Channel(combChannelNo, counter, moho.document)
			
			local chInfo = MOHO.MohoLayerChannel:new_local()
			layer:GetChannelInfo(counter, chInfo)
			 
			return counter, channel, chInfo
		end
	end
	return iterateFn, subChannelCount, -1
end

function AE_Utilities:IterateAllLayers(moho, from, to)
	local iterateLayersFn = function(condition, counter)
		counter = counter + 1
		if not condition or counter <= condition then
			local layer = moho.document:LayerByAbsoluteID(counter)
			if layer then
				return counter, layer
			end
		end
	end
	return iterateLayersFn, to, (from and from-1 or -1) 
end

function AE_Utilities:IterateMohoList(obj, countFn, getFn)
	local iteratorFn = function(condition, counter)
		counter = counter + 1
		if counter < countFn(obj) then
			local entity = getFn(obj,counter)
			return counter, entity
		end	
	end
	return iteratorFn, nil, -1
end

function AE_Utilities:MohoListToTable(obj, countFn, getFn)
	local collection = {}
	for i, entity in self:IterateMohoList(obj, countFn, getFn) do
		table.insert(collection, entity)
	end
	return collection
end

function AE_Utilities:IterateAllChannels(moho, layer)
	local chInfo = MOHO.MohoLayerChannel:new_local()
	local channelCounter = 0
	local maxChannel = layer:CountChannels()-6 --exlude 5 last channels: all and camera tracks
	layer:GetChannelInfo(0, chInfo)
	
	
	local iterateFn = function(condition, subCounter)
		subCounter = subCounter + 1
		if subCounter >= chInfo.subChannelCount then			
			repeat   
				channelCounter = channelCounter + 1
				if channelCounter <= maxChannel then
					layer:GetChannelInfo(channelCounter, chInfo)
					subCounter = 0
				end
			until channelCounter > maxChannel or chInfo.subChannelCount > 0	
		end
		if channelCounter <= maxChannel and not chInfo.selectionBased then			
			local channel = layer:Channel(channelCounter, subCounter, moho.document)
			return subCounter, channelCounter, channel, chInfo
		end
	end
	
	return iterateFn, nil, -1

end

-----------------------------------------------------------------------------------
-------------------------- Added in version 1.11 ----------------------------------
-----------------------------------------------------------------------------------

function AE_Utilities:IsEqualValues(channel, val1, val2, accuracy)
	accuracy = accuracy or 0.0000001
	local chanType = channel:ChannelType()
	if chanType == MOHO.CHANNEL_VAL then return (math.abs(val1 - val2) < accuracy) 
	elseif chanType == MOHO.CHANNEL_VEC2 then  return (((val1-val2):Mag()) < accuracy)
	elseif chanType == MOHO.CHANNEL_VEC3 then return (((val1-val2):Mag()) < accuracy)
	elseif chanType == MOHO.CHANNEL_COLOR then return val1.r == val2.r and val1.g == val2.g and val1.b == val2.b and val1.a == val2.a 
	elseif chanType == MOHO.CHANNEL_BOOL then return (val1 == val2) 
	elseif chanType == MOHO.CHANNEL_STRING then return (val1 == val2)
	end
	return false
end

-----------------------------------------------------------------------------------
-------------------------- Added in version 1.15 ----------------------------------
-----------------------------------------------------------------------------------

function AE_Utilities:Matrix2transform(theMatrix, printOutput, returnOutput)
	local translation = LM.Vector3:new_local()
	theMatrix:Transform(translation)
	local v3rz = LM.Vector3:new_local()
	v3rz:Set(1,0,0)
	theMatrix:Transform(v3rz)
	v3rz = v3rz - translation
	local rotationZ = math.atan2(v3rz.y, v3rz.x)
	
	local flip = false
	local v3rz2 = LM.Vector3:new_local()
	v3rz2:Set(0,1,0)
	theMatrix:Transform(v3rz2)
	v3rz2 = v3rz2 - translation
	local alterZrotation = math.atan2(-v3rz2.x, v3rz2.y)
	if math.abs(alterZrotation - rotationZ) > 0.01 then
		flip = true
		if math.abs(alterZrotation) < math.abs(rotationZ) then 
			rotationZ = alterZrotation
		end
	end

	local v3sx = LM.Vector3:new_local()	
	v3sx:Set(1,0,0)
	theMatrix:Transform(v3sx)
	local v3sy = LM.Vector3:new_local()	
	v3sy:Set(0,1,0)	
	theMatrix:Transform(v3sy)
	local v3sz = LM.Vector3:new_local()	
	v3sz:Set(0,0,1)	
	theMatrix:Transform(v3sz)
	local scaleX = (v3sx - translation):Mag()
	local scaleY = (v3sy - translation):Mag()
	local scaleZ = (v3sz - translation):Mag()
	local scale = LM.Vector3:new_local()
	scale:Set(scaleX, scaleY, scaleZ)
	local pString, rString, sString = nil, nil, nil
	if returnOutput or printOutput then
		pString = "("..translation.x..", "..translation.y..", "..translation.z..")\n"
		rString = "degr: "..rotationZ*180/math.pi.."\n"
		sString = "("..scale.x..", "..scale.y..", "..scale.z..")\n"
		fString = tostring(flip)
	end
	if printOutput then
		print(pString)
		print(rString)
		print(sString)
		print(fString)
	end
	return translation, rotationZ, scale, flip, pString, rString, sString, fString
end

function AE_Utilities:StringifyValue(value, chanType)
	  if chanType == MOHO.CHANNEL_VAL then return tostring(value)
	  elseif chanType == MOHO.CHANNEL_VEC2 then return tostring(value.x).." "..tostring(value.y)
	  elseif chanType == MOHO.CHANNEL_VEC3 then return tostring(value.x).." "..tostring(value.y).." "..tostring(value.z) 
	  elseif chanType == MOHO.CHANNEL_COLOR then return tostring(value.r).." "..tostring(value.g).." "..tostring(value.b).." "..tostring(value.a)  
	  elseif chanType == MOHO.CHANNEL_BOOL then return tostring(value) 
	  elseif chanType == MOHO.CHANNEL_STRING then return value
	  end
	  return "unknown type"
end 

-----------------------------------------------------------------------------------
-------------------------- Added in version 1.18 ----------------------------------
-----------------------------------------------------------------------------------

function AE_Utilities:GetChannelValue(moho, channel, frame, actionName)
	local value = channel:GetValue(frame)
	if actionName then 
		if channel:ActionByName(actionName) then 
			local derivedChannel = self:GetDerivedChannel(moho, channel:ActionByName(actionName))
			value = derivedChannel:GetValue(frame)
		else
			value = channel:GetValue(0)
		end
	end
	return value
end

function AE_Utilities:GetBoneMatrix(moho, bone, frame, actionName)
	local matrix = LM.Matrix:new_local()

	matrix:Identity()


	local pos = self:GetChannelValue(moho, bone.fAnimPos, frame, actionName)
	local angle = self:GetChannelValue(moho, bone.fAnimAngle, frame, actionName)
	local scale = self:GetChannelValue(moho, bone.fAnimScale, frame, actionName)
	local flipEnd = self:GetChannelValue(moho, bone.fFlipH, frame, actionName)
	local flipSide = self:GetChannelValue(moho, bone.fFlipV, frame, actionName)

	matrix:Translate(pos.x, pos.y, 0)
	matrix:Rotate(LM.Z_AXIS, angle)
	
	
	local x = scale
	if bone.fLength == 0 then y = x else y = 1 end
	if flipEnd then x = -1 * x end
	if flipSide then y = -1 * y end
	
	matrix:Scale(x,y,1)
	
	return matrix
end 

function AE_Utilities:GetGlobalBonePos(moho, skel, bone, frame, actionName)
	local vector = LM.Vector2:new_local()
	local nextBone = bone
	repeat 
		local prevBone = nextBone
		local matrix = self:GetBoneMatrix(moho, nextBone, frame, actionName)
		matrix:Transform(vector)
		if nextBone.fParent > -1 then nextBone = skel:Bone(nextBone.fParent) end
	until nextBone == prevBone
	return vector
end

function AE_Utilities:GetGlobalBonePRS(moho, skel, bone, frame, actionName)

	local pos = self:GetChannelValue(moho, bone.fAnimPos, frame, actionName)
	local angle = self:GetChannelValue(moho, bone.fAnimAngle, frame, actionName)
	local scale = self:GetChannelValue(moho, bone.fAnimScale, frame, actionName)
	local flipEnd = self:GetChannelValue(moho, bone.fFlipH, frame, actionName)
	local flipSide = self:GetChannelValue(moho, bone.fFlipV, frame, actionName)	
	
	local parentBone = skel:Bone(bone.fParent)
	if not parentBone then  
		return pos, angle, scale, flipEnd, flipSide
	end
	
	local parPos, parAngle, parScale, parFlipEnd, parFlipSide = self:GetGlobalBonePRS(moho, skel, parentBone, frame, actionName)
	
	local resultAngle = parAngle + angle
	if parFlipSide and not parFlipEnd then resultAngle = parAngle - angle
	elseif not parFlipSide and parFlipEnd then resultAngle = parAngle + math.pi - angle
	elseif parFlipSide and parFlipEnd then resultAngle = parAngle + math.pi + angle
	end
	
	local x = parScale
	local y = 1 
	if parentBone.fLength == 0 then y = parScale end
	local resultPos = LM.Vector2:new_local()
	resultPos:Set(pos.x * x, pos.y * y)
	
	local rotateMatrix = LM.Matrix:new_local()
	rotateMatrix:Identity()
	rotateMatrix:Rotate(LM.Z_AXIS, parAngle)
	rotateMatrix:Transform(resultPos)
	
	resultPos = resultPos + parPos
	
	return resultPos, resultAngle, scale, flipEnd, flipSide
end

function AE_Utilities:GetGlobalBoneMatrix(moho, skel, bone, frame, actionName)
	local prevMatrix = LM.Matrix:new_local()
	prevMatrix:Identity()
	local nextBone = bone
	repeat 
		local prevBone = nextBone
		local matrix = self:GetBoneMatrix(moho, nextBone, frame, actionName)
		matrix:Multiply(prevMatrix)
		prevMatrix:Set(matrix)
		if nextBone.fParent > -1 then nextBone = skel:Bone(nextBone.fParent) end
	until nextBone == prevBone
	return prevMatrix
end

function AE_Utilities:GetLayerByUUID(moho, uuid)
	for i, layer in self:IterateAllLayers(moho) do
		if layer:UUID() == uuid then return layer end
	end
	return nil
end

-- added at 1.19

function AE_Utilities:GetGlobalLayerMatrix(moho, layer, frame) -- actions not processed yet
	local prevMatrix = LM.Matrix:new_local()
	prevMatrix:Identity()
	local nextLayer = layer
	repeat
		local prevLayer = nextLayer
		local matrix = LM.Matrix:new_local()
		nextLayer:GetLayerTransform(frame, matrix, moho.document)
		matrix:Multiply(prevMatrix)
		prevMatrix:Set(matrix)
		if nextLayer:Parent() then nextLayer = nextLayer:Parent() end
	until nextLayer == prevLayer
	local cameraMatrix = LM.Matrix:new_local()
	moho.document:GetCameraMatrix(frame, cameraMatrix)
	cameraMatrix:Invert()
	cameraMatrix:Multiply(prevMatrix)
	return cameraMatrix
end

function AE_Utilities:GetSecondLayer(moho)
	for i, layer in AE_Utilities:IterateAllLayers(moho) do
		if layer:SecondarySelection() and layer ~= moho.layer then
			return layer
		end
	end
	return nil
end

function AE_Utilities:GetSecondLayers(moho)
	local secondLayers = {}
	for i, layer in AE_Utilities:IterateAllLayers(moho) do
		if layer:SecondarySelection() and layer ~= moho.layer then
			table.insert(secondLayers, layer)
		end
	end
	return secondLayers
end

function AE_Utilities:GetShapeOrderChannel(moho, layer)
	local maxChannel = layer:CountChannels()-1
	local chInfo = MOHO.MohoLayerChannel:new_local()
	local orderChannel = nil
	for i=0, maxChannel do
		layer:GetChannelInfo(i, chInfo)
		if chInfo.channelID == CHANNEL_SHAPE_ORDER then
			return moho:ChannelAsAnimString(layer:Channel(i, 0, moho.document))
		end
	end
end

function AE_Utilities:GetLayerRelativePath(moho, fromLayer, toLayer)
	if fromLayer == toLayer then return fromLayer:Name() end
	local path = ""
	local commonParent = fromLayer
	while commonParent and not self:IsAncestor(commonParent, toLayer) do
		commonParent = commonParent:Parent()
		path = path .. "../"
	end
	local secondPath = toLayer:Name()
	local nextParent = toLayer:Parent()
	while nextParent and commonParent ~= nextParent do
		secondPath = nextParent:Name() .. "/" .. secondPath
		nextParent = nextParent:Parent()
	end
	return (path .. secondPath)
end

function AE_Utilities:GetLayerFromRelativePath(moho, fromLayer, path)
	local foundLayer = fromLayer
	local restPath = path
	while string.sub(restPath, 1, 3) == "../" do
		if not foundLayer then return nil end
		foundLayer = foundLayer:Parent()
		restPath = string.sub(restPath, 4)
	end
	local nextSlash = string.find(restPath, "/")
	local nextName = string.sub(restPath, 1, (nextSlash and nextSlash - 1 or -1))
	if not foundLayer then 
		foundLayer = moho.document:LayerByName(nextName)
	else 
		foundLayer = moho:LayerAsGroup(foundLayer)
		if (foundLayer) then foundLayer = foundLayer:LayerByName(nextName) end
	end
	if not foundLayer then return nil end
	restPath = string.sub(restPath, #nextName + 2)	
	while #restPath > 0 do
		nextSlash = string.find(restPath, "/")
		nextName = string.sub(restPath, 1, (nextSlash and nextSlash - 1 or -1))
		foundLayer = moho:LayerAsGroup(foundLayer)
		if (foundLayer) then foundLayer = foundLayer:LayerByName(nextName) end
		if not foundLayer then return nil end
		restPath = string.sub(restPath, #nextName + 2)
	end
	return foundLayer
end

function AE_Utilities:GetOffsetChannel(moho, layer, curve, curvePoint, prePoint)
	local mesh = moho:LayerAsVector(layer)
	if not mesh then return nil end
	mesh = mesh:Mesh()
	local ptID = mesh:PointID(curve:Point(curvePoint))
	local crvID = mesh:CurveID(curve)
	local channelOffset = 3
	if not prePoint then channelOffset = 4 end
	local subNo = 0
	for p = 0, (ptID - 1) do
		subNo = subNo + mesh:Point(p):CountCurves()*5
	end
	for c = 0, mesh:Point(ptID):CountCurves() - 1 do
		local nextCurve = mesh:Point(ptID):Curve(c)
		if nextCurve == curve then break
		else subNo = subNo + 5
		end
	end
	subNo = subNo + channelOffset
	for subID, ID, channel, chInfo in self:IterateAllChannels(moho, layer) do
		if subID == subNo and chInfo.channelID == CHANNEL_CURVE then
			return moho:ChannelAsAnimVal(channel), subID, ID, chInfo
		end
	end
end

AE_Utilities
Unlisted

Script type: Utility

Uploaded: Jul 14 2020, 07:42

Last modified: Apr 09 2021, 09:38

Common methods necessary for some of my other scripts
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: 309