Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Park 3D

0.00/5 (No votes)
14 Mar 2009 1  
This simple application simulates a 3D environment with some objects in different depth and size based on stereoscopic calculations.
Sample Image

Contents

Introduction

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.

Background

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.

Behind the Infinity!

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)

How does Skewing Work?

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 ),  			// upper-left point
    new Point(ground.Width, 0),  		// upper-right point
    new Point(-skewAmount, ground.Height)  	// lower-left point
    };

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 ),  			// upper-left point
    new Point((int)(picLeft3D.Width+(myGroundHeight /myAltitude)),
				myBackHeight),  	// upper-right point
    new Point((int)(-myGroundHeight /myAltitude ),
				picLeft3D.Height)	// lower-left point
    };

   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).

Using the Code

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()
    // add some pictures from internal resources
    picsGround.Add(Properties.Resources.ground1);
    picsBirds.Add(Properties.Resources.Bird_03_june);
    picsFlowers.Add(Properties.Resources.sweetpea2);
.
.
.
     //load some pictures from Hard disk (different sub directories)
    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)Math.Pow ((110-  
					// (float)trbViewAngle.Value)/5,.8)/3;
float myStartingSize = .5f;
float myAltitude = (float)trbAltitude.Value / 4;
int myGroundHeight = (int)(picLeft3D.Height / myViewAngle );
int   myBackHeight = picLeft3D.Height - myGroundHeight;

//by skewing the right image, we make landscape
Point[] skewPoints = {
                        new Point(0, myBackHeight ),  	// upper-left point
                        new Point((int)(picLeft3D.Width+(myGroundHeight /myAltitude)), 
			myBackHeight),  		// upper-right point
                        new Point((int)(-myGroundHeight /myAltitude ), 
			picLeft3D.Height)  	// lower-left point
                      };

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.

    // drawing all objects of the collection in order by depth on the scene
    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();
}


// Draw Graphics on the scene
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);
}

GUI 

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:

Points of Interest

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.

Saving Images

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.

  1. Choosing Save option from the menu (or pressing F2 key) saves the current scene. 
  2. Selecting Auto Save option from the menu will save all scenes automatically.

How Can You View Stereoscopic Pictures?

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.

History

  • 1st March, 2009: First release
  • 2nd March, 2009: Article updated
  • 13th March, 2009: Article updated

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here