
'Used for intersection
Friend Structure ImpactPoint
	Dim HitPoint As Vector3
	Dim Normal As Vector3
	Dim ItemIndex As Integer
End Structure

'A class that allow loading and saving the world's state.
Friend Class WorldClass
	Friend CompleteFilePath As String
	Friend FileName As String
	Friend FolderPath As String
	
	'Used for any game specific information
	Friend Class Tag
		Friend Attribute As String
		Friend Value As String
		Friend Sub New(ByVal NewAttribute As String, ByVal NewValue As String)
			Attribute = NewAttribute
			Value = NewValue
		End Sub
	End Class
	Friend Class TriangleBuffer
		Friend Tri As Triangle
		Friend Normal As Vector3
		Friend InverseABSystem As Matrix3
		Friend BX As Single
		Friend CX As Single
		Friend CZ As Single
		Friend Sub New(ByVal NewTri As Triangle)
			Dim AB As Vector3
			Dim AC As Vector3
			Dim ABDir As Vector3
			Dim ACDir As Vector3
			Dim LocalTriangle As Triangle
			Dim ABSystem As Matrix3
			Tri = NewTri
			AB = ToVector3(Tri.A, Tri.B)
			AC = ToVector3(Tri.A, Tri.C)
			ABDir = NormalVector3(AB)
			ACDir = NormalVector3(AC)
			Normal = NormalVector3(CrosProduct(ABDir, ACDir))
			ABSystem = MakeMatrix3(ABDir, Normal, CrosProduct(Normal, ABDir))
			InverseABSystem = TransposeMatrix3(ABSystem)
			LocalTriangle = MakeTriangle(MakeVector3(0,0,0), MulVecMat3(AB, InverseABSystem), MulVecMat3(AC, InverseABSystem))
			BX = LocalTriangle.B.X
			CX = LocalTriangle.C.X
			CZ = LocalTriangle.C.Z
		End Sub
	End Class
	Friend Class Model
		Friend FileName As String
		Friend ModelRef As Integer 'Reference number to engine's models
		Friend GeometryBuffer As New List(Of TriangleBuffer)
		Friend UseCount As Integer 'The number of items using it from the last time it was counted
		Friend Sub New(ByVal NewFileName As String)
			FileName = NewFileName
		End Sub
		Friend Sub BufferGeometry()
			GeometryBuffer.Clear()
			If ModelRef > 0 Then
				Dim P As Integer
				Dim T As Integer
				For P = 0 to frmMain.DGE.Model_GetNumberOfParts(ModelRef) - 1
					If frmMain.DGE.Model_Part_GetMaxDetailLevel(ModelRef, P) >= 2 Then 'Only use parts from the highest detail level
						For T = 0 to frmMain.DGE.Model_Part_GetTriangleCount(ModelRef, P) - 1
							Dim Tri As Triangle
							frmMain.DGE.Model_Part_Vertice_GetPos_OutV3(ModelRef, P, T, 0) : Tri.A = GetVector3FromMatrixBuffer() : RE()
							frmMain.DGE.Model_Part_Vertice_GetPos_OutV3(ModelRef, P, T, 1) : Tri.B = GetVector3FromMatrixBuffer() : RE()
							frmMain.DGE.Model_Part_Vertice_GetPos_OutV3(ModelRef, P, T, 2) : Tri.C = GetVector3FromMatrixBuffer() : RE()
							GeometryBuffer.Add(New TriangleBuffer(Tri))
						Next T
					End If
				Next P
			End If
		End Sub
	End Class
	Friend Models As New List(Of Model)
	
	Friend Enum ItemType
		Geometry_Ghost 'Just showing a model
		Geometry_RigidBody_Static 'With a rigid body that don't move
		Geometry_RigidBody_Dynamic 'With a rigid body that move freely in the physical simulation
		SpawnLocation
		NavigationPoint
		Collectable
		Node 'The model is only seen during debug mode
	End Enum
	
	Friend Class Item
		Friend ModelIndex As Integer 'Index to Models
		Friend ItemType As ItemType
		Friend InstanceRef as Integer 'Reference number to engine's visual instances
		Friend Tags As New List(Of Tag) 'A dictionary of tags
		Private Selected As Boolean 'Let it be affected by modifications that affect selected items
		Private HighLighted As Boolean 'A tool is hovering over it
		Friend Locked As Boolean 'Only allow it to be selected by name because it is a large part of the world's geometry
		Friend Position As Vector3
		Friend XAxis As Vector3
		Friend YAxis As Vector3
		Friend ZAxis As Vector3
		Friend Sub New(ByVal NewModelIndex As Integer, ByVal NewLocked As Boolean, ByVal NewSelected As Boolean, ByVal NewPosition As Vector3, ByVal NewAxisSystem As Matrix3)
			ModelIndex = NewModelIndex
			Locked = NewLocked
			Selected = NewSelected
			Position = NewPosition
			XAxis = NewAxisSystem.XAxis
			YAxis = NewAxisSystem.YAxis
			ZAxis = NewAxisSystem.ZAxis
			Update()
		End Sub
		Friend Sub SetSelected(NewSelected As Boolean)
			Selected = NewSelected
			Update()
		End Sub
		Friend Function GetSelected() As Boolean
			GetSelected = Selected
		End Function
		Friend Sub SetHighLighted(NewHighLighted As Boolean)
			HighLighted = NewHighLighted
			Update()
		End Sub
		Friend Function GetHighLighted() As Boolean
			GetHighLighted = HighLighted
		End Function
		Friend Sub Update()
			If InstanceRef > 0 Then
				If HighLighted Then
					If Selected Then
						frmMain.DGE.Instance_SetColor(InstanceRef, 0, 2, 1, 1) : RE()
					Else
						frmMain.DGE.Instance_SetColor(InstanceRef, 1.7, 1.7, 1.7, 1) : RE()
					End If
				Else
					If Selected Then
						frmMain.DGE.Instance_SetColor(InstanceRef, 0, 2, 0, 1) : RE()
					Else
						frmMain.DGE.Instance_SetColor(InstanceRef, 1, 1, 1, 1) : RE()
					End If
				End If
				frmMain.DGE.Instance_SetPosition(InstanceRef, Position.X, Position.Y, Position.Z) : RE()
				frmMain.DGE.Instance_SetXAxis(InstanceRef, XAxis.X, XAxis.Y, XAxis.Z) : RE()
				frmMain.DGE.Instance_SetYAxis(InstanceRef, YAxis.X, YAxis.Y, YAxis.Z) : RE()
				frmMain.DGE.Instance_SetZAxis(InstanceRef, ZAxis.X, ZAxis.Y, ZAxis.Z) : RE()
			End If
		End Sub
	End Class
	Friend Items As New List(Of Item)
	
	Friend Sub New(ByVal NewFileName As String, ByVal LoadExisting As Boolean)
		CompleteFilePath = NewFileName 'Folders and filename
		FileName = FileNameWithoutPath(NewFileName) 'Only filename
		FolderPath = FolderPathFromFileName(NewFileName) 'Only folders
		If LoadExisting Then
			Load(NewFileName)
		End If
	End Sub
	
	Friend Sub SetFileName(ByVal NewFileName As String)
		CompleteFilePath = NewFileName 'Folders and filename
		FileName = FileNameWithoutPath(NewFileName) 'Only filename
		FolderPath = FolderPathFromFileName(NewFileName) 'Only folders
	End Sub
	
	Private Sub SetCurrentDirectory()
		InsertStringToEngine(FolderPath) : frmMain.DGE.Engine_SetCurrentDirectory_InSB : RE()
	End Sub
	
	'Pre condition: FileName don't contain any slash or dot around the filename.
	'Post condition: Returns the index iof the model matching FileName in Models or -1 if not found.
	Friend Function GetModelIndexByName(ByVal FileName As String)
		Dim I As Integer
		Dim UpperName As String
		UpperName = DetToUpper(FileName)
		For I = 0 to Models.Count - 1
			If DetToUpper(Models(I).FileName) = UpperName Then 'Case insensitive name matching
				Return I
			End If
		Next I
		Return -1
	End Function
	
	'Pre condition: FileName don't contain any slash or dot around the filename.
	'Side effect: Loads the model using FileName and adds it to Models.
	Friend Sub AddModel(ByVal FileName As String)
		If GetModelIndexByName(FileName) > -1 Then
			MsgBox("The model " & FileName & " is already loaded.", MsgBoxStyle.OkOnly, "Already loaded!")
		Else
			Models.Add(New Model(FileName))
			SetCurrentDirectory()
			Models(Models.Count - 1).ModelRef = LoadModel(FileName)
			Models(Models.Count - 1).BufferGeometry()
		End If
	End Sub
	
	'Side effect: Removes the model at Index and any items that are using it.
	Friend Sub DeleteModel(Index As Integer)
		Dim I As Integer
		
		'Handle references to models
		For I = Items.Count - 1 to 0 Step -1
			If Items(I).ModelIndex = Index Then
				DeleteItem(I) 'Remove items that are using the model
			ElseIf Items(I).ModelIndex > Index Then
				Items(I).ModelIndex = Items(I).ModelIndex - 1 'Shift every index after the removed model
			End If
		Next I
		
		'Remove the model
		SafeDeleteModel(Models(Index).ModelRef)
		Models.RemoveAt(Index)
	End Sub

	'Pre condition: ModelIndex refer to the element in Models to create an item with
	Friend Sub AddItem(ByVal ModelIndex As Integer, ByVal Locked As Boolean, ByVal Selected As Boolean, ByVal NewPosition As Vector3, ByVal NewAxisSystem As Matrix3)
		If ModelIndex < 0 or ModelIndex > Models.Count - 1 Then
			MsgBox("Select a model from the list for your new item.", MsgBoxStyle.OkOnly, "Nothing to place!")
		Else
			Items.Add(New Item(ModelIndex, Locked, Selected, NewPosition, NewAxisSystem))
			Items(Items.Count - 1).InstanceRef = frmMain.DGE.Instance_Create(Models(ModelIndex).ModelRef) : RE()
			Items(Items.Count - 1).Update()
			frmMain.DGE.Instance_SetAutoDetailLevel(Items(Items.Count - 1).InstanceRef, True)
		End If
	End Sub
	
	'Side effect: Removes the item at Index.
	Friend Sub DeleteItem(Index As Integer)
		SafeDeleteInstance(Items(Index).InstanceRef)
		Items.RemoveAt(Index)
	End Sub
	
	Private Function GetTable(ByRef DB As Database, ByVal Name As String) As Integer
		'Try to get the existing table and create the missing table without rows if it does not exist
		GetTable = DB.AddTable(Name, ExistingAction.ReturnExistingIndex)
	End Function
	
	Private Function GetColumn(ByRef DB As Database, ByRef TableIndex As Integer, ByVal Name As String, ByVal DefaultValue As String) As Integer
		'Try to get the existing column and create the missing column filled with the default value if it does not exist
		GetColumn = DB.AddColumn(TableIndex, Name, DefaultValue, ExistingAction.ReturnExistingIndex)
	End Function
	
	'Side effect: Loads the world from FileName
	Friend Sub Load(ByVal FileName As String)
		Dim I As Integer
		Dim DB As New Database()
			Dim TableIndex_Models As Integer
				Dim ColumnIndex_FileName As Integer
			Dim TableIndex_Items As Integer
				Dim ColumnIndex_ModelIndex As Integer
				Dim ColumnIndex_Locked As Integer
				Dim ColumnIndex_Pos_X As Integer
				Dim ColumnIndex_Pos_Y As Integer
				Dim ColumnIndex_Pos_Z As Integer
				Dim ColumnIndex_XAxis_X As Integer
				Dim ColumnIndex_XAxis_Y As Integer
				Dim ColumnIndex_XAxis_Z As Integer
				Dim ColumnIndex_YAxis_X As Integer
				Dim ColumnIndex_YAxis_Y As Integer
				Dim ColumnIndex_YAxis_Z As Integer
			Dim TableIndex_Tags As Integer
				Dim ColumnIndex_ItemIndex As Integer
				Dim ColumnIndex_Attribute As Integer
				Dim ColumnIndex_Value As Integer
		
		'Load the database
		If Not(DB.LoadFromFile(FileName)) Then Exit Sub
		
		'Get tables
		TableIndex_Models = GetTable(DB, "Models")
			ColumnIndex_FileName = GetColumn(DB, TableIndex_Models, "FileName", "")
		TableIndex_Items = GetTable(DB, "Items")
			ColumnIndex_ModelIndex = GetColumn(DB, TableIndex_Items, "ModelIndex", "0")
			ColumnIndex_Locked = GetColumn(DB, TableIndex_Items, "Locked", "N")
			ColumnIndex_Pos_X = GetColumn(DB, TableIndex_Items, "Pos_X", "0")
			ColumnIndex_Pos_Y = GetColumn(DB, TableIndex_Items, "Pos_Y", "0")
			ColumnIndex_Pos_Z = GetColumn(DB, TableIndex_Items, "Pos_Z", "0")
			ColumnIndex_XAxis_X = GetColumn(DB, TableIndex_Items, "XAxis_X", "1")
			ColumnIndex_XAxis_Y = GetColumn(DB, TableIndex_Items, "XAxis_Y", "0")
			ColumnIndex_XAxis_Z = GetColumn(DB, TableIndex_Items, "XAxis_Z", "0")
			ColumnIndex_YAxis_X = GetColumn(DB, TableIndex_Items, "YAxis_X", "0")
			ColumnIndex_YAxis_Y = GetColumn(DB, TableIndex_Items, "YAxis_Y", "1")
			ColumnIndex_YAxis_Z = GetColumn(DB, TableIndex_Items, "YAxis_Z", "0")
		TableIndex_Tags = GetTable(DB, "Tags")
			ColumnIndex_ItemIndex = GetColumn(DB, TableIndex_Tags, "ItemIndex", "0")
			ColumnIndex_Attribute = GetColumn(DB, TableIndex_Tags, "Attribute", "")
			ColumnIndex_Value = GetColumn(DB, TableIndex_Tags, "Value", "")
		
		'Remove existing engine content
		UnloadEngineContent()
		
		'Clear data structures
		ClearData()
		
		'Garbage collect
		frmMain.DGE.Engine_RemoveUnusedResources() : RE()
		
		'Fill data structures
		For I = 0 to DB.GetTableRows(TableIndex_Models) - 1
			Models.Add(New Model(DB.GetCell(TableIndex_Models, ColumnIndex_FileName, I)))
		Next I
		For I = 0 to DB.GetTableRows(TableIndex_Items) - 1
			Dim Pos As Vector3
			Dim XAxis As Vector3
			Dim YAxis As Vector3
			Dim ZAxis As Vector3
			Pos = MakeVector3(DBTC_ToSingle(DB.GetCell(TableIndex_Items, ColumnIndex_Pos_X, I)), DBTC_ToSingle(DB.GetCell(TableIndex_Items, ColumnIndex_Pos_Y, I)), DBTC_ToSingle(DB.GetCell(TableIndex_Items, ColumnIndex_Pos_Z, I)))
			XAxis = NormalVector3(MakeVector3(DBTC_ToSingle(DB.GetCell(TableIndex_Items, ColumnIndex_XAxis_X, I)), DBTC_ToSingle(DB.GetCell(TableIndex_Items, ColumnIndex_XAxis_Y, I)), DBTC_ToSingle(DB.GetCell(TableIndex_Items, ColumnIndex_XAxis_Z, I))))
			YAxis = NormalVector3(MakeVector3(DBTC_ToSingle(DB.GetCell(TableIndex_Items, ColumnIndex_YAxis_X, I)), DBTC_ToSingle(DB.GetCell(TableIndex_Items, ColumnIndex_YAxis_Y, I)), DBTC_ToSingle(DB.GetCell(TableIndex_Items, ColumnIndex_YAxis_Z, I))))
			ZAxis = NormalVector3(CrosProduct(XAxis, YAxis))
			Items.Add(New Item(DBTC_ToInteger(DB.GetCell(TableIndex_Items, ColumnIndex_ModelIndex, I)), DBTC_ToBoolean(DB.GetCell(TableIndex_Items, ColumnIndex_Locked, I)), False, Pos, MakeMatrix3(XAxis, YAxis, ZAxis)))
		Next I
		For I = 0 to DB.GetTableRows(TableIndex_Tags) - 1
			Dim ItemIndex As Integer
			ItemIndex = DBTC_ToInteger(DB.GetCell(TableIndex_Tags, ColumnIndex_ItemIndex, I))
			Items(ItemIndex).Tags.Add(New Tag(DB.GetCell(TableIndex_Tags, ColumnIndex_Attribute, I), DB.GetCell(TableIndex_Tags, ColumnIndex_Value, I)))
		Next I
		
		'Load engine content
		LoadEngineContent()
	End Sub
	
	'Side effect: Saves the content of the class to CompleteFilePath that were given in the world's constructor
	Friend Sub Save()
		Const SavedDecimals As Integer = 8
		Dim I As Integer
		Dim J As Integer
		Dim RowA As Integer
		Dim RowB As Integer
		Dim DB As New Database()
			Dim TableIndex_Models As Integer
				Dim ColumnIndex_FileName As Integer
			Dim TableIndex_Items As Integer
				Dim ColumnIndex_ModelIndex As Integer
				Dim ColumnIndex_Locked As Integer
				Dim ColumnIndex_Pos_X As Integer
				Dim ColumnIndex_Pos_Y As Integer
				Dim ColumnIndex_Pos_Z As Integer
				Dim ColumnIndex_XAxis_X As Integer
				Dim ColumnIndex_XAxis_Y As Integer
				Dim ColumnIndex_XAxis_Z As Integer
				Dim ColumnIndex_YAxis_X As Integer
				Dim ColumnIndex_YAxis_Y As Integer
				Dim ColumnIndex_YAxis_Z As Integer
			Dim TableIndex_Tags As Integer
				Dim ColumnIndex_ItemIndex As Integer
				Dim ColumnIndex_Attribute As Integer
				Dim ColumnIndex_Value As Integer
		
		'Create tables
		TableIndex_Models = DB.AddTable("Models", ExistingAction.ReturnExistingIndex)
			ColumnIndex_FileName = DB.AddColumn(TableIndex_Models, "FileName", "", ExistingAction.ReturnExistingIndex)
		TableIndex_Items = DB.AddTable("Items", ExistingAction.ReturnExistingIndex)
			ColumnIndex_ModelIndex = DB.AddColumn(TableIndex_Items, "ModelIndex", "-1", ExistingAction.ReturnExistingIndex)
			ColumnIndex_Locked = DB.AddColumn(TableIndex_Items, "Locked", "N", ExistingAction.ReturnExistingIndex)
			ColumnIndex_Pos_X = DB.AddColumn(TableIndex_Items, "Pos_X", "0", ExistingAction.ReturnExistingIndex)
			ColumnIndex_Pos_Y = DB.AddColumn(TableIndex_Items, "Pos_Y", "0", ExistingAction.ReturnExistingIndex)
			ColumnIndex_Pos_Z = DB.AddColumn(TableIndex_Items, "Pos_Z", "0", ExistingAction.ReturnExistingIndex)
			ColumnIndex_XAxis_X = DB.AddColumn(TableIndex_Items, "XAxis_X", "1", ExistingAction.ReturnExistingIndex)
			ColumnIndex_XAxis_Y = DB.AddColumn(TableIndex_Items, "XAxis_Y", "0", ExistingAction.ReturnExistingIndex)
			ColumnIndex_XAxis_Z = DB.AddColumn(TableIndex_Items, "XAxis_Z", "0", ExistingAction.ReturnExistingIndex)
			ColumnIndex_YAxis_X = DB.AddColumn(TableIndex_Items, "YAxis_X", "0", ExistingAction.ReturnExistingIndex)
			ColumnIndex_YAxis_Y = DB.AddColumn(TableIndex_Items, "YAxis_Y", "1", ExistingAction.ReturnExistingIndex)
			ColumnIndex_YAxis_Z = DB.AddColumn(TableIndex_Items, "YAxis_Z", "0", ExistingAction.ReturnExistingIndex)
		TableIndex_Tags = DB.AddTable("Tags", ExistingAction.ReturnExistingIndex)
			ColumnIndex_ItemIndex = DB.AddColumn(TableIndex_Tags, "ItemIndex", "-1", ExistingAction.ReturnExistingIndex)
			ColumnIndex_Attribute = DB.AddColumn(TableIndex_Tags, "Attribute", "", ExistingAction.ReturnExistingIndex)
			ColumnIndex_Value = DB.AddColumn(TableIndex_Tags, "Value", "", ExistingAction.ReturnExistingIndex)
		
		'Fill the model table
		For I = 0 To Models.Count - 1
			RowA = DB.AddRow(TableIndex_Models, "")
			DB.SetCell(TableIndex_Models, ColumnIndex_FileName, RowA, Models(I).FileName)
		Next I
		
		'Fill the item table
		For I = 0 To Items.Count - 1
			RowA = DB.AddRow(TableIndex_Items, "")
			DB.SetCell(TableIndex_Items, ColumnIndex_ModelIndex, RowA, DBTC_FromInteger(Items(I).ModelIndex))
			DB.SetCell(TableIndex_Items, ColumnIndex_Locked, RowA, DBTC_FromBoolean(Items(I).Locked))
			DB.SetCell(TableIndex_Items, ColumnIndex_Pos_X, RowA, DBTC_FromSingle(Items(I).Position.X, SavedDecimals))
			DB.SetCell(TableIndex_Items, ColumnIndex_Pos_Y, RowA, DBTC_FromSingle(Items(I).Position.Y, SavedDecimals))
			DB.SetCell(TableIndex_Items, ColumnIndex_Pos_Z, RowA, DBTC_FromSingle(Items(I).Position.Z, SavedDecimals))
			DB.SetCell(TableIndex_Items, ColumnIndex_XAxis_X, RowA, DBTC_FromSingle(Items(I).XAxis.X, SavedDecimals))
			DB.SetCell(TableIndex_Items, ColumnIndex_XAxis_Y, RowA, DBTC_FromSingle(Items(I).XAxis.Y, SavedDecimals))
			DB.SetCell(TableIndex_Items, ColumnIndex_XAxis_Z, RowA, DBTC_FromSingle(Items(I).XAxis.Z, SavedDecimals))
			DB.SetCell(TableIndex_Items, ColumnIndex_YAxis_X, RowA, DBTC_FromSingle(Items(I).YAxis.X, SavedDecimals))
			DB.SetCell(TableIndex_Items, ColumnIndex_YAxis_Y, RowA, DBTC_FromSingle(Items(I).YAxis.Y, SavedDecimals))
			DB.SetCell(TableIndex_Items, ColumnIndex_YAxis_Z, RowA, DBTC_FromSingle(Items(I).YAxis.Z, SavedDecimals))
			
			'Normalize the list of tags in each item into one merged table refering to the owning items by index
			For J = 0 To Items(I).Tags.Count - 1
				RowB = DB.AddRow(TableIndex_Tags,"")
				DB.SetCell(TableIndex_Tags, ColumnIndex_ItemIndex, RowB, DBTC_FromInteger(I))
				DB.SetCell(TableIndex_Tags, ColumnIndex_Attribute, RowB, Items(I).Tags(J).Attribute)
				DB.SetCell(TableIndex_Tags, ColumnIndex_Value, RowB, Items(I).Tags(J).Value)
			Next J
		Next I
		
		'Save the database
		DB.SaveToFile(CompleteFilePath, False)
	End Sub
	
	Friend Sub UnloadEngineContent()
		Dim I As Integer
		
		'Delete visual instances in the engine
		For I = 0 to Items.Count - 1
			SafeDeleteInstance(Items(I).InstanceRef)
		Next I
		
		'Delete their models in the engine
		For I = 0 to Models.Count - 1
			SafeDeleteModel(Models(I).ModelRef)
		Next I
	End Sub
	
	Friend Sub LoadEngineContent()
		Dim I As Integer
		
		'Load models using their names
		For I = 0 to Models.Count - 1
			SetCurrentDirectory()
			Models(I).ModelRef = LoadModel(Models(I).FileName)
			Models(I).BufferGeometry()
		Next I
		
		'Create visual representation of the items
		For I = 0 to Items.Count - 1
			Items(I).InstanceRef = frmMain.DGE.Instance_Create(Models(Items(I).ModelIndex).ModelRef) : RE()
			Items(I).Update()
			frmMain.DGE.Instance_SetAutoDetailLevel(Items(I).InstanceRef, True)
		Next I
	End Sub
	
	Friend Sub SetSelection(ByVal Selected As Boolean)
		Dim I As Integer
		For I = 0 to Items.Count - 1
			Items(I).SetSelected(Selected)
			Items(I).Update()
		Next I
	End Sub
	
	Friend Sub MoveSelected(Offset As Vector3)
		Dim I As Integer
		For I = 0 to Items.Count - 1
			If Items(I).GetSelected Then
				Items(I).Position = AddVector3(Items(I).Position, Offset)
				Items(I).Update()
			End If
		Next I
	End Sub
	
	Private Sub ClearData()
		Models.Clear()
		Items.Clear()
	End Sub
	
	'Postcondition: Returns true if something intersected
	Friend Function VisualLineIntersection(ByRef ContactOutput As ImpactPoint, ByVal InputLine As Line3) As Boolean
		Dim I As Integer
		VisualLineIntersection = False
		For I = 0 to Items.Count - 1
			If ItemLineIntersection(ContactOutput, InputLine, Items(I).Position, MakeMatrix3(Items(I).XAxis, Items(I).YAxis, Items(I).ZAxis), Items(I).ModelIndex) Then
				InputLine.EndPoint = ContactOutput.HitPoint
				ContactOutput.ItemIndex = I 'Remember the item's index
				VisualLineIntersection = True
			End If
		Next I
	End Function
	
	'Precondition: AxisSystem is orthogonal
	'Postcondition: Returns true if the item intersected
	Friend Function ItemLineIntersection(ByRef ContactOutput As ImpactPoint, ByRef InputLine As Line3, ByRef Pos As Vector3, ByRef AxisSystem As Matrix3, ByVal ModelIndex As Integer) As Boolean
		ItemLineIntersection = False
		
		Dim BoundMin As Vector3
		Dim BoundMax As Vector3
		frmMain.DGE.Model_GetBoundingBoxMinimum_OutV3(Models(ModelIndex).ModelRef) : BoundMin = GetVector3FromMatrixBuffer() : RE()
		frmMain.DGE.Model_GetBoundingBoxMaximum_OutV3(Models(ModelIndex).ModelRef) : BoundMax = GetVector3FromMatrixBuffer() : RE()
		
		Dim ModelSpaceLine As Line3
		ModelSpaceLine.StartPoint = MulVecMat3(SubVector3(InputLine.StartPoint, Pos), TransposeMatrix3(AxisSystem))
		ModelSpaceLine.EndPoint = MulVecMat3(SubVector3(InputLine.EndPoint, Pos), TransposeMatrix3(AxisSystem))
		
		If (ModelSpaceLine.StartPoint.X > BoundMax.X andalso ModelSpaceLine.EndPoint.X > BoundMax.X) orelse
		   (ModelSpaceLine.StartPoint.X < BoundMin.X andalso ModelSpaceLine.EndPoint.X < BoundMin.X) orelse
		   (ModelSpaceLine.StartPoint.Y > BoundMax.Y andalso ModelSpaceLine.EndPoint.Y > BoundMax.Y) orelse
		   (ModelSpaceLine.StartPoint.Y < BoundMin.Y andalso ModelSpaceLine.EndPoint.Y < BoundMin.Y) orelse
		   (ModelSpaceLine.StartPoint.Z > BoundMax.Z andalso ModelSpaceLine.EndPoint.Z > BoundMax.Z) orelse
		   (ModelSpaceLine.StartPoint.Z < BoundMin.Z andalso ModelSpaceLine.EndPoint.Z < BoundMin.Z) Then
			'Both points are outside on the same side
		Else
			'¤¤¤¤ Try to reuse code here
			Dim StartInside As Boolean
			StartInside = ModelSpaceLine.StartPoint.X > BoundMin.X andalso ModelSpaceLine.StartPoint.X < BoundMax.X andalso ModelSpaceLine.StartPoint.Y > BoundMin.Y andalso ModelSpaceLine.StartPoint.Y < BoundMax.Y andalso ModelSpaceLine.StartPoint.Z > BoundMin.Z andalso ModelSpaceLine.StartPoint.Z < BoundMax.Z
			Dim EndInside As Boolean
			EndInside = ModelSpaceLine.EndPoint.X > BoundMin.X andalso ModelSpaceLine.EndPoint.X < BoundMax.X andalso ModelSpaceLine.EndPoint.Y > BoundMin.Y andalso ModelSpaceLine.EndPoint.Y < BoundMax.Y andalso ModelSpaceLine.EndPoint.Z > BoundMin.Z andalso ModelSpaceLine.EndPoint.Z < BoundMax.Z
			
			'Bounding box intersection test
			If Models(ModelIndex).GeometryBuffer.Count > 12 andalso Not(StartInside) andalso Not(EndInside) Then
				Const Treshold As Single = 0.0001 'Prevent gaps from rounding errors
				Dim MaxXRatio As Single
				Dim MaxXPoint As Vector3
				MaxXRatio = InverseLerp(ModelSpaceLine.StartPoint.X, ModelSpaceLine.EndPoint.X, BoundMax.X)
				MaxXPoint = LerpVector3(ModelSpaceLine.StartPoint, ModelSpaceLine.EndPoint, MaxXRatio)
				If MaxXPoint.Y >= BoundMin.Y - Treshold andalso MaxXPoint.Y <= BoundMax.Y + Treshold andalso MaxXPoint.Z >= BoundMin.Z - Treshold andalso MaxXPoint.Z <= BoundMax.Z + Treshold Then
					'Intersect at MaxX
				Else
					Dim MinXRatio As Single
					Dim MinXPoint As Vector3
					MinXRatio = InverseLerp(ModelSpaceLine.StartPoint.X, ModelSpaceLine.EndPoint.X, BoundMin.X)
					MinXPoint = LerpVector3(ModelSpaceLine.StartPoint, ModelSpaceLine.EndPoint, MinXRatio)
					If MinXPoint.Y >= BoundMin.Y - Treshold andalso MinXPoint.Y <= BoundMax.Y + Treshold andalso MinXPoint.Z >= BoundMin.Z - Treshold andalso MinXPoint.Z <= BoundMax.Z + Treshold Then
						'Intersect at MinX
					Else
						Dim MaxYRatio As Single
						Dim MaxYPoint As Vector3
						MaxYRatio = InverseLerp(ModelSpaceLine.StartPoint.Y, ModelSpaceLine.EndPoint.Y, BoundMax.Y)
						MaxYPoint = LerpVector3(ModelSpaceLine.StartPoint, ModelSpaceLine.EndPoint, MaxYRatio)
						If MaxYPoint.X >= BoundMin.X - Treshold andalso MaxYPoint.X <= BoundMax.X + Treshold andalso MaxYPoint.Z >= BoundMin.Z - Treshold andalso MaxYPoint.Z <= BoundMax.Z + Treshold Then
							'Intersect at MaxY
						Else
							Dim MinYRatio As Single
							Dim MinYPoint As Vector3
							MinYRatio = InverseLerp(ModelSpaceLine.StartPoint.Y, ModelSpaceLine.EndPoint.Y, BoundMin.Y)
							MinYPoint = LerpVector3(ModelSpaceLine.StartPoint, ModelSpaceLine.EndPoint, MinYRatio)
							If MinYPoint.X >= BoundMin.X - Treshold andalso MinYPoint.X <= BoundMax.X + Treshold andalso MinYPoint.Z >= BoundMin.Z - Treshold andalso MinYPoint.Z <= BoundMax.Z + Treshold Then
								'Intersect at MinY
							Else
								Dim MaxZRatio As Single
								Dim MaxZPoint As Vector3
								MaxZRatio = InverseLerp(ModelSpaceLine.StartPoint.Z, ModelSpaceLine.EndPoint.Z, BoundMax.Z)
								MaxZPoint = LerpVector3(ModelSpaceLine.StartPoint, ModelSpaceLine.EndPoint, MaxZRatio)
								If MaxZPoint.X >= BoundMin.X - Treshold andalso MaxZPoint.X <= BoundMax.X + Treshold andalso MaxZPoint.Y >= BoundMin.Y - Treshold andalso MaxZPoint.Y <= BoundMax.Y + Treshold Then
									'Intersect at MaxZ
								Else
									Dim MinZRatio As Single
									Dim MinZPoint As Vector3
									MinZRatio = InverseLerp(ModelSpaceLine.StartPoint.Z, ModelSpaceLine.EndPoint.Z, BoundMin.Z)
									MinZPoint = LerpVector3(ModelSpaceLine.StartPoint, ModelSpaceLine.EndPoint, MinZRatio)
									If MinZPoint.X >= BoundMin.X - Treshold andalso MinZPoint.X <= BoundMax.X + Treshold andalso MinZPoint.Y >= BoundMin.Y - Treshold andalso MinZPoint.Y <= BoundMax.Y + Treshold Then
										'Intersect at MinZ
									Else
										'Not intersecting with the box
										Exit Function
									End If
								End If
							End If
						End If
					End If
				End If
			End If
			
			Dim T As Integer
			For T = 0 to Models(ModelIndex).GeometryBuffer.Count - 1
				If LineTriangleIntersection(ContactOutput, ModelSpaceLine, Models(ModelIndex).GeometryBuffer(T)) Then
					'Prevent hitting other triangles behind it
					ModelSpaceLine.EndPoint = ContactOutput.HitPoint
					
					'Convert result back from model space to world space
					ContactOutput.HitPoint = AddVector3(MulVecMat3(ContactOutput.HitPoint, AxisSystem), Pos)
					ContactOutput.Normal = MulVecMat3(ContactOutput.Normal, AxisSystem)
					
					'Prevent hitting other items behind it
					InputLine.EndPoint = ContactOutput.HitPoint
					
					'We have an intersection
					ItemLineIntersection = True
				End If
			Next T
		End If
	End Function
	
	Friend Function NormalFromTriangle(ByRef Tri As Triangle) As Vector3
		NormalFromTriangle = NormalVector3(CrosProduct(ToVector3(Tri.A, Tri.C), ToVector3(Tri.A, Tri.B)))
	End Function
	
	'Precondition: A is zero and B is right of A
	Friend Function PointInsideTriangle(ByRef Point As Vector3, ByVal B_X As Single, ByVal C_X As Single, ByVal C_Z As Single) As Boolean
		Dim DownRatio As Single
		If C_Z = 0 Then
			'Can't intersect another line
			PointInsideTriangle = False
		Else
			DownRatio = Point.Z / C_Z
			If Point.Z < 0 Then
				'Above
				PointInsideTriangle = False
			ElseIf Point.X > Lerp(B_X, C_X, DownRatio) Then
				'Right
				PointInsideTriangle = False
			ElseIf Point.X < C_X * DownRatio Then
				'Left
				PointInsideTriangle = False
			Else
				'Inside
				PointInsideTriangle = True
			End If
		End If
	End Function
	
	Friend Function LineTriangleIntersection(ByRef ContactOutput As ImpactPoint, ByVal InputLine As Line3, ByRef InputTriangle As TriangleBuffer) As Boolean
		'Make a local line
		Dim LocalLine As Line3 'Relative to A and ABSystem
		LocalLine.StartPoint = MulVecMat3(SubVector3(InputLine.StartPoint, InputTriangle.Tri.A), InputTriangle.InverseABSystem)
		LocalLine.EndPoint = MulVecMat3(SubVector3(InputLine.EndPoint, InputTriangle.Tri.A), InputTriangle.InverseABSystem)
		
		If LocalLine.StartPoint.Y > 0 and LocalLine.EndPoint.Y < 0 Then
			Dim Ratio As Single
			Dim LocalIntersection As Vector3
			
			Ratio = InverseLerp(LocalLine.StartPoint.y, LocalLine.EndPoint.Y, 0)
			LocalIntersection = LerpVector3(LocalLine.StartPoint, LocalLine.EndPoint, Ratio)
			
			If PointInsideTriangle(LocalIntersection, InputTriangle.BX, InputTriangle.CX, InputTriangle.CZ) Then
				ContactOutput.HitPoint = LerpVector3(InputLine.StartPoint, InputLine.EndPoint, Ratio)
				ContactOutput.Normal = InputTriangle.Normal
				LineTriangleIntersection = True
			Else
				'The point that intersected the plane was outside of the triangle
				LineTriangleIntersection = False
			End If
		Else
			'No intersection because the line must go from outside to inside
			LineTriangleIntersection = False
		End If
	End Function
	
	Friend Sub CountModelUse()
		Dim I As Integer
		Dim M As Integer
		For M = 0 to Models.Count - 1
			Models(M).UseCount = 0
		Next M
		For I = 0 to Items.Count - 1
			M = Items(I).ModelIndex
			If M >= 0 and M < Models.Count Then
				Models(M).UseCount = Models(M).UseCount + 1
			End If
		Next I
	End Sub
	
	Friend Sub RemoveUnusedModels()
		'Count use
		CountModelUse()
		
		'Remove unused models
		Dim M As Integer
		For M = Models.Count - 1 to 0 Step -1
			If Models(M).UseCount <= 0 Then
				DeleteModel(M)
			End If
		Next M
	End Sub
End Class
