How to close a PShape with 2 arcs and two lines

I’ve been trying to make a PShape like a segment of a color wheel, but I am not sure how to close it so I can use a stroke, and fills, and move them around on the screen easily.

I don’t know whether I should try to find a way to calculate the pixels of the other end of the two arcs, it seems like mathematically I’m not going to be able to close the loop properly.

In this example, I have drawn the desired shape twice, and tried to create a PShape to match a single segment.

Can someone please tell me how to do this?

PShape outline, arc1, arc2, line1, line2;

void setup() {
  size(800, 480);
  outline = createShape(GROUP);

  arc1 = createShape(ARC, 0, 0, 460, 460, 0, radians(30), OPEN);
  arc2 = createShape(ARC, 0, 0, 115, 115, 0, radians(30), OPEN);
  line1 = createShape(LINE, 57.5, 0, 230, 0);


  outline.addChild(arc1);
  outline.addChild(arc2);
  outline.addChild(line1);
}
void draw() {
  background(50);

  pushMatrix();
  translate(width/2, height/2);
  shape(outline);
  popMatrix();

  pushMatrix();
  translate(width/2, height/2);
  rotate(radians(-105));
  noFill();
  arc(0, 0, 460, 460, 0, radians(30));
  arc(0, 0, 115, 115, 0, radians(30));
  line(57.5, 0, 230, 0);
  pushMatrix();
  rotate(radians(30));
  line(57.5, 0, 230, 0);
  popMatrix();
  popMatrix();

  pushMatrix();
  translate(width/2, height/2);
  rotate(radians(-135));
  noFill();
  arc(0, 0, 460, 460, 0, radians(30), OPEN);
  arc(0, 0, 115, 115, 0, radians(30), OPEN);
  line(57.5, 0, 230, 0);
  pushMatrix();
  rotate(radians(30));
  line(57.5, 0, 230, 0);
  popMatrix();
  popMatrix();
}

You can do it in a similar way to the way you are doing it without PShape – use rotate. In this case, rather than rotating within a matrix stack, define your line segment in terms of PVector points, then rotate those points (which rotates the line segment, like a clock hand).

// https://discourse.processing.org/t/how-to-close-a-pshape-with-2-arcs-and-two-lines/5287
// 2018-11-10

PShape outline, arcInner, arcOuter, line1, line2;

void setup() {
  size(800, 480);
  outline = createShape(GROUP);

  noFill(); // set desired shape fill state when you create the shape
  
  float diameter1 = 115;
  float diameter2 = 460;
  float sweep = radians(30);
  
  // add first arc
  arcInner = createShape(ARC, 0, 0, diameter1, diameter1, 0, sweep, OPEN);
  outline.addChild(arcInner);

  // add second arc
  arcOuter = createShape(ARC, 0, 0, diameter2, diameter2, 0, sweep, OPEN);
  outline.addChild(arcOuter);

  // define line  PVectors -- which can be rotated
  PVector lineStart = new PVector(diameter1/2.0, 0);
  PVector lineStop = new PVector(diameter2/2.0, 0);
  // add first line
  line1 = createShape(LINE, lineStart.x, lineStart.y, lineStop.x, lineStop.y);
  outline.addChild(line1);

  // rotate line points
  lineStart.rotate(sweep);
  lineStop.rotate(sweep);

  // draw second line
  line2 = createShape(LINE, lineStart.x, lineStart.y, lineStop.x, lineStop.y);
  outline.addChild(line2);
}
void draw() {
  background(50);

  pushMatrix();
  translate(width/2, height/2);
  shape(outline);
  popMatrix();
}

It may be easier to understand what the above is doing with a simpler example that doesn’t use PShape:

// Moiré clockhand
// https://discourse.processing.org/t/how-to-close-a-pshape-with-2-arcs-and-two-lines/5287
// 2018-11-10

PVector lineStart, lineStop;

