extends MeshInstance3D
class_name MyPortal

## My own portal implementation

@export var exit_portal: MyPortal

#preload("res://scripts/my_portal_material.tres")
const MY_PORTAL_MATERIAL = preload("uid://cwobk5ik5k5aj")

var _viewport: SubViewport
var _camera: Camera3D = Camera3D.new()
var _main_camera: Camera3D = null

var exit_scale: int = 1  # This is not really needed I think

func _ready() -> void:
	assert(exit_portal != null)
	
	_main_camera = get_viewport().get_camera_3d()
	
	# NOTE: In case the exit portal is in a different world, better put the viewport and camera 
	# onto IT, rather than me (?)
	_viewport = SubViewport.new()
	_viewport.name = name + "_Viewport"
	_viewport.size = get_viewport().size
	add_child(_viewport)
	
	_camera.name = name + "_ExitCamera"
	_viewport.add_child(_camera)
	
	var mat: ShaderMaterial = MY_PORTAL_MATERIAL
	material_override = mat
	mat.set_shader_parameter("albedo", _viewport.get_texture())
	

func _process(delta: float) -> void:
	_camera.global_transform = real_to_exit_transform(_main_camera.global_transform)
	# TODO: Adjust near clip plane to the portal
	_camera.near = _main_camera.near
	_camera.far = _main_camera.far
	_camera.fov = _main_camera.fov
	_camera.keep_aspect = _main_camera.keep_aspect


## Return a new Transform3D relative to the exit portal based on the real Transform3D relative to this portal.
func real_to_exit_transform(real:Transform3D) -> Transform3D:
	# Convert from global space to local space at the entrance (this) portal
	var local:Transform3D = global_transform.affine_inverse() * real
	# Compensate for any scale the entrance portal may have
	var unscaled:Transform3D = local.scaled(global_transform.basis.get_scale())
	# Flip it (the portal always flips the view 180 degrees)
	var flipped:Transform3D = unscaled.rotated(Vector3.UP, PI)
	# Apply any scale the exit portal may have (and apply custom exit scale)
	var exit_scale_vector:Vector3 = exit_portal.global_transform.basis.get_scale()
	var scaled_at_exit:Transform3D = flipped.scaled(Vector3.ONE / exit_scale_vector * exit_scale)
	# Convert from local space at the exit portal to global space
	var local_at_exit:Transform3D = exit_portal.global_transform * scaled_at_exit
	return local_at_exit

## Return a new position relative to the exit portal based on the real position relative to this portal.
func real_to_exit_position(real:Vector3) -> Vector3:
	# Convert from global space to local space at the entrance (this) portal
	var local:Vector3 = global_transform.affine_inverse() * real
	# Compensate for any scale the entrance portal may have
	var unscaled:Vector3 = local * global_transform.basis.get_scale()
	# Apply any scale the exit portal may have (and apply custom exit scale)
	var exit_scale_vector:Vector3 = Vector3(-1, 1, 1) * exit_portal.global_transform.basis.get_scale()
	var scaled_at_exit:Vector3 = unscaled / exit_scale_vector * exit_scale
	# Convert from local space at the exit portal to global space
	var local_at_exit:Vector3 = exit_portal.global_transform * scaled_at_exit
	return local_at_exit

## Return a new direction relative to the exit portal based on the real direction relative to this portal.
func real_to_exit_direction(real:Vector3) -> Vector3:
	# Convert from global to local space at the entrance (this) portal
	var local:Vector3 = global_transform.basis.inverse() * real
	# Compensate for any scale the entrance portal may have
	var unscaled:Vector3 = local * global_transform.basis.get_scale()
	# Flip it (the portal always flips the view 180 degrees)
	var flipped:Vector3 = unscaled.rotated(Vector3.UP, PI)
	# Apply any scale the exit portal may have (and apply custom exit scale)
	var exit_scale_vector:Vector3 = exit_portal.global_transform.basis.get_scale()
	var scaled_at_exit:Vector3 = flipped / exit_scale_vector * exit_scale
	# Convert from local space at the exit portal to global space
	var local_at_exit:Vector3 = exit_portal.global_transform.basis * scaled_at_exit
	return local_at_exit
