[variables] (v0.19) 	Added TBoolVariable; documentation correction

Rem
	Copyright (c) 2009 Tim Howard

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
	-----------------------------------------------------------------------------

	variables.bmx (Contains: TV_INTEGER, TV_STRING, TV_FLOAT, TV_EVAL,
							TVariable, TStringVariable, TFloatVariable, TIntVariable, TEvalVariable, TIdentifier,
							TVariableMap, )

End Rem

SuperStrict

Rem
bbdoc: Variables module
End Rem
Module duct.variables

ModuleInfo "Version: 0.19"
ModuleInfo "Copyright: Tim Howard"
ModuleInfo "License: MIT"

ModuleInfo "History: Version 0.19"
ModuleInfo "History: Added TBoolVariable; documentation correction"
ModuleInfo "History: Version 0.18"
ModuleInfo "History: TIdentifier.AddValue now sets the variable's parent to itself"
ModuleInfo "History: Added SetParent and GetParent to TVaraible"
ModuleInfo "History: Version 0.17"
ModuleInfo "History: General cleanup"
ModuleInfo "History: Version 0.16"
ModuleInfo "History: Added SetAmbiguous to all TVariable types"
ModuleInfo "History: Added option to always use quoting with TStringVariables (on by default)"
ModuleInfo "History: Fixed TStringVariable.ConvToString returning of Null strings"
ModuleInfo "History: Version 0.15"
ModuleInfo "History: Change the Create method definition for easier use"
ModuleInfo "History: Changed the '/eval::' recognizer to '/e:' (as per SNode format change)"
ModuleInfo "History: Added the Copy method to every variable type"
ModuleInfo "History: Changed some formatting"
ModuleInfo "History: Version 0.14"
ModuleInfo "History: Changed type tabbing"
ModuleInfo "History: Fixed script output for TStringVariable (quotes are now only added if whitespace is present)"
ModuleInfo "History: Fixed script output for TIdentifier (identifier names can now contain whitespace, in which case they must be quoted)"
ModuleInfo "History: Version 0.13"
ModuleInfo "History: Added the DeSerializeUniversal function to TVariable"
ModuleInfo "History: Added Serialize and DeSerialize methods to all variable types"
ModuleInfo "History: Version 0.12"
ModuleInfo "History: Changed module name from 'variablemap' to 'variables'"
ModuleInfo "History: Moved all of duct.utilparser here"
ModuleInfo "History: Added the GetTVType function to all variable types"
ModuleInfo "History: Moved TV_* constants from duct.template here"
ModuleInfo "History: Version 0.11"
ModuleInfo "History: Added the ReportType function to all variable types"
ModuleInfo "History: Version 0.10"
ModuleInfo "History: Added the RawToVariable function to TVariable"
ModuleInfo "History: General code cleanup"
ModuleInfo "History: Version 0.09"
ModuleInfo "History: Corrected usage of syntax (in Returns, Cases, News and Selects)"
ModuleInfo "History: Version 0.08"
ModuleInfo "History: Added the TEvalVariable type"
ModuleInfo "History: Initial release"

Import brl.stream
Import brl.linkedlist

Import duct.etc
Import duct.objectmap

Rem
	bbdoc: Template variable type for the TIntVariable type.
End Rem
Const TV_INTEGER:Int = 1
Rem
	bbdoc: Template variable type for the TTStringVariable type.
End Rem
Const TV_STRING:Int = 2
Rem
	bbdoc: Template variable type for the TFloatVariable type.
End Rem
Const TV_FLOAT:Int = 3
Rem
	bbdoc: Template variable type for the TEvalVariable type.
End Rem
Const TV_EVAL:Int = 4
Rem
	bbdoc: Template variable type for the TIdentifier type.
End Rem
Const TV_IDEN:Int = 5
Rem
	bbdoc: Template variable type for the TIdentifier type.
End Rem
Const TV_BOOL:Int = 6

Rem
	bbdoc: The Variable type.
	about: This is the base variable type, you should extend from this to use it.
End Rem
Type TVariable Abstract

	Field m_name:String
	Field m_parent:TVariable

'#region Field accessors

	Rem
		bbdoc: Set the variable's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		m_name = name
	End Method

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

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

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

	Rem
		bbdoc: Base method for setting the value of the variable to the given string (conversion).
	End Rem
	Method SetAmbiguous(value:String) Abstract

	Rem
		bbdoc: Base method for converting variable data to a script-ready string.
	End Rem
	Method ConvToString:String() Abstract

	Rem
		bbdoc: Base method for converting variable data to a printable/usable-in-code string.
	End Rem
	Method ValueAsString:String() Abstract

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable.
		returns: A clone of the variable.
	End Rem
	Method Copy:TVariable() Abstract

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False) Abstract

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TVariable(stream:TStream, tv:Int = True, name:Int = False) Abstract

