Go to QuArK Web Site
Structure of the program
Updated 24 Dec 2021
Upper levels:
QuArK Information Base
4. The Source Code

 4.3. Structure of the program

 [ Prev - Up - Next ] 

 Index


 QObjects

Armin Rigo - 05 Apr 2018   [ Top ] 

The most central part of QuArK is its internal objects on which everything else is based. These objects are represented by all the classes that directly or indirectly inherit from QObject, defined in qobjects.pas. Here are the main rules about QObjects.

Although the class QObject has got a few fields, these fields should be regarded as used by QuArK's internal management only. All important data must be stored using a combination of the two main features of QObjects : their Specific/Args and their subobjects (called subelements in the code).

Specific/Args are similar to the ones of Quake entities : it is a list of pairs of strings. The first string, the Specific, is an arbitrary name for a setting; the corresponding second string gives the value of this setting.

Objects also each have a list of subobjects. This lets objects be organized in a hierarchy.

The basic principles are that the Specific/Args and the subobjects contain all the information, and that this object hierarchy is central for all operations :

  • to load a file, we actually convert it into an object or a hierarchy of objects;
  • to save a file, we convert the objects back to the destination file format;
  • we can only display and work with objects, never directly with files.

 QFileObjects

Armin Rigo - 05 Apr 2018   [ Top ] 

QFileObject is an abstract class that inherits from QObject. The general rule is that classes that inherit from QFileObject can be saved to or loaded from disk files, whereas classes that inherit from QObject only are internal objects whose purpose is only to be used as subobjects of other objects.

For example, here is how a map file is internally organized. The ".map" file type itself is managed by a class called QMapFile that inherits from QFileObject, whereas inside the map, the hierarchy of polyhedrons, groups, entities, etc. is represented by a hierarchy of objects that inherit from QObject, because there is no point in saving these objects as single files. Graphically :

Map object
 +-- worldspawn
      +-- polyhedron
      +-- polyhedron 2
      +-- group
           +-- entity
           +-- etc

The links in this diagram mean "is a sub-object".


 Textures

DanielPharos - 27 Apr 2021   [ Top ] 

Texture objects in QuArK are structed as follows:

  1. QFileObject
    1. QTexture
      1. QTextureFile
        1. QTexture1
        2. QTexture2
        3. QTextureSin
        4. QTextureKP
    2. QImage
      1. QBmp
      2. QPcx
      3. QTga

 Texture format

DanielPharos - 24 Dec 2021   [ Top ] 

Image data, or textures, are stored in specifics of objects, or Pixel Set Descriptors (PSD) QuArK's internal image format.

There are three specifics:

Image# (Where # is a number)
Pal
Alpha

Usually, there's only one image per texture, so there is only an Image1. If the texture doesn't contain any alpha-color data, Alpha doesn't exist. If the picture isn't a paletted image, like a .pcx file, Pal doesn't exist.

There are two different formats that QuArK uses internally, and every loaded image is converted to those formats on load:

  • 24-bit non-paletted, and
  • 8-bit paletted with a 24-bit palette.

The Image-specific is stored in a way Windows can understand directly. The details of it might seem a bit weird, but this is the way Windows works (this gave Windows extra speed back in the old 486-days). All the data in the Image-specific must be aligned on a 32-bit boundary per scanline. What does this mean?

A scanline is simply a row of pixels as displayed on the screen. These are picture-width pixels in a single row. However, the amount of bytes used per row must be a multiple of 4 (32 bits = 4 bytes of 8 bits each). So there is a padding amount of empty, unused, erroneous bytes at the end of each row. You can set them to any value, since they won't be displayed, but making them zero's is probably nicest. How to calculate the amount of padding bytes, you ask?
The amount of bits used in a single row of pixels is: picture-width x bits per pixel.

The reason for the 8 bits each is because RGB images have a Decimal numbering for each Red, Green, Blue channel, or color component, that ranges from 0 to 255, or 256 digits, where pure white would be 255, 255, 255. Alpha would also be an additional component which we are skipping over for right now.

But the point is, you can see how cumbersome that would be to store, as well as take more bits in memory. So to overcome this they use what is known as the Hex or Hexadecimal numbering system instead. This system has a total of 16 digits, 0 through 9 plus A, B, C, D, E AND F. Each pixel is an RGBA, Red, Green, Blue and Alpha (which may not be used) component. Each of those components gets 2 bits for their Hex number looking something like this, FFFFFF, which again is pure white, with Alpha being overlooked for now. If an image does have an Alpha component then its Hex number would look like this FFFFFFFF,
2 bits per component x 4 RGBA components = 8 bits each per pixel.
Pretty slick how that all works, isn't it? A good example is to look at this Color Converter.

