RecentComments

Comment RSS

Domain Specific Languages with M - Part 1/3 Defining the syntax

by Ioannis 10. April 2010 09:07

Say you are implementing software for a company that rents items. And you are presented with some bizarre and complex logic on the way renting fees are calculated. To be more specific requirements come to you in the following form:

“I want a renting fee scenario where an item for the first month is charged 10$ per day. Then, the same item costs 5$ per week for the next two months. Finally, after this period of three months the fee will be 20$ per month. But this is just one scenario…”

Clearly your inclination will be to go talk to the guy, help him write all his scenarios and then come up with a clever algorithm that calculates the fees and (hopefully) a UI where the guy can add more scenarios and persist them in the database.

Or you can implement a DSL. A DSL that allows somebody to express such complex renting-fee scenarios in an easy to use language. Hopefully this language will offer you more expressiveness and flexibility than a nicely constructed UI. From the moment you realize that renting fees will need requirement analysis, you come up with the following DSL:

 

Scenario (“Monthly”)
    For 1 month charge 10$ per day.
    For 2 months charge 5$ per week.
    Eventually charge 20$ per month.
End

 

Clearly this is something that can be given to the guy and ask him to express his renting fee scenarios using this language. At the time he writes his specs, you have work to do:

 

  • Create a database that stores those scenarios in some form.
  • Implement the algorithm that calculates the fees for any arbitrary scenario you may receive.

 

It all boils down to finding a way to parse the language and express meaningful information from it. How does “M” facilitate this process? As you will see you get a lot of out of the box goodies by implementing this DSL in “M”.

 

 Defining the syntax in M

Open Intellipad and select “DSL Grammar Mode”:

 

 

Create a text file that contains a demo scenario like the one we have written above.  Now go ahead and select “Open File as Input and Show Output”:

 

 

This will split your window in three. On the left you have your demo program in your new DSL. In the middle you write your DSL’ s syntax. On the right you get what “M” understands from your syntax and the demo program. Go ahead and write in the middle (where the syntax goes) the following:

 

module RentingApp
{
    language RentLanguage
    {
        syntax Main=any*;
    }
}

 

That is, you define a module named “RentingApp” which contains the syntax of the language “RentLanguage”. The syntax consists of a single rule that matches everything. The rule says that “Main” contains zero of more occurrences of any character. The moment you write these lines, the framework understands the input language and displays it on the right (Note that the result is a set of single characters):

 

 

 Of course this is of little help. Let’s go and make the syntax more specific by providing the following rules:

 

 

module RentingApp
{
    language RentLang
    {
        interleave whitespace = (" " | "\r" | "\n" | "\t")+;
       
        syntax Main=RentCharge*;
        syntax RentCharge="Scenario" "(" ChargeScenarioName ")" ChargeRules "End";
        syntax ChargeRules=(ChargeRule ".")*;
        token ChargeRule= "For" !('.')+ | "Eventually" !('.')+;
       
        syntax ChargeScenarioName=QuotedIdentifier;
        token QuotedIdentifier = '"' !('\r' | '\n' | '"')+ '"';
    }
}

 

The first thing to note is the directive “interleave…” which tells that our language does not take into consideration any spaces or returns or tabs.

The <Main> part consists of one or more <RentCharge> parts. Each <RentCharge> part begins with the word “Scenario“ and an opening “(“, has a <ChargeScenarioName> part followed by a closing “)”, then has a <ChangeRules> part and finally ends with the word “End”.

The <ChargeScenarioName> part is a <QuotedIdentifier> which matches one or more characters within quotes apart from “\r” and \n”.

The <ChargeRules> part consists of zero or more <ChargeRule> parts which end with “.”.

The <ChargeRule> parts start with the word “For” and then matches everything except the “.” or start with “Eventutally” and then anything except “.”.

This is practically how we construct the syntax of the DSL. The full syntax supporting the language we are building for the renting scenarios is as follows:

 