'#end region (Data handling)

	Rem
		bbdoc: Base method: Get the type of this variable.
	End Rem
	Function ReportType:String() Abstract

	Rem
		bbdoc: Base method: Get the TV_* type of this variable.
	End Rem
	Function GetTVType:Int() Abstract

	Rem
		bbdoc: Convert raw data (raw data being things like: "/eval::(a+b/0.4181)*a-b" - an EvalVariable, "A String variable1!!", 3452134 - an IntVariable, 1204.00321 - a FloatVariable) into a Variable.
		returns: A new Variable, or Null if something whacky occured.
		about: @etype is optional, it is used to go automagically to one type of a Variable (1 & 4=String (will check for '/eval::' - EvalVariables), 2=Integer, 3=Float).

		@varname is also an optional parameter. It will be used as the name of the variable.
	End Rem
	Function RawToVariable:TVariable(vraw:String, etype:Int = 0, varname:String = "")
		Local variable:TVariable

		If vraw = Null
			DebugLog("(TVariable.RawToVariable) @vraw = Null; returning StringVariable (with @varname and Null value)")
			Return New TStringVariable.Create(varname, Null)
		End If

		'If etype = 1 ' Explicitly a string
		'
		'	variable = TVariable(New TStringVariable.Create("", vraw))
		'
		If etype = 0 ' Determine the value's type (must be either integer, double, or a string with no spaces)
			Local i:Int

			' ASCII '0' to '9' = 48-57; '-' = 45, '+' = 43; and '.' = 46
			For i = 0 To vraw.Length - 1
				Local c:Int

				c = vraw[i]
				If c >= 48 And c <= 57 Or c = 43 Or c = 45
					If etype = 0 ' Leave float and string alone
						etype = 2 ' Integer so far..
					End If
				Else If c = 46
					If etype = 2 ' Already declared as an integer?
						etype = 3
					End If
				Else ' If the character is not numerical there is nothing else to deduce and the value is a string
					etype = 4
					Exit
				End If
			Next

			Select etype
				Case 2 ' Integer
					variable = TVariable(New TIntVariable.Create(varname, Int(vraw)))
				Case 3 ' Double/Float
					variable = TVariable(New TFloatVariable.Create(varname, Float(vraw)))
				'Case 4 ' String - non-explicit
				'  Local evaltest:Int = vraw.ToLower().Find("/eval::")
				'
				'	If evaltest >= 0
				'		variable = TVariable(New TEvalVariable.Create(varname, vraw[evaltest + 7..]))
				'	Else
				'		variable = TVariable(New TStringVariable.Create(varname, vraw))
				'	End If
			End Select

		End If

		If etype = 1 Or etype = 4
			Local evaltest:Int = vraw.ToLower().Find("/e:")
			If evaltest >= 0
				variable = TVariable(New TEvalVariable.Create(varname, vraw[evaltest + 7..]))
			Else
				variable = TVariable(New TStringVariable.Create(varname, vraw))
			End If
		End If
		' DebugLog("TSNode.LoadScriptFromStream().RawToVariable(); vraw = ~q" + vraw + "~q \" + etype)
		?Debug
		If variable = Null Then DebugLog("( TVariable.RawToVariable() ) Unknown error, 'variable' is Null.")
		?
		Return variable
	End Function

	Rem
		bbdoc: Universally deserialize a variable from the given stream.
		returns: A DeSerialized variable.
		about: This will deserialize any variable from the stream.

		This requires the variable to have been serialized with the template type (see #Serialize parameters).

		@name tells the further DeSerialize calls if the name should be DeSerialized or not.
	End Rem
	Function DeSerializeUniversal:TVariable(stream:TStream, name:Int = False)
		Local tv:Int

		tv = Int(stream.ReadByte())

		Select tv
			Case TV_INTEGER
				Return New TIntVariable.DeSerialize(stream, True, name)
			Case TV_STRING
				Return New TStringVariable.DeSerialize(stream, True, name)
			Case TV_FLOAT
				Return New TFloatVariable.DeSerialize(stream, True, name)
			Case TV_EVAL
				Return New TEvalVariable.DeSerialize(stream, True, name)
			Case TV_IDEN
				Return New TIdentifier.DeSerialize(stream, True, name)

			Default
				DebugLog("(TVariable.DeSerializeUniversal) Failed to recognize the TV in the stream: " + tv)

		End Select
		Return Null
	End Function

End Type

Rem
	bbdoc: The TStringVariable type.
End Rem
Type TStringVariable Extends TVariable

	Global m_alwaysusequotes:Int = True

	Field m_value:String

	Rem
		bbdoc: Create a new StringVariable.
		returns: The new StringVariable (itself).
	End Rem
	Method Create:TStringVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the TStringVariable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A String representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		If m_alwaysusequotes = True Or m_value.Contains("~t") Or m_value.Contains(" ") Or m_value = Null
			Return "~q" + m_value + "~q"
		Else
			Return m_value
		End If
	End Method

	Rem
		bbdoc: Get the StringVariable as a String.
		returns: The variable's value converted to a String.
		about: Here for complete-ness, no difference to `instance.Get()`.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TStringVariable()
		Return New TStringVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_STRING)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TStringVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("string").
	End Rem
	Function ReportType:String()
		Return "string"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_STRING).
	End Rem
	Function GetTVType:Int()
		Return TV_STRING
	End Function

