RB3DSpace Class
Used to view and manipulate objects in 3D space.
Notes
Requirements
The 3D classes in REALbasic require either Quesa or QuickDraw 3D. The latter is a library by Apple Computer and part of QuickTime, while Quesa is an open-source project that provides very similar functionality. The Quesa libraries are available at http://www.quesa.org. Which one you use may depend on the operating system you're running.
Windows
On Windows, copy the Quesa.dll file to your Windows System directory. Under Windows, you have a choice. QuickDraw 3D is part of QuickTime for Windows, so you can use that or Quesa (and OpenGL). The Rb3DSpace control is written assuming Quesa, and since Windows locates libraries based on their file names, to use QuickDraw 3D you'll need to rename QD3D.DLL to QUESA.DLL. (This is not recommended; Quesa gives better performance under Windows.)
Macintosh
On Macintosh, copy the file "Quesa" to your /Library/CFMSupport folder. Under Mac OS "classic," QuickDraw 3D is installed by default, so you don't need to install any additional libraries. You can instead install Quesa Classic if you prefer.
Linux
Linux requires OpenGL to be installed.
If the required 3D software is not installed, you will get a NilObjectException when the compiler encounters an RB3DSpace object.
If the required libraries are not installed if the Objects property of any Rb3DSpace control will be Nil. If the proper libraries are available, this property is automatically initialized to a Group3D (i.e., it's non-nil). A check such as the following (in the Open event of the Rb3DSpace control) is a good idea:
Note that this works only under Mac OS. Under Windows, if the QUESA.DLL library can't be found, your program will not even launch. This is just a side-effect of the way shared libraries work under Windows.
Background
The RB3DSpace control is where 3D renderings are done. The axes of the 3D space are displayed when you set the DebugCube property to True. The Z axis is the "depth" dimension and the "size" of this axis is controlled by the Hither and Yon properties. The Hither and Yon properties define the near and far clipping planes (the Z axis); you want to keep Hither and Yon as close together as possible in order to make best use of the Z-buffer of your hardware. FieldOfView is in degrees, and specifies how wide is the angle that the camera sees; make this smaller to zoom in, or larger to zoom out.
Note that the camera is defined as an object; it doesn't need any geometry, but this lets you move the camera exactly as you would move any other object. The background is defined as an Object3D, but you could assign a Group3D to it instead if you want.
All other objects in the scene are stored in the Objects property. It is a Group3D object so that you can easily add things to it (or iterate over objects in the scene, etc.). If you need to find the object or point in 3D space which corresponds to a given pixel position within the view, use FindObject and FindPoint. These will return Nil if the point clicked contains no object.
The Element3D class represents anything that can go into a Group3D and, therefore, can be displayed by the Objects property of the RB3DSpace. All such objects have a position, orientation, and scale. Note that the Rb3DSpace's Camera is an Element3D, rather than an Object3D.
The Trimesh class allows you to create and manipulate triangular meshes on-the-fly. It works with four "helper" classes that get and set trimesh data: TriangleList, UVList, VectorList, and ColorList. Also, the Material class lets you create and manipulate colors and textures on-the-fly.
Specifying a Location in 3D Space
Any location in a 3D space can be specified the three coordinates: X, Y, and Z. If you imagine the world as one infinite, flat plane, you would pick some arbitrary point as the "origin." Then you can specify any location by a distance East/West from the origin, a distance North/South, and a height/depth.
The arbitrary center of the virtual universe is called the "origin." By convention, the three coordinates in 3D graphics are called X, Y, and Z and the origin is the point 0,0,0. The X-axis is the line through the origin formed by assuming Y=0 and Z=0; similarly, the XY plane is the infinite plane through the origin where Z=0.
The standard interpretation of the axes is that the XZ plane is the ground (or parallel to the ground), and the Y coordinate is height. In REALbasic, the three axes in REALbasic are arranged such that if you're looking in the -Z direction, with +Y pointing up, then +X is to the right. (This is the default orientation of the camera, too.) So, if you're looking in this direction, than an increase in the X coordinate of an object's position will cause it to move to the right from your point of view. An increase in Z will cause it to move closer to you, and a decrease in Z will cause it to move further away.
The FieldOfView Property
FieldOfView is the angle, in degrees, that is shown in the area of the RB3DSpace control. A normal human has a field of view of about 180 degrees, but the monitor itself is only on the order of 10 degrees across for a typical viewing distance. If you try to compress a large field of view down to a display only 10 degrees across, you'll cause a "fisheye" effect which looks very unnatural. On the other hand, if you use only a 10 degree field of view in a typical 3D environment, users will feel like they've got tunnel vision and will constantly be panning the camera around. The FieldOfView you choose is a balance between these two conflicting constraints: You want to see a useful amount of the scene, but you want it drawn as realistically as possible.
A typical FieldOfView value for many 3D games is about 40 to 50 degrees. However, you can provide a "zoom" (telescopic vision) option simply by reducing the FieldOfView temporarily -- a smaller FieldOfView means that you're displaying a smaller part of the world in the same area on screen, hence everything in that part looks bigger.
Finally, the telescopic quality of a small FieldOfView also allows you to reduce (and nearly eliminate) perspective when you need to. Put the camera very far away from the scene and use a small FieldOfView; this is equivalent to looking at the ground from orbit, and under those conditions, there is almost no foreshortening.
The Hither and Yon Properties
These are standard 3D graphics jargon for the "near-far" (Z) axis. Nothing closer to the camera than the Hither distance gets drawn; and if an object happens to be halfway straddling Hither, it will simply be cut in half. Everything closer than Hither is clipped out of the scene (thus, the "near clipping plane"). Similarly, nothing farther from the camera than Yon is drawn.
If there were nothing more to it, you would simply set Hither to zero and Yon to some very large number so that things aren't clipped. But in reality, that's a very bad idea. The reason is that video cards have a limited amount of resolution in the "depth buffer" that's used to keep track of what's in front of what. The ratio Yon/Hither must be evenly divided by the number of bits in the card's depth buffer. If that ratio is very large, then the depth resolution will be inadequate and you'll see all sorts of visual artifacts -- objects mixed together in odd ways, further-away objects being drawn in front of closer ones, etc. So it's very important to the quality of your rendering that you keep the Yon/Hither ratio as small as possible. Increase Hither and decrease Yon as much as you can in your application.
Orienting an object
There are an infinite number of orientations that all point towards a certain point of interest (or "POI"). Prove this to yourself by taking (or imagining) an actual camera with a view-finder. Look through the view-finder at some object, say a tree down the street. Now, without losing sight of the tree, rotate the camera around the axis between it and the POI. There are an infinite number of different orientations you could stop the camera in, even though they all look at the tree.
The first thing one must do is define some additional constraint on the orientation. A common one is to keep the object as "upright" as possible. There are still many ways to interpret this, but here's one solution:
// Make object 'obj' point towards the point of interest 'POI',
// by first doing a yaw (from the default orientation), and
// then a pitch. The object's previous orientation is not
// involved.
Dim yawAngle As Double
Dim pitchAngle As Double
Dim v1, v2 As Vector3D
// Find the yaw angle needed to get in the right general direction
// (i.e., ignoring Y, get it facing the right XZ direction).
// The Atan2 function provides a very easy way to find this.
yawAngle = Atan2(POI.x-obj.position.x, POI.z-obj.position.z)
// Set the object's orientation to a yaw by that amount.
obj.orientation.SetRotateAboutAxis 0,1,0, yawAngle
// Now, find the pitch angle needed to account for Y.
// We use a dot product to get the angle between a vector in
// the direction the object's facing now, and a vector that
// points towards the POI.
v1 = New Vector3D
v1.z = 1.0
v1 = obj.orientation.Transform(v1)
v2 = New Vector3D
v2.x = POI.x - obj.position.x
v2.y = POI.y - obj.position.y
v2.z = POI.z - obj.position.z
v2.Normalize
pitchAngle = Acos( Min( Max(v1.Dot(v2),-1.0),1.0) )
If v2.y > 0 then
pitchAngle = -pitchAngle
end if
// Apply that pitch.
obj.Pitch pitchAngle
Orbiting an Object around a POI
This is another task that can be interpreted in many ways. Start by figuring out exactly what want to do. If the object in "orbit" is a camera that you want to move around a Point Of Interest (POI), there are several reasonable options:
Move the camera on the surface of an imaginary, Y-axis-aligned cylinder which surrounds the object. Left-right movements will move the camera laterally around the cylinder, and up-down movements will move the camera up and down the side of the cylinder. Note that this means that the camera gets further away from the POI when you move up or down. But it also means that the left-right/up-down directions are always very well defined and obvious.
Move the camera on the surface of an imaginary sphere around the object. The camera object will have a certain direction it is facing (towards the center), and left-right/up-down will move the camera on the surface of the sphere relative to that direction. This keeps a constant distance from the POI, but the controls can get confusing since the moving object can easily end up upside down or sideways.
Move the camera on the surface of an imaginary sphere around the object. In this case, let left-right control the longitudinal position of the camera, and up-down control the latitude. This keeps the camera a constant distance from the POI, and is fairly easy to control since (like the cylindrical case) the left-right/up-down directions are always well defined.
In all these cases, the position of the object can be described by two parameters. Left-right controls increase or decrease one of the parameters, and up-down controls adjust the other. The only difference among them is how these parameters are converted into 3D coordinates (and, therefore, what the parameters mean). In all cases, you could add a third parameter, radius, to change the size of the orbit.
Here is the code for the first option:
// Compute a position on a virtual Y-aligned cylinder, centered on POI,
// with the given radius, angle, and altitude.
Dim out As Vector3D
out = New Vector3D
out.x = POI.x + radius * Cos(angle)
out.z = POI.z + radius * Sin(angle)
out.y = POI.y + altitude
Return out
Example
This example shows how to load a 3DMF file into an RB3DSpace. This code is placed in the Open event. Note that "3DMF" should be declared in via the FileType class or with the File Type Sets Editor, with Type code "3DMF". In this example, the File Type Set called "FileTypes1" contains the required file types. The All method does automatic text conversion and returns all the file types defined in this set
Dim obj As Object3D
//allow only 3DMF files types to be shown
f = GetOpenFolderItem(FileTypes1.All)
If f <> Nil then
// create an object
obj = New Object3D
// give the object a shape specified by the 3DMF
obj.AddShapeFromFile f
If obj.Shapecount < 0 then
MsgBox "No valid 3DMF data found in "+.DisplayName+"."
Return
End if
// add the object to this Rb3DSpace
Me.objects.Append obj
end if
// move the camera away from the origin so that we can see the 3DMF
Dim v as Vector3D
v= New Vector3D
v.X=0
v.Y=2
v.Z=10
Me.Camera.Position= v
The last line moves the camera away from the object so that the 3DMF is visible. By default, the camera is at location 0,0,0 (a.k.a., the "origin" in the 3D space). By default, objects are also loaded at the same place. This means that the camera is inside your object, which is usually not where you want it to be. Fix this by moving the camera away from the origin. How far you need to move depends on how big your object is.
The Position property is a Vector3D that specifies the X, Y, and Z coordinates of the camera. You can pass a Vector3D as in this example or with lines like:
See Also
Bounds3D, ColorList, Element3D, Group3D, Light3D, Material, Object3D, Quaternion, TriangleList, Trimesh, UVList, Vector3D, VectorList classes.