Now that we've covered the basics of animation, translation and rotation, we can combine these concepts to animate a basic scene. The exercise in this section tries to push you to really apply the skills we have covered previously so do not be frustrated if you have trouble figuring out the solution. Instead of simply pressing Show Solution immediately, you should try to make changes to your code and re-run the program until you get the desired result. The point of this section is for you to try to come up with programming solutions on your own, test them, and if they do not work, test them again until they do. If you read a problem and don't know where to begin, start with what you do know. For example, if it says write a procedure that takes in arguments X and Y then start by writing the basic code for a procedure:
procedure SomeProc(X,Y : Double);
begin
end;
From there just experiment by trying different code and observing the result. If you simply look at the solutions and copy the code verbatim this section will be a waste of time.
Excercise 3-5. Write code to build an animated scene. Although you can simply use the basic Workspace template, if you would like to save this program after you create it, simply make a copy of the Workspace folder and rename it. That way you can continue using the basic Workspace template.
We return to the scene we created in the HelloWorld application:
procedure OnEnterFrame;
begin
BkColor := clAqua;
FillEllipse(-13,9,-10,6,clYellow);
FillRect(-14,-7,14,-10,clBrown);
end;
We want to first add the body of a windmill to the scene. To do this, run the program to display the scene and press Ctrl+G to view the grid. Then copy the scene to the clipboard using Ctrl+C. Now you can open your preferred drawing program and paste the image (often using Ctrl+V or selecting Edit -> Paste from the menu). We suggest using something like MS Paint on Windows or GIMP on Mac/Linux. In an external drawing program you can draw any structure and then use the grid to determine the coordinates you will need to use for programming. Using this technique, draw the body of a windmill (not the blades) using the FillPolygon function. Remember that the syntax of FillPolygon is something like FillPolygon([Vector(0,0),Vector(10,10),...],clBlack,clBrown) where black is the outline color and brown is the fill color.
You can use any coordinates and any outline/fill you like. We choose black and peach as the outline and fill colors. The coordinates are shown in the code below:
Since we will deal with rotation of the blades, it would be nice to write a function that converts an angle
and radius to X and Y coordinates. When coordinates are specified as a radius and
we call them
polar coordinates. When coordinates are specified as X and Y we call them
rectangular or
Cartesian coordinates. So our function will be called PolarToXY and it should take in arguments Theta and Radius and return a TVector2D of X and Y coordinates. Hint: If you have a circle of radius 5, how to you determine the X and Y coordinates of a point that is 30 degrees from the x-axis.
All we have to do is use sin and cos:
In its current form, the function PolarToXY will only work for circles centered at the origin (0,0). Since we will want to deal with rotations other than ones at the origin, modify the function by taking in arguments XOffset and YOffset that shift the resulting X and Y coordinates.
Add XOffset to Result.X and YOffset to Result.Y.
Now we will actually create the blades. The blades can be drawn as isosceles triangles with the tip of the triangle at the axis of rotation. Keep in mind that we want the blades to rotate so the easiest way to do this would be to make the outer two vertices of the blade run along the circumference of a circle:
Write a procedure that draws a single blade (for now). The vertex at the axis of rotation should have an angle of about
but this is completely dependent on how wide you want the blades to be. The length can be about 3, but you could make them longer or shorter. This procedure will need to take in arguments X, Y, and Theta to determine the position (corresponding to the axis of rotation) and current location on the circle of the blade. Call the procedure inside of OnEnterFrame in order to test it until you get it to do what you want. Hint: You can use PolarToXY to determine the coordinates and FillPolygon to draw the blade.
There are multiple ways to write this procedure. We give one example below. Blade is called in the last line of the OnEnterFrame procedure, but nothing else in this procedure is changed.
Now duplicate the call to Blade to make all the blades. You will need to decide (1) how many blades your windmill will have and (2) what changes to make in the arguments passed to Blade to make this happen. Note: You do not need to change anything in the Blade procedure itself. You need to change only the calls to Blade inside OnEnterFrame. This should be fairly easy if you give it some thought. For example, how could you give the windmill 4 blades just by changing an argument in 4 different calls to Blade?
The argument Theta determines the position of rotation for the blade. So if we want 4 blades we simply duplicate the call to Blade and add 90 degrees each time (360/4 = 90). Note: If you had wanted to be really clever you could have done this with a for loop.
Take a breath. The next step is easy. Make it look like the blades are actually connected to something at the axis of rotation by placing a small circle there.
Either use some multiple of FrameCount or define your own global variable and increment it by a certain amount on each frame.
Subtract FrameCount/10 from the Y coordinate of the sun.
As the sun sets, the sky gets darker and the sunlight we perceive changes from yellow to a reddish color due to particles in the atmosphere. Make the background color shift from full intensity clAqua to 0 intensity using HSI2Color(H,S,I). You can determine the Hue that matches clAqua by using the function GetHue(clAqua). Make the sun shift from yellow to red. To do this, start with a yellow hue by using GetHue(clYellow) and then shift the hue toward red (remember that Hues correspond to the colors of the rainbow ROY G BIV starting with red). This time you will want to declare your own global variables SkyIntensity and SunHue. Decrement them by some step size (you should experiment to see what step size works best) until they reach 0. If you decrement past 0, the color will loop back around to 255 since hues and intensities are represented as bytes. You can do this with an if statement. Remember that you can initialize global variables in the Initialize procedure.
This required a number of changes to the code. Let's start with SkyIntensity. We declare it as an integer and initialize to 255. We cannot initialize SunHue here because we want to set its color to GetHue(clYellow) and we can only initialize variables with constant expressions (because GetHue requires execution of code and therefore must be in the begin-end block). We replace clAqua with the function HSI2Color. This is appropriate because HSI2Color returns a color which is what the procedure FillRect expects to get. The hue is aqua so we use GetHue(clAqua) as the first argument. We want full color saturation so we use 255 as the second. Finally, we specify SkyIntensity as the third because this will allow us to vary the intensity of the color. If you're wondering why we have to use GetHue(clAqua) instead of just clAqua for the hue, keep in mind the difference between a hue and a color. A color specifies the exact RGB intensities that the hardware displays. A hue simply specifies a shade. Calling HSI2Color(GetHue(clAqua),255,255) returns a number that is equal to clAqua because a hue is equal to its corresponding color at full saturation and intensity. Finally, we need to decrement SkyIntensity on each frame. We do this with an if statement that decrements SkyIntensity until it reaches 0.
Next we have SunHue. SunHue is defined as a real because decrementing in integer increments makes the sun turn red too quickly. We need to decrement in fractional increments. For the color of the circle, we use HSI2Color(Round(SunHue),255,255)) because we want full saturation and full intensity (the sun doesn't go out as it sets it just changes hue). We have to use the Round function because HSI2Color expects to receive an integer argument. So if the SunHue is 20.4, we just pass 20 to HSI2Color. Finally, we decrement SunHue by 0.3 each time (you can do it faster or slower if you like) until it reaches 0.
As you can see from working through the previous example, programming is seldomly a linear process. Instead it is iterative in which portions of code are continuously remixed in order to create the desired program.