End Type

Rem
	bbdoc: The TFloatVariable type.
End Rem
Type TFloatVariable Extends TVariable

	Field m_value:Float

	Rem
		bbdoc: Create a new FloatVariable.
		returns: The new FloatVariable (itself).
	End Rem
	Method Create:TFloatVariable(name:String = Null, value:Float)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Float)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Float()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Float(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A scriptable form of the StringVariable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Local conv:String = String(m_value), i:Int, encountered:Int

		For i = conv.Find(".") To conv.Length - 1
			If conv[i] = 48
				If encountered = True
					conv = conv[..i]
					Exit
				End If
			Else If conv[i] <> 46
				encountered = True
			End If
		Next

		Return conv
	End Method

	Rem
		bbdoc: Get the FloatVariable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TFloatVariable()
		Return New TFloatVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_FLOAT)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If
		stream.WriteFloat(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TFloatVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If
		m_value = stream.ReadFloat()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("float").
	End Rem
	Function ReportType:String()
		Return "float"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_FLOAT).
	End Rem
	Function GetTVType:Int()
		Return TV_FLOAT
	End Function

End Type

Rem
	bbdoc: The TIntVariable type.
End Rem
Type TIntVariable Extends TVariable

	Field m_value:Int

	Rem
		bbdoc: Create a new IntVariable.
		returns: The new IntVariable (itself).
	End Rem
	Method Create:TIntVariable(name:String = Null, value:Int)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Int)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Int()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Int(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A string representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return String(m_value)
	End Method

	Rem
		bbdoc: Get the IntVariable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TIntVariable()
		Return New TIntVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_INTEGER)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		stream.WriteInt(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TIntVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = stream.ReadInt()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("int").
	End Rem
	Function ReportType:String()
		Return "int"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_INTEGER).
	End Rem
	Function GetTVType:Int()
		Return TV_INTEGER
	End Function

End Type

Rem
	bbdoc: The TEvalVariable type.
End Rem
Type TEvalVariable Extends TVariable

	Field m_value:String

	Rem
		bbdoc: Create a new EvalVariable.
		returns: The new EvalVariable (itself).
	End Rem
	Method Create:TEvalVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the equation string for the EvalVariable.
		returns: Nothing
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the equation string.
		returns: The equation string.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the EvalVariable to a visual representation of its data.
		returns: The scriptable form of the EvalVariable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return "~q/e:" + m_value + "~q"
	End Method

	Rem
		bbdoc: Get the EvalVariable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TEvalVariable()
		Return New TEvalVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_EVAL)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TEvalVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("eval").
	End Rem
	Function ReportType:String()
		Return "eval"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_EVAL).
	End Rem
	Function GetTVType:Int()
		Return TV_EVAL
	End Function

End Type

Rem
	bbdoc: The TBoolVariable type.
End Rem
Type TBoolVariable Extends TVariable

	Field m_value:Int

	Rem
		bbdoc: Create a new BoolVariable.
		returns: The new BoolVariable (itself).
	End Rem
	Method Create:TBoolVariable(name:String = Null, value:Int)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Int)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Int()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Int(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A string representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return String(m_value)
	End Method

	Rem
		bbdoc: Get the TBoolVariable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TBoolVariable()
		Return New TBoolVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_BOOL)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If
		stream.WriteByte(Byte(m_value))
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TBoolVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If
		m_value = Int(stream.ReadByte())
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("bool").
	End Rem
	Function ReportType:String()
		Return "bool"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_BOOL).
	End Rem
	Function GetTVType:Int()
		Return TV_BOOL
	End Function

End Type

Rem
	bbdoc: The Identifier type.
End Rem
Type TIdentifier Extends TVariable

	Field m_values:TList

	Method New()
	End Method

	Rem
		bbdoc: Create a new Identifier.
		returns: The new Identfier (itself).
	End Rem
	Method Create:TIdentifier()
		m_values = New TList
		Return Self
	End Method

	Rem
		bbdoc: Create a new Identifier with the given data.
		returns: The new Identfier (itself).
		about: If the @values parameter is Null a new list will be created.
	End Rem
	Method CreateByData:TIdentifier(name:String, values:TList = Null)
		SetName(name)
		If values = Null
			m_values = New TList
		Else
			m_values = values
		End If
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the Identifier's values.
		returns: Nothing.
	End Rem
	Method SetValues(values:TList)
		m_values = values
	End Method

	Rem
		bbdoc: Get the Identifier's values.
		returns: A list containing the values which the Identifier holds.
	End Rem
	Method GetValues:TList()
		Return m_values
	End Method

	Rem
		bbdoc: Deprecated for this type.
		returns: Nothing.
		about: This does nothing.
	End Rem
	Method SetAmbiguous(value:String)
	End Method

	Rem
		bbdoc: Convert the identifier to a string.
		returns: A string representation of the identifier.
	End Rem
	Method ConvToString:String()
		Local op:String, variable:TVariable

		If m_name.Contains("~t") = True Or m_name.Contains(" ") = True
			op = "~q" + m_name + "~q "
		Else
			op = m_name + " "
		End If

		For variable = EachIn m_values
			op:+variable.ConvToString() + " "
		Next
		op = op[..op.Length - 1]
		Return op
	End Method

	Rem
		bbdoc: Get the Identifier as a string.
		returns: The Identifier contents converted to a string.
		about: Here for complete-ness, simply calls #ConvToString.
	End Rem
	Method ValueAsString:String()
		Return ConvToString()
	End Method

