 Windows Phone

# Windows Phone 7 : Using the Touch Screen (part 4) - Sprite Hit Testing - Elliptical Hit Tests, Building the Hit Tests into the Game Framework

7/17/2013 3:38:48 AM
##### 3.3. Elliptical Hit Tests

Although rectangular hit tests are appropriate in some cases, in others it might be useful to test against a round sprite shape. To facilitate this, we can perform an elliptical hit test.

The ellipse that will be tested will completely fill the rectangular region occupied by the sprite, as shown in Figure 4.

##### Figure 4. The elliptical test region contained within a rectangular sprite Of course, ellipses, unlike circles, are affected by rotation, so we need to take this into account when working out whether a test point falls inside the ellipse. In Figure 5(a), we can see a rotated ellipse whose scale is such that its width is twice its height. Also marked in the figure are two test points, the first of which is within the ellipse, whereas the second is not (though it is within the bounds of the sprite rectangle).

The approach that we take to determine whether the points are within the ellipse starts off the same as that used for the rectangle: performing the calculation to rotate the points and the ellipse back to an angle of zero. Once that has been done we can ignore the rotation and concentrate just on the elliptical shape. Again, we don't actually draw the sprite with the angle reset to zero or update the sprite's properties; we just perform the calculations that would be required for this rotation. If we were to draw the rotation, we would end up with the arrangement shown in Figure 5(b).

##### Figure 5. Elliptical hit tests against a rotated scaled sprite Having obtained the coordinates relative to an unrotated ellipse, we can now determine whether the points are within the ellipse or not. For a circle this would be easy: we would find the radius of the circle and we would find the distance from the test point to the center of the circle. If the point distance is less than the radius, the point is inside the circle.

For an ellipse, this process is more complex, however. An ellipse doesn't have a radius because the distance from its center to its edge varies as the edge is traversed.

Fortunately, there is a very easy way to resolve this. We know how the sprite has been scaled, so we can divide the width of the ellipse by the scaled sprite width, and divide the height of the ellipse by the scaled sprite height. This will result in a new ellipse that is exactly one unit wide and one unit high. The size is less important than the fact that this resulting size is now that of a circle (with a radius of 0.5) rather than an ellipse, meaning that we can perform calculations against it very easily. Instead of scaling the sprite in this way, we can scale the test point and then see whether its distance from the circle center is less than 0.5. If so, the point is a hit; otherwise, it's a miss.

The steps required for the whole procedure are as follows; they are just like the steps for the rectangular hit test:

• Move the touch point to be in object space rather than in screen space.

• Rotate the point back by the sprite rotation angle.

• Move the point to be relative to the center of the ellipse.

• Divide the point's x position by the ellipse width and its y position by the ellipse height to scale down relative to a unit-width circle.

• Test the point distance from the circle center to see whether it is within the circle's radius of 0.5.

Table 4-2 shows each of these calculations for each of the touch points shown in Figure 5. The sprite in question is 64 × 64 pixels and has been scaled to be double its normal width, resulting in an ellipse with a width of 128 pixels and a height of 64 pixels. Its center (and origin) is at the coordinate (200, 100).

##### Table 2. Calculation steps to determine whether a test point is within a rotated scaled ellipse
Test Point 1Test Point 2
Screen coordinate(224, 117)(248, 134)
Object-space coordinate(24, 17)(48, 34)
Rotated coordinate(27.6, 10.2)(55.2, 20.4)
Ellipse width/height128 pixels by 64 pixels
Rotated coordinate scaled by width and height(0.216, 0.159)(0.432, 0.318)
Distance from circle center at (0, 0)0.2680.536
Point contained within rectangle (distance <= 0.5)YesNo

As this table shows, the test point 1 coordinate is inside the ellipse (its calculated distance is less than 0.5), and the test point 2 coordinate is not.

The complete function to perform this calculation is shown in Listing 9. This code is taken from the SpriteObject class, so it has direct access to the sprite's properties.

##### Example 9. Checking a test point to see if it is within a rotated and scaled sprite ellipse
 ```protected bool IsPointInObject_EllipseTest(Microsoft.Xna.Framework.Vector2 point) { Rectangle bbox; Vector2 rotatedPoint = Vector2.Zero; // Retrieve the basic sprite bounding box bbox = BoundingBox; // Subtract the ellipse's top-left position from the test point so that the test // point is relative to the origin position rather than relative to the screen point -= Position; // Rotate the point by the negative angle of the sprite to cancel out the sprite // rotation rotatedPoint.X = (float)(Math.Cos(-Angle) * point.X - Math.Sin(-Angle) * point.Y); rotatedPoint.Y = (float)(Math.Sin(-Angle) * point.X + Math.Cos(-Angle) * point.Y); // Add back the origin point multiplied by the scale. // This will put us in the top-left corner of the bounding box. rotatedPoint += Origin * Scale; // Subtract the bounding box midpoint from each axis. // This will put us in the center of the ellipse. rotatedPoint -= new Vector2(bbox.Width / 2, bbox.Height / 2); // Divide the point by the width and height of the bounding box. // This will result in values between −0.5 and +0.5 on each axis for ``` ```// positions within the bounding box. As both axes are then on the same // scale we can check the distance from the center point as a circle, // without having to worry about elliptical shapes. rotatedPoint /= new Vector2(bbox.Width, bbox.Height); // See if the distance from the origin to the point is <= 0.5 // (the radius of a unit-size circle). If so, we are within the ellipse. return (rotatedPoint.Length() <= 0.5f); } ```
##### 3.4. Building the Hit Tests into the Game Framework

Checking touch points against game objects to see whether they have been selected is an operation that will be common to many games. To save each game from having to reimplement this logic, we will build these checks into the game framework.

This procedure starts off as an abstract function in GameObjectBase called IsPointInObject, as shown in Listing 10. It expects a Vector2 parameter to identify the position on the screen to test and returns a boolean value indicating whether that point is contained within the object.

##### Example 10. The abstract declaration for IsPointInObject contained inside GameObjectBase
 ```/// /// Determine whether the specified position is contained within the object /// public abstract bool IsPointInObject(Vector2 point); ```

To implement the IsPointInObject function for sprites, it is overridden within SpriteObject. We will enable our sprites to support testing against both the rectangular and elliptical tests that we have described, and to allow the game to specify which type of test to use a new property is added to the class, named AutoHitTestMode. The property is given the AutoHitTestModes enumeration as its type, allowing either Rectangle or Ellipse to be selected.

The SpriteObject implementation of IsPointInObject checks to see which of these hit modes is selected and then calls into either IsPointInObject_RectangleTest (as shown in Listing 8) or IsPointInObject_EllipseTest (as shown in Listing 9). Any game object can thus have its AutoHitTestMode property set at initialization and can then simply test points by calling the IsPointInObject function.

For sprites that need to perform some alternative or more complex processing when checking for hit points (perhaps just as simple as only allowing a hit to take place under certain conditions or perhaps implementing entirely new region calculations), the IsPointInObject can be further overridden in derived game object classes. Top 10 - First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)  