Two.js: Implementing drag behavior

Created on 31 Dec 2016  Â·  11Comments  Â·  Source: jonobr1/two.js

How would you implement a drag behavior on two.js shapes? The two strategies I can imagine are dragging the underlying DOM elements, which would restrict me to an SVG renderer (but enable the use of helpers such as jquery-ui). Or implementing this "from scratch" using mouse events and point-in-shape detections, which would work with all renderers.

Any thoughts? Existing examples perhaps?

Regards,
Olivier.

enhancement question

Most helpful comment

Hi. Thanks for your answer!

The third option is interesting; I like the fact that it is renderer agnostic. So I implemented a small example of this approach by combining a spatial index (rbush), for efficiency, with some basic circle hit detection for precision: two-js-hit-detection.

In the course of writing this, I encountered some surprising behaviors. I would appreciate to know you take on them.

  1. I had difficulties positioning elements until I understood that I overwrote existing position vectors. You cannot do myShape.translation = new Two.Vector(x,y) but have to keep the existing instance and use myShape.translation.set(x,y) or myShape.translate.x = x. I guess this has to do with events registered on the existing instance.
  2. I wrongly supposed that attributes set on a group were inherited by its owned shapes, excepted if they had the same property redefined (similarly to the "cascading" behavior of CSS). Actually, this is order dependent: if a property is first set on a group, then (with a different value) on its child, the child property will be different (as expected). But if the property is first set on the child, setting it later on its group will overwrite the child property (and all children of this group will have the same value for this property). I interpret this as the group doing a one-time property propagation to all its children, instead of a "cascading" behavior.
  3. I did not find a way to get shapes construction parameters (in my sample, circles centers and radius) once instanced. I would have liked to iterate on shapes and attach hit functions on them once built, to decouple shapes construction from hit detection setup. But I had to do both at the same time, while I still had constructors parameters. Since you are using the "scene graph" approach, I had expected some level of "reflexivity". Is this deliberate, in which case I should attach those properties I am interested with to instances while I build them?

I reckon the first two points cannot be altered without incompatible API or behavior changes. Maybe notes in the documentation could highlight those points?

Thanks,
Olivier.

All 11 comments

You have a lot of options! If you have complicated shapes (beyond circles / squares) and need very accurate hit testing then I would recommend the SVG route. e.g: https://jsfiddle.net/jonobr1/e2uvusf7/

If you have a fixed size canvas then overlaying an SVG element (Two.js) scene on top of the visible one is another way to go: https://jsfiddle.net/jonobr1/6cw5nz5p/4/

I don't have an example for this one, but if you're just hit-testing circles and squares then rolling that as your own is the best.

Hope this helps!

Hi. Thanks for your answer!

The third option is interesting; I like the fact that it is renderer agnostic. So I implemented a small example of this approach by combining a spatial index (rbush), for efficiency, with some basic circle hit detection for precision: two-js-hit-detection.

In the course of writing this, I encountered some surprising behaviors. I would appreciate to know you take on them.

  1. I had difficulties positioning elements until I understood that I overwrote existing position vectors. You cannot do myShape.translation = new Two.Vector(x,y) but have to keep the existing instance and use myShape.translation.set(x,y) or myShape.translate.x = x. I guess this has to do with events registered on the existing instance.
  2. I wrongly supposed that attributes set on a group were inherited by its owned shapes, excepted if they had the same property redefined (similarly to the "cascading" behavior of CSS). Actually, this is order dependent: if a property is first set on a group, then (with a different value) on its child, the child property will be different (as expected). But if the property is first set on the child, setting it later on its group will overwrite the child property (and all children of this group will have the same value for this property). I interpret this as the group doing a one-time property propagation to all its children, instead of a "cascading" behavior.
  3. I did not find a way to get shapes construction parameters (in my sample, circles centers and radius) once instanced. I would have liked to iterate on shapes and attach hit functions on them once built, to decouple shapes construction from hit detection setup. But I had to do both at the same time, while I still had constructors parameters. Since you are using the "scene graph" approach, I had expected some level of "reflexivity". Is this deliberate, in which case I should attach those properties I am interested with to instances while I build them?

I reckon the first two points cannot be altered without incompatible API or behavior changes. Maybe notes in the documentation could highlight those points?

Thanks,
Olivier.

This is some really great work Olivier! The demo is super cool as well! Below are some details that hopefully help explain Two.js further:

  1. You're correct. However we can augment the API so you can do shape.translation = new Two.Vector(x, y);. This is how the children attribute works as well so I think this would be a great enhancement! Thanks for the input.
  2. Correct again! Because of the development process I added methods on a group to apply styles to all children in an "immediate" sense. I'm actually not entirely sure how I would develop a CSS approach, but I think this is a really interesting idea. Alternatively, if you have any suggestions on how to make this more explicit for the short-term so this doesn't confuse other developers I'm all ears!
  3. I'm not sure I entirely understand.., but I see in your example you are using the master branch. The dev branch (https://github.com/jonobr1/two.js/tree/dev) has properties on most primitives for easy manipulation. For example a Two.Circle has a radius with all proper flags, getters, and setters setup so you can modify radius and the shape will confirm. In the version of your demo you have to re-plot the vertices positions to reflect a changed "radius", which property doesn't exist.