'#end region (Field accessors)

'#region Value handling

	Rem
		bbdoc: Get a value at an index.
		returns: The value in the identifier's list at the given index, or Null if it could not be retrieved.
		about: The index is zero-based.
	End Rem
	Method GetValueAtIndex:TVariable(index:Int)
		If m_values <> Null And index > - 1 And index < m_values.Count()
			Return TVariable(m_values.ValueAtIndex(index))
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the number of values.
		returns: The number of values the Identifier contains.
	End Rem
	Method GetValueCount:Int()
		If m_values <> Null
			Return m_values.Count()
		End If
		Return 0
	End Method

	Rem
		bbdoc: Add a value to the Identifier.
		returns: True for success, or False for failure.
		about: NOTE: This will set the given value's parent.
	End Rem
	Method AddValue:Int(value:TVariable)
		If m_values <> Null And value <> Null
			m_values.AddLast(value)
			value.SetParent(Self)
			Return True
		End If
		Return False
	End Method

'#end region (Value handling)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TIdentifier()
		Local clone:TIdentifier
		clone = New TIdentifier.CreateByData(m_name)
		For Local variable:TVariable = EachIn m_values
			clone.AddValue(variable.Copy())
		Next
		Return clone
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should serialize the values' name (the Identifier's name is always read/written to the stream).
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		Local variable:TVariable

		If tv = True
			stream.WriteByte(TV_STRING)
		End If
		WriteLString(stream, m_name)

		If m_values = Null
			stream.WriteInt(0)
		Else
			stream.WriteInt(m_values.Count())
			For variable = EachIn m_values
				variable.Serialize(stream, True, name)
			Next
		End If
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should deserialize the values' name (the Identifier's name is always read/written to the stream).
	End Rem
	Method DeSerialize:TIdentifier(stream:TStream, tv:Int = True, name:Int = False)
		Local count:Int, n:Int

		If tv = True
			stream.ReadByte()
		End If
		m_name = ReadLString(stream)

		count = stream.ReadInt()
		If count > 0
			For n = 0 To count - 1
				DeSerializeUniversal(stream, name)
			Next
		End If

		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("identifier").
	End Rem
	Function ReportType:String()
		Return "identifier"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_IDEN).
	End Rem
	Function GetTVType:Int()
		Return TV_IDEN
	End Function

End Type

Rem
	bbdoc: The VariableMap type.
EndRem
Type TVariableMap Extends TObjectMap

	Rem
		bbdoc: Create a new VariableMap.
		returns: The new VariableMap (itself).
	End Rem
	Method Create:TVariableMap()
		Return Self
	End Method