void setup() {
  size(480, 480);
  float diameter1 = 115;
  float diameter2 = 460;
  // define line  PVectors -- which can be rotated
  lineStart = new PVector(diameter1/2.0, 0);
  lineStop = new PVector(diameter2/2.0, 0);
  // draw second line
}
void draw() {
  pushMatrix();
  translate(width/2, height/2);
  line(lineStart.x, lineStart.y, lineStop.x, lineStop.y);
  popMatrix();

  lineStart.rotate(0.05);
  lineStop.rotate(0.05);
}

1 Like

good, thanks @jeremydouglass , but doing the outline is only one question,

how can a group ( of shapes ( even if closed )) be treated like a closed shape and filled??


PShape l1,l2,l3,l4,all;
int w=100;

void setup(){
 size(300,300); 
 
 all = createShape(GROUP);
 l1 = createShape(LINE,0,0,w,0);
 l2 = createShape(LINE,w,0,w,w);
 l3 = createShape(LINE,w,w,0,w);
 l4 = createShape(LINE,0,w,0,0);
 all.addChild(l1);
 all.addChild(l2);
 all.addChild(l3);
 all.addChild(l4);
 //all.fill(0,200,0); //  fill() can only be called between beginShape() and endShape()
}

void draw(){
  background(200,200,0);
  fill(0,200,0);  // not change
  //all.setFill(0, 200);   // ERROR This renderer does not support setFill() for individual vertices
  shape(all,width/2,height/2);
}

i have no idea??

only for a single vertex shape the dynamic fill works for me

PShape s;

void segment(float in_r,float out_r, float start_p,float stop_p){
  s = createShape();
  s.beginShape();
  float rx,ry;
  for ( float f = start_p ; f < stop_p ; f+=(stop_p-start_p)/360){
  rx = in_r * sin(f);
  ry = in_r * cos(f);  
  s.vertex(rx,ry); 
  }
  rx = in_r * sin(stop_p);
  ry = in_r * cos(stop_p);  
  s.vertex(rx,ry); 
  rx = out_r * sin(stop_p);
  ry = out_r * cos(stop_p);  
  s.vertex(rx,ry); 
  for ( float f = stop_p ; f > start_p ; f-=(stop_p-start_p)/360){
  rx = out_r * sin(f);
  ry = out_r * cos(f);  
  s.vertex(rx,ry); 
  }
  rx = in_r * sin(start_p);
  ry = in_r * cos(start_p);  
  s.vertex(rx,ry); 
  s.endShape(CLOSE);  //
}

void setup() {
  size(300, 300,P2D);
  segment(50,90,PI*0.2,PI*1.7);
}

void draw() {
  background(200,200,0);
  shape(s, width/2, height/2);
  s.setFill(color(255-mouseY, mouseX, 255));
//  s.setStroke(false);
}

Ah, yes, I see – I thought the goal was to make the PShape example do what the non-PShape example was doing, didn’t read closely.

Here is a very simple approach that works to create a stroked, filled ring-arc (an arc with a hole). See the animated sketch below:

// https://discourse.processing.org/t/how-to-close-a-pshape-with-2-arcs-and-two-lines/5287/3
// animated arc with hole -- press any key to hide hole
// 2018-11-11 Jeremy Douglass Processing 3.4

float amt;
boolean hideHole;

void draw() {
  background(192);
  amt = millis()%5000/5000.0 * TWO_PI; // animation step

  // draw arc
  stroke(0);
  fill(255);
  arc(50, 50, 80, 80, 0, amt, PIE);

  // draw hole
  if (!hideHole) {
    fill(192);
    arc(50, 50, 30, 30, 0, amt, PIE);
    // remove innner lines
    noStroke();
    ellipse(50, 50, 29, 29);
  }
}
void keyReleased(){
  hideHole = !hideHole;
}

AnimatingArcWithHole--screenshot

