Thats the Q3 BSP code of my old engine:
The gamma setting in this code is wrong. Many times later I've found a ?original? code by Carmack:
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
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 Type
The 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