'#region Collections

	Rem
		bbdoc: Insert a variable into the map.
		returns: True if the variable was added, or False if it was not (the variable's name is Null).
	End Rem
	Method InsertVariable:Int(variable:TVariable)
		If variable.GetName() <> Null
			_Insert(variable.GetName(), variable)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Remove a variable by the name given.
		returns: True if a variable was removed, or False if it was not (the map contains no variable with the name given).
		about: The name is case-sensitive.
	End Rem
	Method RemoveVariableByName:Int(name:String)
		Return _Remove(name)
	End Method

	Rem
		bbdoc: Get a variable from the map by its name.
		returns: The variable object, or if the variable was not found, Null.
		about: The name is case-sensitive.
	End Rem
	Method GetVariableByName:TVariable(name:String)
		Return TVariable(_ValueByKey(name))
	End Method

'#end region (Collections)

End Type

Rem
	Copyright (c) 2009 Tim Howard

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
	-----------------------------------------------------------------------------

	variables.bmx (Contains: TV_INTEGER, TV_STRING, TV_FLOAT, TV_EVAL,
							TVariable, TStringVariable, TFloatVariable, TIntVariable, TEvalVariable, TIdentifier,
							TVariableMap, )

End Rem

SuperStrict

Rem
bbdoc: Variables module
End Rem
Module duct.variables

ModuleInfo "Version: 0.18"
ModuleInfo "Copyright: Tim Howard"
ModuleInfo "License: MIT"

ModuleInfo "History: Version 0.18"
ModuleInfo "History: TIdentifier.AddValue now sets the variable's parent to itself"
ModuleInfo "History: Added SetParent and GetParent to TVaraible"
ModuleInfo "History: Version 0.17"
ModuleInfo "History: General cleanup"
ModuleInfo "History: Version 0.16"
ModuleInfo "History: Added SetAmbiguous to all TVariable types"
ModuleInfo "History: Added option to always use quoting with TStringVariables (on by default)"
ModuleInfo "History: Fixed TStringVariable.ConvToString returning of Null strings"
ModuleInfo "History: Version 0.15"
ModuleInfo "History: Change the Create method definition for easier use"
ModuleInfo "History: Changed the '/eval::' recognizer to '/e:' (as per SNode format change)"
ModuleInfo "History: Added the Copy method to every variable type"
ModuleInfo "History: Changed some formatting"
ModuleInfo "History: Version 0.14"
ModuleInfo "History: Changed type tabbing"
ModuleInfo "History: Fixed script output for TStringVariable (quotes are now only added if whitespace is present)"
ModuleInfo "History: Fixed script output for TIdentifier (identifier names can now contain whitespace, in which case they must be quoted)"
ModuleInfo "History: Version 0.13"
ModuleInfo "History: Added the DeSerializeUniversal function to TVariable"
ModuleInfo "History: Added Serialize and DeSerialize methods to all variable types"
ModuleInfo "History: Version 0.12"
ModuleInfo "History: Changed module name from 'variablemap' to 'variables'"
ModuleInfo "History: Moved all of duct.utilparser here"
ModuleInfo "History: Added the GetTVType function to all variable types"
ModuleInfo "History: Moved TV_* constants from duct.template here"
ModuleInfo "History: Version 0.11"
ModuleInfo "History: Added the ReportType function to all variable types"
ModuleInfo "History: Version 0.10"
ModuleInfo "History: Added the RawToVariable function to TVariable"
ModuleInfo "History: General code cleanup"
ModuleInfo "History: Version 0.09"
ModuleInfo "History: Corrected usage of syntax (in Returns, Cases, News and Selects)"
ModuleInfo "History: Version 0.08"
ModuleInfo "History: Added the TEvalVariable type"
ModuleInfo "History: Initial release"

Import brl.stream
Import brl.linkedlist

Import duct.etc
Import duct.objectmap

Rem
	bbdoc: Template variable type for the TIntVariable type.
End Rem
Const TV_INTEGER:Int = 1
Rem
	bbdoc: Template variable type for the TTStringVariable type.
End Rem
Const TV_STRING:Int = 2
Rem
	bbdoc: Template variable type for the TFloatVariable type.
End Rem
Const TV_FLOAT:Int = 3
Rem
	bbdoc: Template variable type for the TEvalVariable type.
End Rem
Const TV_EVAL:Int = 4
Rem
	bbdoc: Template variable type for the TIdentifier type.
End Rem
Const TV_IDEN:Int = 5

Rem
	bbdoc: The Variable type.
	about: This is the base variable type, you should extend from this to use it.
End Rem
Type TVariable Abstract

	Field m_name:String
	Field m_parent:TVariable

'#region Field accessors

	Rem
		bbdoc: Set the variable's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		m_name = name
	End Method

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

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

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

	Rem
		bbdoc: Base method for setting the value of the variable to the given string (conversion).
	End Rem
	Method SetAmbiguous(value:String) Abstract

	Rem
		bbdoc: Base method for converting variable data to a script-ready string.
	End Rem
	Method ConvToString:String() Abstract

	Rem
		bbdoc: Base method for converting variable data to a printable/usable-in-code string.
	End Rem
	Method ValueAsString:String() Abstract

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable.
		returns: A clone of the variable.
	End Rem
	Method Copy:TVariable() Abstract

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False) Abstract

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TVariable(stream:TStream, tv:Int = True, name:Int = False) Abstract

