Skip to content

Commit

Permalink
rel curvature computations
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfink committed Dec 14, 2024
1 parent d0f8384 commit 4e8d518
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 6 deletions.
8 changes: 4 additions & 4 deletions renderer/pyscript-offline/public/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
firstsg = relaxer.DifferentialGlyph.from_glyph(firstsg)
firstsg.x = -2.
firstsg.y = 0.
firstsg.angle = -math.pi/2#math.pi/6#-math.pi/2
firstsg.angle = math.pi/6#-math.pi/2
text.add_glyph(firstsg)

cat = dictionary.glyph_by_id("cat")
Expand All @@ -31,6 +31,6 @@

canvas.render(text)

document.body.append(f"Non-constant-velocity penalty: {str(relaxer.velocity_penalty(rel))}. ")
cat.dx = 1.
document.body.append(f"Derivative: {str(relaxer.deriv_velocity_penalty(rel))}. ")
document.body.append(f"Max curvature: {str(relaxer.curvature_penalty(rel))}. ")
firstsg.dangle = 1.
document.body.append(f"Derivative: {str(relaxer.deriv_curvature_penalty(rel))}. ")
111 changes: 109 additions & 2 deletions renderer/pyscript-offline/public/relaxer.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,13 @@ def dpoly(rel):
bezier = svgpathtools.CubicBezier(rel.bp0.dz, rel.bp0.handledz, rel.bp1.handledz, rel.bp1.dz)
return bezier.poly()



# The particular penalties below are not necessarily the ones we'll want to
# go with in the end.

def velocity_penalty(rel):
"Penalty score for this rel not going at constant velocity."
"Penalty for this rel not going at constant velocity."
bezier = rel.svgpathtools_bezier()
dd = bezier.poly().deriv().deriv()
ddx = [float(dd[i].real) for i in range(0,2)]
Expand All @@ -108,7 +110,7 @@ def velocity_penalty(rel):
ddx[0]*ddx[0]+ddy[0]*ddy[0]

