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.
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.
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:
voidB3dCamera
::SetViewPoint
(B3dVector
originInWorldSpace
,B3dWorld
*world
)
Or you can "attach" your camera's viewpoint to some other existing thing:
int32B3dCamera
::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
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
int32B3dThing
::LinkTo
(B3dLink
*link
, ulongmode
=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:
int32B3dThing
::Unlink
(ulongmode
= 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 are the offspring of links and hierarchy. If you want to package a
small number of B3dThing
s 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 B3dLink
s 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:
longgroup_things
(longcount
,B3dThing
**list
,B3dThing
**group
, char *name
,B3dVector
*origin
= 0L, floatthreshold
= -1.0, ulongmode
=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 B3dLink
s 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.
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
, uint32renderFlags
=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.
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 ulongB3dThing
::Properties
() voidB3dThing
::SetProperties
(ulongmode
)
...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. :-)
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.
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.
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:
intpid
; 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_idload_image
(int32argc
, 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_idthread
; 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.
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.