Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to set a list of clickable shapes #75

Open
gregja opened this issue Jun 26, 2019 · 18 comments
Open

How to set a list of clickable shapes #75

gregja opened this issue Jun 26, 2019 · 18 comments

Comments

@gregja
Copy link

gregja commented Jun 26, 2019

I wouldlike to create small games with Zdog, so I need to be able to click on each shape individually.

I solved the problem with an old technique which is working pretty weel with Zdog : I created 2 canvas, one for the demo and a second I named "ghost" and which is a copy of the first. But not an exact copy because each shape has a unique color which is stored in a catalog of colors.
When I click on the "demo" canvas, I store the coordinates of the mouse and I check which color has those coordinates on the "ghost canvas". So I know which shape is clicked.

You can see an example on that pen :
https://codepen.io/gregja/pen/rEGmGB

and I added the code in my repository :
https://github.com/gregja/zdogXperiments

It's experimental, but it's a good beginning : don't hesitate to suggest some improvements, all ideas are welcome.

Be careful : don't use anything else than the "getMousePos" function I wrote in the script. I tested different versions and it's the only one which works well with Chrome.

@gregja
Copy link
Author

gregja commented Jun 26, 2019

Sorry I forgot one thing : I don't know - for the moment - how to synchronize the draggable mode between the vibible canvas and his ghost, so I deactivated the draggable mode.

@gregja
Copy link
Author

gregja commented Jun 27, 2019

Hi, I fixed a small bug tonight, on my example of "ghost canvas" : there was a bad synchro between the 2 canvas for the arrow keys.

@webappslove
Copy link

Thanks a lot for this! Here's a fork with enabled dragRotate: https://codepen.io/ncodefun/pen/aggZKP

@gregja
Copy link
Author

gregja commented Jul 15, 2019

Thank's a lot to @webappslove, it works fine :)

@oganm
Copy link

oganm commented Aug 29, 2019

I will be attempting to implement a generalizable version of this method into my wrapping of zdog since one of the primary use cases is to use it as an input device but it is somewhat annoying to do. Was wondering if there was a plan to implement a built in way to get clicked objects. @desandro

@oganm
Copy link

oganm commented Sep 17, 2019

I was able to implement a generalization solution that I hope is scalable. Instead of maintaining two canvases and keeping their rotations in sync, at click time I create a new canvas, a new illustration and copy contents of the current illustration to the new canvas. The new canvas has a black background and I iterate over the child objects of the the illustration to give them colors by incrementing the color value by 1 whenever a child with a color/backface is encountered. The click then returns the pixel value which is turned back into an integer to act as the object id.

I am new to javascript so this is not probably not the best way to do it. Some problems it has:

  • Adding child objects to pre-existing objects will shift the ids of all elements which is not ideal. I was trying to find a way to get some identifying information from the child objects but I wasn't able to do so
  • Single objects will occupy multiple ids since the iteration doesn't understand the nature of the object it is iterating over. It seems that backface of objects are also children with the color property so an ellipse with a backface will occupy 2 ids. This was a bug on my side
  • I couldn't find a way to make this work for the SVG context. I add an event clistener to the svg using svg.addEventListener and it triggers when the background of the svg is clicked but the objects drawn within the context don't seem to inherit this property. I wasn't able to force zdog objects to trigger on a click. My understanding is limited but I suspect editing the individual svg elements is required which would be fairly difficult without fiddling with zdog itself.
  • Still a little wonky when resize: true. I didn't delve deep into how resize exactly works but as it stands the two canvases don't quite sync up

Don't have a codepen but relevant code can be found here

Edit: I was able to get the specific object that was clicked by appending an ID to every object after creation by looping around the original object whose children has id's appended to them. New version is here

@blurymind
Copy link

blurymind commented Oct 17, 2019

I was actually toying with the idea of creating a zdog modeler and thinking of using zdog itself to draw the move/scale/rotate handles of selected objects. In order for that to work I need to be able to detect when the user has clicked on shapes

It would be very nice if click detection was a part of zdog's api instead of a hack

@gregja
Copy link
Author

gregja commented Oct 17, 2019

Hi @blurymind ,
I think you're right, the actual dragger mode is associated to the global canvas. I didn't take the time to check but I suppose the quaternion object used by zdog for rotations is working with a unique instance for the moment.
The problem is not easy to solve but it is very interesting.
For the dragging mode, I didn't find for the moment a solution which satisfies me.
For the quaternion, I'm not a specialist so maybe I'm wrong, but I suppose we need a quaternion instance for each object "rotable". Maybe the quaternion object of zdog is enough flexible to be adapted, it's a way interesting to learn.

@blurymind
Copy link

blurymind commented Oct 17, 2019

perhaps the api can simply detect when the user has clicked on a shape and trigger a callback like this:

let selectedObject = null
//my callback here
function myCallback(event) {
  console.log("clickedObject", event.zzdog.object)
  //from there on we can translate the object while the mouse is down inside mousedrag event
  // selectedObject.translate.x += mousedragDistance
  selectedObject = event.zzdog.object
}

