Game Development Community

Mouse Cursor

by Devboy · in Torque X 2D · 01/21/2007 (11:55 am) · 17 replies

Can anyone suggest how to add a mouse cursor and picking into my game?

#1
01/21/2007 (12:00 pm)
this.IsMouseVisible = true;

I'm not sure what "and picking into my game" means?

#2
01/21/2007 (12:01 pm)
Hi,

I meant object picking. Meaning selecting which object is under the mouse.

[edit]

T2DSceneObject has this property:

public bool PickingAllowed { get; }

In which context does it have meaning?
It was false, and I couldn't figure out how to change it.

Any ideas?
#3
01/21/2007 (12:35 pm)
I'm still not entirely sure what you mean. Do you mean you want things you click on to trigger 'onClick' events? Do you mean you want to be able to click on something and have the app know where you clicked?

I'm currently working on an entire event system for keyboards and mice. It's coming along nicely and I'm able to trigger onMouseUp, onMouseDown, onKeyUp, onKeyDown, as well as timed keys (after x miliseconds of holding down the key, it triggers an event again), etc.

I'm not sure if I'll release it as a resource or not as I'm not sure what GG has planned in this area, but I could help out with simple questions.

#4
01/21/2007 (1:39 pm)
I'm trying to get a reference to the scene object(s) that is under my cursor.
#5
01/21/2007 (1:40 pm)
Yer SOL for now then as that hasn't been set up. You can roll your own or wait for the 3D stuff to get put in (as I doubt GG is focusing on anything mouse related until 3D)

As I said, I am writing up an EventManager which will do just that. I might post it as a resource soonish.

#6
01/21/2007 (5:19 pm)
Devboy, you're obviously not SOL for now as Jonathan obviously has found a way to do it. I remember a post somewhere on the forums about mousing in TX and a GG employee mentioned that mouse was supported through the input manager stuff. Maybe you can use that as a jumping off point to set up mouse axis and button controls and use processTick on one of your components to check mouse position and button state to do a collision check at that point.

#7
01/21/2007 (6:08 pm)
... Don't know how my post came off sounding 'better than you', but instead was:

Quote:
You can roll your own or wait for the 3D stuff to get put in (as I doubt GG is focusing on anything mouse related until 3D)

So I'm not quite sure where that is stating that "you can't do it unless you're me." I created the event manager for my game, so it's pretty married into it at this point. The reason I said I may post it as a resource is that I'd have to strip it out and remove the custom stuff that wouldn't fit in a generic implementation and I'm not sure if I'll have time to do that anytime soon.

Maybe you should reread my post and edit your statements to not attack me?

GG said they could handle binding left, right, middle, and x and y movements to commands much like you do with the keyboard, but not events. XNA has a mouse class and mousestate class which you use to determine where the mouse is however, so that would be where you'd want to start.

#8
01/22/2007 (9:15 am)
"Tensions are high... let's just hope some robot doesn't kill everybody!"

Seriously, play nice or I'll turn this forum around.

I'll do it.



Ok, I can't, but play nice anyway.
We're all here to have fun, right?
#9
01/22/2007 (11:49 am)
I'm trying to, don't know what the problem is *shrug*

#10
01/22/2007 (4:45 pm)
Focus people! :)

Well I dug some more and found:
T2DSceneGraph.Instance.Container.FindObjects()

Now if someone could tell why it's not working ;)

