Issue 2-43, October 29, 1997

Be Engineering Insights: 3d Kit Tips And Hints

By Pierre Raynaud-Richard

To judge from recent movie releases, Hollywood's two favorite subjects are the President of the United States and aliens. The movie "Independence Day" used both. But—can you believe it?—there were a few technological "inconsistencies." The worst was the way David Levinson (the Jeff Goldblum character) used a software virus to disable the shield of the alien armada. Here's how Levinson *really* saved the world: He found the proper function call. But let's back up a bit...

Did you really think that the alien detained in Area 51 was a mere fighter pilot? No way! He was also a distinguished linguist, and a high-level technologist. He spent years studying our languages, especially English, and our technology, especially software engineering. He found both of them so interesting that he couldn't resist writing his own Fortran compiler (at the time, he had no real choice).

He even reimplemented his 3 billion native system calls so that he could use Fortran to control his fighter. And because he loved our human languages so much, he wrote the whole Fortran API in English, and also used the advanced localization features of his OS so his Tracker would display English text.

So what did Levinson really do? When he found the alien's navigation system, he brought up the "Find Panel" and used it to search for the keywords "disable" and "shield." And that's how he found a header file defining the Fortran API...

INTEGER FUNCTION DISABLE_SHIELD(TARGET) INTEGER TARGET;

which accepted the values K_FIGHTER, K_MOTHER_SHIP, and K_ALL_ARMADA. And now you know the rest of the story. Stand by for news.

Look in the Headers

The lesson from "Independence Day" is clear: If you know how to search for information in header files, you can find a lot of good stuff. Such is the case of the current alpha version of the 3d Kit, available at ftp://ftp.be.com/pub/beos_updates/developers/3dKitAlpha.zip.

The sample code is good, but the commented headers are even better. The only problem is that a lot of the 3d Kit API isn't as straightforward as DISABLE_SHIELD(). Although I can't explain the entire 3d Kit architecture in a single article, I'll try to highlight a few obscure points.

B3dCamera and Its "ViewPoint"

Why isn't the B3dCamera a B3dThing, which lets you put it where you want it in the world, and move it around the way you need to? That's an interesting question that came up in BeDevTalk. The idea behind the B3dCamera was to create a graphics engine to generate frames, rather than simply give you a point of view in space. A B3dCamera encapsulates the whole process used to look at the world. A camera's position and orientation (in other words, its "thing"-ness) is certainly relevant information, but it hardly scratches the surface of a B3dCamera.

In the B3dCamera class, the "viewpoint" is expressed (and can be retrieved) as a B3dThing:

B3dThing *B3dCamera::ViewPoint()

You can let the B3dCamera create the B3dThing viewpoint for you by specifying the location of the viewpoint:

void B3dCamera::SetViewPoint(B3dVector originInWorldSpace,
                             B3dWorld *world)

Or you can "attach" your camera's viewpoint to some other existing thing:

int32 B3dCamera::SetViewPoint(B3dThing *view_point)

The latter method lets you see the world through the "eyes" of the other "thing" you've attached it to. Where the thing goes, your camera goes.

B3dLink

B3dLink may be one of the most problematic concepts in the 3d Kit—not because it's complex, but because its name and API are too simple to reveal any information about it.

B3dLink provides a model that describes the relationships between B3dThing objects as a graph of master/slave dependencies, where the state of a slave object (its position and orientation, primarily) depends on the states of a variable number of master objects. The state of the slave can also depend on the flow of time.

For example, you can "glue" two objects together so they always stay a certain distance apart. Or you can describe the trajectory that a thing should follow as a B3dLink that respects the flow of time, but that has no master objects.

The B3dLink mechanism can be predicated on almost anything: You aren't limited to only the state of the master objects and the flow of time. For example, let's say you want to have an object flying around "following" a music sound track. Just create a B3dLink subclass that periodically checks the global state of your music system and off you go.

Another feature of B3dLink is that it lets you "relink" on the fly. You just have to call

int32 B3dThing::LinkTo(B3dLink *link, ulong mode =
                       B_CHECK_CYCLE)

LinkTo() unlinks and destroys the B3dLink object that's currently controlling your object and makes your object a slave of the new link. If you simply want to unlink without relinking, you call:

int32 B3dThing::Unlink(ulong mode = 0L)

Be careful though, because the thing's B3dLink object is destroyed when you unlink it. This means that you can't "temporarily" unlink an object (i.e., unlink, move the object, relink). If you have to do that, you've chosen the wrong kind of B3dLink. A properly designed link integrates all the control you need to move your object.

Another way to modify the state of something at every frame is to use a "frame hook." Frame hooks aren't as nice as links, so you should avoid them, but they may be necessary in some cases (for example, the B3dCinepakChannel uses a frame hook to synchronize the update of new frames). The API is in B3dUniverse.h. Check the 3dmov sample code for more info.