def deriv_velocity_penalty(rel):
"""Derivative of penalty score for this rel not going at constant velocity.
"""Derivative of penalty for this rel not going at constant velocity.
The glyphs which rel is attached to should be DifferentialGlyph objects."""
bezier = rel.svgpathtools_bezier()
Expand All @@ -124,3 +126,108 @@ def deriv_velocity_penalty(rel):
ddx[0]*du_ddx[1]+ddy[0]*du_ddy[1] +\
du_ddx[0]*ddx[1]+du_ddy[0]*ddy[1] +\
2*(ddx[0]*du_ddx[0]+ddy[0]*du_ddy[0])

def curvature_penalty(rel):
"Maximum unsigned curvature attained at any point of this rel."
return _curvature_penalty(rel, False)

def deriv_curvature_penalty(rel):
"Derivative of maximum unsigned curvature attained."
return _curvature_penalty(rel, True)

def _curvature_penalty(rel, differentiate):
"""Computations for maximum curvature on this rel.
Parameters:
differentiate: boolean, whether to return the derivative."""
# Let x, y be the coordinates of the cubic curve.
# Let ' denote derivative in the parameter of the curve (which we call t).
# Signed curvature is k = (x'y'' - y'x'')/((x'^2+y^'2)^(3/2)).
# k' = [ (x''y''+x'y'''-y''x''-y'x''')(x'^2+y^'2)^(3/2)
# - (x'y''-y'x'')*3/2(x'^2+y'^2)^(1/2)*2(x'x''+y'y'') ]
# / ((x^2+y^2)^3)
# = [ (x'y'''-y'x''')(x'^2+y^'2) - (x'y''-y'x'')*3(x'x''+y'y'') ]
# / ((x^2+y^2)^(5/2))).
# For a cubic curve the degree of the numerator is generically 5
# because of cancellations in the leading coefficient.
bezier = rel.svgpathtools_bezier()
d = bezier.poly().deriv()
dx = numpy.poly1d([float(c.real) for c in d.c])
dy = numpy.poly1d([float(c.imag) for c in d.c])
ddx = dx.deriv()
ddy = dy.deriv()
dddx = ddx.deriv()
dddy = ddy.deriv()

def curvature(t):
"Evaluation of the signed curvature at curve parameter t."
dxt = dx(t)
dyt = dy(t)
return (dxt*ddy(t)-dyt*ddx(t)) / pow(dxt*dxt+dyt*dyt, 1.5)

# numerator of k'
ndk = numpy.polysub(numpy.polymul(
numpy.polysub(numpy.polymul(dx, dddy), numpy.polymul(dy, dddx)),
numpy.polyadd(numpy.polymul(dx, dx), numpy.polymul(dy, dy)) ),
numpy.polymul(
numpy.polysub(numpy.polymul(dx, ddy), numpy.polymul(dy, ddx)),
numpy.polyadd(numpy.polymul(dx, ddx), numpy.polymul(dy, ddy)) )*3.
)
criticals = numpy.roots(ndk)
criticals = [0., 1.] + [t.real for t in criticals if t.imag==0. and t.real >= 0. and t.real <= 1.]
k_at_criticals = [curvature(t) for t in criticals]
abs_kmax, tmax, imax = max((abs(k_at_criticals[i]),criticals[i],i) for i in range(len(criticals)))
if not differentiate:
return abs_kmax

sign_max = -1. if k_at_criticals[imax] < 0. else 1.

du_d = dpoly(rel).deriv()
du_dx = numpy.poly1d([float(c.real) for c in du_d.c])
du_dy = numpy.poly1d([float(c.imag) for c in du_d.c])
du_ddx = du_dx.deriv()
du_ddy = du_dy.deriv()

dxt = dx(tmax)
dyt = dy(tmax)
ddxt = ddx(tmax)
ddyt = ddy(tmax)
du_dxt = du_dx(tmax)
du_dyt = du_dy(tmax)
du_ddxt = du_ddx(tmax)
du_ddyt = du_ddy(tmax)

# If the maximum curvature is attained at an endpoint,
# we just want the u-derivative of curvature there.
# The evaluations du_dxt, etc. above are correct for this purpose.
#
# But if the maximum curvature is attained at an interior critical point tmax,
# work out a=dt/du on the locus of critical points by implicit differentiation,
# and then differentiate the max curvature not in direction du but du+a dt.
if imax >= 2:
dddxt = dddx(tmax)
dddyt = dddy(tmax)
du_dddx = du_ddx.deriv()
du_dddy = du_ddy.deriv()
du_dddxt = du_dddx(tmax)
du_dddyt = du_dddy(tmax)
# Consider p = ndk(t) + u du_ndk(t) and find (dp/du)/(dp/dt)
# at (t, u) = (tmax, 0).
dp = ndk.deriv()(tmax) # ndk.deriv() = (dp/dt)|u=0
# And below is dp/du, which is independent of u, at tmax.
# recall, ndk = (x'y'''-y'x''')(x'^2+y^'2) - (x'y''-y'x'')*3(x'x''+y'y'')
du_p = (du_dxt*dddyt+dxt*du_dddyt-du_dyt*dddxt-dyt*du_dddxt)*(dxt*dxt+dyt*dyt) + \
2*(dxt*dddyt-dyt*dddxt)*(du_dxt*dxt+du_dyt*dyt) - \
3*(du_dxt*ddyt+dxt*du_ddyt-du_dyt*ddxt-dyt*du_ddxt)*(dxt*ddxt+dyt*ddyt) - \
3*(dxt*ddyt-dyt*ddxt)*(du_dxt*ddxt+dxt*du_ddxt+du_dyt*ddyt+dyt*du_ddyt)
a = du_p/dp
# Replace the four du_ variables used below by derivatives in direction du+a dt.
du_dxt += a*ddxt
du_dyt += a*ddyt
du_ddxt += a*dddxt
du_ddyt += a*dddyt

du_k = ((du_dxt*ddyt+dxt*du_ddyt-du_dyt*ddxt-dyt*du_ddxt)*(dxt*dxt+dyt*dyt) - \
3*(dxt*ddyt-dyt*ddxt)*(du_dxt*dxt+du_dyt*dyt)) / \
pow(dxt*dxt+dyt*dyt, 2.5)
return sign_max * du_k

0 comments on commit 4e8d518

Please sign in to comment.