The key to understand how a Moho curve transformed into a regular cubic Bezier curve, is the way Moho calculates these internal points:
On the curve the point P1 has two neighbour points, P0 and P2.
P1 has two internal control points on each side, one for the P0-P1 curve and one for the P1-P2. To calculate the position of these points, get the vector between the P0 and P2 neighbours, normalize it (rescale its length to 1) then multiply it with the P1 point's Curvature value.
N= Norm(P2-P0) * Curvature(P1)
For the P0-P1 curve, calculate the length of the P1-P0 vector, and multiply the normalized "direction" vector with it:
CP01= P1 - N * Mag(P1 - P0)
And for the P1-P2 curve, calculate the length of the P2-P1 vector, and multiply the
normalized "direction" vector with it:
CP12= P1 + N * Mag(P2 - P1)
At the ends of an open curve, where only one neighbour exists for the endpoint, use the endpoint itself instead of the missing neighbour, ie. P0= P1 or P2= P1.
The sample below is a tool's DrawMe() callback function. You can copy it into any of the existing tools to see the internal control point handles and the calculated curves. It calculates the things in a bit "optimized" way, but the calculation basically the same as above.
Code: Select all
function FA_TryMe:DrawMe(moho, view)
local mesh = moho:Mesh()
if (mesh == nil) then return end
local gfx= view:Graphics()
local matrix= LM.Matrix:new_local()
moho.layer:GetFullTransform(moho.frame, matrix, moho.document)
gfx:Push()
gfx:ApplyMatrix(matrix)
gfx:SetColor(255, 0, 0)
local cveN= mesh:CountCurves()
for i= 0, cveN - 1 do
local cve= mesh:Curve(i)
local ptN= cve:CountPoints()
if (ptN > 1) then
local oldPt= cve:Point(0)
local oldCurvature= cve:GetCurvature(0, moho.frame)
--cp0..cp3 are the cubic Bezier curve points
local cp0x= oldPt.fPos.x
local cp0y= oldPt.fPos.y
local cp1x,cp1y,cp2x,cp2y,cp3x,cp3y, len
local newPt= cve:Point(1)
local newCurvature= cve:GetCurvature(1, moho.frame)
--setting the neighbours of the first point
local prevPt= oldPt
local handle= LM.Vector2:new_local()
local vec= LM.Vector2:new_local()
if (cve.fClosed) then
prevPt= cve:Point(ptN - 1)
end
--first point's parameters
handle:Set(newPt.fPos.x - prevPt.fPos.x, newPt.fPos.y - prevPt.fPos.y)
handle:NormMe()
vec:Set(newPt.fPos.x - oldPt.fPos.x, newPt.fPos.y - oldPt.fPos.y)
local oldLen= vec:Mag()
gfx:DrawFatMarker(cp0x,cp0y, 4)
--CountSegments() is ptN-1 for open curves...
for j= 2, cve:CountSegments() + 1 do
--calculating the inner control points
cp3x= newPt.fPos.x
cp3y= newPt.fPos.y
--distance to the previous neighbour: control point #1
len= oldLen * oldCurvature
cp1x= cp0x + handle.x * len
cp1y= cp0y + handle.y * len
len= oldLen * newCurvature
--administration and getting the next point
prevPt= oldPt
oldPt= newPt
oldCurvature= newCurvature
if (j < ptN) then
newPt= cve:Point(j)
newCurvature= cve:GetCurvature(j, moho.frame)
--the next neighbour vector
vec:Set(newPt.fPos.x - oldPt.fPos.x, newPt.fPos.y - oldPt.fPos.y)
oldLen= vec:Mag()
elseif (cve.fClosed) then
--closed curve, last two segments
newPt= cve:Point(j - ptN)
newCurvature= cve:GetCurvature(j - ptN, moho.frame)
end
--open curve, last segment: keep the original newPt for the handle vector
--handle of the endpoint: normalized vector between the neighbours
handle:Set(newPt.fPos.x - prevPt.fPos.x, newPt.fPos.y - prevPt.fPos.y)
handle:NormMe()
--distance to the next neigbour: control point #2
cp2x= cp3x - handle.x * len
cp2y= cp3y - handle.y * len
--drawing the control handles
gfx:DrawLine(cp0x,cp0y,cp1x,cp1y)
gfx:DrawLine(cp2x,cp2y,cp3x,cp3y)
--drawing the Bezier curve, based on http://en.wikipedia.org/wiki/B%C3%A9zier_curve
local cx = 3.0 * (cp1x - cp0x)
local bx = 3.0 * (cp2x - cp1x) - cx
local ax = cp3x - cp0x - cx - bx
local cy = 3.0 * (cp1y - cp0y)
local by = 3.0 * (cp2y - cp1y) - cy
local ay = cp3y - cp0y - cy - by
local oldX = nil
local oldY
for t= 0, 1, 0.05 do
local tSquared = t * t
local tCubed = tSquared * t
local resultX = (ax * tCubed) + (bx * tSquared) + (cx * t) + cp0x
local resultY = (ay * tCubed) + (by * tSquared) + (cy * t) + cp0y
if (oldX) then
gfx:DrawLine(oldX,oldY,resultX,resultY)
end
oldX= resultX
oldY= resultY
end
cp0x= cp3x
cp0y= cp3y
end
gfx:DrawFatMarker(cp0x,cp0y, 4)
end
end
gfx:Pop()
end