@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!")