'#end region (Data handling)

	Rem
		bbdoc: Base method: Get the type of this variable.
	End Rem
	Function ReportType:String() Abstract

	Rem
		bbdoc: Base method: Get the TV_* type of this variable.
	End Rem
	Function GetTVType:Int() Abstract

	Rem
		bbdoc: Convert raw data (raw data being things like: "/eval::(a+b/0.4181)*a-b" - an EvalVariable, "A String variable1!!", 3452134 - an IntVariable, 1204.00321 - a FloatVariable) into a Variable.
		returns: A new Variable, or Null if something whacky occured.
		about: @etype is optional, it is used to go automagically to one type of a Variable (1 & 4=String (will check for '/eval::' - EvalVariables), 2=Integer, 3=Float).

		@varname is also an optional parameter. It will be used as the name of the variable.
	End Rem
	Function RawToVariable:TVariable(vraw:String, etype:Int = 0, varname:String = "")
		Local variable:TVariable

		If vraw = Null
			DebugLog("(TVariable.RawToVariable) @vraw = Null; returning StringVariable (with @varname and Null value)")
			Return New TStringVariable.Create(varname, Null)
		End If

		'If etype = 1 ' Explicitly a string
		'
		'	variable = TVariable(New TStringVariable.Create("", vraw))
		'
		If etype = 0 ' Determine the value's type (must be either integer, double, or a string with no spaces)
			Local i:Int

			' ASCII '0' to '9' = 48-57; '-' = 45, '+' = 43; and '.' = 46
			For i = 0 To vraw.Length - 1
				Local c:Int

				c = vraw[i]
				If c >= 48 And c <= 57 Or c = 43 Or c = 45
					If etype = 0 ' Leave float and string alone
						etype = 2 ' Integer so far..
					End If
				Else If c = 46
					If etype = 2 ' Already declared as an integer?
						etype = 3
					End If
				Else ' If the character is not numerical there is nothing else to deduce and the value is a string
					etype = 4
					Exit
				End If
			Next

			Select etype
				Case 2 ' Integer
					variable = TVariable(New TIntVariable.Create(varname, Int(vraw)))
				Case 3 ' Double/Float
					variable = TVariable(New TFloatVariable.Create(varname, Float(vraw)))
				'Case 4 ' String - non-explicit
				'  Local evaltest:Int = vraw.ToLower().Find("/eval::")
				'
				'	If evaltest >= 0
				'		variable = TVariable(New TEvalVariable.Create(varname, vraw[evaltest + 7..]))
				'	Else
				'		variable = TVariable(New TStringVariable.Create(varname, vraw))
				'	End If
			End Select

		End If

		If etype = 1 Or etype = 4
			Local evaltest:Int = vraw.ToLower().Find("/e:")
			If evaltest >= 0
				variable = TVariable(New TEvalVariable.Create(varname, vraw[evaltest + 7..]))
			Else
				variable = TVariable(New TStringVariable.Create(varname, vraw))
			End If
		End If
		' DebugLog("TSNode.LoadScriptFromStream().RawToVariable(); vraw = ~q" + vraw + "~q \" + etype)
		?Debug
		If variable = Null Then DebugLog("( TVariable.RawToVariable() ) Unknown error, 'variable' is Null.")
		?
		Return variable
	End Function

	Rem
		bbdoc: Universally deserialize a variable from the given stream.
		returns: A DeSerialized variable.
		about: This will deserialize any variable from the stream.

		This requires the variable to have been serialized with the template type (see #Serialize parameters).

		@name tells the further DeSerialize calls if the name should be DeSerialized or not.
	End Rem
	Function DeSerializeUniversal:TVariable(stream:TStream, name:Int = False)
		Local tv:Int

		tv = Int(stream.ReadByte())

		Select tv
			Case TV_INTEGER
				Return New TIntVariable.DeSerialize(stream, True, name)
			Case TV_STRING
				Return New TStringVariable.DeSerialize(stream, True, name)
			Case TV_FLOAT
				Return New TFloatVariable.DeSerialize(stream, True, name)
			Case TV_EVAL
				Return New TEvalVariable.DeSerialize(stream, True, name)
			Case TV_IDEN
				Return New TIdentifier.DeSerialize(stream, True, name)

			Default
				DebugLog("(TVariable.DeSerializeUniversal) Failed to recognize the TV in the stream: " + tv)

		End Select
		Return Null
	End Function

End Type

Rem
	bbdoc: The TStringVariable type.
End Rem
Type TStringVariable Extends TVariable

	Global m_alwaysusequotes:Int = True

	Field m_value:String

	Rem
		bbdoc: Create a new StringVariable.
		returns: The new StringVariable (itself).
	End Rem
	Method Create:TStringVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the TStringVariable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A String representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		If m_alwaysusequotes = True Or m_value.Contains("~t") Or m_value.Contains(" ") Or m_value = Null
			Return "~q" + m_value + "~q"
		Else
			Return m_value
		End If
	End Method

	Rem
		bbdoc: Get the StringVariable as a String.
		returns: The variable's value converted to a String.
		about: Here for complete-ness, no difference to `instance.Get()`.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TStringVariable()
		Return New TStringVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_STRING)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TStringVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("string").
	End Rem
	Function ReportType:String()
		Return "string"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_STRING).
	End Rem
	Function GetTVType:Int()
		Return TV_STRING
	End Function

End Type

Rem
	bbdoc: The TFloatVariable type.
End Rem
Type TFloatVariable Extends TVariable

	Field m_value:Float

	Rem
		bbdoc: Create a new FloatVariable.
		returns: The new FloatVariable (itself).
	End Rem
	Method Create:TFloatVariable(name:String = Null, value:Float)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Float)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Float()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Float(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A scriptable form of the StringVariable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Local conv:String = String(m_value), i:Int, encountered:Int

		For i = conv.Find(".") To conv.Length - 1
			If conv[i] = 48
				If encountered = True
					conv = conv[..i]
					Exit
				End If
			Else If conv[i] <> 46
				encountered = True
			End If
		Next

		Return conv
	End Method

	Rem
		bbdoc: Get the FloatVariable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TFloatVariable()
		Return New TFloatVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_FLOAT)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If
		stream.WriteFloat(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TFloatVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If
		m_value = stream.ReadFloat()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("float").
	End Rem
	Function ReportType:String()
		Return "float"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_FLOAT).
	End Rem
	Function GetTVType:Int()
		Return TV_FLOAT
	End Function

