Mouse hit test/know when mouse is over something.

Started by
16 comments, last by szecs 14 years, 3 months ago
Quote:Original post by szecs
And why would you rotate the items?

Some of the stuff can be rotated by the mouse, so you click on it, move the mouse to rotate it, let go with mouse. If you can think of a better way to rotate than with a rotate transform let me know :P

And I'm going along with the checking, I just thought it might use a lot of CPU usage doing 200+ (4 'if's for 50+ objects) 'if' statements every time WM_MOUSEMOVE is sent which would be perhaps 20 times per second.

Anyway my question still is how can I apply this to object that have changed via a transform since I only know their location before the transform?
Advertisement
You only check when the mouse button is pressed not every time the mouse moves.

// some pseudo code
// if the mouse button is clickedif (mouseButtonDown){    // get the mouse position    mousePos = getMousePos();    // Test ALL possible collisions    if (mouseInRect(mySprite->getRect() )    {        // the mouse clicked on mySprite    }    else if (mouseInRect(myMenuItemSprite->getRect() )    {        // the mouse clicked on myMenuItemSprite    }    else if (etc...)}


and if your using Windows then check into this stuff

POINT mousePos; // Mouse positionGetCursorPos(&mousePos);ScreenToClient(hWnd, &mousePos);RECT clickableZone;SetRect(&clickableZone, 0, 0, 100, 100));if (PtInRect(&clickableZone, mousePos)){    // Mouse clicked in this zone}


and you could do something like this in your sprite class

RECT Sprite::getRect(){	RECT r1;	r1.left   = static_cast<LONG>(position.x);	r1.top    = static_cast<LONG>(position.y);	r1.right  = static_cast<LONG>(position.x + size.x);	r1.bottom = static_cast<LONG>(position.y + size.y);	return r1;}
Thanks for that, I get a better idea of how I'm going to do this now. It makes sense if I do the check when the mouse is down, and that code will be helpful.

Now I only have one more problem, getting the location of an object after a transform. In GDI/GDI+, was there some way to get the position of something after a transform? Because as far as my program knows, it hasn't moved.

Here's what I mean.
//find the angle to rotate by using the mouse position relative to where the rotation will occurangle=atan2((mousePos.y-200.0f),(mousePos.x-250.0f))* 57.29f;//rotate the render target by the angleRT->SetTransform(D2D1::Matrix3x2F::Rotation(angle, D2D1::Point2F(250.0f, 200.0f)));//draw the line from 250,200 to 250,50 on the rotated render targetRT->DrawLine(D2D1::Point2F(250, 200),D2D1::Point2F(250, 50),Black, 12.0, Strokestyle);


My program will always see the line as being from 250,200 to 250,50 while I see it rotated to be facing towards to mouse. I know it'll start at 250,200 to 250,50 but after I've rotated it how will I know it's new position?

EDIT: For the actual hit testing I found this function on MSDN, ID2D1Geometry::FillContainsPoint pretty self explanatory.
Should work for the normal hit testing, may also fix my transform problem but if not I still need to find out how. I'll reply later saying if it fixes it or not.

[Edited by - Waterfox on December 28, 2009 5:37:07 AM]
There's a very effective way to determine if the mouse is over an object after it has been transformed. You transform the mouse coordinates by the inverse of the transformation matrix that transformed the object. Then you can check the modified mouse coordinates against the original object location using exactly the same methods as before.

Simple example:

The mouse is at (10,5). The object you're checking against is centered at (4,1), but you wish to render it at (10,5) instead. Which will make it appear as though it's under the mouse. The translation matrix must apply an offset of (6,4) to move it into the correct position.
Now the inverse of that matrix is (-6,-4), and if you transform the mouse coords by this, the result is (4,1). Which is where the object is recorded as being!
This property extends to any affine transformation, so you can rotate, stretch, skew, etc, and your program will still detect the mouse hit perfectly.

