93 lines
3.0 KiB
GDScript
93 lines
3.0 KiB
GDScript
@tool
|
|
extends Camera3D
|
|
|
|
@export var home: Node3D
|
|
@export var target: Node3D
|
|
@export var portal_mesh: MeshInstance3D:
|
|
set(v):
|
|
portal_mesh = v
|
|
if v != null:
|
|
_portal_mesh_aabb = v.get_aabb()
|
|
|
|
var _portal_mesh_aabb: AABB
|
|
|
|
@export var player_camera: Node3D
|
|
@export_range(0, 1, 0.01) var frame_width: float = 0.1
|
|
|
|
@export_tool_button("Grab editor camera", "Camera3D")
|
|
var _tb_grab_editor_camera: Callable = _grab_editor_camera
|
|
|
|
|
|
func _ready() -> void:
|
|
if home == null:
|
|
var p = get_parent()
|
|
while p is not Node3D:
|
|
p = p.get_parent()
|
|
home = p
|
|
print(name + ": home = " + home.name)
|
|
|
|
var env: Environment = get_world_3d().environment.duplicate()
|
|
env.tonemap_mode = Environment.TONE_MAPPER_LINEAR
|
|
env.tonemap_exposure = 1
|
|
self.environment = env
|
|
|
|
if not Engine.is_editor_hint():
|
|
assert(check())
|
|
|
|
var vp = SubViewport.new()
|
|
vp.name = home.name + "_SubViewport"
|
|
vp.size = get_viewport().size
|
|
await get_parent().ready
|
|
get_parent().add_child(vp)
|
|
|
|
self.reparent(vp)
|
|
|
|
var mat = portal_mesh.mesh.surface_get_material(0) as ShaderMaterial
|
|
mat.set_shader_parameter("albedo", vp.get_texture())
|
|
|
|
|
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
|
func _process(delta: float) -> void:
|
|
if not check():
|
|
return
|
|
|
|
var player_to_home = home.global_transform.affine_inverse() * player_camera.global_transform
|
|
var flipped = player_to_home.rotated(Vector3.UP, PI)
|
|
var relative_to_target = target.global_transform * flipped
|
|
|
|
self.global_transform = relative_to_target
|
|
|
|
self.near = _get_near_plane(_portal_mesh_aabb)
|
|
|
|
|
|
func _get_near_plane(_mesh_aabb: AABB) -> float:
|
|
var corner_1:Vector3 = target.to_global(Vector3(_mesh_aabb.position.x, _mesh_aabb.position.y, 0))
|
|
var corner_2:Vector3 = target.to_global(Vector3(_mesh_aabb.position.x + _mesh_aabb.size.x, _mesh_aabb.position.y, 0))
|
|
var corner_3:Vector3 = target.to_global(Vector3(_mesh_aabb.position.x + _mesh_aabb.size.x, _mesh_aabb.position.y + _mesh_aabb.size.y, 0))
|
|
var corner_4:Vector3 = target.to_global(Vector3(_mesh_aabb.position.x, _mesh_aabb.position.y + _mesh_aabb.size.y, 0))
|
|
|
|
# Calculate the distance along the exit camera forward vector at which each of the portal corners projects
|
|
var camera_forward:Vector3 = -global_transform.basis.z.normalized()
|
|
|
|
var d_1:float = (corner_1 - global_position).dot(camera_forward)
|
|
var d_2:float = (corner_2 - global_position).dot(camera_forward)
|
|
var d_3:float = (corner_3 - global_position).dot(camera_forward)
|
|
var d_4:float = (corner_4 - global_position).dot(camera_forward)
|
|
|
|
# The near clip distance is the shortest distance which still contains all the corners
|
|
return max(0.01, min(d_1, d_2, d_3, d_4) - frame_width)
|
|
|
|
func check() -> bool:
|
|
return target != null and \
|
|
player_camera != null and \
|
|
home != null and \
|
|
portal_mesh != null and \
|
|
_portal_mesh_aabb != null
|
|
|
|
func _grab_editor_camera() -> void:
|
|
if Engine.is_editor_hint():
|
|
print("Grabbing editor camera")
|
|
player_camera = EditorInterface.get_editor_viewport_3d(0).get_camera_3d()
|
|
else:
|
|
print("Not in editor - cannot grab editor camera!")
|