Hi @dehyde
The approach suggested in the SO thread linked by @hotfooted is interesting but you might face a problem when dealing with curveVertex
or bezierVertex
as you won’t be able to retrieve all the vertices along that line that are necessary to compute their perpendicular projection.
Below an annotated example sketch (Python mode) that:
-
computes a degree 3 polyline (close to Bezier spline) from a set of control points using the Catmull-Clark subdivision algorithm
-
calculates the perpendicular projection of each vertex of that polyline on both sides (inner and outer edge of your path).
-
draw a PShape made of
QUAD_STRIP
from these projected vertices
You can adjust the subdivision level to your needs, tune the length of the perpendicular lines (the width of your path) and interactively change the position of the control points with the mouse.
ITER = 5 #up to 7 max
SW = 25.0
toMove = None
def setup():
size(1000, 600, P2D)
strokeWeight(2)
smooth(8)
noFill()
global ctrlPnts, pnts
ctrlPnts = [PVector(250, 320), PVector(450, 150), PVector(610, 200), PVector(850, 280), PVector(500, 400), PVector(540, 480), PVector(260, 500)]
pnts = ctrlPnts
subdivide()
def draw():
background('#FFFFFF')
#Draw lines between control points
pushStyle()
strokeWeight(1)
stroke(40, 40, 255)
for i, p in enumerate(ctrlPnts):
line(p.x, p.y, ctrlPnts[(i+1)%len(ctrlPnts)].x, ctrlPnts[(i+1)%len(ctrlPnts)].y)
popStyle()
#Draw control points
pushStyle()
stroke(255, 30, 90)
strokeWeight(10)
for p in ctrlPnts:
point(p.x, p.y)
popStyle()
#Draw Catmull-Clark subdivided polyline (spline)
for i in xrange(len(pnts)):
p1 = pnts[i]
p2 = pnts[(i+1)%len(pnts)]
line(p1.x, p1.y, p2.x, p2.y)
#Draw PShape from quads
pushStyle()
strokeWeight(.7)
fill(95, 250, 90, 60)
beginShape(QUAD_STRIP)
for i in xrange(len(pnts)+1):
p1 = pnts[i-1] #last point
pm = pnts[i%len(pnts)] #midpoint
p2 = pnts[(i+1)%len(pnts)] # next point
theta = atan2(p2.y - p1.y, p2.x - p1.x) + HALF_PI
px = (SW*.5) * cos(theta)
py = (SW*.5) * sin(theta)
vertex(pm.x - px, pm.y - py) #vertices for the outer edge of path
vertex(pm.x + px, pm.y + py) #vertices for the inner edge of path
endShape()
popStyle()
def mouseDragged():
global toMove
for p in ctrlPnts:
if dist(p.x, p.y, mouseX, mouseY) < 12:
toMove = p
break
try:
toMove.set(mouseX, mouseY)
except:
print "No control point selected"
def mouseReleased():
global toMove
do = False
for p in ctrlPnts:
if dist(p.x, p.y, mouseX, mouseY) < 12:
do = True
break
if do:
toMove = None
subdivide()
def subdivide():
global pnts
pnts = ctrlPnts
#Catmull-Clark subdivision
for j in xrange(ITER):
newPnts = []
for i in xrange(len(pnts)):
p1 = pnts[i]
p2 = pnts[(i+1)%len(pnts)]
p3 = pnts[(i+2)%len(pnts)]
qp = p1 * .5 + p2 * .5
rp = p1 * .125 + p2 * .75 + p3 * .125 #(Catmull-Clark subdivision = degree 3)
newPnts.append(qp)
newPnts.append(rp)
pnts = newPnts
edit: You could also offset the contour of a polygon along its vertices normals. The contour and its offsetted self would compose the inner and outer edges of the PShape.
The contour of a polygon offsetted 20 times
They are a dozen of SO threads on the subject (inward/outward offset polygon) so feel free to check there if you’re interested, you’ll probably find different approaches to your problem.
Meanwhile, here’s one them implemented in Processing Python mode:
### Based on @MBo's solution suggested here -> https://stackoverflow.com/questions/54033808/how-to-offset-polygon-edges
SW = -25.0 #Stroke width of path/PShape (-/+ for inner/outer offset)
def setup():
size(1000, 600, P2D)
background('#FFFFFF')
fill(230)
smooth(8)
#List of vertices (polygon)
points = [PVector(250, 320), PVector(450, 150), PVector(610, 200), PVector(850, 280), PVector(500, 400), PVector(540, 480), PVector(260, 500)]
beginShape(QUAD_STRIP)
for i in xrange(len(points)+1):
pp = points[(i-1)%len(points)] #previous vertex
pc = points[i%len(points)] #current vertex
pn = points[(i+1)%len(points)] #next vertex
thetaA = atan2(pc.y - pp.y, pc.x - pp.x) + HALF_PI #right angle with the edge pp-pc
thetaB = atan2(pn.y - pc.y, pn.x - pc.x) + HALF_PI #right angle with the edge pc-pn
a = PVector(cos(thetaA), sin(thetaA)) #vector pointing perpendicularly to edge pp-pc
b = PVector(cos(thetaB), sin(thetaB)) #vector pointing perpendicularly to edge pc-pn
nsum = (a + b).normalize() #normalized sum
l = SW / sqrt(1 + PVector.dot(a,b)) #desired length
po = pc + (l * nsum) #offsetted vertex
vertex(pc.x, pc.y)
vertex(po.x, po.y)
endShape(CLOSE)