Hope this helps!

Dev branch now implements Two.Shape.translation to work in the way you expect. Thanks again for the feedback!

Thanks for your interest and quick answer!

  1. I switched to the dev version and took advantage of your improvement. This works flawlessly! (I had an issue initially since I confused the "development" version with the "dev branch" version...)
  2. In the short term, I suppose a note in the documentation would do, with some example to show that properties setting is order-dependant perhaps? Code-wise, to keep the existing "propagate and forget" behavior, maybe your could keep the current value of a child property if you could somehow detect that it was set beforehand? But you'd still need to be able to detect if a property was directly set on the child or copied from its parent... That's unnecessarily complex, so no, I think some kind of traversal of the scene tree would be required to compute each shape property in a cascading way. Which leads me to point 3...
  3. My understanding is that the scene graph does not keep track of the original properties describing a shape, but only of the informations required to draw it (anchors, transformations, style...). What I would like to do is to be able to apply a visitor pattern to the scene graph, and be informed that I am currently visiting a circle of radius 30, etc. But the informations passed to makeCircle are actually "compiled" to a "draw object", then lost (and not retained as readable properties). This makes sense, since I understand you want us to be able to deform shapes once they are created (as in that great physics demo). But this is then not so much a "scene graph" as a "drawing list hierarchy" (I am not saying this in a derogatory way!). Of course, you mileage/definitions may vary, and I can simply enrich shapes with the informations I need.

Since my demo piqued your interest, maybe I could ask you for some advice? I have not been able to reproduce the "touch device" capabilities of the rubber ball demo, even if I feel I included most of its touch-related events handling rather verbatim. Do you have an idea why I don't get a similar behavior (where finger swiping would color hit circles immediately)?

Regards,
Olivier.

I rewrote the demo to illustrate my third point.

For this purpose I added extra informations to just-created shapes to keep track of their properties. This helped me in isolating hit testing related code in lib/hitTester.js, where I could take advantage of those informations to build the proper hit test with respect to geometrical properties.

Regards,
Olivier.

Awesome! Glad it helps and again sorry for the confusion. Here are more tidbits that I hope help:

For this Extra Information Section you should be okay without adding that. The x and y values reside on circle.translation.x and circle.translation.y respectively. Finally, a circle has a radius attribute which is a getter / setter so you can check against that. The development branch has a number of properties added to each primitive shape so you can set and get those properties you initially apply without worrying about the other.

I'll make a note to try to be more explicit about how Two.Group styles work when affecting child nodes. As far as the touch code from the Rubber Ball demo in order to get your hit-test working.., I'm not totally sure. Could you point me to where you've added your touch code?

It's all pretty vanilla. Add touch events to the two.renderer.domElement and on touchmove get the first touch and set a Two.Vector with the pageX and pageY to get an absolute position of your touch... It could be that you need to add some meta tags so scrolling doesn't happen on the page.., or preventDefault or stopPropagation on the touch events themselves so that "native" handlers are nullified.

Thanks! I took advantage of the new Two.Circle and its properties to simplify my demo further. Indeed I do not need to enrich shapes anymore.

With respect to the touch behavior: here is the relevant code. I also added some scrolling-related meta tag to the HTML.

Any advice would be greatly appreciated!

Regards,
Olivier.

That's great to hear @oparisy! I think the only thing I can find different is that you're listeners are on the document instead of the window. Other than that it looks the same as the Rubber Ball demo...

So I gave my touch support issue a look, following your advice. Actually I cut and pasted the rubber ball code, but forgot to remove the .originalEvent reference, which is jQuery-specific. Now my demo properly react to touch events.

Thanks for your time! I'm closing this issue since my concerns are addressed: drag support can be built upon the hit detection I could demonstrate thanks to your feedback.

Regards,
Olivier.

Glad to hear you got it!

On Sat, Jan 28, 2017, 3:29 AM oparisy notifications@github.com wrote:

So I gave my touch support issue a look, following your advice. Actually I
cut and pasted the rubber ball code, but forgot to remove the
.originalEvent reference, which is jQuery-specific. Now my demo properly
react to touch events.

Thanks for your time! I'm closing this issue since my concerns are
addressed: drag support can be built upon the hit detection I could
demonstrate thanks to your feedback.

Regards,
Olivier.

—
You are receiving this because you were assigned.

Reply to this email directly, view it on GitHub
https://github.com/jonobr1/two.js/issues/209#issuecomment-275842921, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AANbgY4fpdmihu5xLqmrJjX5V30Mw-Goks5rWyamgaJpZM4LYkYh
.

>

http://jonobr1.com/

Was this page helpful?
0 / 5 - 0 ratings

Related issues

oliver-ni picture oliver-ni  Â·  7Comments

Neglexis picture Neglexis  Â·  7Comments

adamdburton picture adamdburton  Â·  6Comments

PranjalVyas1507 picture PranjalVyas1507  Â·  4Comments

trusktr picture trusktr  Â·  8Comments