Since you're concerned about optimisation, it would be ideal to compute and store the inverse transformation matrix for each object when they change. Although inverting a 3x3 matrix is quite a bit faster than the 4x4s you deal with in 3D.
In addition to what taz outlines regarding applying the inverse transforms, there are a couple of ways to do this if you need to know, without the user clicking the mouse, what object the mouse is over. It is not too slow to perform a foreach for 50 objects the mouse-move event and catch the reference of the selected object. If you break when finding a mouse-object collision, you'll save some cycles (but again, this should be *negligable* - think about a real-time game, it has way more collisions to detect than fifty objects!)and if you need outrageous efficiency, there's data structures that can sort the objects effectively for searching by coordinate (as mentioned, quad-trees; but you probably don't want to mess around quad-trees if you're struggling with this type of problem)

An alternative is to set up a Timer object, that fires an event every 25-50ms or so. In your MouseMove handler, you update the current mouse position. When the Timer fires, you perform the collision detection on all of your objects. This way the foreach is only executed every time the timer fires, instead of every time the mouse moves. You can even make it so the timer only runs the foreach in cases where the mouse is in the client area, for example - if the position hasn't changed, don't bother running the foreach loop.
Quote:Original post by ddboarm
One thing I was experimenting with using GDI+ and drawing to a panel control (C#) was how to catch the MouseDown, ~Move, etc. No matter which way I looked, all the suggestions pointed towards using a 'foreach' loop to determine the mouse coordinates. Only problem is that the foreach cannot keep up with fast movement of the mouse.

I published static events in the panel that each GDI+ object (Rectangle, etc.) then subscribed to (if it cared). Then, each of my GDI+ objects were made aware of mouse events. I was even told that this was effectively no different than a 'foreach' loop, but performance differences between the two implementations tells a different story.

I know there may be some differences between Direct2D and GDI+, but this solution shouldn't be affected by those differences - at least I wouldn't think so. I, admittedly, am not experienced with any graphics other than GDI+ (yet).


I would be interested to see this profiled; my guess is the bottle-neck was elsewhere; there may have been something specifically in the implementation of the first case that prevented it from performing as efficiently as it should.
Hi!

Well, this is something I've asked long long time ago.
The solution is called "image map".

Lets say you have a 800x600px screen resolution.
Now, in the app's memory you'd create an image.
Let's suppose you know you're not gonna have more than 255 images, or at least "clickable items", on your screen.
Now, in the app's memory you'd create a 800x600px bitmap image with color depth of 255 colors.

Now listen carefully;
Create an array such as "Images *m_color_to_images[255]".
Each "clickable item" will be put in a different slot in that array.
Now draw only the "figure" of each of these items, in the color of that item's index, in a "background image" which you are not gonna render. It's internal.

Now when you want to see which item is being pointed to, you'd get the mouse coordinates.
Then you check that pixel's color. As said in my example, it's maximum value would be 255.
Now that you know the color, it maps to your "m_color_to_images", which will give you a pointer to that item.

Example:
You have 5 images.
Set "m_color_to_images" from index of zero to four a pointer to these images, respectively.
On your "background image" draw these five item's figures, in any order you want, while for example, item indexed '3' will have it's figure color set to '3'.
When you get the mouse pointer's coordinates, check the pixel's color.
If the color is '3' then you know that the user is pointing at this image.
Check your "m_color_to_images" array for the appropriate pointer to that actual image/object.

The down-side is that if your images move a lot, then you'll to update this "background image" map; Clear it and redraw as necessary. Much CPU work I think, but works flawlessly, and you never get into any loop when checking where the user points to.

I hope it's clear. If not, read it again and again.
This technique will grant you the ability to have non-rectangle images!

Good luck!
Quote:Original post by mynick123123
Hi! ...
It is clear and called "color picking" (in a way). I was thinking of the same thing, but it seems to use too much memory for a simple GUI. In most cases, you won't have 255 GUI elements, or if you do, they are aligned or something, so touching each other, like a list/listbox/calendar/whatever, so you could simply calculate which item is clicked.

For example (list):
item = starting_item_index + (mouse_y-list_top_y)/item_height; 


So at the end, you will only have say 20 mouse-inside-area tests, so not much.

Checking it will take say 5 time, rendering say 1000. (=1005)
Optimized the picking 1 time, rendering say 1000 (=1001) (not much)

Color picking could be used in windowed GUI, when you have lot of GUI windows.
So r can be the window ID, g can be item ID, b can be attributes for the item.
This way you can render the windows in proper order on top of each other or anything.

This topic is closed to new replies.

Advertisement