Introduction to 3D in REALbasic with Rb3D


by Joseph Nastasi

rb3d01.jpg

alt=“A-OK! The Wings of Mercury

Introduction

In this article, I will be previewing REALbasic3D (Rb3D), a real-time 3D graphic engine to be included in REALbasic release 3.5. First, I’ll present a quick overview of the engine, then go into more detail and finally, provide a simple demo program to demonstrate Rb3D. I have been using Rb3D as the 3D engine on the new version of my spacecraft simulator, A-OK! The Wings of Mercury and have almost a year’s worth of extensive experience with it.

REALbasic has taken the Mac OS development world by storm. Widely recognized as a breakthrough product, it continues to evolve and improve rapidly. Developed by Real Software, REALbasic has always been targeted as a development platform for games. The very first release included a 2D sprite surface that allowed fairly complex 2D and pseudo-3D games to be created. But what if you wanted true 3D? There were several choices, including OpenGL wrapper classes and a full-featured, but not-yet-released, commercial plug-in. Last year, REALbasic software engineer, Joe Strout created a 3D plug-in for REALbasic, called Rb3D. Unlike wrapper classes, which forced you to think like a C++ programmer instead of a REALbasic programmer, Rb3D followed REALbasic’s syntax and object-oriented approach principles.

At the suggestion and request of its customers, including yours truly, Real Software has decided to bundle Rb3D as an internal feature of REALbasic. This has huge implications for programmers looking for a rapid 3D development environment. It also means that 3D programming in REALbasic will be supported on both Mac OS and Windows.

A Quick Look

Rb3D contains four classes and one control. That’s it! We’ll get into some detail below, but basically, there’s a control for defining the view and the camera that’s creating that view, and four classes that define 3D points, orientations, objects and groups of objects. As stated above, if you are familiar with REALbasic, you’ll feel right at home with the syntax.

Rb3D allows you to control all the basic properties of a 3D entity: position, orientation, size and visibility. It also provides basic camera, light and environment controls. Rb3D runs on top of Apple’s now defunct QuickDraw 3D or the open source, multi-platform replacement, Quesa. Quesa runs on top of OpenGL. The good news is that having it built upon two open source graphic platforms allows Real Software to add enhancements that are incorporated into Quesa by other programmers.

alt=“Software Layer”

rb3d02.gif

Rb3D is not in the same league, feature-wise as large, commercial 3D engines. However, the engine does have a few neat tricks up its sleeve that are especially useful for those trying to create a program that doesn’t require a PowerMac G4 600MHz or a 1GHz Pentium III. Its initial simplicity can be an asset to developers for several reasons: First, REALbasicis a great entry-level approach to programming. If Rb3D sported a huge feature set, it would no doubt be too daunting for a beginning programmer. Secondly, Real Software has consistently sought the advice and ideas of its core customer community, so this represents an opportunity for the REALbasic development community to participate in the design of this new 3D engine. But it does provide a very good platform for 3D applications-not just games-with minimal complexity and cost.

Since REALbasic is a compiled language and Rb3D is just a compiled library that is linked in, your 3D programs run fairly quickly. Programming in 3D can be daunting to the beginner. Having sweated out the transition to real time 3D by various methods, I can safely say that, unless you need advanced visual effects or need 30 complicated models moving at 60 frames per second, REALbasic’s Rb3D will suit you just fine.

What You Need

Now that I have you dreaming of making your next game a 3d hit, let’s take a look at what will be needed. First, you should visit Real Software and download (better yet buy) REALbasic 3.2. along with a copy of the Rb3D plug-in. You can also use the current developer release of REALbasic 3.5. Next, you will need a copy of the latest OpenGL SDK, and the latest Mac OS build of Quesa. Alternatively you could use QuickDraw3D instead of Quesa. If you’re using QD3D, you don’t need OpenGL. QD3D does its rendering via RAVE. Most users of classic MacOS already have QD3D 1.6 installed, unless they’ve run the QuickTime 5 installer, which actually removes it. (You can put it back by running the installer again and choosing Full or Custom install.)

The Rb3D Control