Note that it has limitations:

  1. animation isn’t perfectly smooth

    In some cases, the arc() function isn’t accurate enough for smooth drawing. For example, the shape may jitter on screen when rotating slowly. If you’re having an issue with how arcs are rendered, you’ll need to draw the arc yourself with beginShape()/endShape() or a PShape. arc() / Reference / Processing.org

  2. ellipse and arc don’t perfectly align – so there may be some noise on the inner strokes

  3. it uses background color to remove the inner segment. If you need transparency in the inner ring for layering / compositing then you need to draw the ring-arc to a PGraphics and clear the inner ellipse of pixels / use mask() (mask() / Reference / Processing.org)

1 Like

Thanks for the suggestions. I’m sorry I wasn’t more clear what I was trying to do.

I set out trying to learn about PShape, and different ways of using it. So really, I was mostly looking for how to complete the loop on a shape like this, so they can be closed and filled.

I thought I could make something like a puzzle with pieces shaped as segments of a donut, so each piece could be filled with different colors and moved around individually,

The only thing I was having a hard time understanding is how are you supposed to make a PShape that has arcs or curves close like they show in the reference for createShape() by using endShape(CLOSE)

Maybe I was just wrong in thinking you can draw using an arc, then at the endpoint change to vertex drawing.

yes, it is understandable,
it should be also possible instead of

