Giving variable thickness to a line (calculating normals)

2018-11-24-194014_400x400_scrot

ArrayList<PVector> points;
ArrayList<PVector> normals;

void setup() {
  size(400, 400);
  background(255);

  // 1. Define polyline points
  points = new ArrayList<PVector>();
  points.add(new PVector(61, 183));
  points.add(new PVector(108, 113));
  points.add(new PVector(193, 118));
  points.add(new PVector(256, 158));
  points.add(new PVector(248, 239));
  points.add(new PVector(258, 310));
  points.add(new PVector(328, 353));
  points.add(new PVector(377, 341));

  // 2. calculations

  // Ensure normals has the same size as points
  normals = new ArrayList<PVector>();
  while (normals.size() < points.size()) {
    normals.add(new PVector());
  }

  // Calculate normals
  for (int i=0; i<points.size()-1; i++) {
    PVector sub = PVector.sub(points.get(i), points.get(i+1)); 
    normals.get(i).set(-sub.y, sub.x);
  }
  for (int i=1; i<points.size(); i++) {
    PVector sub = PVector.sub(points.get(i), points.get(i-1)); 
    normals.get(i).add(sub.y, -sub.x);
  }
  
  // Resize normals
  for(PVector n : normals) {
    n.normalize().mult(random(10, 30));
  }  
  
  // 3. Use the calculated normals
  // to draw in different ways
  
  // draw calculated thick line
  noStroke();
  fill(#FFCC00);
  beginShape(PConstants.QUAD_STRIP);
  for (int i=0; i<points.size(); i++) {
    PVector p = points.get(i);
    PVector n = normals.get(i);   
    vertex(p.x + n.x, p.y + n.y);
    vertex(p.x - n.x, p.y - n.y);
  }
  endShape();

  // draw the original line
  stroke(#552200);
  noFill();
  beginShape();
  for (PVector p : points) {
    vertex(p.x, p.y);
  }
  endShape();

  // draw line vertices
  stroke(#552200);
  strokeWeight(2);
  fill(255);
  for (PVector p : points) {
    ellipse(p.x, p.y, 6, 6);
  }
  
  // draw contour points
  noStroke();
  fill(#883300);
  for (int i=0; i<points.size(); i++) {
    PVector p = points.get(i);
    PVector n = normals.get(i);   
    ellipse(p.x + n.x, p.y + n.y, 3, 3);
    ellipse(p.x - n.x, p.y - n.y, 3, 3);
  }
}
void draw() {
}

Here an animated version on p5.js:

5 Likes

This is actually quite useful for something I was trying to do recently. Thanks for sharing hamoid.

1 Like

Thank you so much @hamoid!

I have ported it to Python mode :smiley:

"""
Giving variable thickness to a line (calculating normals)
Python port of code by Hamoid, found at:
https://discourse.processing.org/t/giving-variable-thickness-to-a-line-calculating-normals/5890
"""

def draw():
    pass

def setup():
    global points, normals
    size(400, 400)
    background(255)

    # 1. Define polyline points
    points = []
    points.append(PVector(61, 183))
    points.append(PVector(108, 113))
    points.append(PVector(193, 118))
    points.append(PVector(256, 158))
    points.append(PVector(248, 239))
    points.append(PVector(258, 310))
    points.append(PVector(328, 353))
    points.append(PVector(377, 341))

    # 2. calculations

    # Ensure normals has the same size as points
    normals = [PVector()] * len(points)

    # Calculate normals
    for i in range(len(points) - 1):
        sub = PVector.sub(points[i], points[i + 1])
        normals[i] = PVector(-sub.y, sub.x)

    for i in range(1, len(points)):
        sub = PVector.sub(points[i], points[i - 1])
        normals[i].add(PVector(sub.y, -sub.x))

    # Resize normals
    for n in normals:
        n.normalize().mult(random(10, 30))

    # 3. Use the calculated normals to draw in different ways

    # draw calculated thick line
    noStroke()
    fill("#FFCC00")
    with beginShape(QUAD_STRIP):
        for (p, n) in zip(points, normals):
            vertex(p.x + n.x, p.y + n.y)
            vertex(p.x - n.x, p.y - n.y)

    # draw the original line
    stroke("552200")
    noFill()
    with beginShape():
        for p in points:
            vertex(p.x, p.y)

    # draw line vertices
    stroke("#552200")
    strokeWeight(2)
    fill(255)
    for p in points:
        ellipse(p.x, p.y, 6, 6)

    # draw contour points
    noStroke()
    fill("#883300")
    for (p, n) in zip(points, normals):
        ellipse(p.x + n.x, p.y + n.y, 3, 3)
        ellipse(p.x - n.x, p.y - n.y, 3, 3)

2 Likes

Oh, I forgot to add this operator overloaded Python style PVector use:

    # Calculate normals
    for i in range(len(points) - 1):
        sub = points[i] - points[i + 1]
        normals[i] = PVector(-sub.y, sub.x)

    for i in range(1, len(points)):
        sub = points[i] - points[i - 1]
        normals[i] += PVector(sub.y, -sub.x)
1 Like

Thanks! Using - to subtract PVectors is a really nice use of Python operator overloading.

I am glad you provided both forms, however. The previous form is less Pythonic but easier for new users to translate into / out of Java – for those who use both, or who are trying to learn from sketches in one mode while working in another.

2 Likes