The basic control of Rb3D is named, appropriately enough, Rb3DSpace. It exists on the REALbasic control bar and is dragged on to the window of your choice, as you would the canvas control. In fact, Rb3DSpace’s basic properties are the same as the RectControl class as far as Position and Appearance go: width, height, visible, etc. New for the 3.5a2 release is that Rb3DSpace can handle all the usual events directly (MouseUp, MouseDrag, MouseDown, MouseEnter, MouseExit, Open, Close and DropObject). With the plug-in you had to handle mouse clicks in the window that Rb3DSpace was in.

There are a set of Initial Properties that can be set in the IDE. In fact, that is our first limitation. With two exceptions, these properties must be set in the IDE, you cannot adjust them while the program is running. This limitation will be removed for the final 3.5 release, though.

  • Hither and Yon — These properties define how close and how far away and object can be from the “camera” inside Rb3DSpace and still be visible. You can save lots of processing time by making this range as small as possible for your application. More importantly, Hither and Yon affect rendering quality; if the yon/hither ratio is too large, you get visual artifacts such as “scissoring” and other unwanted effects.
  • FieldOfView — This property defines the “camera” lens. A lower value will narrow the viewable field, acting like a telephoto lens. Conversely, a larger value will create a larger viewable field, simulating a wide-angle lens.
  • SkyColor — This property allows you to set background color for this particular Rb3DSpace. This is sufficient for simple games and object editor applications.
  • AmbientLight — This property sets the non-directional lighting source. A larger value means more light. Generally, you set this to a value about 30% of the next property, FloodLight for average contrast. A very low value would result in a more dramatic lighting effect and a high value would result in a “washed out” look.
  • FloodLight — This property sets a directional light source. As with AmbientLight, a larger number means more light. Another limitation: you cannot change the position and direction of this light. The position is fixed at an infinite point at -X,+Y,-Z, and is pointing to the center of the coordinate system, 0,0,0. This is a good general “portrait” position: up, to one side and behind the camera. At some point (not 3.5 though) this will be made variable, indeed a separate light object will probably be included to allow complete flexibility.
  • Wireframe — This is a boolean property that allows you to toggle how the models get rendered by Rb3DSpace: shaded or wireframe. Wireframe is sometimes useful for a 3D object editor or to create a “Sci Fi” 3D look. This property can be changed during run-time.
  • DebugCube — This property is boolean, a new addition for Rb3DSpace and very useful. It basically creates a background with all the axes marked. When you move objects or rotate your Rb3DSpace camera around, you can see exactly what section of the coordinate grid you are pointing at. This can be enabled or disabled at run-time which is handy for debugging and super for 3D editors.

alt=“Properties”

rb3d03.gif”

There two methods associated with Rb3DSpace, typically accessed in a MouseUp or MouseDown event. FindObject takes an X,Y coordinate within Rb3DSpace and returns any Object3D (more below) that lies on that point. In a shooting game this method can be used to verify if a target has been hit. FindPoint is similar: pass it an X,Y coordinate and it will return the equivalent 3D point. Very useful for 3D editors as you can know the 3D position the user is pointing to.

Rb3DSpace has a Camera, Background and Object property associated with it. These three items are specialized version of Rb3DSpace’s core classes, Object3D and Group3D. The Camera object can be positioned and rotated. The Object property is basically an array (Group3D) that you can add objects to. Every Object3D that you append to Rb3DSpace’s Object property will be visible in that Rb3DSpace, provided that the Object3D’s visible flag is true and the Object3D is in the Rb3DSpace’s field of view.

Background works exactly like the Object property except for one major difference: The Ambient and FloodLights do not affect any Object3D in the Background group. This is useful for things like a sky box; you don’t want shading on air! The Background’s position relative to the camera is fixed. This means that when the camera moves, the background moves as well. This is an important characteristic, because it makes the background appear to be infinitely far away; no matter how you move, you don’t get any closer to it.

Vectors