private void LeftMouseDown(float val)
{
	int mouseX = Mouse.GetState().X;
	int mouseY = Mouse.GetState().Y;
	int viewWidth = GraphicsDeviceManager.GraphicsDevice.Viewport.Width;
	int viewHeight = GraphicsDeviceManager.GraphicsDevic'e.Viewport.Height;

	T2DSceneCamera cam = T2DSceneGraph.Instance.Camera as T2DSceneCamera;
	Vector2 camPos = cam.Position;
		
	float pixelWidth = (float)(cam.SceneMax.X - cam.SceneMin.X) / (float)viewWidth;
	float pixelHeight = (float)(cam.SceneMax.Y - cam.SceneMin.Y) / (float)viewHeight;
			
	T2DSceneContainerQueryData qd = new T2DSceneContainerQueryData();
	qd.FindInvisible = true;
	qd.ObjectTypes = TorqueObjectType.AllObjects;
	//qd.Rectangle = new GarageGames.Torque.MathUtil.RectangleF((mouseX * pixelWidth) - camPos.X, cam.SceneMax.Y - ((mouseY * pixelHeight) - camPos.Y), 1, 1);
	float mouseWorldX = camPos.X + ((mouseX - (float)viewWidth / 2.0f) * pixelWidth);
	float mouseWorldY = camPos.Y + ((mouseY - (float)viewHeight / 2.0f) * pixelHeight);
	qd.Rectangle = new GarageGames.Torque.MathUtil.RectangleF(mouseWorldX, mouseWorldY, 10, 10);
	//qd.Rectangle = new GarageGames.Torque.MathUtil.RectangleF(0, 0, 100, 100);
	qd.onObjectFound += HandleObjectFound;
	qd.IgnorePhysics = false;
	T2DSceneGraph.Instance.Container.FindObjects(qd);
	T2DAnimatedSprite player = TorqueObjectDatabase.Instance.FindObject("Playa") as T2DAnimatedSprite;
	Console.WriteLine("x: {0}, y: {1}; player at {2}", mouseWorldX, mouseWorldY, player.Position);
}
#11
01/22/2007 (5:14 pm)
What exactly isn't working and what exactly are you wanting it to do? What do you have that calls this method? Are you wanting it to be triggered every time you left click? You need to be more specific for us to help.

If you want it to trigger every time you left click, you'll need to bindcommand it just like keyboard keys. Use XMouseDevice.MouseObjects.LeftButton and point it to this method.

Will have more time later to look over the code and what it's doing.

#12
01/23/2007 (7:48 am)
Sorry, wrote it at 3 AM. Not enough info.

As the method name implies, this method is bound to the mouse left button down event.

Each time I click it this code is run.
As I've said before the required end result is to locate which object is under the mouse cursor so I can manipulate it.

According to the TorqueX API:

- Name -
T2DSceneContainer.FindObjects(SceneContainerQueryData queryData)

- Description -
"Perform a spatial query on the container. Fields on the query will determine which objects are found. (inherited from SceneContainer)"

Yes, I'm expecting to get a callback when my mouse is over my player and the left mouse button is clicked.
See, I added a "hardcoded" look up for the player object there in the end. I'm printing out the world position of my query rectangle and the world position of the player and although they are the same I don't get a "hit".

further more, I tried a trivial big-ass rectangle that covers the whole scene (it's up there marked out). Still didn't get a hit. I was hoping someone over at GG would be so kind to let me know if and how this is supposed to work.

Thanks
#13
01/23/2007 (12:32 pm)
Got it!

this was missing:
qd.LayerMask = 0xFFFFFFFF;

This is not enough though. The test isn't against the objects' collision polygon but against their external extent.
I need to perform a point-in-poly test for every potential object.
I will post that when I have it.
#14
01/24/2007 (2:24 pm)
As promised, this code will find if a mouse cursor is on the inside or the outside a collision polygon of a torque scene object.

There are two methods here:
1) Queries the scene graph for all objects who's full extent (i.e. their image's rectangle as it is in world coordinates), intersects with the mouse cursor's tip.
2) Takes all the objects the spatial query above returned and does a fine grained test against their collision polygon. It does this by testing for each edge in the collision skin whether the points is in front (the inside of the convex collision poly), or the back of it. If it is on the back of one of the edges, it is outside the collision poly altogether.

I'm guessing there could be more efficient way to do this (and GG will hopefully add this type of search to the scene graph querying :)), but this will do for now.

Hope it helps someone

