Mouse Picking

From C4 Engine Wiki

Jump to: navigation, search

Hello everyone. I have been reading the forums and the wiki for quite a while now, and it seems that a lot of people are having trouble doing what I just accomplished: Mouse Picking. This might not be the best written tutorial, and they may not be the most Elegant solutions, but it is my first one ever so please bare with me. If you have a better / more efficient way to do something that I explain, please feel free to fix it.

In order to use mouse input in the C4 engine, the only way I have been able to do it is by using a handy little class called the MouseEventHandler.

SimpleBall.h

Find the lines

MovementAction	*forwardAction;
MovementAction	*backwardAction;
MovementAction	*leftAction;
MovementAction	*rightAction;
MovementAction	*upAction;
MovementAction	*downAction;

and put these underneath them

MouseEventHandler *mouseInput;
static void HandleMouseInput(EventType eventType, const Point& point, void *data);

also, in the public section of the Game class, put this line

static void GetWorldRayFromClick(const Camera *camera, const Point& p, Ray *ray);
Ray testRay;


SimpleBall.cpp

Now we are ready to head over to SimpleBall.cpp and add the real code :) In the Game::Game() constructor just put at the bottom these two lines

mouseInput = new MouseEventHandler(&HandleMouseInput, this);
TheEngine->InstallMouseEventHandler(mouseInput);

Now for the meat of your mouse input system, the HandleMouseInput function. Find a nice place to put the code for your function somewhere in the file, and add these lines.

void Game::HandleMouseInput(C4::EventType eventType, const C4::Point &point, void *data)
{
	switch(eventType)
	{
	case kEventMouseDown:
                //add code to toggle a bool of the LMButton state
		TheGame->GetWorldRayFromClick(TheWorldMgr->GetWorld()->GetCamera(), point, &TheGame->testRay);
                TestTheRay(); //A little hack function... will explain more later.
		break;
	case kEventMouseMoved:
		//put code to update your mouse cursor position
		break;
	case kEventMouseUp:
		//add code to toggle a bool of the LMButton state
		break;
	}
}

Ok, let me take a minute to explain some things. After you added the line TheEngine->InstallMouseEventHandler(mouseInput); to the constructor, this tells the engine that anytime you move your mouse, click a button, move the scroll wheel, or anything of that sort, it is going to gather some data about the event and call the function you told it to, in this case HandleMouseInput. Now it is up to your function what to do with these events. You could have it blank and not do anything, or you can expand it into a fully functional input system. The eventType passed in to your function tells it what has happened that has made the function get called. The eventType can be any one of these following types:

kEventMouseDown 	| The left mouse button was pressed.
kEventMouseUp 	        | The left mouse button was released.
kEventRightMouseDown 	| The right mouse button was pressed.
kEventRightMouseUp 	| The right mouse button was released.
kEventMouseMoved 	| The mouse location was moved.
kEventMouseWheel 	| The mouse wheel was moved.

Then you can do as I did and make a little switch statement that sections off what you do for each event.

In my own system, I have each one of these events toggle some sort of bool variable, and then each frame I run a seperate function that executes different actions according to these toggles. That way, when you receive a mouse down event, you can set a bool like LMButtonDown to true, and each frame, if you haven't gotten a mouse up event, you can treat the button as it were always down... Which is what you typically want... But enough of that, that is for a different article.


This next code was so nicely provided by Eric, and it is itself listed in the Code Snippets section, but you need it for this code to work, so I will include it here.

void Game::GetWorldRayFromClick(const Camera *camera, const Point& p, Ray *ray)
{
    // Get the viewport rect for the camera.
    const Rect& viewRect = camera->GetObject()->GetViewRect();
 
    // Normalize the click coordinates.
    float x = p.x / (float) viewRect.Width();
    float y = p.y / (float) viewRect.Height();
 
    // Cast a local ray from the camera.
    camera->CastRay(x, y, ray);
 
    // Transform the ray into world space.
    const Transform4D& cameraTransform = camera->GetNodeTransform();
    ray->origin = cameraTransform * ray->origin;
    ray->direction = (cameraTransform * ray->direction).Normalize();
 
    // Set the rest of the ray fields.
    ray->radius = 0.0F;
    ray->tmin = 0.0F;
    ray->tmax = 100.0F;   // Set to max distance ray will go.
}

If you have any questions about this code, please direct them to Eric as this is his code, and while I understand it, I probably would not be the best one to answer questions on this. This code basically does in C4 what gluUnProject does in OpenGL. It basically takes the window X and Y coordinates of the mouse and transforms it based on the cameras view matrix to get a 3D ray that you can use to detect collisions in the scene.

Now I know that this next part is a dirty hack. All that I did was write a little tiny function that sits right above my HandleMouseInput function and does a simple command. Remember, put this code above your HandleMouseInput code

///////////////////
///// Dirty  //////
///////////////////
void TestTheRay()
{
	Point3D origin = TheGame->testRay.origin;
	Point3D target = origin + (TheGame->testRay.direction * Vector3D(1000.0f, 1000.0f, 1000.0f));
	CollisionData data;
	TheWorldMgr->GetWorld()->DetectCollision(TheGame->testRay.origin, target, 0.5f, 0, &data);
}

Now what this code does is it takes your ray and uses it in the function DetectCollision. DetectCollision looks like this

bool DetectCollision(const Point3D& p1, const Point3D& p2, float radius, unsigned long clas, CollisionData *data) const;

Now because the Ray class does not simply give you a p1 and p2, you have to do a little something to use it like this. In my function,

Point3D target = origin + (TheGame->testRay.direction * Vector3D(1000.0f, 1000.0f, 1000.0f));

basically I am detecting if there is a collision between the origin of the ray (the camera) and a point 1000 times farther along the direction of the ray. If there is then your CollisionData data fills up with all sorts of good information about what you hit.

Conclusion

You now have a very simple system that will take your mouse click and cast a 3D ray into your scene. From that point, the TestTheRay function does very little, and was only used by me to debug if the process was working. In order for this to do anything however, now you are going to have to code around this process to make the collisions do things like select that unit on the screen or etc...

PS. In order for this to work, you have to open the console with the grave button (`) and type load <worldname> such as 'load Demo', without the quotes.

Personal tools