micycle1 / PGS

Processing Geometry Suite
https://micycle1.github.io/PGS/
170 stars 13 forks source link

Intermittent "Invalid number of points in LinearRing" when using `PGS_ShapeBoolean.subtract()` #22

Closed mrwoodo closed 3 years ago

mrwoodo commented 3 years ago

Hi @micycle1, I'm using PGS to do hidden line removal for my old Roland plotter. The idea is that because the plotter has no concept of .fill(), I'm progressively subtracting / merging shapes from each other.

It seems to work for the most part, but I intermittently get an illegal arg exception - Invalid number of points in LinearRing (found 2 - must be 0 or >= 4) when calling PGS_ShapeBoolean.subtract()

I've tried to mock this up in the below code - it will stop at the offending PShape and draw in pink. All the 'valid' PShapes above that one will be drawn in black.

Hoping for some pointers please, maybe I'm misusing the subtract / shape group ? Apologies in advance, I'm new to both Java & Processing!

subtract

import micycle.pgs.*;

void setup() 
{
  size(3000, 1800);
}

void draw()
{  
  noLoop();
  ArrayList<RandomPoly> polys = new ArrayList<RandomPoly>();
  int max = 20;

  //create objects, randomly placed over each other
  for (int i = 0; i < max; i++)
  {
    boolean intersectsPrevious = false;
    while (!intersectsPrevious)
    {
      RandomPoly p = new RandomPoly();

      if (i == 0)
        intersectsPrevious = true;
      else 
      intersectsPrevious = PGS_ShapePredicates.intersect(polys.get(i - 1).shape, p.shape);

      if (intersectsPrevious)
      {
        polys.add(p);
      }
    }
  }

  //now plot them in reverse order
  PShape group = polys.get(polys.size() - 1).shape;
  for (int i = polys.size() - 2; i >= 0; i--)
  {
    RandomPoly background = polys.get(i);
    PShape merged = mergeShapes(background.shape, group);

    if (merged == null)
    {
      shape(background.shape); //draw invalid shape in pink and stop
      break;
    } else
    {
      group = merged;
    }
  }

  PGS_Conversion.setAllStrokeColor(group, color(0), 1);
  shape(group);
}

//Here we are taking a background and foreground shape, and making a group of out of 
// 1 - the foreground shape, and 
// 2 - the subtraction of foreground from background
//This allows us to send to a plotter that has no concept of fill() 
PShape mergeShapes(PShape backgroundShape, PShape foregroundShape)
{
  PShape g = createShape(GROUP);

  g.addChild(foregroundShape);
  try
  {
    g.addChild(PGS_ShapeBoolean.subtract(backgroundShape, foregroundShape));
  }
  catch(Exception e) //java.lang.IllegalArgumentException: Invalid number of points in LinearRing (found 2 - must be 0 or >= 4)
  {
    print(e);
    return null;
  }

  return g;
}

class RandomPoly
{
  PShape shape;
  PVector pos;

  RandomPoly()
  {
    float xStart = randomGaussian() * 200 + width / 2;
    float yStart = randomGaussian() * 200 + height / 2;

    pos = new PVector(xStart, yStart);
    shape = PGS_Transformation.translate(
      PGS_Construction.createRandomPolygon(4, width / 4, height / 4), 
      pos.x, 
      pos.y);
  }
}
micycle1 commented 3 years ago

The library assumed that PShapes with isClosed() as true would have to be polygonal, but seems that some lines (shapes with 2 vertices) are slipping through as 'closed', causing an error trying to create a polygon from them. I've made a code change to fix this.


With the way you've coded the approach you're generating lots of group PShapes with nested components. These can all be seen when we translate them randomly.

image

Another approach could be: for each layer, subtract from it the union of all its upper layers (however I haven't tested this).

mrwoodo commented 3 years ago

Thank you @micycle1 can't wait to try it out! I tried to use the updated .jar from jitpack - It is a lot smaller (140kb) compared to the original I grabbed from /releases. I'm guessing it doesn't have all the dependencies bundled in, sorry it's all a dark art to me at the moment.

micycle1 commented 3 years ago

Yeah, that's correct. The Jitpack version doesn't include dependencies. As it's a maven artifact, it's designed to be used with Maven which will download all dependencies automatically.

I'll release a new version at some point -- now I'm working on some other features to be included in it.