private void LeftMouseDown(float val)
{
	int mouseX = Mouse.GetState().X;
	int mouseY = Mouse.GetState().Y;
	int viewWidth = GraphicsDeviceManager.GraphicsDevice.Viewport.Width;
	int viewHeight = GraphicsDeviceManager.GraphicsDevice.Viewport.Height;

	T2DSceneCamera cam = T2DSceneGraph.Instance.Camera as T2DSceneCamera;
	Vector2 camPos = cam.Position;
	
	float pixelWidth = (float)(cam.SceneMax.X - cam.SceneMin.X) / (float)viewWidth;
	float pixelHeight = (float)(cam.SceneMax.Y - cam.SceneMin.Y) / (float)viewHeight;
	
	T2DSceneContainerQueryData qd = new T2DSceneContainerQueryData();
	qd.FindInvisible = true;
	qd.ObjectTypes = TorqueObjectType.AllObjects;
	float mouseWorldX = camPos.X + ((mouseX - (float)viewWidth / 2.0f) * pixelWidth);
	float mouseWorldY = camPos.Y + ((mouseY - (float)viewHeight / 2.0f) * pixelHeight);
	qd.Rectangle = new GarageGames.Torque.MathUtil.RectangleF(mouseWorldX, mouseWorldY, 1, 1);
	qd.IgnorePhysics = false;
	qd.ResultList = new List<ISceneContainerObject>();

	T2DSceneGraph sg = T2DSceneGraph.Instance;
	T2DSceneContainer sc = sg.Container as T2DSceneContainer;
	qd.LayerMask = 0xFFFFFFFF;
	sc.FindObjects(qd);
	//T2DAnimatedSprite player = TorqueObjectDatabase.Instance.FindObject("Playa") as T2DAnimatedSprite;
	//Console.WriteLine("x: {0}, y: {1}; player at {2}", mouseWorldX, mouseWorldY, player.Position);

	foreach (ISceneContainerObject containerObj in qd.ResultList)
	{
		bool b = TestPointInCollisionSkin(containerObj as T2DSceneObject, new Vector2(mouseWorldX, mouseWorldY));
		//Console.WriteLine("Point is inside:{0}, object {1}", b, (containerObj as TorqueObject).AutoName);
	}
}

private bool TestPointInCollisionSkin(T2DSceneObject t2DSceneObject, Vector2 testPoint)
{
	if (t2DSceneObject.Collision == null)
		return false;

	Vector2[] vertices = (t2DSceneObject.Collision.Images[0] as T2DPolyImage).CollisionPoly;
	Vector3[] edges = new Vector3[vertices.Length];
	bool bInside = true;
	Vector3 edge, edge2, testVector, cross, normal;
	float dot = 0;

	//transform the testPoint to object space
	testPoint = Vector2.Transform(testPoint, Matrix.Invert(t2DSceneObject.Transform));

	for (int i = 0; i < vertices.Length; i++)
	{
		//Get the polygon edge and the vector to test (which is on the polygon plane)
		if (i == vertices.Length - 1)
			edges[i] = new Vector3(vertices[0] - vertices[i], 0);
		else
			edges[i] = new Vector3(vertices[i + 1] - vertices[i], 0);				
	}

	for (int i = 0; i < edges.Length; i++)
	{
		edge = edges[i];

		if (i < edges.Length - 1)
			edge2 = edges[i + 1];
		else
			edge2 = edges[0];
		
		//create the vector i'm testing against
		testVector = new Vector3(testPoint - vertices[i], 0);

		//Get the vector perpendicular to the two edges
		cross = Vector3.Cross(edge, edge2);

		//Get the normal to the edge
		normal = Vector3.Cross(cross, edge);

		//Normalize normal and test vectors
		normal.Normalize();
		testVector.Normalize();

		//Get the dot and see if angle is larger than 90 degrees (test is behind the edge)
		Vector3.Dot(ref normal, ref testVector, out dot);
		if (dot < 0)
		{
			bInside = false;
			break;
		}
	}
	return bInside;
}
#15
01/28/2007 (2:10 pm)
Thanks Devboy,

Very useful code as I was just approaching the same problem myself.
#16
01/28/2007 (3:10 pm)
Haven't read through the actual code yet, but you state in the message that it's looking at the collision polygon instead of the actual object's location. Why wouldn't you look at the location so that any object, with or without collision, can be detected? You'll obviously get the background, but you can just set the backgrounds at certain layers and disclude those in your click test. Just a thought.




www.linkedin.com/img/webpromo/btn_viewmy_160x25.gif
#17
02/05/2007 (11:22 am)
@Devboy - didn't test the code or anything, but nice work. This functionality is on our short list of things we need to add shortly after 1.0. Glad to see that you have a solution for people here and now though.