PShapeSVG contains() function doesn't work whe

2019-08-23 11:55发布

问题:

As the title said, why doesn't it work if I scale the shape doing shape.scale(0.5)?

It doesn't work even if I do shape(0,0,200,200) meaning I draw the shape not in the original dimensions. Is this a bug or I'm missing something?

回答1:

It kind of is a bug, although I'm not sure how severe. As you found out from your test the contains() method doesn't work when transformations(translation/rotation/scale) are used.

I've got two somewhat hacky workarounds:

  1. Storing a separate array of vertices, manually adding(offsetting/translating) and multiplying(scaling) position values then testing if the point lies inside the polygon.
  2. Using a transformation matrix and it's inverse to convert from screen(typical Processing) coordinates to transformed SVG coordinates.

The first solution sounds like a bit of work and pointless duplication of data, not mention annoying when it comes to handling rotations and also error prone for 'stacked'/complex transformations.

The second workaround looks slightly hacky since contains() should've just worked, but it makes uses of Processing classes so it's not that bad. It works like this:

  1. create a transformation matrix to which you apply the transformation you need on your shape(e.g. translate(), rotate(), scale()) and store it
  2. apply this transformation to the shape
  3. store the inverse of this transformation matrix to convert screen coordinates to svg coordinates (with transformations) and this way contains() will work.

The svg comes from Examples > Basic > Shape > GetChild. You can open the sketch folder(Ctrl + K / CMD + K) to get "usa-wikipedia.svg" if you want to test the code as is:

import processing.opengl.*;

PShape ohio;
PMatrix2D coordSysSvgInv;//svg coordinate system inversed

void setup() {
  size(1200, 480,OPENGL);//the catch is, even though we use PMatrix2D, PShape's applyMatrix() only seems to work with the P3D or OpenGL renderer
  PShape usa = loadShape("usa-wikipedia.svg");
  ohio = (PShape)usa.getChild("OH");

  PMatrix2D transform = new PMatrix2D();    //apply transforms(position,rotation,scale) to this matrix
  transform.scale(2);                       //be aware that the order of operation matters!
  transform.translate(-800,-300);           //this matrix can be used to convert from screen coordinats to SVG coordinates
  coordSysSvgInv = transform.get(); //clone the svg to screen transformation matrix
  coordSysSvgInv.invert();          //simply invert it to get the screen to svg

  ohio.applyMatrix(transform);              //apply this transformation matrix to the SVG
}

void draw() {
  //update
  PVector mouseInSVG = screenToSVG(mouseX,mouseY);
  boolean isOver = ohio.contains(mouseInSVG.x,mouseInSVG.y);
  //draw
  background(255);
  ohio.disableStyle();
  fill(isOver ? color(0,192,0) : color(255,127,0));
  shape(ohio);
}
PVector screenToSVG(float x,float y){
  PVector result = new PVector();//create a new PVector to store transformed vector
  coordSysSvgInv.mult(new PVector(x,y),result);//transform PVector by multiplying it to the inverse svg coord. sys.
  return result;
}

I've noticed that the applyMatrix() method only works with the P3D and OPENGL renderers even thought I'm passing a PMatrix2D instance, otherwise this warning appears:

applyMatrix() with x, y, and z coordinates can only be used with a renderer that supports 3D, such as P3D or OPENGL. Use a version without a z-coordinate instead.

The 'cleaner' option is to modify the contains() method in the PShape class, then recompile Processing's core.jar and use the updated jar. If this is for a one-off small project I don't know if it's worth the trouble though, it might faster to have the slightly messier code from above than recompiling/updating core.jar.