Proper error handling when trying to write the metafile to disk

refs gh-1

Rem
	bbdoc: Maximus module base.
	about: An abstract type for the similarities between module scopes and modules.
End Rem
Type mxModuleBase Abstract

	Field m_name:String

'#region Field accessors

	Rem
		bbdoc: Set the base's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleBase.SetName) name cannot be Null"
		m_name = name
	End Method

	Rem
		bbdoc: Get the base's name.
		returns: The base's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Return False
	End Method

End Type

Rem
	bbdoc: Maximus module scope.
End Rem
Type mxModuleScope Extends mxModuleBase

	Field m_modules:dObjectMap

	Method New()
		m_modules = New dObjectMap
	End Method

	Rem
		bbdoc: Add the given module to the scope.
		returns: True if the module was added, or False if it was not (the module is Null).
	End Rem
	Method AddModule:Int(modul:mxModule)
		If modul
			m_modules._Insert(modul.GetName(), modul)
			modul.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if the given module name is found in the scope.
		returns: True if the given module was found in the scope, or False if it was not.
	End Rem
	Method HasModule:Int(modname:String)
		Return m_modules._Contains(modname)
	End Method

	Rem
		bbdoc: Get a module with the given name.
		returns: The module with the given name, or Null if there is no module with the given name.
	End Rem
	Method GetModuleWithName:mxModule(modname:String)
		Return mxModule(m_modules._ObjectWithKey(modname))
	End Method

	Rem
		bbdoc: Load the given dJObject into the scope.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleScope(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				If dJObject(variable)
					AddModule(New mxModule.FromJSON(dJObject(variable)))
				'Else
				'	SetCommonFromVariable(variable)
				End If
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the module enumerator for the scope.
		returns: The module enumerator for the scope.
	End Rem
	Method ModuleEnumerator:TMapEnumerator()
		Return m_modules.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module.
End Rem
Type mxModule Extends mxModuleBase

	Field m_parent:mxModuleScope
	Field m_description:String
	Field m_versions:dObjectMap

	Method New()
		m_versions = New dObjectMap
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the module's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModuleScope)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the module's parent.
		returns: The module's parent.
	End Rem
	Method GetParent:mxModuleScope()
		Return m_parent
	End Method

	Rem
		bbdoc: Get the full name of the module (e.g. "modscope.module").
		returns: The full name of the module.
	End Rem
	Method GetFullName:String()
		Return m_parent.m_name + "." + m_name
	End Method

	Rem
		bbdoc: Set the module's description.
		returns: Nothing.
	End Rem
	Method SetDescription(description:String)
		m_description = description
	End Method

	Rem
		bbdoc: Get the module's description.
		returns: The module's description.
	End Rem
	Method GetDescription:String()
		Return m_description
	End Method

'#end region Field accessors

	Rem
		bbdoc: Get the installed version of the module (if it is installed).
		returns: The version of the module that is installed, or Null if the module is not installed.
	End Rem
	Method GetInstalledVersion:mxModuleVersion()
		Local version:String = mxModUtils.GetInstalledVersionFromVerID(GetFullName())
		If version
			Return New mxModuleVersion.Create(Self, version, Null)
		End If
		Return Null
	End Method

	Rem
		bbdoc: Add the given version to the module.
		returns: True if the version was added, or False if it was not (the version is Null).
	End Rem
	Method AddVersion:Int(version:mxModuleVersion)
		If version
			m_versions._Insert(version.GetName(), version)
			version.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if there is a version with the given name.
		returns: True if the version was found, or False if it was not.
	End Rem
	Method HasVersion:Int(name:String)
		Return m_versions._Contains(name)
	End Method

	Rem
		bbdoc: Get a version with the given name.
		returns: The version with the given name, or Null if there is no version with the given name.
	End Rem
	Method GetVersionWithName:mxModuleVersion(name:String)
		Return mxModuleVersion(m_versions._ObjectWithKey(name))
	End Method

	Rem
		bbdoc: Get the latest (non-dev if available) version for this module.
		returns: The latest version for this module.
		about: The latest version will be returned, or the dev version if it is the only version.
	End Rem
	Method GetLatestVersion:mxModuleVersion()
		Local hver:mxModuleVersion
		For Local ver:mxModuleVersion = EachIn VersionEnumerator()
			If ver.GetName() <> "dev"
				If Not hver Or ver.Compare(hver) = 1
					hver = ver
				End If
			End If
		Next
		If Not hver
			hver = GetVersionWithName("dev")
		End If
		Return hver
	End Method

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		If Super.SetCommonFromVariable(variable)
			Return True
		Else
			Select variable.GetName().ToLower()
				Case "desc"
					SetDescription(dValueVariable(variable).ValueAsString())
				Case "versions"
					For Local jobj:dJObject = EachIn dJObject(variable)
						AddVersion(New mxModuleVersion.FromJSON(jobj))
					Next
				Default
					Return False
			End Select
			Return True
		End If
	End Method

	Rem
		bbdoc: Load the given dJObject into the module.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModule(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the version enumerator for the module.
		returns: The version enumerator for the module.
	End Rem
	Method VersionEnumerator:TMapEnumerator()
		Return m_versions.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module version.
End Rem
Type mxModuleVersion

	Field m_parent:mxModule
	Field m_name:String, m_url:String
	Field m_dependencies:mxModuleDependencies

	Method New()
		m_dependencies = New mxModuleDependencies
	End Method

	Rem
		bbdoc: Create a module version.
		returns: Itself.
	End Rem
	Method Create:mxModuleVersion(parent:mxModule, name:String, url:String)
		SetParent(parent)
		SetName(name)
		SetUrl(url)
		Return Self
	End Method

	Rem
		bbdoc: Clone the version.
		returns: A clone of the version.
		about: If @withdeps is True (default value), the dependencies will be added as well.
	End Rem
	Method Clone:mxModuleVersion(withdeps:Int = True)
		Local v:mxModuleVersion = New mxModuleVersion.Create(m_parent, m_name, m_url)
		For Local dep:mxModuleDependency = EachIn DependencyEnumerator()
			v.m_dependencies.AddDependency(dep)
		Next
		Return v
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the version's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModule)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the version's parent.
		returns: The version's parent.
	End Rem
	Method GetParent:mxModule()
		Return m_parent
	End Method

	Rem
		bbdoc: Set the version's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleVersion.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the version's name.
		returns: The version's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the version's url.
		returns: Nothing.
	End Rem
	Method SetUrl(url:String)
		'Assert url, "(mxModuleVersion.SetUrl) url cannot be Null!"
		m_url = url
	End Method

	Rem
		bbdoc: Get the version's url.
		returns: The version's url.
	End Rem
	Method GetUrl:String()
		Return m_url
	End Method

	Rem
		bbdoc: Get the temporary fetch path for the version's source archive.
		returns: The temporary file path for the version's source archive.
	End Rem
	Method GetTemporaryFilePath:String()
		Return "tmp/" + m_parent.GetFullName() + "-" + m_name + ".zip"
	End Method

	Rem
		bbdoc: Set the version's dependencies.
		returns: Nothing.
	End Rem
	Method SetDependencies(dependencies:mxModuleDependencies)
		m_dependencies = dependencies
	End Method

	Rem
		bbdoc: Get the version's dependencies.
		returns: The version's dependencies.
	End Rem
	Method GetDependencies:mxModuleDependencies()
		Return m_dependencies
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "url"
				SetUrl(dValueVariable(variable).ValueAsString())
			Case "deps"
				m_dependencies.FromJSON(dJArray(variable))
			Default
				Return False
		End Select
		Return True
	End Method

	Rem
		bbdoc: Load the given dJObject into the version.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleVersion(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the dependency enumerator for the version.
		returns: The dependency enumerator for the version.
	End Rem
	Method DependencyEnumerator:TMapEnumerator()
		Return m_dependencies.DependencyEnumerator()
	End Method

	Rem
		bbdoc: Compare the version with the given version.
		returns: 0 if the two versions are the same (same in version number alone), 1 if this version is greater than the given, or -1 if the given version is greater than this version.
	End Rem
	Method Compare:Int(with:Object)
		Local ver:mxModuleVersion = mxModuleVersion(with)
		If ver
			Local version1:mxVersionObject = New mxVersionObject.Parse(m_name)
			Local version2:mxVersionObject = New mxVersionObject.Parse(ver.m_name)
			Return version1.Compare(version2)
		End If
		Return Super.Compare(with)
	End Method

	Rem
		bbdoc: Fetch the version's source archive.
		returns: Nothing.
	End Rem
	Method FetchSourceArchive()
		Local file:String = GetTemporaryFilePath()
		logger.LogMessage("fetching: " + GetUrl() + " -> " + file + "    ", False)
		If FileType(file) = FILETYPE_NONE
			Local stream:TStream = WriteFileExplicitly(file)
			If stream
				Local request:TRESTRequest = New TRESTRequest, response:TRESTResponse
				request.SetProgressCallback(_ProgressCallback, New _mxProgressStore)
				request.SetStream(stream)
				Try
					response = request.Call(GetUrl(), ["User-Agent: " + mainapp.m_useragent], "GET")
				Catch e:Object
					stream.Close()
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", [e.ToString()]))
				End Try
				stream.Close()
				If response.responseCode = 200
					logger.LogMessage(_s("message.fetch.done", [String(response.responseCode)]))
				Else
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", ["Bad response code: " + String(response.responseCode)]))
				End If
			Else
				ThrowError(_s("error.writeperms", [file]))
			End If
		Else
			logger.LogMessage(_s("message.fetch.alreadyfetched"))
		End If
	End Method

	Rem
		bbdoc: Progress callback for source archive fetching.
		returns: Zero (no error).
	End Rem
	Function _ProgressCallback:Int(data:Object, dltotal:Double, dlnow:Double, ultotal:Double, ulnow:Double)
		Local store:_mxProgressStore = _mxProgressStore(data)
		Local prog:Int = (dlnow / dltotal) * 100
		If prog > store.m_progress + 5
			store.m_progress = prog
			WriteStdOut(".")
		End If
		Return 0
	End Function

End Type

Rem
	bbdoc: A object representing a version, not to be confused with mxModuleVersion
	about: Supported formats are 1, 1.1 and 1.1.1.
	Do note that because the version is represented as a Double there's a change
	the precision of the final number is off.

	1.1.2 Would be converted to 1.001002 but when converted to a Double it results
	in 1.0010019999999999.

	1.1.12 However would be converted to 1.001012 and when converted to a Double it
	results in 1.0010120000000000.

	A version string of 'dev' will be represented as 99999
End Rem
Type mxVersionObject

	Field m_version:Double

	Rem
		bbdoc: Parse a version string
		returns: Itself.
	End Rem
	Method Parse:mxVersionObject(version:String)
		If version = "dev"
			m_version = 99999
			Return Self
		End If

		Local parts:String[] = version.Split(".")
		Local converted:String = parts[0]
		If parts.Length > 1
			converted:+"."
			For Local part:String = EachIn parts[1..]
				While part.Length <> 3
					part = "0" + part
				WEnd
				converted:+part
			Next

			'Make sure we format to x.x.x
			If parts.Length = 2
				converted:+"000"
			End If
		End If

		m_version = converted.ToDouble()
		Return Self
	End Method

	Rem
		bbdoc: Compare version numbers
	End Rem
	Method Compare:Int(withObject:Object)
		Local version:mxVersionObject = mxVersionObject(withObject)
		If version
			If m_version = version.m_version
				Return 0
			Else If m_version > version.m_version
				Return 1
			Else If m_version < version.m_version
				Return - 1
			End If
		End If
		Return Super.Compare(withObject)
	End Method
End Type

Rem
	bbdoc: Maximus metafile handler
End Rem
Type mxMetaFile

	Field m_metafile:String
	Field m_scope:String
	Field m_name:String
	Field m_version:String

	Rem
		bbdoc: Create a metafile handler.
		returns: Itself.
	End Rem
	Method Create:mxMetaFile(metafile:String)
		SetMetaFile(metafile)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the metafile file path.
		returns: Nothing.
	End Rem
	Method SetMetaFile(metafile:String)
		m_metafile = metafile
	End Method

	Rem
		bbdoc: Get the metafile path.
		returns: The metafile path.
	End Rem
	Method GetMetaFile:String()
		Return m_metafile
	End Method

	Rem
		bbdoc: Set metadata
		returns: Nothing.
	End Rem
	Method SetMetaData(scope:String, name:String, version:String)
		Self.m_scope = scope
		Self.m_name = name
		Self.m_version = version
	End Method

'#end region Field accessors

	Rem
		bbdoc: Load the configuration.
		returns: Nothing.
	End Rem
	Method Load()
		If FileType(GetMetaFile()) = FILETYPE_FILE
			Try
				Local contents:String = LoadText(GetMetaFile()).Trim()
				Local parts:String[] = contents.Split("/")

				If parts.Length <> 2
					Throw _s("error.load.meta.invalid_format") ;
				End If

				Local modinfo:String[] = parts[0].Split(".")
				m_scope = modinfo[0]
				m_name = modinfo[1]
				m_version = parts[1]
			Catch e:Object
				logger.LogError(_s("error.load.metafile.parse", [GetMetaFile()]))
				logger.LogError(_s("error.load.metafile.parse", [e.ToString()]))
			End Try
		Else
			logger.LogWarning(_s("error.load.metafile.notfound", [GetMetaFile()]))
		End If
	End Method

	Rem
		bbdoc: Save the configuration
		returns: Nothing.
	End Rem
	Method Save()
		If m_scope.Length = 0 Or m_name.Length = 0 Or m_version.Length = 0
			ThrowError(_s("error.save.metafile.incomplete", [m_scope, m_name, m_version]))
		End If

		Local file:String = GetMetaFile()
		Local stream:TStream = WriteFileExplicitly(file)
		If stream
			stream.WriteString(m_scope + "." + m_name + "/" + m_version)
			stream.Close
		Else
			ThrowError(_s("error.writeperms", [file]))
		End If
	End Method
End Type

Rem
	bbdoc: Maximus module base.
	about: An abstract type for the similarities between module scopes and modules.
End Rem
Type mxModuleBase Abstract

	Field m_name:String

'#region Field accessors

	Rem
		bbdoc: Set the base's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleBase.SetName) name cannot be Null"
		m_name = name
	End Method

	Rem
		bbdoc: Get the base's name.
		returns: The base's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Return False
	End Method

End Type

Rem
	bbdoc: Maximus module scope.
End Rem
Type mxModuleScope Extends mxModuleBase

	Field m_modules:dObjectMap

	Method New()
		m_modules = New dObjectMap
	End Method

	Rem
		bbdoc: Add the given module to the scope.
		returns: True if the module was added, or False if it was not (the module is Null).
	End Rem
	Method AddModule:Int(modul:mxModule)
		If modul
			m_modules._Insert(modul.GetName(), modul)
			modul.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if the given module name is found in the scope.
		returns: True if the given module was found in the scope, or False if it was not.
	End Rem
	Method HasModule:Int(modname:String)
		Return m_modules._Contains(modname)
	End Method

	Rem
		bbdoc: Get a module with the given name.
		returns: The module with the given name, or Null if there is no module with the given name.
	End Rem
	Method GetModuleWithName:mxModule(modname:String)
		Return mxModule(m_modules._ObjectWithKey(modname))
	End Method

	Rem
		bbdoc: Load the given dJObject into the scope.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleScope(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				If dJObject(variable)
					AddModule(New mxModule.FromJSON(dJObject(variable)))
				'Else
				'	SetCommonFromVariable(variable)
				End If
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the module enumerator for the scope.
		returns: The module enumerator for the scope.
	End Rem
	Method ModuleEnumerator:TMapEnumerator()
		Return m_modules.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module.
End Rem
Type mxModule Extends mxModuleBase

	Field m_parent:mxModuleScope
	Field m_description:String
	Field m_versions:dObjectMap

	Method New()
		m_versions = New dObjectMap
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the module's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModuleScope)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the module's parent.
		returns: The module's parent.
	End Rem
	Method GetParent:mxModuleScope()
		Return m_parent
	End Method

	Rem
		bbdoc: Get the full name of the module (e.g. "modscope.module").
		returns: The full name of the module.
	End Rem
	Method GetFullName:String()
		Return m_parent.m_name + "." + m_name
	End Method

	Rem
		bbdoc: Set the module's description.
		returns: Nothing.
	End Rem
	Method SetDescription(description:String)
		m_description = description
	End Method

	Rem
		bbdoc: Get the module's description.
		returns: The module's description.
	End Rem
	Method GetDescription:String()
		Return m_description
	End Method

'#end region Field accessors

	Rem
		bbdoc: Get the installed version of the module (if it is installed).
		returns: The version of the module that is installed, or Null if the module is not installed.
	End Rem
	Method GetInstalledVersion:mxModuleVersion()
		Local version:String = mxModUtils.GetInstalledVersionFromVerID(GetFullName())
		If version
			Return New mxModuleVersion.Create(Self, version, Null)
		End If
		Return Null
	End Method

	Rem
		bbdoc: Add the given version to the module.
		returns: True if the version was added, or False if it was not (the version is Null).
	End Rem
	Method AddVersion:Int(version:mxModuleVersion)
		If version
			m_versions._Insert(version.GetName(), version)
			version.SetParent(Self)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Check if there is a version with the given name.
		returns: True if the version was found, or False if it was not.
	End Rem
	Method HasVersion:Int(name:String)
		Return m_versions._Contains(name)
	End Method

	Rem
		bbdoc: Get a version with the given name.
		returns: The version with the given name, or Null if there is no version with the given name.
	End Rem
	Method GetVersionWithName:mxModuleVersion(name:String)
		Return mxModuleVersion(m_versions._ObjectWithKey(name))
	End Method

	Rem
		bbdoc: Get the latest (non-dev if available) version for this module.
		returns: The latest version for this module.
		about: The latest version will be returned, or the dev version if it is the only version.
	End Rem
	Method GetLatestVersion:mxModuleVersion()
		Local hver:mxModuleVersion
		For Local ver:mxModuleVersion = EachIn VersionEnumerator()
			If ver.GetName() <> "dev"
				If Not hver Or ver.Compare(hver) = 1
					hver = ver
				End If
			End If
		Next
		If Not hver
			hver = GetVersionWithName("dev")
		End If
		Return hver
	End Method

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		If Super.SetCommonFromVariable(variable)
			Return True
		Else
			Select variable.GetName().ToLower()
				Case "desc"
					SetDescription(dValueVariable(variable).ValueAsString())
				Case "versions"
					For Local jobj:dJObject = EachIn dJObject(variable)
						AddVersion(New mxModuleVersion.FromJSON(jobj))
					Next
				Default
					Return False
			End Select
			Return True
		End If
	End Method

	Rem
		bbdoc: Load the given dJObject into the module.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModule(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the version enumerator for the module.
		returns: The version enumerator for the module.
	End Rem
	Method VersionEnumerator:TMapEnumerator()
		Return m_versions.ValueEnumerator()
	End Method

End Type

Rem
	bbdoc: Maximus module version.
End Rem
Type mxModuleVersion

	Field m_parent:mxModule
	Field m_name:String, m_url:String
	Field m_dependencies:mxModuleDependencies

	Method New()
		m_dependencies = New mxModuleDependencies
	End Method

	Rem
		bbdoc: Create a module version.
		returns: Itself.
	End Rem
	Method Create:mxModuleVersion(parent:mxModule, name:String, url:String)
		SetParent(parent)
		SetName(name)
		SetUrl(url)
		Return Self
	End Method

	Rem
		bbdoc: Clone the version.
		returns: A clone of the version.
		about: If @withdeps is True (default value), the dependencies will be added as well.
	End Rem
	Method Clone:mxModuleVersion(withdeps:Int = True)
		Local v:mxModuleVersion = New mxModuleVersion.Create(m_parent, m_name, m_url)
		For Local dep:mxModuleDependency = EachIn DependencyEnumerator()
			v.m_dependencies.AddDependency(dep)
		Next
		Return v
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the version's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:mxModule)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the version's parent.
		returns: The version's parent.
	End Rem
	Method GetParent:mxModule()
		Return m_parent
	End Method

	Rem
		bbdoc: Set the version's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		Assert name, "(mxModuleVersion.SetName) name cannot be Null!"
		m_name = name
	End Method

	Rem
		bbdoc: Get the version's name.
		returns: The version's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the version's url.
		returns: Nothing.
	End Rem
	Method SetUrl(url:String)
		'Assert url, "(mxModuleVersion.SetUrl) url cannot be Null!"
		m_url = url
	End Method

	Rem
		bbdoc: Get the version's url.
		returns: The version's url.
	End Rem
	Method GetUrl:String()
		Return m_url
	End Method

	Rem
		bbdoc: Get the temporary fetch path for the version's source archive.
		returns: The temporary file path for the version's source archive.
	End Rem
	Method GetTemporaryFilePath:String()
		Return "tmp/" + m_parent.GetFullName() + "-" + m_name + ".zip"
	End Method

	Rem
		bbdoc: Set the version's dependencies.
		returns: Nothing.
	End Rem
	Method SetDependencies(dependencies:mxModuleDependencies)
		m_dependencies = dependencies
	End Method

	Rem
		bbdoc: Get the version's dependencies.
		returns: The version's dependencies.
	End Rem
	Method GetDependencies:mxModuleDependencies()
		Return m_dependencies
	End Method

'#end region Field accessors

	Rem
		bbdoc: Set a common field from the given variable.
		returns: True if the given variable was handled, or False if it was not.
	End Rem
	Method SetCommonFromVariable:Int(variable:dVariable)
		Select variable.GetName().ToLower()
			Case "url"
				SetUrl(dValueVariable(variable).ValueAsString())
			Case "deps"
				m_dependencies.FromJSON(dJArray(variable))
			Default
				Return False
		End Select
		Return True
	End Method

	Rem
		bbdoc: Load the given dJObject into the version.
		returns: Itself, or Null if @root is Null.
	End Rem
	Method FromJSON:mxModuleVersion(root:dJObject)
		If root
			SetName(root.GetName())
			For Local variable:dVariable = EachIn root
				SetCommonFromVariable(variable)
			Next
			Return Self
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the dependency enumerator for the version.
		returns: The dependency enumerator for the version.
	End Rem
	Method DependencyEnumerator:TMapEnumerator()
		Return m_dependencies.DependencyEnumerator()
	End Method

	Rem
		bbdoc: Compare the version with the given version.
		returns: 0 if the two versions are the same (same in version number alone), 1 if this version is greater than the given, or -1 if the given version is greater than this version.
	End Rem
	Method Compare:Int(with:Object)
		Local ver:mxModuleVersion = mxModuleVersion(with)
		If ver
			Local version1:mxVersionObject = New mxVersionObject.Parse(m_name)
			Local version2:mxVersionObject = New mxVersionObject.Parse(ver.m_name)
			Return version1.Compare(version2)
		End If
		Return Super.Compare(with)
	End Method

	Rem
		bbdoc: Fetch the version's source archive.
		returns: Nothing.
	End Rem
	Method FetchSourceArchive()
		Local file:String = GetTemporaryFilePath()
		logger.LogMessage("fetching: " + GetUrl() + " -> " + file + "    ", False)
		If FileType(file) = FILETYPE_NONE
			Local stream:TStream = WriteFileExplicitly(file)
			If stream
				Local request:TRESTRequest = New TRESTRequest, response:TRESTResponse
				request.SetProgressCallback(_ProgressCallback, New _mxProgressStore)
				request.SetStream(stream)
				Try
					response = request.Call(GetUrl(), ["User-Agent: " + mainapp.m_useragent], "GET")
				Catch e:Object
					stream.Close()
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", [e.ToString()]))
				End Try
				stream.Close()
				If response.responseCode = 200
					logger.LogMessage(_s("message.fetch.done", [String(response.responseCode)]))
				Else
					DeleteFile(file)
					logger.LogMessage("")
					ThrowError(_s("error.fetch.archive", ["Bad response code: " + String(response.responseCode)]))
				End If
			Else
				ThrowError(_s("error.writeperms", [file]))
			End If
		Else
			logger.LogMessage(_s("message.fetch.alreadyfetched"))
		End If
	End Method

	Rem
		bbdoc: Progress callback for source archive fetching.
		returns: Zero (no error).
	End Rem
	Function _ProgressCallback:Int(data:Object, dltotal:Double, dlnow:Double, ultotal:Double, ulnow:Double)
		Local store:_mxProgressStore = _mxProgressStore(data)
		Local prog:Int = (dlnow / dltotal) * 100
		If prog > store.m_progress + 5
			store.m_progress = prog
			WriteStdOut(".")
		End If
		Return 0
	End Function

End Type

Rem
	bbdoc: A object representing a version, not to be confused with mxModuleVersion
	about: Supported formats are 1, 1.1 and 1.1.1.
	Do note that because the version is represented as a Double there's a change
	the precision of the final number is off.

	1.1.2 Would be converted to 1.001002 but when converted to a Double it results
	in 1.0010019999999999.

	1.1.12 However would be converted to 1.001012 and when converted to a Double it
	results in 1.0010120000000000.

	A version string of 'dev' will be represented as 99999
End Rem
Type mxVersionObject

	Field m_version:Double

	Rem
		bbdoc: Parse a version string
		returns: Itself.
	End Rem
	Method Parse:mxVersionObject(version:String)
		If version = "dev"
			m_version = 99999
			Return Self
		End If

		Local parts:String[] = version.Split(".")
		Local converted:String = parts[0]
		If parts.Length > 1
			converted:+"."
			For Local part:String = EachIn parts[1..]
				While part.Length <> 3
					part = "0" + part
				WEnd
				converted:+part
			Next

			'Make sure we format to x.x.x
			If parts.Length = 2
				converted:+"000"
			End If
		End If

		m_version = converted.ToDouble()
		Return Self
	End Method

	Rem
		bbdoc: Compare version numbers
	End Rem
	Method Compare:Int(withObject:Object)
		Local version:mxVersionObject = mxVersionObject(withObject)
		If version
			If m_version = version.m_version
				Return 0
			Else If m_version > version.m_version
				Return 1
			Else If m_version < version.m_version
				Return - 1
			End If
		End If
		Return Super.Compare(withObject)
	End Method
End Type

Rem
	bbdoc: Maximus metafile handler
End Rem
Type mxMetaFile

	Field m_metafile:String
	Field m_scope:String
	Field m_name:String
	Field m_version:String

	Rem
		bbdoc: Create a metafile handler.
		returns: Itself.
	End Rem
	Method Create:mxMetaFile(metafile:String)
		SetMetaFile(metafile)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the metafile file path.
		returns: Nothing.
	End Rem
	Method SetMetaFile(metafile:String)
		m_metafile = metafile
	End Method

	Rem
		bbdoc: Get the metafile path.
		returns: The metafile path.
	End Rem
	Method GetMetaFile:String()
		Return m_metafile
	End Method

	Rem
		bbdoc: Set metadata
		returns: Nothing.
	End Rem
	Method SetMetaData(scope:String, name:String, version:String)
		Self.m_scope = scope
		Self.m_name = name
		Self.m_version = version
	End Method

'#end region Field accessors

	Rem
		bbdoc: Load the configuration.
		returns: Nothing.
	End Rem
	Method Load()
		If FileType(GetMetaFile()) = FILETYPE_FILE
			Try
				Local contents:String = LoadText(GetMetaFile()).Trim()
				Local parts:String[] = contents.Split("/")

				If parts.Length <> 2
					Throw _s("error.load.meta.invalid_format") ;
				End If

				Local modinfo:String[] = parts[0].Split(".")
				m_scope = modinfo[0]
				m_name = modinfo[1]
				m_version = parts[1]
			Catch e:Object
				logger.LogError(_s("error.load.metafile.parse", [GetMetaFile()]))
				logger.LogError(_s("error.load.metafile.parse", [e.ToString()]))
			End Try
		Else
			logger.LogWarning(_s("error.load.metafile.notfound", [GetMetaFile()]))
		End If
	End Method

	Rem
		bbdoc: Save the configuration
		returns: Nothing.
	End Rem
	Method Save()
		If m_scope.Length = 0 Or m_name.Length = 0 Or m_version.Length = 0
			ThrowError(_s("error.save.metafile.incomplete", [m_scope, m_name, m_version]))
		End If

		Local stream:TStream = WriteFileExplicitly(GetMetaFile())
		stream.WriteString(m_scope + "." + m_name + "/" + m_version)
		stream.Close
	End Method
End Type


TBSPModel trees now render as polygons instead of triangles.

Strict

Rem
	bbdoc: OpenGL 1.1 driver for MaxB3D
End Rem
Module MaxB3D.GLDriver
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import Prime.GLMax2DEx

Private
Function ModuleLog(message$)
	TMaxB3DLogger.Write "gldriver",message
End Function

Public

Global GL_LIGHT[]=[GL_LIGHT0,GL_LIGHT1,GL_LIGHT2,GL_LIGHT3,GL_LIGHT4,GL_LIGHT5,GL_LIGHT6,GL_LIGHT7]

Type TGLMaxB3DDriver Extends TMaxB3DDriver
	Field _currentdata:TMaxB3DShaderData= New TMaxB3DShaderData

	Method SetGraphics(g:TGraphics)
		Super.SetGraphics g
		If g<>Null Startup
	End Method

	Function BindTexture(index,tex)
		If index=-1
			For Local i=0 To 7
				BindTexture i,tex
			Next
			Return
		EndIf
		Global currenttexture[] = [-1,-1,-1,-1,-1,-1,-1,-1]
		If tex<>currenttexture[index]
			glBindTexture GL_TEXTURE_2D,tex
			currenttexture[index]=tex
		EndIf
	End Function

	Method Startup()
		Global _firsttime=True
		If _firsttime
			Local caps:TGLCaps=TGLCaps(_caps)
			ModuleLog "Initializing GL driver"
			ModuleLog "Vendor:   "+String.FromCString(Byte Ptr(glGetString(GL_VENDOR)))
			ModuleLog "Renderer: "+String.FromCString(Byte Ptr(glGetString(GL_RENDERER)))
			ModuleLog "Version:  "+String.FromCString(Byte Ptr(glGetString(GL_VERSION)))
			ModuleLog "Extensions supported: "+" ".Join(caps.Extensions)
			EndMax2D
			_firsttime=False
		EndIf
		EnableStates
	End Method

	Function EnableStates()
		glEnable GL_LIGHTING
		glEnable GL_DEPTH_TEST
		glEnable GL_CULL_FACE
		glEnable GL_SCISSOR_TEST

		glEnable GL_NORMALIZE

		glEnableClientState GL_VERTEX_ARRAY
		glEnableClientState GL_COLOR_ARRAY
		glEnableClientState GL_NORMAL_ARRAY

		glFrontFace GL_CW

		glLightModeli GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR
		glLightModeli GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE

		glClearDepth 1.0
		glDepthFunc GL_LEQUAL
		glHint GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST

		glAlphaFunc GL_GEQUAL,0.5

		BindTexture -1,0
	End Function

	Method MakeBuffer:TBuffer(src:Object,width,height,flags)
		Local buffer:TBuffer
		Select True
		Case TTextureFrame(src)<>Null
			buffer=TGLBuffer(TTextureFrame(src)._buffer)
			If buffer=Null
				Local res:TGLTextureRes=UpdateTextureRes(TTextureFrame(src),0)
				buffer=TGLMax2DExDriver(_parent).MakeGLBuffer(res._id,width,height,flags)
			EndIf
		Default
			buffer=TMax2DExDriver(_parent).MakeBuffer(src,width,height,flags)
		End Select
		Return buffer
	End Method

	Method Abbr$()
		Return "gl"
	End Method

	Method GetCaps:TCaps()
		Local caps:TGLCaps=New TGLCaps
		caps.Extensions=String.FromCString(glGetString(GL_EXTENSIONS)).Split(" ")
		If caps.HasExtension("GL_ARB_point_sprite")<>-1
			caps.PointSprites=True
			glGetFloatv GL_POINT_SIZE_MAX_ARB, Varptr caps.MaxPointSize
		EndIf
		Return caps
	End Method

	Method SetMax2D(enable)
		If enable
			glPopClientAttrib
			glPopAttrib
			If _shaderdriver _shaderdriver.Use(Null,Null)
			TGLMax2DExDriver(_parent).ResetGLContext _current
			glMatrixMode GL_TEXTURE
			glLoadIdentity
			glMatrixMode GL_COLOR
			glPopMatrix
		Else
			glPushAttrib GL_ALL_ATTRIB_BITS
			glPushClientAttrib GL_CLIENT_ALL_ATTRIB_BITS
			glMatrixMode GL_TEXTURE
			glPushMatrix
			glMatrixMode GL_COLOR
			glPushMatrix

			EnableStates()
		EndIf
	End Method
	Method SetCamera(camera:TCamera, config:TWorldConfig)
		If config.Dither
			glEnable GL_DITHER
		Else
			glDisable GL_DITHER
		EndIf

		Local vy#=config.Height-camera._viewheight-camera._viewy
		glViewport(camera._viewx,vy,camera._viewwidth,camera._viewheight)
		glScissor(camera._viewx,vy,camera._viewwidth,camera._viewheight)
		glClearColor(camera._brush._r,camera._brush._g,camera._brush._b,1.0)

		If camera._clsmode&CLSMODE_COLOR And camera._clsmode&CLSMODE_DEPTH
			glDepthMask GL_TRUE
			glClear GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT
		Else
			If camera._clsmode&CLSMODE_COLOR
				glClear GL_COLOR_BUFFER_BIT
			Else
				If camera._clsmode&CLSMODE_DEPTH
					glDepthMask GL_TRUE
					glClear GL_DEPTH_BUFFER_BIT
				EndIf
			EndIf
		EndIf

		If camera._fogmode<>FOGMODE_NONE
			glEnable GL_FOG
			glFogi GL_FOG_MODE,GL_LINEAR
			glFogf GL_FOG_START,camera._fognear
			glFogf GL_FOG_END,camera._fogfar
			Local rgb#[]=[camera._fogr,camera._fogg,camera._fogb]
			glFogfv GL_FOG_COLOR,rgb
		Else
			glDisable GL_FOG
		EndIf

		camera.UpdateMatrices()

		glMatrixMode GL_PROJECTION
		glLoadMatrixf camera._projection.ToPtr()
		glMatrixMode GL_MODELVIEW
		glLoadMatrixf camera._modelview.ToPtr()

		' Temporary hack
		glGetFloatv GL_PROJECTION_MATRIX,camera._projection._m
		camera._frustum=TFrustum.Extract(camera._modelview,camera._projection)
	End Method

	Method SetLight(light:TLight,index)
		If light=Null
			glDisable GL_LIGHT[index]
			Return
		Else
			If light._hidden=True
				glDisable GL_LIGHT[index]
				Return False
			EndIf
		EndIf

		glEnable GL_LIGHT[index]

		glPushMatrix
		glMultMatrixf light.GetMatrix(True).ToPtr()

		Local white_light#[]=[1.0,1.0,1.0,1.0]
		glLightfv GL_LIGHT[index],GL_SPECULAR,white_light

		Local z#=1.0
		Local w#=0.0
		If light._mode>LIGHT_DIRECTIONAL
			z=0.0
			w=1.0
		EndIf

		Local rgba#[]=[light._brush._r,light._brush._g,light._brush._b,1.0]
		Local pos#[]=[0.0,0.0,-z,w]

		glLightfv GL_LIGHT[index],GL_POSITION,pos
		glLightfv GL_LIGHT[index],GL_DIFFUSE,rgba

		If light._mode<>LIGHT_DIRECTIONAL
			Local light_range#[]=[0.0]
			Local range#[]=[light._range]
			glLightfv GL_LIGHT[index],GL_CONSTANT_ATTENUATION,light_range
			glLightfv GL_LIGHT[index],GL_LINEAR_ATTENUATION,range
		EndIf

		If light._mode=LIGHT_SPOT
			Local dir#[]=[0.0,0.0,-1.0]
			Local outer#[]=[light._outer/2.0]
			Local exponent#[]=[10.0]
			glLightfv GL_LIGHT[index],GL_SPOT_DIRECTION,dir
			glLightfv GL_LIGHT[index],GL_SPOT_CUTOFF,outer
			glLightfv GL_LIGHT[index],GL_SPOT_EXPONENT,exponent
		EndIf

		glPopMatrix
	End Method

	Method SetBrush(brush:TBrush,hasalpha,config:TWorldConfig)
		Local alpha_test

		Local ambient#[]=[config.AmbientRed/255.0,config.AmbientGreen/255.0,config.AmbientBlue/255.0]

		If hasalpha
			glEnable GL_BLEND
			glDepthMask GL_FALSE
		Else
			glDisable GL_BLEND
			glDepthMask GL_TRUE
		EndIf

		Select brush._blend
			Case BLEND_NONE,BLEND_ALPHA
				glBlendFunc GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA
			Case BLEND_MULTIPLY
				glBlendFunc GL_DST_COLOR,GL_ZERO
			Case BLEND_ADD
				glBlendFunc GL_SRC_ALPHA,GL_ONE
		End Select

		If brush._fx&FX_FULLBRIGHT
			ambient[0]=1.0
			ambient[1]=1.0
			ambient[2]=1.0
		EndIf

		If brush._fx&FX_VERTEXCOLOR
			glEnable GL_COLOR_MATERIAL
		Else
			glDisable GL_COLOR_MATERIAL
		EndIf

		If brush._fx&FX_FLATSHADED
			glShadeModel GL_FLAT
		Else
			glShadeModel GL_SMOOTH
		EndIf

		If brush._fx&FX_NOFOG
			glDisable GL_FOG
		EndIf

		If brush._fx&FX_NOCULLING
			glDisable GL_CULL_FACE
		Else
			glEnable GL_CULL_FACE
		EndIf

		If brush._fx&FX_WIREFRAME Or config.Wireframe
			glPolygonMode GL_FRONT_AND_BACK,GL_LINE
		Else
			glPolygonMode GL_FRONT_AND_BACK,GL_FILL
		EndIf

		Local no_mat#[]=[0.0,0.0]
		Local mat_ambient#[]=[brush._r,brush._g,brush._b,brush._a]
		Local mat_diffuse#[]=[brush._r,brush._g,brush._b,brush._a]
		Local mat_specular#[]=[brush._shine,brush._shine,brush._shine,brush._shine]
		Local mat_shininess#[]=[100.0]

		glMaterialfv GL_FRONT,GL_AMBIENT,mat_ambient
		glMaterialfv GL_FRONT,GL_DIFFUSE,mat_diffuse
		glMaterialfv GL_FRONT,GL_SPECULAR,mat_specular
		glMaterialfv GL_FRONT,GL_SHININESS,mat_shininess
		glLightModelfv GL_LIGHT_MODEL_AMBIENT,ambient

		For Local i=0 To 7
			glActiveTextureARB GL_TEXTURE0+i

			Local texture:TTexture=brush._texture[i]
			If texture=Null Or texture._blend=BLEND_NONE
				glDisable GL_TEXTURE_2D
				BindTexture i,0
				Continue
			EndIf

			Local texres:TGLTextureRes=TGLTextureRes(UpdateTextureRes(texture._frame[brush._textureframe[i]],texture._flags))

			glEnable GL_TEXTURE_2D

			BindTexture i,texres._id

			glMatrixMode GL_TEXTURE
			glLoadIdentity
			glTranslatef texture._px,-texture._py,0
			glScalef -texture._sx,texture._sy,1

			alpha_test :| texture._flags&TEXTURE_ALPHA Or texture._flags&TEXTURE_MASKED

			If texture._flags&TEXTURE_MIPMAP
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
			EndIf

			If texture._flags&TEXTURE_CLAMPU
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT
			EndIf

			If texture._flags&TEXTURE_CLAMPV
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT
			EndIf

			If texture._flags&TEXTURE_SPHEREMAP
				glEnable GL_TEXTURE_GEN_S
				glEnable GL_TEXTURE_GEN_T
				glTexGeni GL_S,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP
				glTexGeni GL_T,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP
			Else
				glDisable GL_TEXTURE_GEN_S
				glDisable GL_TEXTURE_GEN_T
			EndIf

			Select texture._blend
			Case BLEND_NONE glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE)
			Case BLEND_ALPHA 	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE)
			Case BLEND_MULTIPLY glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE)
			'Case BLEND_MULTIPLY glTexEnvf(GL_TEXTURE_ENV,GL_COMBINE_RGB_EXT,GL_MODULATE)
			Case BLEND_ADD glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_ADD)
			Case BLEND_DOT3
				glTexEnvf GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT
				glTexEnvf GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_DOT3_RGB_EXT
			Case BLEND_MULTIPLY2
				glTexEnvi GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE
				glTexEnvi GL_TEXTURE_ENV,GL_COMBINE_RGB,GL_MODULATE
				glTexEnvi GL_TEXTURE_ENV,GL_RGB_SCALE,2.0
			Default glTexEnvf GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE
			End Select
		Next

		If alpha_test
			glEnable GL_ALPHA_TEST
		Else
			glDisable GL_ALPHA_TEST
		EndIf

		If _shaderdriver _shaderdriver.Use(brush._shader,_currentdata)
	End Method

	Method RenderSurface(resource:TSurfaceRes,brush:TBrush)
		Local res:TGLSurfaceRes=TGLSurfaceRes(resource)

		For Local i=0 To 7
			glClientActiveTextureARB GL_TEXTURE0+i

			Local texture:TTexture=brush._texture[i]
			If texture=Null
				glDisableClientState GL_TEXTURE_COORD_ARRAY
				Continue
			EndIf

			glEnableClientState GL_TEXTURE_COORD_ARRAY
			glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[4+texture._coords]
			glTexCoordPointer 2,GL_FLOAT,0,Null
		Next

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[0]
		glVertexPointer 3,GL_FLOAT,0,Null

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[1]
		glNormalPointer GL_FLOAT,0,Null

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[2]
		glColorPointer 4,GL_FLOAT,0,Null

		glBindBufferARB GL_ELEMENT_ARRAY_BUFFER_ARB,res._vbo[3]
		glDrawElements GL_TRIANGLES,res._trianglecnt*3,GL_UNSIGNED_INT,Null

		Return res._trianglecnt
	End Method

	Method BeginEntityRender(entity:TEntity)
		If entity._order<>0
			glDisable GL_DEPTH_TEST
			glDepthMask GL_FALSE
		Else
			glEnable GL_DEPTH_TEST
			glDepthMask GL_TRUE
		EndIf

		glMatrixMode GL_MODELVIEW
		glPushMatrix
		glMultMatrixf entity.GetMatrix(True,False).ToPtr()

		Global matrix:TMatrix=New TMatrix
		glGetFloatv GL_MODELVIEW_MATRIX,matrix._m
	End Method
	Method EndEntityRender(entity:TEntity)
		glMatrixMode GL_MODELVIEW
		glPopMatrix
	End Method

	Method RenderFlat(flat:TFlat)
		Local x#,y#,z#
		flat.GetScale x,y,z,True

		glBegin GL_QUADS
			glNormal3f 0,1,0
			glTexCoord2f 0,0
			glVertex3f -1,0, 1
			glTexCoord2f x,0
			glVertex3f 1, 0, 1
			glTexCoord2f x,z
			glVertex3f 1, 0, -1
			glTexCoord2f 0,z
			glVertex3f -1,0, -1
		glEnd
		Return 2
	End Method

	Method RenderSprite(sprite:TSprite)
		If False
			glPointParameterfvARB GL_POINT_DISTANCE_ATTENUATION_ARB, [0.0, 0.0, 1.0]

			glPointSize 1.0

			glPointParameterfARB GL_POINT_FADE_THRESHOLD_SIZE_ARB,60.0
			glPointParameterfARB GL_POINT_SIZE_MIN_ARB,0.0
			glPointParameterfARB GL_POINT_SIZE_MAX_ARB,9999.0

			glTexEnvf GL_POINT_SPRITE_ARB,GL_COORD_REPLACE_ARB,GL_TRUE

			glEnable GL_POINT_SPRITE_ARB

			glBegin GL_POINTS
			glVertex3f 0,0,0
			glEnd

			glDisable GL_POINT_SPRITE_ARB
		Else
			glBegin GL_QUADS
				glNormal3f 0,0,-1
				glTexCoord2f 0,1
				glVertex3f 1, -1, 0
				glTexCoord2f 0,0
				glVertex3f 1, 1, 0
				glTexCoord2f 1,0
				glVertex3f -1,1, 0
				glTexCoord2f 1,1
				glVertex3f -1,-1, 0
			glEnd
		EndIf
	End Method

	Method RenderTerrain(terrain:TTerrain)

		glDisableClientState GL_TEXTURE_COORD_ARRAY
		For Local i=0 To 7
			Local texture:TTexture=terrain._brush._texture[i]
			If texture=Null Continue
			glEnableClientState(GL_TEXTURE_COORD_ARRAY)
			glClientActiveTextureARB(GL_TEXTURE0+i)
			glTexCoordPointer(2,GL_FLOAT,20,terrain._data)
		Next

		glEnableClientState  GL_VERTEX_ARRAY
		glDisableClientState GL_COLOR_ARRAY
		glDisableClientState GL_NORMAL_ARRAY

		glVertexPointer(3,GL_FLOAT, 20, terrain._data+2)
		glDrawArrays(GL_TRIANGLES, 0, 3*terrain._count)

		glEnableClientState GL_VERTEX_ARRAY
		glEnableClientState GL_COLOR_ARRAY
		glEnableClientState GL_NORMAL_ARRAY
	End Method

	Method RenderBSPTree(tree:TBSPTree)
		Local node:TBSPNode=tree.Node
		If node=Null Return
		Local triangles
		triangles:+RenderBSPTree(node.In)

		For Local poly:TBSPPolygon=EachIn node.On
			glBegin GL_POLYGON
			glNormal3f poly.Plane.x,poly.Plane.y,poly.Plane.z
			For Local i=0 To poly.Count()-1
				glVertex3f poly.Point[i].x,poly.Point[i].y,poly.Point[i].z
			Next
			triangles:+poly.Count()-2
			glEnd
		Next

		triangles:+RenderBSPTree(node.Out)
		Return triangles
	End Method

	Method UpdateTextureRes:TGLTextureRes(frame:TTextureFrame,flags)
		If frame=Null Return Null

		Local glres:TGLTextureRes=TGLTextureRes(frame._res)
		If glres=Null
			glres=New TGLTextureRes
			frame._res=glres
			frame._updateres=True
		EndIf
		If Not frame._updateres Return glres

		If glres._id=0 glGenTextures(1,Varptr glres._id)
		BindTexture 0,glres._id
		Local pixmap:TPixmap=frame._pixmap
		gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA8,pixmap.width,pixmap.height,GL_BGRA,GL_UNSIGNED_BYTE,pixmap.pixels)

		frame._updateres=0
		Return glres
	End Method

	Method UpdateSurfaceRes:TGLSurfaceRes(surface:TSurface)
		Local res:TGLSurfaceRes=TGLSurfaceRes(surface._res)
		If res=Null res=New TGLSurfaceRes;surface._res=res
		If surface._reset=0 Return res

		If res._vbo[0]=0 glGenBuffersARB(4,res._vbo)

		If surface._reset=-1 Then surface._reset=1|2|4|8|16|32|64|128|256

		If surface._reset&1 UploadVertexBuffer res._vbo[0],surface._vertexpos
		If surface._reset&2 UploadVertexNormals res._vbo[1],surface._vertexnml
		If surface._reset&4 UploadVertexBuffer res._vbo[2],surface._vertexclr
		If surface._reset&8
			glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,res._vbo[3])
			glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB,surface._triangle.length*4,surface._triangle,GL_STATIC_DRAW_ARB)
		EndIf

		For Local i=0 To surface._vertextex.length-1
			If res._vbo[4+i]=0 glGenBuffersARB(1,Varptr res._vbo[4+i])
			If surface._reset&Int(2^(4+i)) UploadVertexBuffer res._vbo[4+i],surface._vertextex[i]
		Next

		res._trianglecnt=surface._trianglecnt
		res._vertexcnt=surface._vertexcnt
		surface._reset=0
		Return res
	End Method

	Method MergeSurfaceRes:TGLSurfaceRes(base:TSurface,animation:TSurface,data)
		If animation=Null Return UpdateSurfaceRes(base)
		Local res_base:TGLSurfaceRes=UpdateSurfaceRes(base)
		Local res_anim:TGLSurfaceRes=UpdateSurfaceRes(animation)
		Local res:TGLSurfaceRes=res_base.Copy()
		res._vbo[0]=res_anim._vbo[0]
		Return res
	End Method

	Method UploadVertexBuffer(vbo,data#[])
		glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo)
		glBufferDataARB(GL_ARRAY_BUFFER_ARB,data.length*4,data,GL_STATIC_DRAW_ARB)
	End Method

	Method UploadVertexNormals(vbo,data#[])
		glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo)
		glBufferDataARB(GL_ARRAY_BUFFER_ARB,data.length*4,data,GL_STATIC_DRAW_ARB)
	End Method
