7. Transformations - Tutorial

Vortex2D uses stack to manage with transformations. Each new transformation pushed into stack is applied before rest. If you need to rotate and then translate something order of push calls should be: translate, rotate. This model is chosen because it i natural for hierarchical transformation tree.

In this tutorial we'll make some kind of postcard with ability to move, scale and rotate it on screen.

drawingTransformations_50x50.jpg

Transformation Objects

Vortex2D supports 3 special transformation objects: Vortex.Drawing.Translation, Vortex.Drawing.Rotation, Vortex.Drawing.Scaling. Translation is pretty much simple. It contains only offset property. Rotation specifies pivot point and angle of rotation. Scaling specifies pivot point and scaling factor for X and Y axis. All of these types has rich constructors, properties to get/set parameters and can be converted to Matrix2D with ToMatrix() method.

It is fine with translations to make 0, 0 point as pivot and use translation to move it where we want. In tutorial we have all of transformations which are init to their defaults on start:

_CardTranslation = new Translation(400, 300); //points to center of screen
_CardScaling = Scaling.Empty;
_CardRotation = Rotation.Empty;

Also some kind of momentum was implemented for all of transformations. Code is complex but should be easy to understand how translation/rotation/scaling speeds are calculated:

//Process rotation machanics
float rotationSpeed = 0.0f;

if (Keyboard.IsDown(Key.A)) rotationSpeed -= ROTATION_SPEED;
if (Keyboard.IsDown(Key.D)) rotationSpeed += ROTATION_SPEED;


if (rotationSpeed != 0) {
    //if user picked some rotation direction lets set it in full speed
    _RotationSpeed = rotationSpeed;
} else {
    //otherwise make it less with each frame
    _RotationSpeed = _RotationSpeed * (1.0f - time.FrameTime * 4);
}

//Process movement mechanics
Vector translationSpeed = Vector.Zero;

if (Keyboard.IsDown(Key.Left)) translationSpeed.X -= TRANSLATION_SPEED;
if (Keyboard.IsDown(Key.Right)) translationSpeed.X += TRANSLATION_SPEED;
if (Keyboard.IsDown(Key.Up)) translationSpeed.Y -= TRANSLATION_SPEED;
if (Keyboard.IsDown(Key.Down)) translationSpeed.Y += TRANSLATION_SPEED;

if (!translationSpeed.IsEmpty) {
    _TranslationSpeed = translationSpeed;
} else {
    _TranslationSpeed = _TranslationSpeed * (1.0f - time.FrameTime * 4);
}

//Process scaling mechanics
float scalingSpeed = 0.0f;

if (Keyboard.IsDown(Key.S)) scalingSpeed -= SCALING_SPEED;
if (Keyboard.IsDown(Key.W)) scalingSpeed += SCALING_SPEED;

if (scalingSpeed != 0.0f) {
    _ScalingSpeed = scalingSpeed;
} else {
    _ScalingSpeed = _ScalingSpeed * (1.0f - time.FrameTime * 4);
}

//Check for reset
if (Keyboard.IsPressed(Key.R)) {
    SetTransformationDefaults();
}
After speeds are calculated we need to calculate transformations based on speed and time of frame render:

//translation is bounded; card center should be in screen space
_CardTranslation.Offset = (_CardTranslation.Offset + _TranslationSpeed * time.FrameTime).Max(TRANSLATION_LOW_BOUND).Min(TRANSLATION_HIGH_BOUND);
_CardRotation.Angle += _RotationSpeed * time.FrameTime;
//scaling should be at least 0.05
_CardScaling.Factor = (_CardScaling.Factor + _ScalingSpeed * time.FrameTime).Max(new Vector(0.05f));

Transform Canvas Output

Each frame we update transformation objects and in render method we should use them somehow to move/rotate and scale drawing output. Canvas supports transformation stack based on Matrix2D object. It means we should push transformation and pop after it will be unnecessary. Methods Canvas2D.PushTransformation and Canvas2D.PopTransformation are designed for it. First puts Translation, Rotation, Scaling or Matrix2D object into stack, second one remove it form there.

In example code I've used more elegant way to push/pop transformations - canvas scoping (<=). All geometry is drawn inside using scope will be transformed. Also pop will be automatically invoked after execution will go out of using scope.

 using (canvas <= _CardTranslation & canvas <= _CardRotation.ToMatrix() & canvas <= _CardScaling) {
    //all drawn inside of this scope will be transformed:

    canvas.DrawSprite(0, 0, _FlowerTexture.ToSprite(), ColorU.White);
    canvas.DrawString(_CorridaFont, 30, 190, "Flowers,\nThey are so beautiful...", ColorU.White);
    canvas.DrawRect(canvas.Region.Offset(-400, -300), ColorU.Yellow);
}

Transformation Overrides

Few words about transformation overrides. Using render targets commonly you need to reset transformation into default state or somehow else. It could be done with Canvas2D.PushTransformationWithOverride method. It also pushes transformation into stack but don't multiply it with rest. Also canvas scoping analog for push with override is present: operator <<. Samples:

canvas.PushTransformationWithOverride(Matrix2D.Identity);
...
canvas.PopTransformation();
//or 
using (canvas << Matrix2D.Identity) {
    ...
}

Conclusion

Vortex2D supports transformation stack for 2D output. You can specify transformations with 4 types of objects, 3 special: Translation, Rotation, Scaling and base Matrix2D. Transformations are applied with PushTransformation/PopTransformation methods but it is better way to use canvas scoping (operator <=).

Last edited Aug 20, 2010 at 10:31 AM by AlexKhomich, version 6

Comments

No comments yet.