micycle1 / PGS

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

Expected a different result from `PGS_ShapeBoolean.union()` #45

Closed mheesakkers closed 2 years ago

mheesakkers commented 2 years ago

Hi Micycle,

This seems like a great library thanks! I was testing the boolean functionality with two simples shapes an ellipse and a rectangle but it didn't gave me the result I was expecting. It seems the PShapes change positions when united with each other? The expected behaviour would be a unified shape at the same position I'd say. Thought?

import micycle.pgs.*;

void setup() {
 size(595, 842, P2D);
}

void draw() {
  background(0);

  PShape shapeOne = createShape(ELLIPSE, 100, 100, width * 0.4, width * 0.4);
  shapeOne.setFill(#ffff00);

  PShape shapeTwo = createShape(RECT, 150, 150, width * 0.5, width * 0.5);
  shapeTwo.setFill(#00ff00);

  shape(shapeOne);
  shape(shapeTwo);

  PShape union = PGS_ShapeBoolean.union(shapeOne, shapeTwo);
  union.setFill(#ff0000);
  translate(0, 300);
  shape(union);

}

In yellow and green the source shapes. In red the unified shape, slightly translated on the y-axis.

Screenshot 2022-01-04 at 19 07 12
micycle1 commented 2 years ago

createShape() essentially creates a shape whose vertices are centered on (0, 0); a shape's translation is not encoded in its vertices' coordinates but rather in the shape's affine transformation matrix (which is applied afterwards to a shape's vertices whenever the shape is drawn).

PGS doesn't take into account this translation matrix (because it's not actually a public/accessible field), and reads the shape's raw coordinate values only.

One way around this is to first translate the shape using PGS_Transformation.translate(). After doing this a shape's vertices' coordinate values will actually encode the translation. Then PGS works as expected:

import micycle.pgs.*;

void setup() {
  size(595, 842, P2D);
}

void draw() {
  background(0);

  PShape shapeOne = createShape(ELLIPSE, 0, 0, width * 0.4, width * 0.4);
  shapeOne = PGS_Transformation.translate(shapeOne, 100, 100);
  shapeOne.setFill(#ffff00);
  shapeOne.setStroke(false);

  PShape shapeTwo = createShape(RECT, 150, 150, width * 0.5, width * 0.5);
  shapeTwo = PGS_Transformation.translate(shapeTwo, 150, 150);
  shapeTwo.setFill(#00ff00);
  shapeTwo.setStroke(false);

  shape(shapeOne);
  shape(shapeTwo);

  PShape union = PGS_ShapeBoolean.union(shapeOne, shapeTwo);
  union.setFill(#ff0000);
  translate(0, 300);
  shape(union);
}

image

micycle1 commented 2 years ago

A possible fix/workaround to such issues arising from shapes created via createShape() (and any calls to translate() or rotate(), that applies to all shapes) is to have PGS read a PShape's affine matrix and apply any transformations to the shape's vertices before further processing. The PShape affine matrix (matrix) will need to be accessed via Java reflection as it is a protected field.

micycle1 commented 2 years ago

The fix mentioned above has been implemented in https://github.com/micycle1/PGS/commit/ecb62927a97269e7f9a133d8bc975c8e31a30bc0.