End Type

Type TGLCaps Extends TCaps
	Field Extensions$[]

	Method HasExtension(ext$)
		For Local extension$=EachIn Extensions
			If extension.Find(ext)<>-1 Return True
		Next
		Return False
	End Method

	Method Copy:TGLCaps()
		Local caps:TGLCaps=New TGLCaps
		caps.CopyBase(Self)
		caps.Extensions=Extensions[..]
		Return caps
	End Method
End Type

Type TGLSurfaceRes Extends TSurfaceRes
	Field _vbo[12]
	Field _texcoord

	Method Copy:TGLSurfaceRes()
		Local res:TGLSurfaceRes=New TGLSurfaceRes
		res._vertexcnt=_vertexcnt;res._trianglecnt=_trianglecnt
		res._vbo=_vbo[..]
		res._texcoord=_texcoord
		Return res
	End Method
End Type

Type TGLTextureRes Extends TTextureRes
	Field _id
End Type

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function GLMaxB3DDriver:TGLMaxB3DDriver()
	If GLMax2DExDriver()
		Global driver:TGLMaxB3DDriver=New TGLMaxB3DDriver
		driver._parent=GLMax2DExDriver()
		Return driver
	End If
End Function

Rem
	bbdoc: Utility function that sets the MaxB3D GL driver and calls Graphics.