End Type

Rem
	bbdoc: The TIntVariable type.
End Rem
Type TIntVariable Extends TVariable

	Field m_value:Int

	Rem
		bbdoc: Create a new IntVariable.
		returns: The new IntVariable (itself).
	End Rem
	Method Create:TIntVariable(name:String = Null, value:Int)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Int)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Int()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Int(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A string representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return String(m_value)
	End Method

	Rem
		bbdoc: Get the IntVariable as a String.
		returns: The variable value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TIntVariable()
		Return New TIntVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_INTEGER)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		stream.WriteInt(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TIntVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = stream.ReadInt()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("int").
	End Rem
	Function ReportType:String()
		Return "int"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_INTEGER).
	End Rem
	Function GetTVType:Int()
		Return TV_INTEGER
	End Function

End Type

Rem
	bbdoc: The TEvalVariable type.
End Rem
Type TEvalVariable Extends TVariable

	Field m_value:String

	Rem
		bbdoc: Create a new EvalVariable.
		returns: The new EvalVariable (itself).
	End Rem
	Method Create:TEvalVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the equation string for the EvalVariable.
		returns: Nothing
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the equation string.
		returns: The equation string.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the EvalVariable to a visual representation of its data.
		returns: The scriptable form of the EvalVariable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return "~q/e:" + m_value + "~q"
	End Method

	Rem
		bbdoc: Get the EvalVariable as a String.
		returns: The variable value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TEvalVariable()
		Return New TEvalVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.WriteByte(TV_EVAL)
		End If
		If name = True
			WriteLString(stream, m_name)
		End If

		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method DeSerialize:TEvalVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True
			stream.ReadByte()
		End If
		If name = True
			m_name = ReadLString(stream)
		End If

		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("eval").
	End Rem
	Function ReportType:String()
		Return "eval"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_EVAL).
	End Rem
	Function GetTVType:Int()
		Return TV_EVAL
	End Function

End Type

Rem
	bbdoc: The Identifier type.
End Rem
Type TIdentifier Extends TVariable

	Field m_values:TList

	Method New()
	End Method

	Rem
		bbdoc: Create a new Identifier.
		returns: The new Identfier (itself).
	End Rem
	Method Create:TIdentifier()
		m_values = New TList
		Return Self
	End Method

	Rem
		bbdoc: Create a new Identifier with the given data.
		returns: The new Identfier (itself).
		about: If the @values parameter is Null a new list will be created.
	End Rem
	Method CreateByData:TIdentifier(name:String, values:TList = Null)
		SetName(name)
		If values = Null
			m_values = New TList
		Else
			m_values = values
		End If
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the Identifier's values.
		returns: Nothing.
	End Rem
	Method SetValues(values:TList)
		m_values = values
	End Method

	Rem
		bbdoc: Get the Identifier's values.
		returns: A list containing the values which the Identifier holds.
	End Rem
	Method GetValues:TList()
		Return m_values
	End Method

	Rem
		bbdoc: Deprecated for this type.
		returns: Nothing.
		about: This does nothing.
	End Rem
	Method SetAmbiguous(value:String)
	End Method

	Rem
		bbdoc: Convert the identifier to a string.
		returns: A string representation of the identifier.
	End Rem
	Method ConvToString:String()
		Local op:String, variable:TVariable

		If m_name.Contains("~t") = True Or m_name.Contains(" ") = True
			op = "~q" + m_name + "~q "
		Else
			op = m_name + " "
		End If

		For variable = EachIn m_values
			op:+variable.ConvToString() + " "
		Next
		op = op[..op.Length - 1]
		Return op
	End Method

	Rem
		bbdoc: Get the Identifier as a string.
		returns: The Identifier contents converted to a string.
		about: Here for complete-ness, simply calls #ConvToString.
	End Rem
	Method ValueAsString:String()
		Return ConvToString()
	End Method

'#end region (Field accessors)

'#region Value handling

	Rem
		bbdoc: Get a value at an index.
		returns: The value in the identifier's list at the given index, or Null if it could not be retrieved.
		about: The index is zero-based.
	End Rem
	Method GetValueAtIndex:TVariable(index:Int)
		If m_values <> Null And index > - 1 And index < m_values.Count()
			Return TVariable(m_values.ValueAtIndex(index))
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the number of values.
		returns: The number of values the Identifier contains.
	End Rem
	Method GetValueCount:Int()
		If m_values <> Null
			Return m_values.Count()
		End If
		Return 0
	End Method

	Rem
		bbdoc: Add a value to the Identifier.
		returns: True for success, or False for failure.
		about: NOTE: This will set the given value's parent.
	End Rem
	Method AddValue:Int(value:TVariable)
		If m_values <> Null And value <> Null
			m_values.AddLast(value)
			value.SetParent(Self)
			Return True
		End If
		Return False
	End Method

