Sofie
a real-time 3d-engine
General Comment: Sofie is still alpha software, so do not expect
- that everything works ok, and
- the demo makes fun!
Download files - to download files
Screenshots of the Demo
Quickstart - to get the demos running
Short story behind the current demo
Directory
Abstract
Sofie is a real-time 3d-engine, with which it is easy to construct objects consisting of
textured polygons and let them interagate. It could be used to program games or
virtual realities. Sofie is licensed under the GNU-General Public License, and
therefore full source-code is provided.
Versions
This documentation belongs to Sofie-0.2 which was released 10.November 1997.
Compared to Sofie-0.1, Version 0.2 comes with a new Object Manager, which is not
quite ready, but it should work well with the demo. The main change is, that this
new Object Manager can handle objects with immerge into another object.
I promised in the documentation of Sofie-0.1 that Version 0.2 will
have homing missiles. I postpone this for Sofie-0.3. The demo is now
not so well-balanced. This will change. Graphics of the demo will be improved
(if I find somebody who volunters), Computer Enemies will be improved.
Some functions will be optimised. (there is still a lot to optimise).
The object manager is still not ready. More OO methodes will be implemented.
Particles in space will be implemented, to get impression of speed.
Requirements
To run Sofie you need
- Linux
- gnu c++ compiler et al
- X with
You can check the availability of these features with the command xdpyinfo.
- joystick support by kernel, if you want to use a joystick
- the program pnmscale installed, if you want to resize the window
- a fast pentium (the author's computer is a pentium 133)
License
Sofie is copyrighted © 1997 by Stephan Schießling and
distributed under the terms of the GENERAL PUBLIC LICENSE (GPL).
If you do not know that, please contact the
Free Software Foundation, Inc.
675 Mass Ave, Cambridge, MA 02139, USA.
History
Work on Sofie began in december 1996, it was intended as little project to learn basics of 3d graphics.
The name Sofie was chosen, because at the same time the author became father of a little girl named Sofie.
The first versions of sofie used SVGALIB. First there was only one polygon which could be rotated.
At that time the author got to know a free software project called wt, which is a real time 3d-engine
like doom. But unfortunately this project obviously stopped, there was no progress for years.
The second bad thing about it was, that it was not possible to move in all directions of 3D-space.
This is the difference between games like doom and games like descent. Therefore the author decided
to write a free-direction 3d engine from scratch. wt had this nice stone textures, and they got used in Sofie.
Programming with SVGALIB was really a pain, because if Sofie was buggy the hole system "crashed".
Actually it did not crash, but the keyboard was reprogrammed, therefore one was not able to type in commands.
Because of that limitation the author decided to switch to X and uses the MIT-SHM extension.
This combination has proven to be equally fast as SVGALIB, but debugging was much easier.
There is an interesting project out there in cyberspace, called GGI. This will substitute SVGALIB soon,
and has not the limitations of SVGALIB. If GGI is ready, the author intends to support it.
FAQ
The demo is too slow!
No, your computer is to slow. But try resizing Sofies window, this may help.
The keys are not working!
Activate Sofies window by moving the mouse pointer into Sofies window.
Activate NumLock, by pressing Num until NumLock light appears.
Joystick does not work!
The joystick can only work if the joystick driver is installed,
it is a loadable module for the kernel.
Per default the joystick is not activated by sofie. To activate,
set the JOYSTICK label in the makefile src/Makefile.
After resizing, a lot of error messages occur!
You probably activated the cockpit, and resizing of the window
implies resizing of the cockpit. Sofie uses for that operation
the program pnmscale. Get this program and install it in your system.
If you can't do that, deactivate cockpit. (use the key c for that)
Why is the window so tiny?
Then I ask you: Why is your resolution so high? Games usually use a resolution of
320x200 pixels, so do I. If your X uses 1024x768, then Sofie's Window is really
tiny. But you simply could resize the window, if you have enough time...
Another possibility is change the resolution X is running. The author uses normally
1024x768 but for Sofie he sometimes switches by some keypresses to 400x300.
How to use Sofie
What are Objects?
In a 3d scenario you encounter objects which are independent of other objects.
For example there is one spacefighter and there are other spacefighters.
Or there is a plane flying through a big room. The spacefighters, the plane
and the room are examples of objects, which are represented by the class Object.
Here you find a simplified version of that class:
struct Object {
float power;
float mass;
int live_time;
View view;
Point velocity;
Polygon_List polygons;
Pilot pilot;
String id;
}
- Every object has a value mass assigned to it. Those who know a little
bit physics know what mass is. (A good rule is: The bigger the object,
the bigger the mass value). Similar to that is the power value. Say your
object is a spacefighter. Then it has an engine on board which produces energy
(for shield, energy weapons, engine unit). float power is the amount of energy
availible by the object. If this is <= 0, then the object is considered as
non-existing. In a game some objects are not used the whole time. Therefore they
should be dynamically constructed (with a new-operator) but this could take a lot
of time. Sometimes it is better to construct them at the beginning and give them
power <=0. To "construct" them one has to set power > 0.
- You can observe, that an object has a live_time, that means it is not expected
that an object "lives" forever. An example would be a spaceship which explodes.
(But if livetime=0 then it lives forever).
- An object has its own view, that is a coordinate system of its own.
This should be clear, imagine the object is centered at some point and
has there a special orientation. This is expressed by an affine coordinate
system: There are three linear independent vectors x,y,z and an origin
(which is certainly not always 0). (By the way x,y,z are right handed
and normalized, of course).
- There is also a velocity vector. This is not the same as speed,
because speed is a scalar. The absolute value of the velocity vector is speed,
and velocity gives also the direction in which the object is moving.
- Of course objects can change the velocity. The one who is responsible
for that is the pilot. Therefore every objects has a pilot, even rooms
(objects which do not move get a pilot, the zero pilot, ie. a lazy pilot who never
gives commands).
- An object should have a tool to keep track of other objects. This is
the job of the radar (see .....). The pilot will use that class.
- Until now an object is really anonymous. But that needs not to be.
The string id is used to identify the object. This information will be
passed to all radars. For example you could give your spaceship the
indentifier "Enterprise".
- Last there is a list of (textured) polygons. Of course an object should
know of its shape. (Actually the shape is a BSP_Tree (see ....)).
How to create Objects?
Look at the world files. There should be a lot of examples.
I'll show you an example here:
Object * ship=new Object;
// now the view coordinate sytem of the object
ship->view.O.init( 10 , -10 , 5 ); // the origin (should be objects center)
ship->view.Xv.init( 1 , 0 , 0 ); // x coordinate
ship->view.Yv.init( 0 , 1 , 0 ); // y coordinate
ship->view.Zv.init( 0 , 0 , 1 ); // z coordinate
Pilot pilot;
ship->attach_pilot(pilot);
ship->set_id("Enterprise");
//here the shape of the object will be defined.
//(see How to create the shape of an object)
Object_Manager objects; // only necessary once
objects.add(ship);
First there will be allocated dynamic memory for the object ship. Then the
coordinate system will be defined. view.O is the origin (where the
center of ship is located) and view.Xv, view.Yv, view.Zv are the three
coordinate vectors. But you could leave out the definition of
view.Xv,view.Yv and view.Zv. Otherwise you should be sure that these 3 vectors
form a right handed normalized coordinate system. Then a pilot is defined.
Here the generic pilot is used, this is the "lazy" pilot (who does nothing),
mentioned above.
Our object ship gets then the identifier "Enterprise". Finally we have to register
the constructed object by the object manager. For a whole world there is only one
such object manager necessary, but many objects.
The shape of the object is defined by a list of polygons (see ....), and
this list could be very long. If there shall be several objects of the
same shape, then it is annoying to define for every object the same shape
by a polygon list. In this case one simply copies an existing object.
This is achieved by using an other constructor:
Object * ship2=new Object(ship);
Here ship2 is a copy of ship. Then you can alter the other entities like above.
What are subobjects?
What would be an airplane without rockets? An airplane is considered as an object.
A rocket is considered as a subobject. Say an airplane has two rockets, then there
will be two subobjects rocket1 and rocket2 defined. A subobject is again an object,
but it is sub because it is associated to another object.
How to define polygons
A polygon in 3d-space is given by its vertices. All vertices must be contained
in one affine plane. If one likes to define a triangle T, then first define its
vertices T1,T2,T3, and then the Polygon:
Point T1(0,0,0);
Point T2(1,0,0);
Point T3(0,1,0);
Polygon T(T1,T2,T3);
If you want a polygon with 4,5,6 vertices, then the definition is analogous.
For n vertices you have to something like that:
int n=10;
Point * field=new Point[n];
field[0].init(0,0,1);
....
field[9].init(0,1,0);
Polygon T(field,n);
There is one restriction to the order of the vertices. You have to use one
of the two (if starting vertex is fixed) possible circulations. Lets say
you have a square defined by the Points P1,P2,P3,P4, where P2--P4 is a
diagonal, then don't use the order Polygon(P1,P2,P4,P3);
To draw a polygon, the polygon should have a color assigned to it.
You assign a color by
Pixel white=65535;
T.color=white;
(At the moment, color 0 has a special meaning, but this will probably change).
Sofie can also deal with textured polygons. (For those who don't know it, a
Texture is a wallpaper for the polygon). To define a texured polygon,
lets better say mipmapped polygon, one has to define a mipmap first:
Mipmap M("textures/imagexyz");
This mipmap could be used by many polygons. Therefore define each mipmap only once.
For every polygon one has to define a texture coordinate system (two dimensional):
Point TO(1,2,5); // origin
Point TR(0,1,0); // first coordinate vector
Point TU(0,0,1); // second coordinate vector
The affine coordinate system TO,TR,TU is a coordinate system for the plane
in which the polygon lies. Actually Sofie does not insist on that, because
it projects TO, TR, TU orthogonally on this plane to get such a coordinate system
(if possible).
In practice, one has the vertices of the polygon, and likes to use one vertex
as texture origin, and the two adjacent edges as texture coordinates TR, TU.
The length of TR and TU determine the size of a texel of the mipmap.
How to achieve that:
// define Points P1,P2,P3
MPolygon P(P1,P2,P3); // not Polygon ! this is a mipmapped polygon
Point TR,TU; // P1 should be the texture origin
TR.adjust(P1,P2,128)
TU.adjust(P1,P3,64)
TR.as_texture_coord();
TU.as_texture_coord();
Here P1 is the texture origin, TR has direction from P1 to P2, and the length
is such that 128 Pixel of the Mipmap will exactly fit in the edge from P1 to P2.
Analogous for TU.
And here is a complete definition of a mipmapped polygon:
// define Points P1,P2,P3
Point TR,TU; // P1 should be the texture origin
TR.adjust(P1,P2,128)
TU.adjust(P1,P3,64)
TR.as_texture_coord();
TU.as_texture_coord();
Mipmap M("textures/imagexyz");
MPolygon P(P1,P2,P3); // not Polygon ! this is a mipmapped polygon
P.attach_mipmap(P1,TR,TU,M);
The methode attach_mipmap(...) should be clear. Now P is a mipmapped Polygon.
In most cases, a world is constructed such that a polygon can only be viewed
from one side (imagine, that one object is a cube, and you never want to
fly into that cube). Then you should tell that the polygon by
P.backface=true;
This will vastly increase the rendering speed. The attentive reader should
have noticed, that there was no commitment which side of the polygon has to be
drawn. This is described by the direction of the vertex circulation. This works
with the right hand rule. With the four fingers follow on the direction of
the vertex circulation. Then your thoumb is perpendicular to the polygon and
points in a special direction. A viewer which looks from some location in that
direction onto the polygon will see this polygon. A viewer which looks from
some location in the opposite direction onto the polygon will not see this polygon.
There are exactly to vertex circulations and each is corresponding by that rule
to one side of the polygon.
Now you know how to define polygons. Last the polygon should be added to
the shape of Object myobject. This is done by
myobject.registrate(&P);
If all Polygons are registrated by the Object, then you should inform your
object by
myobject.shape->build();
Now your object is defined.
What is a radar?
A radar is a class which contains information about active objects in the world
with exact location and their identification. Actually the object manager provides
a global radar (object_loc), and each radar gets this information.
It is possible to build the radar with a filter. That is, you can tell the
radar to show only those object with specific identifications.
The radar is importent for the pilot of an object. A computer pilot gets a
radar to determine the positions of objects. To draw the cockpit, the radar
is also necessary.
What is the class input for?
A pilot gets as input the radar. The output is an instance of class input.
The objects gets information how to act by the class input.
What is a pilot?
An objects needs someone who steers the object. For example if you
steer an object by the console, then your object has the console_pilot
assigned to it. Or if you use a file with steering information to steer an
object, than this object has the player_pilot assigned to it.
In general a pilot is a class which provides a special methode
Input * process(Radar &radar);
ie, the pilot gets a radar as input and it provides as output a instance
of class Input (=output of pilot). To write a pilot, one has to modify
this methode.
What different pilots are there already?
- The generic Pilot: Pilot
does nothing
- the Console Pilot: Console_Pilot
for using the console as input
- the modified Console Pilot: MConsole_Pilot
the same as Console_Pilot, but a,y,z act in another way.
- the Recorder Pilot: Recorder_Pilot
uses the console as input, but creates a file with this input
- the Player Pilot: Player_Pilot
uses the file the Recorder Pilot produced to steer the object
- the basic computer enemy: Basic_CPilot
a computer enemy with very limited behaviour
- the mine pilot: Mine_Pilot
does not move location, but fires at all enemies of it.
Sofies internals
To read the source code, get the package doc++ and type
make doc++. Then you load the file src/html/aindex.html
in your html browser.
The screen is managed by the class Screen_Manager. It keeps
track of all free (ie. not already drawn) pixels.
With the screen manager it is possible to draw the polygons
from front to back. And at the same time it is easy to implement
a cockpit.
The shape of an object consists of polygons. The problem is
the hidden surface removal. This is achieved with the
BSP tree technique.
How to contribute
Of course Sofie is not a small software project, so that it is not easy to catch up.
But there are some tasks (not all) which could be done independently.
- If you are not experienced in programming, then I have the right thing for you:
Create world files, ie. create objects and paint them with nice textures.
- Write a new Computer Pilot. This is actually only a function, which gets as
input the locations of other objects and as output an input class. You only have to
know input and output. In between you are free.
- For tougher guys: Create a whole game on top of Sofie.
- If nothing of the above fits your interests, then may be you know
what you like to contribute.
Expression of thanks
- Jan Valtanen for the construction of the afighter (look at the seventh screenshot)
and for interesting conversation about the project.
Stephan Schießling
Last modified: Fri Nov 7 01:05:16 MET DST