Bits per pixel is usually a number like 1, 2, 4, 8, 16, 24 or 32. This now gives the amount of bits per line. We need to round this up to the closest multiple of 32 bits. The simplest way is by adding an additional 31, then dividing by 32 and rounding down the result. This will give the amount of bytes per scanline.

To find the amount of padding bytes, simply subtract the amount of bytes used in a single row of pixels:
padding = picture-width x bits per pixel - amount-of-bits-per-scanline

But be careful: the scanlines are stored upside-down. This means that the first bytes you read in, are actually the first pixels of the LAST line to display!
(The picture is stored vertically mirrored, or flipped!) Just do a simple picture-height - line-I-want to get the correct number of the scanline. Since you know the amount of bytes per scanline (nicely aligned, so no half-byte locations here!), you can find the start of the scanline by a simple multiplication.

Since QuArK uses only 8 bits or 24 bits internally, there are only two possibilities. Check for the existence of the Pal-specific: if it's there, you're looking at an 8-bit texture, if not, then it must be a 24-bit texture.

If Pal does exists, then this is a list of 256, 24 bit colors, stored in a 32-bit format: RGB (Red, Green, Blue, no Alpha here). So each color is 4 bytes in size. Color 0 is the first color in the list, etc.

Alpha contains any Alpha data there might be. This is just a list of alpha-color values, no padding involved.


 Loading files

Armin Rigo - 24 Dec 2021   [ Top ] 

Here are comments that will help you in writing support for a new file type.

QuArK has got a complex file management structure, but you need not be aware of it as long as you use the "official" way of loading and saving files. Doing so will let QuArK handle things such as loading or saving directly inside a .pak file, and load-on-demand.

To support a new file type, a class inherits from QFileObject and overrides some key methods. The first one is TypeInfo, which should return the file extension. QuArK recognize files based on their extension only, and then invokes the correct class'  LoadFile  method. This method must process the file it receive in a parameter and turn *all* information it contains into Specific/Args and subobjects.

For example, the method  LoadFile  of QMapFile parses the map file and creates the whole hierarchy of groups, polyhedrons and entities.

Some file types are suitable for load-on-demand, which is implemented through subobjects whose class also has a  LoadFile  method. For example, when loading a .PAK file, it is of course not completely loaded into memory : the directory of the file is processed, and folders and subfolders are created accordingly. The files inside the folders are also created but this time through the method  LoadedItem , which may be called from a  LoadFile  method only. It creates a subobject which is marked as "not loaded yet"; this subobject's own  LoadFile  method will be called when necessary to actually load the (sub-)file.

In summary, the method  LoadFile  must :

  • load every information contained in the file and store it all as Specific/Args and subobjects, with one exception : calls to  LoadedItem , which does not immediately load the data;
  • don't do anything else (no direct disk access, no display, etc.), with one exception : calls to LoadSibling, to access files besides the one currently loading (typically, Quake 2's models and sprites refer to .pcx files for the model skin and sprite images).

 Saving files

Armin Rigo - 05 Apr 2018   [ Top ] 

Saving files is the exact inverse of loading them. The method  SaveFile  must turn the Specific/Args and the subobjects structure back into a file.

If the  LoadFile  method used LoadSibling,  SaveFile  calls Info.WriteSibling (see QkMdl.pas).


 Displaying files

Armin Rigo - 05 Apr 2018   [ Top ] 

Files are never displayed directly in QuArK : only internal objects can be. This means that the display code must only rely on the Specific/Args and eventually on the subobjects hierarchy to display the content of a file.

The general rule about display is that subobjects are visible in a tree hierarchy and the user can edit them freely (add subobjects, rename or delete some, move them around, and so on).

For simple objects (typically, the ones with no subobject), the display form gives users access to all parameters of the object. For example, an image object is displayed in a form whose bottom gives parameters such as the size and the color depth of the image.

Semi-complex objects, paradoxally, have simpler display forms : their information is stored as subobjects and the user can edit them without any particular form. For example, a sprite object is basically just a list of images, so that it is represented as an object with images as subobjects. Editing them for the user is quite easy : all he has to do is rename, move, add, delete images. A particular sprite form is needed only for the stuff that cannot be edited this way, e.g. how long each image is to stay in the sprite animation sequence.

Really-complex objects (e.g. maps or models) require more sophisticated display code, which only gives a preview of the object when users click on it in the QuArK Explorer and invokes a complex editor when needed.

Note that for the subobjects of an object to be displayed in tree views, you need to override the  IsExplorerItem  method. This method controls whether each subobject is displayed or not, or whether it is allowed to be dropped there by the user or not. In general it should be implemented as follows :

function xxx.IsExplorerItem(Q: QObject) : TIsExplorerItem;
begin
 Result:=ieResult[(Q is someclass) or (Q is someotherclass) or...];
end;


Copyright (c) 2022, GNU General Public License by The QuArK (Quake Army Knife) Community - https://quark.sourceforge.io/

 [ Prev - Top - Next ]