How does one know where an object is in a 3D world? The Vector3D class contains that information. You can assign it any 3D location by setting its X,Y and Z properties. There are methods to find the point’s distance from the 3D origin (Length(), LenSquared(), to find the angle between two 3D points (dot()), and to find a 3D point that is perpendicular to the plane defined by two 3D points (cross()) and to scale a 3D points distance from the origin to one (normalize()). Other Rb3D classes inherit the Vector3D class to define their position in 3D space or vector. I’ll explain this point in a bit.

Quaternions

Which way is an object facing? Welcome to the wonderful world of quaternions. The concept for these little gems came to a math whiz Sir William Rowan Hamilton in 1843 while walking to the Royal Irish Academy where he was the Astronomer Royal. As he passed the Brougham Bridge, he carved the basic equations into the stone of the bridge. Usually when I’m crossing a bridge, I’m wondering why it just cost me $7 US to cross the Hudson River. Oh well, I’ll never be knighted!

Quaternions are a very handy and stable way of representing an object’s orientation. If you’ve dabbled in 3D before, you may know that there are other ways of representing orientation, matrix, Euler angles, etc. All have limitations that can cause major visual problems. I won’t go too deeply into what the Quaternion object can do, but here’s the basics. The properties are W, X, Y and Z, but you’ll rarely set them directly. You can set an object’s orientation, one axis at time by using SetRotateAboutAxis. You pick an axis and give it the angle you want to set it to (in radians). It’s important to remember that this is an absolute orientation, not relative. To rotate an object by ‘X,’ you’ll need some other methods defined below. As with Vector3D, other Rb3D classes inherit the Quaternion class.

Objects

Now we get to the big kahuna, Object3D. As stated before, an Object3D has a Vector3D, the Position property, and a Quaternion, the Orientation property, associated with it. Another new property for the 3.5 version of Rb3D is the Scale property. For example, an object can be made twice as large or twice as small by setting this value to 2.0 and 0.5, respectively. The Visible property is a boolean value that allows you to instantly hide or show an object. The neat thing with this is that you can load all your Object3D’s at the beginning of your program and selectively make them appear. Objects that are not visible take very little computation time. They do take up memory though, so you need to be aware of this and set your application’s memory requirements (Classic Mac OS) appropriately, if you have many complicated models.

Shape is a very powerful property. When you first set up an Object3D, you load a 3DMF file into it (see below). You can load multiple 3DMF objects into it and then change the Shape instantly under program control. If you were displaying a cloud, for example, you could model several subtle variations, load them into Object3D and then change the Shape for a dynamically changing cloud!

Object3D has many powerful methods. When we discussed Quaternions, we mentioned the ability to rotate an object in a relative manner: turn left 45 degrees, for example. The Pitch, Yaw and Roll methods take an angle (in radians) and rotate along the particular axis by that amount. Important point: yes, these are Euler angles, BUT they are being converted to Quaternions by this method; you still get all the benefits of Quaternion’s mathematical stability. You can move an Object3D by changing its Position. In some applications, like a first-person maze exploration game, there is a much simpler method: MoveForward(). Let’s say you make the Rb3DSpace Camera (remember, it’s just another Object3D) rotate to look up, down, left and right. Passing the MoveForward method a positive value will move the Camera that many units; an negative value moves it backward.

How do you get a 3DMF model into an Object3D anyway? Currently a 3D model must be a 3DMF file. 3DMF was the meta file standard for Apple’s QuickDraw 3D and while QD3D has gone the way of the dinosaur, 3DMF is a fairly well supported format, even in the Windows world. It came in two flavors, binary and text. Although Rb3D can handle both, I prefer text for two reasons: text is much more robust and allows for modification of a model after it is exported. So you set up a typical REALbasic text stream read procedure that passes the entire file into a string. You then declare a new Object3D and use the AddShapeFromString method to convert the 3DMF string into a REAL (pun intended) Object3D.

In some applications, you may want dozens of the same object. Let say you’re creating a 3D Space Invader game and each row has 10 aliens that are the same. No need to load ten individual objects. Once an Object3D is created and a 3DMF model loaded, you can use the Clone() method to assign the same Object3D to another Object3D. What’s the point? Cloning simply refers to all the geometry and textures of the initial Object3D. This is faster because you don’t have to have ten separate file loads, and takes up less memory because only one set of geometrical attributes are stored.

Sometimes you don’t need a 3D object. Sometimes a simple 2D image will do. AddShapePicture() takes a REALbasic Picture class and a Scale value as parameters and loads them into a previously created Object3D. You then treat it as any other 3D object. A typical example might be a backdrop of a skyline that will be so far from the camera that a 3D version is not required. The only trick to this technique is that the image is visible from one side only. If the Rb3DSpace Camera moves behind it, the image will disappear. In cases where this might happen, you only need rotate the Object3D that contains the image so it’s always facing the Camera. AddShapePictureWithMask takes an additional REALbasic Picture class and uses it as a mask.

Groups

The final Rb3D class is Group3D. As the name suggests, this class allows you to treat a whole bunch of Object3D classes as one. Since it is a sub class of Object3D, you can do all the same things to an entire Group3D as you can to one individual Object3D. You can load parts of a car (wheels—remember to clone three of them, body, etc.) separately, then load them all into a Group3D that represents the entire car. You can then move or rotate the entire car by moving or rotating the Group3D. Note: this takes more processing time to do than manipulating each part separately. If you have a complicated model or have a lot of other things going on, this is not always the best approach.

The Append() method adds an Object3D to the Group3D. Remove() does just that and you can pass either the Object3D name or its index into the Group3D. Forget how many objects you shoved into a Group3D? Just call Count() which returns the number of objects. By keeping track of and passing an Object3D’s index into a Group3D, you can access it using the Item() method. This allows you to, let’s say make a part of larger model visible or not.

In our discussion of the Rb3DSpace class we mentioned that the Object property is really a Group3D class. Finally, if you’ve changed the Group position or orientation, and want this change to be applied to all of the group contents, just call Update(). This is only needed to update a Group3D that is not visible and therefore being drawn by Rb3DSpace.

Creating a Demo

I’m going to build a very simple demo program and go through it section by section. I will use two of the models included in the demo that comes with the latest alpha release of REALbasic 3.5, but you can download the finished program and models from here as well.

Open a new REALbasic project and drag the Rb3DSpace control on to the default window. Let’s leave all the properties at their default values, with the possible exception of the SkyColor. That blue makes me gag!

Now let’s create a new method, loadModel. We’ll make it flexible so we can use it in other programs later:

<br />
Function loadModel(filename as string) as Object3D<br />
Dim obj as Object3D<br />
Dim f as FolderItem<br />
Dim input as TextInputStream<br />
Dim data as String<br />
// load the 3DMF model<br />
f = GetFolderItem(filename) // assume it's in the same folder<br />
if f &lt;&gt; nil then<br />
input = f.OpenAsTextFile // has to be text files<br />
data = input.ReadAll // get 3DMF data as one big string<br />
obj = New Object3D // create new Object3D<br />
obj.AddShapeFromString data // load model data into new Object3D<br />
end if<br />
return obj // calling code should make sure this isn't nil.<br />
End Function<br />

Now we need to put the call to loadModel someplace. In simple programs, the easiest is in the open event of the Rb3DSpace control itself. So, in Rb3DSpace1 (the default control name), lets put the following code:

<br />
Sub Open()<br />
Dim obj as Object3D // storage for one model<br />
obj = loadModel("Penguin.3DMF")// load the model an assign it to obj<br />
obj.position.z = -300.0 // move the model in front of the camera<br />
me.objects.Append obj // append obj to Rb3D's Objects group<br />
End Sub<br />

That’s it. Just hit run. There is our little flippered friend in all his tuxedo glory. Kill the application and change the FieldOfView from its default of 50 degrees to 20. Nice close up. Okay, let’s go the other way and change FieldOfView to 100 degrees and run. Looks a little lonely!

alt=“Field of View

rb3d04.jpg

Remember that we put the penguin 300 units from the camera (which is at 0,0,0 by default)? Let’s change the Hither from its default of 5 to 260. We’re telling the Rb3DSpace Camera to ignore the first 260 units in front of it. If all has gone right, our friend should be without a bill and hole in his pot belly. Now change Hither back to 5 and change Yon to 260. Now we’re telling the Camera to ignore everything after 260 units. We should see one penguin bill and belly! Set the Yon property to 10000.

alt=“Hither and Yon”

rb3d05.jpg

Now let’s play with the lights. If you set the AmbientLight value to 0, then our penguin will look like he belongs in a Batman movie, nice and sinister looking. (A dark skycolor works best for this.)

alt=“Sky Color

rb3d06.jpg

Checking the Wireframe option will expose the model’s framework. Finally, try checking the Debug Cube option. You’ll see that left is -X and up is +Y. What about the Z direction? Add this line to the end of the Open event method:

obj.visible=false

Hit run and the penguin will be gone and we can see that we’re facing the -Z axis.

Remove the last line and add the following:

me.background = loadModel("Background.3DMF")<br />
// load the background model into the Rb3D background object

Make sure the Yon value is set to 10000 and run. Now you’ll see a simple land/sky background. Go back and play around with the light settings and you’ll see that they do not effect the background.

alt=“Background”

rb3d07.jpg

Camera Movement

Let’s add the ability to move the Rb3DSpace Camera. In the main window’s KeyDown event method, place the following code:

<br />
Function KeyDown(Key as string) as boolean<br />
select case asc(Key)<br />
case 28 // left arrow<br />
Rb3DSpace1.Camera.Yaw 0.1 // rotate left 5.625 degrees<br />
case 29 // right arrow<br />
Rb3DSpace1.Camera.Yaw -0.1 // rotate right 5.625 degrees<br />
case 30 // up arrow<br />
Rb3DSpace1.Camera.Pitch -0.1 // pitch up 5.625 degrees<br />
case 31 // down arrow<br />
Rb3DSpace1.Camera.Pitch 0.1 // pitch down 5.625 degrees<br />
case 11 // page up<br />
Rb3DSpace1.Camera.Roll -0.1 // roll left 5.625 degrees<br />
case 12 // page down<br />
Rb3DSpace1.Camera.Roll 0.1 // roll right 5.625 degrees<br />
case 43 // keypad +<br />
Rb3DSpace1.Camera.MoveForward 10.0 // move forward 10 units<br />
case 45 // keypad -<br />
Rb3DSpace1.Camera.MoveForward -10.0 // move backward 10 units<br />
else<br />
return true<br />
end select<br />
Rb3DSpace1.Refresh // IMPORTANT must refresh the Rb3D control<br />
return true<br />
End Function<br />

Now the you can move the camera around (and therefore the first person viewpoint) using the arrow, page up/down and the keypad +/- keys. Note that 0.1 used in the Pitch, Yaw and Roll methods is one tenth of a radian.

Making Models

A fair number of 3D modeling programs output 3DMF files. Some don’t do a very good job of it or they produce very detailed models that result in huge, complex files. Rb3D’s creator, Joe Strout is also the author of a very cool modeling program, Meshwork. This program was designed from the ground up to produce low polygon count models, especially tuned for real-time 3D. In addition to 3DMF files, Meshwork can import and export 3DS, DXF, POV-Ray, VRML and other formats. The cost is a paltry $30 (there’s a demo available) and there’s an active user email list as well.

Conclusion

The Rb3D is an incredible addition to REALbasic’s repertoire. Now even beginners can create fairly sophisticated 3D applications simply and quickly. I’ve only touched on Rb3D’s capability. The normal and developer release REALbasic lists are already discussing Rb3D, so you’ll pick up ideas there as well as general REALbasic knowledge. As a taste of what you can do with Rb3D here is a development screen shot of a Mercury/Atlas launch from my product A-OK! The Wings of Mercury. The flames are animated, as is the smoke and there are over 40 parts that move, get ejected or spew flame or smoke in that one model. This is a straight screen shot with no image editing.

alt=“Rocket Launch”

rb3d08.jpg

Acknowledgments

Thanks to Joseph Strout for creating this fabulous addition to the REALbasic feature list and for his support and mentoring me on 3D over the last 2.5 years. Additional thanks to Geoff Perlman for having the wisdom to listen to his customers and for adding Rb3D as soon as it made sense to do so. Finally, I need to thank my incredible wife, Sheila, and my wonderful children, Nick, Julian and Sierra, for putting up with the impossibly long hours spent in development of A-OK! WoM. We’re rounding the final bend, guys! All my love!

Bio: Joseph Nastasi is a free lance multimedia programmer and producer. Living in New Jersey with his wife and three children, he is a contractor for Flash, Director and REALbasic projects. A life-long space enthusiast, he has been a consultant for many space-related projects, including movies, NASA history projects and web events

introduction,3d,realbasic,rb3d

Recent Forum Threads

About iDevGames

Since 1998, iDevGames has been educating, supporting and enhancing the community of game developers that produce video games for the Apple Mac and iPhone platforms. Get the latest game development news by subscribing to our news feed.