createShape(ARC ....

use kind of primitives macro between begin … endShape.

I don’t believe that you can use child shapes in that way. You can call methods between beginShape() and endShape() and use them in combination to create a closed shape – see beginShape() / Reference / Processing.org – but you cannot assemble children and then expect them to act like a single path – they are more like separate photoshop layers; each is distinct.

If you want to mix a list of vertices, you can, but you cannot use that with a beginShape mode (like TRIANGLE_FAN) or with a createShape mode (like ARC). See for example the curveVertex reference:

Specifies vertex coordinates for curves. This function may only be used between beginShape() and endShape() and only when there is no MODE parameter specified to beginShape() . curveVertex() / Reference / Processing.org

Mixing vertex, curveVertex, and bezierVertex is okay, however. Notice that you need a minimum number of curveVertex or bezierVertex points in a row in order for there to be a curve effect, or they may be treated as normal vertices or deleted.

beginShape();
vertex(10, 10);
vertex(10, 90);
curveVertex(40, 30);
curveVertex(70, 60);
curveVertex(50, 50);
curveVertex(5, 55);
curveVertex(55, 5);
vertex(90, 90);
vertex(90, 10);
endShape(CLOSE);

MixedVertices--screenshot

1 Like

That makes sense. Thanks for explaining. I’m probably going to want to do something with this in Box2D so because of the convex requirement I’ll have to use interior triangles anyhow.

This is kind of where I was going with it, I was kind of hoping I could use some nice arcs in there. But if that’s just not going to work with a PShape, that’s okay.

I do have a question about creating a group. In the “wheelGroup” group, all the segments are stacked on top of each other. Is there a way to set them as a group with the translations applied?

Wheel wheel;
float speed;
color[] RYB = { #FE2712, #FC600A, #FB9902, #FCCC1A, 
  #FEFE33, #B2D732, #66B032, #347C98, 
  #0247FE, #4424D6, #8601AF, #C21460 };
void setup() {
  size(800, 480);
  wheel = new Wheel(460, 260, 12);
  speed = 200;
}
void draw() {
  background(50);
  wheel.display();
}
class Wheel {
  PShape wheelGroup;
  PShape[] segments;
  float oD, iD, angle;
  int nSides;
  Wheel(float oD, float iD, int nSides) {
    segments = new PShape[nSides];
    wheelGroup = createShape(GROUP);
    angle = TWO_PI / nSides;
    this.oD = oD;
    this.iD = iD;
    this.nSides = nSides;
    for (int n = 0; n < nSides; n++) {
      segments[n] = createSegment(n);
    }
    createWheel();
  }
  void display() {
    // display individual segments
    pushMatrix();
    translate(200, height/2);
    rotate(frameCount / speed);
    for (int n = 0; n < nSides; n++) {
      pushMatrix();
      rotate(TWO_PI / nSides * n);
      shape(segments[n]);
      popMatrix();
    }
    popMatrix();

    // display wheel group
    pushMatrix();
    translate(width - 200, height/2);
    rotate(frameCount / speed);
    shape(wheelGroup);
    popMatrix();
  }
  PShape createSegment(int n) {
    PShape segment;
    segment = createShape();
    segment.beginShape();
    for (float a = 0; a <= angle; a += angle) {
      float outx = 0 + cos(a) * oD/2;
      float outy = 0 + sin(a) * oD/2;
      segment.vertex(outx, outy);
    }
    for (float b = angle; b >= 0; b -= angle) {
      float inx = 0 + cos(b) * iD/2;
      float iny = 0 + sin(b) * iD/2;
      segment.vertex(inx, iny);
    }
    segment.endShape(CLOSE);
    segment.setStroke(RYB[n]);
    segment.setFill(RYB[n]);
    return segment;
  }
  void createWheel() {
    for (int n = 0; n < nSides; n++) {
      pushMatrix();
      rotate(TWO_PI / nSides * n);
      wheelGroup.addChild(segments[n]);
      popMatrix();
    }
    println("Wheel Child Count:", wheelGroup.getChildCount());
  }
}
1 Like

you not use your GROUP from outside?

instead of

wheel.display();

i would expect like

shape(wheelGroup,width/2,height/2);

!UNTESTED

or give your Wheel class a (posX,posY,…)
and call inside display

shape(wheelGroup,posX,posY);

Following up on the earlier example question – If later you are looking to mix arcs with line segments in a closed PShape, know that this is possible, you just need to implement an approximation of a circle arc segment using the existing primitives – curveVertex, bezierVertex, or quadraticVertex. These can then be combined with straight line segments using vertex, and form closed shapes that resemble arcs or ring segments.

The key thing for these is that they only give you approximates of the circle arc, and you probably need to have points along the circumference at TWO_PI/16 or below to avoid distortion.

I’ll try that, thanks.

I’ve been looking at those in the reference docs for quite some time, I guess it’s time to give them a try. Thanks again.

There are a few gotchas, so consult their reference pages carefully. Be careful about how you include the minimum number of points per curve-type points to get an effect, and lead and follow with the needed extra points – e.g. with a control points for curveVertex, and leading with a normal point for bezierVertex.

“The first and last points in a series of curveVertex() lines will be used to guide the beginning and end of a the curve.” curveVertex() / Reference / Processing.org

You can use either to approximate a circle, then use that to draw arcs. The control point math for the bezierVertex version can get a bit complex – for discussion see How to create circle with Bézier curves?. For curveVertex (Catmull-Rom splines) you really just need to find points on the circumference using the parametric equation for a circle.

Here is an example that draws an arc portion of a ring (doughnut). It draws a vertex, an arc of curveVertex, and a vertex. If you connect two of them running in different directions within the same beginShape/endShape (an inner arc and an outer arc) then you get a ring arc, and it can be closed and filled.

Notice it has the same subtle animation problems that arc has, and that the “resolution” setting is necessary to prevent distortion.

/**
 * Curve Vertex Arc Ring
 * Draw arc with curveVertex. Mouse changes angle, thickness. Key toggles points.
 * 2018-11-12 Jeremy Douglass - Processing 3.4
 * https://discourse.processing.org/t/how-to-close-a-pshape-with-2-arcs-and-two-lines/5287/12
 */
boolean seePoints;
void setup() {
  size(400, 200);
}

void draw() {
  background(192);
  // mouse-controlled angle
  float r = width / 4.0 - 20;
  float angle = mouseX/(float)width * TWO_PI;
  translate(width/4, height/2);
  // vertex arc ring
  beginShape();
  float r2 = r * mouseY/(float)height;
  curveVertexRing(0, 0, r, r2, 0, angle);
  endShape(CLOSE);
  // arc
  translate(width/2, 0);
  arc(0, 0, 2*r, 2*r, 0, angle, PIE);
}

void keyReleased() {
  seePoints = !seePoints;
}

void curveVertexRing(float cx, float cy, float r, float r2, float start, float stop) {
  curveVertexArc(cx, cy, r, start, stop);
  curveVertexArc(cx, cy, r2, stop, start);
}

void curveVertexArc(float cx, float cy, float r, float start, float stop) {
  float resolution = TWO_PI/32; // angle per curve point -- too few distorts
  float x, y;
  float a = start;
  int segments =  max(ceil(abs(stop-start)/resolution), 4);
  vertex(cx + r * cos(start), cy + r * sin(start));  
  float step = (stop-start)/(float)segments;
  for (int i=0; i<=segments; i++) {
    // define curve point with parametric equation for circle
    x = cx + r * cos(a);
    y = cy + r * sin(a);
    curveVertex(x, y);
    a += step;
    if (seePoints) ellipse(x, y, 10, 10); // inspect control points
  }
  vertex(cx + r * cos(stop), cy + r * sin(stop));
}

CurveVertexArcRing--screenshot

1 Like

I’ve tried using a few different methods to display wheelGroup, but all 12 shape segments are in a single stack.

The only way they will “fan out” is by using a loop and going through the array, rotating after each one…

I guess each segment is receiving the same exact vertex coordinates as they are created, does that seem like what’s wrong?

I also tried moving the rotation into the constructor, its making 12 segments but not arranging them. Shouldn’t push/popMatrix affect the vertex coordinates as they are created?

Wheel wheel;
float speed;
color[] RYB = { #FE2712, #FC600A, #FB9902, #FCCC1A, 
  #FEFE33, #B2D732, #66B032, #347C98, 
  #0247FE, #4424D6, #8601AF, #C21460 };
void setup() {
  size(800, 480);
  wheel = new Wheel(460, 260, 12);
  speed = 200;
}
void draw() {
  background(50);
  wheel.display();
  shape(wheel.wheelGroup);
}
class Wheel {
  PShape wheelGroup;
  PShape[] segments;
  PVector location;
  float oD, iD, angle;
  int nSides;
  Wheel(float oD, float iD, int nSides) {
    segments = new PShape[nSides];
    wheelGroup = createShape(GROUP);
    location = new PVector(0, 0);

    angle = TWO_PI / nSides;
    this.oD = oD;
    this.iD = iD;
    this.nSides = nSides;
    
    pushMatrix();
    translate(location.x, location.y);
    for (int n = 0; n < nSides; n++) {
      pushMatrix();
      rotate(TWO_PI / nSides * n);
      segments[n] = createSegment(n);
      wheelGroup.addChild(segments[n]);
      popMatrix();
    }
    popMatrix();
    createWheel();
  }
  void display() {
    // display by translating/rotating individual segments from array
    pushMatrix();
    translate(200, height/2);
    rotate(frameCount / speed);
    for (int n = 0; n < nSides; n++) {
      pushMatrix();
      rotate(TWO_PI / nSides * n);
      shape(segments[n]);
      popMatrix();
    }
    popMatrix();

    // display wheel group
    pushMatrix();
    translate(width - 200, height/2);
    rotate(frameCount / speed);
    shape(wheelGroup, location.x, location.y);
    popMatrix();
  }
  void createWheel() {
    //pushMatrix();
    //translate(location.x, location.y);
    //for (int n = 0; n < nSides; n++) {
    //  pushMatrix();
    //  rotate(TWO_PI / nSides * n);
    //  wheelGroup.addChild(segments[n]);
    //  popMatrix();
    //}
    //popMatrix();
    println("Wheel Child Count:", wheelGroup.getChildCount());
  }
  PShape createSegment(int n) {
    PShape segment;
    segment = createShape();
    segment.beginShape();
    for (float a = 0; a <= angle; a += angle) {
      float outx = 0 + cos(a) * oD/2;
      float outy = 0 + sin(a) * oD/2;
      segment.vertex(outx, outy);
    }
    for (float b = angle; b >= 0; b -= angle) {
      float inx = 0 + cos(b) * iD/2;
      float iny = 0 + sin(b) * iD/2;
      segment.vertex(inx, iny);
    }
    segment.endShape(CLOSE);
    segment.setStroke(RYB[n]);
    segment.setFill(RYB[n]);

    return segment;
  }
}

Hi ddownn,

I quickly read the discussion so I may have missed so important things but is it something like this that you would need:

void setup() {
  size(800, 480);
  background(20);
  
  translate(width/2, height/2);
  createSegment();
}

void createSegment(){
  noFill();
  stroke(200);
  strokeWeight(2);
  
  beginShape();
  
  vertex(150*cos(-PI/2-PI/8), 150*sin(-PI/2-PI/8));
  vertex(200*cos(-PI/2-PI/8), 200*sin(-PI/2-PI/8));
  bezierVertex(220*cos(-PI/2-PI/16), 220*sin(-PI/2-PI/16), 220*cos(-PI/2+PI/16), 220*sin(-PI/2+PI/16), 200*cos(-PI/2+PI/8), 200*sin(-PI/2+PI/8));
  vertex(150*cos(-PI/2+PI/8), 150*sin(-PI/2+PI/8));
  bezierVertex(160*cos(-PI/2+PI/16), 160*sin(-PI/2+PI/16), 160*cos(-PI/2-PI/16), 160*sin(-PI/2-PI/16), 150*cos(-PI/2-PI/8), 150*sin(-PI/2-PI/8));
 
  endShape(CLOSE);
}

It just need some proper math to position the bezier points to better approximate each arc to the circle it is supposed to be part of.

Thanks, I appreciate the help with that. Can you tell me what I’m missing with the part about creating the group of shapes, how to translate each segment inside the group so that the group ends up as a wheel instead of a stack of segments?

You can’t use translate and rotate in the main sketch and have it affect the shape coordinates. You need to either transform the absolute coordinates – vertex(a+x, b+y) – or use create a PShape() ps and call ps.translate, ps.rotate. Since you are adding children – which act like photoshop layers – as segment, you would call segment.translate(), segment.rotate() in order to change the drawing matrix on each segment.

Notice that these can be cumulative – they are not automatically reset at the end of draw the way the matrix is cleared for translate() and rotate().

1 Like

That works great. thanks

Wheel wheel;
float speed;
color[] RYB = { #FE2712, #FC600A, #FB9902, #FCCC1A, 
  #FEFE33, #B2D732, #66B032, #347C98, 
  #0247FE, #4424D6, #8601AF, #C21460 };
void setup() {
  size(800, 480);
  wheel = new Wheel(new PVector(width/2, height/2), 460, 260, 12);
  speed = 200;
}
void draw() {
  background(50);
  wheel.display();
}
class Wheel {
  PShape wheelGroup;
  PShape[] segments;
  PVector location;
  float oD, iD, angle;
  int nSides;
  Wheel(PVector location, float oD, float iD, int nSides) {
    segments = new PShape[nSides];
    wheelGroup = createShape(GROUP);
    this.location = location;
    angle = TWO_PI / nSides;
    this.oD = oD;
    this.iD = iD;
    this.nSides = nSides;
    
    createWheel();
  }
  void display() {
    pushMatrix();
    translate(location.x, location.y);
    rotate(frameCount / speed);
    shape(wheelGroup);
    popMatrix();
  }
  void createWheel() {
    for (int n = 0; n < nSides; n++) {
      segments[n] = createSegment(n);
      wheelGroup.addChild(segments[n]);
      segments[n].rotate(TWO_PI / nSides * n);
    }
  }
  PShape createSegment(int n) {
    PShape segment;
    segment = createShape();
    segment.beginShape();
    for (float a = 0; a <= angle; a += angle) {
      float outx = 0 + cos(a) * oD/2;
      float outy = 0 + sin(a) * oD/2;
      segment.vertex(outx, outy);
    }
    for (float b = angle; b >= 0; b -= angle) {
      float inx = 0 + cos(b) * iD/2;
      float iny = 0 + sin(b) * iD/2;
      segment.vertex(inx, iny);
    }
    segment.endShape(CLOSE);
    segment.setStroke(RYB[n]);
    segment.setFill(RYB[n]);

    return segment;
  }
}
1 Like