Groups

Groups are the offspring of links and hierarchy. If you want to package a small number of B3dThings together and handle them as a single object, use a group. This implies a relationship between the "main" object that represents the group (the "group master") and all the group members.

That's where the B3dLink comes in. Every member of a group has to be linked (directly or through the link hierarchy) to the group master. If you've already defined B3dLinks between some members of the group, only those members that don't already have a master will be directly linked to the group master. The nature of the link is defined by some bits in the "mode" flag in the group_things() function:

long group_things(long count,
                  B3dThing **list,
                  B3dThing **group,
                  char *name,
                  B3dVector *origin = 0L,
                  float threshold = -1.0,
                  ulong mode = B_EXTEND_GROUP |
                               B_RIGID_LINK |
                               B_CHECK_CYCLE);

In the default case, everything is linked "rigidly." That is, all members keep the same position relative to the group master. Once a group has been created, all interactions with it should be done only through the group master. Internal B3dLinks will still work, but you won't be able to link a group member to an object outside the group.

See B3dThing.h and B3dLink.h for more details.

The Z-Buffer and Anti-Aliased Bitmaps

If you're just playing with simple objects that don't touch each another, the rendering engine's default sorting mode should be sufficient. But if you want to go beyond that—and especially if you want to have objects touching one another—you need to enable the "depth buffer" (or "Z-Buffer"). For now, the Z-buffer has to be enabled at the graphic buffer level, directly in the B3dView constructor:

B3dView::B3dView(char *name,
                 BRect frame,
                 B3dUniverse *universe = NULL,
                 uint32 renderFlags = B_3D_BLACK_BACKGROUND)