new Zdog.Ellipse({
  addTo: illo,
  diameter: 80,
  translate: { z: 40 },
  stroke: 20,
  color: '#636',
  onClick: myCallback  //<-- set here
});

The developer then only needs to capture the release event. This would make it super easy to implement manipulation handles and object selection.

But I guess we would also need some way of implementing a box selection of multiple objects. How would we do that in a simple way?

Anyways, this is super interesting to me, but from the standpoint of wanting to make an open source editor for zzdog content

@gregja
Copy link
Author

gregja commented Oct 17, 2019

You're right @blurymind, but the onclick event is easier to implement with a SVG graphic, it's more complex with a canvas. Complex subject, but really interesting ;)

@blurymind
Copy link

blurymind commented Oct 18, 2019

@gregja zzdog is rendering svgs right? Can't we make any shape clickable just by passing to the rendered svg a callback function upon creation?

https://stackoverflow.com/questions/2296097/making-an-svg-image-object-clickable-with-onclick-avoiding-absolute-positioning

Edit: ah I see, its challenging because it happens inside a canvas element

Could we add some onclick event listener inside the shape?
https://github.com/metafizzy/zdog/blob/master/js/shape.js#L207
There must be a way to get to it

@stevensona
Copy link

stevensona commented May 23, 2020

@blurymind If you are willing to live with svg rendering, you can add event handlers to shape's render elements (paths) using existing public API e.g.

var shape = // ... some Zdog Shape with fill: true
shape.getRenderElement(null, Zdog.SvgRenderer).addEventListener('click', () => alert('the shape was clicked!'));

Caveats:

  • Shape must be filled for path to fire mouse/click events
  • Must use Svg Renderer -- Zdog Illustration element is <svg>
  • getRenderElement's first param is ctx but is not used, so null (or anything) can safely be passed in.
  • the getRenderElement API is strange
  • Does not play nicely with Draggable which seems to hijack mousedown, mouseup, etc events.

Seems a little easier to deal with then creating a "ghost canvas" and checking pixel colors. I am willing to live with the listed caveats, but I wish there was a better API for it. I briefly considered adding something, but given that it only works with SvgRenderer, I doubt there will be much interest.

@ceruulean
Copy link

I think there is a way with Canvas API if it's implemented in the library. isPointInPath, isPointInStroke will accept a Path2D. But as of now, Path parameter is not supported in Safari.

@coder787
Copy link

@blurymind If you are willing to live with svg rendering, you can add event handlers to shape's render elements (paths) using existing public API e.g.

var shape = // ... some Zdog Shape with fill: true
shape.getRenderElement(null, Zdog.SvgRenderer).addEventListener('click', () => alert('the shape was clicked!'));

Caveats:

  • Shape must be filled for path to fire mouse/click events
  • Must use Svg Renderer -- Zdog Illustration element is <svg>
  • getRenderElement's first param is ctx but is not used, so null (or anything) can safely be passed in.
  • the getRenderElement API is strange
  • Does not play nicely with Draggable which seems to hijack mousedown, mouseup, etc events.

Seems a little easier to deal with then creating a "ghost canvas" and checking pixel colors. I am willing to live with the listed caveats, but I wish there was a better API for it. I briefly considered adding something, but given that it only works with SvgRenderer, I doubt there will be much interest.

I made a codepen of this SVG click idea, some interesting things like the shape doesn't have to be filled I think, I put comments in the code.

https://codepen.io/coder787/pen/MWJOavG

@edwonedwon
Copy link

I am absolutely desperate for click detection on shapes as well.

I want to be able to make it so users can click and drag little animals in my ZDog scene, and I can't do it without click detection. All of the ideas in this thread, while clever, each have their drawbacks. What we need is a simple way to do this built into the API.

With click detection, you could build entire games or even functional websites with ZDog, the potential is huge!

@ziebzie
Copy link

ziebzie commented Apr 28, 2022

@gregja @oganm @blurymind @edwonedwon @coder787 Hi, any progres in ZDog cklickable elements? Im very into it now, and I was happy like a child when i discovered this topic. please let me know guys if you made any progress with this function. Cheers.

@oganm
Copy link

oganm commented Apr 29, 2022

No updates on my part and haven't been checking the latest discussions here but the code should still work for canvas outputs with the old caveats (no resizing, no svg support and written by someone who doesn't really know javascript)

These should be enough to replicate it.

this piece where you create a listener for mouse clicks on the original canvas.

This part creates an empty and invisible canvas to be used later. x here (widget in the function below) just includes the specifications of the canvas.

and this function that is executed on a click. It uses the invisible canvas to create a copy of the original canvas with a unique color for each item and decides which item you just clicked based on the color under the mouse if it was clicked on this invisible canvas.

Probably not the best way to do it but was working last time i checked.

@bhushan6
Copy link

I used the same technique to add event listeners in react-zdog library. Works really fine, though syncing the ghost canvas and visible canvas was challenge, I managed to do it using Proxy object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

10 participants