metafizzy / zdog

Flat, round, designer-friendly pseudo-3D engine for canvas & SVG

Home Page:https://zzz.dog

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to set a list of clickable shapes

gregja opened this issue · comments

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.

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.

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.

commented

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

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

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

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

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

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.

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

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 ;)

@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

@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 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.

@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

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!

@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.

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.

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.