The values for renderFlags are described over in 3dDefs.h, so finding them is a bit confusing. Although you almost always need B_3D_BLACK_BACKGROUND to erase the buffer to black before each new frame, if you know for sure that your 3D rendering will redraw every pixel in the frame (for example, if you're using an "environment thing," as explained below), you can disable it and save a bit of computation. The two other options are B_3D_ZBUFFER to enable the depth buffer and B_3D_ANTIALIAS_TEXTURES to enable anti-aliasing for texture mapping. Anti-aliasing (in particular) slows things down quite a bit, but it certainly looks much nicer, especially if you zoom in very close on a texture.

Fake Backgound Objects and Environment Objects

You can do another 3dThing.h trick with the flags B_BACKGROUND_THING and B_ENVIRONMENT_THING. You set them after construction using the following API...

inline ulong B3dThing::Properties()
void B3dThing::SetProperties(ulong mode)

...using calls that look like this:

thing->SetProperties(thing->Properties() |
                     B_BACKGROUND_THING);

The "background thing" and "environment thing" properties are ugly hacks that let you fake a couple of features that aren't currently supported by the 3d Kit.

A real background object would be something huge and static (like the future B3dClosedWorld, discussed in the 3d Kit white paper, http://www.be.com/developers/3DWhitePaper.html) that would represent the "scene" in which other moving objects would evolve. You would be able to go around the background, below it, behind it, and so on, without using the depth buffer, and with more potential optimizations.

For now, though, the real thing doesn't exist. If you want to simulate a background object without having to use the depth buffer, just set the B_BACKGROUND_THING flag for the background object, and it will always be drawn before any other thing that doesn't have B_BACKGROUND_THING set. It's ugly...but it works (the butterfly demo uses this trick for the temple).

An environment thing is an object that you want always to be visible in the distance. A thing that's set to B_ENVIRONMENT_THING is always drawn first—even before the B_BACKGROUND_THING things. An environment thing is affected by the camera's rotation, but not by the camera's linear movement (its "translation"). Whatever the real size of an environment thing is, it's considered to be so big and so far away that no translation of the ViewPoint of the camera can affect the way you see it, which is a good approximation of a distant environment.

For example, you can create a reversed sphere (i.e., seen from the center), onto which you've mapped a spherical view of your environment. When you put it in your world and set B_ENVIRONMENT_THING, the camera will always see it from its center, properly rotated and never moved. Even if it's closer than some object, it's always drawn first, so it appears to be behind everything else. If you use both a depth buffer and the B_ENVIRONMENT_THING flag, it's best to use a big radius for the sphere, or you could get a nasty surprise. :-)

Importing Models

We support VRML 1.0 reasonably well, but who knows how well everybody (including Be) is respecting this format? If you want to create complicted shapes using a CAD program, it's probably the best way. But if you want to generate a shape using your own code (or your own CAD application), and you don't want to write/integrate a VRML exporter, an easy way to do it is to use the raw teapot.data format. It's a trivial format (check /boot/beos/etc/teapot.data for an example):

PointCount X0 Y0 Z0 NX0 NY0 NZ0 ... Xn Yn Zn NXn NYn Nzn

FaceCount A0 B0 C0 ... Am Bm Cm

The first list describes the list of points (PointCount = n+1, from 0 to n). Xn, Yn and Zn are the coordinates of each point; NXn, NYn, NZn are the coordinates of the normal vector of the shape in this point.

The second list is the list of triangles (FaceCount = m+1, from 0 to m), where An, Bn and Cn are the indices of the 3 points in the point list, going clockwise around the triangle.

Those are your 3d Kit tips for now. More would be better, but I hope these will be of some help to you. On behalf of everyone who has been working on the 3d Kit, I would like to thank all the developers that have used the 3d Kit, especially for sending us feedback, questions, and bug reports. We know it's not finished yet, but whenever I have a few days to create new demos (before a trade-show or a developer conference), I don't regret the work we've put into it.


News From The Front

By William Adams

It's hard to believe, but this marks my 53rd newsletter article since joining Be. When I joined Be, I immediately asked for newsletter duty, because I was so excited by the opportunities that lay ahead, and I wanted to spread my enthusiasm. In my 53 articles, I hope that I have helped a lot of new developers to understand, and become enthusiastic about, the BeOS.

This article marks a turning point in my career at Be. Before joining Be I was intensely committed to engineering. But I was a bit burned out on it, and for awhile I just wanted to help other developers write apps. Having a child encouraged me into a helping mode as well.

Now, after a year as a Be evangelist, I am returning to writing code on a regular basis. As a member of the engineering group I will be working on projects that I hope will further the prospects of Be and continue to help developers take advantage of this most awesome OS.

From now on, I'll be writing articles in rotation with other engineers. So this is the last installment of "From The Front."

In the future, you'll be seeing more articles written by our DTS staff and tech writers. They'll bring you good news and articles full of wisdom. Their topics will come from suggestions made by our end-users and developers. That means YOU.

If you have suggestions, go to our web page and fill out the form for suggested topics (see the next article for the URL). We'll try to address as many of them as we can in a timely manner.

Although I'll no longer be in your face on a weekly basis, you'll no doubt still hear from me. I'm full of excitement about the BeOS, and I'm looking to infect more converts, so I'll be there. As I'm always fond of saying "There you have it." I'll see you at the front.


Developers' Workshop: Fork(), Spawn_thread(), And The Beos

By Doug Fulton

This issue of the Be Newsletter introduces "Developers' Workshop," a new weekly feature that provides answers to our developers' questions. Each week, a Be technical support or documentation professional will choose a question (or two) sent in by an actual developer and provide an answer.

How different is this from the regular Engineering Insights columns? In content, probably not a whole lot—you'll see source code, pointers to fuller examples on the Web site, and get tips and suggestions from the horse's mouth. Beyond that, the column is an opportunity for the documentation folks to go into greater detail on certain subjects, and for the tech supporters to speak to a wider audience.

But the biggest "Developers' Workshop" difference is this: You get to ask the questions. Here's how:

We've created a new section on our web site. Please send us your Newsletter topic suggestions by visiting the web site at: http://www.be.com/developers/suggestion_box.html.

Send us your questions, and we'll use them as fodder for this column and for the Engineering Insights, as well. Today we'll dispel a rumor and visit an old friend.

An issue that's been bugging a few of our developers involves a statement made in the Kernel Kit chapter of the Be Book. Turn to hymn #105:

Threads and the POSIX fork() function are not compatible... If you call spawn_thread() and then try to call fork(), the fork() call will fail. And vice versa.

Is it really so? Well, not exactly...if you call fork() from thread land, the call won't fail, but you may be asking for trouble. The fork()'d process inherits global data from the "parent" team. Unlike a spawned thread, however, the data isn't shared between the processes: It's copy-on-write. This means that the child can *see* the parent's be_app object and all its BWindows (as examples), but it can't do anything with them. The child can't lock be_app, or send it a message, or talk to the windows. Even lower-level resources, such as semaphores and ports, are off limits (they're also copy-on-write). The forked process can't do anything "team-like."

So why would anyone be tempted to fork from within a threaded Be application? A nearly justifiable answer (and the only one that anyone around here can think of) is that it's because they're porting an existing chunk of code that they want to drop into a Be application framework. If this sounds like you, you have a couple of options, but only one of them makes sense. Let's start from scratch:

Go ahead and try porting your forking code. The code that's executed in the forked thread can only make POSIX calls, but since this is ported code, you won't be invoking "sensitive" global entities (such as be_app or semaphores) anyway. But are even the POSIX calls safe? Nope...exit(), for one, is a killer. If the child calls exit(), the game is over. There may be other POSIX calls that are deadly—running through the suite in the context of a multi-threaded app isn't a priority in the testing lab.

