Table of Contents
The scene graph drawn by the BigWorld client is built from small convex chunks of space. This has many benefits including easy streaming, reduced loading times, concurrent world editing, and facilitation of server scene updates and physics checking.
The concepts and implementation of the chunking system are described in the following sections.
The following terms are related to the BigWorld chunking system:
A space is a continuous three-dimensional Cartesian medium. Each space is divided piecewise into chunks, which occupy the entire space but do not overlap. Every point in the space is in exactly one chunk. A space is split into columns of 100x100 metres in the horizontal dimensions, and total vertical range. Examples of separate spaces include planets, parallel spaces, space stations, and 'detached' apartment/dungeon levels.
A chunk is a convex three-dimensional volume. It contains a description of the scene objects that reside inside it. Scene objects include models, lights, entities, terrain blocks, etc, known as chunk items. It also defines the set of planes that form its boundary.
Note
The outside chunk of a column is exempt from this — it needs only define the four planes to adjacent column. The boundary planes of other chunks overlapping that grid square are used to build a complete picture of the division of space inside it.
Some planes have portals defined on them, indicating that a neighbouring chunk is visible through them.
A portal is a polygon plus a reference to the chunk that is visible through that polygon. It includes a flag to indicate whether it permits objects to pass through it. Some portals may be named so that scripts can address them, and change their permissivity.
The following files are used by the chunking system:
-
One space.settings file for each space in the universe (XML format)
For details on this file's grammar, see the document File Grammar Guide's section space.settings.
-
<res>/spaces/<space>/space.settings
-
Environment settings
-
Bounding rectangle of grid squares
-
-
-
Multiple .chunk files for each space (XML format)
For details on this file's grammar, see the document File Grammar Guide's section .chunk.
-
<res>/spaces/<space>/XXXXZZZZo.chunk (o = outside)
-
<res>/spaces/<space>/CCCCCCCCi.chunk (i = inside)
-
List of scene objects
-
Texture sets used
-
Boundary planes and portals (including references to visible chunks)
-
Collision scene
-
-
-
Multiple .cdata files for each space (binary format)
-
<res>/spaces/<space>/XXXXZZZZ.cdata
-
Terrain data such as:
-
Height map data
-
Overlay data
-
Textures used
-
— or —
-
Multiple instances of lighting data for each object in the chunk:
-
Static lighting data
-
A colour value for each vertex in the model
-
-
-
Includes are transparent after being loaded (to client, server,
and scripts). Label clashes are handled by appending
'_n
' to labels, where
N
is the number of objects
with that label already.
Includes are expanded inline where they are encountered, and do not need to have a bounding box for the purposes of the client or server.
The World Editor does not generate includes.
Material overrides and animation declarations remain the domain of model files. For more details, see Models.
Only entities that are implicitly instantiated need to have their ID field filled in. If it is zero or is missing, then the entity is assigned a unique ID from either the client's pool (if it is a client-instantiated entity) or the creating cell's pool (if it is a server-instantiated entity).
If an entity needs a label, it must include the label as a property in its formal type definition.
The special chunk identifier heaven may be used if only the sky (gradient, clouds, sun, moon, stars, etc...) is to be drawn there. Similarly with earth, if the terrain ought to be drawn. Therefore, outside chunks will have six sides, with the heaven chunk on the top and the earth chunk on the bottom.
The absence of a chunk reference in a portal means it is unconnected and that nothing will be drawn there.
If a chunk is included inside another, then its boundary planes are ignored — only things like its includes, models, lights, and sounds are used.
An internal portal means that the specified boundary is not an actual boundary, but instead that the space occupied by the chunk it connects to (and all chunks that that chunk connects to) should be logically subtracted from the space owned by this chunk, as defined by its non-internal boundaries. This was originally intended only for 'outside' chunks to connect to 'inside' chunks, but it may be readily adapted for 'interior portals', the complement to 'boundary portals'.
In a portal definition, the vAxis for the 2D polygon points is found by the cross product of the normal with uAxis.
In boundary definitions, the normals should point inward.
Everything in the chunk except the bounding box is interpreted in the local space of the chunk (as specified in the top-level transform section).
At every frame, the Chunk Manager performs a simple graph traversal of all the chunks it has loaded, looking for new chunks to load. It follows the portals between chunks, keeping track of how far it has 'travelled' in its scan. Its scan is limited to the maximum visible distance, i.e., a little further than the far plane distance.
The closest unloaded chunk it finds on this traversal is the chunk that is loaded next. Loading is done in a separate thread, so it does not interfere with the running of the game. Similarly, any chunks that are beyond the reach of the scan are candidates for ejecting (unloading).
The focus grid is a set of columns surrounding the camera position. Each column is 100x100metres and is aligned to the terrain squares and outside chunks. The focus grid is sized to just exceed the far plane.
For a far plane of 500m, for example, the focus grid goes 700m in each direction, making for 14 x 14 = 196 columns total.
The set of columns in the focus grid is dependent on the camera position. As the camera moves, the focus grid disposes columns that are no longer under the grid and 'focuses' on ones that have just come close enough.
Each column contains a hull tree and a quad tree.
A hull tree is a kind of binary-space partitioning tree for convex hulls. It can handle hulls that overlap. It can do point tests and proper line traversals.
The hull tree is formed from the boundaries of all the chunks that overlap the column. From this tree, the chunk that any given point lies in can be quickly determined. (e.g., the location of the camera)
The quad tree is made up of the bounding boxes (or, potentially, any other bounding convex hull) of all the obstacles that overlap the column. This tree is used for collision scene tests. Chunk items are responsible for adding and implementing obstacles. Currently only model and terrain chunk items add any obstacles.
If a chunk or obstacle is in more than one column, it is added to the trees of both columns.
Using its focus grid of obstacle quad trees, the chunk space class can sweep any 3D shape through its space, and report all the triangle collisions to a callback object. The currently supported 3D shapes are points and triangles, but any other could be added with very little difficulty.
The bottommost level of collision checking is handled by a generic obstacle interface, so any conceivable obstacle could be added to this collision scene, as long as it can quickly determine when another shape collides with it (in its own local coordinates).
For more details, see file bigworld/src/client/physics.cpp.
A sway item is a chunk item that is swayed by the passage of other chunk items. Whenever a dynamic chunk item moves, any sway items in that chunk get the sway method called on them, specifying source and destiny of movement.
Currently the only user of this is ChunkWater. It uses movements that pass through its surface to make ripples in the water. This is why the ripples work for any kind of dynamic item — from dynamic obstacles/moving platforms to player models to small bullets.
-
In mf/src/lib/chunk/chunk_water.cpp:
/** * Constructor */ ChunkWater::ChunkWater() : ChunkItem( 5 ),
pWater_( NULL ) { } ... /** * Apply a disturbance to this body of water */ void ChunkWater::sway( const Vector3 & src, const Vector3 & dst ) { if (pWater_ != NULL) { pWater_->addMovement( src, dst ); } }
-
In mf/src/lib/chunk/chunk_item.hpp:
... class ChunkItem : public SpecialChunkItem { public: ChunkItem( int wantFlags = 0 ) : SpecialChunkItem( wantFlags ) { } }; ... typedef ClientChunkItem SpecialChunkItem; ... class ClientChunkItem : public ChunkItemBase { public: ClientChunkItem( int wantFlags = 0 ) : ChunkItemBase( wantFlags ) { } }; ... ChunkItemBase( int wantFlags = 0 ); bool wantsDraw() const { return !!(wantFlags_ & 1); } bool wantsTick() const { return !!(wantFlags_ & 2); } bool wantsSway() const { return !!(wantFlags_ & 4); } bool wantsNest() const { return !!(wantFlags_ & 8); }