module Renting
{
    language RentLanguage
    {
        interleave whitespace = (" " | "\r" | "\n")+ | Comment;
       
        syntax Main= Charges+;
        syntax Charges=ChargeStartToken "(" ChargeName ")" ChargeBody ChargeEndToken;
        syntax ChargeBody=ChargeRule+;
        syntax ChargeRule=ForToken PeriodAmount Period ChargeToken CurrencyAmount CurrencyToken "per" Period "."
                         |EventuallyToken ChargeToken CurrencyAmount CurrencyToken PerToken Period ".";
        token PeriodAmount=('0'..'9')+;
       
        token Period="week"|"weeks"|"month"|"months"|"days"|"day"|"year"|"years";
        token CurrencyAmount=('0'..'9')+ DecimalSeparator ('0'..'9')* | ('0'..'9')+;
        token ChargeName=QuotedIdentifier;
        token DecimalSeparator=",";
        token ChargeStartToken="Scenario";
        token ChargeEndToken="End";
        token ForToken="For";
        token EventuallyToken="Eventually";
        token CurrencyToken="$";
        token ChargeToken="charge";
        token PerToken="per";
        token Comment = "//" !("\r" | "\n")+;
        token QuotedIdentifier = '"' !('\r' | '\n' | '"')+ '"';
    }
}

 

As you can see we do not hard code any of the literals of the language (such as “Scenario” etc) within the syntax rules. We do that to be able to easily change the spoken language of our DSL (for example from English to Greek). The syntax you see above recognizes all the possible sentences that will be written for the renting scenario and as you may have noticed from the first rule, it accepts more than one renting scenarios at once.

You can download the aforementiond grammar for the English language here and a little different version for the Greek langauge here.

In the next post we will decorate our DSL with attributes and create productions to alter the output of the DSL parser.

 

 

Shout it

kick it on DotNetKicks.com

Tags:

.NET

XNA 2D Basic Collision Detection with Rotation (Scene2Sprite)

by Ioannis 16. February 2010 07:54

In the previous post we have worked with collision detection between roated sprites. In this post we will deal with the situtation where we want to detect whether a specific sprite that can be rotated is in a specific area in the scene (eg a car is on the road in the scene).

Suppose that you have the following scenario:

 

A car is driven by the user around the scene. When the car hits the boundaries of the road, the car should lose all of its speed. The user uses the UP/DOWN keys to acelerate/break and LEFT/RIGHT for steering.

You want to detect the situation where the car is going out of the road bounds. A way to do this is as follows:

Use a second texture of the road, one that you will use for collision detection which has a specific plain color for the road:

Then get the bounding rectangle of the rotated texture that is parallel to the window's axis:

Within this rectangle check whether there is one point that is on the car's texture but the equivalent point on the road texture is not gray.

This programmatically is achieved as follows:

To get a rotated texture alone from scene, you use an additional back-buffer where you draw the rotated car's texture and then get a snapshot of it. The AdditionalRenderTargetForCollision is initialized once in the main game loop (download the project to see how):

public static RenderTarget2D AdditionalRenderTargetForCollision { get; set; }

private static Color[] _getSingleSpriteAloneFromScene(SpriteBatch spriteBatch,Texture2D textureToDraw,
                             Vector2 Position,Vector2 Origin,float Theta,List<Vector2> RectanglePoints)
{
    spriteBatch.GraphicsDevice.SetRenderTarget(0, AdditionalRenderTargetForCollision);
    spriteBatch.GraphicsDevice.Clear(Color.Black);
    spriteBatch.Begin();
    spriteBatch.Draw(textureToDraw, Position, null, Color.White, 
                                     Theta, Origin, 1.0f, SpriteEffects.None, 0);
    spriteBatch.End();
    spriteBatch.GraphicsDevice.SetRenderTarget(0, null);
    Texture2D SceneTexture = AdditionalRenderTargetForCollision.GetTexture();
    Rectangle BoundingRectangle = _getBoundingRectangleOfRotatedRectangle(RectanglePoints);
    int PixelSize = BoundingRectangle.Width * BoundingRectangle.Height;
    Color[] TextureData = new Color[PixelSize];
    SceneTexture.GetData(0, BoundingRectangle, TextureData, 0, PixelSize);

    return TextureData;
}

The code that checks for bounds is as follows:

public static bool SpriteIsOnValidArea(Texture2D TextureScene, Texture2D TextureSprite, Vector2 PosSprite, Vector2 OrigSprite, List<Vector2> RectSprite,float ThetaSprite,SpriteBatch spriteBatch,Color OKColor)
 {
     Color[] TextureDataSprite = _getSingleSpriteAloneFromScene(spriteBatch, TextureSprite, PosSprite, OrigSprite, ThetaSprite, RectSprite);
     Color[] TextureDataScene =  new Color[TextureScene.Width*TextureScene.Height];
     TextureScene.GetData(TextureDataScene);


     Rectangle RectangleSpriteBounding = _getBoundingRectangleOfRotatedRectangle(RectSprite);
     for (int i = RectangleSpriteBounding.Top; i < RectangleSpriteBounding.Bottom; i++)
         for (int j = RectangleSpriteBounding.Left; j < RectangleSpriteBounding.Right; j++)
             if (TextureDataScene[i * TextureScene.Width + j] != OKColor && TextureDataSprite[(i-RectangleSpriteBounding.Top) *
                 RectangleSpriteBounding.Width + (j-RectangleSpriteBounding.Left)] != new Color(0,0,0,0))
                 return false;
    
     return true;
 }

 

The code initially gets the car's rotated texture and the scene's texture. It then uses a method to calculate the bounding rectangle of the rotated sprite's texture. Then, within this rectangle it tests for the sprite's pixels one by one. If it locates even one texture's point that is not on the selected color (OKColor) it returns false (the car is out of bounds).

The project that performs this kind of collision detection can be downloaded here.

kick it on DotNetKicks.com

Shout it

Tags:

XNA

XNA 2D Basic Collision Detection with Rotation (Sprite2Sprite)

by Ioannis 10. February 2010 07:26