Okay, so exit() is out, and other POSIX calls might be dangerous. What's safe? The only (reasonably) reliable call is exec() (and its brethren). Typically, this is why you're forking in the first place. A fork setup is usually an expanded version of this:

int pid;
char *args[2];
args[0] = strdup("Clock");
args[1] = NULL;

if ((pid=fork()) == 0)
  execv("Clock", args);

But if you've come this far—if you're forking just to get another team running—you might as well rewrite the code to use the Kernel Kit's load_image():

thread_id load_image(int32 argc, const char **argv,
                     const char **env)

You have to supply an initial argument count and environment variable array (which you can pick up for free by extern'ing the global environ variable), but otherwise the load_image() call can be swapped in for the fork()/execv() calls. The new team inherits file descriptors from the caller, so you don't have to rewrite your pipe code. Here's what it looks like:

/* Declare the environ array. */
extern char **environ;

thread_id thread;
char *args[2];
args[0] = strdup("Clock");
args[1] = NULL;

thread = load_image(2, args, environ);

Given the caveats surrounding fork() and the ease with which you can trade in your fork()/execv()'s for a shiny new load_image(), there's not much reason to force square pids into round threads.

Don't be confused: The BeOS implementation of fork() is prefectly healthy. If you're porting a program that's pure POSIX, go ahead and fork(); it will work today, it will work tomorrow. But as soon as you introduce shared resources into your app—the type of resources that you can share between spawned threads—you're living on borrowed cycles.


The Substance of the DOJ Complaint Against Microsoft

By Jean-Louis Gassée

I write the word substance with intent, as I'd like to separate common sense from the law, and sentiment from colder prognosis.

We can read the DOJ filing, at:

http://www.usdoj.gov/atr/cases3/micros2/1237.htm

...the original 1995 "Consent Decree" on which the current complaint is based, at:

http://www.usdoj.gov/atr/cases3/micros0/0047.htm

...and Microsoft's first official response at:

http://www.microsoft.com/corpinfo/doj/dojletter.htm

My associates shake their heads and tell me reading legal briefs is decidedly an acquired taste. What can I say, I love US law. Ascribe my deviant enthusiasm to the stronger loyalty often exhibited by new immigrants.

On the legal front, I see four questions: Who defines what can and cannot go into an operating system; is Explorer bundled with Windows in violation of the Consent Decree; did Microsoft use strong arm tactics in violation of the Consent Decree; and does Microsoft's NDA goes too far in preventing OEMs from airing their grievances.

I'll skip the last one, it's peripheral to the more important issues and Microsoft has already conceded they didn't "mean" for the NDA to apply to Federal investigations.

Did Microsoft use strong-arm tactics in violation of the law or the terms of the Consent Decree? Possibly, but in a way, such behavior is subordinated to the bigger issue of bundling.

And, of course, we don't want the government to tell us which lines of C++ code belong to the OS and which ones belong to the application. That's not the central legal issue. The real question is whether or not, in the eye of the law, Explorer is a separated product or an extension of Windows.

Microsoft says Explorer is a system extension, while the DOJ points out Microsoft offered and continues to offer it as a separate product. This is true, buy Microsoft's Money 98 and it ships with a copy of Explorer.

As a result of their licensing agreement with Microsoft, OEMs cannot alter Windows at will. This is the lever Microsoft is accused of pulling against Netscape. Don't delete Explorer, it is a part of Windows. The DOJ views Explorer as a separate application and accuses Microsoft of using the integration pretense as a way to make impossible for OEMs to remove Explorer and install Navigator instead. To which Microsoft rightly retorts they must keep Explorer and they can install Navigator as well.

Can a software product be a separate application in some cases and part of the system in some others? What about modular systems? The legal discussion is guaranteed to be involved and I have no idea which way the legal razor will cut this issue.

On the common sense front, the situation is ripe with ironies. For instance, I wonder if OEMs are allowed to pick and choose which drivers (graphics cards, network adapters) they install on the system they ship. I believe they actually perform such modifications of the system.

Legal contortions aside, is adding or removing a driver from the system distribution much different from removing Explorer? Microsoft says they want Windows unmodified because they're concerned about the quality of the user's experience. At the same time, Microsoft claims the right to alter Java almost any way they like. Again, they might be right in their reading of their Java license agreement (read their countersuit at http://www.microsoft.com/corpinfo/java/java2.htm), but the public perception might be negative.

Speaking of ironies, when Windows 98 ships, the bundling issue will become moot unless Microsoft decides the system needs to ship with more and better functions, such a shell for Microsoft Office applications guaranteeing users a cleaner, better integrated set of office productivity solutions.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.