![]() |
| Most recent update (All By Hand(TM)): 30-Dec-2006 19:19 |
|
Starry, starry night
The Frontier Galaxy VII: Frontier ObjectsAs mentioned first in Part II: Distant Suns, the 3D objects in the game are all referenced by a single number in a single large list. All of the objects in the entire galaxy! That means everything from a humble Escape Pod (est. size: 4 meters) up to the map locator grid (est. size: 4ly). The game is quite clever in the way 3d objects are represented: a single structure defines each and everyone of them by size, shape and color. The coordinates are simple integers, but one of the members of this structure is Scale; the coordinates defining the object are multiplied by this (actually, more like shifted left). The 3D shapes are drawn by a clever routine, reading sort of a command language that defines it as we go. The language can make objects be drawn differently for size, distance, rotation and even external parameters, such as time or global game preferences. The commands in Frontier:First Encounters are clearly based on those in Frontier:Elite 2 -- there are a few small differences, mostly to do with suddenly having megabytes of memory instead of kilobytes; if you visit the FrontierVerse you can find my original doc outlining the FE2 objects. This one is all about FFE. You might want to download the companion program ShowMesh which I wrote to, er, "disassemble" the models into ASCII text, but this produces a long, long listing which won't mean anything to you -- yet! Read on...
As usual, I'll start with the base structures. And (also as usual) there are again a few unknowns in here... The Object ListIn FE2 there are three different lists of objects: the main objects appearing in the game proper (starships, planets, cities, you name it), a list of specials (vector fonts and the tombstone when you failed to dodge something lethal), and the intro objects (an Explorer happily dodging Eagles and the odd space station). In FFE there is only one big list in which you can find everything. The list itself is nothing but a long list of pointers to individual objects. There appears to be no definition of how many objects there are in the list (apparently those who create it knew how many there were). Each pointer points to either zero (no object for that number; hopefully not used in the game!), or to a Model structure. The Model StructureEvery single 3D model is defined with exactly the same structure. If you ever wondered how it is possible to use an asteroid by way of spaceship, read on! It's just a matter of plugging in the right numbers in the right locations. Each object starts like this:
typedef struct {
unsigned short * Mesh_ptr;
signed char * Vertices_ptr;
int NumVertices;
signed char * Normals_ptr;
int NumNormals;
int Scale;
int Scale2;
int Radius;
int Primitives;
color_t DefaultColor;
char padding;
int field_28;
int field_2C;
int field_30;
unsigned short * Collision_ptr;
ShipDef_t * Shipdef_ptr;
int LinefeedCharacter;
unsigned short * Character[1];
} Model_t;
Additional structuresStructure
typedef struct {
short ForwardThrust ;
short RearThrust ;
char Gunmountings ;
char FuelScoop ;
short Mass ;
short Capacity ;
short Price ;
short Scale ;
short Description ;
short Crew ;
short Missiles ;
char Drive ;
char IntegralDrive ;
short EliteBonus ;
short Camera1_x, Camera1_y, Camera2_x, Camera2_y ;
short frontMount_x, frontMount_y ;
short rearMount_x, rearMount_y ;
short topMount_x, topMount_y ;
short bottomMount_x, bottomMount_y ;
} ShipDef_t ;
Vertex and Normal definitionsThese are arrays of 6 bytes each. For vertices, the first byte determines the type of coordinate, then a padding byte follows, next are 3 signed characters for x,y and z, and closed with another padding byte. typedef struct {
unsigned char type;
unsigned char pad1;
signed char x,y,z;
unsigned char pad2;
} Vertex_t ;
As said above, this structure only defines the even-numbered vertices. Whereever you need an odd numbered vertex, first calculate the even one, then copy it for the odd vertex and negate the There are a lot of different types; I don't know the meaning of all of them. A few: Type 1 is a normal vertex and defines a 3D point as usual everywhere in the rest of the world. Type 5 is a negate-all vertex. The z member is another vertex number; this one gets the same x, y and z coordinates, all negated. The odd-numbered vertex has its Type 7 is a weird one, it is used when this point should ramble around at random. Experimenting with it, it seems the x value here is the number of the actual vertex (taken from the same list) serving as base, and the z value is some max amount of random movement. Type 11 is another pretty weird one. Its y and z values are taken as vertex numbers, and this point should be exactly inbetween those. The x member appears to be (mostly) 1, and is not used. Type 19 uses yet another feature: external variables! The variable number is in the ; As seen in the Adder model: #52/34h: 19, -63, 46, 50 // Interpolate(50,46, LOCAL[1]) where
The C0h prefix means "use local variable"; what remains is "1", so, use the value of local variable #1 (whatever that is at the moment). I'm not sure on the max value for the variable and whether they can have a sign; my wireframe model display program is broken (again!) so I can't try it out. Actual coordinate values can be defined recursively, where, for example, point A is halfway between point B, which is calculated from a variable, and point C, which is fixed. When drawing, you'll need to calculate them one by one. Also note that for every odd vertex number, you'll need to calculate the even member first and then apply the negative x thingy.
The Normals also have a "type" value -- this is, in fact, a vertex coordinate, which should be used as the reference point for visibility checking. I got it working somehow by rotating the normal and this reference vertex, and then check if their scalar(?) value > 0 ... sort of. If you take any normal x,y,z and calculate The additional +2 is because "no normal needed" is encoded in the objects as the number '0'. If a normal is needed, its number goes up from 2 (and you should take normal number minus 2 from the array for its values). VariablesThe variables are either 16-bit If a variable is needed somewhere, you must inspect the bits to see where to get its value from (or store to). The def bits are the two highest ones, so masking with C0h there are four possibilities:
00h: immediate value in range 0..63
40h: large immediate value. Shift left 10 bits for anything from 0 to &FC00
(in steps of &400).
80h: a value from the Global pool, numbered 0..63. Variable #0 is the TIME,
as can be seen in some animated objects
C0h: local variable, numbered 0..6. Should be initialized somewhere before being used!
The game uses local variables 0..6 only. The results of math calculations are always put into the local pool (oddly, the code doesn't seem to require the The global variable list gets filled for each model instance. A model instance is a single unique occurrence of any model in the current game; if you see three Vipers on your screen, each one is a single instance of the Viper model. This can be seen in the (far from complete!) list of global variables, where you can find the unique id of each model: 0: Game Time/1024 1: Landing State, Flags 2: Unique_Id, low word 3: Unique_Id, high word 6: Game Time, low word 7: Game Time, high word 8: Game Date, low word 9: Game Date, high word 10: Planet Orbital Radius ... 13: Current (left?) side thrust 14: Current (right?) side thrust 15: Current Main thrust ... 22: Equipment bytes 0 and 1 (bits are set for equipment) 23: Equipment bytes 2 and 3 27: Front and Rear Gun types 28: Top and Bottom Gun types 29: Missile 0 and 1 types 30: Missile 2 and 3 types 31: Missile 4 and 5 types 32: Missile 6 and 7 types 33: Missile 8 and 9 types 34: Hull Mass 35: Number of Shields Variable number 0 is the game time divided by 1024. The game runs internally on 49710 ticks per second; this is sort-of conversion to get a 50 fps timer resolution. Some variables can have different meanings depending on what model they are for. A few graphic commands use a variant of the variable retrieval routine, where either an immediate value or a variable is needed:
00h and 40h: immediate value of ((Value & 0x7f)<<9)+((Value >> 7) & 0x1ff)
80h: Global one as above
C0h: Local one as above
Command codesThe structure member The byte order for these words is Intel-mode; I stuck to it when I realized it was a complete mess. There are cases where high and low bytes should be swapped, and others where they shouldn't. I'll try to give a relevant example for each command, extracted using my proggie ShowMesh. If you'd like to see all definitions-as-I-understand-them, download and run it. It requires to be in the same directory as an original firstenc.dat (size 2,101,248 bytes) as provided in, amongst others, JJFFE, and it is a console program, meaning you have to read very fast or redirect its output into a file. A number of text editors can run an external program and capture its output automatically; Jongware's personal recommendation is TextPad. If you don't have one of those, redirect the output to a file like this: in a command window, type The first block after the command name are the parameters word-wise; if a word should be splitted into bytes, they are shown with a vertical bar inbetween, high byte on the left side, low byte on the right. The example code dump is in hex, its comments are in decimal. In the explanations it should be clear from the context where I switch between those! 0 = End0000 Example: in just about every model. 0000 ; End In model #0 (Vector Font): 0720 ; End End. Stop. Halt. Do No More. Usually the entire word is 0000h, but in the vector fonts each separate character ends with some value in it. This value (shifted right by 6) points to a vertex in the font model. This vertex is used only to transform the text cursor position to the right -- e.g., transform this vertex, then add its x, y, and z to your current text position. That's the next character's position. If a character value is less than 32 (space) you should insert a linefeed -- its transformation value is the vertex referenced by the Every individual model should end with the 1 = Ball0001 MATERIAL RADIUS VERTEX|NORMALA simple example first; the common mine, if it is far away: 0001 0666 8025 2200 ; Ball(RGB(6,6,6), Radius(19200), Vertex(34), Normal(NORMAL_NONE))
In the Escape Capsule (its big yellow light):
0001 2EE0 E02E 0482 ; Ball(UNLIT | COLOR_YELLOW, Radius(24000), Vertex(4), Normal(2 | LIGHT))
The ball is coded to use normal #2 here, but by looking at the code I understand the normal value isn't used at all... The bit 2 = Line0002 MATERIAL STARTPOINT|ENDPOINT An easy one. Check this out (half of the graphics font '+' when viewed from afar; the other half looks the same but uses other vertices): 0002 3000 070D ; Line(LOCALCOLOUR | UNLIT, 13,7) Don't ask about the A line can always be seen from every angle so it does not use a Normal (that would be Abnormal). 3 = Triangle0003 MATERIAL POINT1|POINT2 POINT3|NORMAL Also, an easy one. A single triangle from the StowMaster Fighter looks like this: 0003 4034 0809 0204 ; Triangle(Texture_034h, 8,9,2, Normal(4)) It's a fair bet you can optimize your drawing code if you first calculate the normal and check if the triangle is visible. If so, fetch the coordinates for the vertices (including properly re-calculating them, and in the case of coordinate number 9 here, negating its z), transform those to the screen and blit. 4 = Square0004 MATERIAL POINT3|POINT1 POINT2|POINT4 0|NORMAL It'll get complicated later on; this one is ee-zee too, apart from the vertex order. See this snippet (top [or bottom] from the Lifter): 0004 4016 0605 0704 0002 ; Square(Texture_016h, 5,7,6,4, Normal(2)) The zero aside the normal is not used. The vertex order is "heuristically derived", that means "I guessed it". If you get a strange x shape instead of a square swap coordinates until it works OK. 5 = Polygon0005 MATERIAL NBYTES|NORMAL [POLYCMD]+ 0000 Previous commands to easy for you? This one is a even-odd filled polygon command, including holes and curves, and may intersect itself (hence the "even-odd"). It took me a fair bit of hacking before I got to grips with it; still haven't figgered out all possibilities.
First is first in reading order in this hex dump! It's the first byte of an Intel-type word, so it is actually the second byte in the original code (and its first parameter byte is the first byte of the pair). I'll give examples of each sub-type since it is probably (somewhat) easier to understand that way.
Object #312:Six-sided Landing pad:
0005 2444 0E00 0400 0002 0604 0605 0603 0601 0000 ; Poly(UNLIT | COLOR_GRAY, Normal(NORMAL_NONE), StartLine(0,2), ContinueLine(4), ContinueLine(5), ContinueLine(3), ContinueLine(1), EndPoly() ) Following the command code The first byte is a The next byte is The next bytes are again The last command,
Object #35:Gyr attack fighter: 0005 0353 0C02 020A 0A0C 0D0B 0617 0616 0000 ; Poly(RGB(3,5,3), Normal(2), StartCurve(10,12,13,11), ContinueLine(23), ContinueLine(22), EndPoly() ) This one starts at the fourth word with a
Object #34:Viper Defence MkII: 0005 4028 0E0F 0203 030F 0D07 0809 1715 0603 0000 ; Poly(Texture_028h, Normal(15), StartCurve(3,15,13,7), ContinueCurve(9,23,21), ContinueLine(3), EndPoly() )
This one also starts with a curve (the words The next line is a straight one, from the last point of the curve to coordinate number 3. Since this closes the figure nicely there is no close line needed.
So far every polygon started with a Start type and continued with continue types. It is possible to make intersecting figures with these (on the side of the "2001" orbiting station there are gray markings which do just this); it is also possible to forcibly end a segment and start a new set of lines, creating a hole. 0005 3000 1800 0228 2826 2729 0809 0828 0A00 020E 0E0A 0B0F 0811 100E 0000 ; Poly(LOCALCOLOUR | UNLIT, Normal(NORMAL_NONE), StartCurve(40,38,39,41), ContinueCurve(9,8,40), EndSegment(), StartCurve(14,10,11,15), ContinueCurve(17,16,14), EndPoly() ) Here we have the letter 'O' in the vector font (object #0), which should have a big hole in the middle. Ah, there it is! Command Easy to decode, hard to draw. To draw the big texts in my map program FFEStarSys I tried several different polyfill routines but found none which could handle both curves and holes with grace and speed. I resorted to cheating: I converted the entire vector font to EPS images and from that to a real TrueType font, and use FreeType to display it.
The final subtype is to add a Circle to the polygon, using 0005 0888 0602 0C12 0902 0000 ; Poly(COLOR_WHITE, Normal(2), Circle(Vertex(18),Normal(2),Radius(9)), EndPoly() ) ... on each of the four sides, but I have no real clue on how to work with it! I can't get anything useful on my screen in the right orientation. To make it just that more complicated, the circle command can be used more than once in a single polygon command: Object #61:Boa Freighter 0005 20EE 0E0A 0C0E 030A 0C0F 030A 0C10 030A 0000 ; Poly(UNLIT | COLOR_CYAN, Normal(10), Circle(Vertex(14),Normal(10),Radius(3)), Circle(Vertex(15),Normal(10),Radius(3)), Circle(Vertex(16),Normal(10),Radius(3)), EndPoly() ) ... draws three circles at once. I've seen no combination of "regular" polygons and circles in one single polygon, so this one may be a quick hack courtesy of the original coders...
It is entirely possible to define a 3-dimensional shape with a single polygon command; the Imperial Courier does this. But since there is a single Normal defined for the entire plane you can get unexpected results when you rotate such an object: the polygon is sometimes drawn when it shouldn't and sometimes not when it should. See also the Imp Courier, where its sides appear and disappear just before they should. A new version really needs Bezier patches for this! 6 = TRANSFORMATION?XXX6 [ADDITIONAL]* Used a lot. It sets or resets the active transformation matrix. Very complicated stuff. Maybe I'll tell you about it later... There may be data in the 11 unused bits (shown as Xes above) and some additional words may follow, which clearly belong to the same command but are also unknown. Looking at the code, I've deducted that the remaining bits define how much bytes there are, in this way: switch (originalCmd >> 13)
{
case 1: Skip bytes until you see a FFh, then read another word
case 2: No additional bytes
case 3: Skip bytes until you see a FFh, then read another word
case 7: if (originalCmd >> 5) is one of
2045: two additional bytes
2046: none
2047: none
default: two additional bytes
default: no additional bytes
}
... and I'm not even sure if I'm right ... Wanna see examples? All taken from the original code! 0006 ; Cmd 6 [nop] 4746 ; Cmd 6 [2] FFE6 ; Cmd 6 [7]: 2047 6FC6 FF26 002C ; Cmd 6 [3] E406 FCE0 ; Cmd 6 [7]: 1824 E8E6 FFE3 ; Cmd 6 [7]: 1863 ... where the bracketed number is my sub-classification, and the purpose of the number (derived from its parameters) is unknown. 7 = Mirror Triangle0007 MATERIAL POINT1|POINT2 POINT3|NORMAL Two triangles for the price of one in the Sidewinder: 0007 4027 0002 0406
; Triangle(Texture_027h, 0,2,4, Normal(6))
; Triangle(Texture_027h, 1,3,5, Normal(7))
The first triangle is defined the same way as number 3, the normal triangle; for the second one, use mirror coordinates and normal. This does not mean 'take the next one' (coordinate number 1 becomes #2) but instead flip the lowest bit (mirror of number 1 is number 0 and vice versa). The same holds for the normal. 8 = Mirror Square0008 MATERIAL POINT1|POINT2 POINT3|POINT4 0|NORMAL Two squares, also for the price of one, in the generic missile: 0008 1000 1604 1802 0008
; Square(LOCALCOLOUR, 22,4,24,2, Normal(8))
; Square(LOCALCOLOUR, 23,5,25,3, Normal(9))
Pretty much the same as the mirror triangle. 9 = Pine0009 MATERIAL STARTPOINT|ENDPOINT XXXX Should be named 'ellipsoid' but I like the name 'pine' coz it's used for a patch of pine trees. The This pair of jet flames comes from the Eagle LR fighter: 0009 20EE 302A 04B0 ; Pine(UNLIT | COLOR_CYAN, ..) 0009 20EE 312B 04B0 ; Pine(UNLIT | COLOR_CYAN, ..) A side note: the pine trees aren't texture-mapped, but jet flames are. Pretty weird, 'cause their color is usually defined to be plain cyan. 10 (0Ah) = Text000A MATERIAL FONT_SCALE|NORMAL ORIENTATION|VERTEX TEXTID The byte [*] Did anyone ever notice that? In the game when zooming in you can barely see something is there, but I remember a close-up in a games magazine where it was big enough to read! Imagine my surprise when I found it is actually there. This Is Not A Red Herring! The There is something odd about the orientations. Transforming the matrices to cater for rotation and mirroring toggles a single bit flag on and off, which is only checked in the DrawText routine. This flag determines if the current total of rotations would yield the text upside down, and adjusts the matrix accordingly. The problem is, every time I change just about anything in the relevant code, some of my texts are upside-down or mirrored, where others stay in the correct position! The The Graphics font consists of windows and doors; they point in the code to text strings like "0J" and more of this ilk, and are pasted onto the sides of buildings. As an example: the identifier on the side of the Interplanetary Shuttle: 000A 0000 0607 4612 3016
; Text(COLOR_BLACK, Normal(7), Vertex(18), Scale(6), VECTOR_FONT, 3016h)
... where 000A 0888 0406 4218 3022
; Text(COLOR_WHITE, Normal(6), Vertex(24), Scale(4), VECTOR_FONT, 3022h)
... where 11 (0Bh) = Skip If Not Visible/Skip If Further ThandddB DISTANCE_OR_NORMAL
Two commands in one. If dddB DISTANCE
(where, obviously, If the bit is set the command is dddB 80h | NORMAL
If the [*] The skip bits used are the 11 top bits. Shifting the command right by 5 (to get rid of the command code) yields the number of words to skip, but since every command code and its parameters are always words I used an easy explanation. There are another 2 commands which use this same mechanism; their codes have a '1' in the upper nibble, so using this same text there will not work. Example: in the vector font, the comma won't be displayed if it gets very small: 008B 0258 ; if (DISTANCE > 600) goto L137
0003 3000 000A 0C00 ; Triangle(LOCALCOLOUR | UNLIT, 0,10,12, Normal(NORMAL_NONE))
L137: 0620 ; End
(This could have been coded as In the generic missile, first the body of the missile gets drawn using a few rectangles. Then: 000B 0248 ; if (DISTANCE > 584) end ..and the tail fins are drawn. After that: 000B 01A4 ; if (DISTANCE > 420) end .. and the fins at the warhead are drawn. The further away the missile is, the less is drawn! The Not Visible command works quite the same. A check is made if the Normal is visible; if not, bytes are skipped. A common example, taken here from the Sidewinder, is whether to display sub-objects: 004B 8002 ; if !Visible(Normal(2)) goto L161
188E 4012 ; Subobject(196:ECM Antenna, Vertex(18), Orientation(40h))
L161: ...(more code)
Normal number 2 apparently points upwards, and if you can't see the top of the Sidewinder there is no point in drawing the ECM antenna there. 12 (0Ch) = Skip If Visible/Skip If Closer ThandddC DISTANCE_OR_NORMAL
Again, two commands in one. If dddC DISTANCE
(where, again (and hopefully again obvious), If the bit is set the command is dddC 80h | NORMAL
Pretty much the same as the previous command, just the other way around. I'll suffice with a few examples. Again one from the generic missile (see also #11 above). It actually starts with these commands: 008C 0334 ; if (DISTANCE < 820) goto L37 0002 1000 1800 ; Line(LOCALCOLOUR, 0,24) 0000 ; End Meaning, if you are up close to this missile, fill in the details (and hope it is your own missile); otherwise, draw just a tiny line. And a snippet of the Imp Explorer:
02AC 8018 ; if Visible(Normal(24)) goto L493
0004 4034 3632 3830 002A ; Square(Texture_034h, 50,56,54,48, Normal(42))
0003 4034 3034 3E36 ; Triangle(Texture_034h, 48,52,62, Normal(54))
0003 4034 3234 3838 ; Triangle(Texture_034h, 50,52,56, Normal(56))
0003 4034 3438 3E3A ; Triangle(Texture_034h, 52,56,62, Normal(58))
0003 4034 3036 3E3C ; Triangle(Texture_034h, 48,54,62, Normal(60))
L493: ...(more code)
... where, apparently, normal #24 points in the other direction -- the one you're not facing. 13 (0Dh) and 29 (1Dh) = MathDEST|(OPERAND|0Dh) SOURCE1|SOURCE2 Looks complicated, doesn't it?
The possible operands and their results are the following: 0: SOURCE1 + SOURCE2 1: SOURCE1 - SOURCE2 2: SOURCE1 * SOURCE2 3: SOURCE1 / SOURCE2 4: SOURCE1 >> SOURCE2 ; Logical shift 5: SOURCE1 << SOURCE2 6: MAX(SOURCE1, SOURCE2) ; Not entirely sure... 7: MIN(SOURCE1, SOURCE2) ; Ditto... 8: SOURCE1 * 10000h / SOURCE2 9: SOURCE1 SAR SOURCE2 ; Arithmetic shift 10: SOURCE1 unknown_op SOURCE2 ; ... 11: SOURCE1 if SOURCE1 is less than SOURCE2, 0 otherwise ; Not sure... 12: SOURCE1 if SOURCE1 is greater than SOURCE2, 0 otherwise ; Not sure... 13: SOURCE1 * SIN(SOURCE2) 14: SOURCE1 * COS(SOURCE2) 15: SOURCE1 AND SOURCE2 ; Bitwise AND I'm quite unsure about all of this! The input for the The Skeet Cruiser has a big humping thing on the top; it is moved using C35D 0A80 ; LOCAL[3] = TIME << 10 C3DD C35F ; LOCAL[3] = 31744 * SIN(LOCAL[3]) C31D 5FC3 ; LOCAL[3] = LOCAL[3] - 31744 where If your ship has a scanner fitted, it is rotated using C35D 0980 ; LOCAL[3] = TIME << 9 103C 00C3 ; Rotate -- default 00C3 18CE 060E ; Subobject(198:Scanner antenna, Vertex(14), Orientation(06h)) (but see the Rotate command for second thoughts on that) 14 (0Eh) = SubobjectobjE ORIENTATION|VERTEX ; Bit 7 of ORIENTATION clear
objE ORIENTATION|VERTEX VERTEX2|VERTEX1 VERTEX4|VERTEX3 ; Bit 7 of ORIENTATION set
This is a sub-object -- it's a regular object from elsewhere in the list, with the number in the high 11 bits in If bit 6 ( There is also the special case of orientation
If the high bit of C20D 8382 ; LOCAL[2] = GLOBAL[2] + GLOBAL[3] C1FD 1FC2 ; LOCAL[1] = LOCAL[2] & 31 132E 0B24 ; Subobject(153:Cargo, Vertex(36), Orientation(0Bh)) C24D 05C2 ; LOCAL[2] = LOCAL[2] >> 5 C1FD 1FC2 ; LOCAL[1] = LOCAL[2] & 31 132E 0B26 ; Subobject(153:Cargo, Vertex(38), Orientation(0Bh)) C24D 05C2 ; LOCAL[2] = LOCAL[2] >> 5 C1FD 1FC2 ; LOCAL[1] = LOCAL[2] & 31 132E 0B28 ; Subobject(153:Cargo, Vertex(40), Orientation(0Bh)) Only local variables 0, 1, and 2 are passed on to the sub-object; in addition, The Cargo object examines the variable
Missiles and other equipment bits and pieces are always drawn as sub-objects, where a global variable is tested to check if this equipment is actually in your possession. The missile itself is also an interesting example. I mentioned the "generic missile" a few times. All missiles refer to the same object; this is then drawn as a sub-object, where a local variable defines the colors of the missile, and for the rest they are all the same.
If bit 7 of An example is the Imperial Courier engine pod. It's connected to the ship with a square. In the Engine Pod one finds: 0004 0666 FDFC FE1A 0000 ; Square(RGB(6,6,6), 252,254,253,26, Normal(NORMAL_NONE)) This engine pod only defines 34 vertices, so the numbers 252 to 254 are out of range. They should be taken from the values passed on from the main definition of the Courier, where it looks like this: 1AEE 9028 2614 7F24
; Subobject(215:Imperial Courier engine pod, Vertex(40), Orientation(10h), 38,20,127,36)
Note that extra vertex number 4 has a value of 127, which is way out of range for the coordinates of the Courier, but since this particular coordinate isn't used in the engine pod it is apparently never calculated and thus not a problem. 15 (0Fh) = Never used!000F What can I say? Since this code is never used, I can't tell you anything about it. We'll have to wait for Elite 4 and hope it pops up there. 16 (10h) = Open Cone0010 ... The command codes are a re-interpretation of those in Frontier:Elite 2 (with a few small differences), and there this code defines a cone without top and bottom caps. The engines on the Lifter, for example, are huge grey cones, and if you rotate the Lifter you can see they are open. In FFE this command is unused; the next command is used whereever a cone is deemed necessary. 17 (11h) = Cone0011 MATERIAL VERTEX2|VERTEX1 NORMAL1|RADIUS1 NORMAL2|RADIUS2 MATERIAL1 MATERIAL2 The cone is drawn between From the Puma Clipper (one of the tiny engine cones at the back; there are actually three of'em): 0011 4008 100E 0106 0286 0000 20EE
; Cone(Texture_008h, Vertex(14), Vertex(16),
Radius(6), Normal(1), COLOR_BLACK,
Radius(134), Normal(2), UNLIT | COLOR_CYAN)
18 (12h) = DisplayTextVERTEX|12 TEXTID The The proverbial example should be the IMRA Command Ship, where apparently 0012 4041 ; DisplayText (0, "\n\n Vaccine Carrier") this appears. Being not such a wonderful Elite Commander, I wouldn't know because I never got that far. Thumbing through my mental notes: I once traced the full code to draw a sector map in FFE, and I seem to recall that object #171 (Dummy Star Sector, with 128 zero vertices and not a lot more) gets filled with actual 3D star coordinates and the names of the stars are pasted on using this command. There is another strange object (one of the rotating space stations) which uses this snippet: 0312 40C9 ; DisplayText (24, "12") 03D2 40CA ; DisplayText (30, "15") 0352 40CB ; DisplayText (26, "13") 0332 40CC ; DisplayText (25, "12x") 0372 40CD ; DisplayText (27, "13x") Anyone seen that in the game? 19 (13h) = Skip If Bit Clearddd|13 BIT|VARIABLE
If the skip distance The following snippet controls the alternating blinking landing lights in the Eagle LR fighter: 01F3 0680 ; if !(TIME & 32) goto L220
01B3 0580 ; if !(TIME & 16) goto L220
0006 ; Cmd 6 [nop]
0093 0480 ; if !(TIME & 8) goto L214
0001 20E0 E001 1080 ; Ball(UNLIT | COLOR_GREEN, Radius(960), Vertex(16), Normal(0 | LIGHT))
L214: 0094 0480 ; if (TIME & 8) goto L220
0001 2E00 E001 1180 ; Ball(UNLIT | COLOR_RED, Radius(960), Vertex(17), Normal(0 | LIGHT))
L220: ... (more code)
Where 20 (14h) = Skip If Bit Setddd|14 BIT|VARIABLE
Exactly the same as the previous code, only in reverse. If the skip distance 21 (15h) = Process Vertex?ddd|15
This command takes the vertex in the upper 11 bits 0615 ; Process vertex #48? 006E 0030 ; Subobject(3:Generic Missile, Vertex(48), Orientation(00h)) 0635 ; Process vertex #49? 006E 0031 ; Subobject(3:Generic Missile, Vertex(49), Orientation(00h)) ..where right after this command something is done using the same vertex number. The highest bit may be set, and in that case the number is definitely not a vertex number... Also in the Osprey: F015 ; Nop ...where, arguably, the comment "Nop" should have been "Nop????" 22 (16h) = Curve0016 MATERIAL POINT1|POINT2 POINT3|POINT4 0000 I used to call this command "Bezier" but got mail telling me off! Well, "curve" is such a generic description, hope I don't tread on someone's toes with that. Use this to draw a nice curvy curve between See, for example, the suspension bridge in New San Francisco (Sol(0,0)III: Earth).
0016 0444 010A 0B00 0000 ; Curve(COLOR_GRAY, 1,10,11,0) 0016 0444 111A 1B10 0000 ; Curve(COLOR_GRAY, 17,26,27,16) 0016 0444 0E0C 0C00 0000 ; Curve(COLOR_GRAY, 14,12,12,0) 0016 0444 0F0D 0D01 0000 ; Curve(COLOR_GRAY, 15,13,13,1) 0016 0444 1E1C 1C10 0000 ; Curve(COLOR_GRAY, 30,28,28,16) 0016 0444 1F1D 1D11 0000 ; Curve(COLOR_GRAY, 31,29,29,17) The 23 (17h) = Not Used0017 Not used. If you encounter this as a command code there is something wrong with your program. Sorry. 24 (18h) = Ball Array0018 MATERIAL RADIUS [VERTEX|VERTEX]+ Get you ball arrays here! It seems balls are so popular, everybody wants one. Preferably more than one! This command reads the bytes after The But first, a simple example. This one is a complete 3D object! It's object #374, and depicts a group of 14 trees (that's what I think it is meant to be) inside the transparent biodomes.
0018 2080 C409 0001 0405 0809 0C0D 0607 0A02 030E 7F7F
; BallArray(UNLIT | RGB(0,8,0), Radius(5000, ..)
0000 ; End
The To show growing and shrinking balls a variable radius is needed, this rather large example from the Hyperspace warp (object #154) shows how: C25D 0D80 ; LOCAL[2] = TIME << 13 C24D 05C2 ; LOCAL[2] = LOCAL[2] >> 5 C14D 0284 ; LOCAL[1] = GLOBAL[4] >> 2 C10D C2C1 ; LOCAL[1] = LOCAL[1] + LOCAL[2] 0694 1081 ; if (GLOBAL[1] != 0) goto L64 0018 246E 00C1 007F ; BallArray(UNLIT | RGB(4,6,14), Radius(LOCAL[1], ..) 05CB 0926 ; if (DISTANCE > 2342) goto L64 C14D 01C1 ; LOCAL[1] = LOCAL[1] >> 1 C24D 02C1 ; LOCAL[2] = LOCAL[1] >> 2 C10D C2C1 ; LOCAL[1] = LOCAL[1] + LOCAL[2] 0018 268E 00C1 007F ; BallArray(UNLIT | RGB(6,8,14), Radius(LOCAL[1], ..) 044B 0752 ; if (DISTANCE > 1874) goto L64 C14D 01C1 ; LOCAL[1] = LOCAL[1] >> 1 C24D 02C1 ; LOCAL[2] = LOCAL[1] >> 2 C10D C2C1 ; LOCAL[1] = LOCAL[1] + LOCAL[2] 0018 28AE 00C1 007F ; BallArray(UNLIT | RGB(8,10,14), Radius(LOCAL[1], ..) 02CB 057E ; if (DISTANCE > 1406) goto L64 C14D 01C1 ; LOCAL[1] = LOCAL[1] >> 1 C24D 02C1 ; LOCAL[2] = LOCAL[1] >> 2 C10D C2C1 ; LOCAL[1] = LOCAL[1] + LOCAL[2] 0018 2ACE 00C1 007F ; BallArray(UNLIT | RGB(10,12,14), Radius(LOCAL[1], ..) 014B 0464 ; if (DISTANCE > 1124) goto L64 C14D 01C1 ; LOCAL[1] = LOCAL[1] >> 1 C24D 02C1 ; LOCAL[2] = LOCAL[1] >> 2 C10D C2C1 ; LOCAL[1] = LOCAL[1] + LOCAL[2] 0018 2CEE 00C1 007F ; BallArray(UNLIT | RGB(12,14,14), Radius(LOCAL[1], ..) Assuming my interpretations of the math commands are OK, you can see how (sort of) This same model also draws red-to-yellow globs in quite the same way; one of the first commands in the code selects which part to use and whether to add sparkly blue lines or not. 25 (19h) = View matrix?ddd19
This is another unknown. The 11 top bits 26 (1Ah) = Set Color001A MATERIAL 00|NORMAL 001A MATERIAL1 VARIABLE|00 MATERIAL2 MATERIAL3 MATERIAL4
MATERIAL5 MATERIAL6 MATERIAL7 MATERIAL8
The There are two variants: the short one just sets the material to use, with an associated A simple example from the Cobra Mk III: 001A 0444 000C ; SetColor(Normal(12), COLOR_GRAY)
177B 0654 0000 3000
; ScaledSubobject (187:Nose wheel, Vertex(84),Orientation(06h),Scale(0x0000),LOCALCOLOUR | UNLIT)
177B 0652 1000 3000
; ScaledSubobject (187:Nose wheel, Vertex(82),Orientation(06h),Scale(0x1000),LOCALCOLOUR | UNLIT)
177B 0653 1000 3000
; ScaledSubobject (187:Nose wheel, Vertex(83),Orientation(06h),Scale(0x1000),LOCALCOLOUR | UNLIT)
(The command A rather funny example is the pilot head in the Eagle. Here you can see how global variable [2] is used to determine its color.
001A 2E00 8200 20E0 2EE0 200E 2EEE 2E80 2E0E 20EE ; SetColor(GLOBAL[2],
UNLIT | COLOR_RED,
UNLIT | COLOR_GREEN,
UNLIT | COLOR_YELLOW,
UNLIT | COLOR_BLUE,
UNLIT | RGB(14,14,14),
UNLIT | RGB(14,8,0),
UNLIT | RGB(14,0,14),
UNLIT | COLOR_CYAN)
0001 3000 A014 0600 ; Ball(LOCALCOLOUR | UNLIT, Radius(10560), Vertex(6), Normal(NORMAL_NONE))
I think
In the image you can see two vertices 14 and 15. Between those the text ".IXI." is drawn -- it mystified me for a while, then I realized it is meant to look like a steering wheel! 27 (1Bh) = Scaled Sub-objectobj1B ORIENTATION|VERTEX SCALE MATERIAL ; Bit 7 of ORIENTATION clear
obj1B ORIENTATION|VERTEX SCALE MATERIAL VERTEX2|VERTEX1 VERTEX4|VERTEX3 ; Bit 7 of ORIENTATION set
Pretty much like the normal sub-object but with two added twists. This one defines an additional Another addition is the As in the normal subobject, the 167B 804A 4000 5009 7F7F 7F4C
; ScaledSubobject (179:object_179, Vertex(74),Orientation(00h),Scale(0x4000),
LOCALCOLOUR | Texture_Metal_Black_0, 127,127,127,76)
...
167B 944B 4000 5009 7F7F 7F4D
; ScaledSubobject (179:object_179, Vertex(75),Orientation(14h),Scale(0x4000),
LOCALCOLOUR | Texture_Metal_Black_0, 127,127,127,77)
There are a few weird vertex values Another example: the laser turret on the Griffin Carrier: 1ABB 1048 F000 4026
; ScaledSubobject (213:Turret, Vertex(72),Orientation(10h),Scale(0xF000),Texture_026h)
28 (1Ch) = Rotateddd1C VALUE
This is another command on which I am stymied. It seems I am 100% sure of the function, though. See this piece of the Interplanetary Shuttle. 191C 0000 ; Rotate -- default 0000 833C FD44 ; Rotate -- default FD44 000A 0000 0606 4610 3016 ; Text(COLOR_BLACK, Normal(6), Vertex(16), Scale(6), VECTOR_FONT, 3016h) 191C 0040 ; Rotate -- default 0040 833C FD44 ; Rotate -- default FD44 000A 0000 0607 4612 3016 ; Text(COLOR_BLACK, Normal(7), Vertex(18), Scale(6), VECTOR_FONT, 3016h) Recall that text code FFE uses matrices to perform its rotations and I really can't get my head around those! That might explain a bit why this command still doesn't make any sense to me. The "might-be-variable" idea comes from observing scanners. They are usually plugged in as a sub-object, and right before they are, something like this appears (in the Cobra Mk III): C35D 0980 ; LOCAL[3] = TIME << 9 183C 00C3 ; Rotate -- default 00C3 004B 800E ; if !Visible(Normal(14)) goto L228 18CE 0646 ; Subobject(198:Scanner antenna, Vertex(70), Orientation(06h)) Local variable 3 is coded as Part of my confusion may arise from the orientation problems I've encountered, because this orientation/rotation (or mirroring) obviously needs to be concatenated with any previous number of 29 (1Dh) = Math part 2DEST|(OPERAND|1Dh) SOURCE1|SOURCE2 This is the second part of the 30 (1Eh) = Unknown!ddd1E
Yet another unknown! The numeric parameter in the command itself is usually seen as Example: the green star map grid (object #166) C01E ; Cmd 1Eh (parm. 0C01h) The original code is a real mess there; for the Star Map Grid it appears to draw the stars (!), in other cases it does something with the aforementioned alternate rotation matrix. 31 (1Fh) = Planet!?001F MATERIAL VERTEX2|VERTEX1 VERTEX4|VERTEX3 EXTRA1 EXTRA2 EXTRA3
[ADDITIONAL]* 0000
Rather a simple code but with very graphic consequences. The
There may be zero additional subsets, in which case this code is just 6 words long. All stars and a few planets consist of just this one command. A small example is Object #144 (Type'B'hot blue star): 001F 0DEF 0206 0004 0009 1999 0001 ; Cmd 1Fh(RGB(13,14,15), 6,2,4,0, 0x0009,0x1999,0x0001,
0000 ; 0x0000)
0000 ; End
The A somewhat larger example (the "small barren sphere of rock"): 001F 0333 0206 0004 0002 03D7 05F4 ; Cmd 1Fh(RGB(3,3,3), 6,2,4,0, 0x0002,0x03D7,0x05F4, 0807 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 7,8,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 087D 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 125,8,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 08E3 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 227,8,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 092F 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 47,9,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 09AA 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 170,9,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 09BA 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 186,9,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 09CC 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 204,9,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0A48 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 72,10,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0A4F 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 79,10,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0A79 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 121,10,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0ABD 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 189,10,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0B56 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 86,11,12 , 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0C40 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 64,12,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0C7C 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 124,12,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0CAE 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 174,12,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0CD9 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 217,12,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0000 ; 0x0000) The translations in the comment to bytes and words is pure guesswork! Just to show how difficult it is to make something out of this command, here is the code for the "World with indigenous life and oxygen atmosphere": 001F 0667 0206 0004 0000 0B85 0478 ; Cmd 1Fh(RGB(6,6,7), 6,2,4,0, 0x0000,0x0B85,0x0478, 0807 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 7,8,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 087D 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 125,8,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 08E3 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 227,8,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 092F 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 47,9,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 09AA 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 170,9,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 09BA 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 186,9,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 09CC 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 204,9,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0A48 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 72,10,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0A4F 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 79,10,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0A79 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 121,10,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0ABD 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 189,10,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0B56 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 86,11,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0C40 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 64,12,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0C7C 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 124,12,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0CAE 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 174,12,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0CD9 000C 0040 00F4 FFB3 FFFF 00CE FFFF ; 217,12,12, 0x0040,0x00F4,0xFFB3,0xFFFF,0x00CE,0xFFFF, 0000 ; 0x0000) It looks startingly the same...
Next time I'll explain the Now I'm gonna have a nice long lie-down with a wet towel on my head...
Based on original data and algorithms from Frontier:Elite 2 and Frontier:First Encounters by David Braben (Frontier Developments) Original copyright holders: | |||||||||||||||||||||||||||||
![]() |
After penning this story down I think I should have titled it "Frontier Objects Explaining". If you're as confused as I am let met know at jongware. |