(If you are in a hurry and do not want to know how it's done but just do it, dowload the project and extract the CollisionDetection2D.cs file to include in your project that contains the collision detection methods)

In my previous post ,we have seen several collision detection techniques that can be used in 2D games in XNA. In that post, we have left out the changes that affect our code when textures can also be rotated. In this post, I intend to fill this gap by picking up from exactly where we stopped and adding rotation to the equation.

Therefore, if you haven't read the previous post , I recommend that you go and give it a look. In this post we will cover what happens when we want to check for collision between two sprites that can be rotated.? In the next post we will work with the case where the collision needs to be checked with elements from the scene (such as a car in a road), and the sprite (eg the car) can be rotated.

Prior to considering the two aforementioned problems lets see how we can rotate a texture in XNA. Rotating is done by using a different overload of the _spriteBatch.Draw method. We want our sprite (DrawableGameComponent) to be decorated with two more properties:

private Vector2 _textureReferencePoint;
public Vector2 TextureReferencePoint
{
    get { return _textureReferencePoint; }
    set { _textureReferencePoint = value; }
}

private float _rotationAngle;
public float RotationAngle
{
    get { return _rotationAngle; }
    set { _rotationAngle = value; }
}

(...)

The first is the reference point of the texture and the second is the angle of the rotation. The reference  is the point that is used as reference when we want to put a texture to a specific position. It is expressed in texture coordinates which means that point (0,0) is the upper-left corner of the texture. In the basic draw method where we do not use the texture's reference point, XNA defaults to point (0,0). So when we say that our texture should be drawn at _position (a Vector2 object) the texture's reference point is moved to _position and the texture's pixels are drawn based on that point:

If we change the texture's reference point, then, the new reference point will be moved to _position, resulting in a different placement of the texture at _postion. So if reference is set to (texture.width/2,texture.height/2) then:

The texture's reference point plays also a central role in rotation since it is the center around which the rotation will be performed. The following figure shows two 90 degrees rotations around different reference points:

 The rotation angle is measured in rads. You can use degrees and convert them to rads using the following formula:

 

((2 * (float)Math.PI) / 360) * AngleInDegrees.

 

Having those two properties available, we can now use a different overload of the SpriteBatch.Draw method to rotate our texture as we draw it on screen (the rotation will be performed around the TextureReferencePoint and the texture's TextureReferencePoint point will be placed at _position):

 _spriteBatch.Draw(_texture, _position,null,Color.White,RotationAngle,TextureReferencePoint,1.0f,SpriteEffects.None,0f);

 

The 1.0f stands for scaling (play with it to make your texture smaller/bigger).

And now for the tricky part that concerns collisions:

 

Sprite/Sprite collision detection (extends the previous post)

The first thing to be done is to adjust our bouding shapes accordingly since now the texture's origin has changed and _position points at the central point of the texture and not its upper left corner. Then, we need to take into consideration that the texture is rotated so along with it we must rotate the bounding shape.

We use the following method that rotates a point (PointToRotate) around an arbitary origin (OriginOfRotation point):

private Vector2 _rotatePoint(Vector2 PointToRotate,Vector2 OriginOfRotation,float ThetaInRads)
{
    Vector2 RotationVector = PointToRotate - OriginOfRotation;
    Vector2 RotatedVector = new Vector2()
    {
        X = (float)(RotationVector.X * Math.Cos(ThetaInRads) - RotationVector.Y * Math.Sin(ThetaInRads)),
        Y = (float)(RotationVector.X * Math.Sin(ThetaInRads) + RotationVector.Y * Math.Cos(ThetaInRads))
    };

    return OriginOfRotation + RotatedVector;
}

Apparently in the case where we use bounding circles, we do not have to do anything apart from taking into consideration that the origin of the texture is not at (0,0) but at (texture.width/2, texture.height/2) and therefore the center of the bounding circle is just the _position vector as follows:

public Vector2 CircleCenter
{
    get { return new Vector2(_position.X,_position.Y); }
}
public int CircleRadius
{
    get { return Math.Max(_texture.Width / 2, _texture.Height / 2); }
}

But with rectangles and triangles there is the need of rotating the bounding shape along with the texture. We use the aforementioned rotation method as follows. For the triangle:

public Vector2 TrianglePoint1
{
    get { return _rotatePoint(_position - TextureReferencePoint, _position, RotationAngle); }
}
public Vector2 TriangleOffset1 { get; set; }
public Vector2 TrianglePoint2
{
    get { return _rotatePoint(_position - TextureReferencePoint + TriangleOffset1, 
                                                _position, RotationAngle); }
}
public Vector2 TriangleOffset2 { get; set; }
public Vector2 TrianglePoint3
{
    get { return _rotatePoint(_position - TextureReferencePoint + TriangleOffset2,
                                                _position, RotationAngle); }
}

And for the rectangle we keep the same properties upper left point, width and height and we also use the rotation angle and the rotation origin.

For the circle and the triangle the collision detection methods demonstrated at the previous post work perfectly. But the method used for the bounding rectangles fails since it does not support rotation. To solve this we divde the rectangle in two triangles and then we use the same method we use  for the triangles. Therefore we do not pass the upper left corners and the widths and heights but the individual points of the rectangle making a method that can detect collisions for any 4point shape. You can see this in action (see the methods _createTrianglesFromRectangle() and BoundingRectangle() from the project).

But how do we deal with Per-Pixel collision detection? First, we do a collision detection for the rotated bounding rectangles by using the previously mentioned "bounding rectangle" approach. If this detects a collision the we need to check a per-pixel collision. But to do this we need to know the overlapping area of two rotated bounding rectangles which is kinda difficult. Actually, even if we could find the overlapping area of the two rectangles it would be difficult to perform a per-pixel collision detection on this area which could be of any shape (not parallel to the axis).

So instead of that we use a bounding rectangle that encloses the rotated texture as shown in the figure (check the _getBoundingRectangleOfRotatedRectangle() method of the project):

All we want to do is get the pixels within this bounding rectagles and perform a per-pixel collision detection on them. Apparently those are not the pixels of the texture only. The plot thickens...

A solution is a follows:

  • Draw the texture on screen.
  • Get a snapshot of the screen at the bounding rectangles of the rotated textures.
  • Perform a PerPixel collision detection on those two snapshots.

But how can we take a snapshot that contains only the two textures and not the background? Well, we create an imaginary drawing surface and we draw on that surface the textures one by one. Upon drawing each one on the imaginary surface we take the desired snapshots. Then we have what we need to perform perpixel collision detection the "traditional" way (Check the methods _getSingleSpriteAloneFromScene() and PerPixelWR() from the project).

 

Shout it

kick it on DotNetKicks.com

Tags:

XNA

Powered by BlogEngine.NET 1.5.0.7

Programming Blogs - BlogCatalog Blog Directory Add to Technorati Favorites

MVP Award

Ioannis Panagopoulos





This blog is using BlogEngine.Net and is hosted in the hoster below. I have not experienced any problems installing BlogEngine.Net in the host and I am satisfied with the host's response times. Therefore I recommend it.


DiscountASP Add