
Option Explicit

'¤¤¤¤ Set NeedToRedraw to true when something has changed

MustInherit Friend Class Comp_Abstract
	Friend Location as Rect
	Friend Visible As Boolean
	
	Friend Sub New()
		Visible = True
		Location = MakeRect(0,0,100,100) 'Just show it somewhere in case that the application won't placed it
	End Sub
	
	'Abstract methods
	MustOverride Friend Sub Draw(ByVal Active As Boolean, ByVal OutputDrawSurface As integer)
	MustOverride Friend Sub Event_MouseDown(ByVal e As AxDFPGELib._DDFPGEEvents_MouseDownEvent)
	MustOverride Friend Sub Event_MouseUp(e As AxDFPGELib._DDFPGEEvents_MouseUpEvent)
	MustOverride Friend Sub Event_MouseMove(ByVal e As AxDFPGELib._DDFPGEEvents_MouseMoveEvent)
	MustOverride Friend Sub Resize()
End Class

Module ComponentManager
	'Tools
	Friend Enum ToolAction
		SelectionCursor
		CreateItem
		MoveSelected
		RotateSelected
		ScaleSelected
	End Enum
	Friend Tool as ToolAction
	
	'Components
	Friend ActiveComponent As Integer = 3
	Dim Components As New List(Of Comp_Abstract)
	Friend Toolbar As New Comp_Toolbar 'Toolbar
	Friend UpperLeftSurface As New Comp_Surface 'Top view
	Friend UpperRightSurface As New Comp_Surface 'Right view
	Friend LowerLeftSurface As New Comp_Surface 'Back view
	Friend LowerRightSurface As New Comp_Surface 'Perspective view
	
	'Selection
	Friend HoverItem As Integer = -1
	
	'Placing
	Friend Structure Location
		Dim Pos as Vector3
		Dim AxisSystem As Matrix3
		Dim Exist As Boolean
	End Structure
	Friend Place As Location
	
	'Rendering
	Friend DrawSurface_Final As Integer
	Friend NeedToRedraw As Boolean
	
	'Cameras
	Friend Structure MovingCamera
		Dim CameraRef As Integer
		Dim Pos As Vector3
		Dim Longitude As Single
		Dim Lattitude As Single
	End Structure
	Friend Structure FlatCamera
		Dim CameraRef As Integer
		Dim Pos As Vector3
		Dim Direction As Vector3
		Dim Up As Vector3
		Dim Zoom As Single
	End Structure
	Friend TopCamera As FlatCamera
	Friend RightCamera As FlatCamera
	Friend BackCamera As FlatCamera
	Friend PerspectiveCamera As MovingCamera
	
	Friend Enum CompIndex
		UpperLeft
		UpperRight
		LowerLeft
		LowerRight
		Toolbar
	End Enum
	
	Friend Sub Components_Init()
		'Get the version specific ID to the final draw surface
		DrawSurface_Final = frmMain.DGE.DrawSurface_GetFinalOutput : RE()
		
		'Connect cameras to views
		UpperLeftSurface.CameraRef = TopCamera.CameraRef
		UpperRightSurface.CameraRef = RightCamera.CameraRef
		LowerLeftSurface.CameraRef = BackCamera.CameraRef
		LowerRightSurface.CameraRef = PerspectiveCamera.CameraRef
		
		'Create the tools in the toolbar after loading it's resources
		Toolbar.InitTools()
		
		'Add components to the system
		Components.Add(UpperLeftSurface)
		Components.Add(UpperRightSurface)
		Components.Add(LowerLeftSurface)
		Components.Add(LowerRightSurface)
		Components.Add(Toolbar)
		'New components must be added at the same location in CompIndex
		
		'Tell the engine to not show the result until Engine_UpdateSwapChain is called
		frmMain.DGE.Engine_SetAutomaticSwapChain(False)
	End Sub
	
	Friend Sub Components_Redraw()
		'Only redraw if something has changed to save power
		If NeedToRedraw Then
			'Clear the background
			frmMain.DGE.DrawSurface_FillWithColor(DrawSurface_Final, 0.2, 0.23, 0.23, 1) : RE()
			
			'Redraw the components
			Dim I As Integer
			For I = 0 to Components.Count - 1
				If Components(I).Visible Then
					Components(I).Draw(I = ActiveComponent, DrawSurface_Final)
				End If
			Next I
			
			'Show the result
			frmMain.DGE.Engine_UpdateSwapChain()
			
			'Remember that we have updated the image
			NeedToRedraw = False
		End If
	End Sub
	
	Friend Sub Components_Resize()
		NeedToRedraw = True
		Dim I As Integer
		For I = 0 to Components.Count - 1
			Components(I).Resize()
		Next I
	End Sub
	
	Dim LastMousePos As Vector2
	
	Friend Function GetLineFromMousePosition(ByRef CameraRef As Integer, ByVal WindowWidth As Integer, ByVal WindowHegiht As Integer, ByVal MouseX As Integer, ByVal MouseY As Integer, ByVal NearClipDistance As Single, ByVal FarClipDistance As Single) As Line3
		Dim CameraSystem As Matrix4
		Dim CameraAxisSystem As Matrix3
		Dim FOV As Single
		Dim ZeroToOne As Vector2 '0..1
		Dim NegToPos As Vector2 '-1..1
		Dim XScale As Single
		Dim YScale As Single
		Dim UnscaledPosition As Vector3
		frmMain.DGE.Camera_Get4x4System_OutM4(PerspectiveCamera.CameraRef) : CameraSystem = GetMatrix4FromMatrixBuffer() : RE()
		CameraAxisSystem = Matrix4ToMatrix3(CameraSystem)
		FOV = frmMain.DGE.Camera_GetFieldOfViewInRadians(PerspectiveCamera.CameraRef) : RE()
		If frmMain.DGE.Camera_IsFieldOfViewVertical(PerspectiveCamera.CameraRef) Then
			'Vertical
			YScale = System.Math.Tan(FOV / 2)
			XScale = (YScale / WindowHegiht) * WindowWidth
		Else
			'Horizontal
			XScale = System.Math.Tan(FOV / 2)
			YScale = (XScale / WindowWidth) * WindowHegiht
		End If
		ZeroToOne = MakeVector2(MouseX / WindowWidth, MouseY / WindowHegiht)
		NegToPos = MakeVector2((ZeroToOne.X * 2) - 1, 1 - (ZeroToOne.Y * 2))
		UnscaledPosition = MakeVector3(NegToPos.x * XScale, NegToPos.y * YScale, 1)
		GetLineFromMousePosition.StartPoint = AddVector3(Vector4ToVector3(CameraSystem.WAxis), MulVecMat3(MulVector3(UnscaledPosition, NearClipDistance), CameraAxisSystem))
		GetLineFromMousePosition.EndPoint = AddVector3(Vector4ToVector3(CameraSystem.WAxis), MulVecMat3(MulVector3(UnscaledPosition, FarClipDistance), CameraAxisSystem))
	End Function
	
	Friend Sub Components_MouseDown(e As AxDFPGELib._DDFPGEEvents_MouseDownEvent)
		If Not(World Is Nothing) Then
			Dim I As Integer
			For I = Components.Count - 1 to 0 Step -1
				If Rect_Inside(Components(I).Location, e.x, e.y) Then
					Components(I).Event_MouseDown(e)
					Exit for
				End If
			Next I
			Place3DCursor(e.x, e.y)
			ToolDown(e.button)
			NeedToRedraw = True
			LastMousePos = MakeVector2(e.x, e.y)
		End If
	End Sub
	
	Friend Sub Components_MouseMove(e As AxDFPGELib._DDFPGEEvents_MouseMoveEvent)
		Dim I As Integer
		For I = Components.Count - 1 to 0 Step -1
			If Rect_Inside(Components(I).Location, e.x, e.y) Then
				If e.button = 0 then
					ActiveComponent = I
				End If
				Components(I).Event_MouseMove(e)
				If I <> CompIndex.Toolbar Then
					Toolbar.HoverIndex = -1
				End If
				Exit for
			End If
		Next I
		
		Place3DCursor(e.x, e.y)
		If e.button > 0 Then
			ToolDrag(e.button, MakeVector2(e.x - LastMousePos.x, e.y - LastMousePos.y))
		End If
		LastMousePos = MakeVector2(e.x, e.y)
	End Sub
	
	Friend Sub Components_MouseUp(e As AxDFPGELib._DDFPGEEvents_MouseUpEvent)
		Dim I As Integer
		For I = Components.Count - 1 to 0 Step -1
			If Rect_Inside(Components(I).Location, e.x, e.y) Then
				Components(I).Event_MouseUp(e)
				Exit for
			End If
		Next I
	End Sub
	
	Dim CursorX As Integer
	Dim CursorY As Integer
	Friend CursorNeedUpdate As Boolean
	
	Private Sub Place3DCursor(X As Integer, Y As Integer)
		If CursorX <> X or CursorY <> Y Then
			CursorNeedUpdate = True
		End If
		CursorX = X
		CursorY = Y
	End Sub
	
	Private Sub SetHoverItem(ItemIndex As Integer)
		If Not(World is Nothing) and HoverItem <> ItemIndex Then
			If HoverItem > -1 and HoverItem < World.Items.Count Then
				World.Items(HoverItem).SetHighLighted(False)
			End If
			If ItemIndex > -1 and ItemIndex < World.Items.Count andalso Not(World.Items(ItemIndex).Locked) Then
				World.Items(ItemIndex).SetHighLighted(True)
				HoverItem = ItemIndex
			Else
				HoverItem = -1
			End If
		End If
	End Sub
	
	Private Sub ZoomFlatCamera(ByRef Camera As FlatCamera, ByVal Delta As Integer)
		If Delta > 0 Then
			Camera.zoom = Clamp(1,Camera.zoom * 2, 1966080)
		ElseIf Delta < 0 Then
			Camera.zoom = Clamp(1,Camera.zoom / 2, 1966080)
		End If
	End Sub
	
	Friend Sub Components_Scroll(e As System.Windows.Forms.MouseEventArgs)
		Select Case ActiveComponent
		Case CompIndex.UpperLeft 'Top
			ZoomFlatCamera(TopCamera, e.Delta)
		Case CompIndex.UpperRight 'Right
			ZoomFlatCamera(RightCamera, e.Delta)
		Case CompIndex.LowerLeft 'Back
			ZoomFlatCamera(BackCamera, e.Delta)
		End Select
		NeedToRedraw = True
	End Sub
	
	Private Sub ToolDrag(Button As Short, Offset As Vector2)
		If Not(World Is Nothing) Then
			Select Case ActiveComponent
			Case CompIndex.UpperLeft 'Top
				If Button = 1 Then
					Select Case Tool
					Case ToolAction.SelectionCursor
						
					Case ToolAction.CreateItem
						
					Case ToolAction.MoveSelected
						'Move selected items
						World.MoveSelected(MakeVector3(Offset.x * 2 / TopCamera.Zoom, 0, -Offset.y * 2 / TopCamera.Zoom))
						'¤¤¤¤ Try to apply everything in one step to reduce rounding errors.
						'¤¤¤¤ The initial location from when the mouse was pressed down must be stored in each item.
					Case ToolAction.RotateSelected
						
					End Select
				ElseIf Button = 2 then
					'Panorate camera
					TopCamera.Pos.x = TopCamera.Pos.x - (Offset.x * 2 / TopCamera.Zoom)
					TopCamera.Pos.z = TopCamera.Pos.z + (Offset.y * 2 / TopCamera.Zoom)
				End If
			Case CompIndex.UpperRight 'Right
				If Button = 1 Then
					Select Case Tool
					Case ToolAction.SelectionCursor
						
					Case ToolAction.CreateItem
						
					Case ToolAction.MoveSelected
						'Move selected items
						World.MoveSelected(MakeVector3(0, -Offset.y * 2 / RightCamera.Zoom, Offset.x * 2 / RightCamera.Zoom))
						'¤¤¤¤ Try to apply everything in one step to reduce rounding errors.
						'¤¤¤¤ The initial location from when the mouse was pressed down must be stored in each item.
					Case ToolAction.RotateSelected
						
					End Select
				ElseIf Button = 2 then
					'Panorate camera
					RightCamera.Pos.z = RightCamera.Pos.z - (Offset.x * 2 / RightCamera.Zoom)
					RightCamera.Pos.y = RightCamera.Pos.y + (Offset.y * 2 / RightCamera.Zoom)
				End If
			Case CompIndex.LowerLeft 'Back
				If Button = 1 Then
					Select Case Tool
					Case ToolAction.SelectionCursor
						
					Case ToolAction.CreateItem
						
					Case ToolAction.MoveSelected
						'Move selected items
						World.MoveSelected(MakeVector3(Offset.x * 2 / BackCamera.Zoom, -Offset.y * 2 / BackCamera.Zoom, 0))
						'¤¤¤¤ Try to apply everything in one step to reduce rounding errors.
						'¤¤¤¤ The initial location from when the mouse was pressed down must be stored in each item.
					Case ToolAction.RotateSelected
						
					End Select
				ElseIf Button = 2 then
					'Panorate camera
					BackCamera.Pos.x = BackCamera.Pos.x - (Offset.x * 2 / BackCamera.Zoom)
					BackCamera.Pos.y = BackCamera.Pos.y + (Offset.y * 2 / BackCamera.Zoom)
				End If
			Case CompIndex.LowerRight 'Perspective
				If Button = 2 then
					'Rotate camera
					PerspectiveCamera.Longitude = PerspectiveCamera.Longitude + (Offset.X * 0.005)
					PerspectiveCamera.Lattitude = PerspectiveCamera.Lattitude + (Offset.Y * -0.005)
				End If
			End Select
			NeedToRedraw = True
		End If
	End Sub
	
	Friend Sub Components_TimeEvents()
		If Not(World Is Nothing) Then
			Update3DCursor()
			Select Case ActiveComponent
			Case CompIndex.UpperLeft 'Top
			
			Case CompIndex.UpperRight 'Right
			
			Case CompIndex.LowerLeft 'Back
			
			Case CompIndex.LowerRight 'Perspective
				'Get the camera's axis system
				Dim CameraSystem As Matrix4
				frmMain.DGE.Camera_Get4x4System_OutM4(PerspectiveCamera.CameraRef) : CameraSystem = GetMatrix4FromMatrixBuffer() : RE()
				
				'Get keyboard input
				Dim Turn As Single
				Dim Forward As Single
				Dim Sideway As Single
				Dim Up As Single
				Dim MoveSpeed As Single
				Turn = KeyDown_ZeroToOne(Keys.Right) - KeyDown_ZeroToOne(Keys.Left)
				Forward = (KeyDown_ZeroToOne(Keys.Up) + KeyDown_ZeroToOne(Keys.W)) - (KeyDown_ZeroToOne(Keys.Down) + KeyDown_ZeroToOne(Keys.S))
				Sideway = KeyDown_ZeroToOne(Keys.D) - KeyDown_ZeroToOne(Keys.A)
				Up = KeyDown_ZeroToOne(Keys.E) - KeyDown_ZeroToOne(Keys.Q)
				If Turn <> 0 or Forward <> 0 or Sideway <> 0 or Up <> 0 Then
					NeedToRedraw = True
					CursorNeedUpdate = True
				End If
				If KeyDown_Truth(Keys.LShiftKey) or KeyDown_Truth(Keys.RShiftKey) Then
					MoveSpeed = 0.5 'Move faster
				Else
					MoveSpeed = 0.05
				End If
				
				If Not(KeyDown_Truth(Keys.LControlKey) or KeyDown_Truth(Keys.RControlKey)) Then 'Prevent moving from keyboard shortcuts
					'Move the camera
					PerspectiveCamera.Longitude = PerspectiveCamera.Longitude + (Turn * 0.02)
					PerspectiveCamera.Pos = AddVector3(PerspectiveCamera.Pos, MulVector3(Vector4ToVector3(CameraSystem.ZAxis), Forward * MoveSpeed))
					PerspectiveCamera.Pos = AddVector3(PerspectiveCamera.Pos, MulVector3(Vector4ToVector3(CameraSystem.XAxis), Sideway * MoveSpeed))
					PerspectiveCamera.Pos = AddVector3(PerspectiveCamera.Pos, MulVector3(Vector4ToVector3(CameraSystem.YAxis), Up * MoveSpeed))
				End If
			End Select
		End If
	End Sub
	
	Private Sub ToolDown(Button As Short)
		If Not(World Is Nothing) Then
			Select Case ActiveComponent
			Case CompIndex.UpperLeft 'Top
				If Button = 1 Then
					Select Case Tool
					Case ToolAction.SelectionCursor
						
					Case ToolAction.CreateItem
						
					End Select
				End If
			Case CompIndex.UpperRight 'Right
				If Button = 1 Then
					Select Case Tool
					Case ToolAction.SelectionCursor
						
					Case ToolAction.CreateItem
						
					End Select
				End If
			Case CompIndex.LowerLeft 'Back
				If Button = 1 Then
					Select Case Tool
					Case ToolAction.SelectionCursor
						
					Case ToolAction.CreateItem
						
					End Select
				End If
			Case CompIndex.LowerRight 'Perspective
				If Button = 1 Then
					Select Case Tool
					Case ToolAction.SelectionCursor
						'Get key states
						Dim AddSelection As Boolean
						Dim SubSelection As Boolean
						AddSelection = KeyDown_Truth(Keys.LControlKey) or KeyDown_Truth(Keys.RControlKey) 'Ctrl keys
						SubSelection = KeyDown_Truth(Keys.Menu) 'Alt key
						
						'Deselect everything if we are not adding or removing items from the selection
						If Not(AddSelection or SubSelection) Then
							World.SetSelection(False) 'Deselect everything
						End If
						
						'Use the 3D cursor's item index
						If HoverItem > -1 and HoverItem < World.Items.Count Then
							If SubSelection Then
								World.Items(HoverItem).SetSelected(False)
							Else
								World.Items(HoverItem).SetSelected(True)
							End If
						End If
					Case ToolAction.CreateItem
						If Place.Exist Then
							'Place on another item
							World.AddItem(frmMain.lstModels.SelectedIndex, False, True, Place.Pos, Place.AxisSystem)
						Else
							'Place at world center to allow placing world geometry
							World.AddItem(frmMain.lstModels.SelectedIndex, False, True, MakeVector3(0, 0, 0), MakeAxisSystem_Polar(0, 0))
						End If
						frmMain.ItemUpdate()
					End Select
				End If
			End Select
			NeedToRedraw = True
		End If
	End Sub
	
	Private Sub Update3DCursor()
		Dim L As Line3
		Dim Contact As ImpactPoint
		Dim MadeContact As Boolean
		If CursorNeedUpdate Then
			If ActiveComponent = CompIndex.LowerRight and Not(World is Nothing) Then
				Select Case Tool
				Case ToolAction.SelectionCursor, ToolAction.CreateItem
					L = GetLineFromMousePosition(PerspectiveCamera.CameraRef, Rect_GetWidth(LowerRightSurface.Location), Rect_GetHeight(LowerRightSurface.Location), CursorX - Components(ActiveComponent).Location.Left, CursorY - Components(ActiveComponent).Location.Top, NearClip, FarClip)
					MadeContact = World.VisualLineIntersection(Contact, L)
				Case Else
					MadeContact = False
				End Select
			Else
				MadeContact = False
			End If
			If MadeContact Then
				'Remember where things will be placed
				Place.Exist = True
				Dim CameraSystem As Matrix4
				Dim CameraDir As Vector3
				frmMain.DGE.Camera_Get4x4System_OutM4(PerspectiveCamera.CameraRef) : CameraSystem = GetMatrix4FromMatrixBuffer() : RE()
				CameraDir = NormalVector3(MakeVector3(CameraSystem.ZAxis.X, 0, CameraSystem.ZAxis.Z))
				If frmMain.chRoundAngle.Checked Then
					CameraDir = RoundDirection(CameraDir)
				End If
				If frmMain.chUpFromNormal.Checked Then
					Place.AxisSystem = MakeAxisSystem_DirectedUp(Contact.Normal, CameraDir)
				Else
					Place.AxisSystem = MakeAxisSystem_Orthogonalized(CameraDir, MakeVector3(0, 1, 0))
				End If
				Dim ModelIndex As Integer
				ModelIndex = frmMain.lstModels.SelectedIndex
				If frmMain.chBottomPlace.Checked and Tool = ToolAction.CreateItem and ModelIndex > -1 andalso World.Models(ModelIndex).ModelRef > 0 Then
					Dim BottomHeight As Single
					frmMain.DGE.Model_GetBoundingBoxMinimum_OutV3(World.Models(ModelIndex).ModelRef) : BottomHeight = frmMain.DGE.GetY1() : RE()
					Place.Pos = SubVector3(Contact.HitPoint, MulVector3(Place.AxisSystem.YAxis, BottomHeight))
				Else
					Place.Pos = Contact.HitPoint
				End If
				
				'Show the model
				If ModelIndex > -1 and Tool = ToolAction.CreateItem Then
					'The item's model
					frmMain.DGE.Instance_ReplaceModel(frmMain.Instance_Cursor, World.Models(ModelIndex).ModelRef) : RE()
				Else
					'Default cursor model
					frmMain.DGE.Instance_ReplaceModel(frmMain.Instance_Cursor, frmMain.Model_Cursor) : RE()
				End If
				
				'Show the cursor on the intersection point
				frmMain.DGE.Instance_SetPosition(frmMain.Instance_Cursor, Place.Pos.X, Place.Pos.Y, Place.Pos.Z) : RE()
				SetInstanceAxisSystem(frmMain.Instance_Cursor, Place.AxisSystem)
				SetVisibility(frmMain.Instance_Cursor, ActiveComponent = CompIndex.LowerRight)
				
				'Highlight the item
				SetHoverItem(Contact.ItemIndex)
			Else
				'Hide the 3D cursor
				SetVisibility(frmMain.Instance_Cursor, False)
				
				'Don't have an item highilghted
				SetHoverItem(-1)
				
				'We can't place things like this
				Place.Exist = False
			End If
			NeedToRedraw = True
			CursorNeedUpdate = False
		End If
	End Sub
End Module
