16 June 2007

Performing a hitTest with Silverlight

In my Silverlight coding travels, I've found the urgent need to carry out a hitTest to determine where my end-users mouse is currently located but also what elements are underneath it within Silverlight.

So what's a hitTest you ask? well essentially it's when you ask "Is this object colliding or in contact with that object?". It's used majority of the time in Flash world to determine where a relationship stops and starts with a MovieClip and Mouse (X/Y) coordinates. It can also be commonly used for collision detection with other MovieClips when you make a point and click "fire" game (ie shoot bullets via your mouse at other objects on screen).

Silverlight at present doesn't have a hitTest method, well it does but it's only found within Inking.

Well before one and all start to curse Silverlight for limited functionality compared to Flash (heh), it's not all doom and gloom. Thanks to the hierarchy index within Silverlight, one is able to ask objects individually where the mouse currently resides, but also where it currently is within the elements themselves.

Now for some code..

An example, say you had a Rectangle1 within Canvas1, and you want to find out where the mouse currently is, but also where that mouse is within the actual Rectangle1 that's relative to the Rectangle1 itself (ie if the Mouse x is 30px in from the Rectangle1's edge?).

To do this you would do the following (this is JScript Code btw):

    	// Commit Default Properties.
    	private_commitProperties : function() {
	      var local_owner = this;
		var cnvs = $get("slControl").Content.FindName("Canvas1");
		// EVENT: onMouseMove
        	var local_onMouseMove = function(sender, eventArgs) {
            	local_owner.MouseObj = eventArgs;
            	local_owner.private_updateDisplayList();
        	}
		
        	// Create Events
       	cnvs.addEventListener("MouseMove",  local_onMouseMove);
    	},	

    	// Update the DisplayList.    
    	private_updateDisplayList : function() {
		var cnvs = $get("slControl").Content.FindName("Canvas1");	
		var rect = cnvs.FindName("Rectangle1");

		// Determine where the X/Y Mouse Co-ordinates are 
		// within Silverlight itself.
		var currentX = this.MouseObj.GetPosition(null).x;
		var currentY = this.MouseObj.GetPosition(null).y;
		
		// Determine where the X/Y Mouse is within the Canvas
		var cnvsX = this.MouseObj.GetPosition( cnvs ).x;
		var cnvsY = this.MouseObj.GetPosition( cnvs ).y;

		// Determine where the X/Y Mouse is within the Rectangle
		var rectX = this.MouseObj.GetPosition( rect ).x;
		var rectY = this.MouseObj.GetPosition( rect ).y;


		// IsMouseInsideRect ?
		var isMouseInsideRect = false;
		if( rectX >=0 && rectX <= rect.GetValue("Width")) {
			isMouseInsideRect  = true;
		} 

		// IsMouseInsideCanvas ?
		var IsMouseInsideCanvas = false;
		if( cnvsX >=0 && cnvsX <= cnvs.GetValue("Width")) {
			IsMouseInsideCanvas = true;
		} 
		
		if( IsMouseInsideCanvas  & IsMouseInsideCanvas ) {
			window.status = "Mouse is Inside Rectangle";
		} else {
			window.status = "Mouse is Outside Rectangle";
		}
    },

So what's happening here? Firstly I'm using ASP.NET AJAX ToolKit so the $get() is found within this framework. I'm also wiring up a local object to react to the MouseMove event, which in turn invokes private_updateDisplayList() method.

I then inside JavaScript create two variables (cnvs & rect) as pointers to XAML elements within Silverlight.

MouseObj you'll note was declared inside the local_onMouseMove method, which essentially translates to Silverlights MouseEventArgs and inside this object, you have a method GetPosition() which returns a Pointer (x/y).

Using this, I'm able to then determine in the first round (currentX/currentY) where the Cursor inside Silverlight currently is (why, not important in the above example but thought it's worth noting you can achieve this simply by providing null as your reference object).

The second round, I then determine where the Cursor is currently at in relation to the Canvas1 (cnvsX/cnvsY), which should return the same results as currentX (seen as though Canvas1 is the root element).

I then determine where the Cursor is located within the Rectangle1 element itself. Now, what will happen here is if the Rectanle1.x is located on 100 pixels from the left, and the Cursor is located 150 pixels from the left, the rectX will return "50" as its result.

Ahhh so now you see, that the GetPosition() method returns the appropriate x/y coordinates relative to the reference object in question and local x/y as the result. Important to know this one!

Lastly, I then determine if the Cursor is within the Rectangle1 boundaries, and to do this it's a simple case of asking "Does the rectX sit between 0 and the width of Rectangle1?, if so then it's within"

Note: If Rectangle1 has a width of 200px and the Cursor is located at 400px from the edge of Rectangle1rectX will still return a positive integer which is essentially cursor's actual position (Math.round(currentX-rectX)) relative to the Rectangle1 (Summary: rectX will always return positive integer never a negative one).

Where to from here?

Don't be afraid, Silverlight 1.1 is basically alpha and all this shows is that with enough brain power a CursorManager Class could be put together quite easily and managed code approach to using hitTest logic could apply here. The overall walk-away point for all is that, Silverlight has a lot of powerful primitives in place, enough for anyone to build upwards from and create their own approach to Silverlight. Combining this effort with AJAX and one could get some interesting HTML/Silverlight offerings on the table.

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# WynApse said:

Silverlight Cream for June 16, 2007

16 June 07 at 5:50 PM
# Nick on Silverlight and WPF said:

I put together a little Silverlight app demonstrating how to drag and drop between different areas of

27 June 07 at 9:38 PM
# stefano solinas said:

I'm trying a similar  technique, but I just found that if I rotate the object collision don't understand rotation, any new idea?

put @gmail after obsidianart

06 May 08 at 11:22 AM

Leave a Comment

Comment Policy: No HTML allowed. URIs and line breaks are converted automatically. Your e–mail address will not show up on any public page.

(required) 
(optional)
(required) 

About scbarnes

Scott Barnes currently is a Rich Platform Product Manager (WPF & Silverlight). He has been working with Adobe/Macromedia technology for the past 10 years with a main focus specifically on Internet Applications (aka. RIA, Rich Client Technology etc).

Scott first started out as a graphic designer in the late 90’s and over the years developed a passion for programmatic art (Designer + Developer mind). He recently has branched out further into 3D modelling and animation making full use of both his designer + developer mindset.

"..The reasonable man adapts himself to the world; the unreasonable one persists in trying to adapt the world to himself. Therefore, all progress depends on the unreasonable man..." - George Bernard Shaw
Page view tracker