Thats the Q3 BSP code of my old engine:
SuperStrict
Import BRL.FileSystem
Import BRL.EndianStream
Import "Entity.bmx"
Import "Texture.bmx"
' Header
Const DDD_Q3MAP_MAGIC : Int = $50534249 ' "IBSP"
Const DDD_Q3MAP_VERSION : Int = $2E
' Lumps
Const DDD_Q3MAP_ENTITIES : Int = 0
Const DDD_Q3MAP_TEXTURES : Int = 1
Const DDD_Q3MAP_PLANES : Int = 2
Const DDD_Q3MAP_NODES : Int = 3
Const DDD_Q3MAP_LEAFS : Int = 4
Const DDD_Q3MAP_LEAFFACES : Int = 5
Const DDD_Q3MAP_LEAFBRUSHES : Int = 6
Const DDD_Q3MAP_MODELS : Int = 7
Const DDD_Q3MAP_BRUSHES : Int = 8
Const DDD_Q3MAP_BRUSHSIDES : Int = 9
Const DDD_Q3MAP_VERTICES : Int = 10
Const DDD_Q3MAP_INDICES : Int = 11
Const DDD_Q3MAP_EFFECTS : Int = 12
Const DDD_Q3MAP_FACES : Int = 13
Const DDD_Q3MAP_LIGHTMAPS : Int = 14
Const DDD_Q3MAP_LIGHTVOLS : Int = 15
Const DDD_Q3MAP_VISDATA : Int = 16
' FaceTypes
Const DDD_Q3MAP_POLYGON : Int = 1
Const DDD_Q3MAP_PATCH : Int = 2
Const DDD_Q3MAP_MESH : Int = 3
Type TQ3Map Extends TEntity
Field TextureCount : Int
Field LightmapCount : Int
Field Textures : Int[]
Field Lightmaps : Int[]
Method Render(Camera:TEntity)
End Method
Function Load:TQ3Map(URL:Object, Gamma:Float=0.0, Tesselation:Int=4)
Return TQ3MapLoader.Load(URL, Gamma, Tesselation)
End Function
End Type
Type TQ3Header
Field Magic : Int
Field Version : Int
Field Lumps : Int[17, 2]
Method Read(Stream:TStream)
Self.Magic = Stream.ReadInt()
Self.Version = Stream.ReadInt()
Stream.Read(Self.Lumps, 136)
End Method
Method Check:Int()
If (Self.Magic <> DDD_Q3MAP_MAGIC) Or ..
(Self.Version <> DDD_Q3MAP_VERSION) Then
Return False
Else
Return True
EndIf
End Method
End Type
Type TQ3Texture
Field Name : String
Field Flags : Int
Field Contents : Int
Method Read(Stream:TStream)
Self.Name = Stream.ReadString(64)
Self.Flags = Stream.ReadInt()
Self.Contents = Stream.ReadInt()
If Self.Name.Find("~0") <> -1 Then ..
Self.Name = Self.Name[0..Self.Name.Find("~0")]
End Method
Method GetFilename:String()
If Self.Name = "noshader" Then
Return ""
ElseIf FileType(Self.Name+".jpg") = 1 Then
Return Self.Name+".jpg"
ElseIf FileType(Self.Name+".tga") = 1 Then
Return Self.Name+".tga"
Else
If FileType(StripDir(Self.Name)+".jpg") = 1 Then
Return StripDir(Self.Name)+".jpg"
ElseIf FileType(StripDir(Self.Name)+".tga") = 1 Then
Return StripDir(Self.Name)+".tga"
Else
Return ""
EndIf
EndIf
End Method
Method LoadGLTexture:Int()
Local Pixmap:TPixmap, Format:Int, Width:Int, Height:Int
Local Texture:Int
Pixmap = LoadPixmap(Self.GetFilename())
If Not Pixmap Then Return -1
Select Pixmap.Format
Case PF_RGB888
Format = GL_RGB
Case PF_BGR888
Pixmap = Pixmap.Convert(PF_RGB888)
Format = GL_RGB
Case PF_RGBA8888
Format = GL_RGBA
Case PF_BGRA8888
Pixmap = Pixmap.Convert(PF_RGBA8888)
Format = GL_RGBA
End Select
Width = Pixmap.Width
Height = Pixmap.Height
TTexture.AdjustTexSize(Width, Height)
If (Width <> Pixmap.Width) Or (Height <> Pixmap.Height) Then ..
Pixmap = ResizePixmap(Pixmap, Width, Height)
glGenTextures(1, Varptr(Texture))
glBindTexture(GL_TEXTURE_2D, Texture)
gluBuild2DMipmaps(GL_TEXTURE_2D, Format, Width, Height, Format, ..
GL_UNSIGNED_BYTE, Pixmap.Pixels)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
Return Texture
End Method
End Type
Type TQ3Lightmap
Field Pixels : Byte[49152]
Method Read(Stream:TStream)
Stream.Read(Self.Pixels, 49152)
End Method
Method MakeGLTexture:Int(Gamma:Float=0.0)
Local Index:Int, Bias:Int, Pixel:Int, Texture:Int
If Gamma <> 0.0 Then
For Index = 0 Until 49152
Bias = Gamma*255.0
Pixel = Self.Pixels[Index]
Pixel :+ Bias
If Pixel < 0 Then Pixel = 0
If Pixel > 255 Then Pixel = 255
Self.Pixels[Index] = Pixel
Next
EndIf
glGenTextures(1, Varptr(Texture))
glBindTexture(GL_TEXTURE_2D, Texture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 128, 128, 0, GL_RGB, ..
GL_UNSIGNED_BYTE, Self.Pixels)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE)
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE)
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS)
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE)
glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE, 2.0)
Return Texture
End Method
End Type
Type TQ3BiQuadPatch
Field ControlPoints : Float[9, 14]
Field Vertices : Float[,]
Field Indices : Int[]
Method Tesselate(Tesselation:Int)
End Method
End Type
Type TQ3Face
Field TextureID : Int
Field Effect : Int
Field FaceType : Int
Field FirstVertex : Int
Field NumVertices : Int
Field FirstIndex : Int
Field NumIndices : Int
Field LightmapID : Int
Field LightmapStart : Int[2]
Field LightmapSize : Int[2]
Field LightmapOrigin : Float[3]
Field Tangent : Float[2, 3]
Field Normal : Float[3]
Field PatchSize : Int[2]
Field Patches : TQ3BiQuadPatch[,]
Method Read(Loader:TQ3MapLoader, Tesselation:Int)
Self.TextureID = Loader.Stream.ReadInt()
Self.Effect = Loader.Stream.ReadInt()
Self.FaceType = Loader.Stream.ReadInt()
Self.FirstVertex = Loader.Stream.ReadInt()
Self.NumVertices = Loader.Stream.ReadInt()
Self.FirstIndex = Loader.Stream.ReadInt()
Self.NumIndices = Loader.Stream.ReadInt()
Self.LightmapID = Loader.Stream.ReadInt()
Loader.Stream.Read(Self.LightmapStart, 8)
Loader.Stream.Read(Self.LightmapSize, 8)
Self.LightmapOrigin[0] = Loader.Stream.ReadFloat()
Self.LightmapOrigin[2] = -Loader.Stream.ReadFloat()
Self.LightmapOrigin[1] = Loader.Stream.ReadFloat()
Self.Tangent[0, 0] = Loader.Stream.ReadFloat()
Self.Tangent[0, 2] = -Loader.Stream.ReadFloat()
Self.Tangent[0, 1] = Loader.Stream.ReadFloat()
Self.Tangent[1, 0] = Loader.Stream.ReadFloat()
Self.Tangent[1, 2] = -Loader.Stream.ReadFloat()
Self.Tangent[1, 1] = Loader.Stream.ReadFloat()
Loader.Stream.Read(Self.Normal, 12)
Loader.Stream.Read(Self.PatchSize, 8)
If Self.FaceType = DDD_Q3MAP_PATCH Then
ElseIf Self.FaceType = DDD_Q3MAP_POLYGON Or ..
Self.FaceType = DDD_Q3MAP_MESH Then
EndIf
End Method
End Type
Type TQ3MapLoader
Field Stream : TStream
Field Map : TQ3Map
Field Header : TQ3Header
Field Vertices : Float[,]
Field Indices : Int[]
Field Faces : TQ3Face[]
Field Planes : Float[,]
Function Load:TQ3Map(URL:Object, Gamma:Float=0.0, Tesselation:Int=4)
Local Loader:TQ3MapLoader, OldDir:String, Count:Int, Index:Int
Local Texture:TQ3Texture, Lightmap:TQ3LightMap
' Check if Stream can load
Loader = New TQ3MapLoader
Loader.Stream = LittleEndianStream(ReadStream(URL))
If Not Loader.Stream Then Return Null
' Check header
Loader.Header = New TQ3Header
Loader.Header.Read(Loader.Stream)
If Not Loader.Header.Check() Then
Loader.Stream.Close()
Return Null
EndIf
' New Quake3 Map
Loader.Map = New TQ3Map
' Change Directory for Textures
OldDir = CurrentDir()
If String(URL) <> "" Then ChangeDir(ExtractDir(String(URL)))
' Load Textures
Loader.Stream.Seek(Loader.Header.Lumps[DDD_Q3MAP_TEXTURES, 0])
Count = Loader.Header.Lumps[DDD_Q3MAP_TEXTURES, 1]/72
Loader.Map.TextureCount = Count
Loader.Map.Textures = New Int[Count]
Texture = New TQ3Texture
For Index = 0 Until Count
Texture.Read(Loader.Stream)
Loader.Map.Textures[Index] = Texture.LoadGLTexture()
Next
' Load Lightmaps
Loader.Stream.Seek(Loader.Header.Lumps[DDD_Q3MAP_LIGHTMAPS, 0])
Count = Loader.Header.Lumps[DDD_Q3MAP_LIGHTMAPS, 1]/49152
Loader.Map.LightmapCount = Count
Loader.Map.Lightmaps = New Int[Count]
Lightmap = New TQ3LightMap
For Index = 0 Until Count
Lightmap.Read(Loader.Stream)
Loader.Map.Lightmaps[Index] = Lightmap.MakeGLTexture(Gamma)
Next
' Load Vertices
Loader.Stream.Seek(Loader.Header.Lumps[DDD_Q3MAP_VERTICES, 0])
Count = Loader.Header.Lumps[DDD_Q3MAP_VERTICES, 1]/44
Loader.Vertices = New Float[Count, 14]
For Index = 0 Until Count
' Position
Loader.Vertices[Index, 0] = Loader.Stream.ReadFloat()/64.0
Loader.Vertices[Index, 2] = Loader.Stream.ReadFloat()/(-64.0)
Loader.Vertices[Index, 1] = Loader.Stream.ReadFloat()/64.0
' Texture UV
Loader.Vertices[Index, 6] = Loader.Stream.ReadFloat()
Loader.Vertices[Index, 7] = Loader.Stream.ReadFloat()
' Lightmap UV
Loader.Vertices[Index, 8] = Loader.Stream.ReadFloat()
Loader.Vertices[Index, 9] = Loader.Stream.ReadFloat()
' Normal
Loader.Vertices[Index, 3] = Loader.Stream.ReadFloat()
Loader.Vertices[Index, 5] = -Loader.Stream.ReadFloat()
Loader.Vertices[Index, 4] = Loader.Stream.ReadFloat()
' Color
Loader.Vertices[Index, 10] = Float(Loader.Stream.ReadByte())/255.0
Loader.Vertices[Index, 11] = Float(Loader.Stream.ReadByte())/255.0
Loader.Vertices[Index, 12] = Float(Loader.Stream.ReadByte())/255.0
Loader.Vertices[Index, 13] = Float(Loader.Stream.ReadByte())/255.0
Next
' Load Indices
Loader.Stream.Seek(Loader.Header.Lumps[DDD_Q3MAP_INDICES, 0])
Count = Loader.Header.Lumps[DDD_Q3MAP_INDICES, 1]/4
Loader.Indices = New Int[Count]
Loader.Stream.Read(Loader.Indices, Count*4)
' Load Faces
Loader.Stream.Seek(Loader.Header.Lumps[DDD_Q3MAP_FACES, 0])
Count = Loader.Header.Lumps[DDD_Q3MAP_FACES, 1]/104
Loader.Faces = New TQ3Face[Count]
For Index = 0 Until Count
Loader.Faces[Index] = New TQ3Face
Loader.Faces[Index].Read(Loader, Tesselation)
Next
Loader.Stream.Close()
ChangeDir(OldDir)
Return Loader.Map
End Function
End TypeThe gamma setting in this code is wrong. Many times later I've found a ?original? code by Carmack:
//===============================================================================
static void HSVtoRGB( float h, float s, float v, float rgb[3] )
{
int i;
float f;
float p, q, t;
h *= 5;
i = floor( h );
f = h - i;
p = v * ( 1 - s );
q = v * ( 1 - s * f );
t = v * ( 1 - s * ( 1 - f ) );
switch ( i )
{
case 0:
rgb[0] = v;
rgb[1] = t;
rgb[2] = p;
break;
case 1:
rgb[0] = q;
rgb[1] = v;
rgb[2] = p;
break;
case 2:
rgb[0] = p;
rgb[1] = v;
rgb[2] = t;
break;
case 3:
rgb[0] = p;
rgb[1] = q;
rgb[2] = v;
break;
case 4:
rgb[0] = t;
rgb[1] = p;
rgb[2] = v;
break;
case 5:
rgb[0] = v;
rgb[1] = p;
rgb[2] = q;
break;
}
}
/*
===============
R_ColorShiftLightingBytes
===============
*/
static void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) {
int shift, r, g, b;
// shift the color data based on overbright range
shift = r_mapOverBrightBits->integer - tr.overbrightBits;
// shift the data based on overbright range
r = in[0] << shift;
g = in[1] << shift;
b = in[2] << shift;
// normalize by color instead of saturating to white
if ( ( r | g | b ) > 255 ) {
int max;
max = r > g ? r : g;
max = max > b ? max : b;
r = r * 255 / max;
g = g * 255 / max;
b = b * 255 / max;
}
out[0] = r;
out[1] = g;
out[2] = b;
out[3] = in[3];
}
/*
===============
R_LoadLightmaps
===============
*/
#define LIGHTMAP_SIZE 128
static void R_LoadLightmaps( lump_t *l ) {
byte *buf, *buf_p;
int len;
MAC_STATIC byte image[LIGHTMAP_SIZE*LIGHTMAP_SIZE*4];
int i, j;
float maxIntensity = 0;
double sumIntensity = 0;
len = l->filelen;
if ( !len ) {
return;
}
buf = fileBase + l->fileofs;
// we are about to upload textures
R_SyncRenderThread();
// create all the lightmaps
tr.numLightmaps = len / (LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3);
if ( tr.numLightmaps == 1 ) {
//FIXME: HACK: maps with only one lightmap turn up fullbright for some reason.
//this avoids this, but isn't the correct solution.
tr.numLightmaps++;
}
// if we are in r_vertexLight mode, we don't need the lightmaps at all
if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) {
return;
}
for ( i = 0 ; i < tr.numLightmaps ; i++ ) {
// expand the 24 bit on-disk to 32 bit
buf_p = buf + i * LIGHTMAP_SIZE*LIGHTMAP_SIZE * 3;
if ( r_lightmap->integer == 2 )
{ // color code by intensity as development tool (FIXME: check range)
for ( j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ )
{
float r = buf_p[j*3+0];
float g = buf_p[j*3+1];
float b = buf_p[j*3+2];
float intensity;
float out[3];
intensity = 0.33f * r + 0.685f * g + 0.063f * b;
if ( intensity > 255 )
intensity = 1.0f;
else
intensity /= 255.0f;
if ( intensity > maxIntensity )
maxIntensity = intensity;
HSVtoRGB( intensity, 1.00, 0.50, out );
image[j*4+0] = out[0] * 255;
image[j*4+1] = out[1] * 255;
image[j*4+2] = out[2] * 255;
image[j*4+3] = 255;
sumIntensity += intensity;
}
} else {
for ( j = 0 ; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) {
R_ColorShiftLightingBytes( &buf_p[j*3], &image[j*4] );
image[j*4+3] = 255;
}
}
tr.lightmaps[i] = R_CreateImage( va("*lightmap%d",i), image,
LIGHTMAP_SIZE, LIGHTMAP_SIZE, qfalse, qfalse, GL_CLAMP );
}
if ( r_lightmap->integer == 2 ) {
ri.Printf( PRINT_ALL, "Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) );
}
}So the original gamma correction is based on the HSV Lighting Model.
Q3_POLYGON and Q3_MESH faces are easy bring in to MiniB3D Surfaces. Q3_PATCHES are Bi Quad Patches that needs a tesselation by giving control points. Blitz3D have a fixed tesselation value, but I hope, MiniB3D will have an extra parameter like LoadBSP:TQ3Map(URL:OBject, Gamma:Float=0.0, Tesselation:Int=4).
The challenge to render Q3 Maps is the BSP algorithm. You have to detect in what leaf is the camera(using point to plane computions) and render only these faces, that is joined with the VIS Data for a leaf.
I don't know if there is a clever way to use TMesh and TSurface or using a slim version for rendering to bring up more render performence(becouse, there is no shader, brush, cubemap and etc. stuff needed)
cu olli