'#end region (Value handling)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:TIdentifier()
		Local clone:TIdentifier
		clone = New TIdentifier.CreateByData(m_name)
		For Local variable:TVariable = EachIn m_values
			clone.AddValue(variable.Copy())
		Next
		Return clone
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should serialize the values' name (the Identifier's name is always read/written to the stream).
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		Local variable:TVariable

		If tv = True
			stream.WriteByte(TV_STRING)
		End If
		WriteLString(stream, m_name)

		If m_values = Null
			stream.WriteInt(0)
		Else
			stream.WriteInt(m_values.Count())
			For variable = EachIn m_values
				variable.Serialize(stream, True, name)
			Next
		End If
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should deserialize the values' name (the Identifier's name is always read/written to the stream).
	End Rem
	Method DeSerialize:TIdentifier(stream:TStream, tv:Int = True, name:Int = False)
		Local count:Int, n:Int

		If tv = True
			stream.ReadByte()
		End If
		m_name = ReadLString(stream)

		count = stream.ReadInt()
		If count > 0
			For n = 0 To count - 1
				DeSerializeUniversal(stream, name)
			Next
		End If

		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("identifier").
	End Rem
	Function ReportType:String()
		Return "identifier"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_IDEN).
	End Rem
	Function GetTVType:Int()
		Return TV_IDEN
	End Function

End Type

Rem
	bbdoc: The VariableMap type.
EndRem
Type TVariableMap Extends TObjectMap

	Rem
		bbdoc: Create a new VariableMap.
		returns: The new VariableMap (itself).
	End Rem
	Method Create:TVariableMap()
		Return Self
	End Method

'#region Collections

	Rem
		bbdoc: Insert a variable into the map.
		returns: True if the variable was added, or False if it was not (the variable's name is Null).
	End Rem
	Method InsertVariable:Int(variable:TVariable)
		If variable.GetName() <> Null
			_Insert(variable.GetName(), variable)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Remove a variable by the name given.
		returns: True if a variable was removed, or False if it was not (the map contains no variable with the name given).
		about: The name is case-sensitive.
	End Rem
	Method RemoveVariableByName:Int(name:String)
		Return _Remove(name)
	End Method

	Rem
		bbdoc: Get a variable from the map by its name.
		returns: The variable object, or if the variable was not found, Null.
		about: The name is case-sensitive.
	End Rem
	Method GetVariableByName:TVariable(name:String)
		Return TVariable(_ValueByKey(name))
	End Method

'#end region (Collections)

End Type


Enabled alpha testing with masked textures.

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_CCW

		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=_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)
		Local vy#=WorldConfig.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
		glLoadIdentity
		glLoadMatrixf camera._lastprojection.ToPtr()
		glScalef -1,1,-1

		glMatrixMode GL_MODELVIEW
		glLoadMatrixf camera._lastmodelview.ToPtr()

		' Temporary hack
		glGetFloatv GL_PROJECTION_MATRIX,camera._lastprojection._m
		camera._lastfrustum=TFrustum.Extract(camera._lastmodelview,camera._lastprojection)

		_currentdata._projection=camera._lastprojection
	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)
		glDisable GL_ALPHA_TEST

		Local ambient#[]=[WorldConfig.AmbientRed/255.0,WorldConfig.AmbientGreen/255.0,WorldConfig.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 WorldConfig.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

			If texture._flags&TEXTURE_ALPHA or texture._flags&TEXTURE_MASKED
				glEnable GL_ALPHA_TEST
			Else
				glDisable GL_ALPHA_TEST
			EndIf

			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 _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
		_currentdata._modelviewproj=matrix.Multiply(_currentdata._projection)
	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

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_CCW

		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=_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)
		Local vy#=WorldConfig.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
		glLoadIdentity
		glLoadMatrixf camera._lastprojection.ToPtr()
		glScalef -1,1,-1

		glMatrixMode GL_MODELVIEW
		glLoadMatrixf camera._lastmodelview.ToPtr()

		' Temporary hack
		glGetFloatv GL_PROJECTION_MATRIX,camera._lastprojection._m
		camera._lastfrustum=TFrustum.Extract(camera._lastmodelview,camera._lastprojection)

		_currentdata._projection=camera._lastprojection
	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)
		glDisable GL_ALPHA_TEST

		Local ambient#[]=[WorldConfig.AmbientRed/255.0,WorldConfig.AmbientGreen/255.0,WorldConfig.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 WorldConfig.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

			If texture._flags&TEXTURE_ALPHA
				glEnable GL_ALPHA_TEST
			Else
				glDisable GL_ALPHA_TEST
			EndIf

			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 _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
		_currentdata._modelviewproj=matrix.Multiply(_currentdata._projection)
	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