nanovg
nanovg copied to clipboard
Any facility to hit test on stroke and fill path?
like function
cairo_in_stroke (cairo_t *cr, double x, double y);
cairo_in_fill (cairo_t *cr, double x, double y);
This is a duplicate issue, see #63
Sorry if I'm bored with this, but no advice on how to achieve it? It might be possible to use the NVGpathCache?
The functionality is not trivial to implement and I have not gotten time to do it. If you're willing to help, I can give you directions how to implement it.
First, we'll need distance to cubic bezier and ray/line cubic bezier intersection algorithms. Then testing a point on stroke is just matter of checking if a point is close enough a bezier segment (correct join/cap handling needs a bit more). Point in fill can be done using ray stabbing (https://en.wikipedia.org/wiki/Point_in_polygon).
thanks for asking, sorry if I steal any more time, but my first idea was to use an triangle intersection (https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm) So which method to adopt to have the data of vectors on which to run the algorithm? Thank you for your time.
The triangles for the stroke are a bit larger to account for antialiasing. For fills you would need to account for the stencil trick (see Drawing Filled, Concave Polygons Using the Stencil Buffer, http://www.glprogramming.com/red/chapter14.html).
You can probably find the algorithms I mentioned earlier from here: http://pomax.github.io/bezierinfo/
I do not know if the system adopted by me is the best one, and obviously I think there are other ways to do this, however, it has been tested and seems to work well.
Here's the code that I used, and how I use it.
int nvgOnStroke(NVGcontext* ctx, float mx, float my, float tol)
{
const NVGpath* path;
int i, j;
float ax, ay;
float bx, by;
for (i = 0; i < ctx->cache->npaths; i++) {
path = &ctx->cache->paths[i];
if (path->nstroke) {
for (i = 0, j = path->nstroke - 1; i < path->nstroke; j = i++) {
ax = path->stroke[i].x;
ay = path->stroke[i].y;
bx = path->stroke[j].x;
by = path->stroke[j].y;
if (nvg__distPtSeg(mx, my, ax, ay, bx, by) < tol*tol) {
return 1;
}
}
}
}
return 0;
}
int nvgOnFill(NVGcontext* ctx, float mx, float my)
{
const NVGpath* path;
int i, j, c = 0;
float ax, ay;
float bx, by;
float flag;
for (i = 0; i < ctx->cache->npaths; i++) {
path = &ctx->cache->paths[i];
if (path->nfill) {
for (i = 0, j = path->nfill - 1; i < path->nfill; j = i++) {
ax = path->fill[i].x;
ay = path->fill[i].y;
bx = path->fill[j].x;
by = path->fill[j].y;
flag = (ay > my) != (by > my);
if (flag) {
if ( (mx < (bx - ax) * (my - ay) / (by - ay) + ax) )
c = !c;
}
}
return c;
}
}
}
...
nvgSave(vg);
...
nvgBeginPath(vg);
nvgFillColor(vg, nvgRGBA(0, 0, 255, 255));
nvgCircle(vg, 100, 50, 20);
nvgFill(vg);
if (nvgOnFill(vg, mx, my))
{
nvgFillColor(vg, nvgRGBA(255, 0, 255, 255));
nvgFill(vg);
}
...
nvgRestore(vg);
...
or this way
...
static int onStroke;
nvgSave(vg);
...
nvgTranslate(vg, x, y);
nvgRotate(vg, 45);
nvgBeginPath(vg);
nvgStrokeWidth(vg, 4.0f);
nvgStrokeColor(vg, nvgRGBA(onStroke? 255:0, 192, 255, 255));
nvgMoveTo(vg, 0, 10);
nvgLineTo(vg, 10, 50);
nvgBezierTo(vg, 0,100, 200, 100, 200, 10);
nvgClosePath(vg);
nvgStroke(vg);
onStroke = nvgOnStroke(vg, mx, my, 4);
...
nvgRestore(vg);
...

I had a slightly different need to picking: I didn't want to test against each path as I rendered I just wanted to be able to query which path (that I have deemed pickable) is under a given point. If this is useful to anyone else as well I have added this functionality to my fork of nanovg at https://github.com/MikeWW/nanovg
It gives two new functions for specifying pickable paths:
void nvgPickFill(NVGcontext* ctx, int id);
void nvgPickStroke(NVGcontext* ctx, int id);
and two new functions for querying pickable paths:
int nvgPick(NVGcontext* ctx, float x, float y);
int nvgPickAll(NVGcontext* ctx, float x, float y, int* ids, int maxids);
A little more info is in the readme in my fork. Internally it deals with the path command lists rather than the tesselations as I didn't want to keep the tesselated meshes around.
I've tried to style the code similar to nanovg so it fits in :-)
@MikeWW That looks really good! Would you be willing to make a PR for that? I have some comments on style and API naming, but otherwise implementation looks thorough.
For API naming, I'd like to use the terms html canvas is using: hit region and hit test. What do you think of:
enum NVGPickFlags {
NVG_TEST_FILL = 1,
NVG_TEST_STROKE = 2,
};
void nvgFillHitRegion(NVGcontext* ctx, int id);
void nvgStrokeHitRegion(NVGcontext* ctx, int id);
int nvgHitTest(NVGcontext* ctx, float x, float y, int flags);
int nvgHitTestAll(NVGcontext* ctx, float x, float y, int flags, int* ids, int maxids);
What do you think of Cairo type of per shape hit testing (cairo_in_fill, cairo_in_stroke)? Your proposed API may need some kind of regioning. I.e. if the user is drawing UI and a simple editor using the API. On one hand the cairo API is far simpler to implement, on the other hand, it forces you to carry out more state around when drawing.
Cool, glad you like it :-) Many thanks for nanoVG - it is a great little library! I'm all up for making a PR and have no problems making some changes.
I have implemented your first naming/API suggestions and have a quick implementation of Cairo style methods working but as I'm travelling atm I will commit them to my fork and create an initial PR when I get home in a couple of hours.
I'm not sure what you mean by "regioning", could you explain further? (Regioning in Cairo seems to be for clipping/masking).
As an aside, I originally wanted the picking code separate so it was easier to cut out if not required but since it needs access to details in nanovg.c I settled on a temporary fix of including nanovg_pick.c at the end of nanovg.c. A better solution is to break out the internal types from nanovg.c into their own private header file. Is that OK with you?
By regioning I mean that you might use nanovg to render a lot of things, but hit test only parts of the code, like:
- UI code
- node graph code (uses picking)
- UI code
- timeline code (uses picking)
So the picking is local to some sections of the code, not global. One option would be to have begin/end for picking section. For the cairo style API, you don't have the above problem, as you test picking on the go, but on the flip side, it forces you to carry around the UI state a bit more.
Header for internal stuff + separate file sound good.
OK, got it.
I tend to just make whatever I need pickable (as you pay no cost if something is not pickable) and use the IDs to differentiate (e.g. there is a mapping of ID -> UI element somewhere). I'm not sure what regioning would buy you other than a unique ID space for each region maybe?
Often treating the ID as a few bit flags as well as a numeric ID, or as two numeric IDs is useful - e.g. top X bits encode node graph/timeline/whatever else and the remaining Y bits encode an element/element part.
Anyway, I'm open to regions if I can understand the use case :-)
Will have a PR shortly.
Any updates on this one? Is there a branch I can use where this PR has been applied? Thanks!
(Edit: it seems PR https://github.com/memononen/nanovg/pull/230 has not been applied yet)
@djowel, there was a bug fix into that PR just now. If you are maintaining your own fork, I recommend picking up that fix - it fixes some subtle problem with beziers.
I am +1'ing for merging that PR, it is very useful and seems that @memononen agreed to merge it some time ago, but it still has not been merged.
Thanks for the info. I see it fixed the rotation problem mentioned in the PR page. Cool!
ping any chance of merging this in @memononen ?
I have tried to pick this up few times, but failed to find the time to really dig into it. I had 2 issues with the code: 1) it should be simple to opt out from it, 2) it did not support all the features (I think it missed text, even it basic form). And right now it's crazy, so not going to happen any time soon.
Thanks for the update anyway!