End Rem
Function GLGraphics3D:TGraphics(width,height,depth=0,hertz=60,flags=0)
	SetGraphicsDriver GLMaxB3DDriver(),GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER
	Return Graphics(width,height,depth,hertz,flags)
End Function

Local driver:TGLMaxB3DDriver=GLMaxB3DDriver()
If driver SetGraphicsDriver driver,GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER

Strict

Rem
	bbdoc: OpenGL 1.1 driver for MaxB3D
End Rem
Module MaxB3D.GLDriver
ModuleInfo "Author: Kevin Primm"
ModuleInfo "License: MIT"

Import MaxB3D.Core
Import Prime.GLMax2DEx

Private
Function ModuleLog(message$)
	TMaxB3DLogger.Write "gldriver",message
End Function

Public

Global GL_LIGHT[]=[GL_LIGHT0,GL_LIGHT1,GL_LIGHT2,GL_LIGHT3,GL_LIGHT4,GL_LIGHT5,GL_LIGHT6,GL_LIGHT7]

Type TGLMaxB3DDriver Extends TMaxB3DDriver
	Field _currentdata:TMaxB3DShaderData= New TMaxB3DShaderData

	Method SetGraphics(g:TGraphics)
		Super.SetGraphics g
		If g<>Null Startup
	End Method

	Function BindTexture(index,tex)
		If index=-1
			For Local i=0 To 7
				BindTexture i,tex
			Next
			Return
		EndIf
		Global currenttexture[] = [-1,-1,-1,-1,-1,-1,-1,-1]
		If tex<>currenttexture[index]
			glBindTexture GL_TEXTURE_2D,tex
			currenttexture[index]=tex
		EndIf
	End Function

	Method Startup()
		Global _firsttime=True
		If _firsttime
			Local caps:TGLCaps=TGLCaps(_caps)
			ModuleLog "Initializing GL driver"
			ModuleLog "Vendor:   "+String.FromCString(Byte Ptr(glGetString(GL_VENDOR)))
			ModuleLog "Renderer: "+String.FromCString(Byte Ptr(glGetString(GL_RENDERER)))
			ModuleLog "Version:  "+String.FromCString(Byte Ptr(glGetString(GL_VERSION)))
			ModuleLog "Extensions supported: "+" ".Join(caps.Extensions)
			EndMax2D
			_firsttime=False
		EndIf
		EnableStates
	End Method

	Function EnableStates()
		glEnable GL_LIGHTING
		glEnable GL_DEPTH_TEST
		glEnable GL_CULL_FACE
		glEnable GL_SCISSOR_TEST

		glEnable GL_NORMALIZE

		glEnableClientState GL_VERTEX_ARRAY
		glEnableClientState GL_COLOR_ARRAY
		glEnableClientState GL_NORMAL_ARRAY

		glFrontFace GL_CW

		glLightModeli GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR
		glLightModeli GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE

		glClearDepth 1.0
		glDepthFunc GL_LEQUAL
		glHint GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST

		glAlphaFunc GL_GEQUAL,0.5

		BindTexture -1,0
	End Function

	Method MakeBuffer:TBuffer(src:Object,width,height,flags)
		Local buffer:TBuffer
		Select True
		Case TTextureFrame(src)<>Null
			buffer=TGLBuffer(TTextureFrame(src)._buffer)
			If buffer=Null
				Local res:TGLTextureRes=UpdateTextureRes(TTextureFrame(src),0)
				buffer=TGLMax2DExDriver(_parent).MakeGLBuffer(res._id,width,height,flags)
			EndIf
		Default
			buffer=TMax2DExDriver(_parent).MakeBuffer(src,width,height,flags)
		End Select
		Return buffer
	End Method

	Method Abbr$()
		Return "gl"
	End Method

	Method GetCaps:TCaps()
		Local caps:TGLCaps=New TGLCaps
		caps.Extensions=String.FromCString(glGetString(GL_EXTENSIONS)).Split(" ")
		If caps.HasExtension("GL_ARB_point_sprite")<>-1
			caps.PointSprites=True
			glGetFloatv GL_POINT_SIZE_MAX_ARB, Varptr caps.MaxPointSize
		EndIf
		Return caps
	End Method

	Method SetMax2D(enable)
		If enable
			glPopClientAttrib
			glPopAttrib
			If _shaderdriver _shaderdriver.Use(Null,Null)
			TGLMax2DExDriver(_parent).ResetGLContext _current
			glMatrixMode GL_TEXTURE
			glLoadIdentity
			glMatrixMode GL_COLOR
			glPopMatrix
		Else
			glPushAttrib GL_ALL_ATTRIB_BITS
			glPushClientAttrib GL_CLIENT_ALL_ATTRIB_BITS
			glMatrixMode GL_TEXTURE
			glPushMatrix
			glMatrixMode GL_COLOR
			glPushMatrix

			EnableStates()
		EndIf
	End Method
	Method SetCamera(camera:TCamera, config:TWorldConfig)
		If config.Dither
			glEnable GL_DITHER
		Else
			glDisable GL_DITHER
		EndIf

		Local vy#=config.Height-camera._viewheight-camera._viewy
		glViewport(camera._viewx,vy,camera._viewwidth,camera._viewheight)
		glScissor(camera._viewx,vy,camera._viewwidth,camera._viewheight)
		glClearColor(camera._brush._r,camera._brush._g,camera._brush._b,1.0)

		If camera._clsmode&CLSMODE_COLOR And camera._clsmode&CLSMODE_DEPTH
			glDepthMask GL_TRUE
			glClear GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT
		Else
			If camera._clsmode&CLSMODE_COLOR
				glClear GL_COLOR_BUFFER_BIT
			Else
				If camera._clsmode&CLSMODE_DEPTH
					glDepthMask GL_TRUE
					glClear GL_DEPTH_BUFFER_BIT
				EndIf
			EndIf
		EndIf

		If camera._fogmode<>FOGMODE_NONE
			glEnable GL_FOG
			glFogi GL_FOG_MODE,GL_LINEAR
			glFogf GL_FOG_START,camera._fognear
			glFogf GL_FOG_END,camera._fogfar
			Local rgb#[]=[camera._fogr,camera._fogg,camera._fogb]
			glFogfv GL_FOG_COLOR,rgb
		Else
			glDisable GL_FOG
		EndIf

		camera.UpdateMatrices()

		glMatrixMode GL_PROJECTION
		glLoadMatrixf camera._projection.ToPtr()
		glMatrixMode GL_MODELVIEW
		glLoadMatrixf camera._modelview.ToPtr()

		' Temporary hack
		glGetFloatv GL_PROJECTION_MATRIX,camera._projection._m
		camera._frustum=TFrustum.Extract(camera._modelview,camera._projection)
	End Method

	Method SetLight(light:TLight,index)
		If light=Null
			glDisable GL_LIGHT[index]
			Return
		Else
			If light._hidden=True
				glDisable GL_LIGHT[index]
				Return False
			EndIf
		EndIf

		glEnable GL_LIGHT[index]

		glPushMatrix
		glMultMatrixf light.GetMatrix(True).ToPtr()

		Local white_light#[]=[1.0,1.0,1.0,1.0]
		glLightfv GL_LIGHT[index],GL_SPECULAR,white_light

		Local z#=1.0
		Local w#=0.0
		If light._mode>LIGHT_DIRECTIONAL
			z=0.0
			w=1.0
		EndIf

		Local rgba#[]=[light._brush._r,light._brush._g,light._brush._b,1.0]
		Local pos#[]=[0.0,0.0,-z,w]

		glLightfv GL_LIGHT[index],GL_POSITION,pos
		glLightfv GL_LIGHT[index],GL_DIFFUSE,rgba

		If light._mode<>LIGHT_DIRECTIONAL
			Local light_range#[]=[0.0]
			Local range#[]=[light._range]
			glLightfv GL_LIGHT[index],GL_CONSTANT_ATTENUATION,light_range
			glLightfv GL_LIGHT[index],GL_LINEAR_ATTENUATION,range
		EndIf

		If light._mode=LIGHT_SPOT
			Local dir#[]=[0.0,0.0,-1.0]
			Local outer#[]=[light._outer/2.0]
			Local exponent#[]=[10.0]
			glLightfv GL_LIGHT[index],GL_SPOT_DIRECTION,dir
			glLightfv GL_LIGHT[index],GL_SPOT_CUTOFF,outer
			glLightfv GL_LIGHT[index],GL_SPOT_EXPONENT,exponent
		EndIf

		glPopMatrix
	End Method

	Method SetBrush(brush:TBrush,hasalpha,config:TWorldConfig)
		Local alpha_test

		Local ambient#[]=[config.AmbientRed/255.0,config.AmbientGreen/255.0,config.AmbientBlue/255.0]

		If hasalpha
			glEnable GL_BLEND
			glDepthMask GL_FALSE
		Else
			glDisable GL_BLEND
			glDepthMask GL_TRUE
		EndIf

		Select brush._blend
			Case BLEND_NONE,BLEND_ALPHA
				glBlendFunc GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA
			Case BLEND_MULTIPLY
				glBlendFunc GL_DST_COLOR,GL_ZERO
			Case BLEND_ADD
				glBlendFunc GL_SRC_ALPHA,GL_ONE
		End Select

		If brush._fx&FX_FULLBRIGHT
			ambient[0]=1.0
			ambient[1]=1.0
			ambient[2]=1.0
		EndIf

		If brush._fx&FX_VERTEXCOLOR
			glEnable GL_COLOR_MATERIAL
		Else
			glDisable GL_COLOR_MATERIAL
		EndIf

		If brush._fx&FX_FLATSHADED
			glShadeModel GL_FLAT
		Else
			glShadeModel GL_SMOOTH
		EndIf

		If brush._fx&FX_NOFOG
			glDisable GL_FOG
		EndIf

		If brush._fx&FX_NOCULLING
			glDisable GL_CULL_FACE
		Else
			glEnable GL_CULL_FACE
		EndIf

		If brush._fx&FX_WIREFRAME Or config.Wireframe
			glPolygonMode GL_FRONT_AND_BACK,GL_LINE
		Else
			glPolygonMode GL_FRONT_AND_BACK,GL_FILL
		EndIf

		Local no_mat#[]=[0.0,0.0]
		Local mat_ambient#[]=[brush._r,brush._g,brush._b,brush._a]
		Local mat_diffuse#[]=[brush._r,brush._g,brush._b,brush._a]
		Local mat_specular#[]=[brush._shine,brush._shine,brush._shine,brush._shine]
		Local mat_shininess#[]=[100.0]

		glMaterialfv GL_FRONT,GL_AMBIENT,mat_ambient
		glMaterialfv GL_FRONT,GL_DIFFUSE,mat_diffuse
		glMaterialfv GL_FRONT,GL_SPECULAR,mat_specular
		glMaterialfv GL_FRONT,GL_SHININESS,mat_shininess
		glLightModelfv GL_LIGHT_MODEL_AMBIENT,ambient

		For Local i=0 To 7
			glActiveTextureARB GL_TEXTURE0+i

			Local texture:TTexture=brush._texture[i]
			If texture=Null Or texture._blend=BLEND_NONE
				glDisable GL_TEXTURE_2D
				BindTexture i,0
				Continue
			EndIf

			Local texres:TGLTextureRes=TGLTextureRes(UpdateTextureRes(texture._frame[brush._textureframe[i]],texture._flags))

			glEnable GL_TEXTURE_2D

			BindTexture i,texres._id

			glMatrixMode GL_TEXTURE
			glLoadIdentity
			glTranslatef texture._px,-texture._py,0
			glScalef -texture._sx,texture._sy,1

			alpha_test :| texture._flags&TEXTURE_ALPHA Or texture._flags&TEXTURE_MASKED

			If texture._flags&TEXTURE_MIPMAP
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
			EndIf

			If texture._flags&TEXTURE_CLAMPU
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT
			EndIf

			If texture._flags&TEXTURE_CLAMPV
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE
			Else
				glTexParameteri GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT
			EndIf

			If texture._flags&TEXTURE_SPHEREMAP
				glEnable GL_TEXTURE_GEN_S
				glEnable GL_TEXTURE_GEN_T
				glTexGeni GL_S,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP
				glTexGeni GL_T,GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP
			Else
				glDisable GL_TEXTURE_GEN_S
				glDisable GL_TEXTURE_GEN_T
			EndIf

			Select texture._blend
			Case BLEND_NONE glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE)
			Case BLEND_ALPHA 	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE)
			Case BLEND_MULTIPLY glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE)
			'Case BLEND_MULTIPLY glTexEnvf(GL_TEXTURE_ENV,GL_COMBINE_RGB_EXT,GL_MODULATE)
			Case BLEND_ADD glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_ADD)
			Case BLEND_DOT3
				glTexEnvf GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT
				glTexEnvf GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_DOT3_RGB_EXT
			Case BLEND_MULTIPLY2
				glTexEnvi GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE
				glTexEnvi GL_TEXTURE_ENV,GL_COMBINE_RGB,GL_MODULATE
				glTexEnvi GL_TEXTURE_ENV,GL_RGB_SCALE,2.0
			Default glTexEnvf GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE
			End Select
		Next

		If alpha_test
			glEnable GL_ALPHA_TEST
		Else
			glDisable GL_ALPHA_TEST
		EndIf

		If _shaderdriver _shaderdriver.Use(brush._shader,_currentdata)
	End Method

	Method RenderSurface(resource:TSurfaceRes,brush:TBrush)
		Local res:TGLSurfaceRes=TGLSurfaceRes(resource)

		For Local i=0 To 7
			glClientActiveTextureARB GL_TEXTURE0+i

			Local texture:TTexture=brush._texture[i]
			If texture=Null
				glDisableClientState GL_TEXTURE_COORD_ARRAY
				Continue
			EndIf

			glEnableClientState GL_TEXTURE_COORD_ARRAY
			glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[4+texture._coords]
			glTexCoordPointer 2,GL_FLOAT,0,Null
		Next

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[0]
		glVertexPointer 3,GL_FLOAT,0,Null

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[1]
		glNormalPointer GL_FLOAT,0,Null

		glBindBufferARB GL_ARRAY_BUFFER_ARB,res._vbo[2]
		glColorPointer 4,GL_FLOAT,0,Null

		glBindBufferARB GL_ELEMENT_ARRAY_BUFFER_ARB,res._vbo[3]
		glDrawElements GL_TRIANGLES,res._trianglecnt*3,GL_UNSIGNED_INT,Null

		Return res._trianglecnt
	End Method

	Method BeginEntityRender(entity:TEntity)
		If entity._order<>0
			glDisable GL_DEPTH_TEST
			glDepthMask GL_FALSE
		Else
			glEnable GL_DEPTH_TEST
			glDepthMask GL_TRUE
		EndIf

		glMatrixMode GL_MODELVIEW
		glPushMatrix
		glMultMatrixf entity.GetMatrix(True,False).ToPtr()

		Global matrix:TMatrix=New TMatrix
		glGetFloatv GL_MODELVIEW_MATRIX,matrix._m
	End Method
	Method EndEntityRender(entity:TEntity)
		glMatrixMode GL_MODELVIEW
		glPopMatrix
	End Method

	Method RenderFlat(flat:TFlat)
		Local x#,y#,z#
		flat.GetScale x,y,z,True

		glBegin GL_QUADS
			glNormal3f 0,1,0
			glTexCoord2f 0,0
			glVertex3f -1,0, 1
			glTexCoord2f x,0
			glVertex3f 1, 0, 1
			glTexCoord2f x,z
			glVertex3f 1, 0, -1
			glTexCoord2f 0,z
			glVertex3f -1,0, -1
		glEnd
		Return 2
	End Method

	Method RenderSprite(sprite:TSprite)
		If False
			glPointParameterfvARB GL_POINT_DISTANCE_ATTENUATION_ARB, [0.0, 0.0, 1.0]

			glPointSize 1.0

			glPointParameterfARB GL_POINT_FADE_THRESHOLD_SIZE_ARB,60.0
			glPointParameterfARB GL_POINT_SIZE_MIN_ARB,0.0
			glPointParameterfARB GL_POINT_SIZE_MAX_ARB,9999.0

			glTexEnvf GL_POINT_SPRITE_ARB,GL_COORD_REPLACE_ARB,GL_TRUE

			glEnable GL_POINT_SPRITE_ARB

			glBegin GL_POINTS
			glVertex3f 0,0,0
			glEnd

			glDisable GL_POINT_SPRITE_ARB
		Else
			glBegin GL_QUADS
				glNormal3f 0,0,-1
				glTexCoord2f 0,1
				glVertex3f 1, -1, 0
				glTexCoord2f 0,0
				glVertex3f 1, 1, 0
				glTexCoord2f 1,0
				glVertex3f -1,1, 0
				glTexCoord2f 1,1
				glVertex3f -1,-1, 0
			glEnd
		EndIf
	End Method

	Method RenderTerrain(terrain:TTerrain)

		glDisableClientState GL_TEXTURE_COORD_ARRAY
		For Local i=0 To 7
			Local texture:TTexture=terrain._brush._texture[i]
			If texture=Null Continue
			glEnableClientState(GL_TEXTURE_COORD_ARRAY)
			glClientActiveTextureARB(GL_TEXTURE0+i)
			glTexCoordPointer(2,GL_FLOAT,20,terrain._data)
		Next

		glEnableClientState  GL_VERTEX_ARRAY
		glDisableClientState GL_COLOR_ARRAY
		glDisableClientState GL_NORMAL_ARRAY

		glVertexPointer(3,GL_FLOAT, 20, terrain._data+2)
		glDrawArrays(GL_TRIANGLES, 0, 3*terrain._count)

		glEnableClientState GL_VERTEX_ARRAY
		glEnableClientState GL_COLOR_ARRAY
		glEnableClientState GL_NORMAL_ARRAY
	End Method

	Method RenderBSPTree(tree:TBSPTree)
		Local node:TBSPNode=tree.Node
		If node=Null Return
		Local triangles
		triangles:+RenderBSPTree(node.In)

		For Local poly:TBSPPolygon=EachIn node.On
			glBegin GL_POLYGON
			glNormal3f poly.Plane.x,poly.Plane.y,poly.Plane.z
			Local ptA:TVector=poly.Point[0],v0
			For Local i=1 To poly.Count()-2
				Local ptB:TVector=poly.Point[i],ptC:TVector=poly.Point[i+1]
				glVertex3f ptA.x,ptA.y,ptA.z
				glVertex3f ptB.x,ptB.y,ptB.z
				glVertex3f ptC.x,ptC.y,ptC.z
			Next
			triangles:+poly.Count()-2
			glEnd
		Next

		triangles:+RenderBSPTree(node.Out)
		Return triangles
	End Method

	Method UpdateTextureRes:TGLTextureRes(frame:TTextureFrame,flags)
		If frame=Null Return Null

		Local glres:TGLTextureRes=TGLTextureRes(frame._res)
		If glres=Null
			glres=New TGLTextureRes
			frame._res=glres
			frame._updateres=True
		EndIf
		If Not frame._updateres Return glres

		If glres._id=0 glGenTextures(1,Varptr glres._id)
		BindTexture 0,glres._id
		Local pixmap:TPixmap=frame._pixmap
		gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA8,pixmap.width,pixmap.height,GL_BGRA,GL_UNSIGNED_BYTE,pixmap.pixels)

		frame._updateres=0
		Return glres
	End Method

	Method UpdateSurfaceRes:TGLSurfaceRes(surface:TSurface)
		Local res:TGLSurfaceRes=TGLSurfaceRes(surface._res)
		If res=Null res=New TGLSurfaceRes;surface._res=res
		If surface._reset=0 Return res

		If res._vbo[0]=0 glGenBuffersARB(4,res._vbo)

		If surface._reset=-1 Then surface._reset=1|2|4|8|16|32|64|128|256

		If surface._reset&1 UploadVertexBuffer res._vbo[0],surface._vertexpos
		If surface._reset&2 UploadVertexNormals res._vbo[1],surface._vertexnml
		If surface._reset&4 UploadVertexBuffer res._vbo[2],surface._vertexclr
		If surface._reset&8
			glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,res._vbo[3])
			glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB,surface._triangle.length*4,surface._triangle,GL_STATIC_DRAW_ARB)
		EndIf

		For Local i=0 To surface._vertextex.length-1
			If res._vbo[4+i]=0 glGenBuffersARB(1,Varptr res._vbo[4+i])
			If surface._reset&Int(2^(4+i)) UploadVertexBuffer res._vbo[4+i],surface._vertextex[i]
		Next

		res._trianglecnt=surface._trianglecnt
		res._vertexcnt=surface._vertexcnt
		surface._reset=0
		Return res
	End Method

	Method MergeSurfaceRes:TGLSurfaceRes(base:TSurface,animation:TSurface,data)
		If animation=Null Return UpdateSurfaceRes(base)
		Local res_base:TGLSurfaceRes=UpdateSurfaceRes(base)
		Local res_anim:TGLSurfaceRes=UpdateSurfaceRes(animation)
		Local res:TGLSurfaceRes=res_base.Copy()
		res._vbo[0]=res_anim._vbo[0]
		Return res
	End Method

	Method UploadVertexBuffer(vbo,data#[])
		glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo)
		glBufferDataARB(GL_ARRAY_BUFFER_ARB,data.length*4,data,GL_STATIC_DRAW_ARB)
	End Method

	Method UploadVertexNormals(vbo,data#[])
		glBindBufferARB(GL_ARRAY_BUFFER_ARB,vbo)
		glBufferDataARB(GL_ARRAY_BUFFER_ARB,data.length*4,data,GL_STATIC_DRAW_ARB)
	End Method
End Type

Type TGLCaps Extends TCaps
	Field Extensions$[]

	Method HasExtension(ext$)
		For Local extension$=EachIn Extensions
			If extension.Find(ext)<>-1 Return True
		Next
		Return False
	End Method

	Method Copy:TGLCaps()
		Local caps:TGLCaps=New TGLCaps
		caps.CopyBase(Self)
		caps.Extensions=Extensions[..]
		Return caps
	End Method
End Type

Type TGLSurfaceRes Extends TSurfaceRes
	Field _vbo[12]
	Field _texcoord

	Method Copy:TGLSurfaceRes()
		Local res:TGLSurfaceRes=New TGLSurfaceRes
		res._vertexcnt=_vertexcnt;res._trianglecnt=_trianglecnt
		res._vbo=_vbo[..]
		res._texcoord=_texcoord
		Return res
	End Method
End Type

Type TGLTextureRes Extends TTextureRes
	Field _id
End Type

Rem
	bbdoc: Needs documentation. #TODO
End Rem
Function GLMaxB3DDriver:TGLMaxB3DDriver()
	If GLMax2DExDriver()
		Global driver:TGLMaxB3DDriver=New TGLMaxB3DDriver
		driver._parent=GLMax2DExDriver()
		Return driver
	End If
End Function

Rem
	bbdoc: Utility function that sets the MaxB3D GL driver and calls Graphics.
End Rem
Function GLGraphics3D:TGraphics(width,height,depth=0,hertz=60,flags=0)
	SetGraphicsDriver GLMaxB3DDriver(),GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER
	Return Graphics(width,height,depth,hertz,flags)
End Function

Local driver:TGLMaxB3DDriver=GLMaxB3DDriver()
If driver SetGraphicsDriver driver,GRAPHICS_BACKBUFFER|GRAPHICS_DEPTHBUFFER