Contents
Our brain is such a clever organ; it can do something that we don't know how it does. It catches a small difference between 2 images captured by our eyes and recognizes the depth and distance of the objects.
In this application, I used the method our brain does, and cheat it by making an unreal 3D environment.
In my previous article (Stereoscopy), I tried to explain some logic behind the stereoscopic systems and how you can make a 3D environment by moving the objects horizontally in the pair images. Now I wish to show how to make a 3D imagination of a 2D object only by skewing it. In this part, the ground is skewed from a simple image and makes a nice landscape.
I have to explain something first. In the pair images, when objects are in the same place, we saw them in the base of the scene. Actually the basement is equal to the infinity in the real world. When we change the place of the objects horizontally in one of the pair images, our brain detects it as a different depth from the basement. For example in parallel view, moving the objects of the left image to the right side, makes them appear closer to us and when you move them even more, they would be even more closer. There is an issue here. In the real world, the infinity is behind all other objects, but here, we can make objects behind the infinity! Just move an object of the left image to the left (instead of the right); you will see it behind the basement. You've accomplished an impossible mission!
The fish is behind the infinity! (Parallel mode)
When we make a difference in place of the objects in one image, their depth would be different. But what happens if we make differences to different parts of only one object?
Consider the arrow in the following figure:
Naturally we will see it in different depths, from far to near us. The amount of differences between some parts of the arrow are shown in the following figure:
And the following figure is shown skewing action on a surface, as I used in this application for the ground.
We need position of 3 points for skewing a shape: upper-left, upper-right and lower-left points.
Upper-left and upper-right points are equal to none skewed shape. X axis value of lower-left point must be decreased by the amount of skew.
Point[] skewPoints = {
new Point(0, 0 ),
new Point(ground.Width, 0),
new Point(-skewAmount, ground.Height)
};
When we skew the shape, a blank space would appear in the lower-right corner, so we would add some values to the width of the shape to hide it on the scene. This value is equal to the skew's amount.
Skew amount is calculated by dividing the height of the ground by altitude value (myGroundHeight/myAltitude
), and must be added to the width of both skewed and non skewed shapes. The code is as follows:
Point[] skewPoints = {
new Point(0, myBackHeight ),
new Point((int)(picLeft3D.Width+(myGroundHeight /myAltitude)),
myBackHeight),
new Point((int)(-myGroundHeight /myAltitude ),
picLeft3D.Height)
};
gRight.DrawImage(picGround.BackgroundImage, skewPoints);
gLeft.DrawImage(picGround.BackgroundImage, new Rectangle
(0, myBackHeight, (int)(picLeft3D.Width + myGroundHeight /
myAltitude ), myGroundHeight), new Rectangle(0, 0,
picGround.BackgroundImage.Width, picGround.BackgroundImage.Height),
GraphicsUnit.Pixel);
There are two controls for choosing favorable amounts of ground (View angle) and height (Altitude).
The algorithm is something like the following flowchart:
Initially I collect all objects’ images (Trees, Birds, Flowers…) from internal resources or hard disk in the proper Lists:
private List<Image> picsBack=new List<Image> ();
private List<Image> picsGround=new List<Image>() ;
private List<Image> picsBirds=new List<Image>() ;
private List<Image> picsTrees=new List<Image>() ;
private List<Image> picsAnimals=new List<Image>() ;
private List<Image> picsFlowers=new List<Image>() ;
.
.
.
private void loadPictures()
picsGround.Add(Properties.Resources.ground1);
picsBirds.Add(Properties.Resources.Bird_03_june);
picsFlowers.Add(Properties.Resources.sweetpea2);
.
.
.
loadFromHard("Ground", picsGround, null);
loadFromHard("Back", picsBack, null);
loadFromHard("Birds", picsBirds, null);
loadFromHard("Trees", picsTrees, null);
loadFromHard("Animals", picsAnimals, null);
loadFromHard("Flowers", picsFlowers , null);
}
private void loadFromHard(string myDir, List<Image> myPicList, List<Image> myPicList2)
{
DirectoryInfo myFileDir = new DirectoryInfo(Application.StartupPath + "\\" + myDir);
if (myFileDir.Exists)
{
// For each image extension (.jpg, .bmp, etc.)
foreach (string imageType in imageTypes)
{
// all graphic files in the directory
foreach (FileInfo myFile in myFileDir.GetFiles(imageType))
{
// add image
try
{
Image image = Image.FromFile(myFile.FullName);
myPicList.Add(image);
}
catch (OutOfMemoryException)
{
continue;
}
}
}
}
}
Every time we click on the Run button, we need to calculate and hold objects’ information somewhere, so I use myAllPicCollection
for this job. This List
is made from a class as follows:
private class picCollection
{
internal Image myImage = null;
internal Image myImage2 = null;
internal float myX = 0;
internal float myY = 0;
internal float myScale = 0;
internal float myDepth = 0;
}
private picCollection myPicInfo;
private List myAllPicCollection ;
And now, we want to make objects. The following code is for making animals and is similar to making other objects.
First, I select a random object from the proper collection:
myPicInfo = new picCollection();
myPicInfo.myImage = picsAnimals[myRandom.Next(picsAnimals.Count)];
myPicInfo.myImage2 = myPicInfo.myImage;
Then calculate a random depth, depending on trbDepthAnimals
control on the form..
myPicInfo.myDepth = (int)(myRandom.NextDouble() *
((float)trbDepthAnimals.Value / (15 * myRandom.NextDouble())));
The size of the object depends to its depth, general size control, object's type size control, altitude and its starting size.
myPicInfo.myScale = (float)(myPicInfo.myDepth * Math.Pow(trbSizeAnimal.Value, 1.3) /
1000 * myGeneralSize / Math.Pow(myAltitude, .5) + myStartingSize/10);
The X position is selected randomly, depends on the width of the screen.
myPicInfo.myX = (myRandom.Next((int)myPicInfo.myDepth, picLeft3D.Width -
(int)(myPicInfo.myImage.Width * myPicInfo.myScale) - 5) ) + 5;
But the Y position depends on more factors.
myPicInfo.myY = (myBackHeight - myPicInfo.myImage.Height *
myPicInfo.myScale + myPicInfo.myDepth * myAltitude);
myAllPicCollection.Add(myPicInfo);
After collecting all the information, we prepare to draw them. At first, we define 2 bitmaps for the left and right images. And draw the background:
Bitmap leftBitmap = new Bitmap(picLeft3D.Width, picLeft3D.Height );
Graphics gLeft = Graphics.FromImage(leftBitmap);
gLeft.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Bitmap rightBitmap = new Bitmap(picRight3D.Width, picRight3D.Height );
Graphics gRight = Graphics.FromImage(rightBitmap ); ;
gRight.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
.
.
.
float myGeneralSize = .05f * trbGeneralSize.Value;
float myViewAngle = .03f * (140 - trbViewAngle.Value);
float myStartingSize = .5f;
float myAltitude = (float)trbAltitude.Value / 4;
int myGroundHeight = (int)(picLeft3D.Height / myViewAngle );
int myBackHeight = picLeft3D.Height - myGroundHeight;
Point[] skewPoints = {
new Point(0, myBackHeight ),
new Point((int)(picLeft3D.Width+(myGroundHeight /myAltitude)),
myBackHeight),
new Point((int)(-myGroundHeight /myAltitude ),
picLeft3D.Height)
};
gLeft.DrawImage(picBack.BackgroundImage, new Rectangle(0, 0, picLeft3D.Width,
myBackHeight+2), new Rectangle(0, 0, picBack.BackgroundImage.Width,
picBack.BackgroundImage.Height), GraphicsUnit.Pixel);
gRight.DrawImage(picBack.BackgroundImage, new Rectangle(0, 0, picRight3D.Width,
myBackHeight+2), new Rectangle(0, 0, picBack.BackgroundImage.Width,
picBack.BackgroundImage.Height), GraphicsUnit.Pixel);
gLeft.DrawImage(picGround.BackgroundImage, new Rectangle(0, myBackHeight,
(int)(picLeft3D.Width + myGroundHeight / myAltitude ), myGroundHeight),
new Rectangle(0, 0, picGround.BackgroundImage.Width,
picGround.BackgroundImage.Height), GraphicsUnit.Pixel);
gRight.DrawImage(picGround.BackgroundImage, skewPoints);
And finally draw all objects on the scene, depending on their depth.
for (int d = 0; d < 1000; d++)
{
foreach (picCollection myP in myAllPicCollection)
{
if (d == myP.myDepth) DrawPicture(gLeft, gRight, myP);
}
}
picLeft3D.Image = leftBitmap;
picRight3D.Image = rightBitmap;
if (mnuAutoSave.Checked) ImageSvae();
}
private void DrawPicture(Graphics gLeft, Graphics gRight, picCollection myP)
{
gLeft.DrawImage(myP.myImage, myP.myX, myP.myY,
myP.myImage.Width * myP.myScale, myP.myImage.Height * myP.myScale);
gRight.DrawImage(myP.myImage2, myP.myX - myP.myDepth,
myP.myY, myP.myImage2.Width * myP.myScale, myP.myImage2.Height * myP.myScale);
}
The GUI items are shown below:
The View angle scale works as follows:
When it shifts to the left:
And when it shifts to the right:
And the Altitude scale works like this:
When it shifts to the left:
And when it shifts to the right:
You can change or add new objects to the environment. There are several directories in the executable file's place that contain these images. At the moment, there is any control on the images, so you have to resize them to the proper size.
There are two ways to save your scene on the hard disk. In both methods, images will be saved in Save directory at the executable file’s place.
- Choosing Save option from the menu (or pressing F2 key) saves the current scene.
- Selecting Auto Save option from the menu will save all scenes automatically.
There are different methods for this job, but the simplest are Parallel and Cross eye. In these methods, you don't need any additional devices, so you can do it anywhere and anytime. I explained these methods in my other article so you can try it here: Stereoscopy.
- 1st March, 2009: First release
- 2nd March, 2009: Article updated
- 13th March, 2009: Article updated