Valve Developer CommunityにあるSource BSP File Formatのページを日本語訳したもの(Google 翻訳 + 分かりやすく)
面倒くさがった結果機械翻訳もびっくりな謎の訳が出来上がっているかもしれませんがご容赦ください……
また、読んでてよく分からなかった部分には(?)を置いてます。
この記事は2005年10月のRofによる「The Source Engine BSP File Format」に基づいており、サービスが停止する前にGeocitiesから取得されています。元のバージョンへのミラーがここにあります。
- 前書き
- 概要
- BSPファイルのヘッダ
- Lump
このドキュメントでは、Source Engineで使用されるBSPファイルの構造について説明します。このファイル形式は、Half-Life 1のエンジン(GoldSrc)のものと似ていますが、同じではありません。GoldSrcのBSPファイルはQuake、Quake II、QuakeWorldおよびQuake III Arenaのフォーマットに基づいています。このため、Max McGuireの記事 Quake 2 BSP File Formatは全体的な構造と、類似している部分の構造を理解する上で非常に役に立ちます。
このドキュメントは、RofがVMEX(Half-Life 2 BSPファイルの逆コンパイラ)の作成中に彼が書いたノートを拡張したものです。したがって、マップの逆コンパイル(BSPファイルをHammer Map Editorで読み込み可能なVMFファイルに変換すること)を実行するために必要な部分に焦点を当てています。
ここにあるほとんどの情報は、上記のMax McGuireの記事とSource SDKのソースコード(特に、C言語のヘッダーファイルであるpublic/bspfile.h)、そしてRof自身がVMEXの作成中に行った実験から得られたものです。
このドキュメントは読者がC/C++、ジオメトリ、およびSourceのマッピング用語についてある程度の理解があることを想定しています。コード(主にC言語の構造体)は等幅フォント
で書かれています。また、ここにある構造体は明瞭性と一貫性のため、SDKヘッダファイルにある実際の定義から変更されていることがあります。
BSPファイルには、マップをレンダリングして遊ぶためにSource Engineで必要とされる大多数の情報が含まれています。これには以下のような情報が含まれます。
- レベル内のすべてのポリゴンのジオメトリ
- 各ポリゴンが描画される時に用いるテクスチャの名前と方向
- ゲーム中にプレイヤーや他のアイテムの物理的挙動をシミュレートするために使われるデータ
- マップ内のすべてのブラシベース、モデル(プロップ)ベース、不可視(論理)エンティティの位置とプロパティ
- BSP木と可視性テーブル(マップジオメトリ内のプレイヤーの位置の特定と、マップの見える範囲を可能な限り効率的にレンダリングするのに使用)
また、マップファイルにはレベルで使用されているカスタムテクスチャやモデルをマップのPakfile lumpの中に任意で埋め込むこともできます(下記参照)。
BSPファイルに格納されていない情報として、マルチプレイヤーゲーム(Counter-Strike: SourceやHalf-Life 2: Deathmatchなど)でマップを読み込んだ後に表示されるマップの説明テキスト(mapname.txtに格納されています)と、ノンプレイヤーキャラクター(NPC、マップをナビゲートする必要がある)が使用するAIナビゲーションファイル(mapname.navに格納されています)があります。Souce Engineのファイルシステムの仕組み上、これらの外部ファイルはBSPファイルのPakfile lumpに埋め込まれることもありますが、通常はそうではありません。
公式マップのファイルは、Steam Game Cache File(GCF)形式で保存され、ゲームエンジンによりSteamファイルシステムを通じてアクセスされます。GCFファイルから中身を抽出してSteam外から閲覧するには、NemesisのGCFScapeを使用します。VPKファイル形式を使用している新しいゲームでは通常、マップはオペレーティングシステムのファイルシステムに直接保存されます。
BSPファイルのデータは、PC/Macではリトルエンディアンで、PS3/Xbox360ではビッグエンディアンで保存されます。リトルエンディアンのファイルをJavaなどのビッグエンディアン形式のプラットフォームで読み込む場合は、バイトスワップが必要です(逆も同様です)。
BSPファイルはヘッダから始まります。この構造は、ファイルがValve Source Engine BSPファイルであることと、フォーマットのバージョンを示して、その後にファイル内の各データ(lumpと呼ばれる)の場所、長さ、およびバージョンが最大64個分続きます。最後に、マップの修正回数が書かれています。
ヘッダの構造はSDKの public/bspfile.h というヘッダファイルに記載されています。このファイルは、このドキュメント全体を通して広範囲にわたって参照されています。また、ヘッダは合計で1036バイトです。
Alien Swarmでは、この構造体はBSPHeader_tに名前が変更されています。
struct dheader_t
{
int ident; // BSPファイルの識別子
int version; // BSPファイルのバージョン
lump_t lumps[HEADER_LUMPS]; // lumpディレクトリの配列
int mapRevision; // マップの修正回数
};
ident
は、次のように定義された4バイトのマジックナンバーです。
// リトルエンディアン "VBSP" 0x50534256
#define IDBSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'V')
したがって、ファイルの最初の4バイトは常にVBSP
(ASCII形式)です。これらのバイトはファイルをValve BSPファイルとして識別します。他のBSPファイルの形式では、異なるマジックナンバーを使用します(例えば、id SoftwareのQuake Engineを用いたゲームはIBSP
で始まります)。GoldSrcのBSP形式では、マジックナンバーはまったく使用されません。また、マジックナンバーの順序はファイルのエンディアンを決定するためにも使用できます。VBSP
はリトルエンディアンに、PSBV
はビッグエンディアンに使用されます。
2番目のint値は、BSPファイル形式のバージョン(BSPVERSION)です。 Source ゲームの場合、この値はVampire: The Masquerade – Bloodlinesを除いて19から21の範囲です(下記の表を参照)。他のエンジン(Half-Life 1、Quakeシリーズなど)のBSPファイル形式は、全く異なるバージョン番号の範囲を使用することに注意してください。
この表は、Source Engineを用いたいくつかのゲームで使用されているBSPバージョンの概要を示しています。
バージョン | ゲーム | 備考 |
---|---|---|
17 | Vampire: The Masquerade – Bloodlines | dface_tに変更あり |
17~18 |
|
ベータ版のリーク |
19 | Sin Episodes | |
19~20 |
|
リリース時は19で、Source 2007/2009アップデートからは 部分的に20 |
|
||
|
||
|
||
20 |
|
|
|
||
|
||
|
||
|
最近のマップはStaticPropLump_tに変更あり(version = 10) ゲームlump、エンティティ情報、PAKファイルはLZMA圧縮 |
|
|
||
|
dworldlight_tとStaticPropLump_tに変更あり(version = 8) | |
Zeno Clash | StaticPropLump_tに変更あり(version = 7) | |
|
dshader_t、StaticPropLump_t、texinfo_t、dgamelump_tに 変更あり |
|
Vindictus | 多くの変更された構造体を使用 | |
|
StaticPropLump_tに変更あり | |
Bloody Good Time | StaticPropLump_tに変更あり | |
Black Mesa: Source | StaticPropLump_tに変更あり(version = 11) | |
21 |
|
lump_tと古いdbrushside_t(?)に変更あり |
|
||
|
StaticPropLump_tに変更あり(version = 10) | |
|
StaticPropLump_tに変更あり | |
Insurgency | ||
The Stanley Parable | ||
Tactical Intervention | 256ビット XOR暗号化 | |
22 |
|
早期ベータ版 dbrushside_tとddispinfo_tに変更あり |
23 | dbrushside_t、ddispinfo_t、doverlay_tに変更あり | |
27 | Contagion | |
29 | Titanfall | 大幅に変更されている |
ゲーム固有のBSP形式の詳細については、Source BSP File Format/Game-Specificを参照してください。
次に、16バイトのlump_t
構造体の配列が続きます。HEADER_LUMPSは64と定義されているため、全部で64個の要素があります。ただし、ゲームやバージョンによっては定義されていないものや空のものもあります。
各lump_t
はbspfile.hで定義されています。
struct lump_t
{
int fileofs; // ファイルへのオフセット(バイト単位)
int filelen; // Lumpの長さ(バイト単位)
int version; // Lumpの形式のバージョン
char fourCC[4]; // Lump識別子
};
最初の2つのint値はbspファイルの先頭からのバイトオフセットとそのLumpに含まれるデータブロックのバイト長を表します。続いて、そのLumpの形式のバージョン番号(通常は0)と、通常0, 0, 0, 0である4バイトの識別子があります。圧縮されたLumpの場合、fourCCには非圧縮のLumpデータサイズが整数で示されています(詳細はLumpの圧縮の節を参照)。lump_t配列の未使用のメンバについてはすべての要素が0に設定されています。 Lumpのオフセット(と、それに対応するデータ)は最も近い4バイトの境界に切り上げられています。ただし、Lumpの長さについてはこの限りではありません。
lump_t
配列が指すデータの種類は、配列内の位置によって定義されます。例えば、配列の最初のLump (Lump 0) は常にBSPファイルのエンティティデータです(下表参照)。BSPファイル内の実際のデータの位置は、そのLumpのoffsetとlengthによって定義されるため、ファイル内で特定の順番に並んでいる必要はありません。例えば、エンティティデータはLump配列の最初にあるにも関わらず通常はBSPファイルの最後に格納されます。したがって、lump_tヘッダの配列はLumpデータに関するディレクトリのようなものであり、Lumpデータはファイル内を自由に配置することができます。
配列内におけるLumpの順番は以下のように定義されます。
番号 | エンジン | 名前 | 目的 |
---|---|---|---|
0 |
|
LUMP_ENTITIES | マップ上のエンティティ |
1 |
|
LUMP_PLANES | 平面の配列 |
2 |
|
LUMP_TEXDATA | テクスチャ名へのインデックス |
3 |
|
LUMP_VERTEXES | 頂点の配列 |
4 |
|
LUMP_VISIBLITY | 圧縮された可視性に関するビット配列 |
5 |
|
LUMP_NODES | BSP木のノード |
6 |
|
LUMP_TEXINFO | 面のテクスチャ配列 |
7 |
|
LUMP_FACES | 面の配列 |
8 |
|
LUMP_LIGHTING | ライトマップのサンプル |
9 |
|
LUMP_OCCLUSION | オクルージョンポリゴンと頂点 |
10 |
|
LUMP_LEAFS | BSP木の葉ノード(リーフ) |
11 |
|
LUMP_FACEIDS | dfaceとHammerの面IDの関連付けと、 detail propを配置する時の乱数の種に使用 |
12 |
|
LUMP_EDGES | 辺の配列 |
13 |
|
LUMP_SURFEDGES | 辺へのインデックス |
14 |
|
LUMP_MODELS | ブラシモデル |
15 |
|
LUMP_WORLDLIGHTS | エンティティLumpから変換された内部の ワールドライト(?) |
16 |
|
LUMP_LEAFFACES | 各leafの面へのインデックス |
17 |
|
LUMP_LEAFBRUSHES | 各leafのブラシへのインデックス |
18 |
|
LUMP_BRUSHES | ブラシの配列 |
19 |
|
LUMP_BRUSHSIDES | ブラシサイドの配列 |
20 |
|
LUMP_AREAS | エリアの配列 |
21 |
|
LUMP_AREAPORTALS | エリア間のポータル |
22 |
|
LUMP_PORTALS |
するポリゴン? |
|
LUMP_UNUSED0 | 未使用 | |
|
LUMP_PROPCOLLISION | 静的プロップの凸包リスト | |
23 |
|
LUMP_CLUSTERS | プレイヤーが入れるリーフ |
|
LUMP_UNUSED1 | 未使用 | |
|
LUMP_PROPHULLS | 静的プロップの凸包 | |
24 |
|
LUMP_PORTALVERTS | ポータルポリゴンの頂点 |
|
LUMP_UNUSED2 | 未使用 | |
|
LUMP_PROPHULLVERTS | 静的プロップの凸包の頂点 | |
25 |
|
LUMP_CLUSTERPORTALS |
するポリゴン? |
|
LUMP_UNUSED3 | 未使用 | |
|
LUMP_PROPTRIS | 静的プロップの凸包三角形 へのインデックスとカウント(?) |
|
26 |
|
LUMP_DISPINFO | Displacementの面配列 |
27 |
|
LUMP_ORIGINALFACES | 分割前のブラシ面の配列 |
28 |
|
LUMP_PHYSDISP | Displacementの物理衝突判定データ |
29 |
|
LUMP_PHYSCOLLIDE | 物理衝突判定データ |
30 |
|
LUMP_VERTNORMALS | 面の平面の法線(?) |
31 |
|
LUMP_VERTNORMALINDICES | 面の平面の法線(?)へのインデックス |
32 |
|
LUMP_DISP_LIGHTMAP_ALPHAS | Displacementのライトマップのアルファ(?) (Source 2006からは未使用もしくは空) |
33 |
|
LUMP_DISP_VERTS | Displacementの表面メッシュの頂点 |
34 |
|
LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS | Displacementのライトマップサンプル位置 |
35 |
|
LUMP_GAME_LUMP | ゲーム固有のデータLump |
36 |
|
LUMP_LEAFWATERDATA | 水中のリーフのためのデータ |
37 |
|
LUMP_PRIMITIVES | 水ポリゴンデータ |
38 |
|
LUMP_PRIMVERTS | 水ポリゴン頂点 |
39 |
|
LUMP_PRIMINDICES | 水ポリゴン頂点へのインデックスの配列 |
40 |
|
LUMP_PAKFILE | 埋め込まれた非圧縮のZip形式のファイル |
41 |
|
LUMP_CLIPPORTALVERTS | クリップされたポータルポリゴンの頂点 |
42 |
|
LUMP_CUBEMAPS | env_cubemapの位置の配列 |
43 |
|
LUMP_TEXDATA_STRING_DATA | テクスチャ名のデータ |
44 |
|
LUMP_TEXDATA_STRING_TABLE | LUMP_TEXDATA_STRING_DATA へのインデックス配列 |
45 |
|
LUMP_OVERLAYS | info_overlayのデータ配列 |
46 |
|
LUMP_LEAFMINDISTTOWATER | リーフから水までの距離 |
47 |
|
LUMP_FACE_MACRO_TEXTURE_INFO | 面のマクロテクスチャ情報 |
48 |
|
LUMP_DISP_TRIS | Displacementの表面の三角形 |
49 |
|
LUMP_PHYSCOLLIDESURFACE | 圧縮されたWin32固有のHavok地形面衝突 判定データ(?) 廃止されたため使われない |
|
LUMP_PROP_BLOB | 静的プロップの三角形と文字列のデータ(?) | |
50 |
|
LUMP_WATEROVERLAYS |
|
51 |
|
LUMP_LIGHTMAPPAGES | Xboxでのライティング関係の別の実装 |
|
LUMP_LEAF_AMBIENT_INDEX_HDR | LUMP_LEAF_AMBIENT_LIGHTING_HDRへの インデックス |
|
52 |
|
LUMP_LIGHTMAPPAGEINFOS | Xboxでのライティング関係の別の インデックス |
|
LUMP_LEAF_AMBIENT_INDEX | LUMP_LEAF_AMBIENT_LIGHTINGへの インデックス |
|
53 |
|
LUMP_LIGHTING_HDR | HDRライトマップサンプル |
54 |
|
LUMP_WORLDLIGHTS_HDR | エンティティLumpから変換された内部の HDRワールドライト |
55 |
|
LUMP_LEAF_AMBIENT_LIGHTING_HDR | リーフごとのアンビエントライト サンプル(HDR) |
56 |
|
LUMP_LEAF_AMBIENT_LIGHTING | リーフごとのアンビエントライト サンプル(LDR) |
57 |
|
LUMP_XZIPPAKFILE | Xboxで、XZipバージョンのpakファイル 廃止されている |
58 |
|
LUMP_FACES_HDR | HDRマップは異なる面データを持つことが ある |
59 |
|
LUMP_MAP_FLAGS | レベル全体の拡張フラグ すべてのレベルに存在するわけではない |
60 |
|
LUMP_OVERLAY_FADES | オーバーレイのフェード距離 |
61 |
|
LUMP_OVERLAY_SYSTEM_LEVELS | システムレベル設定(このオーバーレイを 描画するための最小/最大CPU&GPU) |
62 |
|
LUMP_PHYSLEVEL | To do |
63 |
|
LUMP_MULTIBLEND | Displacementのマルチブレンド情報 |
Lump 53~56はバージョン20以上のBSPファイルでのみ使用されます。Lump 22~25はバージョン20では使用されていません。 既知の要素について、データLumpの構造を以下に説明します。Lumpの多くは単純な構造の配列で、またいくつかはその内容に応じて可変長です。各データの最大サイズまたは要素数はbspfile.hでもMAX_MAP_*として定義されています。
最後に、ヘッダはマップリビジョン番号を表すint値で終わります。この数値は、マップのvmfファイルのリビジョン番号(mapversion
)に基いています。これは、Hammer Editorで保存される度に増加する値です。
ヘッダのすぐ後ろには、最初のデータLumpが続いています。実際には最初のデータLumpはLump 1(平面データ配列)ですが、これは前のリスト中ののoffsetフィールドで指定された任意のLumpです。
PlayStation 3やXbox 360などのコンソールプラットフォーム用のBSPファイルは通常、LZMAで圧縮されたLumpを格納しています。この場合、Lumpデータは次のヘッダで始まります(public/tier1/lzmaDecoder.hより)。
struct lzma_header_t
{
unsigned int id;
unsigned int actualSize; // 常にリトルエンディアン
unsigned int lzmaSize; // 常にリトルエンディアン
unsigned char properties[5];
};
id
は以下のように定義されています。
// リトルエンディアン "LZMA"
#define LZMA_ID (('A'<<24)|('M'<<16)|('Z'<<8)|('L'))
圧縮に関して、2つのスペシャルケースがあります。LUMP_PAKFILE
は決して圧縮されず、LUMP_GAME_LUMP
の各Gamelumpは個別に圧縮されます。圧縮されたGamelumpのサイズは、現在のGamelumpのオフセットを次のもののオフセットから引くことで決めることができます。このため、最後のGamelumpは常にオフセットを含む空のダミーです。
BSPジオメトリの基礎は、BSP木構造全体の分割面として使われる平面によって定義されます。
平面Lump (Lump 1) はdplane_t
構造体の配列です。
struct dplane_t
{
Vector normal; // 法線ベクトル
float dist; // 原点からの距離
int type; // 平面軸識別子(?)
};
Vector型は以下のように定義される3次元ベクトルです。
struct Vector
{
float x;
float y;
float z;
};
float型は4バイトの長さを持つため、1つの平面につきサイズは20バイトで、平面Lumpのサイズは20の倍数です。
平面は、その面に垂直な単位ベクトル(長さが1.0のベクトル)である法線ベクトルを示す要素normal
によって表現されます。平面の位置は、マップの原点(0, 0, 0)から平面上の最も近い点までの距離dist
によって与えられます。
数学的には、平面は方程式
Ax + By + Cz = D
を満たす点(x, y, z)の集合として記述されます。ここで、点(A, B, C)は平面の法線ベクトル、すなわちnormal
で、Dはdist
です。各平面は無限に広がっていて、マップ全体を平面の上(F = 0)、平面の前(F > 0)、平面の後ろ(F < 0)の3つに分割します。
平面は特定の向きを持ち、それによって平面の前と後ろが対応づけられます。また、平面の向きを反転するにはA、B、C、Dの各要素を反転します。
この構造体のtype
メンバは座標軸に垂直な平面を示すフラグが含まれているようですが、通常は使用されません。
マップには最大で65536枚の平面が存在します(MAX_MAP_PLANES
)。
頂点Lump (Lump 3) はマップジオメトリのブラシのすべての頂点(コーナー)座標の配列です。各頂点は3つの浮動小数点数からなるVector
型で表され、1つにつき12バイトのサイズを持ちます。
2つの面で頂点が正確に一致する場合、頂点はそれらの面で共有されることに注意してください。
マップには最大で65536個の頂点が存在します(MAX_MAP_VERTS
)。
辺Lump (Lump 12) はdedge_t構造体の配列です。
struct dedge_t
{
unsigned short v[2]; // 頂点インデックス
};
各辺は単なる頂点インデックス(頂点Lumpの配列へのインデックス)の組です。辺は2頂点間の直線として定義されます。通常、辺LumpはSurfedge配列を介して参照されます(下記)。
頂点と同じように、隣接する面の間で辺を共有することができます。
マップには最大で256000本の辺が存在します(MAX_MAP_EDGES
)。
「Surface edge lump」の略であると推定されるSurfedge Lump (Lump 13) は(符号付きの)整数の配列です。Surfedgeはやや複雑な方法で辺の配列を参照するために使用されます。Surfedge配列は正または負の値を取ります。この数値の絶対値は辺の配列へのインデックスです。もしこの数値が正の数である場合は、辺が最初の頂点から2番めの頂点の方向に定義されていることを意味しています。負の数の場合は、2番めの頂点から最初の頂点へ向かうように定義されています。
この方法により、Surfedge配列は辺を特定の方向に向かうように参照できます(辺を方向づけする理由については、以下の面の項目を参照してください)。
1つのマップに付き512000個のSurfedgeまでという制限があります(MAX_MAP_SURFEDGES
)。ただし、Surfedgeの数は必ずしもマップの辺の数と同じではありません。
面Lump (Lump 7) にはプレイヤーの視点をレンダリングするためにゲームエンジンによって使用されるマップの主要なジオメトリを含まれます。面LumpにはBSP分割処理を行った後の面が含まれています。したがって、面Lumpに含まれる面はHammer Editorで作成されたブラシの面には直接対応しません。また、面は常に平らな凸多角形ですが、同一直線上にある頂点を含むことができます。
面Lumpはマップファイルの中でも複雑な構造の1つです。このLumpは、56バイトの長さを持つdface_t
の配列です。
struct dface_t
{
unsigned short planenum; // 平面番号
byte side; // 面の向きと平面番号による平面の向きが反対なら1
byte onNode; // ノードにあるなら1、リーフにあるなら0
int firstedge; // Surfedge Lumpへのインデックス
short numedges; // 面を構成するSurfedgeの数
short texinfo; // テクスチャ情報
short dispinfo; // Displacement情報
short surfaceFogVolumeID; // ?
byte styles[4]; // 切り替え可能なライティングの情報
int lightofs; // ライトマップLumpへのオフセット
float area; // 面の面積(単位はHammer units^2)
int LightmapTextureMinsInLuxels[2]; // テクスチャライティング情報
int LightmapTextureSizeInLuxels[2]; // テクスチャライティング情報
int origFace; // この面の分割元となった元の面
unsigned short numPrims; // プリミティブ
unsigned short firstPrimID;
unsigned int smoothingGroups; // ライトマップスムージンググループ
};
最初のメンバであるplanenum
は平面番号(この面と位置の合う平面Lumpへのインデックス)です。この時、参照された平面がこの面と同じ方向を向いている場合、side
は0です。そうでない場合は、非ゼロです。
(3つのタイプのジオメトリデータを用いたd1_trainstation_01のX線ビューをアニメーションしたもの。フルサイズで見るにはこちら。)
firstedge
はSurfedge配列へのインデックスです。firstedge
番目のSurfedgeからそれに続くnumedges
個のSurfedgeが面を構成する辺を定義します。上で述べたように、Surfedge配列の値が正か負かは、辺の配列に格納された対応する頂点の組をどちらの方向にたどるかを示します。したがって、面を構成する頂点は面に向かって見ると時計回りの順になるように参照されます。これによって面のレンダリングが容易になり、視点から離れた面を高速に間引きすることができます。
texinfo
はTexInfo配列へのインデックスで(以下参照)、面に描かれるべきテクスチャを示します。dispinfo
はDispInfo配列へのインデックスで、0以上の場合は面はDisplacementであり、Displacementの境界を定義します。そうでない場合は、dispinfo
は-1です。surfaceFogVolumeID
はプレイヤーの視点が水中にあるか水面を見ている時にフォグを描画することに関係しているように思われます。
origFace
はこの面の分割される元となった「元の面(Original Face)」へのインデックスです。numPrims
とfirstPrimID
は「非ポリゴンプリミティブ」(以下参照)の描画に関連しています。dface_t
構造体の他のメンバは、面のライティング情報を参照するために使用されます(下記のライティングLumpを参照)。
面の数は65536枚に制限されています(MAX_MAP_FACES
)。
元の面Lump (Lump 27) は面Lumpと同じ構造を持ちますが、BSP分割処理が行われる前の面の配列が含まれています。したがって、これらの面は面の配列よりもマップのコンパイル前に存在する元のブラシ面に近く、また面の数はより少ないです。元の面のorigFace
はすべて0です。また、元の面の配列の要素数も最大で65536枚です。
面と元の面の両方がカリングされます。つまり、マップのコンパイル前に存在する多くの面(主にマップの境界より外側の方を向いている面)が配列から削除されます。
ブラシLump (Lump 18) にはコンパイル前のVMFファイルに含まれていたすべてのブラシが含まれています。面とは異なり、ブラシは辺や頂点の代わりに平面を使う空間領域構成法(CSG)で定義されています。Source BSPファイルにはブラシとブラシ側面のLumpが存在するため、この情報が存在しないGoldSrcのファイルよりもかなり容易に逆コンパイルできます。このLumpは12バイトの長さを持つdbrush_t
構造体の配列です。
struct dbrush_t
{
int firstside; // 最初のブラシ側面(へのインデックス)
int numsides; // ブラシ側面の数
int contents; // コンテンツフラグ
};
最初のint値firstside
はブラシ側面Lumpへのインデックスで、firstside
番目のブラシ側面からnumsides
枚のブラシ側面がこのブラシのすべての側面を構成します。contents
にはこのブラシの内容を決定するビットフラグが格納されています。値はビットOR演算されたもので、フラグはpublic/bspflags.hで定義されています。
名前 | 値 | 備考 |
---|---|---|
CONTENTS_EMPTY | 0 | 空の空間 |
CONTENTS_SOLID | 0x1 | 固体の中では目が見えない |
CONTENTS_WINDOW | 0x2 | 半透明だが、水ではない(つまりガラス) |
CONTENTS_AUX | 0x4 | |
CONTENTS_GRATE | 0x8 | アルファテストされた「鉄格子」のテクスチャで、弾丸と視線は通過するが、固体は通過しない |
CONTENTS_SLIME | 0x10 | |
CONTENTS_WATER | 0x20 | |
CONTENTS_MIST | 0x40 | |
CONTENTS_OPAQUE | 0x80 | AIの視線を遮断する |
CONTENTS_TESTFOGVOLUME | 0x100 | 透けて見えないもの(固体ではないかもしれないにもかかわらず) |
CONTENTS_UNUSED | 0x200 | 未使用 |
CONTENTS_UNUSED6 | 0x400 | 未使用 |
CONTENTS_TEAM1 | 0x800 | チームごとにプレイヤーやオブジェクトの衝突判定を区別するために使用される |
CONTENTS_TEAM2 | 0x1000 | |
CONTENTS_IGNORE_NODRAW_OPAQUE | 0x2000 | SURF_NODRAW を持つ面ではCONTENTS_OPAQUE を無視する |
CONTENTS_MOVEABLE | 0x4000 | MOVETYPE_PUSH のエンティティ(ドア、足場など)にあたる |
CONTENTS_AREAPORTAL | 0x8000 | 以下のフラグは不可視で、ブラシを侵食しない |
CONTENTS_PLAYERCLIP | 0x10000 | |
CONTENTS_MONSTERCLIP | 0x20000 | |
CONTENTS_CURRENT_0 | 0x40000 | CURRENT系は他のフラグに追加されるもので、複数追加されることもある |
CONTENTS_CURRENT_90 | 0x80000 | |
CONTENTS_CURRENT_180 | 0x100000 | |
CONTENTS_CURRENT_270 | 0x200000 | |
CONTENTS_CURRENT_UP | 0x400000 | |
CONTENTS_CURRENT_DOWN | 0x800000 | |
CONTENTS_ORIGIN | 0x1000000 | エンティティをBSP処理する前に削除される |
CONTENTS_MONSTER | 0x2000000 | ブラシに与えられるフラグではなく、ゲーム中に関わる |
CONTENTS_DEBRIS | 0x4000000 | |
CONTENTS_DETAIL | 0x8000000 | VisLeaf処理の後にブラシに追加される |
CONTENTS_TRANSLUCENT | 0x10000000 | 面に透明度がある場合に自動的に付与 |
CONTENTS_LADDER | 0x20000000 | |
CONTENTS_HITBOX | 0x40000000 | トレースで正確なヒットボックスを使うためのもの(?) |
これらのフラグの一部は、以前のゲームエンジンから引き継がれているように見え、Sourceのマップでは使われていないものもあります。また、これらのフラグはマップのリーフの中身について説明するためにも使われます(下記参照)。CONTENTS_DETAIL
フラグはマップのコンパイル前にfunc_detailエンティティであったブラシをマークするために使われます。
ブラシの配列の要素数は8192個に制限されています(MAX_MAP_BRUSHES
)。
ブラシ側面Lump (Lump 19) は8バイトの構造体の配列です。
struct dbrushside_t
{
unsigned short planenum; // リーフの外側を向いた平面
short texinfo; // テクスチャ情報
short dispinfo; // Displacementの情報
short bevel; // 側面が斜めなら1
};
planenum
は平面の配列へのインデックスで、そのブラシ側面に対応する平面を示します。texinfo
とdispinfo
はテクスチャとDisplacement情報のLumpへの参照を示します。bevel
は普通のブラシ側面なら0ですが、斜面の場合は1です(衝突判定に使われるものと思われます)。
面の配列とは異なり、ワールドの外を向いたブラシ側面はカリング(削除)されません。その代わり、コンパイル処理中にテクスチャ情報がtools/[toolsnodraw]
に変更されます。ここで注意すべきことは、ブラシをレンダリングするのに使われる対応する面の配列の要素と、ブラシとブラシ側面とを関連付ける直接的な方法が存在しないことです。ブラシ側面はすべてのプレイヤーとワールドブラシとの衝突判定を計算するためにエンジンによって使われます(VPhysicsオブジェクトは代わりにLump 29が使われます)。
ブラシ側面は最大で65536枚です(MAX_MAP_BRUSHSIDES
)。また、ブラシごとのブラシ側面の最大数は128枚です(MAX_BRUSH_SIDES
)。
ノード配列 (Lump 5) とリーフ配列 (Lump 10) はマップのBSP木(Binary Space Partition Tree)を定義します。BSP木はマップのジオメトリに対するプレイヤーの視点の位置と、可視性情報(下記参照)からマップのどの部分が描画されるかを素早く決定するためにエンジンによって使用されます。
ノードとリーフは木構造を構築します。各リーフはマップのボリュームを定義したものを表し、各ノードはすべての子ノードの総ボリュームを表します。
各ノードには必ず2つの子ノードがあり、子ノードは別のノードかリーフです。子ノードさらに2つの別の子ノードを持っていて、これは木のすべての分岐がリーフを指すまで続きます。また、各ノードは平面の配列内にある平面も参照します。プレイヤーの視点を決定する時、エンジンは視点がどのリーフの中にあるかを探します。この時、根ノード(ノード0)が参照している平面と視点の座標を比較し、平面の前に視点がある場合は最初の子ノードへ、後ろにある場合は2番めの子ノードへ移動します。比較するノードがリーフになるまでこれを続けることで、視点がどのリーフの中に存在するかを判断することができます。したがって、エンジンは視点の位置が見つかるまでBSP木を走査します。そして、リーフは親ノードの平面によって定義される重ならない凸なボリュームとしてマップのボリュームを分割します。
BSP木がどのように構築されるかについての詳細は、「BSP for dummies」の記事を参照してください。
ノード配列は32バイトの構造体で構成されています。
struct dnode_t
{
int planenum; // 平面の配列へのインデックス
int children[2]; // 負の値は -(leafs + 1)番目のリーフを表す
short mins[3]; // 視錐台カリング用
short maxs[3];
unsigned short firstface; // 面の配列へのインデックス
unsigned short numfaces; // 両面をカウントする
short area; // すべての子ノードが同じエリアの場合はエリアへのインデックス
// そうでない場合は-1
short paddding; // 32バイトの長さを持つpad(?)
};
planenum
は平面の配列の要素を表します。children[]
メンバはこのノードが持つ2つの子ノードです。もしこの値が正なら、ノードへのインデックスで、負なら、 -1-childはリーフの配列へのインデックスを表します(例えば、-100は99番目のリーフを参照します)。
mins[]
とmaxs[]
メンバはノードを囲むバウンディングボックスの座標です。firstface
とnumfaces
はこのノードに含まれているマップの面を表す面の配列へのインデックスです。0の場合は面が含まれていません。area
の値はこのノードにおけるマップのエリアです(下記参照)。マップには最大65536個のノードが存在します(MAX_MAP_NODES
)。
リーフ配列は要素が56バイトの長さを持つ配列です。
struct dleaf_t
{
int contents; // ブラシをすべてビットORしたもの(不必要?)
short cluster; // リーフ内のクラスタの数
short area:9; // リーフ内のエリアの数
short flags:7; // フラグ
short mins[3]; // 視錐台カリング用
short maxs[3];
unsigned short firstleafface; // リーフ面へのインデックス
unsigned short numleaffaces;
unsigned short firstleafbrush; // リーフブラシへのインデックス
unsigned short numleafbrushes;
short leafWaterDataID; // 水中でない場合は-1
//!!! バージョン19以前のマップは以下のコメントブロックを外す
/*
CompressedLightCube ambientLighting; // エンティティ用 計算済みのライティング情報
short padding; // 4バイトの境界のためのpadding(?)
*/
};
リーフの構造は子と平面への参照を持たないことを除いてノードのそれと似ています。追加の要素としてcontents
フラグ(前述のブラシLumpを参照)とcluster
(クラスタの数、下記参照)があります。contents
フラグはリーフ内のブラシの中身を示すフラグです。area
とflags
は16ビットの空間を共有していて、下位9ビットがarea
、上位7ビットがflags
です。area
はエリアナンバーを、flags
はリーフに関するフラグを示します。firstleafface
とnumleaffaces
はリーフ面の配列へのインデックスで、リーフ内に面がある時にどの面があるかを表します。firstleafbrush
とnumleafbrushes
も同様にリーフブラシの配列を通してリーフ内にあるブラシへのインデックスを表します。
ambientLighitng
という要素は24バイトのCompressedLightCube
構造体で、リーフ内にあるオブジェクトのライティングに関するものです。バージョン17のBSPファイルはdleaf_t構造体がアンビエントライティングのデータを省くように変更されていて、リーフごとのサイズが32バイトになっています。同じ構造体はバージョン20のBSPファイルでも用いられていて、LDRとHDRのためのアンビエントライティングの情報はおそらく新しいLumpであるLump 55とLump56に格納されています。
すべてのリーフは凸多面体で、親ノードの平面により定義されます。リーフは重ならず、マップ内の任意の点はただ1つのリーフの中に属します。固体のブラシで満たされていないリーフはプレイヤーが入ることが可能で、そのようなリーフはクラスタ番号が設定されています。これは可視性の情報と一緒に使用されます(下記)。
マップには通常複数の独立したBSP木が存在します。各木はモデル配列(下記)の要素に対応し、モデル配列の要素には各木の根ノードへの参照があります。最初のBSP木はworldspawnでモデルで、レベル全体のジオメトリです。続くBSP木はマップの各ブラシエンティティのモデルです。
BSP木の作成はマップのコンパイルの第一段階でVBSPプログラムにより行われます。マップの制作者によるHINTブラシやfunc_detailの使用と、すべてのブラシの注意深い配置は、どのようにBSP木が作られマップがリーフに分割されるかに影響を及ぼすことがあります。
リーフ面Lump (Lump 16) はunsigned short型の配列で、各要素は面の配列へのインデックスです。リーフブラシLump(これもunsigned short型の配列です) (Lump 17) も同様のことをブラシに対して行います。これらの最大サイズはどちらも65536個です(MAX_MAP_LEAFFACES
、MAX_MAP_LEAFBRUSHES
)。
マップのテクスチャ情報は様々なLumpに分割されています。Texinfo Lumpが最も基本的なもので、面やブラシ側面の配列から参照され、他のテクスチャ関係のLumpを参照します。
Texinfo Lump (Lump 6) はtexinfo_t
構造体の配列です。
struct texinfo_t
{
float textureVecs[2][4]; // [s/t][xyz オフセット]
float lightmapVecs[2][4]; // [s/t][xyz オフセット] - 長さの単位はtexels/area
int flags; // miptex フラグ オーバーライド
int texdata; // テクスチャ名やサイズなどへのポインタ
}
各Texinfoのサイズは72バイトです。
最初のfloat型配列は、ワールドジオメトリ上にレンダリングされる時のテクスチャの方向とスケーリングを表す2つのベクトルです。 2つのベクトルs、tは、テクスチャピクセル座標空間における左から右へ、下から上への方向をワールドにマッピングするものです。各ベクトルには、x、y、z成分と、ワールドに対するテクスチャの「シフト」のオフセットがあります。 ベクトルの長さは、各方向へのテクスチャのスケーリングを表します。
テクスチャピクセル(またはテクセル)の2次元座標(u, v)は、次式によって面上の点のワールド座標(x、y、z)にマッピングされる。
u = tv0,0 * x + tv0,1 * y + tv0,2 * z + tv0,3
v = tv1,0 * x + tv1,1 * y + tv1,2 * z + tv1,3
(すなわち、その方向へのオフセットと頂点のベクトルとの内積です。ここで、tvA, BはtextureVecs[A][B]
です。)
さらに、(u, v)を計算してからグラフィックスカードに送るテクスチャ座標に変換するには、uとvをテクスチャの幅と高さでそれぞれ割ります。
lightmapVecs
は、テクスチャのライトマップサンプルをワールドに同様にマッピングします。
flags
にはbspflags.hで定義されているビットフラグが含まれます。
名前 | 値 | 備考 |
---|---|---|
SURF_LIGHT |
0x1 | 値は光の強さを保持する |
SURF_SKY2D |
0x2 | 描画しない、2Dスカイボックスを描画することを示し、3Dスカイボックスは描画しない |
SURF_SKY |
0x4 | 描画しないが、スカイボックスに追加する |
SURF_WARP |
0x8 | 乱流ワープ(?) |
SURF_TRANS |
0x10 | テクスチャは半透明 |
SURF_NOPORTAL |
0x20 | この表面にはポータルを置くことができない |
SURF_TRIGGER |
0x40 | Xboxでオクルーダーに不具合が生じるため回避策としてトリガーサーフェスを消すためのもの |
SURF_NODRAW |
0x80 | テクスチャの参照をしないようにするもの |
SURF_HINT |
0x100 | BSPの境界面を作る |
SURF_SKIP |
0x200 | 完全に無視し、閉じてないブラシを可能にする |
SURF_NOLIGHT |
0x400 | ライティングを計算しない |
SURF_BUMPLIGHT |
0x800 | バンプマップ付きの表面のために3つのライトマップを計算する |
SURF_NOSHADOWS |
0x1000 | 影を落とさない |
SURF_NODECALS |
0x2000 | デカールを受け取らない |
SURF_NOCHOP |
0x4000 | この表面は分割されない |
SURF_HITBOX |
0x8000 | この面はヒットボックスの一部である |
フラグは、テクスチャの.vmtファイルの内容から派生しているように見え、そのテクスチャの特殊なプロパティを指定します。
最後に、texdata
はTexdata配列へのインデックスで、実際のテクスチャを指定します。
Texinfoのインデックス(面やブラシ側面から参照される)は-1が与えられることがあります。これはテクスチャ情報がその面に関連付けられていないことを示し、テクスチャのタイプにSKIP、CLIP、もしくはINVISIBLEを指定したブラシ面をコンパイルすると発生します。
Texdata配列 (Lump 2) は以下の構造体で構成されています。
struct dtexdata_t
{
Vector reflectivity; // RGB反射率
int nameStringTableID; // TexdataStringTableへのインデックス
int width, height; // 元画像
int view_width, view_height;
};
reflectivity
ベクトルは、マテリアルの.vtfファイルから取り出されたテクスチャの反射率のRGB成分に対応します。これは、テクスチャ表面からどのように光が反射するかのラジオシティ(照明)の計算におそらく使用されます。nameStringTableID
はTexdataStringTableへのインデックスです(下記)。 他のメンバは、テクスチャのソースイメージに関連しています。
TexdataStringTable (Lump 44) はint型の配列で、TexdataStringData (Lump 43) へのインデックスです。TexdataStringData Lumpはヌル終端文字列で表されたテクスチャ名を連結したものです。
マップには最大12288個のTexinfoが存在します(MAX_MAP_TEXINFO
)。Texdataの制限は最大2048個です(MAX_MAP_TEXDATA
)。また、TexdataStringDataのサイズは最大256000バイトです(MAX_MAP_TEXDATA_STRING_DATA
)。そして、テクスチャ名は最大128文字までです(TEXTURE_NAME_LENGTH
)。
ModelとはBSPファイル形式の用語で、しばしば「bmodel」とも呼ばれるブラシと面の集合のことです。Source SDKで「studiomodel」と呼ばれるHammer Editorで使用されるプロップモデルの方と混同しないように注意してください。
Model Lump (Lump 14) は24バイトのdmodel_t
構造体で構成されています。
struct dmodel_t
{
Vector mins, maxs; // バウンディングボックス
Vector origin; // サウンドやライティング用
int headnode; // ノード配列へのインデックス
int firstface, numfaces; // 面の配列へのインデックス
};
mins
とmaxs
はModelのバウンディングボックスを示す点です。origin
が設定されている場合、Modelの原点座標がその点であることを意味します。headnode
はこのModelを表すBSP木の根ノードを示すノード配列へのインデックスです。firstface
とnumfaces
は面の配列へのインデックスで、Modelを構成する面を示します。
この配列にある最初のModel(Model 0)は常に「worldspawn」(エンティティ以外のマップ全体のジオメトリとfunc_detailブラシの集合)です。続くModelはブラシエンティティに関連付けられるもので、エンティティLumpから参照されます。
マップには最大で1024個のModelが存在します(MAX_MAP_MODELS
)。これにはworldspawnのModelも含みます。
可視性Lump (Lump 4) はこれまでに解説したものとはやや異なった形式のLumpです。これを理解するためには、Source Engineの可視性システムがどのように機能するかについての議論が必要です。
ノードとリーフの節で述べたように、マップ内の任意の点はリーフと呼ばれる凸ボリュームに分類されます。マップ内の(外側の空間に触れていない)ブラシで覆われていないリーフは、プレイヤーの視点を含む可能性があります。このようなプレイヤーの入れるリーフ(visleaf とも呼ばれる)にはクラスタ番号が割り当てられます。Source BSPファイルでは1つの進入可能なリーフには1つのクラスタ番号が対応しています。
(用語がここでは少し分かりにくくなっています。「Quake 2 BSP File Format」の記事によると、Q2 Engineでは各クラスタに複数の隣接するリーフが存在する可能性があります。したがって、クラスタはリーフの集まりとみなせるためクラスタという名前で呼ばれています。この状況は、Sourceのマップをコンパイルする時にも発生することがあります。しかしながら、VVISのコンパイル処理が終了した後、これらの隣接するリーフ(と、それらの親ノード)は通常1つのリーフに統合されます。古いSourceのマップ(Counter-Strike: Global Offensiveより前)では、クラスタごとにリーフは1つしかないようですが、いくつかのCS:GOのマップでは最終的にコンパイルされたBSP内で、複数のリーフが同じクラスタに属することがあります。これは、ほとんどのCS:GOマップの3Dスカイボックスで特に起こると思われ、de_cbbleやde_nukeなどの最近改装されたマップの主なプレイ可能領域から見ることができます。)
各クラスタは、プレイヤーが存在する可能性を持つマップ内のボリュームです。マップを素早くレンダリングするために、ゲームエンジンは現在のクラスタから見えるクラスタについてのみジオメトリを描画します。プレイヤーのいるクラスタから完全に見えないクラスタを描く必要はありません。クラスタ間の可視性の計算はVVISコンパイルツールの役目で、その結果得られるデータは可視性Lumpに格納されます。
エンジンがあるクラスタが見えると認識すると、リーフデータはそのクラスタに存在するすべての面を参照し、結果としてそのクラスタの内容をレンダリングすることができます。
データはビットベクトルの配列として格納されます。各クラスタについて、他のどのクラスタが見えるかのリストはn番目のビットがn番目のクラスタに対応するビット配列(1なら見える、0なら見えない)として配列に格納されます。これはクラスタのPotentially Visiblity Set(PVS)として知られています。このデータはサイズが大きいため、各ビットベクトルは0ビットのランレングス符号化グループによって圧縮されます。
また、各クラスタに対してPotentially Audible Set(PAS)の配列も作成されます。これはあるクラスタで発生する音をどのクラスタで聞くことができるかを表します。PASは、現在のクラスタのPVS内にあるすべてのクラスタのPVSビットを統合することで作成されるようです。
可視性Lumpは以下のように定義されます。
struct dvis_t
{
int numclusters;
int byteofs[numclusters][2]
};
最初のint値はマップ内の総クラスタ数です。その後にはint型配列が続き、この配列はLumpの先頭から各クラスタのPVSビット配列の開始点とPAS配列の開始点へのオフセットです。配列の後には圧縮されたビットベクトルがあります。
ランレングス圧縮の復号化は次のように動作します。特定のクラスタのPVSを見つけるためには、byteofs[]
配列内のオフセットによって指定されたバイトから開始します。PVSバッファの現在のバイトが0の場合、次のバイトの値に8をかけた値がスキップするクラスタの数で、これはそのクラスタからは見えないクラスタです。現在のバイトが0でない場合は、設定されているビットがそのクラスタから見えるクラスタに対応します。これがマップ内の総クラスタ数の分だけ続きます。
ビットベクトルを展開するCコードの例は「Quake 2 BSP File Format」のドキュメントにあります。
可視性Lumpの最大サイズは0x1000000バイトです(MAX_MAP_VISIBLITY
)。すなわち、16MBです。
関連: Patching levels with lump files
エンティティLump (Lump 0) はエンティティのデータをコンパイル前のVMFファイルにあるKeyValueフォーマットに非常によく似た形式で格納するASCIIテキストバッファです。一般的な形式は次の通りです。
{
"world_maxs" "480 480 480"
"world_mins" "-480 -480 -224"
"maxpropscreenwidth" "-1"
"skyname" "sky_wasteland02"
"classname" "worldspawn"
}
{
"origin" "-413.793 -384 -192"
"angles" "0 0 0"
"classname" "info_player_start"
}
{
"model" "*1"
"targetname" "secret_1"
"origin" "424 -1536 1800"
"Solidity" "1"
"StartDisabled" "0"
"InputFilter" "0"
"disablereceiveshadows" "0"
"disableshadows" "0"
"rendermode" "0"
"renderfx" "0"
"rendercolor" "255 255 255"
"renderamt" "255"
"classname" "func_brush"
}
エンティティは中括弧([
と]
)に囲まれて定義され、引用符で囲まれたキーと値のペアを各行にリストします。最初のエンティティは常にworldspawnです。classname
プロパティはエンティティの種類を指定し、エンティティの名前がHammerで定義されていればtargetname
がその名前を指します。model
プロパティはアスタリスク(*)で始まる場合は少し特殊で、続く数字はブラシエンティティのModelに対応するModel配列(上記)へのインデックスです。それ以外の場合、model
プロパティはコンパイルされたモデルの名前を表します。他のキーと値のペアは、Hammerで設定されたエンティティのプロパティに対応します。
注釈: エンティティのうちのいくつか(func_detail、env_cubemap、info_overlay、prop_staticを含む)は内部的なもので、通常はワールドに吸収されるためコンパイル処理中にエンティティLumpから削除されます。
Source Engineのバージョンに応じて、エンティティLumpには4096(Source 2004)から16384(Alien Swarm)個のエンティティを含むことができます(MAX_MAP_ENTITIES
)。これらの制限は、エンジンの実際のエンティティの制限とは無関係です。各キーは最大32文字まで(MAX_KEY
)、値は最大1024文字までです(MAX_VALUE
)。
Gamelump (Lump 35) は、Source Engineを使用したゲーム固有のマップデータに使用されるように意図されているため、以前に定義されたフォーマットを変更することなくファイルフォーマットを拡張することができます。GamelumpはGamelumpヘッダから始まります。
struct dgamelumpheader_t
{
int lumpCount; // Gamelumpの数
dgamelump_t gamelump[lumpCount];
};
Gamelumpディレクトリ配列は次のように定義されます。
struct dgamelump_t
{
int id; // Gamelump ID
unsigned short flags; // フラグ
unsigned short version; // Gamelumpバージョン
int fileofs; // このGamelumpへのオフセット
int filelen; // 長さ
};
Gamelumpはどのデータが格納されているかを定義する4バイトのid
メンバによって識別され、データのバイト位置と長さはfileofs
とfilelen
で与えられます。fileofs
はBSPファイル先頭からの相対的なオフセットであり、Gamelumpのオフセットとは関係ないことに注意してください。ただし、Portal 2のコンソール版に関しては例外で、そこではfileofs
はGamelumpからの相対的なオフセットです。
興味深いのは、「scrp」(ASCII表記、10進数で1936749168)というGamelump IDを用いるprop_staticエンティティの格納に使われるGamelumpです。他のほとんどのエンティティとは異なり、Static PropはエンティティLumpに格納されません。Sourceで用いるGamelumpの形式はpublic/gamebspfile.hで定義されています。
Static Prop Gamelumpの最初の要素は辞書で、int値のカウントに続いてマップで使用されるモデル(Prop)の名前のリストがあります。
struct StaticPropDictLump_t
{
int dictEntries;
char name[dictEntries]; // モデル名
};
name
の要素はそれぞれ128文字で、ヌル文字でこの文字数まで埋められています。
辞書に続くのはリーフ配列です。
struct StaticPropLeafLump_t
{
int leafEntries;
unsigned short leaf[leafEntries];
};
おそらくこの配列は各Propが配置されているリーフを見つけるためにリーフLumpへインデックスをつけるために使用されます。また、prop_staticはいくつかのリーフにまたがって配置される場合があります。
次に、StaticPropLump_t
構造体の数を示すint値に続いてその構造体が多く続きます。
struct StaticPropLump_t
{
// v4
Vector Origin; // 原点座標
QAngle Angles; // 方向(ピッチ ロール ヨー)
unsigned short PropType; // モデル名の辞書へのインデックス
unsigned short FirstLeaf; // リーフ配列へのインデックス
unsigned short LeafCount;
unsigned char Solid; // 固体性(?)の種類
unsigned char Flags;
int Skin; // モデルスキン番号
float FadeMinDist;
float FadeMaxDist;
Vector LightingOrigin; // ライティング用
// v5から
float ForcedFadeScale; // フェード距離スケール
// v6とv7のみ
unsigned short MinDXLevel; // 見えるための最低DirectXバージョン
unsigned short MaxDXLevel; // 見えるための最大DirectXバージョン
// v8から
unsigned char MinCPULevel;
unsigned char MaxCPULevel;
unsigned char MinGPULevel;
unsigned char MaxGPULevel;
// v7から
color32 DiffuseModulation; // インスタンスごとの、色とアルファのモジュレーション
// v10から
float unknown;
// v9から
bool DisableX360; // trueならXbox360で表示しない
};
Propの座標はOrigin
メンバによって、向き(ピッチ ロール ヨー)は3つのfloat型ベクトルであるAngles
で与えられます。PropType
は上で与えられたPropモデル名の辞書へのインデックスです。他の要素はマップのBSP構造におけるPropの位置、ライティング、Hammerで設定された他のエンティティプロパティに対応します。Gamelumpの指定されたバージョン(dgamelump_t.version
を参照)が十分に高い場合は、さらに要素が存在します(ForcedFadeScale
など)。バージョン4と5のStatic Prop GamelumpはHL2の公式マップで使用されています。TF2からはバージョン6が現れました。Left 4 Deadのマップの一部ではバージョン7が使用され、Zeno Clashのマップでは変更が加えられたバージョン7が使用されています。バージョン8は主にLeft 4 Deadで使用され、バージョン9はLeft 4 Dead 2で使用されます。Tactical Interventionでは新しいバージョンであるバージョン10が現れます。
Source BSPファイルで使われる他のGamelumpは、Detail prop gamelump(dprp)、Detail prop lighting gamelump(LDRはdplt、HDRはdplh)です。これらはDisplacementに特定のテクスチャを指定した場合に自動的に現れるprop_detailエンティティ(草むらなど)に使用されます。
Gamelumpのサイズには特に制限はないようです。
DisplacementサーフェスはBSPファイルの中で最も複雑な部分であり、ここで説明されるのはそのフォーマットの一部のみです。DisplacementのデータはいくつかのデータLumpに分割されていますが、それらの基本的な参照はDispInfo Lump (Lump 26) によるものです。DispInfoは面、元の面、ブラシ側面の配列から参照されます。
struct ddispinfo_t
{
Vector startPosition; // 方向付けのために用いられる開始位置
int DispVertStart; // LUMP_DISP_VERTSへのインデックス
int DispTriStart; // LUMP_DISP_TRISへのインデックス
int power; // サーフェスのサイズを示す(2^power - 1)
int minTess; // 許容される最小のテセレーション(?)
float smoothingAngle; // ライティング スムージング角度
int contents; // サーフェス コンテンツ
unsigned short MapFace; // このDisplacementがどの面から得られたかを示すインデックス
int LightmapAlphaStart; // ddisplightmapalphaへのインデックス
int LightmapSamplePositionStart; // LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONSへのインデックス
CDispNeighbor EdgeNeighbors[4]; // NEIGHBOREDGE_ の定義によりインデックスされる
CDispCornerNeighbors CornerNeighbors[4]; // CORNER_ の定義によりインデックスされる
unsigned int AllowedVerts[10]; // アクティブな頂点(?)
};
この構造体は176バイトの長さを持ちます。startPosition
はDisplacementの最初のコーナーの座標です。DispVertStart
とDispTriStart
はDispVerts LumpとDispTris Lumpへのインデックスです。power
はDisplacementの分割数を表します。許容値は2、3、4で、これらの値はDisplacementの各辺を4,8、16本に分割することに対応しています。この構造体はEdgeNeighbors
およびCornerNeighbors
メンバを介してこのDisplacementの側面や角に隣接するDisplacementも参照します。隣接するDisplacementの順序には複雑な規則があります。詳細はbspfile.hのコメントを参照してください。MapFace
は面の配列へのインデックスで、このDisplacementに変換される元になった面です。この面にはテクスチャ、Displacement全体の物理的位置、Displacementの境界を設定するために使用されます。
DispVerts Lump (Lump 33) にはDisplacementの頂点データが含まれていて、次のように与えられます。
struct dDispVert
{
Vector vec; // Displacementのボリュームを定めるベクトル場
float dist; // Displacement距離.
float alpha; // 「頂点ごとの」アルファ値
};
vec
はDisplacementの各頂点について、元の(平坦な)位置からの変位を表すベクトルを正規化したものです。dist
は変位の距離です。alpha
はその頂点でのテクスチャのアルファブレンド値です。
power
がpのDisplacementは、DispVertStartから(2p + 1)2個のDispVertsを参照します。
DispTris Lump (Lump 48) にはDisplacementのメッシュの特定の三角形のプロパティに関する「三角形タグ」もしくはフラグが含まれています。
struct dDispTri
{
unsigned short Tags; // Displacementの三角形タグ
};
フラグが示すものは以下の通りです。
名前 | 値 |
---|---|
DISPTRI_TAG_SURFACE |
0x1 |
DISPTRI_TAG_WALKABLE |
0x2 |
DISPTRI_TAG_BUILDABLE |
0x4 |
DISPTRI_FLAG_SURFPROP1 |
0x8 |
DISPTRI_FLAG_SURFPROP2 |
0x10 |
power
がpのDisplacementには2×(2p)2個のDispTrisがあります。それらはおそらくその位置が歩行可能かどうかなど、Displacementを構成する各三角形のプロパティを示すために使用されます。
DispInfoは1つのマップにつき2048個の制限があり、DispVertsとDispTrisの制限は2048個すべてのDisplacementのpower
が4である場合の個数に制限されています(つまり、もっとも細かく分割された場合の個数です)。
Displacementに関連する他のデータは、DispLightmapAlphas Lump (Lump 32) とDispLightmapSamplePos Lump (Lump 34) であり、Displacementのライティングに関連していると思われます。
Pakfile Lump (Lump 40) はBSPファイルに埋め込まれた複数のファイルを格納できる特別なLumpです。通常、マップ内のenv_cubemapエンティティからの反射マップを格納するための特別なテクスチャ(.vtf)ファイルとマテリアル(.vmt)ファイルが含まれています。これらのファイルはbuildcubemaps
コンソールコマンドが実行された時にビルドされてPakfile Lumpに格納されます。Pakfileにはマップで使用されるカスタムテクスチャやPropのようなものも任意で含めることができ、それらはBSPZIPプログラム(もしくはPakratのような代替プログラム)を用いてBSPファイル内に配置されます。また、これらのファイルはゲームエンジンのファイルシステムに統合されて、外部のファイルが使用される前に優先的に読み込まれます。
Pakfile Lumpの形式は、圧縮が指定されていない場合(つまり、個々のファイルが非圧縮形式で保存されている場合)はZip圧縮ユーティリティで用いられる形式と同じです。Pakfile Lumpを展開すると、WinZipなどのプログラムで開くことができるようになります。
ヘッダファイルpublic/zip_uncompressed.hは、Pakfile Lumpに存在する構造体を定義します。Lumpの最後の要素はZIP_EndOfCentralDirRecord
構造体です。これはその構造体の直前に、Pakに存在する各ファイルに対して1つずつあるZIP_FileHeader
構造体の配列を指します。これらのヘッダはそれぞれ、ファイルのデータが後ろに続いているZIP_LocalFileHeader
構造体を指し示します。
Pakfile Lumpは通常、BSPファイルの最後の要素です。
Cubemap Lump (Lump 42) は16バイトのdcubemapsample_t
構造体の配列です。
struct dcubemapsample_t
{
int origin[3]; // 最も近い整数に丸められたライトの位置
int size; // Cubemapの解像度(0: デフォルト)
};
dcubemapsample_t
構造体は、マップ内のenv_cubemapエンティティの場所を定義します。origin
メンバには、Cubemapの整数座標x、y、zが含まれ、size
メンバは2size
- 1ピクセルの正方形として指定されるCubemapの解像度で、0の場合はデフォルトのサイズである6(32×32ピクセル)になります。ファイルには最大1024個のCubemapが存在します(MAX_MAP_CUBEMAPSAMPLES
)。
buildcubemap
コンソールコマンドが実行されると、各Cubemapエンティティの位置でマップのスナップショットが6つ(各方向について1つずつ)撮影されます。これらのスナップショットはマルチフレームテクスチャファイル(.vtf)に格納され、Pakfile Lump(上記)に追加されます。テクスチャ名はcX_Y_Z.vtf
で、(X, Y, Z)はCubemapの(整数)座標です。
環境マッピングされたマテリアルを含む面(例えば光沢のあるテクスチャ)は、マテリアル名を介してCubemapを参照します。(例えば)walls/shiny.vmt
と名前の付いたマテリアルは書き換えられて(新しいTexinfoとTexdataが作成されて)、変更されたマテリアル名であるmaps/mapname/walls/shiny_X_Y_Z.vmt
を参照するようになります。ここで(X、Y、Z)は 前述したようにCubemapの座標です。この.vmtファイルはPakfileにも格納され、$envmapプロパティを使用してCubemapの.vtfファイルを参照します。
バージョン20のファイルにはさらにcX_Y_Z_hdr.vtf
がPakfile Lumpに追加されます。これにはRGBA16161616F形式(チャンネルごとに16ビット)のHDRテクスチャファイルが含まれています。
単純なデカール(infodecalエンティティ)とは異なり、info_overlayはエンティティLumpから削除されて、オーバーレイLump (Lump 45) に分けて保存されます。この構造体はHammerのエンティティのプロパティをほぼ正確に反映しています。
struct doverlay_t
{
int Id;
short TexInfo;
unsigned short FaceCountAndRenderOrder;
int Ofaces[OVERLAY_BSP_FACE_COUNT];
float U[2];
float V[2];
Vector UVPoints[4];
Vector Origin;
Vector BasisNormal;
};
FaceCountAndRenderOrder
メンバは2つの部分に分かれています。下位14ビットはオーバーレイが表示される面の数で、上位2ビットはオーバーレイの表示順序です(重なったオーバーレイの場合)。Ofaces
は要素数64の配列で(OVERLAY_BSP_FACE_COUNT
)、オーバーレイが表示される面へのインデックスが格納されています。他の要素はオーバーレイのテクスチャ、スケール、向きを設定します。1つのファイルには最大512個のオーバーレイが存在します(MAX_MAP_OVERLAYS
)。また、Dota 2ではオーバーレイ数の制限が大幅に増加しています。
ライティングLump (Lump 8) はマップの面の静的ライトマップサンプルを格納するために使用されます。各ライトマップサンプルは、テクスチャピクセルの色と乗算する色合いであり、様々な強度の照明を生成します。これらのライトマップはマップコンパイル時のVRAD処理中に作成され、dface_t
構造体から参照されます。現在のライティングLumpのバージョンは1です。
dface_t
ではstyles[]
配列で定義された最大4つのライトスタイルを持つことができます(ただし値255はライトスタイルがないことを示す)。面の各方向についてのルクセル数は、2つのLightmapTextureSizeInLuxels[]
メンバの値(+ 1)によって与えられ、面ごとのルクセルの総和は次のようになります。
(LightmapTextureSizeInLuxels[0] + 1) * (LightmapTextureSizeInLuxels[1] + 1)
面はそれぞれ、dface_t
のlightofs
メンバによってライティングLump内のオフセットを持ちます(もし、スカイボックスやnodrawなどの不可視のテクスチャであるなどの理由でその面にライティング情報が使われていない場合、lightofs
は-1です)。ライトマップサンプルの総数は(ライトスタイル数)×(ルクセル数)で、各サンプルはColorRGBExp32
構造体で与えられます。
struct ColorRGBExp32
{
byte r, g, b;
signed char exponent;
};
この構造体から、それぞれの色成分に2exponent
をかけることで標準のRGB形式を得ることができます。バンプマップ付きのテクスチャを持つ面の場合、おそらくバンプマップを計算するためのサンプルを含むためライトマップサンプルの数は通常の4倍になります。
lightofs
で参照されるサンプルグループの直前には、面のライティングの平均値がライトスタイルごとに、styles[]
配列で与えられた順番とは逆の順番になって存在しています。
バージョン20のBSPファイルには、同じサイズの2つ目のライティングLump (Lump 53) が含まれています。これは、各ライトマップサンプルに対してより正確な(より高精度の)HDRデータを格納するものと推定されています。フォーマットは現在不明ですが、1サンプルにつき32ビットです。
ライティングLumpの最大サイズは0x1000000バイトです(MAX_MAP_LIGHTING
)。すなわち、16MBです。
アンビエントライティングLump (Lump 55とLump 56) は、BSPバージョン20以降に存在します。Lump 55はHDRライティングに使用され、Lump 56はLDRライティングに使用されます。これらのLumpは、Volumetric Ambient Lighting情報(例えば、NPC、ビューモデル、静的でないPropなどのエンティティのためのライティング情報)を格納するために使われます。バージョン20より前はこのデータをリーフLumpのdleaf_t
構造体に格納していましたが、この新しいLumpよりもはるかに低い精度でした。
アンビエントライティングLumpは両方ともdleafambientlighting_t
構造体の配列です。
struct dleafambientlighting_t
{
CompressedLightCube cube;
byte x; // 固定小数点で、リーフのバウンディングボックスの割合
byte y; // 固定小数点で、リーフのバウンディングボックスの割合
byte z; // 固定小数点で、リーフのバウンディングボックスの割合
byte pad; // 未使用
};
各リーフはdleafambientlighting_t
構造体のうちのいくつかに関連付けられています。各構造体はx
、y
、z
メンバによって指定された位置にある周囲光データのキューブを含みます。これらの座標はリーフが持つバウンディングボックスの割合として格納されます。つまり、xが0の場合はリーフの最西端で、255の場合は最東端、128の場合は中心を表します。
各サンプルのライティングデータは、CompressedLightCube
構造体で表されます。これは、前のセクションで説明したColorRGBExp32
構造体が6つ格納された配列です。
struct CompressedLightCube
{
ColorRGBExp32 m_Color[6];
};
配列中の各ライティングサンプルは、3D空間内の各座標軸方向から受ける光の量に対応します。
コンパイル時に、VRADは各リーフで周囲光のサンプルを取る位置ランダムに生成し、各サンプル点についてのライティング情報をdleafambientlighting_t
構造体へ格納します。各リーフと周囲光のサンプルを関連付けるために、アンビエントライティングインデックスLump (Lump 51とLump 32) が用いられます。Lump 51はHDRについてのアンビエントライティングインデックス情報を、Lump 52はLDRについての情報を格納します。
アンビエントライティングインデックスLumpはdleafambientindex_t
構造体の配列です。
struct dleafambientindex_t
{
unsigned short ambientSampleCount;
unsigned short firstAmbientSample;
};
この配列のN番目のdleafambientindex_t
構造体は常にdleaf_t
配列のN番目に対応します。ambientSampleCount
フィールドは対応するリーフに関連する周囲光の数で、firstAmbientSample
はアンビエントライティング配列へのインデックスで、これは関連付けられたリーフの最初の周囲光サンプルを参照します。
オクルージョンLump (Lump 9) にはポリゴンジオメトリとfunc_occluderエンティティで使用されるいくつかのフラグが含まれています。他のブラシエンティティとは異なり、func_occluderはエンティティLumpで「model」キーを使用しません。代わりに、そのブラシはコンパイル処理中にエンティティから分離され、occludernum
という数値としてオクルーダーキーが割り当てられます。tools/[toolsoccluder]
またはtools/[toolstrigger]
のテクスチャが付いたブラシの側面は、オクルーダーキーとともに保存され、このLumpにいくつかの追加情報が格納されます。
このLumpは3つに分割され、オクルーダーの総数に、一定のサイズのdoccluderdata_t
フィールドの配列が続いたものから始まります。次のパートは別の整数値で始まり、こちらはオクルーダーの総ポリゴン数です。続いてその数だけdoccluderpolydata_t
フィールドの配列が続きます。3つ目のパートはオクルーダーの総頂点数を表す整数から始まり、頂点インデックスが続きます。
struct doccluder_t
{
int count;
doccluderdata_t data[count];
int polyDataCount;
doccluderpolydata_t polyData[polyDataCount];
int vertexIndexCount;
int vertexIndices[vertexIndexCount];
};
doccluderdata_t
構造体にはオクルーダーのフラグと寸法、そしてその領域が含まれています。firstpoly
はpolycount
を含むdoccluderpolydata_t
への最初のインデックスです。
struct doccluderdata_t
{
int flags;
int firstpoly; // doccluderpolysへのインデックス
int polycount; // ポリゴンの数
Vector mins; // 全頂点の最小値
Vector maxs; // 全頂点の最大値
// v1から
int area;
};
オクルーダーポリゴンはdoccluderpolydata_t
構造体に格納されていて、firstvertexindex
フィールドを含みます。これはオクルーダーの頂点配列へのインデックスで、オクルーダーの頂点配列の要素は頂点Lump (Lump 3) の配列へのインデックスとなっています。頂点インデックスの総数はvertexcount
に格納されます。
struct doccluderpolydata_t
{
int firstvertexindex; // doccludervertindicesへのインデックス
int vertexcount; // 頂点インデックスの数
int planenum;
};
Physcolldie Lump (Lump 29) にはワールドの物理的なデータが含まれています。
このLumpはひと続きのmodelで構成されていて、各modelは以下から構成されます。
dphysmodel_t
ヘッダstruct dphysmodel_t { int modelIndex; // おそらくこの物理モデルを適用するモデルへのインデックス? int dataSize; // コリジョンデータセクションの合計サイズ int keydataSize; // テキストセクションのサイズ int solidCount; // コリジョンデータセクションの数 };
- 一連のコリジョンデータセクション(
compactsurfaceheader_t
を含む) - テキストセクション
このLumpはmodelIndex
が-1に設定されたdphysmodel_t
構造体で終了します。
最後の2つの部分は、PHYファイル形式と同じに見えます。すなわち、正確な内容は不明です。compactsurfaceheader_t
構造体には各コリジョンデータセクション(ヘッダの残りも含む)のサイズが含まれているので、このLumpは次のように解析できます。
while(true) {
header = readHeader();
if(header.modelIndex == -1)
break;
for(int k = 0; k < header.solidCount; k++) {
size = read4ByteInt();
collisionData = readBytes(size);
}
textData = readBytes(header.keydataSize);
}
To do: これらの情報の一部は推測に基づくものである可能性があるため、更なる調査が必要です。
- Worldlights Lump (Lump 15) にはワールドにあるそれぞれのスタティックライトエンティティに関する情報が含まれていて、移動するエンティティの半動的ライティングを提供するために使用されているようです。
- Area Lump (Lump 20) は、Areaportal Lump (Lump 21) を参照し、func_areaportalおよびfunc_areaportalwindowエンティティとともに使用することでマップをレンダリングするかしないかを切り替えられる仕切りを定義します。
- Portal Lump (Lump 22) 、Cluster Lump (Lump 23) 、PortalVerts Lump (Lump 24) 、ClusterPortals (Lump 25) 、ClipPortalVerts (Lump 41) はコンパイルのVVIS処理でどのクラスタがあるクラスタから見ることができるかを確認するために使用されます。クラスタはマップ内のプレイヤーが進入可能なリーフ ボリュームです(上記)。「ポータル」はクラスタまたはリーフが隣接する部分のポリゴン境界のことです。この情報の大部分はVRADプログラムによってスタティックライティングを計算するためにも用いられ、その後はBSPファイルから削除されます。
- PhysCollide Lump (Lump 29) とPhysCollideSurface Lump (Lump 49) はゲームエンジンでエンティティの衝突に関する物理シミュレーションに関連しているようです。
- VertNormal Lump (Lump 30) とVertNormalIndices Lump (Lump 31) は面のライトマップのスムージングに関連している可能性があります。
- FaceMacroTextureInfo Lump (Lump 47) は、マップ内の面の数と同じ数の要素を持ったshort値の配列です。この要素に-1(0xFFFF)以外のものが含まれている場合、その面はTexDataStringTableのテクスチャ名へのインデックスを持っています。VRADでは、対応するテクスチャはワールドエクステント(?)にマッピングされ、その面のライトマップのモジュレーションとして使用されます。すべての面に適用されるベースマクロテクスチャ(
materials/macro/mapname/base.vtf
に位置する)が見つかることもあります。VTMBのマップだけがマクロテクスチャを使用しているようです。 - LeafWaterData Lump (Lump 36) とLeafMinDistToWater Lump (Lump 46) は、水のボリュームに関してプレイヤーの位置を決定するために用いられるようです。
- Primitives Lump (Lump 37) とPrimVerts Lump (Lump 38) は、「非ポリゴンプリミティブ」に関することに使用されます。これらはもともと水のメッシュを分割するためだけに用いられていたため、SDK Sourceでは「waterstrips」、「waterverts」、「waterindices」と呼ばれることもあります。現在は、面を構成する辺に「T字型接合」(2つの頂点からなる直線上に頂点がある状態)が含まれる場合に隣接する面との間にクラックが発生するのを防ぐために使用されています。PrimIndices Lumpは面の頂点間の三角形のセットを定義して、面をテセレーションします。そして、それらはPrimitives Lumpから参照されます。Primitive Lumpは面Lumpによって参照されます。現在のマップでは、PrimVerts Lumpは全く使用されていないようです(参考)。
- HDRライティング情報を含んでいるバージョン20のファイルは、さらに4つの追加のLumpを持っていますが、現在その内容は正確には分かっていません。Lump 53は常に標準のライティングLump (Lump 8) と同じサイズであり、おそらく各ライトマップサンプルについて精度の高いデータを含んでいます。Lump 54はWorldlight Lump (Lump 15) と同じサイズであり、おそらくライトエンティティにおけるHDR関連のデータを含みます。